You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

6.4 KiB

G-041 punk::args multi-form matching: automated form selection for parsing and documentation

Status: proposed Scope: src/modules/punk/args-999999.0a1.0.tm (parse form selection, arg_error/usage form marking), src/modules/punk/ns-999999.0a1.0.tm (cmdhelp/synopsis closest-form indication), src/tests/modules/punk/args/testsuites/ Goal: for multi-form definitions punk::args determines which form(s) an argument list matches - parse without -form attempts all permitted forms instead of effectively form 0, -form accepts the documented list-of-forms restriction, and the documentation surface indicates the match ('i after cancel ' presents the cancel form; 's after cancel someid' marks the closest synopsis) - with explicit single-form restriction retained for callers that require it. Acceptance: parsing a multiform definition (after-like fixture) without -form succeeds when the args match exactly one form (the pinned GAP tests in forms.test flip to auto-selected results); an argument list matching no form (or several) produces an error naming the candidate forms rather than a form-0 type error; -form with a list of form names/indices restricts parsing to that subset (currently an 'Expected int 0-N or one of ...' error); 'i <args...>' and synopsis output indicate the best-matching form(s) for supplied args; explicit -form behaviour is unchanged; the full punk::args and punk::ns suites pass.

Context

Multi-form definitions (@form directive) describe commands whose argument sets are orthogonal - the motivating example is the Tcl builtin after, documented with several forms (after ms ?script...?, after cancel id, after idle script..., ...). The definition and display side works: form_names are recorded, punk::ns::synopsis renders every form (## FORM n headers), and punk::args::parse -form <name-or-index> parses against an explicitly chosen form.

What is missing (characterized 2026-07-08 with an after-like fixture, probe results pinned as GAP tests in src/tests/modules/punk/args/testsuites/args/forms.test):

  • punk::args::parse without -form (default *) does NOT attempt the other forms: arguments that match only a non-zero form (e.g. cancel after#1) fail with form 0's type/arity error ("Bad number of values ... Not enough remaining values", the leading word rejected by form 0's int type). Effectively * parses form 0 only.
  • -form is documented as "Restrict parsing to the set of forms listed" and typed as a list, but a list of two form names errors with "invalid -form value ... Expected int 0-N or one of ''" - only a single name or index is accepted.
  • Documentation surface: i after cancel <id> renders help for the default form rather than determining that the cancel form is the closest match, and s after cancel someid lists all synopses without marking the matching one.
  • Internal note: punk::args::parse's own definition is multiform (withid/withdef, discriminated by literal types) but the proc hand-parses its arguments on the happy path, so the multiform selection machinery is not exercised internally either.

User direction (2026-07-08): there are times when restricting to a particular form is appropriate and times when automated selection is better - both must remain expressible. This is an advanced improvement, deferred; the immediate prerequisite work was making the punk::args test suite comprehensive so the current behaviour is pinned before any form-selection logic lands.

Approach (sketch - to be refined when activated)

  1. Form candidacy: for -form * (or a list), attempt each permitted form's parse; classify outcomes (clean match / match-with-defaults / type-or-arity failure). Exactly one clean match -> auto-select. None or several -> error naming candidate forms (and for 'several', possibly a deterministic preference rule - e.g. first-declared - recorded when decided).
  2. Literal/leading-word discrimination as a fast path: many multiform commands (after, parse itself) discriminate on a leading literal - a pre-pass on literal/literalprefix leader types can pick the form without full parse attempts.
  3. -form list support: accept a subset of names/indices as documented; single value behaviour unchanged.
  4. Documentation surface: cmdhelp/arg_error accept the supplied trailing args (cmdhelp already parses them for goodargs marking) and use the same candidacy logic to pick or rank forms; synopsis display marks the matching form(s).
  5. Error-message quality: a no-form-matches error should show per-form failure reasons compactly rather than only form 0's.

Downstream consumer note (2026-07-08, G-044): the interactive command-completion/hinting goal consumes form candidacy on PARTIAL argument lists - as the user types, the hinting display wants "which forms are still compatible with the words so far" - so the candidacy mechanism should be exposed as an API that accepts an incomplete argument list and returns per-form compatibility (not only an all-or-nothing full parse). Design for that consumer when shaping step 1.

Alternatives considered

  • Documenting alternate forms as separate subhelp choiceinfo entries (the trace-style pattern) - works only when a literal subcommand word exists and fragments a command's documentation into pseudo-subcommands; rejected as the general answer.
  • Requiring callers to always pass -form - status quo for parsing, but unusable for the interactive doc surface ('i after cancel ') where the user supplies args, not form names.

Notes

  • Probe script: scratchpad formprobe.tcl (2026-07-08); findings encoded in src/tests/modules/punk/args/testsuites/args/forms.test (GAP-labelled tests reference this goal and flip when it is implemented).
  • Related: G-040 (choice aliasing/doc-lookup parity - same "parser and doc surface must agree" principle); punk::args::parse -cache interaction with per-form attempts will need review (cache key already includes selected form).
  • Incidental find during the same probing: an uncommented debug puts stderr ">>>_get_dict_can_assign_value NOT alloc_ok..." fired on every failed clause type assignment (punk/args ~l.6186; its happy-path twin was already commented). Fixed 2026-07-08 (punk::args 0.2.3) - noted here because form-attempt logic will exercise that failure path heavily.
  • Archived-goal references in this file: G-040 achieved 2026-07-08 (goals/archive/G-040-punkargs-choicealiases.md).