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
@@ -1,5 +1,6 @@
1
1
  require 'readline'
2
2
  require 'yaml'
3
+ Apricot.require "repl"
3
4
 
4
5
  module Apricot
5
6
  class REPL
@@ -37,20 +38,26 @@ module Apricot
37
38
  }
38
39
 
39
40
  COMMAND_COMPLETIONS = COMMANDS.keys.sort
40
- SPECIAL_COMPLETIONS = SpecialForm::Specials.keys.map(&:to_s)
41
+ SPECIAL_COMPLETIONS = SpecialForm::SPECIAL_FORMS.keys.map(&:to_s)
41
42
 
42
- def initialize(prompt = 'apr> ', bytecode = false, history_file = nil)
43
+ def initialize(prompt = 'apr> ', history_file = nil)
43
44
  @prompt = prompt
44
- @bytecode = bytecode
45
45
  @history_file = File.expand_path(history_file || HISTORY_FILE)
46
46
  @line = 1
47
47
  end
48
48
 
49
49
  def run
50
+ # *1, *2, and *3 shall hold the results of the previous three
51
+ # evaluations.
52
+ Apricot::Core.set_var(:'*1', nil)
53
+ Apricot::Core.set_var(:'*2', nil)
54
+ Apricot::Core.set_var(:'*3', nil)
55
+
56
+ # Set up some Readline options.
50
57
  Readline.completion_append_character = " "
51
58
  Readline.basic_word_break_characters = " \t\n\"'`~@;{[("
52
- Readline.basic_quote_characters = "\""
53
59
 
60
+ # Set up tab completion.
54
61
  Readline.completion_proc = proc do |s|
55
62
  if s.start_with? '!'
56
63
  # User is typing a REPL command
@@ -69,46 +76,78 @@ module Apricot
69
76
  load_history
70
77
  terminal_state = `stty -g`.chomp
71
78
 
79
+ # Clear the current line before starting the REPL. This means the user
80
+ # can begin typing before the prompt appears and it will gracefully
81
+ # appear in front of their code when the REPL is ready, without any ugly
82
+ # text duplication issues.
83
+ clear_line
84
+
72
85
  while code = readline_with_history
73
86
  stripped = code.strip
74
- if stripped.empty?
75
- next
76
- elsif stripped.start_with?('!')
87
+
88
+ # Ignore blank lines.
89
+ next if stripped.empty?
90
+
91
+ # Handle REPL commands.
92
+ if stripped.start_with?('!')
77
93
  if COMMANDS.include?(stripped) && block = COMMANDS[stripped][:code]
78
94
  instance_eval(&block)
79
95
  else
80
96
  puts "Unknown command: #{stripped}"
81
97
  end
98
+
82
99
  next
83
100
  end
84
101
 
102
+ # Otherwise treat the input as code to evaluate.
85
103
  begin
86
- @compiled_code =
87
- Apricot::Compiler.compile_string(code, "(eval)", @line, @bytecode)
88
- value = Rubinius.run_script @compiled_code
89
- puts "=> #{value.apricot_inspect}"
90
- Apricot.current_namespace.set_var(:_, value)
91
- e = nil
92
- rescue Apricot::SyntaxError => e
93
- if e.incomplete?
104
+ begin
105
+ forms = Apricot::Reader.read_string(code, "(eval)", @line)
106
+ rescue Apricot::SyntaxError => e
107
+ # Reraise unless this is an incomplete error (meaning we can read
108
+ # more on the next line).
109
+ raise unless e.incomplete?
110
+
94
111
  begin
95
- more_code = Readline.readline(' ' * @prompt.length, false)
112
+ indent = ' ' * @prompt.length
113
+ more_code = Readline.readline(indent, false)
114
+
96
115
  if more_code
97
- code << "\n" << more_code
98
- Readline::HISTORY << Readline::HISTORY.pop + "\n" +
99
- ' ' * @prompt.length + more_code
116
+ code << "\n" << indent << more_code
117
+ Readline::HISTORY.pop
118
+ Readline::HISTORY << code
100
119
  retry
101
120
  else
102
121
  print "\r" # print the exception at the start of the line
122
+ raise
103
123
  end
104
124
  rescue Interrupt
105
125
  # This is raised by Ctrl-C. Stop trying to read more code and
