Faire un analyseur lexical
je travaille avec un programme D'analyse Lexical en ce moment et J'utilise Java. J'ai cherché des réponses à ce problème, mais jusqu'à présent, je n'en ai trouvé aucune. Voici mon problème:
Entrée:
System.out.println ("Hello World");
Sortie Désirée:
Lexeme----------------------Token
System [Key_Word]
. [Object_Accessor]
out [Key_Word]
. [Object_Accessor]
println [Key_Word]
( [left_Parenthesis]
"Hello World" [String_Literal]
) [right_Parenthesis]
; [statement_separator]
je suis encore un débutant, alors j'espère que vous pourrez m'aider. Grâce.
5 réponses
vous n'avez besoin ni D'ANTLR ni du livre du Dragon pour écrire un simple analyseur lexical à la main. Même les analyseurs lexicaux pour les langages plus complets (comme Java) ne sont pas très compliqués à écrire à la main. Évidemment, si vous avez une tâche industrielle, vous pourriez vouloir considérer des outils de force industrielle comme ANTLR ou une variante de lex, mais pour le plaisir d'apprendre comment l'analyse lexicale fonctionne, écrire une à la main serait probablement un exercice utile. Je suppose que c'est le cas, puisque vous avez dit tu es encore un débutant.
voici un analyseur lexical simple, écrit en Java, pour un sous-ensemble D'un langage Scheme-like, que j'ai écrit après avoir vu cette question. Je pense que le code est relativement facile à comprendre même si vous n'avez jamais vu une lexer avant, simplement parce que briser un flux de caractères (dans ce cas a String
) dans un flux de jetons (dans ce cas, un List<Token>
) n'est pas si difficile. Si vous avez des questions je peux essayer d'expliquer plus en profondeur.
import java.util.List;
import java.util.ArrayList;
/*
* Lexical analyzer for Scheme-like minilanguage:
* (define (foo x) (bar (baz x)))
*/
public class Lexer {
public static enum Type {
// This Scheme-like language has three token types:
// open parens, close parens, and an "atom" type
LPAREN, RPAREN, ATOM;
}
public static class Token {
public final Type t;
public final String c; // contents mainly for atom tokens
// could have column and line number fields too, for reporting errors later
public Token(Type t, String c) {
this.t = t;
this.c = c;
}
public String toString() {
if(t == Type.ATOM) {
return "ATOM<" + c + ">";
}
return t.toString();
}
}
/*
* Given a String, and an index, get the atom starting at that index
*/
public static String getAtom(String s, int i) {
int j = i;
for( ; j < s.length(); ) {
if(Character.isLetter(s.charAt(j))) {
j++;
} else {
return s.substring(i, j);
}
}
return s.substring(i, j);
}
public static List<Token> lex(String input) {
List<Token> result = new ArrayList<Token>();
for(int i = 0; i < input.length(); ) {
switch(input.charAt(i)) {
case '(':
result.add(new Token(Type.LPAREN, "("));
i++;
break;
case ')':
result.add(new Token(Type.RPAREN, ")"));
i++;
break;
default:
if(Character.isWhitespace(input.charAt(i))) {
i++;
} else {
String atom = getAtom(input, i);
i += atom.length();
result.add(new Token(Type.ATOM, atom));
}
break;
}
}
return result;
}
public static void main(String[] args) {
if(args.length < 1) {
System.out.println("Usage: java Lexer \"((some Scheme) (code to) lex)\".");
return;
}
List<Token> tokens = lex(args[0]);
for(Token t : tokens) {
System.out.println(t);
}
}
}
Exemple utilisation:
~/code/scratch $ java Lexer ""
~/code/scratch $ java Lexer "("
LPAREN
~/code/scratch $ java Lexer "()"
LPAREN
RPAREN
~/code/scratch $ java Lexer "(foo)"
LPAREN
ATOM<foo>
RPAREN
~/code/scratch $ java Lexer "(foo bar)"
LPAREN
ATOM<foo>
ATOM<bar>
RPAREN
~/code/scratch $ java Lexer "(foo (bar))"
LPAREN
ATOM<foo>
LPAREN
ATOM<bar>
RPAREN
RPAREN
une fois que vous aurez écrit un ou deux lexers simples comme celui-ci, vous aurez une assez bonne idée de la façon dont ce problème se décompose. Il serait alors intéressant d'explorer comment utiliser des outils automatisés comme lex. La théorie qui sous-tend les matchers basés sur l'expression régulière n'est pas trop difficile, mais il faut un certain temps pour bien la comprendre. Je pense que l'écriture de lexers à la main motive cette étude et vous aide à aborder le problème mieux que de plonger dans la théorie derrière convertir expressions régulières à automate fini (D'abord NFA, puis NFA à DFAs), etc... glisser dans cette théorie peut être beaucoup à prendre à la fois, et il est facile de se dépasser.
personnellement, alors que le livre du Dragon est bon et très complet, la couverture pourrait ne pas être la plus facile à comprendre parce qu'il vise à être complet, pas nécessairement accessible. Vous pourriez vouloir essayer quelques autres compilateur textes avant d'ouvrir le Dragon livre. Voici quelques livres gratuits, qui ont assez bonne introduction de la couverture, à mon humble avis:
http://www.ethoberon.ethz.ch/WirthPubl/CBEAll.pdf
http://www.diku.dk / ~ torbenm / Basics/
quelques articles sur la mise en oeuvre des expressions régulières (l'analyse lexicale automatisée utilise habituellement des expressions régulières)
http://swtch.com/~rsc / regexp/
j'espère que ça aide. Bonne chance.
ANTLR 4 va faire exactement cela avec l' Java.g4
grammaire de référence. Vous avez deux options selon à quel point vous voulez que la manipulation des séquences D'échappement Unicode suive la spécification de la langue.
- https://github.com/antlr/grammars-v4/blob/master/java/Java.g4: cette grammaire ne traite que les séquences d'échappement Unicode comme des caractères à l'intérieur d'une chaîne ou d'un caractère littéral.
- https://github.com/antlr/antlr4/blob/master/tool/test/org/antlr/v4/test/Java-LR.g4 (doit être renommé en Java.G4 avant utilisation): cette grammaire exige que vous enveloppez votre
ANTLRInputStream
dans unJavaUnicodeInputStream
, qui traite les séquences D'échappement Unicode selon le JLS avant de les transmettre au lexer.
Modifier: les noms des jetons produits par cette grammaire diffèrent légèrement de votre tableau.
- Votre
Key_Word
jetonIdentifier
Object_Accessor
jetonDOT
left_Parenthesis
jetonLPAREN
String_Literal
jetonStringLiteral
right_Parenthesis
jetonRPAREN
statement_separator
jetonSEMI
l'analyse lexicale est un sujet en soi qui va généralement de pair avec la conception et l'analyse des compilateurs. Vous devriez le lire avant d'essayer de coder quoi que ce soit. Mon livre préféré sur ce sujet est l' Dragon livre qui devrait vous donner une bonne introduction à la conception de compilateur et fournit même des pseudocodes pour toutes les phases de compilateur que vous pouvez facilement traduire en Java et se déplacer à partir de là.
En bref, l'idée principale est d'analyser l'entrée et la diviser en jetons qui appartiennent à certaines classes (parenthèses ou mots-clés, par exemple, dans votre sortie désirée) en utilisant une machine d'état fini. Processus de la construction de la machine d'état est en fait la seule partie difficile de cette analyse et le livre du Dragon vous fournira un grand aperçu de cette chose.
Vous pouvez utiliser des bibliothèques comme Lex & Bison
en C ou Antlr
en Java. L'analyse lexicale peut être fait par des automates. Je vais vous donner un petit exemple:
supposons que vous ayez besoin de tokenize une chaîne où les mots-clés (langue) sont {'echo', '.', ' ', 'end')
. Par mots-clés, je veux dire langue se compose de mots-clés suivants seulement. Donc, si j'ai d'entrée
echo .
end .
Mon analyseur lexical doit de sortie
echo ECHO
SPACE
. DOT
end END
SPACE
. DOT
maintenant pour construire automata pour un tel tokenizer, je peux commencer par
->(SPACE) (Back)
|
(S)-------------E->C->H->O->(ECHO) (Back)
| |
.->(DOT)(Back) ->N->D ->(END) (Back to Start)
diagramme ci-Dessus est pro très mauvais, mais l'idée est que vous avez un début etat représenté par S
maintenant que vous consommez E
et passer à un autre état, maintenant que vous attendez N
ou C
venir END
et ECHO
respectivement. Vous continuez à consommer des caractères et atteignez différents états dans cette simple machine d'état fini. En fin de compte, vous atteignez certains Emit
état, par exemple après avoir consommé de l' E
,N
,D
vous atteignez l'état d'émission pour END
qui émet le jeton et puis vous revenez à start
état. Ce cycle continue pour toujours aussi loin que vous avez le flux de caractères venant à votre tokenizer. Sur le caractère invalide, vous pouvez soit lancer une erreur ou ignorer selon le design.
CookCC (https://github.com/coconut2015/cookcc) génère un très rapide, petit, zéro-dépendance lexer pour Java.