├── .gitignore ├── LICENSE ├── Package.swift ├── README.md └── Sources └── Kaleidoscope ├── AST.swift ├── IRGenerator.swift ├── Lexer.swift ├── Parser.swift └── main.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "Kaleidoscope", 5 | dependencies: [ 6 | .Package(url: "https://github.com/trill-lang/LLVMSwift.git", majorVersion: 0) 7 | ] 8 | ) 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kaleidoscope 2 | 3 | Kaleidoscope is a language used in tutorials for the 4 | [LLVM Compiler Project](http://llvm.org/docs/tutorial/) 5 | 6 | This repo contains an implementation of the Kaleidoscope compiler in Swift, 7 | as it was presented to Playgrounds Conference on Feb. 23rd, 2017. 8 | 9 | There are a few build instructions you'll need to follow before the 10 | project will build: 11 | 12 | - Install LLVM 3.9 using your favorite package manager. For example: 13 | - `brew install llvm` 14 | - Ensure `llvm-config` is in your `PATH` 15 | - That will reside in the `/bin` folder wherever your package manager 16 | installed LLVM. 17 | - `git clone https://github.com/trill-lang/LLVMSwift.git` 18 | - Run `swift LLVMSwift/utils/make-pkgconfig.swift` 19 | 20 | # Author 21 | 22 | - Harlan Haskins ([@harlanhaskins](https://github.com/harlanhaskins)) 23 | 24 | # License 25 | 26 | This project is released under the MIT license, a copy of which is available 27 | in this repository. 28 | -------------------------------------------------------------------------------- /Sources/Kaleidoscope/AST.swift: -------------------------------------------------------------------------------- 1 | struct Prototype { 2 | let name: String 3 | let params: [String] 4 | } 5 | 6 | struct Definition { 7 | let prototype: Prototype 8 | let expr: Expr 9 | } 10 | 11 | class File { 12 | private(set) var externs = [Prototype]() 13 | private(set) var definitions = [Definition]() 14 | private(set) var expressions = [Expr]() 15 | private(set) var prototypeMap = [String: Prototype]() 16 | 17 | func prototype(name: String) -> Prototype? { 18 | return prototypeMap[name] 19 | } 20 | 21 | func addExpression(_ expression: Expr) { 22 | expressions.append(expression) 23 | } 24 | 25 | func addExtern(_ prototype: Prototype) { 26 | externs.append(prototype) 27 | prototypeMap[prototype.name] = prototype 28 | } 29 | 30 | func addDefinition(_ definition: Definition) { 31 | definitions.append(definition) 32 | prototypeMap[definition.prototype.name] = definition.prototype 33 | } 34 | } 35 | 36 | indirect enum Expr { 37 | case number(Double) 38 | case variable(String) 39 | case binary(Expr, BinaryOperator, Expr) 40 | case ifelse(Expr, Expr, Expr) 41 | case call(String, [Expr]) 42 | } 43 | -------------------------------------------------------------------------------- /Sources/Kaleidoscope/IRGenerator.swift: -------------------------------------------------------------------------------- 1 | import LLVM 2 | 3 | enum IRError: Error, CustomStringConvertible { 4 | case unknownFunction(String) 5 | case unknownVariable(String) 6 | case wrongNumberOfArgs(String, expected: Int, got: Int) 7 | 8 | var description: String { 9 | switch self { 10 | case .unknownFunction(let name): 11 | return "unknown function '\(name)'" 12 | case .unknownVariable(let name): 13 | return "unknwon variable '\(name)'" 14 | case .wrongNumberOfArgs(let name, let expected, let got): 15 | return "call to function '\(name)' with \(got) arguments (expected \(expected))" 16 | } 17 | } 18 | } 19 | 20 | class IRGenerator { 21 | let module: Module 22 | let builder: IRBuilder 23 | let file: File 24 | 25 | private var parameterValues = [String: IRValue]() 26 | 27 | init(moduleName: String = "main", file: File) { 28 | self.module = Module(name: moduleName) 29 | self.builder = IRBuilder(module: module) 30 | self.file = file 31 | } 32 | 33 | func emit() throws { 34 | for extern in file.externs { 35 | emitPrototype(extern) 36 | } 37 | for definition in file.definitions { 38 | try emitDefinition(definition) 39 | } 40 | try emitMain() 41 | } 42 | 43 | func emitPrintf() -> Function { 44 | if let function = module.function(named: "printf") { return function } 45 | let printfType = FunctionType(argTypes: [PointerType(pointee: IntType.int8)], 46 | returnType: IntType.int32, 47 | isVarArg: true) 48 | return builder.addFunction("printf", type: printfType) 49 | } 50 | 51 | func emitMain() throws { 52 | let mainType = FunctionType(argTypes: [], returnType: VoidType()) 53 | let function = builder.addFunction("main", type: mainType) 54 | let entry = function.appendBasicBlock(named: "entry") 55 | builder.positionAtEnd(of: entry) 56 | 57 | let formatString = builder.buildGlobalStringPtr("%f\n") 58 | let printf = emitPrintf() 59 | 60 | for expr in file.expressions { 61 | let val = try emitExpr(expr) 62 | builder.buildCall(printf, args: [formatString, val]) 63 | } 64 | 65 | builder.buildRetVoid() 66 | } 67 | 68 | @discardableResult // declare double @foo(double %n, double %m) 69 | func emitPrototype(_ prototype: Prototype) -> Function { 70 | if let function = module.function(named: prototype.name) { 71 | return function 72 | } 73 | let argTypes = [IRType](repeating: FloatType.double, 74 | count: prototype.params.count) 75 | let funcType = FunctionType(argTypes: argTypes, 76 | returnType: FloatType.double) 77 | let function = builder.addFunction(prototype.name, type: funcType) 78 | 79 | for (var param, name) in zip(function.parameters, prototype.params) { 80 | param.name = name 81 | } 82 | 83 | return function 84 | } 85 | 86 | @discardableResult 87 | func emitDefinition(_ definition: Definition) throws -> Function { 88 | let function = emitPrototype(definition.prototype) 89 | 90 | for (idx, arg) in definition.prototype.params.enumerated() { 91 | let param = function.parameter(at: idx)! 92 | parameterValues[arg] = param 93 | } 94 | 95 | let entryBlock = function.appendBasicBlock(named: "entry") 96 | builder.positionAtEnd(of: entryBlock) 97 | 98 | let expr = try emitExpr(definition.expr) 99 | builder.buildRet(expr) 100 | 101 | parameterValues.removeAll() 102 | 103 | return function 104 | } 105 | 106 | func emitExpr(_ expr: Expr) throws -> IRValue { 107 | switch expr { 108 | case .variable(let name): 109 | guard let param = parameterValues[name] else { 110 | throw IRError.unknownVariable(name) 111 | } 112 | return param 113 | case .number(let value): 114 | return FloatType.double.constant(value) 115 | case .binary(let lhs, let op, let rhs): 116 | let lhsVal = try emitExpr(lhs) 117 | let rhsVal = try emitExpr(rhs) 118 | switch op { 119 | case .plus: 120 | return builder.buildAdd(lhsVal, rhsVal) 121 | case .minus: 122 | return builder.buildSub(lhsVal, rhsVal) 123 | case .divide: 124 | return builder.buildDiv(lhsVal, rhsVal) 125 | case .times: 126 | return builder.buildMul(lhsVal, rhsVal) 127 | case .mod: 128 | return builder.buildRem(lhsVal, rhsVal) 129 | case .equals: 130 | let comparison = builder.buildFCmp(lhsVal, rhsVal, 131 | .orderedEqual) 132 | return builder.buildIntToFP(comparison, 133 | type: FloatType.double, 134 | signed: false) 135 | } 136 | case .call(let name, let args): 137 | guard let prototype = file.prototype(name: name) else { 138 | throw IRError.unknownFunction(name) 139 | } 140 | guard prototype.params.count == args.count else { 141 | throw IRError.wrongNumberOfArgs(name, 142 | expected: prototype.params.count, 143 | got: args.count) 144 | } 145 | let callArgs = try args.map(emitExpr) 146 | let function = emitPrototype(prototype) 147 | return builder.buildCall(function, args: callArgs) 148 | case .ifelse(let cond, let thenExpr, let elseExpr): 149 | let checkCond = builder.buildFCmp(try emitExpr(cond), 150 | FloatType.double.constant(0.0), 151 | .orderedNotEqual) 152 | 153 | let thenBB = builder.currentFunction!.appendBasicBlock(named: "then") 154 | let elseBB = builder.currentFunction!.appendBasicBlock(named: "else") 155 | let mergeBB = builder.currentFunction!.appendBasicBlock(named: "merge") 156 | 157 | builder.buildCondBr(condition: checkCond, then: thenBB, else: elseBB) 158 | 159 | builder.positionAtEnd(of: thenBB) 160 | let thenVal = try emitExpr(thenExpr) 161 | builder.buildBr(mergeBB) 162 | 163 | builder.positionAtEnd(of: elseBB) 164 | let elseVal = try emitExpr(elseExpr) 165 | builder.buildBr(mergeBB) 166 | 167 | builder.positionAtEnd(of: mergeBB) 168 | 169 | let phi = builder.buildPhi(FloatType.double) 170 | phi.addIncoming([(thenVal, thenBB), (elseVal, elseBB)]) 171 | 172 | return phi 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /Sources/Kaleidoscope/Lexer.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import Darwin 3 | #elseif os(Linux) 4 | import Glibc 5 | #endif 6 | 7 | enum BinaryOperator: UnicodeScalar { 8 | case plus = "+", minus = "-", 9 | times = "*", divide = "/", 10 | mod = "%", equals = "=" 11 | } 12 | 13 | enum Token: Equatable { 14 | case leftParen, rightParen, def, extern, comma, semicolon, `if`, then, `else` 15 | case identifier(String) 16 | case number(Double) 17 | case `operator`(BinaryOperator) 18 | 19 | static func ==(lhs: Token, rhs: Token) -> Bool { 20 | switch (lhs, rhs) { 21 | case (.leftParen, .leftParen), (.rightParen, .rightParen), 22 | (.def, .def), (.extern, .extern), (.comma, .comma), 23 | (.semicolon, .semicolon), (.if, .if), (.then, .then), 24 | (.else, .else): 25 | return true 26 | case let (.identifier(id1), .identifier(id2)): 27 | return id1 == id2 28 | case let (.number(n1), .number(n2)): 29 | return n1 == n2 30 | case let (.operator(op1), .operator(op2)): 31 | return op1 == op2 32 | default: 33 | return false 34 | } 35 | } 36 | } 37 | 38 | extension Character { 39 | var value: Int32 { 40 | return Int32(String(self).unicodeScalars.first!.value) 41 | } 42 | var isSpace: Bool { 43 | return isspace(value) != 0 44 | } 45 | var isAlphanumeric: Bool { 46 | return isalnum(value) != 0 || self == "_" 47 | } 48 | } 49 | 50 | class Lexer { 51 | let input: String 52 | var index: String.Index 53 | 54 | init(input: String) { 55 | self.input = input 56 | self.index = input.startIndex 57 | } 58 | 59 | var currentChar: Character? { 60 | return index < input.endIndex ? input[index] : nil 61 | } 62 | 63 | func advanceIndex() { 64 | input.characters.formIndex(after: &index) 65 | } 66 | 67 | func readIdentifierOrNumber() -> String { 68 | var str = "" 69 | while let char = currentChar, char.isAlphanumeric || char == "." { 70 | str.characters.append(char) 71 | advanceIndex() 72 | } 73 | return str 74 | } 75 | 76 | func advanceToNextToken() -> Token? { 77 | // Skip all spaces until a non-space token 78 | while let char = currentChar, char.isSpace { 79 | advanceIndex() 80 | } 81 | // If we hit the end of the input, then we're done 82 | guard let char = currentChar else { 83 | return nil 84 | } 85 | 86 | // Handle single-scalar tokens, like comma, 87 | // leftParen, rightParen, and the operators 88 | let singleTokMapping: [Character: Token] = [ 89 | ",": .comma, "(": .leftParen, ")": .rightParen, 90 | ";": .semicolon, "+": .operator(.plus), "-": .operator(.minus), 91 | "*": .operator(.times), "/": .operator(.divide), 92 | "%": .operator(.mod), "=": .operator(.equals) 93 | ] 94 | 95 | if let tok = singleTokMapping[char] { 96 | advanceIndex() 97 | return tok 98 | } 99 | 100 | // This is where we parse identifiers or numbers 101 | // We're going to use Swift's built-in double parsing 102 | // logic here. 103 | if char.isAlphanumeric { 104 | let str = readIdentifierOrNumber() 105 | 106 | if let dbl = Double(str) { 107 | return .number(dbl) 108 | } 109 | 110 | // Look for known tokens, otherwise fall back to 111 | // the identifier token 112 | switch str { 113 | case "def": return .def 114 | case "extern": return .extern 115 | case "if": return .if 116 | case "then": return .then 117 | case "else": return .else 118 | default: return .identifier(str) 119 | } 120 | } 121 | return nil 122 | } 123 | 124 | func lex() -> [Token] { 125 | var toks = [Token]() 126 | while let tok = advanceToNextToken() { 127 | toks.append(tok) 128 | } 129 | return toks 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /Sources/Kaleidoscope/Parser.swift: -------------------------------------------------------------------------------- 1 | enum ParseError: Error { 2 | case unexpectedToken(Token) 3 | case unexpectedEOF 4 | } 5 | 6 | class Parser { 7 | let tokens: [Token] 8 | var index = 0 9 | 10 | init(tokens: [Token]) { 11 | self.tokens = tokens 12 | } 13 | 14 | var currentToken: Token? { 15 | return index < tokens.count ? tokens[index] : nil 16 | } 17 | 18 | func consumeToken(n: Int = 1) { 19 | index += n 20 | } 21 | 22 | func parseFile() throws -> File { 23 | let file = File() 24 | while let tok = currentToken { 25 | switch tok { 26 | case .extern: 27 | file.addExtern(try parseExtern()) 28 | case .def: 29 | file.addDefinition(try parseDefinition()) 30 | default: 31 | let expr = try parseExpr() 32 | try consume(.semicolon) 33 | file.addExpression(expr) 34 | } 35 | } 36 | return file 37 | } 38 | 39 | func parseExpr() throws -> Expr { 40 | guard let token = currentToken else { 41 | throw ParseError.unexpectedEOF 42 | } 43 | var expr: Expr 44 | switch token { 45 | case .leftParen: // ( ) 46 | consumeToken() 47 | expr = try parseExpr() 48 | try consume(.rightParen) 49 | case .number(let value): 50 | consumeToken() 51 | expr = .number(value) 52 | case .identifier(let value): 53 | consumeToken() 54 | if case .leftParen? = currentToken { 55 | let params = try parseCommaSeparated(parseExpr) 56 | expr = .call(value, params) 57 | } else { 58 | expr = .variable(value) 59 | } 60 | case .if: // if then else 61 | consumeToken() 62 | let cond = try parseExpr() 63 | try consume(.then) 64 | let thenVal = try parseExpr() 65 | try consume(.else) 66 | let elseVal = try parseExpr() 67 | expr = .ifelse(cond, thenVal, elseVal) 68 | default: 69 | throw ParseError.unexpectedToken(token) 70 | } 71 | 72 | if case .operator(let op)? = currentToken { 73 | consumeToken() 74 | let rhs = try parseExpr() 75 | expr = .binary(expr, op, rhs) 76 | } 77 | 78 | return expr 79 | } 80 | 81 | func consume(_ token: Token) throws { 82 | guard let tok = currentToken else { 83 | throw ParseError.unexpectedEOF 84 | } 85 | guard token == tok else { 86 | throw ParseError.unexpectedToken(token) 87 | } 88 | consumeToken() 89 | } 90 | 91 | func parseIdentifier() throws -> String { 92 | guard let token = currentToken else { 93 | throw ParseError.unexpectedEOF 94 | } 95 | guard case .identifier(let name) = token else { 96 | throw ParseError.unexpectedToken(token) 97 | } 98 | consumeToken() 99 | return name 100 | } 101 | 102 | func parsePrototype() throws -> Prototype { 103 | let name = try parseIdentifier() 104 | let params = try parseCommaSeparated(parseIdentifier) 105 | return Prototype(name: name, params: params) 106 | } 107 | 108 | func parseCommaSeparated(_ parseFn: () throws -> TermType) throws -> [TermType] { 109 | try consume(.leftParen) 110 | var vals = [TermType]() 111 | while let tok = currentToken, tok != .rightParen { 112 | let val = try parseFn() 113 | if case .comma? = currentToken { 114 | try consume(.comma) 115 | } 116 | vals.append(val) 117 | } 118 | try consume(.rightParen) 119 | return vals 120 | } 121 | 122 | func parseExtern() throws -> Prototype { 123 | try consume(.extern) 124 | let proto = try parsePrototype() 125 | try consume(.semicolon) 126 | return proto 127 | } 128 | 129 | func parseDefinition() throws -> Definition { 130 | try consume(.def) 131 | let prototype = try parsePrototype() 132 | let expr = try parseExpr() 133 | let def = Definition(prototype: prototype, expr: expr) 134 | try consume(.semicolon) 135 | return def 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Sources/Kaleidoscope/main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LLVM 3 | 4 | extension String: Error {} 5 | 6 | typealias KSMainFunction = @convention(c) () -> Void 7 | 8 | do { 9 | guard CommandLine.arguments.count > 1 else { 10 | throw "usage: kaleidoscope " 11 | } 12 | 13 | let path = URL(fileURLWithPath: CommandLine.arguments[1]) 14 | let input = try String(contentsOf: path, encoding: .utf8) 15 | let toks = Lexer(input: input).lex() 16 | let file = try Parser(tokens: toks).parseFile() 17 | let irGen = IRGenerator(file: file) 18 | try irGen.emit() 19 | try irGen.module.verify() 20 | let llPath = path.deletingPathExtension().appendingPathExtension("ll") 21 | if FileManager.default.fileExists(atPath: llPath.path) { 22 | try FileManager.default.removeItem(at: llPath) 23 | } 24 | FileManager.default.createFile(atPath: llPath.path, contents: nil) 25 | try irGen.module.print(to: llPath.path) 26 | print("Successfully wrote LLVM IR to \(llPath.lastPathComponent)") 27 | 28 | 29 | let objPath = path.deletingPathExtension().appendingPathExtension("o") 30 | if FileManager.default.fileExists(atPath: objPath.path) { 31 | try FileManager.default.removeItem(at: objPath) 32 | } 33 | 34 | let targetMachine = try TargetMachine() 35 | try targetMachine.emitToFile(module: irGen.module, 36 | type: .object, 37 | path: objPath.path) 38 | print("Successfully wrote binary object file to \(objPath.lastPathComponent)") 39 | 40 | } catch { 41 | print("error: \(error)") 42 | exit(-1) 43 | } 44 | --------------------------------------------------------------------------------