aboutsummaryrefslogtreecommitdiffstats
path: root/core/base/match.moon
diff options
context:
space:
mode:
Diffstat (limited to 'core/base/match.moon')
-rw-r--r--core/base/match.moon93
1 files changed, 93 insertions, 0 deletions
diff --git a/core/base/match.moon b/core/base/match.moon
new file mode 100644
index 0000000..a20bcdc
--- /dev/null
+++ b/core/base/match.moon
@@ -0,0 +1,93 @@
+----
+-- Utilities for matching `Result` types.
+--
+-- @module match
+unpack or= table.unpack
+
+class Pattern
+ new: (opts) =>
+ if 'string' == type opts
+ splat, const, type, opt = opts\match '^(%*?)(=?)([%w%-%_%/]+)(%??)$'
+ assert type, "couldn't parse type pattern '#{opts}'"
+ opts = {
+ :type
+ splat: splat == '*'
+ const: const == '='
+ opt: opt == '?'
+ }
+
+ @type = opts.type
+ @const = opts.const
+ @opt = opts.opt
+ @splat = opts.splat
+
+ matches: (result) =>
+ return false unless result
+
+ if @const
+ return false unless result\is_const!
+
+ if not result.value
+ return @type == 'nil'
+
+ return true if @type == 'any'
+
+ result.value.type == @type
+
+ match: (results) =>
+ if @splat
+ matched = while @matches results[1]
+ table.remove results, 1
+
+ assert @opt or #matched > 0, "expected at least one argument for spread"
+ matched
+ else
+ matches = @matches results[1]
+ assert @opt or matches, "couldn't match argument #{results[1]} as #{@}"
+ if matches then table.remove results, 1
+
+ __tostring: =>
+ str = @type
+ str = '*' .. str if @splat
+ str = '=' .. str if @const
+ str = str .. '?' if @opt
+ str
+
+--- match inputs to a argument type definition.
+--
+-- `pattern` is a string of type entries. Every type entry can be like this:
+--
+-- - `any` - matches one `Result` and returns it.
+-- - `typename` - matches one `Result` of type `typename` and returns it.
+-- - `=typename` - matches one eval-time const `Result`s of type `typname` and
+-- returns it.
+-- - `typename?` - matches what `typename` would match, if possible (greedy).
+-- Otherwise returns `nil`.
+-- - `*typename` - matches as many `typename` `Result`s as possible (greedy).
+-- Throws if there isn't at least one such `Result`. Returns a sequence of
+-- `Result`s.
+-- - `*typename?` - like `*typename`, except it also matches zero `Result`s.
+-- - `*=typename`, `=typename?` and `*=typename?` behave as expected.
+--
+-- Except for `typename?` and `*typename?`, all entries throw if they cannot
+-- match the next `Result` in `inputs`.
+--
+-- Throws if there are leftover `inputs` after matching all of `pattern`.
+--
+-- @tparam string pattern the argument type definition
+-- @tparam {Result,...} inputs the list of inputs
+-- @treturn {Result|{Result,...},...} the inputs as matched against `pattern`
+match = (pattern, inputs) ->
+ patterns = while pattern
+ pat, rest = pattern\match '^([^ ]+) (.*)$'
+ pat = pattern unless pat
+ pattern = rest
+ Pattern pat
+ values = [p\match inputs for p in *patterns]
+ assert #inputs == 0, "#{#inputs} extra arguments given!"
+ values
+
+{
+ :Pattern
+ :match
+}