@barnum/barnum 0.0.0-main-ef57b8fd → 0.0.0-main-39525152
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/artifacts/linux-arm64/barnum +0 -0
- package/artifacts/linux-x64/barnum +0 -0
- package/artifacts/macos-arm64/barnum +0 -0
- package/artifacts/macos-x64/barnum +0 -0
- package/artifacts/win-x64/barnum.exe +0 -0
- package/dist/ast.d.ts +16 -6
- package/dist/ast.js +44 -8
- package/dist/bind.d.ts +2 -2
- package/dist/bind.js +5 -5
- package/dist/builtins.d.ts +25 -2
- package/dist/builtins.js +53 -14
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/race.d.ts +1 -1
- package/dist/race.js +1 -1
- package/dist/recursive.js +5 -5
- package/dist/try-catch.d.ts +1 -1
- package/dist/try-catch.js +1 -1
- package/package.json +1 -1
- package/src/ast.ts +71 -12
- package/src/bind.ts +7 -7
- package/src/builtins.ts +63 -14
- package/src/index.ts +4 -2
- package/src/race.ts +1 -1
- package/src/recursive.ts +5 -11
- package/src/try-catch.ts +1 -1
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/ast.d.ts
CHANGED
|
@@ -68,16 +68,20 @@ export type BuiltinKind = {
|
|
|
68
68
|
} | {
|
|
69
69
|
kind: "Flatten";
|
|
70
70
|
} | {
|
|
71
|
-
kind: "
|
|
71
|
+
kind: "GetField";
|
|
72
72
|
value: string;
|
|
73
73
|
} | {
|
|
74
|
-
kind: "
|
|
74
|
+
kind: "GetIndex";
|
|
75
75
|
value: number;
|
|
76
76
|
} | {
|
|
77
77
|
kind: "Pick";
|
|
78
78
|
value: string[];
|
|
79
79
|
} | {
|
|
80
80
|
kind: "CollectSome";
|
|
81
|
+
} | {
|
|
82
|
+
kind: "SplitFirst";
|
|
83
|
+
} | {
|
|
84
|
+
kind: "SplitLast";
|
|
81
85
|
} | {
|
|
82
86
|
kind: "WrapInField";
|
|
83
87
|
value: string;
|
|
@@ -130,14 +134,20 @@ export type TypedAction<In = unknown, Out = unknown, Refs extends string = never
|
|
|
130
134
|
drop(): TypedAction<In, never, Refs>;
|
|
131
135
|
/** Wrap output as a tagged union member. Requires full variant map TDef so __def is carried. */
|
|
132
136
|
tag<TDef extends Record<string, unknown>, TKind extends keyof TDef & string>(kind: TKind): TypedAction<In, TaggedUnion<TDef>, Refs>;
|
|
133
|
-
/** Extract a field from the output object. `a.
|
|
134
|
-
|
|
137
|
+
/** Extract a field from the output object. `a.getField("name")` ≡ `pipe(a, getField("name"))`. */
|
|
138
|
+
getField<TField extends keyof Out & string>(field: TField): TypedAction<In, Out[TField], Refs>;
|
|
139
|
+
/** Extract an element from the output tuple by index. `a.getIndex(0)` ≡ `pipe(a, getIndex(0))`. */
|
|
140
|
+
getIndex<TIn, TTuple extends unknown[], TIndex extends number, TRefs extends string>(this: TypedAction<TIn, TTuple, TRefs>, index: TIndex): TypedAction<TIn, TTuple[TIndex], TRefs>;
|
|
135
141
|
/** Wrap output in an object under a field name. `a.wrapInField("foo")` ≡ `pipe(a, wrapInField("foo"))`. */
|
|
136
142
|
wrapInField<TField extends string>(field: TField): TypedAction<In, Record<TField, Out>, Refs>;
|
|
137
143
|
/** Merge a tuple of objects into a single object. `a.merge()` ≡ `pipe(a, merge())`. */
|
|
138
144
|
merge(): TypedAction<In, MergeTuple<Out>, Refs>;
|
|
139
145
|
/** Select fields from the output. `a.pick("x", "y")` ≡ `pipe(a, pick("x", "y"))`. */
|
|
140
146
|
pick<TKeys extends (keyof Out & string)[]>(...keys: TKeys): TypedAction<In, Pick<Out, TKeys[number]>, Refs>;
|
|
147
|
+
/** Head/tail decomposition. Only callable when Out is TElement[]. */
|
|
148
|
+
splitFirst<TIn, TElement, TRefs extends string>(this: TypedAction<TIn, TElement[], TRefs>): TypedAction<TIn, Option<[TElement, TElement[]]>, TRefs>;
|
|
149
|
+
/** Init/last decomposition. Only callable when Out is TElement[]. */
|
|
150
|
+
splitLast<TIn, TElement, TRefs extends string>(this: TypedAction<TIn, TElement[], TRefs>): TypedAction<TIn, Option<[TElement[], TElement]>, TRefs>;
|
|
141
151
|
/**
|
|
142
152
|
* Transform the Some value inside an Option output. Only callable when
|
|
143
153
|
* Out is Option<T>. Uses `this` parameter constraint to gate availability.
|
|
@@ -331,7 +341,7 @@ export declare const IDENTITY: Action;
|
|
|
331
341
|
* If the body completes normally → output is TOut.
|
|
332
342
|
* If restart fires → body re-executes with the restarted value.
|
|
333
343
|
*
|
|
334
|
-
* Compiled form: `RestartHandle(id,
|
|
344
|
+
* Compiled form: `RestartHandle(id, GetIndex(0), body)`
|
|
335
345
|
*/
|
|
336
346
|
export declare function recur<TIn = never, TOut = any>(bodyFn: (restart: TypedAction<TIn, never>) => Pipeable<TIn, TOut>): TypedAction<PipeIn<TIn>, TOut>;
|
|
337
347
|
/**
|
|
@@ -349,7 +359,7 @@ export declare function recur<TIn = never, TOut = any>(bodyFn: (restart: TypedAc
|
|
|
349
359
|
export declare function earlyReturn<TEarlyReturn = never, TIn = any, TOut = any>(bodyFn: (earlyReturn: TypedAction<TEarlyReturn, never>) => Pipeable<TIn, TOut>): TypedAction<TIn, TEarlyReturn | TOut>;
|
|
350
360
|
/**
|
|
351
361
|
* Build the restart+branch compiled form:
|
|
352
|
-
* `Chain(Tag("Continue"), RestartHandle(id,
|
|
362
|
+
* `Chain(Tag("Continue"), RestartHandle(id, GetIndex(0), Branch({ Continue: continueArm, Break: breakArm })))`
|
|
353
363
|
*
|
|
354
364
|
* Input is tagged Continue so the Branch enters the continueArm on first execution.
|
|
355
365
|
* Continue tag → restart → re-enters continueArm. Break tag → restart → runs breakArm, exits `RestartHandle`.
|
package/dist/ast.js
CHANGED
|
@@ -49,7 +49,7 @@ function tagMethod(kind) {
|
|
|
49
49
|
},
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
|
-
function
|
|
52
|
+
function getFieldMethod(field) {
|
|
53
53
|
return typedAction({
|
|
54
54
|
kind: "Chain",
|
|
55
55
|
first: this,
|
|
@@ -57,7 +57,20 @@ function getMethod(field) {
|
|
|
57
57
|
kind: "Invoke",
|
|
58
58
|
handler: {
|
|
59
59
|
kind: "Builtin",
|
|
60
|
-
builtin: { kind: "
|
|
60
|
+
builtin: { kind: "GetField", value: field },
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function getIndexMethod(index) {
|
|
66
|
+
return typedAction({
|
|
67
|
+
kind: "Chain",
|
|
68
|
+
first: this,
|
|
69
|
+
rest: {
|
|
70
|
+
kind: "Invoke",
|
|
71
|
+
handler: {
|
|
72
|
+
kind: "Builtin",
|
|
73
|
+
builtin: { kind: "GetIndex", value: index },
|
|
61
74
|
},
|
|
62
75
|
},
|
|
63
76
|
});
|
|
@@ -95,6 +108,26 @@ function pickMethod(...keys) {
|
|
|
95
108
|
},
|
|
96
109
|
});
|
|
97
110
|
}
|
|
111
|
+
function splitFirstMethod() {
|
|
112
|
+
return typedAction({
|
|
113
|
+
kind: "Chain",
|
|
114
|
+
first: this,
|
|
115
|
+
rest: {
|
|
116
|
+
kind: "Invoke",
|
|
117
|
+
handler: { kind: "Builtin", builtin: { kind: "SplitFirst" } },
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
function splitLastMethod() {
|
|
122
|
+
return typedAction({
|
|
123
|
+
kind: "Chain",
|
|
124
|
+
first: this,
|
|
125
|
+
rest: {
|
|
126
|
+
kind: "Invoke",
|
|
127
|
+
handler: { kind: "Builtin", builtin: { kind: "SplitLast" } },
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
}
|
|
98
131
|
function mapOptionMethod(action) {
|
|
99
132
|
// Desugars to: self.then(branch({ Some: pipe(action, tag("Some")), None: tag("None") }))
|
|
100
133
|
// But branch auto-unwraps value, so:
|
|
@@ -182,10 +215,13 @@ export function typedAction(action) {
|
|
|
182
215
|
flatten: { value: flattenMethod, configurable: true },
|
|
183
216
|
drop: { value: dropMethod, configurable: true },
|
|
184
217
|
tag: { value: tagMethod, configurable: true },
|
|
185
|
-
|
|
218
|
+
getField: { value: getFieldMethod, configurable: true },
|
|
219
|
+
getIndex: { value: getIndexMethod, configurable: true },
|
|
186
220
|
wrapInField: { value: wrapInFieldMethod, configurable: true },
|
|
187
221
|
merge: { value: mergeMethod, configurable: true },
|
|
188
222
|
pick: { value: pickMethod, configurable: true },
|
|
223
|
+
splitFirst: { value: splitFirstMethod, configurable: true },
|
|
224
|
+
splitLast: { value: splitLastMethod, configurable: true },
|
|
189
225
|
mapOption: { value: mapOptionMethod, configurable: true },
|
|
190
226
|
mapErr: { value: mapErrMethod, configurable: true },
|
|
191
227
|
unwrapOr: { value: unwrapOrMethod, configurable: true },
|
|
@@ -209,7 +245,7 @@ export function forEach(action) {
|
|
|
209
245
|
return typedAction({ kind: "ForEach", action: action });
|
|
210
246
|
}
|
|
211
247
|
/**
|
|
212
|
-
* Insert
|
|
248
|
+
* Insert GetField("value") before each case handler in a branch.
|
|
213
249
|
* This implements auto-unwrapping: the engine dispatches on `kind`, then
|
|
214
250
|
* extracts `value` before passing to the handler. Case handlers receive
|
|
215
251
|
* the payload directly, not the full `{ kind, value }` variant.
|
|
@@ -223,7 +259,7 @@ function unwrapBranchCases(cases) {
|
|
|
223
259
|
kind: "Invoke",
|
|
224
260
|
handler: {
|
|
225
261
|
kind: "Builtin",
|
|
226
|
-
builtin: { kind: "
|
|
262
|
+
builtin: { kind: "GetField", value: "value" },
|
|
227
263
|
},
|
|
228
264
|
},
|
|
229
265
|
rest: cases[key],
|
|
@@ -240,7 +276,7 @@ export function branch(cases) {
|
|
|
240
276
|
// ---------------------------------------------------------------------------
|
|
241
277
|
const EXTRACT_PAYLOAD = {
|
|
242
278
|
kind: "Invoke",
|
|
243
|
-
handler: { kind: "Builtin", builtin: { kind: "
|
|
279
|
+
handler: { kind: "Builtin", builtin: { kind: "GetIndex", value: 0 } },
|
|
244
280
|
};
|
|
245
281
|
const TAG_CONTINUE = {
|
|
246
282
|
kind: "Invoke",
|
|
@@ -264,7 +300,7 @@ export const IDENTITY = {
|
|
|
264
300
|
* If the body completes normally → output is TOut.
|
|
265
301
|
* If restart fires → body re-executes with the restarted value.
|
|
266
302
|
*
|
|
267
|
-
* Compiled form: `RestartHandle(id,
|
|
303
|
+
* Compiled form: `RestartHandle(id, GetIndex(0), body)`
|
|
268
304
|
*/
|
|
269
305
|
export function recur(bodyFn) {
|
|
270
306
|
const restartHandlerId = allocateRestartHandlerId();
|
|
@@ -310,7 +346,7 @@ export function earlyReturn(bodyFn) {
|
|
|
310
346
|
// ---------------------------------------------------------------------------
|
|
311
347
|
/**
|
|
312
348
|
* Build the restart+branch compiled form:
|
|
313
|
-
* `Chain(Tag("Continue"), RestartHandle(id,
|
|
349
|
+
* `Chain(Tag("Continue"), RestartHandle(id, GetIndex(0), Branch({ Continue: continueArm, Break: breakArm })))`
|
|
314
350
|
*
|
|
315
351
|
* Input is tagged Continue so the Branch enters the continueArm on first execution.
|
|
316
352
|
* Continue tag → restart → re-enters continueArm. Break tag → restart → runs breakArm, exits `RestartHandle`.
|
package/dist/bind.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type Action, type ExtractInput, type ExtractOutput, type TypedAction }
|
|
|
3
3
|
* A typed reference to a bound value. Output is `TValue`.
|
|
4
4
|
*
|
|
5
5
|
* Use `.then()` (not `pipe()`) when chaining a VarRef into a generic
|
|
6
|
-
* action like `pick` or `
|
|
6
|
+
* action like `pick` or `getField` — pipe overloads can't infer
|
|
7
7
|
* the generic's type parameter from the VarRef's output.
|
|
8
8
|
*/
|
|
9
9
|
export type VarRef<TValue> = TypedAction<never, TValue>;
|
|
@@ -33,7 +33,7 @@ export type InferVarRefs<TBindings extends Action[]> = {
|
|
|
33
33
|
* All(...bindings, Identity),
|
|
34
34
|
* ResumeHandle(r0, readVar(0),
|
|
35
35
|
* ResumeHandle(r1, readVar(1),
|
|
36
|
-
* Chain(
|
|
36
|
+
* Chain(GetIndex(N), body)
|
|
37
37
|
* )
|
|
38
38
|
* )
|
|
39
39
|
* )
|
package/dist/bind.js
CHANGED
|
@@ -18,7 +18,7 @@ function createVarRef(resumeHandlerId) {
|
|
|
18
18
|
* `state` (index 1) is the full All output tuple. The handler produces
|
|
19
19
|
* `[state[n], state]` — value is state[n], new_state is state (unchanged).
|
|
20
20
|
*
|
|
21
|
-
* Expanded AST: All(Chain(
|
|
21
|
+
* Expanded AST: All(Chain(GetIndex(1), GetIndex(n)), GetIndex(1))
|
|
22
22
|
*/
|
|
23
23
|
function readVar(n) {
|
|
24
24
|
return {
|
|
@@ -30,14 +30,14 @@ function readVar(n) {
|
|
|
30
30
|
kind: "Invoke",
|
|
31
31
|
handler: {
|
|
32
32
|
kind: "Builtin",
|
|
33
|
-
builtin: { kind: "
|
|
33
|
+
builtin: { kind: "GetIndex", value: 1 },
|
|
34
34
|
},
|
|
35
35
|
},
|
|
36
36
|
rest: {
|
|
37
37
|
kind: "Invoke",
|
|
38
38
|
handler: {
|
|
39
39
|
kind: "Builtin",
|
|
40
|
-
builtin: { kind: "
|
|
40
|
+
builtin: { kind: "GetIndex", value: n },
|
|
41
41
|
},
|
|
42
42
|
},
|
|
43
43
|
},
|
|
@@ -45,7 +45,7 @@ function readVar(n) {
|
|
|
45
45
|
kind: "Invoke",
|
|
46
46
|
handler: {
|
|
47
47
|
kind: "Builtin",
|
|
48
|
-
builtin: { kind: "
|
|
48
|
+
builtin: { kind: "GetIndex", value: 1 },
|
|
49
49
|
},
|
|
50
50
|
},
|
|
51
51
|
],
|
|
@@ -67,7 +67,7 @@ export function bind(bindings, body) {
|
|
|
67
67
|
kind: "Invoke",
|
|
68
68
|
handler: {
|
|
69
69
|
kind: "Builtin",
|
|
70
|
-
builtin: { kind: "
|
|
70
|
+
builtin: { kind: "GetIndex", value: pipelineInputIndex },
|
|
71
71
|
},
|
|
72
72
|
},
|
|
73
73
|
rest: bodyAction,
|
package/dist/builtins.d.ts
CHANGED
|
@@ -18,8 +18,8 @@ export declare const drop: TypedAction<any, never>;
|
|
|
18
18
|
export declare function tag<TDef extends Record<string, unknown>, TKind extends keyof TDef & string>(kind: TKind): TypedAction<TDef[TKind], TaggedUnion<TDef>>;
|
|
19
19
|
export declare function merge<TTuple extends Record<string, unknown>[]>(): TypedAction<TTuple, MergeTuple<TTuple>>;
|
|
20
20
|
export declare function flatten<TElement>(): TypedAction<TElement[][], TElement[]>;
|
|
21
|
-
export declare function
|
|
22
|
-
export declare function
|
|
21
|
+
export declare function getField<TObj extends Record<string, unknown>, TField extends keyof TObj & string>(field: TField): TypedAction<TObj, TObj[TField]>;
|
|
22
|
+
export declare function getIndex<TTuple extends unknown[], TIndex extends number>(index: TIndex): TypedAction<TTuple, TTuple[TIndex]>;
|
|
23
23
|
export declare function pick<TObj extends Record<string, unknown>, TKeys extends (keyof TObj & string)[]>(...keys: TKeys): TypedAction<TObj, Pick<TObj, TKeys[number]>>;
|
|
24
24
|
export declare function dropResult<TInput, TOutput>(action: Pipeable<TInput, TOutput>): TypedAction<TInput, never>;
|
|
25
25
|
/**
|
|
@@ -57,6 +57,29 @@ export declare function withResource<TIn extends Record<string, unknown>, TResou
|
|
|
57
57
|
export declare function tap<TInput extends Record<string, unknown>>(action: Pipeable<TInput, any>): TypedAction<TInput, TInput>;
|
|
58
58
|
export declare function wrapInField<TField extends string, TValue>(field: TField): TypedAction<TValue, Record<TField, TValue>>;
|
|
59
59
|
export declare function range(start: number, end: number): TypedAction<any, number[]>;
|
|
60
|
+
/**
|
|
61
|
+
* Deconstruct an array into its first element and the remaining elements.
|
|
62
|
+
* `TElement[] → Option<[TElement, TElement[]]>`
|
|
63
|
+
*
|
|
64
|
+
* Returns `Some([first, rest])` for non-empty arrays, `None` for empty arrays.
|
|
65
|
+
* This is the array equivalent of cons/uncons — enables recursive iteration
|
|
66
|
+
* patterns via `loop` + `splitFirst` + `branch`.
|
|
67
|
+
*
|
|
68
|
+
* This is a builtin (SplitFirst) because it requires array-length branching
|
|
69
|
+
* that can't be composed from existing AST nodes.
|
|
70
|
+
*/
|
|
71
|
+
export declare function splitFirst<TElement>(): TypedAction<TElement[], OptionT<[TElement, TElement[]]>>;
|
|
72
|
+
/**
|
|
73
|
+
* Deconstruct an array into the leading elements and the last element.
|
|
74
|
+
* `TElement[] → Option<[TElement[], TElement]>`
|
|
75
|
+
*
|
|
76
|
+
* Returns `Some([init, last])` for non-empty arrays, `None` for empty arrays.
|
|
77
|
+
* Mirror of `splitFirst` — enables processing from the tail end.
|
|
78
|
+
*
|
|
79
|
+
* This is a builtin (SplitLast) because it requires array-length branching
|
|
80
|
+
* that can't be composed from existing AST nodes.
|
|
81
|
+
*/
|
|
82
|
+
export declare function splitLast<TElement>(): TypedAction<TElement[], OptionT<[TElement[], TElement]>>;
|
|
60
83
|
/**
|
|
61
84
|
* Option namespace. All combinators produce TypedAction AST nodes that
|
|
62
85
|
* desugar to branch + existing builtins, except collect which uses the
|
package/dist/builtins.js
CHANGED
|
@@ -64,26 +64,26 @@ export function flatten() {
|
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
66
|
// ---------------------------------------------------------------------------
|
|
67
|
-
//
|
|
67
|
+
// GetField — extract a single field from an object
|
|
68
68
|
// ---------------------------------------------------------------------------
|
|
69
|
-
export function
|
|
69
|
+
export function getField(field) {
|
|
70
70
|
return typedAction({
|
|
71
71
|
kind: "Invoke",
|
|
72
72
|
handler: {
|
|
73
73
|
kind: "Builtin",
|
|
74
|
-
builtin: { kind: "
|
|
74
|
+
builtin: { kind: "GetField", value: field },
|
|
75
75
|
},
|
|
76
76
|
});
|
|
77
77
|
}
|
|
78
78
|
// ---------------------------------------------------------------------------
|
|
79
|
-
//
|
|
79
|
+
// GetIndex — extract a single element from an array by index
|
|
80
80
|
// ---------------------------------------------------------------------------
|
|
81
|
-
export function
|
|
81
|
+
export function getIndex(index) {
|
|
82
82
|
return typedAction({
|
|
83
83
|
kind: "Invoke",
|
|
84
84
|
handler: {
|
|
85
85
|
kind: "Builtin",
|
|
86
|
-
builtin: { kind: "
|
|
86
|
+
builtin: { kind: "GetIndex", value: index },
|
|
87
87
|
},
|
|
88
88
|
});
|
|
89
89
|
}
|
|
@@ -146,16 +146,16 @@ export function withResource({ create, action, dispose, }) {
|
|
|
146
146
|
kind: "All",
|
|
147
147
|
actions: [action, identity],
|
|
148
148
|
});
|
|
149
|
-
// Step 3: all(
|
|
149
|
+
// Step 3: all(getIndex(0), chain(getIndex(1), dispose)) → [TOut, unknown]
|
|
150
150
|
const disposeAndKeepResult = typedAction({
|
|
151
151
|
kind: "All",
|
|
152
152
|
actions: [
|
|
153
|
-
|
|
154
|
-
chain(
|
|
153
|
+
getIndex(0),
|
|
154
|
+
chain(getIndex(1), dispose),
|
|
155
155
|
],
|
|
156
156
|
});
|
|
157
|
-
// Step 4:
|
|
158
|
-
return chain(chain(chain(acquireAndMerge, actionAndKeepMerged), disposeAndKeepResult),
|
|
157
|
+
// Step 4: getIndex(0) → TOut
|
|
158
|
+
return chain(chain(chain(acquireAndMerge, actionAndKeepMerged), disposeAndKeepResult), getIndex(0));
|
|
159
159
|
}
|
|
160
160
|
// ---------------------------------------------------------------------------
|
|
161
161
|
// Tap — run an action for side effects, preserve original input
|
|
@@ -227,6 +227,45 @@ export function range(start, end) {
|
|
|
227
227
|
});
|
|
228
228
|
}
|
|
229
229
|
// ---------------------------------------------------------------------------
|
|
230
|
+
// SplitFirst — head/tail decomposition of an array
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
/**
|
|
233
|
+
* Deconstruct an array into its first element and the remaining elements.
|
|
234
|
+
* `TElement[] → Option<[TElement, TElement[]]>`
|
|
235
|
+
*
|
|
236
|
+
* Returns `Some([first, rest])` for non-empty arrays, `None` for empty arrays.
|
|
237
|
+
* This is the array equivalent of cons/uncons — enables recursive iteration
|
|
238
|
+
* patterns via `loop` + `splitFirst` + `branch`.
|
|
239
|
+
*
|
|
240
|
+
* This is a builtin (SplitFirst) because it requires array-length branching
|
|
241
|
+
* that can't be composed from existing AST nodes.
|
|
242
|
+
*/
|
|
243
|
+
export function splitFirst() {
|
|
244
|
+
return typedAction({
|
|
245
|
+
kind: "Invoke",
|
|
246
|
+
handler: { kind: "Builtin", builtin: { kind: "SplitFirst" } },
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
// SplitLast — init/last decomposition of an array
|
|
251
|
+
// ---------------------------------------------------------------------------
|
|
252
|
+
/**
|
|
253
|
+
* Deconstruct an array into the leading elements and the last element.
|
|
254
|
+
* `TElement[] → Option<[TElement[], TElement]>`
|
|
255
|
+
*
|
|
256
|
+
* Returns `Some([init, last])` for non-empty arrays, `None` for empty arrays.
|
|
257
|
+
* Mirror of `splitFirst` — enables processing from the tail end.
|
|
258
|
+
*
|
|
259
|
+
* This is a builtin (SplitLast) because it requires array-length branching
|
|
260
|
+
* that can't be composed from existing AST nodes.
|
|
261
|
+
*/
|
|
262
|
+
export function splitLast() {
|
|
263
|
+
return typedAction({
|
|
264
|
+
kind: "Invoke",
|
|
265
|
+
handler: { kind: "Builtin", builtin: { kind: "SplitLast" } },
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
// ---------------------------------------------------------------------------
|
|
230
269
|
// Option namespace — combinators for Option<T> tagged unions
|
|
231
270
|
// ---------------------------------------------------------------------------
|
|
232
271
|
// Shared AST fragments for Option desugaring
|
|
@@ -242,7 +281,7 @@ const EXTRACT_VALUE = {
|
|
|
242
281
|
kind: "Invoke",
|
|
243
282
|
handler: {
|
|
244
283
|
kind: "Builtin",
|
|
245
|
-
builtin: { kind: "
|
|
284
|
+
builtin: { kind: "GetField", value: "value" },
|
|
246
285
|
},
|
|
247
286
|
};
|
|
248
287
|
const DROP = {
|
|
@@ -253,7 +292,7 @@ const IDENTITY = {
|
|
|
253
292
|
kind: "Invoke",
|
|
254
293
|
handler: { kind: "Builtin", builtin: { kind: "Identity" } },
|
|
255
294
|
};
|
|
256
|
-
/** Wrap branch cases with
|
|
295
|
+
/** Wrap branch cases with GetField("value") auto-unwrapping. */
|
|
257
296
|
function optionBranch(someCaseBody, noneCaseBody) {
|
|
258
297
|
return {
|
|
259
298
|
kind: "Branch",
|
|
@@ -416,7 +455,7 @@ const TAG_ERR = {
|
|
|
416
455
|
kind: "Invoke",
|
|
417
456
|
handler: { kind: "Builtin", builtin: { kind: "Tag", value: "Err" } },
|
|
418
457
|
};
|
|
419
|
-
/** Wrap branch cases with
|
|
458
|
+
/** Wrap branch cases with GetField("value") auto-unwrapping. */
|
|
420
459
|
function resultBranch(okCaseBody, errCaseBody) {
|
|
421
460
|
return {
|
|
422
461
|
kind: "Branch",
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { TaggedUnion, OptionDef, ResultDef } from "./ast.js";
|
|
2
2
|
export * from "./ast.js";
|
|
3
|
-
export { constant, identity, drop, tag, merge, flatten,
|
|
3
|
+
export { constant, identity, drop, tag, merge, flatten, getField, getIndex, pick, dropResult, withResource, tap, range, splitFirst, splitLast, wrapInField, Option, Result, } from "./builtins.js";
|
|
4
4
|
export * from "./handler.js";
|
|
5
5
|
export { runPipeline, type RunPipelineOptions, type LogLevel } from "./run.js";
|
|
6
6
|
export { zodToCheckedJsonSchema } from "./schema.js";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from "./ast.js";
|
|
2
|
-
export { constant, identity, drop, tag, merge, flatten,
|
|
2
|
+
export { constant, identity, drop, tag, merge, flatten, getField, getIndex, pick, dropResult, withResource, tap, range, splitFirst, splitLast, wrapInField, Option, Result, } from "./builtins.js";
|
|
3
3
|
export * from "./handler.js";
|
|
4
4
|
export { runPipeline } from "./run.js";
|
|
5
5
|
export { zodToCheckedJsonSchema } from "./schema.js";
|
package/dist/race.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { type Pipeable, type Result, type TypedAction } from "./ast.js";
|
|
|
8
8
|
*
|
|
9
9
|
* Compiled form (restart+Branch, same substrate as loop/earlyReturn):
|
|
10
10
|
* `Chain(Tag("Continue"),`
|
|
11
|
-
* `RestartHandle(id,
|
|
11
|
+
* `RestartHandle(id, GetIndex(0),`
|
|
12
12
|
* `Branch({`
|
|
13
13
|
* `Continue: All(Chain(a, breakPerform), Chain(b, breakPerform), ...),`
|
|
14
14
|
* `Break: identity,`
|
package/dist/race.js
CHANGED
|
@@ -35,7 +35,7 @@ function breakPerform(restartHandlerId) {
|
|
|
35
35
|
*
|
|
36
36
|
* Compiled form (restart+Branch, same substrate as loop/earlyReturn):
|
|
37
37
|
* `Chain(Tag("Continue"),`
|
|
38
|
-
* `RestartHandle(id,
|
|
38
|
+
* `RestartHandle(id, GetIndex(0),`
|
|
39
39
|
* `Branch({`
|
|
40
40
|
* `Continue: All(Chain(a, breakPerform), Chain(b, breakPerform), ...),`
|
|
41
41
|
* `Break: identity,`
|
package/dist/recursive.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { typedAction, branch, } from "./ast.js";
|
|
2
2
|
import { all } from "./all.js";
|
|
3
3
|
import { chain } from "./chain.js";
|
|
4
|
-
import { constant, identity,
|
|
4
|
+
import { constant, identity, getField, getIndex, tag } from "./builtins.js";
|
|
5
5
|
import { allocateResumeHandlerId } from "./effect-id.js";
|
|
6
6
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
7
|
const UNUSED_STATE = undefined;
|
|
@@ -35,10 +35,10 @@ export function defineRecursiveFunctions(bodiesFn) {
|
|
|
35
35
|
const callTokens = Array.from({ length: fnCount }, (_, i) => typedAction(chain(tag(`Call${i}`), resumePerform)));
|
|
36
36
|
// Get function body ASTs
|
|
37
37
|
const bodyActions = bodiesFn(...callTokens);
|
|
38
|
-
// Branch cases: CallN →
|
|
38
|
+
// Branch cases: CallN → GetField("value") → bodyN
|
|
39
39
|
const cases = {};
|
|
40
40
|
for (let i = 0; i < bodyActions.length; i++) {
|
|
41
|
-
cases[`Call${i}`] = chain(
|
|
41
|
+
cases[`Call${i}`] = chain(getField("value"), bodyActions[i]);
|
|
42
42
|
}
|
|
43
43
|
// Return curried entry-point combinator
|
|
44
44
|
return (entryFn) => {
|
|
@@ -46,8 +46,8 @@ export function defineRecursiveFunctions(bodiesFn) {
|
|
|
46
46
|
return typedAction(chain(all(identity, constant(UNUSED_STATE)), {
|
|
47
47
|
kind: "ResumeHandle",
|
|
48
48
|
resume_handler_id: resumeHandlerId,
|
|
49
|
-
body: chain(
|
|
50
|
-
handler: all(chain(
|
|
49
|
+
body: chain(getIndex(0), userBody),
|
|
50
|
+
handler: all(chain(getIndex(0), branch(cases)), constant(UNUSED_STATE)),
|
|
51
51
|
}));
|
|
52
52
|
};
|
|
53
53
|
}
|
package/dist/try-catch.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { type Pipeable, type TypedAction } from "./ast.js";
|
|
|
12
12
|
*
|
|
13
13
|
* Compiled form (restart+Branch, same substrate as loop/earlyReturn):
|
|
14
14
|
* `Chain(Tag("Continue"),`
|
|
15
|
-
* `RestartHandle(id,
|
|
15
|
+
* `RestartHandle(id, GetIndex(0),`
|
|
16
16
|
* `Branch({ Continue: body, Break: recovery })))`
|
|
17
17
|
*
|
|
18
18
|
* throwError = `Chain(Tag("Break"), RestartPerform(id))`
|
package/dist/try-catch.js
CHANGED
|
@@ -16,7 +16,7 @@ import { allocateRestartHandlerId } from "./effect-id.js";
|
|
|
16
16
|
*
|
|
17
17
|
* Compiled form (restart+Branch, same substrate as loop/earlyReturn):
|
|
18
18
|
* `Chain(Tag("Continue"),`
|
|
19
|
-
* `RestartHandle(id,
|
|
19
|
+
* `RestartHandle(id, GetIndex(0),`
|
|
20
20
|
* `Branch({ Continue: body, Break: recovery })))`
|
|
21
21
|
*
|
|
22
22
|
* throwError = `Chain(Tag("Break"), RestartPerform(id))`
|
package/package.json
CHANGED
package/src/ast.ts
CHANGED
|
@@ -91,10 +91,12 @@ export type BuiltinKind =
|
|
|
91
91
|
| { kind: "Tag"; value: string }
|
|
92
92
|
| { kind: "Merge" }
|
|
93
93
|
| { kind: "Flatten" }
|
|
94
|
-
| { kind: "
|
|
95
|
-
| { kind: "
|
|
94
|
+
| { kind: "GetField"; value: string }
|
|
95
|
+
| { kind: "GetIndex"; value: number }
|
|
96
96
|
| { kind: "Pick"; value: string[] }
|
|
97
97
|
| { kind: "CollectSome" }
|
|
98
|
+
| { kind: "SplitFirst" }
|
|
99
|
+
| { kind: "SplitLast" }
|
|
98
100
|
| { kind: "WrapInField"; value: string }
|
|
99
101
|
| { kind: "Sleep"; value: number };
|
|
100
102
|
|
|
@@ -181,10 +183,20 @@ export type TypedAction<
|
|
|
181
183
|
tag<TDef extends Record<string, unknown>, TKind extends keyof TDef & string>(
|
|
182
184
|
kind: TKind,
|
|
183
185
|
): TypedAction<In, TaggedUnion<TDef>, Refs>;
|
|
184
|
-
/** Extract a field from the output object. `a.
|
|
185
|
-
|
|
186
|
+
/** Extract a field from the output object. `a.getField("name")` ≡ `pipe(a, getField("name"))`. */
|
|
187
|
+
getField<TField extends keyof Out & string>(
|
|
186
188
|
field: TField,
|
|
187
189
|
): TypedAction<In, Out[TField], Refs>;
|
|
190
|
+
/** Extract an element from the output tuple by index. `a.getIndex(0)` ≡ `pipe(a, getIndex(0))`. */
|
|
191
|
+
getIndex<
|
|
192
|
+
TIn,
|
|
193
|
+
TTuple extends unknown[],
|
|
194
|
+
TIndex extends number,
|
|
195
|
+
TRefs extends string,
|
|
196
|
+
>(
|
|
197
|
+
this: TypedAction<TIn, TTuple, TRefs>,
|
|
198
|
+
index: TIndex,
|
|
199
|
+
): TypedAction<TIn, TTuple[TIndex], TRefs>;
|
|
188
200
|
/** Wrap output in an object under a field name. `a.wrapInField("foo")` ≡ `pipe(a, wrapInField("foo"))`. */
|
|
189
201
|
wrapInField<TField extends string>(
|
|
190
202
|
field: TField,
|
|
@@ -195,6 +207,14 @@ export type TypedAction<
|
|
|
195
207
|
pick<TKeys extends (keyof Out & string)[]>(
|
|
196
208
|
...keys: TKeys
|
|
197
209
|
): TypedAction<In, Pick<Out, TKeys[number]>, Refs>;
|
|
210
|
+
/** Head/tail decomposition. Only callable when Out is TElement[]. */
|
|
211
|
+
splitFirst<TIn, TElement, TRefs extends string>(
|
|
212
|
+
this: TypedAction<TIn, TElement[], TRefs>,
|
|
213
|
+
): TypedAction<TIn, Option<[TElement, TElement[]]>, TRefs>;
|
|
214
|
+
/** Init/last decomposition. Only callable when Out is TElement[]. */
|
|
215
|
+
splitLast<TIn, TElement, TRefs extends string>(
|
|
216
|
+
this: TypedAction<TIn, TElement[], TRefs>,
|
|
217
|
+
): TypedAction<TIn, Option<[TElement[], TElement]>, TRefs>;
|
|
198
218
|
/**
|
|
199
219
|
* Transform the Some value inside an Option output. Only callable when
|
|
200
220
|
* Out is Option<T>. Uses `this` parameter constraint to gate availability.
|
|
@@ -418,7 +438,7 @@ function tagMethod(this: TypedAction, kind: string): TypedAction {
|
|
|
418
438
|
});
|
|
419
439
|
}
|
|
420
440
|
|
|
421
|
-
function
|
|
441
|
+
function getFieldMethod(this: TypedAction, field: string): TypedAction {
|
|
422
442
|
return typedAction({
|
|
423
443
|
kind: "Chain",
|
|
424
444
|
first: this,
|
|
@@ -426,7 +446,21 @@ function getMethod(this: TypedAction, field: string): TypedAction {
|
|
|
426
446
|
kind: "Invoke",
|
|
427
447
|
handler: {
|
|
428
448
|
kind: "Builtin",
|
|
429
|
-
builtin: { kind: "
|
|
449
|
+
builtin: { kind: "GetField", value: field },
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function getIndexMethod(this: TypedAction, index: number): TypedAction {
|
|
456
|
+
return typedAction({
|
|
457
|
+
kind: "Chain",
|
|
458
|
+
first: this,
|
|
459
|
+
rest: {
|
|
460
|
+
kind: "Invoke",
|
|
461
|
+
handler: {
|
|
462
|
+
kind: "Builtin",
|
|
463
|
+
builtin: { kind: "GetIndex", value: index },
|
|
430
464
|
},
|
|
431
465
|
},
|
|
432
466
|
});
|
|
@@ -468,6 +502,28 @@ function pickMethod(this: TypedAction, ...keys: string[]): TypedAction {
|
|
|
468
502
|
});
|
|
469
503
|
}
|
|
470
504
|
|
|
505
|
+
function splitFirstMethod(this: TypedAction): TypedAction {
|
|
506
|
+
return typedAction({
|
|
507
|
+
kind: "Chain",
|
|
508
|
+
first: this,
|
|
509
|
+
rest: {
|
|
510
|
+
kind: "Invoke",
|
|
511
|
+
handler: { kind: "Builtin", builtin: { kind: "SplitFirst" } },
|
|
512
|
+
},
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function splitLastMethod(this: TypedAction): TypedAction {
|
|
517
|
+
return typedAction({
|
|
518
|
+
kind: "Chain",
|
|
519
|
+
first: this,
|
|
520
|
+
rest: {
|
|
521
|
+
kind: "Invoke",
|
|
522
|
+
handler: { kind: "Builtin", builtin: { kind: "SplitLast" } },
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
|
|
471
527
|
function mapOptionMethod(this: TypedAction, action: Action): TypedAction {
|
|
472
528
|
// Desugars to: self.then(branch({ Some: pipe(action, tag("Some")), None: tag("None") }))
|
|
473
529
|
// But branch auto-unwraps value, so:
|
|
@@ -562,10 +618,13 @@ export function typedAction<
|
|
|
562
618
|
flatten: { value: flattenMethod, configurable: true },
|
|
563
619
|
drop: { value: dropMethod, configurable: true },
|
|
564
620
|
tag: { value: tagMethod, configurable: true },
|
|
565
|
-
|
|
621
|
+
getField: { value: getFieldMethod, configurable: true },
|
|
622
|
+
getIndex: { value: getIndexMethod, configurable: true },
|
|
566
623
|
wrapInField: { value: wrapInFieldMethod, configurable: true },
|
|
567
624
|
merge: { value: mergeMethod, configurable: true },
|
|
568
625
|
pick: { value: pickMethod, configurable: true },
|
|
626
|
+
splitFirst: { value: splitFirstMethod, configurable: true },
|
|
627
|
+
splitLast: { value: splitLastMethod, configurable: true },
|
|
569
628
|
mapOption: { value: mapOptionMethod, configurable: true },
|
|
570
629
|
mapErr: { value: mapErrMethod, configurable: true },
|
|
571
630
|
unwrapOr: { value: unwrapOrMethod, configurable: true },
|
|
@@ -625,7 +684,7 @@ export function forEach<In, Out>(
|
|
|
625
684
|
}
|
|
626
685
|
|
|
627
686
|
/**
|
|
628
|
-
* Insert
|
|
687
|
+
* Insert GetField("value") before each case handler in a branch.
|
|
629
688
|
* This implements auto-unwrapping: the engine dispatches on `kind`, then
|
|
630
689
|
* extracts `value` before passing to the handler. Case handlers receive
|
|
631
690
|
* the payload directly, not the full `{ kind, value }` variant.
|
|
@@ -641,7 +700,7 @@ function unwrapBranchCases(
|
|
|
641
700
|
kind: "Invoke",
|
|
642
701
|
handler: {
|
|
643
702
|
kind: "Builtin",
|
|
644
|
-
builtin: { kind: "
|
|
703
|
+
builtin: { kind: "GetField", value: "value" },
|
|
645
704
|
},
|
|
646
705
|
},
|
|
647
706
|
rest: cases[key],
|
|
@@ -691,7 +750,7 @@ export type LoopResult<TContinue, TBreak> = TaggedUnion<
|
|
|
691
750
|
|
|
692
751
|
const EXTRACT_PAYLOAD: Action = {
|
|
693
752
|
kind: "Invoke",
|
|
694
|
-
handler: { kind: "Builtin", builtin: { kind: "
|
|
753
|
+
handler: { kind: "Builtin", builtin: { kind: "GetIndex", value: 0 } },
|
|
695
754
|
};
|
|
696
755
|
|
|
697
756
|
const TAG_CONTINUE: Action = {
|
|
@@ -720,7 +779,7 @@ export const IDENTITY: Action = {
|
|
|
720
779
|
* If the body completes normally → output is TOut.
|
|
721
780
|
* If restart fires → body re-executes with the restarted value.
|
|
722
781
|
*
|
|
723
|
-
* Compiled form: `RestartHandle(id,
|
|
782
|
+
* Compiled form: `RestartHandle(id, GetIndex(0), body)`
|
|
724
783
|
*/
|
|
725
784
|
export function recur<TIn = never, TOut = any>(
|
|
726
785
|
bodyFn: (restart: TypedAction<TIn, never>) => Pipeable<TIn, TOut>,
|
|
@@ -784,7 +843,7 @@ export function earlyReturn<TEarlyReturn = never, TIn = any, TOut = any>(
|
|
|
784
843
|
|
|
785
844
|
/**
|
|
786
845
|
* Build the restart+branch compiled form:
|
|
787
|
-
* `Chain(Tag("Continue"), RestartHandle(id,
|
|
846
|
+
* `Chain(Tag("Continue"), RestartHandle(id, GetIndex(0), Branch({ Continue: continueArm, Break: breakArm })))`
|
|
788
847
|
*
|
|
789
848
|
* Input is tagged Continue so the Branch enters the continueArm on first execution.
|
|
790
849
|
* Continue tag → restart → re-enters continueArm. Break tag → restart → runs breakArm, exits `RestartHandle`.
|
package/src/bind.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { pipe } from "./pipe.js";
|
|
|
17
17
|
* A typed reference to a bound value. Output is `TValue`.
|
|
18
18
|
*
|
|
19
19
|
* Use `.then()` (not `pipe()`) when chaining a VarRef into a generic
|
|
20
|
-
* action like `pick` or `
|
|
20
|
+
* action like `pick` or `getField` — pipe overloads can't infer
|
|
21
21
|
* the generic's type parameter from the VarRef's output.
|
|
22
22
|
*/
|
|
23
23
|
export type VarRef<TValue> = TypedAction<never, TValue>;
|
|
@@ -61,7 +61,7 @@ export type InferVarRefs<TBindings extends Action[]> = {
|
|
|
61
61
|
* `state` (index 1) is the full All output tuple. The handler produces
|
|
62
62
|
* `[state[n], state]` — value is state[n], new_state is state (unchanged).
|
|
63
63
|
*
|
|
64
|
-
* Expanded AST: All(Chain(
|
|
64
|
+
* Expanded AST: All(Chain(GetIndex(1), GetIndex(n)), GetIndex(1))
|
|
65
65
|
*/
|
|
66
66
|
function readVar(n: number): Action {
|
|
67
67
|
return {
|
|
@@ -73,14 +73,14 @@ function readVar(n: number): Action {
|
|
|
73
73
|
kind: "Invoke",
|
|
74
74
|
handler: {
|
|
75
75
|
kind: "Builtin",
|
|
76
|
-
builtin: { kind: "
|
|
76
|
+
builtin: { kind: "GetIndex", value: 1 },
|
|
77
77
|
},
|
|
78
78
|
},
|
|
79
79
|
rest: {
|
|
80
80
|
kind: "Invoke",
|
|
81
81
|
handler: {
|
|
82
82
|
kind: "Builtin",
|
|
83
|
-
builtin: { kind: "
|
|
83
|
+
builtin: { kind: "GetIndex", value: n },
|
|
84
84
|
},
|
|
85
85
|
},
|
|
86
86
|
},
|
|
@@ -88,7 +88,7 @@ function readVar(n: number): Action {
|
|
|
88
88
|
kind: "Invoke",
|
|
89
89
|
handler: {
|
|
90
90
|
kind: "Builtin",
|
|
91
|
-
builtin: { kind: "
|
|
91
|
+
builtin: { kind: "GetIndex", value: 1 },
|
|
92
92
|
},
|
|
93
93
|
},
|
|
94
94
|
],
|
|
@@ -111,7 +111,7 @@ function readVar(n: number): Action {
|
|
|
111
111
|
* All(...bindings, Identity),
|
|
112
112
|
* ResumeHandle(r0, readVar(0),
|
|
113
113
|
* ResumeHandle(r1, readVar(1),
|
|
114
|
-
* Chain(
|
|
114
|
+
* Chain(GetIndex(N), body)
|
|
115
115
|
* )
|
|
116
116
|
* )
|
|
117
117
|
* )
|
|
@@ -148,7 +148,7 @@ export function bind<TBindings extends Action[], TOut>(
|
|
|
148
148
|
kind: "Invoke",
|
|
149
149
|
handler: {
|
|
150
150
|
kind: "Builtin",
|
|
151
|
-
builtin: { kind: "
|
|
151
|
+
builtin: { kind: "GetIndex", value: pipelineInputIndex },
|
|
152
152
|
},
|
|
153
153
|
},
|
|
154
154
|
rest: bodyAction,
|
package/src/builtins.ts
CHANGED
|
@@ -93,10 +93,10 @@ export function flatten<TElement>(): TypedAction<TElement[][], TElement[]> {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
// ---------------------------------------------------------------------------
|
|
96
|
-
//
|
|
96
|
+
// GetField — extract a single field from an object
|
|
97
97
|
// ---------------------------------------------------------------------------
|
|
98
98
|
|
|
99
|
-
export function
|
|
99
|
+
export function getField<
|
|
100
100
|
TObj extends Record<string, unknown>,
|
|
101
101
|
TField extends keyof TObj & string,
|
|
102
102
|
>(field: TField): TypedAction<TObj, TObj[TField]> {
|
|
@@ -104,23 +104,23 @@ export function extractField<
|
|
|
104
104
|
kind: "Invoke",
|
|
105
105
|
handler: {
|
|
106
106
|
kind: "Builtin",
|
|
107
|
-
builtin: { kind: "
|
|
107
|
+
builtin: { kind: "GetField", value: field },
|
|
108
108
|
},
|
|
109
109
|
});
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
// ---------------------------------------------------------------------------
|
|
113
|
-
//
|
|
113
|
+
// GetIndex — extract a single element from an array by index
|
|
114
114
|
// ---------------------------------------------------------------------------
|
|
115
115
|
|
|
116
|
-
export function
|
|
116
|
+
export function getIndex<TTuple extends unknown[], TIndex extends number>(
|
|
117
117
|
index: TIndex,
|
|
118
118
|
): TypedAction<TTuple, TTuple[TIndex]> {
|
|
119
119
|
return typedAction({
|
|
120
120
|
kind: "Invoke",
|
|
121
121
|
handler: {
|
|
122
122
|
kind: "Builtin",
|
|
123
|
-
builtin: { kind: "
|
|
123
|
+
builtin: { kind: "GetIndex", value: index },
|
|
124
124
|
},
|
|
125
125
|
});
|
|
126
126
|
}
|
|
@@ -216,25 +216,25 @@ export function withResource<
|
|
|
216
216
|
actions: [action as Action, identity as Action],
|
|
217
217
|
});
|
|
218
218
|
|
|
219
|
-
// Step 3: all(
|
|
219
|
+
// Step 3: all(getIndex(0), chain(getIndex(1), dispose)) → [TOut, unknown]
|
|
220
220
|
const disposeAndKeepResult = typedAction<
|
|
221
221
|
[TOut, TResource & TIn],
|
|
222
222
|
[TOut, unknown]
|
|
223
223
|
>({
|
|
224
224
|
kind: "All",
|
|
225
225
|
actions: [
|
|
226
|
-
|
|
226
|
+
getIndex<[TOut, TResource & TIn], 0>(0) as Action,
|
|
227
227
|
chain(
|
|
228
|
-
|
|
228
|
+
getIndex<[TOut, TResource & TIn], 1>(1),
|
|
229
229
|
dispose as Pipeable<TResource & TIn, unknown>,
|
|
230
230
|
) as Action,
|
|
231
231
|
],
|
|
232
232
|
});
|
|
233
233
|
|
|
234
|
-
// Step 4:
|
|
234
|
+
// Step 4: getIndex(0) → TOut
|
|
235
235
|
return chain(
|
|
236
236
|
chain(chain(acquireAndMerge, actionAndKeepMerged), disposeAndKeepResult),
|
|
237
|
-
|
|
237
|
+
getIndex<[TOut, unknown], 0>(0),
|
|
238
238
|
) as TypedAction<TIn, TOut>;
|
|
239
239
|
}
|
|
240
240
|
|
|
@@ -317,6 +317,55 @@ export function range(start: number, end: number): TypedAction<any, number[]> {
|
|
|
317
317
|
});
|
|
318
318
|
}
|
|
319
319
|
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
321
|
+
// SplitFirst — head/tail decomposition of an array
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Deconstruct an array into its first element and the remaining elements.
|
|
326
|
+
* `TElement[] → Option<[TElement, TElement[]]>`
|
|
327
|
+
*
|
|
328
|
+
* Returns `Some([first, rest])` for non-empty arrays, `None` for empty arrays.
|
|
329
|
+
* This is the array equivalent of cons/uncons — enables recursive iteration
|
|
330
|
+
* patterns via `loop` + `splitFirst` + `branch`.
|
|
331
|
+
*
|
|
332
|
+
* This is a builtin (SplitFirst) because it requires array-length branching
|
|
333
|
+
* that can't be composed from existing AST nodes.
|
|
334
|
+
*/
|
|
335
|
+
export function splitFirst<TElement>(): TypedAction<
|
|
336
|
+
TElement[],
|
|
337
|
+
OptionT<[TElement, TElement[]]>
|
|
338
|
+
> {
|
|
339
|
+
return typedAction({
|
|
340
|
+
kind: "Invoke",
|
|
341
|
+
handler: { kind: "Builtin", builtin: { kind: "SplitFirst" } },
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ---------------------------------------------------------------------------
|
|
346
|
+
// SplitLast — init/last decomposition of an array
|
|
347
|
+
// ---------------------------------------------------------------------------
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Deconstruct an array into the leading elements and the last element.
|
|
351
|
+
* `TElement[] → Option<[TElement[], TElement]>`
|
|
352
|
+
*
|
|
353
|
+
* Returns `Some([init, last])` for non-empty arrays, `None` for empty arrays.
|
|
354
|
+
* Mirror of `splitFirst` — enables processing from the tail end.
|
|
355
|
+
*
|
|
356
|
+
* This is a builtin (SplitLast) because it requires array-length branching
|
|
357
|
+
* that can't be composed from existing AST nodes.
|
|
358
|
+
*/
|
|
359
|
+
export function splitLast<TElement>(): TypedAction<
|
|
360
|
+
TElement[],
|
|
361
|
+
OptionT<[TElement[], TElement]>
|
|
362
|
+
> {
|
|
363
|
+
return typedAction({
|
|
364
|
+
kind: "Invoke",
|
|
365
|
+
handler: { kind: "Builtin", builtin: { kind: "SplitLast" } },
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
320
369
|
// ---------------------------------------------------------------------------
|
|
321
370
|
// Option namespace — combinators for Option<T> tagged unions
|
|
322
371
|
// ---------------------------------------------------------------------------
|
|
@@ -334,7 +383,7 @@ const EXTRACT_VALUE: Action = {
|
|
|
334
383
|
kind: "Invoke",
|
|
335
384
|
handler: {
|
|
336
385
|
kind: "Builtin",
|
|
337
|
-
builtin: { kind: "
|
|
386
|
+
builtin: { kind: "GetField", value: "value" },
|
|
338
387
|
},
|
|
339
388
|
};
|
|
340
389
|
const DROP: Action = {
|
|
@@ -346,7 +395,7 @@ const IDENTITY: Action = {
|
|
|
346
395
|
handler: { kind: "Builtin", builtin: { kind: "Identity" } },
|
|
347
396
|
};
|
|
348
397
|
|
|
349
|
-
/** Wrap branch cases with
|
|
398
|
+
/** Wrap branch cases with GetField("value") auto-unwrapping. */
|
|
350
399
|
function optionBranch(someCaseBody: Action, noneCaseBody: Action): Action {
|
|
351
400
|
return {
|
|
352
401
|
kind: "Branch",
|
|
@@ -541,7 +590,7 @@ const TAG_ERR: Action = {
|
|
|
541
590
|
handler: { kind: "Builtin", builtin: { kind: "Tag", value: "Err" } },
|
|
542
591
|
};
|
|
543
592
|
|
|
544
|
-
/** Wrap branch cases with
|
|
593
|
+
/** Wrap branch cases with GetField("value") auto-unwrapping. */
|
|
545
594
|
function resultBranch(okCaseBody: Action, errCaseBody: Action): Action {
|
|
546
595
|
return {
|
|
547
596
|
kind: "Branch",
|
package/src/index.ts
CHANGED
package/src/race.ts
CHANGED
|
@@ -53,7 +53,7 @@ function breakPerform(restartHandlerId: RestartHandlerId): Action {
|
|
|
53
53
|
*
|
|
54
54
|
* Compiled form (restart+Branch, same substrate as loop/earlyReturn):
|
|
55
55
|
* `Chain(Tag("Continue"),`
|
|
56
|
-
* `RestartHandle(id,
|
|
56
|
+
* `RestartHandle(id, GetIndex(0),`
|
|
57
57
|
* `Branch({`
|
|
58
58
|
* `Continue: All(Chain(a, breakPerform), Chain(b, breakPerform), ...),`
|
|
59
59
|
* `Break: identity,`
|
package/src/recursive.ts
CHANGED
|
@@ -7,13 +7,7 @@ import {
|
|
|
7
7
|
} from "./ast.js";
|
|
8
8
|
import { all } from "./all.js";
|
|
9
9
|
import { chain } from "./chain.js";
|
|
10
|
-
import {
|
|
11
|
-
constant,
|
|
12
|
-
identity,
|
|
13
|
-
extractField,
|
|
14
|
-
extractIndex,
|
|
15
|
-
tag,
|
|
16
|
-
} from "./builtins.js";
|
|
10
|
+
import { constant, identity, getField, getIndex, tag } from "./builtins.js";
|
|
17
11
|
import { allocateResumeHandlerId } from "./effect-id.js";
|
|
18
12
|
|
|
19
13
|
// ---------------------------------------------------------------------------
|
|
@@ -84,11 +78,11 @@ export function defineRecursiveFunctions<TDefs extends FunctionDef[]>(
|
|
|
84
78
|
...(callTokens as FunctionRefs<TDefs>),
|
|
85
79
|
) as Action[];
|
|
86
80
|
|
|
87
|
-
// Branch cases: CallN →
|
|
81
|
+
// Branch cases: CallN → GetField("value") → bodyN
|
|
88
82
|
const cases: Record<string, Action> = {};
|
|
89
83
|
for (let i = 0; i < bodyActions.length; i++) {
|
|
90
84
|
cases[`Call${i}`] = chain(
|
|
91
|
-
|
|
85
|
+
getField("value"),
|
|
92
86
|
bodyActions[i] as any,
|
|
93
87
|
) as Action;
|
|
94
88
|
}
|
|
@@ -101,9 +95,9 @@ export function defineRecursiveFunctions<TDefs extends FunctionDef[]>(
|
|
|
101
95
|
chain(all(identity, constant(UNUSED_STATE)), {
|
|
102
96
|
kind: "ResumeHandle",
|
|
103
97
|
resume_handler_id: resumeHandlerId,
|
|
104
|
-
body: chain(
|
|
98
|
+
body: chain(getIndex(0), userBody as any) as Action,
|
|
105
99
|
handler: all(
|
|
106
|
-
chain(
|
|
100
|
+
chain(getIndex(0), branch(cases) as any),
|
|
107
101
|
constant(UNUSED_STATE),
|
|
108
102
|
) as Action,
|
|
109
103
|
} as Action) as Action,
|
package/src/try-catch.ts
CHANGED
|
@@ -25,7 +25,7 @@ import { allocateRestartHandlerId } from "./effect-id.js";
|
|
|
25
25
|
*
|
|
26
26
|
* Compiled form (restart+Branch, same substrate as loop/earlyReturn):
|
|
27
27
|
* `Chain(Tag("Continue"),`
|
|
28
|
-
* `RestartHandle(id,
|
|
28
|
+
* `RestartHandle(id, GetIndex(0),`
|
|
29
29
|
* `Branch({ Continue: body, Break: recovery })))`
|
|
30
30
|
*
|
|
31
31
|
* throwError = `Chain(Tag("Break"), RestartPerform(id))`
|