agency-lang 0.0.91 → 0.0.92
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.
- package/dist/lib/backends/agencyGenerator.js +7 -4
- package/dist/lib/backends/typescriptBuilder.js +6 -7
- package/dist/lib/ir/builders.d.ts +6 -2
- package/dist/lib/ir/builders.js +4 -4
- package/dist/lib/ir/fluent.d.ts +3 -1
- package/dist/lib/ir/fluent.js +2 -2
- package/dist/lib/ir/prettyPrint.js +4 -2
- package/dist/lib/ir/tsIR.d.ts +1 -0
- package/dist/lib/parsers/access.test.js +67 -0
- package/dist/lib/parsers/binop.test.js +62 -0
- package/dist/lib/parsers/parsers.js +20 -11
- package/dist/lib/types/access.d.ts +3 -0
- package/dist/lib/types/binop.d.ts +1 -1
- package/dist/lib/types/binop.js +1 -0
- package/package.json +1 -1
- package/stdlib/array.js +3 -0
- package/stdlib/index.js +3 -0
|
@@ -793,13 +793,16 @@ export class AgencyGenerator {
|
|
|
793
793
|
return assigned ? multiLine : this.indentStr(multiLine);
|
|
794
794
|
}
|
|
795
795
|
processAccessChainElement(node) {
|
|
796
|
+
const dot = node.optional ? "?." : ".";
|
|
796
797
|
switch (node.kind) {
|
|
797
798
|
case "property":
|
|
798
|
-
return
|
|
799
|
-
case "index":
|
|
800
|
-
|
|
799
|
+
return `${dot}${node.name}`;
|
|
800
|
+
case "index": {
|
|
801
|
+
const inner = this.processNode(node.index).trim();
|
|
802
|
+
return node.optional ? `?.[${inner}]` : `[${inner}]`;
|
|
803
|
+
}
|
|
801
804
|
case "methodCall":
|
|
802
|
-
return
|
|
805
|
+
return `${dot}${this.generateFunctionCallExpression(node.functionCall, "valueAccess")}`;
|
|
803
806
|
default:
|
|
804
807
|
throw new Error(`Unknown access chain element kind: ${node.kind}`);
|
|
805
808
|
}
|
|
@@ -661,10 +661,10 @@ export class TypeScriptBuilder {
|
|
|
661
661
|
for (const element of node.chain) {
|
|
662
662
|
switch (element.kind) {
|
|
663
663
|
case "property":
|
|
664
|
-
result = ts.prop(result, element.name);
|
|
664
|
+
result = ts.prop(result, element.name, { optional: element.optional });
|
|
665
665
|
break;
|
|
666
666
|
case "index":
|
|
667
|
-
result = ts.index(result, this.processNode(element.index));
|
|
667
|
+
result = ts.index(result, this.processNode(element.index), { optional: element.optional });
|
|
668
668
|
break;
|
|
669
669
|
case "methodCall": {
|
|
670
670
|
const callNode = this.generateFunctionCallExpression(element.functionCall, "valueAccess");
|
|
@@ -675,15 +675,14 @@ export class TypeScriptBuilder {
|
|
|
675
675
|
const args = isClassMethod
|
|
676
676
|
? [...callNode.arguments, this.buildMethodCallConfig()]
|
|
677
677
|
: callNode.arguments;
|
|
678
|
-
const
|
|
679
|
-
|
|
680
|
-
.call(args)
|
|
681
|
-
.done();
|
|
678
|
+
const propNode = ts.prop(result, callNode.callee.name, { optional: element.optional });
|
|
679
|
+
const callExpr = ts.call(propNode, args);
|
|
682
680
|
result = isClassMethod ? ts.await(callExpr) : callExpr;
|
|
683
681
|
}
|
|
684
682
|
else {
|
|
685
683
|
// Fallback for complex cases (e.g. await-wrapped)
|
|
686
|
-
|
|
684
|
+
const dot = element.optional ? "?." : ".";
|
|
685
|
+
result = ts.raw(`${this.str(result)}${dot}${this.str(callNode)}`);
|
|
687
686
|
}
|
|
688
687
|
break;
|
|
689
688
|
}
|
|
@@ -51,8 +51,12 @@ export declare const ts: {
|
|
|
51
51
|
parenLeft?: boolean;
|
|
52
52
|
parenRight?: boolean;
|
|
53
53
|
}): TsBinOp;
|
|
54
|
-
prop(object: TsNode, property: string
|
|
55
|
-
|
|
54
|
+
prop(object: TsNode, property: string, opts?: {
|
|
55
|
+
optional?: boolean;
|
|
56
|
+
}): TsPropertyAccess;
|
|
57
|
+
index(object: TsNode, property: TsNode, opts?: {
|
|
58
|
+
optional?: boolean;
|
|
59
|
+
}): TsPropertyAccess;
|
|
56
60
|
spread(expr: TsNode): TsSpread;
|
|
57
61
|
id(name: string): TsIdentifier;
|
|
58
62
|
str(value: string): TsStringLiteral;
|
package/dist/lib/ir/builders.js
CHANGED
|
@@ -166,11 +166,11 @@ export const ts = {
|
|
|
166
166
|
parenRight: opts?.parenRight,
|
|
167
167
|
};
|
|
168
168
|
},
|
|
169
|
-
prop(object, property) {
|
|
170
|
-
return { kind: "propertyAccess", object, property, computed: false };
|
|
169
|
+
prop(object, property, opts) {
|
|
170
|
+
return { kind: "propertyAccess", object, property, computed: false, ...(opts?.optional && { optional: true }) };
|
|
171
171
|
},
|
|
172
|
-
index(object, property) {
|
|
173
|
-
return { kind: "propertyAccess", object, property, computed: true };
|
|
172
|
+
index(object, property, opts) {
|
|
173
|
+
return { kind: "propertyAccess", object, property, computed: true, ...(opts?.optional && { optional: true }) };
|
|
174
174
|
},
|
|
175
175
|
spread(expr) {
|
|
176
176
|
return { kind: "spread", expr };
|
package/dist/lib/ir/fluent.d.ts
CHANGED
|
@@ -9,7 +9,9 @@ export declare class TsChain {
|
|
|
9
9
|
readonly node: TsNode;
|
|
10
10
|
constructor(node: TsNode);
|
|
11
11
|
/** Property access: .prop("foo") → ts.prop(this, "foo") */
|
|
12
|
-
prop(name: string
|
|
12
|
+
prop(name: string, opts?: {
|
|
13
|
+
optional?: boolean;
|
|
14
|
+
}): TsChain;
|
|
13
15
|
map(fn: TsNode): TsChain;
|
|
14
16
|
/** Computed index: .index(expr) → ts.index(this, expr) */
|
|
15
17
|
index(expr: TsNode): TsChain;
|
package/dist/lib/ir/fluent.js
CHANGED
|
@@ -11,8 +11,8 @@ export class TsChain {
|
|
|
11
11
|
this.node = node;
|
|
12
12
|
}
|
|
13
13
|
/** Property access: .prop("foo") → ts.prop(this, "foo") */
|
|
14
|
-
prop(name) {
|
|
15
|
-
return new TsChain(ts.prop(this.node, name));
|
|
14
|
+
prop(name, opts) {
|
|
15
|
+
return new TsChain(ts.prop(this.node, name, opts));
|
|
16
16
|
}
|
|
17
17
|
map(fn) {
|
|
18
18
|
return new TsChain(ts.call(ts.prop(this.node, "map"), [fn]));
|
|
@@ -189,9 +189,11 @@ export function printTs(node, indent = 0) {
|
|
|
189
189
|
case "propertyAccess": {
|
|
190
190
|
const obj = printTs(node.object, indent);
|
|
191
191
|
if (node.computed) {
|
|
192
|
-
|
|
192
|
+
const prefix = node.optional ? "?." : "";
|
|
193
|
+
return `${obj}${prefix}[${printTs(node.property, indent)}]`;
|
|
193
194
|
}
|
|
194
|
-
|
|
195
|
+
const dot = node.optional ? "?." : ".";
|
|
196
|
+
return `${obj}${dot}${node.property}`;
|
|
195
197
|
}
|
|
196
198
|
case "spread":
|
|
197
199
|
return `...${printTs(node.expr, indent)}`;
|
package/dist/lib/ir/tsIR.d.ts
CHANGED
|
@@ -340,6 +340,73 @@ describe("valueAccessParser", () => {
|
|
|
340
340
|
}
|
|
341
341
|
});
|
|
342
342
|
});
|
|
343
|
+
describe("optional chaining", () => {
|
|
344
|
+
it('should parse "foo?.bar" with optional property access', () => {
|
|
345
|
+
const result = valueAccessParser("foo?.bar");
|
|
346
|
+
expect(result.success).toBe(true);
|
|
347
|
+
if (result.success) {
|
|
348
|
+
expect(result.result).toEqualWithoutLoc({
|
|
349
|
+
type: "valueAccess",
|
|
350
|
+
base: { type: "variableName", value: "foo" },
|
|
351
|
+
chain: [{ kind: "property", name: "bar", optional: true }],
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
it('should parse "foo?.bar?.baz" with chained optional access', () => {
|
|
356
|
+
const result = valueAccessParser("foo?.bar?.baz");
|
|
357
|
+
expect(result.success).toBe(true);
|
|
358
|
+
if (result.success) {
|
|
359
|
+
expect(result.result).toEqualWithoutLoc({
|
|
360
|
+
type: "valueAccess",
|
|
361
|
+
base: { type: "variableName", value: "foo" },
|
|
362
|
+
chain: [
|
|
363
|
+
{ kind: "property", name: "bar", optional: true },
|
|
364
|
+
{ kind: "property", name: "baz", optional: true },
|
|
365
|
+
],
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
it('should parse "foo?.bar.baz" mixing optional and regular access', () => {
|
|
370
|
+
const result = valueAccessParser("foo?.bar.baz");
|
|
371
|
+
expect(result.success).toBe(true);
|
|
372
|
+
if (result.success) {
|
|
373
|
+
expect(result.result).toEqualWithoutLoc({
|
|
374
|
+
type: "valueAccess",
|
|
375
|
+
base: { type: "variableName", value: "foo" },
|
|
376
|
+
chain: [
|
|
377
|
+
{ kind: "property", name: "bar", optional: true },
|
|
378
|
+
{ kind: "property", name: "baz" },
|
|
379
|
+
],
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
it('should parse "arr?.[0]" with optional index access', () => {
|
|
384
|
+
const result = valueAccessParser("arr?.[0]");
|
|
385
|
+
expect(result.success).toBe(true);
|
|
386
|
+
if (result.success) {
|
|
387
|
+
expect(result.result).toEqualWithoutLoc({
|
|
388
|
+
type: "valueAccess",
|
|
389
|
+
base: { type: "variableName", value: "arr" },
|
|
390
|
+
chain: [{ kind: "index", index: { type: "number", value: "0" }, optional: true }],
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
it('should parse "obj?.method()" with optional method call', () => {
|
|
395
|
+
const result = valueAccessParser("obj?.method()");
|
|
396
|
+
expect(result.success).toBe(true);
|
|
397
|
+
if (result.success) {
|
|
398
|
+
expect(result.result).toEqualWithoutLoc({
|
|
399
|
+
type: "valueAccess",
|
|
400
|
+
base: { type: "variableName", value: "obj" },
|
|
401
|
+
chain: [{
|
|
402
|
+
kind: "methodCall",
|
|
403
|
+
functionCall: { type: "functionCall", functionName: "method", arguments: [] },
|
|
404
|
+
optional: true,
|
|
405
|
+
}],
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
});
|
|
343
410
|
describe("failure cases", () => {
|
|
344
411
|
it('should fail to parse ""', () => {
|
|
345
412
|
expect(valueAccessParser("").success).toBe(false);
|
|
@@ -524,6 +524,68 @@ describe("binOpParser", () => {
|
|
|
524
524
|
}
|
|
525
525
|
});
|
|
526
526
|
});
|
|
527
|
+
// Nullish coalescing operator
|
|
528
|
+
describe("nullish coalescing operator", () => {
|
|
529
|
+
it('should parse "a ?? b"', () => {
|
|
530
|
+
const result = binOpParser("a ?? b");
|
|
531
|
+
expect(result.success).toBe(true);
|
|
532
|
+
if (result.success) {
|
|
533
|
+
expect(result.result).toEqualWithoutLoc({
|
|
534
|
+
type: "binOpExpression",
|
|
535
|
+
operator: "??",
|
|
536
|
+
left: { type: "variableName", value: "a" },
|
|
537
|
+
right: { type: "variableName", value: "b" },
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
it('should parse "a ?? b ?? c" as left-associative', () => {
|
|
542
|
+
const result = binOpParser("a ?? b ?? c");
|
|
543
|
+
expect(result.success).toBe(true);
|
|
544
|
+
if (result.success) {
|
|
545
|
+
expect(result.result).toEqualWithoutLoc({
|
|
546
|
+
type: "binOpExpression",
|
|
547
|
+
operator: "??",
|
|
548
|
+
left: {
|
|
549
|
+
type: "binOpExpression",
|
|
550
|
+
operator: "??",
|
|
551
|
+
left: { type: "variableName", value: "a" },
|
|
552
|
+
right: { type: "variableName", value: "b" },
|
|
553
|
+
},
|
|
554
|
+
right: { type: "variableName", value: "c" },
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
it('should parse "a ?? 42"', () => {
|
|
559
|
+
const result = binOpParser("a ?? 42");
|
|
560
|
+
expect(result.success).toBe(true);
|
|
561
|
+
if (result.success) {
|
|
562
|
+
expect(result.result).toEqualWithoutLoc({
|
|
563
|
+
type: "binOpExpression",
|
|
564
|
+
operator: "??",
|
|
565
|
+
left: { type: "variableName", value: "a" },
|
|
566
|
+
right: { type: "number", value: "42" },
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
it('should parse ?? with lower precedence than comparison', () => {
|
|
571
|
+
const result = binOpParser("a == b ?? c");
|
|
572
|
+
expect(result.success).toBe(true);
|
|
573
|
+
if (result.success) {
|
|
574
|
+
// Should parse as (a == b) ?? c
|
|
575
|
+
expect(result.result).toEqualWithoutLoc({
|
|
576
|
+
type: "binOpExpression",
|
|
577
|
+
operator: "??",
|
|
578
|
+
left: {
|
|
579
|
+
type: "binOpExpression",
|
|
580
|
+
operator: "==",
|
|
581
|
+
left: { type: "variableName", value: "a" },
|
|
582
|
+
right: { type: "variableName", value: "b" },
|
|
583
|
+
},
|
|
584
|
+
right: { type: "variableName", value: "c" },
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
});
|
|
527
589
|
// Failure cases
|
|
528
590
|
describe("regex match operators", () => {
|
|
529
591
|
const testCases = [
|
|
@@ -409,27 +409,35 @@ export const functionCallParser = label("a function call", _functionCallParser);
|
|
|
409
409
|
// =============================================================================
|
|
410
410
|
// access.ts
|
|
411
411
|
// =============================================================================
|
|
412
|
-
// Parse
|
|
412
|
+
// Parse "?." or "." — returns true for optional, false for regular
|
|
413
|
+
const dotParser = or(map(str("?."), () => true), map(char("."), () => false));
|
|
414
|
+
// Parse a single chain element: .method(), ?.method(), .property, ?.property, [index], ?.[index]
|
|
413
415
|
const dotMethodCallParser = (input) => {
|
|
414
|
-
|
|
415
|
-
const dotResult = char(".")(input);
|
|
416
|
+
const dotResult = dotParser(input);
|
|
416
417
|
if (!dotResult.success)
|
|
417
418
|
return failure("expected dot", input);
|
|
418
|
-
const
|
|
419
|
+
const optional = dotResult.result;
|
|
420
|
+
const afterDot = dotResult.rest;
|
|
421
|
+
// First try: functionCall (name + parens)
|
|
422
|
+
const fcResult = _functionCallParser(afterDot);
|
|
419
423
|
if (fcResult.success) {
|
|
420
|
-
return success({ kind: "methodCall", functionCall: fcResult.result }, fcResult.rest);
|
|
424
|
+
return success({ kind: "methodCall", functionCall: fcResult.result, ...(optional && { optional: true }) }, fcResult.rest);
|
|
421
425
|
}
|
|
422
|
-
// Second try:
|
|
423
|
-
const nameResult = variableNameParser(
|
|
426
|
+
// Second try: just a property name
|
|
427
|
+
const nameResult = variableNameParser(afterDot);
|
|
424
428
|
if (nameResult.success) {
|
|
425
|
-
return success({ kind: "property", name: nameResult.result.value }, nameResult.rest);
|
|
429
|
+
return success({ kind: "property", name: nameResult.result.value, ...(optional && { optional: true }) }, nameResult.rest);
|
|
426
430
|
}
|
|
427
431
|
return failure("expected property name or method call after dot", input);
|
|
428
432
|
};
|
|
433
|
+
// Parse "?.[" or "[" — returns true for optional, false for regular
|
|
434
|
+
const bracketParser = or(map(str("?.["), () => true), map(char("["), () => false));
|
|
429
435
|
const indexChainParser = (input) => {
|
|
430
|
-
const parser = seqC(
|
|
436
|
+
const parser = seqC(capture(bracketParser, "optional"), optionalSpaces, capture(lazy(() => exprParser), "index"), optionalSpaces, char("]"));
|
|
431
437
|
const result = parser(input);
|
|
432
|
-
|
|
438
|
+
if (!result.success)
|
|
439
|
+
return result;
|
|
440
|
+
return success({ kind: "index", index: result.result.index, ...(result.result.optional && { optional: true }) }, result.rest);
|
|
433
441
|
};
|
|
434
442
|
const chainElementParser = or(dotMethodCallParser, indexChainParser);
|
|
435
443
|
export const _valueAccessParser = (input) => {
|
|
@@ -607,8 +615,9 @@ export const exprParser = label("an expression", buildExpressionParser(atom, [
|
|
|
607
615
|
[
|
|
608
616
|
{ op: wsOp("&&"), assoc: "left", apply: makeBinOp("&&") },
|
|
609
617
|
],
|
|
610
|
-
// Precedence 1: logical OR
|
|
618
|
+
// Precedence 1: logical OR, nullish coalescing
|
|
611
619
|
[
|
|
620
|
+
{ op: wsOp("??"), assoc: "left", apply: makeBinOp("??") },
|
|
612
621
|
{ op: wsOp("||"), assoc: "left", apply: makeBinOp("||") },
|
|
613
622
|
],
|
|
614
623
|
// Precedence 0: catch (unwrap Result with fallback)
|
|
@@ -3,12 +3,15 @@ import { BaseNode } from "./base.js";
|
|
|
3
3
|
export type AccessChainElement = {
|
|
4
4
|
kind: "property";
|
|
5
5
|
name: string;
|
|
6
|
+
optional?: boolean;
|
|
6
7
|
} | {
|
|
7
8
|
kind: "index";
|
|
8
9
|
index: Expression;
|
|
10
|
+
optional?: boolean;
|
|
9
11
|
} | {
|
|
10
12
|
kind: "methodCall";
|
|
11
13
|
functionCall: FunctionCall;
|
|
14
|
+
optional?: boolean;
|
|
12
15
|
};
|
|
13
16
|
export type ValueAccess = BaseNode & {
|
|
14
17
|
type: "valueAccess";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BaseNode } from "./base.js";
|
|
2
2
|
import { Expression } from "../types.js";
|
|
3
3
|
export type BinOpArgument = Expression;
|
|
4
|
-
export type Operator = "+" | "-" | "*" | "/" | "%" | "==" | "===" | "!=" | "+=" | "-=" | "*=" | "/=" | "<" | ">" | "<=" | ">=" | "&&" | "||" | "!" | "=~" | "!~" | "|>" | "catch";
|
|
4
|
+
export type Operator = "+" | "-" | "*" | "/" | "%" | "==" | "===" | "!=" | "+=" | "-=" | "*=" | "/=" | "<" | ">" | "<=" | ">=" | "&&" | "||" | "!" | "=~" | "!~" | "??" | "|>" | "catch";
|
|
5
5
|
export declare const PRECEDENCE: Record<string, number>;
|
|
6
6
|
export type BinOpExpression = BaseNode & {
|
|
7
7
|
type: "binOpExpression";
|
package/dist/lib/types/binop.js
CHANGED
package/package.json
CHANGED
package/stdlib/array.js
CHANGED
|
@@ -33,6 +33,9 @@ const __globalCtx = new RuntimeContext({
|
|
|
33
33
|
dirname: __dirname
|
|
34
34
|
});
|
|
35
35
|
const graph = __globalCtx.graph;
|
|
36
|
+
import { TraceWriter } from "agency-lang/runtime";
|
|
37
|
+
const __traceWriter = new TraceWriter("/Users/adityabhargava/agency-lang/tests/debugger/trace-test.agencytrace", "stdlib/array.agency");
|
|
38
|
+
__globalCtx.traceWriter = __traceWriter;
|
|
36
39
|
// Path-dependent builtin wrappers
|
|
37
40
|
export function readSkill({ filepath }) {
|
|
38
41
|
return _readSkillRaw({ filepath, dirname: __dirname });
|
package/stdlib/index.js
CHANGED
|
@@ -32,6 +32,9 @@ const __globalCtx = new RuntimeContext({
|
|
|
32
32
|
dirname: __dirname
|
|
33
33
|
});
|
|
34
34
|
const graph = __globalCtx.graph;
|
|
35
|
+
import { TraceWriter } from "agency-lang/runtime";
|
|
36
|
+
const __traceWriter = new TraceWriter("/Users/adityabhargava/agency-lang/tests/debugger/trace-test.agencytrace", "stdlib/index.agency");
|
|
37
|
+
__globalCtx.traceWriter = __traceWriter;
|
|
35
38
|
// Path-dependent builtin wrappers
|
|
36
39
|
export function readSkill({ filepath }) {
|
|
37
40
|
return _readSkillRaw({ filepath, dirname: __dirname });
|