2

この質問は、まず、私の質問と重複していません。実は3つ質問があります。

以下のコードでは、入れ子になっている可能性のある複数行のブロック コメントを解析するパーサーを作成しようとしています。引用された他の質問とは対照的に、再帰関数を使用せずに簡単な方法で問題を解決しようとします(他の投稿への受け入れられた回答を参照してください)。

私が遭遇した最初の問題は、FParsec の skipManyTill パーサーもストリームから end パーサーを消費することでした。そこで、skipManyTillEx (「endp を除く」の Ex ;) ) を作成しました。skipManyTillEx は機能しているようです-少なくとも、fsxスクリプトにも追加した1つのテストケースでは。

しかし、示されているコードでは、「消費せずに成功するパーサーにコンビネーター 'many' が適用されました...」というエラーが表示されます。私の理論は、commentContentパーサーがこのエラーを生成する行であるということです。

ここで、私の質問:

  1. 私が選択したアプローチが機能しない理由はありますか? 1のソリューションは、残念ながら私のシステムではコンパイルできないようで、(ネストされた) 複数行のコメントに対して再帰的な低レベルのパーサーを使用します。
  2. 私が実装した方法に問題がある人はいますskipManyTillExか? skipManyTill私がそれを実装した方法は、主に解析フローを制御する方法の面で、実装されている方法とはある程度異なります。元skipManyTillの では、Reply<_>p および endp の が とともに追跡されstream.StateTagます。対照的に、私の実装では、ステータス コードstream.StateTagのみに依存して、を使用する必要はありませんでした。Reply<_>解析に失敗した場合はskipManyTillEx、ストリームの初期状態に戻り、エラーを報告します。バックトラッキング コードが原因で「多数」のエラーが発生する可能性はありますか? 代わりに何をしなければなりませんか?
  3. (そしてそれが主な質問です)-この「多くの...」エラーメッセージが消えるようにパーサーを修正する方法を誰かが見ていますか?

コードは次のとおりです。

#r @"C:\hgprojects\fparsec\Build\VS11\bin\Debug\FParsecCS.dll"
#r @"C:\hgprojects\fparsec\Build\VS11\bin\Debug\FParsec.dll"

open FParsec

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

let Show (s : string) : string =
    printfn "%s" s
    s

let test p i =
    i |> Show |> testParser p |> ignore

////////////////////////////////////////////////////////////////////////////////////////////////
let skipManyTillEx (p : Parser<_,_>) (endp : Parser<_,_>) : Parser<unit,unit> =
    fun stream ->
        let tryParse (p : Parser<_,_>) (stm : CharStream<unit>) : bool = 
            let spre = stm.State
            let reply = p stream
            match reply.Status with
            | ReplyStatus.Ok -> 
                stream.BacktrackTo spre
                true
            | _ -> 
                stream.BacktrackTo spre
                false
        let initialState = stream.State
        let mutable preply = preturn () stream
        let mutable looping = true
        while (not (tryParse endp stream)) && looping do
            preply <- p stream
            match preply.Status with
            | ReplyStatus.Ok -> ()
            | _ -> looping <- false
        match preply.Status with
            | ReplyStatus.Ok -> preply
            | _ ->
                let myReply = Reply(Error, mergeErrors preply.Error (messageError "skipManyTillEx failed") )
                stream.BacktrackTo initialState
                myReply



let ublockComment, ublockCommentImpl = createParserForwardedToRef()
let bcopenTag = "/*"
let bccloseTag = "*/"
let pbcopen = pstring bcopenTag
let pbcclose = pstring bccloseTag
let ignoreCommentContent : Parser<unit,unit> = skipManyTillEx (skipAnyChar)  (choice [pbcopen; pbcclose] |>> fun x -> ())
let ignoreSubComment : Parser<unit,unit> = ublockComment
let commentContent : Parser<unit,unit> = skipMany (choice [ignoreCommentContent; ignoreSubComment])
do ublockCommentImpl := between (pbcopen) (pbcclose) (commentContent) |>> fun c -> ()

do test (skipManyTillEx (pchar 'a' |>> fun c -> ()) (pchar 'b') >>. (restOfLine true)) "aaaabcccc"

// do test ublockComment "/**/"
//do test ublockComment "/* This is a comment \n With multiple lines. */"
do test ublockComment "/* Bla bla bla /* nested bla bla */ more outer bla bla */"
4

2 に答える 2

2

あなたの質問を見てみましょう...

1. 選択したアプローチが機能しない理由はありますか?

あなたのアプローチは間違いなく機能します。バグを取り除くだけです。


2.私が実装した方法に問題がある人はいますskipManyTillExか?

いいえ、あなたの実装は問題ないようです。それは単なる組み合わせでskipManyあり、skipManyTillExそれが問題です。

let ignoreCommentContent : Parser<unit,unit> = skipManyTillEx (skipAnyChar)  (choice [pbcopen; pbcclose] |>> fun x -> ())
let commentContent : Parser<unit,unit> = skipMany (choice [ignoreCommentContent; ignoreSubComment])

