I'm trying to parse a list of asset types where each asset type potentially has a name. After the list is done I'd like to continue parsing a list of attributes for the asset types, one list for all asset types.
The string I'm trying to parse looks like:
converter named a023, signaltower, powerunit named 23 attributes power, temperature
The parser signature looks like
Parser<((Asset * AssetName option) list * Attribute liste),unit>
I got parsing the assets name, and the attributes separately, the problem arise when I combine the two and list is done, it then fails on the attributes string stating Expecting: 'named'
.
To me it seems that it is trying the opt assetname parser which fails on the attributes string, but I am not sure how to ignore that and move on when the list is "done" (after all the asset name part is optional)..
type AssetName = AssetName of string
let named = str "named" >>. spaces1 >>. word
let assetName = spaces1 >>. (named |>> AssetName)
type Asset = | Converter | Signaltower | Powerunit
let assetType = ["converter"; "signaltower"; "powerunit";] |> Seq.map pstring |> choice
let findAsset = function
| "converter" -> Converter
| "signaltower" -> Signaltower
| "powerunit" -> Powerunit
| _ -> raise <| Exception "Invalid asset type"
let asset = (assetType |>> findAsset) .>>. opt assetName
type Attribute = Attribute of string
let attribute = word |>> Attribute
let attributes = spaces1 >>. str "attributes" >>. spaces1 >>. sepBy attribute commaMaybeSpace
let p = sepBy asset (pchar ',' >>. spaces) .>>. attributes
let r input = run p3 input
r "converter named a023, signaltower, powerunit named 23 attributes power, temperature"
Edit As discussed in a similar answer and also hinted at in the documentation, an optional parser opt p
fails if p
fails after changing its state. Instead, backtracking (restoring the state) should be used, either with the attempt
parser, or with one of the backtracking operators.
Keeping in mind that opt (attempt (p >>. q))
is equivalent to opt (p >>? q)
, try
let assetName = spaces1 >>? (named |>> AssetName)
Not sure if it has some bearing on the issue, but the type of the parser in the example does not match the grammar you are describing. I think you want:
asset-type : | Converter | Signaltower | Powerunit
named-asset : | asset-type | asset-type named ident
asset-list : named-asset,...,named-asset
attributed-asset-list : asset-list attributes ident-list
The following line does not repeat the parser for named-asset.
let p = asset .>>. attributes
// val p : Parser<((Asset * AssetName option) * Attribute list),unit>
You can replace it with
let p = sepBy asset (pchar ',' >>. spaces) .>>. attributes
// val p : Parser<((Asset * AssetName option) list * Attribute list),unit>
"converter named a023, signaltower, powerunit named 23 attributes power, temperature"
|> run p |> printfn "%A"
// Success: ([(Converter, Some (AssetName "a023")); (Signaltower, null);
// (Powerunit, Some (AssetName "23"))],
// [Attribute "power"; Attribute "temperature"])