apricot 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +1 -0
  5. data/Gemfile.lock +229 -11
  6. data/README.md +46 -29
  7. data/Rakefile +1 -1
  8. data/apricot.gemspec +7 -3
  9. data/benchmarks/factorial.rb +51 -0
  10. data/benchmarks/interpolate.rb +20 -0
  11. data/bin/apricot +5 -23
  12. data/examples/bot.apr +1 -4
  13. data/examples/cinch-bot.apr +3 -3
  14. data/examples/sinatra.apr +9 -0
  15. data/kernel/core.apr +124 -75
  16. data/kernel/repl.apr +37 -0
  17. data/lib/apricot.rb +7 -26
  18. data/lib/apricot/boot.rb +24 -0
  19. data/lib/apricot/code_loader.rb +108 -0
  20. data/lib/apricot/compiler.rb +265 -32
  21. data/lib/apricot/generator.rb +10 -3
  22. data/lib/apricot/identifier.rb +25 -10
  23. data/lib/apricot/list.rb +28 -41
  24. data/lib/apricot/macroexpand.rb +14 -8
  25. data/lib/apricot/misc.rb +2 -1
  26. data/lib/apricot/namespace.rb +20 -3
  27. data/lib/apricot/{parser.rb → reader.rb} +221 -194
  28. data/lib/apricot/repl.rb +67 -24
  29. data/lib/apricot/ruby_ext.rb +27 -16
  30. data/lib/apricot/scopes.rb +159 -0
  31. data/lib/apricot/seq.rb +43 -1
  32. data/lib/apricot/special_forms.rb +16 -695
  33. data/lib/apricot/special_forms/def.rb +32 -0
  34. data/lib/apricot/special_forms/do.rb +23 -0
  35. data/lib/apricot/special_forms/dot.rb +112 -0
  36. data/lib/apricot/special_forms/fn.rb +342 -0
  37. data/lib/apricot/special_forms/if.rb +31 -0
  38. data/lib/apricot/special_forms/let.rb +8 -0
  39. data/lib/apricot/special_forms/loop.rb +10 -0
  40. data/lib/apricot/special_forms/quote.rb +9 -0
  41. data/lib/apricot/special_forms/recur.rb +26 -0
  42. data/lib/apricot/special_forms/try.rb +146 -0
  43. data/lib/apricot/variables.rb +65 -0
  44. data/lib/apricot/version.rb +1 -1
  45. data/spec/compiler_spec.rb +53 -450
  46. data/spec/fn_spec.rb +206 -0
  47. data/spec/list_spec.rb +1 -1
  48. data/spec/reader_spec.rb +349 -0
  49. data/spec/spec_helper.rb +40 -4
  50. data/spec/special_forms_spec.rb +203 -0
  51. metadata +99 -133
  52. data/lib/apricot/ast.rb +0 -3
  53. data/lib/apricot/ast/identifier.rb +0 -111
  54. data/lib/apricot/ast/list.rb +0 -99
  55. data/lib/apricot/ast/literals.rb +0 -240
  56. data/lib/apricot/ast/node.rb +0 -45
  57. data/lib/apricot/ast/scopes.rb +0 -147
  58. data/lib/apricot/ast/toplevel.rb +0 -66
  59. data/lib/apricot/ast/variables.rb +0 -64
  60. data/lib/apricot/printers.rb +0 -12
  61. data/lib/apricot/stages.rb +0 -60
  62. data/spec/parser_spec.rb +0 -312
@@ -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
@@ -1,55 +1,288 @@
1
1
  module Apricot
2
- class Compiler < Rubinius::Compiler
3
- def self.compile(file, output = nil, debug = false)
4
- compiler = new :apricot_file, :compiled_file
2
+ module Compiler
3
+ module_function
5
4
 
6
- compiler.parser.input file
7
- compiler.packager.print(BytecodePrinter) if debug
8
- compiler.writer.name = output || Rubinius::Compiler.compiled_name(file)
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
- prepare_compiled_code compiler.run
11
- end
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
- def self.compile_string(code, file = nil, line = 1, debug = false)
14
- compiler = new :apricot_string, :compiled_method
27
+ g.ret
15
28
 
16
- compiler.parser.input(code, file || "(none)", line)
17
- compiler.packager.print(BytecodePrinter) if debug
29
+ scope = g.scopes.pop
30
+ g.local_count = scope.local_count
31
+ g.local_names = scope.local_names
18
32
 
19
- prepare_compiled_code compiler.run
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 self.compile_node(node, file = "(none)", line = 1, debug = false)
23
- compiler = new :apricot_bytecode, :compiled_method
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
- compiler.generator.input AST::TopLevel.new([node], file, line, false)
26
- compiler.packager.print(BytecodePrinter) if debug
46
+ dir = File.dirname(compiled_name)
27
47
 
28
- prepare_compiled_code compiler.run
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 self.compile_form(node, file = "(none)", line = 1, debug = false)
32
- compile_node(AST::Node.from_value(node, line), file, line, debug)
68
+ def compile_form(form, file = "(eval)", line = 1)
69
+ generate([form], file, line)
33
70
  end
34
71
 
35
- def self.prepare_compiled_code(cc)
36
- cc.scope = Rubinius::ConstantScope.new(Object)
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 self.eval(code, file = "(eval)", line = 1, debug = false)
41
- cc = compile_string(code, file, line, debug)
42
- Rubinius.run_script cc
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
- def self.eval_form(form, file = "(eval)", line = 1, debug = false)
46
- cc = compile_form(form, file, line, debug)
47
- Rubinius.run_script cc
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 self.eval_node(node, file = "(eval)", line = 1, debug = false)
51
- cc = compile_node(node, file, line, debug)
52
- Rubinius.run_script cc
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