Commit | Line | Data |
---|---|---|
2539e6af KR |
1 | //****************************************************************************** |
2 | // MAL - env | |
3 | //****************************************************************************** | |
4 | ||
5 | import Foundation | |
6 | ||
7 | typealias EnvironmentVars = [MalSymbol: MalVal] | |
8 | ||
425305df KR |
9 | private let kSymbolAmpersand = as_symbol(make_symbol("&")) |
10 | private let kSymbolNil = as_symbol(make_symbol("")) | |
11 | private let kNil = make_nil() | |
2539e6af | 12 | |
425305df | 13 | final class Environment { |
2539e6af KR |
14 | init(outer: Environment?) { |
15 | self.outer = outer | |
16 | } | |
17 | ||
425305df KR |
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 | } | |
2539e6af KR |
23 | if sym != kSymbolAmpersand { |
24 | if index < exprs.count { | |
425305df | 25 | set(sym, try! exprs.nth(index)) |
2539e6af | 26 | } else { |
7728b50b | 27 | set(sym, kNil) |
2539e6af KR |
28 | } |
29 | continue | |
30 | } | |
425305df KR |
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) | |
2539e6af KR |
40 | break |
41 | } | |
7728b50b | 42 | return kNil |
2539e6af KR |
43 | } |
44 | ||
425305df KR |
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 | ||
2539e6af | 61 | func set(sym: MalSymbol, _ value: MalVal) -> MalVal { |
7728b50b KR |
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 | } | |
2539e6af KR |
91 | return value |
92 | } | |
93 | ||
94 | func get(sym: MalSymbol) -> MalVal? { | |
425305df | 95 | if num_bindings > 4 { if let val = data[sym] { return val }; return outer?.get(sym) } |
7728b50b KR |
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 } } | |
2539e6af KR |
100 | return outer?.get(sym) |
101 | } | |
102 | ||
103 | private var outer: Environment? | |
104 | private var data = EnvironmentVars() | |
7728b50b | 105 | private var num_bindings = 0 |
425305df KR |
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 | |
2539e6af | 114 | } |