@optique/core 1.0.4 → 1.0.6

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.
@@ -516,6 +516,35 @@ function createExclusiveState(parentState, index, parser, result) {
516
516
  return require_annotations.annotateFreshArray(parentState, [index, annotatedResult]);
517
517
  }
518
518
  /**
519
+ * Detects a branch result that handled only the global options terminator.
520
+ * Such a result advances the shared parser context, but does not identify
521
+ * which exclusive branch matched.
522
+ * @internal
523
+ */
524
+ function isOptionsTerminatorOnlyResult(context, result) {
525
+ return !context.optionsTerminated && context.buffer[0] === "--" && result.success && result.next.optionsTerminated && result.consumed.length === 1 && result.consumed[0] === "--";
526
+ }
527
+ function isUnselectedBooleanOptionState(state) {
528
+ return state != null && typeof state === "object" && "success" in state && state.success === true && "value" in state && state.value === false;
529
+ }
530
+ function preserveExclusiveStateAfterOptionsTerminator(context, result) {
531
+ const mergedExec = require_execution_context.mergeChildExec(context.exec, result.next.exec);
532
+ return {
533
+ success: true,
534
+ next: {
535
+ ...context,
536
+ buffer: result.next.buffer,
537
+ optionsTerminated: true,
538
+ state: context.state,
539
+ ...mergedExec != null ? {
540
+ exec: mergedExec,
541
+ dependencyRegistry: mergedExec.dependencyRegistry
542
+ } : {}
543
+ },
544
+ consumed: result.consumed
545
+ };
546
+ }
547
+ /**
519
548
  * Creates a complete() method shared by or() and longestMatch().
520
549
  * @internal
521
550
  */
