Pegjs: Prise en charge de l'analyse des langages basés sur l'indentation

Créé le 16 oct. 2013  ·  34Commentaires  ·  Source: pegjs/pegjs

J'utilisais un tas de langages basés sur l'indentation, comme CoffeeScript, Jade, et je souhaite créer moi-même des DSL.
Je trouve quelques astuces pour maintenir les indentations dans pegjs en cherchant, je me demandais s'il existait une solution cohérente :
http://stackoverflow.com/questions/11659095/parse-indentation-level-with-peg-js
http://stackoverflow.com/questions/4205442/peg-for-python-style-indentation
https://gist.github.com/jakubkulhan/3192844
https://groups.google.com/forum/#!searchin/pegjs/indent/pegjs/RkbAB4rPlfU/xxafrY5wGCEJ
Mais pegjs prendra-t-il en charge cette fonctionnalité ?

feature

Commentaire le plus utile

Il est très dangereux de se fier aux effets secondaires que vous ajoutez dans des gestionnaires personnalisés pour analyser les grammaires basées sur l'indentation. Ne le fais pas. Pegjs devrait ajouter une certaine capacité à pousser et à éclater l'état conditionnel afin de sécuriser les indentations d'analyse (et d'autres grammaires contextuelles).

C'est ce que je fais pour l'instant, et je vous recommande de faire ceci : Prétraitez le fichier d'entrée et insérez vos propres jetons de retrait/retrait. J'utilise respectivement {{{{ et }}}}. Ensuite, votre grammaire est sans contexte et peut être analysée normalement. Cela peut gâcher vos valeurs de ligne/colonne, mais vous pouvez les corriger dans un post-processeur.

Tous les 34 commentaires

Il est très dangereux de se fier aux effets secondaires que vous ajoutez dans des gestionnaires personnalisés pour analyser les grammaires basées sur l'indentation. Ne le fais pas. Pegjs devrait ajouter une certaine capacité à pousser et à éclater l'état conditionnel afin de sécuriser les indentations d'analyse (et d'autres grammaires contextuelles).

C'est ce que je fais pour l'instant, et je vous recommande de faire ceci : Prétraitez le fichier d'entrée et insérez vos propres jetons de retrait/retrait. J'utilise respectivement {{{{ et }}}}. Ensuite, votre grammaire est sans contexte et peut être analysée normalement. Cela peut gâcher vos valeurs de ligne/colonne, mais vous pouvez les corriger dans un post-processeur.

Si vous n'avez pas besoin de cibler javascript, Pegasus , mon clone pegjs pour C#, prend en charge l'état push/pop. Voici un article wiki sur la façon de faire exactement ce que vous voulez : https://github.com/otac0n/Pegasus/wiki/Significant-Whitespace-Parsing

Je voudrais proposer que pegjs utilise ma syntaxe comme point de départ pour l'analyse basée sur l'état.

La capacité de pousser et d'éclater en toute sécurité est agréable. Je l'utiliserais s'il était basé sur Javascript. Cela ne vaut tout simplement pas la peine d'intégrer un CLR juste pour l'analyse.

C'est ce que je m'étais dis. Je pense, dans ce cas, que je devrais probablement essayer de rétroporter mes améliorations dans pegjs.

Cependant, je ne veux pas nécessairement le faire sans avoir une conversation avec @dmajda.

@otac0n C'est sympa. Je n'écris pas C# . JavaScript est bien mieux pour moi.

Les langages basés sur l'indentation sont importants. Je veux chercher à simplifier leur analyse après 1.0.0.

Je pense que ce problème est mieux résolu en autorisant l'état en général, tout comme Pegasus le fait et comme suggéré dans # 285. Voici une idée (ce qui suit est la grammaire d'espaces blancs significative de Pegasus traduite en pegjs et avec mon idée de syntaxe ajoutée):

{var indentation = 0}

program
  = s:statements eof { return s }

statements
  = line+

line
  = INDENTATION s:statement { return s }

