在下表中列出需要实现的单词符号 关键字(KW)
类别 | 语法关键词 |
---|---|
关键字 | (1) int (2) void (3) return (4) const (5) main |
运算符(OP)
运算符类型 | 语法关键词 |
---|---|
计算运算符 | (6) + (7) - (8) * (9) / (10) % |
比较运算符 | (11) = (12) > (13) < (14) == (15) <= (16) >= (17) != |
逻辑运算符 | (18) && (19) || |
界符(SE)
类型 | 语法关键词 |
---|---|
界符 | (20)( (21) ) (22) { (23) } (24); (25) , |
标识符(IDN) 定义与 C 语言保持相同,为字母、数字和下划线(_)组成的不以数字开头的串
整数(INT) 的定义与 C 语言类似,整数由数字串表示
上述符号在Java语言中,我们借助HashMap存储
public static final Map<String, Integer> TYPE_TO_CONTENT_DICT_KW = new HashMap<String, Integer>() {
{
put("int", 1);
put("void", 2);
put("return", 3);
put("const", 4);
put("main", 5);
}
};
private static final Map<String, Integer> TYPE_TO_CONTENT_DICT_OP = new HashMap<String, Integer>() {
{
put("+", 6);
put("-", 7);
put("*", 8);
put("/", 9);
put("%", 10);
put("=", 11);
put(">", 12);
put("<", 13);
put("==", 14);
put("<=", 15);
put(">=", 16);
put("!=", 17);
put("&&", 18);
put("||", 19);
}
};
public static final Map<String, Integer> TYPE_TO_CONTENT_DICT_SE = new HashMap<String, Integer>() {{
put("(", 20);
put(")", 21);
put("{", 22);
put("}", 23);
put(";", 24);
put(",", 25);
}};
借助java.io包,完成读取txt文件并将其转化为字符串
public class ReadTxt {
public static String readTxt(String txtPath) {
File file = new File(txtPath);
if (file.isFile() && file.exists()) {
try {
FileInputStream fileInputStream = new FileInputStream(file);
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
StringBuffer sb = new StringBuffer();
String text = null;
while ((text = bufferedReader.readLine()) != null) {
sb.append(text);
}
return sb.toString();
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}
如下图:
运用面向对象的编程思想,将NFA,DFA和词法分析器等抽象成一个个类。
由简到繁,先将类的定义规划好,再逐渐扩充完善细节。
根据NFA定义依据五元组$$M=(S, \sum, \sigma, S_0, F)$$构造成一个类,使用面向对象的编程方式来进行词法分析器的编写。
-
Node
表示图中的一个节点。
-
id
是节点的编号; -
isLast
用来判断是否是终止节点; -
needRollback
用来判断是否需要回退; -
tag
用于判定最终得到的type类别。 -
Node()
节点的构造函数 -
compareTo(Node o)
实现Comparable<Node>
接口,保证在后续算法中的集合中节点是有序的。
-
public class Node implements Comparable<Node> {
public int id;
public boolean isLast;
public boolean needRollback;
public String type;
public Node(int id, boolean isLast, boolean needRollback, String type) {
this.id = id;
this.isLast = isLast;
this.needRollback = needRollback;
this.type = type;
}
@Override
public int compareTo(Node o) {
return this.id - o.id;
}
}
-
Edge
表示图中的边。
fromNodeId
表示有向边的出发节点;toNodeId
使用集合表示通过tag
可以抵达的节点;tag
表示获得tag
可以从fromNodeId
转化为toNodeIds
。
public class Edge {
public int fromNodeId;
public int toNodeId;
public String tag;//边上的符号标记
public Edge(int fromNodeId, int toNodeId, String tag) {
this.fromNodeId = fromNodeId;
this.toNodeId = toNodeId;
this.tag = tag;
}
}
-
NFA
根据点集和边集构造NFA。
nodeList
用于存储点的ArrayListedgeList
用于存储边的ArrayListtags
存储状态转移的标签
public class NFA {
public ArrayList<Node> nodeList = new ArrayList<>();
public ArrayList<Edge> edgeList = new ArrayList<>();
public String[] tags = {
"[+]", "-", "[*]", "/", "%", "=", "[(]", "[)]", "[{]", "[}]", ";", ",",
">", "[^=]", "<", "|", "&", "[_a-zA-Z]", "[_0-9a-zA-Z]", "[^_0-9a-zA-Z]",
"[1-9]", "[0-9]", "[^0-9]"
};
public NFA() {//构造函数
...
}
public String toString() {//实现该方法后可在控制台打印,便于调试
...
}
}
-
DFA的数据结构与NFA类似,此处不再赘述。
-
NFA到DFA的转变需要我们实现确定化算法。
-
首先从$$s_0$$出发,仅经过任意条$$\epsilon$$箭弧能够到达的状态组成的集合$$I$$作为$$M'$$的初态$$q_0$$ .
-
分别把从$$q_0$$(对应于$$M$$的状态子集$$I$$)出发,经过任意$$a\in \sum$$的$$a$$弧转换$$I_a$$所组成的集合作为$$M'$$的状态,如此继续,直到不再有新的状态为止。
public TreeSet<Node> epsilonClosure(TreeSet<Node> nodeSet) {
//使用bfs方法计算epsilon闭包,即经过若干条epsilon边可到达的边
Queue<Node> q = new ArrayDeque<>();
for (Node n : nodeSet) q.add(n);
while (!q.isEmpty()) {
Node top = q.poll();
for (Edge e : nfa.edgeList) {
if (top.id == e.fromNodeId && e.tag.equals("epsilon")) {
if (nodeSet.contains(nfa.nodeList.get(e.toNodeId)))
continue;
nodeSet.add(nfa.nodeList.get(e.toNodeId));
q.add(nfa.nodeList.get(e.toNodeId));
}
}
}
return nodeSet;
}
public TreeSet<Node> move(TreeSet<Node> nodeSet, String a) {
//move计算经过一次a边可以到达的点
TreeSet<Node> res = new TreeSet<>();
for (Node n : nodeSet) {
for (Edge e : nfa.edgeList) {
if (n.id == e.fromNodeId && e.tag.equals(a)) {
res.add(nfa.nodeList.get(e.toNodeId));
}
}
}
return res;
}
public void determine() {
nodeList.add(new Node(0, false, false, ""));
TreeSet<Node> startNodeSet = new TreeSet<>();
startNodeSet.add(nfa.nodeList.get(0));
startNodeSet = epsilonClosure(startNodeSet);//
ArrayList<TreeSet<Node>> nodeArrayList = new ArrayList<>();
nodeArrayList.add(startNodeSet);
int pointer = 0;
int nowId = 0;
while (pointer < nodeArrayList.size()) {
TreeSet<Node> nodeSet = nodeArrayList.get(pointer);
for (String tag : nfa.tags) {
TreeSet<Node> moveNodeSet = move(epsilonClosure(nodeSet), tag);
if (moveNodeSet.isEmpty())
continue;
if (!nodeArrayList.contains(moveNodeSet)) {
nodeArrayList.add(moveNodeSet);
Node firstInSet = nodeSet.first();
nowId++;
nodeList.add(new Node(nowId, firstInSet.isLast, firstInSet.needRollback, firstInSet.type));
edgeList.add(new Edge(pointer, nowId, tag));
} else {
int toNewNodeId = nodeArrayList.indexOf(moveNodeSet);
edgeList.add(new Edge(pointer, toNewNodeId, tag));
}
}
pointer++;
}
}
-
构造状态的初始划分$\Pi$:终态$S_t$ 和非终态$S- S_t$两组
-
对$\Pi$施用传播性原则构造新划分$\Pi_n$
-
如$\Pi_n$ ==
$\Pi_f$ ,则令$\Pi_f$ =$\Pi$ 并继续步骤4,否则$\Pi$ :=$\Pi_n$ 重复2 -
为
$\Pi_f$ 中的每一组选一代表,这些代表构成M’的状态。若s是一代表,且δ(s,a)=t, 令r是t组的代表,则M’中有一转换δ’(s, a)=r。 M’ 的开始状态是含有$s_0$的那组的代表, M’的终态是含有st的那组的代表 -
去掉M’中的死状态
public void minimize() {
//最小化算法
ArrayList<TreeSet<Node>> P = new ArrayList<>();
TreeSet<Node> F = new TreeSet<>();
TreeSet<Node> NF = new TreeSet<>();
for (Node n : nodeList) {
if (n.isLast) F.add(n);
else NF.add(n);
}
P.add(F);
P.add(NF);
ArrayList<TreeSet<Node>> W = new ArrayList<>();
W.add(F);
W.add(NF);
while (!W.isEmpty()) {
TreeSet<Node> A = W.get(0);
W.remove(0);
TreeSet<Node> premove = new TreeSet<>();
for (String tag : nfa.tags) {
ArrayList<TreeSet<Node>> X = new ArrayList<>();
for (TreeSet<Node> p : P) {
TreeSet<Node> pTag = new TreeSet<>();
TreeSet<Node> pNotTag = new TreeSet<>();
for (Node n : p) {
for (Edge e : edgeList) {
if (e.fromNodeId == n.id && e.tag.equals(tag) || e.toNodeId == n.id && e.tag.equals(tag)) {
pTag.add(n);
}
}
}
for (Node n : p) {
if (!pTag.contains(n)) {
pNotTag.add(n);
}
}
if (!pTag.isEmpty() && !pNotTag.isEmpty()) {
X.add(pTag);
X.add(pNotTag);
premove = p;
break;
}
}
P.remove(premove);
for (TreeSet<Node> x : X) {
P.add(x);
if (!W.contains(x)) {
W.add(x);
} else W.remove(x);
}
}
}
ArrayList<Node> newNodeList = new ArrayList<>();
ArrayList<Edge> newEdgeList = new ArrayList<>();
for (int i = 0; i < P.size(); i++) {
TreeSet<Node> p = P.get(i);
Node firstInSet = p.first();
newNodeList.add(new Node(i, firstInSet.isLast, firstInSet.needRollback, firstInSet.type));
}
for (Edge e : edgeList) {
int fromNodeId = 0;
int toNodeId = 0;
for (int i = 0; i < P.size(); i++) {
TreeSet<Node> p = P.get(i);
if (p.contains(nodeList.get(e.fromNodeId))) {
fromNodeId = i;
}
if (p.contains(nodeList.get(e.toNodeId))) {
toNodeId = i;
}
}
newEdgeList.add(new Edge(fromNodeId, toNodeId, e.tag));
}
edgeList = newEdgeList;
nodeList = newNodeList;
startId = 1;
nowId = 1;
}
对于词法分析器的结果输出,同样借助面向对象的思想。 Token类为代词符号。
lexeme
待测代码中的单词符号tokenTpye
单词符号种别tokenNum
单词符号内容
public class Token {
String lexeme;
String tokenType;
String tokenNum;
public Token(String lexeme, String tokenType, String tokenNum) {
...//构造函数
}
}
TokenTable为单词符号构成的单词表
public class TokenTable {
private ArrayList<Token> tokens;
public TokenTable() {
tokens = new ArrayList<Token>();
}
public void printTokenTable() {
...//输出到控制台
}
public void pushToken(Token token) {
tokens.add(token);
}
public void saveTokenTable(String path) {
...//保存到文件
}
}
lexer类 根据有限状态自动机构建单词符号表
class Lexer {
private String source;
private TokenTable tokenTable;
private DFA dfa;
public Lexer(String source, TokenTable tokenTable, DFA dfa) {
this.source = source;
this.tokenTable = tokenTable;
this.dfa = dfa;
}
public void run() {
String token_now = "";
String text = source;
int i = 0;
int ID = 0;
while (i < text.length()) {
// 需要跳过的情况
Character ch = text.charAt(i);
if (token_now.equals("") && (ch == '\n' || ch == ' ' || ch == '\t')) {
i += 1;
continue;
}
token_now += ch;
// 匹配成功到下一个节点
if (dfa.nextId(ch.toString())) {
ID = dfa.nowId;
// 判断is_final
if (dfa.isFinal(ID)) {
// 判断is_back_off
if (dfa.isBackOff(ID)) {
// 指针回退一个
token_now = token_now.substring(0, token_now.length() - 1);
i -= 1;
}
// 获得最终节点的tag
String node_tag = dfa.getTag(ID);
// 这个判断应该是dfa提供的
String token_type = dfa.getTokenType(token_now, node_tag);
String token_num = dfa.getTokenNum(token_now, token_type);
tokenTable.pushToken(new Token(token_now, token_type, token_num));
token_now = "";
dfa.getStart();
}
i += 1;
}
// 匹配失败,则抛出异常
else {
System.out.println(ch);
System.out.println("Lexical error: 不符合c--词法!");
return;
}
}
if (!dfa.isFinal(ID)) {
System.out.println("Lexical error: 最终一个词不是完整的token");
return;
}
}
}
样例0
int a = 10;
int main(){
a=10;
return 0;
}
输出0
int <KW,1>
a <IDN,a>
= <OP,11>
10 <INT,10>
; <SE,24>
int <KW,1>
main <KW,5>
( <SE,20>
) <SE,21>
{ <SE,22>
a <IDN,a>
= <OP,11>
10 <INT,10>
; <SE,24>
return <KW,3>
0 <INT,0>
; <SE,24>
} <SE,23>
样例1
int a;
void func(){
a=10;
return 0;
}
输出1
int <KW,1>
a <IDN,a>
; <SE,24>
void <KW,2>
func <IDN,func>
( <SE,20>
) <SE,21>
{ <SE,22>
a <IDN,a>
= <OP,11>
10 <INT,10>
; <SE,24>
return <KW,3>
0 <INT,0>
; <SE,24>
} <SE,23>
样例2
int a = 3;
int b = 5;
void func(){
return a + b;
}
输出2
int <KW,1>
a <IDN,a>
= <OP,11>
3 <INT,3>
; <SE,24>
int <KW,1>
b <IDN,b>
= <OP,11>
5 <INT,5>
; <SE,24>
void <KW,2>
func <IDN,func>
( <SE,20>
) <SE,21>
{ <SE,22>
int <KW,1>
a <IDN,a>
= <OP,11>
5 <INT,5>
; <SE,24>
return <KW,3>
a <IDN,a>
+ <OP,6>
b <IDN,b>
; <SE,24>
} <SE,23>
样例3
int a = 3;
int b = 5;
void func(){
int a = 5;
return a + b;
}
输出3
int <KW,1>
a <IDN,a>
= <OP,11>
3 <INT,3>
; <SE,24>
int <KW,1>
b <IDN,b>
= <OP,11>
5 <INT,5>
; <SE,24>
void <KW,2>
func <IDN,func>
( <SE,20>
) <SE,21>
{ <SE,22>
int <KW,1>
a <IDN,a>
= <OP,11>
5 <INT,5>
; <SE,24>
return <KW,3>
a <IDN,a>
+ <OP,6>
b <IDN,b>
; <SE,24>
} <SE,23>
样例4
int a = 3,b = 1,c = 3;
void func(){
int a = 5;
return a + b;
}
输出4
int <KW,1>
a <IDN,a>
= <OP,11>
3 <INT,3>
, <SE,25>
b <IDN,b>
= <OP,11>
1 <INT,1>
, <SE,25>
c <IDN,c>
= <OP,11>
3 <INT,3>
; <SE,24>
void <KW,2>
func <IDN,func>
( <SE,20>
) <SE,21>
{ <SE,22>
int <KW,1>
a <IDN,a>
= <OP,11>
5 <INT,5>
; <SE,24>
return <KW,3>
a <IDN,a>
+ <OP,6>
b <IDN,b>
; <SE,24>
} <SE,23>
样例5
int a = 3;
int b = 1;
int c = 3;
int e = 0, f = 10;
void func(){
int a = 5;
int c = 2;
int temp1 = f + e;
int temp2 = f + 2;
int temp3 = c;
return a + b;
}
输出5
int <KW,1>
a <IDN,a>
= <OP,11>
3 <INT,3>
; <SE,24>
int <KW,1>
b <IDN,b>
= <OP,11>
1 <INT,1>
; <SE,24>
int <KW,1>
c <IDN,c>
= <OP,11>
3 <INT,3>
; <SE,24>
int <KW,1>
e <IDN,e>
= <OP,11>
0 <INT,0>
, <SE,25>
f <IDN,f>
= <OP,11>
10 <INT,10>
; <SE,24>
void <KW,2>
func <IDN,func>
( <SE,20>
) <SE,21>
{ <SE,22>
int <KW,1>
a <IDN,a>
= <OP,11>
5 <INT,5>
; <SE,24>
int <KW,1>
c <IDN,c>
= <OP,11>
2 <INT,2>
; <SE,24>
int <KW,1>
temp1 <IDN,temp1>
= <OP,11>
f <IDN,f>
+ <OP,6>
e <IDN,e>
; <SE,24>
int <KW,1>
temp2 <IDN,temp2>
= <OP,11>
f <IDN,f>
+ <OP,6>
2 <INT,2>
; <SE,24>
int <KW,1>
temp3 <IDN,temp3>
= <OP,11>
c <IDN,c>
; <SE,24>
return <KW,3>
a <IDN,a>
+ <OP,6>
b <IDN,b>
; <SE,24>
} <SE,23>