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.
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