本章對應官方教程第6章。在以前的教程中咱們爲Kaleidoscope實現了一些基本的功能,但如今它有個大問題,那就是沒有更多的操做符。因此本章內容展現瞭如何爲讓Kaleidoscope支持自定義操做符。html
教程以下:git
教你使用swift寫編譯器玩具(0)github
教你使用swift寫編譯器玩具(1)express
教你使用swift寫編譯器玩具(2)swift
既然咱們要支持自定義運算符,那麼確定是須要在函數的處理上提供支持。由於咱們須要支持一元運算符和二元運算符,因此須要定義兩個token
。他們分別是unary
,用於擴展一元運算符。和binary
,用於擴展二元運算符。
咱們舉兩個例子說明用戶自定義操做符的用法。
用於擴展一元運算符的函數寫法,擴展了"非"
操做符。
def unary ! (v)
if v then
0
else
1;
複製代碼
用於擴展二元運算符的函數寫法,擴展了"或"
操做符。
def binary | 5 (LHS RHS)
if LHS then
1
else if RHS then
1
else
0;
複製代碼
須要作的第一件仍是完善token
的解析。
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")
}
複製代碼
既然咱們是用def
去自定義操做符,那麼咱們確定須要改變以前的AST數據結構。
首先咱們來看PrototypeAST
的改變。
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)
//這實際上是默認linkage,這裏爲了和官方教程保持一致,顯示的寫一下
f.linkage = .external
//設置參數名
var p = f.firstParameter
for i in 0..<args.count {
p?.name = args[i]
p = p?.next()
}
return f
}
}
複製代碼
緊接着咱們須要改變parsePrototype()
方法。
/// 解析函數原型
///
/// - Returns: 函數原型AST
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()
//不是ASCII字符不可使用
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()
方法裏支持一下。
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()
方法裏把自定義操做符放在全局操做符表中。
func codeGen() -> Function? {
...
//若是是操做符,把他放在全局的操做符表中
if proto.isOperator {
BinOpPrecedence[proto.operatorName!] = proto.precedence
}
...
}
複製代碼
因爲以前在Kaleidoscope中不支持一元運算符,因此咱們須要新增一個AST Node UnaryExprAST
。
class UnaryExprAST: ExprAST {
let op: String
let operand: ExprAST
init(_ op: String, _ operand: ExprAST) {
self.op = op
self.operand = operand
}
}
複製代碼
接着按照慣例咱們在Parser
中添加解析邏輯。
/// 解析一元表達式
///
/// - Returns: AST
private func parseUnaryExpr() -> ExprAST? {
//當前token不是操做符,那就是基本類型
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()
//這裏須要遞歸的處理一元運算符,好比說 !! x,這裏有兩個!!須要處理
if let operand = parseUnaryExpr() {
return UnaryExprAST(op, operand)
}
return nil
}
複製代碼
爲了調用這個方法,咱們須要改變以前調用parsePrimary()
方法的地方改成調用parseUnaryExpr()
方法。
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()
方法添加解析支持。
...
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()
方法便可。
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")
}
複製代碼
一元運算符
//輸入
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.
複製代碼
二元運算符
//輸入
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.
複製代碼