我正在尝试解析可能是项目列表的内容,或者可能只是一个项目。我想把结果放到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]
当列表中只有一个项目时,返回OneThing
的正确方法是什么?
如果我在返回之前测试列表,我可以这样做,如下所示。但这并没有真正“感觉”正确。
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
答案 0 :(得分:5)
如果您不喜欢在解析后测试列表长度,那么您可以尝试切换<|>
表达式以首先测试单项案例,并使用notFollowedBy
来确保单个 - item case与列表不匹配:
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州(强调原文):
解析器
p1 <|> p2
首先应用解析器p1
。如果p1
成功,则返回p1
的结果。如果p1
失败且出现非致命错误而未更改解析器状态,则应用解析器p2
。
如果没有attempt
,“一个或两个”将首先尝试使用oneThingOnly
进行解析,这将消耗“一”,然后在“或”上失败,但是解析器状态本来会改变的。在尝试解析器之前,attempt
组合器基本上是解析器状态的“书签”,如果该解析器失败,它将返回“书签”。因此,<|>
会在attempt oneThingOnly
之后看到未更改的解析器状态,然后会尝试lotsOfThings
。