106
126
  # just give up. Remove the current input from history.
127
+ puts "^C"
107
128
  current_code = Readline::HISTORY.pop
108
- @line -= current_code.count "\n"
109
- e = nil # ignore the syntax error since the code was Ctrl-C'd
129
+ @line -= current_code.count("\n")
130
+ next
110
131
  end
111
132
  end
133
+
134
+ forms.each do |form|
135
+ @compiled_code =
136
+ Apricot::Compiler.compile_form(form, "(eval)", @line)
137
+
138
+ value = Rubinius.run_script(@compiled_code)
139
+ puts "=> #{value.apricot_inspect}"
140
+
141
+ # Save the result of the evaluation in *1 and shift down the older
142
+ # previous values.
143
+ old = Apricot::Core.get_var(:'*1')
144
+ older = Apricot::Core.get_var(:'*2')
145
+ Apricot::Core.set_var(:'*1', value)
146
+ Apricot::Core.set_var(:'*2', old)
147
+ Apricot::Core.set_var(:'*3', older)
148
+ end
149
+
150
+ e = nil
112
151
  rescue Interrupt => e
113
152
  # Raised by Ctrl-C. Print a newline so the error message is on the
114
153
  # next line.
@@ -133,6 +172,12 @@ module Apricot
133
172
  system('stty', terminal_state) if terminal_state # Restore the terminal
134
173
  end
135
174
 
175
+ # Clear the current line in the terminal. This snippet was stolen from
176
+ # Pry.
177
+ def clear_line
178
+ puts "\e[0A\e[0G"
179
+ end
180
+
136
181
  def load_history
137
182
  if File.exist?(@history_file)
138
183
  hist = YAML.load_file @history_file
@@ -172,10 +217,8 @@ module Apricot
172
217
 
173
218
  line
174
219
  rescue Interrupt
175
- # This is raised by Ctrl-C. Remove the line from history then try to
176
- # read another line.
220
+ # This is raised by Ctrl-C. Try to read another line.
177
221
  puts "^C"
178
- Readline::HISTORY.pop
179
222
  @line -= 1
180
223
  retry
181
224
  end
@@ -1,5 +1,9 @@
1
1
  class Object
2
- attr_accessor :apricot_meta
2
+ attr_reader :apricot_meta
3
+
4
+ def apricot_meta=(meta)
5
+ @apricot_meta = meta unless frozen?
6
+ end
3
7
 
4
8
  def apricot_inspect
5
9
  inspect
@@ -71,6 +75,10 @@ class Array
71
75
  def count
72
76
  @array.length - @offset
73
77
  end
78
+
79
+ def to_a
80
+ @array[@offset..-1]
81
+ end
74
82
  end
75
83
  end
76
84
 
@@ -94,8 +102,8 @@ class Hash
94
102
  str << '}'
95
103
  end
96
104
 
97
- def apricot_call(key)
98
- self[key]
105
+ def apricot_call(key, default = nil)
106
+ fetch(key, default)
99
107
  end
100
108
 
101
109
  alias_method :apricot_str, :apricot_inspect
@@ -119,11 +127,10 @@ class Set
119
127
  str << '}'
120
128
  end
121
129
 
122
- def [](elem)
123
- elem if self.include? elem
130
+ def apricot_call(elem, default = nil)
131
+ include?(elem) ? elem : default
124
132
  end
125
133
 
126
- alias_method :apricot_call, :[]
127
134
  alias_method :apricot_str, :apricot_inspect
128
135
 
129
136
  def to_seq
@@ -155,15 +162,19 @@ class Symbol
155
162
  def apricot_inspect
156
163
  str = to_s
157
164
 
158
- if str =~ /\A#{Apricot::Parser::IDENTIFIER}+\z/
165
+ if str =~ /\A#{Apricot::Reader::IDENTIFIER}+\z/
159
166
  ":#{str}"
160
167
  else
161
168
  ":#{str.inspect}"
162
169
  end
163
170
  end
164
171
 
165
- def apricot_call(o)
166
- o[self]
172
+ def apricot_call(obj, default = nil)
173
+ if obj.is_a?(Hash) || obj.is_a?(Set)
174
+ obj.apricot_call(self, default)
175
+ else
176
+ nil
177
+ end
167
178
  end
