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
@@ -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 << ' ' }