7.1 KiB
G-046 punk::args deferred -help resolution (parse-time performance + reentrancy) and rendering/value-shape fixes
Status: proposed Scope: src/modules/punk/args-999999.0a1.0.tm (resolve/get_dict: display-field deferral, dynamic-cache subst path, prefix writeback, string renderer, cmdhelp-facing messages), src/modules/punk/ansi-999999.0a1.0.tm (mark_columns argdoc as the reentrancy/perf testbed), src/tests/modules/punk/args/testsuites/ (GAP tests flip; perf verification) Acceptance: parsing/argument resolution provably skips -help expansion (a definition whose -help contains a ${[...]} that would error or record its invocation shows the substitution did NOT run during a parse-only path, only for help display); first parse of punk::ansi::mark_columns drops from ~4s to well under a second with 'i punk::ansi::mark_columns' still rendering the embedded example, and a -help that parses its OWN definition id resolves or errors cleanly rather than looping; first-parse timing improves for at least one other heavily documented command (recorded in the detail file); rendering_atdynamic_multiline_help_insertion_GAP flips to all-aligned; choicegroups_imap_prefix_listwrap_GAP flips to shape-identical (prefix input yields the same plain string as exact input); the -return string renderer's cmd-help continuations align under the first line with relative indents preserved (rendering_string_renderer_characterization updated); the 'Bad number of leading values...' prefix shown by goodargs parsing in 'i string is'-style output is reworded or suppressed for the usage-display path; full punk::args and punk::ns suites pass with no non-GAP expectations weakened.
Context
Drafted 2026-07-09 from defects found (and pinned as GAP tests) during the tests-first characterization pass. User emphasis at approval: "Any performance gain we can gain from deferring -help processing out of argument resolution would be worthwhile" - deferral is the headline item, not merely a reentrancy patch.
1. Deferred -help resolution (performance + reentrancy)
punk::args::resolve currently tstr-processes entire definition blocks, including -help sections. The in-source review note (resolve, near the block-processing loop) already observes: "-help section processing is sometimes expensive ... isn't required for parsing of arguments ... ideally we would delay expansion of -help sections until needed for display, and use a different cache key for the parsing vs display versions of the resolved definition."
The reentrancy class this also fixes, reproduced 2026-07-09 with punk::ansi::mark_columns: its argdoc -help contains ${[punk::ansi::argdoc::num_columns_example]}, and that example proc itself calls mark_columns, whose punk::args::parse resolves the same still-resolving definition. First call measured at ~3.94 SECONDS (60us thereafter, once cached). The mark_columns source (punk/ansi ~6443-6470) documents the hazard - including the observed "too many nested evaluations (infinite loop?)" when [a red] etc. are used in that -help - and lists the candidate fixes: (a) avoid parsing functions in argdocs (limiting, not DRY), (b) reentrancy guard, (c) don't process -help during argument resolution. This goal takes (c) as primary (with the perf benefit), with a reentrancy guard as defence-in-depth for the display path (a -help that renders help for its OWN id must error cleanly or resolve, never loop).
Design notes:
- separate cache keys for parse-spec vs display-spec (per the in-source note); the @dynamic unresolved-cache (ptlist/paramlist subst mechanism) splits accordingly.
- fields in display-only scope: -help on @cmd/@examples/args, -choicelabels content, @formdisplay bodies - anything not consulted by get_dict for matching/validation. (-choices/-choicealiases/-typesynopsis ARE parse-relevant or synopsis-relevant - classify explicitly in implementation.)
2. @dynamic multiline paramindent gap
rendering_atdynamic_multiline_help_insertion_GAP: a ${$DYN_X} double substitution whose
second-round result is multiline lands lines 2+ at insertion-column minus 4 (internal
relative indents preserved), because the cached-path plain subst has no paramindent
re-alignment - contrast single-level ${[cmd]} insertion which aligns fully
(rendering_tstr_multiline_command_insertion). Fix: apply the same paramindent treatment
to second-round substitution results. Note interaction with item 1: if display fields
defer, the fix belongs in the display-time expansion path.
3. Prefix-normalization value shape (list-wrap)
choicegroups_imap_prefix_listwrap_GAP: for choices containing backslashes (punk::imap4's {\Deleted} etc. - spaces would behave likewise), exact input stores the plain string but prefix-normalized input stores a one-element LIST ({\Deleted}) - the get_dict overwrite writeback lsets into the stored value treating a scalar as a list. Consumers get a different value shape depending on how the user typed the choice. Fix the writeback for the single-value case (the -multiple/clause paths already index deliberately).
4. -return string renderer cmd-help alignment
rendering_string_renderer_characterization: the string renderer joins the -help first line onto the "Description: " label but drops continuations flush-left (relative indents preserved among themselves). The table renderer aligns properly. Align continuations under the first line. (The characterization test's pinned flush-left expectations get updated to the aligned form.)
5. cmdhelp goodargs message accuracy
'i string is' (and any command whose definition requires leaders) prefixes its usage output with "Bad number of leading values for punk::args::parse $args_remaining ..." - an internal-looking, inaccurate message produced by cmdhelp's goodargs-marking parse of the (empty) trailing args. Reword for the usage-display path or suppress (the parse is only advisory for highlighting supplied args). User: "the message is not accurate and could do with a fix up at some stage."
Alternatives considered
- Reentrancy guard alone (without deferral) for item 1 - rejected as primary: it fixes the loop but forfeits the first-parse performance win the user explicitly wants, and heavy -help processing would still run on every first parse.
- Documenting "don't call parsing functions from argdocs" - rejected (the mark_columns source itself calls this limiting and non-DRY).
Notes
- GAP tests to flip: rendering_atdynamic_multiline_help_insertion_GAP, choicegroups_imap_prefix_listwrap_GAP; characterization tests to update: rendering_string_renderer_characterization. Safety net: the full 128-test punk/args suite (esp. rendering.test invariants and dev/dynamic-cache.test) and punk/ns cmdflow.
- Perf evidence baseline (2026-07-09): mark_columns first parse ~3.94s / subsequent 60us (punk902z dev, this machine). tclcore moduledoc commands (e.g. ::lseq with helpers::example blocks) are candidates for the second timing datapoint.
- Related: G-045 (authoring ergonomics - same resolve neighbourhood; free sequencing), G-041 (form matching will exercise resolve paths more heavily - deferral lightens its per-form attempts), G-044 (completion wants fast parse-spec resolution).