Browse Source

G-071 achieved: punk::args 0.9.0 allocation choice screen - lseq-class optional-element arglists parse in-form (project 0.12.11)

Root cause: get_dict_can_assign_value blanket-satisfied the member check
for any argument carrying choices, so an optional restricted-choice value
(the lseq '..'/'to' noise word) consumed any word whenever arity
permitted, starving later elements/clauses and failing validation with a
confusing trailing-choices error ('lseq 0 10 2' shape).

Fix: an allocation choice screen using choiceword_match (the shared G-040
implementation - exact/alias/prefix/nocase semantics; -choicemultiple
words screened per list member within min/max), applied only where
allocation has an alternative: -optional arguments and further
occurrences of -multiple arguments. Required arguments are deliberately
not screened - the word must fill them and validation's choiceviolation
reporting stays informative (the existing parsestatus/cmdhelp/choices
pins forced and now document both refinements). -choicerestricted 0
behaviour unchanged.

Results: the allocation.test GAPs flipped ('1 2 3' and '1 2 by 3' parse
with correct clause shapes {{} 3}/{by 3}; '1 2 3 4' gets the plain
excess-values rejection instead of blaming the unrelated optional);
prefix normalization survives the screen; all noise-word variants and
if-shape guards unchanged. lseq moduledoc matrix parses per lseq.n under
-form range, and 'i lseq 0 10 2' works end-to-end. Remaining lseq
divergences trace to G-041 form selection or the -type expr laxity
recorded in G-055 - not allocation.

Also corrected an increment-1 mis-premise: parse_status ALREADY accepts
-form before the withid/withdef tail per its documented synopsis - the
probe had passed it trailing. GAP replaced by
parsestatus_form_option_order (correct-order acceptance incl. the form
key, trailing rejection). No code change was needed for that acceptance
clause.

Verification: punk::args suite 193 pass / 1 pre-existing skip / 0 fail;
punk::ns 53/53; full source-tree suite 813 pass / 13 skip / 1 fail =
exec-14.3 only (known baseline) - zero regressions (tclsh 9.0.3; lseq
verification in punk902z src, Tcl 9.0.2).

Flip edits: G-071 index entry moved to GOALS-archive.md (achieved
2026-07-12), detail Status flipped with the acceptance review and
before/after record; reference sweep updates G-041 (prerequisite fixed,
parse_status correction) and G-072 Notes. The detail-file move to
goals/archive/ follows as a pure rename; goals_lint transiently unclean
between the two commits.

Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 2 days ago
parent
commit
d4519f41d3
  1. 4
      CHANGELOG.md
  2. 4
      GOALS-archive.md
  3. 4
      GOALS.md
  4. 17
      goals/G-041-punkargs-form-matching.md
  5. 59
      goals/G-071-punkargs-optional-allocation.md
  6. 10
      goals/G-072-punkargs-compound-clause-types.md
  7. 2
      punkproject.toml
  8. 91
      src/modules/punk/args-999999.0a1.0.tm
  9. 3
      src/modules/punk/args-buildversion.txt
  10. 66
      src/tests/modules/punk/args/testsuites/args/allocation.test

4
CHANGELOG.md

