Kaleidoscope语言,语法规则如下:
# 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)
如何针对这个语言,写一个小型编译器?
首先先进行词法分析,一些c++的函数用法我都用注释的形式写出来了,很适合初学者看哦
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <iostream>
//===----------------------------------------------------------------------===//
// Lexer
//===----------------------------------------------------------------------===//
//1 词法分析。把一个一个的词读出来,看看是否和规定的语言一样
// The lexer returns tokens [0-255] if it is an unknown character, otherwise one
// of these for known things.
enum Token {
tok_eof = -1,
// commands
tok_def = -2,
tok_extern = -3,
// primary
tok_identifier = -4,//标识符
tok_number = -5
};
//枚举类型 用字符串代表常量
//std::命名空间 没有使用using namespace std;
static std::string IdentifierStr; // Filled in if tok_identifier
static double NumVal; // Filled in if tok_number
/// gettok - Return the next token from standard input.
static int gettok() {
static int LastChar = ' ';
//xxxx xxxx 这种,判断第一个字符 字母 数字..
//首先使用while循环去除输入的空格和换行
// Skip any whitespace.
while (isspace(LastChar))
LastChar = getchar();
//分析是否是字母,当字母后面的字符是数字或者字母的时候,将其加到 IdentifierStr
//这一步是保存声明变量或者函数名的关键字的。一起生成一个identifier,再与已有的关键字对比,检查是否是关键字
if (isalpha(LastChar)) { // identifier: [a-zA-Z][a-zA-Z0-9]*
IdentifierStr = LastChar;
//首字符是字母,后面跟数字或者字母,得到IdentifierStr ,判断这个是否是标识符
while (isalnum((LastChar = getchar())))
IdentifierStr += LastChar;
//def
if (IdentifierStr == "def")
return tok_def;
//extern
if (IdentifierStr == "extern")
return tok_extern;
//如果不是上面两个 那就返回这是个标识符
return tok_identifier;
}
//检查是否是数字,后面的判断 LastChar == '.'是检查小数点。如果是数字将其保存NumVal
//返回的tok_number代表当前返回的是数字
if (isdigit(LastChar) || LastChar == '.') { // Number: [0-9.]+
//字符串 Numstr
std::string NumStr;
//后面是数字或者小数点 加上,按理来说判断首字符不需要判断是不是小数点,emmm姑且先这样吧
do{
NumStr += LastChar;
LastChar = getchar();
} while (isdigit(LastChar) || LastChar == '.');
//strtod 将字符串转换成浮点数,NumStr.c_str()返回一个指向正规C字符串的指针常量
NumVal = strtod(NumStr.c_str(), nullptr);
return tok_number;
}
//这里代表是注释
if (LastChar == '#') {
// Comment until end of line.
do
LastChar = getchar();
while (LastChar != EOF && LastChar != '\n' && LastChar != '\r');
//注释行读完,继续调用gettok()
if (LastChar != EOF)
return gettok();
}
// Check for end of file. Don't eat the EOF.
if (LastChar == EOF)
return tok_eof;
// Otherwise, just return the character as its ascii value.这个字符或者是&之类的..
int ThisChar = LastChar;
LastChar = getchar();
return ThisChar;
}
int main(){
while(true){
int tok = gettok();
std::cout<<"got token:"<<tok<<std::endl;
}
return 0;
}
测试这个词法分析器
接下来使用递归下降解析法和运算符优先解析法
在解析之前,先定义解析器的输出----抽象语法树
/// ExprAST - Base class for all expression nodes.
class ExprAST {
public:
virtual ~ExprAST() = default;
};
/// NumberExprAST - Expression class for numeric literals like "1.0".
///将文字的数值捕获为实例变量
class NumberExprAST : public ExprAST {
double Val;
public:
NumberExprAST(double Val) : Val(Val) {}
};
/// VariableExprAST - Expression class for referencing a variable, like "a".
class VariableExprAST : public ExprAST {
std::string Name;
public:
VariableExprAST(const std::string &Name) : Name(Name) {}
};
//这是一个二元表达式类 也就是类似于 A = B ,这个A 就是用LHS查询,而B用RHS查询
/// BinaryExprAST - Expression class for a binary operator.
class BinaryExprAST : public ExprAST {
char Op;
std::unique_ptr<ExprAST> LHS, RHS;
public:
BinaryExprAST(char Op, std::unique_ptr<ExprAST> LHS,
std::unique_ptr<ExprAST> RHS)
: Op(Op), LHS(std::move(LHS)), RHS(std::move(RHS)) {}
//上面是构造函数赋值
};
//调用表达式的节点 相当于 sin(x),这种类型
/// CallExprAST - Expression class for function calls.
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)) {}
};
/// PrototypeAST - This class represents the "prototype" for a function,
/// which captures its name, and its argument names (thus implicitly the number
/// of arguments the function takes).
//函数原型表达式,相当于是表示 def fib(x) 这一串
class PrototypeAST {
std::string Name;
std::vector<std::string> Args;
public:
PrototypeAST(const std::string &Name, std::vector<std::string> Args)
: Name(Name), Args(std::move(Args)) {}
const std::string &getName() const { return Name; }
};
/// FunctionAST - This class represents a function definition itself.、
//函数的语法树,加上函数体
class FunctionAST {
std::unique_ptr<PrototypeAST> Proto;//这里面是一个 Name 和一个String数组,用来存放函数名称和参数
std::unique_ptr<ExprAST> Body;
public:
FunctionAST(std::unique_ptr<PrototypeAST> Proto,
std::unique_ptr<ExprAST> Body)
: Proto(std::move(Proto)), Body(std::move(Body)) {}
};
有了要构建的 AST,我们需要定义解析器代码来构建它。这里的想法是,我们想要将诸如“x+y”(词法分析器处理过的三个返回值)解析为 AST,可以通过一下调用来构造:
这是一个调用例子:
auto LHS = std::make_unique<VariableExprAST>("x");
auto RHS = std::make_unique<VariableExprAST>("y");
auto Result = std::make_unique<BinaryExprAST>('+', std::move(LHS),
std::move(RHS));
接下来是解析器
//===----------------------------------------------------------------------===//
// Parser
//===----------------------------------------------------------------------===//
//有了要构建的 AST,我们需要定义解析器代码来构建它。这里的想法是
//我们想要将诸如“x+y”(词法分析器作为三个标记返回)之类的内容解析为可以通过如下调用生成的 AST:
// auto LHS = std::make_unique<VariableExprAST>("x");
// auto RHS = std::make_unique<VariableExprAST>("y");
// auto Result = std::make_unique<BinaryExprAST>('+', std::move(LHS),
// std::move(RHS));
/// CurTok/getNextToken - Provide a simple token buffer. CurTok is the current
/// token the parser is looking at. getNextToken reads another token from the
/// lexer and updates CurTok with its results.
//CurTok是解析器正在查看的当前标记,getNextToken()更新CurTok
static int CurTok;
static int getNextToken() { return CurTok = gettok(); }//返回的是标记值
//这里的标记是用于 语法分析
//比如你可以给所有的类型标记为1,所有字母标记为2,所有数字标记为3.当年你识别到 1(类型标记)之后,你应该继续识别到2才正确,3不可以在1 的后面。
/// LogError* - These are little helper functions for error handling.
std::unique_ptr<ExprAST> LogError(const char *Str) {
fprintf(stderr, "Error: %s\n", Str);
return nullptr;
}
std::unique_ptr<PrototypeAST> LogErrorP(const char *Str) {
LogError(Str);
return nullptr;
}
static std::unique_ptr<ExprAST> ParseExpression();
//===========数值解析器===========
//它期望在当前令牌是tok_number令牌时被调用。
//它获取当前数值,创建一个NumberExprAST节点,将词法分析器推进到下一个标记,最后返回。
/// numberexpr ::= number
static std::unique_ptr<ExprAST> ParseNumberExpr() {
auto Result = std::make_unique<NumberExprAST>(NumVal);
//auto自动推断该变量的类型
//make_unique 同 unique_ptr 、auto_ptr 等一样,都是 smart pointer,
//可以取代new 并且无需 delete pointer,有助于代码管理
//static double NumVal 如果解析器读到的是数字,放在其中
getNextToken(); // consume the number
return std::move(Result);
//move()
//move 本意为 "移动",但该函数并不能移动任何数据,它的功能很简单,就是将某个左值强制转化为右值。
//基于 move() 函数特殊的功能,其常用于实现移动语义。
//move() 函数的用法也很简单,其语法格式如下:
//move( arg )
//其中,arg 表示指定的左值对象。该函数会返回 arg 对象的右值形式
//也就是说读到这个数字Num之后 会将其转化为右值 x = Num
}
//===========括号运算符解析器===========
/// parenexpr ::= '(' expression ')'
static std::unique_ptr<ExprAST> ParseParenExpr() {
getNextToken(); // eat (.
//这个parseExpression是啥??按下不表
auto V = ParseExpression();
if (!V)
return nullptr;
if (CurTok != ')')//如果读完数据 后面不是 ),报错
return LogError("expected ')'");
getNextToken(); // eat ).
return V;
}
//===========变量引用和函数调用解析器===========
/// identifierexpr
/// ::= identifier
/// ::= identifier '(' expression* ')'
static std::unique_ptr<ExprAST> ParseIdentifierExpr() {
std::string IdName = IdentifierStr;
//把标识符存入IdName
getNextToken(); // eat identifier.
//移动到下一个,如果不是 (,那就是简单的变量引用 = x1 这样
if (CurTok != '(') // Simple variable ref.
return std::make_unique<VariableExprAST>(IdName);
// Call.如果有括号,那就是函数调用
getNextToken(); // eat ( 下一个,就是其中变量
std::vector<std::unique_ptr<ExprAST>> Args;
//
if (CurTok != ')') {//如果不是 ),那就是需要传入参数
while (true) {
if (auto Arg = ParseExpression())
Args.push_back(std::move(Arg));//将参数放入向量 Args中
else
return nullptr;
if (CurTok == ')')
break;
if (CurTok != ',')//如果不是 参数 不是 ) 又不是 ,报错
return LogError("Expected ')' or ',' in argument list");
getNextToken();
}
}
// Eat the ')'.
getNextToken();
return std::make_unique<CallExprAST>(IdName, std::move(Args));
}
//定义一个辅助函数来将所有简单的表达式解析逻辑包装到一个入口点
/// primary
/// ::= identifierexpr
/// ::= numberexpr
/// ::= parenexpr
/// 可以看到 使用 CurTok来检查属于哪种表达式很方便
static std::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();
}
}
//===========二元表达式解析器===========
/*
二进制表达式非常难以解析,因为它们通常是模棱两可的。
例如,当给定字符串“x+y*z”时,解析器可以选择将其解析为“(x+y)*z”或“x+(y*z)”。
使用数学中的通用定义,我们期望后面的解析,因为“*”(乘法)比“+”(加法)具有更高的优先级。
有很多方法可以处理这个问题,但是一种有效的方法是使用运算符优先级解析。
这种解析技术使用二元运算符的优先级来指导递归。首先,我们需要一个优先级表:
*/
/// BinopPrecedence - 保留所定义的每个二进制操作符的优先级
static std::map<char, int> BinopPrecedence;
/// GetTokPrecedence - 获取待处理的二进制操作符token的优先级
static int GetTokPrecedence() {
if (!isascii(CurTok))
return -1;
// Make sure it's a declared binop.
int TokPrec = BinopPrecedence[CurTok];
if (TokPrec <= 0)
return -1;
return TokPrec;
}
/*
在main函数中设置优先级
map<char, int> BinopPrecedence
map是一个键值对容器。
map内部自建一颗红黑树,这棵树具有对数据自动排序的功能,
因此,map内的数据都是按key的值排好序的。
Install standard binary operators.
1 is lowest precedence.
BinopPrecedence['<'] = 10;
BinopPrecedence['+'] = 20;
BinopPrecedence['-'] = 20;
BinopPrecedence['*'] = 40;
*/
/// binoprhs
/// ::= ('+' primary)*
static std::unique_ptr<ExprAST> ParseBinOpRHS(int ExprPrec,
std::unique_ptr<ExprAST> LHS) {
//例如x1 + x2 * x3,LHS = x1
while (true) {
//TokPrec是 +的优先级
int TokPrec = GetTokPrecedence();
// If this is a binop that binds at least as tightly as the current binop,
// consume it, otherwise we are done.
//优先级<0
if (TokPrec < ExprPrec)
return LHS;
// Okay, we know this is a binop.
//如果大于,就是二元表达式的 op,BinOp = +
int BinOp = CurTok;
// eat binop,CurTok = x2
getNextToken();
// Parse the primary expression after the binary operator.
//解析RHS = x2,CurTok = *
auto RHS = ParsePrimary();
if (!RHS)
return nullptr;
// If BinOp binds less tightly with RHS than the operator after RHS, let
// the pending operator take RHS as its LHS.
//如果BinOp与RHS的绑定不如RHS后的操作符紧密,则让pending的操作符将RHS作为其LHS。
//NextPrec为*优先级
int NextPrec = GetTokPrecedence();
//这就是 + 与x2绑定不如 x2和*绑定紧密
if (TokPrec < NextPrec) {
//将x2 * x3合并起来了,RHS = (x2 * x3),这里的RHS实际是一个指向x2 变量的语法树指针
RHS = ParseBinOpRHS(TokPrec + 1, std::move(RHS));
if (!RHS)
return nullptr;
}
// Merge LHS/RHS.
//最后LHS = x1 + (x2 * x3)
LHS =
std::make_unique<BinaryExprAST>(BinOp, std::move(LHS), std::move(RHS));
}
}
/// expression
/// ::= primary binoprhs
///
static std::unique_ptr<ExprAST> ParseExpression() {
auto LHS = ParsePrimary();//判断 左操作值属于那种表达式,数值或者变量 构建好对应的AST
if (!LHS)
return nullptr;
//move()将左值转换为右值
return ParseBinOpRHS(0, std::move(LHS));
}
//===========函数原型解析器===========
/// prototype
/// ::= def fun(x)
static std::unique_ptr<PrototypeAST> ParsePrototype() {
//到这里 def被吃掉了 ,现在是函数名 fun
if (CurTok != tok_identifier)
return LogErrorP("Expected function name in prototype");
std::string FnName = IdentifierStr;
getNextToken();
if (CurTok != '(')
return LogErrorP("Expected '(' in prototype");
//将参数全部存入向量 ArgNames
std::vector<std::string> ArgNames;
while (getNextToken() == tok_identifier)
ArgNames.push_back(IdentifierStr);
if (CurTok != ')')
return LogErrorP("Expected ')' in prototype");
// success.
getNextToken(); // eat ')'.
return std::make_unique<PrototypeAST>(FnName, std::move(ArgNames));
}
/// definition ::= 'def' prototype expression
static std::unique_ptr<FunctionAST> ParseDefinition() {
getNextToken(); // eat def.
auto Proto = ParsePrototype();
if (!Proto)
return nullptr;
if (auto E = ParseExpression())
return std::make_unique<FunctionAST>(std::move(Proto), std::move(E));
return nullptr;
}
/// external ::= 'extern' prototype
//支持“extern”声明“sin”和“cos”等函数
static std::unique_ptr<PrototypeAST> ParseExtern() {
getNextToken(); // eat extern.
return ParsePrototype();
}
/// toplevelexpr ::= expression
static std::unique_ptr<FunctionAST> ParseTopLevelExpr() {
if (auto E = ParseExpression()) {
// Make an anonymous proto.
auto Proto = std::make_unique<PrototypeAST>("__anon_expr",
std::vector<std::string>());
return std::make_unique<FunctionAST>(std::move(Proto), std::move(E));
}
return nullptr;
}
//===----------------------------------------------------------------------===//
// Top-Level parsing
//===----------------------------------------------------------------------===//
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() {
// Evaluate a top-level expression into an anonymous function.
if (ParseTopLevelExpr()) {
fprintf(stderr, "Parsed a top-level expr\n");
} else {
// Skip token for error recovery.
getNextToken();
}
}
/// top ::= definition | external | expression | ';'
static void MainLoop() {
while (true) {
fprintf(stderr, "ready> ");
//CurTok的值就是现在读到的东西是什么,通过不同的CurTok进入不同的处理函数
switch (CurTok) {
case tok_eof:
return;
case ';': // ignore top-level semicolons.
getNextToken();
break;
case tok_def://函数定义
HandleDefinition();
break;
case tok_extern:
HandleExtern();
break;
default:
HandleTopLevelExpression();
break;
}
}
}
//===----------------------------------------------------------------------===//
// Main driver code.
//===----------------------------------------------------------------------===//
int main() {
// Install standard binary operators.
// 1 is lowest precedence.
//符号的优先级设定
BinopPrecedence['<'] = 10;
BinopPrecedence['+'] = 20;
BinopPrecedence['-'] = 20;
BinopPrecedence['*'] = 40; // highest.
// Prime the first token.
fprintf(stderr, "ready> ");
getNextToken();
// Run the main "interpreter loop" now.
MainLoop();
return 0;
}
到这里可以编译一下,测试这个解释器
直接贴全部代码了,呜呜呜之前写的忘记保存了,在这里面有添加IR的打印信息
#include "llvm/ADT/APFloat.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Type.h"
#include "llvm/IR/Verifier.h"
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include <iostream>
using namespace llvm;
//===----------------------------------------------------------------------===//
// Lexer
//===----------------------------------------------------------------------===//
//1 词法分析。把一个一个的词读出来,看看是否和规定的语言一样
// The lexer returns tokens [0-255] if it is an unknown character, otherwise one
// of these for known things.
enum Token {
tok_eof = -1,
// commands
tok_def = -2,
tok_extern = -3,
// primary
tok_identifier = -4,//标识符
tok_number = -5
};
//枚举类型 用字符串代表常量
//std::命名空间 没有使用using namespace std;
static std::string IdentifierStr; // Filled in if tok_identifier
static double NumVal; // Filled in if tok_number
/// gettok - Return the next token from standard input.
static int gettok() {
static int LastChar = ' ';
//xxxx xxxx 这种,判断第一个字符 字母 数字..
//首先使用while循环去除输入的空格和换行
// Skip any whitespace.
while (isspace(LastChar))
LastChar = getchar();
//分析是否是字母,当字母后面的字符是数字或者字母的时候,将其加到 IdentifierStr
//这一步是保存声明变量或者函数名的关键字的。一起生成一个identifier,再与已有的关键字对比,检查是否是关键字
if (isalpha(LastChar)) { // identifier: [a-zA-Z][a-zA-Z0-9]*
IdentifierStr = LastChar;
//首字符是字母,后面跟数字或者字母,得到IdentifierStr ,判断这个是否是标识符
while (isalnum((LastChar = getchar())))
IdentifierStr += LastChar;
//def
if (IdentifierStr == "def")
return tok_def;
//extern
if (IdentifierStr == "extern")
return tok_extern;
//如果不是上面两个 那就返回这是个标识符
return tok_identifier;
}
//检查是否是数字,后面的判断 LastChar == '.'是检查小数点。如果是数字将其保存NumVal
//返回的tok_number代表当前返回的是数字
if (isdigit(LastChar) || LastChar == '.') { // Number: [0-9.]+
//字符串 Numstr
std::string NumStr;
//后面是数字或者小数点 加上,按理来说判断首字符不需要判断是不是小数点,emmm姑且先这样吧
do{
NumStr += LastChar;
LastChar = getchar();
} while (isdigit(LastChar) || LastChar == '.');
//strtod 将字符串转换成浮点数,NumStr.c_str()返回一个指向正规C字符串的指针常量
NumVal = strtod(NumStr.c_str(), nullptr);
return tok_number;
}
//这里代表是注释
if (LastChar == '#') {
// Comment until end of line.
do
LastChar = getchar();
while (LastChar != EOF && LastChar != '\n' && LastChar != '\r');
//注释行读完,继续调用gettok()
if (LastChar != EOF)
return gettok();
}
// Check for end of file. Don't eat the EOF.
if (LastChar == EOF)
return tok_eof;
// Otherwise, just return the character as its ascii value.这个字符或者是&之类的..
int ThisChar = LastChar;
LastChar = getchar();
return ThisChar;
}
// int main(){
// while(true){
// int tok = gettok();
// std::cout<<"got token:"<<tok<<std::endl;
// }
// return 0;
// }
//===----------------------------------------------------------------------===//
// Abstract Syntax Tree (aka Parse Tree)
//===----------------------------------------------------------------------===//
//使用递归下降解析法和运算符优先解析法
//在解析之前,先定义解析器的输出----抽象语法树
//namespace表现逻辑聚集关系,
//namespace AST{
/// ExprAST - Base class for all expression nodes.
class ExprAST {
public:
virtual ~ExprAST() = default;
virtual Value *codegen() = 0;
//codegen()方法表示为AST节点以及它所依赖的东西发出的IR,会返回一个 LLVM Value 对象
//这里的Value类是SSA 值最独特的方面是它们的值是在相关指令执行时计算的,
//并且在指令重新执行之前(并且如果)它不会获得新值。
//换句话说,没有办法“改变”一个 SSA 值。有关更多信息,请阅读静态单一分配
};
/// NumberExprAST - Expression class for numeric literals like "1.0".
///将文字的数值捕获为实例变量
class NumberExprAST : public ExprAST {
double Val;
public:
NumberExprAST(double Val) : Val(Val) {}
Value *codegen() override;
};
/// VariableExprAST - Expression class for referencing a variable, like "a".
class VariableExprAST : public ExprAST {
std::string Name;
public:
VariableExprAST(const std::string &Name) : Name(Name) {}
Value *codegen() override;
};
//这是一个二元表达式类 也就是类似于 A = B ,这个A 就是用LHS查询,而B用RHS查询
/// BinaryExprAST - Expression class for a binary operator.
class BinaryExprAST : public ExprAST {
char Op;
std::unique_ptr<ExprAST> LHS, RHS;
public:
BinaryExprAST(char Op, std::unique_ptr<ExprAST> LHS,
std::unique_ptr<ExprAST> RHS)
: Op(Op), LHS(std::move(LHS)), RHS(std::move(RHS)) {}
//上面是构造函数赋值
Value *codegen() override;
};
//调用表达式的节点 相当于 sin(x),这种类型
/// CallExprAST - Expression class for function calls.
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)) {}
Value *codegen() override;
};
/// PrototypeAST - This class represents the "prototype" for a function,
/// which captures its name, and its argument names (thus implicitly the number
/// of arguments the function takes).
//函数原型表达式。
class PrototypeAST {
std::string Name;
std::vector<std::string> Args;
public:
PrototypeAST(const std::string &Name, std::vector<std::string> Args)
: Name(Name), Args(std::move(Args)) {}
const std::string &getName() const { return Name; }
Function *codegen();
};
/// FunctionAST - This class represents a function definition itself.、
//函数的语法树,
class FunctionAST {
std::unique_ptr<PrototypeAST> Proto;//这里面是一个 Name 和一个String数组,用来存放函数名称和参数
std::unique_ptr<ExprAST> Body;
public:
FunctionAST(std::unique_ptr<PrototypeAST> Proto,
std::unique_ptr<ExprAST> Body)
: Proto(std::move(Proto)), Body(std::move(Body)) {}
Function *codegen();
};
//===----------------------------------------------------------------------===//
// Parser
//===----------------------------------------------------------------------===//
//有了要构建的 AST,我们需要定义解析器代码来构建它。这里的想法是
//我们想要将诸如“x+y”(词法分析器作为三个标记返回)之类的内容解析为可以通过如下调用生成的 AST:
// auto LHS = std::make_unique<VariableExprAST>("x");
// auto RHS = std::make_unique<VariableExprAST>("y");
// auto Result = std::make_unique<BinaryExprAST>('+', std::move(LHS),
// std::move(RHS));
/// CurTok/getNextToken - Provide a simple token buffer. CurTok is the current
/// token the parser is looking at. getNextToken reads another token from the
/// lexer and updates CurTok with its results.
//CurTok是解析器正在查看的当前标记,getNextToken()更新CurTok
static int CurTok;
static int getNextToken() { return CurTok = gettok(); }//返回的是标记值
//这里的标记是用于 语法分析
//比如你可以给所有的类型标记为1,所有字母标记为2,所有数字标记为3.当年你识别到 1(类型标记)之后,你应该继续识别到2才正确,3不可以在1 的后面。
/// LogError* - These are little helper functions for error handling.
std::unique_ptr<ExprAST> LogError(const char *Str) {
fprintf(stderr, "Error: %s\n", Str);
return nullptr;
}
std::unique_ptr<PrototypeAST> LogErrorP(const char *Str) {
LogError(Str);
return nullptr;
}
static std::unique_ptr<ExprAST> ParseExpression();
//===========数值解析器===========
//它期望在当前令牌是tok_number令牌时被调用。
//它获取当前数值,创建一个NumberExprAST节点,将词法分析器推进到下一个标记,最后返回。
/// numberexpr ::= number
static std::unique_ptr<ExprAST> ParseNumberExpr() {
auto Result = std::make_unique<NumberExprAST>(NumVal);
//auto自动推断该变量的类型
//make_unique 同 unique_ptr 、auto_ptr 等一样,都是 smart pointer,
//可以取代new 并且无需 delete pointer,有助于代码管理
//static double NumVal 如果解析器读到的是数字,放在其中
getNextToken(); // consume the number
return std::move(Result);
//move()
//move 本意为 "移动",但该函数并不能移动任何数据,它的功能很简单,就是将某个左值强制转化为右值。
//基于 move() 函数特殊的功能,其常用于实现移动语义。
//move() 函数的用法也很简单,其语法格式如下:
//move( arg )
//其中,arg 表示指定的左值对象。该函数会返回 arg 对象的右值形式
//也就是说读到这个数字Num之后 会将其转化为右值 x = Num
}
//===========括号运算符解析器===========
/// parenexpr ::= '(' expression ')'
static std::unique_ptr<ExprAST> ParseParenExpr() {
getNextToken(); // eat (.
//这个parseExpression是啥??按下不表
auto V = ParseExpression();
if (!V)
return nullptr;
if (CurTok != ')')//如果读完数据 后面不是 ),报错
return LogError("expected ')'");
getNextToken(); // eat ).
return V;
}
//===========变量引用和函数调用解析器===========
/// identifierexpr
/// ::= identifier
/// ::= identifier '(' expression* ')'
static std::unique_ptr<ExprAST> ParseIdentifierExpr() {
std::string IdName = IdentifierStr;
//把标识符存入IdName
getNextToken(); // eat identifier.
//移动到下一个,如果不是 (,那就是简单的变量引用 = x1 这样
if (CurTok != '(') // Simple variable ref.
return std::make_unique<VariableExprAST>(IdName);
// Call.如果有括号,那就是函数调用
getNextToken(); // eat ( 下一个,就是其中变量
std::vector<std::unique_ptr<ExprAST>> Args;
//
if (CurTok != ')') {//如果不是 ),那就是需要传入参数
while (true) {
if (auto Arg = ParseExpression())
Args.push_back(std::move(Arg));//将参数放入向量 Args中
else
return nullptr;
if (CurTok == ')')
break;
if (CurTok != ',')//如果不是 参数 不是 ) 又不是 ,报错
return LogError("Expected ')' or ',' in argument list");
getNextToken();
}
}
// Eat the ')'.
getNextToken();
return std::make_unique<CallExprAST>(IdName, std::move(Args));
}
//定义一个辅助函数来将所有简单的表达式解析逻辑包装到一个入口点
/// primary
/// ::= identifierexpr
/// ::= numberexpr
/// ::= parenexpr
/// 可以看到 使用 CurTok来检查属于哪种表达式很方便
static std::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();
}
}
//===========二元表达式解析器===========
/*
二进制表达式非常难以解析,因为它们通常是模棱两可的。
例如,当给定字符串“x+y*z”时,解析器可以选择将其解析为“(x+y)*z”或“x+(y*z)”。
使用数学中的通用定义,我们期望后面的解析,因为“*”(乘法)比“+”(加法)具有更高的优先级。
有很多方法可以处理这个问题,但是一种有效的方法是使用运算符优先级解析。
这种解析技术使用二元运算符的优先级来指导递归。首先,我们需要一个优先级表:
*/
/// BinopPrecedence - 保留所定义的每个二进制操作符的优先级
static std::map<char, int> BinopPrecedence;
/// GetTokPrecedence - 获取待处理的二进制操作符token的优先级
static int GetTokPrecedence() {
if (!isascii(CurTok))
return -1;
// Make sure it's a declared binop.
int TokPrec = BinopPrecedence[CurTok];
if (TokPrec <= 0)
return -1;
return TokPrec;
}
/*
在main函数中设置优先级
map<char, int> BinopPrecedence
map是一个键值对容器。
map内部自建一颗红黑树,这棵树具有对数据自动排序的功能,
因此,map内的数据都是按key的值排好序的。
Install standard binary operators.
1 is lowest precedence.
BinopPrecedence['<'] = 10;
BinopPrecedence['+'] = 20;
BinopPrecedence['-'] = 20;
BinopPrecedence['*'] = 40;
*/
/// binoprhs
/// ::= ('+' primary)*
static std::unique_ptr<ExprAST> ParseBinOpRHS(int ExprPrec,
std::unique_ptr<ExprAST> LHS) {
//例如x1 + x2 * x3,LHS = x1
while (true) {
//TokPrec是 +的优先级
int TokPrec = GetTokPrecedence();
// If this is a binop that binds at least as tightly as the current binop,
// consume it, otherwise we are done.
//优先级<0
if (TokPrec < ExprPrec)
return LHS;
// Okay, we know this is a binop.
//如果大于,就是二元表达式的 op,BinOp = +
int BinOp = CurTok;
// eat binop,CurTok = x2
getNextToken();
// Parse the primary expression after the binary operator.
//解析RHS = x2,CurTok = *
auto RHS = ParsePrimary();
if (!RHS)
return nullptr;
// If BinOp binds less tightly with RHS than the operator after RHS, let
// the pending operator take RHS as its LHS.
//如果BinOp与RHS的绑定不如RHS后的操作符紧密,则让pending的操作符将RHS作为其LHS。
//NextPrec为*优先级
int NextPrec = GetTokPrecedence();
//这就是 + 与x2绑定不如 x2和*绑定紧密
if (TokPrec < NextPrec) {
//将x2 * x3合并起来了,RHS = (x2 * x3),这里的RHS实际是一个指向x2 变量的语法树指针
RHS = ParseBinOpRHS(TokPrec + 1, std::move(RHS));
if (!RHS)
return nullptr;
}
// Merge LHS/RHS.
//最后LHS = x1 + (x2 * x3)
LHS =
std::make_unique<BinaryExprAST>(BinOp, std::move(LHS), std::move(RHS));
}
}
/// expression
/// ::= primary binoprhs
///
static std::unique_ptr<ExprAST> ParseExpression() {
auto LHS = ParsePrimary();//判断 左操作值属于那种表达式,数值或者变量 构建好对应的AST
if (!LHS)
return nullptr;
//move()将左值转换为右值
return ParseBinOpRHS(0, std::move(LHS));
}
//===========函数原型解析器===========
/// prototype
/// ::= def fun(x)
static std::unique_ptr<PrototypeAST> ParsePrototype() {
//到这里 def被吃掉了 ,现在是函数名 fun
if (CurTok != tok_identifier)
return LogErrorP("Expected function name in prototype");
std::string FnName = IdentifierStr;
getNextToken();
if (CurTok != '(')
return LogErrorP("Expected '(' in prototype");
//将参数全部存入向量 ArgNames
std::vector<std::string> ArgNames;
while (getNextToken() == tok_identifier)
ArgNames.push_back(IdentifierStr);
if (CurTok != ')')
return LogErrorP("Expected ')' in prototype");
// success.
getNextToken(); // eat ')'.
return std::make_unique<PrototypeAST>(FnName, std::move(ArgNames));
}
/// definition ::= 'def' prototype expression
static std::unique_ptr<FunctionAST> ParseDefinition() {
getNextToken(); // eat def.
auto Proto = ParsePrototype();
if (!Proto)
return nullptr;
if (auto E = ParseExpression())
return std::make_unique<FunctionAST>(std::move(Proto), std::move(E));
return nullptr;
}
/// external ::= 'extern' prototype
//支持“extern”声明“sin”和“cos”等函数
static std::unique_ptr<PrototypeAST> ParseExtern() {
getNextToken(); // eat extern.
return ParsePrototype();
}
/// toplevelexpr ::= expression
static std::unique_ptr<FunctionAST> ParseTopLevelExpr() {
if (auto E = ParseExpression()) {
// Make an anonymous proto.
auto Proto = std::make_unique<PrototypeAST>("__anon_expr",
std::vector<std::string>());
return std::make_unique<FunctionAST>(std::move(Proto), std::move(E));
}
return nullptr;
}
//===----------------------------------------------------------------------===//
// Main driver code.
//===----------------------------------------------------------------------===//
// int main() {
// // Install standard binary operators.
// // 1 is lowest precedence.
// //符号的优先级设定
// BinopPrecedence['<'] = 10;
// BinopPrecedence['+'] = 20;
// BinopPrecedence['-'] = 20;
// BinopPrecedence['*'] = 40; // highest.
// // Prime the first token.
// fprintf(stderr, "ready> ");
// getNextToken();
// // Run the main "interpreter loop" now.
// MainLoop();
// return 0;
// }
//TheContext是以恶搞不透明的对象,有许多LLVM的数据核心
static std::unique_ptr<LLVMContext> TheContext;
//Builder是一个帮助对象,可以生成LLVM的指令
//IRBuilder类模板的实例 跟踪插入指令的当前位置,并具有创建新指令的方法。
static std::unique_ptr<Module> TheModule;
//TheModule是一个包含函数和全局变量的 LLVM 结构。在许多方面,
//它是 LLVM IR 用来包含代码的顶级结构。它将拥有我们生成的所有 IR 的内存,
//这就是 codegen() 方法返回原始 Value* 而不是 unique_ptr<Value> 的原因。
static std::unique_ptr<IRBuilder<>> Builder;
//NamedValues映射跟踪当前范围内定义了哪些值以及它们的 LLVM 表示形式是什么。
static std::map<std::string, Value *> NamedValues;
//像我们用于解析器的“LogError”方法,它将用于报告代码生成期间发现的错误
Value *LogErrorV(const char *Str) {
LogError(Str);
return nullptr;
}
//为表达式节点生成LLVM代码
//实现每个AST类的codegen方法,语法如下
Value *NumberExprAST::codegen(){
//在LLVM IR中,数字常量用ConstantFP类表示
//该类在内部保存数值APFloat(APFloat具有保存任意精度浮点常量的能力)
//注意,在LLVM IR中常量都是唯一的且是共享的
return ConstantFP::get(*TheContext,APFloat(Val));
}
//对变量节点生成LLVM IR
//此代码仅检查指定名称是否在映射中(如果没有,则引用未知变量)并返回它的值。
Value *VariableExprAST::codegen() {
// NamedValues映射中唯一可以包含的值是函数参数。
Value *V = NamedValues[Name];
if (!V)
LogErrorV("Unknown variable name");
return V;
}
//二元运算符
//在这里递归地codegen(),IRBuilder知道在哪里插入新创建的指令
//我们所需要做的就是,指定要创建的指令,例如CreateFAdd()
//要使用的操作数,并且可以为生成的指令提供名称"addtmp"
//LLVM 的一个好处是名称只是一个提示。例如,如果上面的代码发出多个“addtmp”变量,
//LLVM 将自动为每个变量提供一个递增的、唯一的数字后缀。
//指令的本地值名称完全是可选的,但它使读取 IR 转储变得更加容易。
//LLVM 指令受到严格规则的约束:例如,加法指令的左右操作数必须具有相同的类型,
//加法的结果类型必须与操作数类型匹配。因为 Kaleidoscope 中的所有值都是双精度值,
//所以这使得 add、sub 和 mul 的代码非常简单。
Value *BinaryExprAST::codegen(){
Value *L = LHS->codegen();
Value *R = RHS->codegen();
if(!L || !R){
return nullptr;
}
switch (Op)
{
case '+':
return Builder->CreateFAdd(L,R,"addtmp");
case '-':
return Builder->CreateFSub(L, R, "subtmp");
case '*':
return Builder->CreateFMul(L, R, "multmp");
case '<':
L = Builder->CreateFCmpULT(L, R, "cmptmp");
//LLVM 指定fcmp 指令总是返回一个“i1”值(一个一位整数)。
//问题在于 Kaleidoscope 希望该值是 0.0 或 1.0 值。
//为了获得这些语义,我们将 fcmp 指令与uitofp 指令结合起来。
//该指令通过将输入视为无符号值将其输入整数转换为浮点值。
//相反,如果我们使用sitofp 指令,Kaleidoscope '<' 运算符将根据输入值返回 0.0 和 -1.0。
return Builder->CreateUIToFP(L, Type::getDoubleTy(*TheContext),
"booltmp");
default:
return LogErrorV("invalid binary operator");
}
}
//上面的代码最初在 LLVM 模块的符号表中进行函数名查找。回想一下,LLVM 模块是保存我们 JIT 函数的容器。
//通过为每个函数指定与用户指定的名称相同的名称,我们可以使用 LLVM 符号表为我们解析函数名称。
Value *CallExprAST::codegen() {
// Look up the name in the global module table.
Function *CalleeF = TheModule->getFunction(Callee);
if (!CalleeF)
return LogErrorV("Unknown function referenced");
// If argument mismatch error.
if (CalleeF->arg_size() != Args.size())
return LogErrorV("Incorrect # arguments passed");
std::vector<Value *> ArgsV;
for (unsigned i = 0, e = Args.size(); i != e; ++i) {
ArgsV.push_back(Args[i]->codegen());
if (!ArgsV.back())
return nullptr;
}
return Builder->CreateCall(CalleeF, ArgsV, "calltmp");
}
//这里还是没理解到 原型AST,就当作是函数头???之后再理解吧
//原型和函数的代码生成
//此函数返回“Function*”而不是“Value*”
Function *PrototypeAST::codegen() {
//Make the function type: double(double,double) etc.
std::vector<Type *> Doubles(Args.size(), Type::getDoubleTy(*TheContext));
//FunctionType::get创建FunctionType应该用于给定原型的调用
//由于这个语言只有双精度,所以这里传入Double
FunctionType *FT =
FunctionType::get(Type::getDoubleTy(*TheContext), Doubles, false);
//创建了与 Prototype 对应的 IR Function。这指示要使用的类型、链接和名称,以及要插入的模块
Function *F =
Function::Create(FT, Function::ExternalLinkage, Name, TheModule.get());
//设置所有参数的名称
unsigned Idx = 0;
for (auto &Arg : F->args())
Arg.setName(Args[Idx++]);
return F;
}
Function *FunctionAST::codegen() {
// First, check for an existing function from a previous 'extern' declaration.
Function *TheFunction = TheModule->getFunction(Proto->getName());
if (!TheFunction)
TheFunction = Proto->codegen();
if (!TheFunction)
return nullptr;
// Create a new basic block to start insertion into.
BasicBlock *BB = BasicBlock::Create(*TheContext, "entry", TheFunction);
Builder->SetInsertPoint(BB);
// Record the function arguments in the NamedValues map.
NamedValues.clear();
for (auto &Arg : TheFunction->args())
NamedValues[std::string(Arg.getName())] = &Arg;
if (Value *RetVal = Body->codegen()) {
// Finish off the function.
Builder->CreateRet(RetVal);
// Validate the generated code, checking for consistency.
verifyFunction(*TheFunction);
return TheFunction;
}
// Error reading body, remove function.
TheFunction->eraseFromParent();
return nullptr;
}
//===----------------------------------------------------------------------===//
// Top-Level parsing and JIT Driver
//===----------------------------------------------------------------------===//
static void InitializeModule() {
// Open a new context and module.
TheContext = std::make_unique<LLVMContext>();
TheModule = std::make_unique<Module>("my cool jit", *TheContext);
// Create a new builder for the module.
Builder = std::make_unique<IRBuilder<>>(*TheContext);
}
static void HandleDefinition() {
if (auto FnAST = ParseDefinition()) {
if (auto *FnIR = FnAST->codegen()) {
fprintf(stderr, "Parsed a function definition.\n");
FnIR->print(errs());
fprintf(stderr, "\n");
}
} else {
// Skip token for error recovery.
getNextToken();
}
}
static void HandleExtern() {
if (auto ProtoAST = ParseExtern()) {
if (auto *FnIR = ProtoAST->codegen()) {
fprintf(stderr, "Parsed an extern\n");
FnIR->print(errs());
fprintf(stderr, "\n");
}
} else {
// Skip token for error recovery.
getNextToken();
}
}
static void HandleTopLevelExpression() {
// Evaluate a top-level expression into an anonymous function.
if (auto FnAST = ParseTopLevelExpr()) {
if (auto *FnIR = FnAST->codegen()) {
fprintf(stderr, "Parsed a top-level expr\n");
FnIR->print(errs());
fprintf(stderr, "\n");
// Remove the anonymous expression.
FnIR->eraseFromParent();
}
} else {
// Skip token for error recovery.
getNextToken();
}
}
/// top ::= definition | external | expression | ';'
static void MainLoop() {
while (true) {
fprintf(stderr, "ready> ");
//CurTok的值就是现在读到的东西是什么,通过不同的CurTok进入不同的处理函数
switch (CurTok) {
case tok_eof:
return;
case ';': // ignore top-level semicolons.
getNextToken();
break;
case tok_def://函数定义
HandleDefinition();
break;
case tok_extern:
HandleExtern();
break;
default:
HandleTopLevelExpression();
break;
}
}
}
//===----------------------------------------------------------------------===//
// Main driver code.
//===----------------------------------------------------------------------===//
int main() {
// Install standard binary operators.
// 1 is lowest precedence.
BinopPrecedence['<'] = 10;
BinopPrecedence['+'] = 20;
BinopPrecedence['-'] = 20;
BinopPrecedence['*'] = 40; // highest.
// Prime the first token.
fprintf(stderr, "ready> ");
getNextToken();
// Make the module, which holds all the code.
InitializeModule();
// Run the main "interpreter loop" now.
MainLoop();
fprintf(stderr, "输出:\n");
// Print out all of the generated code.
TheModule->print(errs(), nullptr);
return 0;
}
最后编译输出:
可以看到中间表示是一条一条的指令
编译命令如下:
g++ -g -O3 toy.cpp `llvm-config --cxxflags --ldflags --system-libs --libs core` -std=c++14 -o toy
因为使用了智能指针,所以这里要加上-std=c++14