@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.
@@ -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(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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