agency-lang 0.0.16 → 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.
@@ -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:
@@ -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);
@@ -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":
@@ -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
  }
@@ -220,10 +221,11 @@ export class TypeScriptGenerator extends BaseGenerator {
220
221
  this.functionsUsed.add(arg.functionName);
221
222
  return this.generateFunctionCallExpression(arg);
222
223
  /* } else if (arg.type === "accessExpression") {
223
- return this.processAccessExpression(arg);
224
- } else if (arg.type === "indexAccess") {
225
- return this.processIndexAccess(arg);
226
- */ }
224
+ return this.processAccessExpression(arg);
225
+ } else if (arg.type === "indexAccess") {
226
+ return this.processIndexAccess(arg);
227
+ */
228
+ }
227
229
  else {
228
230
  // return this.generateLiteral(arg);
229
231
  return this.processNode(arg);
@@ -252,6 +254,8 @@ export class TypeScriptGenerator extends BaseGenerator {
252
254
  return literal.value;
253
255
  case "string":
254
256
  return `"${escape(literal.value)}"`;
257
+ case "multiLineString":
258
+ return `\`${escape(literal.value)}\``;
255
259
  case "variableName":
256
260
  return literal.value;
257
261
  case "prompt":
@@ -1,9 +1,10 @@
1
- import { InterpolationSegment, Literal, NumberLiteral, PromptLiteral, StringLiteral, TextSegment, VariableNameLiteral } from "../types.js";
1
+ import { InterpolationSegment, Literal, MultiLineStringLiteral, NumberLiteral, PromptLiteral, StringLiteral, TextSegment, VariableNameLiteral } from "../types.js";
2
2
  import { Parser } from "tarsec";
3
3
  export declare const textSegmentParser: Parser<TextSegment>;
4
4
  export declare const interpolationSegmentParser: Parser<InterpolationSegment>;
5
5
  export declare const promptParser: Parser<PromptLiteral>;
6
6
  export declare const numberParser: Parser<NumberLiteral>;
7
7
  export declare const stringParser: Parser<StringLiteral>;
8
+ export declare const multiLineStringParser: Parser<MultiLineStringLiteral>;
8
9
  export declare const variableNameParser: Parser<VariableNameLiteral>;
9
10
  export declare const literalParser: Parser<Literal>;
@@ -1,13 +1,14 @@
1
1
  import { backtick, varNameChar } from "./utils.js";
2
- import { capture, char, digit, letter, many, many1Till, many1WithJoin, manyTill, manyWithJoin, map, or, seq, seqC, set, } from "tarsec";
2
+ import { capture, char, digit, letter, many, many1Till, many1WithJoin, manyTillOneOf, manyTillStr, manyWithJoin, map, or, seq, seqC, set, str, } from "tarsec";
3
3
  export const textSegmentParser = map(many1Till(or(backtick, char("$"))), (text) => ({
4
4
  type: "text",
5
5
  value: text,
6
6
  }));
7
- export const interpolationSegmentParser = seqC(set("type", "interpolation"), char("$"), char("{"), capture(many1Till(char("}")), "variableName"), char("}"));
7
+ export const interpolationSegmentParser = seqC(set("type", "interpolation"), char("$"), char("{"), capture(manyTillStr("}"), "variableName"), char("}"));
8
8
  export const promptParser = seqC(set("type", "prompt"), backtick, capture(many(or(textSegmentParser, interpolationSegmentParser)), "segments"), backtick);
9
9
  export const numberParser = seqC(set("type", "number"), capture(many1WithJoin(or(char("-"), char("."), digit)), "value"));
10
- export const stringParser = seqC(set("type", "string"), char('"'), capture(manyTill(char('"')), "value"), char('"'));
10
+ export const stringParser = seqC(set("type", "string"), char('"'), capture(manyTillOneOf(['"', "\n"]), "value"), char('"'));
11
+ export const multiLineStringParser = seqC(set("type", "multiLineString"), str('"""'), capture(manyTillStr('"""'), "value"), str('"""'));
11
12
  export const variableNameParser = seq([
12
13
  set("type", "variableName"),
13
14
  capture(letter, "init"),
@@ -18,4 +19,4 @@ export const variableNameParser = seq([
18
19
  value: `${captures.init}${captures.value}`,
19
20
  };
20
21
  });
21
- export const literalParser = or(promptParser, numberParser, stringParser, variableNameParser);
22
+ export const literalParser = or(promptParser, numberParser, multiLineStringParser, stringParser, variableNameParser);
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { textSegmentParser, interpolationSegmentParser, promptParser, numberParser, stringParser, variableNameParser, literalParser, } from "./literals.js";
2
+ import { textSegmentParser, interpolationSegmentParser, promptParser, numberParser, stringParser, multiLineStringParser, variableNameParser, literalParser, } from "./literals.js";
3
3
  describe("literals parsers", () => {
4
4
  describe("textSegmentParser", () => {
5
5
  const testCases = [
@@ -112,7 +112,16 @@ describe("literals parsers", () => {
112
112
  { input: "${foo", expected: { success: false } },
113
113
  { input: "$foo}", expected: { success: false } },
114
114
  { input: "{foo}", expected: { success: false } },
115
- { input: "${}", expected: { success: false } },
115
+ {
116
+ input: "${}",
117
+ expected: {
118
+ success: true,
119
+ result: {
120
+ type: "interpolation",
121
+ variableName: "",
122
+ },
123
+ },
124
+ },
116
125
  { input: "", expected: { success: false } },
117
126
  { input: "foo", expected: { success: false } },
118
127
  ];
@@ -391,13 +400,6 @@ describe("literals parsers", () => {
391
400
  result: { type: "string", value: "tab\there" },
392
401
  },
393
402
  },
394
- {
395
- input: '"newline\nhere"',
396
- expected: {
397
- success: true,
398
- result: { type: "string", value: "newline\nhere" },
399
- },
400
- },
401
403
  {
402
404
  input: '"special!@#$%^&*()"',
403
405
  expected: {
@@ -425,6 +427,13 @@ describe("literals parsers", () => {
425
427
  { input: "'hello'", expected: { success: false } },
426
428
  { input: "", expected: { success: false } },
427
429
  { input: "hello", expected: { success: false } },
430
+ /// use """ for multi-line strings
431
+ {
432
+ input: '"newline\nhere"',
433
+ expected: {
434
+ success: false,
435
+ },
436
+ },
428
437
  ];
429
438
  testCases.forEach(({ input, expected }) => {
430
439
  if (expected.success) {
@@ -444,6 +453,170 @@ describe("literals parsers", () => {
444
453
  }
445
454
  });
446
455
  });
456
+ describe("multiLineStringParser", () => {
457
+ const testCases = [
458
+ // Happy path - simple strings
459
+ {
460
+ input: '"""hello"""',
461
+ expected: {
462
+ success: true,
463
+ result: { type: "multiLineString", value: "hello" },
464
+ },
465
+ },
466
+ {
467
+ input: '"""world"""',
468
+ expected: {
469
+ success: true,
470
+ result: { type: "multiLineString", value: "world" },
471
+ },
472
+ },
473
+ // Empty multi-line string
474
+ {
475
+ input: '""""""',
476
+ expected: {
477
+ success: true,
478
+ result: { type: "multiLineString", value: "" },
479
+ },
480
+ },
481
+ // Multi-line strings with actual newlines
482
+ {
483
+ input: '"""line1\nline2"""',
484
+ expected: {
485
+ success: true,
486
+ result: { type: "multiLineString", value: "line1\nline2" },
487
+ },
488
+ },
489
+ {
490
+ input: '"""line1\nline2\nline3"""',
491
+ expected: {
492
+ success: true,
493
+ result: { type: "multiLineString", value: "line1\nline2\nline3" },
494
+ },
495
+ },
496
+ {
497
+ input: '"""\nstarts with newline"""',
498
+ expected: {
499
+ success: true,
500
+ result: { type: "multiLineString", value: "\nstarts with newline" },
501
+ },
502
+ },
503
+ {
504
+ input: '"""ends with newline\n"""',
505
+ expected: {
506
+ success: true,
507
+ result: { type: "multiLineString", value: "ends with newline\n" },
508
+ },
509
+ },
510
+ // Strings with special characters
511
+ {
512
+ input: '"""Hello, World!"""',
513
+ expected: {
514
+ success: true,
515
+ result: { type: "multiLineString", value: "Hello, World!" },
516
+ },
517
+ },
518
+ {
519
+ input: '"""123"""',
520
+ expected: {
521
+ success: true,
522
+ result: { type: "multiLineString", value: "123" },
523
+ },
524
+ },
525
+ {
526
+ input: '""" spaces and tabs\t\t"""',
527
+ expected: {
528
+ success: true,
529
+ result: { type: "multiLineString", value: " spaces and tabs\t\t" },
530
+ },
531
+ },
532
+ {
533
+ input: '"""special!@#$%^&*()"""',
534
+ expected: {
535
+ success: true,
536
+ result: { type: "multiLineString", value: "special!@#$%^&*()" },
537
+ },
538
+ },
539
+ // Strings containing single and double quotes
540
+ {
541
+ input: '"""single\'quotes\'here"""',
542
+ expected: {
543
+ success: true,
544
+ result: { type: "multiLineString", value: "single'quotes'here" },
545
+ },
546
+ },
547
+ {
548
+ input: '"""double"quotes"here"""',
549
+ expected: {
550
+ success: true,
551
+ result: { type: "multiLineString", value: 'double"quotes"here' },
552
+ },
553
+ },
554
+ {
555
+ input: '"""mixed"and\'quotes"""',
556
+ expected: {
557
+ success: true,
558
+ result: { type: "multiLineString", value: `mixed"and'quotes` },
559
+ },
560
+ },
561
+ // Strings containing backticks and interpolation-like syntax
562
+ {
563
+ input: '"""`backtick`"""',
564
+ expected: {
565
+ success: true,
566
+ result: { type: "multiLineString", value: "`backtick`" },
567
+ },
568
+ },
569
+ {
570
+ input: '"""${notInterpolation}"""',
571
+ expected: {
572
+ success: true,
573
+ result: { type: "multiLineString", value: "${notInterpolation}" },
574
+ },
575
+ },
576
+ // Multiple consecutive newlines
577
+ {
578
+ input: '"""line1\n\n\nline2"""',
579
+ expected: {
580
+ success: true,
581
+ result: { type: "multiLineString", value: "line1\n\n\nline2" },
582
+ },
583
+ },
584
+ // Mixed whitespace
585
+ {
586
+ input: '""" \n\t\n """',
587
+ expected: {
588
+ success: true,
589
+ result: { type: "multiLineString", value: " \n\t\n " },
590
+ },
591
+ },
592
+ // Failure cases
593
+ { input: '"""hello', expected: { success: false } },
594
+ { input: 'hello"""', expected: { success: false } },
595
+ { input: '""hello"""', expected: { success: false } },
596
+ { input: '"""hello""', expected: { success: false } },
597
+ { input: '"hello"', expected: { success: false } },
598
+ { input: "'hello'", expected: { success: false } },
599
+ { input: "", expected: { success: false } },
600
+ { input: "hello", expected: { success: false } },
601
+ ];
602
+ testCases.forEach(({ input, expected }) => {
603
+ if (expected.success) {
604
+ it(`should parse ${JSON.stringify(input)} successfully`, () => {
605
+ const result = multiLineStringParser(input);
606
+ expect(result.success).toBe(true);
607
+ if (result.success) {
608
+ expect(result.result).toEqual(expected.result);
609
+ }
610
+ });
611
+ }
612
+ else {
613
+ it(`should fail to parse ${JSON.stringify(input)}`, () => {
614
+ const result = multiLineStringParser(input);
615
+ expect(result.success).toBe(false);
616
+ });
617
+ }
618
+ });
619
+ });
447
620
  describe("variableNameParser", () => {
448
621
  const testCases = [
449
622
  // Happy path
@@ -1,4 +1,4 @@
1
- export type Literal = NumberLiteral | StringLiteral | VariableNameLiteral | PromptLiteral;
1
+ export type Literal = NumberLiteral | MultiLineStringLiteral | StringLiteral | VariableNameLiteral | PromptLiteral;
2
2
  export type NumberLiteral = {
3
3
  type: "number";
4
4
  value: string;
@@ -7,6 +7,10 @@ export type StringLiteral = {
7
7
  type: "string";
8
8
  value: string;
9
9
  };
10
+ export type MultiLineStringLiteral = {
11
+ type: "multiLineString";
12
+ value: string;
13
+ };
10
14
  export type VariableNameLiteral = {
11
15
  type: "variableName";
12
16
  value: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agency-lang",
3
- "version": "0.0.16",
3
+ "version": "0.0.17",
4
4
  "description": "The Agency language",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -55,4 +55,4 @@
55
55
  "typescript": "^5.9.3",
56
56
  "vitest": "^4.0.16"
57
57
  }
58
- }
58
+ }