apricot 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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 << ' ' }
|