agency-lang 0.0.15 → 0.0.16

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 (38) hide show
  1. package/dist/lib/backends/agencyGenerator.js +20 -3
  2. package/dist/lib/backends/agencyGenerator.test.d.ts +1 -0
  3. package/dist/lib/backends/agencyGenerator.test.js +205 -0
  4. package/dist/lib/backends/baseGenerator.js +3 -1
  5. package/dist/lib/backends/graphGenerator.js +7 -5
  6. package/dist/lib/backends/typescriptGenerator.d.ts +3 -3
  7. package/dist/lib/backends/typescriptGenerator.js +14 -12
  8. package/dist/lib/parsers/access.d.ts +3 -5
  9. package/dist/lib/parsers/access.js +54 -13
  10. package/dist/lib/parsers/access.test.js +40 -500
  11. package/dist/lib/parsers/assignment.js +2 -2
  12. package/dist/lib/parsers/assignment.test.d.ts +1 -0
  13. package/dist/lib/parsers/assignment.test.js +279 -0
  14. package/dist/lib/parsers/dataStructures.js +3 -3
  15. package/dist/lib/parsers/function.d.ts +3 -1
  16. package/dist/lib/parsers/function.js +6 -4
  17. package/dist/lib/parsers/function.test.js +653 -8
  18. package/dist/lib/parsers/functionCall.js +3 -2
  19. package/dist/lib/parsers/functionCall.test.js +310 -0
  20. package/dist/lib/parsers/literals.js +11 -2
  21. package/dist/lib/parsers/literals.test.js +7 -7
  22. package/dist/lib/parsers/matchBlock.js +2 -2
  23. package/dist/lib/parsers/parserUtils.test.d.ts +1 -0
  24. package/dist/lib/parsers/parserUtils.test.js +46 -0
  25. package/dist/lib/parsers/returnStatement.js +2 -2
  26. package/dist/lib/parsers/returnStatement.test.d.ts +1 -0
  27. package/dist/lib/parsers/returnStatement.test.js +268 -0
  28. package/dist/lib/parsers/specialVar.test.d.ts +1 -0
  29. package/dist/lib/parsers/specialVar.test.js +219 -0
  30. package/dist/lib/types/access.d.ts +5 -5
  31. package/dist/lib/types/access.js +6 -1
  32. package/dist/lib/types/dataStructures.d.ts +3 -3
  33. package/dist/lib/types/function.d.ts +10 -4
  34. package/dist/lib/types/matchBlock.d.ts +2 -2
  35. package/dist/lib/types/returnStatement.d.ts +2 -2
  36. package/dist/lib/types/whileLoop.d.ts +2 -2
  37. package/dist/lib/types.d.ts +3 -3
  38. package/package.json +3 -3
