From 1a8debe87762072b3a63b769aa515ebf63b4c70d Mon Sep 17 00:00:00 2001 From: s-ol Date: Mon, 13 Apr 2020 20:40:35 +0200 Subject: move into proper Lua module (`alv`) --- Makefile | 12 +- alv/ast.moon | 40 ++++++ alv/base/builtin.moon | 96 ++++++++++++++ alv/base/fndef.moon | 26 ++++ alv/base/init.moon | 39 ++++++ alv/base/input.moon | 146 +++++++++++++++++++++ alv/base/match.moon | 302 +++++++++++++++++++++++++++++++++++++++++++ alv/base/op.moon | 168 ++++++++++++++++++++++++ alv/builtin.moon | 333 ++++++++++++++++++++++++++++++++++++++++++++++++ alv/cell.moon | 189 +++++++++++++++++++++++++++ alv/config.ld | 16 +++ alv/copilot.moon | 85 ++++++++++++ alv/cycle.moon | 26 ++++ alv/error.moon | 94 ++++++++++++++ alv/extensions.md | 221 ++++++++++++++++++++++++++++++++ alv/init.moon | 66 ++++++++++ alv/invoke.moon | 124 ++++++++++++++++++ alv/logger.moon | 82 ++++++++++++ alv/parsing.moon | 73 +++++++++++ alv/registry.moon | 145 +++++++++++++++++++++ alv/result.moon | 126 ++++++++++++++++++ alv/scope.moon | 118 +++++++++++++++++ alv/stream/base.moon | 68 ++++++++++ alv/stream/event.moon | 100 +++++++++++++++ alv/stream/init.moon | 18 +++ alv/stream/io.moon | 55 ++++++++ alv/stream/value.moon | 237 ++++++++++++++++++++++++++++++++++ alv/tag.moon | 122 ++++++++++++++++++ alv/version.moon | 17 +++ bin/alv | 23 ++++ bin/copilot.bat | 6 + bin/copilot.sh | 23 ++++ copilot.moon | 67 ---------- core/ast.moon | 40 ------ core/base/builtin.moon | 96 -------------- core/base/fndef.moon | 26 ---- core/base/init.moon | 39 ------ core/base/input.moon | 146 --------------------- core/base/match.moon | 302 ------------------------------------------- core/base/op.moon | 168 ------------------------ core/builtin.moon | 333 ------------------------------------------------ core/cell.moon | 189 --------------------------- core/config.ld | 16 --- core/cycle.moon | 26 ---- core/error.moon | 94 -------------- core/extensions.md | 221 -------------------------------- core/init.moon | 55 -------- core/invoke.moon | 124 ------------------ core/parsing.moon | 73 ----------- core/registry.moon | 145 --------------------- core/result.moon | 126 ------------------ core/scope.moon | 118 ----------------- core/stream/base.moon | 68 ---------- core/stream/event.moon | 100 --------------- core/stream/init.moon | 18 --- core/stream/io.moon | 55 -------- core/stream/value.moon | 237 ---------------------------------- core/tag.moon | 122 ------------------ core/version.moon | 17 --- extra/docs.moon | 4 +- extra/layout.moon | 2 +- init.moon | 34 ----- lib/logic.moon | 2 +- lib/math.moon | 2 +- lib/midi.moon | 2 +- lib/midi/core.moon | 2 +- lib/midi/launchctl.moon | 2 +- lib/osc.moon | 2 +- lib/pilot.moon | 2 +- lib/random.moon | 2 +- lib/sc.moon | 41 +++++- lib/string.moon | 2 +- lib/time.moon | 2 +- lib/util.moon | 2 +- logger.moon | 63 --------- 75 files changed, 3244 insertions(+), 3139 deletions(-) create mode 100644 alv/ast.moon create mode 100644 alv/base/builtin.moon create mode 100644 alv/base/fndef.moon create mode 100644 alv/base/init.moon create mode 100644 alv/base/input.moon create mode 100644 alv/base/match.moon create mode 100644 alv/base/op.moon create mode 100644 alv/builtin.moon create mode 100644 alv/cell.moon create mode 100644 alv/config.ld create mode 100644 alv/copilot.moon create mode 100644 alv/cycle.moon create mode 100644 alv/error.moon create mode 100644 alv/extensions.md create mode 100644 alv/init.moon create mode 100644 alv/invoke.moon create mode 100644 alv/logger.moon create mode 100644 alv/parsing.moon create mode 100644 alv/registry.moon create mode 100644 alv/result.moon create mode 100644 alv/scope.moon create mode 100644 alv/stream/base.moon create mode 100644 alv/stream/event.moon create mode 100644 alv/stream/init.moon create mode 100644 alv/stream/io.moon create mode 100644 alv/stream/value.moon create mode 100644 alv/tag.moon create mode 100644 alv/version.moon create mode 100755 bin/alv create mode 100644 bin/copilot.bat create mode 100755 bin/copilot.sh delete mode 100644 copilot.moon delete mode 100644 core/ast.moon delete mode 100644 core/base/builtin.moon delete mode 100644 core/base/fndef.moon delete mode 100644 core/base/init.moon delete mode 100644 core/base/input.moon delete mode 100644 core/base/match.moon delete mode 100644 core/base/op.moon delete mode 100644 core/builtin.moon delete mode 100644 core/cell.moon delete mode 100644 core/config.ld delete mode 100644 core/cycle.moon delete mode 100644 core/error.moon delete mode 100644 core/extensions.md delete mode 100644 core/init.moon delete mode 100644 core/invoke.moon delete mode 100644 core/parsing.moon delete mode 100644 core/registry.moon delete mode 100644 core/result.moon delete mode 100644 core/scope.moon delete mode 100644 core/stream/base.moon delete mode 100644 core/stream/event.moon delete mode 100644 core/stream/init.moon delete mode 100644 core/stream/io.moon delete mode 100644 core/stream/value.moon delete mode 100644 core/tag.moon delete mode 100644 core/version.moon delete mode 100644 init.moon delete mode 100644 logger.moon diff --git a/Makefile b/Makefile index eac8666..00c307b 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ MODULES=$(wildcard lib/*.moon) lib/midi/launchctl.moon MODREFS=$(MODULES:lib/%.moon=docs/reference/%.html) -CORE=$(wildcard core/*.moon core/**/*.moon) $(wildcard core/*.md) -DEPS=core/version.moon extra/docs.moon extra/layout.moon extra/dom.moon +CORE=$(wildcard alv/*.moon alv/**/*.moon) $(wildcard alv/*.md) +DEPS=alv/version.moon extra/docs.moon extra/layout.moon extra/dom.moon .PHONY: docs reference internals release clean @@ -10,8 +10,8 @@ reference: $(MODREFS) docs/reference/index.html internals: docs/internals/index.html release: - rm -f core/version.moon - extra/git-version.sh >core/version.moon + rm -f alv/version.moon + extra/git-version.sh >alv/version.moon docs/%.html: docs/%.md $(DEPS) @echo "building page $<" @@ -31,8 +31,8 @@ docs/ldoc.css: docs/style.css docs/ldoc.ltp: $(DEPS) moon extra/docs.moon $@ ldoc -docs/internals/index.html: core/config.ld docs/ldoc.ltp docs/ldoc.css $(CORE) - ldoc core +docs/internals/index.html: alv/config.ld docs/ldoc.ltp docs/ldoc.css $(CORE) + ldoc alv clean: rm -rf docs/reference diff --git a/alv/ast.moon b/alv/ast.moon new file mode 100644 index 0000000..a25d1cd --- /dev/null +++ b/alv/ast.moon @@ -0,0 +1,40 @@ +---- +-- AST Node Interface. +-- +-- implemented by `Value` and `Cell`. +-- +-- @classmod AST + +--- members +-- @section members + + --- evaluate this AST Node. + -- + -- Evaluate this node and return a `Result`. + -- + -- @function eval + -- @tparam Scope scope the scope to evaluate in + -- @treturn Result the evaluation result + + --- quote this AST Node, preserving its identity. + -- + --- Returns a mutable copy of this Node that shares its identity. + -- + -- @function quote + -- @treturn AST + + --- 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 expressions recursively). + -- + -- @function clone + -- @tparam Tag parent + -- @treturn AST + + --- stringify this AST Node. + -- + -- Should return the exact string this node was parsed from (if it was parsed). + -- + -- @function stringify + -- @treturn string the exact string this Node was parsed from diff --git a/alv/base/builtin.moon b/alv/base/builtin.moon new file mode 100644 index 0000000..fd36c69 --- /dev/null +++ b/alv/base/builtin.moon @@ -0,0 +1,96 @@ +---- +-- Builtin / Special Form evaluation Strategy (`builtin`). +-- +-- Responsible for quoting/evaluating subexpressions, instantiating and setting +-- up `Op`s, updating the current `Scope`, etc. +-- See `builtin` and `invoke` for examples. +-- +-- @classmod Builtin + +class Builtin +--- Builtin interface. +-- +-- methods that have to be implemented by `Builtin` implementations. +-- @section interface + + --- create a new instance. + -- + -- @tparam Cell cell the Cell to evaluate + -- @tparam Value head the (`AST:eval`d) `head` of the Cell to evaluate + new: (@cell, @head) => + @tag = @cell.tag + @tag\register @ + + --- 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: => + + --- setup or copy state from previous instance of same type. + -- + -- `prev` is only passed if Builtin types of prev and current expression match. + -- Otherwise, or when no previous expression exists, `nil` is passed. + -- + -- @tparam ?Builtin prev the previous Builtin instance + setup: (prev) => + + --- the `Cell` this Builtin was created for. + -- @tfield Cell cell + + --- the evaluated head of `cell`. + -- @tfield AST head + + --- the identity of `cell`. + -- @tfield Tag tag + +--- static functions +-- @section static + + --- create and setup a `Builtin` for a given tag, then evaluate it. + -- + -- Create a new instance using `tag` and `head` and call `setup` on it. + -- If a previous instance with the same `tag` exists and has the same `head`, + -- it pass it to `setup`. Register the `Builtin` with `tag`, evaluate it + -- and return the `Result`. + -- + -- @tparam Cell cell the `Cell` being evaluated + -- @tparam Scope scope the active scope + -- @tparam Value head the (`AST:eval`d) head of the `Cell` being evaluated + -- @treturn Result the result of evaluation + @eval_cell: (cell, scope, head) => + last = cell.tag\last! + compatible = last and (last.__class == @) and last.head == head + + L\trace if compatible + "reusing #{last} for #{cell.tag} <#{@__name} #{head}>" + else if last + "replacing #{last} with new #{cell.tag} <#{@__name} #{head}>" + else + "initializing #{cell.tag} <#{@__name} #{head}>" + + builtin = @ cell, head + if compatible + builtin\setup last + else + last\destroy! if last + builtin\setup nil + + builtin\eval scope, cell\tail! + + __tostring: => "<#{@@__name} #{@head}>" + __inherited: (cls) => cls.__base.__tostring = @__tostring + +{ + :Builtin +} diff --git a/alv/base/fndef.moon b/alv/base/fndef.moon new file mode 100644 index 0000000..b79ad85 --- /dev/null +++ b/alv/base/fndef.moon @@ -0,0 +1,26 @@ +---- +-- 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 +--- static functions +-- @section static + + --- create a new instance + -- + -- @classmethod + -- @tparam {Value,...} params (`AST:quote`d) naming the function parameters + -- @tparam AST body (`AST: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/alv/base/init.moon b/alv/base/init.moon new file mode 100644 index 0000000..c37d85c --- /dev/null +++ b/alv/base/init.moon @@ -0,0 +1,39 @@ +---- +-- Base definitions for extensions. +-- +-- This module exports the following classes and tables that extension modules +-- may need: +-- +-- @module base +-- @see Op +-- @see Builtin +-- @see FnDef +-- @see Input +-- @see base.match.val +-- @see base.match.evt +-- @see ValueStream +-- @see EventStream +-- @see IOStream +-- @see Result +-- @see Error + +import Op from require 'alv.base.op' +import Builtin from require 'alv.base.builtin' +import FnDef from require 'alv.base.fndef' +import Input from require 'alv.base.input' +import val, evt from require 'alv.base.match' +import ValueStream, EventStream, IOStream from require 'alv.stream' +import Result from require 'alv.result' +import Error from require 'alv.error' + +{ + :Op + :Builtin + :FnDef + :Input + :val, :evt + + -- redundant exports, to keep anything an extension might need in one import + :ValueStream, :EventStream, :IOStream + :Result, :Error +} diff --git a/alv/base/input.moon b/alv/base/input.moon new file mode 100644 index 0000000..19e55a4 --- /dev/null +++ b/alv/base/input.moon @@ -0,0 +1,146 @@ +---- +-- Update scheduling policy for `Op` arguments. +-- +-- @classmod Input +import ValueStream, EventStream, IOStream from require 'alv.stream' +import Result from require 'alv.result' + +inherits = (klass, frm) -> + assert klass, "cant find the ancestor of nil" + return true if klass == frm + while klass.__parent + return true if klass.__parent == frm + klass = klass.__parent + false + +match_parent = (inst, map) -> + klass = assert inst and inst.__class, "not an instance" + if key = map[klass] + return key + + while klass.__parent + if key = map[klass.__parent] + return key + + klass = klass.__parent + +local ColdInput, ValueInput, IOInput, mapping + +class Input +--- Input interface. +-- +-- Methods that have to be implemented by `Input` implementations. +-- @section interface + + --- create a new Input. + -- + -- @classmethod + -- @tparam Stream stream + new: (@stream) => + assert @stream, "nil passed to Input: #{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`. + -- + -- @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`:@{ValueStream:dirty|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 + + --- return the metatype name of this `Input` (optional). + metatype: => @stream.metatype + + --- the current value + -- + -- @tfield ValueStream stream + +--- members +-- @section members + + --- alias for `unwrap`. + __call: => @stream\unwrap! + + __tostring: => "#{@@__name}:#{@stream}" + __inherited: (cls) => + cls.__base.__call = @__call + cls.__base.__tostring = @__tostring + +--- static functions +-- @section static + + --- Create a `cold` `Input`. + -- + -- Never marked dirty. Use this for input streams that are only read when + -- another `Input` is dirty. + -- + -- @tparam Stream|Result value + @cold: (value) -> + if value.__class == Result + value = assert value.value, "Input from result without value!" + ColdInput value + + --- Create a `hot` `Input`. + -- + -- Behaviour depends on what kind of `Stream` `value` is: + -- + -- - `ValueStream`: Marked dirty for the eval-tick if old and new `ValueStream` differ. + -- - `EventStream` and `IOStream`: Marked dirty only if the current `EventStream` is dirty. + -- + -- This is the most common `Input` strategy. + -- + -- @tparam Stream|Result value + @hot: (value) -> + if value.__class == Result + value = assert value.value, "Input from result without value!" + + InputType = match_parent value, mapping + assert InputType, "Input from unknown value: #{value}" + InputType 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 = nil + dirty: => + return @dirty_setup if @dirty_setup != nil + @stream\dirty! + +class IOInput extends Input + io: true + +mapping = { + [ValueStream]: ValueInput + [EventStream]: Input + [IOStream]: IOInput +} + +{ + :Input +} diff --git a/alv/base/match.moon b/alv/base/match.moon new file mode 100644 index 0000000..62075c0 --- /dev/null +++ b/alv/base/match.moon @@ -0,0 +1,302 @@ +----- +--- Pattern capturing for Op argument parsing. +-- +-- There is only one basic buildings block for assembling patterns: +-- `Type`. It can match `ValueStream`s and `EventStream`s depending on its +-- metatype argument and can take an optional type name to match as an argument. +-- +-- In addition to this primitive, the following modifiers are available: +-- `Repeat`, `Sequence`, `Choice`, and `Optional`. They can be used directly, +-- but there is also a number of shorthands for assembling patterns quickly: +-- +-- - `val()` and `evt()`: Shorthands for `Type('value')` and `Type('event')` +-- - `val.num`: Shorthand for `Type('value', 'num')` +-- - `evt.str`: Shorthand for `Type('event', 'str')` +-- - `pat * 2`: Shorthand for `Repeat(pat, 1, 2)` (1-4 times `pat`) +-- - `pat * 0`: Shorthand for `Repeat(pat, 1, nil)` (1-* times `pat`) +-- - `pat ^ 2`: Shorthand for `Repeat(pat, 0, 2)` (0-4 times `pat`) +-- - `pat ^ 0`: Shorthand for `Repeat(pat, 0, nil)` (0-* times `pat`) +-- - `a + b + … + z`: Shorthand for `Sequence{ a, b, ..., z }` +-- - `a / b / … / z`: Shorthand for `Choice{ a, b, ..., z }` +-- - `-pat`: Shorthand for `Optional(pat)` +-- +-- To perform the actual matching, call the `:match` method on a pattern and +-- pass a sequence of `Result`s. The method will either return the captured +-- `Result`s (or a table structuring them) +-- +-- Any ambiguous pattern can be set to 'recall mode' by invoking it. +-- Recalling patterns will memorize the first Result they match, and +-- only match further Results of the same type. For example +-- +-- arg = (val.num / val.str)! +-- pattern = arg + arg +-- +-- ...will match either two numbers or two strings, but not one number and one +-- string. Recalling works on `Choice` and `Type` patterns. `Type` patterns +-- without a type (`val!` and `evt!`) always behave like this. +-- +-- On `Sequence` patterns, a special method `:named` exists. It takes a +-- sequence of keys that are used instead of integers when constructing the +-- capture table: +-- +-- pattern = (val.str + val.num):named('key', 'value') +-- pattern:match(...) +-- -- returns { {key='a', value=1}, {key='b', value=2}, ...} +-- +-- @module base.match +import Error from require 'alv.error' +import ValueStream, EventStream from require 'alv.stream' + +local Repeat, Sequence, Choice, Optional + +typestr = (result) -> + str = result\type! + str ..= '!' if result\metatype! == 'event' + str + +class Pattern + match: (seq) => + @reset! + num, cap = @capture seq, 1 + if num != #seq + args = table.concat [typestr arg for arg in *seq], ' ' + msg = "couldn't match arguments (#{args}) against pattern #{@}" + error Error 'argument', msg + cap + + remember: (key) => + return true unless @recall + + @recalled or= key + @recalled == key + + rep: (min, max) => Repeat @, min, max + + reset: => @recalled = nil + + __call: => @ + __mul: (num) => Repeat @, 1, if num != 0 then num + __pow: (num) => Repeat @, 0, if num != 0 then num + __add: (other) => Sequence { @, other } + __div: (other) => Choice { @, other } + __unm: => Optional @ + + __inherited: (cls) => + cls.__base.__call or= @__call + cls.__base.__mul or= @__mul + cls.__base.__pow or= @__pow + cls.__base.__add or= @__add + cls.__base.__div or= @__div + cls.__base.__unm or= @__unm + +--- Base Stream Pattern. +-- +-- When instantiated with `type`, only succeeds for `Stream`s whose value and +-- meta types match. +-- +-- Otherwise, matches Streams based only on `metatype` for the first match, but +-- using both afterwards (recall mode). +-- +-- @function Stream +-- @tparam string metatype "value" or "event" +-- @tparam ?string type type name +class Type extends Pattern + new: (@metatype, @type) => + @recall = not @type + + capture: (seq, i) => + return unless seq[i] + type, mt = seq[i]\type!, seq[i]\metatype! + return unless @metatype == mt + match = if @type then type == @type else @remember type + if match + 1, seq[i] + + __tostring: => + str = @type or @metatype + str ..= '!' if @metatype == 'event' + str + +--- Repeat a pattern. +-- +-- Matches a given `inner` pattern as many times as possible, within the given +-- minimum/maximum counts. Matching this pattern results in a sequence of the +-- individual captures produced by the inner pattern. +-- +-- @function Repeat +-- @tparam Pattern inner the original pattern +-- @tparam ?number min minimum amount of repetitions +-- @tparam ?number max maximum amount of repetitions (default infinite) +class Repeat extends Pattern + new: (@inner, @min, @max) => + + capture: (seq, i) => + take, all = 0, {} + while true + num, cap = @inner\capture seq, i+take + break unless num + + take += num + table.insert all, cap + + break if @max and take >= @max + + return if @min and take < @min + return if @max and take > @max + + take, all + + reset: => + @inner\reset! + + __call: => + @@ @inner!, @min, @max + + __tostring: => + min = @min or '0' + max = @max or '*' + "#{@inner}{#{min}-#{max}}" + +--- Match multiple patterns in order. +-- +-- Matches the inner patterns in order, only succeeds if all of them match. +-- Captures the individual captures produced by the inner patterns in a +-- sequence, or table with keys specified in `keys` or using the `:named(...)` +-- modifier. +-- +-- @function Sequence +-- @tparam {Pattern,...} elements the inner patterns +-- @tparam ?{string,...} keys the keys to use when capturing matches +class Sequence extends Pattern + new: (@elements, @keys) => + + capture: (seq, i) => + take, all = 0, {} + for key, elem in ipairs @elements + num, cap = elem\capture seq, i+take + return unless num + + take += num + key = @keys[key] if @keys + all[key] = cap + + take, all + + reset: => + for elem in *@elements + elem\reset! + + named: (...) => + @@ [e for e in *@elements], { ... } + + __call: => + @@ [e! for e in *@elements] + + __add: (other) => + elements = [e for e in *@elements] + table.insert elements, other + @@ elements + + __tostring: => + core = table.concat [tostring e for e in *@elements], ' ' + "(#{core})" + +--- Match one of multiple options. +-- +-- Matches using the first matching pattern in `elements` and returns its +-- captured value. Supports recalling the matched subpattern. +-- +-- @function Choice +-- @tparam {Pattern,...} elements the inner patterns +-- @tparam ?{string,...} keys the keys to use when capturing matches +class Choice extends Pattern + new: (@elements, @recall=false) => + + capture: (seq, i) => + for key, elem in ipairs @elements + num, cap = elem\capture seq, i + if num and @remember key + return num, cap + + reset: => + super! + for elem in *@elements + elem\reset! + + __call: => + @@ [e! for e in *@elements], true + + __div: (other) => + elements = [e for e in *@elements] + table.insert elements, other + @@ elements + + __tostring: => + core = table.concat [tostring e for e in *@elements], ' | ' + "(#{core})" + +--- Optionally match a pattern. +-- +-- Matches using the first matching pattern in `elements` and returns its +-- captured value. Supports recalling the matched subpattern. +-- +-- @function Optional +-- @tparam {Pattern,...} elements the inner patterns +-- @tparam ?{string,...} keys the keys to use when capturing matches +class Optional extends Pattern + new: (@inner) => + + capture: (seq, i) => + num, cap = @inner\capture seq, i + num or 0, cap + + reset: => + @inner\reset! + + __call: => + @@ @inner! + + __unm: => @ + + __tostring: => "#{@inner}?" + +--- `Value` shorthands. +-- +-- Call or index with a string to obtain a `Type` instance. +-- Call to obtain a wildcard pattern. +-- +-- val.str, val.num +-- val['vec3'], val('vec3') +-- val() +-- +-- @table val +val = setmetatable {}, { + __index: (key) => + with v = Type 'value', key + @[key] = v + + __call: (...) => Type 'value', ... +} + +--- `Event` shorthands. +-- +-- Call or index with a string to obtain an `Type` instance. +-- Call to obtain a wildcard pattern. +-- +-- evt.bang, evt.str, evt.num +-- evt['midi/message'], evt('midi/message') +-- evt() +-- +-- @table evt +evt = setmetatable {}, { + __index: (key) => + with v = Type 'event', key + @[key] = v + + __call: (...) => Type 'event', ... +} + +{ + :Type, :Repeat, :Sequence, :Choice, :Optional + :val, :evt +} diff --git a/alv/base/op.moon b/alv/base/op.moon new file mode 100644 index 0000000..b0a83dc --- /dev/null +++ b/alv/base/op.moon @@ -0,0 +1,168 @@ +---- +-- Persistent expression Operator. +-- +-- @classmod Op + +deepcopy = (val) -> + switch type val + when 'number', 'string', 'boolean', 'nil' + val + when 'table' + assert (not getmetatable {}), "state should only contain simple tables!" + {(deepcopy k), (deepcopy v) for k,v in pairs val} + else + error "state cannot contain values of type '#{type val}'" + +class Op +--- members +-- @section members + + do_yield = (table) -> + for k, v in pairs table + if v.__class + coroutine.yield v + else + do_yield v + --- yield all `Input`s from the (potentially nested) `inputs` table + -- + -- @treturn iterator iterator over `inputs` + all_inputs: => coroutine.wrap -> do_yield @inputs + + --- create a mutable copy of this Op. + -- + -- Used to wrap insulate eval-cycles from each other. The copy does not have + -- `inputs` set, since it is expected that this is (re)set in `setup`. + -- + -- @treturn Op + fork: => + out = if @out then @out\fork! + state = if @state then deepcopy @state + @@ out, state + + --- internal state of this Op. + -- + -- This may be any simple Lua value, including Lua tables, as long as it has + -- no metatables, multiple references/loops, userdata etc. + -- + -- @tfield table state + + --- `Stream` instance representing this Op's computed output value. + -- + -- Must be set to a `Stream` instance once `setup` finishes. Must not change + -- type, be removed or replaced outside of `new` and `setup`. If it is a + -- `ValueStream`, it should have a value assigned via `set` or the + -- constructor once `tick` is called the first time. If `out`'s value is not + -- initialized in `new` or `setup`, the implementation must make sure + -- `tick``(true)` is called at least on the first eval-cycle the Op goes + -- through, e.g. by using an `Input.hot` with a `ValueStream`. + -- + -- @tfield Stream out + + --- table containing `Input`s to this Op. + -- + -- 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. + -- + -- @tfield {Input,...} inputs + +--- Op interface. +-- +-- methods that have to be implemented by `Op` implementations. +-- @section interface + + --- construct a new instance. + -- + -- The optional parameters `out` and `state` are used by `fork` to duplicate + -- an instance. If the constructor is overriden, these parameters must be + -- forwarded to the superconstructor unchanged. + -- + -- @function new + -- @classmethod + -- @tparam ?Stream out `out` + -- @tparam ?table state `state` + + --- 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 {Result,...} inputs 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 may have + -- special behaviour immediately after `setup` that can cause them to become + -- dirty at eval-time. In this case, an eval-time tick is executed. You can + -- detect this using the `setup` parameter. + -- + -- `tick` is called after `setup`. `tick` is not called immediately after + -- `setup` if no `inputs` are dirty. Update `out` here. + -- + -- @tparam bool setup whether this is an eval-time tick + tick: => + + --- called when the Op is destroyed (optional). + destroy: => + +--- 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. + -- + -- @classmethod + -- @tparam ?Stream out `out` + -- @tparam ?table state `state` + new: (@out, @state) => + + 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 + --- 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: (inputs) => + old_inputs = @inputs + @inputs = inputs + do_setup old_inputs, @inputs + + do_unwrap = (value) -> + if value.__class + value\unwrap! + else + {k, do_unwrap v for k,v in pairs value} + --- `\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_unwrap @inputs + + __tostring: => "" + __inherited: (cls) => cls.__base.__tostring = @__tostring + +{ + :Op +} diff --git a/alv/builtin.moon b/alv/builtin.moon new file mode 100644 index 0000000..23412bf --- /dev/null +++ b/alv/builtin.moon @@ -0,0 +1,333 @@ +---- +-- Builtin `Builtin`s and `Op`s. +-- +-- Please see the [reference](../../reference/index.html#builtins) for +-- documentation. +-- +-- @module builtin +import Builtin, Op, FnDef, Input, val, evt from require 'alv.base' +import ValueStream, LiteralValue from require 'alv.stream.value' +import Result from require 'alv.result' +import Cell from require 'alv.cell' +import Scope from require 'alv.scope' +import Tag from require 'alv.tag' +import op_invoke from require 'alv.invoke' + +doc = ValueStream.meta + meta: + name: 'doc' + summary: "Print documentation in console." + examples: { '(doc sym)' } + description: "Print the documentation for `sym` to the console" + + value: class extends Builtin + format_meta = => + str = @summary + if @examples + for example in *@examples + str ..= '\n' .. example + if @description + str ..= '\n' .. @description\match '^\n*(.+)\n*$' + str + + eval: (scope, tail) => + assert #tail == 1, "'doc' takes exactly one parameter" + + result = L\push tail[1]\eval, scope + with Result children: { def } + meta = result.value.meta + L\print "(doc #{tail[1]}):\n#{format_meta meta}\n" + +def = ValueStream.meta + meta: + name: 'def' + summary: "Declare symbols in current scope." + examples: { '(def sym1 val-expr1 [sym2 val-expr2…])' } + description: " +Define the symbols `sym1`, `sym2`, … to resolve to the values of `val-expr1`, +`val-expr2`, …." + + value: class extends Builtin + eval: (scope, tail) => + L\trace "evaling #{@}" + assert #tail > 1, "'def' requires at least 2 arguments" + assert #tail % 2 == 0, "'def' requires an even number of arguments" + + children = L\push -> + return for i=1,#tail,2 + name, val_expr = tail[i], tail[i+1] + name = (name\quote scope)\unwrap 'sym' + + with val_expr\eval scope + scope\set name, \make_ref! + + Result :children + +use = ValueStream.meta + meta: + name: 'use' + summary: "Merge scopes into current scope." + examples: { '(use scope1 [scope2…])' } + description: " +Copy all symbol definitions from `scope1`, `scope2`, … to the current scope. +All arguments have to be evaltime constant." + + value: class extends Builtin + eval: (scope, tail) => + L\trace "evaling #{@}" + for child in *tail + result = L\push child\eval, scope + value = result\const! + scope\use value\unwrap 'scope', "'use' only works on scopes" + + Result! + +require_ = ValueStream.meta + meta: + name: 'require' + summary: "Load a module." + examples: { '(require name)' } + description: "Load a module and return its scope." + + value: class extends Builtin + eval: (scope, tail) => + L\trace "evaling #{@}" + assert #tail == 1, "'require' takes exactly one parameter" + + result = L\push tail[1]\eval, scope + name = result\const! + + L\trace @, "loading module #{name}" + scope = ValueStream.wrap require "lib.#{name\unwrap 'str'}" + Result :value + +import_ = ValueStream.meta + meta: + name: 'import' + summary: "Require and define modules." + examples: { '(import sym1 [sym2…])' } + description: " +Requires modules `sym1`, `sym2`, … and define them as `sym1`, `sym2`, … in the +current scope." + + value: class extends Builtin + eval: (scope, tail) => + L\trace "evaling #{@}" + assert #tail > 0, "'import' requires at least one arguments" + + for child in *tail + name = (child\quote scope)\unwrap 'sym' + value = ValueStream.wrap require "lib.#{name}" + scope\set name, Result :value -- (require "lib.#{name})\unwrap 'scope' + Result! + +import_star = ValueStream.meta + meta: + name: 'import*' + summary: "Require and use modules." + examples: { '(import* sym1 [sym2…])' } + description: " +Requires modules `sym1`, `sym2`, … and merges them into the current scope." + + value: class extends Builtin + eval: (scope, tail) => + L\trace "evaling #{@}" + assert #tail > 0, "'import' requires at least one arguments" + + + for child in *tail + name = (child\quote scope)\unwrap 'sym' + value = ValueStream.wrap require "lib.#{name}" + scope\use value\unwrap 'scope' -- (require "lib.#{name}")\unwrap 'scope' + + Result! + +fn = ValueStream.meta + meta: + name: 'fn' + summary: "Declare a function." + examples: { '(fn (p1 [p2…]) body-expr)' } + description: " +The symbols `p1`, `p2`, ... will resolve to the arguments passed when the +function is invoked." + + value: class extends Builtin + eval: (scope, tail) => + L\trace "evaling #{@}" + assert #tail == 2, "'fn' takes exactly two arguments" + { params, body } = tail + + assert params.__class == Cell, "'fn's first argument has to be an expression" + param_symbols = for param in *params.children + assert param.type == 'sym', "function parameter declaration has to be a symbol" + param\quote scope + + body = body\quote scope + Result value: with ValueStream.wrap FnDef param_symbols, body, scope + .meta = { + summary: "(user defined function)" + examples: { "(??? #{table.concat [p! for p in *param_symbols], ' '})" } + } + +defn = ValueStream.meta + meta: + name: 'defn' + summary: "Define a function." + examples: { '(defn name-sym (p1 [p2…]) body-expr)' } + description: " +Declare a function and define it as `name-sym` in the current scope. +The symbols `p1`, `p2`, ... will resolve to the arguments passed when the +function is invoked." + + value: class extends Builtin + eval: (scope, tail) => + L\trace "evaling #{@}" + assert #tail == 3, "'defn' takes exactly three arguments" + { name, params, body } = tail + + name = (name\quote scope)\unwrap 'sym' + assert params.__class == Cell, "'defn's second argument has to be an expression" + param_symbols = for param in *params.children + assert param.type == 'sym', "function parameter declaration has to be a symbol" + param\quote scope + + body = body\quote scope + + value = with ValueStream.wrap FnDef param_symbols, body, scope + .meta = + :name + summary: "(user defined function)" + examples: { "(#{name} #{table.concat [p! for p in *param_symbols], ' '})" } + + scope\set name, Result :value + Result! + +do_expr = ValueStream.meta + meta: + name: 'do_expr' + summary: "Evaluate multiple expressions in a new scope." + examples: { '(do expr1 [expr2…])' } + description: " +Evaluate `expr1`, `expr2`, … and return the value of the last expression." + + value: class extends Builtin + eval: (scope, tail) => + scope = Scope scope + Result children: [expr\eval scope for expr in *tail] + +if_ = ValueStream.meta + meta: + name: 'if' + summary: "Make an evaltime const choice." + examples: { '(if bool then-expr [else-expr])' } + description: " +`bool` has to be an evaltime constant. If it is truthy, this expression is equivalent +to `then-expr`, otherwise it is equivalent to `else-xpr` if given, or nil otherwise." + + value: class extends Builtin + eval: (scope, tail) => + L\trace "evaling #{@}" + assert #tail >= 2, "'if' needs at least two parameters" + assert #tail <= 3, "'if' needs at most three parameters" + + { xif, xthen, xelse } = tail + + xif = L\push xif\eval, scope + xif = xif\const!\unwrap! + + if xif + xthen\eval scope + elseif xelse + xelse\eval scope + +trace_ = ValueStream.meta + meta: + name: 'trace!' + summary: "Trace an expression's value at evaltime." + examples: { '(trace! expr)' } + + value: class extends Builtin + eval: (scope, tail) => + L\trace "evaling #{@}" + assert #tail == 1, "'trace!' takes exactly one parameter" + + with result = L\push tail[1]\eval, scope + L\print "trace! #{tail[1]\stringify!}: #{result.value}" + +trace = ValueStream.meta + meta: + name: 'trace' + summary: "Trace an expression's values at runtime." + examples: { '(trace expr)' } + + value: class extends Builtin + class traceOp extends Op + setup: (inputs) => + super + prefix: Input.cold inputs[1] + value: Input.hot inputs[2] + + tick: => + L\print "trace #{@inputs.prefix!}: #{@inputs.value.stream}" + + eval: (scope, tail) => + L\trace "evaling #{@}" + assert #tail == 1, "'trace!' takes exactly one parameter" + + tag = @tag\clone Tag.parse '-1' + inner = Cell tag, { + LiteralValue 'opdef', traceOp, 'trace' + ValueStream.str tostring tail[1] + tail[1] + } + inner\eval scope + +print = ValueStream.meta + meta: + name: 'print' + summary: "Print string values." + examples: { '(print str)' } + + value: class extends Op + setup: (inputs) => + value = (val.str / evt.str)\match inputs + super value: Input.hot value + + tick: => + if @inputs.value\metatype! == 'event' + for msg in *@inputs.value! + print msg + else + print @inputs.value! + +{ + :doc + :trace, 'trace!': trace_, :print + + :def, :use + require: require_ + import: import_ + 'import*': import_star + + true: ValueStream.meta + meta: + name: 'true' + summary: "The boolean constant `true`." + value: ValueStream.bool true + + false: ValueStream.meta + meta: + name: 'false' + summary: "The boolean constant `false`." + value: ValueStream.bool false + + bang: ValueStream.meta + meta: + name: 'bang' + summary: "A `bang` value-constant." + value: ValueStream 'bang', true + + :fn, :defn + 'do': do_expr + if: if_ +} diff --git a/alv/cell.moon b/alv/cell.moon new file mode 100644 index 0000000..f7e9773 --- /dev/null +++ b/alv/cell.moon @@ -0,0 +1,189 @@ +---- +-- 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 ValueStream from require 'alv.stream' +import Error from require 'alv.error' +import op_invoke, fn_invoke from require 'alv.invoke' +import Tag from require 'alv.tag' + +local RootCell + +class Cell +--- members +-- @section members + + 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 + + --- the parsed Tag. + -- + -- @tfield Tag tag + + --- sequence of child AST Nodes + -- + -- @tfield {AST,...} children + + --- optional sequence of whitespace segments. + -- + -- If set, `whitespace[i]` is the whitespace between `children[i]` and + -- `children[i+1]`, or the closing parenthesis of this Cell. `whitespace[0]` + -- is the space between the opening parenthesis and `children[1]`. + -- + -- @tfield ?{string,...} whitespace + +--- AST interface +-- +-- `Cell` implements the `AST` interface. +-- @section ast + + --- evaluate this Cell. + -- + -- `AST:eval`uates the head of the expression, and finds the appropriate + -- `Builtin` 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 `Builtin:eval_cell` on it. + -- + -- @tparam Scope scope the scope to evaluate in + -- @treturn Result the evaluation result + eval: (scope) => + head = assert @head!, Error 'syntax', "cannot evaluate empty expr" + head = (head\eval scope)\const! + Builtin = switch head.type + when 'opdef' + -- scope\get 'op-invoke' + op_invoke + when 'fndef' + -- scope\get 'fn-invoke' + fn_invoke + when 'builtin' + head\unwrap! + else + error Error 'type', "#{head} is not an opdef, fndef or builtin" + + Builtin\eval_cell @, scope, head + + --- 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 + quote: => + 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` + -- to `tag` and cloning all child expressions recursively. + -- + -- @tparam Tag parent + -- @treturn Cell + 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] + if depth == 0 + buf ..= '...' + else + for i, child in ipairs @children + buf ..= child\stringify if depth == -1 then -1 else depth - 1 + buf ..= if depth > 0 then ' ' else @white[i] + + if depth > 0 + buf = buf\sub 1, #buf - 1 + + tag = if depth == -1 then @tag\stringify! else '' + + '(' .. tag .. buf .. ')' + +--- static functions +-- @section static + + parse_args = (tag, parts) -> + if not parts + parts, tag = tag, nil + + children, white = {}, { [0]: parts[1] } + + for i = 2,#parts,2 + children[i/2] = parts[i] + white[i/2] = parts[i+1] + + tag, children, white + --- parse a Cell (for parsing with Lpeg). + -- + -- @tparam[opt] Tag tag + -- @tparam table parts + -- @treturn Cell + @parse: (...) -> + tag, children, white = parse_args ... + Cell tag, children, white + + --- 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: (...) -> + tag, children, white = parse_args (Tag.parse '0'), ... + RootCell tag, children, white + +-- @type RootCell +class RootCell extends Cell + head: => ValueStream.sym 'do' + tail: => @children + + stringify: => + buf = '' + buf ..= @white[0] + + for i, child in ipairs @children + buf ..= child\stringify! + buf ..= @white[i] + + buf + +{ + :Cell + :RootCell +} diff --git a/alv/config.ld b/alv/config.ld new file mode 100644 index 0000000..01160f8 --- /dev/null +++ b/alv/config.ld @@ -0,0 +1,16 @@ +project = 'alive internals' +title = 'developer docs' + +description = "`alive` developer documentation" +full_description = [[This section documents the *implementation* of the alive +language and copilot. It is relevant to everyone who is looking to modify, +improve or extend alive with new modules, language or interpreter features. + +If you are looking for the language reference for users, head over to the +[reference](../reference/index.html) section of the documentation.]] + +format = 'discount' +style = 'docs' +template = 'docs' +topics={'alv/extensions.md'} +dir = 'docs/internals' diff --git a/alv/copilot.moon b/alv/copilot.moon new file mode 100644 index 0000000..478cbae --- /dev/null +++ b/alv/copilot.moon @@ -0,0 +1,85 @@ +---- +-- File watcher and CLI entrypoint. +-- +-- @classmod Copilot +lfs = require 'lfs' +import Scope from require 'alv.scope' +import Registry from require 'alv.registry' +import Error from require 'alv.error' +import program from require 'alv.parsing' +globals = Scope.from_table require 'alv.builtin' + +slurp = (file) -> + file = io.open file, 'r' + with file\read '*all' + file\close! + +spit = (file, str) -> + file = io.open file, 'w' + file\write str + file\close! + +class Copilot +--- static functions +-- @section static + + --- create a new Copilot. + -- @classmethod + -- @tparam string file name/path of the alive file to watch and execute + new: (@file) => + @registry = Registry! + + @last_modification = 0 + + mode = lfs.attributes @file, 'mode' + if mode != 'file' + error "not a file: #{@file}" + +--- members +-- @section members + + --- poll for changes and tick. + tick: => + @poll! + + if @root + @registry\begin_tick! + ok, error = Error.try "updating", -> + @root\tick_io! + @root\tick! + if not ok + print error + @registry\end_tick! + + eval: => + @registry\begin_eval! + ok, ast = Error.try "parsing '#{@file}'", program\match, slurp @file + if not (ok and ast) + print ast or Error 'syntax', "failed to parse" + @registry\rollback_eval! + return + + scope = Scope globals + ok, root = Error.try "evaluating '#{@file}'", ast\eval, scope, @registry + if not ok + print root + @registry\rollback_eval! + return + + @registry\end_eval! + @root = root + spit @file, ast\stringify! + + poll: => + { :mode, :modification } = (lfs.attributes @file) or {} + if mode != 'file' + return + + if @last_modification < modification + L\log "#{@file} changed at #{modification}" + @eval! + @last_modification = os.time! + +{ + :Copilot +} diff --git a/alv/cycle.moon b/alv/cycle.moon new file mode 100644 index 0000000..f5b7e15 --- /dev/null +++ b/alv/cycle.moon @@ -0,0 +1,26 @@ +-- late-resolve cyclic dependencies +-- +-- this module provides a proxy for resolving values from modules which cannot +-- be loaded due to cyclic dependencies. Instead of +-- +-- import Something from require 'alv.somewhere' +-- Something ... +-- +-- use +-- +-- import somewhere from require 'alv.cycle' +-- somewhere.Something ... +-- +-- Make sure cycle:load() is called before you access or dereference +-- `somewhere`. + +load = => + for name, module in pairs @ + for k, v in pairs require "alv.#{name}" + module[k] = v + +setmetatable {}, __index: (key) => + return load if key == 'load' + + with v = {} + rawset @, key, v diff --git a/alv/error.moon b/alv/error.moon new file mode 100644 index 0000000..e72cc5f --- /dev/null +++ b/alv/error.moon @@ -0,0 +1,94 @@ +---- +-- Language error and traceback. +-- +-- @classmod Error + +unpack or= table.unpack + +class Error +--- members +-- @section members + + --- append a traceback frame. + -- + -- `where` should denote where the Error occured and fit grammatically to + -- complete the sentence `"{error} occured while {where}"` + -- + -- @tparam string where + add_frame: (where) => @trace ..= "\n while #{where}" + + __tostring: => + str = "#{@kind} error: #{@message}" + if @detail + str ..= "\n#{@detail}" + if @trace + str ..= @trace + str + +--- static functions +-- @section static + + --- create a new Error. + -- + -- `kind` should be one of: + -- + -- - `'reference'`: error concerning symbol resolution + -- - `'argument'`: error concerning Op argument matching + -- - `'implementation'`: error in the Lua/MoonScript implementation of alive. + -- Should not occur in normal operation, and constitutes a bug. + -- + -- @classmethod + -- @tparam string kind + -- @tparam string message + -- @tparam ?string detail + new: (@kind, @message, @detail) => + @trace = '' + + handler = (err) -> + if err.__class == Error + err + else + Error 'implementation', err, debug.traceback "Lua error below:", 2 + --- Wrap function errors in a traceback frame. + -- + -- Execute `fn(...)`, and turn any error thrown as a result into an + -- `Error` instance, before re-throwing it. + -- + -- When `Error` instances are caught, `frame` is added to the traceback. + -- All other error values are turned into `'implementation'` Errors. + -- + -- @tparam string frame + -- @tparam function fn + @wrap: (frame, fn, ...) -> + results = { xpcall fn, handler, ... } + ok = table.remove results, 1 + if ok + unpack results + else + error with results[1] + \add_frame frame + + --- Capture and wrap function errors in traceback frame. + -- + -- Execute `fn(...)`, and turn any error thrown as a result into an + -- `Error` instance, before re-throwing it. + -- + -- When `Error` instances are caught, `frame` is added to the traceback. + -- All other error values are turned into `'implementation'` Errors. + -- + -- @tparam string frame + -- @tparam function fn + -- @treturn boolean `ok` true if exeuction suceeded without errors + -- @treturn Error|any `error_or_results` the `Error` instance or results + @try: (frame, fn, ...) -> + results = { xpcall fn, handler, ... } + ok = table.remove results, 1 + if ok + ok, unpack results + else + ok, with results[1] + \add_frame frame + +{ + :Error +} diff --git a/alv/extensions.md b/alv/extensions.md new file mode 100644 index 0000000..82a4d10 --- /dev/null +++ b/alv/extensions.md @@ -0,0 +1,221 @@ +# writing `alive` extensions + +Extensions for `alive` are implemented in [Lua][lua] or [MoonScript][moonscript] +(which runs as Lua). When an `alive` module is [`(require)`][builtins-req]d, +alive looks for a Lua module `lib.[module]`. You can simply add a new file with +extension `.lua` or `.moon` in the `lib` directory of your alive installation or +somewhere else in your `LUA_PATH`. + +To write extensions, a number of classes and utilities are required. All of +these are exported in the `base` module. + +## documentation metadata +The lua module should return a `Scope` or a table that will be converted using +`Scope.from_table`. All exports should be documented using `ValueStream.meta`, +which attaches a `meta` table to the value that is used for error messages, +documentation generation and [`(doc)`][builtins-doc]. + + import ValueStream from require 'alv.base' + + two = ValueStream.meta + meta: + name: 'two' + summary: "the number two" + value: 2 + + { + :two + } + +In the `meta` table `summary` is the only required key, but all of the +information that applies should be provided. + +- `name`: the name of this export (for error reporting). +- `summary`: a one-line plain-text description of this entry. Should be + capitalized and end with a period. +- `examples`: a table of strings, each of which is a short one-line code + example illustrating the argument names for an Op. +- `description`: a longer markdown-formatted description of the functionality + of this entry. + +## defining `Op`s +Most extensions will want to define a number of *Op*s to be used by the user. +They are implemented by deriving from the `Op` class and implementing at least +the `Op:setup` and `Op:tick` methods. + + import ValueStream, Op, Input, evt from require 'alv.base' + + total_sum = ValueStream.meta + meta: + name: 'total-sum' + summary: "Keep a total of incoming numbers." + examples: { '(total-sum num!)' } + description: "Keep a total sum of incoming number events, extension-style." + + value: class extends Op + new: (...) => + super ... + @state or= { total: 0 } + @out or= ValueStream 'num', @state.total + + setup: (inputs, scope) => + num = evt.num\match inputs + super num: Inputs.hot num + + tick: => + @state.total += @inputs.num! + @out\set @state.total + + { + 'total-sum': total_sum + } + +### Op:setup +`Op:setup` is called once every *eval cycle* to parse the Op's arguments, check +their types, choose the updating behaviour and define the output type. + +The arguments to `:setup` are a list of inputs (each is a `Result` instance), +and the `Scope` the evaluation happened in. Ops generally shouldn't use the +scope, but might look up 'magic' dynamic symbols like `\*clock\*`. + +#### argument parsing +Arguments should be parsed using `base.match`. The two exports `base.match.val` +and `base.match.evt` are used to build complex patterns that can parse and +validate the Op arguments into complex structures (see the module documentation +for more information). + + import val, evt from require 'alv.base' + + pattern = evt.bang + val.str + val.num*3 + -evt! + { trig, str, numbers, optional } = pattern\match inputs + +This example matches first an `EventStream` of type `bang`, then a `ValueStream` +of type `str`, followed by one, two or three `num`-values and finally an +optional argument `EventStream` of any type. `:match` will throw an error if it +couldn't (fully) match the arguments and otherwise return a structured mapping +of the inputs. + +If there are more complex dependencies between arguments, it is recommended to +do as much of the parsing as possible using the `base.match` and then continue +manually. For invalid or missing arguments, `Error` instances should be thrown +using `error` or `assert`. + +#### input setup +There are two types of inputs: `Input.hot` and `Input.cold`: + +*Cold* inputs do not cause the Op to update when changes to the input stream +are made. They are useful to 'ignore' changes to inputs which are only relevant +when another input changed value. Imagine for example a `send-value-when` Op, +which sends a value only when a `bang!` input is live. This Op doesn't have to +update when the value changes, it's enough to update only when the trigger +input changes and simply read the value in that moment. + +*Hot* inputs on the other hand mark the input stream as a dependency for the +Op. Depending on the type of `Stream`, the semantics are a little different: + +- For `ValueStream`s, the Op updates whenever the current value changes. When + an input stream is swapped out for another one at evaltime, but their values + are momentarily equal, the input is not considered dirty. +- For `EventStream`s and `IOStream`s, the Op updates whenever the stream is + dirty. There is no special handling when the stream is swapped out at + evaltime. + +All `Result`s from the `inputs` argument that are taken into consideration +should be wrapped in an `Input` instance using either `Input.hot` or +`Input.cold`, and need to be passed to the `Op:setup` super implementation. +To illustrate with the `send-value-when` example: + + setup: (inputs, scope) => + { trig, value } = match 'bang! any', inputs + + super + trig: Inputs.hot trig + value: Inputs.cold value + +`Op:setup` takes a table that can have any (even nested) shape you want, as +long as all 'leaf values' are `Input` instances. The following are both valid: + + super { (Inputs.hot trig), (Inputs.cold value) } + + super + trigger: Inputs.hot trig + values: { (Inputs.cold a), (Inputs.cold b), (Inputs.cold c) } + +#### output setup +When `Op:setup` finishes, `@out` has to be set to a `Stream` instance. The +instance can be created in `Op:setup`, or by overriding the constructor and +delegating to the original one using `super`. In general setting it in the +constructor is preferred, and it is only moved to `Op:setup` if the output +type depends on the arguments received. + +There are three types of `Stream`s that can be created: + +- `ValueStream`s track *continuous values*. They can only have one value per + tick, and downstream Ops will not update when a *ValueStream* has been set + to the same value it already had. They are updated using `ValueStream:set`. +- `EventStream`s transmit *momentary events*. They can transmit multiple events + in a single tick. `EventStream`s do not keep a value set on the last tick on + the next tick. They are updated using `EventStream:add`. +- `IOStream`s are like `EventStream`s, but their `IOStream:tick` method is + polled by the event loop at the start of every tick. This gives them a chance + to effectively create changes 'out of thin air' and kickstart the execution + of the dataflow engine. All *runtime* execution is due to an `IOStream` + becoming dirty somewhere. See the section on implementing `IOStream`s below + for more information. + +### Op:tick +`Op:tick` is called whenever any of the inputs are *dirty*. This is where the +Op's main logic will go. Generally here it should be checked which input(s) +changed, and then internal state and the output value may be updated. + +## defining `Builtin`s +Builtins are more powerful than Ops, because they control whether, which and +how their arguments are evaluated. They roughly correspond to *macros* in Lisps. +There is less of a concrete guideline for implementing Builtins because there +are a lot more options, and it really depends a lot on what the Builtin should +achieve. Nevertheless, a good starting point is to read the `Builtin` class +documentation, take a look at `Builtin`s in `alv/builtin.moon` and get +familiar with the relevant internal interfaces (especially `AST`, `Result`, and +`Scope`). + +## defining `IOStream`s +`IOStream`s are `EventStream`s that can 'magically' create events out of +nothing. They are the source of all processing in alive. Whenever you want to +bring events into alive from an external protocol or application, an IOStream +will be necessary. + +To implement a custom IOStream, create it as a class that inherits from the +`IOStream` base and implement the constructor and `IOStream:tick`: + + import IOStream from require 'alv.base' + + class UnreliableStream extends IOStream + new: => super 'bang' + + tick: => + if math.random! < 0.1 + @add true + +In the constructor, you should call the super-constructor `EventStream.new` to +set the event type. Often this will be a custom event that is only used inside +your extension (such as e.g. the `midi/port` type in the [midi][modules-midi] +module), but it can also be a primitive type like `'num'` in this example. In +`:tick`, your IOStream is given a chance to communicate with the external world +and create any resulting events. The example stream above randomly sends bang +events out, with a 10% chance each 'tick' of the system. Note that there is no +guarantee about when or how often ticks occur, so you really shouldn't rely on +them this way in a real extension. + +### using `IOStream`s +There's a couple of ways IOStreams can be used and exposed to the user of your +extension. You can either expose an instance of your IOStream directly +(documented using `ValueStream.meta`), or offer an Op that creates and returns +an instance in `Op.out` - that way the IOStream can be created only on demand +and take parameters. It is also possible to not exepose the IOStream at all, +and rather pass it as a hardcoded input into an Op's `Op.inputs`. + +[lua]: https://www.lua.org/ +[moonscript]: http://moonscript.org/ +[builtins-req]: ../../reference/index.html#require +[builtins-doc]: ../../reference/index.html#doc +[modules-midi]: ../../reference/midi.html diff --git a/alv/init.moon b/alv/init.moon new file mode 100644 index 0000000..d86cd1f --- /dev/null +++ b/alv/init.moon @@ -0,0 +1,66 @@ +---- +-- `alive` public API. +-- +-- @module init +if _VERSION == 'Lua 5.1' + export assert + assert = (a, msg, ...) -> + if not a + error msg + a, msg, ... + +import Logger from require 'alv.logger' +import ValueStream, EventStream, IOStream from require 'alv.stream' +import Result from require 'alv.result' +import Scope from require 'alv.scope' +import Error from require 'alv.error' +import Registry, SimpleRegistry from require 'alv.registry' +import Tag from require 'alv.tag' + +import Cell, RootCell from require 'alv.cell' +import program from require 'alv.parsing' + +with require 'alv.cycle' + \load! + +import Copilot from require 'alv.copilot' +globals = Scope.from_table require 'alv.builtin' + +--- exports +-- @table exports +-- @tfield ValueStream ValueStream +-- @tfield EventStream EventStream +-- @tfield IOStream IOStream +-- @tfield Result Result +-- @tfield Cell Cell +-- @tfield RootCell RootCell +-- @tfield Scope Scope +-- @tfield Error Error +-- @tfield Registry Registry +-- @tfield Tag Tag +-- @tfield Copilot Copilot +-- @tfield Logger Logger +-- @tfield Scope globals global definitons +-- @tfield parse function to turn a `string` into a root `Cell` +{ + :ValueStream, :EventStream, :IOStream + :Cell, :RootCell + :Result, :Scope, :Error + + :Registry, :SimpleRegistry, :Tag + + :globals + + :Copilot, :Logger + + parse: (str) -> + assert (program\match str), Error 'syntax', "failed to parse" + + eval: (str, inject) -> + scope = Scope nil, globals + scope\use inject if inject + + ast = assert (program\match str), "failed to parse" + result = ast\eval scope + result\const! +} diff --git a/alv/invoke.moon b/alv/invoke.moon new file mode 100644 index 0000000..6afa57a --- /dev/null +++ b/alv/invoke.moon @@ -0,0 +1,124 @@ +---- +-- Builtins for invoking `Op`s and `FnDef`s. +-- +-- @module invoke +import Result from require 'alv.result' +import Builtin from require 'alv.base' +import Scope from require 'alv.scope' +import Error from require 'alv.error' + +get_name = (value, raw) -> + meta = if value.meta then value.meta.name + locl = if raw and raw.type == 'sym' then raw! + + if locl + if meta and meta != locl + "'#{meta}' (local '#{locl}')" + else + "'#{locl}'" + else if meta + "'#{meta}'" + else + "(unnamed)" + +--- `Builtin` implementation that invokes an `Op`. +-- +-- @type op_invoke +class op_invoke extends Builtin + --- `Builtin:setup` implementation. + -- + -- `Op:fork`s the `prev`'s `Op` instance if given. Creates a new instance + -- otherwise. + setup: (prev) => + if prev + @op = prev.op\fork! + else + def = @head\unwrap 'opdef', "cant op-invoke #{@head}" + @op = def! + + --- `Builtin:destroy` implementation. + -- + -- calls `op`:@{Op:destroy|destroy}. + destroy: => @op\destroy! + + --- evaluate an `Op` invocation. + -- + -- `AST:eval`s the tail, and passes the result to `op`:@{Op:setup|setup}. Then + -- checks if any of `op`:@{Op:all_inputs|all_inputs} are @{Input:dirty|dirty}, + -- and if so, calls `op`:@{Op:tick|tick}. + -- + -- The `Result` contains `op`, `Op.value` and all the `Result`s from the tail. + -- + -- @tparam Scope scope the active scope + -- @tparam {AST,...} tail the arguments to this expression + -- @treturn Result + eval: (scope, tail) => + children = [L\push expr\eval, scope for expr in *tail] + + frame = "invoking op #{get_name @head, @cell\head!} at [#{@tag}]" + Error.wrap frame, @op\setup, [result for result in *children], scope + + any_dirty = false + for input in @op\all_inputs! + if input\dirty! + any_dirty = true + break + + if any_dirty + @op\tick true + + for input in @op\all_inputs! + input\finish_setup! + + Result :children, value: @op.out, op: @op + + --- The `Op` instance. + -- + -- @tfield Op op + +--- `Builtin` implementation that invokes a `FnDef`. +-- +-- @type fn_invoke +class fn_invoke extends Builtin + --- evaluate a user-function invocation. + -- + -- Creates a new `Scope` that inherits from `FnDef.scope` and has + -- `outer_scope` as an additional parent for dynamic symbol resolution. + -- Then `AST:eval`s the tail in `outer_scope`, and defines the results to the + -- names in `FnDef.params` in the newly created scope. Lastly, `AST:clone`s + -- `FnDef.body` with the prefix `Builtin.tag`, and `AST:eval`s it in the newly + -- created `Scope`. + -- + -- The `Result` contains the `Stream` from the cloned AST, and its children + -- are all the `Result`s from evaluating the tail as well as the cloned + -- `AST`s. + -- + -- @tparam Scope outer_scope the active scope + -- @tparam {AST,...} tail the arguments to this expression + -- @treturn Result the result of this evaluation + eval: (outer_scope, tail) => + name = get_name @head, @cell\head! + frame = "invoking function #{name} at [#{@tag}]" + + { :params, :body, :scope } = @head\unwrap 'fndef', "cant fn-invoke #{@head}" + if #params != #tail + err = Error 'argument', "expected #{#params} arguments, found #{#tail}" + err\add_frame frame + error err + + fn_scope = Scope scope, outer_scope + + children = for i=1,#params + name = params[i]\unwrap 'sym' + with L\push tail[i]\eval, outer_scope + fn_scope\set name, \make_ref! + + clone = body\clone @tag + result = Error.wrap frame, clone\eval, fn_scope + + table.insert children, result + Result :children, value: result.value + +{ + :op_invoke, :fn_invoke +} diff --git a/alv/logger.moon b/alv/logger.moon new file mode 100644 index 0000000..973e8b5 --- /dev/null +++ b/alv/logger.moon @@ -0,0 +1,82 @@ +---- +-- Logger implementation. +-- +-- @classmod Logger +unpack or= table.unpack + +export L +L or= setmetatable {}, __index: => -> + +class Logger + levels = { + debug: 0 + trace: 1 + log: 2 + warn: 3 + error: 4 + print: 5 + silent: 6 + } + +--- members +-- @section members + + --- push an indentation level and execute a function in it. + -- @tparam function fn the function to execute + -- @param ... parameters to `fn` + push: (fn, ...) => + last = @prefix + @prefix ..= ' ' + + res = { xpcall fn, debug.traceback, ... } + + @prefix = last + + if ok = table.remove res, 1 + unpack res + else + error unpack res + +--- static functions +-- @section static + + --- create a new Logger. + -- @classmethod + -- @tparam string level the log-level to log at. + new: (level='log') => + @level = levels[level] or level + @prefix = '' + + for name, level in pairs levels + @[name] = (first, ...) => + return unless @level <= level + + where = debug.traceback '', 2 + if level == levels.error or @level == levels.debug + print @prefix .. first, ... + print where + else + print @prefix .. first, ... + + if level == levels.print + @push = (fn, ...) => fn ... + + --- set up the global Logger singleton. + -- + -- The available log-levels are: + -- + -- - `'debug'` + -- - `'trace'` + -- - `'log'` (the default) + -- - `'warn'` + -- - `'error'` + -- - `'print'` + -- - `'silent'` + -- + -- @tparam ?string level the level to initialize the logger at. + @init: (...) -> + L = Logger ... + +{ + :Logger +} diff --git a/alv/parsing.moon b/alv/parsing.moon new file mode 100644 index 0000000..f9d6f98 --- /dev/null +++ b/alv/parsing.moon @@ -0,0 +1,73 @@ +---- +-- Lpeg Grammar for parsing `alive` code. +-- +-- @module parsing +import ValueStream from require 'alv.stream' +import Cell from require 'alv.cell' +import Tag from require 'alv.tag' +import R, S, P, V, C, Ct from require 'lpeg' + +-- whitespace +wc = S ' \t\r\n' +comment = P { + 'comment', + expr: (P '(') * ((V 'expr') + (1 - P ')'))^0 * (P ')') + comment: (P '#(') * ((V 'expr') + (1 - P ')'))^0 * (P ')') +} +space = (wc^1 * (comment * wc^1)^0) / 1 -- required whitespace +mspace = (comment + wc)^0 / 1 -- optional whitespace + +-- atoms +digit = R '09' +first = (R 'az', 'AZ') + S '-_+*/.!?=%' +sym = first * (first + digit)^0 / ValueStream\parse 'sym' + +strd = '"' * (C ((P '\\"') + (P '\\\\') + (1 - P '"'))^0) * '"' / ValueStream\parse 'str', '\"' +strq = "'" * (C ((P "\\'") + (P '\\\\') + (1 - P "'"))^0) * "'" / ValueStream\parse 'str', '\'' +str = strd + strq + +int = digit^1 +float = (digit^1 * '.' * digit^0) + (digit^0 * '.' * digit^1) +num = ((P '-')^-1 * (float + int)) / ValueStream\parse 'num' + +atom = num + sym + str + +expr = (V 'cell') + atom +explist = Ct mspace * ((V 'expr') * (space * (V 'expr'))^0 * mspace)^-1 + +tag = (P '[') * (digit^1 / Tag.parse) * (P ']') +cell = (P '(') * tag^-1 * (V 'explist') * (P ')') / Cell.parse + +root = P { + (V 'explist') / Cell.parse_root + :expr, :explist, :cell +} + +cell = P { + 'cell' + :expr, :explist, :cell +} + +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 + :atom + :expr + :explist + :cell + :root + :program +} diff --git a/alv/registry.moon b/alv/registry.moon new file mode 100644 index 0000000..c41e684 --- /dev/null +++ b/alv/registry.moon @@ -0,0 +1,145 @@ +---- +-- `Tag` Registry. +-- +-- @classmod Registry +import Result from require 'alv.result' +import Error from require 'alv.error' + +class Registry +--- internals for `Tag` +-- @section internals + + --- lookup the last registration. + -- + -- @tparam number|string index the registration index + -- @treturn any + last: (index) => @last_map[index] + + --- set the current registration. + -- + -- @tparam string\number index the registration index + -- @tparam any expr the registration value + -- @tparam[default=false] boolean ignore_dup ignore duplicate registrations + register: (index, expr, ignore_dup=false) => + L\trace "reg: setting #{index} to #{expr}" + if not ignore_dup and @map[index] + error Error 'tag', "duplicate tags [#{index}]!" + @map[index] = expr + + --- request identity and registration for blank tag. + -- + -- @tparam Tag tag the blank tag + -- @tparam any expr the registration value + init: (tag, expr) => + L\trace "reg: init pending to #{expr}" + table.insert @pending, { :tag, :expr } + +--- members +-- @section members + + --- begin an evaluation cycle. + -- + -- Begin an evaltime cycle (and tick). + -- Set the active Registry and clear out pending registrations. + -- + -- All calls go `begin_eval` must be matched with either a call to + -- `end_eval` or `rollback_eval`. + begin_eval: => + @latest_map = @last_map + @begin_tick! + @map, @pending = {}, {} + + --- end an evaluation cycle. + -- + -- Register all pending `Tag`s and destroy all orphaned registrations. + -- Unset the active Registry. + end_eval: => + for tag, val in pairs @last_map + val\destroy! unless @map[tag] + + for { :tag, :expr } in *@pending + -- tag was solved by another pending registration + -- (e.g. first [A] is solved, then [5.A] is solved) + continue if tag\index! + + next_tag = #@map + 1 + L\trace "assigned new tag #{next_tag} to #{tag} #{expr}" + tag\set next_tag + @map[tag\index!] = expr + + @last_map = @map + @end_tick! + + --- abort an evaluation cycle. + -- + -- Unset the active Registry. + rollback_eval: => + @end_tick! + + --- begin a run cycle. + -- + -- Increment the tick index and set the active Registry. + begin_tick: => + @grab! + @next_tick! + + --- end a run cycle. + -- + -- Unset the active Registry. + end_tick: => + @release! + + --- manually increment the tick index (for testing). + next_tick: => + @tick += 1 + + --- set the active Registry. + grab: => + assert not @prev, "already have a previous registry? #{@prev}" + @prev, Registry.active_registry = Registry.active_registry, @ + + --- unset the active Registry. + release: => + assert @ == Registry.active_registry, "not the active registry!" + Registry.active_registry, @prev = @prev, nil + +--- static functions +-- @section static + + --- create a new Registry. + -- @classmethod + new: => + @last_map, @map = {}, {} + @tick = 0 + + --- get the active Registry. + -- + -- Raises an erro when there is no active Regsitry. + -- + -- @treturn Registry + @active: -> assert Registry.active_registry, "no active Registry!" + +class SimpleRegistry extends Registry + new: => + @cnt = 1 + @tick = 0 + + next_tick: => + @tick += 1 + + init: (tag, expr) => + tag\set @cnt + @cnt += 1 + + last: (index) => + register: (index, expr) => + + wrap: (fn) => (...) -> + @grab! + with fn ... + @release! + +{ + :Registry + :SimpleRegistry +} diff --git a/alv/result.moon b/alv/result.moon new file mode 100644 index 0000000..6c994cb --- /dev/null +++ b/alv/result.moon @@ -0,0 +1,126 @@ +---- +-- Result of evaluating an expression. +-- +-- `Result`s form a tree that controls execution order and message passing +-- between `Op`s. +-- +-- @classmod Result +class Result +--- members +-- @section members + + --- return whether this Result's value is const. + is_const: => not next @side_inputs + + --- assert value-constness and return the value. + -- @tparam[opt] string msg the error message to throw + -- @treturn any + const: (msg) => + assert not (next @side_inputs), msg or "eval-time const expected" + @value + + --- assert this result has a value, return its type. + -- @treturn string + type: => + assert @value, "Result with value expected" + @value.type + + --- assert this result has a value, returns its metatype. + -- @treturn string `"value"` or `"event"` + metatype: => + assert @value, "Result with value expected" + @value.metatype + + --- create a copy of this result with value-copy semantics. + -- the copy has the same @value and @side_inputs, but will not update + -- anything on \tick. + make_ref: => + with Result value: @value + .side_inputs = @side_inputs + + --- tick all IOStream 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 + stream\tick! if input.io + + --- in depth-first order, tick all Ops which have dirty Inputs. + -- + -- short-circuits if there are no dirty Inputs in the entire subtree + tick: => + any_dirty = false + for stream, input in pairs @side_inputs + if input\dirty! + any_dirty = true + break + + -- 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 Inputs from children may + -- have become dirty due to \tick + self_dirty = false + for stream in @op\all_inputs! + if stream\dirty! + self_dirty = true + + return unless self_dirty + + @op\tick! + + __tostring: => + buf = " 0 + buf ..= ">" + buf + + --- the `Stream` result + -- + -- @tfield ?Stream value + + --- an Op + -- + -- @tfield ?Op op + + --- list of child `Result`s from subexpressions + -- + -- @tfield {}|{Result,...} children + + --- cached mapping of all `Stream`/`Input` pairs affecting this Result. + -- + -- This is the union of all `children`s `side_inputs` and all `Input`s from + -- `op` that are not the `value` of any child. + -- + -- @tfield {[Stream]=Input,...} side_inputs + +--- static functions +-- @section static + + --- create a new Result. + -- @classmethod + -- @param params table with optional keys op, value, children. default: {} + new: (params={}) => + @value = params.value + @op = params.op + @children = params.children or {} + + @side_inputs, is_child = {}, {} + for child in *@children + for stream, input in pairs child.side_inputs + @side_inputs[stream] = input + if child.value + is_child[child.value] = true + + if @op + for input in @op\all_inputs! + if input.io or not is_child[input.stream] + @side_inputs[input.stream] = input + +{ + :Result +} diff --git a/alv/scope.moon b/alv/scope.moon new file mode 100644 index 0000000..6a0e2a5 --- /dev/null +++ b/alv/scope.moon @@ -0,0 +1,118 @@ +---- +-- Mapping from `sym`s to `Result`s. +-- +-- @classmod Scope +import ValueStream from require 'alv.stream' +import Result from require 'alv.result' +import Error from require 'alv.error' + +class Scope +--- members +-- @section members + + --- set a Lua value in the scope. + -- + -- wraps `val` in a `ValueStream` and `Result` before calling `set`. + -- + -- @tparam string key + -- @tparam any val + set_raw: (key, val) => + value = ValueStream.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" + assert val.value, Error 'type', "cannot define symbol to nil" + assert not @values[key], Error 'type', "cannot redefine symbol '#{key}'!" + @values[key] = val + + recurse: (key) => + parent = if key\match '^%*.*%*$' then @dynamic_parent else @parent + parent or= @parent + if parent + L\push parent\get, key + else + error Error 'reference', "undefined symbol '#{key}'" + + --- resolve a key in this Scope. + -- + -- @tparam string key the key to resolve + -- @treturn ?Result the value of the definition that was found, or `nil` + get: (key) => + L\debug "checking for #{key} in #{@}" + if val = @values[key] + L\trace "found #{val} in #{@}" + return val + + start, rest = key\match '^(.-)/(.+)' + if not start + return @recurse key + + child = @get start + if not child + error Error 'reference', "undefined symbol '#{start}'" + if child\type! != 'scope' + error Error 'reference', "'#{start}' is not a scope" + child.value!\get rest, while_msg + + --- 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 + + __tostring: => + buf = " 5 + keys = [key for key in *keys[,5]] + keys[6] = '...' + buf ..= " [#{table.concat keys, ', '}]" + + buf ..= ">" + buf + +--- static functions +-- @section static + + --- create a new Scope. + -- + -- @classmethod + -- @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 = {} + + --- convert a Lua table to a Scope. + -- + -- `tbl` may contain more tables (or `Scope`s). + -- Uses `ValueStream.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 + \set_raw k, v + +{ + :Scope +} diff --git a/alv/stream/base.moon b/alv/stream/base.moon new file mode 100644 index 0000000..37cc8a4 --- /dev/null +++ b/alv/stream/base.moon @@ -0,0 +1,68 @@ +---- +-- base Stream interface. +-- +-- implemented by `ValueStream`, `EventStream`, and `IOStream`. +-- +-- @classmod Stream + +class Stream +--- Stream interface. +-- +-- Methods that have to be implemented by `Stream` implementations +-- (`ValueStream`, `EventStream`, `IOStream`). +-- +-- @section interface + + --- return whether this Stream was changed in the current tick. + -- + -- @function dirty + -- @treturn boolean + + --- create a mutable copy of this Stream. + -- + -- Used to insulate eval-cycles from each other. + -- + -- @function fork + -- @treturn Stream + + --- the type name of this Stream's value. + -- + -- the following builtin typenames are used: + -- + -- - `str` - strings, `value` is a Lua string + -- - `sym` - symbols, `value` is a Lua string + -- - `num` - numbers, `value` is a Lua number + -- - `bool` - booleans, `value` is a Lua boolean + -- - `bang` - trigger signals, `value` is a Lua boolean + -- - `opdef` - `value` is an `Op` subclass + -- - `builtin` - `value` is a `Builtin` subclass + -- - `fndef` - `value` is a `FnDef` instance + -- - `scope` - `value` is a `Scope` instance + -- + -- @tfield string type + + --- documentation metadata. + -- + -- an optional table containing metadata for error messages and + -- documentation. The following keys are recognized: + -- + -- - `name`: optional name + -- - `summary`: single-line description (markdown) + -- - `examples`: optional list of single-line code examples + -- - `description`: optional full-text description (markdown) + -- + -- @tfield ?table meta + +--- static functions +-- @section static + + --- construct a new Stream. + -- + -- @classmethod + -- @tparam string type the type name + -- @tparam ?table meta the `meta` table + new: (@type, @meta={}) => + +{ + :Stream +} diff --git a/alv/stream/event.moon b/alv/stream/event.moon new file mode 100644 index 0000000..97570a2 --- /dev/null +++ b/alv/stream/event.moon @@ -0,0 +1,100 @@ +---- +-- Stream of momentary events. +-- +-- @classmod EventStream +import Stream from require 'alv.stream.base' +import Result from require 'alv.result' +import Error from require 'alv.error' +import scope, base, registry from require 'alv.cycle' + +class EventStream extends Stream +--- members +-- @section members + + --- return whether this stream was changed in the current tick. + -- + -- @treturn bool + dirty: => @updated == registry.Registry.active!.tick + + --- push an event value into the stream. + -- + -- Marks this stream as dirty for the remainder of the current tick. + -- + -- @tparam any event + add: (event) => + if not @dirty! + @events = {} + + @updated = registry.Registry.active!.tick + table.insert @events, event + + --- get the sequence of current events (if any). + -- + -- Returns `events` if `dirty`, or an empty table otherwise. + -- 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 {any,...} `events` + unwrap: (type, msg) => + assert type == @type, msg or "#{@} is not a #{type}" if type + if @dirty! then @events else {} + + --- create a mutable copy of this stream. + -- + -- Used to wrap insulate eval-cycles from each other. + -- + -- @treturn EventStream + fork: => @@ @type + + --- alias for `unwrap`. + __call: (...) => @unwrap ... + + __tostring: => + "<#{@@__name} #{@type}>" + + --- Stream metatype. + -- + -- @tfield string metatype + metatype: 'event' + + --- the type name of the stream. + -- + -- the following builtin typenames are used: + -- + -- - `str` - strings, `value` is a Lua string + -- - `sym` - symbols, `value` is a Lua string + -- - `num` - numbers, `value` is a Lua number + -- - `bool` - booleans, `value` is a Lua boolean + -- - `bang` - trigger signals, `value` is a Lua boolean + -- - `opdef` - `value` is an `Op` subclass + -- - `builtin` - `value` is a `Builtin` subclass + -- - `fndef` - `value` is a `FnDef` instance + -- - `scope` - `value` is a `Scope` instance + -- + -- @tfield string type + + --- documentation metadata. + -- + -- an optional table containing metadata for error messages and + -- documentation. The following keys are recognized: + -- + -- - `name`: optional name + -- - `summary`: single-line description (markdown) + -- - `examples`: optional list of single-line code examples + -- - `description`: optional full-text description (markdown) + -- + -- @tfield ?table meta + +--- static functions +-- @section static + + --- construct a new EventStream. + -- + -- @classmethod + -- @tparam string type the type name + new: (type) => super type + +{ + :EventStream +} diff --git a/alv/stream/init.moon b/alv/stream/init.moon new file mode 100644 index 0000000..f9393b0 --- /dev/null +++ b/alv/stream/init.moon @@ -0,0 +1,18 @@ +---- +-- `Stream` interface and implementations. +-- +-- @see Stream +-- @see ValueStream +-- @see EventStream +-- @see IOStream +-- +-- @module stream +import ValueStream from require 'alv.stream.value' +import EventStream from require 'alv.stream.event' +import IOStream from require 'alv.stream.io' + +{ + :ValueStream + :EventStream + :IOStream +} diff --git a/alv/stream/io.moon b/alv/stream/io.moon new file mode 100644 index 0000000..9b0159c --- /dev/null +++ b/alv/stream/io.moon @@ -0,0 +1,55 @@ +---- +-- Stream of external side-effects. +-- +-- Unlike other `Stream`s, this is not updated/set by an `Op` instace, but is +-- continuously polled for changes by the runtime and may mark itself as +-- *dirty* at any point in time. All runtime execution happens due to IOStream +-- updates, which ripple through the `Result` tree. +-- +-- @classmod IOStream +import EventStream from require 'alv.stream.event' + +class IOStream extends EventStream +--- IOStream interface. +-- +-- methods that have to be implemented by `IOStream` implementations. +-- @section interface + + --- construct a new IOStream. + -- + -- Must prepare the instance for `dirty` to be called. + -- The super-constructor should be called to set `Stream.type`. + -- + -- @classmethod + -- @tparam string type the typename of this stream. + new: (type) => super type + + --- create a mutable copy of this stream. + -- + -- Used to wrap insulate eval-cycles from each other. + -- + -- @treturn IOStream + fork: => @ + + --- poll for changes. + -- + -- Called every frame by the main event loop to update internal state. + tick: => + + --- check whether this adapter requires processing. + -- + -- Must return a boolean indicating whether `Op`s that refer to this instance + -- via `Input.hot` should be notified (via `Op:tick`). May be called multiple + -- times. May be called before `tick` on the first frame after construction. + -- + -- If this is not overrided, the `EventStream` interface can be used, see + -- `EventStream.add`, `EventStream.unwrap`, and `EventStream.dirty`. + -- + -- @function dirty + -- @treturn bool whether processing is required + + __inherited: (cls) => cls.__base.__tostring = @__tostring + +{ + :IOStream +} diff --git a/alv/stream/value.moon b/alv/stream/value.moon new file mode 100644 index 0000000..213a26c --- /dev/null +++ b/alv/stream/value.moon @@ -0,0 +1,237 @@ +---- +-- Continuous stream of values. +-- +-- Implements the `Stream` and `AST` intefaces. +-- +-- @classmod ValueStream +import Stream from require 'alv.stream.base' +import Result from require 'alv.result' +import Error from require 'alv.error' +import scope, base, registry from require 'alv.cycle' + +ancestor = (klass) -> + assert klass, "cant find the ancestor of nil" + while klass.__parent + klass = klass.__parent + klass + +class ValueStream extends Stream +--- members +-- @section members + + --- return whether this stream was changed in the current tick. + -- + -- @treturn bool + dirty: => @updated == registry.Registry.active!.tick + + --- update this stream's value. + -- + -- Marks this stream as dirty for the remainder of the current tick. + set: (@value) => @updated = registry.Registry.active!.tick + + --- 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 any `value` + unwrap: (type, msg) => + assert type == @type, msg or "#{@} is not a #{type}" if type + @value + + --- create a mutable copy of this stream. + -- + -- Used to insulate eval-cycles from each other. + -- + -- @treturn ValueStream + fork: => + with ValueStream @type, @value, @raw + .updated = @updated + + --- alias for `unwrap`. + __call: (...) => @unwrap ... + + --- compare two values. + -- + -- Compares two `ValueStream`s by comparing their types and their Lua values. + __eq: (other) => other.type == @type and other.value == @value + + __tostring: => + value = if @meta.name + @meta.name + else if 'table' == (type @value) and rawget @value, '__base' + @value.__name + else + tostring @value + "<#{@@__name} #{@type}: #{value}>" + + --- Stream metatype. + -- + -- @tfield string metatype + metatype: 'value' + + --- the type name of this stream. + -- + -- the following builtin typenames are used: + -- + -- - `str` - strings, `value` is a Lua string + -- - `sym` - symbols, `value` is a Lua string + -- - `num` - numbers, `value` is a Lua number + -- - `bool` - booleans, `value` is a Lua boolean + -- - `bang` - trigger signals, `value` is a Lua boolean + -- - `opdef` - `value` is an `Op` subclass + -- - `builtin` - `value` is a `Builtin` subclass + -- - `fndef` - `value` is a `FnDef` instance + -- - `scope` - `value` is a `Scope` instance + -- + -- @tfield string type + + --- the wrapped Lua value. + -- @tfield any value + + --- documentation metadata. + -- + -- an optional table containing metadata for error messages and + -- documentation. The following keys are recognized: + -- + -- - `name`: optional name + -- - `summary`: single-line description (markdown) + -- - `examples`: optional list of single-line code examples + -- - `description`: optional full-text description (markdown) + -- + -- @tfield ?table meta + +--- AST interface +-- +-- `ValueStream` 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' + Result value: @ + when 'sym' + Error.wrap "resolving symbol '#{@value}'", scope\get, @value + else + error "cannot evaluate #{@}" + + --- quote this literal constant. + -- + -- @treturn ValueStream self + quote: => @ + + --- stringify this literal constant. + -- + -- Throws an error if `raw` is not set. + -- + -- @treturn string the exact string this stream was parsed from + stringify: => assert @raw, "stringifying ValueStream that wasn't parsed" + + --- clone this literal constant. + -- + -- @treturn ValueStream self + clone: (prefix) => @ + +--- static functions +-- @section static + + --- construct a new ValueStream. + -- + -- @classmethod + -- @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) => super type + + unescape = (str) -> str\gsub '\\([\'"\\])', '%1' + --- 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 + + --- 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) + -- @treturn ValueStream + @wrap: (val, name='(unknown)') -> + typ = switch type val + when 'number' then 'num' + when 'string' then 'str' + when 'table' + if rawget val, '__base' + -- a class + switch ancestor val + when base.Op then 'opdef' + when base.Builtin then 'builtin' + else + error "#{name}: cannot wrap class '#{val.__name}'" + elseif val.__class + -- an instance + switch ancestor val.__class + when scope.Scope then 'scope' + when base.FnDef then 'fndef' + when Stream then return val + else + error "#{name}: cannot wrap '#{val.__class.__name}' instance" + else + -- plain table + return ValueStream 'scope', scope.Scope.from_table val + else + error "#{name}: cannot wrap Lua type '#{type val}'" + + ValueStream typ, val + + --- create a constant number. + -- @tparam number num the number + -- @treturn ValueStream + @num: (num) -> ValueStream 'num', num, tostring num + + --- create a constant string. + -- @tparam string str the string + -- @treturn ValueStream + @str: (str) -> ValueStream 'str', str, "'#{str}'" + + --- create a constant symbol. + -- @tparam string sym the symbol + -- @treturn ValueStream + @sym: (sym) -> ValueStream 'sym', sym, sym + + --- create a constant boolean. + -- @tparam boolean bool the boolean + -- @treturn ValueStream + @bool: (bool) -> ValueStream 'bool', bool, tostring bool + + --- wrap and document a value. + -- + -- wraps `args.value` using `wrap`, then assigns `meta`. + -- + -- @tparam table args table with keys `value` and `meta` + -- @treturn ValueStream + @meta: (args) -> + with ValueStream.wrap args.value + .meta = args.meta if args.meta + +class LiteralValue extends ValueStream + eval: => Result value: @ + +{ + :ValueStream + :LiteralValue +} diff --git a/alv/tag.moon b/alv/tag.moon new file mode 100644 index 0000000..904cd20 --- /dev/null +++ b/alv/tag.moon @@ -0,0 +1,122 @@ +---- +-- Identity provider for `Cell`s and `Builtin`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 'alv.registry' + +local ClonedTag + +class DummyReg + destroy: => + + __tostring: => "" + +dummy = DummyReg! + +class Tag +--- members +-- @section members + + --- obtain the registered value of the last eval-cycle. + -- + -- Obtain the value that was previously registered for this tag on the last + -- eval-cylce. + -- + -- @treturn ?any + last: => + if index = @index! + Registry.active!\last index + + --- 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 + register: (expr) => + if index = @index! + Registry.active!\register index, expr + else + Registry.active!\init @, expr + + --- 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 + if index = @index! + Registry.active!\register index, dummy, true + else + Registry.active!\init @, dummy + + assert parent, "need parent to clone!" + ClonedTag @, parent + + stringify: => if @value then "[#{@value}]" else '' + __tostring: => if @value then "#{@value}" else '?' + +--- internals for `Registry` +-- @section internals + + new: (@value) => + + --- get a unique index value for this Tag. + -- + -- The index is equal to `value` for simple tags and a path-like string for + -- cloned tags. + -- + -- @treturn ?number|string + index: => @value + + --- callback to set value for blank tags. + -- @tparam number value + set: (value) => + assert not @value, "#{@} is not blank" + @value = value + +--- static functions +-- @section static + + --- create a blank `Tag`. + -- + -- @treturn Tag + @blank: -> Tag! + + --- parse a `Tag` (for Lpeg parsing). + -- + -- @tparam string num the number-string + -- @treturn Tag + @parse: (num) -> Tag tonumber num + +class ClonedTag extends Tag + new: (@original, @parent) => + + index: => + orig = @original\index! + parent = @parent\index! + if orig and parent + "#{parent}.#{orig}" + + set: (value) => + assert @parent.value, "cloned tag #{@} set before parent" + @original\set value + + stringify: => error "cant stringify ClonedTag" + + __tostring: => + if @parent + "#{@parent}.#{@original}" + else + tostring @original + +{ + :Tag +} diff --git a/alv/version.moon b/alv/version.moon new file mode 100644 index 0000000..6cee1b1 --- /dev/null +++ b/alv/version.moon @@ -0,0 +1,17 @@ +---- +-- `alive` source code version information. +-- +-- @module version + +--- exports +-- @table exports +-- @tfield string tag the last versions git tag +-- @tfield string web the repo web URL +-- @tfield string repo the git repo URL +-- @tfield string release the web URL of this release +{ + tag: "v0.1-rc2" + web: "https://github.com/s-ol/alivecoding" + repo: "https://github.com/s-ol/alivecoding.git" + release: "https://github.com/s-ol/alivecoding/releases/tag/v0.1-rc2" +} diff --git a/bin/alv b/bin/alv new file mode 100755 index 0000000..6abe6c8 --- /dev/null +++ b/bin/alv @@ -0,0 +1,23 @@ +#!/usr/bin/env moon +import Copilot, Logger from require 'alv' +import sleep from require 'system' + +arguments, key = {} +for a in *arg + if match = a\match '^%-%-(.*)' + key = match + arguments[key] = true + elseif key + arguments[key] = a + key = nil + else + table.insert arguments, a + +Logger.init arguments.log + +assert arguments[1], "no filename given" +copilot = Copilot arguments[1] + +while true + copilot\tick! + sleep 1 / 1000 diff --git a/bin/copilot.bat b/bin/copilot.bat new file mode 100644 index 0000000..58c1bf9 --- /dev/null +++ b/bin/copilot.bat @@ -0,0 +1,6 @@ +@echo off +setlocal +set PATH=%PATH%;%~dp0\lua\lua\bin +set LUA_PATH=%LUA_PATH%;%~dp0\?.lua;%~dp0\alive\?\init.lua +moon %~dp0\init.moon %* +exit /b %ERRORLEVEL% diff --git a/bin/copilot.sh b/bin/copilot.sh new file mode 100755 index 0000000..68e8ebd --- /dev/null +++ b/bin/copilot.sh @@ -0,0 +1,23 @@ +#!/bin/sh +set -e + +tmpdir=$(mktemp -d) +trap 'rm -rf "$tmpdir"' EXIT INT TERM HUP + +ARGS="$@" +FIFO=$tmpdir/fifo +CONF=$tmpdir/conf +LIBDIR=$(dirname $0) +LUA_PATH="$LIBDIR/?.lua;$LIBDIR/?/init.lua;$LUA_PATH" + +cat > "$CONF" << 'EOF' +split +focus +screen -t evaltime sh -c 'tty > "$FIFO"; read done < "$FIFO"' +focus +screen -t runtime sh -c 'read tty < "$FIFO"; moon "$LIBDIR/alv/copilot.moon" $ARGS 2> "$tty"; echo "[press enter to exit]"; read prompt; echo done > "$FIFO"' +EOF + +mkfifo "$FIFO" +export FIFO ARGS LIBDIR LUA_PATH +exec screen -mc "$CONF" diff --git a/copilot.moon b/copilot.moon deleted file mode 100644 index 8f98ab6..0000000 --- a/copilot.moon +++ /dev/null @@ -1,67 +0,0 @@ -lfs = require 'lfs' -import parse, globals, Scope, Error, Registry from require 'core' - -slurp = (file) -> - file = io.open file, 'r' - with file\read '*all' - file\close! - -spit = (file, str) -> - file = io.open file, 'w' - file\write str - file\close! - -class Copilot - new: (@file) => - @registry = Registry! - - @last_modification = 0 - - mode = lfs.attributes @file, 'mode' - if mode != 'file' - error "not a file: #{@file}" - - tick: => - @poll! - - if @root - @registry\begin_tick! - ok, error = Error.try "updating", -> - @root\tick_io! - @root\tick! - if not ok - print error - @registry\end_tick! - - eval: => - @registry\begin_eval! - ok, ast = Error.try "parsing '#{@file}'", parse, slurp @file - if not ok - print ast - @registry\rollback_eval! - return - - scope = Scope globals - ok, root = Error.try "evaluating '#{@file}'", ast\eval, scope, @registry - if not ok - print root - @registry\rollback_eval! - return - - @registry\end_eval! - @root = root - spit @file, ast\stringify! - - poll: => - { :mode, :modification } = (lfs.attributes @file) or {} - if mode != 'file' - return - - if @last_modification < modification - L\log "#{@file} changed at #{modification}" - @eval! - @last_modification = os.time! - -{ - :Copilot -} diff --git a/core/ast.moon b/core/ast.moon deleted file mode 100644 index a25d1cd..0000000 --- a/core/ast.moon +++ /dev/null @@ -1,40 +0,0 @@ ----- --- AST Node Interface. --- --- implemented by `Value` and `Cell`. --- --- @classmod AST - ---- members --- @section members - - --- evaluate this AST Node. - -- - -- Evaluate this node and return a `Result`. - -- - -- @function eval - -- @tparam Scope scope the scope to evaluate in - -- @treturn Result the evaluation result - - --- quote this AST Node, preserving its identity. - -- - --- Returns a mutable copy of this Node that shares its identity. - -- - -- @function quote - -- @treturn AST - - --- 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 expressions recursively). - -- - -- @function clone - -- @tparam Tag parent - -- @treturn AST - - --- stringify this AST Node. - -- - -- Should return the exact string this node was parsed from (if it was parsed). - -- - -- @function stringify - -- @treturn string the exact string this Node was parsed from diff --git a/core/base/builtin.moon b/core/base/builtin.moon deleted file mode 100644 index fd36c69..0000000 --- a/core/base/builtin.moon +++ /dev/null @@ -1,96 +0,0 @@ ----- --- Builtin / Special Form evaluation Strategy (`builtin`). --- --- Responsible for quoting/evaluating subexpressions, instantiating and setting --- up `Op`s, updating the current `Scope`, etc. --- See `builtin` and `invoke` for examples. --- --- @classmod Builtin - -class Builtin ---- Builtin interface. --- --- methods that have to be implemented by `Builtin` implementations. --- @section interface - - --- create a new instance. - -- - -- @tparam Cell cell the Cell to evaluate - -- @tparam Value head the (`AST:eval`d) `head` of the Cell to evaluate - new: (@cell, @head) => - @tag = @cell.tag - @tag\register @ - - --- 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: => - - --- setup or copy state from previous instance of same type. - -- - -- `prev` is only passed if Builtin types of prev and current expression match. - -- Otherwise, or when no previous expression exists, `nil` is passed. - -- - -- @tparam ?Builtin prev the previous Builtin instance - setup: (prev) => - - --- the `Cell` this Builtin was created for. - -- @tfield Cell cell - - --- the evaluated head of `cell`. - -- @tfield AST head - - --- the identity of `cell`. - -- @tfield Tag tag - ---- static functions --- @section static - - --- create and setup a `Builtin` for a given tag, then evaluate it. - -- - -- Create a new instance using `tag` and `head` and call `setup` on it. - -- If a previous instance with the same `tag` exists and has the same `head`, - -- it pass it to `setup`. Register the `Builtin` with `tag`, evaluate it - -- and return the `Result`. - -- - -- @tparam Cell cell the `Cell` being evaluated - -- @tparam Scope scope the active scope - -- @tparam Value head the (`AST:eval`d) head of the `Cell` being evaluated - -- @treturn Result the result of evaluation - @eval_cell: (cell, scope, head) => - last = cell.tag\last! - compatible = last and (last.__class == @) and last.head == head - - L\trace if compatible - "reusing #{last} for #{cell.tag} <#{@__name} #{head}>" - else if last - "replacing #{last} with new #{cell.tag} <#{@__name} #{head}>" - else - "initializing #{cell.tag} <#{@__name} #{head}>" - - builtin = @ cell, head - if compatible - builtin\setup last - else - last\destroy! if last - builtin\setup nil - - builtin\eval scope, cell\tail! - - __tostring: => "<#{@@__name} #{@head}>" - __inherited: (cls) => cls.__base.__tostring = @__tostring - -{ - :Builtin -} diff --git a/core/base/fndef.moon b/core/base/fndef.moon deleted file mode 100644 index b79ad85..0000000 --- a/core/base/fndef.moon +++ /dev/null @@ -1,26 +0,0 @@ ----- --- 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 ---- static functions --- @section static - - --- create a new instance - -- - -- @classmethod - -- @tparam {Value,...} params (`AST:quote`d) naming the function parameters - -- @tparam AST body (`AST: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 deleted file mode 100644 index 9153f79..0000000 --- a/core/base/init.moon +++ /dev/null @@ -1,39 +0,0 @@ ----- --- Base definitions for extensions. --- --- This module exports the following classes and tables that extension modules --- may need: --- --- @module base --- @see Op --- @see Builtin --- @see FnDef --- @see Input --- @see base.match.val --- @see base.match.evt --- @see ValueStream --- @see EventStream --- @see IOStream --- @see Result --- @see Error - -import Op from require 'core.base.op' -import Builtin from require 'core.base.builtin' -import FnDef from require 'core.base.fndef' -import Input from require 'core.base.input' -import val, evt from require 'core.base.match' -import ValueStream, EventStream, IOStream from require 'core.stream' -import Result from require 'core.result' -import Error from require 'core.error' - -{ - :Op - :Builtin - :FnDef - :Input - :val, :evt - - -- redundant exports, to keep anything an extension might need in one import - :ValueStream, :EventStream, :IOStream - :Result, :Error -} diff --git a/core/base/input.moon b/core/base/input.moon deleted file mode 100644 index bcf188e..0000000 --- a/core/base/input.moon +++ /dev/null @@ -1,146 +0,0 @@ ----- --- Update scheduling policy for `Op` arguments. --- --- @classmod Input -import ValueStream, EventStream, IOStream from require 'core.stream' -import Result from require 'core.result' - -inherits = (klass, frm) -> - assert klass, "cant find the ancestor of nil" - return true if klass == frm - while klass.__parent - return true if klass.__parent == frm - klass = klass.__parent - false - -match_parent = (inst, map) -> - klass = assert inst and inst.__class, "not an instance" - if key = map[klass] - return key - - while klass.__parent - if key = map[klass.__parent] - return key - - klass = klass.__parent - -local ColdInput, ValueInput, IOInput, mapping - -class Input ---- Input interface. --- --- Methods that have to be implemented by `Input` implementations. --- @section interface - - --- create a new Input. - -- - -- @classmethod - -- @tparam Stream stream - new: (@stream) => - assert @stream, "nil passed to Input: #{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`. - -- - -- @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`:@{ValueStream:dirty|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 - - --- return the metatype name of this `Input` (optional). - metatype: => @stream.metatype - - --- the current value - -- - -- @tfield ValueStream stream - ---- members --- @section members - - --- alias for `unwrap`. - __call: => @stream\unwrap! - - __tostring: => "#{@@__name}:#{@stream}" - __inherited: (cls) => - cls.__base.__call = @__call - cls.__base.__tostring = @__tostring - ---- static functions --- @section static - - --- Create a `cold` `Input`. - -- - -- Never marked dirty. Use this for input streams that are only read when - -- another `Input` is dirty. - -- - -- @tparam Stream|Result value - @cold: (value) -> - if value.__class == Result - value = assert value.value, "Input from result without value!" - ColdInput value - - --- Create a `hot` `Input`. - -- - -- Behaviour depends on what kind of `Stream` `value` is: - -- - -- - `ValueStream`: Marked dirty for the eval-tick if old and new `ValueStream` differ. - -- - `EventStream` and `IOStream`: Marked dirty only if the current `EventStream` is dirty. - -- - -- This is the most common `Input` strategy. - -- - -- @tparam Stream|Result value - @hot: (value) -> - if value.__class == Result - value = assert value.value, "Input from result without value!" - - InputType = match_parent value, mapping - assert InputType, "Input from unknown value: #{value}" - InputType 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 = nil - dirty: => - return @dirty_setup if @dirty_setup != nil - @stream\dirty! - -class IOInput extends Input - io: true - -mapping = { - [ValueStream]: ValueInput - [EventStream]: Input - [IOStream]: IOInput -} - -{ - :Input -} diff --git a/core/base/match.moon b/core/base/match.moon deleted file mode 100644 index 700b50d..0000000 --- a/core/base/match.moon +++ /dev/null @@ -1,302 +0,0 @@ ------ ---- Pattern capturing for Op argument parsing. --- --- There is only one basic buildings block for assembling patterns: --- `Type`. It can match `ValueStream`s and `EventStream`s depending on its --- metatype argument and can take an optional type name to match as an argument. --- --- In addition to this primitive, the following modifiers are available: --- `Repeat`, `Sequence`, `Choice`, and `Optional`. They can be used directly, --- but there is also a number of shorthands for assembling patterns quickly: --- --- - `val()` and `evt()`: Shorthands for `Type('value')` and `Type('event')` --- - `val.num`: Shorthand for `Type('value', 'num')` --- - `evt.str`: Shorthand for `Type('event', 'str')` --- - `pat * 2`: Shorthand for `Repeat(pat, 1, 2)` (1-4 times `pat`) --- - `pat * 0`: Shorthand for `Repeat(pat, 1, nil)` (1-* times `pat`) --- - `pat ^ 2`: Shorthand for `Repeat(pat, 0, 2)` (0-4 times `pat`) --- - `pat ^ 0`: Shorthand for `Repeat(pat, 0, nil)` (0-* times `pat`) --- - `a + b + … + z`: Shorthand for `Sequence{ a, b, ..., z }` --- - `a / b / … / z`: Shorthand for `Choice{ a, b, ..., z }` --- - `-pat`: Shorthand for `Optional(pat)` --- --- To perform the actual matching, call the `:match` method on a pattern and --- pass a sequence of `Result`s. The method will either return the captured --- `Result`s (or a table structuring them) --- --- Any ambiguous pattern can be set to 'recall mode' by invoking it. --- Recalling patterns will memorize the first Result they match, and --- only match further Results of the same type. For example --- --- arg = (val.num / val.str)! --- pattern = arg + arg --- --- ...will match either two numbers or two strings, but not one number and one --- string. Recalling works on `Choice` and `Type` patterns. `Type` patterns --- without a type (`val!` and `evt!`) always behave like this. --- --- On `Sequence` patterns, a special method `:named` exists. It takes a --- sequence of keys that are used instead of integers when constructing the --- capture table: --- --- pattern = (val.str + val.num):named('key', 'value') --- pattern:match(...) --- -- returns { {key='a', value=1}, {key='b', value=2}, ...} --- --- @module base.match -import Error from require 'core.error' -import ValueStream, EventStream from require 'core.stream' - -local Repeat, Sequence, Choice, Optional - -typestr = (result) -> - str = result\type! - str ..= '!' if result\metatype! == 'event' - str - -class Pattern - match: (seq) => - @reset! - num, cap = @capture seq, 1 - if num != #seq - args = table.concat [typestr arg for arg in *seq], ' ' - msg = "couldn't match arguments (#{args}) against pattern #{@}" - error Error 'argument', msg - cap - - remember: (key) => - return true unless @recall - - @recalled or= key - @recalled == key - - rep: (min, max) => Repeat @, min, max - - reset: => @recalled = nil - - __call: => @ - __mul: (num) => Repeat @, 1, if num != 0 then num - __pow: (num) => Repeat @, 0, if num != 0 then num - __add: (other) => Sequence { @, other } - __div: (other) => Choice { @, other } - __unm: => Optional @ - - __inherited: (cls) => - cls.__base.__call or= @__call - cls.__base.__mul or= @__mul - cls.__base.__pow or= @__pow - cls.__base.__add or= @__add - cls.__base.__div or= @__div - cls.__base.__unm or= @__unm - ---- Base Stream Pattern. --- --- When instantiated with `type`, only succeeds for `Stream`s whose value and --- meta types match. --- --- Otherwise, matches Streams based only on `metatype` for the first match, but --- using both afterwards (recall mode). --- --- @function Stream --- @tparam string metatype "value" or "event" --- @tparam ?string type type name -class Type extends Pattern - new: (@metatype, @type) => - @recall = not @type - - capture: (seq, i) => - return unless seq[i] - type, mt = seq[i]\type!, seq[i]\metatype! - return unless @metatype == mt - match = if @type then type == @type else @remember type - if match - 1, seq[i] - - __tostring: => - str = @type or @metatype - str ..= '!' if @metatype == 'event' - str - ---- Repeat a pattern. --- --- Matches a given `inner` pattern as many times as possible, within the given --- minimum/maximum counts. Matching this pattern results in a sequence of the --- individual captures produced by the inner pattern. --- --- @function Repeat --- @tparam Pattern inner the original pattern --- @tparam ?number min minimum amount of repetitions --- @tparam ?number max maximum amount of repetitions (default infinite) -class Repeat extends Pattern - new: (@inner, @min, @max) => - - capture: (seq, i) => - take, all = 0, {} - while true - num, cap = @inner\capture seq, i+take - break unless num - - take += num - table.insert all, cap - - break if @max and take >= @max - - return if @min and take < @min - return if @max and take > @max - - take, all - - reset: => - @inner\reset! - - __call: => - @@ @inner!, @min, @max - - __tostring: => - min = @min or '0' - max = @max or '*' - "#{@inner}{#{min}-#{max}}" - ---- Match multiple patterns in order. --- --- Matches the inner patterns in order, only succeeds if all of them match. --- Captures the individual captures produced by the inner patterns in a --- sequence, or table with keys specified in `keys` or using the `:named(...)` --- modifier. --- --- @function Sequence --- @tparam {Pattern,...} elements the inner patterns --- @tparam ?{string,...} keys the keys to use when capturing matches -class Sequence extends Pattern - new: (@elements, @keys) => - - capture: (seq, i) => - take, all = 0, {} - for key, elem in ipairs @elements - num, cap = elem\capture seq, i+take - return unless num - - take += num - key = @keys[key] if @keys - all[key] = cap - - take, all - - reset: => - for elem in *@elements - elem\reset! - - named: (...) => - @@ [e for e in *@elements], { ... } - - __call: => - @@ [e! for e in *@elements] - - __add: (other) => - elements = [e for e in *@elements] - table.insert elements, other - @@ elements - - __tostring: => - core = table.concat [tostring e for e in *@elements], ' ' - "(#{core})" - ---- Match one of multiple options. --- --- Matches using the first matching pattern in `elements` and returns its --- captured value. Supports recalling the matched subpattern. --- --- @function Choice --- @tparam {Pattern,...} elements the inner patterns --- @tparam ?{string,...} keys the keys to use when capturing matches -class Choice extends Pattern - new: (@elements, @recall=false) => - - capture: (seq, i) => - for key, elem in ipairs @elements - num, cap = elem\capture seq, i - if num and @remember key - return num, cap - - reset: => - super! - for elem in *@elements - elem\reset! - - __call: => - @@ [e! for e in *@elements], true - - __div: (other) => - elements = [e for e in *@elements] - table.insert elements, other - @@ elements - - __tostring: => - core = table.concat [tostring e for e in *@elements], ' | ' - "(#{core})" - ---- Optionally match a pattern. --- --- Matches using the first matching pattern in `elements` and returns its --- captured value. Supports recalling the matched subpattern. --- --- @function Optional --- @tparam {Pattern,...} elements the inner patterns --- @tparam ?{string,...} keys the keys to use when capturing matches -class Optional extends Pattern - new: (@inner) => - - capture: (seq, i) => - num, cap = @inner\capture seq, i - num or 0, cap - - reset: => - @inner\reset! - - __call: => - @@ @inner! - - __unm: => @ - - __tostring: => "#{@inner}?" - ---- `Value` shorthands. --- --- Call or index with a string to obtain a `Type` instance. --- Call to obtain a wildcard pattern. --- --- val.str, val.num --- val['vec3'], val('vec3') --- val() --- --- @table val -val = setmetatable {}, { - __index: (key) => - with v = Type 'value', key - @[key] = v - - __call: (...) => Type 'value', ... -} - ---- `Event` shorthands. --- --- Call or index with a string to obtain an `Type` instance. --- Call to obtain a wildcard pattern. --- --- evt.bang, evt.str, evt.num --- evt['midi/message'], evt('midi/message') --- evt() --- --- @table evt -evt = setmetatable {}, { - __index: (key) => - with v = Type 'event', key - @[key] = v - - __call: (...) => Type 'event', ... -} - -{ - :Type, :Repeat, :Sequence, :Choice, :Optional - :val, :evt -} diff --git a/core/base/op.moon b/core/base/op.moon deleted file mode 100644 index b0a83dc..0000000 --- a/core/base/op.moon +++ /dev/null @@ -1,168 +0,0 @@ ----- --- Persistent expression Operator. --- --- @classmod Op - -deepcopy = (val) -> - switch type val - when 'number', 'string', 'boolean', 'nil' - val - when 'table' - assert (not getmetatable {}), "state should only contain simple tables!" - {(deepcopy k), (deepcopy v) for k,v in pairs val} - else - error "state cannot contain values of type '#{type val}'" - -class Op ---- members --- @section members - - do_yield = (table) -> - for k, v in pairs table - if v.__class - coroutine.yield v - else - do_yield v - --- yield all `Input`s from the (potentially nested) `inputs` table - -- - -- @treturn iterator iterator over `inputs` - all_inputs: => coroutine.wrap -> do_yield @inputs - - --- create a mutable copy of this Op. - -- - -- Used to wrap insulate eval-cycles from each other. The copy does not have - -- `inputs` set, since it is expected that this is (re)set in `setup`. - -- - -- @treturn Op - fork: => - out = if @out then @out\fork! - state = if @state then deepcopy @state - @@ out, state - - --- internal state of this Op. - -- - -- This may be any simple Lua value, including Lua tables, as long as it has - -- no metatables, multiple references/loops, userdata etc. - -- - -- @tfield table state - - --- `Stream` instance representing this Op's computed output value. - -- - -- Must be set to a `Stream` instance once `setup` finishes. Must not change - -- type, be removed or replaced outside of `new` and `setup`. If it is a - -- `ValueStream`, it should have a value assigned via `set` or the - -- constructor once `tick` is called the first time. If `out`'s value is not - -- initialized in `new` or `setup`, the implementation must make sure - -- `tick``(true)` is called at least on the first eval-cycle the Op goes - -- through, e.g. by using an `Input.hot` with a `ValueStream`. - -- - -- @tfield Stream out - - --- table containing `Input`s to this Op. - -- - -- 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. - -- - -- @tfield {Input,...} inputs - ---- Op interface. --- --- methods that have to be implemented by `Op` implementations. --- @section interface - - --- construct a new instance. - -- - -- The optional parameters `out` and `state` are used by `fork` to duplicate - -- an instance. If the constructor is overriden, these parameters must be - -- forwarded to the superconstructor unchanged. - -- - -- @function new - -- @classmethod - -- @tparam ?Stream out `out` - -- @tparam ?table state `state` - - --- 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 {Result,...} inputs 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 may have - -- special behaviour immediately after `setup` that can cause them to become - -- dirty at eval-time. In this case, an eval-time tick is executed. You can - -- detect this using the `setup` parameter. - -- - -- `tick` is called after `setup`. `tick` is not called immediately after - -- `setup` if no `inputs` are dirty. Update `out` here. - -- - -- @tparam bool setup whether this is an eval-time tick - tick: => - - --- called when the Op is destroyed (optional). - destroy: => - ---- 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. - -- - -- @classmethod - -- @tparam ?Stream out `out` - -- @tparam ?table state `state` - new: (@out, @state) => - - 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 - --- 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: (inputs) => - old_inputs = @inputs - @inputs = inputs - do_setup old_inputs, @inputs - - do_unwrap = (value) -> - if value.__class - value\unwrap! - else - {k, do_unwrap v for k,v in pairs value} - --- `\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_unwrap @inputs - - __tostring: => "" - __inherited: (cls) => cls.__base.__tostring = @__tostring - -{ - :Op -} diff --git a/core/builtin.moon b/core/builtin.moon deleted file mode 100644 index 68b825c..0000000 --- a/core/builtin.moon +++ /dev/null @@ -1,333 +0,0 @@ ----- --- Builtin `Builtin`s and `Op`s. --- --- Please see the [reference](../../reference/index.html#builtins) for --- documentation. --- --- @module builtin -import Builtin, Op, FnDef, Input, val, evt from require 'core.base' -import ValueStream, LiteralValue from require 'core.stream.value' -import Result from require 'core.result' -import Cell from require 'core.cell' -import Scope from require 'core.scope' -import Tag from require 'core.tag' -import op_invoke from require 'core.invoke' - -doc = ValueStream.meta - meta: - name: 'doc' - summary: "Print documentation in console." - examples: { '(doc sym)' } - description: "Print the documentation for `sym` to the console" - - value: class extends Builtin - format_meta = => - str = @summary - if @examples - for example in *@examples - str ..= '\n' .. example - if @description - str ..= '\n' .. @description\match '^\n*(.+)\n*$' - str - - eval: (scope, tail) => - assert #tail == 1, "'doc' takes exactly one parameter" - - result = L\push tail[1]\eval, scope - with Result children: { def } - meta = result.value.meta - L\print "(doc #{tail[1]}):\n#{format_meta meta}\n" - -def = ValueStream.meta - meta: - name: 'def' - summary: "Declare symbols in current scope." - examples: { '(def sym1 val-expr1 [sym2 val-expr2…])' } - description: " -Define the symbols `sym1`, `sym2`, … to resolve to the values of `val-expr1`, -`val-expr2`, …." - - value: class extends Builtin - eval: (scope, tail) => - L\trace "evaling #{@}" - assert #tail > 1, "'def' requires at least 2 arguments" - assert #tail % 2 == 0, "'def' requires an even number of arguments" - - children = L\push -> - return for i=1,#tail,2 - name, val_expr = tail[i], tail[i+1] - name = (name\quote scope)\unwrap 'sym' - - with val_expr\eval scope - scope\set name, \make_ref! - - Result :children - -use = ValueStream.meta - meta: - name: 'use' - summary: "Merge scopes into current scope." - examples: { '(use scope1 [scope2…])' } - description: " -Copy all symbol definitions from `scope1`, `scope2`, … to the current scope. -All arguments have to be evaltime constant." - - value: class extends Builtin - eval: (scope, tail) => - L\trace "evaling #{@}" - for child in *tail - result = L\push child\eval, scope - value = result\const! - scope\use value\unwrap 'scope', "'use' only works on scopes" - - Result! - -require_ = ValueStream.meta - meta: - name: 'require' - summary: "Load a module." - examples: { '(require name)' } - description: "Load a module and return its scope." - - value: class extends Builtin - eval: (scope, tail) => - L\trace "evaling #{@}" - assert #tail == 1, "'require' takes exactly one parameter" - - result = L\push tail[1]\eval, scope - name = result\const! - - L\trace @, "loading module #{name}" - scope = ValueStream.wrap require "lib.#{name\unwrap 'str'}" - Result :value - -import_ = ValueStream.meta - meta: - name: 'import' - summary: "Require and define modules." - examples: { '(import sym1 [sym2…])' } - description: " -Requires modules `sym1`, `sym2`, … and define them as `sym1`, `sym2`, … in the -current scope." - - value: class extends Builtin - eval: (scope, tail) => - L\trace "evaling #{@}" - assert #tail > 0, "'import' requires at least one arguments" - - for child in *tail - name = (child\quote scope)\unwrap 'sym' - value = ValueStream.wrap require "lib.#{name}" - scope\set name, Result :value -- (require "lib.#{name})\unwrap 'scope' - Result! - -import_star = ValueStream.meta - meta: - name: 'import*' - summary: "Require and use modules." - examples: { '(import* sym1 [sym2…])' } - description: " -Requires modules `sym1`, `sym2`, … and merges them into the current scope." - - value: class extends Builtin - eval: (scope, tail) => - L\trace "evaling #{@}" - assert #tail > 0, "'import' requires at least one arguments" - - - for child in *tail - name = (child\quote scope)\unwrap 'sym' - value = ValueStream.wrap require "lib.#{name}" - scope\use value\unwrap 'scope' -- (require "lib.#{name}")\unwrap 'scope' - - Result! - -fn = ValueStream.meta - meta: - name: 'fn' - summary: "Declare a function." - examples: { '(fn (p1 [p2…]) body-expr)' } - description: " -The symbols `p1`, `p2`, ... will resolve to the arguments passed when the -function is invoked." - - value: class extends Builtin - eval: (scope, tail) => - L\trace "evaling #{@}" - assert #tail == 2, "'fn' takes exactly two arguments" - { params, body } = tail - - assert params.__class == Cell, "'fn's first argument has to be an expression" - param_symbols = for param in *params.children - assert param.type == 'sym', "function parameter declaration has to be a symbol" - param\quote scope - - body = body\quote scope - Result value: with ValueStream.wrap FnDef param_symbols, body, scope - .meta = { - summary: "(user defined function)" - examples: { "(??? #{table.concat [p! for p in *param_symbols], ' '})" } - } - -defn = ValueStream.meta - meta: - name: 'defn' - summary: "Define a function." - examples: { '(defn name-sym (p1 [p2…]) body-expr)' } - description: " -Declare a function and define it as `name-sym` in the current scope. -The symbols `p1`, `p2`, ... will resolve to the arguments passed when the -function is invoked." - - value: class extends Builtin - eval: (scope, tail) => - L\trace "evaling #{@}" - assert #tail == 3, "'defn' takes exactly three arguments" - { name, params, body } = tail - - name = (name\quote scope)\unwrap 'sym' - assert params.__class == Cell, "'defn's second argument has to be an expression" - param_symbols = for param in *params.children - assert param.type == 'sym', "function parameter declaration has to be a symbol" - param\quote scope - - body = body\quote scope - - value = with ValueStream.wrap FnDef param_symbols, body, scope - .meta = - :name - summary: "(user defined function)" - examples: { "(#{name} #{table.concat [p! for p in *param_symbols], ' '})" } - - scope\set name, Result :value - Result! - -do_expr = ValueStream.meta - meta: - name: 'do_expr' - summary: "Evaluate multiple expressions in a new scope." - examples: { '(do expr1 [expr2…])' } - description: " -Evaluate `expr1`, `expr2`, … and return the value of the last expression." - - value: class extends Builtin - eval: (scope, tail) => - scope = Scope scope - Result children: [expr\eval scope for expr in *tail] - -if_ = ValueStream.meta - meta: - name: 'if' - summary: "Make an evaltime const choice." - examples: { '(if bool then-expr [else-expr])' } - description: " -`bool` has to be an evaltime constant. If it is truthy, this expression is equivalent -to `then-expr`, otherwise it is equivalent to `else-xpr` if given, or nil otherwise." - - value: class extends Builtin - eval: (scope, tail) => - L\trace "evaling #{@}" - assert #tail >= 2, "'if' needs at least two parameters" - assert #tail <= 3, "'if' needs at most three parameters" - - { xif, xthen, xelse } = tail - - xif = L\push xif\eval, scope - xif = xif\const!\unwrap! - - if xif - xthen\eval scope - elseif xelse - xelse\eval scope - -trace_ = ValueStream.meta - meta: - name: 'trace!' - summary: "Trace an expression's value at evaltime." - examples: { '(trace! expr)' } - - value: class extends Builtin - eval: (scope, tail) => - L\trace "evaling #{@}" - assert #tail == 1, "'trace!' takes exactly one parameter" - - with result = L\push tail[1]\eval, scope - L\print "trace! #{tail[1]\stringify!}: #{result.value}" - -trace = ValueStream.meta - meta: - name: 'trace' - summary: "Trace an expression's values at runtime." - examples: { '(trace expr)' } - - value: class extends Builtin - class traceOp extends Op - setup: (inputs) => - super - prefix: Input.cold inputs[1] - value: Input.hot inputs[2] - - tick: => - L\print "trace #{@inputs.prefix!}: #{@inputs.value.stream}" - - eval: (scope, tail) => - L\trace "evaling #{@}" - assert #tail == 1, "'trace!' takes exactly one parameter" - - tag = @tag\clone Tag.parse '-1' - inner = Cell tag, { - LiteralValue 'opdef', traceOp, 'trace' - ValueStream.str tostring tail[1] - tail[1] - } - inner\eval scope - -print = ValueStream.meta - meta: - name: 'print' - summary: "Print string values." - examples: { '(print str)' } - - value: class extends Op - setup: (inputs) => - value = (val.str / evt.str)\match inputs - super value: Input.hot value - - tick: => - if @inputs.value\metatype! == 'event' - for msg in *@inputs.value! - print msg - else - print @inputs.value! - -{ - :doc - :trace, 'trace!': trace_, :print - - :def, :use - require: require_ - import: import_ - 'import*': import_star - - true: ValueStream.meta - meta: - name: 'true' - summary: "The boolean constant `true`." - value: ValueStream.bool true - - false: ValueStream.meta - meta: - name: 'false' - summary: "The boolean constant `false`." - value: ValueStream.bool false - - bang: ValueStream.meta - meta: - name: 'bang' - summary: "A `bang` value-constant." - value: ValueStream 'bang', true - - :fn, :defn - 'do': do_expr - if: if_ -} diff --git a/core/cell.moon b/core/cell.moon deleted file mode 100644 index b028f14..0000000 --- a/core/cell.moon +++ /dev/null @@ -1,189 +0,0 @@ ----- --- 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 ValueStream from require 'core.stream' -import Error from require 'core.error' -import op_invoke, fn_invoke from require 'core.invoke' -import Tag from require 'core.tag' - -local RootCell - -class Cell ---- members --- @section members - - 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 - - --- the parsed Tag. - -- - -- @tfield Tag tag - - --- sequence of child AST Nodes - -- - -- @tfield {AST,...} children - - --- optional sequence of whitespace segments. - -- - -- If set, `whitespace[i]` is the whitespace between `children[i]` and - -- `children[i+1]`, or the closing parenthesis of this Cell. `whitespace[0]` - -- is the space between the opening parenthesis and `children[1]`. - -- - -- @tfield ?{string,...} whitespace - ---- AST interface --- --- `Cell` implements the `AST` interface. --- @section ast - - --- evaluate this Cell. - -- - -- `AST:eval`uates the head of the expression, and finds the appropriate - -- `Builtin` 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 `Builtin:eval_cell` on it. - -- - -- @tparam Scope scope the scope to evaluate in - -- @treturn Result the evaluation result - eval: (scope) => - head = assert @head!, Error 'syntax', "cannot evaluate empty expr" - head = (head\eval scope)\const! - Builtin = switch head.type - when 'opdef' - -- scope\get 'op-invoke' - op_invoke - when 'fndef' - -- scope\get 'fn-invoke' - fn_invoke - when 'builtin' - head\unwrap! - else - error Error 'type', "#{head} is not an opdef, fndef or builtin" - - Builtin\eval_cell @, scope, head - - --- 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 - quote: => - 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` - -- to `tag` and cloning all child expressions recursively. - -- - -- @tparam Tag parent - -- @treturn Cell - 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] - if depth == 0 - buf ..= '...' - else - for i, child in ipairs @children - buf ..= child\stringify if depth == -1 then -1 else depth - 1 - buf ..= if depth > 0 then ' ' else @white[i] - - if depth > 0 - buf = buf\sub 1, #buf - 1 - - tag = if depth == -1 then @tag\stringify! else '' - - '(' .. tag .. buf .. ')' - ---- static functions --- @section static - - parse_args = (tag, parts) -> - if not parts - parts, tag = tag, nil - - children, white = {}, { [0]: parts[1] } - - for i = 2,#parts,2 - children[i/2] = parts[i] - white[i/2] = parts[i+1] - - tag, children, white - --- parse a Cell (for parsing with Lpeg). - -- - -- @tparam[opt] Tag tag - -- @tparam table parts - -- @treturn Cell - @parse: (...) -> - tag, children, white = parse_args ... - Cell tag, children, white - - --- 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: (...) -> - tag, children, white = parse_args (Tag.parse '0'), ... - RootCell tag, children, white - --- @type RootCell -class RootCell extends Cell - head: => ValueStream.sym 'do' - tail: => @children - - stringify: => - buf = '' - buf ..= @white[0] - - for i, child in ipairs @children - buf ..= child\stringify! - buf ..= @white[i] - - buf - -{ - :Cell - :RootCell -} diff --git a/core/config.ld b/core/config.ld deleted file mode 100644 index f00deba..0000000 --- a/core/config.ld +++ /dev/null @@ -1,16 +0,0 @@ -project = 'alive internals' -title = 'developer docs' - -description = "`alive` developer documentation" -full_description = [[This section documents the *implementation* of the alive -language and copilot. It is relevant to everyone who is looking to modify, -improve or extend alive with new modules, language or interpreter features. - -If you are looking for the language reference for users, head over to the -[reference](../reference/index.html) section of the documentation.]] - -format = 'discount' -style = 'docs' -template = 'docs' -topics={'core/extensions.md'} -dir = 'docs/internals' diff --git a/core/cycle.moon b/core/cycle.moon deleted file mode 100644 index 57d4a75..0000000 --- a/core/cycle.moon +++ /dev/null @@ -1,26 +0,0 @@ --- late-resolve cyclic dependencies --- --- this module provides a proxy for resolving values from modules which cannot --- be loaded due to cyclic dependencies. Instead of --- --- import Something from require 'core.somewhere' --- Something ... --- --- use --- --- import somewhere from require 'core.cycle' --- somewhere.Something ... --- --- Make sure cycle:load() is called before you access or dereference --- `somewhere`. - -load = => - for name, module in pairs @ - for k, v in pairs require "core.#{name}" - module[k] = v - -setmetatable {}, __index: (key) => - return load if key == 'load' - - with v = {} - rawset @, key, v diff --git a/core/error.moon b/core/error.moon deleted file mode 100644 index e72cc5f..0000000 --- a/core/error.moon +++ /dev/null @@ -1,94 +0,0 @@ ----- --- Language error and traceback. --- --- @classmod Error - -unpack or= table.unpack - -class Error ---- members --- @section members - - --- append a traceback frame. - -- - -- `where` should denote where the Error occured and fit grammatically to - -- complete the sentence `"{error} occured while {where}"` - -- - -- @tparam string where - add_frame: (where) => @trace ..= "\n while #{where}" - - __tostring: => - str = "#{@kind} error: #{@message}" - if @detail - str ..= "\n#{@detail}" - if @trace - str ..= @trace - str - ---- static functions --- @section static - - --- create a new Error. - -- - -- `kind` should be one of: - -- - -- - `'reference'`: error concerning symbol resolution - -- - `'argument'`: error concerning Op argument matching - -- - `'implementation'`: error in the Lua/MoonScript implementation of alive. - -- Should not occur in normal operation, and constitutes a bug. - -- - -- @classmethod - -- @tparam string kind - -- @tparam string message - -- @tparam ?string detail - new: (@kind, @message, @detail) => - @trace = '' - - handler = (err) -> - if err.__class == Error - err - else - Error 'implementation', err, debug.traceback "Lua error below:", 2 - --- Wrap function errors in a traceback frame. - -- - -- Execute `fn(...)`, and turn any error thrown as a result into an - -- `Error` instance, before re-throwing it. - -- - -- When `Error` instances are caught, `frame` is added to the traceback. - -- All other error values are turned into `'implementation'` Errors. - -- - -- @tparam string frame - -- @tparam function fn - @wrap: (frame, fn, ...) -> - results = { xpcall fn, handler, ... } - ok = table.remove results, 1 - if ok - unpack results - else - error with results[1] - \add_frame frame - - --- Capture and wrap function errors in traceback frame. - -- - -- Execute `fn(...)`, and turn any error thrown as a result into an - -- `Error` instance, before re-throwing it. - -- - -- When `Error` instances are caught, `frame` is added to the traceback. - -- All other error values are turned into `'implementation'` Errors. - -- - -- @tparam string frame - -- @tparam function fn - -- @treturn boolean `ok` true if exeuction suceeded without errors - -- @treturn Error|any `error_or_results` the `Error` instance or results - @try: (frame, fn, ...) -> - results = { xpcall fn, handler, ... } - ok = table.remove results, 1 - if ok - ok, unpack results - else - ok, with results[1] - \add_frame frame - -{ - :Error -} diff --git a/core/extensions.md b/core/extensions.md deleted file mode 100644 index 6af9bca..0000000 --- a/core/extensions.md +++ /dev/null @@ -1,221 +0,0 @@ -# writing `alive` extensions - -Extensions for `alive` are implemented in [Lua][lua] or [MoonScript][moonscript] -(which runs as Lua). When an `alive` module is [`(require)`][builtins-req]d, -alive looks for a Lua module `lib.[module]`. You can simply add a new file with -extension `.lua` or `.moon` in the `lib` directory of your alive installation or -somewhere else in your `LUA_PATH`. - -To write extensions, a number of classes and utilities are required. All of -these are exported in the `base` module. - -## documentation metadata -The lua module should return a `Scope` or a table that will be converted using -`Scope.from_table`. All exports should be documented using `ValueStream.meta`, -which attaches a `meta` table to the value that is used for error messages, -documentation generation and [`(doc)`][builtins-doc]. - - import ValueStream from require 'core.base' - - two = ValueStream.meta - meta: - name: 'two' - summary: "the number two" - value: 2 - - { - :two - } - -In the `meta` table `summary` is the only required key, but all of the -information that applies should be provided. - -- `name`: the name of this export (for error reporting). -- `summary`: a one-line plain-text description of this entry. Should be - capitalized and end with a period. -- `examples`: a table of strings, each of which is a short one-line code - example illustrating the argument names for an Op. -- `description`: a longer markdown-formatted description of the functionality - of this entry. - -## defining `Op`s -Most extensions will want to define a number of *Op*s to be used by the user. -They are implemented by deriving from the `Op` class and implementing at least -the `Op:setup` and `Op:tick` methods. - - import ValueStream, Op, Input, evt from require 'core.base' - - total_sum = ValueStream.meta - meta: - name: 'total-sum' - summary: "Keep a total of incoming numbers." - examples: { '(total-sum num!)' } - description: "Keep a total sum of incoming number events, extension-style." - - value: class extends Op - new: (...) => - super ... - @state or= { total: 0 } - @out or= ValueStream 'num', @state.total - - setup: (inputs, scope) => - num = evt.num\match inputs - super num: Inputs.hot num - - tick: => - @state.total += @inputs.num! - @out\set @state.total - - { - 'total-sum': total_sum - } - -### Op:setup -`Op:setup` is called once every *eval cycle* to parse the Op's arguments, check -their types, choose the updating behaviour and define the output type. - -The arguments to `:setup` are a list of inputs (each is a `Result` instance), -and the `Scope` the evaluation happened in. Ops generally shouldn't use the -scope, but might look up 'magic' dynamic symbols like `\*clock\*`. - -#### argument parsing -Arguments should be parsed using `base.match`. The two exports `base.match.val` -and `base.match.evt` are used to build complex patterns that can parse and -validate the Op arguments into complex structures (see the module documentation -for more information). - - import val, evt from require 'core.base' - - pattern = evt.bang + val.str + val.num*3 + -evt! - { trig, str, numbers, optional } = pattern\match inputs - -This example matches first an `EventStream` of type `bang`, then a `ValueStream` -of type `str`, followed by one, two or three `num`-values and finally an -optional argument `EventStream` of any type. `:match` will throw an error if it -couldn't (fully) match the arguments and otherwise return a structured mapping -of the inputs. - -If there are more complex dependencies between arguments, it is recommended to -do as much of the parsing as possible using the `base.match` and then continue -manually. For invalid or missing arguments, `Error` instances should be thrown -using `error` or `assert`. - -#### input setup -There are two types of inputs: `Input.hot` and `Input.cold`: - -*Cold* inputs do not cause the Op to update when changes to the input stream -are made. They are useful to 'ignore' changes to inputs which are only relevant -when another input changed value. Imagine for example a `send-value-when` Op, -which sends a value only when a `bang!` input is live. This Op doesn't have to -update when the value changes, it's enough to update only when the trigger -input changes and simply read the value in that moment. - -*Hot* inputs on the other hand mark the input stream as a dependency for the -Op. Depending on the type of `Stream`, the semantics are a little different: - -- For `ValueStream`s, the Op updates whenever the current value changes. When - an input stream is swapped out for another one at evaltime, but their values - are momentarily equal, the input is not considered dirty. -- For `EventStream`s and `IOStream`s, the Op updates whenever the stream is - dirty. There is no special handling when the stream is swapped out at - evaltime. - -All `Result`s from the `inputs` argument that are taken into consideration -should be wrapped in an `Input` instance using either `Input.hot` or -`Input.cold`, and need to be passed to the `Op:setup` super implementation. -To illustrate with the `send-value-when` example: - - setup: (inputs, scope) => - { trig, value } = match 'bang! any', inputs - - super - trig: Inputs.hot trig - value: Inputs.cold value - -`Op:setup` takes a table that can have any (even nested) shape you want, as -long as all 'leaf values' are `Input` instances. The following are both valid: - - super { (Inputs.hot trig), (Inputs.cold value) } - - super - trigger: Inputs.hot trig - values: { (Inputs.cold a), (Inputs.cold b), (Inputs.cold c) } - -#### output setup -When `Op:setup` finishes, `@out` has to be set to a `Stream` instance. The -instance can be created in `Op:setup`, or by overriding the constructor and -delegating to the original one using `super`. In general setting it in the -constructor is preferred, and it is only moved to `Op:setup` if the output -type depends on the arguments received. - -There are three types of `Stream`s that can be created: - -- `ValueStream`s track *continuous values*. They can only have one value per - tick, and downstream Ops will not update when a *ValueStream* has been set - to the same value it already had. They are updated using `ValueStream:set`. -- `EventStream`s transmit *momentary events*. They can transmit multiple events - in a single tick. `EventStream`s do not keep a value set on the last tick on - the next tick. They are updated using `EventStream:add`. -- `IOStream`s are like `EventStream`s, but their `IOStream:tick` method is - polled by the event loop at the start of every tick. This gives them a chance - to effectively create changes 'out of thin air' and kickstart the execution - of the dataflow engine. All *runtime* execution is due to an `IOStream` - becoming dirty somewhere. See the section on implementing `IOStream`s below - for more information. - -### Op:tick -`Op:tick` is called whenever any of the inputs are *dirty*. This is where the -Op's main logic will go. Generally here it should be checked which input(s) -changed, and then internal state and the output value may be updated. - -## defining `Builtin`s -Builtins are more powerful than Ops, because they control whether, which and -how their arguments are evaluated. They roughly correspond to *macros* in Lisps. -There is less of a concrete guideline for implementing Builtins because there -are a lot more options, and it really depends a lot on what the Builtin should -achieve. Nevertheless, a good starting point is to read the `Builtin` class -documentation, take a look at `Builtin`s in `core/builtin.moon` and get -familiar with the relevant internal interfaces (especially `AST`, `Result`, and -`Scope`). - -## defining `IOStream`s -`IOStream`s are `EventStream`s that can 'magically' create events out of -nothing. They are the source of all processing in alive. Whenever you want to -bring events into alive from an external protocol or application, an IOStream -will be necessary. - -To implement a custom IOStream, create it as a class that inherits from the -`IOStream` base and implement the constructor and `IOStream:tick`: - - import IOStream from require 'core.base' - - class UnreliableStream extends IOStream - new: => super 'bang' - - tick: => - if math.random! < 0.1 - @add true - -In the constructor, you should call the super-constructor `EventStream.new` to -set the event type. Often this will be a custom event that is only used inside -your extension (such as e.g. the `midi/port` type in the [midi][modules-midi] -module), but it can also be a primitive type like `'num'` in this example. In -`:tick`, your IOStream is given a chance to communicate with the external world -and create any resulting events. The example stream above randomly sends bang -events out, with a 10% chance each 'tick' of the system. Note that there is no -guarantee about when or how often ticks occur, so you really shouldn't rely on -them this way in a real extension. - -### using `IOStream`s -There's a couple of ways IOStreams can be used and exposed to the user of your -extension. You can either expose an instance of your IOStream directly -(documented using `ValueStream.meta`), or offer an Op that creates and returns -an instance in `Op.out` - that way the IOStream can be created only on demand -and take parameters. It is also possible to not exepose the IOStream at all, -and rather pass it as a hardcoded input into an Op's `Op.inputs`. - -[lua]: https://www.lua.org/ -[moonscript]: http://moonscript.org/ -[builtins-req]: ../../reference/index.html#require -[builtins-doc]: ../../reference/index.html#doc -[modules-midi]: ../../reference/midi.html diff --git a/core/init.moon b/core/init.moon deleted file mode 100644 index 6e23fa7..0000000 --- a/core/init.moon +++ /dev/null @@ -1,55 +0,0 @@ ----- --- `alive` public API. --- --- @module init -L or= setmetatable {}, __index: => -> - -import ValueStream, EventStream, IOStream from require 'core.stream' -import Result from require 'core.result' -import Scope from require 'core.scope' -import Error from require 'core.error' -import Registry, SimpleRegistry from require 'core.registry' -import Tag from require 'core.tag' - -import Cell from require 'core.cell' -import cell, program from require 'core.parsing' - -with require 'core.cycle' - \load! - -globals = Scope.from_table require 'core.builtin' - ---- exports --- @table exports --- @tfield ValueStream ValueStream --- @tfield EventStream EventStream --- @tfield IOStream IOStream --- @tfield Result Result --- @tfield Cell Cell --- @tfield RootCell RootCell --- @tfield Scope Scope --- @tfield Error Error --- @tfield Registry Registry --- @tfield Tag Tag --- @tfield Scope globals global definitons --- @tfield parse function to turn a `string` into a root `Cell` -{ - :ValueStream, :EventStream, :IOStream - :Cell, :RootCell - :Result, :Scope, :Error - - :Registry, :SimpleRegistry, :Tag - - :globals - - parse: (str) -> - assert (program\match str), Error 'syntax', "failed to parse" - - eval: (str, inject) -> - scope = Scope nil, globals - scope\use inject if inject - - ast = assert (program\match str), "failed to parse" - result = ast\eval scope - result\const! -} diff --git a/core/invoke.moon b/core/invoke.moon deleted file mode 100644 index 8fcb525..0000000 --- a/core/invoke.moon +++ /dev/null @@ -1,124 +0,0 @@ ----- --- Builtins for invoking `Op`s and `FnDef`s. --- --- @module invoke -import Result from require 'core.result' -import Builtin from require 'core.base' -import Scope from require 'core.scope' -import Error from require 'core.error' - -get_name = (value, raw) -> - meta = if value.meta then value.meta.name - locl = if raw and raw.type == 'sym' then raw! - - if locl - if meta and meta != locl - "'#{meta}' (local '#{locl}')" - else - "'#{locl}'" - else if meta - "'#{meta}'" - else - "(unnamed)" - ---- `Builtin` implementation that invokes an `Op`. --- --- @type op_invoke -class op_invoke extends Builtin - --- `Builtin:setup` implementation. - -- - -- `Op:fork`s the `prev`'s `Op` instance if given. Creates a new instance - -- otherwise. - setup: (prev) => - if prev - @op = prev.op\fork! - else - def = @head\unwrap 'opdef', "cant op-invoke #{@head}" - @op = def! - - --- `Builtin:destroy` implementation. - -- - -- calls `op`:@{Op:destroy|destroy}. - destroy: => @op\destroy! - - --- evaluate an `Op` invocation. - -- - -- `AST:eval`s the tail, and passes the result to `op`:@{Op:setup|setup}. Then - -- checks if any of `op`:@{Op:all_inputs|all_inputs} are @{Input:dirty|dirty}, - -- and if so, calls `op`:@{Op:tick|tick}. - -- - -- The `Result` contains `op`, `Op.value` and all the `Result`s from the tail. - -- - -- @tparam Scope scope the active scope - -- @tparam {AST,...} tail the arguments to this expression - -- @treturn Result - eval: (scope, tail) => - children = [L\push expr\eval, scope for expr in *tail] - - frame = "invoking op #{get_name @head, @cell\head!} at [#{@tag}]" - Error.wrap frame, @op\setup, [result for result in *children], scope - - any_dirty = false - for input in @op\all_inputs! - if input\dirty! - any_dirty = true - break - - if any_dirty - @op\tick true - - for input in @op\all_inputs! - input\finish_setup! - - Result :children, value: @op.out, op: @op - - --- The `Op` instance. - -- - -- @tfield Op op - ---- `Builtin` implementation that invokes a `FnDef`. --- --- @type fn_invoke -class fn_invoke extends Builtin - --- evaluate a user-function invocation. - -- - -- Creates a new `Scope` that inherits from `FnDef.scope` and has - -- `outer_scope` as an additional parent for dynamic symbol resolution. - -- Then `AST:eval`s the tail in `outer_scope`, and defines the results to the - -- names in `FnDef.params` in the newly created scope. Lastly, `AST:clone`s - -- `FnDef.body` with the prefix `Builtin.tag`, and `AST:eval`s it in the newly - -- created `Scope`. - -- - -- The `Result` contains the `Stream` from the cloned AST, and its children - -- are all the `Result`s from evaluating the tail as well as the cloned - -- `AST`s. - -- - -- @tparam Scope outer_scope the active scope - -- @tparam {AST,...} tail the arguments to this expression - -- @treturn Result the result of this evaluation - eval: (outer_scope, tail) => - name = get_name @head, @cell\head! - frame = "invoking function #{name} at [#{@tag}]" - - { :params, :body, :scope } = @head\unwrap 'fndef', "cant fn-invoke #{@head}" - if #params != #tail - err = Error 'argument', "expected #{#params} arguments, found #{#tail}" - err\add_frame frame - error err - - fn_scope = Scope scope, outer_scope - - children = for i=1,#params - name = params[i]\unwrap 'sym' - with L\push tail[i]\eval, outer_scope - fn_scope\set name, \make_ref! - - clone = body\clone @tag - result = Error.wrap frame, clone\eval, fn_scope - - table.insert children, result - Result :children, value: result.value - -{ - :op_invoke, :fn_invoke -} diff --git a/core/parsing.moon b/core/parsing.moon deleted file mode 100644 index aadcab4..0000000 --- a/core/parsing.moon +++ /dev/null @@ -1,73 +0,0 @@ ----- --- Lpeg Grammar for parsing `alive` code. --- --- @module parsing -import ValueStream from require 'core.stream' -import Cell from require 'core.cell' -import Tag from require 'core.tag' -import R, S, P, V, C, Ct from require 'lpeg' - --- whitespace -wc = S ' \t\r\n' -comment = P { - 'comment', - expr: (P '(') * ((V 'expr') + (1 - P ')'))^0 * (P ')') - comment: (P '#(') * ((V 'expr') + (1 - P ')'))^0 * (P ')') -} -space = (wc^1 * (comment * wc^1)^0) / 1 -- required whitespace -mspace = (comment + wc)^0 / 1 -- optional whitespace - --- atoms -digit = R '09' -first = (R 'az', 'AZ') + S '-_+*/.!?=%' -sym = first * (first + digit)^0 / ValueStream\parse 'sym' - -strd = '"' * (C ((P '\\"') + (P '\\\\') + (1 - P '"'))^0) * '"' / ValueStream\parse 'str', '\"' -strq = "'" * (C ((P "\\'") + (P '\\\\') + (1 - P "'"))^0) * "'" / ValueStream\parse 'str', '\'' -str = strd + strq - -int = digit^1 -float = (digit^1 * '.' * digit^0) + (digit^0 * '.' * digit^1) -num = ((P '-')^-1 * (float + int)) / ValueStream\parse 'num' - -atom = num + sym + str - -expr = (V 'cell') + atom -explist = Ct mspace * ((V 'expr') * (space * (V 'expr'))^0 * mspace)^-1 - -tag = (P '[') * (digit^1 / Tag.parse) * (P ']') -cell = (P '(') * tag^-1 * (V 'explist') * (P ')') / Cell.parse - -root = P { - (V 'explist') / Cell.parse_root - :expr, :explist, :cell -} - -cell = P { - 'cell' - :expr, :explist, :cell -} - -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 - :atom - :expr - :explist - :cell - :root - :program -} diff --git a/core/registry.moon b/core/registry.moon deleted file mode 100644 index a0134ec..0000000 --- a/core/registry.moon +++ /dev/null @@ -1,145 +0,0 @@ ----- --- `Tag` Registry. --- --- @classmod Registry -import Result from require 'core.result' -import Error from require 'core.error' - -class Registry ---- internals for `Tag` --- @section internals - - --- lookup the last registration. - -- - -- @tparam number|string index the registration index - -- @treturn any - last: (index) => @last_map[index] - - --- set the current registration. - -- - -- @tparam string\number index the registration index - -- @tparam any expr the registration value - -- @tparam[default=false] boolean ignore_dup ignore duplicate registrations - register: (index, expr, ignore_dup=false) => - L\trace "reg: setting #{index} to #{expr}" - if not ignore_dup and @map[index] - error Error 'tag', "duplicate tags [#{index}]!" - @map[index] = expr - - --- request identity and registration for blank tag. - -- - -- @tparam Tag tag the blank tag - -- @tparam any expr the registration value - init: (tag, expr) => - L\trace "reg: init pending to #{expr}" - table.insert @pending, { :tag, :expr } - ---- members --- @section members - - --- begin an evaluation cycle. - -- - -- Begin an evaltime cycle (and tick). - -- Set the active Registry and clear out pending registrations. - -- - -- All calls go `begin_eval` must be matched with either a call to - -- `end_eval` or `rollback_eval`. - begin_eval: => - @latest_map = @last_map - @begin_tick! - @map, @pending = {}, {} - - --- end an evaluation cycle. - -- - -- Register all pending `Tag`s and destroy all orphaned registrations. - -- Unset the active Registry. - end_eval: => - for tag, val in pairs @last_map - val\destroy! unless @map[tag] - - for { :tag, :expr } in *@pending - -- tag was solved by another pending registration - -- (e.g. first [A] is solved, then [5.A] is solved) - continue if tag\index! - - next_tag = #@map + 1 - L\trace "assigned new tag #{next_tag} to #{tag} #{expr}" - tag\set next_tag - @map[tag\index!] = expr - - @last_map = @map - @end_tick! - - --- abort an evaluation cycle. - -- - -- Unset the active Registry. - rollback_eval: => - @end_tick! - - --- begin a run cycle. - -- - -- Increment the tick index and set the active Registry. - begin_tick: => - @grab! - @next_tick! - - --- end a run cycle. - -- - -- Unset the active Registry. - end_tick: => - @release! - - --- manually increment the tick index (for testing). - next_tick: => - @tick += 1 - - --- set the active Registry. - grab: => - assert not @prev, "already have a previous registry? #{@prev}" - @prev, Registry.active_registry = Registry.active_registry, @ - - --- unset the active Registry. - release: => - assert @ == Registry.active_registry, "not the active registry!" - Registry.active_registry, @prev = @prev, nil - ---- static functions --- @section static - - --- create a new Registry. - -- @classmethod - new: => - @last_map, @map = {}, {} - @tick = 0 - - --- get the active Registry. - -- - -- Raises an erro when there is no active Regsitry. - -- - -- @treturn Registry - @active: -> assert Registry.active_registry, "no active Registry!" - -class SimpleRegistry extends Registry - new: => - @cnt = 1 - @tick = 0 - - next_tick: => - @tick += 1 - - init: (tag, expr) => - tag\set @cnt - @cnt += 1 - - last: (index) => - register: (index, expr) => - - wrap: (fn) => (...) -> - @grab! - with fn ... - @release! - -{ - :Registry - :SimpleRegistry -} diff --git a/core/result.moon b/core/result.moon deleted file mode 100644 index 6c994cb..0000000 --- a/core/result.moon +++ /dev/null @@ -1,126 +0,0 @@ ----- --- Result of evaluating an expression. --- --- `Result`s form a tree that controls execution order and message passing --- between `Op`s. --- --- @classmod Result -class Result ---- members --- @section members - - --- return whether this Result's value is const. - is_const: => not next @side_inputs - - --- assert value-constness and return the value. - -- @tparam[opt] string msg the error message to throw - -- @treturn any - const: (msg) => - assert not (next @side_inputs), msg or "eval-time const expected" - @value - - --- assert this result has a value, return its type. - -- @treturn string - type: => - assert @value, "Result with value expected" - @value.type - - --- assert this result has a value, returns its metatype. - -- @treturn string `"value"` or `"event"` - metatype: => - assert @value, "Result with value expected" - @value.metatype - - --- create a copy of this result with value-copy semantics. - -- the copy has the same @value and @side_inputs, but will not update - -- anything on \tick. - make_ref: => - with Result value: @value - .side_inputs = @side_inputs - - --- tick all IOStream 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 - stream\tick! if input.io - - --- in depth-first order, tick all Ops which have dirty Inputs. - -- - -- short-circuits if there are no dirty Inputs in the entire subtree - tick: => - any_dirty = false - for stream, input in pairs @side_inputs - if input\dirty! - any_dirty = true - break - - -- 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 Inputs from children may - -- have become dirty due to \tick - self_dirty = false - for stream in @op\all_inputs! - if stream\dirty! - self_dirty = true - - return unless self_dirty - - @op\tick! - - __tostring: => - buf = " 0 - buf ..= ">" - buf - - --- the `Stream` result - -- - -- @tfield ?Stream value - - --- an Op - -- - -- @tfield ?Op op - - --- list of child `Result`s from subexpressions - -- - -- @tfield {}|{Result,...} children - - --- cached mapping of all `Stream`/`Input` pairs affecting this Result. - -- - -- This is the union of all `children`s `side_inputs` and all `Input`s from - -- `op` that are not the `value` of any child. - -- - -- @tfield {[Stream]=Input,...} side_inputs - ---- static functions --- @section static - - --- create a new Result. - -- @classmethod - -- @param params table with optional keys op, value, children. default: {} - new: (params={}) => - @value = params.value - @op = params.op - @children = params.children or {} - - @side_inputs, is_child = {}, {} - for child in *@children - for stream, input in pairs child.side_inputs - @side_inputs[stream] = input - if child.value - is_child[child.value] = true - - if @op - for input in @op\all_inputs! - if input.io or not is_child[input.stream] - @side_inputs[input.stream] = input - -{ - :Result -} diff --git a/core/scope.moon b/core/scope.moon deleted file mode 100644 index 526ca8d..0000000 --- a/core/scope.moon +++ /dev/null @@ -1,118 +0,0 @@ ----- --- Mapping from `sym`s to `Result`s. --- --- @classmod Scope -import ValueStream from require 'core.stream' -import Result from require 'core.result' -import Error from require 'core.error' - -class Scope ---- members --- @section members - - --- set a Lua value in the scope. - -- - -- wraps `val` in a `ValueStream` and `Result` before calling `set`. - -- - -- @tparam string key - -- @tparam any val - set_raw: (key, val) => - value = ValueStream.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" - assert val.value, Error 'type', "cannot define symbol to nil" - assert not @values[key], Error 'type', "cannot redefine symbol '#{key}'!" - @values[key] = val - - recurse: (key) => - parent = if key\match '^%*.*%*$' then @dynamic_parent else @parent - parent or= @parent - if parent - L\push parent\get, key - else - error Error 'reference', "undefined symbol '#{key}'" - - --- resolve a key in this Scope. - -- - -- @tparam string key the key to resolve - -- @treturn ?Result the value of the definition that was found, or `nil` - get: (key) => - L\debug "checking for #{key} in #{@}" - if val = @values[key] - L\trace "found #{val} in #{@}" - return val - - start, rest = key\match '^(.-)/(.+)' - if not start - return @recurse key - - child = @get start - if not child - error Error 'reference', "undefined symbol '#{start}'" - if child\type! != 'scope' - error Error 'reference', "'#{start}' is not a scope" - child.value!\get rest, while_msg - - --- 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 - - __tostring: => - buf = " 5 - keys = [key for key in *keys[,5]] - keys[6] = '...' - buf ..= " [#{table.concat keys, ', '}]" - - buf ..= ">" - buf - ---- static functions --- @section static - - --- create a new Scope. - -- - -- @classmethod - -- @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 = {} - - --- convert a Lua table to a Scope. - -- - -- `tbl` may contain more tables (or `Scope`s). - -- Uses `ValueStream.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 - \set_raw k, v - -{ - :Scope -} diff --git a/core/stream/base.moon b/core/stream/base.moon deleted file mode 100644 index 37cc8a4..0000000 --- a/core/stream/base.moon +++ /dev/null @@ -1,68 +0,0 @@ ----- --- base Stream interface. --- --- implemented by `ValueStream`, `EventStream`, and `IOStream`. --- --- @classmod Stream - -class Stream ---- Stream interface. --- --- Methods that have to be implemented by `Stream` implementations --- (`ValueStream`, `EventStream`, `IOStream`). --- --- @section interface - - --- return whether this Stream was changed in the current tick. - -- - -- @function dirty - -- @treturn boolean - - --- create a mutable copy of this Stream. - -- - -- Used to insulate eval-cycles from each other. - -- - -- @function fork - -- @treturn Stream - - --- the type name of this Stream's value. - -- - -- the following builtin typenames are used: - -- - -- - `str` - strings, `value` is a Lua string - -- - `sym` - symbols, `value` is a Lua string - -- - `num` - numbers, `value` is a Lua number - -- - `bool` - booleans, `value` is a Lua boolean - -- - `bang` - trigger signals, `value` is a Lua boolean - -- - `opdef` - `value` is an `Op` subclass - -- - `builtin` - `value` is a `Builtin` subclass - -- - `fndef` - `value` is a `FnDef` instance - -- - `scope` - `value` is a `Scope` instance - -- - -- @tfield string type - - --- documentation metadata. - -- - -- an optional table containing metadata for error messages and - -- documentation. The following keys are recognized: - -- - -- - `name`: optional name - -- - `summary`: single-line description (markdown) - -- - `examples`: optional list of single-line code examples - -- - `description`: optional full-text description (markdown) - -- - -- @tfield ?table meta - ---- static functions --- @section static - - --- construct a new Stream. - -- - -- @classmethod - -- @tparam string type the type name - -- @tparam ?table meta the `meta` table - new: (@type, @meta={}) => - -{ - :Stream -} diff --git a/core/stream/event.moon b/core/stream/event.moon deleted file mode 100644 index 7712fc4..0000000 --- a/core/stream/event.moon +++ /dev/null @@ -1,100 +0,0 @@ ----- --- Stream of momentary events. --- --- @classmod EventStream -import Stream from require 'core.stream.base' -import Result from require 'core.result' -import Error from require 'core.error' -import scope, base, registry from require 'core.cycle' - -class EventStream extends Stream ---- members --- @section members - - --- return whether this stream was changed in the current tick. - -- - -- @treturn bool - dirty: => @updated == registry.Registry.active!.tick - - --- push an event value into the stream. - -- - -- Marks this stream as dirty for the remainder of the current tick. - -- - -- @tparam any event - add: (event) => - if not @dirty! - @events = {} - - @updated = registry.Registry.active!.tick - table.insert @events, event - - --- get the sequence of current events (if any). - -- - -- Returns `events` if `dirty`, or an empty table otherwise. - -- 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 {any,...} `events` - unwrap: (type, msg) => - assert type == @type, msg or "#{@} is not a #{type}" if type - if @dirty! then @events else {} - - --- create a mutable copy of this stream. - -- - -- Used to wrap insulate eval-cycles from each other. - -- - -- @treturn EventStream - fork: => @@ @type - - --- alias for `unwrap`. - __call: (...) => @unwrap ... - - __tostring: => - "<#{@@__name} #{@type}>" - - --- Stream metatype. - -- - -- @tfield string metatype - metatype: 'event' - - --- the type name of the stream. - -- - -- the following builtin typenames are used: - -- - -- - `str` - strings, `value` is a Lua string - -- - `sym` - symbols, `value` is a Lua string - -- - `num` - numbers, `value` is a Lua number - -- - `bool` - booleans, `value` is a Lua boolean - -- - `bang` - trigger signals, `value` is a Lua boolean - -- - `opdef` - `value` is an `Op` subclass - -- - `builtin` - `value` is a `Builtin` subclass - -- - `fndef` - `value` is a `FnDef` instance - -- - `scope` - `value` is a `Scope` instance - -- - -- @tfield string type - - --- documentation metadata. - -- - -- an optional table containing metadata for error messages and - -- documentation. The following keys are recognized: - -- - -- - `name`: optional name - -- - `summary`: single-line description (markdown) - -- - `examples`: optional list of single-line code examples - -- - `description`: optional full-text description (markdown) - -- - -- @tfield ?table meta - ---- static functions --- @section static - - --- construct a new EventStream. - -- - -- @classmethod - -- @tparam string type the type name - new: (type) => super type - -{ - :EventStream -} diff --git a/core/stream/init.moon b/core/stream/init.moon deleted file mode 100644 index 3fff136..0000000 --- a/core/stream/init.moon +++ /dev/null @@ -1,18 +0,0 @@ ----- --- `Stream` interface and implementations. --- --- @see Stream --- @see ValueStream --- @see EventStream --- @see IOStream --- --- @module stream -import ValueStream from require 'core.stream.value' -import EventStream from require 'core.stream.event' -import IOStream from require 'core.stream.io' - -{ - :ValueStream - :EventStream - :IOStream -} diff --git a/core/stream/io.moon b/core/stream/io.moon deleted file mode 100644 index e3e59a2..0000000 --- a/core/stream/io.moon +++ /dev/null @@ -1,55 +0,0 @@ ----- --- Stream of external side-effects. --- --- Unlike other `Stream`s, this is not updated/set by an `Op` instace, but is --- continuously polled for changes by the runtime and may mark itself as --- *dirty* at any point in time. All runtime execution happens due to IOStream --- updates, which ripple through the `Result` tree. --- --- @classmod IOStream -import EventStream from require 'core.stream.event' - -class IOStream extends EventStream ---- IOStream interface. --- --- methods that have to be implemented by `IOStream` implementations. --- @section interface - - --- construct a new IOStream. - -- - -- Must prepare the instance for `dirty` to be called. - -- The super-constructor should be called to set `Stream.type`. - -- - -- @classmethod - -- @tparam string type the typename of this stream. - new: (type) => super type - - --- create a mutable copy of this stream. - -- - -- Used to wrap insulate eval-cycles from each other. - -- - -- @treturn IOStream - fork: => @ - - --- poll for changes. - -- - -- Called every frame by the main event loop to update internal state. - tick: => - - --- check whether this adapter requires processing. - -- - -- Must return a boolean indicating whether `Op`s that refer to this instance - -- via `Input.hot` should be notified (via `Op:tick`). May be called multiple - -- times. May be called before `tick` on the first frame after construction. - -- - -- If this is not overrided, the `EventStream` interface can be used, see - -- `EventStream.add`, `EventStream.unwrap`, and `EventStream.dirty`. - -- - -- @function dirty - -- @treturn bool whether processing is required - - __inherited: (cls) => cls.__base.__tostring = @__tostring - -{ - :IOStream -} diff --git a/core/stream/value.moon b/core/stream/value.moon deleted file mode 100644 index 68e16b0..0000000 --- a/core/stream/value.moon +++ /dev/null @@ -1,237 +0,0 @@ ----- --- Continuous stream of values. --- --- Implements the `Stream` and `AST` intefaces. --- --- @classmod ValueStream -import Stream from require 'core.stream.base' -import Result from require 'core.result' -import Error from require 'core.error' -import scope, base, registry from require 'core.cycle' - -ancestor = (klass) -> - assert klass, "cant find the ancestor of nil" - while klass.__parent - klass = klass.__parent - klass - -class ValueStream extends Stream ---- members --- @section members - - --- return whether this stream was changed in the current tick. - -- - -- @treturn bool - dirty: => @updated == registry.Registry.active!.tick - - --- update this stream's value. - -- - -- Marks this stream as dirty for the remainder of the current tick. - set: (@value) => @updated = registry.Registry.active!.tick - - --- 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 any `value` - unwrap: (type, msg) => - assert type == @type, msg or "#{@} is not a #{type}" if type - @value - - --- create a mutable copy of this stream. - -- - -- Used to insulate eval-cycles from each other. - -- - -- @treturn ValueStream - fork: => - with ValueStream @type, @value, @raw - .updated = @updated - - --- alias for `unwrap`. - __call: (...) => @unwrap ... - - --- compare two values. - -- - -- Compares two `ValueStream`s by comparing their types and their Lua values. - __eq: (other) => other.type == @type and other.value == @value - - __tostring: => - value = if @meta.name - @meta.name - else if 'table' == (type @value) and rawget @value, '__base' - @value.__name - else - tostring @value - "<#{@@__name} #{@type}: #{value}>" - - --- Stream metatype. - -- - -- @tfield string metatype - metatype: 'value' - - --- the type name of this stream. - -- - -- the following builtin typenames are used: - -- - -- - `str` - strings, `value` is a Lua string - -- - `sym` - symbols, `value` is a Lua string - -- - `num` - numbers, `value` is a Lua number - -- - `bool` - booleans, `value` is a Lua boolean - -- - `bang` - trigger signals, `value` is a Lua boolean - -- - `opdef` - `value` is an `Op` subclass - -- - `builtin` - `value` is a `Builtin` subclass - -- - `fndef` - `value` is a `FnDef` instance - -- - `scope` - `value` is a `Scope` instance - -- - -- @tfield string type - - --- the wrapped Lua value. - -- @tfield any value - - --- documentation metadata. - -- - -- an optional table containing metadata for error messages and - -- documentation. The following keys are recognized: - -- - -- - `name`: optional name - -- - `summary`: single-line description (markdown) - -- - `examples`: optional list of single-line code examples - -- - `description`: optional full-text description (markdown) - -- - -- @tfield ?table meta - ---- AST interface --- --- `ValueStream` 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' - Result value: @ - when 'sym' - Error.wrap "resolving symbol '#{@value}'", scope\get, @value - else - error "cannot evaluate #{@}" - - --- quote this literal constant. - -- - -- @treturn ValueStream self - quote: => @ - - --- stringify this literal constant. - -- - -- Throws an error if `raw` is not set. - -- - -- @treturn string the exact string this stream was parsed from - stringify: => assert @raw, "stringifying ValueStream that wasn't parsed" - - --- clone this literal constant. - -- - -- @treturn ValueStream self - clone: (prefix) => @ - ---- static functions --- @section static - - --- construct a new ValueStream. - -- - -- @classmethod - -- @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) => super type - - unescape = (str) -> str\gsub '\\([\'"\\])', '%1' - --- 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 - - --- 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) - -- @treturn ValueStream - @wrap: (val, name='(unknown)') -> - typ = switch type val - when 'number' then 'num' - when 'string' then 'str' - when 'table' - if rawget val, '__base' - -- a class - switch ancestor val - when base.Op then 'opdef' - when base.Builtin then 'builtin' - else - error "#{name}: cannot wrap class '#{val.__name}'" - elseif val.__class - -- an instance - switch ancestor val.__class - when scope.Scope then 'scope' - when base.FnDef then 'fndef' - when Stream then return val - else - error "#{name}: cannot wrap '#{val.__class.__name}' instance" - else - -- plain table - return ValueStream 'scope', scope.Scope.from_table val - else - error "#{name}: cannot wrap Lua type '#{type val}'" - - ValueStream typ, val - - --- create a constant number. - -- @tparam number num the number - -- @treturn ValueStream - @num: (num) -> ValueStream 'num', num, tostring num - - --- create a constant string. - -- @tparam string str the string - -- @treturn ValueStream - @str: (str) -> ValueStream 'str', str, "'#{str}'" - - --- create a constant symbol. - -- @tparam string sym the symbol - -- @treturn ValueStream - @sym: (sym) -> ValueStream 'sym', sym, sym - - --- create a constant boolean. - -- @tparam boolean bool the boolean - -- @treturn ValueStream - @bool: (bool) -> ValueStream 'bool', bool, tostring bool - - --- wrap and document a value. - -- - -- wraps `args.value` using `wrap`, then assigns `meta`. - -- - -- @tparam table args table with keys `value` and `meta` - -- @treturn ValueStream - @meta: (args) -> - with ValueStream.wrap args.value - .meta = args.meta if args.meta - -class LiteralValue extends ValueStream - eval: => Result value: @ - -{ - :ValueStream - :LiteralValue -} diff --git a/core/tag.moon b/core/tag.moon deleted file mode 100644 index 3c90998..0000000 --- a/core/tag.moon +++ /dev/null @@ -1,122 +0,0 @@ ----- --- Identity provider for `Cell`s and `Builtin`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 - -class DummyReg - destroy: => - - __tostring: => "" - -dummy = DummyReg! - -class Tag ---- members --- @section members - - --- obtain the registered value of the last eval-cycle. - -- - -- Obtain the value that was previously registered for this tag on the last - -- eval-cylce. - -- - -- @treturn ?any - last: => - if index = @index! - Registry.active!\last index - - --- 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 - register: (expr) => - if index = @index! - Registry.active!\register index, expr - else - Registry.active!\init @, expr - - --- 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 - if index = @index! - Registry.active!\register index, dummy, true - else - Registry.active!\init @, dummy - - assert parent, "need parent to clone!" - ClonedTag @, parent - - stringify: => if @value then "[#{@value}]" else '' - __tostring: => if @value then "#{@value}" else '?' - ---- internals for `Registry` --- @section internals - - new: (@value) => - - --- get a unique index value for this Tag. - -- - -- The index is equal to `value` for simple tags and a path-like string for - -- cloned tags. - -- - -- @treturn ?number|string - index: => @value - - --- callback to set value for blank tags. - -- @tparam number value - set: (value) => - assert not @value, "#{@} is not blank" - @value = value - ---- static functions --- @section static - - --- create a blank `Tag`. - -- - -- @treturn Tag - @blank: -> Tag! - - --- parse a `Tag` (for Lpeg parsing). - -- - -- @tparam string num the number-string - -- @treturn Tag - @parse: (num) -> Tag tonumber num - -class ClonedTag extends Tag - new: (@original, @parent) => - - index: => - orig = @original\index! - parent = @parent\index! - if orig and parent - "#{parent}.#{orig}" - - set: (value) => - assert @parent.value, "cloned tag #{@} set before parent" - @original\set value - - stringify: => error "cant stringify ClonedTag" - - __tostring: => - if @parent - "#{@parent}.#{@original}" - else - tostring @original - -{ - :Tag -} diff --git a/core/version.moon b/core/version.moon deleted file mode 100644 index 6cee1b1..0000000 --- a/core/version.moon +++ /dev/null @@ -1,17 +0,0 @@ ----- --- `alive` source code version information. --- --- @module version - ---- exports --- @table exports --- @tfield string tag the last versions git tag --- @tfield string web the repo web URL --- @tfield string repo the git repo URL --- @tfield string release the web URL of this release -{ - tag: "v0.1-rc2" - web: "https://github.com/s-ol/alivecoding" - repo: "https://github.com/s-ol/alivecoding.git" - release: "https://github.com/s-ol/alivecoding/releases/tag/v0.1-rc2" -} diff --git a/extra/docs.moon b/extra/docs.moon index 19c272b..aee7970 100644 --- a/extra/docs.moon +++ b/extra/docs.moon @@ -1,4 +1,4 @@ -import ValueStream, Scope from require 'core' +import ValueStream, Scope from require 'alv' import render, layout, autoref from require 'extra.layout' import section, h1, h2, h3, p, ul, li, a, code, r from require 'extra.dom' @@ -67,7 +67,7 @@ spit OUT, switch command h2 a "builtins", href: '#builtins' p "These definitions are automatically loaded into the global Scope of every alive session." - ul for key, val in opairs require 'core.builtin' + ul for key, val in opairs require 'alv.builtin' li render key, ValueStream.wrap val } } diff --git a/extra/layout.moon b/extra/layout.moon index e64b736..a0d8df1 100644 --- a/extra/layout.moon +++ b/extra/layout.moon @@ -1,4 +1,4 @@ -version = require 'core.version' +version = require 'alv.version' import compile from require 'discount' render_meta = (meta) -> diff --git a/init.moon b/init.moon deleted file mode 100644 index e93bc4b..0000000 --- a/init.moon +++ /dev/null @@ -1,34 +0,0 @@ -if _VERSION == 'Lua 5.1' - package.path ..= ';./?/init.lua' - package.moonpath ..= ';./?/init.moon' - - export assert - assert = (a, msg, ...) -> - if not a - error msg - a, msg, ... - --- run from CLI -import Logger from require 'logger' -import Copilot from require 'copilot' -import sleep from require 'system' - -arguments, key = {} -for a in *arg - if match = a\match '^%-%-(.*)' - key = match - arguments[key] = true - elseif key - arguments[key] = a - key = nil - else - table.insert arguments, a - -Logger.init arguments.log - -assert arguments[1], "no filename given" -copilot = Copilot arguments[1] - -while true - copilot\tick! - sleep 1 / 1000 diff --git a/lib/logic.moon b/lib/logic.moon index 27de579..62de1c1 100644 --- a/lib/logic.moon +++ b/lib/logic.moon @@ -1,4 +1,4 @@ -import Op, ValueStream, Input, Error, val from require 'core.base' +import Op, ValueStream, Input, Error, val from require 'alv.base' all_same = (first, list) -> for v in *list diff --git a/lib/math.moon b/lib/math.moon index b2f4ce0..6f5f3fb 100644 --- a/lib/math.moon +++ b/lib/math.moon @@ -1,4 +1,4 @@ -import Op, ValueStream, Error, Input, val from require 'core.base' +import Op, ValueStream, Error, Input, val from require 'alv.base' unpack or= table.unpack class ReduceOp extends Op diff --git a/lib/midi.moon b/lib/midi.moon index 2f3a109..e83b05d 100644 --- a/lib/midi.moon +++ b/lib/midi.moon @@ -1,4 +1,4 @@ -import ValueStream, EventStream, Op, Input, val, evt from require 'core.base' +import ValueStream, EventStream, Op, Input, val, evt from require 'alv.base' import input, output, inout, apply_range from require 'lib.midi.core' gate = ValueStream.meta diff --git a/lib/midi/core.moon b/lib/midi/core.moon index e520bbf..ddb1e8a 100644 --- a/lib/midi/core.moon +++ b/lib/midi/core.moon @@ -1,4 +1,4 @@ -import ValueStream, IOStream, Op, Input, Error, val from require 'core.base' +import ValueStream, IOStream, Op, Input, Error, val from require 'alv.base' import RtMidiIn, RtMidiOut, RtMidi from require 'luartmidi' bit = do diff --git a/lib/midi/launchctl.moon b/lib/midi/launchctl.moon index cbabe5a..42837f0 100644 --- a/lib/midi/launchctl.moon +++ b/lib/midi/launchctl.moon @@ -1,4 +1,4 @@ -import ValueStream, EventStream, Op, Input, val, evt from require 'core.base' +import ValueStream, EventStream, Op, Input, val, evt from require 'alv.base' import apply_range, bit from require 'lib.midi.core' import bor, lshift from bit diff --git a/lib/osc.moon b/lib/osc.moon index 2819e97..b7e25e7 100644 --- a/lib/osc.moon +++ b/lib/osc.moon @@ -1,4 +1,4 @@ -import Op, ValueStream, Input, val, evt from require 'core.base' +import Op, ValueStream, Input, val, evt from require 'alv.base' import pack from require 'osc' import dns, udp from require 'socket' diff --git a/lib/pilot.moon b/lib/pilot.moon index 00d2d3a..0d6eefb 100644 --- a/lib/pilot.moon +++ b/lib/pilot.moon @@ -1,4 +1,4 @@ -import Op, ValueStream, Input, val, evt from require 'core.base' +import Op, ValueStream, Input, val, evt from require 'alv.base' import udp from require 'socket' local conn diff --git a/lib/random.moon b/lib/random.moon index 49da2b9..8c4d096 100644 --- a/lib/random.moon +++ b/lib/random.moon @@ -1,4 +1,4 @@ -import ValueStream, Error, Op, Input, val, evt from require 'core.base' +import ValueStream, Error, Op, Input, val, evt from require 'alv.base' apply_range = (range, val) -> if range\type! == 'str' diff --git a/lib/sc.moon b/lib/sc.moon index c92d4f7..2857392 100644 --- a/lib/sc.moon +++ b/lib/sc.moon @@ -1,4 +1,4 @@ -import Op, ValueStream, Input, val, evt from require 'core.base' +import Op, ValueStream, Input, val, evt from require 'alv.base' import pack from require 'osc' import dns, udp from require 'socket' @@ -39,6 +39,45 @@ Plays the synth `synth` on the `udp/socket` `socket` whenever `trig` is live. msg = pack '/s_new', synth, -1, 0, 1, unpack ctrls socket\send msg +play_ = ValueStream.meta + meta: + name: 'play!' + summary: 'Play a SuperCollider SynthDef on events.' + examples: { '(play [socket] synth [param evt/val…])' } + description: " +Plays the synth `synth` on the `udp/socket` `socket` whenever any `evt` is live. + +- `socket` should be a `udp/socket` value. This argument can be omitted and the + value be passed as a dynamic definition in `*sock*` instead. +- `synth` is the SC synthdef name. It should be a string-value. +- `param` is the name of a synthdef parameter. It should be a string-value. +- `val` and `evt` are the parameter values to send. They should be number + streams. Incoming events will cause a note to be played, while value changes + will not." + value: class extends Op + pattern = -val['udp/socket'] + val.str + (val.str + (val.num / evt.num))\rep 0 + setup: (inputs, scope) => + { socket, synth, trig, ctrls } = pattern\match inputs + + flat = {} + for { key, value } in *ctrls + table.insert flat, Input.cold key + table.insert flat, if value\metatype! == 'event' + Input.hot value + else + Input.cold value + + super + socket: Input.cold socket or scope\get '*sock*' + synth: Input.cold synth + ctrls: flat + + tick: => + { :socket, :synth, :ctrls } = @unwrap_all! + msg = pack '/s_new', synth, -1, 0, 1, unpack ctrls + socket\send msg + { :play + 'play!': play_ } diff --git a/lib/string.moon b/lib/string.moon index 3f9b28b..faa7ab3 100644 --- a/lib/string.moon +++ b/lib/string.moon @@ -1,4 +1,4 @@ -import Op, ValueStream, Input from require 'core.base' +import Op, ValueStream, Input from require 'alv.base' str = ValueStream.meta meta: diff --git a/lib/time.moon b/lib/time.moon index 344b5fa..2589d96 100644 --- a/lib/time.moon +++ b/lib/time.moon @@ -1,7 +1,7 @@ import ValueStream, EventStream, IOStream, Error, Op, Input, val, evt -from require 'core.base' +from require 'alv.base' import monotime from require 'system' class Clock extends IOStream diff --git a/lib/util.moon b/lib/util.moon index d760a64..dc133e4 100644 --- a/lib/util.moon +++ b/lib/util.moon @@ -1,4 +1,4 @@ -import Op, ValueStream, EventStream, Input, val, evt from require 'core.base' +import Op, ValueStream, EventStream, Input, val, evt from require 'alv.base' all_same = (list) -> for v in *list[2,] diff --git a/logger.moon b/logger.moon deleted file mode 100644 index cd41cc8..0000000 --- a/logger.moon +++ /dev/null @@ -1,63 +0,0 @@ -unpack or= table.unpack - -class Logger - levels = { - debug: 0 - trace: 1 - log: 2 - warn: 3 - error: 4 - print: 5 - silent: 6 - } - - mklog = (max_level) -> - new: (level='log') => - @level = levels[level] or level - @prefix = '' - - for name, level in pairs levels - @[name] = (first, ...) => - return unless @level <= level - - where = debug.traceback '', 2 - if level == levels.error or @level == levels.debug - print @prefix .. first, ... - print where - else - print @prefix .. first, ... - - if level == levels.print - @push = (fn, ...) => fn ... - - push: (fn, ...) => - last = @prefix - @prefix ..= ' ' - - res = { xpcall fn, debug.traceback, ... } - - @prefix = last - - if ok = table.remove res, 1 - unpack res - else - error unpack res - - try: (msg, fn, ...) => - result = { xpcall fn, debug.traceback, ... } - ok = table.remove result, 1 - - if not ok - @error msg, unpack result - return - - unpack result - --- static - init: (...) -> - export L - L = Logger ... - -{ - :Logger -} -- cgit v1.2.3