Parser Combinator

Parser

// fichier INI
[section]
data=12
data2=chat
[section2]
toto=1
{
  "section": {
    "data": 12,
    "data2": "chat"
  },
  "section2": {
    "toto": 1
  }
}

Fichier INI en JSON

Anatomie d'un parser

Currying

Il s'agit d'une fonction retournant une fonction

Permet de retarder l'appel de la fonction

Permet aussi de spécialiser un comportement

Sans currying

const func1 => console.log("func1");
func1() // affiche "func1" dans la console

Avec currying

const buildFunc1 = function() {
  return function() {
    console.log("func1")
  }
};

buildFunc1(); // n'affiche rien dans la console
const buildFunc1 = function() {
  return function() {
    console.log("func1")
  }
};

buildFunc1()(); // affiche "func1" dans la console
const buildFunc1 = () => () => console.log("func1")

buildFunc1()(); // affiche "func1" dans la console

Currying avec contexte

const buildFunc2 = contexte => () => console.log("func2 "+contexte)

buildFunc2("toto")(); // affiche "func2 toto" dans la console

Le contexte est injecté dans la fonction renvoyé

Function First class citizen 

const buildFunc2 = contexte => () => console.log("func2 "+contexte)

const func2WithToto = buildFunc2("toto");
const func2WithTata = buildFunc2("tata");

func2WithToto(); // affiche "func2 toto" dans la console
func2WithTata(); // affiche "func2 tata" dans la console

Une fonction est une variable comme une autre

La fonction peut aussi avoir des paramètres

const buildFunc3 = contexte => prefix => console.log( prefix + " func3 "+ contexte)

const func2WithToto = buildFunc3("toto");
const func2WithTata = buildFunc3("tata");

func2WithToto("prefix1"); // affiche "prefix1 func2 toto" dans la console
func2WithTata("prefix2"); // affiche "prefix2 func2 tata" dans la console

Et être utilisé en conjonction du contexte injecté

Tag parser

Renvoie un parser capable de matcher le token

text = "AAB"
parser = tag "A"
resultat = parser text // succès
text = "BAB"
parser = tag "A"
resultat = parser text // échec

Parser un seul caractère

text = "chateau"
parser = tag "chat"
resultat = parser text // succès
text = "chapeau"
parser = tag "chat"
resultat = parser text // échec

Parser un ensemble de token

Le ET logique

text = "ABB"
parserA = tag "A"
parserB = tag "B"
parser = parserA AND parserB
resultat = parser text // succès
text = "AAB"
parserA = tag "A"
parserB = tag "B"
parser = parserA AND parserB
resultat = parser text // échec

Lier deux parsers tag

text = "ABC"
parserA = tag "A"
parserB = tag "B"
parserC = tag "C"
parserAandB = parserA AND parserB
parser = parserAandB AND parserC
resultat = parser text // succès

Lier 2 parsers tout court

On généralise !

text = "ABC"
parserA = tag "A"
parserB = tag "B"
parserC = tag "C"
parser = ALL [ parserA, parserB, parserC ]
resultat = parser text // succès

Sous forme de code

Le OU logique

parserA = tag "A"
parserB = tag "B"
parser = parserA OR parserB
resultat = parser "AZ" // succès
resultat = parser "BZ" // succès
resultat = parser "ZZ" // échec

Lier deux parsers tag

On généralise !

parserA = tag "A"
parserB = tag "B"
parserC = tag "C"
parser = ANY [ parserA, parserB, parserC ]
resultat = parser "AZZ" // succès
resultat = parser "BZZ" // succès
resultat = parser "CZZ" // succès
resultat = parser "ZZZ" // échec

Sous forme de code

Répétition

parser = REPEAT ( tag "A", {3} )
resultat = parser "AAAA" // succès
resultat = parser "AAAB" // succès
resultat = parser "ABAA" // échec
resultat = parser "AABA" // échec

Appliquer une répétition à un parser

Exactement n fois

Le parser doit matcher exactement n fois

Au moins n fois

Fail si le parser n'a pas matché au moins n fois

Au plus n fois

Matchera entre 0 et n fois

0 fois ou plus

Ne fail jamais

1 fois ou plus

Fail si le parser répété ne match pas au moins une fois

Délimiter un bloc

parserOpen = tag "("
parserClose = tag ")"
parser = DELIMITER parserOpen parserClose
resultat = parser "( toto )" // succès le group est " toto "
resultat = parser " toto )" // échec le texte ne commence pas par le délimiter de début
resultat = parser "( toto " // échec le groupe ne se ferme jamais

Le cas "simple"

parserOpen = tag "("
parserClose = tag ")"
parser = DELIMITER parserOpen parserClose
resultat = parser "( ( 2 + 3 ) * 5 )" // succès renvoie un résultat 
//composite contenant le groupe imbriqué " 2 + 3 " suivi du reste " * 5 "

Le cas imbriqué

Consommer et modifier le résultat d'un parser

Applique une fonction sur un parser et en renvoie un nouveau modifié

parser1 = tag "1"
// fn est une fonction qui en cas de succès réalise un parseInt de la chaine de caractère
parser = fn parser1 
resultat = parser1 "1" // retourne "1" en chaine de caractères
resultat = parser "1" // retourne 1 sous la forme d'un nombre exploitable

Un texte qui devient un nombre

Détecter la représentation RVB textuelle d'une couleur hexadécimale

2 formats:

  • #fff
  • #ffffff

Mais insensible à la casse

  • #FFf
  • #ffFFff

On s'occupe de la partie 6 digits

Et de la partie 3 digits

On combine tout

parserColorHexa "fff" // échec, ne commence pas par "#"
parserColorHexa "#ff" // échec, ne possède ni 3 ni 6 digit après le "#"
parserColorHexa "#fff" // succès
parserColorHexa "#fFf" // succès
parserColorHexa "#ffffff" // succès
parserColorHexa "#fFFfff" // succès

Résultat de notre parser