从Token流到Sentence这一步是怎么做的

如题所述

第1个回答  2017-09-29
词法分析,语法分析是不同的阶段,每个阶段只关注自己的事情,例如词法分析器不需要去考虑“句子”方面的问题,词法分析的目的就是将source code生成一个个的token,例如下面的代码:

main int )(; if while else
你用C++编译器的词法分析部分去扫面这段源码,是没有问题的,因为词法分析器的目的就是用来检查词法错误,生成token。使用多种匹配模式,也就是自动机来匹配token,根据前缀的不同来选择不同的匹配模式,采取最长匹配原则,有时也会向前看多个字符。如果源码中出现了 " 123if3" 这样的单元的话,会分解成123 if3,但是如果出现了@,肯定要报错了。

你说的识别“句子”的任务是由语法分析器来实现。当然词法分析器和语法分析器连接是很紧密的,一般情况是语法分析器调用词法分析器来获取下一个token来进行语法分析,通常不是先进行一遍词法分析生成一长串的token流,然后语法分析器接受这一长串token流,再进行语法分析。例如LL(1)语法分析的代码结构如下所示。

for(;;)
{
// 调用scan词法分析器获取下一个token,然后再调用相应的parse*()函数去解析后面的
// token属不属于相应的句法结构
switch(scan.getToken().getKind())
{
case tok::KEYWORD_if:
ParseIfStatement();
break;
case tok::KEYWORD_while():
ParseWhileStatement();
break;
// ...
default:
// 错误处理
errorReport();
}
}
例如token流如下:

var num = var;

递归下降的语法分析器调用词法分析器获取第一个token"var",判断后面是DeclStmt,然后调用ParseDeclStmt()去解析后面的token,ParseDeclStmt()的代码结构如下所示:
/// \brief 需要存储语法树节点并执行错误恢复处理的,简单起见,就没有写
Stmt* Parser::ParseDeclStmt()
{
switch()
{
case tok::KEYWORD_var:
return ParseVarDecl();
break;
case tok::KEYWORD_class:
return ParseClassDecl();
default:
// 错误处理
errorReport();
return nullptr;
}
}
这样按照你定义的文法格式调用的相应的函数,这个过程才会涉及到输入的token流是否构成句子的问题,像我们上面提到的“23if3”,生成了 23 if 3,但没有这样的句法,会报语法错误,所以说词法分析做的本职工作很有限,但是是基础中的基础。像“var num = var”这个句子,就是错误因为在本应该出现右值的地方出现了关键字。当然你可以使用自底向上的方式使用移入归约的方式进行语法分析。

语法分析完还会有语义分析,即使通过了语法分析,也不代表你的代码是正确的,例如下面的代码。

// start
#include <iostream>
add(1,2);

// source code

// end

上面代码所示是一个函数调用,但是该函数前面并没有定义,语法分析是没有问题,因为上面的句子是一个完整的CallExpr的结构,但是语义分析时会报错,因为add函数并没有定义,这里还会涉及到SymbolTable,Scope,Type Check等等一系列的问题,语义分析也可以嵌入在语法分析的过程中实现,使用一种语法制导翻译(syntax-directed translation),代码结构如下。

Stmt* Parser::ParseDeclStmt()
{
// 语法分析

// ...

// 定义action routine,检查当前是否为重定义,检查类型是否一致等等
action.ActOnDeclStmt();
return std::make_unique<DeclStmt>(/* DeclStmt构造函数的参数 */) ;
}
也可以构造好抽象语法树(AST)以后,再进行语义分析。本回答被提问者采纳
相似回答