系列文章目录
LLVM系列第一章:编译LLVM源码
LLVM系列第二章:模块Module
LLVM系列第三章:函数Function
LLVM系列第四章:逻辑代码块Block
LLVM系列第五章:全局变量Global Variable
LLVM系列第六章:函数返回值Return
LLVM系列第七章:函数参数Function Arguments
LLVM系列第八章:算术运算语句Arithmetic Statement
LLVM系列第九章:控制流语句if-else
LLVM系列第十章:控制流语句if-else-phi
LLVM系列第十一章:写一个Hello World
LLVM系列第十二章:写一个简单的词法分析器Lexer
LLVM系列第十三章:写一个简单的语法分析器Parser
LLVM系列第十四章:写一个简单的语义分析器Semantic Analyzer
LLVM系列第十五章:写一个简单的中间代码生成器IR Generator
LLVM系列第十六章:写一个简单的编译器
LLVM系列第十七章:for循环
LLVM系列第十八章:写一个简单的IR处理流程Pass
LLVM系列第十九章:写一个简单的Module Pass
LLVM系列第二十章:写一个简单的Function Pass
LLVM系列第二十一章:写一个简单的Loop Pass
LLVM系列第二十二章:写一个简单的编译时函数调用统计器(Pass)
LLVM系列第二十三章:写一个简单的运行时函数调用统计器(Pass)
LLVM系列第二十四章:用Xcode编译调试LLVM源码
LLVM系列第二十五章:简单统计一下LLVM源码行数
LLVM系列第二十六章:理解LLVMContext
LLVM系列第二十七章:理解IRBuilder
LLVM系列第二十八章:写一个JIT Hello World
LLVM系列第二十九章:写一个简单的常量加法“消除”工具(Pass)
本文目录
前言
在此记录下,基于LLVM写一个简单的语法分析器(Simple Parser)的过程,以备查阅。
开发环境的配置请参考 《LLVM系列第一章:编译LLVM源码》。
我们再来简单复习一下,编译器前端的流程:
更多关于编译器前端的介绍,请参看《LLVM系列第三章:写一个简单的词法分析器Lexer》。
本章内容,与词法分析(Lexer)及语法分析(Parser)有关,是一个最简单的示例而已。
一、SimpleLang语言
为了方便起见,我们自己定义一种很简单的语言(名为SimpleLang)如下(示例):
calc : ("with" ident ("," ident)* ":")? expr ;
expr: term(("+"|"-")term)* ;
term : factor (( "*" | "/") factor)* ;
factor : ident | number | "(" expr ")" ;
ident : ([a-zAZ])+ ;
number : ([0-9])+ ;
这也是我们在《LLVM系列第三章:写一个简单的词法分析器Lexer》中用到的语言。
二、项目结构
我们把这个简单的项目命名为SimpleParser。源码组织结构如下(示例):
% tree -I "build|build-xcode"
.
├── CMakeLists.txt
└── src
├── AST.h
├── CMakeLists.txt
├── Lexer.cpp
├── Lexer.h
├── Parser.cpp
├── Parser.h
└── ParserPlayer.cpp
各文件的内容大体如下:
- AST.h,SimpleLang语言的抽象语法树(AST)的定义及实现代码
- Lexer.h和Lexer.cpp,SimpleLang语言的词法分析器(Lexer)的定义及实现代码
- Parser.h和Parser.cpp,SimpleLang语言的语法分析器(Parser)的定义及实现代码
- ParserPlayer.cpp,main函数,即Parser的测试代码
三、项目细节
1. 程序模块
这个简单的项目只包含了一个模块:
- SimpleParser,一个简单的可执行程序文件
以下是跟项目组织结构相关的部分CMake脚本。
(1) 项目根目录(示例):
# CMakeLists.txt
...
project ("SimpleParser")
...
add_subdirectory ("src")
这里创建了一个项目(project),并把src目录下的子项目加入进来。
(2) src目录(示例):
# src/CMakeLists.txt
...
add_executable(SimpleParser ...)
...
这是src目录下的子项目,用来构建SimpleParser程序。
2. 引入LLVM
我们需要做一些与LLVM相关的配置,才能顺利地使用LLVM(示例):
# CMakeLists.txt
...
find_package(LLVM REQUIRED CONFIG)
message("Found LLVM ${LLVM_PACKAGE_VERSION}, build type ${LLVM_BUILD_TYPE}")
list(APPEND CMAKE_MODULE_PATH ${LLVM_DIR})
...
add_definitions(${LLVM_DEFINITIONS})
include_directories(SYSTEM ${LLVM_INCLUDE_DIRS})
llvm_map_components_to_libnames(llvm_libs Core)
...
# src/CMakeLists.txt
...
target_link_libraries(SimpleParser PRIVATE ${llvm_libs})
3. Simple Lexer
对于我们的Simple Lexer,已在《LLVM系列第三章:写一个简单的词法分析器Lexer》中详细介绍,这里就不再赘述了。
4. Simple Parser
我们把重点放在语法分析器(Parser)上:
- src/ParserPlayer.cpp,包含了main函数,及测试代码
- src/Parser.h,src/Parser.cpp,包含了语法分析器(Parser)的定义及实现代码
- AST.h,包含了抽象语法树(AST)的定义及实现
main函数(示例):
#include "Parser.h"
...
static llvm::cl::opt<std::string> input(llvm::cl::Positional, llvm::cl::desc("<input expression>"), llvm::cl::init(""));
int main(int argc, const char** argv)
{
llvm::InitLLVM llvmInitializer(argc, argv);
llvm::cl::ParseCommandLineOptions(argc, argv, "SimpleParser - a simple code parser\n");
llvm::outs() << "Input: \"" << input << "\"\n";
Lexer lexer(input);
Parser parser(lexer);
AST* tree = parser.Parse();
if (!tree || parser.HasError())
{
llvm::errs() << "Syntax errors occured\n";
return 1;
}
ASTPrinter printer;
tree->Accept(printer);
...
}
我们看到以上代码调用了Parser来做语法分析,并调用ASTPrinter逐一打印出AST中所有的节点。Parser的定义如下(示例):
class Parser
{
public:
Parser(Lexer& lexer) :
lexer(lexer),
hasError(false)
{
Advance();
}
AST* Parse();
bool HasError()
{
return hasError;
}
private:
AST* ParseCalc();
Expr* ParseExpr();
Expr* ParseTerm();
Expr* ParseFactor();
void AddError()
{
llvm::errs() << "Unexpected: " << token.GetText() << "\n";
hasError = true;
}
void Advance()
{
lexer.GetNext(token);
}
bool IsNextTokenOfType(Token::TokenType tokenType)
{
if (!token.Is(tokenType))
{
AddError();
return true;
}
return false;
}
bool Consume(Token::TokenType tokenType)
{
if (IsNextTokenOfType(tokenType))
{
return true;
}
Advance();
return false;
}
private:
Lexer& lexer;
Token token;
bool hasError;
};
Parser的实现如下(示例):
AST* Parser::Parse()
{
AST* tree = ParseCalc();
// IsNextTokenOfType(Token::kEOI);
return tree;
}
AST* Parser::ParseCalc()
{
Expr* expr;
llvm::SmallVector<llvm::StringRef, 8> variables;
if (token.Is(Token::kKeywordWith))
{
Advance();
if (IsNextTokenOfType(Token::kIdent))
{
goto errorTag;
}
variables.push_back(token.GetText());
Advance();
while (token.Is(Token::kComma))
{
Advance();
if (IsNextTokenOfType(Token::kIdent))
{
goto errorTag;
}
variables.push_back(token.GetText());
Advance();
}
if (Consume(Token::kColon))
{
goto errorTag;
}
}
expr = ParseExpr();
if (IsNextTokenOfType(Token::kEOI))
{
goto errorTag;
}
if (variables.empty())
{
return expr;
}
else
{
return new WithDeclaration(variables, expr);
}
errorTag:
while (token.GetType() != Token::kEOI)
{
Advance();
}
return nullptr;
}
Expr* Parser::ParseExpr()
{
Expr* left = ParseTerm();
while (token.IsOneOf(Token::kPlus, Token::kMinus))
{
BinaryOp::Operator op = token.Is(Token::kPlus) ? BinaryOp::kPlus : BinaryOp::kMinus;
Advance();
Expr* right = ParseTerm();
left = new BinaryOp(op, left, right);
}
return left;
}
Expr* Parser::ParseTerm()
{
Expr* left = ParseFactor();
while (token.IsOneOf(Token::kStar, Token::kSlash))
{
BinaryOp::Operator op = token.Is(Token::kStar) ? BinaryOp::kMultiple : BinaryOp::kDivide;
Advance();
Expr* right = ParseFactor();
left = new BinaryOp(op, left, right);
}
return left;
}
Expr* Parser::ParseFactor()
{
Expr* expr = nullptr;
switch (token.GetType())
{
case Token::kNumber:
expr = new Factor(Factor::kNumber, token.GetText());
Advance();
break;
case Token::kIdent:
expr = new Factor(Factor::kIdent, token.GetText());
Advance();
break;
case Token::kLeftParen:
Advance();
expr = ParseExpr();
if (!Consume(Token::kRightParen))
{
break;
}
default:
if (!expr)
{
AddError();
}
while (
!token.IsOneOf(Token::kRightParen, Token::kStar, Token::kPlus, Token::kMinus, Token::kSlash, Token::kEOI))
{
Advance();
}
break;
}
return expr;
}
注意到Parser调用了Lexer把代码切分成一个个的Token,然后用这些Token构建出一颗抽象语法树(AST)。
AST的定义及实现如下(示例):
class ASTVisitor
{
public:
virtual void Visit(AST&)
{
}
virtual void Visit(Expr&)
{
}
virtual void Visit(Factor&) = 0;
virtual void Visit(BinaryOp&) = 0;
virtual void Visit(WithDeclaration&) = 0;
};
class AST
{
public:
virtual ~AST()
{
}
virtual void Accept(ASTVisitor& visitor) = 0;
};
class Expr : public AST
{
public:
Expr()
{
}
};
class Factor : public Expr
{
public:
enum ValueType
{
kIdent,
kNumber
};
private:
ValueType type;
llvm::StringRef value;
public:
Factor(ValueType inType, llvm::StringRef inVal) :
type(inType),
value(inVal)
{
}
ValueType GetType()
{
return type;
}
llvm::StringRef GetVal()
{
return value;
}
virtual void Accept(ASTVisitor& visitor) override
{
visitor.Visit(*this);
}
};
class BinaryOp : public Expr
{
public:
enum Operator
{
kPlus,
kMinus,
kMultiple,
kDivide
};
private:
Expr* left;
Expr* right;
Operator op;
public:
BinaryOp(Operator inOp, Expr* inLeftExpr, Expr* inRightExpr) :
op(inOp),
left(inLeftExpr),
right(inRightExpr)
{
}
Expr* GetLeft()
{
return left;
}
Expr* GetRight()
{
return right;
}
Operator GetOperator()
{
return op;
}
virtual void Accept(ASTVisitor& visitor) override
{
visitor.Visit(*this);
}
std::string GetDisplayText() const
{
switch (op)
{
case kPlus:
return "+";
case kMinus:
return "-";
case kMultiple:
return "*";
case kDivide:
return "/";
default:
break;
}
return "";
}
};
class WithDeclaration : public AST
{
using VariableVector = llvm::SmallVector<llvm::StringRef, 8>;
public:
WithDeclaration(llvm::SmallVector<llvm::StringRef, 8> inVars, Expr* inExpr) :
variables(inVars),
expr(inExpr)
{
}
VariableVector::const_iterator begin()
{
return variables.begin();
}
VariableVector::const_iterator end()
{
return variables.end();
}
Expr* getExpr()
{
return expr;
}
virtual void Accept(ASTVisitor& visitor) override
{
visitor.Visit(*this);
}
private:
VariableVector variables;
Expr* expr;
};
抽象语法树(AST)中所有类型的节点的定义,看起来也像一棵树:
这里用到了经典的设计模式“Visitor模式”,ASTVisitor类的定义如下:
注意,我们把ASTPrinter放在了文件ParserPlayer.cpp中。
四、编译
1. 生成项目文件
用CMake生成项目文件(示例):
mkdir build
cd build
cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ..
输出log如下(示例):
-- The C compiler identification is AppleClang 13.0.0.13000029
-- The CXX compiler identification is AppleClang 13.0.0.13000029
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found ZLIB: /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk/usr/lib/libz.tbd (found version "1.2.11")
-- Found LibXml2: /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk/usr/lib/libxml2.tbd (found version "2.9.4")
Found LLVM 12.0.1, build type Release
-- Configuring done
-- Generating done
-- Build files have been written to: .../SimpleParser/build
如果要生成Xcode项目文件,我们稍微改一下cmake命令的参数即可(示例):
mkdir build-xcode
cd build-xcode
cmake -G Xcode -DCMAKE_BUILD_TYPE=Debug ..
2. 编译
在编译之前,我们可以用clang-format工具把代码美化一下(示例):
clang-format -i src/*.cpp src/*.h
用ninja进行编译(示例):
ninja
输出log如下(示例):
[4/4] Linking CXX executable src/SimpleParser
3. 运行
运行simplelexer(示例):
src/SimpleParser "with abc,xyz: (abc+xyz)*3 - 10/abc"
我们用于测试的SimpleLang程序代码,只有这么简单的一句而已with abc,xyz: (abc+xyz)*3 - 10/abc
。输出结果如下(示例):
Input: "with abc,xyz: (abc+xyz)*3 - 10/abc"
with variables: abc xyz
----
BinaryOp: -
****
BinaryOp: *
++++
BinaryOp: +
Factor: abc
Factor: xyz
++++
Factor: 3
****
BinaryOp: /
Factor: 10
Factor: abc
----
可以看到,我们的SimpleParser工具把SimpleLang程序代码切分成了一系列的Token,用这些Token构建出了一颗抽象语法树(AST),最后把AST打印了出来。
五、总结
我们参考编译器设计中常用的数据结构定义及算法,基于LLVM提供的API,用C++写了一个很简单的代码语法分析器,并且编译运行成功。完整源码示例请参看:
https://github.com/wuzhanglin/llvm-simple-parser