从零写一个编译器(八):语义分析之构造符号表

项目的完整代码在 C2j-Compiler

前言

在之前完成了描述符号表的数据结构,现在就可以正式构造符号表了。符号表的创建自然是要根据语法分析过程中走的,所以符号表的创建就在LRStateTableParser里的takeActionForReduce方法

不过在此之前,当然还需要一个方便对这个符号表操作的类了

这一篇主要的两个文件是

  • TypeSystem.java
  • LRStateTableParser.java

操作符号表

操作符号表的方法都在TypeSystem类里。

TypeSystem里主要有这几个方法:

类型说明符

逻辑都很简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public TypeLink newType(String typeText) {
Specifier sp;
int type = Specifier.NONE;
boolean isLong = false, isSigned = true;
switch (typeText.charAt(0)) {
case 'c':
if (typeText.charAt(1) == 'h') {
type = Specifier.CHAR;
}
break;
case 'd':
case 'f':
System.err.println("Floating point Numbers are not supported");
System.exit(1);
break;
case 'i':
type = Specifier.INT;
break;
case 'l':
isLong = true;
break;
case 'u':
isSigned = false;
break;
case 'v':
if (typeText.charAt(2) == 'i') {
type = Specifier.VOID;
}
break;
case 's':
//ignore short signed
break;
default:
break;
}

sp = new Specifier();
sp.setType(type);
sp.setLong(isLong);
sp.setSign(isSigned);

TypeLink link = new TypeLink(false, false, sp);

return link;
}

创建存储类型

其实这一部分有的到后面解释执行或者代码生成的时候,现在这个编译器是不处理的

这一部分的逻辑也很简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public TypeLink newClass(String classText) {
Specifier sp = new Specifier();
sp.setType(Specifier.NONE);
setClassType(sp, classText.charAt(0));

TypeLink link = new TypeLink(false, false, sp);
return link;
}

private void setClassType(Specifier sp, char c) {
switch(c) {
case 0:
sp.setStorageClass(Specifier.FIXED);
sp.setStatic(false);
sp.setExternal(false);
break;
case 't':
sp.setStorageClass(Specifier.TYPEDEF);
break;
case 'r':
sp.setStorageClass(Specifier.REGISTER);
break;
case 's':
sp.setStatic(true);
break;
case 'e':
sp.setExternal(true);
break;
default:
System.err.println("Internal error, Invalid Class type");
System.exit(1);
break;
}
}

给符号添加修饰符

addSpecifierToDeclaration是为当前整个Symbol链都加上修饰符,比如遇见int x,y,z;这种情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public Declarator addDeclarator(Symbol symbol, int declaratorType) {
Declarator declarator = new Declarator(declaratorType);
TypeLink link = new TypeLink(true, false, declarator);
symbol.addDeclarator(link);

return declarator;
}

public void addSpecifierToDeclaration(TypeLink specifier, Symbol symbol) {
while (symbol != null) {
symbol.addSpecifier(specifier);
symbol = symbol.getNextSymbol();
}
}

剩下用到再提

构造符号表

构造符号表的过程在语法分析的过程里,也就是进行reduce操作的时候。很好理解问什么符号表的构建会在reduce操作时发生,因为当发生reduce操作的时候就代表产生了一个变量名或者是产生了一个变量定义,这时候是把它们加入符号表的最好时机

所以在语法分析的过程中加入一个方法来处理reduce时的操作,此外还需要一个属性堆栈来辅助操作,属性堆栈的作用就是用来保存之前的操作,以方便后面使用

比如现在产生了一个修饰符,但是语法分析过程还没有读入变量名,就先把这个修饰符压入属性堆栈,等读入变量名的时候就可以创建一个Symbol,再把修饰符弹出堆栈链接到Symbol上

takeActionForReduce

takeActionForReduce方法的参数就是做reduce操作依据的产生式的编号

只看一下比较复杂的:

  • StructSpecifier_TO_TypeSpecifier:

这是结构定义生成一个结构体类型的declartor,对应的推导式是

1
2
3
* TYPE_SPECIFIER -> STRUCT_SPECIFIER
* STTUCT_SPECIFIER -> STRUCT OPT_TAG LC DEF_LIST RC
* | STRUCT TAG

先生成一个Specifier再设置它的vStruct属性,也就是声明这是一个结构体,之后拿到结构体定义,从这个推导式中我们可以看出,结构体定义肯定在它的上一步,也就是被放入了属性堆栈的顶端

  • SPECIFIERS_TypeOrClass_TO_SPECIFIERS

这里对应的推导式是

1
*  SPECIFIERS ->  SPECIFIERS TYPE_OR_CLASS

目的其实就是合并多个specifier

  • DEFLIST

1
2
3
4
case SyntaxProductionInit.ExtDeclList_COMMA_ExtDecl_TO_ExtDeclList:
case SyntaxProductionInit.VarList_COMMA_ParamDeclaration_TO_VarList:
case SyntaxProductionInit.DeclList_Comma_Decl_TO_DeclList:
case SyntaxProductionInit.DefList_Def_TO_DefList:

