Architecture of symbols
Features
Let us show the most essential parts
Symbol definition
In general the rule is
context.Symbol = async (arguments, env) => {
//evaluating arguments
const a = await interpretate(arguments[0], env);
//body
some calculations
//may return or not
return result;
}
Contexts
To prevent the mess of duplicated function names and etc, you can specify the context or let's say library of symbols using
var library = {};
interpretate.extendContext(library);
library.Symbol = async (args, env) => {/*...*/}
The interpreter will check all available contexts and find the first match. However, you can prioritize the context providing the information in env
object
core.GrandSymbol = async (args, env) => {
const data = await interpretate(args, {...env, context: library});
//...
}
More about env
object is here Meta Data.
In the same manner, the separation between Line[]
used in Graphics3D
and in Graphics
functions was made.
External functions
This feature is still in development
If the called expression was not found in all available contexts, the interpreter will extend core
with a server-call, i.e. will ask kernel to evaluate the unknown symbol and return back the result.
By the default, those special missing expressions becomes in the end virtual containers and can call update
method on other containerized functions.
See executables and Sub symbols for more information about methods and etc.
Metadata
To share some data between functions, to use local and global memory of the executable objects, specifying methods of evaluation and DOM access the following object is provided
env = {
element: document.element, /* a Code Mirror widget */
context: core, /* default context */
/* subsymbol */
method: undefined | 'update' | 'destory',
/* global and local memory of front-end objects*/
local: {},
global: {call: uid},
numerical: false, /* keep symbolic or not */
hold: false /* keep exps in a List and JS Objects */
unsafe: false /* allows to ask Wolfram Kernel for undefined symbols */
novirtual: false /* prevents creatation of virtual containers (all functions inside are executed normally) */
/* anything you want to share */
root: ExecutableObject/* a reference to the FrontEndObject container */
...
//anything else you want
}
local
and global
cannot be deeply-copied, therefore they will be the same for all nested expressions if the expression is containerized (frontend object or virtual), then local
will be overridden.
Ones, which ave the most influence to the process of interpreting, are
context
- prioritizes the context to fetch, the symbol to evaluate. See section Contextsmethod
- it is meant to specify a sub-symbol for the whole three of WL expression (or in practice so-called method of interpreting)
Methods | Sub-symbols
undefined
(leave empty)update
- usually called when the sub-expression was updateddestroy
- used only on the wolfram frontend, when the widget was removed
The following sub-symbols can be defined as
core.MySymbol = (args, env) => {}
core.MySymbol.update = (args, env) => {}
core.MySymbol.destory = (args, env) => {}
You can think about it if it was a class definition with a constructor and several methods.
It is good to declare update
method for your function, even if you do not plan to use it explicitly, define core.MySymbol.update = core.MySymbol
For the sake of performance, when something changes with the data inside the plot, there is no need to reevaluate the whole tree of WL expressions. Therefore, we can specify the method of reevaluation
//to create a plot
core.ListLinePlotly = (args, env) => {
...
Plotly.newPlot(env.element, data)
}
//to update only the data
core.ListLinePlotly.update = (args, env) => {
...
Plotly.restyle(env.element, newdata)
}
When a nested expression was changed, the nearest parent can be called using update
method like on a picture
In principle, interpreter has no idea about what update
and etc means, one can write any "method", which is basically just a sub-symbol.
For Graphics3D
object is crucial to use destroy
method, when object has removed from the editor, since it is necessary to remove the animation
cycle
core.Graphics3D.destroy = (args, env) => {
cancelAnimationFrame(env.local.aid);
//to cancel the animation
}
DOM
When the WL expressing is called via FrontEndVirtual
on editor's side it creates local
memory and unique identifier, where one can attach DOM element using
AttachDOM["dom-element-id"]
which is accessible by env.element
field
core.Canvas = (args, env) => {
const canvas = document.createElement('canvas');
env.element.append(canvas);
//....
}
Global and local memory
env.global
is visible to all containers and its inner WL expressions, whereenv.global.stack
contains the call-treeenv.local
is isolated memory related to individualexecutable
functions, i.e. real or virtual containers.
core.MyFunction = (args, env) => {
env.local = {} //pointer to the local memory of the instance
env.global = {} //pointer to the global memory of the call tree
env //sharable memory
}