UNPKG

hyperscript.org

Version:

a small scripting language for the web

1,148 lines (1,038 loc) 148 kB
//AMD insanity (function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define([], factory); } else { // Browser globals root._hyperscript = factory(); } }(typeof self !== 'undefined' ? self : this, function () { return (function () { 'use strict'; //==================================================================== // Utilities //==================================================================== function mergeObjects(obj1, obj2) { for (var key in obj2) { if (obj2.hasOwnProperty(key)) { obj1[key] = obj2[key]; } } return obj1; } function parseJSON(jString) { try { return JSON.parse(jString); } catch(error) { logError(error); return null; } } function logError(msg) { if(console.error) { console.error(msg); } else if (console.log) { console.log("ERROR: ", msg); } } // https://stackoverflow.com/a/8843181 function varargConstructor(Cls, args) { return new (Cls.bind.apply(Cls, [Cls].concat(args))); } var globalScope = typeof self !== 'undefined' ? self : typeof global !== 'undefined' ? global : this; //==================================================================== // Lexer //==================================================================== var _lexer = function () { var OP_TABLE = { '+': 'PLUS', '-': 'MINUS', '*': 'MULTIPLY', '/': 'DIVIDE', '.': 'PERIOD', '\\': 'BACKSLASH', ':': 'COLON', '%': 'PERCENT', '|': 'PIPE', '!': 'EXCLAMATION', '?': 'QUESTION', '#': 'POUND', '&': 'AMPERSAND', ';': 'SEMI', ',': 'COMMA', '(': 'L_PAREN', ')': 'R_PAREN', '<': 'L_ANG', '>': 'R_ANG', '<=': 'LTE_ANG', '>=': 'GTE_ANG', '==': 'EQ', '===': 'EQQ', '!=': 'NEQ', '!==': 'NEQQ', '{': 'L_BRACE', '}': 'R_BRACE', '[': 'L_BRACKET', ']': 'R_BRACKET', '=': 'EQUALS' }; function isValidCSSClassChar(c) { return isAlpha(c) || isNumeric(c) || c === "-" || c === "_"; } function isValidCSSIDChar(c) { return isAlpha(c) || isNumeric(c) || c === "-" || c === "_" || c === ":"; } function isWhitespace(c) { return c === " " || c === "\t" || isNewline(c); } function positionString(token) { return "[Line: " + token.line + ", Column: " + token.col + "]" } function isNewline(c) { return c === '\r' || c === '\n'; } function isNumeric(c) { return c >= '0' && c <= '9'; } function isAlpha(c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } function isIdentifierChar(c) { return (c === "_" || c === "$"); } function isReservedChar(c) { return (c === "`" || c === "^"); } function makeTokensObject(tokens, consumed, source) { var ignoreWhiteSpace = true; matchTokenType("WHITESPACE"); // consume any initial whitespace function raiseError(tokens, error) { _parser.raiseParseError(tokens, error); } function requireOpToken(value) { var token = matchOpToken(value); if (token) { return token; } else { raiseError(this, "Expected '" + value + "' but found '" + currentToken().value + "'"); } } function matchAnyOpToken(op1, op2, op3) { for (var i = 0; i < arguments.length; i++) { var opToken = arguments[i]; var match = matchOpToken(opToken); if (match) { return match; } } } function matchOpToken(value) { if (currentToken() && currentToken().op && currentToken().value === value) { return consumeToken(); } } function requireTokenType(type1, type2, type3, type4) { var token = matchTokenType(type1, type2, type3, type4); if (token) { return token; } else { raiseError(this, "Expected one of " + JSON.stringify([type1, type2, type3])); } } function matchTokenType(type1, type2, type3, type4) { if (currentToken() && currentToken().type && [type1, type2, type3, type4].indexOf(currentToken().type) >= 0) { return consumeToken(); } } function requireToken(value, type) { var token = matchToken(value, type); if (token) { return token; } else { raiseError(this, "Expected '" + value + "' but found '" + currentToken().value + "'"); } } function matchToken(value, type) { var type = type || "IDENTIFIER"; if (currentToken() && currentToken().value === value && currentToken().type === type) { return consumeToken(); } } function consumeToken() { var match = tokens.shift(); consumed.push(match); if(ignoreWhiteSpace) { matchTokenType("WHITESPACE"); // consume any whitespace until the next token } return match; } function consumeUntilWhitespace() { var tokenList = []; ignoreWhiteSpace = false; while (currentToken() && currentToken().type !== "WHITESPACE" && currentToken().type !== "EOF") { tokenList.push(consumeToken()); } ignoreWhiteSpace = true; return tokenList; } function hasMore() { return tokens.length > 0; } function currentToken(ignoreWhiteSpace) { var token; if (ignoreWhiteSpace) { var i = 0; do { token = tokens[i++] } while (token && token.type === "WHITESPACE"); } else { token = tokens[0]; } if (token) { return token; } else { return { type:"EOF" } } } return { matchAnyOpToken: matchAnyOpToken, matchOpToken: matchOpToken, requireOpToken: requireOpToken, matchTokenType: matchTokenType, requireTokenType: requireTokenType, consumeToken: consumeToken, matchToken: matchToken, requireToken: requireToken, list: tokens, consumed: consumed, source: source, hasMore: hasMore, currentToken: currentToken, consumeUntilWhitespace: consumeUntilWhitespace } } function tokenize(string) { var source = string; var tokens = []; var position = 0; var column = 0; var line = 1; var lastToken = "<START>"; while (position < source.length) { if (currentChar() === "-" && nextChar() === "-") { consumeComment(); } else { if (isWhitespace(currentChar())) { tokens.push(consumeWhitespace()); } else if (!possiblePrecedingSymbol() && currentChar() === "." && isAlpha(nextChar())) { tokens.push(consumeClassReference()); } else if (!possiblePrecedingSymbol() && currentChar() === "#" && isAlpha(nextChar())) { tokens.push(consumeIdReference()); } else if (isAlpha(currentChar()) || isIdentifierChar(currentChar())) { tokens.push(consumeIdentifier()); } else if (isNumeric(currentChar())) { tokens.push(consumeNumber()); } else if (currentChar() === '"' || currentChar() === "'") { tokens.push(consumeString()); } else if (OP_TABLE[currentChar()]) { tokens.push(consumeOp()); } else if (isReservedChar(currentChar())) { tokens.push(makeToken('RESERVED', currentChar)) } else { if (position < source.length) { throw Error("Unknown token: " + currentChar() + " "); } } } } return makeTokensObject(tokens, [], source); function makeOpToken(type, value) { var token = makeToken(type, value); token.op = true; return token; } function makeToken(type, value) { return { type: type, value: value, start: position, end: position + 1, column: column, line: line }; } function consumeComment() { while (currentChar() && !isNewline(currentChar())) { consumeChar(); } consumeChar(); } function consumeClassReference() { var classRef = makeToken("CLASS_REF"); var value = consumeChar(); while (isValidCSSClassChar(currentChar())) { value += consumeChar(); } classRef.value = value; classRef.end = position; return classRef; } function consumeIdReference() { var idRef = makeToken("ID_REF"); var value = consumeChar(); while (isValidCSSIDChar(currentChar())) { value += consumeChar(); } idRef.value = value; idRef.end = position; return idRef; } function consumeIdentifier() { var identifier = makeToken("IDENTIFIER"); var value = consumeChar(); while (isAlpha(currentChar()) || isIdentifierChar(currentChar())) { value += consumeChar(); } identifier.value = value; identifier.end = position; return identifier; } function consumeNumber() { var number = makeToken("NUMBER"); var value = consumeChar(); while (isNumeric(currentChar())) { value += consumeChar(); } if (currentChar() === ".") { value += consumeChar(); } while (isNumeric(currentChar())) { value += consumeChar(); } number.value = value; number.end = position; return number; } function consumeOp() { var value = consumeChar(); // consume leading char while (currentChar() && OP_TABLE[value + currentChar()]) { value += consumeChar(); } var op = makeOpToken(OP_TABLE[value], value); op.value = value; op.end = position; return op; } function consumeString() { var string = makeToken("STRING"); var startChar = consumeChar(); // consume leading quote var value = ""; while (currentChar() && currentChar() !== startChar) { if (currentChar() === "\\") { consumeChar(); // consume escape char and move on } value += consumeChar(); } if (currentChar() !== startChar) { throw Error("Unterminated string at " + positionString(string)); } else { consumeChar(); // consume final quote } string.value = value; string.end = position; return string; } function currentChar() { return source.charAt(position); } function nextChar() { return source.charAt(position + 1); } function consumeChar() { lastToken = currentChar(); position++; column++; return lastToken; } function possiblePrecedingSymbol() { return isAlpha(lastToken) || isNumeric(lastToken) || lastToken === ")" || lastToken === "}" || lastToken === "]" } function consumeWhitespace() { var whitespace = makeToken("WHITESPACE"); var value = ""; while (currentChar() && isWhitespace(currentChar())) { if (isNewline(currentChar())) { column = 0; line++; } value += consumeChar(); } whitespace.value = value; whitespace.end = position; return whitespace; } } return { tokenize: tokenize, makeTokensObject: makeTokensObject } }(); //==================================================================== // Parser //==================================================================== var _parser = function () { var GRAMMAR = {} var COMMANDS = {} var FEATURES = {} var LEAF_EXPRESSIONS = []; var INDIRECT_EXPRESSIONS = []; function parseElement(type, tokens, root) { var elementDefinition = GRAMMAR[type]; if (elementDefinition) return elementDefinition(_parser, _runtime, tokens, root); } function requireElement(type, tokens, message, root) { var result = parseElement(type, tokens, root); return result || raiseParseError(tokens, message || "Expected " + type.autocapitalize); } function parseAnyOf(types, tokens) { for (var i = 0; i < types.length; i++) { var type = types[i]; var expression = parseElement(type, tokens); if (expression) { return expression; } } } function addGrammarElement(name, definition) { GRAMMAR[name] = definition; } function addCommand(keyword, definition) { var commandGrammarType = keyword + "Command"; var commandDefinitionWrapper = function (parser, runtime, tokens) { var commandElement = definition(parser, runtime, tokens); if (commandElement) { commandElement.type = commandGrammarType; commandElement.execute = function (context) { return runtime.unifiedExec(this, context); } return commandElement; } }; GRAMMAR[commandGrammarType] = commandDefinitionWrapper; COMMANDS[keyword] = commandDefinitionWrapper; } function addFeature(keyword, definition) { var featureGrammarType = keyword + "Feature"; var featureDefinitionWrapper = function (parser, runtime, tokens) { var featureElement = definition(parser, runtime, tokens); if (featureElement) { featureElement.keyword = keyword; featureElement.type = featureGrammarType; return featureElement; } }; GRAMMAR[featureGrammarType] = featureDefinitionWrapper; FEATURES[keyword] = featureDefinitionWrapper; } function addLeafExpression(name, definition) { LEAF_EXPRESSIONS.push(name); addGrammarElement(name, definition); } function addIndirectExpression(name, definition) { INDIRECT_EXPRESSIONS.push(name); addGrammarElement(name, definition); } /* ============================================================================================ */ /* Core hyperscript Grammar Elements */ /* ============================================================================================ */ addGrammarElement("feature", function(parser, runtime, tokens) { var featureDefinition = FEATURES[tokens.currentToken().value]; if (featureDefinition) { return featureDefinition(parser, runtime, tokens); } }) addGrammarElement("command", function(parser, runtime, tokens) { var commandDefinition = COMMANDS[tokens.currentToken().value]; if (commandDefinition) { return commandDefinition(parser, runtime, tokens); } }) addGrammarElement("commandList", function(parser, runtime, tokens) { var cmd = parser.parseElement("command", tokens); if (cmd) { tokens.matchToken("then"); cmd.next = parser.parseElement("commandList", tokens); return cmd; } }) addGrammarElement("leaf", function(parser, runtime, tokens) { var result = parseAnyOf(LEAF_EXPRESSIONS, tokens); // symbol is last so it doesn't consume any constants if (result == null) { return parseElement('symbol', tokens); } else { return result; } }) addGrammarElement("indirectExpression", function(parser, runtime, tokens, root) { for (var i = 0; i < INDIRECT_EXPRESSIONS.length; i++) { var indirect = INDIRECT_EXPRESSIONS[i]; var result = parser.parseElement(indirect, tokens, root); if(result){ return result; } } return root; }); addGrammarElement("primaryExpression", function(parser, runtime, tokens) { var leaf = parser.parseElement("leaf", tokens); if (leaf) { return parser.parseElement("indirectExpression", tokens, leaf); } parser.raiseParseError(tokens, "Unexpected value: " + tokens.currentToken().value); }); /* ============================================================================================ */ /* END Core hyperscript Grammar Elements */ /* ============================================================================================ */ function createParserContext(tokens) { var currentToken = tokens.currentToken(); var source = tokens.source; var lines = source.split("\n"); var line = currentToken ? currentToken.line - 1 : lines.length - 1; var contextLine = lines[line]; var offset = currentToken ? currentToken.column : contextLine.length - 1; return contextLine + "\n" + " ".repeat(offset) + "^^\n\n"; } function raiseParseError(tokens, message) { message = (message || "Unexpected Token : " + tokens.currentToken().value) + "\n\n" + createParserContext(tokens); var error = new Error(message); error.tokens = tokens; throw error } function parseHyperScript(tokens) { return parseElement("hyperscript", tokens) } function setParent(elt, parent) { if (elt) { elt.parent = parent; setParent(elt.next, parent); } } function commandBoundary(token) { if (token.value == "end" || token.value == "then" || COMMANDS[token.value] || FEATURES[token.value] || token.type == "EOF") { return true; } } return { // parser API setParent: setParent, requireElement: requireElement, parseElement: parseElement, commandBoundary: commandBoundary, parseAnyOf: parseAnyOf, parseHyperScript: parseHyperScript, raiseParseError: raiseParseError, addGrammarElement: addGrammarElement, addCommand: addCommand, addFeature: addFeature, addLeafExpression: addLeafExpression, addIndirectExpression: addIndirectExpression, } }(); //==================================================================== // Runtime //==================================================================== var _runtime = function () { function matchesSelector(elt, selector) { // noinspection JSUnresolvedVariable var matchesFunction = elt.matches || elt.matchesSelector || elt.msMatchesSelector || elt.mozMatchesSelector || elt.webkitMatchesSelector || elt.oMatchesSelector; return matchesFunction && matchesFunction.call(elt, selector); } function makeEvent(eventName, detail) { var evt; if (window.CustomEvent && typeof window.CustomEvent === 'function') { evt = new CustomEvent(eventName, {bubbles: true, cancelable: true, detail: detail}); } else { evt = document.createEvent('CustomEvent'); evt.initCustomEvent(eventName, true, true, detail); } return evt; } function triggerEvent(elt, eventName, detail) { var detail = detail || {}; detail["sentBy"] = elt; var event = makeEvent(eventName, detail); var eventResult = elt.dispatchEvent(event); return eventResult; } function isArrayLike(value) { return Array.isArray(value) || value instanceof NodeList; } function forEach(value, func) { if (value == null) { // do nothing } else if (isArrayLike(value)) { for (var i = 0; i < value.length; i++) { func(value[i]); } } else { func(value); } } var ARRAY_SENTINEL = {array_sentinel:true} function linearize(args) { var arr = []; for (var i = 0; i < args.length; i++) { var arg = args[i]; if (Array.isArray(arg)) { arr.push(ARRAY_SENTINEL); for (var j = 0; j < arg.length; j++) { arr.push(arg[j]); } arr.push(ARRAY_SENTINEL); } else { arr.push(arg); } } return arr; } function delinearize(values){ var arr = []; for (var i = 0; i < values.length; i++) { var value = values[i]; if (value === ARRAY_SENTINEL) { value = values[++i]; var valueArray = []; arr.push(valueArray); while (value !== ARRAY_SENTINEL) { valueArray.push(value); value = values[++i]; } } else { arr.push(value); } } return arr; } function unwrapAsyncs(values) { for (var i = 0; i < values.length; i++) { var value = values[i]; if (value.asyncWrapper) { values[i] = value.value; } if (Array.isArray(value)) { for (var j = 0; j < value.length; j++) { var valueElement = value[j]; if (valueElement.asyncWrapper) { value[j] = valueElement.value; } } } } } var HALT = {halt_flag:true}; function unifiedExec(command, ctx) { while(true) { var next = unifiedEval(command, ctx); if (next == null) { console.error(command, " did not return a next element to execute! context: " , ctx) return; } else if (next.then) { next.then(function (resolvedNext) { unifiedExec(resolvedNext, ctx); }).catch(function(reason){ if (ctx.meta && ctx.meta.reject) { ctx.meta.reject(reason); } else { // TODO: no meta context to reject with, trigger event? } }); return; } else if (next === HALT) { // done return; } else { command = next; // move to the next command } } } function unifiedEval(parseElement, ctx) { var async = false; var wrappedAsyncs = false; var args = [ctx]; if (parseElement.args) { for (var i = 0; i < parseElement.args.length; i++) { var argument = parseElement.args[i]; if (argument == null) { args.push(null); } else if (Array.isArray(argument)) { var arr = []; for (var j = 0; j < argument.length; j++) { var element = argument[j]; var value = element.evaluate(ctx); // OK if (value) { if (value.then) { async = true; } else if (value.asyncWrapper) { wrappedAsyncs = true; } } arr.push(value); } args.push(arr); } else if (argument.evaluate) { var value = argument.evaluate(ctx); // OK if (value) { if (value.then) { async = true; } else if (value.asyncWrapper) { wrappedAsyncs = true; } } args.push(value); } else { args.push(argument); } } } if (async) { return new Promise(function(resolve, reject){ var linearized = linearize(args); Promise.all(linearized).then(function(values){ values = delinearize(values); if (wrappedAsyncs) { unwrapAsyncs(values); } try{ var apply = parseElement.op.apply(parseElement, values); resolve(apply); } catch(e) { reject(e); } }).catch(function(reason){ if (ctx.meta && ctx.meta.reject) { ctx.meta.reject(reason); } else { // TODO: no meta context to reject with, trigger event? } }) }) } else { if (wrappedAsyncs) { unwrapAsyncs(args); } try { return parseElement.op.apply(parseElement, args); } catch (e) { if (ctx.meta && ctx.meta.reject) { ctx.meta.reject(e); } else { throw e; } } } } var _scriptAttrs = null; function getScriptAttributes() { if (_scriptAttrs == null) { _scriptAttrs = _hyperscript.config.attributes.replace(/ /g,'').split(",") } return _scriptAttrs; } function getScript(elt) { for (var i = 0; i < getScriptAttributes().length; i++) { var scriptAttribute = getScriptAttributes()[i]; if (elt.hasAttribute && elt.hasAttribute(scriptAttribute)) { return elt.getAttribute(scriptAttribute) } } if (elt.type === "text/hyperscript") { return elt.innerText; } return null; } function makeContext(root, elt, event) { var ctx = { meta: { parser: _parser, lexer: _lexer, runtime: _runtime, root: root, iterators: root }, me: elt, event: event, detail: event ? event.detail : null, body: 'document' in globalScope ? document.body : null } ctx.meta.ctx = ctx; return ctx; } function applyEventListeners(hypeScript, elt) { forEach(hypeScript.onFeatures, function (onFeature) { forEach( onFeature.elsewhere ? [document] : onFeature.from ? onFeature.from.evaluate({}) : [elt], function(target){ // OK NO PROMISE target.addEventListener(onFeature.on.evaluate(), function(evt){ // OK NO PROMISE if (onFeature.elsewhere && elt.contains(evt.target)) return var ctx = makeContext(onFeature, elt, evt); onFeature.execute(ctx) }); }) }); } function getScriptSelector() { return getScriptAttributes().map(function (attribute) { return "[" + attribute + "]"; }).join(", "); } function isType(o, type) { return Object.prototype.toString.call(o) === "[object " + type + "]"; } function evaluate(typeOrSrc, srcOrCtx, ctxArg) { if (isType(srcOrCtx, "Object")) { var src = typeOrSrc; var ctx = srcOrCtx; var type = "expression" } else if (isType(srcOrCtx, "String")) { var src = srcOrCtx; var type = typeOrSrc var ctx = ctxArg; } else { var src = typeOrSrc; var ctx = {}; var type = "expression"; } ctx = ctx || {}; var compiled = _parser.parseElement(type, _lexer.tokenize(src) ); return compiled.evaluate ? compiled.evaluate(ctx) : compiled.execute(ctx); // OK } function processNode(elt) { var selector = _runtime.getScriptSelector(); if (matchesSelector(elt, selector)) { initElement(elt); } if (elt.querySelectorAll) { forEach(elt.querySelectorAll(selector), function (elt) { initElement(elt); }); } if (elt.type === "text/hyperscript") { initElement(elt, document.body); } if (elt.querySelectorAll) { forEach(elt.querySelectorAll("[type=\'text/hyperscript\']"), function (elt) { initElement(elt, document.body); }); } } function initElement(elt, target) { var internalData = getInternalData(elt); if (!internalData.initialized) { var src = getScript(elt); if (src) { try { internalData.initialized = true; internalData.script = src; var tokens = _lexer.tokenize(src); var hyperScript = _parser.parseHyperScript(tokens); _runtime.applyEventListeners(hyperScript, target || elt); setTimeout(function () { triggerEvent(target || elt, 'load'); }, 1); } catch(e) { console.error("hyperscript errors were found on the following element:", elt, "\n\n", e.message, e.stack); } } } } function getInternalData(elt) { var dataProp = 'hyperscript-internal-data'; var data = elt[dataProp]; if (!data) { data = elt[dataProp] = {}; } return data; } function typeCheck(value, typeString, nullOk) { if (value == null && nullOk) { return value; } var typeName = Object.prototype.toString.call(value).slice(8, -1); var typeCheckValue = value && typeName === typeString; if (typeCheckValue) { return value; } else { throw new Error("Typecheck failed! Expected: " + typeString + ", Found: " + typeName); } } function resolveSymbol(str, context) { if (str === "me" || str === "my") { return context["me"]; } if (str === "it" || str === "its") { return context["it"]; } else { if (context.meta && context.meta.context) { var fromMetaContext = context.meta.context[str]; if (typeof fromMetaContext !== "undefined") { return fromMetaContext; } } var fromContext = context[str]; if (typeof fromContext !== "undefined") { return fromContext; } else { return globalScope[str]; } } } function findNext(command) { if (command) { if (command.resolveNext) { return command.resolveNext(); } else if (command.next) { return command.next; } else { return findNext(command.parent) } } } function resolveProperty(root, property) { if (root != null) { var val = root[property]; if (typeof val !== 'undefined') { return val; } else { if (isArrayLike(root)) { if (property === "first") { return root[0]; } else if (property === "last") { return root[root.length - 1]; } else if (property === "random") { return root[Math.floor(root.length * Math.random())] } else { // flat map var result = []; for (var i = 0; i < root.length; i++) { var component = root[i]; var componentValue = component[property]; if (componentValue) { result.push(componentValue); } } return result; } } } } } function assignToNamespace(nameSpace, name, value) { var root = globalScope; while (nameSpace.length > 0) { var propertyName = nameSpace.shift(); var newRoot = root[propertyName]; if (newRoot == null) { newRoot = {}; root[propertyName] = newRoot; } root = newRoot; } root[name] = value; } var hyperscriptUrl = 'document' in globalScope ? document.currentScript.src : null return { typeCheck: typeCheck, forEach: forEach, triggerEvent: triggerEvent, matchesSelector: matchesSelector, getScript: getScript, applyEventListeners: applyEventListeners, processNode: processNode, evaluate: evaluate, getScriptSelector: getScriptSelector, resolveSymbol: resolveSymbol, makeContext: makeContext, findNext: findNext, unifiedEval: unifiedEval, unifiedExec: unifiedExec, resolveProperty: resolveProperty, assignToNamespace: assignToNamespace, hyperscriptUrl: hyperscriptUrl, HALT: HALT } }(); //==================================================================== // Grammar //==================================================================== { _parser.addLeafExpression("parenthesized", function(parser, runtime, tokens) { if (tokens.matchOpToken('(')) { var expr = parser.requireElement("expression", tokens); tokens.requireOpToken(")"); return { type: "parenthesized", expr: expr, evaluate: function (context) { return expr.evaluate(context); //OK } } } }) _parser.addLeafExpression("string", function(parser, runtime, tokens) { var stringToken = tokens.matchTokenType('STRING'); if (stringToken) { return { type: "string", token: stringToken, evaluate: function (context) { return stringToken.value; } } } }) _parser.addGrammarElement("nakedString", function(parser, runtime, tokens) { if (tokens.hasMore()) { var tokenArr = tokens.consumeUntilWhitespace(); tokens.matchTokenType("WHITESPACE"); return { type: "nakedString", tokens: tokenArr, evaluate: function (context) { return tokenArr.map(function (t) {return t.value}).join(""); } } } }) _parser.addLeafExpression("number", function(parser, runtime, tokens) { var number = tokens.matchTokenType('NUMBER'); if (number) { var numberToken = number; var value = parseFloat(number.value) return { type: "number", value: value, numberToken: numberToken, evaluate: function () { return value; } } } }) _parser.addLeafExpression("idRef", function(parser, runtime, tokens) { var elementId = tokens.matchTokenType('ID_REF'); if (elementId) { return { type: "idRef", value: elementId.value.substr(1), evaluate: function (context) { return document.getElementById(this.val