apricot 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +3 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +26 -0
- data/README.md +90 -0
- data/Rakefile +9 -0
- data/apricot.gemspec +22 -0
- data/bin/apricot +58 -0
- data/examples/bot.apr +23 -0
- data/examples/cinch-bot.apr +12 -0
- data/examples/hanoi.apr +10 -0
- data/examples/hello.apr +1 -0
- data/examples/plot.apr +28 -0
- data/examples/quine.apr +1 -0
- data/kernel/core.apr +928 -0
- data/lib/apricot/ast/identifier.rb +111 -0
- data/lib/apricot/ast/list.rb +99 -0
- data/lib/apricot/ast/literals.rb +240 -0
- data/lib/apricot/ast/node.rb +45 -0
- data/lib/apricot/ast/scopes.rb +147 -0
- data/lib/apricot/ast/toplevel.rb +66 -0
- data/lib/apricot/ast/variables.rb +64 -0
- data/lib/apricot/ast.rb +3 -0
- data/lib/apricot/compiler.rb +55 -0
- data/lib/apricot/cons.rb +27 -0
- data/lib/apricot/errors.rb +38 -0
- data/lib/apricot/generator.rb +15 -0
- data/lib/apricot/identifier.rb +91 -0
- data/lib/apricot/list.rb +96 -0
- data/lib/apricot/macroexpand.rb +47 -0
- data/lib/apricot/misc.rb +11 -0
- data/lib/apricot/namespace.rb +59 -0
- data/lib/apricot/parser.rb +541 -0
- data/lib/apricot/printers.rb +12 -0
- data/lib/apricot/repl.rb +254 -0
- data/lib/apricot/ruby_ext.rb +254 -0
- data/lib/apricot/seq.rb +44 -0
- data/lib/apricot/special_forms.rb +735 -0
- data/lib/apricot/stages.rb +60 -0
- data/lib/apricot/version.rb +3 -0
- data/lib/apricot.rb +30 -0
- data/spec/compiler_spec.rb +499 -0
- data/spec/identifier_spec.rb +58 -0
- data/spec/list_spec.rb +96 -0
- data/spec/parser_spec.rb +312 -0
- data/spec/spec_helper.rb +10 -0
- metadata +188 -0
@@ -0,0 +1,111 @@
|
|
1
|
+
module Apricot
|
2
|
+
module AST
|
3
|
+
class Identifier < Node
|
4
|
+
def initialize(line, name)
|
5
|
+
super(line)
|
6
|
+
@id = Apricot::Identifier.intern(name)
|
7
|
+
end
|
8
|
+
|
9
|
+
def reference(g)
|
10
|
+
@reference ||= if name == :self
|
11
|
+
SelfReference.new
|
12
|
+
elsif qualified?
|
13
|
+
NamespaceReference.new(unqualified_name, ns)
|
14
|
+
else
|
15
|
+
g.scope.find_var(name)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def name
|
20
|
+
@id.name
|
21
|
+
end
|
22
|
+
|
23
|
+
def constant?
|
24
|
+
@id.constant?
|
25
|
+
end
|
26
|
+
|
27
|
+
def qualified?
|
28
|
+
@id.qualified?
|
29
|
+
end
|
30
|
+
|
31
|
+
def ns
|
32
|
+
@id.ns
|
33
|
+
end
|
34
|
+
|
35
|
+
def unqualified_name
|
36
|
+
@id.unqualified_name
|
37
|
+
end
|
38
|
+
|
39
|
+
def const_names
|
40
|
+
@id.const_names
|
41
|
+
end
|
42
|
+
|
43
|
+
def bytecode(g)
|
44
|
+
pos(g)
|
45
|
+
|
46
|
+
if constant?
|
47
|
+
g.push_const const_names.first
|
48
|
+
const_names.drop(1).each {|n| g.find_const n }
|
49
|
+
else
|
50
|
+
reference(g).bytecode(g)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# called by (def <identifier> <value>)
|
55
|
+
def assign_bytecode(g, value)
|
56
|
+
if constant?
|
57
|
+
if const_names.length == 1
|
58
|
+
g.push_scope
|
59
|
+
else
|
60
|
+
g.push_const const_names[0]
|
61
|
+
const_names[1..-2].each {|n| g.find_const n }
|
62
|
+
end
|
63
|
+
|
64
|
+
g.push_literal const_names.last
|
65
|
+
value.bytecode(g)
|
66
|
+
g.send :const_set, 2
|
67
|
+
else
|
68
|
+
g.compile_error "Can't change the value of self" if name == :self
|
69
|
+
|
70
|
+
g.push_const :Apricot
|
71
|
+
g.send :current_namespace, 0
|
72
|
+
g.push_literal name
|
73
|
+
value.bytecode(g)
|
74
|
+
g.send :set_var, 2
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def quote_bytecode(g)
|
79
|
+
pos(g)
|
80
|
+
|
81
|
+
g.push_const :Apricot
|
82
|
+
g.find_const :Identifier
|
83
|
+
g.push_literal name
|
84
|
+
g.send :intern, 1
|
85
|
+
end
|
86
|
+
|
87
|
+
def meta(g)
|
88
|
+
ref = reference(g)
|
89
|
+
ref.is_a?(NamespaceReference) && ref.meta
|
90
|
+
end
|
91
|
+
|
92
|
+
def namespace_fn?(g)
|
93
|
+
ref = reference(g)
|
94
|
+
ref.is_a?(NamespaceReference) && ref.fn?
|
95
|
+
end
|
96
|
+
|
97
|
+
def module_method?(g)
|
98
|
+
ref = reference(g)
|
99
|
+
ref.is_a?(NamespaceReference) && ref.method?
|
100
|
+
end
|
101
|
+
|
102
|
+
def to_value
|
103
|
+
@id
|
104
|
+
end
|
105
|
+
|
106
|
+
def node_equal?(other)
|
107
|
+
self.name == other.name
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Apricot::AST
|
2
|
+
class List < Node
|
3
|
+
attr_reader :elements
|
4
|
+
|
5
|
+
def initialize(line, elements)
|
6
|
+
super(line)
|
7
|
+
@elements = elements
|
8
|
+
end
|
9
|
+
|
10
|
+
def bytecode(g, macroexpand = true)
|
11
|
+
pos(g)
|
12
|
+
|
13
|
+
if @elements.empty?
|
14
|
+
quote_bytecode(g)
|
15
|
+
return
|
16
|
+
end
|
17
|
+
|
18
|
+
callee = @elements.first
|
19
|
+
args = @elements.drop(1)
|
20
|
+
|
21
|
+
# Handle special forms such as def, let, fn, quote, etc
|
22
|
+
if callee.is_a?(Identifier) && special = Apricot::SpecialForm[callee.name]
|
23
|
+
special.bytecode(g, args)
|
24
|
+
return
|
25
|
+
end
|
26
|
+
|
27
|
+
if macroexpand
|
28
|
+
form = Node.from_value(Apricot.macroexpand(self.to_value), line)
|
29
|
+
|
30
|
+
# Avoid recursing and macroexpanding again if expansion returns a list
|
31
|
+
if form.is_a?(List)
|
32
|
+
form.bytecode(g, false)
|
33
|
+
else
|
34
|
+
form.bytecode(g)
|
35
|
+
end
|
36
|
+
return
|
37
|
+
end
|
38
|
+
|
39
|
+
# Handle (foo ...) and (Foo/bar ...) calls
|
40
|
+
if callee.is_a?(Identifier)
|
41
|
+
meta = callee.meta(g)
|
42
|
+
|
43
|
+
# Handle inlinable function calls
|
44
|
+
if meta && meta[:inline] && (!meta[:'inline-arities'] ||
|
45
|
+
meta[:'inline-arities'].apricot_call(args.length))
|
46
|
+
# Apply the inliner macro to the arguments and compile the result.
|
47
|
+
inliner = meta[:inline]
|
48
|
+
args.map!(&:to_value)
|
49
|
+
|
50
|
+
begin
|
51
|
+
inlined_form = inliner.apricot_call(*args)
|
52
|
+
rescue => e
|
53
|
+
g.compile_error "Inliner macro for '#{callee.name}' raised an exception:\n #{e}"
|
54
|
+
end
|
55
|
+
|
56
|
+
Node.from_value(inlined_form, line).bytecode(g)
|
57
|
+
return
|
58
|
+
elsif callee.namespace_fn?(g) || callee.module_method?(g)
|
59
|
+
ns_id = Apricot::Identifier.intern(callee.ns.name)
|
60
|
+
g.push_const ns_id.const_names.first
|
61
|
+
ns_id.const_names.drop(1).each {|n| g.find_const(n) }
|
62
|
+
|
63
|
+
args.each {|arg| arg.bytecode(g) }
|
64
|
+
g.send callee.unqualified_name, args.length
|
65
|
+
return
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Handle everything else
|
70
|
+
callee.bytecode(g)
|
71
|
+
args.each {|arg| arg.bytecode(g) }
|
72
|
+
g.send :apricot_call, args.length
|
73
|
+
end
|
74
|
+
|
75
|
+
def quote_bytecode(g)
|
76
|
+
g.push_const :Apricot
|
77
|
+
g.find_const :List
|
78
|
+
|
79
|
+
if @elements.empty?
|
80
|
+
g.find_const :EmptyList
|
81
|
+
else
|
82
|
+
@elements.each {|e| e.quote_bytecode(g) }
|
83
|
+
g.send :[], @elements.length
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_value
|
88
|
+
Apricot::List[*@elements.map(&:to_value)]
|
89
|
+
end
|
90
|
+
|
91
|
+
def node_equal?(other)
|
92
|
+
self.elements == other.elements
|
93
|
+
end
|
94
|
+
|
95
|
+
def [](*i)
|
96
|
+
@elements[*i]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,240 @@
|
|
1
|
+
module Apricot::AST
|
2
|
+
class BasicLiteral < Node
|
3
|
+
def quote_bytecode(g)
|
4
|
+
bytecode(g)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class Literal < BasicLiteral
|
9
|
+
attr_reader :value
|
10
|
+
|
11
|
+
def initialize(line, value)
|
12
|
+
super(line)
|
13
|
+
@value = value
|
14
|
+
end
|
15
|
+
|
16
|
+
def bytecode(g)
|
17
|
+
pos(g)
|
18
|
+
g.push @value
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_value
|
22
|
+
case @value
|
23
|
+
when :true then true
|
24
|
+
when :false then false
|
25
|
+
when :nil then nil
|
26
|
+
else @value
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def node_equal?(other)
|
31
|
+
self.value == other.value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.new_integer(line, value)
|
36
|
+
case value
|
37
|
+
when Bignum
|
38
|
+
BignumLiteral.new(line, value)
|
39
|
+
when Fixnum
|
40
|
+
Literal.new(line, value)
|
41
|
+
else
|
42
|
+
raise TypeError, "#{value} is not an integer"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class BignumLiteral < Literal
|
47
|
+
def bytecode(g)
|
48
|
+
pos(g)
|
49
|
+
g.push_unique_literal @value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class FloatLiteral < Literal
|
54
|
+
def bytecode(g)
|
55
|
+
pos(g)
|
56
|
+
g.push_unique_literal @value
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class SymbolLiteral < Literal
|
61
|
+
def bytecode(g)
|
62
|
+
pos(g)
|
63
|
+
g.push_literal @value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class StringLiteral < Literal
|
68
|
+
def bytecode(g)
|
69
|
+
pos(g)
|
70
|
+
g.push_literal @value
|
71
|
+
g.string_dup # Duplicate string to avoid mutating the literal
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class RationalLiteral < BasicLiteral
|
76
|
+
attr_reader :numerator, :denominator
|
77
|
+
|
78
|
+
def initialize(line, numerator, denominator)
|
79
|
+
super(line)
|
80
|
+
@numerator = numerator
|
81
|
+
@denominator = denominator
|
82
|
+
end
|
83
|
+
|
84
|
+
def bytecode(g)
|
85
|
+
pos(g)
|
86
|
+
|
87
|
+
# A rational literal should only be converted to a Rational the first
|
88
|
+
# time it is encountered. We push a literal nil here, and then overwrite
|
89
|
+
# the literal value with the created Rational if it is nil, i.e. the
|
90
|
+
# first time only. Subsequent encounters will use the previously created
|
91
|
+
# Rational. This idea was copied from Rubinius::AST::RegexLiteral.
|
92
|
+
idx = g.add_literal(nil)
|
93
|
+
g.push_literal_at idx
|
94
|
+
g.dup
|
95
|
+
g.is_nil
|
96
|
+
|
97
|
+
lbl = g.new_label
|
98
|
+
g.gif lbl
|
99
|
+
g.pop
|
100
|
+
g.push_self
|
101
|
+
g.push @numerator
|
102
|
+
g.push @denominator
|
103
|
+
g.send :Rational, 2, true
|
104
|
+
g.set_literal idx
|
105
|
+
lbl.set!
|
106
|
+
end
|
107
|
+
|
108
|
+
def to_value
|
109
|
+
Rational(@numerator, @denominator)
|
110
|
+
end
|
111
|
+
|
112
|
+
def node_equal?(other)
|
113
|
+
self.numerator == other.numerator && self.denominator == other.denominator
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class RegexLiteral < BasicLiteral
|
118
|
+
attr_accessor :pattern, :options
|
119
|
+
|
120
|
+
def initialize(line, pattern, options)
|
121
|
+
super(line)
|
122
|
+
@pattern = pattern
|
123
|
+
@options = options
|
124
|
+
end
|
125
|
+
|
126
|
+
def bytecode(g)
|
127
|
+
pos(g)
|
128
|
+
|
129
|
+
idx = g.add_literal(nil)
|
130
|
+
g.push_literal_at idx
|
131
|
+
g.dup
|
132
|
+
g.is_nil
|
133
|
+
|
134
|
+
lbl = g.new_label
|
135
|
+
g.gif lbl
|
136
|
+
g.pop
|
137
|
+
g.push_const :Regexp
|
138
|
+
g.push_literal @pattern
|
139
|
+
g.push @options
|
140
|
+
g.send :new, 2
|
141
|
+
g.set_literal idx
|
142
|
+
lbl.set!
|
143
|
+
end
|
144
|
+
|
145
|
+
def to_value
|
146
|
+
Regexp.new(@pattern, @options)
|
147
|
+
end
|
148
|
+
|
149
|
+
def node_equal?(other)
|
150
|
+
self.pattern == other.pattern && self.options == other.options
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
class CollectionLiteral < Node
|
155
|
+
attr_reader :elements
|
156
|
+
|
157
|
+
def initialize(line, elements)
|
158
|
+
super(line)
|
159
|
+
@elements = elements
|
160
|
+
end
|
161
|
+
|
162
|
+
def quote_bytecode(g)
|
163
|
+
bytecode(g, true)
|
164
|
+
end
|
165
|
+
|
166
|
+
def node_equal?(other)
|
167
|
+
self.elements == other.elements
|
168
|
+
end
|
169
|
+
|
170
|
+
def [](*i)
|
171
|
+
@elements[*i]
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
class ArrayLiteral < CollectionLiteral
|
176
|
+
def bytecode(g, quoted = false)
|
177
|
+
pos(g)
|
178
|
+
|
179
|
+
@elements.each {|e| quoted ? e.quote_bytecode(g) : e.bytecode(g) }
|
180
|
+
g.make_array @elements.length
|
181
|
+
end
|
182
|
+
|
183
|
+
def to_value
|
184
|
+
@elements.map(&:to_value)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
class HashLiteral < CollectionLiteral
|
189
|
+
def bytecode(g, quoted = false)
|
190
|
+
pos(g)
|
191
|
+
|
192
|
+
# Create a new Hash
|
193
|
+
g.push_const :Hash
|
194
|
+
g.push(@elements.length / 2)
|
195
|
+
g.send :new_from_literal, 1
|
196
|
+
|
197
|
+
# Add keys and values
|
198
|
+
@elements.each_slice(2) do |k, v|
|
199
|
+
g.dup # The Hash
|
200
|
+
|
201
|
+
if quoted
|
202
|
+
k.quote_bytecode(g)
|
203
|
+
v.quote_bytecode(g)
|
204
|
+
else
|
205
|
+
k.bytecode(g)
|
206
|
+
v.bytecode(g)
|
207
|
+
end
|
208
|
+
|
209
|
+
g.send :[]=, 2
|
210
|
+
g.pop # []= leaves v on the stack
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def to_value
|
215
|
+
h = {}
|
216
|
+
@elements.each_slice(2) {|k,v| h[k.to_value] = v.to_value }
|
217
|
+
h
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
class SetLiteral < CollectionLiteral
|
222
|
+
def bytecode(g, quoted = false)
|
223
|
+
pos(g)
|
224
|
+
|
225
|
+
g.push_const :Set
|
226
|
+
g.send :new, 0 # TODO: Inline this new?
|
227
|
+
|
228
|
+
@elements.each do |e|
|
229
|
+
quoted ? e.quote_bytecode(g) : e.bytecode(g)
|
230
|
+
g.send :add, 1
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def to_value
|
235
|
+
s = Set.new
|
236
|
+
@elements.each {|e| s << e.to_value }
|
237
|
+
s
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Apricot
|
2
|
+
module AST
|
3
|
+
class Node
|
4
|
+
attr_reader :line
|
5
|
+
|
6
|
+
def initialize(line)
|
7
|
+
@line = line
|
8
|
+
end
|
9
|
+
|
10
|
+
def pos(g)
|
11
|
+
g.set_line(@line)
|
12
|
+
end
|
13
|
+
|
14
|
+
def ==(other)
|
15
|
+
return true if self.equal?(other)
|
16
|
+
return false unless self.class == other.class
|
17
|
+
self.node_equal?(other)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.from_value(val, line = 0)
|
21
|
+
case val
|
22
|
+
when true then Literal.new(line, :true)
|
23
|
+
when false then Literal.new(line, :false)
|
24
|
+
when nil then Literal.new(line, :nil)
|
25
|
+
when Integer then AST.new_integer(line, val)
|
26
|
+
when Symbol then SymbolLiteral.new(line, val)
|
27
|
+
when Float then FloatLiteral.new(line, val)
|
28
|
+
when String then StringLiteral.new(line, val)
|
29
|
+
when Rational then RationalLiteral.new(line, val.numerator, val.denominator)
|
30
|
+
when Regexp then RegexLiteral.new(line, val.source, val.options)
|
31
|
+
when Array then ArrayLiteral.new(line, val.map {|x| from_value x, line})
|
32
|
+
when Set then SetLiteral.new(line, val.map {|x| from_value x, line})
|
33
|
+
when Apricot::Identifier then Identifier.new(line, val.name)
|
34
|
+
when Apricot::Seq then List.new(line, val.map {|x| from_value x, line})
|
35
|
+
when Hash
|
36
|
+
elems = []
|
37
|
+
val.each_pair {|k,v| elems << from_value(k, line) << from_value(v, line) }
|
38
|
+
HashLiteral.new(line, elems)
|
39
|
+
else
|
40
|
+
raise TypeError, "No AST node for #{val} (#{val.class})"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module Apricot
|
2
|
+
module AST
|
3
|
+
# This is a scope with real local variable storage, i.e. it is part of a
|
4
|
+
# block of code like a fn or the top level program. Let scopes do not have
|
5
|
+
# storage and must ask for storage from one of these.
|
6
|
+
module StorageScope
|
7
|
+
def variable_names
|
8
|
+
@variable_names ||= []
|
9
|
+
end
|
10
|
+
|
11
|
+
def store_new_local(name)
|
12
|
+
slot = next_slot
|
13
|
+
variable_names << name
|
14
|
+
slot
|
15
|
+
end
|
16
|
+
|
17
|
+
def next_slot
|
18
|
+
variable_names.size
|
19
|
+
end
|
20
|
+
|
21
|
+
def local_count
|
22
|
+
variable_names.size
|
23
|
+
end
|
24
|
+
|
25
|
+
def local_names
|
26
|
+
variable_names
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Scope
|
31
|
+
attr_reader :parent, :variables
|
32
|
+
# The loop label stores the code location where a (recur) form should
|
33
|
+
# jump to. The secondary loop label is used in the case of recur in a fn
|
34
|
+
# overload with variadic arguments. If the array passed for the variadic
|
35
|
+
# arguments in the recur is empty, it should instead jump to the
|
36
|
+
# matching non-variadic overload, if applicable.
|
37
|
+
attr_accessor :loop_label, :secondary_loop_label
|
38
|
+
|
39
|
+
def initialize(parent)
|
40
|
+
@parent = parent
|
41
|
+
@variables = {}
|
42
|
+
@loop_label = nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class FnScope < Scope
|
47
|
+
attr_reader :name, :self_reference
|
48
|
+
|
49
|
+
def initialize(parent, name)
|
50
|
+
super(parent)
|
51
|
+
|
52
|
+
if name
|
53
|
+
@name = name
|
54
|
+
name_slot = @parent.store_new_local(name)
|
55
|
+
@self_reference = LocalReference.new(name_slot, 1)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# An identifier or a nested scope is looking up a variable. If the
|
60
|
+
# variable is found here, return a reference to it. Otherwise look it up
|
61
|
+
# on the parent and increment its depth because it is beyond the bounds
|
62
|
+
# of the current block of code (fn).
|
63
|
+
def find_var(name, depth = 0)
|
64
|
+
return @self_reference if name == @name
|
65
|
+
|
66
|
+
@parent.find_var(name, depth + 1)
|
67
|
+
end
|
68
|
+
|
69
|
+
# A (recur) is looking for a recursion target (ie. a loop or a fn
|
70
|
+
# overload scope).
|
71
|
+
def find_recur_target
|
72
|
+
@parent.find_recur_target
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class OverloadScope < Scope
|
77
|
+
include StorageScope
|
78
|
+
|
79
|
+
attr_accessor :splat
|
80
|
+
alias_method :splat?, :splat
|
81
|
+
|
82
|
+
attr_accessor :block_arg
|
83
|
+
|
84
|
+
def initialize(parent_fn)
|
85
|
+
super(parent_fn)
|
86
|
+
end
|
87
|
+
|
88
|
+
# An identifier or a nested scope is looking up a variable. If the
|
89
|
+
# variable is found here, return a reference to it. Otherwise look it up
|
90
|
+
# on the parent (a fn). Don't increase the depth, the lookup on the fn
|
91
|
+
# will do that, and if we do it twice then the generated
|
92
|
+
# push_local_depth instructions look up too many scopes.
|
93
|
+
def find_var(name, depth = 0)
|
94
|
+
if slot = @variables[name]
|
95
|
+
LocalReference.new(slot, depth)
|
96
|
+
else
|
97
|
+
@parent.find_var(name, depth)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Create a new local on the current level.
|
102
|
+
def new_local(name)
|
103
|
+
name = name.name if name.is_a? Identifier
|
104
|
+
@variables[name] = store_new_local(name)
|
105
|
+
end
|
106
|
+
|
107
|
+
# A (recur) is looking for a recursion target. This, being a fn
|
108
|
+
# overload, is one.
|
109
|
+
def find_recur_target
|
110
|
+
self
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# The let scope doesn't have real storage for locals. It stores its locals
|
115
|
+
# on the nearest enclosing real scope, which is any separate block of code
|
116
|
+
# such as a fn, defn, defmacro or the top level of the program.
|
117
|
+
class LetScope < Scope
|
118
|
+
# An identifier or a nested scope is looking up a variable.
|
119
|
+
def find_var(name, depth = 0)
|
120
|
+
if slot = @variables[name]
|
121
|
+
LocalReference.new(slot, depth)
|
122
|
+
else
|
123
|
+
@parent.find_var(name, depth)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Create a new local on the current level, with storage on the nearest
|
128
|
+
# enclosing real scope.
|
129
|
+
def new_local(name)
|
130
|
+
name = name.name if name.is_a? Identifier
|
131
|
+
@variables[name] = @parent.store_new_local(name)
|
132
|
+
end
|
133
|
+
|
134
|
+
# A deeper let is asking for a new local slot. Pass it along to the
|
135
|
+
# parent so it eventually reaches a real scope.
|
136
|
+
def store_new_local(name)
|
137
|
+
@parent.store_new_local(name)
|
138
|
+
end
|
139
|
+
|
140
|
+
# A (recur) is looking for a recursion target. This is one only if it is
|
141
|
+
# a (loop) form.
|
142
|
+
def find_recur_target
|
143
|
+
loop_label ? self : @parent.find_recur_target
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Apricot
|
2
|
+
module AST
|
3
|
+
class TopLevel < Node
|
4
|
+
include StorageScope
|
5
|
+
|
6
|
+
attr_reader :elements, :file
|
7
|
+
|
8
|
+
def initialize(elements, file, line = 1, evaluate = false)
|
9
|
+
@elements = elements
|
10
|
+
@file = file
|
11
|
+
@line = line
|
12
|
+
@evaluate = evaluate
|
13
|
+
end
|
14
|
+
|
15
|
+
def bytecode(g)
|
16
|
+
g.name = :__top_level__
|
17
|
+
g.file = @file.to_sym
|
18
|
+
|
19
|
+
g.scopes << self
|
20
|
+
|
21
|
+
pos(g)
|
22
|
+
|
23
|
+
if @elements.empty?
|
24
|
+
g.push_nil
|
25
|
+
else
|
26
|
+
@elements.each_with_index do |e, i|
|
27
|
+
g.pop unless i == 0
|
28
|
+
e.bytecode(g)
|
29
|
+
# We evaluate top level forms as we generate the bytecode for them
|
30
|
+
# so macros defined in a file can be used immediately after the
|
31
|
+
# definition.
|
32
|
+
Apricot::Compiler.eval_node(e, @file) if @evaluate
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
g.ret
|
37
|
+
|
38
|
+
g.scopes.pop
|
39
|
+
|
40
|
+
g.local_count = local_count
|
41
|
+
g.local_names = local_names
|
42
|
+
end
|
43
|
+
|
44
|
+
# A nested scope is looking up a variable. There are no local variables
|
45
|
+
# at the top level, so look up the variable on the current namespace.
|
46
|
+
def find_var(name, depth = nil)
|
47
|
+
# Ignore depth, it has no bearing on namespace lookups.
|
48
|
+
NamespaceReference.new(name)
|
49
|
+
end
|
50
|
+
|
51
|
+
def node_equal?(other)
|
52
|
+
self.file == other.file && self.elements == other.elements
|
53
|
+
end
|
54
|
+
|
55
|
+
def [](*i)
|
56
|
+
@elements[*i]
|
57
|
+
end
|
58
|
+
|
59
|
+
# A (recur) is looking for a recursion target. Since this is the top
|
60
|
+
# level, which has no parent, the lookup has failed.
|
61
|
+
def find_recur_target
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|