문제

As far as I understand the choice combinator implicitly appends pzero parser to my parser list and when fparsec fails to parse next part of input stream, it should search for brackets.

Here is minimal complete code:

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

but for the input string

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

I got following error:

Failure: Error in Ln: 6 Col: 1
}
^
Expecting: '//', 'IMPORTLIB' or 'importlib'
도움이 되었습니까?

해결책

It seems that the problem here is that the stmtList parser is implemented with the sepBy1 combinator. sepBy1 stmt sep parses one or more occurrences of p separated (but not ended) by sep, i.e. in EBNF: p (sep p)*. When the parser sees the semicolon after importlib(""msxml6.dll""), it expects another statement after the whitespace.

If you want to make the semicolon at the end of a statement list optional, you could simply use sepEndBy1 instead of sepBy1, or if you always want to require a semicolon, you could use

let stmtList = many1 stmt

do stmtRef:= choice [importlib; importalias] .>> str ";"
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top