@lokascript/compilation-service 3.2.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +203 -1
- package/dist/{chunk-XOAPC6M5.js → chunk-GWYYJAZY.js} +76 -293
- package/dist/chunk-GWYYJAZY.js.map +1 -0
- package/dist/http.cjs +147 -33184
- package/dist/http.cjs.map +1 -1
- package/dist/http.d.cts +1 -1
- package/dist/http.d.ts +1 -1
- package/dist/http.js +2 -22
- package/dist/http.js.map +1 -1
- package/dist/index.cjs +141 -33163
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -45
- package/dist/index.d.ts +3 -45
- package/dist/index.js +1 -6
- package/dist/index.js.map +1 -1
- package/dist/{service-Da54XrEI.d.cts → service-2lEnO5hA.d.cts} +35 -73
- package/dist/{service-Da54XrEI.d.ts → service-2lEnO5hA.d.ts} +35 -73
- package/package.json +11 -7
- package/dist/chunk-GBPMR66W.js +0 -23
- package/dist/chunk-GBPMR66W.js.map +0 -1
- package/dist/chunk-IFSCZ7JT.js +0 -85
- package/dist/chunk-IFSCZ7JT.js.map +0 -1
- package/dist/chunk-QQLDIJOC.js +0 -58
- package/dist/chunk-QQLDIJOC.js.map +0 -1
- package/dist/chunk-XOAPC6M5.js.map +0 -1
- package/dist/core-parser-adapter-W5UPZR4X-7CPPQMOH.js +0 -12
- package/dist/core-parser-adapter-W5UPZR4X-7CPPQMOH.js.map +0 -1
- package/dist/dist-BDTQTYGC.js +0 -4463
- package/dist/dist-BDTQTYGC.js.map +0 -1
- package/dist/dist-SO754VTX.js +0 -27677
- package/dist/dist-SO754VTX.js.map +0 -1
- package/dist/multilingual-TCFUREGW.js +0 -485
- package/dist/multilingual-TCFUREGW.js.map +0 -1
- package/dist/semantic-adapter-JAAALHRO-WXAFQY4T.js +0 -12
- package/dist/semantic-adapter-JAAALHRO-WXAFQY4T.js.map +0 -1
package/dist/dist-BDTQTYGC.js
DELETED
|
@@ -1,4463 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CoreParserAdapter,
|
|
3
|
-
createCoreParserAdapter
|
|
4
|
-
} from "./chunk-QQLDIJOC.js";
|
|
5
|
-
import {
|
|
6
|
-
SemanticParserAdapter,
|
|
7
|
-
createSemanticAdapter
|
|
8
|
-
} from "./chunk-IFSCZ7JT.js";
|
|
9
|
-
import "./chunk-GBPMR66W.js";
|
|
10
|
-
|
|
11
|
-
// ../aot-compiler/dist/index.js
|
|
12
|
-
import {
|
|
13
|
-
isExplicitSyntax,
|
|
14
|
-
parseExplicit,
|
|
15
|
-
fromProtocolJSON,
|
|
16
|
-
semanticNodeToRuntimeAST
|
|
17
|
-
} from "@lokascript/framework";
|
|
18
|
-
var DEFAULT_OPTIONS = {
|
|
19
|
-
attributeNames: ["_", "data-hs", "data-hyperscript"],
|
|
20
|
-
includeScriptTags: true,
|
|
21
|
-
defaultLanguage: "en"
|
|
22
|
-
};
|
|
23
|
-
function createAttributeRegex(attrName) {
|
|
24
|
-
const escaped = attrName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
25
|
-
return new RegExp(`${escaped}\\s*=\\s*(?:"([^"]*?)"|'([^']*?)'|([^\\s>]+))`, "gi");
|
|
26
|
-
}
|
|
27
|
-
var ELEMENT_REGEX = /<([a-z][a-z0-9-]*)\s+([^>]*?)>/gi;
|
|
28
|
-
var SCRIPT_TAG_REGEX = /<script\s+[^>]*type\s*=\s*["']text\/hyperscript["'][^>]*>([\s\S]*?)<\/script>/gi;
|
|
29
|
-
var ID_REGEX = /\bid\s*=\s*["']([^"']+)["']/i;
|
|
30
|
-
var CLASS_REGEX = /\bclass\s*=\s*["']([^"']+)["']/i;
|
|
31
|
-
var LANG_REGEX = /\blang\s*=\s*["']([^"']+)["']/i;
|
|
32
|
-
var HTMLScanner = class {
|
|
33
|
-
constructor(options = {}) {
|
|
34
|
-
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
35
|
-
this.attributeRegexes = /* @__PURE__ */ new Map();
|
|
36
|
-
for (const attr of this.options.attributeNames) {
|
|
37
|
-
this.attributeRegexes.set(attr, createAttributeRegex(attr));
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Extract hyperscript from HTML source code.
|
|
42
|
-
*/
|
|
43
|
-
extract(source, filename) {
|
|
44
|
-
const scripts = [];
|
|
45
|
-
scripts.push(...this.extractFromAttributes(source, filename));
|
|
46
|
-
if (this.options.includeScriptTags) {
|
|
47
|
-
scripts.push(...this.extractFromScriptTags(source, filename));
|
|
48
|
-
}
|
|
49
|
-
return scripts;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Extract hyperscript from element attributes.
|
|
53
|
-
*/
|
|
54
|
-
extractFromAttributes(source, filename) {
|
|
55
|
-
const scripts = [];
|
|
56
|
-
let elementMatch;
|
|
57
|
-
ELEMENT_REGEX.lastIndex = 0;
|
|
58
|
-
while ((elementMatch = ELEMENT_REGEX.exec(source)) !== null) {
|
|
59
|
-
const tagName = elementMatch[1];
|
|
60
|
-
const attributes = elementMatch[2];
|
|
61
|
-
const elementStart = elementMatch.index;
|
|
62
|
-
const location = this.calculateLocation(source, elementStart, filename);
|
|
63
|
-
for (const [attrName, regex] of this.attributeRegexes) {
|
|
64
|
-
regex.lastIndex = 0;
|
|
65
|
-
const attrMatch = regex.exec(attributes);
|
|
66
|
-
if (attrMatch) {
|
|
67
|
-
const code = attrMatch[1] ?? attrMatch[2] ?? attrMatch[3];
|
|
68
|
-
if (code && code.trim()) {
|
|
69
|
-
const elementId = this.extractId(attributes);
|
|
70
|
-
const elementSelector = this.buildSelector(tagName, attributes, elementId);
|
|
71
|
-
const language = this.extractLanguage(attributes) ?? this.options.defaultLanguage;
|
|
72
|
-
const decodedCode = this.decodeHTMLEntities(code);
|
|
73
|
-
scripts.push({
|
|
74
|
-
code: decodedCode,
|
|
75
|
-
location: {
|
|
76
|
-
...location,
|
|
77
|
-
column: location.column + attrMatch.index
|
|
78
|
-
},
|
|
79
|
-
elementId,
|
|
80
|
-
elementSelector,
|
|
81
|
-
language,
|
|
82
|
-
attributeName: attrName
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return scripts;
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Extract hyperscript from script tags.
|
|
92
|
-
*/
|
|
93
|
-
extractFromScriptTags(source, filename) {
|
|
94
|
-
const scripts = [];
|
|
95
|
-
let match;
|
|
96
|
-
SCRIPT_TAG_REGEX.lastIndex = 0;
|
|
97
|
-
while ((match = SCRIPT_TAG_REGEX.exec(source)) !== null) {
|
|
98
|
-
const code = match[1].trim();
|
|
99
|
-
if (code) {
|
|
100
|
-
const location = this.calculateLocation(source, match.index, filename);
|
|
101
|
-
scripts.push({
|
|
102
|
-
code,
|
|
103
|
-
location,
|
|
104
|
-
language: this.options.defaultLanguage,
|
|
105
|
-
attributeName: "script"
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return scripts;
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Calculate line and column from character offset.
|
|
113
|
-
*/
|
|
114
|
-
calculateLocation(source, offset, filename) {
|
|
115
|
-
const before = source.slice(0, offset);
|
|
116
|
-
const lines = before.split("\n");
|
|
117
|
-
const line = lines.length;
|
|
118
|
-
const column = (lines[lines.length - 1]?.length ?? 0) + 1;
|
|
119
|
-
return { file: filename, line, column };
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Extract ID from attributes string.
|
|
123
|
-
*/
|
|
124
|
-
extractId(attributes) {
|
|
125
|
-
const match = ID_REGEX.exec(attributes);
|
|
126
|
-
return match?.[1];
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Extract language from attributes string.
|
|
130
|
-
*/
|
|
131
|
-
extractLanguage(attributes) {
|
|
132
|
-
const match = LANG_REGEX.exec(attributes);
|
|
133
|
-
return match?.[1];
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Build a CSS selector for an element.
|
|
137
|
-
*/
|
|
138
|
-
buildSelector(tagName, attributes, elementId) {
|
|
139
|
-
if (elementId) {
|
|
140
|
-
return `#${elementId}`;
|
|
141
|
-
}
|
|
142
|
-
let selector = tagName;
|
|
143
|
-
const classMatch = CLASS_REGEX.exec(attributes);
|
|
144
|
-
if (classMatch) {
|
|
145
|
-
const classes = classMatch[1].split(/\s+/).filter(Boolean);
|
|
146
|
-
if (classes.length > 0) {
|
|
147
|
-
selector += "." + classes.join(".");
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
for (const attrName of this.options.attributeNames) {
|
|
151
|
-
if (attributes.includes(attrName + "=")) {
|
|
152
|
-
selector += `[${attrName}]`;
|
|
153
|
-
break;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return selector;
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Decode HTML entities in attribute values.
|
|
160
|
-
*/
|
|
161
|
-
decodeHTMLEntities(text) {
|
|
162
|
-
return text.replace(/"/g, '"').replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&").replace(/&#(\d+);/g, (_, code) => String.fromCharCode(parseInt(code, 10))).replace(/&#x([0-9a-f]+);/gi, (_, code) => String.fromCharCode(parseInt(code, 16)));
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
var VueScanner = class extends HTMLScanner {
|
|
166
|
-
/**
|
|
167
|
-
* Extract hyperscript from Vue SFC.
|
|
168
|
-
*/
|
|
169
|
-
extract(source, filename) {
|
|
170
|
-
const templateMatch = /<template[^>]*>([\s\S]*?)<\/template>/i.exec(source);
|
|
171
|
-
if (!templateMatch) {
|
|
172
|
-
return [];
|
|
173
|
-
}
|
|
174
|
-
const template = templateMatch[1];
|
|
175
|
-
const templateOffset = templateMatch.index + templateMatch[0].indexOf(">") + 1;
|
|
176
|
-
const scripts = super.extract(template, filename);
|
|
177
|
-
const linesBefore = source.slice(0, templateOffset).split("\n").length - 1;
|
|
178
|
-
for (const script of scripts) {
|
|
179
|
-
script.location.line += linesBefore;
|
|
180
|
-
}
|
|
181
|
-
return scripts;
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
var SvelteScanner = class extends HTMLScanner {
|
|
185
|
-
/**
|
|
186
|
-
* Extract hyperscript from Svelte component.
|
|
187
|
-
* Svelte components are HTML-like, so we can use the base scanner
|
|
188
|
-
* but need to exclude script and style blocks.
|
|
189
|
-
*/
|
|
190
|
-
extract(source, filename) {
|
|
191
|
-
let cleanedSource = source.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, (match) => " ".repeat(match.length)).replace(/<style[^>]*>[\s\S]*?<\/style>/gi, (match) => " ".repeat(match.length));
|
|
192
|
-
return super.extract(cleanedSource, filename);
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
var JSXScanner = class {
|
|
196
|
-
constructor(options = {}) {
|
|
197
|
-
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Extract hyperscript from JSX source.
|
|
201
|
-
* Handles both regular attributes and spread attributes.
|
|
202
|
-
*/
|
|
203
|
-
extract(source, filename) {
|
|
204
|
-
const scripts = [];
|
|
205
|
-
for (const attrName of this.options.attributeNames) {
|
|
206
|
-
const escaped = attrName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
207
|
-
const stringRegex = new RegExp(`${escaped}\\s*=\\s*["']([^"']+)["']`, "g");
|
|
208
|
-
let match;
|
|
209
|
-
while ((match = stringRegex.exec(source)) !== null) {
|
|
210
|
-
const code = match[1];
|
|
211
|
-
if (code && code.trim()) {
|
|
212
|
-
scripts.push({
|
|
213
|
-
code,
|
|
214
|
-
location: this.calculateLocation(source, match.index, filename),
|
|
215
|
-
language: this.options.defaultLanguage,
|
|
216
|
-
attributeName: attrName
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
const templateRegex = new RegExp(`${escaped}\\s*=\\s*\\{\\s*\`([^\`]+)\`\\s*\\}`, "g");
|
|
221
|
-
while ((match = templateRegex.exec(source)) !== null) {
|
|
222
|
-
const code = match[1];
|
|
223
|
-
if (code && code.trim()) {
|
|
224
|
-
scripts.push({
|
|
225
|
-
code,
|
|
226
|
-
location: this.calculateLocation(source, match.index, filename),
|
|
227
|
-
language: this.options.defaultLanguage,
|
|
228
|
-
attributeName: attrName
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
const exprRegex = new RegExp(`${escaped}\\s*=\\s*\\{\\s*["']([^"']+)["']\\s*\\}`, "g");
|
|
233
|
-
while ((match = exprRegex.exec(source)) !== null) {
|
|
234
|
-
const code = match[1];
|
|
235
|
-
if (code && code.trim()) {
|
|
236
|
-
scripts.push({
|
|
237
|
-
code,
|
|
238
|
-
location: this.calculateLocation(source, match.index, filename),
|
|
239
|
-
language: this.options.defaultLanguage,
|
|
240
|
-
attributeName: attrName
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
return scripts;
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* Calculate line and column from character offset.
|
|
249
|
-
*/
|
|
250
|
-
calculateLocation(source, offset, filename) {
|
|
251
|
-
const before = source.slice(0, offset);
|
|
252
|
-
const lines = before.split("\n");
|
|
253
|
-
const line = lines.length;
|
|
254
|
-
const column = (lines[lines.length - 1]?.length ?? 0) + 1;
|
|
255
|
-
return { file: filename, line, column };
|
|
256
|
-
}
|
|
257
|
-
};
|
|
258
|
-
async function scanFiles(files, readFile, options = {}) {
|
|
259
|
-
const result = {
|
|
260
|
-
scripts: [],
|
|
261
|
-
files: [],
|
|
262
|
-
errors: []
|
|
263
|
-
};
|
|
264
|
-
const htmlScanner = new HTMLScanner(options);
|
|
265
|
-
const vueScanner = new VueScanner(options);
|
|
266
|
-
const svelteScanner = new SvelteScanner(options);
|
|
267
|
-
const jsxScanner = new JSXScanner(options);
|
|
268
|
-
for (const file of files) {
|
|
269
|
-
try {
|
|
270
|
-
const content = await readFile(file);
|
|
271
|
-
let scripts = [];
|
|
272
|
-
if (file.endsWith(".vue")) {
|
|
273
|
-
scripts = vueScanner.extract(content, file);
|
|
274
|
-
} else if (file.endsWith(".svelte")) {
|
|
275
|
-
scripts = svelteScanner.extract(content, file);
|
|
276
|
-
} else if (file.match(/\.(jsx|tsx)$/)) {
|
|
277
|
-
scripts = jsxScanner.extract(content, file);
|
|
278
|
-
} else if (file.match(/\.(html|htm)$/)) {
|
|
279
|
-
scripts = htmlScanner.extract(content, file);
|
|
280
|
-
}
|
|
281
|
-
result.scripts.push(...scripts);
|
|
282
|
-
result.files.push(file);
|
|
283
|
-
} catch (error) {
|
|
284
|
-
result.errors.push({
|
|
285
|
-
file,
|
|
286
|
-
error: error instanceof Error ? error.message : String(error)
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
return result;
|
|
291
|
-
}
|
|
292
|
-
function createScanner(filename, options) {
|
|
293
|
-
if (filename.endsWith(".vue")) {
|
|
294
|
-
return new VueScanner(options);
|
|
295
|
-
}
|
|
296
|
-
if (filename.endsWith(".svelte")) {
|
|
297
|
-
return new SvelteScanner(options);
|
|
298
|
-
}
|
|
299
|
-
if (filename.match(/\.(jsx|tsx)$/)) {
|
|
300
|
-
return new JSXScanner(options);
|
|
301
|
-
}
|
|
302
|
-
return new HTMLScanner(options);
|
|
303
|
-
}
|
|
304
|
-
var Analyzer = class {
|
|
305
|
-
/**
|
|
306
|
-
* Analyze an AST and return analysis results.
|
|
307
|
-
*/
|
|
308
|
-
analyze(ast) {
|
|
309
|
-
const visitor = new AnalysisVisitor();
|
|
310
|
-
visitor.visit(ast);
|
|
311
|
-
return visitor.getResult();
|
|
312
|
-
}
|
|
313
|
-
};
|
|
314
|
-
var AnalysisVisitor = class {
|
|
315
|
-
constructor() {
|
|
316
|
-
this.commandsUsed = /* @__PURE__ */ new Set();
|
|
317
|
-
this.localVars = /* @__PURE__ */ new Map();
|
|
318
|
-
this.globalVars = /* @__PURE__ */ new Map();
|
|
319
|
-
this.contextVars = /* @__PURE__ */ new Set();
|
|
320
|
-
this.pureExpressions = [];
|
|
321
|
-
this.dynamicExpressions = [];
|
|
322
|
-
this.selectors = [];
|
|
323
|
-
this.selectorMap = /* @__PURE__ */ new Map();
|
|
324
|
-
this.domQueries = [];
|
|
325
|
-
this.eventTypes = [];
|
|
326
|
-
this.behaviors = [];
|
|
327
|
-
this.runtimeHelpers = /* @__PURE__ */ new Set();
|
|
328
|
-
this.warnings = [];
|
|
329
|
-
this.hasAsync = false;
|
|
330
|
-
this.hasLoops = false;
|
|
331
|
-
this.hasConditionals = false;
|
|
332
|
-
this.canThrow = false;
|
|
333
|
-
this.currentNestingDepth = 0;
|
|
334
|
-
this.maxNestingDepth = 0;
|
|
335
|
-
}
|
|
336
|
-
/**
|
|
337
|
-
* Visit an AST node and its children.
|
|
338
|
-
*/
|
|
339
|
-
visit(node) {
|
|
340
|
-
if (!node) return;
|
|
341
|
-
switch (node.type) {
|
|
342
|
-
case "event":
|
|
343
|
-
this.visitEvent(node);
|
|
344
|
-
break;
|
|
345
|
-
case "command":
|
|
346
|
-
this.visitCommand(node);
|
|
347
|
-
break;
|
|
348
|
-
case "if":
|
|
349
|
-
this.visitIf(node);
|
|
350
|
-
break;
|
|
351
|
-
case "repeat":
|
|
352
|
-
this.visitRepeat(node);
|
|
353
|
-
break;
|
|
354
|
-
case "foreach":
|
|
355
|
-
this.visitForEach(node);
|
|
356
|
-
break;
|
|
357
|
-
case "while":
|
|
358
|
-
this.visitWhile(node);
|
|
359
|
-
break;
|
|
360
|
-
case "variable":
|
|
361
|
-
this.visitVariable(node, "read");
|
|
362
|
-
break;
|
|
363
|
-
case "selector":
|
|
364
|
-
this.visitSelector(node);
|
|
365
|
-
break;
|
|
366
|
-
case "binary":
|
|
367
|
-
this.visitBinary(node);
|
|
368
|
-
break;
|
|
369
|
-
case "call":
|
|
370
|
-
this.visitCall(node);
|
|
371
|
-
break;
|
|
372
|
-
case "identifier":
|
|
373
|
-
this.visitIdentifier(node);
|
|
374
|
-
break;
|
|
375
|
-
default:
|
|
376
|
-
this.visitChildren(node);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
/**
|
|
380
|
-
* Get the analysis result.
|
|
381
|
-
*/
|
|
382
|
-
getResult() {
|
|
383
|
-
return {
|
|
384
|
-
commandsUsed: this.commandsUsed,
|
|
385
|
-
variables: {
|
|
386
|
-
locals: this.localVars,
|
|
387
|
-
globals: this.globalVars,
|
|
388
|
-
contextVars: this.contextVars
|
|
389
|
-
},
|
|
390
|
-
expressions: {
|
|
391
|
-
pure: this.pureExpressions,
|
|
392
|
-
dynamic: this.dynamicExpressions,
|
|
393
|
-
selectors: this.selectors
|
|
394
|
-
},
|
|
395
|
-
controlFlow: {
|
|
396
|
-
hasAsync: this.hasAsync,
|
|
397
|
-
hasLoops: this.hasLoops,
|
|
398
|
-
hasConditionals: this.hasConditionals,
|
|
399
|
-
canThrow: this.canThrow,
|
|
400
|
-
maxNestingDepth: this.maxNestingDepth
|
|
401
|
-
},
|
|
402
|
-
dependencies: {
|
|
403
|
-
domQueries: [...new Set(this.domQueries)],
|
|
404
|
-
eventTypes: [...new Set(this.eventTypes)],
|
|
405
|
-
behaviors: [...new Set(this.behaviors)],
|
|
406
|
-
runtimeHelpers: Array.from(this.runtimeHelpers)
|
|
407
|
-
},
|
|
408
|
-
warnings: this.warnings
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
// ===========================================================================
|
|
412
|
-
// VISITOR METHODS
|
|
413
|
-
// ===========================================================================
|
|
414
|
-
visitEvent(node) {
|
|
415
|
-
this.eventTypes.push(node.event);
|
|
416
|
-
if (node.modifiers?.debounce) {
|
|
417
|
-
this.runtimeHelpers.add("debounce");
|
|
418
|
-
}
|
|
419
|
-
if (node.modifiers?.throttle) {
|
|
420
|
-
this.runtimeHelpers.add("throttle");
|
|
421
|
-
}
|
|
422
|
-
if (node.body) {
|
|
423
|
-
for (const child of node.body) {
|
|
424
|
-
this.visit(child);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
visitCommand(node) {
|
|
429
|
-
this.commandsUsed.add(node.name);
|
|
430
|
-
if (["set", "increment", "decrement", "default"].includes(node.name)) {
|
|
431
|
-
const target = node.roles?.destination ?? node.roles?.patient ?? node.args?.[0];
|
|
432
|
-
const name = target?.type === "identifier" ? target.value : void 0;
|
|
433
|
-
if (name && /^[A-Za-z_]\w*$/.test(name)) {
|
|
434
|
-
this.registerVariable(name, "local", "write");
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
switch (node.name) {
|
|
438
|
-
case "fetch":
|
|
439
|
-
this.hasAsync = true;
|
|
440
|
-
this.runtimeHelpers.add("fetchJSON");
|
|
441
|
-
this.runtimeHelpers.add("fetchText");
|
|
442
|
-
break;
|
|
443
|
-
case "wait":
|
|
444
|
-
case "settle":
|
|
445
|
-
this.hasAsync = true;
|
|
446
|
-
this.runtimeHelpers.add("wait");
|
|
447
|
-
break;
|
|
448
|
-
case "toggle":
|
|
449
|
-
this.runtimeHelpers.add("toggle");
|
|
450
|
-
break;
|
|
451
|
-
case "set":
|
|
452
|
-
this.runtimeHelpers.add("setProp");
|
|
453
|
-
break;
|
|
454
|
-
case "put":
|
|
455
|
-
this.runtimeHelpers.add("put");
|
|
456
|
-
break;
|
|
457
|
-
case "increment":
|
|
458
|
-
case "decrement":
|
|
459
|
-
this.runtimeHelpers.add("setProp");
|
|
460
|
-
break;
|
|
461
|
-
case "send":
|
|
462
|
-
case "trigger":
|
|
463
|
-
this.runtimeHelpers.add("send");
|
|
464
|
-
break;
|
|
465
|
-
case "halt":
|
|
466
|
-
case "exit":
|
|
467
|
-
this.canThrow = true;
|
|
468
|
-
break;
|
|
469
|
-
case "throw":
|
|
470
|
-
this.canThrow = true;
|
|
471
|
-
break;
|
|
472
|
-
case "call":
|
|
473
|
-
if (node.args?.[0]?.type === "identifier") {
|
|
474
|
-
this.behaviors.push(node.args[0].value);
|
|
475
|
-
}
|
|
476
|
-
break;
|
|
477
|
-
}
|
|
478
|
-
if (node.args) {
|
|
479
|
-
for (const arg of node.args) {
|
|
480
|
-
this.visit(arg);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
if (node.target) {
|
|
484
|
-
this.visit(node.target);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
visitIf(node) {
|
|
488
|
-
this.hasConditionals = true;
|
|
489
|
-
this.enterNesting();
|
|
490
|
-
this.visit(node.condition);
|
|
491
|
-
for (const child of node.thenBranch) {
|
|
492
|
-
this.visit(child);
|
|
493
|
-
}
|
|
494
|
-
if (node.elseIfBranches) {
|
|
495
|
-
for (const branch of node.elseIfBranches) {
|
|
496
|
-
this.visit(branch.condition);
|
|
497
|
-
for (const child of branch.body) {
|
|
498
|
-
this.visit(child);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
if (node.elseBranch) {
|
|
503
|
-
for (const child of node.elseBranch) {
|
|
504
|
-
this.visit(child);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
this.exitNesting();
|
|
508
|
-
}
|
|
509
|
-
visitRepeat(node) {
|
|
510
|
-
this.hasLoops = true;
|
|
511
|
-
this.enterNesting();
|
|
512
|
-
if (node.count && typeof node.count !== "number") {
|
|
513
|
-
this.visit(node.count);
|
|
514
|
-
}
|
|
515
|
-
if (node.whileCondition) {
|
|
516
|
-
this.visit(node.whileCondition);
|
|
517
|
-
}
|
|
518
|
-
if (node.untilEvent !== void 0) {
|
|
519
|
-
this.hasAsync = true;
|
|
520
|
-
this.runtimeHelpers.add("wait");
|
|
521
|
-
if (node.untilEventTarget) this.visit(node.untilEventTarget);
|
|
522
|
-
}
|
|
523
|
-
this.visitLoopBody(node);
|
|
524
|
-
this.exitNesting();
|
|
525
|
-
}
|
|
526
|
-
visitForEach(node) {
|
|
527
|
-
this.hasLoops = true;
|
|
528
|
-
this.enterNesting();
|
|
529
|
-
this.registerVariable(node.itemName, "local", "write");
|
|
530
|
-
this.visit(node.collection);
|
|
531
|
-
this.visitLoopBody(node);
|
|
532
|
-
this.exitNesting();
|
|
533
|
-
}
|
|
534
|
-
visitWhile(node) {
|
|
535
|
-
this.hasLoops = true;
|
|
536
|
-
this.enterNesting();
|
|
537
|
-
this.visit(node.condition);
|
|
538
|
-
this.visitLoopBody(node);
|
|
539
|
-
this.exitNesting();
|
|
540
|
-
}
|
|
541
|
-
/** The parts every loop form shares: its `index` local, body, and `else`. */
|
|
542
|
-
visitLoopBody(node) {
|
|
543
|
-
if (node.indexName) {
|
|
544
|
-
this.registerVariable(node.indexName, "local", "write");
|
|
545
|
-
}
|
|
546
|
-
for (const child of [...node.body, ...node.elseBody ?? []]) {
|
|
547
|
-
this.visit(child);
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
visitVariable(node, access) {
|
|
551
|
-
const name = node.name.startsWith(":") || node.name.startsWith("$") ? node.name.slice(1) : node.name;
|
|
552
|
-
this.registerVariable(name, node.scope, access);
|
|
553
|
-
}
|
|
554
|
-
visitSelector(node) {
|
|
555
|
-
const selector = node.value;
|
|
556
|
-
if (!this.selectorMap.has(selector)) {
|
|
557
|
-
const info = {
|
|
558
|
-
selector,
|
|
559
|
-
usages: [],
|
|
560
|
-
isId: selector.startsWith("#") && !selector.includes(" "),
|
|
561
|
-
canCache: this.canCacheSelector(selector)
|
|
562
|
-
};
|
|
563
|
-
this.selectorMap.set(selector, info);
|
|
564
|
-
this.selectors.push(info);
|
|
565
|
-
}
|
|
566
|
-
this.selectorMap.get(selector).usages.push(this.currentLocation ?? { file: "", line: 0, column: 0 });
|
|
567
|
-
this.domQueries.push(selector);
|
|
568
|
-
if (this.isPureSelector(selector)) {
|
|
569
|
-
this.pureExpressions.push(node);
|
|
570
|
-
} else {
|
|
571
|
-
this.dynamicExpressions.push(node);
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
visitBinary(node) {
|
|
575
|
-
this.visit(node.left);
|
|
576
|
-
this.visit(node.right);
|
|
577
|
-
if (this.isPureExpression(node)) {
|
|
578
|
-
this.pureExpressions.push(node);
|
|
579
|
-
}
|
|
580
|
-
switch (node.operator) {
|
|
581
|
-
case "contains":
|
|
582
|
-
this.runtimeHelpers.add("contains");
|
|
583
|
-
break;
|
|
584
|
-
case "matches":
|
|
585
|
-
this.runtimeHelpers.add("matches");
|
|
586
|
-
break;
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
visitCall(node) {
|
|
590
|
-
this.visit(node.callee);
|
|
591
|
-
if (node.args) {
|
|
592
|
-
for (const arg of node.args) {
|
|
593
|
-
this.visit(arg);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
this.dynamicExpressions.push(node);
|
|
597
|
-
}
|
|
598
|
-
visitIdentifier(node) {
|
|
599
|
-
const value = node.value ?? node.name;
|
|
600
|
-
if (!value) return;
|
|
601
|
-
switch (value) {
|
|
602
|
-
case "me":
|
|
603
|
-
case "my":
|
|
604
|
-
this.contextVars.add("me");
|
|
605
|
-
break;
|
|
606
|
-
case "you":
|
|
607
|
-
case "your":
|
|
608
|
-
this.contextVars.add("you");
|
|
609
|
-
break;
|
|
610
|
-
case "it":
|
|
611
|
-
case "result":
|
|
612
|
-
this.contextVars.add("it");
|
|
613
|
-
break;
|
|
614
|
-
case "event":
|
|
615
|
-
this.contextVars.add("event");
|
|
616
|
-
break;
|
|
617
|
-
case "body":
|
|
618
|
-
case "document":
|
|
619
|
-
case "window":
|
|
620
|
-
break;
|
|
621
|
-
default:
|
|
622
|
-
if (value.charAt(0) === value.charAt(0).toUpperCase()) {
|
|
623
|
-
this.behaviors.push(value);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
// ===========================================================================
|
|
628
|
-
// HELPER METHODS
|
|
629
|
-
// ===========================================================================
|
|
630
|
-
visitChildren(node) {
|
|
631
|
-
for (const key of Object.keys(node)) {
|
|
632
|
-
const value = node[key];
|
|
633
|
-
if (value && typeof value === "object") {
|
|
634
|
-
if (Array.isArray(value)) {
|
|
635
|
-
for (const item of value) {
|
|
636
|
-
if (item && typeof item === "object" && "type" in item) {
|
|
637
|
-
this.visit(item);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
} else if ("type" in value) {
|
|
641
|
-
this.visit(value);
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
registerVariable(name, scope, access) {
|
|
647
|
-
const map = scope === "global" ? this.globalVars : this.localVars;
|
|
648
|
-
if (!map.has(name)) {
|
|
649
|
-
map.set(name, {
|
|
650
|
-
name,
|
|
651
|
-
scope,
|
|
652
|
-
reads: [],
|
|
653
|
-
writes: [],
|
|
654
|
-
type: "unknown"
|
|
655
|
-
});
|
|
656
|
-
}
|
|
657
|
-
const info = map.get(name);
|
|
658
|
-
const location = this.currentLocation ?? { file: "", line: 0, column: 0 };
|
|
659
|
-
if (access === "read") {
|
|
660
|
-
info.reads.push(location);
|
|
661
|
-
} else {
|
|
662
|
-
info.writes.push(location);
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
enterNesting() {
|
|
666
|
-
this.currentNestingDepth++;
|
|
667
|
-
this.maxNestingDepth = Math.max(this.maxNestingDepth, this.currentNestingDepth);
|
|
668
|
-
}
|
|
669
|
-
exitNesting() {
|
|
670
|
-
this.currentNestingDepth--;
|
|
671
|
-
}
|
|
672
|
-
canCacheSelector(selector) {
|
|
673
|
-
const dynamicPseudo = /:(not|has|is|where|nth-|first-|last-|only-|empty|focus|hover|active|visited)/i;
|
|
674
|
-
return !dynamicPseudo.test(selector);
|
|
675
|
-
}
|
|
676
|
-
isPureSelector(selector) {
|
|
677
|
-
return /^[#.][a-zA-Z_][a-zA-Z0-9_-]*$/.test(selector);
|
|
678
|
-
}
|
|
679
|
-
isPureExpression(node) {
|
|
680
|
-
if (!node) return true;
|
|
681
|
-
switch (node.type) {
|
|
682
|
-
case "literal":
|
|
683
|
-
return true;
|
|
684
|
-
case "identifier": {
|
|
685
|
-
const value = node.value;
|
|
686
|
-
return !["me", "you", "it", "result", "event"].includes(value ?? "");
|
|
687
|
-
}
|
|
688
|
-
case "binary": {
|
|
689
|
-
const bin = node;
|
|
690
|
-
return this.isPureExpression(bin.left) && this.isPureExpression(bin.right);
|
|
691
|
-
}
|
|
692
|
-
default:
|
|
693
|
-
return false;
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
};
|
|
697
|
-
function analyze(ast) {
|
|
698
|
-
const analyzer = new Analyzer();
|
|
699
|
-
return analyzer.analyze(ast);
|
|
700
|
-
}
|
|
701
|
-
var TARGET_SLOTS = /* @__PURE__ */ new Map([
|
|
702
|
-
["add", "to"],
|
|
703
|
-
["remove", "from"],
|
|
704
|
-
["toggle", "on"]
|
|
705
|
-
]);
|
|
706
|
-
function extractClassOp(node) {
|
|
707
|
-
if (node.type !== "command") return null;
|
|
708
|
-
const cmd = node;
|
|
709
|
-
if (!TARGET_SLOTS.has(cmd.name)) return null;
|
|
710
|
-
const args = cmd.args ?? [];
|
|
711
|
-
if (args.length === 0) return null;
|
|
712
|
-
const arg = args[0];
|
|
713
|
-
if (arg.type !== "selector") return null;
|
|
714
|
-
const selector = arg.value;
|
|
715
|
-
if (!selector.startsWith(".")) return null;
|
|
716
|
-
const className = selector.slice(1);
|
|
717
|
-
if (!className) return null;
|
|
718
|
-
return { command: cmd.name, className };
|
|
719
|
-
}
|
|
720
|
-
function classTarget(cmd) {
|
|
721
|
-
if (cmd.target) return cmd.target;
|
|
722
|
-
const slot = cmd.modifiers?.[TARGET_SLOTS.get(cmd.name)];
|
|
723
|
-
return slot && typeof slot === "object" && "type" in slot ? slot : void 0;
|
|
724
|
-
}
|
|
725
|
-
function batchTargetKey(cmd) {
|
|
726
|
-
const target = classTarget(cmd);
|
|
727
|
-
if (!target) return "me";
|
|
728
|
-
if (target.type === "identifier" && target.value === "me") return "me";
|
|
729
|
-
if (target.type === "selector" && /^#[\w-]+$/.test(target.value)) {
|
|
730
|
-
return target.value;
|
|
731
|
-
}
|
|
732
|
-
return null;
|
|
733
|
-
}
|
|
734
|
-
function resolveTarget(key) {
|
|
735
|
-
return key === "me" ? "_ctx.me" : `document.getElementById('${key.slice(1)}')`;
|
|
736
|
-
}
|
|
737
|
-
function reordersClass(run, op) {
|
|
738
|
-
return run.some((node) => {
|
|
739
|
-
const other = extractClassOp(node);
|
|
740
|
-
return other.command !== op.command && other.className === op.className;
|
|
741
|
-
});
|
|
742
|
-
}
|
|
743
|
-
function createBatchNode(run, targetKey) {
|
|
744
|
-
const adds = [];
|
|
745
|
-
const removes = [];
|
|
746
|
-
const toggles = [];
|
|
747
|
-
for (const node of run) {
|
|
748
|
-
const op = extractClassOp(node);
|
|
749
|
-
switch (op.command) {
|
|
750
|
-
case "add":
|
|
751
|
-
adds.push(op.className);
|
|
752
|
-
break;
|
|
753
|
-
case "remove":
|
|
754
|
-
removes.push(op.className);
|
|
755
|
-
break;
|
|
756
|
-
case "toggle":
|
|
757
|
-
toggles.push(op.className);
|
|
758
|
-
break;
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
return {
|
|
762
|
-
type: "batchedClassOps",
|
|
763
|
-
target: resolveTarget(targetKey),
|
|
764
|
-
adds,
|
|
765
|
-
removes,
|
|
766
|
-
toggles
|
|
767
|
-
};
|
|
768
|
-
}
|
|
769
|
-
function batchBody(nodes) {
|
|
770
|
-
const result = [];
|
|
771
|
-
let currentRun = [];
|
|
772
|
-
let runKey = "";
|
|
773
|
-
function flushRun() {
|
|
774
|
-
if (currentRun.length >= 2) {
|
|
775
|
-
result.push(createBatchNode(currentRun, runKey));
|
|
776
|
-
} else if (currentRun.length === 1) {
|
|
777
|
-
result.push(currentRun[0]);
|
|
778
|
-
}
|
|
779
|
-
currentRun = [];
|
|
780
|
-
}
|
|
781
|
-
for (const node of nodes) {
|
|
782
|
-
const op = extractClassOp(node);
|
|
783
|
-
const key = op && batchTargetKey(node);
|
|
784
|
-
if (op && key) {
|
|
785
|
-
if (currentRun.length > 0 && key === runKey && !reordersClass(currentRun, op)) {
|
|
786
|
-
currentRun.push(node);
|
|
787
|
-
} else {
|
|
788
|
-
flushRun();
|
|
789
|
-
currentRun = [node];
|
|
790
|
-
runKey = key;
|
|
791
|
-
}
|
|
792
|
-
} else {
|
|
793
|
-
flushRun();
|
|
794
|
-
result.push(node);
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
flushRun();
|
|
798
|
-
return result;
|
|
799
|
-
}
|
|
800
|
-
function walkAndBatch(node) {
|
|
801
|
-
switch (node.type) {
|
|
802
|
-
case "event": {
|
|
803
|
-
const event = node;
|
|
804
|
-
if (event.body && event.body.length > 0) {
|
|
805
|
-
return { ...event, body: batchBody(event.body).map(walkAndBatch) };
|
|
806
|
-
}
|
|
807
|
-
return node;
|
|
808
|
-
}
|
|
809
|
-
case "if": {
|
|
810
|
-
const ifNode = node;
|
|
811
|
-
const result = { ...ifNode };
|
|
812
|
-
result.thenBranch = batchBody(ifNode.thenBranch).map(walkAndBatch);
|
|
813
|
-
if (ifNode.elseBranch) {
|
|
814
|
-
result.elseBranch = batchBody(ifNode.elseBranch).map(walkAndBatch);
|
|
815
|
-
}
|
|
816
|
-
if (ifNode.elseIfBranches) {
|
|
817
|
-
result.elseIfBranches = ifNode.elseIfBranches.map((branch) => ({
|
|
818
|
-
condition: branch.condition,
|
|
819
|
-
body: batchBody(branch.body).map(walkAndBatch)
|
|
820
|
-
}));
|
|
821
|
-
}
|
|
822
|
-
return result;
|
|
823
|
-
}
|
|
824
|
-
case "repeat": {
|
|
825
|
-
const repeat = node;
|
|
826
|
-
return { ...repeat, body: batchBody(repeat.body).map(walkAndBatch) };
|
|
827
|
-
}
|
|
828
|
-
case "foreach": {
|
|
829
|
-
const forEach = node;
|
|
830
|
-
return { ...forEach, body: batchBody(forEach.body).map(walkAndBatch) };
|
|
831
|
-
}
|
|
832
|
-
case "while": {
|
|
833
|
-
const whileNode = node;
|
|
834
|
-
return { ...whileNode, body: batchBody(whileNode.body).map(walkAndBatch) };
|
|
835
|
-
}
|
|
836
|
-
default:
|
|
837
|
-
return node;
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
var ClassBatchingPass = class {
|
|
841
|
-
constructor() {
|
|
842
|
-
this.name = "class-batching";
|
|
843
|
-
}
|
|
844
|
-
shouldRun(analysis) {
|
|
845
|
-
return analysis.commandsUsed.has("add") || analysis.commandsUsed.has("remove") || analysis.commandsUsed.has("toggle");
|
|
846
|
-
}
|
|
847
|
-
transform(ast, _analysis) {
|
|
848
|
-
return walkAndBatch(ast);
|
|
849
|
-
}
|
|
850
|
-
};
|
|
851
|
-
var ConstantFoldingPass = class {
|
|
852
|
-
constructor() {
|
|
853
|
-
this.name = "constant-folding";
|
|
854
|
-
}
|
|
855
|
-
shouldRun(analysis) {
|
|
856
|
-
return analysis.expressions.pure.length > 0;
|
|
857
|
-
}
|
|
858
|
-
transform(ast, _analysis) {
|
|
859
|
-
return this.foldNode(ast);
|
|
860
|
-
}
|
|
861
|
-
foldNode(node) {
|
|
862
|
-
if (!node) return node;
|
|
863
|
-
const processed = this.processChildren(node);
|
|
864
|
-
if (processed.type === "binary") {
|
|
865
|
-
return this.foldBinary(processed);
|
|
866
|
-
}
|
|
867
|
-
return processed;
|
|
868
|
-
}
|
|
869
|
-
processChildren(node) {
|
|
870
|
-
const result = { ...node };
|
|
871
|
-
for (const key of Object.keys(node)) {
|
|
872
|
-
const value = node[key];
|
|
873
|
-
if (Array.isArray(value)) {
|
|
874
|
-
result[key] = value.map(
|
|
875
|
-
(item) => item && typeof item === "object" && "type" in item ? this.foldNode(item) : item
|
|
876
|
-
);
|
|
877
|
-
} else if (value && typeof value === "object" && "type" in value) {
|
|
878
|
-
result[key] = this.foldNode(value);
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
return result;
|
|
882
|
-
}
|
|
883
|
-
foldBinary(node) {
|
|
884
|
-
const left = node.left;
|
|
885
|
-
const right = node.right;
|
|
886
|
-
if (left.type !== "literal" || right.type !== "literal") {
|
|
887
|
-
return node;
|
|
888
|
-
}
|
|
889
|
-
const leftVal = left.value;
|
|
890
|
-
const rightVal = right.value;
|
|
891
|
-
let result;
|
|
892
|
-
switch (node.operator) {
|
|
893
|
-
// Arithmetic
|
|
894
|
-
case "+":
|
|
895
|
-
if (typeof leftVal === "number" && typeof rightVal === "number") {
|
|
896
|
-
result = leftVal + rightVal;
|
|
897
|
-
}
|
|
898
|
-
break;
|
|
899
|
-
case "-":
|
|
900
|
-
if (typeof leftVal === "number" && typeof rightVal === "number") {
|
|
901
|
-
result = leftVal - rightVal;
|
|
902
|
-
}
|
|
903
|
-
break;
|
|
904
|
-
case "*":
|
|
905
|
-
if (typeof leftVal === "number" && typeof rightVal === "number") {
|
|
906
|
-
result = leftVal * rightVal;
|
|
907
|
-
}
|
|
908
|
-
break;
|
|
909
|
-
case "/":
|
|
910
|
-
if (typeof leftVal === "number" && typeof rightVal === "number" && rightVal !== 0) {
|
|
911
|
-
result = leftVal / rightVal;
|
|
912
|
-
}
|
|
913
|
-
break;
|
|
914
|
-
case "%":
|
|
915
|
-
if (typeof leftVal === "number" && typeof rightVal === "number" && rightVal !== 0) {
|
|
916
|
-
result = leftVal % rightVal;
|
|
917
|
-
}
|
|
918
|
-
break;
|
|
919
|
-
// String concatenation
|
|
920
|
-
case "&":
|
|
921
|
-
result = String(leftVal) + String(rightVal);
|
|
922
|
-
break;
|
|
923
|
-
// Logical
|
|
924
|
-
case "and":
|
|
925
|
-
case "&&":
|
|
926
|
-
result = leftVal && rightVal;
|
|
927
|
-
break;
|
|
928
|
-
case "or":
|
|
929
|
-
case "||":
|
|
930
|
-
result = leftVal || rightVal;
|
|
931
|
-
break;
|
|
932
|
-
// Comparison
|
|
933
|
-
case "is":
|
|
934
|
-
case "==":
|
|
935
|
-
result = leftVal === rightVal;
|
|
936
|
-
break;
|
|
937
|
-
case "is not":
|
|
938
|
-
case "!=":
|
|
939
|
-
result = leftVal !== rightVal;
|
|
940
|
-
break;
|
|
941
|
-
case "<":
|
|
942
|
-
if (typeof leftVal === "number" && typeof rightVal === "number") {
|
|
943
|
-
result = leftVal < rightVal;
|
|
944
|
-
}
|
|
945
|
-
break;
|
|
946
|
-
case ">":
|
|
947
|
-
if (typeof leftVal === "number" && typeof rightVal === "number") {
|
|
948
|
-
result = leftVal > rightVal;
|
|
949
|
-
}
|
|
950
|
-
break;
|
|
951
|
-
case "<=":
|
|
952
|
-
if (typeof leftVal === "number" && typeof rightVal === "number") {
|
|
953
|
-
result = leftVal <= rightVal;
|
|
954
|
-
}
|
|
955
|
-
break;
|
|
956
|
-
case ">=":
|
|
957
|
-
if (typeof leftVal === "number" && typeof rightVal === "number") {
|
|
958
|
-
result = leftVal >= rightVal;
|
|
959
|
-
}
|
|
960
|
-
break;
|
|
961
|
-
}
|
|
962
|
-
if (result !== void 0) {
|
|
963
|
-
return {
|
|
964
|
-
type: "literal",
|
|
965
|
-
value: result
|
|
966
|
-
};
|
|
967
|
-
}
|
|
968
|
-
return node;
|
|
969
|
-
}
|
|
970
|
-
};
|
|
971
|
-
var SelectorCachingPass = class {
|
|
972
|
-
constructor() {
|
|
973
|
-
this.name = "selector-caching";
|
|
974
|
-
}
|
|
975
|
-
shouldRun(analysis) {
|
|
976
|
-
return analysis.expressions.selectors.some((s) => s.usages.length > 1 && s.canCache);
|
|
977
|
-
}
|
|
978
|
-
transform(ast, analysis) {
|
|
979
|
-
const selectorsToCache = /* @__PURE__ */ new Set();
|
|
980
|
-
for (const info of analysis.expressions.selectors) {
|
|
981
|
-
if (info.usages.length > 1 && info.canCache) {
|
|
982
|
-
selectorsToCache.add(info.selector);
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
if (selectorsToCache.size === 0) {
|
|
986
|
-
return ast;
|
|
987
|
-
}
|
|
988
|
-
return this.markSelectors(ast, selectorsToCache);
|
|
989
|
-
}
|
|
990
|
-
markSelectors(node, toCache) {
|
|
991
|
-
if (!node) return node;
|
|
992
|
-
if (node.type === "selector") {
|
|
993
|
-
const selector = node.value;
|
|
994
|
-
if (toCache.has(selector)) {
|
|
995
|
-
return {
|
|
996
|
-
...node,
|
|
997
|
-
_cached: true,
|
|
998
|
-
_cacheKey: this.generateCacheKey(selector)
|
|
999
|
-
};
|
|
1000
|
-
}
|
|
1001
|
-
return node;
|
|
1002
|
-
}
|
|
1003
|
-
const result = { ...node };
|
|
1004
|
-
for (const key of Object.keys(node)) {
|
|
1005
|
-
const value = node[key];
|
|
1006
|
-
if (Array.isArray(value)) {
|
|
1007
|
-
result[key] = value.map(
|
|
1008
|
-
(item) => item && typeof item === "object" && "type" in item ? this.markSelectors(item, toCache) : item
|
|
1009
|
-
);
|
|
1010
|
-
} else if (value && typeof value === "object" && "type" in value) {
|
|
1011
|
-
result[key] = this.markSelectors(value, toCache);
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
return result;
|
|
1015
|
-
}
|
|
1016
|
-
generateCacheKey(selector) {
|
|
1017
|
-
return "_sel_" + selector.replace(/[^a-zA-Z0-9]/g, "_").replace(/_+/g, "_").slice(0, 20);
|
|
1018
|
-
}
|
|
1019
|
-
};
|
|
1020
|
-
var DeadCodeEliminationPass = class {
|
|
1021
|
-
constructor() {
|
|
1022
|
-
this.name = "dead-code-elimination";
|
|
1023
|
-
}
|
|
1024
|
-
shouldRun(analysis) {
|
|
1025
|
-
return analysis.controlFlow.canThrow;
|
|
1026
|
-
}
|
|
1027
|
-
transform(ast, _analysis) {
|
|
1028
|
-
return this.eliminateDeadCode(ast);
|
|
1029
|
-
}
|
|
1030
|
-
eliminateDeadCode(node) {
|
|
1031
|
-
if (!node) return node;
|
|
1032
|
-
if (Array.isArray(node)) {
|
|
1033
|
-
return this.eliminateFromArray(node);
|
|
1034
|
-
}
|
|
1035
|
-
const result = { ...node };
|
|
1036
|
-
for (const key of ["body", "thenBranch", "elseBranch"]) {
|
|
1037
|
-
const value = node[key];
|
|
1038
|
-
if (Array.isArray(value)) {
|
|
1039
|
-
result[key] = this.eliminateFromArray(value);
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
for (const key of Object.keys(node)) {
|
|
1043
|
-
if (["body", "thenBranch", "elseBranch"].includes(key)) continue;
|
|
1044
|
-
const value = node[key];
|
|
1045
|
-
if (value && typeof value === "object" && "type" in value) {
|
|
1046
|
-
result[key] = this.eliminateDeadCode(value);
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
return result;
|
|
1050
|
-
}
|
|
1051
|
-
eliminateFromArray(nodes) {
|
|
1052
|
-
const result = [];
|
|
1053
|
-
for (const node of nodes) {
|
|
1054
|
-
const processed = this.eliminateDeadCode(node);
|
|
1055
|
-
result.push(processed);
|
|
1056
|
-
if (this.isTerminator(node)) {
|
|
1057
|
-
break;
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
return result;
|
|
1061
|
-
}
|
|
1062
|
-
isTerminator(node) {
|
|
1063
|
-
if (node.type === "command") {
|
|
1064
|
-
return ["halt", "exit", "return", "throw"].includes(node.name);
|
|
1065
|
-
}
|
|
1066
|
-
return false;
|
|
1067
|
-
}
|
|
1068
|
-
};
|
|
1069
|
-
var LoopUnrollingPass = class {
|
|
1070
|
-
constructor() {
|
|
1071
|
-
this.name = "loop-unrolling";
|
|
1072
|
-
this.maxUnrollCount = 5;
|
|
1073
|
-
}
|
|
1074
|
-
shouldRun(analysis) {
|
|
1075
|
-
return analysis.controlFlow.hasLoops;
|
|
1076
|
-
}
|
|
1077
|
-
transform(ast, _analysis) {
|
|
1078
|
-
return this.unrollLoops(ast);
|
|
1079
|
-
}
|
|
1080
|
-
unrollLoops(node) {
|
|
1081
|
-
if (!node) return node;
|
|
1082
|
-
if (node.type === "repeat") {
|
|
1083
|
-
const unrolled = this.tryUnroll(node);
|
|
1084
|
-
if (unrolled) {
|
|
1085
|
-
return unrolled;
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
const result = { ...node };
|
|
1089
|
-
for (const key of Object.keys(node)) {
|
|
1090
|
-
const value = node[key];
|
|
1091
|
-
if (Array.isArray(value)) {
|
|
1092
|
-
result[key] = value.map(
|
|
1093
|
-
(item) => item && typeof item === "object" && "type" in item ? this.unrollLoops(item) : item
|
|
1094
|
-
);
|
|
1095
|
-
} else if (value && typeof value === "object" && "type" in value) {
|
|
1096
|
-
result[key] = this.unrollLoops(value);
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
return result;
|
|
1100
|
-
}
|
|
1101
|
-
tryUnroll(node) {
|
|
1102
|
-
const repeatNode = node;
|
|
1103
|
-
if (typeof repeatNode.count !== "number") {
|
|
1104
|
-
return null;
|
|
1105
|
-
}
|
|
1106
|
-
const count = repeatNode.count;
|
|
1107
|
-
const body = repeatNode.body ?? [];
|
|
1108
|
-
if (count > this.maxUnrollCount) {
|
|
1109
|
-
return null;
|
|
1110
|
-
}
|
|
1111
|
-
if (body.length > 3) {
|
|
1112
|
-
return null;
|
|
1113
|
-
}
|
|
1114
|
-
if (this.usesIndex(body)) {
|
|
1115
|
-
return null;
|
|
1116
|
-
}
|
|
1117
|
-
if (repeatNode.indexName !== void 0 || repeatNode.elseBody !== void 0) {
|
|
1118
|
-
return null;
|
|
1119
|
-
}
|
|
1120
|
-
const unrolled = [];
|
|
1121
|
-
for (let i = 0; i < count; i++) {
|
|
1122
|
-
unrolled.push(...this.cloneBody(body));
|
|
1123
|
-
}
|
|
1124
|
-
return {
|
|
1125
|
-
type: "sequence",
|
|
1126
|
-
commands: unrolled,
|
|
1127
|
-
_unrolled: true
|
|
1128
|
-
};
|
|
1129
|
-
}
|
|
1130
|
-
usesIndex(nodes) {
|
|
1131
|
-
for (const node of nodes) {
|
|
1132
|
-
if (this.nodeUsesIndex(node)) {
|
|
1133
|
-
return true;
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
return false;
|
|
1137
|
-
}
|
|
1138
|
-
nodeUsesIndex(node) {
|
|
1139
|
-
if (!node) return false;
|
|
1140
|
-
if (node.type === "identifier") {
|
|
1141
|
-
if (node.value === "index" || node.value === ":index") {
|
|
1142
|
-
return true;
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
if (node.type === "variable") {
|
|
1146
|
-
if (node.name === "index" || node.name === ":index") {
|
|
1147
|
-
return true;
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
for (const key of Object.keys(node)) {
|
|
1151
|
-
const value = node[key];
|
|
1152
|
-
if (Array.isArray(value)) {
|
|
1153
|
-
for (const item of value) {
|
|
1154
|
-
if (item && typeof item === "object" && "type" in item) {
|
|
1155
|
-
if (this.nodeUsesIndex(item)) {
|
|
1156
|
-
return true;
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
} else if (value && typeof value === "object" && "type" in value) {
|
|
1161
|
-
if (this.nodeUsesIndex(value)) {
|
|
1162
|
-
return true;
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
return false;
|
|
1167
|
-
}
|
|
1168
|
-
cloneBody(nodes) {
|
|
1169
|
-
return JSON.parse(JSON.stringify(nodes));
|
|
1170
|
-
}
|
|
1171
|
-
};
|
|
1172
|
-
var OptimizationPipeline = class {
|
|
1173
|
-
constructor() {
|
|
1174
|
-
this.passes = [
|
|
1175
|
-
new ConstantFoldingPass(),
|
|
1176
|
-
new SelectorCachingPass(),
|
|
1177
|
-
new DeadCodeEliminationPass(),
|
|
1178
|
-
new ClassBatchingPass(),
|
|
1179
|
-
new LoopUnrollingPass()
|
|
1180
|
-
];
|
|
1181
|
-
}
|
|
1182
|
-
/**
|
|
1183
|
-
* Run all applicable optimizations on the AST.
|
|
1184
|
-
*/
|
|
1185
|
-
optimize(ast, analysis, level = 2) {
|
|
1186
|
-
if (level === 0) {
|
|
1187
|
-
return ast;
|
|
1188
|
-
}
|
|
1189
|
-
let current = ast;
|
|
1190
|
-
const appliedOptimizations = [];
|
|
1191
|
-
const passesToRun = level === 1 ? this.passes.slice(0, 2) : this.passes;
|
|
1192
|
-
for (const pass of passesToRun) {
|
|
1193
|
-
if (pass.shouldRun(analysis)) {
|
|
1194
|
-
current = pass.transform(current, analysis);
|
|
1195
|
-
appliedOptimizations.push(pass.name);
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
return {
|
|
1199
|
-
...current,
|
|
1200
|
-
_optimizations: appliedOptimizations
|
|
1201
|
-
};
|
|
1202
|
-
}
|
|
1203
|
-
/**
|
|
1204
|
-
* Add a custom optimization pass.
|
|
1205
|
-
*/
|
|
1206
|
-
addPass(pass) {
|
|
1207
|
-
this.passes.push(pass);
|
|
1208
|
-
}
|
|
1209
|
-
};
|
|
1210
|
-
function createOptimizer() {
|
|
1211
|
-
return /* @__PURE__ */ new OptimizationPipeline();
|
|
1212
|
-
}
|
|
1213
|
-
function optimize(ast, analysis, level = 2) {
|
|
1214
|
-
const pipeline = new OptimizationPipeline();
|
|
1215
|
-
return pipeline.optimize(ast, analysis, level);
|
|
1216
|
-
}
|
|
1217
|
-
function classifyCommand(node) {
|
|
1218
|
-
if (node.type !== "command") return null;
|
|
1219
|
-
const cmd = node;
|
|
1220
|
-
switch (cmd.name) {
|
|
1221
|
-
case "add":
|
|
1222
|
-
return classifyAddCommand(cmd);
|
|
1223
|
-
case "remove":
|
|
1224
|
-
return classifyRemoveCommand(cmd);
|
|
1225
|
-
case "put":
|
|
1226
|
-
return classifyPutCommand(cmd);
|
|
1227
|
-
case "set":
|
|
1228
|
-
return classifySetCommand(cmd);
|
|
1229
|
-
default:
|
|
1230
|
-
return null;
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
function getIdTarget(cmd) {
|
|
1234
|
-
if (!cmd.target) return null;
|
|
1235
|
-
if (cmd.target.type !== "selector") return null;
|
|
1236
|
-
const sel = cmd.target.value;
|
|
1237
|
-
if (!sel.startsWith("#")) return null;
|
|
1238
|
-
return sel.slice(1);
|
|
1239
|
-
}
|
|
1240
|
-
function classifyAddCommand(cmd) {
|
|
1241
|
-
const targetId = getIdTarget(cmd);
|
|
1242
|
-
if (!targetId) return null;
|
|
1243
|
-
const args = cmd.args ?? [];
|
|
1244
|
-
if (args.length === 0) return null;
|
|
1245
|
-
const arg = args[0];
|
|
1246
|
-
if (arg.type !== "selector") return null;
|
|
1247
|
-
const val = arg.value;
|
|
1248
|
-
if (!val.startsWith(".")) return null;
|
|
1249
|
-
return { type: "addClass", targetId, value: val.slice(1) };
|
|
1250
|
-
}
|
|
1251
|
-
function classifyRemoveCommand(cmd) {
|
|
1252
|
-
const targetId = getIdTarget(cmd);
|
|
1253
|
-
if (!targetId) return null;
|
|
1254
|
-
const args = cmd.args ?? [];
|
|
1255
|
-
if (args.length === 0) return null;
|
|
1256
|
-
const arg = args[0];
|
|
1257
|
-
if (arg.type !== "selector") return null;
|
|
1258
|
-
const val = arg.value;
|
|
1259
|
-
if (!val.startsWith(".")) return null;
|
|
1260
|
-
return { type: "removeClass", targetId, value: val.slice(1) };
|
|
1261
|
-
}
|
|
1262
|
-
function classifyPutCommand(cmd) {
|
|
1263
|
-
const targetId = getIdTarget(cmd);
|
|
1264
|
-
if (!targetId) return null;
|
|
1265
|
-
const args = cmd.args ?? [];
|
|
1266
|
-
if (args.length === 0) return null;
|
|
1267
|
-
const arg = args[0];
|
|
1268
|
-
if (arg.type !== "literal") return null;
|
|
1269
|
-
const val = arg.value;
|
|
1270
|
-
if (typeof val !== "string") return null;
|
|
1271
|
-
return { type: "putContent", targetId, value: val };
|
|
1272
|
-
}
|
|
1273
|
-
function classifySetCommand(cmd) {
|
|
1274
|
-
const targetId = getIdTarget(cmd);
|
|
1275
|
-
if (!targetId) return null;
|
|
1276
|
-
const args = cmd.args ?? [];
|
|
1277
|
-
if (args.length < 2) return null;
|
|
1278
|
-
const attrArg = args[0];
|
|
1279
|
-
if (attrArg.type !== "identifier") return null;
|
|
1280
|
-
const attrValue = attrArg.value;
|
|
1281
|
-
if (!attrValue || !attrValue.startsWith("@")) return null;
|
|
1282
|
-
const attrName = attrValue.slice(1);
|
|
1283
|
-
const valArg = args[1];
|
|
1284
|
-
if (valArg.type !== "literal") return null;
|
|
1285
|
-
const val = valArg.value;
|
|
1286
|
-
if (typeof val !== "string" && typeof val !== "number") return null;
|
|
1287
|
-
return { type: "setAttribute", targetId, value: String(val), attrName };
|
|
1288
|
-
}
|
|
1289
|
-
function classifyInitCommands(commands) {
|
|
1290
|
-
const pure = [];
|
|
1291
|
-
const impure = [];
|
|
1292
|
-
for (let i = 0; i < commands.length; i++) {
|
|
1293
|
-
const classified = classifyCommand(commands[i]);
|
|
1294
|
-
if (classified) {
|
|
1295
|
-
pure.push({ index: i, command: classified });
|
|
1296
|
-
} else {
|
|
1297
|
-
impure.push({ index: i, node: commands[i] });
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
return { pure, impure };
|
|
1301
|
-
}
|
|
1302
|
-
function applyPureCommand(html, cmd) {
|
|
1303
|
-
switch (cmd.type) {
|
|
1304
|
-
case "addClass":
|
|
1305
|
-
return addClassToElement(html, cmd.targetId, cmd.value);
|
|
1306
|
-
case "removeClass":
|
|
1307
|
-
return removeClassFromElement(html, cmd.targetId, cmd.value);
|
|
1308
|
-
case "putContent":
|
|
1309
|
-
return putContentIntoElement(html, cmd.targetId, cmd.value);
|
|
1310
|
-
case "setAttribute":
|
|
1311
|
-
return setAttributeOnElement(html, cmd.targetId, cmd.attrName, cmd.value);
|
|
1312
|
-
default:
|
|
1313
|
-
return html;
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
function transformElementById(html, id, transform) {
|
|
1317
|
-
const idPattern = new RegExp(`(<[^>]*\\bid=["']${escapeRegExp(id)}["'][^>]*>)`, "i");
|
|
1318
|
-
const match = html.match(idPattern);
|
|
1319
|
-
if (!match) return html;
|
|
1320
|
-
return html.replace(match[1], transform(match[1]));
|
|
1321
|
-
}
|
|
1322
|
-
function addClassToElement(html, id, className) {
|
|
1323
|
-
return transformElementById(html, id, (tag) => {
|
|
1324
|
-
const classMatch = tag.match(/\bclass=["']([^"']*)["']/i);
|
|
1325
|
-
if (classMatch) {
|
|
1326
|
-
const existing = classMatch[1];
|
|
1327
|
-
const classes = existing.split(/\s+/).filter(Boolean);
|
|
1328
|
-
if (!classes.includes(className)) {
|
|
1329
|
-
classes.push(className);
|
|
1330
|
-
}
|
|
1331
|
-
return tag.replace(classMatch[0], `class="${classes.join(" ")}"`);
|
|
1332
|
-
}
|
|
1333
|
-
return tag.replace(/>$/, ` class="${className}">`);
|
|
1334
|
-
});
|
|
1335
|
-
}
|
|
1336
|
-
function removeClassFromElement(html, id, className) {
|
|
1337
|
-
return transformElementById(html, id, (tag) => {
|
|
1338
|
-
const classMatch = tag.match(/\bclass=["']([^"']*)["']/i);
|
|
1339
|
-
if (!classMatch) return tag;
|
|
1340
|
-
const classes = classMatch[1].split(/\s+/).filter((c) => c !== className);
|
|
1341
|
-
if (classes.length === 0) {
|
|
1342
|
-
return tag.replace(classMatch[0], "").replace(/\s+>/, ">");
|
|
1343
|
-
}
|
|
1344
|
-
return tag.replace(classMatch[0], `class="${classes.join(" ")}"`);
|
|
1345
|
-
});
|
|
1346
|
-
}
|
|
1347
|
-
function putContentIntoElement(html, id, content) {
|
|
1348
|
-
const idPattern = new RegExp(
|
|
1349
|
-
`(<[a-zA-Z][^>]*\\bid=["']${escapeRegExp(id)}["'][^>]*>)((?:(?!<\\/[a-zA-Z]).)*?)(<\\/[a-zA-Z][^>]*>)`,
|
|
1350
|
-
"is"
|
|
1351
|
-
);
|
|
1352
|
-
const match = html.match(idPattern);
|
|
1353
|
-
if (!match) return html;
|
|
1354
|
-
return html.replace(match[0], `${match[1]}${escapeHtml(content)}${match[3]}`);
|
|
1355
|
-
}
|
|
1356
|
-
function setAttributeOnElement(html, id, attrName, value) {
|
|
1357
|
-
return transformElementById(html, id, (tag) => {
|
|
1358
|
-
const attrPattern = new RegExp(`\\b${escapeRegExp(attrName)}=["'][^"']*["']`, "i");
|
|
1359
|
-
const attrMatch = tag.match(attrPattern);
|
|
1360
|
-
if (attrMatch) {
|
|
1361
|
-
return tag.replace(attrMatch[0], `${attrName}="${escapeHtml(value)}"`);
|
|
1362
|
-
}
|
|
1363
|
-
return tag.replace(/>$/, ` ${attrName}="${escapeHtml(value)}">`);
|
|
1364
|
-
});
|
|
1365
|
-
}
|
|
1366
|
-
function escapeRegExp(str) {
|
|
1367
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1368
|
-
}
|
|
1369
|
-
function escapeHtml(str) {
|
|
1370
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1371
|
-
}
|
|
1372
|
-
function preRenderInitBlock(html, initCommands) {
|
|
1373
|
-
const { pure, impure } = classifyInitCommands(initCommands);
|
|
1374
|
-
let modifiedHtml = html;
|
|
1375
|
-
let preRenderedCount = 0;
|
|
1376
|
-
for (const { command } of pure) {
|
|
1377
|
-
const before = modifiedHtml;
|
|
1378
|
-
modifiedHtml = applyPureCommand(modifiedHtml, command);
|
|
1379
|
-
if (modifiedHtml !== before) {
|
|
1380
|
-
preRenderedCount++;
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
return {
|
|
1384
|
-
html: modifiedHtml,
|
|
1385
|
-
remainingInitCommands: impure.map((i) => i.node),
|
|
1386
|
-
preRenderedCount
|
|
1387
|
-
};
|
|
1388
|
-
}
|
|
1389
|
-
function sanitizeClassName(name) {
|
|
1390
|
-
if (!/^-?[a-zA-Z_][a-zA-Z0-9_-]*$/.test(name)) {
|
|
1391
|
-
return null;
|
|
1392
|
-
}
|
|
1393
|
-
return name;
|
|
1394
|
-
}
|
|
1395
|
-
function sanitizeSelector(selector) {
|
|
1396
|
-
return selector.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\0/g, "");
|
|
1397
|
-
}
|
|
1398
|
-
function sanitizeIdentifier(name) {
|
|
1399
|
-
return name.replace(/[^a-zA-Z0-9_$]/g, "_");
|
|
1400
|
-
}
|
|
1401
|
-
var ExpressionCodegen = class {
|
|
1402
|
-
constructor(ctx) {
|
|
1403
|
-
this.ctx = ctx;
|
|
1404
|
-
}
|
|
1405
|
-
/**
|
|
1406
|
-
* Generate JavaScript code for an expression.
|
|
1407
|
-
*/
|
|
1408
|
-
generate(node) {
|
|
1409
|
-
if (!node) {
|
|
1410
|
-
return "undefined";
|
|
1411
|
-
}
|
|
1412
|
-
switch (node.type) {
|
|
1413
|
-
case "literal":
|
|
1414
|
-
return this.generateLiteral(node);
|
|
1415
|
-
case "identifier":
|
|
1416
|
-
return this.generateIdentifier(node);
|
|
1417
|
-
case "selector":
|
|
1418
|
-
return this.generateSelector(node);
|
|
1419
|
-
case "variable":
|
|
1420
|
-
return this.generateVariable(node);
|
|
1421
|
-
case "binary":
|
|
1422
|
-
return this.generateBinary(node);
|
|
1423
|
-
case "member":
|
|
1424
|
-
return this.generateMember(node);
|
|
1425
|
-
case "possessive":
|
|
1426
|
-
return this.generatePossessive(node);
|
|
1427
|
-
case "call":
|
|
1428
|
-
return this.generateCall(node);
|
|
1429
|
-
case "positional":
|
|
1430
|
-
return this.generatePositional(node);
|
|
1431
|
-
case "array":
|
|
1432
|
-
return this.generateArray(node);
|
|
1433
|
-
case "object":
|
|
1434
|
-
return this.generateObject(node);
|
|
1435
|
-
case "template":
|
|
1436
|
-
return this.generateTemplate(node);
|
|
1437
|
-
case "unary":
|
|
1438
|
-
return this.generateUnary(node);
|
|
1439
|
-
case "typeCheck":
|
|
1440
|
-
return this.generateTypeCheck(node);
|
|
1441
|
-
case "conditional":
|
|
1442
|
-
return this.generateConditional(node);
|
|
1443
|
-
case "sequence":
|
|
1444
|
-
return "undefined";
|
|
1445
|
-
default:
|
|
1446
|
-
if ("value" in node) {
|
|
1447
|
-
return JSON.stringify(node.value);
|
|
1448
|
-
}
|
|
1449
|
-
throw new Error(`Unknown expression type: ${node.type}`);
|
|
1450
|
-
}
|
|
1451
|
-
}
|
|
1452
|
-
// ===========================================================================
|
|
1453
|
-
// LITERAL EXPRESSIONS
|
|
1454
|
-
// ===========================================================================
|
|
1455
|
-
generateLiteral(node) {
|
|
1456
|
-
const value = node.value;
|
|
1457
|
-
if (value === null) {
|
|
1458
|
-
return "null";
|
|
1459
|
-
}
|
|
1460
|
-
if (value === void 0) {
|
|
1461
|
-
return "undefined";
|
|
1462
|
-
}
|
|
1463
|
-
if (typeof value === "string") {
|
|
1464
|
-
return JSON.stringify(value);
|
|
1465
|
-
}
|
|
1466
|
-
if (typeof value === "number") {
|
|
1467
|
-
return String(value);
|
|
1468
|
-
}
|
|
1469
|
-
if (typeof value === "boolean") {
|
|
1470
|
-
return String(value);
|
|
1471
|
-
}
|
|
1472
|
-
return JSON.stringify(value);
|
|
1473
|
-
}
|
|
1474
|
-
// ===========================================================================
|
|
1475
|
-
// IDENTIFIER EXPRESSIONS
|
|
1476
|
-
// ===========================================================================
|
|
1477
|
-
generateIdentifier(node) {
|
|
1478
|
-
const name = node.value ?? node.name ?? "";
|
|
1479
|
-
switch (name) {
|
|
1480
|
-
case "me":
|
|
1481
|
-
case "my":
|
|
1482
|
-
return "_ctx.me";
|
|
1483
|
-
case "you":
|
|
1484
|
-
case "your":
|
|
1485
|
-
return "_ctx.you";
|
|
1486
|
-
case "it":
|
|
1487
|
-
case "result":
|
|
1488
|
-
return "_ctx.it";
|
|
1489
|
-
case "event":
|
|
1490
|
-
return "_ctx.event";
|
|
1491
|
-
case "body":
|
|
1492
|
-
return "document.body";
|
|
1493
|
-
case "document":
|
|
1494
|
-
return "document";
|
|
1495
|
-
case "window":
|
|
1496
|
-
return "window";
|
|
1497
|
-
case "true":
|
|
1498
|
-
return "true";
|
|
1499
|
-
case "false":
|
|
1500
|
-
return "false";
|
|
1501
|
-
case "null":
|
|
1502
|
-
return "null";
|
|
1503
|
-
case "undefined":
|
|
1504
|
-
return "undefined";
|
|
1505
|
-
default:
|
|
1506
|
-
if (name.startsWith(":")) {
|
|
1507
|
-
const varName = sanitizeIdentifier(name.slice(1));
|
|
1508
|
-
return `_ctx.locals.get('${varName}')`;
|
|
1509
|
-
}
|
|
1510
|
-
if (name.startsWith("$") || name.startsWith("::")) {
|
|
1511
|
-
const varName = sanitizeIdentifier(name.replace(/^(\$|::)/, ""));
|
|
1512
|
-
this.ctx.requireHelper("globals");
|
|
1513
|
-
return `_rt.globals.get('${varName}')`;
|
|
1514
|
-
}
|
|
1515
|
-
if (this.ctx.analysis?.variables?.locals?.get(name)?.scope === "local") {
|
|
1516
|
-
return `_ctx.locals.get('${sanitizeIdentifier(name)}')`;
|
|
1517
|
-
}
|
|
1518
|
-
return sanitizeIdentifier(name);
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
// ===========================================================================
|
|
1522
|
-
// SELECTOR EXPRESSIONS
|
|
1523
|
-
// ===========================================================================
|
|
1524
|
-
generateSelector(node) {
|
|
1525
|
-
const selector = node.value;
|
|
1526
|
-
const sanitized = sanitizeSelector(selector);
|
|
1527
|
-
if (this.ctx.canCacheSelector(selector)) {
|
|
1528
|
-
return this.ctx.getCachedSelector(selector);
|
|
1529
|
-
}
|
|
1530
|
-
if (selector.startsWith("#") && !selector.includes(" ") && !selector.includes(".")) {
|
|
1531
|
-
const id = selector.slice(1);
|
|
1532
|
-
return `document.getElementById('${sanitizeSelector(id)}')`;
|
|
1533
|
-
}
|
|
1534
|
-
return `document.querySelector('${sanitized}')`;
|
|
1535
|
-
}
|
|
1536
|
-
// ===========================================================================
|
|
1537
|
-
// VARIABLE EXPRESSIONS
|
|
1538
|
-
// ===========================================================================
|
|
1539
|
-
generateVariable(node) {
|
|
1540
|
-
const name = node.name;
|
|
1541
|
-
const varName = sanitizeIdentifier(
|
|
1542
|
-
name.startsWith(":") || name.startsWith("$") ? name.slice(1) : name
|
|
1543
|
-
);
|
|
1544
|
-
switch (node.scope) {
|
|
1545
|
-
case "local":
|
|
1546
|
-
return `_ctx.locals.get('${varName}')`;
|
|
1547
|
-
case "global":
|
|
1548
|
-
this.ctx.requireHelper("globals");
|
|
1549
|
-
return `_rt.globals.get('${varName}')`;
|
|
1550
|
-
case "element":
|
|
1551
|
-
this.ctx.requireHelper("elementVars");
|
|
1552
|
-
return `_rt.elementVars(_ctx.me).get('${varName}')`;
|
|
1553
|
-
default:
|
|
1554
|
-
return `_ctx.locals.get('${varName}')`;
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
// ===========================================================================
|
|
1558
|
-
// BINARY EXPRESSIONS
|
|
1559
|
-
// ===========================================================================
|
|
1560
|
-
generateBinary(node) {
|
|
1561
|
-
const left = this.generate(node.left);
|
|
1562
|
-
const right = this.generate(node.right);
|
|
1563
|
-
const op = node.operator;
|
|
1564
|
-
switch (op) {
|
|
1565
|
-
// Equality, with core's phrase forms of each (`n is equal to 3`).
|
|
1566
|
-
case "is":
|
|
1567
|
-
case "==":
|
|
1568
|
-
case "am":
|
|
1569
|
-
case "equals":
|
|
1570
|
-
case "is equal":
|
|
1571
|
-
case "is equal to":
|
|
1572
|
-
return `(${left} === ${right})`;
|
|
1573
|
-
case "is not":
|
|
1574
|
-
case "!=":
|
|
1575
|
-
case "is not equal":
|
|
1576
|
-
case "is not equal to":
|
|
1577
|
-
return `(${left} !== ${right})`;
|
|
1578
|
-
case "really equals":
|
|
1579
|
-
case "is really":
|
|
1580
|
-
case "is really equal to":
|
|
1581
|
-
return `(${left} === ${right})`;
|
|
1582
|
-
case "is not really":
|
|
1583
|
-
case "is not really equal to":
|
|
1584
|
-
return `(${left} !== ${right})`;
|
|
1585
|
-
// Comparison. Left as is, `(n is greater than 2)` is not JavaScript.
|
|
1586
|
-
case "<":
|
|
1587
|
-
case ">":
|
|
1588
|
-
case "<=":
|
|
1589
|
-
case ">=":
|
|
1590
|
-
return `(${left} ${op} ${right})`;
|
|
1591
|
-
case "is less than":
|
|
1592
|
-
return `(${left} < ${right})`;
|
|
1593
|
-
case "is greater than":
|
|
1594
|
-
return `(${left} > ${right})`;
|
|
1595
|
-
case "is less than or equal to":
|
|
1596
|
-
return `(${left} <= ${right})`;
|
|
1597
|
-
case "is greater than or equal to":
|
|
1598
|
-
return `(${left} >= ${right})`;
|
|
1599
|
-
// Arithmetic
|
|
1600
|
-
case "+":
|
|
1601
|
-
case "-":
|
|
1602
|
-
case "*":
|
|
1603
|
-
case "/":
|
|
1604
|
-
case "%":
|
|
1605
|
-
return `(${left} ${op} ${right})`;
|
|
1606
|
-
// Hyperscript's word for `%`; left as is, `(n mod 2)` is not JavaScript.
|
|
1607
|
-
case "mod":
|
|
1608
|
-
return `(${left} % ${right})`;
|
|
1609
|
-
// Logical
|
|
1610
|
-
case "and":
|
|
1611
|
-
case "&&":
|
|
1612
|
-
return `(${left} && ${right})`;
|
|
1613
|
-
case "or":
|
|
1614
|
-
case "||":
|
|
1615
|
-
return `(${left} || ${right})`;
|
|
1616
|
-
// String/collection operators
|
|
1617
|
-
case "contains":
|
|
1618
|
-
case "contain":
|
|
1619
|
-
case "includes":
|
|
1620
|
-
case "include":
|
|
1621
|
-
this.ctx.requireHelper("contains");
|
|
1622
|
-
return `_rt.contains(${left}, ${right})`;
|
|
1623
|
-
case "does not contain":
|
|
1624
|
-
case "do not contain":
|
|
1625
|
-
case "does not contains":
|
|
1626
|
-
case "does not include":
|
|
1627
|
-
this.ctx.requireHelper("contains");
|
|
1628
|
-
return `!_rt.contains(${left}, ${right})`;
|
|
1629
|
-
// `matches` tests a selector, so a selector operand stays a string: the
|
|
1630
|
-
// element it would query is not a selector (`_rt.matches(el, el)`).
|
|
1631
|
-
case "matches":
|
|
1632
|
-
case "match":
|
|
1633
|
-
this.ctx.requireHelper("matches");
|
|
1634
|
-
return `_rt.matches(${left}, ${this.selectorText(node.right, right)})`;
|
|
1635
|
-
case "does not match":
|
|
1636
|
-
case "do not match":
|
|
1637
|
-
this.ctx.requireHelper("matches");
|
|
1638
|
-
return `!_rt.matches(${left}, ${this.selectorText(node.right, right)})`;
|
|
1639
|
-
// Membership, `n is in [1, 3]`: the right side contains the left.
|
|
1640
|
-
case "is in":
|
|
1641
|
-
case "am in":
|
|
1642
|
-
this.ctx.requireHelper("contains");
|
|
1643
|
-
return `_rt.contains(${right}, ${left})`;
|
|
1644
|
-
case "is not in":
|
|
1645
|
-
case "am not in":
|
|
1646
|
-
this.ctx.requireHelper("contains");
|
|
1647
|
-
return `!_rt.contains(${right}, ${left})`;
|
|
1648
|
-
case "starts with":
|
|
1649
|
-
return `${left}.startsWith(${right})`;
|
|
1650
|
-
case "ends with":
|
|
1651
|
-
return `${left}.endsWith(${right})`;
|
|
1652
|
-
// A scoped query, `<li/> in #list`: the matches inside the scope, as
|
|
1653
|
-
// both engines read it. JavaScript's `in` (the default below) tests a
|
|
1654
|
-
// property KEY, so `(document.querySelector('li') in …)` matched nothing.
|
|
1655
|
-
case "in":
|
|
1656
|
-
if (node.left.type === "selector") {
|
|
1657
|
-
const query = sanitizeSelector(node.left.value);
|
|
1658
|
-
return `${right}.querySelectorAll('${query}')`;
|
|
1659
|
-
}
|
|
1660
|
-
return `(${left} ${op} ${right})`;
|
|
1661
|
-
// Class check
|
|
1662
|
-
case "has":
|
|
1663
|
-
if (node.right.type === "selector") {
|
|
1664
|
-
const sel = node.right.value;
|
|
1665
|
-
if (sel.startsWith(".")) {
|
|
1666
|
-
const className = sanitizeClassName(sel.slice(1));
|
|
1667
|
-
if (className) {
|
|
1668
|
-
return `${left}.classList.contains('${className}')`;
|
|
1669
|
-
}
|
|
1670
|
-
}
|
|
1671
|
-
}
|
|
1672
|
-
this.ctx.requireHelper("contains");
|
|
1673
|
-
return `_rt.contains(${left}, ${right})`;
|
|
1674
|
-
// Type check
|
|
1675
|
-
case "is a":
|
|
1676
|
-
case "is an":
|
|
1677
|
-
return `(typeof ${left} === ${right} || ${left} instanceof ${right})`;
|
|
1678
|
-
case "is not a":
|
|
1679
|
-
case "is not an":
|
|
1680
|
-
return `(typeof ${left} !== ${right} && !(${left} instanceof ${right}))`;
|
|
1681
|
-
// Concatenation
|
|
1682
|
-
case "&":
|
|
1683
|
-
return `(String(${left}) + String(${right}))`;
|
|
1684
|
-
default:
|
|
1685
|
-
return `(${left} ${op} ${right})`;
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
/** A selector operand as its quoted text; any other operand as generated. */
|
|
1689
|
-
selectorText(node, generated) {
|
|
1690
|
-
return node.type === "selector" ? `'${sanitizeSelector(node.value)}'` : generated;
|
|
1691
|
-
}
|
|
1692
|
-
// ===========================================================================
|
|
1693
|
-
// MEMBER EXPRESSIONS
|
|
1694
|
-
// ===========================================================================
|
|
1695
|
-
generateMember(node) {
|
|
1696
|
-
const object = this.generate(node.object);
|
|
1697
|
-
const property = node.property;
|
|
1698
|
-
const prop = typeof property === "string" ? property : !node.computed && property.type === "identifier" ? property.value ?? property.name : void 0;
|
|
1699
|
-
if (prop !== void 0) {
|
|
1700
|
-
if (prop.startsWith("*")) {
|
|
1701
|
-
const styleProp = prop.slice(1);
|
|
1702
|
-
return `${object}.style.${styleProp}`;
|
|
1703
|
-
}
|
|
1704
|
-
if (prop.startsWith("@")) {
|
|
1705
|
-
const attrName = prop.slice(1);
|
|
1706
|
-
return `${object}.getAttribute('${sanitizeSelector(attrName)}')`;
|
|
1707
|
-
}
|
|
1708
|
-
if (node.computed) {
|
|
1709
|
-
return `${object}[${JSON.stringify(prop)}]`;
|
|
1710
|
-
}
|
|
1711
|
-
return `${object}.${sanitizeIdentifier(prop)}`;
|
|
1712
|
-
}
|
|
1713
|
-
const propExpr = this.generate(node.property);
|
|
1714
|
-
return `${object}[${propExpr}]`;
|
|
1715
|
-
}
|
|
1716
|
-
// ===========================================================================
|
|
1717
|
-
// POSSESSIVE EXPRESSIONS
|
|
1718
|
-
// ===========================================================================
|
|
1719
|
-
generatePossessive(node) {
|
|
1720
|
-
const object = this.generate(node.object);
|
|
1721
|
-
const property = node.property;
|
|
1722
|
-
if (property.startsWith("*")) {
|
|
1723
|
-
const styleProp = property.slice(1);
|
|
1724
|
-
return `${object}.style.${styleProp}`;
|
|
1725
|
-
}
|
|
1726
|
-
if (property.startsWith("@")) {
|
|
1727
|
-
const attrName = property.slice(1);
|
|
1728
|
-
return `${object}.getAttribute('${sanitizeSelector(attrName)}')`;
|
|
1729
|
-
}
|
|
1730
|
-
if (property === "values") {
|
|
1731
|
-
this.ctx.requireHelper("getValues");
|
|
1732
|
-
return `_rt.getValues(${object})`;
|
|
1733
|
-
}
|
|
1734
|
-
const domProps = [
|
|
1735
|
-
"value",
|
|
1736
|
-
"textContent",
|
|
1737
|
-
"innerHTML",
|
|
1738
|
-
"innerText",
|
|
1739
|
-
"outerHTML",
|
|
1740
|
-
"checked",
|
|
1741
|
-
"disabled",
|
|
1742
|
-
"selected",
|
|
1743
|
-
"hidden",
|
|
1744
|
-
"src",
|
|
1745
|
-
"href",
|
|
1746
|
-
"id",
|
|
1747
|
-
"className",
|
|
1748
|
-
"classList",
|
|
1749
|
-
"parentElement",
|
|
1750
|
-
"parentNode",
|
|
1751
|
-
"children",
|
|
1752
|
-
"firstChild",
|
|
1753
|
-
"lastChild",
|
|
1754
|
-
"nextSibling",
|
|
1755
|
-
"previousSibling",
|
|
1756
|
-
"nextElementSibling",
|
|
1757
|
-
"previousElementSibling",
|
|
1758
|
-
"offsetWidth",
|
|
1759
|
-
"offsetHeight",
|
|
1760
|
-
"offsetTop",
|
|
1761
|
-
"offsetLeft",
|
|
1762
|
-
"clientWidth",
|
|
1763
|
-
"clientHeight",
|
|
1764
|
-
"scrollWidth",
|
|
1765
|
-
"scrollHeight",
|
|
1766
|
-
"scrollTop",
|
|
1767
|
-
"scrollLeft"
|
|
1768
|
-
];
|
|
1769
|
-
if (domProps.includes(property)) {
|
|
1770
|
-
return `${object}.${property}`;
|
|
1771
|
-
}
|
|
1772
|
-
this.ctx.requireHelper("getProp");
|
|
1773
|
-
return `_rt.getProp(${object}, '${sanitizeSelector(property)}')`;
|
|
1774
|
-
}
|
|
1775
|
-
// ===========================================================================
|
|
1776
|
-
// CALL EXPRESSIONS
|
|
1777
|
-
// ===========================================================================
|
|
1778
|
-
generateCall(node) {
|
|
1779
|
-
const args = (node.args ?? []).map((arg) => this.generate(arg)).join(", ");
|
|
1780
|
-
if (node.callee.type === "member" || node.callee.type === "possessive") {
|
|
1781
|
-
const memberNode = node.callee;
|
|
1782
|
-
const object = this.generate(memberNode.object);
|
|
1783
|
-
const method = typeof memberNode.property === "string" ? memberNode.property : this.generate(memberNode.property);
|
|
1784
|
-
if (typeof memberNode.property === "string") {
|
|
1785
|
-
return `${object}.${sanitizeIdentifier(memberNode.property)}(${args})`;
|
|
1786
|
-
}
|
|
1787
|
-
return `${object}[${method}](${args})`;
|
|
1788
|
-
}
|
|
1789
|
-
const callee = this.generate(node.callee);
|
|
1790
|
-
return `${callee}(${args})`;
|
|
1791
|
-
}
|
|
1792
|
-
// ===========================================================================
|
|
1793
|
-
// POSITIONAL EXPRESSIONS
|
|
1794
|
-
// ===========================================================================
|
|
1795
|
-
generatePositional(node) {
|
|
1796
|
-
const position = node.position;
|
|
1797
|
-
const target = node.target ? this.generate(node.target) : null;
|
|
1798
|
-
switch (position) {
|
|
1799
|
-
case "first":
|
|
1800
|
-
if (target) {
|
|
1801
|
-
if (node.target?.type === "selector") {
|
|
1802
|
-
const sel = node.target.value;
|
|
1803
|
-
return `document.querySelector('${sanitizeSelector(sel)}')`;
|
|
1804
|
-
}
|
|
1805
|
-
this.ctx.requireHelper("first");
|
|
1806
|
-
return `_rt.first(${target})`;
|
|
1807
|
-
}
|
|
1808
|
-
return "_ctx.me";
|
|
1809
|
-
case "last":
|
|
1810
|
-
if (target) {
|
|
1811
|
-
if (node.target?.type === "selector") {
|
|
1812
|
-
const sel = node.target.value;
|
|
1813
|
-
const sanitized = sanitizeSelector(sel);
|
|
1814
|
-
return `(()=>{const _els=document.querySelectorAll('${sanitized}');return _els[_els.length-1]})()`;
|
|
1815
|
-
}
|
|
1816
|
-
this.ctx.requireHelper("last");
|
|
1817
|
-
return `_rt.last(${target})`;
|
|
1818
|
-
}
|
|
1819
|
-
return "_ctx.me";
|
|
1820
|
-
case "random":
|
|
1821
|
-
if (target) {
|
|
1822
|
-
this.ctx.requireHelper("random");
|
|
1823
|
-
return `_rt.random(${target})`;
|
|
1824
|
-
}
|
|
1825
|
-
return "_ctx.me";
|
|
1826
|
-
case "next":
|
|
1827
|
-
return "_ctx.me.nextElementSibling";
|
|
1828
|
-
case "previous":
|
|
1829
|
-
return "_ctx.me.previousElementSibling";
|
|
1830
|
-
case "closest":
|
|
1831
|
-
if (target && node.target?.type === "selector") {
|
|
1832
|
-
const sel = node.target.value;
|
|
1833
|
-
return `_ctx.me.closest('${sanitizeSelector(sel)}')`;
|
|
1834
|
-
}
|
|
1835
|
-
if (target) {
|
|
1836
|
-
return `_ctx.me.closest(${target})`;
|
|
1837
|
-
}
|
|
1838
|
-
return "_ctx.me.parentElement";
|
|
1839
|
-
case "parent":
|
|
1840
|
-
return "_ctx.me.parentElement";
|
|
1841
|
-
default:
|
|
1842
|
-
throw new Error(`Unknown positional: ${position}`);
|
|
1843
|
-
}
|
|
1844
|
-
}
|
|
1845
|
-
// ===========================================================================
|
|
1846
|
-
// ARRAY EXPRESSIONS
|
|
1847
|
-
// ===========================================================================
|
|
1848
|
-
generateArray(node) {
|
|
1849
|
-
const elements = node.elements ?? [];
|
|
1850
|
-
const items = elements.map((el) => this.generate(el)).join(", ");
|
|
1851
|
-
return `[${items}]`;
|
|
1852
|
-
}
|
|
1853
|
-
// ===========================================================================
|
|
1854
|
-
// OBJECT EXPRESSIONS
|
|
1855
|
-
// ===========================================================================
|
|
1856
|
-
generateObject(node) {
|
|
1857
|
-
const properties = node.properties ?? [];
|
|
1858
|
-
const pairs = properties.map((prop) => {
|
|
1859
|
-
const key = typeof prop.key === "string" ? JSON.stringify(prop.key) : this.generate(prop.key);
|
|
1860
|
-
const value = this.generate(prop.value);
|
|
1861
|
-
return `${key}: ${value}`;
|
|
1862
|
-
}).join(", ");
|
|
1863
|
-
return `{${pairs}}`;
|
|
1864
|
-
}
|
|
1865
|
-
// ===========================================================================
|
|
1866
|
-
// TEMPLATE EXPRESSIONS
|
|
1867
|
-
// ===========================================================================
|
|
1868
|
-
generateTemplate(node) {
|
|
1869
|
-
const parts = node.parts ?? [];
|
|
1870
|
-
const segments = parts.map((part) => {
|
|
1871
|
-
if (typeof part === "string") {
|
|
1872
|
-
return part.replace(/`/g, "\\`").replace(/\$/g, "\\$");
|
|
1873
|
-
}
|
|
1874
|
-
return "${" + this.generate(part) + "}";
|
|
1875
|
-
}).join("");
|
|
1876
|
-
return "`" + segments + "`";
|
|
1877
|
-
}
|
|
1878
|
-
// ===========================================================================
|
|
1879
|
-
// UNARY EXPRESSIONS
|
|
1880
|
-
// ===========================================================================
|
|
1881
|
-
/** `x is a Number`, as both engines test it: see the runtime's `typeCheck`. */
|
|
1882
|
-
generateTypeCheck(node) {
|
|
1883
|
-
this.ctx.requireHelper("typeCheck");
|
|
1884
|
-
const test = `_rt.typeCheck(${this.generate(node.value)}, ${JSON.stringify(node.typeName)}, ${node.nullOk})`;
|
|
1885
|
-
return node.negated ? `!${test}` : test;
|
|
1886
|
-
}
|
|
1887
|
-
generateUnary(node) {
|
|
1888
|
-
const operand = this.generate(node.operand);
|
|
1889
|
-
switch (node.operator) {
|
|
1890
|
-
case "not":
|
|
1891
|
-
case "!":
|
|
1892
|
-
return `!${operand}`;
|
|
1893
|
-
case "-":
|
|
1894
|
-
return `-${operand}`;
|
|
1895
|
-
case "+":
|
|
1896
|
-
return `+${operand}`;
|
|
1897
|
-
case "no":
|
|
1898
|
-
return `!${operand}`;
|
|
1899
|
-
// Core's postfix tests. Left as is, `exists#d1` is not JavaScript.
|
|
1900
|
-
case "exists":
|
|
1901
|
-
case "some":
|
|
1902
|
-
this.ctx.requireHelper("exists");
|
|
1903
|
-
return `_rt.exists(${operand})`;
|
|
1904
|
-
case "does not exist":
|
|
1905
|
-
this.ctx.requireHelper("exists");
|
|
1906
|
-
return `!_rt.exists(${operand})`;
|
|
1907
|
-
case "is empty":
|
|
1908
|
-
this.ctx.requireHelper("isEmpty");
|
|
1909
|
-
return `_rt.isEmpty(${operand})`;
|
|
1910
|
-
case "is not empty":
|
|
1911
|
-
this.ctx.requireHelper("isEmpty");
|
|
1912
|
-
return `!_rt.isEmpty(${operand})`;
|
|
1913
|
-
case "beep!":
|
|
1914
|
-
return `((v) => (console.log('%c[beep]', 'color: orange; font-weight: bold', v), v))(${operand})`;
|
|
1915
|
-
default:
|
|
1916
|
-
return `${node.operator}${operand}`;
|
|
1917
|
-
}
|
|
1918
|
-
}
|
|
1919
|
-
// ===========================================================================
|
|
1920
|
-
// CONDITIONAL EXPRESSIONS
|
|
1921
|
-
// ===========================================================================
|
|
1922
|
-
generateConditional(node) {
|
|
1923
|
-
const condition = this.generate(node.condition);
|
|
1924
|
-
const consequent = this.generate(node.consequent);
|
|
1925
|
-
const alternate = this.generate(node.alternate);
|
|
1926
|
-
return `(${condition} ? ${consequent} : ${alternate})`;
|
|
1927
|
-
}
|
|
1928
|
-
};
|
|
1929
|
-
function generateExpression(node, ctx) {
|
|
1930
|
-
const codegen = new ExpressionCodegen(ctx);
|
|
1931
|
-
return codegen.generate(node);
|
|
1932
|
-
}
|
|
1933
|
-
var isNode = (value) => typeof value === "object" && value !== null && "type" in value;
|
|
1934
|
-
function commandTarget(node, ...slots) {
|
|
1935
|
-
if (node.target) return node.target;
|
|
1936
|
-
for (const slot of slots) {
|
|
1937
|
-
const found = slot === "arg" ? node.args?.[0] : node.modifiers?.[slot];
|
|
1938
|
-
if (isNode(found)) return found;
|
|
1939
|
-
}
|
|
1940
|
-
return void 0;
|
|
1941
|
-
}
|
|
1942
|
-
function isScopedQuery(node) {
|
|
1943
|
-
const binary = node;
|
|
1944
|
-
return binary?.type === "binary" && binary.operator === "in" && binary.left.type === "selector";
|
|
1945
|
-
}
|
|
1946
|
-
function allMatches(node, ctx) {
|
|
1947
|
-
if (isScopedQuery(node)) return ctx.generateExpression(node);
|
|
1948
|
-
if (node?.type !== "selector") return null;
|
|
1949
|
-
const selector = node.value;
|
|
1950
|
-
return /^#[\w-]+$/.test(selector) ? null : `document.querySelectorAll('${sanitizeSelector(selector)}')`;
|
|
1951
|
-
}
|
|
1952
|
-
function forEachTarget(node, ctx, op) {
|
|
1953
|
-
const all = allMatches(node, ctx);
|
|
1954
|
-
if (all) return `${all}.forEach(el => ${op("el")})`;
|
|
1955
|
-
return op(node ? ctx.generateExpression(node) : "_ctx.me");
|
|
1956
|
-
}
|
|
1957
|
-
var ToggleCodegen = class {
|
|
1958
|
-
constructor() {
|
|
1959
|
-
this.command = "toggle";
|
|
1960
|
-
}
|
|
1961
|
-
generate(node, ctx) {
|
|
1962
|
-
const args = node.args ?? [];
|
|
1963
|
-
if (args.length === 0) return null;
|
|
1964
|
-
const targetNode = commandTarget(node, "on");
|
|
1965
|
-
const arg = args[0];
|
|
1966
|
-
if (arg.type === "selector") {
|
|
1967
|
-
const selector = arg.value;
|
|
1968
|
-
if (selector.startsWith(".")) {
|
|
1969
|
-
const className = sanitizeClassName(selector.slice(1));
|
|
1970
|
-
if (!className) return null;
|
|
1971
|
-
return {
|
|
1972
|
-
code: forEachTarget(targetNode, ctx, (el) => `${el}.classList.toggle('${className}')`),
|
|
1973
|
-
async: false,
|
|
1974
|
-
sideEffects: true
|
|
1975
|
-
};
|
|
1976
|
-
}
|
|
1977
|
-
}
|
|
1978
|
-
if (arg.type === "identifier") {
|
|
1979
|
-
const value = arg.value ?? "";
|
|
1980
|
-
if (value.startsWith("@")) {
|
|
1981
|
-
const attrName = value.slice(1);
|
|
1982
|
-
ctx.requireHelper("toggleAttr");
|
|
1983
|
-
return {
|
|
1984
|
-
code: forEachTarget(
|
|
1985
|
-
targetNode,
|
|
1986
|
-
ctx,
|
|
1987
|
-
(el) => `_rt.toggleAttr(${el}, '${sanitizeSelector(attrName)}')`
|
|
1988
|
-
),
|
|
1989
|
-
async: false,
|
|
1990
|
-
sideEffects: true
|
|
1991
|
-
};
|
|
1992
|
-
}
|
|
1993
|
-
}
|
|
1994
|
-
ctx.requireHelper("toggle");
|
|
1995
|
-
return {
|
|
1996
|
-
code: forEachTarget(
|
|
1997
|
-
targetNode,
|
|
1998
|
-
ctx,
|
|
1999
|
-
(el) => `_rt.toggle(${ctx.generateExpression(arg)}, ${el})`
|
|
2000
|
-
),
|
|
2001
|
-
async: false,
|
|
2002
|
-
sideEffects: true
|
|
2003
|
-
};
|
|
2004
|
-
}
|
|
2005
|
-
};
|
|
2006
|
-
var AddCodegen = class {
|
|
2007
|
-
constructor() {
|
|
2008
|
-
this.command = "add";
|
|
2009
|
-
}
|
|
2010
|
-
generate(node, ctx) {
|
|
2011
|
-
const args = node.args ?? [];
|
|
2012
|
-
if (args.length === 0) return null;
|
|
2013
|
-
const targetSlot = node.target ?? node.modifiers?.to;
|
|
2014
|
-
const target = targetSlot ? ctx.generateExpression(targetSlot) : "_ctx.me";
|
|
2015
|
-
const arg = args[0];
|
|
2016
|
-
if (arg.type === "selector") {
|
|
2017
|
-
const selector = arg.value;
|
|
2018
|
-
if (selector.startsWith(".")) {
|
|
2019
|
-
const className = sanitizeClassName(selector.slice(1));
|
|
2020
|
-
if (!className) return null;
|
|
2021
|
-
return {
|
|
2022
|
-
code: forEachTarget(targetSlot, ctx, (el) => `${el}.classList.add('${className}')`),
|
|
2023
|
-
async: false,
|
|
2024
|
-
sideEffects: true
|
|
2025
|
-
};
|
|
2026
|
-
}
|
|
2027
|
-
}
|
|
2028
|
-
if (arg.type === "htmlLiteral") {
|
|
2029
|
-
const tagNode = arg;
|
|
2030
|
-
const tag = tagNode.tag ?? "div";
|
|
2031
|
-
const classes = tagNode.classes ?? [];
|
|
2032
|
-
const id = tagNode.id;
|
|
2033
|
-
let code = `(() => { const _el = document.createElement('${tag}');`;
|
|
2034
|
-
if (classes.length > 0) {
|
|
2035
|
-
code += ` _el.className = '${classes.join(" ")}';`;
|
|
2036
|
-
}
|
|
2037
|
-
if (id) {
|
|
2038
|
-
code += ` _el.id = '${id}';`;
|
|
2039
|
-
}
|
|
2040
|
-
code += ` ${target}.appendChild(_el); return _el; })()`;
|
|
2041
|
-
return { code, async: false, sideEffects: true };
|
|
2042
|
-
}
|
|
2043
|
-
ctx.requireHelper("addClass");
|
|
2044
|
-
return {
|
|
2045
|
-
code: forEachTarget(
|
|
2046
|
-
targetSlot,
|
|
2047
|
-
ctx,
|
|
2048
|
-
(el) => `_rt.addClass(${el}, ${ctx.generateExpression(arg)})`
|
|
2049
|
-
),
|
|
2050
|
-
async: false,
|
|
2051
|
-
sideEffects: true
|
|
2052
|
-
};
|
|
2053
|
-
}
|
|
2054
|
-
};
|
|
2055
|
-
var RemoveCodegen = class {
|
|
2056
|
-
constructor() {
|
|
2057
|
-
this.command = "remove";
|
|
2058
|
-
}
|
|
2059
|
-
generate(node, ctx) {
|
|
2060
|
-
const args = node.args ?? [];
|
|
2061
|
-
if (args.length === 0) {
|
|
2062
|
-
const targetSlot2 = node.target ?? node.modifiers?.from;
|
|
2063
|
-
return {
|
|
2064
|
-
code: forEachTarget(targetSlot2, ctx, (el) => `${el}.remove()`),
|
|
2065
|
-
async: false,
|
|
2066
|
-
sideEffects: true
|
|
2067
|
-
};
|
|
2068
|
-
}
|
|
2069
|
-
const targetSlot = node.target ?? node.modifiers?.from;
|
|
2070
|
-
const arg = args[0];
|
|
2071
|
-
const isElement = arg.type === "selector" ? !/^[.@]/.test(arg.value) : arg.type === "identifier" && !String(arg.value ?? "").startsWith("@") || arg.type === "variable" || isScopedQuery(arg);
|
|
2072
|
-
if (!targetSlot && isElement) {
|
|
2073
|
-
return {
|
|
2074
|
-
code: forEachTarget(arg, ctx, (el) => `${el}.remove()`),
|
|
2075
|
-
async: false,
|
|
2076
|
-
sideEffects: true
|
|
2077
|
-
};
|
|
2078
|
-
}
|
|
2079
|
-
if (arg.type === "selector") {
|
|
2080
|
-
const selector = arg.value;
|
|
2081
|
-
if (selector.startsWith(".")) {
|
|
2082
|
-
const className = sanitizeClassName(selector.slice(1));
|
|
2083
|
-
if (!className) return null;
|
|
2084
|
-
return {
|
|
2085
|
-
code: forEachTarget(targetSlot, ctx, (el) => `${el}.classList.remove('${className}')`),
|
|
2086
|
-
async: false,
|
|
2087
|
-
sideEffects: true
|
|
2088
|
-
};
|
|
2089
|
-
}
|
|
2090
|
-
}
|
|
2091
|
-
ctx.requireHelper("removeClass");
|
|
2092
|
-
return {
|
|
2093
|
-
code: forEachTarget(
|
|
2094
|
-
targetSlot,
|
|
2095
|
-
ctx,
|
|
2096
|
-
(el) => `_rt.removeClass(${el}, ${ctx.generateExpression(arg)})`
|
|
2097
|
-
),
|
|
2098
|
-
async: false,
|
|
2099
|
-
sideEffects: true
|
|
2100
|
-
};
|
|
2101
|
-
}
|
|
2102
|
-
};
|
|
2103
|
-
function variableStore(node, ctx) {
|
|
2104
|
-
let name;
|
|
2105
|
-
let scope;
|
|
2106
|
-
if (node.type === "variable") {
|
|
2107
|
-
name = node.name;
|
|
2108
|
-
scope = node.scope;
|
|
2109
|
-
} else if (node.type === "identifier") {
|
|
2110
|
-
name = node.value ?? node.name ?? "";
|
|
2111
|
-
if (/^(\$|::)/.test(name)) scope = "global";
|
|
2112
|
-
else if (/^[A-Za-z_]\w*$/.test(name)) scope = "local";
|
|
2113
|
-
else return null;
|
|
2114
|
-
} else {
|
|
2115
|
-
return null;
|
|
2116
|
-
}
|
|
2117
|
-
const key = sanitizeIdentifier(name.replace(/^(\$|::|:)/, ""));
|
|
2118
|
-
if (scope === "global") {
|
|
2119
|
-
ctx.requireHelper("globals");
|
|
2120
|
-
return { read: `_rt.globals.get('${key}')`, write: (v) => `_rt.globals.set('${key}', ${v})` };
|
|
2121
|
-
}
|
|
2122
|
-
if (scope === "element") {
|
|
2123
|
-
ctx.requireHelper("elementVars");
|
|
2124
|
-
const store = `_rt.elementVars(_ctx.me)`;
|
|
2125
|
-
return { read: `${store}.get('${key}')`, write: (v) => `${store}.set('${key}', ${v})` };
|
|
2126
|
-
}
|
|
2127
|
-
return { read: `_ctx.locals.get('${key}')`, write: (v) => `_ctx.locals.set('${key}', ${v})` };
|
|
2128
|
-
}
|
|
2129
|
-
var SetCodegen = class {
|
|
2130
|
-
constructor() {
|
|
2131
|
-
this.command = "set";
|
|
2132
|
-
}
|
|
2133
|
-
generate(node, ctx) {
|
|
2134
|
-
const args = node.args ?? [];
|
|
2135
|
-
const modifiers = node.modifiers;
|
|
2136
|
-
const roles = node.roles;
|
|
2137
|
-
const targetNode = roles?.destination ?? args[0];
|
|
2138
|
-
const valueNode = roles?.patient ?? modifiers?.to ?? args[1];
|
|
2139
|
-
if (!targetNode || !valueNode) return null;
|
|
2140
|
-
const value = ctx.generateExpression(valueNode);
|
|
2141
|
-
const store = variableStore(targetNode, ctx);
|
|
2142
|
-
if (store) return { code: store.write(value), async: false, sideEffects: true };
|
|
2143
|
-
if (targetNode.type === "possessive") {
|
|
2144
|
-
const possessive = targetNode;
|
|
2145
|
-
const obj = ctx.generateExpression(possessive.object);
|
|
2146
|
-
const prop = possessive.property;
|
|
2147
|
-
if (prop.startsWith("*")) {
|
|
2148
|
-
const styleProp = prop.slice(1);
|
|
2149
|
-
return {
|
|
2150
|
-
code: `${obj}.style.${styleProp} = ${value}`,
|
|
2151
|
-
async: false,
|
|
2152
|
-
sideEffects: true
|
|
2153
|
-
};
|
|
2154
|
-
}
|
|
2155
|
-
if (prop.startsWith("@")) {
|
|
2156
|
-
const attrName = prop.slice(1);
|
|
2157
|
-
return {
|
|
2158
|
-
code: `${obj}.setAttribute('${sanitizeSelector(attrName)}', ${value})`,
|
|
2159
|
-
async: false,
|
|
2160
|
-
sideEffects: true
|
|
2161
|
-
};
|
|
2162
|
-
}
|
|
2163
|
-
return {
|
|
2164
|
-
code: `${obj}.${sanitizeIdentifier(prop)} = ${value}`,
|
|
2165
|
-
async: false,
|
|
2166
|
-
sideEffects: true
|
|
2167
|
-
};
|
|
2168
|
-
}
|
|
2169
|
-
if (targetNode.type === "member") {
|
|
2170
|
-
const memberCode = ctx.generateExpression(targetNode);
|
|
2171
|
-
return {
|
|
2172
|
-
code: `${memberCode} = ${value}`,
|
|
2173
|
-
async: false,
|
|
2174
|
-
sideEffects: true
|
|
2175
|
-
};
|
|
2176
|
-
}
|
|
2177
|
-
return null;
|
|
2178
|
-
}
|
|
2179
|
-
};
|
|
2180
|
-
var PutCodegen = class {
|
|
2181
|
-
constructor() {
|
|
2182
|
-
this.command = "put";
|
|
2183
|
-
}
|
|
2184
|
-
generate(node, ctx) {
|
|
2185
|
-
const args = node.args ?? [];
|
|
2186
|
-
const roles = node.roles;
|
|
2187
|
-
const modifiers = node.modifiers;
|
|
2188
|
-
const contentNode = roles?.patient ?? args[0];
|
|
2189
|
-
if (!contentNode) return null;
|
|
2190
|
-
const content = ctx.generateExpression(contentNode);
|
|
2191
|
-
let targetNode = roles?.destination;
|
|
2192
|
-
if (!targetNode && modifiers) {
|
|
2193
|
-
for (const key of ["into", "before", "after"]) {
|
|
2194
|
-
if (modifiers[key] && typeof modifiers[key] === "object") {
|
|
2195
|
-
targetNode = modifiers[key];
|
|
2196
|
-
break;
|
|
2197
|
-
}
|
|
2198
|
-
}
|
|
2199
|
-
}
|
|
2200
|
-
targetNode ?? (targetNode = node.target);
|
|
2201
|
-
const into = (op) => forEachTarget(targetNode, ctx, op);
|
|
2202
|
-
let modifier = "into";
|
|
2203
|
-
if (roles?.method && roles.method.value) {
|
|
2204
|
-
modifier = String(roles.method.value);
|
|
2205
|
-
} else if (modifiers) {
|
|
2206
|
-
if (modifiers.before) modifier = "before";
|
|
2207
|
-
else if (modifiers.after) modifier = "after";
|
|
2208
|
-
else if (typeof modifiers.position === "string") modifier = modifiers.position;
|
|
2209
|
-
}
|
|
2210
|
-
switch (modifier) {
|
|
2211
|
-
case "into":
|
|
2212
|
-
return {
|
|
2213
|
-
code: into((el) => `${el}.innerHTML = ${content}`),
|
|
2214
|
-
async: false,
|
|
2215
|
-
sideEffects: true
|
|
2216
|
-
};
|
|
2217
|
-
case "before":
|
|
2218
|
-
return {
|
|
2219
|
-
code: into((el) => `${el}.insertAdjacentHTML('beforebegin', ${content})`),
|
|
2220
|
-
async: false,
|
|
2221
|
-
sideEffects: true
|
|
2222
|
-
};
|
|
2223
|
-
case "after":
|
|
2224
|
-
return {
|
|
2225
|
-
code: into((el) => `${el}.insertAdjacentHTML('afterend', ${content})`),
|
|
2226
|
-
async: false,
|
|
2227
|
-
sideEffects: true
|
|
2228
|
-
};
|
|
2229
|
-
case "at start of":
|
|
2230
|
-
case "start":
|
|
2231
|
-
return {
|
|
2232
|
-
code: into((el) => `${el}.insertAdjacentHTML('afterbegin', ${content})`),
|
|
2233
|
-
async: false,
|
|
2234
|
-
sideEffects: true
|
|
2235
|
-
};
|
|
2236
|
-
case "at end of":
|
|
2237
|
-
case "end":
|
|
2238
|
-
return {
|
|
2239
|
-
code: into((el) => `${el}.insertAdjacentHTML('beforeend', ${content})`),
|
|
2240
|
-
async: false,
|
|
2241
|
-
sideEffects: true
|
|
2242
|
-
};
|
|
2243
|
-
default:
|
|
2244
|
-
return {
|
|
2245
|
-
code: into((el) => `${el}.innerHTML = ${content}`),
|
|
2246
|
-
async: false,
|
|
2247
|
-
sideEffects: true
|
|
2248
|
-
};
|
|
2249
|
-
}
|
|
2250
|
-
}
|
|
2251
|
-
};
|
|
2252
|
-
var ShowCodegen = class {
|
|
2253
|
-
constructor() {
|
|
2254
|
-
this.command = "show";
|
|
2255
|
-
}
|
|
2256
|
-
generate(node, ctx) {
|
|
2257
|
-
const targetNode = commandTarget(node, "arg");
|
|
2258
|
-
return {
|
|
2259
|
-
code: forEachTarget(targetNode, ctx, (el) => `${el}.style.display = ''`),
|
|
2260
|
-
async: false,
|
|
2261
|
-
sideEffects: true
|
|
2262
|
-
};
|
|
2263
|
-
}
|
|
2264
|
-
};
|
|
2265
|
-
var HideCodegen = class {
|
|
2266
|
-
constructor() {
|
|
2267
|
-
this.command = "hide";
|
|
2268
|
-
}
|
|
2269
|
-
generate(node, ctx) {
|
|
2270
|
-
const targetNode = commandTarget(node, "arg");
|
|
2271
|
-
return {
|
|
2272
|
-
code: forEachTarget(targetNode, ctx, (el) => `${el}.style.display = 'none'`),
|
|
2273
|
-
async: false,
|
|
2274
|
-
sideEffects: true
|
|
2275
|
-
};
|
|
2276
|
-
}
|
|
2277
|
-
};
|
|
2278
|
-
var EmptyCodegen = class {
|
|
2279
|
-
constructor() {
|
|
2280
|
-
this.command = "empty";
|
|
2281
|
-
}
|
|
2282
|
-
generate(node, ctx) {
|
|
2283
|
-
const targetNode = commandTarget(node, "arg");
|
|
2284
|
-
return {
|
|
2285
|
-
code: forEachTarget(targetNode, ctx, (el) => `${el}.replaceChildren()`),
|
|
2286
|
-
async: false,
|
|
2287
|
-
sideEffects: true
|
|
2288
|
-
};
|
|
2289
|
-
}
|
|
2290
|
-
};
|
|
2291
|
-
var FocusCodegen = class {
|
|
2292
|
-
constructor() {
|
|
2293
|
-
this.command = "focus";
|
|
2294
|
-
}
|
|
2295
|
-
generate(node, ctx) {
|
|
2296
|
-
const targetNode = commandTarget(node, "arg");
|
|
2297
|
-
const target = targetNode ? ctx.generateExpression(targetNode) : "_ctx.me";
|
|
2298
|
-
return {
|
|
2299
|
-
code: `${target}.focus()`,
|
|
2300
|
-
async: false,
|
|
2301
|
-
sideEffects: true
|
|
2302
|
-
};
|
|
2303
|
-
}
|
|
2304
|
-
};
|
|
2305
|
-
var BlurCodegen = class {
|
|
2306
|
-
constructor() {
|
|
2307
|
-
this.command = "blur";
|
|
2308
|
-
}
|
|
2309
|
-
generate(node, ctx) {
|
|
2310
|
-
const targetNode = commandTarget(node, "arg");
|
|
2311
|
-
const target = targetNode ? ctx.generateExpression(targetNode) : "_ctx.me";
|
|
2312
|
-
return {
|
|
2313
|
-
code: `${target}.blur()`,
|
|
2314
|
-
async: false,
|
|
2315
|
-
sideEffects: true
|
|
2316
|
-
};
|
|
2317
|
-
}
|
|
2318
|
-
};
|
|
2319
|
-
var LogCodegen = class {
|
|
2320
|
-
constructor() {
|
|
2321
|
-
this.command = "log";
|
|
2322
|
-
}
|
|
2323
|
-
generate(node, ctx) {
|
|
2324
|
-
const args = node.args ?? [];
|
|
2325
|
-
const values = args.map((arg) => ctx.generateExpression(arg)).join(", ");
|
|
2326
|
-
return {
|
|
2327
|
-
code: `console.log(${values || "''"})`,
|
|
2328
|
-
async: false,
|
|
2329
|
-
sideEffects: true
|
|
2330
|
-
};
|
|
2331
|
-
}
|
|
2332
|
-
};
|
|
2333
|
-
var WaitCodegen = class {
|
|
2334
|
-
constructor() {
|
|
2335
|
-
this.command = "wait";
|
|
2336
|
-
}
|
|
2337
|
-
generate(node, ctx) {
|
|
2338
|
-
const args = node.args ?? [];
|
|
2339
|
-
const roles = node.roles;
|
|
2340
|
-
const arg = roles?.duration ?? args[0];
|
|
2341
|
-
if (!arg) return null;
|
|
2342
|
-
if (arg.type === "literal") {
|
|
2343
|
-
const value = arg.value;
|
|
2344
|
-
if (typeof value === "number") {
|
|
2345
|
-
ctx.requireHelper("wait");
|
|
2346
|
-
return {
|
|
2347
|
-
code: `await _rt.wait(${value})`,
|
|
2348
|
-
async: true,
|
|
2349
|
-
sideEffects: false
|
|
2350
|
-
};
|
|
2351
|
-
}
|
|
2352
|
-
if (typeof value === "string") {
|
|
2353
|
-
const ms = parseDuration(value);
|
|
2354
|
-
if (ms !== null) {
|
|
2355
|
-
ctx.requireHelper("wait");
|
|
2356
|
-
return {
|
|
2357
|
-
code: `await _rt.wait(${ms})`,
|
|
2358
|
-
async: true,
|
|
2359
|
-
sideEffects: false
|
|
2360
|
-
};
|
|
2361
|
-
}
|
|
2362
|
-
}
|
|
2363
|
-
}
|
|
2364
|
-
const duration = ctx.generateExpression(arg);
|
|
2365
|
-
ctx.requireHelper("wait");
|
|
2366
|
-
return {
|
|
2367
|
-
code: `await _rt.wait(${duration})`,
|
|
2368
|
-
async: true,
|
|
2369
|
-
sideEffects: false
|
|
2370
|
-
};
|
|
2371
|
-
}
|
|
2372
|
-
};
|
|
2373
|
-
var FetchCodegen = class {
|
|
2374
|
-
constructor() {
|
|
2375
|
-
this.command = "fetch";
|
|
2376
|
-
}
|
|
2377
|
-
generate(node, ctx) {
|
|
2378
|
-
const args = node.args ?? [];
|
|
2379
|
-
const roles = node.roles;
|
|
2380
|
-
const modifiers = node.modifiers;
|
|
2381
|
-
const urlNode = roles?.source ?? args[0];
|
|
2382
|
-
if (!urlNode) return null;
|
|
2383
|
-
const url = ctx.generateExpression(urlNode);
|
|
2384
|
-
let format = "text";
|
|
2385
|
-
if (roles?.responseType) {
|
|
2386
|
-
const rt = roles.responseType;
|
|
2387
|
-
format = String(rt.name ?? rt.value ?? "text");
|
|
2388
|
-
} else if (modifiers?.as) {
|
|
2389
|
-
const asVal = modifiers.as;
|
|
2390
|
-
if (typeof asVal === "string") {
|
|
2391
|
-
format = asVal;
|
|
2392
|
-
} else if (typeof asVal === "object" && asVal !== null) {
|
|
2393
|
-
format = String(
|
|
2394
|
-
asVal.name ?? asVal.value ?? "text"
|
|
2395
|
-
);
|
|
2396
|
-
}
|
|
2397
|
-
}
|
|
2398
|
-
switch (format) {
|
|
2399
|
-
case "json":
|
|
2400
|
-
ctx.requireHelper("fetchJSON");
|
|
2401
|
-
return {
|
|
2402
|
-
code: `_ctx.it = await _rt.fetchJSON(${url})`,
|
|
2403
|
-
async: true,
|
|
2404
|
-
sideEffects: true
|
|
2405
|
-
};
|
|
2406
|
-
case "html":
|
|
2407
|
-
ctx.requireHelper("fetchHTML");
|
|
2408
|
-
return {
|
|
2409
|
-
code: `_ctx.it = await _rt.fetchHTML(${url})`,
|
|
2410
|
-
async: true,
|
|
2411
|
-
sideEffects: true
|
|
2412
|
-
};
|
|
2413
|
-
case "text":
|
|
2414
|
-
default:
|
|
2415
|
-
ctx.requireHelper("fetchText");
|
|
2416
|
-
return {
|
|
2417
|
-
code: `_ctx.it = await _rt.fetchText(${url})`,
|
|
2418
|
-
async: true,
|
|
2419
|
-
sideEffects: true
|
|
2420
|
-
};
|
|
2421
|
-
}
|
|
2422
|
-
}
|
|
2423
|
-
};
|
|
2424
|
-
var SendCodegen = class {
|
|
2425
|
-
constructor() {
|
|
2426
|
-
this.command = "send";
|
|
2427
|
-
}
|
|
2428
|
-
generate(node, ctx) {
|
|
2429
|
-
const args = node.args ?? [];
|
|
2430
|
-
if (args.length === 0) return null;
|
|
2431
|
-
const eventName = ctx.generateExpression(args[0]);
|
|
2432
|
-
const targetNode = commandTarget(node, "on", "to");
|
|
2433
|
-
const detail = args.length > 1 ? ctx.generateExpression(args[1]) : "undefined";
|
|
2434
|
-
ctx.requireHelper("send");
|
|
2435
|
-
return {
|
|
2436
|
-
code: forEachTarget(targetNode, ctx, (el) => `_rt.send(${el}, ${eventName}, ${detail})`),
|
|
2437
|
-
async: false,
|
|
2438
|
-
sideEffects: true
|
|
2439
|
-
};
|
|
2440
|
-
}
|
|
2441
|
-
};
|
|
2442
|
-
var IncrementCodegen = class {
|
|
2443
|
-
constructor() {
|
|
2444
|
-
this.command = "increment";
|
|
2445
|
-
}
|
|
2446
|
-
generate(node, ctx) {
|
|
2447
|
-
const args = node.args ?? [];
|
|
2448
|
-
const roles = node.roles;
|
|
2449
|
-
const modifiers = node.modifiers;
|
|
2450
|
-
const target = roles?.destination ?? roles?.patient ?? args[0];
|
|
2451
|
-
if (!target) return null;
|
|
2452
|
-
const amountNode = roles?.quantity ?? modifiers?.by ?? args[1];
|
|
2453
|
-
const amount = amountNode ? ctx.generateExpression(amountNode) : "1";
|
|
2454
|
-
const store = variableStore(target, ctx);
|
|
2455
|
-
if (store) {
|
|
2456
|
-
const code = store.write(`(Number(${store.read}) || 0) + ${amount}`);
|
|
2457
|
-
return { code, async: false, sideEffects: true };
|
|
2458
|
-
}
|
|
2459
|
-
const targetCode = ctx.generateExpression(target);
|
|
2460
|
-
return {
|
|
2461
|
-
code: `${targetCode}.textContent = (parseFloat(${targetCode}.textContent) || 0) + ${amount}`,
|
|
2462
|
-
async: false,
|
|
2463
|
-
sideEffects: true
|
|
2464
|
-
};
|
|
2465
|
-
}
|
|
2466
|
-
};
|
|
2467
|
-
var DecrementCodegen = class {
|
|
2468
|
-
constructor() {
|
|
2469
|
-
this.command = "decrement";
|
|
2470
|
-
}
|
|
2471
|
-
generate(node, ctx) {
|
|
2472
|
-
const args = node.args ?? [];
|
|
2473
|
-
const roles = node.roles;
|
|
2474
|
-
const modifiers = node.modifiers;
|
|
2475
|
-
const target = roles?.destination ?? roles?.patient ?? args[0];
|
|
2476
|
-
if (!target) return null;
|
|
2477
|
-
const amountNode = roles?.quantity ?? modifiers?.by ?? args[1];
|
|
2478
|
-
const amount = amountNode ? ctx.generateExpression(amountNode) : "1";
|
|
2479
|
-
const store = variableStore(target, ctx);
|
|
2480
|
-
if (store) {
|
|
2481
|
-
const code = store.write(`(Number(${store.read}) || 0) - ${amount}`);
|
|
2482
|
-
return { code, async: false, sideEffects: true };
|
|
2483
|
-
}
|
|
2484
|
-
const targetCode = ctx.generateExpression(target);
|
|
2485
|
-
return {
|
|
2486
|
-
code: `${targetCode}.textContent = (parseFloat(${targetCode}.textContent) || 0) - ${amount}`,
|
|
2487
|
-
async: false,
|
|
2488
|
-
sideEffects: true
|
|
2489
|
-
};
|
|
2490
|
-
}
|
|
2491
|
-
};
|
|
2492
|
-
var HaltCodegen = class {
|
|
2493
|
-
constructor() {
|
|
2494
|
-
this.command = "halt";
|
|
2495
|
-
}
|
|
2496
|
-
generate(_node, ctx) {
|
|
2497
|
-
ctx.requireHelper("HALT");
|
|
2498
|
-
return {
|
|
2499
|
-
code: `throw _rt.HALT`,
|
|
2500
|
-
async: false,
|
|
2501
|
-
sideEffects: true
|
|
2502
|
-
};
|
|
2503
|
-
}
|
|
2504
|
-
};
|
|
2505
|
-
var ExitCodegen = class {
|
|
2506
|
-
constructor() {
|
|
2507
|
-
this.command = "exit";
|
|
2508
|
-
}
|
|
2509
|
-
generate(_node, ctx) {
|
|
2510
|
-
ctx.requireHelper("EXIT");
|
|
2511
|
-
return {
|
|
2512
|
-
code: `throw _rt.EXIT`,
|
|
2513
|
-
async: false,
|
|
2514
|
-
sideEffects: true
|
|
2515
|
-
};
|
|
2516
|
-
}
|
|
2517
|
-
};
|
|
2518
|
-
var ReturnCodegen = class {
|
|
2519
|
-
constructor() {
|
|
2520
|
-
this.command = "return";
|
|
2521
|
-
}
|
|
2522
|
-
generate(node, ctx) {
|
|
2523
|
-
const args = node.args ?? [];
|
|
2524
|
-
const value = args.length > 0 ? ctx.generateExpression(args[0]) : "undefined";
|
|
2525
|
-
return {
|
|
2526
|
-
code: `return ${value}`,
|
|
2527
|
-
async: false,
|
|
2528
|
-
sideEffects: true
|
|
2529
|
-
};
|
|
2530
|
-
}
|
|
2531
|
-
};
|
|
2532
|
-
var CallCodegen = class {
|
|
2533
|
-
constructor() {
|
|
2534
|
-
this.command = "call";
|
|
2535
|
-
}
|
|
2536
|
-
generate(node, ctx) {
|
|
2537
|
-
const args = node.args ?? [];
|
|
2538
|
-
if (args.length === 0) return null;
|
|
2539
|
-
const fn = ctx.generateExpression(args[0]);
|
|
2540
|
-
return {
|
|
2541
|
-
code: fn,
|
|
2542
|
-
async: false,
|
|
2543
|
-
sideEffects: true
|
|
2544
|
-
};
|
|
2545
|
-
}
|
|
2546
|
-
};
|
|
2547
|
-
var SCROLL_BLOCK = {
|
|
2548
|
-
top: "start",
|
|
2549
|
-
middle: "center",
|
|
2550
|
-
bottom: "end",
|
|
2551
|
-
nearest: "nearest"
|
|
2552
|
-
};
|
|
2553
|
-
var SCROLL_INLINE = { left: "start", center: "center", right: "end" };
|
|
2554
|
-
function scrollOptions(modifiers) {
|
|
2555
|
-
const word = (slot) => {
|
|
2556
|
-
const value = modifiers?.[slot]?.value;
|
|
2557
|
-
return typeof value === "string" ? value : void 0;
|
|
2558
|
-
};
|
|
2559
|
-
const position = word("position") ?? "";
|
|
2560
|
-
const options = [];
|
|
2561
|
-
if (SCROLL_BLOCK[position]) options.push(`block: '${SCROLL_BLOCK[position]}'`);
|
|
2562
|
-
if (SCROLL_INLINE[position]) options.push(`inline: '${SCROLL_INLINE[position]}'`);
|
|
2563
|
-
const behavior = modifiers?.smooth ? "smooth" : word("behavior");
|
|
2564
|
-
options.push(
|
|
2565
|
-
`behavior: '${behavior === "smooth" || behavior === "instant" ? behavior : "auto"}'`
|
|
2566
|
-
);
|
|
2567
|
-
return `{ ${options.join(", ")} }`;
|
|
2568
|
-
}
|
|
2569
|
-
var ScrollCodegen = class {
|
|
2570
|
-
constructor() {
|
|
2571
|
-
this.command = "scroll";
|
|
2572
|
-
}
|
|
2573
|
-
generate(node, ctx) {
|
|
2574
|
-
const targetNode = commandTarget(node, "arg", "of");
|
|
2575
|
-
const target = targetNode ? ctx.generateExpression(targetNode) : "_ctx.me";
|
|
2576
|
-
return {
|
|
2577
|
-
code: `${target}.scrollIntoView(${scrollOptions(node.modifiers)})`,
|
|
2578
|
-
async: false,
|
|
2579
|
-
sideEffects: true
|
|
2580
|
-
};
|
|
2581
|
-
}
|
|
2582
|
-
};
|
|
2583
|
-
var TakeCodegen = class {
|
|
2584
|
-
constructor() {
|
|
2585
|
-
this.command = "take";
|
|
2586
|
-
}
|
|
2587
|
-
generate(node, ctx) {
|
|
2588
|
-
const args = node.args ?? [];
|
|
2589
|
-
if (args.length === 0) return null;
|
|
2590
|
-
const arg = args[0];
|
|
2591
|
-
if (arg.type !== "selector") return null;
|
|
2592
|
-
const selector = arg.value;
|
|
2593
|
-
if (!selector.startsWith(".")) return null;
|
|
2594
|
-
const className = sanitizeClassName(selector.slice(1));
|
|
2595
|
-
if (!className) return null;
|
|
2596
|
-
const source = commandTarget(node, "from");
|
|
2597
|
-
if (source) {
|
|
2598
|
-
let from = allMatches(source, ctx);
|
|
2599
|
-
if (!from) {
|
|
2600
|
-
ctx.requireHelper("toArray");
|
|
2601
|
-
from = `_rt.toArray(${ctx.generateExpression(source)})`;
|
|
2602
|
-
}
|
|
2603
|
-
return {
|
|
2604
|
-
code: `(() => { ${from}.forEach(el => el.classList.remove('${className}')); _ctx.me.classList.add('${className}'); })()`,
|
|
2605
|
-
async: false,
|
|
2606
|
-
sideEffects: true
|
|
2607
|
-
};
|
|
2608
|
-
}
|
|
2609
|
-
return {
|
|
2610
|
-
code: `(() => { const _me = _ctx.me; _me.parentElement?.querySelectorAll('.${className}').forEach(el => el.classList.remove('${className}')); _me.classList.add('${className}'); })()`,
|
|
2611
|
-
async: false,
|
|
2612
|
-
sideEffects: true
|
|
2613
|
-
};
|
|
2614
|
-
}
|
|
2615
|
-
};
|
|
2616
|
-
function generateIf(node, ctx, generateBody) {
|
|
2617
|
-
const exprCodegen = new ExpressionCodegen(ctx);
|
|
2618
|
-
const condition = exprCodegen.generate(node.condition);
|
|
2619
|
-
const thenBody = generateBody(node.thenBranch);
|
|
2620
|
-
let code = `if (${condition}) {
|
|
2621
|
-
${thenBody}
|
|
2622
|
-
}`;
|
|
2623
|
-
if (node.elseIfBranches) {
|
|
2624
|
-
for (const branch of node.elseIfBranches) {
|
|
2625
|
-
const branchCondition = exprCodegen.generate(branch.condition);
|
|
2626
|
-
const branchBody = generateBody(branch.body);
|
|
2627
|
-
code += ` else if (${branchCondition}) {
|
|
2628
|
-
${branchBody}
|
|
2629
|
-
}`;
|
|
2630
|
-
}
|
|
2631
|
-
}
|
|
2632
|
-
if (node.elseBranch) {
|
|
2633
|
-
const elseBody = generateBody(node.elseBranch);
|
|
2634
|
-
code += ` else {
|
|
2635
|
-
${elseBody}
|
|
2636
|
-
}`;
|
|
2637
|
-
}
|
|
2638
|
-
return code;
|
|
2639
|
-
}
|
|
2640
|
-
function emitLoop(parts) {
|
|
2641
|
-
const pass = [
|
|
2642
|
-
...parts.elseBody !== void 0 ? ["_ran = true;"] : [],
|
|
2643
|
-
...parts.enter ? [parts.enter] : [],
|
|
2644
|
-
...parts.indexName ? [`_ctx.locals.set('${sanitizeIdentifier(parts.indexName)}', _i);`] : [],
|
|
2645
|
-
parts.body
|
|
2646
|
-
].join("\n");
|
|
2647
|
-
const step = parts.step ? `_i++, ${parts.step}` : "_i++";
|
|
2648
|
-
const loop = parts.bottomTested ? (
|
|
2649
|
-
// A `continue` jumps straight to the test, so the counter moves there.
|
|
2650
|
-
`let _i = 0;
|
|
2651
|
-
do {
|
|
2652
|
-
${pass}
|
|
2653
|
-
} while ((${step}, ${parts.test}));`
|
|
2654
|
-
) : `for (let _i = 0; ${parts.test}; ${step}) {
|
|
2655
|
-
${pass}
|
|
2656
|
-
}`;
|
|
2657
|
-
if (parts.elseBody === void 0) return parts.bottomTested ? `{
|
|
2658
|
-
${loop}
|
|
2659
|
-
}` : loop;
|
|
2660
|
-
return `{
|
|
2661
|
-
let _ran = false;
|
|
2662
|
-
${loop}
|
|
2663
|
-
if (!_ran) {
|
|
2664
|
-
${parts.elseBody}
|
|
2665
|
-
}
|
|
2666
|
-
}`;
|
|
2667
|
-
}
|
|
2668
|
-
function generateRepeat(node, ctx, generateBody) {
|
|
2669
|
-
const exprCodegen = new ExpressionCodegen(ctx);
|
|
2670
|
-
const shared = {
|
|
2671
|
-
indexName: node.indexName,
|
|
2672
|
-
body: generateBody(node.body),
|
|
2673
|
-
elseBody: node.elseBody ? generateBody(node.elseBody) : void 0
|
|
2674
|
-
};
|
|
2675
|
-
if (node.count !== void 0) {
|
|
2676
|
-
const count = typeof node.count === "number" ? String(node.count) : exprCodegen.generate(node.count);
|
|
2677
|
-
return emitLoop({ ...shared, test: `_i < ${count}`, indexName: node.indexName ?? "index" });
|
|
2678
|
-
}
|
|
2679
|
-
if (node.whileCondition) {
|
|
2680
|
-
return emitLoop({ ...shared, test: exprCodegen.generate(node.whileCondition) });
|
|
2681
|
-
}
|
|
2682
|
-
if (node.untilEvent !== void 0) {
|
|
2683
|
-
ctx.requireHelper("wait");
|
|
2684
|
-
const target = node.untilEventTarget ? exprCodegen.generate(node.untilEventTarget) : "_ctx.me";
|
|
2685
|
-
return `{
|
|
2686
|
-
let _fired = false;
|
|
2687
|
-
${target}.addEventListener(${JSON.stringify(node.untilEvent)}, () => { _fired = true; }, { once: true });
|
|
2688
|
-
${emitLoop({ ...shared, test: "!_fired", step: "await _rt.wait(0)" })}
|
|
2689
|
-
}`;
|
|
2690
|
-
}
|
|
2691
|
-
return emitLoop({ ...shared, test: "true" });
|
|
2692
|
-
}
|
|
2693
|
-
function generateForEach(node, ctx, generateBody) {
|
|
2694
|
-
const exprCodegen = new ExpressionCodegen(ctx);
|
|
2695
|
-
const source = node.collection;
|
|
2696
|
-
const collection = source.type === "selector" && !String(source.value).startsWith("#") ? `document.querySelectorAll('${sanitizeSelector(String(source.value))}')` : exprCodegen.generate(source);
|
|
2697
|
-
const itemName = sanitizeIdentifier(node.itemName);
|
|
2698
|
-
return `{
|
|
2699
|
-
const _collection = ${collection};
|
|
2700
|
-
const _arr = _collection == null ? [] : Array.isArray(_collection) ? _collection : Array.from(_collection);
|
|
2701
|
-
${emitLoop({
|
|
2702
|
-
test: "_i < _arr.length",
|
|
2703
|
-
enter: `_ctx.locals.set('${itemName}', _arr[_i]);`,
|
|
2704
|
-
indexName: node.indexName ?? "index",
|
|
2705
|
-
body: generateBody(node.body),
|
|
2706
|
-
elseBody: node.elseBody ? generateBody(node.elseBody) : void 0
|
|
2707
|
-
})}
|
|
2708
|
-
}`;
|
|
2709
|
-
}
|
|
2710
|
-
function generateWhile(node, ctx, generateBody) {
|
|
2711
|
-
const exprCodegen = new ExpressionCodegen(ctx);
|
|
2712
|
-
return emitLoop({
|
|
2713
|
-
test: exprCodegen.generate(node.condition),
|
|
2714
|
-
bottomTested: node.bottomTested,
|
|
2715
|
-
indexName: node.indexName,
|
|
2716
|
-
body: generateBody(node.body),
|
|
2717
|
-
elseBody: node.elseBody ? generateBody(node.elseBody) : void 0
|
|
2718
|
-
});
|
|
2719
|
-
}
|
|
2720
|
-
var UnlessCodegen = class {
|
|
2721
|
-
constructor() {
|
|
2722
|
-
this.command = "unless";
|
|
2723
|
-
}
|
|
2724
|
-
generate(node, ctx) {
|
|
2725
|
-
const args = node.args ?? [];
|
|
2726
|
-
if (args.length === 0) return null;
|
|
2727
|
-
const condition = ctx.generateExpression(args[0]);
|
|
2728
|
-
return {
|
|
2729
|
-
code: `if (!(${condition}))`,
|
|
2730
|
-
async: false,
|
|
2731
|
-
sideEffects: false
|
|
2732
|
-
};
|
|
2733
|
-
}
|
|
2734
|
-
};
|
|
2735
|
-
var ThrowCodegen = class {
|
|
2736
|
-
constructor() {
|
|
2737
|
-
this.command = "throw";
|
|
2738
|
-
}
|
|
2739
|
-
generate(node, ctx) {
|
|
2740
|
-
const args = node.args ?? [];
|
|
2741
|
-
const value = args.length > 0 ? ctx.generateExpression(args[0]) : "'Error'";
|
|
2742
|
-
return {
|
|
2743
|
-
code: `throw new Error(${value})`,
|
|
2744
|
-
async: false,
|
|
2745
|
-
sideEffects: true
|
|
2746
|
-
};
|
|
2747
|
-
}
|
|
2748
|
-
};
|
|
2749
|
-
var DefaultCodegen = class {
|
|
2750
|
-
constructor() {
|
|
2751
|
-
this.command = "default";
|
|
2752
|
-
}
|
|
2753
|
-
generate(node, ctx) {
|
|
2754
|
-
const args = node.args ?? [];
|
|
2755
|
-
const roles = node.roles;
|
|
2756
|
-
const modifiers = node.modifiers;
|
|
2757
|
-
const targetNode = roles?.destination ?? args[0];
|
|
2758
|
-
const valueNode = roles?.patient ?? modifiers?.to ?? args[1];
|
|
2759
|
-
if (!targetNode || !valueNode) return null;
|
|
2760
|
-
const value = ctx.generateExpression(valueNode);
|
|
2761
|
-
const store = variableStore(targetNode, ctx);
|
|
2762
|
-
if (store) {
|
|
2763
|
-
const code = `if (${store.read} == null) ${store.write(value)}`;
|
|
2764
|
-
return { code, async: false, sideEffects: true };
|
|
2765
|
-
}
|
|
2766
|
-
return null;
|
|
2767
|
-
}
|
|
2768
|
-
};
|
|
2769
|
-
var GoCodegen = class {
|
|
2770
|
-
constructor() {
|
|
2771
|
-
this.command = "go";
|
|
2772
|
-
}
|
|
2773
|
-
generate(node, ctx) {
|
|
2774
|
-
const args = node.args ?? [];
|
|
2775
|
-
const roles = node.roles;
|
|
2776
|
-
const mods = node.modifiers;
|
|
2777
|
-
if (mods?.back) {
|
|
2778
|
-
return { code: "history.back()", async: false, sideEffects: true };
|
|
2779
|
-
}
|
|
2780
|
-
if (mods?.position || mods?.behavior) {
|
|
2781
|
-
const element = roles?.destination ?? args[0];
|
|
2782
|
-
if (!element) return null;
|
|
2783
|
-
return {
|
|
2784
|
-
code: `${ctx.generateExpression(element)}.scrollIntoView(${scrollOptions(mods)})`,
|
|
2785
|
-
async: false,
|
|
2786
|
-
sideEffects: true
|
|
2787
|
-
};
|
|
2788
|
-
}
|
|
2789
|
-
const target = mods?.url ?? roles?.destination ?? args[0];
|
|
2790
|
-
if (!target) return null;
|
|
2791
|
-
if (target.type === "identifier") {
|
|
2792
|
-
const name = target.value;
|
|
2793
|
-
if (name === "back") {
|
|
2794
|
-
return { code: "history.back()", async: false, sideEffects: true };
|
|
2795
|
-
}
|
|
2796
|
-
if (name === "forward") {
|
|
2797
|
-
return { code: "history.forward()", async: false, sideEffects: true };
|
|
2798
|
-
}
|
|
2799
|
-
}
|
|
2800
|
-
ctx.requireHelper("go");
|
|
2801
|
-
const newWindow = mods?.in !== void 0 ? ", true" : "";
|
|
2802
|
-
return {
|
|
2803
|
-
code: `_rt.go(${ctx.generateExpression(target)}${newWindow})`,
|
|
2804
|
-
async: false,
|
|
2805
|
-
sideEffects: true
|
|
2806
|
-
};
|
|
2807
|
-
}
|
|
2808
|
-
};
|
|
2809
|
-
var AppendCodegen = class {
|
|
2810
|
-
constructor() {
|
|
2811
|
-
this.command = "append";
|
|
2812
|
-
}
|
|
2813
|
-
generate(node, ctx) {
|
|
2814
|
-
const args = node.args ?? [];
|
|
2815
|
-
const roles = node.roles;
|
|
2816
|
-
const contentNode = roles?.patient ?? args[0];
|
|
2817
|
-
if (!contentNode) return null;
|
|
2818
|
-
const content = ctx.generateExpression(contentNode);
|
|
2819
|
-
const targetNode = commandTarget(node, "to");
|
|
2820
|
-
const target = targetNode ? ctx.generateExpression(targetNode) : "_ctx.me";
|
|
2821
|
-
return {
|
|
2822
|
-
code: `${target}.insertAdjacentHTML('beforeend', ${content})`,
|
|
2823
|
-
async: false,
|
|
2824
|
-
sideEffects: true
|
|
2825
|
-
};
|
|
2826
|
-
}
|
|
2827
|
-
};
|
|
2828
|
-
var PickCodegen = class {
|
|
2829
|
-
constructor() {
|
|
2830
|
-
this.command = "pick";
|
|
2831
|
-
}
|
|
2832
|
-
generate(node, ctx) {
|
|
2833
|
-
const args = node.args ?? [];
|
|
2834
|
-
const roles = node.roles;
|
|
2835
|
-
const collection = roles?.source ?? args[0];
|
|
2836
|
-
if (!collection) return null;
|
|
2837
|
-
const arr = ctx.generateExpression(collection);
|
|
2838
|
-
return {
|
|
2839
|
-
code: `_ctx.it = (() => { const _a = ${arr}; return _a[Math.floor(Math.random() * _a.length)]; })()`,
|
|
2840
|
-
async: false,
|
|
2841
|
-
sideEffects: true
|
|
2842
|
-
};
|
|
2843
|
-
}
|
|
2844
|
-
};
|
|
2845
|
-
var PushUrlCodegen = class {
|
|
2846
|
-
constructor() {
|
|
2847
|
-
this.command = "push-url";
|
|
2848
|
-
}
|
|
2849
|
-
generate(node, ctx) {
|
|
2850
|
-
const args = node.args ?? [];
|
|
2851
|
-
const roles = node.roles;
|
|
2852
|
-
const urlNode = roles?.destination ?? args[0];
|
|
2853
|
-
if (!urlNode) return null;
|
|
2854
|
-
const url = ctx.generateExpression(urlNode);
|
|
2855
|
-
return {
|
|
2856
|
-
code: `history.pushState({}, '', ${url})`,
|
|
2857
|
-
async: false,
|
|
2858
|
-
sideEffects: true
|
|
2859
|
-
};
|
|
2860
|
-
}
|
|
2861
|
-
};
|
|
2862
|
-
var ReplaceUrlCodegen = class {
|
|
2863
|
-
constructor() {
|
|
2864
|
-
this.command = "replace-url";
|
|
2865
|
-
}
|
|
2866
|
-
generate(node, ctx) {
|
|
2867
|
-
const args = node.args ?? [];
|
|
2868
|
-
const roles = node.roles;
|
|
2869
|
-
const urlNode = roles?.destination ?? args[0];
|
|
2870
|
-
if (!urlNode) return null;
|
|
2871
|
-
const url = ctx.generateExpression(urlNode);
|
|
2872
|
-
return {
|
|
2873
|
-
code: `history.replaceState({}, '', ${url})`,
|
|
2874
|
-
async: false,
|
|
2875
|
-
sideEffects: true
|
|
2876
|
-
};
|
|
2877
|
-
}
|
|
2878
|
-
};
|
|
2879
|
-
var GetCodegen = class {
|
|
2880
|
-
constructor() {
|
|
2881
|
-
this.command = "get";
|
|
2882
|
-
}
|
|
2883
|
-
generate(node, ctx) {
|
|
2884
|
-
const args = node.args ?? [];
|
|
2885
|
-
const roles = node.roles;
|
|
2886
|
-
const expr = roles?.patient ?? args[0];
|
|
2887
|
-
if (!expr) return null;
|
|
2888
|
-
const value = ctx.generateExpression(expr);
|
|
2889
|
-
return {
|
|
2890
|
-
code: `_ctx.it = _ctx.result = ${value}`,
|
|
2891
|
-
async: false,
|
|
2892
|
-
sideEffects: true
|
|
2893
|
-
};
|
|
2894
|
-
}
|
|
2895
|
-
};
|
|
2896
|
-
var BreakCodegen = class {
|
|
2897
|
-
constructor() {
|
|
2898
|
-
this.command = "break";
|
|
2899
|
-
}
|
|
2900
|
-
generate(_node, _ctx) {
|
|
2901
|
-
return {
|
|
2902
|
-
code: "break",
|
|
2903
|
-
async: false,
|
|
2904
|
-
sideEffects: false
|
|
2905
|
-
};
|
|
2906
|
-
}
|
|
2907
|
-
};
|
|
2908
|
-
var ContinueCodegen = class {
|
|
2909
|
-
constructor() {
|
|
2910
|
-
this.command = "continue";
|
|
2911
|
-
}
|
|
2912
|
-
generate(_node, _ctx) {
|
|
2913
|
-
return {
|
|
2914
|
-
code: "continue",
|
|
2915
|
-
async: false,
|
|
2916
|
-
sideEffects: false
|
|
2917
|
-
};
|
|
2918
|
-
}
|
|
2919
|
-
};
|
|
2920
|
-
var BeepCodegen = class {
|
|
2921
|
-
constructor() {
|
|
2922
|
-
this.command = "beep";
|
|
2923
|
-
}
|
|
2924
|
-
generate(node, ctx) {
|
|
2925
|
-
const args = node.args ?? [];
|
|
2926
|
-
const roles = node.roles;
|
|
2927
|
-
const expr = roles?.patient ?? args[0];
|
|
2928
|
-
if (!expr) {
|
|
2929
|
-
return {
|
|
2930
|
-
code: `console.log('%c[beep]', 'color: orange; font-weight: bold')`,
|
|
2931
|
-
async: false,
|
|
2932
|
-
sideEffects: true
|
|
2933
|
-
};
|
|
2934
|
-
}
|
|
2935
|
-
const value = ctx.generateExpression(expr);
|
|
2936
|
-
return {
|
|
2937
|
-
code: `console.log('%c[beep]', 'color: orange; font-weight: bold', ${value})`,
|
|
2938
|
-
async: false,
|
|
2939
|
-
sideEffects: true
|
|
2940
|
-
};
|
|
2941
|
-
}
|
|
2942
|
-
};
|
|
2943
|
-
var JsCodegen = class {
|
|
2944
|
-
constructor() {
|
|
2945
|
-
this.command = "js";
|
|
2946
|
-
}
|
|
2947
|
-
generate(node, ctx) {
|
|
2948
|
-
const args = node.args ?? [];
|
|
2949
|
-
const roles = node.roles;
|
|
2950
|
-
const bodyNode = roles?.patient ?? args[0];
|
|
2951
|
-
if (!bodyNode) return null;
|
|
2952
|
-
if (bodyNode.type === "literal") {
|
|
2953
|
-
const code = bodyNode.value;
|
|
2954
|
-
if (typeof code === "string") {
|
|
2955
|
-
return {
|
|
2956
|
-
code: `(function(_ctx) { ${code} })(_ctx)`,
|
|
2957
|
-
async: false,
|
|
2958
|
-
sideEffects: true
|
|
2959
|
-
};
|
|
2960
|
-
}
|
|
2961
|
-
}
|
|
2962
|
-
const value = ctx.generateExpression(bodyNode);
|
|
2963
|
-
return {
|
|
2964
|
-
code: `_ctx.it = _ctx.result = ${value}`,
|
|
2965
|
-
async: false,
|
|
2966
|
-
sideEffects: true
|
|
2967
|
-
};
|
|
2968
|
-
}
|
|
2969
|
-
};
|
|
2970
|
-
var CopyCodegen = class {
|
|
2971
|
-
constructor() {
|
|
2972
|
-
this.command = "copy";
|
|
2973
|
-
}
|
|
2974
|
-
generate(node, ctx) {
|
|
2975
|
-
const args = node.args ?? [];
|
|
2976
|
-
const roles = node.roles;
|
|
2977
|
-
const contentNode = roles?.patient ?? args[0];
|
|
2978
|
-
if (!contentNode) return null;
|
|
2979
|
-
const content = ctx.generateExpression(contentNode);
|
|
2980
|
-
return {
|
|
2981
|
-
code: `await navigator.clipboard.writeText(String(${content}))`,
|
|
2982
|
-
async: true,
|
|
2983
|
-
sideEffects: true
|
|
2984
|
-
};
|
|
2985
|
-
}
|
|
2986
|
-
};
|
|
2987
|
-
var MakeCodegen = class {
|
|
2988
|
-
constructor() {
|
|
2989
|
-
this.command = "make";
|
|
2990
|
-
}
|
|
2991
|
-
generate(node, ctx) {
|
|
2992
|
-
const args = node.args ?? [];
|
|
2993
|
-
const roles = node.roles;
|
|
2994
|
-
const targetNode = roles?.patient ?? args[0];
|
|
2995
|
-
if (!targetNode) return null;
|
|
2996
|
-
if (targetNode.type === "htmlLiteral") {
|
|
2997
|
-
const tagNode = targetNode;
|
|
2998
|
-
const tag = tagNode.tag ?? "div";
|
|
2999
|
-
const classes = tagNode.classes ?? [];
|
|
3000
|
-
const id = tagNode.id;
|
|
3001
|
-
const attrs = tagNode.attributes ?? {};
|
|
3002
|
-
let code = `(() => { const _el = document.createElement('${tag}');`;
|
|
3003
|
-
if (classes.length > 0) {
|
|
3004
|
-
code += ` _el.className = '${classes.join(" ")}';`;
|
|
3005
|
-
}
|
|
3006
|
-
if (id) {
|
|
3007
|
-
code += ` _el.id = '${sanitizeIdentifier(id)}';`;
|
|
3008
|
-
}
|
|
3009
|
-
for (const [attr, val] of Object.entries(attrs)) {
|
|
3010
|
-
code += ` _el.setAttribute('${sanitizeSelector(attr)}', '${sanitizeSelector(val)}');`;
|
|
3011
|
-
}
|
|
3012
|
-
code += ` return _el; })()`;
|
|
3013
|
-
return {
|
|
3014
|
-
code: `_ctx.it = _ctx.result = ${code}`,
|
|
3015
|
-
async: false,
|
|
3016
|
-
sideEffects: true
|
|
3017
|
-
};
|
|
3018
|
-
}
|
|
3019
|
-
if (targetNode.type === "literal") {
|
|
3020
|
-
const tag = targetNode.value;
|
|
3021
|
-
if (typeof tag === "string") {
|
|
3022
|
-
return {
|
|
3023
|
-
code: `_ctx.it = _ctx.result = document.createElement('${sanitizeSelector(tag)}')`,
|
|
3024
|
-
async: false,
|
|
3025
|
-
sideEffects: true
|
|
3026
|
-
};
|
|
3027
|
-
}
|
|
3028
|
-
}
|
|
3029
|
-
const expr = ctx.generateExpression(targetNode);
|
|
3030
|
-
return {
|
|
3031
|
-
code: `_ctx.it = _ctx.result = document.createElement(${expr})`,
|
|
3032
|
-
async: false,
|
|
3033
|
-
sideEffects: true
|
|
3034
|
-
};
|
|
3035
|
-
}
|
|
3036
|
-
};
|
|
3037
|
-
var SwapCodegen = class {
|
|
3038
|
-
constructor() {
|
|
3039
|
-
this.command = "swap";
|
|
3040
|
-
}
|
|
3041
|
-
generate(node, ctx) {
|
|
3042
|
-
const args = node.args ?? [];
|
|
3043
|
-
const roles = node.roles;
|
|
3044
|
-
if (args.length > 0 && args[0].type === "identifier" && args[0].value === "delete") {
|
|
3045
|
-
const targetNode2 = args[1] ?? node.target;
|
|
3046
|
-
if (!targetNode2) return null;
|
|
3047
|
-
const target2 = ctx.generateExpression(targetNode2);
|
|
3048
|
-
return { code: `${target2}.remove()`, async: false, sideEffects: true };
|
|
3049
|
-
}
|
|
3050
|
-
const contentNode = roles?.patient ?? args[args.length - 1];
|
|
3051
|
-
const targetNode = roles?.destination ?? node.target ?? args[0];
|
|
3052
|
-
if (!targetNode || !contentNode) return null;
|
|
3053
|
-
const target = ctx.generateExpression(targetNode);
|
|
3054
|
-
const content = ctx.generateExpression(contentNode);
|
|
3055
|
-
const strategy = this.resolveStrategy(node, args);
|
|
3056
|
-
switch (strategy) {
|
|
3057
|
-
case "outerHTML":
|
|
3058
|
-
return { code: `${target}.outerHTML = ${content}`, async: false, sideEffects: true };
|
|
3059
|
-
case "beforebegin":
|
|
3060
|
-
return {
|
|
3061
|
-
code: `${target}.insertAdjacentHTML('beforebegin', ${content})`,
|
|
3062
|
-
async: false,
|
|
3063
|
-
sideEffects: true
|
|
3064
|
-
};
|
|
3065
|
-
case "afterbegin":
|
|
3066
|
-
return {
|
|
3067
|
-
code: `${target}.insertAdjacentHTML('afterbegin', ${content})`,
|
|
3068
|
-
async: false,
|
|
3069
|
-
sideEffects: true
|
|
3070
|
-
};
|
|
3071
|
-
case "beforeend":
|
|
3072
|
-
return {
|
|
3073
|
-
code: `${target}.insertAdjacentHTML('beforeend', ${content})`,
|
|
3074
|
-
async: false,
|
|
3075
|
-
sideEffects: true
|
|
3076
|
-
};
|
|
3077
|
-
case "afterend":
|
|
3078
|
-
return {
|
|
3079
|
-
code: `${target}.insertAdjacentHTML('afterend', ${content})`,
|
|
3080
|
-
async: false,
|
|
3081
|
-
sideEffects: true
|
|
3082
|
-
};
|
|
3083
|
-
case "morph":
|
|
3084
|
-
ctx.requireHelper("morph");
|
|
3085
|
-
return { code: `_rt.morph(${target}, ${content})`, async: false, sideEffects: true };
|
|
3086
|
-
case "innerHTML":
|
|
3087
|
-
default:
|
|
3088
|
-
return { code: `${target}.innerHTML = ${content}`, async: false, sideEffects: true };
|
|
3089
|
-
}
|
|
3090
|
-
}
|
|
3091
|
-
resolveStrategy(node, args) {
|
|
3092
|
-
const mods = node.modifiers;
|
|
3093
|
-
if (mods?.strategy && typeof mods.strategy === "string") return mods.strategy.toLowerCase();
|
|
3094
|
-
if (args.length >= 2 && args[0].type === "identifier") {
|
|
3095
|
-
const name = (args[0].value ?? "").toLowerCase();
|
|
3096
|
-
const strategies = {
|
|
3097
|
-
innerhtml: "innerHTML",
|
|
3098
|
-
outerhtml: "outerHTML",
|
|
3099
|
-
morph: "morph",
|
|
3100
|
-
beforebegin: "beforebegin",
|
|
3101
|
-
afterbegin: "afterbegin",
|
|
3102
|
-
beforeend: "beforeend",
|
|
3103
|
-
afterend: "afterend",
|
|
3104
|
-
into: "innerHTML",
|
|
3105
|
-
over: "outerHTML"
|
|
3106
|
-
};
|
|
3107
|
-
if (strategies[name]) return strategies[name];
|
|
3108
|
-
}
|
|
3109
|
-
return "innerHTML";
|
|
3110
|
-
}
|
|
3111
|
-
};
|
|
3112
|
-
var MorphCodegen = class {
|
|
3113
|
-
constructor() {
|
|
3114
|
-
this.command = "morph";
|
|
3115
|
-
}
|
|
3116
|
-
generate(node, ctx) {
|
|
3117
|
-
const args = node.args ?? [];
|
|
3118
|
-
const roles = node.roles;
|
|
3119
|
-
const targetNode = roles?.destination ?? node.target ?? args[0];
|
|
3120
|
-
const contentNode = roles?.patient ?? args[args.length > 1 ? args.length - 1 : 0];
|
|
3121
|
-
if (!targetNode) return null;
|
|
3122
|
-
const target = ctx.generateExpression(targetNode);
|
|
3123
|
-
ctx.requireHelper("morph");
|
|
3124
|
-
if (!contentNode || contentNode === targetNode) {
|
|
3125
|
-
return { code: `_rt.morph(${target}, '')`, async: false, sideEffects: true };
|
|
3126
|
-
}
|
|
3127
|
-
const content = ctx.generateExpression(contentNode);
|
|
3128
|
-
return { code: `_rt.morph(${target}, ${content})`, async: false, sideEffects: true };
|
|
3129
|
-
}
|
|
3130
|
-
};
|
|
3131
|
-
var TransitionCodegen = class {
|
|
3132
|
-
constructor() {
|
|
3133
|
-
this.command = "transition";
|
|
3134
|
-
}
|
|
3135
|
-
generate(node, ctx) {
|
|
3136
|
-
const args = node.args ?? [];
|
|
3137
|
-
const roles = node.roles;
|
|
3138
|
-
const mods = node.modifiers;
|
|
3139
|
-
const propertyNode = roles?.patient ?? args[0];
|
|
3140
|
-
if (!propertyNode) return null;
|
|
3141
|
-
const property = ctx.generateExpression(propertyNode);
|
|
3142
|
-
const valueNode = mods?.to ?? args[1];
|
|
3143
|
-
if (!valueNode) return null;
|
|
3144
|
-
const value = ctx.generateExpression(valueNode);
|
|
3145
|
-
const target = node.target ? ctx.generateExpression(node.target) : "_ctx.me";
|
|
3146
|
-
const durationNode = mods?.over;
|
|
3147
|
-
const duration = durationNode ? ctx.generateExpression(durationNode) : "300";
|
|
3148
|
-
const timingNode = mods?.with;
|
|
3149
|
-
const timing = timingNode ? ctx.generateExpression(timingNode) : "'ease'";
|
|
3150
|
-
ctx.requireHelper("transition");
|
|
3151
|
-
return {
|
|
3152
|
-
code: `await _rt.transition(${target}, ${property}, ${value}, ${duration}, ${timing})`,
|
|
3153
|
-
async: true,
|
|
3154
|
-
sideEffects: true
|
|
3155
|
-
};
|
|
3156
|
-
}
|
|
3157
|
-
};
|
|
3158
|
-
var MeasureCodegen = class {
|
|
3159
|
-
constructor() {
|
|
3160
|
-
this.command = "measure";
|
|
3161
|
-
}
|
|
3162
|
-
generate(node, ctx) {
|
|
3163
|
-
const args = node.args ?? [];
|
|
3164
|
-
const roles = node.roles;
|
|
3165
|
-
const target = node.target ? ctx.generateExpression(node.target) : "_ctx.me";
|
|
3166
|
-
const propNode = roles?.patient ?? args[0];
|
|
3167
|
-
if (!propNode) {
|
|
3168
|
-
return {
|
|
3169
|
-
code: `_ctx.it = _ctx.result = ${target}.getBoundingClientRect()`,
|
|
3170
|
-
async: false,
|
|
3171
|
-
sideEffects: true
|
|
3172
|
-
};
|
|
3173
|
-
}
|
|
3174
|
-
if (propNode.type === "identifier") {
|
|
3175
|
-
const propName = propNode.value ?? "";
|
|
3176
|
-
const rectProps = ["width", "height", "top", "left", "right", "bottom", "x", "y"];
|
|
3177
|
-
if (rectProps.includes(propName.toLowerCase())) {
|
|
3178
|
-
return {
|
|
3179
|
-
code: `_ctx.it = _ctx.result = ${target}.getBoundingClientRect().${propName.toLowerCase()}`,
|
|
3180
|
-
async: false,
|
|
3181
|
-
sideEffects: true
|
|
3182
|
-
};
|
|
3183
|
-
}
|
|
3184
|
-
}
|
|
3185
|
-
const prop = ctx.generateExpression(propNode);
|
|
3186
|
-
ctx.requireHelper("measure");
|
|
3187
|
-
return {
|
|
3188
|
-
code: `_ctx.it = _ctx.result = _rt.measure(${target}, ${prop})`,
|
|
3189
|
-
async: false,
|
|
3190
|
-
sideEffects: true
|
|
3191
|
-
};
|
|
3192
|
-
}
|
|
3193
|
-
};
|
|
3194
|
-
var SettleCodegen = class {
|
|
3195
|
-
constructor() {
|
|
3196
|
-
this.command = "settle";
|
|
3197
|
-
}
|
|
3198
|
-
generate(node, ctx) {
|
|
3199
|
-
const targetNode = commandTarget(node, "arg");
|
|
3200
|
-
const target = targetNode ? ctx.generateExpression(targetNode) : "_ctx.me";
|
|
3201
|
-
const mods = node.modifiers;
|
|
3202
|
-
const timeoutNode = mods?.for;
|
|
3203
|
-
const timeout = timeoutNode ? ctx.generateExpression(timeoutNode) : "5000";
|
|
3204
|
-
ctx.requireHelper("settle");
|
|
3205
|
-
return {
|
|
3206
|
-
code: `await _rt.settle(${target}, ${timeout})`,
|
|
3207
|
-
async: true,
|
|
3208
|
-
sideEffects: true
|
|
3209
|
-
};
|
|
3210
|
-
}
|
|
3211
|
-
};
|
|
3212
|
-
var TellCodegen = class {
|
|
3213
|
-
constructor() {
|
|
3214
|
-
this.command = "tell";
|
|
3215
|
-
}
|
|
3216
|
-
generate(node, ctx) {
|
|
3217
|
-
const args = node.args ?? [];
|
|
3218
|
-
const roles = node.roles;
|
|
3219
|
-
const targetNode = roles?.destination ?? args[0];
|
|
3220
|
-
if (!targetNode) return null;
|
|
3221
|
-
const target = ctx.generateExpression(targetNode);
|
|
3222
|
-
return {
|
|
3223
|
-
code: `{ const _prevMe = _ctx.me; _ctx.me = ${target}; _ctx.you = ${target};`,
|
|
3224
|
-
async: false,
|
|
3225
|
-
sideEffects: true
|
|
3226
|
-
};
|
|
3227
|
-
}
|
|
3228
|
-
};
|
|
3229
|
-
var AsyncCodegen = class {
|
|
3230
|
-
constructor() {
|
|
3231
|
-
this.command = "async";
|
|
3232
|
-
}
|
|
3233
|
-
generate(_node, _ctx) {
|
|
3234
|
-
return {
|
|
3235
|
-
code: `(async () => {`,
|
|
3236
|
-
async: false,
|
|
3237
|
-
// The outer handler doesn't await this
|
|
3238
|
-
sideEffects: true
|
|
3239
|
-
};
|
|
3240
|
-
}
|
|
3241
|
-
};
|
|
3242
|
-
var InstallCodegen = class {
|
|
3243
|
-
constructor() {
|
|
3244
|
-
this.command = "install";
|
|
3245
|
-
}
|
|
3246
|
-
generate(node, ctx) {
|
|
3247
|
-
const args = node.args ?? [];
|
|
3248
|
-
const roles = node.roles;
|
|
3249
|
-
const behaviorNode = roles?.patient ?? args[0];
|
|
3250
|
-
if (!behaviorNode) return null;
|
|
3251
|
-
const target = node.target ? ctx.generateExpression(node.target) : "_ctx.me";
|
|
3252
|
-
let behaviorName;
|
|
3253
|
-
if (behaviorNode.type === "identifier") {
|
|
3254
|
-
behaviorName = behaviorNode.value ?? "";
|
|
3255
|
-
} else if (behaviorNode.type === "literal") {
|
|
3256
|
-
behaviorName = String(behaviorNode.value);
|
|
3257
|
-
} else {
|
|
3258
|
-
behaviorName = ctx.generateExpression(behaviorNode);
|
|
3259
|
-
ctx.requireHelper("installBehavior");
|
|
3260
|
-
return {
|
|
3261
|
-
code: `_rt.installBehavior(${target}, ${behaviorName})`,
|
|
3262
|
-
async: false,
|
|
3263
|
-
sideEffects: true
|
|
3264
|
-
};
|
|
3265
|
-
}
|
|
3266
|
-
ctx.requireHelper("installBehavior");
|
|
3267
|
-
const paramsNode = args.length > 1 ? args[1] : void 0;
|
|
3268
|
-
if (paramsNode && paramsNode.type === "object") {
|
|
3269
|
-
const params = ctx.generateExpression(paramsNode);
|
|
3270
|
-
return {
|
|
3271
|
-
code: `_rt.installBehavior(${target}, '${sanitizeIdentifier(behaviorName)}', ${params})`,
|
|
3272
|
-
async: false,
|
|
3273
|
-
sideEffects: true
|
|
3274
|
-
};
|
|
3275
|
-
}
|
|
3276
|
-
return {
|
|
3277
|
-
code: `_rt.installBehavior(${target}, '${sanitizeIdentifier(behaviorName)}')`,
|
|
3278
|
-
async: false,
|
|
3279
|
-
sideEffects: true
|
|
3280
|
-
};
|
|
3281
|
-
}
|
|
3282
|
-
};
|
|
3283
|
-
var RenderCodegen = class {
|
|
3284
|
-
constructor() {
|
|
3285
|
-
this.command = "render";
|
|
3286
|
-
}
|
|
3287
|
-
generate(node, ctx) {
|
|
3288
|
-
const args = node.args ?? [];
|
|
3289
|
-
const roles = node.roles;
|
|
3290
|
-
const mods = node.modifiers;
|
|
3291
|
-
const templateNode = roles?.patient ?? args[0];
|
|
3292
|
-
if (!templateNode) return null;
|
|
3293
|
-
const template = ctx.generateExpression(templateNode);
|
|
3294
|
-
const target = node.target ? ctx.generateExpression(node.target) : "_ctx.me";
|
|
3295
|
-
const varsNode = mods?.with ?? (args.length > 1 ? args[1] : void 0);
|
|
3296
|
-
ctx.requireHelper("render");
|
|
3297
|
-
if (varsNode) {
|
|
3298
|
-
const vars = ctx.generateExpression(varsNode);
|
|
3299
|
-
return {
|
|
3300
|
-
code: `${target}.innerHTML = _rt.render(${template}, ${vars})`,
|
|
3301
|
-
async: false,
|
|
3302
|
-
sideEffects: true
|
|
3303
|
-
};
|
|
3304
|
-
}
|
|
3305
|
-
return {
|
|
3306
|
-
code: `${target}.innerHTML = _rt.render(${template}, {})`,
|
|
3307
|
-
async: false,
|
|
3308
|
-
sideEffects: true
|
|
3309
|
-
};
|
|
3310
|
-
}
|
|
3311
|
-
};
|
|
3312
|
-
var commandCodegens = /* @__PURE__ */ new Map([
|
|
3313
|
-
["toggle", new ToggleCodegen()],
|
|
3314
|
-
["add", new AddCodegen()],
|
|
3315
|
-
["remove", new RemoveCodegen()],
|
|
3316
|
-
["set", new SetCodegen()],
|
|
3317
|
-
["put", new PutCodegen()],
|
|
3318
|
-
["show", new ShowCodegen()],
|
|
3319
|
-
["hide", new HideCodegen()],
|
|
3320
|
-
["empty", new EmptyCodegen()],
|
|
3321
|
-
["focus", new FocusCodegen()],
|
|
3322
|
-
["blur", new BlurCodegen()],
|
|
3323
|
-
["log", new LogCodegen()],
|
|
3324
|
-
["wait", new WaitCodegen()],
|
|
3325
|
-
["fetch", new FetchCodegen()],
|
|
3326
|
-
["send", new SendCodegen()],
|
|
3327
|
-
["trigger", new SendCodegen()],
|
|
3328
|
-
// Alias
|
|
3329
|
-
["increment", new IncrementCodegen()],
|
|
3330
|
-
["decrement", new DecrementCodegen()],
|
|
3331
|
-
["halt", new HaltCodegen()],
|
|
3332
|
-
["exit", new ExitCodegen()],
|
|
3333
|
-
["return", new ReturnCodegen()],
|
|
3334
|
-
["call", new CallCodegen()],
|
|
3335
|
-
["scroll", new ScrollCodegen()],
|
|
3336
|
-
["take", new TakeCodegen()],
|
|
3337
|
-
["unless", new UnlessCodegen()],
|
|
3338
|
-
["throw", new ThrowCodegen()],
|
|
3339
|
-
["default", new DefaultCodegen()],
|
|
3340
|
-
["go", new GoCodegen()],
|
|
3341
|
-
["append", new AppendCodegen()],
|
|
3342
|
-
["pick", new PickCodegen()],
|
|
3343
|
-
["push-url", new PushUrlCodegen()],
|
|
3344
|
-
["replace-url", new ReplaceUrlCodegen()],
|
|
3345
|
-
["get", new GetCodegen()],
|
|
3346
|
-
["break", new BreakCodegen()],
|
|
3347
|
-
["continue", new ContinueCodegen()],
|
|
3348
|
-
["beep", new BeepCodegen()],
|
|
3349
|
-
["js", new JsCodegen()],
|
|
3350
|
-
["copy", new CopyCodegen()],
|
|
3351
|
-
["make", new MakeCodegen()],
|
|
3352
|
-
// Phase 1 additions
|
|
3353
|
-
["swap", new SwapCodegen()],
|
|
3354
|
-
["morph", new MorphCodegen()],
|
|
3355
|
-
["transition", new TransitionCodegen()],
|
|
3356
|
-
["measure", new MeasureCodegen()],
|
|
3357
|
-
["settle", new SettleCodegen()],
|
|
3358
|
-
["tell", new TellCodegen()],
|
|
3359
|
-
["async", new AsyncCodegen()],
|
|
3360
|
-
["install", new InstallCodegen()],
|
|
3361
|
-
["render", new RenderCodegen()]
|
|
3362
|
-
]);
|
|
3363
|
-
function generateCommand(node, ctx) {
|
|
3364
|
-
const codegen = commandCodegens.get(node.name);
|
|
3365
|
-
if (!codegen) {
|
|
3366
|
-
return null;
|
|
3367
|
-
}
|
|
3368
|
-
return codegen.generate(node, ctx);
|
|
3369
|
-
}
|
|
3370
|
-
function parseDuration(duration) {
|
|
3371
|
-
const match = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h)?$/i.exec(duration.trim());
|
|
3372
|
-
if (!match) return null;
|
|
3373
|
-
const value = parseFloat(match[1]);
|
|
3374
|
-
const unit = (match[2] ?? "ms").toLowerCase();
|
|
3375
|
-
switch (unit) {
|
|
3376
|
-
case "ms":
|
|
3377
|
-
return value;
|
|
3378
|
-
case "s":
|
|
3379
|
-
return value * 1e3;
|
|
3380
|
-
case "m":
|
|
3381
|
-
return value * 6e4;
|
|
3382
|
-
case "h":
|
|
3383
|
-
return value * 36e5;
|
|
3384
|
-
default:
|
|
3385
|
-
return value;
|
|
3386
|
-
}
|
|
3387
|
-
}
|
|
3388
|
-
var EventHandlerCodegen = class {
|
|
3389
|
-
constructor(ctx, analysis) {
|
|
3390
|
-
this.ctx = ctx;
|
|
3391
|
-
this.analysis = analysis;
|
|
3392
|
-
}
|
|
3393
|
-
/**
|
|
3394
|
-
* Generate complete code for an event handler.
|
|
3395
|
-
*/
|
|
3396
|
-
generate(node) {
|
|
3397
|
-
const eventName = node.event;
|
|
3398
|
-
const modifiers = node.modifiers ?? {};
|
|
3399
|
-
const body = node.body ?? [];
|
|
3400
|
-
const bodyCode = this.generateBody(body);
|
|
3401
|
-
const isAsync = this.analysis.controlFlow.hasAsync;
|
|
3402
|
-
const listenerOptions = this.buildListenerOptions(modifiers);
|
|
3403
|
-
const modifierCode = this.generateModifierCode(modifiers);
|
|
3404
|
-
const handlerCode = this.generateHandlerFunction(eventName, bodyCode, modifierCode, isAsync);
|
|
3405
|
-
const bindingCode = this.generateBindingCode(eventName, modifiers, listenerOptions);
|
|
3406
|
-
const cleanup = this.generateCleanupCode(eventName, modifiers);
|
|
3407
|
-
const imports = this.collectImports();
|
|
3408
|
-
return {
|
|
3409
|
-
handlerCode,
|
|
3410
|
-
bindingCode,
|
|
3411
|
-
cleanup,
|
|
3412
|
-
async: isAsync,
|
|
3413
|
-
imports
|
|
3414
|
-
};
|
|
3415
|
-
}
|
|
3416
|
-
// ===========================================================================
|
|
3417
|
-
// BODY GENERATION
|
|
3418
|
-
// ===========================================================================
|
|
3419
|
-
/**
|
|
3420
|
-
* Generate code for the handler body.
|
|
3421
|
-
*/
|
|
3422
|
-
generateBody(nodes) {
|
|
3423
|
-
const statements = [];
|
|
3424
|
-
for (const node of nodes) {
|
|
3425
|
-
const code = this.generateNode(node);
|
|
3426
|
-
if (code) {
|
|
3427
|
-
statements.push(code);
|
|
3428
|
-
}
|
|
3429
|
-
}
|
|
3430
|
-
return statements.join("\n");
|
|
3431
|
-
}
|
|
3432
|
-
/**
|
|
3433
|
-
* Generate code for a single AST node.
|
|
3434
|
-
*/
|
|
3435
|
-
generateNode(node) {
|
|
3436
|
-
switch (node.type) {
|
|
3437
|
-
case "command":
|
|
3438
|
-
return this.generateCommandCode(node);
|
|
3439
|
-
case "if":
|
|
3440
|
-
return generateIf(node, this.ctx, (nodes) => this.generateBody(nodes));
|
|
3441
|
-
case "repeat":
|
|
3442
|
-
return generateRepeat(node, this.ctx, (nodes) => this.generateBody(nodes));
|
|
3443
|
-
case "foreach":
|
|
3444
|
-
return generateForEach(node, this.ctx, (nodes) => this.generateBody(nodes));
|
|
3445
|
-
case "while":
|
|
3446
|
-
return generateWhile(node, this.ctx, (nodes) => this.generateBody(nodes));
|
|
3447
|
-
case "sequence":
|
|
3448
|
-
return this.generateBody(node.commands);
|
|
3449
|
-
case "event": {
|
|
3450
|
-
const nested = node;
|
|
3451
|
-
return this.generateBody(nested.body ?? []);
|
|
3452
|
-
}
|
|
3453
|
-
case "batchedClassOps": {
|
|
3454
|
-
const batch = node;
|
|
3455
|
-
const lines = [];
|
|
3456
|
-
if (batch.adds.length > 0) {
|
|
3457
|
-
lines.push(`${batch.target}.classList.add(${batch.adds.map((c) => `'${c}'`).join(", ")})`);
|
|
3458
|
-
}
|
|
3459
|
-
if (batch.removes.length > 0) {
|
|
3460
|
-
lines.push(
|
|
3461
|
-
`${batch.target}.classList.remove(${batch.removes.map((c) => `'${c}'`).join(", ")})`
|
|
3462
|
-
);
|
|
3463
|
-
}
|
|
3464
|
-
for (const t of batch.toggles) {
|
|
3465
|
-
lines.push(`${batch.target}.classList.toggle('${t}')`);
|
|
3466
|
-
}
|
|
3467
|
-
return lines.join(";\n");
|
|
3468
|
-
}
|
|
3469
|
-
default:
|
|
3470
|
-
return null;
|
|
3471
|
-
}
|
|
3472
|
-
}
|
|
3473
|
-
/**
|
|
3474
|
-
* Generate code for a command.
|
|
3475
|
-
*/
|
|
3476
|
-
generateCommandCode(node) {
|
|
3477
|
-
const result = generateCommand(node, this.ctx);
|
|
3478
|
-
if (!result) {
|
|
3479
|
-
return null;
|
|
3480
|
-
}
|
|
3481
|
-
return result.code;
|
|
3482
|
-
}
|
|
3483
|
-
// ===========================================================================
|
|
3484
|
-
// MODIFIER HANDLING
|
|
3485
|
-
// ===========================================================================
|
|
3486
|
-
/**
|
|
3487
|
-
* Build AddEventListenerOptions from modifiers.
|
|
3488
|
-
*/
|
|
3489
|
-
buildListenerOptions(modifiers) {
|
|
3490
|
-
const options = {};
|
|
3491
|
-
if (modifiers.once) {
|
|
3492
|
-
options.once = true;
|
|
3493
|
-
}
|
|
3494
|
-
if (modifiers.passive) {
|
|
3495
|
-
options.passive = true;
|
|
3496
|
-
}
|
|
3497
|
-
if (modifiers.capture) {
|
|
3498
|
-
options.capture = true;
|
|
3499
|
-
}
|
|
3500
|
-
return options;
|
|
3501
|
-
}
|
|
3502
|
-
/**
|
|
3503
|
-
* Generate code for event modifiers (prevent, stop).
|
|
3504
|
-
*/
|
|
3505
|
-
generateModifierCode(modifiers) {
|
|
3506
|
-
const code = [];
|
|
3507
|
-
if (modifiers.prevent) {
|
|
3508
|
-
code.push("_event.preventDefault();");
|
|
3509
|
-
}
|
|
3510
|
-
if (modifiers.stop) {
|
|
3511
|
-
code.push("_event.stopPropagation();");
|
|
3512
|
-
}
|
|
3513
|
-
return code.join("\n");
|
|
3514
|
-
}
|
|
3515
|
-
// ===========================================================================
|
|
3516
|
-
// HANDLER FUNCTION GENERATION
|
|
3517
|
-
// ===========================================================================
|
|
3518
|
-
/**
|
|
3519
|
-
* Generate the complete handler function.
|
|
3520
|
-
*/
|
|
3521
|
-
generateHandlerFunction(_eventName, bodyCode, modifierCode, isAsync) {
|
|
3522
|
-
const asyncKeyword = isAsync ? "async " : "";
|
|
3523
|
-
const handlerId = this.ctx.handlerId;
|
|
3524
|
-
const contextInit = `const _ctx = _rt.createContext(_event, this);`;
|
|
3525
|
-
const combinedBody = [modifierCode, bodyCode].filter(Boolean).join("\n");
|
|
3526
|
-
const needsTryCatch = this.analysis.controlFlow.canThrow;
|
|
3527
|
-
let functionBody;
|
|
3528
|
-
if (needsTryCatch) {
|
|
3529
|
-
functionBody = `${contextInit}
|
|
3530
|
-
try {
|
|
3531
|
-
${combinedBody}
|
|
3532
|
-
} catch (_e) {
|
|
3533
|
-
if (_e === _rt.HALT) {
|
|
3534
|
-
_event.preventDefault();
|
|
3535
|
-
return;
|
|
3536
|
-
}
|
|
3537
|
-
if (_e === _rt.EXIT) {
|
|
3538
|
-
return;
|
|
3539
|
-
}
|
|
3540
|
-
throw _e;
|
|
3541
|
-
}`;
|
|
3542
|
-
} else {
|
|
3543
|
-
functionBody = `${contextInit}
|
|
3544
|
-
${combinedBody}`;
|
|
3545
|
-
}
|
|
3546
|
-
return `${asyncKeyword}function _handler_${handlerId}(_event) {
|
|
3547
|
-
${functionBody}
|
|
3548
|
-
}`;
|
|
3549
|
-
}
|
|
3550
|
-
// ===========================================================================
|
|
3551
|
-
// BINDING CODE GENERATION
|
|
3552
|
-
// ===========================================================================
|
|
3553
|
-
/**
|
|
3554
|
-
* Generate code to bind the handler to an element.
|
|
3555
|
-
*/
|
|
3556
|
-
generateBindingCode(eventName, modifiers, options) {
|
|
3557
|
-
const handlerId = this.ctx.handlerId;
|
|
3558
|
-
const event = JSON.stringify(eventName);
|
|
3559
|
-
let handler = `_handler_${handlerId}`;
|
|
3560
|
-
if (modifiers.debounce) {
|
|
3561
|
-
this.ctx.requireHelper("debounce");
|
|
3562
|
-
handler = `_rt.debounce(${handler}, ${modifiers.debounce})`;
|
|
3563
|
-
}
|
|
3564
|
-
if (modifiers.throttle) {
|
|
3565
|
-
this.ctx.requireHelper("throttle");
|
|
3566
|
-
handler = `_rt.throttle(${handler}, ${modifiers.throttle})`;
|
|
3567
|
-
}
|
|
3568
|
-
const optionsKeys = Object.keys(options).filter((k) => options[k]);
|
|
3569
|
-
const optionsArg = optionsKeys.length > 0 ? `, { ${optionsKeys.map((k) => `${k}: true`).join(", ")} }` : "";
|
|
3570
|
-
if (modifiers.from) {
|
|
3571
|
-
const delegateSelector = JSON.stringify(modifiers.from);
|
|
3572
|
-
this.ctx.requireHelper("delegate");
|
|
3573
|
-
return `_rt.delegate(_el, ${event}, ${delegateSelector}, ${handler}${optionsArg});`;
|
|
3574
|
-
}
|
|
3575
|
-
return `_el.addEventListener(${event}, ${handler}${optionsArg});`;
|
|
3576
|
-
}
|
|
3577
|
-
// ===========================================================================
|
|
3578
|
-
// CLEANUP CODE GENERATION
|
|
3579
|
-
// ===========================================================================
|
|
3580
|
-
/**
|
|
3581
|
-
* Generate cleanup code for removing the handler.
|
|
3582
|
-
*/
|
|
3583
|
-
generateCleanupCode(eventName, modifiers) {
|
|
3584
|
-
if (modifiers.once) {
|
|
3585
|
-
return null;
|
|
3586
|
-
}
|
|
3587
|
-
const handlerId = this.ctx.handlerId;
|
|
3588
|
-
const event = JSON.stringify(eventName);
|
|
3589
|
-
const capture = modifiers.capture ? ", true" : "";
|
|
3590
|
-
return `_el.removeEventListener(${event}, _handler_${handlerId}${capture});`;
|
|
3591
|
-
}
|
|
3592
|
-
// ===========================================================================
|
|
3593
|
-
// IMPORTS COLLECTION
|
|
3594
|
-
// ===========================================================================
|
|
3595
|
-
/**
|
|
3596
|
-
* Collect required runtime imports.
|
|
3597
|
-
*/
|
|
3598
|
-
collectImports() {
|
|
3599
|
-
const imports = ["createContext"];
|
|
3600
|
-
for (const helper of this.ctx.requiredHelpers) {
|
|
3601
|
-
if (!imports.includes(helper)) {
|
|
3602
|
-
imports.push(helper);
|
|
3603
|
-
}
|
|
3604
|
-
}
|
|
3605
|
-
for (const helper of this.analysis.dependencies.runtimeHelpers) {
|
|
3606
|
-
if (!imports.includes(helper)) {
|
|
3607
|
-
imports.push(helper);
|
|
3608
|
-
}
|
|
3609
|
-
}
|
|
3610
|
-
return imports;
|
|
3611
|
-
}
|
|
3612
|
-
};
|
|
3613
|
-
function generateEventHandler(node, ctx, analysis) {
|
|
3614
|
-
const codegen = new EventHandlerCodegen(ctx, analysis);
|
|
3615
|
-
return codegen.generate(node);
|
|
3616
|
-
}
|
|
3617
|
-
function generateBindings(handlers) {
|
|
3618
|
-
const bindings = [];
|
|
3619
|
-
for (const { selector, eventName, handlerId, options } of handlers) {
|
|
3620
|
-
const sanitized = sanitizeSelector(selector);
|
|
3621
|
-
const event = JSON.stringify(eventName);
|
|
3622
|
-
const optionsKeys = options ? Object.keys(options).filter((k) => options[k]) : [];
|
|
3623
|
-
const optionsArg = optionsKeys.length > 0 ? `, { ${optionsKeys.map((k) => `${k}: true`).join(", ")} }` : "";
|
|
3624
|
-
bindings.push(
|
|
3625
|
-
`document.querySelectorAll('${sanitized}').forEach(_el => _el.addEventListener(${event}, _handler_${handlerId}${optionsArg}));`
|
|
3626
|
-
);
|
|
3627
|
-
}
|
|
3628
|
-
return bindings.join("\n");
|
|
3629
|
-
}
|
|
3630
|
-
function generateInitialization(handlers) {
|
|
3631
|
-
const bindings = generateBindings(handlers);
|
|
3632
|
-
return `_rt.ready(() => {
|
|
3633
|
-
${bindings}
|
|
3634
|
-
});`;
|
|
3635
|
-
}
|
|
3636
|
-
var DEFAULT_COMPILE_OPTIONS = {
|
|
3637
|
-
language: "en",
|
|
3638
|
-
confidenceThreshold: 0.7,
|
|
3639
|
-
debug: false,
|
|
3640
|
-
codegen: {
|
|
3641
|
-
target: "es2020",
|
|
3642
|
-
mode: "esm",
|
|
3643
|
-
minify: false,
|
|
3644
|
-
sourceMaps: true,
|
|
3645
|
-
runtimeImport: "@hyperfixi/aot-compiler/runtime",
|
|
3646
|
-
preserveComments: false,
|
|
3647
|
-
debugMode: false
|
|
3648
|
-
},
|
|
3649
|
-
optimizationLevel: 2
|
|
3650
|
-
};
|
|
3651
|
-
var DEFAULT_CODEGEN_OPTIONS = {
|
|
3652
|
-
target: "es2020",
|
|
3653
|
-
mode: "esm",
|
|
3654
|
-
minify: false,
|
|
3655
|
-
sourceMaps: true,
|
|
3656
|
-
runtimeImport: "@hyperfixi/aot-compiler/runtime",
|
|
3657
|
-
preserveComments: false,
|
|
3658
|
-
debugMode: false
|
|
3659
|
-
};
|
|
3660
|
-
var AOTCompiler = class {
|
|
3661
|
-
constructor() {
|
|
3662
|
-
this.parser = null;
|
|
3663
|
-
this.semanticParser = null;
|
|
3664
|
-
this.analyzer = new Analyzer();
|
|
3665
|
-
this.optimizer = new OptimizationPipeline();
|
|
3666
|
-
this.usedIds = /* @__PURE__ */ new Set();
|
|
3667
|
-
}
|
|
3668
|
-
/**
|
|
3669
|
-
* Set the traditional parser instance.
|
|
3670
|
-
*/
|
|
3671
|
-
setParser(parser) {
|
|
3672
|
-
this.parser = parser;
|
|
3673
|
-
}
|
|
3674
|
-
/**
|
|
3675
|
-
* Set the semantic parser for multilingual support.
|
|
3676
|
-
*/
|
|
3677
|
-
setSemanticParser(parser) {
|
|
3678
|
-
this.semanticParser = parser;
|
|
3679
|
-
}
|
|
3680
|
-
/**
|
|
3681
|
-
* Reset compiler state between compilations.
|
|
3682
|
-
*/
|
|
3683
|
-
reset() {
|
|
3684
|
-
this.usedIds.clear();
|
|
3685
|
-
}
|
|
3686
|
-
// ===========================================================================
|
|
3687
|
-
// EXTRACTION
|
|
3688
|
-
// ===========================================================================
|
|
3689
|
-
/**
|
|
3690
|
-
* Extract hyperscript from source code.
|
|
3691
|
-
*/
|
|
3692
|
-
extract(source, filename) {
|
|
3693
|
-
const scanner = this.createScanner(filename);
|
|
3694
|
-
return scanner.extract(source, filename);
|
|
3695
|
-
}
|
|
3696
|
-
/**
|
|
3697
|
-
* Create appropriate scanner for file type.
|
|
3698
|
-
*/
|
|
3699
|
-
createScanner(filename) {
|
|
3700
|
-
if (filename.endsWith(".vue")) {
|
|
3701
|
-
return new VueScanner();
|
|
3702
|
-
}
|
|
3703
|
-
if (filename.endsWith(".svelte")) {
|
|
3704
|
-
return new SvelteScanner();
|
|
3705
|
-
}
|
|
3706
|
-
if (filename.match(/\.(jsx|tsx)$/)) {
|
|
3707
|
-
return new JSXScanner();
|
|
3708
|
-
}
|
|
3709
|
-
return new HTMLScanner();
|
|
3710
|
-
}
|
|
3711
|
-
// ===========================================================================
|
|
3712
|
-
// PARSING
|
|
3713
|
-
// ===========================================================================
|
|
3714
|
-
/**
|
|
3715
|
-
* Parse a hyperscript string to AST.
|
|
3716
|
-
* Supports natural language, explicit bracket syntax, and JSON input.
|
|
3717
|
-
*/
|
|
3718
|
-
parse(code, options = {}) {
|
|
3719
|
-
const { language = "en", confidenceThreshold = 0.7, debug = false } = options;
|
|
3720
|
-
let ast = null;
|
|
3721
|
-
if (isExplicitSyntax(code)) {
|
|
3722
|
-
try {
|
|
3723
|
-
const node = parseExplicit(code);
|
|
3724
|
-
ast = semanticNodeToRuntimeAST(node);
|
|
3725
|
-
if (debug) {
|
|
3726
|
-
console.log(`[aot] Parsed explicit syntax: "${code}"`);
|
|
3727
|
-
}
|
|
3728
|
-
} catch (error) {
|
|
3729
|
-
if (debug) {
|
|
3730
|
-
console.log(`[aot] Explicit parse error for "${code}":`, error);
|
|
3731
|
-
}
|
|
3732
|
-
}
|
|
3733
|
-
if (ast) return this.ensureEventHandler(ast);
|
|
3734
|
-
}
|
|
3735
|
-
if (this.looksLikeJSON(code)) {
|
|
3736
|
-
try {
|
|
3737
|
-
const json = JSON.parse(code.trim());
|
|
3738
|
-
const node = fromProtocolJSON(json);
|
|
3739
|
-
ast = semanticNodeToRuntimeAST(node);
|
|
3740
|
-
if (debug) {
|
|
3741
|
-
console.log(`[aot] Parsed JSON input: "${code.slice(0, 80)}..."`);
|
|
3742
|
-
}
|
|
3743
|
-
} catch (error) {
|
|
3744
|
-
if (debug) {
|
|
3745
|
-
console.log(`[aot] JSON parse error for "${code.slice(0, 80)}":`, error);
|
|
3746
|
-
}
|
|
3747
|
-
}
|
|
3748
|
-
if (ast) return this.ensureEventHandler(ast);
|
|
3749
|
-
}
|
|
3750
|
-
if (language !== "en" && this.semanticParser?.supportsLanguage(language)) {
|
|
3751
|
-
const result = this.semanticParser.analyze(code, language);
|
|
3752
|
-
if (result.node && result.confidence >= confidenceThreshold) {
|
|
3753
|
-
const { ast: semanticAst, warnings } = this.semanticParser.buildAST(result.node);
|
|
3754
|
-
if (debug && warnings.length > 0) {
|
|
3755
|
-
console.log(`[aot] Semantic warnings for "${code}":`, warnings);
|
|
3756
|
-
}
|
|
3757
|
-
ast = semanticAst;
|
|
3758
|
-
} else if (debug) {
|
|
3759
|
-
console.log(
|
|
3760
|
-
`[aot] Semantic parse failed for "${code}": ${result.errors?.join(", ") || "low confidence"}`
|
|
3761
|
-
);
|
|
3762
|
-
}
|
|
3763
|
-
}
|
|
3764
|
-
if (!ast && this.parser) {
|
|
3765
|
-
try {
|
|
3766
|
-
ast = this.parser.parse(code, language);
|
|
3767
|
-
} catch (error) {
|
|
3768
|
-
if (debug) {
|
|
3769
|
-
console.log(`[aot] Parse error for "${code}":`, error);
|
|
3770
|
-
}
|
|
3771
|
-
}
|
|
3772
|
-
}
|
|
3773
|
-
if (!ast) {
|
|
3774
|
-
ast = this.createSimpleAST(code);
|
|
3775
|
-
}
|
|
3776
|
-
return this.ensureEventHandler(ast);
|
|
3777
|
-
}
|
|
3778
|
-
/**
|
|
3779
|
-
* Create a simple AST from code (for testing without full parser).
|
|
3780
|
-
*/
|
|
3781
|
-
createSimpleAST(code) {
|
|
3782
|
-
const eventMatch = /^on\s+(\w+)(?:\.(\w+))?\s+(.+)$/i.exec(code.trim());
|
|
3783
|
-
if (eventMatch) {
|
|
3784
|
-
const [, eventName, modifier, body] = eventMatch;
|
|
3785
|
-
return {
|
|
3786
|
-
type: "event",
|
|
3787
|
-
event: eventName,
|
|
3788
|
-
modifiers: modifier ? { [modifier]: true } : {},
|
|
3789
|
-
body: [this.parseSimpleCommand(body)].filter(Boolean)
|
|
3790
|
-
};
|
|
3791
|
-
}
|
|
3792
|
-
const cmd = this.parseSimpleCommand(code);
|
|
3793
|
-
if (cmd) {
|
|
3794
|
-
return {
|
|
3795
|
-
type: "event",
|
|
3796
|
-
event: "click",
|
|
3797
|
-
body: [cmd]
|
|
3798
|
-
};
|
|
3799
|
-
}
|
|
3800
|
-
return null;
|
|
3801
|
-
}
|
|
3802
|
-
/**
|
|
3803
|
-
* Parse a simple command (for testing).
|
|
3804
|
-
*/
|
|
3805
|
-
parseSimpleCommand(code) {
|
|
3806
|
-
const trimmed = code.trim();
|
|
3807
|
-
const toggleMatch = /^toggle\s+\.([a-zA-Z_][a-zA-Z0-9_-]*)$/i.exec(trimmed);
|
|
3808
|
-
if (toggleMatch) {
|
|
3809
|
-
return {
|
|
3810
|
-
type: "command",
|
|
3811
|
-
name: "toggle",
|
|
3812
|
-
args: [{ type: "selector", value: "." + toggleMatch[1] }]
|
|
3813
|
-
};
|
|
3814
|
-
}
|
|
3815
|
-
const addMatch = /^add\s+\.([a-zA-Z_][a-zA-Z0-9_-]*)$/i.exec(trimmed);
|
|
3816
|
-
if (addMatch) {
|
|
3817
|
-
return {
|
|
3818
|
-
type: "command",
|
|
3819
|
-
name: "add",
|
|
3820
|
-
args: [{ type: "selector", value: "." + addMatch[1] }]
|
|
3821
|
-
};
|
|
3822
|
-
}
|
|
3823
|
-
const removeMatch = /^remove\s+\.([a-zA-Z_][a-zA-Z0-9_-]*)$/i.exec(trimmed);
|
|
3824
|
-
if (removeMatch) {
|
|
3825
|
-
return {
|
|
3826
|
-
type: "command",
|
|
3827
|
-
name: "remove",
|
|
3828
|
-
args: [{ type: "selector", value: "." + removeMatch[1] }]
|
|
3829
|
-
};
|
|
3830
|
-
}
|
|
3831
|
-
if (/^show$/i.test(trimmed)) {
|
|
3832
|
-
return { type: "command", name: "show", args: [] };
|
|
3833
|
-
}
|
|
3834
|
-
if (/^hide$/i.test(trimmed)) {
|
|
3835
|
-
return { type: "command", name: "hide", args: [] };
|
|
3836
|
-
}
|
|
3837
|
-
if (/^focus$/i.test(trimmed)) {
|
|
3838
|
-
return { type: "command", name: "focus", args: [] };
|
|
3839
|
-
}
|
|
3840
|
-
if (/^blur$/i.test(trimmed)) {
|
|
3841
|
-
return { type: "command", name: "blur", args: [] };
|
|
3842
|
-
}
|
|
3843
|
-
const setMatch = /^set\s+:(\w+)\s+to\s+(.+)$/i.exec(trimmed);
|
|
3844
|
-
if (setMatch) {
|
|
3845
|
-
const [, varName, valueStr] = setMatch;
|
|
3846
|
-
return {
|
|
3847
|
-
type: "command",
|
|
3848
|
-
name: "set",
|
|
3849
|
-
args: [{ type: "variable", name: ":" + varName, scope: "local" }],
|
|
3850
|
-
modifiers: { to: this.parseSimpleValue(valueStr) }
|
|
3851
|
-
};
|
|
3852
|
-
}
|
|
3853
|
-
const incrMatch = /^(increment|decrement)\s+:(\w+)$/i.exec(trimmed);
|
|
3854
|
-
if (incrMatch) {
|
|
3855
|
-
const [, cmd, varName] = incrMatch;
|
|
3856
|
-
return {
|
|
3857
|
-
type: "command",
|
|
3858
|
-
name: cmd.toLowerCase(),
|
|
3859
|
-
args: [{ type: "variable", name: ":" + varName, scope: "local" }]
|
|
3860
|
-
};
|
|
3861
|
-
}
|
|
3862
|
-
const waitMatch = /^wait\s+(\d+(?:\.\d+)?)(ms|s)$/i.exec(trimmed);
|
|
3863
|
-
if (waitMatch) {
|
|
3864
|
-
const [, value, unit] = waitMatch;
|
|
3865
|
-
const ms = unit.toLowerCase() === "s" ? parseFloat(value) * 1e3 : parseFloat(value);
|
|
3866
|
-
return {
|
|
3867
|
-
type: "command",
|
|
3868
|
-
name: "wait",
|
|
3869
|
-
args: [{ type: "literal", value: ms }]
|
|
3870
|
-
};
|
|
3871
|
-
}
|
|
3872
|
-
const logMatch = /^log\s+(.+)$/i.exec(trimmed);
|
|
3873
|
-
if (logMatch) {
|
|
3874
|
-
return {
|
|
3875
|
-
type: "command",
|
|
3876
|
-
name: "log",
|
|
3877
|
-
args: [this.parseSimpleValue(logMatch[1])]
|
|
3878
|
-
};
|
|
3879
|
-
}
|
|
3880
|
-
const sendMatch = /^send\s+"([^"]+)"(?:\s+to\s+(\w+))?$/i.exec(trimmed);
|
|
3881
|
-
if (sendMatch) {
|
|
3882
|
-
const [, eventName, target] = sendMatch;
|
|
3883
|
-
const node = {
|
|
3884
|
-
type: "command",
|
|
3885
|
-
name: "send",
|
|
3886
|
-
args: [{ type: "literal", value: eventName }]
|
|
3887
|
-
};
|
|
3888
|
-
if (target) {
|
|
3889
|
-
node.target = { type: "identifier", value: target };
|
|
3890
|
-
}
|
|
3891
|
-
return node;
|
|
3892
|
-
}
|
|
3893
|
-
const fetchMatch = /^fetch\s+(\S+)(?:\s+as\s+(\w+))?$/i.exec(trimmed);
|
|
3894
|
-
if (fetchMatch) {
|
|
3895
|
-
const [, url, format] = fetchMatch;
|
|
3896
|
-
const node = {
|
|
3897
|
-
type: "command",
|
|
3898
|
-
name: "fetch",
|
|
3899
|
-
args: [{ type: "literal", value: url }]
|
|
3900
|
-
};
|
|
3901
|
-
if (format) {
|
|
3902
|
-
node.modifiers = { as: format };
|
|
3903
|
-
}
|
|
3904
|
-
return node;
|
|
3905
|
-
}
|
|
3906
|
-
const putMatch = /^put\s+"([^"]+)"\s+(into|before|after)\s+(\S+)$/i.exec(trimmed);
|
|
3907
|
-
if (putMatch) {
|
|
3908
|
-
const [, content, position, target] = putMatch;
|
|
3909
|
-
return {
|
|
3910
|
-
type: "command",
|
|
3911
|
-
name: "put",
|
|
3912
|
-
args: [{ type: "literal", value: content }],
|
|
3913
|
-
modifiers: { position, [position]: this.parseSimpleValue(target) }
|
|
3914
|
-
};
|
|
3915
|
-
}
|
|
3916
|
-
return null;
|
|
3917
|
-
}
|
|
3918
|
-
/**
|
|
3919
|
-
* Parse a simple value string to an AST node.
|
|
3920
|
-
*/
|
|
3921
|
-
parseSimpleValue(valueStr) {
|
|
3922
|
-
const trimmed = valueStr.trim();
|
|
3923
|
-
const strMatch = /^"([^"]*)"$/.exec(trimmed) ?? /^'([^']*)'$/.exec(trimmed);
|
|
3924
|
-
if (strMatch) {
|
|
3925
|
-
return { type: "literal", value: strMatch[1] };
|
|
3926
|
-
}
|
|
3927
|
-
if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
|
|
3928
|
-
return { type: "literal", value: parseFloat(trimmed) };
|
|
3929
|
-
}
|
|
3930
|
-
if (trimmed === "true") return { type: "literal", value: true };
|
|
3931
|
-
if (trimmed === "false") return { type: "literal", value: false };
|
|
3932
|
-
if (/^[#.][a-zA-Z_]/.test(trimmed)) {
|
|
3933
|
-
return { type: "selector", value: trimmed };
|
|
3934
|
-
}
|
|
3935
|
-
if (trimmed.startsWith(":")) {
|
|
3936
|
-
return { type: "variable", name: trimmed, scope: "local" };
|
|
3937
|
-
}
|
|
3938
|
-
return { type: "identifier", value: trimmed };
|
|
3939
|
-
}
|
|
3940
|
-
// ===========================================================================
|
|
3941
|
-
// EXPLICIT / JSON INPUT HELPERS
|
|
3942
|
-
// ===========================================================================
|
|
3943
|
-
/**
|
|
3944
|
-
* Check if input looks like JSON (LLM JSON format).
|
|
3945
|
-
*/
|
|
3946
|
-
looksLikeJSON(code) {
|
|
3947
|
-
const trimmed = code.trim();
|
|
3948
|
-
return trimmed.startsWith("{") && trimmed.includes('"action"');
|
|
3949
|
-
}
|
|
3950
|
-
/**
|
|
3951
|
-
* Ensure top-level node is always an event handler.
|
|
3952
|
-
* The codegen pipeline (EventHandlerCodegen) requires this.
|
|
3953
|
-
*/
|
|
3954
|
-
ensureEventHandler(ast) {
|
|
3955
|
-
if (ast && ast.type !== "event") {
|
|
3956
|
-
return {
|
|
3957
|
-
type: "event",
|
|
3958
|
-
event: "click",
|
|
3959
|
-
modifiers: {},
|
|
3960
|
-
body: [ast]
|
|
3961
|
-
};
|
|
3962
|
-
}
|
|
3963
|
-
return ast;
|
|
3964
|
-
}
|
|
3965
|
-
// ===========================================================================
|
|
3966
|
-
// ANALYSIS
|
|
3967
|
-
// ===========================================================================
|
|
3968
|
-
/**
|
|
3969
|
-
* Analyze an AST for optimization and code generation.
|
|
3970
|
-
*/
|
|
3971
|
-
analyze(ast) {
|
|
3972
|
-
return this.analyzer.analyze(ast);
|
|
3973
|
-
}
|
|
3974
|
-
// ===========================================================================
|
|
3975
|
-
// INIT PRE-RENDERING
|
|
3976
|
-
// ===========================================================================
|
|
3977
|
-
/**
|
|
3978
|
-
* Pre-render pure init block commands into an HTML template at build time.
|
|
3979
|
-
*
|
|
3980
|
-
* Analyzes init block commands for purity (static DOM writes with literal
|
|
3981
|
-
* values targeting #id selectors) and applies them directly to the HTML.
|
|
3982
|
-
* Impure commands are preserved for normal runtime execution.
|
|
3983
|
-
*
|
|
3984
|
-
* @param html - The HTML template string
|
|
3985
|
-
* @param initCommands - Parsed commands from an init block
|
|
3986
|
-
* @returns Modified HTML, remaining impure commands, and count of pre-rendered commands
|
|
3987
|
-
*/
|
|
3988
|
-
preRenderInit(html, initCommands) {
|
|
3989
|
-
return preRenderInitBlock(html, initCommands);
|
|
3990
|
-
}
|
|
3991
|
-
/**
|
|
3992
|
-
* Extract init blocks from HTML, pre-render pure commands, and return modified HTML.
|
|
3993
|
-
* This is a higher-level convenience that combines extraction, parsing, and pre-rendering.
|
|
3994
|
-
*
|
|
3995
|
-
* @param html - The HTML template string
|
|
3996
|
-
* @param options - Compile options (language, etc.)
|
|
3997
|
-
* @returns Modified HTML with pre-rendered init effects and compilation results
|
|
3998
|
-
*/
|
|
3999
|
-
preRenderInitBlocks(html, options = {}) {
|
|
4000
|
-
const scripts = this.extract(html, "template.html");
|
|
4001
|
-
let modifiedHtml = html;
|
|
4002
|
-
let totalPreRendered = 0;
|
|
4003
|
-
const remainingScripts = [];
|
|
4004
|
-
for (const script of scripts) {
|
|
4005
|
-
const ast = this.parse(script.code, {
|
|
4006
|
-
...options,
|
|
4007
|
-
language: script.language ?? options.language ?? "en"
|
|
4008
|
-
});
|
|
4009
|
-
if (!ast) {
|
|
4010
|
-
remainingScripts.push(script);
|
|
4011
|
-
continue;
|
|
4012
|
-
}
|
|
4013
|
-
const eventNode = ast;
|
|
4014
|
-
if (eventNode.type === "event" && eventNode.event === "init" && eventNode.body) {
|
|
4015
|
-
const result = preRenderInitBlock(modifiedHtml, eventNode.body);
|
|
4016
|
-
modifiedHtml = result.html;
|
|
4017
|
-
totalPreRendered += result.preRenderedCount;
|
|
4018
|
-
if (result.remainingInitCommands.length > 0) {
|
|
4019
|
-
remainingScripts.push(script);
|
|
4020
|
-
}
|
|
4021
|
-
} else {
|
|
4022
|
-
remainingScripts.push(script);
|
|
4023
|
-
}
|
|
4024
|
-
}
|
|
4025
|
-
return {
|
|
4026
|
-
html: modifiedHtml,
|
|
4027
|
-
preRenderedCount: totalPreRendered,
|
|
4028
|
-
remainingScripts
|
|
4029
|
-
};
|
|
4030
|
-
}
|
|
4031
|
-
// ===========================================================================
|
|
4032
|
-
// COMPILATION
|
|
4033
|
-
// ===========================================================================
|
|
4034
|
-
/**
|
|
4035
|
-
* Compile a single hyperscript string to JavaScript.
|
|
4036
|
-
*/
|
|
4037
|
-
compileScript(code, options = {}) {
|
|
4038
|
-
const mergedOptions = { ...DEFAULT_COMPILE_OPTIONS, ...options };
|
|
4039
|
-
const ast = this.parse(code, mergedOptions);
|
|
4040
|
-
if (!ast) {
|
|
4041
|
-
return {
|
|
4042
|
-
success: false,
|
|
4043
|
-
errors: ["Failed to parse hyperscript"],
|
|
4044
|
-
warnings: [],
|
|
4045
|
-
metadata: {
|
|
4046
|
-
handlerId: "",
|
|
4047
|
-
parserUsed: "traditional",
|
|
4048
|
-
commandsUsed: [],
|
|
4049
|
-
optimizationsApplied: [],
|
|
4050
|
-
needsRuntime: true,
|
|
4051
|
-
runtimeHelpers: []
|
|
4052
|
-
}
|
|
4053
|
-
};
|
|
4054
|
-
}
|
|
4055
|
-
const analysis = this.analyze(ast);
|
|
4056
|
-
const optimized = this.optimizer.optimize(ast, analysis, mergedOptions.optimizationLevel ?? 2);
|
|
4057
|
-
const handlerId = this.generateHandlerId(ast, code);
|
|
4058
|
-
const ctx = this.createCodegenContext(handlerId, analysis, mergedOptions);
|
|
4059
|
-
const generated = this.generateCode(optimized, ctx, analysis);
|
|
4060
|
-
const allHelpers = /* @__PURE__ */ new Set([...ctx.requiredHelpers, ...generated.imports]);
|
|
4061
|
-
if (optimized.type === "event") {
|
|
4062
|
-
const eventNode = optimized;
|
|
4063
|
-
const bodyLen = eventNode.body?.length ?? 0;
|
|
4064
|
-
if (bodyLen > 0 && generated.code) {
|
|
4065
|
-
const handlerMatch = /function\s+_handler_\w+\(_event\)\s*\{([\s\S]*)\}\s*$/.exec(
|
|
4066
|
-
generated.code
|
|
4067
|
-
);
|
|
4068
|
-
if (handlerMatch) {
|
|
4069
|
-
const bodyContent = handlerMatch[1].replace(/const _ctx\s*=\s*[^;]*;/g, "").replace(/const _el\s*=\s*[^;]*;/g, "").trim();
|
|
4070
|
-
if (!bodyContent) {
|
|
4071
|
-
analysis.warnings.push(
|
|
4072
|
-
`Event handler body appears empty despite ${bodyLen} parsed command(s). This may indicate a code generation failure for: "${code}"`
|
|
4073
|
-
);
|
|
4074
|
-
}
|
|
4075
|
-
}
|
|
4076
|
-
}
|
|
4077
|
-
}
|
|
4078
|
-
return {
|
|
4079
|
-
success: true,
|
|
4080
|
-
code: generated.code,
|
|
4081
|
-
warnings: analysis.warnings,
|
|
4082
|
-
metadata: {
|
|
4083
|
-
handlerId,
|
|
4084
|
-
parserUsed: mergedOptions.language !== "en" ? "semantic" : "traditional",
|
|
4085
|
-
language: mergedOptions.language,
|
|
4086
|
-
commandsUsed: Array.from(analysis.commandsUsed),
|
|
4087
|
-
optimizationsApplied: optimized._optimizations ?? [],
|
|
4088
|
-
needsRuntime: allHelpers.size > 0,
|
|
4089
|
-
runtimeHelpers: Array.from(allHelpers)
|
|
4090
|
-
}
|
|
4091
|
-
};
|
|
4092
|
-
}
|
|
4093
|
-
/**
|
|
4094
|
-
* Compile a pre-parsed AST to JavaScript.
|
|
4095
|
-
* Skips parsing entirely — accepts an interchange-format ASTNode directly.
|
|
4096
|
-
* Use this when the AST has already been produced by a semantic parser,
|
|
4097
|
-
* explicit syntax parser, or other external source.
|
|
4098
|
-
*/
|
|
4099
|
-
compileAST(ast, options = {}) {
|
|
4100
|
-
const mergedOptions = { ...DEFAULT_COMPILE_OPTIONS, ...options };
|
|
4101
|
-
let normalized = ast;
|
|
4102
|
-
if (normalized.type !== "event") {
|
|
4103
|
-
normalized = {
|
|
4104
|
-
type: "event",
|
|
4105
|
-
event: "click",
|
|
4106
|
-
modifiers: {},
|
|
4107
|
-
body: [normalized]
|
|
4108
|
-
};
|
|
4109
|
-
}
|
|
4110
|
-
const analysis = this.analyze(normalized);
|
|
4111
|
-
const optimized = this.optimizer.optimize(
|
|
4112
|
-
normalized,
|
|
4113
|
-
analysis,
|
|
4114
|
-
mergedOptions.optimizationLevel ?? 2
|
|
4115
|
-
);
|
|
4116
|
-
const sourceHint = JSON.stringify(ast).slice(0, 200);
|
|
4117
|
-
const handlerId = this.generateHandlerId(optimized, sourceHint);
|
|
4118
|
-
const ctx = this.createCodegenContext(handlerId, analysis, mergedOptions);
|
|
4119
|
-
const generated = this.generateCode(optimized, ctx, analysis);
|
|
4120
|
-
const allHelpers = /* @__PURE__ */ new Set([...ctx.requiredHelpers, ...generated.imports]);
|
|
4121
|
-
if (optimized.type === "event") {
|
|
4122
|
-
const eventNode = optimized;
|
|
4123
|
-
const bodyLen = eventNode.body?.length ?? 0;
|
|
4124
|
-
if (bodyLen > 0 && generated.code) {
|
|
4125
|
-
const handlerMatch = /function\s+_handler_\w+\(_event\)\s*\{([\s\S]*)\}\s*$/.exec(
|
|
4126
|
-
generated.code
|
|
4127
|
-
);
|
|
4128
|
-
if (handlerMatch) {
|
|
4129
|
-
const bodyContent = handlerMatch[1].replace(/const _ctx\s*=\s*[^;]*;/g, "").replace(/const _el\s*=\s*[^;]*;/g, "").trim();
|
|
4130
|
-
if (!bodyContent) {
|
|
4131
|
-
analysis.warnings.push(
|
|
4132
|
-
`Event handler body appears empty despite ${bodyLen} parsed command(s). This may indicate a code generation failure for pre-parsed AST.`
|
|
4133
|
-
);
|
|
4134
|
-
}
|
|
4135
|
-
}
|
|
4136
|
-
}
|
|
4137
|
-
}
|
|
4138
|
-
return {
|
|
4139
|
-
success: true,
|
|
4140
|
-
code: generated.code,
|
|
4141
|
-
warnings: analysis.warnings,
|
|
4142
|
-
metadata: {
|
|
4143
|
-
handlerId,
|
|
4144
|
-
parserUsed: "traditional",
|
|
4145
|
-
// pre-parsed, no parser involved
|
|
4146
|
-
language: mergedOptions.language,
|
|
4147
|
-
commandsUsed: Array.from(analysis.commandsUsed),
|
|
4148
|
-
optimizationsApplied: optimized._optimizations ?? [],
|
|
4149
|
-
needsRuntime: allHelpers.size > 0,
|
|
4150
|
-
runtimeHelpers: Array.from(allHelpers)
|
|
4151
|
-
}
|
|
4152
|
-
};
|
|
4153
|
-
}
|
|
4154
|
-
/**
|
|
4155
|
-
* Compile explicit bracket syntax or LLM JSON to JavaScript.
|
|
4156
|
-
* Uses the framework IR directly — no semantic or traditional parser needed.
|
|
4157
|
-
*/
|
|
4158
|
-
compileExplicit(code, options = {}) {
|
|
4159
|
-
try {
|
|
4160
|
-
const ast = isExplicitSyntax(code) ? semanticNodeToRuntimeAST(parseExplicit(code)) : semanticNodeToRuntimeAST(fromProtocolJSON(JSON.parse(code.trim())));
|
|
4161
|
-
return this.compileAST(ast, options);
|
|
4162
|
-
} catch (error) {
|
|
4163
|
-
return {
|
|
4164
|
-
success: false,
|
|
4165
|
-
errors: [error instanceof Error ? error.message : String(error)],
|
|
4166
|
-
warnings: [],
|
|
4167
|
-
metadata: {
|
|
4168
|
-
handlerId: "",
|
|
4169
|
-
parserUsed: "traditional",
|
|
4170
|
-
commandsUsed: [],
|
|
4171
|
-
optimizationsApplied: [],
|
|
4172
|
-
needsRuntime: false,
|
|
4173
|
-
runtimeHelpers: []
|
|
4174
|
-
}
|
|
4175
|
-
};
|
|
4176
|
-
}
|
|
4177
|
-
}
|
|
4178
|
-
/**
|
|
4179
|
-
* Compile multiple extracted scripts.
|
|
4180
|
-
*/
|
|
4181
|
-
compile(scripts, options = {}) {
|
|
4182
|
-
const handlers = [];
|
|
4183
|
-
const fallbacks = [];
|
|
4184
|
-
const allImports = /* @__PURE__ */ new Set();
|
|
4185
|
-
for (const script of scripts) {
|
|
4186
|
-
const scriptOptions = {
|
|
4187
|
-
...options,
|
|
4188
|
-
language: script.language ?? options.language ?? "en"
|
|
4189
|
-
};
|
|
4190
|
-
const result = this.compileScript(script.code, scriptOptions);
|
|
4191
|
-
if (result.success && result.code) {
|
|
4192
|
-
handlers.push({
|
|
4193
|
-
id: result.metadata.handlerId,
|
|
4194
|
-
source: script.code,
|
|
4195
|
-
events: this.extractEvents(result.code),
|
|
4196
|
-
code: result.code,
|
|
4197
|
-
binding: {
|
|
4198
|
-
elementId: script.elementId,
|
|
4199
|
-
elementSelector: script.elementSelector
|
|
4200
|
-
}
|
|
4201
|
-
});
|
|
4202
|
-
for (const helper of result.metadata.runtimeHelpers) {
|
|
4203
|
-
allImports.add(helper);
|
|
4204
|
-
}
|
|
4205
|
-
} else {
|
|
4206
|
-
fallbacks.push({
|
|
4207
|
-
id: `fallback_${fallbacks.length}`,
|
|
4208
|
-
script: script.code,
|
|
4209
|
-
reason: result.errors?.join(", ") ?? "Unknown error",
|
|
4210
|
-
location: script.location
|
|
4211
|
-
});
|
|
4212
|
-
}
|
|
4213
|
-
}
|
|
4214
|
-
const combinedCode = this.generateCombinedCode(handlers, Array.from(allImports), options);
|
|
4215
|
-
return {
|
|
4216
|
-
handlers,
|
|
4217
|
-
code: combinedCode,
|
|
4218
|
-
fallbacks,
|
|
4219
|
-
stats: {
|
|
4220
|
-
total: scripts.length,
|
|
4221
|
-
compiled: handlers.length,
|
|
4222
|
-
fallbacks: fallbacks.length,
|
|
4223
|
-
totalSize: combinedCode.length,
|
|
4224
|
-
runtimeSize: this.estimateRuntimeSize(allImports)
|
|
4225
|
-
}
|
|
4226
|
-
};
|
|
4227
|
-
}
|
|
4228
|
-
// ===========================================================================
|
|
4229
|
-
// CODE GENERATION
|
|
4230
|
-
// ===========================================================================
|
|
4231
|
-
/**
|
|
4232
|
-
* Generate JavaScript code from an optimized AST.
|
|
4233
|
-
*/
|
|
4234
|
-
generateCode(ast, ctx, analysis) {
|
|
4235
|
-
if (ast.type === "event") {
|
|
4236
|
-
const eventCodegen = new EventHandlerCodegen(ctx, analysis);
|
|
4237
|
-
const generated = eventCodegen.generate(ast);
|
|
4238
|
-
return {
|
|
4239
|
-
code: generated.handlerCode,
|
|
4240
|
-
imports: generated.imports
|
|
4241
|
-
};
|
|
4242
|
-
}
|
|
4243
|
-
const exprCodegen = new ExpressionCodegen(ctx);
|
|
4244
|
-
return {
|
|
4245
|
-
code: exprCodegen.generate(ast),
|
|
4246
|
-
imports: Array.from(ctx.requiredHelpers)
|
|
4247
|
-
};
|
|
4248
|
-
}
|
|
4249
|
-
/**
|
|
4250
|
-
* Generate combined code for multiple handlers.
|
|
4251
|
-
*/
|
|
4252
|
-
generateCombinedCode(handlers, imports, options) {
|
|
4253
|
-
const codegenOptions = { ...DEFAULT_CODEGEN_OPTIONS, ...options.codegen };
|
|
4254
|
-
const lines = [];
|
|
4255
|
-
const allImports = [.../* @__PURE__ */ new Set([...imports, "ready"])];
|
|
4256
|
-
const importList = allImports.join(", ");
|
|
4257
|
-
if (codegenOptions.mode === "iife") {
|
|
4258
|
-
lines.push("(function() {");
|
|
4259
|
-
lines.push("var _ls = typeof lokascriptRuntime !== 'undefined' ? lokascriptRuntime : {};");
|
|
4260
|
-
lines.push("var _rt = { " + allImports.map((i) => `${i}: _ls.${i}`).join(", ") + " };");
|
|
4261
|
-
} else {
|
|
4262
|
-
if (codegenOptions.mode === "esm") {
|
|
4263
|
-
lines.push(`import { ${importList} } from '${codegenOptions.runtimeImport}';`);
|
|
4264
|
-
} else if (codegenOptions.mode === "cjs") {
|
|
4265
|
-
lines.push(`const { ${importList} } = require('${codegenOptions.runtimeImport}');`);
|
|
4266
|
-
}
|
|
4267
|
-
lines.push("const _rt = { " + allImports.map((i) => `${i}: ${i}`).join(", ") + " };");
|
|
4268
|
-
}
|
|
4269
|
-
lines.push("");
|
|
4270
|
-
for (const handler of handlers) {
|
|
4271
|
-
if (codegenOptions.preserveComments) {
|
|
4272
|
-
lines.push(`// Original: ${handler.source}`);
|
|
4273
|
-
}
|
|
4274
|
-
lines.push(handler.code);
|
|
4275
|
-
lines.push("");
|
|
4276
|
-
}
|
|
4277
|
-
lines.push("// Bind handlers to elements");
|
|
4278
|
-
lines.push("_rt.ready(() => {");
|
|
4279
|
-
for (const handler of handlers) {
|
|
4280
|
-
const selector = handler.binding.elementId ? `#${handler.binding.elementId}` : handler.binding.elementSelector ?? "[_]";
|
|
4281
|
-
for (const event of handler.events) {
|
|
4282
|
-
lines.push(` document.querySelectorAll('${selector}').forEach(_el => {`);
|
|
4283
|
-
lines.push(` _el.addEventListener('${event}', _handler_${handler.id});`);
|
|
4284
|
-
lines.push(" });");
|
|
4285
|
-
}
|
|
4286
|
-
}
|
|
4287
|
-
lines.push("});");
|
|
4288
|
-
if (codegenOptions.mode === "iife") {
|
|
4289
|
-
lines.push("})();");
|
|
4290
|
-
}
|
|
4291
|
-
return lines.join("\n");
|
|
4292
|
-
}
|
|
4293
|
-
// ===========================================================================
|
|
4294
|
-
// HELPERS
|
|
4295
|
-
// ===========================================================================
|
|
4296
|
-
/**
|
|
4297
|
-
* Generate a unique handler ID.
|
|
4298
|
-
*/
|
|
4299
|
-
generateHandlerId(ast, code) {
|
|
4300
|
-
const event = ast.event ?? "handler";
|
|
4301
|
-
const body = ast.body ?? [];
|
|
4302
|
-
const firstCmd = body[0];
|
|
4303
|
-
const command = firstCmd?.name ?? "action";
|
|
4304
|
-
let hash = 5381;
|
|
4305
|
-
for (let i = 0; i < code.length; i++) {
|
|
4306
|
-
hash = (hash << 5) + hash ^ code.charCodeAt(i);
|
|
4307
|
-
}
|
|
4308
|
-
const hashStr = Math.abs(hash).toString(36).slice(0, 4);
|
|
4309
|
-
let id = `${event}_${command}_${hashStr}`;
|
|
4310
|
-
let suffix = 0;
|
|
4311
|
-
while (this.usedIds.has(id)) {
|
|
4312
|
-
id = `${event}_${command}_${hashStr}${suffix++}`;
|
|
4313
|
-
}
|
|
4314
|
-
this.usedIds.add(id);
|
|
4315
|
-
return id;
|
|
4316
|
-
}
|
|
4317
|
-
/**
|
|
4318
|
-
* Create a codegen context.
|
|
4319
|
-
*/
|
|
4320
|
-
createCodegenContext(handlerId, analysis, options) {
|
|
4321
|
-
const requiredHelpers = /* @__PURE__ */ new Set();
|
|
4322
|
-
let idCounter = 0;
|
|
4323
|
-
const exprCodegenRef = { current: null };
|
|
4324
|
-
const ctx = {
|
|
4325
|
-
handlerId,
|
|
4326
|
-
generateId: (prefix = "_id") => `${prefix}_${idCounter++}`,
|
|
4327
|
-
generateExpression: (node) => {
|
|
4328
|
-
if (!exprCodegenRef.current) {
|
|
4329
|
-
exprCodegenRef.current = new ExpressionCodegen(ctx);
|
|
4330
|
-
}
|
|
4331
|
-
return exprCodegenRef.current.generate(node);
|
|
4332
|
-
},
|
|
4333
|
-
implicitTarget: "_ctx.me",
|
|
4334
|
-
localVarDeclarations: "",
|
|
4335
|
-
// A selector is queried each time it is evaluated, as on both engines. The
|
|
4336
|
-
// cache named a variable nothing declared (`_sel__d1_0`: a handler naming
|
|
4337
|
-
// #d1 twice threw), and a declared one goes stale when the handler
|
|
4338
|
-
// replaces the element.
|
|
4339
|
-
canCacheSelector: () => false,
|
|
4340
|
-
getCachedSelector: (selector) => `document.querySelector('${selector}')`,
|
|
4341
|
-
requireHelper: (name) => {
|
|
4342
|
-
requiredHelpers.add(name);
|
|
4343
|
-
},
|
|
4344
|
-
requiredHelpers,
|
|
4345
|
-
analysis,
|
|
4346
|
-
options: { ...DEFAULT_CODEGEN_OPTIONS, ...options.codegen }
|
|
4347
|
-
};
|
|
4348
|
-
return ctx;
|
|
4349
|
-
}
|
|
4350
|
-
/**
|
|
4351
|
-
* Extract event names from generated code.
|
|
4352
|
-
*/
|
|
4353
|
-
extractEvents(code) {
|
|
4354
|
-
const events = [];
|
|
4355
|
-
const match = /function\s+_handler_\w+\s*\(\s*_event\s*\)/.exec(code);
|
|
4356
|
-
if (match) {
|
|
4357
|
-
events.push("click");
|
|
4358
|
-
}
|
|
4359
|
-
return events.length > 0 ? events : ["click"];
|
|
4360
|
-
}
|
|
4361
|
-
/**
|
|
4362
|
-
* Estimate runtime size based on required helpers.
|
|
4363
|
-
*/
|
|
4364
|
-
estimateRuntimeSize(imports) {
|
|
4365
|
-
const helperSizes = {
|
|
4366
|
-
createContext: 200,
|
|
4367
|
-
HALT: 50,
|
|
4368
|
-
EXIT: 50,
|
|
4369
|
-
globals: 100,
|
|
4370
|
-
toggle: 150,
|
|
4371
|
-
toggleAttr: 100,
|
|
4372
|
-
addClass: 80,
|
|
4373
|
-
removeClass: 80,
|
|
4374
|
-
getProp: 120,
|
|
4375
|
-
setProp: 120,
|
|
4376
|
-
getValues: 180,
|
|
4377
|
-
contains: 150,
|
|
4378
|
-
matches: 50,
|
|
4379
|
-
debounce: 150,
|
|
4380
|
-
throttle: 150,
|
|
4381
|
-
wait: 80,
|
|
4382
|
-
send: 150,
|
|
4383
|
-
delegate: 200,
|
|
4384
|
-
fetchJSON: 150,
|
|
4385
|
-
fetchText: 150,
|
|
4386
|
-
fetchHTML: 180,
|
|
4387
|
-
ready: 100
|
|
4388
|
-
};
|
|
4389
|
-
let total = 500;
|
|
4390
|
-
for (const helper of imports) {
|
|
4391
|
-
total += helperSizes[helper] ?? 100;
|
|
4392
|
-
}
|
|
4393
|
-
return total;
|
|
4394
|
-
}
|
|
4395
|
-
};
|
|
4396
|
-
function createCompiler() {
|
|
4397
|
-
return new AOTCompiler();
|
|
4398
|
-
}
|
|
4399
|
-
async function compileHyperscript(code, options) {
|
|
4400
|
-
const compiler = new AOTCompiler();
|
|
4401
|
-
const result = compiler.compileScript(code, options);
|
|
4402
|
-
if (!result.success) {
|
|
4403
|
-
throw new Error(result.errors?.join(", ") ?? "Compilation failed");
|
|
4404
|
-
}
|
|
4405
|
-
return result.code ?? "";
|
|
4406
|
-
}
|
|
4407
|
-
async function createMultilingualCompiler() {
|
|
4408
|
-
const compiler = new AOTCompiler();
|
|
4409
|
-
try {
|
|
4410
|
-
const { createCoreParserAdapter: createCoreParserAdapter2 } = await import("./core-parser-adapter-W5UPZR4X-7CPPQMOH.js");
|
|
4411
|
-
compiler.setParser(await createCoreParserAdapter2());
|
|
4412
|
-
} catch {
|
|
4413
|
-
}
|
|
4414
|
-
try {
|
|
4415
|
-
const { createSemanticAdapter: createSemanticAdapter2 } = await import("./semantic-adapter-JAAALHRO-WXAFQY4T.js");
|
|
4416
|
-
compiler.setSemanticParser(await createSemanticAdapter2());
|
|
4417
|
-
} catch {
|
|
4418
|
-
}
|
|
4419
|
-
return compiler;
|
|
4420
|
-
}
|
|
4421
|
-
var VERSION = "1.0.0";
|
|
4422
|
-
export {
|
|
4423
|
-
AOTCompiler,
|
|
4424
|
-
Analyzer,
|
|
4425
|
-
ConstantFoldingPass,
|
|
4426
|
-
CoreParserAdapter,
|
|
4427
|
-
DeadCodeEliminationPass,
|
|
4428
|
-
EventHandlerCodegen,
|
|
4429
|
-
ExpressionCodegen,
|
|
4430
|
-
HTMLScanner,
|
|
4431
|
-
JSXScanner,
|
|
4432
|
-
LoopUnrollingPass,
|
|
4433
|
-
OptimizationPipeline,
|
|
4434
|
-
SelectorCachingPass,
|
|
4435
|
-
SemanticParserAdapter,
|
|
4436
|
-
SvelteScanner,
|
|
4437
|
-
VERSION,
|
|
4438
|
-
VueScanner,
|
|
4439
|
-
analyze,
|
|
4440
|
-
commandCodegens,
|
|
4441
|
-
compileHyperscript,
|
|
4442
|
-
createCompiler,
|
|
4443
|
-
createCoreParserAdapter,
|
|
4444
|
-
createMultilingualCompiler,
|
|
4445
|
-
createOptimizer,
|
|
4446
|
-
createScanner,
|
|
4447
|
-
createSemanticAdapter,
|
|
4448
|
-
generateBindings,
|
|
4449
|
-
generateCommand,
|
|
4450
|
-
generateEventHandler,
|
|
4451
|
-
generateExpression,
|
|
4452
|
-
generateForEach,
|
|
4453
|
-
generateIf,
|
|
4454
|
-
generateInitialization,
|
|
4455
|
-
generateRepeat,
|
|
4456
|
-
generateWhile,
|
|
4457
|
-
optimize,
|
|
4458
|
-
sanitizeClassName,
|
|
4459
|
-
sanitizeIdentifier,
|
|
4460
|
-
sanitizeSelector,
|
|
4461
|
-
scanFiles
|
|
4462
|
-
};
|
|
4463
|
-
//# sourceMappingURL=dist-BDTQTYGC.js.map
|