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.
Files changed (3) hide show
  1. package/dist/index.d.ts +62 -23
  2. package/dist/index.js +390 -134
  3. 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 functions for each AST node type.
743
- * Each handler receives:
744
- * - The node (correctly typed based on node.type)
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: (node: Program, recurse: (n: ASTNode) => T) => T;
751
- NumberLiteral: (node: NumberLiteral, recurse: (n: ASTNode) => T) => T;
752
- StringLiteral: (node: StringLiteral, recurse: (n: ASTNode) => T) => T;
753
- BooleanLiteral: (node: BooleanLiteral, recurse: (n: ASTNode) => T) => T;
754
- ArrayLiteral: (node: ArrayLiteral, recurse: (n: ASTNode) => T) => T;
755
- Identifier2: (node: Identifier2, recurse: (n: ASTNode) => T) => T;
756
- BinaryOp: (node: BinaryOp, recurse: (n: ASTNode) => T) => T;
757
- UnaryOp: (node: UnaryOp, recurse: (n: ASTNode) => T) => T;
758
- FunctionCall: (node: FunctionCall, recurse: (n: ASTNode) => T) => T;
759
- Assignment: (node: Assignment, recurse: (n: ASTNode) => T) => T;
760
- IfExpression: (node: IfExpression, recurse: (n: ASTNode) => T) => T;
761
- ForExpression: (node: ForExpression, recurse: (n: ASTNode) => T) => T;
762
- IndexAccess: (node: IndexAccess, recurse: (n: ASTNode) => T) => T;
763
- RangeExpression: (node: RangeExpression, recurse: (n: ASTNode) => T) => T;
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: (node: ASTNode, recurse: (n: ASTNode) => T) => T): T;
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: (newValue) => all[name] = () => newValue
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", 26 /* If */],
261
- ["then", 27 /* Then */],
262
- ["else", 28 /* Else */],
263
- ["for", 29 /* For */],
264
- ["in", 30 /* In */],
265
- ["when", 31 /* When */],
266
- ["into", 32 /* 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 [33 /* Eof */, cursor.pos, cursor.pos];
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 leftNeedsParens = needsParens(n.left, n.operator, true) || n.operator === "^" && isUnaryOp(n.left);
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] !== 33 /* Eof */) {
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 === 26 /* If */) {
1401
+ if (tokenKind === 28 /* If */) {
1309
1402
  advance2(state);
1310
1403
  const condition = parseExpression(state, 0);
1311
- if (peekKind(state) !== 27 /* Then */) {
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) !== 28 /* Else */) {
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 === 29 /* For */) {
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) !== 30 /* In */) {
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) === 31 /* When */) {
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) === 32 /* Into */) {
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) !== 27 /* Then */) {
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
- if (knownValues.has(stmt.name) || reassigned.has(stmt.name)) {
1706
- knownValues.delete(stmt.name);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "littlewing",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "A minimal, high-performance multi-type expression language with lexer, parser, and interpreter. Optimized for browsers with type-safe execution.",
5
5
  "keywords": [
6
6
  "calculator",