Swift*: add seq/string?. swift: gensym/or macro fix
[jackhill/mal.git] / swift / env.swift
1 //******************************************************************************
2 // MAL - env
3 //******************************************************************************
4
5 import Foundation
6
7 typealias EnvironmentVars = [MalSymbol: MalVal]
8
9 private let kSymbolAmpersand = as_symbol(make_symbol("&"))
10 private let kSymbolNil = as_symbol(make_symbol(""))
11 private let kNil = make_nil()
12
13 final class Environment {
14 init(outer: Environment?) {
15 self.outer = outer
16 }
17
18 func set_bindings(binds: MalSequence, with_exprs exprs: MalSequence) throws -> MalVal {
19 for var index: MalIntType = 0; index < binds.count; ++index {
20 guard let sym = as_symbolQ(try! binds.nth(index)) else {
21 try throw_error("an entry in binds was not a symbol: index=\(index), binds[index]=\(try! binds.nth(index))")
22 }
23 if sym != kSymbolAmpersand {
24 if index < exprs.count {
25 set(sym, try! exprs.nth(index))
26 } else {
27 set(sym, kNil)
28 }
29 continue
30 }
31
32 guard (index + 1) < binds.count else {
33 try throw_error("found & but no symbol")
34 }
35 guard let rest_sym = as_symbolQ(try! binds.nth(index + 1)) else {
36 try throw_error("& was not followed by a symbol: index=\(index), binds[index]=\(try! binds.nth(index))")
37 }
38 let rest = exprs.range_from(index, to: exprs.count)
39 set(rest_sym, rest)
40 break
41 }
42 return kNil
43 }
44
45 // In this implementation, rather than storing everything in a dictionary,
46 // we optimize for small environments by having a hard-coded set of four
47 // slots. We use these slots when creating small environments, such as when
48 // a function is invoked. Testing shows that supporting up to four variables
49 // in this way is a good trade-off. Otherwise, if we have more than four
50 // variables, we switch over to using a dictionary. Testing also shows that
51 // trying to use both the slots and the dictionary for large environments is
52 // not as efficient as just completely switching over to the dictionary.
53 //
54 // Interestingly, even though the MalVal return value is hardly ever used at
55 // the call site, removing it and returning nothing is a performance loss.
56 // This is because returning 'value' allows the compiler to skip calling
57 // swift_release on it. The result is that set() calls swift_release twice
58 // (on self and sym), as opposed to three times (on self, sym, and value) if
59 // it were to return something other than one of the parameters.
60
61 func set(sym: MalSymbol, _ value: MalVal) -> MalVal {
62 if num_bindings == 0 {
63 slot_name0 = sym; slot_value0 = value; ++num_bindings
64 } else if num_bindings == 1 {
65 if slot_name0 == sym { slot_value0 = value }
66 else { slot_name1 = sym; slot_value1 = value; ++num_bindings }
67 } else if num_bindings == 2 {
68 if slot_name0 == sym { slot_value0 = value }
69 else if slot_name1 == sym { slot_value1 = value }
70 else { slot_name2 = sym; slot_value2 = value; ++num_bindings }
71 } else if num_bindings == 3 {
72 if slot_name0 == sym { slot_value0 = value }
73 else if slot_name1 == sym { slot_value1 = value }
74 else if slot_name2 == sym { slot_value2 = value }
75 else { slot_name3 = sym; slot_value3 = value; ++num_bindings }
76 } else if num_bindings == 4 {
77 if slot_name0 == sym { slot_value0 = value }
78 else if slot_name1 == sym { slot_value1 = value }
79 else if slot_name2 == sym { slot_value2 = value }
80 else if slot_name3 == sym { slot_value3 = value }
81 else {
82 data[slot_name0] = slot_value0
83 data[slot_name1] = slot_value1
84 data[slot_name2] = slot_value2
85 data[slot_name3] = slot_value3
86 data[sym] = value; ++num_bindings
87 }
88 } else {
89 data[sym] = value
90 }
91 return value
92 }
93
94 func get(sym: MalSymbol) -> MalVal? {
95 if num_bindings > 4 { if let val = data[sym] { return val }; return outer?.get(sym) }
96 if num_bindings > 3 { if slot_name3 == sym { return slot_value3 } }
97 if num_bindings > 2 { if slot_name2 == sym { return slot_value2 } }
98 if num_bindings > 1 { if slot_name1 == sym { return slot_value1 } }
99 if num_bindings > 0 { if slot_name0 == sym { return slot_value0 } }
100 return outer?.get(sym)
101 }
102
103 private var outer: Environment?
104 private var data = EnvironmentVars()
105 private var num_bindings = 0
106 private var slot_name0 = kSymbolNil
107 private var slot_name1 = kSymbolNil
108 private var slot_name2 = kSymbolNil
109 private var slot_name3 = kSymbolNil
110 private var slot_value0 = kNil
111 private var slot_value1 = kNil
112 private var slot_value2 = kNil
113 private var slot_value3 = kNil
114 }