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.

17
demandé sur Andrew Swan 2013-07-25 06:51:58

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.

37
répondu michiakig 2013-07-25 03:56:12

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.

Modifier: les noms des jetons produits par cette grammaire diffèrent légèrement de votre tableau.

  • Votre Key_Word jeton Identifier
  • Object_Accessor jeton DOT
  • left_Parenthesis jeton LPAREN
  • String_Literal jeton StringLiteral
  • right_Parenthesis jeton RPAREN
  • statement_separator jeton SEMI
5
répondu Sam Harwell 2013-07-25 03:07:06

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.

2
répondu darxsys 2013-07-25 03:00:06

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.

2
répondu Shivam 2013-07-25 03:11:12

CookCC (https://github.com/coconut2015/cookcc) génère un très rapide, petit, zéro-dépendance lexer pour Java.

0
répondu user1456982 2018-08-10 15:30:46