Browse Source

Tcl 9.1 compat: punk::lib 0.3.1, punk::args 0.3.2 - lseq TIP 746, safe-interp disassemble; project 0.4.12

TIP 746 (Tcl 9.1) removed lseq's expr-operand behaviour. punk::lib::range
(lseq branch) now normalizes int[+-]int offsets via offset_expr so callers
like `range 0 [llength $list]-1` keep working (punk::ansi::grepstr broke
under 9.1b0, taking example-block highlighting and the punk::args
examples.test with it). The lseq branch is also aligned with the tcl8
fallback contract: default 'by' now infers direction (descending ranges
previously returned empty under tcl9) and 'by 0' returns empty (Tcl 9.1
lseq changed by-0 to return one element).
Direct lseq expression operands expr-wrapped: punk::lib lzipn_tcl9b/c,
cols, cols2; punk::args zero_based_posns.
check::has_tclbug_safeinterp_compile falls back to interp invokehidden
tcl:unsupported:disassemble - Tcl 9.1 safe interps hide tcl::unsupported::*.
New modules/punk/lib range.test pins the range contract on 9.0 and 9.1;
core tests AGENTS.md documents native-tclsh vs punk-exe exec.test baselines.

Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 6 days ago
parent
commit
87eafd63b0
  1. 7
      CHANGELOG.md
  2. 2
      punkproject.toml
  3. 3
      src/modules/punk/args-999999.0a1.0.tm
  4. 3
      src/modules/punk/args-buildversion.txt
  5. 32
      src/modules/punk/lib-999999.0a1.0.tm
  6. 3
      src/modules/punk/lib-buildversion.txt
  7. 1
      src/tests/core/AGENTS.md
  8. 60
      src/tests/modules/punk/lib/testsuites/lib/range.test

7
CHANGELOG.md

@ -5,6 +5,13 @@ 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.4.12] - 2026-07-09
- Tcl 9.1b0 runtime compatibility (first `punk91.exe` build on `tclsfe-x64`; the developed baseline remains Tcl 9.0.2):
- punk::lib 0.3.1: TIP 746 removed `lseq`'s expr-operand behaviour in Tcl 9.1 — `punk::lib::range` (lseq branch) now normalizes `int[+-]int` offset operands itself, so callers like `range 0 [llength $list]-1` keep working (this broke `punk::ansi::grepstr` and with it example-block highlighting and the punk::args examples.test under 9.1). Also aligned the lseq branch with the tcl8 fallback contract: default `by` now infers direction (descending `range 5 1` previously returned empty under tcl9 — silently breaking documented reverse-range callers) and `by 0` returns an empty list (9.1's `lseq ... by 0` changed to return one element). Direct `lseq` expression operands wrapped in `expr` (`lzipn_tcl9b`/`lzipn_tcl9c`/`cols`/`cols2`). `check::has_tclbug_safeinterp_compile` falls back to `interp invokehidden tcl:unsupported:disassemble` — Tcl 9.1 safe interps hide `tcl::unsupported::*` instead of exposing it.
- punk::args 0.3.2: `zero_based_posns` expr-wraps its `count-1` lseq operand (TIP 746).
- New `modules/punk/lib` range.test pins the `range` contract (offset expressions, direction inference, `by 0`) under both 9.0 and 9.1.
## [0.4.11] - 2026-07-09
- punk::args 0.3.1: fixed `helpers::example` `-title` path calling bare `[a]` (resolvable only where a global `a` alias exists, e.g. punk shells — errored under plain tclsh); found by the new test coverage.

2
punkproject.toml

@ -1,3 +1,3 @@
[project]
name = "punkshell"
version = "0.4.11"
version = "0.4.12"

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

