用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:
- 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: ;
后记:
该篇叙述的只是一个简单的词法解析入门,希望通过可运行的代码,让大家能体会一下词法分析的流程,从感性上获得直接的认识,为后续理解完整专业的词法解析打下基础。
完整的代码我会上传到csdn, 大家可以获得代码后,自己运行尝试一下。我将在后续的文章中,继续与大家一起探讨一个完整编译器的开发。
另外,我希望将此教程制作成视频模式,大家通过观看视频,可以更直观的看到代码调试,解析,运行等流程,更容易学习和加深理解,如果哪位朋友有兴趣,留个邮箱,我把
制作好的视频发给你们,并虚心的向诸位朋友求教。
用java实现一个简易编译器1-词法解析入门的更多相关文章
- 用java实现一个简易编译器2-语法解析
- 用java实现一个简易编译器-语法解析
语法和解析树: 举个例子看看,语法解析的过程.句子:“我看到刘德华唱歌”.在计算机里,怎么用程序解析它呢.从语法上看,句子的组成是由主语,动词,和谓语从句组成,主语是“我”,动词是“看见”, 谓语从句 ...
- 用java实现一个简易编译器
- 使用 java 实现一个简单的 markdown 语法解析器
1. 什么是 markdown Markdown 是一种轻量级的「标记语言」,它的优点很多,目前也被越来越多的写作爱好者,撰稿者广泛使用.看到这里请不要被「标记」.「语言」所迷惑,Markdown 的 ...
- java 实现一个简易计算器
import java.util.Scanner;public class Test { public static void main(String[] args) { count(); } pub ...
- python django搭建一个简易博客的解析(按照文件顺序逐一讲解)
上次讲解了一下各py文件的内容,但比较乱,所以这次整理了一个顺序版. 源代码请在http://github/Cheng0829/mysite自行下载 mysite: db.sqlite3:数据库文件. ...
- 学了编译原理能否用 Java 写一个编译器或解释器?
16 个回答 默认排序 RednaxelaFX JavaScript.编译原理.编程 等 7 个话题的优秀回答者 282 人赞同了该回答 能.我一开始学编译原理的时候就是用Java写了好多小编译器和 ...
- 从零开始实现一个简易的Java MVC框架(三)--实现IOC
Spring中的IOC IoC全称是Inversion of Control,就是控制反转,他其实不是spring独有的特性或者说也不是java的特性,他是一种设计思想.而DI(Dependency ...
- 如何使用Java AWT 创建一个简易计算器
摘要:手把手教你使用 Java AWT 创建一个简易计算器. 本文分享自华为云社区<手把手教你使用 Java AWT 创建一个简易计算器>,作者:海拥 . 关于AWT AWT (抽象窗口工 ...
随机推荐
- CentOS 7 Flannel的安装与配置
1. 安装前的准备 etcd 3.2.9 Docker 17.12.0-ce 三台机器10.100.97.236, 10.100.97.92, 10.100.97.81 etcd不同版本之间的差别还是 ...
- Replication--复制事务和复制命令
--=============================================== 对复制一直属于一知半解浑浑噩噩的状态,仅知道一些皮毛,对很多细节没有深入学习过, 如果不对之处,请各 ...
- 理解go语言 协程之间的通讯
go已经越来越被重视了,特别适合大型互联网公司基础服务的编写,高效,高并发,可以同时允许多个明星出轨,多个明星结婚 都不在话下,下面介绍下GO协程通讯的过程 直接上代码 package main im ...
- netcore问题总结
1. webclient在在netcore异步文件下载的时候,下载进度为空,只有最后下载完了,进度才会是100%,但是在netframework中就没有这个问题,异步文件下载进度会正常提醒. 2. n ...
- ASP.NET MVC 实现带论坛功能的网站 第一步——-实现用户注册.
首先我们要实现用户的注册功能.进入visual studio 点击文件->新建->项目->选择ASP.NET Web应用程序(.NET Framework)->选择的模板为MV ...
- can not connect cube in performancce dashboard
需要在所有安装sharepoint服务器里面安装ADOMD组件 \Program Files\Microsoft Office Servers\15.0\WebServices\PpsMonitori ...
- 批处理系列(18) - 基于BitLocker的开锁上锁
首先要配置好BitLocker. 上锁 lock.bat @echo off rem 上锁前要结束一些程序,浏览器要注意:有恢复上次关闭项功能,按需设置取消该操作 taskkill /f /t /im ...
- LinkedBlockingQueue源码解析(1)
此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 1.对于LinkedBlockingQueue需要掌握以下几点 创建 入队(添加元素) 出队(删除元素) 2 ...
- Spring 开发第一步(四)Spring与JDBC事务
Spring使用各种不同的TransactionManager来管理各种不同数据源事务底层(比如jdbc数据源.hibernate数据源.JPA数据源等等).在此基础上使用各种对应的Template来 ...
- SFML从入门到放弃(3) 视角和碰撞检测
SFML从入门到放弃(3) 视角和碰撞检测 视角 window.draw();所画出的对象是在世界坐标的绝对位置. 视角可以选定在窗口中显示世界坐标下的的哪一个区域. sf::View init_vi ...