Pegjs: règle "inattendue"

Créé le 23 mars 2019  ·  25Commentaires  ·  Source: pegjs/pegjs

  • Demande de fonctionnalité :

Ajoutez une règle "inattendue" pour remplacer le message d'erreur standard.

unexpected = 
      keywords { return "Unexpected keyword "+text()+"."; }
    / expression { return "Unexpected expression «"+text()+"»."; }
    / lamdba_function { return "Unexpected lambda function."; } ;

Pour l'implémenter, changez simplement peg$buildError par :

  function peg$buildError() {
    var expected = peg$expected[0];
    var failPos = expected.pos;

    // if "unexpected" rule exist this might throw the appropriate error.
    if (typeof peg$parseunexpected !== 'undefined') {
      // make peg$expect temporary unavailable, set the cursor position to the fail position
      // next get the output of the rule, if it's a string, return it.
      const tmp = peg$expect;
      peg$expect = new Function();
      peg$currPos = failPos;
      const unexpected = peg$parseunexpected();
      peg$expect = tmp;
      if (typeof unexpected === 'string') {
        const length = peg$currPos - failPos;
        const location = failPos < input.length
        ? peg$computeLocation(failPos, failPos + length)
        : peg$computeLocation(failPos, failPos);
        return new peg$SyntaxError(unexpected, expected.variants, unexpected, location);
      }
    }
    // else return standard error.
    const unexpected = input.charAt(failPos);
    const location = failPos < input.length
        ? peg$computeLocation(failPos, failPos + 1)
        : peg$computeLocation(failPos, failPos);
    return new peg$SyntaxError(
      peg$SyntaxError.buildMessage(expected.variants, unexpected), expected.variants, unexpected, location);
  }

Comportement attendu:
Améliorer la gestion des erreurs.

Si ce n'est pas éthique, y a-t-il quelqu'un qui peut me dire comment créer un plugin qui ferait le changement ?
Merci beaucoup

feature

Commentaire le plus utile

Juste un peu de bikeshedding, mais lorsqu'une telle fonctionnalité est implémentée, l'utilisateur doit laisser le choix de la règle à définir comme règle "inattendue", par exemple avec

pegjs.generate( grammar, { unexpected: "unrecognised_token" } )

Tous les 25 commentaires

Je suis d'accord que cette fonctionnalité pourrait être ajoutée, mais jusqu'à présent, une façon de le faire est d'utiliser la fonction error(...) .

Par exemple, vous avez quelque chose qui ne peut pas correspondre :

  = FirstMatch
  / AnotherMatch

Vous pouvez ajouter l'expression suivante, en faisant correspondre chaque caractère pour vous assurer qu'aucune autre correspondance ne sera effectuée :

  =   .+
  {
    error("Unexpected thing: " + text().substring(0,1));
  }

Et puis vous pourriez l'appeler quand quelque chose qui devrait correspondre ne correspond pas.

  = FirstMatch
  / AnotherMatch
  / UnexpectedThing

votre règle UnexpectedThing ne peut pas gérer les mots-clés, expressions ou autres atomes de votre grammaire, elle ne renvoie qu'un seul caractère : text().substring(0,1) , donc ce que vous faites est exactement la même chose que l'analyseur pegjs produit. La fonctionnalité que j'ai proposée est un peu différente car elle gère toutes les règles spécifiées dans la règle unexpected à n'importe quel point de l'entrée, de manière totalement implicite et non redondante.

Voici un exemple de ce que le code ci-dessus produira pour mon analyseur dactylographié :
Saisir:

public function some_func(){
     public function wtf_here_func(){
     }
}

Erreur de sortie :

Ligne X, Colonne 4 : Attendu... mais "p" trouvé.

Erreur de sortie : (avec fonctionnalité de règle inattendue)

Ligne X, Colonne 4 : déclaration de méthode "wtf_here_func" inattendue.

La seule chose que j'ai ajoutée à ma grammaire est :

unexpected = m:method_declaration { return `Unexpected "${m.identifier}" method declaration.` };

Je remarque que l'utilisation de la fonction error est plus élégante que l'instruction return, vous avez raison. Donc ce qui précède devrait devenir :

unexpected = m:method_declaration { error(`Unexpected "${m.identifier}" method declaration.`) };

Essayez-le vous-même et vous verrez tout ce qu'il peut apporter à nos parseurs.

