2

据我了解,选择组合器隐式地将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'
4

1 回答 1

3

看来这里的问题是stmtList解析器是用sepBy1组合器实现的。sepBy1 stmt sep解析一个或多个由p分隔(但不结束)的事件sep,即在 EBNF 中:p (sep p)*。当解析器看到 importlib(""msxml6.dll"") 之后的分号时,它期望在空格之后有另一个语句。

如果你想让语句列表末尾的分号可选,你可以简单地使用sepEndBy1而不是sepBy1,或者如果你总是想要一个分号,你可以使用

let stmtList = many1 stmt

do stmtRef:= choice [importlib; importalias] .>> str ";"
于 2013-07-27T10:00:21.877 回答