Создание лексического анализатора

Я работаю с программой лексического анализатора прямо сейчас, и я использую Java. Я искал ответы на эту проблему, но до сих пор мне не удалось найти их. Вот моя проблема:

вход:

System.out.println ("Hello World");

Желаемый Результат:

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]

Я все еще новичок, поэтому я надеюсь, что вы, ребята, можете помочь мне в этом. Спасибо.

5 ответов


вам не нужно ни ANTLR, ни книга Дракона, чтобы написать простой лексический анализатор вручную. Даже лексические анализаторы для более полных языков (например, Java) не так уж сложно писать вручную. Очевидно, что если у вас есть промышленная задача, вы можете рассмотреть промышленные инструменты прочности, такие как ANTLR или какой-то вариант lex, но ради изучения того, как работает лексический анализ, написание одного от руки, вероятно, окажется полезным упражнением. Я предполагаю, что это так, поскольку вы сказали ты все еще новичок.

вот простой лексический анализатор, написанный на Java, для подмножества схемного языка, который я написал после просмотра этого вопроса. Я думаю, что код относительно легко понять, даже если вы никогда не видели лексер раньше, просто потому, что нарушая поток символов (в этом случае String) в поток токенов (в данном случае a List<Token>) не так сложно. Если у вас есть вопросы, я могу попытаться объяснить более подробно.

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);
        }
    }
}

пример использовать:

~/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

как только вы напишете один или два простых лексера, как это, вы получите довольно хорошее представление о том, как эта проблема разлагается. Тогда было бы интересно изучить, как использовать автоматизированные инструменты, такие как Лекс. Теория, лежащая в основе регулярных выражений, не слишком сложна, но требуется некоторое время, чтобы полностью понять. Я думаю, что написание лексеров вручную мотивирует это исследование и помогает вам справиться с проблемой лучше, чем погружение в теорию преобразования регулярные выражения для конечной автоматизации (сначала NFAs, затем NFAs для DFAs)и т. д... вброд в эту теорию может быть много, чтобы принять сразу, и это легко получить перегружены.

лично, в то время как книга Дракона хороша и очень тщательна, охват может быть не самым простым для понимания, потому что он стремится быть полным, не обязательно доступным. Возможно, вы захотите попробовать некоторые другие тексты компилятора, прежде чем открывать книгу Dragon. Вот несколько бесплатных книг, в которых есть довольно хорошие вводное освещение, IMHO:

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

http://www.diku.dk / ~torbenm / основы/

некоторые статьи о реализации регулярных выражений (автоматизированный лексический анализ обычно использует регулярные выражения)

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

надеюсь, это поможет. Удача.


ANTLR 4 будет делать именно это с помощью тега Java.g4 справочник по грамматике. У вас есть два варианта в зависимости от того, насколько близко вы хотите, чтобы обработка escape-последовательностей Unicode следовала спецификации языка.

Edit: имена токенов, производимых этой грамматикой, немного отличаются от ваших таблица.

  • код Key_Word маркер Identifier
  • код Object_Accessor маркер DOT
  • код left_Parenthesis маркер LPAREN
  • код String_Literal маркер StringLiteral
  • код right_Parenthesis маркер RPAREN
  • код statement_separator маркер SEMI

лексический анализ-это тема сама по себе, которая обычно идет вместе с дизайном и анализом компилятора. Вы должны прочитать об этом, прежде чем пытаться что-либо код. Моя любимая книга на эту тему это Дракон книга, которая должна дать вам хорошее введение в дизайн компилятора и даже предоставляет псевдокоды для всех фаз компилятора, которые вы можете легко перевести на Java и перейти оттуда.

короче, главная идея-для разбора и разделить его на токены, принадлежащие определенным классам (скобки или ключевые слова, например, в желаемом выходе) с использованием конечного автомата. Процесс государственного машиностроения на самом деле является единственной трудной частью этого анализа, и книга Dragon предоставит Вам большое понимание этой вещи.


вы можете использовать библиотеки, такие как Lex & Bison В C или Antlr в Java. Лексический анализ может быть сделан через создание автоматов. Приведу небольшой пример:--19-->

Предположим, вам нужно обозначить строку, где ключевые слова (язык) являются {'echo', '.', ' ', 'end'). Под ключевыми словами я подразумеваю, что язык состоит только из следующих ключевых слов. Поэтому, если я введу

echo .
end .

мой лексер должен вывести

echo ECHO
 SPACE
. DOT
end END
 SPACE
. DOT

теперь, чтобы построить автоматы для такого токенизатора, я могу начать с

  ->(SPACE) (Back)
 |   
(S)-------------E->C->H->O->(ECHO) (Back)
 |              |
 .->(DOT)(Back)  ->N->D ->(END) (Back to Start)

выше диаграммы, вероятно, очень плохо, но идея в том, что у вас есть начальное состояние, представленное S теперь вы потребляете E и перейти в другое состояние, теперь вы ожидаете N или C пришел к END и ECHO соответственно. Вы продолжаете потреблять символы и достигаете разных состояний в этой простой машине конечного состояния. В конечном счете, вы достигаете определенного Emit состояние, например, после потребления E, N, D вы достигнете выделяет государство для END который выдает токен, а затем вы возвращаетесь к start государство. Этот цикл продолжается вечно, поскольку у вас есть поток символов, поступающий в ваш токенизатор. На недопустимом символе вы можете либо вызвать ошибку, либо игнорировать в зависимости от дизайна.


CookCC (https://github.com/coconut2015/cookcc) генерирует очень быстрый, небольшой, лексер нулевой зависимости для Java.