前言 本章对应官方教程第6章 。在之前的教程中我们为Kaleidoscope实现了一些基本的功能,但现在它有个大问题,那就是没有更多的操作符。所以本章内容展示了如何为让Kaleidoscope支持自定义操作符。
教程如下:
教你使用swift写编译器玩具(0)
教你使用swift写编译器玩具(1)
教你使用swift写编译器玩具(2)
教你使用swift写编译器玩具(3)
教你使用swift写编译器玩具(4)
教你使用swift写编译器玩具(5)
教你使用swift写编译器玩具(6)
教你使用swift写编译器玩具(7)
教你使用swift写编译器玩具(8)
仓库在这
开始 既然我们要支持自定义运算符,那么肯定是需要在函数的处理上提供支持。因为我们需要支持一元运算符和二元运算符,所以需要定义两个token
。他们分别是unary
,用于扩展一元运算符。和binary
,用于扩展二元运算符。
我们举两个例子说明用户自定义操作符的用法。
用于扩展一元运算符的函数写法,扩展了"非"
操作符。
1 2 3 4 5 def unary ! (v) if v then 0 else 1 ;
用于扩展二元运算符的函数写法,扩展了"或"
操作符。
1 2 3 4 5 6 7 def binary | 5 (LHS RHS) if LHS then 1 else if RHS then 1 else 0 ;
Token解析 需要做的第一件还是完善token
的解析。
1 2 3 4 5 6 7 8 9 10 11 12 enum Token { ... case binary case unary ... } else if identifierStr == "binary" { currentToken = CurrentToken (token: .binary, val: "binary" ) } else if identifierStr == "unary" { currentToken = CurrentToken (token: .unary, val: "unary" ) }
扩展AST Node 既然我们是用def
去自定义操作符,那么我们肯定需要改变之前的AST数据结构。
首先我们来看PrototypeAST
的改变。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 enum PrototypeKind : Int { case identifier case unary case binary }class PrototypeAST { let name: String let args: [String ] let isOperator: Bool let precedence: UInt private var isBinaryOp: Bool { return isOperator && args.count == 2 } private var isUnaryOp: Bool { return isOperator && args.count == 1 } var operatorName: String ? { guard isUnaryOp || isOperator else { return nil } return String (Array (name).last! ) } init (_ name: String , _ args: [String ], _ isOperator: Bool = false , _ precedence: UInt = 0 ) { self .name = name self .args = args self .isOperator = isOperator self .precedence = precedence } func codeGen () -> Function { let doubles = Array (repeating: FloatType .double, count: args.count) let ft = FunctionType (doubles, FloatType .double, variadic: false ) var f: Function = theModule.addFunction(name, type: ft) f.linkage = .external var p = f.firstParameter for i in 0 ..< args.count { p? .name = args[i] p = p? .next() } return f } }
紧接着我们需要改变parsePrototype()
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 func parsePrototype () -> PrototypeAST { var fnName: String let kind: PrototypeKind var binaryPrecedence: UInt = 30 switch lexer.currentToken! .token { case .identifier: fnName = lexer.currentToken! .val kind = .identifier lexer.nextToken() break case .binary: lexer.nextToken() guard Array (lexer.currentToken! .val)[0 ].isASCII else { fatalError ("Expected binary operator." ) } fnName = "binary" fnName += lexer.currentToken! .val kind = .binary lexer.nextToken() if lexer.currentToken! .token == .number { let num = UInt (lexer.currentToken! .val)! if num < 1 || num > 100 { fatalError ("Invalid precedence: must be 1...100." ) } binaryPrecedence = num lexer.nextToken() } break default : fatalError ("Expected function name in prototype." ) } if lexer.currentToken! .val != "(" { fatalError ("Expected '(' in prototype" ) } lexer.nextToken() var argNames: [String ] = [] while lexer.currentToken! .token == .identifier { argNames.append(lexer.currentToken! .val) lexer.nextToken() } if lexer.currentToken! .val != ")" { fatalError ("Expected ')' in prototype" ) } lexer.nextToken() if kind != .identifier && kind.rawValue != argNames.count { fatalError ("Invalid number of operands for operator." ) } return PrototypeAST (fnName, argNames, kind.rawValue != 0 , binaryPrecedence) }
其实这里的变化无非就是需要多解析一元表达式和二元表达式这两种情况而已。
解析完AST之后我们还需要支持代码生成,所以我们在BinaryExprAST
中的codeGen()
方法里支持一下。
1 2 3 4 5 6 7 8 9 10 func codeGen () -> IRValue ? { ... let fn = getFunction(named: "binary" + op) guard fn != nil else { fatalError ("\(String(describing: fn)) binary operator not found!" ) } let ops = [l! , r! ] return builder.buildCall(fn! , args: ops, name: "binaryOp" ) }
在FunctionAST
的codeGen()
方法里把自定义操作符放在全局操作符表中。
1 2 3 4 5 6 7 8 func codeGen () -> Function ? { ... if proto.isOperator { BinOpPrecedence [proto.operatorName! ] = proto.precedence } ... }
支持一元运算符 由于之前在Kaleidoscope中不支持一元运算符,所以我们需要新增一个AST Node UnaryExprAST
。
1 2 3 4 5 6 7 8 9 10 11 12 class UnaryExprAST : ExprAST { let op: String let operand: ExprAST init (_ op: String , _ operand: ExprAST ) { self .op = op self .operand = operand } }
接着按照惯例我们在Parser
中添加解析逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private func parseUnaryExpr () -> ExprAST ? { if lexer.currentToken! .val == "(" || lexer.currentToken! .val == "," || Array (lexer.currentToken! .val)[0 ].isLetter || Array (lexer.currentToken! .val)[0 ].isNumber { return parsePrimary() } let op = lexer.currentToken! .val lexer.nextToken() if let operand = parseUnaryExpr() { return UnaryExprAST (op, operand) } return nil }
为了调用这个方法,我们需要改变之前调用parsePrimary()
方法的地方改为调用parseUnaryExpr()
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private func parseBinOpRHS (_ exprPrec: Int, _ lhs: inout ExprAST) -> ExprAST ? { while true { ... var rhs = parseUnaryExpr() guard rhs != nil else { return nil } ... } func parseExpression () -> ExprAST ? { var lhs = parseUnaryExpr() guard lhs != nil else { return nil } return parseBinOpRHS(0 , & lhs! ) }
接着我们为parsePrototype()
方法添加解析支持。
1 2 3 4 5 6 7 8 9 10 11 12 ... case .unary: lexer.nextToken() guard Array (lexer.currentToken! .val)[0 ].isASCII else { fatalError ("Expected unary operator." ) } fnName = "unary" fnName += lexer.currentToken! .val kind = .unary lexer.nextToken() break ...
最后我们实现UnaryExprAST
的codeGen()
方法即可。
1 2 3 4 5 6 7 8 9 10 11 func codeGen () -> IRValue ? { let operandVal = operand.codeGen() guard operandVal != nil else { return nil } let fn = getFunction(named: "unary" + op) guard fn != nil else { fatalError ("Unknow unary operator." ) } return builder.buildCall(fn! , args: [operandVal! ], name: "unaryOp" ) }
测试 一元运算符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 //输入 def unary ! (v) if v then 0 else 1 def testfunc(x ) !x testfunc(1 ) //输出 Read function definition:define i64 @"unary!" (i64 %v ) {entry: %ifCond = icmp eq i64 %v , 0 %. = select i1 %ifCond , i64 1 , i64 0 ret i64 %. } Read function definition:define i64 @testfunc (i64 %x ) {entry: %unaryOp = call i64 @"unary!" (i64 %x ) ret i64 %unaryOp } Read top-level expression:define i64 @__anon_expr () {entry: %call = call i64 @testfunc (i64 1 ) ret i64 %call } Evaluated to 0 .
二元运算符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 //输入 def binary > 10 (LHS RHS) RHS < LHS def testfunc(v) if v > 10 then 1 else 0 testfunc(1 ) //输出 Read function definition:define i64 @"binary>" (i64 %LHS , i64 %RHS ) {entry: %boolCmp = icmp slt i64 %RHS , %LHS %0 = sext i1 %boolCmp to i64 ret i64 %0 } Read function definition:define i64 @testfunc (i64 %v ) {entry: %binaryOp = call i64 @"binary>" (i64 %v , i64 10 ) %ifCond = icmp eq i64 %binaryOp , 0 %. = select i1 %ifCond , i64 0 , i64 1 ret i64 %. } Read top-level expression:define i64 @__anon_expr () {entry: %call = call i64 @testfunc (i64 1 ) ret i64 %call } Evaluated to 0 .