Inspired by a comp.lang.tcl articel [L1 ], I rethought my older way of AtExit and here it is in a tcl 8.5 version:
namespace eval ::atExit {
variable scripts;
variable state;
variable stopOnError;
variable mode;
set scripts [list];
set state 1;
set stopOnError 0;
set mode "normal"
proc register {args} {
if {[llength $args] == 0} {
error "wrong # args: should be \"atExit register command ?arg arg ...?\"";
}
variable scripts;
if {[info complete $args] == 0} {
error "invalid exit script \"$args\"";
}
lappend scripts $args;
if {{enter ::atExit::enter} ni [trace info execution exit]} {
trace add execution exit enter ::atExit::enter;
}
return [format {atExit%x} [llength $scripts]];
}
proc unregister {args} {
if {[llength $args] == 0} {
error "wrong # args: should be \"atExit unregister token ?token ...?\"";
}
variable scripts;
foreach token $args {
if {[scan $token {atExit%x} index] != 1} {
error "invalid atExit script token \"$token\"";
}
set scripts [lreplace $scripts $index-1 $index-1];
}
if {[llength $scripts] == 0} {
trace remove execution exit enter ::atExit::enter;
}
return;
}
proc scripts {} {
variable scripts;
return $scripts;
}
proc state {{newState NaV}} {
variable state;
if {$newState ne "NaV"} {
if {[string is boolean -strict $newState] == 1} {
set state [string is true $newState];
} elseif {$newState in {disabled normal}} {
set state [expr {$newState eq "normal" ? 1 : 0}];
} else {
error "expected a valid boolean value, but got \"$newState\"";
}
}
return $state;
}
proc stoponerror {{newState "NaV"}} {
variable stopOnError;
if {$newState ne "NaV"} {
if {[string is boolean -strict $newState] == 1} {
set stopOnError [string is true $newState];
} elseif {$newState in {disabled normal}} {
set stopOnError [expr {$newState eq "normal" ? 1 : 0}];
} else {
error "expected a valid boolean value, but got \"$newState\"";
}
}
return $stopOnError;
}
proc mode {{newMode "NaV"}} {
variable mode;
if {$newMode ne "NaV"} {
if {$newMode in {fifu normal}} {
set mode "normal";
} elseif {$newMode in {lafu reversed}} {
set mode "reversed";
} else {
error "bad evaluation mode \"$newMode\": must be fifu, lafu, normal, or reversed";
}
}
return $mode;
}
proc enter {args} {
variable state;
variable scripts;
variable stopOnError;
variable mode;
if {$state == 0} {
return;
}
foreach script [expr {$mode eq "normal" ? $scripts : [lreverse $scripts]}] {
set rc [catch {uplevel #0 $script;} reason];
if {$rc == 1} {
if {$stopOnError == 1} {
error $reason $::errorInfo $::errorCode;
} elseif {[string length $reason] > 0} {
puts stderr $reason;
}
} elseif {$rc == 3} {
if {[string length $reason] > 0} {
puts stderr $reason;
}
break;
}
}
return;
}
namespace export -clear register unregister scripts state stoponerror mode;
namespace ensemble create;
}
if {0} {
proc test1 {} {
puts [info level 0];
gets stdin;
}
proc test2 {} {
puts [info level 0];
gets stdin;
error "test2 error";
}
proc test3 {} {
puts [info level 0];
gets stdin;
return -code break "test3 break";
}
atExit register test1;
atExit register test2;
atExit register test3;
atExit stoponerror 0;
# normal use case
#
exit;
# stop-on-error use case
# => the error caused by test2 lets exit stop and return
#
atExit stoponerror 1;
exit;
# reversed use case:
# => test3 will evaluated at first
# => the break causes to ignore test2 and test2
#
atExit mode reversed;
atExit stoponerror 1;
exit;
} LV 2008-Jan-17 Just an observation - every statement above ends in a ; which the interpreter will of course recognize, but which isn't required. But you probably already knew that...
male 2008-Jan-17 ... yes, I know this!
I'm just too stupid to write C(++) without forgetting semicolons after writing tcl code without. So I use them consequently in tcl and C++.