Je vois trois problèmes avec le système actuel de rapport d'erreurs dans PEG.js :
null
Reporting using `null` is inflexible (it doesn't allow to carry any information about the kind of error) and makes it impossible to return `null` as a regular value from actions, which would be useful sometimes (for example [in a JSON parser](https://github.com/dmajda/pegjs/blob/791034fad92c9cd7a9d1c71187df03441bbfd521/examples/json.pegjs#L45)).
Voir aussi #17.
For example, imagine a HTTP 1.1 parser that wants to report an error about missing `Host` header in a HTTP request with a message like "A HTTP 1.1 request must contain a Host header". Currently the only way to do this is to throw an exception with desired message manually. This is cumbersome and it does not interact well with the backtracking behavior (throwing an exception halts the parsing completely, even when it is possible to backtrack and try another alternatives).
expected
de SyntaxError
est difficile à traiter mécaniquementThe `expected` property of `SyntaxError` is either an array of expectations (each describing one alternative which the parser expected at the position of error) or `null` (meaning that the end of input was expected).
Chaque attente est représentée par une chaîne. Cette chaîne peut signifier un littéral attendu (représenté à l'aide de sa syntaxe PEG.js — une chaîne entre guillemets), une classe de caractères (représentée à l'aide de sa syntaxe PEG.js — [...]
) ou n'importe quel caractère (représenté par la chaîne "any"
). Pour différencier ces cas, il faut analyser manuellement les représentations de chaîne, ce qui rend inutilement difficile la création d'outils basés sur PEG.js tels que les autocompléteurs.
Je prévois de reconcevoir le système de rapport d'erreurs pour résoudre ces problèmes. Avant de décrire les changements auxquels je pense, une petite description du fonctionnement du système actuel est nécessaire.
Lorsqu'un analyseur PEG.js tente de faire correspondre un littéral de chaîne, une classe de caractères ou .
, et échoue, il produit un _échec de correspondance_. Il se compose d'une _position_ et d'une _attente_ (description de ce que l'analyseur a essayé de faire correspondre).
Après avoir produit un échec de correspondance, l'analyseur revient en arrière et essaie éventuellement d'autres alternatives. Si aucun d'entre eux ne réussit et qu'il n'y a plus rien à essayer, l'analyseur lève l'exception SyntaxError
. Lors de la définition de ses propriétés (telles que la position, les attentes, le message d'erreur, etc.), l'analyseur ne considère que l'échec de correspondance le plus éloigné (celui avec la plus grande position). S'il y a plus de tels échecs, leurs attentes sont combinées.
La situation est un peu plus compliquée si des règles nommées sont utilisées, mais cela n'est pas pertinent pour cette discussion.
Je pense aux changements suivants dans le système de rapport d'erreur :
SyntaxError.expected
ne seront pas représentées par des chaînes, mais par des objets. Les objets auront une propriété type
(avec des valeurs comme "literal"
, "class"
, "any"
, "eof"
) qui déterminera le type d'attente. Pour certains types d'attentes, d'autres propriétés contiendront les détails (par exemple pour l'attente "literal"
où serait une propriété value
contenant la chaîne attendue). Cela simplifiera le traitement mécanique des attentes.Une alternative à la propriété type
consiste à utiliser des classes. Mais je pense que la propriété type
sera plus facile à gérer pour les utilisateurs.
null
. Ce sera une valeur normale et ne signifiera pas une erreur.error
. Il prendra un message d'erreur comme paramètre. Les échecs déclenchés par cette fonction (appelés _échecs de correspondance personnalisée_) consisteront en une _position_ et un _message d'erreur_. Ils n'auront aucune attente.La fonction error
n'interrompra pas l'exécution de l'action, elle la marquera simplement comme ayant échoué et enregistrera le message d'erreur. L'échec réel ne se produira qu'après la fin de l'exécution de l'action. Si la fonction error
est invoquée plusieurs fois, la dernière invocation l'emportera (son message d'erreur sera utilisé).
Les échecs de correspondance personnalisés seront traités comme des échecs de correspondance normaux, ce qui signifie qu'ils n'arrêteront pas complètement l'analyse et laisseront l'analyseur revenir en arrière et éventuellement essayer d'autres alternatives. Il y a cependant une différence - lors de la levée finale de l'exception SyntaxError
, la règle de combinaison des attentes qui s'applique aux échecs de correspondance réguliers ne s'appliquera pas aux échecs personnalisés. Si dans l'ensemble des échecs de correspondance avec la position la plus éloignée, il y a au moins un échec personnalisé, il remplacera simplement complètement les échecs réguliers. S'il y a plus d'échecs personnalisés, celui qui a été produit en dernier l'emportera.
Une exception SyntaxError
basée sur un échec de correspondance personnalisée sera différente des exceptions basées sur un échec normal. Sa propriété message
sera égale au message d'erreur de l'échec et sa propriété expected
sera null
.
Exemple
```
début = signe : [+-] ? chiffres :[0-9]+ {
var result = parseInt((sign || "") + digits.join(""), 10);
if (result % 2 == 0) {
error("The number must be an odd integer.");
}
return result;
}
```
À l'entrée 2
, l'analyseur généré à partir de la grammaire ci-dessus produira un SyntaxError
avec message
défini sur "The number must be an odd integer."
et expected
défini sur null
expected
. Il prendra une description de valeur attendue comme paramètre. Cette fonction sera fournie principalement comme une commodité pour les situations où l'on n'a pas besoin de générer un message d'erreur complet et de générer automatiquement le formulaire "Expected _X_ but "2" found." est assez.Une exception SyntaxError
basée sur un échec de correspondance généré par la fonction expected
sera similaire aux exceptions basées sur un échec normal.
Exemple
```
début = signe : [+-] ? chiffres :[0-9]+ {
var result = parseInt((sign || "") + digits.join(""), 10);
if (result % 2 == 0) {
expected("odd integer");
}
return result;
}
```
À l'entrée 2
, l'analyseur généré à partir de la grammaire ci-dessus produira un SyntaxError
avec message
défini sur "Expected odd integer but "2" found."
et expected
défini sur [ { type: "user", description: "odd integer" } ]
J'accueille toutes les notes sur les modifications proposées - veuillez les ajouter en tant que commentaires. Je prévois de commencer à mettre en œuvre la proposition (ou une version modifiée basée sur les commentaires) bientôt.
Il serait préférable que plusieurs invocations de la fonction d'erreur conduisent à plusieurs erreurs. Vous pouvez ensuite continuer à analyser autant que possible.
string = '"' value:(!(eol / '"') .)+ '"' { return value; }
/ '"' value:(!(eol / '"') .)+ { error('unterminated string constant'); return value; }
Je recommanderais également d'ajouter la prise en charge des avertissements.
Il serait préférable que plusieurs invocations de la fonction d'erreur conduisent à plusieurs erreurs. Vous pouvez ensuite continuer à analyser autant que possible.
Pourriez-vous nommer des cas d'utilisation où vous en auriez besoin ? Il me semble que l'exemple que vous avez fourni fonctionnerait également avec ma proposition.
J'ai déjà quelques cas d'utilisation, mais je ne sais pas à quel point ils sont représentatifs, alors j'aimerais en voir d'autres.
Je recommanderais également d'ajouter la prise en charge des avertissements.
J'y pense aussi.
Un problème avec plusieurs erreurs et avertissements est qu'ils auraient besoin d'une interface différente de la simple et intuitive « parse
renvoie une valeur en cas de succès ou une exception en cas d'erreur ». L'analyseur devrait signaler plusieurs erreurs en cas d'échec de l'analyse, ainsi que des avertissements en cas d'analyse réussie et non réussie.
Quel type d'API trouveriez-vous le plus intuitif ici ? Encore une fois, j'ai quelques idées, mais j'aimerais voir ce que les autres en pensent.
Pourriez-vous nommer des cas d'utilisation où vous en auriez besoin ? Il me semble que l'exemple que vous avez fourni fonctionnerait également avec ma proposition.
Mon principal cas d'utilisation concerne les erreurs d'analyse non fatales. Chaînes non terminées, identifiants commençant par un chiffre, commentaires imbriqués, points-virgules manquants, etc.
Généralement, il est préférable de laisser l'analyseur avancer aussi loin que possible, afin que le plus d'erreurs possible puisse être montré à l'utilisateur. Si l'analyseur pouvait même terminer l'analyse et laisser les étapes suivantes (par exemple la compilation) signaler également les erreurs, ce serait formidable.
C'est désormais implémenté . Le script tools/impact
signale l'impact suivant sur les performances de l'ensemble des commits :
Speed impact
------------
Before: 1144.21 kB/s
After: 999.89 kB/s
Difference: -12.62%
Size impact
-----------
Before: 863523 b
After: 1019968 b
Difference: 18.11%
(Measured by /tools/impact with Node.js v0.6.18 on x86_64 GNU/Linux.)
Je pense qu'une pénalité de vitesse de 12,62 % et une pénalité de taille de 18,11 % suffisent pour résoudre un ensemble de problèmes aussi anciens.
Fermeture.
@dmajda : C'est une excellente nouvelle ! Je suis tellement content que null
ne signale plus l'échec.
Donc, il n'y a pas encore de fonction "warning" ?
Le sujet de la fonction d'avertissement est suivi au #325
Commentaire le plus utile
Donc, il n'y a pas encore de fonction "warning" ?