diff --git a/CHANGELOG.md b/CHANGELOG.md index a71b87a9..9d5ffa7a 100644 --- a/CHANGELOG.md +++ b/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.18] - 2026-07-13 + +- tclcore moduledoc 0.3.1 (authoring-style only): the after id-shape harvest is consumed via tstr placeholders with the variable in the argdoc namespace (the defspace), replacing the interim string-map token - the module now showcases the neater placeholder style documented in punk::args 0.11.1, with string map reserved for when build-time substitution is genuinely necessary. Behaviour unchanged. + ## [0.12.17] - 2026-07-13 - punk::args 0.11.1 (documentation-only): the define help now documents the two definition-registration styles (direct define vs lazy PUNKARGS/register::NAMESPACES registration used by module templates and moduledocs) and the tstr interpolation rules - display-field deferral, parse-field expansion at first resolve, the defspace rule (argdoc child namespace wins when present), the silent-literal fallback for unresolvable placeholders, and the safe patterns for load-time-computed values. diff --git a/goals/G-055-tclcore-regen-workflow.md b/goals/G-055-tclcore-regen-workflow.md index 5c088c37..f17a3e85 100644 --- a/goals/G-055-tclcore-regen-workflow.md +++ b/goals/G-055-tclcore-regen-workflow.md @@ -157,9 +157,11 @@ other commands including the multi-form ::after. rule); a variable set in the parent is unresolvable there and the param is left SILENTLY literal (diagnostic gap - a literal ${...} reaching a parse field is always an authoring error and could warn). Load-time-computed - values are safest via build-time substitution (explicit tstr wrap as - ::tcl::string::is, or a string-map token as ::after) or by setting the - variable in the argdoc namespace. Parity pins in tclcoreparity.test + values work via plain tstr placeholders with the variable set in the + argdoc namespace (the ::after harvest showcases this since tclcore 0.3.1, + user-preferred style), or via build-time substitution (explicit tstr wrap + as ::tcl::string::is; string-map token reserved for when build-time + substitution is genuinely necessary). Parity pins in tclcoreparity.test including the accepted dead-id divergence. - TIP 746 (user-flagged 2026-07-12, https://core.tcl-lang.org/tips/doc/trunk/tip/746.md) removes the expr diff --git a/punkproject.toml b/punkproject.toml index d583ccab..18e4dbe7 100644 --- a/punkproject.toml +++ b/punkproject.toml @@ -1,4 +1,4 @@ [project] name = "punkshell" -version = "0.12.17" +version = "0.12.18" license = "BSD-2-Clause" diff --git a/src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm b/src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm index b1ace07b..5fd219b4 100644 --- a/src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm +++ b/src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm @@ -4535,23 +4535,24 @@ tcl::namespace::eval punk::args::moduledoc::tclcore { #accept/reject parity follows the interpreter these docs load into. #The probe is safe: create + immediately cancel - no event loop entry, no output, #no lasting state. - set after_id_prefix "after#" - if {![catch {after 999999 {}} _aip_id]} { - catch {after cancel $_aip_id} - if {[regexp {^(.+#)\d+$} $_aip_id -> _aip_prefix]} { - set after_id_prefix $_aip_prefix + #The harvested prefix is consumed via tstr ${$after_id_prefix} placeholders in the + #::after definition below. The variable MUST live in the argdoc namespace: that is + #the DEFSPACE registered PUNKARGS definitions resolve their placeholders in + #whenever an argdoc child exists - even when the PUNKARGS list itself is in the + #parent, as here - and an unresolvable param is left silently literal (see the + #'Interpolation and the defspace' section of the punk::args::define help). + namespace eval argdoc { + set after_id_prefix "after#" + if {![catch {after 999999 {}} _aip_id]} { + catch {after cancel $_aip_id} + if {[regexp {^(.+#)\d+$} $_aip_id -> _aip_prefix]} { + set after_id_prefix $_aip_prefix + } } + unset -nocomplain _aip_id _aip_prefix } - #the %AFTERIDPREFIX% token below is substituted at build time (string map; the - #%TYPECHOICES% pattern in punk::args' own argdocs is the precedent). A tstr - #${$after_id_prefix} in the -type field would also work, BUT only if the variable - #lives in the DEFSPACE the definition resolves in - for registered PUNKARGS - #definitions that is the argdoc subnamespace whenever one exists (even when the - #PUNKARGS list itself is in the parent, as here), and an unresolvable param is - #left silently literal. Build-time substitution avoids the defspace subtlety - #entirely for load-time-computed values like this harvest. # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- - lappend PUNKARGS [list [string map [list %AFTERIDPREFIX% $after_id_prefix] { + lappend PUNKARGS [list { #test of @form @id -id ::after @cmd -name "Built-in: after"\ @@ -4599,10 +4600,10 @@ tcl::namespace::eval punk::args::moduledoc::tclcore { #An id-SHAPED word remains genuinely ambiguous with a script of the same text - #real Tcl resolves that by id liveness at runtime (tries the id first, falls #back to script match), which no static type expresses (goals/G-055 record). - id -type stringstartswith(%AFTERIDPREFIX%) -typesynopsis id -help\ + id -type stringstartswith(${$after_id_prefix}) -typesynopsis id -help\ "Identifier of the delayed command to cancel. It must have been the return value from a previous - after command (%AFTERIDPREFIX%N shaped)." + after command (${$after_id_prefix}N shaped)." #@form -form {cancelscript} -synopsis "after cancel script ?script...?" @@ -4627,13 +4628,13 @@ tcl::namespace::eval punk::args::moduledoc::tclcore { @values -min 0 -max 1 #real 'after info ' errors at runtime ("event ... doesn't #exist") - the model's shape rejection keeps error-vs-ok parity - id -optional 1 -type stringstartswith(%AFTERIDPREFIX%) -typesynopsis id -help\ + id -optional 1 -type stringstartswith(${$after_id_prefix}) -typesynopsis id -help\ "Identifier of an existing event handler - the return value from some previous call to after - (%AFTERIDPREFIX%N shaped). It must not have triggered + (${$after_id_prefix}N shaped). It must not have triggered yet or been canceled." - }] "@doc -name Manpage: -url [manpage_tcl after]"\ + } "@doc -name Manpage: -url [manpage_tcl after]"\ { @examples -help { This defines a command to make Tcl do nothing at all for N seconds: diff --git a/src/modules/punk/args/moduledoc/tclcore-buildversion.txt b/src/modules/punk/args/moduledoc/tclcore-buildversion.txt index afdec034..a8679934 100644 --- a/src/modules/punk/args/moduledoc/tclcore-buildversion.txt +++ b/src/modules/punk/args/moduledoc/tclcore-buildversion.txt @@ -1,5 +1,6 @@ -0.3.0 +0.3.1 #First line must be a semantic version number #all other lines are ignored. +#0.3.1 - authoring-style only (user-directed): the ::after id-shape harvest is consumed via tstr ${$after_id_prefix} placeholders instead of the 0.3.0 build-time %AFTERIDPREFIX% string map - the harvest variable now lives in the argdoc namespace (the defspace registered PUNKARGS definitions resolve placeholders in when an argdoc child exists; see the punk::args::define 'Interpolation and the defspace' help section), which makes plain placeholders work in both the -type parse field (expanded at first resolve) and the -help display fields (expanded at display time). This module showcases the tstr style; string map remains reserved for cases where build-time substitution is genuinely necessary. Behaviour identical to 0.3.0 (resolved -type, form discrimination, parity pins and help renders re-verified). #0.3.0 - ::after cancel-id discrimination (user-directed 2026-07-13): the cancelid and info forms' id argument is typed stringstartswith() with the prefix harvested from the RUNNING interpreter at define time (safe create+cancel probe 'after 999999 {}' / 'after cancel $id' - G-054 technique; prefix is after# on 8.6.11 and 9.0.3, hardcoded after#%d in tclTimer.c), substituted into the definition via a build-time %AFTERIDPREFIX% string map (CORRECTED finding: parse-field tstr IS expanded for registered PUNKARGS definitions, but in the argdoc subnamespace when one exists - a variable set in the parent namespace is unresolvable there and the param is left silently literal; build-time substitution sidesteps the defspace subtlety). -typesynopsis id keeps the synopsis rendering as the man page's 'id'. Effect under G-041 form candidacy: 'after cancel ' resolves cleanly to the cancelscript form matching real semantics (real 'after cancel' with a non-id is a silent script-match no-op), and 'after info ' is model-rejected where real errors at runtime (parity-true); an id-SHAPED word after cancel remains truthfully ambiguous (cancelid+cancelscript) - real Tcl resolves that junction by id liveness at runtime, which no static type expresses; dead-id over-acceptance on 'after info' recorded as the accepted runtime-liveness boundary. Both ids gain man-page-derived -help text. Parity pins added in tclcoreparity.test (id-shape harvest agreement, cancel discrimination incl the liveness ambiguity, info error-vs-ok parity + accepted dead-id divergence). #0.2.0 - G-054: 'string is' class choices (and the generated per-class virtual docids) are harvested from the RUNNING interpreter at define time instead of a hand-maintained list - a deliberately invalid probe of the builtin yields the authoritative class set from its error message (8.6: 21 classes, no dict; unreleased 8.7: +dict +unicode; 9.0: +dict, unicode removed), fixing accept/reject drift such as the doc wrongly accepting 'string is dict' under 8.6. Hand-written man-page descriptions apply only to classes the runtime accepts (generic label for unrecognized future classes); static version notes on dict (not in 8.6) and unicode (unreleased 8.7 only). Parity pinned by tclcoreparity.test with expectations derived from the live interpreter (green on 8.6.13, 8.7a6, 9.0.3)