Browse Source
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.commaster
3 changed files with 127 additions and 0 deletions
@ -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). |
||||||
@ -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…
Reference in new issue