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 numberfwhich cannot be infinite or NaNOM.string(s)creates a new OpenMath string object from the given JavaScript stringsOM.bytearray(a)creates a new OpenMath bytearray object from the given JavaScript Uint8ArrayaOM.symbol(name,cd[,uri])creates a new OpenMath symbol with the given name (name) and content dictionary (cd), which are both strings. An optional baseurican also be passed.OM.variable(x)creates a new OpenMath variable whose name is given in the stringxOM.application(c1,c2,...,cn)creates a new OpenMath application whose children are theOMinstancesc1throughcn. This represents an application ofc1as a function to the argumentsc2throughcn, 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 theOMinstancexwith new attribute pairs added. Each (ki,vi) pair is a key-value pair, in whichkimust be an OpenMath symbol (anOMinstance) and eachvimust be anyOMinstance.OM.binding(h,v1,v2,...,vn,b)creates a new OpenMath binding in which the head symbolh(which must be an OpenMath symbol node, anOMinstance) binds the variablesv1throughvn(which must be OpenMath variable nodes, alsoOMinstances) in the bodyb(which can be anyOMinstance).OM.error(s,c1,c2,...,cn)creates a new OpenMath error object in whichsis the head symbol (anOMinstance representing an OpenMath symbol) and there are zero or more other childrenc1throughcnthat can be anyOMinstances
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 newOMinstance 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 anOMinstance.
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)returnsnullif the object is valid JSON that represents and OpenMath data structure, and thus could be wrapped in anOMinstance, or a string error message if it is notOM.decode(jsonObject)creates anOMinstance by wrapping the given object in anOMobject, if possible, or throws an error ifOM.checkJSONfails on the given inputOM.decode(string)parses the string as JSON and then calls the previous function.OM(tree)creates a newOMinstance 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.typeyields the type of anOMinstance, 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.valueyields 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.nameyields the string name of a variable or symbol, and is undefined in all other cases.instance.cdyields the content dictionary of a symbol, and is undefined in all other cases.instance.uriyields the base URI of a symbol, if one was provided, and is undefined in all other cases.instance.symbolyields the head symbol for a binding or error object, and is undefined in all other cases.instance.bodyyields the body of a binding object, and is undefined in all other cases.instance.childrenyields 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.variablesyields 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 twoOMinstances 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 anOMinstance.
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 otherOMobject, 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 (anOMinstance) 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 anOMsymbol instance, and the value must be anyOMinstance.
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
childrenarray, 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
variablesarray - 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 ofindexInParentto 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 tootherand 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,valuecontains the numerical value (if one was able to be computed) andmessageis 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.