前言 本章对应官方教程第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 .