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, 大家可以获得代码后,自己运行尝试一下。我将在后续的文章中,继续与大家一起探讨一个完整编译器的开发。

     

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

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

  • 相关阅读:
    The resource identified by this request is only capable of generating responses with characteristics
    javaweb写的在线聊天应用
    JavaScript写一个拼图游戏
    jQ插件--时间线插件和拖拽API
    Javascript写俄罗斯方块游戏
    详解jQ的support模块
    磁盘IO的性能指标 阻塞与非阻塞、同步与异步 I/O模型
    Airflow Python工作流引擎的重要概念介绍
    DEVOPS 运维开发系列
    MYSQL 两表 排除 重复记录
  • 原文地址:https://www.cnblogs.com/csguo/p/7614698.html
Copyright © 2011-2022 走看看