使用 FParsec 解析分隔列表
Parsing separated lists with FParsec
我正在尝试解析可能是项目列表或可能只是一个项目的内容。我想把结果放到一个DU中(下面Thing
)。
我处理这个问题的方式如下,但它给了我一个列表,即使列表中只有一个东西。
let test p str =
match run p str with
| Success(result, _, _) -> printfn "Success: %A" result
| Failure(errorMsg, _, _) -> printfn "Failure: %s" errorMsg
type Thing =
| OneThing of int
| LotsOfThings of Thing list
let str s = pstringCI s .>> spaces
let one = str "one" |>> fun x -> OneThing 1
let two = str "two" |>> fun x -> OneThing 2
let three = str "three" |>> fun x -> OneThing 3
let oneThing = (one <|> two <|> three)
let lotsOfThings = sepBy1 oneThing (str "or") |>> LotsOfThings
let lotsFirst = (lotsOfThings <|> oneThing)
test lotsFirst "one or two" // Success: LotsOfThings [OneThing 1; OneThing 2]
test lotsFirst "one" // Success: LotsOfThings [OneThing 1]
当列表中只有一项时 return OneThing
的正确方法是什么?
如果我在 returning 之前测试列表,我可以做到这一点,如下所示。但这并不真的 "feel" 正确。
let lotsOfThings = sepBy1 oneThing (str "or") |>> fun l -> if l.Length = 1 then l.[0] else l |> LotsOfThings
上面的LinqPad在这里:http://share.linqpad.net/sd8tpj.linq
如果你不喜欢在解析后测试列表长度,那么你可以尝试切换你的 <|>
表达式来先测试单项情况,然后使用 notFollowedBy
来确保单项案例与列表不匹配:
let oneThing = (one <|> two <|> three)
let separator = str "or"
let lotsOfThings = sepBy1 oneThing separator |>> LotsOfThings
let oneThingOnly = oneThing .>> (notFollowedBy separator)
let lotsSecond = (attempt oneThingOnly) <|> lotsOfThings
test lotsSecond "one or two" // Success: LotsOfThings [OneThing 1; OneThing 2]
test lotsSecond "one" // Success: OneThing 1
请注意 attempt
解析器与 oneThingOnly
的使用。那是因为 documentation for the <|>
parser 状态(原文强调):
The parser p1 <|> p2
first applies the parser p1
. If p1
succeeds, the result of p1
is returned. If p1
fails with a non‐fatal error and without changing the parser state, the parser p2
is applied.
如果没有 attempt
,"one or two" 将首先尝试使用 oneThingOnly
进行解析,这会消耗 "one",然后在 "or" 上失败],但解析器状态会被改变。 attempt
组合器在尝试解析器之前基本上会生成解析器状态的 "bookmark",如果该解析器失败,它会返回到 "bookmark"。因此 <|>
会在 attempt oneThingOnly
之后看到未更改的解析器状态,然后会尝试 lotsOfThings
.
我正在尝试解析可能是项目列表或可能只是一个项目的内容。我想把结果放到一个DU中(下面Thing
)。
我处理这个问题的方式如下,但它给了我一个列表,即使列表中只有一个东西。
let test p str =
match run p str with
| Success(result, _, _) -> printfn "Success: %A" result
| Failure(errorMsg, _, _) -> printfn "Failure: %s" errorMsg
type Thing =
| OneThing of int
| LotsOfThings of Thing list
let str s = pstringCI s .>> spaces
let one = str "one" |>> fun x -> OneThing 1
let two = str "two" |>> fun x -> OneThing 2
let three = str "three" |>> fun x -> OneThing 3
let oneThing = (one <|> two <|> three)
let lotsOfThings = sepBy1 oneThing (str "or") |>> LotsOfThings
let lotsFirst = (lotsOfThings <|> oneThing)
test lotsFirst "one or two" // Success: LotsOfThings [OneThing 1; OneThing 2]
test lotsFirst "one" // Success: LotsOfThings [OneThing 1]
当列表中只有一项时 return OneThing
的正确方法是什么?
如果我在 returning 之前测试列表,我可以做到这一点,如下所示。但这并不真的 "feel" 正确。
let lotsOfThings = sepBy1 oneThing (str "or") |>> fun l -> if l.Length = 1 then l.[0] else l |> LotsOfThings
上面的LinqPad在这里:http://share.linqpad.net/sd8tpj.linq
如果你不喜欢在解析后测试列表长度,那么你可以尝试切换你的 <|>
表达式来先测试单项情况,然后使用 notFollowedBy
来确保单项案例与列表不匹配:
let oneThing = (one <|> two <|> three)
let separator = str "or"
let lotsOfThings = sepBy1 oneThing separator |>> LotsOfThings
let oneThingOnly = oneThing .>> (notFollowedBy separator)
let lotsSecond = (attempt oneThingOnly) <|> lotsOfThings
test lotsSecond "one or two" // Success: LotsOfThings [OneThing 1; OneThing 2]
test lotsSecond "one" // Success: OneThing 1
请注意 attempt
解析器与 oneThingOnly
的使用。那是因为 documentation for the <|>
parser 状态(原文强调):
The parser
p1 <|> p2
first applies the parserp1
. Ifp1
succeeds, the result ofp1
is returned. Ifp1
fails with a non‐fatal error and without changing the parser state, the parserp2
is applied.
如果没有 attempt
,"one or two" 将首先尝试使用 oneThingOnly
进行解析,这会消耗 "one",然后在 "or" 上失败],但解析器状态会被改变。 attempt
组合器在尝试解析器之前基本上会生成解析器状态的 "bookmark",如果该解析器失败,它会返回到 "bookmark"。因此 <|>
会在 attempt oneThingOnly
之后看到未更改的解析器状态,然后会尝试 lotsOfThings
.