web-dev-qa-db-fra.com

Faire un analyseur lexical

Je travaille actuellement avec un programme Lexical Analyzer et j'utilise Java. Je cherchais des réponses à ce problème, mais jusqu'à présent, je n'en ai trouvé aucune. Voici mon problème:

Contribution:

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 toujours débutant et j'espère que vous pourrez m'aider à ce sujet. Merci.

17
KLoverated

ANTLR ni le livre Dragon ne sont nécessaires pour écrire un analyseur lexical simple à la main. Même les analyseurs lexicaux pour des langages plus complets (comme Java) ne sont pas terriblement compliqués à écrire à la main. Évidemment, si vous avez une tâche industrielle, vous voudrez peut-être envisager des outils industriels tels que ANTLR ou une variante de Lex, mais pour comprendre comment fonctionne l'analyse lexicale, écrire un à la main s'avérera probablement un exercice utile. Je suppose que c'est le cas, puisque vous avez dit que vous êtes toujours un débutant.

Voici un analyseur lexical simple, écrit en Java, pour un sous-ensemble d'un langage semblable à Scheme, 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 un lexer auparavant, tout simplement parce que briser un flux de caractères (dans ce cas un String) en un flux de jetons (dans ce cas un List<Token>) n'est-ce pas 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 d'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 avez é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. Ensuite, il serait intéressant d’explorer comment utiliser des outils automatisés comme Lex. La théorie à la base des opérateurs d’expression régulière n’est pas trop difficile à comprendre, mais il faut un certain temps pour bien la comprendre. Je pense que l'écriture manuelle de lexers motive cette étude et vous aide à mieux comprendre le problème que de plonger dans la théorie qui sous-tend la conversion d'expressions régulières en automates finis (d'abord les NFA, puis les NFA en DFA), etc. beaucoup à prendre en même temps, et il est facile de se sentir dépassé.

Personnellement, bien que le livre Dragon soit bon et très complet, la couverture n’est peut-être pas la plus facile à comprendre car elle se veut complète, pas nécessairement accessible. Vous voudrez peut-être essayer d’autres textes du compilateur avant d’ouvrir le livre Dragon. Voici quelques livres gratuits, qui ont une bonne introduction, IMHO:

http://www.ethoberon.ethz.ch/WirthPubl/CBEAll.pdf

http://www.diku.dk/~torbenm/Basics/

Quelques articles sur l'implémentation d'expressions régulières (l'analyse lexicale automatisée utilise généralement des expressions régulières)

http://swtch.com/~rsc/regexp/

J'espère que ça aide. Bonne chance.

43
michiakig

ANTLR 4 fera exactement cela avec la grammaire de référence Java.g4. Vous avez le choix entre deux options, en fonction de la précision avec laquelle vous souhaitez que la gestion des séquences d'échappement Unicode suive les spécifications de langue.

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

  • Votre jeton Key_Word est Identifier
  • Votre jeton Object_Accessor est DOT
  • Votre jeton left_Parenthesis est LPAREN
  • Votre jeton String_Literal est StringLiteral
  • Votre jeton right_Parenthesis est RPAREN
  • Votre jeton statement_separator est SEMI
5
Sam Harwell

Vous pouvez utiliser des bibliothèques telles que Lex & Bison en C ou Antlr en Java. L'analyse lexicale peut être réalisée en faisant des automates. Je vais vous donner un petit exemple:

Supposons que vous deviez créer une chaîne où les mots-clés (langue) sont {'echo', '.', ' ', 'end'). Par mots-clés, j'entends la langue, c'est-à-dire les mots-clés suivants. Donc si j'entre

echo .
end .

Mon lexer devrait sortir

echo ECHO
 SPACE
. DOT
end END
 SPACE
. DOT

Maintenant, pour construire des automates 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)

Le diagramme ci-dessus est très mauvais, mais l’idée est que vous avez un état de départ représenté par S et que vous consommez maintenant E et que vous passez à un autre état. Vous vous attendez maintenant à ce que N ou C arrive pour END et ECHO. Vous continuez à consommer des personnages et à atteindre différents états dans cette simple machine à états finis. En fin de compte, vous atteignez certains états Emit, par exemple après avoir consommé E, N, D, vous atteignez l'état d'émission pour END qui émet le jeton puis vous revenez à l'état start. Ce cycle se poursuit indéfiniment dans la mesure où des flux de caractères arrivent sur votre tokenizer. Sur un caractère invalide, vous pouvez soit renvoyer une erreur, soit ignorer, en fonction de la conception.

2
Shivam

L'analyse lexicale est un sujet en soi qui va généralement de pair avec la conception et l'analyse du compilateur. Vous devriez lire à ce sujet avant d'essayer de coder quoi que ce soit. Mon livre préféré sur ce sujet est le livre Dragon qui devrait vous donner une bonne introduction à la conception du compilateur et même fournir des pseudocodes pour toutes les phases du compilateur que vous pouvez facilement traduire en Java et déplacer à partir de là.

En bref, l’idée principale est d’analyser l’entrée et de la diviser en jetons appartenant à certaines classes (parenthèses ou mots-clés, par exemple, dans la sortie souhaitée) à l’aide d’une machine à états finis. Le processus de construction de la machine à états est en réalité le seul élément difficile de cette analyse et le livre Dragon vous en donnera une bonne idée.

2
darxsys

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

0
user1456982