apricot 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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/boot.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Apricot
|
2
|
+
LIB_PATH = File.expand_path("../../../kernel", __FILE__)
|
3
|
+
$LOAD_PATH << LIB_PATH
|
4
|
+
|
5
|
+
Core = Namespace.new
|
6
|
+
|
7
|
+
Core.set_var(:"*ns*", Core)
|
8
|
+
|
9
|
+
Core.set_var(:"in-ns", lambda do |constant|
|
10
|
+
Apricot.current_namespace = Namespace.find_or_create constant
|
11
|
+
end)
|
12
|
+
|
13
|
+
Core.set_var(:ns, lambda do |constant|
|
14
|
+
List[Identifier.intern(:"in-ns"),
|
15
|
+
List[Identifier.intern(:quote), constant]]
|
16
|
+
end)
|
17
|
+
Core.get_var(:ns).apricot_meta = {:macro => true}
|
18
|
+
|
19
|
+
Apricot.require "core"
|
20
|
+
|
21
|
+
# ::User = Namespace.new
|
22
|
+
Apricot.current_namespace = Core
|
23
|
+
# TODO: make Apricot::Core public vars visible in User, default to User
|
24
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Apricot
|
2
|
+
def self.load(file)
|
3
|
+
CodeLoader.load(file)
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.require(file)
|
7
|
+
CodeLoader.require(file)
|
8
|
+
end
|
9
|
+
|
10
|
+
module CodeLoader
|
11
|
+
module_function
|
12
|
+
|
13
|
+
def load(path)
|
14
|
+
full_path = find_source(path)
|
15
|
+
raise LoadError, "no such file to load -- #{path}" unless full_path
|
16
|
+
|
17
|
+
load_file full_path
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def require(path)
|
22
|
+
full_path = find_source(path)
|
23
|
+
raise LoadError, "no such file to load -- #{path}" unless full_path
|
24
|
+
|
25
|
+
if loaded? full_path
|
26
|
+
false
|
27
|
+
else
|
28
|
+
load_file full_path
|
29
|
+
$LOADED_FEATURES << full_path
|
30
|
+
true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def load_file(path)
|
35
|
+
compiled_name = Rubinius::ToolSet::Runtime::Compiler.compiled_name(path)
|
36
|
+
|
37
|
+
stat = File::Stat.stat path
|
38
|
+
compiled_stat = File::Stat.stat compiled_name
|
39
|
+
|
40
|
+
# Try to load the cached bytecode if it exists and is newer than the
|
41
|
+
# source file.
|
42
|
+
if stat && compiled_stat && stat.mtime < compiled_stat.mtime
|
43
|
+
begin
|
44
|
+
code = Rubinius.invoke_primitive :compiledfile_load, compiled_name,
|
45
|
+
Rubinius::Signature, Rubinius::RUBY_LIB_VERSION
|
46
|
+
usable = true
|
47
|
+
rescue Rubinius::Internal
|
48
|
+
usable = false
|
49
|
+
end
|
50
|
+
|
51
|
+
if usable
|
52
|
+
Rubinius.run_script code
|
53
|
+
return
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
Compiler.compile_and_eval_file path
|
58
|
+
end
|
59
|
+
|
60
|
+
def find_source(path)
|
61
|
+
path += ".apr" unless has_extension? path
|
62
|
+
path = File.expand_path path if home_path? path
|
63
|
+
|
64
|
+
if qualified_path? path
|
65
|
+
if loadable? path
|
66
|
+
path
|
67
|
+
else
|
68
|
+
false
|
69
|
+
end
|
70
|
+
else
|
71
|
+
search_load_path path
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def search_load_path(path)
|
76
|
+
$LOAD_PATH.each do |dir|
|
77
|
+
full_path = "#{dir}/#{path}"
|
78
|
+
return full_path if loadable? full_path
|
79
|
+
end
|
80
|
+
|
81
|
+
false
|
82
|
+
end
|
83
|
+
|
84
|
+
def has_extension?(path)
|
85
|
+
!File.extname(path).empty?
|
86
|
+
end
|
87
|
+
|
88
|
+
def home_path?(path)
|
89
|
+
path[0] == '~'
|
90
|
+
end
|
91
|
+
|
92
|
+
def qualified_path?(path)
|
93
|
+
# TODO: fix for Windows
|
94
|
+
path[0] == '/' || path.prefix?("./") || path.prefix?("../")
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns true if the path exists, is a regular file, and is readable.
|
98
|
+
def loadable?(path)
|
99
|
+
@stat = File::Stat.stat path
|
100
|
+
return false unless @stat
|
101
|
+
@stat.file? and @stat.readable?
|
102
|
+
end
|
103
|
+
|
104
|
+
def loaded?(path)
|
105
|
+
$LOADED_FEATURES.include?(path)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
data/lib/apricot/compiler.rb
CHANGED
@@ -1,55 +1,288 @@
|
|
1
1
|
module Apricot
|
2
|
-
|
3
|
-
|
4
|
-
compiler = new :apricot_file, :compiled_file
|
2
|
+
module Compiler
|
3
|
+
module_function
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
def generate(forms, file = "(none)", line = 1, evaluate = false)
|
6
|
+
g = Generator.new
|
7
|
+
g.name = :__apricot__
|
8
|
+
g.file = file.to_sym
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
g.scopes << TopLevelScope.new
|
11
|
+
|
12
|
+
g.set_line(line)
|
13
|
+
|
14
|
+
if forms.empty?
|
15
|
+
g.push_nil
|
16
|
+
else
|
17
|
+
forms.each_with_index do |e, i|
|
18
|
+
g.pop unless i == 0
|
19
|
+
bytecode(g, e)
|
20
|
+
|
21
|
+
# We evaluate top level forms as we generate the bytecode for them
|
22
|
+
# so macros can be used immediately after their definitions.
|
23
|
+
eval_form(e, file) if evaluate
|
24
|
+
end
|
25
|
+
end
|
12
26
|
|
13
|
-
|
14
|
-
compiler = new :apricot_string, :compiled_method
|
27
|
+
g.ret
|
15
28
|
|
16
|
-
|
17
|
-
|
29
|
+
scope = g.scopes.pop
|
30
|
+
g.local_count = scope.local_count
|
31
|
+
g.local_names = scope.local_names
|
18
32
|
|
19
|
-
|
33
|
+
g.close
|
34
|
+
g.encode
|
35
|
+
cc = g.package(Rubinius::CompiledCode)
|
36
|
+
cc.scope = Rubinius::ConstantScope.new(Object)
|
37
|
+
cc
|
20
38
|
end
|
21
39
|
|
22
|
-
def
|
23
|
-
|
40
|
+
def compile_and_eval_file(file)
|
41
|
+
cc = generate(Reader.read_file(file), file, 1, true)
|
42
|
+
|
43
|
+
if Rubinius::CodeLoader.save_compiled?
|
44
|
+
compiled_name = Rubinius::ToolSet::Runtime::Compiler.compiled_name(file)
|
24
45
|
|
25
|
-
|
26
|
-
compiler.packager.print(BytecodePrinter) if debug
|
46
|
+
dir = File.dirname(compiled_name)
|
27
47
|
|
28
|
-
|
48
|
+
unless File.directory?(dir)
|
49
|
+
parts = []
|
50
|
+
|
51
|
+
until dir == "/" or dir == "."
|
52
|
+
parts << dir
|
53
|
+
dir = File.dirname(dir)
|
54
|
+
end
|
55
|
+
|
56
|
+
parts.reverse_each do |d|
|
57
|
+
Dir.mkdir d unless File.directory?(d)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
Rubinius::ToolSet::Runtime::CompiledFile.dump cc, compiled_name,
|
62
|
+
Rubinius::Signature, Rubinius::RUBY_LIB_VERSION
|
63
|
+
end
|
64
|
+
|
65
|
+
cc
|
29
66
|
end
|
30
67
|
|
31
|
-
def
|
32
|
-
|
68
|
+
def compile_form(form, file = "(eval)", line = 1)
|
69
|
+
generate([form], file, line)
|
33
70
|
end
|
34
71
|
|
35
|
-
def
|
36
|
-
|
37
|
-
cc
|
72
|
+
def eval_form(form, file = "(eval)", line = 1)
|
73
|
+
Rubinius.run_script(compile_form(form, file, line))
|
38
74
|
end
|
39
75
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
76
|
+
def eval(code, file = "(eval)", line = 1)
|
77
|
+
forms = Reader.read_string(code, file,line)
|
78
|
+
|
79
|
+
return nil if forms.empty?
|
80
|
+
|
81
|
+
forms[0..-2].each do |form|
|
82
|
+
eval_form(form, file, line)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Return the result of the last form in the program.
|
86
|
+
eval_form(forms.last, file, line)
|
43
87
|
end
|
44
88
|
|
45
|
-
|
46
|
-
|
47
|
-
|
89
|
+
SELF = Identifier.intern(:self)
|
90
|
+
|
91
|
+
def bytecode(g, form, quoted = false, macroexpand = true)
|
92
|
+
pos(g, form)
|
93
|
+
|
94
|
+
case form
|
95
|
+
when Identifier
|
96
|
+
if quoted
|
97
|
+
g.push_const :Apricot
|
98
|
+
g.find_const :Identifier
|
99
|
+
g.push_literal form.name
|
100
|
+
g.send :intern, 1
|
101
|
+
else
|
102
|
+
if form.constant?
|
103
|
+
g.push_const form.const_names.first
|
104
|
+
form.const_names.drop(1).each {|n| g.find_const n }
|
105
|
+
elsif form == SELF
|
106
|
+
g.push_self
|
107
|
+
elsif form.qualified?
|
108
|
+
QualifiedReference.new(form.unqualified_name, form.qualifier).bytecode(g)
|
109
|
+
else
|
110
|
+
g.scope.find_var(form.name).bytecode(g)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
when Seq
|
115
|
+
if quoted || form.empty?
|
116
|
+
g.push_const :Apricot
|
117
|
+
g.find_const :List
|
118
|
+
|
119
|
+
if form.empty?
|
120
|
+
g.find_const :EMPTY_LIST
|
121
|
+
else
|
122
|
+
form.each {|e| bytecode(g, e, true) }
|
123
|
+
g.send :[], form.count
|
124
|
+
end
|
125
|
+
else
|
126
|
+
callee, args = form.first, form.rest
|
127
|
+
|
128
|
+
# Handle special forms such as def, let, fn, quote, etc
|
129
|
+
if callee.is_a?(Identifier)
|
130
|
+
if special = SpecialForm[callee.name]
|
131
|
+
special.bytecode(g, args)
|
132
|
+
return
|
133
|
+
end
|
134
|
+
|
135
|
+
if macroexpand
|
136
|
+
form = Apricot.macroexpand(form)
|
137
|
+
|
138
|
+
if form.is_a?(Seq)
|
139
|
+
# Avoid recursing and macroexpanding again if expansion returns a list
|
140
|
+
bytecode(g, form, false, false)
|
141
|
+
else
|
142
|
+
bytecode(g, form)
|
143
|
+
end
|
144
|
+
|
145
|
+
return
|
146
|
+
end
|
147
|
+
|
148
|
+
meta = callee.meta
|
149
|
+
|
150
|
+
# Handle inlinable function calls
|
151
|
+
if meta && meta[:inline] && (!meta[:'inline-arities'] ||
|
152
|
+
meta[:'inline-arities'].apricot_call(args.count))
|
153
|
+
begin
|
154
|
+
inlined_form = meta[:inline].apricot_call(*args)
|
155
|
+
rescue => e
|
156
|
+
g.compile_error "Inliner function for '#{callee.name}' raised an exception:\n #{e}"
|
157
|
+
end
|
158
|
+
|
159
|
+
g.tail_position = false
|
160
|
+
bytecode(g, inlined_form)
|
161
|
+
return
|
162
|
+
end
|
163
|
+
|
164
|
+
if callee.fn? || callee.method?
|
165
|
+
qualifier_id = Identifier.intern(callee.qualifier.name)
|
166
|
+
first_name, *rest_names = qualifier_id.const_names
|
167
|
+
|
168
|
+
g.push_const first_name
|
169
|
+
rest_names.each {|n| g.find_const(n) }
|
170
|
+
|
171
|
+
args.each {|arg| bytecode(g, arg) }
|
172
|
+
g.send callee.unqualified_name, args.count
|
173
|
+
return
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Handle everything else
|
178
|
+
g.tail_position = false
|
179
|
+
bytecode(g, callee)
|
180
|
+
args.each {|arg| bytecode(g, arg) }
|
181
|
+
g.send :apricot_call, args.count
|
182
|
+
end
|
183
|
+
|
184
|
+
when Symbol
|
185
|
+
g.push_literal form
|
186
|
+
|
187
|
+
when Array
|
188
|
+
form.each {|e| bytecode(g, e, quoted) }
|
189
|
+
g.make_array form.size
|
190
|
+
|
191
|
+
when String
|
192
|
+
g.push_literal form
|
193
|
+
g.string_dup # Duplicate string to prevent mutating the literal
|
194
|
+
|
195
|
+
when Hash
|
196
|
+
# Create a new Hash
|
197
|
+
g.push_const :Hash
|
198
|
+
g.push form.size
|
199
|
+
g.send :new_from_literal, 1
|
200
|
+
|
201
|
+
# Add keys and values
|
202
|
+
form.each_pair do |key, value|
|
203
|
+
g.dup # the Hash
|
204
|
+
bytecode(g, key, quoted)
|
205
|
+
bytecode(g, value, quoted)
|
206
|
+
g.send :[]=, 2
|
207
|
+
g.pop # drop the return value of []=
|
208
|
+
end
|
209
|
+
|
210
|
+
when Fixnum
|
211
|
+
g.push form
|
212
|
+
|
213
|
+
when true
|
214
|
+
g.push :true
|
215
|
+
|
216
|
+
when nil
|
217
|
+
g.push :nil
|
218
|
+
|
219
|
+
when false
|
220
|
+
g.push :false
|
221
|
+
|
222
|
+
when Float
|
223
|
+
g.push_unique_literal form
|
224
|
+
|
225
|
+
when Regexp
|
226
|
+
once(g) do
|
227
|
+
g.push_const :Regexp
|
228
|
+
g.push_literal form.source
|
229
|
+
g.push form.options
|
230
|
+
g.send :new, 2
|
231
|
+
end
|
232
|
+
|
233
|
+
when Rational
|
234
|
+
once(g) do
|
235
|
+
g.push_self
|
236
|
+
g.push form.numerator
|
237
|
+
g.push form.denominator
|
238
|
+
g.send :Rational, 2, true
|
239
|
+
end
|
240
|
+
|
241
|
+
when Set
|
242
|
+
g.push_const :Set
|
243
|
+
g.send :new, 0 # TODO: Inline this new?
|
244
|
+
|
245
|
+
form.each do |elem|
|
246
|
+
bytecode(g, elem, quoted)
|
247
|
+
g.send :add, 1
|
248
|
+
end
|
249
|
+
|
250
|
+
when Bignum
|
251
|
+
g.push_unique_literal form
|
252
|
+
|
253
|
+
else
|
254
|
+
g.compile_error "Can't generate bytecode for #{form} (#{form.class})"
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# Some literals, such as regexps and rationals, should only be created the
|
259
|
+
# first time they are encountered. We push a literal nil here, and then
|
260
|
+
# overwrite the literal value with the created object if it is nil, i.e.
|
261
|
+
# the first time only. Subsequent encounters will use the previously
|
262
|
+
# created object. This idea was copied from Rubinius::AST::RegexLiteral.
|
263
|
+
#
|
264
|
+
# The passed block should take a generator and generate the bytecode to
|
265
|
+
# create the object the first time.
|
266
|
+
def once(g)
|
267
|
+
idx = g.add_literal(nil)
|
268
|
+
g.push_literal_at idx
|
269
|
+
g.dup
|
270
|
+
g.is_nil
|
271
|
+
|
272
|
+
lbl = g.new_label
|
273
|
+
g.gif lbl
|
274
|
+
g.pop
|
275
|
+
|
276
|
+
yield g
|
277
|
+
|
278
|
+
g.set_literal idx
|
279
|
+
lbl.set!
|
48
280
|
end
|
49
281
|
|
50
|
-
def
|
51
|
-
|
52
|
-
|
282
|
+
def pos(g, form)
|
283
|
+
if (meta = form.apricot_meta) && (line = meta[:line])
|
284
|
+
g.set_line(line)
|
285
|
+
end
|
53
286
|
end
|
54
287
|
end
|
55
288
|
end
|