statement
  = s:simpleStatement eol { return s }
  / "if" _ n:name _? ":" eol INDENT !"bar " s:statements UNDENT {
      return { condition: n, statements: s }
    }
  / "def" _ n:name _? ":" eol INDENT s:statements UNDENT {
      return { name: n, statements: s }
    }

simpleStatement
  = a:name _? "=" _? b:name { return { lValue: a, expression: b } }

name
  = [a-zA-Z] [a-zA-Z0-9]* { return text() }

_ = [ \t]+

eol = _? comment? ("\r\n" / "\n\r" / "\r" / "\n" / eof)

comment = "//" [^\r\n]*

eof = !.

INDENTATION
  = spaces:" "* &{ return spaces.length == indentation }

INDENT
  = #STATE{indentation}{ indentation += 4 }

UNDENT
  = #STATE{indentation}{ indentation -= 4 }

Notez les blocs #STATE{indentation} près du bas (évidemment inspirés de Pegasus). J'appelle ces blocs d'état. L'idée est de permettre un bloc d'état avant les actions. Voici un bloc d'état plus compliqué :

#STATE{a, b, arr: {arr.slice()}, obj: {shallowCopy(obj)}, c}

C'est un raccourci pour :

#STATE{a: {a}, b: {b}, arr: {arr.slice()}, obj: {shallowCopy(obj)}, c: {c}}

En d'autres termes, une fois le développement abrégé appliqué, le contenu d'un bloc d'état est une liste de identifier ":" "{" code "}" . L'ajout d'un bloc d'état avant une action indique à pegjs que cette action modifiera les identifiants répertoriés, et si la règle est annulée, ces identifiants doivent être réinitialisés au code entre les accolades.

Voici les fonctions compilées pour INDENT et UNDENT à partir de la grammaire ci-dessus, avec la réinitialisation de la variable indentation ajoutée :

    function peg$parseINDENT() {
      var s0, s1, t0;

      s0 = peg$currPos;
      t0 = indentation;
      s1 = [];
      if (s1 !== peg$FAILED) {
        peg$reportedPos = s0;
        s1 = peg$c41();
      } else {
        indentation = t0;
      }
      s0 = s1;

      return s0;
    }

    function peg$parseUNDENT() {
      var s0, s1, t0;

      s0 = peg$currPos;
      t0 = indentation;
      s1 = [];
      if (s1 !== peg$FAILED) {
        peg$reportedPos = s0;
        s1 = peg$c42();
      } else {
        indentation = t0;
      }
      s0 = s1;

      return s0;
    }

Et voici un peu comment le « bloc d'état compliqué » ci-dessus pourrait être compilé :

s0 = peg$currPos;
t0 = a;
t1 = b;
t2 = arr.slice();
t3 = shallowCopy(obj);
t4 = c;
// ...
if (s1 !== peg$FAILED) {
  // ...
} else {
  peg$currPos = s0;
  a = t0;
  b = t1;
  arr = t2;
  obj = t3;
  c = t4;
}

Que pensez-vous de cette idée de pouvoir :

  • Dites à pegjs quelles variables avec état seront modifiées par une action.
  • Fournissez le code nécessaire pour stocker ces variables si elles doivent être réinitialisées. (Y compris la syntaxe abrégée pour le cas simple où la variable est une valeur primitive.)

Et que penses-tu de la syntaxe ?

Edit : Voici la grammaire syntaxique proposée (juste pour le plaisir) :

diff --git a/src/parser.pegjs b/src/parser.pegjs
index 08f6c4f..09e079f 100644
--- a/src/parser.pegjs
+++ b/src/parser.pegjs
@@ -116,12 +116,31 @@ ChoiceExpression
     }

 ActionExpression
