diff --git a/GOALS.md b/GOALS.md index 66e29f87..31c974bb 100644 --- a/GOALS.md +++ b/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 diff --git a/goals/G-071-punkargs-optional-allocation.md b/goals/G-071-punkargs-optional-allocation.md new file mode 100644 index 00000000..fcea6397 --- /dev/null +++ b/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). diff --git a/goals/G-072-punkargs-compound-clause-types.md b/goals/G-072-punkargs-compound-clause-types.md new file mode 100644 index 00000000..4709dd02 --- /dev/null +++ b/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 |, +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.