@@ -752,9 +781,15 @@ function or(...args) {
752
781
  let zeroConsumedBranch = null;
753
782
  let zeroConsumedCount = 0;
754
783
  let provisionalConsuming = null;
784
+ let optionsTerminatorResult = null;
755
785
  let provisionalAmbiguous = false;
756
786
  for (const [parser, i] of orderedParsers) {
757
787
  const result = parser.parse(withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
788
+ if (isOptionsTerminatorOnlyResult(context, result)) {
789
+ if (activeState?.[0] === i && activeState[1].success && activeState[1].consumed.length > 0 && result.next.buffer.length < 1) return preserveExclusiveStateAfterOptionsTerminator(context, result);
790
+ optionsTerminatorResult ??= result;
791
+ continue;
792
+ }
758
793
  if (result.success && result.consumed.length > 0) {
759
794
  if (result.provisional) {
760
795
  const activeBranchLocked = activeState != null && activeState[1].success && activeState[1].consumed.length > 0;
@@ -935,6 +970,7 @@ function or(...args) {
935
970
  }
936
971
  }
937
972
  }
973
+ if (optionsTerminatorResult != null) return preserveExclusiveStateAfterOptionsTerminator(context, optionsTerminatorResult);
938
974
  return {
939
975
  ...error,
940
976
  success: false
@@ -948,10 +984,16 @@ function or(...args) {
948
984
  let zeroConsumedBranch = null;
949
985
  let zeroConsumedCount = 0;
950
986
  let provisionalConsuming = null;
987
+ let optionsTerminatorResult = null;
951
988
  let provisionalAmbiguous = false;
952
989
  for (const [parser, i] of orderedParsers) {
953
990
  const resultOrPromise = parser.parse(withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
954
991
  const result = await resultOrPromise;
992
+ if (isOptionsTerminatorOnlyResult(context, result)) {
993
+ if (activeState?.[0] === i && activeState[1].success && activeState[1].consumed.length > 0 && result.next.buffer.length < 1) return preserveExclusiveStateAfterOptionsTerminator(context, result);
994
+ optionsTerminatorResult ??= result;
995
+ continue;
996
+ }
955
997
  if (result.success && result.consumed.length > 0) {
956
998
  if (result.provisional) {
957
999
  const activeBranchLocked = activeState != null && activeState[1].success && activeState[1].consumed.length > 0;
@@ -1134,6 +1176,7 @@ function or(...args) {
1134
1176
  }
1135
1177
  }
1136
1178
  }
1179
+ if (optionsTerminatorResult != null) return preserveExclusiveStateAfterOptionsTerminator(context, optionsTerminatorResult);
1137
1180
  return {
1138
1181
  ...error,
1139
1182
  success: false
@@ -1221,82 +1264,122 @@ function longestMatch(...args) {
1221
1264
  return createUnexpectedInputErrorWithScopedSuggestions(defaultMsg, token, parsers, options?.errors?.suggestions);
1222
1265
  })()
1223
1266
  });
1224
- const parseSync = (context) => {
1267
+ const commitLongestMatch = (context, match, optionsTerminatorResult) => {
1268
+ const selectedResult = optionsTerminatorResult == null ? match.result : {
1269
+ ...match.result,
1270
+ next: {
1271
+ ...match.result.next,
1272
+ buffer: optionsTerminatorResult.next.buffer,
1273
+ optionsTerminated: true
1274
+ },
1275
+ consumed: [...optionsTerminatorResult.consumed, ...match.result.consumed]
1276
+ };
1277
+ const parser = parsers[match.index];
1278
+ const mergedExec = require_execution_context.mergeChildExec(context.exec, selectedResult.next.exec);
1279
+ return {
1280
+ success: true,
1281
+ next: {
1282
+ ...context,
1283
+ buffer: selectedResult.next.buffer,
1284
+ optionsTerminated: selectedResult.next.optionsTerminated,
1285
+ state: createExclusiveState(context.state, match.index, parser, selectedResult),
1286
+ ...mergedExec != null ? {
1287
+ exec: mergedExec,
1288
+ dependencyRegistry: mergedExec.dependencyRegistry
1289
+ } : {}
1290
+ },
1291
+ consumed: selectedResult.consumed
1292
+ };
1293
+ };
1294
+ const resolveLongestMatchOptionsTerminator = (context, bestMatch, optionsTerminatorMatches) => {
1295
+ if (optionsTerminatorMatches.length < 1) return null;
1296
+ if (bestMatch != null && bestMatch.consumed > 0) return null;
1297
+ const activeState = normalizeExclusiveState(context.state);
1298
+ if (activeState != null && activeState[1].success && activeState[1].consumed.length > 0) return preserveExclusiveStateAfterOptionsTerminator(context, optionsTerminatorMatches[0].result);
1299
+ let selected = bestMatch == null ? null : {
1300
+ match: bestMatch,
1301
+ fromTerminator: false
1302
+ };
1303
+ for (const match of optionsTerminatorMatches) {
1304
+ if (!match.selectedAfterTerminator && (extractRequiredUsage(parsers[match.index].usage).length > 0 || isUnselectedBooleanOptionState(match.result.next.state))) continue;
1305
+ const selectedIsProvisional = selected != null && !!selected.match.result.provisional;
1306
+ const matchIsProvisional = !!match.result.provisional;
1307
+ if (selected == null || selectedIsProvisional && !matchIsProvisional || selectedIsProvisional === matchIsProvisional && match.index < selected.match.index) selected = {
1308
+ match,
1309
+ fromTerminator: true
1310
+ };
1311
+ }
1312
+ if (selected != null) return commitLongestMatch(context, selected.match, selected.fromTerminator ? void 0 : optionsTerminatorMatches[0].result);
1313
+ return preserveExclusiveStateAfterOptionsTerminator(context, optionsTerminatorMatches[0].result);
1314
+ };
1315
+ const parseSyncBranches = (context) => {
1225
1316
  let bestMatch = null;
1317
+ const optionsTerminatorMatches = [];
1226
1318
  let error = getInitialError(context);
1227
1319
  const activeState = normalizeExclusiveState(context.state);
1228
1320
  for (let i = 0; i < syncParsers.length; i++) {
1229
1321
  const parser = syncParsers[i];
1230
- const result = parser.parse(withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
1322
+ const childContext = withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser);
1323
+ const result = parser.parse(childContext);
1324
+ if (isOptionsTerminatorOnlyResult(context, result)) {
1325
+ optionsTerminatorMatches.push({
1326
+ index: i,
1327
+ result,
1328
+ consumed: 1,
1329
+ ...normalizeExclusiveState(result.next.state) != null ? { selectedAfterTerminator: true } : {}
1330
+ });
1331
+ continue;
1332
+ }
1231
1333
  if (result.success) {
1232
1334
  const consumed = context.buffer.length - result.next.buffer.length;
1233
1335
  const bestIsProvisional = bestMatch != null && bestMatch.result.success && !!bestMatch.result.provisional;
1234
1336
  if (bestMatch === null || consumed > bestMatch.consumed || consumed === bestMatch.consumed && bestIsProvisional && !result.provisional) bestMatch = {
1235
1337
  index: i,
1236
- parser,
1237
1338
  result,
1238
1339
  consumed
1239
1340
  };
1240
1341
  } else if (error.consumed < result.consumed) error = result;
1241
1342
  }
1242
- if (bestMatch && bestMatch.result.success) {
1243
- const mergedExec = require_execution_context.mergeChildExec(context.exec, bestMatch.result.next.exec);
1244
- return {
1245
- success: true,
1246
- next: {
1247
- ...context,
1248
- buffer: bestMatch.result.next.buffer,
1249
- optionsTerminated: bestMatch.result.next.optionsTerminated,
1250
- state: createExclusiveState(context.state, bestMatch.index, bestMatch.parser, bestMatch.result),
1251
- ...mergedExec != null ? {
1252
- exec: mergedExec,
1253
- dependencyRegistry: mergedExec.dependencyRegistry
1254
- } : {}
1255
- },
1256
- consumed: bestMatch.result.consumed
1257
- };
1258
- }
1343
+ const optionsTerminatorResolution = resolveLongestMatchOptionsTerminator(context, bestMatch, optionsTerminatorMatches);
1344
+ if (optionsTerminatorResolution != null) return optionsTerminatorResolution;
1345
+ if (bestMatch != null) return commitLongestMatch(context, bestMatch);
1259
1346
  return {
1260
1347
  ...error,
1261
1348
  success: false
1262
1349
  };
1263
1350
  };
1264
- const parseAsync = async (context) => {
1351
+ const parseAsyncBranches = async (context) => {
1265
1352
  let bestMatch = null;
1353
+ const optionsTerminatorMatches = [];
1266
1354
  let error = getInitialError(context);
1267
1355
  const activeState = normalizeExclusiveState(context.state);
1268
1356
  for (let i = 0; i < parsers.length; i++) {
1269
1357
  const parser = parsers[i];
1270
- const resultOrPromise = parser.parse(withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
1358
+ const childContext = withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser);
1359
+ const resultOrPromise = parser.parse(childContext);
1271
1360
  const result = await resultOrPromise;
1361
+ if (isOptionsTerminatorOnlyResult(context, result)) {
1362
+ optionsTerminatorMatches.push({
1363
+ index: i,
1364
+ result,
1365
+ consumed: 1,
1366
+ ...normalizeExclusiveState(result.next.state) != null ? { selectedAfterTerminator: true } : {}
1367
+ });
1368
+ continue;
1369
+ }
1272
1370
  if (result.success) {
1273
1371
  const consumed = context.buffer.length - result.next.buffer.length;
1274
1372
  const bestIsProvisional = bestMatch != null && bestMatch.result.success && !!bestMatch.result.provisional;
1275
1373
  if (bestMatch === null || consumed > bestMatch.consumed || consumed === bestMatch.consumed && bestIsProvisional && !result.provisional) bestMatch = {
1276
1374
  index: i,
1277
- parser,
1278
1375
  result,
1279
1376
  consumed
1280
1377
  };
1281
1378
  } else if (error.consumed < result.consumed) error = result;
1282
1379
  }
1283
- if (bestMatch && bestMatch.result.success) {
1284
- const mergedExec = require_execution_context.mergeChildExec(context.exec, bestMatch.result.next.exec);
1285
- return {
1286
- success: true,
1287
- next: {
1288
- ...context,
1289
- buffer: bestMatch.result.next.buffer,
1290
- optionsTerminated: bestMatch.result.next.optionsTerminated,
1291
- state: createExclusiveState(context.state, bestMatch.index, bestMatch.parser, bestMatch.result),
1292
- ...mergedExec != null ? {
1293
- exec: mergedExec,
1294
- dependencyRegistry: mergedExec.dependencyRegistry
1295
- } : {}
1296
- },
1297
- consumed: bestMatch.result.consumed
1298
- };
1299
- }
1380
+ const optionsTerminatorResolution = resolveLongestMatchOptionsTerminator(context, bestMatch, optionsTerminatorMatches);
1381
+ if (optionsTerminatorResolution != null) return optionsTerminatorResolution;
1382
+ if (bestMatch != null) return commitLongestMatch(context, bestMatch);
1300
1383
  return {
1301
1384
  ...error,
1302
1385
  success: false
@@ -1319,7 +1402,7 @@ function longestMatch(...args) {
1319
1402
  return extractExclusivePhase2Seed(parsers, state, exec, combinedMode);
1320
1403
  },
1321
1404
  parse(context) {
1322
- return require_mode_dispatch.dispatchByMode(combinedMode, () => parseSync(context), () => parseAsync(context));
1405
+ return require_mode_dispatch.dispatchByMode(combinedMode, () => parseSyncBranches(context), () => parseAsyncBranches(context));
1323
1406
  },
1324
1407
  getSuggestRuntimeNodes(state, path) {
1325
1408
  return getExclusiveSuggestRuntimeNodes(parsers, state, path);
@@ -1365,6 +1448,33 @@ function longestMatch(...args) {
1365
1448
  return multiResult;
1366
1449
  }
1367
1450
  /**
1451
+ * Creates the initial parse error shared by object-like combinators.
1452
+ * @param context The current parser context.
1453
+ * @param noMatchContext The kinds of input accepted by the combinator.
1454
+ * @param errors Optional custom error formatters.
1455
+ * @returns A zero-consumption parse error.
1456
+ */
1457
+ function createObjectLikeInitialError(context, noMatchContext, errors) {
1458
+ if (context.buffer.length < 1) {
1459
+ const customEndOfInput = errors?.endOfInput;
1460
+ return {
1461
+ consumed: 0,
1462
+ error: customEndOfInput ? typeof customEndOfInput === "function" ? customEndOfInput(noMatchContext) : customEndOfInput : generateNoMatchError(noMatchContext)
1463
+ };
1464
+ }
1465
+ const token = context.buffer[0];
1466
+ const customMessage = errors?.unexpectedInput;
1467
+ if (customMessage) return {
1468
+ consumed: 0,
1469
+ error: typeof customMessage === "function" ? customMessage(token) : customMessage
1470
+ };
1471
+ const baseError = require_message.message`Unexpected option or argument: ${token}.`;
1472
+ return {
1473
+ consumed: 0,
1474
+ error: require_suggestion.createErrorWithSuggestions(baseError, token, context.usage, "both", errors?.suggestions)
1475
+ };
1476
+ }
1477
+ /**
1368
1478
  * Internal sync helper for object suggest functionality.
1369
1479
  * @internal
1370
1480
  */
@@ -1756,19 +1866,7 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1756
1866
  if (!options.allowDuplicates) checkDuplicateOptionNames(parserPairs.map(([field, parser]) => [field, parser.usage]));
1757
1867
  const noMatchContext = analyzeNoMatchContext(parserKeys.map((k) => parsers[k]));
1758
1868
  const combinedMode = parserKeys.some((k) => parsers[k].mode === "async") ? "async" : "sync";
1759
- const getInitialError = (context) => ({
1760
- consumed: 0,
1761
- error: context.buffer.length > 0 ? (() => {
1762
- const token = context.buffer[0];
1763
- const customMessage = options.errors?.unexpectedInput;
1764
- if (customMessage) return typeof customMessage === "function" ? customMessage(token) : customMessage;
1765
- const baseError = require_message.message`Unexpected option or argument: ${token}.`;
1766
- return require_suggestion.createErrorWithSuggestions(baseError, token, context.usage, "both", options.errors?.suggestions);
1767
- })() : (() => {
1768
- const customEndOfInput = options.errors?.endOfInput;
1769
- return customEndOfInput ? typeof customEndOfInput === "function" ? customEndOfInput(noMatchContext) : customEndOfInput : generateNoMatchError(noMatchContext);
1770
- })()
1771
- });
1869
+ const getInitialError = (context) => createObjectLikeInitialError(context, noMatchContext, options.errors);
1772
1870
  const parseSync = (context) => {
1773
1871
  let error = getInitialError(context);
1774
1872
  let currentContext = context;
@@ -2917,6 +3015,7 @@ function merge(...args) {
2917
3015
  const syncSorted = syncWithIndex.toSorted(([a], [b]) => b.priority - a.priority);
2918
3016
  const syncParsers = syncSorted.map(([p]) => p);
2919
3017
  if (!options.allowDuplicates) checkDuplicateOptionNames(sorted.map(([parser, originalIndex]) => [String(originalIndex), parser.usage]));
3018
+ const noMatchContext = analyzeNoMatchContext(rawParsers);
2920
3019
  const mergedFieldParsers = collectChildFieldParsers(parsers);
2921
3020
  const duplicateOutputFieldNames = collectDuplicateFieldNames(mergedFieldParsers);
2922
3021
  const parserStateKey = (index) => `__parser_${index}`;
@@ -3009,8 +3108,7 @@ function merge(...args) {
3009
3108
  };
3010
3109
  return {
3011
3110
  success: false,
3012
- consumed: 0,
3013
- error: require_message.message`No matching option or argument found.`
3111
+ ...createObjectLikeInitialError(context, noMatchContext)
3014
3112
  };
3015
3113
  };
3016
3114
  const parseAsync = async (context) => {
@@ -3055,8 +3153,7 @@ function merge(...args) {
3055
3153
  };
3056
3154
  return {
3057
3155
  success: false,
3058
- consumed: 0,
3059
- error: require_message.message`No matching option or argument found.`
3156
+ ...createObjectLikeInitialError(context, noMatchContext)
3060
3157
  };
3061
3158
  };
3062
3159
  const mergeParser = {
@@ -516,6 +516,35 @@ function createExclusiveState(parentState, index, parser, result) {
516
516
  return annotateFreshArray(parentState, [index, annotatedResult]);
517
517
  }
518
518
  /**
519
+ * Detects a branch result that handled only the global options terminator.
520
+ * Such a result advances the shared parser context, but does not identify
521
+ * which exclusive branch matched.
522
+ * @internal
523
+ */
524
+ function isOptionsTerminatorOnlyResult(context, result) {
525
+ return !context.optionsTerminated && context.buffer[0] === "--" && result.success && result.next.optionsTerminated && result.consumed.length === 1 && result.consumed[0] === "--";
526
+ }
527
+ function isUnselectedBooleanOptionState(state) {
528
+ return state != null && typeof state === "object" && "success" in state && state.success === true && "value" in state && state.value === false;
529
+ }
530
+ function preserveExclusiveStateAfterOptionsTerminator(context, result) {
531
+ const mergedExec = mergeChildExec(context.exec, result.next.exec);
532
+ return {
533
+ success: true,
534
+ next: {
535
+ ...context,
536
+ buffer: result.next.buffer,
537
+ optionsTerminated: true,
538
+ state: context.state,
539
+ ...mergedExec != null ? {
540
+ exec: mergedExec,
541
+ dependencyRegistry: mergedExec.dependencyRegistry
542
+ } : {}
543
+ },
544
+ consumed: result.consumed
545
+ };
546
+ }
547
+ /**
519
548
  * Creates a complete() method shared by or() and longestMatch().
520
549
  * @internal
521
550
  */
@@ -752,9 +781,15 @@ function or(...args) {
752
781
  let zeroConsumedBranch = null;
753
782
  let zeroConsumedCount = 0;
754
783
  let provisionalConsuming = null;
784
+ let optionsTerminatorResult = null;
755
785
  let provisionalAmbiguous = false;
756
786
  for (const [parser, i] of orderedParsers) {
757
787
  const result = parser.parse(withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
788
+ if (isOptionsTerminatorOnlyResult(context, result)) {
789
+ if (activeState?.[0] === i && activeState[1].success && activeState[1].consumed.length > 0 && result.next.buffer.length < 1) return preserveExclusiveStateAfterOptionsTerminator(context, result);
790
+ optionsTerminatorResult ??= result;
791
+ continue;
792
+ }
758
793
  if (result.success && result.consumed.length > 0) {
759
794
  if (result.provisional) {
760
795
  const activeBranchLocked = activeState != null && activeState[1].success && activeState[1].consumed.length > 0;
@@ -935,6 +970,7 @@ function or(...args) {
935
970
  }
936
971
  }
937
972
  }
973
+ if (optionsTerminatorResult != null) return preserveExclusiveStateAfterOptionsTerminator(context, optionsTerminatorResult);
938
974
  return {
939
975
  ...error,
940
976
  success: false
@@ -948,10 +984,16 @@ function or(...args) {
948
984
  let zeroConsumedBranch = null;
949
985
  let zeroConsumedCount = 0;
950
986
  let provisionalConsuming = null;
987
+ let optionsTerminatorResult = null;
951
988
  let provisionalAmbiguous = false;
952
989
  for (const [parser, i] of orderedParsers) {
953
990
  const resultOrPromise = parser.parse(withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
954
991
  const result = await resultOrPromise;
992
+ if (isOptionsTerminatorOnlyResult(context, result)) {
993
+ if (activeState?.[0] === i && activeState[1].success && activeState[1].consumed.length > 0 && result.next.buffer.length < 1) return preserveExclusiveStateAfterOptionsTerminator(context, result);
994
+ optionsTerminatorResult ??= result;
995
+ continue;
996
+ }
955
997
  if (result.success && result.consumed.length > 0) {
956
998
  if (result.provisional) {
957
999
  const activeBranchLocked = activeState != null && activeState[1].success && activeState[1].consumed.length > 0;
@@ -1134,6 +1176,7 @@ function or(...args) {
1134
1176
  }
1135
1177
  }
1136
1178
  }
1179
+ if (optionsTerminatorResult != null) return preserveExclusiveStateAfterOptionsTerminator(context, optionsTerminatorResult);
1137
1180
  return {
1138
1181
  ...error,
1139
1182
  success: false
@@ -1221,82 +1264,122 @@ function longestMatch(...args) {
1221
1264
  return createUnexpectedInputErrorWithScopedSuggestions(defaultMsg, token, parsers, options?.errors?.suggestions);
1222
1265
  })()
1223
1266
  });
1224
- const parseSync = (context) => {
1267
+ const commitLongestMatch = (context, match, optionsTerminatorResult) => {
1268
+ const selectedResult = optionsTerminatorResult == null ? match.result : {
1269
+ ...match.result,
1270
+ next: {
1271
+ ...match.result.next,
1272
+ buffer: optionsTerminatorResult.next.buffer,
1273
+ optionsTerminated: true
1274
+ },
1275
+ consumed: [...optionsTerminatorResult.consumed, ...match.result.consumed]
1276
+ };
1277
+ const parser = parsers[match.index];
1278
+ const mergedExec = mergeChildExec(context.exec, selectedResult.next.exec);
1279
+ return {
1280
+ success: true,
1281
+ next: {
1282
+ ...context,
1283
+ buffer: selectedResult.next.buffer,
1284
+ optionsTerminated: selectedResult.next.optionsTerminated,
1285
+ state: createExclusiveState(context.state, match.index, parser, selectedResult),
1286
+ ...mergedExec != null ? {
1287
+ exec: mergedExec,
1288
+ dependencyRegistry: mergedExec.dependencyRegistry
1289
+ } : {}
1290
+ },
1291
+ consumed: selectedResult.consumed
1292
+ };
1293
+ };
1294
+ const resolveLongestMatchOptionsTerminator = (context, bestMatch, optionsTerminatorMatches) => {
1295
+ if (optionsTerminatorMatches.length < 1) return null;
1296
+ if (bestMatch != null && bestMatch.consumed > 0) return null;
1297
+ const activeState = normalizeExclusiveState(context.state);
1298
+ if (activeState != null && activeState[1].success && activeState[1].consumed.length > 0) return preserveExclusiveStateAfterOptionsTerminator(context, optionsTerminatorMatches[0].result);
1299
+ let selected = bestMatch == null ? null : {
1300
+ match: bestMatch,
1301
+ fromTerminator: false
1302
+ };
1303
+ for (const match of optionsTerminatorMatches) {
1304
+ if (!match.selectedAfterTerminator && (extractRequiredUsage(parsers[match.index].usage).length > 0 || isUnselectedBooleanOptionState(match.result.next.state))) continue;
1305
+ const selectedIsProvisional = selected != null && !!selected.match.result.provisional;
1306
+ const matchIsProvisional = !!match.result.provisional;
1307
+ if (selected == null || selectedIsProvisional && !matchIsProvisional || selectedIsProvisional === matchIsProvisional && match.index < selected.match.index) selected = {
1308
+ match,
1309
+ fromTerminator: true
1310
+ };
1311
+ }
1312
+ if (selected != null) return commitLongestMatch(context, selected.match, selected.fromTerminator ? void 0 : optionsTerminatorMatches[0].result);
1313
+ return preserveExclusiveStateAfterOptionsTerminator(context, optionsTerminatorMatches[0].result);
1314
+ };
1315
+ const parseSyncBranches = (context) => {
1225
1316
  let bestMatch = null;
1317
+ const optionsTerminatorMatches = [];
1226
1318
  let error = getInitialError(context);
1227
1319
  const activeState = normalizeExclusiveState(context.state);
1228
1320
  for (let i = 0; i < syncParsers.length; i++) {
1229
1321
  const parser = syncParsers[i];
1230
- const result = parser.parse(withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
1322
+ const childContext = withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser);
1323
+ const result = parser.parse(childContext);
1324
+ if (isOptionsTerminatorOnlyResult(context, result)) {
1325
+ optionsTerminatorMatches.push({
1326
+ index: i,
1327
+ result,
1328
+ consumed: 1,
1329
+ ...normalizeExclusiveState(result.next.state) != null ? { selectedAfterTerminator: true } : {}
1330
+ });
1331
+ continue;
1332
+ }
1231
1333
  if (result.success) {
1232
1334
  const consumed = context.buffer.length - result.next.buffer.length;
1233
1335
  const bestIsProvisional = bestMatch != null && bestMatch.result.success && !!bestMatch.result.provisional;
1234
1336
  if (bestMatch === null || consumed > bestMatch.consumed || consumed === bestMatch.consumed && bestIsProvisional && !result.provisional) bestMatch = {
1235
1337
  index: i,
1236
- parser,
1237
1338
  result,
1238
1339
  consumed
1239
1340
  };
1240
1341
  } else if (error.consumed < result.consumed) error = result;
1241
1342
  }
1242
- if (bestMatch && bestMatch.result.success) {
1243
- const mergedExec = mergeChildExec(context.exec, bestMatch.result.next.exec);
1244
- return {
1245
- success: true,
1246
- next: {
1247
- ...context,
1248
- buffer: bestMatch.result.next.buffer,
1249
- optionsTerminated: bestMatch.result.next.optionsTerminated,
1250
- state: createExclusiveState(context.state, bestMatch.index, bestMatch.parser, bestMatch.result),
1251
- ...mergedExec != null ? {
1252
- exec: mergedExec,
1253
- dependencyRegistry: mergedExec.dependencyRegistry
1254
- } : {}
1255
- },
1256
- consumed: bestMatch.result.consumed
1257
- };
1258
- }
1343
+ const optionsTerminatorResolution = resolveLongestMatchOptionsTerminator(context, bestMatch, optionsTerminatorMatches);
1344
+ if (optionsTerminatorResolution != null) return optionsTerminatorResolution;
1345
+ if (bestMatch != null) return commitLongestMatch(context, bestMatch);
1259
1346
  return {
1260
1347
  ...error,
1261
1348
  success: false
1262
1349
  };
1263
1350
  };
1264
- const parseAsync = async (context) => {
1351
+ const parseAsyncBranches = async (context) => {
1265
1352
  let bestMatch = null;
1353
+ const optionsTerminatorMatches = [];
1266
1354
  let error = getInitialError(context);
1267
1355
  const activeState = normalizeExclusiveState(context.state);
1268
1356
  for (let i = 0; i < parsers.length; i++) {
1269
1357
  const parser = parsers[i];
1270
- const resultOrPromise = parser.parse(withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser));
1358
+ const childContext = withChildContext$1(context, i, activeState == null || activeState[0] !== i || !activeState[1].success ? parser.initialState : activeState[1].next.state, parser);
1359
+ const resultOrPromise = parser.parse(childContext);
1271
1360
  const result = await resultOrPromise;
1361
+ if (isOptionsTerminatorOnlyResult(context, result)) {
1362
+ optionsTerminatorMatches.push({
1363
+ index: i,
1364
+ result,
1365
+ consumed: 1,
1366
+ ...normalizeExclusiveState(result.next.state) != null ? { selectedAfterTerminator: true } : {}
1367
+ });
1368
+ continue;
1369
+ }
1272
1370
  if (result.success) {
1273
1371
  const consumed = context.buffer.length - result.next.buffer.length;
1274
1372
  const bestIsProvisional = bestMatch != null && bestMatch.result.success && !!bestMatch.result.provisional;
1275
1373
  if (bestMatch === null || consumed > bestMatch.consumed || consumed === bestMatch.consumed && bestIsProvisional && !result.provisional) bestMatch = {
1276
1374
  index: i,
1277
- parser,
1278
1375
  result,
1279
1376
  consumed
1280
1377
  };
1281
1378
  } else if (error.consumed < result.consumed) error = result;
1282
1379
  }
1283
- if (bestMatch && bestMatch.result.success) {
1284
- const mergedExec = mergeChildExec(context.exec, bestMatch.result.next.exec);
1285
- return {
1286
- success: true,
1287
- next: {
1288
- ...context,
1289
- buffer: bestMatch.result.next.buffer,
1290
- optionsTerminated: bestMatch.result.next.optionsTerminated,
1291
- state: createExclusiveState(context.state, bestMatch.index, bestMatch.parser, bestMatch.result),
1292
- ...mergedExec != null ? {
1293
- exec: mergedExec,
1294
- dependencyRegistry: mergedExec.dependencyRegistry
1295
- } : {}
1296
- },
1297
- consumed: bestMatch.result.consumed
1298
- };
1299
- }
1380
+ const optionsTerminatorResolution = resolveLongestMatchOptionsTerminator(context, bestMatch, optionsTerminatorMatches);
1381
+ if (optionsTerminatorResolution != null) return optionsTerminatorResolution;
1382
+ if (bestMatch != null) return commitLongestMatch(context, bestMatch);
1300
1383
  return {
1301
1384
  ...error,
1302
1385
  success: false
@@ -1319,7 +1402,7 @@ function longestMatch(...args) {
1319
1402
  return extractExclusivePhase2Seed(parsers, state, exec, combinedMode);
1320
1403
  },
1321
1404
  parse(context) {
1322
- return dispatchByMode(combinedMode, () => parseSync(context), () => parseAsync(context));
1405
+ return dispatchByMode(combinedMode, () => parseSyncBranches(context), () => parseAsyncBranches(context));
1323
1406
  },
1324
1407
  getSuggestRuntimeNodes(state, path) {
1325
1408
  return getExclusiveSuggestRuntimeNodes(parsers, state, path);
@@ -1365,6 +1448,33 @@ function longestMatch(...args) {
1365
1448
  return multiResult;
1366
1449
  }
1367
1450
  /**
1451
+ * Creates the initial parse error shared by object-like combinators.
1452
+ * @param context The current parser context.
1453
+ * @param noMatchContext The kinds of input accepted by the combinator.
1454
+ * @param errors Optional custom error formatters.
1455
+ * @returns A zero-consumption parse error.
1456
+ */
1457
+ function createObjectLikeInitialError(context, noMatchContext, errors) {
1458
+ if (context.buffer.length < 1) {
1459
+ const customEndOfInput = errors?.endOfInput;
1460
+ return {
1461
+ consumed: 0,
1462
+ error: customEndOfInput ? typeof customEndOfInput === "function" ? customEndOfInput(noMatchContext) : customEndOfInput : generateNoMatchError(noMatchContext)
1463
+ };
1464
+ }
1465
+ const token = context.buffer[0];
1466
+ const customMessage = errors?.unexpectedInput;
1467
+ if (customMessage) return {
1468
+ consumed: 0,
1469
+ error: typeof customMessage === "function" ? customMessage(token) : customMessage
1470
+ };
1471
+ const baseError = message`Unexpected option or argument: ${token}.`;
1472
+ return {
1473
+ consumed: 0,
1474
+ error: createErrorWithSuggestions(baseError, token, context.usage, "both", errors?.suggestions)
1475
+ };
1476
+ }
1477
+ /**
1368
1478
  * Internal sync helper for object suggest functionality.
1369
1479
  * @internal
1370
1480
  */
@@ -1756,19 +1866,7 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1756
1866
  if (!options.allowDuplicates) checkDuplicateOptionNames(parserPairs.map(([field, parser]) => [field, parser.usage]));
1757
1867
  const noMatchContext = analyzeNoMatchContext(parserKeys.map((k) => parsers[k]));
1758
1868
  const combinedMode = parserKeys.some((k) => parsers[k].mode === "async") ? "async" : "sync";
1759
- const getInitialError = (context) => ({
1760
- consumed: 0,
1761
- error: context.buffer.length > 0 ? (() => {
1762
- const token = context.buffer[0];
1763
- const customMessage = options.errors?.unexpectedInput;
1764
- if (customMessage) return typeof customMessage === "function" ? customMessage(token) : customMessage;
1765
- const baseError = message`Unexpected option or argument: ${token}.`;
1766
- return createErrorWithSuggestions(baseError, token, context.usage, "both", options.errors?.suggestions);
1767
- })() : (() => {
1768
- const customEndOfInput = options.errors?.endOfInput;
1769
- return customEndOfInput ? typeof customEndOfInput === "function" ? customEndOfInput(noMatchContext) : customEndOfInput : generateNoMatchError(noMatchContext);
1770
- })()
1771
- });
1869
+ const getInitialError = (context) => createObjectLikeInitialError(context, noMatchContext, options.errors);
1772
1870
  const parseSync = (context) => {
1773
1871
  let error = getInitialError(context);
1774
1872
  let currentContext = context;
@@ -2917,6 +3015,7 @@ function merge(...args) {
2917
3015
  const syncSorted = syncWithIndex.toSorted(([a], [b]) => b.priority - a.priority);
2918
3016
  const syncParsers = syncSorted.map(([p]) => p);
2919
3017
  if (!options.allowDuplicates) checkDuplicateOptionNames(sorted.map(([parser, originalIndex]) => [String(originalIndex), parser.usage]));
3018
+ const noMatchContext = analyzeNoMatchContext(rawParsers);
2920
3019
  const mergedFieldParsers = collectChildFieldParsers(parsers);
2921
3020
  const duplicateOutputFieldNames = collectDuplicateFieldNames(mergedFieldParsers);
2922
3021
  const parserStateKey = (index) => `__parser_${index}`;
@@ -3009,8 +3108,7 @@ function merge(...args) {
3009
3108
  };
3010
3109
  return {
3011
3110
  success: false,
3012
- consumed: 0,
3013
- error: message`No matching option or argument found.`
3111
+ ...createObjectLikeInitialError(context, noMatchContext)
3014
3112
  };
3015
3113
  };
3016
3114
  const parseAsync = async (context) => {
@@ -3055,8 +3153,7 @@ function merge(...args) {
3055
3153
  };
3056
3154
  return {
3057
3155
  success: false,
3058
- consumed: 0,
3059
- error: message`No matching option or argument found.`
3156
+ ...createObjectLikeInitialError(context, noMatchContext)
3060
3157
  };
3061
3158
  };
3062
3159
  const mergeParser = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",