agency-lang 0.0.15 → 0.0.17

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 (41) hide show
  1. package/dist/lib/backends/agencyGenerator.js +24 -6
  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.d.ts +1 -0
  5. package/dist/lib/backends/baseGenerator.js +12 -2
  6. package/dist/lib/backends/graphGenerator.js +7 -5
  7. package/dist/lib/backends/typescriptGenerator.d.ts +3 -3
  8. package/dist/lib/backends/typescriptGenerator.js +17 -11
  9. package/dist/lib/parsers/access.d.ts +3 -5
  10. package/dist/lib/parsers/access.js +54 -13
  11. package/dist/lib/parsers/access.test.js +40 -500
  12. package/dist/lib/parsers/assignment.js +2 -2
  13. package/dist/lib/parsers/assignment.test.d.ts +1 -0
  14. package/dist/lib/parsers/assignment.test.js +279 -0
  15. package/dist/lib/parsers/dataStructures.js +3 -3
  16. package/dist/lib/parsers/function.d.ts +3 -1
  17. package/dist/lib/parsers/function.js +6 -4
  18. package/dist/lib/parsers/function.test.js +653 -8
  19. package/dist/lib/parsers/functionCall.js +3 -2
  20. package/dist/lib/parsers/functionCall.test.js +310 -0
  21. package/dist/lib/parsers/literals.d.ts +2 -1
  22. package/dist/lib/parsers/literals.js +15 -5
  23. package/dist/lib/parsers/literals.test.js +189 -16
  24. package/dist/lib/parsers/matchBlock.js +2 -2
  25. package/dist/lib/parsers/parserUtils.test.d.ts +1 -0
  26. package/dist/lib/parsers/parserUtils.test.js +46 -0
  27. package/dist/lib/parsers/returnStatement.js +2 -2
  28. package/dist/lib/parsers/returnStatement.test.d.ts +1 -0
  29. package/dist/lib/parsers/returnStatement.test.js +268 -0
  30. package/dist/lib/parsers/specialVar.test.d.ts +1 -0
  31. package/dist/lib/parsers/specialVar.test.js +219 -0
  32. package/dist/lib/types/access.d.ts +5 -5
  33. package/dist/lib/types/access.js +6 -1
  34. package/dist/lib/types/dataStructures.d.ts +3 -3
  35. package/dist/lib/types/function.d.ts +10 -4
  36. package/dist/lib/types/literals.d.ts +5 -1
  37. package/dist/lib/types/matchBlock.d.ts +2 -2
  38. package/dist/lib/types/returnStatement.d.ts +2 -2
  39. package/dist/lib/types/whileLoop.d.ts +2 -2
  40. package/dist/lib/types.d.ts +3 -3
  41. package/package.json +2 -2
