Skip to content
Permalink
d1a1f28786
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
1591 lines (1300 sloc) 66.9 KB
Walker = (require 'giraphe').default
uuid = require 'uuid'
{ EventEmitter } = require 'events'
_ = require './utilities.coffee'
debugging = require './debugging.coffee'
# I'll give $US 5,000 to the person who fucking *fixes* how Node handles globals inside modules. ಠ_ಠ
{ constructify, parameterizable, delegated
, passthrough, selfify, modifier
, terminal: term } = _
{ ENV, verbosity, is_silent, colour
, emergency, alert, critical, error, warning, notice, info, debug, verbose, wtf } = debugging
# API entry-point
# ===============
module.exports =
Paws =
utilities: _
debugging: debugging
Paws.debugging.infect Paws
# Core data-types
# ===============
# The Paws object-space implements a single graph (in the computer-science sense) of homogeneous(!)
# objects. Each object, or node in that graph, is called a `Thing`; and is singly-linked² to an
# ordered list of other nodes.
#
# The first member of the metadata-links¹ on a `Thing` (referred to as the ‘noughtie’) is generally
# reserved for special use from within Paws; and thus Paws' lists are effectively one-indexed.
#
# In addition to these links to other nodes that every `Thing` has, some `Thing`s carry around
# additional information; these are implemented as additional JavaScript types, such as `Label`
# (which carries around identity, and a description in the form of a Unicode string) or `Execution`
# (which encapsulates procedure and execution-status information.)
#
# The Paws model is to consider that underlying information as ‘data’ (the actual *concerns* of a
# Paws program), and the links *between* those data as ‘metadata’; describing **the relationships
# amongst** the actual data.
#
# Although objects appear from within Paws to be ordered lists of other objects; they are often
# *treated* as ersatz key-value-dictionaries. To this purpose, a single key-value ‘pair’ is
# often represented as a list-`Thing` containing only a key `Label`, and the associated value.
#
# Each (type of) object also has a `receiver` associated with it, involved in the evaluation
# process; an `Execution` for a procedure that receives messages ‘sent’ to the object in question.
# This defines the (default) action taken when the object is, for example, the subject on the left-
# hand-side of a simple expression.
#
# The default receiver for a flavour of object depends on the additional data it carries around
# (that is, the JavaScript type of the node). For instance, the default receiver for plain `Thing`s
# (those carrying no additional data around) is an equivalent to the `::find` operation; that is, to
# treat the subject-object as a dictionary, and the message-object as a key to search that
# dictionary for. The default for any object, however, can be overridden per-object, changing how a
# given node in the graph responds to `Combination`s with other objects. (See the documentation for
# `Execution` for more exposition on the evaluation model.)
#
# ---- ---- ----
#
# 1. The links from one `Thing` to another are encoded as `Relation` objects, which are further
# annotated with the property of **‘ownership’**: an object that ‘owns’ an object below it in the
# graph is claiming that object as a component of the *overall data-structure* that the parent
# object represents. (Put simply: a series of ownership-annotated links in the datagraph describe a
# single data-structure as a subgraph thereof.)
#
# 2. Note that although Paws objects are, by default, singly-linked, each `Thing` *also* includes
# separate reverse-links to all of the `Thing`s that ‘own’ it, to facilitate responsibility
# calculations. (Although actual ownership flows only-downwards along the graph, responsibility
# existing lower on the graph can still preclude owning ancestors from being adopted; so these back-
# links are maintained on those descendants as hints that they have adopted.)
#
# ---- ---- ----
#
# Many Paws operations change the data-graph; *any* of these operations can, thus, also effectively
# mutate responsibility. Libside, this is handled by the user: ‘<operation> assumes you've taken
# responsibility for its arguments’, or ‘<operation> will return when responsibility is available.’
# This becomes a little more complicated when calling from JavaScript, however.
#
# While *everything* is effectively asynch in Paws (that is, after all, sort of the point!), most
# things happening within a reactor-tick (or in a consuming/embedding application) are
# *synchronous*. This is problematic, both because there is no way to ‘block’ an operation mutating
# responsibility until such responsibility is available, and because when these operations are
# already being called from within the reactor-tick of a particular Paws operation, we can't
# retroactively change that operation to an `Operation['adopt']`! From another perspective,
# JavaScript operations happen atomically, in a single reactor-tick, from perspective of Paws
# operations.)
#
# This is exposed and communicated in this JavaScript API by partitioning available methods into
# three categories, and prefixing the names based on their behaviour. In general,
#
# 1. *underscore-prefixed methods* like `::_set` generally do no responsibility-checking, and must
# be used after explicitly checking that the required responsibility is available (or during a
# tick on a native that has responsibility for the relevant arguments);
#
# 2. *bare methods* like `::set` or `::dedicate` are still synchronous, but will preform
# responsibility-checking in the due course of their behaviour, and throw synchronously if they
# are unable to complete their operations or need to be placed into the operation-queue;
#
# 3. and *dollar-prefixed methods* like `::$dedicate` will return a `Promise`, and preform their
# behaviour *asynchronously*, placing themselves in the Paws operation-queue if necessary
# (meaning that, if called *during* a reactor-tick, these may effectively unstage and block the
# calling operation.)
#
# In general, underscore-prefixed methods may be considered ‘private’, as the bare methods will
# behave exactly the same, but with additional reasonableness-checks; and dollar-prefixed methods
# are often essentially analogues to libside primitives.
#
# ### Method summary:
# `Thing`s are obtained via ...
# - direct creation, `new Thing`, with a list of children,
# - by `::clone`ing an existing `Thing`,
# - or, as a convenience, with a provided JavaScript template, with `.construct`, below.
#
# Their `Relation`s to children are stored in an `Array`, manipulable ...
# - canonically, as an ordered set: `::at`, `::set`, `::push`, `::pop`, `::shift`, and `::unshift`;
# - or as a pseudo-dictionary, with ordered ‘pairs’ added by `::define` and queried by `::find`.
#
# Meanwhile, the data-structure ownership amongst a structure's elements is exposed through:
# - `::own_at` (or `Relation::owned_by`), to create new membership in a data-structure;
# - `::disown_at` (or `Relation::contained_by`), to leave ownership-less links between nodes;
# - and `::is_owned_by` to query ownership relationships.
#
# > Of note, as another convenience: all methods that take a `Thing` and manipulate relationships,
# > can *also* take given a pre-constructed `Relation` indicating the desired relationship. It
# > won't be used directly, but the relationship will be imitated by the changes produced in the
# > data-graph:
# >
# > a_thing.set(1, another_thing.owned_by(a_thing))
# > # Equivalent to:
# > a_thing.set(1, another_thing)
# > a_thing.own_at(1)
Paws.Thing = Thing = parameterizable class Thing extends EventEmitter
constructor: constructify(return:@) (elements...)->
@id = uuid.v4()
@metadata = new Array
@owners = new Array
@custodians = { direct: [], inherited: [] }
@supplicants = { direct: [], inherited: [] }
@push elements... if elements.length
@metadata.unshift undefined if @_?.noughtify != no
# Constructs a generic ‘key/value’ style `Thing` from a `representation` (a JavaScript `Object`-
# hash) thereof. This convenience method expects arguments constructed as pairs of 1. any string
# (as the key, which will be converted into the `Label`), and 2. a Paws `Thing`-subclass (as the
# value.) These may be nested.
#
# > For instance, given `{foo: thing_A, bar: thing_B}`, `construct()` will product a `Thing`
# resembling the following (disregarding noughties):
#
# ((‘foo’, thing_B), (‘bar’, thing_B))
#
# The ‘pair-ish’ values are always owned by their container; as are, by default, the ‘leaf’
# objects passed in. (The latter is a behaviour configurable by `.with(own: no)`.)
#
# @option own: Construct the structure as as `own`ing newly-created sub-Things
# @option names: `rename` the constructed `Thing`s according to the key they're being assigned to
@construct: (representation)->
pairs = for key, value of representation
if _.isFunction value
value = Native.synchronous value
else unless value instanceof Thing or value instanceof Relation
value = @construct value
leave_ownership_alone = value instanceof Relation
should_own = @_?.own ? (if leave_ownership_alone then undefined else yes)
value.rename key if @_?.names
Thing.pair key, value, should_own
return Thing.with(own: yes) pairs...
# ### Common ###
# Creates a copy of the `Thing` it is called on. Alternatively, can be given an extant `Thing`
# copy this `Thing` *to*, over-writing that `Thing`'s metadata. In the process, the
# `Relation`s within this relation are themselves cloned, so that changes to the new clone's
# ownership don't affect the original.
clone: (to)->
to ?= new Thing.with(noughtify: no)()
to.name = @name unless to.name?
to.metadata = @metadata.map (rel)->
rel = rel?.clone()
rel?.from = to
rel
return to
compare: (to)-> to == this
# ### Shared, private methods ###
# Private; implements the pre-checking-and-throwing behaviour for *public* methods that
# eventually call `_add_parent_and_inherit_custodians()`. Expects a pre-constructed array.
_validate_availability_to: (custodians)->
unless @available_to custodians...
# FIXME: Add more useful debugging information
throw new ResponsibilityError(
"Attempt to add Thing held under conflicting responsibility.")
# XXX: N.B., when modifying ownership-mutation: Multiple Relations `from` and `to` the *same pair
# of Things* can exist in `@owners`, because they can exist in the `@metadata` of the
# parent, and one of them could be deleted, leaving the second.
# Private; assumes the caller has checked availability, expects a safe (unused) `Relation`.
_add_parent_and_inherit_custodians: (rel)->
@owners.push rel unless _.contains @owners, rel
rel.from._all_custodians().forEach (li)=>
@_add_custodian li
return this
# Private; does several useful things:
#
# - Remove a passed parent from the `owners` array,
# - check the *other* owners for all responsibility inherited through the removed owner,
# - and only then remove any *no-longer-reachable* `custodians`.
#
# N.B.: May only be called on a Relation *actually present as an owner* (i.e. call only on
# `rel.to`, and after checking `rel.owns`.)
_del_parent_and_inherited_custodians: (rel)->
_.pull @owners, rel
rel.from._all_custodians().forEach (li)=>
unless _.any(@owners, (owner)=> _.includes owner._all_custodians(), li )
@_del_custodian li
return this
# Private; implements the guts of dedication for internal methods.
_add_custodian: (li)->
fam = if this is li.ward then 'direct' else 'inherited'
unless _.includes @custodians[fam], li
@custodians[fam].push li
# Private; implements the guts of emancipation for internal methods.
_del_custodian: (li)->
fam = if this is li.ward then 'direct' else 'inherited'
_.pull @custodians[fam], li
# ### ‘Array-ish’ metadata manipulation ###
at: (idx)-> @metadata[idx]?.to
# Directly set the child at a particular index to the passed value.
#
# @arg {Number} idx Index on the receiver's metadata to change
# @arg {(Relation|Thing)} it Node to place at the given index
# @returns {Relation} New relation placed at idx
# @throws ResponsibilityError If the new child is to be owned by the parent, but is not available
# to one of the parent's custodian-Executions
#---
# TODO: Async `::$set`.
set: (idx, arg)->
unless arg?
return @_set idx, undefined
rel = new Relation this, arg
if rel.owns
rel.to._validate_availability_to @_all_custodians()
return @_set idx, rel
# Private; directly assigns another `Thing` to an index-specified location in the receiver's
# metadata. Assumes the caller has checked availability, expects a safe (brand-new / unused)
# `Relation`.
#
# @see set
_set: (idx, rel)->
prev = @metadata[idx]
if rel?.owns
rel.to._add_parent_and_inherit_custodians rel
if prev?.owns
prev.to._del_parent_and_inherited_custodians prev
@metadata[idx] = rel
return rel
# Append passed values to the receiver's metadata.
#
# Passed values can each be either a `Thing` (`foo.push(a_thing)`), or a `Relation` expressing
# the desired ownership of that value (`foo.push(a_thing.contained_by(foo))` or the same with
# `owned_by`, for instance). The arguments need not be homogenous.
#
# You can configure the ownership of all the passed values, wholesale, by explicitly setting the
# `own:` option to either `true` or `false`:
#
# blah.with(own: yes).push(foo, bar, baz)
#
# If the `Thing` in question is affected by responsibility conflicting with that flowing from /
# through the receiver (that is, it is both marked as owned, and held with a conflicting license
# by other `Liability`), a `ResponsibilityError` will be thrown.
#
# @arg {...(Relation|Thing)} elements Values to be appended as children
# @returns {Thing} The receiver
# @throws ResponsibilityError If one of the new children is to be owned by the parent,
# but is not available to one of the parent's custodian-Executions
# @option own {Boolean} Value with which to override the ownership of the children
# @see unshift
#---
# TODO: Async `::$push`.
# TODO: There's gotta be some shortcut-fusion / lazy-evaluation methodology to squash the
# iteration here, and the iteration in `::_push`, into one iteration-pass ...
push: (elements...)->
relations = elements.map (it)=>
if it instanceof Relation
rel = it.clone()
rel.from = this
if it instanceof Thing
rel = new Relation this, it
rel?.owns = @_?.own if @_?.own?
return rel
unless _.isEmpty (custodians = @_all_custodians())
_.forEach relations, (rel)=>
if rel?.owns
rel.to._validate_availability_to custodians
return @_push relations
# Private; directly adds other `Thing`s to the end of the receiver's metadata. Assumes the caller
# has checked availability, expects an `Array` of safe (brand-new / unused) `Relations`.
#
# @see push
_push: (relations)->
_.forEach relations, (rel)=>
if rel?.owns
rel.to._add_parent_and_inherit_custodians rel
@metadata = @metadata.concat relations
return this
# XXX: pop() and shift(), being remove-only operations, currently are purely synchronous, with no
# possibility of failure. Theoretically, though, at least libside, removal is still a
# mutating operation, and *should* require ownership.
# Remove the ordinally-last `Thing` from the receiver's metadata relations, and returns it.
#
# If the departing `Thing` in question is affected by responsibility flowing from/through the
# receiver, it will be `emancipated()`.
#
# @returns {Thing} The removed entry
pop: ->
rel = @metadata.pop()
if rel?.owns
rel.to._del_parent_and_inherited_custodians rel
return rel?.to
# Remove the ordinally-first `Thing`, after the noughty, from the receiver's metadata relations;
# and returns it.
#
# If the departing `Thing` in question is affected by responsibility flowing from/through the
# receiver, it will be `emancipated()`.
#
# @returns {Thing} The removed entry
shift: ->
noughty = @metadata.shift()
rel = @metadata.shift()
@metadata.unshift noughty
if rel?.owns
rel.to._del_parent_and_inherited_custodians rel
return rel?.to
# Prepend passed values to the receiver's metadata, immediately following the noughty.
#
# Passed values can each be either a `Thing` (`foo.unshift(a_thing)`), or a `Relation` expressing
# the desired ownership of that value (`foo.unshift(a_thing.contained_by(foo))` or the same with
# `owned_by`, for instance). The arguments need not be homogenous.
#
# You can configure the ownership of all the passed values, wholesale, by explicitly setting the
# `own:` option to either `true` or `false`:
#
# blah.with(own: yes).unshift(foo, bar, baz)
#
# If the `Thing` in question is affected by responsibility conflicting with that flowing from /
# through the receiver (that is, it is both marked as owned, and held with a conflicting license
# by other `Liability`), a `ResponsibilityError` will be thrown.
#
# @arg {...(Relation|Thing)} elements Values to be prepended as children
# @returns {Thing} The receiver
# @throws ResponsibilityError If one of the new children is to be owned by the parent,
# but is not available to one of the parent's custodian-Executions
# @option own {Boolean} Value with which to override the ownership of the children
# @see push
#---
# TODO: Async `::$unshift`.
# TODO: cf. TODO on push() re: shortcut-fusion
unshift: (elements...)->
# FIXME: DRYME, this is all a duplicate of ::push
relations = elements.map (it)=>
if it instanceof Relation
rel = it.clone()
rel.from = this
if it instanceof Thing
rel = new Relation this, it
rel?.owns = @_?.own if @_?.own?
return rel
unless _.isEmpty (custodians = @_all_custodians())
_.forEach relations, (rel)=>
if rel?.owns
rel.to._validate_availability_to custodians
return @_unshift relations
# Private; directly adds other `Things` to the beginning-save-one of the receiver's metadata.
# Assumes the caller has checked availability, expects a disposable / brand-new `Array` of safe
# (also brand-new / unused) `Relations`.
#
# @see unshift
_unshift: (relations)->
_.forEach relations, (rel)=>
if rel?.owns
rel.to._add_parent_and_inherit_custodians rel
protect_noughty = @metadata.length != 0
noughty = @metadata.shift() if protect_noughty
@metadata = relations.concat @metadata
@metadata.unshift noughty if protect_noughty
return this
# ### ‘Dictionary-ish’ metadata manipulation ###
# Convenience method to create a ‘pair-ish’ `Thing` (one with only two members, the first of
# which is a `Label` ‘key.’)
#
# The created thing will own the label, but not the value, by default. This can be overriden by
# passing a third indicating whether to `own` the value.
#
# (N.B. that this cannot fail, despite modifying ownership — the parent is newly-created, and
# thus has no custodians.)
@pair: (key, value, own)->
pair = new Thing
pair._push Label(key).owned_by pair
pair._push value.contained_by(pair, own) if value
return pair
# A convenience to append a new ‘pair’ to the end of the receiver (thus “defining” a value, if
# the receiver is seen as a pseudo-dictionary.)
#
# The pair-`Thing` itself and `Label` are always owned by the receiver; but the `value` (although
# it defaults to being not-owned) can be configured by passing a Relation:
#
# blah.define('foo', bar.owned_by(blah))
#
# Availability of the `value` will be checked, and a `ResponsibilityError` will be thrown in the
# case of a conflict. (See `::push`.)
#---
# TODO: handling undefined values
define: (key, value)->
pair = Thing.pair key, value
@push pair.owned_by this
# This implements the core algorithm of the default jux-receiver; this algorithm is very
# crucial to Paws' object system:
#
# Working through the metadata in reverse, select those items whose *first* (not the noughty; but
# subscript-one) item `compare()`s truthfully to the searched-for key. Return them in the order
# found (thus, “in reverse”), such that the latter-most item in the metadata that was found to
# match is returned as the first match. For libside purposes, only this (the very latter-most
# matching item) is used.
#
# Of note, in this implementation, we additionally test *if the matching item is a pair*. For
# most *intended* purposes, this should work fine; but it departs slightly from the spec.
# We'll see if we keep it that way.
#---
# TODO: `pair` option, can be disabled to return the 'valueish' things, instead of the pairs
# TODO: `raw` option, to return the `Relation`s, instead of the wrapped `Thing`s
find: (key)->
key = new Label(key) unless key instanceof Thing
results = @metadata.filter (rel)->
rel?.to?.isPair?() and key.compare rel.to.at 1
_.pluck(results.reverse(), 'to')
# ### Ownership ###
# FIXME: Responsibility ;_;
own_at: (idx)->
if (prev = @metadata[idx])? and not prev.owns
@metadata[idx] = prev.as_owned()
@metadata[idx].to._add_parent_and_inherit_custodians @metadata[idx]
else prev
disown_at: (idx)->
if (prev = @metadata[idx])? and prev.owns
@metadata[idx] = prev.as_contained()
@metadata[idx].to._del_parent_and_inherited_custodians prev
@metadata[idx]
else prev
# FIXME: Okay, this naming is becoming a mess.
owns_at: (idx)-> @metadata[idx]?.owns
is_owned_by: (other)->
if other instanceof Relation
_.contains @owners, other
else
_.any @owners, 'from', other
#---
# XXX: At construct-time, this depends on `Relation` being defined. Sans hoisting (WHY DID I LAY
# THIS OUT THIS WAY?), these can't be exist until init-time — see `Thing._init_walkers`,
# `Thing._init_responsibility_walkers`, etc.
_walk: walk = undefined
# This returns a flat array of all the descendants of the receiver that satisfy a condition,
# supplied by an optional callback.
#
# The callback may explicitly return `false` to indicate a descendant should be excluded; or the
# sentinel value `Thing.abortIteration` to terminate the graph-walking early.
#
# The `descendants` return-value may be *passed in* as a pre-cache; any Things already existing
# in that cache will not be touched by this function. (Tread carefully: if the data-graph is
# modified between the *creation* of `descendants`, and the re-execution of this function, then
# that cache may no longer be valid!)
_walk_descendants: walk_descendants = undefined
@abortIteration: Walker.abortIteration
@_init_walkers: ->
Thing::_walk = walk =
new Walker class: Thing, key: 'id'
, edge: { class: Relation, extract_path: 'to' }
, inspector: Paws.inspect
Thing::_walk_descendants = walk_descendants =
walk -> _.compact(@metadata)
# ### Responsibility ###
is_adopted: ->
not _.isEmpty(@custodians.direct) or not _.isEmpty(@custodians.inherited)
is_supplicated: ->
not _.isEmpty(@supplicants.direct) or not _.isEmpty(@supplicants.inherited)
_any_custodian: (f)->
_.any(@custodians.direct, f) or
_.any(@custodians.inherited, f)
_any_supplicant: (f)->
_.any(@supplicants.direct, f) or
_.any(@supplicants.inherited, f)
_all_custodians: (f)->
custodians = _(@custodians.direct).concat(@custodians.inherited).uniq()
if f?
_.all custodians, f
else
custodians.value()
_all_supplicants: (f)->
supplicants = _(@supplicants.direct).concat(@supplicants.inherited).uniq()
if f?
_.all supplicants, f
else
supplicants.value()
# Returns a walker that only walks descendants *with custodians*.
_walk_adopted: walk_adopted = undefined
@_init_responsibility_walkers: ->
Thing::_walk_adopted = walk_adopted =
walk_descendants -> @is_adopted()
# Indicates success if the passed `Execution` *already* holds the indicated license (or a greater
# one) for the receiver. (For instance, if the `Execution` holds write-license to a parent of the
# receiver, then `belongs_to(exe, 'read')` would indicate success.)
#
# If passed a `Liability` instead, this object is simply checked for the presence of that
# specific `Liability`.
#---
# TODO: Handle being passed 1. an Execution but no License at all, or 2. a Liability *and* a
# License
belongs_to: (it, license)->
return false unless @is_adopted()
if license?
if license is 'write' or license is yes
return @_any_custodian (liability)->
return true if liability.custodian is it and
liability.write() is yes
else
return @_any_custodian (liability)->
return true if liability.custodian is it
else
return _.includes(@custodians.direct, it) or
_.includes(@custodians.inherited, it)
# Private helper that checks *only* the receiver's custodians for conflicts
_directly_available_to: (liability)->
return true if @belongs_to liability.custodian, liability.write()
if liability.write()
return false if @_any_custodian (li)=> li.custodian != this
else
return false if @_any_custodian (li)=> li.custodian != this and li.write()
return true
# Determines if the receiver *can* be `dedicate`-ed to the passed `Liability`.
#
# Returns `true` if all owned-descendants of the receiver are available for adoption (i.e. have
# no conflicting responsibility); and `false` if the receiver or one of its descendants *cannot*
# be adopted (i.e. currently has some form of conflicting responsibility.)
#
# This is, of course, checked as a part of `dedicate`; so explicitly calling this method is
# usually only necessary if something being available for adoption *changes the decision of what
# to adopt*. (Or, possibly, adopting across reactor ticks?)
#---
# TODO: This *badly* needs to share a cache with `::dedicate`, as the previous implementation did
# TODO: Specify how this handles being given an *unrelated* (or blank) Liability
available_to: (liabilities...)->
liabilities = liabilities[0] if _.isArray liabilities[0]
# First, check this object itself,
return false unless _.every liabilities, (li)=> @_directly_available_to li
# Then, depth-first traverse every *owned child*
walk_result = @_walk_descendants ->
return Thing.abortIteration unless _.every liabilities, (li)=> @_directly_available_to li
return (false != walk_result)
# When passed an existing `descendants` object, this assumes you obtained that by already having
# checked their availability via `::available_to`.
#---
# FIXME: The constant `uniq`'ing is going to also be slow: need to collect that into a single
# event after any modifications? Ugh, I need `Set`. /=
_dedicate: (liability)->
unless _.includes @custodians.direct, liability
_.values(@_walk_descendants()).forEach (descendant)=>
family = if descendant is liability.ward then 'direct' else 'inherited'
descendant.custodians[family].push liability
descendant.custodians[family] = _.uniq descendant.custodians[family]
# FIXME: DOCME
# TODO: Make this accept an Execution directly, as a convenience
dedicate: (liabilities...)->
liabilities = liabilities[0] if _.isArray liabilities[0]
return false unless _.every liabilities, (li)=> @available_to li
liabilities.forEach (li)=> @_dedicate li
return true
# The inverse of `::_dedicate`, this removes an existing `Liability` from the receiver (and its
# owned-descendants.)
#
# Returns `true` if the `Liability` was successfully removed (or if the receiver didn't belong to
# it in the first place).
#
# Nota bene: This can *only* remove responsibility *from the root node of the adopted sub-graph*.
# It will return truthfully if the `Liability` on which it is called is not in the
# directly-responsible `custodians` for the receiver; this also applies if the passed
# `Liability` roots on another node! You probably want `Liability::discard`, which
# calls this method.
_emancipate: (liability)->
return true unless _.includes @custodians.direct, liability
@_walk_descendants -> @_del_custodian liability; undefined
return true
# DOCME
# XXX: At the moment, `_emancipate` cannot return false; so this isn't properly transactional. If
# there's any way for emancipation to fail, though, it's important to fix this method to
# *not actually remove the custodians* until the efficacy of the operation can be verified.
emancipate: (liabilities...)->
liabilities = liabilities[0] if _.isArray liabilities[0]
return _.all liabilities, (liability)=> @_emancipate liability
# This adds a passed `Liability` into the receiver's `supplicants` array, indicating that the
# `Execution` needs to be resumed when it will be able to successfully obtain responsibility for
# the receiver.
#
# This should only be called after `::dedicate` has been called, and has indicated failure; this
# is handled for you by ... # FIXME: DOCME
supplicate: (liability)->
@supplicants.push liability
# ### Utility / convenience ###
# TODO: Option to include the noughty
toArray: (cb)-> @metadata.slice(1).map (rel)-> (cb ? _.identity) rel?.to
# This ascertains if the receiver is “pair-shaped” — has precisely two non-metadata elements, the
# first of which is a Label, and the second of which isn't undefined.
#---
# FIXME: This almost certainly *shouldn't* exist publically at all; and especially shouldn't be
# central to the crucial `find()` algorihtm. D:
isPair: -> Boolean (@metadata.length is 3) and @metadata[1].to instanceof Label and @metadata[2]
keyish: -> @at 1
valueish: -> @at 2
# Convenience methods to create `Relation`s *to* the receiver.
contained_by: (other, own)-> new Relation other, this, own # Defaults to `no`
owned_by: (other)-> new Relation other, this, yes
rename: selfify (name)-> @name = name
# ### Initialization ###
@_init: ->
@_init_walkers()
@_init_responsibility_walkers()
# The default receiver for `Thing`s simply preforms a ‘lookup.’
Thing::receiver = new Native (params)->
caller = params.at 0
subject = params.at 1
message = params.at 2
results = subject.find message
if results[0]
caller.respond results[0].valueish()
# FIXME: Welp, this is horrible error-handling. "Print a warning and freeze forevah!!!"
else
notice "~~ No results on #{Paws.inspect subject} for #{Paws.inspect message}."
.rename 'thing✕'
# A `Label` is a type of `Thing` which encapsulates a static Unicode string, serving two purposes:
#
# 1. Unique-comparison: Two `Label`s originally created with equivalent sequences of Unicode
# codepoints will `::compare` as equal. Across codebases, `Label`s share identity, even when they
# don't share objective content / metadata relationships.
#
# That is: `foo ['bar']` will find the same object assigned with a different instance of `'bar'`.
# 2. Description: Although not designed to be used to manipulate character-data as a general
# string-type, `Label`s can be used to associate an arbitrary Unicode sequence with some other
# data, as a name or description. (Hence, ‘label.’)
#
# ---- ---- ----
#
# Due to their intented use as descriptions and comparators, the string-data associated with a
# `Label` cannot be mutated after creation; and they cannot be combined or otherwise manipulated.
# However, they *can* be `::explode`d into an ordered-list of individual codepoints, each
# represented as a new `Label`; and then manipulated in that form. These poor-man's mutable-strings
# (colloquially called ‘character soups’) are provided as a primitive form of string-manipulation.
Paws.Label = Label = class Label extends Thing
constructor: constructify(return:@) (@alien)->
if @alien instanceof Label
@alien.clone this
clone: (to)->
super (to ?= new Label)
to.alien = @alien
return to
compare: (to)->
to instanceof Label and
to.alien == @alien
# FIXME: JavaScript's `split()` doesn't handle wide-character (surrogate in UTF-16, or 4-byte in
# UTF-8) Unicode -very-well.- (at all!)
explode: ->
it = new Thing
it.push.apply it, _.map @alien.split(''), (char)-> new Label char
it
# *Programs* in Paws are a series of nested sequences-of-Paws-objects. For programs that originate
# as text (not all Paws programs do!), those `Thing`s are created at parse-time; Paws doesn't
# operate on text (or an intermediat representation thereof) in the way that many programming-
# languages do.
#
# The primary way you interact with code in Paws, is this, the `Execution`. An `Execution`, however,
# doesn't represent a simple invocable procedure, the way a `Function` might in JavaScript; instead,
# it represents the *partial completion thereof*. When you invoke a Paws `Execution`, you're not
# spawning a wholesale evaluation of that procedure, but rather *continuing* a previous evaluation.
#
# Each time you invoke an `Execution`, the script is advanced a single step to produce a new
# computational task, in the form of a ‘combination:’ a subject-object, and a message-object to be
# sent to it. The `receiver` (another `Execution`, see the above `Thing` documentation) of the
# *subject* will then be invoked in turn, and handed the message-object. (This means that each
# intentional step in a Paws program necessarily involves at *least* two effective steps; the
# invocation of the `Execution` intended, and then the invocation of the resulting subject's
# `receiver`.)
#
# Obviously, however, as Paws is an asynchronous language, *any amount* of actual work can happen
# following an instigator's invocation of an `Execution`; for instance, although the default-
# receivers are all implemented as `Native`s, completable in a single invocation as an atomic
# operation ... a custom receiver could preform any amount of work, before producing a result-value
# for the combination that caused it.
#
# Further, a crucial aspect of Paws is the ability to *branch* `Execution`-flow. This is acheived by
# `::clone`ing a partially-evaluated `Execution`. As the original procedure progresses, its
# instructions are gradually processed and discarded; meanwhile, however, an earlier clone's will
# *not* be. When so-branched, an `Execution`'s state is all duplicated to the clone, unaffected by
# changes to the original `Execution`'s position or objective-context.
#
# **Nota bene:** While clones do not share *additions* to their respective context, `locals`, the
# clone made is *shallow in nature*. This means that the two `Execution`s' `locals`
# objects share the original assignments (that is, the original pairs) created prior
# to the cloning. This further means that either branch can modify an existing
# assignment-pair on `locals` instead of `::define`ing a new, overriding pair, thus
# making the change visible to prior clones, if desired.
#
# ---- ---- ----
#
# This implementation stores the `Execution` information as three crucial elements:
#
# 1. A stack of positions in a `Script`, as `Position`s in the `instructions` array,
# 2. a further stack of the `results` of outer `instruction`s,
# 3. and its objective evaluation context, a `Thing` accessible as `locals`.
#
# The position is primarily maintained by `::advance`; diving into and climbing back out of sub-
# expressions to produce `Combination`s for the reactor. As it digs into a sub-expression, the
# position in the outer `Expression` is maintained in the `instructions` stack; while the results of
# the last `Combination` for each outer expression are correspondingly stored in `results`.
#
# **Nota anche: The `instructions` stack lists *completed* nodes; ones for which a `Combination` has
# already been generated.)**
Paws.Execution = Execution = class Execution extends Thing
constructor: constructify (@begin)->
if typeof @begin == 'function' then return Native.apply this, arguments
@begin = new Position @begin if @begin? and not (@begin instanceof Position)
if @begin
@results = [ null ]
@instructions = [ @begin ]
else
@results = [ ]
@instructions = [ ]
@pristine = yes
@locals = new Thing().rename 'locals'
@locals.define 'locals', @locals
this .define 'locals', @locals.owned_by this
@ops = new Array
@wards = new Array
return this
# ### Common ###
# This method of the `Execution` types will copy all data relevant to advancement of the
# execution to a `Execution` instance. This includes the pristine-ness (boolean), the `results`
# and `instructions` stacks (or for a `Native`, any `bits`.) A clone made thus can be advanced
# just as the original would have been, without affecting the original's position.
#
# Of note: along with all the other data copied from the old instance, the new clone will inherit
# the original `locals`. This is intentional.
#---
# TODO: nuke-API equivalent of lib-API's `branch()()`
clone: (to)->
super (to ?= new Execution)
to.pristine = @pristine
# FIXME: Remove old 'locals' from the Exec's cloned metadata?
to.locals = @locals.clone().rename 'locals'
to.define 'locals', to.locals.owned_by to
to.advancements = @advancements if @advancements?
if @instructions? and @results?
to.instructions = @instructions.slice 0
to.results = @results.slice 0
return to
# ### Operation-queue ###
# Pushes a new `Operation` onto this `Execution`'s `ops`-queue.
queue: (something)->
return @queue new Operation arguments... unless something.op?
@ops.push something
# A convenience method for pushing an 'advance' `Operation`, specifically.
respond: (response)->
@queue new Operation 'advance', arguments...
# This informs an `Execution` of the ‘result’ of the last `Combination` returned from `next`.
# This value is stored in the `results` stack, and is later used as one of the values in furhter
# `Combination`s.
register_response: (response)-> @results[0] = response
# ### Position management and advancement ###
complete:-> !this.instructions.length
# Returns the *current* `Position`; i.e. the top element of the `instructions` stack.
current:-> @instructions[0]
# Returns the next `Combination` that needs to be preformed for the advancement of this
# `Execution`. This is a mutating call, and each time it is called, it will produce a new
# (subsequent) `Combination` for this `Execution`.
#
# It usually only makes sense for this to be called after a response to the *previous*
# combination has been signaled by `register_response` (obviously unless this is the first time
# it's being advanced.) This also accepts an optional argument, the passing of which is identical
# to calling `::register_response` with that value before-hand.
#
# For combinations involving the start of a new expression, `null` will be returned as one part
# of the `Combination`; this indicates no meaningful data from the stack for that node. (The
# reactor will interpret this as an instruction to insert this `Execution`'s `locals` into that
# combo, instead.)
advance: (response)->
return undefined if @complete()
@register_response response if response?
# If we're continuing to advance a partially-completed `Execution`, ...
completed = @instructions[0]
previous_response = @results[0]
if not @pristine
# Gets the next instruction from the current sequence (via `Position#next`)
@instructions[0] =
upcoming = completed.next()
# If we've completed the current sub-expression (a sequence.), then we're going to step out
# (pop the stack.) and preform the indirected combination.
if not upcoming?
outer_current_value = @results[1]
@instructions.shift(); @results.shift()
return new Combination outer_current_value, previous_response
# Discards the last response at the current stack-level, if this is the beginning of a new
# semicolon-delimited expression.
if upcoming.index is 0
@results[0] =
previous_response = null
it = upcoming.valueOf()
# If the current node is a `Thing`, we combine it against the top of the `results`-stack
# (either another Thing, or `null` if this is the start of an expression.)
if it instanceof Thing
upcoming_value = it
return new Combination previous_response, upcoming_value
# If it's not a `Thing`, it must be a sub-expression (that is, a `parse.Sequence`).
else
upcoming = new Position it
# If we've got what appears to be an empty sub-expression, then we're dealing with the
# special-case syntax for referencing the Paws equivalent of `this`. We treat this like
# a simple embedded-`Thing` combination, except with the current `Execution` as the
# `Thing` in question:
if not upcoming.valueOf()?
return new Combination previous_response, this
# Else, the ‘upcoming’ node is a real sub-expression, and we're going to ‘dive’ into it
# (push it onto the stack.)
@instructions.unshift upcoming; @results.unshift null
# At this point, we're left at the *beginning* of a new expression. Either we'll be looking at
# a `Thing`, or a nested series of ‘immediate’ (i.e. `[[[foo ...`) sub-expressions, eventually
# ending in a `Thing` (since truly empty sub-expressions are impossible.)
@pristine = no
# If we *are* looking at another (or an arbitrary number of further) immediate
# sub-expression(s), we need to push it (all of them) onto the stack.
while (it = @instructions[0].valueOf())? and it.expressions?
@instructions.unshift new Position it; @results.unshift null
upcoming = @instructions[0]
# (another opportunity for an empty sub-expression / self-reference)
if not upcoming.valueOf()?
return new Combination previous_response, this
# At this point, through one of several paths above, we've definitely descended into a (or
# several) sub-expression(s), and are definitely looking at the first `Thing` in a new
# expression.
upcoming_value = upcoming.valueOf()
return new Combination null, upcoming_value
# ### Responsibility ###
# Given a `Liability`, this will record that responsibility into the receiver's `wards`.
#
# Note: This does not preform any verifications or availability checks; that must be handled by
# the caller; for that reason, this is usually called after `Thing::dedicate`. (You
# probably want to use `Liability::commit` instead of doing these things manually.)
accept: (liability)->
@wards.push liability unless _.contains @wards, liability
return liability
# Given a `Liability`, this will remove that responsibility from the receiver's `wards`.
#
# Note: This is usually called after `Thing::emancipate`. (You probably want to use
# `Liability::discard` instead of doing these things manually.)
abjure: (liability)->
_.pull @wards, liability
return liability
# ### Utility / convenience ###
# Creates a list-thing of the form that receiver `Execution`s expect.
@create_params: (caller, subject, message)-> new Thing.with(noughtify: no)(arguments...)
# ### Initialization ###
@_init: ->
# The `Execution` default-receiver is specified to preform a ‘call-pattern’ invocation:
# cloning the subject-`Execution`, resuming that clone, and explicitly *not* resuming the
# caller.
Execution::receiver = new Native (params)->
subject = params.at 1
message = params.at 2
subject.clone().respond message
.rename 'execution✕'
# Correspondingly to normal `Execution`s, some procedures are provided by the implementation (or
# those extending Paws with the API, such as yourself) as `Native`s. These consist of chunks of
# JavaScript code to be executed each time the faux-`Execution` is invoked.
#
# To imitate the behaviour of a natural `Execution`, `Native` procedures are advanced destructively
# each time they are invoked: a chunk of behaviour is discarded. Similarly, each of these `bits` of
# behaviour can be separately argumented with ‘results’ when invoked (again, just as a regular
# `Execution` must be parameterized on each reinvocation.) This can best be visualized as an
# explicit coroutine, with each `yield` being represented by the end of one `Function` and beginning
# of the next.
#
# To further imitate the cloning-behaviour of `Execution`s, we shallow-clone any JavaScript members
# added by the `Native`'s `bits` during their evaluation to clones of the `Native` itself.
#
# *Note:* All of the `Native`s provided by this implementation are stored in a separate project,
# `primitives`. See `primitives/infrastructure.coffee` for the majority thereof.
#
# ---- ---- ----
#
# We implement `Native`s as an array of `bits`; `Function`s that receive the Paws resumption-value
# as their sole argument. Instead of storing information on the objecive `locals`, `Native`s'
# implementations have the option of storing partial progress on the `Native` instance itself; to
# this end, `this` within the body of a `Function`-bit will be the `Native` instance being invoked.
# (Note that although the *enumerable properties* will be copied from the `Native` to a clone
# thereof, the *object-identity* will obviously have changed.)
#
# Of great use, we also provide the `.synchronous` convenience function; although most `Native`s
# imitate fully-asynchronous, coroutine-style procedures, this function can be used to construct
# faux-synchronous-style procedures that consume all of their parameters before evaluating and
# producing a result. (This expidently allows `Native` procedures to be written as single,
# synchronous `Function`s, that accept multiple arguments and `return` a single result.)
Paws.Native = Native = class Native extends Execution
constructor: constructify(return:@) (@bits...)->
delete @begin
delete @instructions
delete @results
@advancements = @bits.length
# ### Common ###
clone: (to)->
super (to ?= new Native)
_.map Object.getOwnPropertyNames(this), (key)=>
to[key] = this[key] unless to[key]?
to.bits = @bits.slice 0
return to
# ### Overrides ###
complete:-> not @bits.length
current:-> @bits[0]
# `advancing` to the next unit of work for a `Native` is substantially simpler than doing so for
# a normal `Execution`: we simply remove (and return) another body-section from `bits`.
advance: (response)->
return undefined if @complete()
@pristine = no
return @bits.shift()
# ### Utility / convenience ###
# This alternative constructor will automatically generate a series of ‘bits’ that will curry the
# appropriate number of arguments into a single, final function.
#
# Instead of having to write individual function-bits for your `Native` that collect the
# appropriate set of resumption-values into a series of “arguments” that you need for your task,
# you can use this convenience constructor for the common situation that you're treating an
# `Execution` as equivalent to a synchronous JavaScript function.
#
# ----
#
# This takes a single function, and checks the number of arguments it requires before generating
# the corresponding bits to acquire those arguments.
#
# Then, once the resultant `Native` has been resumed the appropriate number of times (plus one
# extra initial resumption with a `caller` as the value, as is standard coproductive practice in
# Paws), the synchronous JavaScript passed in as the argument here will be invoked.
#
# That invocation will provide the arguments recorded in the function's implementation, as well
# as a context-object containing the following information available on `this`:
#
# caller
# : The first resumption-value provided to the generated `Native`. Usually, itself, an
# `Execution`, in the coproductive pattern.
# execution
# : The original `this`. That is, the generated `Native` that was constructed from the function.
#
# After your function executes, if it results in a non-null return value, then the `caller`
# provided as the first response Paws-side will be resumed one final time with that as the
# corresponding response. (Hence the name of this method: it provides a ‘synchronous’ (ish)
# result after all the parameters have been asynchronously collected.)
#
# @param { function(... [Thing], this:{caller: Execution, this}): ?Thing }
# synch_body The synchronous function we'll generate an Execution to match
@synchronous = (synch_body) ->
advancements = synch_body.length + 1
# First, we construct the *middle* bits of the coproductive pattern (that is, the ones that
# handle all but the *last* actual argument the passed function requires.) These are pretty
# generic: they simply partially-apply their RV to the *last* bit (which will be defined
# below.) Thus, they participate in currying their argument into the final invocation of
# the synchronous function.
bits = new Array(advancements - 1).join().split(',').map ->
(caller, value)->
# FIXME: Pretty this up with prototype extensions. (#last, anybody?)
@bits[@bits.length - 1] = _.partial @bits[@bits.length - 1], value
caller.respond this
# Next, we construct the *first* bit, which is a special case responsible for receiving the
# `caller` (as is usually the case in the coproductive pattern.) It takes its resumption-
# value, and curries it into *every* following bit. (Notice that both the middle bits, above,
# and the concluding bit, below, save a spot for a `caller` argument.)
bits[0] = (caller)->
@bits = @bits.map (bit)=> _.partial bit, caller
caller.respond this
# Now, the complex part. The *final* bit has quite a few arguments curried into it:
#
# - Immediately (at generate-time), the locals we'll need within the body: the `Paws` API,
# and the `synch_body` we were passed. This is necessary, because we're building the body
# in a new JavaScript environment, due to the `eval`-y `Function` constructor;
# - Second (later on, throughout invocation-time), the `caller` curried in by the first bit;
# - Third, any *actual arguments* curried in by intermediate bits.
#
# In addition to these, it's got one final argument (the actual resumption-value with which
# this final bit is invoked, after all the other bits have been exhausted).
#
# These values are curred into a function we construct within the body-string below, that
# proceeds to provide the *actual* arguments to the synchronous `func`, as well as
# constructing a context-object to act as the `this` described above.
#---
# FIXME: Remove the `Paws` pass, if it's unnecessary
arg_names = ['synch_body', 'caller'].concat Array(advancements).join('_').split('')
last_bit = """
var that = { caller: caller, execution: this }
var result = synch_body.apply(that, [].slice.call(arguments, 2))
if (typeof result !== 'undefined' && result !== null) {
caller.respond(result) }
"""
bits[advancements - 1] = _.partial Function(arg_names..., last_bit), synch_body
it = new Native bits...
it.synchronous = synch_body
return it
# Supporting types
# ================
#---
# N.B.: Apeing the ES6 Set() interface
Paws.ThingSet = ThingSet = class ThingSet
constructor: constructify(return:@) (things...)->
@clear()
things.forEach (thing)=>
@add thing
size: -> Object.keys(@_store).length
# TODO: Assertions or error-handling of some sort
add: (value)->
@_store[value.id] = value
return this
clear: -> @_store = new Object
delete: (value)->
h = @has value
delete @_store[value.id]
return h
has: (value)-> @_store[value.id]?
#---
# N.B.: Hahaha, CoffeeScript is failing me even harder than usual, here.
ThingSet.prototype[Symbol.iterator] = -> {
foo: 'bar'
}
Paws.Relation = Relation = parameterizable delegated('to', Thing) class Relation
constructor: constructify(return:@) (from, to, owns)->
if from instanceof Relation
from.clone this
else if to instanceof Relation
to.clone this
@from = from
else
@from = from
@to = to
@owns = false
@owns = !!owns if owns?
# Copy the receiver `Relation` to a new `Relation` instance. (Can also overwrite the contents of
# an existing `Relation`, if passed, with this receiver's state.)
#---
# FIXME: Make truly immutable (i.e. refuse to modify once this Relation has been used in a Thing)
#
# UPDATE (June 2016): idk, *copying* Relations every time the edge is modified seems performance-
# foolish?
clone: (other)->
other ?= new Relation
other.from = @from
other.to = @to
other.owns = @owns
return other
as_contained: (own)->
it = @clone()
it.owns = own ? no
return it
as_owned: ->
it = @clone()
it.owns = yes
return it
# Provided for API-parity with `Thing`
contained_by: (other, own)-> new Relation other, @to, own ? @owns
owned_by: (other)-> new Relation other, @to, yes
# This is a an intersection-type representing the ‘responsibility’ mapping between an object, and
# the `Execution` responsible for it. It encapsulates:
#
# - the `ward` `Thing` that the responsibility is for,
# - the `custodian` `Execution` currently responsible for it,
# - and the `license`ing-status of that `Execution` (`true` for 'write'-exclusivity, `false` for
# 'read'-only.)
Paws.Liability = Liability = delegated('for', Thing) class Liability
constructor: constructify(return:@) (@custodian, @ward, license = 'read')->
@_write = license is yes or license is 'write'
write: -> @_write
read: -> not @_write
# This simply determines if two `Liability`s are precisely identical.
compare: (other)->
return true if this is other
other._write is @_write &&
other.custodian is @custodian &&
other.ward is @ward
# This is the union of `Thing::dedicate` and `Execution::accept`, indicating the acceptance of
# responsibility (represented by the receiver `Liability`) of the `custodian` `Execution` for the
# `ward` `Thing`.
#
# This returns `false` if the `Thing::dedicate`ion fails, indicating that the responsibility
# represented by the receiver conflicts with actively-held responsibility on the part of another
# `Execution` than the receiver's `custodian`.
commit: ->
return false unless @ward.dedicate this
@custodian.accept this
return true
# This is the union of `Thing::emancipate` and `Execution::abjure`, indicating the abjuration of
# responsibility (represented by the receiver `Liability`) of the `custodian` `Execution` for the
# `ward` `Thing`.
#
# This results in `Thing::_signal` (see `Thing::emancipate`), indicating to the reactor that new
# changes in responsibility may allow supplicant `Execution`s to be staged.
discard: ->
@custodian.abjure this
@ward.emancipate this
return true
# A `Combination` represents a single operation in the Paws semantic. An instance of this class
# contains the information necessary to process a pending combo (as returned by
# `Execution::next`).
Paws.Combination = Combination = class Combination
constructor: constructify (@subject, @message)->
# A `Position` records the last element of a given expression from which a `Combination` was
# generated. It also contains the information necessary to find the *next* element of that
# expression's sequence (i.e. the indices of both the element within the expression, and the
# expression within the sequence.)
#
# This class is considered immutable.
#---
# FIXME: Factor out the Script-types (Expression/Sequence) from the parser, so I can include them
# verbatim into both parser.coffee and datagraph.coffee
Paws.Position = Position = class Position
constructor: constructify(return:@) (@_sequence, @expression_index = 0, @index = 0)->
unless @_sequence?.expressions? # ... passed an Expression, not a Sequence
@_sequence = expressions: [@_sequence] # Construct a faux-Sequence
[@index, @expression_index] = [@expression_index, 0]
# FIXME: argument validation.
#unless _.isArray(@_sequence) and _.isArray(@_sequence[0])
expression: -> @_sequence.expressions[@expression_index]
valueOf: -> @_sequence.expressions[@expression_index]?.at @index
clone: ->
new Position @_sequence, @expression_index, @index
# Returns a new `Position`, representing the next element of the parent sequence needing
# combination. (If the current element is the last word of the last expression in the sequence,
# returns `undefined`.)
next: ->
if @expression().at(@index + 1)?
return new Position @_sequence, @expression_index, @index + 1
if @_sequence.expressions[@expression_index + 1]?
return new Position @_sequence, @expression_index + 1
# An `Operation` is effectively just a delayed function-invocation; every operation is a function-
# member of this class, which is evaluable once preceding operations in a given `Execution`'s queue
# are complete.
#
# `Operation` types are added with `.register`; they are written as a function executed in the
# context of the `Execution` to which they are applied, passed the `params` stored on the
# `Operation` instance in the queue.
#
# Operations are not removed from a queue (as completed) until they indicate success by returning a
# truthy value. (They shouldn't, therefore, preform mutating operations and then indicate failure!)
#
# Available operations:
#
# - `'advance'`: given a resumption-value, this will apply that value to the `Execution` in
# question, as the result of the previous `Combo` generated by it. This will advance the
# evaluation of that `Execution` by one step, generally producing the *next* `Combo`.
# - `'adopt'`: given a target object, block further operations (so, advancements) in this
# `Execution`'s queue until it is available for responsibility; then take that responsibility.
# This is acheived by returning false every time it's attempted, unless the responsibility in
# question has become available.
Paws.Operation = Operation = class Operation
constructor: constructify(return:@) (@op, @params...)->
@operations: new Object
@register: (op, func)-> @operations[op] = func
# Takes an `Execution` to preform this operation `against`, preforms the `op`, and returns a
# return-value as defined by the operation.
perform: (against)->
Operation.operations[@op].apply(against, @params)
Operation.register 'advance', (response)->
if process.env['TRACE_REACTOR']
warning ">> #{this}#{response}"
if @current() instanceof Function
body = @current().toString()
wtf term.block body, (line)-> '' + line.slice 0, -4
else
body = @current().expression().with context: 3, tag: no
.toString focus: @current().valueOf()
debug term.block body, (line)-> '' + line.slice 0, -4
if @complete()
warning ' ╰┄ complete!' if process.env['TRACE_REACTOR']
# NYI: The old `reactor.coffee` extended EventEmitter and emitted a `flushed` event when the
# global-queue was full; this was depended upon both by the `paws.js` executable and
# (much more heavily) by the `Rule` implementation to determine when something was
# “finished.”
#
# I've known for a while, though, that I need a more robust and langauge-integration
# definition /implementation thereof, so I guess this queueless-rewrite is as good a time
# as any to figure that the fuck out?
#stage.flushed() unless stage.upcoming()
return
next = @advance response
if typeof next is 'function'
next.call this, response
return true
else
if process.env['TRACE_REACTOR']
warning " ╰┈ ⇢ combo: #{next.subject} × #{next.message}"
require('assert') next.message?
subject = next.subject ? @locals
#message = next.message ? @locals # this should be impossible.
params = Execution.create_params this, subject, next.message
params.rename '<parameters>'
# FIXME: Er. What? How does `respond` play with `Reactor::queue` ... I've clearly gained some
# disunified design-plans at some point. D:
subject.receiver.clone().respond params
return true
Operation.register 'adopt', (liability)->
# XXX: Debugging NYI.
#if process.env['TRACE_REACTOR']
# warning ">> #{this} ← #{response}"
# if @current() instanceof Function
# body = @current().toString()
# wtf term.block body, (line)-> ' │ ' + line.slice 0, -4
# else
# body = @current().expression().with context: 3, tag: no
# .toString focus: @current().valueOf()
# debug term.block body, (line)-> ' │ ' + line.slice 0, -4
if @complete()
# XXX: Debugging NYI.
#warning ' ╰┄ complete!' if process.env['TRACE_REACTOR']
warning 'Completed Execution attempted to adopt o_O'
return
# This will return `false` if `Thing::dedicate` does so, which indicates that, in turn, the
# `Thing::available_to` failed.
unless succeeded = liability.commit()
liability.ward.supplicate liability
# If the dedication failed, then this operation failed as well
return succeeded
# Error types
# ===========
# This is the error thrown by synchronous, responsibility-checking methods (ones not prefixed by `_`
# or `$`) when the responsibility required for the operation isn't available / held by the
# currently-evaluating code.
Paws.ResponsibilityError = class ResponsibilityError extends Error
# Debugging output
# ================
# Convenience to call whatever string-making methods are available on the passed JavaScript value.
Paws.inspect = (object)->
object?.inspect?() or
object instanceof Thing && Thing::inspect.apply(object) or
_.node.inspect object
# Generates a short, string-ish form of the UUID to uniquely identify a given object during debug
Thing::_inspectID = ->
if @id? then @id.slice(-8) else ''
# Describes a given object, using the string-ish unique ID along with the object's `name`, if any.
Thing::_inspectNames = ->
names = []
names.push @_inspectID() if @_inspectID and (not @name or process.env['ALWAYS_ID'] or @_?.tag == no)
names.push term.bold @name if @name
names
Thing::_inspectTag = -> @constructor.__name__ or @constructor.name
# Wraps an (optional) string and prefixes it with a description of the receiving object. If called
# without content to wrap, this simply wraps all of the object's names.
Thing::_tagged = (content)->
names = (@_inspectNames or Thing::_inspectNames).call this
tag = (@_inspectTag or Thing::_inspectTag ).call this
if this instanceof Thing and @isPair()
names.push '~' + @keyish().alien
else
names.unshift(tag) if tag
content = if content then ' '+content else ''
"<#{names.join ':'}#{content}>"
Native::_inspectNames = ->
names = Thing::_inspectNames.call this
if @advancements?
calls = @advancements - @bits.length
names[names.length - 1] = names[names.length - 1] + new Array(calls).join 'ʹ'
names
# The first public-entry into the debugging code; `toString` produces a short(ish) description of
# the receiving Paws object.
Thing::toString = ->
if @_?.tag == no then @_inspectNames().join(':') else @_tagged()
# As an alternative to `toString`, one can invoke `inspect` to produce a more-lengthy description of
# some Paws objects (possibly multi-line.)
Thing::inspect = ->
@toString()
Label::_inspectNames = ->
if @name then term.bold [@name] else []
Label::toString = ->
output = "#{@alien}"
if @_?.tag == no then output else @_tagged output
# By default, this will print a serialized version of the `Execution`, with `focus` on the current
# `Thing`, and a type-tag. If explicitly invoked with `tag: true`, then the serialized content will
# be omitted; if instead with `serialize: true`, then the tag will be omitted.
#---
# FIXME: Should `Execution` and `Native` do their multi-line debugging-info-printing in `inspect`?
Execution::toString = ->
if @_?.tag != yes or @_?.serialize == yes
output = "{ #{if @begin? then @begin.toString focus: @current().valueOf() else ''} }"
if @_?.tag == no or @_?.serialize == yes then output else @_tagged output
# For `Native`s, we instead print only the tag by default, *if it is named*. If a name is absent, we
# print the serialized implementation as well.
Native::toString = ->
if @_?.serialize != no and (not @name or @_?.tag == no or @_?.serialize == yes)
output = if @synchronous
synch = @synchronous.toString()
synch.slice synch.indexOf("{"), synch.lastIndexOf("}") + 1
else
bodies = @bits.map (bit)->
bit = bit.toString()
bit.slice bit.indexOf("{"), bit.lastIndexOf("}") + 1
bodies.join ' -> '
if @_?.tag == no then output else @_tagged output
# Initialization
# ==============
Thing._init()
Execution._init()
debug "++ Datagraph available"