#
EXES := $(patsubst %.swift,%,$(SRCS))
+#
+# Also generate references to any debug-symbol directories we may make when -g
+# is specified.
+#
+DSYMS := $(patsubst %.swift,%.dSYM,$(SRCS))
+
#
# Given a name like "./step<#>_foo", return <#>.
#
#
# Various helpful variables.
#
-SWIFT := swiftc
+DEV_DIR := $(firstword $(wildcard /Applications/Xcode-beta.app /Applications/Xcode.app))
+SWIFT := $(shell DEVELOPER_DIR="$(DEV_DIR)" xcrun --find swiftc)
+SDKROOT := $(shell DEVELOPER_DIR="$(DEV_DIR)" xcrun --show-sdk-path)
STEP_TEMPLATE := ./templates/step.swift
FILTER := ./templates/filter_steps.sh
UTIL_SRC := $(filter-out $(STEP_TEMPLATE) $(SRCS),$(wildcard ./*.swift))
-SDKROOT ?= $(shell xcrun --show-sdk-path)
-OPT := #-Ounchecked
+ifndef TYPES
+TYPES := CLASS
+endif
+ifeq ($(TYPES), ENUM)
+UTIL_SRC := $(filter-out ./types_class.swift,$(UTIL_SRC))
+else
+UTIL_SRC := $(filter-out ./types_enum.swift,$(UTIL_SRC))
+endif
+OPT := -Ounchecked -whole-module-optimization
DEBUG := #-g
EXTRA := #-v
COMMON := $(UTIL_SRC) $(OPT) $(DEBUG) $(EXTRA) -import-objc-header ./bridging-header.h -L /usr/lib -ledit -sdk $(SDKROOT)
# Build the executable from the input sources consisting of the appropriate
# "step" file and the supporting files in $(UTIL_SRC).
#
-$(EXES) : % : %.swift $(UTIL_SRC)
+$(EXES) : % : %.swift $(UTIL_SRC) ./Makefile
@echo "Making : $@"
@$(SWIFT) $< $(COMMON) -o $@
@$(FILTER) $(call get_step_number,$@) $< $@
#
-# Delete all of the built executables.
+# Delete all of the build output (other than generated "step" source files)
#
clean:
- @rm -f $(EXES)
+ @rm -rf $(EXES) $(DSYMS)
#
# Display some variables for debugging.
dump:
@echo " SRCS = $(SRCS)"
@echo " EXES = $(EXES)"
+ @echo " DSYMS = $(DSYMS)"
@echo " UTIL = $(UTIL_SRC)"
+ @echo " SWIFT = $(SWIFT)"
@echo "SDKROOT = $(SDKROOT)"
@echo " STEPS = $(call get_all_step_numbers)"
-//******************************************************************************
-// MAL - core
-//******************************************************************************
-
-import Foundation
-
-typealias MalVarArgs = ArraySlice<MalVal>
-
-func fn_eq(obj1: MalVal, obj2: MalVal) -> Bool {
- return obj1 == obj2
-}
-
-func fn_throw(exception: MalVal) -> MalVal {
- return MalError(object: exception)
-}
-
-func fn_nilQ(obj: MalVal) -> Bool {
- return is_nil(obj)
-}
-
-func fn_trueQ(obj: MalVal) -> Bool {
- return is_true(obj)
-}
-
-func fn_falseQ(obj: MalVal) -> Bool {
- return is_false(obj)
-}
-
-func fn_symbol(s: String) -> MalVal {
- return MalSymbol(symbol: s)
-}
-
-func fn_symbolQ(obj: MalVal) -> Bool {
- return is_symbol(obj)
-}
-
-func fn_keyword(s: MalVal) -> MalVal {
- if is_keyword(s) {
- return s
- }
- if is_string(s) {
- return MalKeyword(keyword: (s as! MalString).value)
- }
- return MalError(message: "expected string or keyword")
-}
-
-func fn_keywordQ(obj: MalVal) -> Bool {
- return is_keyword(obj)
-}
-
-func fn_prstr(args: MalVarArgs) -> String {
- let args_str_array = args.map { pr_str($0, true) }
- return " ".join(args_str_array)
-}
-
-func fn_str(args: MalVarArgs) -> String {
- let args_str_array = args.map { pr_str($0, false) }
- return "".join(args_str_array)
-}
-
-func fn_prn(args: MalVarArgs) {
- let args_str_array = args.map { pr_str($0, true) }
- let args_str = " ".join(args_str_array)
- println(args_str)
-}
-
-func fn_println(args: MalVarArgs) {
- let args_str_array = args.map { pr_str($0, false) }
- let args_str = " ".join(args_str_array)
- println(args_str)
-}
-
-func fn_readstring(s: String) -> MalVal {
- return read_str(s)
-}
-
-func fn_readline(s: String) -> String? {
- return _readline(s)
-}
-
-func fn_slurp(s: String) -> MalVal {
- var err: NSError? = nil
- let result = String(contentsOfFile:s, encoding: NSUTF8StringEncoding, error:&err)
- if result == nil {
- if err != nil {
- return MalError(message: "error reading file \(s): \(err!.localizedDescription)")
- }
- return MalError(message: "unknown error reading file \(s)")
- }
- return MalString(unescaped: result!);
-}
-
-func fn_lt(arg1: Int64, arg2: Int64) -> Bool {
- return arg1 < arg2
-}
-
-func fn_lte(arg1: Int64, arg2: Int64) -> Bool {
- return arg1 <= arg2
-}
-
-func fn_gt(arg1: Int64, arg2: Int64) -> Bool {
- return arg1 > arg2
-}
-
-func fn_gte(arg1: Int64, arg2: Int64) -> Bool {
- return arg1 >= arg2
-}
-
-func fn_add(arg1: Int64, arg2: Int64) -> Int64 {
- return arg1 + arg2
-}
-
-func fn_subtract(arg1: Int64, arg2: Int64) -> Int64 {
- return arg1 - arg2
-}
-
-func fn_multiply(arg1: Int64, arg2: Int64) -> Int64 {
- return arg1 * arg2
-}
-
-func fn_divide(arg1: Int64, arg2: Int64) -> Int64 {
- return arg1 / arg2
-}
-
-func fn_timems() -> Int64 {
- var time = timeval(tv_sec:0, tv_usec:0)
- let res = gettimeofday(&time, nil)
- if res == 0 {
- return (Int64(time.tv_sec) * 1_000_000 + Int64(time.tv_usec)) / 1000
- }
- return -1
-}
-
-func fn_list(args: MalVarArgs) -> MalVal {
- return MalList(slice: args)
-}
-
-func fn_listQ(obj: MalVal) -> Bool {
- return is_list(obj)
-}
-
-func fn_vector(args: MalVarArgs) -> MalVal {
- return MalVector(slice: args)
-}
-
-func fn_vectorQ(obj: MalVal) -> Bool {
- return is_vector(obj)
-}
-
-func fn_hashmap(args: MalVarArgs) -> MalVal {
- return MalHashMap(slice: args)
-}
-
-func fn_hashmapQ(obj: MalVal) -> Bool {
- return is_hashmap(obj)
-}
-
-func fn_assoc(hash: MalHashMap, args: MalVarArgs) -> MalVal {
- if args.count % 2 != 0 {
- return MalError(message: "expected even number of elements, got \(args.count)")
- }
- var new_dictionary = hash.hash
- for var index = 0; index < args.count; index += 2 {
- new_dictionary[args[index]] = args[index + 1]
- }
- return MalHashMap(hash: new_dictionary)
-}
-
-func fn_dissoc(hash: MalHashMap, args: MalVarArgs) -> MalVal {
- var new_dictionary = hash.hash
- for value in args {
- new_dictionary.removeValueForKey(value)
- }
- return MalHashMap(hash: new_dictionary)
-}
-
-func fn_get(obj: MalVal, key: MalVal) -> MalVal {
- if is_vector(obj) {
- if !is_integer(key) { return MalError(message: "expected integer key for get(vector), got \(key)") }
- let as_vector = obj as! MalVector
- let index = key as! MalInteger
- if Int(index.value) >= as_vector.count { return MalError(message: "index out of range: \(index) >= \(as_vector.count)") }
- return as_vector[Int(index.value)]
- }
- if is_hashmap(obj) {
- let as_hash = obj as! MalHashMap
- if let value = as_hash[key] { return value }
- return MalNil()
- }
- if is_nil(obj) {
- return obj
- }
- return MalError(message: "get called on unsupported type: \(obj)")
-}
-
-func fn_containsQ(obj: MalVal, key: MalVal) -> MalVal {
- if is_vector(obj) {
- if !is_integer(key) { return MalError(message: "expected integer key for contains(vector), got \(key)") }
- let as_vector = obj as! MalVector
- let index = key as! MalInteger
- return Int(index.value) < as_vector.count ? MalTrue() : MalFalse()
- }
- if is_hashmap(obj) {
- let as_hash = obj as! MalHashMap
- return as_hash[key] != nil ? MalTrue() : MalFalse()
- }
- return MalError(message: "contains? called on unsupported type: \(obj)")
-}
-
-func fn_keys(hash: MalHashMap) -> MalVal {
- return MalList(array: hash.hash.keys.array)
-}
-
-func fn_values(hash: MalHashMap) -> MalVal {
- return MalList(array: hash.hash.values.array)
-}
-
-func fn_sequentialQ(obj: MalVal) -> Bool {
- return is_sequence(obj)
-}
-
-func fn_cons(first:MalVal, rest:MalSequence) -> MalVal {
- var new_elements = [MalVal]([first])
- new_elements.extend(rest.slice)
- return MalList(array: new_elements)
-}
-
-func fn_concat(args: MalVarArgs) -> MalVal {
- var result = [MalVal]()
- for arg in args {
- if !is_sequence(arg) { return MalError(message: "expected list, got \(arg)") }
- result.extend((arg as! MalSequence).slice)
- }
- return MalList(array: result)
-}
-
-func fn_nth(list: MalSequence, index: Int) -> MalVal {
- return list[index]
-}
-
-func fn_first(arg: MalVal) -> MalVal {
- if is_nil(arg) {
- return arg
- }
- if is_sequence(arg) {
- let list = arg as! MalSequence
- return list.first()
- }
- return MalError(message: "expected list, got \(arg)")
-}
-
-func fn_rest(list: MalSequence) -> MalVal {
- return MalList(slice: list.rest().slice)
-}
-
-func fn_emptyQ(obj: MalVal) -> Bool {
- if is_sequence(obj) {
- let list = obj as! MalSequence
- return list.isEmpty
- }
- return true
-}
-
-func fn_count(obj: MalVal) -> Int64 {
- if is_nil(obj) {
- return 0
- }
- if is_sequence(obj) {
- let as_seq = obj as! MalSequence
- return Int64(as_seq.count)
- }
- if is_hashmap(obj) {
- let hash = obj as! MalHashMap
- return Int64(hash.count)
- }
- if is_string(obj) {
- let string = obj as! MalString
- return Int64(count(string.value.utf16))
- }
- return 0
-}
-
-func fn_apply(args: MalVarArgs) -> MalVal {
- if args.count < 2 { return MalError(message: "expected at least 2 arguments to apply, got \(args.count)") }
- let first = args[0]
- var middle = args[1 ..< args.count - 1]
- let last = args[args.count - 1]
- if !is_function(first) { return MalError(message: "expected function for first argument to apply, got \(first)") }
- if !is_sequence(last) { return MalError(message: "expected sequence for last argument to apply, got \(last)") }
- middle.extend((last as! MalSequence).slice)
- return (first as! MalFunction).apply(MalList(slice: middle))
-}
-
-func fn_map(fn: MalFunction, list: MalSequence) -> MalVal {
- var result = [MalVal]()
- result.reserveCapacity(list.count)
- for var index = 0; index < list.count; ++index {
- let apply_res = fn.apply(MalList(slice: list.slice[index...index]))
- if is_error(apply_res) { return apply_res }
- result.append(apply_res)
- }
- return MalList(array: result)
-}
-
-func fn_conj(first: MalSequence, rest: MalVarArgs) -> MalVal {
- var result = [MalVal]()
- result.reserveCapacity(first.count + rest.count)
- if is_list(first) {
- for var index = 0; index < rest.count; ++index {
- result.append(rest[rest.count - 1 - index])
- }
- result.extend(first.slice)
- return MalList(array: result)
- } else {
- result.extend(first.slice)
- result.extend(rest)
- return MalVector(array: result)
- }
-}
-
-func fn_meta(obj: MalVal) -> MalVal {
- return obj.meta != nil ? obj.meta! : MalNil()
-}
-
-func fn_withmeta(form:MalVal, meta:MalVal) -> MalVal {
- var new_form = form.clone()
- new_form.meta = meta
- return new_form
-}
-
-func fn_atom(obj: MalVal) -> MalVal {
- return MalAtom(object: obj)
-}
-
-func fn_atomQ(obj: MalVal) -> Bool {
- return is_atom(obj)
-}
-
-func fn_deref(form:MalVal) -> MalVal {
- if !is_atom(form) { return MalError(message: "expected atom, got \(form)") }
- return (form as! MalAtom).value
-}
-
-func fn_resetBang(atom: MalAtom, obj: MalVal) -> MalVal {
- atom.value = obj
- return obj
-}
-
-func fn_swapBang(var atom: MalAtom, fn: MalFunction, rest: MalVarArgs) -> MalVal {
- var new_args = [MalVal]([atom.value])
- new_args.extend(rest)
- let result = fn.apply(MalList(array: new_args))
- if is_error(result) { return result }
- atom.value = result
- return result
-}
-
-//******************************************************************************
-//
-// The facility for invoking built-in functions makes use of a name ->
-// function-pointer table (defined down below). The function-pointers accept a
-// sequence of MalVals and return a MalVal as a result. Each built-in function
-// that does actual work, on the other hand, may expect a different set of
-// parameters of different types, and may naturally return a result of any type.
-// In order to convert between these two types of interfaces, we have these
-// unwrap functions. These functions implement the (MalSequence) -> MalVal
-// interface expected by EVAL, and convert that information into Ints, Strings,
-// etc. expected by the built-in functions.
-//
-//******************************************************************************
-
-func with_one_parameter(args: MalSequence, fn: (MalVal) -> MalVal) -> MalVal {
- if args.count < 1 { return MalError(message: "expected at least 1 parameter, got \(args.count)") }
- let arg1 = args[0]
- //let rest = args[1..<args.count]
- return fn(arg1)
-}
-
-func with_two_parameters(args: MalSequence, fn: (MalVal, MalVal) -> MalVal) -> MalVal {
- if args.count < 2 { return MalError(message: "expected at least 2 parameter, got \(args.count)") }
- let arg1 = args[0]
- let arg2 = args[1]
- //let rest = args[2..<args.count]
- return fn(arg1, arg2)
-}
-
-// ========== 0-parameter functions ==========
-
-// () -> Int64
-
-func unwrap(args: MalSequence, fn: () -> Int64) -> MalVal {
- return MalInteger(value: fn())
-}
-
-// () -> MalVal
-
-func unwrap(args: MalSequence, fn: () -> MalVal) -> MalVal {
- return fn()
-}
-
-// ========== 1-parameter functions ==========
-
-// (MalHashMap) -> MalVal
-
-func unwrap(args: MalSequence, fn: (MalHashMap) -> MalVal) -> MalVal {
- return with_one_parameter(args) { (arg1) -> MalVal in
- if !is_hashmap(arg1) { return MalError(message: "expected hashmap, got \(arg1)") }
- return fn(arg1 as! MalHashMap)
- }
-}
-
-// (MalSequence) -> MalVal
-
-func unwrap(args: MalSequence, fn: (MalSequence) -> MalVal) -> MalVal {
- return with_one_parameter(args) { (arg1) -> MalVal in
- if !is_sequence(arg1) { return MalError(message: "expected list, got \(arg1)") }
- return fn(arg1 as! MalSequence)
- }
-}
-
-// (MalVal) -> Bool
-
-func unwrap(args: MalSequence, fn: (MalVal) -> Bool) -> MalVal {
- return with_one_parameter(args) { (arg1) -> MalVal in
- return fn(arg1) ? MalTrue() : MalFalse()
- }
-}
-
-// (MalVal) -> Int64
-
-func unwrap(args: MalSequence, fn: (MalVal) -> Int64) -> MalVal {
- return with_one_parameter(args) { (arg1) -> MalVal in
- return MalInteger(value: fn(arg1))
- }
-}
-
-// (MalVal) -> MalVal
-
-func unwrap(args: MalSequence, fn: (MalVal) -> MalVal) -> MalVal {
- return with_one_parameter(args) { (arg1) -> MalVal in
- return fn(arg1)
- }
-}
-
-// (String) -> MalVal
-
-func unwrap(args: MalSequence, fn: (String) -> MalVal) -> MalVal {
- return with_one_parameter(args) { (arg1) -> MalVal in
- if !is_string(arg1) { return MalError(message: "expected string, got \(arg1)") }
- return fn((arg1 as! MalString).value)
- }
-}
-
-// (String) -> MalVal?
-
-func unwrap(args: MalSequence, fn: (String) -> MalVal?) -> MalVal {
- return with_one_parameter(args) { (arg1) -> MalVal in
- if !is_string(arg1) { return MalError(message: "expected string, got \(arg1)") }
- let res = fn((arg1 as! MalString).value)
- return res != nil ? res! : MalNil()
- }
-}
-
-// (String) -> String
-
-func unwrap(args: MalSequence, fn: (String) -> String) -> MalVal {
- return with_one_parameter(args) { (arg1) -> MalVal in
- if !is_string(arg1) { return MalError(message: "expected string, got \(arg1)") }
- return MalString(unescaped: fn((arg1 as! MalString).value))
- }
-}
-
-// (String) -> String?
-
-func unwrap(args: MalSequence, fn: (String) -> String?) -> MalVal {
- return with_one_parameter(args) { (arg1) -> MalVal in
- if !is_string(arg1) { return MalError(message: "expected string, got \(arg1)") }
- let res = fn((arg1 as! MalString).value)
- return res != nil ? MalString(unescaped:res!) : MalNil()
- }
-}
-
-// ========== 2-parameter functions ==========
-
-// (Int64, Int64) -> Bool
-
-func unwrap(args: MalSequence, fn: (Int64, Int64) -> Bool) -> MalVal {
- return with_two_parameters(args) { (arg1, arg2) -> MalVal in
- if !is_integer(arg1) { return MalError(message: "expected number, got \(arg1)") }
- if !is_integer(arg2) { return MalError(message: "expected number, got \(arg2)") }
- return fn((arg1 as! MalInteger).value, (arg2 as! MalInteger).value) ? MalTrue() : MalFalse()
- }
-}
-
-// (Int64, Int64) -> Int64
-
-func unwrap(args: MalSequence, fn: (Int64, Int64) -> Int64) -> MalVal {
- return with_two_parameters(args) { (arg1, arg2) -> MalVal in
- if !is_integer(arg1) { return MalError(message: "expected number, got \(arg1)") }
- if !is_integer(arg2) { return MalError(message: "expected number, got \(arg2)") }
- return MalInteger(value: fn((arg1 as! MalInteger).value, (arg2 as! MalInteger).value))
- }
-}
-
-// (MalAtom, MalVal) -> MalVal
-
-func unwrap(args: MalSequence, fn: (MalAtom, MalVal) -> MalVal) -> MalVal {
- return with_two_parameters(args) { (arg1, arg2) -> MalVal in
- if !is_atom(arg1) { return MalError(message: "expected atom, got \(arg1)") }
- return fn((arg1 as! MalAtom), arg2)
- }
-}
-
-// (MalFunction, MalSequence) -> MalVal
-
-func unwrap(args: MalSequence, fn: (MalFunction, MalSequence) -> MalVal) -> MalVal {
- return with_two_parameters(args) { (arg1, arg2) -> MalVal in
- if !is_function(arg1) { return MalError(message: "expected function, got \(arg1)") }
- if !is_sequence(arg2) { return MalError(message: "expected sequence, got \(arg2)") }
- return fn((arg1 as! MalFunction), (arg2 as! MalSequence))
- }
-}
-
-// (MalSequence, Int) -> MalVal
-
-func unwrap(args: MalSequence, fn: (MalSequence, Int) -> MalVal) -> MalVal {
- return with_two_parameters(args) { (arg1, arg2) -> MalVal in
- if !is_sequence(arg1) { return MalError(message: "expected sequence, got \(arg1)") }
- if !is_integer(arg2) { return MalError(message: "expected number, got \(arg2)") }
- return fn((arg1 as! MalSequence), Int((arg2 as! MalInteger).value))
- }
-}
-
-// (MalVal, MalSequence) -> MalVal
-
-func unwrap(args: MalSequence, fn: (MalVal, MalSequence) -> MalVal) -> MalVal {
- return with_two_parameters(args) { (arg1, arg2) -> MalVal in
- if !is_sequence(arg2) { return MalError(message: "expected sequence, got \(arg2)") }
- return fn(arg1, (arg2 as! MalSequence))
- }
-}
-
-// (MalVal, MalVal) -> Bool
-
-func unwrap(args: MalSequence, fn: (MalVal, MalVal) -> Bool) -> MalVal {
- return with_two_parameters(args) { (arg1, arg2) -> MalVal in
- return fn(arg1, arg2) ? MalTrue() : MalFalse()
- }
-}
-
-// (MalVal, MalVal) -> MalVal
-
-func unwrap(args: MalSequence, fn: (MalVal, MalVal) -> MalVal) -> MalVal {
- return with_two_parameters(args) { (arg1, arg2) -> MalVal in
- return fn(arg1, arg2)
- }
-}
-
-// ========== Variadic functions ==========
-
-// (MalVarArgs) -> ()
-
-func unwrap(args: MalSequence, fn: (MalVarArgs) -> ()) -> MalVal {
- fn(args.slice)
- return MalNil()
-}
-
-// (MalVarArgs) -> String
-
-func unwrap(args: MalSequence, fn: (MalVarArgs) -> String) -> MalVal {
- return MalString(unescaped: fn(args.slice))
-}
-
-// (MalVarArgs) -> MalVal
-
-func unwrap(args: MalSequence, fn: (MalVarArgs) -> MalVal) -> MalVal {
- return fn(args.slice)
-}
-
-// (MalAtom, MalFunction, MalVarArgs) -> MalVal
-
-func unwrap(args: MalSequence, fn: (MalAtom, MalFunction, MalVarArgs) -> MalVal) -> MalVal {
- return with_two_parameters(args) { (arg1, arg2) -> MalVal in
- if !is_atom(arg1) { return MalError(message: "expected atom, got \(arg1)") }
- if !is_function(arg2) { return MalError(message: "expected function, got \(arg2)") }
- return fn((arg1 as! MalAtom), (arg2 as! MalFunction), args[2..<args.count])
- }
-}
-
-// (MalHashMap, MalVarArgs) -> MalVal
-
-func unwrap(args: MalSequence, fn: (MalHashMap, MalVarArgs) -> MalVal) -> MalVal {
- return with_one_parameter(args) { (arg1) -> MalVal in
- if !is_hashmap(arg1) { return MalError(message: "expected hashmap, got \(arg1)") }
- return fn((arg1 as! MalHashMap), args[1..<args.count])
- }
-}
-
-// (MalSequence, MalVarArgs) -> MalVal
-
-func unwrap(args: MalSequence, fn: (MalSequence, MalVarArgs) -> MalVal) -> MalVal {
- return with_one_parameter(args) { (arg1) -> MalVal in
- if !is_sequence(arg1) { return MalError(message: "expected sequence, got \(arg1)") }
- return fn((arg1 as! MalSequence), args[1..<args.count])
- }
-}
-
-// *o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*
-
-let ns: [String: MalBuiltin.BuiltinSignature] = [
- "=": { unwrap($0, fn_eq) },
- "throw": { unwrap($0, fn_throw) },
-
- "nil?": { unwrap($0, fn_nilQ) },
- "true?": { unwrap($0, fn_trueQ) },
- "false?": { unwrap($0, fn_falseQ) },
- "symbol": { unwrap($0, fn_symbol) },
- "symbol?": { unwrap($0, fn_symbolQ) },
- "keyword": { unwrap($0, fn_keyword) },
- "keyword?": { unwrap($0, fn_keywordQ) },
-
- "pr-str": { unwrap($0, fn_prstr) },
- "str": { unwrap($0, fn_str) },
- "prn": { unwrap($0, fn_prn) },
- "println": { unwrap($0, fn_println) },
- "read-string": { unwrap($0, fn_readstring) },
- "readline": { unwrap($0, fn_readline) },
- "slurp": { unwrap($0, fn_slurp) },
-
- "<": { unwrap($0, fn_lt) },
- "<=": { unwrap($0, fn_lte) },
- ">": { unwrap($0, fn_gt) },
- ">=": { unwrap($0, fn_gte) },
- "+": { unwrap($0, fn_add) },
- "-": { unwrap($0, fn_subtract) },
- "*": { unwrap($0, fn_multiply) },
- "/": { unwrap($0, fn_divide) },
- "time-ms": { unwrap($0, fn_timems) },
-
- "list": { unwrap($0, fn_list) },
- "list?": { unwrap($0, fn_listQ) },
- "vector": { unwrap($0, fn_vector) },
- "vector?": { unwrap($0, fn_vectorQ) },
- "hash-map": { unwrap($0, fn_hashmap) },
- "map?": { unwrap($0, fn_hashmapQ) },
- "assoc": { unwrap($0, fn_assoc) },
- "dissoc": { unwrap($0, fn_dissoc) },
- "get": { unwrap($0, fn_get) },
- "contains?": { unwrap($0, fn_containsQ) },
- "keys": { unwrap($0, fn_keys) },
- "vals": { unwrap($0, fn_values) },
-
- "sequential?": { unwrap($0, fn_sequentialQ) },
- "cons": { unwrap($0, fn_cons) },
- "concat": { unwrap($0, fn_concat) },
- "nth": { unwrap($0, fn_nth) },
- "first": { unwrap($0, fn_first) },
- "rest": { unwrap($0, fn_rest) },
- "empty?": { unwrap($0, fn_emptyQ) },
- "count": { unwrap($0, fn_count) },
- "apply": { unwrap($0, fn_apply) },
- "map": { unwrap($0, fn_map) },
- "conj": { unwrap($0, fn_conj) },
-
- "meta": { unwrap($0, fn_meta) },
- "with-meta": { unwrap($0, fn_withmeta) },
- "atom": { unwrap($0, fn_atom) },
- "atom?": { unwrap($0, fn_atomQ) },
- "deref": { unwrap($0, fn_deref) },
- "reset!": { unwrap($0, fn_resetBang) },
- "swap!": { unwrap($0, fn_swapBang) },
-]
-
-func load_builtins(env: Environment) {
- for (name, fn) in ns {
- env.set(MalSymbol(symbol: name), MalBuiltin(function: fn))
- }
-}
+//******************************************************************************
+// MAL - core
+//******************************************************************************
+
+import Foundation
+
+// This is a simple type distinct from all MalVal types so that we can pass a
+// sequence to a function and be able to distinguish between those functions
+// that want a sequence as a parameter and those that want a sequence that holds
+// the rest of the function parameters.
+//
+final class MalVarArgs {
+ init(_ value: MalSequence) { self.value = value }
+ init(_ value: MalVal) { self.value = as_sequence(value) }
+ let value: MalSequence
+}
+
+private func fn_eq(obj1: MalVal, obj2: MalVal) throws -> Bool {
+ return obj1 == obj2
+}
+
+private func fn_throw(exception: MalVal) throws -> MalVal {
+ try throw_error(exception)
+}
+
+private func fn_nilQ(obj: MalVal) throws -> Bool {
+ return is_nil(obj)
+}
+
+private func fn_trueQ(obj: MalVal) throws -> Bool {
+ return is_true(obj)
+}
+
+private func fn_falseQ(obj: MalVal) throws -> Bool {
+ return is_false(obj)
+}
+
+private func fn_symbol(s: String) throws -> MalVal {
+ return make_symbol(s)
+}
+
+private func fn_symbolQ(obj: MalVal) throws -> Bool {
+ return is_symbol(obj)
+}
+
+private func fn_keyword(s: MalVal) throws -> MalVal {
+ if is_keyword(s) {
+ return s
+ }
+ if is_string(s) {
+ return make_keyword(as_string(s))
+ }
+ try throw_error("expected string or keyword")
+}
+
+private func fn_keywordQ(obj: MalVal) throws -> Bool {
+ return is_keyword(obj)
+}
+
+private func fn_prstr(args: MalVarArgs) throws -> String {
+ let args_str_array = args.value.map { pr_str($0, true) }
+ return args_str_array.joinWithSeparator(" ")
+}
+
+private func fn_str(args: MalVarArgs) throws -> String {
+ let args_str_array = args.value.map { pr_str($0, false) }
+ return args_str_array.joinWithSeparator("")
+}
+
+private func fn_prn(args: MalVarArgs) {
+ let args_str_array = args.value.map { pr_str($0, true) }
+ let args_str = args_str_array.joinWithSeparator(" ")
+ print(args_str)
+}
+
+private func fn_println(args: MalVarArgs) {
+ let args_str_array = args.value.map { pr_str($0, false) }
+ let args_str = args_str_array.joinWithSeparator(" ")
+ print(args_str)
+}
+
+private func fn_readstring(s: String) throws -> MalVal {
+ return try read_str(s)
+}
+
+private func fn_readline(s: String) throws -> String? {
+ return _readline(s)
+}
+
+private func fn_slurp(s: String) throws -> MalVal {
+ do {
+ let result = try String(contentsOfFile: s, encoding: NSUTF8StringEncoding)
+ return make_string(result)
+ } catch let error as NSError {
+ try throw_error("unknown error reading file \(error)")
+ }
+}
+
+private func fn_lt(arg1: MalIntType, arg2: MalIntType) throws -> Bool {
+ return arg1 < arg2
+}
+
+private func fn_lte(arg1: MalIntType, arg2: MalIntType) throws -> Bool {
+ return arg1 <= arg2
+}
+
+private func fn_gt(arg1: MalIntType, arg2: MalIntType) throws -> Bool {
+ return arg1 > arg2
+}
+
+private func fn_gte(arg1: MalIntType, arg2: MalIntType) throws -> Bool {
+ return arg1 >= arg2
+}
+
+private func fn_add(arg1: MalIntType, arg2: MalIntType) throws -> MalIntType {
+ return arg1 + arg2
+}
+
+private func fn_subtract(arg1: MalIntType, arg2: MalIntType) throws -> MalIntType {
+ return arg1 - arg2
+}
+
+private func fn_multiply(arg1: MalIntType, arg2: MalIntType) throws -> MalIntType {
+ return arg1 * arg2
+}
+
+private func fn_divide(arg1: MalIntType, arg2: MalIntType) throws -> MalIntType {
+ return arg1 / arg2
+}
+
+private func fn_timems() throws -> MalIntType {
+ var time = timeval(tv_sec: 0, tv_usec: 0)
+ let res = gettimeofday(&time, nil)
+ if res == 0 {
+ return (MalIntType(time.tv_sec) * 1_000_000 + MalIntType(time.tv_usec)) / 1000
+ }
+ return -1
+}
+
+private func fn_list(args: MalVarArgs) throws -> MalVal {
+ return make_list(args.value)
+}
+
+private func fn_listQ(obj: MalVal) throws -> Bool {
+ return is_list(obj)
+}
+
+private func fn_vector(args: MalVarArgs) throws -> MalVal {
+ return make_vector(args.value)
+}
+
+private func fn_vectorQ(obj: MalVal) throws -> Bool {
+ return is_vector(obj)
+}
+
+private func fn_hashmap(args: MalVarArgs) throws -> MalVal {
+ return make_hashmap(args.value)
+}
+
+private func fn_hashmapQ(obj: MalVal) throws -> Bool {
+ return is_hashmap(obj)
+}
+
+private func fn_assoc(hash: MalHashMap, args: MalVarArgs) throws -> MalVal {
+ guard args.value.count % 2 == 0 else {
+ try throw_error("expected even number of elements, got \(args.value.count)")
+ }
+ var new_dictionary = hash.hash
+ for var index: MalIntType = 0; index < args.value.count; index += 2 {
+ new_dictionary[try! args.value.nth(index)] = try! args.value.nth(index + 1)
+ }
+ return make_hashmap(new_dictionary)
+}
+
+private func fn_dissoc(hash: MalHashMap, args: MalVarArgs) throws -> MalVal {
+ var new_dictionary = hash.hash
+ for value in args.value {
+ new_dictionary.removeValueForKey(value)
+ }
+ return make_hashmap(new_dictionary)
+}
+
+private func fn_get(obj: MalVal, key: MalVal) throws -> MalVal {
+ if let as_vec = as_vectorQ(obj) {
+ guard let index = as_integerQ(key) else {
+ try throw_error("expected integer key for get(vector), got \(key)")
+ }
+ let n = as_inttype(index)
+ guard n >= as_vec.count else { try throw_error("index out of range: \(n) >= \(as_vec.count)") }
+ return try! as_vec.nth(n)
+ }
+ if let as_hash = as_hashmapQ(obj) {
+ if let value = as_hash.value_for(key) { return value }
+ return make_nil()
+ }
+ if is_nil(obj) {
+ return obj
+ }
+ try throw_error("get called on unsupported type: \(obj)")
+}
+
+private func fn_containsQ(obj: MalVal, key: MalVal) throws -> MalVal {
+ if let as_vec = as_vectorQ(obj) {
+ guard let index = as_integerQ(key) else {
+ try throw_error("expected integer key for contains(vector), got \(key)")
+ }
+ let n = as_inttype(index)
+ return n < as_vec.count ? make_true() : make_false()
+ }
+ if let as_hash = as_hashmapQ(obj) {
+ return as_hash.value_for(key) != nil ? make_true() : make_false()
+ }
+ try throw_error("contains? called on unsupported type: \(obj)")
+}
+
+private func fn_keys(hash: MalHashMap) throws -> MalVal {
+ return hash.keys
+}
+
+private func fn_values(hash: MalHashMap) throws -> MalVal {
+ return hash.values
+}
+
+private func fn_sequentialQ(obj: MalVal) throws -> Bool {
+ return is_sequence(obj)
+}
+
+private func fn_cons(first: MalVal, rest: MalSequence) throws -> MalVal {
+ return rest.cons(first)
+}
+
+private func fn_concat(args: MalVarArgs) throws -> MalVal {
+ var result = make_list()
+ for arg in args.value {
+ guard let arg_as_seq = as_sequenceQ(arg) else {
+ try throw_error("expected list, got \(arg)")
+ }
+ result = try! as_sequence(result).concat(arg_as_seq)
+ }
+ return result
+}
+
+private func fn_nth(list: MalSequence, index: MalIntType) throws -> MalVal {
+ return try list.nth(index)
+}
+
+private func fn_first(arg: MalVal) throws -> MalVal {
+ if is_nil(arg) {
+ return arg
+ }
+ if let list = as_sequenceQ(arg) {
+ return list.first()
+ }
+ try throw_error("expected list, got \(arg)")
+}
+
+private func fn_rest(list: MalSequence) throws -> MalVal {
+ return list.rest()
+}
+
+private func fn_emptyQ(obj: MalVal) throws -> Bool {
+ if let list = as_sequenceQ(obj) {
+ return list.isEmpty
+ }
+ return true
+}
+
+private func fn_count(obj: MalVal) throws -> MalIntType {
+ if is_nil(obj) {
+ return 0
+ }
+ if let as_seq = as_sequenceQ(obj) {
+ return as_seq.count
+ }
+ if let as_hash = as_hashmapQ(obj) {
+ return as_hash.count
+ }
+ if let as_str = as_stringQ(obj) {
+ return MalIntType(as_stringtype(as_str).characters.count)
+ }
+ return 0
+}
+
+private func fn_apply(args: MalVarArgs) throws -> MalVal {
+ guard args.value.count >= 2 else {
+ try throw_error("expected at least 2 arguments to apply, got \(args.value.count)")
+ }
+
+ let first = args.value.first()
+ let middle = args.value.range_from(1, to: args.value.count - 1)
+ let last = args.value.last()
+
+ guard let fn = as_functionQ(first) else {
+ try throw_error("expected function for first argument to apply, got \(first)")
+ }
+ guard let seq = as_sequenceQ(last) else {
+ try throw_error("expected sequence for last argument to apply, got \(last)")
+ }
+ let exprs = try! as_sequence(middle).concat(seq)
+ return try fn.apply(as_sequence(exprs))
+}
+
+private func fn_map(fn: MalFunction, list: MalSequence) throws -> MalVal {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(list.count))
+ for var index: MalIntType = 0; index < list.count; ++index {
+ let apply_res = try fn.apply(as_sequence(make_list_from(try! list.nth(index))))
+ result.append(apply_res)
+ }
+ return make_list(result)
+}
+
+private func fn_conj(first: MalSequence, rest: MalVarArgs) throws -> MalVal {
+ return try first.conj(rest.value)
+}
+
+private func fn_meta(obj: MalVal) throws -> MalVal {
+ if let meta = get_meta(obj) {
+ return meta
+ }
+
+ return make_nil()
+}
+
+private func fn_withmeta(form: MalVal, meta: MalVal) throws -> MalVal {
+ return with_meta(form, meta)
+}
+
+private func fn_atom(obj: MalVal) throws -> MalVal {
+ return make_atom(obj)
+}
+
+private func fn_atomQ(obj: MalVal) throws -> Bool {
+ return is_atom(obj)
+}
+
+private func fn_deref(atom: MalAtom) throws -> MalVal {
+ return atom.object
+}
+
+private func fn_resetBang(atom: MalAtom, obj: MalVal) throws -> MalVal {
+ return atom.set_object(obj)
+}
+
+private func fn_swapBang(let atom: MalAtom, fn: MalFunction, rest: MalVarArgs) throws -> MalVal {
+ var new_args = make_list_from(atom.object)
+ new_args = try as_sequence(new_args).concat(rest.value)
+ let result = try fn.apply(as_sequence(new_args))
+ return atom.set_object(result)
+}
+
+//******************************************************************************
+//
+// The facility for invoking built-in functions makes use of a name ->
+// function-pointer table (defined down below). The function-pointers accept a
+// sequence of MalVals and return a MalVal as a result. Each built-in function
+// that does actual work, on the other hand, may expect a different set of
+// parameters of different types, and may naturally return a result of any type.
+// In order to convert between these two types of interfaces, we have these
+// unwrap_args functions. These functions implement the (MalSequence) -> MalVal
+// interface expected by EVAL, and convert that information into Ints, Strings,
+// etc. expected by the built-in functions.
+//
+//******************************************************************************
+
+private func with_one_parameter(args: MalSequence, @noescape fn: (MalVal) throws -> MalVal) throws -> MalVal {
+ guard args.count >= 1 else { try throw_error("expected at least 1 parameter, got \(args.count)") }
+ let arg1 = try! args.nth(0)
+ return try fn(arg1)
+}
+
+private func with_two_parameters(args: MalSequence, @noescape fn: (MalVal, MalVal) throws -> MalVal) throws -> MalVal {
+ guard args.count >= 2 else { try throw_error("expected at least 2 parameter, got \(args.count)") }
+ let arg1 = try! args.nth(0)
+ let arg2 = try! args.nth(1)
+ return try fn(arg1, arg2)
+}
+
+// ========== 0-parameter functions ==========
+
+// () -> MalIntType
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: () throws -> MalIntType) throws -> MalVal {
+ return make_integer(try fn())
+}
+
+// () -> MalVal
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: () throws -> MalVal) throws -> MalVal {
+ return try fn()
+}
+
+// ========== 1-parameter functions ==========
+
+// (MalAtom) -> MalVal
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: (MalAtom) throws -> MalVal) throws -> MalVal {
+ return try with_one_parameter(args) { (arg1) -> MalVal in
+ guard let atom = as_atomQ(arg1) else {
+ try throw_error("expected atom, got \(arg1)")
+ }
+ return try fn(atom)
+ }
+}
+
+// (MalHashMap) -> MalVal
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: (MalHashMap) throws -> MalVal) throws -> MalVal {
+ return try with_one_parameter(args) { (arg1) -> MalVal in
+ guard let hash = as_hashmapQ(arg1) else {
+ try throw_error("expected hashmap, got \(arg1)")
+ }
+ return try fn(hash)
+ }
+}
+
+// (MalSequence) -> MalVal
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: (MalSequence) throws -> MalVal) throws -> MalVal {
+ return try with_one_parameter(args) { (arg1) -> MalVal in
+ guard let seq = as_sequenceQ(arg1) else {
+ try throw_error("expected list, got \(arg1)")
+ }
+ return try fn(seq)
+ }
+}
+
+// (MalVal) -> Bool
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: (MalVal) throws -> Bool) throws -> MalVal {
+ return try with_one_parameter(args) { (arg1) -> MalVal in
+ return try fn(arg1) ? make_true() : make_false()
+ }
+}
+
+// (MalVal) -> MalIntType
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: (MalVal) throws -> MalIntType) throws -> MalVal {
+ return try with_one_parameter(args) { (arg1) -> MalVal in
+ return make_integer(try fn(arg1))
+ }
+}
+
+// (MalVal) -> MalVal
+
+func unwrap_args(args: MalSequence, @noescape forFunction fn: (MalVal) throws -> MalVal) throws -> MalVal {
+ return try with_one_parameter(args) { (arg1) -> MalVal in
+ return try fn(arg1)
+ }
+}
+
+// (String) -> MalVal
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: (String) throws -> MalVal) throws -> MalVal {
+ return try with_one_parameter(args) { (arg1) -> MalVal in
+ guard let str = as_stringQ(arg1) else {
+ try throw_error("expected string, got \(arg1)")
+ }
+ return try fn(as_stringtype(str))
+ }
+}
+
+// (String) -> MalVal?
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: (String) throws -> MalVal?) throws -> MalVal {
+ return try with_one_parameter(args) { (arg1) -> MalVal in
+ guard let str = as_stringQ(arg1) else {
+ try throw_error("expected string, got \(arg1)")
+ }
+ let res = try fn(as_stringtype(str))
+ return res != nil ? res! : make_nil()
+ }
+}
+
+// (String) -> String
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: (String) throws -> String) throws -> MalVal {
+ return try with_one_parameter(args) { (arg1) -> MalVal in
+ guard let str = as_stringQ(arg1) else {
+ try throw_error("expected string, got \(arg1)")
+ }
+ return make_string(try fn(as_stringtype(str)))
+ }
+}
+
+// (String) -> String?
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: (String) throws -> String?) throws -> MalVal {
+ return try with_one_parameter(args) { (arg1) -> MalVal in
+ guard let str = as_stringQ(arg1) else {
+ try throw_error("expected string, got \(arg1)")
+ }
+ let res = try fn(as_stringtype(str))
+ return res != nil ? make_string(res!) : make_nil()
+ }
+}
+
+// ========== 2-parameter functions ==========
+
+// (MalIntType, MalIntType) -> Bool
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: (MalIntType, MalIntType) throws -> Bool) throws -> MalVal {
+ return try with_two_parameters(args) { (arg1, arg2) -> MalVal in
+ guard let int1 = as_integerQ(arg1) else {
+ try throw_error("expected number, got \(arg1)")
+ }
+ guard let int2 = as_integerQ(arg2) else {
+ try throw_error("expected number, got \(arg2)")
+ }
+ return try fn(as_inttype(int1), as_inttype(int2)) ? make_true() : make_false()
+ }
+}
+
+// (MalIntType, MalIntType) -> MalIntType
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: (MalIntType, MalIntType) throws -> MalIntType) throws -> MalVal {
+ return try with_two_parameters(args) { (arg1, arg2) -> MalVal in
+ guard let int1 = as_integerQ(arg1) else {
+ try throw_error("expected number, got \(arg1)")
+ }
+ guard let int2 = as_integerQ(arg2) else {
+ try throw_error("expected number, got \(arg2)")
+ }
+ return make_integer(try fn(as_inttype(int1), as_inttype(int2)))
+ }
+}
+
+// (MalAtom, MalVal) -> MalVal
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: (MalAtom, MalVal) throws -> MalVal) throws -> MalVal {
+ return try with_two_parameters(args) { (arg1, arg2) -> MalVal in
+ guard let atom = as_atomQ(arg1) else {
+ try throw_error("expected atom, got \(arg1)")
+ }
+ return try fn(atom, arg2)
+ }
+}
+
+// (MalFunction, MalSequence) -> MalVal
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: (MalFunction, MalSequence) throws -> MalVal) throws -> MalVal {
+ return try with_two_parameters(args) { (arg1, arg2) -> MalVal in
+ guard let fn1 = as_functionQ(arg1) else {
+ try throw_error("expected function, got \(arg1)")
+ }
+ guard let seq2 = as_sequenceQ(arg2) else {
+ try throw_error("expected sequence, got \(arg2)")
+ }
+ return try fn(fn1, seq2)
+ }
+}
+
+// (MalSequence, MalIntType) -> MalVal
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: (MalSequence, MalIntType) throws -> MalVal) throws -> MalVal {
+ return try with_two_parameters(args) { (arg1, arg2) -> MalVal in
+ guard let seq = as_sequenceQ(arg1) else {
+ try throw_error("expected sequence, got \(arg1)")
+ }
+ guard let int = as_integerQ(arg2) else {
+ try throw_error("expected number, got \(arg2)")
+ }
+ return try fn(seq, as_inttype(int))
+ }
+}
+
+// (MalVal, MalSequence) -> MalVal
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: (MalVal, MalSequence) throws -> MalVal) throws -> MalVal {
+ return try with_two_parameters(args) { (arg1, arg2) -> MalVal in
+ guard let seq = as_sequenceQ(arg2) else {
+ try throw_error("expected sequence, got \(arg2)")
+ }
+ return try fn(arg1, seq)
+ }
+}
+
+// (MalVal, MalVal) -> Bool
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: (MalVal, MalVal) throws -> Bool) throws -> MalVal {
+ return try with_two_parameters(args) { (arg1, arg2) -> MalVal in
+ return try fn(arg1, arg2) ? make_true() : make_false()
+ }
+}
+
+// (MalVal, MalVal) -> MalVal
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: (MalVal, MalVal) throws -> MalVal) throws -> MalVal {
+ return try with_two_parameters(args) { (arg1, arg2) -> MalVal in
+ return try fn(arg1, arg2)
+ }
+}
+
+// ========== Variadic functions ==========
+
+// (MalVarArgs) -> ()
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: (MalVarArgs) throws -> ()) throws -> MalVal {
+ try fn(MalVarArgs(args))
+ return make_nil()
+}
+
+// (MalVarArgs) -> String
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: (MalVarArgs) throws -> String) throws -> MalVal {
+ return make_string(try fn(MalVarArgs(args)))
+}
+
+// (MalVarArgs) -> MalVal
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: (MalVarArgs) throws -> MalVal) throws -> MalVal {
+ return try fn(MalVarArgs(args))
+}
+
+// (MalAtom, MalFunction, MalVarArgs) -> MalVal
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: (MalAtom, MalFunction, MalVarArgs) throws -> MalVal) throws -> MalVal {
+ return try with_two_parameters(args) { (arg1, arg2) -> MalVal in
+ guard let atom = as_atomQ(arg1) else {
+ try throw_error("expected atom, got \(arg1)")
+ }
+ guard let fn2 = as_functionQ(arg2) else {
+ try throw_error("expected function, got \(arg2)")
+ }
+ return try fn(atom, fn2, MalVarArgs(as_sequence(args.rest()).rest()))
+ }
+}
+
+// (MalHashMap, MalVarArgs) -> MalVal
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: (MalHashMap, MalVarArgs) throws -> MalVal) throws -> MalVal {
+ return try with_one_parameter(args) { (arg1) -> MalVal in
+ guard let hash = as_hashmapQ(arg1) else {
+ try throw_error("expected hashmap, got \(arg1)")
+ }
+ return try fn(hash, MalVarArgs(args.rest()))
+ }
+}
+
+// (MalSequence, MalVarArgs) -> MalVal
+
+private func unwrap_args(args: MalSequence, @noescape forFunction fn: (MalSequence, MalVarArgs) throws -> MalVal) throws -> MalVal {
+ return try with_one_parameter(args) { (arg1) -> MalVal in
+ guard let seq = as_sequenceQ(arg1) else {
+ try throw_error("expected sequence, got \(arg1)")
+ }
+ return try fn(seq, MalVarArgs(args.rest()))
+ }
+}
+
+// *o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*
+
+let ns: [String: MalBuiltin.Signature] = [
+ "=": { try unwrap_args($0, forFunction: fn_eq) },
+ "throw": { try unwrap_args($0, forFunction: fn_throw) },
+
+ "nil?": { try unwrap_args($0, forFunction: fn_nilQ) },
+ "true?": { try unwrap_args($0, forFunction: fn_trueQ) },
+ "false?": { try unwrap_args($0, forFunction: fn_falseQ) },
+ "symbol": { try unwrap_args($0, forFunction: fn_symbol) },
+ "symbol?": { try unwrap_args($0, forFunction: fn_symbolQ) },
+ "keyword": { try unwrap_args($0, forFunction: fn_keyword) },
+ "keyword?": { try unwrap_args($0, forFunction: fn_keywordQ) },
+
+ "pr-str": { try unwrap_args($0, forFunction: fn_prstr) },
+ "str": { try unwrap_args($0, forFunction: fn_str) },
+ "prn": { try unwrap_args($0, forFunction: fn_prn) },
+ "println": { try unwrap_args($0, forFunction: fn_println) },
+ "read-string": { try unwrap_args($0, forFunction: fn_readstring) },
+ "readline": { try unwrap_args($0, forFunction: fn_readline) },
+ "slurp": { try unwrap_args($0, forFunction: fn_slurp) },
+
+ "<": { try unwrap_args($0, forFunction: fn_lt) },
+ "<=": { try unwrap_args($0, forFunction: fn_lte) },
+ ">": { try unwrap_args($0, forFunction: fn_gt) },
+ ">=": { try unwrap_args($0, forFunction: fn_gte) },
+ "+": { try unwrap_args($0, forFunction: fn_add) },
+ "-": { try unwrap_args($0, forFunction: fn_subtract) },
+ "*": { try unwrap_args($0, forFunction: fn_multiply) },
+ "/": { try unwrap_args($0, forFunction: fn_divide) },
+ "time-ms": { try unwrap_args($0, forFunction: fn_timems) },
+
+ "list": { try unwrap_args($0, forFunction: fn_list) },
+ "list?": { try unwrap_args($0, forFunction: fn_listQ) },
+ "vector": { try unwrap_args($0, forFunction: fn_vector) },
+ "vector?": { try unwrap_args($0, forFunction: fn_vectorQ) },
+ "hash-map": { try unwrap_args($0, forFunction: fn_hashmap) },
+ "map?": { try unwrap_args($0, forFunction: fn_hashmapQ) },
+ "assoc": { try unwrap_args($0, forFunction: fn_assoc) },
+ "dissoc": { try unwrap_args($0, forFunction: fn_dissoc) },
+ "get": { try unwrap_args($0, forFunction: fn_get) },
+ "contains?": { try unwrap_args($0, forFunction: fn_containsQ) },
+ "keys": { try unwrap_args($0, forFunction: fn_keys) },
+ "vals": { try unwrap_args($0, forFunction: fn_values) },
+
+ "sequential?": { try unwrap_args($0, forFunction: fn_sequentialQ) },
+ "cons": { try unwrap_args($0, forFunction: fn_cons) },
+ "concat": { try unwrap_args($0, forFunction: fn_concat) },
+ "nth": { try unwrap_args($0, forFunction: fn_nth) },
+ "first": { try unwrap_args($0, forFunction: fn_first) },
+ "rest": { try unwrap_args($0, forFunction: fn_rest) },
+ "empty?": { try unwrap_args($0, forFunction: fn_emptyQ) },
+ "count": { try unwrap_args($0, forFunction: fn_count) },
+ "apply": { try unwrap_args($0, forFunction: fn_apply) },
+ "map": { try unwrap_args($0, forFunction: fn_map) },
+ "conj": { try unwrap_args($0, forFunction: fn_conj) },
+
+ "meta": { try unwrap_args($0, forFunction: fn_meta) },
+ "with-meta": { try unwrap_args($0, forFunction: fn_withmeta) },
+ "atom": { try unwrap_args($0, forFunction: fn_atom) },
+ "atom?": { try unwrap_args($0, forFunction: fn_atomQ) },
+ "deref": { try unwrap_args($0, forFunction: fn_deref) },
+ "reset!": { try unwrap_args($0, forFunction: fn_resetBang) },
+ "swap!": { try unwrap_args($0, forFunction: fn_swapBang) },
+]
+
+func load_builtins(env: Environment) {
+ for (name, fn) in ns {
+ env.set(as_symbol(make_symbol(name)), make_builtin(fn))
+ }
+}
typealias EnvironmentVars = [MalSymbol: MalVal]
-let kSymbolAmpersand = MalSymbol(symbol: "&")
-let kNil = MalNil()
-let kNilSymbol = MalSymbol(symbol: "")
+private let kSymbolAmpersand = as_symbol(make_symbol("&"))
+private let kSymbolNil = as_symbol(make_symbol(""))
+private let kNil = make_nil()
-class Environment {
+final class Environment {
init(outer: Environment?) {
self.outer = outer
}
- func set_bindings(binds: MalSequence, with_exprs exprs: MalSequence) -> MalVal {
- for var index = 0; index < binds.count; ++index {
- if !is_symbol(binds[index]) { return MalError(message: "an entry in binds was not a symbol: index=\(index), binds[index]=\(binds[index])") }
- let sym = binds[index] as! MalSymbol
+ func set_bindings(binds: MalSequence, with_exprs exprs: MalSequence) throws -> MalVal {
+ for var index: MalIntType = 0; index < binds.count; ++index {
+ guard let sym = as_symbolQ(try! binds.nth(index)) else {
+ try throw_error("an entry in binds was not a symbol: index=\(index), binds[index]=\(try! binds.nth(index))")
+ }
if sym != kSymbolAmpersand {
if index < exprs.count {
- set(sym, exprs[index])
+ set(sym, try! exprs.nth(index))
} else {
set(sym, kNil)
}
continue
}
- // I keep getting messed up by the following, so here's an
- // explanation. We are iterating over two lists, and are at this
- // point:
- //
- // index
- // |
- // v
- // binds: (... & name)
- // exprs: (... a b c d e ...)
- //
- // In the following, we increment index to get to "name", and then
- // later decrement it to get to (a b c d e ...)
- if ++index >= binds.count { return MalError(message: "found & but no symbol") }
- if !is_symbol(binds[index]) { return MalError(message: "& was not followed by a symbol: index=\(index), binds[index]=\(binds[index])") }
- let rest_sym = binds[index--] as! MalSymbol
- let rest = exprs[index..<exprs.count]
- set(rest_sym, MalList(slice: rest))
+
+ guard (index + 1) < binds.count else {
+ try throw_error("found & but no symbol")
+ }
+ guard let rest_sym = as_symbolQ(try! binds.nth(index + 1)) else {
+ try throw_error("& was not followed by a symbol: index=\(index), binds[index]=\(try! binds.nth(index))")
+ }
+ let rest = exprs.range_from(index, to: exprs.count)
+ set(rest_sym, rest)
break
}
return kNil
}
+ // In this implementation, rather than storing everything in a dictionary,
+ // we optimize for small environments by having a hard-coded set of four
+ // slots. We use these slots when creating small environments, such as when
+ // a function is invoked. Testing shows that supporting up to four variables
+ // in this way is a good trade-off. Otherwise, if we have more than four
+ // variables, we switch over to using a dictionary. Testing also shows that
+ // trying to use both the slots and the dictionary for large environments is
+ // not as efficient as just completely switching over to the dictionary.
+ //
+ // Interestingly, even though the MalVal return value is hardly ever used at
+ // the call site, removing it and returning nothing is a performance loss.
+ // This is because returning 'value' allows the compiler to skip calling
+ // swift_release on it. The result is that set() calls swift_release twice
+ // (on self and sym), as opposed to three times (on self, sym, and value) if
+ // it were to return something other than one of the parameters.
+
func set(sym: MalSymbol, _ value: MalVal) -> MalVal {
if num_bindings == 0 {
slot_name0 = sym; slot_value0 = value; ++num_bindings
}
func get(sym: MalSymbol) -> MalVal? {
- if num_bindings > 4 { if let val = data[sym] { return val } }
+ if num_bindings > 4 { if let val = data[sym] { return val }; return outer?.get(sym) }
if num_bindings > 3 { if slot_name3 == sym { return slot_value3 } }
if num_bindings > 2 { if slot_name2 == sym { return slot_value2 } }
if num_bindings > 1 { if slot_name1 == sym { return slot_value1 } }
private var outer: Environment?
private var data = EnvironmentVars()
private var num_bindings = 0
- private var slot_name0: MalSymbol = kNilSymbol
- private var slot_name1: MalSymbol = kNilSymbol
- private var slot_name2: MalSymbol = kNilSymbol
- private var slot_name3: MalSymbol = kNilSymbol
- private var slot_value0: MalVal = kNil
- private var slot_value1: MalVal = kNil
- private var slot_value2: MalVal = kNil
- private var slot_value3: MalVal = kNil
+ private var slot_name0 = kSymbolNil
+ private var slot_name1 = kSymbolNil
+ private var slot_name2 = kSymbolNil
+ private var slot_name3 = kSymbolNil
+ private var slot_value0 = kNil
+ private var slot_value1 = kNil
+ private var slot_value2 = kNil
+ private var slot_value3 = kNil
}
import Foundation
+var MalValPrintReadably = true
+
func with_print_readably<T>(print_readably: Bool, fn: () -> T) -> T {
let old = MalValPrintReadably
MalValPrintReadably = print_readably
return result
}
-func pr_str(m: MalVal, print_readably: Bool) -> String {
+func pr_str(m: MalVal, _ print_readably: Bool = MalValPrintReadably) -> String {
return with_print_readably(print_readably) {
- m.description
+ if is_string(m) {
+ return print_readably ? escape(m.description) : m.description
+ }
+ if is_keyword(m) {
+ return ":\(m.description)"
+ }
+ return m.description
}
}
-//******************************************************************************
-// MAL - reader
-//******************************************************************************
-
-import Foundation
-
-let kSymbolWithMeta = MalSymbol(symbol: "with-meta")
-let kSymbolDeref = MalSymbol(symbol: "deref")
-
-let token_pattern =
- "[[:space:],]*" + // Skip whitespace: a sequence of zero or more commas or [:space:]'s
- "(" +
- "~@" + // Literal "~@"
- "|" +
- "[\\[\\]{}()`'~^@]" + // Punctuation: Any one of []{}()`'~^@
- "|" +
- "\"(?:\\\\.|[^\\\\\"])*\"" + // Quoted string: characters other than \ or ", or any escaped characters
- "|" +
- ";.*" + // Comment: semicolon followed by anything
- "|" +
- "[^[:space:]\\[\\]{}()`'\",;]*" + // Symbol, keyword, number, nil, true, false: any sequence of chars but [:space:] or []{}()`'",;
- ")"
-
-let atom_pattern =
- "(^;.*$)" + // Comment
- "|" +
- "(^-?[0-9]+$)" + // Integer
- "|" +
- "(^-?[0-9][0-9.]*$)" + // Float
- "|" +
- "(^nil$)" + // nil
- "|" +
- "(^true$)" + // true
- "|" +
- "(^false$)" + // false
- "|" +
- "(^\".*\"$)" + // String
- "|" +
- "(:.*)" + // Keyword
- "|" +
- "(^[^\"]*$)" // Symbol
-
-var token_regex: NSRegularExpression = NSRegularExpression(pattern:token_pattern, options:.allZeros, error:nil)!
-var atom_regex: NSRegularExpression = NSRegularExpression(pattern:atom_pattern, options:.allZeros, error:nil)!
-
-class Reader {
-
- init(_ tokens: [String]) {
- self.tokens = tokens
- self.index = 0
- }
-
- func next() -> String? {
- let token = peek()
- increment()
- return token
- }
-
- func peek() -> String? {
- if index < tokens.count {
- return tokens[index]
- }
- return nil
- }
-
- private func increment() {
- ++index
- }
-
- private let tokens: [String]
- private var index: Int
-}
-
-func tokenizer(s: String) -> [String] {
- var tokens = [String]()
- let range = NSMakeRange(0, count(s.utf16))
- let matches = token_regex.matchesInString(s, options:.allZeros, range:range)
- for match in matches as! [NSTextCheckingResult] {
- if match.range.length > 0 {
- let token = (s as NSString).substringWithRange(match.rangeAtIndex(1))
- tokens.append(token)
- }
- }
- return tokens
-}
-
-private func have_match_at(match: NSTextCheckingResult, index:Int) -> Bool {
- return Int64(match.rangeAtIndex(index).location) < LLONG_MAX
-}
-
-func read_atom(token: String) -> MalVal {
- let range = NSMakeRange(0, count(token.utf16))
- let matches = atom_regex.matchesInString(token, options:.allZeros, range:range)
- for match in matches as! [NSTextCheckingResult] {
- if have_match_at(match, 1) { // Comment
- return MalComment(comment: token)
- } else if have_match_at(match, 2) { // Integer
- if let value = NSNumberFormatter().numberFromString(token)?.longLongValue {
- return MalInteger(value: value)
- }
- return MalError(message: "invalid integer: \(token)")
- } else if have_match_at(match, 3) { // Float
- if let value = NSNumberFormatter().numberFromString(token)?.doubleValue {
- return MalFloat(value: value)
- }
- return MalError(message: "invalid float: \(token)")
- } else if have_match_at(match, 4) { // nil
- return MalNil()
- } else if have_match_at(match, 5) { // true
- return MalTrue()
- } else if have_match_at(match, 6) { // false
- return MalFalse()
- } else if have_match_at(match, 7) { // String
- return MalString(escaped: token)
- } else if have_match_at(match, 8) { // Keyword
- return MalKeyword(keyword: dropFirst(token))
- } else if have_match_at(match, 9) { // Symbol
- return MalSymbol(symbol: token)
- }
- }
- return MalError(message: "Unknown token=\(token)")
-}
-
-func read_elements(r: Reader, open: String, close: String) -> ([MalVal]?, MalVal?) {
- var list = [MalVal]()
- while let token = r.peek() {
- if token == close {
- r.increment() // Consume the closing paren
- return (list, nil)
- } else {
- let item = read_form(r)
- if is_error(item) {
- return (nil, item)
- }
- if item.type != .TypeComment {
- list.append(item)
- }
- }
- }
- return (nil, MalError(message: "ran out of tokens -- possibly unbalanced ()'s"))
-}
-
-func read_list(r: Reader) -> MalVal {
- let (list, err) = read_elements(r, "(", ")")
- return err != nil ? err! : MalList(array: list!)
-}
-
-func read_vector(r: Reader) -> MalVal {
- let (list, err) = read_elements(r, "[", "]")
- return err != nil ? err! : MalVector(array: list!)
-}
-
-func read_hashmap(r: Reader) -> MalVal {
- let (list, err) = read_elements(r, "{", "}")
- return err != nil ? err! : MalHashMap(array: list!)
-}
-
-func common_quote(r: Reader, symbol: String) -> MalVal {
- let next = read_form(r)
- if is_error(next) { return next }
- return MalList(objects: MalSymbol(symbol: symbol), next)
-}
-
-func read_form(r: Reader) -> MalVal {
- if let token = r.next() {
- switch token {
- case "(":
- return read_list(r)
- case ")":
- return MalError(message: "unexpected \")\"")
- case "[":
- return read_vector(r)
- case "]":
- return MalError(message: "unexpected \"]\"")
- case "{":
- return read_hashmap(r)
- case "}":
- return MalError(message: "unexpected \"}\"")
- case "`":
- return common_quote(r, "quasiquote")
- case "'":
- return common_quote(r, "quote")
- case "~":
- return common_quote(r, "unquote")
- case "~@":
- return common_quote(r, "splice-unquote")
- case "^":
- let meta = read_form(r)
- if is_error(meta) { return meta }
- let form = read_form(r)
- if is_error(form) { return form }
- return MalList(objects: kSymbolWithMeta, form, meta)
- case "@":
- let form = read_form(r)
- if is_error(form) { return form }
- return MalList(objects: kSymbolDeref, form)
- default:
- return read_atom(token)
- }
- }
- return MalError(message: "ran out of tokens -- possibly unbalanced ()'s")
-}
-
-func read_str(s: String) -> MalVal {
- let tokens = tokenizer(s)
- let reader = Reader(tokens)
- let obj = read_form(reader)
- return obj
-}
+//******************************************************************************
+// MAL - reader
+//******************************************************************************
+
+import Foundation
+
+private let kSymbolWithMeta = make_symbol("with-meta")
+private let kSymbolDeref = make_symbol("deref")
+
+private let token_pattern =
+ "[[:space:],]*" + // Skip whitespace: a sequence of zero or more commas or [:space:]'s
+ "(" +
+ "~@" + // Literal "~@"
+ "|" +
+ "[\\[\\]{}()`'~^@]" + // Punctuation: Any one of []{}()`'~^@
+ "|" +
+ "\"(?:\\\\.|[^\\\\\"])*\"" + // Quoted string: characters other than \ or ", or any escaped characters
+ "|" +
+ ";.*" + // Comment: semicolon followed by anything
+ "|" +
+ "[^[:space:]\\[\\]{}()`'\",;]*" + // Symbol, keyword, number, nil, true, false: any sequence of chars but [:space:] or []{}()`'",;
+ ")"
+
+private let atom_pattern =
+ "(^;.*$)" + // Comment
+ "|" +
+ "(^-?[0-9]+$)" + // Integer
+ "|" +
+ "(^-?[0-9][0-9.]*$)" + // Float
+ "|" +
+ "(^nil$)" + // nil
+ "|" +
+ "(^true$)" + // true
+ "|" +
+ "(^false$)" + // false
+ "|" +
+ "(^\".*\"$)" + // String
+ "|" +
+ "(:.*)" + // Keyword
+ "|" +
+ "(^[^\"]*$)" // Symbol
+
+private var token_regex: NSRegularExpression = try! NSRegularExpression(pattern: token_pattern, options: NSRegularExpressionOptions())
+private var atom_regex: NSRegularExpression = try! NSRegularExpression(pattern: atom_pattern, options: NSRegularExpressionOptions())
+
+private final class Reader {
+
+ init(_ tokens: [String]) {
+ self.tokens = tokens
+ self.index = 0
+ }
+
+ func next() -> String? {
+ let token = peek()
+ increment()
+ return token
+ }
+
+ func peek() -> String? {
+ if index < tokens.count {
+ return tokens[index]
+ }
+ return nil
+ }
+
+ private func increment() {
+ ++index
+ }
+
+ private let tokens: [String]
+ private var index: Int
+}
+
+private func tokenizer(s: String) -> [String] {
+ var tokens = [String]()
+ let range = NSMakeRange(0, s.characters.count)
+ let matches = token_regex.matchesInString(s, options: NSMatchingOptions(), range: range)
+ for match in matches {
+ if match.range.length > 0 {
+ let token = (s as NSString).substringWithRange(match.rangeAtIndex(1))
+ tokens.append(token)
+ }
+ }
+ return tokens
+}
+
+private func have_match(match: NSTextCheckingResult, at_index index: Int) -> Bool {
+ return Int64(match.rangeAtIndex(index).location) < LLONG_MAX
+}
+
+private func read_atom(token: String) throws -> MalVal {
+ let range = NSMakeRange(0, token.characters.count)
+ let matches = atom_regex.matchesInString(token, options: NSMatchingOptions(), range: range)
+ for match in matches {
+ if have_match(match, at_index: 1) { // Comment
+ return make_comment()
+ } else if have_match(match, at_index: 2) { // Integer
+ guard let value = NSNumberFormatter().numberFromString(token)?.longLongValue else {
+ try throw_error("invalid integer: \(token)")
+ }
+ return make_integer(value)
+ } else if have_match(match, at_index: 3) { // Float
+ guard let value = NSNumberFormatter().numberFromString(token)?.doubleValue else {
+ try throw_error("invalid float: \(token)")
+ }
+ return make_float(value)
+ } else if have_match(match, at_index: 4) { // nil
+ return make_nil()
+ } else if have_match(match, at_index: 5) { // true
+ return make_true()
+ } else if have_match(match, at_index: 6) { // false
+ return make_false()
+ } else if have_match(match, at_index: 7) { // String
+ return make_string(unescape(token))
+ } else if have_match(match, at_index: 8) { // Keyword
+ return make_keyword(token[token.startIndex.successor() ..< token.endIndex])
+ } else if have_match(match, at_index: 9) { // Symbol
+ return make_symbol(token)
+ }
+ }
+ try throw_error("Unknown token=\(token)")
+}
+
+private func read_elements(r: Reader, _ open: String, _ close: String) throws -> [MalVal] {
+ var list = [MalVal]()
+ while let token = r.peek() {
+ if token == close {
+ r.increment() // Consume the closing paren
+ return list
+ } else {
+ let item = try read_form(r)
+ if !is_comment(item) {
+ list.append(item)
+ }
+ }
+ }
+ try throw_error("ran out of tokens -- possibly unbalanced ()'s")
+}
+
+private func read_list(r: Reader) throws -> MalVal {
+ return make_list(try read_elements(r, "(", ")"))
+}
+
+private func read_vector(r: Reader) throws -> MalVal {
+ return make_vector(try read_elements(r, "[", "]"))
+}
+
+private func read_hashmap(r: Reader) throws -> MalVal {
+ return make_hashmap(try read_elements(r, "{", "}"))
+}
+
+private func common_quote(r: Reader, _ symbol: String) throws -> MalVal {
+ let next = try read_form(r)
+ return make_list_from(make_symbol(symbol), next)
+}
+
+private func read_form(r: Reader) throws -> MalVal {
+ if let token = r.next() {
+ switch token {
+ case "(":
+ return try read_list(r)
+ case ")":
+ try throw_error("unexpected \")\"")
+ case "[":
+ return try read_vector(r)
+ case "]":
+ try throw_error("unexpected \"]\"")
+ case "{":
+ return try read_hashmap(r)
+ case "}":
+ try throw_error("unexpected \"}\"")
+ case "`":
+ return try common_quote(r, "quasiquote")
+ case "'":
+ return try common_quote(r, "quote")
+ case "~":
+ return try common_quote(r, "unquote")
+ case "~@":
+ return try common_quote(r, "splice-unquote")
+ case "^":
+ let meta = try read_form(r)
+ let form = try read_form(r)
+ return make_list_from(kSymbolWithMeta, form, meta)
+ case "@":
+ let form = try read_form(r)
+ return make_list_from(kSymbolDeref, form)
+ default:
+ return try read_atom(token)
+ }
+ }
+ try throw_error("ran out of tokens -- possibly unbalanced ()'s")
+}
+
+func read_str(s: String) throws -> MalVal {
+ let tokens = tokenizer(s)
+ let reader = Reader(tokens)
+ let obj = try read_form(reader)
+ return obj
+}
private let HISTORY_FILE = "~/.mal-history"
-func with_history_file(do_to_history_file:(UnsafePointer<Int8>) -> ()) {
+private func with_history_file(do_to_history_file: (UnsafePointer<Int8>) -> ()) {
HISTORY_FILE.withCString {
(c_str) -> () in
let abs_path = tilde_expand(UnsafeMutablePointer<Int8>(c_str))
// Parse the string into an AST.
//
-func READ(str: String) -> String {
+private func READ(str: String) -> String {
return str
}
// Walk the AST and completely evaluate it, handling macro expansions, special
// forms and function calls.
//
-func EVAL(ast: String) -> String {
+private func EVAL(ast: String) -> String {
return ast
}
// Convert the value into a human-readable string for printing.
//
-func PRINT(exp: String) -> String {
+private func PRINT(exp: String) -> String {
return exp
}
// Perform the READ and EVAL steps. Useful for when you don't care about the
// printable result.
//
-func RE(text: String) -> String {
+private func RE(text: String) -> String {
let ast = READ(text)
let exp = EVAL(ast)
return exp
// Perform the full READ/EVAL/PRINT, returning a printable string.
//
-func REP(text: String) -> String {
+private func REP(text: String) -> String {
let exp = RE(text)
return PRINT(exp)
}
// Perform the full REPL.
//
-func REPL() {
+private func REPL() {
while true {
if let text = _readline("user> ") {
- println("\(REP(text))")
+ print("\(REP(text))")
} else {
- println()
+ print("")
break
}
}
// Parse the string into an AST.
//
-func READ(str: String) -> MalVal {
- return read_str(str)
+private func READ(str: String) throws -> MalVal {
+ return try read_str(str)
}
// Walk the AST and completely evaluate it, handling macro expansions, special
// forms and function calls.
//
-func EVAL(ast: MalVal) -> MalVal {
- if is_error(ast) { return ast }
+private func EVAL(ast: MalVal) -> MalVal {
return ast
}
// Convert the value into a human-readable string for printing.
//
-func PRINT(exp: MalVal) -> String? {
- if is_error(exp) { return nil }
+private func PRINT(exp: MalVal) -> String {
return pr_str(exp, true)
}
// Perform the READ and EVAL steps. Useful for when you don't care about the
// printable result.
//
-func RE(text: String) -> MalVal? {
- if text.isEmpty { return nil }
- let ast = READ(text)
- if is_error(ast) {
- println("Error parsing input: \(ast)")
- return nil
- }
- let exp = EVAL(ast)
- if is_error(exp) {
- println("Error evaluating input: \(exp)")
- return nil
+private func RE(text: String) -> MalVal? {
+ if !text.isEmpty {
+ do {
+ let ast = try READ(text)
+ return EVAL(ast)
+ } catch let error as MalException {
+ print("Error parsing input: \(error)")
+ } catch {
+ print("Error parsing input: \(error)")
+ }
}
- return exp
+ return nil
}
// Perform the full READ/EVAL/PRINT, returning a printable string.
//
-func REP(text: String) -> String? {
+private func REP(text: String) -> String? {
let exp = RE(text)
if exp == nil { return nil }
return PRINT(exp!)
// Perform the full REPL.
//
-func REPL() {
+private func REPL() {
while true {
if let text = _readline("user> ") {
if let output = REP(text) {
- println("\(output)")
+ print("\(output)")
}
} else {
- println()
+ print("")
break
}
}
// Parse the string into an AST.
//
-func READ(str: String) -> MalVal {
- return read_str(str)
+private func READ(str: String) throws -> MalVal {
+ return try read_str(str)
}
// Perform a simple evaluation of the `ast` object. If it's a symbol,
// elements (or just the values, in the case of the hashmap). Otherwise, return
// the object unchanged.
//
-func eval_ast(ast: MalVal, env: Environment) -> MalVal {
- switch ast.type {
- case .TypeSymbol:
- let symbol = ast as! MalSymbol
- if let val = env.get(symbol) {
- return val
- }
- return MalError(message: "'\(symbol)' not found") // Specific text needed to match MAL unit tests
- case .TypeList:
- let list = ast as! MalList
- var result = [MalVal]()
- result.reserveCapacity(list.count)
- for item in list {
- let eval = EVAL(item, env)
- if is_error(eval) { return eval }
- result.append(eval)
- }
- return MalList(array: result)
- case .TypeVector:
- let vec = ast as! MalVector
- var result = [MalVal]()
- result.reserveCapacity(vec.count)
- for item in vec {
- let eval = EVAL(item, env)
- if is_error(eval) { return eval }
- result.append(eval)
- }
- return MalVector(array: result)
- case .TypeHashMap:
- let hash = ast as! MalHashMap
- var result = [MalVal]()
- result.reserveCapacity(hash.count * 2)
- for (k, v) in hash {
- let new_v = EVAL(v, env)
- if is_error(new_v) { return new_v }
- result.append(k)
- result.append(new_v)
- }
- return MalHashMap(array: result)
- default:
- return ast
+private func eval_ast(ast: MalVal, _ env: Environment) throws -> MalVal {
+ if let symbol = as_symbolQ(ast) {
+ guard let val = env.get(symbol) else {
+ try throw_error("'\(symbol)' not found") // Specific text needed to match MAL unit tests
+ }
+ return val
+ }
+ if let list = as_listQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(list.count))
+ for item in list {
+ let eval = try EVAL(item, env)
+ result.append(eval)
+ }
+ return make_list(result)
}
+ if let vec = as_vectorQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(vec.count))
+ for item in vec {
+ let eval = try EVAL(item, env)
+ result.append(eval)
+ }
+ return make_vector(result)
+ }
+ if let hash = as_hashmapQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(hash.count) * 2)
+ for (k, v) in hash {
+ let new_v = try EVAL(v, env)
+ result.append(k)
+ result.append(new_v)
+ }
+ return make_hashmap(result)
+ }
+ return ast
}
// Walk the AST and completely evaluate it, handling macro expansions, special
// forms and function calls.
//
-func EVAL(var ast: MalVal, var env: Environment) -> MalVal {
- if is_error(ast) { return ast }
+private func EVAL(ast: MalVal, _ env: Environment) throws -> MalVal {
if !is_list(ast) {
// Not a list -- just evaluate and return.
- let answer = eval_ast(ast, env)
+ let answer = try eval_ast(ast, env)
return answer
}
// Special handling if it's a list.
- var list = ast as! MalList
+ let list = as_list(ast)
if list.isEmpty {
- return list
+ return ast
}
// Standard list to be applied. Evaluate all the elements first.
- let eval = eval_ast(ast, env)
- if is_error(eval) { return eval }
+ let eval = try eval_ast(ast, env)
// The result had better be a list and better be non-empty.
- let eval_list = eval as! MalList
+ let eval_list = as_list(eval)
if eval_list.isEmpty {
- return eval_list
+ return eval
}
// Get the first element of the list and execute it.
let first = eval_list.first()
- let rest = eval_list.rest()
+ let rest = as_sequence(eval_list.rest())
- if is_builtin(first) {
- let fn = first as! MalBuiltin
- let answer = fn.apply(rest)
+ if let fn = as_builtinQ(first) {
+ let answer = try fn.apply(rest)
return answer
}
// The first element wasn't a function to be executed. Return an
// error saying so.
- return MalError(message: "first list item does not evaluate to a function: \(first)")
+ try throw_error("first list item does not evaluate to a function: \(first)")
}
// Convert the value into a human-readable string for printing.
//
-func PRINT(exp: MalVal) -> String? {
- if is_error(exp) { return nil }
+private func PRINT(exp: MalVal) -> String {
return pr_str(exp, true)
}
// Perform the READ and EVAL steps. Useful for when you don't care about the
// printable result.
//
-func RE(text: String, env: Environment) -> MalVal? {
- if text.isEmpty { return nil }
- let ast = READ(text)
- if is_error(ast) {
- println("Error parsing input: \(ast)")
- return nil
- }
- let exp = EVAL(ast, env)
- if is_error(exp) {
- println("Error evaluating input: \(exp)")
- return nil
+private func RE(text: String, _ env: Environment) -> MalVal? {
+ if !text.isEmpty {
+ do {
+ let ast = try READ(text)
+ do {
+ return try EVAL(ast, env)
+ } catch let error as MalException {
+ print("Error evaluating input: \(error)")
+ } catch {
+ print("Error evaluating input: \(error)")
+ }
+ } catch let error as MalException {
+ print("Error parsing input: \(error)")
+ } catch {
+ print("Error parsing input: \(error)")
+ }
}
- return exp
+ return nil
}
// Perform the full READ/EVAL/PRINT, returning a printable string.
//
-func REP(text: String, env: Environment) -> String? {
+private func REP(text: String, _ env: Environment) -> String? {
let exp = RE(text, env)
if exp == nil { return nil }
return PRINT(exp!)
// Perform the full REPL.
//
-func REPL(env: Environment) {
+private func REPL(env: Environment) {
while true {
if let text = _readline("user> ") {
if let output = REP(text, env) {
- println("\(output)")
+ print("\(output)")
}
} else {
- println()
+ print("")
break
}
}
}
func main() {
- var env = Environment(outer: nil)
+ let env = Environment(outer: nil)
load_history_file()
load_builtins(env)
-//******************************************************************************
-// MAL - step 3 - env
-//******************************************************************************
-// This file is automatically generated from templates/step.swift. Rather than
-// editing it directly, it's probably better to edit templates/step.swift and
-// regenerate this file. Otherwise, your change might be lost if/when someone
-// else performs that process.
-//******************************************************************************
-
-import Foundation
-
-// Symbols used in this module.
-//
-let kSymbolDef = MalSymbol(symbol: "def!")
-let kSymbolLet = MalSymbol(symbol: "let*")
-
-// Parse the string into an AST.
-//
-func READ(str: String) -> MalVal {
- return read_str(str)
-}
-
-// Perform a simple evaluation of the `ast` object. If it's a symbol,
-// dereference it and return its value. If it's a collection, call EVAL on all
-// elements (or just the values, in the case of the hashmap). Otherwise, return
-// the object unchanged.
-//
-func eval_ast(ast: MalVal, env: Environment) -> MalVal {
- switch ast.type {
- case .TypeSymbol:
- let symbol = ast as! MalSymbol
- if let val = env.get(symbol) {
- return val
- }
- return MalError(message: "'\(symbol)' not found") // Specific text needed to match MAL unit tests
- case .TypeList:
- let list = ast as! MalList
- var result = [MalVal]()
- result.reserveCapacity(list.count)
- for item in list {
- let eval = EVAL(item, env)
- if is_error(eval) { return eval }
- result.append(eval)
- }
- return MalList(array: result)
- case .TypeVector:
- let vec = ast as! MalVector
- var result = [MalVal]()
- result.reserveCapacity(vec.count)
- for item in vec {
- let eval = EVAL(item, env)
- if is_error(eval) { return eval }
- result.append(eval)
- }
- return MalVector(array: result)
- case .TypeHashMap:
- let hash = ast as! MalHashMap
- var result = [MalVal]()
- result.reserveCapacity(hash.count * 2)
- for (k, v) in hash {
- let new_v = EVAL(v, env)
- if is_error(new_v) { return new_v }
- result.append(k)
- result.append(new_v)
- }
- return MalHashMap(array: result)
- default:
- return ast
- }
-}
-
-// EVALuate "def!".
-//
-func eval_def(list: MalSequence, env: Environment) -> MalVal {
- if list.count != 3 {
- return MalError(message: "expected 2 arguments to def!, got \(list.count - 1)")
- }
- let arg1 = list[1]
- let arg2 = list[2]
- if !is_symbol(arg1) {
- return MalError(message: "expected symbol for first argument to def!")
- }
- let sym = arg1 as! MalSymbol
- let value = EVAL(arg2, env)
- if is_error(value) { return value }
- return env.set(sym, value)
-}
-
-// EVALuate "let*".
-//
-func eval_let(list: MalSequence, env: Environment) -> MalVal {
- if list.count != 3 {
- return MalError(message: "expected 2 arguments to let*, got \(list.count - 1)")
- }
- let arg1 = list[1]
- let arg2 = list[2]
- if !is_sequence(arg1) {
- return MalError(message: "expected list for first argument to let*")
- }
- let bindings = arg1 as! MalSequence
- if bindings.count % 2 == 1 {
- return MalError(message: "expected even number of elements in bindings to let*, got \(bindings.count)")
- }
- var new_env = Environment(outer: env)
- for var index = 0; index < bindings.count; index += 2 {
- let binding_name = bindings[index]
- let binding_value = bindings[index + 1]
-
- if !is_symbol(binding_name) {
- return MalError(message: "expected symbol for first element in binding pair")
- }
- let binding_symbol = binding_name as! MalSymbol
- let evaluated_value = EVAL(binding_value, new_env)
- if is_error(evaluated_value) { return evaluated_value }
- new_env.set(binding_symbol, evaluated_value)
- }
- return EVAL(arg2, new_env)
-}
-
-// Walk the AST and completely evaluate it, handling macro expansions, special
-// forms and function calls.
-//
-func EVAL(var ast: MalVal, var env: Environment) -> MalVal {
- if is_error(ast) { return ast }
-
- if !is_list(ast) {
-
- // Not a list -- just evaluate and return.
-
- let answer = eval_ast(ast, env)
- return answer
- }
-
- // Special handling if it's a list.
-
- var list = ast as! MalList
-
- if list.isEmpty {
- return list
- }
-
- // Check for special forms, where we want to check the operation
- // before evaluating all of the parameters.
-
- let arg0 = list.first()
- if is_symbol(arg0) {
- let fn_symbol = arg0 as! MalSymbol
-
- switch fn_symbol {
- case kSymbolDef: return eval_def(list, env)
- case kSymbolLet: return eval_let(list, env)
- default: break
- }
- }
-
- // Standard list to be applied. Evaluate all the elements first.
-
- let eval = eval_ast(ast, env)
- if is_error(eval) { return eval }
-
- // The result had better be a list and better be non-empty.
-
- let eval_list = eval as! MalList
- if eval_list.isEmpty {
- return eval_list
- }
-
- // Get the first element of the list and execute it.
-
- let first = eval_list.first()
- let rest = eval_list.rest()
-
- if is_builtin(first) {
- let fn = first as! MalBuiltin
- let answer = fn.apply(rest)
- return answer
- }
-
- // The first element wasn't a function to be executed. Return an
- // error saying so.
-
- return MalError(message: "first list item does not evaluate to a function: \(first)")
-}
-
-// Convert the value into a human-readable string for printing.
-//
-func PRINT(exp: MalVal) -> String? {
- if is_error(exp) { return nil }
- return pr_str(exp, true)
-}
-
-// Perform the READ and EVAL steps. Useful for when you don't care about the
-// printable result.
-//
-func RE(text: String, env: Environment) -> MalVal? {
- if text.isEmpty { return nil }
- let ast = READ(text)
- if is_error(ast) {
- println("Error parsing input: \(ast)")
- return nil
- }
- let exp = EVAL(ast, env)
- if is_error(exp) {
- println("Error evaluating input: \(exp)")
- return nil
- }
- return exp
-}
-
-// Perform the full READ/EVAL/PRINT, returning a printable string.
-//
-func REP(text: String, env: Environment) -> String? {
- let exp = RE(text, env)
- if exp == nil { return nil }
- return PRINT(exp!)
-}
-
-// Perform the full REPL.
-//
-func REPL(env: Environment) {
- while true {
- if let text = _readline("user> ") {
- if let output = REP(text, env) {
- println("\(output)")
- }
- } else {
- println()
- break
- }
- }
-}
-
-func main() {
- var env = Environment(outer: nil)
-
- load_history_file()
- load_builtins(env)
-
- REPL(env)
-
- save_history_file()
-}
+//******************************************************************************
+// MAL - step 3 - env
+//******************************************************************************
+// This file is automatically generated from templates/step.swift. Rather than
+// editing it directly, it's probably better to edit templates/step.swift and
+// regenerate this file. Otherwise, your change might be lost if/when someone
+// else performs that process.
+//******************************************************************************
+
+import Foundation
+
+// Symbols used in this module.
+//
+private let kValDef = make_symbol("def!")
+private let kValLet = make_symbol("let*")
+private let kValTry = make_symbol("try*")
+
+private let kSymbolDef = as_symbol(kValDef)
+private let kSymbolLet = as_symbol(kValLet)
+
+// Parse the string into an AST.
+//
+private func READ(str: String) throws -> MalVal {
+ return try read_str(str)
+}
+
+// Perform a simple evaluation of the `ast` object. If it's a symbol,
+// dereference it and return its value. If it's a collection, call EVAL on all
+// elements (or just the values, in the case of the hashmap). Otherwise, return
+// the object unchanged.
+//
+private func eval_ast(ast: MalVal, _ env: Environment) throws -> MalVal {
+ if let symbol = as_symbolQ(ast) {
+ guard let val = env.get(symbol) else {
+ try throw_error("'\(symbol)' not found") // Specific text needed to match MAL unit tests
+ }
+ return val
+ }
+ if let list = as_listQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(list.count))
+ for item in list {
+ let eval = try EVAL(item, env)
+ result.append(eval)
+ }
+ return make_list(result)
+ }
+ if let vec = as_vectorQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(vec.count))
+ for item in vec {
+ let eval = try EVAL(item, env)
+ result.append(eval)
+ }
+ return make_vector(result)
+ }
+ if let hash = as_hashmapQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(hash.count) * 2)
+ for (k, v) in hash {
+ let new_v = try EVAL(v, env)
+ result.append(k)
+ result.append(new_v)
+ }
+ return make_hashmap(result)
+ }
+ return ast
+}
+
+// EVALuate "def!".
+//
+private func eval_def(list: MalSequence, _ env: Environment) throws -> MalVal {
+ guard list.count == 3 else {
+ try throw_error("expected 2 arguments to def!, got \(list.count - 1)")
+ }
+ let arg1 = try! list.nth(1)
+ let arg2 = try! list.nth(2)
+ guard let sym = as_symbolQ(arg1) else {
+ try throw_error("expected symbol for first argument to def!")
+ }
+ let value = try EVAL(arg2, env)
+ return env.set(sym, value)
+}
+
+// EVALuate "let*".
+//
+private func eval_let(list: MalSequence, _ env: Environment) throws -> MalVal {
+ guard list.count == 3 else {
+ try throw_error("expected 2 arguments to let*, got \(list.count - 1)")
+ }
+ let arg1 = try! list.nth(1)
+ let arg2 = try! list.nth(2)
+ guard let bindings = as_sequenceQ(arg1) else {
+ try throw_error("expected list for first argument to let*")
+ }
+ guard bindings.count % 2 == 0 else {
+ try throw_error("expected even number of elements in bindings to let*, got \(bindings.count)")
+ }
+ let new_env = Environment(outer: env)
+ for var index: MalIntType = 0; index < bindings.count; index += 2 {
+ let binding_name = try! bindings.nth(index)
+ let binding_value = try! bindings.nth(index + 1)
+ guard let binding_symbol = as_symbolQ(binding_name) else {
+ try throw_error("expected symbol for first element in binding pair")
+ }
+ let evaluated_value = try EVAL(binding_value, new_env)
+ new_env.set(binding_symbol, evaluated_value)
+ }
+ return try EVAL(arg2, new_env)
+}
+
+// Walk the AST and completely evaluate it, handling macro expansions, special
+// forms and function calls.
+//
+private func EVAL(ast: MalVal, _ env: Environment) throws -> MalVal {
+
+ if !is_list(ast) {
+
+ // Not a list -- just evaluate and return.
+
+ let answer = try eval_ast(ast, env)
+ return answer
+ }
+
+ // Special handling if it's a list.
+
+ let list = as_list(ast)
+
+ if list.isEmpty {
+ return ast
+ }
+
+ // Check for special forms, where we want to check the operation
+ // before evaluating all of the parameters.
+
+ let arg0 = list.first()
+ if let fn_symbol = as_symbolQ(arg0) {
+
+ switch fn_symbol {
+ case kSymbolDef: return try eval_def(list, env)
+ case kSymbolLet: return try eval_let(list, env)
+ default: break
+ }
+ }
+
+ // Standard list to be applied. Evaluate all the elements first.
+
+ let eval = try eval_ast(ast, env)
+
+ // The result had better be a list and better be non-empty.
+
+ let eval_list = as_list(eval)
+ if eval_list.isEmpty {
+ return eval
+ }
+
+ // Get the first element of the list and execute it.
+
+ let first = eval_list.first()
+ let rest = as_sequence(eval_list.rest())
+
+ if let fn = as_builtinQ(first) {
+ let answer = try fn.apply(rest)
+ return answer
+ }
+
+ // The first element wasn't a function to be executed. Return an
+ // error saying so.
+
+ try throw_error("first list item does not evaluate to a function: \(first)")
+}
+
+// Convert the value into a human-readable string for printing.
+//
+private func PRINT(exp: MalVal) -> String {
+ return pr_str(exp, true)
+}
+
+// Perform the READ and EVAL steps. Useful for when you don't care about the
+// printable result.
+//
+private func RE(text: String, _ env: Environment) -> MalVal? {
+ if !text.isEmpty {
+ do {
+ let ast = try READ(text)
+ do {
+ return try EVAL(ast, env)
+ } catch let error as MalException {
+ print("Error evaluating input: \(error)")
+ } catch {
+ print("Error evaluating input: \(error)")
+ }
+ } catch let error as MalException {
+ print("Error parsing input: \(error)")
+ } catch {
+ print("Error parsing input: \(error)")
+ }
+ }
+ return nil
+}
+
+// Perform the full READ/EVAL/PRINT, returning a printable string.
+//
+private func REP(text: String, _ env: Environment) -> String? {
+ let exp = RE(text, env)
+ if exp == nil { return nil }
+ return PRINT(exp!)
+}
+
+// Perform the full REPL.
+//
+private func REPL(env: Environment) {
+ while true {
+ if let text = _readline("user> ") {
+ if let output = REP(text, env) {
+ print("\(output)")
+ }
+ } else {
+ print("")
+ break
+ }
+ }
+}
+
+func main() {
+ let env = Environment(outer: nil)
+
+ load_history_file()
+ load_builtins(env)
+
+ REPL(env)
+
+ save_history_file()
+}
-//******************************************************************************
-// MAL - step 4 - if/fn/do
-//******************************************************************************
-// This file is automatically generated from templates/step.swift. Rather than
-// editing it directly, it's probably better to edit templates/step.swift and
-// regenerate this file. Otherwise, your change might be lost if/when someone
-// else performs that process.
-//******************************************************************************
-
-import Foundation
-
-// Symbols used in this module.
-//
-let kSymbolDef = MalSymbol(symbol: "def!")
-let kSymbolDo = MalSymbol(symbol: "do")
-let kSymbolFn = MalSymbol(symbol: "fn*")
-let kSymbolIf = MalSymbol(symbol: "if")
-let kSymbolLet = MalSymbol(symbol: "let*")
-
-// Parse the string into an AST.
-//
-func READ(str: String) -> MalVal {
- return read_str(str)
-}
-
-// Perform a simple evaluation of the `ast` object. If it's a symbol,
-// dereference it and return its value. If it's a collection, call EVAL on all
-// elements (or just the values, in the case of the hashmap). Otherwise, return
-// the object unchanged.
-//
-func eval_ast(ast: MalVal, env: Environment) -> MalVal {
- switch ast.type {
- case .TypeSymbol:
- let symbol = ast as! MalSymbol
- if let val = env.get(symbol) {
- return val
- }
- return MalError(message: "'\(symbol)' not found") // Specific text needed to match MAL unit tests
- case .TypeList:
- let list = ast as! MalList
- var result = [MalVal]()
- result.reserveCapacity(list.count)
- for item in list {
- let eval = EVAL(item, env)
- if is_error(eval) { return eval }
- result.append(eval)
- }
- return MalList(array: result)
- case .TypeVector:
- let vec = ast as! MalVector
- var result = [MalVal]()
- result.reserveCapacity(vec.count)
- for item in vec {
- let eval = EVAL(item, env)
- if is_error(eval) { return eval }
- result.append(eval)
- }
- return MalVector(array: result)
- case .TypeHashMap:
- let hash = ast as! MalHashMap
- var result = [MalVal]()
- result.reserveCapacity(hash.count * 2)
- for (k, v) in hash {
- let new_v = EVAL(v, env)
- if is_error(new_v) { return new_v }
- result.append(k)
- result.append(new_v)
- }
- return MalHashMap(array: result)
- default:
- return ast
- }
-}
-
-// EVALuate "def!".
-//
-func eval_def(list: MalSequence, env: Environment) -> MalVal {
- if list.count != 3 {
- return MalError(message: "expected 2 arguments to def!, got \(list.count - 1)")
- }
- let arg1 = list[1]
- let arg2 = list[2]
- if !is_symbol(arg1) {
- return MalError(message: "expected symbol for first argument to def!")
- }
- let sym = arg1 as! MalSymbol
- let value = EVAL(arg2, env)
- if is_error(value) { return value }
- return env.set(sym, value)
-}
-
-// EVALuate "let*".
-//
-func eval_let(list: MalSequence, env: Environment) -> MalVal {
- if list.count != 3 {
- return MalError(message: "expected 2 arguments to let*, got \(list.count - 1)")
- }
- let arg1 = list[1]
- let arg2 = list[2]
- if !is_sequence(arg1) {
- return MalError(message: "expected list for first argument to let*")
- }
- let bindings = arg1 as! MalSequence
- if bindings.count % 2 == 1 {
- return MalError(message: "expected even number of elements in bindings to let*, got \(bindings.count)")
- }
- var new_env = Environment(outer: env)
- for var index = 0; index < bindings.count; index += 2 {
- let binding_name = bindings[index]
- let binding_value = bindings[index + 1]
-
- if !is_symbol(binding_name) {
- return MalError(message: "expected symbol for first element in binding pair")
- }
- let binding_symbol = binding_name as! MalSymbol
- let evaluated_value = EVAL(binding_value, new_env)
- if is_error(evaluated_value) { return evaluated_value }
- new_env.set(binding_symbol, evaluated_value)
- }
- return EVAL(arg2, new_env)
-}
-
-// EVALuate "do".
-//
-func eval_do(list: MalSequence, env: Environment) -> MalVal {
- let evaluated_ast = eval_ast(list.rest(), env)
- if is_error(evaluated_ast) { return evaluated_ast }
- let evaluated_seq = evaluated_ast as! MalSequence
- return evaluated_seq.last()
-}
-
-// EVALuate "if".
-//
-func eval_if(list: MalSequence, env: Environment) -> MalVal {
- if list.count < 3 {
- return MalError(message: "expected at least 2 arguments to if, got \(list.count - 1)")
- }
- let cond_result = EVAL(list[1], env)
- var new_ast = MalVal()
- if is_truthy(cond_result) {
- new_ast = list[2]
- } else if list.count == 4 {
- new_ast = list[3]
- } else {
- return MalNil()
- }
- return EVAL(new_ast, env)
-}
-
-// EVALuate "fn*".
-//
-func eval_fn(list: MalSequence, env: Environment) -> MalVal {
- if list.count != 3 {
- return MalError(message: "expected 2 arguments to fn*, got \(list.count - 1)")
- }
- if !is_sequence(list[1]) {
- return MalError(message: "expected list or vector for first argument to fn*")
- }
- return MalClosure(eval: EVAL, args:list[1] as! MalSequence, body:list[2], env:env)
-}
-
-// Walk the AST and completely evaluate it, handling macro expansions, special
-// forms and function calls.
-//
-func EVAL(var ast: MalVal, var env: Environment) -> MalVal {
- if is_error(ast) { return ast }
-
- if !is_list(ast) {
-
- // Not a list -- just evaluate and return.
-
- let answer = eval_ast(ast, env)
- return answer
- }
-
- // Special handling if it's a list.
-
- var list = ast as! MalList
-
- if list.isEmpty {
- return list
- }
-
- // Check for special forms, where we want to check the operation
- // before evaluating all of the parameters.
-
- let arg0 = list.first()
- if is_symbol(arg0) {
- let fn_symbol = arg0 as! MalSymbol
-
- switch fn_symbol {
- case kSymbolDef: return eval_def(list, env)
- case kSymbolLet: return eval_let(list, env)
- case kSymbolDo: return eval_do(list, env)
- case kSymbolIf: return eval_if(list, env)
- case kSymbolFn: return eval_fn(list, env)
- default: break
- }
- }
-
- // Standard list to be applied. Evaluate all the elements first.
-
- let eval = eval_ast(ast, env)
- if is_error(eval) { return eval }
-
- // The result had better be a list and better be non-empty.
-
- let eval_list = eval as! MalList
- if eval_list.isEmpty {
- return eval_list
- }
-
- // Get the first element of the list and execute it.
-
- let first = eval_list.first()
- let rest = eval_list.rest()
-
- if is_builtin(first) {
- let fn = first as! MalBuiltin
- let answer = fn.apply(rest)
- return answer
- } else if is_closure(first) {
- let fn = first as! MalClosure
- var new_env = Environment(outer: fn.env)
- let result = new_env.set_bindings(fn.args, with_exprs:rest)
- if is_error(result) { return result }
- let answer = EVAL(fn.body, new_env)
- return answer
- }
-
- // The first element wasn't a function to be executed. Return an
- // error saying so.
-
- return MalError(message: "first list item does not evaluate to a function: \(first)")
-}
-
-// Convert the value into a human-readable string for printing.
-//
-func PRINT(exp: MalVal) -> String? {
- if is_error(exp) { return nil }
- return pr_str(exp, true)
-}
-
-// Perform the READ and EVAL steps. Useful for when you don't care about the
-// printable result.
-//
-func RE(text: String, env: Environment) -> MalVal? {
- if text.isEmpty { return nil }
- let ast = READ(text)
- if is_error(ast) {
- println("Error parsing input: \(ast)")
- return nil
- }
- let exp = EVAL(ast, env)
- if is_error(exp) {
- println("Error evaluating input: \(exp)")
- return nil
- }
- return exp
-}
-
-// Perform the full READ/EVAL/PRINT, returning a printable string.
-//
-func REP(text: String, env: Environment) -> String? {
- let exp = RE(text, env)
- if exp == nil { return nil }
- return PRINT(exp!)
-}
-
-// Perform the full REPL.
-//
-func REPL(env: Environment) {
- while true {
- if let text = _readline("user> ") {
- if let output = REP(text, env) {
- println("\(output)")
- }
- } else {
- println()
- break
- }
- }
-}
-
-func main() {
- var env = Environment(outer: nil)
-
- load_history_file()
- load_builtins(env)
-
- RE("(def! not (fn* (a) (if a false true)))", env)
- REPL(env)
-
- save_history_file()
-}
+//******************************************************************************
+// MAL - step 4 - if/fn/do
+//******************************************************************************
+// This file is automatically generated from templates/step.swift. Rather than
+// editing it directly, it's probably better to edit templates/step.swift and
+// regenerate this file. Otherwise, your change might be lost if/when someone
+// else performs that process.
+//******************************************************************************
+
+import Foundation
+
+// Symbols used in this module.
+//
+private let kValDef = make_symbol("def!")
+private let kValDo = make_symbol("do")
+private let kValFn = make_symbol("fn*")
+private let kValIf = make_symbol("if")
+private let kValLet = make_symbol("let*")
+private let kValTry = make_symbol("try*")
+
+private let kSymbolDef = as_symbol(kValDef)
+private let kSymbolDo = as_symbol(kValDo)
+private let kSymbolFn = as_symbol(kValFn)
+private let kSymbolIf = as_symbol(kValIf)
+private let kSymbolLet = as_symbol(kValLet)
+
+// Parse the string into an AST.
+//
+private func READ(str: String) throws -> MalVal {
+ return try read_str(str)
+}
+
+// Perform a simple evaluation of the `ast` object. If it's a symbol,
+// dereference it and return its value. If it's a collection, call EVAL on all
+// elements (or just the values, in the case of the hashmap). Otherwise, return
+// the object unchanged.
+//
+private func eval_ast(ast: MalVal, _ env: Environment) throws -> MalVal {
+ if let symbol = as_symbolQ(ast) {
+ guard let val = env.get(symbol) else {
+ try throw_error("'\(symbol)' not found") // Specific text needed to match MAL unit tests
+ }
+ return val
+ }
+ if let list = as_listQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(list.count))
+ for item in list {
+ let eval = try EVAL(item, env)
+ result.append(eval)
+ }
+ return make_list(result)
+ }
+ if let vec = as_vectorQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(vec.count))
+ for item in vec {
+ let eval = try EVAL(item, env)
+ result.append(eval)
+ }
+ return make_vector(result)
+ }
+ if let hash = as_hashmapQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(hash.count) * 2)
+ for (k, v) in hash {
+ let new_v = try EVAL(v, env)
+ result.append(k)
+ result.append(new_v)
+ }
+ return make_hashmap(result)
+ }
+ return ast
+}
+
+// EVALuate "def!".
+//
+private func eval_def(list: MalSequence, _ env: Environment) throws -> MalVal {
+ guard list.count == 3 else {
+ try throw_error("expected 2 arguments to def!, got \(list.count - 1)")
+ }
+ let arg1 = try! list.nth(1)
+ let arg2 = try! list.nth(2)
+ guard let sym = as_symbolQ(arg1) else {
+ try throw_error("expected symbol for first argument to def!")
+ }
+ let value = try EVAL(arg2, env)
+ return env.set(sym, value)
+}
+
+// EVALuate "let*".
+//
+private func eval_let(list: MalSequence, _ env: Environment) throws -> MalVal {
+ guard list.count == 3 else {
+ try throw_error("expected 2 arguments to let*, got \(list.count - 1)")
+ }
+ let arg1 = try! list.nth(1)
+ let arg2 = try! list.nth(2)
+ guard let bindings = as_sequenceQ(arg1) else {
+ try throw_error("expected list for first argument to let*")
+ }
+ guard bindings.count % 2 == 0 else {
+ try throw_error("expected even number of elements in bindings to let*, got \(bindings.count)")
+ }
+ let new_env = Environment(outer: env)
+ for var index: MalIntType = 0; index < bindings.count; index += 2 {
+ let binding_name = try! bindings.nth(index)
+ let binding_value = try! bindings.nth(index + 1)
+ guard let binding_symbol = as_symbolQ(binding_name) else {
+ try throw_error("expected symbol for first element in binding pair")
+ }
+ let evaluated_value = try EVAL(binding_value, new_env)
+ new_env.set(binding_symbol, evaluated_value)
+ }
+ return try EVAL(arg2, new_env)
+}
+
+// EVALuate "do".
+//
+private func eval_do(list: MalSequence, _ env: Environment) throws -> MalVal {
+ let evaluated_ast = try eval_ast(list.rest(), env)
+ let evaluated_seq = as_sequence(evaluated_ast)
+ return evaluated_seq.last()
+}
+
+// EVALuate "if".
+//
+private func eval_if(list: MalSequence, _ env: Environment) throws -> MalVal {
+ guard list.count >= 3 else {
+ try throw_error("expected at least 2 arguments to if, got \(list.count - 1)")
+ }
+ let cond_result = try EVAL(try! list.nth(1), env)
+ var new_ast: MalVal
+ if is_truthy(cond_result) {
+ new_ast = try! list.nth(2)
+ } else if list.count == 4 {
+ new_ast = try! list.nth(3)
+ } else {
+ return make_nil()
+ }
+ return try EVAL(new_ast, env)
+}
+
+// EVALuate "fn*".
+//
+private func eval_fn(list: MalSequence, _ env: Environment) throws -> MalVal {
+ guard list.count == 3 else {
+ try throw_error("expected 2 arguments to fn*, got \(list.count - 1)")
+ }
+ guard let seq = as_sequenceQ(try! list.nth(1)) else {
+ try throw_error("expected list or vector for first argument to fn*")
+ }
+ return make_closure((eval: EVAL, args: seq, body: try! list.nth(2), env: env))
+}
+
+// Walk the AST and completely evaluate it, handling macro expansions, special
+// forms and function calls.
+//
+private func EVAL(ast: MalVal, _ env: Environment) throws -> MalVal {
+
+ if !is_list(ast) {
+
+ // Not a list -- just evaluate and return.
+
+ let answer = try eval_ast(ast, env)
+ return answer
+ }
+
+ // Special handling if it's a list.
+
+ let list = as_list(ast)
+
+ if list.isEmpty {
+ return ast
+ }
+
+ // Check for special forms, where we want to check the operation
+ // before evaluating all of the parameters.
+
+ let arg0 = list.first()
+ if let fn_symbol = as_symbolQ(arg0) {
+
+ switch fn_symbol {
+ case kSymbolDef: return try eval_def(list, env)
+ case kSymbolLet: return try eval_let(list, env)
+ case kSymbolDo: return try eval_do(list, env)
+ case kSymbolIf: return try eval_if(list, env)
+ case kSymbolFn: return try eval_fn(list, env)
+ default: break
+ }
+ }
+
+ // Standard list to be applied. Evaluate all the elements first.
+
+ let eval = try eval_ast(ast, env)
+
+ // The result had better be a list and better be non-empty.
+
+ let eval_list = as_list(eval)
+ if eval_list.isEmpty {
+ return eval
+ }
+
+ // Get the first element of the list and execute it.
+
+ let first = eval_list.first()
+ let rest = as_sequence(eval_list.rest())
+
+ if let fn = as_builtinQ(first) {
+ let answer = try fn.apply(rest)
+ return answer
+ } else if let fn = as_closureQ(first) {
+ let new_env = Environment(outer: fn.env)
+ let _ = try new_env.set_bindings(fn.args, with_exprs: rest)
+ let answer = try EVAL(fn.body, new_env)
+ return answer
+ }
+
+ // The first element wasn't a function to be executed. Return an
+ // error saying so.
+
+ try throw_error("first list item does not evaluate to a function: \(first)")
+}
+
+// Convert the value into a human-readable string for printing.
+//
+private func PRINT(exp: MalVal) -> String {
+ return pr_str(exp, true)
+}
+
+// Perform the READ and EVAL steps. Useful for when you don't care about the
+// printable result.
+//
+private func RE(text: String, _ env: Environment) -> MalVal? {
+ if !text.isEmpty {
+ do {
+ let ast = try READ(text)
+ do {
+ return try EVAL(ast, env)
+ } catch let error as MalException {
+ print("Error evaluating input: \(error)")
+ } catch {
+ print("Error evaluating input: \(error)")
+ }
+ } catch let error as MalException {
+ print("Error parsing input: \(error)")
+ } catch {
+ print("Error parsing input: \(error)")
+ }
+ }
+ return nil
+}
+
+// Perform the full READ/EVAL/PRINT, returning a printable string.
+//
+private func REP(text: String, _ env: Environment) -> String? {
+ let exp = RE(text, env)
+ if exp == nil { return nil }
+ return PRINT(exp!)
+}
+
+// Perform the full REPL.
+//
+private func REPL(env: Environment) {
+ while true {
+ if let text = _readline("user> ") {
+ if let output = REP(text, env) {
+ print("\(output)")
+ }
+ } else {
+ print("")
+ break
+ }
+ }
+}
+
+func main() {
+ let env = Environment(outer: nil)
+
+ load_history_file()
+ load_builtins(env)
+
+ RE("(def! not (fn* (a) (if a false true)))", env)
+ REPL(env)
+
+ save_history_file()
+}
// The number of times EVAL has been entered recursively. We keep track of this
// so that we can protect against overrunning the stack.
//
-var EVAL_level = 0
+private var EVAL_level = 0
// The maximum number of times we let EVAL recurse before throwing an exception.
// Testing puts this at some place between 1800 and 1900. Let's keep it at 500
// for safety's sake.
//
-let EVAL_leval_max = 500
+private let EVAL_leval_max = 500
// Control whether or not tail-call optimization (TCO) is enabled. We want it
// `true` most of the time, but may disable it for debugging purposes (it's
// easier to get a meaningful backtrace that way).
//
-let TCO = true
+private let TCO = true
// Control whether or not we emit debugging statements in EVAL.
//
-let DEBUG_EVAL = false
+private let DEBUG_EVAL = false
// String used to prefix information logged in EVAL. Increasing lengths of the
// string are used the more EVAL is recursed.
//
-let INDENT_TEMPLATE = "|----|----|----|----|----|----|----|----|" +
+private let INDENT_TEMPLATE = "|----|----|----|----|----|----|----|----|" +
"----|----|----|----|----|----|----|----|----|----|----|" +
"----|----|----|----|----|----|----|----|----|----|----|" +
"----|----|----|----|----|----|----|----|----|----|----|" +
// Holds the prefix of INDENT_TEMPLATE used for actual logging.
//
-var indent = String()
+private var indent = String()
// Symbols used in this module.
//
-let kSymbolDef = MalSymbol(symbol: "def!")
-let kSymbolDo = MalSymbol(symbol: "do")
-let kSymbolFn = MalSymbol(symbol: "fn*")
-let kSymbolIf = MalSymbol(symbol: "if")
-let kSymbolLet = MalSymbol(symbol: "let*")
-
-// Class to help control the incrementing and decrementing of EVAL_level. We
-// create one of these on entry to EVAL, incrementing the level. When the
-// variable goes out of scope, the object is destroyed, decrementing the level.
-//
-class EVAL_Counter {
- init() {
- ++EVAL_level
- }
- deinit {
- --EVAL_level
- }
+private let kValDef = make_symbol("def!")
+private let kValDo = make_symbol("do")
+private let kValFn = make_symbol("fn*")
+private let kValIf = make_symbol("if")
+private let kValLet = make_symbol("let*")
+private let kValTry = make_symbol("try*")
+
+private let kSymbolDef = as_symbol(kValDef)
+private let kSymbolDo = as_symbol(kValDo)
+private let kSymbolFn = as_symbol(kValFn)
+private let kSymbolIf = as_symbol(kValIf)
+private let kSymbolLet = as_symbol(kValLet)
+
+func substring(s: String, _ begin: Int, _ end: Int) -> String {
+ return s[s.startIndex.advancedBy(begin) ..< s.startIndex.advancedBy(end)]
}
// Parse the string into an AST.
//
-func READ(str: String) -> MalVal {
- return read_str(str)
+private func READ(str: String) throws -> MalVal {
+ return try read_str(str)
}
// Perform a simple evaluation of the `ast` object. If it's a symbol,
// elements (or just the values, in the case of the hashmap). Otherwise, return
// the object unchanged.
//
-func eval_ast(ast: MalVal, env: Environment) -> MalVal {
- switch ast.type {
- case .TypeSymbol:
- let symbol = ast as! MalSymbol
- if let val = env.get(symbol) {
- return val
- }
- return MalError(message: "'\(symbol)' not found") // Specific text needed to match MAL unit tests
- case .TypeList:
- let list = ast as! MalList
- var result = [MalVal]()
- result.reserveCapacity(list.count)
- for item in list {
- let eval = EVAL(item, env)
- if is_error(eval) { return eval }
- result.append(eval)
- }
- return MalList(array: result)
- case .TypeVector:
- let vec = ast as! MalVector
- var result = [MalVal]()
- result.reserveCapacity(vec.count)
- for item in vec {
- let eval = EVAL(item, env)
- if is_error(eval) { return eval }
- result.append(eval)
- }
- return MalVector(array: result)
- case .TypeHashMap:
- let hash = ast as! MalHashMap
- var result = [MalVal]()
- result.reserveCapacity(hash.count * 2)
- for (k, v) in hash {
- let new_v = EVAL(v, env)
- if is_error(new_v) { return new_v }
- result.append(k)
- result.append(new_v)
- }
- return MalHashMap(array: result)
- default:
- return ast
+private func eval_ast(ast: MalVal, _ env: Environment) throws -> MalVal {
+ if let symbol = as_symbolQ(ast) {
+ guard let val = env.get(symbol) else {
+ try throw_error("'\(symbol)' not found") // Specific text needed to match MAL unit tests
+ }
+ return val
+ }
+ if let list = as_listQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(list.count))
+ for item in list {
+ let eval = try EVAL(item, env)
+ result.append(eval)
+ }
+ return make_list(result)
+ }
+ if let vec = as_vectorQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(vec.count))
+ for item in vec {
+ let eval = try EVAL(item, env)
+ result.append(eval)
+ }
+ return make_vector(result)
+ }
+ if let hash = as_hashmapQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(hash.count) * 2)
+ for (k, v) in hash {
+ let new_v = try EVAL(v, env)
+ result.append(k)
+ result.append(new_v)
+ }
+ return make_hashmap(result)
}
+ return ast
}
-enum TCOVal {
+private enum TCOVal {
case NoResult
case Return(MalVal)
case Continue(MalVal, Environment)
init() { self = .NoResult }
init(_ result: MalVal) { self = .Return(result) }
init(_ ast: MalVal, _ env: Environment) { self = .Continue(ast, env) }
- init(_ e: String) { self = .Return(MalError(message: e)) }
}
// EVALuate "def!".
//
-func eval_def(list: MalSequence, env: Environment) -> TCOVal {
- if list.count != 3 {
- return TCOVal("expected 2 arguments to def!, got \(list.count - 1)")
+private func eval_def(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count == 3 else {
+ try throw_error("expected 2 arguments to def!, got \(list.count - 1)")
}
- let arg1 = list[1]
- let arg2 = list[2]
- if !is_symbol(arg1) {
- return TCOVal("expected symbol for first argument to def!")
+ let arg1 = try! list.nth(1)
+ let arg2 = try! list.nth(2)
+ guard let sym = as_symbolQ(arg1) else {
+ try throw_error("expected symbol for first argument to def!")
}
- let sym = arg1 as! MalSymbol
- let value = EVAL(arg2, env)
- if is_error(value) { return TCOVal(value) }
+ let value = try EVAL(arg2, env)
return TCOVal(env.set(sym, value))
}
// EVALuate "let*".
//
-func eval_let(list: MalSequence, env: Environment) -> TCOVal {
- if list.count != 3 {
- return TCOVal("expected 2 arguments to let*, got \(list.count - 1)")
+private func eval_let(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count == 3 else {
+ try throw_error("expected 2 arguments to let*, got \(list.count - 1)")
}
- let arg1 = list[1]
- let arg2 = list[2]
- if !is_sequence(arg1) {
- return TCOVal("expected list for first argument to let*")
+ let arg1 = try! list.nth(1)
+ let arg2 = try! list.nth(2)
+ guard let bindings = as_sequenceQ(arg1) else {
+ try throw_error("expected list for first argument to let*")
}
- let bindings = arg1 as! MalSequence
- if bindings.count % 2 == 1 {
- return TCOVal("expected even number of elements in bindings to let*, got \(bindings.count)")
+ guard bindings.count % 2 == 0 else {
+ try throw_error("expected even number of elements in bindings to let*, got \(bindings.count)")
}
- var new_env = Environment(outer: env)
- for var index = 0; index < bindings.count; index += 2 {
- let binding_name = bindings[index]
- let binding_value = bindings[index + 1]
-
- if !is_symbol(binding_name) {
- return TCOVal("expected symbol for first element in binding pair")
+ let new_env = Environment(outer: env)
+ for var index: MalIntType = 0; index < bindings.count; index += 2 {
+ let binding_name = try! bindings.nth(index)
+ let binding_value = try! bindings.nth(index + 1)
+ guard let binding_symbol = as_symbolQ(binding_name) else {
+ try throw_error("expected symbol for first element in binding pair")
}
- let binding_symbol = binding_name as! MalSymbol
- let evaluated_value = EVAL(binding_value, new_env)
- if is_error(evaluated_value) { return TCOVal(evaluated_value) }
+ let evaluated_value = try EVAL(binding_value, new_env)
new_env.set(binding_symbol, evaluated_value)
}
if TCO {
return TCOVal(arg2, new_env)
}
- return TCOVal(EVAL(arg2, new_env))
+ return TCOVal(try EVAL(arg2, new_env))
}
// EVALuate "do".
//
-func eval_do(list: MalSequence, env: Environment) -> TCOVal {
+private func eval_do(list: MalSequence, _ env: Environment) throws -> TCOVal {
if TCO {
- let eval = eval_ast(MalList(slice: list[1..<list.count-1]), env)
- if is_error(eval) { return TCOVal(eval) }
+ let _ = try eval_ast(list.range_from(1, to: list.count-1), env)
return TCOVal(list.last(), env)
}
- let evaluated_ast = eval_ast(list.rest(), env)
- if is_error(evaluated_ast) { return TCOVal(evaluated_ast) }
- let evaluated_seq = evaluated_ast as! MalSequence
+ let evaluated_ast = try eval_ast(list.rest(), env)
+ let evaluated_seq = as_sequence(evaluated_ast)
return TCOVal(evaluated_seq.last())
}
// EVALuate "if".
//
-func eval_if(list: MalSequence, env: Environment) -> TCOVal {
- if list.count < 3 {
- return TCOVal("expected at least 2 arguments to if, got \(list.count - 1)")
+private func eval_if(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count >= 3 else {
+ try throw_error("expected at least 2 arguments to if, got \(list.count - 1)")
}
- let cond_result = EVAL(list[1], env)
- var new_ast = MalVal()
+ let cond_result = try EVAL(try! list.nth(1), env)
+ var new_ast: MalVal
if is_truthy(cond_result) {
- new_ast = list[2]
+ new_ast = try! list.nth(2)
} else if list.count == 4 {
- new_ast = list[3]
+ new_ast = try! list.nth(3)
} else {
- return TCOVal(MalNil())
+ return TCOVal(make_nil())
}
if TCO {
return TCOVal(new_ast, env)
}
- return TCOVal(EVAL(new_ast, env))
+ return TCOVal(try EVAL(new_ast, env))
}
// EVALuate "fn*".
//
-func eval_fn(list: MalSequence, env: Environment) -> TCOVal {
- if list.count != 3 {
- return TCOVal("expected 2 arguments to fn*, got \(list.count - 1)")
+private func eval_fn(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count == 3 else {
+ try throw_error("expected 2 arguments to fn*, got \(list.count - 1)")
}
- if !is_sequence(list[1]) {
- return TCOVal("expected list or vector for first argument to fn*")
+ guard let seq = as_sequenceQ(try! list.nth(1)) else {
+ try throw_error("expected list or vector for first argument to fn*")
}
- return TCOVal(MalClosure(eval: EVAL, args:list[1] as! MalSequence, body:list[2], env:env))
+ return TCOVal(make_closure((eval: EVAL, args: seq, body: try! list.nth(2), env: env)))
}
// Walk the AST and completely evaluate it, handling macro expansions, special
// forms and function calls.
//
-func EVAL(var ast: MalVal, var env: Environment) -> MalVal {
- let x = EVAL_Counter()
- if EVAL_level > EVAL_leval_max {
- return MalError(message: "Recursing too many levels (> \(EVAL_leval_max))")
+private func EVAL(var ast: MalVal, var _ env: Environment) throws -> MalVal {
+ EVAL_level++
+ defer { EVAL_level-- }
+ guard EVAL_level <= EVAL_leval_max else {
+ try throw_error("Recursing too many levels (> \(EVAL_leval_max))")
}
if DEBUG_EVAL {
- indent = prefix(INDENT_TEMPLATE, EVAL_level)
+ indent = substring(INDENT_TEMPLATE, 0, EVAL_level)
}
while true {
- if is_error(ast) { return ast }
- if DEBUG_EVAL { println("\(indent)> \(ast)") }
+ if DEBUG_EVAL { print("\(indent)> \(ast)") }
if !is_list(ast) {
// Not a list -- just evaluate and return.
- let answer = eval_ast(ast, env)
- if DEBUG_EVAL { println("\(indent)>>> \(answer)") }
+ let answer = try eval_ast(ast, env)
+ if DEBUG_EVAL { print("\(indent)>>> \(answer)") }
return answer
}
// Special handling if it's a list.
- var list = ast as! MalList
- if DEBUG_EVAL { println("\(indent)>. \(list)") }
+ let list = as_list(ast)
+ if DEBUG_EVAL { print("\(indent)>. \(list)") }
if list.isEmpty {
- return list
+ return ast
}
// Check for special forms, where we want to check the operation
// before evaluating all of the parameters.
let arg0 = list.first()
- if is_symbol(arg0) {
- var res: TCOVal
- let fn_symbol = arg0 as! MalSymbol
+ if let fn_symbol = as_symbolQ(arg0) {
+ let res: TCOVal
switch fn_symbol {
- case kSymbolDef: res = eval_def(list, env)
- case kSymbolLet: res = eval_let(list, env)
- case kSymbolDo: res = eval_do(list, env)
- case kSymbolIf: res = eval_if(list, env)
- case kSymbolFn: res = eval_fn(list, env)
+ case kSymbolDef: res = try eval_def(list, env)
+ case kSymbolLet: res = try eval_let(list, env)
+ case kSymbolDo: res = try eval_do(list, env)
+ case kSymbolIf: res = try eval_if(list, env)
+ case kSymbolFn: res = try eval_fn(list, env)
default: res = TCOVal()
}
switch res {
// Standard list to be applied. Evaluate all the elements first.
- let eval = eval_ast(ast, env)
- if is_error(eval) { return eval }
+ let eval = try eval_ast(ast, env)
// The result had better be a list and better be non-empty.
- let eval_list = eval as! MalList
+ let eval_list = as_list(eval)
if eval_list.isEmpty {
- return eval_list
+ return eval
}
- if DEBUG_EVAL { println("\(indent)>> \(eval)") }
+ if DEBUG_EVAL { print("\(indent)>> \(eval)") }
// Get the first element of the list and execute it.
let first = eval_list.first()
- let rest = eval_list.rest()
+ let rest = as_sequence(eval_list.rest())
- if is_builtin(first) {
- let fn = first as! MalBuiltin
- let answer = fn.apply(rest)
- if DEBUG_EVAL { println("\(indent)>>> \(answer)") }
+ if let fn = as_builtinQ(first) {
+ let answer = try fn.apply(rest)
+ if DEBUG_EVAL { print("\(indent)>>> \(answer)") }
return answer
- } else if is_closure(first) {
- let fn = first as! MalClosure
- var new_env = Environment(outer: fn.env)
- let result = new_env.set_bindings(fn.args, with_exprs:rest)
- if is_error(result) { return result }
+ } else if let fn = as_closureQ(first) {
+ let new_env = Environment(outer: fn.env)
+ let _ = try new_env.set_bindings(fn.args, with_exprs: rest)
if TCO {
env = new_env
ast = fn.body
continue
}
- let answer = EVAL(fn.body, new_env)
- if DEBUG_EVAL { println("\(indent)>>> \(answer)") }
+ let answer = try EVAL(fn.body, new_env)
+ if DEBUG_EVAL { print("\(indent)>>> \(answer)") }
return answer
}
// The first element wasn't a function to be executed. Return an
// error saying so.
- return MalError(message: "first list item does not evaluate to a function: \(first)")
+ try throw_error("first list item does not evaluate to a function: \(first)")
}
}
// Convert the value into a human-readable string for printing.
//
-func PRINT(exp: MalVal) -> String? {
- if is_error(exp) { return nil }
+private func PRINT(exp: MalVal) -> String {
return pr_str(exp, true)
}
// Perform the READ and EVAL steps. Useful for when you don't care about the
// printable result.
//
-func RE(text: String, env: Environment) -> MalVal? {
- if text.isEmpty { return nil }
- let ast = READ(text)
- if is_error(ast) {
- println("Error parsing input: \(ast)")
- return nil
- }
- let exp = EVAL(ast, env)
- if is_error(exp) {
- println("Error evaluating input: \(exp)")
- return nil
+private func RE(text: String, _ env: Environment) -> MalVal? {
+ if !text.isEmpty {
+ do {
+ let ast = try READ(text)
+ do {
+ return try EVAL(ast, env)
+ } catch let error as MalException {
+ print("Error evaluating input: \(error)")
+ } catch {
+ print("Error evaluating input: \(error)")
+ }
+ } catch let error as MalException {
+ print("Error parsing input: \(error)")
+ } catch {
+ print("Error parsing input: \(error)")
+ }
}
- return exp
+ return nil
}
// Perform the full READ/EVAL/PRINT, returning a printable string.
//
-func REP(text: String, env: Environment) -> String? {
+private func REP(text: String, _ env: Environment) -> String? {
let exp = RE(text, env)
if exp == nil { return nil }
return PRINT(exp!)
// Perform the full REPL.
//
-func REPL(env: Environment) {
+private func REPL(env: Environment) {
while true {
if let text = _readline("user> ") {
if let output = REP(text, env) {
- println("\(output)")
+ print("\(output)")
}
} else {
- println()
+ print("")
break
}
}
}
func main() {
- var env = Environment(outer: nil)
+ let env = Environment(outer: nil)
load_history_file()
load_builtins(env)
// The number of times EVAL has been entered recursively. We keep track of this
// so that we can protect against overrunning the stack.
//
-var EVAL_level = 0
+private var EVAL_level = 0
// The maximum number of times we let EVAL recurse before throwing an exception.
// Testing puts this at some place between 1800 and 1900. Let's keep it at 500
// for safety's sake.
//
-let EVAL_leval_max = 500
+private let EVAL_leval_max = 500
// Control whether or not tail-call optimization (TCO) is enabled. We want it
// `true` most of the time, but may disable it for debugging purposes (it's
// easier to get a meaningful backtrace that way).
//
-let TCO = true
+private let TCO = true
// Control whether or not we emit debugging statements in EVAL.
//
-let DEBUG_EVAL = false
+private let DEBUG_EVAL = false
// String used to prefix information logged in EVAL. Increasing lengths of the
// string are used the more EVAL is recursed.
//
-let INDENT_TEMPLATE = "|----|----|----|----|----|----|----|----|" +
+private let INDENT_TEMPLATE = "|----|----|----|----|----|----|----|----|" +
"----|----|----|----|----|----|----|----|----|----|----|" +
"----|----|----|----|----|----|----|----|----|----|----|" +
"----|----|----|----|----|----|----|----|----|----|----|" +
// Holds the prefix of INDENT_TEMPLATE used for actual logging.
//
-var indent = String()
+private var indent = String()
// Symbols used in this module.
//
-let kSymbolArgv = MalSymbol(symbol: "*ARGV*")
-let kSymbolDef = MalSymbol(symbol: "def!")
-let kSymbolDo = MalSymbol(symbol: "do")
-let kSymbolEval = MalSymbol(symbol: "eval")
-let kSymbolFn = MalSymbol(symbol: "fn*")
-let kSymbolIf = MalSymbol(symbol: "if")
-let kSymbolLet = MalSymbol(symbol: "let*")
-
-// Class to help control the incrementing and decrementing of EVAL_level. We
-// create one of these on entry to EVAL, incrementing the level. When the
-// variable goes out of scope, the object is destroyed, decrementing the level.
-//
-class EVAL_Counter {
- init() {
- ++EVAL_level
- }
- deinit {
- --EVAL_level
- }
+private let kValArgv = make_symbol("*ARGV*")
+private let kValDef = make_symbol("def!")
+private let kValDo = make_symbol("do")
+private let kValEval = make_symbol("eval")
+private let kValFn = make_symbol("fn*")
+private let kValIf = make_symbol("if")
+private let kValLet = make_symbol("let*")
+private let kValTry = make_symbol("try*")
+
+private let kSymbolArgv = as_symbol(kValArgv)
+private let kSymbolDef = as_symbol(kValDef)
+private let kSymbolDo = as_symbol(kValDo)
+private let kSymbolEval = as_symbol(kValEval)
+private let kSymbolFn = as_symbol(kValFn)
+private let kSymbolIf = as_symbol(kValIf)
+private let kSymbolLet = as_symbol(kValLet)
+
+func substring(s: String, _ begin: Int, _ end: Int) -> String {
+ return s[s.startIndex.advancedBy(begin) ..< s.startIndex.advancedBy(end)]
}
// Parse the string into an AST.
//
-func READ(str: String) -> MalVal {
- return read_str(str)
+private func READ(str: String) throws -> MalVal {
+ return try read_str(str)
}
// Perform a simple evaluation of the `ast` object. If it's a symbol,
// elements (or just the values, in the case of the hashmap). Otherwise, return
// the object unchanged.
//
-func eval_ast(ast: MalVal, env: Environment) -> MalVal {
- switch ast.type {
- case .TypeSymbol:
- let symbol = ast as! MalSymbol
- if let val = env.get(symbol) {
- return val
- }
- return MalError(message: "'\(symbol)' not found") // Specific text needed to match MAL unit tests
- case .TypeList:
- let list = ast as! MalList
- var result = [MalVal]()
- result.reserveCapacity(list.count)
- for item in list {
- let eval = EVAL(item, env)
- if is_error(eval) { return eval }
- result.append(eval)
- }
- return MalList(array: result)
- case .TypeVector:
- let vec = ast as! MalVector
- var result = [MalVal]()
- result.reserveCapacity(vec.count)
- for item in vec {
- let eval = EVAL(item, env)
- if is_error(eval) { return eval }
- result.append(eval)
- }
- return MalVector(array: result)
- case .TypeHashMap:
- let hash = ast as! MalHashMap
- var result = [MalVal]()
- result.reserveCapacity(hash.count * 2)
- for (k, v) in hash {
- let new_v = EVAL(v, env)
- if is_error(new_v) { return new_v }
- result.append(k)
- result.append(new_v)
- }
- return MalHashMap(array: result)
- default:
- return ast
+private func eval_ast(ast: MalVal, _ env: Environment) throws -> MalVal {
+ if let symbol = as_symbolQ(ast) {
+ guard let val = env.get(symbol) else {
+ try throw_error("'\(symbol)' not found") // Specific text needed to match MAL unit tests
+ }
+ return val
+ }
+ if let list = as_listQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(list.count))
+ for item in list {
+ let eval = try EVAL(item, env)
+ result.append(eval)
+ }
+ return make_list(result)
+ }
+ if let vec = as_vectorQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(vec.count))
+ for item in vec {
+ let eval = try EVAL(item, env)
+ result.append(eval)
+ }
+ return make_vector(result)
+ }
+ if let hash = as_hashmapQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(hash.count) * 2)
+ for (k, v) in hash {
+ let new_v = try EVAL(v, env)
+ result.append(k)
+ result.append(new_v)
+ }
+ return make_hashmap(result)
}
+ return ast
}
-enum TCOVal {
+private enum TCOVal {
case NoResult
case Return(MalVal)
case Continue(MalVal, Environment)
init() { self = .NoResult }
init(_ result: MalVal) { self = .Return(result) }
init(_ ast: MalVal, _ env: Environment) { self = .Continue(ast, env) }
- init(_ e: String) { self = .Return(MalError(message: e)) }
}
// EVALuate "def!".
//
-func eval_def(list: MalSequence, env: Environment) -> TCOVal {
- if list.count != 3 {
- return TCOVal("expected 2 arguments to def!, got \(list.count - 1)")
+private func eval_def(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count == 3 else {
+ try throw_error("expected 2 arguments to def!, got \(list.count - 1)")
}
- let arg1 = list[1]
- let arg2 = list[2]
- if !is_symbol(arg1) {
- return TCOVal("expected symbol for first argument to def!")
+ let arg1 = try! list.nth(1)
+ let arg2 = try! list.nth(2)
+ guard let sym = as_symbolQ(arg1) else {
+ try throw_error("expected symbol for first argument to def!")
}
- let sym = arg1 as! MalSymbol
- let value = EVAL(arg2, env)
- if is_error(value) { return TCOVal(value) }
+ let value = try EVAL(arg2, env)
return TCOVal(env.set(sym, value))
}
// EVALuate "let*".
//
-func eval_let(list: MalSequence, env: Environment) -> TCOVal {
- if list.count != 3 {
- return TCOVal("expected 2 arguments to let*, got \(list.count - 1)")
+private func eval_let(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count == 3 else {
+ try throw_error("expected 2 arguments to let*, got \(list.count - 1)")
}
- let arg1 = list[1]
- let arg2 = list[2]
- if !is_sequence(arg1) {
- return TCOVal("expected list for first argument to let*")
+ let arg1 = try! list.nth(1)
+ let arg2 = try! list.nth(2)
+ guard let bindings = as_sequenceQ(arg1) else {
+ try throw_error("expected list for first argument to let*")
}
- let bindings = arg1 as! MalSequence
- if bindings.count % 2 == 1 {
- return TCOVal("expected even number of elements in bindings to let*, got \(bindings.count)")
+ guard bindings.count % 2 == 0 else {
+ try throw_error("expected even number of elements in bindings to let*, got \(bindings.count)")
}
- var new_env = Environment(outer: env)
- for var index = 0; index < bindings.count; index += 2 {
- let binding_name = bindings[index]
- let binding_value = bindings[index + 1]
-
- if !is_symbol(binding_name) {
- return TCOVal("expected symbol for first element in binding pair")
+ let new_env = Environment(outer: env)
+ for var index: MalIntType = 0; index < bindings.count; index += 2 {
+ let binding_name = try! bindings.nth(index)
+ let binding_value = try! bindings.nth(index + 1)
+ guard let binding_symbol = as_symbolQ(binding_name) else {
+ try throw_error("expected symbol for first element in binding pair")
}
- let binding_symbol = binding_name as! MalSymbol
- let evaluated_value = EVAL(binding_value, new_env)
- if is_error(evaluated_value) { return TCOVal(evaluated_value) }
+ let evaluated_value = try EVAL(binding_value, new_env)
new_env.set(binding_symbol, evaluated_value)
}
if TCO {
return TCOVal(arg2, new_env)
}
- return TCOVal(EVAL(arg2, new_env))
+ return TCOVal(try EVAL(arg2, new_env))
}
// EVALuate "do".
//
-func eval_do(list: MalSequence, env: Environment) -> TCOVal {
+private func eval_do(list: MalSequence, _ env: Environment) throws -> TCOVal {
if TCO {
- let eval = eval_ast(MalList(slice: list[1..<list.count-1]), env)
- if is_error(eval) { return TCOVal(eval) }
+ let _ = try eval_ast(list.range_from(1, to: list.count-1), env)
return TCOVal(list.last(), env)
}
- let evaluated_ast = eval_ast(list.rest(), env)
- if is_error(evaluated_ast) { return TCOVal(evaluated_ast) }
- let evaluated_seq = evaluated_ast as! MalSequence
+ let evaluated_ast = try eval_ast(list.rest(), env)
+ let evaluated_seq = as_sequence(evaluated_ast)
return TCOVal(evaluated_seq.last())
}
// EVALuate "if".
//
-func eval_if(list: MalSequence, env: Environment) -> TCOVal {
- if list.count < 3 {
- return TCOVal("expected at least 2 arguments to if, got \(list.count - 1)")
+private func eval_if(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count >= 3 else {
+ try throw_error("expected at least 2 arguments to if, got \(list.count - 1)")
}
- let cond_result = EVAL(list[1], env)
- var new_ast = MalVal()
+ let cond_result = try EVAL(try! list.nth(1), env)
+ var new_ast: MalVal
if is_truthy(cond_result) {
- new_ast = list[2]
+ new_ast = try! list.nth(2)
} else if list.count == 4 {
- new_ast = list[3]
+ new_ast = try! list.nth(3)
} else {
- return TCOVal(MalNil())
+ return TCOVal(make_nil())
}
if TCO {
return TCOVal(new_ast, env)
}
- return TCOVal(EVAL(new_ast, env))
+ return TCOVal(try EVAL(new_ast, env))
}
// EVALuate "fn*".
//
-func eval_fn(list: MalSequence, env: Environment) -> TCOVal {
- if list.count != 3 {
- return TCOVal("expected 2 arguments to fn*, got \(list.count - 1)")
+private func eval_fn(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count == 3 else {
+ try throw_error("expected 2 arguments to fn*, got \(list.count - 1)")
}
- if !is_sequence(list[1]) {
- return TCOVal("expected list or vector for first argument to fn*")
+ guard let seq = as_sequenceQ(try! list.nth(1)) else {
+ try throw_error("expected list or vector for first argument to fn*")
}
- return TCOVal(MalClosure(eval: EVAL, args:list[1] as! MalSequence, body:list[2], env:env))
+ return TCOVal(make_closure((eval: EVAL, args: seq, body: try! list.nth(2), env: env)))
}
// Walk the AST and completely evaluate it, handling macro expansions, special
// forms and function calls.
//
-func EVAL(var ast: MalVal, var env: Environment) -> MalVal {
- let x = EVAL_Counter()
- if EVAL_level > EVAL_leval_max {
- return MalError(message: "Recursing too many levels (> \(EVAL_leval_max))")
+private func EVAL(var ast: MalVal, var _ env: Environment) throws -> MalVal {
+ EVAL_level++
+ defer { EVAL_level-- }
+ guard EVAL_level <= EVAL_leval_max else {
+ try throw_error("Recursing too many levels (> \(EVAL_leval_max))")
}
if DEBUG_EVAL {
- indent = prefix(INDENT_TEMPLATE, EVAL_level)
+ indent = substring(INDENT_TEMPLATE, 0, EVAL_level)
}
while true {
- if is_error(ast) { return ast }
- if DEBUG_EVAL { println("\(indent)> \(ast)") }
+ if DEBUG_EVAL { print("\(indent)> \(ast)") }
if !is_list(ast) {
// Not a list -- just evaluate and return.
- let answer = eval_ast(ast, env)
- if DEBUG_EVAL { println("\(indent)>>> \(answer)") }
+ let answer = try eval_ast(ast, env)
+ if DEBUG_EVAL { print("\(indent)>>> \(answer)") }
return answer
}
// Special handling if it's a list.
- var list = ast as! MalList
- if DEBUG_EVAL { println("\(indent)>. \(list)") }
+ let list = as_list(ast)
+ if DEBUG_EVAL { print("\(indent)>. \(list)") }
if list.isEmpty {
- return list
+ return ast
}
// Check for special forms, where we want to check the operation
// before evaluating all of the parameters.
let arg0 = list.first()
- if is_symbol(arg0) {
- var res: TCOVal
- let fn_symbol = arg0 as! MalSymbol
+ if let fn_symbol = as_symbolQ(arg0) {
+ let res: TCOVal
switch fn_symbol {
- case kSymbolDef: res = eval_def(list, env)
- case kSymbolLet: res = eval_let(list, env)
- case kSymbolDo: res = eval_do(list, env)
- case kSymbolIf: res = eval_if(list, env)
- case kSymbolFn: res = eval_fn(list, env)
+ case kSymbolDef: res = try eval_def(list, env)
+ case kSymbolLet: res = try eval_let(list, env)
+ case kSymbolDo: res = try eval_do(list, env)
+ case kSymbolIf: res = try eval_if(list, env)
+ case kSymbolFn: res = try eval_fn(list, env)
default: res = TCOVal()
}
switch res {
// Standard list to be applied. Evaluate all the elements first.
- let eval = eval_ast(ast, env)
- if is_error(eval) { return eval }
+ let eval = try eval_ast(ast, env)
// The result had better be a list and better be non-empty.
- let eval_list = eval as! MalList
+ let eval_list = as_list(eval)
if eval_list.isEmpty {
- return eval_list
+ return eval
}
- if DEBUG_EVAL { println("\(indent)>> \(eval)") }
+ if DEBUG_EVAL { print("\(indent)>> \(eval)") }
// Get the first element of the list and execute it.
let first = eval_list.first()
- let rest = eval_list.rest()
+ let rest = as_sequence(eval_list.rest())
- if is_builtin(first) {
- let fn = first as! MalBuiltin
- let answer = fn.apply(rest)
- if DEBUG_EVAL { println("\(indent)>>> \(answer)") }
+ if let fn = as_builtinQ(first) {
+ let answer = try fn.apply(rest)
+ if DEBUG_EVAL { print("\(indent)>>> \(answer)") }
return answer
- } else if is_closure(first) {
- let fn = first as! MalClosure
- var new_env = Environment(outer: fn.env)
- let result = new_env.set_bindings(fn.args, with_exprs:rest)
- if is_error(result) { return result }
+ } else if let fn = as_closureQ(first) {
+ let new_env = Environment(outer: fn.env)
+ let _ = try new_env.set_bindings(fn.args, with_exprs: rest)
if TCO {
env = new_env
ast = fn.body
continue
}
- let answer = EVAL(fn.body, new_env)
- if DEBUG_EVAL { println("\(indent)>>> \(answer)") }
+ let answer = try EVAL(fn.body, new_env)
+ if DEBUG_EVAL { print("\(indent)>>> \(answer)") }
return answer
}
// The first element wasn't a function to be executed. Return an
// error saying so.
- return MalError(message: "first list item does not evaluate to a function: \(first)")
+ try throw_error("first list item does not evaluate to a function: \(first)")
}
}
// Convert the value into a human-readable string for printing.
//
-func PRINT(exp: MalVal) -> String? {
- if is_error(exp) { return nil }
+private func PRINT(exp: MalVal) -> String {
return pr_str(exp, true)
}
// Perform the READ and EVAL steps. Useful for when you don't care about the
// printable result.
//
-func RE(text: String, env: Environment) -> MalVal? {
- if text.isEmpty { return nil }
- let ast = READ(text)
- if is_error(ast) {
- println("Error parsing input: \(ast)")
- return nil
- }
- let exp = EVAL(ast, env)
- if is_error(exp) {
- println("Error evaluating input: \(exp)")
- return nil
+private func RE(text: String, _ env: Environment) -> MalVal? {
+ if !text.isEmpty {
+ do {
+ let ast = try READ(text)
+ do {
+ return try EVAL(ast, env)
+ } catch let error as MalException {
+ print("Error evaluating input: \(error)")
+ } catch {
+ print("Error evaluating input: \(error)")
+ }
+ } catch let error as MalException {
+ print("Error parsing input: \(error)")
+ } catch {
+ print("Error parsing input: \(error)")
+ }
}
- return exp
+ return nil
}
// Perform the full READ/EVAL/PRINT, returning a printable string.
//
-func REP(text: String, env: Environment) -> String? {
+private func REP(text: String, _ env: Environment) -> String? {
let exp = RE(text, env)
if exp == nil { return nil }
return PRINT(exp!)
// Perform the full REPL.
//
-func REPL(env: Environment) {
+private func REPL(env: Environment) {
while true {
if let text = _readline("user> ") {
if let output = REP(text, env) {
- println("\(output)")
+ print("\(output)")
}
} else {
- println()
+ print("")
break
}
}
// taken as a script to execute. If one exists, it is executed in lieu of
// running the REPL.
//
-func process_command_line(args:[String], env:Environment) -> Bool {
- var argv = MalList()
+private func process_command_line(args: [String], _ env: Environment) -> Bool {
+ var argv = make_list()
if args.count > 2 {
let args1 = args[2..<args.count]
- let args2 = args1.map { MalString(unescaped: $0) as MalVal }
+ let args2 = args1.map { make_string($0) }
let args3 = [MalVal](args2)
- argv = MalList(array: args3)
+ argv = make_list(args3)
}
env.set(kSymbolArgv, argv)
}
func main() {
- var env = Environment(outer: nil)
+ let env = Environment(outer: nil)
load_history_file()
load_builtins(env)
RE("(def! not (fn* (a) (if a false true)))", env)
RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", env)
- env.set(kSymbolEval, MalBuiltin(function: {
- unwrap($0) {
- (ast:MalVal) -> MalVal in
- EVAL(ast, env)
+ env.set(kSymbolEval, make_builtin({
+ try! unwrap_args($0) {
+ (ast: MalVal) -> MalVal in
+ try EVAL(ast, env)
}
}))
// The number of times EVAL has been entered recursively. We keep track of this
// so that we can protect against overrunning the stack.
//
-var EVAL_level = 0
+private var EVAL_level = 0
// The maximum number of times we let EVAL recurse before throwing an exception.
// Testing puts this at some place between 1800 and 1900. Let's keep it at 500
// for safety's sake.
//
-let EVAL_leval_max = 500
+private let EVAL_leval_max = 500
// Control whether or not tail-call optimization (TCO) is enabled. We want it
// `true` most of the time, but may disable it for debugging purposes (it's
// easier to get a meaningful backtrace that way).
//
-let TCO = true
+private let TCO = true
// Control whether or not we emit debugging statements in EVAL.
//
-let DEBUG_EVAL = false
+private let DEBUG_EVAL = false
// String used to prefix information logged in EVAL. Increasing lengths of the
// string are used the more EVAL is recursed.
//
-let INDENT_TEMPLATE = "|----|----|----|----|----|----|----|----|" +
+private let INDENT_TEMPLATE = "|----|----|----|----|----|----|----|----|" +
"----|----|----|----|----|----|----|----|----|----|----|" +
"----|----|----|----|----|----|----|----|----|----|----|" +
"----|----|----|----|----|----|----|----|----|----|----|" +
// Holds the prefix of INDENT_TEMPLATE used for actual logging.
//
-var indent = String()
+private var indent = String()
// Symbols used in this module.
//
-let kSymbolArgv = MalSymbol(symbol: "*ARGV*")
-let kSymbolConcat = MalSymbol(symbol: "concat")
-let kSymbolCons = MalSymbol(symbol: "cons")
-let kSymbolDef = MalSymbol(symbol: "def!")
-let kSymbolDo = MalSymbol(symbol: "do")
-let kSymbolEval = MalSymbol(symbol: "eval")
-let kSymbolFn = MalSymbol(symbol: "fn*")
-let kSymbolIf = MalSymbol(symbol: "if")
-let kSymbolLet = MalSymbol(symbol: "let*")
-let kSymbolQuasiQuote = MalSymbol(symbol: "quasiquote")
-let kSymbolQuote = MalSymbol(symbol: "quote")
-let kSymbolSpliceUnquote = MalSymbol(symbol: "splice-unquote")
-let kSymbolUnquote = MalSymbol(symbol: "unquote")
-
-// Class to help control the incrementing and decrementing of EVAL_level. We
-// create one of these on entry to EVAL, incrementing the level. When the
-// variable goes out of scope, the object is destroyed, decrementing the level.
-//
-class EVAL_Counter {
- init() {
- ++EVAL_level
- }
- deinit {
- --EVAL_level
- }
+private let kValArgv = make_symbol("*ARGV*")
+private let kValConcat = make_symbol("concat")
+private let kValCons = make_symbol("cons")
+private let kValDef = make_symbol("def!")
+private let kValDo = make_symbol("do")
+private let kValEval = make_symbol("eval")
+private let kValFn = make_symbol("fn*")
+private let kValIf = make_symbol("if")
+private let kValLet = make_symbol("let*")
+private let kValQuasiQuote = make_symbol("quasiquote")
+private let kValQuote = make_symbol("quote")
+private let kValSpliceUnquote = make_symbol("splice-unquote")
+private let kValUnquote = make_symbol("unquote")
+private let kValTry = make_symbol("try*")
+
+private let kSymbolArgv = as_symbol(kValArgv)
+private let kSymbolConcat = as_symbol(kValConcat)
+private let kSymbolCons = as_symbol(kValCons)
+private let kSymbolDef = as_symbol(kValDef)
+private let kSymbolDo = as_symbol(kValDo)
+private let kSymbolEval = as_symbol(kValEval)
+private let kSymbolFn = as_symbol(kValFn)
+private let kSymbolIf = as_symbol(kValIf)
+private let kSymbolLet = as_symbol(kValLet)
+private let kSymbolQuasiQuote = as_symbol(kValQuasiQuote)
+private let kSymbolQuote = as_symbol(kValQuote)
+private let kSymbolSpliceUnquote = as_symbol(kValSpliceUnquote)
+private let kSymbolUnquote = as_symbol(kValUnquote)
+
+func substring(s: String, _ begin: Int, _ end: Int) -> String {
+ return s[s.startIndex.advancedBy(begin) ..< s.startIndex.advancedBy(end)]
}
// Parse the string into an AST.
//
-func READ(str: String) -> MalVal {
- return read_str(str)
+private func READ(str: String) throws -> MalVal {
+ return try read_str(str)
}
// Return whether or not `val` is a non-empty list.
//
-func is_pair(val:MalVal) -> Bool {
- if !is_sequence(val) { return false }
- let list = val as! MalSequence
- return !list.isEmpty
+private func is_pair(val: MalVal) -> Bool {
+ if let seq = as_sequenceQ(val) {
+ return !seq.isEmpty
+ }
+ return false
}
// Evaluate `quasiquote`, possibly recursing in the process.
// list) with the remaining items of the list containing that splice-quote
// expression. However, it's not clear to me why the handling of "unquote" is
// not handled similarly, for consistency's sake.
-
-func quasiquote(qq_arg:MalVal) -> MalVal {
+//
+private func quasiquote(qq_arg: MalVal) throws -> MalVal {
// If the argument is an atom or empty list:
//
// Return: (quote <argument>)
if !is_pair(qq_arg) {
- return MalList(objects: kSymbolQuote, qq_arg)
+ return make_list_from(kValQuote, qq_arg)
}
// The argument is a non-empty list -- that is (item rest...)
//
// Return: item
- let qq_list = qq_arg as! MalSequence
- if is_symbol(qq_list.first()) {
- let sym = qq_list.first() as! MalSymbol
- if sym == kSymbolUnquote {
- return qq_list.count >= 2 ? qq_list[1] : MalNil()
- }
+ let qq_list = as_sequence(qq_arg)
+ if let sym = as_symbolQ(qq_list.first()) where sym == kSymbolUnquote {
+ return qq_list.count >= 2 ? try! qq_list.nth(1) : make_nil()
}
// If the first item from the list is itself a non-empty list starting with
// Return: (concat item quasiquote(rest...))
if is_pair(qq_list.first()) {
- let qq_list_item0 = qq_list.first() as! MalSequence
- if is_symbol(qq_list_item0.first()) {
- let sym = qq_list_item0.first() as! MalSymbol
- if sym == kSymbolSpliceUnquote {
- let result = quasiquote(qq_list.rest())
- if is_error(result) { return result }
- return MalList(array: [kSymbolConcat, qq_list_item0[1], result])
- }
+ let qq_list_item0 = as_sequence(qq_list.first())
+ if let sym = as_symbolQ(qq_list_item0.first()) where sym == kSymbolSpliceUnquote {
+ let result = try quasiquote(qq_list.rest())
+ return make_list_from(kValConcat, try! qq_list_item0.nth(1), result)
}
}
//
// Return: (cons (quasiquote item) (quasiquote (rest...))
- let first = quasiquote(qq_list.first())
- if is_error(first) { return first }
-
- let rest = quasiquote(qq_list.rest())
- if is_error(rest) { return rest }
-
- return MalList(objects: kSymbolCons, first, rest)
+ let first = try quasiquote(qq_list.first())
+ let rest = try quasiquote(qq_list.rest())
+ return make_list_from(kValCons, first, rest)
}
// Perform a simple evaluation of the `ast` object. If it's a symbol,
// elements (or just the values, in the case of the hashmap). Otherwise, return
// the object unchanged.
//
-func eval_ast(ast: MalVal, env: Environment) -> MalVal {
- switch ast.type {
- case .TypeSymbol:
- let symbol = ast as! MalSymbol
- if let val = env.get(symbol) {
- return val
- }
- return MalError(message: "'\(symbol)' not found") // Specific text needed to match MAL unit tests
- case .TypeList:
- let list = ast as! MalList
- var result = [MalVal]()
- result.reserveCapacity(list.count)
- for item in list {
- let eval = EVAL(item, env)
- if is_error(eval) { return eval }
- result.append(eval)
- }
- return MalList(array: result)
- case .TypeVector:
- let vec = ast as! MalVector
- var result = [MalVal]()
- result.reserveCapacity(vec.count)
- for item in vec {
- let eval = EVAL(item, env)
- if is_error(eval) { return eval }
- result.append(eval)
- }
- return MalVector(array: result)
- case .TypeHashMap:
- let hash = ast as! MalHashMap
- var result = [MalVal]()
- result.reserveCapacity(hash.count * 2)
- for (k, v) in hash {
- let new_v = EVAL(v, env)
- if is_error(new_v) { return new_v }
- result.append(k)
- result.append(new_v)
- }
- return MalHashMap(array: result)
- default:
- return ast
+private func eval_ast(ast: MalVal, _ env: Environment) throws -> MalVal {
+ if let symbol = as_symbolQ(ast) {
+ guard let val = env.get(symbol) else {
+ try throw_error("'\(symbol)' not found") // Specific text needed to match MAL unit tests
+ }
+ return val
}
+ if let list = as_listQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(list.count))
+ for item in list {
+ let eval = try EVAL(item, env)
+ result.append(eval)
+ }
+ return make_list(result)
+ }
+ if let vec = as_vectorQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(vec.count))
+ for item in vec {
+ let eval = try EVAL(item, env)
+ result.append(eval)
+ }
+ return make_vector(result)
+ }
+ if let hash = as_hashmapQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(hash.count) * 2)
+ for (k, v) in hash {
+ let new_v = try EVAL(v, env)
+ result.append(k)
+ result.append(new_v)
+ }
+ return make_hashmap(result)
+ }
+ return ast
}
-enum TCOVal {
+private enum TCOVal {
case NoResult
case Return(MalVal)
case Continue(MalVal, Environment)
init() { self = .NoResult }
init(_ result: MalVal) { self = .Return(result) }
init(_ ast: MalVal, _ env: Environment) { self = .Continue(ast, env) }
- init(_ e: String) { self = .Return(MalError(message: e)) }
}
// EVALuate "def!".
//
-func eval_def(list: MalSequence, env: Environment) -> TCOVal {
- if list.count != 3 {
- return TCOVal("expected 2 arguments to def!, got \(list.count - 1)")
+private func eval_def(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count == 3 else {
+ try throw_error("expected 2 arguments to def!, got \(list.count - 1)")
}
- let arg1 = list[1]
- let arg2 = list[2]
- if !is_symbol(arg1) {
- return TCOVal("expected symbol for first argument to def!")
+ let arg1 = try! list.nth(1)
+ let arg2 = try! list.nth(2)
+ guard let sym = as_symbolQ(arg1) else {
+ try throw_error("expected symbol for first argument to def!")
}
- let sym = arg1 as! MalSymbol
- let value = EVAL(arg2, env)
- if is_error(value) { return TCOVal(value) }
+ let value = try EVAL(arg2, env)
return TCOVal(env.set(sym, value))
}
// EVALuate "let*".
//
-func eval_let(list: MalSequence, env: Environment) -> TCOVal {
- if list.count != 3 {
- return TCOVal("expected 2 arguments to let*, got \(list.count - 1)")
+private func eval_let(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count == 3 else {
+ try throw_error("expected 2 arguments to let*, got \(list.count - 1)")
}
- let arg1 = list[1]
- let arg2 = list[2]
- if !is_sequence(arg1) {
- return TCOVal("expected list for first argument to let*")
+ let arg1 = try! list.nth(1)
+ let arg2 = try! list.nth(2)
+ guard let bindings = as_sequenceQ(arg1) else {
+ try throw_error("expected list for first argument to let*")
}
- let bindings = arg1 as! MalSequence
- if bindings.count % 2 == 1 {
- return TCOVal("expected even number of elements in bindings to let*, got \(bindings.count)")
+ guard bindings.count % 2 == 0 else {
+ try throw_error("expected even number of elements in bindings to let*, got \(bindings.count)")
}
- var new_env = Environment(outer: env)
- for var index = 0; index < bindings.count; index += 2 {
- let binding_name = bindings[index]
- let binding_value = bindings[index + 1]
-
- if !is_symbol(binding_name) {
- return TCOVal("expected symbol for first element in binding pair")
+ let new_env = Environment(outer: env)
+ for var index: MalIntType = 0; index < bindings.count; index += 2 {
+ let binding_name = try! bindings.nth(index)
+ let binding_value = try! bindings.nth(index + 1)
+ guard let binding_symbol = as_symbolQ(binding_name) else {
+ try throw_error("expected symbol for first element in binding pair")
}
- let binding_symbol = binding_name as! MalSymbol
- let evaluated_value = EVAL(binding_value, new_env)
- if is_error(evaluated_value) { return TCOVal(evaluated_value) }
+ let evaluated_value = try EVAL(binding_value, new_env)
new_env.set(binding_symbol, evaluated_value)
}
if TCO {
return TCOVal(arg2, new_env)
}
- return TCOVal(EVAL(arg2, new_env))
+ return TCOVal(try EVAL(arg2, new_env))
}
// EVALuate "do".
//
-func eval_do(list: MalSequence, env: Environment) -> TCOVal {
+private func eval_do(list: MalSequence, _ env: Environment) throws -> TCOVal {
if TCO {
- let eval = eval_ast(MalList(slice: list[1..<list.count-1]), env)
- if is_error(eval) { return TCOVal(eval) }
+ let _ = try eval_ast(list.range_from(1, to: list.count-1), env)
return TCOVal(list.last(), env)
}
- let evaluated_ast = eval_ast(list.rest(), env)
- if is_error(evaluated_ast) { return TCOVal(evaluated_ast) }
- let evaluated_seq = evaluated_ast as! MalSequence
+ let evaluated_ast = try eval_ast(list.rest(), env)
+ let evaluated_seq = as_sequence(evaluated_ast)
return TCOVal(evaluated_seq.last())
}
// EVALuate "if".
//
-func eval_if(list: MalSequence, env: Environment) -> TCOVal {
- if list.count < 3 {
- return TCOVal("expected at least 2 arguments to if, got \(list.count - 1)")
+private func eval_if(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count >= 3 else {
+ try throw_error("expected at least 2 arguments to if, got \(list.count - 1)")
}
- let cond_result = EVAL(list[1], env)
- var new_ast = MalVal()
+ let cond_result = try EVAL(try! list.nth(1), env)
+ var new_ast: MalVal
if is_truthy(cond_result) {
- new_ast = list[2]
+ new_ast = try! list.nth(2)
} else if list.count == 4 {
- new_ast = list[3]
+ new_ast = try! list.nth(3)
} else {
- return TCOVal(MalNil())
+ return TCOVal(make_nil())
}
if TCO {
return TCOVal(new_ast, env)
}
- return TCOVal(EVAL(new_ast, env))
+ return TCOVal(try EVAL(new_ast, env))
}
// EVALuate "fn*".
//
-func eval_fn(list: MalSequence, env: Environment) -> TCOVal {
- if list.count != 3 {
- return TCOVal("expected 2 arguments to fn*, got \(list.count - 1)")
+private func eval_fn(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count == 3 else {
+ try throw_error("expected 2 arguments to fn*, got \(list.count - 1)")
}
- if !is_sequence(list[1]) {
- return TCOVal("expected list or vector for first argument to fn*")
+ guard let seq = as_sequenceQ(try! list.nth(1)) else {
+ try throw_error("expected list or vector for first argument to fn*")
}
- return TCOVal(MalClosure(eval: EVAL, args:list[1] as! MalSequence, body:list[2], env:env))
+ return TCOVal(make_closure((eval: EVAL, args: seq, body: try! list.nth(2), env: env)))
}
// EVALuate "quote".
//
-func eval_quote(list: MalSequence, env: Environment) -> TCOVal {
+private func eval_quote(list: MalSequence, _ env: Environment) throws -> TCOVal {
if list.count >= 2 {
- return TCOVal(list[1])
+ return TCOVal(try! list.nth(1))
}
- return TCOVal(MalNil())
+ return TCOVal(make_nil())
}
// EVALuate "quasiquote".
//
-func eval_quasiquote(list: MalSequence, env: Environment) -> TCOVal {
- if list.count >= 2 {
- if TCO {
- return TCOVal(quasiquote(list[1]), env)
- }
- return TCOVal(EVAL(quasiquote(list[1]), env))
+private func eval_quasiquote(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count >= 2 else {
+ try throw_error("Expected non-nil parameter to 'quasiquote'")
+ }
+ if TCO {
+ return TCOVal(try quasiquote(try! list.nth(1)), env)
}
- return TCOVal("Expected non-nil parameter to 'quasiquote'")
+ return TCOVal(try EVAL(try quasiquote(try! list.nth(1)), env))
}
// Walk the AST and completely evaluate it, handling macro expansions, special
// forms and function calls.
//
-func EVAL(var ast: MalVal, var env: Environment) -> MalVal {
- let x = EVAL_Counter()
- if EVAL_level > EVAL_leval_max {
- return MalError(message: "Recursing too many levels (> \(EVAL_leval_max))")
+private func EVAL(var ast: MalVal, var _ env: Environment) throws -> MalVal {
+ EVAL_level++
+ defer { EVAL_level-- }
+ guard EVAL_level <= EVAL_leval_max else {
+ try throw_error("Recursing too many levels (> \(EVAL_leval_max))")
}
if DEBUG_EVAL {
- indent = prefix(INDENT_TEMPLATE, EVAL_level)
+ indent = substring(INDENT_TEMPLATE, 0, EVAL_level)
}
while true {
- if is_error(ast) { return ast }
- if DEBUG_EVAL { println("\(indent)> \(ast)") }
+ if DEBUG_EVAL { print("\(indent)> \(ast)") }
if !is_list(ast) {
// Not a list -- just evaluate and return.
- let answer = eval_ast(ast, env)
- if DEBUG_EVAL { println("\(indent)>>> \(answer)") }
+ let answer = try eval_ast(ast, env)
+ if DEBUG_EVAL { print("\(indent)>>> \(answer)") }
return answer
}
// Special handling if it's a list.
- var list = ast as! MalList
- if DEBUG_EVAL { println("\(indent)>. \(list)") }
+ let list = as_list(ast)
+ if DEBUG_EVAL { print("\(indent)>. \(list)") }
if list.isEmpty {
- return list
+ return ast
}
// Check for special forms, where we want to check the operation
// before evaluating all of the parameters.
let arg0 = list.first()
- if is_symbol(arg0) {
- var res: TCOVal
- let fn_symbol = arg0 as! MalSymbol
+ if let fn_symbol = as_symbolQ(arg0) {
+ let res: TCOVal
switch fn_symbol {
- case kSymbolDef: res = eval_def(list, env)
- case kSymbolLet: res = eval_let(list, env)
- case kSymbolDo: res = eval_do(list, env)
- case kSymbolIf: res = eval_if(list, env)
- case kSymbolFn: res = eval_fn(list, env)
- case kSymbolQuote: res = eval_quote(list, env)
- case kSymbolQuasiQuote: res = eval_quasiquote(list, env)
+ case kSymbolDef: res = try eval_def(list, env)
+ case kSymbolLet: res = try eval_let(list, env)
+ case kSymbolDo: res = try eval_do(list, env)
+ case kSymbolIf: res = try eval_if(list, env)
+ case kSymbolFn: res = try eval_fn(list, env)
+ case kSymbolQuote: res = try eval_quote(list, env)
+ case kSymbolQuasiQuote: res = try eval_quasiquote(list, env)
default: res = TCOVal()
}
switch res {
// Standard list to be applied. Evaluate all the elements first.
- let eval = eval_ast(ast, env)
- if is_error(eval) { return eval }
+ let eval = try eval_ast(ast, env)
// The result had better be a list and better be non-empty.
- let eval_list = eval as! MalList
+ let eval_list = as_list(eval)
if eval_list.isEmpty {
- return eval_list
+ return eval
}
- if DEBUG_EVAL { println("\(indent)>> \(eval)") }
+ if DEBUG_EVAL { print("\(indent)>> \(eval)") }
// Get the first element of the list and execute it.
let first = eval_list.first()
- let rest = eval_list.rest()
+ let rest = as_sequence(eval_list.rest())
- if is_builtin(first) {
- let fn = first as! MalBuiltin
- let answer = fn.apply(rest)
- if DEBUG_EVAL { println("\(indent)>>> \(answer)") }
+ if let fn = as_builtinQ(first) {
+ let answer = try fn.apply(rest)
+ if DEBUG_EVAL { print("\(indent)>>> \(answer)") }
return answer
- } else if is_closure(first) {
- let fn = first as! MalClosure
- var new_env = Environment(outer: fn.env)
- let result = new_env.set_bindings(fn.args, with_exprs:rest)
- if is_error(result) { return result }
+ } else if let fn = as_closureQ(first) {
+ let new_env = Environment(outer: fn.env)
+ let _ = try new_env.set_bindings(fn.args, with_exprs: rest)
if TCO {
env = new_env
ast = fn.body
continue
}
- let answer = EVAL(fn.body, new_env)
- if DEBUG_EVAL { println("\(indent)>>> \(answer)") }
+ let answer = try EVAL(fn.body, new_env)
+ if DEBUG_EVAL { print("\(indent)>>> \(answer)") }
return answer
}
// The first element wasn't a function to be executed. Return an
// error saying so.
- return MalError(message: "first list item does not evaluate to a function: \(first)")
+ try throw_error("first list item does not evaluate to a function: \(first)")
}
}
// Convert the value into a human-readable string for printing.
//
-func PRINT(exp: MalVal) -> String? {
- if is_error(exp) { return nil }
+private func PRINT(exp: MalVal) -> String {
return pr_str(exp, true)
}
// Perform the READ and EVAL steps. Useful for when you don't care about the
// printable result.
//
-func RE(text: String, env: Environment) -> MalVal? {
- if text.isEmpty { return nil }
- let ast = READ(text)
- if is_error(ast) {
- println("Error parsing input: \(ast)")
- return nil
- }
- let exp = EVAL(ast, env)
- if is_error(exp) {
- println("Error evaluating input: \(exp)")
- return nil
+private func RE(text: String, _ env: Environment) -> MalVal? {
+ if !text.isEmpty {
+ do {
+ let ast = try READ(text)
+ do {
+ return try EVAL(ast, env)
+ } catch let error as MalException {
+ print("Error evaluating input: \(error)")
+ } catch {
+ print("Error evaluating input: \(error)")
+ }
+ } catch let error as MalException {
+ print("Error parsing input: \(error)")
+ } catch {
+ print("Error parsing input: \(error)")
+ }
}
- return exp
+ return nil
}
// Perform the full READ/EVAL/PRINT, returning a printable string.
//
-func REP(text: String, env: Environment) -> String? {
+private func REP(text: String, _ env: Environment) -> String? {
let exp = RE(text, env)
if exp == nil { return nil }
return PRINT(exp!)
// Perform the full REPL.
//
-func REPL(env: Environment) {
+private func REPL(env: Environment) {
while true {
if let text = _readline("user> ") {
if let output = REP(text, env) {
- println("\(output)")
+ print("\(output)")
}
} else {
- println()
+ print("")
break
}
}
// taken as a script to execute. If one exists, it is executed in lieu of
// running the REPL.
//
-func process_command_line(args:[String], env:Environment) -> Bool {
- var argv = MalList()
+private func process_command_line(args: [String], _ env: Environment) -> Bool {
+ var argv = make_list()
if args.count > 2 {
let args1 = args[2..<args.count]
- let args2 = args1.map { MalString(unescaped: $0) as MalVal }
+ let args2 = args1.map { make_string($0) }
let args3 = [MalVal](args2)
- argv = MalList(array: args3)
+ argv = make_list(args3)
}
env.set(kSymbolArgv, argv)
}
func main() {
- var env = Environment(outer: nil)
+ let env = Environment(outer: nil)
load_history_file()
load_builtins(env)
RE("(def! not (fn* (a) (if a false true)))", env)
RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", env)
- env.set(kSymbolEval, MalBuiltin(function: {
- unwrap($0) {
- (ast:MalVal) -> MalVal in
- EVAL(ast, env)
+ env.set(kSymbolEval, make_builtin({
+ try! unwrap_args($0) {
+ (ast: MalVal) -> MalVal in
+ try EVAL(ast, env)
}
}))
// The number of times EVAL has been entered recursively. We keep track of this
// so that we can protect against overrunning the stack.
//
-var EVAL_level = 0
+private var EVAL_level = 0
// The maximum number of times we let EVAL recurse before throwing an exception.
// Testing puts this at some place between 1800 and 1900. Let's keep it at 500
// for safety's sake.
//
-let EVAL_leval_max = 500
+private let EVAL_leval_max = 500
// Control whether or not tail-call optimization (TCO) is enabled. We want it
// `true` most of the time, but may disable it for debugging purposes (it's
// easier to get a meaningful backtrace that way).
//
-let TCO = true
+private let TCO = true
// Control whether or not we emit debugging statements in EVAL.
//
-let DEBUG_EVAL = false
+private let DEBUG_EVAL = false
// String used to prefix information logged in EVAL. Increasing lengths of the
// string are used the more EVAL is recursed.
//
-let INDENT_TEMPLATE = "|----|----|----|----|----|----|----|----|" +
+private let INDENT_TEMPLATE = "|----|----|----|----|----|----|----|----|" +
"----|----|----|----|----|----|----|----|----|----|----|" +
"----|----|----|----|----|----|----|----|----|----|----|" +
"----|----|----|----|----|----|----|----|----|----|----|" +
// Holds the prefix of INDENT_TEMPLATE used for actual logging.
//
-var indent = String()
+private var indent = String()
// Symbols used in this module.
//
-let kSymbolArgv = MalSymbol(symbol: "*ARGV*")
-let kSymbolConcat = MalSymbol(symbol: "concat")
-let kSymbolCons = MalSymbol(symbol: "cons")
-let kSymbolDef = MalSymbol(symbol: "def!")
-let kSymbolDefMacro = MalSymbol(symbol: "defmacro!")
-let kSymbolDo = MalSymbol(symbol: "do")
-let kSymbolEval = MalSymbol(symbol: "eval")
-let kSymbolFn = MalSymbol(symbol: "fn*")
-let kSymbolIf = MalSymbol(symbol: "if")
-let kSymbolLet = MalSymbol(symbol: "let*")
-let kSymbolMacroExpand = MalSymbol(symbol: "macroexpand")
-let kSymbolQuasiQuote = MalSymbol(symbol: "quasiquote")
-let kSymbolQuote = MalSymbol(symbol: "quote")
-let kSymbolSpliceUnquote = MalSymbol(symbol: "splice-unquote")
-let kSymbolUnquote = MalSymbol(symbol: "unquote")
-
-// Class to help control the incrementing and decrementing of EVAL_level. We
-// create one of these on entry to EVAL, incrementing the level. When the
-// variable goes out of scope, the object is destroyed, decrementing the level.
-//
-class EVAL_Counter {
- init() {
- ++EVAL_level
- }
- deinit {
- --EVAL_level
- }
+private let kValArgv = make_symbol("*ARGV*")
+private let kValConcat = make_symbol("concat")
+private let kValCons = make_symbol("cons")
+private let kValDef = make_symbol("def!")
+private let kValDefMacro = make_symbol("defmacro!")
+private let kValDo = make_symbol("do")
+private let kValEval = make_symbol("eval")
+private let kValFn = make_symbol("fn*")
+private let kValIf = make_symbol("if")
+private let kValLet = make_symbol("let*")
+private let kValMacroExpand = make_symbol("macroexpand")
+private let kValQuasiQuote = make_symbol("quasiquote")
+private let kValQuote = make_symbol("quote")
+private let kValSpliceUnquote = make_symbol("splice-unquote")
+private let kValUnquote = make_symbol("unquote")
+private let kValTry = make_symbol("try*")
+
+private let kSymbolArgv = as_symbol(kValArgv)
+private let kSymbolConcat = as_symbol(kValConcat)
+private let kSymbolCons = as_symbol(kValCons)
+private let kSymbolDef = as_symbol(kValDef)
+private let kSymbolDefMacro = as_symbol(kValDefMacro)
+private let kSymbolDo = as_symbol(kValDo)
+private let kSymbolEval = as_symbol(kValEval)
+private let kSymbolFn = as_symbol(kValFn)
+private let kSymbolIf = as_symbol(kValIf)
+private let kSymbolLet = as_symbol(kValLet)
+private let kSymbolMacroExpand = as_symbol(kValMacroExpand)
+private let kSymbolQuasiQuote = as_symbol(kValQuasiQuote)
+private let kSymbolQuote = as_symbol(kValQuote)
+private let kSymbolSpliceUnquote = as_symbol(kValSpliceUnquote)
+private let kSymbolUnquote = as_symbol(kValUnquote)
+
+func substring(s: String, _ begin: Int, _ end: Int) -> String {
+ return s[s.startIndex.advancedBy(begin) ..< s.startIndex.advancedBy(end)]
}
// Parse the string into an AST.
//
-func READ(str: String) -> MalVal {
- return read_str(str)
+private func READ(str: String) throws -> MalVal {
+ return try read_str(str)
}
// Return whether or not `val` is a non-empty list.
//
-func is_pair(val:MalVal) -> Bool {
- if !is_sequence(val) { return false }
- let list = val as! MalSequence
- return !list.isEmpty
+private func is_pair(val: MalVal) -> Bool {
+ if let seq = as_sequenceQ(val) {
+ return !seq.isEmpty
+ }
+ return false
}
// Expand macros for as long as the expression looks like a macro invocation.
//
-func macroexpand(var ast:MalVal, env:Environment) -> MalVal {
+private func macroexpand(var ast: MalVal, _ env: Environment) throws -> MalVal {
while true {
- if !is_list(ast) { break }
- let ast_as_list = ast as! MalList
- if ast_as_list.isEmpty { break }
- let first = ast_as_list.first()
- if !is_symbol(first) { break }
- let macro_name = first as! MalSymbol
- let obj = env.get(macro_name)
- if obj == nil { break }
- if !is_closure(obj!) { break }
- let macro = obj! as! MalClosure
- if !macro.is_macro { break }
- var new_env = Environment(outer: macro.env)
- let rest = ast_as_list.rest()
- let res = new_env.set_bindings(macro.args, with_exprs:rest)
- if is_error(res) { return res }
- ast = EVAL(macro.body, new_env)
+ if let ast_as_list = as_listQ(ast) where !ast_as_list.isEmpty,
+ let macro_name = as_symbolQ(ast_as_list.first()),
+ let obj = env.get(macro_name),
+ let macro = as_macroQ(obj)
+ {
+ let new_env = Environment(outer: macro.env)
+ let rest = as_sequence(ast_as_list.rest())
+ let _ = try new_env.set_bindings(macro.args, with_exprs: rest)
+ ast = try EVAL(macro.body, new_env)
+ continue
+ }
+ return ast
}
- return ast
}
// Evaluate `quasiquote`, possibly recursing in the process.
// list) with the remaining items of the list containing that splice-quote
// expression. However, it's not clear to me why the handling of "unquote" is
// not handled similarly, for consistency's sake.
-
-func quasiquote(qq_arg:MalVal) -> MalVal {
+//
+private func quasiquote(qq_arg: MalVal) throws -> MalVal {
// If the argument is an atom or empty list:
//
// Return: (quote <argument>)
if !is_pair(qq_arg) {
- return MalList(objects: kSymbolQuote, qq_arg)
+ return make_list_from(kValQuote, qq_arg)
}
// The argument is a non-empty list -- that is (item rest...)
//
// Return: item
- let qq_list = qq_arg as! MalSequence
- if is_symbol(qq_list.first()) {
- let sym = qq_list.first() as! MalSymbol
- if sym == kSymbolUnquote {
- return qq_list.count >= 2 ? qq_list[1] : MalNil()
- }
+ let qq_list = as_sequence(qq_arg)
+ if let sym = as_symbolQ(qq_list.first()) where sym == kSymbolUnquote {
+ return qq_list.count >= 2 ? try! qq_list.nth(1) : make_nil()
}
// If the first item from the list is itself a non-empty list starting with
// Return: (concat item quasiquote(rest...))
if is_pair(qq_list.first()) {
- let qq_list_item0 = qq_list.first() as! MalSequence
- if is_symbol(qq_list_item0.first()) {
- let sym = qq_list_item0.first() as! MalSymbol
- if sym == kSymbolSpliceUnquote {
- let result = quasiquote(qq_list.rest())
- if is_error(result) { return result }
- return MalList(array: [kSymbolConcat, qq_list_item0[1], result])
- }
+ let qq_list_item0 = as_sequence(qq_list.first())
+ if let sym = as_symbolQ(qq_list_item0.first()) where sym == kSymbolSpliceUnquote {
+ let result = try quasiquote(qq_list.rest())
+ return make_list_from(kValConcat, try! qq_list_item0.nth(1), result)
}
}
//
// Return: (cons (quasiquote item) (quasiquote (rest...))
- let first = quasiquote(qq_list.first())
- if is_error(first) { return first }
-
- let rest = quasiquote(qq_list.rest())
- if is_error(rest) { return rest }
-
- return MalList(objects: kSymbolCons, first, rest)
+ let first = try quasiquote(qq_list.first())
+ let rest = try quasiquote(qq_list.rest())
+ return make_list_from(kValCons, first, rest)
}
// Perform a simple evaluation of the `ast` object. If it's a symbol,
// elements (or just the values, in the case of the hashmap). Otherwise, return
// the object unchanged.
//
-func eval_ast(ast: MalVal, env: Environment) -> MalVal {
- switch ast.type {
- case .TypeSymbol:
- let symbol = ast as! MalSymbol
- if let val = env.get(symbol) {
- return val
- }
- return MalError(message: "'\(symbol)' not found") // Specific text needed to match MAL unit tests
- case .TypeList:
- let list = ast as! MalList
- var result = [MalVal]()
- result.reserveCapacity(list.count)
- for item in list {
- let eval = EVAL(item, env)
- if is_error(eval) { return eval }
- result.append(eval)
- }
- return MalList(array: result)
- case .TypeVector:
- let vec = ast as! MalVector
- var result = [MalVal]()
- result.reserveCapacity(vec.count)
- for item in vec {
- let eval = EVAL(item, env)
- if is_error(eval) { return eval }
- result.append(eval)
- }
- return MalVector(array: result)
- case .TypeHashMap:
- let hash = ast as! MalHashMap
- var result = [MalVal]()
- result.reserveCapacity(hash.count * 2)
- for (k, v) in hash {
- let new_v = EVAL(v, env)
- if is_error(new_v) { return new_v }
- result.append(k)
- result.append(new_v)
- }
- return MalHashMap(array: result)
- default:
- return ast
+private func eval_ast(ast: MalVal, _ env: Environment) throws -> MalVal {
+ if let symbol = as_symbolQ(ast) {
+ guard let val = env.get(symbol) else {
+ try throw_error("'\(symbol)' not found") // Specific text needed to match MAL unit tests
+ }
+ return val
+ }
+ if let list = as_listQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(list.count))
+ for item in list {
+ let eval = try EVAL(item, env)
+ result.append(eval)
+ }
+ return make_list(result)
+ }
+ if let vec = as_vectorQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(vec.count))
+ for item in vec {
+ let eval = try EVAL(item, env)
+ result.append(eval)
+ }
+ return make_vector(result)
+ }
+ if let hash = as_hashmapQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(hash.count) * 2)
+ for (k, v) in hash {
+ let new_v = try EVAL(v, env)
+ result.append(k)
+ result.append(new_v)
+ }
+ return make_hashmap(result)
}
+ return ast
}
-enum TCOVal {
+private enum TCOVal {
case NoResult
case Return(MalVal)
case Continue(MalVal, Environment)
init() { self = .NoResult }
init(_ result: MalVal) { self = .Return(result) }
init(_ ast: MalVal, _ env: Environment) { self = .Continue(ast, env) }
- init(_ e: String) { self = .Return(MalError(message: e)) }
}
// EVALuate "def!" and "defmacro!".
//
-func eval_def(list: MalSequence, env: Environment) -> TCOVal {
- if list.count != 3 {
- return TCOVal("expected 2 arguments to def!, got \(list.count - 1)")
- }
- let arg0 = list[0] as! MalSymbol
- let arg1 = list[1]
- let arg2 = list[2]
- if !is_symbol(arg1) {
- return TCOVal("expected symbol for first argument to def!")
- }
- let sym = arg1 as! MalSymbol
- let value = EVAL(arg2, env)
- if is_error(value) { return TCOVal(value) }
- if arg0 == kSymbolDefMacro {
- if is_closure(value) {
- let as_closure = value as! MalClosure
- as_closure.is_macro = true
- } else {
- return TCOVal("expected closure, got \(value)")
+private func eval_def(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count == 3 else {
+ try throw_error("expected 2 arguments to def!, got \(list.count - 1)")
+ }
+ let arg0 = try! list.nth(0)
+ let arg1 = try! list.nth(1)
+ let arg2 = try! list.nth(2)
+ guard let sym = as_symbolQ(arg1) else {
+ try throw_error("expected symbol for first argument to def!")
+ }
+ var value = try EVAL(arg2, env)
+ if as_symbol(arg0) == kSymbolDefMacro {
+ guard let closure = as_closureQ(value) else {
+ try throw_error("expected closure, got \(value)")
}
+ value = make_macro(closure)
}
return TCOVal(env.set(sym, value))
}
// EVALuate "let*".
//
-func eval_let(list: MalSequence, env: Environment) -> TCOVal {
- if list.count != 3 {
- return TCOVal("expected 2 arguments to let*, got \(list.count - 1)")
+private func eval_let(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count == 3 else {
+ try throw_error("expected 2 arguments to let*, got \(list.count - 1)")
}
- let arg1 = list[1]
- let arg2 = list[2]
- if !is_sequence(arg1) {
- return TCOVal("expected list for first argument to let*")
+ let arg1 = try! list.nth(1)
+ let arg2 = try! list.nth(2)
+ guard let bindings = as_sequenceQ(arg1) else {
+ try throw_error("expected list for first argument to let*")
}
- let bindings = arg1 as! MalSequence
- if bindings.count % 2 == 1 {
- return TCOVal("expected even number of elements in bindings to let*, got \(bindings.count)")
+ guard bindings.count % 2 == 0 else {
+ try throw_error("expected even number of elements in bindings to let*, got \(bindings.count)")
}
- var new_env = Environment(outer: env)
- for var index = 0; index < bindings.count; index += 2 {
- let binding_name = bindings[index]
- let binding_value = bindings[index + 1]
-
- if !is_symbol(binding_name) {
- return TCOVal("expected symbol for first element in binding pair")
+ let new_env = Environment(outer: env)
+ for var index: MalIntType = 0; index < bindings.count; index += 2 {
+ let binding_name = try! bindings.nth(index)
+ let binding_value = try! bindings.nth(index + 1)
+ guard let binding_symbol = as_symbolQ(binding_name) else {
+ try throw_error("expected symbol for first element in binding pair")
}
- let binding_symbol = binding_name as! MalSymbol
- let evaluated_value = EVAL(binding_value, new_env)
- if is_error(evaluated_value) { return TCOVal(evaluated_value) }
+ let evaluated_value = try EVAL(binding_value, new_env)
new_env.set(binding_symbol, evaluated_value)
}
if TCO {
return TCOVal(arg2, new_env)
}
- return TCOVal(EVAL(arg2, new_env))
+ return TCOVal(try EVAL(arg2, new_env))
}
// EVALuate "do".
//
-func eval_do(list: MalSequence, env: Environment) -> TCOVal {
+private func eval_do(list: MalSequence, _ env: Environment) throws -> TCOVal {
if TCO {
- let eval = eval_ast(MalList(slice: list[1..<list.count-1]), env)
- if is_error(eval) { return TCOVal(eval) }
+ let _ = try eval_ast(list.range_from(1, to: list.count-1), env)
return TCOVal(list.last(), env)
}
- let evaluated_ast = eval_ast(list.rest(), env)
- if is_error(evaluated_ast) { return TCOVal(evaluated_ast) }
- let evaluated_seq = evaluated_ast as! MalSequence
+ let evaluated_ast = try eval_ast(list.rest(), env)
+ let evaluated_seq = as_sequence(evaluated_ast)
return TCOVal(evaluated_seq.last())
}
// EVALuate "if".
//
-func eval_if(list: MalSequence, env: Environment) -> TCOVal {
- if list.count < 3 {
- return TCOVal("expected at least 2 arguments to if, got \(list.count - 1)")
+private func eval_if(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count >= 3 else {
+ try throw_error("expected at least 2 arguments to if, got \(list.count - 1)")
}
- let cond_result = EVAL(list[1], env)
- var new_ast = MalVal()
+ let cond_result = try EVAL(try! list.nth(1), env)
+ var new_ast: MalVal
if is_truthy(cond_result) {
- new_ast = list[2]
+ new_ast = try! list.nth(2)
} else if list.count == 4 {
- new_ast = list[3]
+ new_ast = try! list.nth(3)
} else {
- return TCOVal(MalNil())
+ return TCOVal(make_nil())
}
if TCO {
return TCOVal(new_ast, env)
}
- return TCOVal(EVAL(new_ast, env))
+ return TCOVal(try EVAL(new_ast, env))
}
// EVALuate "fn*".
//
-func eval_fn(list: MalSequence, env: Environment) -> TCOVal {
- if list.count != 3 {
- return TCOVal("expected 2 arguments to fn*, got \(list.count - 1)")
+private func eval_fn(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count == 3 else {
+ try throw_error("expected 2 arguments to fn*, got \(list.count - 1)")
}
- if !is_sequence(list[1]) {
- return TCOVal("expected list or vector for first argument to fn*")
+ guard let seq = as_sequenceQ(try! list.nth(1)) else {
+ try throw_error("expected list or vector for first argument to fn*")
}
- return TCOVal(MalClosure(eval: EVAL, args:list[1] as! MalSequence, body:list[2], env:env))
+ return TCOVal(make_closure((eval: EVAL, args: seq, body: try! list.nth(2), env: env)))
}
// EVALuate "quote".
//
-func eval_quote(list: MalSequence, env: Environment) -> TCOVal {
+private func eval_quote(list: MalSequence, _ env: Environment) throws -> TCOVal {
if list.count >= 2 {
- return TCOVal(list[1])
+ return TCOVal(try! list.nth(1))
}
- return TCOVal(MalNil())
+ return TCOVal(make_nil())
}
// EVALuate "quasiquote".
//
-func eval_quasiquote(list: MalSequence, env: Environment) -> TCOVal {
- if list.count >= 2 {
- if TCO {
- return TCOVal(quasiquote(list[1]), env)
- }
- return TCOVal(EVAL(quasiquote(list[1]), env))
+private func eval_quasiquote(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count >= 2 else {
+ try throw_error("Expected non-nil parameter to 'quasiquote'")
+ }
+ if TCO {
+ return TCOVal(try quasiquote(try! list.nth(1)), env)
}
- return TCOVal("Expected non-nil parameter to 'quasiquote'")
+ return TCOVal(try EVAL(try quasiquote(try! list.nth(1)), env))
}
// EVALuate "macroexpand".
//
-func eval_macroexpand(list: MalSequence, env: Environment) -> TCOVal {
- if list.count >= 2 {
- return TCOVal(macroexpand(list[1], env))
+private func eval_macroexpand(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count >= 2 else {
+ try throw_error("Expected parameter to 'macroexpand'")
}
- return TCOVal("Expected parameter to 'macroexpand'")
+ return TCOVal(try macroexpand(try! list.nth(1), env))
}
// Walk the AST and completely evaluate it, handling macro expansions, special
// forms and function calls.
//
-func EVAL(var ast: MalVal, var env: Environment) -> MalVal {
- let x = EVAL_Counter()
- if EVAL_level > EVAL_leval_max {
- return MalError(message: "Recursing too many levels (> \(EVAL_leval_max))")
+private func EVAL(var ast: MalVal, var _ env: Environment) throws -> MalVal {
+ EVAL_level++
+ defer { EVAL_level-- }
+ guard EVAL_level <= EVAL_leval_max else {
+ try throw_error("Recursing too many levels (> \(EVAL_leval_max))")
}
if DEBUG_EVAL {
- indent = prefix(INDENT_TEMPLATE, EVAL_level)
+ indent = substring(INDENT_TEMPLATE, 0, EVAL_level)
}
while true {
- if is_error(ast) { return ast }
- if DEBUG_EVAL { println("\(indent)> \(ast)") }
+ if DEBUG_EVAL { print("\(indent)> \(ast)") }
if !is_list(ast) {
// Not a list -- just evaluate and return.
- let answer = eval_ast(ast, env)
- if DEBUG_EVAL { println("\(indent)>>> \(answer)") }
+ let answer = try eval_ast(ast, env)
+ if DEBUG_EVAL { print("\(indent)>>> \(answer)") }
return answer
}
// Special handling if it's a list.
- var list = ast as! MalList
- ast = macroexpand(ast, env)
+ var list = as_list(ast)
+ ast = try macroexpand(ast, env)
if !is_list(ast) { return ast }
- list = ast as! MalList
+ list = as_list(ast)
- if DEBUG_EVAL { println("\(indent)>. \(list)") }
+ if DEBUG_EVAL { print("\(indent)>. \(list)") }
if list.isEmpty {
- return list
+ return ast
}
// Check for special forms, where we want to check the operation
// before evaluating all of the parameters.
let arg0 = list.first()
- if is_symbol(arg0) {
- var res: TCOVal
- let fn_symbol = arg0 as! MalSymbol
+ if let fn_symbol = as_symbolQ(arg0) {
+ let res: TCOVal
switch fn_symbol {
- case kSymbolDef: res = eval_def(list, env)
- case kSymbolDefMacro: res = eval_def(list, env)
- case kSymbolLet: res = eval_let(list, env)
- case kSymbolDo: res = eval_do(list, env)
- case kSymbolIf: res = eval_if(list, env)
- case kSymbolFn: res = eval_fn(list, env)
- case kSymbolQuote: res = eval_quote(list, env)
- case kSymbolQuasiQuote: res = eval_quasiquote(list, env)
- case kSymbolMacroExpand: res = eval_macroexpand(list, env)
+ case kSymbolDef: res = try eval_def(list, env)
+ case kSymbolDefMacro: res = try eval_def(list, env)
+ case kSymbolLet: res = try eval_let(list, env)
+ case kSymbolDo: res = try eval_do(list, env)
+ case kSymbolIf: res = try eval_if(list, env)
+ case kSymbolFn: res = try eval_fn(list, env)
+ case kSymbolQuote: res = try eval_quote(list, env)
+ case kSymbolQuasiQuote: res = try eval_quasiquote(list, env)
+ case kSymbolMacroExpand: res = try eval_macroexpand(list, env)
default: res = TCOVal()
}
switch res {
// Standard list to be applied. Evaluate all the elements first.
- let eval = eval_ast(ast, env)
- if is_error(eval) { return eval }
+ let eval = try eval_ast(ast, env)
// The result had better be a list and better be non-empty.
- let eval_list = eval as! MalList
+ let eval_list = as_list(eval)
if eval_list.isEmpty {
- return eval_list
+ return eval
}
- if DEBUG_EVAL { println("\(indent)>> \(eval)") }
+ if DEBUG_EVAL { print("\(indent)>> \(eval)") }
// Get the first element of the list and execute it.
let first = eval_list.first()
- let rest = eval_list.rest()
+ let rest = as_sequence(eval_list.rest())
- if is_builtin(first) {
- let fn = first as! MalBuiltin
- let answer = fn.apply(rest)
- if DEBUG_EVAL { println("\(indent)>>> \(answer)") }
+ if let fn = as_builtinQ(first) {
+ let answer = try fn.apply(rest)
+ if DEBUG_EVAL { print("\(indent)>>> \(answer)") }
return answer
- } else if is_closure(first) {
- let fn = first as! MalClosure
- var new_env = Environment(outer: fn.env)
- let result = new_env.set_bindings(fn.args, with_exprs:rest)
- if is_error(result) { return result }
+ } else if let fn = as_closureQ(first) {
+ let new_env = Environment(outer: fn.env)
+ let _ = try new_env.set_bindings(fn.args, with_exprs: rest)
if TCO {
env = new_env
ast = fn.body
continue
}
- let answer = EVAL(fn.body, new_env)
- if DEBUG_EVAL { println("\(indent)>>> \(answer)") }
+ let answer = try EVAL(fn.body, new_env)
+ if DEBUG_EVAL { print("\(indent)>>> \(answer)") }
return answer
}
// The first element wasn't a function to be executed. Return an
// error saying so.
- return MalError(message: "first list item does not evaluate to a function: \(first)")
+ try throw_error("first list item does not evaluate to a function: \(first)")
}
}
// Convert the value into a human-readable string for printing.
//
-func PRINT(exp: MalVal) -> String? {
- if is_error(exp) { return nil }
+private func PRINT(exp: MalVal) -> String {
return pr_str(exp, true)
}
// Perform the READ and EVAL steps. Useful for when you don't care about the
// printable result.
//
-func RE(text: String, env: Environment) -> MalVal? {
- if text.isEmpty { return nil }
- let ast = READ(text)
- if is_error(ast) {
- println("Error parsing input: \(ast)")
- return nil
- }
- let exp = EVAL(ast, env)
- if is_error(exp) {
- println("Error evaluating input: \(exp)")
- return nil
+private func RE(text: String, _ env: Environment) -> MalVal? {
+ if !text.isEmpty {
+ do {
+ let ast = try READ(text)
+ do {
+ return try EVAL(ast, env)
+ } catch let error as MalException {
+ print("Error evaluating input: \(error)")
+ } catch {
+ print("Error evaluating input: \(error)")
+ }
+ } catch let error as MalException {
+ print("Error parsing input: \(error)")
+ } catch {
+ print("Error parsing input: \(error)")
+ }
}
- return exp
+ return nil
}
// Perform the full READ/EVAL/PRINT, returning a printable string.
//
-func REP(text: String, env: Environment) -> String? {
+private func REP(text: String, _ env: Environment) -> String? {
let exp = RE(text, env)
if exp == nil { return nil }
return PRINT(exp!)
// Perform the full REPL.
//
-func REPL(env: Environment) {
+private func REPL(env: Environment) {
while true {
if let text = _readline("user> ") {
if let output = REP(text, env) {
- println("\(output)")
+ print("\(output)")
}
} else {
- println()
+ print("")
break
}
}
// taken as a script to execute. If one exists, it is executed in lieu of
// running the REPL.
//
-func process_command_line(args:[String], env:Environment) -> Bool {
- var argv = MalList()
+private func process_command_line(args: [String], _ env: Environment) -> Bool {
+ var argv = make_list()
if args.count > 2 {
let args1 = args[2..<args.count]
- let args2 = args1.map { MalString(unescaped: $0) as MalVal }
+ let args2 = args1.map { make_string($0) }
let args3 = [MalVal](args2)
- argv = MalList(array: args3)
+ argv = make_list(args3)
}
env.set(kSymbolArgv, argv)
}
func main() {
- var env = Environment(outer: nil)
+ let env = Environment(outer: nil)
load_history_file()
load_builtins(env)
RE("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) " +
"`(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))", env)
- env.set(kSymbolEval, MalBuiltin(function: {
- unwrap($0) {
- (ast:MalVal) -> MalVal in
- EVAL(ast, env)
+ env.set(kSymbolEval, make_builtin({
+ try! unwrap_args($0) {
+ (ast: MalVal) -> MalVal in
+ try EVAL(ast, env)
}
}))
// The number of times EVAL has been entered recursively. We keep track of this
// so that we can protect against overrunning the stack.
//
-var EVAL_level = 0
+private var EVAL_level = 0
// The maximum number of times we let EVAL recurse before throwing an exception.
// Testing puts this at some place between 1800 and 1900. Let's keep it at 500
// for safety's sake.
//
-let EVAL_leval_max = 500
+private let EVAL_leval_max = 500
// Control whether or not tail-call optimization (TCO) is enabled. We want it
// `true` most of the time, but may disable it for debugging purposes (it's
// easier to get a meaningful backtrace that way).
//
-let TCO = true
+private let TCO = true
// Control whether or not we emit debugging statements in EVAL.
//
-let DEBUG_EVAL = false
+private let DEBUG_EVAL = false
// String used to prefix information logged in EVAL. Increasing lengths of the
// string are used the more EVAL is recursed.
//
-let INDENT_TEMPLATE = "|----|----|----|----|----|----|----|----|" +
+private let INDENT_TEMPLATE = "|----|----|----|----|----|----|----|----|" +
"----|----|----|----|----|----|----|----|----|----|----|" +
"----|----|----|----|----|----|----|----|----|----|----|" +
"----|----|----|----|----|----|----|----|----|----|----|" +
// Holds the prefix of INDENT_TEMPLATE used for actual logging.
//
-var indent = String()
+private var indent = String()
// Symbols used in this module.
//
-let kSymbolArgv = MalSymbol(symbol: "*ARGV*")
-let kSymbolCatch = MalSymbol(symbol: "catch*")
-let kSymbolConcat = MalSymbol(symbol: "concat")
-let kSymbolCons = MalSymbol(symbol: "cons")
-let kSymbolDef = MalSymbol(symbol: "def!")
-let kSymbolDefMacro = MalSymbol(symbol: "defmacro!")
-let kSymbolDo = MalSymbol(symbol: "do")
-let kSymbolEval = MalSymbol(symbol: "eval")
-let kSymbolFn = MalSymbol(symbol: "fn*")
-let kSymbolIf = MalSymbol(symbol: "if")
-let kSymbolLet = MalSymbol(symbol: "let*")
-let kSymbolMacroExpand = MalSymbol(symbol: "macroexpand")
-let kSymbolQuasiQuote = MalSymbol(symbol: "quasiquote")
-let kSymbolQuote = MalSymbol(symbol: "quote")
-let kSymbolSpliceUnquote = MalSymbol(symbol: "splice-unquote")
-let kSymbolUnquote = MalSymbol(symbol: "unquote")
-let kSymbolTry = MalSymbol(symbol: "try*")
-
-// Class to help control the incrementing and decrementing of EVAL_level. We
-// create one of these on entry to EVAL, incrementing the level. When the
-// variable goes out of scope, the object is destroyed, decrementing the level.
-//
-class EVAL_Counter {
- init() {
- ++EVAL_level
- }
- deinit {
- --EVAL_level
- }
+private let kValArgv = make_symbol("*ARGV*")
+private let kValCatch = make_symbol("catch*")
+private let kValConcat = make_symbol("concat")
+private let kValCons = make_symbol("cons")
+private let kValDef = make_symbol("def!")
+private let kValDefMacro = make_symbol("defmacro!")
+private let kValDo = make_symbol("do")
+private let kValEval = make_symbol("eval")
+private let kValFn = make_symbol("fn*")
+private let kValIf = make_symbol("if")
+private let kValLet = make_symbol("let*")
+private let kValMacroExpand = make_symbol("macroexpand")
+private let kValQuasiQuote = make_symbol("quasiquote")
+private let kValQuote = make_symbol("quote")
+private let kValSpliceUnquote = make_symbol("splice-unquote")
+private let kValUnquote = make_symbol("unquote")
+private let kValTry = make_symbol("try*")
+
+private let kSymbolArgv = as_symbol(kValArgv)
+private let kSymbolCatch = as_symbol(kValCatch)
+private let kSymbolConcat = as_symbol(kValConcat)
+private let kSymbolCons = as_symbol(kValCons)
+private let kSymbolDef = as_symbol(kValDef)
+private let kSymbolDefMacro = as_symbol(kValDefMacro)
+private let kSymbolDo = as_symbol(kValDo)
+private let kSymbolEval = as_symbol(kValEval)
+private let kSymbolFn = as_symbol(kValFn)
+private let kSymbolIf = as_symbol(kValIf)
+private let kSymbolLet = as_symbol(kValLet)
+private let kSymbolMacroExpand = as_symbol(kValMacroExpand)
+private let kSymbolQuasiQuote = as_symbol(kValQuasiQuote)
+private let kSymbolQuote = as_symbol(kValQuote)
+private let kSymbolSpliceUnquote = as_symbol(kValSpliceUnquote)
+private let kSymbolUnquote = as_symbol(kValUnquote)
+private let kSymbolTry = as_symbol(kValTry)
+
+func substring(s: String, _ begin: Int, _ end: Int) -> String {
+ return s[s.startIndex.advancedBy(begin) ..< s.startIndex.advancedBy(end)]
}
// Parse the string into an AST.
//
-func READ(str: String) -> MalVal {
- return read_str(str)
+private func READ(str: String) throws -> MalVal {
+ return try read_str(str)
}
// Return whether or not `val` is a non-empty list.
//
-func is_pair(val:MalVal) -> Bool {
- if !is_sequence(val) { return false }
- let list = val as! MalSequence
- return !list.isEmpty
+private func is_pair(val: MalVal) -> Bool {
+ if let seq = as_sequenceQ(val) {
+ return !seq.isEmpty
+ }
+ return false
}
// Expand macros for as long as the expression looks like a macro invocation.
//
-func macroexpand(var ast:MalVal, env:Environment) -> MalVal {
+private func macroexpand(var ast: MalVal, _ env: Environment) throws -> MalVal {
while true {
- if !is_list(ast) { break }
- let ast_as_list = ast as! MalList
- if ast_as_list.isEmpty { break }
- let first = ast_as_list.first()
- if !is_symbol(first) { break }
- let macro_name = first as! MalSymbol
- let obj = env.get(macro_name)
- if obj == nil { break }
- if !is_closure(obj!) { break }
- let macro = obj! as! MalClosure
- if !macro.is_macro { break }
- var new_env = Environment(outer: macro.env)
- let rest = ast_as_list.rest()
- let res = new_env.set_bindings(macro.args, with_exprs:rest)
- if is_error(res) { return res }
- ast = EVAL(macro.body, new_env)
+ if let ast_as_list = as_listQ(ast) where !ast_as_list.isEmpty,
+ let macro_name = as_symbolQ(ast_as_list.first()),
+ let obj = env.get(macro_name),
+ let macro = as_macroQ(obj)
+ {
+ let new_env = Environment(outer: macro.env)
+ let rest = as_sequence(ast_as_list.rest())
+ let _ = try new_env.set_bindings(macro.args, with_exprs: rest)
+ ast = try EVAL(macro.body, new_env)
+ continue
+ }
+ return ast
}
- return ast
}
// Evaluate `quasiquote`, possibly recursing in the process.
// list) with the remaining items of the list containing that splice-quote
// expression. However, it's not clear to me why the handling of "unquote" is
// not handled similarly, for consistency's sake.
-
-func quasiquote(qq_arg:MalVal) -> MalVal {
+//
+private func quasiquote(qq_arg: MalVal) throws -> MalVal {
// If the argument is an atom or empty list:
//
// Return: (quote <argument>)
if !is_pair(qq_arg) {
- return MalList(objects: kSymbolQuote, qq_arg)
+ return make_list_from(kValQuote, qq_arg)
}
// The argument is a non-empty list -- that is (item rest...)
//
// Return: item
- let qq_list = qq_arg as! MalSequence
- if is_symbol(qq_list.first()) {
- let sym = qq_list.first() as! MalSymbol
- if sym == kSymbolUnquote {
- return qq_list.count >= 2 ? qq_list[1] : MalNil()
- }
+ let qq_list = as_sequence(qq_arg)
+ if let sym = as_symbolQ(qq_list.first()) where sym == kSymbolUnquote {
+ return qq_list.count >= 2 ? try! qq_list.nth(1) : make_nil()
}
// If the first item from the list is itself a non-empty list starting with
// Return: (concat item quasiquote(rest...))
if is_pair(qq_list.first()) {
- let qq_list_item0 = qq_list.first() as! MalSequence
- if is_symbol(qq_list_item0.first()) {
- let sym = qq_list_item0.first() as! MalSymbol
- if sym == kSymbolSpliceUnquote {
- let result = quasiquote(qq_list.rest())
- if is_error(result) { return result }
- return MalList(array: [kSymbolConcat, qq_list_item0[1], result])
- }
+ let qq_list_item0 = as_sequence(qq_list.first())
+ if let sym = as_symbolQ(qq_list_item0.first()) where sym == kSymbolSpliceUnquote {
+ let result = try quasiquote(qq_list.rest())
+ return make_list_from(kValConcat, try! qq_list_item0.nth(1), result)
}
}
//
// Return: (cons (quasiquote item) (quasiquote (rest...))
- let first = quasiquote(qq_list.first())
- if is_error(first) { return first }
-
- let rest = quasiquote(qq_list.rest())
- if is_error(rest) { return rest }
-
- return MalList(objects: kSymbolCons, first, rest)
+ let first = try quasiquote(qq_list.first())
+ let rest = try quasiquote(qq_list.rest())
+ return make_list_from(kValCons, first, rest)
}
// Perform a simple evaluation of the `ast` object. If it's a symbol,
// elements (or just the values, in the case of the hashmap). Otherwise, return
// the object unchanged.
//
-func eval_ast(ast: MalVal, env: Environment) -> MalVal {
- switch ast.type {
- case .TypeSymbol:
- let symbol = ast as! MalSymbol
- if let val = env.get(symbol) {
- return val
- }
- return MalError(message: "'\(symbol)' not found") // Specific text needed to match MAL unit tests
- case .TypeList:
- let list = ast as! MalList
- var result = [MalVal]()
- result.reserveCapacity(list.count)
- for item in list {
- let eval = EVAL(item, env)
- if is_error(eval) { return eval }
- result.append(eval)
- }
- return MalList(array: result)
- case .TypeVector:
- let vec = ast as! MalVector
- var result = [MalVal]()
- result.reserveCapacity(vec.count)
- for item in vec {
- let eval = EVAL(item, env)
- if is_error(eval) { return eval }
- result.append(eval)
- }
- return MalVector(array: result)
- case .TypeHashMap:
- let hash = ast as! MalHashMap
- var result = [MalVal]()
- result.reserveCapacity(hash.count * 2)
- for (k, v) in hash {
- let new_v = EVAL(v, env)
- if is_error(new_v) { return new_v }
- result.append(k)
- result.append(new_v)
- }
- return MalHashMap(array: result)
- default:
- return ast
+private func eval_ast(ast: MalVal, _ env: Environment) throws -> MalVal {
+ if let symbol = as_symbolQ(ast) {
+ guard let val = env.get(symbol) else {
+ try throw_error("'\(symbol)' not found") // Specific text needed to match MAL unit tests
+ }
+ return val
+ }
+ if let list = as_listQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(list.count))
+ for item in list {
+ let eval = try EVAL(item, env)
+ result.append(eval)
+ }
+ return make_list(result)
+ }
+ if let vec = as_vectorQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(vec.count))
+ for item in vec {
+ let eval = try EVAL(item, env)
+ result.append(eval)
+ }
+ return make_vector(result)
+ }
+ if let hash = as_hashmapQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(hash.count) * 2)
+ for (k, v) in hash {
+ let new_v = try EVAL(v, env)
+ result.append(k)
+ result.append(new_v)
+ }
+ return make_hashmap(result)
}
+ return ast
}
-enum TCOVal {
+private enum TCOVal {
case NoResult
case Return(MalVal)
case Continue(MalVal, Environment)
init() { self = .NoResult }
init(_ result: MalVal) { self = .Return(result) }
init(_ ast: MalVal, _ env: Environment) { self = .Continue(ast, env) }
- init(_ e: String) { self = .Return(MalError(message: e)) }
}
// EVALuate "def!" and "defmacro!".
//
-func eval_def(list: MalSequence, env: Environment) -> TCOVal {
- if list.count != 3 {
- return TCOVal("expected 2 arguments to def!, got \(list.count - 1)")
- }
- let arg0 = list[0] as! MalSymbol
- let arg1 = list[1]
- let arg2 = list[2]
- if !is_symbol(arg1) {
- return TCOVal("expected symbol for first argument to def!")
- }
- let sym = arg1 as! MalSymbol
- let value = EVAL(arg2, env)
- if is_error(value) { return TCOVal(value) }
- if arg0 == kSymbolDefMacro {
- if is_closure(value) {
- let as_closure = value as! MalClosure
- as_closure.is_macro = true
- } else {
- return TCOVal("expected closure, got \(value)")
+private func eval_def(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count == 3 else {
+ try throw_error("expected 2 arguments to def!, got \(list.count - 1)")
+ }
+ let arg0 = try! list.nth(0)
+ let arg1 = try! list.nth(1)
+ let arg2 = try! list.nth(2)
+ guard let sym = as_symbolQ(arg1) else {
+ try throw_error("expected symbol for first argument to def!")
+ }
+ var value = try EVAL(arg2, env)
+ if as_symbol(arg0) == kSymbolDefMacro {
+ guard let closure = as_closureQ(value) else {
+ try throw_error("expected closure, got \(value)")
}
+ value = make_macro(closure)
}
return TCOVal(env.set(sym, value))
}
// EVALuate "let*".
//
-func eval_let(list: MalSequence, env: Environment) -> TCOVal {
- if list.count != 3 {
- return TCOVal("expected 2 arguments to let*, got \(list.count - 1)")
+private func eval_let(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count == 3 else {
+ try throw_error("expected 2 arguments to let*, got \(list.count - 1)")
}
- let arg1 = list[1]
- let arg2 = list[2]
- if !is_sequence(arg1) {
- return TCOVal("expected list for first argument to let*")
+ let arg1 = try! list.nth(1)
+ let arg2 = try! list.nth(2)
+ guard let bindings = as_sequenceQ(arg1) else {
+ try throw_error("expected list for first argument to let*")
}
- let bindings = arg1 as! MalSequence
- if bindings.count % 2 == 1 {
- return TCOVal("expected even number of elements in bindings to let*, got \(bindings.count)")
+ guard bindings.count % 2 == 0 else {
+ try throw_error("expected even number of elements in bindings to let*, got \(bindings.count)")
}
- var new_env = Environment(outer: env)
- for var index = 0; index < bindings.count; index += 2 {
- let binding_name = bindings[index]
- let binding_value = bindings[index + 1]
-
- if !is_symbol(binding_name) {
- return TCOVal("expected symbol for first element in binding pair")
+ let new_env = Environment(outer: env)
+ for var index: MalIntType = 0; index < bindings.count; index += 2 {
+ let binding_name = try! bindings.nth(index)
+ let binding_value = try! bindings.nth(index + 1)
+ guard let binding_symbol = as_symbolQ(binding_name) else {
+ try throw_error("expected symbol for first element in binding pair")
}
- let binding_symbol = binding_name as! MalSymbol
- let evaluated_value = EVAL(binding_value, new_env)
- if is_error(evaluated_value) { return TCOVal(evaluated_value) }
+ let evaluated_value = try EVAL(binding_value, new_env)
new_env.set(binding_symbol, evaluated_value)
}
if TCO {
return TCOVal(arg2, new_env)
}
- return TCOVal(EVAL(arg2, new_env))
+ return TCOVal(try EVAL(arg2, new_env))
}
// EVALuate "do".
//
-func eval_do(list: MalSequence, env: Environment) -> TCOVal {
+private func eval_do(list: MalSequence, _ env: Environment) throws -> TCOVal {
if TCO {
- let eval = eval_ast(MalList(slice: list[1..<list.count-1]), env)
- if is_error(eval) { return TCOVal(eval) }
+ let _ = try eval_ast(list.range_from(1, to: list.count-1), env)
return TCOVal(list.last(), env)
}
- let evaluated_ast = eval_ast(list.rest(), env)
- if is_error(evaluated_ast) { return TCOVal(evaluated_ast) }
- let evaluated_seq = evaluated_ast as! MalSequence
+ let evaluated_ast = try eval_ast(list.rest(), env)
+ let evaluated_seq = as_sequence(evaluated_ast)
return TCOVal(evaluated_seq.last())
}
// EVALuate "if".
//
-func eval_if(list: MalSequence, env: Environment) -> TCOVal {
- if list.count < 3 {
- return TCOVal("expected at least 2 arguments to if, got \(list.count - 1)")
+private func eval_if(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count >= 3 else {
+ try throw_error("expected at least 2 arguments to if, got \(list.count - 1)")
}
- let cond_result = EVAL(list[1], env)
- var new_ast = MalVal()
+ let cond_result = try EVAL(try! list.nth(1), env)
+ var new_ast: MalVal
if is_truthy(cond_result) {
- new_ast = list[2]
+ new_ast = try! list.nth(2)
} else if list.count == 4 {
- new_ast = list[3]
+ new_ast = try! list.nth(3)
} else {
- return TCOVal(MalNil())
+ return TCOVal(make_nil())
}
if TCO {
return TCOVal(new_ast, env)
}
- return TCOVal(EVAL(new_ast, env))
+ return TCOVal(try EVAL(new_ast, env))
}
// EVALuate "fn*".
//
-func eval_fn(list: MalSequence, env: Environment) -> TCOVal {
- if list.count != 3 {
- return TCOVal("expected 2 arguments to fn*, got \(list.count - 1)")
+private func eval_fn(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count == 3 else {
+ try throw_error("expected 2 arguments to fn*, got \(list.count - 1)")
}
- if !is_sequence(list[1]) {
- return TCOVal("expected list or vector for first argument to fn*")
+ guard let seq = as_sequenceQ(try! list.nth(1)) else {
+ try throw_error("expected list or vector for first argument to fn*")
}
- return TCOVal(MalClosure(eval: EVAL, args:list[1] as! MalSequence, body:list[2], env:env))
+ return TCOVal(make_closure((eval: EVAL, args: seq, body: try! list.nth(2), env: env)))
}
// EVALuate "quote".
//
-func eval_quote(list: MalSequence, env: Environment) -> TCOVal {
+private func eval_quote(list: MalSequence, _ env: Environment) throws -> TCOVal {
if list.count >= 2 {
- return TCOVal(list[1])
+ return TCOVal(try! list.nth(1))
}
- return TCOVal(MalNil())
+ return TCOVal(make_nil())
}
// EVALuate "quasiquote".
//
-func eval_quasiquote(list: MalSequence, env: Environment) -> TCOVal {
- if list.count >= 2 {
- if TCO {
- return TCOVal(quasiquote(list[1]), env)
- }
- return TCOVal(EVAL(quasiquote(list[1]), env))
+private func eval_quasiquote(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count >= 2 else {
+ try throw_error("Expected non-nil parameter to 'quasiquote'")
+ }
+ if TCO {
+ return TCOVal(try quasiquote(try! list.nth(1)), env)
}
- return TCOVal("Expected non-nil parameter to 'quasiquote'")
+ return TCOVal(try EVAL(try quasiquote(try! list.nth(1)), env))
}
// EVALuate "macroexpand".
//
-func eval_macroexpand(list: MalSequence, env: Environment) -> TCOVal {
- if list.count >= 2 {
- return TCOVal(macroexpand(list[1], env))
+private func eval_macroexpand(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count >= 2 else {
+ try throw_error("Expected parameter to 'macroexpand'")
}
- return TCOVal("Expected parameter to 'macroexpand'")
+ return TCOVal(try macroexpand(try! list.nth(1), env))
}
// EVALuate "try*" (and "catch*").
//
-func eval_try(list: MalSequence, env: Environment) -> TCOVal {
+private func eval_try(list: MalSequence, _ env: Environment) throws -> TCOVal {
// This is a subset of the Clojure try/catch:
//
// (try* expr (catch exception-name expr))
- if list.count < 2 {
- println("try*: no body parameter")
- return TCOVal(MalNil()) // No body parameter
- }
- let res = EVAL(list[1], env)
- if !is_error(res) { return TCOVal(res) }
- if list.count < 3 {
- println("try*: no catch parameter")
- return TCOVal(MalNil()) // No catch parameter
- }
- if !is_sequence(list[2]) {
- println("try*: second parameter to 'try' is not a sequence")
- return TCOVal(MalNil()) // Second parameter to 'try' is not a sequence
- }
- let catch_list = list[2] as! MalSequence
- if catch_list.count < 3 {
- println("try*: not enough catch parameters")
- return TCOVal(MalNil()) // Not enough catch parameters
- }
- if !is_symbol(catch_list[0]) {
- println("try*: first parameter in catch list is not a symbol")
- return TCOVal(MalNil()) // First parameter in catch list is not a symbol
- }
- let catch_symbol = catch_list[0] as! MalSymbol
- if catch_symbol != kSymbolCatch {
- println("try*: first parameter in catch list is not 'catch'")
- return TCOVal(MalNil()) // First parameter in catch list is not 'catch'
- }
- if !is_symbol(catch_list[1]) {
- println("try*: first parameter to 'catch' is not a symbol")
- return TCOVal(MalNil()) // First parameter to 'catch' is not a symbol
- }
- let catch_name = catch_list[1] as! MalSymbol
- let catch_expr = catch_list[2]
- let catch_env = Environment(outer: env)
- let error = res as! MalError
- catch_env.set_bindings(MalList(objects: catch_name), with_exprs: MalList(objects: error.value))
- return TCOVal(EVAL(catch_expr, catch_env))
+ guard list.count >= 2 else {
+ try throw_error("try*: no body parameter")
+ }
+
+ do {
+ return TCOVal(try EVAL(try! list.nth(1), env))
+ } catch let error as MalException {
+ guard list.count >= 3,
+ let catch_list = as_sequenceQ(try! list.nth(2)) where catch_list.count >= 3,
+ let _ = as_symbolQ(try! catch_list.nth(0)) else
+ {
+ throw error // No catch parameter
+ }
+ let catch_name = try! catch_list.nth(1)
+ let catch_expr = try! catch_list.nth(2)
+ let catch_env = Environment(outer: env)
+ try catch_env.set_bindings(as_sequence(make_list_from(catch_name)),
+ with_exprs: as_sequence(make_list_from(error.exception)))
+ return TCOVal(try EVAL(catch_expr, catch_env))
+ }
}
// Walk the AST and completely evaluate it, handling macro expansions, special
// forms and function calls.
//
-func EVAL(var ast: MalVal, var env: Environment) -> MalVal {
- let x = EVAL_Counter()
- if EVAL_level > EVAL_leval_max {
- return MalError(message: "Recursing too many levels (> \(EVAL_leval_max))")
+private func EVAL(var ast: MalVal, var _ env: Environment) throws -> MalVal {
+ EVAL_level++
+ defer { EVAL_level-- }
+ guard EVAL_level <= EVAL_leval_max else {
+ try throw_error("Recursing too many levels (> \(EVAL_leval_max))")
}
if DEBUG_EVAL {
- indent = prefix(INDENT_TEMPLATE, EVAL_level)
+ indent = substring(INDENT_TEMPLATE, 0, EVAL_level)
}
while true {
- if is_error(ast) { return ast }
- if DEBUG_EVAL { println("\(indent)> \(ast)") }
+ if DEBUG_EVAL { print("\(indent)> \(ast)") }
if !is_list(ast) {
// Not a list -- just evaluate and return.
- let answer = eval_ast(ast, env)
- if DEBUG_EVAL { println("\(indent)>>> \(answer)") }
+ let answer = try eval_ast(ast, env)
+ if DEBUG_EVAL { print("\(indent)>>> \(answer)") }
return answer
}
// Special handling if it's a list.
- var list = ast as! MalList
- ast = macroexpand(ast, env)
+ var list = as_list(ast)
+ ast = try macroexpand(ast, env)
if !is_list(ast) { return ast }
- list = ast as! MalList
+ list = as_list(ast)
- if DEBUG_EVAL { println("\(indent)>. \(list)") }
+ if DEBUG_EVAL { print("\(indent)>. \(list)") }
if list.isEmpty {
- return list
+ return ast
}
// Check for special forms, where we want to check the operation
// before evaluating all of the parameters.
let arg0 = list.first()
- if is_symbol(arg0) {
- var res: TCOVal
- let fn_symbol = arg0 as! MalSymbol
+ if let fn_symbol = as_symbolQ(arg0) {
+ let res: TCOVal
switch fn_symbol {
- case kSymbolDef: res = eval_def(list, env)
- case kSymbolDefMacro: res = eval_def(list, env)
- case kSymbolLet: res = eval_let(list, env)
- case kSymbolDo: res = eval_do(list, env)
- case kSymbolIf: res = eval_if(list, env)
- case kSymbolFn: res = eval_fn(list, env)
- case kSymbolQuote: res = eval_quote(list, env)
- case kSymbolQuasiQuote: res = eval_quasiquote(list, env)
- case kSymbolMacroExpand: res = eval_macroexpand(list, env)
- case kSymbolTry: res = eval_try(list, env)
+ case kSymbolDef: res = try eval_def(list, env)
+ case kSymbolDefMacro: res = try eval_def(list, env)
+ case kSymbolLet: res = try eval_let(list, env)
+ case kSymbolDo: res = try eval_do(list, env)
+ case kSymbolIf: res = try eval_if(list, env)
+ case kSymbolFn: res = try eval_fn(list, env)
+ case kSymbolQuote: res = try eval_quote(list, env)
+ case kSymbolQuasiQuote: res = try eval_quasiquote(list, env)
+ case kSymbolMacroExpand: res = try eval_macroexpand(list, env)
+ case kSymbolTry: res = try eval_try(list, env)
default: res = TCOVal()
}
switch res {
// Standard list to be applied. Evaluate all the elements first.
- let eval = eval_ast(ast, env)
- if is_error(eval) { return eval }
+ let eval = try eval_ast(ast, env)
// The result had better be a list and better be non-empty.
- let eval_list = eval as! MalList
+ let eval_list = as_list(eval)
if eval_list.isEmpty {
- return eval_list
+ return eval
}
- if DEBUG_EVAL { println("\(indent)>> \(eval)") }
+ if DEBUG_EVAL { print("\(indent)>> \(eval)") }
// Get the first element of the list and execute it.
let first = eval_list.first()
- let rest = eval_list.rest()
+ let rest = as_sequence(eval_list.rest())
- if is_builtin(first) {
- let fn = first as! MalBuiltin
- let answer = fn.apply(rest)
- if DEBUG_EVAL { println("\(indent)>>> \(answer)") }
+ if let fn = as_builtinQ(first) {
+ let answer = try fn.apply(rest)
+ if DEBUG_EVAL { print("\(indent)>>> \(answer)") }
return answer
- } else if is_closure(first) {
- let fn = first as! MalClosure
- var new_env = Environment(outer: fn.env)
- let result = new_env.set_bindings(fn.args, with_exprs:rest)
- if is_error(result) { return result }
+ } else if let fn = as_closureQ(first) {
+ let new_env = Environment(outer: fn.env)
+ let _ = try new_env.set_bindings(fn.args, with_exprs: rest)
if TCO {
env = new_env
ast = fn.body
continue
}
- let answer = EVAL(fn.body, new_env)
- if DEBUG_EVAL { println("\(indent)>>> \(answer)") }
+ let answer = try EVAL(fn.body, new_env)
+ if DEBUG_EVAL { print("\(indent)>>> \(answer)") }
return answer
}
// The first element wasn't a function to be executed. Return an
// error saying so.
- return MalError(message: "first list item does not evaluate to a function: \(first)")
+ try throw_error("first list item does not evaluate to a function: \(first)")
}
}
// Convert the value into a human-readable string for printing.
//
-func PRINT(exp: MalVal) -> String? {
- if is_error(exp) { return nil }
+private func PRINT(exp: MalVal) -> String {
return pr_str(exp, true)
}
// Perform the READ and EVAL steps. Useful for when you don't care about the
// printable result.
//
-func RE(text: String, env: Environment) -> MalVal? {
- if text.isEmpty { return nil }
- let ast = READ(text)
- if is_error(ast) {
- println("Error parsing input: \(ast)")
- return nil
- }
- let exp = EVAL(ast, env)
- if is_error(exp) {
- println("Error evaluating input: \(exp)")
- return nil
+private func RE(text: String, _ env: Environment) -> MalVal? {
+ if !text.isEmpty {
+ do {
+ let ast = try READ(text)
+ do {
+ return try EVAL(ast, env)
+ } catch let error as MalException {
+ print("Error evaluating input: \(error)")
+ } catch {
+ print("Error evaluating input: \(error)")
+ }
+ } catch let error as MalException {
+ print("Error parsing input: \(error)")
+ } catch {
+ print("Error parsing input: \(error)")
+ }
}
- return exp
+ return nil
}
// Perform the full READ/EVAL/PRINT, returning a printable string.
//
-func REP(text: String, env: Environment) -> String? {
+private func REP(text: String, _ env: Environment) -> String? {
let exp = RE(text, env)
if exp == nil { return nil }
return PRINT(exp!)
// Perform the full REPL.
//
-func REPL(env: Environment) {
+private func REPL(env: Environment) {
while true {
if let text = _readline("user> ") {
if let output = REP(text, env) {
- println("\(output)")
+ print("\(output)")
}
} else {
- println()
+ print("")
break
}
}
// taken as a script to execute. If one exists, it is executed in lieu of
// running the REPL.
//
-func process_command_line(args:[String], env:Environment) -> Bool {
- var argv = MalList()
+private func process_command_line(args: [String], _ env: Environment) -> Bool {
+ var argv = make_list()
if args.count > 2 {
let args1 = args[2..<args.count]
- let args2 = args1.map { MalString(unescaped: $0) as MalVal }
+ let args2 = args1.map { make_string($0) }
let args3 = [MalVal](args2)
- argv = MalList(array: args3)
+ argv = make_list(args3)
}
env.set(kSymbolArgv, argv)
}
func main() {
- var env = Environment(outer: nil)
+ let env = Environment(outer: nil)
load_history_file()
load_builtins(env)
RE("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) " +
"`(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))", env)
- env.set(kSymbolEval, MalBuiltin(function: {
- unwrap($0) {
- (ast:MalVal) -> MalVal in
- EVAL(ast, env)
+ env.set(kSymbolEval, make_builtin({
+ try! unwrap_args($0) {
+ (ast: MalVal) -> MalVal in
+ try EVAL(ast, env)
}
}))
// The number of times EVAL has been entered recursively. We keep track of this
// so that we can protect against overrunning the stack.
//
-var EVAL_level = 0
+private var EVAL_level = 0
// The maximum number of times we let EVAL recurse before throwing an exception.
// Testing puts this at some place between 1800 and 1900. Let's keep it at 500
// for safety's sake.
//
-let EVAL_leval_max = 500
+private let EVAL_leval_max = 500
// Control whether or not tail-call optimization (TCO) is enabled. We want it
// `true` most of the time, but may disable it for debugging purposes (it's
// easier to get a meaningful backtrace that way).
//
-let TCO = true
+private let TCO = true
// Control whether or not we emit debugging statements in EVAL.
//
-let DEBUG_EVAL = false
+private let DEBUG_EVAL = false
// String used to prefix information logged in EVAL. Increasing lengths of the
// string are used the more EVAL is recursed.
//
-let INDENT_TEMPLATE = "|----|----|----|----|----|----|----|----|" +
+private let INDENT_TEMPLATE = "|----|----|----|----|----|----|----|----|" +
"----|----|----|----|----|----|----|----|----|----|----|" +
"----|----|----|----|----|----|----|----|----|----|----|" +
"----|----|----|----|----|----|----|----|----|----|----|" +
// Holds the prefix of INDENT_TEMPLATE used for actual logging.
//
-var indent = String()
+private var indent = String()
// Symbols used in this module.
//
-let kSymbolArgv = MalSymbol(symbol: "*ARGV*")
-let kSymbolCatch = MalSymbol(symbol: "catch*")
-let kSymbolConcat = MalSymbol(symbol: "concat")
-let kSymbolCons = MalSymbol(symbol: "cons")
-let kSymbolDef = MalSymbol(symbol: "def!")
-let kSymbolDefMacro = MalSymbol(symbol: "defmacro!")
-let kSymbolDo = MalSymbol(symbol: "do")
-let kSymbolEval = MalSymbol(symbol: "eval")
-let kSymbolFn = MalSymbol(symbol: "fn*")
-let kSymbolIf = MalSymbol(symbol: "if")
-let kSymbolLet = MalSymbol(symbol: "let*")
-let kSymbolMacroExpand = MalSymbol(symbol: "macroexpand")
-let kSymbolQuasiQuote = MalSymbol(symbol: "quasiquote")
-let kSymbolQuote = MalSymbol(symbol: "quote")
-let kSymbolSpliceUnquote = MalSymbol(symbol: "splice-unquote")
-let kSymbolUnquote = MalSymbol(symbol: "unquote")
-let kSymbolTry = MalSymbol(symbol: "try*")
-
-// Class to help control the incrementing and decrementing of EVAL_level. We
-// create one of these on entry to EVAL, incrementing the level. When the
-// variable goes out of scope, the object is destroyed, decrementing the level.
-//
-class EVAL_Counter {
- init() {
- ++EVAL_level
- }
- deinit {
- --EVAL_level
- }
+private let kValArgv = make_symbol("*ARGV*")
+private let kValCatch = make_symbol("catch*")
+private let kValConcat = make_symbol("concat")
+private let kValCons = make_symbol("cons")
+private let kValDef = make_symbol("def!")
+private let kValDefMacro = make_symbol("defmacro!")
+private let kValDo = make_symbol("do")
+private let kValEval = make_symbol("eval")
+private let kValFn = make_symbol("fn*")
+private let kValIf = make_symbol("if")
+private let kValLet = make_symbol("let*")
+private let kValMacroExpand = make_symbol("macroexpand")
+private let kValQuasiQuote = make_symbol("quasiquote")
+private let kValQuote = make_symbol("quote")
+private let kValSpliceUnquote = make_symbol("splice-unquote")
+private let kValUnquote = make_symbol("unquote")
+private let kValTry = make_symbol("try*")
+
+private let kSymbolArgv = as_symbol(kValArgv)
+private let kSymbolCatch = as_symbol(kValCatch)
+private let kSymbolConcat = as_symbol(kValConcat)
+private let kSymbolCons = as_symbol(kValCons)
+private let kSymbolDef = as_symbol(kValDef)
+private let kSymbolDefMacro = as_symbol(kValDefMacro)
+private let kSymbolDo = as_symbol(kValDo)
+private let kSymbolEval = as_symbol(kValEval)
+private let kSymbolFn = as_symbol(kValFn)
+private let kSymbolIf = as_symbol(kValIf)
+private let kSymbolLet = as_symbol(kValLet)
+private let kSymbolMacroExpand = as_symbol(kValMacroExpand)
+private let kSymbolQuasiQuote = as_symbol(kValQuasiQuote)
+private let kSymbolQuote = as_symbol(kValQuote)
+private let kSymbolSpliceUnquote = as_symbol(kValSpliceUnquote)
+private let kSymbolUnquote = as_symbol(kValUnquote)
+private let kSymbolTry = as_symbol(kValTry)
+
+func substring(s: String, _ begin: Int, _ end: Int) -> String {
+ return s[s.startIndex.advancedBy(begin) ..< s.startIndex.advancedBy(end)]
}
// Parse the string into an AST.
//
-func READ(str: String) -> MalVal {
- return read_str(str)
+private func READ(str: String) throws -> MalVal {
+ return try read_str(str)
}
// Return whether or not `val` is a non-empty list.
//
-func is_pair(val:MalVal) -> Bool {
- if !is_sequence(val) { return false }
- let list = val as! MalSequence
- return !list.isEmpty
+private func is_pair(val: MalVal) -> Bool {
+ if let seq = as_sequenceQ(val) {
+ return !seq.isEmpty
+ }
+ return false
}
// Expand macros for as long as the expression looks like a macro invocation.
//
-func macroexpand(var ast:MalVal, env:Environment) -> MalVal {
+private func macroexpand(var ast: MalVal, _ env: Environment) throws -> MalVal {
while true {
- if !is_list(ast) { break }
- let ast_as_list = ast as! MalList
- if ast_as_list.isEmpty { break }
- let first = ast_as_list.first()
- if !is_symbol(first) { break }
- let macro_name = first as! MalSymbol
- let obj = env.get(macro_name)
- if obj == nil { break }
- if !is_closure(obj!) { break }
- let macro = obj! as! MalClosure
- if !macro.is_macro { break }
- var new_env = Environment(outer: macro.env)
- let rest = ast_as_list.rest()
- let res = new_env.set_bindings(macro.args, with_exprs:rest)
- if is_error(res) { return res }
- ast = EVAL(macro.body, new_env)
+ if let ast_as_list = as_listQ(ast) where !ast_as_list.isEmpty,
+ let macro_name = as_symbolQ(ast_as_list.first()),
+ let obj = env.get(macro_name),
+ let macro = as_macroQ(obj)
+ {
+ let new_env = Environment(outer: macro.env)
+ let rest = as_sequence(ast_as_list.rest())
+ let _ = try new_env.set_bindings(macro.args, with_exprs: rest)
+ ast = try EVAL(macro.body, new_env)
+ continue
+ }
+ return ast
}
- return ast
}
// Evaluate `quasiquote`, possibly recursing in the process.
// list) with the remaining items of the list containing that splice-quote
// expression. However, it's not clear to me why the handling of "unquote" is
// not handled similarly, for consistency's sake.
-
-func quasiquote(qq_arg:MalVal) -> MalVal {
+//
+private func quasiquote(qq_arg: MalVal) throws -> MalVal {
// If the argument is an atom or empty list:
//
// Return: (quote <argument>)
if !is_pair(qq_arg) {
- return MalList(objects: kSymbolQuote, qq_arg)
+ return make_list_from(kValQuote, qq_arg)
}
// The argument is a non-empty list -- that is (item rest...)
//
// Return: item
- let qq_list = qq_arg as! MalSequence
- if is_symbol(qq_list.first()) {
- let sym = qq_list.first() as! MalSymbol
- if sym == kSymbolUnquote {
- return qq_list.count >= 2 ? qq_list[1] : MalNil()
- }
+ let qq_list = as_sequence(qq_arg)
+ if let sym = as_symbolQ(qq_list.first()) where sym == kSymbolUnquote {
+ return qq_list.count >= 2 ? try! qq_list.nth(1) : make_nil()
}
// If the first item from the list is itself a non-empty list starting with
// Return: (concat item quasiquote(rest...))
if is_pair(qq_list.first()) {
- let qq_list_item0 = qq_list.first() as! MalSequence
- if is_symbol(qq_list_item0.first()) {
- let sym = qq_list_item0.first() as! MalSymbol
- if sym == kSymbolSpliceUnquote {
- let result = quasiquote(qq_list.rest())
- if is_error(result) { return result }
- return MalList(array: [kSymbolConcat, qq_list_item0[1], result])
- }
+ let qq_list_item0 = as_sequence(qq_list.first())
+ if let sym = as_symbolQ(qq_list_item0.first()) where sym == kSymbolSpliceUnquote {
+ let result = try quasiquote(qq_list.rest())
+ return make_list_from(kValConcat, try! qq_list_item0.nth(1), result)
}
}
//
// Return: (cons (quasiquote item) (quasiquote (rest...))
- let first = quasiquote(qq_list.first())
- if is_error(first) { return first }
-
- let rest = quasiquote(qq_list.rest())
- if is_error(rest) { return rest }
-
- return MalList(objects: kSymbolCons, first, rest)
+ let first = try quasiquote(qq_list.first())
+ let rest = try quasiquote(qq_list.rest())
+ return make_list_from(kValCons, first, rest)
}
// Perform a simple evaluation of the `ast` object. If it's a symbol,
// elements (or just the values, in the case of the hashmap). Otherwise, return
// the object unchanged.
//
-func eval_ast(ast: MalVal, env: Environment) -> MalVal {
- switch ast.type {
- case .TypeSymbol:
- let symbol = ast as! MalSymbol
- if let val = env.get(symbol) {
- return val
- }
- return MalError(message: "'\(symbol)' not found") // Specific text needed to match MAL unit tests
- case .TypeList:
- let list = ast as! MalList
- var result = [MalVal]()
- result.reserveCapacity(list.count)
- for item in list {
- let eval = EVAL(item, env)
- if is_error(eval) { return eval }
- result.append(eval)
- }
- return MalList(array: result)
- case .TypeVector:
- let vec = ast as! MalVector
- var result = [MalVal]()
- result.reserveCapacity(vec.count)
- for item in vec {
- let eval = EVAL(item, env)
- if is_error(eval) { return eval }
- result.append(eval)
- }
- return MalVector(array: result)
- case .TypeHashMap:
- let hash = ast as! MalHashMap
- var result = [MalVal]()
- result.reserveCapacity(hash.count * 2)
- for (k, v) in hash {
- let new_v = EVAL(v, env)
- if is_error(new_v) { return new_v }
- result.append(k)
- result.append(new_v)
- }
- return MalHashMap(array: result)
- default:
- return ast
+private func eval_ast(ast: MalVal, _ env: Environment) throws -> MalVal {
+ if let symbol = as_symbolQ(ast) {
+ guard let val = env.get(symbol) else {
+ try throw_error("'\(symbol)' not found") // Specific text needed to match MAL unit tests
+ }
+ return val
+ }
+ if let list = as_listQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(list.count))
+ for item in list {
+ let eval = try EVAL(item, env)
+ result.append(eval)
+ }
+ return make_list(result)
+ }
+ if let vec = as_vectorQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(vec.count))
+ for item in vec {
+ let eval = try EVAL(item, env)
+ result.append(eval)
+ }
+ return make_vector(result)
+ }
+ if let hash = as_hashmapQ(ast) {
+ var result = [MalVal]()
+ result.reserveCapacity(Int(hash.count) * 2)
+ for (k, v) in hash {
+ let new_v = try EVAL(v, env)
+ result.append(k)
+ result.append(new_v)
+ }
+ return make_hashmap(result)
}
+ return ast
}
-enum TCOVal {
+private enum TCOVal {
case NoResult
case Return(MalVal)
case Continue(MalVal, Environment)
init() { self = .NoResult }
init(_ result: MalVal) { self = .Return(result) }
init(_ ast: MalVal, _ env: Environment) { self = .Continue(ast, env) }
- init(_ e: String) { self = .Return(MalError(message: e)) }
}
// EVALuate "def!" and "defmacro!".
//
-func eval_def(list: MalSequence, env: Environment) -> TCOVal {
- if list.count != 3 {
- return TCOVal("expected 2 arguments to def!, got \(list.count - 1)")
- }
- let arg0 = list[0] as! MalSymbol
- let arg1 = list[1]
- let arg2 = list[2]
- if !is_symbol(arg1) {
- return TCOVal("expected symbol for first argument to def!")
- }
- let sym = arg1 as! MalSymbol
- let value = EVAL(arg2, env)
- if is_error(value) { return TCOVal(value) }
- if arg0 == kSymbolDefMacro {
- if is_closure(value) {
- let as_closure = value as! MalClosure
- as_closure.is_macro = true
- } else {
- return TCOVal("expected closure, got \(value)")
+private func eval_def(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count == 3 else {
+ try throw_error("expected 2 arguments to def!, got \(list.count - 1)")
+ }
+ let arg0 = try! list.nth(0)
+ let arg1 = try! list.nth(1)
+ let arg2 = try! list.nth(2)
+ guard let sym = as_symbolQ(arg1) else {
+ try throw_error("expected symbol for first argument to def!")
+ }
+ var value = try EVAL(arg2, env)
+ if as_symbol(arg0) == kSymbolDefMacro {
+ guard let closure = as_closureQ(value) else {
+ try throw_error("expected closure, got \(value)")
}
+ value = make_macro(closure)
}
return TCOVal(env.set(sym, value))
}
// EVALuate "let*".
//
-func eval_let(list: MalSequence, env: Environment) -> TCOVal {
- if list.count != 3 {
- return TCOVal("expected 2 arguments to let*, got \(list.count - 1)")
+private func eval_let(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count == 3 else {
+ try throw_error("expected 2 arguments to let*, got \(list.count - 1)")
}
- let arg1 = list[1]
- let arg2 = list[2]
- if !is_sequence(arg1) {
- return TCOVal("expected list for first argument to let*")
+ let arg1 = try! list.nth(1)
+ let arg2 = try! list.nth(2)
+ guard let bindings = as_sequenceQ(arg1) else {
+ try throw_error("expected list for first argument to let*")
}
- let bindings = arg1 as! MalSequence
- if bindings.count % 2 == 1 {
- return TCOVal("expected even number of elements in bindings to let*, got \(bindings.count)")
+ guard bindings.count % 2 == 0 else {
+ try throw_error("expected even number of elements in bindings to let*, got \(bindings.count)")
}
- var new_env = Environment(outer: env)
- for var index = 0; index < bindings.count; index += 2 {
- let binding_name = bindings[index]
- let binding_value = bindings[index + 1]
-
- if !is_symbol(binding_name) {
- return TCOVal("expected symbol for first element in binding pair")
+ let new_env = Environment(outer: env)
+ for var index: MalIntType = 0; index < bindings.count; index += 2 {
+ let binding_name = try! bindings.nth(index)
+ let binding_value = try! bindings.nth(index + 1)
+ guard let binding_symbol = as_symbolQ(binding_name) else {
+ try throw_error("expected symbol for first element in binding pair")
}
- let binding_symbol = binding_name as! MalSymbol
- let evaluated_value = EVAL(binding_value, new_env)
- if is_error(evaluated_value) { return TCOVal(evaluated_value) }
+ let evaluated_value = try EVAL(binding_value, new_env)
new_env.set(binding_symbol, evaluated_value)
}
if TCO {
return TCOVal(arg2, new_env)
}
- return TCOVal(EVAL(arg2, new_env))
+ return TCOVal(try EVAL(arg2, new_env))
}
// EVALuate "do".
//
-func eval_do(list: MalSequence, env: Environment) -> TCOVal {
+private func eval_do(list: MalSequence, _ env: Environment) throws -> TCOVal {
if TCO {
- let eval = eval_ast(MalList(slice: list[1..<list.count-1]), env)
- if is_error(eval) { return TCOVal(eval) }
+ let _ = try eval_ast(list.range_from(1, to: list.count-1), env)
return TCOVal(list.last(), env)
}
- let evaluated_ast = eval_ast(list.rest(), env)
- if is_error(evaluated_ast) { return TCOVal(evaluated_ast) }
- let evaluated_seq = evaluated_ast as! MalSequence
+ let evaluated_ast = try eval_ast(list.rest(), env)
+ let evaluated_seq = as_sequence(evaluated_ast)
return TCOVal(evaluated_seq.last())
}
// EVALuate "if".
//
-func eval_if(list: MalSequence, env: Environment) -> TCOVal {
- if list.count < 3 {
- return TCOVal("expected at least 2 arguments to if, got \(list.count - 1)")
+private func eval_if(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count >= 3 else {
+ try throw_error("expected at least 2 arguments to if, got \(list.count - 1)")
}
- let cond_result = EVAL(list[1], env)
- var new_ast = MalVal()
+ let cond_result = try EVAL(try! list.nth(1), env)
+ var new_ast: MalVal
if is_truthy(cond_result) {
- new_ast = list[2]
+ new_ast = try! list.nth(2)
} else if list.count == 4 {
- new_ast = list[3]
+ new_ast = try! list.nth(3)
} else {
- return TCOVal(MalNil())
+ return TCOVal(make_nil())
}
if TCO {
return TCOVal(new_ast, env)
}
- return TCOVal(EVAL(new_ast, env))
+ return TCOVal(try EVAL(new_ast, env))
}
// EVALuate "fn*".
//
-func eval_fn(list: MalSequence, env: Environment) -> TCOVal {
- if list.count != 3 {
- return TCOVal("expected 2 arguments to fn*, got \(list.count - 1)")
+private func eval_fn(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count == 3 else {
+ try throw_error("expected 2 arguments to fn*, got \(list.count - 1)")
}
- if !is_sequence(list[1]) {
- return TCOVal("expected list or vector for first argument to fn*")
+ guard let seq = as_sequenceQ(try! list.nth(1)) else {
+ try throw_error("expected list or vector for first argument to fn*")
}
- return TCOVal(MalClosure(eval: EVAL, args:list[1] as! MalSequence, body:list[2], env:env))
+ return TCOVal(make_closure((eval: EVAL, args: seq, body: try! list.nth(2), env: env)))
}
// EVALuate "quote".
//
-func eval_quote(list: MalSequence, env: Environment) -> TCOVal {
+private func eval_quote(list: MalSequence, _ env: Environment) throws -> TCOVal {
if list.count >= 2 {
- return TCOVal(list[1])
+ return TCOVal(try! list.nth(1))
}
- return TCOVal(MalNil())
+ return TCOVal(make_nil())
}
// EVALuate "quasiquote".
//
-func eval_quasiquote(list: MalSequence, env: Environment) -> TCOVal {
- if list.count >= 2 {
- if TCO {
- return TCOVal(quasiquote(list[1]), env)
- }
- return TCOVal(EVAL(quasiquote(list[1]), env))
+private func eval_quasiquote(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count >= 2 else {
+ try throw_error("Expected non-nil parameter to 'quasiquote'")
+ }
+ if TCO {
+ return TCOVal(try quasiquote(try! list.nth(1)), env)
}
- return TCOVal("Expected non-nil parameter to 'quasiquote'")
+ return TCOVal(try EVAL(try quasiquote(try! list.nth(1)), env))
}
// EVALuate "macroexpand".
//
-func eval_macroexpand(list: MalSequence, env: Environment) -> TCOVal {
- if list.count >= 2 {
- return TCOVal(macroexpand(list[1], env))
+private func eval_macroexpand(list: MalSequence, _ env: Environment) throws -> TCOVal {
+ guard list.count >= 2 else {
+ try throw_error("Expected parameter to 'macroexpand'")
}
- return TCOVal("Expected parameter to 'macroexpand'")
+ return TCOVal(try macroexpand(try! list.nth(1), env))
}
// EVALuate "try*" (and "catch*").
//
-func eval_try(list: MalSequence, env: Environment) -> TCOVal {
+private func eval_try(list: MalSequence, _ env: Environment) throws -> TCOVal {
// This is a subset of the Clojure try/catch:
//
// (try* expr (catch exception-name expr))
- if list.count < 2 {
- println("try*: no body parameter")
- return TCOVal(MalNil()) // No body parameter
- }
- let res = EVAL(list[1], env)
- if !is_error(res) { return TCOVal(res) }
- if list.count < 3 {
- println("try*: no catch parameter")
- return TCOVal(MalNil()) // No catch parameter
- }
- if !is_sequence(list[2]) {
- println("try*: second parameter to 'try' is not a sequence")
- return TCOVal(MalNil()) // Second parameter to 'try' is not a sequence
- }
- let catch_list = list[2] as! MalSequence
- if catch_list.count < 3 {
- println("try*: not enough catch parameters")
- return TCOVal(MalNil()) // Not enough catch parameters
- }
- if !is_symbol(catch_list[0]) {
- println("try*: first parameter in catch list is not a symbol")
- return TCOVal(MalNil()) // First parameter in catch list is not a symbol
- }
- let catch_symbol = catch_list[0] as! MalSymbol
- if catch_symbol != kSymbolCatch {
- println("try*: first parameter in catch list is not 'catch'")
- return TCOVal(MalNil()) // First parameter in catch list is not 'catch'
- }
- if !is_symbol(catch_list[1]) {
- println("try*: first parameter to 'catch' is not a symbol")
- return TCOVal(MalNil()) // First parameter to 'catch' is not a symbol
- }
- let catch_name = catch_list[1] as! MalSymbol
- let catch_expr = catch_list[2]
- let catch_env = Environment(outer: env)
- let error = res as! MalError
- catch_env.set_bindings(MalList(objects: catch_name), with_exprs: MalList(objects: error.value))
- return TCOVal(EVAL(catch_expr, catch_env))
+ guard list.count >= 2 else {
+ try throw_error("try*: no body parameter")
+ }
+
+ do {
+ return TCOVal(try EVAL(try! list.nth(1), env))
+ } catch let error as MalException {
+ guard list.count >= 3,
+ let catch_list = as_sequenceQ(try! list.nth(2)) where catch_list.count >= 3,
+ let _ = as_symbolQ(try! catch_list.nth(0)) else
+ {
+ throw error // No catch parameter
+ }
+ let catch_name = try! catch_list.nth(1)
+ let catch_expr = try! catch_list.nth(2)
+ let catch_env = Environment(outer: env)
+ try catch_env.set_bindings(as_sequence(make_list_from(catch_name)),
+ with_exprs: as_sequence(make_list_from(error.exception)))
+ return TCOVal(try EVAL(catch_expr, catch_env))
+ }
}
// Walk the AST and completely evaluate it, handling macro expansions, special
// forms and function calls.
//
-func EVAL(var ast: MalVal, var env: Environment) -> MalVal {
- let x = EVAL_Counter()
- if EVAL_level > EVAL_leval_max {
- return MalError(message: "Recursing too many levels (> \(EVAL_leval_max))")
+private func EVAL(var ast: MalVal, var _ env: Environment) throws -> MalVal {
+ EVAL_level++
+ defer { EVAL_level-- }
+ guard EVAL_level <= EVAL_leval_max else {
+ try throw_error("Recursing too many levels (> \(EVAL_leval_max))")
}
if DEBUG_EVAL {
- indent = prefix(INDENT_TEMPLATE, EVAL_level)
+ indent = substring(INDENT_TEMPLATE, 0, EVAL_level)
}
while true {
- if is_error(ast) { return ast }
- if DEBUG_EVAL { println("\(indent)> \(ast)") }
+ if DEBUG_EVAL { print("\(indent)> \(ast)") }
if !is_list(ast) {
// Not a list -- just evaluate and return.
- let answer = eval_ast(ast, env)
- if DEBUG_EVAL { println("\(indent)>>> \(answer)") }
+ let answer = try eval_ast(ast, env)
+ if DEBUG_EVAL { print("\(indent)>>> \(answer)") }
return answer
}
// Special handling if it's a list.
- var list = ast as! MalList
- ast = macroexpand(ast, env)
+ var list = as_list(ast)
+ ast = try macroexpand(ast, env)
if !is_list(ast) { return ast }
- list = ast as! MalList
+ list = as_list(ast)
- if DEBUG_EVAL { println("\(indent)>. \(list)") }
+ if DEBUG_EVAL { print("\(indent)>. \(list)") }
if list.isEmpty {
- return list
+ return ast
}
// Check for special forms, where we want to check the operation
// before evaluating all of the parameters.
let arg0 = list.first()
- if is_symbol(arg0) {
- var res: TCOVal
- let fn_symbol = arg0 as! MalSymbol
+ if let fn_symbol = as_symbolQ(arg0) {
+ let res: TCOVal
switch fn_symbol {
- case kSymbolDef: res = eval_def(list, env)
- case kSymbolDefMacro: res = eval_def(list, env)
- case kSymbolLet: res = eval_let(list, env)
- case kSymbolDo: res = eval_do(list, env)
- case kSymbolIf: res = eval_if(list, env)
- case kSymbolFn: res = eval_fn(list, env)
- case kSymbolQuote: res = eval_quote(list, env)
- case kSymbolQuasiQuote: res = eval_quasiquote(list, env)
- case kSymbolMacroExpand: res = eval_macroexpand(list, env)
- case kSymbolTry: res = eval_try(list, env)
+ case kSymbolDef: res = try eval_def(list, env)
+ case kSymbolDefMacro: res = try eval_def(list, env)
+ case kSymbolLet: res = try eval_let(list, env)
+ case kSymbolDo: res = try eval_do(list, env)
+ case kSymbolIf: res = try eval_if(list, env)
+ case kSymbolFn: res = try eval_fn(list, env)
+ case kSymbolQuote: res = try eval_quote(list, env)
+ case kSymbolQuasiQuote: res = try eval_quasiquote(list, env)
+ case kSymbolMacroExpand: res = try eval_macroexpand(list, env)
+ case kSymbolTry: res = try eval_try(list, env)
default: res = TCOVal()
}
switch res {
// Standard list to be applied. Evaluate all the elements first.
- let eval = eval_ast(ast, env)
- if is_error(eval) { return eval }
+ let eval = try eval_ast(ast, env)
// The result had better be a list and better be non-empty.
- let eval_list = eval as! MalList
+ let eval_list = as_list(eval)
if eval_list.isEmpty {
- return eval_list
+ return eval
}
- if DEBUG_EVAL { println("\(indent)>> \(eval)") }
+ if DEBUG_EVAL { print("\(indent)>> \(eval)") }
// Get the first element of the list and execute it.
let first = eval_list.first()
- let rest = eval_list.rest()
+ let rest = as_sequence(eval_list.rest())
- if is_builtin(first) {
- let fn = first as! MalBuiltin
- let answer = fn.apply(rest)
- if DEBUG_EVAL { println("\(indent)>>> \(answer)") }
+ if let fn = as_builtinQ(first) {
+ let answer = try fn.apply(rest)
+ if DEBUG_EVAL { print("\(indent)>>> \(answer)") }
return answer
- } else if is_closure(first) {
- let fn = first as! MalClosure
- var new_env = Environment(outer: fn.env)
- let result = new_env.set_bindings(fn.args, with_exprs:rest)
- if is_error(result) { return result }
+ } else if let fn = as_closureQ(first) {
+ let new_env = Environment(outer: fn.env)
+ let _ = try new_env.set_bindings(fn.args, with_exprs: rest)
if TCO {
env = new_env
ast = fn.body
continue
}
- let answer = EVAL(fn.body, new_env)
- if DEBUG_EVAL { println("\(indent)>>> \(answer)") }
+ let answer = try EVAL(fn.body, new_env)
+ if DEBUG_EVAL { print("\(indent)>>> \(answer)") }
return answer
}
// The first element wasn't a function to be executed. Return an
// error saying so.
- return MalError(message: "first list item does not evaluate to a function: \(first)")
+ try throw_error("first list item does not evaluate to a function: \(first)")
}
}
// Convert the value into a human-readable string for printing.
//
-func PRINT(exp: MalVal) -> String? {
- if is_error(exp) { return nil }
+private func PRINT(exp: MalVal) -> String {
return pr_str(exp, true)
}
// Perform the READ and EVAL steps. Useful for when you don't care about the
// printable result.
//
-func RE(text: String, env: Environment) -> MalVal? {
- if text.isEmpty { return nil }
- let ast = READ(text)
- if is_error(ast) {
- println("Error parsing input: \(ast)")
- return nil
- }
- let exp = EVAL(ast, env)
- if is_error(exp) {
- println("Error evaluating input: \(exp)")
- return nil
+private func RE(text: String, _ env: Environment) -> MalVal? {
+ if !text.isEmpty {
+ do {
+ let ast = try READ(text)
+ do {
+ return try EVAL(ast, env)
+ } catch let error as MalException {
+ print("Error evaluating input: \(error)")
+ } catch {
+ print("Error evaluating input: \(error)")
+ }
+ } catch let error as MalException {
+ print("Error parsing input: \(error)")
+ } catch {
+ print("Error parsing input: \(error)")
+ }
}
- return exp
+ return nil
}
// Perform the full READ/EVAL/PRINT, returning a printable string.
//
-func REP(text: String, env: Environment) -> String? {
+private func REP(text: String, _ env: Environment) -> String? {
let exp = RE(text, env)
if exp == nil { return nil }
return PRINT(exp!)
// Perform the full REPL.
//
-func REPL(env: Environment) {
+private func REPL(env: Environment) {
while true {
if let text = _readline("user> ") {
if let output = REP(text, env) {
- println("\(output)")
+ print("\(output)")
}
} else {
- println()
+ print("")
break
}
}
// taken as a script to execute. If one exists, it is executed in lieu of
// running the REPL.
//
-func process_command_line(args:[String], env:Environment) -> Bool {
- var argv = MalList()
+private func process_command_line(args: [String], _ env: Environment) -> Bool {
+ var argv = make_list()
if args.count > 2 {
let args1 = args[2..<args.count]
- let args2 = args1.map { MalString(unescaped: $0) as MalVal }
+ let args2 = args1.map { make_string($0) }
let args3 = [MalVal](args2)
- argv = MalList(array: args3)
+ argv = make_list(args3)
}
env.set(kSymbolArgv, argv)
}
func main() {
- var env = Environment(outer: nil)
+ let env = Environment(outer: nil)
load_history_file()
load_builtins(env)
- RE("(def! *host-language* \"swift\")", env);
+ RE("(def! *host-language* \"swift\")", env)
RE("(def! not (fn* (a) (if a false true)))", env)
RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", env)
RE("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) " +
RE("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) " +
"`(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))", env)
- env.set(kSymbolEval, MalBuiltin(function: {
- unwrap($0) {
- (ast:MalVal) -> MalVal in
- EVAL(ast, env)
+ env.set(kSymbolEval, make_builtin({
+ try! unwrap_args($0) {
+ (ast: MalVal) -> MalVal in
+ try EVAL(ast, env)
}
}))
if process_command_line(Process.arguments, env) {
- RE("(println (str \"Mal [\" *host-language*\"]\"))", env);
+ RE("(println (str \"Mal [\" *host-language*\"]\"))", env)
REPL(env)
}
// The number of times EVAL has been entered recursively. We keep track of this // malstep(5,6,7,8,9,A)
// so that we can protect against overrunning the stack. // malstep(5,6,7,8,9,A)
// // malstep(5,6,7,8,9,A)
-var EVAL_level = 0 // malstep(5,6,7,8,9,A)
+private var EVAL_level = 0 // malstep(5,6,7,8,9,A)
// malstep(5,6,7,8,9,A)
// The maximum number of times we let EVAL recurse before throwing an exception. // malstep(5,6,7,8,9,A)
// Testing puts this at some place between 1800 and 1900. Let's keep it at 500 // malstep(5,6,7,8,9,A)
// for safety's sake. // malstep(5,6,7,8,9,A)
// // malstep(5,6,7,8,9,A)
-let EVAL_leval_max = 500 // malstep(5,6,7,8,9,A)
+private let EVAL_leval_max = 500 // malstep(5,6,7,8,9,A)
// malstep(5,6,7,8,9,A)
// Control whether or not tail-call optimization (TCO) is enabled. We want it // malstep(5,6,7,8,9,A)
// `true` most of the time, but may disable it for debugging purposes (it's // malstep(5,6,7,8,9,A)
// easier to get a meaningful backtrace that way). // malstep(5,6,7,8,9,A)
// // malstep(5,6,7,8,9,A)
-let TCO = true // malstep(5,6,7,8,9,A)
+private let TCO = true // malstep(5,6,7,8,9,A)
// malstep(5,6,7,8,9,A)
// Control whether or not we emit debugging statements in EVAL. // malstep(5,6,7,8,9,A)
// // malstep(5,6,7,8,9,A)
-let DEBUG_EVAL = false // malstep(5,6,7,8,9,A)
+private let DEBUG_EVAL = false // malstep(5,6,7,8,9,A)
// malstep(5,6,7,8,9,A)
// String used to prefix information logged in EVAL. Increasing lengths of the // malstep(5,6,7,8,9,A)
// string are used the more EVAL is recursed. // malstep(5,6,7,8,9,A)
// // malstep(5,6,7,8,9,A)
-let INDENT_TEMPLATE = "|----|----|----|----|----|----|----|----|" + // malstep(5,6,7,8,9,A)
+private let INDENT_TEMPLATE = "|----|----|----|----|----|----|----|----|" + // malstep(5,6,7,8,9,A)
"----|----|----|----|----|----|----|----|----|----|----|" + // malstep(5,6,7,8,9,A)
"----|----|----|----|----|----|----|----|----|----|----|" + // malstep(5,6,7,8,9,A)
"----|----|----|----|----|----|----|----|----|----|----|" + // malstep(5,6,7,8,9,A)
// malstep(5,6,7,8,9,A)
// Holds the prefix of INDENT_TEMPLATE used for actual logging. // malstep(5,6,7,8,9,A)
// // malstep(5,6,7,8,9,A)
-var indent = String() // malstep(5,6,7,8,9,A)
+private var indent = String() // malstep(5,6,7,8,9,A)
// malstep(5,6,7,8,9,A)
// Symbols used in this module. // malstep(3,4,5,6,7,8,9,A)
// // malstep(3,4,5,6,7,8,9,A)
-let kSymbolArgv = MalSymbol(symbol: "*ARGV*") // malstep(6,7,8,9,A)
-let kSymbolCatch = MalSymbol(symbol: "catch*") // malstep(9,A)
-let kSymbolConcat = MalSymbol(symbol: "concat") // malstep(7,8,9,A)
-let kSymbolCons = MalSymbol(symbol: "cons") // malstep(7,8,9,A)
-let kSymbolDef = MalSymbol(symbol: "def!") // malstep(3,4,5,6,7,8,9,A)
-let kSymbolDefMacro = MalSymbol(symbol: "defmacro!") // malstep(8,9,A)
-let kSymbolDo = MalSymbol(symbol: "do") // malstep(4,5,6,7,8,9,A)
-let kSymbolEval = MalSymbol(symbol: "eval") // malstep(6,7,8,9,A)
-let kSymbolFn = MalSymbol(symbol: "fn*") // malstep(4,5,6,7,8,9,A)
-let kSymbolIf = MalSymbol(symbol: "if") // malstep(4,5,6,7,8,9,A)
-let kSymbolLet = MalSymbol(symbol: "let*") // malstep(3,4,5,6,7,8,9,A)
-let kSymbolMacroExpand = MalSymbol(symbol: "macroexpand") // malstep(8,9,A)
-let kSymbolQuasiQuote = MalSymbol(symbol: "quasiquote") // malstep(7,8,9,A)
-let kSymbolQuote = MalSymbol(symbol: "quote") // malstep(7,8,9,A)
-let kSymbolSpliceUnquote = MalSymbol(symbol: "splice-unquote") // malstep(7,8,9,A)
-let kSymbolUnquote = MalSymbol(symbol: "unquote") // malstep(7,8,9,A)
-let kSymbolTry = MalSymbol(symbol: "try*") // malstep(9,A)
+private let kValArgv = make_symbol("*ARGV*") // malstep(6,7,8,9,A)
+private let kValCatch = make_symbol("catch*") // malstep(9,A)
+private let kValConcat = make_symbol("concat") // malstep(7,8,9,A)
+private let kValCons = make_symbol("cons") // malstep(7,8,9,A)
+private let kValDef = make_symbol("def!") // malstep(3,4,5,6,7,8,9,A)
+private let kValDefMacro = make_symbol("defmacro!") // malstep(8,9,A)
+private let kValDo = make_symbol("do") // malstep(4,5,6,7,8,9,A)
+private let kValEval = make_symbol("eval") // malstep(6,7,8,9,A)
+private let kValFn = make_symbol("fn*") // malstep(4,5,6,7,8,9,A)
+private let kValIf = make_symbol("if") // malstep(4,5,6,7,8,9,A)
+private let kValLet = make_symbol("let*") // malstep(3,4,5,6,7,8,9,A)
+private let kValMacroExpand = make_symbol("macroexpand") // malstep(8,9,A)
+private let kValQuasiQuote = make_symbol("quasiquote") // malstep(7,8,9,A)
+private let kValQuote = make_symbol("quote") // malstep(7,8,9,A)
+private let kValSpliceUnquote = make_symbol("splice-unquote") // malstep(7,8,9,A)
+private let kValUnquote = make_symbol("unquote") // malstep(7,8,9,A)
+private let kValTry = make_symbol("try*") // malstep(3,4,5,6,7,8,9,A)
// malstep(3,4,5,6,7,8,9,A)
-// Class to help control the incrementing and decrementing of EVAL_level. We // malstep(5,6,7,8,9,A)
-// create one of these on entry to EVAL, incrementing the level. When the // malstep(5,6,7,8,9,A)
-// variable goes out of scope, the object is destroyed, decrementing the level. // malstep(5,6,7,8,9,A)
-// // malstep(5,6,7,8,9,A)
-class EVAL_Counter { // malstep(5,6,7,8,9,A)
- init() { // malstep(5,6,7,8,9,A)
- ++EVAL_level // malstep(5,6,7,8,9,A)
- } // malstep(5,6,7,8,9,A)
- deinit { // malstep(5,6,7,8,9,A)
- --EVAL_level // malstep(5,6,7,8,9,A)
- } // malstep(5,6,7,8,9,A)
+private let kSymbolArgv = as_symbol(kValArgv) // malstep(6,7,8,9,A)
+private let kSymbolCatch = as_symbol(kValCatch) // malstep(9,A)
+private let kSymbolConcat = as_symbol(kValConcat) // malstep(7,8,9,A)
+private let kSymbolCons = as_symbol(kValCons) // malstep(7,8,9,A)
+private let kSymbolDef = as_symbol(kValDef) // malstep(3,4,5,6,7,8,9,A)
+private let kSymbolDefMacro = as_symbol(kValDefMacro) // malstep(8,9,A)
+private let kSymbolDo = as_symbol(kValDo) // malstep(4,5,6,7,8,9,A)
+private let kSymbolEval = as_symbol(kValEval) // malstep(6,7,8,9,A)
+private let kSymbolFn = as_symbol(kValFn) // malstep(4,5,6,7,8,9,A)
+private let kSymbolIf = as_symbol(kValIf) // malstep(4,5,6,7,8,9,A)
+private let kSymbolLet = as_symbol(kValLet) // malstep(3,4,5,6,7,8,9,A)
+private let kSymbolMacroExpand = as_symbol(kValMacroExpand) // malstep(8,9,A)
+private let kSymbolQuasiQuote = as_symbol(kValQuasiQuote) // malstep(7,8,9,A)
+private let kSymbolQuote = as_symbol(kValQuote) // malstep(7,8,9,A)
+private let kSymbolSpliceUnquote = as_symbol(kValSpliceUnquote) // malstep(7,8,9,A)
+private let kSymbolUnquote = as_symbol(kValUnquote) // malstep(7,8,9,A)
+private let kSymbolTry = as_symbol(kValTry) // malstep(9,A)
+ // malstep(3,4,5,6,7,8,9,A)
+func substring(s: String, _ begin: Int, _ end: Int) -> String { // malstep(5,6,7,8,9,A)
+ return s[s.startIndex.advancedBy(begin) ..< s.startIndex.advancedBy(end)] // malstep(5,6,7,8,9,A)
} // malstep(5,6,7,8,9,A)
// malstep(5,6,7,8,9,A)
//
// Parse the string into an AST. // malstep(0,1,2,3,4,5,6,7,8,9,A)
// // malstep(0,1,2,3,4,5,6,7,8,9,A)
-func READ(str: String) -> String { // malstep(0)
+private func READ(str: String) -> String { // malstep(0)
return str // malstep(0)
} // malstep(0)
-func READ(str: String) -> MalVal { // malstep(1,2,3,4,5,6,7,8,9,A)
- return read_str(str) // malstep(1,2,3,4,5,6,7,8,9,A)
+private func READ(str: String) throws -> MalVal { // malstep(1,2,3,4,5,6,7,8,9,A)
+ return try read_str(str) // malstep(1,2,3,4,5,6,7,8,9,A)
} // malstep(1,2,3,4,5,6,7,8,9,A)
// malstep(0,1,2,3,4,5,6,7,8,9,A)
// Return whether or not `val` is a non-empty list. // malstep(7,8,9,A)
// // malstep(7,8,9,A)
-func is_pair(val:MalVal) -> Bool { // malstep(7,8,9,A)
- if !is_sequence(val) { return false } // malstep(7,8,9,A)
- let list = val as! MalSequence // malstep(7,8,9,A)
- return !list.isEmpty // malstep(7,8,9,A)
+private func is_pair(val: MalVal) -> Bool { // malstep(7,8,9,A)
+ if let seq = as_sequenceQ(val) { // malstep(7,8,9,A)
+ return !seq.isEmpty // malstep(7,8,9,A)
+ } // malstep(7,8,9,A)
+ return false // malstep(7,8,9,A)
} // malstep(7,8,9,A)
// malstep(7,8,9,A)
// Expand macros for as long as the expression looks like a macro invocation. // malstep(8,9,A)
// // malstep(8,9,A)
-func macroexpand(var ast:MalVal, env:Environment) -> MalVal { // malstep(8,9,A)
+private func macroexpand(var ast: MalVal, _ env: Environment) throws -> MalVal { // malstep(8,9,A)
while true { // malstep(8,9,A)
- if !is_list(ast) { break } // malstep(8,9,A)
- let ast_as_list = ast as! MalList // malstep(8,9,A)
- if ast_as_list.isEmpty { break } // malstep(8,9,A)
- let first = ast_as_list.first() // malstep(8,9,A)
- if !is_symbol(first) { break } // malstep(8,9,A)
- let macro_name = first as! MalSymbol // malstep(8,9,A)
- let obj = env.get(macro_name) // malstep(8,9,A)
- if obj == nil { break } // malstep(8,9,A)
- if !is_closure(obj!) { break } // malstep(8,9,A)
- let macro = obj! as! MalClosure // malstep(8,9,A)
- if !macro.is_macro { break } // malstep(8,9,A)
- var new_env = Environment(outer: macro.env) // malstep(8,9,A)
- let rest = ast_as_list.rest() // malstep(8,9,A)
- let res = new_env.set_bindings(macro.args, with_exprs:rest) // malstep(8,9,A)
- if is_error(res) { return res } // malstep(8,9,A)
- ast = EVAL(macro.body, new_env) // malstep(8,9,A)
+ if let ast_as_list = as_listQ(ast) where !ast_as_list.isEmpty, // malstep(8,9,A)
+ let macro_name = as_symbolQ(ast_as_list.first()), // malstep(8,9,A)
+ let obj = env.get(macro_name), // malstep(8,9,A)
+ let macro = as_macroQ(obj) // malstep(8,9,A)
+ { // malstep(8,9,A)
+ let new_env = Environment(outer: macro.env) // malstep(8,9,A)
+ let rest = as_sequence(ast_as_list.rest()) // malstep(8,9,A)
+ let _ = try new_env.set_bindings(macro.args, with_exprs: rest) // malstep(8,9,A)
+ ast = try EVAL(macro.body, new_env) // malstep(8,9,A)
+ continue // malstep(8,9,A)
+ } // malstep(8,9,A)
+ return ast // malstep(8,9,A)
} // malstep(8,9,A)
- return ast // malstep(8,9,A)
} // malstep(8,9,A)
// malstep(8,9,A)
// Evaluate `quasiquote`, possibly recursing in the process. // malstep(7,8,9,A)
// list) with the remaining items of the list containing that splice-quote // malstep(7,8,9,A)
// expression. However, it's not clear to me why the handling of "unquote" is // malstep(7,8,9,A)
// not handled similarly, for consistency's sake. // malstep(7,8,9,A)
- // malstep(7,8,9,A)
-func quasiquote(qq_arg:MalVal) -> MalVal { // malstep(7,8,9,A)
+// // malstep(7,8,9,A)
+private func quasiquote(qq_arg: MalVal) throws -> MalVal { // malstep(7,8,9,A)
// malstep(7,8,9,A)
// If the argument is an atom or empty list: // malstep(7,8,9,A)
// // malstep(7,8,9,A)
// Return: (quote <argument>) // malstep(7,8,9,A)
// malstep(7,8,9,A)
if !is_pair(qq_arg) { // malstep(7,8,9,A)
- return MalList(objects: kSymbolQuote, qq_arg) // malstep(7,8,9,A)
+ return make_list_from(kValQuote, qq_arg) // malstep(7,8,9,A)
} // malstep(7,8,9,A)
// malstep(7,8,9,A)
// The argument is a non-empty list -- that is (item rest...) // malstep(7,8,9,A)
// // malstep(7,8,9,A)
// Return: item // malstep(7,8,9,A)
// malstep(7,8,9,A)
- let qq_list = qq_arg as! MalSequence // malstep(7,8,9,A)
- if is_symbol(qq_list.first()) { // malstep(7,8,9,A)
- let sym = qq_list.first() as! MalSymbol // malstep(7,8,9,A)
- if sym == kSymbolUnquote { // malstep(7,8,9,A)
- return qq_list.count >= 2 ? qq_list[1] : MalNil() // malstep(7,8,9,A)
- } // malstep(7,8,9,A)
+ let qq_list = as_sequence(qq_arg) // malstep(7,8,9,A)
+ if let sym = as_symbolQ(qq_list.first()) where sym == kSymbolUnquote { // malstep(7,8,9,A)
+ return qq_list.count >= 2 ? try! qq_list.nth(1) : make_nil() // malstep(7,8,9,A)
} // malstep(7,8,9,A)
// malstep(7,8,9,A)
// If the first item from the list is itself a non-empty list starting with // malstep(7,8,9,A)
// Return: (concat item quasiquote(rest...)) // malstep(7,8,9,A)
// malstep(7,8,9,A)
if is_pair(qq_list.first()) { // malstep(7,8,9,A)
- let qq_list_item0 = qq_list.first() as! MalSequence // malstep(7,8,9,A)
- if is_symbol(qq_list_item0.first()) { // malstep(7,8,9,A)
- let sym = qq_list_item0.first() as! MalSymbol // malstep(7,8,9,A)
- if sym == kSymbolSpliceUnquote { // malstep(7,8,9,A)
- let result = quasiquote(qq_list.rest()) // malstep(7,8,9,A)
- if is_error(result) { return result } // malstep(7,8,9,A)
- return MalList(array: [kSymbolConcat, qq_list_item0[1], result]) // malstep(7,8,9,A)
- } // malstep(7,8,9,A)
+ let qq_list_item0 = as_sequence(qq_list.first()) // malstep(7,8,9,A)
+ if let sym = as_symbolQ(qq_list_item0.first()) where sym == kSymbolSpliceUnquote { // malstep(7,8,9,A)
+ let result = try quasiquote(qq_list.rest()) // malstep(7,8,9,A)
+ return make_list_from(kValConcat, try! qq_list_item0.nth(1), result) // malstep(7,8,9,A)
} // malstep(7,8,9,A)
} // malstep(7,8,9,A)
// malstep(7,8,9,A)
// // malstep(7,8,9,A)
// Return: (cons (quasiquote item) (quasiquote (rest...)) // malstep(7,8,9,A)
// malstep(7,8,9,A)
- let first = quasiquote(qq_list.first()) // malstep(7,8,9,A)
- if is_error(first) { return first } // malstep(7,8,9,A)
- // malstep(7,8,9,A)
- let rest = quasiquote(qq_list.rest()) // malstep(7,8,9,A)
- if is_error(rest) { return rest } // malstep(7,8,9,A)
- // malstep(7,8,9,A)
- return MalList(objects: kSymbolCons, first, rest) // malstep(7,8,9,A)
+ let first = try quasiquote(qq_list.first()) // malstep(7,8,9,A)
+ let rest = try quasiquote(qq_list.rest()) // malstep(7,8,9,A)
+ return make_list_from(kValCons, first, rest) // malstep(7,8,9,A)
} // malstep(7,8,9,A)
// malstep(7,8,9,A)
// Perform a simple evaluation of the `ast` object. If it's a symbol, // malstep(2,3,4,5,6,7,8,9,A)
// elements (or just the values, in the case of the hashmap). Otherwise, return // malstep(2,3,4,5,6,7,8,9,A)
// the object unchanged. // malstep(2,3,4,5,6,7,8,9,A)
// // malstep(2,3,4,5,6,7,8,9,A)
-func eval_ast(ast: MalVal, env: Environment) -> MalVal { // malstep(2,3,4,5,6,7,8,9,A)
- switch ast.type { // malstep(2,3,4,5,6,7,8,9,A)
- case .TypeSymbol: // malstep(2,3,4,5,6,7,8,9,A)
- let symbol = ast as! MalSymbol // malstep(2,3,4,5,6,7,8,9,A)
- if let val = env.get(symbol) { // malstep(2,3,4,5,6,7,8,9,A)
- return val // malstep(2,3,4,5,6,7,8,9,A)
- } // malstep(2,3,4,5,6,7,8,9,A)
- return MalError(message: "'\(symbol)' not found") // Specific text needed to match MAL unit tests // malstep(2,3,4,5,6,7,8,9,A)
- case .TypeList: // malstep(2,3,4,5,6,7,8,9,A)
- let list = ast as! MalList // malstep(2,3,4,5,6,7,8,9,A)
- var result = [MalVal]() // malstep(2,3,4,5,6,7,8,9,A)
- result.reserveCapacity(list.count) // malstep(2,3,4,5,6,7,8,9,A)
- for item in list { // malstep(2,3,4,5,6,7,8,9,A)
- let eval = EVAL(item, env) // malstep(2,3,4,5,6,7,8,9,A)
- if is_error(eval) { return eval } // malstep(2,3,4,5,6,7,8,9,A)
- result.append(eval) // malstep(2,3,4,5,6,7,8,9,A)
- } // malstep(2,3,4,5,6,7,8,9,A)
- return MalList(array: result) // malstep(2,3,4,5,6,7,8,9,A)
- case .TypeVector: // malstep(2,3,4,5,6,7,8,9,A)
- let vec = ast as! MalVector // malstep(2,3,4,5,6,7,8,9,A)
- var result = [MalVal]() // malstep(2,3,4,5,6,7,8,9,A)
- result.reserveCapacity(vec.count) // malstep(2,3,4,5,6,7,8,9,A)
- for item in vec { // malstep(2,3,4,5,6,7,8,9,A)
- let eval = EVAL(item, env) // malstep(2,3,4,5,6,7,8,9,A)
- if is_error(eval) { return eval } // malstep(2,3,4,5,6,7,8,9,A)
- result.append(eval) // malstep(2,3,4,5,6,7,8,9,A)
- } // malstep(2,3,4,5,6,7,8,9,A)
- return MalVector(array: result) // malstep(2,3,4,5,6,7,8,9,A)
- case .TypeHashMap: // malstep(2,3,4,5,6,7,8,9,A)
- let hash = ast as! MalHashMap // malstep(2,3,4,5,6,7,8,9,A)
- var result = [MalVal]() // malstep(2,3,4,5,6,7,8,9,A)
- result.reserveCapacity(hash.count * 2) // malstep(2,3,4,5,6,7,8,9,A)
- for (k, v) in hash { // malstep(2,3,4,5,6,7,8,9,A)
- let new_v = EVAL(v, env) // malstep(2,3,4,5,6,7,8,9,A)
- if is_error(new_v) { return new_v } // malstep(2,3,4,5,6,7,8,9,A)
- result.append(k) // malstep(2,3,4,5,6,7,8,9,A)
- result.append(new_v) // malstep(2,3,4,5,6,7,8,9,A)
- } // malstep(2,3,4,5,6,7,8,9,A)
- return MalHashMap(array: result) // malstep(2,3,4,5,6,7,8,9,A)
- default: // malstep(2,3,4,5,6,7,8,9,A)
- return ast // malstep(2,3,4,5,6,7,8,9,A)
+private func eval_ast(ast: MalVal, _ env: Environment) throws -> MalVal { // malstep(2,3,4,5,6,7,8,9,A)
+ if let symbol = as_symbolQ(ast) { // malstep(2,3,4,5,6,7,8,9,A)
+ guard let val = env.get(symbol) else { // malstep(2,3,4,5,6,7,8,9,A)
+ try throw_error("'\(symbol)' not found") // Specific text needed to match MAL unit tests // malstep(2,3,4,5,6,7,8,9,A)
+ } // malstep(2,3,4,5,6,7,8,9,A)
+ return val // malstep(2,3,4,5,6,7,8,9,A)
+ } // malstep(2,3,4,5,6,7,8,9,A)
+ if let list = as_listQ(ast) { // malstep(2,3,4,5,6,7,8,9,A)
+ var result = [MalVal]() // malstep(2,3,4,5,6,7,8,9,A)
+ result.reserveCapacity(Int(list.count)) // malstep(2,3,4,5,6,7,8,9,A)
+ for item in list { // malstep(2,3,4,5,6,7,8,9,A)
+ let eval = try EVAL(item, env) // malstep(2,3,4,5,6,7,8,9,A)
+ result.append(eval) // malstep(2,3,4,5,6,7,8,9,A)
+ } // malstep(2,3,4,5,6,7,8,9,A)
+ return make_list(result) // malstep(2,3,4,5,6,7,8,9,A)
+ } // malstep(2,3,4,5,6,7,8,9,A)
+ if let vec = as_vectorQ(ast) { // malstep(2,3,4,5,6,7,8,9,A)
+ var result = [MalVal]() // malstep(2,3,4,5,6,7,8,9,A)
+ result.reserveCapacity(Int(vec.count)) // malstep(2,3,4,5,6,7,8,9,A)
+ for item in vec { // malstep(2,3,4,5,6,7,8,9,A)
+ let eval = try EVAL(item, env) // malstep(2,3,4,5,6,7,8,9,A)
+ result.append(eval) // malstep(2,3,4,5,6,7,8,9,A)
+ } // malstep(2,3,4,5,6,7,8,9,A)
+ return make_vector(result) // malstep(2,3,4,5,6,7,8,9,A)
+ } // malstep(2,3,4,5,6,7,8,9,A)
+ if let hash = as_hashmapQ(ast) { // malstep(2,3,4,5,6,7,8,9,A)
+ var result = [MalVal]() // malstep(2,3,4,5,6,7,8,9,A)
+ result.reserveCapacity(Int(hash.count) * 2) // malstep(2,3,4,5,6,7,8,9,A)
+ for (k, v) in hash { // malstep(2,3,4,5,6,7,8,9,A)
+ let new_v = try EVAL(v, env) // malstep(2,3,4,5,6,7,8,9,A)
+ result.append(k) // malstep(2,3,4,5,6,7,8,9,A)
+ result.append(new_v) // malstep(2,3,4,5,6,7,8,9,A)
+ } // malstep(2,3,4,5,6,7,8,9,A)
+ return make_hashmap(result) // malstep(2,3,4,5,6,7,8,9,A)
} // malstep(2,3,4,5,6,7,8,9,A)
+ return ast // malstep(2,3,4,5,6,7,8,9,A)
} // malstep(2,3,4,5,6,7,8,9,A)
// malstep(2,3,4,5,6,7,8,9,A)
-enum TCOVal { // malstep(5,6,7,8,9,A)
+private enum TCOVal { // malstep(5,6,7,8,9,A)
case NoResult // malstep(5,6,7,8,9,A)
case Return(MalVal) // malstep(5,6,7,8,9,A)
case Continue(MalVal, Environment) // malstep(5,6,7,8,9,A)
init() { self = .NoResult } // malstep(5,6,7,8,9,A)
init(_ result: MalVal) { self = .Return(result) } // malstep(5,6,7,8,9,A)
init(_ ast: MalVal, _ env: Environment) { self = .Continue(ast, env) } // malstep(5,6,7,8,9,A)
- init(_ e: String) { self = .Return(MalError(message: e)) } // malstep(5,6,7,8,9,A)
} // malstep(5,6,7,8,9,A)
// malstep(5,6,7,8,9,A)
// EVALuate "def!". // malstep(3,4,5,6,7)
// EVALuate "def!" and "defmacro!". // malstep(8,9,A)
// // malstep(3,4,5,6,7,8,9,A)
-func eval_def(list: MalSequence, env: Environment) -> MalVal { // malstep(3,4)
-func eval_def(list: MalSequence, env: Environment) -> TCOVal { // malstep(5,6,7,8,9,A)
- if list.count != 3 { // malstep(3,4,5,6,7,8,9,A)
- return MalError(message: "expected 2 arguments to def!, got \(list.count - 1)") // malstep(3,4)
- return TCOVal("expected 2 arguments to def!, got \(list.count - 1)") // malstep(5,6,7,8,9,A)
+private func eval_def(list: MalSequence, _ env: Environment) throws -> MalVal { // malstep(3,4)
+private func eval_def(list: MalSequence, _ env: Environment) throws -> TCOVal { // malstep(5,6,7,8,9,A)
+ guard list.count == 3 else { // malstep(3,4,5,6,7,8,9,A)
+ try throw_error("expected 2 arguments to def!, got \(list.count - 1)") // malstep(3,4,5,6,7,8,9,A)
} // malstep(3,4,5,6,7,8,9,A)
- let arg0 = list[0] as! MalSymbol // malstep(8,9,A)
- let arg1 = list[1] // malstep(3,4,5,6,7,8,9,A)
- let arg2 = list[2] // malstep(3,4,5,6,7,8,9,A)
- if !is_symbol(arg1) { // malstep(3,4,5,6,7,8,9,A)
- return MalError(message: "expected symbol for first argument to def!") // malstep(3,4)
- return TCOVal("expected symbol for first argument to def!") // malstep(5,6,7,8,9,A)
+ let arg0 = try! list.nth(0) // malstep(8,9,A)
+ let arg1 = try! list.nth(1) // malstep(3,4,5,6,7,8,9,A)
+ let arg2 = try! list.nth(2) // malstep(3,4,5,6,7,8,9,A)
+ guard let sym = as_symbolQ(arg1) else { // malstep(3,4,5,6,7,8,9,A)
+ try throw_error("expected symbol for first argument to def!") // malstep(3,4,5,6,7,8,9,A)
} // malstep(3,4,5,6,7,8,9,A)
- let sym = arg1 as! MalSymbol // malstep(3,4,5,6,7,8,9,A)
- let value = EVAL(arg2, env) // malstep(3,4,5,6,7,8,9,A)
- if is_error(value) { return value } // malstep(3,4)
- if is_error(value) { return TCOVal(value) } // malstep(5,6,7,8,9,A)
- if arg0 == kSymbolDefMacro { // malstep(8,9,A)
- if is_closure(value) { // malstep(8,9,A)
- let as_closure = value as! MalClosure // malstep(8,9,A)
- as_closure.is_macro = true // malstep(8,9,A)
- } else { // malstep(8,9,A)
- return TCOVal("expected closure, got \(value)") // malstep(8,9,A)
+ let value = try EVAL(arg2, env) // malstep(3,4,5,6,7)
+ var value = try EVAL(arg2, env) // malstep(8,9,A)
+ if as_symbol(arg0) == kSymbolDefMacro { // malstep(8,9,A)
+ guard let closure = as_closureQ(value) else { // malstep(8,9,A)
+ try throw_error("expected closure, got \(value)") // malstep(8,9,A)
} // malstep(8,9,A)
+ value = make_macro(closure) // malstep(8,9,A)
} // malstep(8,9,A)
return env.set(sym, value) // malstep(3,4)
return TCOVal(env.set(sym, value)) // malstep(5,6,7,8,9,A)
// malstep(3,4,5,6,7,8,9,A)
// EVALuate "let*". // malstep(3,4,5,6,7,8,9,A)
// // malstep(3,4,5,6,7,8,9,A)
-func eval_let(list: MalSequence, env: Environment) -> MalVal { // malstep(3,4)
-func eval_let(list: MalSequence, env: Environment) -> TCOVal { // malstep(5,6,7,8,9,A)
- if list.count != 3 { // malstep(3,4,5,6,7,8,9,A)
- return MalError(message: "expected 2 arguments to let*, got \(list.count - 1)") // malstep(3,4)
- return TCOVal("expected 2 arguments to let*, got \(list.count - 1)") // malstep(5,6,7,8,9,A)
+private func eval_let(list: MalSequence, _ env: Environment) throws -> MalVal { // malstep(3,4)
+private func eval_let(list: MalSequence, _ env: Environment) throws -> TCOVal { // malstep(5,6,7,8,9,A)
+ guard list.count == 3 else { // malstep(3,4,5,6,7,8,9,A)
+ try throw_error("expected 2 arguments to let*, got \(list.count - 1)") // malstep(3,4,5,6,7,8,9,A)
} // malstep(3,4,5,6,7,8,9,A)
- let arg1 = list[1] // malstep(3,4,5,6,7,8,9,A)
- let arg2 = list[2] // malstep(3,4,5,6,7,8,9,A)
- if !is_sequence(arg1) { // malstep(3,4,5,6,7,8,9,A)
- return MalError(message: "expected list for first argument to let*") // malstep(3,4)
- return TCOVal("expected list for first argument to let*") // malstep(5,6,7,8,9,A)
+ let arg1 = try! list.nth(1) // malstep(3,4,5,6,7,8,9,A)
+ let arg2 = try! list.nth(2) // malstep(3,4,5,6,7,8,9,A)
+ guard let bindings = as_sequenceQ(arg1) else { // malstep(3,4,5,6,7,8,9,A)
+ try throw_error("expected list for first argument to let*") // malstep(3,4,5,6,7,8,9,A)
} // malstep(3,4,5,6,7,8,9,A)
- let bindings = arg1 as! MalSequence // malstep(3,4,5,6,7,8,9,A)
- if bindings.count % 2 == 1 { // malstep(3,4,5,6,7,8,9,A)
- return MalError(message: "expected even number of elements in bindings to let*, got \(bindings.count)") // malstep(3,4)
- return TCOVal("expected even number of elements in bindings to let*, got \(bindings.count)") // malstep(5,6,7,8,9,A)
+ guard bindings.count % 2 == 0 else { // malstep(3,4,5,6,7,8,9,A)
+ try throw_error("expected even number of elements in bindings to let*, got \(bindings.count)") // malstep(3,4,5,6,7,8,9,A)
} // malstep(3,4,5,6,7,8,9,A)
- var new_env = Environment(outer: env) // malstep(3,4,5,6,7,8,9,A)
- for var index = 0; index < bindings.count; index += 2 { // malstep(3,4,5,6,7,8,9,A)
- let binding_name = bindings[index] // malstep(3,4,5,6,7,8,9,A)
- let binding_value = bindings[index + 1] // malstep(3,4,5,6,7,8,9,A)
- // malstep(3,4,5,6,7,8,9,A)
- if !is_symbol(binding_name) { // malstep(3,4,5,6,7,8,9,A)
- return MalError(message: "expected symbol for first element in binding pair") // malstep(3,4)
- return TCOVal("expected symbol for first element in binding pair") // malstep(5,6,7,8,9,A)
+ let new_env = Environment(outer: env) // malstep(3,4,5,6,7,8,9,A)
+ for var index: MalIntType = 0; index < bindings.count; index += 2 { // malstep(3,4,5,6,7,8,9,A)
+ let binding_name = try! bindings.nth(index) // malstep(3,4,5,6,7,8,9,A)
+ let binding_value = try! bindings.nth(index + 1) // malstep(3,4,5,6,7,8,9,A)
+ guard let binding_symbol = as_symbolQ(binding_name) else { // malstep(3,4,5,6,7,8,9,A)
+ try throw_error("expected symbol for first element in binding pair") // malstep(3,4,5,6,7,8,9,A)
} // malstep(3,4,5,6,7,8,9,A)
- let binding_symbol = binding_name as! MalSymbol // malstep(3,4,5,6,7,8,9,A)
- let evaluated_value = EVAL(binding_value, new_env) // malstep(3,4,5,6,7,8,9,A)
- if is_error(evaluated_value) { return evaluated_value } // malstep(3,4)
- if is_error(evaluated_value) { return TCOVal(evaluated_value) } // malstep(5,6,7,8,9,A)
+ let evaluated_value = try EVAL(binding_value, new_env) // malstep(3,4,5,6,7,8,9,A)
new_env.set(binding_symbol, evaluated_value) // malstep(3,4,5,6,7,8,9,A)
} // malstep(3,4,5,6,7,8,9,A)
if TCO { // malstep(5,6,7,8,9,A)
return TCOVal(arg2, new_env) // malstep(5,6,7,8,9,A)
} // malstep(5,6,7,8,9,A)
- return EVAL(arg2, new_env) // malstep(3,4)
- return TCOVal(EVAL(arg2, new_env)) // malstep(5,6,7,8,9,A)
+ return try EVAL(arg2, new_env) // malstep(3,4)
+ return TCOVal(try EVAL(arg2, new_env)) // malstep(5,6,7,8,9,A)
} // malstep(3,4,5,6,7,8,9,A)
// malstep(3,4,5,6,7,8,9,A)
// EVALuate "do". // malstep(4,5,6,7,8,9,A)
// // malstep(4,5,6,7,8,9,A)
-func eval_do(list: MalSequence, env: Environment) -> MalVal { // malstep(4)
-func eval_do(list: MalSequence, env: Environment) -> TCOVal { // malstep(5,6,7,8,9,A)
+private func eval_do(list: MalSequence, _ env: Environment) throws -> MalVal { // malstep(4)
+private func eval_do(list: MalSequence, _ env: Environment) throws -> TCOVal { // malstep(5,6,7,8,9,A)
if TCO { // malstep(5,6,7,8,9,A)
- let eval = eval_ast(MalList(slice: list[1..<list.count-1]), env) // malstep(5,6,7,8,9,A)
- if is_error(eval) { return TCOVal(eval) } // malstep(5,6,7,8,9,A)
+ let _ = try eval_ast(list.range_from(1, to: list.count-1), env) // malstep(5,6,7,8,9,A)
return TCOVal(list.last(), env) // malstep(5,6,7,8,9,A)
} // malstep(5,6,7,8,9,A)
// malstep(5,6,7,8,9,A)
- let evaluated_ast = eval_ast(list.rest(), env) // malstep(4,5,6,7,8,9,A)
- if is_error(evaluated_ast) { return evaluated_ast } // malstep(4)
- if is_error(evaluated_ast) { return TCOVal(evaluated_ast) } // malstep(5,6,7,8,9,A)
- let evaluated_seq = evaluated_ast as! MalSequence // malstep(4,5,6,7,8,9,A)
+ let evaluated_ast = try eval_ast(list.rest(), env) // malstep(4,5,6,7,8,9,A)
+ let evaluated_seq = as_sequence(evaluated_ast) // malstep(4,5,6,7,8,9,A)
return evaluated_seq.last() // malstep(4)
return TCOVal(evaluated_seq.last()) // malstep(5,6,7,8,9,A)
} // malstep(4,5,6,7,8,9,A)
// malstep(4,5,6,7,8,9,A)
// EVALuate "if". // malstep(4,5,6,7,8,9,A)
// // malstep(4,5,6,7,8,9,A)
-func eval_if(list: MalSequence, env: Environment) -> MalVal { // malstep(4)
-func eval_if(list: MalSequence, env: Environment) -> TCOVal { // malstep(5,6,7,8,9,A)
- if list.count < 3 { // malstep(4,5,6,7,8,9,A)
- return MalError(message: "expected at least 2 arguments to if, got \(list.count - 1)") // malstep(4)
- return TCOVal("expected at least 2 arguments to if, got \(list.count - 1)") // malstep(5,6,7,8,9,A)
+private func eval_if(list: MalSequence, _ env: Environment) throws -> MalVal { // malstep(4)
+private func eval_if(list: MalSequence, _ env: Environment) throws -> TCOVal { // malstep(5,6,7,8,9,A)
+ guard list.count >= 3 else { // malstep(4,5,6,7,8,9,A)
+ try throw_error("expected at least 2 arguments to if, got \(list.count - 1)") // malstep(4,5,6,7,8,9,A)
} // malstep(4,5,6,7,8,9,A)
- let cond_result = EVAL(list[1], env) // malstep(4,5,6,7,8,9,A)
- var new_ast = MalVal() // malstep(4,5,6,7,8,9,A)
+ let cond_result = try EVAL(try! list.nth(1), env) // malstep(4,5,6,7,8,9,A)
+ var new_ast: MalVal // malstep(4,5,6,7,8,9,A)
if is_truthy(cond_result) { // malstep(4,5,6,7,8,9,A)
- new_ast = list[2] // malstep(4,5,6,7,8,9,A)
+ new_ast = try! list.nth(2) // malstep(4,5,6,7,8,9,A)
} else if list.count == 4 { // malstep(4,5,6,7,8,9,A)
- new_ast = list[3] // malstep(4,5,6,7,8,9,A)
+ new_ast = try! list.nth(3) // malstep(4,5,6,7,8,9,A)
} else { // malstep(4,5,6,7,8,9,A)
- return MalNil() // malstep(4)
- return TCOVal(MalNil()) // malstep(5,6,7,8,9,A)
+ return make_nil() // malstep(4)
+ return TCOVal(make_nil()) // malstep(5,6,7,8,9,A)
} // malstep(4,5,6,7,8,9,A)
if TCO { // malstep(5,6,7,8,9,A)
return TCOVal(new_ast, env) // malstep(5,6,7,8,9,A)
} // malstep(5,6,7,8,9,A)
- return EVAL(new_ast, env) // malstep(4)
- return TCOVal(EVAL(new_ast, env)) // malstep(5,6,7,8,9,A)
+ return try EVAL(new_ast, env) // malstep(4)
+ return TCOVal(try EVAL(new_ast, env)) // malstep(5,6,7,8,9,A)
} // malstep(4,5,6,7,8,9,A)
// malstep(4,5,6,7,8,9,A)
// EVALuate "fn*". // malstep(4,5,6,7,8,9,A)
// // malstep(4,5,6,7,8,9,A)
-func eval_fn(list: MalSequence, env: Environment) -> MalVal { // malstep(4)
-func eval_fn(list: MalSequence, env: Environment) -> TCOVal { // malstep(5,6,7,8,9,A)
- if list.count != 3 { // malstep(4,5,6,7,8,9,A)
- return MalError(message: "expected 2 arguments to fn*, got \(list.count - 1)") // malstep(4)
- return TCOVal("expected 2 arguments to fn*, got \(list.count - 1)") // malstep(5,6,7,8,9,A)
+private func eval_fn(list: MalSequence, _ env: Environment) throws -> MalVal { // malstep(4)
+private func eval_fn(list: MalSequence, _ env: Environment) throws -> TCOVal { // malstep(5,6,7,8,9,A)
+ guard list.count == 3 else { // malstep(4,5,6,7,8,9,A)
+ try throw_error("expected 2 arguments to fn*, got \(list.count - 1)") // malstep(4,5,6,7,8,9,A)
} // malstep(4,5,6,7,8,9,A)
- if !is_sequence(list[1]) { // malstep(4,5,6,7,8,9,A)
- return MalError(message: "expected list or vector for first argument to fn*") // malstep(4)
- return TCOVal("expected list or vector for first argument to fn*") // malstep(5,6,7,8,9,A)
+ guard let seq = as_sequenceQ(try! list.nth(1)) else { // malstep(4,5,6,7,8,9,A)
+ try throw_error("expected list or vector for first argument to fn*") // malstep(4,5,6,7,8,9,A)
} // malstep(4,5,6,7,8,9,A)
- return MalClosure(eval: EVAL, args:list[1] as! MalSequence, body:list[2], env:env) // malstep(4)
- return TCOVal(MalClosure(eval: EVAL, args:list[1] as! MalSequence, body:list[2], env:env)) // malstep(5,6,7,8,9,A)
+ return make_closure((eval: EVAL, args: seq, body: try! list.nth(2), env: env)) // malstep(4)
+ return TCOVal(make_closure((eval: EVAL, args: seq, body: try! list.nth(2), env: env))) // malstep(5,6,7,8,9,A)
} // malstep(4,5,6,7,8,9,A)
// malstep(4,5,6,7,8,9,A)
// EVALuate "quote". // malstep(7,8,9,A)
// // malstep(7,8,9,A)
-func eval_quote(list: MalSequence, env: Environment) -> TCOVal { // malstep(7,8,9,A)
+private func eval_quote(list: MalSequence, _ env: Environment) throws -> TCOVal { // malstep(7,8,9,A)
if list.count >= 2 { // malstep(7,8,9,A)
- return TCOVal(list[1]) // malstep(7,8,9,A)
+ return TCOVal(try! list.nth(1)) // malstep(7,8,9,A)
} // malstep(7,8,9,A)
- return TCOVal(MalNil()) // malstep(7,8,9,A)
+ return TCOVal(make_nil()) // malstep(7,8,9,A)
} // malstep(7,8,9,A)
// malstep(7,8,9,A)
// EVALuate "quasiquote". // malstep(7,8,9,A)
// // malstep(7,8,9,A)
-func eval_quasiquote(list: MalSequence, env: Environment) -> TCOVal { // malstep(7,8,9,A)
- if list.count >= 2 { // malstep(7,8,9,A)
- if TCO { // malstep(7,8,9,A)
- return TCOVal(quasiquote(list[1]), env) // malstep(7,8,9,A)
- } // malstep(7,8,9,A)
- return TCOVal(EVAL(quasiquote(list[1]), env)) // malstep(7,8,9,A)
+private func eval_quasiquote(list: MalSequence, _ env: Environment) throws -> TCOVal { // malstep(7,8,9,A)
+ guard list.count >= 2 else { // malstep(7,8,9,A)
+ try throw_error("Expected non-nil parameter to 'quasiquote'") // malstep(7,8,9,A)
+ } // malstep(7,8,9,A)
+ if TCO { // malstep(7,8,9,A)
+ return TCOVal(try quasiquote(try! list.nth(1)), env) // malstep(7,8,9,A)
} // malstep(7,8,9,A)
- return TCOVal("Expected non-nil parameter to 'quasiquote'") // malstep(7,8,9,A)
+ return TCOVal(try EVAL(try quasiquote(try! list.nth(1)), env)) // malstep(7,8,9,A)
} // malstep(7,8,9,A)
// malstep(7,8,9,A)
// EVALuate "macroexpand". // malstep(8,9,A)
// // malstep(8,9,A)
-func eval_macroexpand(list: MalSequence, env: Environment) -> TCOVal { // malstep(8,9,A)
- if list.count >= 2 { // malstep(8,9,A)
- return TCOVal(macroexpand(list[1], env)) // malstep(8,9,A)
+private func eval_macroexpand(list: MalSequence, _ env: Environment) throws -> TCOVal { // malstep(8,9,A)
+ guard list.count >= 2 else { // malstep(8,9,A)
+ try throw_error("Expected parameter to 'macroexpand'") // malstep(8,9,A)
} // malstep(8,9,A)
- return TCOVal("Expected parameter to 'macroexpand'") // malstep(8,9,A)
+ return TCOVal(try macroexpand(try! list.nth(1), env)) // malstep(8,9,A)
} // malstep(8,9,A)
// malstep(8,9,A)
// EVALuate "try*" (and "catch*"). // malstep(9,A)
// // malstep(9,A)
-func eval_try(list: MalSequence, env: Environment) -> TCOVal { // malstep(9,A)
+private func eval_try(list: MalSequence, _ env: Environment) throws -> TCOVal { // malstep(9,A)
// This is a subset of the Clojure try/catch: // malstep(9,A)
// // malstep(9,A)
// (try* expr (catch exception-name expr)) // malstep(9,A)
// malstep(9,A)
- if list.count < 2 { // malstep(9,A)
- println("try*: no body parameter") // malstep(9,A)
- return TCOVal(MalNil()) // No body parameter // malstep(9,A)
+ guard list.count >= 2 else { // malstep(9,A)
+ try throw_error("try*: no body parameter") // malstep(9,A)
} // malstep(9,A)
- let res = EVAL(list[1], env) // malstep(9,A)
- if !is_error(res) { return TCOVal(res) } // malstep(9,A)
- if list.count < 3 { // malstep(9,A)
- println("try*: no catch parameter") // malstep(9,A)
- return TCOVal(MalNil()) // No catch parameter // malstep(9,A)
- } // malstep(9,A)
- if !is_sequence(list[2]) { // malstep(9,A)
- println("try*: second parameter to 'try' is not a sequence") // malstep(9,A)
- return TCOVal(MalNil()) // Second parameter to 'try' is not a sequence // malstep(9,A)
- } // malstep(9,A)
- let catch_list = list[2] as! MalSequence // malstep(9,A)
- if catch_list.count < 3 { // malstep(9,A)
- println("try*: not enough catch parameters") // malstep(9,A)
- return TCOVal(MalNil()) // Not enough catch parameters // malstep(9,A)
- } // malstep(9,A)
- if !is_symbol(catch_list[0]) { // malstep(9,A)
- println("try*: first parameter in catch list is not a symbol") // malstep(9,A)
- return TCOVal(MalNil()) // First parameter in catch list is not a symbol // malstep(9,A)
- } // malstep(9,A)
- let catch_symbol = catch_list[0] as! MalSymbol // malstep(9,A)
- if catch_symbol != kSymbolCatch { // malstep(9,A)
- println("try*: first parameter in catch list is not 'catch'") // malstep(9,A)
- return TCOVal(MalNil()) // First parameter in catch list is not 'catch' // malstep(9,A)
- } // malstep(9,A)
- if !is_symbol(catch_list[1]) { // malstep(9,A)
- println("try*: first parameter to 'catch' is not a symbol") // malstep(9,A)
- return TCOVal(MalNil()) // First parameter to 'catch' is not a symbol // malstep(9,A)
+ // malstep(9,A)
+ do { // malstep(9,A)
+ return TCOVal(try EVAL(try! list.nth(1), env)) // malstep(9,A)
+ } catch let error as MalException { // malstep(9,A)
+ guard list.count >= 3, // malstep(9,A)
+ let catch_list = as_sequenceQ(try! list.nth(2)) where catch_list.count >= 3, // malstep(9,A)
+ let _ = as_symbolQ(try! catch_list.nth(0)) else // malstep(9,A)
+ { // malstep(9,A)
+ throw error // No catch parameter // malstep(9,A)
+ } // malstep(9,A)
+ let catch_name = try! catch_list.nth(1) // malstep(9,A)
+ let catch_expr = try! catch_list.nth(2) // malstep(9,A)
+ let catch_env = Environment(outer: env) // malstep(9,A)
+ try catch_env.set_bindings(as_sequence(make_list_from(catch_name)), // malstep(9,A)
+ with_exprs: as_sequence(make_list_from(error.exception))) // malstep(9,A)
+ return TCOVal(try EVAL(catch_expr, catch_env)) // malstep(9,A)
} // malstep(9,A)
- let catch_name = catch_list[1] as! MalSymbol // malstep(9,A)
- let catch_expr = catch_list[2] // malstep(9,A)
- let catch_env = Environment(outer: env) // malstep(9,A)
- let error = res as! MalError // malstep(9,A)
- catch_env.set_bindings(MalList(objects: catch_name), with_exprs: MalList(objects: error.value)) // malstep(9,A)
- return TCOVal(EVAL(catch_expr, catch_env)) // malstep(9,A)
} // malstep(9,A)
// malstep(9,A)
//
// Walk the AST and completely evaluate it, handling macro expansions, special // malstep(0,1,2,3,4,5,6,7,8,9,A)
// forms and function calls. // malstep(0,1,2,3,4,5,6,7,8,9,A)
// // malstep(0,1,2,3,4,5,6,7,8,9,A)
-func EVAL(ast: String) -> String { // malstep(0)
+private func EVAL(ast: String) -> String { // malstep(0)
return ast // malstep(0)
} // malstep(0)
-func EVAL(ast: MalVal) -> MalVal { // malstep(1)
- if is_error(ast) { return ast } // malstep(1)
+private func EVAL(ast: MalVal) -> MalVal { // malstep(1)
return ast // malstep(1)
} // malstep(1)
-func EVAL(var ast: MalVal, var env: Environment) -> MalVal { // malstep(2,3,4,5,6,7,8,9,A)
- let x = EVAL_Counter() // malstep(5,6,7,8,9,A)
- if EVAL_level > EVAL_leval_max { // malstep(5,6,7,8,9,A)
- return MalError(message: "Recursing too many levels (> \(EVAL_leval_max))") // malstep(5,6,7,8,9,A)
+private func EVAL(ast: MalVal, _ env: Environment) throws -> MalVal { // malstep(2,3,4)
+private func EVAL(var ast: MalVal, var _ env: Environment) throws -> MalVal { // malstep(5,6,7,8,9,A)
+ EVAL_level++ // malstep(5,6,7,8,9,A)
+ defer { EVAL_level-- } // malstep(5,6,7,8,9,A)
+ guard EVAL_level <= EVAL_leval_max else { // malstep(5,6,7,8,9,A)
+ try throw_error("Recursing too many levels (> \(EVAL_leval_max))") // malstep(5,6,7,8,9,A)
} // malstep(5,6,7,8,9,A)
// malstep(5,6,7,8,9,A)
if DEBUG_EVAL { // malstep(5,6,7,8,9,A)
- indent = prefix(INDENT_TEMPLATE, EVAL_level) // malstep(5,6,7,8,9,A)
+ indent = substring(INDENT_TEMPLATE, 0, EVAL_level) // malstep(5,6,7,8,9,A)
} // malstep(5,6,7,8,9,A)
// malstep(5,6,7,8,9,A)
while true { // malstep(5,6,7,8,9,A)
- if is_error(ast) { return ast } // malstep(2,3,4,5,6,7,8,9,A)
- if DEBUG_EVAL { println("\(indent)> \(ast)") } // malstep(5,6,7,8,9,A)
+ if DEBUG_EVAL { print("\(indent)> \(ast)") } // malstep(5,6,7,8,9,A)
// malstep(2,3,4,5,6,7,8,9,A)
if !is_list(ast) { // malstep(2,3,4,5,6,7,8,9,A)
// malstep(2,3,4,5,6,7,8,9,A)
// Not a list -- just evaluate and return. // malstep(2,3,4,5,6,7,8,9,A)
// malstep(2,3,4,5,6,7,8,9,A)
- let answer = eval_ast(ast, env) // malstep(2,3,4,5,6,7,8,9,A)
- if DEBUG_EVAL { println("\(indent)>>> \(answer)") } // malstep(5,6,7,8,9,A)
+ let answer = try eval_ast(ast, env) // malstep(2,3,4,5,6,7,8,9,A)
+ if DEBUG_EVAL { print("\(indent)>>> \(answer)") } // malstep(5,6,7,8,9,A)
return answer // malstep(2,3,4,5,6,7,8,9,A)
} // malstep(2,3,4,5,6,7,8,9,A)
// malstep(2,3,4,5,6,7,8,9,A)
// Special handling if it's a list. // malstep(2,3,4,5,6,7,8,9,A)
// malstep(2,3,4,5,6,7,8,9,A)
- var list = ast as! MalList // malstep(2,3,4,5,6,7,8,9,A)
- ast = macroexpand(ast, env) // malstep(8,9,A)
+ let list = as_list(ast) // malstep(2,3,4,5,6,7)
+ var list = as_list(ast) // malstep(8,9,A)
+ ast = try macroexpand(ast, env) // malstep(8,9,A)
if !is_list(ast) { return ast } // malstep(8,9,A)
- list = ast as! MalList // malstep(8,9,A)
+ list = as_list(ast) // malstep(8,9,A)
// malstep(8,9,A)
- if DEBUG_EVAL { println("\(indent)>. \(list)") } // malstep(5,6,7,8,9,A)
+ if DEBUG_EVAL { print("\(indent)>. \(list)") } // malstep(5,6,7,8,9,A)
// malstep(2,3,4,5,6,7,8,9,A)
if list.isEmpty { // malstep(2,3,4,5,6,7,8,9,A)
- return list // malstep(2,3,4,5,6,7,8,9,A)
+ return ast // malstep(2,3,4,5,6,7,8,9,A)
} // malstep(2,3,4,5,6,7,8,9,A)
// malstep(2,3,4,5,6,7,8,9,A)
// Check for special forms, where we want to check the operation // malstep(3,4,5,6,7,8,9,A)
// before evaluating all of the parameters. // malstep(3,4,5,6,7,8,9,A)
// malstep(3,4,5,6,7,8,9,A)
let arg0 = list.first() // malstep(3,4,5,6,7,8,9,A)
- if is_symbol(arg0) { // malstep(3,4,5,6,7,8,9,A)
- var res: TCOVal // malstep(5,6,7,8,9,A)
- let fn_symbol = arg0 as! MalSymbol // malstep(3,4,5,6,7,8,9,A)
+ if let fn_symbol = as_symbolQ(arg0) { // malstep(3,4,5,6,7,8,9,A)
+ let res: TCOVal // malstep(5,6,7,8,9,A)
// malstep(3,4,5,6,7,8,9,A)
switch fn_symbol { // malstep(3,4,5,6,7,8,9,A)
- case kSymbolDef: return eval_def(list, env) // malstep(3,4)
- case kSymbolDef: res = eval_def(list, env) // malstep(5,6,7,8,9,A)
- case kSymbolDefMacro: res = eval_def(list, env) // malstep(8,9,A)
- case kSymbolLet: return eval_let(list, env) // malstep(3,4)
- case kSymbolLet: res = eval_let(list, env) // malstep(5,6,7,8,9,A)
- case kSymbolDo: return eval_do(list, env) // malstep(4)
- case kSymbolDo: res = eval_do(list, env) // malstep(5,6,7,8,9,A)
- case kSymbolIf: return eval_if(list, env) // malstep(4)
- case kSymbolIf: res = eval_if(list, env) // malstep(5,6,7,8,9,A)
- case kSymbolFn: return eval_fn(list, env) // malstep(4)
- case kSymbolFn: res = eval_fn(list, env) // malstep(5,6,7,8,9,A)
- case kSymbolQuote: res = eval_quote(list, env) // malstep(7,8,9,A)
- case kSymbolQuasiQuote: res = eval_quasiquote(list, env) // malstep(7,8,9,A)
- case kSymbolMacroExpand: res = eval_macroexpand(list, env) // malstep(8,9,A)
- case kSymbolTry: res = eval_try(list, env) // malstep(9,A)
+ case kSymbolDef: return try eval_def(list, env) // malstep(3,4)
+ case kSymbolDef: res = try eval_def(list, env) // malstep(5,6,7,8,9,A)
+ case kSymbolDefMacro: res = try eval_def(list, env) // malstep(8,9,A)
+ case kSymbolLet: return try eval_let(list, env) // malstep(3,4)
+ case kSymbolLet: res = try eval_let(list, env) // malstep(5,6,7,8,9,A)
+ case kSymbolDo: return try eval_do(list, env) // malstep(4)
+ case kSymbolDo: res = try eval_do(list, env) // malstep(5,6,7,8,9,A)
+ case kSymbolIf: return try eval_if(list, env) // malstep(4)
+ case kSymbolIf: res = try eval_if(list, env) // malstep(5,6,7,8,9,A)
+ case kSymbolFn: return try eval_fn(list, env) // malstep(4)
+ case kSymbolFn: res = try eval_fn(list, env) // malstep(5,6,7,8,9,A)
+ case kSymbolQuote: res = try eval_quote(list, env) // malstep(7,8,9,A)
+ case kSymbolQuasiQuote: res = try eval_quasiquote(list, env) // malstep(7,8,9,A)
+ case kSymbolMacroExpand: res = try eval_macroexpand(list, env) // malstep(8,9,A)
+ case kSymbolTry: res = try eval_try(list, env) // malstep(9,A)
default: break // malstep(3,4)
default: res = TCOVal() // malstep(5,6,7,8,9,A)
} // malstep(3,4,5,6,7,8,9,A)
// malstep(3,4,5,6,7,8,9,A)
// Standard list to be applied. Evaluate all the elements first. // malstep(2,3,4,5,6,7,8,9,A)
// malstep(2,3,4,5,6,7,8,9,A)
- let eval = eval_ast(ast, env) // malstep(2,3,4,5,6,7,8,9,A)
- if is_error(eval) { return eval } // malstep(2,3,4,5,6,7,8,9,A)
+ let eval = try eval_ast(ast, env) // malstep(2,3,4,5,6,7,8,9,A)
// malstep(2,3,4,5,6,7,8,9,A)
// The result had better be a list and better be non-empty. // malstep(2,3,4,5,6,7,8,9,A)
// malstep(2,3,4,5,6,7,8,9,A)
- let eval_list = eval as! MalList // malstep(2,3,4,5,6,7,8,9,A)
+ let eval_list = as_list(eval) // malstep(2,3,4,5,6,7,8,9,A)
if eval_list.isEmpty { // malstep(2,3,4,5,6,7,8,9,A)
- return eval_list // malstep(2,3,4,5,6,7,8,9,A)
+ return eval // malstep(2,3,4,5,6,7,8,9,A)
} // malstep(2,3,4,5,6,7,8,9,A)
// malstep(2,3,4,5,6,7,8,9,A)
- if DEBUG_EVAL { println("\(indent)>> \(eval)") } // malstep(5,6,7,8,9,A)
+ if DEBUG_EVAL { print("\(indent)>> \(eval)") } // malstep(5,6,7,8,9,A)
// malstep(5,6,7,8,9,A)
// Get the first element of the list and execute it. // malstep(2,3,4,5,6,7,8,9,A)
// malstep(2,3,4,5,6,7,8,9,A)
let first = eval_list.first() // malstep(2,3,4,5,6,7,8,9,A)
- let rest = eval_list.rest() // malstep(2,3,4,5,6,7,8,9,A)
+ let rest = as_sequence(eval_list.rest()) // malstep(2,3,4,5,6,7,8,9,A)
// malstep(2,3,4,5,6,7,8,9,A)
- if is_builtin(first) { // malstep(2,3,4,5,6,7,8,9,A)
- let fn = first as! MalBuiltin // malstep(2,3,4,5,6,7,8,9,A)
- let answer = fn.apply(rest) // malstep(2,3,4,5,6,7,8,9,A)
- if DEBUG_EVAL { println("\(indent)>>> \(answer)") } // malstep(5,6,7,8,9,A)
+ if let fn = as_builtinQ(first) { // malstep(2,3,4,5,6,7,8,9,A)
+ let answer = try fn.apply(rest) // malstep(2,3,4,5,6,7,8,9,A)
+ if DEBUG_EVAL { print("\(indent)>>> \(answer)") } // malstep(5,6,7,8,9,A)
return answer // malstep(2,3,4,5,6,7,8,9,A)
- } else if is_closure(first) { // malstep(4,5,6,7,8,9,A)
- let fn = first as! MalClosure // malstep(4,5,6,7,8,9,A)
- var new_env = Environment(outer: fn.env) // malstep(4,5,6,7,8,9,A)
- let result = new_env.set_bindings(fn.args, with_exprs:rest) // malstep(4,5,6,7,8,9,A)
- if is_error(result) { return result } // malstep(4,5,6,7,8,9,A)
+ } else if let fn = as_closureQ(first) { // malstep(4,5,6,7,8,9,A)
+ let new_env = Environment(outer: fn.env) // malstep(4,5,6,7,8,9,A)
+ let _ = try new_env.set_bindings(fn.args, with_exprs: rest) // malstep(4,5,6,7,8,9,A)
if TCO { // malstep(5,6,7,8,9,A)
env = new_env // malstep(5,6,7,8,9,A)
ast = fn.body // malstep(5,6,7,8,9,A)
continue // malstep(5,6,7,8,9,A)
} // malstep(5,6,7,8,9,A)
- let answer = EVAL(fn.body, new_env) // malstep(4,5,6,7,8,9,A)
- if DEBUG_EVAL { println("\(indent)>>> \(answer)") } // malstep(5,6,7,8,9,A)
+ let answer = try EVAL(fn.body, new_env) // malstep(4,5,6,7,8,9,A)
+ if DEBUG_EVAL { print("\(indent)>>> \(answer)") } // malstep(5,6,7,8,9,A)
return answer // malstep(4,5,6,7,8,9,A)
} // malstep(2,3,4,5,6,7,8,9,A)
// malstep(2,3,4,5,6,7,8,9,A)
// The first element wasn't a function to be executed. Return an // malstep(2,3,4,5,6,7,8,9,A)
// error saying so. // malstep(2,3,4,5,6,7,8,9,A)
// malstep(2,3,4,5,6,7,8,9,A)
- return MalError(message: "first list item does not evaluate to a function: \(first)") // malstep(2,3,4,5,6,7,8,9,A)
+ try throw_error("first list item does not evaluate to a function: \(first)") // malstep(2,3,4,5,6,7,8,9,A)
} // malstep(5,6,7,8,9,A)
} // malstep(2,3,4,5,6,7,8,9,A)
// malstep(0,1,2,3,4,5,6,7,8,9,A)
// Convert the value into a human-readable string for printing. // malstep(0,1,2,3,4,5,6,7,8,9,A)
// // malstep(0,1,2,3,4,5,6,7,8,9,A)
-func PRINT(exp: String) -> String { // malstep(0)
+private func PRINT(exp: String) -> String { // malstep(0)
return exp // malstep(0)
} // malstep(0)
-func PRINT(exp: MalVal) -> String? { // malstep(1,2,3,4,5,6,7,8,9,A)
- if is_error(exp) { return nil } // malstep(1,2,3,4,5,6,7,8,9,A)
+private func PRINT(exp: MalVal) -> String { // malstep(1,2,3,4,5,6,7,8,9,A)
return pr_str(exp, true) // malstep(1,2,3,4,5,6,7,8,9,A)
} // malstep(1,2,3,4,5,6,7,8,9,A)
// malstep(0,1,2,3,4,5,6,7,8,9,A)
// Perform the READ and EVAL steps. Useful for when you don't care about the // malstep(0,1,2,3,4,5,6,7,8,9,A)
// printable result. // malstep(0,1,2,3,4,5,6,7,8,9,A)
// // malstep(0,1,2,3,4,5,6,7,8,9,A)
-func RE(text: String) -> String { // malstep(0)
+private func RE(text: String) -> String { // malstep(0)
let ast = READ(text) // malstep(0)
let exp = EVAL(ast) // malstep(0)
return exp // malstep(0)
} // malstep(0)
-func RE(text: String) -> MalVal? { // malstep(1)
-func RE(text: String, env: Environment) -> MalVal? { // malstep(2,3,4,5,6,7,8,9,A)
- if text.isEmpty { return nil } // malstep(1,2,3,4,5,6,7,8,9,A)
- let ast = READ(text) // malstep(1,2,3,4,5,6,7,8,9,A)
- if is_error(ast) { // malstep(1,2,3,4,5,6,7,8,9,A)
- println("Error parsing input: \(ast)") // malstep(1,2,3,4,5,6,7,8,9,A)
- return nil // malstep(1,2,3,4,5,6,7,8,9,A)
- } // malstep(1,2,3,4,5,6,7,8,9,A)
- let exp = EVAL(ast) // malstep(1)
- let exp = EVAL(ast, env) // malstep(2,3,4,5,6,7,8,9,A)
- if is_error(exp) { // malstep(1,2,3,4,5,6,7,8,9,A)
- println("Error evaluating input: \(exp)") // malstep(1,2,3,4,5,6,7,8,9,A)
- return nil // malstep(1,2,3,4,5,6,7,8,9,A)
+private func RE(text: String) -> MalVal? { // malstep(1)
+private func RE(text: String, _ env: Environment) -> MalVal? { // malstep(2,3,4,5,6,7,8,9,A)
+ if !text.isEmpty { // malstep(1,2,3,4,5,6,7,8,9,A)
+ do { // malstep(1,2,3,4,5,6,7,8,9,A)
+ let ast = try READ(text) // malstep(1,2,3,4,5,6,7,8,9,A)
+ do { // malstep(2,3,4,5,6,7,8,9,A)
+ return EVAL(ast) // malstep(1)
+ return try EVAL(ast, env) // malstep(2,3,4,5,6,7,8,9,A)
+ } catch let error as MalException { // malstep(2,3,4,5,6,7,8,9,A)
+ print("Error evaluating input: \(error)") // malstep(2,3,4,5,6,7,8,9,A)
+ } catch { // malstep(2,3,4,5,6,7,8,9,A)
+ print("Error evaluating input: \(error)") // malstep(2,3,4,5,6,7,8,9,A)
+ } // malstep(2,3,4,5,6,7,8,9,A)
+ } catch let error as MalException { // malstep(1,2,3,4,5,6,7,8,9,A)
+ print("Error parsing input: \(error)") // malstep(1,2,3,4,5,6,7,8,9,A)
+ } catch { // malstep(1,2,3,4,5,6,7,8,9,A)
+ print("Error parsing input: \(error)") // malstep(1,2,3,4,5,6,7,8,9,A)
+ } // malstep(1,2,3,4,5,6,7,8,9,A)
} // malstep(1,2,3,4,5,6,7,8,9,A)
- return exp // malstep(1,2,3,4,5,6,7,8,9,A)
+ return nil // malstep(1,2,3,4,5,6,7,8,9,A)
} // malstep(1,2,3,4,5,6,7,8,9,A)
// malstep(0,1,2,3,4,5,6,7,8,9,A)
//
// Perform the full READ/EVAL/PRINT, returning a printable string. // malstep(0,1,2,3,4,5,6,7,8,9,A)
// // malstep(0,1,2,3,4,5,6,7,8,9,A)
-func REP(text: String) -> String { // malstep(0)
+private func REP(text: String) -> String { // malstep(0)
let exp = RE(text) // malstep(0)
return PRINT(exp) // malstep(0)
} // malstep(0)
-func REP(text: String) -> String? { // malstep(1)
+private func REP(text: String) -> String? { // malstep(1)
let exp = RE(text) // malstep(1)
if exp == nil { return nil } // malstep(1)
return PRINT(exp!) // malstep(1)
} // malstep(1)
-func REP(text: String, env: Environment) -> String? { // malstep(2,3,4,5,6,7,8,9,A)
+private func REP(text: String, _ env: Environment) -> String? { // malstep(2,3,4,5,6,7,8,9,A)
let exp = RE(text, env) // malstep(2,3,4,5,6,7,8,9,A)
if exp == nil { return nil } // malstep(2,3,4,5,6,7,8,9,A)
return PRINT(exp!) // malstep(2,3,4,5,6,7,8,9,A)
// Perform the full REPL. // malstep(0,1,2,3,4,5,6,7,8,9,A)
// // malstep(0,1,2,3,4,5,6,7,8,9,A)
-func REPL() { // malstep(0)
+private func REPL() { // malstep(0)
while true { // malstep(0)
if let text = _readline("user> ") { // malstep(0)
- println("\(REP(text))") // malstep(0)
+ print("\(REP(text))") // malstep(0)
} else { // malstep(0)
- println() // malstep(0)
+ print("") // malstep(0)
break // malstep(0)
} // malstep(0)
} // malstep(0)
} // malstep(0)
-func REPL() { // malstep(1)
+private func REPL() { // malstep(1)
while true { // malstep(1)
if let text = _readline("user> ") { // malstep(1)
if let output = REP(text) { // malstep(1)
- println("\(output)") // malstep(1)
+ print("\(output)") // malstep(1)
} // malstep(1)
} else { // malstep(1)
- println() // malstep(1)
+ print("") // malstep(1)
break // malstep(1)
} // malstep(1)
} // malstep(1)
} // malstep(1)
-func REPL(env: Environment) { // malstep(2,3,4,5,6,7,8,9,A)
+private func REPL(env: Environment) { // malstep(2,3,4,5,6,7,8,9,A)
while true { // malstep(2,3,4,5,6,7,8,9,A)
if let text = _readline("user> ") { // malstep(2,3,4,5,6,7,8,9,A)
if let output = REP(text, env) { // malstep(2,3,4,5,6,7,8,9,A)
- println("\(output)") // malstep(2,3,4,5,6,7,8,9,A)
+ print("\(output)") // malstep(2,3,4,5,6,7,8,9,A)
} // malstep(2,3,4,5,6,7,8,9,A)
} else { // malstep(2,3,4,5,6,7,8,9,A)
- println() // malstep(2,3,4,5,6,7,8,9,A)
+ print("") // malstep(2,3,4,5,6,7,8,9,A)
break // malstep(2,3,4,5,6,7,8,9,A)
} // malstep(2,3,4,5,6,7,8,9,A)
} // malstep(2,3,4,5,6,7,8,9,A)
// taken as a script to execute. If one exists, it is executed in lieu of // malstep(6,7,8,9,A)
// running the REPL. // malstep(6,7,8,9,A)
// // malstep(6,7,8,9,A)
-func process_command_line(args:[String], env:Environment) -> Bool { // malstep(6,7,8,9,A)
- var argv = MalList() // malstep(6,7,8,9,A)
+private func process_command_line(args: [String], _ env: Environment) -> Bool { // malstep(6,7,8,9,A)
+ var argv = make_list() // malstep(6,7,8,9,A)
if args.count > 2 { // malstep(6,7,8,9,A)
let args1 = args[2..<args.count] // malstep(6,7,8,9,A)
- let args2 = args1.map { MalString(unescaped: $0) as MalVal } // malstep(6,7,8,9,A)
+ let args2 = args1.map { make_string($0) } // malstep(6,7,8,9,A)
let args3 = [MalVal](args2) // malstep(6,7,8,9,A)
- argv = MalList(array: args3) // malstep(6,7,8,9,A)
+ argv = make_list(args3) // malstep(6,7,8,9,A)
} // malstep(6,7,8,9,A)
env.set(kSymbolArgv, argv) // malstep(6,7,8,9,A)
// malstep(6,7,8,9,A)
} // malstep(6,7,8,9,A)
// malstep(6,7,8,9,A)
func main() { // malstep(0,1,2,3,4,5,6,7,8,9,A)
- var env = Environment(outer: nil) // malstep(2,3,4,5,6,7,8,9,A)
+ let env = Environment(outer: nil) // malstep(2,3,4,5,6,7,8,9,A)
// malstep(2,3,4,5,6,7,8,9,A)
load_history_file() // malstep(0,1,2,3,4,5,6,7,8,9,A)
load_builtins(env) // malstep(2,3,4,5,6,7,8,9,A)
// malstep(2,3,4,5,6,7,8,9,A)
- RE("(def! *host-language* \"swift\")", env); // malstep(A)
+ RE("(def! *host-language* \"swift\")", env) // malstep(A)
RE("(def! not (fn* (a) (if a false true)))", env) // malstep(4,5,6,7,8,9,A)
RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", env) // malstep(6,7,8,9,A)
RE("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) " + // malstep(8,9,A)
RE("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) " + // malstep(8,9,A)
"`(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))", env) // malstep(8,9,A)
// malstep(6,7,8,9,A)
- env.set(kSymbolEval, MalBuiltin(function: { // malstep(6,7,8,9,A)
- unwrap($0) { // malstep(6,7,8,9,A)
- (ast:MalVal) -> MalVal in // malstep(6,7,8,9,A)
- EVAL(ast, env) // malstep(6,7,8,9,A)
+ env.set(kSymbolEval, make_builtin({ // malstep(6,7,8,9,A)
+ try! unwrap_args($0) { // malstep(6,7,8,9,A)
+ (ast: MalVal) -> MalVal in // malstep(6,7,8,9,A)
+ try EVAL(ast, env) // malstep(6,7,8,9,A)
} // malstep(6,7,8,9,A)
})) // malstep(6,7,8,9,A)
// malstep(6,7,8,9,A)
REPL() // malstep(0,1)
REPL(env) // malstep(2,3,4,5)
if process_command_line(Process.arguments, env) { // malstep(6,7,8,9,A)
- RE("(println (str \"Mal [\" *host-language*\"]\"))", env); // malstep(A)
+ RE("(println (str \"Mal [\" *host-language*\"]\"))", env) // malstep(A)
REPL(env) // malstep(6,7,8,9,A)
} // malstep(6,7,8,9,A)
// malstep(2,3,4,5,6,7,8,9,A)
+++ /dev/null
-//******************************************************************************
-// MAL - types
-//******************************************************************************
-//
-// CLASS TREE: NOTES:
-// --------------------- ----------------------------------------------------
-// MalVal
-// ├── MalNil ┐
-// ├── MalTrue │ May make these subclasses of MalConstant
-// ├── MalFalse ┘
-// ├── MalInteger ┐ May make these subclasses of MalNumber along
-// ├── MalFloat ┘ with MalRatio and MalComplex
-// ├── MalSymbol
-// ├── MalKeyword
-// ├── MalString
-// ├── MalSequence Allows us to use either List or Vector in the same context.
-// │ ├── MalList
-// │ └── MalVector
-// ├── MalHashMap
-// ├── MalAtom
-// ├── MalFunction Allows us to uniformly invoke either a built-in or a closure.
-// │ ├── MalClosure
-// │ └── MalBuiltin
-// ├── MalComment
-// └── MalError Indicates an error in parsing or executing.
-//
-import Foundation
-
-// ===== MalVal =====
-
-var MalValPrintReadably = true
-
-class MalVal : Printable, Hashable {
- enum MalType : String {
- case TypeUnknown = "Unknown"
- case TypeNil = "Nil"
- case TypeTrue = "True"
- case TypeFalse = "False"
- case TypeInteger = "Integer"
- case TypeFloat = "Float"
- case TypeSymbol = "Symbol"
- case TypeKeyword = "Keyword"
- case TypeString = "String"
- case TypeList = "List"
- case TypeVector = "Vector"
- case TypeHashMap = "HashMap"
- case TypeAtom = "Atom"
- case TypeClosure = "Closure"
- case TypeBuiltin = "Builtin"
- case TypeComment = "Comment"
- case TypeError = "Error"
- }
-
- init(meta: MalVal? = nil) { self.meta = meta }
- init(other: MalVal) { self.meta = other.meta }
- func clone() -> MalVal { return MalVal(other:self) }
- var type: MalType { return .TypeUnknown }
- var description: String { return type.rawValue }
- var hashValue: Int { return description.hashValue }
- var meta: MalVal?
- //class var print_readably = true // Swift does not yet support class variables.
-}
-
-func is_nil(v: MalVal) -> Bool { return v.type == .TypeNil }
-func is_true(v: MalVal) -> Bool { return v.type == .TypeTrue }
-func is_false(v: MalVal) -> Bool { return v.type == .TypeFalse }
-func is_integer(v: MalVal) -> Bool { return v.type == .TypeInteger }
-func is_float(v: MalVal) -> Bool { return v.type == .TypeFloat }
-func is_symbol(v: MalVal) -> Bool { return v.type == .TypeSymbol }
-func is_keyword(v: MalVal) -> Bool { return v.type == .TypeKeyword }
-func is_string(v: MalVal) -> Bool { return v.type == .TypeString }
-func is_list(v: MalVal) -> Bool { return v.type == .TypeList }
-func is_vector(v: MalVal) -> Bool { return v.type == .TypeVector }
-func is_hashmap(v: MalVal) -> Bool { return v.type == .TypeHashMap }
-func is_atom(v: MalVal) -> Bool { return v.type == .TypeAtom }
-func is_closure(v: MalVal) -> Bool { return v.type == .TypeClosure }
-func is_builtin(v: MalVal) -> Bool { return v.type == .TypeBuiltin }
-func is_comment(v: MalVal) -> Bool { return v.type == .TypeComment }
-func is_error(v: MalVal) -> Bool { return v.type == .TypeError }
-
-func is_truthy(v: MalVal) -> Bool { return !is_falsey(v) }
-func is_falsey(v: MalVal) -> Bool { let type = v.type; return type == .TypeNil || type == .TypeFalse }
-func is_number(v: MalVal) -> Bool { let type = v.type; return type == .TypeInteger || type == .TypeFloat }
-func is_sequence(v: MalVal) -> Bool { let type = v.type; return type == .TypeList || type == .TypeVector }
-func is_function(v: MalVal) -> Bool { let type = v.type; return type == .TypeClosure || type == .TypeBuiltin }
-
-func == (left: MalVal, right: MalVal) -> Bool {
- // Special case lists/vectors, since they are different types that are
- // nonetheless comparable.
- if is_sequence(left) && is_sequence(right) {
- let left_seq = left as! MalSequence
- let right_seq = right as! MalSequence
- return left_seq == right_seq
- }
-
- if left.type != right.type {
- return false
- }
-
- switch left.type {
- case .TypeUnknown: return false
- case .TypeNil: return (left as! MalNil) == (right as! MalNil)
- case .TypeTrue: return (left as! MalTrue) == (right as! MalTrue)
- case .TypeFalse: return (left as! MalFalse) == (right as! MalFalse)
- case .TypeInteger: return (left as! MalInteger) == (right as! MalInteger)
- case .TypeFloat: return (left as! MalFloat) == (right as! MalFloat)
- case .TypeSymbol: return (left as! MalSymbol) == (right as! MalSymbol)
- case .TypeKeyword: return (left as! MalKeyword) == (right as! MalKeyword)
- case .TypeString: return (left as! MalString) == (right as! MalString)
- case .TypeList: return (left as! MalList) == (right as! MalList)
- case .TypeVector: return (left as! MalVector) == (right as! MalVector)
- case .TypeHashMap: return (left as! MalHashMap) == (right as! MalHashMap)
- case .TypeAtom: return (left as! MalAtom) == (right as! MalAtom)
- case .TypeClosure: return (left as! MalClosure) == (right as! MalClosure)
- case .TypeBuiltin: return (left as! MalBuiltin) == (right as! MalBuiltin)
- case .TypeComment: return (left as! MalComment) == (right as! MalComment)
- case .TypeError: return (left as! MalError) == (right as! MalError)
- }
-}
-
-func != (left: MalVal, right: MalVal) -> Bool {
- return !(left == right)
-}
-
-// ===== MalNil =====
-
-class MalNil: MalVal {
- override func clone() -> MalVal { return MalNil(other:self) }
- override var type: MalType { return .TypeNil }
- override var description: String { return "nil" }
-}
-func == (left: MalNil, right: MalNil) -> Bool { return true }
-
-// ===== MalTrue =====
-
-class MalTrue: MalVal {
- override func clone() -> MalVal { return MalTrue(other:self) }
- override var type: MalType { return .TypeTrue }
- override var description: String { return "true" }
-}
-func == (left: MalTrue, right: MalTrue) -> Bool { return true }
-
-// ===== MalFalse =====
-
-class MalFalse: MalVal {
- override func clone() -> MalVal { return MalFalse(other:self) }
- override var type: MalType { return .TypeFalse }
- override var description: String { return "false" }
-}
-func == (left: MalFalse, right: MalFalse) -> Bool { return true }
-
-// ===== MalInteger =====
-
-class MalInteger: MalVal {
- init(value: Int64, meta: MalVal? = nil) { self.value = value; super.init(meta: meta) }
- init(other: MalInteger) { self.value = other.value; super.init(other: other) }
- override func clone() -> MalVal { return MalInteger(other:self) }
- override var type: MalType { return .TypeInteger }
- override var description: String { return "\(value)" }
- let value: Int64
-}
-func == (left: MalInteger, right: MalInteger) -> Bool { return left.value == right.value }
-
-// ===== MalFloat =====
-
-class MalFloat: MalVal {
- init(value: Double, meta: MalVal? = nil) { self.value = value; super.init(meta: meta) }
- init(other: MalFloat) { self.value = other.value; super.init(other: other) }
- override func clone() -> MalVal { return MalFloat(other:self) }
- override var type: MalType { return .TypeFloat }
- override var description: String { return "\(value)" }
- let value: Double
-}
-func == (left: MalFloat, right: MalFloat) -> Bool { return left.value == right.value }
-
-// ===== MalSymbol =====
-
-var symbolHash = [String:Int]()
-var symbolArray = [String]()
-
-private func indexForSymbol(s: String) -> Int {
- if let i = symbolHash[s] {
- return i
- }
-
- symbolArray.append(s)
- symbolHash[s] = symbolArray.count - 1
- return symbolArray.count - 1
-}
-
-private func symbolForIndex(i: Int) -> String {
- return symbolArray[i]
-}
-
-class MalSymbol: MalVal, Hashable {
- init(symbol: String, meta: MalVal? = nil) { self.index = indexForSymbol(symbol); super.init(meta: meta) }
- init(other: MalSymbol) { self.index = other.index; super.init(other: other) }
- override func clone() -> MalVal { return MalSymbol(other:self) }
- override var type: MalType { return .TypeSymbol }
- override var description: String { return symbolForIndex(self.index) }
- override var hashValue: Int { return self.index }
- let index: Int
-}
-func == (left: MalSymbol, right: MalSymbol) -> Bool { return left.index == right.index }
-
-// ===== MalKeyword =====
-
-class MalKeyword: MalVal {
- typealias Keyword = String
- init(keyword: Keyword, meta: MalVal? = nil) { self.value = keyword; super.init(meta: meta) }
- init(other: MalKeyword) { self.value = other.value; super.init(other: other) }
- override func clone() -> MalVal { return MalKeyword(other:self) }
- override var type: MalType { return .TypeKeyword }
- override var description: String { return ":\(value)" }
- let value: Keyword
-}
-func == (left: MalKeyword, right: MalKeyword) -> Bool { return left.value == right.value }
-
-// ===== MalString =====
-
-class MalString: MalVal {
- init(escaped value: String, meta: MalVal? = nil) { self.value = MalString.unescape(value); super.init(meta: meta) } // String is quoted, and special chars are escaped with \.
- init(unescaped value: String, meta: MalVal? = nil) { self.value = value; super.init(meta: meta) } // String is just the way we like it.
- init(other: MalString) { self.value = other.value; super.init(other: other) }
- override func clone() -> MalVal { return MalString(other:self) }
- override var type: MalType { return .TypeString }
- override var description: String { return MalValPrintReadably ? MalString.escape(value) : value }
- let value: String
-
- class func unescape(s: String) -> String {
- var index = 0
- var prev_is_escape = false
- var str = ""
- for ch in s {
- if index == count(s.utf16) - 1 { continue }
- if index++ == 0 { continue }
- if prev_is_escape {
- prev_is_escape = false;
- if ch == "n" { str += "\n" }
- else if ch == "r" { str += "\r" }
- else if ch == "t" { str += "\t" }
- else { str.append(ch) }
- } else if ch == "\\" {
- prev_is_escape = true
- } else {
- str.append(ch)
- }
- }
- return str
- }
-
- class func escape(s: String) -> String {
- var str = ""
- for ch in s {
- if ch == "\n" { str += "\\n"; continue }
- if ch == "\r" { str += "\\r"; continue }
- if ch == "\t" { str += "\\t"; continue }
- if ch == "\"" || ch == "\\" { str += "\\" }
- str.append(ch)
- }
- str = "\"" + str + "\""
- return str
- }
-}
-func == (left: MalString, right: MalString) -> Bool { return left.value == right.value }
-
-// ===== MalSequence =====
-
-class MalSequence: MalVal, SequenceType {
- init(slice: ArraySlice<MalVal>, meta: MalVal? = nil) { self.slice = slice; super.init(meta: meta) }
- init(other: MalSequence) { self.slice = other.slice; super.init(other: other) }
- override var type: MalType { return .TypeUnknown }
- override var description: String { return "" }
-
- func first() -> MalVal { return !isEmpty ? slice[0] : MalNil() }
- func last() -> MalVal { return !isEmpty ? slice[slice.count - 1] : MalNil() }
- func rest() -> MalSequence { return MalSequence(slice: ArraySlice<MalVal>()) }
- func map<U>(transform: (MalVal) -> U) -> ArraySlice<U> { return slice.map(transform) }
- func reduce<U>(initial: U, combine: (U, MalVal) -> U) -> U { return slice.reduce(initial, combine: combine) }
- var count: Int { return slice.count }
- var isEmpty: Bool { return slice.isEmpty }
- subscript(index: Int) -> MalVal { return index < slice.count ? slice[index] : MalError(message: "index (\(index)) out of range (\(slice.count))") }
- subscript(subRange: Range<Int>) -> ArraySlice<MalVal> { return slice[subRange] }
-
- // SequenceType
- func generate() -> ArraySlice<MalVal>.Generator { return slice.generate() }
-
- let slice: ArraySlice<MalVal>
-}
-func == (left: MalSequence, right: MalSequence) -> Bool {
- if left.count != right.count { return false }
- for var index = 0; index < left.count; ++index { if left[index] != right[index] { return false } }
- return true
-}
-
-// ===== MalList =====
-
-class MalList: MalSequence {
- override init(slice: ArraySlice<MalVal>, meta: MalVal? = nil) { super.init(slice: slice, meta: meta) }
- convenience init(array: [MalVal], meta: MalVal? = nil) { self.init(slice: array[0..<array.count], meta: meta) }
- convenience init(objects: MalVal...) { self.init(array: objects) }
- init(other: MalList) { super.init(other: other) }
- override func clone() -> MalVal { return MalList(other:self) }
- override var type: MalType { return .TypeList }
- override var description: String { return "(" + " ".join(map { $0.description }) + ")" }
- override func rest() -> MalSequence { return !isEmpty ? MalList(slice: slice[1..<slice.count]) : MalList() }
-}
-func == (left: MalList, right: MalList) -> Bool {
- return (left as MalSequence) == (right as MalSequence)
-}
-
-// ===== MalVector =====
-
-class MalVector: MalSequence {
- override init(slice: ArraySlice<MalVal>, meta: MalVal? = nil) { super.init(slice: slice, meta: meta) }
- convenience init(array: [MalVal], meta: MalVal? = nil) { self.init(slice: array[0..<array.count], meta: meta) }
- convenience init(objects: MalVal...) { self.init(array: objects) }
- init(other: MalVector) { super.init(other: other) }
- override func clone() -> MalVal { return MalVector(other:self) }
- override var type: MalType { return .TypeVector }
- override var description: String { return "[" + " ".join(map { $0.description }) + "]" }
- override func rest() -> MalSequence { return !isEmpty ? MalVector(slice: slice[1..<slice.count]) : MalVector() }
-}
-func == (left: MalVector, right: MalVector) -> Bool {
- return (left as MalSequence) == (right as MalSequence)
-}
-
-// ===== MalHashMap =====
-
-class MalHashMap: MalVal, SequenceType {
- convenience override init(meta: MalVal? = nil) { self.init(hash: [MalVal:MalVal](), meta:meta) }
- init(hash: [MalVal:MalVal], meta: MalVal? = nil) { self.hash = hash; super.init(meta: meta) }
- convenience init(slice: ArraySlice<MalVal>, meta: MalVal? = nil) { var hash = [MalVal:MalVal](); for var index = 0; index < slice.count; index += 2 { hash[slice[index]] = slice[index + 1] }; self.init(hash: hash, meta: meta) }
- convenience init(array: Array<MalVal>, meta: MalVal? = nil) { var hash = [MalVal:MalVal](); for var index = 0; index < array.count; index += 2 { hash[array[index]] = array[index + 1] }; self.init(hash: hash, meta: meta) }
- init(other: MalHashMap) { self.hash = other.hash; super.init(other: other) }
- override func clone() -> MalVal { return MalHashMap(other:self) }
- override var type: MalType { return .TypeHashMap }
- override var description: String {
- var a = [String]()
- for (k, v) in hash {
- a.append("\(k.description) \(v.description)")
- }
- let s = " ".join(a)
- return "{\(s)}" }
- func generate() -> DictionaryGenerator<MalVal, MalVal> { return hash.generate() }
- var count: Int { return hash.count }
- var isEmpty: Bool { return hash.isEmpty }
- subscript(key: MalVal) -> MalVal? { return hash[key] }
- let hash: [MalVal:MalVal]
-}
-func == (left: MalHashMap, right: MalHashMap) -> Bool {
- if left.count != right.count { return false }
- var left_index = left.hash.startIndex
- var right_index = right.hash.startIndex
- while left_index != left.hash.endIndex {
- let (left_key, left_value) = left.hash[left_index]
- let (right_key, right_value) = right.hash[right_index]
- if (left_key != right_key) || (left_value != right_value) {
- return false
- }
- left_index = left_index.successor()
- right_index = right_index.successor()
- }
- return true
-}
-
-// ===== MalAtom =====
-
-class MalAtom: MalVal {
- init(object: MalVal, meta: MalVal? = nil) { self.value = object; super.init(meta: meta) }
- init(other: MalAtom) { self.value = other.value; super.init(other: other) }
- override func clone() -> MalVal { return MalAtom(other:self) }
- override var type: MalType { return .TypeAtom }
- override var description: String { return "(atom \(value.description))" }
- var value: MalVal
-}
-func == (left: MalAtom, right: MalAtom) -> Bool { return false }
-
-// ===== MalFunction =====
-
-class MalFunction: MalVal {
- override init(meta: MalVal? = nil) { super.init(meta: meta) }
- init(other: MalFunction) { super.init(other: other) }
- func apply(exprs: MalSequence) -> MalVal { return MalNil() }
-}
-
-// ===== MalClosure =====
-
-class MalClosure: MalFunction {
- typealias Evaluator = (MalVal, Environment) -> MalVal
- init(eval: Evaluator, args: MalSequence, body: MalVal, env: Environment, meta: MalVal? = nil) {
- self.eval = eval;
- self.args = args;
- self.body = body;
- self.env = env;
- self.is_macro = false;
- super.init(meta: meta) }
- init(eval: Evaluator, args: MalSequence, body: MalVal, env: Environment, is_macro: Bool, meta: MalVal? = nil) {
- self.eval = eval;
- self.args = args;
- self.body = body;
- self.env = env;
- self.is_macro = is_macro;
- super.init(meta: meta) }
- init(other: MalClosure) {
- self.eval = other.eval
- self.args = other.args
- self.body = other.body
- self.env = other.env
- self.is_macro = other.is_macro
- super.init(other: other) }
- override func clone() -> MalVal { return MalClosure(other:self) }
- override var type: MalType { return .TypeClosure }
- override var description: String { return "#<Closure>: (fn* \(args.description) \(body.description))" }
- override func apply(exprs: MalSequence) -> MalVal {
- var new_env = Environment(outer: env)
- let result = new_env.set_bindings(args, with_exprs:exprs)
- if is_error(result) {
- return result
- }
- // Calling EVAL indirectly via an 'eval' data member is a bit of a hack.
- // We can't call EVAL directly because this file (types.swift) needs to
- // be used with many different versions of the main MAL file
- // (step[0-10]*.swift), and EVAL is declared differently across those
- // versions. By using this indirection, we avoid that problem.
- return eval(body, new_env)
- }
- let eval: Evaluator
- let args: MalSequence
- let body: MalVal
- let env: Environment
- var is_macro: Bool
-}
-func == (left: MalClosure, right: MalClosure) -> Bool { return false }
-
-// ===== MalBuiltin =====
-
-class MalBuiltin: MalFunction {
- typealias BuiltinSignature = (MalSequence) -> MalVal
- init(function: BuiltinSignature, meta: MalVal? = nil) { self.value = function; super.init(meta: meta) }
- init(other: MalBuiltin) { self.value = other.value; super.init(other: other) }
- override func clone() -> MalVal { return MalBuiltin(other:self) }
- override var type: MalType { return .TypeBuiltin }
- override var description: String { return "#<Builtin>" }
- override func apply(exprs: MalSequence) -> MalVal { return value(exprs) }
- let value: BuiltinSignature!
-}
-func == (left: MalBuiltin, right: MalBuiltin) -> Bool { return false } // Can't compare function references in Swift
-
-// ===== MalComment =====
-
-class MalComment: MalVal {
- init(comment: String, meta: MalVal? = nil) { self.value = comment; super.init(meta: meta) }
- init(other: MalComment) { self.value = other.value; super.init(other: other) }
- override func clone() -> MalVal { return MalComment(other:self) }
- override var type: MalType { return .TypeComment }
- override var description: String { return value }
- let value: String
-}
-func == (left: MalComment, right: MalComment) -> Bool { return false }
-
-// ===== MalError =====
-
-class MalError: MalVal {
- init(message: String, meta: MalVal? = nil) { self.value = MalString(unescaped:message); super.init(meta: meta) }
- init(object: MalVal, meta: MalVal? = nil) { self.value = object; super.init(meta: meta) }
- init(other: MalError) { self.value = other.value; super.init(other: other) }
- override func clone() -> MalVal { return MalError(other:self) }
- override var type: MalType { return .TypeError }
- override var description: String { return value.description }
- let value: MalVal
-}
-func == (left: MalError, right: MalError) -> Bool { return false }
--- /dev/null
+//******************************************************************************
+// MAL - types, implemented as a Swift "class".
+//******************************************************************************
+
+import Foundation
+
+// ==================== Types / Constants / Variables ====================
+
+typealias MalProtocol = protocol<Equatable, CustomStringConvertible, Hashable>
+
+typealias MalIntType = Int64
+typealias MalFloatType = Double
+typealias MalSymbolType = String
+typealias MalKeywordType = String
+typealias MalStringType = String
+typealias MalVectorType = ArraySlice<MalVal>
+typealias MalHashType = Dictionary<MalVal, MalVal>
+
+private let kUnknown = MalUnknown()
+private let kNil = MalNil()
+private let kTrue = MalTrue()
+private let kFalse = MalFalse()
+private let kComment = MalComment()
+
+// ==================== MalVal ====================
+
+class MalVal : MalProtocol {
+ init() {
+ self._meta = nil
+ }
+ init(_ other: MalVal, _ meta: MalVal?) {
+ self._meta = meta
+ }
+ init(_ meta: MalVal?) {
+ self._meta = meta
+ }
+
+ // CustomStringConvertible
+ //
+ var description: String { die() }
+
+ // Hashable
+ //
+ var hashValue: Int { return description.hashValue }
+
+ // MalVal
+ //
+ func clone_with_meta(meta: MalVal) -> MalVal { die() }
+ final var meta: MalVal? { return self._meta }
+
+ let _meta: MalVal?
+}
+
+// Equatable
+//
+let tMalUnknown = class_getName(MalUnknown)
+let tMalNil = class_getName(MalNil)
+let tMalTrue = class_getName(MalTrue)
+let tMalFalse = class_getName(MalFalse)
+let tMalComment = class_getName(MalComment)
+let tMalInteger = class_getName(MalInteger)
+let tMalFloat = class_getName(MalFloat)
+let tMalSymbol = class_getName(MalSymbol)
+let tMalKeyword = class_getName(MalKeyword)
+let tMalString = class_getName(MalString)
+let tMalList = class_getName(MalList)
+let tMalVector = class_getName(MalVector)
+let tMalHashMap = class_getName(MalHashMap)
+let tMalAtom = class_getName(MalAtom)
+let tMalClosure = class_getName(MalClosure)
+let tMalBuiltin = class_getName(MalBuiltin)
+let tMalMacro = class_getName(MalMacro)
+
+func ==(left: MalVal, right: MalVal) -> Bool {
+ let leftClass = object_getClassName(left)
+ let rightClass = object_getClassName(right)
+
+ if leftClass == tMalUnknown && rightClass == tMalUnknown { return as_unknown(left) == as_unknown(right) }
+ if leftClass == tMalNil && rightClass == tMalNil { return as_nil(left) == as_nil(right) }
+ if leftClass == tMalTrue && rightClass == tMalTrue { return as_true(left) == as_true(right) }
+ if leftClass == tMalFalse && rightClass == tMalFalse { return as_false(left) == as_false(right) }
+ if leftClass == tMalComment && rightClass == tMalComment { return as_comment(left) == as_comment(right) }
+ if leftClass == tMalInteger && rightClass == tMalInteger { return as_integer(left) == as_integer(right) }
+ if leftClass == tMalFloat && rightClass == tMalFloat { return as_float(left) == as_float(right) }
+ if leftClass == tMalSymbol && rightClass == tMalSymbol { return as_symbol(left) == as_symbol(right) }
+ if leftClass == tMalKeyword && rightClass == tMalKeyword { return as_keyword(left) == as_keyword(right) }
+ if leftClass == tMalString && rightClass == tMalString { return as_string(left) == as_string(right) }
+ //if leftClass == tMalList && rightClass == tMalList { return as_sequence(left) == as_sequence(right) }
+ //if leftClass == tMalVector && rightClass == tMalVector { return as_sequence(left) == as_sequence(right) }
+ if leftClass == tMalHashMap && rightClass == tMalHashMap { return as_hashmap(left) == as_hashmap(right) }
+ if leftClass == tMalAtom && rightClass == tMalAtom { return as_atom(left) == as_atom(right) }
+ if leftClass == tMalClosure && rightClass == tMalClosure { return as_closure(left) == as_closure(right) }
+ if leftClass == tMalBuiltin && rightClass == tMalBuiltin { return as_builtin(left) == as_builtin(right) }
+ if leftClass == tMalMacro && rightClass == tMalMacro { return as_macro(left) == as_macro(right) }
+ //
+ // Special case lists/vectors, since they are different types that are
+ // nonetheless comparable.
+ if
+ (leftClass == tMalList || leftClass == tMalVector) &&
+ (rightClass == tMalList || rightClass == tMalVector) {
+ return as_sequence(left) == as_sequence(right)
+ }
+
+ return false
+}
+
+func !=(left: MalVal, right: MalVal) -> Bool {
+ return !(left == right)
+}
+
+// ==================== MalUnknown ====================
+
+final class MalUnknown: MalVal {
+ override var description: String { return "unknown" }
+ override func clone_with_meta(meta: MalVal) -> MalVal { return MalUnknown(meta) }
+}
+func ==(left: MalUnknown, right: MalUnknown) -> Bool { return false }
+
+// ==================== MalNil ====================
+
+final class MalNil: MalVal {
+ override var description: String { return "nil" }
+ override func clone_with_meta(meta: MalVal) -> MalVal { return MalNil(meta) }
+}
+func ==(left: MalNil, right: MalNil) -> Bool { return true }
+
+// ==================== MalTrue ====================
+
+final class MalTrue: MalVal {
+ override var description: String { return "true" }
+ override func clone_with_meta(meta: MalVal) -> MalVal { return MalTrue(meta) }
+}
+func ==(left: MalTrue, right: MalTrue) -> Bool { return true }
+
+// ==================== MalFalse ====================
+
+final class MalFalse: MalVal {
+ override var description: String { return "false" }
+ override func clone_with_meta(meta: MalVal) -> MalVal { return MalFalse(meta) }
+}
+func ==(left: MalFalse, right: MalFalse) -> Bool { return true }
+
+// ==================== MalComment ====================
+
+final class MalComment: MalVal {
+ override var description: String { return "Comment" }
+ override func clone_with_meta(meta: MalVal) -> MalVal { return MalComment(meta) }
+}
+
+// Equatable
+//
+func ==(left: MalComment, right: MalComment) -> Bool { return false }
+
+// ==================== MalInteger ====================
+
+final class MalInteger: MalVal {
+ override init() {
+ self._integer = 0
+ super.init()
+ }
+ init(_ other: MalInteger, _ meta: MalVal? = nil) {
+ self._integer = other._integer
+ super.init(other, meta)
+ }
+ init(_ integer: MalIntType) {
+ self._integer = integer
+ super.init()
+ }
+
+ // CustomStringConvertible
+ //
+ override var description: String { return "\(self._integer)" }
+
+ // Hashable
+ //
+ override var hashValue: Int { return Int(self._integer) }
+
+ // MalInteger
+ //
+ override func clone_with_meta(meta: MalVal) -> MalVal { return MalInteger(self, meta) }
+ var integer: MalIntType { return self._integer }
+
+ private let _integer: MalIntType
+}
+
+// Equatable
+//
+func ==(left: MalInteger, right: MalInteger) -> Bool { return left.integer == right.integer }
+
+// ==================== MalFloat ====================
+
+final class MalFloat: MalVal {
+ override init() {
+ self._float = 0
+ super.init()
+ }
+ init(_ other: MalFloat, _ meta: MalVal? = nil) {
+ self._float = other._float
+ super.init(other, meta)
+ }
+ init(_ float: Double) {
+ self._float = float
+ super.init()
+ }
+
+ // CustomStringConvertible
+ //
+ override var description: String { return "\(self._float)" }
+
+ // Hashable
+ //
+ override var hashValue: Int { return Int(self._float) }
+
+ // MalFloat
+ //
+ override func clone_with_meta(meta: MalVal) -> MalVal { return MalFloat(self, meta) }
+ var float: MalFloatType { return self._float }
+
+ private let _float: Double
+}
+
+// Equatable
+//
+func ==(left: MalFloat, right: MalFloat) -> Bool { return left.float == right.float }
+
+// ==================== MalSymbol ====================
+
+private var symbolHash = [MalSymbolType : Int]()
+private var symbolArray = [MalSymbolType]()
+
+private func indexForSymbol(s: MalSymbolType) -> Int {
+ if let i = symbolHash[s] {
+ return i
+ }
+
+ symbolArray.append(s)
+ symbolHash[s] = symbolArray.count - 1
+ return symbolArray.count - 1
+}
+
+private func symbolForIndex(i: Int) -> MalSymbolType {
+ return symbolArray[i]
+}
+
+final class MalSymbol: MalVal {
+ override init() {
+ self._index = indexForSymbol("")
+ super.init()
+ }
+ init(_ other: MalSymbol, _ meta: MalVal? = nil) {
+ self._index = other._index
+ super.init(other, meta)
+ }
+ init(_ symbol: MalSymbolType) {
+ self._index = indexForSymbol(symbol)
+ super.init()
+ }
+
+ // CustomStringConvertible
+ //
+ override var description: String { return symbolForIndex(self._index) }
+
+ // Hashable
+ //
+ override var hashValue: Int { return self._index }
+
+ // MalSymbol
+ override func clone_with_meta(meta: MalVal) -> MalVal { return MalSymbol(self, meta) }
+ var index: Int { return self._index }
+
+ private let _index: Int
+}
+
+// Equatable
+//
+func ==(left: MalSymbol, right: MalSymbol) -> Bool { return left.index == right.index }
+
+// ==================== MalKeyword ====================
+
+final class MalKeyword: MalVal {
+ override init() {
+ self._keyword = ""
+ super.init()
+ }
+ init(_ other: MalKeyword, _ meta: MalVal? = nil) {
+ self._keyword = other._keyword
+ super.init(other, meta)
+ }
+ init(_ keyword: MalKeywordType) {
+ self._keyword = keyword
+ super.init()
+ }
+ init(_ string: MalString) {
+ self._keyword = string.string
+ super.init()
+ }
+
+ // CustomStringConvertible
+ //
+ override var description: String { return self._keyword } // ":" added in pr_str
+
+ // MalKeyword
+ //
+ override func clone_with_meta(meta: MalVal) -> MalVal { return MalKeyword(self, meta) }
+ var keyword: MalKeywordType { return self._keyword }
+
+ private let _keyword: MalKeywordType
+}
+
+// Equatable
+//
+func ==(left: MalKeyword, right: MalKeyword) -> Bool { return left._keyword == right._keyword }
+
+// ==================== MalString ====================
+
+final class MalString: MalVal {
+ override init() {
+ self._string = ""
+ super.init()
+ }
+ init(_ other: MalString, _ meta: MalVal? = nil) {
+ self._string = other._string
+ super.init(other, meta)
+ }
+ init(_ string: MalStringType) {
+ self._string = string
+ super.init()
+ }
+
+ // CustomStringConvertible
+ //
+ override var description: String { return self._string }
+
+ // MalString
+ //
+ override func clone_with_meta(meta: MalVal) -> MalVal { return MalString(self, meta) }
+ var string: MalStringType { return self._string }
+
+ private let _string: MalStringType
+}
+
+// Equatable
+//
+func ==(left: MalString, right: MalString) -> Bool { return left.string == right.string }
+
+// ==================== MalSequence ====================
+
+class MalSequence: MalVal, SequenceType {
+ override init() {
+ self.count = 0
+ self.isEmpty = true
+ super.init()
+ }
+ init(_ other: MalSequence, _ meta: MalVal? = nil) {
+ self.count = other.count
+ self.isEmpty = other.isEmpty
+ super.init(other, meta)
+ }
+ init(_ count: MalIntType, _ isEmpty: Bool) {
+ self.count = count
+ self.isEmpty = isEmpty
+ super.init()
+ }
+
+ // SequenceType
+ //
+ func generate() -> MalVectorType.Generator { die() }
+
+ // MalSequence
+ //
+ var count: MalIntType
+ var isEmpty: Bool
+
+ func first() -> MalVal { die() }
+ func last() -> MalVal { die() }
+ func rest() -> MalVal { die() }
+ func nth(n: MalIntType) throws -> MalVal { die() }
+ func range_from(from: MalIntType, to: MalIntType) -> MalVal { die() }
+ func cons(element: MalVal) -> MalVal { die() }
+ func concat(seq: MalSequence) throws -> MalVal { die() }
+ func conj(seq: MalSequence) throws -> MalVal { die() }
+ func map<U>(@noescape transform: (MalVal) -> U) -> ArraySlice<U> { die() }
+ func reduce<U>(initial: U, @noescape combine: (U, MalVal) -> U) -> U { die() }
+}
+
+// Equatable
+//
+func ==(left: MalSequence, right: MalSequence) -> Bool {
+ if left.count != right.count { return false }
+ var left_gen = left.generate()
+ var right_gen = right.generate()
+ while true {
+ if let left = left_gen.next(), right = right_gen.next() {
+ if left != right {
+ return false
+ }
+ } else {
+ break
+ }
+ }
+ return true
+}
+
+// ==================== MalList ====================
+
+final class MalList: MalSequence {
+ override init() {
+ self._slice = MalVectorType()
+ super.init(MalIntType(self._slice.count), self._slice.isEmpty)
+ }
+ init(_ other: MalList, _ meta: MalVal? = nil) {
+ self._slice = other._slice
+ super.init(other, meta)
+ }
+ init(seq: MalSequence) { // We need the "seq" in order to differentiate it from the previous init()
+ self._slice = seq.reduce(MalVectorType()){ var s = $0; s.append($1); return s }
+ super.init(MalIntType(self._slice.count), self._slice.isEmpty)
+ }
+ init(_ slice: MalVectorType) {
+ self._slice = slice
+ super.init(MalIntType(self._slice.count), self._slice.isEmpty)
+ }
+ init(_ array: Array<MalVal>) {
+ self._slice = array[0..<array.count]
+ super.init(MalIntType(self._slice.count), self._slice.isEmpty)
+ }
+ init<T: CollectionType where T.Generator.Element == MalVal>(_ collection: T) {
+ self._slice = collection.reduce(MalVectorType()){ var s = $0; s.append($1); return s }
+ super.init(MalIntType(self._slice.count), self._slice.isEmpty)
+ }
+
+ // CustomStringConvertible
+ //
+ override var description: String { return "(" + self.map { pr_str($0) }.joinWithSeparator(" ") + ")" }
+
+ // SequenceType
+ //
+ override func generate() -> MalVectorType.Generator { return self._slice.generate() }
+
+ // MalSequence
+ //
+ override func first() -> MalVal { return isEmpty ? make_nil() : try! nth(0) }
+ override func last() -> MalVal { return try! nth(count - 1) }
+ override func rest() -> MalVal { return range_from(MalIntType(1), to: MalIntType(count)) }
+ override func nth(n: MalIntType) throws -> MalVal { guard n < count else { try throw_error("index (\(n)) out of range (\(count))") }; return self._slice[self._slice.startIndex.advancedBy(Int(n))] }
+ override func range_from(from: MalIntType, to: MalIntType) -> MalVal {
+ return from <= to && to <= count
+ ? make_list(self._slice[self._slice.startIndex.advancedBy(Int(from))..<self._slice.startIndex.advancedBy(Int(to))])
+ : make_list()
+ }
+ override func cons(element: MalVal) -> MalVal {
+ var result = self._slice
+ result.insert(element, atIndex: result.startIndex)
+ return make_list(result)
+ }
+ override func concat(seq: MalSequence) throws -> MalVal {
+ var result = self._slice
+ if let list = as_listQ(seq) {
+ result.appendContentsOf(list._slice)
+ } else if let vector = as_vectorQ(seq) {
+ result.appendContentsOf(vector._slice)
+ } else {
+ try throw_error("Expected sequence, got \(seq)")
+ }
+ return make_list(result)
+ }
+ override func conj(seq: MalSequence) throws -> MalVal {
+ var result: Array<MalVal>
+ if let list = as_listQ(seq) {
+ result = list._slice.reverse()
+ } else if let vector = as_vectorQ(seq) {
+ result = vector._slice.reverse()
+ } else {
+ try throw_error("Expected sequence, got \(seq)")
+ }
+ result.appendContentsOf(self._slice)
+ return make_list(result)
+ }
+ override func map<U>(@noescape transform: (MalVal) -> U) -> ArraySlice<U> { return ArraySlice<U>(self._slice.map(transform)) }
+ override func reduce<U>(initial: U, @noescape combine: (U, MalVal) -> U) -> U { return self._slice.reduce(initial, combine: combine) }
+
+ // MalList
+ //
+ override func clone_with_meta(meta: MalVal) -> MalVal { return MalList(self, meta) }
+
+ private let _slice: MalVectorType
+}
+
+// Equatable
+//
+func ==(left: MalList, right: MalList) -> Bool {
+ return as_sequence(left) == as_sequence(right)
+}
+
+// ==================== MalVector ====================
+
+final class MalVector: MalSequence {
+ override init() {
+ self._slice = MalVectorType()
+ super.init(MalIntType(self._slice.count), self._slice.isEmpty)
+ }
+ init(_ other: MalVector, _ meta: MalVal? = nil) {
+ self._slice = other._slice
+ super.init(other, meta)
+ }
+ init(seq: MalSequence) { // We need the "seq" in order to differentiate it from the previous init()
+ self._slice = seq.reduce(MalVectorType()){ var s = $0; s.append($1); return s }
+ super.init(MalIntType(self._slice.count), self._slice.isEmpty)
+ }
+ init(_ slice: MalVectorType) {
+ self._slice = slice
+ super.init(MalIntType(self._slice.count), self._slice.isEmpty)
+ }
+ init(_ array: Array<MalVal>) {
+ self._slice = array[0..<array.count]
+ super.init(MalIntType(self._slice.count), self._slice.isEmpty)
+ }
+ init<T: CollectionType where T.Generator.Element == MalVal>(_ collection: T) {
+ self._slice = collection.reduce(MalVectorType()){ var s = $0; s.append($1); return s }
+ super.init(MalIntType(self._slice.count), self._slice.isEmpty)
+ }
+
+ // CustomStringConvertible
+ //
+ override var description: String { return "[" + self.map { pr_str($0) }.joinWithSeparator(" ") + "]" }
+
+ // SequenceType
+ //
+ override func generate() -> MalVectorType.Generator { return self._slice.generate() }
+
+ // MalSequence
+ //
+ override func first() -> MalVal { return isEmpty ? make_nil() : try! nth(0) }
+ override func last() -> MalVal { return try! nth(count - 1) }
+ override func rest() -> MalVal { return range_from(MalIntType(1), to: MalIntType(count)) }
+ override func nth(n: MalIntType) throws -> MalVal { guard n < count else { try throw_error("index (\(n)) out of range (\(count))") }; return self._slice[self._slice.startIndex.advancedBy(Int(n))] }
+ override func range_from(from: MalIntType, to: MalIntType) -> MalVal {
+ return from <= to && to <= count
+ ? make_list(self._slice[self._slice.startIndex.advancedBy(Int(from))..<self._slice.startIndex.advancedBy(Int(to))]) // Yes, make_list
+ : make_list() // Yes, make_list
+ }
+ override func cons(element: MalVal) -> MalVal {
+ var result = self._slice
+ result.insert(element, atIndex: result.startIndex)
+ return make_list(result) // Yes, make_list
+ }
+ override func concat(seq: MalSequence) throws -> MalVal {
+ var result = self._slice
+ if let list = as_listQ(seq) {
+ result.appendContentsOf(list._slice)
+ } else if let vector = as_vectorQ(seq) {
+ result.appendContentsOf(vector._slice)
+ } else {
+ try throw_error("Expected sequence, got \(seq)")
+ }
+ return make_list(result)
+ }
+ override func conj(seq: MalSequence) throws -> MalVal {
+ var result = self._slice
+ if let list = as_listQ(seq) {
+ result.appendContentsOf(list._slice)
+ } else if let vector = as_vectorQ(seq) {
+ result.appendContentsOf(vector._slice)
+ } else {
+ try throw_error("Expected sequence, got \(seq)")
+ }
+ return make_vector(result)
+ }
+ override func map<U>(@noescape transform: (MalVal) -> U) -> ArraySlice<U> { return ArraySlice<U>(self._slice.map(transform)) }
+ override func reduce<U>(initial: U, @noescape combine: (U, MalVal) -> U) -> U { return self._slice.reduce(initial, combine: combine) }
+
+ // MalVector
+ //
+ override func clone_with_meta(meta: MalVal) -> MalVal { return MalVector(self, meta) }
+
+ private let _slice: MalVectorType
+}
+
+// Equatable
+//
+func ==(left: MalVector, right: MalVector) -> Bool {
+ return as_sequence(left) == as_sequence(right)
+}
+
+// ==================== MalHashMap ====================
+
+final class MalHashMap: MalVal, SequenceType {
+ override init() {
+ self._hash = MalHashType()
+ self.count = MalIntType(self._hash.count)
+ self.isEmpty = self._hash.isEmpty
+ super.init()
+ }
+ init(_ other: MalHashMap, _ meta: MalVal? = nil) {
+ self._hash = other._hash
+ self.count = MalIntType(self._hash.count)
+ self.isEmpty = self._hash.isEmpty
+ super.init(other, meta)
+ }
+ init(_ hash: MalHashType) {
+ self._hash = hash
+ self.count = MalIntType(self._hash.count)
+ self.isEmpty = self._hash.isEmpty
+ super.init()
+ }
+ convenience init(_ seq: MalSequence) {
+ var hash = MalHashType()
+ for var index: MalIntType = 0; index < seq.count; index += 2 {
+ hash[try! seq.nth(index)] = try! seq.nth(index + 1)
+ }
+ self.init(hash)
+ }
+ convenience init<T: CollectionType where T.Generator.Element == MalVal>(_ collection: T) {
+ // TBD: Use SequenceType/generate
+ var hash = MalHashType()
+ for var index = collection.startIndex; index != collection.endIndex; {
+ let key = collection[index++]
+ let value = collection[index++]
+ hash[key] = value
+ }
+ self.init(hash)
+ }
+
+ // CustomStringConvertible
+ //
+ override var description: String {
+ // TBD: Use reduce
+ var a = [String]()
+ for (k, v) in self._hash {
+ a.append("\(pr_str(k)) \(pr_str(v))")
+ }
+ let s = a.joinWithSeparator(" ")
+ return "{\(s)}"
+ }
+
+ // SequenceType
+ //
+ func generate() -> MalHashType.Generator { return self._hash.generate() }
+
+ // MalHashMap
+ //
+ let count: MalIntType
+ let isEmpty: Bool
+ var hash: MalHashType { return self._hash }
+ var keys: MalVal { return make_list(self._hash.keys) }
+ var values: MalVal { return make_list(self._hash.values) }
+
+ override func clone_with_meta(meta: MalVal) -> MalVal { return MalHashMap(self, meta) }
+
+ func value_for(key: MalVal) -> MalVal? {
+ return self._hash[key]
+ }
+
+ private let _hash: MalHashType
+}
+
+// Equatable
+//
+func ==(left: MalHashMap, right: MalHashMap) -> Bool {
+ if left.count != right.count { return false }
+ var left_gen = left.generate()
+ var right_gen = right.generate()
+ while true {
+ if let left = left_gen.next(), let right = right_gen.next() {
+ if left.0 != right.0 || left.1 != right.1 {
+ return false
+ }
+ } else {
+ break
+ }
+ }
+ return true
+}
+
+// ==================== MalAtom ====================
+
+final class MalAtom: MalVal {
+ override init() {
+ self._object = make_nil()
+ super.init()
+ }
+ init(_ other: MalAtom, _ meta: MalVal? = nil) {
+ self._object = other._object
+ super.init(other, meta)
+ }
+ init(object: MalVal) {
+ self._object = object
+ super.init()
+ }
+
+ // CustomStringConvertible
+ //
+ override var description: String { return "(atom \(self._object.description))" }
+
+ // MalAtom
+ //
+ override func clone_with_meta(meta: MalVal) -> MalVal { return MalAtom(self, meta) }
+ var object: MalVal { return self._object }
+
+ func set_object(obj: MalVal) -> MalVal {
+ self._object = obj
+ return obj
+ }
+
+ private var _object: MalVal
+}
+
+// Equatable
+//
+func ==(left: MalAtom, right: MalAtom) -> Bool { return left.object == right.object }
+
+// ==================== MalFunction ====================
+
+class MalFunction: MalVal {
+ override init() {
+ super.init()
+ }
+ init(_ other: MalFunction, _ meta: MalVal? = nil) {
+ super.init(other, meta)
+ }
+
+ // MalFunction
+ //
+ func apply(exprs: MalSequence) throws -> MalVal { die() }
+}
+
+// ==================== MalClosure ====================
+
+final class MalClosure: MalFunction {
+ typealias Evaluator = (MalVal, Environment) throws -> MalVal
+ typealias Parameters = (eval: Evaluator, args: MalSequence, body: MalVal, env: Environment)
+
+ override init() {
+ self._eval = nil
+ self._args = as_sequence(make_list())
+ self._body = make_nil()
+ self._env = Environment(outer: nil)
+ super.init()
+ }
+ init(_ other: MalClosure, _ meta: MalVal? = nil) {
+ self._eval = other._eval
+ self._args = other._args
+ self._body = other._body
+ self._env = other._env
+ super.init(other, meta)
+ }
+ init(_ p: Parameters) {
+ self._eval = p.eval
+ self._args = p.args
+ self._body = p.body
+ self._env = p.env
+ super.init()
+ }
+
+ // CustomStringConvertible
+ //
+ override var description: String { return "#<Closure>: (fn* \(self._args.description) \(self._body.description))" }
+
+ // MalFunction
+ //
+ override func apply(exprs: MalSequence) throws -> MalVal {
+ let new_env = Environment(outer: self._env)
+ let _ = try new_env.set_bindings(self._args, with_exprs: exprs)
+ // Calling EVAL indirectly via an 'eval' data member is a bit of a hack.
+ // We can't call EVAL directly because this file (types.swift) needs to
+ // be used with many different versions of the main MAL file
+ // (step[0-10]*.swift), and EVAL is declared differently across those
+ // versions. By using this indirection, we avoid that problem.
+ return try self._eval(self._body, new_env)
+ }
+
+ // MalClosure
+ //
+ override func clone_with_meta(meta: MalVal) -> MalVal { return MalClosure(self, meta) }
+
+ var args: MalSequence { return self._args }
+ var body: MalVal { return self._body }
+ var env: Environment { return self._env }
+
+ private let _eval: Evaluator!
+ private let _args: MalSequence
+ private let _body: MalVal
+ private let _env: Environment
+}
+
+// Equatable
+//
+func ==(left: MalClosure, right: MalClosure) -> Bool { return false }
+
+// ==================== MalBuiltin ====================
+
+final class MalBuiltin: MalFunction {
+ typealias Signature = (MalSequence) throws -> MalVal
+
+ override init() {
+ self._fn = nil
+ super.init()
+ }
+ init(_ other: MalBuiltin, _ meta: MalVal? = nil) {
+ self._fn = other._fn
+ super.init(other, meta)
+ }
+ init(_ fn: Signature) {
+ self._fn = fn
+ super.init()
+ }
+
+ // CustomStringConvertible
+ //
+ override var description: String { return "#<Builtin>" }
+
+ // MalFunction
+ //
+ override func apply(exprs: MalSequence) throws -> MalVal { return try self._fn(exprs) }
+
+ // MalBuiltin
+ //
+ override func clone_with_meta(meta: MalVal) -> MalVal { return MalBuiltin(self, meta) }
+
+ private let _fn: Signature!
+}
+
+// Equatable
+//
+func ==(left: MalBuiltin, right: MalBuiltin) -> Bool { return false } // Can't compare function references in Swift
+
+// ==================== MalMacro ====================
+
+final class MalMacro : MalVal {
+ override init() {
+ self._closure = as_closure(make_closure())
+ super.init()
+ }
+ init(_ other: MalMacro, _ meta: MalVal? = nil) {
+ self._closure = other._closure
+ super.init(other, meta)
+ }
+ init(_ closure: MalClosure) {
+ self._closure = closure
+ super.init()
+ }
+
+ // CustomStringConvertible
+ //
+ override var description: String { return self._closure.description }
+
+ // MalMacro
+ //
+ override func clone_with_meta(meta: MalVal) -> MalVal { return MalMacro(self, meta) }
+
+ var args: MalSequence { return self._closure.args }
+ var body: MalVal { return self._closure.body }
+ var env: Environment { return self._closure.env }
+
+ private let _closure: MalClosure
+}
+
+// Equatable
+//
+func ==(left: MalMacro, right: MalMacro) -> Bool { return false }
+
+
+// ==================== Constructors ====================
+
+// ----- Default -----
+
+func make_unknown () -> MalVal { return kUnknown }
+func make_nil () -> MalVal { return kNil }
+func make_true () -> MalVal { return kTrue }
+func make_false () -> MalVal { return kFalse }
+func make_comment () -> MalVal { return kComment }
+func make_integer () -> MalVal { return MalInteger() }
+func make_float () -> MalVal { return MalFloat() }
+func make_symbol () -> MalVal { return MalSymbol() }
+func make_keyword () -> MalVal { return MalKeyword() }
+func make_string () -> MalVal { return MalString() }
+func make_list () -> MalVal { return MalList() }
+func make_vector () -> MalVal { return MalVector() }
+func make_hashmap () -> MalVal { return MalHashMap() }
+func make_atom () -> MalVal { return MalAtom() }
+func make_closure () -> MalVal { return MalClosure() }
+func make_builtin () -> MalVal { return MalBuiltin() }
+func make_macro () -> MalVal { return MalMacro() }
+
+// ----- Copy -----
+
+func make_integer (v: MalInteger) -> MalVal { return MalInteger(v) }
+func make_float (v: MalFloat) -> MalVal { return MalFloat(v) }
+func make_symbol (v: MalSymbol) -> MalVal { return MalSymbol(v) }
+func make_keyword (v: MalKeyword) -> MalVal { return MalKeyword(v) }
+func make_string (v: MalString) -> MalVal { return MalString(v) }
+func make_list (v: MalList) -> MalVal { return MalList(v) }
+func make_vector (v: MalVector) -> MalVal { return MalVector(v) }
+func make_hashmap (v: MalHashMap) -> MalVal { return MalHashMap(v) }
+func make_atom (v: MalAtom) -> MalVal { return MalAtom(v) }
+func make_closure (v: MalClosure) -> MalVal { return MalClosure(v) }
+func make_builtin (v: MalBuiltin) -> MalVal { return MalBuiltin(v) }
+func make_macro (v: MalMacro) -> MalVal { return MalMacro(v) }
+
+// ----- Parameterized -----
+
+func make_integer (v: MalIntType) -> MalVal { return MalInteger(v) }
+func make_float (v: MalFloatType) -> MalVal { return MalFloat(v) }
+func make_symbol (v: String) -> MalVal { return MalSymbol(v) }
+func make_keyword (v: String) -> MalVal { return MalKeyword(v) }
+func make_keyword (v: MalString) -> MalVal { return MalKeyword(v) }
+func make_string (v: String) -> MalVal { return MalString(v) }
+func make_list (v: MalSequence) -> MalVal { return MalList(seq: v) }
+func make_list (v: MalVectorType) -> MalVal { return MalList(v) }
+func make_list (v: Array<MalVal>) -> MalVal { return MalList(v) }
+func make_list_from (v: MalVal...) -> MalVal { return MalList(v) }
+func make_list<T: CollectionType where T.Generator.Element == MalVal>
+ (v: T) -> MalVal { return MalList(v) }
+func make_vector (v: MalSequence) -> MalVal { return MalVector(seq: v) }
+func make_vector (v: MalVectorType) -> MalVal { return MalVector(v) }
+func make_vector (v: Array<MalVal>) -> MalVal { return MalVector(v) }
+func make_vector<T: CollectionType where T.Generator.Element == MalVal>
+ (v: T) -> MalVal { return MalVector(v) }
+func make_hashmap (v: MalSequence) -> MalVal { return MalHashMap(v) }
+func make_hashmap (v: MalHashType) -> MalVal { return MalHashMap(v) }
+func make_hashmap<T: CollectionType where T.Generator.Element == MalVal>
+ (v: T) -> MalVal { return MalHashMap(v) }
+func make_atom (v: MalVal) -> MalVal { return MalAtom(object: v) }
+func make_closure (v: MalClosure.Parameters) -> MalVal { return MalClosure(v) }
+func make_builtin (v: MalBuiltin.Signature) -> MalVal { return MalBuiltin(v) }
+func make_macro (v: MalClosure) -> MalVal { return MalMacro(v) }
+
+// ==================== Predicates ====================
+
+// ----- Simple -----
+
+func is_unknown (v: MalVal) -> Bool { return v is MalUnknown }
+func is_nil (v: MalVal) -> Bool { return v is MalNil }
+func is_true (v: MalVal) -> Bool { return v is MalTrue }
+func is_false (v: MalVal) -> Bool { return v is MalFalse }
+func is_comment (v: MalVal) -> Bool { return v is MalComment }
+func is_integer (v: MalVal) -> Bool { return v is MalInteger }
+func is_float (v: MalVal) -> Bool { return v is MalFloat }
+func is_symbol (v: MalVal) -> Bool { return v is MalSymbol }
+func is_keyword (v: MalVal) -> Bool { return v is MalKeyword }
+func is_string (v: MalVal) -> Bool { return v is MalString }
+func is_list (v: MalVal) -> Bool { return v is MalList }
+func is_vector (v: MalVal) -> Bool { return v is MalVector }
+func is_hashmap (v: MalVal) -> Bool { return v is MalHashMap }
+func is_atom (v: MalVal) -> Bool { return v is MalAtom }
+func is_closure (v: MalVal) -> Bool { return v is MalClosure }
+func is_builtin (v: MalVal) -> Bool { return v is MalBuiltin }
+func is_macro (v: MalVal) -> Bool { return v is MalMacro }
+
+// ----- Compound -----
+
+func is_truthy (v: MalVal) -> Bool { return !is_falsey(v) }
+func is_falsey (v: MalVal) -> Bool { return is_nil(v) || is_false(v) }
+func is_number (v: MalVal) -> Bool { return is_integer(v) || is_float(v) }
+func is_sequence (v: MalVal) -> Bool { return is_list(v) || is_vector(v) }
+func is_function (v: MalVal) -> Bool { return is_closure(v) || is_builtin(v) }
+
+// ==================== Converters/Extractors ====================
+
+func as_unknown (v: MalVal) -> MalUnknown { return v as! MalUnknown }
+func as_nil (v: MalVal) -> MalNil { return v as! MalNil }
+func as_true (v: MalVal) -> MalTrue { return v as! MalTrue }
+func as_false (v: MalVal) -> MalFalse { return v as! MalFalse }
+func as_comment (v: MalVal) -> MalComment { return v as! MalComment }
+func as_integer (v: MalVal) -> MalInteger { return v as! MalInteger }
+func as_float (v: MalVal) -> MalFloat { return v as! MalFloat }
+func as_symbol (v: MalVal) -> MalSymbol { return v as! MalSymbol }
+func as_keyword (v: MalVal) -> MalKeyword { return v as! MalKeyword }
+func as_string (v: MalVal) -> MalString { return v as! MalString }
+func as_list (v: MalVal) -> MalList { return v as! MalList }
+func as_vector (v: MalVal) -> MalVector { return v as! MalVector }
+func as_hashmap (v: MalVal) -> MalHashMap { return v as! MalHashMap }
+func as_atom (v: MalVal) -> MalAtom { return v as! MalAtom }
+func as_closure (v: MalVal) -> MalClosure { return v as! MalClosure }
+func as_builtin (v: MalVal) -> MalBuiltin { return v as! MalBuiltin }
+func as_macro (v: MalVal) -> MalMacro { return v as! MalMacro }
+
+func as_sequence (v: MalVal) -> MalSequence { return v as! MalSequence }
+func as_function (v: MalVal) -> MalFunction { return v as! MalFunction }
+
+func as_inttype (v: MalVal) -> MalIntType { return as_integer(v).integer }
+func as_floattype (v: MalVal) -> MalFloatType { return as_float(v).float }
+func as_stringtype (v: MalVal) -> MalStringType { return as_string(v).string }
+
+func as_inttype (v: MalInteger) -> MalIntType { return v.integer }
+func as_floattype (v: MalFloat) -> MalFloatType { return v.float }
+func as_stringtype (v: MalString) -> MalStringType { return v.string }
+
+func as_unknownQ (v: MalVal) -> MalUnknown? { return v as? MalUnknown }
+func as_nilQ (v: MalVal) -> MalNil? { return v as? MalNil }
+func as_trueQ (v: MalVal) -> MalTrue? { return v as? MalTrue }
+func as_falseQ (v: MalVal) -> MalFalse? { return v as? MalFalse }
+func as_commentQ (v: MalVal) -> MalComment? { return v as? MalComment }
+func as_integerQ (v: MalVal) -> MalInteger? { return v as? MalInteger }
+func as_floatQ (v: MalVal) -> MalFloat? { return v as? MalFloat }
+func as_symbolQ (v: MalVal) -> MalSymbol? { return v as? MalSymbol }
+func as_keywordQ (v: MalVal) -> MalKeyword? { return v as? MalKeyword }
+func as_stringQ (v: MalVal) -> MalString? { return v as? MalString }
+func as_listQ (v: MalVal) -> MalList? { return v as? MalList }
+func as_vectorQ (v: MalVal) -> MalVector? { return v as? MalVector }
+func as_hashmapQ (v: MalVal) -> MalHashMap? { return v as? MalHashMap }
+func as_atomQ (v: MalVal) -> MalAtom? { return v as? MalAtom }
+func as_closureQ (v: MalVal) -> MalClosure? { return v as? MalClosure }
+func as_builtinQ (v: MalVal) -> MalBuiltin? { return v as? MalBuiltin }
+func as_macroQ (v: MalVal) -> MalMacro? { return v as? MalMacro }
+
+func as_sequenceQ (v: MalVal) -> MalSequence? { return v as? MalSequence }
+func as_functionQ (v: MalVal) -> MalFunction? { return v as? MalFunction }
+
+func as_inttypeQ (v: MalVal) -> MalIntType? { return as_integerQ(v)?.integer }
+func as_floattypeQ (v: MalVal) -> MalFloatType? { return as_floatQ(v)?.float }
+func as_stringtypeQ (v: MalVal) -> MalStringType? { return as_stringQ(v)?.string }
+
+// ==================== Exceptions ====================
+
+enum MalException: ErrorType, CustomStringConvertible {
+ case None
+ case Message(String)
+ case Object(MalVal)
+
+ var exception: MalVal {
+ switch self {
+ case .None:
+ return make_nil()
+ case .Message(let v):
+ return make_string(v)
+ case .Object(let v):
+ return v
+ }
+ }
+
+ // CustomStringConvertible
+ //
+ var description: String {
+ switch self {
+ case .None:
+ return "NIL Exception"
+ case .Message(let v):
+ return v
+ case .Object(let v):
+ return v.description
+ }
+ }
+}
+
+@noreturn
+func throw_error(v: String) throws { throw MalException.Message(v) }
+
+@noreturn
+func throw_error(v: MalVal) throws { throw MalException.Object(v) }
+
+// ==================== Utilities ====================
+
+@noreturn private func die() {
+ preconditionFailure("Should not get here")
+}
+
+func get_meta(v: MalVal) -> MalVal? {
+ return v.meta
+}
+
+func with_meta(obj: MalVal, _ meta: MalVal) -> MalVal {
+ return obj.clone_with_meta(meta)
+}
+
+func unescape(s: String) -> String {
+ var index = 0
+ var prev_is_escape = false
+ var str = ""
+ let chars = s.characters
+ for ch in chars {
+ if index == chars.count - 1 { continue }
+ if index++ == 0 { continue }
+ if prev_is_escape {
+ prev_is_escape = false
+ if ch == "n" { str.appendContentsOf("\n") }
+ else if ch == "r" { str.appendContentsOf("\r") }
+ else if ch == "t" { str.appendContentsOf("\t") }
+ else { str.append(ch) }
+ } else if ch == "\\" {
+ prev_is_escape = true
+ } else {
+ str.append(ch)
+ }
+ }
+ return str
+}
+
+func escape(s: String) -> String {
+ var str = ""
+ let chars = s.characters
+ for ch in chars {
+ if ch == "\n" { str.appendContentsOf("\\n"); continue }
+ if ch == "\r" { str.appendContentsOf("\\r"); continue }
+ if ch == "\t" { str.appendContentsOf("\\t"); continue }
+ if ch == "\"" || ch == "\\" { str.appendContentsOf("\\") }
+ str.append(ch)
+ }
+ str = "\"" + str + "\""
+ return str
+}
--- /dev/null
+//******************************************************************************
+// MAL - types, implemented as a Swift "enum".
+//******************************************************************************
+
+import Foundation
+
+// ===== Types / Constants / Variables =====
+
+typealias MalProtocol = protocol<Equatable, CustomStringConvertible, Hashable>
+
+typealias MalIntType = Int64
+typealias MalFloatType = Double
+typealias MalSymbolType = String
+typealias MalKeywordType = String
+typealias MalStringType = String
+typealias MalVectorType = ArraySlice<MalVal>
+typealias MalHashType = Dictionary<MalVal, MalVal>
+
+typealias MalInteger = MalIntType
+typealias MalFloat = MalFloatType
+typealias MalSymbol = MalSymbolType
+typealias MalKeyword = MalKeywordType
+typealias MalString = MalStringType
+
+private let kUnknown = MalVal.TypeUnknown
+private let kNil = MalVal.TypeNil
+private let kTrue = MalVal.TypeTrue
+private let kFalse = MalVal.TypeFalse
+private let kComment = MalVal.TypeComment
+
+// ==================== MalSequence ====================
+
+class MalSequence : MalProtocol, SequenceType {
+ init() {
+ self.count = 0
+ self.isEmpty = true
+ }
+ init(_ seq: MalSequence) {
+ self.count = seq.count
+ self.isEmpty = seq.isEmpty
+ }
+ init(_ count: MalIntType) {
+ self.count = count
+ self.isEmpty = self.count == 0
+ }
+
+ // CustomStringConvertible
+ //
+ var description: String { die() }
+
+ // Hashable
+ //
+ var hashValue: Int { die() }
+
+ // SequenceType
+ //
+ func generate() -> MalVectorType.Generator { die() }
+
+ // MalSequence
+ //
+ let count: MalIntType
+ let isEmpty: Bool
+
+ func first() -> MalVal { die() }
+ func last() -> MalVal { die() }
+ func rest() -> MalVal { die() }
+ func nth(n: MalIntType) throws -> MalVal { die() }
+ func range_from(from: MalIntType, to: MalIntType) -> MalVal { die() }
+ func cons(element: MalVal) -> MalVal { die() }
+ func concat(seq: MalSequence) throws -> MalVal { die() }
+ func conj(seq: MalSequence) throws -> MalVal { die() }
+ func map<U>(@noescape transform: (MalVal) -> U) -> ArraySlice<U> { die() }
+ func reduce<U>(initial: U, @noescape combine: (U, MalVal) -> U) -> U { die() }
+}
+
+// Equatable
+//
+func ==(left: MalSequence, right: MalSequence) -> Bool {
+ if left.count != right.count { return false }
+ var left_gen = left.generate()
+ var right_gen = right.generate()
+ while true {
+ if let left = left_gen.next(), right = right_gen.next() {
+ if left != right {
+ return false
+ }
+ } else {
+ break
+ }
+ }
+ return true
+}
+
+// ==================== MalList ====================
+
+final class MalList : MalSequence {
+ override convenience init() {
+ self.init(MalVectorType())
+ }
+ init(_ other: MalList, _ meta: MalVal?) {
+ self._slice = other._slice
+ self._meta = meta
+ super.init(other)
+ }
+ override convenience init(_ seq: MalSequence) {
+ if let list = seq as? MalList { self.init(list._slice) }
+ else
+ if let vector = seq as? MalVector { self.init(vector._slice) }
+ else
+ { self.init(seq.reduce(MalVectorType()){ var s = $0; s.append($1); return s }) }
+ }
+ init(_ slice: MalVectorType) {
+ self._slice = slice
+ self._meta = nil
+ super.init(MalIntType(self._slice.count))
+ }
+ convenience init(_ array: Array<MalVal>) {
+ self.init(array[0..<array.count])
+ }
+ convenience init<T: SequenceType where T.Generator.Element == MalVal>(_ collection: T) {
+ self.init(collection.reduce(MalVectorType()){ var s = $0; s.append($1); return s })
+ }
+
+ // CustomStringConvertible
+ //
+ override var description: String { return "(" + self.map { pr_str($0) }.joinWithSeparator(" ") + ")" }
+
+ // Hashable
+ //
+ override var hashValue: Int { return description.hashValue }
+
+ // SequenceType
+ //
+ override func generate() -> MalVectorType.Generator { return self._slice.generate() }
+
+ // MalSequence
+ //
+ override func first() -> MalVal { return isEmpty ? make_nil() : try! nth(0) }
+ override func last() -> MalVal { return try! nth(count - 1) }
+ override func rest() -> MalVal { return range_from(MalIntType(1), to: MalIntType(count)) }
+ override func nth(n: MalIntType) throws -> MalVal { guard n < count else { try throw_error("index (\(n)) out of range (\(count))") }; return self._slice[self._slice.startIndex.advancedBy(Int(n))] }
+ override func range_from(from: MalIntType, to: MalIntType) -> MalVal {
+ return from <= to && to <= count
+ ? make_list(self._slice[self._slice.startIndex.advancedBy(Int(from))..<self._slice.startIndex.advancedBy(Int(to))])
+ : make_list()
+ }
+ override func cons(element: MalVal) -> MalVal {
+ var result = self._slice
+ result.insert(element, atIndex: result.startIndex)
+ return make_list(result)
+ }
+ override func concat(seq: MalSequence) throws -> MalVal {
+ var result = self._slice
+ if let list = as_listQ(seq) {
+ result.appendContentsOf(list._slice)
+ } else if let vector = as_vectorQ(seq) {
+ result.appendContentsOf(vector._slice)
+ } else {
+ try throw_error("Expected sequence, got \(seq)")
+ }
+ return make_list(result)
+ }
+ override func conj(seq: MalSequence) throws -> MalVal {
+ var result: Array<MalVal>
+ if let list = as_listQ(seq) {
+ result = list._slice.reverse()
+ } else if let vector = as_vectorQ(seq) {
+ result = vector._slice.reverse()
+ } else {
+ try throw_error("Expected sequence, got \(seq)")
+ }
+ result.appendContentsOf(self._slice)
+ return make_list(result)
+ }
+ override func map<U>(@noescape transform: (MalVal) -> U) -> ArraySlice<U> { return ArraySlice<U>(self._slice.map(transform)) }
+ override func reduce<U>(initial: U, @noescape combine: (U, MalVal) -> U) -> U { return self._slice.reduce(initial, combine: combine) }
+
+ // MalList
+ //
+ var meta: MalVal? { return self._meta }
+
+ private let _slice: MalVectorType
+ private let _meta: MalVal?
+}
+
+// Equatable
+//
+func ==(left: MalList, right: MalList) -> Bool {
+ return (left as MalSequence) == (right as MalSequence)
+}
+
+// ==================== MalVector ====================
+
+final class MalVector : MalSequence {
+ override convenience init() {
+ self.init(MalVectorType())
+ }
+ init(_ other: MalVector, _ meta: MalVal?) {
+ self._slice = other._slice
+ self._meta = meta
+ super.init(other)
+ }
+ override convenience init(_ seq: MalSequence) {
+ if let list = seq as? MalList { self.init(list._slice) }
+ else
+ if let vector = seq as? MalVector { self.init(vector._slice) }
+ else
+ { self.init(seq.reduce(MalVectorType()){ var s = $0; s.append($1); return s }) }
+ }
+ init(_ slice: MalVectorType) {
+ self._slice = slice
+ self._meta = nil
+ super.init(MalIntType(self._slice.count))
+ }
+ convenience init(_ array: Array<MalVal>) {
+ self.init(array[0..<array.count])
+ }
+ convenience init<T: SequenceType where T.Generator.Element == MalVal>(_ collection: T) {
+ self.init(collection.reduce(MalVectorType()){ var s = $0; s.append($1); return s })
+ }
+
+ // CustomStringConvertible
+ //
+ override var description: String { return "[" + self.map { pr_str($0) }.joinWithSeparator(" ") + "]" }
+
+ // Hashable
+ //
+ override var hashValue: Int { return description.hashValue }
+
+ // SequenceType
+ //
+ override func generate() -> MalVectorType.Generator { return self._slice.generate() }
+
+ // MalSequence
+ //
+ override func first() -> MalVal { return isEmpty ? make_nil() : try! nth(0) }
+ override func last() -> MalVal { return try! nth(count - 1) }
+ override func rest() -> MalVal { return range_from(MalIntType(1), to: MalIntType(count)) }
+ override func nth(n: MalIntType) throws -> MalVal { guard n < count else { try throw_error("index (\(n)) out of range (\(count))") }; return self._slice[self._slice.startIndex.advancedBy(Int(n))] }
+ override func range_from(from: MalIntType, to: MalIntType) -> MalVal {
+ return from <= to && to <= count
+ ? make_list(self._slice[self._slice.startIndex.advancedBy(Int(from))..<self._slice.startIndex.advancedBy(Int(to))]) // Yes, make_list
+ : make_list() // Yes, make_list
+ }
+ override func cons(element: MalVal) -> MalVal {
+ var result = self._slice
+ result.insert(element, atIndex: result.startIndex)
+ return make_list(result) // Yes, make_list
+ }
+ override func concat(seq: MalSequence) throws -> MalVal {
+ var result = self._slice
+ if let list = as_listQ(seq) {
+ result.appendContentsOf(list._slice)
+ } else if let vector = as_vectorQ(seq) {
+ result.appendContentsOf(vector._slice)
+ } else {
+ try throw_error("Expected sequence, got \(seq)")
+ }
+ return make_vector(result)
+ }
+ override func conj(seq: MalSequence) throws -> MalVal {
+ var result = self._slice
+ if let list = as_listQ(seq) {
+ result.appendContentsOf(list._slice)
+ } else if let vector = as_vectorQ(seq) {
+ result.appendContentsOf(vector._slice)
+ } else {
+ try throw_error("Expected sequence, got \(seq)")
+ }
+ return make_vector(result)
+ }
+ override func map<U>(@noescape transform: (MalVal) -> U) -> ArraySlice<U> { return ArraySlice<U>(self._slice.map(transform)) }
+ override func reduce<U>(initial: U, @noescape combine: (U, MalVal) -> U) -> U { return self._slice.reduce(initial, combine: combine) }
+
+ // MalVector
+ //
+ var meta: MalVal? { return self._meta }
+
+ private let _slice: MalVectorType
+ private let _meta: MalVal?
+}
+
+// Equatable
+//
+func ==(left: MalVector, right: MalVector) -> Bool {
+ return (left as MalSequence) == (right as MalSequence)
+}
+
+// ==================== MalHashMap ====================
+
+final class MalHashMap : MalProtocol, SequenceType {
+ convenience init() {
+ self.init(MalHashType())
+ }
+ init(_ other: MalHashMap, _ meta: MalVal?) {
+ self._hash = other._hash
+ self._meta = meta
+ self.count = MalIntType(self._hash.count)
+ self.isEmpty = self._hash.isEmpty
+ }
+ init(_ hash: MalHashType) {
+ self._hash = hash
+ self._meta = nil
+ self.count = MalIntType(self._hash.count)
+ self.isEmpty = self._hash.isEmpty
+ }
+ convenience init(_ seq: MalSequence) {
+ var hash = MalHashType()
+ for var index: MalIntType = 0; index < seq.count; index += 2 {
+ hash[try! seq.nth(index)] = try! seq.nth(index + 1)
+ }
+ self.init(hash)
+ }
+ convenience init<T: CollectionType where T.Generator.Element == MalVal>(_ collection: T) {
+ var hash = MalHashType()
+ for var index = collection.startIndex; index != collection.endIndex; {
+ let key = collection[index++]
+ let value = collection[index++]
+ hash[key] = value
+ }
+ self.init(hash)
+ }
+
+ // CustomStringConvertible
+ //
+ var description: String {
+ var a = [String]()
+ for (k, v) in self._hash {
+ a.append("\(pr_str(k)) \(pr_str(v))")
+ }
+ let s = a.joinWithSeparator(" ")
+ return "{\(s)}"
+ }
+
+ // Hashable
+ //
+ var hashValue: Int { return description.hashValue }
+
+ // SequenceType
+ //
+ func generate() -> MalHashType.Generator { return self._hash.generate() }
+
+ // MalHashMap
+ //
+ let count: MalIntType
+ let isEmpty: Bool
+ var hash: MalHashType { return self._hash }
+ var keys: MalVal { return make_list(self._hash.keys) }
+ var values: MalVal { return make_list(self._hash.values) }
+ var meta: MalVal? { return self._meta }
+
+ func value_for(key: MalVal) -> MalVal? {
+ return self._hash[key]
+ }
+
+ private let _hash: MalHashType
+ private let _meta: MalVal?
+}
+
+// Equatable
+//
+func ==(left: MalHashMap, right: MalHashMap) -> Bool {
+ if left.count != right.count { return false }
+ var left_gen = left.generate()
+ var right_gen = right.generate()
+ while true {
+ if let left = left_gen.next(), right = right_gen.next() {
+ if left.0 != right.0 || left.1 != right.1 {
+ return false
+ }
+ } else {
+ break
+ }
+ }
+ return true
+}
+
+// ==================== MalAtom ====================
+
+final class MalAtom : MalProtocol {
+ convenience init() {
+ self.init(make_nil())
+ }
+ init(_ other: MalAtom, _ meta: MalVal?) {
+ self._object = other._object
+ self._meta = meta
+ }
+ init(_ object: MalVal) {
+ self._object = object
+ self._meta = nil
+ }
+
+ // CustomStringConvertible
+ //
+ var description: String { return "(atom \(pr_str(self._object)))" }
+
+ // Hashable
+ //
+ var hashValue: Int { return description.hashValue }
+
+ // MalAtom
+ //
+ var object: MalVal { return self._object }
+ var meta: MalVal? { return self._meta }
+
+ func set_object(obj: MalVal) -> MalVal {
+ self._object = obj
+ return obj
+ }
+
+ private var _object: MalVal
+ private let _meta: MalVal?
+}
+
+// Equatable
+//
+func ==(left: MalAtom, right: MalAtom) -> Bool { return left.object == right.object }
+
+// ==================== MalFunction ====================
+
+class MalFunction : MalProtocol {
+ init() {
+ }
+ init(_ other: MalFunction) {
+ }
+
+ // CustomStringConvertible
+ //
+ var description: String { die() }
+
+ // Hashable
+ //
+ var hashValue: Int { die() }
+
+ // MalFunction
+ //
+ func apply(exprs: MalSequence) throws -> MalVal { die() }
+}
+
+// Equatable
+//
+func ==(left: MalFunction, right: MalFunction) -> Bool { return false }
+
+// ==================== MalClosure ====================
+
+
+final class MalClosure : MalFunction {
+ typealias Evaluator = (MalVal, Environment) throws -> MalVal
+ typealias Parameters = (eval: Evaluator, args: MalSequence, body: MalVal, env: Environment)
+
+ override convenience init() {
+ self.init((
+ eval: {(a: MalVal, b: Environment) -> MalVal in make_nil() },
+ args: as_sequence(make_list()),
+ body: make_nil(),
+ env: Environment(outer: nil)
+ ))
+ }
+ init(_ other: MalClosure, _ meta: MalVal?) {
+ self._eval = other._eval
+ self._args = other._args
+ self._body = other._body
+ self._env = other._env
+ self._meta = meta
+ super.init(other)
+ }
+ init(_ p: Parameters) {
+ self._eval = p.eval
+ self._args = p.args
+ self._body = p.body
+ self._env = p.env
+ self._meta = nil
+ super.init()
+ }
+
+ // CustomStringConvertible
+ //
+ override var description: String { return "#<Closure>: (fn* \(self._args.description) \(self._body.description))" }
+
+ // Hashable
+ //
+ override var hashValue: Int { return description.hashValue }
+
+ // MalFunction
+ //
+ override func apply(exprs: MalSequence) throws -> MalVal {
+ let new_env = Environment(outer: self._env)
+ let _ = try new_env.set_bindings(self._args, with_exprs: exprs)
+ // Calling EVAL indirectly via an 'eval' data member is a bit of a hack.
+ // We can't call EVAL directly because this file (types.swift) needs to
+ // be used with many different versions of the main MAL file
+ // (step[0-10]*.swift), and EVAL is declared differently across those
+ // versions. By using this indirection, we avoid that problem.
+ return try self._eval(self._body, new_env)
+ }
+
+ var args: MalSequence { return self._args }
+ var body: MalVal { return self._body }
+ var env: Environment { return self._env }
+ var meta: MalVal? { return self._meta }
+
+ private let _eval: Evaluator!
+ private let _args: MalSequence
+ private let _body: MalVal
+ private let _env: Environment
+ private let _meta: MalVal?
+}
+
+// Equatable
+//
+func ==(left: MalClosure, right: MalClosure) -> Bool { return false }
+
+// ==================== MalBuiltin ====================
+
+final class MalBuiltin : MalFunction {
+ typealias Signature = (MalSequence) throws -> MalVal
+
+ override convenience init() {
+ self.init( {(MalSequence) -> MalVal in make_nil()} )
+ }
+ init(_ other: MalBuiltin, _ meta: MalVal?) {
+ self._fn = other._fn
+ self._meta = meta
+ super.init(other)
+ }
+ init(_ fn: Signature) {
+ self._fn = fn
+ self._meta = nil
+ super.init()
+ }
+
+ // CustomStringConvertible
+ //
+ override var description: String { return "#<Builtin>" }
+
+ // Hashable
+ //
+ override var hashValue: Int { return description.hashValue }
+
+ // MalBuiltin
+ //
+ override func apply(exprs: MalSequence) throws -> MalVal { return try self._fn(exprs) }
+ var meta: MalVal? { return self._meta }
+
+ private let _fn: Signature!
+ private let _meta: MalVal?
+}
+
+// Equatable
+//
+func ==(left: MalBuiltin, right: MalBuiltin) -> Bool { return false } // Can't compare function references in Swift
+
+// ==================== MalMacro ====================
+
+final class MalMacro : MalProtocol {
+ convenience init() {
+ self.init(as_closure(make_closure()))
+ }
+ init(_ other: MalMacro, _ meta: MalVal?) {
+ self._closure = other._closure
+ self._meta = meta
+ }
+ init(_ closure: MalClosure) {
+ self._closure = closure
+ self._meta = nil
+ }
+
+ // CustomStringConvertible
+ //
+ var description: String { return self._closure.description }
+
+ // Hashable
+ //
+ var hashValue: Int { return description.hashValue }
+
+ var args: MalSequence { return self._closure.args }
+ var body: MalVal { return self._closure.body }
+ var env: Environment { return self._closure.env }
+ var meta: MalVal? { return self._meta }
+
+ private let _closure: MalClosure
+ private let _meta: MalVal?
+}
+
+// Equatable
+//
+func ==(left: MalMacro, right: MalMacro) -> Bool { return false }
+
+// ==================== MalVal ====================
+
+enum MalVal : MalProtocol {
+ case TypeUnknown
+ case TypeNil
+ case TypeTrue
+ case TypeFalse
+ case TypeComment
+ case TypeInteger (MalInteger)
+ case TypeFloat (MalFloat)
+ case TypeSymbol (MalSymbol)
+ case TypeKeyword (MalKeyword)
+ case TypeString (MalString)
+ case TypeList (MalList)
+ case TypeVector (MalVector)
+ case TypeHashMap (MalHashMap)
+ case TypeAtom (MalAtom)
+ case TypeClosure (MalClosure)
+ case TypeBuiltin (MalBuiltin)
+ case TypeMacro (MalMacro)
+
+ // CustomStringConvertible
+ //
+ var description: String {
+ switch self {
+ case .TypeUnknown: return "unknown"
+ case .TypeNil: return "nil"
+ case .TypeTrue: return "true"
+ case .TypeFalse: return "false"
+ case .TypeComment: return "comment"
+ case .TypeInteger (let v): return v.description
+ case .TypeFloat (let v): return v.description
+ case .TypeSymbol (let v): return v
+ case .TypeKeyword (let v): return v
+ case .TypeString (let v): return v
+ case .TypeList (let v): return v.description
+ case .TypeVector (let v): return v.description
+ case .TypeHashMap (let v): return v.description
+ case .TypeAtom (let v): return v.description
+ case .TypeClosure (let v): return v.description
+ case .TypeBuiltin (let v): return v.description
+ case .TypeMacro (let v): return v.description
+ }
+ }
+
+ // Hashable
+ //
+ var hashValue: Int {
+ switch self {
+ case .TypeUnknown: return 0
+ case .TypeNil: return 0
+ case .TypeTrue: return 0
+ case .TypeFalse: return 0
+ case .TypeComment: return 0
+ case .TypeInteger (let v): return v.hashValue
+ case .TypeFloat (let v): return v.hashValue
+ case .TypeSymbol (let v): return v.hashValue
+ case .TypeKeyword (let v): return v.hashValue
+ case .TypeString (let v): return v.hashValue
+ case .TypeList (let v): return v.hashValue
+ case .TypeVector (let v): return v.hashValue
+ case .TypeHashMap (let v): return v.hashValue
+ case .TypeAtom (let v): return v.hashValue
+ case .TypeClosure (let v): return v.hashValue
+ case .TypeBuiltin (let v): return v.hashValue
+ case .TypeMacro (let v): return v.hashValue
+ }
+ }
+}
+
+// Equatable
+//
+func ==(left: MalVal, right: MalVal) -> Bool {
+ switch (left, right) {
+ case (.TypeUnknown, .TypeUnknown): return true
+ case (.TypeNil, .TypeNil): return true
+ case (.TypeTrue, .TypeTrue): return true
+ case (.TypeFalse, .TypeFalse): return true
+ case (.TypeComment, .TypeComment): return false
+ case (.TypeInteger (let vLeft), .TypeInteger (let vRight)): return vLeft == vRight
+ case (.TypeFloat (let vLeft), .TypeFloat (let vRight)): return vLeft == vRight
+ case (.TypeSymbol (let vLeft), .TypeSymbol (let vRight)): return vLeft == vRight
+ case (.TypeKeyword (let vLeft), .TypeKeyword (let vRight)): return vLeft == vRight
+ case (.TypeString (let vLeft), .TypeString (let vRight)): return vLeft == vRight
+ case (.TypeList (let vLeft), .TypeList (let vRight)): return vLeft == vRight
+ case (.TypeVector (let vLeft), .TypeVector (let vRight)): return vLeft == vRight
+ case (.TypeHashMap (let vLeft), .TypeHashMap (let vRight)): return vLeft == vRight
+ case (.TypeAtom (let vLeft), .TypeAtom (let vRight)): return vLeft == vRight
+ case (.TypeClosure (let vLeft), .TypeClosure (let vRight)): return vLeft == vRight
+ case (.TypeBuiltin (let vLeft), .TypeBuiltin (let vRight)): return vLeft == vRight
+ case (.TypeMacro (let vLeft), .TypeMacro (let vRight)): return vLeft == vRight
+
+ case (.TypeList (let vLeft), .TypeVector (let vRight)): return vLeft == vRight
+ case (.TypeVector (let vLeft), .TypeList (let vRight)): return vLeft == vRight
+
+ default: return false
+ }
+}
+
+func ==(left: MalList, right: MalVector) -> Bool {
+ if left.count != right.count { return false }
+ var left_gen = left.generate()
+ var right_gen = right.generate()
+ while true {
+ if let left = left_gen.next(), right = right_gen.next() {
+ if left != right {
+ return false
+ }
+ } else {
+ break
+ }
+ }
+ return true
+}
+
+func ==(left: MalVector, right: MalList) -> Bool {
+ if left.count != right.count { return false }
+ var left_gen = left.generate()
+ var right_gen = right.generate()
+ while true {
+ if let left = left_gen.next(), right = right_gen.next() {
+ if left != right {
+ return false
+ }
+ } else {
+ break
+ }
+ }
+ return true
+}
+
+// ==================== Constructors ====================
+
+// ----- Default -----
+
+func make_unknown () -> MalVal { return kUnknown }
+func make_nil () -> MalVal { return kNil }
+func make_true () -> MalVal { return kTrue }
+func make_false () -> MalVal { return kFalse }
+func make_comment () -> MalVal { return kComment }
+func make_integer () -> MalVal { return make_integer (MalInteger()) }
+func make_float () -> MalVal { return make_float (MalFloat()) }
+func make_symbol () -> MalVal { return make_symbol (MalSymbol()) }
+func make_keyword () -> MalVal { return make_keyword (MalKeyword()) }
+func make_string () -> MalVal { return make_string (MalString()) }
+func make_list () -> MalVal { return make_list (MalList()) }
+func make_vector () -> MalVal { return make_vector (MalVector()) }
+func make_hashmap () -> MalVal { return make_hashmap (MalHashMap()) }
+func make_atom () -> MalVal { return make_atom (MalAtom()) }
+func make_closure () -> MalVal { return make_closure (MalClosure()) }
+func make_builtin () -> MalVal { return make_builtin (MalBuiltin()) }
+func make_macro () -> MalVal { return make_macro (MalMacro()) }
+
+// ----- Base -----
+
+func make_integer (v: MalInteger) -> MalVal { return MalVal.TypeInteger(v) }
+func make_float (v: MalFloat) -> MalVal { return MalVal.TypeFloat(v) }
+func make_symbol (v: MalSymbol) -> MalVal { return MalVal.TypeSymbol(v) }
+func make_keyword (v: MalKeyword) -> MalVal { return MalVal.TypeKeyword(v) }
+func make_string (v: MalString) -> MalVal { return MalVal.TypeString(v) }
+func make_list (v: MalList) -> MalVal { return MalVal.TypeList(v) }
+func make_vector (v: MalVector) -> MalVal { return MalVal.TypeVector(v) }
+func make_hashmap (v: MalHashMap) -> MalVal { return MalVal.TypeHashMap(v) }
+func make_atom (v: MalAtom) -> MalVal { return MalVal.TypeAtom(v) }
+func make_closure (v: MalClosure) -> MalVal { return MalVal.TypeClosure(v) }
+func make_builtin (v: MalBuiltin) -> MalVal { return MalVal.TypeBuiltin(v) }
+func make_macro (v: MalMacro) -> MalVal { return MalVal.TypeMacro(v) }
+
+// ----- Parameterized -----
+
+func make_list (v: MalSequence) -> MalVal { return make_list(MalList(v)) }
+func make_list (v: MalVectorType) -> MalVal { return make_list(MalList(v)) }
+func make_list (v: Array<MalVal>) -> MalVal { return make_list(MalList(v)) }
+func make_list_from (v: MalVal...) -> MalVal { return make_list(MalList(v)) }
+func make_list<T: SequenceType where T.Generator.Element == MalVal>
+ (v: T) -> MalVal { return make_list(MalList(v)) }
+func make_vector (v: MalSequence) -> MalVal { return make_vector(MalVector(v)) }
+func make_vector (v: MalVectorType) -> MalVal { return make_vector(MalVector(v)) }
+func make_vector (v: Array<MalVal>) -> MalVal { return make_vector(MalVector(v)) }
+func make_vector_from (v: MalVal...) -> MalVal { return make_vector(MalVector(v)) }
+func make_vector<T: SequenceType where T.Generator.Element == MalVal>
+ (v: T) -> MalVal { return make_vector(MalVector(v)) }
+func make_hashmap (v: MalSequence) -> MalVal { return make_hashmap(MalHashMap(v)) }
+func make_hashmap (v: MalHashType) -> MalVal { return make_hashmap(MalHashMap(v)) }
+func make_hashmap<T: CollectionType where T.Generator.Element == MalVal>
+ (v: T) -> MalVal { return make_hashmap(MalHashMap(v)) }
+func make_atom (v: MalVal) -> MalVal { return make_atom(MalAtom(v)) }
+func make_closure (v: MalClosure.Parameters) -> MalVal { return make_closure(MalClosure(v)) }
+func make_builtin (v: MalBuiltin.Signature) -> MalVal { return make_builtin(MalBuiltin(v)) }
+func make_macro (v: MalClosure) -> MalVal { return make_macro(MalMacro(v)) }
+
+// ==================== Predicates ====================
+
+// ----- Simple -----
+
+func is_unknown (v: MalVal) -> Bool { if case .TypeUnknown = v { return true } else { return false } }
+func is_nil (v: MalVal) -> Bool { if case .TypeNil = v { return true } else { return false } }
+func is_true (v: MalVal) -> Bool { if case .TypeTrue = v { return true } else { return false } }
+func is_false (v: MalVal) -> Bool { if case .TypeFalse = v { return true } else { return false } }
+func is_comment (v: MalVal) -> Bool { if case .TypeComment = v { return true } else { return false } }
+func is_integer (v: MalVal) -> Bool { if case .TypeInteger = v { return true } else { return false } }
+func is_float (v: MalVal) -> Bool { if case .TypeFloat = v { return true } else { return false } }
+func is_symbol (v: MalVal) -> Bool { if case .TypeSymbol = v { return true } else { return false } }
+func is_keyword (v: MalVal) -> Bool { if case .TypeKeyword = v { return true } else { return false } }
+func is_string (v: MalVal) -> Bool { if case .TypeString = v { return true } else { return false } }
+func is_list (v: MalVal) -> Bool { if case .TypeList = v { return true } else { return false } }
+func is_vector (v: MalVal) -> Bool { if case .TypeVector = v { return true } else { return false } }
+func is_hashmap (v: MalVal) -> Bool { if case .TypeHashMap = v { return true } else { return false } }
+func is_atom (v: MalVal) -> Bool { if case .TypeAtom = v { return true } else { return false } }
+func is_closure (v: MalVal) -> Bool { if case .TypeClosure = v { return true } else { return false } }
+func is_builtin (v: MalVal) -> Bool { if case .TypeBuiltin = v { return true } else { return false } }
+func is_macro (v: MalVal) -> Bool { if case .TypeMacro = v { return true } else { return false } }
+
+// ----- Compound -----
+
+func is_truthy (v: MalVal) -> Bool { return !is_falsey(v) }
+func is_falsey (v: MalVal) -> Bool { switch v { case .TypeNil, .TypeFalse: return true; default: return false } }
+func is_number (v: MalVal) -> Bool { switch v { case .TypeInteger, .TypeFloat: return true; default: return false } }
+func is_sequence (v: MalVal) -> Bool { switch v { case .TypeList, .TypeVector: return true; default: return false } }
+func is_function (v: MalVal) -> Bool { switch v { case .TypeClosure, .TypeBuiltin: return true; default: return false } }
+
+// ==================== Converters/Extractors ====================
+
+func as_integer (v: MalVal) -> MalInteger { if case .TypeInteger(let w) = v { return w }; die("expected integer, got \(v)") }
+func as_float (v: MalVal) -> MalFloat { if case .TypeFloat(let w) = v { return w }; die("expected float, got \(v)") }
+func as_symbol (v: MalVal) -> MalSymbol { if case .TypeSymbol(let w) = v { return w }; die("expected symbol, got \(v)") }
+func as_keyword (v: MalVal) -> MalKeyword { if case .TypeKeyword(let w) = v { return w }; die("expected keyword, got \(v)") }
+func as_string (v: MalVal) -> MalString { if case .TypeString(let w) = v { return w }; die("expected string, got \(v)") }
+func as_list (v: MalVal) -> MalList { if case .TypeList(let w) = v { return w }; die("expected list, got \(v)") }
+func as_vector (v: MalVal) -> MalVector { if case .TypeVector(let w) = v { return w }; die("expected vector, got \(v)") }
+func as_hashmap (v: MalVal) -> MalHashMap { if case .TypeHashMap(let w) = v { return w }; die("expected hashmap, got \(v)") }
+func as_atom (v: MalVal) -> MalAtom { if case .TypeAtom(let w) = v { return w }; die("expected atom, got \(v)") }
+func as_closure (v: MalVal) -> MalClosure { if case .TypeClosure(let w) = v { return w }; die("expected closure, got \(v)") }
+func as_builtin (v: MalVal) -> MalBuiltin { if case .TypeBuiltin(let w) = v { return w }; die("expected builtin, got \(v)") }
+func as_macro (v: MalVal) -> MalMacro { if case .TypeMacro(let w) = v { return w }; die("expected macro, got \(v)") }
+
+func as_sequence (v: MalVal) -> MalSequence {
+ switch v {
+ case .TypeList(let v): return v
+ case .TypeVector(let v): return v
+ default: die("expected sequence, got \(v)")
+ }
+}
+func as_function (v: MalVal) -> MalFunction {
+ switch v {
+ case .TypeClosure(let v): return v
+ case .TypeBuiltin(let v): return v
+ default: die("expected function, got \(v)")
+ }
+}
+
+func as_inttype (v: MalVal) -> MalIntType { return as_integer(v) }
+func as_floattype (v: MalVal) -> MalFloatType { return as_float(v) }
+func as_stringtype (v: MalVal) -> MalStringType { return as_string(v) }
+
+func as_inttype (v: MalInteger) -> MalIntType { return v }
+func as_floattype (v: MalFloat) -> MalFloatType { return v }
+func as_stringtype (v: MalString) -> MalStringType { return v }
+
+func as_integerQ (v: MalVal) -> MalInteger? { if case .TypeInteger(let w) = v { return w }; return nil }
+func as_floatQ (v: MalVal) -> MalFloat? { if case .TypeFloat(let w) = v { return w }; return nil }
+func as_symbolQ (v: MalVal) -> MalSymbol? { if case .TypeSymbol(let w) = v { return w }; return nil }
+func as_keywordQ (v: MalVal) -> MalKeyword? { if case .TypeKeyword(let w) = v { return w }; return nil }
+func as_stringQ (v: MalVal) -> MalString? { if case .TypeString(let w) = v { return w }; return nil }
+func as_listQ (v: MalVal) -> MalList? { if case .TypeList(let w) = v { return w }; return nil }
+func as_vectorQ (v: MalVal) -> MalVector? { if case .TypeVector(let w) = v { return w }; return nil }
+func as_hashmapQ (v: MalVal) -> MalHashMap? { if case .TypeHashMap(let w) = v { return w }; return nil }
+func as_atomQ (v: MalVal) -> MalAtom? { if case .TypeAtom(let w) = v { return w }; return nil }
+func as_closureQ (v: MalVal) -> MalClosure? { if case .TypeClosure(let w) = v { return w }; return nil }
+func as_builtinQ (v: MalVal) -> MalBuiltin? { if case .TypeBuiltin(let w) = v { return w }; return nil }
+func as_macroQ (v: MalVal) -> MalMacro? { if case .TypeMacro(let w) = v { return w }; return nil }
+
+func as_listQ (v: MalSequence) -> MalList? { return v as? MalList }
+func as_vectorQ (v: MalSequence) -> MalVector? { return v as? MalVector }
+
+func as_sequenceQ (v: MalVal) -> MalSequence? {
+ switch v {
+ case .TypeList(let v): return v
+ case .TypeVector(let v): return v
+ default: return nil
+ }
+}
+func as_functionQ (v: MalVal) -> MalFunction? {
+ switch v {
+ case .TypeClosure(let v): return v
+ case .TypeBuiltin(let v): return v
+ default: return nil
+ }
+}
+
+func as_inttypeQ (v: MalVal) -> MalIntType? { return as_integerQ(v) }
+func as_floattypeQ (v: MalVal) -> MalFloatType? { return as_floatQ(v) }
+func as_stringtypeQ (v: MalVal) -> MalStringType? { return as_stringQ(v) }
+
+// ==================== Exceptions ====================
+
+enum MalException: ErrorType, CustomStringConvertible {
+ case None
+ case Message(String)
+ case Object(MalVal)
+
+ var exception: MalVal {
+ switch self {
+ case .None:
+ return make_nil()
+ case .Message(let v):
+ return make_string(v)
+ case .Object(let v):
+ return v
+ }
+ }
+
+ // CustomStringConvertible
+ //
+ var description: String {
+ switch self {
+ case .None:
+ return "NIL Exception"
+ case .Message(let v):
+ return v
+ case .Object(let v):
+ return v.description
+ }
+ }
+}
+
+@noreturn
+func throw_error(v: String) throws { throw MalException.Message(v) }
+
+@noreturn
+func throw_error(v: MalVal) throws { throw MalException.Object(v) }
+
+// ==================== Utilities ====================
+
+@noreturn private func die(msg: String) {
+ preconditionFailure(msg)
+}
+
+@noreturn private func die() {
+ die("Should not get here")
+}
+
+func get_meta(v: MalVal) -> MalVal? {
+ switch v {
+ case .TypeUnknown: return nil
+ case .TypeNil: return nil
+ case .TypeTrue: return nil
+ case .TypeFalse: return nil
+ case .TypeComment: return nil
+ case .TypeInteger: return nil
+ case .TypeFloat: return nil
+ case .TypeSymbol: return nil
+ case .TypeKeyword: return nil
+ case .TypeString: return nil
+ case .TypeList (let v): return v.meta
+ case .TypeVector (let v): return v.meta
+ case .TypeHashMap (let v): return v.meta
+ case .TypeAtom (let v): return v.meta
+ case .TypeClosure (let v): return v.meta
+ case .TypeBuiltin (let v): return v.meta
+ case .TypeMacro (let v): return v.meta
+ }
+}
+
+func with_meta(obj: MalVal, _ meta: MalVal) -> MalVal {
+ switch obj {
+ case .TypeUnknown: return obj
+ case .TypeNil: return obj
+ case .TypeTrue: return obj
+ case .TypeFalse: return obj
+ case .TypeComment: return obj
+ case .TypeInteger: return obj
+ case .TypeFloat: return obj
+ case .TypeSymbol: return obj
+ case .TypeKeyword: return obj
+ case .TypeString: return obj
+ case .TypeList (let v): return make_list(MalList(v, meta))
+ case .TypeVector (let v): return make_vector(MalVector(v, meta))
+ case .TypeHashMap (let v): return make_hashmap(MalHashMap(v, meta))
+ case .TypeAtom (let v): return make_atom(MalAtom(v, meta))
+ case .TypeClosure (let v): return make_closure(MalClosure(v, meta))
+ case .TypeBuiltin (let v): return make_builtin(MalBuiltin(v, meta))
+ case .TypeMacro (let v): return make_macro(MalMacro(v, meta))
+ }
+}
+
+func unescape(s: String) -> String {
+ var index = 0
+ var prev_is_escape = false
+ var str = ""
+ let chars = s.characters
+ for ch in chars {
+ if index == chars.count - 1 { continue }
+ if index++ == 0 { continue }
+ if prev_is_escape {
+ prev_is_escape = false
+ if ch == "n" { str.appendContentsOf("\n") }
+ else if ch == "r" { str.appendContentsOf("\r") }
+ else if ch == "t" { str.appendContentsOf("\t") }
+ else { str.append(ch) }
+ } else if ch == "\\" {
+ prev_is_escape = true
+ } else {
+ str.append(ch)
+ }
+ }
+ return str
+}
+
+func escape(s: String) -> String {
+ var str = ""
+ let chars = s.characters
+ for ch in chars {
+ if ch == "\n" { str.appendContentsOf("\\n"); continue }
+ if ch == "\r" { str.appendContentsOf("\\r"); continue }
+ if ch == "\t" { str.appendContentsOf("\\t"); continue }
+ if ch == "\"" || ch == "\\" { str.appendContentsOf("\\") }
+ str.append(ch)
+ }
+ str = "\"" + str + "\""
+ return str
+}