上一篇:定义语法
下一篇:语义分析
语法树
是啥
在知道了文法的定义后,我们就要根据文法分析生成语法树了。
语法树可以表达是文法展开的过程,比如i+i*i
对应文法是
E ::= T | E + T | E - T
T ::= F | T * F | T / F
F ::= i
文法展开过程是:
E -> E + T -> T + T -> F + T -> i + T
-> i + T * F -> i + F * F -> i + i * i
根据文法构造的语法树就是:
E
/|\
F + T
| /|\
i F * F
| |
i i
就很清楚了,我们得到语法树后可以计算”中序”遍历的序列,并对每个节点加括号,那么就可以还原原算式和文法的结构。
(i+(i*i))
(-E-----)
F+(-T-)
i F*F
i i
如果是连等式,那么有
a=b=c=d=e=1
=
/ \
a =
/ \
b =
/ \
c =
/ \
d =
/ \
e 1
因为=是右结合的,所以语法树是右偏的。
可以说语法树展现了语句的结构,也展现文法的展开方式。
前置离散数学和数据结构。
语法树实现及节点类型表示参见
https://github.com/huanghongxun/Compiler/blob/master/compiler/syntax_tree.h
https://github.com/huanghongxun/Compiler/blob/master/compiler/syntax_descriptors.h
求值
我们有必要实现一个在语法树上直接运算的程序,这样我们可以在编译期完成常量的计算,计算数组定义时各维大小的表达式(如int a[2 * 3][(int) 3.0 / 2]),以及实现constexpr。
说简单其实可以说简单,我们可以这么写(伪代码)
object eval(AST ast)
{
if (ast->type is binary_operator) // 二元运算符
{
switch (ast->op)
{
case "+": // 两个子树先求值再加和
return eval(ast->children[0]) + eval(ast->children[1]);
case "-": // 两个子树先求值再做差
return eval(ast->children[0]) - eval(ast->children[1]);
... // * / << >> && & || | ^ % ... < > <= >=
}
}
else if (ast->type is unary_operator) // 一元运算符
{
switch (ast->op)
{
case "-":
return -eval(ast->children[0]);
... // + & * ! ~
}
}
else if (ast->type is cast) // 转型
{
switch (ast->cast_to)
{
is int:
return (int)eval(ast->children[0]);
...
}
else if (ast->type is constant)
{
return ast->value;
}
else
throw runtime_error("Unsupported syntax tree type");
}
如果你了解树的话,上面的代码应该很好懂。也就是说,我们先递归求解子树的值,然后根据当前的语法树节点进行计算。
具体实现参见 https://github.com/huanghongxun/Compiler/blob/master/compiler/syntax_tree_evaluation.cpp
不过实际上进行计算的话是需要类型的,如果我们在语义分析阶段完成类型分析后实现会更简单。具体计算数组维度的请参见语义分析代码
https://github.com/huanghongxun/Compiler/blob/master/compiler/semantic_analyzer.cpp
递归下降分析法
例子
递归下降分析法这个名称听起来挺高端的,但是实际上就是对于每条文法都实现一个子程序而已,在介绍实际做法之前,我们先考虑如下的例子:
我们要解析这样的字符串表示A(B(,C(,)),D(,)。
我们定义文法:
C ::= a .. z
R ::= e | C(R,R)
其中R表示定义一棵二叉树,叶节点表示为C(,)。
我们要怎么解析呢?根据递归下降分析法的要求,我们要对每条文法实现一个子程序,文法C无非就是isalpha,我们实现R的解析:
int i = 0;
tree *parse_R()
{
tree *node = new tree;
auto c = str[i]; // 拿到当前的字符
if (isalpha(c)) // 当前的字符是C,我们能匹配到R的第二条
{
i++; // 跳过字母
assert(str[i++] == '('); // 字母下一位必须是左括号
node->left = parse_R(); // 递归匹配R
assert(str[i++] == ','); // 必须使用逗号隔开
node->right = parse_R(); // 递归匹配R
assert(str[i++] == ')'); // 必须以右括号结束
return node;
}
else if (c == ',' || c == ')') // 当前字符不是字母,但可以匹配到R的第一条,因为空的下一位只能是逗号和右括号
{
return null;
}
else // R的两条都没有匹配到,当前字符不是字母,空的下一位却不是逗号和右括号
assert(false); // 不符合语法
}
这样我们就可以解析出树了。这个方法是最快的,我们只需要扫描一遍字符串即可。
实现
接下来我们讨论实际的写法,比如之前定义的
UNIT_3 ::= UNIT_2 | UNIT_3 + UNIT_2 | UNIT_3 - UNIT_2
如果我们直接递归调用UNIT_3实现UNIT_3,肯定是不行的,因为我们程序实现不好做到预判运算符,对于这种递归,我们手动展开UNIT_3:
UNIT_3 ::= UNIT_2 +/- UNIT_2 +/- ... +/- UNIT_2
那么很容易发现,无非就是从左往右匹配UNIT_2,然后我们就会遇到+和-,然后再遇到下一个+和-,也就是说因为从左往右解析,我们自然地完成了符号的左结合特性。我们可以这么实现:
syntax_tree *parse_unit3()
{
syntax_tree *ast = parse_unit2();
while (peek_token() == "+" || peek_token() == "-")
{
string t = next_token();
syntax_tree n_ast = new syntax_tree(descriptor_binary_operator("+"));
n_ast->children.push_back(ast);
n_ast->children.push_back(parse_unit2());
ast = n_ast;
}
return ast;
}
因为更高优先级的已经在parse_unit2处理过了,所以我们能保证更高优先级的一定在子树中,即优先执行更高优先级的运算符。
对于右结合的赋值系列符号,我们可以这么写:
syntax_tree *compiler::syntax_analyzer::parse_unit9()
{
syntax_tree *node = parse_unit8();
if (peek_token("=") || peek_token("+=") || peek_token("-=") || peek_token("/=") || peek_token("%=") || peek_token("*=") || peek_token("|=") || peek_token("&=") || peek_token("^=") || peek_token("<<=") || peek_token(">>="))
{
auto op = next_token();
AST ast = make_shared(descriptor_assign(op.code), op);
ast->children.push_back(node);
ast->children.push_back(parse_unit9());
return ast;
}
else
{
return node;
}
}
因为右结合,所以我们就可以直接按照文法递归实现。
注意
我们在解析语句的时候,会有几种情况,分别是定义变量、表达式、if等特殊功能语句。其中特殊功能语句开头的标识符都比较特别,区分起来很容易,我们不作讨论。我们讨论一下关于区分定义变量和表达式。考虑到两者的开头都允许字母数字下划线组成的标识符,我们必须区分标识符是变量名还是类型,我们需要在语法分析期记录类型表,我们在遇到static,const,long以及类型表中的标识符、或者其他定义的类型修饰符(比如接下来为了实现与解释器交互的内建函数,我们需要__built_in标识,VC的__cdecl和__stdcall等)时,匹配到定义变量,否则如果是变量名,当作表达式处理;如果都不能匹配,那么只能是未声明的类型或者未定义的变量。为了简化问题,更加精确的错误分析我们就不讨论了。
我们甚至可以合并函数和变量定义的文法。
具体实现参考
https://github.com/huanghongxun/Compiler/blob/master/compiler/syntax_analyzer.cpp
https://github.com/huanghongxun/Compiler/blob/master/compiler/parsers.cpp
上一篇:定义语法
下一篇:语义分析