-  = expression:SequenceExpression code:(__ CodeBlock)? {
+  = expression:SequenceExpression code:((__ StateBlock)? __ CodeBlock)? {
       return code !== null
-        ? { type: "action", expression: expression, code: code[1] }
+        ? {
+            type:       "action",
+            expression: expression,
+            code:       code[2],
+            stateVars:  (code[0] !== null ? code[0][1] : [])
+          }
         : expression;
     }

+StateBlock "state block"
+  = "#STATE{" __ first:StateBlockItem rest:(__ "," __ StateBlockItem)* __ "}" {
+      return buildList(first, rest, 3);
+    }
+
+StateBlockItem
+  = varName:Identifier expression:(__ ":" __ CodeBlock)? {
+      return {
+        type:       "stateVar",
+        name:       varName,
+        expression: expression !== null ? expression[3] : varName
+      };
+    }
+
 SequenceExpression
   = first:LabeledExpression rest:(__ LabeledExpression)* {
       return rest.length > 0

Salut les gars,
Juste pour clarifier, ai-je raison de dire qu'il est préférable de ne pas utiliser PEG.js (avec des solutions de contournement à partir du début de ce problème) avec des langages basés sur l'indentation jusqu'à ce que ce problème soit résolu ?
Merci.

@hoho, je ne vous comprends pas. Mais j'ai trouvé plus tard une autre solution pour analyser les indentations avec un combinateur d'analyseur comme des solutions et cela a fonctionné. Et je pense que mon indentation d'origine pour analyser les indentations avec PEG.js a disparu.

Je veux dire qu'il existe des solutions de contournement pour analyser l'indentation, mais les commentaires indiquent que ces solutions de contournement échoueront dans certains cas.

Permettez-moi de clarifier la situation : l'analyse des langages basés sur l'indentation dans PEG.js est possible. Il existe différentes solutions mentionnées ci-dessus et je viens d'en créer une autre alors que j'essayais d'avoir une "sensation" pour cela (c'est une grammaire d'un langage simple avec deux déclarations, dont l'une peut contenir des sous-instructions en retrait - similaire à par exemple if en Python).

Une chose commune à toutes les solutions est qu'elles doivent suivre l'état d'indentation manuellement (car PEG.js ne peut pas le faire). Cela signifie qu'il y a deux limitations :

  1. Vous ne pouvez pas compiler la grammaire avec la mise en cache en toute sécurité (car l'analyseur syntaxique pourrait utiliser les résultats mis en cache au lieu d'exécuter du code de manipulation d'état).
  2. Vous ne pouvez pas revenir en arrière sur les niveaux d'indentation (car il n'y a actuellement aucun moyen de dérouler l'état lors du retour en arrière). En d'autres termes, vous ne pouvez pas analyser un langage dans lequel il existe deux constructions valides qui ne peuvent être désambiguïsées qu'après un changement de niveau de nouvelle ligne et d'indentation.

La limitation 1 peut entraîner des problèmes de performances dans certains cas, mais je ne pense pas qu'il existe de nombreuses langues pour lesquelles la limitation 2 serait un problème.

Je suis d'accord avec cet état jusqu'à la 1.0.0 et je prévois de revenir sur ce sujet quelque temps après. Le premier niveau d'amélioration pourrait consister à supprimer la limitation 2 en utilisant un suivi d'état plus explicite (comme suggéré ci-dessus) ou en fournissant un crochet de retour en arrière (afin que l'on puisse dérouler correctement l'état). Le deuxième niveau pourrait éliminer le besoin de suivre l'état d'indentation manuellement en fournissant un moyen déclaratif de le faire. Cela pourrait aider avec la limitation 1.

H, j'ai écrit un correctif (minuscule, hacky) pour PEG.js qui prend en charge le retour en arrière approprié, comme je l'ai expliqué ici : https://github.com/pegjs/pegjs/issues/45

désolé pour la bosse 😜

Je cherchais juste à créer des analyseurs CSON et YAML pour un langage que je conçois, et tout en cherchant des moyens de créer un analyseur basé sur l'indentation avec PEG.js, j'ai proposé une méthode simple qui :

1) ne repose pas sur l'état push/pop
2) affirmer les niveaux d'indentation via le code dans les actions

Il m'était venu à l'esprit que l'une ou l'autre des 2 solutions ci-dessus ajoutait en fait des problèmes de performances aux parseurs générés. En plus à mon avis :

