diff options
| author | s-ol <s-ol@users.noreply.github.com> | 2020-03-25 10:43:29 +0000 |
|---|---|---|
| committer | s-ol <s-ol@users.noreply.github.com> | 2020-03-25 11:25:05 +0000 |
| commit | 7cb862f6f6079509dafd466fff83c719cb2fd89e (patch) | |
| tree | 843f92c4d4b2507e88a525b7c56c29d343958e5b /core | |
| parent | Value -> Value/Event/IO-Stream (diff) | |
| download | alive-7cb862f6f6079509dafd466fff83c719cb2fd89e.tar.gz alive-7cb862f6f6079509dafd466fff83c719cb2fd89e.zip | |
new core.base.match, update lib
Diffstat (limited to 'core')
| -rw-r--r-- | core/base/init.moon | 10 | ||||
| -rw-r--r-- | core/base/match.moon | 368 | ||||
| -rw-r--r-- | core/base/op.moon | 18 | ||||
| -rw-r--r-- | core/builtin.moon | 7 | ||||
| -rw-r--r-- | core/result.moon | 12 | ||||
| -rw-r--r-- | core/stream/base.moon | 11 | ||||
| -rw-r--r-- | core/stream/event.moon | 12 | ||||
| -rw-r--r-- | core/stream/io.moon | 9 | ||||
| -rw-r--r-- | core/stream/value.moon | 14 | ||||
| -rw-r--r-- | core/version.moon | 4 |
10 files changed, 346 insertions, 119 deletions
diff --git a/core/base/init.moon b/core/base/init.moon index 8e376fc..1b71086 100644 --- a/core/base/init.moon +++ b/core/base/init.moon @@ -1,14 +1,16 @@ ---- -- Base definitions for extensions. -- --- This module exports the following classes that extension modules may need: +-- This module exports the following classes and tables that extension modules +-- may need: -- -- @module base -- @see Op -- @see Action -- @see FnDef -- @see Input --- @see match +-- @see base.match.val +-- @see base.match.evt -- @see ValueStream -- @see EventStream -- @see IOStream @@ -19,7 +21,7 @@ import Op from require 'core.base.op' import Action from require 'core.base.action' import FnDef from require 'core.base.fndef' import Input from require 'core.base.input' -import match from require 'core.base.match' +import val, evt from require 'core.base.match' import ValueStream, EventStream, IOStream from require 'core.stream' import Result from require 'core.result' import Error from require 'core.error' @@ -29,7 +31,7 @@ import Error from require 'core.error' :Action :FnDef :Input - :match + :val, :evt -- redundant exports, to keep anything an extension might need in one import :ValueStream, :EventStream, :IOStream diff --git a/core/base/match.moon b/core/base/match.moon index 69d064d..b780ff6 100644 --- a/core/base/match.moon +++ b/core/base/match.moon @@ -1,95 +1,295 @@ ----- --- Utilities for matching `Result` types. +----- +--- Pattern capturing for Op argument parsing. -- --- @module match +-- There is only one basic buildings block for assembling patterns: +-- `Type`. It can match `ValueStream`s and `EventStream`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: +-- +-- - `val()` and `evt()`: Shorthands for `Type('value')` and `Type('event')` +-- - `val.num`: Shorthand for `Type('value', 'num')` +-- - `evt.str`: Shorthand for `Type('event', 'str')` +-- - `pat * 2`: Shorthand for `Repeat(pat, 1, 2)` (1-4 times `pat`) +-- - `pat * 0`: Shorthand for `Repeat(pat, 1, nil)` (1-* times `pat`) +-- - `pat ^ 2`: Shorthand for `Repeat(pat, 0, 2)` (0-4 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 `Result`s. The method will either return the captured +-- `Result`s (or a table structuring them) +-- +-- Any ambiguous pattern can be set to 'recall mode' by invoking it. +-- Recalling patterns will memorize the first Result they match, and +-- only match further Results of the same type. For example +-- +-- arg = (val.num / val.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. `Type` patterns +-- without a type (`val!` and `evt!`) always behave like this. +-- +-- 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 = (val.str + val.num):named{'key', 'value'} +-- pattern:match(...) +-- -- returns { {key='a', value=1}, {key='b', value=2}, ...} +-- +-- @module base.match import Error from require 'core.error' -unpack or= table.unpack +import ValueStream, EventStream from require 'core.stream' + +local Repeat, Sequence, Choice, Optional 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, Error 'argument', "expected at least one argument for spread" - matched - else - result = results[1] - matches = @matches result - assert @opt or matches, Error 'argument', "argument #{result and result\type!} incompatible with expected type #{@}" - if matches then table.remove results, 1 + match: (seq) => + @reset! + num, cap = @capture seq, 1 + assert num == #seq, do + Error 'argument', "couldn't match arguments against pattern #{@}" + 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 Stream Pattern. +-- +-- When instantiated with `type`, only succeeds for `Stream`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 Stream +-- @tparam string metatype "value" or "event" +-- @tparam ?string type type name +class Type extends Pattern + new: (@metatype, @type) => + @recall = not @type + + capture: (seq, i) => + return unless seq[i] + type, mt = seq[i]\type!, seq[i]\metatype! + return unless @metatype == mt + match = if @type then type == @type else @remember type + if match + 1, seq[i] __tostring: => - str = @type - str = '*' .. str if @splat - str = '=' .. str if @const - str = str .. '?' if @opt + str = tostring @type + str ..= '!' if @metatype == 'event' 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, Error 'argument', "#{#inputs} extra arguments given!" - values +--- 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) => + take, all = 0, {} + while true + num, cap = @inner\capture seq, i+take + break unless num + + take += num + table.insert all, cap + + break if @max and take >= @max + + return if @min and take < @min + return if @max and take > @max + + take, 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(keys)` +-- 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] + + __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}?" + +--- `Value` shorthands. +-- +-- Call or index with a string to obtain a `Type` instance. +-- Call to obtain a wildcard pattern. +-- +-- val.str, val.num +-- val['vec3'], val('vec3') +-- val() +-- +-- @table val +val = setmetatable {}, { + __index: (key) => + with v = Type 'value', key + @[key] = v + + __call: (...) => Type 'value', ... +} + +--- `Event` shorthands. +-- +-- Call or index with a string to obtain an `Type` instance. +-- Call to obtain a wildcard pattern. +-- +-- evt.bang, evt.str, evt.num +-- evt['midi/message'], evt('midi/message') +-- evt() +-- +-- @table evt +evt = setmetatable {}, { + __index: (key) => + with v = Type 'event', key + @[key] = v + + __call: (...) => Type 'event', ... +} { - :Pattern - :match + :Type, :Repeat, :Sequence, :Choice, :Optional + :val, :evt } diff --git a/core/base/op.moon b/core/base/op.moon index 4880afd..b0a83dc 100644 --- a/core/base/op.moon +++ b/core/base/op.moon @@ -5,7 +5,7 @@ deepcopy = (val) -> switch type val - when 'number', 'string', 'boolean' + when 'number', 'string', 'boolean', 'nil' val when 'table' assert (not getmetatable {}), "state should only contain simple tables!" @@ -35,8 +35,8 @@ class Op -- -- @treturn Op fork: => - out = if @out then @out\fork - state = if @state then deepcopy state + out = if @out then @out\fork! + state = if @state then deepcopy @state @@ out, state --- internal state of this Op. @@ -53,8 +53,8 @@ class Op -- `ValueStream`, it should have a value assigned via `set` or the -- constructor once `tick` is called the first time. If `out`'s value is not -- initialized in `new` or `setup`, the implementation must make sure - -- `tick``(true)` is called at -- least on the first eval-cycle the Op goes - -- through, e.g. by using an `Input.value`. + -- `tick``(true)` is called at least on the first eval-cycle the Op goes + -- through, e.g. by using an `Input.hot` with a `ValueStream`. -- -- @tfield Stream out @@ -95,10 +95,10 @@ class Op --- handle incoming events and update `out` (optional). -- - -- Called once per frame if any `Input`s are dirty. Some `Input`s (like - -- `Input.value`) have special behaviour immediately after `setup`, that can - -- cause them to become dirty at eval-time. In this case, an eval-time tick - -- is executed. You can detect this using the `setup` parameter. + -- Called once per frame if any `Input`s are dirty. Some `Input`s may have + -- special behaviour immediately after `setup` that can cause them to become + -- dirty at eval-time. In this case, an eval-time tick is executed. You can + -- detect this using the `setup` parameter. -- -- `tick` is called after `setup`. `tick` is not called immediately after -- `setup` if no `inputs` are dirty. Update `out` here. diff --git a/core/builtin.moon b/core/builtin.moon index f9087c4..1ba9293 100644 --- a/core/builtin.moon +++ b/core/builtin.moon @@ -5,7 +5,7 @@ -- documentation. -- -- @module builtin -import Action, Op, FnDef, Input, match from require 'core.base' +import Action, Op, FnDef, Input from require 'core.base' import ValueStream, LiteralValue from require 'core.stream.value' import Result from require 'core.result' import Cell from require 'core.cell' @@ -263,10 +263,9 @@ trace = ValueStream.meta value: class extends Action class traceOp extends Op setup: (inputs) => - { prefix, value } = match 'str any', inputs super - prefix: Input.cold prefix - value: Input.hot value + prefix: Input.cold inputs[1] + value: Input.hot inputs[2] tick: => L\print "trace #{@inputs.prefix!}: #{@inputs.value.stream}" diff --git a/core/result.moon b/core/result.moon index cf228ec..6c994cb 100644 --- a/core/result.moon +++ b/core/result.moon @@ -12,17 +12,25 @@ class Result --- return whether this Result's value is const. is_const: => not next @side_inputs - --- assert value-constness and returns the value. + --- assert value-constness and return the value. -- @tparam[opt] string msg the error message to throw + -- @treturn any const: (msg) => assert not (next @side_inputs), msg or "eval-time const expected" @value - --- assert this result has a value, returns its type. + --- assert this result has a value, return its type. + -- @treturn string type: => assert @value, "Result with value expected" @value.type + --- assert this result has a value, returns its metatype. + -- @treturn string `"value"` or `"event"` + metatype: => + assert @value, "Result with value expected" + @value.metatype + --- create a copy of this result with value-copy semantics. -- the copy has the same @value and @side_inputs, but will not update -- anything on \tick. diff --git a/core/stream/base.moon b/core/stream/base.moon index ee4ee17..cdccdc2 100644 --- a/core/stream/base.moon +++ b/core/stream/base.moon @@ -53,17 +53,6 @@ class Stream -- -- @tfield ?table meta - __tostring: => - value = if @meta.name - @meta.name - else if 'table' == (type @value) and rawget @value, '__base' - @value.__name - else - tostring @value - "<#{@@__name} #{@type}: #{value}>" - - __inherited: (cls) => cls.__base.__tostring = @__tostring - --- static functions -- @section static diff --git a/core/stream/event.moon b/core/stream/event.moon index 5dbf846..f7b49c4 100644 --- a/core/stream/event.moon +++ b/core/stream/event.moon @@ -26,7 +26,7 @@ class EventStream extends Stream @events = {} @updated = registry.Registry.active!.tick - table.insert @events, {} + table.insert @events, event --- get the sequence of current events (if any). -- @@ -45,11 +45,19 @@ class EventStream extends Stream -- Used to wrap insulate eval-cycles from each other. -- -- @treturn EventStream - fork: => EventStream @type + fork: => @@ @type --- alias for `unwrap`. __call: (...) => @unwrap ... + __tostring: => + "<#{@@__name} #{@type}>" + + --- Stream metatype. + -- + -- @tfield string metatype + metatype: 'event' + --- the type name of the stream. -- -- the following builtin typenames are used: diff --git a/core/stream/io.moon b/core/stream/io.moon index fcaeb9f..e3e59a2 100644 --- a/core/stream/io.moon +++ b/core/stream/io.moon @@ -24,6 +24,13 @@ class IOStream extends EventStream -- @tparam string type the typename of this stream. new: (type) => super type + --- create a mutable copy of this stream. + -- + -- Used to wrap insulate eval-cycles from each other. + -- + -- @treturn IOStream + fork: => @ + --- poll for changes. -- -- Called every frame by the main event loop to update internal state. @@ -32,7 +39,7 @@ class IOStream extends EventStream --- check whether this adapter requires processing. -- -- Must return a boolean indicating whether `Op`s that refer to this instance - -- via `Input.io` should be notified (via `Op:tick`). May be called multiple + -- via `Input.hot` should be notified (via `Op:tick`). May be called multiple -- times. May be called before `tick` on the first frame after construction. -- -- If this is not overrided, the `EventStream` interface can be used, see diff --git a/core/stream/value.moon b/core/stream/value.moon index 0220f06..ce3ffc7 100644 --- a/core/stream/value.moon +++ b/core/stream/value.moon @@ -57,6 +57,20 @@ class ValueStream extends Stream -- Compares two `ValueStream`s by comparing their types and their Lua values. __eq: (other) => other.type == @type and other.value == @value + __tostring: => + value = if @meta.name + @meta.name + else if 'table' == (type @value) and rawget @value, '__base' + @value.__name + else + tostring @value + "<#{@@__name} #{@type}: #{value}>" + + --- Stream metatype. + -- + -- @tfield string metatype + metatype: 'value' + --- the type name of this stream. -- -- the following builtin typenames are used: diff --git a/core/version.moon b/core/version.moon index af97600..41d4ab3 100644 --- a/core/version.moon +++ b/core/version.moon @@ -12,8 +12,8 @@ -- @tfield string web the repo web URL { tag: "v0.0" - rev_short: "662def4" - rev_long: "662def4ef082412147ae8126e80065d245f4b426" + rev_short: "677c0d2" + rev_long: "677c0d2f01e14bbeca1583ec7878d80c71c3aa68" repo: "https://github.com/s-ol/alivecoding.git" web: "https://github.com/s-ol/alivecoding/tree/v0.0" } |
