aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2020-03-07 20:58:30 +0000
committers-ol <s-ol@users.noreply.github.com>2020-03-07 20:58:51 +0000
commit6a7a2ddaca798f3cccac394d1fb9f317cbe90de6 (patch)
tree4e49e53057b39f75813311ede13f87d238061fe6 /core
parentspellcheck (diff)
downloadalive-6a7a2ddaca798f3cccac394d1fb9f317cbe90de6.tar.gz
alive-6a7a2ddaca798f3cccac394d1fb9f317cbe90de6.zip
add ldoc documentation
Diffstat (limited to 'core')
-rw-r--r--core/base.moon298
-rw-r--r--core/base/action.moon95
-rw-r--r--core/base/fndef.moon22
-rw-r--r--core/base/init.moon35
-rw-r--r--core/base/input.moon131
-rw-r--r--core/base/io.moon34
-rw-r--r--core/base/match.moon93
-rw-r--r--core/base/op.moon132
-rw-r--r--core/cell.moon121
-rw-r--r--core/config.ld4
-rw-r--r--core/init.moon27
-rw-r--r--core/invoke.moon4
-rw-r--r--core/parsing.moon19
-rw-r--r--core/pattern.moon65
-rw-r--r--core/registry.moon35
-rw-r--r--core/result.moon56
-rw-r--r--core/scope.moon46
-rw-r--r--core/tag.moon63
-rw-r--r--core/value.moon115
-rw-r--r--core/version.moon14
20 files changed, 939 insertions, 470 deletions
diff --git a/core/base.moon b/core/base.moon
deleted file mode 100644
index 7962545..0000000
--- a/core/base.moon
+++ /dev/null
@@ -1,298 +0,0 @@
--- base definitions for extensions
-import Value from require 'core.value'
-import Result from require 'core.result'
-import match from require 'core.pattern'
-
-unpack or= table.unpack
-
--- an incoming side-effect adapter, polled by the main event loop to pump
--- events into the dataflow graph.
---
--- subclasses must implement this interface:
---
--- :new() - construct a new instance
---
--- must prepare the instance for :dirty().
---
--- :tick() - poll for changes
---
--- called every frame by the event loop to update internal state.
---
--- :dirty() - whether this adapter requires processing
---
--- must return a boolean indicating whether `Op`s that refer to this instance
--- via `IOInput` should be notified (via `Op:tick()`). May be called multiple
--- multiple times. May be called before :tick() on the first frame after
--- construction.
---
-class IO
- -- called in the main event loop
- tick: =>
-
- -- whether a tree update is necessary
- dirty: =>
-
--- a persistent expression Operator
---
--- subclasses must implement this interface:
---
--- :new() - construct a new instance
---
--- the super-constructor can be used to construct a `Value` instance in @out.
---
--- :setup(inputs, scope) - parse arguments and patch self
---
--- called once every eval-cycle. `inputs` is a list of `Result`s that are the
--- argument to this op. The `inputs` have to be wrapped in `Input` instances
--- to define update behaviour. Use `match` to parse them, then delegate to
--- super to patch the `Input` instances.
---
--- :tick(setup) - handle incoming events and update @out
---
--- called once per frame if any inputs are dirty. Some `Input`s (like
--- `ValueInput`) have special behaviour immediately after :setup(). You can
--- detect this using the `setup` parameter, which is true the first time
--- :tick() is called after :setup(). :tick() is not called immediately after
--- :setup() if no `@inputs` are dirty. Update @out here.
---
--- :destroy() - called when the Op is destroyed
---
--- .out - a `Value` instance representing this Op's computed output value.
---
--- @out must be set to a `Value` instance once :setup() finishes. @out must
--- not change type, be removed or replaced outside of :new() and :setup().
--- @out should have a value assigned via :set() or the `Value` constructor
--- once :tick(true) is called. If @out's value is not initialized in :new()
--- or :setup(), the implementation must make sure :tick(true) is called at
--- least on the first eval-cycle the Op goes through, e.g. by using a
--- `ValueInput`.
---
-class Op
--- super-implementations for extensions
- -- if `type` is passed, an output stream is instantiated.
- -- if `init` is passed, the stream is initialized to that Lua value.
- -- it is okay not to use this and create the output stream in :setup() if the
- -- type is not known at this time.
- new: (type, init) =>
- if type
- @out = Value type, init
-
- -- setups previous @inputs, if any, with the new inputs, and writes them to
- -- `@inputs`. The `inputs` table can be nested with string or integer keys,
- -- but all leaf-entries must be `Input` instances. It must not contain loops
- -- or instances of other classes.
- setup: do
- do_setup = (old, cur) ->
- for k, cur_val in pairs cur
- old_val = old and old[k]
-
- -- are these inputs or nested tables?
- cur_plain = cur_val and not cur_val.__class
- old_plain = old_val and not old_val.__class
-
- if cur_plain and old_plain
- -- both are tables, recurse
- do_setup old_val, cur_val
- elseif not (cur_plain or old_plain)
- -- both are streams (or nil), setup them
- cur_val\setup old_val
-
- (inputs) =>
- old_inputs = @inputs
- @inputs = inputs
- do_setup old_inputs, @inputs
-
- tick: =>
- destroy: =>
-
--- utilities
- -- iterate over the (potentially nested) inputs table
- all_inputs: do
- do_yield = (table) ->
- for k, v in pairs table
- if v.__class
- coroutine.yield v
- else
- do_yield v
-
- => coroutine.wrap -> do_yield @inputs
-
- unwrap_all: do
- do_unwrap = (value) ->
- if value.__class
- value\unwrap!
- else
- {k, do_unwrap v for k,v in pairs value}
-
- => do_unwrap @inputs
-
- assert_types: (...) =>
- num = select '#', ...
- assert #@inputs >= num, "argument count mismatch"
- @assert_first_types ...
-
- assert_first_types: (...) =>
- num = select '#', ...
- for i = 1, num
- expect = select i, ...
- assert @inputs[i].type == expect, "expected argument #{i} of #{@} to be of type #{expect} but found #{@inputs[i]}"
-
--- static
- __tostring: => "<op: #{@@__name}>"
- __inherited: (cls) => cls.__base.__tostring = @__tostring
-
--- a builtin / special form / cell-evaluation strategy.
---
--- responsible for quoting/evaluating subexpressions, instantiating and patching
--- Ops updating the current Scope, etc. See core.builtin and core.invoke for
--- many examples.
-class Action
- -- head: the (:eval'd) head of the Cell to evaluate (a Const)
- -- tag: the Tag of the expression to evaluate
- new: (head, @tag) =>
- @patch head
-
- -- * eval args
- -- * perform scope effects
- -- * patch nested exprs
- -- * return runtime-tree value
- eval: (scope, tail) => error "not implemented"
-
- -- free resources
- destroy: =>
-
- -- update this instance for :eval() with new head
- -- if :patch() returns false, this instance is :destroy'ed and recreated
- -- instead must *not* return false when called after :new()
- -- only considered if Action types match
- patch: (head) =>
- if head == @head
- true
-
- @head = head
-
--- static
- -- find & patch the action for the expression with Tag 'tag' if it exists,
- -- and is compatible with the new Cell contents, otherwise instantiate it.
- -- register the action with the tag, evaluate it and return the Result
- @eval_cell: (scope, tag, head, tail) =>
- last = tag\last!
- compatible = last and
- (last.__class == @) and
- (last\patch head) and
- last
-
- L\trace if compatible
- "reusing #{last} for #{tag} <#{@__name} #{head}>"
- else if last
- "replacing #{last} with new #{tag} <#{@__name} #{head}>"
- else
- "initializing #{tag} <#{@__name} #{head}>"
-
- action = if compatible
- tag\keep compatible
- compatible
- else
- last\destroy! if last
- with next = @ head, tag
- tag\replace next
-
- action\eval scope, tail
-
- __tostring: => "<#{@@__name} #{@head}>"
- __inherited: (cls) => cls.__base.__tostring = @__tostring
-
--- an ALV function definition
---
--- when called, expands its body with params bound to the fn arguments
--- (see core.invoke.fn-invoke)
-class FnDef
- -- params: sequence of (:quote'd) symbols, each naming a function parameter
- -- body: (:quote'd) expression the function evaluates to
- -- scope: the lexical scope the function was defined in (closure)
- new: (@params, @body, @scope) =>
-
- __tostring: =>
- "(fn (#{table.concat [p\stringify! for p in *@params], ' '}) ...)"
-
--- an update scheduling policy for `Op`.
---
--- subclasses must implement this interface:
---
--- :new(value) - create an instance
---
--- `value` is either a `Value` or a `Result` instance and should be unwrapped.
---
--- :setup(prev) - copy state from old instance
---
--- called by `Op:setup()` with another `Input` instance or `nil` once this instance is
--- registered. Must prepare this instance for :dirty().
---
---- :dirty() - whether this input requires processing
---
--- must return a boolean indicating whether `Op`s that refer to this instance
--- should be notified (via `Op:tick()`).
---
--- :finish_setup() - leave setup state
---
--- called after the Op has completed (or skipped) its first `Op:tick()` after
--- `Op:setup()`. Must prepare this instance for dataflow operation.
---
-class Input
- new: (value) =>
- assert value, "nil passed to Input: #{value}"
- @stream = switch value.__class
- when Result
- assert value.value, "Input from result without value!"
- when Value
- value
- else
- error "Input from unknown value: #{value}"
-
- setup: (previous) =>
-
- finish_setup: =>
- dirty: => @stream\dirty!
- unwrap: => @stream\unwrap!
- type: => @stream.type
-
- __call: => @stream\unwrap!
- __tostring: => "#{@@__name}:#{@stream}"
- __inherited: (cls) =>
- cls.__base.__call = @__call
- cls.__base.__tostring = @__tostring
-
--- Never marked dirty. Use this for input streams that are only read when
--- another input fires.
-class ColdInput extends Input
- dirty: => false
-
--- Marked dirty for the setup-tick if old and new stream differ in current
--- value. This is the most common `Input` strategy. Should be used whenever a
--- value denotes state.
-class ValueInput extends Input
- setup: (old) => @dirty_setup = not old or @stream != old.stream
- finish_setup: => @dirty_setup = false
- dirty: => @dirty_setup or @stream\dirty!
-
--- Only marked dirty if the input stream itself is dirty. Should be used
--- whenever a value denotes a momentary event or impulse.
-class EventInput extends Input
-
--- Marked dirty when an IO object is dirty. Must be used for IO values.
-class IOInput extends Input
- impure: true
- dirty: => @stream\unwrap!\dirty!
-
-{
- :IO
- :Op
- :Action
- :FnDef
-
- :ValueInput, :EventInput, :IOInput, :ColdInput
-
- -- redundant exports, to keep anything an extension might need in one import
- :Value, :Result
- :match
-}
diff --git a/core/base/action.moon b/core/base/action.moon
new file mode 100644
index 0000000..0b9dc77
--- /dev/null
+++ b/core/base/action.moon
@@ -0,0 +1,95 @@
+----
+-- Builtin / Special Form / `Cell`-evaluation Strategy.
+--
+-- Responsible for quoting/evaluating subexpressions, instantiating and patching
+-- `Op`s, updating the current `Scope`, etc.
+-- See `builtin` and `invoke` for examples.
+--
+-- @classmod Action
+
+class Action
+--- Action interface.
+--
+-- methods that have to be implemented by `Action` implementations.
+-- @section interface
+
+ --- create a new instance
+ -- @tparam Value head the (`\eval`d) head of the Cell to evaluate
+ -- @tparam Tag tag the Tag of the expression to evaluate
+ new: (head, @tag) =>
+ @patch head
+
+ --- perform the actual evaluation
+ --
+ -- Implementations should:
+ --
+ -- - eval or quote `tail` values
+ -- - perform scope effects
+ -- - wrap all child-results
+ --
+ -- @tparam Scope scope the active scope
+ -- @tparam {AST,...} tail the arguments to this expression
+ -- @treturn Result the result of this evaluation
+ eval: (scope, tail) => error "not implemented"
+
+ --- free resources
+ destroy: =>
+
+ --- attempt to update this instance with a new `@head` prior to `\eval`.
+ --
+ -- If `\patch` returns `false`, this instance is `\destroy`ed and recreated.
+ -- Must *not* return `false` when called immediately after `\new`.
+ -- Only considered if Action types of old and new expression match.
+ --
+ -- @tparam AST head the new head value
+ -- @treturn bool whether patching was successful
+ patch: (head) =>
+ if head == @head
+ true
+
+ @head = head
+
+--- static functions
+-- @section static
+
+ --- get-or-update an `Action` for a given tag, then evaluate it.
+ --
+ -- Find the action for the expression with `Tag` `tag` if it exists,
+ -- and is compatible with the new `head`, otherwise instantiate one.
+ -- Register the `Action` with `tag`, evaluate it and return the `Result`.
+ --
+ -- @tparam Scope scope the active scope
+ -- @tparam Tag tag the tag of the `Cell` being evaluated
+ -- @tparam Value head the (`\eval`d) head of the `Cell` being evaluated
+ -- @tparam {Ast,...} tail the raw AST parameters to the `Cell` being evaluated
+ -- @treturn Result the result of evaluation
+ eval_cell: (scope, tag, head, tail) =>
+ last = tag\last!
+ compatible = last and
+ (last.__class == @) and
+ (last\patch head) and
+ last
+
+ L\trace if compatible
+ "reusing #{last} for #{tag} <#{@__name} #{head}>"
+ else if last
+ "replacing #{last} with new #{tag} <#{@__name} #{head}>"
+ else
+ "initializing #{tag} <#{@__name} #{head}>"
+
+ action = if compatible
+ tag\keep compatible
+ compatible
+ else
+ last\destroy! if last
+ with next = @ head, tag
+ tag\replace next
+
+ action\eval scope, tail
+
+ __tostring: => "<#{@@__name} #{@head}>"
+ __inherited: (cls) => cls.__base.__tostring = @__tostring
+
+{
+ :Action
+}
diff --git a/core/base/fndef.moon b/core/base/fndef.moon
new file mode 100644
index 0000000..b6c284b
--- /dev/null
+++ b/core/base/fndef.moon
@@ -0,0 +1,22 @@
+----
+-- `alive` user-function definition (`fndef`).
+--
+-- When called, expands to its body with params bound to the fn arguments (see
+-- `invoke.fn_invoke`).
+--
+-- @classmod FnDef
+
+class FnDef
+ --- create a new instance
+ --
+ -- @tparam {Value,...} params (`\quote`d) naming the function parameters
+ -- @tparam AST body (`\quote`d) expression the function evaluates to
+ -- @tparam Scope scope the lexical scope the function was defined in (closure)
+ new: (@params, @body, @scope) =>
+
+ __tostring: =>
+ "(fn (#{table.concat [p\stringify! for p in *@params], ' '}) ...)"
+
+{
+ :FnDef
+}
diff --git a/core/base/init.moon b/core/base/init.moon
new file mode 100644
index 0000000..64b34f4
--- /dev/null
+++ b/core/base/init.moon
@@ -0,0 +1,35 @@
+----
+-- Base definitions for extensions.
+--
+-- This module exports the following classes that extension modules may need:
+--
+-- @module base
+-- @see IO
+-- @see Op
+-- @see Action
+-- @see FnDef
+-- @see Input
+-- @see match
+-- @see Value
+-- @see Result
+
+import IO from require 'core.base.io'
+import Op from require 'core.base.op'
+import Action from require 'core.base.action'
+import FnDef from require 'core.base.fndef'
+import Input from require 'core.base.input'
+import match from require 'core.base.match'
+import Value from require 'core.value'
+import Result from require 'core.result'
+
+{
+ :IO
+ :Op
+ :Action
+ :FnDef
+ :Input
+ :match
+
+ -- redundant exports, to keep anything an extension might need in one import
+ :Value, :Result
+}
diff --git a/core/base/input.moon b/core/base/input.moon
new file mode 100644
index 0000000..14c812e
--- /dev/null
+++ b/core/base/input.moon
@@ -0,0 +1,131 @@
+----
+-- Update scheduling policy for `Op` arguments.
+--
+-- @classmod Input
+import Value from require 'core.value'
+import Result from require 'core.result'
+
+local ColdInput, ValueInput, EventInput, IOInput
+
+class Input
+--- Input interface.
+--
+-- Methods that have to be implemented by `Input` implementations.
+-- @section interface
+
+ --- create an instance (optional).
+ --
+ -- `value` is either a `Value` or a `Result` instance and should be
+ -- unwrapped and assigned to `@stream`.
+ --
+ -- @function new
+ -- @tparam Value|Result value
+ new: (value) =>
+ assert value, "nil passed to Input: #{value}"
+ @stream = switch value.__class
+ when Result
+ assert value.value, "Input from result without value!"
+ when Value
+ value
+ else
+ error "Input from unknown value: #{value}"
+
+ --- copy state from old instance (optional).
+ --
+ -- called by `Op``\setup` with another `Input` instance or `nil` once this instance is
+ -- registered. Must prepare this instance for `\dirty`.
+ --
+ -- May enter a 'setup state' that is exited using `\finish_setup`.
+ --
+ -- @function setup
+ -- @tparam `Input?` prev previous `Input` intance or nil
+ setup: (prev) =>
+
+ --- whether this input requires processing (optional).
+ --
+ -- must return a boolean indicating whether `Op`s that refer to this instance
+ -- should be notified (via `Op``\tick`). If not overwritten, delegates to
+ -- `@stream\dirty`.
+ --
+ -- @treturn bool whether processing is necessary
+ dirty: => @stream\dirty!
+
+ --- leave setup state (optional).
+ --
+ -- called after the `Op` has completed (or skipped) its first `Op``\tick` after
+ -- `Op``\setup`. Must prepare this instance for dataflow operation.
+ finish_setup: =>
+
+ --- unwrap to Lua value (optional).
+ --
+ -- @treturn any the raw Lua value
+ unwrap: => @stream\unwrap!
+
+ --- return the type name of this `Input` (optional).
+ type: => @stream.type
+
+--- methods.
+--
+-- @section methods
+
+ --- alias for `\unwrap`.
+ __call: => @stream\unwrap!
+
+ __tostring: => "#{@@__name}:#{@stream}"
+ __inherited: (cls) =>
+ cls.__base.__call = @__call
+ cls.__base.__tostring = @__tostring
+
+--- constructors
+-- @section constructors
+
+ --- Create a `ColdInput`.
+ --
+ -- Never marked dirty. Use this for input streams that are only read when
+ -- another `Input` is dirty.
+ --
+ -- @tparam Value|Result value
+ cold: (value) -> ColdInput value
+
+ --- Create a `ValueInput`.
+ --
+ -- Marked dirty for the eval-tick if old and new `Value` differ. This is the
+ -- most common `Input` strategy. Should be used whenever a
+ -- value denotes state.
+ --
+ -- @tparam Value|Result value
+ value: (value) -> ValueInput value
+
+ --- Create an `EventInput`.
+ --
+ -- Only marked dirty if the `Value` itself is dirty. Should be used whenever
+ -- an `Input` denotes a momentary event or impulse.
+ --
+ -- @tparam Value|Result value
+ event: (value) -> EventInput value
+
+ --- Create an `IOInput`.
+ --
+ -- Marked dirty only when an `IO` is dirty. Must be used only for `Value`s
+ -- which `\unwrap` to `IO` instances.
+ --
+ -- @tparam Value|Result value
+ io: (value) -> IOInput value
+
+class ColdInput extends Input
+ dirty: => false
+
+class ValueInput extends Input
+ setup: (old) => @dirty_setup = not old or @stream != old.stream
+ finish_setup: => @dirty_setup = false
+ dirty: => @dirty_setup or @stream\dirty!
+
+class EventInput extends Input
+
+class IOInput extends Input
+ impure: true
+ dirty: => @stream\unwrap!\dirty!
+
+{
+ :Input
+}
diff --git a/core/base/io.moon b/core/base/io.moon
new file mode 100644
index 0000000..e7399e8
--- /dev/null
+++ b/core/base/io.moon
@@ -0,0 +1,34 @@
+----
+-- Incoming side-effect adapter, polled by the main event loop to pump
+-- events into the dataflow graph.
+--
+-- @classmod IO
+
+class IO
+--- IO interface.
+--
+-- methods that have to be implemented by `IO` implementations.
+-- @section interface
+
+ --- construct a new instance.
+ --
+ -- Must prepare the instance for `\dirty` to be called.
+ new: =>
+
+ --- poll for changes.
+ --
+ -- Called every frame by the main event loop to update internal state.
+ tick: =>
+
+ --- check whether this adapter requires processing.
+ --
+ -- Must return a boolean indicating whether `Op`s that refer to this instance
+ -- via `Input``.io` should be notified (via `Op``\tick`). May be called multiple
+ -- times. May be called before `\tick` on the first frame after construction.
+ --
+ -- @treturn bool whether processing is required
+ dirty: =>
+
+{
+ :IO
+}
diff --git a/core/base/match.moon b/core/base/match.moon
new file mode 100644
index 0000000..a20bcdc
--- /dev/null
+++ b/core/base/match.moon
@@ -0,0 +1,93 @@
+----
+-- Utilities for matching `Result` types.
+--
+-- @module match
+unpack or= table.unpack
+
+class Pattern
+ new: (opts) =>
+ if 'string' == type opts
+ splat, const, type, opt = opts\match '^(%*?)(=?)([%w%-%_%/]+)(%??)$'
+ assert type, "couldn't parse type pattern '#{opts}'"
+ opts = {
+ :type
+ splat: splat == '*'
+ const: const == '='
+ opt: opt == '?'
+ }
+
+ @type = opts.type
+ @const = opts.const
+ @opt = opts.opt
+ @splat = opts.splat
+
+ matches: (result) =>
+ return false unless result
+
+ if @const
+ return false unless result\is_const!
+
+ if not result.value
+ return @type == 'nil'
+
+ return true if @type == 'any'
+
+ result.value.type == @type
+
+ match: (results) =>
+ if @splat
+ matched = while @matches results[1]
+ table.remove results, 1
+
+ assert @opt or #matched > 0, "expected at least one argument for spread"
+ matched
+ else
+ matches = @matches results[1]
+ assert @opt or matches, "couldn't match argument #{results[1]} as #{@}"
+ if matches then table.remove results, 1
+
+ __tostring: =>
+ str = @type
+ str = '*' .. str if @splat
+ str = '=' .. str if @const
+ str = str .. '?' if @opt
+ str
+
+--- match inputs to a argument type definition.
+--
+-- `pattern` is a string of type entries. Every type entry can be like this:
+--
+-- - `any` - matches one `Result` and returns it.
+-- - `typename` - matches one `Result` of type `typename` and returns it.
+-- - `=typename` - matches one eval-time const `Result`s of type `typname` and
+-- returns it.
+-- - `typename?` - matches what `typename` would match, if possible (greedy).
+-- Otherwise returns `nil`.
+-- - `*typename` - matches as many `typename` `Result`s as possible (greedy).
+-- Throws if there isn't at least one such `Result`. Returns a sequence of
+-- `Result`s.
+-- - `*typename?` - like `*typename`, except it also matches zero `Result`s.
+-- - `*=typename`, `=typename?` and `*=typename?` behave as expected.
+--
+-- Except for `typename?` and `*typename?`, all entries throw if they cannot
+-- match the next `Result` in `inputs`.
+--
+-- Throws if there are leftover `inputs` after matching all of `pattern`.
+--
+-- @tparam string pattern the argument type definition
+-- @tparam {Result,...} inputs the list of inputs
+-- @treturn {Result|{Result,...},...} the inputs as matched against `pattern`
+match = (pattern, inputs) ->
+ patterns = while pattern
+ pat, rest = pattern\match '^([^ ]+) (.*)$'
+ pat = pattern unless pat
+ pattern = rest
+ Pattern pat
+ values = [p\match inputs for p in *patterns]
+ assert #inputs == 0, "#{#inputs} extra arguments given!"
+ values
+
+{
+ :Pattern
+ :match
+}
diff --git a/core/base/op.moon b/core/base/op.moon
new file mode 100644
index 0000000..6d1ec98
--- /dev/null
+++ b/core/base/op.moon
@@ -0,0 +1,132 @@
+----
+-- Persistent expression Operator.
+--
+-- @classmod Op
+import Value from require 'core.value'
+
+class Op
+--- Op interface.
+--
+-- methods that have to be implemented by `Op` implementations.
+-- @section interface
+
+ --- construct a new instance.
+ --
+ -- The super-constructor can be used to construct a `Value` instance in `@out`.
+ --
+ -- @function new
+
+ --- parse arguments and patch self.
+ --
+ -- Called once every eval-cycle. `inputs` is a list of `Result`s that are the
+ -- argument to this op. The `inputs` have to be wrapped in `Input` instances
+ -- to define update behaviour. Use `base.match` to parse them, then delegate to
+ -- `super\setup` to patch the `Input` instances.
+ --
+ -- @function setup
+ -- @tparam inputs [`Result`] a sequence of `Result`s
+ -- @tparam scope Scope the active scope
+
+ --- handle incoming events and update `@out` (optional).
+ --
+ -- Called once per frame if any `Input`s are dirty. Some `Input`s (like
+ -- `ValueInput`) have special behaviour immediately after `\setup`, that can
+ -- cause them to become dirty at eval-time. In this case, an eval-time tick
+ -- is executed. You can detect this using the `setup` parameter.
+ --
+ -- `\tick` is called after `\setup`. `tick` is not called immediately after
+ -- `\setup` if no `@inputs` are dirty. Update `@out` here.
+ --
+ -- @tparam bool setup whether this is an eval-time tick
+ tick: =>
+
+ --- called when the Op is destroyed (optional).
+ destroy: =>
+
+ --- a `Value` instance representing this Op's computed output value.
+ --
+ -- Must be set to a `Value` instance once `\setup` finishes. Must not change
+ -- type, be removed or replaced outside of `\new` and `\setup`. Should have a
+ -- value assigned via `\set` or the `Value` constructor once `\tick` is
+ -- called the first time. If `@out`'s value is not initialized in `\new`
+ -- or `\setup`, the implementation must make sure `\tick(true)` is called at
+ -- least on the first eval-cycle the Op goes through, e.g. by using a
+ -- `ValueInput`.
+ --
+ -- @tfield Value out
+
+--- implementation utilities.
+--
+-- super-methods and utilities for use by implementations.
+-- @section super
+
+ --- if `type` is passed, an output stream is instantiated.
+ -- if `init` is passed, the stream is initialized to that Lua value.
+ -- it is okay not to use this and create the output stream in :setup() if the
+ -- type is not known at this time.
+ --
+ -- @tparam[opt] string type the type-name for `@out`
+ -- @tparam[optchain] any init the initial value for `@out`
+ new: (type, init) =>
+ if type
+ @out = Value type, init
+
+ --- setup previous `@inputs`, if any, with the new inputs, and write them to
+ -- `@inputs`. The `inputs` table can be nested with string or integer keys,
+ -- but all leaf-entries must be `Input` instances. It must not contain loops
+ -- or instances of other classes.
+ --
+ -- @tparam table inputs table of `Input`s
+ setup: do
+ do_setup = (old, cur) ->
+ for k, cur_val in pairs cur
+ old_val = old and old[k]
+
+ -- are these inputs or nested tables?
+ cur_plain = cur_val and not cur_val.__class
+ old_plain = old_val and not old_val.__class
+
+ if cur_plain and old_plain
+ -- both are tables, recurse
+ do_setup old_val, cur_val
+ elseif not (cur_plain or old_plain)
+ -- both are streams (or nil), setup them
+ cur_val\setup old_val
+
+ (inputs) =>
+ old_inputs = @inputs
+ @inputs = inputs
+ do_setup old_inputs, @inputs
+
+ --- yield all `Input`s from the (potentially nested) inputs table
+ --
+ -- @treturn iterator iterator over all `Input`s
+ all_inputs: do
+ do_yield = (table) ->
+ for k, v in pairs table
+ if v.__class
+ coroutine.yield v
+ else
+ do_yield v
+
+ => coroutine.wrap -> do_yield @inputs
+
+ --- `\unwrap` all `Input`s in `@inputs` and return a table with the same
+ -- shape.
+ --
+ -- @treturn table the values of all `Input`s
+ unwrap_all: do
+ do_unwrap = (value) ->
+ if value.__class
+ value\unwrap!
+ else
+ {k, do_unwrap v for k,v in pairs value}
+
+ => do_unwrap @inputs
+
+ __tostring: => "<op: #{@@__name}>"
+ __inherited: (cls) => cls.__base.__tostring = @__tostring
+
+{
+ :Op
+}
diff --git a/core/cell.moon b/core/cell.moon
index 850a9db..4d46b64 100644
--- a/core/cell.moon
+++ b/core/cell.moon
@@ -1,12 +1,60 @@
--- ALV Cell type
+----
+-- S-Expression Cell, implements the `AST` interface.
+--
+-- Consists of a head expression and any number of tail expressions (both `AST`
+-- nodes), a `Tag`, and optionally the internal whitespace as parsed.
+--
+-- @classmod Cell
import Value from require 'core.value'
import op_invoke, fn_invoke from require 'core.invoke'
import Tag from require 'core.tag'
--- An S-Expression with a head expression and any number of tail expressions,
--- an optional tag, and optionally the internal whitespace as parsed.
+local RootCell
+
class Cell
--- AST interface
+--- methods
+-- @section methods
+
+ -- tag: the parsed Tag
+ -- children: sequence of child AST Nodes
+ -- white: optional sequence of whitespace segments ([0 .. #@children])
+ new: (@tag=Tag.blank!, @children, @white) =>
+ if not @white
+ @white = [' ' for i=1,#@children]
+ @white[0] = ''
+
+ assert #@white == #@children, "mismatched whitespace length"
+
+ --- get the head of the cell.
+ --
+ -- @treturn AST
+ head: => @children[1]
+
+ --- get the tail of the cell.
+ --
+ -- @treturn {AST,...}
+ tail: => [c for c in *@children[2,]]
+
+ __tostring: => @stringify 2
+
+--- AST interface
+--
+-- `Cell` implements the `AST` interface.
+-- @section ast
+
+ --- evaluate this Cell.
+ --
+ -- `\eval`uates the head of the expression, and finds the appropriate
+ -- `Action` to invoke:
+ --
+ -- - if head is an `opdef`, use `invoke.op_invoke`
+ -- - if head is a `fndef`, use `invoke.fn_invoke`
+ -- - if head is a `builtin`, unwrap it
+ --
+ -- then calls `\eval_cell` on the `Action`.
+ --
+ -- @tparam Scope scope the scope to evaluate in
+ -- @treturn Result the evaluation result
eval: (scope) =>
head = assert @head!, "cannot evaluate expr without head"
head = (head\eval scope)\const!
@@ -24,19 +72,36 @@ class Cell
Action\eval_cell scope, @tag, head, @tail!
- -- quoting a Cell recursively quotes children, but preserves identity. This
- -- means that a quoted Cell may only be 'used' once. Use :clone() otherwise.
+ --- quote this Cell, preserving its identity.
+ --
+ -- Recursively quotes children, but preserves identity (i.e, shares the
+ -- `Tag`). A quoted Cell may only be 'used' once. If you want to `\eval` a
+ -- `Cell` multiple times, use `\clone`.
+ --
+ -- @treturn Cell quoted
quote: (scope) =>
children = [child\quote scope for child in *@children]
Cell @tag, children, @white
+ --- create a clone with its own identity.
+ --
-- creates a clone of this Cell with its own identity by prepending a `parent`
- -- tag and cloning all child expressoins recursively.
+ -- to `@tag` and cloning all child expressoins recursively.
+ --
+ -- @treturn Cell clone
clone: (parent) =>
tag = @tag\clone parent
children = [child\clone parent for child in *@children]
Cell tag, children, @white
+ --- stringify this Cell.
+ --
+ -- if `depth` is passed, does not faithfully recreate the original string but
+ -- rather create useful debug output.
+ --
+ -- @tparam[opt] int depth the maximum depth, defaults to infinite
+ -- @treturn string the exact string this Cell was parsed from, unless `@tag`
+ -- changed
stringify: (depth=-1) =>
buf = ''
buf ..= if depth > 0 then '' else @white[0]
@@ -54,22 +119,8 @@ class Cell
'(' .. tag .. buf .. ')'
--- internal
- -- tag: the parsed Tag
- -- children: sequence of child AST Nodes
- -- white: optional sequence of whitespace segments ([0 .. #@children])
- new: (@tag=Tag.blank!, @children, @white) =>
- if not @white
- @white = [' ' for i=1,#@children]
- @white[0] = ''
-
- assert #@white == #@children, "mismatched whitespace length"
-
- head: => @children[1]
- tail: => [c for c in *@children[2,]]
-
--- static
- __tostring: => @stringify 2
+--- static functions
+-- @section static
parse_args = (tag, parts) ->
if not parts
@@ -82,13 +133,26 @@ class Cell
white[i/2] = parts[i+1]
tag, children, white
- @parse: (...) =>
+ --- parse a Cell (for parsing with Lpeg).
+ --
+ -- @tparam[opt] Tag tag
+ -- @tparam table parts
+ -- @treturn Cell
+ parse: (...) =>
tag, children, white = parse_args ...
@ tag, children, white
--- A parenthesis-less Cell (root of an ALV document).
---
--- has an implicit head of 'do'.
+ --- parse a root Cell (for parsing with Lpeg).
+ --
+ -- Root-Cells are at the root of an ALV document.
+ -- They have an implicit head of 'do' and a `[0]` tag.
+ --
+ -- @tparam table parts
+ -- @treturn Cell
+ parse_root: (parts) =>
+ RootCell.parse RootCell, (Tag\parse '0'), parts
+
+-- @type RootCell
class RootCell extends Cell
head: => Value.sym 'do'
tail: => @children
@@ -103,9 +167,6 @@ class RootCell extends Cell
buf
- @parse: (...) =>
- @__parent.parse @, (Tag\parse '0'), ...
-
{
:Cell
:RootCell
diff --git a/core/config.ld b/core/config.ld
new file mode 100644
index 0000000..1c07337
--- /dev/null
+++ b/core/config.ld
@@ -0,0 +1,4 @@
+project = 'alive copilot'
+title = 'alive copilot reference'
+format = 'discount'
+dir = 'docs/copilot'
diff --git a/core/init.moon b/core/init.moon
index 7122e0f..c4e8ffd 100644
--- a/core/init.moon
+++ b/core/init.moon
@@ -1,3 +1,17 @@
+----
+-- `alive` public API.
+--
+-- @see Value
+-- @see Result
+-- @see Cell
+-- @see Scope
+-- @see Registry
+-- @see Tag
+-- @field globals
+-- @field parse
+-- @field eval
+--
+-- @module init
L or= setmetatable {}, __index: => ->
import Value from require 'core.value'
@@ -6,7 +20,7 @@ import Scope from require 'core.scope'
import Registry from require 'core.registry'
import Tag from require 'core.tag'
-import Cell, RootCell from require 'core.cell'
+import Cell from require 'core.cell'
import cell, program from require 'core.parsing'
with require 'core.cycle'
@@ -14,6 +28,17 @@ with require 'core.cycle'
globals = Scope.from_table require 'core.builtin'
+--- exports
+-- @table exports
+-- @tfield Value Value
+-- @tfield Result Result
+-- @tfield Cell Cell
+-- @tfield RootCell RootCell
+-- @tfield Scope Scope
+-- @tfield Registry Registry
+-- @tfield Tag Tag
+-- @tfield Scope globals global definitons
+-- @tfield parse function to turn a `string` into a root `Cell`
{
:Value, :Result
:Cell, :RootCell
diff --git a/core/invoke.moon b/core/invoke.moon
index 7ff16dc..69ce881 100644
--- a/core/invoke.moon
+++ b/core/invoke.moon
@@ -1,3 +1,7 @@
+----
+-- Builtins for invoking `Op`s and `FnDef`s.
+--
+-- @module invoke
import Value from require 'core.value'
import Result from require 'core.result'
import Action from require 'core.base'
diff --git a/core/parsing.moon b/core/parsing.moon
index 8a99d89..911f473 100644
--- a/core/parsing.moon
+++ b/core/parsing.moon
@@ -1,5 +1,9 @@
+----
+-- Lpeg Grammar for parsing `alive` code.
+--
+-- @module parsing
import Value from require 'core.value'
-import Cell, RootCell from require 'core.cell'
+import Cell from require 'core.cell'
import Tag from require 'core.tag'
import R, S, P, V, C, Ct from require 'lpeg'
@@ -35,7 +39,7 @@ tag = (P '[') * (digit^1 / Tag\parse) * (P ']')
cell = (P '(') * tag^-1 * (V 'explist') * (P ')') / Cell\parse
root = P {
- (V 'explist') / RootCell\parse
+ (V 'explist') / Cell\parse_root
:expr, :explist, :cell
}
@@ -46,6 +50,17 @@ cell = P {
program = root * -1
+--- exports
+-- @table exports
+-- @tfield pattern comment
+-- @tfield pattern space
+-- @tfield pattern atom
+-- @tfield pattern expr
+-- @tfield pattern explist
+-- @tfield pattern explist
+-- @tfield pattern cell
+-- @tfield pattern root
+-- @tfield pattern program the main parsing entrypoint
{
:comment
:space
diff --git a/core/pattern.moon b/core/pattern.moon
deleted file mode 100644
index 13497af..0000000
--- a/core/pattern.moon
+++ /dev/null
@@ -1,65 +0,0 @@
-unpack or= table.unpack
-
-class Pattern
- new: (opts) =>
- if 'string' == type opts
- splat, const, type, opt = opts\match '^(%*?)(=?)([%w%-%_%/]+)(%??)$'
- assert type, "couldn't parse type pattern '#{opts}'"
- opts = {
- :type
- splat: splat == '*'
- const: const == '='
- opt: opt == '?'
- }
-
- @type = opts.type
- @const = opts.const
- @opt = opts.opt
- @splat = opts.splat
-
- matches: (result) =>
- return false unless result
-
- if @const
- return false unless result\is_const!
-
- if not result.value
- return @type == 'nil'
-
- return true if @type == 'any'
-
- result.value.type == @type
-
- match: (results) =>
- if @splat
- matched = while @matches results[1]
- table.remove results, 1
-
- assert @opt or #matched > 0, "expected at least one argument for spread"
- matched
- else
- matches = @matches results[1]
- assert @opt or matches, "couldn't match argument #{results[1]} as #{@}"
- if matches then table.remove results, 1
-
- __tostring: =>
- str = @type
- str = '*' .. str if @splat
- str = '=' .. str if @const
- str = str .. '?' if @opt
- str
-
-match = (pattern, results) ->
- patterns = while pattern
- pat, rest = pattern\match '^([^ ]+) (.*)$'
- pat = pattern unless pat
- pattern = rest
- Pattern pat
- values = [p\match results for p in *patterns]
- assert #results == 0, "#{#results} extra arguments given!"
- values
-
-{
- :Pattern
- :match
-}
diff --git a/core/registry.moon b/core/registry.moon
index fca7e5d..edce9bd 100644
--- a/core/registry.moon
+++ b/core/registry.moon
@@ -1,14 +1,12 @@
+----
+-- `Tag` Registry.
+--
+-- @classmod Registry
+
import Value from require 'core.value'
import Result from require 'core.result'
class Registry
- new: () =>
- @map = {}
- @io = {}
-
- @tick = 0
- @kr = Result value: Value.bool true
-
-- methods for Tag
last: (index) => @last_map[index]
@@ -24,8 +22,23 @@ class Registry
active: -> assert Registry.active_registry, "no active Registry!"
--- public methods
+ next_tag: => #@map + 1
+
+--- methods
+-- @section methods
+ --- create an instance.
+ new: =>
+ @map = {}
+ @io = {}
+
+ @tick = 0
+ @kr = Result value: Value.bool true
+
+ --- wrap a function with an eval-cycle.
+ --
+ -- @tparam function fn
+ -- @treturn function `fn` wrapped with eval-cycle logic
wrap_eval: (fn) => (...) ->
@grab!
@last_map, @map, @pending = @map, {}, {}
@@ -47,6 +60,10 @@ class Registry
@release!
+ --- wrap a function with a tick.
+ --
+ -- @tparam function fn
+ -- @treturn function `fn` wrapped with tick logic
wrap_tick: (fn) => (...) ->
@grab!
@tick += 1
@@ -66,8 +83,6 @@ class Registry
assert @ == @@active_registry, "not the active registry!"
@@active_registry, @prev = @prev, nil
- next_tag: => #@map + 1
-
class SimpleRegistry extends Registry
new: =>
@cnt = 1
diff --git a/core/result.moon b/core/result.moon
index 7fda8e5..9f0bce7 100644
--- a/core/result.moon
+++ b/core/result.moon
@@ -1,16 +1,26 @@
+----
+-- Result of evaluating an expression.
+--
+-- Contains (all optional):
+--
+-- - `@value`: a `Value`
+-- - `@op`: an `Op` (to update)
+-- - `@children`: a lsit of child `Results` (from subexpressions)
+-- - `@side_inputs`: cached list of all `Inputs` affecting any `Op` in this
+-- subtree
+--
+-- `Result`s form a tree that controls execution order and message passing
+-- between `Op`s.
+--
+-- @classmod Result
import base from require 'core.cycle'
--- Result of evaluating an expression
--- carries (all optional):
--- - a Value
--- - an Op (to update)
--- - children (results of subexpressions that were evaluated)
--- - cached list of all Dispatchers affecting all Ops in the subtree
---
--- Results form a tree that controls execution order and message passing
--- between Ops.
class Result
- -- params: table with optional keys op, value, children
+--- methods
+-- @section methods
+
+ --- create a new Result.
+ -- @param params table with optional keys op, value, children. default: {}
new: (params={}) =>
@value = params.value
@op = params.op
@@ -28,35 +38,38 @@ class Result
if input.impure or not is_child[input.stream]
@side_inputs[input.stream] = input
+ --- return whether this Result's value is const.
is_const: => not next @side_inputs
- -- asserts value-constness and returns the value
+ --- assert value-constness and returns the value.
+ -- @tparam[opt] string msg the error message to throw
const: (msg) =>
assert not (next @side_inputs), msg or "eval-time const expected"
@value
- -- asserts a value exists and returns its type
+ --- assert this result has a value, returns its type.
type: =>
assert @value, "Result with value expected"
@value.type
- -- create a value-copy of this result that has the same impulses but without
- -- affecting the original's update logic
+ --- create a copy of this result with value-copy semantics.
+ -- the copy has the same @value and @side_inputs, but will not update
+ -- anything on \tick.
make_ref: =>
with Result value: @value
.side_inputs = @side_inputs
- -- tick all IO instances that are effecting this (sub) tree
- -- should be called once per frame on the root, right before tick
+ --- tick all IO instances that are effecting this (sub)tree.
+ -- should be called once per frame on the root, right before tick.
tick_io: =>
for stream, input in pairs @side_inputs
if input.__class == base.IOInput
io = input!
io\tick!
- -- in depth-first order, tick all Ops who have dirty Stream inputs or impulses
+ --- in depth-first order, tick all Ops which have dirty Inputs.
--
- -- short-circuits if there are no dirty Streams in the entire subtree
+ -- short-circuits if there are no dirty Inputs in the entire subtree
tick: =>
any_dirty = false
for stream, input in pairs @side_inputs
@@ -64,15 +77,15 @@ class Result
any_dirty = true
break
- -- early-out if no streams are dirty in this whole subtree
+ -- early-out if no Inputs are dirty in this whole subtree
return unless any_dirty
for child in *@children
child\tick!
if @op
- -- we have to check self_dirty here, because streams from child
- -- expressions might have changed
+ -- we have to check self_dirty here, because Inputs from children may
+ -- have become dirty due to \tick
self_dirty = false
for stream in @op\all_inputs!
if stream\dirty!
@@ -84,7 +97,6 @@ class Result
@op\tick!
--- static
__tostring: =>
buf = "<result=#{@value}"
buf ..= " #{@op}" if @op
diff --git a/core/scope.moon b/core/scope.moon
index 4ed95d1..c61a446 100644
--- a/core/scope.moon
+++ b/core/scope.moon
@@ -1,14 +1,36 @@
+----
+-- Mapping from `sym`s to `Result`s.
+--
+-- @classmod Scope
import Value from require 'core.value'
import Result from require 'core.result'
class Scope
+--- methods
+-- @section methods
+
+ --- create an instance.
+ --
+ -- @tparam[opt] Scope parent a parent this scope inherits definitions from
+ -- @tparam[opt] Scope dynamic_parent a parent scope that should be checked for
+ -- dynamic definitions
new: (@parent, @dynamic_parent) =>
@values = {}
+ --- set a Lua value in the scope.
+ --
+ -- wraps `val` in a `Value` and `Result` before calling `\set`.
+ --
+ -- @tparam string key
+ -- @tparam any val
set_raw: (key, val) =>
value = Value.wrap val, key
@values[key] = Result :value
+ --- set a symbol to a `Result`.
+ --
+ -- @tparam string key
+ -- @tparam Result val
set: (key, val) =>
L\trace "setting #{key} = #{val} in #{@}"
assert val.__class == Result, "expected #{key}=#{val} to be Result"
@@ -20,6 +42,11 @@ class Scope
parent or= @parent
return parent and L\push parent\get, key
+ --- resolve a key in this Scope.
+ --
+ -- @tparam string key the key to resolve
+ -- @tparam[opt] string prefix a prefix (for internal use with nested scopes)
+ -- @treturn ?Result the value of the definition that was found, or `nil`
get: (key, prefix='') =>
L\debug "checking for #{key} in #{@}"
if val = @values[key]
@@ -35,11 +62,26 @@ class Scope
assert child and child.value.type == 'scope', "#{start} is not a scope (looking for #{key})"
child.value\unwrap!\get rest, "#{prefix}#{start}/"
+ --- copy definitions from another scope.
+ --
+ -- copies all definitions from `other`. Does not copy inherited definitions.
+ --
+ -- @tparam Scope other
use: (other) =>
L\trace "using defs from #{other} in #{@}"
for k, v in pairs other.values
@values[k] = v
+--- static functions
+-- @section static
+
+ --- converts a Lua table to a Scope.
+ --
+ -- `tbl` may contain more tables (or `Scope`s).
+ -- Uses `Value``.wrap` on the values recursively.
+ --
+ -- @tparam table tbl the table to convert
+ -- @treturn Scope
from_table: (tbl) ->
with Scope!
for k, v in pairs tbl
@@ -64,4 +106,6 @@ class Scope
buf ..= ">"
buf
-:Scope
+{
+ :Scope
+}
diff --git a/core/tag.moon b/core/tag.moon
index f38d36a..5e6de24 100644
--- a/core/tag.moon
+++ b/core/tag.moon
@@ -1,3 +1,12 @@
+----
+-- Identity provider for `Cell`s and `Action`s.
+--
+-- Tags are one of:
+-- - 'blank' (`[?]`, to be auto-assigned by the Copilot)
+-- - literal (`[1]`)
+-- - cloned (`[X.Y]`, obtained by cloning Y with parent X)
+--
+-- @classmod Tag
import Registry from require 'core.registry'
local ClonedTag
@@ -10,30 +19,47 @@ class DummyReg
dummy = DummyReg!
class Tag
- -- obtain the value that was previously registered (using keep or replace) for
- -- this tag
+--- methods
+-- @section methods
+
+ --- obtain the registered value of the last eval-cycle.
+ --
+ -- Obtain the value that was previously registered (using `\keep` or
+ -- `\replace`) for this tag on the last eval-cylce.
+ --
+ -- @treturn ?any
last: =>
if index = @index!
Registry.active!\last index
- -- assert that `expr` is the value that was previously registered for this
- -- tag, and keep it for the current eval cycle.
- -- fails for blank tags.
+ --- keep using a the value from the last eval-cycle.
+ --
+ -- Assert that `expr` is the value that was previously registered for this
+ -- tag, and keep it for the current eval cycle. Fails for blank tags.
+ --
+ -- @tparam any expr the value to register
keep: (expr) =>
index = assert @index!
assert expr == Registry.active!\last index
Registry.active!\replace index, expr
- -- register `expr` for this tag for the current eval cycle.
- -- registers blank tags.
+ --- register `expr` for this tag for the current eval cycle.
+ --
+ -- Will mark blank tags for auto-assignment at the end of the eval cycle.
+ --
+ -- @tparam any expr the value to register
replace: (expr) =>
if index = @index!
Registry.active!\replace index, expr
else
Registry.active!\init @, expr
- -- create a copy of this tag scoped to a `parent` tag.
- -- registers blank tags.
+ --- create a copy of this tag scoped to a `parent` tag.
+ --
+ -- Will mark blank tags for auto-assignment at the end of the eval cycle.
+ --
+ -- @tparam Tag parent the parent tag
+ -- @treturn Tag the cloned tag
clone: (parent) =>
-- ensure this tag is registered for the current eval cycle,
-- even if it is blank and has no associated value
@@ -45,6 +71,9 @@ class Tag
assert parent, "need parent to clone!"
ClonedTag @, parent
+ stringify: => if @value then "[#{@value}]" else ''
+ __tostring: => if @value then "#{@value}" else '?'
+
-- internal
new: (@value) =>
@@ -56,13 +85,19 @@ class Tag
assert not @value, "#{@} is not blank"
@value = value
--- static
+--- static functions
+-- @section static
- @blank: -> Tag!
- @parse: (num) => @ tonumber num
+ --- create a blank `Tag`.
+ --
+ -- @treturn Tag
+ blank: -> Tag!
- stringify: => if @value then "[#{@value}]" else ''
- __tostring: => if @value then "#{@value}" else '?'
+ --- parse a `Tag` (for Lpeg parsing).
+ --
+ -- @tparam string num the number-string
+ -- @treturn Tag
+ parse: (num) => @ tonumber num
class ClonedTag extends Tag
new: (@original, @parent) =>
diff --git a/core/value.moon b/core/value.moon
index 26771ea..c6ed42d 100644
--- a/core/value.moon
+++ b/core/value.moon
@@ -1,3 +1,7 @@
+----
+-- `alive` Value(stream), implements the `AST` inteface.
+--
+-- @classmod Value
import Result from require 'core.result'
import scope, base, registry from require 'core.cycle'
@@ -7,26 +11,63 @@ ancestor = (klass) ->
klass = klass.__parent
klass
--- ALV Type wrapper
class Value
- -- @type - type name.
- -- builtin types: * literals: sym, num, bool
- -- * scope, opdef, fndef, builtin
- -- @value - Lua value - access through :unwrap()
+--- methods
+-- @section methods
+
+ --- construct a new Value.
+ --
+ -- @tparam string type the type name
+ -- @tparam any value the Lua value to be accessed through \unwrap
+ -- @tparam string raw the raw string that resulted in this value. Used by `parsing`.
new: (@type, @value, @raw) =>
- @updated = nil
+ --- return whether this Value was changed in the current tick.
+ --
+ -- @treturn bool
dirty: => @updated == registry.Registry.active!.tick
+ --- update this Value.
+ --
+ -- Marks this Value as dirty for the remainder of the current tick.
set: (@value) => @updated = registry.Registry.active!.tick
- -- unwrap to the Lua type
- -- asserts @type == type, msg if given
+ --- unwrap to the Lua type.
+ --
+ -- Asserts `@type == type` if `type` is given.
+ --
+ -- @tparam[opt] string type the type to check for
+ -- @tparam[optchain] string msg message to throw if type don't match
+ -- @treturn @value
unwrap: (type, msg) =>
assert type == @type, msg or "#{@} is not a #{type}" if type
@value
--- AST interface
+ --- alias for `\unwrap`.
+ __call: (...) => @unwrap ...
+
+ --- compare two values.
+ --
+ -- Compares two `Value`s by comparing their types and their Lua values.
+ __eq: (other) => other.type == @type and other.value == @value
+
+ __tostring: =>
+ value = if 'table' == (type @value) and rawget @value, '__base' then @value.__name else @value
+ "<#{@@__name} #{@type}: #{value}>"
+
+--- AST interface
+--
+-- `Value` implements the `AST` interface.
+-- @section ast
+
+ --- evaluate this literal constant.
+ --
+ -- Throws an error if `@type` is not a literal (`num`, `str` or `sym`).
+ -- Returns an eval-time const result for `num` and `str`.
+ -- Resolves `sym`s in `scope` and returns a reference to them.
+ --
+ -- @tparam Scope scope the scope to evaluate in
+ -- @treturn Result the evaluation result
eval: (scope) =>
switch @type
when 'num', 'str'
@@ -36,24 +77,33 @@ class Value
else
error "cannot evaluate #{@}"
+ --- quote this literal constant.
+ --
+ -- @treturn Value self
quote: => @
+ --- stringify this literal constant.
+ --
+ -- Throws an error if `@raw` is not set.
+ --
+ -- @treturn string the exact string this Value was parsed from
stringify: => assert @raw, "stringifying Value that wasn't parsed"
+ --- clone this literal constant.
+ --
+ -- @treturn Value self
clone: (prefix) => @
- -- in case of doubt:
- -- clone: (prefix) => Value @type, @value, @raw
--- static
- __tostring: =>
- value = if 'table' == (type @value) and rawget @value, '__base' then @value.__name else @value
- "<#{@@__name} #{@type}: #{value}>"
- __call: (...) => @unwrap ...
- __eq: (other) => other.type == @type and other.value == @value
-
- -- wrap a Lua type
- @wrap: (val, name='(unknown)') ->
+--- static functions
+-- @section static
+ --- wrap a Lua value.
+ --
+ -- Attempts to guess the type and wrap a Lua value.
+ --
+ -- @tparam any val the value to wrap
+ -- @tparam[opt] string name the name of this value (for error logging)
+ wrap: (val, name='(unknown)') ->
typ = switch type val
when 'number' then 'num'
when 'string' then 'str'
@@ -83,16 +133,31 @@ class Value
Value typ, val
unescape = (str) -> str\gsub '\\([\'"\\])', '%1'
- @parse: (type, sep) =>
+ --- create a capture-function (for parsing with Lpeg).
+ --
+ -- @tparam string type the type name (one of `num`, `sym` or `str`)
+ -- @tparam string sep the seperator char (only for `str`)
+ parse: (type, sep) =>
switch type
when 'num' then (match) -> @ 'num', (tonumber match), match
when 'sym' then (match) -> @ 'sym', match, match
when 'str' then (match) -> @ 'str', (unescape match), sep .. match .. sep
- @num: (num) -> Value 'num', num, tostring num
- @str: (str) -> Value 'str', str, "'#{str}'"
- @sym: (sym) -> Value 'sym', sym, sym
- @bool: (bool) -> Value 'bool', bool, tostring bool
+ --- create a constant number.
+ -- @tparam number num the number
+ num: (num) -> Value 'num', num, tostring num
+
+ --- create a constant string.
+ -- @tparam string str the string
+ str: (str) -> Value 'str', str, "'#{str}'"
+
+ --- create a constant symbol.
+ -- @tparam string sym the symbol
+ sym: (sym) -> Value 'sym', sym, sym
+
+ --- create a constant boolean.
+ -- @tparam boolean bool the boolean
+ bool: (bool) -> Value 'bool', bool, tostring bool
{
:Value
diff --git a/core/version.moon b/core/version.moon
index 029dd54..c33a8a1 100644
--- a/core/version.moon
+++ b/core/version.moon
@@ -1,5 +1,15 @@
+----
+-- `alive` source code version information
+--
+-- @module version
+
+--- exports
+-- @table exports
+-- @tfield string tag the last versions git tag
+-- @tfield string rev_short the short git revision hash
+-- @tfield string rev_long the full git revision hash
{
tag: "v0.0"
- rev_short: "4742960"
- rev_long: "4742960c455251f306bf271d50f69724b8beba04"
+ rev_short: "891c138"
+ rev_long: "891c138c8a8a4c9269f3eb70f424b5c1ea3898b0"
}