@optique/core 1.4.0-dev.2683 → 1.4.0-dev.2709

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.
@@ -0,0 +1,233 @@
1
+ //#region src/internal/usage.ts
2
+ const EMPTY = {
3
+ visible: false,
4
+ omissible: true,
5
+ hidesRequired: false
6
+ };
7
+ function isHiddenLeaf(term, isHidden) {
8
+ return (term.type === "argument" || term.type === "option" || term.type === "command" || term.type === "passthrough") && isHidden(term.hidden);
9
+ }
10
+ function isDegenerateLeaf(term) {
11
+ return term.type === "option" && term.names.length === 0 || term.type === "command" && term.name === "" || term.type === "argument" && term.metavar.length === 0 || term.type === "literal" && term.value === "";
12
+ }
13
+ function projectTerms(terms, isHidden) {
14
+ let visible = false;
15
+ let omissible = true;
16
+ let hidesRequired = false;
17
+ for (const term of terms) {
18
+ const projection = projectTerm(term, isHidden);
19
+ hidesRequired ||= projection.hidesRequired;
20
+ if (!projection.visible) continue;
21
+ visible = true;
22
+ omissible &&= projection.omissible;
23
+ }
24
+ return {
25
+ visible,
26
+ omissible,
27
+ hidesRequired
28
+ };
29
+ }
30
+ function projectTerm(term, isHidden) {
31
+ if (isHiddenLeaf(term, isHidden)) return {
32
+ ...EMPTY,
33
+ hidesRequired: term.type !== "passthrough"
34
+ };
35
+ if (isDegenerateLeaf(term)) return EMPTY;
36
+ switch (term.type) {
37
+ case "argument":
38
+ case "option":
39
+ case "command":
40
+ case "literal": return {
41
+ visible: true,
42
+ omissible: false,
43
+ hidesRequired: false
44
+ };
45
+ case "passthrough":
46
+ case "ellipsis": return {
47
+ visible: true,
48
+ omissible: true,
49
+ hidesRequired: false
50
+ };
51
+ case "optional": {
52
+ const inner = projectTerms(term.terms, isHidden);
53
+ return {
54
+ ...inner,
55
+ omissible: true,
56
+ hidesRequired: false
57
+ };
58
+ }
59
+ case "sequence": return projectTerms(term.terms, isHidden);
60
+ case "multiple": {
61
+ const inner = projectTerms(term.terms, isHidden);
62
+ return {
63
+ ...inner,
64
+ omissible: term.min < 1 || inner.omissible,
65
+ hidesRequired: term.min > 0 && inner.hidesRequired
66
+ };
67
+ }
68
+ case "exclusive": {
69
+ let visible = false;
70
+ let omissible = false;
71
+ let hidesRequired = false;
72
+ for (const branch of term.terms) {
73
+ const projection = projectTerms(branch, isHidden);
74
+ hidesRequired ||= projection.hidesRequired;
75
+ if (!projection.visible) continue;
76
+ visible = true;
77
+ omissible ||= projection.omissible;
78
+ }
79
+ return {
80
+ visible,
81
+ omissible,
82
+ hidesRequired
83
+ };
84
+ }
85
+ }
86
+ }
87
+ /**
88
+ * Returns the terms `X` when the visible part of `terms` is exactly
89
+ * `optional(X)`, possibly nested, with a required `X`, and no required term
90
+ * was hidden.
91
+ */
92
+ function unwrapSingleOptional(terms, isHidden) {
93
+ if (projectTerms(terms, isHidden).hidesRequired) return void 0;
94
+ const visible = terms.filter((t) => projectTerm(t, isHidden).visible);
95
+ if (visible.length !== 1) return void 0;
96
+ const [only] = visible;
97
+ if (only.type !== "optional") return void 0;
98
+ const inner = projectTerms(only.terms, isHidden);
99
+ if (!inner.visible || inner.hidesRequired) return void 0;
100
+ if (inner.omissible) return unwrapSingleOptional(only.terms, isHidden);
101
+ return only.terms;
102
+ }
103
+ function resolveTerms(terms, isHidden) {
104
+ return terms.map((term) => resolveTerm(term, isHidden));
105
+ }
106
+ function resolveTerm(term, isHidden) {
107
+ switch (term.type) {
108
+ case "optional": return {
109
+ type: "optional",
110
+ terms: resolveTerms(term.terms, isHidden)
111
+ };
112
+ case "sequence": return {
113
+ type: "sequence",
114
+ terms: resolveTerms(term.terms, isHidden)
115
+ };
116
+ case "multiple": {
117
+ const terms = resolveTerms(term.terms, isHidden);
118
+ const resolved = {
119
+ type: "multiple",
120
+ terms,
121
+ min: term.min
122
+ };
123
+ if (term.acceptsEmpty == null) return resolved;
124
+ const projection = projectTerm(resolved, isHidden);
125
+ if (!projection.visible || projection.omissible === term.acceptsEmpty) return resolved;
126
+ if (term.acceptsEmpty) return {
127
+ type: "optional",
128
+ terms: [resolved]
129
+ };
130
+ if (term.min < 1) return resolved;
131
+ const required = unwrapSingleOptional(terms, isHidden);
132
+ return required == null ? resolved : {
133
+ ...resolved,
134
+ terms: required
135
+ };
136
+ }
137
+ case "exclusive": {
138
+ const branches = term.terms.map((branch) => resolveTerms(branch, isHidden));
139
+ const resolved = {
140
+ type: "exclusive",
141
+ terms: branches
142
+ };
143
+ if (term.acceptsEmpty == null) return resolved;
144
+ const projection = projectTerm(resolved, isHidden);
145
+ if (!projection.visible || projection.omissible === term.acceptsEmpty) return resolved;
146
+ if (term.acceptsEmpty) {
147
+ const visible = branches.filter((branch) => projectTerms(branch, isHidden).visible);
148
+ return {
149
+ type: "optional",
150
+ terms: visible.length === 1 ? visible[0] : [{
151
+ type: "exclusive",
152
+ terms: visible
153
+ }]
154
+ };
155
+ }
156
+ const required = [];
157
+ for (const branch of branches) {
158
+ const branchProjection = projectTerms(branch, isHidden);
159
+ if (!branchProjection.visible || !branchProjection.omissible) {
160
+ required.push(branch);
161
+ continue;
162
+ }
163
+ const unwrapped = unwrapSingleOptional(branch, isHidden);
164
+ if (unwrapped == null) return resolved;
165
+ required.push(unwrapped);
166
+ }
167
+ return {
168
+ type: "exclusive",
169
+ terms: required
170
+ };
171
+ }
172
+ default: return term;
173
+ }
174
+ }
175
+ /**
176
+ * Rewrites a usage so that plain synopsis notation reflects the recorded
177
+ * empty-input behavior of its groups.
178
+ *
179
+ * Each `exclusive` or `multiple` term that records `acceptsEmpty` is drawn
180
+ * as omissible exactly when its parser accepts an empty argument list,
181
+ * using only exact local rewrites: a group that should be omissible is
182
+ * wrapped in an optional term, and a group that should be required has
183
+ * `optional(X)` alternatives or repeated items replaced by `X`. A group
184
+ * whose visible terms cannot be drawn that way keeps its declared notation.
185
+ * Enclosing groups are resolved after their children, so their constraints
186
+ * take precedence.
187
+ *
188
+ * The result records no `acceptsEmpty` fields, so resolving it again
189
+ * returns an equal usage. Hidden terms are kept; callers filter them for
190
+ * display afterwards.
191
+ * @param usage The usage to resolve.
192
+ * @param isHidden Reports whether a term is hidden in the rendering context.
193
+ * @returns The resolved usage.
194
+ * @internal
195
+ */
196
+ function resolveUsageForDisplay(usage, isHidden) {
197
+ return resolveTerms(usage, isHidden);
198
+ }
199
+ /**
200
+ * Removes every `acceptsEmpty` record from a usage without changing how it
201
+ * is drawn otherwise.
202
+ * @param usage The usage to copy.
203
+ * @returns The usage without records.
204
+ * @internal
205
+ */
206
+ function forgetEmptyInput(usage) {
207
+ return usage.map(forgetEmptyInputTerm);
208
+ }
209
+ function forgetEmptyInputTerm(term) {
210
+ switch (term.type) {
211
+ case "optional": return {
212
+ type: "optional",
213
+ terms: forgetEmptyInput(term.terms)
214
+ };
215
+ case "sequence": return {
216
+ type: "sequence",
217
+ terms: forgetEmptyInput(term.terms)
218
+ };
219
+ case "multiple": return {
220
+ type: "multiple",
221
+ terms: forgetEmptyInput(term.terms),
222
+ min: term.min
223
+ };
224
+ case "exclusive": return {
225
+ type: "exclusive",
226
+ terms: term.terms.map(forgetEmptyInput)
227
+ };
228
+ default: return term;
229
+ }
230
+ }
231
+
232
+ //#endregion
233
+ export { forgetEmptyInput, resolveUsageForDisplay };
package/dist/message.cjs CHANGED
@@ -347,7 +347,8 @@ function renderMessage(msg, options, theme, ambient) {
347
347
  yield {
348
348
  text: match[0],
349
349
  width: require_displaywidth.getDisplayWidth(match[0]),
350
- scopes: []
350
+ scopes: [],
351
+ trimOnWrap: true
351
352
  };
352
353
  }
