apricot 0.0.1 → 0.0.2
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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.ruby-version +1 -1
- data/.travis.yml +1 -0
- data/Gemfile.lock +229 -11
- data/README.md +46 -29
- data/Rakefile +1 -1
- data/apricot.gemspec +7 -3
- data/benchmarks/factorial.rb +51 -0
- data/benchmarks/interpolate.rb +20 -0
- data/bin/apricot +5 -23
- data/examples/bot.apr +1 -4
- data/examples/cinch-bot.apr +3 -3
- data/examples/sinatra.apr +9 -0
- data/kernel/core.apr +124 -75
- data/kernel/repl.apr +37 -0
- data/lib/apricot.rb +7 -26
- data/lib/apricot/boot.rb +24 -0
- data/lib/apricot/code_loader.rb +108 -0
- data/lib/apricot/compiler.rb +265 -32
- data/lib/apricot/generator.rb +10 -3
- data/lib/apricot/identifier.rb +25 -10
- data/lib/apricot/list.rb +28 -41
- data/lib/apricot/macroexpand.rb +14 -8
- data/lib/apricot/misc.rb +2 -1
- data/lib/apricot/namespace.rb +20 -3
- data/lib/apricot/{parser.rb → reader.rb} +221 -194
- data/lib/apricot/repl.rb +67 -24
- data/lib/apricot/ruby_ext.rb +27 -16
- data/lib/apricot/scopes.rb +159 -0
- data/lib/apricot/seq.rb +43 -1
- data/lib/apricot/special_forms.rb +16 -695
- data/lib/apricot/special_forms/def.rb +32 -0
- data/lib/apricot/special_forms/do.rb +23 -0
- data/lib/apricot/special_forms/dot.rb +112 -0
- data/lib/apricot/special_forms/fn.rb +342 -0
- data/lib/apricot/special_forms/if.rb +31 -0
- data/lib/apricot/special_forms/let.rb +8 -0
- data/lib/apricot/special_forms/loop.rb +10 -0
- data/lib/apricot/special_forms/quote.rb +9 -0
- data/lib/apricot/special_forms/recur.rb +26 -0
- data/lib/apricot/special_forms/try.rb +146 -0
- data/lib/apricot/variables.rb +65 -0
- data/lib/apricot/version.rb +1 -1
- data/spec/compiler_spec.rb +53 -450
- data/spec/fn_spec.rb +206 -0
- data/spec/list_spec.rb +1 -1
- data/spec/reader_spec.rb +349 -0
- data/spec/spec_helper.rb +40 -4
- data/spec/special_forms_spec.rb +203 -0
- metadata +99 -133
- data/lib/apricot/ast.rb +0 -3
- data/lib/apricot/ast/identifier.rb +0 -111
- data/lib/apricot/ast/list.rb +0 -99
- data/lib/apricot/ast/literals.rb +0 -240
- data/lib/apricot/ast/node.rb +0 -45
- data/lib/apricot/ast/scopes.rb +0 -147
- data/lib/apricot/ast/toplevel.rb +0 -66
- data/lib/apricot/ast/variables.rb +0 -64
- data/lib/apricot/printers.rb +0 -12
- data/lib/apricot/stages.rb +0 -60
- data/spec/parser_spec.rb +0 -312
data/lib/apricot/ast.rb
DELETED
@@ -1,111 +0,0 @@
|
|
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
|
data/lib/apricot/ast/list.rb
DELETED
@@ -1,99 +0,0 @@
|
|
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
|
data/lib/apricot/ast/literals.rb
DELETED
@@ -1,240 +0,0 @@
|
|
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
|