-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbaz5_common.cr
More file actions
416 lines (332 loc) · 9.06 KB
/
baz5_common.cr
File metadata and controls
416 lines (332 loc) · 9.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
require "./src/wirewright"
module Rewrite
alias Any = Some | None
alias Some = One | Many
record None do
def term?
end
def each(&)
end
def reduce(& : Term -> Rewrite::Any)
self
end
def map(default, &)
One.new(Term.of(yield default))
end
def map(&)
self
end
def +(other : Any)
other
end
def diff(orig : Term)
self
end
end
record One, term : Term do
def term?
term
end
def each(&)
yield term
end
def reduce(& : Term -> Rewrite::Any)
yield term
end
def map(default, &)
One.new(Term.of(yield term))
end
def map(&)
One.new(Term.of(yield term))
end
def +(other : One)
if term == other.term
self
else
Many.new(Term[term, other.term])
end
end
def +(other : Many)
if other.list.itemsize == 1 && other[0] == term
self
else
Many.new(other.prepend(term))
end
end
def +(other : None)
self
end
def diff(orig : Term)
term == orig ? Rewrite.none : self
end
end
record Many, list : Term::Dict do
def term?
Term.of(list)
end
def each(&)
yield Term.of(list)
end
def reduce(& : Term -> Rewrite::Any)
changed = false
newlist = Term::Dict.build do |commit|
list.items.each do |item|
case offspring = yield item
in Rewrite::None
commit << item
in Rewrite::One
commit << offspring.term
changed = true
in Rewrite::Many
commit.concat(offspring.list.items)
changed = true
end
end
end
unless changed
return None.new
end
if newlist.size == 1
return One.new(newlist[0])
end
Many.new(newlist)
end
def map(default, &)
map { |item| yield item }
end
def map(&)
Many.new(list.transaction do |commit|
list.each_item_with_index do |item, index|
commit.with(index, yield item)
end
end)
end
def +(other : One)
if list.empty? || (list.itemsize == 1 && list[0] == other.term)
other
else
Many.new(list.append(other.term))
end
end
def +(other : Many)
if list.empty?
other
elsif other.list.empty?
self
else
Many.new(list.transaction &.concat(other.list.items))
end
end
def diff(orig : Term)
if list == Term[{orig}]
return Rewrite.none
end
if list.itemsize == 1
return Rewrite.one(list[0])
end
self
end
end
def self.none
None.new
end
def self.one(term)
One.new(Term.of(term))
end
def self.many(list)
Many.new(Term[list])
end
end
# In `ProcRuleset`, rules are Crystal procs (later, native code). Useful for
# implementing primitives.
#
# TODO: This is DEPRECATED and will be removed.
struct ProcRuleset
alias ProcRule = Term::Dict -> Rewrite::Any
alias ProcBackmap = Term::Dict -> Term::Dict
struct Builder
def initialize(@ruleary : Array({Term, ProcRule | ProcBackmap}))
end
def rule(pattern : Term, &fn : ProcRule) : Nil
@ruleary << {pattern, fn}
end
def backmap(pattern : Term, &fn : ProcBackmap) : Nil
@ruleary << {pattern, fn}
end
def rulep(ml : String, &fn : ProcRule) : Nil
rule(ML.term(ml), &fn)
end
def backmapp(ml : String, &fn : ProcBackmap) : Nil
backmap(ML.term(ml), &fn)
end
# :nodoc:
macro pi(methodp, ml, &block)
{% icaps = ml.scan(::Ww::Term::Case::RE_CAPTURES).map { |match| (match[1] || match[2]).id }.uniq %}
{% location = "#{block.filename.id}:#{block.line_number}:#{block.column_number}" %}
{{methodp}}({{ml}}) do |%env|
{% for icap in icaps %}
{% icap_id = icap.id.gsub(/-/, "_") %}
{% unless block.args.any? { |arg| arg.id == icap_id } %}
{{icap_id}} = (%env[{{icap.id.symbolize}}]? || raise "case: #{ {{location}} }: missing capture '{{icap.id}}'")
{% end %}
{% end %}
pass(
{% for capture in block.args %}
(%env[{{capture.id.symbolize}}]? || raise "rulepi: #{ {{location}} }: missing capture '{{capture.id}}'"),
{% end %}
) {{block}}
end
end
macro rulepi(ml, &block)
pi(rulep, {{ml}}) {{block}}
end
macro backmappi(ml, &block)
pi(backmapp, {{ml}}) {{block}}
end
macro rulepi1(ml, &block)
rulepi({{ml}}) do {% unless block.args.empty? %} |{{block.args.splat}}| {% end %}
%result = pass do
{{block.body}}
end
Rewrite::One.new(Term.of(%result))
end
end
end
# :nodoc:
def initialize(
@pset : M1::PatternSet(Term),
@rules : Hash(UInt32, ProcRule)?,
@backmaps : Hash(UInt32, ProcBackmap)?,
)
end
def self.build(&)
ruleary = [] of {Term, ProcRule | ProcBackmap}
builder = Builder.new(ruleary)
with builder yield builder
decls = Term::Dict.build do |commit|
ruleary.each_with_index do |(pattern, proc), index|
case proc
in ProcRule then commit << {:rule, index, pattern}
in ProcBackmap then commit << {:backmap, index, pattern}
end
end
end
selector = ML.term(%[[type←(%any rule backmap) index←(%number +i32) pattern_]])
rules = backmaps = nil
index = 0u32
pset = M1::PatternSet(Term).select(selector, Term.of(decls)) do |_, env|
_, proc = ruleary[env[:index].to(Int32)]
case env[:type]
when Term.of(:rule)
rules ||= {} of UInt32 => ProcRule
rules[index] = proc.as(ProcRule)
when Term.of(:backmap)
backmaps ||= {} of UInt32 => ProcBackmap
backmaps[index] = proc.as(ProcBackmap)
else
unreachable
end
index += 1
true
end
new(pset, rules, backmaps)
end
private def rule?(index) : ProcRule?
@rules.try { |rules| rules[index]? }
end
private def backmap?(index) : ProcBackmap?
@backmaps.try { |backmaps| backmaps[index]? }
end
def call(matchee matchee0 : Term) : Rewrite::Any
@pset.query(matchee0) do |envs, index|
next unless env = envs.single?
if rule = rule?(index)
offspring = rule.call(env)
elsif backmap = backmap?(index)
raise "not supported"
end
case offspring
in Nil
unreachable
in Rewrite::One
different = matchee0 != offspring.term
in Rewrite::Many
different = Term[{matchee0}] != offspring.list
in Rewrite::None
different = false
end
return different ? offspring : Rewrite::None.new
end
Rewrite::None.new
end
end
# NOTE: `pattern` is provided for info, you will usually not need to match it;
# the whole point of `Ruleset` and `M1::PatternSet` is that it matches it for you.
module Rule
extend self
alias Any = Template | Backmap
record Template, pattern : Term, body : Term
record Backmap, pattern : Term, backspec : Term
end
class Ruleset
# :nodoc:
def initialize(@pset : M1::PatternSet(Term), @rules : Slice(Rule::Any))
end
# - Capture `template` in *selector* forms a template rule.
# - Capture `backspec` in *selector* forms a backmap rule.
def self.select(selector, *bases, **kwargs)
rules = [] of Rule::Any
pset = M1::PatternSet(Term).select(selector, *bases, **kwargs) do |normp, env|
template = env[:template]?
backspec = env[:backspec]?
next if template && backspec # confused
if template
rules << Rule::Template.new(env[:pattern], template)
next true # ok
end
if backspec
rules << Rule::Backmap.new(env[:pattern], backspec)
next true # ok
end
# skip
end
new(pset, rules.to_readonly_slice(&.itself))
end
# FIXME: It would make more sense for this to return `{Ruleset, Term}` instead, so
# that we can pass through *base* if it's not a dict.
def self.ruleset_and_rest(selector, base, **kwargs) : {Ruleset, Term::Dict}
ruleset = self.select(selector, base)
unless base.type.dict?
return ruleset, Term[]
end
rest = base.pairspart.transaction do |commit|
base.items.each do |item|
next if M1.probe?(selector, item) # it is a rule
commit << item
end
end
{ruleset, rest}
end
DEFAULT_SELECTOR = ML.term("(%any° [rule pattern_ template_] [backmap pattern_ backspec_])")
def self.select(base : Term)
self.select(DEFAULT_SELECTOR, base)
end
def each_candidate(matchee : Term, & : M1::Op::Any, Rule::Any ->)
@pset.each_candidate(matchee) do |op, index|
yield op, @rules[index]
end
end
def each_candidate_with_id(matchee : Term, & : {M1::Op::Any, Rule::Any}, UInt32 ->)
@pset.each_candidate(matchee) do |op, index|
yield({op, @rules[index]}, index)
end
end
def query(matchee : Term, & : Indexable(Term::Dict), Rule::Any ->)
@pset.query(matchee) do |envs, index|
yield envs, @rules[index]
end
end
def to_s(io)
io << "Ruleset(<" << @rules.size << " rule(s)>)"
end
end