@optique/core 1.0.0-dev.1711 → 1.0.0-dev.1714
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 +310 -27
- package/dist/constructs.d.cts +15 -0
- package/dist/constructs.d.ts +15 -0
- package/dist/constructs.js +310 -27
- package/package.json +1 -1
package/dist/constructs.cjs
CHANGED
|
@@ -712,9 +712,33 @@ 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
|
+
const activeBranchLocked = activeState != null && activeState[1].success && activeState[1].consumed.length > 0;
|
|
722
|
+
if (activeBranchLocked && activeState[0] === i) {
|
|
723
|
+
provisionalConsuming = {
|
|
724
|
+
index: i,
|
|
725
|
+
parser,
|
|
726
|
+
result
|
|
727
|
+
};
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
if (activeBranchLocked && provisionalConsuming != null && provisionalConsuming.index === activeState[0]) continue;
|
|
731
|
+
if (provisionalConsuming == null && !provisionalAmbiguous) provisionalConsuming = {
|
|
732
|
+
index: i,
|
|
733
|
+
parser,
|
|
734
|
+
result
|
|
735
|
+
};
|
|
736
|
+
else {
|
|
737
|
+
provisionalConsuming = null;
|
|
738
|
+
provisionalAmbiguous = true;
|
|
739
|
+
}
|
|
740
|
+
continue;
|
|
741
|
+
}
|
|
718
742
|
if (activeState?.[0] !== i && activeState?.[1].success) {
|
|
719
743
|
if (activeState[1].consumed.length === 0) {
|
|
720
744
|
const mergedExec$2 = mergeChildExec(context.exec, result.next.exec);
|
|
@@ -812,6 +836,66 @@ function or(...args) {
|
|
|
812
836
|
consumed: []
|
|
813
837
|
};
|
|
814
838
|
}
|
|
839
|
+
if (provisionalConsuming !== null) {
|
|
840
|
+
const activeIsLockedDifferent = activeState != null && activeState[1].success && activeState[1].consumed.length > 0 && activeState[0] !== provisionalConsuming.index;
|
|
841
|
+
if (!activeIsLockedDifferent) {
|
|
842
|
+
const mergedExec = mergeChildExec(context.exec, provisionalConsuming.result.next.exec);
|
|
843
|
+
return {
|
|
844
|
+
success: true,
|
|
845
|
+
provisional: true,
|
|
846
|
+
next: {
|
|
847
|
+
...context,
|
|
848
|
+
buffer: provisionalConsuming.result.next.buffer,
|
|
849
|
+
optionsTerminated: provisionalConsuming.result.next.optionsTerminated,
|
|
850
|
+
state: createExclusiveState(context.state, provisionalConsuming.index, provisionalConsuming.parser, provisionalConsuming.result),
|
|
851
|
+
...mergedExec != null ? {
|
|
852
|
+
exec: mergedExec,
|
|
853
|
+
dependencyRegistry: mergedExec.dependencyRegistry
|
|
854
|
+
} : {}
|
|
855
|
+
},
|
|
856
|
+
consumed: provisionalConsuming.result.consumed
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
if (activeState != null && activeState[1].success) {
|
|
860
|
+
const previouslyConsumed = activeState[1].consumed;
|
|
861
|
+
const checkResult = provisionalConsuming.parser.parse({
|
|
862
|
+
...withChildContext(context, provisionalConsuming.index, provisionalConsuming.parser.initialState, provisionalConsuming.parser),
|
|
863
|
+
buffer: previouslyConsumed
|
|
864
|
+
});
|
|
865
|
+
const canConsumeShared = checkResult.success && checkResult.consumed.length === previouslyConsumed.length && checkResult.consumed.every((c, idx) => c === previouslyConsumed[idx]);
|
|
866
|
+
if (canConsumeShared && checkResult.success) {
|
|
867
|
+
const replayExec = mergeChildExec(context.exec, checkResult.next.exec);
|
|
868
|
+
const replayedResult = provisionalConsuming.parser.parse(withChildContext({
|
|
869
|
+
...context,
|
|
870
|
+
...replayExec != null ? {
|
|
871
|
+
exec: replayExec,
|
|
872
|
+
dependencyRegistry: replayExec.dependencyRegistry
|
|
873
|
+
} : {}
|
|
874
|
+
}, provisionalConsuming.index, checkResult.next.state, provisionalConsuming.parser));
|
|
875
|
+
if (replayedResult.success) {
|
|
876
|
+
const mergedExec = mergeChildExec(replayExec, replayedResult.next.exec);
|
|
877
|
+
return {
|
|
878
|
+
success: true,
|
|
879
|
+
provisional: true,
|
|
880
|
+
next: {
|
|
881
|
+
...context,
|
|
882
|
+
buffer: replayedResult.next.buffer,
|
|
883
|
+
optionsTerminated: replayedResult.next.optionsTerminated,
|
|
884
|
+
state: createExclusiveState(context.state, provisionalConsuming.index, provisionalConsuming.parser, {
|
|
885
|
+
...replayedResult,
|
|
886
|
+
consumed: [...previouslyConsumed, ...replayedResult.consumed]
|
|
887
|
+
}),
|
|
888
|
+
...mergedExec != null ? {
|
|
889
|
+
exec: mergedExec,
|
|
890
|
+
dependencyRegistry: mergedExec.dependencyRegistry
|
|
891
|
+
} : {}
|
|
892
|
+
},
|
|
893
|
+
consumed: replayedResult.consumed
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
815
899
|
return {
|
|
816
900
|
...error,
|
|
817
901
|
success: false
|
|
@@ -824,10 +908,34 @@ function or(...args) {
|
|
|
824
908
|
orderedParsers.sort(([_, a], [__, b]) => activeState?.[0] === a ? -1 : activeState?.[0] === b ? 1 : a - b);
|
|
825
909
|
let zeroConsumedBranch = null;
|
|
826
910
|
let zeroConsumedCount = 0;
|
|
911
|
+
let provisionalConsuming = null;
|
|
912
|
+
let provisionalAmbiguous = false;
|
|
827
913
|
for (const [parser, i] of orderedParsers) {
|
|
828
914
|
const resultOrPromise = parser.parse(withChildContext(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
|
|
829
915
|
const result = await resultOrPromise;
|
|
830
916
|
if (result.success && result.consumed.length > 0) {
|
|
917
|
+
if (result.provisional) {
|
|
918
|
+
const activeBranchLocked = activeState != null && activeState[1].success && activeState[1].consumed.length > 0;
|
|
919
|
+
if (activeBranchLocked && activeState[0] === i) {
|
|
920
|
+
provisionalConsuming = {
|
|
921
|
+
index: i,
|
|
922
|
+
parser,
|
|
923
|
+
result
|
|
924
|
+
};
|
|
925
|
+
continue;
|
|
926
|
+
}
|
|
927
|
+
if (activeBranchLocked && provisionalConsuming != null && provisionalConsuming.index === activeState[0]) continue;
|
|
928
|
+
if (provisionalConsuming == null && !provisionalAmbiguous) provisionalConsuming = {
|
|
929
|
+
index: i,
|
|
930
|
+
parser,
|
|
931
|
+
result
|
|
932
|
+
};
|
|
933
|
+
else {
|
|
934
|
+
provisionalConsuming = null;
|
|
935
|
+
provisionalAmbiguous = true;
|
|
936
|
+
}
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
831
939
|
if (activeState?.[0] !== i && activeState?.[1].success) {
|
|
832
940
|
if (activeState[1].consumed.length === 0) {
|
|
833
941
|
const mergedExec$2 = mergeChildExec(context.exec, result.next.exec);
|
|
@@ -927,6 +1035,66 @@ function or(...args) {
|
|
|
927
1035
|
consumed: []
|
|
928
1036
|
};
|
|
929
1037
|
}
|
|
1038
|
+
if (provisionalConsuming !== null) {
|
|
1039
|
+
const activeIsLockedDifferent = activeState != null && activeState[1].success && activeState[1].consumed.length > 0 && activeState[0] !== provisionalConsuming.index;
|
|
1040
|
+
if (!activeIsLockedDifferent) {
|
|
1041
|
+
const mergedExec = mergeChildExec(context.exec, provisionalConsuming.result.next.exec);
|
|
1042
|
+
return {
|
|
1043
|
+
success: true,
|
|
1044
|
+
provisional: true,
|
|
1045
|
+
next: {
|
|
1046
|
+
...context,
|
|
1047
|
+
buffer: provisionalConsuming.result.next.buffer,
|
|
1048
|
+
optionsTerminated: provisionalConsuming.result.next.optionsTerminated,
|
|
1049
|
+
state: createExclusiveState(context.state, provisionalConsuming.index, provisionalConsuming.parser, provisionalConsuming.result),
|
|
1050
|
+
...mergedExec != null ? {
|
|
1051
|
+
exec: mergedExec,
|
|
1052
|
+
dependencyRegistry: mergedExec.dependencyRegistry
|
|
1053
|
+
} : {}
|
|
1054
|
+
},
|
|
1055
|
+
consumed: provisionalConsuming.result.consumed
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
if (activeState != null && activeState[1].success) {
|
|
1059
|
+
const previouslyConsumed = activeState[1].consumed;
|
|
1060
|
+
const checkResult = await provisionalConsuming.parser.parse({
|
|
1061
|
+
...withChildContext(context, provisionalConsuming.index, provisionalConsuming.parser.initialState, provisionalConsuming.parser),
|
|
1062
|
+
buffer: previouslyConsumed
|
|
1063
|
+
});
|
|
1064
|
+
const canConsumeShared = checkResult.success && checkResult.consumed.length === previouslyConsumed.length && checkResult.consumed.every((c, idx) => c === previouslyConsumed[idx]);
|
|
1065
|
+
if (canConsumeShared && checkResult.success) {
|
|
1066
|
+
const replayExec = mergeChildExec(context.exec, checkResult.next.exec);
|
|
1067
|
+
const replayedResult = await provisionalConsuming.parser.parse(withChildContext({
|
|
1068
|
+
...context,
|
|
1069
|
+
...replayExec != null ? {
|
|
1070
|
+
exec: replayExec,
|
|
1071
|
+
dependencyRegistry: replayExec.dependencyRegistry
|
|
1072
|
+
} : {}
|
|
1073
|
+
}, provisionalConsuming.index, checkResult.next.state, provisionalConsuming.parser));
|
|
1074
|
+
if (replayedResult.success) {
|
|
1075
|
+
const mergedExec = mergeChildExec(replayExec, replayedResult.next.exec);
|
|
1076
|
+
return {
|
|
1077
|
+
success: true,
|
|
1078
|
+
provisional: true,
|
|
1079
|
+
next: {
|
|
1080
|
+
...context,
|
|
1081
|
+
buffer: replayedResult.next.buffer,
|
|
1082
|
+
optionsTerminated: replayedResult.next.optionsTerminated,
|
|
1083
|
+
state: createExclusiveState(context.state, provisionalConsuming.index, provisionalConsuming.parser, {
|
|
1084
|
+
...replayedResult,
|
|
1085
|
+
consumed: [...previouslyConsumed, ...replayedResult.consumed]
|
|
1086
|
+
}),
|
|
1087
|
+
...mergedExec != null ? {
|
|
1088
|
+
exec: mergedExec,
|
|
1089
|
+
dependencyRegistry: mergedExec.dependencyRegistry
|
|
1090
|
+
} : {}
|
|
1091
|
+
},
|
|
1092
|
+
consumed: replayedResult.consumed
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
930
1098
|
return {
|
|
931
1099
|
...error,
|
|
932
1100
|
success: false
|
|
@@ -1020,7 +1188,8 @@ function longestMatch(...args) {
|
|
|
1020
1188
|
const result = parser.parse(withChildContext(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
|
|
1021
1189
|
if (result.success) {
|
|
1022
1190
|
const consumed = context.buffer.length - result.next.buffer.length;
|
|
1023
|
-
|
|
1191
|
+
const bestIsProvisional = bestMatch != null && bestMatch.result.success && !!bestMatch.result.provisional;
|
|
1192
|
+
if (bestMatch === null || consumed > bestMatch.consumed || consumed === bestMatch.consumed && bestIsProvisional && !result.provisional) bestMatch = {
|
|
1024
1193
|
index: i,
|
|
1025
1194
|
parser,
|
|
1026
1195
|
result,
|
|
@@ -1060,7 +1229,8 @@ function longestMatch(...args) {
|
|
|
1060
1229
|
const result = await resultOrPromise;
|
|
1061
1230
|
if (result.success) {
|
|
1062
1231
|
const consumed = context.buffer.length - result.next.buffer.length;
|
|
1063
|
-
|
|
1232
|
+
const bestIsProvisional = bestMatch != null && bestMatch.result.success && !!bestMatch.result.provisional;
|
|
1233
|
+
if (bestMatch === null || consumed > bestMatch.consumed || consumed === bestMatch.consumed && bestIsProvisional && !result.provisional) bestMatch = {
|
|
1064
1234
|
index: i,
|
|
1065
1235
|
parser,
|
|
1066
1236
|
result,
|
|
@@ -3656,18 +3826,35 @@ function group(label, parser, options = {}) {
|
|
|
3656
3826
|
* // defaultResult.value = [undefined, {}]
|
|
3657
3827
|
* ```
|
|
3658
3828
|
*
|
|
3659
|
-
* ###
|
|
3829
|
+
* ### Speculative branch parsing
|
|
3660
3830
|
*
|
|
3661
3831
|
* When the discriminator is an async parser that succeeds without consuming
|
|
3662
3832
|
* 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
|
-
*
|
|
3833
|
+
* is normally deferred to the complete phase. To allow branch-specific
|
|
3834
|
+
* tokens to be consumed, `conditional()` speculatively tries all named
|
|
3835
|
+
* branches during parse. If exactly one branch can consume tokens, it is
|
|
3836
|
+
* tentatively selected and verified against the resolved discriminator
|
|
3837
|
+
* during the complete phase.
|
|
3838
|
+
*
|
|
3839
|
+
* If the discriminator resolves to a different branch than the one that
|
|
3840
|
+
* consumed tokens (contradictory input), the parse fails. When multiple
|
|
3841
|
+
* branches can consume the same tokens (ambiguous), speculation is skipped
|
|
3842
|
+
* entirely to keep branch selection order-independent.
|
|
3843
|
+
*
|
|
3844
|
+
* #### Known limitations
|
|
3845
|
+
*
|
|
3846
|
+
* - When a default branch accepts the same tokens as a named branch,
|
|
3847
|
+
* speculation prefers the named branch. If the discriminator later
|
|
3848
|
+
* resolves to a value not in the named branches, the parse fails
|
|
3849
|
+
* instead of falling back to the default branch. To avoid this,
|
|
3850
|
+
* ensure named branch options are distinct from the default branch.
|
|
3851
|
+
* - Within `longestMatch()`, a longer speculative match can beat a
|
|
3852
|
+
* shorter definitive one. If the speculative match fails during
|
|
3853
|
+
* completion, the tokens consumed by it are not recoverable.
|
|
3854
|
+
* - The dependency runtime seeds both discriminator and branch sources
|
|
3855
|
+
* before verifying the speculative selection. A discriminator that
|
|
3856
|
+
* depends on branch-local dependency sources could be circularly
|
|
3857
|
+
* confirmed by the speculative branch.
|
|
3671
3858
|
*
|
|
3672
3859
|
* @since 0.8.0
|
|
3673
3860
|
*/
|
|
@@ -3880,13 +4067,87 @@ function conditional(discriminator, branches, defaultBranch, options) {
|
|
|
3880
4067
|
const discriminatorResult = await discriminator.parse({ ...withChildContext(context, "_discriminator", state.discriminatorState, discriminator) });
|
|
3881
4068
|
if (discriminatorResult.success) {
|
|
3882
4069
|
if (discriminatorResult.consumed.length === 0 && discriminator.$mode === "async") {
|
|
4070
|
+
const discriminatorExec = mergeChildExec(context.exec, discriminatorResult.next.exec);
|
|
4071
|
+
const speculationContext = {
|
|
4072
|
+
...context,
|
|
4073
|
+
buffer: discriminatorResult.next.buffer,
|
|
4074
|
+
optionsTerminated: discriminatorResult.next.optionsTerminated,
|
|
4075
|
+
...discriminatorExec != null ? {
|
|
4076
|
+
exec: discriminatorExec,
|
|
4077
|
+
dependencyRegistry: discriminatorExec.dependencyRegistry
|
|
4078
|
+
} : {}
|
|
4079
|
+
};
|
|
4080
|
+
let speculativeHit;
|
|
4081
|
+
let provisionalHit;
|
|
4082
|
+
let provisionalAmbiguous = false;
|
|
4083
|
+
let speculativeError;
|
|
4084
|
+
let ambiguous = false;
|
|
4085
|
+
for (const [key, bp] of branchParsers) {
|
|
4086
|
+
const branchResult = await bp.parse(withChildContext(speculationContext, "_branch", bp.initialState, bp, bp.usage));
|
|
4087
|
+
if (branchResult.success && branchResult.consumed.length > 0) {
|
|
4088
|
+
if (branchResult.provisional) {
|
|
4089
|
+
if (provisionalHit == null && !provisionalAmbiguous) provisionalHit = {
|
|
4090
|
+
key,
|
|
4091
|
+
bp,
|
|
4092
|
+
result: branchResult
|
|
4093
|
+
};
|
|
4094
|
+
else {
|
|
4095
|
+
provisionalHit = void 0;
|
|
4096
|
+
provisionalAmbiguous = true;
|
|
4097
|
+
}
|
|
4098
|
+
continue;
|
|
4099
|
+
}
|
|
4100
|
+
if (speculativeHit != null) {
|
|
4101
|
+
ambiguous = true;
|
|
4102
|
+
break;
|
|
4103
|
+
}
|
|
4104
|
+
speculativeHit = {
|
|
4105
|
+
key,
|
|
4106
|
+
bp,
|
|
4107
|
+
result: branchResult
|
|
4108
|
+
};
|
|
4109
|
+
}
|
|
4110
|
+
if (!branchResult.success && branchResult.consumed > 0 && (speculativeError == null || speculativeError.consumed < branchResult.consumed)) speculativeError = branchResult;
|
|
4111
|
+
}
|
|
4112
|
+
if (speculativeHit != null && (provisionalHit != null || provisionalAmbiguous)) ambiguous = true;
|
|
4113
|
+
if (speculativeHit == null && !ambiguous && !provisionalAmbiguous && provisionalHit != null) speculativeHit = provisionalHit;
|
|
4114
|
+
if (speculativeHit != null && !ambiguous) {
|
|
4115
|
+
const { key, bp, result: branchResult } = speculativeHit;
|
|
4116
|
+
if (branchResult.success) {
|
|
4117
|
+
const annotatedDiscriminatorState$2 = getAnnotatedChildState(state, discriminatorResult.next.state, discriminator);
|
|
4118
|
+
const mergedExec = mergeChildExec(discriminatorExec, branchResult.next.exec);
|
|
4119
|
+
return {
|
|
4120
|
+
success: true,
|
|
4121
|
+
provisional: true,
|
|
4122
|
+
next: {
|
|
4123
|
+
...branchResult.next,
|
|
4124
|
+
state: {
|
|
4125
|
+
...state,
|
|
4126
|
+
discriminatorState: annotatedDiscriminatorState$2,
|
|
4127
|
+
selectedBranch: {
|
|
4128
|
+
kind: "branch",
|
|
4129
|
+
key
|
|
4130
|
+
},
|
|
4131
|
+
branchState: getAnnotatedChildState(state, branchResult.next.state, bp),
|
|
4132
|
+
speculative: true
|
|
4133
|
+
},
|
|
4134
|
+
...mergedExec != null ? {
|
|
4135
|
+
exec: mergedExec,
|
|
4136
|
+
dependencyRegistry: mergedExec.dependencyRegistry
|
|
4137
|
+
} : {}
|
|
4138
|
+
},
|
|
4139
|
+
consumed: branchResult.consumed
|
|
4140
|
+
};
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
3883
4143
|
let deferredBranchState = state.branchState;
|
|
3884
4144
|
if (defaultBranch !== void 0) {
|
|
3885
|
-
const defaultResult = await defaultBranch.parse(withChildContext(
|
|
4145
|
+
const defaultResult = await defaultBranch.parse(withChildContext(speculationContext, "_branch", state.branchState ?? defaultBranch.initialState, defaultBranch, defaultBranch.usage));
|
|
3886
4146
|
if (defaultResult.success && defaultResult.consumed.length > 0) {
|
|
3887
|
-
const defaultExec = mergeChildExec(context.exec, defaultResult.next.exec);
|
|
4147
|
+
const defaultExec = mergeChildExec(discriminatorExec ?? context.exec, defaultResult.next.exec);
|
|
3888
4148
|
return {
|
|
3889
4149
|
success: true,
|
|
4150
|
+
...defaultResult.provisional ? { provisional: true } : {},
|
|
3890
4151
|
next: {
|
|
3891
4152
|
...defaultResult.next,
|
|
3892
4153
|
state: {
|
|
@@ -3903,10 +4164,10 @@ function conditional(discriminator, branches, defaultBranch, options) {
|
|
|
3903
4164
|
};
|
|
3904
4165
|
}
|
|
3905
4166
|
if (!defaultResult.success && defaultResult.consumed > 0) return defaultResult;
|
|
3906
|
-
if (defaultResult.success && defaultResult.consumed.length === 0 &&
|
|
4167
|
+
if (defaultResult.success && defaultResult.consumed.length === 0 && speculationContext.buffer.length === 0) deferredBranchState = getAnnotatedChildState(state, defaultResult.next.state, defaultBranch);
|
|
3907
4168
|
}
|
|
4169
|
+
if (speculativeError != null && !ambiguous) return speculativeError;
|
|
3908
4170
|
const annotatedDiscriminatorState$1 = getAnnotatedChildState(state, discriminatorResult.next.state, discriminator);
|
|
3909
|
-
const mergedExec = mergeChildExec(context.exec, discriminatorResult.next.exec);
|
|
3910
4171
|
return {
|
|
3911
4172
|
success: true,
|
|
3912
4173
|
provisional: true,
|
|
@@ -3917,9 +4178,9 @@ function conditional(discriminator, branches, defaultBranch, options) {
|
|
|
3917
4178
|
discriminatorState: annotatedDiscriminatorState$1,
|
|
3918
4179
|
branchState: deferredBranchState
|
|
3919
4180
|
},
|
|
3920
|
-
...
|
|
3921
|
-
exec:
|
|
3922
|
-
dependencyRegistry:
|
|
4181
|
+
...discriminatorExec != null ? {
|
|
4182
|
+
exec: discriminatorExec,
|
|
4183
|
+
dependencyRegistry: discriminatorExec.dependencyRegistry
|
|
3923
4184
|
} : {}
|
|
3924
4185
|
},
|
|
3925
4186
|
consumed: []
|
|
@@ -4141,6 +4402,18 @@ function conditional(discriminator, branches, defaultBranch, options) {
|
|
|
4141
4402
|
};
|
|
4142
4403
|
};
|
|
4143
4404
|
const completeAsync = async (state, exec) => {
|
|
4405
|
+
let wasSpeculative = false;
|
|
4406
|
+
if (state.speculative && state.selectedBranch?.kind === "branch") if (exec?.phase !== "parse" && exec?.phase !== "suggest") {
|
|
4407
|
+
wasSpeculative = true;
|
|
4408
|
+
state = {
|
|
4409
|
+
...state,
|
|
4410
|
+
speculative: void 0
|
|
4411
|
+
};
|
|
4412
|
+
} else state = {
|
|
4413
|
+
...state,
|
|
4414
|
+
discriminatorValue: state.selectedBranch.key,
|
|
4415
|
+
speculative: void 0
|
|
4416
|
+
};
|
|
4144
4417
|
if (state.selectedBranch === void 0) {
|
|
4145
4418
|
if (exec?.phase !== "parse" && exec?.phase !== "suggest") {
|
|
4146
4419
|
const annotatedDiscriminatorStateForDeferred = getAnnotatedChildState(state, state.discriminatorState, discriminator);
|
|
@@ -4225,20 +4498,30 @@ function conditional(discriminator, branches, defaultBranch, options) {
|
|
|
4225
4498
|
};
|
|
4226
4499
|
const needsDiscriminatorCompletion = state.selectedBranch.kind !== "default" && !(state.discriminatorValue != null && state.discriminatorValue === state.selectedBranch.key);
|
|
4227
4500
|
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
4501
|
let discriminatorValue;
|
|
4237
4502
|
if (state.selectedBranch.kind === "default") discriminatorValue = void 0;
|
|
4238
4503
|
else if (state.discriminatorValue != null && state.discriminatorValue === state.selectedBranch.key) discriminatorValue = state.discriminatorValue;
|
|
4239
4504
|
else {
|
|
4240
4505
|
const completedDiscriminator = unwrapCompleteResult(discriminatorCompleteResult);
|
|
4241
|
-
|
|
4506
|
+
if (completedDiscriminator.success) discriminatorValue = completedDiscriminator.value;
|
|
4507
|
+
else if (wasSpeculative) return completedDiscriminator;
|
|
4508
|
+
else discriminatorValue = state.selectedBranch.key;
|
|
4509
|
+
}
|
|
4510
|
+
if (wasSpeculative && state.selectedBranch.kind === "branch" && discriminatorValue !== state.selectedBranch.key) {
|
|
4511
|
+
const speculativeKey = state.selectedBranch.key;
|
|
4512
|
+
const resolvedKey = discriminatorValue ?? "";
|
|
4513
|
+
return {
|
|
4514
|
+
success: false,
|
|
4515
|
+
error: options?.errors?.branchMismatch ? options.errors.branchMismatch(resolvedKey, speculativeKey) : require_message.message`Branch mismatch: tokens for ${speculativeKey} were consumed, but the discriminator resolved to ${resolvedKey}.`
|
|
4516
|
+
};
|
|
4517
|
+
}
|
|
4518
|
+
const branchResult = unwrapCompleteResult(await branchParser.complete(resolvedBranchState, withChildExecPath(completionExec, "_branch")));
|
|
4519
|
+
if (!branchResult.success) {
|
|
4520
|
+
if (discriminatorValue !== void 0 && options?.errors?.branchError) return {
|
|
4521
|
+
success: false,
|
|
4522
|
+
error: options.errors.branchError(discriminatorValue, branchResult.error)
|
|
4523
|
+
};
|
|
4524
|
+
return branchResult;
|
|
4242
4525
|
}
|
|
4243
4526
|
return {
|
|
4244
4527
|
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.
|
|
@@ -1331,6 +1332,20 @@ interface ConditionalErrorOptions {
|
|
|
1331
1332
|
* Custom error message for no matching input.
|
|
1332
1333
|
*/
|
|
1333
1334
|
noMatch?: Message | ((context: NoMatchContext) => Message);
|
|
1335
|
+
/**
|
|
1336
|
+
* Custom error message when speculative branch parsing committed
|
|
1337
|
+
* to one branch but the resolved discriminator value names a
|
|
1338
|
+
* different branch. This is the contradictory-input case: tokens
|
|
1339
|
+
* specific to one branch were consumed during the parse phase,
|
|
1340
|
+
* but the discriminator (e.g., from `prompt()` or a deferred
|
|
1341
|
+
* config source) ultimately resolved to a different key.
|
|
1342
|
+
*
|
|
1343
|
+
* Receives both the discriminator value the parser actually
|
|
1344
|
+
* resolved to (`discriminatorValue`) and the speculative key the
|
|
1345
|
+
* branch tokens were committed to (`speculativeKey`).
|
|
1346
|
+
* @since 0.10.1
|
|
1347
|
+
*/
|
|
1348
|
+
branchMismatch?: (discriminatorValue: string, speculativeKey: string) => Message;
|
|
1334
1349
|
}
|
|
1335
1350
|
/**
|
|
1336
1351
|
* Options for customizing the {@link conditional} combinator behavior.
|
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.
|
|
@@ -1331,6 +1332,20 @@ interface ConditionalErrorOptions {
|
|
|
1331
1332
|
* Custom error message for no matching input.
|
|
1332
1333
|
*/
|
|
1333
1334
|
noMatch?: Message | ((context: NoMatchContext) => Message);
|
|
1335
|
+
/**
|
|
1336
|
+
* Custom error message when speculative branch parsing committed
|
|
1337
|
+
* to one branch but the resolved discriminator value names a
|
|
1338
|
+
* different branch. This is the contradictory-input case: tokens
|
|
1339
|
+
* specific to one branch were consumed during the parse phase,
|
|
1340
|
+
* but the discriminator (e.g., from `prompt()` or a deferred
|
|
1341
|
+
* config source) ultimately resolved to a different key.
|
|
1342
|
+
*
|
|
1343
|
+
* Receives both the discriminator value the parser actually
|
|
1344
|
+
* resolved to (`discriminatorValue`) and the speculative key the
|
|
1345
|
+
* branch tokens were committed to (`speculativeKey`).
|
|
1346
|
+
* @since 0.10.1
|
|
1347
|
+
*/
|
|
1348
|
+
branchMismatch?: (discriminatorValue: string, speculativeKey: string) => Message;
|
|
1334
1349
|
}
|
|
1335
1350
|
/**
|
|
1336
1351
|
* Options for customizing the {@link conditional} combinator behavior.
|
package/dist/constructs.js
CHANGED
|
@@ -712,9 +712,33 @@ 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
|
+
const activeBranchLocked = activeState != null && activeState[1].success && activeState[1].consumed.length > 0;
|
|
722
|
+
if (activeBranchLocked && activeState[0] === i) {
|
|
723
|
+
provisionalConsuming = {
|
|
724
|
+
index: i,
|
|
725
|
+
parser,
|
|
726
|
+
result
|
|
727
|
+
};
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
if (activeBranchLocked && provisionalConsuming != null && provisionalConsuming.index === activeState[0]) continue;
|
|
731
|
+
if (provisionalConsuming == null && !provisionalAmbiguous) provisionalConsuming = {
|
|
732
|
+
index: i,
|
|
733
|
+
parser,
|
|
734
|
+
result
|
|
735
|
+
};
|
|
736
|
+
else {
|
|
737
|
+
provisionalConsuming = null;
|
|
738
|
+
provisionalAmbiguous = true;
|
|
739
|
+
}
|
|
740
|
+
continue;
|
|
741
|
+
}
|
|
718
742
|
if (activeState?.[0] !== i && activeState?.[1].success) {
|
|
719
743
|
if (activeState[1].consumed.length === 0) {
|
|
720
744
|
const mergedExec$2 = mergeChildExec(context.exec, result.next.exec);
|
|
@@ -812,6 +836,66 @@ function or(...args) {
|
|
|
812
836
|
consumed: []
|
|
813
837
|
};
|
|
814
838
|
}
|
|
839
|
+
if (provisionalConsuming !== null) {
|
|
840
|
+
const activeIsLockedDifferent = activeState != null && activeState[1].success && activeState[1].consumed.length > 0 && activeState[0] !== provisionalConsuming.index;
|
|
841
|
+
if (!activeIsLockedDifferent) {
|
|
842
|
+
const mergedExec = mergeChildExec(context.exec, provisionalConsuming.result.next.exec);
|
|
843
|
+
return {
|
|
844
|
+
success: true,
|
|
845
|
+
provisional: true,
|
|
846
|
+
next: {
|
|
847
|
+
...context,
|
|
848
|
+
buffer: provisionalConsuming.result.next.buffer,
|
|
849
|
+
optionsTerminated: provisionalConsuming.result.next.optionsTerminated,
|
|
850
|
+
state: createExclusiveState(context.state, provisionalConsuming.index, provisionalConsuming.parser, provisionalConsuming.result),
|
|
851
|
+
...mergedExec != null ? {
|
|
852
|
+
exec: mergedExec,
|
|
853
|
+
dependencyRegistry: mergedExec.dependencyRegistry
|
|
854
|
+
} : {}
|
|
855
|
+
},
|
|
856
|
+
consumed: provisionalConsuming.result.consumed
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
if (activeState != null && activeState[1].success) {
|
|
860
|
+
const previouslyConsumed = activeState[1].consumed;
|
|
861
|
+
const checkResult = provisionalConsuming.parser.parse({
|
|
862
|
+
...withChildContext(context, provisionalConsuming.index, provisionalConsuming.parser.initialState, provisionalConsuming.parser),
|
|
863
|
+
buffer: previouslyConsumed
|
|
864
|
+
});
|
|
865
|
+
const canConsumeShared = checkResult.success && checkResult.consumed.length === previouslyConsumed.length && checkResult.consumed.every((c, idx) => c === previouslyConsumed[idx]);
|
|
866
|
+
if (canConsumeShared && checkResult.success) {
|
|
867
|
+
const replayExec = mergeChildExec(context.exec, checkResult.next.exec);
|
|
868
|
+
const replayedResult = provisionalConsuming.parser.parse(withChildContext({
|
|
869
|
+
...context,
|
|
870
|
+
...replayExec != null ? {
|
|
871
|
+
exec: replayExec,
|
|
872
|
+
dependencyRegistry: replayExec.dependencyRegistry
|
|
873
|
+
} : {}
|
|
874
|
+
}, provisionalConsuming.index, checkResult.next.state, provisionalConsuming.parser));
|
|
875
|
+
if (replayedResult.success) {
|
|
876
|
+
const mergedExec = mergeChildExec(replayExec, replayedResult.next.exec);
|
|
877
|
+
return {
|
|
878
|
+
success: true,
|
|
879
|
+
provisional: true,
|
|
880
|
+
next: {
|
|
881
|
+
...context,
|
|
882
|
+
buffer: replayedResult.next.buffer,
|
|
883
|
+
optionsTerminated: replayedResult.next.optionsTerminated,
|
|
884
|
+
state: createExclusiveState(context.state, provisionalConsuming.index, provisionalConsuming.parser, {
|
|
885
|
+
...replayedResult,
|
|
886
|
+
consumed: [...previouslyConsumed, ...replayedResult.consumed]
|
|
887
|
+
}),
|
|
888
|
+
...mergedExec != null ? {
|
|
889
|
+
exec: mergedExec,
|
|
890
|
+
dependencyRegistry: mergedExec.dependencyRegistry
|
|
891
|
+
} : {}
|
|
892
|
+
},
|
|
893
|
+
consumed: replayedResult.consumed
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
815
899
|
return {
|
|
816
900
|
...error,
|
|
817
901
|
success: false
|
|
@@ -824,10 +908,34 @@ function or(...args) {
|
|
|
824
908
|
orderedParsers.sort(([_, a], [__, b]) => activeState?.[0] === a ? -1 : activeState?.[0] === b ? 1 : a - b);
|
|
825
909
|
let zeroConsumedBranch = null;
|
|
826
910
|
let zeroConsumedCount = 0;
|
|
911
|
+
let provisionalConsuming = null;
|
|
912
|
+
let provisionalAmbiguous = false;
|
|
827
913
|
for (const [parser, i] of orderedParsers) {
|
|
828
914
|
const resultOrPromise = parser.parse(withChildContext(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
|
|
829
915
|
const result = await resultOrPromise;
|
|
830
916
|
if (result.success && result.consumed.length > 0) {
|
|
917
|
+
if (result.provisional) {
|
|
918
|
+
const activeBranchLocked = activeState != null && activeState[1].success && activeState[1].consumed.length > 0;
|
|
919
|
+
if (activeBranchLocked && activeState[0] === i) {
|
|
920
|
+
provisionalConsuming = {
|
|
921
|
+
index: i,
|
|
922
|
+
parser,
|
|
923
|
+
result
|
|
924
|
+
};
|
|
925
|
+
continue;
|
|
926
|
+
}
|
|
927
|
+
if (activeBranchLocked && provisionalConsuming != null && provisionalConsuming.index === activeState[0]) continue;
|
|
928
|
+
if (provisionalConsuming == null && !provisionalAmbiguous) provisionalConsuming = {
|
|
929
|
+
index: i,
|
|
930
|
+
parser,
|
|
931
|
+
result
|
|
932
|
+
};
|
|
933
|
+
else {
|
|
934
|
+
provisionalConsuming = null;
|
|
935
|
+
provisionalAmbiguous = true;
|
|
936
|
+
}
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
831
939
|
if (activeState?.[0] !== i && activeState?.[1].success) {
|
|
832
940
|
if (activeState[1].consumed.length === 0) {
|
|
833
941
|
const mergedExec$2 = mergeChildExec(context.exec, result.next.exec);
|
|
@@ -927,6 +1035,66 @@ function or(...args) {
|
|
|
927
1035
|
consumed: []
|
|
928
1036
|
};
|
|
929
1037
|
}
|
|
1038
|
+
if (provisionalConsuming !== null) {
|
|
1039
|
+
const activeIsLockedDifferent = activeState != null && activeState[1].success && activeState[1].consumed.length > 0 && activeState[0] !== provisionalConsuming.index;
|
|
1040
|
+
if (!activeIsLockedDifferent) {
|
|
1041
|
+
const mergedExec = mergeChildExec(context.exec, provisionalConsuming.result.next.exec);
|
|
1042
|
+
return {
|
|
1043
|
+
success: true,
|
|
1044
|
+
provisional: true,
|
|
1045
|
+
next: {
|
|
1046
|
+
...context,
|
|
1047
|
+
buffer: provisionalConsuming.result.next.buffer,
|
|
1048
|
+
optionsTerminated: provisionalConsuming.result.next.optionsTerminated,
|
|
1049
|
+
state: createExclusiveState(context.state, provisionalConsuming.index, provisionalConsuming.parser, provisionalConsuming.result),
|
|
1050
|
+
...mergedExec != null ? {
|
|
1051
|
+
exec: mergedExec,
|
|
1052
|
+
dependencyRegistry: mergedExec.dependencyRegistry
|
|
1053
|
+
} : {}
|
|
1054
|
+
},
|
|
1055
|
+
consumed: provisionalConsuming.result.consumed
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
if (activeState != null && activeState[1].success) {
|
|
1059
|
+
const previouslyConsumed = activeState[1].consumed;
|
|
1060
|
+
const checkResult = await provisionalConsuming.parser.parse({
|
|
1061
|
+
...withChildContext(context, provisionalConsuming.index, provisionalConsuming.parser.initialState, provisionalConsuming.parser),
|
|
1062
|
+
buffer: previouslyConsumed
|
|
1063
|
+
});
|
|
1064
|
+
const canConsumeShared = checkResult.success && checkResult.consumed.length === previouslyConsumed.length && checkResult.consumed.every((c, idx) => c === previouslyConsumed[idx]);
|
|
1065
|
+
if (canConsumeShared && checkResult.success) {
|
|
1066
|
+
const replayExec = mergeChildExec(context.exec, checkResult.next.exec);
|
|
1067
|
+
const replayedResult = await provisionalConsuming.parser.parse(withChildContext({
|
|
1068
|
+
...context,
|
|
1069
|
+
...replayExec != null ? {
|
|
1070
|
+
exec: replayExec,
|
|
1071
|
+
dependencyRegistry: replayExec.dependencyRegistry
|
|
1072
|
+
} : {}
|
|
1073
|
+
}, provisionalConsuming.index, checkResult.next.state, provisionalConsuming.parser));
|
|
1074
|
+
if (replayedResult.success) {
|
|
1075
|
+
const mergedExec = mergeChildExec(replayExec, replayedResult.next.exec);
|
|
1076
|
+
return {
|
|
1077
|
+
success: true,
|
|
1078
|
+
provisional: true,
|
|
1079
|
+
next: {
|
|
1080
|
+
...context,
|
|
1081
|
+
buffer: replayedResult.next.buffer,
|
|
1082
|
+
optionsTerminated: replayedResult.next.optionsTerminated,
|
|
1083
|
+
state: createExclusiveState(context.state, provisionalConsuming.index, provisionalConsuming.parser, {
|
|
1084
|
+
...replayedResult,
|
|
1085
|
+
consumed: [...previouslyConsumed, ...replayedResult.consumed]
|
|
1086
|
+
}),
|
|
1087
|
+
...mergedExec != null ? {
|
|
1088
|
+
exec: mergedExec,
|
|
1089
|
+
dependencyRegistry: mergedExec.dependencyRegistry
|
|
1090
|
+
} : {}
|
|
1091
|
+
},
|
|
1092
|
+
consumed: replayedResult.consumed
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
930
1098
|
return {
|
|
931
1099
|
...error,
|
|
932
1100
|
success: false
|
|
@@ -1020,7 +1188,8 @@ function longestMatch(...args) {
|
|
|
1020
1188
|
const result = parser.parse(withChildContext(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
|
|
1021
1189
|
if (result.success) {
|
|
1022
1190
|
const consumed = context.buffer.length - result.next.buffer.length;
|
|
1023
|
-
|
|
1191
|
+
const bestIsProvisional = bestMatch != null && bestMatch.result.success && !!bestMatch.result.provisional;
|
|
1192
|
+
if (bestMatch === null || consumed > bestMatch.consumed || consumed === bestMatch.consumed && bestIsProvisional && !result.provisional) bestMatch = {
|
|
1024
1193
|
index: i,
|
|
1025
1194
|
parser,
|
|
1026
1195
|
result,
|
|
@@ -1060,7 +1229,8 @@ function longestMatch(...args) {
|
|
|
1060
1229
|
const result = await resultOrPromise;
|
|
1061
1230
|
if (result.success) {
|
|
1062
1231
|
const consumed = context.buffer.length - result.next.buffer.length;
|
|
1063
|
-
|
|
1232
|
+
const bestIsProvisional = bestMatch != null && bestMatch.result.success && !!bestMatch.result.provisional;
|
|
1233
|
+
if (bestMatch === null || consumed > bestMatch.consumed || consumed === bestMatch.consumed && bestIsProvisional && !result.provisional) bestMatch = {
|
|
1064
1234
|
index: i,
|
|
1065
1235
|
parser,
|
|
1066
1236
|
result,
|
|
@@ -3656,18 +3826,35 @@ function group(label, parser, options = {}) {
|
|
|
3656
3826
|
* // defaultResult.value = [undefined, {}]
|
|
3657
3827
|
* ```
|
|
3658
3828
|
*
|
|
3659
|
-
* ###
|
|
3829
|
+
* ### Speculative branch parsing
|
|
3660
3830
|
*
|
|
3661
3831
|
* When the discriminator is an async parser that succeeds without consuming
|
|
3662
3832
|
* 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
|
-
*
|
|
3833
|
+
* is normally deferred to the complete phase. To allow branch-specific
|
|
3834
|
+
* tokens to be consumed, `conditional()` speculatively tries all named
|
|
3835
|
+
* branches during parse. If exactly one branch can consume tokens, it is
|
|
3836
|
+
* tentatively selected and verified against the resolved discriminator
|
|
3837
|
+
* during the complete phase.
|
|
3838
|
+
*
|
|
3839
|
+
* If the discriminator resolves to a different branch than the one that
|
|
3840
|
+
* consumed tokens (contradictory input), the parse fails. When multiple
|
|
3841
|
+
* branches can consume the same tokens (ambiguous), speculation is skipped
|
|
3842
|
+
* entirely to keep branch selection order-independent.
|
|
3843
|
+
*
|
|
3844
|
+
* #### Known limitations
|
|
3845
|
+
*
|
|
3846
|
+
* - When a default branch accepts the same tokens as a named branch,
|
|
3847
|
+
* speculation prefers the named branch. If the discriminator later
|
|
3848
|
+
* resolves to a value not in the named branches, the parse fails
|
|
3849
|
+
* instead of falling back to the default branch. To avoid this,
|
|
3850
|
+
* ensure named branch options are distinct from the default branch.
|
|
3851
|
+
* - Within `longestMatch()`, a longer speculative match can beat a
|
|
3852
|
+
* shorter definitive one. If the speculative match fails during
|
|
3853
|
+
* completion, the tokens consumed by it are not recoverable.
|
|
3854
|
+
* - The dependency runtime seeds both discriminator and branch sources
|
|
3855
|
+
* before verifying the speculative selection. A discriminator that
|
|
3856
|
+
* depends on branch-local dependency sources could be circularly
|
|
3857
|
+
* confirmed by the speculative branch.
|
|
3671
3858
|
*
|
|
3672
3859
|
* @since 0.8.0
|
|
3673
3860
|
*/
|
|
@@ -3880,13 +4067,87 @@ function conditional(discriminator, branches, defaultBranch, options) {
|
|
|
3880
4067
|
const discriminatorResult = await discriminator.parse({ ...withChildContext(context, "_discriminator", state.discriminatorState, discriminator) });
|
|
3881
4068
|
if (discriminatorResult.success) {
|
|
3882
4069
|
if (discriminatorResult.consumed.length === 0 && discriminator.$mode === "async") {
|
|
4070
|
+
const discriminatorExec = mergeChildExec(context.exec, discriminatorResult.next.exec);
|
|
4071
|
+
const speculationContext = {
|
|
4072
|
+
...context,
|
|
4073
|
+
buffer: discriminatorResult.next.buffer,
|
|
4074
|
+
optionsTerminated: discriminatorResult.next.optionsTerminated,
|
|
4075
|
+
...discriminatorExec != null ? {
|
|
4076
|
+
exec: discriminatorExec,
|
|
4077
|
+
dependencyRegistry: discriminatorExec.dependencyRegistry
|
|
4078
|
+
} : {}
|
|
4079
|
+
};
|
|
4080
|
+
let speculativeHit;
|
|
4081
|
+
let provisionalHit;
|
|
4082
|
+
let provisionalAmbiguous = false;
|
|
4083
|
+
let speculativeError;
|
|
4084
|
+
let ambiguous = false;
|
|
4085
|
+
for (const [key, bp] of branchParsers) {
|
|
4086
|
+
const branchResult = await bp.parse(withChildContext(speculationContext, "_branch", bp.initialState, bp, bp.usage));
|
|
4087
|
+
if (branchResult.success && branchResult.consumed.length > 0) {
|
|
4088
|
+
if (branchResult.provisional) {
|
|
4089
|
+
if (provisionalHit == null && !provisionalAmbiguous) provisionalHit = {
|
|
4090
|
+
key,
|
|
4091
|
+
bp,
|
|
4092
|
+
result: branchResult
|
|
4093
|
+
};
|
|
4094
|
+
else {
|
|
4095
|
+
provisionalHit = void 0;
|
|
4096
|
+
provisionalAmbiguous = true;
|
|
4097
|
+
}
|
|
4098
|
+
continue;
|
|
4099
|
+
}
|
|
4100
|
+
if (speculativeHit != null) {
|
|
4101
|
+
ambiguous = true;
|
|
4102
|
+
break;
|
|
4103
|
+
}
|
|
4104
|
+
speculativeHit = {
|
|
4105
|
+
key,
|
|
4106
|
+
bp,
|
|
4107
|
+
result: branchResult
|
|
4108
|
+
};
|
|
4109
|
+
}
|
|
4110
|
+
if (!branchResult.success && branchResult.consumed > 0 && (speculativeError == null || speculativeError.consumed < branchResult.consumed)) speculativeError = branchResult;
|
|
4111
|
+
}
|
|
4112
|
+
if (speculativeHit != null && (provisionalHit != null || provisionalAmbiguous)) ambiguous = true;
|
|
4113
|
+
if (speculativeHit == null && !ambiguous && !provisionalAmbiguous && provisionalHit != null) speculativeHit = provisionalHit;
|
|
4114
|
+
if (speculativeHit != null && !ambiguous) {
|
|
4115
|
+
const { key, bp, result: branchResult } = speculativeHit;
|
|
4116
|
+
if (branchResult.success) {
|
|
4117
|
+
const annotatedDiscriminatorState$2 = getAnnotatedChildState(state, discriminatorResult.next.state, discriminator);
|
|
4118
|
+
const mergedExec = mergeChildExec(discriminatorExec, branchResult.next.exec);
|
|
4119
|
+
return {
|
|
4120
|
+
success: true,
|
|
4121
|
+
provisional: true,
|
|
4122
|
+
next: {
|
|
4123
|
+
...branchResult.next,
|
|
4124
|
+
state: {
|
|
4125
|
+
...state,
|
|
4126
|
+
discriminatorState: annotatedDiscriminatorState$2,
|
|
4127
|
+
selectedBranch: {
|
|
4128
|
+
kind: "branch",
|
|
4129
|
+
key
|
|
4130
|
+
},
|
|
4131
|
+
branchState: getAnnotatedChildState(state, branchResult.next.state, bp),
|
|
4132
|
+
speculative: true
|
|
4133
|
+
},
|
|
4134
|
+
...mergedExec != null ? {
|
|
4135
|
+
exec: mergedExec,
|
|
4136
|
+
dependencyRegistry: mergedExec.dependencyRegistry
|
|
4137
|
+
} : {}
|
|
4138
|
+
},
|
|
4139
|
+
consumed: branchResult.consumed
|
|
4140
|
+
};
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
3883
4143
|
let deferredBranchState = state.branchState;
|
|
3884
4144
|
if (defaultBranch !== void 0) {
|
|
3885
|
-
const defaultResult = await defaultBranch.parse(withChildContext(
|
|
4145
|
+
const defaultResult = await defaultBranch.parse(withChildContext(speculationContext, "_branch", state.branchState ?? defaultBranch.initialState, defaultBranch, defaultBranch.usage));
|
|
3886
4146
|
if (defaultResult.success && defaultResult.consumed.length > 0) {
|
|
3887
|
-
const defaultExec = mergeChildExec(context.exec, defaultResult.next.exec);
|
|
4147
|
+
const defaultExec = mergeChildExec(discriminatorExec ?? context.exec, defaultResult.next.exec);
|
|
3888
4148
|
return {
|
|
3889
4149
|
success: true,
|
|
4150
|
+
...defaultResult.provisional ? { provisional: true } : {},
|
|
3890
4151
|
next: {
|
|
3891
4152
|
...defaultResult.next,
|
|
3892
4153
|
state: {
|
|
@@ -3903,10 +4164,10 @@ function conditional(discriminator, branches, defaultBranch, options) {
|
|
|
3903
4164
|
};
|
|
3904
4165
|
}
|
|
3905
4166
|
if (!defaultResult.success && defaultResult.consumed > 0) return defaultResult;
|
|
3906
|
-
if (defaultResult.success && defaultResult.consumed.length === 0 &&
|
|
4167
|
+
if (defaultResult.success && defaultResult.consumed.length === 0 && speculationContext.buffer.length === 0) deferredBranchState = getAnnotatedChildState(state, defaultResult.next.state, defaultBranch);
|
|
3907
4168
|
}
|
|
4169
|
+
if (speculativeError != null && !ambiguous) return speculativeError;
|
|
3908
4170
|
const annotatedDiscriminatorState$1 = getAnnotatedChildState(state, discriminatorResult.next.state, discriminator);
|
|
3909
|
-
const mergedExec = mergeChildExec(context.exec, discriminatorResult.next.exec);
|
|
3910
4171
|
return {
|
|
3911
4172
|
success: true,
|
|
3912
4173
|
provisional: true,
|
|
@@ -3917,9 +4178,9 @@ function conditional(discriminator, branches, defaultBranch, options) {
|
|
|
3917
4178
|
discriminatorState: annotatedDiscriminatorState$1,
|
|
3918
4179
|
branchState: deferredBranchState
|
|
3919
4180
|
},
|
|
3920
|
-
...
|
|
3921
|
-
exec:
|
|
3922
|
-
dependencyRegistry:
|
|
4181
|
+
...discriminatorExec != null ? {
|
|
4182
|
+
exec: discriminatorExec,
|
|
4183
|
+
dependencyRegistry: discriminatorExec.dependencyRegistry
|
|
3923
4184
|
} : {}
|
|
3924
4185
|
},
|
|
3925
4186
|
consumed: []
|
|
@@ -4141,6 +4402,18 @@ function conditional(discriminator, branches, defaultBranch, options) {
|
|
|
4141
4402
|
};
|
|
4142
4403
|
};
|
|
4143
4404
|
const completeAsync = async (state, exec) => {
|
|
4405
|
+
let wasSpeculative = false;
|
|
4406
|
+
if (state.speculative && state.selectedBranch?.kind === "branch") if (exec?.phase !== "parse" && exec?.phase !== "suggest") {
|
|
4407
|
+
wasSpeculative = true;
|
|
4408
|
+
state = {
|
|
4409
|
+
...state,
|
|
4410
|
+
speculative: void 0
|
|
4411
|
+
};
|
|
4412
|
+
} else state = {
|
|
4413
|
+
...state,
|
|
4414
|
+
discriminatorValue: state.selectedBranch.key,
|
|
4415
|
+
speculative: void 0
|
|
4416
|
+
};
|
|
4144
4417
|
if (state.selectedBranch === void 0) {
|
|
4145
4418
|
if (exec?.phase !== "parse" && exec?.phase !== "suggest") {
|
|
4146
4419
|
const annotatedDiscriminatorStateForDeferred = getAnnotatedChildState(state, state.discriminatorState, discriminator);
|
|
@@ -4225,20 +4498,30 @@ function conditional(discriminator, branches, defaultBranch, options) {
|
|
|
4225
4498
|
};
|
|
4226
4499
|
const needsDiscriminatorCompletion = state.selectedBranch.kind !== "default" && !(state.discriminatorValue != null && state.discriminatorValue === state.selectedBranch.key);
|
|
4227
4500
|
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
4501
|
let discriminatorValue;
|
|
4237
4502
|
if (state.selectedBranch.kind === "default") discriminatorValue = void 0;
|
|
4238
4503
|
else if (state.discriminatorValue != null && state.discriminatorValue === state.selectedBranch.key) discriminatorValue = state.discriminatorValue;
|
|
4239
4504
|
else {
|
|
4240
4505
|
const completedDiscriminator = unwrapCompleteResult(discriminatorCompleteResult);
|
|
4241
|
-
|
|
4506
|
+
if (completedDiscriminator.success) discriminatorValue = completedDiscriminator.value;
|
|
4507
|
+
else if (wasSpeculative) return completedDiscriminator;
|
|
4508
|
+
else discriminatorValue = state.selectedBranch.key;
|
|
4509
|
+
}
|
|
4510
|
+
if (wasSpeculative && state.selectedBranch.kind === "branch" && discriminatorValue !== state.selectedBranch.key) {
|
|
4511
|
+
const speculativeKey = state.selectedBranch.key;
|
|
4512
|
+
const resolvedKey = discriminatorValue ?? "";
|
|
4513
|
+
return {
|
|
4514
|
+
success: false,
|
|
4515
|
+
error: options?.errors?.branchMismatch ? options.errors.branchMismatch(resolvedKey, speculativeKey) : message`Branch mismatch: tokens for ${speculativeKey} were consumed, but the discriminator resolved to ${resolvedKey}.`
|
|
4516
|
+
};
|
|
4517
|
+
}
|
|
4518
|
+
const branchResult = unwrapCompleteResult(await branchParser.complete(resolvedBranchState, withChildExecPath(completionExec, "_branch")));
|
|
4519
|
+
if (!branchResult.success) {
|
|
4520
|
+
if (discriminatorValue !== void 0 && options?.errors?.branchError) return {
|
|
4521
|
+
success: false,
|
|
4522
|
+
error: options.errors.branchError(discriminatorValue, branchResult.error)
|
|
4523
|
+
};
|
|
4524
|
+
return branchResult;
|
|
4242
4525
|
}
|
|
4243
4526
|
return {
|
|
4244
4527
|
success: true,
|