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

代码中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. public static void main(String[] args) {
  3. Lexer lexer = new Lexer();
  4. //Parser parser = new Parser(lexer);
  5. //parser.statements();
  6. lexer.runLexer();
  7. }
  8. }

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

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

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

时间: 2024-08-07 16:09:18

用java实现一个简易编译器1-词法解析入门的相关文章

用java实现一个简易编译器-语法解析

语法和解析树: 举个例子看看,语法解析的过程.句子:"我看到刘德华唱歌".在计算机里,怎么用程序解析它呢.从语法上看,句子的组成是由主语,动词,和谓语从句组成,主语是"我",动词是"看见", 谓语从句是"刘德华唱歌".因此一个句子可以分解成 主语 + 动词 + 谓语从句: 句子-->主语+动词 + 谓语从句 主语是名词,因此有 : 主语->名词 句子里的名词有: "我", "刘德华&q

Java实现一个简易HTTP服务器 (二) -- 多线程

Java实现一个简易HTTP服务器 (二) -- 多线程 运行方法:新建项目后在目录下创建index.html. favicon为可选项,作为网站图标 public class Server { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(80); System.out.println("Server started at &quo

Java实现一个简易HTTP服务器(一)-- socket

Java实现一个简易HTTP服务器(一)-- socket -----2019-9-29---------- public class Server { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(89); System.out.println("Server started at " + new Date() + &qu

Java实现一个简易HTTP服务器(三)

从InputStream读取数据,构造Request.Request包含Http请求的参数. public class Request { public String method; // 请求方法 public String path; // 请求路径 public String portal; // 协议名称 public Map<String, String> map; // 请求头中的附加参数 public String payload; // 请求体 private int BUFF

java 实现一个简易计算器

import java.util.Scanner;public class Test { public static void main(String[] args) { count(); } public static void count() { Scanner input1 = new Scanner(System.in); System.out.println("输入一个数"); double a = input1.nextDouble(); while (true) { Sc

学了编译原理能否用 Java 写一个编译器或解释器?

16 个回答 默认排序? RednaxelaFX JavaScript.编译原理.编程 等 7 个话题的优秀回答者 282 人赞同了该回答 能.我一开始学编译原理的时候就是用Java写了好多小编译器和解释器.其实用什么语言来实现编译器并不是最重要的部分(虽然Java也不是实现编译器最方便的语言),最初用啥语言都可以. 我在大学的时候,我们的软件工程和计算机科学的编译原理课的作业好像都是可以用Java来写的.反正我印象中我给这两门课写的作业都是用的Java. ===================

用java开发编译器之Thompson构造:正则表达式的词法解析

Thompson构造:正则表达式的词法解析 大家好,欢迎大家来到coding迪斯尼,阅读博客的朋友可以到我的网易云课堂中,通过视频的方式查看代码的调试和执行过程: http://study.163.com/course/courseMain.htm?courseId=1002830012 继上一节我们开发了闭包替换功能后,这一节,我们继续推进Thompson 构造算法的开发.我们的目标是,给定一组正则表达式后,把他转换为NFA有限状态自动机.无论是正则表达式,还是最终的有限状态自动机,他们的本质

通过实现一个简易打包工具,分析打包的原理

概述 眼下wepack似乎已经成了前端开发中不可缺少的工具之一,而他的一切皆模块的思想随着webpack版本不断的迭代(webpack 4)使其打包速度更快,效率更高的为我们的前端工程化服务 相信大家使用webpack已经很熟练了,他通过一个配置对象,其中包括对入口,出口,插件的配置等,然后内部根据这个配置对象去对整个项目工程进行打包,从一个js文件切入(此为单入口,当然也可以设置多入口文件打包),将该文件中所有的依赖的文件通过特定的loader和插件都会按照我们的需求为我们打包出来,这样在面对

从零开始实现一个简易的Java MVC框架(三)--实现IOC

Spring中的IOC IoC全称是Inversion of Control,就是控制反转,他其实不是spring独有的特性或者说也不是java的特性,他是一种设计思想.而DI(Dependency Injection),即依赖注入就是Ioc的一种实现方式.关于Ioc和DI的具体定义和优缺点等大家可以自行查找资料了解一下,这里就不详细赘述,总之spring的IoC功能很大程度上便捷了我们的开发工作. 在实现我们的Ioc之前,我们先了解一下spring的依赖注入,在spring中依赖注入有三种方式