353
354
  }
@@ -356,7 +357,8 @@ function renderMessage(msg, options, theme, ambient) {
356
357
  if (normalizedText.trim() === "" && normalizedText.length > 0) yield {
357
358
  text: " ",
358
359
  width: 1,
359
- scopes: []
360
+ scopes: [],
361
+ trimOnWrap: true
360
362
  };
361
363
  else {
362
364
  wordPattern.lastIndex = 0;
@@ -366,7 +368,8 @@ function renderMessage(msg, options, theme, ambient) {
366
368
  yield {
367
369
  text: match[0],
368
370
  width: require_displaywidth.getDisplayWidth(match[0]),
369
- scopes: []
371
+ scopes: [],
372
+ trimOnWrap: true
370
373
  };
371
374
  }
372
375
  }
@@ -383,22 +386,46 @@ function renderMessage(msg, options, theme, ambient) {
383
386
  }
384
387
  const tokens = [];
385
388
  let totalWidth = initialWidth;
386
- for (const token of stream()) {
389
+ let pendingWrap = false;
390
+ let atWrappedLineStart = false;
391
+ for (let token of stream()) {
387
392
  if (token.width === -1) {
388
393
  tokens.push(token);
389
394
  totalWidth = 0;
395
+ pendingWrap = false;
396
+ atWrappedLineStart = false;
397
+ continue;
398
+ }
399
+ const wraps = pendingWrap || options.maxWidth != null && totalWidth > 0 && totalWidth + token.width > options.maxWidth;
400
+ if ((wraps || atWrappedLineStart) && token.trimOnWrap) {
401
+ const text$1 = token.text.trimStart();
402
+ token = {
403
+ ...token,
404
+ text: text$1,
405
+ width: token.width - require_displaywidth.getDisplayWidth(token.text.slice(0, token.text.length - text$1.length))
406
+ };
407
+ if (text$1 === "") {
408
+ if (wraps) pendingWrap = true;
409
+ continue;
410
+ }
411
+ }
412
+ if (pendingWrap && require_displaywidth.stripAnsi(token.text) === "") {
413
+ tokens.push(token);
390
414
  continue;
391
415
  }
392
- if (options.maxWidth != null && totalWidth > 0 && totalWidth + token.width > options.maxWidth) {
416
+ if (wraps) {
393
417
  tokens.push({
394
418
  text: "\n",
395
419
  width: -1,
396
420
  scopes: []
397
421
  });
398
422
  totalWidth = 0;
423
+ pendingWrap = false;
424
+ atWrappedLineStart = true;
399
425
  }
400
426
  tokens.push(token);
401
427
  totalWidth += token.width;
428
+ if (token.width > 0) atWrappedLineStart = false;
402
429
  }
403
430
  return require_terminal_internal.serializeTokens(tokens, useColors, ambient == null ? resetSuffix : "", ambient);
404
431
  }
package/dist/message.js CHANGED
@@ -1,4 +1,4 @@
1
- import { getDisplayWidth } from "./displaywidth.js";
1
+ import { getDisplayWidth, stripAnsi } from "./displaywidth.js";
2
2
  import { formatTerminalTerm, fragmentTokens, serializeTokens } from "./terminal-internal.js";
3
3
  import { messageRenderers } from "./message-registry.js";
4
4
 
@@ -347,7 +347,8 @@ function renderMessage(msg, options, theme, ambient) {
347
347
  yield {
348
348
  text: match[0],
349
349
  width: getDisplayWidth(match[0]),
350
- scopes: []
350
+ scopes: [],
351
+ trimOnWrap: true
351
352
  };
352
353
  }
353
354
  }
@@ -356,7 +357,8 @@ function renderMessage(msg, options, theme, ambient) {
356
357
  if (normalizedText.trim() === "" && normalizedText.length > 0) yield {
357
358
  text: " ",
358
359
  width: 1,
359
- scopes: []
360
+ scopes: [],
361
+ trimOnWrap: true
360
362
  };
361
363
  else {
362
364
  wordPattern.lastIndex = 0;
@@ -366,7 +368,8 @@ function renderMessage(msg, options, theme, ambient) {
366
368
  yield {
367
369
  text: match[0],
368
370
  width: getDisplayWidth(match[0]),
369
- scopes: []
371
+ scopes: [],
372
+ trimOnWrap: true
370
373
  };
371
374
  }
372
375
  }
@@ -383,22 +386,46 @@ function renderMessage(msg, options, theme, ambient) {
383
386
  }
384
387
  const tokens = [];
385
388
  let totalWidth = initialWidth;
386
- for (const token of stream()) {
389
+ let pendingWrap = false;
390
+ let atWrappedLineStart = false;
391
+ for (let token of stream()) {
387
392
  if (token.width === -1) {
388
393
  tokens.push(token);
389
394
  totalWidth = 0;
395
+ pendingWrap = false;
396
+ atWrappedLineStart = false;
397
+ continue;
398
+ }
399
+ const wraps = pendingWrap || options.maxWidth != null && totalWidth > 0 && totalWidth + token.width > options.maxWidth;
400
+ if ((wraps || atWrappedLineStart) && token.trimOnWrap) {
401
+ const text$1 = token.text.trimStart();
402
+ token = {
403
+ ...token,
404
+ text: text$1,
405
+ width: token.width - getDisplayWidth(token.text.slice(0, token.text.length - text$1.length))
406
+ };
407
+ if (text$1 === "") {
408
+ if (wraps) pendingWrap = true;
409
+ continue;
410
+ }
411
+ }
412
+ if (pendingWrap && stripAnsi(token.text) === "") {
413
+ tokens.push(token);
390
414
  continue;
391
415
  }
392
- if (options.maxWidth != null && totalWidth > 0 && totalWidth + token.width > options.maxWidth) {
416
+ if (wraps) {
393
417
  tokens.push({
394
418
  text: "\n",
395
419
  width: -1,
396
420
  scopes: []
397
421
  });
398
422
  totalWidth = 0;
423
+ pendingWrap = false;
424
+ atWrappedLineStart = true;
399
425
  }
400
426
  tokens.push(token);
401
427
  totalWidth += token.width;
428
+ if (token.width > 0) atWrappedLineStart = false;
402
429
  }
403
430
  return serializeTokens(tokens, useColors, ambient == null ? resetSuffix : "", ambient);
404
431
  }
@@ -4,6 +4,7 @@ const require_mode_dispatch = require('./internal/mode-dispatch.cjs');
4
4
  const require_dependency_runtime = require('./dependency-runtime.cjs');
5
5
  const require_internal_parser = require('./internal/parser.cjs');
6
6
  const require_annotation_state = require('./annotation-state.cjs');
7
+ const require_empty_input = require('./internal/empty-input.cjs');
7
8
  const require_execution_context = require('./execution-context.cjs');
8
9
  const require_short_option = require('./short-option.cjs');
9
10
  const require_passthrough = require('./internal/passthrough.cjs');
@@ -27,6 +28,14 @@ function isUnstartedMultipleItemState(state, originalState) {
27
28
  const unwrappedOriginalState = unwrapMultipleItemState(originalState);
28
29
  return unwrappedState.value === unwrappedOriginalState.value && (unwrappedState.viaBoundCliWrapper || unwrappedOriginalState.viaBoundCliWrapper || unwrappedOriginalState.value != null && typeof unwrappedOriginalState.value === "object");
29
30
  }
31
+ /**
32
+ * Returns whether an optional-style wrapper around the given parser is known
33
+ * to complete successfully from its initial state without delegating to a
34
+ * source binding or a deferred completion.
35
+ */
36
+ function completesOptionalLikeFallback(parser) {
37
+ return typeof parser.shouldDeferCompletion !== "function" && parser.dependencyMetadata?.source == null && parser[require_internal_parser.unmatchedNonCliDependencySourceStateMarker] !== true ? true : void 0;
38
+ }
30
39
  function isBoundCliWrapperState(state) {
31
40
  return state != null && typeof state === "object" && Object.hasOwn(state, "hasCliValue") && typeof state.hasCliValue === "boolean" && (Object.hasOwn(state, "cliState") || state.hasCliValue === false);
32
41
  }
@@ -485,7 +494,9 @@ function optional(parser) {
485
494
  state: deriveOptionalInnerParseState(state, parser)
486
495
  }]);
487
496
  require_passthrough.defineKnownCompletionLookup(optionalParser, (state) => require_passthrough.getKnownCompletion(parser, deriveOptionalInnerParseState(state, parser)));
488
- return fluent(require_short_option.scopeParser(optionalParser, require_short_option.combinedOptionScope([parser])));
497
+ const scopedOptionalParser = require_short_option.scopeParser(optionalParser, require_short_option.combinedOptionScope([parser]));
498
+ require_empty_input.defineEmptyInputFacts(scopedOptionalParser, require_empty_input.optionalLikeFacts(require_empty_input.getEmptyInputFacts(parser), parser.initialState, completesOptionalLikeFallback(parser)));
499
+ return fluent(scopedOptionalParser);
489
500
  }
