专栏名称: QYizhong
目录
相关文章推荐
爱否科技  ·  【广告】iPhone 16 Pro Max ... ·  3 天前  
爱否科技  ·  三星 Galaxy S25 Edge ... ·  3 天前  
爱否科技  ·  【广告】iPhone 16 Pro Max ... ·  4 天前  
爱否科技  ·  vivo Y300i ... ·  2 天前  
CINNO  ·  深紫外Micro ... ·  5 天前  
51好读  ›  专栏  ›  QYizhong

教你使用swift写编译器玩具(2)

QYizhong  · 掘金  ·  · 2019-08-07 17:38

正文

阅读 6

教你使用swift写编译器玩具(2)

前言

本章对应 官方教程第2章 ,介绍实现解析器(Parser)和抽象语法树(AST)。

开始

AST

AST用来解释代码的行为,我们希望语言中的每个构造都有一个AST,所以我们首先需要一个AST的基类,在swift中我们可以使用 protocol

protocol ExprAST {}
复制代码

注意,在Kaleidoscope中我们只支持Double类型,所以首先我们需要有一个保存数值的AST。

class NumberExprAST: ExprAST {
    
    let value: Double
    
    init(_ value: Double) {
        self.value = value
    }
    
}
复制代码

用于保存变量名的AST,比如说保存"abc"。

class VariableExprAST: ExprAST {
    
    let name: String
    
    init(_ name: String) {
        self.name = name
    }
    
}
复制代码

用于保存二元运算符的AST,比如"+"。

class BinaryExprAST: ExprAST {
    
    let op: String
    
    let lhs: ExprAST
    
    let rhs: ExprAST
    
    init(_ op: String, _ lhs: ExprAST, _ rhs: ExprAST) {
        self.op = op
        self.lhs = lhs
        self.rhs = rhs
    }
    
}
复制代码

因为这个类适用于二元运算符,所以AST需要记录操作符左边的AST(lhs)以及右边的AST(rhs)以及操作符的名字。

函数的AST包括原型AST( PrototypeAST )、函数定义AST( FunctionAST )和函数调用AST( CallExprAST )。原型AST即函数的声明。

class PrototypeAST {
    
    let name: String
    
    let args: [String]
    
    init(_ name: String, _ args: [String]) {
        self.name = name
        self.args = args
    }
    
}
复制代码

PrototypeAST需要保存函数名以及参数名。

class FunctionAST {
    
    let proto: PrototypeAST
    
    let body: ExprAST
    
    init(_ proto: PrototypeAST, _ body: ExprAST) {
        self.proto = proto
        self.body = body
    }
    
}
复制代码

FunctionAST保存函数声明的AST proto和函数定义的AST body。

class CallExprAST: ExprAST {
    
    var callee: String?
    
    var args: [ExprAST]?
    
    init(_ callee: String, _ args: [ExprAST]) {
        self.callee = callee
        self.args = args
    }
    
}
复制代码

CallExprAST用来解析函数的调用。

开始解析

在上一章中,我们已经实现了一个可以解析出token的lexer。下面我们需要完善Lexer并且实现Parser,它用来解析出AST。

我们为Lexer添加输入方法以及代理方法提供给Parser。

    /// 解析代码
    ///
    /// - Parameter sourceInput: 代码数据源
    public func start(_ sourceInput: String) {
        let blockArray = sourceInput.split(separator: ";")
        for block in blockArray {
            source = Array(block + ";")
            index = 0
            lastChar = " "
            nextToken()
            switch currentToken!.token {
            case .def:
                delegate?.lexerWithDefinition(self)
                continue
            case .extern:
                delegate?.lexerWithExtern(self)
                continue
            case .number, .identifier:
                delegate?.lexerWithTopLevelExpression(self)
                continue
            default:
                continue
            }
        }
    }
复制代码

因为Kaleidoscope用";"分割代码块,为了方便处理我们就可以直接根据";"进行分块解析即可。

接下来我们定义Parser。

class Parser {
    
    private let lexer = Lexer()
    
    init() {
        lexer.delegate = self
    }
    
}

extension Parser {
    
    /// 解析代码
    ///
    /// - Parameter sourceInput: 代码数据源
    public func parse(_ sourceInput: String) {
        lexer.start(sourceInput)
    }
    
}
复制代码

解析基本表达式

我们从解析数字开始。

    /// 解析数值常量
    ///
    /// - Returns: AST
    private func parseNumberExpr() -> ExprAST {
        let result = NumberExprAST(Double(lexer.currentToken!.val)!)
        lexer.nextToken()
        return result
    }
复制代码

这个其实看代码毫无疑问,生成NumberAST完获取下一个token即可。

解析 "'(' expression ')'" 形式的表达式。

    /// 解析'('开头的表达式
    ///
    /// - Returns: AST
    func parseParenExpr() -> ExprAST? {
        lexer.nextToken()//跳过'('
        let v = parseExpression()
        guard v != nil else {
            return nil
        }
        if lexer.currentToken!.val != ")" {
            fatalError("Expected '\(lexer.currentToken!.val)'")
        }
        lexer.nextToken()//跳过')'
        return v
    }
复制代码

parseExpression()方法将会在下面介绍到。

解析变量或者函数调用。

    /// 解析变量引用和函数调用
    ///
    /// - Returns: AST
    private func parseIdentifierExpr() -> ExprAST? {
        let idName = lexer.currentToken!.val
        lexer.nextToken()
        if lexer.currentToken!.val != "(" {
          	//说明只是普通的变量
            return VariableExprAST(idName)
        }
      	//走到这说明是函数调用
        lexer.nextToken()
        var args: [ExprAST] = []
        if lexer.currentToken!.val != ")" {
          	//这个循环用来解析传入参数
            while true {
                let arg = parseExpression()
                guard arg != nil else {
                    return nil
                }
                args.append(arg!)
              	//匹配到")"说明解析该结束了
                if lexer.currentToken!.val == ")" {
                    break
                }
              	//不同参数之间用","分割
                if lexer.currentToken!.val != "," {
                    fatalError("Expected ')' or ',' in argument list"






请到「今天看啥」查看全文