aboutsummaryrefslogtreecommitdiffstats
path: root/alv/base/match.moon
blob: 8ee3e16becb9af934c826f01373c2b8f0c92ff9b (plain)
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
-----
--- Pattern capturing for Op argument parsing.
--
-- There is only one basic buildings block for assembling patterns:
-- `Type`. It can match `SigStream`s and `EvtStream`s depending on its
-- metatype argument and can take an optional type name to match as an argument.
--
-- In addition to this primitive, the following modifiers are available:
-- `Repeat`, `Sequence`, `Choice`, and `Optional`. They can be used directly,
-- but there is also a number of shorthands for assembling patterns quickly:
--
-- - `const()`, `sig()` and `evt()`: Shorthands for `Type('='), Type('~'), Type('!')`
-- - `const.sym`: Shorthand for `Type('=', T.sym)`
-- - `sig.num`: Shorthand for `Type('~', T.num)`
-- - `evt.str`: Shorthand for `Type('!', T.str)`
-- - `pat * 2`: Shorthand for `Repeat(pat, 1, 2)` (1-2 times `pat`)
-- - `pat * 0`: Shorthand for `Repeat(pat, 1, nil)` (1-* times `pat`)
-- - `pat ^ 2`: Shorthand for `Repeat(pat, 0, 2)` (0-2 times `pat`)
-- - `pat ^ 0`: Shorthand for `Repeat(pat, 0, nil)` (0-* times `pat`)
-- - `a + b + … + z`: Shorthand for `Sequence{ a, b, ..., z }`
-- - `a / b / … / z`: Shorthand for `Choice{ a, b, ..., z }`
-- - `-pat`: Shorthand for `Optional(pat)`
--
-- To perform the actual matching, call the `:match` method on a pattern and
-- pass a sequence of `RTNode`s. The method will either return the captured
-- `RTNode`s (or a table structuring them)
--
-- Any ambiguous pattern can be set to 'recall mode' by invoking it.
-- Recalling patterns will memorize the first RTNode they match, and
-- only match further RTNodes of the same type. For example
--
--     arg = (sig.num / sig.str)!
--     pattern = arg + arg
--
-- ...will match either two numbers or two strings, but not one number and one
-- string. Recalling works on `Choice` and `Type` patterns.
--
-- On `Sequence` patterns, a special method `:named` exists. It takes a
-- sequence of keys that are used instead of integers when constructing the
-- capture table:
--
--     pattern = (sig.str + sig.num):named('key', 'value')
--     pattern:match(...)
--     -- returns { {key='a', value=1}, {key='b', value=2}, ...}
--
-- @module base.match
import Error from require 'alv.error'
import T from require 'alv.type'

local Repeat, Sequence, Choice, Optional

class Pattern
  fulltype = (res) -> (tostring res.type) .. res.metatype

  match: (seq) =>
    @reset!
    num, cap = @capture seq, 1
    if num != #seq
      args = table.concat [fulltype arg.result for arg in *seq], ' '
      msg = "couldn't match arguments (#{args}) against pattern #{@}"
      error Error 'argument', msg
    cap

  remember: (key) =>
    return true unless @recall

    @recalled or= key
    @recalled == key

  rep: (min, max) => Repeat @, min, max

  reset: => @recalled = nil

  __call: => @
  __mul: (num) => Repeat @, 1, if num != 0 then num
  __pow: (num) => Repeat @, 0, if num != 0 then num
  __add: (other) => Sequence { @, other }
  __div: (other) => Choice { @, other }
  __unm: => Optional @

  __inherited: (cls) =>
    cls.__base.__call or= @__call
    cls.__base.__mul or= @__mul
    cls.__base.__pow or= @__pow
    cls.__base.__add or= @__add
    cls.__base.__div or= @__div
    cls.__base.__unm or= @__unm

