1

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"
hsulriksen
  • 572
  • 4
  • 10

1 Answers1

1

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"])
kaefer
  • 5,491
  • 1
  • 15
  • 20
  • Thank you, this is close, but it fails if the last asset is just powerunit.. ``` "converter named a023, signaltower, powerunit attributes- power, temperature" |> run p |> printfn "%A" //Failure: //Error in Ln: 1 Col: 46 //converter named a023, signaltower, powerunit attributes power, temperature // ^ //Expecting: 'named' val it : unit = () ``` – hsulriksen May 05 '20 at 12:38
  • @hsulriksen The penny drops. We probably need some [backtracking](https://stackoverflow.com/a/46662400) here, `let assetName = spaces1 >>? (named |>> AssetName)`. – kaefer May 05 '20 at 16:01
  • Thank you! I had tried with >>? and >>.? but at the wrong place (instead of the opt). Now I just need to decode this to some logic in this head of mine. – hsulriksen May 05 '20 at 18:52