mirror of
https://github.com/bigskysoftware/htmx.git
synced 2025-10-02 15:25:26 +00:00
1603 lines
69 KiB
JavaScript
1603 lines
69 KiB
JavaScript
//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';
|
|
|
|
//-----------------------------------------------
|
|
// Lexer
|
|
//-----------------------------------------------
|
|
var _lexer = function () {
|
|
var OP_TABLE = {
|
|
'+': 'PLUS',
|
|
'-': 'MINUS',
|
|
'*': 'MULTIPLY',
|
|
'/': 'DIVIDE',
|
|
'.': 'PERIOD',
|
|
'\\': 'BACKSLASH',
|
|
':': 'COLON',
|
|
'%': 'PERCENT',
|
|
'|': 'PIPE',
|
|
'!': 'EXCLAMATION',
|
|
'?': 'QUESTION',
|
|
'#': 'POUND',
|
|
'&': 'AMPERSAND',
|
|
';': 'SEMI',
|
|
',': 'COMMA',
|
|
'(': 'L_PAREN',
|
|
')': 'R_PAREN',
|
|
'<': 'L_ANG',
|
|
'>': 'R_ANG',
|
|
'<=': 'LTE_ANG',
|
|
'>=': 'GTE_ANG',
|
|
'==': 'EQ',
|
|
'===': 'EQQ',
|
|
'{': '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 makeTokensObject(tokens, consumed, source) {
|
|
|
|
var ignoreWhiteSpace = true;
|
|
matchTokenType("WHITESPACE"); // consume any initial whitespace
|
|
|
|
function raiseError(tokens, error) {
|
|
_parser.raiseParseError(tokens, error);
|
|
}
|
|
|
|
function requireOpToken(value) {
|
|
var token = matchOpToken(value);
|
|
if (token) {
|
|
return token;
|
|
} else {
|
|
raiseError(this, "Expected '" + value + "' but found '" + currentToken().value + "'");
|
|
}
|
|
}
|
|
|
|
function matchAnyOpToken(op1, op2, op3) {
|
|
for (var i = 0; i < arguments.length; i++) {
|
|
var opToken = arguments[i];
|
|
var match = matchOpToken(opToken);
|
|
if (match) {
|
|
return match;
|
|
}
|
|
}
|
|
}
|
|
|
|
function matchOpToken(value) {
|
|
if (currentToken() && currentToken().op && currentToken().value === value) {
|
|
return consumeToken();
|
|
}
|
|
}
|
|
|
|
function requireTokenType(type1, type2, type3, type4) {
|
|
var token = matchTokenType(type1, type2, type3, type4);
|
|
if (token) {
|
|
return token;
|
|
} else {
|
|
raiseError(this, "Expected one of " + JSON.stringify([type1, type2, type3]));
|
|
}
|
|
}
|
|
|
|
function matchTokenType(type1, type2, type3, type4) {
|
|
if (currentToken() && currentToken().type && [type1, type2, type3, type4].indexOf(currentToken().type) >= 0) {
|
|
return consumeToken();
|
|
}
|
|
}
|
|
|
|
function requireToken(value, type) {
|
|
var token = matchToken(value, type);
|
|
if (token) {
|
|
return token;
|
|
} else {
|
|
raiseError(this, "Expected '" + value + "' but found '" + currentToken().value + "'");
|
|
}
|
|
}
|
|
|
|
function matchToken(value, type) {
|
|
var type = type || "IDENTIFIER";
|
|
if (currentToken() && currentToken().value === value && currentToken().type === type) {
|
|
return consumeToken();
|
|
}
|
|
}
|
|
|
|
function consumeToken() {
|
|
var match = tokens.shift();
|
|
consumed.push(match);
|
|
if(ignoreWhiteSpace) {
|
|
matchTokenType("WHITESPACE"); // consume any whitespace until the next token
|
|
}
|
|
return match;
|
|
}
|
|
|
|
function consumeUntilWhitespace() {
|
|
var tokenList = [];
|
|
ignoreWhiteSpace = false;
|
|
while (currentToken() && currentToken().type !== "WHITESPACE") {
|
|
tokenList.push(consumeToken());
|
|
}
|
|
ignoreWhiteSpace = true;
|
|
return tokenList;
|
|
}
|
|
|
|
function hasMore() {
|
|
return tokens.length > 0;
|
|
}
|
|
|
|
function currentToken() {
|
|
return tokens[0];
|
|
}
|
|
|
|
return {
|
|
matchAnyOpToken: matchAnyOpToken,
|
|
matchOpToken: matchOpToken,
|
|
requireOpToken: requireOpToken,
|
|
matchTokenType: matchTokenType,
|
|
requireTokenType: requireTokenType,
|
|
consumeToken: consumeToken,
|
|
matchToken: matchToken,
|
|
requireToken: requireToken,
|
|
list: tokens,
|
|
source: source,
|
|
hasMore: hasMore,
|
|
currentToken: currentToken,
|
|
consumeUntilWhitespace: consumeUntilWhitespace
|
|
}
|
|
}
|
|
|
|
function tokenize(string) {
|
|
var source = string;
|
|
var tokens = [];
|
|
var position = 0;
|
|
var column = 0;
|
|
var line = 1;
|
|
var lastToken = "<START>";
|
|
|
|
while (position < source.length) {
|
|
if (currentChar() === "-" && nextChar() === "-") {
|
|
consumeComment();
|
|
} else {
|
|
if (isWhitespace(currentChar())) {
|
|
tokens.push(consumeWhitespace());
|
|
} else if (!possiblePrecedingSymbol() && currentChar() === "." && isAlpha(nextChar())) {
|
|
tokens.push(consumeClassReference());
|
|
} else if (!possiblePrecedingSymbol() && currentChar() === "#" && isAlpha(nextChar())) {
|
|
tokens.push(consumeIdReference());
|
|
} else if (isAlpha(currentChar())) {
|
|
tokens.push(consumeIdentifier());
|
|
} else if (isNumeric(currentChar())) {
|
|
tokens.push(consumeNumber());
|
|
} else if (currentChar() === '"' || currentChar() === "'") {
|
|
tokens.push(consumeString());
|
|
} else if (OP_TABLE[currentChar()]) {
|
|
tokens.push(consumeOp());
|
|
} else {
|
|
if (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())) {
|
|
value += consumeChar();
|
|
}
|
|
identifier.value = value;
|
|
identifier.end = position;
|
|
return identifier;
|
|
}
|
|
|
|
function consumeNumber() {
|
|
var number = makeToken("NUMBER");
|
|
var value = consumeChar();
|
|
while (isNumeric(currentChar())) {
|
|
value += consumeChar();
|
|
}
|
|
if (currentChar() === ".") {
|
|
value += consumeChar();
|
|
}
|
|
while (isNumeric(currentChar())) {
|
|
value += consumeChar();
|
|
}
|
|
number.value = value;
|
|
number.end = position;
|
|
return number;
|
|
}
|
|
|
|
function consumeOp() {
|
|
var value = consumeChar(); // consume leading char
|
|
while (currentChar() && OP_TABLE[value + currentChar()]) {
|
|
value += consumeChar();
|
|
}
|
|
var op = makeOpToken(OP_TABLE[value], value);
|
|
op.value = value;
|
|
op.end = position;
|
|
return op;
|
|
}
|
|
|
|
function consumeString() {
|
|
var string = makeToken("STRING");
|
|
var startChar = consumeChar(); // consume leading quote
|
|
var value = "";
|
|
while (currentChar() && currentChar() !== startChar) {
|
|
if (currentChar() === "\\") {
|
|
consumeChar(); // consume escape char and move on
|
|
}
|
|
value += consumeChar();
|
|
}
|
|
if (currentChar() !== startChar) {
|
|
throw Error("Unterminated string at " + positionString(string));
|
|
} else {
|
|
consumeChar(); // consume final quote
|
|
}
|
|
string.value = value;
|
|
string.end = position;
|
|
return string;
|
|
}
|
|
|
|
function currentChar() {
|
|
return source.charAt(position);
|
|
}
|
|
|
|
function nextChar() {
|
|
return source.charAt(position + 1);
|
|
}
|
|
|
|
function consumeChar() {
|
|
lastToken = currentChar();
|
|
position++;
|
|
column++;
|
|
return lastToken;
|
|
}
|
|
|
|
function possiblePrecedingSymbol() {
|
|
return isAlpha(lastToken) || isNumeric(lastToken) || lastToken === ")" || lastToken === "}" || lastToken === "]"
|
|
}
|
|
|
|
function consumeWhitespace() {
|
|
var whitespace = makeToken("WHITESPACE");
|
|
var value = "";
|
|
while (currentChar() && isWhitespace(currentChar())) {
|
|
if (isNewline(currentChar())) {
|
|
column = 0;
|
|
line++;
|
|
}
|
|
value += consumeChar();
|
|
}
|
|
whitespace.value = value;
|
|
whitespace.end = position;
|
|
return whitespace;
|
|
}
|
|
}
|
|
|
|
return {
|
|
tokenize: tokenize
|
|
}
|
|
}();
|
|
|
|
//-----------------------------------------------
|
|
// Parser
|
|
//-----------------------------------------------
|
|
var _parser = function () {
|
|
|
|
var GRAMMAR = {}
|
|
|
|
function addGrammarElement(name, definition) {
|
|
GRAMMAR[name] = definition;
|
|
}
|
|
|
|
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 parseElement(type, tokens, root) {
|
|
var expressionDef = GRAMMAR[type];
|
|
if (expressionDef) return expressionDef(_parser, tokens, root);
|
|
}
|
|
|
|
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 parseHyperScript(tokens) {
|
|
return parseElement("hyperscript", tokens)
|
|
}
|
|
|
|
function transpile(node, defaultVal) {
|
|
if (node == null) {
|
|
return defaultVal;
|
|
}
|
|
var src = node.transpile();
|
|
if (node.next) {
|
|
return src + "\n" + transpile(node.next)
|
|
} else {
|
|
return src;
|
|
}
|
|
}
|
|
|
|
return {
|
|
// parser API
|
|
parseElement: parseElement,
|
|
parseAnyOf: parseAnyOf,
|
|
parseHyperScript: parseHyperScript,
|
|
raiseParseError: raiseParseError,
|
|
addGrammarElement: addGrammarElement,
|
|
transpile: transpile
|
|
}
|
|
}();
|
|
|
|
//-----------------------------------------------
|
|
// Runtime
|
|
//-----------------------------------------------
|
|
var _runtime = function () {
|
|
var SCRIPT_ATTRIBUTES = ["_", "script", "data-script"];
|
|
|
|
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 forEach(arr, func) {
|
|
if (arr.length) {
|
|
for (var i = 0; i < arr.length; i++) {
|
|
func(arr[i]);
|
|
}
|
|
} else {
|
|
func(arr);
|
|
}
|
|
}
|
|
|
|
function evalTarget(root, path) {
|
|
if (root.length) {
|
|
var last = root;
|
|
} else {
|
|
var last = [root];
|
|
}
|
|
|
|
while (path.length > 0) {
|
|
var prop = path.shift();
|
|
var next = []
|
|
// flat map
|
|
for (var i = 0; i < last.length; i++) {
|
|
var element = last[i];
|
|
var nextVal = element[prop];
|
|
if (nextVal && nextVal.length) {
|
|
next = next.concat(nextVal);
|
|
} else {
|
|
next.push(nextVal);
|
|
}
|
|
}
|
|
last = next;
|
|
}
|
|
|
|
return last;
|
|
}
|
|
|
|
function getScript(elt) {
|
|
for (var i = 0; i < SCRIPT_ATTRIBUTES.length; i++) {
|
|
var scriptAttribute = SCRIPT_ATTRIBUTES[i];
|
|
if (elt.hasAttribute && elt.hasAttribute(scriptAttribute)) {
|
|
return elt.getAttribute(scriptAttribute)
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function applyEventListeners(hypeScript, elt) {
|
|
forEach(hypeScript.eventListeners, function (eventListener) {
|
|
eventListener(elt);
|
|
});
|
|
}
|
|
|
|
function setScriptAttrs(values) {
|
|
SCRIPT_ATTRIBUTES = values;
|
|
}
|
|
|
|
function getScriptSelector() {
|
|
return SCRIPT_ATTRIBUTES.map(function (attribute) {
|
|
return "[" + attribute + "]";
|
|
}).join(", ");
|
|
}
|
|
|
|
function isType(o, type) {
|
|
return Object.prototype.toString.call(o) === "[object " + type + "]";
|
|
}
|
|
|
|
function evaluate(typeOrSrc, srcOrCtx, ctxArg) {
|
|
if (isType(srcOrCtx, "Object")) {
|
|
var src = typeOrSrc;
|
|
var ctx = srcOrCtx;
|
|
var type = "expression"
|
|
} else if (isType(srcOrCtx, "String")) {
|
|
var src = srcOrCtx;
|
|
var type = typeOrSrc
|
|
var ctx = ctxArg;
|
|
} else {
|
|
var src = typeOrSrc;
|
|
var ctx = {};
|
|
var type = "expression";
|
|
}
|
|
ctx = ctx || {};
|
|
var compiled = _parser.parseElement(type, _lexer.tokenize(src) ).transpile();
|
|
var evalString = "(function(" + Object.keys(ctx).join(",") + "){return " + compiled + "})";
|
|
// TODO parser debugging
|
|
if(false) console.log("transpile: " + compiled);
|
|
if(false) console.log("evalString: " + evalString);
|
|
var args = Object.keys(ctx).map(function (key) {
|
|
return ctx[key]
|
|
});
|
|
if(false) console.log("args", args);
|
|
return eval(evalString).apply(null, args);
|
|
}
|
|
|
|
function initElement(elt) {
|
|
var src = getScript(elt);
|
|
if (src) {
|
|
var tokens = _lexer.tokenize(src);
|
|
var hyperScript = _parser.parseHyperScript(tokens);
|
|
var transpiled = _parser.transpile(hyperScript);
|
|
if(true) console.log(transpiled);
|
|
var hyperscriptObj = eval(transpiled);
|
|
hyperscriptObj.applyEventListenersTo(elt);
|
|
}
|
|
}
|
|
|
|
function ajax(method, url, callback, data) {
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.onload = function() {
|
|
callback(this.response)
|
|
};
|
|
xhr.open(method, url);
|
|
xhr.send(JSON.stringify(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);
|
|
}
|
|
}
|
|
|
|
return {
|
|
typeCheck: typeCheck,
|
|
forEach: forEach,
|
|
evalTarget: evalTarget,
|
|
triggerEvent: triggerEvent,
|
|
matchesSelector: matchesSelector,
|
|
getScript: getScript,
|
|
applyEventListeners: applyEventListeners,
|
|
setScriptAttrs: setScriptAttrs,
|
|
initElement: initElement,
|
|
evaluate: evaluate,
|
|
getScriptSelector: getScriptSelector,
|
|
ajax: ajax,
|
|
}
|
|
}();
|
|
|
|
//-----------------------------------------------
|
|
// Expressions
|
|
//-----------------------------------------------
|
|
|
|
_parser.addGrammarElement("parenthesized", function (parser, tokens) {
|
|
if (tokens.matchOpToken('(')) {
|
|
var expr = parser.parseElement("expression", tokens);
|
|
tokens.requireOpToken(")");
|
|
return {
|
|
type: "parenthesized",
|
|
expr: expr,
|
|
transpile: function () {
|
|
return "(" + parser.transpile(expr) + ")";
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
_parser.addGrammarElement("string", function (parser, tokens) {
|
|
var stringToken = tokens.matchTokenType('STRING');
|
|
if (stringToken) {
|
|
return {
|
|
type: "string",
|
|
token: stringToken,
|
|
transpile: function () {
|
|
if (stringToken.value.indexOf("'") === 0) {
|
|
return "'" + stringToken.value + "'";
|
|
} else {
|
|
return '"' + stringToken.value + '"';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
_parser.addGrammarElement("nakedString", function (parser, tokens) {
|
|
if (tokens.hasMore()) {
|
|
var tokenArr = tokens.consumeUntilWhitespace();
|
|
tokens.matchTokenType("WHITESPACE");
|
|
return {
|
|
type: "nakedString",
|
|
tokens: tokenArr,
|
|
transpile: function () {
|
|
return "'" + tokenArr.map(function(t){return t.value}).join("") + "'";
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
_parser.addGrammarElement("number", function (parser, tokens) {
|
|
var number = tokens.matchTokenType('NUMBER');
|
|
if (number) {
|
|
var numberToken = number;
|
|
var value = parseFloat(number.value)
|
|
return {
|
|
type: "number",
|
|
value: value,
|
|
numberToken: numberToken,
|
|
transpile: function () {
|
|
return numberToken.value;
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
_parser.addGrammarElement("idRef", function (parser, tokens) {
|
|
var elementId = tokens.matchTokenType('ID_REF');
|
|
if (elementId) {
|
|
return {
|
|
type: "idRef",
|
|
value: elementId.value.substr(1),
|
|
transpile: function () {
|
|
return "document.getElementById('" + this.value + "')"
|
|
}
|
|
};
|
|
}
|
|
})
|
|
|
|
_parser.addGrammarElement("classRef", function (parser, tokens) {
|
|
var classRef = tokens.matchTokenType('CLASS_REF');
|
|
if (classRef) {
|
|
return {
|
|
type: "classRef",
|
|
value: classRef.value,
|
|
className: function () {
|
|
return this.value.substr(1);
|
|
},
|
|
transpile: function () {
|
|
return "document.querySelectorAll('" + this.value + "')"
|
|
}
|
|
};
|
|
}
|
|
})
|
|
|
|
_parser.addGrammarElement("attributeRef", function (parser, tokens) {
|
|
if (tokens.matchOpToken("[")) {
|
|
var name = tokens.matchTokenType("IDENTIFIER");
|
|
var value = null;
|
|
if (tokens.matchOpToken("=")) {
|
|
value = parser.parseElement("expression", tokens);
|
|
}
|
|
tokens.requireOpToken("]");
|
|
return {
|
|
type: "attribute_expression",
|
|
name: name.value,
|
|
value: value,
|
|
transpile: function () {
|
|
if (this.value) {
|
|
return "({name: '" + this.name + "', value: " + parser.transpile(this.value) + "})";
|
|
} else {
|
|
return "({name: '" + this.name + "'})";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
_parser.addGrammarElement("objectLiteral", function (parser, tokens) {
|
|
if (tokens.matchOpToken("{")) {
|
|
var fields = []
|
|
if (!tokens.matchOpToken("}")) {
|
|
do {
|
|
var name = tokens.requireTokenType("IDENTIFIER");
|
|
tokens.requireOpToken(":");
|
|
var value = parser.parseElement("expression", tokens);
|
|
fields.push({name: name, value: value});
|
|
} while (tokens.matchOpToken(","))
|
|
tokens.requireOpToken("}");
|
|
}
|
|
return {
|
|
type: "objectLiteral",
|
|
fields: fields,
|
|
transpile: function () {
|
|
return "({" + fields.map(function (field) {
|
|
return field.name.value + ":" + parser.transpile(field.value)
|
|
}).join(", ") + "})";
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
})
|
|
|
|
_parser.addGrammarElement("symbol", function (parser, tokens) {
|
|
var identifier = tokens.matchTokenType('IDENTIFIER');
|
|
if (identifier) {
|
|
return {
|
|
type: "symbol",
|
|
name: identifier.value,
|
|
transpile: function () {
|
|
return identifier.value;
|
|
}
|
|
};
|
|
}
|
|
});
|
|
|
|
_parser.addGrammarElement("implicitMeTarget", function (parser, tokens) {
|
|
return {
|
|
type: "implicitMeTarget",
|
|
transpile: function () {
|
|
return "[me]"
|
|
}
|
|
};
|
|
});
|
|
|
|
_parser.addGrammarElement("implicitAllTarget", function (parser, tokens) {
|
|
return {
|
|
type: "implicitAllTarget",
|
|
transpile: function () {
|
|
return 'document.querySelectorAll("*")';
|
|
}
|
|
};
|
|
});
|
|
|
|
_parser.addGrammarElement("millisecondLiteral", function (parser, tokens) {
|
|
var number = tokens.requireTokenType(tokens, "NUMBER");
|
|
var factor = 1;
|
|
if (tokens.matchToken("s")) {
|
|
factor = 1000;
|
|
} else if (tokens.matchToken("ms")) {
|
|
// do nothing
|
|
}
|
|
return {
|
|
type: "millisecondLiteral",
|
|
number: number,
|
|
factor: factor,
|
|
transpile: function () {
|
|
return factor * parseFloat(this.number.value);
|
|
}
|
|
};
|
|
});
|
|
|
|
_parser.addGrammarElement("boolean", function (parser, tokens) {
|
|
var booleanLiteral = tokens.matchToken("true") || tokens.matchToken("false");
|
|
if (booleanLiteral) {
|
|
return {
|
|
type: "boolean",
|
|
transpile: function () {
|
|
return booleanLiteral.value;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
_parser.addGrammarElement("null", function (parser, tokens) {
|
|
if (tokens.matchToken('null')) {
|
|
return {
|
|
type: "null",
|
|
transpile: function () {
|
|
return "null";
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
_parser.addGrammarElement("arrayLiteral", function (parser, tokens) {
|
|
if (tokens.matchOpToken('[')) {
|
|
var values = [];
|
|
if (!tokens.matchOpToken(']')) {
|
|
do {
|
|
var expr = parser.parseElement("expression", tokens);
|
|
if (expr == null) {
|
|
parser.raiseParseError(tokens, "Expected an expression");
|
|
}
|
|
values.push(expr);
|
|
} while(tokens.matchOpToken(","))
|
|
tokens.requireOpToken("]");
|
|
}
|
|
return {
|
|
type: "arrayLiteral",
|
|
values:values,
|
|
transpile: function () {
|
|
return "[" + values.map(function(v){ return parser.transpile(v) }).join(", ") + "]";
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
_parser.addGrammarElement("blockLiteral", function (parser, 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.parseElement("expression", tokens);
|
|
if (expr == null) {
|
|
parser.raiseParseError(tokens, "Expected an expression");
|
|
}
|
|
return {
|
|
type: "blockLiteral",
|
|
args: args,
|
|
expr: expr,
|
|
transpile: function () {
|
|
return "function(" + args.map(function (arg) {
|
|
return arg.value
|
|
}).join(", ") + "){ return " +
|
|
parser.transpile(expr) + " }";
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
_parser.addGrammarElement("leaf", function (parser, tokens) {
|
|
return parser.parseAnyOf(["parenthesized", "boolean", "null", "string", "number", "idRef", "classRef", "symbol", "propertyRef", "objectLiteral", "arrayLiteral", "blockLiteral"], tokens)
|
|
});
|
|
|
|
_parser.addGrammarElement("propertyAccess", function (parser, tokens, root) {
|
|
if (tokens.matchOpToken(".")) {
|
|
var prop = tokens.requireTokenType("IDENTIFIER");
|
|
var propertyAccess = {
|
|
type: "propertyAccess",
|
|
root: root,
|
|
prop: prop,
|
|
transpile: function () {
|
|
return parser.transpile(root) + "." + prop.value;
|
|
}
|
|
};
|
|
return _parser.parseElement("indirectExpression", tokens, propertyAccess);
|
|
}
|
|
});
|
|
|
|
_parser.addGrammarElement("functionCall", function (parser, tokens, root) {
|
|
if (tokens.matchOpToken("(")) {
|
|
var args = [];
|
|
do {
|
|
args.push(parser.parseElement("expression", tokens));
|
|
} while (tokens.matchOpToken(","))
|
|
tokens.requireOpToken(")");
|
|
var functionCall = {
|
|
type: "functionCall",
|
|
root: root,
|
|
args: args,
|
|
transpile: function () {
|
|
return parser.transpile(root) + "(" + args.map(function (arg) {
|
|
return parser.transpile(arg)
|
|
}).join(",") + ")"
|
|
}
|
|
};
|
|
return _parser.parseElement("indirectExpression", tokens, functionCall);
|
|
}
|
|
});
|
|
|
|
_parser.addGrammarElement("indirectExpression", function (parser, tokens, root) {
|
|
var propAccess = parser.parseElement("propertyAccess", tokens, root);
|
|
if (propAccess) {
|
|
return propAccess;
|
|
}
|
|
|
|
var functionCall = parser.parseElement("functionCall", tokens, root);
|
|
if (functionCall) {
|
|
return functionCall;
|
|
}
|
|
|
|
return root;
|
|
});
|
|
|
|
_parser.addGrammarElement("primaryExpression", function (parser, tokens) {
|
|
var leaf = parser.parseElement("leaf", tokens);
|
|
if (leaf) {
|
|
return parser.parseElement("indirectExpression", tokens, leaf);
|
|
}
|
|
parser.raiseParseError(tokens, "Unexpected value: " + tokens.currentToken().value);
|
|
});
|
|
|
|
_parser.addGrammarElement("postfixExpression", function (parser, 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,
|
|
transpile: function () {
|
|
return "_hyperscript.runtime.typeCheck(" + parser.transpile(root) + ", '" + typeName.value + "', " + nullOk + ")";
|
|
}
|
|
}
|
|
} else {
|
|
return root;
|
|
}
|
|
});
|
|
|
|
_parser.addGrammarElement("logicalNot", function (parser, tokens) {
|
|
if (tokens.matchToken("not")) {
|
|
var root = parser.parseElement("unaryExpression", tokens);
|
|
return {
|
|
type: "logicalNot",
|
|
root: root,
|
|
transpile: function () {
|
|
return "!" + parser.transpile(root);
|
|
}
|
|
};
|
|
}
|
|
});
|
|
|
|
_parser.addGrammarElement("negativeNumber", function (parser, tokens) {
|
|
if (tokens.matchOpToken("-")) {
|
|
var root = parser.parseElement("unaryExpression", tokens);
|
|
return {
|
|
type: "negativeNumber",
|
|
root: root,
|
|
transpile: function () {
|
|
return "-" + parser.transpile(root);
|
|
}
|
|
};
|
|
}
|
|
});
|
|
|
|
_parser.addGrammarElement("unaryExpression", function (parser, tokens) {
|
|
return parser.parseAnyOf(["logicalNot", "negativeNumber", "postfixExpression"], tokens);
|
|
});
|
|
|
|
_parser.addGrammarElement("mathOperator", function (parser, tokens) {
|
|
var expr = parser.parseElement("unaryExpression", tokens);
|
|
var mathOp, initialMathOp = null;
|
|
mathOp = tokens.matchAnyOpToken("+", "-", "*", "/", "%")
|
|
while (mathOp) {
|
|
initialMathOp = initialMathOp || mathOp;
|
|
if(initialMathOp.value !== mathOp.value) {
|
|
parser.raiseParseError(tokens, "You must parenthesize math operations with different operators")
|
|
}
|
|
var rhs = parser.parseElement("unaryExpression", tokens);
|
|
expr = {
|
|
type: "mathOperator",
|
|
operator: mathOp.value,
|
|
lhs: expr,
|
|
rhs: rhs,
|
|
transpile: function () {
|
|
return parser.transpile(this.lhs) + " " + this.operator + " " + parser.transpile(this.rhs);
|
|
}
|
|
}
|
|
mathOp = tokens.matchAnyOpToken("+", "-", "*", "/", "%")
|
|
}
|
|
return expr;
|
|
});
|
|
|
|
_parser.addGrammarElement("mathExpression", function (parser, tokens) {
|
|
return parser.parseAnyOf(["mathOperator", "unaryExpression"], tokens);
|
|
});
|
|
|
|
_parser.addGrammarElement("comparisonOperator", function (parser, tokens) {
|
|
var expr = parser.parseElement("mathExpression", tokens);
|
|
var comparisonOp, initialComparisonOp = null;
|
|
comparisonOp = tokens.matchAnyOpToken("<", ">", "<=", ">=", "==", "===")
|
|
while (comparisonOp) {
|
|
initialComparisonOp = initialComparisonOp || comparisonOp;
|
|
if(initialComparisonOp.value !== comparisonOp.value) {
|
|
parser.raiseParseError(tokens, "You must parenthesize comparison operations with different operators")
|
|
}
|
|
var rhs = parser.parseElement("mathExpression", tokens);
|
|
expr = {
|
|
type: "comparisonOperator",
|
|
operator: comparisonOp.value,
|
|
lhs: expr,
|
|
rhs: rhs,
|
|
transpile: function () {
|
|
return parser.transpile(this.lhs) + " " + this.operator + " " + parser.transpile(this.rhs);
|
|
}
|
|
}
|
|
comparisonOp = tokens.matchAnyOpToken("<", ">", "<=", ">=", "==", "===")
|
|
}
|
|
return expr;
|
|
});
|
|
|
|
_parser.addGrammarElement("comparisonExpression", function (parser, tokens) {
|
|
return parser.parseAnyOf(["comparisonOperator", "mathExpression"], tokens);
|
|
});
|
|
|
|
_parser.addGrammarElement("logicalOperator", function (parser, 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.parseElement("comparisonExpression", tokens);
|
|
expr = {
|
|
type: "logicalOperator",
|
|
operator: logicalOp.value,
|
|
lhs: expr,
|
|
rhs: rhs,
|
|
transpile: function () {
|
|
return parser.transpile(this.lhs) + " " + (this.operator === "and" ? " && " : " || ") + " " + parser.transpile(this.rhs);
|
|
}
|
|
}
|
|
logicalOp = tokens.matchToken("and") || tokens.matchToken("or");
|
|
}
|
|
return expr;
|
|
});
|
|
|
|
_parser.addGrammarElement("logicalExpression", function (parser, tokens) {
|
|
return parser.parseAnyOf(["logicalOperator", "mathExpression"], tokens);
|
|
});
|
|
|
|
_parser.addGrammarElement("expression", function (parser, tokens) {
|
|
return parser.parseElement("logicalExpression", tokens);
|
|
});
|
|
|
|
_parser.addGrammarElement("target", function (parser, tokens) {
|
|
var root = parser.parseAnyOf(["symbol", "classRef", "idRef"], tokens);
|
|
if (root == null) {
|
|
parser.raiseParseError(tokens, "Expected a valid target expression");
|
|
}
|
|
|
|
var propPath = []
|
|
while (tokens.matchOpToken(".")) {
|
|
propPath.push(tokens.requireTokenType("IDENTIFIER").value)
|
|
}
|
|
|
|
return {
|
|
type: "target",
|
|
propPath: propPath,
|
|
root: root,
|
|
transpile: function () {
|
|
return "_hyperscript.runtime.evalTarget(" + parser.transpile(root) + ", [" + propPath.map(function (prop) {
|
|
return "\"" + prop + "\""
|
|
}).join(", ") + "])";
|
|
}
|
|
};
|
|
});
|
|
|
|
_parser.addGrammarElement("command", function (parser, tokens) {
|
|
return parser.parseAnyOf(["onCmd", "addCmd", "removeCmd", "toggleCmd", "waitCmd", "sendCmd",
|
|
"takeCmd", "logCmd", "callCmd", "putCmd", "ifCmd", "ajaxCmd"], tokens);
|
|
})
|
|
|
|
_parser.addGrammarElement("commandList", function (parser, tokens) {
|
|
var cmd = parser.parseElement("command", tokens);
|
|
if (cmd) {
|
|
tokens.matchToken("then");
|
|
cmd.next = parser.parseElement("commandList", tokens);
|
|
return cmd;
|
|
}
|
|
})
|
|
|
|
_parser.addGrammarElement("hyperscript", function (parser, tokens) {
|
|
var eventListeners = []
|
|
do {
|
|
eventListeners.push(parser.parseElement("eventListener", tokens));
|
|
} while (tokens.matchToken("end") && tokens.hasMore())
|
|
if (tokens.hasMore()) {
|
|
parser.raiseParseError(tokens);
|
|
}
|
|
return {
|
|
type: "hyperscript",
|
|
eventListeners: eventListeners,
|
|
transpile: function () {
|
|
return "(function(){\n" +
|
|
"var eventListeners = []\n" +
|
|
eventListeners.map(function (el) {
|
|
return "eventListeners.push(" + parser.transpile(el) + ");\n"
|
|
}) +
|
|
" function applyEventListenersTo(elt) { _hyperscript.runtime.applyEventListeners(this, elt) }" +
|
|
" return {eventListeners:eventListeners, applyEventListenersTo:applyEventListenersTo}\n" +
|
|
"})()"
|
|
}
|
|
};
|
|
})
|
|
|
|
|
|
_parser.addGrammarElement("eventListener", function (parser, tokens) {
|
|
tokens.requireToken("on");
|
|
var on = parser.parseElement("dotPath", tokens);
|
|
if (on == null) {
|
|
parser.raiseParseError(tokens, "Expected event name")
|
|
}
|
|
if (tokens.matchToken("from")) {
|
|
var from = parser.parseElement("target", tokens);
|
|
if (from == null) {
|
|
parser.raiseParseError(tokens, "Expected target value")
|
|
}
|
|
} else {
|
|
var from = parser.parseElement("implicitMeTarget", tokens);
|
|
}
|
|
|
|
var args = [];
|
|
if (tokens.matchOpToken("(")) {
|
|
do {
|
|
args.push(tokens.requireTokenType('IDENTIFIER'));
|
|
} while (tokens.matchOpToken(","))
|
|
tokens.requireOpToken(')')
|
|
}
|
|
|
|
var start = parser.parseElement("commandList", tokens);
|
|
var eventListener = {
|
|
type: "eventListener",
|
|
on: on,
|
|
from: from,
|
|
start: start,
|
|
transpile: function () {
|
|
return "(function(me){" +
|
|
"var my = me;\n" +
|
|
"_hyperscript.runtime.forEach( " + parser.transpile(from) + ", function(target){\n" +
|
|
" target.addEventListener('" + parser.transpile(on) + "', function(event){\n" +
|
|
args.map(function(arg){return "var " + arg.value + " = event.detail." + arg.value + ";"}).join("\n") + "\n" +
|
|
parser.transpile(start) +
|
|
" })\n" +
|
|
"})\n" +
|
|
"})"
|
|
}
|
|
};
|
|
return eventListener;
|
|
});
|
|
|
|
_parser.addGrammarElement("addCmd", function (parser, 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.parseElement("target", tokens);
|
|
} else {
|
|
var to = parser.parseElement("implicitMeTarget");
|
|
}
|
|
|
|
return {
|
|
type: "addCmd",
|
|
classRef: classRef,
|
|
attributeRef: attributeRef,
|
|
to: to,
|
|
transpile: function () {
|
|
if (this.classRef) {
|
|
return "_hyperscript.runtime.forEach( " + parser.transpile(to) + ", function (target) {" +
|
|
" target.classList.add('" + classRef.className() + "')" +
|
|
"})";
|
|
} else {
|
|
return "_hyperscript.runtime.forEach( " + parser.transpile(to) + ", function (target) {" +
|
|
" target.setAttribute('" + attributeRef.name + "', " + parser.transpile(attributeRef) + ".value)" +
|
|
"})";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
_parser.addGrammarElement("removeCmd", function (parser, 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.parseElement("target", tokens);
|
|
} else {
|
|
var from = parser.parseElement("implicitMeTarget");
|
|
}
|
|
|
|
return {
|
|
type: "removeCmd",
|
|
classRef: classRef,
|
|
attributeRef: attributeRef,
|
|
elementExpr: elementExpr,
|
|
from: from,
|
|
transpile: function () {
|
|
if (this.elementExpr) {
|
|
return "_hyperscript.runtime.forEach( " + parser.transpile(elementExpr) + ", function (target) {" +
|
|
" target.parentElement.removeChild(target)" +
|
|
"})";
|
|
} else {
|
|
if (this.classRef) {
|
|
return "_hyperscript.runtime.forEach( " + parser.transpile(from) + ", function (target) {" +
|
|
" target.classList.remove('" + classRef.className() + "')" +
|
|
"})";
|
|
} else {
|
|
return "_hyperscript.runtime.forEach( " + parser.transpile(from) + ", function (target) {" +
|
|
" target.removeAttribute('" + attributeRef.name + "')" +
|
|
"})";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
_parser.addGrammarElement("toggleCmd", function (parser, tokens) {
|
|
if (tokens.matchToken("toggle")) {
|
|
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.parseElement("target", tokens);
|
|
} else {
|
|
var on = parser.parseElement("implicitMeTarget");
|
|
}
|
|
return {
|
|
type: "toggleCmd",
|
|
classRef: classRef,
|
|
attributeRef: attributeRef,
|
|
on: on,
|
|
transpile: function () {
|
|
if (this.classRef) {
|
|
return "_hyperscript.runtime.forEach( " + parser.transpile(on) + ", function (target) {" +
|
|
" target.classList.toggle('" + classRef.className() + "')" +
|
|
"})";
|
|
} else {
|
|
return "_hyperscript.runtime.forEach( " + parser.transpile(on) + ", function (target) {" +
|
|
" if(target.hasAttribute('" + attributeRef.name + "')) {\n" +
|
|
" target.removeAttribute('" + attributeRef.name + "');\n" +
|
|
" } else { \n" +
|
|
" target.setAttribute('" + attributeRef.name + "', " + parser.transpile(attributeRef) + ".value)" +
|
|
" }" +
|
|
"})";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
_parser.addGrammarElement("waitCmd", function (parser, tokens) {
|
|
if (tokens.matchToken("wait")) {
|
|
var time = parser.parseElement('millisecondLiteral', tokens);
|
|
return {
|
|
type: "waitCmd",
|
|
time: time,
|
|
transpile: function () {
|
|
var capturedNext = this.next;
|
|
delete this.next;
|
|
return "setTimeout(function () { " + parser.transpile(capturedNext) + " }, " + parser.transpile(this.time) + ")";
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
_parser.addGrammarElement("dotPath", function (parser, tokens) {
|
|
var root = tokens.matchTokenType("IDENTIFIER");
|
|
if (root) {
|
|
var path = [root.value];
|
|
while (tokens.matchOpToken(".")) {
|
|
path.push(tokens.requireTokenType("IDENTIFIER").value);
|
|
}
|
|
return {
|
|
type: "dotPath",
|
|
path: path,
|
|
transpile: function () {
|
|
return path.join(".");
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
_parser.addGrammarElement("sendCmd", function (parser, tokens) {
|
|
if (tokens.matchToken("send")) {
|
|
|
|
var eventName = parser.parseElement("dotPath", tokens);
|
|
|
|
var details = parser.parseElement("objectLiteral", tokens);
|
|
if (tokens.matchToken("to")) {
|
|
var to = parser.parseElement("target", tokens);
|
|
} else {
|
|
var to = parser.parseElement("implicitMeTarget");
|
|
}
|
|
|
|
return {
|
|
type: "sendCmd",
|
|
eventName: eventName,
|
|
details: details,
|
|
to: to,
|
|
transpile: function () {
|
|
return "_hyperscript.runtime.forEach( " + parser.transpile(to) + ", function (target) {" +
|
|
" _hyperscript.runtime.triggerEvent(target, '" + parser.transpile(eventName) + "'," + parser.transpile(details, "{}") + ")" +
|
|
"})";
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
_parser.addGrammarElement("takeCmd", function (parser, tokens) {
|
|
if (tokens.matchToken("take")) {
|
|
var classRef = tokens.requireTokenType(tokens, "CLASS_REF");
|
|
|
|
if (tokens.matchToken("from")) {
|
|
var from = parser.parseElement("target", tokens);
|
|
} else {
|
|
var from = parser.parseElement("implicitAllTarget")
|
|
}
|
|
|
|
if (tokens.matchToken("for")) {
|
|
var forElt = parser.parseElement("target", tokens);
|
|
} else {
|
|
var forElt = parser.parseElement("implicitMeTarget")
|
|
}
|
|
|
|
return {
|
|
type: "takeCmd",
|
|
classRef: classRef,
|
|
from: from,
|
|
forElt: forElt,
|
|
transpile: function () {
|
|
var clazz = this.classRef.value.substr(1);
|
|
return " _hyperscript.runtime.forEach(" + parser.transpile(from) + ", function (target) { target.classList.remove('" + clazz + "') }); " +
|
|
"_hyperscript.runtime.forEach( " + parser.transpile(forElt) + ", function (target) {" +
|
|
" target.classList.add('" + clazz + "')" +
|
|
"})";
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
_parser.addGrammarElement("logCmd", function (parser, tokens) {
|
|
if (tokens.matchToken("log")) {
|
|
var exprs = [parser.parseElement("expression", tokens)];
|
|
while (tokens.matchOpToken(",")) {
|
|
exprs.push(parser.parseElement("expression", tokens));
|
|
}
|
|
if (tokens.matchToken("with")) {
|
|
var withExpr = parser.parseElement("expression", tokens);
|
|
}
|
|
return {
|
|
type: "logCmd",
|
|
exprs: exprs,
|
|
withExpr: withExpr,
|
|
transpile: function () {
|
|
if (withExpr) {
|
|
return parser.transpile(withExpr) + "(" + exprs.map(function (expr) {
|
|
return parser.transpile(expr)
|
|
}).join(", ") + ")";
|
|
} else {
|
|
return "console.log(" + exprs.map(function (expr) {
|
|
return parser.transpile(expr)
|
|
}).join(", ") + ")";
|
|
}
|
|
}
|
|
};
|
|
}
|
|
})
|
|
|
|
_parser.addGrammarElement("callCmd", function (parser, tokens) {
|
|
if (tokens.matchToken("call")) {
|
|
return {
|
|
type: "callCmd",
|
|
expr: parser.parseElement("expression", tokens),
|
|
transpile: function () {
|
|
return "var it = " + parser.transpile(this.expr);
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
_parser.addGrammarElement("putCmd", function (parser, tokens) {
|
|
if (tokens.matchToken("put")) {
|
|
|
|
var value = parser.parseElement("expression", tokens);
|
|
|
|
var operation = tokens.matchToken("into") ||
|
|
tokens.matchToken("before") ||
|
|
tokens.matchToken("afterbegin") ||
|
|
tokens.matchToken("beforeend") ||
|
|
tokens.matchToken("after");
|
|
|
|
if (operation == null) {
|
|
parser.raiseParseError(tokens, "Expected one of 'into', 'before', 'afterbegin', 'beforeend', 'after'")
|
|
}
|
|
var target = parser.parseElement("target", tokens);
|
|
|
|
var directWrite = target.propPath.length === 0 && operation.value === "into";
|
|
var symbolWrite = directWrite && target.root.type === "symbol";
|
|
if (directWrite && !symbolWrite) {
|
|
parser.raiseParseError(tokens, "Can only put directly into symbols, not references")
|
|
}
|
|
|
|
return {
|
|
type: "putCmd",
|
|
target: target,
|
|
op: operation.value,
|
|
symbolWrite: symbolWrite,
|
|
value: value,
|
|
transpile: function () {
|
|
if (this.symbolWrite) {
|
|
return "var " + target.root.name + " = " + parser.transpile(value);
|
|
} else {
|
|
if (this.op === "into") {
|
|
var lastProperty = target.propPath.pop(); // steal last property for assignment
|
|
return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" +
|
|
" target." + lastProperty + "=" + parser.transpile(value) +
|
|
"})";
|
|
} else if (this.op === "before") {
|
|
return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" +
|
|
" target.insertAdjacentHTML('beforebegin', " + parser.transpile(value) + ")" +
|
|
"})";
|
|
} else if (this.op === "afterbegin") {
|
|
return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" +
|
|
" target.insertAdjacentHTML('afterbegin', " + parser.transpile(value) + ")" +
|
|
"})";
|
|
} else if (this.op === "beforeend") {
|
|
return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" +
|
|
" target.insertAdjacentHTML('beforeend', " + parser.transpile(value) + ")" +
|
|
"})";
|
|
} else if (this.op === "after") {
|
|
return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" +
|
|
" target.insertAdjacentHTML('afterend', " + parser.transpile(value) + ")" +
|
|
"})";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
_parser.addGrammarElement("ifCmd", function (parser, tokens) {
|
|
if (tokens.matchToken("if")) {
|
|
var expr = parser.parseElement("expression", tokens);
|
|
var trueBranch = parser.parseElement("commandList", tokens);
|
|
if (tokens.matchToken("else")) {
|
|
var falseBranch = parser.parseElement("commandList", tokens);
|
|
}
|
|
if (tokens.hasMore()) {
|
|
tokens.requireToken("end");
|
|
}
|
|
return {
|
|
type: "ifCmd",
|
|
expr: expr,
|
|
trueBranch: trueBranch,
|
|
falseBranch: falseBranch,
|
|
transpile: function () {
|
|
return "if(" + parser.transpile(expr) + "){" + "" + parser.transpile(trueBranch) + "}" +
|
|
" else {" + parser.transpile(falseBranch, "") + "}"
|
|
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
_parser.addGrammarElement("ajaxCmd", function (parser, tokens) {
|
|
if (tokens.matchToken("ajax")) {
|
|
var method = tokens.matchToken("GET") || tokens.matchToken("POST");
|
|
if (method == null) {
|
|
parser.raiseParseError(tokens, "Requires either GET or POST");
|
|
}
|
|
if (method.value === "GET") {
|
|
tokens.requireToken("from");
|
|
} else {
|
|
if (!tokens.matchToken("to")) {
|
|
var data = parser.parseElement("expression", tokens);
|
|
tokens.requireToken("to");
|
|
}
|
|
}
|
|
|
|
var url = parser.parseElement("string", tokens);
|
|
if (url == null) {
|
|
var url = parser.parseElement("nakedString", tokens);
|
|
}
|
|
|
|
return {
|
|
type: "requestCommand",
|
|
method: method,
|
|
transpile: function () {
|
|
var capturedNext = this.next;
|
|
delete this.next;
|
|
return "_hyperscript.runtime.ajax('" + method.value + "', " +
|
|
parser.transpile(url) + ", " +
|
|
"function(response){ " + parser.transpile(capturedNext) + " }," +
|
|
parser.transpile(data, "null") + ")";
|
|
}
|
|
};
|
|
}
|
|
})
|
|
|
|
//-----------------------------------------------
|
|
// API
|
|
//-----------------------------------------------
|
|
|
|
function start(scriptAttrs) {
|
|
if (scriptAttrs) {
|
|
_runtime.setScriptAttrs(scriptAttrs);
|
|
}
|
|
var fn = function () {
|
|
var elements = document.querySelectorAll(_runtime.getScriptSelector());
|
|
_runtime.forEach(elements, function (elt) {
|
|
init(elt);
|
|
})
|
|
};
|
|
if (document.readyState !== 'loading') {
|
|
fn();
|
|
} else {
|
|
document.addEventListener('DOMContentLoaded', fn);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function init(elt) {
|
|
_runtime.initElement(elt);
|
|
}
|
|
|
|
function evaluate(str) {
|
|
return _runtime.evaluate(str);
|
|
}
|
|
|
|
return {
|
|
lexer: _lexer,
|
|
parser: _parser,
|
|
runtime: _runtime,
|
|
evaluate: evaluate,
|
|
init: init,
|
|
start: start
|
|
}
|
|
}
|
|
)()
|
|
})); |