--- Base Result Pattern.
--
-- When instantiated with `type`, only succeeds for `Result`s whose value and
-- meta types match.
--
-- Otherwise, matches Streams based only on `metatype` for the first match, but
-- using both afterwards (recall mode).
--
-- @function Type
-- @tparam string metatype `'~', '!' or '='
-- @tparam ?string type type name
class Type extends Pattern
  new: (@metatype, @type, @recall=false) =>

  casts = { '!!': true, '==': true, '~~': true, '~=': true }
  capture: (seq, i) =>
    return unless seq[i]
    type, mt = seq[i]\type!, seq[i]\metatype!

    if @metatype and not casts[@metatype .. mt]
      return

    match = if @type then type == @type else @remember type
    if match
      1, seq[i]

  __call: => @@ @metatype, @type, true
  __tostring: => "#{@type or 'any'}#{@metatype or ''}"

--- Repeat a pattern.
--
-- Matches a given `inner` pattern as many times as possible, within the given
-- minimum/maximum counts. Matching this pattern results in a sequence of the
-- individual captures produced by the inner pattern.
--
-- @function Repeat
-- @tparam Pattern inner the original pattern
-- @tparam ?number min minimum amount of repetitions
-- @tparam ?number max maximum amount of repetitions (default infinite)
class Repeat extends Pattern
  new: (@inner, @min, @max) =>

  capture: (seq, i) =>
    total, rep, all = 0, 0, {}
    while true
      num, cap = @inner\capture seq, i+total
      break unless num

      total += num
      rep += 1
      table.insert all, cap

      break if @max and rep >= @max

    return if @min and rep < @min
    return if @max and rep > @max

    total, all

  reset: =>
    @inner\reset!

  __call: =>
    @@ @inner!, @min, @max

  __tostring: =>
    min = @min or '0'
    max = @max or '*'
    "#{@inner}{#{min}-#{max}}"

--- Match multiple patterns in order.
--
-- Matches the inner patterns in order, only succeeds if all of them match.
-- Captures the individual captures produced by the inner patterns in a
-- sequence, or table with keys specified in `keys` or using the `:named(...)`
-- modifier.
--
-- @function Sequence
-- @tparam {Pattern,...} elements the inner patterns
-- @tparam ?{string,...} keys the keys to use when capturing matches
class Sequence extends Pattern
  new: (@elements, @keys) =>

  capture: (seq, i) =>
    take, all = 0, {}
    for key, elem in ipairs @elements
      num, cap = elem\capture seq, i+take
      return unless num

      take += num
      key = @keys[key] if @keys
      all[key] = cap

    take, all

  reset: =>
    for elem in *@elements
      elem\reset!

  named: (...) =>
    @@ [e for e in *@elements], { ... }

  __call: =>
    @@ [e! for e in *@elements], @keys

  __add: (other) =>
    elements = [e for e in *@elements]
    table.insert elements, other
    @@ elements

  __tostring: =>
    core = table.concat [tostring e for e in *@elements], ' '
    "(#{core})"

--- Match one of multiple options.
--
-- Matches using the first matching pattern in `elements` and returns its
-- captured value. Supports recalling the matched subpattern.
--
-- @function Choice
-- @tparam {Pattern,...} elements the inner patterns
-- @tparam ?{string,...} keys the keys to use when capturing matches
class Choice extends Pattern
  new: (@elements, @recall=false) =>

  capture: (seq, i) =>
    for key, elem in ipairs @elements
      num, cap = elem\capture seq, i
      if num and @remember key
        return num, cap

  reset: =>
    super!
    for elem in *@elements
      elem\reset!

  __call: =>
    @@ [e! for e in *@elements], true

  __div: (other) =>
    elements = [e for e in *@elements]
    table.insert elements, other
    @@ elements

  __tostring: =>
    core = table.concat [tostring e for e in *@elements], ' | '
    "(#{core})"

--- Optionally match a pattern.
--
-- Matches using the first matching pattern in `elements` and returns its
-- captured value. Supports recalling the matched subpattern.
--
-- @function Optional
-- @tparam {Pattern,...} elements the inner patterns
-- @tparam ?{string,...} keys the keys to use when capturing matches
class Optional extends Pattern
  new: (@inner) =>

  capture: (seq, i) =>
    num, cap = @inner\capture seq, i
    num or 0, cap

  reset: =>
    @inner\reset!

  __call: =>
    @@ @inner!

  __unm: => @

  __tostring: => "#{@inner}?"

--- `Type` shorthands for matching `Constant`s.
--
-- Call or index with a string to obtain an `Type` instance.
-- Call to obtain a wildcard pattern.
--
--     const.bang, const.str, const.num
--     const['midi/message'], const(Primitive 'midi/message')
--     const()
--
-- @table const
const = setmetatable {}, {
  __index: (key) =>
    with v = Type '=', T[key]
      @[key] = v

  __call: (...) => Type '=', ...
}

--- `Type` shorthands for matching `ValueStream`s and `Constant`s.
--
-- Call or index with a type or string to obtain a `Type` instance.
-- Call to obtain a wildcard pattern.
--
--     sig.str, sig.num
--     sig['vec3'], sig(T.vec3)
--     sig()
--
-- @table sig
sig = setmetatable {}, {
  __index: (key) =>
    with v = Type '~', T[key]
      @[key] = v

  __call: (...) => Type '~', ...
}

--- `Type` shorthands for matching `EvtStream`s.
--
-- Call or index with a type or string to obtain an `Type` instance.
-- Call to obtain a wildcard pattern.
--
--     evt.bang, evt.str, evt.num
--     evt['midi/message'], evt(Primitive 'midi/message')
--     evt()
--
-- @table evt
evt = setmetatable {}, {
  __index: (key) =>
    with v = Type '!', T[key]
      @[key] = v

  __call: (...) => Type '!', ...
}

--- `Type` shorthands for matching any `Result`s.
--
-- Call or index with a type or string to obtain an `Type` instance.
-- Call to obtain a wildcard pattern.
--
--     any.bang, any.str, any.num
--     any['midi/message'], any(Primitive 'midi/message')
--     any()
--
-- @table evt
any = setmetatable {}, {
  __index: (key) =>
    with v = Type nil, T[key]
      @[key] = v

  __call: (...) => Type nil, ...
}

{
  :Type, :Repeat, :Sequence, :Choice, :Optional
  :const, :sig, :evt, :any
}