@ -11590,7 +11590,8 @@ tcl::namespace::eval punk::args::lib {
#The internal rep can be an 'arithseries' with no string representation
proc zero_based_posns {count} {
if {$count < 1} {return}
lseq 0 $count-1
#expr wrapper required: lseq in Tcl 9.1+ (TIP 746) no longer evaluates expression operands
lseq 0 [expr {$count-1}]
}
} else {
proc zero_based_posns {count} {

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

@ -1,6 +1,7 @@
0.3.1
0.3.2
#First line must be a semantic version number
#all other lines are ignored.
#0.3.2 - Tcl 9.1 compatibility (TIP 746 removed expr behaviour from lseq operands): zero_based_posns now wraps its count-1 operand in expr
#0.3.1 - fixed helpers::example -title path calling bare [a] (only resolvable where a global 'a' alias exists, e.g. punk shells) - now punk::ansi::a; found by the new examples.test running under plain tclsh
#0.3.0 - new: -choicealiases {alias canonical ...} argument option (G-040) - aliases accepted exact under any -choiceprefix/-nocase setting, join the prefix-calculation pool when -choiceprefix is true, and normalize to their canonical choice in parse results; deny applies to the matched name (alias deny requires full alias; canonical reached via alias exempt from the canonical's own deny); usage display folds aliases into the canonical entry's label as an (alias:...) note and includes alias names in display prefix calculation; alias->existing-choice and no-collision validated at definition resolve. new public helper punk::args::choiceword_match - the single choice-word matching implementation extracted from get_dict, also consumed by punk::ns cmd_traverse for doc-lookup parity
#0.2.3 - fixed "@opts -any 1" adhoc option passthrough: an option not in the definition crashed get_dict ("can't read argname") at the name-mapping stage instead of passing through - adhoc opts now fall back to their raw supplied name (argstate/arg_checks already carried them); commented out a debug puts stderr (">>>_get_dict_can_assign_value NOT alloc_ok...") that fired on every failed clause type assignment (e.g. any multiform parse miss)

32
src/modules/punk/lib-999999.0a1.0.tm

@ -320,7 +320,10 @@ tcl::namespace::eval punk::lib::check {
interp create x -safe
} errMsg]} {
x eval {proc ensembletest {} {string index a 0}}
set bytecode_safe [x eval {tcl::unsupported::disassemble proc ::ensembletest}]
if {[catch {x eval {tcl::unsupported::disassemble proc ::ensembletest}} bytecode_safe]} {
#Tcl 9.1+ removes tcl::unsupported::* from safe interps - disassemble is instead a hidden command (tcl:unsupported:disassemble)
set bytecode_safe [interp invokehidden x tcl:unsupported:disassemble proc ::ensembletest]
}
if {$show} {
puts safe:
puts $bytecode_safe
@ -1311,14 +1314,27 @@ namespace eval punk::lib {
#tcl 8.7+ lseq significantly faster, especially for larger ranges
#The internal rep can be an 'arithseries' with no string representation
#support minimal set from to
proc range {from to {by 1}} {
proc range {from to {by ""}} {
#note inconsistency with lseq 1 10 by -9 vs lseq 1 10 by -10
#https://core.tcl-lang.org/tcl/tktview/999b6966b2
#lseq in Tcl 9.1+ no longer evaluates expression operands such as 4-1 (TIP 746)
#range continues to accept int[+-]int offset forms e.g `range 0 [llength $list]-1` (same contract as the non-lseq fallback below)
if {![string is double -strict $from]} {set from [offset_expr $from]}
if {![string is double -strict $to]} {set to [offset_expr $to]}
if {$by eq ""} {
#direction inferred - lseq produces a descending sequence when from > to (matching the non-lseq fallback below)
return [lseq $from $to]
}
if {![string is double -strict $by]} {set by [offset_expr $by]}
if {$by == 0} {
#documented range behaviour (and lseq behaviour prior to Tcl 9.1): a step of zero produces no results
return [list]
}
lseq $from $to by $by
}
} else {
#lseq accepts basic expressions e.g 4-2 for both arguments
#e.g we can do lseq 0 [llength $list]-1
#lseq prior to Tcl 9.1 (TIP 746) accepted basic expressions e.g 4-2 for both arguments
#range accepts int[+-]int offset forms in all branches e.g we can do range 0 [llength $list]-1
#if range is to be consistent with the lseq version above - it should support that, even though we don't support most lseq functionality in either wrapper.
#our range function doesn't support double like lseq does. (deliberate) review
proc range {from to {by ""}} {
@ -1595,7 +1611,7 @@ namespace eval punk::lib {
foreach len $lens list $args {
lappend flatlist {*}$list {*}[lrepeat [expr {($numcolumns - ($len % $numcolumns)) % $numcolumns}] ""]
}
lmap c [lseq 0 $numcolumns-1] {lsearch -stride $numcolumns -index $c -inline -all -subindices $flatlist *}
lmap c [lseq 0 [expr {$numcolumns-1}]] {lsearch -stride $numcolumns -index $c -inline -all -subindices $flatlist *}
}
proc lzipn_tcl9c {args} {
#SLOW
@ -1614,7 +1630,7 @@ namespace eval punk::lib {
}
lappend zip_l [lsearch -stride $cols_remaining -index 0 -inline -all -subindices $flatlist *]
set flen [llength $flatlist]
set flatlist [lremove $flatlist {*}[lseq 0 to $flen-1 by $cols_remaining]]
set flatlist [lremove $flatlist {*}[lseq 0 to [expr {$flen-1}] by $cols_remaining]]
incr cols_remaining -1
}
return $zip_l
@ -8613,14 +8629,14 @@ tcl::namespace::eval punk::lib::flatgrid {
}
proc cols {list numcolumns {blank NULL}} {
set cols [list]
foreach colindex [lseq 0 $numcolumns-1] {
foreach colindex [lseq 0 [expr {$numcolumns-1}]] {
lappend cols [lsearch -stride $numcolumns -index [list $colindex 0] -subindices -all -inline [list {*}$list {*}[lrepeat [filler_count [llength $list] $numcolumns] $blank]] *]
}
return $cols
}
proc cols2 {list numcolumns {blank NULL}} {
set cols [list]
foreach colindex [lseq 0 $numcolumns-1] {
foreach colindex [lseq 0 [expr {$numcolumns-1}]] {
lappend cols [col2 $list $numcolumns $colindex $blank]
}
return $cols

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

@ -1,6 +1,7 @@
0.3.0
0.3.1
#First line must be a semantic version number
#all other lines are ignored.
#0.3.1 - Tcl 9.1 compatibility (TIP 746 removed expr behaviour from lseq operands): range (lseq branch) now normalizes int[+-]int offset operands itself via offset_expr - callers such as `range 0 [llength $list]-1` keep working; also aligned the lseq branch with the fallback contract: default by now infers direction (range 5 1 -> descending, previously empty under tcl9) and by 0 returns empty (Tcl 9.1 lseq changed by-0 to return one element). lzipn_tcl9b/lzipn_tcl9c/cols/cols2: lseq expression operands wrapped in expr. check::has_tclbug_safeinterp_compile: Tcl 9.1 safe interps hide tcl::unsupported::* - falls back to interp invokehidden tcl:unsupported:disassemble
#0.3.0 - new: punk::lib::check::has_libbug_udp_threadexit + libbug_udp_threadexit_applies classifier - version-based detection of tcludp < 1.0.13 on Tcl 9 Windows (per-thread exit handler closes process-global event handles; G-036). has_libbug_* is the new check family for bundled/vendored library bugs, surfaced through 'help tcl' alongside has_tclbug_*; buginfo dicts may carry a full 'url' reference key (non tcl-core trackers)
#0.2.1 - call-site update for punk::console 0.6.0 tsv array rename: console -> punk_console (is_raw); no behaviour change
#0.2.0 - new: snapshot_package_paths proc returns a script string reproducing tm list, auto_path, and package prefer for use in thread::create init scripts

1
src/tests/core/AGENTS.md

@ -25,6 +25,7 @@ Selected tests taken from or modeled after Tcl core tests. These tests should pr
- `<tcl_interpreter> src/tests/runtests.tcl -report compact -show-passes 0 -include-paths core/**` passes for changes in this subtree.
- Compare results with standard `tclsh` and the target Punk executable when compatibility behavior is the subject of the change.
- Known baselines (2026-07-09): under a native `tclsh` (9.0.x/9.1) exactly one failure is expected — `exec-14.3`. Under a built Punk executable (`punk902z`, `punk91`) two additional exec.test failures (`exec-16.2`, `exec-17.1`) and ~22 extra skips appear because the tests re-exec `[interpreter]` (a full punkshell exe whose boot touches stdio) with redirected/closed standard channels; these are punk-exe artifacts, identical on Tcl 9.0.2 and 9.1, not core incompatibilities.
## Child DOX Index

60
src/tests/modules/punk/lib/testsuites/lib/range.test

@ -0,0 +1,60 @@
package require tcltest
package require punk::lib
namespace eval ::testspace {
namespace import ::tcltest::*
variable common {
set result ""
}
test range_basic {ascending and descending integer ranges (direction inferred when by not given)}\
-setup $common -body {
lappend result [punk::lib::range 1 5]
lappend result [punk::lib::range 5 1]
}\
-cleanup {
}\
-result [list\
{1 2 3 4 5}\
{5 4 3 2 1}
]
test range_by_step {explicit by step}\
-setup $common -body {
lappend result [punk::lib::range 1 7 2]
}\
-cleanup {
}\
-result [list\
{1 3 5 7}
]
test range_offset_expr_operands {int[+-]int offset expressions accepted in all operands (lseq in Tcl 9.1+ TIP 746 no longer evaluates these itself)}\
-setup $common -body {
#common calling pattern e.g punk::ansi::grepstr: range 0 [llength $list]-1
set l {a b c d}
lappend result [punk::lib::range 0 [llength $l]-1]
lappend result [punk::lib::range 2-1 [llength $l]+1]
lappend result [punk::lib::range 0 8 1+1]
}\
-cleanup {
}\
-result [list\
{0 1 2 3}\
{1 2 3 4 5}\
{0 2 4 6 8}
]
test range_by_zero {step of zero produces empty list as per lseq}\
-setup $common -body {
lappend result [llength [punk::lib::range 1 5 0]]
set result
}\
-cleanup {
}\
-result [list\
0
]
}
tcltest::cleanupTests ;#needed to produce test summary.
Loading…
Cancel
Save