为什么组合“之间”与“选择”无法作为应用解析器?

时间:2013-07-26 20:40:05

标签: f# fparsec

据我所知,选择组合器隐式将 pzero 解析器附加到我的解析器列表中,当fparsec无法解析输入流的下一部分时,它应该搜索括号

这是最小的完整代码:

open System
open System.Collections.Generic
open FParsec

type IDL =
    |Library of string * IDL list
    |ImportLib of string
    |ImportAlias of string

let comment : Parser<unit,unit> = pstring "//" >>. skipRestOfLine true >>. spaces
let ws = spaces >>. (opt comment)
let str s = pstring s >>. ws
let identifierString = ws >>. many1Satisfy isLetter .>> ws // [A-z]+
let identifierPath = ws >>. many1Satisfy (fun c -> isLetter c || isDigit c || c = '.' || c = '\\' || c = '/') .>> ws // valid path letters
let keywords = ["importlib"; "IMPORTLIB"; "interface"; "typedef"; "coclass"]
let keywordsSet = new HashSet<string>(keywords)
let isKeyword (set : HashSet<string>) str = set.Contains(str)

let pidentifier set (f : Parser<string, unit>) : Parser<string, unit> =
    let expectedIdentifier = expected "identifier"
    fun stream ->
        let state = stream.State
        let reply = f stream
        if reply.Status <> Ok || not (isKeyword set reply.Result) then
            printf "got id %s\n" reply.Result
            ws stream |> ignore
            reply
        else // result is keyword, so backtrack to before the string
            stream.BacktrackTo(state)
            Reply(Error, expectedIdentifier)

let identifier = pidentifier keywordsSet

let stmt, stmtRef = createParserForwardedToRef()

let stmtList = sepBy1 stmt (str ";")

let importlib =
    str "importlib" >>.
        between (str "(" .>> str "\"") (str "\"" >>. str ")")
            (identifier identifierPath) |>> ImportLib

let importalias =
    str "IMPORTLIB" >>.
        between (str "(") (str ")")
            (identifier identifierString) |>> ImportAlias

let library =
    pipe2
        (str "library" >>. identifier identifierString)
        (between (str "{") (str "}") stmtList)
        (fun lib slist -> Library(lib, slist))

do stmtRef:= choice [importlib; importalias]

let prog =
    ws >>. library .>> ws .>> eof

let s = @"
library ModExpress
{
    importlib(""stdole2.tlb"");
    importlib(""msxml6.dll"");
}"

let test p str =
    match run p str with
    | Success(result, _, _)   -> printfn "Success: %A" result
    | Failure(errorMsg, _, _) -> printfn "Failure: %s" errorMsg

test prog s

System.Console.Read() |> ignore

但是对于输入字符串

library ModExpress
{
        importlib(""stdole2.tlb"");
        importlib(""msxml6.dll"");
}

我收到了以下错误:

Failure: Error in Ln: 6 Col: 1
}
^
Expecting: '//', 'IMPORTLIB' or 'importlib'

1 个答案:

答案 0 :(得分:3)

这里的问题似乎是stmtList解析器是用sepBy1组合器实现的。 sepBy1 stmt sep解析p的一个或多个出现sep分隔(但未结束)sepEndBy1,即在EBNF中:p(sep p)*。当解析器在importlib(“”msxml6.dll“”)之后看到分号时,它需要在空格之后的另一个语句。

如果要在语句列表的末尾选择分号,可以使用sepBy1代替let stmtList = many1 stmt do stmtRef:= choice [importlib; importalias] .>> str ";" ,或者如果您总是想要分号,则可以使用< / p>

{{1}}