其实这部分是连接一系列变量的定义,比如ExtDeclList_COMMA_ExtDecl_TO_ExtDeclList就是对应像x,y这样用逗号分割的连续定义,把它们的符号连接起来

  • DECLARTOR

1
2
3
case SyntaxProductionInit.OptSpecifier_ExtDeclList_Semi_TO_ExtDef:
case SyntaxProductionInit.TypeNT_VarDecl_TO_ParamDeclaration:
case SyntaxProductionInit.Specifiers_DeclList_Semi_TO_Def:

这里其实是完成一个完成的定义,像int x,y;后把拿到Specifier放入到每个符号去,比较特殊的是拿到Specifier的位置,这是根据reduce次数计算的。

  • STRUCT和FUNCTION

接下来比较需要注意的就是处理struct和function的方法

  • 在处理连续的变量声明的时候,如果遇见的类型是结构体的话,就进行这样一个处理,如果当前是个结构体声明,我们就直接把这个结构体里的符号,也就是structDefine的fields放入argList(这个原本应该是放函数参数的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private void handleStructVariable(Symbol symbol) {
if (symbol == null) {
return;
}

boolean isStruct = false;
TypeLink typeLink = symbol.typeLinkBegin;
Specifier specifier = null;
while (typeLink != null) {
if (!typeLink.isDeclarator) {
specifier = (Specifier) typeLink.getTypeObject();
if (specifier.getType() == Specifier.STRUCTURE) {
isStruct = true;
break;
}
}

typeLink = typeLink.toNext();
}

if (isStruct) {
StructDefine structDefine = specifier.getStruct();
Symbol copy = null, headCopy = null, original = structDefine.getFields();
while (original != null) {
if (copy != null) {
Symbol sym = original.copy();
copy.setNextSymbol(sym);
copy = sym;
} else {
copy = original.copy();
headCopy = copy;
}

original = original.getNextSymbol();
}

symbol.setArgList(headCopy);
}
}
  • 这个方法其实就是根据有没有参数来判断当前函数的名字在堆栈的哪个位置
1
2
3
4
5
6
7
8
9
10
11
private void setFunctionSymbol(boolean hasArgs) {
Symbol funcSymbol;
if (hasArgs) {
funcSymbol = (Symbol) valueStack.get(valueStack.size() - 4);
} else {
funcSymbol = (Symbol) valueStack.get(valueStack.size() - 3);
}

typeSystem.addDeclarator(funcSymbol, Declarator.FUNCTION);
attributeForParentNode = funcSymbol;
}

takeActionForReduce源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
private void takeActionForReduce(int productionNum) {
switch (productionNum) {
case SyntaxProductionInit.TYPE_TO_TYPE_SPECIFIER:
attributeForParentNode = typeSystem.newType(text);
break;
case SyntaxProductionInit.EnumSpecifier_TO_TypeSpecifier:
attributeForParentNode = typeSystem.newType("int");
break;
case SyntaxProductionInit.StructSpecifier_TO_TypeSpecifier:
attributeForParentNode = typeSystem.newType(text);
TypeLink link = (TypeLink) attributeForParentNode;
Specifier sp = (Specifier) link.getTypeObject();
sp.setType(Specifier.STRUCTURE);
StructDefine struct = (StructDefine) valueStack.get(valueStack.size() - 1);
sp.setStruct(struct);
break;

case SyntaxProductionInit.SPECIFIERS_TypeOrClass_TO_SPECIFIERS:
attributeForParentNode = valueStack.peek();
Specifier last = (Specifier) ((TypeLink) valueStack.get(valueStack.size() - 2)).getTypeObject();
Specifier dst = (Specifier) ((TypeLink) attributeForParentNode).getTypeObject();
typeSystem.specifierCopy(dst, last);
break;
case SyntaxProductionInit.NAME_TO_NewName:
case SyntaxProductionInit.Name_TO_NameNT:
attributeForParentNode = typeSystem.newSymbol(text, nestingLevel);
break;
case SyntaxProductionInit.START_VarDecl_TO_VarDecl:
case SyntaxProductionInit.Start_VarDecl_TO_VarDecl:
typeSystem.addDeclarator((Symbol) attributeForParentNode, Declarator.POINTER);
break;

case SyntaxProductionInit.VarDecl_LB_ConstExpr_RB_TO_VarDecl:
Declarator declarator = typeSystem.addDeclarator((Symbol) valueStack.get(valueStack.size() - 4), Declarator.ARRAY);
int arrayNum = (Integer) attributeForParentNode;
declarator.setElementNum(arrayNum);
attributeForParentNode = valueStack.get(valueStack.size() - 4);
break;

case SyntaxProductionInit.Name_TO_Unary:
attributeForParentNode = typeSystem.getSymbolByText(text, nestingLevel, symbolScope);
break;

case SyntaxProductionInit.ExtDeclList_COMMA_ExtDecl_TO_ExtDeclList:
case SyntaxProductionInit.VarList_COMMA_ParamDeclaration_TO_VarList:
case SyntaxProductionInit.DeclList_Comma_Decl_TO_DeclList:
case SyntaxProductionInit.DefList_Def_TO_DefList: {

Symbol currentSym = (Symbol) attributeForParentNode;
Symbol lastSym = null;
if (productionNum == SyntaxProductionInit.DefList_Def_TO_DefList) {
lastSym = (Symbol) valueStack.get(valueStack.size() - 2);
} else {
lastSym = (Symbol) valueStack.get(valueStack.size() - 3);
}

currentSym.setNextSymbol(lastSym);
}
break;
case SyntaxProductionInit.OptSpecifier_ExtDeclList_Semi_TO_ExtDef:
case SyntaxProductionInit.TypeNT_VarDecl_TO_ParamDeclaration:
case SyntaxProductionInit.Specifiers_DeclList_Semi_TO_Def:
Symbol symbol = (Symbol) attributeForParentNode;

TypeLink specifier;
if (productionNum == SyntaxProductionInit.TypeNT_VarDecl_TO_ParamDeclaration) {
specifier = (TypeLink) (valueStack.get(valueStack.size() - 2));
} else {
specifier = (TypeLink) (valueStack.get(valueStack.size() - 3));
}

typeSystem.addSpecifierToDeclaration(specifier, symbol);
typeSystem.addSymbolsToTable(symbol, symbolScope);

handleStructVariable(symbol);
break;

case SyntaxProductionInit.VarDecl_Equal_Initializer_TO_Decl:
//Here you need to set the Symbol object for the response, otherwise there will be an error above Symbol symbol = (Symbol)attributeForParentNode;
attributeForParentNode = (Symbol) valueStack.get(valueStack.size() - 2);
break;

case SyntaxProductionInit.NewName_LP_VarList_RP_TO_FunctDecl:
setFunctionSymbol(true);
Symbol argList = (Symbol) valueStack.get(valueStack.size() - 2);
((Symbol) attributeForParentNode).args = argList;
typeSystem.addSymbolsToTable((Symbol) attributeForParentNode, symbolScope);

symbolScope = ((Symbol) attributeForParentNode).getName();
Symbol sym = argList;
while (sym != null) {
sym.addScope(symbolScope);
sym = sym.getNextSymbol();
}
break;

case SyntaxProductionInit.NewName_LP_RP_TO_FunctDecl:
setFunctionSymbol(false);
typeSystem.addSymbolsToTable((Symbol) attributeForParentNode, symbolScope);
symbolScope = ((Symbol) attributeForParentNode).getName();

break;

case SyntaxProductionInit.OptSpecifiers_FunctDecl_CompoundStmt_TO_ExtDef:
symbol = (Symbol) valueStack.get(valueStack.size() - 2);
specifier = (TypeLink) (valueStack.get(valueStack.size() - 3));
typeSystem.addSpecifierToDeclaration(specifier, symbol);

symbolScope = GLOBAL_SCOPE;
break;

case SyntaxProductionInit.Name_To_Tag:
symbolScope = text;
attributeForParentNode = typeSystem.getStructFromTable(text);
if (attributeForParentNode == null) {
attributeForParentNode = new StructDefine(text, nestingLevel, null);
typeSystem.addStructToTable((StructDefine) attributeForParentNode);
}

break;

case SyntaxProductionInit.Struct_OptTag_LC_DefList_RC_TO_StructSpecifier:
Symbol defList = (Symbol) valueStack.get(valueStack.size() - 2);
StructDefine structObj = (StructDefine) valueStack.get(valueStack.size() - 4);
structObj.setFields(defList);
attributeForParentNode = structObj;
symbolScope = GLOBAL_SCOPE;
break;

case SyntaxProductionInit.Enum_TO_EnumNT:
enumVal = 0;
break;

case SyntaxProductionInit.NameNT_TO_Emurator:
doEnum();
break;
case SyntaxProductionInit.Name_Eequal_ConstExpr_TO_Enuerator:
enumVal = (Integer) (valueStack.get(valueStack.size() - 1));
attributeForParentNode = (Symbol) (valueStack.get(valueStack.size() - 3));
doEnum();
break;
case SyntaxProductionInit.Number_TO_ConstExpr:
case SyntaxProductionInit.Number_TO_Unary:
attributeForParentNode = Integer.valueOf(text);
break;
default:
break;
}

astBuilder.buildSyntaxTree(productionNum, text);
}

作用域

在上面说的构造符号表的过程还有一个点没有说,就是每个符号的作用域问题。

在LRStateTableParser有两个属性

  • nestingLevel
    用来表明当前符号的嵌套层次
  • symbolScope
    用来表示当前符号的作用域,如果是全局变量就设置为GLOBAL_SCOPE,为函数内部的即设置为对应的函数名

nestingLevel在shift操作遇见左右括号时,就会进行相应的加减

symbolScope则是在reduce过程,如果遇见一个函数定义或者完成一个完整的函数定义就会进行相应的设置

小结

这一篇就是利用reduce操作的时候,我们可以知道是根据哪个产生式做的reduce,就可以在其中插入符号表的构建。过程看着可能比较复杂,但其实就是:

根据reduce的产生式创建信息 -> 根据reduce的产生式来组合信息