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
}