hyperscript.org
Version:
a small scripting language for the web
1,994 lines (1,837 loc) • 194 kB
JavaScript
///=========================================================================
/// This module provides the core runtime and grammar for hyperscript
///=========================================================================
//AMD insanity
/** @var {HyperscriptObject} _hyperscript */
(function (root, factory) {
if (typeof module != 'undefined') {
module.exports = factory();
} else if (typeof this.define === "function" && this.define.amd) {
// AMD. Register as an anonymous module.
this.define([], factory);
} else {
// Browser globals
root._hyperscript = factory();
}
})(typeof self !== "undefined" ? self : this, function () {
"use strict";
//====================================================================
// Utilities
//====================================================================
/**
* mergeObjects combines the keys from obj2 into obj2, then returns obj1
*
* @param {object} obj1
* @param {object} obj2
* @returns object
*/
function mergeObjects(obj1, obj2) {
for (var key in obj2) {
if (obj2.hasOwnProperty(key)) {
obj1[key] = obj2[key];
}
}
return obj1;
}
function getOrInitObject(root, prop) {
var value = root[prop];
if (value) {
return value;
} else {
var newObj = {};
root[prop] = newObj;
return newObj;
}
}
/**
* parseJSON parses a JSON string into a corresponding value. If the
* value passed in is not valid JSON, then it logs an error and returns `null`.
*
* @param {string} jString
* @returns any
*/
function parseJSON(jString) {
try {
return JSON.parse(jString);
} catch (error) {
logError(error);
return null;
}
}
/**
* logError writes an error message to the Javascript console. It can take any
* value, but msg should commonly be a simple string.
* @param {*} msg
*/
function logError(msg) {
if (console.error) {
console.error(msg);
} else if (console.log) {
console.log("ERROR: ", msg);
}
}
// TODO: JSDoc description of what's happening here
function varargConstructor(Cls, args) {
return new (Cls.bind.apply(Cls, [Cls].concat(args)))();
}
var globalScope = (1, eval)("this");
//====================================================================
// Standard library
//====================================================================
class ElementCollection {
constructor(css, relativeToElement) {
this._css = css;
this.relativeToElement = relativeToElement;
}
get css() {
return _runtime.escapeSelector(this._css);
}
get className() {
return this._css.substr(1);
}
get id() {
return this.className();
}
[Symbol.iterator]() {
return _runtime.getRootNode(this.relativeToElement)
.querySelectorAll(this.css)
[Symbol.iterator]();
}
}
//====================================================================
// Lexer
//====================================================================
/** @type LexerObject */
var _lexer = (function () {
var OP_TABLE = {
"+": "PLUS",
"-": "MINUS",
"*": "MULTIPLY",
"/": "DIVIDE",
".": "PERIOD",
"..": "ELLIPSIS",
"\\": "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",
};
/**
* isValidCSSClassChar returns `true` if the provided character is valid in a CSS class.
* @param {string} c
* @returns boolean
*/
function isValidCSSClassChar(c) {
return isAlpha(c) || isNumeric(c) || c === "-" || c === "_" || c === ":";
}
/**
* isValidCSSIDChar returns `true` if the provided character is valid in a CSS ID
* @param {string} c
* @returns boolean
*/
function isValidCSSIDChar(c) {
return isAlpha(c) || isNumeric(c) || c === "-" || c === "_" || c === ":";
}
/**
* isWhitespace returns `true` if the provided character is whitespace.
* @param {string} c
* @returns boolean
*/
function isWhitespace(c) {
return c === " " || c === "\t" || isNewline(c);
}
/**
* positionString returns a string representation of a Token's line and column details.
* @param {Token} token
* @returns string
*/
function positionString(token) {
return "[Line: " + token.line + ", Column: " + token.column + "]";
}
/**
* isNewline returns `true` if the provided character is a carrage return or newline
* @param {string} c
* @returns boolean
*/
function isNewline(c) {
return c === "\r" || c === "\n";
}
/**
* isNumeric returns `true` if the provided character is a number (0-9)
* @param {string} c
* @returns boolean
*/
function isNumeric(c) {
return c >= "0" && c <= "9";
}
/**
* isAlpha returns `true` if the provided character is a letter in the alphabet
* @param {string} c
* @returns boolean
*/
function isAlpha(c) {
return (c >= "a" && c <= "z") || (c >= "A" && c <= "Z");
}
/**
* @param {string} c
* @param {boolean} [dollarIsOp]
* @returns boolean
*/
function isIdentifierChar(c, dollarIsOp) {
return c === "_" || c === "$";
}
/**
* @param {string} c
* @returns boolean
*/
function isReservedChar(c) {
return c === "`" || c === "^";
}
/**
* @param {Token[]} tokens
* @param {Token[]} consumed
* @param {string} source
* @returns {TokensObject}
*/
function makeTokensObject(tokens, consumed, source) {
consumeWhitespace(); // consume initial whitespace
/** @type Token | null */
var _lastConsumed = null;
function consumeWhitespace() {
while (token(0, true).type === "WHITESPACE") {
consumed.push(tokens.shift());
}
}
/**
* @param {TokensObject} tokens
* @param {*} error
*/
function raiseError(tokens, error) {
_parser.raiseParseError(tokens, error);
}
/**
* @param {string} value
* @returns {Token}
*/
function requireOpToken(value) {
var token = matchOpToken(value);
if (token) {
return token;
} else {
raiseError(this, "Expected '" + value + "' but found '" + currentToken().value + "'");
}
}
/**
* @param {string} op1
* @param {string} [op2]
* @param {string} [op3]
* @returns {Token | void}
*/
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;
}
}
}
/**
* @param {string} op1
* @param {string} [op2]
* @param {string} [op3]
* @returns {Token | void}
*/
function matchAnyToken(op1, op2, op3) {
for (var i = 0; i < arguments.length; i++) {
var opToken = arguments[i];
var match = matchToken(opToken);
if (match) {
return match;
}
}
}
/**
* @param {string} value
* @returns {Token | void}
*/
function matchOpToken(value) {
if (currentToken() && currentToken().op && currentToken().value === value) {
return consumeToken();
}
}
/**
* @param {string} type1
* @param {string} [type2]
* @param {string} [type3]
* @param {string} [type4]
* @returns {Token}
*/
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]));
}
}
/**
* @param {string} type1
* @param {string} [type2]
* @param {string} [type3]
* @param {string} [type4]
* @returns {Token | void}
*/
function matchTokenType(type1, type2, type3, type4) {
if (
currentToken() &&
currentToken().type &&
[type1, type2, type3, type4].indexOf(currentToken().type) >= 0
) {
return consumeToken();
}
}
/**
* @param {string} value
* @param {string} [type]
* @returns {Token}
*/
function requireToken(value, type) {
var token = matchToken(value, type);
if (token) {
return token;
} else {
raiseError(this, "Expected '" + value + "' but found '" + currentToken().value + "'");
}
}
/**
* @param {string} value
* @param {string} [type]
* @returns {Token | void}
*/
function matchToken(value, type) {
if (follows.indexOf(value) !== -1) {
return; // disallowed token here
}
var type = type || "IDENTIFIER";
if (currentToken() && currentToken().value === value && currentToken().type === type) {
return consumeToken();
}
}
/**
* @returns {Token}
*/
function consumeToken() {
var match = tokens.shift();
consumed.push(match);
_lastConsumed = match;
consumeWhitespace(); // consume any whitespace
return match;
}
/**
* @param {string} value
* @param {string} [type]
* @returns {Token[]}
*/
function consumeUntil(value, type) {
/** @type Token[] */
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;
}
/**
* @returns {string}
*/
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");
}
/**
* @returns {boolean}
*/
function hasMore() {
return tokens.length > 0;
}
/**
* @param {number} n
* @param {boolean} [dontIgnoreWhitespace]
* @returns {Token}
*/
function token(n, dontIgnoreWhitespace) {
var /**@type {Token}*/ 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>>>",
};
}
}
/**
* @returns {Token}
*/
function currentToken() {
return token(0);
}
/**
* @returns {Token | null}
*/
function lastMatch() {
return _lastConsumed;
}
/**
* @returns {string}
*/
function sourceFor() {
return source.substring(this.startToken.start, this.endToken.end);
}
/**
* @returns {string}
*/
function lineFor() {
return source.split("\n")[this.startToken.line - 1];
}
var follows = [];
function pushFollow(str) {
follows.push(str);
}
function popFollow() {
follows.pop();
}
function clearFollows() {
var tmp = follows;
follows = [];
return tmp;
}
function restoreFollows(f) {
follows = f;
}
/** @type {TokensObject} */
return {
pushFollow: pushFollow,
popFollow: popFollow,
clearFollow: clearFollows,
restoreFollow: restoreFollows,
matchAnyToken: matchAnyToken,
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,
lastMatch: lastMatch,
token: token,
consumeUntil: consumeUntil,
consumeUntilWhitespace: consumeUntilWhitespace,
lastWhitespace: lastWhitespace,
sourceFor: sourceFor,
lineFor: lineFor,
};
}
/**
* @param {Token[]} tokens
* @returns {boolean}
*/
function isValidSingleQuoteStringStart(tokens) {
if (tokens.length > 0) {
var previousToken = tokens[tokens.length - 1];
if (
previousToken.type === "IDENTIFIER" ||
previousToken.type === "CLASS_REF" ||
previousToken.type === "ID_REF"
) {
return false;
}
if (previousToken.op && (previousToken.value === ">" || previousToken.value === ")")) {
return false;
}
}
return true;
}
/**
* @param {string} string
* @param {boolean} [template]
* @returns {TokensObject}
*/
function tokenize(string, template) {
var tokens = /** @type {Token[]}*/ [];
var source = string;
var position = 0;
var column = 0;
var line = 1;
var lastToken = "<START>";
var templateBraceCount = 0;
function inTemplate() {
return template && templateBraceCount === 0;
}
while (position < source.length) {
if (currentChar() === "-" && nextChar() === "-") {
consumeComment();
} else {
if (isWhitespace(currentChar())) {
tokens.push(consumeWhitespace());
} else if (
!possiblePrecedingSymbol() &&
currentChar() === "." &&
(isAlpha(nextChar()) || nextChar() === "{")
) {
tokens.push(consumeClassReference());
} else if (
!possiblePrecedingSymbol() &&
currentChar() === "#" &&
(isAlpha(nextChar()) || nextChar() === "{")
) {
tokens.push(consumeIdReference());
} else if (currentChar() === "[" && nextChar() === "@") {
tokens.push(consumeAttributeReference());
} else if (currentChar() === "@") {
tokens.push(consumeShortAttributeReference());
} else if (isAlpha(currentChar()) || (!inTemplate() && isIdentifierChar(currentChar()))) {
tokens.push(consumeIdentifier());
} else if (isNumeric(currentChar())) {
tokens.push(consumeNumber());
} else if (!inTemplate() && (currentChar() === '"' || currentChar() === "`")) {
tokens.push(consumeString());
} else if (!inTemplate() && currentChar() === "'") {
if (isValidSingleQuoteStringStart(tokens)) {
tokens.push(consumeString());
} else {
tokens.push(consumeOp());
}
} else if (OP_TABLE[currentChar()]) {
if (lastToken === "$" && currentChar() === "{") {
templateBraceCount++;
}
if (currentChar() === "}") {
templateBraceCount--;
}
tokens.push(consumeOp());
} else if (inTemplate() || isReservedChar(currentChar())) {
tokens.push(makeToken("RESERVED", consumeChar()));
} else {
if (position < source.length) {
throw Error("Unknown token: " + currentChar() + " ");
}
}
}
}
return makeTokensObject(tokens, [], source);
/**
* @param {string} [type]
* @param {string} [value]
* @returns {Token}
*/
function makeOpToken(type, value) {
var token = makeToken(type, value);
token.op = true;
return token;
}
/**
* @param {string} [type]
* @param {string} [value]
* @returns {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();
}
/**
* @returns Token
*/
function consumeClassReference() {
var classRef = makeToken("CLASS_REF");
var value = consumeChar();
if (currentChar() === "{") {
classRef.template = true;
value += consumeChar();
while (currentChar() && currentChar() !== "}") {
value += consumeChar();
}
if (currentChar() !== "}") {
throw Error("Unterminated class reference");
} else {
value += consumeChar(); // consume final curly
}
} else {
while (isValidCSSClassChar(currentChar())) {
value += consumeChar();
}
}
classRef.value = value;
classRef.end = position;
return classRef;
}
/**
* @returns Token
*/
function consumeAttributeReference() {
var attributeRef = makeToken("ATTRIBUTE_REF");
var value = consumeChar();
while (position < source.length && currentChar() !== "]") {
value += consumeChar();
}
if (currentChar() === "]") {
value += consumeChar();
}
attributeRef.value = value;
attributeRef.end = position;
return attributeRef;
}
function consumeShortAttributeReference() {
var attributeRef = makeToken("ATTRIBUTE_REF");
var value = consumeChar();
while (isValidCSSIDChar(currentChar())) {
value += consumeChar();
}
attributeRef.value = value;
attributeRef.end = position;
return attributeRef;
}
/**
* @returns Token
*/
function consumeIdReference() {
var idRef = makeToken("ID_REF");
var value = consumeChar();
if (currentChar() === "{") {
idRef.template = true;
value += consumeChar();
while (currentChar() && currentChar() !== "}") {
value += consumeChar();
}
if (currentChar() !== "}") {
throw Error("Unterminated id reference");
} else {
consumeChar(); // consume final quote
}
} else {
while (isValidCSSIDChar(currentChar())) {
value += consumeChar();
}
}
idRef.value = value;
idRef.end = position;
return idRef;
}
/**
* @returns Token
*/
function consumeIdentifier() {
var identifier = makeToken("IDENTIFIER");
var value = consumeChar();
while (isAlpha(currentChar()) || isIdentifierChar(currentChar())) {
value += consumeChar();
}
identifier.value = value;
identifier.end = position;
return identifier;
}
/**
* @returns Token
*/
function consumeNumber() {
var number = makeToken("NUMBER");
var value = consumeChar();
while (isNumeric(currentChar())) {
value += consumeChar();
}
if (currentChar() === "." && isNumeric(nextChar())) {
value += consumeChar();
}
while (isNumeric(currentChar())) {
value += consumeChar();
}
number.value = value;
number.end = position;
return number;
}
/**
* @returns Token
*/
function consumeOp() {
var op = makeOpToken();
var value = consumeChar(); // consume leading char
while (currentChar() && OP_TABLE[value + currentChar()]) {
value += consumeChar();
}
op.type = OP_TABLE[value];
op.value = value;
op.end = position;
return op;
}
/**
* @returns Token
*/
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;
}
/**
* @returns string
*/
function currentChar() {
return source.charAt(position);
}
/**
* @returns string
*/
function nextChar() {
return source.charAt(position + 1);
}
/**
* @returns string
*/
function consumeChar() {
lastToken = currentChar();
position++;
column++;
return lastToken;
}
/**
* @returns boolean
*/
function possiblePrecedingSymbol() {
return (
isAlpha(lastToken) ||
isNumeric(lastToken) ||
lastToken === ")" ||
lastToken === "}" ||
lastToken === "]"
);
}
/**
* @returns Token
*/
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
//====================================================================
/** @type ParserObject */
var _parser = (function () {
/** @type {Object<string,GrammarDefinition>} */
var GRAMMAR = {};
/** @type {Object<string,GrammarDefinition>} */
var COMMANDS = {};
/** @type {Object<string,GrammarDefinition>} */
var FEATURES = {};
var LEAF_EXPRESSIONS = [];
var INDIRECT_EXPRESSIONS = [];
/**
* @param {*} parseElement
* @param {*} start
* @param {TokensObject} tokens
*/
function initElt(parseElement, start, tokens) {
parseElement.startToken = start;
parseElement.sourceFor = tokens.sourceFor;
parseElement.lineFor = tokens.lineFor;
parseElement.programSource = tokens.source;
}
/**
* @param {string} type
* @param {TokensObject} tokens
* @param {GrammarElement?} root
* @returns GrammarElement
*/
function parseElement(type, tokens, root = undefined) {
var elementDefinition = GRAMMAR[type];
if (elementDefinition) {
var start = tokens.currentToken();
var parseElement = elementDefinition(_parser, _runtime, tokens, root);
if (parseElement) {
initElt(parseElement, start, tokens);
parseElement.endToken = parseElement.endToken || tokens.lastMatch();
var root = parseElement.root;
while (root != null) {
initElt(root, start, tokens);
root = root.root;
}
}
return parseElement;
}
}
/**
* @param {string} type
* @param {TokensObject} tokens
* @param {string} [message]
* @param {*} [root]
* @returns {GrammarElement}
*/
function requireElement(type, tokens, message, root) {
var result = parseElement(type, tokens, root);
if (!result) raiseParseError(tokens, message || "Expected " + type);
// @ts-ignore
return result;
}
/**
* @param {string[]} types
* @param {TokensObject} tokens
* @returns {GrammarElement}
*/
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;
}
}
}
/**
* @param {string} name
* @param {GrammarDefinition} definition
*/
function addGrammarElement(name, definition) {
GRAMMAR[name] = definition;
}
/**
* @param {string} keyword
* @param {GrammarDefinition} definition
*/
function addCommand(keyword, definition) {
var commandGrammarType = keyword + "Command";
var commandDefinitionWrapper = function (parser, runtime, tokens) {
const commandElement = definition(parser, runtime, tokens);
if (commandElement) {
commandElement.type = commandGrammarType;
commandElement.execute = function (context) {
context.meta.command = commandElement;
return runtime.unifiedExec(this, context);
};
return commandElement;
}
};
GRAMMAR[commandGrammarType] = commandDefinitionWrapper;
COMMANDS[keyword] = commandDefinitionWrapper;
}
/**
* @param {string} keyword
* @param {GrammarDefinition} definition
*/
function addFeature(keyword, definition) {
var featureGrammarType = keyword + "Feature";
/** @type {GrammarDefinition} */
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;
}
/**
* @param {string} name
* @param {GrammarDefinition} definition
*/
function addLeafExpression(name, definition) {
LEAF_EXPRESSIONS.push(name);
addGrammarElement(name, definition);
}
/**
* @param {string} name
* @param {GrammarDefinition} 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 featureElement = parser.requireElement("feature", tokens);
tokens.requireOpToken(")");
return featureElement;
}
var featureDefinition = FEATURES[tokens.currentToken().value];
if (featureDefinition) {
return featureDefinition(parser, runtime, tokens);
}
});
addGrammarElement("command", function (parser, runtime, tokens) {
if (tokens.matchOpToken("(")) {
const commandElement = parser.requireElement("command", tokens);
tokens.requireOpToken(")");
return commandElement;
}
var commandDefinition = COMMANDS[tokens.currentToken().value];
let commandElement;
if (commandDefinition) {
commandElement = commandDefinition(parser, runtime, tokens);
} else if (tokens.currentToken().type === "IDENTIFIER" && tokens.token(1).value === "(") {
commandElement = parser.requireElement("pseudoCommand", tokens);
}
if (commandElement) {
return parser.parseElement("indirectStatement", tokens, commandElement);
}
return commandElement;
});
addGrammarElement("commandList", function (parser, runtime, tokens) {
var cmd = parser.parseElement("command", tokens);
if (cmd) {
tokens.matchToken("then");
const next = parser.parseElement("commandList", tokens);
if (next) cmd.next = next;
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);
}
return result;
});
addGrammarElement("indirectExpression", function (parser, runtime, tokens, root) {
for (var i = 0; i < INDIRECT_EXPRESSIONS.length; i++) {
var indirect = INDIRECT_EXPRESSIONS[i];
root.endToken = tokens.lastMatch();
var result = parser.parseElement(indirect, tokens, root);
if (result) {
return result;
}
}
return root;
});
addGrammarElement("indirectStatement", function (parser, runtime, tokens, root) {
if (tokens.matchToken("unless")) {
root.endToken = tokens.lastMatch();
var conditional = parser.requireElement("expression", tokens);
var unless = {
type: "unlessStatementModifier",
args: [conditional],
op: function (context, conditional) {
if (conditional) {
return this.next;
} else {
return root;
}
},
execute: function (context) {
return runtime.unifiedExec(this, context);
},
};
root.parent = unless;
return unless;
}
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 */
/* ============================================================================================ */
/**
*
* @param {TokensObject} tokens
* @returns string
*/
function createParserContext(tokens) {
var currentToken = tokens.currentToken();
var source = tokens.source;
var lines = source.split("\n");
var line = currentToken && currentToken.line ? currentToken.line - 1 : lines.length - 1;
var contextLine = lines[line];
var offset = currentToken && currentToken.line ? currentToken.column : contextLine.length - 1;
return contextLine + "\n" + " ".repeat(offset) + "^^\n\n";
}
/**
* @param {TokensObject} tokens
* @param {string} [message]
*/
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;
}
/**
* @param {TokensObject} tokens
* @returns {GrammarElement}
*/
function parseHyperScript(tokens) {
var result = parseElement("hyperscript", tokens);
if (tokens.hasMore()) raiseParseError(tokens);
if (result) return result;
}
/**
* @param {GrammarElement} elt
* @param {GrammarElement} parent
*/
function setParent(elt, parent) {
if (elt) {
elt.parent = parent;
setParent(elt.next, parent);
}
}
/**
* @param {Token} token
* @returns {GrammarDefinition}
*/
function commandStart(token) {
return COMMANDS[token.value];
}
/**
* @param {Token} token
* @returns {GrammarDefinition}
*/
function featureStart(token) {
return FEATURES[token.value];
}
/**
* @param {Token} token
* @returns {boolean}
*/
function commandBoundary(token) {
if (
token.value == "end" ||
token.value == "then" ||
token.value == "else" ||
token.value == ")" ||
commandStart(token) ||
featureStart(token) ||
token.type == "EOF"
) {
return true;
}
return false;
}
/**
* @param {TokensObject} tokens
* @returns {(string | GrammarElement)[]}
*/
function parseStringTemplate(tokens) {
/** @type {(string | GrammarElement)[]} */
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 ? token.value : "";
}
} while (tokens.hasMore());
returnArr.push(tokens.lastWhitespace());
return returnArr;
}
// parser API
return {
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: /** @type DynamicConversionFunction[] */ [],
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) {
console.log(val);
return Number(val);
},
Date: function (val) {
return new Date(val);
},
Array: function (val) {
return Array.from(val);
},
JSON: function (val) {
return JSON.stringify(val);
},
Object: function (val) {
if (val instanceof String) {
val = val.toString();
}
if (typeof val === "string") {
return JSON.parse(val);
} else {
return mergeObjects({}, val);
}
},
};
/********************************************
* RUNTIME OBJECT
********************************************/
/** @type {RuntimeObject} */
var _runtime = (function () {
/**
* @param {HTMLElement} elt
* @param {string} selector
* @returns boolean
*/
function matchesSelector(elt, selector) {
// noinspection JSUnresolvedVariable
var matchesFunction =
// @ts-ignore
elt.matches || elt.matchesSelector || elt.msMatchesSelector || elt.mozMatchesSelector || elt.webkitMatchesSelector || elt.oMatchesSelector;
return matchesFunction && matchesFunction.call(elt, selector);
}
/**
* @param {string} eventName
* @param {Object} [detail]
* @returns {Event}
*/
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;
}
/**
* @param {Element} elt
* @param {string} eventName
* @param {Object} [detail]
* @returns {boolean}
*/
function triggerEvent(elt, eventName, detail) {
detail = detail || {};
detail["sentBy"] = elt;
var event = makeEvent(eventName, detail);
var eventResult = elt.dispatchEvent(event);
return eventResult;
}
/**
* isArrayLike returns `true` if the provided value is an array or
* a NodeList (which is close enough to being an array for our purposes).
*
* @param {any} value
* @returns {value is Array | NodeList}
*/
function isArrayLike(value) {
return Array.isArray(value) || value instanceof NodeList;
}
/**
* isIterable returns `true` if the provided value supports the
* iterator protocol.
*
* @param {any} value
* @returns {value is Iterable}
*/
function isIterable(value) {
return typeof value === 'object'
&& Symbol.iterator in value
&& typeof value[Symbol.iterator] === 'function';
}
/**
* shouldAutoIterate returns `true` if the provided value
* should be implicitly iterated over when accessing properties,
* and as the target of some commands.
*
* Currently, this is when the value is an {ElementCollection}
* or {isArrayLike} returns true.
*
* @param {any} value
* @returns {value is any[] | NodeList | ElementCollection}
*/
function shouldAutoIterate(value) {
return value instanceof ElementCollection || isArrayLike(value);
}
/**
* forEach executes the provided `func` on every item in the `value` array.
* if `value` is a single item (and not an array) then `func` is simply called
* once. If `value` is null, then no further actions are taken.
*
* @template T
* @param {T | Iterable<T>} value
* @param {(item: T) => void} func
*/
function forEach(value, func) {
if (value == null) {
// do nothing
} else if (isIterable(value)) {
for (const nth of value) {
func(nth);
}
} else if (isArrayLike(value)) {
for (var i = 0; i < value.length; i++) {
func(value[i]);
}
} else {
func(value);
}
}
/**
* implicitLoop executes the provided `func` on:
* - every item of {value}, if {value} should be auto-iterated
* (see {shouldAutoIterate})
* - {value} otherwise
*
* @template T
* @param {NodeList | T | T[]} value
* @param {(item:Node | T) => void} func
*/
function implicitLoop(value, func) {
if (shouldAutoIterate(value)) {
for (const x of value) func(x);
} 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 = {};
/**
* @param {GrammarElement} command
* @param {Context} ctx
*/
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
}
}
}
/**
* @param {*} parseElement
* @param {Context} ctx
* @returns {*}
*/
function unifiedEval(parseElement, ctx) {
/** @type any[] */
var args = [ctx];
var async = false;
var wrappedAsyncs = false;
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;
/**
* getAttributes returns the attribute name(s) to use when
* locating hyperscript scripts in a DOM element. If no value
* has been configured, it defaults to _hyperscript.config.attributes
* @returns string[]
*/
function getScriptAttributes() {
if (_scriptAttrs == null) {
_scriptAttrs = _hyperscript.config.attributes.replace(/ /g, "").split(",");
}
return _scriptAttrs;
}
/**
* @param {Element} elt
* @returns {string | null}
*/
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 instanceof HTMLScriptElement && elt.type === "text/hyperscript") {
return elt.innerText;
}
return null;
}
var hyperscriptFeaturesMap = new WeakMap
/**
* @param {Element} elt
* @returns {Object}
*/
function getHyperscriptFeatures(elt) {
var hyperscriptFeatures = hyperscriptFeaturesMap.get(elt);
if (typeof hyperscriptFeatures === 'undefined') {
hyperscriptFeaturesMap.set(elt, hyperscriptFeatures = {});
}
return hyperscriptFeatures;
}
/**
* @param {Object} owner
* @param {Context} ctx
*/
function addFeatures(owner, ctx) {
if (owner) {
mergeObjects(ctx, getHyperscriptFeatures(owner));
addFeatures(owner.parentElement, ctx);
}
}
/**
* @param {*} owner
* @param {*} feature
* @param {*} hyperscriptTarget
* @param {*} event
* @returns {Context}
*/
function makeContext(owner, feature, hyperscriptTarget, event) {
/** @type {Context} */
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;
addFeatures(owner, ctx);
return ctx;
}
/**
* @returns string
*/
function getScriptSelector() {
return getScriptAttributes()
.map(function (attribute) {
return "[" + attribute + "]";
})
.join(", ");
}
/**
* @param {any} value
* @param {string} type
* @returns {any}
*/
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;
}
// TODO: There do not seem to be any references to this function.
// Is it still in use, or can it be removed?
function isType(o, type) {
return Object.prototype.toString.call(o) === "[object " + type + "]";
}
/**
* @param {string} src
* @returns {GrammarElement}
*/
function parse(src) {
var tokens = _lexer.tokenize(src);
if (_parser.commandStart(tokens.currentToken())) {
var commandList = _parser.requireElement("commandList", tokens);
var last = commandList;
while (last.next) {
last = last.next;
}
last.next = {
op: function () {
return HALT;
},
};
return commandList;
} else if (_parser.featureStart(tokens.currentToken())) {
var hyperscript = _parser.requireElement("hyperscript", tokens);
return hyperscript;
} else {
var expression = _parser.requireElement("expression", tokens);
return expression;
}
}
/**
* @param {string} src
* @param {Context} [ctx]
* @returns {any}
*/
function evaluate(src, ctx) {
var body = 'document' in globalScope ? globalScope.document.body : makeModule();
ctx = mergeObjects(makeContext(body, null, body, null), ctx || {});
var element = parse(src);
if (element.execute) {
return element.execute(ctx);
} else if (element.apply) {
element.apply(body, null);
return getHyperscriptFeatures(body);
} else {
return element.evaluate(ctx);
}
function makeModule() {
return {}
}
}
/**
* @param {HTMLElement} elt
*/
function processNode(elt) {
var selector = _runtime.getScriptSelector();
if (matchesSelector(elt, selector)) {
initElement(elt, elt);
}
if (elt instanceof HTMLScriptElement && elt.type === "text/hyperscript") {
initElement(elt, document.body);
}
if (elt.querySelectorAll) {
forEach(elt.querySelectorAll(selector + ", [type='text/hyperscript']"), function (elt) {
initElement(elt, elt instanceof HTMLScriptElement && elt.type === "text/hyperscript" ? document.body : elt);
});
}
}
/**
* @param {Element} elt
* @param {Element} [target]
*/
function initElement(elt, target) {
if (elt.closest && elt.closest(_hyperscript.config.disableSelector)) {
return;
}
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);
if (!hyperScript) return;
hyperScript.apply(target || elt, elt);
setTimeout(function () {
triggerEvent(target || elt, "load", {
hyperscript: true,
});
}, 1);
} catch (e) {
_runtime.triggerEvent(elt, "exception", {
error: e,
});
console.error(
"hyperscript errors were found on the following element:",
elt,
"\n\n",
e.message,
e.stack
);
}
}
}
}
var internalDataMap = new WeakMap
/**
* @param {Element} elt
* @returns {Object}
*/
function getInternalData(elt) {
var internalData = internalDataMap.get(elt);
if (typeof internalData === 'undefined') {
internalDataMap.set(elt, internalData = {});
}
return internalData;
}
/**
* @param {any} value
* @param {string} typeString
* @param {boolean} [nullOk]
* @returns {boolean}
*/
function typeCheck(value, typeString, nullOk) {
if (value == null && nullOk) {
return true;
}
var typeName = Object.prototype.toString.call(value).slice(8, -1);
return typeName === typeString;
}
function getElementScope(context) {
var elt = context.meta && context.meta.owner;
if (elt) {
var internalData = getInternalData(elt);
var scopeName = "elementScope";
if (context.meta.feature && context.meta.feature.behavior) {
scopeName = context.meta.feature.behavior + "Scope";
}
var elementScope = getOrInitObject(internalData, scopeName);
return elementScope;
} else {
return {}; // no element, return empty scope
}
}
/**
* @param {string} str
* @param {Context} context
* @returns {any}
*/
function resolveSymbol(str, context, type) {
if (str === "me" || str === "my" || str === "I") {
return context["me"];
}
if (str === "it" || str === "its") {
return context["result"];
}
if (str === "you" || str === "your" || str === "yourself") {
return context["beingTold"];
} else {
if (type === "global") {
return globalScope[str];
} else if (type === "element") {
var elementScope = getElementScope(context);
return elementScope[str];
} else if (type === "local") {
return context[str];
} else {
// meta scope (used for event conditionals)
if (context.meta && context.meta.context) {
var fromMetaContext = context.meta.context[str];
if (typeof fromMetaContext !== "undefined") {
return fromMetaContext;
}
}
// local scope
var fromContext = context[str];
if (typeof fromContext !== "undefined") {
return fromContext;
} else {
// element scope
var elementScope = getElementScope(context);
fromContext = elementScope[str];
if (typeof fromContext !== "undefined") {
return fromContext;
} else {
// global scope
return globalScope[str];
}
}
}
}
}
function setSymbol(str, context, type, value) {
if (type === "global") {
globalScope[str] = value;
} else if (type === "element") {
var elementScope = getElementScope(context);
elementScope[str] = value;
} else if (type === "local") {
context[str] = value;
} else {
// local scope
var fromContext = context[str];
if (typeof fromContext !== "undefined") {
context[str] = value;
} else {
// element scope
var elementScope = getElementScope(context);
fromContext = elementScope[str];
if (typeof fromContext !== "undefined") {
elementScope[str] = value;
} else {
// global scope