LLVM系列第十三章:写一个简单的语法分析器Parser

本文介绍了如何基于LLVM实现一个简单的语法分析器,包括词法分析、抽象语法树构建和Parser的实现过程。通过实例展示了如何构造SimpleLang语言的解析器,以及如何使用C++和LLVM库进行编译和运行测试。
摘要由CSDN通过智能技术生成

系列文章目录

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)

flex&bison系列



前言

在此记录下,基于LLVM写一个简单的语法分析器(Simple Parser)的过程,以备查阅。

开发环境的配置请参考 《LLVM系列第一章:编译LLVM源码》。

我们再来简单复习一下,编译器前端的流程:

词法分析Lexical
语法分析Syntax
语义分析Semantic
中间代码IR

更多关于编译器前端的介绍,请参看《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

各文件的内容大体如下:

  1. AST.h,SimpleLang语言的抽象语法树(AST)的定义及实现代码
  2. Lexer.h和Lexer.cpp,SimpleLang语言的词法分析器(Lexer)的定义及实现代码
  3. Parser.h和Parser.cpp,SimpleLang语言的语法分析器(Parser)的定义及实现代码
  4. ParserPlayer.cpp,main函数,即Parser的测试代码

三、项目细节

1. 程序模块

这个简单的项目只包含了一个模块:

  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)上:

  1. src/ParserPlayer.cpp,包含了main函数,及测试代码
  2. src/Parser.h,src/Parser.cpp,包含了语法分析器(Parser)的定义及实现代码
  3. 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)中所有类型的节点的定义,看起来也像一棵树:

AST
+Accept(ASTVisitor& visitor)
WithDeclaration
+Accept(ASTVisitor& visitor)
Expr
+Accept(ASTVisitor& visitor)
Factor
+Accept(ASTVisitor& visitor)
BinaryOp
+Accept(ASTVisitor& visitor)

这里用到了经典的设计模式“Visitor模式”,ASTVisitor类的定义如下:

ASTVisitor
+Visit(AST&)
+Visit(Expr&)
+Visit(Factor&)
+Visit(BinaryOp&)
+Visit(WithDeclaration&)
ASTPrinter
+Visit(Factor&)
+Visit(BinaryOp&)
+Visit(WithDeclaration&)

注意,我们把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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值