API Reference
Getting started
In the browser
Import the JavaScript, which you can download from our repository directly or import from a CDN with the following one-liner.
<script src='https://cdn.jsdelivr.net/npm/openmath-js@1/openmath.js'></script>
From the command line
Or install this package into your project the usual way:
npm install openmath-js
Then within any of your modules, import it as follows.
OM = import( "openmath-js" ).OM;
After that, any of the example code snippets in this documentation should function as-is.
Creating OpenMath objects
The prototype for OpenMath data structures (that is, expression trees) is
named OMNode
in the global namespace (the browser window
) and is also
named OM
for convenience; they are the same object. It is defined as an
ES6 class.
Rather than use its constructor, there are a number of factory functions
that create OM
instances, as follows.
OM.integer(i)
creates a new OpenMath integer object from the given JavaScript integeri
. If you want to store a big integer, pass it as a string instead of an integer, as inOM.integer('583257320489234290')
.OM.float(f)
creates a new OpenMath float object from the given JavaScript numberf
which cannot be infinite or NaNOM.string(s)
creates a new OpenMath string object from the given JavaScript strings
OM.bytearray(a)
creates a new OpenMath bytearray object from the given JavaScript Uint8Arraya
OM.symbol(name,cd[,uri])
creates a new OpenMath symbol with the given name (name
) and content dictionary (cd
), which are both strings. An optional baseuri
can also be passed.OM.variable(x)
creates a new OpenMath variable whose name is given in the stringx
OM.application(c1,c2,...,cn)
creates a new OpenMath application whose children are theOM
instancesc1
throughcn
. This represents an application ofc1
as a function to the argumentsc2
throughcn
, where n may be 1. Note that this makes copies of all the children given to it, rather than removing them from their current contexts. This allows the function to be called on the same argument several times, for instance.OM.attribution(x,k1,v1,k2,v2,...,kn,vn)
creates a copy of theOM
instancex
with new attribute pairs added. Each (ki
,vi
) pair is a key-value pair, in whichki
must be an OpenMath symbol (anOM
instance) and eachvi
must be anyOM
instance.OM.binding(h,v1,v2,...,vn,b)
creates a new OpenMath binding in which the head symbolh
(which must be an OpenMath symbol node, anOM
instance) binds the variablesv1
throughvn
(which must be OpenMath variable nodes, alsoOM
instances) in the bodyb
(which can be anyOM
instance).OM.error(s,c1,c2,...,cn)
creates a new OpenMath error object in whichs
is the head symbol (anOM
instance representing an OpenMath symbol) and there are zero or more other childrenc1
throughcn
that can be anyOM
instances
Example use:
Because the above method can easily become annoyingly lengthy, we also provide a shorthand for writing OpenMath expressions as strings and having them parsed in a convenient way. Full details are covered in a separate page about simple encoding and decoding. Here is a brief summary.
OM.simpleDecode(string)
creates a newOM
instance decoded from the given string. If your input is invalid (not a string, or a string not containing a valid simple encoding) then the return value will be a string error message, rather than anOM
instance.
Example usage:
Each of the functions in this section have nicknames. For each factory
function given above, it has a three-letter nickname to help you write
shorter code that builds OpenMath tree structures. The nicknames are all in
the OM
namespace, and include int
, flo
, str
, byt
, sym
, var
,
app
, att
, bin
, and err
. Thus, for instance, you can write the
following code to build a valid OpenMath expression.
Finally, the simpleDecode
function also has the nickname simple
, so the
most compact form is the following.
The OM
objects are just wrappers around JSON tree structures that provide
methods for interacting with those tree structures. You can get access to
the tree structur itself with myInstance.tree
. It is not quite JSON,
because it has circular references, as children nodes point to their parent
nodes, but it is close to JSON.
The specification for how OpenMath expressions are stored as JSON trees is given in comments at the top of the source code, should you need it. The following methods are available for working with such structures, but these are rarely used by the client, and are mostly for internal purposes.
OM.checkJSON(jsonObject)
returnsnull
if the object is valid JSON that represents and OpenMath data structure, and thus could be wrapped in anOM
instance, or a string error message if it is notOM.decode(jsonObject)
creates anOM
instance by wrapping the given object in anOM
object, if possible, or throws an error ifOM.checkJSON
fails on the given inputOM.decode(string)
parses the string as JSON and then calls the previous function.OM(tree)
creates a newOM
instance wrapping the given JSON tree; this is the same asOM.decode
, but more compact. Note that you can thus get two instances that refer to the same internal data viaOM(otherInstance.tree)
.
Writing/saving OpenMath objects
instance.encode()
is the inverse ofOM.decode()
, and yields a JSON string useful for serializing instances in a compact wayinstance.simpleEncode()
is the inverse ofOM.simpleDecode()
, and converts instances into the simple encoding mentioned above. Note that it does not support errors, byte arrays, or attributions.instance.toXML()
yields the XML encoding defined in the OpenMath standard.
Properties of OpenMath Objects
instance.type
yields the type of anOM
instance, a string containing just one or two letters, one of i, f, st, ba, sy, v, a, bi, e, which mean integer, float, string, bytearray, symbol, variable, application, binding, and error, respectively. These come directly from the JSON encoding documented in the source.instance.value
yields the value of those atomic types that have one, as an atomic JavaScript datum. Integers and floats yield a JavaScript number, strings yield a JavaScript string, and bytearrays yield a JavaScript UInt8Array. This property is undefined in all other cases.instance.name
yields the string name of a variable or symbol, and is undefined in all other cases.instance.cd
yields the content dictionary of a symbol, and is undefined in all other cases.instance.uri
yields the base URI of a symbol, if one was provided, and is undefined in all other cases.instance.symbol
yields the head symbol for a binding or error object, and is undefined in all other cases.instance.body
yields the body of a binding object, and is undefined in all other cases.instance.children
yields a JavaScript array containing the child nodes for application or error objects, and is an empty array in all other cases. It may be an empty array in the case of error objects as well, if they have only a head symbol.instance.variables
yields the list of variables bound by a binding object, as a JavaScript array, and is undefined in all other cases.
Note that each of these properties is actually produced by a getter function, and thus is not always as efficient as you might think. For instance, you may not wish to write loops like this:
for ( var i = 0 ; i < anOMinstance.children.length ; i++ )
process( anOMinstance.children[i] ); // calls getter many times
Rather, you might do better with a loop like this:
for ( var i = 0, ch = anOMinstance.children ; i < ch.length ; i++ )
process( ch[i] ); // doesn't call getter at all on this line
Tree-Related Functions
As mentioned at the end of the first section,
it is possible to create two different OM
instances that refer to the same
internal tree structure. And as mentioned immediately above, calling
instance.children[i]
produces a new instance each time you call it.
Thus instance.children[0]
and instance.children[0]
will refer to the
same internal JSON structure, but will be different OM
instances. We can
thus check equality in two ways.
instance.sameObjectAs(otherInstance)
asks whether twoOM
instances refer to the same internal JSON data, the same node in the same tree.instance.equals(other[,checkAttributes])
compares structural equality only, and does not care whether the two instances are the same tree. It includes attributes in the comparison if and only if the second argument is set to true, which is its default.instance.copy()
makes a structural deep copy of anOM
instance.
Examples:
You can also modify tree structures as follows.
instance.remove()
returns no value, but removes the instance from its parent tree, if that does not break the parent tree's validity. If it does, this function takes no action. For instance, you cannot remove the head symbol from a binding or error structure.instance.replaceWith(other)
replaces the instance in its parent structure with the given otherOM
object, if the resulting tree would be valid, or does nothing otherwise. The original is returned on success, and undefined is returned on failure.instance.getAttribute(keySymbol)
returns the corresponding value for the given key symbol (anOM
instance) in the instance's attributes, if there is one, or undefined if there is not.instance.setAttribute(keySymbol,value)
adds or changes an attribute on the instance. The key symbol must be anOM
symbol instance, and the value must be anyOM
instance.
Searching OpenMath Trees
We have devised a way for indexing and addressing children and descendants within parent/ancestor trees, and the following functions use that convention. You can read about the indexing/addressing convention in the source code documentation, the section entitled "Parent-Child Relationships."
instance.findInParent()
returns a single string indicating the index of the node in its parent. The return value will be one of five types:- a string containing "c" followed by a number, as in 'c7' - this means
that the node is in it's parent's
children
array, and is at index 7 - a string containing "v" followed by a number, as in 'v0' - this is the
same as the previous, but for the parent's
variables
array - the string "b" - this means that the node is the body and its parent is a binding
- the string "s" - this means that the node is a symbol for its parent, which is either an error or a binding
- a lengthier string beginning with "{" - this is the JSON encoded version of the attribute key for which the node is the corresponding value
- undefined if none of the above apply (e.g., no parent, or invalid tree structure)
- a string containing "c" followed by a number, as in 'c7' - this means
that the node is in it's parent's
instance.findChild(indexString)
is the inverse of the previous, in that it takes as input a string that the previous might give as output, and finds the corresponding child tree by that index.
Example:
instance.address(inThisAncestor)
is a generalization ofindexInParent
to arbitrary depth. It returns an array of indices that one would need to follow, as a path, to walk from the given ancestor node, down through the tree, to reach this instance. If no ancestor is given (or a non-ancestor is given) then the topmost ancestor is used instead.instance.index(indexArray)
is the inverse of the previous function, taking an array of indices and walking that path into its descendants, returning the resulting subtree, or undefined if one or more of the steps were invalid.
Examples:
You can filter children or descendants by predicates.
instance.childrenSatisfying(P)
returns an array of all immediate children c for which P(c) returns true. This array may be empty. For the purposes of this function, immediate children include not only what is returned byinstance.children
, but also head symbols of bindings and errors, and bodies of bindings.instance.descendantsSatisfying(P)
is the same as the previous, but considers indirect descendants as well. Note that original subtrees are returned, not copies, so modifying them will change the original instance.instance.hasDescendantSatisfying(P)
returns true or false, equal to callinginstance.descendantsSatisfying(P).length > 0
, except this is faster because it can stop searching once it has found one.
Free and Bound Variables
Many applications of OpenMath relate to logic and/or programming, in which variable binding and substitutin plays a critical role. The following functions make it easy to ask questions and perform the most common operations related to variable binding and substitution.
instance.freeVariables()
returns and array of free variable names, as strings, that appear anywhere as descendants of the instance. Each name is only reported once, even if it occurs many times. This does not recur into attributes or error children.instance.isFree(inThisAncestor)
returns true if all variables free in the instance are free in the given ancestor. An invalid (or omitted) ancestor causes the routine to use the top-most ancestor as well. If any variable free in the instance is not free in the ancestor, return false.instance.occursFree(other)
returns true if there exists a descendant of the instance that's structurally equivalent toother
and that is free where it occurs in the given instance, or returns false if there is not.instance.isFreeToReplace(subtree,inThisAncestor)
returns true if replacing the given subtree with the given instance would make any variables free in the instance become bound in the given ancestor. As before, an invalid or omitted ancestor will use the topmost ancestor of the subtree instead.instance.replaceFree(original,replacement,inThisAncestor)
recursively searches through all descendants D of the instance that are structurally equiavlent to the given original, and wheneverreplacement.isFreeToReplace(D,inThisAncestor)
yields true, callD.replaceWith(replacement)
. It does not recur into attributes.
Miscellany
Sometimes it is useful to be able to take any JavaScript string and convert it into a string that could be used as a valid OpenMath identifier (such as a variable or symbol name). Because only a subset of Unicode is permitted, we provide an injection (although not a very compact one) from all strings into the set of strings accepted as valid OpenMath identifiers. The range of the function is strings of the form "id_[many decimal digits here]".
OM.encodeAsIdentifier(anyString)
performs the encodingOM.decodeIdentifier(encodedString)
inverts the previous function
Example:
Some applications find it useful to be able to evaluate simple numerical OpenMath expressions.
instance.evaluate()
attempts to evaluate a numerical expression that uses the basic operations of arithmetic, powers, roots, trigonometry, pi, e, and a few other simple concepts. It returns a JavaScript object with two members,value
contains the numerical value (if one was able to be computed) andmessage
is a string that may contain some details, such as when rounding needed to occur.
Example:
More Examples
In addition to the brief examples shown in this file, the test suite in the source code repository is (naturally) a large set of examples of how the module works, and they become useful approximately where "factory functions" are tested.