Pegjs: Possibilité d'ignorer certaines productions

Créé le 8 oct. 2010  ·  29Commentaires  ·  Source: pegjs/pegjs

Ce serait bien de pouvoir dire au lexer/parser d'ignorer certaines productions (c'est-à-dire les productions d'espaces blancs et de commentaires) afin qu'il devienne inutile de salir toutes les autres productions avec des allocations de commentaires/espaces blancs. Cela peut ne pas être possible cependant, en raison du fait que lexing est intégré à l'analyse syntaxique ?

Merci

feature

Commentaire le plus utile

@atesgoral - J'ai renfloué. Je n'avais pas besoin d'un "véritable analyseur" - j'avais seulement besoin d'isoler certains éléments nommés dans le fichier cible.

J'ai donc fait ce que n'importe quel type mauviette ferait - utiliser des expressions régulières. (Et puis j'ai eu deux problèmes :-)

Mais cela a fait l'affaire, j'ai donc pu passer au défi suivant. Bonne chance dans votre projet !

Tous les 29 commentaires

D'accord. Existe-t-il un moyen propre de le faire pour le moment?

@benekastah : Il n'y a pas de voie propre pour le moment.

Ce serait difficile à faire sans changer le fonctionnement de PEG.js. Les solutions possibles incluent :

  1. Permet d'ajouter un lexer avant l'analyseur généré.
  2. Intégrez des informations sur les règles engorgées quelque part dans la grammaire. Cela signifierait probablement aussi faire la distinction entre le niveau lexical et syntaxique de la grammaire - quelque chose que j'aimerais éviter.

Je ne vais pas travailler dessus maintenant, mais c'est quelque chose à quoi penser dans la fonctionnalité.

J'aurais besoin de cette fonctionnalité pour.

Peut-être pourriez-vous introduire un jeton « saut ». Ainsi, si une règle renvoie ce jeton, il sera ignoré et n'obtiendra aucun nœud à l'AST (c'est-à-dire une entrée dans le tableau).

Je cherche aussi un moyen de le faire.

J'ai un gros fichier de grammaire (il analyse le format ASN.1 pour les fichiers SNMP MIB). Je ne l'ai pas écrit, mais je l'ai transformé trivialement à partir de la forme originale pour créer un analyseur dans PEG.js. (C'est bien. En fait, c'est extrêmement astucieux qu'il m'a fallu moins de 15 minutes pour le peaufiner afin que PEG.js l'accepte.)

Malheureusement, la grammaire a été écrite avec la possibilité d'ignorer simplement les espaces et les commentaires lorsqu'elle les rencontre. Par conséquent, aucun fichier MIB réel ne peut être traité, car l'analyseur s'arrête à la première occurrence d'espace.

Je ne suis pas impatient d'avoir à comprendre la grammaire pour pouvoir insérer tous les espaces blancs appropriés dans toutes les règles (il y a environ 126 productions...) Existe-t-il un autre moyen de le faire?

NB : Dans le cas où je dois modifier la grammaire à la main, j'ai demandé de l'aide pour certaines questions dans un ticket de la liste Google Groupes. http://groups.google.com/group/pegjs/browse_thread/thread/568b629f093983b7

Merci beaucoup!

Merci aux membres de Google Groupes. Je pense avoir suffisamment d'informations pour me permettre de faire ce que je veux.

Mais j'attends vraiment avec impatience la possibilité dans PEG.js de marquer les espaces/commentaires comme quelque chose à ignorer complètement afin que je n'aie pas à prendre quelques heures pour modifier une grammaire par ailleurs propre... Merci !

Riche

Je suis d'accord avec l'affirmation selon laquelle pegjs a besoin de la possibilité de sauter des jetons. Je vais peut-être me renseigner, car si vous voulez écrire une grammaire sérieuse, vous deviendrez fou en mettant ws entre chaque jeton.

Puisque les parseurs générés sont modulaires. Pour contourner le problème, créez un lexer simpliste et utilisez sa sortie comme entrée pour le for-real, par exemple :

elideWS.pegjs :

s = input:(whitespaceCharacter / textCharacter)*
{
var résultat = "" ;

for(var i = 0;i < input.length;i++) result += input[i];
renvoyer le résultat ;
}

whitespaceCharacter = [ nt] { return ""; }
textCharacter = c:[^ nt] { return c; }

mais cela pose des problèmes lorsque les espaces sont un délimiteur - comme pour les identifiants

Se heurter à ce problème assez souvent.
Mais ce n'est pas facile d'écrire un bon lexique (on peut finir par dupliquer une bonne partie de la grammaire initiale pour avoir un lexique cohérent).

Ce que je pensais, c'est pouvoir définir des règles de saut qui peuvent être utilisées comme alternatives chaque fois qu'il n'y a pas de correspondance. Cela introduit cependant le besoin d'une classe ininterrompue. Exemple avec arithmetics.pegjs utilisant des flotteurs

  = Term (("+" / "-") Term)*

Term
  = Factor (("*" / "/") Factor)*

Factor
  = "(" Expression ")"
  / Float

Float "float"
  = "-"? # [0-9]+ # ("." # [0-9]+) // # means that skip rules cannot match

// skip rule marked by "!="
// skip rules cannot match the empty string
_ "whitespace"
  != [ \t\n\r]+

Je digère encore ça. Tous les commentaires? Peut-être une idée très stupide.

Donc la différence est que vous voulez distinguer quand le moteur global est
fonctionnant en mode lexer (l'espace blanc est significatif) et lorsqu'il ne l'est pas (l'espace blanc est
ignoré).

Y a-t-il un cas où vous ne voulez pas ignorer les espaces en mode lexer
Comme une option? Ou inversement, quand vous n'êtes pas dans une regex ? Je pense que non.

Est-ce que ce qui suit serait équivalent?

Flotter
"-?[0-9]+("." [0-9]+)"

ou autrement étendre la cheville pour traiter les regex typiques directement et à l'extérieur
une chaîne entre guillemets (qui inclut des expressions régulières) les espaces blancs sont ignorés.

Le 19 avril 2014, à 15h22, Andrei Neculau [email protected] a écrit :

Se heurter à ce problème assez souvent.
Mais ce n'est pas facile d'écrire un bon lexique (on peut finir par dupliquer une bonne partie de la grammaire initiale pour avoir un lexique cohérent).

Ce que je pensais, c'est pouvoir définir des règles de saut qui peuvent être utilisées comme alternatives chaque fois qu'il n'y a pas de correspondance. Cela introduit cependant le besoin d'une classe ininterrompue. Exemple avec arithmetics.pegjs utilisant Floats

Expression
= Terme (("+" / "-") Terme)*

Terme
= Facteur (("_" / "/") Facteur)_

Facteur
= "(" Expression ")"
/ Flotter

Flotteur "flotteur"
= "-" ? # [0-9]+ # ("." # [0-9]+) // # signifie que les règles de saut ne peuvent pas correspondre

// ignore la règle marquée par "!="
// les règles de saut ne peuvent pas correspondre à la chaîne vide
_ "espace blanc"
!= [ tnr]+
Je digère encore ça. Tous les commentaires? Peut-être une idée très stupide.

-
Répondez directement à cet e-mail ou consultez-le sur GitHub.

@waTeim En fait non.

Traditionnellement, le processus d'analyse est divisé en lexing et en analyse. Pendant le lexage, chaque caractère est important, y compris les espaces. Mais ceux-ci sont ensuite convertis en un jeton « rejeter ». L'analyseur, lorsqu'il passe au jeton suivant, rejettera alors tous les jetons rejetés. La partie importante est que vous pouvez supprimer n'importe quoi, pas seulement les espaces. Ce comportement est exactement ce que @andreineculau décrit.

L'idée de base pour implémenter cela est de devoir vérifier en plus toutes les règles de rejet lors de la transition d'un état à l'autre.

Le 23 avril 2014, à 14 h 54, Sean Farrell [email protected] a écrit :

@waTeim En fait non.

Donc on est d'accord. L'approche traditionnelle est suffisante. Il n'y a pas besoin d'avoir
la partie strictement analyseur reconnaît l'existence de jetons rejetés et il y a
aucune raison de faire en sorte que la partie lexer se comporte de manière conditionnelle (de manière contextuelle)
en ce qui concerne la reconnaissance des jetons.

Par conséquent, il n'est pas nécessaire d'avoir des éléments de collage (par exemple '#') dans le langage
car il suffit que

1) les jetons peuvent être créés uniquement à partir de regex et ne sont pas contextuels.
2) les jetons peuvent être marqués pour être défaussés sans exception.

