web-dev-qa-db-ja.com

字句アナライザーの作成

私は現在、Lexical Analyzerプログラムで作業しており、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]

私はまだ初心者ですので、皆さんが私を助けてくれることを願っています。ありがとう。

19
KLoverated

単純な字句解析プログラムを手で書くのに、ANTLRもDragon本も必要ありません。 Javaなどのより完全な言語用の字句解析器でさえ、手で書くのはそれほど複雑ではありません。産業用のタスクがある場合は、ANTLRやLexバリアントなどの産業用強度ツールを検討する必要があることは明らかですが、字句解析の仕組みを学習するためには、1つずつ手書きするのが有用な演習になる可能性があります。あなたはまだ初心者だと言ったので、これが事実だと思います。

これは、この質問を見た後に書いた、Schemeに似た言語のサブセット用の、Javaで書かれた簡単な字句解析プログラムです。文字のストリーム(この場合はString)をトークンのストリーム(この場合は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

このように1つまたは2つの簡単な字句解析プログラムを作成すると、この問題がどのように分解されるかについてかなり良いアイデアが得られます。それから、Lexのような自動化されたツールを使用する方法を探ることは興味深いでしょう。正規表現ベースのマッチャーの背後にある理論はそれほど難しくはありませんが、完全に理解するにはしばらく時間がかかります。字句解析器を手で書くことは、研究をやる気にさせ、正規表現を有限自動化(最初のNFA、次にNFAからDFA)に変換する背後にある理論に飛び込むよりも、問題を理解するのに役立つと思います...一度にたくさん取り入れることで、圧倒されやすくなります。

個人的には、ドラゴンの本は優れた非常に徹底的なものですが、網羅することを目的としているため、必ずしもアクセスしやすいとは限らないため、カバレッジを理解するのは最も簡単ではありません。 Dragonブックを開く前に、他のコンパイラーテキストを試してみてください。ここにいくつかの無料の本がありますが、それらは入門編としてかなり良いものです。

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

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

正規表現の実装に関するいくつかの記事(自動化された字句解析は通常正規表現を使用します)

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

それがお役に立てば幸いです。幸運を。

44
michiakig

ANTLR 4 は、Java.g4参照文法。 Unicodeエスケープシーケンスの処理が言語仕様に従う程度に応じて、2つのオプションがあります。

編集:この文法によって生成されるトークンの名前は、テーブルとわずかに異なります。

  • きみの Key_WordトークンはIdentifierです
  • きみの Object_AccessorトークンはDOTです
  • きみの left_ParenthesisトークンはLPARENです
  • きみの String_LiteralトークンはStringLiteralです
  • きみの right_ParenthesisトークンはRPARENです
  • きみの statement_separatorトークンはSEMIです
5
Sam Harwell

字句解析は、それ自体がトピックであり、通常はコンパイラーの設計と解析に関連します。何かをコーディングする前に、それについて読む必要があります。このトピックに関する私のお気に入りの本は Dragon 本です。これは、コンパイラー設計の優れた入門書であり、簡単にJava =そしてそこから移動します。

要するに、主なアイデアは、入力を解析し、有限状態マシンを使用して特定のクラス(たとえば、目的の出力の括弧またはキーワード)に属するトークンに分割することです。ステートマシンの構築プロセスは、実際にはこの分析の唯一の困難な部分であり、ドラゴンの本はこのことに関する優れた洞察を提供します。

3
darxsys

CではLex & BisonやJavaではAntlrなどのライブラリを使用できます。字句解析は、オートマトンを作成することで実行できます。例を挙げましょう。

キーワード(言語)が{'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はそれぞれENDECHOに対応します。この単純な有限状態マシン内でキャラクターを消費し続け、さまざまな状態に到達します。最終的に、特定のEmit状態に到達します。たとえば、ENDを消費した後、トークンを出力するENDの出力状態に到達すると、 start状態に戻ります。このサイクルは、トークナイザーにキャラクターのストリームが来る限りずっと続きます。無効な文字については、設計に応じてエラーをスローするか無視することができます。

2
Shivam

CookCC( https://github.com/coconut2015/cookcc )は、Java用の非常に高速で小さい、依存性のないレクサーを生成します。

0
user1456982