Browse Source
parsekey coverage review outcome: - tests/args/parsekey.test: pin mash-path last-wins ordering, prefix-abbreviation keying and @opts -any passthrough; GAP pins for last-defined-member default precedence, cross-member -multiple value loss, parsekey/optname collision conflation, and values/leaders parsekey breakage (abort/ignore/reject) - new tests/dev/parsekey-knownbugs.test: desired-behaviour pins disabled behind punkargsKnownBug (value/leader key renaming, cross-member accumulation in received order) - GOALS.md + detail files: G-083 argument relations (per-arg -conflicts checked against received args, @opts strict mode for shared-parsekey groups, define-time parsekey collision validation, lsearch/clock clicks dogfood); G-084 -parsekey completeness (cross-member -multiple collection, leaders/values support or define-time rejection, defaulted-member precedence documented) - src/tests/modules/AGENTS.md: punk/args suite index updated Verified: full punk/args suite under Tcl 9.0.3 = 221 total / 217 passed / 0 failed / 4 skipped (intended disabled pins); parsekey.test 16/16 under 8.7; goals_lint clean. Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.commaster
6 changed files with 449 additions and 1 deletions
@ -0,0 +1,86 @@ |
|||||||
|
# G-083 punk::args argument relations: strict mutual exclusivity and parsekey-group integrity |
||||||
|
|
||||||
|
Status: proposed |
||||||
|
Scope: src/modules/punk/args-999999.0a1.0.tm (resolve/spec compiler, parse paths incl mash, arg_error/synopsis/usage renderers, errorcode vocabulary), src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm (lsearch, clock clicks dogfood), src/tests/modules/punk/args/testsuites/args/ + testsuites/dev/ (GAP flips + new characterization) |
||||||
|
Goal: punk::args definitions can express hard mutual exclusivity - a per-argument -conflicts relation (keyed on parsekeys, checked only against received arguments so defaults never conflict) and an @opts-level strict mode making distinct-member co-occurrence within a shared-parsekey group an error - while legacy last-wins remains the default, so lsearch-class commands (grouped styles with pairwise incompatibilities like -sorted vs -glob/-regexp and -bisect vs -all/-not) parse and document faithfully without combinatorial @form explosion. |
||||||
|
Acceptance: parse raises a usage-style error with a structured errorcode (PUNKARGS VALIDATION optionconflict shape) naming both offending received arguments for -conflicts violations and strict-group co-occurrence, through both the ordinary and mash option paths with prefix abbreviations resolved before the check; define time rejects a -parsekey colliding with a distinct defined argument's name (parsekey_collides_with_defined_optname_GAP flips); legacy behaviour is untouched by default (full existing punk::args suite passes, including the pinned last-wins/prepend-defaults override idiom); the lsearch moduledoc models its documented incompatibilities with the new vocabulary and drops its "punk::args fixes required for grouped mutually exclusive options" caveat; usage/synopsis output for conflict rules is pinned by characterization tests. |
||||||
|
|
||||||
|
## Context |
||||||
|
|
||||||
|
Review finding (2026-07-14): punk::args has no way to express "error if these |
||||||
|
arguments are combined". What exists today: |
||||||
|
|
||||||
|
- `-parsekey` shared keys give SOFT exclusivity by overwrite: last received |
||||||
|
member wins under the common key. That semantics is deliberate and pinned |
||||||
|
(parsekey_repeat_ordering: overriding earlier non -multiple options must |
||||||
|
always be possible - it protects the prepend-defaults idiom |
||||||
|
`punk::args::parse [list -flag default {*}$userargs]`). |
||||||
|
- `-group`/`-grouphelp` associate options for usage display only; no parse |
||||||
|
semantics. |
||||||
|
- Multiple `@form`s are the only strict mechanism - structural, combinatorially |
||||||
|
explosive, and they fork the documentation. |
||||||
|
- The `namespace which` moduledoc entry works around the gap by modelling |
||||||
|
`-command`/`-variable` as one positional with `literalprefix(...)` choices |
||||||
|
(exactly-one-of, but only for a fixed position, not free-order flags) and |
||||||
|
carries `##todo - make mutually exclusive - (separate forms)`. |
||||||
|
|
||||||
|
Evidence of need in the tclcore moduledoc alone: |
||||||
|
|
||||||
|
- The lsearch definition states: "(documentation incomplete - punk::args fixes |
||||||
|
required for grouped mutually exclusive options and prefix calculation)". |
||||||
|
- lsearch `-sorted` is "mutually exclusive with -glob and -regexp" per the man |
||||||
|
page - yet it shares the -MATCHSTYLE parsekey group with them, whose members |
||||||
|
are otherwise last-wins. Both semantics coexist within ONE group, so a |
||||||
|
per-group boolean alone cannot model lsearch. |
||||||
|
- lsearch `-bisect` "cannot be used with either -all or -not" - a cross-group |
||||||
|
conflict with no shared value key. |
||||||
|
- `clock clicks` `-milliseconds`/`-microseconds` carry a definition comment |
||||||
|
noting they are mutually exclusive and unmodelled. |
||||||
|
|
||||||
|
A define-time integrity hole was found in the same review: nothing prevents a |
||||||
|
`-parsekey` value colliding with a DIFFERENT defined option's name (e.g. |
||||||
|
`{-a -parsekey -b} {-b}`); the two silently conflate into an implicit |
||||||
|
shared-key group (pinned by parsekey_collides_with_defined_optname_GAP). |
||||||
|
|
||||||
|
## Approach |
||||||
|
|
||||||
|
Design leans (settle and record here during the work): |
||||||
|
|
||||||
|
- Per-argument `-conflicts {parsekey-or-flag ...}`: violation checked against |
||||||
|
RECEIVED arguments only, after prefix/abbreviation resolution, never against |
||||||
|
defaults. Applies to any pair regardless of grouping. |
||||||
|
- `@opts`-level strict mode for a shared-parsekey group (name TBD, e.g. |
||||||
|
`-parsekeymode override|error` or `-groupexclusive`): distinct-member |
||||||
|
co-occurrence errors; same-member repetition stays G-053 territory |
||||||
|
(`-multiple {0 1}`), and legacy last-wins remains the default everywhere. |
||||||
|
- The mash (short-flag bundling) parse path implements its own parsekey logic |
||||||
|
and must share the conflict check - covered explicitly by acceptance. |
||||||
|
- Errorcode vocabulary: extend PUNKARGS VALIDATION with an `optionconflict` |
||||||
|
member mirroring the existing `optionmissing` shape, designed so a later |
||||||
|
requires-relation slots in alongside. |
||||||
|
- Usage/synopsis rendering: the group rendering machinery (OPT_GROUPS headers, |
||||||
|
parsekey hints) is most of the display substrate; conflict rules need a |
||||||
|
representation there. |
||||||
|
|
||||||
|
## Alternatives considered |
||||||
|
|
||||||
|
- Per-group strict boolean only - insufficient: lsearch mixes last-wins and |
||||||
|
error semantics within one group (see Context). |
||||||
|
- Modelling exclusivity via `@form` explosion - rejected: combinatorial and |
||||||
|
forks documentation; the goal exists to avoid this. |
||||||
|
- Folding into G-053 range-valued -multiple - rejected: G-053 is occurrence |
||||||
|
arity of ONE argument; this goal is relations BETWEEN arguments. They |
||||||
|
compose (shared error vocabulary, different axes). |
||||||
|
|
||||||
|
## Notes |
||||||
|
|
||||||
|
- `-requires`/`-implies` (dependency relations, e.g. lsearch `-subindices` |
||||||
|
with `-index`, `-bisect` implying `-sorted`) deliberately deferred - the man |
||||||
|
page semantics there are mostly soft ("has no effect unless"); design the |
||||||
|
errorcode vocabulary so they can be added without reshaping it. |
||||||
|
- Test assets already in place (2026-07-14): characterization pins in |
||||||
|
src/tests/modules/punk/args/testsuites/args/parsekey.test (mash ordering, |
||||||
|
prefix abbreviation, collision conflation GAP) ready to flip/extend. |
||||||
|
- Related: G-053 (occurrence arity), G-072 (compound clause types - orthogonal), |
||||||
|
G-084 (parsekey completeness fixes; its cross-member -multiple design |
||||||
|
decision should be made with this goal's vocabulary in mind). |
||||||
@ -0,0 +1,50 @@ |
|||||||
|
# G-084 punk::args -parsekey completeness: cross-member -multiple collection, leaders/values support, defined default precedence |
||||||
|
|
||||||
|
Status: proposed |
||||||
|
Scope: src/modules/punk/args-999999.0a1.0.tm (parse paths, resolve directive handling for @leaders/@values), src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm (sites carrying '#todo - fix -parsekey for leaders and values'), src/tests/modules/punk/args/testsuites/args/parsekey.test + testsuites/dev/parsekey-knownbugs.test (GAP/known-bug flips) |
||||||
|
Goal: -parsekey behaves as a complete, position-independent result-keying mechanism: distinct -multiple members sharing a parsekey accumulate under the shared key in received order (no silent value loss), -parsekey on leaders and values renames their result/received keys the same way it does for options (or is rejected at define time everywhere it is unsupported - no silent ignore, no parse-time abort), and the precedence rule for multiple defaulted members of one group is documented behaviour rather than an in-code '? review'. |
||||||
|
Acceptance: the three punkargsKnownBug tests in testsuites/dev/parsekey-knownbugs.test are enabled and pass (or are amended to the settled design and pass); the corresponding _GAP pins in parsekey.test are flipped or retired; the '#todo - fix -parsekey for leaders and values' moduledoc comment is resolved; the full existing punk::args suite passes untouched. |
||||||
|
|
||||||
|
## Context |
||||||
|
|
||||||
|
Probe findings from the 2026-07-14 parsekey coverage review, each pinned by a |
||||||
|
_GAP test in src/tests/modules/punk/args/testsuites/args/parsekey.test: |
||||||
|
|
||||||
|
- Cross-member -multiple value loss: with `-add` and `-sub` both `-multiple 1` |
||||||
|
sharing parsekey `-op`, parsing `-add a -sub b -add c` returns `-op b` - the |
||||||
|
collected `-add` occurrences `{a c}` vanish. Occurrences are collected per |
||||||
|
member flag and the shared-key resolution then returns only one member's |
||||||
|
list (parsekey_shared_key_multiple_collection_GAP). |
||||||
|
- `-parsekey` on a value is accepted at define time but ABORTS parsing with an |
||||||
|
internal error; on a leader it is silently ignored; the `@values` directive |
||||||
|
line rejects it as an unrecognised key (parsekey_values_leaders_GAP). The |
||||||
|
tclcore moduledoc carries `#todo - fix -parsekey for leaders and values` |
||||||
|
next to a definition using it, and choices.test notes a leaders/values |
||||||
|
parsekey mechanism would help multi-element clause choice grouping. |
||||||
|
- When more than one member of a shared-parsekey group declares `-default`, |
||||||
|
the last-defined member's default wins - the ordered_opts default handling |
||||||
|
in the source carries a literal `? review` |
||||||
|
(parsekey_shared_key_member_defaults_GAP). |
||||||
|
|
||||||
|
## Approach |
||||||
|
|
||||||
|
- Accumulate-in-received-order is the lean for cross-member -multiple |
||||||
|
collection, pinned (disabled) in dev/parsekey-knownbugs.test as |
||||||
|
parsekey_shared_key_multiple_accumulation. Open design decision: once |
||||||
|
G-083's relations vocabulary exists, "error on cross-member combination |
||||||
|
unless permitted" is a defensible alternative - decide before activating. |
||||||
|
- Leaders/values support should reuse the option path's keying rules |
||||||
|
(result + received dicts, required-list satisfaction) rather than a parallel |
||||||
|
mechanism; where support is deliberately not implemented, define time must |
||||||
|
reject the key with a clear message instead of deferring failure to parse. |
||||||
|
- The defaulted-members precedence rule (whatever is settled) gets stated in |
||||||
|
the -parsekey documentation and the `? review` comment removed. |
||||||
|
|
||||||
|
## Notes |
||||||
|
|
||||||
|
- Desired-behaviour pins (disabled behind punkargsKnownBug): |
||||||
|
src/tests/modules/punk/args/testsuites/dev/parsekey-knownbugs.test - enable |
||||||
|
and flip the parsekey.test _GAP pins as fixes land. |
||||||
|
- Related: G-083 (argument relations - shared design context for the |
||||||
|
cross-member collection decision), G-053 (occurrence arity of a single |
||||||
|
argument). |
||||||
@ -0,0 +1,80 @@ |
|||||||
|
package require tcltest |
||||||
|
|
||||||
|
#added 2026-07-14 (agent) - parsekey coverage review. |
||||||
|
#Desired-behaviour pins for known parsekey gaps. Each has a corresponding _GAP test in |
||||||
|
#testsuites/args/parsekey.test pinning today's behaviour; when a fix lands, enable the |
||||||
|
#test here and flip/remove the GAP pin there. |
||||||
|
namespace eval ::testspace { |
||||||
|
namespace import ::tcltest::* |
||||||
|
tcltest::testConstraint punkargsKnownBug 0 |
||||||
|
|
||||||
|
variable common { |
||||||
|
set result "" |
||||||
|
} |
||||||
|
|
||||||
|
test parsekey_value_result_key {Known bug: -parsekey on a value should rename the values result key (currently aborts parse)}\ |
||||||
|
-constraints punkargsKnownBug\ |
||||||
|
-setup $common -body { |
||||||
|
set docids [list] |
||||||
|
|
||||||
|
set argd [punk::args::parse {hello} withdef @values {v1 -parsekey renamed -type string}] |
||||||
|
lappend docids [dict get $argd id] |
||||||
|
lappend result [dict get $argd values] |
||||||
|
lappend result [dict exists [dict get $argd received] renamed] |
||||||
|
lappend result [dict exists [dict get $argd received] v1] |
||||||
|
}\ |
||||||
|
-cleanup { |
||||||
|
foreach id $docids { |
||||||
|
punk::args::undefine $id 1 |
||||||
|
} |
||||||
|
}\ |
||||||
|
-result [list\ |
||||||
|
{renamed hello} 1 0 |
||||||
|
] |
||||||
|
|
||||||
|
test parsekey_leader_result_key {Known bug: -parsekey on a leader should rename the leaders result key (currently ignored)}\ |
||||||
|
-constraints punkargsKnownBug\ |
||||||
|
-setup $common -body { |
||||||
|
set docids [list] |
||||||
|
|
||||||
|
set argd [punk::args::parse {hello there} withdef {@leaders -min 1 -max 1} {l1 -parsekey renamed -type string} @values {v1 -type string}] |
||||||
|
lappend docids [dict get $argd id] |
||||||
|
lappend result [dict get $argd leaders] |
||||||
|
lappend result [dict exists [dict get $argd received] renamed] |
||||||
|
}\ |
||||||
|
-cleanup { |
||||||
|
foreach id $docids { |
||||||
|
punk::args::undefine $id 1 |
||||||
|
} |
||||||
|
}\ |
||||||
|
-result [list\ |
||||||
|
{renamed hello} 1 |
||||||
|
] |
||||||
|
|
||||||
|
#Design-pending rather than settled: the lean is that -multiple 1 members sharing a |
||||||
|
#-parsekey accumulate under the shared key in received order (today only one member's |
||||||
|
#collected list survives - see parsekey_shared_key_multiple_collection_GAP). |
||||||
|
test parsekey_shared_key_multiple_accumulation {Design-pending: shared-parsekey members with -multiple 1 should accumulate in received order}\ |
||||||
|
-constraints punkargsKnownBug\ |
||||||
|
-setup $common -body { |
||||||
|
set docids [list] |
||||||
|
|
||||||
|
set argd [punk::args::parse {-incr -decr -incr} withdef {@opts -type none -parsekey -dir -multiple 1} {-incr -typedefaults u} {-decr -typedefaults d}] |
||||||
|
lappend docids [dict get $argd id] |
||||||
|
lappend result [dict get $argd opts] |
||||||
|
|
||||||
|
set argd [punk::args::parse {-add a -sub b -add c} withdef {@opts -type string -parsekey -op -multiple 1} -add -sub] |
||||||
|
lappend docids [dict get $argd id] |
||||||
|
lappend result [dict get $argd opts] |
||||||
|
}\ |
||||||
|
-cleanup { |
||||||
|
foreach id $docids { |
||||||
|
punk::args::undefine $id 1 |
||||||
|
} |
||||||
|
}\ |
||||||
|
-result [list\ |
||||||
|
{-dir {u d u}}\ |
||||||
|
{-op {a b c}} |
||||||
|
] |
||||||
|
} |
||||||
|
tcltest::cleanupTests ;#needed to produce test summary line. |
||||||
Loading…
Reference in new issue