Browse Source

goals: add G-071 (optional-element allocation correctness) and G-072 (compound clause types)

Both user-approved drafts from the G-041 prework probe findings:

- G-071: value-allocation correctness for optional standalone values +
  trailing optional-member clauses (the lseq "0 10 2" in-form failure),
  error-blame quality, and parse_status -form support. A G-041
  prerequisite: no lseq form accepts those arglists under the current
  allocator, so form auto-selection alone cannot fix them.
- G-072: named compound clause types with per-alternate element typing
  and arity for heterogeneous repeating clauses (try on/trap handlers),
  building on the design sketches embedded in the ::try moduledoc notes.
  Includes a recorded examination of Elixir gradual set-theoretic types
  (user direction): punk::args -type | and literal() are already a
  fragment of the set algebra; negation would subsume reserved-word
  deny-lists; per-alternate arity falls out of union-of-shapes.

Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 2 days ago
parent
commit
803b44bd6c
  1. 8
      GOALS.md
  2. 54
      goals/G-071-punkargs-optional-allocation.md
  3. 65
      goals/G-072-punkargs-compound-clause-types.md

8
GOALS.md

@ -285,3 +285,11 @@ Detail: goals/G-069-splitter-tclparser-lint.md
### G-070 [proposed] Pure-Tcl tclparser: parse-command API fallback with behavioural parity against the C library
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 [proposed] 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

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