1) s'appuyer sur les états ajoute non seulement une syntaxe PEG.js laide, mais peut également affecter le type d'analyseurs pouvant être générés, car ils auraient besoin de prendre en charge la gestion des états basée sur les actions.
2) parfois, l'ajout de code dans les actions entraîne une règle dépendante du langage, et pour certains développeurs, cela signifie qu'ils ne peuvent pas utiliser de plugins pour générer des parseurs pour d'autres langages comme C ou PHP sans avoir recours à plus de plugins pour gérer les actions sur les règles, ce qui signifie un système de construction plus grand juste pour prendre en charge 1 ou 2 changements.

Après un certain temps, j'ai commencé à créer ma propre variante de l'analyseur PEG.js et j'ai pensé : pourquoi ne pas simplement utiliser les opérateurs de préfixe d'incrémentation ("++") et de décrémentation ("--") (__++ expression__ et __-- expression__ ) pour gérer les résultats des expressions de correspondance (__expression *__ ou __expression +__).

Ce qui suit est un exemple de grammaire basée sur le langage simple basé sur l' intention de @dmajda , réécrit pour utiliser la nouvelle expression __++__ et __-- expression__ au lieu de __& { prédicat }__ :

Start
  = Statements

Statements
  = Statement*

Statement
  = Indent* statement:(S / I) { return statement; }

S
  = "S" EOS {
      return "S";
    }

I
  = "I" EOL ++Indent statements:Statements --Indent { return statements; }
  / "I" EOS { return []; }

Indent "indent"
  = "\t"
 / !__ "  "

__ "white space"
 = " \t"
 / " "

EOS
  = EOL
  / EOF

EOL
  = "\n"

EOF
  = !.

Beaucoup plus agréable à l'oeil, non ? Plus facile à comprendre aussi, à la fois pour les humains et les logiciels.

Comment ça marche? Facile:

1) Indent* indique à l'analyseur que nous voulons 0 ou plus de ce que Indent renvoie
2) ++Indent indique à l'analyseur d'augmenter le nombre minimum de correspondances requis pour Indent
3) Maintenant, chaque fois que l'analyseur est sur le point de renvoyer les correspondances pour Indent , il s'attend d'abord à ce qu'il y ait __1 correspondance de plus__ qu'avant, sinon _peg$SyntaxError_ est renvoyé.
4) --Indent indique à l'analyseur de diminuer le nombre minimum de correspondances requis pour Indent
5) Maintenant, à chaque fois que l'analyseur recherche Indent et renvoie les correspondances qu'il attend __1 moins__ correspondance qu'avant, sinon _peg$SyntaxError_ est renvoyé.

Cette solution est le meilleur moyen d'ajouter la prise en charge de « Significant Whitespace Parsing » sans ajouter une syntaxe laide aux grammaires PEG.js ou bloquer les générateurs tiers.

Voici les règles modifiées pour ajouter la prise en charge de l'analyse dans _src/parser.pegjs_ :

{
  const OPS_TO_PREFIXED_TYPES = {
    "$": "text",
    "&": "simple_and",
    "!": "simple_not",
    "++": "increment_match",
    "--": "decrement_match"
  };
}

PrefixedOperator
  = "$"
  / "&"
  / "!"
  / "++"
  / "--"

SuffixedOperator
  = "?"
  / "*"
  / "+" !"+"

Ai-je raison de supposer que pour le prendre en charge côté compilateur/générateur, nous devrons :

1) ajouter une passe de compilateur qui garantit que __++ expression__ ou __-- expression__ ne sont utilisés que sur __expression *__ ou __expression +__, où __expression__ doit être du type : choix, séquence ou ref_règle
2) ajouter une vérification basée sur le cache dans l'analyseur généré pour __expression *__ ou __expression +__ qui affirme que la correspondance minimale requise est satisfaite avant de renvoyer les correspondances
3) ajouter éventuellement une méthode d'assistance pour les analyseurs générés à implémenter qui renvoie le nombre de correspondances requises pour une règle donnée, par exemple. nMatches( name: String ): Number