490
501
  /**
491
502
  * Error type for structured error messages in {@link withDefault} default value callbacks.
@@ -741,7 +752,9 @@ function withDefault(parser, defaultValue, options) {
741
752
  state: deriveOptionalInnerParseState(state, parser)
742
753
  }]);
743
754
  require_passthrough.defineKnownCompletionLookup(withDefaultParser, (state) => require_passthrough.getKnownCompletion(parser, deriveOptionalInnerParseState(state, parser)));
744
- return fluent(require_short_option.scopeParser(withDefaultParser, require_short_option.combinedOptionScope([parser])));
755
+ const scopedWithDefaultParser = require_short_option.scopeParser(withDefaultParser, require_short_option.combinedOptionScope([parser]));
756
+ require_empty_input.defineEmptyInputFacts(scopedWithDefaultParser, require_empty_input.optionalLikeFacts(require_empty_input.getEmptyInputFacts(parser), parser.initialState, typeof defaultValue === "function" ? void 0 : completesOptionalLikeFallback(parser)));
757
+ return fluent(scopedWithDefaultParser);
745
758
  }
746
759
  /**
747
760
  * Creates a parser that transforms the result value of another parser using
@@ -930,6 +943,7 @@ function map(parser, transform) {
930
943
  if (composed != null) mappedParser.dependencyMetadata = composed;
931
944
  }
932
945
  require_dependency_runtime.defineForwardedEffectfulSchedulingNodes(mappedParser, parser);
946
+ require_empty_input.defineEmptyInputFacts(mappedParser, require_empty_input.getEmptyInputFacts(parser));
933
947
  return fluent(mappedParser);
934
948
  }
935
949
  const deferredValueBrand = Symbol.for("@optique/core/deferredValue");
@@ -1104,6 +1118,17 @@ function isDeferredValue(value) {
1104
1118
  return typeof value === "function" && value[deferredValueBrand] === true;
1105
1119
  }
1106
1120
  /**
1121
+ * Computes the empty-input facts of `multiple(parser, options)`.
1122
+ */
1123
+ function getMultipleEmptyInputFacts(parser, options) {
1124
+ const min = options.min ?? 0;
1125
+ if (options.max != null || !Number.isInteger(min) || min < 0) return {};
1126
+ const facts = require_empty_input.getEmptyInputFacts(parser);
1127
+ const childInitialState = parser.initialState;
1128
+ const retainsItem = facts.step === "success" || facts.step === "provisional" ? facts.next == null ? void 0 : !isUnstartedMultipleItemState(require_empty_input.resolveEmptyStepState(facts.next, childInitialState), childInitialState) : false;
1129
+ return require_empty_input.multipleFacts(facts, retainsItem, childInitialState, min);
1130
+ }
1131
+ /**
1107
1132
  * Creates a parser that allows multiple occurrences of a given parser.
1108
1133
  * This parser can be used to parse multiple values of the same type,
1109
1134
  * such as multiple command-line arguments or options.
@@ -1380,6 +1405,9 @@ function multiple(parser, options = {}) {
1380
1405
  consumed: result.consumed
1381
1406
  };
1382
1407
  };
1408
+ const initialState = [];
1409
+ const emptyInputFacts = getMultipleEmptyInputFacts(parser, options);
1410
+ const acceptsEmpty = require_empty_input.acceptsEmptyInput(emptyInputFacts);
1383
1411
  const resultParser = {
1384
1412
  mode: parser.mode,
1385
1413
  $valueType: [],
@@ -1388,11 +1416,12 @@ function multiple(parser, options = {}) {
1388
1416
  usage: [{
1389
1417
  type: "multiple",
1390
1418
  terms: parser.usage,
1391
- min
1419
+ min,
1420
+ ...acceptsEmpty == null ? {} : { acceptsEmpty }
1392
1421
  }],
1393
1422
  leadingNames: parser.leadingNames,
1394
1423
  acceptingAnyToken: min > 0 && (parser.acceptingAnyToken ?? false),
1395
- initialState: [],
1424
+ initialState,
1396
1425
  canSkip(state, exec) {
1397
1426
  if (state.length < min) return false;
1398
1427
  const currentItemState = state.at(-1);
@@ -1748,7 +1777,9 @@ function multiple(parser, options = {}) {
1748
1777
  parser,
1749
1778
  state: unwrapInjectedWrapper(item.state)
1750
1779
  })));
1751
- return fluent(require_short_option.scopeParser(resultParser, require_short_option.repeatedOptionScope(parser)));
1780
+ const scopedResultParser = require_short_option.scopeParser(resultParser, require_short_option.repeatedOptionScope(parser));
1781
+ require_empty_input.defineEmptyInputFacts(scopedResultParser, emptyInputFacts);
1782
+ return fluent(scopedResultParser);
1752
1783
  }
1753
1784
  /**
1754
1785
  * Creates a parser that requires the wrapped parser to consume at least one
@@ -1888,7 +1919,13 @@ function nonEmpty(parser) {
1888
1919
  parser,
1889
1920
  state
1890
1921
  }]);
1891
- return fluent(require_short_option.scopeParser(nonEmptyParser, require_short_option.combinedOptionScope([parser])));
1922
+ const scopedNonEmptyParser = require_short_option.scopeParser(nonEmptyParser, require_short_option.combinedOptionScope([parser]));
1923
+ const innerFacts = require_empty_input.getEmptyInputFacts(parser);
1924
+ require_empty_input.defineEmptyInputFacts(scopedNonEmptyParser, {
1925
+ step: "failure",
1926
+ ...innerFacts.fromInitial == null ? {} : { fromInitial: innerFacts.fromInitial }
1927
+ });
1928
+ return fluent(scopedNonEmptyParser);
1892
1929
  }
1893
1930
  const fluentParserMarker = Symbol.for("@optique/core/fluent");
1894
1931
  /**
package/dist/modifiers.js CHANGED
@@ -4,6 +4,7 @@ import { dispatchByMode, dispatchIterableByMode, mapModeValue, wrapForMode } fro
4
4
  import { defineForwardedEffectfulSchedulingNodes } from "./dependency-runtime.js";
5
5
  import { defineInheritedAnnotationParser, defineParseLanes, defineSourceBindingOnlyAnnotationCompletionParser, getOwnParseLanes, unmatchedNonCliDependencySourceStateMarker } from "./internal/parser.js";
6
6
  import { getDelegatedAnnotationState, hasDelegatedAnnotationCarrier, isAnnotationWrappedInitialState, normalizeDelegatedAnnotationState, normalizeNestedDelegatedAnnotationState } from "./annotation-state.js";
7
+ import { acceptsEmptyInput, defineEmptyInputFacts, getEmptyInputFacts, multipleFacts, optionalLikeFacts, resolveEmptyStepState } from "./internal/empty-input.js";
7
8
  import { mergeChildExec, withChildContext, withChildExecPath } from "./execution-context.js";
8
9
  import { combinedOptionScope, repeatedOptionScope, scopeParser } from "./short-option.js";
9
10
  import { defineKnownCompletionLookup, defineReachableChildren, getKnownCompletion } from "./internal/passthrough.js";
@@ -27,6 +28,14 @@ function isUnstartedMultipleItemState(state, originalState) {
27
28
  const unwrappedOriginalState = unwrapMultipleItemState(originalState);
28
29
  return unwrappedState.value === unwrappedOriginalState.value && (unwrappedState.viaBoundCliWrapper || unwrappedOriginalState.viaBoundCliWrapper || unwrappedOriginalState.value != null && typeof unwrappedOriginalState.value === "object");
29
30
  }
31
+ /**
32
+ * Returns whether an optional-style wrapper around the given parser is known
33
+ * to complete successfully from its initial state without delegating to a
34
+ * source binding or a deferred completion.
35
+ */
36
+ function completesOptionalLikeFallback(parser) {
37
+ return typeof parser.shouldDeferCompletion !== "function" && parser.dependencyMetadata?.source == null && parser[unmatchedNonCliDependencySourceStateMarker] !== true ? true : void 0;
38
+ }
30
39
  function isBoundCliWrapperState(state) {
31
40
  return state != null && typeof state === "object" && Object.hasOwn(state, "hasCliValue") && typeof state.hasCliValue === "boolean" && (Object.hasOwn(state, "cliState") || state.hasCliValue === false);
32
41
  }
