htmx/test/lib/_hyperscript.js
2021-03-11 05:53:57 -07:00

3919 lines
178 KiB
JavaScript

///=========================================================================
/// This module provides the core runtime and grammar for hyperscript
///=========================================================================
//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',
'$': 'DOLLAR',
';': '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, dollarIsOp) {
return (c === "_" || (!dollarIsOp && c === "$"));
}
function isReservedChar(c) {
return (c === "`" || c === "^");
}
function makeTokensObject(tokens, consumed, source) {
consumeWhitespace(); // consume initial whitespace
function consumeWhitespace(){
while(token(0, true).type === "WHITESPACE") {
consumed.push(tokens.shift());
}
}
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);
consumeWhitespace(); // consume any whitespace
return match;
}
function consumeUntil(value, type) {
var tokenList = [];
var currentToken = token(0, true);
while ((type == null || currentToken.type !== type) &&
(value == null || currentToken.value !== value) &&
currentToken.type !== "EOF") {
var match = tokens.shift();
consumed.push(match);
tokenList.push(currentToken);
currentToken = token(0, true);
}
consumeWhitespace(); // consume any whitespace
return tokenList;
}
function lastWhitespace() {
if (consumed[consumed.length - 1] && consumed[consumed.length - 1].type === "WHITESPACE") {
return consumed[consumed.length - 1].value;
} else {
return "";
}
}
function consumeUntilWhitespace() {
return consumeUntil(null, "WHITESPACE");
}
function hasMore() {
return tokens.length > 0;
}
function token(n, dontIgnoreWhitespace) {
var token;
var i = 0;
do {
if (!dontIgnoreWhitespace) {
while (tokens[i] && tokens[i].type === "WHITESPACE") {
i++;
}
}
token = tokens[i];
n--;
i++;
} while (n > -1)
if (token) {
return token;
} else {
return {
type:"EOF",
value:"<<<EOF>>>"
}
}
}
function currentToken() {
return token(0);
}
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,
token: token,
consumeUntil: consumeUntil,
consumeUntilWhitespace: consumeUntilWhitespace,
lastWhitespace: lastWhitespace
}
}
function tokenize(string, noDollarStart) {
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(), noDollarStart)) {
tokens.push(consumeIdentifier());
} else if (isNumeric(currentChar())) {
tokens.push(consumeNumber());
} else if (currentChar() === '"' || 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;
string.template = startChar === "`";
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);
}
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) {
if (tokens.matchOpToken("(")) {
var featureDefinition = parser.requireElement("feature", tokens);
tokens.requireOpToken(")");
return featureDefinition;
} else {
var featureDefinition = FEATURES[tokens.currentToken().value];
if (featureDefinition) {
return featureDefinition(parser, runtime, tokens);
}
}
})
addGrammarElement("command", function(parser, runtime, tokens) {
if (tokens.matchOpToken("(")) {
var commandDefinition = parser.requireElement("command", tokens);
tokens.requireOpToken(")");
return commandDefinition;
} else {
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 commandStart(token){
return COMMANDS[token.value];
}
function featureStart(token){
return FEATURES[token.value];
}
function commandBoundary(token) {
if (token.value == "end" ||
token.value == "then" ||
token.value == "else" ||
token.value == ")" ||
commandStart(token) ||
featureStart(token) ||
token.type == "EOF") {
return true;
}
}
function parseStringTemplate(tokens) {
var returnArr = [""];
do {
returnArr.push(tokens.lastWhitespace());
if (tokens.currentToken().value === "$") {
tokens.consumeToken();
var startingBrace = tokens.matchOpToken('{');
returnArr.push(requireElement("expression", tokens));
if(startingBrace){
tokens.requireOpToken("}");
}
returnArr.push("");
} else if (tokens.currentToken().value === "\\") {
tokens.consumeToken(); // skip next
tokens.consumeToken()
} else {
var token = tokens.consumeToken();
returnArr[returnArr.length - 1] += token.value;
}
} while (tokens.hasMore())
returnArr.push(tokens.lastWhitespace());
return returnArr;
}
return {
// parser API
setParent: setParent,
requireElement: requireElement,
parseElement: parseElement,
featureStart: featureStart,
commandStart: commandStart,
commandBoundary: commandBoundary,
parseAnyOf: parseAnyOf,
parseHyperScript: parseHyperScript,
raiseParseError: raiseParseError,
addGrammarElement: addGrammarElement,
addCommand: addCommand,
addFeature: addFeature,
addLeafExpression: addLeafExpression,
addIndirectExpression: addIndirectExpression,
parseStringTemplate: parseStringTemplate,
}
}();
//====================================================================
// Runtime
//====================================================================
var CONVERSIONS = {
dynamicResolvers : [],
"String" : function(val){
if(val.toString){
return val.toString();
} else {
return "" + val;
}
},
"Int" : function(val){
return parseInt(val);
},
"Float" : function(val){
return parseFloat(val);
},
"Number" : function(val){
return Number(val);
},
"Date" : function(val){
return Date(val);
},
"Array" : function(val){
return Array.from(val);
},
"JSON" : function(val){
return JSON.stringify(val);
},
"Object" : function(val){
if (typeof val === 'string' || val instanceof String) {
return JSON.parse(val);
} else {
return mergeObjects({}, val);
}
}
}
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) {
try {
var next = unifiedEval(command, ctx);
} catch(e) {
_runtime.registerHyperTrace(ctx, e);
if (ctx.meta.errorHandler && !ctx.meta.handlingError) {
ctx.meta.handlingError = true;
ctx[ctx.meta.errorSymmbol] = e;
command = ctx.meta.errorHandler;
continue;
} else if (ctx.meta.reject) {
ctx.meta.reject(e);
next = HALT;
} else {
throw e;
}
}
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){
_runtime.registerHyperTrace(ctx, reason);
if (ctx.meta.errorHandler && !ctx.meta.handlingError) {
ctx.meta.handlingError = true;
ctx[ctx.meta.errorSymmbol] = reason;
unifiedExec(ctx.meta.errorHandler, ctx);
} else if(ctx.meta.reject) {
ctx.meta.reject(reason);
} else {
throw reason;
}
});
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 ? element.evaluate(ctx) : null; // 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.errorHandler && !ctx.meta.handlingError) {
ctx.meta.handlingError = true;
ctx[ctx.meta.errorSymmbol] = reason;
unifiedExec(ctx.meta.errorHandler, ctx);
} else if(ctx.meta.reject) {
ctx.meta.reject(reason);
} else {
// TODO: no meta context to reject with, trigger event?
}
})
})
} else {
if (wrappedAsyncs) {
unwrapAsyncs(args);
}
return parseElement.op.apply(parseElement, args);
}
}
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(owner, feature, hyperscriptTarget, event) {
var ctx = {
meta: {
parser: _parser,
lexer: _lexer,
runtime: _runtime,
owner: owner,
feature: feature,
iterators: {}
},
me: hyperscriptTarget,
event: event,
target: event ? event.target : null,
detail: event ? event.detail : null,
body: 'document' in globalScope ? document.body : null
}
ctx.meta.ctx = ctx;
return ctx;
}
function getScriptSelector() {
return getScriptAttributes().map(function (attribute) {
return "[" + attribute + "]";
}).join(", ");
}
function convertValue(value, type) {
var dynamicResolvers = CONVERSIONS.dynamicResolvers;
for (var i = 0; i < dynamicResolvers.length; i++) {
var dynamicResolver = dynamicResolvers[i];
var converted = dynamicResolver(type, value);
if (converted !== undefined) {
return converted;
}
}
if (value == null) {
return null;
}
var converter = CONVERSIONS[type];
if (converter) {
return converter(value);
}
throw "Unknown conversion : " + type;
}
function isType(o, type) {
return Object.prototype.toString.call(o) === "[object " + type + "]";
}
function evaluate(src, ctx) {
ctx = ctx || {};
var tokens = _lexer.tokenize(src);
if (_parser.commandStart(tokens.currentToken())) {
var commandList = _parser.parseElement("commandList", tokens);
var last = commandList;
while (last.next) {
last = last.next;
}
last.next = {
op : function() {
return HALT;
}
}
commandList.execute(ctx);
} else if (_parser.featureStart(tokens.currentToken())) {
var hyperscript = _parser.parseElement("hyperscript", tokens);
hyperscript.apply(document.body, null);
return null;
} else {
var expression = _parser.parseElement("expression", tokens);
return expression.evaluate(ctx);
}
}
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);
hyperScript.apply(target || elt, 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" || str === "I") {
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, context) {
if (command) {
if (command.resolveNext) {
return command.resolveNext(context);
} else if (command.next) {
return command.next;
} else {
return findNext(command.parent, context)
}
}
}
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;
}
function getHyperTrace(ctx, thrown) {
var trace = [];
var root = ctx;
while(root.meta.caller) {
root = root.meta.caller;
}
if (root.meta.traceMap) {
return root.meta.traceMap.get(thrown, trace);
}
}
function registerHyperTrace(ctx, thrown) {
var trace = [];
var root = null;
while(ctx != null) {
trace.push(ctx);
root = ctx;
ctx = ctx.meta.caller;
}
if (root.meta.traceMap == null) {
root.meta.traceMap = new Map(); // TODO - WeakMap?
}
if (!root.meta.traceMap.get(thrown)) {
var traceEntry = {
trace: trace,
print : function(logger) {
logger = logger || console.error;
logger("hypertrace /// ")
var maxLen = 0;
for (var i = 0; i < trace.length; i++) {
maxLen = Math.max(maxLen, trace[i].meta.feature.displayName.length);
}
for (var i = 0; i < trace.length; i++) {
var traceElt = trace[i];
logger(" ->", traceElt.meta.feature.displayName.padEnd(maxLen + 2), "-", traceElt.meta.owner)
}
}
};
root.meta.traceMap.set(thrown, traceEntry);
}
}
var hyperscriptUrl = 'document' in globalScope ? document.currentScript.src : null
return {
typeCheck: typeCheck,
forEach: forEach,
triggerEvent: triggerEvent,
matchesSelector: matchesSelector,
getScript: getScript,
processNode: processNode,
evaluate: evaluate,
getScriptSelector: getScriptSelector,
resolveSymbol: resolveSymbol,
makeContext: makeContext,
findNext: findNext,
unifiedEval: unifiedEval,
convertValue: convertValue,
unifiedExec: unifiedExec,
resolveProperty: resolveProperty,
assignToNamespace: assignToNamespace,
registerHyperTrace: registerHyperTrace,
getHyperTrace: getHyperTrace,
getInternalData: getInternalData,
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) {
var rawValue = stringToken.value;
if (stringToken.template) {
var innerTokens = _lexer.tokenize(rawValue, true);
var args = parser.parseStringTemplate(innerTokens);
} else {
var args = [];
}
return {
type: "string",
token: stringToken,
args: args,
op: function (context) {
var returnStr = "";
for (var i = 1; i < arguments.length; i++) {
var val = arguments[i];
if (val) {
returnStr += val;
}
}
return returnStr;
},
evaluate: function (context) {
if (args.length === 0) {
return rawValue;
} else {
return runtime.unifiedEval(this, context);
}
}
};
}
})
_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",
css: elementId.value,
value: elementId.value.substr(1),
evaluate: function (context) {
return document.getElementById(this.value);
}
};
}
})
_parser.addLeafExpression("classRef", function(parser, runtime, tokens) {
var classRef = tokens.matchTokenType('CLASS_REF');
if (classRef) {
return {
type: "classRef",
css: classRef.value,
className: function () {
return this.css.substr(1);
},
evaluate: function () {
return document.querySelectorAll(this.css);
}
};
}
})
_parser.addLeafExpression("queryRef", function(parser, runtime, tokens) {
var queryStart = tokens.matchOpToken('<');
if (queryStart) {
var queryTokens = tokens.consumeUntil("/");
tokens.requireOpToken("/");
tokens.requireOpToken(">");
var queryValue = queryTokens.map(function(t){return t.value}).join("");
return {
type: "queryRef",
css: queryValue,
evaluate: function () {
return document.querySelectorAll(this.css);
}
};
}
})
_parser.addGrammarElement("attributeRef", function(parser, runtime, tokens) {
if (tokens.matchOpToken("[")) {
var content = tokens.consumeUntil("]");
var contentStr = content.map(function (t) {
return t.value
}).join("");
var values = contentStr.split("=");
var name = values[0];
var value = values[1];
tokens.requireOpToken("]");
return {
type: "attribute_expression",
name: name,
value: value,
args: [value],
op:function(context, value){
if (this.value) {
return {name:this.name, value:value}
} else {
return {name:this.name};
}
},
evaluate: function (context) {
return runtime.unifiedEval(this, context);
}
}
}
})
_parser.addLeafExpression("objectLiteral", function(parser, runtime, tokens) {
if (tokens.matchOpToken("{")) {
var fields = []
var valueExpressions = []
if (!tokens.matchOpToken("}")) {
do {
var name = tokens.requireTokenType("IDENTIFIER", "STRING");
tokens.requireOpToken(":");
var value = parser.requireElement("expression", tokens);
valueExpressions.push(value);
fields.push({name: name, value: value});
} while (tokens.matchOpToken(","))
tokens.requireOpToken("}");
}
return {
type: "objectLiteral",
fields: fields,
args: [valueExpressions],
op:function(context, values){
var returnVal = {};
for (var i = 0; i < values.length; i++) {
var field = fields[i];
returnVal[field.name.value] = values[i];
}
return returnVal;
},
evaluate: function (context) {
return runtime.unifiedEval(this, context);
}
}
}
})
_parser.addGrammarElement("namedArgumentList", function(parser, runtime, tokens) {
if (tokens.matchOpToken("(")) {
var fields = []
var valueExpressions = []
if (!tokens.matchOpToken(")")) {
do {
var name = tokens.requireTokenType("IDENTIFIER");
tokens.requireOpToken(":");
var value = parser.requireElement("expression", tokens);
valueExpressions.push(value);
fields.push({name: name, value: value});
} while (tokens.matchOpToken(","))
tokens.requireOpToken(")");
}
return {
type: "namedArgumentList",
fields: fields,
args:[valueExpressions],
op:function(context, values){
var returnVal = {_namedArgList_:true};
for (var i = 0; i < values.length; i++) {
var field = fields[i];
returnVal[field.name.value] = values[i];
}
return returnVal;
},
evaluate: function (context) {
return runtime.unifiedEval(this, context);
}
}
}
})
_parser.addGrammarElement("symbol", function(parser, runtime, tokens) {
var identifier = tokens.matchTokenType('IDENTIFIER');
if (identifier) {
return {
type: "symbol",
token: identifier,
name: identifier.value,
evaluate: function (context) {
return runtime.resolveSymbol(identifier.value, context);
}
};
}
});
_parser.addGrammarElement("implicitMeTarget", function(parser, runtime, tokens) {
return {
type: "implicitMeTarget",
evaluate: function (context) {
return context.me
}
};
});
_parser.addGrammarElement("implicitAllTarget", function(parser, runtime, tokens) {
return {
type: "implicitAllTarget",
evaluate: function (context) {
return document.querySelectorAll("*");
}
};
});
_parser.addLeafExpression("boolean", function(parser, runtime, tokens) {
var booleanLiteral = tokens.matchToken("true") || tokens.matchToken("false");
if (booleanLiteral) {
return {
type: "boolean",
evaluate: function (context) {
return booleanLiteral.value === "true";
}
}
}
});
_parser.addLeafExpression("null", function(parser, runtime, tokens) {
if (tokens.matchToken('null')) {
return {
type: "null",
evaluate: function (context) {
return null;
}
}
}
});
_parser.addLeafExpression("arrayLiteral", function(parser, runtime, tokens) {
if (tokens.matchOpToken('[')) {
var values = [];
if (!tokens.matchOpToken(']')) {
do {
var expr = parser.requireElement("expression", tokens);
values.push(expr);
} while (tokens.matchOpToken(","))
tokens.requireOpToken("]");
}
return {
type: "arrayLiteral",
values: values,
args: [values],
op:function(context, values){
return values;
},
evaluate: function (context) {
return runtime.unifiedEval(this, context);
}
}
}
});
_parser.addLeafExpression("blockLiteral", function(parser, runtime, tokens) {
if (tokens.matchOpToken('\\')) {
var args = []
var arg1 = tokens.matchTokenType("IDENTIFIER");
if (arg1) {
args.push(arg1);
while (tokens.matchOpToken(",")) {
args.push(tokens.requireTokenType("IDENTIFIER"));
}
}
// TODO compound op token
tokens.requireOpToken("-");
tokens.requireOpToken(">");
var expr = parser.requireElement("expression", tokens);
return {
type: "blockLiteral",
args: args,
expr: expr,
evaluate: function (ctx) {
var returnFunc = function(){
//TODO - push scope
for (var i = 0; i < args.length; i++) {
ctx[args[i].value] = arguments[i];
}
return expr.evaluate(ctx) //OK
}
return returnFunc;
}
}
}
});
_parser.addGrammarElement("timeExpression", function(parser, runtime, tokens){
var time = parser.requireElement("expression", tokens);
var factor = 1;
if (tokens.matchToken("s") || tokens.matchToken("seconds")) {
factor = 1000;
} else if (tokens.matchToken("ms") || tokens.matchToken("milliseconds")) {
// do nothing
}
return {
type:"timeExpression",
time: time,
factor: factor,
args: [time],
op: function (context, val) {
return val * this.factor
},
evaluate: function (context) {
return runtime.unifiedEval(this, context);
}
}
})
_parser.addIndirectExpression("propertyAccess", function(parser, runtime, tokens, root) {
if (tokens.matchOpToken(".")) {
var prop = tokens.requireTokenType("IDENTIFIER");
var propertyAccess = {
type: "propertyAccess",
root: root,
prop: prop,
args: [root],
op:function(context, rootVal){
var value = runtime.resolveProperty(rootVal, prop.value);
return value;
},
evaluate: function (context) {
return runtime.unifiedEval(this, context);
}
};
return parser.parseElement("indirectExpression", tokens, propertyAccess);
}
});
_parser.addIndirectExpression("of", function(parser, runtime, tokens, root) {
if (tokens.matchToken("of")) {
var newRoot = parser.requireElement('expression', tokens);
// find the urroot
var childOfUrRoot = null;
var urRoot = root;
while (urRoot.root) {
childOfUrRoot = urRoot;
urRoot = urRoot.root;
}
if (urRoot.type !== 'symbol') {
parser.raiseParseError(tokens, "Cannot take a property of a non-symbol");
}
var prop = urRoot.name;
var propertyAccess = {
type: "ofExpression",
prop: urRoot.token,
root: newRoot,
expression: root,
args: [newRoot],
op:function(context, rootVal){
return runtime.resolveProperty(rootVal, prop);
},
evaluate: function (context) {
return runtime.unifiedEval(this, context);
}
};
if (childOfUrRoot) {
childOfUrRoot.root = propertyAccess;
childOfUrRoot.args = [propertyAccess];
} else {
root = propertyAccess;
}
return parser.parseElement("indirectExpression", tokens, root);
}
});
_parser.addIndirectExpression("inExpression", function(parser, runtime, tokens, root) {
if (tokens.matchToken("in")) {
if (root.type !== "idRef" && root.type === "queryRef" || root.type === "classRef") {
var query = true;
}
var target = parser.requireElement("expression", tokens);
var propertyAccess = {
type: "inExpression",
root: root,
args: [query ? null : root, target],
op:function(context, rootVal, target){
var returnArr = [];
if(query){
runtime.forEach(target, function (targetElt) {
var results = targetElt.querySelectorAll(root.css);
for (var i = 0; i < results.length; i++) {
returnArr.push(results[i]);
}
})
} else {
runtime.forEach(rootVal, function(rootElt){
runtime.forEach(target, function(targetElt){
if (rootElt === targetElt) {
returnArr.push(rootElt);
}
})
})
}
if (returnArr.length > 0) {
return returnArr;
} else {
return null;
}
},
evaluate: function (context) {
return runtime.unifiedEval(this, context);
}
};
return parser.parseElement("indirectExpression", tokens, propertyAccess);
}
});
_parser.addIndirectExpression("asExpression", function(parser, runtime, tokens, root) {
if (tokens.matchToken("as")) {
var conversion = parser.requireElement('dotOrColonPath', tokens).evaluate(); // OK No promise
var propertyAccess = {
type: "asExpression",
root: root,
args: [root],
op:function(context, rootVal){
return runtime.convertValue(rootVal, conversion);
},
evaluate: function (context) {
return runtime.unifiedEval(this, context);
}
};
return parser.parseElement("indirectExpression", tokens, propertyAccess);
}
});
_parser.addIndirectExpression("functionCall", function(parser, runtime, tokens, root) {
if (tokens.matchOpToken("(")) {
var args = [];
if (!tokens.matchOpToken(')')) {
do {
args.push(parser.requireElement("expression", tokens));
} while (tokens.matchOpToken(","))
tokens.requireOpToken(")");
}
if (root.root) {
var functionCall = {
type: "functionCall",
root: root,
argExressions: args,
args: [root.root, args],
op: function (context, rootRoot, args) {
var func = rootRoot[root.prop.value];
if (func.hyperfunc) {
args.push(context);
}
return func.apply(rootRoot, args);
},
evaluate: function (context) {
return runtime.unifiedEval(this, context);
}
}
} else {
var functionCall = {
type: "functionCall",
root: root,
argExressions: args,
args: [root, args],
op: function(context, func, argVals){
if (func.hyperfunc) {
argVals.push(context);
}
var apply = func.apply(null, argVals);
return apply;
},
evaluate: function (context) {
return runtime.unifiedEval(this, context);
}
}
}
return parser.parseElement("indirectExpression", tokens, functionCall);
}
});
_parser.addIndirectExpression("arrayIndex", function (parser, runtime, tokens, root) {
if (tokens.matchOpToken("[")) {
var index = parser.requireElement("expression", tokens);
tokens.requireOpToken("]")
var arrayIndex = {
type: "arrayIndex",
root: root,
index: index,
args: [root, index],
op: function(ctx, root, index) {
return root[index]
},
evaluate: function(context){
return _runtime.unifiedEval(this, context);
}
};
return _parser.parseElement("indirectExpression", tokens, arrayIndex);
}
});
_parser.addGrammarElement("postfixExpression", function(parser, runtime, tokens) {
var root = parser.parseElement("primaryExpression", tokens);
if (tokens.matchOpToken(":")) {
var typeName = tokens.requireTokenType("IDENTIFIER");
var nullOk = !tokens.matchOpToken("!");
return {
type: "typeCheck",
typeName: typeName,
root: root,
nullOk: nullOk,
args: [root],
op: function (context, val) {
return runtime.typeCheck(val, this.typeName.value, this.nullOk);
},
evaluate: function (context) {
return runtime.unifiedEval(this, context);
}
}
} else {
return root;
}
});
_parser.addGrammarElement("logicalNot", function(parser, runtime, tokens) {
if (tokens.matchToken("not")) {
var root = parser.requireElement("unaryExpression", tokens);
return {
type: "logicalNot",
root: root,
args: [root],
op: function (context, val) {
return !val;
},
evaluate: function (context) {
return runtime.unifiedEval(this, context);
}
};
}
});
_parser.addGrammarElement("noExpression", function(parser, runtime, tokens) {
if (tokens.matchToken("no")) {
var root = parser.requireElement("unaryExpression", tokens);
return {
type: "noExpression",
root: root,
args: [root],
op: function (context, val) {
return val == null || val.length === 0;
},
evaluate: function (context) {
return runtime.unifiedEval(this, context);
}
};
}
});
_parser.addGrammarElement("negativeNumber", function(parser, runtime, tokens) {
if (tokens.matchOpToken("-")) {
var root = parser.requireElement("unaryExpression", tokens);
return {
type: "negativeNumber",
root: root,
args: [root],
op:function(context, value){
return -1 * value;
},
evaluate: function (context) {
return runtime.unifiedEval(this, context);
}
};
}
});
_parser.addGrammarElement("unaryExpression", function(parser, runtime, tokens) {
return parser.parseAnyOf(["logicalNot", "noExpression", "negativeNumber", "postfixExpression"], tokens);
});
_parser.addGrammarElement("mathOperator", function(parser, runtime, tokens) {
var expr = parser.parseElement("unaryExpression", tokens);
var mathOp, initialMathOp = null;
mathOp = tokens.matchAnyOpToken("+", "-", "*", "/", "%")
while (mathOp) {
initialMathOp = initialMathOp || mathOp;
var operator = mathOp.value;
if (initialMathOp.value !== operator) {
parser.raiseParseError(tokens, "You must parenthesize math operations with different operators")
}
var rhs = parser.parseElement("unaryExpression", tokens);
expr = {
type: "mathOperator",
lhs: expr,
rhs: rhs,
operator: operator,
args: [expr, rhs],
op:function (context, lhsVal, rhsVal) {
if (this.operator === "+") {
return lhsVal + rhsVal;
} else if (this.operator === "-") {
return lhsVal - rhsVal;
} else if (this.operator === "*") {
return lhsVal * rhsVal;
} else if (this.operator === "/") {
return lhsVal / rhsVal;
} else if (this.operator === "%") {
return lhsVal % rhsVal;
}
},
evaluate: function (context) {
return runtime.unifiedEval(this, context);
}
}
mathOp = tokens.matchAnyOpToken("+", "-", "*", "/", "%")
}
return expr;
});
_parser.addGrammarElement("mathExpression", function(parser, runtime, tokens) {
return parser.parseAnyOf(["mathOperator", "unaryExpression"], tokens);
});
_parser.addGrammarElement("comparisonOperator", function(parser, runtime, tokens) {
var expr = parser.parseElement("mathExpression", tokens);
var comparisonToken = tokens.matchAnyOpToken("<", ">", "<=", ">=", "==", "===", "!=", "!==")
var comparisonStr = comparisonToken ? comparisonToken.value : null;
if (comparisonStr == null) {
if (tokens.matchToken("is") || tokens.matchToken("am")) {
if (tokens.matchToken("not")) {
if (tokens.matchToken("in")) {
comparisonStr = "not in";
} else {
comparisonStr = "!=";
}
} else {
if (tokens.matchToken("in")) {
comparisonStr = "in";
} else {
comparisonStr = "==";
}
}
} else if (tokens.matchToken("matches") || tokens.matchToken("match")) {
comparisonStr = "match";
} else if (tokens.matchToken("contains") || tokens.matchToken("contain")) {
comparisonStr = "contain";
} else if (tokens.matchToken("do") || tokens.matchToken("does")) {
tokens.requireToken('not');
if (tokens.matchToken("matches") || tokens.matchToken("match")) {
comparisonStr = "not match";
} else if (tokens.matchToken("contains") || tokens.matchToken("contain")) {
comparisonStr = "not contain";
} else {
parser.raiseParseError(tokens, "Expected matches or contains");
}
}
}
if (comparisonStr) { // Do not allow chained comparisons, which is dumb
var rhs = parser.requireElement("mathExpression", tokens);
if (comparisonStr === "match" || comparisonStr === "not match") {
rhs = rhs.css ? rhs.css : rhs;
}
expr = {
type: "comparisonOperator",
operator: comparisonStr,
lhs: expr,
rhs: rhs,
args: [expr, rhs],
op:function (context, lhsVal, rhsVal) {
if (this.operator === "==") {
return lhsVal == rhsVal;
} else if (this.operator === "!=") {
return lhsVal != rhsVal;
} if (this.operator === "in") {
return (rhsVal != null) && Array.from(rhsVal).indexOf(lhsVal) >= 0;
} if (this.operator === "not in") {
return (rhsVal == null) || Array.from(rhsVal).indexOf(lhsVal) < 0;
} if (this.operator === "match") {
return (lhsVal != null) && lhsVal.matches(rhsVal);
} if (this.operator === "not match") {
return (lhsVal == null) || !lhsVal.matches(rhsVal);
} if (this.operator === "contain") {
return (lhsVal != null) && lhsVal.contains(rhsVal);
} if (this.operator === "not contain") {
return (lhsVal == null) || !lhsVal.contains(rhsVal);
} if (this.operator === "===") {
return lhsVal === rhsVal;
} else if (this.operator === "!==") {
return lhsVal !== rhsVal;
} else if (this.operator === "<") {
return lhsVal < rhsVal;
} else if (this.operator === ">") {
return lhsVal > rhsVal;
} else if (this.operator === "<=") {
return lhsVal <= rhsVal;
} else if (this.operator === ">=") {
return lhsVal >= rhsVal;
}
},
evaluate: function (context) {
return runtime.unifiedEval(this, context);
}
};
}
return expr;
});
_parser.addGrammarElement("comparisonExpression", function(parser, runtime, tokens) {
return parser.parseAnyOf(["comparisonOperator", "mathExpression"], tokens);
});
_parser.addGrammarElement("logicalOperator", function(parser, runtime, tokens) {
var expr = parser.parseElement("comparisonExpression", tokens);
var logicalOp, initialLogicalOp = null;
logicalOp = tokens.matchToken("and") || tokens.matchToken("or");
while (logicalOp) {
initialLogicalOp = initialLogicalOp || logicalOp;
if (initialLogicalOp.value !== logicalOp.value) {
parser.raiseParseError(tokens, "You must parenthesize logical operations with different operators")
}
var rhs = parser.requireElement("comparisonExpression", tokens);
expr = {
type: "logicalOperator",
operator: logicalOp.value,
lhs: expr,
rhs: rhs,
args: [expr, rhs],
op: function (context, lhsVal, rhsVal) {
if (this.operator === "and") {
return lhsVal && rhsVal;
} else {
return lhsVal || rhsVal;
}
},
evaluate: function (context) {
return runtime.unifiedEval(this, context);
}
}
logicalOp = tokens.matchToken("and") || tokens.matchToken("or");
}
return expr;
});
_parser.addGrammarElement("logicalExpression", function(parser, runtime, tokens) {
return parser.parseAnyOf(["logicalOperator", "mathExpression"], tokens);
});
_parser.addGrammarElement("asyncExpression", function(parser, runtime, tokens) {
if (tokens.matchToken('async')) {
var value = parser.requireElement("logicalExpression", tokens);
var expr = {
type: "asyncExpression",
value: value,
evaluate: function (context) {
return {
asyncWrapper: true,
value: this.value.evaluate(context) //OK
}
}
}
return expr;
} else {
return parser.parseElement("logicalExpression", tokens);
}
});
_parser.addGrammarElement("expression", function(parser, runtime, tokens) {
tokens.matchToken("the"); // optional the
return parser.parseElement("asyncExpression", tokens);
});
_parser.addGrammarElement("target", function(parser, runtime, tokens) {
var expr = _parser.parseElement("expression", tokens);
if (expr.type === "symbol" || expr.type === "idRef" || expr.type === "inExpression" ||
expr.type === "queryRef" || expr.type === "classRef" || expr.type === "ofExpression" ||
expr.type === "propertyAccess") {
return expr;
} else {
_parser.raiseParseError(tokens, "A target expression must be writable");
}
return expr;
});
_parser.addGrammarElement("hyperscript", function(parser, runtime, tokens) {
var features = [];
if (tokens.hasMore()) {
do {
var feature = parser.requireElement("feature", tokens);
features.push(feature);
tokens.matchToken("end"); // optional end
} while (parser.featureStart(tokens.currentToken()) || tokens.currentToken().value === "(")
if (tokens.hasMore()) {
parser.raiseParseError(tokens);
}
}
return {
type: "hyperscript",
features: features,
apply: function (target, source) {
// no op
_runtime.forEach(features, function(feature){
feature.install(target, source);
})
}
};
})
_parser.addFeature("on", function(parser, runtime, tokens) {
if (tokens.matchToken('on')) {
var every = false;
if (tokens.matchToken("every")) {
every = true;
}
var events = [];
var displayName = null;
do {
var on = parser.requireElement("dotOrColonPath", tokens, "Expected event name");
var eventName = on.evaluate(); // OK No Promise
if (displayName) {
displayName = displayName + " or " + eventName;
} else {
displayName = "on " + eventName;
}
var args = [];
// handle argument list (look ahead 3)
if (tokens.token(0).value === "(" &&
(tokens.token(1).value === ")" ||
tokens.token(2).value === "," ||
tokens.token(2).value === ")")) {
tokens.matchOpToken("(");
do {
args.push(tokens.requireTokenType('IDENTIFIER'));
} while (tokens.matchOpToken(","))
tokens.requireOpToken(')')
}
var filter = null;
if (tokens.matchOpToken('[')) {
filter = parser.requireElement("expression", tokens);
tokens.requireOpToken(']');
}
if (tokens.currentToken().type === "NUMBER") {
var startCountToken = tokens.consumeToken();
var startCount = parseInt(startCountToken.value);
if (tokens.matchToken("to")) {
var endCountToken = tokens.consumeToken();
var endCount = parseInt(endCountToken.value);
} else if (tokens.matchToken("and")) {
var unbounded = true;
tokens.requireToken("on");
}
}
var from = null;
var elsewhere = false;
if (tokens.matchToken("from")) {
if (tokens.matchToken('elsewhere')) {
elsewhere = true;
} else {
from = parser.parseElement("target", tokens)
if (!from) {
parser.raiseParseError('Expected either target value or "elsewhere".', tokens);
}
}
}
// support both "elsewhere" and "from elsewhere"
if (from === null && elsewhere === false && tokens.matchToken("elsewhere")) {
elsewhere = true;
}
if (tokens.matchToken('in')) {
var inExpr = parser.parseAnyOf(["idRef", "queryRef", "classRef"], tokens);
}
if (tokens.matchToken('debounced')) {
tokens.requireToken("at");
var timeExpr = parser.requireElement("timeExpression", tokens);
var debounceTime = timeExpr.evaluate({}); // OK No promise TODO make a literal time expr
} else if (tokens.matchToken('throttled')) {
tokens.requireToken("at");
var timeExpr = parser.requireElement("timeExpression", tokens);
var throttleTime = timeExpr.evaluate({}); // OK No promise TODO make a literal time expr
}
events.push({
execCount: 0,
every: every,
on: eventName,
args: args,
filter: filter,
from:from,
inExpr:inExpr,
elsewhere:elsewhere,
startCount : startCount,
endCount : endCount,
unbounded : unbounded,
debounceTime : debounceTime,
throttleTime : throttleTime,
})
} while (tokens.matchToken("or"))
var queue = [];
var queueLast = true;
if (!every) {
if (tokens.matchToken("queue")) {
if (tokens.matchToken("all")) {
var queueAll = true;
var queueLast = false;
} else if(tokens.matchToken("first")) {
var queueFirst = true;
} else if(tokens.matchToken("none")) {
var queueNone = true;
} else {
tokens.requireToken("last");
}
}
}
var start = parser.requireElement("commandList", tokens);
var implicitReturn = {
type: "implicitReturn",
op: function (context) {
// automatically resolve at the end of an event handler if nothing else does
context.meta.resolve();
return runtime.HALT;
},
execute: function (ctx) {
// do nothing
}
};
if (start) {
var end = start;
while (end.next) {
end = end.next;
}
end.next = implicitReturn
} else {
start = implicitReturn
}
var onFeature = {
displayName: displayName,
events:events,
start: start,
every: every,
executing: false,
execCount: 0,
queue: queue,
execute: function (ctx) {
if (this.executing && this.every === false) {
if (queueNone || (queueFirst && queue.length > 0)) {
return;
}
if (queueLast) {
onFeature.queue.length = 0;
}
onFeature.queue.push(ctx);
return;
}
this.execCount++;
this.executing = true;
ctx.meta.resolve = function () {
onFeature.executing = false;
var queued = onFeature.queue.shift();
if (queued) {
setTimeout(function () {
onFeature.execute(queued);
}, 1);
}
}
ctx.meta.reject = function (err) {
console.error(err.message ? err.message : err);
var hypertrace = runtime.getHyperTrace(ctx, err);
if (hypertrace) {
hypertrace.print();
}
runtime.triggerEvent(ctx.me, 'exception', {error: err})
onFeature.executing = false;
var queued = onFeature.queue.shift();
if (queued) {
setTimeout(function () {
onFeature.execute(queued);
}, 1);
}
}
start.execute(ctx);
},
install: function (elt, source) {
runtime.forEach(onFeature.events, function(eventSpec) {
var targets;
if (eventSpec.elsewhere) {
targets = [document];
} else if (eventSpec.from) {
targets = eventSpec.from.evaluate({});
} else {
targets = [elt];
}
runtime.forEach(targets, function (target) { // OK NO PROMISE
target.addEventListener(eventSpec.on, function (evt) { // OK NO PROMISE
var ctx = runtime.makeContext(elt, onFeature, elt, evt);
if (eventSpec.elsewhere && elt.contains(evt.target)) {
return
}
// establish context
runtime.forEach(eventSpec.args, function (arg) {
ctx[arg.value] = ctx.event[arg.value] || (ctx.event.detail ? ctx.event.detail[arg.value] : null);
});
// apply filter
if (eventSpec.filter) {
var initialCtx = ctx.meta.context;
ctx.meta.context = ctx.event;
try {
var value = eventSpec.filter.evaluate(ctx); //OK NO PROMISE
if (value) {
// match the javascript semantics for if statements
} else {
return;
}
} finally {
ctx.meta.context = initialCtx;
}
}
if (eventSpec.inExpr) {
var inElement = evt.target;
while(true) {
if (inElement.matches && inElement.matches(eventSpec.inExpr.css)) {
ctx.it = inElement;
break;
} else {
inElement = inElement.parentElement;
if (inElement == null) {
return; // no match found
}
}
}
}
// verify counts
eventSpec.execCount++;
if (eventSpec.startCount) {
if (eventSpec.endCount) {
if (eventSpec.execCount < eventSpec.startCount ||
eventSpec.execCount > eventSpec.endCount) {
return;
}
} else if (eventSpec.unbounded) {
if (eventSpec.execCount < eventSpec.startCount) {
return;
}
} else if (eventSpec.execCount !== eventSpec.startCount) {
return;
}
}
//debounce
if (eventSpec.debounceTime) {
if (eventSpec.debounced) {
clearTimeout(eventSpec.debounced);
}
eventSpec.debounced = setTimeout(function () {
onFeature.execute(ctx);
}, eventSpec.debounceTime);
return;
}
// throttle
if (eventSpec.throttleTime) {
if (eventSpec.lastExec && Date.now() < eventSpec.lastExec + eventSpec.throttleTime) {
return;
} else {
eventSpec.lastExec = Date.now();
}
}
// apply execute
onFeature.execute(ctx);
});
})
});
}
};
parser.setParent(start, onFeature);
return onFeature;
}
});
_parser.addFeature("def", function(parser, runtime, tokens) {
if (tokens.matchToken('def')) {
var functionName = parser.requireElement("dotOrColonPath", tokens);
var nameVal = functionName.evaluate(); // OK
var nameSpace = nameVal.split(".");
var funcName = nameSpace.pop();
var args = [];
if (tokens.matchOpToken("(")) {
if (tokens.matchOpToken(")")) {
// emtpy args list
} else {
do {
args.push(tokens.requireTokenType('IDENTIFIER'));
} while (tokens.matchOpToken(","))
tokens.requireOpToken(')')
}
}
var start = parser.parseElement("commandList", tokens);
if (tokens.matchToken('catch')) {
var errorSymbol = tokens.requireTokenType('IDENTIFIER').value;
var errorHandler = parser.parseElement("commandList", tokens);
}
var functionFeature = {
displayName: funcName + "(" + args.map(function(arg){ return arg.value }).join(", ") + ")",
name: funcName,
args: args,
start: start,
errorHandler: errorHandler,
errorSymbol: errorSymbol,
install: function (target, source) {
var func = function () {
// null, worker
var elt = 'document' in globalScope ? document.body : globalScope
var ctx = runtime.makeContext(source, functionFeature, elt, null);
// install error handler if any
ctx.meta.errorHandler = errorHandler;
ctx.meta.errorSymmbol = errorSymbol;
for (var i = 0; i < args.length; i++) {
var name = args[i];
var argumentVal = arguments[i];
if (name) {
ctx[name.value] = argumentVal;
}
}
ctx.meta.caller = arguments[args.length];
var resolve, reject = null;
var promise = new Promise(function (theResolve, theReject) {
resolve = theResolve;
reject = theReject;
});
start.execute(ctx);
if (ctx.meta.returned) {
return ctx.meta.returnValue;
} else {
ctx.meta.resolve = resolve;
ctx.meta.reject = reject;
return promise
}
};
func.hyperfunc = true;
runtime.assignToNamespace(nameSpace, funcName, func);
}
};
var implicitReturn = {
type: "implicitReturn",
op: function (context) {
// automatically return at the end of the function if nothing else does
context.meta.returned = true;
if (context.meta.resolve) {
context.meta.resolve();
}
return runtime.HALT;
},
execute: function (context) {
// do nothing
}
}
// terminate body
if (start) {
var end = start;
while (end.next) {
end = end.next;
}
end.next = implicitReturn
} else {
functionFeature.start = implicitReturn
}
// terminate error handler
if (errorHandler) {
var end = errorHandler;
while (end.next) {
end = end.next;
}
end.next = implicitReturn
}
parser.setParent(start, functionFeature);
return functionFeature;
}
});
_parser.addFeature("init", function(parser, runtime, tokens) {
if (tokens.matchToken('init')) {
var start = parser.parseElement("commandList", tokens);
var initFeature = {
start: start,
install: function (target, source) {
setTimeout(function () {
start.execute(runtime.makeContext(target, this, target));
}, 0);
}
};
var implicitReturn = {
type: "implicitReturn",
op: function (context) {
return runtime.HALT;
},
execute: function (context) {
// do nothing
}
}
// terminate body
if (start) {
var end = start;
while (end.next) {
end = end.next;
}
end.next = implicitReturn
} else {
initFeature.start = implicitReturn
}
parser.setParent(start, initFeature);
return initFeature;
}
});
_parser.addFeature("worker", function (parser, runtime, tokens) {
if (tokens.matchToken("worker")) {
parser.raiseParseError(tokens,
"In order to use the 'worker' feature, include " +
"the _hyperscript worker plugin. See " +
"https://hyperscript.org/features/worker/ for " +
"more info.")
}
})
_parser.addGrammarElement("jsBody", function(parser, runtime, tokens) {
var jsSourceStart = tokens.currentToken().start;
var jsLastToken = tokens.currentToken();
var funcNames = [];
var funcName = "";
var expectFunctionDeclaration = false;
while (tokens.hasMore()) {
jsLastToken = tokens.consumeToken();
var peek = tokens.currentToken(true);
if (peek.type === "IDENTIFIER"
&& peek.value === "end") {
break;
}
if (expectFunctionDeclaration) {
if (jsLastToken.type === "IDENTIFIER"
|| jsLastToken.type === "NUMBER") {
funcName += jsLastToken.value;
} else {
if (funcName !== "") funcNames.push(funcName);
funcName = "";
expectFunctionDeclaration = false;
}
} else if (jsLastToken.type === "IDENTIFIER"
&& jsLastToken.value === "function") {
expectFunctionDeclaration = true;
}
}
var jsSourceEnd = jsLastToken.end + 1;
return {
type: 'jsBody',
exposedFunctionNames: funcNames,
jsSource: tokens.source.substring(jsSourceStart, jsSourceEnd),
}
})
_parser.addFeature("js", function(parser, runtime, tokens) {
if (tokens.matchToken('js')) {
var jsBody = parser.parseElement('jsBody', tokens);
var jsSource = jsBody.jsSource +
"\nreturn { " +
jsBody.exposedFunctionNames.map(function (name) {
return name+":"+name;
}).join(",") +
" } ";
var func = new Function(jsSource);
return {
jsSource: jsSource,
function: func,
exposedFunctionNames: jsBody.exposedFunctionNames,
install: function() {
mergeObjects(globalScope, func())
}
}
}
})
_parser.addCommand("js", function (parser, runtime, tokens) {
if (tokens.matchToken("js")) {
// Parse inputs
var inputs = [];
if (tokens.matchOpToken("(")) {
if (tokens.matchOpToken(")")) {
// empty input list
} else {
do {
var inp = tokens.requireTokenType('IDENTIFIER');
inputs.push(inp.value);
} while (tokens.matchOpToken(","));
tokens.requireOpToken(')');
}
}
var jsBody = parser.parseElement('jsBody', tokens);
tokens.matchToken('end');
var func = varargConstructor(Function, inputs.concat([jsBody.jsSource]));
return {
jsSource: jsBody.jsSource,
function: func,
inputs: inputs,
op: function (context) {
var args = [];
inputs.forEach(function (input) {
args.push(runtime.resolveSymbol(input, context))
});
var result = func.apply(globalScope, args)
if (result && typeof result.then === 'function') {
return Promise(function (resolve) {
result.then(function (actualResult) {
context.it = actualResult
resolve(runtime.findNext(this, context));
})
})
} else {
context.it = result
return runtime.findNext(this, context);
}
}
};
}
})
_parser.addCommand("async", function (parser, runtime, tokens) {
if (tokens.matchToken("async")) {
if (tokens.matchToken("do")) {
var body = parser.requireElement('commandList', tokens)
tokens.requireToken("end")
} else {
var body = parser.requireElement('command', tokens)
}
return {
body: body,
op: function (context) {
setTimeout(function(){
body.execute(context);
})
return runtime.findNext(this, context);
}
};
}
})
_parser.addCommand("with", function (parser, runtime, tokens) {
var startToken = tokens.currentToken();
if (tokens.matchToken("with")) {
var value = parser.requireElement("expression", tokens);
var body = parser.requireElement('commandList', tokens)
if (tokens.hasMore()) {
tokens.requireToken("end");
}
var slot = "with_" + startToken.start;
var withCmd = {
value: value,
body: body,
args: [value],
resolveNext: function (context) {
var iterator = context.meta.iterators[slot];
if (iterator.index < iterator.value.length) {
context.me = iterator.value[iterator.index++];
return body;
} else {
// restore original me
context.me = iterator.originalMe;
if (this.next) {
return this.next;
} else {
return runtime.findNext(this.parent, context);
}
}
},
op: function (context, value) {
if (value == null) {
value = [];
} else if (!(Array.isArray(value) || value instanceof NodeList)) {
value = [value];
}
context.meta.iterators[slot] = {
originalMe: context.me,
index: 0,
value: value
};
return this.resolveNext(context);
}
};
parser.setParent(body, withCmd);
return withCmd;
}
})
_parser.addCommand("wait", function(parser, runtime, tokens) {
if (tokens.matchToken("wait")) {
// wait on event
if (tokens.matchToken("for")) {
tokens.matchToken("a"); // optional "a"
var evt = _parser.requireElement("dotOrColonPath", tokens, "Expected event name");
if (tokens.matchToken("from")) {
var on = parser.requireElement("expression", tokens);
}
// wait on event
var waitCmd = {
event: evt,
on: on,
args: [evt, on],
op: function (context, eventName, on) {
var target = on ? on : context.me;
return new Promise(function (resolve) {
var listener = function () {
resolve(runtime.findNext(waitCmd, context));
};
target.addEventListener(eventName, listener, {once: true});
});
}
};
} else {
var time = _parser.requireElement("timeExpression", tokens);
var waitCmd = {
type: "waitCmd",
time: time,
args: [time],
op: function (context, timeValue) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(runtime.findNext(waitCmd, context));
}, timeValue);
});
},
execute: function (context) {
return runtime.unifiedExec(this, context);
}
};
}
return waitCmd
}
})
// TODO - colon path needs to eventually become part of ruby-style symbols
_parser.addGrammarElement("dotOrColonPath", function(parser, runtime, tokens) {
var root = tokens.matchTokenType("IDENTIFIER");
if (root) {
var path = [root.value];
var separator = tokens.matchOpToken(".") || tokens.matchOpToken(":");
if (separator) {
do {
path.push(tokens.requireTokenType("IDENTIFIER").value);
} while (tokens.matchOpToken(separator.value))
}
return {
type: "dotOrColonPath",
path: path,
evaluate: function () {
return path.join(separator ? separator.value : "");
}
}
}
});
_parser.addCommand("send", function(parser, runtime, tokens) {
if (tokens.matchToken('send')) {
var eventName = parser.requireElement("dotOrColonPath", tokens);
var details = parser.parseElement("namedArgumentList", tokens);
if (tokens.matchToken("to")) {
var to = parser.requireElement("target", tokens);
} else {
var to = parser.requireElement("implicitMeTarget");
}
var sendCmd = {
eventName: eventName,
details: details,
to: to,
args: [to, eventName, details],
op: function (context, to, eventName, details) {
runtime.forEach(to, function (target) {
runtime.triggerEvent(target, eventName, details ? details : {});
});
return runtime.findNext(sendCmd, context);
}
};
return sendCmd
}
})
_parser.addCommand("return", function(parser, runtime, tokens) {
if (tokens.matchToken('return')) {
var value = parser.requireElement("expression", tokens);
var returnCmd = {
value: value,
args: [value],
op: function (context, value) {
var resolve = context.meta.resolve;
context.meta.returned = true;
if (resolve) {
if (value) {
resolve(value);
} else {
resolve()
}
} else {
context.meta.returned = true;
context.meta.returnValue = value;
}
return runtime.HALT;
}
};
return returnCmd
}
})
_parser.addCommand("log", function(parser, runtime, tokens) {
if (tokens.matchToken('log')) {
var exprs = [parser.parseElement("expression", tokens)];
while (tokens.matchOpToken(",")) {
exprs.push(parser.requireElement("expression", tokens));
}
if (tokens.matchToken("with")) {
var withExpr = parser.requireElement("expression", tokens);
}
var logCmd = {
exprs: exprs,
withExpr: withExpr,
args: [withExpr, exprs],
op: function (ctx, withExpr, values) {
if (withExpr) {
withExpr.apply(null, values);
} else {
console.log.apply(null, values);
}
return runtime.findNext(this, ctx);
}
};
return logCmd;
}
})
_parser.addCommand("throw", function(parser, runtime, tokens) {
if (tokens.matchToken('throw')) {
var expr = parser.requireElement("expression", tokens);
var throwCmd = {
expr: expr,
args: [expr],
op: function (ctx, expr) {
runtime.registerHyperTrace(ctx, expr);
var reject = ctx.meta && ctx.meta.reject;
if (reject) {
reject(expr);
return runtime.HALT;
} else {
throw expr;
}
}
};
return throwCmd;
}
})
var parseCallOrGet = function(parser, runtime, tokens) {
var expr = parser.requireElement("expression", tokens);
var callCmd = {
expr: expr,
args: [expr],
op: function (context, it) {
context.it = it;
return runtime.findNext(callCmd, context);
}
};
return callCmd
}
_parser.addCommand("call", function(parser, runtime, tokens) {
if (tokens.matchToken('call')) {
var call = parseCallOrGet(parser, runtime, tokens);
if (call.expr && call.expr.type !== "functionCall") {
parser.raiseParseError(tokens, "Must be a function invocation");
}
return call;
}
})
_parser.addCommand("get", function(parser, runtime, tokens) {
if (tokens.matchToken('get')) {
return parseCallOrGet(parser, runtime, tokens);
}
})
_parser.addCommand("set", function(parser, runtime, tokens) {
if (tokens.matchToken('set')) {
var target = parser.requireElement("target", tokens);
tokens.requireToken("to");
var value = parser.requireElement("expression", tokens);
var symbolWrite = target.type === "symbol";
if (target.type !== "symbol" && target.root == null) {
parser.raiseParseError(tokens, "Can only put directly into symbols, not references")
}
var root = null;
var prop = null;
if (symbolWrite) {
// root is null
} else {
prop = target.prop.value;
root = target.root;
}
var setCmd = {
target: target,
symbolWrite: symbolWrite,
value: value,
args: [root, value],
op: function (context, root, valueToSet) {
if (symbolWrite) {
context[target.name] = valueToSet;
} else {
runtime.forEach(root, function (elt) {
elt[prop] = valueToSet;
})
}
return runtime.findNext(this, context);
}
};
return setCmd
}
})
_parser.addCommand("if", function(parser, runtime, tokens) {
if (tokens.matchToken('if')) {
var expr = parser.requireElement("expression", tokens);
tokens.matchToken("then"); // optional 'then'
var trueBranch = parser.parseElement("commandList", tokens);
if (tokens.matchToken("else")) {
var falseBranch = parser.parseElement("commandList", tokens);
}
if (tokens.hasMore()) {
tokens.requireToken("end");
}
var ifCmd = {
expr: expr,
trueBranch: trueBranch,
falseBranch: falseBranch,
args: [expr],
op: function (context, expr) {
if (expr) {
return trueBranch;
} else if (falseBranch) {
return falseBranch;
} else {
return runtime.findNext(this, context);
}
}
};
parser.setParent(trueBranch, ifCmd);
parser.setParent(falseBranch, ifCmd);
return ifCmd
}
})
var parseRepeatExpression = function(parser, tokens, runtime, startedWithForToken) {
var innerStartToken = tokens.currentToken();
if (tokens.matchToken("for") || startedWithForToken) {
var identifierToken = tokens.requireTokenType('IDENTIFIER');
var identifier = identifierToken.value;
tokens.requireToken("in");
var expression = parser.requireElement("expression", tokens);
} else if (tokens.matchToken("in")) {
var identifier = "it";
var expression = parser.requireElement("expression", tokens);
} else if (tokens.matchToken("while")) {
var whileExpr = parser.requireElement("expression", tokens);
} else if (tokens.matchToken("until")) {
var isUntil = true;
if (tokens.matchToken("event")) {
var evt = _parser.requireElement("dotOrColonPath", tokens, "Expected event name");
if (tokens.matchToken("from")) {
var on = parser.requireElement("expression", tokens);
}
} else {
var whileExpr = parser.requireElement("expression", tokens);
}
} else if (tokens.matchTokenType('NUMBER')) {
var times = parseFloat(innerStartToken.value);
tokens.requireToken('times');
} else {
tokens.matchToken("forever"); // consume optional forever
var forever = true;
}
if (tokens.matchToken("index")) {
var identifierToken = tokens.requireTokenType('IDENTIFIER');
var indexIdentifier = identifierToken.value
}
var loop = parser.parseElement("commandList", tokens);
if (tokens.hasMore()) {
tokens.requireToken("end");
}
if (identifier == null) {
identifier = "_implicit_repeat_" + innerStartToken.start;
var slot = identifier;
} else {
var slot = identifier + "_" + innerStartToken.start;
}
var repeatCmd = {
identifier: identifier,
indexIdentifier: indexIdentifier,
slot: slot,
expression: expression,
forever: forever,
times: times,
until: isUntil,
event: evt,
on: on,
whileExpr: whileExpr,
resolveNext: function () {
return this;
},
loop: loop,
args: [whileExpr],
op: function (context, whileValue) {
var iterator = context.meta.iterators[slot];
var keepLooping = false;
if (this.forever) {
keepLooping = true;
} else if (this.until) {
if (evt) {
keepLooping = context.meta.iterators[slot].eventFired === false;
} else {
keepLooping = whileValue !== true;
}
} else if (whileValue) {
keepLooping = true;
} else if (times) {
keepLooping = iterator.index < this.times;
} else {
keepLooping = iterator.value !== null && iterator.index < iterator.value.length
}
if (keepLooping) {
if (iterator.value) {
context[identifier] = iterator.value[iterator.index];
context.it = iterator.value[iterator.index];
} else {
context.it = iterator.index;
}
if (indexIdentifier) {
context[indexIdentifier] = iterator.index;
}
iterator.index++;
return loop;
} else {
context.meta.iterators[slot] = null;
return runtime.findNext(this.parent, context);
}
}
};
parser.setParent(loop, repeatCmd);
var repeatInit = {
name: "repeatInit",
args: [expression, evt, on],
op: function (context, value, event, on) {
context.meta.iterators[slot] = {
index: 0,
value: value,
eventFired: false
};
if (evt) {
var target = on || context.me;
target.addEventListener(event, function (e) {
context.meta.iterators[slot].eventFired = true;
}, {once: true});
}
return repeatCmd; // continue to loop
},
execute: function (context) {
return runtime.unifiedExec(this, context);
}
}
parser.setParent(repeatCmd, repeatInit);
return repeatInit
}
_parser.addCommand("repeat", function(parser, runtime, tokens) {
if (tokens.matchToken('repeat')) {
return parseRepeatExpression(parser, tokens, runtime,false);
}
})
_parser.addCommand("for", function(parser, runtime, tokens) {
if (tokens.matchToken('for')) {
return parseRepeatExpression(parser, tokens, runtime, true);
}
})
_parser.addGrammarElement("stringLike", function(parser, runtime, tokens) {
return _parser.parseAnyOf(["string", "nakedString"], tokens);
});
_parser.addCommand("fetch", function(parser, runtime, tokens) {
if (tokens.matchToken('fetch')) {
var url = parser.requireElement("stringLike", tokens);
var args = parser.parseElement("objectLiteral", tokens);
var type = "text";
if (tokens.matchToken("as")) {
if (tokens.matchToken("json")) {
type = "json";
} else if (tokens.matchToken("response")) {
type = "response";
} else if (tokens.matchToken("text")) {
} else {
parser.raiseParseError(tokens, "Unknown response type: " + tokens.currentToken());
}
}
var fetchCmd = {
url:url,
argExrepssions:args,
args: [url, args],
op: function (context, url, args) {
return new Promise(function (resolve, reject) {
fetch(url, args)
.then(function (value) {
if (type === "response") {
context.it = value;
resolve(runtime.findNext(fetchCmd, context));
} else if (type === "json") {
value.json().then(function (result) {
context.it = result;
resolve(runtime.findNext(fetchCmd, context));
})
} else {
value.text().then(function (result) {
context.it = result;
resolve(runtime.findNext(fetchCmd, context));
})
}
})
.catch(function (reason) {
runtime.triggerEvent(context.me, "fetch:error", {
reason: reason
})
reject(reason);
})
})
}
};
return fetchCmd;
}
})
}
//====================================================================
// Initialization
//====================================================================
function ready(fn) {
if (document.readyState !== 'loading') {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
function getMetaConfig() {
var element = document.querySelector('meta[name="htmx-config"]');
if (element) {
return parseJSON(element.content);
} else {
return null;
}
}
function mergeMetaConfig() {
var metaConfig = getMetaConfig();
if (metaConfig) {
_hyperscript.config = mergeObjects(_hyperscript.config , metaConfig)
}
}
if ('document' in globalScope) {
ready(function () {
mergeMetaConfig();
_runtime.processNode(document.body);
document.addEventListener("htmx:load", function(evt){
_runtime.processNode(evt.detail.elt);
})
})
}
//====================================================================
// API
//====================================================================
return mergeObjects(function (str, ctx) {
return _runtime.evaluate(str, ctx); //OK
}, {
internals: {
lexer: _lexer,
parser: _parser,
runtime: _runtime,
},
addFeature: function (keyword, definition) {
_parser.addFeature(keyword, definition)
},
addCommand: function (keyword, definition) {
_parser.addCommand(keyword, definition)
},
addLeafExpression: function (keyword, definition) {
_parser.addLeafExpression(definition)
},
addIndirectExpression: function (keyword, definition) {
_parser.addIndirectExpression(definition)
},
evaluate: function (str, ctx) { //OK
return _runtime.evaluate(str, ctx); //OK
},
processNode: function (elt) {
_runtime.processNode(elt);
},
config: {
attributes: "_, script, data-script",
defaultTransition: "all 500ms ease-in",
conversions: CONVERSIONS
}
}
)
}
)()
}));
///=========================================================================
/// This module provides the core web functionality for hyperscript
///=========================================================================
(function(){
function mergeObjects(obj1, obj2) {
for (var key in obj2) {
if (obj2.hasOwnProperty(key)) {
obj1[key] = obj2[key];
}
}
return obj1;
}
_hyperscript.addCommand("settle", function(parser, runtime, tokens) {
if (tokens.matchToken("settle")) {
if (!parser.commandBoundary(tokens.currentToken())) {
var on = parser.requireElement("expression", tokens);
} else {
var on = parser.requireElement("implicitMeTarget");
}
var settleCommand = {
type: "settleCmd",
args: [on],
op: function (context, on) {
var resolve = null;
var resolved = false;
var transitionStarted = false;
var promise = new Promise(function (r) {
resolve = r;
});
// listen for a transition begin
on.addEventListener('transitionstart', function () {
transitionStarted = true;
}, {once: true});
// if no transition begins in 500ms, cancel
setTimeout(function () {
if (!transitionStarted && !resolved) {
resolve(runtime.findNext(settleCommand, context));
}
}, 500);
// continue on a transition emd
on.addEventListener('transitionend', function () {
if (!resolved) {
resolve(runtime.findNext(settleCommand, context));
}
}, {once: true});
return promise;
},
execute: function (context) {
return runtime.unifiedExec(this, context);
}
};
return settleCommand
}
})
_hyperscript.addCommand("add", function(parser, runtime, tokens) {
if (tokens.matchToken("add")) {
var classRef = parser.parseElement("classRef", tokens);
var attributeRef = null;
if (classRef == null) {
attributeRef = parser.parseElement("attributeRef", tokens);
if (attributeRef == null) {
parser.raiseParseError(tokens, "Expected either a class reference or attribute expression")
}
}
if (tokens.matchToken("to")) {
var to = parser.requireElement("target", tokens);
} else {
var to = parser.parseElement("implicitMeTarget");
}
if (classRef) {
var addCmd = {
classRef: classRef,
attributeRef: attributeRef,
to: to,
args: [to],
op: function (context, to) {
runtime.forEach(to, function (target) {
target.classList.add(classRef.className());
})
return runtime.findNext(this, context);
}
}
} else {
var addCmd = {
type: "addCmd",
classRef: classRef,
attributeRef: attributeRef,
to: to,
args: [to, attributeRef],
op: function (context, to, attrRef) {
runtime.forEach(to, function (target) {
target.setAttribute(attrRef.name, attrRef.value);
})
return runtime.findNext(addCmd, context);
},
execute: function (ctx) {
return runtime.unifiedExec(this, ctx);
}
};
}
return addCmd
}
});
_hyperscript.addCommand("remove", function(parser, runtime, tokens) {
if (tokens.matchToken('remove')) {
var classRef = parser.parseElement("classRef", tokens);
var attributeRef = null;
var elementExpr = null;
if (classRef == null) {
attributeRef = parser.parseElement("attributeRef", tokens);
if (attributeRef == null) {
elementExpr = parser.parseElement("expression", tokens)
if (elementExpr == null) {
parser.raiseParseError(tokens, "Expected either a class reference, attribute expression or value expression");
}
}
}
if (tokens.matchToken("from")) {
var from = parser.requireElement("target", tokens);
} else {
var from = parser.requireElement("implicitMeTarget");
}
if (elementExpr) {
var removeCmd = {
classRef: classRef,
attributeRef: attributeRef,
elementExpr: elementExpr,
from: from,
args: [elementExpr],
op: function (context, element) {
runtime.forEach(element, function (target) {
target.parentElement.removeChild(target);
})
return runtime.findNext(this, context);
}
};
} else {
var removeCmd = {
classRef: classRef,
attributeRef: attributeRef,
elementExpr: elementExpr,
from: from,
args: [from],
op: function (context, from) {
if (this.classRef) {
runtime.forEach(from, function (target) {
target.classList.remove(classRef.className());
})
} else {
runtime.forEach(from, function (target) {
target.removeAttribute(attributeRef.name);
})
}
return runtime.findNext(this, context);
}
};
}
return removeCmd
}
});
_hyperscript.addCommand("toggle", function(parser, runtime, tokens) {
if (tokens.matchToken('toggle')) {
if (tokens.matchToken('between')) {
var between = true;
var classRef = parser.parseElement("classRef", tokens);
tokens.requireToken("and");
var classRef2 = parser.requireElement("classRef", tokens);
} else {
var classRef = parser.parseElement("classRef", tokens);
var attributeRef = null;
if (classRef == null) {
attributeRef = parser.parseElement("attributeRef", tokens);
if (attributeRef == null) {
parser.raiseParseError(tokens, "Expected either a class reference or attribute expression")
}
}
}
if (tokens.matchToken("on")) {
var on = parser.requireElement("target", tokens);
} else {
var on = parser.requireElement("implicitMeTarget");
}
if (tokens.matchToken("for")) {
var time = parser.requireElement("timeExpression", tokens);
} else if (tokens.matchToken("until")) {
var evt = parser.requireElement("dotOrColonPath", tokens, "Expected event name");
if (tokens.matchToken("from")) {
var from = parser.requireElement("expression", tokens);
}
}
var toggleCmd = {
classRef: classRef,
classRef2: classRef2,
attributeRef: attributeRef,
on: on,
time: time,
evt: evt,
from: from,
toggle: function (on, value) {
if (this.classRef) {
if (between) {
runtime.forEach(on, function (target) {
if (target.classList.contains(classRef.className())) {
target.classList.remove(classRef.className());
target.classList.add(classRef2.className());
} else {
target.classList.add(classRef.className());
target.classList.remove(classRef2.className());
}
});
} else {
runtime.forEach(on, function (target) {
target.classList.toggle(classRef.className())
});
}
} else {
runtime.forEach(on, function (target) {
if (target.hasAttribute(attributeRef.name)) {
target.removeAttribute(attributeRef.name);
} else {
target.setAttribute(attributeRef.name, value)
}
});
}
},
args: [on, attributeRef ? attributeRef.value : null, time, evt, from],
op: function (context, on, value, time, evt, from) {
if (time) {
return new Promise(function (resolve) {
toggleCmd.toggle(on, value);
setTimeout(function () {
toggleCmd.toggle(on, value);
resolve(runtime.findNext(toggleCmd, context));
}, time);
});
} else if (evt) {
return new Promise(function (resolve) {
var target = from || context.me;
target.addEventListener(evt, function () {
toggleCmd.toggle(on, value);
resolve(runtime.findNext(toggleCmd, context));
}, {once: true})
toggleCmd.toggle(on, value);
});
} else {
this.toggle(on, value);
return runtime.findNext(toggleCmd, context);
}
}
};
return toggleCmd
}
})
var HIDE_SHOW_STRATEGIES = {
"display": function (op, element, arg) {
if(arg){
element.style.display = arg;
} else if (op === 'hide') {
element.style.display = 'none';
} else {
element.style.display = 'block';
}
},
"visibility": function (op, element, arg) {
if(arg){
element.style.visibility = arg;
} else if (op === 'hide') {
element.style.visibility = 'hidden';
} else {
element.style.visibility = 'visible';
}
},
"opacity": function (op, element, arg) {
if(arg){
element.style.opacity = arg;
} else if (op === 'hide') {
element.style.opacity = '0';
} else {
element.style.opacity = '1';
}
}
}
var parseShowHideTarget = function (parser, runtime, tokens) {
var target;
var currentTokenValue = tokens.currentToken();
if (currentTokenValue.value === "with" || parser.commandBoundary(currentTokenValue)) {
target = parser.parseElement("implicitMeTarget", tokens);
} else {
target = parser.parseElement("target", tokens);
}
return target;
}
var resolveStrategy = function (parser, tokens, name) {
var configDefault = _hyperscript.config.defaultHideShowStrategy;
var strategies = HIDE_SHOW_STRATEGIES;
if (_hyperscript.config.hideShowStrategies) {
strategies = mergeObjects(strategies, _hyperscript.config.hideShowStrategies); // merge in user provided strategies
}
name = name || configDefault || "display";
var value = strategies[name];
if (value == null) {
parser.raiseParseError(tokens, 'Unknown show/hide strategy : ' + name);
}
return value;
}
_hyperscript.addCommand("hide", function (parser, runtime, tokens) {
if (tokens.matchToken("hide")) {
var target = parseShowHideTarget(parser, runtime, tokens);
var name = null;
if (tokens.matchToken("with")) {
name = tokens.requireTokenType("IDENTIFIER").value;
}
var hideShowStrategy = resolveStrategy(parser, tokens, name);
return {
target: target,
args: [target],
op: function (ctx, target) {
runtime.forEach(target, function (elt) {
hideShowStrategy('hide', elt);
});
return runtime.findNext(this, ctx);
}
}
}
});
_hyperscript.addCommand("show", function (parser, runtime, tokens) {
if (tokens.matchToken("show")) {
var target = parseShowHideTarget(parser, runtime, tokens);
var name = null;
if (tokens.matchToken("with")) {
name = tokens.requireTokenType("IDENTIFIER").value;
}
var arg = null;
if (tokens.matchOpToken(":")) {
var tokenArr = tokens.consumeUntilWhitespace();
tokens.matchTokenType("WHITESPACE");
arg = tokenArr.map(function (t) {
return t.value
}).join("");
}
var hideShowStrategy = resolveStrategy(parser, tokens, name);
return {
target: target,
args: [target],
op: function (ctx, target) {
runtime.forEach(target, function (elt) {
hideShowStrategy('show', elt, arg);
});
return runtime.findNext(this, ctx);
}
}
}
});
_hyperscript.addCommand("trigger", function(parser, runtime, tokens) {
if (tokens.matchToken('trigger')) {
var eventName = parser.requireElement("dotOrColonPath", tokens);
var details = parser.parseElement("namedArgumentList", tokens);
var triggerCmd = {
eventName: eventName,
details: details,
args: [eventName, details],
op: function (context, eventNameStr, details) {
runtime.triggerEvent(context.me, eventNameStr, details ? details : {});
return runtime.findNext(triggerCmd, context);
}
};
return triggerCmd
}
})
_hyperscript.addCommand("take", function(parser, runtime, tokens) {
if (tokens.matchToken('take')) {
var classRef = tokens.requireTokenType(tokens, "CLASS_REF");
if (tokens.matchToken("from")) {
var from = parser.requireElement("target", tokens);
} else {
var from = parser.requireElement("implicitAllTarget")
}
if (tokens.matchToken("for")) {
var forElt = parser.requireElement("target", tokens);
} else {
var forElt = parser.requireElement("implicitMeTarget")
}
var takeCmd = {
classRef: classRef,
from: from,
forElt: forElt,
args: [from, forElt],
op: function (context, from, forElt) {
var clazz = this.classRef.value.substr(1)
runtime.forEach(from, function (target) {
target.classList.remove(clazz);
})
runtime.forEach(forElt, function (target) {
target.classList.add(clazz);
});
return runtime.findNext(this, context);
}
};
return takeCmd
}
})
function putInto(context, prop, valueToPut){
if (prop) {
var value = context[prop];
} else {
var value = context;
}
if (value instanceof Element || value instanceof HTMLDocument) {
value.innerHTML = valueToPut;
} else {
if (prop) {
context[prop] = valueToPut;
} else {
throw "Don't know how to put a value into " + typeof context;
}
}
}
_hyperscript.addCommand("put", function(parser, runtime, tokens) {
if (tokens.matchToken('put')) {
var value = parser.requireElement("expression", tokens);
var operationToken = tokens.matchToken("into") ||
tokens.matchToken("before") ||
tokens.matchToken("after");
if (operationToken == null && tokens.matchToken("at")) {
operationToken = tokens.matchToken("start") ||
tokens.matchToken("end");
tokens.requireToken("of");
}
if (operationToken == null) {
parser.raiseParseError(tokens, "Expected one of 'into', 'before', 'at start of', 'at end of', 'after'");
}
var target = parser.requireElement("target", tokens);
var operation = operationToken.value;
var symbolWrite = false;
var root = null;
var prop = null;
if (target.type === "propertyAccess" && operation === "into") {
prop = target.prop.value;
root = target.root;
} else if(target.type === "symbol" && operation === "into") {
symbolWrite = true;
prop = target.name;
} else {
root = target;
}
var putCmd = {
target: target,
operation: operation,
symbolWrite: symbolWrite,
value: value,
args: [root, value],
op: function (context, root, valueToPut) {
if (symbolWrite) {
putInto(context, prop, valueToPut);
context[target.name] = valueToPut;
} else {
if (operation === "into") {
runtime.forEach(root, function (elt) {
putInto(elt, prop, valueToPut);
})
} else if (operation === "before") {
runtime.forEach(root, function (elt) {
elt.insertAdjacentHTML('beforebegin', valueToPut);
})
} else if (operation === "start") {
runtime.forEach(root, function (elt) {
elt.insertAdjacentHTML('afterbegin', valueToPut);
})
} else if (operation === "end") {
runtime.forEach(root, function (elt) {
elt.insertAdjacentHTML('beforeend', valueToPut);
})
} else if (operation === "after") {
runtime.forEach(root, function (elt) {
elt.insertAdjacentHTML('afterend', valueToPut);
})
}
}
return runtime.findNext(this, context);
}
};
return putCmd
}
})
_hyperscript.addCommand("transition", function(parser, runtime, tokens) {
if (tokens.matchToken("transition")) {
if (tokens.matchToken('element') || tokens.matchToken('elements')) {
var targets = parser.parseElement("expression", tokens);
} else {
var targets = parser.parseElement("implicitMeTarget");
}
var properties = [];
var from = [];
var to = [];
var currentToken = tokens.currentToken();
while (!parser.commandBoundary(currentToken) && currentToken.value !== "using") {
properties.push(tokens.requireTokenType("IDENTIFIER").value);
if (tokens.matchToken("from")) {
from.push(parser.requireElement("stringLike", tokens));
} else {
from.push(null);
}
tokens.requireToken("to");
to.push(parser.requireElement("stringLike", tokens));
currentToken = tokens.currentToken();
}
if (tokens.matchToken("using")) {
var using = parser.requireElement("expression", tokens);
}
var transition = {
to: to,
args: [targets, from, to, using],
op: function (context, targets, from, to, using) {
var promises = [];
runtime.forEach(targets, function(target){
var promise = new Promise(function (resolve, reject) {
var initialTransition = target.style.transition;
target.style.transition = using || _hyperscript.config.defaultTransition;
var internalData = runtime.getInternalData(target);
var computedStyles = getComputedStyle(target);
var initialStyles = {};
for (var i = 0; i < computedStyles.length; i++) {
var name = computedStyles[i];
var initialValue = computedStyles[name];
initialStyles[name] = initialValue;
}
// store intitial values
if (!internalData.initalStyles) {
internalData.initalStyles = initialStyles;
}
for (var i = 0; i < properties.length; i++) {
var property = properties[i];
var fromVal = from[i];
if (fromVal == 'computed' || fromVal == null) {
target.style[property] = initialStyles[property];
} else {
target.style[property] = fromVal;
}
}
// console.log("transition started", transition);
setTimeout(function () {
var autoProps = [];
for (var i = 0; i < properties.length; i++) {
var property = properties[i];
var toVal = to[i];
if (toVal == 'initial') {
var propertyValue = internalData.initalStyles[property];
target.style[property] = propertyValue;
} else {
target.style[property] = toVal;
}
// console.log("set", property, "to", target.style[property], "on", target, "value passed in : ", toVal);
}
target.addEventListener('transitionend', function () {
// console.log("transition ended", transition);
target.style.transition = initialTransition;
resolve();
}, {once:true})
}, 5);
});
promises.push(promise);
})
return Promise.all(promises).then(function(){
return runtime.findNext(transition, context);
})
}
};
return transition
}
});
})()