skipMany両方とも失敗するcommentContentまで実行されます。しかし、あなたの を使用して実装されています。これは、入力を消費せずに成功できる方法で実装されています。これは、入力が消費されない場合、後続のパーサーが失敗したのか、単に何も消費しなかったのかがわからないため、アウターはいつ停止するかを判断できないことを意味します。ignoreCommentContentignoreSubCommentignoreCommentContentskipManyTillExskipMany

manyこれが、パーサーの下のすべてのパーサーが入力を消費する必要がある理由です。そうではないかもしれませskipManyTillExんが、それがエラー メッセージが伝えようとしていることです。

それを修正するにはskipMany1TillEx、少なくとも 1 つの要素自体を消費する を実装する必要があります。


3. この "many ..." エラー メッセージが消えるようにパーサーを修正する方法を知っている人はいますか?

このアプローチはどうですか?

open FParsec
open System

/// Type abbreviation for parsers without user state.
type Parser<'a> = Parser<'a, Unit>

/// Skips C-style multiline comment /*...*/ with arbitrary nesting depth.
let (comment : Parser<_>), commentRef = createParserForwardedToRef ()

/// Skips any character not beginning of comment end marker */.
let skipCommentChar : Parser<_> = 
    notFollowedBy (skipString "*/") >>. skipAnyChar

/// Skips anx mix of nested comments or comment characters.
let commentContent : Parser<_> =
    skipMany (choice [ comment; skipCommentChar ])

// Skips C-style multiline comment /*...*/ with arbitrary nesting depth.
do commentRef := between (skipString "/*") (skipString "*/") commentContent


/// Prints the strings p skipped over on the console.
let printSkipped p = 
    p |> withSkippedString (printfn "Skipped: \"%s\" Matched: \"%A\"")

[
    "/*simple comment*/"
    "/** special / * / case **/"
    "/*testing /*multiple*/ /*nested*/ comments*/ not comment anymore"
    "/*not closed properly/**/"
]
|> List.iter (fun s ->
    printfn "Test Case: \"%s\"" s
    run (printSkipped comment) s |> printfn "Result: %A\n"
)

printfn "Press any key to exit..."
Console.ReadKey true |> ignore

notFollowedByコメント終了マーカー (*/) の一部ではない文字のみをスキップするために使用することで、ネストされたmanyパーサーは必要ありません。

お役に立てれば :)

于 2014-06-06T02:04:28.070 に答える
1

最後に、many問題を解決する方法を見つけました。skipManyTillExカスタムを、私が呼び出した別のカスタム関数に置き換えましたskipManyTill1ExskipManyTill1Ex、以前とは対照的に、skipManyTillEx1 つ以上の解析が成功した場合にのみ成功pします。

このバージョンでは、空のコメント /**/ のテストが失敗すると予想していましたが、機能します。

...
let skipManyTill1Ex (p : Parser<_,_>) (endp : Parser<_,_>) : Parser<unit,unit> =
    fun stream ->
        let tryParse (p : Parser<_,_>) (stm : CharStream<unit>) : bool = 
            let spre = stm.State
            let reply = p stm
            match reply.Status with
            | ReplyStatus.Ok -> 
                stream.BacktrackTo spre
                true
            | _ -> 
                stream.BacktrackTo spre
                false
        let initialState = stream.State
        let mutable preply = preturn () stream
        let mutable looping = true
        let mutable matchCounter = 0
        while (not (tryParse endp stream)) && looping do
            preply <- p stream
            match preply.Status with
            | ReplyStatus.Ok -> 
                matchCounter <- matchCounter + 1
                ()
            | _ -> looping <- false
        match (preply.Status, matchCounter) with
            | (ReplyStatus.Ok, c) when (c > 0) -> preply
            | (_,_) ->
                let myReply = Reply(Error, mergeErrors preply.Error (messageError "skipManyTill1Ex failed") )
                stream.BacktrackTo initialState
                myReply


let ublockComment, ublockCommentImpl = createParserForwardedToRef()
let bcopenTag = "/*"
let bccloseTag = "*/"
let pbcopen = pstring bcopenTag
let pbcclose = pstring bccloseTag
let ignoreCommentContent : Parser<unit,unit> = skipManyTill1Ex (skipAnyChar)  (choice [pbcopen; pbcclose] |>> fun x -> ())
let ignoreSubComment : Parser<unit,unit> = ublockComment
let commentContent : Parser<unit,unit> = skipMany (choice [ignoreCommentContent; ignoreSubComment])
do ublockCommentImpl := between (pbcopen) (pbcclose) (commentContent) |>> fun c -> ()

do test (skipManyTillEx (pchar 'a' |>> fun c -> ()) (pchar 'b') >>. (restOfLine true)) "aaaabcccc"

do test ublockComment "/**/"
do test ublockComment "/* This is a comment \n With multiple lines. */"
do test ublockComment "/* Bla bla bla /* nested bla bla */ more outer bla bla */"
于 2014-06-06T01:42:08.267 に答える