agency-lang 0.0.85 → 0.0.86

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.
Files changed (149) hide show
  1. package/dist/lib/backends/agencyGenerator.d.ts +2 -1
  2. package/dist/lib/backends/agencyGenerator.js +52 -16
  3. package/dist/lib/backends/agencyGenerator.test.js +8 -8
  4. package/dist/lib/backends/typescriptBuilder.d.ts +6 -0
  5. package/dist/lib/backends/typescriptBuilder.js +79 -10
  6. package/dist/lib/cli/commands.d.ts +1 -1
  7. package/dist/lib/cli/commands.js +10 -9
  8. package/dist/lib/cli/test.js +24 -11
  9. package/dist/lib/cli/util.js +6 -3
  10. package/dist/lib/config.d.ts +2 -1
  11. package/dist/lib/config.js +1 -35
  12. package/dist/lib/importPaths.d.ts +34 -0
  13. package/dist/lib/importPaths.js +71 -0
  14. package/dist/lib/importPaths.test.d.ts +1 -0
  15. package/dist/lib/importPaths.test.js +78 -0
  16. package/dist/lib/parser.d.ts +1 -1
  17. package/dist/lib/parser.js +11 -7
  18. package/dist/lib/parser.test.js +3 -68
  19. package/dist/lib/parsers/access.js +4 -3
  20. package/dist/lib/parsers/access.test.js +24 -24
  21. package/dist/lib/parsers/assignment.test.js +2 -2
  22. package/dist/lib/parsers/binop.js +14 -56
  23. package/dist/lib/parsers/binop.test.js +20 -20
  24. package/dist/lib/parsers/body.test.js +1 -1
  25. package/dist/lib/parsers/comment.js +2 -2
  26. package/dist/lib/parsers/comment.test.js +1 -1
  27. package/dist/lib/parsers/dataStructures.js +5 -6
  28. package/dist/lib/parsers/dataStructures.test.js +3 -3
  29. package/dist/lib/parsers/debuggerStatement.d.ts +3 -0
  30. package/dist/lib/parsers/debuggerStatement.js +11 -0
  31. package/dist/lib/parsers/debuggerStatement.test.d.ts +1 -0
  32. package/dist/lib/parsers/debuggerStatement.test.js +43 -0
  33. package/dist/lib/parsers/expression.d.ts +3 -0
  34. package/dist/lib/parsers/expression.js +121 -0
  35. package/dist/lib/parsers/expression.test.d.ts +1 -0
  36. package/dist/lib/parsers/expression.test.js +299 -0
  37. package/dist/lib/parsers/forLoop.test.js +1 -1
  38. package/dist/lib/parsers/function.js +21 -17
  39. package/dist/lib/parsers/function.test.js +8 -8
  40. package/dist/lib/parsers/functionCall.js +4 -7
  41. package/dist/lib/parsers/functionCall.test.js +42 -7
  42. package/dist/lib/parsers/handleBlock.test.js +1 -1
  43. package/dist/lib/parsers/ifElse.test.js +1 -1
  44. package/dist/lib/parsers/importStatement.js +22 -8
  45. package/dist/lib/parsers/importStatement.test.js +27 -27
  46. package/dist/lib/parsers/keyword.test.js +1 -1
  47. package/dist/lib/parsers/literals.js +3 -8
  48. package/dist/lib/parsers/literals.test.js +63 -8
  49. package/dist/lib/parsers/loc.d.ts +9 -0
  50. package/dist/lib/parsers/loc.js +25 -0
  51. package/dist/lib/parsers/matchBlock.d.ts +1 -1
  52. package/dist/lib/parsers/matchBlock.js +4 -7
  53. package/dist/lib/parsers/matchBlock.test.js +3 -3
  54. package/dist/lib/parsers/multiLineComment.test.js +1 -1
  55. package/dist/lib/parsers/parserUtils.test.js +2 -2
  56. package/dist/lib/parsers/returnStatement.js +5 -7
  57. package/dist/lib/parsers/returnStatement.test.js +1 -1
  58. package/dist/lib/parsers/skill.test.js +1 -1
  59. package/dist/lib/parsers/specialVar.js +2 -4
  60. package/dist/lib/parsers/specialVar.test.js +1 -1
  61. package/dist/lib/parsers/tools.test.js +1 -1
  62. package/dist/lib/parsers/typeHints.js +2 -2
  63. package/dist/lib/parsers/typeHints.test.js +15 -15
  64. package/dist/lib/parsers/utils.d.ts +1 -1
  65. package/dist/lib/parsers/vitest.setup.d.ts +9 -0
  66. package/dist/lib/parsers/vitest.setup.js +53 -0
  67. package/dist/lib/parsers/whileLoop.test.js +1 -1
  68. package/dist/lib/preprocessors/importResolver.js +14 -5
  69. package/dist/lib/preprocessors/importResolver.test.js +3 -3
  70. package/dist/lib/preprocessors/typescriptPreprocessor.d.ts +1 -0
  71. package/dist/lib/preprocessors/typescriptPreprocessor.js +70 -3
  72. package/dist/lib/programInfo.js +5 -0
  73. package/dist/lib/programInfo.test.js +1 -1
  74. package/dist/lib/runtime/audit.d.ts +12 -1
  75. package/dist/lib/runtime/builtinTools.d.ts +0 -90
  76. package/dist/lib/runtime/builtinTools.js +0 -65
  77. package/dist/lib/runtime/builtins.d.ts +0 -15
  78. package/dist/lib/runtime/builtins.js +0 -55
  79. package/dist/lib/runtime/hooks.d.ts +2 -0
  80. package/dist/lib/runtime/index.d.ts +5 -3
  81. package/dist/lib/runtime/index.js +4 -3
  82. package/dist/lib/runtime/interrupts.d.ts +11 -4
  83. package/dist/lib/runtime/interrupts.js +20 -4
  84. package/dist/lib/runtime/isDebugger.test.d.ts +1 -0
  85. package/dist/lib/runtime/isDebugger.test.js +24 -0
  86. package/dist/lib/runtime/rewind.d.ts +20 -0
  87. package/dist/lib/runtime/rewind.js +67 -0
  88. package/dist/lib/runtime/rewind.test.d.ts +1 -0
  89. package/dist/lib/runtime/rewind.test.js +80 -0
  90. package/dist/lib/runtime/state/context.d.ts +1 -0
  91. package/dist/lib/runtime/state/context.js +6 -0
  92. package/dist/lib/runtime/state/stateStack.d.ts +7 -0
  93. package/dist/lib/runtime/state/stateStack.js +16 -0
  94. package/dist/lib/symbolTable.d.ts +1 -0
  95. package/dist/lib/symbolTable.js +8 -6
  96. package/dist/lib/symbolTable.test.js +1 -1
  97. package/dist/lib/templates/backends/agency/template.d.ts +6 -0
  98. package/dist/lib/templates/backends/agency/template.js +11 -0
  99. package/dist/lib/templates/backends/typescriptGenerator/debugger.d.ts +7 -0
  100. package/dist/lib/templates/backends/typescriptGenerator/debugger.js +25 -0
  101. package/dist/lib/templates/backends/typescriptGenerator/forSteps.d.ts +1 -1
  102. package/dist/lib/templates/backends/typescriptGenerator/forSteps.js +1 -4
  103. package/dist/lib/templates/backends/typescriptGenerator/imports.d.ts +1 -1
  104. package/dist/lib/templates/backends/typescriptGenerator/imports.js +13 -50
  105. package/dist/lib/templates/backends/typescriptGenerator/rewindCheckpoint.d.ts +8 -0
  106. package/dist/lib/templates/backends/typescriptGenerator/rewindCheckpoint.js +32 -0
  107. package/dist/lib/templates/backends/typescriptGenerator/whileSteps.d.ts +1 -1
  108. package/dist/lib/templates/backends/typescriptGenerator/whileSteps.js +1 -4
  109. package/dist/lib/types/access.d.ts +4 -3
  110. package/dist/lib/types/awaitPending.d.ts +2 -1
  111. package/dist/lib/types/base.d.ts +9 -0
  112. package/dist/lib/types/base.js +1 -0
  113. package/dist/lib/types/binop.d.ts +7 -9
  114. package/dist/lib/types/binop.js +1 -0
  115. package/dist/lib/types/dataStructures.d.ts +7 -8
  116. package/dist/lib/types/debuggerStatement.d.ts +5 -0
  117. package/dist/lib/types/debuggerStatement.js +1 -0
  118. package/dist/lib/types/forLoop.d.ts +4 -4
  119. package/dist/lib/types/function.d.ts +5 -7
  120. package/dist/lib/types/graphNode.d.ts +2 -1
  121. package/dist/lib/types/handleBlock.d.ts +2 -1
  122. package/dist/lib/types/ifElse.d.ts +4 -5
  123. package/dist/lib/types/importStatement.d.ts +7 -4
  124. package/dist/lib/types/importStatement.js +6 -0
  125. package/dist/lib/types/keyword.d.ts +2 -1
  126. package/dist/lib/types/literals.d.ts +9 -9
  127. package/dist/lib/types/matchBlock.d.ts +6 -10
  128. package/dist/lib/types/messageThread.d.ts +2 -1
  129. package/dist/lib/types/returnStatement.d.ts +4 -6
  130. package/dist/lib/types/sentinel.d.ts +11 -0
  131. package/dist/lib/types/sentinel.js +1 -0
  132. package/dist/lib/types/skill.d.ts +2 -1
  133. package/dist/lib/types/specialVar.d.ts +4 -6
  134. package/dist/lib/types/tools.d.ts +2 -1
  135. package/dist/lib/types/typeHints.d.ts +3 -2
  136. package/dist/lib/types/whileLoop.d.ts +4 -5
  137. package/dist/lib/types.d.ts +14 -6
  138. package/dist/lib/types.js +4 -0
  139. package/dist/lib/utils/node.d.ts +7 -5
  140. package/dist/lib/utils/node.js +54 -29
  141. package/package.json +4 -2
  142. package/stdlib/_builtins.js +97 -0
  143. package/stdlib/_math.js +9 -0
  144. package/stdlib/_utils.js +51 -0
  145. package/stdlib/index.agency +115 -0
  146. package/stdlib/index.js +1303 -0
  147. package/stdlib/lib/test.js +3 -0
  148. package/stdlib/math.agency +13 -0
  149. package/stdlib/math.js +383 -0