@@ -100,12 +100,28 @@ export class AgencyGenerator extends BaseGenerator {
100
100
  processFunctionDefinition(node) {
101
101
  const { functionName, body, parameters } = node;
102
102
  // Build parameter list
103
- const params = parameters.join(", ");
103
+ const params = parameters
104
+ .map((p) => {
105
+ if (p.typeHint) {
106
+ const typeStr = variableTypeToString(p.typeHint, this.typeAliases);
107
+ return `${p.name}: ${typeStr}`;
108
+ }
109
+ else {
110
+ return p.name;
111
+ }
112
+ })
113
+ .join(", ");
104
114
  // Start function definition
105
115
  let result = this.indentStr(`def ${functionName}(${params}) {\n`);
106
116
  // Process body with increased indentation
107
117
  this.increaseIndent();
108
- this.functionScopedVariables = [...parameters];
118
+ if (node.docString) {
119
+ const docLines = [`"""`, node.docString.value, `"""`]
120
+ .map((line) => this.indentStr(line))
121
+ .join("\n");
122
+ result += `${docLines}\n`;
123
+ }
124
+ this.functionScopedVariables = [...parameters.map((p) => p.name)];
109
125
  const lines = [];
110
126
  for (const stmt of body) {
111
127
  lines.push(this.processNode(stmt));
@@ -154,7 +170,8 @@ export class AgencyGenerator extends BaseGenerator {
154
170
  }
155
171
  }
156
172
  processDotProperty(node) {
157
- const objectCode = this.processNode(node.object).trim();
173
+ let objectCode = this.processNode(node.object);
174
+ objectCode = objectCode.trim();
158
175
  return `${objectCode}.${node.propertyName}`;
159
176
  }
160
177
  processIndexAccess(node) {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,205 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { AgencyGenerator } from "./agencyGenerator.js";
3
+ import { parseAgency } from "../parser.js";
4
+ describe("AgencyGenerator - Function Parameter Type Hints", () => {
5
+ describe("processFunctionDefinition", () => {
6
+ const testCases = [
7
+ {
8
+ description: "single parameter with type hint",
9
+ input: "def add(x: number) { x }",
10
+ expectedOutput: "def add(x: number) {\nx\n}",
11
+ },
12
+ {
13
+ description: "multiple parameters with type hints",
14
+ input: "def add(x: number, y: number) { x }",
15
+ expectedOutput: "def add(x: number, y: number) {\nx\n}",
16
+ },
17
+ {
18
+ description: "mixed typed and untyped parameters",
19
+ input: "def mixed(x: number, y) { x }",
20
+ expectedOutput: "def mixed(x: number, y) {\nx\n}",
21
+ },
22
+ {
23
+ description: "array type hint",
24
+ input: "def process(items: number[]) { items }",
25
+ expectedOutput: "def process(items: number[]) {\nitems\n}",
26
+ },
27
+ {
28
+ description: "union type hint",
29
+ input: "def flexible(value: string | number) { value }",
30
+ expectedOutput: "def flexible(value: string | number) {\nvalue\n}",
31
+ },
32
+ {
33
+ description: "type hint with docstring",
34
+ input: 'def add(x: number, y: number) {\n """Adds two numbers"""\n x\n}',
35
+ expectedOutput: 'def add(x: number, y: number) {\n """\n Adds two numbers\n """\nx\n}',
36
+ },
37
+ {
38
+ description: "multiple array types",
39
+ input: "def multi(arr: string[], count: number) { arr }",
40
+ expectedOutput: "def multi(arr: string[], count: number) {\narr\n}",
41
+ },
42
+ {
43
+ description: "nested array type",
44
+ input: "def nested(matrix: number[][]) { matrix }",
45
+ expectedOutput: "def nested(matrix: number[][]) {\nmatrix\n}",
46
+ },
47
+ {
48
+ description: "custom type name",
49
+ input: "def handle(data: CustomType) { data }",
50
+ expectedOutput: "def handle(data: CustomType) {\ndata\n}",
51
+ },
52
+ {
53
+ description: "untyped parameters (backward compatibility)",
54
+ input: "def old(x, y) { x }",
55
+ expectedOutput: "def old(x, y) {\nx\n}",
56
+ },
57
+ ];
58
+ testCases.forEach(({ description, input, expectedOutput }) => {
59
+ it(`should correctly generate ${description}`, () => {
60
+ const parseResult = parseAgency(input);
61
+ expect(parseResult.success).toBe(true);
62
+ if (!parseResult.success)
63
+ return;
64
+ const generator = new AgencyGenerator();
65
+ const result = generator.generate(parseResult.result);
66
+ // Normalize whitespace for comparison
67
+ const normalizedOutput = result.output.trim();
68
+ const normalizedExpected = expectedOutput.trim();
69
+ expect(normalizedOutput).toBe(normalizedExpected);
70
+ });
71
+ });
72
+ });
73
+ describe("Round-trip parsing", () => {
74
+ const testCases = [
75
+ {
76
+ description: "function with typed parameters",
77
+ input: "def add(x: number, y: number) { x }",
78
+ },
79
+ {
80
+ description: "function with mixed parameters",
81
+ input: "def mixed(a: string, b) { a }",
82
+ },
83
+ {
84
+ description: "function with array type",
85
+ input: "def process(items: number[]) { items }",
86
+ },
87
+ {
88
+ description: "function with union type",
89
+ input: "def flex(val: string | number) { val }",
90
+ },
91
+ {
92
+ description: "function with complex types",
93
+ input: "def complex(arr: string[], count: number, flag: boolean) { arr }",
94
+ },
95
+ ];
96
+ testCases.forEach(({ description, input }) => {
97
+ it(`should preserve ${description} in round-trip`, () => {
98
+ // First parse
99
+ const firstParse = parseAgency(input);
100
+ expect(firstParse.success).toBe(true);
101
+ if (!firstParse.success)
102
+ return;
103
+ // Generate agency code
104
+ const generator = new AgencyGenerator();
105
+ const generated = generator.generate(firstParse.result);
106
+ // Second parse
107
+ const secondParse = parseAgency(generated.output);
108
+ expect(secondParse.success).toBe(true);
109
+ if (!secondParse.success)
110
+ return;
111
+ // Compare the function nodes - they should be identical
112
+ const firstFunc = firstParse.result.nodes[0];
113
+ const secondFunc = secondParse.result.nodes[0];
114
+ expect(secondFunc.type).toBe("function");
115
+ expect(secondFunc.functionName).toBe(firstFunc.functionName);
116
+ expect(secondFunc.parameters).toEqual(firstFunc.parameters);
117
+ expect(secondFunc.body).toEqual(firstFunc.body);
118
+ });
119
+ });
120
+ });
121
+ describe("Type preservation", () => {
122
+ it("should preserve primitive types", () => {
123
+ const input = "def test(n: number, s: string, b: boolean) { n }";
124
+ const parseResult = parseAgency(input);
125
+ expect(parseResult.success).toBe(true);
126
+ if (!parseResult.success)
127
+ return;
128
+ const generator = new AgencyGenerator();
129
+ const result = generator.generate(parseResult.result);
130
+ expect(result.output).toContain("n: number");
131
+ expect(result.output).toContain("s: string");
132
+ expect(result.output).toContain("b: boolean");
133
+ });
134
+ it("should preserve array types", () => {
135
+ const input = "def test(nums: number[], strs: string[]) { nums }";
136
+ const parseResult = parseAgency(input);
137
+ expect(parseResult.success).toBe(true);
138
+ if (!parseResult.success)
139
+ return;
140
+ const generator = new AgencyGenerator();
141
+ const result = generator.generate(parseResult.result);
142
+ expect(result.output).toContain("nums: number[]");
143
+ expect(result.output).toContain("strs: string[]");
144
+ });
145
+ it("should preserve union types", () => {
146
+ const input = "def test(val: string | number | boolean) { val }";
147
+ const parseResult = parseAgency(input);
148
+ expect(parseResult.success).toBe(true);
149
+ if (!parseResult.success)
150
+ return;
151
+ const generator = new AgencyGenerator();
152
+ const result = generator.generate(parseResult.result);
153
+ expect(result.output).toContain("val: string | number | boolean");
154
+ });
155
+ it("should preserve nested array types", () => {
156
+ const input = "def test(matrix: number[][]) { matrix }";
157
+ const parseResult = parseAgency(input);
158
+ expect(parseResult.success).toBe(true);
159
+ if (!parseResult.success)
160
+ return;
161
+ const generator = new AgencyGenerator();
162
+ const result = generator.generate(parseResult.result);
163
+ expect(result.output).toContain("matrix: number[][]");
164
+ });
165
+ });
166
+ describe("Mixed typed and untyped parameters", () => {
167
+ it("should handle first parameter typed, second untyped", () => {
168
+ const input = "def test(x: number, y) { x }";
169
+ const parseResult = parseAgency(input);
170
+ expect(parseResult.success).toBe(true);
171
+ if (!parseResult.success)
172
+ return;
173
+ const generator = new AgencyGenerator();
174
+ const result = generator.generate(parseResult.result);
175
+ expect(result.output).toContain("x: number");
176
+ expect(result.output).toContain(", y)");
177
+ expect(result.output).not.toContain("y:");
178
+ });
179
+ it("should handle first parameter untyped, second typed", () => {
180
+ const input = "def test(x, y: string) { x }";
181
+ const parseResult = parseAgency(input);
182
+ expect(parseResult.success).toBe(true);
183
+ if (!parseResult.success)
184
+ return;
185
+ const generator = new AgencyGenerator();
186
+ const result = generator.generate(parseResult.result);
187
+ expect(result.output).toContain("y: string");
188
+ expect(result.output).toMatch(/test\(x,/);
189
+ expect(result.output).not.toContain("x:");
190
+ });
191
+ it("should handle alternating typed and untyped parameters", () => {
192
+ const input = "def test(a, b: number, c, d: string) { a }";
193
+ const parseResult = parseAgency(input);
194
+ expect(parseResult.success).toBe(true);
195
+ if (!parseResult.success)
196
+ return;
197
+ const generator = new AgencyGenerator();
198
+ const result = generator.generate(parseResult.result);
199
+ expect(result.output).toContain("b: number");
200
+ expect(result.output).toContain("d: string");
201
+ expect(result.output).not.toContain("a:");
202
+ expect(result.output).not.toContain("c:");
203
+ });
204
+ });
205
+ });
@@ -69,7 +69,7 @@ export class BaseGenerator {
69
69
  return "";
70
70
  }
71
71
  collectFunctionSignature(node) {
72
- this.functionSignatures[node.functionName] = node.parameters;
72
+ this.functionSignatures[node.functionName] = node.parameters.map((param) => param.name);
73
73
  }
74
74
  processGraphNodeName(node) { }
75
75
  processNode(node) {
@@ -113,6 +113,8 @@ export class BaseGenerator {
113
113
  return this.processWhileLoop(node);
114
114
  case "specialVar":
115
115
  return this.processSpecialVar(node);
116
+ case "indexAccess":
117
+ return this.processIndexAccess(node);
116
118
  default:
117
119
  throw new Error(`Unhandled Agency node type: ${node.type}`);
118
120
  }
@@ -257,12 +257,14 @@ export class GraphGenerator extends TypeScriptGenerator {
257
257
  if (arg.type === "functionCall") {
258
258
  this.functionsUsed.add(arg.functionName);
259
259
  return this.generateFunctionCallExpression(arg);
260
- }
261
- else if (arg.type === "accessExpression") {
262
- return this.processAccessExpression(arg);
263
- }
260
+ /* } else if (arg.type === "accessExpression") {
261
+ return this.processAccessExpression(arg);
262
+ } else if (arg.type === "indexAccess") {
263
+ return this.processIndexAccess(arg);
264
+ */ }
264
265
  else {
265
- return this.generateLiteral(arg);
266
+ return this.processNode(arg);
267
+ // return this.generateLiteral(arg);
266
268
  }
267
269
  });
268
270
  const argsString = parts.join(", ");
@@ -1,14 +1,14 @@
1
1
  import { AgencyComment, AgencyProgram, Assignment, Literal, PromptLiteral, PromptSegment, TypeAlias, TypeHint, TypeHintMap } from "../types.js";
2
+ import { SpecialVar } from "../types/specialVar.js";
2
3
  import { AccessExpression, DotFunctionCall, DotProperty, IndexAccess } from "../types/access.js";
3
4
  import { AgencyArray, AgencyObject } from "../types/dataStructures.js";
4
5
  import { FunctionCall, FunctionDefinition } from "../types/function.js";
6
+ import { ImportStatement } from "../types/importStatement.js";
5
7
  import { MatchBlock } from "../types/matchBlock.js";
6
- import { BaseGenerator } from "./baseGenerator.js";
7
8
  import { ReturnStatement } from "../types/returnStatement.js";
8
9
  import { UsesTool } from "../types/tools.js";
9
- import { ImportStatement } from "../types/importStatement.js";
10
10
  import { WhileLoop } from "../types/whileLoop.js";
11
- import { SpecialVar } from "../types/specialVar.js";
11
+ import { BaseGenerator } from "./baseGenerator.js";
12
12
  export declare class TypeScriptGenerator extends BaseGenerator {
13
13
  constructor();
14
14
  protected generateBuiltins(): string;
@@ -1,15 +1,15 @@
1
+ import * as renderSpecialVar from "../templates/backends/graphGenerator/specialVar.js";
2
+ import * as builtinTools from "../templates/backends/typescriptGenerator/builtinTools.js";
3
+ import * as renderFunctionDefinition from "../templates/backends/typescriptGenerator/functionDefinition.js";
1
4
  import * as renderImports from "../templates/backends/typescriptGenerator/imports.js";
2
5
  import * as promptFunction from "../templates/backends/typescriptGenerator/promptFunction.js";
3
6
  import * as renderTool from "../templates/backends/typescriptGenerator/tool.js";
4
7
  import * as renderToolCall from "../templates/backends/typescriptGenerator/toolCall.js";
5
- import * as renderFunctionDefinition from "../templates/backends/typescriptGenerator/functionDefinition.js";
6
- import * as renderSpecialVar from "../templates/backends/graphGenerator/specialVar.js";
7
8
  import { escape, zip } from "../utils.js";
8
9
  import { BaseGenerator } from "./baseGenerator.js";
9
10
  import { generateBuiltinHelpers, mapFunctionName, } from "./typescriptGenerator/builtins.js";
10
11
  import { variableTypeToString } from "./typescriptGenerator/typeToString.js";
11
12
  import { DEFAULT_SCHEMA, mapTypeToZodSchema, } from "./typescriptGenerator/typeToZodSchema.js";
12
- import * as builtinTools from "../templates/backends/typescriptGenerator/builtinTools.js";
13
13
  export class TypeScriptGenerator extends BaseGenerator {
14
14
  constructor() {
15
15
  super();
@@ -157,12 +157,12 @@ export class TypeScriptGenerator extends BaseGenerator {
157
157
  }
158
158
  const properties = {};
159
159
  parameters.forEach((param) => {
160
- const typeHint = this.typeHints[param] || {
160
+ const typeHint = param.typeHint || {
161
161
  type: "primitiveType",
162
162
  value: "string",
163
163
  };
164
164
  const tsType = mapTypeToZodSchema(typeHint, this.typeAliases);
165
- properties[param] = tsType;
165
+ properties[param.name] = tsType;
166
166
  });
167
167
  let schema = "";
168
168
  for (const [key, value] of Object.entries(properties)) {
@@ -183,13 +183,13 @@ export class TypeScriptGenerator extends BaseGenerator {
183
183
  */
184
184
  processFunctionDefinition(node) {
185
185
  const { functionName, body, parameters } = node;
186
- this.functionScopedVariables = [...parameters];
186
+ this.functionScopedVariables = [...parameters.map((p) => p.name)];
187
187
  const bodyCode = [];
188
188
  for (const stmt of body) {
189
189
  bodyCode.push(this.processNode(stmt));
190
190
  }
191
191
  this.functionScopedVariables = [];
192
- const args = parameters.join(", ") || "";
192
+ const args = parameters.map((p) => p.name).join(", ") || "";
193
193
  return renderFunctionDefinition.default({
194
194
  functionName,
195
195
  args: "{" + args + "}",
@@ -219,12 +219,14 @@ export class TypeScriptGenerator extends BaseGenerator {
219
219
  if (arg.type === "functionCall") {
220
220
  this.functionsUsed.add(arg.functionName);
221
221
  return this.generateFunctionCallExpression(arg);
222
- }
223
- else if (arg.type === "accessExpression") {
224
- return this.processAccessExpression(arg);
225
- }
222
+ /* } else if (arg.type === "accessExpression") {
223
+ return this.processAccessExpression(arg);
224
+ } else if (arg.type === "indexAccess") {
225
+ return this.processIndexAccess(arg);
226
+ */ }
226
227
  else {
227
- return this.generateLiteral(arg);
228
+ // return this.generateLiteral(arg);
229
+ return this.processNode(arg);
228
230
  }
229
231
  });
230
232
  let argsString = "";
@@ -1,6 +1,4 @@
1
- import { AccessExpression, DotFunctionCall, DotProperty, IndexAccess } from "../types/access.js";
2
- import { Parser, ParserResult } from "tarsec";
3
- export declare const dotPropertyParser: (input: string) => ParserResult<DotProperty>;
1
+ import { ParserResult } from "tarsec";
2
+ import { AccessExpression, IndexAccess } from "../types/access.js";
3
+ export declare function accessExpressionParser(input: string): ParserResult<AccessExpression>;
4
4
  export declare const indexAccessParser: (input: string) => ParserResult<IndexAccess>;
5
- export declare const dotFunctionCallParser: (input: string) => ParserResult<DotFunctionCall>;
6
- export declare const accessExpressionParser: Parser<AccessExpression>;
@@ -1,19 +1,60 @@
1
- import { capture, char, many1WithJoin, or, seqC, set, } from "tarsec";
1
+ import { capture, char, failure, or, sepBy1, seqC, set, success, } from "tarsec";
2
+ import { accessExpression, } from "../types/access.js";
2
3
  import { agencyArrayParser } from "./dataStructures.js";
3
4
  import { functionCallParser } from "./functionCall.js";
4
- import { literalParser } from "./literals.js";
5
- import { optionalSemicolon } from "./parserUtils.js";
6
- import { varNameChar } from "./utils.js";
7
- export const dotPropertyParser = (input) => {
8
- const parser = seqC(set("type", "dotProperty"), capture(or(literalParser, functionCallParser), "object"), char("."), capture(many1WithJoin(varNameChar), "propertyName"));
9
- return parser(input);
10
- };
5
+ import { literalParser, variableNameParser } from "./literals.js";
6
+ function createAccessExpression(arr) {
7
+ const expression = _createAccessExpression(arr);
8
+ return expression;
9
+ }
10
+ function _createAccessExpression(arr) {
11
+ if (arr.length < 1) {
12
+ throw new Error(`Not enough items to create access expression: ${JSON.stringify(arr)}`);
13
+ }
14
+ if (arr.length === 1) {
15
+ return arr[0];
16
+ }
17
+ if (arr.length > 1) {
18
+ const head = arr.slice(0, -1);
19
+ const last = arr.at(-1);
20
+ switch (last?.type) {
21
+ case "variableName":
22
+ return accessExpression({
23
+ type: "dotProperty",
24
+ object: _createAccessExpression(head),
25
+ propertyName: last.value,
26
+ });
27
+ case "functionCall":
28
+ return accessExpression({
29
+ type: "dotFunctionCall",
30
+ object: _createAccessExpression(head),
31
+ functionCall: last,
32
+ });
33
+ case "indexAccess":
34
+ return accessExpression({
35
+ type: "indexAccess",
36
+ array: _createAccessExpression([...head, last.array]),
37
+ index: last.index,
38
+ });
39
+ default:
40
+ throw new Error(`unknown type ${last && last.type} in createAccessExpression`);
41
+ }
42
+ }
43
+ throw new Error(`we should NEVER get here: ${JSON.stringify(arr)} `);
44
+ }
45
+ export function accessExpressionParser(input) {
46
+ const parser = sepBy1(char("."), or(indexAccessParser, functionCallParser, variableNameParser));
47
+ const result = parser(input);
48
+ if (result.success === false) {
49
+ return result;
50
+ }
51
+ if (result.result.length < 2) {
52
+ return failure("Didn't find property access or function call", input);
53
+ }
54
+ const access = createAccessExpression(result.result);
55
+ return success(access, result.rest);
56
+ }
11
57
  export const indexAccessParser = (input) => {
12
58
  const parser = seqC(set("type", "indexAccess"), capture(or(agencyArrayParser, functionCallParser, literalParser), "array"), char("["), capture(or(functionCallParser, literalParser), "index"), char("]"));
13
59
  return parser(input);
14
60
  };
15
- export const dotFunctionCallParser = (input) => {
16
- const parser = seqC(set("type", "dotFunctionCall"), capture(or(functionCallParser, literalParser), "object"), char("."), capture(functionCallParser, "functionCall"));
17
- return parser(input);
18
- };
19
- export const accessExpressionParser = seqC(set("type", "accessExpression"), capture(or(dotFunctionCallParser, dotPropertyParser, indexAccessParser), "expression"), optionalSemicolon);