Un parseur prend une chaîne de caractères en entrée et retourne soit un succès (avec un résultat), soit une erreur. Par exemple, un parseur d'entier peut prendre "123" comme entrée et retourner le nombre 123.
En elm, ce type de fonctionnalités se trouve dans la bibliothèque elm/parser.
Un parseur est une valeur de type Parser a
, où a
est le type du résultat attendu. Par exemple Parser Int
est le type utilisé pour un parseur d'entier. Pour utiliser un parseur sur une chaîne de caractères, on appelle la fonction run
en lui donnant en entrée à la fois le parseur et la chaîne. Par exemple :
> import Parser exposing (..)
> run int "123"
Ok 123 : Result (List DeadEnd) Int
> run int "123xxx"
Ok 123 : Result (List DeadEnd) Int
> run int "xxx123"
Err [{ col = 1, problem = ExpectingInt, row = 1 }]
: Result (List DeadEnd) Int
Notez que le type de retour est Result
(revoir si besoin la gestion des erreurs).
On peut enchaîner les parseurs avec succeed
et les opérateurs |.
et |=
pour construire une valeur d'un type personnalisé.
Imaginons qu'on ait défini le type suivant :
type alias Point = { x : Float, y : Float }
On peut construire un point en parsant une chaîne de caractères à l'aide du parseur suivant :
extraitPoint : Parser Point
extraitPoint =
succeed Point
|. symbol "("
|. spaces
|= float
|. spaces
|. symbol ","
|. spaces
|= float
|. spaces
|. symbol ")"
Le parseur spaces
ignore les espaces blancs (" ", tabulations, sauts de ligne, etc.), tandis que le parseur symbol
est utilisé pour vérifier qu'une chaîne de caractères commence par une chaîne de caractères littérale spécifique.
Le premier argument de succeed
est Point
, c'est-à-dire le constructeur de valeur pour le type Point
.
L'opérateur |.
est utilisé pour écarter le résultat du parseur qui suit, tandis que |=
est utilisé pour conserver le résultat du parseur qui suit. Ainsi, on détecte "(", on ignore les espaces suivants, on détecte un flottant qu'on passe en premier argument au constructeur Point
, on détecte ",", on ignore les espaces suivants, on détecte un flottant qu'on passe en second argument au constructeur Point
, on détecte ")". Si tout s'est bien passé le résultat contient une valeur de type 'Point'.
> run extraitPoint "(2.5, 6.2)"
Ok { x = 2.5, y = 6.2 }
: Result (List DeadEnd) Point
> run extraitPoint "(2.5 6.2)"
Err [{ col = 6, problem = ExpectingSymbol ",", row = 1 }]
: Result (List DeadEnd) Point
> run extraitPoint "(2.5, 6.2)xxx"
Ok { x = 2.5, y = 6.2 }
: Result (List DeadEnd) Point
Pour construire la valeur d'un type union
, il est nécessaire d'utiliser
le parseur oneOf
qui permet d'essayer plusieurs parseurs dans l'ordre jusqu'à ce qu'un réussisse :
type CouleurCarte = Trefle | Carreau | Coeur | Pique
extraitCouleur : Parseur CouleurCarte
extraitCouleur = oneOf [ succeed Trefle |. symbol "Trefle"
, succeed Carreau |. symbol "Carreau"
, succeed Coeur |. symbol "Coeur"
, succeed Pique |. symbol "Pique"
]
> run extraitCouleur "Trefle"
Ok Trefle : Result (List DeadEnd) EmblemeCarte
> run extraitCouleur "Carreau"
Ok Carreau : Result (List DeadEnd) EmblemeCarte
> run extraitCouleur "Spad"
Err [{ col = 1, problem = ExpectingSymbol "Trefle", row = 1 },{ col = 1, problem = ExpectingSymbol "Carreau", row = 1 },{ col = 1, problem = ExpectingSymbol "Coeur", row = 1 },{ col = 1, problem = ExpectingSymbol "Pique", row = 1 }]
: Result (List DeadEnd) EmblemeCarte
Pour construire la valeur d'un type récursif, il est nécessaire d'utiliser le parseur lazy
. Par exemple, pour parser des expressions imbriquées comme (1+(2+3))
:
type Expr
= Number Int
| Sum Expr Expr
parseSum : Parser Expr
parseSum = succeed Sum
|. symbol "("
|= parseExpr
|. symbol "+"
|= parseExpr
|. symbol ")"
parseExpr : Parser Expr
parseExpr = oneOf [ succeed Number |= int
, lazy (\_ -> parseSum)
]
> run parseExpr "(1+(2+3))"
Ok (Sum (Number 1) (Sum (Number 2) (Number 3)))
: Result (List DeadEnd) Expr