Vous pouvez toujours écrire UnexpectedThing si nécessaire. En fait, vous le faites déjà, nommez-le simplement unexpected . Mais dans votre variante implicite, vous ne pouvez pas avoir différentes règles inattendues pour différentes parties de la grammaire

@Mingun
Ce que vous dites est faux, vous pouvez remplacer ma "règle inattendue" implicite en ajoutant un "UnexpectedThing" dans la règle concernée, comme vous l'avez dit vous-même.
Une règle implicite est tout à fait nécessaire, voici un exemple concret, la règle de déclaration de méthode :

method_declaration = (privacy __)? "function" _ identifier _ '(' _ args _ ')' _ '{' _ instruction* _ '}';

Avec l'explicite UnexpectedThing à la fin :

method_declaration = (privacy __)? "function" _ identifier _ '(' _ args _ ')' _ '{' _ instruction* _ '}' / UnexpectedThing;

Cela ne fonctionne pas si la chose inattendue apparaît entre "fonction" et l'identifiant, ou entre args et ')' etc.
Donc tu devrais faire ceci :

method_declaration = (privacy __)? ("function"/UnexpectedThing) _ identifier _ ('('/UnexpectedThing) _ args _ (')'/UnexpectedThing) _ ('{'/UnexpectedThing) _ instruction* _ ('}'/UnexpectedThing) / UnexpectedThing;

privacy = ... / UnexpectedThing;
_ = ... / UnexpectedThing;
identifier = ... / UnexpectedThing;
instruction = ... / UnexpectedThing;

...

Pourquoi c'est mauvais ?

  • La complexité de la grammaire croît en O(n*2) au lieu de O(n)
  • Le message d'erreur confond les règles attendues pour et les règles inattendues, comme ceci :

Attendu ... ou UnexpectedThing1, UnexpectedThing2..., mais trouvé "x".

Comprenez-vous mon argument maintenant?
Peu importe que vous utilisiez mon code ou non, nous avons besoin d'une règle implicite pour nos analyseurs.

Si vous voulez juste au lieu d'un symbole dans le message Attendu... mais trouvé X pour voir un mot Attendu... mais trouvé XXX , cela devient élémentaire et ne demande aucune modification. Il suffit d'attraper SyntaxError et d'analyser l'entrée à partir de la position d'erreur avec un analyseur spécial "lexer". Vous pouvez même le définir dans le même fichier que la grammaire principale et réutiliser certaines règles. Dans le code :

let parser = PEG.generate(<main grammar>);
// For correct work this parser must parse any input and return string as result
let lexer = PEG.generate(<lexer grammar>);
try {
  return parser.parse(<input>);
} catch (e) {
  if (!(e instanceof parser.SyntaxError)) throw e;

  // lexer must return string
  let found = lexer.parse(input.substr(e.location.start.offset));
  // Or you can use specific rule from the same parser
  //let found = parser.parse(input.substr(e.location.start.offset), { startRule: "unexpected" });
  throw new parser.SyntaxError(
    parser.SyntaxError.buildMessage(e.expected, found),
    e.expected,
    found,
    e.location
  );
  // or you can throw you own exception type
}

L'introduction d'un support spécial dans le générateur à cet effet me semble excessif bien que je ne sois pas contre qu'il y ait une annotation qui marquera la règle comme point d'entrée du lexer ~quand~ si les annotations seront implémentées.

C'est une solution certes, mais trop peu accessible et difficile à maintenir. En tout cas merci pour ton code, c'est toujours bon à prendre.

Je suis d'accord que l'implémentation directe d'un lexer dans le générateur serait la bienvenue.
Quelles préoccupations avez-vous pour que les annotations ne puissent pas être mises en œuvre ?

Quelles préoccupations avez-vous pour que les annotations ne puissent pas être mises en œuvre ?

Malheureusement, comme vous pouvez le voir, le projet est mort ou, du moins, dans une profonde stazis

En fait, j'aime l'idée d'une règle unexpected , mais je pense la mettre en option via l'option features . Ça te va @log4b0at ?

@futagoza Sûrement

@log4b0at - La règle unexpected existe déjà.

Le problème avec une règle inattendue spécialisée sur la nature de la chose qu'elle n'a pas réussi à analyser est qu'elle ne sait pas ce que c'est, car elle n'a pas réussi à l'analyser.

Considérez la grammaire suivante :

Document = (DadJoke "\n"?)+

Kind = [a-zA-Z0-9 ]+

