@xaendar/compiler 0.7.19 → 0.7.20

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,17 +1,20 @@
1
1
  /**
2
- * Compiles a template string into a TypeScript render function body.
2
+ * Compiles a template string into a Javascript render function body.
3
3
  *
4
4
  * Runs the three-stage pipeline:
5
5
  * 1. **Lexer** — tokenises the raw template text.
6
6
  * 2. **Parser** — transforms the token stream into an AST.
7
- * 3. **Render generator** — emits TypeScript source lines from the AST.
7
+ * 3. **Render generator** — emits Javascript source lines from the AST.
8
8
  *
9
9
  * @param input - The raw HTML-like template source to compile.
10
10
  * @param cssVariableName - Optional name of the CSS variable to inject
11
11
  * into the generated `adoptedStyleSheets` assignment.
12
- * @returns A string containing the compiled TypeScript render method body.
12
+ * @returns A string containing the compiled Javascript render method body.
13
13
  */
14
- export declare function compile(input: string, cssVariableName?: string): string;
14
+ export declare function compile(input: string, className: string, cssVariableName?: string): {
15
+ javascript: string;
16
+ typescript: string;
17
+ };
15
18
 
16
19
  /**
17
20
  * @fileoverview CompilerHost interface — the contract between the compiler and the environment it runs in.
@@ -1,5 +1,5 @@
1
1
  import { Stack, indent } from "@xaendar/common";
2
- import ts, { ScriptTarget, SyntaxKind, createSourceFile, forEachChild, isExpressionStatement, isIdentifier } from "typescript";
2
+ import { ScriptTarget, SyntaxKind, createSourceFile, forEachChild, isExpressionStatement, isIdentifier, isPropertyAccessExpression, isPropertyAssignment } from "typescript";
3
3
  //#region ../packages/compiler/src/parser/types/node.enum.ts
4
4
  /**
5
5
  * Discriminant values that identify the type of each AST node produced by the parser.
@@ -41,6 +41,10 @@ var ASTNodeType = /* @__PURE__ */ function(ASTNodeType) {
41
41
  * A `@case` or `@default` branch inside a `@switch`.
42
42
  */
43
43
  ASTNodeType[ASTNodeType["Case"] = 8] = "Case";
44
+ /**
45
+ *
46
+ */
47
+ ASTNodeType[ASTNodeType["Import"] = 9] = "Import";
44
48
  return ASTNodeType;
45
49
  }({});
46
50
  //#endregion
@@ -154,7 +158,7 @@ var CompilerContext = class {
154
158
  }
155
159
  };
156
160
  //#endregion
157
- //#region ../packages/compiler/src/generator/utils/render-generator.utils.ts
161
+ //#region ../packages/compiler/src/generator/utils/generator.utils.ts
158
162
  /**
159
163
  * Complete set of JavaScript global identifiers up to ES2026.
160
164
  *
@@ -356,7 +360,7 @@ var ROOT_NODE = "root";
356
360
  * to `node.getText()` for any subtree that contains no resolvable identifiers.
357
361
  *
358
362
  * @param expression - Either a raw identifier string or a validated
359
- * `ts.Expression` node produced by `validateExpression`.
363
+ * `Expression` node produced by `validateExpression`.
360
364
  * @param compilerContext - The active template scope context.
361
365
  * @returns The resolved expression as a JavaScript string ready for codegen.
362
366
  *
@@ -375,8 +379,8 @@ var ROOT_NODE = "root";
375
379
  * // typeof id !== 'boolean' || pippo instanceof HTMLElement
376
380
  * // → typeof this.id !== 'boolean' || this.pippo instanceof HTMLElement
377
381
  */
