AtExit for tcl 8.5

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++.