@@ -66,12 +66,13 @@ export class AgencyGenerator extends BaseGenerator {
66
66
  return literal.value;
67
67
  case "string":
68
68
  // Escape backslashes and quotes
69
- const escaped = literal.value
70
- .replace(/\\/g, "\\\\")
71
- .replace(/"/g, '\\"');
69
+ const escaped = literal.value;
72
70
  return `"${escaped}"`;
73
71
  case "variableName":
74
72
  return literal.value;
73
+ case "multiLineString":
74
+ const escapedMultiLine = literal.value;
75
+ return `"""${escapedMultiLine}"""`;
75
76
  case "prompt":
76
77
  return this.generatePromptLiteral(literal);
77
78
  default:
@@ -100,12 +101,28 @@ export class AgencyGenerator extends BaseGenerator {
100
101
  processFunctionDefinition(node) {
101
102
  const { functionName, body, parameters } = node;
102
103
  // Build parameter list
103
- const params = parameters.join(", ");
104
+ const params = parameters
105
+ .map((p) => {
106
+ if (p.typeHint) {
107
+ const typeStr = variableTypeToString(p.typeHint, this.typeAliases);
108
+ return `${p.name}: ${typeStr}`;
109
+ }
110
+ else {
111
+ return p.name;
112
+ }
113
+ })
114
+ .join(", ");
104
115
  // Start function definition
105
116
  let result = this.indentStr(`def ${functionName}(${params}) {\n`);
106
117
  // Process body with increased indentation
107
118
  this.increaseIndent();
108
- this.functionScopedVariables = [...parameters];
119
+ if (node.docString) {
120
+ const docLines = [`"""`, node.docString.value, `"""`]
121
+ .map((line) => this.indentStr(line))
122
+ .join("\n");
123
+ result += `${docLines}\n`;
124
+ }
125
+ this.functionScopedVariables = [...parameters.map((p) => p.name)];
109
126
  const lines = [];
110
127
  for (const stmt of body) {
111
128
  lines.push(this.processNode(stmt));
@@ -154,7 +171,8 @@ export class AgencyGenerator extends BaseGenerator {
154
171
  }
155
172
  }
156
173
  processDotProperty(node) {
157
- const objectCode = this.processNode(node.object).trim();
174
+ let objectCode = this.processNode(node.object);
175
+ objectCode = objectCode.trim();
158
176
  return `${objectCode}.${node.propertyName}`;
159
177
  }
160
178
  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
+ });
@@ -15,6 +15,7 @@ export declare class BaseGenerator {
15
15
  protected generatedStatements: string[];
16
16
  protected generatedTypeAliases: string[];
17
17
  protected functionScopedVariables: string[];
18
+ protected globalScopedVariables: string[];
18
19
  protected toolsUsed: string[];
19
20
  protected typeAliases: Record<string, VariableType>;
20
21
  protected functionsUsed: Set<string>;
@@ -4,6 +4,7 @@ export class BaseGenerator {
4
4
  generatedStatements = [];
5
5
  generatedTypeAliases = [];
6
6
  functionScopedVariables = [];
7
+ globalScopedVariables = [];
7
8
  // collect tools for a prompt
8
9
  toolsUsed = [];
9
10
  typeAliases = {};
@@ -39,7 +40,13 @@ export class BaseGenerator {
39
40
  this.collectFunctionSignature(node);
40
41
  }
41
42
  }
42
- // Pass 5: Process all nodes and generate code
43
+ // Pass 5: Collect global scoped variables
44
+ for (const node of program.nodes) {
45
+ if (node.type === "assignment") {
46
+ this.globalScopedVariables.push(node.variableName);
47
+ }
48
+ }
49
+ // Pass 6: Process all nodes and generate code
43
50
  for (const node of program.nodes) {
44
51
  const result = this.processNode(node);
45
52
  this.generatedStatements.push(result);
@@ -69,7 +76,7 @@ export class BaseGenerator {
69
76
  return "";
70
77
  }
71
78
  collectFunctionSignature(node) {
72
- this.functionSignatures[node.functionName] = node.parameters;
79
+ this.functionSignatures[node.functionName] = node.parameters.map((param) => param.name);
73
80
  }
74
81
  processGraphNodeName(node) { }
75
82
  processNode(node) {
@@ -91,6 +98,7 @@ export class BaseGenerator {
91
98
  case "matchBlock":
92
99
  return this.processMatchBlock(node);
93
100
  case "number":
101
+ case "multiLineString":
94
102
  case "string":
95
103
  case "variableName":
96
104
  case "prompt":
@@ -113,6 +121,8 @@ export class BaseGenerator {
113
121
  return this.processWhileLoop(node);
114
122
  case "specialVar":
115
123
  return this.processSpecialVar(node);
124
+ case "indexAccess":
125
+ return this.processIndexAccess(node);
116
126
  default:
117
127
  throw new Error(`Unhandled Agency node type: ${node.type}`);
118
128
  }
@@ -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();
@@ -135,7 +135,8 @@ export class TypeScriptGenerator extends BaseGenerator {
135
135
  .filter((s) => s.type === "interpolation")
136
136
  .map((s) => s.variableName);
137
137
  for (const varName of interpolatedVars) {
138
- if (!this.functionScopedVariables.includes(varName)) {
138
+ if (!this.functionScopedVariables.includes(varName) &&
139
+ !this.globalScopedVariables.includes(varName)) {
139
140
  throw new Error(`Variable '${varName}' used in prompt interpolation but not defined. ` +
140
141
  `Referenced in assignment to '${variableName}'.`);
141
142
  }
@@ -157,12 +158,12 @@ export class TypeScriptGenerator extends BaseGenerator {
157
158
  }
158
159
  const properties = {};
159
160
  parameters.forEach((param) => {
160
- const typeHint = this.typeHints[param] || {
161
+ const typeHint = param.typeHint || {
161
162
  type: "primitiveType",
162
163
  value: "string",
163
164
  };
164
165
  const tsType = mapTypeToZodSchema(typeHint, this.typeAliases);
165
- properties[param] = tsType;
166
+ properties[param.name] = tsType;
166
167
  });
167
168
  let schema = "";
168
169
  for (const [key, value] of Object.entries(properties)) {
@@ -183,13 +184,13 @@ export class TypeScriptGenerator extends BaseGenerator {
183
184
  */
184
185
  processFunctionDefinition(node) {
185
186
  const { functionName, body, parameters } = node;
186
- this.functionScopedVariables = [...parameters];
187
+ this.functionScopedVariables = [...parameters.map((p) => p.name)];
187
188
  const bodyCode = [];
188
189
  for (const stmt of body) {
189
190
  bodyCode.push(this.processNode(stmt));
190
191
  }
191
192
  this.functionScopedVariables = [];
192
- const args = parameters.join(", ") || "";
193
+ const args = parameters.map((p) => p.name).join(", ") || "";
193
194
  return renderFunctionDefinition.default({
194
195
  functionName,
195
196
  args: "{" + args + "}",
@@ -219,12 +220,15 @@ export class TypeScriptGenerator extends BaseGenerator {
219
220
  if (arg.type === "functionCall") {
220
221
  this.functionsUsed.add(arg.functionName);
221
222
  return this.generateFunctionCallExpression(arg);
222
- }
223
- else if (arg.type === "accessExpression") {
223
+ /* } else if (arg.type === "accessExpression") {
224
224
  return this.processAccessExpression(arg);
225
+ } else if (arg.type === "indexAccess") {
226
+ return this.processIndexAccess(arg);
227
+ */
225
228
  }
226
229
  else {
227
- return this.generateLiteral(arg);
230
+ // return this.generateLiteral(arg);
231
+ return this.processNode(arg);
228
232
  }
229
233
  });
230
234
  let argsString = "";
@@ -250,6 +254,8 @@ export class TypeScriptGenerator extends BaseGenerator {
250
254
  return literal.value;
251
255
  case "string":
252
256
  return `"${escape(literal.value)}"`;
257
+ case "multiLineString":
258
+ return `\`${escape(literal.value)}\``;
253
259
  case "variableName":
254
260
  return literal.value;
255
261
  case "prompt":
@@ -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);