2

私が理解している限りでは、choiceコンビネータは暗黙的に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の 1 つ以上の出現を解析します。つまり、EBNF: p (sep p)* です。パーサーは、importlib(""msxml6.dll"") の後にセミコロンがある場合、空白の後に別のステートメントがあることを期待しています。psep

ステートメント リストの末尾のセミコロンを省略可能にしたい場合は、sepEndBy1代わりに を使用するsepBy1か、常にセミコロンを必要とする場合は、次のように使用できます。

let stmtList = many1 stmt

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