@qt-test/apex-dsl-compiler 0.1.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.
@@ -0,0 +1,836 @@
1
+ // src/axml/parser.ts
2
+ function createPosition(state) {
3
+ return {
4
+ line: state.line,
5
+ column: state.column,
6
+ offset: state.pos
7
+ };
8
+ }
9
+ function createLoc(start, end, source) {
10
+ return { start, end, source };
11
+ }
12
+ function parseAXML(source) {
13
+ const state = {
14
+ source,
15
+ pos: 0,
16
+ line: 1,
17
+ column: 0,
18
+ errors: []
19
+ };
20
+ const children = parseChildren(state);
21
+ let ast;
22
+ if (children.length === 0) {
23
+ ast = null;
24
+ } else if (children.length === 1 && children[0].type === "Element") {
25
+ ast = children[0];
26
+ } else {
27
+ ast = {
28
+ type: "Element",
29
+ tag: "block",
30
+ attributes: [],
31
+ directives: [],
32
+ children,
33
+ selfClosing: false,
34
+ isComponent: false,
35
+ start: 0,
36
+ end: source.length,
37
+ loc: createLoc(
38
+ { line: 1, column: 0, offset: 0 },
39
+ { line: state.line, column: state.column, offset: state.pos }
40
+ )
41
+ };
42
+ }
43
+ return { ast, errors: state.errors };
44
+ }
45
+ function parseChildren(state, endTag) {
46
+ const children = [];
47
+ while (state.pos < state.source.length) {
48
+ if (endTag && lookAhead(state, `</${endTag}`)) {
49
+ break;
50
+ }
51
+ const node = parseNode(state);
52
+ if (node) {
53
+ if (node.type === "Text" && children.length > 0 && children[children.length - 1].type === "Text") {
54
+ const prev = children[children.length - 1];
55
+ prev.content += node.content;
56
+ prev.raw += node.raw;
57
+ prev.end = node.end;
58
+ prev.loc.end = node.loc.end;
59
+ } else {
60
+ children.push(node);
61
+ }
62
+ }
63
+ }
64
+ return children;
65
+ }
66
+ function parseNode(state) {
67
+ skipWhitespace(state);
68
+ if (state.pos >= state.source.length) {
69
+ return null;
70
+ }
71
+ if (lookAhead(state, "<!--")) {
72
+ return parseComment(state);
73
+ }
74
+ if (lookAhead(state, "<") && !lookAhead(state, "</")) {
75
+ return parseElement(state);
76
+ }
77
+ return parseText(state);
78
+ }
79
+ function parseElement(state) {
80
+ const start = createPosition(state);
81
+ advance(state);
82
+ const tag = parseTagName(state);
83
+ if (!tag) {
84
+ state.errors.push(`Expected tag name at line ${state.line}`);
85
+ return null;
86
+ }
87
+ const { attributes, directives } = parseAttributes(state);
88
+ skipWhitespace(state);
89
+ const selfClosing = lookAhead(state, "/>");
90
+ if (selfClosing) {
91
+ advance(state, 2);
92
+ } else {
93
+ if (!consume(state, ">")) {
94
+ state.errors.push(`Expected '>' at line ${state.line}`);
95
+ return null;
96
+ }
97
+ const children = parseChildren(state, tag);
98
+ if (!consume(state, `</${tag}`)) {
99
+ state.errors.push(`Expected closing tag </${tag}> at line ${state.line}`);
100
+ }
101
+ skipWhitespace(state);
102
+ consume(state, ">");
103
+ const end2 = createPosition(state);
104
+ return {
105
+ type: "Element",
106
+ tag,
107
+ attributes,
108
+ directives,
109
+ children,
110
+ selfClosing: false,
111
+ isComponent: isCustomComponent(tag),
112
+ start: start.offset,
113
+ end: end2.offset,
114
+ loc: createLoc(start, end2)
115
+ };
116
+ }
117
+ const end = createPosition(state);
118
+ return {
119
+ type: "Element",
120
+ tag,
121
+ attributes,
122
+ directives,
123
+ children: [],
124
+ selfClosing: true,
125
+ isComponent: isCustomComponent(tag),
126
+ start: start.offset,
127
+ end: end.offset,
128
+ loc: createLoc(start, end)
129
+ };
130
+ }
131
+ function parseTagName(state) {
132
+ let name = "";
133
+ while (state.pos < state.source.length) {
134
+ const char = state.source[state.pos];
135
+ if (/[a-zA-Z0-9_-]/.test(char)) {
136
+ name += char;
137
+ advance(state);
138
+ } else {
139
+ break;
140
+ }
141
+ }
142
+ return name;
143
+ }
144
+ function parseAttributes(state) {
145
+ const attributes = [];
146
+ const directives = [];
147
+ while (state.pos < state.source.length) {
148
+ skipWhitespace(state);
149
+ if (lookAhead(state, ">") || lookAhead(state, "/>")) {
150
+ break;
151
+ }
152
+ const start = createPosition(state);
153
+ const name = parseAttributeName(state);
154
+ if (!name) break;
155
+ if (name.startsWith("a:")) {
156
+ const directive = parseDirective(state, name, start);
157
+ if (directive) {
158
+ directives.push(directive);
159
+ }
160
+ continue;
161
+ }
162
+ let value = null;
163
+ let isDynamic = false;
164
+ skipWhitespace(state);
165
+ if (consume(state, "=")) {
166
+ skipWhitespace(state);
167
+ const result = parseAttributeValue(state);
168
+ value = result.value;
169
+ isDynamic = result.isDynamic;
170
+ }
171
+ const end = createPosition(state);
172
+ attributes.push({
173
+ type: "Attribute",
174
+ name,
175
+ value,
176
+ isDynamic,
177
+ start: start.offset,
178
+ end: end.offset,
179
+ loc: createLoc(start, end)
180
+ });
181
+ }
182
+ return { attributes, directives };
183
+ }
184
+ function parseAttributeName(state) {
185
+ let name = "";
186
+ while (state.pos < state.source.length) {
187
+ const char = state.source[state.pos];
188
+ if (/[a-zA-Z0-9_\-:]/.test(char)) {
189
+ name += char;
190
+ advance(state);
191
+ } else {
192
+ break;
193
+ }
194
+ }
195
+ return name;
196
+ }
197
+ function parseAttributeValue(state) {
198
+ const quote = state.source[state.pos];
199
+ if (quote !== '"' && quote !== "'") {
200
+ const start2 = createPosition(state);
201
+ let raw2 = "";
202
+ while (state.pos < state.source.length && !/[\s>]/.test(state.source[state.pos])) {
203
+ raw2 += state.source[state.pos];
204
+ advance(state);
205
+ }
206
+ const end2 = createPosition(state);
207
+ const isDynamic2 = raw2.includes("{{");
208
+ if (isDynamic2) {
209
+ return {
210
+ value: {
211
+ type: "Expression",
212
+ expression: extractExpression(raw2),
213
+ raw: raw2,
214
+ start: start2.offset,
215
+ end: end2.offset,
216
+ loc: createLoc(start2, end2)
217
+ },
218
+ isDynamic: true
219
+ };
220
+ }
221
+ return {
222
+ value: {
223
+ type: "Literal",
224
+ value: raw2,
225
+ raw: raw2,
226
+ start: start2.offset,
227
+ end: end2.offset,
228
+ loc: createLoc(start2, end2)
229
+ },
230
+ isDynamic: false
231
+ };
232
+ }
233
+ advance(state);
234
+ const start = createPosition(state);
235
+ let raw = "";
236
+ while (state.pos < state.source.length && state.source[state.pos] !== quote) {
237
+ raw += state.source[state.pos];
238
+ advance(state);
239
+ }
240
+ const end = createPosition(state);
241
+ advance(state);
242
+ const isDynamic = raw.includes("{{");
243
+ if (isDynamic) {
244
+ return {
245
+ value: {
246
+ type: "Expression",
247
+ expression: extractExpression(raw),
248
+ raw,
249
+ start: start.offset,
250
+ end: end.offset,
251
+ loc: createLoc(start, end)
252
+ },
253
+ isDynamic: true
254
+ };
255
+ }
256
+ return {
257
+ value: {
258
+ type: "Literal",
259
+ value: raw,
260
+ raw,
261
+ start: start.offset,
262
+ end: end.offset,
263
+ loc: createLoc(start, end)
264
+ },
265
+ isDynamic: false
266
+ };
267
+ }
268
+ function parseDirective(state, name, start) {
269
+ const directiveName = name.slice(2);
270
+ let value = null;
271
+ skipWhitespace(state);
272
+ if (consume(state, "=")) {
273
+ skipWhitespace(state);
274
+ const result = parseAttributeValue(state);
275
+ if (result.value?.type === "Expression") {
276
+ value = result.value;
277
+ } else if (result.value?.type === "Literal") {
278
+ value = {
279
+ type: "Expression",
280
+ expression: result.value.value,
281
+ raw: result.value.raw,
282
+ start: result.value.start,
283
+ end: result.value.end,
284
+ loc: result.value.loc
285
+ };
286
+ }
287
+ }
288
+ const end = createPosition(state);
289
+ return {
290
+ type: "Directive",
291
+ name: directiveName,
292
+ value,
293
+ modifiers: [],
294
+ start: start.offset,
295
+ end: end.offset,
296
+ loc: createLoc(start, end)
297
+ };
298
+ }
299
+ function parseText(state) {
300
+ const start = createPosition(state);
301
+ let raw = "";
302
+ while (state.pos < state.source.length) {
303
+ if (lookAhead(state, "<")) {
304
+ break;
305
+ }
306
+ raw += state.source[state.pos];
307
+ advance(state);
308
+ }
309
+ if (!raw) return null;
310
+ const end = createPosition(state);
311
+ const trimmed = raw.trim();
312
+ if (trimmed.startsWith("{{") && trimmed.endsWith("}}") && !trimmed.slice(2, -2).includes("{{")) {
313
+ return {
314
+ type: "Expression",
315
+ expression: trimmed.slice(2, -2).trim(),
316
+ raw,
317
+ start: start.offset,
318
+ end: end.offset,
319
+ loc: createLoc(start, end)
320
+ };
321
+ }
322
+ return {
323
+ type: "Text",
324
+ content: raw,
325
+ raw,
326
+ start: start.offset,
327
+ end: end.offset,
328
+ loc: createLoc(start, end)
329
+ };
330
+ }
331
+ function parseComment(state) {
332
+ const start = createPosition(state);
333
+ advance(state, 4);
334
+ let content = "";
335
+ while (state.pos < state.source.length && !lookAhead(state, "-->")) {
336
+ content += state.source[state.pos];
337
+ advance(state);
338
+ }
339
+ advance(state, 3);
340
+ const end = createPosition(state);
341
+ return {
342
+ type: "Comment",
343
+ content,
344
+ start: start.offset,
345
+ end: end.offset,
346
+ loc: createLoc(start, end)
347
+ };
348
+ }
349
+ function extractExpression(raw) {
350
+ const parts = [];
351
+ let lastIndex = 0;
352
+ const regex = /\{\{(.+?)\}\}/g;
353
+ let match;
354
+ while ((match = regex.exec(raw)) !== null) {
355
+ if (match.index > lastIndex) {
356
+ parts.push(`'${raw.slice(lastIndex, match.index)}'`);
357
+ }
358
+ parts.push(match[1].trim());
359
+ lastIndex = regex.lastIndex;
360
+ }
361
+ if (lastIndex < raw.length) {
362
+ parts.push(`'${raw.slice(lastIndex)}'`);
363
+ }
364
+ return parts.length === 1 ? parts[0] : parts.join(" + ");
365
+ }
366
+ function isCustomComponent(tag) {
367
+ const builtIn = [
368
+ "view",
369
+ "text",
370
+ "image",
371
+ "button",
372
+ "input",
373
+ "textarea",
374
+ "scroll-view",
375
+ "swiper",
376
+ "swiper-item",
377
+ "navigator",
378
+ "block",
379
+ "template",
380
+ "slot",
381
+ "icon",
382
+ "progress",
383
+ "checkbox",
384
+ "checkbox-group",
385
+ "radio",
386
+ "radio-group",
387
+ "switch",
388
+ "slider",
389
+ "picker",
390
+ "picker-view",
391
+ "picker-view-column",
392
+ "form",
393
+ "label",
394
+ "rich-text",
395
+ "web-view",
396
+ "map",
397
+ "canvas",
398
+ "video",
399
+ "audio",
400
+ "camera",
401
+ "live-player",
402
+ "live-pusher"
403
+ ];
404
+ return !builtIn.includes(tag);
405
+ }
406
+ function advance(state, count = 1) {
407
+ for (let i = 0; i < count && state.pos < state.source.length; i++) {
408
+ if (state.source[state.pos] === "\n") {
409
+ state.line++;
410
+ state.column = 0;
411
+ } else {
412
+ state.column++;
413
+ }
414
+ state.pos++;
415
+ }
416
+ }
417
+ function lookAhead(state, str) {
418
+ return state.source.slice(state.pos, state.pos + str.length) === str;
419
+ }
420
+ function consume(state, str) {
421
+ if (lookAhead(state, str)) {
422
+ advance(state, str.length);
423
+ return true;
424
+ }
425
+ return false;
426
+ }
427
+ function skipWhitespace(state) {
428
+ while (state.pos < state.source.length && /\s/.test(state.source[state.pos])) {
429
+ advance(state);
430
+ }
431
+ }
432
+
433
+ // src/axml/compiler.ts
434
+ function compileAXML(source, options = {}) {
435
+ const { ast, errors: parseErrors } = parseAXML(source);
436
+ const ctx = {
437
+ code: "",
438
+ indent: 0,
439
+ components: options.components ?? {},
440
+ usedComponents: /* @__PURE__ */ new Set(),
441
+ usedExpressions: /* @__PURE__ */ new Set(),
442
+ warnings: [],
443
+ errors: [...parseErrors],
444
+ scopeStack: []
445
+ };
446
+ if (!ast) {
447
+ return {
448
+ code: "function render() { return null; }",
449
+ ast: null,
450
+ warnings: ctx.warnings,
451
+ errors: ctx.errors,
452
+ usedComponents: ctx.usedComponents,
453
+ usedExpressions: ctx.usedExpressions
454
+ };
455
+ }
456
+ const renderCode = generateRenderFunction(ast, ctx);
457
+ return {
458
+ code: renderCode,
459
+ ast,
460
+ warnings: ctx.warnings,
461
+ errors: ctx.errors,
462
+ usedComponents: ctx.usedComponents,
463
+ usedExpressions: ctx.usedExpressions
464
+ };
465
+ }
466
+ function generateRenderFunction(ast, ctx) {
467
+ const bodyCode = generateElement(ast, ctx);
468
+ return `function render(_ctx, _cache) {
469
+ const { h, Fragment, renderList, renderSlot, withDirectives, resolveComponent } = _ctx._runtime;
470
+ return ${bodyCode};
471
+ }`;
472
+ }
473
+ function generateElement(node, ctx) {
474
+ const { tag, attributes, directives, children, isComponent } = node;
475
+ const forDirective = directives.find((d) => d.name === "for");
476
+ const ifDirective = directives.find((d) => d.name === "if");
477
+ const elifDirective = directives.find((d) => d.name === "elif");
478
+ const elseDirective = directives.find((d) => d.name === "else");
479
+ if (forDirective) {
480
+ return generateForLoop(node, forDirective, ctx);
481
+ }
482
+ if (ifDirective) {
483
+ return generateConditional(node, ifDirective, "if", ctx);
484
+ }
485
+ if (elifDirective) {
486
+ ctx.warnings.push(`a:elif without preceding a:if at line ${node.loc.start.line}`);
487
+ return generateConditional(node, elifDirective, "elif", ctx);
488
+ }
489
+ if (elseDirective) {
490
+ ctx.warnings.push(`a:else without preceding a:if at line ${node.loc.start.line}`);
491
+ return "null";
492
+ }
493
+ if (tag === "block" || tag === "template") {
494
+ return generateFragment(children, ctx);
495
+ }
496
+ if (tag === "slot") {
497
+ return generateSlot(node, ctx);
498
+ }
499
+ let tagCode;
500
+ if (isComponent) {
501
+ ctx.usedComponents.add(tag);
502
+ const componentPath = ctx.components[tag];
503
+ if (componentPath) {
504
+ tagCode = `resolveComponent("${tag}")`;
505
+ } else {
506
+ tagCode = `resolveComponent("${tag}")`;
507
+ ctx.warnings.push(`Unknown component "${tag}" at line ${node.loc.start.line}`);
508
+ }
509
+ } else {
510
+ tagCode = `"${tag}"`;
511
+ }
512
+ const propsCode = generateProps(attributes, directives, ctx);
513
+ const childrenCode = generateChildren(children, ctx);
514
+ if (childrenCode === "null") {
515
+ if (propsCode === "null") {
516
+ return `h(${tagCode})`;
517
+ }
518
+ return `h(${tagCode}, ${propsCode})`;
519
+ }
520
+ return `h(${tagCode}, ${propsCode}, ${childrenCode})`;
521
+ }
522
+ function generateForLoop(node, directive, ctx) {
523
+ const expression = directive.value?.expression ?? "";
524
+ const forMatch = expression.match(/^\s*(?:\(([^,]+),\s*([^)]+)\)|([^\s]+))\s+in\s+(.+)$/);
525
+ if (!forMatch) {
526
+ ctx.errors.push(`Invalid a:for expression: ${expression}`);
527
+ return "null";
528
+ }
529
+ const itemVar = forMatch[1] || forMatch[3] || "item";
530
+ const indexVar = forMatch[2] || "index";
531
+ const listExpr = forMatch[4];
532
+ const forItemDir = node.directives.find((d) => d.name === "for-item");
533
+ const forIndexDir = node.directives.find((d) => d.name === "for-index");
534
+ const keyDir = node.directives.find((d) => d.name === "key");
535
+ const finalItemVar = forItemDir?.value?.expression ?? itemVar;
536
+ const finalIndexVar = forIndexDir?.value?.expression ?? indexVar;
537
+ const keyExpr = keyDir?.value?.expression ?? finalIndexVar;
538
+ ctx.usedExpressions.add(listExpr);
539
+ ctx.scopeStack.push(finalItemVar, finalIndexVar);
540
+ const filteredDirectives = node.directives.filter(
541
+ (d) => !["for", "for-item", "for-index", "key"].includes(d.name)
542
+ );
543
+ const elementWithoutFor = { ...node, directives: filteredDirectives };
544
+ const elementCode = generateElement(elementWithoutFor, ctx);
545
+ ctx.scopeStack.pop();
546
+ ctx.scopeStack.pop();
547
+ return `renderList(_ctx.${listExpr}, (${finalItemVar}, ${finalIndexVar}) => {
548
+ return ${elementCode};
549
+ }, "${keyExpr}")`;
550
+ }
551
+ function generateConditional(node, directive, type, ctx) {
552
+ const expression = directive.value?.expression ?? "true";
553
+ ctx.usedExpressions.add(expression);
554
+ const filteredDirectives = node.directives.filter((d) => d.name !== type);
555
+ const elementWithoutCondition = { ...node, directives: filteredDirectives };
556
+ const elementCode = generateElement(elementWithoutCondition, ctx);
557
+ return `(${transformExpression(expression, ctx)}) ? ${elementCode} : null`;
558
+ }
559
+ function generateFragment(children, ctx) {
560
+ if (children.length === 0) {
561
+ return "null";
562
+ }
563
+ if (children.length === 1) {
564
+ return generateNode(children[0], ctx);
565
+ }
566
+ const childrenCode = children.map((child) => generateNode(child, ctx)).filter((code) => code !== "null").join(",\n ");
567
+ return `h(Fragment, null, [
568
+ ${childrenCode}
569
+ ])`;
570
+ }
571
+ function generateSlot(node, ctx) {
572
+ const nameAttr = node.attributes.find((a) => a.name === "name");
573
+ const slotName = nameAttr?.value?.type === "Literal" ? nameAttr.value.value : "default";
574
+ const fallback = node.children.length > 0 ? generateChildren(node.children, ctx) : "null";
575
+ const slotProps = node.attributes.filter((a) => a.name !== "name").map((a) => {
576
+ const key = a.name;
577
+ const value = a.value ? a.isDynamic ? transformExpression(a.value.type === "Expression" ? a.value.expression : a.value.value, ctx) : `"${a.value.type === "Literal" ? a.value.value : ""}"` : "true";
578
+ return `${key}: ${value}`;
579
+ }).join(", ");
580
+ const propsCode = slotProps ? `{ ${slotProps} }` : "null";
581
+ return `renderSlot(_ctx.$slots, "${slotName}", ${propsCode}, () => ${fallback})`;
582
+ }
583
+ function generateProps(attributes, directives, ctx) {
584
+ const props = [];
585
+ const events = [];
586
+ for (const attr of attributes) {
587
+ const { name, value, isDynamic } = attr;
588
+ if (name.startsWith("on") || name.startsWith("catch")) {
589
+ const isCatch = name.startsWith("catch");
590
+ const eventName = isCatch ? name.slice(5) : name.slice(2);
591
+ const handler = value?.type === "Expression" ? value.expression : value?.type === "Literal" ? value.value : "";
592
+ if (handler) {
593
+ ctx.usedExpressions.add(handler);
594
+ const eventKey = eventName.toLowerCase();
595
+ events.push(`${eventKey}: { handler: _ctx.${handler}, catch: ${isCatch} }`);
596
+ }
597
+ continue;
598
+ }
599
+ if (name === "class") {
600
+ if (isDynamic && value?.type === "Expression") {
601
+ ctx.usedExpressions.add(value.expression);
602
+ props.push(`class: ${transformExpression(value.expression, ctx)}`);
603
+ } else if (value?.type === "Literal") {
604
+ props.push(`class: "${value.value}"`);
605
+ }
606
+ continue;
607
+ }
608
+ if (name === "style") {
609
+ if (isDynamic && value?.type === "Expression") {
610
+ ctx.usedExpressions.add(value.expression);
611
+ props.push(`style: ${transformExpression(value.expression, ctx)}`);
612
+ } else if (value?.type === "Literal") {
613
+ props.push(`style: "${value.value}"`);
614
+ }
615
+ continue;
616
+ }
617
+ if (name.startsWith("data-")) {
618
+ const dataKey = name.slice(5);
619
+ if (isDynamic && value?.type === "Expression") {
620
+ ctx.usedExpressions.add(value.expression);
621
+ props.push(`"data-${dataKey}": ${transformExpression(value.expression, ctx)}`);
622
+ } else if (value?.type === "Literal") {
623
+ props.push(`"data-${dataKey}": "${value.value}"`);
624
+ } else {
625
+ props.push(`"data-${dataKey}": true`);
626
+ }
627
+ continue;
628
+ }
629
+ if (isDynamic && value?.type === "Expression") {
630
+ ctx.usedExpressions.add(value.expression);
631
+ props.push(`${name}: ${transformExpression(value.expression, ctx)}`);
632
+ } else if (value?.type === "Literal") {
633
+ props.push(`${name}: "${value.value}"`);
634
+ } else if (value === null) {
635
+ props.push(`${name}: true`);
636
+ }
637
+ }
638
+ const refDirective = directives.find((d) => d.name === "ref");
639
+ if (refDirective?.value) {
640
+ props.push(`ref: "${refDirective.value.expression}"`);
641
+ }
642
+ if (events.length > 0) {
643
+ props.push(`_events: { ${events.join(", ")} }`);
644
+ }
645
+ if (props.length === 0) {
646
+ return "null";
647
+ }
648
+ return `{ ${props.join(", ")} }`;
649
+ }
650
+ function generateChildren(children, ctx) {
651
+ const filteredChildren = children.filter((child) => {
652
+ if (child.type === "Comment") return false;
653
+ if (child.type === "Text" && !child.content.trim()) return false;
654
+ return true;
655
+ });
656
+ if (filteredChildren.length === 0) {
657
+ return "null";
658
+ }
659
+ const processedChildren = processConditionalChains(filteredChildren, ctx);
660
+ if (processedChildren.length === 1) {
661
+ return generateNode(processedChildren[0].node, ctx, processedChildren[0].chain);
662
+ }
663
+ const childrenCode = processedChildren.map(({ node, chain }) => generateNode(node, ctx, chain)).join(",\n ");
664
+ return `[
665
+ ${childrenCode}
666
+ ]`;
667
+ }
668
+ function processConditionalChains(children, _ctx) {
669
+ const result = [];
670
+ let currentChain = [];
671
+ for (let i = 0; i < children.length; i++) {
672
+ const child = children[i];
673
+ if (child.type !== "Element") {
674
+ if (currentChain.length > 0) {
675
+ result.push({ node: currentChain[0], chain: currentChain.slice(1) });
676
+ currentChain = [];
677
+ }
678
+ result.push({ node: child });
679
+ continue;
680
+ }
681
+ const hasIf = child.directives.some((d) => d.name === "if");
682
+ const hasElif = child.directives.some((d) => d.name === "elif");
683
+ const hasElse = child.directives.some((d) => d.name === "else");
684
+ if (hasIf) {
685
+ if (currentChain.length > 0) {
686
+ result.push({ node: currentChain[0], chain: currentChain.slice(1) });
687
+ }
688
+ currentChain = [child];
689
+ } else if ((hasElif || hasElse) && currentChain.length > 0) {
690
+ currentChain.push(child);
691
+ if (hasElse) {
692
+ result.push({ node: currentChain[0], chain: currentChain.slice(1) });
693
+ currentChain = [];
694
+ }
695
+ } else {
696
+ if (currentChain.length > 0) {
697
+ result.push({ node: currentChain[0], chain: currentChain.slice(1) });
698
+ currentChain = [];
699
+ }
700
+ result.push({ node: child });
701
+ }
702
+ }
703
+ if (currentChain.length > 0) {
704
+ result.push({ node: currentChain[0], chain: currentChain.slice(1) });
705
+ }
706
+ return result;
707
+ }
708
+ function generateNode(node, ctx, conditionalChain) {
709
+ switch (node.type) {
710
+ case "Element":
711
+ if (conditionalChain && conditionalChain.length > 0) {
712
+ return generateConditionalChain(node, conditionalChain, ctx);
713
+ }
714
+ return generateElement(node, ctx);
715
+ case "Text":
716
+ return generateText(node, ctx);
717
+ case "Expression":
718
+ return generateExpression(node, ctx);
719
+ case "Comment":
720
+ return "null";
721
+ default:
722
+ return "null";
723
+ }
724
+ }
725
+ function generateConditionalChain(ifNode, chain, ctx) {
726
+ const ifDirective = ifNode.directives.find((d) => d.name === "if");
727
+ if (!ifDirective?.value) {
728
+ ctx.errors.push(`Missing condition in a:if at line ${ifNode.loc.start.line}`);
729
+ return "null";
730
+ }
731
+ const ifCondition = transformExpression(ifDirective.value.expression, ctx);
732
+ ctx.usedExpressions.add(ifDirective.value.expression);
733
+ const ifBranchNode = {
734
+ ...ifNode,
735
+ directives: ifNode.directives.filter((d) => d.name !== "if")
736
+ };
737
+ const ifBranch = generateElement(ifBranchNode, ctx);
738
+ let code = `(${ifCondition}) ? ${ifBranch}`;
739
+ for (const chainNode of chain) {
740
+ const elifDirective = chainNode.directives.find((d) => d.name === "elif");
741
+ const elseDirective = chainNode.directives.find((d) => d.name === "else");
742
+ const branchNode = {
743
+ ...chainNode,
744
+ directives: chainNode.directives.filter((d) => !["elif", "else"].includes(d.name))
745
+ };
746
+ const branchCode = generateElement(branchNode, ctx);
747
+ if (elifDirective?.value) {
748
+ const elifCondition = transformExpression(elifDirective.value.expression, ctx);
749
+ ctx.usedExpressions.add(elifDirective.value.expression);
750
+ code += ` : (${elifCondition}) ? ${branchCode}`;
751
+ } else if (elseDirective) {
752
+ code += ` : ${branchCode}`;
753
+ }
754
+ }
755
+ const hasElse = chain.some((n) => n.directives.some((d) => d.name === "else"));
756
+ if (!hasElse) {
757
+ code += " : null";
758
+ }
759
+ return code;
760
+ }
761
+ function generateText(node, ctx) {
762
+ const content = node.content.trim();
763
+ if (!content) return "null";
764
+ if (content.includes("{{")) {
765
+ return generateInterpolatedText(content, ctx);
766
+ }
767
+ return `"${escapeString(content)}"`;
768
+ }
769
+ function generateInterpolatedText(content, ctx) {
770
+ const parts = [];
771
+ let lastIndex = 0;
772
+ const regex = /\{\{(.+?)\}\}/g;
773
+ let match;
774
+ while ((match = regex.exec(content)) !== null) {
775
+ if (match.index > lastIndex) {
776
+ const text = content.slice(lastIndex, match.index);
777
+ if (text) {
778
+ parts.push(`"${escapeString(text)}"`);
779
+ }
780
+ }
781
+ const expr = match[1].trim();
782
+ ctx.usedExpressions.add(expr);
783
+ parts.push(transformExpression(expr, ctx));
784
+ lastIndex = regex.lastIndex;
785
+ }
786
+ if (lastIndex < content.length) {
787
+ const text = content.slice(lastIndex);
788
+ if (text) {
789
+ parts.push(`"${escapeString(text)}"`);
790
+ }
791
+ }
792
+ if (parts.length === 1) {
793
+ return parts[0];
794
+ }
795
+ return parts.join(" + ");
796
+ }
797
+ function generateExpression(node, ctx) {
798
+ ctx.usedExpressions.add(node.expression);
799
+ return transformExpression(node.expression, ctx);
800
+ }
801
+ function transformExpression(expr, ctx) {
802
+ if (/^['"`].*['"`]$/.test(expr)) return expr;
803
+ if (/^\d+(\.\d+)?$/.test(expr)) return expr;
804
+ if (expr === "true" || expr === "false" || expr === "null" || expr === "undefined") {
805
+ return expr;
806
+ }
807
+ const scopeVars = ctx.scopeStack;
808
+ if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(expr)) {
809
+ if (scopeVars.includes(expr)) {
810
+ return expr;
811
+ }
812
+ return `_ctx.${expr}`;
813
+ }
814
+ const rootMatch = expr.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)/);
815
+ if (rootMatch) {
816
+ const root = rootMatch[1];
817
+ if (scopeVars.includes(root)) {
818
+ return expr;
819
+ }
820
+ }
821
+ return expr.replace(/\b([a-zA-Z_$][a-zA-Z0-9_$]*)\b(?!\s*:)/g, (match, id) => {
822
+ const keywords = ["true", "false", "null", "undefined", "this", "typeof", "instanceof", "new", "void", "delete"];
823
+ if (keywords.includes(id) || scopeVars.includes(id)) {
824
+ return match;
825
+ }
826
+ return `_ctx.${id}`;
827
+ });
828
+ }
829
+ function escapeString(str) {
830
+ return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
831
+ }
832
+
833
+ export {
834
+ parseAXML,
835
+ compileAXML
836
+ };