@@ -1,5 +1,5 @@
1
1
  import { SpecialVar } from "../types/specialVar.js";
2
- import { AgencyComment, AgencyMultiLineComment, AgencyNode, AgencyProgram, Assignment, Literal, NewLine, Scope, TypeAlias, TypeHint, TypeHintMap, VariableType } from "../types.js";
2
+ import { AgencyComment, AgencyMultiLineComment, AgencyNode, AgencyProgram, Assignment, DebuggerStatement, Literal, NewLine, Scope, TypeAlias, TypeHint, TypeHintMap, VariableType } from "../types.js";
3
3
  import { AccessChainElement, ValueAccess } from "../types/access.js";
4
4
  import { AgencyArray, AgencyObject } from "../types/dataStructures.js";
5
5
  import { FunctionCall, FunctionDefinition } from "../types/function.js";
@@ -80,6 +80,7 @@ export declare class AgencyGenerator {
80
80
  protected processWhileLoop(node: WhileLoop): string;
81
81
  protected processIfElse(node: IfElse): string;
82
82
  protected processReturnStatement(node: ReturnStatement): string;
83
+ protected processDebuggerStatement(node: DebuggerStatement): string;
83
84
  protected processComment(node: AgencyComment): string;
84
85
  protected processMultiLineComment(node: AgencyMultiLineComment): string;
85
86
  protected processImportStatement(node: ImportStatement): string;
@@ -64,10 +64,33 @@ export class AgencyGenerator {
64
64
  }
65
65
  }
