我的日常工作包括开发类似Pascal的编译器。我一直在优化和代码生成方面工作。
我还想开始学习为同一语言构建一个简单的解析器。然而,我不太确定如何着手。Flex和Bison似乎是首选。但是,使用C++或C#编写解析器是否可能?我对C有点生疏。
Yacc++支持C#,但它是有许可证的。我正在寻找在这方面能找到的所有帮助。建议将不胜感激。
我的日常工作包括开发类似Pascal的编译器。我一直在优化和代码生成方面工作。
我还想开始学习为同一语言构建一个简单的解析器。然而,我不太确定如何着手。Flex和Bison似乎是首选。但是,使用C++或C#编写解析器是否可能?我对C有点生疏。
Yacc++支持C#,但它是有许可证的。我正在寻找在这方面能找到的所有帮助。建议将不胜感激。
我相信你可以在C#中使用ANTLR。虽然我自己从未尝试过,但这里有一个教程here可能会指引你正确的方向。
个人而言,我会自己编写词法分析器和语法分析器 (LL)。以下是一个非常简略的示例。虽然它是用 C++ 编写的,但希望你能适应。它使用了一个名为 PARSE_HIGHER 的宏来方便地插入不同优先级的运算符而不需要大量修改代码。
// routine to scan over whitespace/comments
void ScanWhite(const char* &pc){
while(true){
if(0);
else if (WHITESPACE(*pc)) pc++;
else if (pc[0]=='/' && pc[1]=='/'){
while(*pc && *pc++ != '\n');
}
else break;
}
}
// routine to lex an identifier
bool SeeId(const char* &pc, string sId){
ScanWhite(pc);
const char* pc0 = pc;
if (alpha(*pc)){
sId = "";
while(alphanum(*pc)) sId += (*pc++);
return true;
}
pc = pc0;
return false;
}
// routine to lex a number
bool SeeNum(const char* &pc, double &dNum){
ScanWhite(pc);
const char* pc0 = pc;
if (digit(*pc)){
dNum = 0;
while(digit(*pc)) dNum = dNum * 10 + (*pc++ - '0');
if (*pc == '.'){
double divisor = 1, frac = 0;
while(digit(*pc)){
divisor *= 0.1;
frac += (*pc++ - '0') * divisor;
}
dNum += frac;
}
return true;
}
pc = pc0;
return false;
}
// routine to lex some constant word
bool SeeWord(const char* &pc, const char* sWord){
ScanWhite(pc);
const char* pc0 = pc;
int len = strlen(sWord);
if (strncmp(pc, sWord, len)==0 && !alphanum(pc[len])){
pc += len;
return true;
}
pc = pc0;
return false;
}
// routine to lex a single character like an operator
bool SeeChar(const char* &pc, const char c){
ScanWhite(pc);
const char* pc0 = pc;
if (*pc == c){
pc++;
return true;
}
pc = pc0;
return false;
}
// primitive expression parser
void ParsePrimitiveExpr(const char* &pc, CNode* &p){
double dNum;
char sId[100];
if (0);
else if (SeeNum(pc, dNum)){
p = new CNode(dNum);
}
else if (SeeId(pc, sId)){
// see if its a function call
if (SeeChar(pc, '(')){
p = MakeNewFunctionCallNode(sId);
while(!SeeChar(pc, ')')){
CNode* p1 = null;
ParseExpression(pc, p1);
AddArgumentExprToFunctionCallNode(p, p1);
SeeChar(pc, ','); /* optional comma separator */
}
}
// otherwise its just a variable reference
else {
p = new CNode(sId);
}
}
// handle embedded expressions
else if (SeeChar(pc, '(')){
ParseExpression(pc, p);
if (!SeeChar(pc, ')')) /* deal with syntax error */
}
}
#define PARSE_HIGHER ParsePrimitiveExpr
// product parser
void ParseProduct(const char* &pc, CNode* &p){
PARSE_HIGHER(pc, p);
while(true){
if (0);
else if (SeeChar(pc, '*')){
CNode p1 = null;
PARSE_HIGHER(pc, p1);
p = new CNode('*', p, p1);
}
else if (SeeChar(pc, '/')){
CNode p1 = null;
PARSE_HIGHER(pc, p1);
p = new CNode('/', p, p1);
}
else break;
}
}
#undef PARSE_HIGHER
#define PARSE_HIGHER ParseProduct
// sum parser
void ParseSum(const char* &pc, CNode* &p){
PARSE_HIGHER(pc, p);
while(true){
if (0);
else if (SeeChar(pc, '+')){
CNode p1 = null;
PARSE_HIGHER(pc, p1);
p = new CNode('+', p, p1);
}
else if (SeeChar(pc, '-')){
CNode p1 = null;
PARSE_HIGHER(pc, p1);
p = new CNode('-', p, p1);
}
else break;
}
}
#undef PARSE_HIGHER
// can insert more routines like the above
// to handle move operators
#define PARSE_HIGHER ParseSum
// overall expression parser
void ParseExpression(const char* &pc, CNode* &p){
PARSE_HIGHER(pc, p);
}
添加了一些类似Pascal语言的语句语法:
void ParseStatement(const char* &pc){
char sId[100];
if(0);
else if (SeeWord(pc, "begin")){
while(!SeeWord(pc, "end")){
ParseStatement(pc);
SeeChar(pc, ';');
}
}
else if (SeeWord(pc, "while")){
CNode* p1 = null;
ParseExpression(pc, p1);
ParseStatement(pc);
/* semantics for while statement */
}
else if (SeeWord(pc, "if")){
CNode* p1 = null;
ParseExpression(pc, p1);
ParseStatement(pc);
if (SeeWord(pc, "else")){
ParseStatement(pc);
}
/* semantics for if statement */
}
else if (SeeWord(pc, "for")){
/* you do it */
}
// handle assignments and subroutine calls
else if (SeeId(pc, sId)){
if(0);
else if (SeeChar(pc, '=')){
CNode* p1 = null;
ParseExpression(pc, p1);
/* semantics for assignment statement */
}
else if (SeeChar(pc, '(')){
CNode* p = MakeNewFunctionCallNode(sId);
while(!SeeChar(pc, ')')){
CNode* p1 = null;
ParseExpression(pc, p1);
AddArgumentExprToFunctionCallNode(p, p1);
SeeChar(pc, ','); /* optional comma separator */
}
}
else {
/* we have a 1-word statement, which is OK in pascal */
}
}
else {
/* syntax error */
}
}
它仍需要数组索引、变量声明和函数定义的语法,但我希望清楚地说明如何做到这一点。
bison和flex是经典的解析器生成器。如果你对C++感兴趣,我发现boost spirit很有用。虽然我从未将其用于像编译器这样复杂的东西。我相信其他人会对其他语言(如C#)提出有趣的建议...
手写自己的词法分析器和语法分析器其实挺有趣的。词法分析器是极少数情况下可以正当使用goto和预处理器的场景之一。然而,如果可以避免的话,我不建议在像Pascal这样的完整语言中使用它。那将需要大量工作,我说的是人年。
我用flex和bison编写了一个XSLT解析器。最近我正在使用ANTLR做一个项目:
JFig语言语法是否高效清晰(比Spring-Framework的XML DSL更好)?
我喜欢在ANTLR中工作,比在Flex和Bison中工作更加愉快。在某些方面,ANTLR将您提升到更高的抽象级别。词法定义和解析器语法都可以放在一个文件中。(ANTLR将生成令牌文件。)
关键之一是能够定义树语法。基本上,您对输入语言进行语法分析,并具有将操作重写为高度优化的AST树输出的操作(这些操作保留为内存中的链接数据结构)。然后,您可以将此树传递给在单独的树解析器文件中定义的另一个解析器。树解析器是您执行所需操作项的真正工作的地方。
这是一个不错的方法,因为您可以保持AST形式,并根据需要重复处理它 - 基于后续操作从中剥离特定子树节点进行处理等。将其视为语言解释器。与其进入for循环并一遍又一遍地从头开始处理语言,可以通过其AST表示仅处理它。
在我的情况下,我设计了一个bean工厂来执行IoC依赖注入。我的bean工厂在运行时保留了bean描述符的AST。当它需要创建(或检索)新的bean实例时,我只需将bean描述符AST子树传递给我的树解析器 - 结果就是所需的bean实例(确定如何实例化请求的bean涉及许多因素,包括制作任何其他引用的bean和/或通过元属性应用其他特殊行为)。
最后,我的当前bean工厂面向Java,但我想面向ActionScript3和C# .NET。ANTLR支持这样做。
正如提到的那样,Terrence Parr编写了一本有关如何使用ANTLR的书。它旨在面向需要在ANTLR上实现实际操作的工作程序员(而不是学术性的主题阐述)。