Traditionnellement, le processus d'analyse est divisé en lexing et en analyse. Pendant le lexage, chaque caractère est important, y compris les espaces. Mais ceux-ci sont ensuite convertis en un jeton « rejeter ». L'analyseur, lorsqu'il passe au jeton suivant, rejettera alors tous les jetons rejetés. La partie importante est que vous pouvez supprimer n'importe quoi, pas seulement les espaces. Ce comportement est exactement ce que @andreineculau décrit.

L'idée de base pour implémenter cela est de devoir vérifier en plus toutes les règles de rejet lors de la transition d'un état à l'autre.

-
Répondez directement à cet e-mail ou consultez-le sur GitHub.

Ok alors je t'ai mal compris. Il peut y avoir des cas pour les états lexer, mais c'est une exigence totalement différente et à mon humble avis en dehors de la portée de peg.js.

@waTeim @rioki Oubliez un peu ma suggestion.

Les mains sur, prenez cette règle . Si vous souhaitez simplifier la grammaire de la règle en supprimant le *WS , alors comment demanderiez-vous à PEGjs de ne pas autoriser *WS entre field_name et : ?

@andreineculau Parce que votre grammaire est sensible aux espaces, cela ne s'applique pas. Les jetons de rejet feraient partie de la grammaire, la partie lexicale pour être exact. Je ne sais pas quel est le gros problème ici, cela a déjà été suffisamment résolu dans les années 70. Chaque langue a ses propres jetons désactivables et où ils sont applicables. Les espaces et les commentaires font autant partie de la définition de la langue et donc de la grammaire. Il s'avère simplement qu'avec la plupart des langues, les jetons pouvant être ignorés peuvent se trouver entre tous les autres jetons et l'utilisation d'une règle de suppression rend les choses BEAUCOUP plus simples que d'écrire expr = WS lit WS op WS expr WS ";" pour chaque règle. Imaginez une grammaire comme celle du C avec la gestion des blancs ?

