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 | |
| parent | spellcheck (diff) | |
| download | alive-6a7a2ddaca798f3cccac394d1fb9f317cbe90de6.tar.gz alive-6a7a2ddaca798f3cccac394d1fb9f317cbe90de6.zip | |
add ldoc documentation
38 files changed, 1033 insertions, 606 deletions
@@ -17,6 +17,7 @@ text-based language and works without special editor support. - [lua-rtmidi][rtmidi]: `luarocks install https://raw.githubusercontent.com/s-ol/lua-rtmidi/master/lua-rtmidi-dev-1.rockspec` - [busted][busted]: `luarocks install busted` (optional, for tests) - [discount][discount]: `luarocks install discount` (optional, for docs) +- [ldoc][ldoc]: `luarocks install ldoc` (optional, for docs) \* these are also `moonscript` dependencies and do not neet to be installed manually. diff --git a/core/base.moon b/core/base.moon deleted file mode 100644 index 7962545..0000000 --- a/core/base.moon +++ /dev/null @@ -1,298 +0,0 @@ --- base definitions for extensions -import Value from require 'core.value' -import Result from require 'core.result' -import match from require 'core.pattern' - -unpack or= table.unpack - --- an incoming side-effect adapter, polled by the main event loop to pump --- events into the dataflow graph. --- --- subclasses must implement this interface: --- --- :new() - construct a new instance --- --- must prepare the instance for :dirty(). --- --- :tick() - poll for changes --- --- called every frame by the event loop to update internal state. --- --- :dirty() - whether this adapter requires processing --- --- must return a boolean indicating whether `Op`s that refer to this instance --- via `IOInput` should be notified (via `Op:tick()`). May be called multiple --- multiple times. May be called before :tick() on the first frame after --- construction. --- -class IO - -- called in the main event loop - tick: => - - -- whether a tree update is necessary - dirty: => - --- a persistent expression Operator --- --- subclasses must implement this interface: --- --- :new() - construct a new instance --- --- the super-constructor can be used to construct a `Value` instance in @out. --- --- :setup(inputs, scope) - 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 `match` to parse them, then delegate to --- super to patch the `Input` instances. --- --- :tick(setup) - handle incoming events and update @out --- --- called once per frame if any inputs are dirty. Some `Input`s (like --- `ValueInput`) have special behaviour immediately after :setup(). You can --- detect this using the `setup` parameter, which is true the first time --- :tick() is called after :setup(). :tick() is not called immediately after --- :setup() if no `@inputs` are dirty. Update @out here. --- --- :destroy() - called when the Op is destroyed --- --- .out - a `Value` instance representing this Op's computed output value. --- --- @out must be set to a `Value` instance once :setup() finishes. @out must --- not change type, be removed or replaced outside of :new() and :setup(). --- @out should have a value assigned via :set() or the `Value` constructor --- once :tick(true) is called. 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`. --- -class Op --- super-implementations for extensions - -- 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. - new: (type, init) => - if type - @out = Value type, init - - -- setups previous @inputs, if any, with the new inputs, and writes 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. - 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 - - tick: => - destroy: => - --- utilities - -- iterate over the (potentially nested) inputs table - 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: do - do_unwrap = (value) -> - if value.__class - value\unwrap! - else - {k, do_unwrap v for k,v in pairs value} - - => do_unwrap @inputs - - assert_types: (...) => - num = select '#', ... - assert #@inputs >= num, "argument count mismatch" - @assert_first_types ... - - assert_first_types: (...) => - num = select '#', ... - for i = 1, num - expect = select i, ... - assert @inputs[i].type == expect, "expected argument #{i} of #{@} to be of type #{expect} but found #{@inputs[i]}" - --- static - __tostring: => "<op: #{@@__name}>" - __inherited: (cls) => cls.__base.__tostring = @__tostring - --- a builtin / special form / cell-evaluation strategy. --- --- responsible for quoting/evaluating subexpressions, instantiating and patching --- Ops updating the current Scope, etc. See core.builtin and core.invoke for --- many examples. -class Action - -- head: the (:eval'd) head of the Cell to evaluate (a Const) - -- tag: the Tag of the expression to evaluate - new: (head, @tag) => - @patch head - - -- * eval args - -- * perform scope effects - -- * patch nested exprs - -- * return runtime-tree value - eval: (scope, tail) => error "not implemented" - - -- free resources - destroy: => - - -- update this instance for :eval() with new head - -- if :patch() returns false, this instance is :destroy'ed and recreated - -- instead must *not* return false when called after :new() - -- only considered if Action types match - patch: (head) => - if head == @head - true - - @head = head - --- static - -- find & patch the action for the expression with Tag 'tag' if it exists, - -- and is compatible with the new Cell contents, otherwise instantiate it. - -- register the action with the tag, evaluate it and return the Result - @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 - --- an ALV function definition --- --- when called, expands its body with params bound to the fn arguments --- (see core.invoke.fn-invoke) -class FnDef - -- params: sequence of (:quote'd) symbols, each naming a function parameter - -- body: (:quote'd) expression the function evaluates to - -- scope: the lexical scope the function was defined in (closure) - new: (@params, @body, @scope) => - - __tostring: => - "(fn (#{table.concat [p\stringify! for p in *@params], ' '}) ...)" - --- an update scheduling policy for `Op`. --- --- subclasses must implement this interface: --- --- :new(value) - create an instance --- --- `value` is either a `Value` or a `Result` instance and should be unwrapped. --- --- :setup(prev) - copy state from old instance --- --- called by `Op:setup()` with another `Input` instance or `nil` once this instance is --- registered. Must prepare this instance for :dirty(). --- ---- :dirty() - whether this input requires processing --- --- must return a boolean indicating whether `Op`s that refer to this instance --- should be notified (via `Op:tick()`). --- --- :finish_setup() - leave setup state --- --- called after the Op has completed (or skipped) its first `Op:tick()` after --- `Op:setup()`. Must prepare this instance for dataflow operation. --- -class Input - 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}" - - setup: (previous) => - - finish_setup: => - dirty: => @stream\dirty! - unwrap: => @stream\unwrap! - type: => @stream.type - - __call: => @stream\unwrap! - __tostring: => "#{@@__name}:#{@stream}" - __inherited: (cls) => - cls.__base.__call = @__call - cls.__base.__tostring = @__tostring - --- Never marked dirty. Use this for input streams that are only read when --- another input fires. -class ColdInput extends Input - dirty: => false - --- Marked dirty for the setup-tick if old and new stream differ in current --- value. This is the most common `Input` strategy. Should be used whenever a --- value denotes state. -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! - --- Only marked dirty if the input stream itself is dirty. Should be used --- whenever a value denotes a momentary event or impulse. -class EventInput extends Input - --- Marked dirty when an IO object is dirty. Must be used for IO values. -class IOInput extends Input - impure: true - dirty: => @stream\unwrap!\dirty! - -{ - :IO - :Op - :Action - :FnDef - - :ValueInput, :EventInput, :IOInput, :ColdInput - - -- redundant exports, to keep anything an extension might need in one import - :Value, :Result - :match -} 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 +} diff --git a/core/cell.moon b/core/cell.moon index 850a9db..4d46b64 100644 --- a/core/cell.moon +++ b/core/cell.moon @@ -1,12 +1,60 @@ --- ALV Cell type +---- +-- S-Expression Cell, implements the `AST` interface. +-- +-- Consists of a head expression and any number of tail expressions (both `AST` +-- nodes), a `Tag`, and optionally the internal whitespace as parsed. +-- +-- @classmod Cell import Value from require 'core.value' import op_invoke, fn_invoke from require 'core.invoke' import Tag from require 'core.tag' --- An S-Expression with a head expression and any number of tail expressions, --- an optional tag, and optionally the internal whitespace as parsed. +local RootCell + class Cell --- AST interface +--- methods +-- @section methods + + -- tag: the parsed Tag + -- children: sequence of child AST Nodes + -- white: optional sequence of whitespace segments ([0 .. #@children]) + new: (@tag=Tag.blank!, @children, @white) => + if not @white + @white = [' ' for i=1,#@children] + @white[0] = '' + + assert #@white == #@children, "mismatched whitespace length" + + --- get the head of the cell. + -- + -- @treturn AST + head: => @children[1] + + --- get the tail of the cell. + -- + -- @treturn {AST,...} + tail: => [c for c in *@children[2,]] + + __tostring: => @stringify 2 + +--- AST interface +-- +-- `Cell` implements the `AST` interface. +-- @section ast + + --- evaluate this Cell. + -- + -- `\eval`uates the head of the expression, and finds the appropriate + -- `Action` to invoke: + -- + -- - if head is an `opdef`, use `invoke.op_invoke` + -- - if head is a `fndef`, use `invoke.fn_invoke` + -- - if head is a `builtin`, unwrap it + -- + -- then calls `\eval_cell` on the `Action`. + -- + -- @tparam Scope scope the scope to evaluate in + -- @treturn Result the evaluation result eval: (scope) => head = assert @head!, "cannot evaluate expr without head" head = (head\eval scope)\const! @@ -24,19 +72,36 @@ class Cell Action\eval_cell scope, @tag, head, @tail! - -- quoting a Cell recursively quotes children, but preserves identity. This - -- means that a quoted Cell may only be 'used' once. Use :clone() otherwise. + --- quote this Cell, preserving its identity. + -- + -- Recursively quotes children, but preserves identity (i.e, shares the + -- `Tag`). A quoted Cell may only be 'used' once. If you want to `\eval` a + -- `Cell` multiple times, use `\clone`. + -- + -- @treturn Cell quoted quote: (scope) => children = [child\quote scope for child in *@children] Cell @tag, children, @white + --- create a clone with its own identity. + -- -- creates a clone of this Cell with its own identity by prepending a `parent` - -- tag and cloning all child expressoins recursively. + -- to `@tag` and cloning all child expressoins recursively. + -- + -- @treturn Cell clone clone: (parent) => tag = @tag\clone parent children = [child\clone parent for child in *@children] Cell tag, children, @white + --- stringify this Cell. + -- + -- if `depth` is passed, does not faithfully recreate the original string but + -- rather create useful debug output. + -- + -- @tparam[opt] int depth the maximum depth, defaults to infinite + -- @treturn string the exact string this Cell was parsed from, unless `@tag` + -- changed stringify: (depth=-1) => buf = '' buf ..= if depth > 0 then '' else @white[0] @@ -54,22 +119,8 @@ class Cell '(' .. tag .. buf .. ')' --- internal - -- tag: the parsed Tag - -- children: sequence of child AST Nodes - -- white: optional sequence of whitespace segments ([0 .. #@children]) - new: (@tag=Tag.blank!, @children, @white) => - if not @white - @white = [' ' for i=1,#@children] - @white[0] = '' - - assert #@white == #@children, "mismatched whitespace length" - - head: => @children[1] - tail: => [c for c in *@children[2,]] - --- static - __tostring: => @stringify 2 +--- static functions +-- @section static parse_args = (tag, parts) -> if not parts @@ -82,13 +133,26 @@ class Cell white[i/2] = parts[i+1] tag, children, white - @parse: (...) => + --- parse a Cell (for parsing with Lpeg). + -- + -- @tparam[opt] Tag tag + -- @tparam table parts + -- @treturn Cell + parse: (...) => tag, children, white = parse_args ... @ tag, children, white --- A parenthesis-less Cell (root of an ALV document). --- --- has an implicit head of 'do'. + --- parse a root Cell (for parsing with Lpeg). + -- + -- Root-Cells are at the root of an ALV document. + -- They have an implicit head of 'do' and a `[0]` tag. + -- + -- @tparam table parts + -- @treturn Cell + parse_root: (parts) => + RootCell.parse RootCell, (Tag\parse '0'), parts + +-- @type RootCell class RootCell extends Cell head: => Value.sym 'do' tail: => @children @@ -103,9 +167,6 @@ class RootCell extends Cell buf - @parse: (...) => - @__parent.parse @, (Tag\parse '0'), ... - { :Cell :RootCell diff --git a/core/config.ld b/core/config.ld new file mode 100644 index 0000000..1c07337 --- /dev/null +++ b/core/config.ld @@ -0,0 +1,4 @@ +project = 'alive copilot' +title = 'alive copilot reference' +format = 'discount' +dir = 'docs/copilot' diff --git a/core/init.moon b/core/init.moon index 7122e0f..c4e8ffd 100644 --- a/core/init.moon +++ b/core/init.moon @@ -1,3 +1,17 @@ +---- +-- `alive` public API. +-- +-- @see Value +-- @see Result +-- @see Cell +-- @see Scope +-- @see Registry +-- @see Tag +-- @field globals +-- @field parse +-- @field eval +-- +-- @module init L or= setmetatable {}, __index: => -> import Value from require 'core.value' @@ -6,7 +20,7 @@ import Scope from require 'core.scope' import Registry from require 'core.registry' import Tag from require 'core.tag' -import Cell, RootCell from require 'core.cell' +import Cell from require 'core.cell' import cell, program from require 'core.parsing' with require 'core.cycle' @@ -14,6 +28,17 @@ with require 'core.cycle' globals = Scope.from_table require 'core.builtin' +--- exports +-- @table exports +-- @tfield Value Value +-- @tfield Result Result +-- @tfield Cell Cell +-- @tfield RootCell RootCell +-- @tfield Scope Scope +-- @tfield Registry Registry +-- @tfield Tag Tag +-- @tfield Scope globals global definitons +-- @tfield parse function to turn a `string` into a root `Cell` { :Value, :Result :Cell, :RootCell diff --git a/core/invoke.moon b/core/invoke.moon index 7ff16dc..69ce881 100644 --- a/core/invoke.moon +++ b/core/invoke.moon @@ -1,3 +1,7 @@ +---- +-- Builtins for invoking `Op`s and `FnDef`s. +-- +-- @module invoke import Value from require 'core.value' import Result from require 'core.result' import Action from require 'core.base' diff --git a/core/parsing.moon b/core/parsing.moon index 8a99d89..911f473 100644 --- a/core/parsing.moon +++ b/core/parsing.moon @@ -1,5 +1,9 @@ +---- +-- Lpeg Grammar for parsing `alive` code. +-- +-- @module parsing import Value from require 'core.value' -import Cell, RootCell from require 'core.cell' +import Cell from require 'core.cell' import Tag from require 'core.tag' import R, S, P, V, C, Ct from require 'lpeg' @@ -35,7 +39,7 @@ tag = (P '[') * (digit^1 / Tag\parse) * (P ']') cell = (P '(') * tag^-1 * (V 'explist') * (P ')') / Cell\parse root = P { - (V 'explist') / RootCell\parse + (V 'explist') / Cell\parse_root :expr, :explist, :cell } @@ -46,6 +50,17 @@ cell = P { program = root * -1 +--- exports +-- @table exports +-- @tfield pattern comment +-- @tfield pattern space +-- @tfield pattern atom +-- @tfield pattern expr +-- @tfield pattern explist +-- @tfield pattern explist +-- @tfield pattern cell +-- @tfield pattern root +-- @tfield pattern program the main parsing entrypoint { :comment :space diff --git a/core/pattern.moon b/core/pattern.moon deleted file mode 100644 index 13497af..0000000 --- a/core/pattern.moon +++ /dev/null @@ -1,65 +0,0 @@ -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 = (pattern, results) -> - patterns = while pattern - pat, rest = pattern\match '^([^ ]+) (.*)$' - pat = pattern unless pat - pattern = rest - Pattern pat - values = [p\match results for p in *patterns] - assert #results == 0, "#{#results} extra arguments given!" - values - -{ - :Pattern - :match -} diff --git a/core/registry.moon b/core/registry.moon index fca7e5d..edce9bd 100644 --- a/core/registry.moon +++ b/core/registry.moon @@ -1,14 +1,12 @@ +---- +-- `Tag` Registry. +-- +-- @classmod Registry + import Value from require 'core.value' import Result from require 'core.result' class Registry - new: () => - @map = {} - @io = {} - - @tick = 0 - @kr = Result value: Value.bool true - -- methods for Tag last: (index) => @last_map[index] @@ -24,8 +22,23 @@ class Registry active: -> assert Registry.active_registry, "no active Registry!" --- public methods + next_tag: => #@map + 1 + +--- methods +-- @section methods + --- create an instance. + new: => + @map = {} + @io = {} + + @tick = 0 + @kr = Result value: Value.bool true + + --- wrap a function with an eval-cycle. + -- + -- @tparam function fn + -- @treturn function `fn` wrapped with eval-cycle logic wrap_eval: (fn) => (...) -> @grab! @last_map, @map, @pending = @map, {}, {} @@ -47,6 +60,10 @@ class Registry @release! + --- wrap a function with a tick. + -- + -- @tparam function fn + -- @treturn function `fn` wrapped with tick logic wrap_tick: (fn) => (...) -> @grab! @tick += 1 @@ -66,8 +83,6 @@ class Registry assert @ == @@active_registry, "not the active registry!" @@active_registry, @prev = @prev, nil - next_tag: => #@map + 1 - class SimpleRegistry extends Registry new: => @cnt = 1 diff --git a/core/result.moon b/core/result.moon index 7fda8e5..9f0bce7 100644 --- a/core/result.moon +++ b/core/result.moon @@ -1,16 +1,26 @@ +---- +-- Result of evaluating an expression. +-- +-- Contains (all optional): +-- +-- - `@value`: a `Value` +-- - `@op`: an `Op` (to update) +-- - `@children`: a lsit of child `Results` (from subexpressions) +-- - `@side_inputs`: cached list of all `Inputs` affecting any `Op` in this +-- subtree +-- +-- `Result`s form a tree that controls execution order and message passing +-- between `Op`s. +-- +-- @classmod Result import base from require 'core.cycle' --- Result of evaluating an expression --- carries (all optional): --- - a Value --- - an Op (to update) --- - children (results of subexpressions that were evaluated) --- - cached list of all Dispatchers affecting all Ops in the subtree --- --- Results form a tree that controls execution order and message passing --- between Ops. class Result - -- params: table with optional keys op, value, children +--- methods +-- @section methods + + --- create a new Result. + -- @param params table with optional keys op, value, children. default: {} new: (params={}) => @value = params.value @op = params.op @@ -28,35 +38,38 @@ class Result if input.impure or not is_child[input.stream] @side_inputs[input.stream] = input + --- return whether this Result's value is const. is_const: => not next @side_inputs - -- asserts value-constness and returns the value + --- assert value-constness and returns the value. + -- @tparam[opt] string msg the error message to throw const: (msg) => assert not (next @side_inputs), msg or "eval-time const expected" @value - -- asserts a value exists and returns its type + --- assert this result has a value, returns its type. type: => assert @value, "Result with value expected" @value.type - -- create a value-copy of this result that has the same impulses but without - -- affecting the original's update logic + --- 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. make_ref: => with Result value: @value .side_inputs = @side_inputs - -- tick all IO instances that are effecting this (sub) tree - -- should be called once per frame on the root, right before tick + --- tick all IO instances that are effecting this (sub)tree. + -- should be called once per frame on the root, right before tick. tick_io: => for stream, input in pairs @side_inputs if input.__class == base.IOInput io = input! io\tick! - -- in depth-first order, tick all Ops who have dirty Stream inputs or impulses + --- in depth-first order, tick all Ops which have dirty Inputs. -- - -- short-circuits if there are no dirty Streams in the entire subtree + -- short-circuits if there are no dirty Inputs in the entire subtree tick: => any_dirty = false for stream, input in pairs @side_inputs @@ -64,15 +77,15 @@ class Result any_dirty = true break - -- early-out if no streams are dirty in this whole subtree + -- early-out if no Inputs are dirty in this whole subtree return unless any_dirty for child in *@children child\tick! if @op - -- we have to check self_dirty here, because streams from child - -- expressions might have changed + -- we have to check self_dirty here, because Inputs from children may + -- have become dirty due to \tick self_dirty = false for stream in @op\all_inputs! if stream\dirty! @@ -84,7 +97,6 @@ class Result @op\tick! --- static __tostring: => buf = "<result=#{@value}" buf ..= " #{@op}" if @op diff --git a/core/scope.moon b/core/scope.moon index 4ed95d1..c61a446 100644 --- a/core/scope.moon +++ b/core/scope.moon @@ -1,14 +1,36 @@ +---- +-- Mapping from `sym`s to `Result`s. +-- +-- @classmod Scope import Value from require 'core.value' import Result from require 'core.result' class Scope +--- methods +-- @section methods + + --- create an instance. + -- + -- @tparam[opt] Scope parent a parent this scope inherits definitions from + -- @tparam[opt] Scope dynamic_parent a parent scope that should be checked for + -- dynamic definitions new: (@parent, @dynamic_parent) => @values = {} + --- set a Lua value in the scope. + -- + -- wraps `val` in a `Value` and `Result` before calling `\set`. + -- + -- @tparam string key + -- @tparam any val set_raw: (key, val) => value = Value.wrap val, key @values[key] = Result :value + --- set a symbol to a `Result`. + -- + -- @tparam string key + -- @tparam Result val set: (key, val) => L\trace "setting #{key} = #{val} in #{@}" assert val.__class == Result, "expected #{key}=#{val} to be Result" @@ -20,6 +42,11 @@ class Scope parent or= @parent return parent and L\push parent\get, key + --- resolve a key in this Scope. + -- + -- @tparam string key the key to resolve + -- @tparam[opt] string prefix a prefix (for internal use with nested scopes) + -- @treturn ?Result the value of the definition that was found, or `nil` get: (key, prefix='') => L\debug "checking for #{key} in #{@}" if val = @values[key] @@ -35,11 +62,26 @@ class Scope assert child and child.value.type == 'scope', "#{start} is not a scope (looking for #{key})" child.value\unwrap!\get rest, "#{prefix}#{start}/" + --- copy definitions from another scope. + -- + -- copies all definitions from `other`. Does not copy inherited definitions. + -- + -- @tparam Scope other use: (other) => L\trace "using defs from #{other} in #{@}" for k, v in pairs other.values @values[k] = v +--- static functions +-- @section static + + --- converts a Lua table to a Scope. + -- + -- `tbl` may contain more tables (or `Scope`s). + -- Uses `Value``.wrap` on the values recursively. + -- + -- @tparam table tbl the table to convert + -- @treturn Scope from_table: (tbl) -> with Scope! for k, v in pairs tbl @@ -64,4 +106,6 @@ class Scope buf ..= ">" buf -:Scope +{ + :Scope +} diff --git a/core/tag.moon b/core/tag.moon index f38d36a..5e6de24 100644 --- a/core/tag.moon +++ b/core/tag.moon @@ -1,3 +1,12 @@ +---- +-- Identity provider for `Cell`s and `Action`s. +-- +-- Tags are one of: +-- - 'blank' (`[?]`, to be auto-assigned by the Copilot) +-- - literal (`[1]`) +-- - cloned (`[X.Y]`, obtained by cloning Y with parent X) +-- +-- @classmod Tag import Registry from require 'core.registry' local ClonedTag @@ -10,30 +19,47 @@ class DummyReg dummy = DummyReg! class Tag - -- obtain the value that was previously registered (using keep or replace) for - -- this tag +--- methods +-- @section methods + + --- obtain the registered value of the last eval-cycle. + -- + -- Obtain the value that was previously registered (using `\keep` or + -- `\replace`) for this tag on the last eval-cylce. + -- + -- @treturn ?any last: => if index = @index! Registry.active!\last index - -- assert that `expr` is the value that was previously registered for this - -- tag, and keep it for the current eval cycle. - -- fails for blank tags. + --- keep using a the value from the last eval-cycle. + -- + -- Assert that `expr` is the value that was previously registered for this + -- tag, and keep it for the current eval cycle. Fails for blank tags. + -- + -- @tparam any expr the value to register keep: (expr) => index = assert @index! assert expr == Registry.active!\last index Registry.active!\replace index, expr - -- register `expr` for this tag for the current eval cycle. - -- registers blank tags. + --- register `expr` for this tag for the current eval cycle. + -- + -- Will mark blank tags for auto-assignment at the end of the eval cycle. + -- + -- @tparam any expr the value to register replace: (expr) => if index = @index! Registry.active!\replace index, expr else Registry.active!\init @, expr - -- create a copy of this tag scoped to a `parent` tag. - -- registers blank tags. + --- create a copy of this tag scoped to a `parent` tag. + -- + -- Will mark blank tags for auto-assignment at the end of the eval cycle. + -- + -- @tparam Tag parent the parent tag + -- @treturn Tag the cloned tag clone: (parent) => -- ensure this tag is registered for the current eval cycle, -- even if it is blank and has no associated value @@ -45,6 +71,9 @@ class Tag assert parent, "need parent to clone!" ClonedTag @, parent + stringify: => if @value then "[#{@value}]" else '' + __tostring: => if @value then "#{@value}" else '?' + -- internal new: (@value) => @@ -56,13 +85,19 @@ class Tag assert not @value, "#{@} is not blank" @value = value --- static +--- static functions +-- @section static - @blank: -> Tag! - @parse: (num) => @ tonumber num + --- create a blank `Tag`. + -- + -- @treturn Tag + blank: -> Tag! - stringify: => if @value then "[#{@value}]" else '' - __tostring: => if @value then "#{@value}" else '?' + --- parse a `Tag` (for Lpeg parsing). + -- + -- @tparam string num the number-string + -- @treturn Tag + parse: (num) => @ tonumber num class ClonedTag extends Tag new: (@original, @parent) => diff --git a/core/value.moon b/core/value.moon index 26771ea..c6ed42d 100644 --- a/core/value.moon +++ b/core/value.moon @@ -1,3 +1,7 @@ +---- +-- `alive` Value(stream), implements the `AST` inteface. +-- +-- @classmod Value import Result from require 'core.result' import scope, base, registry from require 'core.cycle' @@ -7,26 +11,63 @@ ancestor = (klass) -> klass = klass.__parent klass --- ALV Type wrapper class Value - -- @type - type name. - -- builtin types: * literals: sym, num, bool - -- * scope, opdef, fndef, builtin - -- @value - Lua value - access through :unwrap() +--- methods +-- @section methods + + --- construct a new Value. + -- + -- @tparam string type the type name + -- @tparam any value the Lua value to be accessed through \unwrap + -- @tparam string raw the raw string that resulted in this value. Used by `parsing`. new: (@type, @value, @raw) => - @updated = nil + --- return whether this Value was changed in the current tick. + -- + -- @treturn bool dirty: => @updated == registry.Registry.active!.tick + --- update this Value. + -- + -- Marks this Value as dirty for the remainder of the current tick. set: (@value) => @updated = registry.Registry.active!.tick - -- unwrap to the Lua type - -- asserts @type == type, msg if given + --- unwrap to the Lua type. + -- + -- Asserts `@type == type` if `type` is given. + -- + -- @tparam[opt] string type the type to check for + -- @tparam[optchain] string msg message to throw if type don't match + -- @treturn @value unwrap: (type, msg) => assert type == @type, msg or "#{@} is not a #{type}" if type @value --- AST interface + --- alias for `\unwrap`. + __call: (...) => @unwrap ... + + --- compare two values. + -- + -- Compares two `Value`s by comparing their types and their Lua values. + __eq: (other) => other.type == @type and other.value == @value + + __tostring: => + value = if 'table' == (type @value) and rawget @value, '__base' then @value.__name else @value + "<#{@@__name} #{@type}: #{value}>" + +--- AST interface +-- +-- `Value` implements the `AST` interface. +-- @section ast + + --- evaluate this literal constant. + -- + -- Throws an error if `@type` is not a literal (`num`, `str` or `sym`). + -- Returns an eval-time const result for `num` and `str`. + -- Resolves `sym`s in `scope` and returns a reference to them. + -- + -- @tparam Scope scope the scope to evaluate in + -- @treturn Result the evaluation result eval: (scope) => switch @type when 'num', 'str' @@ -36,24 +77,33 @@ class Value else error "cannot evaluate #{@}" + --- quote this literal constant. + -- + -- @treturn Value self quote: => @ + --- stringify this literal constant. + -- + -- Throws an error if `@raw` is not set. + -- + -- @treturn string the exact string this Value was parsed from stringify: => assert @raw, "stringifying Value that wasn't parsed" + --- clone this literal constant. + -- + -- @treturn Value self clone: (prefix) => @ - -- in case of doubt: - -- clone: (prefix) => Value @type, @value, @raw --- static - __tostring: => - value = if 'table' == (type @value) and rawget @value, '__base' then @value.__name else @value - "<#{@@__name} #{@type}: #{value}>" - __call: (...) => @unwrap ... - __eq: (other) => other.type == @type and other.value == @value - - -- wrap a Lua type - @wrap: (val, name='(unknown)') -> +--- static functions +-- @section static + --- wrap a Lua value. + -- + -- Attempts to guess the type and wrap a Lua value. + -- + -- @tparam any val the value to wrap + -- @tparam[opt] string name the name of this value (for error logging) + wrap: (val, name='(unknown)') -> typ = switch type val when 'number' then 'num' when 'string' then 'str' @@ -83,16 +133,31 @@ class Value Value typ, val unescape = (str) -> str\gsub '\\([\'"\\])', '%1' - @parse: (type, sep) => + --- create a capture-function (for parsing with Lpeg). + -- + -- @tparam string type the type name (one of `num`, `sym` or `str`) + -- @tparam string sep the seperator char (only for `str`) + parse: (type, sep) => switch type when 'num' then (match) -> @ 'num', (tonumber match), match when 'sym' then (match) -> @ 'sym', match, match when 'str' then (match) -> @ 'str', (unescape match), sep .. match .. sep - @num: (num) -> Value 'num', num, tostring num - @str: (str) -> Value 'str', str, "'#{str}'" - @sym: (sym) -> Value 'sym', sym, sym - @bool: (bool) -> Value 'bool', bool, tostring bool + --- create a constant number. + -- @tparam number num the number + num: (num) -> Value 'num', num, tostring num + + --- create a constant string. + -- @tparam string str the string + str: (str) -> Value 'str', str, "'#{str}'" + + --- create a constant symbol. + -- @tparam string sym the symbol + sym: (sym) -> Value 'sym', sym, sym + + --- create a constant boolean. + -- @tparam boolean bool the boolean + bool: (bool) -> Value 'bool', bool, tostring bool { :Value diff --git a/core/version.moon b/core/version.moon index 029dd54..c33a8a1 100644 --- a/core/version.moon +++ b/core/version.moon @@ -1,5 +1,15 @@ +---- +-- `alive` source code version information +-- +-- @module version + +--- exports +-- @table exports +-- @tfield string tag the last versions git tag +-- @tfield string rev_short the short git revision hash +-- @tfield string rev_long the full git revision hash { tag: "v0.0" - rev_short: "4742960" - rev_long: "4742960c455251f306bf271d50f69724b8beba04" + rev_short: "891c138" + rev_long: "891c138c8a8a4c9269f3eb70f424b5c1ea3898b0" } diff --git a/docs/.gitignore b/docs/.gitignore index 2d19fc7..3014dfc 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1 +1,2 @@ *.html +copilot diff --git a/extra/git-version.sh b/extra/git-version.sh index e53ddbc..612bea4 100755 --- a/extra/git-version.sh +++ b/extra/git-version.sh @@ -5,6 +5,16 @@ REV_SHORT=`git rev-parse --short HEAD` REV_LONG=`git rev-parse HEAD` cat <<EOF +---- +-- \`alive\` source code version information +-- +-- @module version + +--- exports +-- @table exports +-- @tfield string tag the last versions git tag +-- @tfield string rev_short the short git revision hash +-- @tfield string rev_long the full git revision hash { tag: "${TAG}" rev_short: "${REV_SHORT}" diff --git a/lib/debug.moon b/lib/debug.moon index 1ce56cc..4a22e10 100644 --- a/lib/debug.moon +++ b/lib/debug.moon @@ -1,4 +1,4 @@ -import Op, ValueInput, EventInput, match from require 'core.base' +import Op, Input, match from require 'core.base' class out extends Op @doc: "(out [name-str?] value) - log value to the console" @@ -6,8 +6,8 @@ class out extends Op setup: (inputs) => { name, value } = match 'str? any', inputs super - name: name and ValueInput name - value: ValueInput value + name: name and Input.value name + value: Input.value value tick: => { :name, :value } = @unwrap_all! diff --git a/lib/logic.moon b/lib/logic.moon index adff5ee..7cf8695 100644 --- a/lib/logic.moon +++ b/lib/logic.moon @@ -1,4 +1,4 @@ -import Op, ValueInput, match from require 'core.base' +import Op, Input, match from require 'core.base' all_same = (first, list) -> for v in *list @@ -20,8 +20,8 @@ class ReduceOp extends Op setup: (inputs) => { first, rest } = match "any *any", inputs super - first: ValueInput first - rest: [ValueInput v for v in *rest] + first: Input.value first + rest: [Input.value v for v in *rest] tick: => { :first, :rest } = @unwrap_all! @@ -45,8 +45,8 @@ If the value types dont match, the result is an eval-time constant 'false'." super if same { - first: ValueInput first - rest: [ValueInput v for v in *rest] + first: Input.value first + rest: [Input.value v for v in *rest] } else {} @@ -73,7 +73,7 @@ class not_eq extends Op setup: (inputs) => assert #inputs > 1, "neq need at least two values" - super [ValueInput v for v in *inputs] + super [Input.value v for v in *inputs] tick: => if not @inputs[1] @@ -105,7 +105,7 @@ class not_ extends Op setup: (inputs) => { value } = match 'any', inputs - super value: ValueInput value + super value: Input.value value tick: => @out\set not tobool @inputs.value! @@ -115,7 +115,7 @@ class bool extends Op setup: (inputs) => { value } = match 'any', inputs - super value: ValueInput value + super value: Input.value value tick: => @out\set tobool @inputs\value! diff --git a/lib/math.moon b/lib/math.moon index b8b4361..02ba1e1 100644 --- a/lib/math.moon +++ b/lib/math.moon @@ -1,4 +1,4 @@ -import Op, Value, ValueInput, match from require 'core.base' +import Op, Value, Input, match from require 'core.base' unpack or= table.unpack class ReduceOp extends Op @@ -7,8 +7,8 @@ class ReduceOp extends Op setup: (inputs) => { first, rest } = match 'num *num', inputs super - first: ValueInput first - rest: [ValueInput v for v in *rest] + first: Input.value first + rest: [Input.value v for v in *rest] tick: => { :first, :rest } = @unwrap_all! @@ -52,8 +52,8 @@ evenodd_op = (name, remainder) -> setup: (inputs) => { val, div } = match 'num num?', inputs super - val: ValueInput val - div: ValueInput div or Value.num 2 + val: Input.value val + div: Input.value div or Value.num 2 tick: => { :val, :div } = @unwrap_all! @@ -72,7 +72,7 @@ func_op = (name, arity, func) -> setup: (inputs) => { params } = match '*num', inputs assert #params == arity, "#{@} needs exactly #{arity} parameters" if arity != '*' - super [ValueInput p for p in *params] + super [Input.value p for p in *params] tick: => @out\set func unpack @unwrap_all! diff --git a/lib/midi.moon b/lib/midi.moon index 01af84f..33fe936 100644 --- a/lib/midi.moon +++ b/lib/midi.moon @@ -1,4 +1,4 @@ -import Value, Op, ValueInput, IOInput, match from require 'core.base' +import Value, Op, Input, match from require 'core.base' import input, output, inout, apply_range from require 'lib.midi.core' class gate extends Op @@ -10,9 +10,9 @@ class gate extends Op setup: (inputs) => { port, note, chan } = match 'midi/port num num?', inputs super - port: IOInput port - note: ValueInput note - chan: ValueInput chan or Value.num -1 + port: Input.io port + note: Input.value note + chan: Input.value chan or Value.num -1 tick: => { :port, :note, :chan } = @inputs @@ -37,9 +37,9 @@ class trig extends Op setup: (inputs) => { port, note, chan } = match 'midi/port num num?', inputs super - port: IOInput port - note: ValueInput note - chan: ValueInput chan or Value.num -1 + port: Input.io port + note: Input.value note + chan: Input.value chan or Value.num -1 tick: => { :port, :note, :chan } = @inputs @@ -71,10 +71,10 @@ range can be one of: setup: (inputs) => { port, cc, chan, range } = match 'midi/port num num? any?', inputs super - port: IOInput port - cc: ValueInput cc - chan: ValueInput chan or Value.num -1 - range: ValueInput range or Value.str 'uni' + port: Input.io port + cc: Input.value cc + chan: Input.value chan or Value.num -1 + range: Input.value range or Value.str 'uni' if not @out\unwrap! @out\set apply_range @inputs.range, 0 diff --git a/lib/osc.moon b/lib/osc.moon index c459a66..101fb11 100644 --- a/lib/osc.moon +++ b/lib/osc.moon @@ -1,4 +1,4 @@ -import Op, ValueInput, EventInput, ColdInput, match from require 'core.base' +import Op, Input, match from require 'core.base' import pack from require 'osc' import dns, udp from require 'socket' @@ -12,8 +12,8 @@ class connect extends Op setup: (inputs) => { host, port } = match 'str num', inputs super - host: ValueInput host - port: ValueInput port + host: Input.value host + port: Input.value port tick: => { :host, :port } = @unwrap_all! @@ -30,9 +30,9 @@ sends a message only when val is dirty." setup: (inputs) => { socket, path, value } = match 'udp/socket str any', inputs super - socket: ColdInput socket - path: ColdInput path - value: EventInput value + socket: Input.cold socket + path: Input.cold path + value: Input.value value tick: => if @inputs.value\dirty! @@ -51,9 +51,9 @@ sends a whenever any parameter changes." setup: (inputs) => { socket, path, value } = match 'udp/socket str any', inputs super - socket: ValueInput socket - path: ValueInput path - value: ValueInput value + socket: Input.value socket + path: Input.value path + value: Input.value value tick: => { :socket, :path, :value } = @unwrap_all! diff --git a/lib/pilot.moon b/lib/pilot.moon index 5c680ed..f24270e 100644 --- a/lib/pilot.moon +++ b/lib/pilot.moon @@ -1,4 +1,4 @@ -import Op, EventInput, ValueInput, ColdInput, match from require 'core.base' +import Op, Input, match from require 'core.base' import udp from require 'socket' local conn @@ -27,8 +27,8 @@ class play extends Op { trig, args } = match 'bang *any', inputs assert #args < 6, "too many arguments!" super - trig: EventInput trig - args: [ColdInput a for a in *args] + trig: Input.event trig + args: [Input.cold a for a in *args] tick: => { :trig, :args } = @inputs @@ -42,10 +42,10 @@ class play_ extends Op { chan, octv, note, args } = match 'any any any *any', inputs assert #args < 3, "too many arguments!" super - chan: ColdInput chan - octv: ColdInput octv - note: EventInput note - args: [ColdInput a for a in *args] + chan: Input.cold chan + octv: Input.cold octv + note: Input.event note + args: [Input.cold a for a in *args] tick: => if @inputs.note\dirty! @@ -60,9 +60,9 @@ which is one of 'DIS', 'CHO', 'REV' or 'FEE'" setup: (inputs) => { which, a, b } = match 'str num num', inputs super { - ColdInput which - ValueInput a - ValueInput b + Input.cold which + Input.value a + Input.value b } tick: => diff --git a/lib/random.moon b/lib/random.moon index 88fdb89..7cabdf0 100644 --- a/lib/random.moon +++ b/lib/random.moon @@ -1,4 +1,4 @@ -import Op, Value, ValueInput, EventInput, match from require 'core.base' +import Op, Value, Input, match from require 'core.base' apply_range = (range, val) -> if range\type! == 'str' @@ -36,8 +36,8 @@ generates a random value in range on create and trigger. setup: (inputs) => { trig, range } = match 'bang? any?', inputs super - trig: trig and EventInput trig - range: ValueInput range or Value.str 'uni' + trig: trig and Input.event trig + range: Input.value range or Value.str 'uni' tick: => @gen! if @inputs.trig and @inputs.trig\dirty! @@ -59,8 +59,8 @@ each component is in range. setup: (inputs) => { trig, range } = match 'bang? any?', inputs super - trig: trig and EventInput trig - range: ValueInput range or Value.str 'uni' + trig: trig and Input.event trig + range: Input.value range or Value.str 'uni' tick: => @gen! if @inputs.trig and @inputs.trig\dirty! diff --git a/lib/sc.moon b/lib/sc.moon index bae252a..ebdcd3b 100644 --- a/lib/sc.moon +++ b/lib/sc.moon @@ -1,4 +1,4 @@ -import Op, EventInput, ColdInput, match from require 'core.base' +import Op, Input, match from require 'core.base' import pack from require 'osc' import dns, udp from require 'socket' @@ -15,10 +15,10 @@ class play extends Op assert val\type! == 'num', "only numbers are supported as control values" super - socket: ColdInput socket - synth: ColdInput synth - trig: EventInput trig - ctrls: [ColdInput v for v in *ctrls] + trig: Input.event trig + socket: Input.cold socket + synth: Input.cold synth + ctrls: [Input.cold v for v in *ctrls] tick: => if @inputs.trig\dirty! and @inputs.trig! diff --git a/lib/string.moon b/lib/string.moon index 2753381..06509f3 100644 --- a/lib/string.moon +++ b/lib/string.moon @@ -1,11 +1,11 @@ -import Op, ValueInput from require 'core.base' +import Op, Input from require 'core.base' class str extends Op @doc: "(str v1 [v2]...) (.. v1 [v2]...) - concatenate/stringify values" new: => super 'str' - setup: (inputs) => super [ValueInput v for v in *inputs] + setup: (inputs) => super [Input.value v for v in *inputs] tick: => @out\set table.concat [tostring v! for v in *@inputs] { diff --git a/lib/time.moon b/lib/time.moon index 5823d80..44cb998 100644 --- a/lib/time.moon +++ b/lib/time.moon @@ -1,4 +1,4 @@ -import Registry, Value, IO, Op, ValueInput, IOInput, match +import Registry, Value, IO, Op, Input, match from require 'core.base' import monotime from require 'system' @@ -29,7 +29,7 @@ fps defaults to 60 and has to be an eval-time constant" setup: (inputs) => { fps } = match 'num?', inputs - super fps: ValueInput fps or Value.num 60 + super fps: Input.value fps or Value.num 60 tick: => if @inputs.fps\dirty! @@ -52,9 +52,9 @@ wave selects the wave shape from the following: setup: (inputs, scope) => { clock, freq, wave } = match 'clock? num any?', inputs super - clock: IOInput clock or scope\get '*clock*' - freq: ValueInput freq - wave: ValueInput wave or default_wave + clock: Input.io clock or scope\get '*clock*' + freq: Input.value freq + wave: Input.value wave or default_wave tau = math.pi * 2 tick: => @@ -80,9 +80,9 @@ ramps from 0 to max (default same as ramp) once every period seconds." setup: (inputs, scope) => { clock, period, max } = match 'clock? num num?', inputs super - clock: IOInput clock or scope\get '*clock*' - period: ValueInput period - max: max and ValueInput max + clock: Input.io clock or scope\get '*clock*' + period: Input.value period + max: max and Input.value max tick: => clock_dirty = @inputs.clock\dirty! @@ -108,8 +108,8 @@ counts upwards by one every period seconds and returns the number of completed t setup: (inputs, scope) => { clock, period } = match 'clock? num', inputs super - clock: IOInput clock or scope\get '*clock*' - period: ValueInput period + clock: Input.io clock or scope\get '*clock*' + period: Input.value period tick: => if @inputs.clock\dirty! @@ -132,8 +132,8 @@ returns true once every period seconds." setup: (inputs, scope) => { clock, period } = match 'clock? num', inputs super - clock: IOInput clock or scope\get '*clock*' - period: ValueInput period + clock: Input.io clock or scope\get '*clock*' + period: Input.value period tick: => if @inputs.clock\dirty! diff --git a/lib/util.moon b/lib/util.moon index 65bab65..2a43dd2 100644 --- a/lib/util.moon +++ b/lib/util.moon @@ -1,4 +1,4 @@ -import Op, Value, ValueInput, EventInput, ColdInput, match +import Op, Value, Input, match from require 'core.base' all_same = (list) -> @@ -24,8 +24,8 @@ when i is a num, it is (floor)ed and the matching argument (starting from 0) is @out = Value typ super - i: ValueInput i - values: [ValueInput v for v in *values] + i: Input.value i + values: [Input.value v for v in *values] tick: => { :i, :values } = @inputs @@ -74,7 +74,7 @@ class edge extends Op setup: (inputs) => { value } = match 'bool', inputs - super value: EventInput + super value: Input.value tick: => now = @inputs.value! @@ -91,8 +91,8 @@ default defaults to zero." setup: (params) => { value, init } = match 'any any', inputs super - value: EventInput value - init: ColdInput init + value: Input.event value + init: Input.cold init @out = Value value\type! @out\set @inputs.init\unwrap! diff --git a/pilot.alv b/pilot.alv deleted file mode 100644 index 1623d46..0000000 --- a/pilot.alv +++ /dev/null @@ -1,46 +0,0 @@ -([1]import* math time string util) -([2]import osc envelope midi pilot) - -([3]defn make-lfo (type) - ([8]fn ([5]f) ([7]lfo ([6]* f 0.5) type))) - -([9]def sin-lfo ([10]make-lfo 'sin')) - -([11]defn send (name value) - ([13]osc/out '127.0.0.1' 9000 - ([12].. '/param/' name '/set') - value)) - -([28]def trigger ([48]edge ([47]switch ([45]tick .3) true false))) - -([29]pilot/play - trigger #(trigger) - ([30]ramp 8) #(ch) - 3 #(oct) - ([37]switch #(note) - ([46]tick .5) - 'a' 'c' 'e' 'b' 'c') - 4 4) - -([54]def f false t true) - -([67]def kick ([50]edge ([59]switch ([60]tick .15) t f f f t f f f))) - -([23]send 'radius' ([25] - ([24]envelope/ar ([14]midi/cc 0) ([15]midi/cc 1)) - kick)) - -([33]pilot/play kick - 12 2 ([55]switch ([56]tick 2) 'c' 'a' 'f') 2) -([51]pilot/play ([52]edge ([61]switch ([62]tick .15) f f t f f f f t)) 13 3 'c' 1) - -([31]defn cc-effect (name a b) - ([41]pilot/effect name ([32]midi/cc a 0 16) ([40]midi/cc b 0 16))) - -([42]cc-effect 'FEE' 16 17) -#([43]cc-effect 'REV' 18 19) -([44]cc-effect 'BIT' 20 21) - -([63]pilot/effect "REV" ([66]+ 1 ([64]* ([65]lfo .18) 2)) 2) - -([19]send 'offset' ([20]sin-lfo ([16]keep ([26]midi/cc 24 0 4)))) diff --git a/spec/core/cell_spec.moon b/spec/core/cell_spec.moon index 78861ab..8b6d68b 100644 --- a/spec/core/cell_spec.moon +++ b/spec/core/cell_spec.moon @@ -1,4 +1,5 @@ -import Cell, RootCell, Value, Scope, Registry, globals from require 'core' +import Cell, RootCell from require 'core.cell' +import Value, Scope, Registry, globals from require 'core' import Logger from require 'logger' Logger.init 'silent' @@ -20,14 +21,14 @@ describe 'Cell', -> describe 'RootCell', -> test 'head is always "do"', -> - cell = RootCell\parse {} + cell = Cell\parse_root {} assert.is.equal (Value.sym 'do'), cell\head! cell = RootCell nil, { hello_world, two_plus_two } assert.is.equal (Value.sym 'do'), cell\head! test 'tail is all children', -> - cell = RootCell\parse {} + cell = Cell\parse_root {} assert.is.same {}, cell\tail! cell = RootCell nil, { hello_world, two_plus_two } diff --git a/spec/core/pattern_spec.moon b/spec/core/pattern_spec.moon index 64dbef2..4825dcc 100644 --- a/spec/core/pattern_spec.moon +++ b/spec/core/pattern_spec.moon @@ -1,4 +1,4 @@ -import Pattern, match from require 'core.pattern' +import Pattern, match from require 'core.base.match' import Result, Value from require 'core' -- wrap in non-const result diff --git a/test.alv b/test.alv deleted file mode 100644 index 221b7ea..0000000 --- a/test.alv +++ /dev/null @@ -1,9 +0,0 @@ -([1]import* debug time) -([2]import midi) - -([3]trace "help") - -([4]def device ([5]midi/input "system:midi_capture_2")) - -([6]out 'name' ([7]midi/cc device 0)) -([8]out 'gate' ([9]midi/gate device 44)) |