66
66
  this.preprocessAST();
67
+ // Types that should have a blank line before/after them at the top level
68
+ const BLOCK_TYPES = new Set([
69
+ "graphNode", "function", "typeAlias",
70
+ ]);
71
+ const NO_SPACE_TYPES = new Set([
72
+ "comment", "multiLineComment"
73
+ ]);
67
74
  // Pass 7: Process all nodes and generate code
75
+ const stmtPairs = [];
68
76
  for (const node of program.nodes) {
69
77
  const result = this.processNode(node);
70
- this.generatedStatements.push(result);
78
+ if (result !== "") {
79
+ stmtPairs.push({ type: node.type, code: result });
80
+ }
81
+ }
82
+ // Join top-level statements: blank line between block declarations,
83
+ // single newline between simple statements
84
+ const stmtLines = [];
85
+ for (let i = 0; i < stmtPairs.length; i++) {
86
+ if (i > 0) {
87
+ const prev = stmtPairs[i - 1];
88
+ const curr = stmtPairs[i];
89
+ if (BLOCK_TYPES.has(prev.type) || (BLOCK_TYPES.has(curr.type) && !NO_SPACE_TYPES.has(prev.type))) {
90
+ stmtLines.push(""); // blank line
91
+ }
92
+ }
93
+ stmtLines.push(stmtPairs[i].code);
71
94
  }
72
95
  const output = [];
73
96
  this.addIfNonEmpty(this.preprocess(), output);
