Modificateurs FParsec et postfix aux éléments analysés

Oct 08 2020

En tant qu'exercice pour moi-même, j'utilise FParsec pour écrire une fonction qui peut générer une chaîne aléatoire à partir d'une spécification au format Regex (limité).

Par exemple

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

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

J'en ai beaucoup de travail, mais j'ai du mal avec l'idée des termes postfixes (c'est-à-dire qu'un analyseur pourrait avoir besoin de revenir en arrière et de modifier la sortie, pas seulement la position dans le flux Char). Par exemple, "a" vs "a +"

Voici une version simplifiée de mes types de domaine:

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

L'entrée abdoit donc générer [CharLiteral 'a'; CharLiteral 'b']mais ab+doit générer [CharLiteral 'a'; Count (CharLiteral 'b', MinCount 1)]. Donc, cela implique que, lors de la rencontre d'un Countterme dans le flux, l'analyseur doit revenir en arrière sur la sortie afin d'envelopper le dernier terme dans un autre objet.

Maintenant, je ne sais pas comment faire ça. Voici ma définition d'analyse actuelle, qui fonctionne (principalement) mais est très inefficace:

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

Vous pouvez le voir dans parseCountI call parseTermfirst, puis analyser les informations de comptage réelles. Ensuite, parseTermsj'essaye l' parseCountanalyseur à chaque fois, en revenant sur l' entrée si cela ne fonctionne pas. C'est très inefficace car je fais essentiellement deux passes sur presque tous les caractères du flux d'entrée au cas où il serait suivi d'un modificateur de comptage.

Existe-t-il un moyen plus efficace de le faire? J'ai l'impression que je devrais écrire quelque chose de plus comme:

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

mais je ne peux pas faire ce travail car parseCountje devrais avoir besoin d'encapsuler le terme précédent renvoyé par parseTerm.

Réponses

2 Tarmil Oct 09 2020 at 08:22

Je pense que vous pouvez utiliser optpour permettre parseCountde ne pas trouver de décompte s'il n'y en a pas:

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