diff --git a/Sources/KiteCore/Builtins.swift b/Sources/KiteCore/Builtins.swift new file mode 100644 index 0000000..0e31083 --- /dev/null +++ b/Sources/KiteCore/Builtins.swift @@ -0,0 +1,136 @@ +import Foundation + +let preludeSource = """ +(define-syntax when + (syntax-rules () + ((_ test body ...) (if test (begin body ...) #f)))) + +(define-syntax unless + (syntax-rules () + ((_ test body ...) (if test #f (begin body ...))))) +""" + +func installBuiltins(into env: Environment, interp: Interpreter) { + func def(_ name: String, _ special: SpecialKind = .none, _ fn: @escaping ([Value]) throws -> Value) { + env.define(name, .builtin(Builtin(name: name, special: special, fn: fn))) + } + + def("+") { args in + var acc = Num.int(0) + for a in args { acc = numAdd(acc, try asNum(a, "+")) } + return numToValue(acc) + } + def("*") { args in + var acc = Num.int(1) + for a in args { acc = numMul(acc, try asNum(a, "*")) } + return numToValue(acc) + } + def("-") { args in + guard let first = args.first else { throw schemeError("-: needs at least 1 argument") } + if args.count == 1 { + return numToValue(numSub(.int(0), try asNum(first, "-"))) + } + var acc = try asNum(first, "-") + for a in args[1...] { acc = numSub(acc, try asNum(a, "-")) } + return numToValue(acc) + } + def("/") { args in + guard let first = args.first else { throw schemeError("/: needs at least 1 argument") } + if args.count == 1 { + return numToValue(try numDiv(.int(1), try asNum(first, "/"))) + } + var acc = try asNum(first, "/") + for a in args[1...] { acc = try numDiv(acc, try asNum(a, "/")) } + return numToValue(acc) + } + + func chainCompare(_ name: String, _ pred: @escaping (Int) -> Bool) { + def(name) { args in + for i in 1..") { $0 > 0 } + chainCompare("<=") { $0 <= 0 } + chainCompare(">=") { $0 >= 0 } + + func intArg(_ v: Value, _ who: String) throws -> Int { + guard case .integer(let n) = v else { throw schemeError("\(who): expected integer", [v]) } + return n + } + def("modulo") { args in + let a = try intArg(args[0], "modulo") + let b = try intArg(args[1], "modulo") + if b == 0 { throw schemeError("modulo: division by zero") } + let r = a % b + return .integer((r != 0 && (r < 0) != (b < 0)) ? r + b : r) + } + def("remainder") { args in + let a = try intArg(args[0], "remainder") + let b = try intArg(args[1], "remainder") + if b == 0 { throw schemeError("remainder: division by zero") } + return .integer(a % b) + } + def("quotient") { args in + let a = try intArg(args[0], "quotient") + let b = try intArg(args[1], "quotient") + if b == 0 { throw schemeError("quotient: division by zero") } + return .integer(a / b) + } + def("abs") { args in + switch args[0] { + case .integer(let n): return .integer(abs(n)) + case .real(let d): return .real(abs(d)) + case .rational(let n, let d): return makeRational(abs(n), d) + default: throw schemeError("abs: expected number", [args[0]]) + } + } + def("min") { args in + var best = try asNum(args[0], "min") + for a in args[1...] { + let n = try asNum(a, "min") + if numCompare(n, best) < 0 { best = n } + } + return numToValue(best) + } + def("max") { args in + var best = try asNum(args[0], "max") + for a in args[1...] { + let n = try asNum(a, "max") + if numCompare(n, best) > 0 { best = n } + } + return numToValue(best) + } + def("gcd") { args in + var acc = 0 + for a in args { acc = gcdInt(acc, try intArg(a, "gcd")) } + return .integer(acc) + } + def("expt") { args in + if case .integer(let base) = args[0], case .integer(let exp) = args[1], exp >= 0 { + var result = 1 + var overflow = false + for _ in 0.. 0) } + def("negative?") { args in .bool(numCompare(try asNum(args[0], "negative?"), .int(0)) < 0) } + def("even?") { args in .bool(try intArg(args[0], "even?") % 2 == 0) } + def("odd?") { args in .bool(try intArg(args[0], "odd?") % 2 != 0) } + def("1+") { args in numToValue(numAdd(try asNum(args[0], "1+"), .int(1))) } + def("1-") { args in numToValue(numSub(try asNum(args[0], "1-"), .int(1))) } +}