Creating Dicts

I've been playing around a little with dictionary creation. The Tcl code below allows a dictionary, say this one:

    set person {
        name "John Smith"
        age 35
        address {
            address1 "30 New Road"
            address2 "Old Estate"
            town "Reading"
            county "Berkshire"
        }
        children {
            {
                {
                    name Janet
                    age 3
                }
                {
                    name Joe
                    age 5
                }
            }
        }
    }

to be created instead like this:

    set person [dict config {
        set name "John Smith"
        set age 35
        set address [dict config {
            set address1 "30 New Road"
            set address2 "Old Estate"
            set town "Reading"
            set county "Berkshire"
        }]
        set children [list]
        lappend children [dict config {
            set name Janet
            set age 3
        }] [dict config {
            set name Joe
            set age 5
        }]
    }]

Within the dict config code block, a key/value is only added to the dictionary by setting variables in the code block using set (not ::set).

Variables within the dict config code block can't be seen outside of that code block. Also, dict config either creates a new dictionary, or modifies an existing one, as shown in the test code below. The dictionary is updated at the end of the code block.


Here's the code:

namespace eval ::DictExtension {
    namespace export dict

    proc dict {args} {
        if {[lindex $args 0] eq "config"} {
            if {[llength $args] < 2} {
                error "dict config error"
            }
            ::set block [format {
                ::dict for {configVar configVal} $configVars {
                    set $configVar $configVal
                }
                %s
                foreach configVar [::dict keys $configVars] {
                    ::dict set configVars $configVar [set $configVar]
                }
                return $configVars
            } [lindex $args end]]
            if {[llength $args] == 2} {
                return [uplevel [list apply [list {configVars} $block \
                        ::DictExtension] ""]]
            } else {
                if {![uplevel info exists [lindex $args 1]]} {
                    uplevel set [lindex $args 1] \{\}
                }
                if {[llength $args] == 3 || [uplevel ::dict exists \
                            \$[lrange $args 1 end-1]]} {
                    ::set vars [uplevel ::dict get \$[lrange $args 1 end-1]]
                } else {
                    ::set vars ""
                }
                ::set vars [uplevel [list apply [list {configVars} $block \
                        ::DictExtension] \ $vars]]
                ::dict for {var val} $vars {
                    uplevel ::dict set [lrange $args 1 end-1] \{$var\} \{$val\}
                }
            }
        } else {
            return [uplevel ::dict $args]
        }
    }

    proc set {args} {
        if {[llength $args] == 2} {
            upvar configVars vars
            ::dict set vars [lindex $args 0] [lindex $args 1]
        }
        return [uplevel ::set $args]
    }
}

namespace eval test {
    namespace import ::DictExtension::*

    set person [dict config {
        set name "John Smith"
        set age 35
        set address [dict config {
            set address1 "30 New Road"
            set address2 "Old Estate"
            set town "Reading"
            set county "Berkshire"
        }]
        set children [list]
        lappend children [dict config {
            set name Janet
            set age 3
        }] [dict config {
            set name Joe
            set age 5
        }]
    }]
    puts "initial:\n$person"
    puts [dict get $person name]
    puts [dict get $person address address1]

    dict config person address {
        set address1 "New House"
        set address2 "Another Estate"
        set town "Milton Keynes"
        set county "Buckinghamshire"
    }
    puts "\nfirst update:\n$person"
    puts [dict get $person name]
    puts [dict get $person address address1]

    dict config person {
        incr age
        lappend children [dict config {
            set name Jack
            set age 0
        }]
    }
    puts "\nsecond update:\n$person"
    puts [dict get $person name]
    puts [dict get $person age]
}

And here's the output:

initial:
name {John Smith} age 35 address {address1 {30 New Road} address2 {Old Estate} town Reading county Berkshire} children {{name Janet age 3} {name Joe age 5}} 
John Smith
30 New Road

first update:
name {John Smith} age 35 address {address1 {New House} address2 {Another Estate} town {Milton Keynes} county Buckinghamshire} children {{name Janet age 3} {name Joe age 5}}
John Smith
New House

second update:
name {John Smith} age 36 address {address1 {New House} address2 {Another Estate} town {Milton Keynes} county Buckinghamshire} children {{name Janet age 3} {name Joe age 5} {name Jack age 0}}
John Smith
36

As dict config variable scope is limited to the code block, use uplevel to access variables external to the code block. For example, below uses uplevel 1 to get access to name, and uplevel 2 to get access to location.

    set name "John Smith"
    set location Berkshire
    set person [dict config {
        set name [uplevel 1 {set name}]
        set age 35
        set address [dict config {
            set address1 "30 New Road"
            set address2 "Old Estate"
            set town "Reading"
            set county [uplevel 2 {set location}]
        }]
        ...