Je comprends que rétablir les règles de rejet dans pegjs n'est pas facile, mais cela ne signifie pas que ce n'est pas un objectif louable.

Oh mec, section de réponse gratuite ! J'ai beaucoup de choses à dire, désolé pour la longueur.

1) Pour les gens de TL;DR, si je pouvais ajouter des éléments de peg, je voulais, je l'aurais écrit comme ça

champ_en-tête
= field_name ":" field_value

espace (IGNORE)
= [t ]+

L'ajout que je ferais est une section d'options qui peut être incluse dans n'importe quelle production

Le langage http-bis ne serait pas limité par cette réécriture (voir annexe a).

2) Mon problème avec le # proposé

Vous avez l'impression que vous échangez en demandant à l'utilisateur de remplir la définition de l'analyseur avec un tas
de jeter les non-terminaux (généralement des espaces/délimiteurs) en obligeant l'utilisateur à remplir
la définition de l'analyseur avec un tas de méta-caractères « ici les caractères ne sont pas supprimés »
inutilement. Certes, il y aurait moins d'occurrences de cela. C'est le cas rare où
les gens consomment réellement des délimiteurs et font quelque chose avec eux, et comme je le commente dans

annexe a, HTTP-bis n'est pas une de ces occurrences, juste mal documentée.

3) États de l'analyseur définis par l'utilisateur

Mais je peux voir à quel point il serait plus facile pour le parseur de simplement couper et coller le
spécification de langue de la définition, donc si vous devez avoir quelque chose comme ça, alors
cela pourrait être fait avec des états lexicaux comme cela a été évoqué plus tôt par Sean. je pense que je le ferais
de la manière suivante.