@ -0,0 +1,54 @@
# G-071 punk::args value-allocation correctness for optional elements (lseq-class arglists) + parse_status -form
Status: proposed
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.
## Context
Found 2026-07-12 during G-041 activation prework (user concern: variable-length
clauses with optional elements). Real-vs-model probing of the tclcore moduledoc
showed the per-form models are doc-faithful (lseq.n's three synopsis lines map 1:1
to the modelled range/start_count/count forms; if/switch/try likewise verified),
but the range form fails IN-FORM whenever the ../to noise word is absent and a
step is present - the allocator insists on assigning the word after start to the
optional '..|to' choice value (whose choices then reject it) instead of skipping
the optional and letting the trailing "by step" clause (-type
{?literalprefix(by)? number|expr}) absorb the remainder. The start_count form
proves the optional-member clause itself can absorb a lone word ('1 count 5 2'
parses as {by step} {{} 2}) when preceding words are pinned by a literal - the
failure is the combination with a skippable standalone optional value.
No other lseq form accepts '0 10 2', so G-041's form auto-selection cannot fix
'i lseq 0 10 2' without this - making this goal a G-041 prerequisite. Probe
results are recorded in goals/G-041-punkargs-form-matching.md Notes (2026-07-12).
parse_status -form: per-form probing during the same session had to fall back to
punk::args::parse + catch because parse_status does not accept -form. The
form-candidacy API sketched in G-041 (and the G-044 partial-arglist consumer)
wants exactly this surface, so it lands here where the characterization suite
needs it first.
## Approach
- Characterize first: reduced fixtures pinning current allocation behaviour for
the failing shape (and the working ::if shapes as regression guards), then fix
the allocator - likely in the lookahead/backtrack decision where
get_dict_can_assign_value rejects an optional's candidate word: rejection of an
optional element should yield the word to subsequent elements/clauses rather
than failing the parse when a consistent allocation exists.
- Blame quality: when no consistent allocation exists, prefer reporting the
element whose constraint actually failed over the first optional tried.
- The G-041 detail file notes the form-attempt logic will exercise these paths
heavily; landing this first keeps that goal's failures meaningful.
## Notes
- Related: G-041 (this is its prerequisite; probe evidence in its Notes), G-053
(occurrence-arity work touches adjacent allocation machinery), G-055 (lseq
parity pins; TIP 746 note there makes lseq operand types version-conditional -
this goal's matrix should derive expectations from the live interpreter).
- Incidental fixes already landed during the probing session: unconditional debug
puts on the clause type-check path (punk::args 0.8.2), and an earlier companion
(0.2.3, noted in G-041).

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

@ -0,0 +1,65 @@
# G-072 punk::args compound clause types: named alternates with per-element typing and per-alternate arity (try-class handlers)
Status: proposed
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/
Goal: heterogeneous repeating clauses - try's on/trap handlers are the motivating case - can be modelled with per-alternate element types, choices and arity: 'try {} on bogus {e} {s}' is rejected with the code choice set displayed, alternates may differ in element count, mixed-order interleaving keeps parsing, and synopsis/help render each alternate's shape distinctly instead of a conflated element - with the definition mechanism (named compound types, composition operators, cross-package scoping) decided and recorded, informed among others by set-theoretic type composition.
Acceptance: a definition mechanism (decided in the work, starting from the sketches embedded in the ::try moduledoc notes: named compound types with arity, @typeimport, composition operators) lets the ::try handler be modelled so that parse rejects 'on bogus ...' displaying the on-code choices (ok/error/return/break/continue or integer) and types the trap pattern as a list, while mixed-order on/trap interleaving and finally positioning keep parsing (the 2026-07-12 probe matrix stays green); alternates of differing arity are expressible and exercised by at least one fixture (per-alternate arity derived or declared - decision recorded); synopsis and help output render each alternate's element shape distinctly (the deliberately-unpiped oncode_or_trappattern -typesynopsis workaround retired); type-name scoping across documentation packages has a recorded decision (namespacing/@typeimport mechanism, or rejection with rationale); the ::try moduledoc adopts the mechanism with real-vs-model parity probes added (::if/::switch adopt it only where it improves their models); definitions not using compound types behave unchanged (full punk::args suite passes, none weakened).
## Context
The ::try moduledoc models handlers as a single 4-element clause
{literal(on)|literal(trap) string list string} -multiple 1. Probing (2026-07-12,
G-041 prework) confirmed this parses everything structural correctly - mixed
on/trap order, finally positioning - but over-accepts semantically: the second
element cannot be typed per-alternate, so 'on bogus {e} {s}' passes where Tcl
errors with the valid completion codes. The moduledoc carries extensive embedded
design notes at ::try recording the difficulty and sketching mechanisms (named
compound types with arity indicators such as <on_handler:4>|<other_handler:3>,
per-definition vs global type registries, @typeimport for cross-package reuse,
RPN composition like {stringstartswith a stringendswith z AND int OR}).
Related over-acceptance findings from the same probes are listed in
goals/G-055-tclcore-regen-workflow.md Notes (reserved-word clause allocation,
'-' fallthrough position constraints) - candidates to fall out of the same
mechanism or be recorded as out of scope here.
## Set-theoretic types input (user direction 2026-07-12)
Investigate whether gradual set-theoretic types as adopted by Elixir
(https://elixir.hexdocs.pm/main/gradual-set-theoretic-types.html) can inform the
mechanism. Brief examination at drafting time:
- Elixir treats types as SETS of values composed with union (or), intersection
(and) and negation (not); literal values are singleton types; dynamic() effects
gradual typing; multi-clause functions are intersections of arrow types.
- Mapping to punk::args: -type's existing | is already set union and
literal(word) is already a singleton type - the existing vocabulary is a
fragment of a set-theoretic algebra, so extension is consistent rather than a
redesign. punk::args' 'any' plays the dynamic() role.
- Negation would directly express the reserved-word gap recorded in G-055's
notes: an if else-script is "script and not literal(elseif)" - i.e. deny-lists
for clause members become type negation instead of a bespoke feature.
- Intersection covers the composite constraints the ::try notes sketched as RPN:
{stringstartswith a stringendswith z AND int OR} reads as
((startswith(a) and endswith(z)) or int) - infix/functional composition on set
semantics can replace the RPN idea.
- Named compound types then become type ALIASES over such compositions, and a
heterogeneous clause is a UNION of tuple-like clause shapes whose arity derives
from each shape's length - answering the notes' open question about declaring
arity for alternates of differing length (arity is per-alternate and computed,
not separately declared).
- A full algebra is likely overkill for the immediate try case; the design should
pick operator spellings consistent with set-theoretic composition so later
extension (negation for reserved words, intersection for composite string
constraints) slots in without breaking earlier definitions.
## Notes
- 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).
- 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.
Loading…
Cancel
Save