Chapter #1: Kaleidoscope language and Lexer & Chapter #2: Implementing a Parser and AST
主要学习内容
这个新手演示教程主要内容,围绕llvm官网给出的教程进行一个系统地介绍和动手学习,应该主要以学习前端为主:
同时,Toby Ho有一个个人主页,全是他提供的教学课程:https://tobyho.com/
,很丰富。
Chapter #1: Kaleidoscope language and Lexer
本教程以一个名为“Kaleidoscope”的玩具语言作为示例(源自“美丽、形式和视图”的含义)。Kaleidoscope是一种过程式语言,允许您定义函数、使用条件语句、进行数学运算等。在教程的过程中,我们将扩展Kaleidoscope以支持if/then/else结构、for循环、用户自定义操作符、带有简单命令行界面的JIT编译、调试信息等功能。
教程希望保持简单,因此在Kaleidoscope中唯一的数据类型是64位浮点类型(在C中称为‘double’)。因此,所有值都隐式地为双精度,并且该语言不需要类型声明。这使得该语言具有非常简洁明了的语法。例如,以下简单示例计算斐波那契数列:
# Compute the x'th fibonacci number.
def fib(x)
if x < 3 then
1
else
fib(x-1)+fib(x-2)
# This expression will compute the 40th number.
fib(40)
1.1 The Lexer
#include <iostream>
#include <string>
using namespace std;
enum Token{
tok_eof=-1,
tok_def=-2,
tok_extern=-3,
tok_identifier=-4,
tok_number=-5
};
// 在编程语言的词法分析器(lexer)中,标识符通常是指用来标识变量、函数、类、模块等命名实体的字符序列。
static string IdentifierStr=""; // filled in if tok_identifier
static double NumVal; // filled in if tok_number
// gettok
static int gettok(){
static int LastChar = ' ';
// skip space
while(isspace(LastChar)){
LastChar = getchar();
}
// get identifier
if (isalpha(LastChar)){ // identifier: [a-zA-z][a-zA-Z0-9]*
IdentifierStr = LastChar;
while (isalnum(LastChar=getchar()))
IdentifierStr += LastChar;
if (IdentifierStr == "def")
return tok_def;
if (IdentifierStr == "tok_extern")
return tok_extern;
return tok_identifier;
}
// get number
if (isdigit(LastChar) || LastChar == '.'){ // Number: [0-9.]+
string NumStr;
do{
NumStr += LastChar;
LastChar = getchar();
}while (isdigit(LastChar) || LastChar == '.');
// 这行代码的功能是将一个字符串转换为双精度浮点数
// .c_str() 方法将 NumStr 转换为以空字符结尾的 C 风格字符串(即以 const char* 的形式返回字符串的指针)
// strtod() 是一个 C 标准库函数,用于将字符串转换为双精度浮点数。两个参数:第一个参数是要转换的字符串的指针,第二个参数是一个指向字符指针的指针,用于存储第一个无法转换的字符的地址。在这里第二个参数为 0,表示不需要获取无法转换的字符的地址。
NumVal = strtod(NumStr.c_str(), 0);
return tok_number;
}
// fill comments
if(LastChar == '#'){
// comments until end of line
do
LastChar = getchar();
while(LastChar !=EOF && LastChar != '\n' && LastChar != '\r');
if (LastChar != EOF)
return gettok();
}
if (LastChar == EOF)
return tok_eof;
// otherwise, just return the character as its ascii value
int ThisChar = LastChar;
LastChar = getchar();
return ThisChar;
}
1.2 test
int main(){
while(true){
int tok = gettok();
cout << "got token" << tok <<endl;
}
}
Chapter #2: Implementing a Parser and AST
本章将向您展示如何使用第1章中构建的词法分析器来构建我们Kaleidoscope语言的完整解析器。一旦我们有了解析器,我们将定义并构建一个抽象语法树(AST)。
我们将构建的解析器使用了递归下降解析(Recursive Descent Parsing)和操作符优先级解析(Operator-Precedence Parsing)相结合的方法来解析Kaleidoscope语言(对于二元表达式采用后者,对于其他内容采用前者)。但在开始解析之前,让我们先来谈谈解析器的输出:抽象语法树。
2.1 the Abstract Syntax Tree (AST)
程序的抽象语法树(AST)以一种易于编译器的后续阶段(例如代码生成)解释的方式捕获了其行为。我们基本上希望语言中的每个结构都有一个对象,并且抽象语法树应该与语言密切相关。在Kaleidoscope中,我们有表达式、原型和函数对象。
a = func(a+2)
// ExprAST- base class
class ExprAST{
public:
virtual ~ExprAST() = default;
};
// NumberExprAST - for numeric literals like "1.0"
class NumberExprAST: public ExprAST{
double Val;
public:
NumberExprAST(double Val) : Val(Val){}
};
class VariableExprAST: public ExprAST{
string Name;
public:
VariableExprAST(const string &Name): Name(Name){}
};
class BinaryExprAST: public ExprAST{ // 2+3
char Op; // + - * \ < >
unique_ptr<ExprAST> LHS, RHS;
// smart pointer: c++ doesn't have garbage collector
public:
BinaryExprAST(char Op, unique_ptr<ExprAST> LHS, unique_ptr<ExprAST> RHS):
Op(Op), LHS(move(LHS)), RHS(move(RHS)){}
};
class CallExprAST: public ExprAST{
string Callee;
vector<unique_ptr<ExprAST>> Args;
public:
CallExprAST(const string &Callee, vector<unique_ptr<ExprAST>> Args):
Callee(Callee), Args(move(Args)){}
};
class PrototypeAST{ // func(string xxx,)
string Name;
vector<string> Args;
public:
PrototypeAST(const string&Name, vector<string> Args):
Name(Name), Args(move(Args)){}
//getName() 后面加上 const 关键字的原因是为了表明这个成员函数是一个常量成员函数。在C++中,常量成员函数是指那些在函数体内不会修改类的成员变量的成员函数
const string &getName() const{
return Name;
}
};
class FunctionAST{ // def func(string xxx,){}
unique_ptr<PrototypeAST> Proto;
unique_ptr<ExprAST> Body;
public:
FunctionAST(unique_ptr<PrototypeAST> Proto, unique_ptr<ExprAST> Body):
Proto(move(Proto)), Body(move(Body)){}
};
2.2 c++智能指针
C++智能指针是一种用于管理动态分配的内存资源的工具。传统的指针(例如使用 new
关键字分配的指针)需要手动释放内存,这可能会导致内存泄漏或者悬挂指针(dangling pointers)等问题。智能指针的出现解决了这些问题,它们能够在适当的时候自动释放内存,从而减少了手动管理内存的复杂性。
在C++中,有三种主要的智能指针:std::unique_ptr
、std::shared_ptr
和 std::weak_ptr
。
-
std::unique_ptr:
std::unique_ptr
是独占所有权的智能指针。只能有一个std::unique_ptr
指向同一个对象。当std::unique_ptr
被销毁时,它所指向的对象也会被销毁。通常用于在函数间传递动态分配的资源所有权。
-
std::shared_ptr:
std::shared_ptr
允许多个指针共享同一个对象。它使用引用计数来跟踪有多少个std::shared_ptr
指向同一个对象。只有当所有std::shared_ptr
都释放时,对象才会被销毁。
-
std::weak_ptr:
std::weak_ptr
是为了解决std::shared_ptr
的循环引用问题而引入的。它允许对对象进行弱引用,但不会增加引用计数。因此,使用std::weak_ptr
不会阻止对象被销毁。
使用智能指针可以提高代码的安全性和可维护性,并减少内存管理错误的发生。然而,需要注意的是,智能指针并不能完全替代手动管理内存,有些情况下仍然需要手动管理内存资源。
2.3 Basic Expression Parsing
// Basic Expression Parsing
// LogError
unique_ptr<ExprAST> LogError(const char *Str){
fprintf(stderr, "Error: %s\n", Str);
return nullptr;
}
unique_ptr<PrototypeAST> LogErrorP(const char *Str){
LogError(Str);
return nullptr;
}
// CurTok/getNextToken - 提供一个简单的token缓冲区。CurTok是解析器当前正在查看的令牌。
// getNextToken从词法分析器中读取另一个令牌,并使用其结果更新CurTok。
static int CurTok;
static int getNextToken(){
return CurTok = gettok();
}
static unique_ptr<ExprAST> ParseExpression();
// 这个例子吃掉了与产生式相对应的所有令牌,并返回词法分析器缓冲区中的下一个令牌
// 这是递归下降解析器的一种相当标准的做法。
// numberexpr ::=number
static unique_ptr<ExprAST> ParseNumberExpr(){
auto Result = make_unique<NumberExprAST>(NumVal);
getNextToken(); // consume the number
return move(Result);
}
// 括号运算符的定义如下:
// parenexpr ::= '(' expression')'
static unique_ptr<ExprAST> ParseParenExpr(){
getNextToken(); // eat (
auto V = ParseExpression();
if (!V)
return nullptr;
if (CurTok != ')')
return LogError("expected ')");
getNextToken(); // eat ')'
return V;
}
// 用于处理变量引用和函数调用
// IdentifierExpr
// :: identifier
// :: identifier '(' expression* ')'
static unique_ptr<ExprAST> ParseIdentifierExpr() {
string IdName = IdentifierStr;
getNextToken(); // eat identifier
if (CurTok == '('){ // call
getNextToken(); // eat (
vector<unique_ptr<ExprAST>> Args;
while(true){
auto Arg = ParseExpression();
if (Arg)
Args.push_back(move(Arg));
else
return nullptr;
if (CurTok == ')'){
getNextToken(); // eat )
break;
}else if (CurTok == ','){
getNextToken(); // eat ,
continue;
}else{
return LogError("Expected ')' or ',' in argument list");
}
}
return make_unique<CallExprAST>(IdName, move(Args));
}else{ // simple variable ref
return make_unique<VariableExprAST>(IdName);
}
}
// 已经把所有简单表达式解析逻辑放到了一起,可以定义一个辅助函数将它们封装到一个入口点中
// primary
static unique_ptr<ExprAST> ParsePrimary(){
switch(CurTok){
default:
return LogError("unknown token when expecting an expression");
case tok_identifier:
return ParseIdentifierExpr();
case tok_number:
return ParseNumberExpr();
case '(':
return ParseParenExpr();
}
}
关于其中,
static int CurTok;
static int getNextToken(){
return CurTok = gettok();
}
static unique_ptr<ExprAST> ParseParenExpr(){
getNextToken(); // eat (
// ...
if (CurTok != ')')
return LogError("expected ')");
getNextToken(); // eat ')'
return V;
}
虽然CurTok是整数类型的,但它仍然可以与字符类型的’)'进行比较,因为字符类型在C++中可以隐式地转换为整数类型(ASCII码值)。在C++中,字符常量被视为整数,其值等于对应字符的ASCII码值。因此,可以直接将字符常量与整数进行比较。
2.4 多态
class ExprAST {
public:
virtual ~ExprAST() = default;
};
class CallExprAST : public ExprAST {
std::string Callee;
std::vector<std::unique_ptr<ExprAST>> Args;
public:
CallExprAST(const std::string &Callee,
std::vector<std::unique_ptr<ExprAST>> Args)
: Callee(Callee), Args(std::move(Args)) {}
};
class VariableExprAST : public ExprAST {
std::string Name;
public:
VariableExprAST(const std::string &Name) : Name(Name) {}
};
static std::unique_ptr<ExprAST> ParseIdentifierExpr() {
std::string IdName = IdentifierStr;
getNextToken(); // eat identifier.
if (CurTok != '(') // Simple variable ref.
return std::make_unique<VariableExprAST>(IdName);
// Call.
getNextToken(); // eat (
// ...
// Eat the ')'.
getNextToken();
return std::make_unique<CallExprAST>(IdName, std::move(Args));
}
在这段代码中,函数ParseIdentifierExpr()的返回类型是std::unique_ptr,而根据不同的条件分支,它返回了不同类型的对象,包括VariableExprAST和CallExprAST。这里的多态性体现在返回类型的多态性上。
由于VariableExprAST和CallExprAST都是ExprAST的派生类,所以它们可以通过指向基类的指针进行访问。当函数ParseIdentifierExpr()返回一个std::unique_ptr时,实际上返回的是一个指向基类的指针,但可以指向派生类的对象。这就是多态性的一种体现,即通过基类指针访问派生类对象。
2.4 Binary Expression Parsing
// Binary Expression Parsing
// 2+2 I +( 2*3 )
static int GetTokPrecedence(){
switch(CurTok){
case '<':
case '>':
return 10;
case '+':
case '-':
return 20;
case '*':
case '/':
return 40;
default:
return -1;
}
}
// binoprhs
// :: = ( + primary ) *
static unique_ptr<ExprAST> ParseBinOpRHS(
int ExprPrec, unique_ptr<ExprAST> LHS){
while(true){
int TokPrec = GetTokPrecedence(); // binop prec
if (TokPrec < ExprPrec){
return LHS;
}else {
int BinOp = CurTok;
getNextToken(); // eat binop
auto RHS = ParsePrimary();
if (RHS){
int NextPrec = GetTokPrecedence();
if(TokPrec < NextPrec){ // + VS *
// ExprPrec is (TokPrec + RHS_Prec)
RHS = ParseBinOpRHS(TokPrec+1, move(RHS));
if (!RHS)
return nullptr;
}
LHS = make_unique<BinaryExprAST>(BinOp, move(LHS), move(RHS));
} else {
return nullptr;
}
}
}
}
// expression
static unique_ptr<ExprAST> ParseExpression(){
auto LHS = ParsePrimary();
if (LHS)
return ParseBinOpRHS(0, move(LHS));
else
return nullptr;
}
2.5 以1+2*3为例测试
假设我们有一个表达式1 + 2 * 3,现在让我们来看看如何使用ParseBinOpRHS函数解析它。
首先,假设我们当前的ExprPrec为0,因为在初始调用时没有指定优先级。我们调用ParseBinOpRHS,并将LHS设置为一个表示数字1的ExprAST对象。
在循环中,我们首先调用GetTokPrecedence()函数获取当前标记的优先级。对于标记’+',GetTokPrecedence()可能返回一个较低的优先级值(例如20),然后与当前的ExprPrec进行比较。由于20 < 0(ExprPrec),因此我们继续循环。
我们获取了操作符’+‘的优先级,并将其与当前的ExprPrec进行比较。因为’+‘的优先级比当前的操作符更高(’+‘的优先级为20,而当前的操作符优先级为0),所以我们继续循环,这意味着我们应该解析’+'右侧的表达式。
我们继续循环,获取了’+‘后面的数字2。然后,我们再次调用GetTokPrecedence()来获取下一个操作符的优先级。在这种情况下,我们获取了’*‘的优先级,它比’+‘更高。现在,我们希望解析’'右侧的表达式。
所以我们递归调用ParseBinOpRHS,并将ExprPrec设置为’‘的优先级加1,也就是21。我们获取了数字3,并结束了递归。在返回到上一层时,我们将右侧的’‘表达式合并到’+'表达式中,然后继续循环。
由于在当前循环中没有更多的二元操作符,我们将返回最终的表达式树,其中包含了所有的操作符和操作数。
2.6 parse the rest
// parse the rest
//prototype
// foobar(n,m),疑问,逗号呢?
static unique_ptr<PrototypeAST> ParsePrototype(){
if (CurTok != tok_identifier)
return LogErrorP("Expected function name in prototype");
string FnName = IdentifierStr;
getNextToken();
if (CurTok != '(')
return LogErrorP("Expected '(' in prototype");
// read args
vector<string> ArgNames;
while (getNextToken() == tok_identifier)
ArgNames.push_back(IdentifierStr);
if (CurTok != ')')
return LogErrorP("Expected ')' in prototype");
getNextToken();
return make_unique<PrototypeAST>(FnName, move(ArgNames));
}
// parse definition, body
static unique_ptr<FunctionAST> ParseDefinition(){
getNextToken();
auto Proto = ParsePrototype();
if (!Proto)
return nullptr;
if (auto E = ParseExpression())
return make_unique<FunctionAST>(move(Proto), move(E));
return nullptr;
}
// extern prototype
static unique_ptr<PrototypeAST> ParseExtern(){
getNextToken(); // eat extern
return ParsePrototype();
}
//topevelexpr expression
static unique_ptr<FunctionAST> ParseTopLevelExpr(){
if (auto E = ParseExpression()){
// make an anonymous proto
auto Proto = make_unique<PrototypeAST>("", vector<string>());
return make_unique<FunctionAST>(move(Proto), move(E));
}
return nullptr;
}
static void HandleDefinition(){
if(ParseDefinition())
fprintf(stderr, "Parsed a function definition. \n");
else //skip token for error recovery
getNextToken();
}
static void HandleExtern(){
if (ParseExtern())
fprintf(stderr, "Parsed an extern\n");
else //skip token for error recovery
getNextToken();
}
static void HandleTopLevelExpression(){
if (ParseTopLevelExpr())
fprintf(stderr, "Parsed a top-level expr\n");
else
getNextToken();
}
2.7 Full Code:
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <string>
#include <memory>
#include <vector>
using namespace std;
enum Token{
tok_eof=-1,
tok_def=-2,
tok_extern=-3,
tok_identifier=-4,
tok_number=-5
};
// 在编程语言的词法分析器(lexer)中,标识符通常是指用来标识变量、函数、类、模块等命名实体的字符序列。
static string IdentifierStr=""; // filled in if tok_identifier
static double NumVal; // filled in if tok_number
// gettok
static int gettok(){
static int LastChar = ' ';
// skip space
while(isspace(LastChar)){
LastChar = getchar();
}
// get identifier
if (isalpha(LastChar)){ // identifier: [a-zA-z][a-zA-Z0-9]*
IdentifierStr = LastChar;
while (isalnum(LastChar=getchar()))
IdentifierStr += LastChar;
if (IdentifierStr == "def")
return tok_def;
if (IdentifierStr == "tok_extern")
return tok_extern;
return tok_identifier;
}
// get number
if (isdigit(LastChar) || LastChar == '.'){ // Number: [0-9.]+
string NumStr;
do{
NumStr += LastChar;
LastChar = getchar();
}while (isdigit(LastChar) || LastChar == '.');
// 这行代码的功能是将一个字符串转换为双精度浮点数
// .c_str() 方法将 NumStr 转换为以空字符结尾的 C 风格字符串(即以 const char* 的形式返回字符串的指针)
// strtod() 是一个 C 标准库函数,用于将字符串转换为双精度浮点数。两个参数:第一个参数是要转换的字符串的指针,第二个参数是一个指向字符指针的指针,用于存储第一个无法转换的字符的地址。在这里第二个参数为 0,表示不需要获取无法转换的字符的地址。
NumVal = strtod(NumStr.c_str(), 0);
return tok_number;
}
// fill comments
if(LastChar == '#'){
// comments until end of line
do
LastChar = getchar();
while(LastChar !=EOF && LastChar != '\n' && LastChar != '\r');
if (LastChar != EOF)
return gettok();
}
if (LastChar == EOF)
return tok_eof;
// otherwise, just return the character as its ascii value
int ThisChar = LastChar;
LastChar = getchar();
return ThisChar;
}
// 2.2. The Abstract Syntax Tree (AST)
// ExprAST- base class
class ExprAST{
public:
virtual ~ExprAST() = default;
};
// NumberExprAST - for numeric literals like "1.0"
class NumberExprAST: public ExprAST{
double Val;
public:
NumberExprAST(double Val) : Val(Val){}
};
class VariableExprAST: public ExprAST{
string Name;
public:
VariableExprAST(const string &Name): Name(Name){}
};
class BinaryExprAST: public ExprAST{ // 2+3
char Op; // + - * \ < >
unique_ptr<ExprAST> LHS, RHS;
// smart pointer: c++ doesn't have garbage collector
public:
BinaryExprAST(char Op, unique_ptr<ExprAST> LHS, unique_ptr<ExprAST> RHS):
Op(Op), LHS(move(LHS)), RHS(move(RHS)){}
};
class CallExprAST: public ExprAST{
string Callee;
vector<unique_ptr<ExprAST>> Args;
public:
CallExprAST(const string &Callee, vector<unique_ptr<ExprAST>> Args):
Callee(Callee), Args(move(Args)){}
};
class PrototypeAST{ // func(string xxx,)
string Name;
vector<string> Args;
public:
PrototypeAST(const string&Name, vector<string> Args):
Name(Name), Args(move(Args)){}
//getName() 后面加上 const 关键字的原因是为了表明这个成员函数是一个常量成员函数。在C++中,常量成员函数是指那些在函数体内不会修改类的成员变量的成员函数
const string &getName() const{
return Name;
}
};
class FunctionAST{ // def func(string xxx,){}
unique_ptr<PrototypeAST> Proto;
unique_ptr<ExprAST> Body;
public:
FunctionAST(unique_ptr<PrototypeAST> Proto, unique_ptr<ExprAST> Body):
Proto(move(Proto)), Body(move(Body)){}
};
// Basic Expression Parsing
// LogError
unique_ptr<ExprAST> LogError(const char *Str){
fprintf(stderr, "Error: %s\n", Str);
return nullptr;
}
unique_ptr<PrototypeAST> LogErrorP(const char *Str){
LogError(Str);
return nullptr;
}
// CurTok/getNextToken - 提供一个简单的token缓冲区。CurTok是解析器当前正在查看的令牌。
// getNextToken从词法分析器中读取另一个令牌,并使用其结果更新CurTok。
static int CurTok;
static int getNextToken(){
return CurTok = gettok();
}
static unique_ptr<ExprAST> ParseExpression();
// 这个例子吃掉了与产生式相对应的所有令牌,并返回词法分析器缓冲区中的下一个令牌
// 这是递归下降解析器的一种相当标准的做法。
// numberexpr ::=number
static unique_ptr<ExprAST> ParseNumberExpr(){
auto Result = make_unique<NumberExprAST>(NumVal);
getNextToken(); // consume the number
return move(Result);
}
// 括号运算符的定义如下:
// parenexpr ::= '(' expression')'
static unique_ptr<ExprAST> ParseParenExpr(){
getNextToken(); // eat (
auto V = ParseExpression();
if (!V)
return nullptr;
if (CurTok != ')')
return LogError("expected ')");
getNextToken(); // eat ')'
return V;
}
// 用于处理变量引用和函数调用
// IdentifierExpr
// :: identifier
// :: identifier '(' expression* ')'
static unique_ptr<ExprAST> ParseIdentifierExpr() {
string IdName = IdentifierStr;
getNextToken(); // eat identifier
if (CurTok == '('){ // call
getNextToken(); // eat (
vector<unique_ptr<ExprAST>> Args;
while(true){
auto Arg = ParseExpression();
if (Arg)
Args.push_back(move(Arg));
else
return nullptr;
if (CurTok == ')'){
getNextToken(); // eat )
break;
}else if (CurTok == ','){
getNextToken(); // eat ,
continue;
}else{
return LogError("Expected ')' or ',' in argument list");
}
}
return make_unique<CallExprAST>(IdName, move(Args));
}else{ // simple variable ref
return make_unique<VariableExprAST>(IdName);
}
}
// 已经把所有简单表达式解析逻辑放到了一起,可以定义一个辅助函数将它们封装到一个入口点中
// primary
static unique_ptr<ExprAST> ParsePrimary(){
switch(CurTok){
default:
return LogError("unknown token when expecting an expression");
case tok_identifier:
return ParseIdentifierExpr();
case tok_number:
return ParseNumberExpr();
case '(':
return ParseParenExpr();
}
}
// Binary Expression Parsing
// 2+2 I +( 2*3 )
static int GetTokPrecedence(){
switch(CurTok){
case '<':
case '>':
return 10;
case '+':
case '-':
return 20;
case '*':
case '/':
return 40;
default:
return -1;
}
}
// binoprhs
// :: = ( + primary ) *
static unique_ptr<ExprAST> ParseBinOpRHS(
int ExprPrec, unique_ptr<ExprAST> LHS){
while(true){
int TokPrec = GetTokPrecedence(); // binop prec
if (TokPrec < ExprPrec){
return LHS;
}else {
int BinOp = CurTok;
getNextToken(); // eat binop
auto RHS = ParsePrimary();
if (RHS){
int NextPrec = GetTokPrecedence();
if(TokPrec < NextPrec){ // + VS *
// ExprPrec is (TokPrec + RHS_Prec)
RHS = ParseBinOpRHS(TokPrec+1, move(RHS));
if (!RHS)
return nullptr;
}
LHS = make_unique<BinaryExprAST>(BinOp, move(LHS), move(RHS));
} else {
return nullptr;
}
}
}
}
// expression
static unique_ptr<ExprAST> ParseExpression(){
auto LHS = ParsePrimary();
if (LHS)
return ParseBinOpRHS(0, move(LHS));
else
return nullptr;
}
// parse the rest
//prototype
// foobar(n,m),疑问,逗号呢?
static unique_ptr<PrototypeAST> ParsePrototype(){
if (CurTok != tok_identifier)
return LogErrorP("Expected function name in prototype");
string FnName = IdentifierStr;
getNextToken();
if (CurTok != '(')
return LogErrorP("Expected '(' in prototype");
// read args
vector<string> ArgNames;
while (getNextToken() == tok_identifier)
ArgNames.push_back(IdentifierStr);
if (CurTok != ')')
return LogErrorP("Expected ')' in prototype");
getNextToken();
return make_unique<PrototypeAST>(FnName, move(ArgNames));
}
// parse definition, body
static unique_ptr<FunctionAST> ParseDefinition(){
getNextToken();
auto Proto = ParsePrototype();
if (!Proto)
return nullptr;
if (auto E = ParseExpression())
return make_unique<FunctionAST>(move(Proto), move(E));
return nullptr;
}
// extern prototype
static unique_ptr<PrototypeAST> ParseExtern(){
getNextToken(); // eat extern
return ParsePrototype();
}
//topevelexpr expression
static unique_ptr<FunctionAST> ParseTopLevelExpr(){
if (auto E = ParseExpression()){
// make an anonymous proto
auto Proto = make_unique<PrototypeAST>("", vector<string>());
return make_unique<FunctionAST>(move(Proto), move(E));
}
return nullptr;
}
static void HandleDefinition(){
if(ParseDefinition())
fprintf(stderr, "Parsed a function definition. \n");
else //skip token for error recovery
getNextToken();
}
static void HandleExtern(){
if (ParseExtern())
fprintf(stderr, "Parsed an extern\n");
else //skip token for error recovery
getNextToken();
}
static void HandleTopLevelExpression(){
if (ParseTopLevelExpr())
fprintf(stderr, "Parsed a top-level expr\n");
else
getNextToken();
}
// the driver
static void MainLoop(){
while(true){
fprintf(stderr, "read> ");
switch(CurTok){
case tok_eof:
return;
case ';': // ignore top-level semicolons
getNextToken();
case tok_def:
HandleDefinition();
break;
case tok_extern:
HandleExtern();
break;
default:
HandleTopLevelExpression();
break;
}
}
}
// int main(){
// while(true){
// int tok = gettok();
// cout << "got token" << tok <<endl;
// }
// }
int main(){
fprintf(stderr, "ready> ");
getNextToken();
MainLoop();
return 0;
}
编译与测试
两种编译方式都可以:
g++ my-lang.cpp -o my-lang.bin
clang++ -g -O3 my-lang.cpp `llvm-config --cxxflags`
test:
能正常解析一个函数定义(但是对参数列表处理还是有问题,没有处理逗号的代码)。