Les analyseurs normaux tels qu'ils sont généralement enseignés ont une étape de lexer avant que l'analyseur touche l'entrée. Le lexer (également «scanner» ou «tokenizer») coupe l'entrée en petits jetons annotés avec un type. Cela permet à l'analyseur principal d'utiliser des jetons comme éléments terminaux plutôt que d'avoir à traiter chaque personnage comme un terminal, ce qui entraîne des gains d'efficacité notables. En particulier, le lexer peut également supprimer tous les commentaires et espaces blancs. Cependant, une phase de tokenizer distincte signifie que les mots-clés ne peuvent pas également être utilisés comme identifiants (à moins que le langage ne prenne en charge le stropping qui est quelque peu tombé en disgrâce, ou préfixe tous les identifiants avec un sigil $foo
).
Pourquoi? Supposons que nous ayons un simple jeton qui comprend les jetons suivants:
FOR = 'for'
LPAREN = '('
RPAREN = ')'
IN = 'in'
IDENT = /\w+/
COLON = ':'
SEMICOLON = ';'
Le tokenizer correspondra toujours au jeton le plus long et préférera les mots clés aux identifiants. Alors interesting
sera lexed comme IDENT:interesting
, mais in
sera lexed que IN
, jamais IDENT:interesting
. Un extrait de code comme
for(var in expression)
sera traduit dans le flux de jetons
FOR LPAREN IDENT:var IN IDENT:expression RPAREN
Jusqu'à présent, cela fonctionne. Mais toute variable in
serait lexée comme mot clé IN
plutôt que comme variable, ce qui casserait le code. Le lexer ne conserve aucun état entre les jetons, et ne peut pas savoir que cela in
devrait généralement être une variable sauf lorsque nous sommes dans une boucle for. De plus, le code suivant doit être légal:
for(in in expression)
Le premier in
serait un identifiant, le second serait un mot-clé.
Il y a deux réactions à ce problème:
Les mots clés contextuels prêtent à confusion, réutilisons plutôt les mots clés.
Java a de nombreux mots réservés, dont certains n'ont aucune utilité, sauf pour fournir des messages d'erreur plus utiles aux programmeurs passant à Java à partir de C ++. L'ajout de nouveaux mots clés rompt le code. L'ajout de mots clés contextuels est source de confusion pour le lecteur du code, à moins qu'ils ne présentent une bonne mise en évidence de la syntaxe, et rend l'outillage difficile à implémenter car ils devront utiliser des techniques d'analyse plus avancées (voir ci-dessous).
Lorsque nous voulons étendre la langue, la seule approche sensée consiste à utiliser des symboles qui n'étaient pas légaux auparavant dans la langue. En particulier, il ne peut s'agir d'identifiants. Avec la syntaxe de boucle foreach, Java a réutilisé le :
mot-clé existant avec une nouvelle signification. Avec lambdas, Java a ajouté un ->
mot - clé qui ne pouvait pas apparaître auparavant dans un programme légal ( -->
serait toujours lexé comme '--' '>'
étant légal, et ->
aurait pu être lexé auparavant '-', '>'
, mais cette séquence serait rejetée par l'analyseur).
Les mots clés contextuels simplifient les langues, implémentons-les
Les Lexers sont incontestablement utiles. Mais au lieu d'exécuter un lexer avant l'analyseur, nous pouvons les exécuter en tandem avec l'analyseur. Les analyseurs ascendants connaissent toujours l'ensemble des types de jetons qui seraient acceptables à n'importe quel emplacement donné. L'analyseur peut alors demander au lexer de faire correspondre l'un de ces types à la position actuelle. Dans une boucle for-each, l'analyseur serait à la position indiquée ·
dans la grammaire (simplifiée) une fois la variable trouvée:
for_loop = for_loop_cstyle | for_each_loop
for_loop_cstyle = 'for' '(' declaration · ';' expression ';' expression ')'
for_each_loop = 'for' '(' declaration · 'in' expression ')'
À cette position, les jetons légaux sont SEMICOLON
ou IN
, mais pas IDENT
. Un mot in
- clé serait totalement sans ambiguïté.
Dans cet exemple particulier, les analyseurs descendants n'auraient pas de problème non plus puisque nous pouvons réécrire la grammaire ci-dessus pour
for_loop = 'for' '(' declaration · for_loop_rest ')'
for_loop_rest = · ';' expression ';' expression
for_loop_rest = · 'in' expression
et tous les jetons nécessaires à la décision peuvent être vus sans retour en arrière.
Tenez compte de la convivialité
Java a toujours tendu vers la simplicité sémantique et syntaxique. Par exemple, le langage ne prend pas en charge la surcharge des opérateurs car cela rendrait le code beaucoup plus compliqué. Ainsi, lorsque nous décidons entre in
et :
pour une syntaxe de boucle pour chaque, nous devons considérer laquelle est moins déroutante et plus apparente pour les utilisateurs. Le cas extrême serait probablement
for (in in in in())
for (in in : in())
(Remarque: Java a des espaces de noms distincts pour les noms de type, les variables et les méthodes. Je pense que c'était surtout une erreur. Cela ne signifie pas que la conception de langage ultérieure doit ajouter plus d' erreurs.)
Quelle alternative offre des séparations visuelles plus claires entre la variable d'itération et la collection itérée? Quelle alternative peut être reconnue plus rapidement lorsque vous regardez le code? J'ai trouvé que séparer les symboles valait mieux qu'une chaîne de mots en ce qui concerne ces critères. D'autres langues ont des valeurs différentes. Par exemple, Python énonce de nombreux opérateurs en anglais afin qu'ils puissent être lus naturellement et sont faciles à comprendre, mais ces mêmes propriétés peuvent rendre assez difficile la compréhension d'un morceau de Python en un coup d'œil.