Updated 2017-05-25 00:55:52 by LarrySmith

I had asked in dict with how to add a key to the dict referred to in the with block. I tried a couple variants but they didn't work. Here's what I came up with (so to speak):

Basically, I wrapped a couple of foreach loops around a script passed to me, one to unpack the existing keys into variable, and the other to repack them. Each loop is quite straightforword. The tricky bit is in addkey, which take the key and the value to set it to, set up access to the original dict variable using the name passed into the script, and then adds the key and the init value onto the end of the dict. It doesn't do any checking to see if you are redeclaring the key. The repack again uses the keys in the dict, which now include the new key(s) added within, so they just get put into the dict.

If you want to avoid cluttering the namespace, you can extend the repack loop with an "unset $key" followed by an "unset dictvar" and everything is nice and tidy. I also added a prefixed %-sign to avoid clobbering "dictvar" - people would expect this to clobber any local names that match keys in the dict, but they would not the %dictvar, so I kind of hid it.
  proc addkey {key init} {
    upvar %dictvar dictname
    uplevel lappend $dictname $key $init
    uplevel set $key $init
  }

  proc withdict {dictvar script} {
    upvar $dictvar dict
    set template {set %dictvar 111
                  foreach key [dict keys $111] {set $key [dict get $111 $key]}
                  222
                  foreach key [dict keys $111] {dict set 111 $key [set $key]; unset $key};unset %dictvar
                 }
    set script [string map [list 111 $dictvar 222 $script] $template]
    uplevel $script
  }

  set test [dict create a 1 b 2 c 3]

  puts "dict is $test"

  withdict test {
    puts "a=$a, b=$b, c=$c"
    incr a; incr b -1; incr c
    addkey d 5
    incr d -1
    puts "a=$a, b=$b, c=$c, d=$d"
  }

  puts "dict is $test"