Java实现编译器词法解析入门 - 极悦
首页 课程 师资 教程 报名

Java实现编译器词法解析入门

  • 2021-04-30 10:51:02
  • 883次 极悦

编译器作用就是将一种计算机无法理解的文本,转译成计算机能执行的语句,我们要做的编译器如下,将带有加法和乘法的算术式子,转译成机器能执行的汇编语句,例如语句:

1+2*3+4, 经过编译后转换成:

t0 = 1

t1 = 2

t2 = 3

t1 *= t2

t0 += t1

t1 = 4

t0 += t1

t0, t1 是对寄存器的模拟,上述语句基本上就类似计算机能执行的汇编语句了。

本章首先专注于词法解析的探讨。

编译原理由两部分组成,一是词法分析,一是语义分析。先说词法分析,词法分析就是将一个语句分割成若干个有意义的字符串的组合,然后给分割的字符串打标签。例如语句:

1+2*3+4; 可以分割成 1+, 2*, 3+, 4; 但这些子字符串没有实质意义,有意义的分割是1, +, 2, * , 3, +, 4, ;. 接着就是给这些分割后的字符串打标签,例如给1, 2, 3, 4 打上的标签是NUM_OR_ID, + 打的标签是PLUS, *的标签是TIMES, ;的标签是SEMI, 好了,看看词法分析的代码,大家可能更容易理解:

Lexer.java:

import java.util.Scanner;  
public class Lexer {  
    public static final int  EOI = 0;  
    public static final int  SEMI = 1;  
    public static final int  PLUS = 2;  
    public static final int  TIMES = 3;  
    public static final int  LP = 4;  
    public static final int  RP = 5;  
    public static final int  NUM_OR_ID = 6;        
    private int lookAhead = -1;        
    public String yytext = "";  
    public int yyleng = 0;  
    public int yylineno = 0;       
    private String input_buffer = "";  
    private String current = "";        
    private boolean isAlnum(char c) {  
        if (Character.isAlphabetic(c) == true ||  
                Character.isDigit(c) == true) {  
            return true;  
        }            
        return false;  
    }  
      
    private int lex() {        
        while (true) {  
                while (current == "") {  
                Scanner s = new Scanner(System.in);  
                while (true) {  
                    String line = s.nextLine();  
                    if (line.equals("end")) {  
                        break;  
                    }  
                    input_buffer += line;  
                }  
                s.close();                    
                if (input_buffer.length() == 0) {  
                    current = "";  
                    return EOI;  
                }                    
                current = input_buffer;  
                ++yylineno;  
                current.trim();  
            }//while (current != "")  
                    for (int i = 0; i < current.length(); i++) {                        
                    yyleng = 0;  
                    yytext = current.substring(0, 1);  
                    switch (current.charAt(i)) {  
                    case ';': current = current.substring(1); return SEMI;  
                    case '+': current = current.substring(1); return PLUS;  
                    case '*': current = current.substring(1);return TIMES;  
                    case '(': current = current.substring(1);return LP;  
                    case ')': current = current.substring(1);return RP;                        
                    case '\n':  
                    case '\t':  
                    case ' ': current = current.substring(1); break;                        
                    default:  
                        if (isAlnum(current.charAt(i)) == false) {  
                            System.out.println("Ignoring illegal input: " + current.charAt(i));  
                        }  
                        else {                                
                            while (isAlnum(current.charAt(i))) {  
                                i++;  
                                yyleng++;  
                            } // while (isAlnum(current.charAt(i)))                                
                            yytext = current.substring(0, yyleng);  
                            current = current.substring(yyleng);   
                            return NUM_OR_ID;  
                        }                            
                        break;                            
                    } //switch (current.charAt(i))  
                }//  for (int i = 0; i < current.length(); i++)                 
        }//while (true)   
    }//lex()        
    public boolean match(int token) {  
        if (lookAhead == -1) {  
            lookAhead = lex();  
        }            
        return token == lookAhead;  
    }        
    public void advance() {  
        lookAhead = lex();  
    }        
    public void runLexer() {  
        while (!match(EOI)) {  
            System.out.println("Token: " + token() + " ,Symbol: " + yytext );  
            advance();  
        }  
    }        
    private String token() {  
        String token = "";  
        switch (lookAhead) {  
        case EOI:  
            token = "EOI";  
            break;  
        case PLUS:  
            token = "PLUS";  
            break;  
        case TIMES:  
            token = "TIMES";  
            break;  
        case NUM_OR_ID:  
            token = "NUM_OR_ID";  
            break;  
        case SEMI:  
            token = "SEMI";  
            break;  
        case LP:  
            token = "LP";  
            break;  
        case RP:  
            token = "RP";  
            break;  
        }            
        return token;  
    }  
}  