@futagoza , c'est propre et intelligent. Je l'aime bien. Je travaille sur un analyseur qui gère l'état, mais le seul état dont nous avons vraiment besoin est les niveaux d'indentation. Je peux utiliser cette idée et vous en attribuer le mérite. Le suivi du niveau d'indentation nécessite toujours un état push/pop et cela peut donc toujours empêcher certaines optimisations, mais la sémantique de celui-ci est très agréable.

Si vous ajoutez des opérateurs à une grammaire, je vous recommande d'ajouter également l'opérateur préfixe @. Son but est simplement d'extraire un seul résultat de règle d'une séquence. En utilisant cela, l'exemple de grammaire devient encore plus propre. Plus aucune action triviale { return x }.

Start
  = Statements

Statements
  = Statement*

Statement
  = Indent* @(S / I)

S
  = "S" EOS {
      return "S";
    }

I
  = "I" EOL ++Indent <strong i="8">@Statements</strong> --Indent
  / "I" EOS { return []; }

Indent "indent"
  = "\t"
 / !__ "  "

__ "white space"
 = " \t"
 / " "

EOS
  = EOL
  / EOF

EOL
  = "\n"

EOF
  = !.

@kodyjking qu'est-ce que tu en penses ?

@futagoza Avez-vous un fork/branch avec le patch d'indentation activé et un petit exemple de grammaire ?

Je travaille sur cette indentation fourche/branche

@krinye "Je recommande également d'ajouter l'opérateur de préfixe @ . Son but est simplement d'extraire un seul résultat de règle d'une séquence"

L'un d'entre vous pourrait-il jeter un œil et faire un commentaire ou une communication avec le correctif. Merci :)

Lisez-moi : changements de fourche

J'ai débogué... increment_match-not-found

Ah, je n'ai pas remarqué la mise en garde :

Ai-je raison de supposer que pour le prendre en charge côté compilateur/générateur, nous devrons :

  • ajoutez une passe de compilateur qui garantit que l'expression ++ ou -- l'expression n'est utilisée que sur l'expression * ou l'expression +, où l'expression doit être de types : choix, séquence ou ref_règle
  • ajouter une vérification basée sur le cache dans l'analyseur syntaxique généré pour l'expression * ou l'expression + qui affirme que la correspondance minimale requise est satisfaite avant de renvoyer les correspondances
  • ajouter éventuellement une méthode d'assistance pour les analyseurs générés à implémenter qui renvoie le nombre de correspondances requises pour une règle donnée, par exemple. nCorrespondances( nom : Chaîne ) : Nombre

Juste pour le plaisir, j'ai essayé d'ajouter ceci dans visitor.js

      increment_match: visitExpression,
      decrement_match: visitExpression,

Maintenant, je reçois Invalid opcode: undefined.

@kristianmandrup concernant l'opérateur @ pour extraire des valeurs uniques des séquences, j'ai un fork avec juste cette fonctionnalité ajoutée à PegJS disponible ici :

https://github.com/krisnye/pegjs

C'est un ajout assez simple.

@krisnye +1 pour l'implémentation basée sur la grammaire, agréable et simple. Si cela ne vous dérange pas, je vais l'ajouter à ma variante de PEG.js 😄

@kristianmandrup à bientôt pour ma suggestion

@futagoza S'il vous plaît faites.

Je discutais de la logique d'indentation avec un collègue et nous suggérons les éléments syntaxiques suivants

// incrémente la variable d'état nommée
identifiant++
// décrémente la variable d'état nommée
identifiant--

// répéter le montant constant ou la variable d'état (avec zéro par défaut si la variable n'a pas encore été incrémentée)
règle{entier | identifiant}
// répéter min/max en utilisant des constantes ou des variables d'état
règle{entier | identifiant, entier | identifiant}

