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