aboutsummaryrefslogtreecommitdiffstats
path: root/core/base
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2020-03-07 20:58:30 +0000
committers-ol <s-ol@users.noreply.github.com>2020-03-07 20:58:51 +0000
commit6a7a2ddaca798f3cccac394d1fb9f317cbe90de6 (patch)
tree4e49e53057b39f75813311ede13f87d238061fe6 /core/base
parentspellcheck (diff)
downloadalive-6a7a2ddaca798f3cccac394d1fb9f317cbe90de6.tar.gz
alive-6a7a2ddaca798f3cccac394d1fb9f317cbe90de6.zip
add ldoc documentation
Diffstat (limited to 'core/base')
-rw-r--r--core/base/action.moon95
-rw-r--r--core/base/fndef.moon22
-rw-r--r--core/base/init.moon35
-rw-r--r--core/base/input.moon131
-rw-r--r--core/base/io.moon34
-rw-r--r--core/base/match.moon93
-rw-r--r--core/base/op.moon132
7 files changed, 542 insertions, 0 deletions
diff --git a/core/base/action.moon b/core/base/action.moon
new file mode 100644
index 0000000..0b9dc77
--- /dev/null
+++ b/core/base/action.moon
@@ -0,0 +1,95 @@
+----
+-- Builtin / Special Form / `Cell`-evaluation Strategy.
+--
+-- Responsible for quoting/evaluating subexpressions, instantiating and patching
+-- `Op`s, updating the current `Scope`, etc.
+-- See `builtin` and `invoke` for examples.
+--
+-- @classmod Action
+
+class Action
+--- Action interface.
+--
+-- methods that have to be implemented by `Action` implementations.
+-- @section interface
+
+ --- create a new instance
+ -- @tparam Value head the (`\eval`d) head of the Cell to evaluate
+ -- @tparam Tag tag the Tag of the expression to evaluate
+ new: (head, @tag) =>
+ @patch head
+
+ --- perform the actual evaluation
+ --
+ -- Implementations should:
+ --
+ -- - eval or quote `tail` values
+ -- - perform scope effects
+ -- - wrap all child-results
+ --
+ -- @tparam Scope scope the active scope
+ -- @tparam {AST,...} tail the arguments to this expression
+ -- @treturn Result the result of this evaluation
+ eval: (scope, tail) => error "not implemented"
+
+ --- free resources
+ destroy: =>
+
+ --- attempt to update this instance with a new `@head` prior to `\eval`.
+ --
+ -- If `\patch` returns `false`, this instance is `\destroy`ed and recreated.
+ -- Must *not* return `false` when called immediately after `\new`.
+ -- Only considered if Action types of old and new expression match.
+ --
+ -- @tparam AST head the new head value
+ -- @treturn bool whether patching was successful
+ patch: (head) =>
+ if head == @head
+ true
+
+ @head = head
+
+--- static functions
+-- @section static
+
+ --- get-or-update an `Action` for a given tag, then evaluate it.
+ --
+ -- Find the action for the expression with `Tag` `tag` if it exists,
+ -- and is compatible with the new `head`, otherwise instantiate one.
+ -- Register the `Action` with `tag`, evaluate it and return the `Result`.
+ --
+ -- @tparam Scope scope the active scope
+ -- @tparam Tag tag the tag of the `Cell` being evaluated
+ -- @tparam Value head the (`\eval`d) head of the `Cell` being evaluated
+ -- @tparam {Ast,...} tail the raw AST parameters to the `Cell` being evaluated
+ -- @treturn Result the result of evaluation
+ eval_cell: (scope, tag, head, tail) =>
+ last = tag\last!
+ compatible = last and
+ (last.__class == @) and
+ (last\patch head) and
+ last
+
+ L\trace if compatible
+ "reusing #{last} for #{tag} <#{@__name} #{head}>"
+ else if last
+ "replacing #{last} with new #{tag} <#{@__name} #{head}>"
+ else
+ "initializing #{tag} <#{@__name} #{head}>"
+
+ action = if compatible
+ tag\keep compatible
+ compatible
+ else
+ last\destroy! if last
+ with next = @ head, tag
+ tag\replace next
+
+ action\eval scope, tail
+
+ __tostring: => "<#{@@__name} #{@head}>"
+ __inherited: (cls) => cls.__base.__tostring = @__tostring
+
+{
+ :Action
+}
diff --git a/core/base/fndef.moon b/core/base/fndef.moon
new file mode 100644
index 0000000..b6c284b
--- /dev/null
+++ b/core/base/fndef.moon
@@ -0,0 +1,22 @@
+----
+-- `alive` user-function definition (`fndef`).
+--
+-- When called, expands to its body with params bound to the fn arguments (see
+-- `invoke.fn_invoke`).
+--
+-- @classmod FnDef
+
+class FnDef
+ --- create a new instance
+ --
+ -- @tparam {Value,...} params (`\quote`d) naming the function parameters
+ -- @tparam AST body (`\quote`d) expression the function evaluates to
+ -- @tparam Scope scope the lexical scope the function was defined in (closure)
+ new: (@params, @body, @scope) =>
+
+ __tostring: =>
+ "(fn (#{table.concat [p\stringify! for p in *@params], ' '}) ...)"
+
+{
+ :FnDef
+}
diff --git a/core/base/init.moon b/core/base/init.moon
new file mode 100644
index 0000000..64b34f4
--- /dev/null
+++ b/core/base/init.moon
@@ -0,0 +1,35 @@
+----
+-- Base definitions for extensions.
+--
+-- This module exports the following classes that extension modules may need:
+--
+-- @module base
+-- @see IO
+-- @see Op
+-- @see Action
+-- @see FnDef
+-- @see Input
+-- @see match
+-- @see Value
+-- @see Result
+
+import IO from require 'core.base.io'
+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 Value from require 'core.value'
+import Result from require 'core.result'
+
+{
+ :IO
+ :Op
+ :Action
+ :FnDef
+ :Input
+ :match
+
+ -- redundant exports, to keep anything an extension might need in one import
+ :Value, :Result
+}
diff --git a/core/base/input.moon b/core/base/input.moon
new file mode 100644
index 0000000..14c812e
--- /dev/null
+++ b/core/base/input.moon
@@ -0,0 +1,131 @@
+----
+-- Update scheduling policy for `Op` arguments.
+--
+-- @classmod Input
+import Value from require 'core.value'
+import Result from require 'core.result'
+
+local ColdInput, ValueInput, EventInput, IOInput
+
+class Input
+--- Input interface.
+--
+-- Methods that have to be implemented by `Input` implementations.
+-- @section interface
+
+ --- create an instance (optional).
+ --
+ -- `value` is either a `Value` or a `Result` instance and should be
+ -- unwrapped and assigned to `@stream`.
+ --
+ -- @function new
+ -- @tparam Value|Result value
+ new: (value) =>
+ assert value, "nil passed to Input: #{value}"
+ @stream = switch value.__class
+ when Result
+ assert value.value, "Input from result without value!"
+ when Value
+ value
+ else
+ error "Input from unknown value: #{value}"
+
+ --- copy state from old instance (optional).
+ --
+ -- called by `Op``\setup` with another `Input` instance or `nil` once this instance is
+ -- registered. Must prepare this instance for `\dirty`.
+ --
+ -- May enter a 'setup state' that is exited using `\finish_setup`.
+ --
+ -- @function setup
+ -- @tparam `Input?` prev previous `Input` intance or nil
+ setup: (prev) =>
+
+ --- whether this input requires processing (optional).
+ --
+ -- must return a boolean indicating whether `Op`s that refer to this instance
+ -- should be notified (via `Op``\tick`). If not overwritten, delegates to
+ -- `@stream\dirty`.
+ --
+ -- @treturn bool whether processing is necessary
+ dirty: => @stream\dirty!
+
+ --- leave setup state (optional).
+ --
+ -- called after the `Op` has completed (or skipped) its first `Op``\tick` after
+ -- `Op``\setup`. Must prepare this instance for dataflow operation.
+ finish_setup: =>
+
+ --- unwrap to Lua value (optional).
+ --
+ -- @treturn any the raw Lua value
+ unwrap: => @stream\unwrap!
+
+ --- return the type name of this `Input` (optional).
+ type: => @stream.type
+
+--- methods.
+--
+-- @section methods
+
+ --- alias for `\unwrap`.
+ __call: => @stream\unwrap!
+
+ __tostring: => "#{@@__name}:#{@stream}"
+ __inherited: (cls) =>
+ cls.__base.__call = @__call
+ cls.__base.__tostring = @__tostring
+
+--- constructors
+-- @section constructors
+
+ --- Create a `ColdInput`.
+ --
+ -- Never marked dirty. Use this for input streams that are only read when
+ -- another `Input` is dirty.
+ --
+ -- @tparam Value|Result value
+ cold: (value) -> ColdInput value
+
+ --- Create a `ValueInput`.
+ --
+ -- Marked dirty for the eval-tick if old and new `Value` differ. This is the
+ -- most common `Input` strategy. Should be used whenever a
+ -- value denotes state.
+ --
+ -- @tparam Value|Result value
+ value: (value) -> ValueInput value
+
+ --- Create an `EventInput`.
+ --
+ -- Only marked dirty if the `Value` itself is dirty. Should be used whenever
+ -- an `Input` denotes a momentary event or impulse.
+ --
+ -- @tparam Value|Result value
+ event: (value) -> EventInput value
+
+ --- Create an `IOInput`.
+ --
+ -- Marked dirty only when an `IO` is dirty. Must be used only for `Value`s
+ -- which `\unwrap` to `IO` instances.
+ --
+ -- @tparam Value|Result value
+ io: (value) -> IOInput value
+
+class ColdInput extends Input
+ dirty: => false
+
+class ValueInput extends Input
+ setup: (old) => @dirty_setup = not old or @stream != old.stream
+ finish_setup: => @dirty_setup = false
+ dirty: => @dirty_setup or @stream\dirty!
+
+class EventInput extends Input
+
+class IOInput extends Input
+ impure: true
+ dirty: => @stream\unwrap!\dirty!
+
+{
+ :Input
+}
diff --git a/core/base/io.moon b/core/base/io.moon
new file mode 100644
index 0000000..e7399e8
--- /dev/null
+++ b/core/base/io.moon
@@ -0,0 +1,34 @@
+----
+-- Incoming side-effect adapter, polled by the main event loop to pump
+-- events into the dataflow graph.
+--
+-- @classmod IO
+
+class IO
+--- IO interface.
+--
+-- methods that have to be implemented by `IO` implementations.
+-- @section interface
+
+ --- construct a new instance.
+ --
+ -- Must prepare the instance for `\dirty` to be called.
+ new: =>
+
+ --- poll for changes.
+ --
+ -- Called every frame by the main event loop to update internal state.
+ tick: =>
+
+ --- 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
+ -- times. May be called before `\tick` on the first frame after construction.
+ --
+ -- @treturn bool whether processing is required
+ dirty: =>
+
+{
+ :IO
+}
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
+}
diff --git a/core/base/op.moon b/core/base/op.moon
new file mode 100644
index 0000000..6d1ec98
--- /dev/null
+++ b/core/base/op.moon
@@ -0,0 +1,132 @@
+----
+-- Persistent expression Operator.
+--
+-- @classmod Op
+import Value from require 'core.value'
+
+class Op
+--- Op interface.
+--
+-- methods that have to be implemented by `Op` implementations.
+-- @section interface
+
+ --- construct a new instance.
+ --
+ -- The super-constructor can be used to construct a `Value` instance in `@out`.
+ --
+ -- @function new
+
+ --- parse arguments and patch self.
+ --
+ -- Called once every eval-cycle. `inputs` is a list of `Result`s that are the
+ -- argument to this op. The `inputs` have to be wrapped in `Input` instances
+ -- to define update behaviour. Use `base.match` to parse them, then delegate to
+ -- `super\setup` to patch the `Input` instances.
+ --
+ -- @function setup
+ -- @tparam inputs [`Result`] a sequence of `Result`s
+ -- @tparam scope Scope the active scope
+
+ --- handle incoming events and update `@out` (optional).
+ --
+ -- Called once per frame if any `Input`s are dirty. Some `Input`s (like
+ -- `ValueInput`) 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.
+ --
+ -- @tparam bool setup whether this is an eval-time tick
+ tick: =>
+
+ --- called when the Op is destroyed (optional).
+ destroy: =>
+
+ --- a `Value` instance representing this Op's computed output value.
+ --
+ -- Must be set to a `Value` instance once `\setup` finishes. Must not change
+ -- type, be removed or replaced outside of `\new` and `\setup`. Should have a
+ -- value assigned via `\set` or the `Value` 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 a
+ -- `ValueInput`.
+ --
+ -- @tfield Value out
+
+--- implementation utilities.
+--
+-- super-methods and utilities for use by implementations.
+-- @section super
+
+ --- if `type` is passed, an output stream is instantiated.
+ -- if `init` is passed, the stream is initialized to that Lua value.
+ -- it is okay not to use this and create the output stream in :setup() if the
+ -- type is not known at this time.
+ --
+ -- @tparam[opt] string type the type-name for `@out`
+ -- @tparam[optchain] any init the initial value for `@out`
+ new: (type, init) =>
+ if type
+ @out = Value type, init
+
+ --- setup previous `@inputs`, if any, with the new inputs, and write them to
+ -- `@inputs`. The `inputs` table can be nested with string or integer keys,
+ -- but all leaf-entries must be `Input` instances. It must not contain loops
+ -- or instances of other classes.
+ --
+ -- @tparam table inputs table of `Input`s
+ setup: do
+ do_setup = (old, cur) ->
+ for k, cur_val in pairs cur
+ old_val = old and old[k]
+
+ -- are these inputs or nested tables?
+ cur_plain = cur_val and not cur_val.__class
+ old_plain = old_val and not old_val.__class
+
+ if cur_plain and old_plain
+ -- both are tables, recurse
+ do_setup old_val, cur_val
+ elseif not (cur_plain or old_plain)
+ -- both are streams (or nil), setup them
+ cur_val\setup old_val
+
+ (inputs) =>
+ old_inputs = @inputs
+ @inputs = inputs
+ do_setup old_inputs, @inputs
+
+ --- yield all `Input`s from the (potentially nested) inputs table
+ --
+ -- @treturn iterator iterator over all `Input`s
+ all_inputs: do
+ do_yield = (table) ->
+ for k, v in pairs table
+ if v.__class
+ coroutine.yield v
+ else
+ do_yield v
+
+ => coroutine.wrap -> do_yield @inputs
+
+ --- `\unwrap` all `Input`s in `@inputs` and return a table with the same
+ -- shape.
+ --
+ -- @treturn table the values of all `Input`s
+ unwrap_all: do
+ do_unwrap = (value) ->
+ if value.__class
+ value\unwrap!
+ else
+ {k, do_unwrap v for k,v in pairs value}
+
+ => do_unwrap @inputs
+
+ __tostring: => "<op: #{@@__name}>"
+ __inherited: (cls) => cls.__base.__tostring = @__tostring
+
+{
+ :Op
+}