L'analyseur sur lequel nous travaillons peut gérer des états arbitraires, mais honnêtement, ce qui précède est tout ce qui est nécessaire pour l'analyse d'indentation.

Merci beaucoup les gars ! Si c'est si facile à faire, pourquoi ne pas créer un fork "dédié" où les choses que vous mentionnez "fonctionnent simplement" ;)
À votre santé!

@krisnye

  1. L'utilisation de __identifier++__ peut facilement provoquer une erreur désordonnée si le développeur voulait dire __identifier+__, c'est pourquoi j'ai choisi d'utiliser __++identifier__ et, par souci de cohérence, __--identifier__
  2. Comme mentionné dans un autre numéro sur les plages, rule{ STATE_REPEAT / RANGE } peut être confondu avec rule{ ACTION } , surtout si vous créez un surligneur de syntaxe pour PEG.js, donc cette approche a été refusée par @dmajda

@kristianmandrup _(OFF TOPIC)_ Je suis doué pour la conception de fonctionnalités et parfois pour la réalisation d'implémentations, mais horrible pour les tester et les évaluer. , et puis oubliez-les 🤣. Pour ma variante actuelle de PEG.js (nommée ePEG.js 😝, une réécriture étendue de PEG.js), j'ajoute les éléments mentionnés ici, ainsi que d'autres fonctionnalités (plages, importations, modèles, etc.), donc j'ajoute tests et benchmarks, mais je travaille aussi actuellement sur un projet C++ qui prend mon temps, donc il n'y a pas d'ETA là-dessus.

@futagoza Merci

Quelqu'un d'autre de cette liste m'a indiqué d'autres solutions de constructeur/générateur d'analyseur que je pourrais également examiner. Tiens moi au courant! À votre santé!

@kristianmandrup Ce n'est pas inclus pour autant que je sache , mais déclaré il y a 3 ans qu'il examinerait la question après la sortie de PEG.js v1, mais d'après ce que je peux dire, ce ne sera pas avant 2 ans, à moins qu'il n'envisage de le faire publier des versions plus mineures de PEG.js v0 (_0.12_, _0.13_, _etc_)

Je voulais dire si vous aviez déjà inclus la prise en charge de l'indentation dans ePEG ou sur la feuille de route ?

@kristianmandrup oh , c'est sur la feuille de route. Je n'ai pas mis à jour le référentiel ePEG.js depuis un certain temps et j'ai récemment décidé de le transformer en une réécriture complète de PEG.js au lieu d'un plugin.

@futagoza a accepté le ++/-- comme pré-opérations, et j'ai oublié la syntaxe d'action, nous l'avons changé en [min, max]

Donc

++identifier
--identifier
rule[integer | identifier]
rule[integer | identifier, integer | identifier]

@krisnye [ ... ] est utilisé pour les classes de caractères, voir https://github.com/pegjs/pegjs#characters

Ce que je fais dans ePEG.js, c'est d'ajouter des plages (également sur la feuille de route) pour obtenir ce que je pense que vous décrivez :

space = [ \t]*
rule = range|expression
  • __expression__ peut être l'un de ceux-ci : __++space__, __--space__ ou __space__
  • __range__ peut être __ min.. __, __ min..max __, __ ..max __ ou __ exact __
  • __min__, __max__ ou __exact__ ne peut être qu'un __entier non signé__
  • l'utilisation d'un __range__ avec un __expression__ (par exemple 2|expression) peut nous permettre de définir le montant total de __expression__ requis pour que __rule__ soit analysé avec succès.
  • l'utilisation de la plage __exact__ avec __++expression__ ou __--expression__ (par exemple 3|++expression) peut nous permettre de définir le montant __entier__ pour __++__ ou __--__, qui est par défaut __1__
  • l'utilisation de la plage __min__ ou __max__ avec __++expression__ ou __--expression__ renvoie une erreur de syntaxe.

Je n'utilise pas de variables d'état car cela serait juste source de confusion avec les identificateurs de règle.