168
179
  end
169
180
 
@@ -179,10 +190,10 @@ class Range
179
190
  class Seq
180
191
  include Apricot::Seq
181
192
 
182
- def initialize(first, last, exclude)
193
+ def initialize(first, last, exclusive)
183
194
  @first = first
184
195
  @last = last
185
- @exclude = exclude
196
+ @exclusive = exclusive
186
197
  end
187
198
 
188
199
  def first
@@ -192,10 +203,10 @@ class Range
192
203
  def next
193
204
  next_val = @first.succ
194
205
 
195
- if @first == @last || (next_val == @last && @exclude)
206
+ if @first == @last || (next_val == @last && @exclusive)
196
207
  nil
197
208
  else
198
- Seq.new(next_val, @last, @exclude)
209
+ Seq.new(next_val, @last, @exclusive)
199
210
  end
200
211
  end
201
212
 
@@ -203,7 +214,7 @@ class Range
203
214
  prev = nil
204
215
  val = @first
205
216
 
206
- until prev == @last || (val == @last && @exclude)
217
+ until prev == @last || (val == @last && @exclusive)
207
218
  yield val
208
219
  prev = val
209
220
  val = val.succ
@@ -216,7 +227,7 @@ end
216
227
 
217
228
  module Enumerable
218
229
  def to_list
219
- list = Apricot::List::EmptyList
230
+ list = Apricot::List::EMPTY_LIST
220
231
  reverse_each {|x| list = list.cons(x) }
221
232
  list
222
233
  end
@@ -249,6 +260,6 @@ class NilClass
249
260
  end
250
261
 
251
262
  def rest
252
- Apricot::List::EmptyList
263
+ Apricot::List::EMPTY_LIST
253
264
  end
254
265
  end
