@optique/core 1.0.0-dev.1711 → 1.0.0-dev.1712
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/constructs.cjs +196 -24
- package/dist/constructs.d.cts +1 -0
- package/dist/constructs.d.ts +1 -0
- package/dist/constructs.js +196 -24
- package/package.json +1 -1
package/dist/constructs.cjs
CHANGED
|
@@ -712,9 +712,23 @@ function or(...args) {
|
|
|
712
712
|
orderedParsers.sort(([_, a], [__, b]) => activeState?.[0] === a ? -1 : activeState?.[0] === b ? 1 : a - b);
|
|
713
713
|
let zeroConsumedBranch = null;
|
|
714
714
|
let zeroConsumedCount = 0;
|
|
715
|
+
let provisionalConsuming = null;
|
|
716
|
+
let provisionalAmbiguous = false;
|
|
715
717
|
for (const [parser, i] of orderedParsers) {
|
|
716
718
|
const result = parser.parse(withChildContext(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
|
|
717
719
|
if (result.success && result.consumed.length > 0) {
|
|
720
|
+
if (result.provisional) {
|
|
721
|
+
if (provisionalConsuming == null && !provisionalAmbiguous) provisionalConsuming = {
|
|
722
|
+
index: i,
|
|
723
|
+
parser,
|
|
724
|
+
result
|
|
725
|
+
};
|
|
726
|
+
else {
|
|
727
|
+
provisionalConsuming = null;
|
|
728
|
+
provisionalAmbiguous = true;
|
|
729
|
+
}
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
718
732
|
if (activeState?.[0] !== i && activeState?.[1].success) {
|
|
719
733
|
if (activeState[1].consumed.length === 0) {
|
|
720
734
|
const mergedExec$2 = mergeChildExec(context.exec, result.next.exec);
|
|
@@ -812,6 +826,24 @@ function or(...args) {
|
|
|
812
826
|
consumed: []
|
|
813
827
|
};
|
|
814
828
|
}
|
|
829
|
+
if (provisionalConsuming !== null && !(activeState != null && activeState[1].success && activeState[1].consumed.length > 0 && activeState[0] !== provisionalConsuming.index)) {
|
|
830
|
+
const mergedExec = mergeChildExec(context.exec, provisionalConsuming.result.next.exec);
|
|
831
|
+
return {
|
|
832
|
+
success: true,
|
|
833
|
+
provisional: true,
|
|
834
|
+
next: {
|
|
835
|
+
...context,
|
|
836
|
+
buffer: provisionalConsuming.result.next.buffer,
|
|
837
|
+
optionsTerminated: provisionalConsuming.result.next.optionsTerminated,
|
|
838
|
+
state: createExclusiveState(context.state, provisionalConsuming.index, provisionalConsuming.parser, provisionalConsuming.result),
|
|
839
|
+
...mergedExec != null ? {
|
|
840
|
+
exec: mergedExec,
|
|
841
|
+
dependencyRegistry: mergedExec.dependencyRegistry
|
|
842
|
+
} : {}
|
|
843
|
+
},
|
|
844
|
+
consumed: provisionalConsuming.result.consumed
|
|
845
|
+
};
|
|
846
|
+
}
|
|
815
847
|
return {
|
|
816
848
|
...error,
|
|
817
849
|
success: false
|
|
@@ -824,10 +856,24 @@ function or(...args) {
|
|
|
824
856
|
orderedParsers.sort(([_, a], [__, b]) => activeState?.[0] === a ? -1 : activeState?.[0] === b ? 1 : a - b);
|
|
825
857
|
let zeroConsumedBranch = null;
|
|
826
858
|
let zeroConsumedCount = 0;
|
|
859
|
+
let provisionalConsuming = null;
|
|
860
|
+
let provisionalAmbiguous = false;
|
|
827
861
|
for (const [parser, i] of orderedParsers) {
|
|
828
862
|
const resultOrPromise = parser.parse(withChildContext(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
|
|
829
863
|
const result = await resultOrPromise;
|
|
830
864
|
if (result.success && result.consumed.length > 0) {
|
|
865
|
+
if (result.provisional) {
|
|
866
|
+
if (provisionalConsuming == null && !provisionalAmbiguous) provisionalConsuming = {
|
|
867
|
+
index: i,
|
|
868
|
+
parser,
|
|
869
|
+
result
|
|
870
|
+
};
|
|
871
|
+
else {
|
|
872
|
+
provisionalConsuming = null;
|
|
873
|
+
provisionalAmbiguous = true;
|
|
874
|
+
}
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
831
877
|
if (activeState?.[0] !== i && activeState?.[1].success) {
|
|
832
878
|
if (activeState[1].consumed.length === 0) {
|
|
833
879
|
const mergedExec$2 = mergeChildExec(context.exec, result.next.exec);
|
|
@@ -927,6 +973,24 @@ function or(...args) {
|
|
|
927
973
|
consumed: []
|
|
928
974
|
};
|
|
929
975
|
}
|
|
976
|
+
if (provisionalConsuming !== null && !(activeState != null && activeState[1].success && activeState[1].consumed.length > 0 && activeState[0] !== provisionalConsuming.index)) {
|
|
977
|
+
const mergedExec = mergeChildExec(context.exec, provisionalConsuming.result.next.exec);
|
|
978
|
+
return {
|
|
979
|
+
success: true,
|
|
980
|
+
provisional: true,
|
|
981
|
+
next: {
|
|
982
|
+
...context,
|
|
983
|
+
buffer: provisionalConsuming.result.next.buffer,
|
|
984
|
+
optionsTerminated: provisionalConsuming.result.next.optionsTerminated,
|
|
985
|
+
state: createExclusiveState(context.state, provisionalConsuming.index, provisionalConsuming.parser, provisionalConsuming.result),
|
|
986
|
+
...mergedExec != null ? {
|
|
987
|
+
exec: mergedExec,
|
|
988
|
+
dependencyRegistry: mergedExec.dependencyRegistry
|
|
989
|
+
} : {}
|
|
990
|
+
},
|
|
991
|
+
consumed: provisionalConsuming.result.consumed
|
|
992
|
+
};
|
|
993
|
+
}
|
|
930
994
|
return {
|
|
931
995
|
...error,
|
|
932
996
|
success: false
|
|
@@ -1020,7 +1084,8 @@ function longestMatch(...args) {
|
|
|
1020
1084
|
const result = parser.parse(withChildContext(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
|
|
1021
1085
|
if (result.success) {
|
|
1022
1086
|
const consumed = context.buffer.length - result.next.buffer.length;
|
|
1023
|
-
|
|
1087
|
+
const bestIsProvisional = bestMatch != null && bestMatch.result.success && !!bestMatch.result.provisional;
|
|
1088
|
+
if (bestMatch === null || consumed > bestMatch.consumed || consumed === bestMatch.consumed && bestIsProvisional && !result.provisional) bestMatch = {
|
|
1024
1089
|
index: i,
|
|
1025
1090
|
parser,
|
|
1026
1091
|
result,
|
|
@@ -1060,7 +1125,8 @@ function longestMatch(...args) {
|
|
|
1060
1125
|
const result = await resultOrPromise;
|
|
1061
1126
|
if (result.success) {
|
|
1062
1127
|
const consumed = context.buffer.length - result.next.buffer.length;
|
|
1063
|
-
|
|
1128
|
+
const bestIsProvisional = bestMatch != null && bestMatch.result.success && !!bestMatch.result.provisional;
|
|
1129
|
+
if (bestMatch === null || consumed > bestMatch.consumed || consumed === bestMatch.consumed && bestIsProvisional && !result.provisional) bestMatch = {
|
|
1064
1130
|
index: i,
|
|
1065
1131
|
parser,
|
|
1066
1132
|
result,
|
|
@@ -3656,18 +3722,35 @@ function group(label, parser, options = {}) {
|
|
|
3656
3722
|
* // defaultResult.value = [undefined, {}]
|
|
3657
3723
|
* ```
|
|
3658
3724
|
*
|
|
3659
|
-
* ###
|
|
3725
|
+
* ### Speculative branch parsing
|
|
3660
3726
|
*
|
|
3661
3727
|
* When the discriminator is an async parser that succeeds without consuming
|
|
3662
3728
|
* input (e.g., `prompt(option(...))` with no CLI input), branch selection
|
|
3663
|
-
* is deferred to the complete phase.
|
|
3664
|
-
*
|
|
3665
|
-
*
|
|
3666
|
-
*
|
|
3667
|
-
*
|
|
3668
|
-
*
|
|
3669
|
-
*
|
|
3670
|
-
*
|
|
3729
|
+
* is normally deferred to the complete phase. To allow branch-specific
|
|
3730
|
+
* tokens to be consumed, `conditional()` speculatively tries all named
|
|
3731
|
+
* branches during parse. If exactly one branch can consume tokens, it is
|
|
3732
|
+
* tentatively selected and verified against the resolved discriminator
|
|
3733
|
+
* during the complete phase.
|
|
3734
|
+
*
|
|
3735
|
+
* If the discriminator resolves to a different branch than the one that
|
|
3736
|
+
* consumed tokens (contradictory input), the parse fails. When multiple
|
|
3737
|
+
* branches can consume the same tokens (ambiguous), speculation is skipped
|
|
3738
|
+
* entirely to keep branch selection order-independent.
|
|
3739
|
+
*
|
|
3740
|
+
* #### Known limitations
|
|
3741
|
+
*
|
|
3742
|
+
* - When a default branch accepts the same tokens as a named branch,
|
|
3743
|
+
* speculation prefers the named branch. If the discriminator later
|
|
3744
|
+
* resolves to a value not in the named branches, the parse fails
|
|
3745
|
+
* instead of falling back to the default branch. To avoid this,
|
|
3746
|
+
* ensure named branch options are distinct from the default branch.
|
|
3747
|
+
* - Within `longestMatch()`, a longer speculative match can beat a
|
|
3748
|
+
* shorter definitive one. If the speculative match fails during
|
|
3749
|
+
* completion, the tokens consumed by it are not recoverable.
|
|
3750
|
+
* - The dependency runtime seeds both discriminator and branch sources
|
|
3751
|
+
* before verifying the speculative selection. A discriminator that
|
|
3752
|
+
* depends on branch-local dependency sources could be circularly
|
|
3753
|
+
* confirmed by the speculative branch.
|
|
3671
3754
|
*
|
|
3672
3755
|
* @since 0.8.0
|
|
3673
3756
|
*/
|
|
@@ -3880,6 +3963,76 @@ function conditional(discriminator, branches, defaultBranch, options) {
|
|
|
3880
3963
|
const discriminatorResult = await discriminator.parse({ ...withChildContext(context, "_discriminator", state.discriminatorState, discriminator) });
|
|
3881
3964
|
if (discriminatorResult.success) {
|
|
3882
3965
|
if (discriminatorResult.consumed.length === 0 && discriminator.$mode === "async") {
|
|
3966
|
+
const discriminatorExec = mergeChildExec(context.exec, discriminatorResult.next.exec);
|
|
3967
|
+
let speculativeHit;
|
|
3968
|
+
let provisionalHit;
|
|
3969
|
+
let provisionalAmbiguous = false;
|
|
3970
|
+
let speculativeError;
|
|
3971
|
+
let ambiguous = false;
|
|
3972
|
+
for (const [key, bp] of branchParsers) {
|
|
3973
|
+
const branchResult = await bp.parse(withChildContext({
|
|
3974
|
+
...context,
|
|
3975
|
+
...discriminatorExec != null ? {
|
|
3976
|
+
exec: discriminatorExec,
|
|
3977
|
+
dependencyRegistry: discriminatorExec.dependencyRegistry
|
|
3978
|
+
} : {}
|
|
3979
|
+
}, "_branch", bp.initialState, bp, bp.usage));
|
|
3980
|
+
if (branchResult.success && branchResult.consumed.length > 0) {
|
|
3981
|
+
if (branchResult.provisional) {
|
|
3982
|
+
if (provisionalHit == null && !provisionalAmbiguous) provisionalHit = {
|
|
3983
|
+
key,
|
|
3984
|
+
bp,
|
|
3985
|
+
result: branchResult
|
|
3986
|
+
};
|
|
3987
|
+
else {
|
|
3988
|
+
provisionalHit = void 0;
|
|
3989
|
+
provisionalAmbiguous = true;
|
|
3990
|
+
}
|
|
3991
|
+
continue;
|
|
3992
|
+
}
|
|
3993
|
+
if (speculativeHit != null) {
|
|
3994
|
+
ambiguous = true;
|
|
3995
|
+
break;
|
|
3996
|
+
}
|
|
3997
|
+
speculativeHit = {
|
|
3998
|
+
key,
|
|
3999
|
+
bp,
|
|
4000
|
+
result: branchResult
|
|
4001
|
+
};
|
|
4002
|
+
}
|
|
4003
|
+
if (!branchResult.success && branchResult.consumed > 0 && (speculativeError == null || speculativeError.consumed < branchResult.consumed)) speculativeError = branchResult;
|
|
4004
|
+
}
|
|
4005
|
+
if (speculativeHit != null && (provisionalHit != null || provisionalAmbiguous)) ambiguous = true;
|
|
4006
|
+
if (speculativeHit == null && !ambiguous && !provisionalAmbiguous && provisionalHit != null) speculativeHit = provisionalHit;
|
|
4007
|
+
if (speculativeHit != null && !ambiguous) {
|
|
4008
|
+
const { key, bp, result: branchResult } = speculativeHit;
|
|
4009
|
+
if (branchResult.success) {
|
|
4010
|
+
const annotatedDiscriminatorState$2 = getAnnotatedChildState(state, discriminatorResult.next.state, discriminator);
|
|
4011
|
+
const mergedExec = mergeChildExec(discriminatorExec, branchResult.next.exec);
|
|
4012
|
+
return {
|
|
4013
|
+
success: true,
|
|
4014
|
+
provisional: true,
|
|
4015
|
+
next: {
|
|
4016
|
+
...branchResult.next,
|
|
4017
|
+
state: {
|
|
4018
|
+
...state,
|
|
4019
|
+
discriminatorState: annotatedDiscriminatorState$2,
|
|
4020
|
+
selectedBranch: {
|
|
4021
|
+
kind: "branch",
|
|
4022
|
+
key
|
|
4023
|
+
},
|
|
4024
|
+
branchState: getAnnotatedChildState(state, branchResult.next.state, bp),
|
|
4025
|
+
speculative: true
|
|
4026
|
+
},
|
|
4027
|
+
...mergedExec != null ? {
|
|
4028
|
+
exec: mergedExec,
|
|
4029
|
+
dependencyRegistry: mergedExec.dependencyRegistry
|
|
4030
|
+
} : {}
|
|
4031
|
+
},
|
|
4032
|
+
consumed: branchResult.consumed
|
|
4033
|
+
};
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
3883
4036
|
let deferredBranchState = state.branchState;
|
|
3884
4037
|
if (defaultBranch !== void 0) {
|
|
3885
4038
|
const defaultResult = await defaultBranch.parse(withChildContext(context, "_branch", state.branchState ?? defaultBranch.initialState, defaultBranch, defaultBranch.usage));
|
|
@@ -3887,6 +4040,7 @@ function conditional(discriminator, branches, defaultBranch, options) {
|
|
|
3887
4040
|
const defaultExec = mergeChildExec(context.exec, defaultResult.next.exec);
|
|
3888
4041
|
return {
|
|
3889
4042
|
success: true,
|
|
4043
|
+
...defaultResult.provisional ? { provisional: true } : {},
|
|
3890
4044
|
next: {
|
|
3891
4045
|
...defaultResult.next,
|
|
3892
4046
|
state: {
|
|
@@ -3905,8 +4059,8 @@ function conditional(discriminator, branches, defaultBranch, options) {
|
|
|
3905
4059
|
if (!defaultResult.success && defaultResult.consumed > 0) return defaultResult;
|
|
3906
4060
|
if (defaultResult.success && defaultResult.consumed.length === 0 && context.buffer.length === 0) deferredBranchState = getAnnotatedChildState(state, defaultResult.next.state, defaultBranch);
|
|
3907
4061
|
}
|
|
4062
|
+
if (speculativeError != null && !ambiguous) return speculativeError;
|
|
3908
4063
|
const annotatedDiscriminatorState$1 = getAnnotatedChildState(state, discriminatorResult.next.state, discriminator);
|
|
3909
|
-
const mergedExec = mergeChildExec(context.exec, discriminatorResult.next.exec);
|
|
3910
4064
|
return {
|
|
3911
4065
|
success: true,
|
|
3912
4066
|
provisional: true,
|
|
@@ -3917,9 +4071,9 @@ function conditional(discriminator, branches, defaultBranch, options) {
|
|
|
3917
4071
|
discriminatorState: annotatedDiscriminatorState$1,
|
|
3918
4072
|
branchState: deferredBranchState
|
|
3919
4073
|
},
|
|
3920
|
-
...
|
|
3921
|
-
exec:
|
|
3922
|
-
dependencyRegistry:
|
|
4074
|
+
...discriminatorExec != null ? {
|
|
4075
|
+
exec: discriminatorExec,
|
|
4076
|
+
dependencyRegistry: discriminatorExec.dependencyRegistry
|
|
3923
4077
|
} : {}
|
|
3924
4078
|
},
|
|
3925
4079
|
consumed: []
|
|
@@ -4141,6 +4295,18 @@ function conditional(discriminator, branches, defaultBranch, options) {
|
|
|
4141
4295
|
};
|
|
4142
4296
|
};
|
|
4143
4297
|
const completeAsync = async (state, exec) => {
|
|
4298
|
+
let wasSpeculative = false;
|
|
4299
|
+
if (state.speculative && state.selectedBranch?.kind === "branch") if (exec?.phase !== "parse" && exec?.phase !== "suggest") {
|
|
4300
|
+
wasSpeculative = true;
|
|
4301
|
+
state = {
|
|
4302
|
+
...state,
|
|
4303
|
+
speculative: void 0
|
|
4304
|
+
};
|
|
4305
|
+
} else state = {
|
|
4306
|
+
...state,
|
|
4307
|
+
discriminatorValue: state.selectedBranch.key,
|
|
4308
|
+
speculative: void 0
|
|
4309
|
+
};
|
|
4144
4310
|
if (state.selectedBranch === void 0) {
|
|
4145
4311
|
if (exec?.phase !== "parse" && exec?.phase !== "suggest") {
|
|
4146
4312
|
const annotatedDiscriminatorStateForDeferred = getAnnotatedChildState(state, state.discriminatorState, discriminator);
|
|
@@ -4225,20 +4391,26 @@ function conditional(discriminator, branches, defaultBranch, options) {
|
|
|
4225
4391
|
};
|
|
4226
4392
|
const needsDiscriminatorCompletion = state.selectedBranch.kind !== "default" && !(state.discriminatorValue != null && state.discriminatorValue === state.selectedBranch.key);
|
|
4227
4393
|
const discriminatorCompleteResult = needsDiscriminatorCompletion ? await discriminator.complete(annotatedDiscriminatorState, withChildExecPath(completionExec, "_discriminator")) : void 0;
|
|
4228
|
-
const branchResult = unwrapCompleteResult(await branchParser.complete(resolvedBranchState, withChildExecPath(completionExec, "_branch")));
|
|
4229
|
-
if (!branchResult.success) {
|
|
4230
|
-
if (state.discriminatorValue !== void 0 && options?.errors?.branchError) return {
|
|
4231
|
-
success: false,
|
|
4232
|
-
error: options.errors.branchError(state.discriminatorValue, branchResult.error)
|
|
4233
|
-
};
|
|
4234
|
-
return branchResult;
|
|
4235
|
-
}
|
|
4236
4394
|
let discriminatorValue;
|
|
4237
4395
|
if (state.selectedBranch.kind === "default") discriminatorValue = void 0;
|
|
4238
4396
|
else if (state.discriminatorValue != null && state.discriminatorValue === state.selectedBranch.key) discriminatorValue = state.discriminatorValue;
|
|
4239
4397
|
else {
|
|
4240
4398
|
const completedDiscriminator = unwrapCompleteResult(discriminatorCompleteResult);
|
|
4241
|
-
|
|
4399
|
+
if (completedDiscriminator.success) discriminatorValue = completedDiscriminator.value;
|
|
4400
|
+
else if (wasSpeculative) return completedDiscriminator;
|
|
4401
|
+
else discriminatorValue = state.selectedBranch.key;
|
|
4402
|
+
}
|
|
4403
|
+
if (wasSpeculative && state.selectedBranch.kind === "branch" && discriminatorValue !== state.selectedBranch.key) return {
|
|
4404
|
+
success: false,
|
|
4405
|
+
error: getNoMatchError$1()
|
|
4406
|
+
};
|
|
4407
|
+
const branchResult = unwrapCompleteResult(await branchParser.complete(resolvedBranchState, withChildExecPath(completionExec, "_branch")));
|
|
4408
|
+
if (!branchResult.success) {
|
|
4409
|
+
if (discriminatorValue !== void 0 && options?.errors?.branchError) return {
|
|
4410
|
+
success: false,
|
|
4411
|
+
error: options.errors.branchError(discriminatorValue, branchResult.error)
|
|
4412
|
+
};
|
|
4413
|
+
return branchResult;
|
|
4242
4414
|
}
|
|
4243
4415
|
return {
|
|
4244
4416
|
success: true,
|
package/dist/constructs.d.cts
CHANGED
|
@@ -1316,6 +1316,7 @@ interface ConditionalState<TDiscriminator extends string> {
|
|
|
1316
1316
|
readonly discriminatorValue: TDiscriminator | undefined;
|
|
1317
1317
|
readonly selectedBranch: SelectedBranch<TDiscriminator> | undefined;
|
|
1318
1318
|
readonly branchState: unknown;
|
|
1319
|
+
readonly speculative?: true;
|
|
1319
1320
|
}
|
|
1320
1321
|
/**
|
|
1321
1322
|
* Options for customizing error messages in the {@link conditional} combinator.
|
package/dist/constructs.d.ts
CHANGED
|
@@ -1316,6 +1316,7 @@ interface ConditionalState<TDiscriminator extends string> {
|
|
|
1316
1316
|
readonly discriminatorValue: TDiscriminator | undefined;
|
|
1317
1317
|
readonly selectedBranch: SelectedBranch<TDiscriminator> | undefined;
|
|
1318
1318
|
readonly branchState: unknown;
|
|
1319
|
+
readonly speculative?: true;
|
|
1319
1320
|
}
|
|
1320
1321
|
/**
|
|
1321
1322
|
* Options for customizing error messages in the {@link conditional} combinator.
|
package/dist/constructs.js
CHANGED
|
@@ -712,9 +712,23 @@ function or(...args) {
|
|
|
712
712
|
orderedParsers.sort(([_, a], [__, b]) => activeState?.[0] === a ? -1 : activeState?.[0] === b ? 1 : a - b);
|
|
713
713
|
let zeroConsumedBranch = null;
|
|
714
714
|
let zeroConsumedCount = 0;
|
|
715
|
+
let provisionalConsuming = null;
|
|
716
|
+
let provisionalAmbiguous = false;
|
|
715
717
|
for (const [parser, i] of orderedParsers) {
|
|
716
718
|
const result = parser.parse(withChildContext(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
|
|
717
719
|
if (result.success && result.consumed.length > 0) {
|
|
720
|
+
if (result.provisional) {
|
|
721
|
+
if (provisionalConsuming == null && !provisionalAmbiguous) provisionalConsuming = {
|
|
722
|
+
index: i,
|
|
723
|
+
parser,
|
|
724
|
+
result
|
|
725
|
+
};
|
|
726
|
+
else {
|
|
727
|
+
provisionalConsuming = null;
|
|
728
|
+
provisionalAmbiguous = true;
|
|
729
|
+
}
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
718
732
|
if (activeState?.[0] !== i && activeState?.[1].success) {
|
|
719
733
|
if (activeState[1].consumed.length === 0) {
|
|
720
734
|
const mergedExec$2 = mergeChildExec(context.exec, result.next.exec);
|
|
@@ -812,6 +826,24 @@ function or(...args) {
|
|
|
812
826
|
consumed: []
|
|
813
827
|
};
|
|
814
828
|
}
|
|
829
|
+
if (provisionalConsuming !== null && !(activeState != null && activeState[1].success && activeState[1].consumed.length > 0 && activeState[0] !== provisionalConsuming.index)) {
|
|
830
|
+
const mergedExec = mergeChildExec(context.exec, provisionalConsuming.result.next.exec);
|
|
831
|
+
return {
|
|
832
|
+
success: true,
|
|
833
|
+
provisional: true,
|
|
834
|
+
next: {
|
|
835
|
+
...context,
|
|
836
|
+
buffer: provisionalConsuming.result.next.buffer,
|
|
837
|
+
optionsTerminated: provisionalConsuming.result.next.optionsTerminated,
|
|
838
|
+
state: createExclusiveState(context.state, provisionalConsuming.index, provisionalConsuming.parser, provisionalConsuming.result),
|
|
839
|
+
...mergedExec != null ? {
|
|
840
|
+
exec: mergedExec,
|
|
841
|
+
dependencyRegistry: mergedExec.dependencyRegistry
|
|
842
|
+
} : {}
|
|
843
|
+
},
|
|
844
|
+
consumed: provisionalConsuming.result.consumed
|
|
845
|
+
};
|
|
846
|
+
}
|
|
815
847
|
return {
|
|
816
848
|
...error,
|
|
817
849
|
success: false
|
|
@@ -824,10 +856,24 @@ function or(...args) {
|
|
|
824
856
|
orderedParsers.sort(([_, a], [__, b]) => activeState?.[0] === a ? -1 : activeState?.[0] === b ? 1 : a - b);
|
|
825
857
|
let zeroConsumedBranch = null;
|
|
826
858
|
let zeroConsumedCount = 0;
|
|
859
|
+
let provisionalConsuming = null;
|
|
860
|
+
let provisionalAmbiguous = false;
|
|
827
861
|
for (const [parser, i] of orderedParsers) {
|
|
828
862
|
const resultOrPromise = parser.parse(withChildContext(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
|
|
829
863
|
const result = await resultOrPromise;
|
|
830
864
|
if (result.success && result.consumed.length > 0) {
|
|
865
|
+
if (result.provisional) {
|
|
866
|
+
if (provisionalConsuming == null && !provisionalAmbiguous) provisionalConsuming = {
|
|
867
|
+
index: i,
|
|
868
|
+
parser,
|
|
869
|
+
result
|
|
870
|
+
};
|
|
871
|
+
else {
|
|
872
|
+
provisionalConsuming = null;
|
|
873
|
+
provisionalAmbiguous = true;
|
|
874
|
+
}
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
831
877
|
if (activeState?.[0] !== i && activeState?.[1].success) {
|
|
832
878
|
if (activeState[1].consumed.length === 0) {
|
|
833
879
|
const mergedExec$2 = mergeChildExec(context.exec, result.next.exec);
|
|
@@ -927,6 +973,24 @@ function or(...args) {
|
|
|
927
973
|
consumed: []
|
|
928
974
|
};
|
|
929
975
|
}
|
|
976
|
+
if (provisionalConsuming !== null && !(activeState != null && activeState[1].success && activeState[1].consumed.length > 0 && activeState[0] !== provisionalConsuming.index)) {
|
|
977
|
+
const mergedExec = mergeChildExec(context.exec, provisionalConsuming.result.next.exec);
|
|
978
|
+
return {
|
|
979
|
+
success: true,
|
|
980
|
+
provisional: true,
|
|
981
|
+
next: {
|
|
982
|
+
...context,
|
|
983
|
+
buffer: provisionalConsuming.result.next.buffer,
|
|
984
|
+
optionsTerminated: provisionalConsuming.result.next.optionsTerminated,
|
|
985
|
+
state: createExclusiveState(context.state, provisionalConsuming.index, provisionalConsuming.parser, provisionalConsuming.result),
|
|
986
|
+
...mergedExec != null ? {
|
|
987
|
+
exec: mergedExec,
|
|
988
|
+
dependencyRegistry: mergedExec.dependencyRegistry
|
|
989
|
+
} : {}
|
|
990
|
+
},
|
|
991
|
+
consumed: provisionalConsuming.result.consumed
|
|
992
|
+
};
|
|
993
|
+
}
|
|
930
994
|
return {
|
|
931
995
|
...error,
|
|
932
996
|
success: false
|
|
@@ -1020,7 +1084,8 @@ function longestMatch(...args) {
|
|
|
1020
1084
|
const result = parser.parse(withChildContext(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
|
|
1021
1085
|
if (result.success) {
|
|
1022
1086
|
const consumed = context.buffer.length - result.next.buffer.length;
|
|
1023
|
-
|
|
1087
|
+
const bestIsProvisional = bestMatch != null && bestMatch.result.success && !!bestMatch.result.provisional;
|
|
1088
|
+
if (bestMatch === null || consumed > bestMatch.consumed || consumed === bestMatch.consumed && bestIsProvisional && !result.provisional) bestMatch = {
|
|
1024
1089
|
index: i,
|
|
1025
1090
|
parser,
|
|
1026
1091
|
result,
|
|
@@ -1060,7 +1125,8 @@ function longestMatch(...args) {
|
|
|
1060
1125
|
const result = await resultOrPromise;
|
|
1061
1126
|
if (result.success) {
|
|
1062
1127
|
const consumed = context.buffer.length - result.next.buffer.length;
|
|
1063
|
-
|
|
1128
|
+
const bestIsProvisional = bestMatch != null && bestMatch.result.success && !!bestMatch.result.provisional;
|
|
1129
|
+
if (bestMatch === null || consumed > bestMatch.consumed || consumed === bestMatch.consumed && bestIsProvisional && !result.provisional) bestMatch = {
|
|
1064
1130
|
index: i,
|
|
1065
1131
|
parser,
|
|
1066
1132
|
result,
|
|
@@ -3656,18 +3722,35 @@ function group(label, parser, options = {}) {
|
|
|
3656
3722
|
* // defaultResult.value = [undefined, {}]
|
|
3657
3723
|
* ```
|
|
3658
3724
|
*
|
|
3659
|
-
* ###
|
|
3725
|
+
* ### Speculative branch parsing
|
|
3660
3726
|
*
|
|
3661
3727
|
* When the discriminator is an async parser that succeeds without consuming
|
|
3662
3728
|
* input (e.g., `prompt(option(...))` with no CLI input), branch selection
|
|
3663
|
-
* is deferred to the complete phase.
|
|
3664
|
-
*
|
|
3665
|
-
*
|
|
3666
|
-
*
|
|
3667
|
-
*
|
|
3668
|
-
*
|
|
3669
|
-
*
|
|
3670
|
-
*
|
|
3729
|
+
* is normally deferred to the complete phase. To allow branch-specific
|
|
3730
|
+
* tokens to be consumed, `conditional()` speculatively tries all named
|
|
3731
|
+
* branches during parse. If exactly one branch can consume tokens, it is
|
|
3732
|
+
* tentatively selected and verified against the resolved discriminator
|
|
3733
|
+
* during the complete phase.
|
|
3734
|
+
*
|
|
3735
|
+
* If the discriminator resolves to a different branch than the one that
|
|
3736
|
+
* consumed tokens (contradictory input), the parse fails. When multiple
|
|
3737
|
+
* branches can consume the same tokens (ambiguous), speculation is skipped
|
|
3738
|
+
* entirely to keep branch selection order-independent.
|
|
3739
|
+
*
|
|
3740
|
+
* #### Known limitations
|
|
3741
|
+
*
|
|
3742
|
+
* - When a default branch accepts the same tokens as a named branch,
|
|
3743
|
+
* speculation prefers the named branch. If the discriminator later
|
|
3744
|
+
* resolves to a value not in the named branches, the parse fails
|
|
3745
|
+
* instead of falling back to the default branch. To avoid this,
|
|
3746
|
+
* ensure named branch options are distinct from the default branch.
|
|
3747
|
+
* - Within `longestMatch()`, a longer speculative match can beat a
|
|
3748
|
+
* shorter definitive one. If the speculative match fails during
|
|
3749
|
+
* completion, the tokens consumed by it are not recoverable.
|
|
3750
|
+
* - The dependency runtime seeds both discriminator and branch sources
|
|
3751
|
+
* before verifying the speculative selection. A discriminator that
|
|
3752
|
+
* depends on branch-local dependency sources could be circularly
|
|
3753
|
+
* confirmed by the speculative branch.
|
|
3671
3754
|
*
|
|
3672
3755
|
* @since 0.8.0
|
|
3673
3756
|
*/
|
|
@@ -3880,6 +3963,76 @@ function conditional(discriminator, branches, defaultBranch, options) {
|
|
|
3880
3963
|
const discriminatorResult = await discriminator.parse({ ...withChildContext(context, "_discriminator", state.discriminatorState, discriminator) });
|
|
3881
3964
|
if (discriminatorResult.success) {
|
|
3882
3965
|
if (discriminatorResult.consumed.length === 0 && discriminator.$mode === "async") {
|
|
3966
|
+
const discriminatorExec = mergeChildExec(context.exec, discriminatorResult.next.exec);
|
|
3967
|
+
let speculativeHit;
|
|
3968
|
+
let provisionalHit;
|
|
3969
|
+
let provisionalAmbiguous = false;
|
|
3970
|
+
let speculativeError;
|
|
3971
|
+
let ambiguous = false;
|
|
3972
|
+
for (const [key, bp] of branchParsers) {
|
|
3973
|
+
const branchResult = await bp.parse(withChildContext({
|
|
3974
|
+
...context,
|
|
3975
|
+
...discriminatorExec != null ? {
|
|
3976
|
+
exec: discriminatorExec,
|
|
3977
|
+
dependencyRegistry: discriminatorExec.dependencyRegistry
|
|
3978
|
+
} : {}
|
|
3979
|
+
}, "_branch", bp.initialState, bp, bp.usage));
|
|
3980
|
+
if (branchResult.success && branchResult.consumed.length > 0) {
|
|
3981
|
+
if (branchResult.provisional) {
|
|
3982
|
+
if (provisionalHit == null && !provisionalAmbiguous) provisionalHit = {
|
|
3983
|
+
key,
|
|
3984
|
+
bp,
|
|
3985
|
+
result: branchResult
|
|
3986
|
+
};
|
|
3987
|
+
else {
|
|
3988
|
+
provisionalHit = void 0;
|
|
3989
|
+
provisionalAmbiguous = true;
|
|
3990
|
+
}
|
|
3991
|
+
continue;
|
|
3992
|
+
}
|
|
3993
|
+
if (speculativeHit != null) {
|
|
3994
|
+
ambiguous = true;
|
|
3995
|
+
break;
|
|
3996
|
+
}
|
|
3997
|
+
speculativeHit = {
|
|
3998
|
+
key,
|
|
3999
|
+
bp,
|
|
4000
|
+
result: branchResult
|
|
4001
|
+
};
|
|
4002
|
+
}
|
|
4003
|
+
if (!branchResult.success && branchResult.consumed > 0 && (speculativeError == null || speculativeError.consumed < branchResult.consumed)) speculativeError = branchResult;
|
|
4004
|
+
}
|
|
4005
|
+
if (speculativeHit != null && (provisionalHit != null || provisionalAmbiguous)) ambiguous = true;
|
|
4006
|
+
if (speculativeHit == null && !ambiguous && !provisionalAmbiguous && provisionalHit != null) speculativeHit = provisionalHit;
|
|
4007
|
+
if (speculativeHit != null && !ambiguous) {
|
|
4008
|
+
const { key, bp, result: branchResult } = speculativeHit;
|
|
4009
|
+
if (branchResult.success) {
|
|
4010
|
+
const annotatedDiscriminatorState$2 = getAnnotatedChildState(state, discriminatorResult.next.state, discriminator);
|
|
4011
|
+
const mergedExec = mergeChildExec(discriminatorExec, branchResult.next.exec);
|
|
4012
|
+
return {
|
|
4013
|
+
success: true,
|
|
4014
|
+
provisional: true,
|
|
4015
|
+
next: {
|
|
4016
|
+
...branchResult.next,
|
|
4017
|
+
state: {
|
|
4018
|
+
...state,
|
|
4019
|
+
discriminatorState: annotatedDiscriminatorState$2,
|
|
4020
|
+
selectedBranch: {
|
|
4021
|
+
kind: "branch",
|
|
4022
|
+
key
|
|
4023
|
+
},
|
|
4024
|
+
branchState: getAnnotatedChildState(state, branchResult.next.state, bp),
|
|
4025
|
+
speculative: true
|
|
4026
|
+
},
|
|
4027
|
+
...mergedExec != null ? {
|
|
4028
|
+
exec: mergedExec,
|
|
4029
|
+
dependencyRegistry: mergedExec.dependencyRegistry
|
|
4030
|
+
} : {}
|
|
4031
|
+
},
|
|
4032
|
+
consumed: branchResult.consumed
|
|
4033
|
+
};
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
3883
4036
|
let deferredBranchState = state.branchState;
|
|
3884
4037
|
if (defaultBranch !== void 0) {
|
|
3885
4038
|
const defaultResult = await defaultBranch.parse(withChildContext(context, "_branch", state.branchState ?? defaultBranch.initialState, defaultBranch, defaultBranch.usage));
|
|
@@ -3887,6 +4040,7 @@ function conditional(discriminator, branches, defaultBranch, options) {
|
|
|
3887
4040
|
const defaultExec = mergeChildExec(context.exec, defaultResult.next.exec);
|
|
3888
4041
|
return {
|
|
3889
4042
|
success: true,
|
|
4043
|
+
...defaultResult.provisional ? { provisional: true } : {},
|
|
3890
4044
|
next: {
|
|
3891
4045
|
...defaultResult.next,
|
|
3892
4046
|
state: {
|
|
@@ -3905,8 +4059,8 @@ function conditional(discriminator, branches, defaultBranch, options) {
|
|
|
3905
4059
|
if (!defaultResult.success && defaultResult.consumed > 0) return defaultResult;
|
|
3906
4060
|
if (defaultResult.success && defaultResult.consumed.length === 0 && context.buffer.length === 0) deferredBranchState = getAnnotatedChildState(state, defaultResult.next.state, defaultBranch);
|
|
3907
4061
|
}
|
|
4062
|
+
if (speculativeError != null && !ambiguous) return speculativeError;
|
|
3908
4063
|
const annotatedDiscriminatorState$1 = getAnnotatedChildState(state, discriminatorResult.next.state, discriminator);
|
|
3909
|
-
const mergedExec = mergeChildExec(context.exec, discriminatorResult.next.exec);
|
|
3910
4064
|
return {
|
|
3911
4065
|
success: true,
|
|
3912
4066
|
provisional: true,
|
|
@@ -3917,9 +4071,9 @@ function conditional(discriminator, branches, defaultBranch, options) {
|
|
|
3917
4071
|
discriminatorState: annotatedDiscriminatorState$1,
|
|
3918
4072
|
branchState: deferredBranchState
|
|
3919
4073
|
},
|
|
3920
|
-
...
|
|
3921
|
-
exec:
|
|
3922
|
-
dependencyRegistry:
|
|
4074
|
+
...discriminatorExec != null ? {
|
|
4075
|
+
exec: discriminatorExec,
|
|
4076
|
+
dependencyRegistry: discriminatorExec.dependencyRegistry
|
|
3923
4077
|
} : {}
|
|
3924
4078
|
},
|
|
3925
4079
|
consumed: []
|
|
@@ -4141,6 +4295,18 @@ function conditional(discriminator, branches, defaultBranch, options) {
|
|
|
4141
4295
|
};
|
|
4142
4296
|
};
|
|
4143
4297
|
const completeAsync = async (state, exec) => {
|
|
4298
|
+
let wasSpeculative = false;
|
|
4299
|
+
if (state.speculative && state.selectedBranch?.kind === "branch") if (exec?.phase !== "parse" && exec?.phase !== "suggest") {
|
|
4300
|
+
wasSpeculative = true;
|
|
4301
|
+
state = {
|
|
4302
|
+
...state,
|
|
4303
|
+
speculative: void 0
|
|
4304
|
+
};
|
|
4305
|
+
} else state = {
|
|
4306
|
+
...state,
|
|
4307
|
+
discriminatorValue: state.selectedBranch.key,
|
|
4308
|
+
speculative: void 0
|
|
4309
|
+
};
|
|
4144
4310
|
if (state.selectedBranch === void 0) {
|
|
4145
4311
|
if (exec?.phase !== "parse" && exec?.phase !== "suggest") {
|
|
4146
4312
|
const annotatedDiscriminatorStateForDeferred = getAnnotatedChildState(state, state.discriminatorState, discriminator);
|
|
@@ -4225,20 +4391,26 @@ function conditional(discriminator, branches, defaultBranch, options) {
|
|
|
4225
4391
|
};
|
|
4226
4392
|
const needsDiscriminatorCompletion = state.selectedBranch.kind !== "default" && !(state.discriminatorValue != null && state.discriminatorValue === state.selectedBranch.key);
|
|
4227
4393
|
const discriminatorCompleteResult = needsDiscriminatorCompletion ? await discriminator.complete(annotatedDiscriminatorState, withChildExecPath(completionExec, "_discriminator")) : void 0;
|
|
4228
|
-
const branchResult = unwrapCompleteResult(await branchParser.complete(resolvedBranchState, withChildExecPath(completionExec, "_branch")));
|
|
4229
|
-
if (!branchResult.success) {
|
|
4230
|
-
if (state.discriminatorValue !== void 0 && options?.errors?.branchError) return {
|
|
4231
|
-
success: false,
|
|
4232
|
-
error: options.errors.branchError(state.discriminatorValue, branchResult.error)
|
|
4233
|
-
};
|
|
4234
|
-
return branchResult;
|
|
4235
|
-
}
|
|
4236
4394
|
let discriminatorValue;
|
|
4237
4395
|
if (state.selectedBranch.kind === "default") discriminatorValue = void 0;
|
|
4238
4396
|
else if (state.discriminatorValue != null && state.discriminatorValue === state.selectedBranch.key) discriminatorValue = state.discriminatorValue;
|
|
4239
4397
|
else {
|
|
4240
4398
|
const completedDiscriminator = unwrapCompleteResult(discriminatorCompleteResult);
|
|
4241
|
-
|
|
4399
|
+
if (completedDiscriminator.success) discriminatorValue = completedDiscriminator.value;
|
|
4400
|
+
else if (wasSpeculative) return completedDiscriminator;
|
|
4401
|
+
else discriminatorValue = state.selectedBranch.key;
|
|
4402
|
+
}
|
|
4403
|
+
if (wasSpeculative && state.selectedBranch.kind === "branch" && discriminatorValue !== state.selectedBranch.key) return {
|
|
4404
|
+
success: false,
|
|
4405
|
+
error: getNoMatchError$1()
|
|
4406
|
+
};
|
|
4407
|
+
const branchResult = unwrapCompleteResult(await branchParser.complete(resolvedBranchState, withChildExecPath(completionExec, "_branch")));
|
|
4408
|
+
if (!branchResult.success) {
|
|
4409
|
+
if (discriminatorValue !== void 0 && options?.errors?.branchError) return {
|
|
4410
|
+
success: false,
|
|
4411
|
+
error: options.errors.branchError(discriminatorValue, branchResult.error)
|
|
4412
|
+
};
|
|
4413
|
+
return branchResult;
|
|
4242
4414
|
}
|
|
4243
4415
|
return {
|
|
4244
4416
|
success: true,
|