27

我现在正在使用词法分析器程序,并且正在使用 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]

我还是一个初学者,所以我希望你们能帮助我。谢谢。

4

6 回答 6

61

你既不需要 ANTLR 也不需要 Dragon 的书来手动编写一个简单的词法分析器。即使是更完整的语言(如 Java)的词法分析器,手工编写也不是很复杂。显然,如果你有一个工业任务,你可能想要考虑工业强度的工具,比如 ANTLR 或一些 lex 变体,但是为了了解词法分析的工作原理,手工编写可能会被证明是一个有用的练习。我假设是这种情况,因为您说您仍然是初学者。

这是一个简单的词法分析器,用 Java 编写,用于类 Scheme 语言的子集,我在看到这个问题后编写的。我认为即使您以前从未见过词法分析器,代码也相对容易理解,仅仅是因为将字符流(在本例中为 a 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

一旦你编写了一两个这样的简单词法分析器,你就会很好地了解这个问题是如何分解的。然后探索如何使用像 lex 这样的自动化工具会很有趣。基于正则表达式的匹配器背后的理论并不难,但要完全理解确实需要一段时间。我认为手工编写词法分析器可以激发这项研究,并帮助您更好地解决问题,而不是深入研究将正则表达式转换为有限自动化(首先是 NFA,然后是 NFA 到 DFA)等背后的理论......涉足该理论可以一次要吸收很多东西,很容易不知所措。

就个人而言,虽然《龙》这本书很好而且非常透彻,但其内容可能不是最容易理解的,因为它的目标是完整,但不一定易于理解。在打开 Dragon 书之前,您可能想尝试一些其他编译器文本。恕我直言,这里有一些免费的书籍,它们的介绍性很好:

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

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

一些关于正则表达式实现的文章(自动词法分析通常使用正则表达式)

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

我希望这会有所帮助。祝你好运。

于 2013-07-25T03:50:43.963 回答
4

ANTLR 4Java.g4将使用参考语法做到这一点。您有两种选择,具体取决于您希望 Unicode 转义序列的处理遵循语言规范的程度。

编辑:此语法产生的标记名称与您的表略有不同。

  • 你的Key_Word令牌是Identifier
  • 你的Object_Accessor令牌是DOT
  • 你的left_Parenthesis令牌是LPAREN
  • 你的String_Literal令牌是StringLiteral
  • 你的right_Parenthesis令牌是RPAREN
  • 你的statement_separator令牌是SEMI
于 2013-07-25T03:01:00.990 回答
3

词法分析本身就是一个主题,通常与编译器设计和分析一起使用。在尝试编写任何代码之前,您应该阅读它。我最喜欢的关于这个主题的书是Dragon书,它应该给你一个很好的编译器设计介绍,甚至提供所有编译器阶段的伪代码,你可以轻松地将其转换为 Java 并从那里移动。

简而言之,主要思想是使用有限状态机解析输入并将其划分为属于某些类(括号或关键字,例如,在您想要的输出中)的标记。状态机构建过程实际上是这个分析中唯一困难的部分,龙书将为您提供对这件事的深入了解。

于 2013-07-25T02:55:04.930 回答
2

您可以使用Lex & BisonC 或AntlrJava 等库。词法分析可以通过制作自动机来完成。我给你举个小例子:

假设您需要对关键字(语言)所在的字符串进行标记{'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或分别CENDECHO。在这个简单的有限状态机中,您不断消耗字符并达到不同的状态。最终,您会达到某个Emit状态,例如在消费E,之后ND您会达到发射状态,END该状态会发出令牌,然后返回start状态。只要字符流进入标记器,这个循环就会永远持续下去。在无效字符上,您可以根据设计抛出错误或忽略。

于 2013-07-25T03:11:12.987 回答
0

CookCC ( https://github.com/coconut2015/cookcc ) 为 Java 生成一个非常快速、小型、零依赖的词法分析器。

于 2018-08-10T15:30:46.817 回答
-2

编写一个程序来制作一个简单的词法分析器,该分析器将从给定的字符流构建一个符号表。您需要读取一个名为“input.txt”的文件来收集所有字符。为简单起见,输入文件将是一个没有标题和方法(主程序的主体)的 C/Java/Python 程序。然后您将识别所有数值、标识符、关键字、数学运算符、逻辑运算符和其他[不同]。有关更多详细信息,请参见示例。您可以假设,每个关键字后面都会有一个空格。

#include<stdio.h>
    #include<stdlib.h>
    #include<string.h>

    int main(){
    /* By Ashik Rabbani
    Daffodil International University,CSE43 */
    keyword_check();
    identifier_check();
    math_operator_check();
    logical_operator_check();
    numerical_check();
    others_check();


        return 0;
    }


    void math_operator_check()
    {

        char ch, string_input[15], operators[] = "+-*/%";
        FILE *fp;
        char tr[20];
        int i,j=0;

        fp = fopen("input.txt","r");

        if(fp == NULL){
            printf("error while opening the file\n");
            exit(0);
        }
       printf("\nMath Operators : ");
        while((ch = fgetc(fp)) != EOF){
               for(i = 0; i < 6; ++i){
                   if(ch == operators[i])
                       printf("%c ", ch);

               }
               }
                printf("\n");



        fclose(fp);
    }


    void logical_operator_check()
    {

        char ch, string_input[15], operators[] = "&&||<>";
        FILE *fp;
        char tr[20];
        int i,j=0;

        fp = fopen("input.txt","r");

        if(fp == NULL){
            printf("error while opening the file\n");
            exit(0);
        }
       printf("\nLogical Operators : ");
        while((ch = fgetc(fp)) != EOF){
               for(i = 0; i < 6; ++i){
                   if(ch == operators[i])
                       printf("%c ", ch);

               }
               }
                printf("\n");



        fclose(fp);
    }

    void numerical_check()
    {

        char ch, string_input[15], operators[] ={'0','1','2','3','4','5','6','7','8','9'};
        FILE *fp;

        int i,j=0;

        fp = fopen("input.txt","r");

        if(fp == NULL){
            printf("error while opening the file\n");
            exit(0);
        }
       printf("\nNumerical Values : ");
        while((ch = fgetc(fp)) != EOF){
               for(i = 0; i < 6; ++i){
                   if(ch == operators[i])
                       printf("%c ", ch);

               }
               }
                printf("\n");



        fclose(fp);
    }

    void others_check()
    {
        char ch, string_input[15], symbols[] = "(){}[]";
        FILE *fp;
        char tr[20];
        int i,j=0;

        fp = fopen("input.txt","r");

        if(fp == NULL){
            printf("error while opening the file\n");
            exit(0);
        }
       printf("\nOthers : ");
        while((ch = fgetc(fp)) != EOF){
               for(i = 0; i < 6; ++i){
                   if(ch == symbols[i])
                       printf("%c ", ch);

               }
               }
               printf("\n");



        fclose(fp);
    }

    void identifier_check()
    {
        char ch, string_input[15];
        FILE *fp;
    char    operators[] ={'0','1','2','3','4','5','6','7','8','9'};
        int i,j=0;

        fp = fopen("input.txt","r");

        if(fp == NULL){
            printf("error while opening the file\n");
            exit(0);
        }

        printf("\nIdentifiers : ");
        while((ch = fgetc(fp)) != EOF){

               if(isalnum(ch)){
                   string_input[j++] = ch;
               }
               else if((ch == ' ' || ch == '\n') && (j != 0)){
                       string_input[j] = '\0';
                       j = 0;

                       if(isKeyword(string_input) == 1)
                       {

                       }

                       else
                           printf("%s ", string_input);
               }

               }

                printf("\n");


        fclose(fp);
    }

    int isKeyword(char string_input[]){
        char keywords[32][10] = {"auto","break","case","char","const","continue","default",
                                "do","double","else","enum","extern","float","for","goto",
                                "if","int","long","register","return","short","signed",
                                "sizeof","static","struct","switch","typedef","union",
                                "unsigned","void","volatile","while"};
        int i, flag = 0;

        for(i = 0; i < 32; ++i){
            if(strcmp(keywords[i], string_input) == 0){
                flag = 1;
                break;
            }
        }

        return flag;
    }

    void keyword_check()
    {

        char ch, string_input[15], operators[] = "+-*/%=";
        FILE *fp;
        char tr[20];
        int i,j=0;

        printf(" Token Identification using C \n By Ashik-E-Rabbani \n 161-15-7093\n\n");

        fp = fopen("input.txt","r");

        if(fp == NULL){
            printf("error while opening the file\n");
            exit(0);
        }

        printf("\nKeywords : ");
        while((ch = fgetc(fp)) != EOF){

               if(isalnum(ch)){
                   string_input[j++] = ch;
               }
               else if((ch == ' ' || ch == '\n') && (j != 0)){
                       string_input[j] = '\0';
                       j = 0;

                       if(isKeyword(string_input) == 1)
                           printf("%s ", string_input);

               }

               }

     printf("\n");


        fclose(fp);
    }
于 2018-12-10T12:48:51.167 回答