zoukankan      html  css  js  c++  java
  • 用java实现一个简易编译器1-词法解析入门

    本文对应代码下载地址为:

    http://download.csdn.net/detail/tyler_download/9435103

    视频地址:

    http://v.youku.com/v_show/id_XMTQ3NTQwMDkxMg==.html?from=s1.8-1-1.2

    技术的发展可谓是日新月异,层出不穷,但无论是炙手可热的大数据,还是火烧鸟了的人工智能,所有这些高大上的尖端科技无不建立在基础技术的根基之上。编译原理,计算机网络,操作系统,便是所有软件技术的基石。在这三根支柱中,维编译原理最为难懂,特别是大学课本那种晦涩难通,不讲人话的言语,更是让人觉得这门基础技术就像九十多岁的老妪,皮肤干巴,老态龙钟,让人提不起一点欲望。除了国内教材,就算是被广为称赞的一千多页的”龙书“,也是满篇理论,让人望而生畏。

    味道怎样,咬一口就知道,手感如何,摸一把就晓得。编译原理缺的不是理论概念,而是能够动手实践的流程,代码,很多原理用话语怎么讲都难以明了,但跑一遍代码,基本就水落石出。本文本着动手实操(念第一声)的原则,用java实现一个简单的编译器,让读者朋友能一感编译原理的实质,我秉持一个原则,没有代码可实践的计算机理论,都是耍流氓。

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

    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:

     

    [java] view plain copy
     
    1. import java.util.Scanner;  
    2.   
    3.   
    4. public class Lexer {  
    5.     public static final int  EOI = 0;  
    6.     public static final int  SEMI = 1;  
    7.     public static final int  PLUS = 2;  
    8.     public static final int  TIMES = 3;  
    9.     public static final int  LP = 4;  
    10.     public static final int  RP = 5;  
    11.     public static final int  NUM_OR_ID = 6;  
    12.       
    13.     private int lookAhead = -1;  
    14.       
    15.     public String yytext = "";  
    16.     public int yyleng = 0;  
    17.     public int yylineno = 0;  
    18.       
    19.     private String input_buffer = "";  
    20.     private String current = "";  
    21.       
    22.     private boolean isAlnum(char c) {  
    23.         if (Character.isAlphabetic(c) == true ||  
    24.                 Character.isDigit(c) == true) {  
    25.             return true;  
    26.         }  
    27.           
    28.         return false;  
    29.     }  
    30.       
    31.     private int lex() {  
    32.       
    33.         while (true) {  
    34.               
    35.             while (current == "") {  
    36.                 Scanner s = new Scanner(System.in);  
    37.                 while (true) {  
    38.                     String line = s.nextLine();  
    39.                     if (line.equals("end")) {  
    40.                         break;  
    41.                     }  
    42.                     input_buffer += line;  
    43.                 }  
    44.                 s.close();  
    45.                   
    46.                 if (input_buffer.length() == 0) {  
    47.                     current = "";  
    48.                     return EOI;  
    49.                 }  
    50.                   
    51.                 current = input_buffer;  
    52.                 ++yylineno;  
    53.                 current.trim();  
    54.             }//while (current != "")  
    55.                   
    56.                 for (int i = 0; i < current.length(); i++) {  
    57.                       
    58.                     yyleng = 0;  
    59.                     yytext = current.substring(0, 1);  
    60.                     switch (current.charAt(i)) {  
    61.                     case ';': current = current.substring(1); return SEMI;  
    62.                     case '+': current = current.substring(1); return PLUS;  
    63.                     case '*': current = current.substring(1);return TIMES;  
    64.                     case '(': current = current.substring(1);return LP;  
    65.                     case ')': current = current.substring(1);return RP;  
    66.                       
    67.                     case ' ':  
    68.                     case ' ':  
    69.                     case ' ': current = current.substring(1); break;  
    70.                       
    71.                     default:  
    72.                         if (isAlnum(current.charAt(i)) == false) {  
    73.                             System.out.println("Ignoring illegal input: " + current.charAt(i));  
    74.                         }  
    75.                         else {  
    76.                               
    77.                             while (isAlnum(current.charAt(i))) {  
    78.                                 i++;  
    79.                                 yyleng++;  
    80.                             } // while (isAlnum(current.charAt(i)))  
    81.                               
    82.                             yytext = current.substring(0, yyleng);  
    83.                             current = current.substring(yyleng);   
    84.                             return NUM_OR_ID;  
    85.                         }  
    86.                           
    87.                         break;  
    88.                           
    89.                     } //switch (current.charAt(i))  
    90.                 }//  for (int i = 0; i < current.length(); i++)   
    91.               
    92.         }//while (true)   
    93.     }//lex()  
    94.       
    95.     public boolean match(int token) {  
    96.         if (lookAhead == -1) {  
    97.             lookAhead = lex();  
    98.         }  
    99.           
    100.         return token == lookAhead;  
    101.     }  
    102.       
    103.     public void advance() {  
    104.         lookAhead = lex();  
    105.     }  
    106.       
    107.     public void runLexer() {  
    108.         while (!match(EOI)) {  
    109.             System.out.println("Token: " + token() + " ,Symbol: " + yytext );  
    110.             advance();  
    111.         }  
    112.     }  
    113.       
    114.     private String token() {  
    115.         String token = "";  
    116.         switch (lookAhead) {  
    117.         case EOI:  
    118.             token = "EOI";  
    119.             break;  
    120.         case PLUS:  
    121.             token = "PLUS";  
    122.             break;  
    123.         case TIMES:  
    124.             token = "TIMES";  
    125.             break;  
    126.         case NUM_OR_ID:  
    127.             token = "NUM_OR_ID";  
    128.             break;  
    129.         case SEMI:  
    130.             token = "SEMI";  
    131.             break;  
    132.         case LP:  
    133.             token = "LP";  
    134.             break;  
    135.         case RP:  
    136.             token = "RP";  
    137.             break;  
    138.         }  
    139.           
    140.         return token;  
    141.     }  
    142. }  



    代码中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

     

    [java] view plain copy
     
    1. public class Compiler {  
    2.   
    3.     public static void main(String[] args) {  
    4.         Lexer lexer = new Lexer();  
    5.         //Parser parser = new Parser(lexer);  
    6.         //parser.statements();  
    7.         lexer.runLexer();  
    8.     }  
    9. }  


    在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: ;

     

    后记:

    该篇叙述的只是一个简单的词法解析入门,希望通过可运行的代码,让大家能体会一下词法分析的流程,从感性上获得直接的认识,为后续理解完整专业的词法解析打下基础。

    完整的代码我会上传到csdn, 大家可以获得代码后,自己运行尝试一下。我将在后续的文章中,继续与大家一起探讨一个完整编译器的开发。

     

    另外,我希望将此教程制作成视频模式,大家通过观看视频,可以更直观的看到代码调试,解析,运行等流程,更容易学习和加深理解,如果哪位朋友有兴趣,留个邮箱,我把

    制作好的视频发给你们,并虚心的向诸位朋友求教。

  • 相关阅读:
    在Centos 7下编译openwrt+njit-client
    开博随笔
    Chapter 6. Statements
    Chapter 4. Arrays and Pointers
    Chapter 3. Library Types
    Chapter 2.  Variables and Basic Types
    关于stm32不常用的中断,如何添加, 比如timer10 timer11等
    keil 报错 expected an identifier
    案例分析 串口的地不要接到电源上 会烧掉
    案例分析 CAN OPEN 调试记录 进度
  • 原文地址:https://www.cnblogs.com/csguo/p/7614698.html
Copyright © 2011-2022 走看看