diff options
| author | s-ol <s-ol@users.noreply.github.com> | 2020-03-07 20:58:30 +0000 |
|---|---|---|
| committer | s-ol <s-ol@users.noreply.github.com> | 2020-03-07 20:58:51 +0000 |
| commit | 6a7a2ddaca798f3cccac394d1fb9f317cbe90de6 (patch) | |
| tree | 4e49e53057b39f75813311ede13f87d238061fe6 /core/base | |
| parent | spellcheck (diff) | |
| download | alive-6a7a2ddaca798f3cccac394d1fb9f317cbe90de6.tar.gz alive-6a7a2ddaca798f3cccac394d1fb9f317cbe90de6.zip | |
add ldoc documentation
Diffstat (limited to 'core/base')
| -rw-r--r-- | core/base/action.moon | 95 | ||||
| -rw-r--r-- | core/base/fndef.moon | 22 | ||||
| -rw-r--r-- | core/base/init.moon | 35 | ||||
| -rw-r--r-- | core/base/input.moon | 131 | ||||
| -rw-r--r-- | core/base/io.moon | 34 | ||||
| -rw-r--r-- | core/base/match.moon | 93 | ||||
| -rw-r--r-- | core/base/op.moon | 132 |
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 +} |