production1(état==1)
= trucs

production2(état==2)
= trucs

fabrication3
= truc {état = 1}

fabrication4
= truc {état = 2}

En d'autres termes, tout comme lex/yacc permettent aux productions d'être uniquement disponibles

si le système est dans un état particulier, et permettre à l'utilisateur de définir cette valeur d'état.

4) Plus d'options

Ou vous pourriez le rendre plus facile pour l'utilisateur et plus évident pour le lecteur avec un autre
option

production(DONTIGNORE)
= trucs

Ce qui permettrait à l'analyseur de remplacer l'action par défaut de rejet des jetons marqués
comme mise au rebut, mais uniquement pour cette production. C'est vraiment la même chose que 3, juste un plus facile
lire. C'est moins flexible que la proposition # car soit une production est tout ignorée

ou pas ignorer, mais je ne pense pas qu'une flexibilité supplémentaire soit nécessaire.

5) L'ajout d'un paramètre à getNextToken() permet la sensibilité au contexte

Je pense que tout cela se résume à (je fais quelques hypothèses ici) actuellement, le
la partie de l'analyseur appelle getNextToken(input), et ce qui doit arriver à la place est d'ajouter un

paramètre à lui getNextToken (entrée, options).

Annexe a) Cette spécification HTTP-bis

Ok j'en ai lu mais je n'ai pas tout lu

Protocole de transfert hypertexte (HTTP/1.1) : syntaxe et routage des messages
brouillon-ietf-httpbis-p1-messagerie-26

Je n'aime pas la façon dont ils ont défini leur grammaire. Je ne suggère pas de changer l'entrée
accepte, mais je ne l'aurais pas défini comme ils l'ont fait. En particulier, je n'aime pas pourquoi ils ont
défini OWS et RWS et BWS qui correspondent tous exactement à la même chaîne de caractères
mais dans des contextes différents. ils ont défini

OWS ::== (SP | HTAB)*
RWS ::== (SP | HTAB)+
BWS ::== OWS

qui est juste une répétition de tabulations et d'espaces

Sans raison valable. Ils ont rendu la langue plus difficile à analyser - nécessitent l'analyseur lexical
pour suivre son contexte - et ils n'avaient pas besoin de le faire.

Ils ont défini OWS comme « espace blanc facultatif » BWS comme « mauvais espace blanc » ou autrement facultatif
espace mais dans le "mauvais" contexte - où ce n'est pas nécessaire - et RWS a requis un espace là où il est
nécessaire pour délimiter les jetons. Nulle part cet espace n'est utilisé sauf peut-être qu'il pourrait y avoir un analyseur
avertissement s'il correspond à BWS (« espaces blancs de fin inutiles détectés » ou quelque chose du genre) qui est tout
les délimiteurs le font de toute façon.

Dans leurs spécifications, le seul endroit où RWS est utilisé est ici

Via = 1#( reçu-protocole RWS reçu par [ RWS comment ] )

 received-protocol = [ protocol-name "/" ] protocol-version
                     ; see Section 6.7
 received-by       = ( uri-host [ ":" port ] ) / pseudonym
 pseudonym         = token

mais 'protocol-version' correspond aux nombres et peut-être aux lettres, tandis que 'received-by' correspond aux nombres et aux lettres. En d'autres termes,
l'analyseur lexical ne va pas reconnaître correctement ces 2 parties à moins qu'elles ne soient séparées par des espaces
et ça va être une erreur de syntaxe avec ou sans RWS explicitement identifié s'il n'y en a pas au moins 1
caractère d'espacement. Supprimez donc simplement RWS des productions et traitez les espaces blancs
partout comme délimiteur et cela ne change pas la langue, juste comment c'est documenté.

Le 24 avril 2014, à 13:23, Andrei Neculau [email protected] a écrit :

@waTeim @rioki Oubliez un peu ma suggestion.

Les mains sur, prenez cette règle. Si vous souhaitez simplifier la grammaire de la règle en supprimant l'OWS, comment demanderiez-vous à PEGjs de ne pas autoriser OWS entre field_name et : ?