Car    = "car: "    Kind ".";
Insect = "insect: " Kind ".";
Annoy  = "annoy: "  Kind ".";

DadJoke = Car / Insect / Annoy

(C'est une blague de papa car, évidemment, la bonne réponse pour chaque règle est "bug".)

Cela devrait sans problèmes analyser l'entrée comme

car: Honda
insect: Beetle
annoy: Whine

Il y a deux façons de lire en manipulant le unexpected . Soit c'est que la règle du transporteur est erronée et vous vous en occupez, soit la règle subsumée est erronée et vous vous en occupez.

Si je comprends bien, ce que vous demandez est une règle pour unexpected qui vous permet de donner une sortie différente selon que ce qui est inattendu était une voiture, un insecte ou une gêne.

Supposons que vous ayez une spécialisation telle que :

const UnexpectedCustoms = { // no, not shoes off
  'annoy'  : 'Unexpected annoyance',
  'insect' : 'Unexpected insect',
  'car'    : 'Unexpected car'
};

Alors, que devrait-il donner comme message d'erreur lorsque je lui donne cette entrée?

defect: bug
microphone: bug
disease: bug
hobbyist: bug

Parmi ces voitures, lesquelles ?

Quels devraient être les messages d'erreur ?

Ce n'est pas résoluble. Ce problème équivaut à dire "hé peg, étant donné que la prochaine chose ne peut pas être interprétée, pourquoi ne me dites-vous pas ce que c'est pour que je puisse le dire à quelqu'un ?"

Vous devez d'abord lui apprendre à interpréter cela. Ensuite, vous n'avez besoin de rien.

C'est, en substance, la raison pour laquelle certaines choses sont configurées comme un tokenizer puis un lexer. Utilisez simplement peg comme tokenizer, dans ce cas, puis écrivez un lexer qui analyse l'AST généré et dit "euh, vous êtes ... vous n'êtes pas autorisé à avoir une parenthèse fermante sans une ouverture "


Alternativement, s'il s'agit de la règle subsumée, plutôt que de la règle du transporteur, vous avez à la place une entrée du formulaire

car: car: car: ...

Existe-t-il un moyen pour pegjs de savoir qu'il s'agit d'une voiture, autre que d'écrire la règle que vous pouvez déjà écrire pour faire correspondre cela, puis de la gérer ?

C'est assez simple à gérer dans la grammaire aujourd'hui, et de nombreuses grammaires le font. Pourquoi voulez-vous des fonctionnalités supplémentaires pour cela ?

Écrivez simplement une règle avec le nom de la fonctionnalité que vous demandez. Pow : c'est fait.

Aucune complexité supplémentaire peg.js n'est nécessaire.


Voici l'autre façon de le dire.

Afin de donner un message d'erreur pour l'analyse incorrecte spécifique, vous auriez besoin d'une analyse correcte des éléments incorrects à interpréter. Soit vous écrivez un analyseur qui accepte les mauvaises choses et les rejette dans les gestionnaires, soit vous écrivez un analyseur secondaire pour gérer le partiel, soit vous écrivez un AST qui peut accepter la mauvaise chose puis interpréter l'AST comme étant faux."


Enfin, cela ne devrait vraiment pas être fait, car unknown est le cinquième ou le sixième nom existant le plus courant pour une règle, après des choses comme document et operator

Je garantirais que plus d'un tiers des grammaires l'ont déjà, car la langue est déjà capable de l'exprimer sans fonctionnalités

Si vous essayez d'ajouter ceci, tout ce que vous faites est de casser les grammaires existantes pour ajouter quelque chose que nous avons déjà

Cela devrait être refusé

@Mingun - Je veux ressusciter ce projet. Il n'y a aucune bonne raison pour qu'il soit mort

Je pense que cette fonctionnalité est destinée à rendre les erreurs plus claires, même si une règle unexpected n'est peut-être pas la bonne façon d'implémenter ce genre de chose.

Le fait est qu'avoir une erreur Unexpected X semble rendre plus pratique la détection des erreurs d'analyse (au moins dans de nombreux cas) plutôt que d'avoir Expected A, B, C, D, E, X, Y or Z ou Expected expression .

Ce serait formidable de voir PEG.js être capable de faire une telle chose.

Alors, comment identifiez-vous ce qu'est le X qui est inattendu ?

C'est probablement le problème de cette fonctionnalité : être capable d'identifier clairement ce qui ne va pas.

Le problème ici n'est pas d'essayer d'identifier ce que X pourrait être, mais d'être sûr de ce qu'est X.

Comme j'ai essayé de l'expliquer ci-dessus, cela s'appelle "l'analyse syntaxique", et la façon de le faire est de le spécifier dans la grammaire

Je ne comprends pas bien le problème que vous essayez de soulever, pouvez-vous me donner plus d'exemples ?

Ils seront littéralement identiques à celui existant.

Essayez de répondre à la question. C'est là socratiquement et rhétoriquement; vous devriez apprendre quel est le problème en essayant de répondre.


Considérez la grammaire suivante :

Document = (DadJoke "\n"?)+

Kind = [a-zA-Z0-9 ]+

Car    = "car: "    Kind ".";
Insect = "insect: " Kind ".";
Annoy  = "annoy: "  Kind ".";

DadJoke = Car / Insect / Annoy

Alors, que devrait-il donner comme message d'erreur lorsque je lui donne cette entrée?

defect: bug
microphone: bug
disease: bug
hobbyist: bug

Si je comprends bien votre demande, l'analyseur est censé dire quelque chose comme "J'ai trouvé une maladie alors que j'attendais une voiture, un insecte ou une gêne".

Comment est-il censé savoir que c'est une maladie ?

L'analyse échoue lorsque l'analyseur ne sait pas quelle est la prochaine chose.

Un message d'erreur pour l'échec de l'analyse qui l'oblige à savoir quelle est la prochaine chose est contradictoire avec la situation contextuelle

defect: bug
microphone: bug
disease: bug
hobbyist: bug

"Quel devrait être le message d'erreur ?"

Vous obtenez cela avec la gestion des erreurs réelle :

Ligne 1, colonne 1 : "ennuyeux : ", "voiture : " ou "insecte : " attendu, mais "d" trouvé.

Si je définis une règle inattendue comme ça

Identifier = [a-zA-Z]+;

unexpected = 
    DadJoke { error("Unexpected dad joke here"); }
/ i:$Identifier { error(`Unexpected identifier "${i}"`); };

j'aurai

Ligne 1, colonne 1 : Identifiant inattendu "défaut"

"Alternativement, s'il s'agit de la règle subsumée, plutôt que de la règle du transporteur, vous avez à la place une entrée du formulaire"
car: car: car:

Ici, vous obtiendrez le message par défaut, car aucune correspondance de règle inattendue ":" punctuator

Ligne 1, colonne 8 : Attendu... blabla... mais trouvé ":"

De plus, le processus de détection des choses inattendues est totalement passif et ne se produit que lorsque les pegjs détectent une erreur, et n'ajoutent aucun surcoût en termes de performances.

Est-ce que cela répond correctement à votre question ?

si vous voulez essayer vous-même, j'ai rapidement fait un code pour la version 0.10 de pegjs, remplacez (dans votre analyseur) peg$buildStructuredError par

function peg$buildStructuredError(expected, found, location) {
    if (typeof peg$parseunexpected !== 'undefined') {
        peg$fail = new Function();
        peg$currPos = location.start.offset;
        peg$parseunexpected();
    }
    return new peg$SyntaxError(peg$SyntaxError.buildMessage(expected, found), expected, found, location);
}

N'est-ce pas à peu près ce qu'a dit Mingun en 2019 ?

Maintenant j'ai peur d'avoir mal compris quelque chose ici

Utiliser un tokenizer a un coût.
une telle fonctionnalité est simple à mettre en œuvre et ne coûte rien à personne

d'accord, c'est un point juste

Juste un peu de bikeshedding, mais lorsqu'une telle fonctionnalité est implémentée, l'utilisateur doit laisser le choix de la règle à définir comme règle "inattendue", par exemple avec

pegjs.generate( grammar, { unexpected: "unrecognised_token" } )

Bonjour, Je viens de faire une pull request pour cette fonctionnalité, suite à vos conseils, à savoir l'utilisation de la fonction d'erreur, beaucoup plus cohérente qu'un retour, suggérée par @norech.
Et l'utilisation d'une option pour changer le nom de la règle, suggérée par @Seb35
Comme @futagoza l'a dit, la fonctionnalité est facultative et est désactivée par défaut. (Je ne connais pas l'option fonctionnalités mais par défaut il n'y a pas de règle inattendue)
Voir le pull #661

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