代码中2到6行是对标签的定义,其中LP 代表左括号(, RP代表右括号), EOI 表示语句末尾, 第10行的lookAhead 变量用于表明当前分割的字符串指向的标签值,yytext用于存储当前正在分析的字符串,yyleng是当前分析的字符串的长度,yylineno是当前分析的字符串所在的行号。input_buffer 用于存储要分析的语句例如: 1+2*3+4; isAlNum 用于判断输入的字符是否是数字或字母。lex() 函数开始了词法分析的流程,31到40行从控制台读入语句,语句以"end"表明结束,例如在控制台输入:

1+2*3+4;

end

回车后,从52行开始执行词法解析流程。以上面的输入为例,input_buffer 存储语句 1+2*3+4, 由于第一个字符是 1, 在for 循环中,落入switch 的default 部分,isAlNum 返回为真,yyleng 自加后值为1, yytext 存储的字符串就是 "1", current前进一个字符变为+2*3+4, 再次执行lex(), 则解析的字符是+, 在for 循环中,落入switch的case '+' 分支,于是yytext为"+", 返回的标签就是PLUS依次类推, advance 调用一次, lex()就执行一次词法分析,当lex执行若干次后,语句1+2*3+4;会被分解成1, +, 2, *, 3, +, 4, ; 。字符串1, 2, 3, 4具有的标签是NUM_OR_ID, + 具有的标签是PLUS, *的标签是TIMES, ;的标签是SEMI.

runLexer() 将驱动词法解析器,执行解析流程,如果解析到的当前字符串,其标签不是EOI(end of input), 也就是没有达到输入末尾,那么就打印出当前分割的字符串和它所属的标签,接着调用advance() 进行下一次解析。

match, advance 会被稍后我们将看到的语法解析器调用。

接下来我们在main函数中,跑起Lexer, 看看词法解析过程:

Compiler.java

public class Compiler {    
    public static void main(String[] args) {  
        Lexer lexer = new Lexer();  
        //Parser parser = new Parser(lexer);  
        //parser.statements();  
        lexer.runLexer();  
    }  
}  

在eclipse 中运行给定代码,然后在控制台中输入如下:

1+2*3+4;

end

程序运行后输出:

Token: NUM_OR_ID ,Symbol: 1

Token: PLUS ,Symbol: +

Token: NUM_OR_ID ,Symbol: 2

Token: TIMES ,Symbol: *

Token: NUM_OR_ID ,Symbol: 3

Token: PLUS ,Symbol: +

Token: NUM_OR_ID ,Symbol: 4

Token: SEMI ,Symbol: ;

以上就是极悦小编介绍的"Java实现编译器词法解析入门"的内容,希望对大家有帮助,如有疑问,请在线咨询,有专业老师随时为您服务。

选你想看

你适合学Java吗?4大专业测评方法

代码逻辑 吸收能力 技术学习能力 综合素质

先测评确定适合在学习

在线申请免费测试名额
价值1998元实验班免费学
姓名
手机
提交