-
Répondez directement à cet e-mail ou consultez-le sur GitHub.

@waTeim Je pense que vous allez

Je n'ai jamais vu d'utilisation appropriée des états lexer provenant de l'analyseur. Le problème fondamental ici est qu'avec une seule anticipation, lorsque l'analyseur voit le jeton changer d'état, le lexeur a déjà lexé par erreur le jeton suivant. Ce que vous proposez est presque impossible à mettre en œuvre sans retour en arrière et ce n'est jamais une bonne fonctionnalité dans un analyseur.

Lorsque vous écrivez une grammaire, vous définissez essentiellement quelles productions sont considérées comme analysées et ce qui peut être siroté. Dans l'exemple de @andreineculau , il existe deux options, soit vous gérez les espaces blancs dans l'analyseur, soit vous définissez la partie ":" de fin du jeton. ( [a-zA-Z0-9!#$%&'+-.^_|~]+ ":" ).

Je pourrais suggérer de transformer le problème en spécifiant une liste blanche - quelles parties je veux capturer et transformer - au lieu d'une liste noire. Bien que les espaces soient un problème avec le système de capture actuel, l'imbrication des règles en est un autre. Comme je l'ai écrit dans le numéro 66, le système LPeg consistant à spécifier ce que vous souhaitez capturer directement, via des transformations ou des captures de chaînes, me semble plus utile que de spécifier une poignée de productions à ignorer et de gérer l'imbrication de toutes les autres productions.

Voir mon commentaire dans le numéro 66 pour un exemple simple de LPeg contre PEG.js en ce qui concerne les captures. Bien que les noms soient un peu cryptiques, consultez la section Captures de la documentation LPeg pour les différentes manières de capturer ou de transformer une production donnée (ou une partie de celle-ci).

Bonjour, j'ai créé un extrait pour ignorer certains cas généraux : null , undefined et les chaînes avec uniquement des symboles d'espace.
Il peut être requis dans l'en-tête du fichier de grammaire, comme :

{
  var strip = require('./strip-ast');
}

Les deux manières de l'améliorer :

  • Filtre personnalisable pour les termes — pour ignorer les termes spécifiques nécessitant une certaine grammaire.
  • Ignorer les tableaux vides imbriqués — cela peut être fait à la deuxième étape après strip , cela supprimera les « pyramides » des tableaux vides imbriqués.
    Si quelqu'un est intéressé, nous pouvons le mettre à niveau vers un package.

@richb-hanover Où vos efforts d'analyseur de définition ASN.1 ont-ils atterri ?

@atesgoral - J'ai renfloué. Je n'avais pas besoin d'un "véritable analyseur" - j'avais seulement besoin d'isoler certains éléments nommés dans le fichier cible.

J'ai donc fait ce que n'importe quel type mauviette ferait - utiliser des expressions régulières. (Et puis j'ai eu deux problèmes :-)

Mais cela a fait l'affaire, j'ai donc pu passer au défi suivant. Bonne chance dans votre projet !

Après avoir jeté un coup d'œil à chevrotain et à son option de saut , quelque chose comme celui-ci est extrêmement souhaitable.

Trop souvent, nous nous retrouvons à écrire quelque chose comme ceci :

Pattern = head:PatternPart tail:( WS "," WS PatternPart )*
{
  return {
    type: 'pattern',
    elements: buildList( head, tail, 3 )
  };
}

Ce serait cool si nous pouvions écrire ceci à la place :

WS "whitespace" = [ \t\n\r] { return '@<strong i="11">@skipped</strong>' }

IgnoredComma = "," { return '@<strong i="12">@skipped</strong>' }

Pattern = head:PatternPart tail:( WS IgnoredComma WS PatternPart )*
{
  return {
    type: 'pattern',
    elements: [head].concat(tail)
  };
}

@richb-hanover, et tous ceux qui sont venus ici à la recherche d'un besoin similaire, j'ai fini par écrire mes propres analyseurs : https://www.npmjs.com/package/asn1exp et https://www.npmjs. com/package/asn1-tree

Un saut serait relativement facile à implémenter en utilisant es6 symbol , ou peut-être plus durablement en passant à l'analyseur un prédicat au moment de l'analyse (je préfère cette dernière option)

Je viens de tomber sur ça aussi.
Ne sachant rien des entrailles de PEG.js, laisse-moi jeter un os là-bas...

Lorsque nous écrivons une règle, à la fin de celle-ci, nous pouvons ajouter un bloc de retour.
Dans ce bloc, nous pouvons appeler des choses comme text() et location() . Ce sont des fonctions internes.

Quelque part dans le code, la valeur renvoyée de ce bloc va dans le flux de sortie.

Alors, que faudrait-il changer dans PEG.js si je veux ignorer une valeur renvoyée par une règle si cette valeur est le retour de l'appel d'une fonction locale skip ?

par exemple comment = "//" space ([^\n])* newline { return skip() }

Comme mentionné ci-dessus, skip() pourrait renvoyer un symbole, qui est ensuite vérifié par le code quelque part et supprimé.
Quelque chose comme ce que lzhaki a dit, mais interne à la bibliothèque

Je ne comprends pas votre question. Cherchez-vous un moyen de ne pas respecter une règle dans certaines circonstances ? Utilisez &{...} ou !{...} . Sinon, n'utilisez pas la valeur renvoyée de la règle comment :

seq = comment r:another_rule { return r; };
choice = (comment / another_rule) { <you need to decide what to return instead of "comment" result> };

Si cela aide quelqu'un, j'ignore les espaces blancs en faisant filtrer le tableau des résultats par ma règle de niveau supérieur.

Exemple:

    = prog:expression+ {return prog.filter(a => a)}

expression
    = float
    / number
    / whitespace

float
    = digits:(number"."number) {return parseFloat(digits.join(""),10)}

number 
    = digits:digit+ {return parseInt(digits.join(""),10)}

digit 
    = [0-9]

whitespace
    = [ \t\r\n] {return undefined}

Cela analysera joyeusement l'entrée tout en gardant les espaces blancs hors du tableau de résultats.
Cela fonctionnera également pour des choses comme les commentaires, il suffit de renvoyer la règle indéfinie et la règle de niveau supérieur la filtrera

Cela ne fonctionne que pour les productions de haut niveau. Vous devez filtrer manuellement chaque parent qui pourrait contenir un enfant filtrable.

@StoneCypher C'est vrai, cela nécessite un travail de haut niveau, mais cela fonctionne pour moi, et je pense que tant que le gammar n'est pas trop complexe, on devrait pouvoir s'en tirer avec un filtre de haut niveau.

En dehors de cela, tout ce à quoi je peux penser, c'est d'avoir une fonction de haut niveau qui filtre les espaces blancs de l'entrée et passe chaque correspondance à travers. Plus lent à coup sûr, et nécessite beaucoup plus d'appels, mais facile si vous (comme moi) transmettez tout dans un générateur de jetons. Vous pouvez appeler la fonction de filtrage à partir de laquelle vous générez des jetons, et n'avez qu'à vous soucier de générer vos jetons et l'espace blanc est plus ou moins automatiquement filtré

L'une des choses que j'ai aimées dans le HEAD actuel de pegjs est sa prise en charge (non documentée) de la sélection de champs sans avoir à créer d'étiquettes et à faire des déclarations de retour. Cela ressemble à ceci :

foo = <strong i="6">@bar</strong> _ <strong i="7">@baz</strong>
bar = $"bar"i
baz = $"baz"i
_ = " "*
parse('barbaz') // returns [ 'bar', 'baz' ]

J'ai l'impression que cela donne une syntaxe agréable, propre et explicite pour ce cas d'utilisation et un tas d'autres.

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

richb-hanover picture richb-hanover  ·  7Commentaires

futagoza picture futagoza  ·  6Commentaires

brettz9 picture brettz9  ·  8Commentaires

mattkanwisher picture mattkanwisher  ·  5Commentaires

mikeaustin picture mikeaustin  ·  7Commentaires