FPARSEC - Combinator "Molti" si lamenta e ... perché non analizzare commenti del blocco come questo?

StackOverflow https://stackoverflow.com//questions/24021546

Domanda

Questa domanda , prima disattivata, non è un duplicato della mia domanda . In realtà ho 3 domande.

Nel codice qui sotto, cerco di creare un parser che parses è possibile annidato commenti a blocco multilinea. In contrasto con l'altra domanda citata, cerco di risolvere il problema in modo semplice senza funzioni ricorsive (vedere la risposta accettata all'altro post).

Il primo problema che ho corso è stato che Skipmanytill Parser di FPARSEC consuma anche il parser finale dal flusso. Così ho creato skipmanytillex (ex per "escludere Endp";)). Lo Skipmanytillex sembra funzionare - almeno per il caso di prova che ho anche aggiunto allo script FSX.

Eppure nel codice, mostrato, ora ottengo il "il combinatore" molti "è stato applicato a un parser che riesce senza consumare ..." Errore. La mia teoria è che il commentContent Parser è la linea che produce questo errore.

Qui, le mie domande:

    .
  1. C'è qualche ragione, perché l'approccio che ho scelto non può funzionare? La soluzione in 1 , che, purtroppo non sembra compilare sul mio sistema Utilizza un parser ricorsivo a basso livello per commenti multilinea (nidificati).
  2. Qualcuno può vedere un problema con il modo in cui ho implementato skipManyTillEx? Il modo in cui l'ho implementato si differenzia di gran parte dal modo in cui viene implementato il skipManyTill, principalmente nell'aspetto di come controllare il flusso di analisi. Nel skipManyTill originale, il Reply<_> di P e ENDP è tracciato, insieme al stream.StateTag. Nella mia implementazione, al contrario non ho visto la necessità di utilizzare stream.StateTag, affidarsi esclusivamente sul codice di stato Reply<_>. In caso di analizzatore non riuscito, backtracks skipManyTillEx allo stato iniziale dei flussi e riporta un errore. Potrebbe forse il codice backtracking causare l'errore "MOLTO"? Cosa dovrei fare invece?
  3. (E questa è la domanda principale) - qualcuno vede, come riparare il parser tale, che questo messaggio di errore "molti ..." va via?
  4. Ecco il codice:

    #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 */"
    
    .

È stato utile?

Soluzione

Infine ha trovato un modo per risolvere il problema del generacolodicetagcode. Sostituito il mio many personalizzato con un'altra funzione personalizzata che ho chiamato skipManyTillEx. skipManyTill1Ex, in contrasto con il precedente skipManyTill1Ex Solo successo se analizzato 1 o più skipManyTillEx con successo.

Mi aspettavo il test per il commento vuoto / ** / per fallire per questa versione ma funziona.

...
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 */"
.

Altri suggerimenti

Diamo un'occhiata alle tue domande ...

.

1. C'è qualche ragione, perché l'approccio che ho scelto non può funzionare?

Il tuo approccio può sicuramente funzionare, devi solo sbattere gli insetti.


.
.

2. Qualcuno può vedere un problema con il modo in cui ho implementato skipManyTillEx?

no. La tua implementazione sembra ok. È solo la combinazione di skipMany e skipManyTillEx che è il problema.

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

skipMany in commentContent Funziona fino a quando ignoreCommentContent e ignoreSubComment non funziona entrambi. Ma ignoreCommentContent viene implementato utilizzando il skipManyTillEx, implementato in un modo che potrebbe avere successo senza consumare input. Ciò significa che il skipMany esterno non sarebbe in grado di determinare quando si ferma perché se nessun input è consumato, non sa se un Parser successivo ha fallito o semplicemente non ha consumato nulla.

Questo è il motivo per cui è richiesto che ogni parser inferiore a un parser many debba consumare input. Il tuo skipManyTillEx potrebbe no, questo è ciò che il messaggio di errore sta cercando di dirti.

Per risolverlo, è necessario implementare un skipMany1TillEx, che consuma almeno un elemento stesso.


.
.

3. Chiunque veda, come risolvere il parser tale, che questo messaggio di errore "molti ..." si allontana?

Che ne dici di questo approccio?

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
.

Utilizzando notFollowedBy per saltare solo i caratteri che non fanno parte di un contrassegno di fine commento (* /), non è necessario alcun parser many nidificato.

Spero che questo aiuti :)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top