hammock-ruby 0.0.1.alpha
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/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
|