对从程序源代码到AST的转换部分做了注释
源码:
toy.cpp
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Verifier.h"
#include <cctype>
#include <cstdio>
#include <map>
#include <string>
#include <vector>
//using namespace llvm;
enum Token_Type { EOF_TOKEN = 0, DEF_TOKEN, IDENTIFIER_TOKEN, NUMERIC_TOKEN };
FILE *file;// toy 语言源代码文件
static std::string Identifier_string;//用来存储 toy 语言程序中当前 get 到的标识符字符串
static int Numeric_Val;// 用来存储 toy 语言程序中的数值量
static int get_token()// 返回下一个当前的 token 类型,并改写全局变量 Identifier_str 或者 NumStr和Numeric_Val
{
static int LastChar = ' ';
while (isspace(LastChar))// 只要是空白键,就接着往下取,直到遇到非空白键字符才跳出循环
LastChar = fgetc(file);
if (isalpha(LastChar)) {// 如果是字母开头的字符串的话,有可能是普通标识符,也有可能是函数定义关键字 def
Identifier_string = LastChar;
while (isalnum((LastChar = fgetc(file))))//如果字符串接下来依然是字母或者数字,则继续读出并拼接在一起
Identifier_string += LastChar;
if (Identifier_string == "def")// 字符串可能是toy 语言关键字 def
return DEF_TOKEN;
return IDENTIFIER_TOKEN;//多数情况下是 普通标识符
}
if (isdigit(LastChar)) {//如果是数字开头的字符串的话,会是一个整数
std::string NumStr;
do {
NumStr += LastChar;
LastChar = fgetc(file);
} while (isdigit(LastChar));
Numeric_Val = strtod(NumStr.c_str(), 0);//将表示整数的字符串转换成整数
return NUMERIC_TOKEN;
}
if (LastChar == '#') {// 整数开头的部分是注释
do
LastChar = fgetc(file);
while (LastChar != EOF && LastChar != '\n' && LastChar != '\r');// 三种情况下注释结束,文件末尾、行尾、回车
if (LastChar != EOF)//如果一行注释结束了,但是下一行依然存在代码等,这是本函数还没有返回token type,则继续递归分析下一个字符串的token type 并返回这个token type
return get_token();
}
if (LastChar == EOF)// 如果遇到了文件结尾
return EOF_TOKEN;
int ThisChar = LastChar;//既不是EOF,也不是字符串或数字,那可能是运算符;程序跑到本函数时,本程序还没有检查程序的正确性,故潜在认为这可能是个运算符;后便会检查
LastChar = fgetc(file);
return ThisChar;
}
namespace {
class BaseAST {// AST 基类,本类的子类都是一种句法结构的AST类;表达式AST,控制语句AST,
public:
virtual ~BaseAST() {}
virtual llvm::Value *Codegen() = 0;//每种具体的AST 都需要生成代码,故定义一个 interface
};
class NumericAST : public BaseAST {// 表示数值的 AST 类
int numeric_val;
public:
NumericAST(int val) : numeric_val(val) {}// 构造时赋值
virtual llvm::Value *Codegen();
};
class VariableAST : public BaseAST {// 表示变量标识符的 AST
std::string Var_Name;
public:
VariableAST(const std::string &name) : Var_Name(name) {}
virtual llvm::Value *Codegen();
};
class BinaryAST : public BaseAST {// 表示 二值运算符表达式的 AST
std::string Bin_Operator;
BaseAST *LHS, *RHS;// 分别指向两个操作数的 AST 实例
public:
BinaryAST(std::string op, BaseAST *lhs, BaseAST *rhs)// 这意味着自底向上构造 AST,否则二值操作数的参数 AST 实例不存在
: Bin_Operator(op), LHS(lhs), RHS(rhs) {}
virtual llvm::Value *Codegen();
};
class FunctionCallAST : public BaseAST {// 调用函数的 AST,被调函数名和参数列表
std::string Function_Callee;
std::vector<BaseAST *> Function_Arguments;// 参数列表,每个参数都是一个 AST 实例
public:
FunctionCallAST(const std::string &callee, std::vector<BaseAST *> &args)
: Function_Callee(callee), Function_Arguments(args) {}
virtual llvm::Value *Codegen();
};
class FunctionDeclAST {// 函数声明 AST 类,因为仅仅做原型对比,故参数是字符串,toy中都是int型的,故只存名字串即可
std::string Func_Name;
std::vector<std::string> Arguments;
public:
FunctionDeclAST(const std::string &name, const std::vector<std::string> &args)
: Func_Name(name), Arguments(args){};
llvm::Function *Codegen();
};
class FunctionDefnAST {// 函数定义的 AST 类,由 函数声明部分和函数体构成
FunctionDeclAST *Func_Decl;
BaseAST *Body; // 没有定义函数体的 AST 类,故这里的Body 可以只是指向一个表达式的 AST
public:
FunctionDefnAST(FunctionDeclAST *proto, BaseAST *body)
: Func_Decl(proto), Body(body) {}
llvm::Function *Codegen();
};
} // namespace
static int Current_token;// 存储当前被分析的 token 的类型 enumerate 值;
static int next_token() { return Current_token = get_token(); }// 通过全局变量File指针等读取下一个 token
static std::map<char, int> Operator_Precedence;// 运算符优先级
static int getBinOpPrecedence() {// 获取二元运算符的优先级
if (!isascii(Current_token))//参数检查, 二元运算符是个ascii 码, +-*/
return -1;
int TokPrec = Operator_Precedence[Current_token];
if (TokPrec <= 0)
return -1;
return TokPrec;// 返回优先级
}
static BaseAST *expression_parser();// 声明表达式解析器函数,下面实现
static BaseAST *identifier_parser() {// 标识符解析器函数
std::string IdName = Identifier_string;// 当前普通标识符串
next_token();// 返回下一个当前的 token 类型,并改写全局变量 Identifier_str 或者 NumStr和Numeric_Val
if (Current_token != '(')//如果 标识符的接下来的token 不是(, 那么这个标识符是一个变量,否则是一个函数
return new VariableAST(IdName);// 创建变量的 AST
next_token();// 走到这里,说明当前搞到了一个(,那么标识符 IdName 是一个函数名,接下来尝试获取参数
std::vector<BaseAST *> Args;//函数参数可能有多个
if (Current_token != ')') { // 如果get到的当前token 不是),说明参数不为空,而且当前token就是这个参数
while (1) {
BaseAST *Arg = expression_parser();// 函数调用时,每个参数都可能是一个表达式
if (!Arg)// 什么情况为 NULL?
return 0;
Args.push_back(Arg);// 入列表
if (Current_token == ')')//参数结束否
break;
if (Current_token != ',')// 为真时,出错;既不是)又不是, 是不符合语法要求的。
return 0;
next_token();
}
}
next_token();
return new FunctionCallAST(IdName, Args);
}
static BaseAST *numeric_parser() {
BaseAST *Result = new NumericAST(Numeric_Val);
next_token();
return Result;
}
static BaseAST *paran_parser() {//如果遇到了表达式中含(, 则忽略这个(,并继续尝试解析接下来的表达式,可能会递归陷入,但最后每每都需要递归检查)的对应存在
next_token();
BaseAST *V = expression_parser();
if (!V)
return 0;
if (Current_token != ')')
return 0;
return V;
}
static BaseAST *Base_Parser() {
switch (Current_token) {
default:
return 0;
case IDENTIFIER_TOKEN:
return identifier_parser();//
case NUMERIC_TOKEN:
return numeric_parser();//创建数字 AST 实例
case '(':
return paran_parser();//陷入递归调用解析expression_parser()
}
}
//这个函数比较复杂一些,需要处理递归的运算符; x * 3 + y; x + 3 * y; x * (3+y); (x*3) + 6; (x + 3) * y
static BaseAST *binary_op_parser(int Old_Prec, BaseAST *LHS) {//binary_op_parser() -> paran_parser() -> expression_parser()
while (1) {
int Operator_Prec = getBinOpPrecedence();
if (Operator_Prec < Old_Prec)
return LHS;//最后返回的LHS是 LHS = new BinaryAST(sting, LHS, RHS);
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);
}
}
static BaseAST *expression_parser() {
BaseAST *LHS = Base_Parser();//取运算符左边的变量(可能是数字,也可能是标识符变量),创建对应的 AST 实例,被LHS指向之。
if (!LHS)
return 0;
return binary_op_parser(0, LHS);// 第一个参数作为binary op AST 的左操作数,同时解析出操作符和右边的操作数,右操作数有可能也是个表达式,故可能递归进expression_parser()的调用中
}
static FunctionDeclAST *func_decl_parser() {
if (Current_token != IDENTIFIER_TOKEN)//函数名字,必须是个字符串
return 0;
std::string FnName = Identifier_string;//将当前token名字转存入 FnName,函数名字token
next_token();//函数名后跟着个(
if (Current_token != '(')//如果不是(,则出错
return 0;
std::vector<std::string> Function_Argument_Names;//开始解析函数参数列表
while (next_token() == IDENTIFIER_TOKEN)// 要么参数列表非空,则取到参数名字;为空,则取到的token应该是).
Function_Argument_Names.push_back(Identifier_string);//两个及以上参数之间用空格隔开,此处未处理“,”
if (Current_token != ')')
return 0;
next_token();//拿到下一个token后,为函数返回做后续准备
//创建函数声明的 AST,并返回之,可能作为函数 定义AST 实例的一个成员
return new FunctionDeclAST(FnName, Function_Argument_Names);
}
static FunctionDefnAST *func_defn_parser() {
next_token();//这是def之后的token,即函数名字,必须是个字符串
FunctionDeclAST *Decl = func_decl_parser();
if (Decl == 0)
return 0;
//在 expression_parser 运行之前,已经取到了下一个 token,发生在func_decl_parser()最后的部分
if (BaseAST *Body = expression_parser())//def 后边必然先出现函数声明,再出现函数体,即表达式。
return new FunctionDefnAST(Decl, Body);
return 0;
}
static FunctionDefnAST *top_level_parser() {
if (BaseAST *E = expression_parser()) {
FunctionDeclAST *Func_Decl =
new FunctionDeclAST("", std::vector<std::string>());
return new FunctionDefnAST(Func_Decl, E);
}
return 0;
}
static void init_precedence() {
Operator_Precedence['-'] = 1;
Operator_Precedence['+'] = 2;
Operator_Precedence['/'] = 3;
Operator_Precedence['*'] = 4;
}
static llvm::Module *Module_Ob;
static llvm::LLVMContext MyGlobalContext;
static llvm::IRBuilder<> Builder(MyGlobalContext);
static std::map<std::string, llvm::Value *> Named_Values;
llvm::Value *NumericAST::Codegen() {
return llvm::ConstantInt::get(llvm::Type::getInt32Ty(MyGlobalContext), numeric_val);
}
llvm::Value *VariableAST::Codegen() {
llvm::Value *V = Named_Values[Var_Name];
return V ? V : 0;
}
llvm::Value *BinaryAST::Codegen() {
llvm::Value *L = LHS->Codegen();
llvm::Value *R = RHS->Codegen();
if (L == 0 || R == 0)
return 0;
switch (atoi(Bin_Operator.c_str())) {
case '+':
return Builder.CreateAdd(L, R, "addtmp");
case '-':
return Builder.CreateSub(L, R, "subtmp");
case '*':
return Builder.CreateMul(L, R, "multmp");
case '/':
return Builder.CreateUDiv(L, R, "divtmp");
default:
return 0;
}
}
llvm::Value *FunctionCallAST::Codegen() {
llvm::Function *CalleeF = Module_Ob->getFunction(Function_Callee);
std::vector<llvm::Value *> ArgsV;
for (unsigned i = 0, e = Function_Arguments.size(); i != e; ++i) {
ArgsV.push_back(Function_Arguments[i]->Codegen());
if (ArgsV.back() == 0)
return 0;
}
return Builder.CreateCall(CalleeF, ArgsV, "calltmp");
}
llvm::Function *FunctionDeclAST::Codegen() {
std::vector<llvm::Type *> Integers(Arguments.size(),
llvm::Type::getInt32Ty(MyGlobalContext));
llvm::FunctionType *FT =
llvm::FunctionType::get(llvm::Type::getInt32Ty(MyGlobalContext), Integers, false);
llvm::Function *F =
llvm::Function::Create(FT, llvm::Function::ExternalLinkage, Func_Name, Module_Ob);
if (F->getName() != Func_Name) {
F->eraseFromParent();
F = Module_Ob->getFunction(Func_Name);
if (!F->empty())
return 0;
if (F->arg_size() != Arguments.size())
return 0;
}
unsigned Idx = 0;
for (llvm::Function::arg_iterator Arg_It = F->arg_begin(); Idx != Arguments.size();
++Arg_It, ++Idx) {
Arg_It->setName(Arguments[Idx]);
Named_Values[Arguments[Idx]] = Arg_It;
}
return F;
}
llvm::Function *FunctionDefnAST::Codegen() {//由函数声明 Codegen 和 函数体的 binary op Codegen拼出来的
Named_Values.clear();
llvm::Function *TheFunction = Func_Decl->Codegen();
if (TheFunction == 0)
return 0;
llvm::BasicBlock *BB = llvm::BasicBlock::Create(MyGlobalContext, "entry", TheFunction);
Builder.SetInsertPoint(BB);
if (llvm::Value *RetVal = Body->Codegen()) {//Body->Codegen 调用 binary op Codegen
Builder.CreateRet(RetVal);
verifyFunction(*TheFunction);
return TheFunction;
}
TheFunction->eraseFromParent();
return 0;
}
static void HandleDefn() {
if (FunctionDefnAST *F = func_defn_parser()) {
if (llvm::Function *LF = F->Codegen()) {
}
} else {
next_token();
}
}
static void HandleTopExpression() {
if (FunctionDefnAST *F = top_level_parser()) {
if (llvm::Function *LF = F->Codegen()) {
}
} else {
next_token();
}
}
static void Driver() {// 完成对全部源文件的读入和 tokenize
while (1) {
switch (Current_token) {
case EOF_TOKEN:// 源码文件结束,返回
return;
case ';':// 遇到;时,忽略,继续下一个 token
next_token();
break;
case DEF_TOKEN:// 遇到 def token,接下来是构建函数定义
HandleDefn();
break;
default:
HandleTopExpression();//
break;
}
}
}
extern "C" double putchard(double X) {
putchar((char)X);
return 0;
}
int main(int argc, char *argv[])
{
llvm::LLVMContext &Context = MyGlobalContext;
init_precedence();// 运算符优先级
file = fopen(argv[1], "r");
if (file == 0) {
printf("Could not open file\n");
}
next_token();
Module_Ob = new llvm::Module("my compiler", Context);
Driver();
Module_Ob->print(llvm::outs(), nullptr);
return 0;
}
如果是比较久的llvm版本,比如llvm-3.5 则将倒数第2行代码替换成:
Module_Ob->dump();
2, Makefile
LLVM_CONFIG ?= llvm-config
#CXX := clang++
ifndef VERBOSE
QUIET :=@
endif
SRC_DIR ?= $(PWD)
LDFLAGS += $(shell $(LLVM_CONFIG) --ldflags)
COMMON_FLAGS = -Wall -Wextra
CXXFLAGS += $(COMMON_FLAGS) $(shell $(LLVM_CONFIG) --cxxflags)
LCXX :=$(shell $(LLVM_CONFIG) --cxxflags)
CPPFLAGS += $(shell $(LLVM_CONFIG) --cppflags) -I$(SRC_DIR)
CLANGLIBS = \
-Wl,--start-group \
-lclang \
-lclangFrontend \
-lclangDriver \
-lclangSerialization \
-lclangParse \
-lclangSema \
-lclangAnalysis \
-lclangEdit \
-lclangAST \
-lclangLex \
-lclangBasic \
-Wl,--end-group
LLVMLIBS = $(shell $(LLVM_CONFIG) --libs)
PROJECT = toy
PROJECT_OBJECTS = toy.o
default: $(PROJECT)
%.o : $(SRC_DIR)/%.cpp
@echo Compiling $*.cpp
$(QUIET)$(CXX) -g -c $(CPFLAGS) $(CXXFLAGS) $<
$(PROJECT) : $(PROJECT_OBJECTS)
@echo Linking $@
$(QUIET)$(CXX) -g -o $@ $(LDFLAGS) $^ $(CLANGLIBS) $(LLVMLIBS) -lncurses
.PHONY: clean
clean:
$(QUIET)rm -f $(PROJECT) $(PROJECT_OBJECTS)
.PHONY: echo
echo:
@echo "CXX is $(CXX)"
@echo "LDFLAGS is $(LDFLAGS)}"
@echo "CXXFLAGS is $(CXXFLAGS)"
@echo "CPPFLAGS is $(CPPFLAGS)"
@echo "SRC_DIR is $(SRC_DIR)"
3, 运行效果
本运行测试是在llvm-18环境测试,更低的版本也没问题,指示如前所述,更改一句源码为dump()即可。
test case:
hello.t
def add (x y)
x + ( y + 77 );
实际的编译命令如下:
g++ -g -c -Wall -Wextra -I/home/hipper/llvm_3_4_0_ex/browse_llvm_17/local_d/include -std=c++17 -fno-exceptions -funwind-tables -fno-rtti -D_GNU_SOURCE -D_DEBUG -D_GLIBCXX_ASSERTIONS -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS /home/hipper/llvm_3_4_0_ex/browse_llvm_17/ex/toy_cooboo/toy.cpp
echo Linking toy
g++ -g -o toy -L/home/hipper/llvm_3_4_0_ex/browse_llvm_17/local_d/lib toy.o -Wl,--start-group -lclang -lclangFrontend -lclangDriver -lclangSerialization -lclangParse -lclangSema -lclangAnalysis -lclangEdit -lclangAST -lclangLex -lclangBasic -Wl,--end-group -lLLVMWindowsManifest -lLLVMXRay -lLLVMLibDriver -lLLVMDlltoolDriver -lLLVMTextAPIBinaryReader -lLLVMCoverage -lLLVMLineEditor -lLLVMNVPTXCodeGen -lLLVMNVPTXDesc -lLLVMNVPTXInfo -lLLVMX86TargetMCA -lLLVMX86Disassembler -lLLVMX86AsmParser -lLLVMX86CodeGen -lLLVMX86Desc -lLLVMX86Info -lLLVMOrcDebugging -lLLVMOrcJIT -lLLVMWindowsDriver -lLLVMMCJIT -lLLVMJITLink -lLLVMInterpreter -lLLVMExecutionEngine -lLLVMRuntimeDyld -lLLVMOrcTargetProcess -lLLVMOrcShared -lLLVMDWP -lLLVMDebugInfoLogicalView -lLLVMDebugInfoGSYM -lLLVMOption -lLLVMObjectYAML -lLLVMObjCopy -lLLVMMCA -lLLVMMCDisassembler -lLLVMLTO -lLLVMPasses -lLLVMHipStdPar -lLLVMCFGuard -lLLVMCoroutines -lLLVMipo -lLLVMVectorize -lLLVMLinker -lLLVMInstrumentation -lLLVMFrontendOpenMP -lLLVMFrontendOffloading -lLLVMFrontendOpenACC -lLLVMFrontendHLSL -lLLVMFrontendDriver -lLLVMExtensions -lLLVMDWARFLinkerParallel -lLLVMDWARFLinkerClassic -lLLVMDWARFLinker -lLLVMGlobalISel -lLLVMMIRParser -lLLVMAsmPrinter -lLLVMSelectionDAG -lLLVMCodeGen -lLLVMTarget -lLLVMObjCARCOpts -lLLVMCodeGenTypes -lLLVMIRPrinter -lLLVMInterfaceStub -lLLVMFileCheck -lLLVMFuzzMutate -lLLVMScalarOpts -lLLVMInstCombine -lLLVMAggressiveInstCombine -lLLVMTransformUtils -lLLVMBitWriter -lLLVMAnalysis -lLLVMProfileData -lLLVMSymbolize -lLLVMDebugInfoBTF -lLLVMDebugInfoPDB -lLLVMDebugInfoMSF -lLLVMDebugInfoDWARF -lLLVMObject -lLLVMTextAPI -lLLVMMCParser -lLLVMIRReader -lLLVMAsmParser -lLLVMMC -lLLVMDebugInfoCodeView -lLLVMBitReader -lLLVMFuzzerCLI -lLLVMCore -lLLVMRemarks -lLLVMBitstreamReader -lLLVMBinaryFormat -lLLVMTargetParser -lLLVMTableGen -lLLVMSupport -lLLVMDemangle -lncurses