Version 4 of Self-rewriting code

Updated 2006-04-27 02:36:36

Richard Suchenwirth 2006-04-26 - A question on comp.lang.tcl brought me to experiment with code that rewrites itself. The idea is that settings are not saved in a separate resource file, but in the script itself, as setting a global variable with the desired value. So the next time the script is run, the previously saved setting applies. Here's what I did:

 proc save {filename varlist} {
     set f [open $filename]
     set data [read $f]
     close $f
     set lines {}
     foreach line [split $data \n] {
          foreach var $varlist {
             if [regexp "^ *set $var " $line] {
                 set line [list set $var [set ::$var]]
             }
         }
         lappend lines $line
     }
     set f [open $filename w]
     puts $f [join $lines \n]
     close $f
 }

#-- Now for a testing demo:

 package require Tk

#-- We will test with this variable:

 set foo hello

 label  .1 -text foo: 
 entry  .2 -textvar foo  
 button .3 -text Save -command [list save [info script] foo]
 eval pack [winfo children .] -side left 

#-- Invaluable little helper for rapid restart:

 bind . <Escape> {exec wish $argv0 &; exit}

lexfiend 2006-04-27 - Here's a modification that places all the state variables in a single section, so that save doesn't inadvertantly overwrite other sets of the state variables and thereby change the program logic. However, note that this forces you to pre-initialize any namespaces on which the state variables depend.

 # Create empty namespaces first
 namespace eval testns {}

 ### VARIABLE SAVE SECTION
 set test1 "I am here"
 set testns::test2 "Here as well"
 ### VARIABLE SAVE SECTION

 proc save {filename varlist} {
     set f [open $filename]
     set data [read $f]
     close $f
     set lines {}
     set inSaveSection 0
     foreach line [split $data \n] {
         if [regexp "^ *#+ *VARIABLE SAVE SECTION" $line] {
             if {!$inSaveSection} {
                 # Start of save section
                 lappend lines "### VARIABLE SAVE SECTION"
                 foreach var $varlist {
                     lappend lines [list set $var [set ::$var]]
                 }
                 set inSaveSection 1
             } else {
                 # End of save section
                 lappend lines "### VARIABLE SAVE SECTION"
                 set inSaveSection 0
             }
         } elseif {$inSaveSection} {
             # Somewhere in save section - do nothing
         } else {
             lappend lines $line
         }
     }
     set f [open $filename w]
     puts $f [join $lines \n]
     close $f
 }

 set testns::test2 "Gone forever"
 set test1 "Not here now"
 save [info script] {test1 testns::test2}

EMJ - 2006-04-26 - Aaaaargh! Self-modifying code, the slippery slope to certain unmaintainability! That of course is a remark from the distant past, when clever people wrote very fast but generally incomprehensible self-modifying assembly language. The warning is still valid, however - be careful!

For the stated purpose, it's not actually too bad, but I think I would use an alias or something rather than just set, in order to make the regex more discriminating. I guess it's simply the reverse of what tkbiff does, writing most of its code to its config file!

IL - This kind of stuff always makes me think of DNA, the assembly of the organic life. I suppose that makes tRNA the compiler and proteins the executable... heh.


Category Example - Arts and crafts of Tcl-Tk programming