使用FParsec解析时如何将单个项映射至OneThing而非LotsOfThings
问题:如何让FParsec解析返回正确的Thing可区分联合类型?
我最近在用FParsec处理解析需求,想要把单个项或者项列表解析后存入一个叫Thing的可区分联合(DU)里。目前的实现代码如下,但遇到个问题——哪怕只有单个项的时候,返回的依然是LotsOfThings类型:
open FParsec 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]
我试过用解析后检查列表长度的方式来处理,但总觉得这种做法不够优雅:
let lotsOfThings = sepBy1 oneThing (str "or") |>> fun l -> if l.Length = 1 then l.[0] else l |> LotsOfThings
正确的实现方式
其实我们可以直接从解析器层面区分单个项和多项的情况,不需要先解析成列表再做判断。核心思路是把解析逻辑拆分为“单个项”加上“零个或多个or+项”的组合,这样就能在解析阶段直接生成对应的Thing类型:
open FParsec type Thing = | OneThing of int | LotsOfThings of Thing list let str s = pstringCI s .>> spaces let one = str "one" |>> fun _ -> OneThing 1 let two = str "two" |>> fun _ -> OneThing 2 let three = str "three" |>> fun _ -> OneThing 3 let oneThing = one <|> two <|> three // 解析"or"后面跟着单个项的结构 let orThing = str "or" >>. oneThing // 组合解析器:先解析第一个项,再解析后续所有的or+项 let thingParser = oneThing .>>. many orThing |>> fun (firstItem, restItems) -> match restItems with | [] -> firstItem // 没有后续项,返回单个OneThing | _ -> LotsOfThings (firstItem :: restItems) // 有后续项,组合成LotsOfThings let test p str = match run p str with | Success(result, _, _) -> printfn "Success: %A" result | Failure(errorMsg, _, _) -> printfn "Failure: %s" errorMsg // 测试案例 test thingParser "one or two" // Success: LotsOfThings [OneThing 1; OneThing 2] test thingParser "one" // Success: OneThing 1 test thingParser "two or three or one" // Success: LotsOfThings [OneThing 2; OneThing 3; OneThing 1]
思路说明
这里没有使用sepBy1(它会强制把单个项也包装成单元素列表),而是拆分了解析逻辑:
- 先解析第一个独立的项;
- 再解析零个或多个以
or开头的后续项; - 最后根据后续项是否存在,直接生成对应的
Thing类型——没有后续项就返回OneThing,有后续项就把所有项组合成列表返回LotsOfThings。
这种方式更贴合解析器的设计思想,避免了事后判断列表长度的“补丁式”处理,代码逻辑也更清晰。
内容的提问来源于stack exchange,提问作者Sean Kearon
相关产品推荐
相关产品推荐

