UNPKG

hyperscript.org

Version:

a small scripting language for the web

1,524 lines (1,517 loc) 410 kB
(() => { var __defProp = Object.defineProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // src/core/tokenizer.js var Tokens = class { #tokens; #consumed = []; #lastConsumed = null; #follows = []; source; constructor(tokens, source) { this.#tokens = tokens; this.source = source; this.consumeWhitespace(); } get list() { return this.#tokens; } get consumed() { return this.#consumed; } // ----- Debug ----- toString() { var cur = this.currentToken(); var lines = this.source.split("\n"); var lineIdx = cur?.line ? cur.line - 1 : lines.length - 1; var col = cur?.line ? cur.column : 0; var contextLine = lines[lineIdx] || ""; var tokenLen = Math.max(1, cur?.value?.length || 1); var gutter = String(lineIdx + 1).length; var out = "Tokens("; out += this.#consumed.filter((t) => t.type !== "WHITESPACE").length + " consumed, "; out += this.#tokens.filter((t) => t.type !== "WHITESPACE").length + " remaining"; out += ", line " + (lineIdx + 1) + ")\n"; out += " " + String(lineIdx + 1).padStart(gutter) + " | " + contextLine + "\n"; out += " ".repeat(gutter + 5) + " ".repeat(col) + "^".repeat(tokenLen); if (cur) out += " " + cur.type + " '" + cur.value + "'"; return out; } // ----- Token access ----- currentToken() { return this.token(0); } token(n, includeWhitespace) { var token; var i = 0; do { if (!includeWhitespace) { while (this.#tokens[i] && this.#tokens[i].type === "WHITESPACE") { i++; } } token = this.#tokens[i]; n--; i++; } while (n > -1); return token || { type: "EOF", value: "<<<EOF>>>" }; } hasMore() { return this.#tokens.length > 0; } lastMatch() { return this.#lastConsumed; } // ----- Token matching ----- matchToken(value, type) { if (this.#follows.includes(value)) return; type = type || "IDENTIFIER"; if (this.currentToken() && this.currentToken().value === value && this.currentToken().type === type) { return this.consumeToken(); } } matchOpToken(value) { if (this.currentToken() && this.currentToken().op && this.currentToken().value === value) { return this.consumeToken(); } } matchTokenType(...types) { if (this.currentToken() && this.currentToken().type && types.includes(this.currentToken().type)) { return this.consumeToken(); } } matchAnyToken(...tokens) { for (var i = 0; i < tokens.length; i++) { var match = this.matchToken(tokens[i]); if (match) return match; } } matchAnyOpToken(...ops) { for (var i = 0; i < ops.length; i++) { var match = this.matchOpToken(ops[i]); if (match) return match; } } // ----- Token consuming ----- consumeToken() { var match = this.#tokens.shift(); this.#consumed.push(match); this.#lastConsumed = match; this.consumeWhitespace(); return match; } consumeWhitespace() { while (this.token(0, true).type === "WHITESPACE") { this.#consumed.push(this.#tokens.shift()); } } consumeUntil(value, type) { var tokenList = []; var currentToken = this.token(0, true); while ((type == null || currentToken.type !== type) && (value == null || currentToken.value !== value) && currentToken.type !== "EOF") { var match = this.#tokens.shift(); this.#consumed.push(match); tokenList.push(currentToken); currentToken = this.token(0, true); } this.consumeWhitespace(); return tokenList; } consumeUntilWhitespace() { return this.consumeUntil(null, "WHITESPACE"); } // ----- Lookahead ----- peekToken(value, peek, type) { peek = peek || 0; type = type || "IDENTIFIER"; let peekNoWhitespace = 0; while (peek > 0) { peekNoWhitespace++; if (this.#tokens[peekNoWhitespace]?.type !== "WHITESPACE") { peek--; } } if (this.#tokens[peekNoWhitespace] && this.#tokens[peekNoWhitespace].value === value && this.#tokens[peekNoWhitespace].type === type) { return this.#tokens[peekNoWhitespace]; } } // ----- Whitespace ----- lastWhitespace() { var last = this.#consumed.at(-1); return last && last.type === "WHITESPACE" ? last.value : ""; } // ----- Follow set management ----- pushFollow(str) { this.#follows.push(str); } popFollow() { this.#follows.pop(); } pushFollows(...strs) { for (var i = 0; i < strs.length; i++) this.#follows.push(strs[i]); return strs.length; } popFollows(count) { for (var i = 0; i < count; i++) this.#follows.pop(); } clearFollows() { var tmp = this.#follows; this.#follows = []; return tmp; } restoreFollows(f) { this.#follows = f; } }; 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", "~": "TILDE", "^": "CARET" }; var Tokenizer = class _Tokenizer { // ----- Instance state ----- #source = ""; #position = 0; #column = 0; #line = 1; #lastToken = "<START>"; #templateBraceCount = 0; #tokens = []; #template = false; #templateMode; // ----- Character classification ----- #isAlpha(c) { return c >= "a" && c <= "z" || c >= "A" && c <= "Z"; } #isNumeric(c) { return c >= "0" && c <= "9"; } #isWhitespace(c) { return c === " " || c === " " || c === "\r" || c === "\n"; } #isNewline(c) { return c === "\r" || c === "\n"; } #isValidCSSChar(c) { return this.#isAlpha(c) || this.#isNumeric(c) || c === "-" || c === "_" || c === ":"; } #isIdentifierChar(c) { return c === "_" || c === "$"; } #isReservedChar(c) { return c === "`"; } static tokenize(string, template) { return new _Tokenizer().tokenize(string, template); } tokenize(string, template) { this.#source = string; this.#position = 0; this.#column = 0; this.#line = 1; this.#lastToken = "<START>"; this.#templateBraceCount = 0; this.#tokens = []; this.#template = template || false; this.#templateMode = "indeterminant"; return this.#tokenize(); } // ----- Character access ----- #currentChar() { return this.#source.charAt(this.#position); } #nextChar() { return this.#source.charAt(this.#position + 1); } #charAt(offset = 1) { return this.#source.charAt(this.#position + offset); } #consumeChar() { this.#lastToken = this.#currentChar(); this.#position++; if (this.#lastToken === "\n") { this.#line++; this.#column = 0; } else { this.#column++; } return this.#lastToken; } // ----- Context checks ----- #inTemplate() { return this.#template && this.#templateBraceCount === 0; } #inCommandMode() { return !this.#inTemplate() || this.#templateMode === "command"; } #possiblePrecedingSymbol() { return this.#isAlpha(this.#lastToken) || this.#isNumeric(this.#lastToken) || this.#lastToken === ")" || this.#lastToken === '"' || this.#lastToken === "'" || this.#lastToken === "`" || this.#lastToken === "}" || this.#lastToken === "]"; } #isValidSingleQuoteStringStart() { if (this.#tokens.length > 0) { var prev = this.#tokens.at(-1); if (prev.type === "IDENTIFIER" || prev.type === "CLASS_REF" || prev.type === "ID_REF") { return false; } if (prev.op && (prev.value === ">" || prev.value === ")")) { return false; } } return true; } // ----- Token constructors ----- #makeToken(type, value) { return { type, value: value || "", start: this.#position, end: this.#position + 1, column: this.#column, line: this.#line }; } #makeOpToken(type, value) { var token = this.#makeToken(type, value); token.op = true; return token; } // ----- Consume methods ----- #consumeComment() { while (this.#currentChar() && !this.#isNewline(this.#currentChar())) { this.#consumeChar(); } this.#consumeChar(); } #consumeWhitespace() { var ws = this.#makeToken("WHITESPACE"); var value = ""; while (this.#currentChar() && this.#isWhitespace(this.#currentChar())) { if (this.#isNewline(this.#currentChar())) { this.#templateMode = "indeterminant"; } value += this.#consumeChar(); } ws.value = value; ws.end = this.#position; return ws; } #consumeClassReference() { var token = this.#makeToken("CLASS_REF"); var value = this.#consumeChar(); if (this.#currentChar() === "{") { token.template = true; value += this.#consumeChar(); while (this.#currentChar() && this.#currentChar() !== "}") { value += this.#consumeChar(); } if (this.#currentChar() !== "}") { throw new Error("Unterminated class reference"); } else { value += this.#consumeChar(); } } else { while (this.#isValidCSSChar(this.#currentChar()) || this.#currentChar() === "\\") { if (this.#currentChar() === "\\") this.#consumeChar(); value += this.#consumeChar(); } } token.value = value; token.end = this.#position; return token; } #consumeIdReference() { var token = this.#makeToken("ID_REF"); var value = this.#consumeChar(); if (this.#currentChar() === "{") { token.template = true; value += this.#consumeChar(); while (this.#currentChar() && this.#currentChar() !== "}") { value += this.#consumeChar(); } if (this.#currentChar() !== "}") { throw new Error("Unterminated id reference"); } else { this.#consumeChar(); } } else { while (this.#isValidCSSChar(this.#currentChar())) { value += this.#consumeChar(); } } token.value = value; token.end = this.#position; return token; } #consumeAttributeReference() { var token = this.#makeToken("ATTRIBUTE_REF"); var value = this.#consumeChar(); while (this.#position < this.#source.length && this.#currentChar() !== "]") { value += this.#consumeChar(); } if (this.#currentChar() === "]") { value += this.#consumeChar(); } token.value = value; token.end = this.#position; return token; } #consumeShortAttributeReference() { var token = this.#makeToken("ATTRIBUTE_REF"); var value = this.#consumeChar(); while (this.#isValidCSSChar(this.#currentChar())) { value += this.#consumeChar(); } if (this.#currentChar() === "=") { value += this.#consumeChar(); if (this.#currentChar() === '"' || this.#currentChar() === "'") { value += this.#consumeString().value; } else if (this.#isAlpha(this.#currentChar()) || this.#isNumeric(this.#currentChar()) || this.#isIdentifierChar(this.#currentChar())) { value += this.#consumeIdentifier().value; } } token.value = value; token.end = this.#position; return token; } #consumeStyleReference() { var token = this.#makeToken("STYLE_REF"); var value = this.#consumeChar(); while (this.#isAlpha(this.#currentChar()) || this.#currentChar() === "-") { value += this.#consumeChar(); } token.value = value; token.end = this.#position; return token; } #consumeTemplateLogic() { var token = this.#makeToken("IDENTIFIER"); this.#consumeChar(); var value = ""; while (this.#isAlpha(this.#currentChar())) { value += this.#consumeChar(); } token.value = value; token.end = this.#position; return token; } #consumeTemplateLine() { var token = this.#makeToken("TEMPLATE_LINE"); token.value = "TEMPLATE_LINE"; var content = ""; while (this.#currentChar() && !this.#isNewline(this.#currentChar())) { content += this.#consumeChar(); } if (this.#currentChar() && this.#isNewline(this.#currentChar())) { this.#consumeChar(); content += "\n"; this.#templateMode = "indeterminant"; } token.content = content; token.end = this.#position; return token; } #consumeTemplateIdentifier() { var token = this.#makeToken("IDENTIFIER"); var value = this.#consumeChar(); var escaped = value === "\\"; if (escaped) value = ""; while (this.#isAlpha(this.#currentChar()) || this.#isNumeric(this.#currentChar()) || this.#isIdentifierChar(this.#currentChar()) || this.#currentChar() === "\\" || this.#currentChar() === "{" || this.#currentChar() === "}") { if (this.#currentChar() === "$" && !escaped) { break; } else if (this.#currentChar() === "\\") { escaped = true; this.#consumeChar(); } else { escaped = false; value += this.#consumeChar(); } } if (this.#currentChar() === "!" && value === "beep") { value += this.#consumeChar(); } token.value = value; token.end = this.#position; return token; } #consumeIdentifier() { var token = this.#makeToken("IDENTIFIER"); var value = this.#consumeChar(); while (this.#isAlpha(this.#currentChar()) || this.#isNumeric(this.#currentChar()) || this.#isIdentifierChar(this.#currentChar())) { value += this.#consumeChar(); } if (this.#currentChar() === "!" && value === "beep") { value += this.#consumeChar(); } token.value = value; token.end = this.#position; return token; } #consumeNumber() { var token = this.#makeToken("NUMBER"); var value = this.#consumeChar(); while (this.#isNumeric(this.#currentChar())) { value += this.#consumeChar(); } if (this.#currentChar() === "." && this.#isNumeric(this.#nextChar())) { value += this.#consumeChar(); } while (this.#isNumeric(this.#currentChar())) { value += this.#consumeChar(); } if (this.#currentChar() === "e" || this.#currentChar() === "E") { if (this.#isNumeric(this.#nextChar())) { value += this.#consumeChar(); } else if (this.#nextChar() === "-") { value += this.#consumeChar(); value += this.#consumeChar(); } } while (this.#isNumeric(this.#currentChar())) { value += this.#consumeChar(); } token.value = value; token.end = this.#position; return token; } #consumeOp() { var token = this.#makeOpToken(); var value = this.#consumeChar(); while (this.#currentChar() && OP_TABLE[value + this.#currentChar()]) { value += this.#consumeChar(); } token.type = OP_TABLE[value]; token.value = value; token.end = this.#position; return token; } #consumeString() { var token = this.#makeToken("STRING"); var startChar = this.#consumeChar(); token.template = startChar === "`"; var value = ""; while (this.#currentChar() && this.#currentChar() !== startChar) { if (this.#currentChar() === "\\") { this.#consumeChar(); let next = this.#consumeChar(); if (next === "b") value += "\b"; else if (next === "f") value += "\f"; else if (next === "n") value += "\n"; else if (next === "r") value += "\r"; else if (next === "t") value += " "; else if (next === "v") value += "\v"; else if (token.template && next === "$") value += "\\$"; else if (next === "x") { const hex = this.#consumeHexEscape(); if (Number.isNaN(hex)) { throw new Error("Invalid hexadecimal escape at [Line: " + token.line + ", Column: " + token.column + "]"); } value += String.fromCharCode(hex); } else value += next; } else { value += this.#consumeChar(); } } if (this.#currentChar() !== startChar) { throw new Error("Unterminated string at [Line: " + token.line + ", Column: " + token.column + "]"); } else { this.#consumeChar(); } token.value = value; token.end = this.#position; return token; } #consumeHexEscape() { if (!this.#currentChar()) return NaN; let result = 16 * Number.parseInt(this.#consumeChar(), 16); if (!this.#currentChar()) return NaN; result += Number.parseInt(this.#consumeChar(), 16); return result; } // ----- Main tokenization loop ----- #isLineComment() { var c = this.#currentChar(), n = this.#nextChar(), n2 = this.#charAt(2); return c === "-" && n === "-" && (this.#isWhitespace(n2) || n2 === "" || n2 === "-") || c === "/" && n === "/" && (this.#isWhitespace(n2) || n2 === "" || n2 === "/"); } #tokenize() { while (this.#position < this.#source.length) { if (this.#isLineComment()) { this.#consumeComment(); } else if (this.#isWhitespace(this.#currentChar())) { this.#tokens.push(this.#consumeWhitespace()); } else if (!this.#possiblePrecedingSymbol() && this.#currentChar() === "." && (this.#isAlpha(this.#nextChar()) || this.#nextChar() === "{" || this.#nextChar() === "-")) { this.#tokens.push(this.#consumeClassReference()); } else if (!this.#possiblePrecedingSymbol() && this.#currentChar() === "#" && (this.#isAlpha(this.#nextChar()) || this.#nextChar() === "{")) { if (this.#template === "lines" && this.#templateMode === "indeterminant") { this.#templateMode = "command"; this.#tokens.push(this.#consumeTemplateLogic()); } else { this.#tokens.push(this.#consumeIdReference()); } } else if (this.#template === "lines" && this.#templateMode === "indeterminant" && this.#templateBraceCount === 0) { this.#templateMode = "template"; this.#tokens.push(this.#consumeTemplateLine()); } else if (this.#currentChar() === "[" && this.#nextChar() === "@") { this.#tokens.push(this.#consumeAttributeReference()); } else if (this.#currentChar() === "@") { this.#tokens.push(this.#consumeShortAttributeReference()); } else if (this.#currentChar() === "*" && this.#isAlpha(this.#nextChar())) { this.#tokens.push(this.#consumeStyleReference()); } else if (this.#inTemplate() && (this.#isAlpha(this.#currentChar()) || this.#currentChar() === "\\") && this.#templateMode !== "command") { this.#tokens.push(this.#consumeTemplateIdentifier()); } else if (this.#inCommandMode() && (this.#isAlpha(this.#currentChar()) || this.#isIdentifierChar(this.#currentChar()))) { this.#tokens.push(this.#consumeIdentifier()); } else if (this.#isNumeric(this.#currentChar())) { this.#tokens.push(this.#consumeNumber()); } else if (this.#inCommandMode() && (this.#currentChar() === '"' || this.#currentChar() === "`")) { this.#tokens.push(this.#consumeString()); } else if (this.#inCommandMode() && this.#currentChar() === "'") { if (this.#isValidSingleQuoteStringStart()) { this.#tokens.push(this.#consumeString()); } else { this.#tokens.push(this.#consumeOp()); } } else if (OP_TABLE[this.#currentChar()]) { if (this.#lastToken === "$" && this.#currentChar() === "{") { this.#templateBraceCount++; } if (this.#currentChar() === "}") { this.#templateBraceCount--; } this.#tokens.push(this.#consumeOp()); } else if (this.#inTemplate() || this.#isReservedChar(this.#currentChar())) { this.#tokens.push(this.#makeToken("RESERVED", this.#consumeChar())); } else { if (this.#position < this.#source.length) { throw new Error("Unknown token: " + this.#currentChar() + " "); } } } return new Tokens(this.#tokens, this.#source); } }; // src/parsetree/base.js var ParseElement = class _ParseElement { errors = []; collectErrors(visited) { if (!visited) visited = /* @__PURE__ */ new Set(); if (visited.has(this)) return []; visited.add(this); var all = [...this.errors]; for (var key of Object.keys(this)) { for (var item of [this[key]].flat()) { if (item instanceof _ParseElement) { all.push(...item.collectErrors(visited)); } } } return all; } sourceFor() { return this.programSource.substring(this.startToken.start, this.endToken.end); } lineFor() { return this.programSource.split("\n")[this.startToken.line - 1]; } static parseEventArgs(parser) { var args = []; if (parser.token(0).value === "(" && (parser.token(1).value === ")" || parser.token(2).value === "," || parser.token(2).value === ")")) { parser.matchOpToken("("); do { args.push(parser.requireTokenType("IDENTIFIER")); } while (parser.matchOpToken(",")); parser.requireOpToken(")"); } return args; } }; var Expression = class extends ParseElement { constructor() { super(); if (this.constructor.grammarName) { this.type = this.constructor.grammarName; } } evaluate(context) { return context.meta.runtime.unifiedEval(this, context); } evalStatically() { throw new Error("This expression cannot be evaluated statically: " + this.type); } }; var Command = class extends ParseElement { constructor() { super(); if (this.constructor.keyword) { this.type = this.constructor.keyword + "Command"; } } execute(context) { context.meta.command = this; return context.meta.runtime.unifiedExec(this, context); } findNext(context) { return context.meta.runtime.findNext(this, context); } }; var Feature = class extends ParseElement { isFeature = true; constructor() { super(); if (this.constructor.keyword) { this.type = this.constructor.keyword + "Feature"; } } install(target, source, args, runtime2) { } /** * Parse optional catch/finally blocks after a command list. * Returns { errorHandler, errorSymbol, finallyHandler } */ static parseErrorAndFinally(parser) { var errorSymbol, errorHandler, finallyHandler; if (parser.matchToken("catch")) { errorSymbol = parser.requireTokenType("IDENTIFIER").value; errorHandler = parser.requireElement("commandList"); parser.ensureTerminated(errorHandler); } if (parser.matchToken("finally")) { finallyHandler = parser.requireElement("commandList"); parser.ensureTerminated(finallyHandler); } return { errorHandler, errorSymbol, finallyHandler }; } }; // src/parsetree/internals.js var EmptyCommandListCommand = class extends Command { constructor() { super(); this.type = "emptyCommandListCommand"; } resolve(context) { return this.findNext(context); } }; var UnlessStatementModifier = class extends Command { constructor(root, conditional) { super(); this.type = "unlessStatementModifier"; this.root = root; this.args = { conditional }; } resolve(context, { conditional }) { if (conditional) { return this.next; } else { return this.root; } } }; var HyperscriptProgram = class extends ParseElement { constructor(features) { super(); this.type = "hyperscript"; this.features = features; } apply(target, source, args, runtime2) { for (const feature of this.features) { feature.install(target, source, args, runtime2); } } }; var FailedFeature = class extends Feature { constructor(error, keyword) { super(); this.type = "failedFeature"; this.keyword = keyword; this.errors.push(error); } install() { } }; var FailedCommand = class extends Command { constructor(error, keyword) { super(); this.type = "failedCommand"; this.keyword = keyword; this.errors.push(error); } resolve() { } }; var ImplicitReturn = class extends Command { constructor() { super(); this.type = "implicitReturn"; } resolve(context) { context.meta.returned = true; if (context.meta.resolve) { context.meta.resolve(); } return context.meta.runtime.HALT; } }; // src/parsetree/expressions/literals.js var literals_exports = {}; __export(literals_exports, { ArrayLiteral: () => ArrayLiteral, BooleanLiteral: () => BooleanLiteral, NakedNamedArgumentList: () => NakedNamedArgumentList, NakedString: () => NakedString, NamedArgumentList: () => NamedArgumentList, NullLiteral: () => NullLiteral, NumberLiteral: () => NumberLiteral, ObjectKey: () => ObjectKey, ObjectLiteral: () => ObjectLiteral, StringLike: () => StringLike, StringLiteral: () => StringLiteral }); var NakedString = class _NakedString extends Expression { static grammarName = "nakedString"; constructor(tokens) { super(); this.tokens = tokens; } static parse(parser) { if (parser.hasMore()) { var tokenArr = parser.consumeUntilWhitespace(); parser.matchTokenType("WHITESPACE"); return new _NakedString(tokenArr); } } evalStatically() { return this.resolve(); } resolve(context) { return this.tokens.map(function(t) { return t.value; }).join(""); } }; var BooleanLiteral = class _BooleanLiteral extends Expression { static grammarName = "boolean"; static expressionType = "leaf"; constructor(value) { super(); this.value = value; } static parse(parser) { var booleanLiteral = parser.matchToken("true") || parser.matchToken("false"); if (!booleanLiteral) return; const value = booleanLiteral.value === "true"; return new _BooleanLiteral(value); } evalStatically() { return this.value; } resolve(context) { return this.value; } }; var NullLiteral = class _NullLiteral extends Expression { static grammarName = "null"; static expressionType = "leaf"; constructor() { super(); } static parse(parser) { if (parser.matchToken("null")) { return new _NullLiteral(); } } evalStatically() { return null; } resolve(context) { return null; } }; var NumberLiteral = class _NumberLiteral extends Expression { static grammarName = "number"; static expressionType = "leaf"; constructor(value, numberToken) { super(); this.value = value; this.numberToken = numberToken; } static parse(parser) { var number = parser.matchTokenType("NUMBER"); if (!number) return; var numberToken = number; var value = parseFloat( /** @type {string} */ number.value ); return new _NumberLiteral(value, numberToken); } evalStatically() { return this.value; } resolve(context) { return this.value; } }; var StringLiteral = class _StringLiteral extends Expression { static grammarName = "string"; static expressionType = "leaf"; constructor(stringToken, rawValue, args) { super(); this.token = stringToken; this.rawValue = rawValue; this.args = args.length > 0 ? { parts: args } : null; } static parse(parser) { var stringToken = parser.matchTokenType("STRING"); if (!stringToken) return; var rawValue = ( /** @type {string} */ stringToken.value ); var args; if (stringToken.template) { var innerTokens = Tokenizer.tokenize(rawValue, true); var innerParser = parser.createChildParser(innerTokens); args = innerParser.parseStringTemplate(); } else { args = []; } return new _StringLiteral(stringToken, rawValue, args); } evalStatically() { if (this.args === null) return this.rawValue; return super.evalStatically(); } resolve(context, { parts } = {}) { if (!parts || parts.length === 0) { return this.rawValue; } var returnStr = ""; for (var i = 0; i < parts.length; i++) { var val = parts[i]; if (val !== void 0) { returnStr += val; } } return returnStr; } }; var ArrayLiteral = class _ArrayLiteral extends Expression { static grammarName = "arrayLiteral"; static expressionType = "leaf"; constructor(values) { super(); this.values = values; this.args = { values }; } static parse(parser) { if (!parser.matchOpToken("[")) return; var values = []; if (!parser.matchOpToken("]")) { do { var expr = parser.requireElement("expression"); values.push(expr); } while (parser.matchOpToken(",")); parser.requireOpToken("]"); } return new _ArrayLiteral(values); } resolve(context, { values }) { return values; } }; var ObjectKey = class _ObjectKey extends Expression { static grammarName = "objectKey"; constructor(key, expr, args) { super(); this.key = key; this.expr = expr; this.args = args; } static parse(parser) { var token; if (token = parser.matchTokenType("STRING")) { return new _ObjectKey(token.value, null, null); } else if (parser.matchOpToken("[")) { var expr = parser.parseElement("expression"); parser.requireOpToken("]"); return new _ObjectKey(null, expr, { value: expr }); } else { var key = ""; do { token = parser.matchTokenType("IDENTIFIER") || parser.matchOpToken("-"); if (token) key += token.value; } while (token); return new _ObjectKey(key, null, null); } } evalStatically() { if (!this.expr) return this.key; return super.evalStatically(); } resolve(ctx, { value } = {}) { if (this.expr) { return value; } return this.key; } }; var ObjectLiteral = class _ObjectLiteral extends Expression { static grammarName = "objectLiteral"; static expressionType = "leaf"; constructor(keyExpressions, valueExpressions) { super(); this.keyExpressions = keyExpressions; this.valueExpressions = valueExpressions; this.args = { keys: keyExpressions, values: valueExpressions }; } static parse(parser) { if (!parser.matchOpToken("{")) return; var keyExpressions = []; var valueExpressions = []; if (!parser.matchOpToken("}")) { do { var name = parser.requireElement("objectKey"); parser.requireOpToken(":"); var value = parser.requireElement("expression"); valueExpressions.push(value); keyExpressions.push(name); } while (parser.matchOpToken(",") && !parser.peekToken("}", 0, "R_BRACE")); parser.requireOpToken("}"); } return new _ObjectLiteral(keyExpressions, valueExpressions); } resolve(context, { keys, values }) { var returnVal = {}; for (var i = 0; i < keys.length; i++) { returnVal[keys[i]] = values[i]; } return returnVal; } }; var NamedArgumentList = class _NamedArgumentList extends Expression { static grammarName = "namedArgumentList"; constructor(fields, valueExpressions) { super(); this.fields = fields; this.args = { values: valueExpressions }; } static parseNaked(parser) { var fields = []; var valueExpressions = []; if (parser.currentToken().type === "IDENTIFIER") { do { var name = parser.requireTokenType("IDENTIFIER"); parser.requireOpToken(":"); var value = parser.requireElement("expression"); valueExpressions.push(value); fields.push({ name, value }); } while (parser.matchOpToken(",")); } return new _NamedArgumentList(fields, valueExpressions); } static parse(parser) { if (!parser.matchOpToken("(")) return; var elt = _NamedArgumentList.parseNaked(parser); parser.requireOpToken(")"); return elt; } resolve(context, { values }) { var returnVal = { _namedArgList_: true }; for (var i = 0; i < values.length; i++) { var field = this.fields[i]; returnVal[field.name.value] = values[i]; } return returnVal; } }; var NakedNamedArgumentList = class extends Expression { static grammarName = "nakedNamedArgumentList"; static parse = NamedArgumentList.parseNaked; }; var StringLike = class extends Expression { static grammarName = "stringLike"; static parse(parser) { return parser.parseAnyOf(["string", "nakedString"]); } }; // src/core/parser.js var ParseError = class { constructor(message, token, source, expected) { this.message = message; this.token = token; this.source = source; this.expected = expected || null; this.line = token?.line ?? null; this.column = token?.column ?? null; } }; var ParseRecoverySentinel = class extends Error { constructor(parseError) { super(parseError.message); this.parseError = parseError; } }; var Parser = class _Parser { #kernel; constructor(kernel2, tokens) { this.#kernel = kernel2; this.tokens = tokens; } toString() { this.tokens.matched; } static formatErrors(errors) { if (!errors.length) return ""; var source = errors[0].source; var lines = source.split("\n"); var byLine = /* @__PURE__ */ new Map(); for (var e of errors) { var lineIdx = e.token?.line ? e.token.line - 1 : lines.length - 1; if (!byLine.has(lineIdx)) byLine.set(lineIdx, []); byLine.get(lineIdx).push(e); } var maxLine = Math.max(...byLine.keys()) + 1; var gutter = String(maxLine).length; var pad = " ".repeat(gutter + 5); var sortedLines = [...byLine.entries()].sort((a, b) => a[0] - b[0]); var prevLineIdx = -1; var out = ""; for (var [lineIdx, lineErrors] of sortedLines) { if (prevLineIdx !== -1 && lineIdx > prevLineIdx + 1) { out += " ".repeat(gutter + 1) + "...\n"; } else if (prevLineIdx === -1 && lineIdx > 0) { out += " ".repeat(gutter + 1) + "...\n"; } prevLineIdx = lineIdx; var lineNum = String(lineIdx + 1).padStart(gutter); var contextLine = lines[lineIdx] || ""; out += " " + lineNum + " | " + contextLine + "\n"; lineErrors.sort((a, b) => (a.column || 0) - (b.column || 0)); var underlineChars = Array(contextLine.length + 10).fill(" "); for (var e of lineErrors) { var col = e.token?.line ? e.token.column : Math.max(0, contextLine.length - 1); var len = Math.max(1, e.token?.value?.length || 1); for (var i = 0; i < len; i++) underlineChars[col + i] = "^"; } out += pad + underlineChars.join("").trimEnd() + "\n"; for (var e of lineErrors) { var col = e.token?.line ? e.token.column : 0; out += pad + " ".repeat(col) + e.message + "\n"; } } return out; } // =========================== // Token delegation methods // =========================== consumeWhitespace() { return this.tokens.consumeWhitespace(); } requireOpToken(value) { var token = this.matchOpToken(value); if (token) return token; this.raiseExpected(value); } matchAnyOpToken(...ops) { return this.tokens.matchAnyOpToken(...ops); } matchAnyToken(...tokens) { return this.tokens.matchAnyToken(...tokens); } matchOpToken(value) { return this.tokens.matchOpToken(value); } requireTokenType(...types) { var token = this.matchTokenType(...types); if (token) return token; this.raiseExpected(...types); } matchTokenType(...types) { return this.tokens.matchTokenType(...types); } requireToken(value, type) { var token = this.matchToken(value, type); if (token) return token; this.raiseExpected(value); } peekToken(value, peek, type) { return this.tokens.peekToken(value, peek, type); } matchToken(value, type) { return this.tokens.matchToken(value, type); } consumeToken() { return this.tokens.consumeToken(); } consumeUntil(value, type) { return this.tokens.consumeUntil(value, type); } lastWhitespace() { return this.tokens.lastWhitespace(); } consumeUntilWhitespace() { return this.tokens.consumeUntilWhitespace(); } hasMore() { return this.tokens.hasMore(); } token(n, includeWhitespace) { return this.tokens.token(n, includeWhitespace); } currentToken() { return this.tokens.currentToken(); } lastMatch() { return this.tokens.lastMatch(); } pushFollow(str) { return this.tokens.pushFollow(str); } popFollow() { return this.tokens.popFollow(); } pushFollows(...strs) { return this.tokens.pushFollows(...strs); } popFollows(count) { return this.tokens.popFollows(count); } clearFollows() { return this.tokens.clearFollows(); } restoreFollows(f) { return this.tokens.restoreFollows(f); } get source() { return this.tokens.source; } get consumed() { return this.tokens.consumed; } get list() { return this.tokens.list; } createChildParser(tokens) { return new _Parser(this.#kernel, tokens); } // =========================== // Kernel delegation methods // =========================== parseElement(type, root = null) { return this.#kernel.parseElement(type, this, root); } requireElement(type, message, root) { return this.#kernel.requireElement(type, this, message, root); } parseAnyOf(types) { return this.#kernel.parseAnyOf(types, this); } raiseError(message, expected) { message = message || "Unexpected Token : " + this.currentToken().value; var parseError = new ParseError(message, this.currentToken(), this.source, expected); throw new ParseRecoverySentinel(parseError); } raiseExpected(...expected) { var msg = expected.length === 1 ? "Expected '" + expected[0] + "' but found '" + this.currentToken().value + "'" : "Expected one of: " + expected.map((e) => "'" + e + "'").join(", "); this.raiseError(msg, expected); } // =========================== // Parser-owned methods // =========================== parseStringTemplate() { var returnArr = [""]; do { returnArr.push(this.lastWhitespace()); if (this.currentToken().value === "$") { this.consumeToken(); var startingBrace = this.matchOpToken("{"); returnArr.push(this.requireElement("expression")); if (startingBrace) { this.requireOpToken("}"); } returnArr.push(""); } else if (this.currentToken().value === "\\") { this.consumeToken(); this.consumeToken(); } else { var token = this.consumeToken(); returnArr[returnArr.length - 1] += token ? token.value : ""; } } while (this.hasMore()); returnArr.push(this.lastWhitespace()); return returnArr; } commandBoundary(token) { if (token.value == "end" || token.value == "then" || token.value == "else" || token.value == "otherwise" || token.value == ")" || this.commandStart(token) || this.featureStart(token) || token.type == "EOF") { return true; } return false; } commandStart(token) { return this.#kernel.commandStart(token); } featureStart(token) { return this.#kernel.featureStart(token); } setParent(elt, parent) { if (typeof elt === "object") { elt.parent = parent; if (typeof parent === "object") { parent.children = parent.children || /* @__PURE__ */ new Set(); parent.children.add(elt); } this.setParent(elt.next, parent); } } parseURLOrExpression() { var cur = this.currentToken(); if (cur.value === "/" && cur.type === "DIVIDE") { var tokens = this.consumeUntilWhitespace(); this.matchTokenType("WHITESPACE"); return new NakedString(tokens); } if (cur.type === "IDENTIFIER" && (cur.value === "http" || cur.value === "https" || cur.value === "ws" || cur.value === "wss")) { var tokens = this.consumeUntilWhitespace(); this.matchTokenType("WHITESPACE"); return new NakedString(tokens); } return this.requireElement("expression"); } ensureTerminated(commandList) { var implicitReturn = new ImplicitReturn(); var end = commandList; while (end.next) { end = end.next; } end.next = implicitReturn; } }; // src/core/kernel.js var LanguageKernel = class { #grammar = {}; #commands = {}; #features = {}; #leafExpressions = []; #indirectExpressions = []; #postfixExpressions = []; #unaryExpressions = []; #topExpressions = []; #assignableExpressions = []; constructor() { this.addGrammarElement("hyperscript", this.parseHyperscriptProgram.bind(this)); this.addGrammarElement("feature", this.parseFeature.bind(this)); this.addGrammarElement("commandList", this.parseCommandList.bind(this)); this.addGrammarElement("command", this.parseCommand.bind(this)); this.addGrammarElement("indirectStatement", this.parseIndirectStatement.bind(this)); this.addGrammarElement("expression", this.parseExpression.bind(this)); this.addGrammarElement("assignableExpression", this.parseAssignableExpression.bind(this)); this.addGrammarElement("unaryExpression", this.parseUnaryExpression.bind(this)); this.addGrammarElement("postfixExpression", this.parsePostfixExpression.bind(this)); this.addGrammarElement("primaryExpression", this.parsePrimaryExpression.bind(this)); this.addGrammarElement("indirectExpression", this.parseIndirectExpression.bind(this)); this.addGrammarElement("leaf", this.parseLeaf.bind(this)); } parseFeature(parser) { if (parser.matchOpToken("(")) { var featureElement = parser.requireElement("feature"); parser.requireOpToken(")"); return featureElement; } var featureDefinition = this.#features[parser.currentToken().value || ""]; if (featureDefinition) { return featureDefinition(parser); } } parseCommand(parser) { if (parser.matchOpToken("(")) { const commandElement2 = parser.requireElement("command"); parser.requireOpToken(")"); return commandElement2; } var commandDefinition = this.#commands[parser.currentToken().value || ""]; let commandElement; if (commandDefinition) { commandElement = commandDefinition(parser); } else if (parser.currentToken().type === "IDENTIFIER") { commandElement = parser.parseElement("pseudoCommand"); } if (commandElement) { return this.parseElement("indirectStatement", parser, commandElement); } return commandElement; } parseCommandList(parser) { if (parser.hasMore()) { var keyword = parser.currentToken().value; var cmd; try { cmd = parser.parseElement("command"); } catch (e) { if (e instanceof ParseRecoverySentinel) { cmd = new FailedCommand(e.parseError, keyword); this.#syncToCommand(parser); } else { throw e; } } if (cmd) { parser.matchToken("then"); const next = parser.parseElement("commandList"); if (next) cmd.next = next; return cmd; } } return new EmptyCommandListCommand(); } parseLeaf(parser) { var result = parser.parseAnyOf(this.#leafExpressions); if (result == null) { return parser.parseElement("symbol"); } return result; } parseIndirectExpression(parser, root) { for (var i = 0; i < this.#indirectExpressions.length; i++) { var indirect = this.#indirectExpressions[i]; root.endToken = parser.lastMatch(); var result = this.parseElement(indirect, parser, root); if (result) { return result; } } return root; } parsePostfixExpression(parser) { var root = parser.parseElement("negativeNumber"); for (var i = 0; i < this.#postfixExpressions.length; i++) { var postfixType = this.#postfixExpressions[i]; var result = this.parseElement(postfixType, parser, root); if (result) { return result; } } return root; } parseUnaryExpression(parser) { parser.matchToken("the"); var result = parser.parseAnyOf(this.#unaryExpressions); if (result) return this.parseElement("indirectExpression", parser, result); return parser.parseElement("postfixExpression"); } parseExpression(parser) { parser.matchToken("the"); return parser.parseAnyOf(this.#topExpressions); } parseAssignableExpression(parser) { parser.matchToken("the"); var expr = parser.parseElement("primaryExpression"); var checkExpr = expr; while (checkExpr && checkExpr.type === "parenthesized") { checkExpr = checkExpr.expr; } if (checkExpr && this.#assignableExpressions.includes(checkExpr.type)) { return expr; } else { parser.raiseError( "A target expression must be writable. The expression type '" + (checkExpr && checkExpr.type) + "' is not." ); } } parseIndirectStatement(parser, root) { if (parser.matchToken("unless")) { root.endToken = parser.lastMatch(); var conditional = parser.requireElement("expression"); var unless = new UnlessStatementModifier(root, conditional); root.parent = unless; return unless; } return root; } parsePrimaryExpression(parser) { var leaf = parser.parseElement("leaf"); if (leaf) { return this.parseElement("indirectExpression", parser, leaf); } parser.raiseError("Unexpected value: " + parser.currentToken().value); } parseHyperscriptProgram(parser) { var features = []; if (parser.hasMore()) { while (parser.currentToken().type !== "EOF") { var keyword = parser.currentToken().value; if (parser.featureStart(parser.currentToken()) || parser.currentToken().value === "(") { try { var feature = parser.requireElement("feature"); features.push(feature); parser.matchToken("end"); } catch (e) { if (e instanceof ParseRecoverySentinel) { features.push(new FailedFeature(e.parseError, keyword)); this.#syncToFeature(parser); } else { throw e; } } } else if (parser.currentToken().value === "end") { break; } else { try { parser.raiseError(); } catch (e) { if (e instanceof ParseRecoverySentinel) { features.push(new FailedFeature(e.parseError, keyword)); this.#syncToFeature(parser); } else { throw e; } } } } } return new HyperscriptProgram(features); } use(plugin) { plugin(this); return this; } initElt(parseElement, start, tokens) { parseElement.startToken = start; parseElement.programSource = tokens.source; } parseElement(type, parser, root = void 0) { var elementDefinition = this.#grammar[type]; if (elementDefinition) {