#1015, which resolves #1013, lets usage groups reflect how their parsers handle an empty argument list. or(), longestMatch(), and multiple() record acceptsEmpty on the exclusive and multiple terms they produce, and both usage formatters draw those groups as optional exactly when the parser accepts empty input. The combinators decide this from internal facts that each built-in parser carries about its empty parse step and completion. Those facts are not public, so only parsers defined in @optique/core can provide them.
Any other parser counts as unknown, and so does any group whose outcome depends on it. For example, bindConfig(fail(), { ... }) has no leading names, so or() would consider it when choosing a branch for an empty argument list. In or(bindConfig(fail(), { ... }), argument(string())) its facts are unknown, the group gets no acceptsEmpty record, and the formatters fall back to the notation the branches declare.
This affects the integration packages in this repository as much as third-party parsers. bindEnv(), bindConfig(), prompt(), bindKeyring(), bindDerivedDefault(), and the discover wrappers cannot reach the internal facts, so groups that contain them are always unknown.
Proposal
Add a public way for a parser to declare its empty-input behavior, and let wrappers pass on the facts of the parser they wrap. Combinators would treat declared facts the same way they treat built-in ones: a missing fact stays unknown, and a declared fact is trusted.
Open questions
- Which facts to expose. The internal model has four: the outcome of the empty parse step (success, provisional, or failure), whether completion succeeds after that step, whether completion succeeds from the initial state, and the shape of the state the step produces. The last one exists only so that
multiple() can tell whether a zero-token step leaves an item behind. It depends on runtime details such as state identity and singleton-array unwrapping, so it probably should stay internal, and repetition of a parser with declared facts would stay unknown.
- How to keep declared facts valid. Built-in facts are bound to the parser's
parse(), complete(), and initialState, so spreading a parser and replacing a method drops them. A public API needs the same protection, or a clear rule that whoever replaces a method must declare the facts again.
- How to describe parsers whose outcome depends on context. Whether
bindEnv() accepts empty input depends on the environment at run time. Declaring it unknown is safe, but a separate “depends on context” value might let formatters keep these parsers drawn as optional on purpose instead of by fallback.
- Whether
withDefault() with a function default, conditional(), merge(), concat(), and seq() should gain built-in facts at the same time, or in separate changes.
Non-goals
#1015, which resolves #1013, lets usage groups reflect how their parsers handle an empty argument list.
or(),longestMatch(), andmultiple()recordacceptsEmptyon theexclusiveandmultipleterms they produce, and both usage formatters draw those groups as optional exactly when the parser accepts empty input. The combinators decide this from internal facts that each built-in parser carries about its empty parse step and completion. Those facts are not public, so only parsers defined in @optique/core can provide them.Any other parser counts as unknown, and so does any group whose outcome depends on it. For example,
bindConfig(fail(), { ... })has no leading names, soor()would consider it when choosing a branch for an empty argument list. Inor(bindConfig(fail(), { ... }), argument(string()))its facts are unknown, the group gets noacceptsEmptyrecord, and the formatters fall back to the notation the branches declare.This affects the integration packages in this repository as much as third-party parsers.
bindEnv(),bindConfig(),prompt(),bindKeyring(),bindDerivedDefault(), and the discover wrappers cannot reach the internal facts, so groups that contain them are always unknown.Proposal
Add a public way for a parser to declare its empty-input behavior, and let wrappers pass on the facts of the parser they wrap. Combinators would treat declared facts the same way they treat built-in ones: a missing fact stays unknown, and a declared fact is trusted.
Open questions
multiple()can tell whether a zero-token step leaves an item behind. It depends on runtime details such as state identity and singleton-array unwrapping, so it probably should stay internal, and repetition of a parser with declared facts would stay unknown.parse(),complete(), andinitialState, so spreading a parser and replacing a method drops them. A public API needs the same protection, or a clear rule that whoever replaces a method must declare the facts again.bindEnv()accepts empty input depends on the environment at run time. Declaring it unknown is safe, but a separate “depends on context” value might let formatters keep these parsers drawn as optional on purpose instead of by fallback.withDefault()with a function default,conditional(),merge(),concat(), andseq()should gain built-in facts at the same time, or in separate changes.Non-goals