@@ -75,7 +98,7 @@ export class AgencyGenerator {
75
98
  this.addIfNonEmpty(this.generateImports(), output);
76
99
  this.addIfNonEmpty(this.generateBuiltins(), output);
77
100
  output.push(...this.generatedTypeAliases);
78
- output.push(this.generatedStatements.join(""));
101
+ output.push(stmtLines.join("\n"));
79
102
  this.addIfNonEmpty(this.postprocess(), output);
80
103
  return {
81
104
  output: output.join("\n"),
@@ -131,6 +154,8 @@ export class AgencyGenerator {
131
154
  return this.generateLiteral(node);
132
155
  case "returnStatement":
133
156
  return this.processReturnStatement(node);
157
+ case "debuggerStatement":
158
+ return this.processDebuggerStatement(node);
134
159
  case "agencyArray":
135
160
  return this.processAgencyArray(node);
136
161
  case "agencyObject":
@@ -195,8 +220,8 @@ export class AgencyGenerator {
195
220
  }
196
221
  isImportedTool(functionName) {
197
222
  return this.importedTools
198
- .map((node) => node.importedTools)
199
- .flat()
223
+ .flatMap((node) => node.importedTools)
224
+ .flatMap((n) => n.importedNames)
200
225
  .includes(functionName);
201
226
  }
202
227
  isAgencyFunction(functionName, context) {
@@ -359,7 +384,7 @@ export class AgencyGenerator {
359
384
  for (const stmt of body) {
360
385
  lines.push(this.processNode(stmt));
361
386
  }
362
- const bodyCode = lines.join("").trimEnd() + "\n";
387
+ const bodyCode = lines.filter(s => s !== "").join("\n").trimEnd() + "\n";
363
388
  result += bodyCode;
364
389
  this.decreaseIndent();
365
390
  result += this.indentStr(`}`);
@@ -459,9 +484,11 @@ export class AgencyGenerator {
459
484
  : node.itemVar;
460
485
  let result = this.indentStr(`for (${vars} in ${iterableCode}) {\n`);
461
486
  this.increaseIndent();
487
+ const lines = [];
462
488
  for (const stmt of node.body) {
463
- result += this.processNode(stmt);
489
+ lines.push(this.processNode(stmt));
464
490
  }
491
+ result += lines.filter(s => s !== "").join("\n").trimEnd() + "\n";
465
492
  this.decreaseIndent();
466
493
  result += this.indentStr(`}`);
467
494
  return result;
@@ -470,9 +497,11 @@ export class AgencyGenerator {
470
497
  const conditionCode = this.processNode(node.condition).trim();
471
498
  let result = this.indentStr(`while (${conditionCode}) {\n`);
472
499
  this.increaseIndent();
500
+ const lines = [];
473
501
  for (const stmt of node.body) {
474
- result += this.processNode(stmt);
502
+ lines.push(this.processNode(stmt));
475
503
  }
504
+ result += lines.filter(s => s !== "").join("\n").trimEnd() + "\n";
476
505
  this.decreaseIndent();
477
506
  result += this.indentStr(`}`);
478
507
  return result;
@@ -487,7 +516,7 @@ export class AgencyGenerator {
487
516
  bodyLines.push(this.processNode(stmt));
488
517
  }
489
518
  this.decreaseIndent();
490
- lines.push(bodyLines.join("").trimEnd() + "\n");
519
+ lines.push(bodyLines.filter(s => s !== "").join("\n").trimEnd() + "\n");
491
520
  if (node.elseBody && node.elseBody.length > 0) {
492
521
  if (node.elseBody.length === 1 && node.elseBody[0].type === "ifElse") {
493
522
  const elseIfCode = this.processIfElse(node.elseBody[0]);
@@ -502,7 +531,7 @@ export class AgencyGenerator {
502
531
  elseBodyLines.push(this.processNode(stmt));
503
532
  }
504
533
  this.decreaseIndent();
505
- lines.push(elseBodyLines.join("").trimEnd() + "\n");
534
+ lines.push(elseBodyLines.filter(s => s !== "").join("\n").trimEnd() + "\n");
506
535
  }
507
536
  }
508
537
  lines.push(this.indentStr(`}`));
@@ -512,6 +541,9 @@ export class AgencyGenerator {
512
541
  const valueCode = this.processNode(node.value).trim();
513
542
  return this.indentStr(`return ${valueCode}`);
514
543
  }
544
+ processDebuggerStatement(node) {
545
+ return this.indentStr(node.label ? `debugger(${JSON.stringify(node.label)})` : "debugger()");
546
+ }
515
547
  // Utility methods
516
548
  processComment(node) {
517
549
  return this.indentStr(`//${node.content}`);
@@ -521,7 +553,10 @@ export class AgencyGenerator {
521
553
  }
522
554
  processImportStatement(node) {
523
555
  const importedNames = node.importedNames.map((name) => this.processImportNameType(name));
524
- const str = this.indentStr(`import ${importedNames.join(", ")} from "${node.modulePath}"`);
556
+ const modulePath = node.modulePath.startsWith("std::")
557
+ ? node.modulePath.replace(/\.agency$/, "")
558
+ : node.modulePath;
559
+ const str = this.indentStr(`import ${importedNames.join(", ")} from "${modulePath}"`);
525
560
  return str;
526
561
  }
527
562
  processImportNameType(node) {
@@ -540,7 +575,8 @@ export class AgencyGenerator {
540
575
  return `import node { ${node.importedNodes.join(", ")} } from "${node.agencyFile}"`;
541
576
  }
542
577
  processImportToolStatement(node) {
543
- return `import tool { ${node.importedTools.join(", ")} } from "${node.agencyFile}"`;
578
+ const toolNames = node.importedTools.flatMap((n) => n.importedNames);
579
+ return `import tool { ${toolNames.join(", ")} } from "${node.agencyFile}"`;
544
580
  }
545
581
  visibilityToString(vis) {
546
582
  switch (vis) {
@@ -569,7 +605,7 @@ export class AgencyGenerator {
569
605
  for (const stmt of body) {
570
606
  lines.push(this.processNode(stmt));
571
607
  }
572
- const bodyCode = lines.join("").trimEnd() + "\n";
608
+ const bodyCode = lines.filter(s => s !== "").join("\n").trimEnd() + "\n";
573
609
  result += bodyCode;
574
610
  this.decreaseIndent();
575
611
  result += this.indentStr(`}`);
@@ -585,7 +621,7 @@ export class AgencyGenerator {
585
621
  return this.indentStr(`@${node.name} = ${this.processNode(node.value).trim()}`);
586
622
  }
587
623
  processNewLine(_node) {
588
- return "\n";
624
+ return "";
589
625
  }
590
626
  processMessageThread(node) {
591
627
  this.increaseIndent();
@@ -594,7 +630,7 @@ export class AgencyGenerator {
594
630
  bodyCodes.push(this.processNode(stmt));
595
631
  }
596
632
  this.decreaseIndent();
597
- const bodyCodeStr = bodyCodes.join("");
633
+ const bodyCodeStr = bodyCodes.filter(s => s !== "").join("\n").trimEnd() + "\n";
598
634
  const threadType = node.threadType;
599
635
  return this.indentStr(`${threadType} {\n${bodyCodeStr}${this.indentStr("}")}`);
600
636
  }
@@ -605,7 +641,7 @@ export class AgencyGenerator {
605
641
  bodyCodes.push(this.processNode(stmt));
606
642
  }
607
643
  this.decreaseIndent();
608
- const bodyCodeStr = bodyCodes.join("");
644
+ const bodyCodeStr = bodyCodes.filter(s => s !== "").join("\n").trimEnd() + "\n";
609
645
  let handlerStr;
610
646
  if (node.handler.kind === "inline") {
611
647
  const paramStr = node.handler.param.typeHint
@@ -617,7 +653,7 @@ export class AgencyGenerator {
617
653
  handlerBodyCodes.push(this.processNode(stmt));
618
654
  }
619
655
  this.decreaseIndent();
620
- const handlerBodyStr = handlerBodyCodes.join("");
656
+ const handlerBodyStr = handlerBodyCodes.filter(s => s !== "").join("\n").trimEnd() + "\n";
621
657
  handlerStr = `(${paramStr}) {\n${handlerBodyStr}${this.indentStr("}")}`;
622
658
  }
623
659
  else {
@@ -57,7 +57,7 @@ describe("AgencyGenerator - Function Parameter Type Hints", () => {
57
57
  ];
58
58
  testCases.forEach(({ description, input, expectedOutput }) => {
59
59
  it(`should correctly generate ${description}`, () => {
60
- const parseResult = parseAgency(input);
60
+ const parseResult = parseAgency(input, {}, false);
61
61
  expect(parseResult.success).toBe(true);
62
62
  if (!parseResult.success)
63
63
  return;
@@ -73,7 +73,7 @@ describe("AgencyGenerator - Function Parameter Type Hints", () => {
73
73
  describe("Type preservation", () => {
74
74
  it("should preserve primitive types", () => {
75
75
  const input = "def test(n: number, s: string, b: boolean) { n }";
76
- const parseResult = parseAgency(input);
76
+ const parseResult = parseAgency(input, {}, false);
77
77
  expect(parseResult.success).toBe(true);
78
78
  if (!parseResult.success)
79
79
  return;
@@ -85,7 +85,7 @@ describe("AgencyGenerator - Function Parameter Type Hints", () => {
85
85
  });
86
86
  it("should preserve array types", () => {
87
87
  const input = "def test(nums: number[], strs: string[]) { nums }";
88
- const parseResult = parseAgency(input);
88
+ const parseResult = parseAgency(input, {}, false);
89
89
  expect(parseResult.success).toBe(true);
90
90
  if (!parseResult.success)
91
91
  return;
@@ -96,7 +96,7 @@ describe("AgencyGenerator - Function Parameter Type Hints", () => {
96
96
  });
97
97
  it("should preserve union types", () => {
98
98
  const input = "def test(val: string | number | boolean) { val }";
99
- const parseResult = parseAgency(input);
99
+ const parseResult = parseAgency(input, {}, false);
100
100
  expect(parseResult.success).toBe(true);
101
101
  if (!parseResult.success)
102
102
  return;
@@ -106,7 +106,7 @@ describe("AgencyGenerator - Function Parameter Type Hints", () => {
106
106
  });
107
107
  it("should preserve nested array types", () => {
108
108
  const input = "def test(matrix: number[][]) { matrix }";
109
- const parseResult = parseAgency(input);
109
+ const parseResult = parseAgency(input, {}, false);
110
110
  expect(parseResult.success).toBe(true);
111
111
  if (!parseResult.success)
112
112
  return;
@@ -118,7 +118,7 @@ describe("AgencyGenerator - Function Parameter Type Hints", () => {
118
118
  describe("Mixed typed and untyped parameters", () => {
119
119
  it("should handle first parameter typed, second untyped", () => {
120
120
  const input = "def test(x: number, y) { x }";
121
- const parseResult = parseAgency(input);
121
+ const parseResult = parseAgency(input, {}, false);
122
122
  expect(parseResult.success).toBe(true);
123
123
  if (!parseResult.success)
124
124
  return;
@@ -130,7 +130,7 @@ describe("AgencyGenerator - Function Parameter Type Hints", () => {
130
130
  });
131
131
  it("should handle first parameter untyped, second typed", () => {
132
132
  const input = "def test(x, y: string) { x }";
133
- const parseResult = parseAgency(input);
133
+ const parseResult = parseAgency(input, {}, false);
134
134
  expect(parseResult.success).toBe(true);
135
135
  if (!parseResult.success)
136
136
  return;
@@ -142,7 +142,7 @@ describe("AgencyGenerator - Function Parameter Type Hints", () => {
142
142
  });
143
143
  it("should handle alternating typed and untyped parameters", () => {
144
144
  const input = "def test(a, b: number, c, d: string) { a }";
145
- const parseResult = parseAgency(input);
145
+ const parseResult = parseAgency(input, {}, false);
146
146
  expect(parseResult.success).toBe(true);
147
147
  if (!parseResult.success)
148
148
  return;
@@ -15,6 +15,9 @@ export declare class TypeScriptBuilder {
15
15
  private loopVars;
16
16
  private insideMessageThread;
17
17
  private insideHandlerBody;
18
+ /** Stack of loop subKeys for generating break/continue cleanup code.
19
+ * Pushed when entering a stepped loop, popped when leaving. */
20
+ private _loopContextStack;
18
21
  private _asyncBranchCheckNeeded;
19
22
  /** Tracks the current substep nesting path. Empty when at the top level
20
23
  * of a stepped body. Non-empty when inside a block (if/else, etc.) that
@@ -60,6 +63,7 @@ export declare class TypeScriptBuilder {
60
63
  private needsParensRight;
61
64
  build(program: AgencyProgram): TsNode;
62
65
  private processNode;
66
+ private processKeyword;
63
67
  private processTypeAlias;
64
68
  private processTypeHint;
65
69
  private processComment;
@@ -101,6 +105,8 @@ export declare class TypeScriptBuilder {
101
105
  * response format from type hints, and tools from config object.
102
106
  */
103
107
  private processLlmCall;
108
+ private processSentinel;
109
+ private processDebuggerStatement;
104
110
  private buildPromptString;
105
111
  private processSpecialVar;
106
112
  private processMessageThread;
@@ -1,8 +1,11 @@
1
1
  import { formatTypeHint } from "../cli/util.js";
2
+ import { toCompiledImportPath } from "../importPaths.js";
2
3
  import { BUILTIN_FUNCTIONS, BUILTIN_TOOLS, BUILTIN_VARIABLES, TYPES_THAT_DONT_TRIGGER_NEW_PART, } from "../config.js";
3
4
  import * as renderImports from "../templates/backends/typescriptGenerator/imports.js";
4
5
  import * as renderInterruptAssignment from "../templates/backends/typescriptGenerator/interruptAssignment.js";
5
6
  import * as renderInterruptReturn from "../templates/backends/typescriptGenerator/interruptReturn.js";
7
+ import * as renderRewindCheckpoint from "../templates/backends/typescriptGenerator/rewindCheckpoint.js";
8
+ import * as renderDebugger from "../templates/backends/typescriptGenerator/debugger.js";
6
9
  import { PRECEDENCE, } from "../types/binop.js";
7
10
  import { expressionToString, getBaseVarName, walkNodesArray, } from "../utils/node.js";
8
11
  import path from "path";
@@ -34,6 +37,9 @@ export class TypeScriptBuilder {
34
37
  loopVars = [];
35
38
  insideMessageThread = false;
36
39
  insideHandlerBody = false;
40
+ /** Stack of loop subKeys for generating break/continue cleanup code.
41
+ * Pushed when entering a stepped loop, popped when leaving. */
42
+ _loopContextStack = [];
37
43
  /*
38
44
  We break up every function and node body into steps,
39
45
  and wrap each statement in an if statement. If that statement
@@ -138,8 +144,8 @@ export class TypeScriptBuilder {
138
144
  }
139
145
  isImportedTool(functionName) {
140
146
  return this.programInfo.importedTools
141
- .map((node) => node.importedTools)
142
- .flat()
147
+ .flatMap((node) => node.importedTools)
148
+ .flatMap((n) => n.importedNames)
143
149
  .includes(functionName);
144
150
  }
145
151
  // Runtime functions that need __state (ctx injection) like user-defined agency functions.
@@ -360,11 +366,37 @@ export class TypeScriptBuilder {
360
366
  case "binOpExpression":
361
367
  return this.processBinOpExpression(node);
362
368
  case "keyword":
363
- return node.value === "break" ? ts.break() : ts.continue();
369
+ return this.processKeyword(node);
370
+ case "sentinel":
371
+ return this.processSentinel(node);
372
+ case "debuggerStatement":
373
+ return this.processDebuggerStatement(node);
364
374
  default:
365
375
  throw new Error(`Unhandled Agency node type: ${node.type}`);
366
376
  }
367
377
  }
378
+ processKeyword(node) {
379
+ const keyword = node.value === "break" ? ts.break() : ts.continue();
380
+ // Inside a handler body or not inside a stepped loop: emit bare keyword
381
+ const loopSubKey = this._loopContextStack[this._loopContextStack.length - 1];
382
+ if (this.insideHandlerBody || loopSubKey === undefined) {
383
+ return keyword;
384
+ }
385
+ // Inside a stepped loop: emit cleanup before the keyword.
386
+ // For continue, we also need to increment the iteration counters
387
+ // so the next iteration doesn't replay the current one.
388
+ const iterStore = `__stack.locals.__iteration_${loopSubKey}`;
389
+ const currentIterVar = `__currentIter_${loopSubKey}`;
390
+ const stmts = [
391
+ ts.raw(`__stack.resetLoopIteration("${loopSubKey}")`),
392
+ ];
393
+ if (node.value === "continue") {
394
+ stmts.push(ts.raw(`${iterStore}++`));
395
+ stmts.push(ts.raw(`${currentIterVar}++`));
396
+ }
397
+ stmts.push(keyword);
398
+ return ts.statements(stmts);
399
+ }
368
400
  // ------- Type system (side effects only) -------
369
401
  processTypeAlias(node) {
370
402
  return ts.raw(`type ${node.aliasName} = ${formatTypeHint(node.aliasedType)};`);
@@ -535,12 +567,14 @@ export class TypeScriptBuilder {
535
567
  }
536
568
  const subStepPath = [...this._subStepPath];
537
569
  const subKey = subStepPath.join("_");
570
+ this._loopContextStack.push(subKey);
538
571
  const bodyNodes = node.body.map((stmt, i) => {
539
572
  this._subStepPath.push(i);
540
573
  const result = this.processStatement(stmt);
541
574
  this._subStepPath.pop();
542
575
  return result;
543
576
  });
577
+ this._loopContextStack.pop();
544
578
  // Unregister loop variables
545
579
  this.loopVars = this.loopVars.filter((v) => v !== node.itemVar && v !== node.indexVar);
546
580
  // Range form: for (i in range(start, end))
@@ -584,13 +618,16 @@ export class TypeScriptBuilder {
584
618
  }
585
619
  processWhileLoopWithSteps(node) {
586
620
  const subStepPath = [...this._subStepPath];
621
+ const subKey = subStepPath.join("_");
587
622
  const condition = this.processNode(node.condition);
623
+ this._loopContextStack.push(subKey);
588
624
  const bodyNodes = node.body.map((stmt, i) => {
589
625
  this._subStepPath.push(i);
590
626
  const result = this.processStatement(stmt);
591
627
  this._subStepPath.pop();
592
628
  return result;
593
629
  });
630
+ this._loopContextStack.pop();
594
631
  return ts.whileSteps(subStepPath, condition, bodyNodes);
595
632
  }
596
633
  processMatchBlockWithSteps(node) {
@@ -614,7 +651,7 @@ export class TypeScriptBuilder {
614
651
  return ts.ifSteps(subStepPath, branches, elseBranch);
615
652
  }
616
653
  processImportStatement(node) {
617
- const from = node.modulePath.replace(/\.agency$/, ".js");
654
+ const from = toCompiledImportPath(node.modulePath);
618
655
  const imports = node.importedNames.map((nameType) => {
619
656
  switch (nameType.type) {
620
657
  case "namedImport":
@@ -643,17 +680,17 @@ export class TypeScriptBuilder {
643
680
  return ts.empty(); // handled in preprocess
644
681
  }
645
682
  processImportToolStatement(node) {
646
- const importNames = node.importedTools
647
- .map((toolName) => [
683
+ const toolNames = node.importedTools.flatMap((n) => n.importedNames);
684
+ const importNames = toolNames
685
+ .flatMap((toolName) => [
648
686
  toolName,
649
687
  `__${toolName}Tool`,
650
688
  `__${toolName}ToolParams`,
651
- ])
652
- .flat();
689
+ ]);
653
690
  return ts.importDecl({
654
691
  importKind: "named",
655
692
  names: importNames,
656
- from: node.agencyFile.replace(/\.agency$/, ".js"),
693
+ from: toCompiledImportPath(node.agencyFile),
657
694
  });
658
695
  }
659
696
  // ------- TsRaw wrapper methods (template-heavy) -------
@@ -720,7 +757,9 @@ export class TypeScriptBuilder {
720
757
  entries[def.functionName] = this.buildToolRegistryEntry(def.functionName, def.functionName, false);
721
758
  }
722
759
  // Add imported tools (they import __toolNameTool and __toolNameToolParams)
723
- const importedToolNames = this.programInfo.importedTools.flatMap((node) => node.importedTools);
760
+ const importedToolNames = this.programInfo.importedTools
761
+ .flatMap((node) => node.importedTools)
762
+ .flatMap((n) => n.importedNames);
724
763
  for (const toolName of importedToolNames) {
725
764
  entries[toolName] = this.buildToolRegistryEntry(toolName, toolName, false);
726
765
  }
@@ -1316,6 +1355,25 @@ export class TypeScriptBuilder {
1316
1355
  }
1317
1356
  return ts.statements(stmts);
1318
1357
  }
1358
+ processSentinel(node) {
1359
+ if (node.value === "checkpoint") {
1360
+ const promptNode = this.processNode(node.data.prompt);
1361
+ const varRef = ts.scopedVar(node.data.targetVariable, node.data.scope, this.moduleId);
1362
+ return ts.raw(renderRewindCheckpoint.default({
1363
+ targetVariable: node.data.targetVariable,
1364
+ prompt: printTs(promptNode),
1365
+ response: printTs(varRef),
1366
+ }));
1367
+ }
1368
+ return ts.empty();
1369
+ }
1370
+ processDebuggerStatement(node) {
1371
+ const code = renderDebugger.default({
1372
+ label: node.label !== undefined ? JSON.stringify(node.label) : "undefined",
1373
+ nodeContext: this.isInsideGraphNode,
1374
+ });
1375
+ return ts.raw(code);
1376
+ }
1319
1377
  buildPromptString({ segments, typeHints, skills, }) {
1320
1378
  const promptParts = [];
1321
1379
  for (const segment of segments) {
@@ -1481,6 +1539,17 @@ export class TypeScriptBuilder {
1481
1539
  return ts.handleSteps(subStepPath, handler, bodyNodes);
1482
1540
  }
1483
1541
  processBodyAsParts(body, opts = {}) {
1542
+ // Debugger mode: insert breakpoints before each step-triggering statement
1543
+ if (this.agencyConfig?.debugger) {
1544
+ const expanded = [];
1545
+ for (const stmt of body) {
1546
+ if (!TYPES_THAT_DONT_TRIGGER_NEW_PART.includes(stmt.type) && stmt.type !== "debuggerStatement") {
1547
+ expanded.push({ type: "debuggerStatement" });
1548
+ }
1549
+ expanded.push(stmt);
1550
+ }
1551
+ body = expanded;
1552
+ }
1484
1553
  const parts = [[]];
1485
1554
  // Maps step index to the branch key (subStepPath) captured at processing time
1486
1555
  const branchKeys = {};
@@ -3,7 +3,7 @@ import { AgencyProgram } from "../index.js";
3
3
  import { type SymbolTable } from "../symbolTable.js";
4
4
  export declare function loadConfig(configPath?: string, verbose?: boolean): AgencyConfig;
5
5
  export declare function readStdin(): Promise<string>;
6
- export declare function parse(contents: string, config: AgencyConfig): AgencyProgram;
6
+ export declare function parse(contents: string, config: AgencyConfig, applyTemplate?: boolean): AgencyProgram;
7
7
  export declare function readFile(inputFile: string): string;
8
8
  export declare function renderGraph(contents: string, config: AgencyConfig): void;
9
9
  export declare function resetCompilationCache(): void;
@@ -6,6 +6,7 @@ import { collectProgramInfo } from "../programInfo.js";
6
6
  import { typeCheck, formatErrors } from "../typeChecker.js";
7
7
  import { buildSymbolTable } from "../symbolTable.js";
8
8
  import { resolveImports } from "../preprocessors/importResolver.js";
9
+ import { resolveAgencyImportPath, isStdlibImport, getStdlibDir, } from "../importPaths.js";
9
10
  import { renderMermaidAscii } from "beautiful-mermaid";
10
11
  import { spawn } from "child_process";
11
12
  import * as fs from "fs";
@@ -52,14 +53,14 @@ export function readStdin() {
52
53
  });
53
54
  });
54
55
  }
55
- export function parse(contents, config) {
56
+ export function parse(contents, config, applyTemplate = true) {
56
57
  const verbose = config.verbose ?? false;
57
- const parseResult = parseAgency(contents, config);
58
+ const parseResult = parseAgency(contents, config, applyTemplate);
58
59
  // Check if parsing was successful
59
60
  if (!parseResult.success) {
60
61
  console.error("Parse error:");
61
62
  console.error(parseResult);
62
- process.exit(1);
63
+ throw new Error("Failed to parse Agency program");
63
64
  }
64
65
  return parseResult.result;
65
66
  }
@@ -120,7 +121,8 @@ export function compile(config, inputFile, _outputFile, options) {
120
121
  }
121
122
  compiledFiles.add(absoluteInputFile);
122
123
  const contents = readFile(inputFile);
123
- const parsedProgram = parse(contents, config);
124
+ const isStdlibFile = absoluteInputFile.startsWith(getStdlibDir());
125
+ const parsedProgram = parse(contents, config, !isStdlibFile);
124
126
  // Build symbol table once at the top level, reuse for recursive calls
125
127
  const symbolTable = options?.symbolTable ?? buildSymbolTable(absoluteInputFile, config);
126
128
  // Resolve unified imports into specialized AST nodes
@@ -140,10 +142,9 @@ export function compile(config, inputFile, _outputFile, options) {
140
142
  }
141
143
  }
142
144
  const imports = getImports(resolvedProgram);
143
- const inputDir = path.dirname(absoluteInputFile);
144
145
  for (const importPath of imports) {
145
- const absPath = path.resolve(inputDir, importPath);
146
- if (config.restrictImports) {
146
+ const absPath = resolveAgencyImportPath(importPath, absoluteInputFile);
147
+ if (config.restrictImports && !isStdlibImport(importPath)) {
147
148
  const projectRoot = process.cwd();
148
149
  if (!absPath.startsWith(projectRoot + path.sep) &&
149
150
  absPath !== projectRoot) {
@@ -154,7 +155,7 @@ export function compile(config, inputFile, _outputFile, options) {
154
155
  }
155
156
  // Update the import path in the AST to reference the new .ts file
156
157
  resolvedProgram.nodes.forEach((node) => {
157
- if (node.type === "importStatement") {
158
+ if (node.type === "importStatement" && !isStdlibImport(node.modulePath)) {
158
159
  node.modulePath = node.modulePath.replace(".agency", ext);
159
160
  }
160
161
  });
@@ -204,7 +205,7 @@ export function run(config, inputFile, outputFile, resumeFile) {
204
205
  });
205
206
  }
206
207
  export async function format(contents, config) {
207
- const parsedProgram = parse(contents, config);
208
+ const parsedProgram = parse(contents, config, false);
208
209
  const generatedCode = generateAgency(parsedProgram);
209
210
  return generatedCode;
210
211
  }
@@ -5,7 +5,7 @@ import prompts from "prompts";
5
5
  import { executeJudge, executeNode, findRecursively, parseTarget, pickANode, promptForArgs, promptForTarget, } from "./util.js";
6
6
  import { color } from "../utils/termcolors.js";
7
7
  import path from "path";
8
- import { compile } from "./commands.js";
8
+ import { compile, loadConfig } from "./commands.js";
9
9
  import { execFileSync } from "child_process";
10
10
  function readFile(filename) {
11
11
  console.log("Trying to read file", filename, "...");
@@ -231,14 +231,22 @@ export function mergeStats(a, b) {
231
231
  function runSingleTest(config, testFile, tests, testCase) {
232
232
  const hasArgs = testCase.input !== "";
233
233
  const relativeSourceFilePath = path.join(path.dirname(testFile), tests.sourceFile);
234
- const result = executeNode({
235
- config,
236
- agencyFile: relativeSourceFilePath,
237
- nodeName: testCase.nodeName,
238
- hasArgs,
239
- argsString: testCase.input,
240
- interruptHandlers: testCase.interruptHandlers,
241
- });
234
+ let result;
235
+ try {
236
+ result = executeNode({
237
+ config,
238
+ agencyFile: relativeSourceFilePath,
239
+ nodeName: testCase.nodeName,
240
+ hasArgs,
241
+ argsString: testCase.input,
242
+ interruptHandlers: testCase.interruptHandlers,
243
+ });
244
+ }
245
+ catch (e) {
246
+ exitIfSignal(e);
247
+ console.log(color.red(` ✗ Test execution error: ${e}`));
248
+ return false;
249
+ }
242
250
  let testPassed = true;
243
251
  for (const criterion of testCase.evaluationCriteria) {
244
252
  if (criterion.type === "exact") {
@@ -383,10 +391,15 @@ export async function testTs(config, inputPaths) {
383
391
  failures.push(dir);
384
392
  continue;
385
393
  }
386
- // Compile the .agency file
394
+ // Compile the .agency file, merging any local agency.json config
387
395
  const agencyPath = path.join(dir, agencyFile);
396
+ const localConfigPath = path.join(dir, "agency.json");
397
+ let mergedConfig = config;
398
+ if (fs.existsSync(localConfigPath)) {
399
+ mergedConfig = { ...config, ...loadConfig(localConfigPath) };
400
+ }
388
401
  try {
389
- compile(config, agencyPath);
402
+ compile(mergedConfig, agencyPath);
390
403
  }
391
404
  catch (e) {
392
405
  console.log(color.red(` ✗ Compilation failed: ${e}`));
@@ -5,6 +5,7 @@ const onCancel = () => {
5
5
  import fs, { readFileSync } from "fs";
6
6
  import path from "path";
7
7
  import { execFileSync } from "child_process";
8
+ import { isStdlibImport, resolveAgencyImportPath, getStdlibDir } from "../importPaths.js";
8
9
  import renderEvaluate from "../templates/cli/evaluate.js";
9
10
  import renderJudgeEvaluate from "../templates/cli/judgeEvaluate.js";
10
11
  import { compile } from "./commands.js";
@@ -201,7 +202,8 @@ export function getImportsRecursively(filename, visited = new Set()) {
201
202
  }
202
203
  visited.add(filename);
203
204
  const contents = fs.readFileSync(filename, "utf-8");
204
- const parsed = parseAgency(contents, { verbose: false });
205
+ const isStdlibFile = filename.startsWith(getStdlibDir());
206
+ const parsed = parseAgency(contents, { verbose: false }, !isStdlibFile);
205
207
  if (!parsed.success) {
206
208
  console.error(`Error parsing ${filename}:`, parsed);
207
209
  return [];
@@ -209,7 +211,7 @@ export function getImportsRecursively(filename, visited = new Set()) {
209
211
  const program = parsed.result;
210
212
  const imports = getImports(program);
211
213
  for (const imp of imports) {
212
- const importedFile = path.resolve(path.dirname(filename), imp);
214
+ const importedFile = resolveAgencyImportPath(imp, filename);
213
215
  if (fs.existsSync(importedFile)) {
214
216
  imports.push(...getImportsRecursively(importedFile, visited));
215
217
  }
@@ -226,7 +228,8 @@ export function getImports(program) {
226
228
  .map((node) => node.agencyFile.trim());
227
229
  // this makes compile() try to parse non-agency files
228
230
  const importStatements = program.nodes
229
- .filter((node) => node.type === "importStatement" && node.modulePath.endsWith(".agency"))
231
+ .filter((node) => node.type === "importStatement" &&
232
+ (node.modulePath.endsWith(".agency") || isStdlibImport(node.modulePath)))
230
233
  .map((node) => node.modulePath.trim());
231
234
  return [...toolAndNodeImports, ...importStatements];
232
235
  }