by T. Michael Keesey ([email protected])
JSEN is a subset of JavaScript (based on the ECMA-262 5.1 standard and largely overlapping JSON) that can represent mathematical expressions, and an associated TypeScript/JavaScript library.
JSEN is available under the MIT license. For more details, see the LICENSE
file.
bin/jsen.js
: JSEN as a JavaScript file.bin/jsen.min.js
: A minified version ofjsen.js
.bin/ecma262.js
: ECMA-262 namespace for JSEN.src/jsen.ts
: The TypeScript source for JSEN.src/jsen.d.ts
: A TypeScript declaration file for JSEN.src/ecma262.ts
: The TypeScript source for the ECMA-262 namespace.src/ecma262.d.ts
: A TypeScript declaration for the ECMA-262 namespace.build.sh
: UNIX shell script to build the JavaScript files from TypeScript source. Requires Java and the TypeScript compiler.
JSEN is JavaScript wherein values are interpreted as expressions.
A String is interpreted as:
- a namespace reference, if it ends with
":"
(but not with"\\:"
); - a qualified identifier, if it includes (but does not end with)
":"
; or - a local identifier, otherwise.
Colons (":"
) in strings may be escaped by a preceding backslash.
Since backslashes must be escaped in JavaScript strings, this is written as two backslashes: "\\"
.
This does not pertain to keys used as local identifiers in namespace declarations (read on).
An Array is interpreted as an application of an operation, where the first element is a string identifying the operation and the following elements (if any) are arguments.
An empty array ([]
) is illegal.
An Object (associative array) is interpreted either as:
- a set of declarations, where each key is a local identifier and each value is an evaluable expression, or
- a namespace, where each key is a URI and each value is a set of declarations.
All other value types (Null, Boolean, Number, Function, nested objects, etc.) are interpreted as themselves.
Here is a JSEN declaration for a namespace whose local identifiers represent approximations of numerical constants:
{
"e": 2.718281828459045,
"pi": 3.141592653589793
}
The JSEN library optionally includes a namespace with the URI "http://ecma-international.org/ecma-262/5.1"
that contains certain elements of the ECMA-262 standard.
Using this, expressions like the following may be formed:
{
"x": ["http://ecma-international.org/ecma-262/5.1:+",
1,
2
],
"y": ["http://ecma-international.org/ecma-262/5.1:Math.sin",
["http://ecma-international.org/ecma-262/5.1:/",
"http://ecma-international.org/ecma-262/5.1:Math.PI",
2
]
],
"z": ["http://ecma-international.org/ecma-262/5.1:Array",
4,
5,
6
]
}
When evaluated, "x"
will yield 3
(JavaScript: 1 + 2
), "y"
will yield 1
(JavaScript: Math.sin(Math.PI / 2)
), and "z"
will yield [4, 5, 6]
(JavaScript: Array(4, 5, 6)
).
The available ECMA-262 entities are:
-
Literals:
undefined
NaN
Infinity
-
Accessors:
[]
-
Operators:
void
+
-
~
!
*
/
%
<<
>>
>>>
<
>
<=
>=
in
==
!=
===
!==
&
^
|
&&
||
?:
(Note that since colons (
":"
) are reserved in JSEN identifiers, the?:
operator's name must be written"?\\:"
.) -
Top-level functions:
isFinite
isNaN
Array
Boolean
Number
The
Array
function has been modified so that a single argument yields an array with that as its single member (instead of using it to determine the length of the array). -
All constants of the
Math
object (Math.E
,Math.LN2
, etc.). -
All functions of the
Math
object (Math.abs
,Math.acos
, etc.).
Since repeated URIs can make expressions rather verbose, JSEN allows for arbitrary references to namespaces:
{
"js": "http://ecma-international.org/ecma-262/5.1:",
"x": [ "js:+", 1, 2 ],
"y": [ "js:Math.sin", [ "js:/", "js:Math.PI", 2 ] ],
"z": [ "js:Array", 4, 5, 6 ]
}
These references only pertain to the namespaces they were declared under, and are not externally accessible.
Identifiers may be declared in any order, and may refer to other identifiers. Within a namespace local names may be used.
{
"pi": "js:Math.PI",
"tau": [ "js:*", "pi", 2 ],
"js": "http://ecma-international.org/ecma-262/5.1:"
}
Note that cyclical references will cause errors when evaluated. The following expression is illegal:
{
"x": "y",
"y": "x"
}
All of the expressions listed so far are JSON, but non-JSON JavaScript may also be used for values not possible under JSON (such as functions):
{
"even": function( x ) { return x % 2 === 0; },
"a": [ "even", 2 ],
"b": [ "even", 3 ]
}
When evaluated, "a"
will yield true
and "b"
will yield false
.
The simplest way to use JSEN is to use the global functions. To declare an identifier as representing the value of an expression:
jsen.decl('urn:my-namespace', 'my-id', 10); // The last argument can be any JSEN expression.
The expression may be evaluated like so:
jsen.eval('urn:my-namespace', 'my-id'); // 10
Declarations may be chained:
jsen.decl('urn:my-namespace', 'my-id', 10)
.decl('urn:my-namespace', 'my-id-2', 20);
In addition to evaluating expresions that have been linked with an identifier through a declaration, expressions may also be evalauted on their own, without an identifier or declaration:
jsen.evalExpr('urn:my-namespace:my-id'); // 10, if the previous code has been run
To use the ECMA-262 entities:
jsen.ecma262.decl();
Now you can use the entities:
jsen.decl('urn:my-namespace', 'my-array-id', ['http://ecma-international.org/ecma-262/5.1:Array', 1, 2]);
jsen.eval('urn:my-namespace', 'my-array-id'); // [1, 2]
For convenience, the ECMA-262 URI is available as a constant: jsen.ecma262.URI
.
Namespaces may be declared all at once. When this is done, you may use abbreviated namespace references:
jsen.decl('urn:my-namespace', {
'js': 'http://ecma-international.org/ecma-262/5.1:',
'my-id': 10,
'my-array-id': ['js:Array', 1, 2]
});
You may also evaluate every identifier in a namespace at once:
jsen.eval('urn:my-namespace'); // { 'my-id': 10, 'my-array-id': [1, 2]}
A set of namespaces may be declared or evaluated all at once:
jsen.decl({
'js': 'http://ecma-international.org/ecma-262/5.1:',
'urn:my-namespace':
{
'my-id': 10,
'my-array-id': ['js:Array', 1, 2]
},
'urn:my-other-namespace':
{
'my-id': ['js:+', 10, 10]
}
});
jsen.eval(); // { 'urn:my-namespace': { 'my-id': 10, 'my-array-id': [1, 2]}, 'urn:my-other-namespace': { 'my-id': 20 } /* Plus all ECMA-262 entities */ }
Note that the namespace reference at the top level ('js'
) is available in both of the declared namespaces.
A function may be used as a namespace. It should expect a single argument (a local identifier). Its output need not be consistent (that is, it can be random), since JSEN stores evaluations.
jsen.decl({
'random': Math.random,
'uppercase': function(localName) { return localName.toUpperCase(); }
});
jsen.eval('random', 'foo'); // A random number.
jsen.eval('random', 'foo'); // The same number.
jsen.eval('random', 'bar'); // A different random number.
jsen.eval('uppercase', 'foo'); // "FOO"
Apart from the global functions, you may use a solver instance. This prevents collisions with other code using JSEN.
To create a solver instance:
var solver = jsen.solver();
Now you can use all the functions in the same manner as the global functions:
jsen.ecma262.decl(solver); // To make ECMA-262 entities available.
solver.decl({
'urn:my-namespace': {
'js': 'http://ecma-international.org/ecma-262/5.1:',
'my-id': 33,
'my-array-id': ['js:Array', 5, 6, 7]
},
'urn:my-other-namespace': {
'my-id': 44
}
});
solver.eval(); // { 'urn:my-namespace': { 'my-id': 33, 'my-array-id': [5, 6, 7]}, 'urn:my-other-namespace': { 'my-id': 44 } /* Plus all ECMA-262 entities */ }
solver.evalExpr([ 'http://ecma-international.org/ecma-262/5.1:minus', 'urn:my-namespace:my-id', 11 ]); // 22