aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2020-03-25 10:43:29 +0000
committers-ol <s-ol@users.noreply.github.com>2020-03-25 11:25:05 +0000
commit7cb862f6f6079509dafd466fff83c719cb2fd89e (patch)
tree843f92c4d4b2507e88a525b7c56c29d343958e5b /core
parentValue -> Value/Event/IO-Stream (diff)
downloadalive-7cb862f6f6079509dafd466fff83c719cb2fd89e.tar.gz
alive-7cb862f6f6079509dafd466fff83c719cb2fd89e.zip
new core.base.match, update lib
Diffstat (limited to 'core')
-rw-r--r--core/base/init.moon10
-rw-r--r--core/base/match.moon368
-rw-r--r--core/base/op.moon18
-rw-r--r--core/builtin.moon7
-rw-r--r--core/result.moon12
-rw-r--r--core/stream/base.moon11
-rw-r--r--core/stream/event.moon12
-rw-r--r--core/stream/io.moon9
-rw-r--r--core/stream/value.moon14
-rw-r--r--core/version.moon4
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"
}