@elwood-lang/core 0.1.0

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.
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Variable scope for Elwood evaluation. Supports nested scopes (let bindings, lambdas).
3
+ */
4
+ export declare class Scope {
5
+ private parent?;
6
+ private vars;
7
+ constructor(parent?: Scope | undefined);
8
+ set(name: string, value: unknown): void;
9
+ get(name: string): unknown | undefined;
10
+ has(name: string): boolean;
11
+ child(): Scope;
12
+ }
13
+ //# sourceMappingURL=scope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../src/scope.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,KAAK;IAGJ,OAAO,CAAC,MAAM,CAAC;IAF3B,OAAO,CAAC,IAAI,CAA8B;gBAEtB,MAAM,CAAC,EAAE,KAAK,YAAA;IAElC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IAIvC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAKtC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAK1B,KAAK,IAAI,KAAK;CAGf"}
package/dist/scope.js ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Variable scope for Elwood evaluation. Supports nested scopes (let bindings, lambdas).
3
+ */
4
+ export class Scope {
5
+ parent;
6
+ vars = new Map();
7
+ constructor(parent) {
8
+ this.parent = parent;
9
+ }
10
+ set(name, value) {
11
+ this.vars.set(name, value);
12
+ }
13
+ get(name) {
14
+ if (this.vars.has(name))
15
+ return this.vars.get(name);
16
+ return this.parent?.get(name);
17
+ }
18
+ has(name) {
19
+ if (this.vars.has(name))
20
+ return true;
21
+ return this.parent?.has(name) ?? false;
22
+ }
23
+ child() {
24
+ return new Scope(this);
25
+ }
26
+ }
27
+ //# sourceMappingURL=scope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope.js","sourceRoot":"","sources":["../src/scope.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,OAAO,KAAK;IAGI;IAFZ,IAAI,GAAG,IAAI,GAAG,EAAmB,CAAC;IAE1C,YAAoB,MAAc;QAAd,WAAM,GAAN,MAAM,CAAQ;IAAG,CAAC;IAEtC,GAAG,CAAC,IAAY,EAAE,KAAc;QAC9B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC7B,CAAC;IAED,GAAG,CAAC,IAAY;QACd,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpD,OAAO,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC;IAED,GAAG,CAAC,IAAY;QACd,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QACrC,OAAO,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC;IACzC,CAAC;IAED,KAAK;QACH,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;CACF"}
@@ -0,0 +1,65 @@
1
+ export declare enum TokenKind {
2
+ StringLiteral = "StringLiteral",
3
+ NumberLiteral = "NumberLiteral",
4
+ TrueLiteral = "TrueLiteral",
5
+ FalseLiteral = "FalseLiteral",
6
+ NullLiteral = "NullLiteral",
7
+ Identifier = "Identifier",
8
+ Dollar = "Dollar",
9
+ DollarDot = "DollarDot",
10
+ Dot = "Dot",
11
+ DotDot = "DotDot",
12
+ LeftBracket = "LeftBracket",
13
+ RightBracket = "RightBracket",
14
+ LeftParen = "LeftParen",
15
+ RightParen = "RightParen",
16
+ LeftBrace = "LeftBrace",
17
+ RightBrace = "RightBrace",
18
+ Pipe = "Pipe",
19
+ FatArrow = "FatArrow",
20
+ Comma = "Comma",
21
+ Colon = "Colon",
22
+ Star = "Star",
23
+ Spread = "Spread",
24
+ Plus = "Plus",
25
+ Minus = "Minus",
26
+ Slash = "Slash",
27
+ Assign = "Assign",
28
+ EqualEqual = "EqualEqual",
29
+ BangEqual = "BangEqual",
30
+ LessThan = "LessThan",
31
+ LessThanOrEqual = "LessThanOrEqual",
32
+ GreaterThan = "GreaterThan",
33
+ GreaterThanOrEqual = "GreaterThanOrEqual",
34
+ AmpersandAmpersand = "AmpersandAmpersand",
35
+ PipePipe = "PipePipe",
36
+ Bang = "Bang",
37
+ Let = "Let",
38
+ If = "If",
39
+ Then = "Then",
40
+ Else = "Else",
41
+ Match = "Match",
42
+ Return = "Return",
43
+ From = "From",
44
+ Asc = "Asc",
45
+ Desc = "Desc",
46
+ On = "On",
47
+ Equals = "Equals",
48
+ Into = "Into",
49
+ Underscore = "Underscore",
50
+ Memo = "Memo",
51
+ Backtick = "Backtick",
52
+ Eof = "Eof"
53
+ }
54
+ export interface SourceSpan {
55
+ start: number;
56
+ end: number;
57
+ line: number;
58
+ column: number;
59
+ }
60
+ export interface Token {
61
+ kind: TokenKind;
62
+ text: string;
63
+ span: SourceSpan;
64
+ }
65
+ //# sourceMappingURL=token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../src/token.ts"],"names":[],"mappings":"AAAA,oBAAY,SAAS;IAEnB,aAAa,kBAAkB;IAC/B,aAAa,kBAAkB;IAC/B,WAAW,gBAAgB;IAC3B,YAAY,iBAAiB;IAC7B,WAAW,gBAAgB;IAG3B,UAAU,eAAe;IACzB,MAAM,WAAW;IACjB,SAAS,cAAc;IACvB,GAAG,QAAQ;IACX,MAAM,WAAW;IAGjB,WAAW,gBAAgB;IAC3B,YAAY,iBAAiB;IAC7B,SAAS,cAAc;IACvB,UAAU,eAAe;IACzB,SAAS,cAAc;IACvB,UAAU,eAAe;IAGzB,IAAI,SAAS;IACb,QAAQ,aAAa;IACrB,KAAK,UAAU;IACf,KAAK,UAAU;IACf,IAAI,SAAS;IACb,MAAM,WAAW;IAGjB,IAAI,SAAS;IACb,KAAK,UAAU;IACf,KAAK,UAAU;IAGf,MAAM,WAAW;IAGjB,UAAU,eAAe;IACzB,SAAS,cAAc;IACvB,QAAQ,aAAa;IACrB,eAAe,oBAAoB;IACnC,WAAW,gBAAgB;IAC3B,kBAAkB,uBAAuB;IAGzC,kBAAkB,uBAAuB;IACzC,QAAQ,aAAa;IACrB,IAAI,SAAS;IAGb,GAAG,QAAQ;IACX,EAAE,OAAO;IACT,IAAI,SAAS;IACb,IAAI,SAAS;IACb,KAAK,UAAU;IACf,MAAM,WAAW;IACjB,IAAI,SAAS;IACb,GAAG,QAAQ;IACX,IAAI,SAAS;IACb,EAAE,OAAO;IACT,MAAM,WAAW;IACjB,IAAI,SAAS;IACb,UAAU,eAAe;IACzB,IAAI,SAAS;IAGb,QAAQ,aAAa;IAGrB,GAAG,QAAQ;CACZ;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,CAAC;CAClB"}
package/dist/token.js ADDED
@@ -0,0 +1,66 @@
1
+ export var TokenKind;
2
+ (function (TokenKind) {
3
+ // Literals
4
+ TokenKind["StringLiteral"] = "StringLiteral";
5
+ TokenKind["NumberLiteral"] = "NumberLiteral";
6
+ TokenKind["TrueLiteral"] = "TrueLiteral";
7
+ TokenKind["FalseLiteral"] = "FalseLiteral";
8
+ TokenKind["NullLiteral"] = "NullLiteral";
9
+ // Identifiers & paths
10
+ TokenKind["Identifier"] = "Identifier";
11
+ TokenKind["Dollar"] = "Dollar";
12
+ TokenKind["DollarDot"] = "DollarDot";
13
+ TokenKind["Dot"] = "Dot";
14
+ TokenKind["DotDot"] = "DotDot";
15
+ // Brackets
16
+ TokenKind["LeftBracket"] = "LeftBracket";
17
+ TokenKind["RightBracket"] = "RightBracket";
18
+ TokenKind["LeftParen"] = "LeftParen";
19
+ TokenKind["RightParen"] = "RightParen";
20
+ TokenKind["LeftBrace"] = "LeftBrace";
21
+ TokenKind["RightBrace"] = "RightBrace";
22
+ // Operators
23
+ TokenKind["Pipe"] = "Pipe";
24
+ TokenKind["FatArrow"] = "FatArrow";
25
+ TokenKind["Comma"] = "Comma";
26
+ TokenKind["Colon"] = "Colon";
27
+ TokenKind["Star"] = "Star";
28
+ TokenKind["Spread"] = "Spread";
29
+ // Arithmetic
30
+ TokenKind["Plus"] = "Plus";
31
+ TokenKind["Minus"] = "Minus";
32
+ TokenKind["Slash"] = "Slash";
33
+ // Assignment
34
+ TokenKind["Assign"] = "Assign";
35
+ // Comparison
36
+ TokenKind["EqualEqual"] = "EqualEqual";
37
+ TokenKind["BangEqual"] = "BangEqual";
38
+ TokenKind["LessThan"] = "LessThan";
39
+ TokenKind["LessThanOrEqual"] = "LessThanOrEqual";
40
+ TokenKind["GreaterThan"] = "GreaterThan";
41
+ TokenKind["GreaterThanOrEqual"] = "GreaterThanOrEqual";
42
+ // Logical
43
+ TokenKind["AmpersandAmpersand"] = "AmpersandAmpersand";
44
+ TokenKind["PipePipe"] = "PipePipe";
45
+ TokenKind["Bang"] = "Bang";
46
+ // Keywords
47
+ TokenKind["Let"] = "Let";
48
+ TokenKind["If"] = "If";
49
+ TokenKind["Then"] = "Then";
50
+ TokenKind["Else"] = "Else";
51
+ TokenKind["Match"] = "Match";
52
+ TokenKind["Return"] = "Return";
53
+ TokenKind["From"] = "From";
54
+ TokenKind["Asc"] = "Asc";
55
+ TokenKind["Desc"] = "Desc";
56
+ TokenKind["On"] = "On";
57
+ TokenKind["Equals"] = "Equals";
58
+ TokenKind["Into"] = "Into";
59
+ TokenKind["Underscore"] = "Underscore";
60
+ TokenKind["Memo"] = "Memo";
61
+ // Interpolation
62
+ TokenKind["Backtick"] = "Backtick";
63
+ // Special
64
+ TokenKind["Eof"] = "Eof";
65
+ })(TokenKind || (TokenKind = {}));
66
+ //# sourceMappingURL=token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.js","sourceRoot":"","sources":["../src/token.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,SAyEX;AAzED,WAAY,SAAS;IACnB,WAAW;IACX,4CAA+B,CAAA;IAC/B,4CAA+B,CAAA;IAC/B,wCAA2B,CAAA;IAC3B,0CAA6B,CAAA;IAC7B,wCAA2B,CAAA;IAE3B,sBAAsB;IACtB,sCAAyB,CAAA;IACzB,8BAAiB,CAAA;IACjB,oCAAuB,CAAA;IACvB,wBAAW,CAAA;IACX,8BAAiB,CAAA;IAEjB,WAAW;IACX,wCAA2B,CAAA;IAC3B,0CAA6B,CAAA;IAC7B,oCAAuB,CAAA;IACvB,sCAAyB,CAAA;IACzB,oCAAuB,CAAA;IACvB,sCAAyB,CAAA;IAEzB,YAAY;IACZ,0BAAa,CAAA;IACb,kCAAqB,CAAA;IACrB,4BAAe,CAAA;IACf,4BAAe,CAAA;IACf,0BAAa,CAAA;IACb,8BAAiB,CAAA;IAEjB,aAAa;IACb,0BAAa,CAAA;IACb,4BAAe,CAAA;IACf,4BAAe,CAAA;IAEf,aAAa;IACb,8BAAiB,CAAA;IAEjB,aAAa;IACb,sCAAyB,CAAA;IACzB,oCAAuB,CAAA;IACvB,kCAAqB,CAAA;IACrB,gDAAmC,CAAA;IACnC,wCAA2B,CAAA;IAC3B,sDAAyC,CAAA;IAEzC,UAAU;IACV,sDAAyC,CAAA;IACzC,kCAAqB,CAAA;IACrB,0BAAa,CAAA;IAEb,WAAW;IACX,wBAAW,CAAA;IACX,sBAAS,CAAA;IACT,0BAAa,CAAA;IACb,0BAAa,CAAA;IACb,4BAAe,CAAA;IACf,8BAAiB,CAAA;IACjB,0BAAa,CAAA;IACb,wBAAW,CAAA;IACX,0BAAa,CAAA;IACb,sBAAS,CAAA;IACT,8BAAiB,CAAA;IACjB,0BAAa,CAAA;IACb,sCAAyB,CAAA;IACzB,0BAAa,CAAA;IAEb,gBAAgB;IAChB,kCAAqB,CAAA;IAErB,UAAU;IACV,wBAAW,CAAA;AACb,CAAC,EAzEW,SAAS,KAAT,SAAS,QAyEpB"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@elwood-lang/core",
3
+ "version": "0.1.0",
4
+ "description": "Elwood — a functional JSON transformation DSL",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs",
13
+ "types": "./dist/index.d.ts"
14
+ }
15
+ },
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "test": "vitest run",
19
+ "test:watch": "vitest"
20
+ },
21
+ "keywords": [
22
+ "json",
23
+ "jsonpath",
24
+ "dsl",
25
+ "transformation",
26
+ "query",
27
+ "functional"
28
+ ],
29
+ "author": "Massimiliano Favilli",
30
+ "license": "MIT",
31
+ "devDependencies": {
32
+ "@types/node": "^25.5.0",
33
+ "typescript": "^5.9.3",
34
+ "vitest": "^2.1.9"
35
+ }
36
+ }
package/src/ast.ts ADDED
@@ -0,0 +1,235 @@
1
+ import type { SourceSpan } from './token.js';
2
+
3
+ // ── Expressions ──
4
+
5
+ export type ElwoodExpression =
6
+ | PipelineExpression
7
+ | PathExpression
8
+ | IdentifierExpression
9
+ | LambdaExpression
10
+ | ObjectExpression
11
+ | ArrayExpression
12
+ | LiteralExpression
13
+ | InterpolatedStringExpression
14
+ | BinaryExpression
15
+ | UnaryExpression
16
+ | IfExpression
17
+ | MatchExpression
18
+ | MemoExpression
19
+ | MethodCallExpression
20
+ | FunctionCallExpression
21
+ | MemberAccessExpression
22
+ | IndexExpression;
23
+
24
+ // ── Top-level ──
25
+
26
+ export interface ScriptNode {
27
+ type: 'Script';
28
+ bindings: LetBindingNode[];
29
+ returnExpression: ElwoodExpression | null;
30
+ span: SourceSpan;
31
+ }
32
+
33
+ export interface LetBindingNode {
34
+ type: 'LetBinding';
35
+ name: string;
36
+ value: ElwoodExpression;
37
+ span: SourceSpan;
38
+ }
39
+
40
+ // ── Expression nodes ──
41
+
42
+ export interface PipelineExpression {
43
+ type: 'Pipeline';
44
+ source: ElwoodExpression;
45
+ operations: PipeOperation[];
46
+ span: SourceSpan;
47
+ }
48
+
49
+ export interface PathExpression {
50
+ type: 'Path';
51
+ segments: PathSegment[];
52
+ isRooted: boolean;
53
+ span: SourceSpan;
54
+ }
55
+
56
+ export interface IdentifierExpression {
57
+ type: 'Identifier';
58
+ name: string;
59
+ span: SourceSpan;
60
+ }
61
+
62
+ export interface LambdaExpression {
63
+ type: 'Lambda';
64
+ parameters: string[];
65
+ body: ElwoodExpression;
66
+ span: SourceSpan;
67
+ }
68
+
69
+ export interface ObjectExpression {
70
+ type: 'Object';
71
+ properties: ObjectProperty[];
72
+ span: SourceSpan;
73
+ }
74
+
75
+ export interface ObjectProperty {
76
+ key: string;
77
+ value: ElwoodExpression;
78
+ span: SourceSpan;
79
+ isSpread?: boolean;
80
+ computedKey?: ElwoodExpression;
81
+ }
82
+
83
+ export interface ArrayExpression {
84
+ type: 'Array';
85
+ items: ElwoodExpression[];
86
+ span: SourceSpan;
87
+ }
88
+
89
+ export interface LiteralExpression {
90
+ type: 'Literal';
91
+ value: string | number | boolean | null;
92
+ span: SourceSpan;
93
+ }
94
+
95
+ export interface InterpolatedStringExpression {
96
+ type: 'InterpolatedString';
97
+ parts: InterpolationPart[];
98
+ span: SourceSpan;
99
+ }
100
+
101
+ export type InterpolationPart =
102
+ | { type: 'Text'; text: string; span: SourceSpan }
103
+ | { type: 'Expression'; expression: ElwoodExpression; span: SourceSpan };
104
+
105
+ export interface BinaryExpression {
106
+ type: 'Binary';
107
+ left: ElwoodExpression;
108
+ operator: BinaryOperator;
109
+ right: ElwoodExpression;
110
+ span: SourceSpan;
111
+ }
112
+
113
+ export interface UnaryExpression {
114
+ type: 'Unary';
115
+ operator: UnaryOperator;
116
+ operand: ElwoodExpression;
117
+ span: SourceSpan;
118
+ }
119
+
120
+ export interface IfExpression {
121
+ type: 'If';
122
+ condition: ElwoodExpression;
123
+ thenBranch: ElwoodExpression;
124
+ elseBranch: ElwoodExpression;
125
+ span: SourceSpan;
126
+ }
127
+
128
+ export interface MatchExpression {
129
+ type: 'Match';
130
+ input: ElwoodExpression;
131
+ arms: MatchArm[];
132
+ span: SourceSpan;
133
+ }
134
+
135
+ export interface MatchArm {
136
+ pattern: ElwoodExpression | null; // null = wildcard
137
+ result: ElwoodExpression;
138
+ span: SourceSpan;
139
+ }
140
+
141
+ export interface MemoExpression {
142
+ type: 'Memo';
143
+ lambda: LambdaExpression;
144
+ span: SourceSpan;
145
+ }
146
+
147
+ export interface MethodCallExpression {
148
+ type: 'MethodCall';
149
+ target: ElwoodExpression;
150
+ methodName: string;
151
+ arguments: ElwoodExpression[];
152
+ span: SourceSpan;
153
+ }
154
+
155
+ export interface FunctionCallExpression {
156
+ type: 'FunctionCall';
157
+ functionName: string;
158
+ arguments: ElwoodExpression[];
159
+ span: SourceSpan;
160
+ }
161
+
162
+ export interface MemberAccessExpression {
163
+ type: 'MemberAccess';
164
+ target: ElwoodExpression;
165
+ memberName: string;
166
+ span: SourceSpan;
167
+ }
168
+
169
+ export interface IndexExpression {
170
+ type: 'Index';
171
+ target: ElwoodExpression;
172
+ index: ElwoodExpression | null; // null = [*]
173
+ span: SourceSpan;
174
+ }
175
+
176
+ // ── Pipe Operations ──
177
+
178
+ export type PipeOperation =
179
+ | WhereOperation
180
+ | SelectOperation
181
+ | SelectManyOperation
182
+ | OrderByOperation
183
+ | GroupByOperation
184
+ | DistinctOperation
185
+ | AggregateOperation
186
+ | SliceOperation
187
+ | BatchOperation
188
+ | JoinOperation
189
+ | ConcatOperation
190
+ | ReduceOperation
191
+ | QuantifierOperation
192
+ | MatchOperation;
193
+
194
+ export interface WhereOperation { type: 'Where'; predicate: ElwoodExpression; span: SourceSpan }
195
+ export interface SelectOperation { type: 'Select'; projection: ElwoodExpression; span: SourceSpan }
196
+ export interface SelectManyOperation { type: 'SelectMany'; projection: ElwoodExpression; span: SourceSpan }
197
+ export interface OrderByOperation { type: 'OrderBy'; keys: { key: ElwoodExpression; ascending: boolean }[]; span: SourceSpan }
198
+ export interface GroupByOperation { type: 'GroupBy'; keySelector: ElwoodExpression; span: SourceSpan }
199
+ export interface DistinctOperation { type: 'Distinct'; span: SourceSpan }
200
+ export interface AggregateOperation { type: 'Aggregate'; name: string; predicate?: ElwoodExpression; span: SourceSpan }
201
+ export interface SliceOperation { type: 'Slice'; kind: 'take' | 'skip'; count: ElwoodExpression; span: SourceSpan }
202
+ export interface BatchOperation { type: 'Batch'; size: ElwoodExpression; span: SourceSpan }
203
+ export interface JoinOperation {
204
+ type: 'Join';
205
+ source: ElwoodExpression;
206
+ leftKey: ElwoodExpression;
207
+ rightKey: ElwoodExpression;
208
+ intoAlias?: string;
209
+ mode: JoinMode;
210
+ span: SourceSpan;
211
+ }
212
+ export interface ConcatOperation { type: 'Concat'; separator?: ElwoodExpression; span: SourceSpan }
213
+ export interface ReduceOperation { type: 'Reduce'; accumulator: ElwoodExpression; initialValue?: ElwoodExpression; span: SourceSpan }
214
+ export interface QuantifierOperation { type: 'Quantifier'; kind: 'any' | 'all'; predicate: ElwoodExpression; span: SourceSpan }
215
+ export interface MatchOperation { type: 'MatchOp'; arms: MatchArm[]; span: SourceSpan }
216
+
217
+ export type JoinMode = 'inner' | 'left' | 'right' | 'full';
218
+
219
+ // ── Path Segments ──
220
+
221
+ export type PathSegment =
222
+ | { type: 'Property'; name: string; span: SourceSpan }
223
+ | { type: 'Index'; index: number | null; span: SourceSpan } // null = wildcard [*]
224
+ | { type: 'Slice'; start: number | null; end: number | null; span: SourceSpan }
225
+ | { type: 'RecursiveDescent'; name: string; span: SourceSpan };
226
+
227
+ // ── Operators ──
228
+
229
+ export type BinaryOperator =
230
+ | 'Add' | 'Subtract' | 'Multiply' | 'Divide'
231
+ | 'Equal' | 'NotEqual'
232
+ | 'LessThan' | 'LessThanOrEqual' | 'GreaterThan' | 'GreaterThanOrEqual'
233
+ | 'And' | 'Or';
234
+
235
+ export type UnaryOperator = 'Negate' | 'Not';