En utilisant une combinaison de __range__, _++__, __--__ ou __ @__, j'espère créer des fichiers de grammaire PEG qui reposent moins sur une règle __action__ pour leur résultat, ce qui devrait augmenter le temps de développement des espaces (par exemple, l'indentation -basé, ASCII art, etc.) car les concepteurs et/ou les implémenteurs de langage n'ont pas à se soucier d'essayer de confirmer si la quantité correcte d'espace blanc a été analysée.
Cela devrait également permettre aux développeurs de plugins de créer des générateurs d'analyseurs qui peuvent optimiser avec _moins peur_ de ce qu'est le langage de notre __action__ (par défaut JavaScript, mais avec les plugins, il peut le changer en CoffeeScript, PHP, etc.).

Il semble donc qu'il ne soit toujours pas possible aujourd'hui d'analyser Python avec PEG.js, n'est-ce pas ?

Si non, est-ce quelque chose qui arrivera bientôt? Y a-t-il un ensemble de tâches requises pour faire ce travail que les gens pourraient contribuer ?

J'ai un projet où j'aimerais pouvoir obtenir un Python AST en JS, le modifier, puis le reconvertir en code source avec le même formatage, donc je serais intéressé à ce que cela se produise s'il y a un clair feuille de route.

@mindjuice Pas encore, c'est prévu pour la publication 1.0.

Si vous ne pouvez pas attendre, mon neveu et moi avons créé un analyseur syntaxique écrit en TypeScript qui utilise la même syntaxe et gère l'indentation. Ce n'est pas encore documenté, mais c'est assez simple. Il y a encore du travail en cours car nous l'utilisons comme analyseur pour une nouvelle conception de langage.

https://github.com/krisnye/pegs

Vous pouvez également essayer un autre générateur d'analyseur avec support tel que chevrotain

Exemple d'indentation Python

Je pense qu'il est tout à fait possible d'analyser l'indentation de type python avec PEGjs.
L'exemple ci-dessous utilise uniquement une indentation basée sur quatre espaces, mais il peut être étendu pour couvrir les tabulations à espacement arbitraire et d'autres caractères d'espacement.
En fait, le langage sur lequel je travaille a une histoire d'indentation légèrement plus compliquée que Python et cette grammaire fonctionne bien pour cela.

{
    let prevIndentCount = 0;
    function print(...s) { console.log(...s); }
}

Indent 'indent'
    = i:("    "+) { 
        let currentIndentCount = i.toString().replace(/,/g, "").length;
        if (currentIndentCount === prevIndentCount + 4) { 
            // DEBUG //
            print("=== Indent ===");
            print("    current:"+currentIndentCount); 
            print("    previous:"+prevIndentCount);
            print("    lineNumber:"+location().start.line); 
            // DEBUG //
            prevIndentCount += 4;
            return "[indent]";
        }
        error("error: expected a 4-space indentation here!")
    } // 4 spaces 

Samedent 'samedent'
    = s:("    "+ / "") &{
        let currentIndentCount = s.toString().replace(/,/g, "").length;
        if (currentIndentCount === prevIndentCount) {
            print("=== Samedent ===");
            return true;
        }
        return false;
    }

Dedent 'dedent'
    = d:("    "+ / "") {
        let currentIndentCount = d.toString().replace(/,/g, "").length;
        if (currentIndentCount < prevIndentCount) {
            // DEBUG //
            print("=== Dedent ===");
            print("    current:"+currentIndentCount); 
            print("    previous:"+prevIndentCount);
            print("    lineNumber:"+location().start.line); 
            // DEBUG //
            prevIndentCount -= 4;
            return "[dedent]";
        }
        error("error: expected a 4-space dedentation here!");
    }

Avec la grammaire ci-dessus, vous pouvez créer une règle de bloc en retrait comme celle-ci :

FunctionDeclaration 
    = 'function' _ Identifier _ FunctionParameterSection _ ":" _ FunctionBody

FunctionBody
    = Newline Indent FunctionSourceCode (Newline Samedent FunctionSourceCode)* Dedent 
Cette page vous a été utile?
0 / 5 - 0 notes