FParsec- und Postfix-Modifikatoren für analysierte Elemente

Oct 08 2020

Als Übung für mich selbst verwende ich FParsec, um eine Funktion zu schreiben, die aus einer Spezifikation in (begrenzter) Regex-Form eine zufällige Zeichenfolge generieren kann.

Z.B

Input: ([Hh]ello ){1,3} world!?
Output: Hello hello world!

Input: (\d{1,2}\.){3}(\d{1,2})
Output: 38.12.29.05

Ich habe viel davon, aber ich habe Probleme mit der Idee von Postfix-Begriffen (dh, ein Parser muss möglicherweise zurückgehen und die Ausgabe ändern, nicht nur die Position im Char-Stream). ZB "a" vs "a +"

Hier ist eine abgespeckte Version meiner Domain-Typen:

type Count =
    | ExactCount of int
    | MinCount of int
    | MaxCount of int
    | RangeCount of int * int

type Term =
    | CharLiteral of char
    | Count of Term * Count

type RandExpSpec = Term list

Die Eingabe absollte also generieren, [CharLiteral 'a'; CharLiteral 'b']aber ab+generieren [CharLiteral 'a'; Count (CharLiteral 'b', MinCount 1)]. Diese Art von impliziert, dass Countder Parser bei der Begegnung mit einem Begriff im Stream die Ausgabe zurückverfolgen muss, um den letzten Begriff in ein anderes Objekt zu packen.

Jetzt weiß ich nicht, wie ich das machen soll. Hier ist meine aktuelle Parsing-Definition, die (meistens) funktioniert, aber sehr ineffizient ist:

let parseCharLiteral = choice [ letter; digit ] |>> CharLiteral

let rec parseTerm =
    parse.Delay(fun () -> choice [ parseCharLiteral ])

and parseCount =
    parseTerm
    .>>. choice [ skipChar '*' >>% (MinCount 0)
                  skipChar '+' >>% (MinCount 1)
                  skipChar '?' >>% (RangeCount(0, 1)) ]
    |>> Count

let parseTerms =
    many ((attempt parseCount) <|> parseTerm) .>> eof

Sie können sehen, dass parseCountich parseTermzuerst anrufe und dann die tatsächlichen Zählinformationen analysiere. Dann parseTermsversuche ich es parseCountjedes Mal mit dem Parser und gehe die Eingabe zurück, wenn es nicht funktioniert. Dies ist sehr ineffizient, da ich im Wesentlichen zwei Durchgänge für fast jedes Zeichen im Eingabestream durchführe, nur für den Fall, dass darauf ein Zählmodifikator folgt.

Gibt es einen effizienteren Weg, dies zu tun? Ich habe das Gefühl, ich sollte etwas mehr schreiben wie:

let parseCharLiteral = choice [ letter; digit ] |>> CharLiteral

let rec parseTerm =
    parse.Delay(fun () -> choice [ parseCharLiteral ] .>>. (attempt parseCount))

and parseCount =
    choice [ skipChar '*' >>% (MinCount 0)
             skipChar '+' >>% (MinCount 1)
             skipChar '?' >>% (RangeCount(0, 1)) ]
    |>> Count

let parseTerms =
    many parseTerm .>> eof

Aber ich kann das nicht zum parseCountLaufen bringen, da ich den vorherigen Begriff, der von zurückgegeben wurde, umbrechen muss parseTerm.

Antworten

2 Tarmil Oct 09 2020 at 08:22

Ich denke, Sie können verwenden, optum zuzulassen parseCount, dass keine Zählung gefunden wird, wenn es keine gibt:

let parseCount =
    parseTerm
    .>>. opt (choice [ skipChar '*' >>% (MinCount 0)
                       skipChar '+' >>% (MinCount 1)
                       skipChar '?' >>% (RangeCount(0, 1)) ])
    |>> function
    | term, None -> term
    | term, Some count -> Count (term, count)

let parseTerms =
    many parseCount .>> eof