378
- function resolveExpression(expression, compilerContext, skipResolution = false) {
379
- return emitNode(expression, expression, compilerContext, skipResolution);
382
+ function resolveExpression(expression, compilerContext, options) {
383
+ return emitNode(expression, expression, compilerContext, mapDefaultOptions(options));
380
384
  }
381
385
  /**
382
386
  * Emits the resolved text for a node.
@@ -388,18 +392,18 @@ function resolveExpression(expression, compilerContext, skipResolution = false)
388
392
  * expression (see {@link resolveIdentifierAccess}).
389
393
  * - Otherwise recurses into children and concatenates their output.
390
394
  */
391
- function emitNode(node, parent, compilerContext, skipResolution = false) {
392
- if (ts.isIdentifier(node) && needsResolution(node, parent)) {
395
+ function emitNode(node, parent, compilerContext, options) {
396
+ if (isIdentifier(node) && needsResolution(node, parent)) {
393
397
  const text = node.text;
394
- if (compilerContext.hasUnresolvableIdentifier(text) || skipResolution) return text;
395
- return resolveIdentifierAccess(text, compilerContext);
398
+ if (compilerContext.hasUnresolvableIdentifier(text) || options.skipResolution) return text;
399
+ return resolveIdentifierAccess(text, compilerContext, options.resolver);
396
400
  }
397
401
  if (!containsResolvableIdentifier(node, parent)) return node.getText();
398
402
  const sourceText = node.getSourceFile().text;
399
403
  let result = "";
400
404
  let lastEnd = node.getStart();
401
- ts.forEachChild(node, (child) => {
402
- result = `${result}${sourceText.slice(lastEnd, child.getStart())}${emitNode(child, node, compilerContext, skipResolution)}`;
405
+ forEachChild(node, (child) => {
406
+ result = `${result}${sourceText.slice(lastEnd, child.getStart())}${emitNode(child, node, compilerContext, options)}`;
403
407
  lastEnd = child.getEnd();
404
408
  });
405
409
  return `${result}${sourceText.slice(lastEnd, node.getEnd())}`;
@@ -423,13 +427,13 @@ function emitNode(node, parent, compilerContext, skipResolution = false) {
423
427
  * @param compilerContext - The active template scope context.
424
428
  * @returns The generated code expression that yields the identifier's value.
425
429
  */
426
- function resolveIdentifierAccess(text, compilerContext) {
430
+ function resolveIdentifierAccess(text, compilerContext, resolver) {
427
431
  if (compilerContext.hasIdentifier(text)) {
428
432
  const kind = compilerContext.getIdentifierKind(text);
429
433
  const access = `context.get('${text}')`;
430
434
  return kind === "signal" ? `${access}()` : access;
431
435
  }
432
- return `this.${text}`;
436
+ return `${resolver}.${text}`;
433
437
  }
434
438
  /**
435
439
  * Returns true if the subtree rooted at `node` contains at least one
@@ -438,9 +442,9 @@ function resolveIdentifierAccess(text, compilerContext) {
438
442
  * Short-circuits as soon as one is found to avoid visiting the whole tree.
439
443
  */
440
444
  function containsResolvableIdentifier(node, parent) {
441
- if (ts.isIdentifier(node) && needsResolution(node, parent)) return true;
445
+ if (isIdentifier(node) && needsResolution(node, parent)) return true;
442
446
  let found = false;
443
- ts.forEachChild(node, (child) => {
447
+ forEachChild(node, (child) => {
444
448
  if (!found) found = containsResolvableIdentifier(child, node);
445
449
  });
446
450
  return found;
@@ -451,7 +455,7 @@ function containsResolvableIdentifier(node, parent) {
451
455
  * and not the property-name side of a member access expression.
452
456
  */
453
457
  function needsResolution(node, parent) {
454
- return !(ts.isPropertyAccessExpression(parent) && parent.name === node || ts.isPropertyAssignment(parent) && parent.name === node || GLOBAL_IDENTIFIERS.has(node.text) || !node.text);
458
+ return !(isPropertyAccessExpression(parent) && parent.name === node || isPropertyAssignment(parent) && parent.name === node || GLOBAL_IDENTIFIERS.has(node.text) || !node.text);
455
459
  }
456
460
  /**
457
461
  * Generates a unique variable name for a DOM element based on its tag name
@@ -496,6 +500,12 @@ function getBlockIdentifier(prefix, parentNode, index) {
496
500
  function getIdentifier(prefix, parentNode, index) {
497
501
  return (parentNode !== "root" ? `${parentNode}__${prefix}${index}` : `${prefix}${index}`).replace(/-/g, "_");
498
502
  }
503
+ function mapDefaultOptions(options) {
504
+ return {
505
+ skipResolution: options?.skipResolution ?? false,
506
+ resolver: options?.resolver ?? "this"
507
+ };
508
+ }
499
509
  //#endregion
500
510
  //#region ../packages/compiler/src/generator/states/generate-element.state.ts
501
511
  /**
@@ -513,33 +523,33 @@ function generateElement(node, parentNode, index, compilerContext, anchor) {
513
523
  const events = mapEvents(node.events, compilerContext);
514
524
  const nodeName = getElementIdentifier(node, parentNode, index);
515
525
  const tagName = node.tagName;
516
- const retval = {
526
+ const retVal = {
517
527
  code: [],
518
528
  functionsToProcess: /* @__PURE__ */ new Map()
519
529
  };
520
530
  switch (tagName) {
521
531
  case "svg":
522
- retval.code.push("context.createElement = createSVGElement");
532
+ retVal.code.push("context.createElement = createSVGElement");
523
533
  break;
524
- case "math": retval.code.push("context.createElement = createMATHMLElement");
534
+ case "math": retVal.code.push("context.createElement = createMATHMLElement");
525
535
  }
526
- retval.code.push(`const ${nodeName} = _renderElement(${parentNode}, context, ${anchor}, '${tagName}',`);
527
- attributes.length ? retval.code.push(...indent([
536
+ retVal.code.push(`const ${nodeName} = _renderElement(${parentNode}, context, ${anchor}, '${tagName}',`);
537
+ attributes.length ? retVal.code.push(...indent([
528
538
  "[",
529
539
  ...indent(attributes),
530
540
  "],"
531
- ])) : retval.code[retval.code.length - 1] = `${retval.code[retval.code.length - 1]} [],`;
532
- events.length ? retval.code.push(...indent([
541
+ ])) : retVal.code[retVal.code.length - 1] = `${retVal.code[retVal.code.length - 1]} [],`;
542
+ events.length ? retVal.code.push(...indent([
533
543
  "[",
534
544
  ...indent(events),
535
545
  "]"
536
- ]), ");") : retval.code[retval.code.length - 1] = `${retval.code[retval.code.length - 1]} []);`;
546
+ ]), ");") : retVal.code[retVal.code.length - 1] = `${retVal.code[retVal.code.length - 1]} []);`;
537
547
  switch (tagName) {
538
548
  case "svg":
539
- case "math": retval.code.push("context.createElement = createElement");
549
+ case "math": retVal.code.push("context.createElement = createElement");
540
550
  }
541
551
  if (node.children.length) {
542
- retval.functionsToProcess.set(`${nodeName}Children`, {
552
+ retVal.functionsToProcess.set(`${nodeName}Children`, {
543
553
  fn: {
544
554
  node,
545
555
  parentNode: nodeName,
@@ -548,9 +558,9 @@ function generateElement(node, parentNode, index, compilerContext, anchor) {
548
558
  },
549
559
  args: [nodeName, "parentContext"]
550
560
  });
551
- retval.code.push(`this.${nodeName}Children(${nodeName}, context);`);
561
+ retVal.code.push(`this.${nodeName}Children(${nodeName}, context);`);
552
562
  }
553
- return retval;
563
+ return retVal;
554
564
  }
555
565
  /**
556
566
  * Maps attribute nodes to their corresponding generated code lines.
@@ -591,10 +601,10 @@ function mapEvents(events, compilerContext) {
591
601
  `handler: '${event.handler}',`,
592
602
  "parameters: ["
593
603
  ])];
594
- if (parameters.length) eventCode.push(...indent([...indent(parameters), "]"]), "}");
604
+ if (parameters.length) eventCode.push(...indent([...indent(parameters), "]"]), "},");
595
605
  else {
596
606
  eventCode[eventCode.length - 1] = `${eventCode[eventCode.length - 1]}]`;
597
- eventCode.push("}");
607
+ eventCode.push("},");
598
608
  }
599
609
  return eventCode;
600
610
  }).flat();
@@ -650,11 +660,11 @@ function generateFor(node, parentNode, index, compilerContext) {
650
660
  const iterableExpr = compilerContext.hasIdentifier(iterableSource) ? iterableSource : `this.${iterableSource}`;
651
661
  const itemsName = getTextIdentifier("items", parentNode, index);
652
662
  const counterName = getTextIdentifier("i", parentNode, index);
653
- const indexName = resolveImplicit(node, "$index");
654
- const firstName = resolveImplicit(node, "$first");
655
- const lastName = resolveImplicit(node, "$last");
656
- const evenName = resolveImplicit(node, "$even");
657
- const oddName = resolveImplicit(node, "$odd");
663
+ const indexName = resolveImplicit$1(node, "$index");
664
+ const firstName = resolveImplicit$1(node, "$first");
665
+ const lastName = resolveImplicit$1(node, "$last");
666
+ const evenName = resolveImplicit$1(node, "$even");
667
+ const oddName = resolveImplicit$1(node, "$odd");
658
668
  const forContext = new CompilerContext([
659
669
  node.itemAlias,
660
670
  [indexName, "signal"],
@@ -688,7 +698,7 @@ function generateFor(node, parentNode, index, compilerContext) {
688
698
  "anchor"
689
699
  ]
690
700
  });
691
- retVal.code.push(`_for(${parentNode}, context, () => ${iterableExpr}, ${node.itemAlias} => ${resolveExpression(node.trackExpression, forContext, true)}, this.${forKey}.bind(this));`);
701
+ retVal.code.push(`_for(${parentNode}, context, () => ${iterableExpr}, ${node.itemAlias} => ${resolveExpression(node.trackExpression, forContext, { skipResolution: true })}, this.${forKey}.bind(this));`);
692
702
  return retVal;
693
703
  }
694
704
  /**
@@ -703,7 +713,7 @@ function generateFor(node, parentNode, index, compilerContext) {
703
713
  * @param implicit - The implicit variable to look up (e.g. `'$index'`).
704
714
  * @returns The alias string if one was declared, otherwise `implicit` itself.
705
715
  */
706
- function resolveImplicit(node, implicit) {
716
+ function resolveImplicit$1(node, implicit) {
707
717
  return node.implicitAliases.get(implicit) ?? implicit;
708
718
  }
709
719
  //#endregion
@@ -718,7 +728,7 @@ function generateIf(node, parentNode, index, compilerContext) {
718
728
  const ifKey = getBlockIdentifier("if", parentNode, index);
719
729
  retVal.code.push(...indent([
720
730
  "{",
721
- ...indent([`condition: () => ${resolveExpression(node.conditionNode, compilerContext).toString()},`, `block: this.${ifKey}.bind(this)`]),
731
+ ...indent([`condition: () => ${resolveExpression(node.conditionNode, compilerContext)},`, `block: this.${ifKey}.bind(this)`]),
722
732
  "},"
723
733
  ]));
724
734
  retVal.functionsToProcess.set(ifKey, {
@@ -742,7 +752,7 @@ function generateIf(node, parentNode, index, compilerContext) {
742
752
  const conditionNode = alt.conditionNode;
743
753
  retVal.code.push(...indent([
744
754
  "{",
745
- ...indent([`condition: () => ${resolveExpression(conditionNode, compilerContext).toString()},`, `block: this.${keyElseIf}.bind(this)`]),
755
+ ...indent([`condition: () => ${resolveExpression(conditionNode, compilerContext)},`, `block: this.${keyElseIf}.bind(this)`]),
746
756
  "},"
747
757
  ]));
748
758
  retVal.functionsToProcess.set(keyElseIf, {
@@ -820,7 +830,11 @@ function generateSwitch(node, parentNode, index, compilerContext) {
820
830
  ]
821
831
  });
822
832
  const fnName = `this.${caseKey}.bind(this)`;
823
- retVal.code.push("{", ...indent([`condition: ${caseNode.condition ? `[${caseNode.condition.join(", ")}]` : `null`},`, `block: ${fnName}`]), "},");
833
+ retVal.code.push(...indent([
834
+ "{",
835
+ ...indent([`condition: ${caseNode.condition ? `[${caseNode.condition.join(", ")}]` : `null`},`, `block: ${fnName}`]),
836
+ "},"
837
+ ]));
824
838
  });
825
839
  retVal.code.push("])");
826
840
  return retVal;
@@ -842,10 +856,12 @@ function generateTextAndInterpolation(node, parentNode, _index, compilerContext)
842
856
  return { code: [`${node.type === ASTNodeType.Text ? `_renderLiteralText(${parentNode}, context, '${node.value}');` : `_renderText(${parentNode}, context, () => ${resolveExpression(node.expression, compilerContext)});`}`] };
843
857
  }
844
858
  //#endregion
859
+ //#region ../packages/compiler/src/generator/states/skip-generation.state.ts
860
+ function skipGeneration(_node, _parentNode, _index, _compilerContext) {}
861
+ //#endregion
845
862
  //#region ../packages/compiler/src/generator/generator.ts
846
863
  var Generator = class {
847
864
  _ast;
848
- _cssVariableName;
849
865
  _nodeToProcess = /* @__PURE__ */ new Map();
850
866
  _states = {
851
867
  [ASTNodeType.Text]: generateTextAndInterpolation,
@@ -853,34 +869,41 @@ var Generator = class {
853
869
  [ASTNodeType.Element]: generateElement,
854
870
  [ASTNodeType.If]: generateIf,
855
871
  [ASTNodeType.For]: generateFor,
856
- [ASTNodeType.Switch]: generateSwitch
872
+ [ASTNodeType.Switch]: generateSwitch,
873
+ [ASTNodeType.Import]: skipGeneration
857
874
  };
858
- constructor(_ast, _cssVariableName) {
875
+ constructor(_ast) {
859
876
  this._ast = _ast;
860
- this._cssVariableName = _cssVariableName;
861
877
  }
862
- generate() {
878
+ generate(cssVariableName) {
863
879
  this._nodeToProcess.clear();
864
880
  const compilerContext = new CompilerContext();
865
- const renderFunctions = ["_render() {", ...indent([`const ${ROOT_NODE} = this._root;`, "const context = new Context(this, { createElement: document.createElement.bind(document), get: () => undefined });"])];
866
- if (this._cssVariableName) renderFunctions.push(indent(`${ROOT_NODE}.adoptedStyleSheets = [${this._cssVariableName}];`));
881
+ const generatedCode = ["_render() {", ...indent([`const ${ROOT_NODE} = this._root;`, "const context = new Context(this, { createElement: document.createElement.bind(document), get: () => undefined });"])];
882
+ if (cssVariableName) generatedCode.push(indent(`${ROOT_NODE}.adoptedStyleSheets = [${cssVariableName}];`));
867
883
  for (let i = 0; i < this._ast.length; i++) {
868
- const { code, functionsToProcess } = this._processNode(this._ast[i], ROOT_NODE, i.toString(), compilerContext, null);
869
- functionsToProcess?.forEach((value, key) => this._nodeToProcess.set(key, value));
870
- renderFunctions.push(...indent(code));
884
+ const result = this._processNode(this._ast[i], ROOT_NODE, i.toString(), compilerContext, null);
885
+ if (result) {
886
+ const { code, functionsToProcess } = result;
887
+ functionsToProcess?.forEach((value, key) => this._nodeToProcess.set(key, value));
888
+ generatedCode.push(...indent(code));
889
+ }
871
890
  }
872
- renderFunctions.push(...indent(["return context;"]), "}");
891
+ generatedCode.push(...indent(["return context;"]), "}");
873
892
  for (const [key, fnData] of this._nodeToProcess.entries()) {
874
893
  const { node, parentNode, context, precode, anchor } = fnData.fn;
875
- renderFunctions.push(`\n${key}(${fnData.args?.join(", ")}) {`, ...indent(["const context = new Context(this, parentContext);"]));
876
- if (precode) renderFunctions.push(indent(precode));
877
- renderFunctions.push(...indent([...node.children.map((child, i) => {
878
- const { code, functionsToProcess } = this._processNode(child, parentNode, i.toString(), context, anchor ?? null);
879
- functionsToProcess?.forEach((value, key) => this._nodeToProcess.set(key, value));
880
- return code;
894
+ generatedCode.push(`\n${key} (${fnData.args?.join(", ")}) {`, ...indent(["const context = new Context(this, parentContext);"]));
895
+ if (precode) generatedCode.push(indent(precode));
896
+ generatedCode.push(...indent([...node.children.map((child, i) => {
897
+ const result = this._processNode(child, parentNode, i.toString(), context, anchor ?? null);
898
+ if (result) {
899
+ const { code, functionsToProcess } = result;
900
+ functionsToProcess?.forEach((value, key) => this._nodeToProcess.set(key, value));
901
+ return code;
902
+ }
903
+ return "";
881
904
  }).flat(), fnData.fn.isForBody ? "return { context, update };" : "return context;"]), "}");
882
905
  }
883
- return renderFunctions.join("\n");
906
+ return generatedCode.join("\n");
884
907
  }
885
908
  _processNode(node, parentNode, index, compilerContext, anchor) {
886
909
  const state = this._states[node.type];
@@ -959,6 +982,11 @@ var LexerState = /* @__PURE__ */ function(LexerState) {
959
982
  * Consuming a template-literal string inside `` {`...`} ``.
960
983
  */
961
984
  LexerState["INTERPOLATION_LITERAL"] = "interpolation-literal";
985
+ /**
986
+ * Consuming an import statement `@import { X, Y, ... }
987
+ */
988
+ LexerState["IMPORT"] = "import";
989
+ LexerState["IMPORT_PATH"] = "import-path";
962
990
  return LexerState;
963
991
  }({});
964
992
  //#endregion
@@ -1051,10 +1079,12 @@ var TokenType = /* @__PURE__ */ function(TokenType) {
1051
1079
  * The closing `}` of a flow-control block body.
1052
1080
  */
1053
1081
  TokenType[TokenType["BLOCK_CLOSE"] = 20] = "BLOCK_CLOSE";
1082
+ TokenType[TokenType["IMPORT"] = 21] = "IMPORT";
1083
+ TokenType[TokenType["IMPORT_PATH"] = 22] = "IMPORT_PATH";
1054
1084
  /**
1055
1085
  * Sentinel token emitted when the end of the input is reached.
1056
1086
  */
1057
- TokenType[TokenType["EOF"] = 21] = "EOF";
1087
+ TokenType[TokenType["EOF"] = 23] = "EOF";
1058
1088
  return TokenType;
1059
1089
  }({});
1060
1090
  //#endregion
@@ -1397,6 +1427,9 @@ function lexFlowControl(cursor, _context) {
1397
1427
  state: LexerState.FLOW_CONTROL_BLOCK,
1398
1428
  tokens: [{ type: TokenType.DEFAULT }]
1399
1429
  };
1430
+ } else if (cursor.peekMatch("import ")) {
1431
+ cursor.advance(7);
1432
+ retVal = { state: LexerState.IMPORT };
1400
1433
  }
1401
1434
  return retVal;
1402
1435
  }
@@ -1427,6 +1460,81 @@ function lexFlowControlBlock(cursor, _context) {
1427
1460
  };
1428
1461
  }
1429
1462
  //#endregion
1463
+ //#region ../packages/compiler/src/lexer/states/lex-import-path.state.ts
1464
+ function lexImportPath(cursor, _context) {
1465
+ let read = true;
1466
+ let path = "";
1467
+ let retVal;
1468
+ let singleQuote = false;
1469
+ cursor.skipSpaces();
1470
+ if (!cursor.peekMatch("from")) throw new Error(`Expected from keywork after list of imports at ${cursor.currentChar}`);
1471
+ cursor.advance(4);
1472
+ cursor.skipSpaces();
1473
+ cursor.advance();
1474
+ switch (cursor.currentChar.code) {
1475
+ case 39:
1476
+ singleQuote = true;
1477
+ break;
1478
+ case 34: break;
1479
+ default: throw new Error(`Import statement must start with ' or ".\nFound character ${cursor.currentChar.value} at ${cursor.formattedPosition}`);
1480
+ }
1481
+ const delimiter = singleQuote ? 39 : 34;
1482
+ while (read) switch (cursor.peek()) {
1483
+ case delimiter:
1484
+ cursor.advance();
1485
+ read = false;
1486
+ retVal = {
1487
+ state: LexerState.TEXT,
1488
+ tokens: [{
1489
+ type: TokenType.IMPORT_PATH,
1490
+ parts: [path]
1491
+ }]
1492
+ };
1493
+ break;
1494
+ default:
1495
+ cursor.advance();
1496
+ path = `${path}${cursor.currentChar.value}`;
1497
+ }
1498
+ return retVal;
1499
+ }
1500
+ //#endregion
1501
+ //#region ../packages/compiler/src/lexer/states/lex-import.state.ts
1502
+ function lexImport(cursor, _context) {
1503
+ let read = true;
1504
+ let importValue = "";
1505
+ let retVal = {
1506
+ state: LexerState.IMPORT_PATH,
1507
+ tokens: []
1508
+ };
1509
+ cursor.skipSpaces();
1510
+ cursor.advance();
1511
+ if (cursor.currentChar.code !== 123) throw new Error(`Expected { after @import at ${cursor.formattedPosition}`);
1512
+ while (read) switch (cursor.peek()) {
1513
+ case 32:
1514
+ cursor.skipSpaces();
1515
+ break;
1516
+ case 44:
1517
+ addImport(retVal, cursor, importValue);
1518
+ importValue = "";
1519
+ break;
1520
+ case 125:
1521
+ addImport(retVal, cursor, importValue);
1522
+ read = false;
1523
+ break;
1524
+ default:
1525
+ cursor.advance();
1526
+ importValue = `${importValue}${cursor.currentChar.value}`;
1527
+ }
1528
+ return retVal;
1529
+ }
1530
+ function addImport(retVal, cursor, value) {
1531
+ cursor.advance();
1532
+ retVal.tokens.push({
1533
+ type: TokenType.IMPORT,
1534
+ parts: [value]
1535
+ });
1536
+ }
1537
+ //#endregion
1430
1538
  //#region ../packages/compiler/src/lexer/states/lex-interpolation-expression.state.ts
1431
1539
  /**
1432
1540
  * Consumes a JavaScript expression interpolation `{ expression }`, tracking nested
@@ -1456,10 +1564,7 @@ function lexInterpolationExpression(cursor, context) {
1456
1564
  let state;
1457
1565
  switch (previousState) {
1458
1566
  case LexerState.ATTRIBUTE:
1459
- if (cursor.peek() !== 34) {
1460
- const { row, column } = cursor.position;
1461
- throw new Error(`Interpolation must be end with double quotes '"' Found ${String.fromCharCode(cursor.peek())} at ${cursor.formattedPosition}`);
1462
- }
1567
+ if (cursor.peek() !== 34) throw new Error(`Interpolation must end with double quotes '"' Found ${String.fromCharCode(cursor.peek())} at ${cursor.formattedPosition}`);
1463
1568
  cursor.advance();
1464
1569
  state = LexerState.TAG_BODY;
1465
1570
  break;
@@ -1516,6 +1621,8 @@ function lexInterpolationliteral(cursor, context) {
1516
1621
  let state;
1517
1622
  switch (previousState) {
1518
1623
  case LexerState.ATTRIBUTE:
1624
+ if (cursor.peek() !== 34) throw new Error(`Attribute interpolation expression must end with double quotes at ${cursor.formattedPosition}`);
1625
+ cursor.advance();
1519
1626
  state = LexerState.TAG_BODY;
1520
1627
  break;
1521
1628
  case LexerState.TEXT: state = LexerState.TEXT;
@@ -1663,7 +1770,7 @@ function lexTagOpenEnd(cursor, _context) {
1663
1770
  parts: []
1664
1771
  }]
1665
1772
  };
1666
- } else throw new Error(`Unexpected character ${nextChar} for closing tag.\nExpected />\nRead of /${String.fromCharCode(nextChar)}\nAt line ${cursor.position.row + 1} col ${cursor.position.column + 1}`);
1773
+ } else throw new Error(`Unexpected character ${nextChar} for closing tag.\nExpected />\nRead of /${String.fromCharCode(nextChar)} at ${cursor.formattedPosition}`);
1667
1774
  }
1668
1775
  return retVal;
1669
1776
  }
@@ -1972,7 +2079,9 @@ var Lexer = class {
1972
2079
  [LexerState.FLOW_CONTROL_BLOCK]: lexFlowControlBlock,
1973
2080
  [LexerState.INTERPOLATION]: lexInterpolation,
1974
2081
  [LexerState.INTERPOLATION_EXPRESSION]: lexInterpolationExpression,
1975
- [LexerState.INTERPOLATION_LITERAL]: lexInterpolationliteral
2082
+ [LexerState.INTERPOLATION_LITERAL]: lexInterpolationliteral,
2083
+ [LexerState.IMPORT]: lexImport,
2084
+ [LexerState.IMPORT_PATH]: lexImportPath
1976
2085
  };
1977
2086
  /**
1978
2087
  * Creates a new Lexer instance for the given template content.
@@ -2043,7 +2152,7 @@ var ParserCursor = class {
2043
2152
  /**
2044
2153
  * Returns a read-only snapshot of the current token.
2045
2154
  */
2046
- getCcurrentToken() {
2155
+ getCurrentToken() {
2047
2156
  return this._currentToken;
2048
2157
  }
2049
2158
  /**
@@ -2241,6 +2350,7 @@ function isAllowedNode(node) {
2241
2350
  case SyntaxKind.ShorthandPropertyAssignment:
2242
2351
  case SyntaxKind.SpreadAssignment:
2243
2352
  case SyntaxKind.SpreadElement:
2353
+ case SyntaxKind.TaggedTemplateExpression:
2244
2354
  case SyntaxKind.SyntaxList: return true;
2245
2355
  default: return false;
2246
2356
  }
@@ -2259,7 +2369,6 @@ function buildDisallowedMessage(node) {
2259
2369
  case SyntaxKind.NewExpression: return "'new' is not allowed inside template expressions.";
2260
2370
  case SyntaxKind.ArrowFunction:
2261
2371
  case SyntaxKind.FunctionExpression: return "Function expressions are not allowed inside template expressions.";
2262
- case SyntaxKind.TaggedTemplateExpression: return "Tagged template expressions are not allowed inside template expressions.";
2263
2372
  case SyntaxKind.BinaryExpression: return isAssignmentOperator(node.operatorToken.kind) ? "Assignments are not allowed inside template expressions. Use @const to declare local template variables instead." : `'${SyntaxKind[node.kind]}' is not allowed inside template expressions.`;
2264
2373
  default: return `'${SyntaxKind[node.kind]}' is not allowed inside template expressions.`;
2265
2374
  }
@@ -2340,7 +2449,7 @@ function parseEvent(cursor, _parseNode, token) {
2340
2449
  const parameters = new Array();
2341
2450
  while (cursor.peek().type === TokenType.EVENT_PAREMETER) {
2342
2451
  cursor.advance();
2343
- parameters.push(validateExpression(cursor.getCcurrentToken().value.parts[0]).node);
2452
+ parameters.push(validateExpression(cursor.getCurrentToken().value.parts[0]).node);
2344
2453
  }
2345
2454
  return {
2346
2455
  name,
@@ -2640,6 +2749,21 @@ function parseIfOrElseIf(cursor, parseNode, token) {
2640
2749
  };
2641
2750
  }
2642
2751
  //#endregion
2752
+ //#region ../packages/compiler/src/parser/states/parse-import.state.ts
2753
+ function parseImport(cursor, _parseNode, _token) {
2754
+ const imports = new Array();
2755
+ while (cursor.peek().type === TokenType.IMPORT) {
2756
+ cursor.advance();
2757
+ imports.push(cursor.getCurrentToken().value.parts[0]);
2758
+ }
2759
+ cursor.advance();
2760
+ return {
2761
+ type: ASTNodeType.Import,
2762
+ values: imports,
2763
+ path: cursor.getCurrentToken().value.parts[0]
2764
+ };
2765
+ }
2766
+ //#endregion
2643
2767
  //#region ../packages/compiler/src/parser/states/parse-switch.state.ts
2644
2768
  /**
2645
2769
  * Parses a `@switch` directive, consuming the SWITCH token, the CONDITION token,
@@ -2722,7 +2846,6 @@ function parseText(cursor, _parseNode, token) {
2722
2846
  * to the Lexer rules. Parsing errors are thrown as exceptions.
2723
2847
  */
2724
2848
  var Parser = class {
2725
- tokens;
2726
2849
  /**
2727
2850
  * Internal cursor for navigating tokens
2728
2851
  */
@@ -2738,7 +2861,8 @@ var Parser = class {
2738
2861
  [TokenType.TAG_OPEN_NAME]: parseElement,
2739
2862
  [TokenType.IF]: parseIfControlFlow,
2740
2863
  [TokenType.FOR]: parseForControlFlow,
2741
- [TokenType.SWITCH]: parseSwitchControlFlow
2864
+ [TokenType.SWITCH]: parseSwitchControlFlow,
2865
+ [TokenType.IMPORT]: parseImport
2742
2866
  };
2743
2867
  /**
2744
2868
  * Creates a new Parser instance.
@@ -2746,8 +2870,7 @@ var Parser = class {
2746
2870
  * @param tokens - Array of tokens produced by the Lexer.
2747
2871
  */
2748
2872
  constructor(tokens) {
2749
- this.tokens = tokens;
2750
- this._cursor = new ParserCursor(this.tokens);
2873
+ this._cursor = new ParserCursor(tokens);
2751
2874
  }
2752
2875
  /**
2753
2876
  * Entry point for parsing the token stream into AST nodes.
@@ -2777,22 +2900,230 @@ var Parser = class {
2777
2900
  }
2778
2901
  };
2779
2902
  //#endregion
2903
+ //#region ../packages/compiler/src/type-checker/states/type-check-element.state.ts
2904
+ function typeCheckElement(node, parentNode, index, context) {
2905
+ const nodeName = getElementIdentifier(node, parentNode.identifier, index);
2906
+ const retVal = {
2907
+ code: [`let ${nodeName}!: HTMLElement`],
2908
+ functionsToProcess: /* @__PURE__ */ new Map()
2909
+ };
2910
+ node.attributes.forEach(({ name, value }) => {
2911
+ if (!(typeof value === "string")) retVal.code.push(`const ${nodeName}_${name} = ${resolveExpression(value.expression, context, { resolver: "root" })};`);
2912
+ });
2913
+ context.addUnresolvableIdentifier("$event");
2914
+ node.events.forEach(({ name, handler, parameters }) => {
2915
+ let parsedEventParameter = false;
2916
+ const mappedParameters = parameters.map((parameter) => {
2917
+ const resolvedParameter = resolveExpression(parameter, context, { resolver: "root" });
2918
+ if (!parsedEventParameter && resolvedParameter === "$event") {
2919
+ parsedEventParameter = true;
2920
+ return `$event`;
2921
+ } else return `${resolvedParameter}`;
2922
+ }).join(", ");
2923
+ const beginning = parsedEventParameter ? "($event)" : "()";
2924
+ retVal.code.push(`const ${nodeName}_${name} = ${beginning} => root.${handler}(${mappedParameters})`);
2925
+ });
2926
+ return retVal;
2927
+ }
2928
+ //#endregion
2929
+ //#region ../packages/compiler/src/type-checker/states/type-check-for.state.ts
2930
+ function typeCheckFor(node, parentNode, index, compilerContext) {
2931
+ const retVal = {
2932
+ code: [],
2933
+ functionsToProcess: /* @__PURE__ */ new Map()
2934
+ };
2935
+ const iterableSource = node.iterableSource;
2936
+ const iterableExpr = compilerContext.hasIdentifier(iterableSource) ? iterableSource : `root.${iterableSource}`;
2937
+ const itemsName = getTextIdentifier("items", parentNode.identifier, index);
2938
+ const counterName = getTextIdentifier("i", parentNode.identifier, index);
2939
+ const indexName = resolveImplicit(node, "$index");
2940
+ const firstName = resolveImplicit(node, "$first");
2941
+ const lastName = resolveImplicit(node, "$last");
2942
+ const evenName = resolveImplicit(node, "$even");
2943
+ const oddName = resolveImplicit(node, "$odd");
2944
+ const forContext = new CompilerContext([
2945
+ node.itemAlias,
2946
+ [indexName, "signal"],
2947
+ [firstName, "signal"],
2948
+ [lastName, "signal"],
2949
+ [evenName, "signal"],
2950
+ [oddName, "signal"]
2951
+ ], compilerContext);
2952
+ const forKey = getBlockIdentifier("for", parentNode.identifier, index);
2953
+ retVal.functionsToProcess.set(forKey, {
2954
+ fn: {
2955
+ node,
2956
+ parentNode,
2957
+ context: forContext
2958
+ },
2959
+ args: [`${itemsName}: typeof ${iterableExpr}`, `${counterName}: number`]
2960
+ });
2961
+ retVal.code.push(`const ${forKey}_${itemsName} = ${iterableExpr}`);
2962
+ retVal.code.push(`const ${forKey}_${node.itemAlias} = ${resolveExpression(node.trackExpression, forContext, {
2963
+ skipResolution: true,
2964
+ resolver: `${iterableExpr}`
2965
+ })}`);
2966
+ return retVal;
2967
+ }
2968
+ /**
2969
+ * Resolves the name that should be used in generated code for a given
2970
+ * implicit variable.
2971
+ *
2972
+ * If the template declared an explicit alias for the variable
2973
+ * (e.g. `; $index = i`) that alias is returned. Otherwise the default
2974
+ * implicit variable name (e.g. `$index`) is used.
2975
+ *
2976
+ * @param node - The `ForNode` whose implicit alias map is consulted.
2977
+ * @param implicit - The implicit variable to look up (e.g. `'$index'`).
2978
+ * @returns The alias string if one was declared, otherwise `implicit` itself.
2979
+ */
2980
+ function resolveImplicit(node, implicit) {
2981
+ return node.implicitAliases.get(implicit) ?? implicit;
2982
+ }
2983
+ //#endregion
2984
+ //#region ../packages/compiler/src/type-checker/states/type-check-if.state.ts
2985
+ function typeCheckIf(node, parentNode, index, compilerContext) {
2986
+ const ifContext = new CompilerContext([], compilerContext);
2987
+ const retVal = {
2988
+ code: [],
2989
+ functionsToProcess: /* @__PURE__ */ new Map()
2990
+ };
2991
+ const ifKey = getBlockIdentifier("if", parentNode.identifier, index);
2992
+ retVal.code.push(`const ${ifKey} = ${resolveExpression(node.conditionNode, compilerContext, { resolver: "root" })};`);
2993
+ retVal.functionsToProcess.set(ifKey, { fn: {
2994
+ node,
2995
+ parentNode,
2996
+ context: ifContext
2997
+ } });
2998
+ let alt = node.alternate;
2999
+ let i = 0;
3000
+ while (alt?.type === ASTNodeType.ElseIf) {
3001
+ const elseIfContext = new CompilerContext([], compilerContext);
3002
+ const keyElseIf = getBlockIdentifier("elseIf", parentNode.identifier, `${index}_${i}`);
3003
+ const conditionNode = alt.conditionNode;
3004
+ retVal.code.push(`const ${keyElseIf} = ${resolveExpression(conditionNode, compilerContext, { resolver: "root" })};`);
3005
+ retVal.functionsToProcess.set(keyElseIf, { fn: {
3006
+ node: alt,
3007
+ parentNode,
3008
+ context: elseIfContext
3009
+ } });
3010
+ alt = alt.alternate;
3011
+ i++;
3012
+ }
3013
+ if (alt) {
3014
+ const elseContext = new CompilerContext([], compilerContext);
3015
+ const keyElse = getBlockIdentifier("else", parentNode.identifier, index);
3016
+ retVal.functionsToProcess.set(keyElse, { fn: {
3017
+ node: alt,
3018
+ parentNode,
3019
+ context: elseContext
3020
+ } });
3021
+ }
3022
+ return retVal;
3023
+ }
3024
+ //#endregion
3025
+ //#region ../packages/compiler/src/type-checker/states/type-check-switch.state.ts
3026
+ function typeCheckSwitch(node, parentNode, index, compilerContext) {
3027
+ const retVal = {
3028
+ code: [],
3029
+ functionsToProcess: /* @__PURE__ */ new Map()
3030
+ };
3031
+ const expression = resolveExpression(node.expression, compilerContext, { resolver: "root" });
3032
+ const keySwitch = getBlockIdentifier("switch", parentNode.identifier, index);
3033
+ retVal.code.push(`const ${keySwitch} = ${expression};`);
3034
+ node.children.forEach((caseNode, i) => {
3035
+ const caseContext = new CompilerContext([], compilerContext);
3036
+ const caseKey = caseNode.condition ? getBlockIdentifier("case", parentNode.identifier, `${index}_${i}`) : getBlockIdentifier("default", parentNode.identifier, index);
3037
+ retVal.functionsToProcess.set(caseKey, { fn: {
3038
+ node: caseNode,
3039
+ parentNode,
3040
+ context: caseContext
3041
+ } });
3042
+ caseNode.condition?.forEach((condition, i) => retVal.code.push(`const ${caseKey}_${i}: typeof ${expression} = ${condition};`));
3043
+ });
3044
+ return retVal;
3045
+ }
3046
+ //#endregion
3047
+ //#region ../packages/compiler/src/type-checker/states/type-check-text-and-interpolation.state.ts
3048
+ function typeCheckTextAndInterpolation(node, parentNode, index, compilerContext) {
3049
+ return { code: [`const ${getTextIdentifier("text", parentNode.identifier, index)} = ${node.type === ASTNodeType.Text ? node.value : resolveExpression(node.expression, compilerContext, { resolver: "root" })};`] };
3050
+ }
3051
+ //#endregion
3052
+ //#region ../packages/compiler/src/type-checker/type-checker.ts
3053
+ var TypeChecker = class {
3054
+ _ast;
3055
+ _nodeToProcess = /* @__PURE__ */ new Map();
3056
+ _states = {
3057
+ [ASTNodeType.Text]: typeCheckTextAndInterpolation,
3058
+ [ASTNodeType.Interpolation]: typeCheckTextAndInterpolation,
3059
+ [ASTNodeType.Element]: typeCheckElement,
3060
+ [ASTNodeType.If]: typeCheckIf,
3061
+ [ASTNodeType.For]: typeCheckFor,
3062
+ [ASTNodeType.Switch]: typeCheckSwitch,
3063
+ [ASTNodeType.Import]: skipGeneration
3064
+ };
3065
+ constructor(_ast) {
3066
+ this._ast = _ast;
3067
+ }
3068
+ generate(className) {
3069
+ this._nodeToProcess.clear();
3070
+ const context = new CompilerContext();
3071
+ const generatedCode = ["function typeCheck() {"];
3072
+ for (let i = 0; i < this._ast.length; i++) {
3073
+ const result = this._processNode(this._ast[i], {
3074
+ identifier: ROOT_NODE,
3075
+ type: className
3076
+ }, i.toString(), context);
3077
+ if (result) {
3078
+ const { code, functionsToProcess } = result;
3079
+ functionsToProcess?.forEach((value, key) => this._nodeToProcess.set(key, value));
3080
+ generatedCode.push(...indent(code));
3081
+ }
3082
+ }
3083
+ generatedCode.push("}");
3084
+ for (const [key, fnData] of this._nodeToProcess.entries()) {
3085
+ const { node, parentNode, precode, context } = fnData.fn;
3086
+ generatedCode.push(`\nfunction ${key} (${fnData.args?.join(", ") ?? ""}) {`);
3087
+ if (precode) generatedCode.push(indent(precode));
3088
+ generatedCode.push(...indent([...node.children.map((child, i) => {
3089
+ const result = this._processNode(child, parentNode, i.toString(), context);
3090
+ if (result) {
3091
+ const { code, functionsToProcess } = result;
3092
+ functionsToProcess?.forEach((value, key) => this._nodeToProcess.set(key, value));
3093
+ return code;
3094
+ }
3095
+ return "";
3096
+ }).flat()]), "}");
3097
+ }
3098
+ return generatedCode.join("\n");
3099
+ }
3100
+ _processNode(node, parentNode, index, context) {
3101
+ const state = this._states[node.type];
3102
+ if (!state) throw new Error(`[Parser] No transition function for token type ${ASTNodeType[node.type]}`);
3103
+ return state(node, parentNode, index, context);
3104
+ }
3105
+ };
3106
+ //#endregion
2780
3107
  //#region ../packages/compiler/src/compile.ts
2781
3108
  /**
2782
- * Compiles a template string into a TypeScript render function body.
3109
+ * Compiles a template string into a Javascript render function body.
2783
3110
  *
2784
3111
  * Runs the three-stage pipeline:
2785
3112
  * 1. **Lexer** — tokenises the raw template text.
2786
3113
  * 2. **Parser** — transforms the token stream into an AST.
2787
- * 3. **Render generator** — emits TypeScript source lines from the AST.
3114
+ * 3. **Render generator** — emits Javascript source lines from the AST.
2788
3115
  *
2789
3116
  * @param input - The raw HTML-like template source to compile.
2790
3117
  * @param cssVariableName - Optional name of the CSS variable to inject
2791
3118
  * into the generated `adoptedStyleSheets` assignment.
2792
- * @returns A string containing the compiled TypeScript render method body.
3119
+ * @returns A string containing the compiled Javascript render method body.
2793
3120
  */
2794
- function compile(input, cssVariableName) {
2795
- return new Generator(new Parser(new Lexer(input).tokenize()).parse(), cssVariableName).generate();
3121
+ function compile(input, className, cssVariableName) {
3122
+ const nodes = new Parser(new Lexer(input).tokenize()).parse();
3123
+ return {
3124
+ javascript: new Generator(nodes).generate(cssVariableName),
3125
+ typescript: new TypeChecker(nodes).generate(className)
3126
+ };
2796
3127
  }
2797
3128
  //#endregion
2798
3129
  export { compile };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xaendar/compiler",
3
- "version": "0.7.19",
3
+ "version": "0.7.20",
4
4
  "description": "A library for transpiling Xaendar Templates into JavaScript code",
5
5
  "sideEffects": false,
6
6
  "type": "module",
@@ -16,8 +16,8 @@
16
16
  }
17
17
  },
18
18
  "dependencies": {
19
- "@xaendar/common": "0.7.19",
20
- "@xaendar/types": "0.7.19",
19
+ "@xaendar/common": "0.7.20",
20
+ "@xaendar/types": "0.7.20",
21
21
  "typescript": "^6.0.3"
22
22
  }
23
23
  }