@optique/core 1.0.0-dev.1712 → 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.
@@ -718,6 +718,16 @@ function or(...args) {
718
718
  const result = parser.parse(withChildContext(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
719
719
  if (result.success && result.consumed.length > 0) {
720
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;
721
731
  if (provisionalConsuming == null && !provisionalAmbiguous) provisionalConsuming = {
722
732
  index: i,
723
733
  parser,
@@ -826,23 +836,65 @@ function or(...args) {
826
836
  consumed: []
827
837
  };
828
838
  }
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
- };
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
+ }
846
898
  }
847
899
  return {
848
900
  ...error,
@@ -863,6 +915,16 @@ function or(...args) {
863
915
  const result = await resultOrPromise;
864
916
  if (result.success && result.consumed.length > 0) {
865
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;
866
928
  if (provisionalConsuming == null && !provisionalAmbiguous) provisionalConsuming = {
867
929
  index: i,
868
930
  parser,
@@ -973,23 +1035,65 @@ function or(...args) {
973
1035
  consumed: []
974
1036
  };
975
1037
  }
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
- };
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
+ }
993
1097
  }
994
1098
  return {
995
1099
  ...error,
@@ -3964,19 +4068,22 @@ function conditional(discriminator, branches, defaultBranch, options) {
3964
4068
  if (discriminatorResult.success) {
3965
4069
  if (discriminatorResult.consumed.length === 0 && discriminator.$mode === "async") {
3966
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
+ };
3967
4080
  let speculativeHit;
3968
4081
  let provisionalHit;
3969
4082
  let provisionalAmbiguous = false;
3970
4083
  let speculativeError;
3971
4084
  let ambiguous = false;
3972
4085
  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));
4086
+ const branchResult = await bp.parse(withChildContext(speculationContext, "_branch", bp.initialState, bp, bp.usage));
3980
4087
  if (branchResult.success && branchResult.consumed.length > 0) {
3981
4088
  if (branchResult.provisional) {
3982
4089
  if (provisionalHit == null && !provisionalAmbiguous) provisionalHit = {
@@ -4035,9 +4142,9 @@ function conditional(discriminator, branches, defaultBranch, options) {
4035
4142
  }
4036
4143
  let deferredBranchState = state.branchState;
4037
4144
  if (defaultBranch !== void 0) {
4038
- const defaultResult = await defaultBranch.parse(withChildContext(context, "_branch", state.branchState ?? defaultBranch.initialState, defaultBranch, defaultBranch.usage));
4145
+ const defaultResult = await defaultBranch.parse(withChildContext(speculationContext, "_branch", state.branchState ?? defaultBranch.initialState, defaultBranch, defaultBranch.usage));
4039
4146
  if (defaultResult.success && defaultResult.consumed.length > 0) {
4040
- const defaultExec = mergeChildExec(context.exec, defaultResult.next.exec);
4147
+ const defaultExec = mergeChildExec(discriminatorExec ?? context.exec, defaultResult.next.exec);
4041
4148
  return {
4042
4149
  success: true,
4043
4150
  ...defaultResult.provisional ? { provisional: true } : {},
@@ -4057,7 +4164,7 @@ function conditional(discriminator, branches, defaultBranch, options) {
4057
4164
  };
4058
4165
  }
4059
4166
  if (!defaultResult.success && defaultResult.consumed > 0) return defaultResult;
4060
- if (defaultResult.success && defaultResult.consumed.length === 0 && context.buffer.length === 0) deferredBranchState = getAnnotatedChildState(state, defaultResult.next.state, defaultBranch);
4167
+ if (defaultResult.success && defaultResult.consumed.length === 0 && speculationContext.buffer.length === 0) deferredBranchState = getAnnotatedChildState(state, defaultResult.next.state, defaultBranch);
4061
4168
  }
4062
4169
  if (speculativeError != null && !ambiguous) return speculativeError;
4063
4170
  const annotatedDiscriminatorState$1 = getAnnotatedChildState(state, discriminatorResult.next.state, discriminator);
@@ -4400,10 +4507,14 @@ function conditional(discriminator, branches, defaultBranch, options) {
4400
4507
  else if (wasSpeculative) return completedDiscriminator;
4401
4508
  else discriminatorValue = state.selectedBranch.key;
4402
4509
  }
4403
- if (wasSpeculative && state.selectedBranch.kind === "branch" && discriminatorValue !== state.selectedBranch.key) return {
4404
- success: false,
4405
- error: getNoMatchError$1()
4406
- };
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
+ }
4407
4518
  const branchResult = unwrapCompleteResult(await branchParser.complete(resolvedBranchState, withChildExecPath(completionExec, "_branch")));
4408
4519
  if (!branchResult.success) {
4409
4520
  if (discriminatorValue !== void 0 && options?.errors?.branchError) return {
@@ -1332,6 +1332,20 @@ interface ConditionalErrorOptions {
1332
1332
  * Custom error message for no matching input.
1333
1333
  */
1334
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;
1335
1349
  }
1336
1350
  /**
1337
1351
  * Options for customizing the {@link conditional} combinator behavior.
@@ -1332,6 +1332,20 @@ interface ConditionalErrorOptions {
1332
1332
  * Custom error message for no matching input.
1333
1333
  */
1334
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;
1335
1349
  }
1336
1350
  /**
1337
1351
  * Options for customizing the {@link conditional} combinator behavior.
@@ -718,6 +718,16 @@ function or(...args) {
718
718
  const result = parser.parse(withChildContext(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
719
719
  if (result.success && result.consumed.length > 0) {
720
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;
721
731
  if (provisionalConsuming == null && !provisionalAmbiguous) provisionalConsuming = {
722
732
  index: i,
723
733
  parser,
@@ -826,23 +836,65 @@ function or(...args) {
826
836
  consumed: []
827
837
  };
828
838
  }
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
- };
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
+ }
846
898
  }
847
899
  return {
848
900
  ...error,
@@ -863,6 +915,16 @@ function or(...args) {
863
915
  const result = await resultOrPromise;
864
916
  if (result.success && result.consumed.length > 0) {
865
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;
866
928
  if (provisionalConsuming == null && !provisionalAmbiguous) provisionalConsuming = {
867
929
  index: i,
868
930
  parser,
@@ -973,23 +1035,65 @@ function or(...args) {
973
1035
  consumed: []
974
1036
  };
975
1037
  }
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
- };
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
+ }
993
1097
  }
994
1098
  return {
995
1099
  ...error,
@@ -3964,19 +4068,22 @@ function conditional(discriminator, branches, defaultBranch, options) {
3964
4068
  if (discriminatorResult.success) {
3965
4069
  if (discriminatorResult.consumed.length === 0 && discriminator.$mode === "async") {
3966
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
+ };
3967
4080
  let speculativeHit;
3968
4081
  let provisionalHit;
3969
4082
  let provisionalAmbiguous = false;
3970
4083
  let speculativeError;
3971
4084
  let ambiguous = false;
3972
4085
  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));
4086
+ const branchResult = await bp.parse(withChildContext(speculationContext, "_branch", bp.initialState, bp, bp.usage));
3980
4087
  if (branchResult.success && branchResult.consumed.length > 0) {
3981
4088
  if (branchResult.provisional) {
3982
4089
  if (provisionalHit == null && !provisionalAmbiguous) provisionalHit = {
@@ -4035,9 +4142,9 @@ function conditional(discriminator, branches, defaultBranch, options) {
4035
4142
  }
4036
4143
  let deferredBranchState = state.branchState;
4037
4144
  if (defaultBranch !== void 0) {
4038
- const defaultResult = await defaultBranch.parse(withChildContext(context, "_branch", state.branchState ?? defaultBranch.initialState, defaultBranch, defaultBranch.usage));
4145
+ const defaultResult = await defaultBranch.parse(withChildContext(speculationContext, "_branch", state.branchState ?? defaultBranch.initialState, defaultBranch, defaultBranch.usage));
4039
4146
  if (defaultResult.success && defaultResult.consumed.length > 0) {
4040
- const defaultExec = mergeChildExec(context.exec, defaultResult.next.exec);
4147
+ const defaultExec = mergeChildExec(discriminatorExec ?? context.exec, defaultResult.next.exec);
4041
4148
  return {
4042
4149
  success: true,
4043
4150
  ...defaultResult.provisional ? { provisional: true } : {},
@@ -4057,7 +4164,7 @@ function conditional(discriminator, branches, defaultBranch, options) {
4057
4164
  };
4058
4165
  }
4059
4166
  if (!defaultResult.success && defaultResult.consumed > 0) return defaultResult;
4060
- if (defaultResult.success && defaultResult.consumed.length === 0 && context.buffer.length === 0) deferredBranchState = getAnnotatedChildState(state, defaultResult.next.state, defaultBranch);
4167
+ if (defaultResult.success && defaultResult.consumed.length === 0 && speculationContext.buffer.length === 0) deferredBranchState = getAnnotatedChildState(state, defaultResult.next.state, defaultBranch);
4061
4168
  }
4062
4169
  if (speculativeError != null && !ambiguous) return speculativeError;
4063
4170
  const annotatedDiscriminatorState$1 = getAnnotatedChildState(state, discriminatorResult.next.state, discriminator);
@@ -4400,10 +4507,14 @@ function conditional(discriminator, branches, defaultBranch, options) {
4400
4507
  else if (wasSpeculative) return completedDiscriminator;
4401
4508
  else discriminatorValue = state.selectedBranch.key;
4402
4509
  }
4403
- if (wasSpeculative && state.selectedBranch.kind === "branch" && discriminatorValue !== state.selectedBranch.key) return {
4404
- success: false,
4405
- error: getNoMatchError$1()
4406
- };
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
+ }
4407
4518
  const branchResult = unwrapCompleteResult(await branchParser.complete(resolvedBranchState, withChildExecPath(completionExec, "_branch")));
4408
4519
  if (!branchResult.success) {
4409
4520
  if (discriminatorValue !== void 0 && options?.errors?.branchError) return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.0.0-dev.1712+ea879e28",
3
+ "version": "1.0.0-dev.1714+ef964285",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",