littlewing 2.0.0 → 2.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.
- package/dist/index.d.ts +62 -23
- package/dist/index.js +390 -134
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
declare namespace exports_ast {
|
|
2
|
-
export { unaryOp, subtract, string, rangeExpr, program, number, notEquals, negate, multiply, modulo, logicalOr, logicalNot, logicalAnd, lessThan, lessEqual, isUnaryOp, isStringLiteral, isRangeExpression, isProgram, isNumberLiteral, isIndexAccess, isIfExpression, isIdentifier, isFunctionCall, isForExpression, isBooleanLiteral, isBinaryOp, isAssignment, isArrayLiteral, indexAccess, ifExpr, identifier, greaterThan, greaterEqual, getNodeName, functionCall, forExpr, exponentiate, equals, divide, boolean, binaryOp, assign, array, add, UnaryOp, StringLiteral, RangeExpression, Program, Operator, NumberLiteral, NodeKind, IndexAccess, IfExpression, Identifier2 as Identifier, FunctionCall, ForExpression, BooleanLiteral, BinaryOp, Assignment, ArrayLiteral, ASTNodeBase, ASTNode };
|
|
2
|
+
export { unaryOp, subtract, string, rangeExpr, program, placeholder, pipeExpr, number, notEquals, negate, multiply, modulo, logicalOr, logicalNot, logicalAnd, lessThan, lessEqual, isUnaryOp, isStringLiteral, isRangeExpression, isProgram, isPlaceholder, isPipeExpression, isNumberLiteral, isIndexAccess, isIfExpression, isIdentifier, isFunctionCall, isForExpression, isBooleanLiteral, isBinaryOp, isAssignment, isArrayLiteral, indexAccess, ifExpr, identifier, greaterThan, greaterEqual, getNodeName, functionCall, forExpr, exponentiate, equals, divide, boolean, binaryOp, assign, array, add, UnaryOp, StringLiteral, RangeExpression, Program, Placeholder, PipeExpression, Operator, NumberLiteral, NodeKind, IndexAccess, IfExpression, Identifier2 as Identifier, FunctionCall, ForExpression, BooleanLiteral, BinaryOp, Assignment, ArrayLiteral, ASTNodeBase, ASTNode };
|
|
3
3
|
}
|
|
4
4
|
/**
|
|
5
5
|
* Binary operator types
|
|
@@ -30,7 +30,9 @@ declare const enum NodeKind {
|
|
|
30
30
|
ArrayLiteral = 10,
|
|
31
31
|
ForExpression = 11,
|
|
32
32
|
IndexAccess = 12,
|
|
33
|
-
RangeExpression = 13
|
|
33
|
+
RangeExpression = 13,
|
|
34
|
+
PipeExpression = 14,
|
|
35
|
+
Placeholder = 15
|
|
34
36
|
}
|
|
35
37
|
/**
|
|
36
38
|
* Program node (multiple statements)
|
|
@@ -151,9 +153,25 @@ interface RangeExpression extends ASTNodeBase {
|
|
|
151
153
|
readonly inclusive: boolean;
|
|
152
154
|
}
|
|
153
155
|
/**
|
|
156
|
+
* Pipe expression (value |> FUN(?, arg))
|
|
157
|
+
* Chains a value through a function call where ? marks the insertion point
|
|
158
|
+
*/
|
|
159
|
+
interface PipeExpression extends ASTNodeBase {
|
|
160
|
+
readonly kind: NodeKind.PipeExpression;
|
|
161
|
+
readonly value: ASTNode;
|
|
162
|
+
readonly name: string;
|
|
163
|
+
readonly args: readonly ASTNode[];
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Placeholder (?) used inside pipe expression arguments
|
|
167
|
+
*/
|
|
168
|
+
interface Placeholder extends ASTNodeBase {
|
|
169
|
+
readonly kind: NodeKind.Placeholder;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
154
172
|
* AST Node - discriminated union of all node types
|
|
155
173
|
*/
|
|
156
|
-
type ASTNode = Program | NumberLiteral | StringLiteral | BooleanLiteral | ArrayLiteral | Identifier2 | BinaryOp | UnaryOp | FunctionCall | Assignment | IfExpression | ForExpression | IndexAccess | RangeExpression;
|
|
174
|
+
type ASTNode = Program | NumberLiteral | StringLiteral | BooleanLiteral | ArrayLiteral | Identifier2 | BinaryOp | UnaryOp | FunctionCall | Assignment | IfExpression | ForExpression | IndexAccess | RangeExpression | PipeExpression | Placeholder;
|
|
157
175
|
/**
|
|
158
176
|
* Type guard functions for discriminated union narrowing
|
|
159
177
|
*/
|
|
@@ -171,6 +189,8 @@ declare function isIfExpression(node: ASTNode): node is IfExpression;
|
|
|
171
189
|
declare function isForExpression(node: ASTNode): node is ForExpression;
|
|
172
190
|
declare function isIndexAccess(node: ASTNode): node is IndexAccess;
|
|
173
191
|
declare function isRangeExpression(node: ASTNode): node is RangeExpression;
|
|
192
|
+
declare function isPipeExpression(node: ASTNode): node is PipeExpression;
|
|
193
|
+
declare function isPlaceholder(node: ASTNode): node is Placeholder;
|
|
174
194
|
/**
|
|
175
195
|
* Builder functions for creating AST nodes manually
|
|
176
196
|
*/
|
|
@@ -234,6 +254,14 @@ declare function indexAccess(object: ASTNode, index: ASTNode): IndexAccess;
|
|
|
234
254
|
*/
|
|
235
255
|
declare function rangeExpr(start: ASTNode, end: ASTNode, inclusive: boolean): RangeExpression;
|
|
236
256
|
/**
|
|
257
|
+
* Create a pipe expression node (value |> FUN(?, arg))
|
|
258
|
+
*/
|
|
259
|
+
declare function pipeExpr(value: ASTNode, name: string, args: readonly ASTNode[]): PipeExpression;
|
|
260
|
+
/**
|
|
261
|
+
* Create a placeholder node (?) for use in pipe expression arguments
|
|
262
|
+
*/
|
|
263
|
+
declare function placeholder(): Placeholder;
|
|
264
|
+
/**
|
|
237
265
|
* Convenience functions for common operations
|
|
238
266
|
*/
|
|
239
267
|
declare function add(left: ASTNode, right: ASTNode): BinaryOp;
|
|
@@ -737,30 +765,41 @@ declare function assertTimeOrDateTime(v: RuntimeValue, context: string): asserts
|
|
|
737
765
|
*/
|
|
738
766
|
declare function assertArray(v: RuntimeValue, context: string): asserts v is readonly RuntimeValue[];
|
|
739
767
|
/**
|
|
768
|
+
* A single visitor handler: receives a narrowed node and a recurse function.
|
|
769
|
+
*
|
|
770
|
+
* @template N The specific AST node type this handler accepts
|
|
771
|
+
* @template T The return type shared across all handlers
|
|
772
|
+
*/
|
|
773
|
+
type VisitorHandler<
|
|
774
|
+
N,
|
|
775
|
+
T
|
|
776
|
+
> = (node: N, recurse: (n: ASTNode) => T) => T;
|
|
777
|
+
/**
|
|
740
778
|
* Type-safe visitor pattern for AST traversal.
|
|
741
779
|
*
|
|
742
|
-
* A visitor is an object with handler
|
|
743
|
-
* Each handler receives
|
|
744
|
-
*
|
|
745
|
-
* - A recurse function to visit child nodes with the same visitor
|
|
780
|
+
* A visitor is an object with one handler per AST node type.
|
|
781
|
+
* Each handler receives the narrowed node and a `recurse` function
|
|
782
|
+
* for visiting child nodes with the same visitor.
|
|
746
783
|
*
|
|
747
784
|
* @template T The return type of visitor handlers
|
|
748
785
|
*/
|
|
749
786
|
type Visitor<T> = {
|
|
750
|
-
Program:
|
|
751
|
-
NumberLiteral:
|
|
752
|
-
StringLiteral:
|
|
753
|
-
BooleanLiteral:
|
|
754
|
-
ArrayLiteral:
|
|
755
|
-
Identifier2:
|
|
756
|
-
BinaryOp:
|
|
757
|
-
UnaryOp:
|
|
758
|
-
FunctionCall:
|
|
759
|
-
Assignment:
|
|
760
|
-
IfExpression:
|
|
761
|
-
ForExpression:
|
|
762
|
-
IndexAccess:
|
|
763
|
-
RangeExpression:
|
|
787
|
+
Program: VisitorHandler<Program, T>;
|
|
788
|
+
NumberLiteral: VisitorHandler<NumberLiteral, T>;
|
|
789
|
+
StringLiteral: VisitorHandler<StringLiteral, T>;
|
|
790
|
+
BooleanLiteral: VisitorHandler<BooleanLiteral, T>;
|
|
791
|
+
ArrayLiteral: VisitorHandler<ArrayLiteral, T>;
|
|
792
|
+
Identifier2: VisitorHandler<Identifier2, T>;
|
|
793
|
+
BinaryOp: VisitorHandler<BinaryOp, T>;
|
|
794
|
+
UnaryOp: VisitorHandler<UnaryOp, T>;
|
|
795
|
+
FunctionCall: VisitorHandler<FunctionCall, T>;
|
|
796
|
+
Assignment: VisitorHandler<Assignment, T>;
|
|
797
|
+
IfExpression: VisitorHandler<IfExpression, T>;
|
|
798
|
+
ForExpression: VisitorHandler<ForExpression, T>;
|
|
799
|
+
IndexAccess: VisitorHandler<IndexAccess, T>;
|
|
800
|
+
RangeExpression: VisitorHandler<RangeExpression, T>;
|
|
801
|
+
PipeExpression: VisitorHandler<PipeExpression, T>;
|
|
802
|
+
Placeholder: VisitorHandler<Placeholder, T>;
|
|
764
803
|
};
|
|
765
804
|
/**
|
|
766
805
|
* Visit an AST node using a visitor object with type-specific handlers.
|
|
@@ -785,5 +824,5 @@ declare function visit<T>(node: ASTNode, visitor: Visitor<T>): T;
|
|
|
785
824
|
* @param defaultHandler Handler for unhandled node types
|
|
786
825
|
* @returns The result of visiting the node
|
|
787
826
|
*/
|
|
788
|
-
declare function visitPartial<T>(node: ASTNode, visitor: Partial<Visitor<T>>, defaultHandler:
|
|
789
|
-
export { visitPartial, visit, typeOf, time, string, parse, optimize, math, isUnaryOp, isStringLiteral, isRangeExpression, isProgram, isNumberLiteral, isIndexAccess, isIfExpression, isIdentifier, isFunctionCall, isForExpression, isBooleanLiteral, isBinaryOp, isAssignment, isArrayLiteral, generate, extractInputVariables, extractAssignedVariables, evaluateScope, evaluate, defaultContext, datetimefull, datetime, core, exports_ast as ast, assertTimeOrDateTime, assertTime, assertString, assertNumber, assertDateTime, assertDateOrDateTime, assertDate, assertBoolean, assertArray, array, Visitor, UnaryOp, StringLiteral, RuntimeValue, RangeExpression, Program, Operator, NumberLiteral, NodeKind, IndexAccess, IfExpression, Identifier2 as Identifier, FunctionCall, ForExpression, ExecutionContext, BooleanLiteral, BinaryOp, Assignment, ArrayLiteral, ASTNodeBase, ASTNode };
|
|
827
|
+
declare function visitPartial<T>(node: ASTNode, visitor: Partial<Visitor<T>>, defaultHandler: VisitorHandler<ASTNode, T>): T;
|
|
828
|
+
export { visitPartial, visit, typeOf, time, string, parse, optimize, math, isUnaryOp, isStringLiteral, isRangeExpression, isProgram, isPlaceholder, isPipeExpression, isNumberLiteral, isIndexAccess, isIfExpression, isIdentifier, isFunctionCall, isForExpression, isBooleanLiteral, isBinaryOp, isAssignment, isArrayLiteral, generate, extractInputVariables, extractAssignedVariables, evaluateScope, evaluate, defaultContext, datetimefull, datetime, core, exports_ast as ast, assertTimeOrDateTime, assertTime, assertString, assertNumber, assertDateTime, assertDateOrDateTime, assertDate, assertBoolean, assertArray, array, VisitorHandler, Visitor, UnaryOp, StringLiteral, RuntimeValue, RangeExpression, Program, Placeholder, PipeExpression, Operator, NumberLiteral, NodeKind, IndexAccess, IfExpression, Identifier2 as Identifier, FunctionCall, ForExpression, ExecutionContext, BooleanLiteral, BinaryOp, Assignment, ArrayLiteral, ASTNodeBase, ASTNode };
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
|
+
var __returnValue = (v) => v;
|
|
3
|
+
function __exportSetter(name, newValue) {
|
|
4
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
5
|
+
}
|
|
2
6
|
var __export = (target, all) => {
|
|
3
7
|
for (var name in all)
|
|
4
8
|
__defProp(target, name, {
|
|
5
9
|
get: all[name],
|
|
6
10
|
enumerable: true,
|
|
7
11
|
configurable: true,
|
|
8
|
-
set: (
|
|
12
|
+
set: __exportSetter.bind(all, name)
|
|
9
13
|
});
|
|
10
14
|
};
|
|
11
15
|
|
|
@@ -17,6 +21,8 @@ __export(exports_ast, {
|
|
|
17
21
|
string: () => string,
|
|
18
22
|
rangeExpr: () => rangeExpr,
|
|
19
23
|
program: () => program,
|
|
24
|
+
placeholder: () => placeholder,
|
|
25
|
+
pipeExpr: () => pipeExpr,
|
|
20
26
|
number: () => number,
|
|
21
27
|
notEquals: () => notEquals,
|
|
22
28
|
negate: () => negate,
|
|
@@ -31,6 +37,8 @@ __export(exports_ast, {
|
|
|
31
37
|
isStringLiteral: () => isStringLiteral,
|
|
32
38
|
isRangeExpression: () => isRangeExpression,
|
|
33
39
|
isProgram: () => isProgram,
|
|
40
|
+
isPlaceholder: () => isPlaceholder,
|
|
41
|
+
isPipeExpression: () => isPipeExpression,
|
|
34
42
|
isNumberLiteral: () => isNumberLiteral,
|
|
35
43
|
isIndexAccess: () => isIndexAccess,
|
|
36
44
|
isIfExpression: () => isIfExpression,
|
|
@@ -75,6 +83,8 @@ var NodeKind;
|
|
|
75
83
|
NodeKind2[NodeKind2["ForExpression"] = 11] = "ForExpression";
|
|
76
84
|
NodeKind2[NodeKind2["IndexAccess"] = 12] = "IndexAccess";
|
|
77
85
|
NodeKind2[NodeKind2["RangeExpression"] = 13] = "RangeExpression";
|
|
86
|
+
NodeKind2[NodeKind2["PipeExpression"] = 14] = "PipeExpression";
|
|
87
|
+
NodeKind2[NodeKind2["Placeholder"] = 15] = "Placeholder";
|
|
78
88
|
})(NodeKind ||= {});
|
|
79
89
|
function isProgram(node) {
|
|
80
90
|
return node.kind === 0 /* Program */;
|
|
@@ -118,6 +128,12 @@ function isIndexAccess(node) {
|
|
|
118
128
|
function isRangeExpression(node) {
|
|
119
129
|
return node.kind === 13 /* RangeExpression */;
|
|
120
130
|
}
|
|
131
|
+
function isPipeExpression(node) {
|
|
132
|
+
return node.kind === 14 /* PipeExpression */;
|
|
133
|
+
}
|
|
134
|
+
function isPlaceholder(node) {
|
|
135
|
+
return node.kind === 15 /* Placeholder */;
|
|
136
|
+
}
|
|
121
137
|
function program(statements) {
|
|
122
138
|
return { kind: 0 /* Program */, statements };
|
|
123
139
|
}
|
|
@@ -172,6 +188,12 @@ function indexAccess(object, index) {
|
|
|
172
188
|
function rangeExpr(start, end, inclusive) {
|
|
173
189
|
return { kind: 13 /* RangeExpression */, start, end, inclusive };
|
|
174
190
|
}
|
|
191
|
+
function pipeExpr(value, name, args) {
|
|
192
|
+
return { kind: 14 /* PipeExpression */, value, name, args };
|
|
193
|
+
}
|
|
194
|
+
function placeholder() {
|
|
195
|
+
return { kind: 15 /* Placeholder */ };
|
|
196
|
+
}
|
|
175
197
|
function add(left, right) {
|
|
176
198
|
return binaryOp(left, "+", right);
|
|
177
199
|
}
|
|
@@ -250,20 +272,156 @@ function getNodeName(node) {
|
|
|
250
272
|
return "IndexAccess";
|
|
251
273
|
case 13 /* RangeExpression */:
|
|
252
274
|
return "RangeExpression";
|
|
275
|
+
case 14 /* PipeExpression */:
|
|
276
|
+
return "PipeExpression";
|
|
277
|
+
case 15 /* Placeholder */:
|
|
278
|
+
return "Placeholder";
|
|
253
279
|
default:
|
|
254
280
|
throw new Error(`Unknown node kind: ${node.kind}`);
|
|
255
281
|
}
|
|
256
282
|
}
|
|
257
283
|
|
|
284
|
+
// src/visitor.ts
|
|
285
|
+
function visit(node, visitor) {
|
|
286
|
+
return visitPartial(node, visitor, (node2) => {
|
|
287
|
+
throw new Error(`No handler for node type: ${getNodeName(node2)}`);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
function visitPartial(node, visitor, defaultHandler) {
|
|
291
|
+
const recurse = (n) => visitPartial(n, visitor, defaultHandler);
|
|
292
|
+
switch (node.kind) {
|
|
293
|
+
case 0 /* Program */:
|
|
294
|
+
return visitor.Program ? visitor.Program(node, recurse) : defaultHandler(node, recurse);
|
|
295
|
+
case 1 /* NumberLiteral */:
|
|
296
|
+
return visitor.NumberLiteral ? visitor.NumberLiteral(node, recurse) : defaultHandler(node, recurse);
|
|
297
|
+
case 8 /* StringLiteral */:
|
|
298
|
+
return visitor.StringLiteral ? visitor.StringLiteral(node, recurse) : defaultHandler(node, recurse);
|
|
299
|
+
case 9 /* BooleanLiteral */:
|
|
300
|
+
return visitor.BooleanLiteral ? visitor.BooleanLiteral(node, recurse) : defaultHandler(node, recurse);
|
|
301
|
+
case 10 /* ArrayLiteral */:
|
|
302
|
+
return visitor.ArrayLiteral ? visitor.ArrayLiteral(node, recurse) : defaultHandler(node, recurse);
|
|
303
|
+
case 2 /* Identifier */:
|
|
304
|
+
return visitor.Identifier ? visitor.Identifier(node, recurse) : defaultHandler(node, recurse);
|
|
305
|
+
case 3 /* BinaryOp */:
|
|
306
|
+
return visitor.BinaryOp ? visitor.BinaryOp(node, recurse) : defaultHandler(node, recurse);
|
|
307
|
+
case 4 /* UnaryOp */:
|
|
308
|
+
return visitor.UnaryOp ? visitor.UnaryOp(node, recurse) : defaultHandler(node, recurse);
|
|
309
|
+
case 5 /* FunctionCall */:
|
|
310
|
+
return visitor.FunctionCall ? visitor.FunctionCall(node, recurse) : defaultHandler(node, recurse);
|
|
311
|
+
case 6 /* Assignment */:
|
|
312
|
+
return visitor.Assignment ? visitor.Assignment(node, recurse) : defaultHandler(node, recurse);
|
|
313
|
+
case 7 /* IfExpression */:
|
|
314
|
+
return visitor.IfExpression ? visitor.IfExpression(node, recurse) : defaultHandler(node, recurse);
|
|
315
|
+
case 11 /* ForExpression */:
|
|
316
|
+
return visitor.ForExpression ? visitor.ForExpression(node, recurse) : defaultHandler(node, recurse);
|
|
317
|
+
case 12 /* IndexAccess */:
|
|
318
|
+
return visitor.IndexAccess ? visitor.IndexAccess(node, recurse) : defaultHandler(node, recurse);
|
|
319
|
+
case 13 /* RangeExpression */:
|
|
320
|
+
return visitor.RangeExpression ? visitor.RangeExpression(node, recurse) : defaultHandler(node, recurse);
|
|
321
|
+
case 14 /* PipeExpression */:
|
|
322
|
+
return visitor.PipeExpression ? visitor.PipeExpression(node, recurse) : defaultHandler(node, recurse);
|
|
323
|
+
case 15 /* Placeholder */:
|
|
324
|
+
return visitor.Placeholder ? visitor.Placeholder(node, recurse) : defaultHandler(node, recurse);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// src/analyzer.ts
|
|
329
|
+
function extractInputVariables(ast) {
|
|
330
|
+
const inputVars = new Set;
|
|
331
|
+
const statements = isProgram(ast) ? ast.statements : [ast];
|
|
332
|
+
for (const statement of statements) {
|
|
333
|
+
if (isAssignment(statement)) {
|
|
334
|
+
if (!containsVariableReference(statement.value)) {
|
|
335
|
+
inputVars.add(statement.name);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return Array.from(inputVars);
|
|
340
|
+
}
|
|
341
|
+
function extractAssignedVariables(ast) {
|
|
342
|
+
const seen = new Set;
|
|
343
|
+
const names = [];
|
|
344
|
+
visitPartial(ast, {
|
|
345
|
+
Program: (n, recurse) => {
|
|
346
|
+
for (const statement of n.statements) {
|
|
347
|
+
recurse(statement);
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
Assignment: (n, recurse) => {
|
|
351
|
+
if (!seen.has(n.name)) {
|
|
352
|
+
seen.add(n.name);
|
|
353
|
+
names.push(n.name);
|
|
354
|
+
}
|
|
355
|
+
recurse(n.value);
|
|
356
|
+
},
|
|
357
|
+
IfExpression: (n, recurse) => {
|
|
358
|
+
recurse(n.condition);
|
|
359
|
+
recurse(n.consequent);
|
|
360
|
+
recurse(n.alternate);
|
|
361
|
+
},
|
|
362
|
+
ForExpression: (n, recurse) => {
|
|
363
|
+
recurse(n.iterable);
|
|
364
|
+
if (n.guard)
|
|
365
|
+
recurse(n.guard);
|
|
366
|
+
if (n.accumulator)
|
|
367
|
+
recurse(n.accumulator.initial);
|
|
368
|
+
recurse(n.body);
|
|
369
|
+
},
|
|
370
|
+
IndexAccess: (n, recurse) => {
|
|
371
|
+
recurse(n.object);
|
|
372
|
+
recurse(n.index);
|
|
373
|
+
},
|
|
374
|
+
RangeExpression: (n, recurse) => {
|
|
375
|
+
recurse(n.start);
|
|
376
|
+
recurse(n.end);
|
|
377
|
+
},
|
|
378
|
+
PipeExpression: (n, recurse) => {
|
|
379
|
+
recurse(n.value);
|
|
380
|
+
for (const arg of n.args) {
|
|
381
|
+
recurse(arg);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}, () => {});
|
|
385
|
+
return names;
|
|
386
|
+
}
|
|
387
|
+
function containsExternalReference(node, boundVars) {
|
|
388
|
+
return visit(node, {
|
|
389
|
+
Program: (n, recurse) => n.statements.some(recurse),
|
|
390
|
+
NumberLiteral: () => false,
|
|
391
|
+
StringLiteral: () => false,
|
|
392
|
+
BooleanLiteral: () => false,
|
|
393
|
+
Identifier: (n) => !boundVars.has(n.name),
|
|
394
|
+
ArrayLiteral: (n, recurse) => n.elements.some(recurse),
|
|
395
|
+
BinaryOp: (n, recurse) => recurse(n.left) || recurse(n.right),
|
|
396
|
+
UnaryOp: (n, recurse) => recurse(n.argument),
|
|
397
|
+
FunctionCall: (n, recurse) => n.args.some(recurse),
|
|
398
|
+
Assignment: (n, recurse) => recurse(n.value),
|
|
399
|
+
IfExpression: (n, recurse) => recurse(n.condition) || recurse(n.consequent) || recurse(n.alternate),
|
|
400
|
+
ForExpression: (n, recurse) => {
|
|
401
|
+
const innerBound = new Set(boundVars);
|
|
402
|
+
innerBound.add(n.variable);
|
|
403
|
+
if (n.accumulator)
|
|
404
|
+
innerBound.add(n.accumulator.name);
|
|
405
|
+
return recurse(n.iterable) || (n.guard ? containsExternalReference(n.guard, innerBound) : false) || (n.accumulator ? recurse(n.accumulator.initial) : false) || containsExternalReference(n.body, innerBound);
|
|
406
|
+
},
|
|
407
|
+
IndexAccess: (n, recurse) => recurse(n.object) || recurse(n.index),
|
|
408
|
+
RangeExpression: (n, recurse) => recurse(n.start) || recurse(n.end),
|
|
409
|
+
PipeExpression: (n, recurse) => recurse(n.value) || n.args.some(recurse),
|
|
410
|
+
Placeholder: () => false
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
function containsVariableReference(node) {
|
|
414
|
+
return containsExternalReference(node, new Set);
|
|
415
|
+
}
|
|
258
416
|
// src/lexer.ts
|
|
259
417
|
var KEYWORDS = new Map([
|
|
260
|
-
["if",
|
|
261
|
-
["then",
|
|
262
|
-
["else",
|
|
263
|
-
["for",
|
|
264
|
-
["in",
|
|
265
|
-
["when",
|
|
266
|
-
["into",
|
|
418
|
+
["if", 28 /* If */],
|
|
419
|
+
["then", 29 /* Then */],
|
|
420
|
+
["else", 30 /* Else */],
|
|
421
|
+
["for", 31 /* For */],
|
|
422
|
+
["in", 32 /* In */],
|
|
423
|
+
["when", 33 /* When */],
|
|
424
|
+
["into", 34 /* Into */]
|
|
267
425
|
]);
|
|
268
426
|
function createCursor(source) {
|
|
269
427
|
return {
|
|
@@ -355,7 +513,7 @@ function nextToken(cursor) {
|
|
|
355
513
|
}
|
|
356
514
|
}
|
|
357
515
|
if (cursor.pos >= cursor.len) {
|
|
358
|
-
return [
|
|
516
|
+
return [35 /* Eof */, cursor.pos, cursor.pos];
|
|
359
517
|
}
|
|
360
518
|
const start = cursor.pos;
|
|
361
519
|
const ch = peek(cursor);
|
|
@@ -456,7 +614,14 @@ function nextToken(cursor) {
|
|
|
456
614
|
advance(cursor);
|
|
457
615
|
return [17 /* Or */, start, cursor.pos];
|
|
458
616
|
}
|
|
617
|
+
if (peek(cursor) === 62) {
|
|
618
|
+
advance(cursor);
|
|
619
|
+
return [26 /* Pipe */, start, cursor.pos];
|
|
620
|
+
}
|
|
459
621
|
throw new Error(`Unexpected character '${String.fromCharCode(ch)}' at position ${start}`);
|
|
622
|
+
case 63:
|
|
623
|
+
advance(cursor);
|
|
624
|
+
return [27 /* Question */, start, cursor.pos];
|
|
460
625
|
default:
|
|
461
626
|
throw new Error(`Unexpected character '${String.fromCharCode(ch)}' at position ${start}`);
|
|
462
627
|
}
|
|
@@ -514,46 +679,6 @@ function lexIdentifier(cursor) {
|
|
|
514
679
|
return [1 /* Identifier */, start, pos];
|
|
515
680
|
}
|
|
516
681
|
|
|
517
|
-
// src/visitor.ts
|
|
518
|
-
function visit(node, visitor) {
|
|
519
|
-
return visitPartial(node, visitor, (node2) => {
|
|
520
|
-
throw new Error(`No handler for node type: ${getNodeName(node2)}`);
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
function visitPartial(node, visitor, defaultHandler) {
|
|
524
|
-
const recurse = (n) => visitPartial(n, visitor, defaultHandler);
|
|
525
|
-
switch (node.kind) {
|
|
526
|
-
case 0 /* Program */:
|
|
527
|
-
return visitor.Program ? visitor.Program(node, recurse) : defaultHandler(node, recurse);
|
|
528
|
-
case 1 /* NumberLiteral */:
|
|
529
|
-
return visitor.NumberLiteral ? visitor.NumberLiteral(node, recurse) : defaultHandler(node, recurse);
|
|
530
|
-
case 8 /* StringLiteral */:
|
|
531
|
-
return visitor.StringLiteral ? visitor.StringLiteral(node, recurse) : defaultHandler(node, recurse);
|
|
532
|
-
case 9 /* BooleanLiteral */:
|
|
533
|
-
return visitor.BooleanLiteral ? visitor.BooleanLiteral(node, recurse) : defaultHandler(node, recurse);
|
|
534
|
-
case 10 /* ArrayLiteral */:
|
|
535
|
-
return visitor.ArrayLiteral ? visitor.ArrayLiteral(node, recurse) : defaultHandler(node, recurse);
|
|
536
|
-
case 2 /* Identifier */:
|
|
537
|
-
return visitor.Identifier ? visitor.Identifier(node, recurse) : defaultHandler(node, recurse);
|
|
538
|
-
case 3 /* BinaryOp */:
|
|
539
|
-
return visitor.BinaryOp ? visitor.BinaryOp(node, recurse) : defaultHandler(node, recurse);
|
|
540
|
-
case 4 /* UnaryOp */:
|
|
541
|
-
return visitor.UnaryOp ? visitor.UnaryOp(node, recurse) : defaultHandler(node, recurse);
|
|
542
|
-
case 5 /* FunctionCall */:
|
|
543
|
-
return visitor.FunctionCall ? visitor.FunctionCall(node, recurse) : defaultHandler(node, recurse);
|
|
544
|
-
case 6 /* Assignment */:
|
|
545
|
-
return visitor.Assignment ? visitor.Assignment(node, recurse) : defaultHandler(node, recurse);
|
|
546
|
-
case 7 /* IfExpression */:
|
|
547
|
-
return visitor.IfExpression ? visitor.IfExpression(node, recurse) : defaultHandler(node, recurse);
|
|
548
|
-
case 11 /* ForExpression */:
|
|
549
|
-
return visitor.ForExpression ? visitor.ForExpression(node, recurse) : defaultHandler(node, recurse);
|
|
550
|
-
case 12 /* IndexAccess */:
|
|
551
|
-
return visitor.IndexAccess ? visitor.IndexAccess(node, recurse) : defaultHandler(node, recurse);
|
|
552
|
-
case 13 /* RangeExpression */:
|
|
553
|
-
return visitor.RangeExpression ? visitor.RangeExpression(node, recurse) : defaultHandler(node, recurse);
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
|
|
557
682
|
// src/utils.ts
|
|
558
683
|
function typeOf(value) {
|
|
559
684
|
if (typeof value === "number")
|
|
@@ -767,14 +892,20 @@ function resolveIndex(target, index) {
|
|
|
767
892
|
if (!Number.isInteger(index)) {
|
|
768
893
|
throw new TypeError(`Index must be an integer, got ${index}`);
|
|
769
894
|
}
|
|
895
|
+
if (typeof target === "string") {
|
|
896
|
+
const codePoints = Array.from(target);
|
|
897
|
+
const len2 = codePoints.length;
|
|
898
|
+
const resolved2 = index < 0 ? len2 + index : index;
|
|
899
|
+
if (resolved2 < 0 || resolved2 >= len2) {
|
|
900
|
+
throw new RangeError(`Index ${index} out of bounds for length ${len2}`);
|
|
901
|
+
}
|
|
902
|
+
return codePoints[resolved2];
|
|
903
|
+
}
|
|
770
904
|
const len = target.length;
|
|
771
905
|
const resolved = index < 0 ? len + index : index;
|
|
772
906
|
if (resolved < 0 || resolved >= len) {
|
|
773
907
|
throw new RangeError(`Index ${index} out of bounds for length ${len}`);
|
|
774
908
|
}
|
|
775
|
-
if (typeof target === "string") {
|
|
776
|
-
return target[resolved];
|
|
777
|
-
}
|
|
778
909
|
return target[resolved];
|
|
779
910
|
}
|
|
780
911
|
function buildRange(start, end, inclusive) {
|
|
@@ -874,7 +1005,14 @@ function collectAllIdentifiers(node) {
|
|
|
874
1005
|
RangeExpression: (n, recurse) => {
|
|
875
1006
|
recurse(n.start);
|
|
876
1007
|
recurse(n.end);
|
|
877
|
-
}
|
|
1008
|
+
},
|
|
1009
|
+
PipeExpression: (n, recurse) => {
|
|
1010
|
+
recurse(n.value);
|
|
1011
|
+
for (const arg of n.args) {
|
|
1012
|
+
recurse(arg);
|
|
1013
|
+
}
|
|
1014
|
+
},
|
|
1015
|
+
Placeholder: () => {}
|
|
878
1016
|
});
|
|
879
1017
|
return identifiers;
|
|
880
1018
|
}
|
|
@@ -882,6 +1020,8 @@ function getTokenPrecedence(kind) {
|
|
|
882
1020
|
switch (kind) {
|
|
883
1021
|
case 24 /* Eq */:
|
|
884
1022
|
return 1;
|
|
1023
|
+
case 26 /* Pipe */:
|
|
1024
|
+
return 2;
|
|
885
1025
|
case 17 /* Or */:
|
|
886
1026
|
return 3;
|
|
887
1027
|
case 16 /* And */:
|
|
@@ -910,62 +1050,6 @@ function getTokenPrecedence(kind) {
|
|
|
910
1050
|
}
|
|
911
1051
|
}
|
|
912
1052
|
|
|
913
|
-
// src/analyzer.ts
|
|
914
|
-
function extractInputVariables(ast) {
|
|
915
|
-
const inputVars = new Set;
|
|
916
|
-
const statements = isProgram(ast) ? ast.statements : [ast];
|
|
917
|
-
for (const statement of statements) {
|
|
918
|
-
if (isAssignment(statement)) {
|
|
919
|
-
if (!containsVariableReference(statement.value)) {
|
|
920
|
-
inputVars.add(statement.name);
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
return Array.from(inputVars);
|
|
925
|
-
}
|
|
926
|
-
function extractAssignedVariables(ast) {
|
|
927
|
-
const seen = new Set;
|
|
928
|
-
const names = [];
|
|
929
|
-
visitPartial(ast, {
|
|
930
|
-
Program: (n, recurse) => {
|
|
931
|
-
for (const statement of n.statements) {
|
|
932
|
-
recurse(statement);
|
|
933
|
-
}
|
|
934
|
-
},
|
|
935
|
-
Assignment: (n, recurse) => {
|
|
936
|
-
if (!seen.has(n.name)) {
|
|
937
|
-
seen.add(n.name);
|
|
938
|
-
names.push(n.name);
|
|
939
|
-
}
|
|
940
|
-
recurse(n.value);
|
|
941
|
-
},
|
|
942
|
-
IfExpression: (n, recurse) => {
|
|
943
|
-
recurse(n.condition);
|
|
944
|
-
recurse(n.consequent);
|
|
945
|
-
recurse(n.alternate);
|
|
946
|
-
},
|
|
947
|
-
ForExpression: (n, recurse) => {
|
|
948
|
-
recurse(n.iterable);
|
|
949
|
-
if (n.guard)
|
|
950
|
-
recurse(n.guard);
|
|
951
|
-
if (n.accumulator)
|
|
952
|
-
recurse(n.accumulator.initial);
|
|
953
|
-
recurse(n.body);
|
|
954
|
-
},
|
|
955
|
-
IndexAccess: (n, recurse) => {
|
|
956
|
-
recurse(n.object);
|
|
957
|
-
recurse(n.index);
|
|
958
|
-
},
|
|
959
|
-
RangeExpression: (n, recurse) => {
|
|
960
|
-
recurse(n.start);
|
|
961
|
-
recurse(n.end);
|
|
962
|
-
}
|
|
963
|
-
}, () => {});
|
|
964
|
-
return names;
|
|
965
|
-
}
|
|
966
|
-
function containsVariableReference(node) {
|
|
967
|
-
return collectAllIdentifiers(node).size > 0;
|
|
968
|
-
}
|
|
969
1053
|
// src/codegen.ts
|
|
970
1054
|
function needsParens(node, operator, isLeft) {
|
|
971
1055
|
if (!isBinaryOp(node))
|
|
@@ -1049,15 +1133,16 @@ function generateNode(node) {
|
|
|
1049
1133
|
BinaryOp: (n, recurse) => {
|
|
1050
1134
|
const left = recurse(n.left);
|
|
1051
1135
|
const right = recurse(n.right);
|
|
1052
|
-
const
|
|
1136
|
+
const opPrec = getOperatorPrecedence(n.operator);
|
|
1137
|
+
const leftNeedsParens = needsParens(n.left, n.operator, true) || n.operator === "^" && isUnaryOp(n.left) || isIfExpression(n.left) || isForExpression(n.left) || isAssignment(n.left) || isRangeExpression(n.left) && opPrec >= 7;
|
|
1053
1138
|
const leftCode = leftNeedsParens ? `(${left})` : left;
|
|
1054
|
-
const rightNeedsParens = needsParens(n.right, n.operator, false);
|
|
1139
|
+
const rightNeedsParens = needsParens(n.right, n.operator, false) || isPipeExpression(n.right) || isAssignment(n.right) || isRangeExpression(n.right) && opPrec >= 6;
|
|
1055
1140
|
const rightCode = rightNeedsParens ? `(${right})` : right;
|
|
1056
1141
|
return `${leftCode} ${n.operator} ${rightCode}`;
|
|
1057
1142
|
},
|
|
1058
1143
|
UnaryOp: (n, recurse) => {
|
|
1059
1144
|
const arg = recurse(n.argument);
|
|
1060
|
-
const parensNeeded = isBinaryOp(n.argument) || isAssignment(n.argument);
|
|
1145
|
+
const parensNeeded = isBinaryOp(n.argument) || isAssignment(n.argument) || isPipeExpression(n.argument) || isRangeExpression(n.argument);
|
|
1061
1146
|
const argCode = parensNeeded ? `(${arg})` : arg;
|
|
1062
1147
|
return `${n.operator}${argCode}`;
|
|
1063
1148
|
},
|
|
@@ -1089,7 +1174,7 @@ function generateNode(node) {
|
|
|
1089
1174
|
IndexAccess: (n, recurse) => {
|
|
1090
1175
|
const object = recurse(n.object);
|
|
1091
1176
|
const index = recurse(n.index);
|
|
1092
|
-
const needsParens2 = isBinaryOp(n.object) || isUnaryOp(n.object) || isAssignment(n.object) || isRangeExpression(n.object);
|
|
1177
|
+
const needsParens2 = isBinaryOp(n.object) || isUnaryOp(n.object) || isAssignment(n.object) || isRangeExpression(n.object) || isPipeExpression(n.object);
|
|
1093
1178
|
const objectCode = needsParens2 ? `(${object})` : object;
|
|
1094
1179
|
return `${objectCode}[${index}]`;
|
|
1095
1180
|
},
|
|
@@ -1097,12 +1182,20 @@ function generateNode(node) {
|
|
|
1097
1182
|
const start = recurse(n.start);
|
|
1098
1183
|
const end = recurse(n.end);
|
|
1099
1184
|
const op = n.inclusive ? "..=" : "..";
|
|
1100
|
-
const startNeedsParens = isBinaryOp(n.start) || isRangeExpression(n.start);
|
|
1101
|
-
const endNeedsParens = isBinaryOp(n.end) || isRangeExpression(n.end);
|
|
1185
|
+
const startNeedsParens = isBinaryOp(n.start) || isRangeExpression(n.start) || isIfExpression(n.start) || isForExpression(n.start) || isAssignment(n.start);
|
|
1186
|
+
const endNeedsParens = isBinaryOp(n.end) || isRangeExpression(n.end) || isPipeExpression(n.end) || isAssignment(n.end);
|
|
1102
1187
|
const startCode = startNeedsParens ? `(${start})` : start;
|
|
1103
1188
|
const endCode = endNeedsParens ? `(${end})` : end;
|
|
1104
1189
|
return `${startCode}${op}${endCode}`;
|
|
1105
|
-
}
|
|
1190
|
+
},
|
|
1191
|
+
PipeExpression: (n, recurse) => {
|
|
1192
|
+
const value = recurse(n.value);
|
|
1193
|
+
const argsCode = n.args.map(recurse).join(", ");
|
|
1194
|
+
const valueNeedsParens = isAssignment(n.value) || isIfExpression(n.value) || isForExpression(n.value);
|
|
1195
|
+
const valueCode = valueNeedsParens ? `(${value})` : value;
|
|
1196
|
+
return `${valueCode} |> ${n.name}(${argsCode})`;
|
|
1197
|
+
},
|
|
1198
|
+
Placeholder: () => "?"
|
|
1106
1199
|
});
|
|
1107
1200
|
}
|
|
1108
1201
|
// src/parser.ts
|
|
@@ -1132,7 +1225,7 @@ function parse(source) {
|
|
|
1132
1225
|
};
|
|
1133
1226
|
const statements = [];
|
|
1134
1227
|
const statementOffsets = [];
|
|
1135
|
-
while (state.currentToken[0] !==
|
|
1228
|
+
while (state.currentToken[0] !== 35 /* Eof */) {
|
|
1136
1229
|
statementOffsets.push(state.currentToken[1]);
|
|
1137
1230
|
statements.push(parseExpression(state, 0));
|
|
1138
1231
|
}
|
|
@@ -1305,40 +1398,40 @@ function parsePrefix(state) {
|
|
|
1305
1398
|
advance2(state);
|
|
1306
1399
|
return array(elements);
|
|
1307
1400
|
}
|
|
1308
|
-
if (tokenKind ===
|
|
1401
|
+
if (tokenKind === 28 /* If */) {
|
|
1309
1402
|
advance2(state);
|
|
1310
1403
|
const condition = parseExpression(state, 0);
|
|
1311
|
-
if (peekKind(state) !==
|
|
1404
|
+
if (peekKind(state) !== 29 /* Then */) {
|
|
1312
1405
|
throw new Error('Expected "then" in if expression');
|
|
1313
1406
|
}
|
|
1314
1407
|
advance2(state);
|
|
1315
1408
|
const consequent = parseExpression(state, 0);
|
|
1316
|
-
if (peekKind(state) !==
|
|
1409
|
+
if (peekKind(state) !== 30 /* Else */) {
|
|
1317
1410
|
throw new Error('Expected "else" in if expression');
|
|
1318
1411
|
}
|
|
1319
1412
|
advance2(state);
|
|
1320
1413
|
const alternate = parseExpression(state, 0);
|
|
1321
1414
|
return ifExpr(condition, consequent, alternate);
|
|
1322
1415
|
}
|
|
1323
|
-
if (tokenKind ===
|
|
1416
|
+
if (tokenKind === 31 /* For */) {
|
|
1324
1417
|
advance2(state);
|
|
1325
1418
|
if (peekKind(state) !== 1 /* Identifier */) {
|
|
1326
1419
|
throw new Error('Expected identifier after "for"');
|
|
1327
1420
|
}
|
|
1328
1421
|
const variableName = readText(state.cursor, state.currentToken);
|
|
1329
1422
|
advance2(state);
|
|
1330
|
-
if (peekKind(state) !==
|
|
1423
|
+
if (peekKind(state) !== 32 /* In */) {
|
|
1331
1424
|
throw new Error('Expected "in" in for expression');
|
|
1332
1425
|
}
|
|
1333
1426
|
advance2(state);
|
|
1334
1427
|
const iterable = parseExpression(state, 0);
|
|
1335
1428
|
let guard = null;
|
|
1336
|
-
if (peekKind(state) ===
|
|
1429
|
+
if (peekKind(state) === 33 /* When */) {
|
|
1337
1430
|
advance2(state);
|
|
1338
1431
|
guard = parseExpression(state, 0);
|
|
1339
1432
|
}
|
|
1340
1433
|
let accumulator = null;
|
|
1341
|
-
if (peekKind(state) ===
|
|
1434
|
+
if (peekKind(state) === 34 /* Into */) {
|
|
1342
1435
|
advance2(state);
|
|
1343
1436
|
if (peekKind(state) !== 1 /* Identifier */) {
|
|
1344
1437
|
throw new Error('Expected identifier after "into"');
|
|
@@ -1352,7 +1445,7 @@ function parsePrefix(state) {
|
|
|
1352
1445
|
const initial = parseExpression(state, 0);
|
|
1353
1446
|
accumulator = { name: accName, initial };
|
|
1354
1447
|
}
|
|
1355
|
-
if (peekKind(state) !==
|
|
1448
|
+
if (peekKind(state) !== 29 /* Then */) {
|
|
1356
1449
|
throw new Error('Expected "then" in for expression');
|
|
1357
1450
|
}
|
|
1358
1451
|
advance2(state);
|
|
@@ -1413,6 +1506,34 @@ function parseInfix(state, left, precedence) {
|
|
|
1413
1506
|
const right = parseExpression(state, precedence + 1);
|
|
1414
1507
|
return rangeExpr(left, right, inclusive);
|
|
1415
1508
|
}
|
|
1509
|
+
if (tokenKind === 26 /* Pipe */) {
|
|
1510
|
+
advance2(state);
|
|
1511
|
+
if (peekKind(state) !== 1 /* Identifier */) {
|
|
1512
|
+
throw new Error("Expected function name after |>");
|
|
1513
|
+
}
|
|
1514
|
+
const name = readText(state.cursor, state.currentToken);
|
|
1515
|
+
advance2(state);
|
|
1516
|
+
if (peekKind(state) !== 20 /* LParen */) {
|
|
1517
|
+
throw new Error("Expected ( after function name in pipe expression");
|
|
1518
|
+
}
|
|
1519
|
+
advance2(state);
|
|
1520
|
+
const args = parsePipeArguments(state);
|
|
1521
|
+
if (peekKind(state) !== 21 /* RParen */) {
|
|
1522
|
+
throw new Error("Expected closing parenthesis");
|
|
1523
|
+
}
|
|
1524
|
+
advance2(state);
|
|
1525
|
+
let hasPlaceholder = false;
|
|
1526
|
+
for (const arg of args) {
|
|
1527
|
+
if (arg.kind === 15 /* Placeholder */) {
|
|
1528
|
+
hasPlaceholder = true;
|
|
1529
|
+
break;
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
if (!hasPlaceholder) {
|
|
1533
|
+
throw new Error("Pipe expression requires at least one ? placeholder");
|
|
1534
|
+
}
|
|
1535
|
+
return pipeExpr(left, name, args);
|
|
1536
|
+
}
|
|
1416
1537
|
if (isBinaryOperator(tokenKind)) {
|
|
1417
1538
|
const operator = readText(state.cursor, state.currentToken);
|
|
1418
1539
|
advance2(state);
|
|
@@ -1434,6 +1555,25 @@ function parseFunctionArguments(state) {
|
|
|
1434
1555
|
}
|
|
1435
1556
|
return args;
|
|
1436
1557
|
}
|
|
1558
|
+
function parsePipeArguments(state) {
|
|
1559
|
+
if (peekKind(state) === 21 /* RParen */) {
|
|
1560
|
+
return [];
|
|
1561
|
+
}
|
|
1562
|
+
const args = [];
|
|
1563
|
+
args.push(parsePipeArg(state));
|
|
1564
|
+
while (peekKind(state) === 25 /* Comma */) {
|
|
1565
|
+
advance2(state);
|
|
1566
|
+
args.push(parsePipeArg(state));
|
|
1567
|
+
}
|
|
1568
|
+
return args;
|
|
1569
|
+
}
|
|
1570
|
+
function parsePipeArg(state) {
|
|
1571
|
+
if (peekKind(state) === 27 /* Question */) {
|
|
1572
|
+
advance2(state);
|
|
1573
|
+
return placeholder();
|
|
1574
|
+
}
|
|
1575
|
+
return parseExpression(state, 0);
|
|
1576
|
+
}
|
|
1437
1577
|
var UNARY_PRECEDENCE = 8;
|
|
1438
1578
|
function isBinaryOperator(kind) {
|
|
1439
1579
|
return BINARY_OPERATOR_TOKENS.has(kind);
|
|
@@ -1550,6 +1690,21 @@ function evaluateNode(node, context, variables, externalVariables) {
|
|
|
1550
1690
|
assertNumber(end, "Range end");
|
|
1551
1691
|
return buildRange(start, end, n.inclusive);
|
|
1552
1692
|
},
|
|
1693
|
+
PipeExpression: (n, recurse) => {
|
|
1694
|
+
const pipedValue = recurse(n.value);
|
|
1695
|
+
const fn = context.functions?.[n.name];
|
|
1696
|
+
if (fn === undefined) {
|
|
1697
|
+
throw new Error(`Undefined function: ${n.name}`);
|
|
1698
|
+
}
|
|
1699
|
+
if (typeof fn !== "function") {
|
|
1700
|
+
throw new Error(`${n.name} is not a function`);
|
|
1701
|
+
}
|
|
1702
|
+
const evaluatedArgs = n.args.map((arg) => arg.kind === 15 /* Placeholder */ ? pipedValue : recurse(arg));
|
|
1703
|
+
return fn(...evaluatedArgs);
|
|
1704
|
+
},
|
|
1705
|
+
Placeholder: () => {
|
|
1706
|
+
throw new Error("Placeholder outside pipe expression");
|
|
1707
|
+
},
|
|
1553
1708
|
ForExpression: (n, recurse) => {
|
|
1554
1709
|
const iterable = recurse(n.iterable);
|
|
1555
1710
|
let items;
|
|
@@ -1653,7 +1808,7 @@ function eliminateDeadCode(program2) {
|
|
|
1653
1808
|
continue;
|
|
1654
1809
|
}
|
|
1655
1810
|
if (isAssignment(stmt)) {
|
|
1656
|
-
if (liveVars.has(stmt.name)) {
|
|
1811
|
+
if (liveVars.has(stmt.name) || mightHaveSideEffects(stmt.value)) {
|
|
1657
1812
|
keptIndices.push(i);
|
|
1658
1813
|
const identifiers = collectAllIdentifiers(stmt.value);
|
|
1659
1814
|
for (const id of identifiers) {
|
|
@@ -1687,14 +1842,37 @@ function eliminateDeadCode(program2) {
|
|
|
1687
1842
|
function isLiteral(node) {
|
|
1688
1843
|
return isNumberLiteral(node) || isStringLiteral(node) || isBooleanLiteral(node);
|
|
1689
1844
|
}
|
|
1845
|
+
function mightHaveSideEffects(node) {
|
|
1846
|
+
return visit(node, {
|
|
1847
|
+
Program: (n, recurse) => n.statements.some(recurse),
|
|
1848
|
+
NumberLiteral: () => false,
|
|
1849
|
+
StringLiteral: () => false,
|
|
1850
|
+
BooleanLiteral: () => false,
|
|
1851
|
+
Identifier: () => false,
|
|
1852
|
+
ArrayLiteral: (n, recurse) => n.elements.some(recurse),
|
|
1853
|
+
BinaryOp: (n, recurse) => recurse(n.left) || recurse(n.right),
|
|
1854
|
+
UnaryOp: (n, recurse) => recurse(n.argument),
|
|
1855
|
+
FunctionCall: () => true,
|
|
1856
|
+
PipeExpression: () => true,
|
|
1857
|
+
Placeholder: () => false,
|
|
1858
|
+
Assignment: () => true,
|
|
1859
|
+
IfExpression: (n, recurse) => recurse(n.condition) || recurse(n.consequent) || recurse(n.alternate),
|
|
1860
|
+
ForExpression: (n, recurse) => recurse(n.iterable) || (n.guard ? recurse(n.guard) : false) || (n.accumulator ? recurse(n.accumulator.initial) : false) || recurse(n.body),
|
|
1861
|
+
IndexAccess: (n, recurse) => recurse(n.object) || recurse(n.index),
|
|
1862
|
+
RangeExpression: (n, recurse) => recurse(n.start) || recurse(n.end)
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1690
1865
|
function propagateConstants(program2, externalVariables) {
|
|
1691
1866
|
const statements = program2.statements;
|
|
1692
1867
|
const forLoopVars = new Set;
|
|
1693
1868
|
for (const stmt of statements) {
|
|
1694
1869
|
collectForLoopVars(stmt, forLoopVars);
|
|
1695
1870
|
}
|
|
1871
|
+
const allAssignmentCounts = new Map;
|
|
1872
|
+
for (const stmt of statements) {
|
|
1873
|
+
countAssignments(stmt, allAssignmentCounts);
|
|
1874
|
+
}
|
|
1696
1875
|
const knownValues = new Map;
|
|
1697
|
-
const reassigned = new Set;
|
|
1698
1876
|
for (const stmt of statements) {
|
|
1699
1877
|
if (!isAssignment(stmt))
|
|
1700
1878
|
continue;
|
|
@@ -1702,11 +1880,9 @@ function propagateConstants(program2, externalVariables) {
|
|
|
1702
1880
|
continue;
|
|
1703
1881
|
if (forLoopVars.has(stmt.name))
|
|
1704
1882
|
continue;
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
reassigned.add(stmt.name);
|
|
1883
|
+
const totalCount = allAssignmentCounts.get(stmt.name) ?? 0;
|
|
1884
|
+
if (totalCount > 1)
|
|
1708
1885
|
continue;
|
|
1709
|
-
}
|
|
1710
1886
|
if (isLiteral(stmt.value)) {
|
|
1711
1887
|
knownValues.set(stmt.name, stmt.value);
|
|
1712
1888
|
}
|
|
@@ -1786,7 +1962,13 @@ function collectReferencedIdentifiers(node, ids) {
|
|
|
1786
1962
|
RangeExpression: (n, recurse) => {
|
|
1787
1963
|
recurse(n.start);
|
|
1788
1964
|
recurse(n.end);
|
|
1789
|
-
}
|
|
1965
|
+
},
|
|
1966
|
+
PipeExpression: (n, recurse) => {
|
|
1967
|
+
recurse(n.value);
|
|
1968
|
+
for (const a of n.args)
|
|
1969
|
+
recurse(a);
|
|
1970
|
+
},
|
|
1971
|
+
Placeholder: () => {}
|
|
1790
1972
|
});
|
|
1791
1973
|
}
|
|
1792
1974
|
function collectForLoopVars(node, vars) {
|
|
@@ -1840,7 +2022,71 @@ function collectForLoopVars(node, vars) {
|
|
|
1840
2022
|
RangeExpression: (n, recurse) => {
|
|
1841
2023
|
recurse(n.start);
|
|
1842
2024
|
recurse(n.end);
|
|
1843
|
-
}
|
|
2025
|
+
},
|
|
2026
|
+
PipeExpression: (n, recurse) => {
|
|
2027
|
+
recurse(n.value);
|
|
2028
|
+
for (const a of n.args)
|
|
2029
|
+
recurse(a);
|
|
2030
|
+
},
|
|
2031
|
+
Placeholder: () => {}
|
|
2032
|
+
});
|
|
2033
|
+
}
|
|
2034
|
+
function countAssignments(node, counts) {
|
|
2035
|
+
visit(node, {
|
|
2036
|
+
Program: (n, recurse) => {
|
|
2037
|
+
for (const s of n.statements)
|
|
2038
|
+
recurse(s);
|
|
2039
|
+
},
|
|
2040
|
+
NumberLiteral: () => {},
|
|
2041
|
+
StringLiteral: () => {},
|
|
2042
|
+
BooleanLiteral: () => {},
|
|
2043
|
+
ArrayLiteral: (n, recurse) => {
|
|
2044
|
+
for (const e of n.elements)
|
|
2045
|
+
recurse(e);
|
|
2046
|
+
},
|
|
2047
|
+
Identifier: () => {},
|
|
2048
|
+
BinaryOp: (n, recurse) => {
|
|
2049
|
+
recurse(n.left);
|
|
2050
|
+
recurse(n.right);
|
|
2051
|
+
},
|
|
2052
|
+
UnaryOp: (n, recurse) => {
|
|
2053
|
+
recurse(n.argument);
|
|
2054
|
+
},
|
|
2055
|
+
FunctionCall: (n, recurse) => {
|
|
2056
|
+
for (const a of n.args)
|
|
2057
|
+
recurse(a);
|
|
2058
|
+
},
|
|
2059
|
+
Assignment: (n, recurse) => {
|
|
2060
|
+
counts.set(n.name, (counts.get(n.name) ?? 0) + 1);
|
|
2061
|
+
recurse(n.value);
|
|
2062
|
+
},
|
|
2063
|
+
IfExpression: (n, recurse) => {
|
|
2064
|
+
recurse(n.condition);
|
|
2065
|
+
recurse(n.consequent);
|
|
2066
|
+
recurse(n.alternate);
|
|
2067
|
+
},
|
|
2068
|
+
ForExpression: (n, recurse) => {
|
|
2069
|
+
recurse(n.iterable);
|
|
2070
|
+
if (n.guard)
|
|
2071
|
+
recurse(n.guard);
|
|
2072
|
+
if (n.accumulator)
|
|
2073
|
+
recurse(n.accumulator.initial);
|
|
2074
|
+
recurse(n.body);
|
|
2075
|
+
},
|
|
2076
|
+
IndexAccess: (n, recurse) => {
|
|
2077
|
+
recurse(n.object);
|
|
2078
|
+
recurse(n.index);
|
|
2079
|
+
},
|
|
2080
|
+
RangeExpression: (n, recurse) => {
|
|
2081
|
+
recurse(n.start);
|
|
2082
|
+
recurse(n.end);
|
|
2083
|
+
},
|
|
2084
|
+
PipeExpression: (n, recurse) => {
|
|
2085
|
+
recurse(n.value);
|
|
2086
|
+
for (const a of n.args)
|
|
2087
|
+
recurse(a);
|
|
2088
|
+
},
|
|
2089
|
+
Placeholder: () => {}
|
|
1844
2090
|
});
|
|
1845
2091
|
}
|
|
1846
2092
|
function substituteIdentifiers(node, knownValues) {
|
|
@@ -1877,7 +2123,9 @@ function substituteIdentifiers(node, knownValues) {
|
|
|
1877
2123
|
return preserveComments(n, forExpr(n.variable, iterable, guard, accumulator, body));
|
|
1878
2124
|
},
|
|
1879
2125
|
IndexAccess: (n, recurse) => preserveComments(n, indexAccess(recurse(n.object), recurse(n.index))),
|
|
1880
|
-
RangeExpression: (n, recurse) => preserveComments(n, rangeExpr(recurse(n.start), recurse(n.end), n.inclusive))
|
|
2126
|
+
RangeExpression: (n, recurse) => preserveComments(n, rangeExpr(recurse(n.start), recurse(n.end), n.inclusive)),
|
|
2127
|
+
PipeExpression: (n, recurse) => preserveComments(n, pipeExpr(recurse(n.value), n.name, n.args.map(recurse))),
|
|
2128
|
+
Placeholder: (n) => n
|
|
1881
2129
|
});
|
|
1882
2130
|
}
|
|
1883
2131
|
function optimize(node, externalVariables) {
|
|
@@ -2026,6 +2274,12 @@ function fold(node) {
|
|
|
2026
2274
|
}
|
|
2027
2275
|
return preserveComments(n, rangeExpr(start, end, n.inclusive));
|
|
2028
2276
|
},
|
|
2277
|
+
PipeExpression: (n, recurse) => {
|
|
2278
|
+
const value = recurse(n.value);
|
|
2279
|
+
const optimizedArgs = n.args.map(recurse);
|
|
2280
|
+
return preserveComments(n, pipeExpr(value, n.name, optimizedArgs));
|
|
2281
|
+
},
|
|
2282
|
+
Placeholder: (n) => n,
|
|
2029
2283
|
Program: (n, recurse) => {
|
|
2030
2284
|
const optimizedStatements = n.statements.map(recurse);
|
|
2031
2285
|
const result = program(optimizedStatements);
|
|
@@ -2774,6 +3028,8 @@ export {
|
|
|
2774
3028
|
isStringLiteral,
|
|
2775
3029
|
isRangeExpression,
|
|
2776
3030
|
isProgram,
|
|
3031
|
+
isPlaceholder,
|
|
3032
|
+
isPipeExpression,
|
|
2777
3033
|
isNumberLiteral,
|
|
2778
3034
|
isIndexAccess,
|
|
2779
3035
|
isIfExpression,
|
package/package.json
CHANGED