@@ -0,0 +1,159 @@
1
+ module Apricot
2
+ # This is a scope with real local variable storage, i.e. it is part of a
3
+ # block of code like a fn or the top level program. Let scopes do not have
4
+ # storage and must ask for storage from one of these.
5
+ module StorageScope
6
+ def variable_names
7
+ @variable_names ||= []
8
+ end
9
+
10
+ def store_new_local(name)
11
+ slot = next_slot
12
+ variable_names << name
13
+ slot
14
+ end
15
+
16
+ def next_slot
17
+ variable_names.size
18
+ end
19
+
20
+ def local_count
21
+ variable_names.size
22
+ end
23
+
24
+ def local_names
25
+ variable_names
26
+ end
27
+ end
28
+
29
+ class TopLevelScope
30
+ include StorageScope
31
+
32
+ # A nested scope is looking up a variable. There are no local variables
33
+ # at the top level, so look up the variable on the current namespace.
34
+ def find_var(name, depth = nil)
35
+ # Ignore depth, it has no bearing on namespace lookups.
36
+ QualifiedReference.new(name, Apricot.current_namespace)
37
+ end
38
+
39
+ # A (recur) is looking for a recursion target. Since this is the top
40
+ # level, which has no parent, the lookup has failed.
41
+ def find_recur_target
42
+ nil
43
+ end
44
+ end
45
+
46
+ class Scope
47
+ attr_reader :parent, :variables
48
+ # The loop label stores the code location where a (recur) form should
49
+ # jump to.
50
+ attr_accessor :loop_label
51
+
52
+ def initialize(parent)
53
+ @parent = parent
54
+ @variables = {}
55
+ @loop_label = nil
56
+ end
57
+ end
58
+
59
+ class FnScope < Scope
60
+ attr_reader :name, :self_reference
61
+
62
+ def initialize(parent, name)
63
+ super(parent)
64
+
65
+ if name
66
+ @name = name
67
+ name_slot = @parent.store_new_local(name)
68
+ @self_reference = LocalReference.new(name_slot, 1)
69
+ end
70
+ end
71
+
72
+ # An identifier or a nested scope is looking up a variable. If the
73
+ # variable is found here, return a reference to it. Otherwise look it up
74
+ # on the parent and increment its depth because it is beyond the bounds
75
+ # of the current block of code (fn).
76
+ def find_var(name, depth = 0)
77
+ return @self_reference if name == @name
78
+
79
+ @parent.find_var(name, depth + 1)
80
+ end
81
+
82
+ # A (recur) is looking for a recursion target (ie. a loop or a fn
83
+ # overload scope).
84
+ def find_recur_target
85
+ @parent.find_recur_target
86
+ end
87
+ end
88
+
89
+ class OverloadScope < Scope
90
+ include StorageScope
91
+
92
+ attr_accessor :splat
93
+ alias_method :splat?, :splat
94
+
95
+ attr_accessor :block_arg
96
+
97
+ def initialize(parent_fn)
98
+ super(parent_fn)
99
+ end
100
+
101
+ # An identifier or a nested scope is looking up a variable. If the
102
+ # variable is found here, return a reference to it. Otherwise look it up
103
+ # on the parent (a fn). Don't increase the depth, the lookup on the fn
104
+ # will do that, and if we do it twice then the generated
105
+ # push_local_depth instructions look up too many scopes.
106
+ def find_var(name, depth = 0)
107
+ if slot = @variables[name]
108
+ LocalReference.new(slot, depth)
109
+ else
110
+ @parent.find_var(name, depth)
111
+ end
112
+ end
113
+
114
+ # Create a new local on the current level.
115
+ def new_local(name)
116
+ name = name.name if name.is_a? Identifier
117
+ @variables[name] = store_new_local(name)
118
+ end
119
+
120
+ # A (recur) is looking for a recursion target. This, being a fn
121
+ # overload, is one.
122
+ def find_recur_target
123
+ self
124
+ end
125
+ end
126
+
127
+ # The let scope doesn't have real storage for locals. It stores its locals
128
+ # on the nearest enclosing real scope, which is any separate block of code
129
+ # such as a fn, defn, defmacro or the top level of the program.
130
+ class LetScope < Scope
131
+ # An identifier or a nested scope is looking up a variable.
132
+ def find_var(name, depth = 0)
133
+ if slot = @variables[name]
134
+ LocalReference.new(slot, depth)
135
+ else
136
+ @parent.find_var(name, depth)
137
+ end
138
+ end
139
+
140
+ # Create a new local on the current level, with storage on the nearest
141
+ # enclosing real scope.
142
+ def new_local(name)
143
+ name = name.name if name.is_a? Identifier
144
+ @variables[name] = @parent.store_new_local(name)
145
+ end
146
+
147
+ # A deeper let is asking for a new local slot. Pass it along to the
148
+ # parent so it eventually reaches a real scope.
149
+ def store_new_local(name)
150
+ @parent.store_new_local(name)
151
+ end
152
+
153
+ # A (recur) is looking for a recursion target. This is one only if it is
154
+ # a (loop) form.
155
+ def find_recur_target
156
+ loop_label ? self : @parent.find_recur_target
157
+ end
158
+ end
159
+ end
@@ -8,9 +8,10 @@ module Apricot
8
8
  # if there are no more items.
9
9
  module Seq
10
10
  include Enumerable
11
+ include Comparable
11
12
 
12
13
  def rest
13
- self.next || Apricot::List::EmptyList
14
+ self.next || List::EMPTY_LIST
14
15
  end
15
16
 
16
17
  def each
@@ -32,6 +33,47 @@ module Apricot
32
33
  false
33
34
  end
34
35
 
36
+ def last
37
+ s = self
38
+
39
+ while s.next
40
+ s = s.next
41
+ end
42
+
43
+ s.first
44
+ end
45
+
46
+ def cons(x)
47
+ Cons.new(x, self)
48
+ end
49
+
50
+ def <=>(other)
51
+ return unless other.is_a?(Seq) || other.nil?
52
+ s, o = self, other
53
+
54
+ while s && o
55
+ comp = s.first <=> o.first
56
+ return comp unless comp == 0
57
+ s = s.next
58
+ o = o.next
59
+ end
60
+
61
+ if s
62
+ 1
63
+ elsif o
64
+ -1
65
+ else
66
+ 0
67
+ end
68
+ end
69
+
70
+ alias_method :eql?, :==
71
+
72
+ def hash
73
+ hashes = map {|x| x.hash }
74
+ hashes.reduce(hashes.size) {|acc,hash| acc ^ hash }
75
+ end
76
+
35
77
  def to_s
36
78
  str = '('
37
79
  each {|x| str << x.apricot_inspect << ' ' }