The languages that Tridash can interface to, and the foreign function interface itself, depend on the compilation target (the compiler backend).
The current version of Tridash has two compilation targets
JavaScript, and 32-bit WebAssembly (wasm32
).
To be able to call an externally defined (defined outside Tridash
code) function from Tridash, a meta-node without a definition has to
be declared. This is achieved using the /external
special operator.
See Section 3.4, “External Meta-Nodes” for the operator’s syntax. Once an
external meta-node is declared, it can be used, in Tridash code, as
though it is a regular meta-node.
The external meta-node has to be linked to an externally defined
function, which implements the functionality of the meta-node, for
each backend. This is achieved via backend specific attributes. For
the JavaScript and WebAssembly backends the attributes are
js-name
and wasm-name
, respectively.
In order to access the value of a node from outside Tridash code, the
node must be given a public identifier. This is done by setting the
node’s public-name
attribute to a string identifier.
Example: Setting Node Public Identifier.
# Set public identifier of `x` to `node_x` /attribute(x, public-name, "node_x")
Node x
is now accessible outside Tridash code by the node_x
identifier.
Currently, it is not checked whether multiple nodes are given the same public identifier. |
A Tridash application is compiled to a single runtime module object. When targeting the JavaScript or WebAssembly backends, this is a JavaScript object with at least the following property:
nodes
The module object also provides the following member function:
set_values(values)
Set the values of multiple nodes simultaneously.
values
is an array with each element being an array of the form
[node, value]
where node
is the node’s runtime node object and
value
is the value to which the node should be set.
The runtime node object provides three methods:
get_value()
set_value(value)
value
.
watch(f)
f
whenever the value of the node
changes. The function is passed the new value of the node as its only
argument.
In order to be able to set the value of a node, it must be
marked as an input node, by setting its |
Unless a node is an input node or an output node, that is
a node with no observers, there is no guarantee that a runtime node
object is created for it, even if it is given a public identifier, due
to node coalescing. To ensure that a runtime node object is created
for a node, for which a public identifier is assigned, its
|
This section details the foreign function interface of the JavaScript backend.
Most Tridash primitive value types correspond directly to JavaScript primitives:
The remaining value types are represented by instances of classes in
the runtime library, which is contained in the Tridash
module object.
Characters are represented by instances of the Tridash.Char
class,
which has one property:
|
A string containing just the character itself. |
Lists are either represented by JavaScript arrays or an internal linked list node class.
The Tridash.head
and Tridash.tail
functions return the head and
tail of a list respectively. These functions will return a type
error failure value if the argument is not a linked list node or
array.
Failure values are represented by an instance of the Tridash.Fail
class, which has one property
|
The failure type. This property is |
Instances of this class are thrown as JavaScript exceptions rather
than being returned directly. Generally failure values are represented
by thunks, see the next section on lazy evaluation, which, when evaluated, throw a
Tridash.Fail
exception.
Use the |
The following functions return a JavaScript object, which serves to identify a builtin failure type.
Tridash.Empty()
Tridash.NoValue()
No
Value
failure type.
Tridash.TypeError()
Type-Error
failure type.
Tridash.IndexOutBounds()
Index-Out-Bounds
failure type.
Tridash.InvalidInteger()
Invalid-Integer
failure type.
Tridash.InvalidReal()
Invalid-Real
failure type.
Tridash.ArityError()
Arity-Error
failure type.
Tridash expressions may be evaluated lazily, that is they are only
evaluated at the point where they are first used. Lazily evaluated
expressions are wrapped in thunk objects, which are instances of the
Tridash.Thunk
class.
Thunks are created using the Tridash.Thunk
constructor which takes
one argument, the thunk computation function. The thunk computation
function should be a function of no arguments which computes, and
returns, the thunk’s result value.
The Tridash.resolve
function, of one argument, computes the value of
a Tridash.Thunk
, passed as the argument, if it has not been computed
already, and returns the resulting value. If the argument is not a
Tridash.Thunk
object it is returned directly.
|
Example: Resolving node value and handling failures.
try { value = Tridash.resolve(value); // Use resolved value ... } catch (e) { if (e instanceof Tridash.Fail) { // Handle failure ... } }
In the JavaScript backend, the name of the JavaScript function,
which provides the implementation of an external meta-node is given
by the value of the node’s js-name
attribute. This attribute must be
a string which names a function that is globally accessible to the
generated JavaScript code, at the time it is run.
Example: Linking external meta-node to JavaScript function.
## Meta-Node of two arguments /external(fn, a, b) ## Link to JavaScript 'my_func' function /attribute(fn, js-name, "my_func")
The function is called passing in the values of the argument nodes as arguments. If the value for an optional argument is not provided to the meta-node, in the Tridash source, the default value is passed to the function. If the optional argument and the arguments following it do not have default values, they are omitted in the generated function call.
Example: External meta-node with optional arguments with default value.
## Meta-Node with 1 required and 1 optional argument /external(fn, a, b : 1) ## Link to JavaScript 'my_func' function /attribute(fn, js-name, "my_func") ## Instance with optional argument omitted fn(x)
This example compiles to the following call to my_func
:
my_func(x, 1)
Example: External meta-node with optional arguments without default value.
## Meta-Node with 1 required and 1 optional argument /external(fn, a, :(b)) ## Link to JavaScript 'my_func' function /attribute(fn, js-name, "my_func") ## Instance with optional argument omitted fn(x)
This example compiles to the following call to my_func
:
my_func(x)
Notice that no value is provided for the b
argument since it was
omitted, in the meta-node instance fn(x)
and it does not have a
default value.
Rest arguments are accumulated into a single JavaScript array which is passed as the last argument to the function. If the rest argument list is empty, the argument is omitted entirely in the generated function call.
Example: External meta-node with rest arguments.
## Meta-Node with 1 required and 1 rest argument /external(fn, x, ..(xs)) ## Link to JavaScript 'my_func' function /attribute(fn, js-name, "my_func") ## Instance 1: 3 rest arguments fn(a, b, c, d) ## Instance 2: no rest arguments
Instance 1 is compiled to the following call to my_func
:
my_func(a, [b, c, d])
Instance 2 is compiled to the following call to my_func
:
my_func(a)
Notice no value is passed for the second argument which corresponds to the rest argument.
Each argument, passed to an external function, may either be an
immediate value or a Tridash.Thunk
object, in the case of an
argument which is lazily evaluated. The Tridash.resolve
function
should be called on each argument, of which the value is actually
used, to ensure that the immediate value is obtained.
The function should always return a value, which becomes the return
value of the meta-node. This can either be an immediate Tridash value,
of one of the types specified in Value Types, or a
Tridash.Thunk
object.
Any failure value exception, thrown inside the function either by the
function itself or while evaluating a thunk, should be
caught and wrapped in a Tridash.Thunk
object which is returned from
the function.
Example: Handling failures in external meta-node functions.
function my_func(a) { try { // Resolve value of the `a` argument // The result may be a failure value a = Tridash.resolve(a); // Do something with `a` ... } catch (e) { // Handle failure value exception if (e instanceof Tridash.Fail) { // Wrap failure in thunk object and return return new Tridash.Thunk(() => { throw e }); } // Rethrow other exceptions throw e; } }
By default, when targeting the JavaScript backend, a standalone
JavaScript file is produced, which assigns the nodes
and
set_values
properties of the runtime module object to the exports
object. This file can be loaded by a JavaScript runtime which supports the
require
function, such as Node.js. The properties of the module
object can then be accessed as properties of the object returned by
require
.
Example: JavaScript Module Object Usage.
# Load the compiled JavaScript module. Substitute `module.js` with the # path to the generated JavaScript file. const module = require('module.js'); const node_x = module.nodes.node_x; // Retrieve value of node_x const value = node_x.get_value(); ... // Set value of node_x to integer 10 node_x.set_value(10);
The get_value
method of the runtime node object takes a single
optional argument, which if true (the default), the value of the
node is fully evaluated if it is a thunk object, representing a
lazily evaluated value. If the argument is false, thunk objects
are not evaluated but are returned directly.
This section details calling JavaScript functions when targeting WebAssembly.
Tridash value types are represented by objects stored in the heap of the compiled WebAssembly module’s memory object. The heap is managed by a tracing garbage collector, which is run whenever a memory allocation requests more space than is available.
In order for a Tridash value to be passed to a JavaScript function,
and vice versa, it is has to be converted (marshalled) to an
equivalent representation using JavaScript types. This functionality
is provided by the Tridash.Marshaller
class in the JavaScript
component of the runtime library, which is encapsulated in the module
object Tridash
.
Tridash value types, when converted to JavaScript value types, are represented by the following:
Tridash.Marshaller.Object
class. The subnodes
property contains
an object with a property for each subnode. The identifier of each
property is the corresponding subnode identifier.
Characters are represented by instances of the
Tridash.Marshaller.Char
class, which has a single property:
|
The character code. |
Symbols are represented by instances of the
Tridash.Marshaller.Symbol
, which has a single property:
|
The symbol name as a string. |
Linked list nodes are represented by instances of the
Tridash.Marshaller.ListNode
class which the following properties:
|
The element stored in the node |
|
The next node in the linked list |
Failure values are represented by instances of the
Tridash.Marshaller.Fail
class, which has a single property:
|
The failure type. |
Raw node references are represented by instances of the
Tridash.Marshaller.Node
class, which has a single property.
|
The node ID. |
The following properties of the Marshaller.FailTypes
represent
builtin failure types as JavaScript Values:
NoValue
No Value
failure type.
TypeError
Type-Error
failure type.
InvalidInteger
Invalid-Integer
failure type.
InvalidReal
Invalid-Real
failure type.
ArityError
Arity-Error
failure type.
IndexOutBounds
Index-Out-Bounds
failure type.
Empty
The Tridash.Marshaller
class is responsible for converting Tridash
values stored in the WebAssembly heap to equivalent JavaScript values
and vice versa. An instance of this class is automatically created
when loading the WebAssembly module.
to_tridash(value)
Convert a JavaScript value (value
), of a type
listed in Value Types, returning a pointer (integer offset)
to the Tridash value with the WebAssembly memory heap.
value
may also be an instance of the Marshaller.TridashValue
class, which represents a value stored on the WebAssembly heap, at the
location given by the ptr
property. This is useful to create arrays
or list objects which contain references to existing Tridash values.
Subnode dictionaries ( |
to_js(pointer)
pointer
within the WebAssembly heap, to a JavaScript value.
stack_push(pointer)
stack_pop()
A pointer may point to a thunk object, which represents a Tridash value which has not been computed yet. When converting a Tridash value to a JavaScript value, all thunk objects are computed. As a result of this each conversion from Tridash to JavaScript may trigger a garbage collection cycle. Likewise, converting a JavaScript value to a Tridash value may also trigger a garbage collection since a new object is created on the heap.
Converting an infinite list or a cyclic object will result in an infinite loop, due to all lazily evaluated values being computed on conversion. |
When converting multiple values, of which the conversion of the first
value, a
, results in a garbage collection cycle being run, the
memory held by the second value, b
, may be reclaimed if it is not
referenced by an object in the root set. Furthermore, the pointer to
b
is no longer valid due to the objects being copied to the new
heap.
To overcome this problem, before converting a Tridash value to
JavaScript, all pointers to other Tridash values should be pushed onto
the root set stack, using the stack_push
method of
Tridash.Marshaller
. After the conversion the updated pointer values
should be popped off the root set stack using the stack_pop
method
of Tridash.Marshaller
.
Every call to |
Example: Converting Multiple Tridash Values to JS Values.
/// Convert the Tridash values, pointer to by `a`, `b` and `c`. // Push pointer `c` to root set stack marshaller.stack_push(c); // Push pointer `b` to root set stack marshaller.stack_push(b); // Convert Tridash value `a` to JS value a = marshaller.to_js(a); // Pop updated pointer `b` from stack b = marshaller.stack_pop(); // Convert Tridash value `b` to JS value b = marshaller.to_js(b); // Pop updated pointer `c` from stack c = marshaller.stack_pop(); // Convert Tridash value `c` to JS value c = marshaller.to_js(c);
The name of the JavaScript function, which provides the implementation
of an external meta-node is given by the value of the node’s
wasm-name
attribute. The value of this attribute may either be a
symbol or string which names a global JavaScript function, or an expression of
the form module.name
where module
is the name of a globally
accessible object, and name
is the name of the property which
contains the function that implements the meta-node.
Example: Linking external meta-node to JavaScript function.
## Meta-Node of two arguments /external(f, a, b) /external(g, a, b) ## Link to JavaScript 'my-func' function /attribute(f, wasm-name, "my-func") ## Link to JavaScript `func` function that is a property ## of the global object `my_lib` /attribute(g, wasm-name, my_lib.func)
The function is called with the arguments being the pointers to the values of the argument nodes, within the module’s memory object.
If the value for an optional argument is not provided to the
meta-node, in the Tridash source, and there is no default value, the
null pointer (0
) is passed to the function.
The |
Example: External meta-node with optional arguments without default value.
## Meta-Node with 1 required and 1 optional argument /external(fn, a, :(b)) ## Link to JavaScript 'my_func' function /attribute(fn, wasm-name, "my_func") ## Instance with optional argument omitted fn(x)
In this example, my_func
will be called with the pointer to the
value of x
as the first argument and the null pointer (0
) as the
second argument.
The function should return its result as a pointer to a Tridash value, within the WebAssembly memory object.
By default, when targeting the WebAssembly backend, a JavaScript file
is produced, along with the .wasm
file containing the WebAssembly
module, which contains a script that loads the module. A promise,
which resolves to the runtime module object containing the nodes
property and set_values
member function, is assigned to the module
property of the exports
object. This file can be loaded by a
JavaScript runtime which supports the require
function, such as
Node.js. The properties of the module object can then be accessed as
properties of the object returned by require
.
A promise to the module object is assigned, rather than the module object itself, since WebAssembly modules are loaded asynchronously. |
The runtime module object also contains the following additional fields:
module
WebAssembly.Instance
object of the compiled Tridash
module.
runtime
WebAssembly.Instance
object of the runtime library
module.
memory
marshaller
Tridash.Marshaller
object for marshalling values
to and from the compiled module’s heap memory.
Example: WebAssembly Module Object Usage.
# Load the compiled Wasm loader script. Substitute `module.js` with # the path to the generated JavaScript file. const module = require('module.js') module.then((mod) => { const marshaller = mod.marshaller; const node_x = mod.nodes.node_x; // Retrieve value of node_x const value = node_x.get_value(); ... // Set value of node_x to integer 10 node_x.set_value(10); });
The set_value
, and get_value
methods, of the runtime node object,
automatically marshal values to and from the WebAssembly module’s
heap.
The get_value
method, of the runtime node object, takes a single
optional argument, which if false returns a raw pointer to the
Tridash value, of the node, rather than converting it to a JavaScript
value. By default this argument is true.