LR(1)语法分析器生成器(生成Action表和Goto表)java实现(一)
序言 : 在看过<自己实现编译器链接器>源码之后,最近在看<编译器设计>,但感觉伪代码还是有点太浮空。没有掌握的感觉,也因为内网几乎没有LR(1)语法分析器生成器的内容,于是我就自己做了一个LR(1)语法分析器生成器。这个生成器除部分代码借鉴了<编译器设计>这本书有上的一些伪代码之外,其他皆为自己写的,可能不是那么完美,但也具有一些借鉴作用。在这里我会将我的思路以及实现代码全部都放在这个贴上。
说明 : 语法分析器生成器被称为编译器中的编译器,其方便快捷的特点使得在语法分析的过程中,许多编译器编写者在语法分析部分都喜欢采用的方法。语法分析器分为两类,一为生成语法分析器,其中包含生成表驱动语法分析器和生成代码语法分析器,二为手动编码语法分析器(顾明思意,手动编码就是自己写,具有可定制特殊语法的特点)。在这里是LR(1)表驱动语法分析器生成器。主要任务就是将BNF范式语句,转换为Action表和Goto表,然后编译器编写者利用这两个表来进行状态的转移去分析语法。
首先观看这贴所需 :
1) 了解 LR(1)的概念
2) 了解表驱动语法分析器的大概实现原理
3) 了解BNF范式
4) 了解FA(有限状态自动机)
5) 具有一定的数据结构与算法基础
我的LR(1)语法分析器生成器介绍 :
输入格式例子 :
start : <Goal>;
<Goal> ::= <Expr>;
<Expr> ::= <Term><Expr'>;
<Expr'> ::= "+" <Term><Expr'>
| "-" <Term><Expr'>
| "ε";
<Term> ::= <Factor><Term'>;
<Term'> ::= "*" <Factor><Term'>
| "/" <Factor><Term'>
| "ε";
<Factor> ::= "("<Expr>")"
| "num"
| "name";
#
首先,我的生成器采用的是标准输入(控制台输入),以#作为整个范式语句的结尾,这也是为了避免大量的代码去进行io操作之类的...在这里输入并非支持完整的BNF语法,而是取消了可选项[],和闭包{}等操作。可以将BNF范式以递归的形式代替闭包{},以枚举(或)的形式代替可选项。这也是为了避免出现太过复杂的数据形式去表示BNF范式。
输入符号类型集 : 终结符(T),非终结符(NT),定义符号(::=),或符号(|)。
特殊要求 :
1) 为了词法分析的便捷,对终结符,非终结符的输入作出了一定的限制 : 终结符被包含于""之内,非终结符被包含于<>之内。
2) 每句话以;结尾。
3) 利用start : <NT>;来确定目标(开始)非终结符符号。
4) 特殊终结谷 : 空 "ε",空是一个特殊的字符,也就是没有。
对于代码实现的第一步考虑即 : 如何将BNF范式语句字符串转换为一个中间格式的数据表示?
首先因为我将BNF削剪了,新语法的BNF产生式只需要利用一个NTNode来表示。因为每个产生式左端都为一个非终结符,而右端则用List<List<Integer>>来表示,所以每个NTNode包含这个非终结符的名称和这个List<List<Integer>>二维链表表示产生式右端。在这个List<List<Integer>>里,我是用一个整型来表示每个符号,首先我们可以先将终结符和非终结符映射在一张链表中,而这个表中的下标也就代表这个终结符或非终结符,但使用整型既可以表示终结符,也可以表示非终结符,这是怎么做到的呢?我采用一个 private static final int MASK = 0X80000000;来进行区分,如若这个符号是终结符,那么在表中我将其或上一个MASK,因为不可能有Integer.MAX_VALUE个T(终结符)或NT(非终结符),所以这必然是有效的。而当我们要访问时,即可通过这个链表中的元素来知道它是一个T还是NT,并且能找到它在对应的T链表或NT链表中的位置,在位置上记载这个符号的名称就可以得到它的名称了。又因为符号名称和符号所在下标是一个双射关系,所以我采用了一个Hash_Map来存储,非终结符Map,key : 非终结符名称,value : 非终结符在NTNode链表中的下标,终结符同理。这样就很好地用数据结构表示了BNF范式语句。
代码版本1.0 :
实现了将BNF字符串使用CodeAnalyzer类转换成BnfContainer。
package cn.vizdl.LR1; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Scanner; /*
项目名 : LR(1) parser generator (LR(1)语法分析器生成器)
项目分析 :
输入 : 输入某文件内存地址。且内部采用 <Expr> ::= <Term> + <Factor>; 的结构输入的LR(1)语法。
这里的仅支持 BNF范式内部的 终结符,非终结符,或运算,;(表示产生式结束),::=(表示为定义为)
在这里不支持闭包,也就是{},因为闭包可以转换为非终结符的递归。
输入文本格式 :
start : <aim_name> //aim_name表示起始符号名称
例子 :
start : <Goal>;
<Goal> ::= <Expr>;
<Expr> ::= <Expr> "+" <Term> | <Expr> "-" <Term>;
<Term> ::= <Term> "*" <Factor> | <Term> "/" <Factor>;
<Factor> ::= "number";
#
以#作为结尾
输入分析 : 因为上下文无关语法是一个四元组,而LR(1)语法又是上下文无关语法的子集。所以采用四元组的形式来表示LR(1)语法,是不会损失信息的。
四元组 (T,NT,S,P)
T : 终结符集合
NT : 非终结符集合
S : 语法的起始符号(非终结符)
P : 产生式集合
T, NT都可以用一个hash_set来表示。
P 可以分为两个部分,左侧一定是一个非终结符,右侧是一个支持或运算的产生式。
产生式左端可以使用Node节点来表示,产生式右端可以使用多个链表(具体有几个取决于当前产生式有多少个或运算符)来表示。
将当下语法分为三级,第一级是Expr,第二级别是Term,第三个级别是Factor
<Expr> ::= <Term> { "|" <Term>}; //产生式(表达式)可以表达成多个小句子 或 起来
<Term> ::= <Factor> { "+" <Factor>}; // + 表示连接
<Factor> ::= <T> | <NT>
输出 :Action 和 GoTo 表。
*/
public class Demo {
public static void main (String[] args) {
//将输入的产生式都放入ch中
Scanner scanner = new Scanner(System.in);
String s = new String();
String c;
//输入处理...
while (true) {
c = scanner.nextLine();
int i;
for (i = 0; i < c.length(); i++) {
if (c.charAt(i) != '#')
s += c.charAt(i);
else {
scanner.close();
break;
}
}
if (i != c.length()) {
break;
}
}
//
BnfContainer bc = new BnfContainer();
CodeAnalyzer ca = new CodeAnalyzer(s, bc);
ca.analyze();
bc.printBNF(); }
} /**
* 用来装载BNF范式的信息。
*/
class BnfContainer {
/**
* 内部类,NT的节点。
* @author HP
*/
class NTNode {
private String name; //符号id
private List<List<Integer>> expr;
public NTNode(String name) {
expr = new ArrayList<List<Integer>>();
this.name = name;
}
/**
* 添加一条expr
* 返回这个expr的下标
* @return
*/
public int addExpr() {
expr.add(new ArrayList<Integer>());
return expr.size() - 1;
}
/**
* 向下标为idx的expr添加value
* @param idx
* @param value
*/
public void addExprElement (int idx, int value) {
this.expr.get(idx).add(value);
}
/**
* 向最后一个表达式添加value
* @param value
*/
public void addExprElement (int value) {
this.addExprElement(this.expr.size() - 1, value);
} public void printNTNode () {
System.out.println("NTNumber : " + this.name);
for (List<Integer> list : this.expr) {
for (Integer val : list) {
System.out.print(val + " ");
}System.out.println();
}
}
} //常量定义
/**
* 这两个常量只出现在终结符
* 因为要将终结符和非终结符
* 放在同一个链表中
* 所以使用这个来辨别终结符和非终结符。
*/
private static final int MASK = 0X80000000; //掩码,用来给终结符做掩饰的编码。
private static final int DECODE = 0X7fffffff; //解码,破译掩码得到原本的编码。
/**
* 非终结符Map
* key : 非终结符名称
* value : 非终结符在production链表中的下标
*/
private HashMap<String,Integer> NTMap;
/**
* 终结符Map
* key : 终结符名称
* value : 终结符在T链表中的下标
*/
private HashMap<String,Integer> TMap;
// 终结符链表
private ArrayList<String> T;
// 产生式链表,因为一个非终结符一个产生式具有双射关系。
private ArrayList<NTNode> production;
//如若未设置,默认为0
public int startIndex = 0;
public BnfContainer() {
//内部数据结构初始化
NTMap = new HashMap<String,Integer>();
TMap = new HashMap<String,Integer>();
T = new ArrayList<String>();
production = new ArrayList<NTNode>();
} /**
* 设置开始非终结符
* @param name
*/
public void setStart (String name) {
this.addNT(name);
this.startIndex = this.NTMap.get(name);
} /**
* 将非终结符的名字传入,即可添加一个非终结符节点。
* @param name
*/
public void addNT (String name) {
if (name.isEmpty()) {
System.out.println("终结符不可为空");
System.exit(-1);
}
if (!NTMap.containsKey(name)) {
NTNode node = new NTNode(name);
NTMap.put(name, production.size());
production.add(node);
}
} /**
* 将终结符传入,增加非终结符。
* @param name
*/
public void addT(String name) {
if (!this.TMap.containsKey(name)) {
this.TMap.put(name, T.size());
//System.out.println(name);
this.T.add(name);
}
} /**
* 输入终结符名称
* 获取终结符编号
* 如若存在当前终结符,返回编号
* 否则返回-1,输出错误警告并且退出。
* @param name
* @return
*/
private int getTSerialNumber (String name) {
this.notFindTWarning(name);
return this.TMap.get(name) | BnfContainer.MASK;
} /**
* 输入非终结符名称
* 获取非终结符编号
* 如若存在当前非终结符,返回编号
* 否则返回-1,输出错误警告并且退出。
* @param name
* @return
*/
private int getNTSerialNumber (String name) {
this.notFindNTWarning(name);
return this.NTMap.get(name);
} /**
* 创建新的表达式并添加到名称为name的非终结符节点上
* 返回表达式编号
*/
public int creatNewExper(String name) {
this.notFindNTWarning(name);
NTNode ntn = this.production.get(this.NTMap.get(name));
return ntn.addExpr();
}
/**
* 向左端非终结符名称为name的产生式
* 第idx表达式添加元素
* @param name
* @param idx
* @param isNt
*/
public void addExpeElement(String name, int idx,boolean isNt, String addElement) {
NTNode ntn = this.production.get(this.NTMap.get(name));
if (isNt) {
this.notFindNTWarning(name);
this.notFindNTWarning(addElement);
ntn.addExprElement(idx, this.getNTSerialNumber(addElement));
}else {
this.addT(addElement);
ntn.addExprElement(idx, this.getTSerialNumber(addElement));
}
} /**
* 向左端非终结符名称为name的产生式
* 最后一个表达式添加元素
* @param name
* @param list
*/
public void addExpeElement(String name,boolean isNt, String addElement) {
NTNode ntn = this.production.get(this.NTMap.get(name));
if (isNt) {
this.notFindNTWarning(name);
this.notFindNTWarning(addElement);
ntn.addExprElement(this.getNTSerialNumber(addElement));
}else {
this.addT(addElement);
ntn.addExprElement(this.getTSerialNumber(addElement));
}
} /**
* 如若找到了当前非终结符,什么都不会发生。
* 否则会提示并且退出程序
* @param name
*/
private void notFindNTWarning(String name) {
if (!this.NTMap.containsKey(name)) {
System.out.println("错误的非终结符" + name + "!");
System.exit(-1);
}
}
/**
* 如若找到了当前终结符,什么都不会发生。
* 否则会提示并且退出程序
* @param name
*/
private void notFindTWarning(String name) {
if (!this.TMap.containsKey(name)) {
System.out.println("错误的终结符" + name + "!");
System.exit(-1);
}
} public void printBNF() {
System.out.println("开始非终结符为 : " + this.production.get(startIndex).name);
System.out.println("终结符对应表 : ");
for (int i = 0; i < this.T.size(); i++) {
System.out.println(this.T.get(i) + " : " + (i | MASK));
}
System.out.println("非终结符对应表 : ");
for (int i = 0; i < this.production.size(); i++) {
System.out.println(this.production.get(i).name + " : " + i);
}
for (NTNode ntn : this.production) {
ntn.printNTNode();
}
}
} /**
* 代码分析器
* 可以将代码转换为信息等价的数据结构
*/
class CodeAnalyzer {
class Token{
boolean isNt;
String name;
public Token (boolean isNt, String name) {
this.isNt = isNt;
this.name = name;
}
}
private char[] text;
private int textSize = 0; //字符串有效长度
private int point = 0; //text解析进度的指针
private BnfContainer bc;
private Token token;
String left; //左侧非终结符
private int count = 0; //记录当前已经解析到哪个产生式了
public CodeAnalyzer (String text, BnfContainer bc) {
this.bc = bc;
//初始化代码分析器
this.initText(text);
this.initStartSymbol();
this.initCodeAnalyzer();
}
/**
* 输入字符串文本,返回处理完毕的字符数组。
* @param s
* @return
*/
private void initText(String s) {
this.text = s.toCharArray();
int idx = 0;
//将字符串变为一个紧凑的字符数组(去除一些妨碍的字符)
while (idx < text.length) {
if (text[idx] == '\r' || text[idx] == '\n' || text[idx] == '\t' || text[idx] == ' ') {
idx++;
}else {
text[textSize++] = text[idx++];
}
}
} private void initStartSymbol() {
// 验证是否存在start:<
point = 0;
char[] needle = { 's', 't', 'a', 'r', 't', ':', '<' };
if (textSize <= needle.length) {
this.notFindStartNT();
}
point = 0;
while (point < needle.length) {
if (needle[point] == text[point]) {
point++;
} else {
this.notFindStartNT();
}
}
point = needle.length;
while (point < textSize && text[point] != '>') {
point++;
}
this.bc.setStart(new String(text, needle.length, point - needle.length));
this.skip(Type.RT);
this.skip(Type.SEMICOLON);
}
/**
* 通过skip来跳过字符
*/
enum Type{
LT, //左尖括号
RT, //右尖括号
SEMICOLON, //分号
QUOTE, //双引号
OR, //或
COLON, // :
EQ, //等于号
}
private void skip (Type t) {
switch(t) {
case LT:
this.skip('<');
break;
case RT:
this.skip('>');
break;
case OR:
this.skip('|');
break;
case SEMICOLON:
this.skip(';');
break;
case QUOTE:
this.skip('"');
break;
case COLON:
this.skip(':');
break;
case EQ:
this.skip('=');
break;
}
}
private void skip (char c) {
if (point >= this.textSize || this.text[point] != c) {
System.out.println("第" + this.count + "个产生式,缺少符号 " + c);
System.exit(-1);
}
point++;
}
/**
* 报错 : 没有找到目标(开始)非终结符号! 并退出程序。
*/
private void notFindStartNT() {
System.out.println("没有找到目标非终结符号!");
System.exit(-1);
} /**
* 之所以一开始就要添加非终结符,而不在解析BNF时候添加
* 是因为,非终结符存在定义的问题,如若 没有定义
* 但有使用(只在右侧出现,未在左侧定义),这个就是错误的。
*/
private void initCodeAnalyzer() {
int idx = this.point;
this.point = 0;
this.count = 0;
while (true) {
while (this.point < textSize && text[this.point] != ';') {
this.point++;
}this.point++;
this.count++;
//如若分号后面没有左括号
if (this.point >= textSize) {
break;
}
String name = this.getNT();
bc.addNT(name);
}this.count = 0;
this.point = idx;
} /**
* BNF
* 从point开始解析字符串。
* <Goal> ::= {<Production>}
* <Production> ::= <左侧非终结符> "::=" <Expr>;
* <Expr> ::= <Term> { "|" <Term>}";";
* <Term> ::= {<Factor>}; //Term在这就是多个终结符或非终结符相连接
* <Factor> ::= <T> | <NT>
*/
public void analyze() {
while (point < this.textSize) {
this.count++;
production();
}
} public void production(){
//先跳过左侧非终结符
this.left = this.getNT();
this.skipDefineSymol();
this.expr();
}
/**
* 跳过 ::=
*/
public void skipDefineSymol() {
skip(Type.COLON);
skip(Type.COLON);
skip(Type.EQ);
}
/**
* 获取非终结符
* <xxx>
*/
public String getNT () {
skip(Type.LT);
StringBuilder res = new StringBuilder();
while (this.point < this.textSize && text[this.point] != '>') {
res.append(text[this.point++]);
}
skip(Type.RT);
return res.toString();
} /**
* 当前指针指向 "T" 中第一个"
* @return
*/
public String getT() {
this.skip(Type.QUOTE);
StringBuilder res = new StringBuilder();
while (this.point < this.textSize && this.text[this.point] != '"') {
res.append(text[this.point++]);
}
this.skip(Type.QUOTE);
return res.toString();
} /**
* 当前指针指向 ::= <T>... 中 = 后一个符号
*/
public void expr(){
this.term();
while (this.point < this.textSize && text[this.point] == '|') {
this.skip(Type.OR);
term();
}this.skip(Type.SEMICOLON);
} /**
* 如若还有符号,当前符号指向 终结符或非终结符的符号 < 或者 "
*/
public void term(){
//创建一个属于当前term的链表
bc.creatNewExper(this.left);
while (this.point < this.textSize && (text[this.point] == '"' || text[this.point] == '<')) {
factor();
bc.addExpeElement(this.left, token.isNt, token.name);
}
} /**
* 通过factor获取token
*/
public void factor(){
//非终结符
if (text[this.point] == '"') {
String name = this.getT();
this.token = new Token(false, name);
}else {
String name = this.getNT();
token = new Token (true, name);
}
}
}
下一篇完整代码连接 : https://www.cnblogs.com/vizdl/p/11331278.html
LR(1)语法分析器生成器(生成Action表和Goto表)java实现(一)的更多相关文章
- LR(1)语法分析器生成器(生成Action表和Goto表)java实现(二)
本来这次想好好写一下博客的...结果耐心有限,又想着烂尾总比断更好些.于是还是把后续代码贴上.不过后续代码是继续贴在BNF容器里面的...可能会显得有些臃肿.但目前管不了那么多了.先贴上来吧hhh.说 ...
- 求LR(0)文法的规范族集和ACTION表、GOTO表的构造算法
原理 数据结构 // GO private static Map<Map<Integer,String>,Integer> GO = new HashMap<Map< ...
- C# 语法分析器(二)LR(0) 语法分析
系列导航 (一)语法分析介绍 (二)LR(0) 语法分析 (三)LALR 语法分析 (四)二义性文法 (五)错误恢复 (六)构造语法分析器 首先,需要介绍下 LALR 语法分析的基础:LR(0) 语法 ...
- 编译原理简单语法分析器(first,follow,分析表)源码下载
编译原理(简单语法分析器下载) http://files.cnblogs.com/files/hujunzheng/%E5%8A%A0%E5%85%A5%E5%90%8C%E6%AD%A5%E7%AC ...
- 开源语法分析器--ANTLR
序言 有的时候,我还真是怀疑过上本科时候学的那些原理课究竟是不是在浪费时间.比方学完操作系统原理之后我们并不能自己动手实现一个操作系统:学完数据库原理我们也不能弄出个像样的DBMS出来:相同,学完 ...
- SQLite Lemon 语法分析器学习与使用
本文是浙江大学出版社的<LEMON语法分析生成器(LALR 1类型)源代码情景分析>学习笔记. 用到的Windows下的编译器介绍MinGW(http://www.mingw.org/): ...
- 简单的C语言编译器--语法分析器
语法分析算是最难的一部分了.总而言之,语法分析就是先设计一系列语法,然后再用设计好的语法去归约词法分析中的结果.最后将归约过程打印出来,或者生成抽象语法树. 1. 设计文法 以下是我的文法(引入的 ...
- 【编译原理】c++实现自下而上语法分析器
写在前面:本博客为本人原创,严禁任何形式的转载!本博客只允许放在博客园(.cnblogs.com),如果您在其他网站看到这篇博文,请通过下面这个唯一的合法链接转到原文! 本博客全网唯一合法URL:ht ...
- C# DateTime的11种构造函数 [Abp 源码分析]十五、自动审计记录 .Net 登陆的时候添加验证码 使用Topshelf开发Windows服务、记录日志 日常杂记——C#验证码 c#_生成图片式验证码 C# 利用SharpZipLib生成压缩包 Sql2012如何将远程服务器数据库及表、表结构、表数据导入本地数据库
C# DateTime的11种构造函数 别的也不多说没直接贴代码 using System; using System.Collections.Generic; using System.Glob ...
随机推荐
- python查询elasticsearch(Query DSL) 实例
import datetime import sys import getopt import hashlib from elasticsearch import Elasticsearch &quo ...
- spark 源码分析之五 -- Spark内置RPC机制剖析之一创建NettyRpcEnv
在前面源码剖析介绍中,spark 源码分析之二 -- SparkContext 的初始化过程 中的SparkEnv和 spark 源码分析之四 -- TaskScheduler的创建和启动过程 中的C ...
- kafka入门(一)简介
1 什么是kafk Apache kafka是消息中间件的一种,在开始学习之前,先简单的解释一下什么是消息中间件. 举个例子,生产者消费者,生产者生产鸡蛋,消费者消费鸡蛋,生产者生产一个鸡蛋,消费者就 ...
- 用CSS3 vh 简单实现DIV全屏居中
vh.vw.vmin.vmax介绍 vw:视窗宽度的百分比(1vw 代表视窗的宽度为 1%)vh:视窗高度的百分比vmin:当前 vw 和 vh 中较小的一个值vmax:当前 vw 和 vh 中较大的 ...
- Python与C/C++相互调用
参考链接:https://www.cnblogs.com/yanzi-meng/p/8066944.html
- spring系列(一):超级经典入门
一 spring是什么 Spring是一个开源框架,它由RodJohnson创建.它是为了解决企业应用开发的复杂性而创建的.Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情. ...
- CDQZ集训DAY6 日记
又炸了. 早上起来其他竞赛生也走了,食堂做饭做的挺潦草,但为什么四川烧麦的馅是米啊??!! 起来看题总觉得都似曾相识.第一题打完40分暴力后想拿莫队搞到70分,但发现能想到的莫队维护都是nsqrt(n ...
- NOIp2018 TG day1 T2暨洛谷P5020 货币系统:题解
题目链接:https://www.luogu.org/problemnew/show/P5020 这道题感觉比较水啊,身为普及组蒟蒻都不费力的做出来了,而且数据范围应该还能大一些,n起码几万几十万都不 ...
- [leetcode] 134. Gas Station (medium)
原题 题意: 过一个循环的加油站,每个加油站可以加一定数量的油,走到下一个加油站需要消耗一定数量的油,判断能否走一圈. 思路: 一开始思路就是遍历一圈,最直接的思路. class Solution { ...
- 如何让Git适应敏捷开发流程?
一旦涉及到版本控制系统,Git实际上代表敏捷开发的水平.Git作为一款强大的开源系统,有较强的灵活性,可以按需匹配任何开发团队的工作流程.而这种分布式相比较集中式来说,可以赋予系统更好的性能特征,且允 ...