@ -5,6 +5,10 @@ The latest `## [X.Y.Z]` header must match the `version` field in `punkproject.to
Entries are newest-first; one bullet per notable change. See the root `AGENTS.md`
"Project Versioning" section for the bump policy.
## [0.12.11] - 2026-07-12
- G-071 achieved: punk::args 0.9.0 allocation choice screen - optional arguments with restricted choice sets no longer greedily consume non-choice words at allocation time, so noise-word grammars parse correctly with the noise word omitted. 'i lseq 0 10 2' (and the '0 10 by 2' / '1 5 by 0' shapes) now parse and render correctly against the tclcore moduledoc; genuinely invalid arglists report a plain excess-values error instead of blaming an unrelated optional argument. Correction recorded: parse_status already accepted -form before the withid/withdef tail per its documented synopsis - the earlier 'missing -form' finding was an argument-order mistake, now pinned by test.
## [0.12.10] - 2026-07-12
- punk::args 0.8.3 (G-071 increment): silenced four more unconditional debug lines that printed to stderr during normal argument parsing whenever an optional element was skipped (visible when using 'i'/help display against commands modelled with noise-word arguments such as if and lseq). New allocation characterization testsuite pins the optional-element mis-allocation ('lseq 0 10 2'-class failures) as GAP tests ahead of the allocator fix.

4
GOALS-archive.md

@ -73,3 +73,7 @@ Acceptance: a documented probe helper yields a wsl_linux_available constraint wh
### G-062 [achieved 2026-07-11] Canonical project license: BSD-2-Clause LICENSE file with SPDX-identified references → detail: goals/archive/G-062-project-license-file.md
Scope: LICENSE.txt (new, repo root), README.md, punkproject.toml ([project] license field), AGENTS.md (Repo-wide Notes license mention)
Acceptance: LICENSE.txt exists at the repo root containing the standard BSD-2-Clause text with a real copyright line; README.md names BSD-2-Clause and points at LICENSE.txt; root AGENTS.md Repo-wide Notes names the license precisely; punkproject.toml [project] carries license = "BSD-2-Clause"; a repo-sweep for top-level license claims finds none contradicting it (sweep result recorded here).
### G-071 [achieved 2026-07-12] punk::args value-allocation correctness for optional elements (lseq-class arglists) + parse_status -form → detail: goals/archive/G-071-punkargs-optional-allocation.md
Scope: src/modules/punk/args-999999.0a1.0.tm (get_dict value allocation, private::get_dict_can_assign_value, parse_status), src/tests/modules/punk/args/testsuites/args/ (new allocation characterization suite), src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm (::lseq as the proving consumer)
Acceptance: a new allocation characterization suite drives the lseq range matrix under explicit -form range - '0 10', '0 10 2', '0 10 by 2', '0 to 10 2', '0 .. 10 by 2', '1 5 by 0' parse per the lseq.n grammar (the three currently-failing cases fixed) and '0 10 2 4' still fails - plus reduced fixtures isolating the shape (optional choice value between required values + trailing optional-member clause) independent of the moduledoc; the ::if noise-word cases (mid-clause ?literal(then)?, clause-leading ?literal(else)?) keep passing - no regression to optional clause members generally; a genuinely invalid arglist's error names the failing element (the '..|to' misblame case pinned fixed); punk::args::parse_status accepts -form (single form name/index at minimum, consistent with parse) with its status structure reporting the form used; full punk::args and punk::ns suites pass with no expectations weakened; before/after results for the probe matrix recorded in this file.

4
GOALS.md

@ -286,10 +286,6 @@ Detail: goals/G-069-splitter-tclparser-lint.md
Scope: src/modules/punk/lib-999999.0a1.0.tm (tclparser_tcl stub + dispatch; new module if size warrants - decided in the work), src/tests/modules/punk/lib/ (parity + fallback suites), TEMP_REFERENCE/ (tclparser reference source, user-provided, read-only)
Detail: goals/G-070-pure-tcl-tclparser.md
### G-071 [active] punk::args value-allocation correctness for optional elements (lseq-class arglists) + parse_status -form
Scope: src/modules/punk/args-999999.0a1.0.tm (get_dict value allocation, private::get_dict_can_assign_value, parse_status), src/tests/modules/punk/args/testsuites/args/ (new allocation characterization suite), src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm (::lseq as the proving consumer)
Detail: goals/G-071-punkargs-optional-allocation.md
### G-072 [proposed] punk::args compound clause types: named alternates with per-element typing and per-alternate arity (try-class handlers)
Scope: src/modules/punk/args-999999.0a1.0.tm (type-expression parsing, clause allocation, synopsis/help renderers), src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm (::try as proving consumer; ::if/::switch as touched), src/tests/modules/punk/args/testsuites/args/
Detail: goals/G-072-punkargs-compound-clause-types.md

17
goals/G-041-punkargs-form-matching.md

@ -97,15 +97,22 @@ when shaping step 1.
optional-member clause. lseq range-form arglists WITHOUT the ../to noise word
but WITH a step ('0 10 2', '0 10 by 2', '1 5 by 0') fail IN the range form (the
error blames '..|to'), while '0 to 10 2' and '0 .. 10 by 2' parse fine. No other
lseq form accepts those arglists, so form auto-selection alone cannot make
'i lseq 0 10 2' work - the allocator fix must precede or accompany this goal
(flagged as a candidate goal 2026-07-12). ::if's noise-word clauses
lseq form accepts those arglists, so form auto-selection alone could not make
'i lseq 0 10 2' work. The allocator prerequisite became G-071 and was FIXED
the same day (achieved 2026-07-12, punk::args 0.9.0 allocation choice screen -
see goals/archive/G-071-punkargs-optional-allocation.md): the lseq matrix now
parses in-form, leaving form auto-selection as this goal's remaining gap for
the 'lseq 3' / switch-block-form cases. ::if's noise-word clauses
('?literal(then)?' mid-clause, '?literal(else)?' clause-leading) parse correctly
in the same probes, so the failure is specific to the optional standalone value
+ trailing optional-member clause combination, not optional clause members
generally.
- punk::args::parse_status does not accept -form - needed for per-form probing,
for this goal's candidacy API, and for the G-044 partial-arglist consumer.
- CORRECTED (G-071 closeout): parse_status DOES accept -form - positioned
before the withid/withdef tail per its documented synopsis; the probe had
passed it trailing. Behaviour pinned in allocation.test
(parsestatus_form_option_order). The candidacy API and the G-044 consumer
can build on the existing surface (list-of-forms support remains this
goal's -form list item, as for parse).
- Over-acceptance divergences (model valid where the real command errors) recorded
for the modelability list in G-055's detail file - not this goal's scope.
- Incidental fix during probing: another unconditional debug puts on the clause

59
goals/G-071-punkargs-optional-allocation.md

@ -1,6 +1,6 @@
# G-071 punk::args value-allocation correctness for optional elements (lseq-class arglists) + parse_status -form
Status: active
Status: achieved 2026-07-12
Scope: src/modules/punk/args-999999.0a1.0.tm (get_dict value allocation, private::get_dict_can_assign_value, parse_status), src/tests/modules/punk/args/testsuites/args/ (new allocation characterization suite), src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm (::lseq as the proving consumer)
Goal: argument lists that are valid for a single form parse correctly when optional standalone values and optional-member clauses are skipped or filled in any documented combination - specifically the lseq range shape (an optional single-word choice value between required values, followed by a trailing optional-member clause), where today the allocator force-feeds a word to the skippable optional and fails ('lseq 0 10 2', '0 10 by 2', '1 5 by 0' all fail in-form while '0 to 10 2' parses) - and parse failures blame the genuinely failing element rather than an unrelated optional; parse_status gains -form so per-form status probing works.
Acceptance: a new allocation characterization suite drives the lseq range matrix under explicit -form range - '0 10', '0 10 2', '0 10 by 2', '0 to 10 2', '0 .. 10 by 2', '1 5 by 0' parse per the lseq.n grammar (the three currently-failing cases fixed) and '0 10 2 4' still fails - plus reduced fixtures isolating the shape (optional choice value between required values + trailing optional-member clause) independent of the moduledoc; the ::if noise-word cases (mid-clause ?literal(then)?, clause-leading ?literal(else)?) keep passing - no regression to optional clause members generally; a genuinely invalid arglist's error names the failing element (the '..|to' misblame case pinned fixed); punk::args::parse_status accepts -form (single form name/index at minimum, consistent with parse) with its status structure reporting the form used; full punk::args and punk::ns suites pass with no expectations weakened; before/after results for the probe matrix recorded in this file.
@ -68,13 +68,56 @@ needs it first.
- Verified (tclsh 9.0.3): full punk::args suite 193 pass / 1 pre-existing skip /
0 fail.
Remaining for acceptance:
- allocator fix: optional-element rejection yields the word to subsequent
elements/clauses when a consistent allocation exists (the three GAPs flip)
- blame quality: misblame GAP flips to naming the genuinely failing element
- parse_status -form support (its GAP flips)
- lseq moduledoc matrix verified under -form range post-fix; before/after
results recorded here; punk::ns suite regression pass
### 2026-07-12 increment 2: allocation choice screen (punk::args 0.9.0) - acceptance complete
- Root cause: get_dict_can_assign_value blanket-satisfied the member check for any
argument with choices ("each tp in the clause is just for validating a value
outside the choice-list when -choicerestricted 0" - applied to restricted sets
too), so an optional choice value consumed any word whenever arity permitted.
Arity always permitted here because (a) the tail-reservation scan cannot see the
by-step clause (its lsearch for literal* misses the optional-wrapped
?literalprefix(by)?), and (b) tail_needs counts only required tail elements.
- Fix: a choiceword_match allocation screen (the shared G-040 implementation, so
allocation acceptance cannot diverge from parse acceptance - exact/alias/prefix/
nocase; -choicemultiple words screened per list member within min/max), applied
ONLY where allocation has an alternative: -optional arguments and further
occurrences of -multiple arguments. Two refinements forced by the existing
suites during implementation, both kept as design decisions:
- REQUIRED arguments are not screened - the word must fill them regardless, and
screening only masked validation's informative choiceviolation as a
missingrequired* (caught by parsestatus.test/cmdhelp.test pins).
- -choicemultiple list-valued words are screened member-wise (caught by
choices.test choicemultiple pins).
The screen compares raw words (no ansistrip): an ansi-wrapped choice word on an
optional argument would be skipped where validation would accept it - accepted
edge, recorded in the code comment.
- Correction of an increment-1 mis-premise: parse_status ALREADY accepts -form -
positioned before the withid/withdef tail per its documented synopsis; the
probing failure was a trailing -form (argument-order mistake). The GAP was
replaced by parsestatus_form_option_order pinning correct-order acceptance
(with the status structure's form key) and trailing-order rejection. The
acceptance clause "parse_status accepts -form ... reporting the form used" was
therefore already satisfied; verified with a multiform fixture (form=beta
reported). The Goal line's "parse_status gains -form" framing was inaccurate -
no code change was needed or made for it.
- Before/after (reduced fixture, tclsh 9.0.3): '1 2 3' {0 invalid sep} ->
{1 valid {start end {by step}}} with clause {{} 3}; '1 2 by 3'
{0 incomplete end} -> {1 valid ...} with clause {by 3}; '1 2 3 4'
{0 invalid sep} -> {0 invalid {}} (same excess-values rejection as the
noise-word twin); prefix semantics preserved ('1 2 b 3' -> clause {by 3},
'1 t 2 3' -> sep normalized to 'to'); all noise-word variants and all if-shape
guards unchanged.
- lseq moduledoc matrix (punk902z src, Tcl 9.0.2): explicit -form range now
parses '0 10 2', '0 10 by 2', '1 5 by 0' per lseq.n (previously in-form
failures); count and start_count forms unchanged; default-form parsing of
'0 10 2' now valid end-to-end (the original 'i lseq 0 10 2' complaint).
Remaining real-vs-model divergences all trace to G-041 form selection or the
-type expr non-validation recorded in G-055's notes - not allocation.
- Verified (tclsh 9.0.3): punk::args suite 193 pass / 1 pre-existing skip / 0
fail (allocation GAPs flipped; the two required-choice-arg pins and the
choicemultiple pins unweakened); punk::ns suite 53/53; full source-tree suite
827 total, 813 pass, 13 skip, 1 fail = exec-14.3 only (known pre-existing
core-test baseline) - zero regressions; make.tcl modules builds clean.
## Notes

10
goals/G-072-punkargs-compound-clause-types.md

@ -56,10 +56,12 @@ mechanism. Brief examination at drafting time:
- Related: G-041 (form selection and clause allocation interact; its candidacy
machinery should not need changes from this goal's types), G-071 (allocation
correctness for optional elements - sequence that first: compound alternates
ride on the same allocator), G-055 (modelability findings list; its parity
workflow verifies whatever this goal makes expressible), G-053 (occurrence
arity - adjacent clause machinery).
correctness for optional elements, achieved 2026-07-12 - see
goals/archive/G-071-punkargs-optional-allocation.md; its choiceword_match
allocation screen and allocation.test fixtures are the base this goal's
alternates ride on), G-055 (modelability findings list; its parity workflow
verifies whatever this goal makes expressible), G-053 (occurrence arity -
adjacent clause machinery).
- Display cost matters: the ::try notes warn bracketed alternate forms "get
unwieldy in synopsis listings" - synopsis rendering of compound types is part
of the mechanism's acceptance, not an afterthought.

2
punkproject.toml

@ -1,4 +1,4 @@
[project]
name = "punkshell"
version = "0.12.10"
version = "0.12.11"
license = "BSD-2-Clause"

91
src/modules/punk/args-999999.0a1.0.tm

@ -7184,6 +7184,49 @@ tcl::namespace::eval punk::args {
set tpidx 0
set newtypelist $thistype
set has_choices [expr {[tcl::dict::exists $ARG_INFO $thisname -choices] || [tcl::dict::exists $ARG_INFO $thisname -choicegroups]}]
set choicescreen_applies 0
if {$has_choices} {
#G-071: with a restricted choice set (the default) allocation SCREENS the
#candidate word against the choices rather than blanket-accepting - but
#ONLY where allocation has an alternative: optional arguments (and extra
#occurrences of -multiple arguments). Previously any word was consumed
#here whenever arity permitted, so an optional choice value (e.g the lseq
#'..'/'to' noise word) greedily took a word that belonged to later
#elements/clauses and the parse failed with a confusing trailing-choices
#error ('lseq 0 10 2' shape). The screen uses choiceword_match - the
#shared G-040 implementation - so allocation acceptance cannot diverge
#from parse acceptance (exact/alias/prefix/nocase semantics included);
#-choicemultiple words are screened per list member. For a REQUIRED
#argument the word must fill it regardless - rejecting here would only
#mask final validation's informative choiceviolation as a
#missingrequired* error, so required arguments are not screened.
#Final validation remains authoritative; the screen compares raw words
#(no ansistrip) - an ansi-wrapped choice word on an optional argument
#would be skipped here where validation would accept it (accepted edge -
#allocation errs toward yielding words onward). With -choicerestricted 0
#any word remains allocatable (each tp in the clause then only validates
#values outside the choice-list).
if {[Dict_getdef $ARG_INFO $thisname -choicerestricted 1]
&& ([tcl::dict::get $ARG_INFO $thisname -optional]
|| ([tcl::dict::get $ARG_INFO $thisname -multiple] && $thisname in $namesreceived))} {
set choicescreen_applies 1
set cw_allchoices [list]
if {[tcl::dict::exists $ARG_INFO $thisname -choices]} {
set cw_allchoices [tcl::dict::get $ARG_INFO $thisname -choices]
}
if {[tcl::dict::exists $ARG_INFO $thisname -choicegroups]} {
tcl::dict::for {_grp grpmembers} [tcl::dict::get $ARG_INFO $thisname -choicegroups] {
lappend cw_allchoices {*}$grpmembers
}
}
set cw_nocase [Dict_getdef $ARG_INFO $thisname -nocase 0]
set cw_prefix [Dict_getdef $ARG_INFO $thisname -choiceprefix 1]
set cw_aliases [Dict_getdef $ARG_INFO $thisname -choicealiases {}]
set cw_deny [Dict_getdef $ARG_INFO $thisname -choiceprefixdenylist {}]
set cw_reserve [Dict_getdef $ARG_INFO $thisname -choiceprefixreservelist {}]
lassign [Dict_getdef $ARG_INFO $thisname -choicemultiple {1 1}] cw_cmmin cw_cmmax
}
}
foreach tp $thistype {
#usual case is a single tp (basic length-1 clause) - but tp may commonly have alternates eg int|literal(xxx)
set v [lindex $all_remaining $alloc_count]
@ -7195,21 +7238,53 @@ tcl::namespace::eval punk::args {
set tp [string trim $tp ?]
set member_satisfied 0
set member_choicechecked 0
if {$has_choices} {
#each tp in the clause is just for validating a value outside the choice-list when -choicerestricted 0
set member_satisfied 1
if {$choicescreen_applies} {
#G-071 allocation screen (see block above the loop)
set member_choicechecked 1
if {$cw_cmmax == 1} {
set cwm [choiceword_match $v $cw_nocase $cw_allchoices $cw_aliases $cw_prefix $cw_deny $cw_reserve]
set member_satisfied [tcl::dict::get $cwm matched]
} else {
#-choicemultiple: the word is itself a list of choices - screen
#each member; count within the declared min/max (max -1 = no limit)
if {[catch {llength $v} v_len]} {
set member_satisfied 0
} elseif {$v_len < $cw_cmmin || ($cw_cmmax > 0 && $v_len > $cw_cmmax)} {
set member_satisfied 0
} else {
set member_satisfied 1
foreach v_member $v {
set cwm [choiceword_match $v_member $cw_nocase $cw_allchoices $cw_aliases $cw_prefix $cw_deny $cw_reserve]
if {![tcl::dict::get $cwm matched]} {
set member_satisfied 0
break
}
}
}
}
} else {
#required argument with restricted choices (word must fill it -
#validation reports choice violations), or -choicerestricted 0
#(each tp in the clause only validates values outside the
#choice-list)
set member_satisfied 1
}
}
if {!$member_satisfied} {
#category lists must exist even when the choice screen decided the member
#(the !member_satisfied blocks below consult their lengths)
set ctg_literals [list]
set ctg_literalprefixes [list]
set ctg_stringstartswith [list]
set ctg_stringendswith [list]
set ctg_other [list]
if {!$member_satisfied && !$member_choicechecked} {
#-----------------------------------------------------------------------------------
#first build category lists of any literal,literalprefix,stringstartwith,other
#
set ctg_literals [list]
set ctg_literalprefixes [list]
set ctg_stringstartswith [list]
set ctg_stringendswith [list]
set ctg_other [list]
#foreach tp_alternative [split $tp |] {}
foreach tp_alternative [private::split_type_expression $tp] {
#JJJJ

3
src/modules/punk/args-buildversion.txt

@ -1,6 +1,7 @@
0.8.3
0.9.0
#First line must be a semantic version number
#all other lines are ignored.
#0.9.0 - G-071: allocation choice screen - get_dict_can_assign_value no longer blanket-accepts any word for an argument with a RESTRICTED choice set; where allocation has an alternative (the argument is -optional, or a further occurrence of a -multiple argument) the candidate word is screened with choiceword_match (the shared G-040 implementation: exact/alias/prefix/nocase semantics; -choicemultiple words screened per list member within min/max), and a non-matching word yields onward to later elements/clauses instead of being consumed and failing validation. Fixes the lseq-class in-form failures: an optional choice noise word between required values plus a trailing optional-member clause ('lseq 0 10 2', '0 10 by 2', '1 5 by 0' now parse; noise-word variants and prefix normalization unchanged; genuinely invalid arglists now get the plain excess-values rejection instead of blaming the unrelated optional). REQUIRED arguments are deliberately not screened - the word must fill them and validation's choiceviolation reporting stays informative. -choicerestricted 0 behaviour unchanged. allocation.test GAPs flipped; parse_status -form documented-order behaviour pinned (a prior mis-premise: -form was already supported before the withid/withdef tail per its synopsis).
#0.8.3 - G-071: silenced four more unconditional debug puts on get_dict's allocation retreat paths ("get_dict cannot assign val:... (111)/(222)/(333)/(444)") - these fired on stderr during NORMAL successful parses whenever an optional element's candidate word was rejected and allocation retreated (e.g. every noise-word skip when parsing if/lseq-modelled commands), polluting interactive output. Same class as the 0.2.3/0.8.2 finds. The retreat logic they marked is the target of the G-071 allocator-correctness work; behaviour unchanged, output only. New allocation.test characterization suite (reduced lseq-range/if-shape fixtures): current mis-allocation and misblame pinned as GAP tests (flip with G-071), noise-word variants and if-shape guards pinned as regression guards, parse_status -form absence pinned as GAP.
#0.8.2 - fixed stray debug output: an unconditional 'puts' in private::get_dict_can_assign_value ("checking tp '<type>' against value '<value>'") fired on multi-element clause type checks (e.g. parsing 'try ... trap ... on ...' style arglists against the tclcore moduledoc), polluting interactive output - now commented like its 0.2.3 companion. Found during G-041 prework probing (real-vs-model divergence sweep of if/switch/try/lseq).
#0.8.1 - G-045 documentation-only: the container quoting rules pinned by defquoting.test promoted into the define -help documentation (-help key section): braced values fully literal (backslash sequences survive as typed) with only tstr placeholders and their backslash escape special; double-quoted values get Tcl backslash semantics at record parse (\n -> newline, doubled backslash collapses) while $ and [] stay literal with no variable/command substitution outside tstr placeholders; the backslash-escaped placeholder idiom renders a literal placeholder.

66
src/tests/modules/punk/args/testsuites/args/allocation.test

@ -9,12 +9,14 @@ package require punk::args
#clauses) from the tclcore moduledoc, so the pins do not depend on moduledoc
#availability or vintage.
#
#GAP-labelled tests pin the CURRENT broken allocation and flip when G-071 lands:
#with the optional 'sep' noise word absent, the allocator force-assigns the word
#after start to 'sep' (whose -choices then reject it) instead of yielding it to
#later elements/clauses - so '1 2 3' and '1 2 by 3' fail in-form while every
#variant carrying the noise word parses. '1 2 3 4' is genuinely invalid but the
#blame lands on 'sep' rather than the failing clause (misblame pin).
#History: added with GAP pins encoding the pre-G-071 broken allocation (the
#allocator blanket-accepted any word for a restricted-choice value at allocation
#time, so the optional 'sep' noise word greedily consumed the word after start
#and '1 2 3' / '1 2 by 3' failed in-form with a trailing-choices error).
#G-071 added a choiceword_match allocation screen for restricted choice sets
#(punk::args 0.9.0): a non-choice word is no longer allocatable to 'sep', so it
#yields onward to end and the by-step clause - the GAPs below flipped to the
#correct outcomes the same day they were added.
namespace eval ::testspace {
namespace import ::tcltest::*
@ -70,31 +72,41 @@ namespace eval ::testspace {
{1 valid {start sep end {by step}}}\
]
#added 2026-07-12 (agent, G-071)
test allocation_range_optskip_bare_step_GAP {GAP: sep absent + bare step ('1 2 3') fails - word 2 force-fed to the skippable optional 'sep' instead of yielding to end + clause}\
#added 2026-07-12 (agent, G-071) - was allocation_range_optskip_bare_step_GAP
#(pinned {0 invalid sep}); flipped by the G-071 allocation choice screen
test allocation_range_optskip_bare_step {sep absent + bare step ('1 2 3'): non-choice word yields past the optional 'sep' to end + clause}\
-setup $common -body {
pstat {1 2 3} ::testspace::alloc_range
lappend result [pstat {1 2 3} ::testspace::alloc_range]
#the clause's optional literalprefix member is omitted (empty)
lappend result [dict get [dict get [punk::args::parse {1 2 3} withid ::testspace::alloc_range] values] {by step}]
}\
-cleanup $cleanup\
-result {0 invalid sep}
-result [list {1 valid {start end {by step}}} {{} 3}]
#added 2026-07-12 (agent, G-071)
test allocation_range_optskip_by_clause_GAP {GAP: sep absent + by-clause ('1 2 by 3') fails as incomplete blaming 'end'}\
#added 2026-07-12 (agent, G-071) - was allocation_range_optskip_by_clause_GAP
#(pinned {0 incomplete end}); flipped by the G-071 allocation choice screen
test allocation_range_optskip_by_clause {sep absent + by-clause ('1 2 by 3'): allocation yields correctly and the clause takes both members}\
-setup $common -body {
pstat {1 2 by 3} ::testspace::alloc_range
lappend result [pstat {1 2 by 3} ::testspace::alloc_range]
lappend result [dict get [dict get [punk::args::parse {1 2 by 3} withid ::testspace::alloc_range] values] {by step}]
#prefix semantics survive the allocation screen: 'b' prefixes the clause
#literal, 't' prefixes the sep choice (normalized to 'to')
lappend result [dict get [dict get [punk::args::parse {1 2 b 3} withid ::testspace::alloc_range] values] {by step}]
lappend result [dict get [dict get [punk::args::parse {1 t 2 3} withid ::testspace::alloc_range] values] sep]
}\
-cleanup $cleanup\
-result {0 incomplete end}
-result [list {1 valid {start end {by step}}} {by 3} {by 3} to]
#added 2026-07-12 (agent, G-071) - '1 2 3 4' must STAY invalid when the
#allocator is fixed (per lseq.n no form accepts it) but the blame should move
#off the unrelated optional 'sep' to the genuinely failing element
test allocation_range_excess_invalid_misblame_GAP {GAP: genuinely invalid '1 2 3 4' is rejected (correct) but blames the unrelated optional 'sep' (misblame)}\
#added 2026-07-12 (agent, G-071) - was allocation_range_excess_invalid_misblame_GAP
#(pinned {0 invalid sep}); the arglist stays invalid (per lseq.n no form accepts
#it) but the blame no longer lands on the unrelated optional 'sep' - it is now
#the same excess-values rejection as the noise-word twin '1 to 2 3 4'
test allocation_range_excess_invalid {genuinely invalid '1 2 3 4' is rejected as excess values without blaming 'sep'}\
-setup $common -body {
pstat {1 2 3 4} ::testspace::alloc_range
}\
-cleanup $cleanup\
-result {0 invalid sep}
-result {0 invalid {}}
#added 2026-07-12 (agent, G-071) - guard: with the noise word present the
#equivalent excess arglist is rejected without touching 'sep'
@ -130,14 +142,20 @@ namespace eval ::testspace {
-cleanup $cleanup\
-result [list {1 valid} {1 valid} {1 valid} {1 valid} {1 valid} {1 valid} {1 valid} {0 invalid}]
#added 2026-07-12 (agent, G-071)
test parsestatus_form_option_GAP {GAP: parse_status does not accept -form - per-form status probing raises instead of parsing}\
#added 2026-07-12 (agent, G-071) - characterization correcting an initial
#mis-premise: parse_status DOES accept -form, positioned per its documented
#synopsis (options BEFORE the withid/withdef tail); a trailing -form is
#rejected like any misplaced option
test parsestatus_form_option_order {parse_status -form works before the withid tail and is rejected trailing}\
-setup $common -body {
set rc [catch {punk::args::parse_status {1 to 2} withid ::testspace::alloc_range -form 0} r]
list $rc [string match "Bad number of values*" $r]
set st [punk::args::parse_status {1 to 2} -form 0 withid ::testspace::alloc_range]
lappend result [list [dict get $st ok] [dict get $st status]]
#the status structure reports the form used
lappend result [dict get $st form]
lappend result [catch {punk::args::parse_status {1 to 2} withid ::testspace::alloc_range -form 0}]
}\
-cleanup $cleanup\
-result {1 1}
-result [list {1 valid} _default 1]
cleanupTests
}

Loading…
Cancel
Save