aboutsummaryrefslogtreecommitdiffstats
path: root/core/stream
diff options
context:
space:
mode:
Diffstat (limited to 'core/stream')
-rw-r--r--core/stream/base.moon79
-rw-r--r--core/stream/event.moon92
-rw-r--r--core/stream/init.moon18
-rw-r--r--core/stream/io.moon48
-rw-r--r--core/stream/value.moon223
5 files changed, 460 insertions, 0 deletions
diff --git a/core/stream/base.moon b/core/stream/base.moon
new file mode 100644
index 0000000..ee4ee17
--- /dev/null
+++ b/core/stream/base.moon
@@ -0,0 +1,79 @@
+----
+-- 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 an `Action` 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
+
+ __tostring: =>
+ value = if @meta.name
+ @meta.name
+ else if 'table' == (type @value) and rawget @value, '__base'
+ @value.__name
+ else
+ tostring @value
+ "<#{@@__name} #{@type}: #{value}>"
+
+ __inherited: (cls) => cls.__base.__tostring = @__tostring
+
+--- 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
new file mode 100644
index 0000000..5dbf846
--- /dev/null
+++ b/core/stream/event.moon
@@ -0,0 +1,92 @@
+----
+-- 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, {}
+
+ --- 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: => EventStream @type
+
+ --- alias for `unwrap`.
+ __call: (...) => @unwrap ...
+
+ --- 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 an `Action` 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
new file mode 100644
index 0000000..3fff136
--- /dev/null
+++ b/core/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 '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
new file mode 100644
index 0000000..fcaeb9f
--- /dev/null
+++ b/core/stream/io.moon
@@ -0,0 +1,48 @@
+----
+-- 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
+
+ --- 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.
+ --
+ -- 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
new file mode 100644
index 0000000..0220f06
--- /dev/null
+++ b/core/stream/value.moon
@@ -0,0 +1,223 @@
+----
+-- 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
+
+ --- 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 an `Action` 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.Action 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
+}