@@ -485,7 +494,9 @@ function optional(parser) {
485
494
  state: deriveOptionalInnerParseState(state, parser)
486
495
  }]);
487
496
  defineKnownCompletionLookup(optionalParser, (state) => getKnownCompletion(parser, deriveOptionalInnerParseState(state, parser)));
488
- return fluent(scopeParser(optionalParser, combinedOptionScope([parser])));
497
+ const scopedOptionalParser = scopeParser(optionalParser, combinedOptionScope([parser]));
498
+ defineEmptyInputFacts(scopedOptionalParser, optionalLikeFacts(getEmptyInputFacts(parser), parser.initialState, completesOptionalLikeFallback(parser)));
499
+ return fluent(scopedOptionalParser);
489
500
  }
490
501
  /**
491
502
  * Error type for structured error messages in {@link withDefault} default value callbacks.
@@ -741,7 +752,9 @@ function withDefault(parser, defaultValue, options) {
741
752
  state: deriveOptionalInnerParseState(state, parser)
742
753
  }]);
743
754
  defineKnownCompletionLookup(withDefaultParser, (state) => getKnownCompletion(parser, deriveOptionalInnerParseState(state, parser)));
744
- return fluent(scopeParser(withDefaultParser, combinedOptionScope([parser])));
755
+ const scopedWithDefaultParser = scopeParser(withDefaultParser, combinedOptionScope([parser]));
756
+ defineEmptyInputFacts(scopedWithDefaultParser, optionalLikeFacts(getEmptyInputFacts(parser), parser.initialState, typeof defaultValue === "function" ? void 0 : completesOptionalLikeFallback(parser)));
757
+ return fluent(scopedWithDefaultParser);
745
758
  }
746
759
  /**
747
760
  * Creates a parser that transforms the result value of another parser using
@@ -930,6 +943,7 @@ function map(parser, transform) {
930
943
  if (composed != null) mappedParser.dependencyMetadata = composed;
931
944
  }
932
945
  defineForwardedEffectfulSchedulingNodes(mappedParser, parser);
946
+ defineEmptyInputFacts(mappedParser, getEmptyInputFacts(parser));
933
947
  return fluent(mappedParser);
934
948
  }
935
949
  const deferredValueBrand = Symbol.for("@optique/core/deferredValue");
@@ -1104,6 +1118,17 @@ function isDeferredValue(value) {
1104
1118
  return typeof value === "function" && value[deferredValueBrand] === true;
1105
1119
  }
1106
1120
  /**
1121
+ * Computes the empty-input facts of `multiple(parser, options)`.
1122
+ */
1123
+ function getMultipleEmptyInputFacts(parser, options) {
1124
+ const min = options.min ?? 0;
1125
+ if (options.max != null || !Number.isInteger(min) || min < 0) return {};
1126
+ const facts = getEmptyInputFacts(parser);
1127
+ const childInitialState = parser.initialState;
1128
+ const retainsItem = facts.step === "success" || facts.step === "provisional" ? facts.next == null ? void 0 : !isUnstartedMultipleItemState(resolveEmptyStepState(facts.next, childInitialState), childInitialState) : false;
1129
+ return multipleFacts(facts, retainsItem, childInitialState, min);
1130
+ }
1131
+ /**
1107
1132
  * Creates a parser that allows multiple occurrences of a given parser.
1108
1133
  * This parser can be used to parse multiple values of the same type,
1109
1134
  * such as multiple command-line arguments or options.
@@ -1380,6 +1405,9 @@ function multiple(parser, options = {}) {
1380
1405
  consumed: result.consumed
1381
1406
  };
1382
1407
  };
1408
+ const initialState = [];
1409
+ const emptyInputFacts = getMultipleEmptyInputFacts(parser, options);
1410
+ const acceptsEmpty = acceptsEmptyInput(emptyInputFacts);
1383
1411
  const resultParser = {
1384
1412
  mode: parser.mode,
1385
1413
  $valueType: [],
@@ -1388,11 +1416,12 @@ function multiple(parser, options = {}) {
1388
1416
  usage: [{
1389
1417
  type: "multiple",
1390
1418
  terms: parser.usage,
1391
- min
1419
+ min,
1420
+ ...acceptsEmpty == null ? {} : { acceptsEmpty }
1392
1421
  }],
1393
1422
  leadingNames: parser.leadingNames,
1394
1423
  acceptingAnyToken: min > 0 && (parser.acceptingAnyToken ?? false),
1395
- initialState: [],
1424
+ initialState,
1396
1425
  canSkip(state, exec) {
1397
1426
  if (state.length < min) return false;
1398
1427
  const currentItemState = state.at(-1);
@@ -1748,7 +1777,9 @@ function multiple(parser, options = {}) {
1748
1777
  parser,
1749
1778
  state: unwrapInjectedWrapper(item.state)
1750
1779
  })));
1751
- return fluent(scopeParser(resultParser, repeatedOptionScope(parser)));
1780
+ const scopedResultParser = scopeParser(resultParser, repeatedOptionScope(parser));
1781
+ defineEmptyInputFacts(scopedResultParser, emptyInputFacts);
1782
+ return fluent(scopedResultParser);
1752
1783
  }
1753
1784
  /**
1754
1785
  * Creates a parser that requires the wrapped parser to consume at least one
@@ -1888,7 +1919,13 @@ function nonEmpty(parser) {
1888
1919
  parser,
1889
1920
  state
1890
1921
  }]);
1891
- return fluent(scopeParser(nonEmptyParser, combinedOptionScope([parser])));
1922
+ const scopedNonEmptyParser = scopeParser(nonEmptyParser, combinedOptionScope([parser]));
1923
+ const innerFacts = getEmptyInputFacts(parser);
1924
+ defineEmptyInputFacts(scopedNonEmptyParser, {
1925
+ step: "failure",
1926
+ ...innerFacts.fromInitial == null ? {} : { fromInitial: innerFacts.fromInitial }
1927
+ });
1928
+ return fluent(scopedNonEmptyParser);
1892
1929
  }
1893
1930
  const fluentParserMarker = Symbol.for("@optique/core/fluent");
1894
1931
  /**