hammock-ruby 0.0.1.alpha
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +132 -0
- data/bin/hammock +55 -0
- data/lib/clojure/core.clj +6872 -0
- data/lib/hammock.rb +0 -0
- data/lib/hammock/aref.rb +57 -0
- data/lib/hammock/array_chunk.rb +49 -0
- data/lib/hammock/atom.rb +50 -0
- data/lib/hammock/block.rb +8 -0
- data/lib/hammock/chunk_buffer.rb +25 -0
- data/lib/hammock/chunked_cons.rb +108 -0
- data/lib/hammock/chunked_seq.rb +97 -0
- data/lib/hammock/compiler.rb +197 -0
- data/lib/hammock/environment.rb +40 -0
- data/lib/hammock/errors.rb +14 -0
- data/lib/hammock/function.rb +187 -0
- data/lib/hammock/ichunked_seq.rb +23 -0
- data/lib/hammock/ideref.rb +5 -0
- data/lib/hammock/ifn.rb +10 -0
- data/lib/hammock/ilookup.rb +6 -0
- data/lib/hammock/interfaces.rb +80 -0
- data/lib/hammock/ipersistent_collection.rb +15 -0
- data/lib/hammock/ireference.rb +15 -0
- data/lib/hammock/iseq.rb +12 -0
- data/lib/hammock/lazy_sequence.rb +82 -0
- data/lib/hammock/lazy_transformer.rb +169 -0
- data/lib/hammock/list.rb +232 -0
- data/lib/hammock/loop_locals.rb +34 -0
- data/lib/hammock/map.rb +205 -0
- data/lib/hammock/meta.rb +12 -0
- data/lib/hammock/multi_method.rb +52 -0
- data/lib/hammock/namespace.rb +185 -0
- data/lib/hammock/reader.rb +570 -0
- data/lib/hammock/recur_locals.rb +16 -0
- data/lib/hammock/reduced.rb +15 -0
- data/lib/hammock/repl.rb +29 -0
- data/lib/hammock/rt.rb +580 -0
- data/lib/hammock/sequence.rb +59 -0
- data/lib/hammock/set.rb +144 -0
- data/lib/hammock/stream.rb +40 -0
- data/lib/hammock/symbol.rb +65 -0
- data/lib/hammock/var.rb +186 -0
- data/lib/hammock/vector.rb +309 -0
- data/lib/hammock/version.rb +3 -0
- data/lib/hammock/volatile.rb +19 -0
- data/spec/examples/data.hmk +4 -0
- data/spec/hammock/reader_spec.rb +242 -0
- data/spec/hammock/rt_spec.rb +10 -0
- data/spec/hammock/sequence_spec.rb +24 -0
- metadata +139 -0
data/lib/hammock/meta.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'hammock/map'
|
2
|
+
require 'hammock/ifn'
|
3
|
+
require 'hammock/errors'
|
4
|
+
|
5
|
+
module Hammock
|
6
|
+
class MultiMethod
|
7
|
+
include IFn
|
8
|
+
attr_reader :method_table
|
9
|
+
|
10
|
+
def initialize(name, dispatch_fn, default_dispatch)
|
11
|
+
@name = name.to_s
|
12
|
+
@dispatch_fn = dispatch_fn
|
13
|
+
@default_dispatch = default_dispatch
|
14
|
+
@method_table = Map.new
|
15
|
+
@lock = Mutex.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def reset
|
19
|
+
@lock.synchronize do
|
20
|
+
@method_table = Map.new
|
21
|
+
end
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_method(dispatch_value, fn)
|
26
|
+
@lock.synchronize do
|
27
|
+
@method_table = method_table.assoc(dispatch_value, fn)
|
28
|
+
end
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def remove_method(dispatch_value)
|
33
|
+
@lock.synchronize do
|
34
|
+
@method_table = method_table.without(dispatch_value)
|
35
|
+
end
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_method(dispatch_val)
|
40
|
+
@method_table.get(dispatch_val) || @method_table.get(@default_dispatch)
|
41
|
+
end
|
42
|
+
|
43
|
+
def call(*args)
|
44
|
+
dval = @dispatch_fn.call(*args)
|
45
|
+
if fn = get_method(dval)
|
46
|
+
fn.call(*args)
|
47
|
+
else
|
48
|
+
raise Error, "No method in multimethod #@name for dispatch value: #{dval}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
require 'atomic'
|
2
|
+
require 'hammock/errors'
|
3
|
+
require 'hammock/map'
|
4
|
+
|
5
|
+
module Hammock
|
6
|
+
NAMESPACES = {}
|
7
|
+
|
8
|
+
class Namespace
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
def self.all
|
12
|
+
Sequence.from_array NAMESPACES.values
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.find(name)
|
16
|
+
NAMESPACES[name_only(name)]
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.remove(name)
|
20
|
+
NAMESPACES.delete(name_only(name))
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.name_only(name)
|
24
|
+
case name
|
25
|
+
when Hammock::Symbol, Namespace
|
26
|
+
name.name
|
27
|
+
else
|
28
|
+
name
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.find_or_create(name)
|
33
|
+
name = name_only(name)
|
34
|
+
existing = find(name)
|
35
|
+
existing and return existing
|
36
|
+
|
37
|
+
newns = new(name)
|
38
|
+
NAMESPACES[name] = newns
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.find_item(ns, symbol)
|
42
|
+
if symbol.ns == ns.name
|
43
|
+
ns.find_var(symbol.name)
|
44
|
+
elsif symbol.ns
|
45
|
+
mod = find(symbol.ns)
|
46
|
+
mod.find_var(symbol.name)
|
47
|
+
else
|
48
|
+
ns.find_var(symbol.name)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def initialize(name)
|
53
|
+
@name = name
|
54
|
+
@mappings = Atomic.new(Map.new)
|
55
|
+
@aliases = Atomic.new(Map.new)
|
56
|
+
end
|
57
|
+
|
58
|
+
def ==(other)
|
59
|
+
self.object_id == other.object_id
|
60
|
+
end
|
61
|
+
|
62
|
+
def aliases
|
63
|
+
@aliases.value
|
64
|
+
end
|
65
|
+
|
66
|
+
def mappings
|
67
|
+
@mappings.value
|
68
|
+
end
|
69
|
+
|
70
|
+
def publics
|
71
|
+
ret = []
|
72
|
+
mappings.each do |k,v|
|
73
|
+
if Var === v && v.namespace == self && v.public?
|
74
|
+
ret << [k,v]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
Map.from_pairs(ret)
|
78
|
+
end
|
79
|
+
|
80
|
+
def interns
|
81
|
+
ret = []
|
82
|
+
mappings.each do |k,v|
|
83
|
+
if Var === v && v.namespace == self
|
84
|
+
ret << [k,v]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
Map.from_pairs(ret)
|
88
|
+
end
|
89
|
+
|
90
|
+
def refers
|
91
|
+
ret = []
|
92
|
+
mappings.each do |k,v|
|
93
|
+
if Var === v && v.namespace != self
|
94
|
+
ret << [k,v]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
Map.from_pairs(ret)
|
98
|
+
end
|
99
|
+
|
100
|
+
def add_alias(sym, ns)
|
101
|
+
sym = self.class.name_only(sym)
|
102
|
+
@aliases.update do |map|
|
103
|
+
map.assoc(sym, ns)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def remove_alias(sym)
|
108
|
+
sym = self.class.name_only(sym)
|
109
|
+
@aliases.update do |map|
|
110
|
+
map.dissoc(sym)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def lookup_alias(name)
|
115
|
+
name = self.class.name_only(name)
|
116
|
+
aliases[name]
|
117
|
+
end
|
118
|
+
|
119
|
+
def find_var(name)
|
120
|
+
name = self.class.name_only(name)
|
121
|
+
mappings[name]
|
122
|
+
end
|
123
|
+
|
124
|
+
def unmap(symbol)
|
125
|
+
sym = Symbol.intern(symbol)
|
126
|
+
if sym.ns
|
127
|
+
raise Error, "Can't unmap a ns-qualified symbol"
|
128
|
+
end
|
129
|
+
@mappings.update do |map|
|
130
|
+
map.dissoc(sym.name)
|
131
|
+
end
|
132
|
+
nil
|
133
|
+
end
|
134
|
+
|
135
|
+
def has_var?(name)
|
136
|
+
name = self.class.name_only(name)
|
137
|
+
mappings.key?(name)
|
138
|
+
end
|
139
|
+
|
140
|
+
def find_var!(name)
|
141
|
+
find_var(name) or raise "Unable to find #{name} within #@name"
|
142
|
+
end
|
143
|
+
|
144
|
+
def intern(symbol)
|
145
|
+
sym = Symbol.intern(symbol)
|
146
|
+
if sym.ns
|
147
|
+
raise ArgumentError, "Can't intern a ns-qualified symbol"
|
148
|
+
end
|
149
|
+
var = Var.new(self, symbol)
|
150
|
+
@mappings.update do |mappings|
|
151
|
+
mappings.assoc(sym.name, var)
|
152
|
+
end
|
153
|
+
var
|
154
|
+
end
|
155
|
+
|
156
|
+
def refer(symbol, var)
|
157
|
+
sym = Symbol.intern(symbol)
|
158
|
+
if sym.ns
|
159
|
+
raise ArgumentError, "Can't intern a ns-qualified symbol"
|
160
|
+
end
|
161
|
+
|
162
|
+
if obj = find_var(symbol)
|
163
|
+
if obj == var
|
164
|
+
return var
|
165
|
+
else
|
166
|
+
# warn on redefine
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
@mappings.update do |mappings|
|
171
|
+
mappings.assoc(sym.name, var)
|
172
|
+
end
|
173
|
+
|
174
|
+
var
|
175
|
+
end
|
176
|
+
|
177
|
+
def inspect
|
178
|
+
"#<Namespace: #@name>"
|
179
|
+
end
|
180
|
+
|
181
|
+
def to_s
|
182
|
+
@name
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,570 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'hammock/meta'
|
3
|
+
require 'hammock/map'
|
4
|
+
require 'hammock/rt'
|
5
|
+
require 'hammock/set'
|
6
|
+
require 'hammock/sequence'
|
7
|
+
require 'hammock/symbol'
|
8
|
+
require 'hammock/vector'
|
9
|
+
|
10
|
+
module Hammock
|
11
|
+
class Reader
|
12
|
+
class LineNumberingIO < SimpleDelegator
|
13
|
+
attr_reader :line_number, :column_number, :filename
|
14
|
+
|
15
|
+
NEWLINE = $/ # line sep
|
16
|
+
|
17
|
+
def initialize(io)
|
18
|
+
@column_number = 0
|
19
|
+
@line_number = 1
|
20
|
+
@filename = if File === io
|
21
|
+
io.path
|
22
|
+
else
|
23
|
+
"(input)"
|
24
|
+
end
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
def getc
|
29
|
+
@column_number += 1
|
30
|
+
char = __getobj__.getc
|
31
|
+
if char == NEWLINE
|
32
|
+
@line_number += 1
|
33
|
+
@last_line_length = @column_number
|
34
|
+
@column_number = 0
|
35
|
+
end
|
36
|
+
char
|
37
|
+
end
|
38
|
+
|
39
|
+
def ungetc(char)
|
40
|
+
__getobj__.ungetc(char)
|
41
|
+
if char == NEWLINE
|
42
|
+
@line_number -= 1
|
43
|
+
@column_number = @last_line_length
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
TOKENS = {
|
49
|
+
"true" => true,
|
50
|
+
"false" => false,
|
51
|
+
"nil" => nil
|
52
|
+
}
|
53
|
+
|
54
|
+
# SYMBOL_PATTERN = Regexp.new("^:?([^/0-9].*/)?(/|[^/0-9][^/]*)$")
|
55
|
+
UNQUOTE = Symbol.intern("clojure.core", "unquote")
|
56
|
+
UNQUOTE_SPLICING = Symbol.intern("clojure.core", "unquote-splicing")
|
57
|
+
APPLY = Symbol.intern("clojure.core", "apply")
|
58
|
+
DEREF = Symbol.intern("clojure.core", "deref")
|
59
|
+
SEQ = Symbol.intern("clojure.core", "seq")
|
60
|
+
CONCAT = Symbol.intern("clojure.core", "concat")
|
61
|
+
LIST = Symbol.intern("list")
|
62
|
+
QUOTE = Symbol.intern("quote")
|
63
|
+
AMP = Symbol.intern("&")
|
64
|
+
VECTOR = Symbol.intern("clojure.core", "vector")
|
65
|
+
HASHMAP = Symbol.intern("clojure.core", "hash-map")
|
66
|
+
HASHSET = Symbol.intern("clojure.core", "hash-set")
|
67
|
+
FN = Symbol.intern("clojure.core", "fn")
|
68
|
+
|
69
|
+
MACROS = {
|
70
|
+
?( => :read_list,
|
71
|
+
?) => :read_rparen,
|
72
|
+
?[ => :read_vector,
|
73
|
+
?] => :read_rbracket,
|
74
|
+
?{ => :read_map,
|
75
|
+
?} => :read_rbrace,
|
76
|
+
?: => :read_keyword,
|
77
|
+
?" => :read_string,
|
78
|
+
?; => :read_comment,
|
79
|
+
?# => :read_dispatched,
|
80
|
+
?% => :read_arg,
|
81
|
+
?' => :read_quoted,
|
82
|
+
?@ => :read_deref,
|
83
|
+
?` => :read_syntax_quoted,
|
84
|
+
?^ => :read_meta,
|
85
|
+
?~ => :read_unquote,
|
86
|
+
"\\" => :read_char
|
87
|
+
}
|
88
|
+
|
89
|
+
DISPATCH_MACROS = {
|
90
|
+
?{ => :read_set,
|
91
|
+
?" => :read_regex,
|
92
|
+
?^ => :read_meta,
|
93
|
+
?( => :read_function,
|
94
|
+
?' => :read_var
|
95
|
+
}
|
96
|
+
|
97
|
+
HEXCHARS = "0123456789ABCDEF".split("")
|
98
|
+
THE_VAR = Symbol.intern("var")
|
99
|
+
|
100
|
+
def whitespace?(char)
|
101
|
+
char == " " || char == "\n" || char == "\t" || char == ","
|
102
|
+
end
|
103
|
+
|
104
|
+
def macro?(char)
|
105
|
+
MACROS.has_key? char
|
106
|
+
end
|
107
|
+
|
108
|
+
def terminating_macro?(char)
|
109
|
+
macro?(char) && char != ?# && char != ?' && char != ?%
|
110
|
+
end
|
111
|
+
|
112
|
+
def ensure_line_numbering(io)
|
113
|
+
if LineNumberingIO === io
|
114
|
+
io
|
115
|
+
else
|
116
|
+
LineNumberingIO.new io
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def read_all(io)
|
121
|
+
io = ensure_line_numbering(io)
|
122
|
+
yield read(io) until io.eof?
|
123
|
+
end
|
124
|
+
|
125
|
+
def read(io)
|
126
|
+
io = ensure_line_numbering(io)
|
127
|
+
until io.eof?
|
128
|
+
char = io.getc
|
129
|
+
while whitespace?(char)
|
130
|
+
char = io.getc
|
131
|
+
end
|
132
|
+
|
133
|
+
break if io.eof?
|
134
|
+
|
135
|
+
if char.match(/\d/)
|
136
|
+
break read_number(io, char)
|
137
|
+
end
|
138
|
+
|
139
|
+
if m = MACROS[char]
|
140
|
+
ret = send(m, io, char)
|
141
|
+
next if ret == io
|
142
|
+
break ret
|
143
|
+
end
|
144
|
+
|
145
|
+
if char == "+" || char == "-"
|
146
|
+
nextc = io.getc
|
147
|
+
io.ungetc(nextc)
|
148
|
+
if nextc.match(/\d/)
|
149
|
+
break read_number(io, char)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
token = read_token(io, char)
|
154
|
+
break interpret_token(io, token)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def interpret_token(io, token)
|
159
|
+
TOKENS.fetch(token) do
|
160
|
+
meta = Map.from_hash(line: io.line_number, column: io.column_number, file: io.filename)
|
161
|
+
Symbol.intern(token).with_meta(meta)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def read_list(io, char)
|
166
|
+
meta = Map.from_hash(line: io.line_number, column: io.column_number, file: io.filename)
|
167
|
+
list = read_delimited_list(")", io)
|
168
|
+
Sequence.from_array(list, meta)
|
169
|
+
end
|
170
|
+
|
171
|
+
def read_vector(io, char)
|
172
|
+
vec = read_delimited_list("]", io)
|
173
|
+
Vector.from_array vec
|
174
|
+
end
|
175
|
+
|
176
|
+
def read_map(io, char)
|
177
|
+
map = read_delimited_list("}", io)
|
178
|
+
Map.from_array map
|
179
|
+
end
|
180
|
+
|
181
|
+
def read_set(io, char)
|
182
|
+
set = read_delimited_list("}", io)
|
183
|
+
Set.from_array set
|
184
|
+
end
|
185
|
+
|
186
|
+
def read_delimited_list(delimiter, io)
|
187
|
+
a = []
|
188
|
+
|
189
|
+
loop do
|
190
|
+
char = io.getc
|
191
|
+
while whitespace?(char)
|
192
|
+
char = io.getc
|
193
|
+
end
|
194
|
+
|
195
|
+
raise "Unexpected EOF at line #{io.line_number}" unless char
|
196
|
+
|
197
|
+
break if char == delimiter
|
198
|
+
|
199
|
+
if m = MACROS[char]
|
200
|
+
ret = send(m, io, char)
|
201
|
+
if ret && ret != io
|
202
|
+
a << ret
|
203
|
+
end
|
204
|
+
else
|
205
|
+
io.ungetc(char)
|
206
|
+
a << read(io)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
a
|
211
|
+
end
|
212
|
+
|
213
|
+
def read_rparen(io, char)
|
214
|
+
raise "Unexpected ) at line #{io.line_number}"
|
215
|
+
end
|
216
|
+
|
217
|
+
def read_keyword(io, colon)
|
218
|
+
keyword = ""
|
219
|
+
char = io.getc
|
220
|
+
if char == ":"
|
221
|
+
prefix = RT::CURRENT_NS.deref.name
|
222
|
+
keyword << prefix << "/"
|
223
|
+
else
|
224
|
+
io.ungetc(char)
|
225
|
+
end
|
226
|
+
|
227
|
+
loop do
|
228
|
+
char = io.getc
|
229
|
+
if whitespace?(char) || terminating_macro?(char) || !char
|
230
|
+
io.ungetc(char)
|
231
|
+
break
|
232
|
+
end
|
233
|
+
keyword << char
|
234
|
+
end
|
235
|
+
keyword.to_sym
|
236
|
+
end
|
237
|
+
|
238
|
+
def read_stringish(io, open_quote)
|
239
|
+
str = ""
|
240
|
+
loop do
|
241
|
+
char = io.getc
|
242
|
+
if !char
|
243
|
+
io.ungetc(char)
|
244
|
+
break
|
245
|
+
end
|
246
|
+
|
247
|
+
break if char == '"'
|
248
|
+
|
249
|
+
if char == "\\"
|
250
|
+
char = io.getc
|
251
|
+
case char
|
252
|
+
when ?t
|
253
|
+
char = "\t"
|
254
|
+
when ?r
|
255
|
+
char = "\r"
|
256
|
+
when ?n
|
257
|
+
char = "\n"
|
258
|
+
when ?b
|
259
|
+
char = "\b"
|
260
|
+
when ?f
|
261
|
+
char = "\f"
|
262
|
+
when ?u
|
263
|
+
char = io.getc
|
264
|
+
unless HEXCHARS.include?(char)
|
265
|
+
raise "Expected only hex characters in unicode escape sequence: line #{io.line_number}"
|
266
|
+
end
|
267
|
+
char = read_unicode_char(io, char)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
str << char
|
272
|
+
end
|
273
|
+
str
|
274
|
+
end
|
275
|
+
|
276
|
+
def read_unicode_char(io, char)
|
277
|
+
digits = char
|
278
|
+
loop do
|
279
|
+
char = io.getc
|
280
|
+
|
281
|
+
if !char
|
282
|
+
io.ungetc(char)
|
283
|
+
break
|
284
|
+
end
|
285
|
+
|
286
|
+
break unless HEXCHARS.include?(char)
|
287
|
+
digits << char
|
288
|
+
end
|
289
|
+
|
290
|
+
interpret_unicode_char(digits)
|
291
|
+
end
|
292
|
+
|
293
|
+
def interpret_unicode_char(digits)
|
294
|
+
[digits.hex].pack "U"
|
295
|
+
end
|
296
|
+
|
297
|
+
def read_string(io, open_quote)
|
298
|
+
str = read_stringish(io, open_quote)
|
299
|
+
str
|
300
|
+
end
|
301
|
+
|
302
|
+
def read_regex(io, open_quote)
|
303
|
+
str = read_stringish(io, open_quote)
|
304
|
+
Regexp.new(str)
|
305
|
+
end
|
306
|
+
|
307
|
+
def read_char(io, escape)
|
308
|
+
char = io.getc
|
309
|
+
c = read_token(io, char)
|
310
|
+
char = case c
|
311
|
+
when "newline" then "\n"
|
312
|
+
when "space" then " "
|
313
|
+
when "tab" then "\t"
|
314
|
+
when "backspace" then "\b"
|
315
|
+
when "formfeed" then "\f"
|
316
|
+
when "return" then "\r"
|
317
|
+
when /^u/
|
318
|
+
interpret_unicode_char(c[1..-1])
|
319
|
+
else
|
320
|
+
c
|
321
|
+
end
|
322
|
+
|
323
|
+
char
|
324
|
+
end
|
325
|
+
|
326
|
+
def read_comment(io, char)
|
327
|
+
while char && char != "\n" do
|
328
|
+
char = io.getc
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
def read_dispatched(io, _)
|
333
|
+
char = io.getc
|
334
|
+
unless char
|
335
|
+
raise "Unexpected EOF at line #{io.line_number}" unless char
|
336
|
+
end
|
337
|
+
|
338
|
+
method = DISPATCH_MACROS.fetch(char)
|
339
|
+
send(method, io, char)
|
340
|
+
end
|
341
|
+
|
342
|
+
def read_number(io, char)
|
343
|
+
digits = read_token(io, char)
|
344
|
+
match_number(digits) or raise "Unable to parse number: #{digits} at line #{io.line_number}"
|
345
|
+
end
|
346
|
+
|
347
|
+
def match_number(digits)
|
348
|
+
case digits
|
349
|
+
when /^[-+]?[\d]+$/
|
350
|
+
digits.to_i
|
351
|
+
when /^[-+]?\d+\.\d+$/
|
352
|
+
digits.to_f
|
353
|
+
when /^[-+]?\d+\/\d+$/
|
354
|
+
digits.to_r
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
def read_quoted(io, quote_mark)
|
359
|
+
read_wrapped(io, QUOTE)
|
360
|
+
end
|
361
|
+
|
362
|
+
def read_deref(io, at)
|
363
|
+
read_wrapped(io, DEREF)
|
364
|
+
end
|
365
|
+
|
366
|
+
def read_wrapped(io, sym)
|
367
|
+
RT.list(sym, read(io))
|
368
|
+
end
|
369
|
+
|
370
|
+
def read_unquote(io, quote_mark)
|
371
|
+
char = io.getc
|
372
|
+
if char == "@"
|
373
|
+
ret = read(io)
|
374
|
+
RT.list(UNQUOTE_SPLICING, ret)
|
375
|
+
else
|
376
|
+
io.ungetc(char)
|
377
|
+
ret = read(io)
|
378
|
+
RT.list(UNQUOTE, ret)
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
def read_syntax_quoted(io, quote_mark)
|
383
|
+
form = read(io)
|
384
|
+
Thread.current[:gensym_env] = Map.new
|
385
|
+
syntax_quote(form)
|
386
|
+
ensure
|
387
|
+
Thread.current[:gensym_env] = nil
|
388
|
+
end
|
389
|
+
|
390
|
+
def syntax_quote(form)
|
391
|
+
ret = nil
|
392
|
+
return unless form
|
393
|
+
if RT.special(form)
|
394
|
+
ret = RT.list(QUOTE, form)
|
395
|
+
elsif Hammock::Symbol === form
|
396
|
+
sym = form
|
397
|
+
if !sym.ns && sym.name.end_with?("#")
|
398
|
+
unless map = Thread.current[:gensym_env]
|
399
|
+
raise "Gensym literal not in syntax-quote"
|
400
|
+
end
|
401
|
+
unless gs = map[sym.name]
|
402
|
+
gs = Symbol.intern(nil, sym.name[0..-2] + "__#{RT.next_id}__auto__")
|
403
|
+
Thread.current[:gensym_env] = map.assoc(sym.name, gs)
|
404
|
+
end
|
405
|
+
sym = gs
|
406
|
+
else
|
407
|
+
ns = RT::CURRENT_NS.deref
|
408
|
+
lookup = ns.find_var(sym.name)
|
409
|
+
if lookup
|
410
|
+
sym = Hammock::Symbol.intern(ns.name, sym.name)
|
411
|
+
else
|
412
|
+
sym
|
413
|
+
end
|
414
|
+
end
|
415
|
+
ret = RT.list(QUOTE, sym)
|
416
|
+
elsif form.respond_to?(:first)
|
417
|
+
if form.first == UNQUOTE
|
418
|
+
ret = form.tail.first
|
419
|
+
elsif form.first == UNQUOTE_SPLICING
|
420
|
+
raise "This is a problem"
|
421
|
+
elsif Map === form
|
422
|
+
ret = RT.list(APPLY, HASHMAP, RT.list(SEQ, RT.cons(CONCAT, syntax_quote_expand_list(form))))
|
423
|
+
elsif Hammock::Set === form
|
424
|
+
ret = RT.list(APPLY, HASHSET, RT.list(SEQ, RT.cons(CONCAT, syntax_quote_expand_list(form))))
|
425
|
+
elsif Vector === form
|
426
|
+
ret = RT.list(APPLY, VECTOR, RT.list(SEQ, RT.cons(CONCAT, syntax_quote_expand_list(form))))
|
427
|
+
elsif Hammock::List === form
|
428
|
+
seq = RT.seq(form)
|
429
|
+
if seq
|
430
|
+
ret = RT.list(SEQ, RT.cons(CONCAT, syntax_quote_expand_list(seq)))
|
431
|
+
else
|
432
|
+
ret = RT.cons(Hammock::Symbol.intern("list"), nil)
|
433
|
+
end
|
434
|
+
end
|
435
|
+
elsif String === form || Numeric === form || ::Symbol === form
|
436
|
+
ret = form
|
437
|
+
else
|
438
|
+
ret = RT.list(QUOTE, form)
|
439
|
+
end
|
440
|
+
ret
|
441
|
+
end
|
442
|
+
|
443
|
+
|
444
|
+
def unquote?(form)
|
445
|
+
Hammock::List === form && (UNQUOTE == RT.first(form))
|
446
|
+
end
|
447
|
+
|
448
|
+
def unquote_splicing?(form)
|
449
|
+
Hammock::List === form && (UNQUOTE_SPLICING == RT.first(form))
|
450
|
+
end
|
451
|
+
|
452
|
+
def syntax_quote_expand_list(seq)
|
453
|
+
ret = Vector.new
|
454
|
+
seq = RT.seq(seq)
|
455
|
+
while seq && !seq.empty?
|
456
|
+
item = seq.first
|
457
|
+
if unquote?(item)
|
458
|
+
ret = ret.cons(RT.list(LIST, RT.second(item)))
|
459
|
+
elsif unquote_splicing?(item)
|
460
|
+
ret = ret.cons(RT.second(item))
|
461
|
+
else
|
462
|
+
ret = ret.cons(RT.list(LIST, syntax_quote(item)))
|
463
|
+
end
|
464
|
+
seq = seq.tail
|
465
|
+
end
|
466
|
+
RT.seq(ret)
|
467
|
+
end
|
468
|
+
|
469
|
+
def read_var(io, quote_mark)
|
470
|
+
Hammock::Sequence.from_array [THE_VAR, read(io)]
|
471
|
+
end
|
472
|
+
|
473
|
+
def read_function(io, paren)
|
474
|
+
Thread.current[:arg_env] = Map.new
|
475
|
+
io.ungetc(paren)
|
476
|
+
form = read(io)
|
477
|
+
args = Vector.new
|
478
|
+
argsyms = Thread.current[:arg_env]
|
479
|
+
keys = argsyms.keys.to_a
|
480
|
+
unless argsyms.empty?
|
481
|
+
higharg = keys.max
|
482
|
+
if higharg > 0
|
483
|
+
(1..higharg).each do |i|
|
484
|
+
sym = argsyms[i] || garg(i)
|
485
|
+
args = args.cons(sym)
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
if restsym = argsyms[-1]
|
490
|
+
args = args.cons(AMP)
|
491
|
+
args = args.cons(restsym)
|
492
|
+
end
|
493
|
+
end
|
494
|
+
RT.list(FN, args, form)
|
495
|
+
ensure
|
496
|
+
Thread.current[:arg_env] = nil
|
497
|
+
end
|
498
|
+
|
499
|
+
def read_meta(io, hat)
|
500
|
+
meta = read(io)
|
501
|
+
if ::Symbol === meta
|
502
|
+
meta = Map.from_array [meta, true]
|
503
|
+
elsif Hammock::Symbol === meta
|
504
|
+
return
|
505
|
+
end
|
506
|
+
following = read(io)
|
507
|
+
|
508
|
+
unless Meta === following
|
509
|
+
raise "#{following.inspect} does not implement metadata"
|
510
|
+
end
|
511
|
+
|
512
|
+
if Map === following.meta
|
513
|
+
meta = following.meta.merge(meta)
|
514
|
+
end
|
515
|
+
|
516
|
+
following.with_meta(meta)
|
517
|
+
end
|
518
|
+
|
519
|
+
def read_token(io, initch)
|
520
|
+
chars = initch.dup
|
521
|
+
loop do
|
522
|
+
char = io.getc
|
523
|
+
if !char || whitespace?(char) || terminating_macro?(char)
|
524
|
+
io.ungetc(char)
|
525
|
+
break
|
526
|
+
end
|
527
|
+
chars << char
|
528
|
+
end
|
529
|
+
chars
|
530
|
+
end
|
531
|
+
|
532
|
+
def garg(n)
|
533
|
+
Symbol.intern (n == -1 ? "rest" : "p#{n}") + "__#{RT.next_id}#"
|
534
|
+
end
|
535
|
+
|
536
|
+
def register_arg(n)
|
537
|
+
argsyms = Thread.current[:arg_env]
|
538
|
+
if(!argsyms)
|
539
|
+
raise ArgumentError, "arg literal not in #()"
|
540
|
+
end
|
541
|
+
unless ret = argsyms[n]
|
542
|
+
ret = garg(n)
|
543
|
+
Thread.current[:arg_env] = argsyms.assoc(n, ret)
|
544
|
+
end
|
545
|
+
ret
|
546
|
+
end
|
547
|
+
|
548
|
+
def read_arg(io, pct)
|
549
|
+
unless Thread.current[:arg_env]
|
550
|
+
return interpret_token(io, read_token(io, '%'))
|
551
|
+
end
|
552
|
+
char = io.getc
|
553
|
+
io.ungetc(char)
|
554
|
+
|
555
|
+
# % alone is first arg
|
556
|
+
if whitespace?(char) || terminating_macro?(char)
|
557
|
+
return register_arg(1)
|
558
|
+
end
|
559
|
+
n = read(io)
|
560
|
+
if n == AMP
|
561
|
+
return register_arg(-1)
|
562
|
+
elsif Integer === n
|
563
|
+
register_arg(n)
|
564
|
+
else
|
565
|
+
raise ArgumentError, "arg literal must be %, %& or %integer"
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
end
|
570
|
+
end
|