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.
- 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/repl.rb
CHANGED
@@ -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::
|
41
|
+
SPECIAL_COMPLETIONS = SpecialForm::SPECIAL_FORMS.keys.map(&:to_s)
|
41
42
|
|
42
|
-
def initialize(prompt = 'apr> ',
|
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
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
87
|
-
Apricot::
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
-
|
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
|
99
|
-
|
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
|
109
|
-
|
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.
|
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
|
data/lib/apricot/ruby_ext.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
class Object
|
2
|
-
|
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
|
-
|
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
|
123
|
-
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::
|
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(
|
166
|
-
|
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,
|
193
|
+
def initialize(first, last, exclusive)
|
183
194
|
@first = first
|
184
195
|
@last = last
|
185
|
-
@
|
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 && @
|
206
|
+
if @first == @last || (next_val == @last && @exclusive)
|
196
207
|
nil
|
197
208
|
else
|
198
|
-
Seq.new(next_val, @last, @
|
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 && @
|
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::
|
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::
|
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
|
data/lib/apricot/seq.rb
CHANGED
@@ -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 ||
|
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 << ' ' }
|