Comme indiqué dans le titre, quel type de données un lexer doit-il retourner / donner à l'analyseur? En lisant l' article sur l' analyse lexicale de Wikipédia, il a déclaré que:
En informatique, l'analyse lexicale est le processus de conversion d'une séquence de caractères (comme dans un programme informatique ou une page Web) en une séquence de jetons ( chaînes avec une «signification» identifiée).
Cependant, en totale contradiction avec la déclaration ci-dessus, lorsqu'une autre question que j'ai posée sur un autre site ( Code Review si vous êtes curieux) a été répondue, la personne qui a répondu a déclaré que:
Le lexer lit généralement la chaîne et le convertit en un flux ... de lexèmes. Les lexèmes doivent seulement être un flux de nombres .
et il a donné ce visuel:
nl_output => 256
output => 257
<string> => 258
Plus Flex
loin dans l'article, Il a mentionné , un lexer déjà existant, et a dit que l'écriture de «règles» avec lui serait plus simple que d'écrire un lexer à la main. Il a ensuite donné cet exemple:
Space [ \r\n\t]
QuotedString "[^"]*"
%%
nl_output {return 256;}
output {return 257;}
{QuotedString} {return 258;}
{Space} {/* Ignore */}
. {error("Unmatched character");}
%%
Pour approfondir mes connaissances et obtenir plus d'informations, j'ai lu l'article Wikipedia sur Flex . l'article Flex a montré que vous pouviez définir un ensemble de règles de syntaxe, avec des jetons, de la manière suivante:
digit [0-9]
letter [a-zA-Z]
%%
"+" { return PLUS; }
"-" { return MINUS; }
"*" { return TIMES; }
"/" { return SLASH; }
"(" { return LPAREN; }
")" { return RPAREN; }
";" { return SEMICOLON; }
"," { return COMMA; }
"." { return PERIOD; }
":=" { return BECOMES; }
"=" { return EQL; }
"<>" { return NEQ; }
"<" { return LSS; }
">" { return GTR; }
"<=" { return LEQ; }
">=" { return GEQ; }
"begin" { return BEGINSYM; }
"call" { return CALLSYM; }
"const" { return CONSTSYM; }
"do" { return DOSYM; }
"end" { return ENDSYM; }
"if" { return IFSYM; }
"odd" { return ODDSYM; }
"procedure" { return PROCSYM; }
"then" { return THENSYM; }
"var" { return VARSYM; }
"while" { return WHILESYM; }
Il me semble que le lexer Flex renvoie des chaînes de mots-clés \ tokens. Mais il pourrait s'agir de constantes de retour égales à certains nombres.
Si le lexer devait retourner des nombres, comment lirait-il les littéraux de chaîne? renvoyer un nombre est très bien pour des mots clés uniques, mais comment traiteriez-vous une chaîne? Le lexeur n'aurait-il pas à convertir la chaîne en nombres binaires, puis l'analyseur reconvertirait les nombres en chaîne. Il semble beaucoup plus logique (et plus facile) pour le lexer de renvoyer des chaînes, puis de laisser l'analyseur convertir les littéraux de chaîne de nombres en nombres réels.
Ou le lexer pourrait-il retourner les deux? J'ai essayé d'écrire un lexer simple en c ++, qui vous permet d'avoir un seul type de retour pour vos fonctions. Me conduisant ainsi à poser ma question.
Pour condenser ma question en un paragraphe: lors de l'écriture d'un lexer, et en supposant qu'il ne pourrait renvoyer qu'un seul type de données (chaînes ou nombres), quel serait le choix le plus logique?
la source
Réponses:
Généralement, si vous traitez une langue par lexing et analyse, vous avez une définition de vos jetons lexicaux, par exemple:
et vous avez une grammaire pour l'analyseur:
Votre lexer prend le flux d'entrée et produit un flux de jetons. Le flux de jetons est consommé par l'analyseur pour produire un arbre d'analyse. Dans certains cas, il suffit de connaître le type de jeton (par exemple, LPAREN, RBRACE, FOR), mais dans certains cas, vous aurez besoin de la valeur réelle associée au jeton. Par exemple, lorsque vous rencontrez un jeton d'ID, vous voudrez les caractères réels qui composent l'ID plus tard lorsque vous essayez de déterminer l'identifiant que vous essayez de référencer.
Donc, vous avez généralement quelque chose de plus ou moins comme ceci:
Ainsi, lorsque le lexer renvoie un jeton, vous savez de quel type il s'agit (dont vous avez besoin pour l'analyse) et de la séquence de caractères à partir de laquelle il a été généré (dont vous aurez besoin plus tard pour interpréter les chaînes et les littéraux numériques, les identifiants, etc.). Il peut sembler que vous renvoyez deux valeurs, car vous renvoyez un type d'agrégat très simple, mais vous avez vraiment besoin des deux parties. Après tout, vous voudriez traiter différemment les programmes suivants:
Ceux-ci produisent la même séquence de types de jetons : IF, LPAREN, NUMBER, GREATER_THAN, NUMBER, RPAREN, LBRACE, ID, LPAREN, STRING, RPAREN, SEMICOLON, RBRACE. Cela signifie qu'ils analysent également la même chose. Mais lorsque vous faites réellement quelque chose avec l'arbre d'analyse, vous vous soucierez que la valeur du premier nombre est '2' (ou '0') et que la valeur du deuxième nombre soit '0' (ou '2 ') et que la valeur de la chaîne est' 2> 0 '(ou' 0> 2 ').
la source
String value
va-t-il être rempli? sera-t-il rempli d'une chaîne ou d'un nombre? Et aussi, comment définir leString
type?parse(inputStream).forEach(token -> print(token.string); print(' '))
(c'est- à- dire qu'il suffit d'imprimer les valeurs de chaîne des jetons, séparées par un espace). C'est assez rapide. Et même si LPAREN ne peut provenir que de "(", cela pourrait être une chaîne constante en mémoire, donc y inclure une référence dans le jeton pourrait ne pas coûter plus cher que d'inclure la référence nulle. En général, je préfère écrire code qui ne fait pas de moi un cas spécial"Token", évidemment. Un lexer produit un flux de jetons, il doit donc renvoyer un flux de jetons .
Les lexers générés par machine ont l'avantage que vous pouvez les générer rapidement, ce qui est particulièrement pratique si vous pensez que votre grammaire lexicale va beaucoup changer. Ils ont l'inconvénient que vous n'avez souvent pas beaucoup de flexibilité dans vos choix d'implémentation.
Cela dit, peu importe si c'est "plus simple"? Écrire le lexer n'est généralement pas la partie la plus difficile!
Ni. Un lexer a généralement une "prochaine" opération qui renvoie un jeton, il doit donc retourner un jeton . Un jeton n'est pas une chaîne ou un nombre. C'est un signe.
Le dernier lexer que j'ai écrit était un lexer de «fidélité totale», ce qui signifie qu'il a renvoyé un jeton qui a suivi l'emplacement de tous les espaces et commentaires - que nous appelons «anecdotes» - dans le programme, ainsi que le jeton. Dans mon lexer, un jeton était défini comme:
Trivia a été défini comme:
Donc, si nous avions quelque chose comme
qui LEX quatre jetons avec les types de jeton
Identifier
,Plus
,Identifier
,Semicolon
, et les largeurs 3, 1, 3, 1. Le premier identifiant a conduit consistant en AnglaisWhitespace
avec une largeur de 4 et de fuite AnglaisWhitespace
avec une largeur de 1. LePlus
n'a pas conduit Anglais et trivia de fin composé d'un espace blanc, d'un commentaire et d'une nouvelle ligne. L'identifiant final a un trivial principal d'un commentaire et un espace, et ainsi de suite.Avec ce schéma, chaque caractère du fichier est pris en compte dans la sortie du lexer, qui est une propriété pratique à avoir pour des choses comme la coloration de la syntaxe.
Bien sûr, si vous n'avez pas besoin de l'info, vous pouvez simplement créer un jeton deux choses: le type et la largeur.
Vous remarquerez peut-être que le jeton et les anecdotes ne contiennent que leur largeur, pas leur position absolue dans le code source. C'est délibéré. Un tel schéma présente des avantages:
Si vous ne vous souciez d'aucun de ces scénarios, un jeton pourrait être représenté comme une sorte et un décalage, plutôt que comme une sorte et une largeur.
Mais le point clé à retenir est que la programmation est l'art de faire des abstractions utiles . Vous manipulez des jetons, faites donc une abstraction utile sur les jetons, puis vous pouvez choisir vous-même les détails d'implémentation qui le sous-tendent.
la source
Généralement, vous renvoyez une petite structure qui a un nombre signifiant le jeton (ou une valeur énumérée pour une facilité d'utilisation) et une valeur facultative (chaîne, ou éventuellement valeur générique / basée sur un modèle). Une autre approche serait de renvoyer un type dérivé pour les éléments qui doivent transporter des données supplémentaires. Les deux sont des solutions assez désagréables, mais assez fines à un problème pratique.
la source
Token *
ou simplement unToken
, ou unTokenPtr
qui est un pointeur partagé deToken
classe. Mais je vois également un lexer renvoyer juste un TokenType et stocker la valeur de chaîne ou de nombre dans d'autres variables globales ou statiques. Une autre question est la suivante: comment pouvons-nous stocker les informations de localisation, dois-je avoir une structure Token contenant des champs TokenType, String et Location? Merci.struct Token {TokenType id; std::string lexeme; int line; int column;}
, non? Pour une fonction publique de Lexer, telle quePeekToken()
, la fonction peut renvoyer unToken *
ouTokenPtr
. Je pense que pendant un certain temps, si la fonction renvoie simplement le TokenType, comment l'analyseur essaie-t-il d'obtenir les autres informations sur le Token? Donc, un pointeur comme le type de données est préférable pour le retour d'une telle fonction. Des commentaires sur mon idée? Merci