hyperscript.org
Version:
a small scripting language for the web
1,524 lines (1,518 loc) • 364 kB
JavaScript
"use strict";
(() => {
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 (elementDefin