├── .gitignore ├── LICENSE.markdown ├── Lispy.swift ├── README.markdown ├── stdlib.lispy └── test.lispy /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | build/ 3 | DerivedData/ 4 | 5 | *.pbxuser 6 | *.mode1v3 7 | *.mode2v3 8 | *.perspectivev3 9 | *.xccheckout 10 | *.moved-aside 11 | *.xcworkspace 12 | *.xcuserstate 13 | 14 | xcuserdata 15 | 16 | !default.pbxuser 17 | !default.mode1v3 18 | !default.mode2v3 19 | !default.perspectivev3 20 | !default.xcworkspace 21 | 22 | profile 23 | *.hmap 24 | *.ipa 25 | 26 | # CocoaPods 27 | Pods/ 28 | !Podfile.lock 29 | 30 | # Temporary files 31 | .DS_Store 32 | .Trashes 33 | .Spotlight-V100 34 | *.swp 35 | *.lock 36 | -------------------------------------------------------------------------------- /LICENSE.markdown: -------------------------------------------------------------------------------- 1 | Licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 2 | 3 | http://creativecommons.org/licenses/by-nc-sa/3.0/ 4 | -------------------------------------------------------------------------------- /Lispy.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /* 4 | A conversion of www.buildyourownlisp.com into Swift. 5 | */ 6 | 7 | // MARK: - Values 8 | 9 | /* 10 | Everything in our LISP is about manipulating values. 11 | 12 | This version of LISP is dynamically typed, meaning that variables do not have 13 | a specific type, they are just names. Only values have a type. 14 | 15 | The Value enum describes a value, its type, and the data it holds. 16 | */ 17 | 18 | typealias Builtin = (_ env: Environment, _ values: [Value]) -> Value 19 | 20 | enum Value { 21 | // When an error has occurred, we create and return an error value. 22 | case error(message: String) 23 | 24 | // An integer number. Also used for boolean values, where 0 is false, != 0 is 25 | // true. Currently there is no support for real numbers (i.e. Float or Double). 26 | case integer(value: Int) 27 | 28 | // A text string. 29 | case text(value: String) 30 | 31 | // A symbol is a name that you can bind to some other value. This is what you 32 | // use to create variables and named fuctions. 33 | case symbol(name: String) 34 | 35 | // An S-Expression is a piece of executable code. Example: (+ 1 2). Only code 36 | // in between ( ) parentheses is evaluated. 37 | case SExpression(values: [Value]) 38 | 39 | // A Q-Expression is a list of literal data items. Example: {1 2 3}. The curly 40 | // brackets turn code into data; the 'eval' function turns a Q-Expression back 41 | // into code. 42 | case QExpression(values: [Value]) 43 | 44 | // The built-in functions are the primitive operations of the language. These 45 | // are too low-level to express in LISP itself. 46 | case builtinFunction(name: String, code: Builtin) 47 | 48 | // A lambda is a user-defined function. Example: \ {x y} {+ x y}. Usually you 49 | // bind this to a name with 'def' so that you can use it more than once. 50 | // 51 | // The formal parameters, {x y} in the example, are stored as a string array, 52 | // ["x", "y"]. The body, {+ x y}, is stored as an array of Values. This is 53 | // more convenient than storing the original Q-Expressions, because we have 54 | // to convert those to arrays anyway. The body is turned into an S-Expression 55 | // when the function gets evaluated. 56 | // 57 | // The Environment object is needed for partial function application, because 58 | // it has the values of the parameters that have been filled in already. 59 | case lambda(env: Environment, formals: [String], body: [Value]) 60 | } 61 | 62 | // MARK: - Creating values 63 | 64 | /* 65 | Normally you'd first parse LISP code into an AST (Abstract Syntax Tree) and 66 | then evaluate that tree. However, you can also create such an AST directly in 67 | Swift by writing: 68 | 69 | // Create the AST for the S-Expression (+ 123 456) 70 | let v1 = Value.symbol(name: "+") 71 | let v2 = Value.integer(value: "123") 72 | let v3 = Value.integer(value: "456") 73 | let v4 = Value.SExpression(values: [v1, v2, v3]) 74 | // And evaluate it... 75 | let result = v4.eval(env) 76 | 77 | However, thanks to the below LiteralConvertible extensions, you can simply 78 | write this as: 79 | 80 | let v = Value.SExpression(values: ["+", 123, 456]) 81 | let result = v.eval(env) 82 | 83 | Swift automatically figures out that "+" is a Symbol and that 123 and 456 are 84 | Integer values. 85 | 86 | It's not super useful, because you almost never need to create an AST by hand 87 | but it's nice for when you want to test something without using the parser. 88 | */ 89 | 90 | // Allows you to write true instead of Value.integer(1); false becomes Value(0). 91 | extension Value: ExpressibleByBooleanLiteral { 92 | init(booleanLiteral value: Bool) { 93 | self = .integer(value: value ? 1 : 0) 94 | } 95 | } 96 | 97 | // Allows you to write 123 instead of Value.integer(123). 98 | extension Value: ExpressibleByIntegerLiteral { 99 | typealias IntegerLiteralType = Int 100 | init(integerLiteral value: IntegerLiteralType) { 101 | self = .integer(value: value) 102 | } 103 | } 104 | 105 | // Allows you to write "A" instead of Value.symbol("A"). 106 | extension Value: ExpressibleByStringLiteral { 107 | typealias StringLiteralType = String 108 | init(stringLiteral value: StringLiteralType) { 109 | self = .symbol(name: value) 110 | } 111 | } 112 | 113 | // Turns an array of values into a Q-Expression. 114 | extension Value: ExpressibleByArrayLiteral { 115 | typealias Element = Value 116 | init(arrayLiteral elements: Element...) { 117 | self = .QExpression(values: elements) 118 | } 119 | } 120 | 121 | // Allows you to write nil to create an empty Q-Expression. 122 | extension Value: ExpressibleByNilLiteral { 123 | init(nilLiteral: ()) { 124 | self = .QExpression(values: []) 125 | } 126 | } 127 | 128 | extension Value { 129 | // Convenience method for returning an empty S-Expression. 130 | static func empty() -> Value { 131 | return .SExpression(values: []) 132 | } 133 | } 134 | 135 | // MARK: - Comparing values 136 | 137 | /* 138 | Values are Equatable, because we must be able to compare them using the '==' 139 | operator, both in Swift as in the LISP language itself. 140 | */ 141 | 142 | extension Value: Equatable { } 143 | 144 | func ==(lhs: Value, rhs: Value) -> Bool { 145 | switch (lhs, rhs) { 146 | case (.error(let message1), .error(let message2)): 147 | return message1 == message2 148 | case (.integer(let value1), .integer(let value2)): 149 | return value1 == value2 150 | case (.text(let value1), .text(let value2)): 151 | return value1 == value2 152 | case (.symbol(let name1), .symbol(let name2)): 153 | return name1 == name2 154 | case (.SExpression(let values1), .SExpression(let values2)): 155 | return values1 == values2 156 | case (.QExpression(let values1), .QExpression(let values2)): 157 | return values1 == values2 158 | case (.builtinFunction(let name1, _), .builtinFunction(let name2, _)): 159 | return name1 == name2 160 | case (.lambda(_, let formals1, let body1), .lambda(_, let formals2, let body2)): 161 | return formals1 == formals2 && body1 == body2 162 | default: 163 | return false 164 | } 165 | } 166 | 167 | // MARK: - Printing values 168 | 169 | /* 170 | The functions in this String extension add and remove escape codes such as 171 | \n and \t. They are quick-and-dirty placeholders for a better implementation. 172 | */ 173 | 174 | extension String { 175 | // Adds escape codes for unprintable characters. Used when printing in the 176 | // REPL and for showing help. In those cases we want to show the text with 177 | // unprintable characters represented by escape codes, just as you'd write 178 | // a string literal in source code. 179 | func escaped() -> String { 180 | var out = "" 181 | for c in self { 182 | switch c { 183 | case "\n": out += "\\n" 184 | case "\t": out += "\\t" 185 | case "\\": out += "\\\\" 186 | default: out += "\(c)" 187 | } 188 | } 189 | return out 190 | } 191 | 192 | // Turns "\n" "\t" and so on into actual characters. Used during parsing. 193 | func unescaped() -> String { 194 | var out = "" 195 | var i = startIndex 196 | while i < endIndex { 197 | let c = self[i] 198 | i = index(after: i) 199 | if c == "\\" && i < endIndex { 200 | switch self[i] { 201 | case "n": out += "\n" 202 | case "t": out += "\t" 203 | case "\\": out += "\\" 204 | default: out += "\(self[i])" 205 | } 206 | i = index(after: i) 207 | } else { 208 | out += "\(c)" 209 | } 210 | } 211 | return out 212 | } 213 | } 214 | 215 | /* 216 | We often need to print Value objects. 217 | 218 | The REPL shows the result of every expression it evaluates, which again is a 219 | Value. It uses the debugDescription for that. (This only happens in the REPL; 220 | we don't print the evaluation results when executing a source file.) 221 | 222 | The debug description is also used to print help information about named 223 | objects and the current environment. 224 | 225 | The regular, non-debug description is used with the 'print' built-in function. 226 | It shows the real value of the object, without any extra fluff. 227 | */ 228 | 229 | extension Value: CustomStringConvertible, CustomDebugStringConvertible { 230 | var description: String { 231 | switch self { 232 | case .text(let value): 233 | return "\(value)" 234 | default: 235 | return debugDescription 236 | } 237 | } 238 | 239 | var debugDescription: String { 240 | switch self { 241 | case .error(let message): 242 | return "Error: \(message)" 243 | case .integer(let value): 244 | return "\(value)" 245 | case .text(let value): 246 | return "\"\(value.escaped())\"" 247 | case .symbol(let name): 248 | return name 249 | case .SExpression(let values): 250 | return "(" + listToString(values) + ")" 251 | case .QExpression(let values): 252 | return "{" + listToString(values) + "}" 253 | case .builtinFunction(let name, _): 254 | return "<\(name)>" 255 | case .lambda(let env, let formals, let body): 256 | var s = "(\\ {\(listToString(formals))} {\(listToString(body))})" 257 | 258 | // If this lambda is a partially applied function, then also print the 259 | // values of the parameters that have been filled in already. 260 | if !env.defs.isEmpty { 261 | for (k, v) in env.defs { 262 | s += " \(k)=\(v.debugDescription)" 263 | } 264 | } 265 | return s 266 | } 267 | } 268 | 269 | private func listToString(_ values: [String]) -> String { 270 | return values.joined(separator: " ") 271 | } 272 | 273 | private func listToString(_ values: [Value]) -> String { 274 | return values.map({ $0.debugDescription }).joined(separator: " ") 275 | } 276 | 277 | var typeName: String { 278 | switch self { 279 | case .error: return "Error" 280 | case .integer: return "Integer" 281 | case .text: return "String" 282 | case .symbol: return "Symbol" 283 | case .SExpression: return "S-Expression" 284 | case .QExpression: return "Q-Expression" 285 | case .builtinFunction: return "Built-in Function" 286 | case .lambda: return "Lambda" 287 | } 288 | } 289 | } 290 | 291 | // MARK: - Environment 292 | 293 | /* 294 | We often want to bind a value to a name, for example to make a variable or to 295 | turn an anonymous lambda into a reusable function. 296 | 297 | These names and their associated values are stored in the environment. 298 | 299 | When a LISP program tries to evaluate a symbol, it looks up that name in the 300 | environment and uses the associated value. It gives a .error value if the name 301 | is not found. 302 | 303 | There is one "global" environment, which exists for the duration of the LISP 304 | program. This contains definitions for all the built-in functions and those 305 | from the standard library. When you use the 'def' command, it adds a new name 306 | and value to this global environment. 307 | 308 | When a function or lambda is evaluated, it is given its own Environment object. 309 | This contains the values for the function's formal parameters, and any local 310 | names you defined with the '=' command. This "local" environment has a link to 311 | the global one through its parent property. 312 | 313 | A cool feature of this LISP is that you're allowed to invoke a function but 314 | not supply it all of its parameters, known as "partial function application". 315 | The result is a new Lambda value that has an Environment with the values for 316 | the parameters you already supplied. 317 | */ 318 | 319 | class Environment { 320 | private(set) var defs = [String: Value]() 321 | private(set) var docs = [String: String]() 322 | 323 | var parent: Environment? 324 | 325 | // Follows the parent references up until we get the global environment. 326 | func globalEnvironment() -> Environment { 327 | var env = self 328 | while case let parent? = env.parent { env = parent } 329 | return env 330 | } 331 | 332 | // Making a copy is necessary for partial function application and recursion, 333 | // because Environment is a reference type, not a value type. 334 | func copy() -> Environment { 335 | let e = Environment() 336 | e.defs = defs 337 | e.parent = parent 338 | return e 339 | } 340 | } 341 | 342 | // These methods add and retrieve values from the environment. 343 | extension Environment { 344 | func get(_ name: String) -> Value { 345 | if let value = defs[name] { 346 | return value 347 | } else if let parent = parent { 348 | return parent.get(name) 349 | } else { 350 | return .error(message: "Unbound symbol '\(name)'") 351 | } 352 | } 353 | 354 | func put(name: String, value: Value) { 355 | defs[name] = value 356 | } 357 | } 358 | 359 | // The environment doesn't just store names and their values, but you can also 360 | // add a documentation string for a name. 361 | extension Environment { 362 | func getDoc(_ name: String) -> String { 363 | if let text = docs[name] { 364 | return text 365 | } else if let parent = parent { 366 | return parent.getDoc(name) 367 | } else { 368 | return "" 369 | } 370 | } 371 | 372 | func putDoc(name: String, descr: String) { 373 | docs[name] = descr 374 | } 375 | } 376 | 377 | // This is used to print the current Environment with the 'help {env}' command. 378 | extension Environment: CustomDebugStringConvertible { 379 | var debugDescription: String { 380 | var s = "" 381 | if parent == nil { 382 | s += "----------Environment (global)----------\n" 383 | } else { 384 | s += "----------Environment (local)-----------\n" 385 | } 386 | 387 | var builtins = [(String, Value)]() 388 | var lambdas = [(String, Value)]() 389 | var variables = [(String, Value)]() 390 | 391 | for name in defs.keys.sorted(by: <) { 392 | let value = defs[name]! 393 | switch value { 394 | case .builtinFunction: 395 | builtins.append((name, value)) 396 | case .lambda: 397 | lambdas.append((name, value)) 398 | default: 399 | variables.append((name, value)) 400 | } 401 | } 402 | 403 | s += "Built-in functions:\n" 404 | for (name, _) in builtins { 405 | s += "\(name)" 406 | if let descr = docs[name] { 407 | s += "\n \(descr)" 408 | } 409 | s += "\n" 410 | } 411 | 412 | s += "\nUser-defined lambdas:\n" 413 | for (name, value) in lambdas { 414 | s += "\(name)" 415 | if let descr = docs[name] { 416 | s += "\n \(descr)" 417 | } 418 | s += "\n \(value.debugDescription)\n" 419 | } 420 | 421 | s += "\nVariables:\n" 422 | for (name, value) in variables { 423 | s += "\(name): \(value.typeName) = \(value.debugDescription)" 424 | if let descr = docs[name] { 425 | s += ", \(descr)" 426 | } 427 | s += "\n" 428 | } 429 | 430 | return s + "----------------------------------------" 431 | } 432 | } 433 | 434 | // MARK: - Evaluating 435 | 436 | /* 437 | After having parsed the abstract syntax tree (AST) from a LISP source file or 438 | the REPL, we need to evaluate it. 439 | 440 | Evaluation means that we look at each Value in turn, process it somehow, and 441 | get a new Value object as a result. Most values simply evaluate to themselves: 442 | a number always stays a number, a string always stays a string. 443 | 444 | The most complicated thing to evaluate is the S-Expression. If an S-Expr has 445 | more than one item, the first is considered to be a function (either built-in 446 | or a user-defined lambda) that gets applied to the rest of the items. 447 | */ 448 | 449 | extension Value { 450 | func eval(_ env: Environment) -> Value { 451 | // Uncomment the next line to see exactly what happens... 452 | //print("eval \(self.debugDescription)") 453 | 454 | switch self { 455 | case .symbol(let name): 456 | return env.get(name) 457 | case .SExpression(let values): 458 | return evalList(env, values) 459 | default: 460 | return self // pass along literally without evaluating 461 | } 462 | } 463 | 464 | // Evaluate the values inside the S-Expression recursively. 465 | private func evalList(_ env: Environment, _ values: [Value]) -> Value { 466 | var values = values 467 | 468 | // Evaluate children. If any of them are symbols, they will be converted 469 | // into the associated value from the environment, such as a function, a 470 | // number, or a Q-Expression. 471 | for i in 0.. Value { 509 | var formals = formals 510 | var args = args 511 | let given = args.count 512 | let expected = formals.count 513 | 514 | while args.count > 0 { 515 | // Have we ran out of formal arguments to bind? 516 | if formals.count == 0 { 517 | return .error(message: "Expected \(expected) arguments, got \(given)") 518 | } 519 | 520 | // Look at the next symbol from the formals. 521 | let sym = formals.removeFirst() 522 | 523 | // Special case to deal with '&' for variable-argument lists. 524 | if sym == "&" { 525 | if formals.count != 1 { 526 | return .error(message: "Expected a single symbol following '&'") 527 | } 528 | // The next formal should be bound to the remaining arguments. 529 | let nextSym = formals.removeFirst() 530 | localEnv.put(name: nextSym, value: .QExpression(values: args)) 531 | break 532 | } 533 | 534 | // Bind the next arg to this name in the function's local environment. 535 | localEnv.put(name: sym, value: args.removeFirst()) 536 | } 537 | 538 | // If a '&' remains in formal list, bind it to an empty Q-Expression. 539 | if formals.count > 0 && formals[0] == "&" { 540 | if formals.count != 2 { 541 | return .error(message: "Expected a single symbol following '&'") 542 | } 543 | // Delete the '&' and associate the final symbol with an empty list. 544 | formals.removeFirst() 545 | let sym = formals.removeFirst() 546 | localEnv.put(name: sym, value: []) 547 | } 548 | 549 | // If all formals have been bound, evaluate the function body. 550 | if formals.count == 0 { 551 | localEnv.parent = parentEnv 552 | return Value.SExpression(values: body).eval(localEnv) 553 | } else { 554 | // Otherwise return partially evaluated function. 555 | return .lambda(env: localEnv, formals: formals, body: body) 556 | } 557 | } 558 | } 559 | 560 | // MARK: - Built-in functions 561 | 562 | /* 563 | As much as possible of the language is implemented in LISP itself, in the 564 | standard library (stdlib.lispy). However, some primitives must be provided 565 | as built-in functions. 566 | */ 567 | 568 | let builtin_list: Builtin = { _, values in .QExpression(values: values) } 569 | 570 | let builtin_eval: Builtin = { env, values in 571 | guard values.count == 1 else { 572 | return .error(message: "'eval' expected 1 argument, got \(values.count)") 573 | } 574 | guard case .QExpression(let qvalues) = values[0] else { 575 | return .error(message: "'eval' expected Q-Expression, got \(values[0])") 576 | } 577 | return Value.SExpression(values: qvalues).eval(env) 578 | } 579 | 580 | let builtin_head: Builtin = { _, values in 581 | guard values.count == 1 else { 582 | return .error(message: "'head' expected 1 argument, got \(values.count)") 583 | } 584 | guard case .QExpression(let qvalues) = values[0] else { 585 | return .error(message: "'head' expected Q-Expression, got \(values[0])") 586 | } 587 | if qvalues.count == 0 { 588 | return .error(message: "'head' expected non-empty Q-Expression, got {}") 589 | } 590 | return .QExpression(values: [qvalues[0]]) 591 | } 592 | 593 | let builtin_tail: Builtin = { env, values in 594 | guard values.count == 1 else { 595 | return .error(message: "'tail' expected 1 argument, got \(values.count)") 596 | } 597 | guard case .QExpression(var qvalues) = values[0] else { 598 | return .error(message: "'tail' expected Q-Expression, got \(values[0])") 599 | } 600 | if qvalues.count == 0 { 601 | return .error(message: "'tail' expected non-empty Q-Expression, got {}") 602 | } 603 | qvalues.removeFirst() 604 | return .QExpression(values: qvalues) 605 | } 606 | 607 | let builtin_join: Builtin = { env, values in 608 | var allValues = [Value]() 609 | for value in values { 610 | if case .QExpression(let qvalues) = value { 611 | allValues += qvalues 612 | } else { 613 | return .error(message: "'join' expected Q-Expression, got \(value)") 614 | } 615 | } 616 | return .QExpression(values: allValues) 617 | } 618 | 619 | /* 620 | The following are mathematical operators. These only work on Integer values. 621 | */ 622 | 623 | typealias BinaryOperator = (Value, Value) -> Value 624 | 625 | func curry(_ op: @escaping (Int, Int) -> Int) -> (_ lhs: Value, _ rhs: Value) -> Value { 626 | return { lhs, rhs in 627 | guard case .integer(let x) = lhs else { 628 | return .error(message: "Expected number, got \(lhs)") 629 | } 630 | guard case .integer(let y) = rhs else { 631 | return .error(message: "Expected number, got \(rhs)") 632 | } 633 | return .integer(value: op(x, y)) 634 | } 635 | } 636 | 637 | func performOnList(_ env: Environment, _ values: [Value], _ op: BinaryOperator) -> Value { 638 | var x = values[0] 639 | for i in 1.. Bool) -> (_ lhs: Value, _ rhs: Value) -> Value { 681 | return { lhs, rhs in 682 | guard case .integer(let x) = lhs else { 683 | return .error(message: "Expected number, got \(lhs)") 684 | } 685 | guard case .integer(let y) = rhs else { 686 | return .error(message: "Expected number, got \(rhs)") 687 | } 688 | return .integer(value: op(x, y) ? 1 : 0) 689 | } 690 | } 691 | 692 | func comparison(_ env: Environment, _ values: [Value], _ op: BinaryOperator) -> Value { 693 | if values.count == 2 { 694 | return op(values[0], values[1]) 695 | } else { 696 | return .error(message: "Comparison expected 2 arguments, got \(values.count)") 697 | } 698 | } 699 | 700 | let builtin_gt: Builtin = { env, values in comparison(env, values, curry(>)) } 701 | let builtin_lt: Builtin = { env, values in comparison(env, values, curry(<)) } 702 | let builtin_ge: Builtin = { env, values in comparison(env, values, curry(>=)) } 703 | let builtin_le: Builtin = { env, values in comparison(env, values, curry(<=)) } 704 | 705 | let builtin_eq: Builtin = { env, values in 706 | if values.count == 2 { 707 | return .integer(value: values[0] == values[1] ? 1 : 0) 708 | } else { 709 | return .error(message: "'==' expected 1 arguments, got \(values.count)") 710 | } 711 | } 712 | 713 | let builtin_ne: Builtin = { env, values in 714 | if values.count == 2 { 715 | return .integer(value: values[0] != values[1] ? 1 : 0) 716 | } else { 717 | return .error(message: "'!=' expected 2 arguments, got \(values.count)") 718 | } 719 | } 720 | 721 | let builtin_if: Builtin = { env, values in 722 | guard values.count == 3 else { 723 | return .error(message: "'if' expected 3 arguments, got \(values.count)") 724 | } 725 | guard case .integer(let cond) = values[0] else { 726 | return .error(message: "'if' expected number, got \(values[0])") 727 | } 728 | guard case .QExpression(let qvalues1) = values[1] else { 729 | return .error(message: "'if' expected Q-Expression, got \(values[1])") 730 | } 731 | guard case .QExpression(let qvalues2) = values[2] else { 732 | return .error(message: "'if' expected Q-Expression, got \(values[2])") 733 | } 734 | 735 | // If condition is true, evaluate first expression, otherwise second. 736 | if cond != 0 { 737 | return Value.SExpression(values: qvalues1).eval(env) 738 | } else { 739 | return Value.SExpression(values: qvalues2).eval(env) 740 | } 741 | } 742 | 743 | /* 744 | Functions for creating variables and functions. 745 | */ 746 | 747 | func bindVariable(_ env: Environment, _ values: [Value]) -> Value { 748 | guard case .QExpression(let qvalues) = values[0] else { 749 | return .error(message: "Expected Q-Expression, got \(values[0])") 750 | } 751 | 752 | // Ensure all values from the Q-Expression are symbols. 753 | var symbols = [String]() 754 | for value in qvalues { 755 | if case .symbol(let name) = value { 756 | symbols.append(name) 757 | } else { 758 | return .error(message: "Expected symbol, got \(value)") 759 | } 760 | } 761 | 762 | // Check correct number of symbols and values. 763 | if symbols.count != values.count - 1 { 764 | return .error(message: "Found \(symbols.count) symbols but \(values.count - 1) values") 765 | } 766 | 767 | // Put the symbols and their associated values into the environment. 768 | for (i, symbol) in symbols.enumerated() { 769 | env.put(name: symbol, value: values[i + 1]) 770 | } 771 | 772 | return Value.empty() 773 | } 774 | 775 | let builtin_def: Builtin = { env, values in bindVariable(env.globalEnvironment(), values) } 776 | let builtin_put: Builtin = { env, values in bindVariable(env, values) } 777 | 778 | let builtin_lambda: Builtin = { env, values in 779 | guard values.count == 2 else { 780 | return .error(message: "'\\' expected 2 arguments, got \(values.count)") 781 | } 782 | guard case .QExpression(let formalsValues) = values[0] else { 783 | return .error(message: "'\\' expected Q-Expression, got \(values[0])") 784 | } 785 | guard case .QExpression(let bodyValues) = values[1] else { 786 | return .error(message: "'\\' expected Q-Expression, got \(values[1])") 787 | } 788 | 789 | // Check that the first Q-Expression contains only symbols. 790 | var symbols = [String]() 791 | for value in formalsValues { 792 | if case .symbol(let name) = value { 793 | symbols.append(name) 794 | } else { 795 | return .error(message: "Expected symbol, got \(value)") 796 | } 797 | } 798 | 799 | return .lambda(env: Environment(), formals: symbols, body: bodyValues) 800 | } 801 | 802 | /* 803 | I/O functions and miscellaneous. 804 | */ 805 | 806 | let builtin_print: Builtin = { env, values in 807 | for value in values { 808 | print(value, terminator: " ") 809 | } 810 | print("") 811 | return Value.empty() 812 | } 813 | 814 | let builtin_error: Builtin = { env, values in 815 | guard values.count == 1 else { 816 | return .error(message: "'error' expected 1 argument, got \(values.count)") 817 | } 818 | guard case .text(let message) = values[0] else { 819 | return .error(message: "'error' expected string, got \(values[0])") 820 | } 821 | return .error(message: message) 822 | } 823 | 824 | let builtin_doc: Builtin = { env, values in 825 | guard values.count == 2 else { 826 | return .error(message: "'doc' expected 2 arguments, got \(values.count)") 827 | } 828 | guard case .QExpression(var qvalues) = values[0] else { 829 | return .error(message: "'doc' expected Q-Expression, got \(values[0])") 830 | } 831 | guard case .text(let descr) = values[1] else { 832 | return .error(message: "'doc' expected number, got \(values[1])") 833 | } 834 | guard qvalues.count == 1 else { 835 | return .error(message: "'doc' expected Q-Expression with 1 symbol") 836 | } 837 | guard case .symbol(let name) = qvalues[0] else { 838 | return .error(message: "'doc' expected symbol, got \(qvalues[0])") 839 | } 840 | env.putDoc(name: name, descr: descr) 841 | return Value.empty() 842 | } 843 | 844 | let builtin_help: Builtin = { env, values in 845 | guard values.count == 1 else { 846 | return .error(message: "'help' expected 1 argument, got \(values.count)") 847 | } 848 | guard case .QExpression(var qvalues) = values[0] else { 849 | return .error(message: "'help' expected Q-Expression, got \(values[0])") 850 | } 851 | guard qvalues.count == 1 else { 852 | return .error(message: "'help' expected Q-Expression with 1 symbol") 853 | } 854 | guard case .symbol(let name) = qvalues[0] else { 855 | return .error(message: "'help' expected symbol, got \(qvalues[0])") 856 | } 857 | 858 | if name == "env" { // special value 859 | debugPrint(env) 860 | } else { 861 | let descr = env.getDoc(name) 862 | if descr != "" { 863 | print(descr) 864 | } else { 865 | print("No documentation found for '\(name)'") 866 | } 867 | } 868 | return Value.empty() 869 | } 870 | 871 | // MARK: - Parser 872 | 873 | /* 874 | This is a simplified version of the parser used in the original tutorial. 875 | It is not very smart or capable; any input it doesn't recognize simply gets 876 | ignored. But it works if you don't try to break it too hard. ;-) 877 | */ 878 | 879 | private func tokenizeString(_ s: String, _ i: inout String.Index) -> Value { 880 | var out = "" 881 | while i < s.endIndex { 882 | let c = s[i] 883 | i = s.index(after: i) 884 | if c == "\"" { 885 | return .text(value: out.unescaped()) 886 | } else { 887 | out += "\(c)" 888 | } 889 | } 890 | return .error(message: "Expected \"") 891 | } 892 | 893 | private func tokenizeAtom(_ s: String) -> Value { 894 | if let i = Int(s) { 895 | return .integer(value: i) 896 | } else { 897 | return .symbol(name: s) 898 | } 899 | } 900 | 901 | private func tokenizeList(_ s: String, _ i: inout String.Index, _ type: String) -> Value { 902 | var token = "" 903 | var array = [Value]() 904 | 905 | while i < s.endIndex { 906 | let c = s[i] 907 | i = s.index(after: i) 908 | 909 | // Symbol or number found. 910 | if (c >= "a" && c <= "z") || (c >= "A" && c <= "Z") || 911 | (c >= "0" && c <= "9") || c == "_" || c == "\\" || 912 | c == "+" || c == "-" || c == "*" || c == "/" || 913 | c == "=" || c == "<" || c == ">" || c == "!" || c == "&" { 914 | token += "\(c)" 915 | } else if c == "\"" { 916 | array.append(tokenizeString(s, &i)) 917 | } else { 918 | if !token.isEmpty { 919 | array.append(tokenizeAtom(token)) 920 | token = "" 921 | } 922 | // Open a new list. 923 | if c == "(" || c == "{" { 924 | array.append(tokenizeList(s, &i, "\(c)")) 925 | } else if c == ")" { 926 | if type == "(" { 927 | return .SExpression(values: array) 928 | } else { 929 | return .error(message: "Unexpected )") 930 | } 931 | } else if c == "}" { 932 | if type == "{" { 933 | return .QExpression(values: array) 934 | } else { 935 | return .error(message: "Unexpected }") 936 | } 937 | } 938 | } 939 | } 940 | 941 | // Don't forget the very last token. 942 | if !token.isEmpty { 943 | array.append(tokenizeAtom(token)) 944 | } 945 | 946 | if type == "(" { 947 | return .error(message: "Expected )") 948 | } else if type == "{" { 949 | return .error(message: "Expected }") 950 | } else if array.count == 1 { 951 | return array[0] 952 | } else { 953 | return .SExpression(values: array) 954 | } 955 | } 956 | 957 | // This is the function you'd call to parse a source file. In order to tell 958 | // expressions apart from each other in the file, each must be wrapped in ( ) 959 | // parentheses. This function parses the first of those S-Expressions it finds. 960 | // You repeatedly call this function until you reach the end of the file. 961 | func parseFile(_ s: String, _ i: inout String.Index) -> Value? { 962 | while i < s.endIndex { 963 | let c = s[i] 964 | i = s.index(after: i) 965 | if c == "(" { 966 | return tokenizeList(s, &i, "(") 967 | } 968 | } 969 | return nil 970 | } 971 | 972 | // This is the function you'd call to parse input from the REPL. On the REPL, 973 | // expressions don't need to be surrounded by parentheses. We automatically 974 | // put the thing into an S-Expression. 975 | func parseREPL(_ s: String) -> Value { 976 | var i = s.startIndex 977 | return tokenizeList(s, &i, "") 978 | } 979 | 980 | // MARK: - Loading source files 981 | 982 | /* 983 | This LISP interpreter can either be used interactively using a REPL, or it 984 | can load and execute one or more source files. On the REPL you can load and 985 | execute a source file using the 'load' command. 986 | 987 | Note: Executing a source file does not produce any output unless you 'print' 988 | it or if there is an error. 989 | */ 990 | 991 | func importFile(_ env: Environment, _ filename: String) -> Value { 992 | do { 993 | let s = try String(contentsOfFile: filename, encoding: .utf8) 994 | var i = s.startIndex 995 | while i < s.endIndex { 996 | if let expr = parseFile(s, &i) { 997 | if case .error(let message) = expr { 998 | print("Parse error: \(message)") 999 | } else { 1000 | let result = expr.eval(env) 1001 | if case .error(let message) = result { 1002 | print("Error: \(message)") 1003 | } 1004 | } 1005 | } 1006 | } 1007 | return Value.empty() 1008 | } catch { 1009 | return .error(message: "Could not load \(filename), reason: \(error)") 1010 | } 1011 | } 1012 | 1013 | let builtin_load: Builtin = { env, values in 1014 | guard values.count == 1 else { 1015 | return .error(message: "Function 'load' expected 1 argument, got \(values.count)") 1016 | } 1017 | guard case .text(let filename) = values[0] else { 1018 | return .error(message: "Function 'load' expected string, got \(values[0])") 1019 | } 1020 | return importFile(env, filename) 1021 | } 1022 | 1023 | // MARK: - REPL 1024 | 1025 | /* 1026 | The REPL simply reads a line of input, parses it, and then tries to evaluate 1027 | the tree of Value objects. The REPL shows the result of every expression that 1028 | gets evaluated. 1029 | */ 1030 | 1031 | func readInput() -> String { 1032 | let keyboard = FileHandle.standardInput 1033 | let inputData = keyboard.availableData 1034 | let string = String(data: inputData, encoding: .utf8)! 1035 | return string.trimmingCharacters(in: .newlines) 1036 | } 1037 | 1038 | func repl(_ env: Environment) { 1039 | print("Lispy Version 0.16") 1040 | print("Press Ctrl+C to Exit") 1041 | 1042 | var lines = "" 1043 | while true { 1044 | print("lispy> ", terminator: "") 1045 | fflush(__stdoutp) 1046 | let input = readInput() 1047 | 1048 | // Does the line end with a semicolon? Then keep listening for more input. 1049 | if !input.isEmpty { 1050 | let lastIndex = input.index(before: input.endIndex) 1051 | if input[lastIndex] == ";" { 1052 | let s = input[input.startIndex ..< lastIndex] 1053 | lines += "\(s)\n" 1054 | continue 1055 | } 1056 | } 1057 | 1058 | lines += input 1059 | let expr = parseREPL(lines) 1060 | if case .error(let message) = expr { 1061 | print("Parse error: \(message)") 1062 | } else { 1063 | debugPrint(expr.eval(env)) 1064 | } 1065 | lines = "" 1066 | } 1067 | } 1068 | 1069 | // MARK: - Initialization 1070 | 1071 | /* 1072 | This adds all the built-in functions to the global environment, and loads the 1073 | standard library. Without these two steps, the language is useless. 1074 | */ 1075 | 1076 | extension Environment { 1077 | func addBuiltinFunction(_ name: String, _ descr: String = "", _ code: @escaping Builtin) { 1078 | put(name: name, value: .builtinFunction(name: name, code: code)) 1079 | putDoc(name: name, descr: descr) 1080 | } 1081 | 1082 | func addBuiltinFunctions() { 1083 | let table = [ 1084 | ("eval", "Evaluate a Q-Expression. Usage: eval {q-expr}", builtin_eval), 1085 | ("list", "Convert one or more values into a Q-Expression. Usage: list value1 value2...", builtin_list), 1086 | ("head", "Return the first value from a Q-Expression. Usage: head {list}", builtin_head), 1087 | ("tail", "Return a new Q-Expression with the first value removed. Usage: tail {list}", builtin_tail), 1088 | ("join", "Combine one or more Q-Expressions into a new one. Usage: join {list} {list}...", builtin_join), 1089 | 1090 | ("+", "Add two numbers", builtin_add), 1091 | ("-", "Subtract two numbers", builtin_subtract), 1092 | ("*", "Multiply two numbers", builtin_multiply), 1093 | ("/", "Divide two numbers", builtin_divide), 1094 | 1095 | (">", "Greater than", builtin_gt), 1096 | ("<", "Less than", builtin_lt), 1097 | (">=", "Greater than or equal to", builtin_ge), 1098 | ("<=", "Less than or equal to", builtin_le), 1099 | ("==", "Equals", builtin_eq), 1100 | ("!=", "Not equals", builtin_ne), 1101 | ("if", "Usage: if condition { true clause } { false clause }", builtin_if), 1102 | 1103 | ("def", "Bind names to one or more values in the global environment. Usage: def {symbol1 symbol2 ...} value1 value2...", builtin_def), 1104 | ("=", "Bind names to one or more values in the current function's environment. Usage: = {symbol1 symbol2 ...} value1 value2...", builtin_put), 1105 | 1106 | ("\\", "Create a lambda. Usage: \\ {parameter names} {function body}", builtin_lambda), 1107 | 1108 | ("print", "Print a value to stdout. Usage: print value", builtin_print), 1109 | ("error", "Create an error value. Usage: error \"message\"", builtin_error), 1110 | 1111 | ("load", "Import a LISP file and evaluate it. Usage: load \"filename.lispy\"", builtin_load), 1112 | 1113 | ("doc", "Add description to a symbol. Usage: doc {symbol} \"help text\"", builtin_doc), 1114 | ("help", "Print information about a function or any other defined value. Usage: help {symbol}. Use help {env} to print out the current environment.", builtin_help), 1115 | ] 1116 | 1117 | for (name, descr, builtin) in table { 1118 | addBuiltinFunction(name, descr, builtin) 1119 | } 1120 | } 1121 | } 1122 | 1123 | let globalEnv = Environment() 1124 | globalEnv.addBuiltinFunctions() 1125 | 1126 | if case .error(let message) = importFile(globalEnv, "stdlib.lispy") { 1127 | print("Error loading standard library. \(message)") 1128 | } 1129 | 1130 | // MARK: - Main loop 1131 | 1132 | /* 1133 | If Lispy is started without arguments, start the REPL. Otherwise, we load and 1134 | execute each of the specified source files. 1135 | */ 1136 | 1137 | var args = CommandLine.arguments 1138 | if args.count > 1 { 1139 | args.removeFirst() 1140 | for arg in args { 1141 | if case .error(let message) = importFile(globalEnv, arg) { 1142 | print(message) 1143 | } 1144 | } 1145 | } else { 1146 | repl(globalEnv) 1147 | } 1148 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # Build Your Own LISP in Swift 2 | 3 | This is Daniel Holden's excellent [Build Your Own LISP](http://buildyourownlisp.com) tutorial converted to Swift 4.2 (Xcode 10 and up). 4 | 5 | ## Usage 6 | 7 | To run Lispy as a REPL type the following from a terminal: 8 | 9 | $ swift Lispy.swift 10 | 11 | Now you can type all kinds of LISP-ish stuff behind the `lispy>` prompt. For example, 12 | 13 | lispy> (+ 1 2 3) 14 | 15 | will add up the values 1, 2, and 3, and produce the output `6`. Yay! 16 | 17 | You can also leave off the parentheses: 18 | 19 | lispy> + 1 2 3 20 | 21 | A useful command is `help`: 22 | 23 | lispy> help {env} 24 | 25 | This shows the contents of the current "environment", which lists all the available functions with a quick summary of what they do. 26 | 27 | You can type full LISP programs into the REPL: 28 | 29 | lispy> fun {factorial n} { if (== n 0) { 1 } { (* n (factorial (- n 1))) } } 30 | lispy> factorial 5 31 | 120 32 | 33 | It may be a bit easier to read across multiple lines. To indicate to the REPL that you're continuing on the next line, end each line with a semicolon: 34 | 35 | lispy> fun {factorial n} { ; 36 | if (== n 0) ; 37 | { 1 } ; 38 | { (* n (factorial (- n 1))) } ; 39 | } 40 | lispy> factorial 5 41 | 120 42 | 43 | You can also import a source file into the REPL: 44 | 45 | > load "test.lispy" 46 | 47 | This evaluates all the expressions in that file, but unlike when you enter code manually it does not print the results. You have to use the `print` function for that. 48 | 49 | To quit the REPL, press Ctrl+C. 50 | 51 | To run Lispy on a source file without using the REPL, type from the terminal: 52 | 53 | $ swift Lispy.swift test.lispy 54 | 55 | Note: Unlike in the REPL, all the expressions in this source file must be surrounded by `( )` parentheses, otherwise the parser won't know where one expression ends and the next begins. 56 | 57 | For more speed, you can compile Lispy.swift using the following command: 58 | 59 | swiftc -sdk $(xcrun --show-sdk-path --sdk macosx) -O -o Lispy Lispy.swift 60 | 61 | ## The language 62 | 63 | This is a simple LISP-like language. It is dynamically typed, meaning that variables do not have a specific datatype -- a variable is just a name that you've associated with some value. Only values have a type. 64 | 65 | Data objects can have the following types: 66 | 67 | - error 68 | - integer number 69 | - text string 70 | - symbol 71 | - S-Expression 72 | - Q-Expression 73 | - built-in function 74 | - user-defined lambda function 75 | 76 | ### Symbols 77 | 78 | A *symbol* is an identifier. You use these to give names to values. 79 | 80 | Symbols may include the characters `a-z A-Z 0-9`, the underscore `_`, the arithmetic operator characters `+ - * /`, the backslash character `\`, the comparison operator characters `= < > !`, or an ampersand `&`. 81 | 82 | To give a name to a value, you use `def`: 83 | 84 | lispy> def {x} 100 85 | lispy> x 86 | 100 87 | 88 | Or more than one variable at a time: 89 | 90 | lispy> def {y z} 200 300 91 | lispy> y 92 | 200 93 | lispy> z 94 | 300 95 | 96 | The variables are now part of the *environment*, which stores the mapping of names to values. 97 | 98 | To see the contents of the current environment: 99 | 100 | lispy> help {env} 101 | 102 | The `help` function is also useful for viewing information on a specific function: 103 | 104 | lispy> help {def} 105 | 106 | By the way, you can create your own help documentation using the `doc` command: 107 | 108 | lispy> doc {my-func} "My awesome function" 109 | lispy> help {my-func} 110 | 111 | `def` always puts the name into the global environment. Each function also has its own local environment. To put a name into that local environment, you use `=`. 112 | 113 | A silly example: 114 | 115 | lispy> def {some-func} (\ {x} { do (= {y} (+ x 1)) (help {env}) }) 116 | 117 | This defines a new lambda and gives it the name `some-func`. When you call it with some value, it creates a new local variable `y` that only exists for the duration of that function. 118 | 119 | lispy> some-func 100 120 | ----------Environment (local)----------- 121 | Variables: 122 | x: Integer = 100 123 | y: Integer = 101 124 | ---------------------------------------- 125 | lispy> y 126 | Error: Unbound symbol 'y' 127 | 128 | After the function finishes, the local environment is destroyed and `x` and `y` no longer exist. 129 | 130 | `def` is quite powerful. A cool example: 131 | 132 | lispy> def {arglist} {a b c} 133 | lispy> arglist 134 | {a b c} 135 | lispy> def arglist 1 2 3 136 | lispy> a 137 | 1 138 | lispy> b 139 | 2 140 | lispy> c 141 | 3 142 | 143 | You can assign a name to any kind of value, even to a Q-Expression or a function. Example: 144 | 145 | lispy> def {p} + 146 | lispy> p 1 2 147 | 3 148 | 149 | When you use an S-Expression, it is evaluated first and then the result is assigned to the value: 150 | 151 | lispy> def {x} 100 152 | lispy> def {y} (+ 1 x) 153 | lispy> y 154 | 101 155 | lispy> def {x} 200 156 | lispy> y 157 | 101 did not change! 158 | 159 | ### S-Expressions 160 | 161 | An *S-Expression* contains executable code. It looks like this: 162 | 163 | (+ 1 2 3) 164 | 165 | The `( )` parentheses are what makes this an S-Expression. 166 | 167 | The first element should be a symbol that represents a function. When the interpreter evaluates an S-Expression, it applies that function to the rest of the elements. 168 | 169 | Note: A big difference with traditional LISP is that these S-Expression lists are not built from *cons cells* but are regular dynamic arrays. 170 | 171 | ### Q-Expressions 172 | 173 | A *Q-Expression* is a list of data. It looks like this: 174 | 175 | {1 2 3} 176 | 177 | The `{ }` braces distinguish this kind of list from an S-Expression. When a Q-Expression is evaluated nothing happens, it's just data. 178 | 179 | In traditional LISPs, you'd use `QUOTE` or `'` to convert an S-Expression (code) into a Q-Expression (data), but here you use `{ ... }` braces instead. 180 | 181 | Q-Expressions allow you to write the following: 182 | 183 | lispy> def {x} 123 184 | 185 | This assigns the name `x` to the value `123`. However, if you write it without the curly braces, 186 | 187 | lispy> def x 123 188 | 189 | then it no longer means, "assign the value `123` to the name `x`" but "assign the value `123` to the name *from the value* of `x`. This might work, or it might not. It depends on whether the name `x` exists already and whether it refers to another symbol. For example: 190 | 191 | lispy> def {x} {y} 192 | {y} 193 | lispy> def x 123 this is really def {y} 123 194 | () 195 | lispy> y 196 | 123 197 | 198 | The function `eval` turns a Q-Expression into an S-Expression and evaluates it: 199 | 200 | lispy> (+ 1 2 3) this is an S-Expression 201 | 6 it is evaluated 202 | 203 | lispy> {+ 1 2 3} this is a Q-Expression 204 | {+ 1 2 3} it does nothing 205 | 206 | lispy> eval {+ 1 2 3} using eval 207 | 6 208 | 209 | The trick in writing proper Lispy programs is to make sure you use S-Expressions and Q-Expressions in the right places. 210 | 211 | ### Built-in functions 212 | 213 | The language comes with a minimal set of built-in functions that can perform basic tasks on Q-Expressions and other values. 214 | 215 | You can do arithmetic on numeric values using `+`, `-`, `*`, `/`. 216 | 217 | **list** Creates a new Q-Expression from one or more values. 218 | 219 | lispy> list 1 2 3 4 220 | {1 2 3 4} 221 | 222 | lispy> list (list 1 2 3) (list 4 5 6) 223 | {{1 2 3} {4 5 6}} 224 | 225 | **head** Returns the first element from a Q-Expression. 226 | 227 | lispy> head {1 2 3} 228 | {1} 229 | 230 | Note: the result is still a Q-Expression, which may not be what you want. If a Q- or S-Expression only has one element, calling `eval` returns just that element. So to pull the value out, `eval` the result: 231 | 232 | lispy> eval (head {1 2 3}) 233 | 1 234 | 235 | **tail** Returns a Q-Expression with the first element removed. 236 | 237 | lispy> tail {1 2 3} 238 | {2 3} 239 | 240 | **join** Combines two or more Q-Expressions. 241 | 242 | lispy> join {1} {2 3} 243 | {1 2 3} 244 | 245 | **eval** Takes a Q-Expression and evaluates it as if it were a S-Expression. 246 | 247 | lispy> eval {head (list 1 2 3 4)} 248 | {1} 249 | 250 | lispy> eval (tail {tail tail {5 6 7}}) 251 | {6 7} 252 | 253 | lispy> eval (head {(+ 1 2) (+ 10 20)}) 254 | 3 255 | 256 | This is what allows you to treat data as code and what makes LISP awesome. 257 | 258 | **print** Prints a value to stdout. 259 | 260 | lispy> print "hello\nworld!" 261 | hello 262 | world! 263 | 264 | You can print anything, not just strings. 265 | 266 | **error** Generates an error value with a message. 267 | 268 | lispy> error "Houston, we've got a problem!" 269 | 270 | **if** Decisions, decisions... `if` lets you make them. 271 | 272 | lispy> if (> x 10) { print "yep" } { print "nope" } 273 | 274 | The code from the first Q-Expression is evaluated when the condition is true; the code from the second Q-Expression otherwise. 275 | 276 | You can compare numbers using `<`, `<=`, `>`, `>=`, `==`, `!=`. These return `1` (true) or `0` false. In general, the value `0` evaluates as false and anything that is not `0` evaluates as true. 277 | 278 | There are no looping constructs in LISP. To make a loop, you need to use recursion. 279 | 280 | ### Lambdas 281 | 282 | Ah, the good stuff! A lambda is like a closure in Swift. 283 | 284 | To create a lambda expression, you use `\` because λ is too hard to type: 285 | 286 | lispy> \ {x y} {+ x y} 287 | 288 | Here, `{x y}` are the formal arguments, and `{+ x y}` is the body of the function. 289 | 290 | Calling the lambda function: 291 | 292 | lispy> (\ {x y} {+ x y}) 10 20 293 | 30 294 | 295 | They are a bit useless by themselves, so here's how you'd give the lambda a name: 296 | 297 | lispy> def {add-together} (\ {x y} {+ x y}) 298 | lispy> add-together 10 20 299 | 30 300 | 301 | The standard library comes with a shortcut notation for defining your own functions: 302 | 303 | lispy> fun {add-together x y} {+ x y} 304 | 305 | This does the exact same thing as above but saves some typing. 306 | 307 | Most of your LISP coding will involve writing your own functions in this manner. 308 | 309 | A function always returns some value. If you have nothing to return, then it's customary to return the empty list `()` or `{}` (or the synonym `nil`). 310 | 311 | Lambdas can take a variable number of arguments, using the syntax `{x & xs}`, where `xs` is a list containing the additional arguments. 312 | 313 | lispy> def {my-join} (\ {x & xs} {join x xs}) 314 | lispy> my-join {a} 315 | {a} 316 | lispy> my-join {a} {b} 317 | {a {b}} 318 | lispy> my-join {a} {b c} 319 | {a {b c}} 320 | lispy> my-join {a} {b} {c} 321 | {a {b} {c}} 322 | 323 | ### Cool tricks with functions 324 | 325 | You don't always need to specify values for all arguments of a function. This is called "partial application". 326 | 327 | This is how you'd normally create and call a function: 328 | 329 | lispy> fun {add-mul x y} {+ x (* x y)} 330 | lispy> add-mul 10 20 331 | 210 332 | 333 | Fair enough. But what if you do this: 334 | 335 | lispy> add-mul 10 336 | (\ {y} {+ x (* x y)}) x=10 337 | 338 | Because `add-mul` expects two arguments and you only specified one, what you get back is a new lambda. This new lambda still expects the parameter `y`. 339 | 340 | Remember how functions have their own local environment? For a partially applied function, that environment contains the values of their arguments. In this case, the new lambda knows that `x` is `10` already. 341 | 342 | What's the use of this? Well, it allows you to do something like: 343 | 344 | lispy> def {add-mul-ten} (add-mul 10) 345 | lispy> add-mul-ten 50 346 | 510 347 | 348 | So you can create new functions from existing functions by only partially evaluating them. Functional programming boffins love it! 349 | 350 | Another hip thing is currying. Yum! The `+` function doesn't normally take a list and you'd call it like so: 351 | 352 | lispy> + 5 6 7 353 | 354 | But `curry` fixes that: 355 | 356 | lispy> curry + {5 6 7} 357 | 358 | The other way around works too, when a function takes a list as input but you wish to call it using variable arguments: 359 | 360 | lispy> uncurry head 5 6 7 361 | 362 | ## The standard library 363 | 364 | A language is pretty useless without a good library of functions. Besides the handful of built-in functions listed above, Lispy comes with a very basic library. These functions are defined in LISP itself. You can see them in [stdlib.lispy](stdlib.lispy). This source file is imported automatically when you start Lispy. 365 | 366 | Some highlights: 367 | 368 | **reverse** Changes the order of the elements in a list. 369 | 370 | lispy> reverse {1 2 3 4} 371 | {4 3 2 1} 372 | 373 | **map** Applies a function to all items in a list. 374 | 375 | lispy> map (\ {x} {+ x 10}) {5 2 11} 376 | {15 12 21} 377 | 378 | **filter** Removes items from a list that do not match the given condition. 379 | 380 | lispy> filter (\ {x} {> x 2}) {5 2 11 -7 8 1} 381 | {5 11 8} 382 | 383 | **foldl** Fold left is like `reduce` in Swift. 384 | 385 | lispy> foldl (\ {a x} {+ a x}) 0 {1 2 3 4} 386 | 10 387 | 388 | Or simply: 389 | 390 | lispy> foldl + 0 {1 2 3 4} 391 | 10 392 | 393 | **select** This works like Swift's `switch` statement. 394 | 395 | (fun {month-day-suffix i} { 396 | select 397 | {(== i 0) "st"} 398 | {(== i 1) "nd"} 399 | {(== i 3) "rd"} 400 | {otherwise "th"} 401 | }) 402 | 403 | **case** Like Objective-C's `switch` statement. ;-) 404 | 405 | (fun {day-name x} { 406 | case x 407 | {0 "Monday"} 408 | {1 "Tuesday"} 409 | {2 "Wednesday"} 410 | {3 "Thursday"} 411 | {4 "Friday"} 412 | {5 "Saturday"} 413 | {6 "Sunday"} 414 | }) 415 | 416 | **do** Perform a sequence of commands. 417 | 418 | lispy> do (print "hello") (print "world") 419 | 420 | ## Future improvements 421 | 422 | - The parser isn't very good. The original tutorial uses parser combinators, which would be a fun thing to play with. 423 | 424 | - The original tutorial allows comments in the source files (starting with `;`), but my parser does not support this currently. 425 | 426 | - The built-in functions use a lot of `guard` statements to verify that input is correct. This is necessary because the language is dynamically typed. But it would be nice to write some Swift "macros" to make this part of the code a bit more readable. 427 | 428 | - Use proper *cons cells* to make lists like a true LISP. 429 | 430 | - The REPL isn't very user-friendly. You can't use the arrow keys to go back. Using semicolons to type on multiple lines is meh. 431 | 432 | - The REPL doesn't require you to put `( )` around everything you write. That is convenient but also a bit misleading since source files do require it. If we were to require `( )` then supporting multiple lines becomes easier: the input isn't done until the last `)` matches up with the first `(`. 433 | 434 | - Add a `Value.Real` type to support floating-point values. When doing arithmetic, integers should be promoted to Reals if necessary. 435 | 436 | - Add a `Value.Boolean` type? 437 | 438 | - Make `Value` conform to `Comparable` instead of just `Equatable`, to simplify the code for the comparison operators. 439 | 440 | - You cannot evaluate functions that take no parameters. This just prints out the name of the function. I don't know if this is a big deal, you could just make it a variable instead. 441 | 442 | - It is not particularly efficient. :-D 443 | 444 | ## Credits 445 | 446 | This is pretty much a straight port from Daniel Holden's [Build Your Own LISP](http://buildyourownlisp.com) code, with some Swift goodness thrown in and a few modifications of my own. Many of the examples in this document are taken from his excellent tutorial. [Go read it now!](http://buildyourownlisp.com) 447 | 448 | Licensed under [Creative Commons Attribution-NonCommercial-ShareAlike 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/) 449 | -------------------------------------------------------------------------------- /stdlib.lispy: -------------------------------------------------------------------------------- 1 | (def {nil} {}) 2 | (def {true} 1) 3 | (def {false} 0) 4 | 5 | (doc {fun} "Shortcut for defining functions. Usage: fun {add-together x y} {+ x y}") 6 | (def {fun} (\ {args body} { 7 | def (head args) (\ (tail args) body) 8 | })) 9 | 10 | (doc {unpack} "Apply a function that normally takes a variable number of arguments to a list. Usage: unpack + {1 2 3}") 11 | (fun {unpack f l} {eval (join (list f) l)}) 12 | 13 | (doc {curry} "Synonym for unpack.") 14 | (def {curry} unpack) 15 | 16 | (doc {pack} "Apply a function that normally takes a list to a variable number of arguments. Usage: pack head 1 2 3" ) 17 | (fun {pack f & xs} {f xs}) 18 | 19 | (doc {uncurry} "Synonym for pack.") 20 | (def {uncurry} pack) 21 | 22 | (doc {reverse} "Reverse the order of the items in the list. Usage: reverse {list}") 23 | (fun {reverse l} { 24 | if (== l nil) 25 | { nil } 26 | { join (reverse (tail l)) (head l) } 27 | }) 28 | 29 | (doc {len} "Count the number of items in a list. Usage: len {list}") 30 | (fun {len l} { 31 | if (== l nil) { 0 } { + 1 (len (tail l)) } 32 | }) 33 | 34 | (doc {first} "Return the first item from a list. Usage: first {list}") 35 | (fun {first l} { eval (head l) }) 36 | 37 | (doc {second} "Return the second item from a list. Usage: second {list}") 38 | (fun {second l} { eval (head (tail l)) }) 39 | 40 | (doc {third} "Return the third item from a list. Usage: third {list}") 41 | (fun {third l} { eval (head (tail (tail l))) }) 42 | 43 | (doc {last} "Return the last item of a list. Usage: last {list}") 44 | (fun {last l} { 45 | if (== l nil) 46 | { nil } 47 | { if (== 1 (len l)) { first l } { last (tail l) }} 48 | }) 49 | 50 | (doc {nth} "Return the nth item from a list. Usage: nth number {list}") 51 | (fun {nth n l} { 52 | if (== n 0) { first l } { nth (- n 1) (tail l) } 53 | }) 54 | 55 | (doc {contains} "Return 1 if a value is a member of a list, otherwise 0. Usage: contains value {list}") 56 | (fun {contains e l} { 57 | if (== l nil) 58 | { false } 59 | { if (== e (first l)) { true } { contains e (tail l) }} 60 | }) 61 | 62 | (doc {take} "Take the first n items into a new list. Usage: take number {list}") 63 | (fun {take n l} { 64 | if (== n 0) 65 | { nil } 66 | { join (head l) (take (- n 1) (tail l)) } 67 | }) 68 | 69 | (doc {drop} "Remove the first n items from a list. Usage: drop number {list}") 70 | (fun {drop n l} { 71 | if (== n 0) { l } { drop (- n 1) (tail l) } 72 | }) 73 | 74 | (doc {split} "Split a list into two sublists at the nth item. Usage: split number {list}") 75 | (fun {split n l} {list (take n l) (drop n l)}) 76 | 77 | (doc {map} "Apply a function to all items in a list. Usage: map func {list}") 78 | (fun {map f l} { 79 | if (== l nil) 80 | { nil } 81 | { join (list (f (first l))) (map f (tail l)) } 82 | }) 83 | 84 | (doc {filter} "Removes items from a list that do not match the given condition. Usage: filter (\\ {x} {...}) {list}") 85 | (fun {filter f l} { 86 | if (== l nil) 87 | { nil } 88 | { join (if (f (first l)) {head l} {nil}) (filter f (tail l)) } 89 | }) 90 | 91 | (doc {foldl} "Fold left. Usage: foldl (\\ {accumulator x} {...}) base-value {list}") 92 | (fun {foldl f z l} { 93 | if (== l nil) 94 | { z } 95 | { foldl f (f z (first l)) (tail l) } 96 | }) 97 | 98 | (fun {sum l} {foldl + 0 l}) 99 | (fun {product l} {foldl * 1 l}) 100 | 101 | (doc {cons} "Append a value to the front of a Q-Expression. Usage: cons value {list}") 102 | (fun {cons x l} { join (list x) l }) 103 | 104 | (doc {do} "Perform several things in sequence. Usage: do (thing1) (thing2)...") 105 | (fun {do & l} { 106 | if (== l nil) { nil } { last l } 107 | }) 108 | 109 | (doc {let} "Open a new scope. Usage: let { do (...) }") 110 | (fun {let b} { 111 | ((\ {_} b) ()) 112 | }) 113 | 114 | (doc {select} "Usage: select {cond1 ...} {cond2 ...} {otherwise ...}") 115 | (fun {select & cs} { 116 | if (== cs nil) 117 | { error "No selection found" } 118 | { if (first (first cs)) {second (first cs)} {unpack select (tail cs)} } 119 | }) 120 | 121 | (doc {otherwise} "The final clause in a select function. Usage: select {...} {...} {otherwise ...}") 122 | (def {otherwise} true) 123 | 124 | (doc {case} "Usage: case condition {val1 ...} {val2 ...}...") 125 | (fun {case x & cs} { 126 | if (== cs nil) 127 | { error "No case found" } 128 | { if (== x (first (first cs))) 129 | { second (first cs) } 130 | { unpack case (join (list x) (tail cs)) }} 131 | }) 132 | 133 | (doc {flip} "Apply a function with its two arguments swapped. Usage: flip func arg1 arg2") 134 | (fun {flip f a b} {f b a}) 135 | 136 | (doc {ghost} "'ghost arg1 arg2' is the same as writing 'eval (list arg1 arg2...)'") 137 | (fun {ghost & xs} {eval xs}) 138 | 139 | (doc {comp} "Composition of two functions. Usage: comp func1 func2 arg") 140 | (fun {comp f g x} {f (g x)}) 141 | 142 | (doc {and} "Logical AND") 143 | (fun {and x y} { 144 | if (!= x 0) 145 | { if (!= y 0) { 1 } { 0 } } 146 | { 0 } 147 | }) 148 | 149 | (doc {and} "Logical OR") 150 | (fun {or x y} { 151 | if (!= x 0) 152 | { 1 } 153 | { if (!= y 0) { 1 } { 0 } } 154 | }) 155 | 156 | (doc {and} "Logical NOT") 157 | (fun {not x} { 158 | if (== x 0) { 1 } { 0 } 159 | }) 160 | 161 | (doc {pi} "Approximation of π. :-)") 162 | (def {pi} 3) 163 | -------------------------------------------------------------------------------- /test.lispy: -------------------------------------------------------------------------------- 1 | (print "Hello\nworld!") 2 | 3 | (def {my-list} {"one" "two" "three" 4 5 6}) 4 | 5 | (print (reverse my-list)) 6 | 7 | (fun {month-day-suffix i} { 8 | select 9 | {(== i 0) "st"} 10 | {(== i 1) "nd"} 11 | {(== i 3) "rd"} 12 | {otherwise "th"} 13 | }) 14 | 15 | (fun {day-name x} { 16 | case x 17 | {0 "Monday"} 18 | {1 "Tuesday"} 19 | {2 "Wednesday"} 20 | {3 "Thursday"} 21 | {4 "Friday"} 22 | {5 "Saturday"} 23 | {6 "Sunday"} 24 | }) 25 | 26 | {doc {fib} "The obligatory Fibonacci function. Usage: fib number") 27 | (fun {fib n} { 28 | select 29 | { (== n 0) 0 } 30 | { (== n 1) 1 } 31 | { otherwise (+ (fib (- n 1)) (fib (- n 2))) } 32 | }) 33 | 34 | (do (print "The 10th Fibonacci number is:") (print (fib 10)) (print "😀")) 35 | --------------------------------------------------------------------------------