使用FParsec解析分隔的列表

Sea*_*ron 4 f# 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]
Run Code Online (Sandbox Code Playgroud)

OneThing当列表中只有一个项目时,返回的正确方法是什么?

如果我在返回之前测试列表,我可以这样做,如下所示.但那并没有真正"感觉"正确.

let lotsOfThings = sepBy1 oneThing (str "or") |>> fun l -> if l.Length = 1 then l.[0] else l |> LotsOfThings
Run Code Online (Sandbox Code Playgroud)

上面的LinqPad在这里:http://share.linqpad.net/sd8tpj.linq

rmu*_*unn 5

如果您不喜欢在解析后测试列表长度,那么您可以尝试切换<|>表达式以首先测试单项案例,并使用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
Run Code Online (Sandbox Code Playgroud)

注意使用attempt解析器oneThingOnly.那是因为解析器文档<|>声明(强调原文):

解析器p1 <|> p2首先应用解析器p1.如果p1成功,p1则返回结果.如果p1失败并出现非致命错误且未更改解析器状态,p2则应用解析器.

没有attempt在那里,"一个或两个"将首先尝试解析oneThingOnly,这将消耗"一",然后在"或"上失败,但解析器状态将被更改.该attempt组合子基本上使得一个解析器状态的"书签",试图解析器之前,如果该解析器失败了,它可以追溯到"书签".所以<|>在看到之后会看到一个未改变的解析器状态attempt oneThingOnly,然后会尝试lotsOfThings.

  • 这种答案可能不合适的一种情况是,如果"真实"解析项目中的"一个"(在"of"之后没有任何内容)之类的输入将是有效的.该文本将失败`oneThingOnly`解析器,但也会失败`lotsOfThings`解析器.在大多数情况下,这不是什么大问题,但如果你需要允许那个特定情况,那么你必须在你的`<|>`选项中添加第三项:`oneThing.>>分隔符.>>(notFollowedBy oneThing )`. (3认同)