FPARSEC - Combinator "Molti" si lamenta e ... perché non analizzare commenti del blocco come questo?
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:
- .
- 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).
- 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 ilskipManyTill
, principalmente nell'aspetto di come controllare il flusso di analisi. NelskipManyTill
originale, ilReply<_>
di P e ENDP è tracciato, insieme alstream.StateTag
. Nella mia implementazione, al contrario non ho visto la necessità di utilizzarestream.StateTag
, affidarsi esclusivamente sul codice di statoReply<_>
. In caso di analizzatore non riuscito, backtracksskipManyTillEx
allo stato iniziale dei flussi e riporta un errore. Potrebbe forse il codice backtracking causare l'errore "MOLTO"? Cosa dovrei fare invece? - (E questa è la domanda principale) - qualcuno vede, come riparare il parser tale, che questo messaggio di errore "molti ..." va via?
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 */"
.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 :)