12 votes

Analyser le niveau d'indentation avec PEG.js

J'ai essentiellement la même question que PEG pour l'indentation de style Python, mais j'aimerais avoir un peu plus d'orientation concernant cette réponse.

La réponse génère avec succès un tableau de chaînes de caractères, chaque ligne d'entrée étant séparée par 'INDENT' et 'DEDENT'. Il semble qu'il ait principalement utilisé PEG.js pour la tokenisation, mais aucun vrai parsing ne se produit.

Alors comment puis-je étendre son exemple pour effectuer un véritable parsing?

Par exemple, comment puis-je modifier cette grammaire:

start = obj
obj = id:id children:(indent obj* outdent)?
    {
        if (children) {
            let o = {}; o[id] = children[1];
            return o;
        } else {
            return id;
        }
    }
id = [a-z]
indent = '{'
outdent = '}'

pour utiliser l'indentation au lieu des accolades pour délimiter les blocs, tout en obtenant le même résultat?

(Utilisez http://pegjs.majda.cz/online pour tester cette grammaire avec l'entrée suivante: a{bcd{zyx{}}})

21voto

Jakub Kulhan Points 761

Parseur :

// ne pas utiliser le cache de résultat, ni le suivi de ligne et de colonne

{ var indentStack = [], indent = ""; }

start
  = INDENT? l:line
    { return l; }

line
  = SAMEDENT line:(!EOL c:. { return c; })+ EOL?
    children:( INDENT c:line* DEDENT { return c; })?
    { var o = {}; o[line] = children; return children ? o : line.join(""); }

EOL
  = "\r\n" / "\n" / "\r"

SAMEDENT
  = i:[ \t]* &{ return i.join("") === indent; }

INDENT
  = &(i:[ \t]+ &{ return i.length > indent.length; }
      { indentStack.push(indent); indent = i.join(""); pos = offset; })

DEDENT
  = { indent = indentStack.pop(); }

Entrée :

a
  b
  c
  d
    z
    y
    x

Sortie :

{
   "a": [
      "b",
      "c",
      {
         "d": [
            "z",
            "y",
            "x"
         ]
      }
   ]
}

Il ne peut pas analyser un objet vide (dernier x), cependant, cela devrait être facile à résoudre. Le truc ici est la règle SAMEDENT, elle réussit lorsque le niveau d'indentation n'a pas changé. INDENT et DEDENT changent le niveau d'indentation actuel sans changer la position dans le texte pos = offset.

6voto

Nils Points 87

Mise à jour 2021

Voici un exemple fonctionnel qui s'exécute dans le playground en ligne de Peggy.js. Peggy.js est un fork de PEG.js en développement actif. PEG.js a été abandonné par David Maida.

L'exemple montre comment les règles INDENT, SAMEDENT et DEDENT sont analysées, et comment utiliser les emplacements d'analyse. Vérifiez la console.

Il utilise ces syntaxes, qui peuvent ne pas être connues d'autres générateurs d'analyseurs :

(en haut du fichier)

  • {{...}} (Initialisateur global) - Exécute ... lors de la génération de l'analyseur.
  • {...} (Initialisateur par analyse) - Exécute ... lors de l'instanciation de l'analyseur.

(dans le fichier)

  • X {...} (action) - Faites ... quand X réussit. Les variables des initialiseurs sont disponibles. Si ... renvoie quelque chose, cela remplacera ce que X renvoie.
  • $X - Renvoie le texte brut analysé avec X, au lieu du résultat de X.
  • ... @X ... (opérateur d'extraction) - Remplace le résultat de ... X ... par le résultat de X.
  • X &{...} (prédicat) - "et ... doit également être vrai pour que X réussisse".
  • X = &(...) - Si ... réussit, X réussit. ... ne consomme pas d'entrée.

Consultez la documentation pour plus d'informations.

{{
    console.clear()
    console.log('Analyseur généré')
}}

{
    let indentstack = []
    let indent = ''
    function found (what) {
        let loc = location()
        console.log(`[${loc.start.line}:${loc.start.column} - ${loc.end.line}:${loc.end.column}] trouvé ${what}`)
    }
    console.log('Analyseur instancié')
}

DOCUMENT = NEWLINES? @THINGS NEWLINES? _

THINGS = ( SAMEDENT @( OBJECT / LINE ) )*

OBJECT = key:KEY childs:(BLOCK / INLINE) {
    found(`objet "${key}"`)
    let o = {}
    o[key] = childs
    return o
}

KEY = @$( [^ \t\r\n:]+ ) _ ':' _

BLOCK = NEWLINES INDENT @THINGS DEDENT

INLINE = line:LINE { return [line] }

LINE = text:$( (!EOL .)+ ) NEWLINES? {
    found(`ligne "${text}"`)
    return text
}

INDENT = &(
    spaces:$( [ \t]+ ) &{
        return spaces.length > indent.length
    } {
        indentstack.push(indent)
        indent = spaces
    }
) {
    found('indentation')
}

SAMEDENT = spaces:$( [ \t]* ) &{
    return spaces === indent
} {
    found('même indentation')
}

/* En raison de cette règle, le cache des résultats doit être désactivé */
DEDENT = &{
    indent = indentstack.pop()
    return true
} {
    found('dé-indentation')
}

_ = [ \t]*
EOL = '\r\n' / '\n' / '\r'
NEWLINES = (_ EOL)+

/* Testez avec cette entrée

H:
  a
  b
  c
  G:
    d
    e
    f

*/

Ancienne réponse

Voici un correctif pour la grammaire de @Jakub Kulhan qui fonctionne dans PEG.js v 0.10.0. La dernière ligne doit être modifiée en = &{ indent = indentStack.pop(); return true;} car PEG.js ne permet plus les actions autonomes ({...}) dans une grammaire. Cette ligne est maintenant un prédicat (&{...}) qui réussit toujours (return true;).

J'ai également supprimé le pos = offset; car cela donne une erreur offset is not defined. Probablement Jakub faisait référence à une variable globale disponible dans les anciennes versions de PEG.js. PEG.js fournit maintenant la fonction location() qui renvoie un objet contenant l'offset et d'autres informations.

// Ne pas utiliser le cache des résultats, ni le suivi des lignes et des colonnes

{ var indentStack = [], indent = ""; }

start
  = INDENT? l:line
    { return l; }

line
  = SAMEDENT line:(!EOL c:. { return c; })+ EOL?
    children:( INDENT c:line* DEDENT { return c; })?
    { var o = {}; o[line] = children; return children ? o : line.join(""); }

EOL
  = "\r\n" / "\n" / "\r"

SAMEDENT
  = i:[ \t]* &{ return i.join("") === indent; }

INDENT
  = &(i:[ \t]+ &{ return i.length > indent.length; }
      { indentStack.push(indent); indent = i.join(""); })

DEDENT
  = &{ indent = indentStack.pop(); return true;}

À partir de la v 0.11.0, PEG.js prend également en charge l'opérateur d'extraction de valeur, @, ce qui permettrait d'écrire cette grammaire de manière encore plus simple, mais comme il n'est pas actuellement présent dans l'analyseur en ligne, je m'abstiendrai de l'ajouter à cet exemple.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X