hkoba: switch with regexp variable capturing.
Note: As of Tcl 8.5, the built in switch command supports regex variable capture natively via the "-matchvar" option.
Example -
# source this and namespace import switch-regexp::* foreach somevar {{** !!} {100 200}} { switch-regexp -- $somevar { {^(\d+)\s+(\d+)} {1 2} {puts "You hit number matches $1 and $2"} {^(\S+)\s+(\S+)} {1 2} {puts "You hit matches $1 and $2"} } }
This produces:
You hit matches ** and !! You hit number matches 100 and 200
# -*- mode: tcl; tab-width: 8 -*- # $Id: 13839,v 1.7 2005-12-17 07:01:54 jcw Exp $ package require cmdline namespace eval switch-regexp { namespace export switch-regexp* proc switch-regexp args { prepare $args opts value patlist varlist cmdlist group set match [match br $value $opts $patlist $varlist $cmdlist $group] if {$br < 0} { return $br } set code [catch { uplevel 1 [list [namespace current]::dispatch $br \ $value $match $varlist $cmdlist $group] } result] eval [list return] [control $code $result] } proc control {code result} { switch -exact -- $code { 0 {return $result} 1 { list -code error -errorcode $::errorCode \ -errorinfo $::errorInfo $result } 2 {list -code return $result} 3 {list -code break} 4 {list -code continue} default {list -code $code $result} } } proc match {brVar value opts patlist varlist cmdlist branch} { upvar 1 $brVar br set br -1 set match [eval [list regexp -inline -indices] \ $opts [list [join $patlist |] $value]] if {![llength $match]} { return } set br [find-matched-branch $match $branch] if {$br < 0} { error "Can't find branch! match is $match" } set match } proc prepare {arglist args} { foreach vn {opts value patlist varlist cmdlist group} an $args { upvar 1 $vn $vn } set opts {} foreach {o v} [cmdline::getoptions arglist { {expanded} {line} {linestop} {lineanchor} {nocase} {start.arg ""} }] { if {$o ne "start" && $v != 0} { lappend opts -$o } elseif {$o eq "start" && $v ne ""} { lappend opts -$o $v } } if {[llength $arglist] != 2} { error "Usage: ?opts..? value {pattern vars body ...}" } foreach {value body} $arglist break set patlist {} set varlist {} set cmdlist {} set group {}; set lastgroup 1 foreach {pat var cmd} $body { lappend patlist (?:$pat) lappend varlist $var lappend cmdlist $cmd lappend group $lastgroup incr lastgroup [llength $var] } } proc dispatch {br value match varlist cmdlist branch} { # puts "match=$match\nbr=$br@$branch\n[branch-get $match $branch $br]" propagate $value [lindex $varlist $br] [branch-get $match $branch $br]\ 1 set code [catch {uplevel 1 [lindex $cmdlist $br]} result] eval [list return] [control $code $result] } proc branch-range {branch nth max} { if {[llength $branch] - 1 <= $nth} { set end $max } else { set end [expr {[lindex $branch [expr {$nth + 1}]] - 1}] } list [lindex $branch $nth] $end } proc branch-get {list branch nth} { foreach {first last} [branch-range $branch $nth [llength $list]]\ break lrange $list $first $last } proc find-matched-branch {match branch} { set i 1; set br 0 set range [branch-range $branch $br [llength $match]] foreach m [lrange $match 1 end] { if {$i >= [lindex $range end]} { # puts "incr br($br). $i vs $range" set range [branch-range $branch [incr br] [llength $match]] } # puts $i=$m=$br=<$range> if {[is-matched $m]} { return $br } incr i } return -1 } proc is-matched pair { expr {[lindex $pair 0] > -1 && [lindex $pair 1] > -1} } proc range {string range} { eval [list string range $string] $range } proc propagate {value vars ranges {level 0}} { if {[set l1 [llength $vars]] != [set l2 [llength $ranges]]} { error "length mismatch: $l1 != $l2\n$vars\n$ranges" } incr level 1 foreach vn $vars range $ranges { upvar $level $vn var set var [range $value $range] } } proc @ varName { upvar 1 $varName var list $varName $var } proc switch-regexp-debug args { prepare $args opts value patlist varlist cmdlist group list [@ opts] [@ value] [@ patlist] [@ varlist] [@ cmdlist] [@ group] } }
And short test cases.
if {[info exists ::argv0] && [info script] == $::argv0} { package require tcltest namespace import tcltest::* set input foobar switch-regexp::prepare [list -expanded $input { ^f(.*) rest {puts $rest} [ob]* ob {puts $ob} }] opts value patlist varlist cmdlist group set i 0 test prepare-[incr i] {arg check} {set opts} {-expanded} test prepare-[incr i] {arg check} {set value} $input test prepare-[incr i] {arg check} {set patlist} {(?:^f(.*)) {(?:[ob]*)}} test prepare-[incr i] {arg check} {set varlist} {rest ob} test prepare-[incr i] {arg check} {set group} {1 2} array unset res test dispatch-1-returned-branch {should match first branch} { switch-regexp::switch-regexp {foo !!} { {^(\d+)\s+(\d+)} {1 2} { puts "hello 0" set res(branch) 0 } {^(\S+)\s+(\S+)} {1 2} { puts "hello 1" set res(branch) 1 } } } 1 test dispatch-1-executed-branch {should exec first branch} { set res(branch) } 1 test dispatch-1-vars {should match first branch} { list $1 $2 } {foo !!} unset 1 2 test dispatch-1-break {break} { set i 0 foreach value {{foo !!} {12 23}} { switch-regexp::switch-regexp $value { {^(\d+)\s+(\d+)} {1 2} { puts "hello 0" set res(branch) 0 } {^(\S+)\s+(\S+)} {1 2} { break } } incr i } set i } 0 unset 1 2 test dispatch-1-continue {continue} { set i 0 foreach value {{foo !!} {12 23}} { switch-regexp::switch-regexp $value { {^(\d+)\s+(\d+)} {1 2} { puts "decimals" set res(branch) 0 } {^(\S+)\s+(\S+)} {1 2} { continue } } puts "incrementing" incr i } list $i $1 $2 } {1 12 23} unset 1 2 test dispatch-1-return {return} { proc t {} { set i 0 foreach value {{foo !!} {12 23}} { switch-regexp::switch-regexp $value { {^(\d+)\s+(\d+)} {1 2} { error "should not leached here" } {^(\S+)\s+(\S+)} {1 2} { return FOO } } incr i } list $i $1 $2 } t } FOO test impl-branch-1 {find matched group} { switch-regexp::branch-range {1 3 7} 0 9 } {1 2} test impl-branch-1 {find matched group} { switch-regexp::branch-range {1 3 7} 1 9 } {3 6} test impl-branch-1 {find matched group} { switch-regexp::branch-range {1 3 7} 2 9 } {7 9} test impl-branch {find matched group} { set group [switch-regexp::find-matched-branch { {2 4} {-1 -1} {-1 -1} {-1 -1} {-1 -1} {3 3} {4 4} } {1 3 6}] } 2 test impl-branch {find matched group} { set group [switch-regexp::find-matched-branch { {2 4} {-1 -1} {-1 -1} {3 3} {4 4} {-1 -1} {-1 -1} } {1 3 6}] } 1 test impl-branch {find matched group} { set group [switch-regexp::find-matched-branch { {2 4} {3 3} {4 4} {-1 -1} {-1 -1} {-1 -1} {-1 -1} } {1 3 6}] } 0 }