语法分析是编译的第二个阶段,语法分析的基本任务就是根据指定的文法识别输入的句子当中的各类短语并构造它的分析树,其实也就是识别由词法分析给出的单词符号串是否是给定文法的正确句子(程序)
语法分析器根据语言的语法规则来解析代码,解析阶段决定了输入的代码是否能够根据既定的语法组成token流
语法分析器则会定义一些函数来把代码组织成一种被称为AST的数据结构。解析器可以采用递归下降的解析技术自顶向下解析,并且相互递归的函数构建AST
为了方便,我们去定义一个静态的全局变量Current_token表示当前token的静态全局变量
static int Current_token;
然后我们再去定义从词法分析器的输入流获得下一个token
//从词法分析器的输入流获得下一个token
static int next_token(){
Current_token = get_token();
return Current_token;
}
接下来我们去定义一个函数,会根据词法分析器确定的token类型调用特定的解析函数,然后去构造一个一个的AST对象,函数定义如下所示
static BaseAST * Base_Parser(){
switch (Current_token) {
default:
return 0;
case IDENTIFIER_TOKEN: return identifier_parser();
case NUMERIC_TOKEN: return numeric_parser();
case '(':return paran_passer();
}
}
输入流被词法分析器构建成token流并且传递给语法分析器,Current_token持有当前处理的token,在这一阶段token类型是已知的,并且根据其类型来调用相应的解析函数来初始化AST
接下来我们看下计算表达式的解析函数
//解析表达式的函数
static BaseAST * expression_parser(){
BaseAST * LHS = Base_Parser();
if(!LHS) return 0;
return binary_op_parser(0,LHS);
}
下面就是关于数值表达式,就是去传入数值然后去读取下一个字符,返回AST类对象
//解析表达式的函数
static BaseAST * expression_parser(){
BaseAST * LHS = Base_Parser();
if(!LHS) return 0;
return binary_op_parser(0,LHS);
}
然后下面就是定义函数来解析解析标识符表达式,标识符可以是变量引用或者是函数调用,它们之间会由括号这一个特征来进行区分
//定义函数来解析函数的声明
static FunctionDeclAST *func_decl_parser() {
if(Current_token != IDENTIFIER_TOKEN)
return 0;
std::string FnName = Identifier_string;
next_token();
if(Current_token != '(')
return 0;
int flag = 0;
std::vector<std::string> Function_Argument_Names;
if(next_token()==IDENTIFIER_TOKEN)
{
while(Current_token==IDENTIFIER_TOKEN||Current_token==',')
{
if(flag==1&&Current_token==',')
{
break;
}
if(Current_token==',')
{
flag=1;
}
else
{
flag=0;
}
Function_Argument_Names.push_back(Identifier_string);
next_token();
}
}
//读到函数声明末尾如果不是)那么就返回
if(Current_token != ')')
return 0;
next_token();
return new FunctionDeclAST(FnName, Function_Argument_Names);
}
下面我们再去定义解析函数定义的函数
static FunctionDefnAST *func_defn_parser() {
next_token();
FunctionDeclAST *Decl = func_decl_parser();
if(Decl == 0) return 0;
if(BaseAST* Body = expression_parser())
return new FunctionDefnAST(Decl, Body);
return 0;
}
关于括号的解析函数
static BaseAST* paran_parser() {
next_token();
BaseAST* V = expression_parser();
if (!V) return 0;
if(Current_token != ')')
return 0;
return V;
}
对于二元表达式的运算的话,我们需要去定义运算符的优先级和初始化优先级的函数方便我们使用,然后再去定义一个函数去返回已定义的二元运算符的优先级
//优先级的map数组
static std::map<char, int> Operator_Precedence;
//初始化运算符优先级
static void init_precedence() {
Operator_Precedence['-'] = 1;
Operator_Precedence['+'] = 2;
Operator_Precedence['/'] = 3;
Operator_Precedence['*'] = 4;
}
static BaseAST* binary_op_parser(int Old_Prec, BaseAST *LHS) {
while(1) {
int Operator_Prec = getBinOpPrecedence();
if(Operator_Prec < Old_Prec)
return LHS;
int BinOp = Current_token;
next_token();
BaseAST* RHS = Base_Parser();
if(!RHS) return 0;
int Next_Prec = getBinOpPrecedence();
if(Operator_Prec < Next_Prec) {
RHS = binary_op_parser(Operator_Prec+1, RHS);
if(RHS == 0) return 0;
}
LHS = new BinaryAST(std::to_string(BinOp), LHS, RHS);
}
}
上面做的其实就是如果遇到一个数值token,那么调用数值表达式的构造函数并由解析器返回数值的AST对象,用数值数据去填充数值AST
标识符也是类似的,就是解析的数据可以是变量或者函数调用,对于函数声明和定义来说,函数名和函数参数都会被解析,接着调用相应的AST类的构造函数