Version 6 of Scripted List

Updated 2014-08-01 14:00:52 by pooryorick

scripted list, by Poor Yorick, is a procedure that takes a script, but instead of evaluating each "command" as a command, it builds up a list by concatenating all the words of all the "commands". In other words, what is normally the command name in a script becomes just another item in the resulting list. The advantage is that it isn't necessary to use the backslash character when the list spans multiple lines, and in-line comments are supported.

The question often arises: "How can I write a list using command and variable substitution, without having to escape the newline between the elements, and with the ability to make comments in between elements of the list and comment out some elements of the list"? Scripted lists are the answer.

Example

set mylist [sl {
    one
    two three  ;#this is a comment
    $somevar
    #ha ha! a two-line \
    comment in the list!
    [some command]
    {;} ;#literal semicolons must be escaped
    {some literal}
    "some $substituted[value]"
}]

More Complex Example

set six six
proc some {args} {
    return {seven eight}
}
proc value {} {
    return {nine ten}
}

proc side {args} {
    upvar var2 var2
    set var2 twelve 
}
set substituted eleven
set mylist [sl {
    one
    two three  ;#this is a comment
    {four five}
    $six
    #this is a a two-line \
    comment in the list!
    [some command]
    {;} ;#literal semicolons must be escaped
    "[value] $substituted"

    only the first command in a line get the special scripted list treatment, \
    but then only only the value othe last command in a line counts which \
    means that this line is like a comment with [side effects], and its \
    result is whatever the last command produces; set var2 
}]

puts $mylist

Output:

one two three {four five} six {seven eight} {;} {nine ten eleven} twelve

Implementation

proc sl script {
    set res {}
    set parts {}
    foreach part [split $script \n] {
        lappend parts $part
        set part [join $parts \n]
        #add the newline that was stripped because it can make a difference
        if {[info complete $part\n]} {
            set parts {}
            set part [string trim $part]
            if {$part eq {}} {
                continue
            }
            if {[string index $part 0] eq {#}} {
                continue
            }
            #Here, the double-substitution via uplevel is intended!
            lappend res {*}[uplevel list $part]
        }
    }
    if {$parts ne {}} {
        error [list {incomplete parts} [join $parts]]
    }
    return $res
}

See Also

scripted templates
a twist on scripted lists
Convenient list arguments - larg
a similar idea from jcw

Discussion

aspect 2014-07-05: I notice the implementation above doesn't deal with inline semicolons very well -- [sl {one;two;three}] tries to evaluate list one;two;three instead of interpreting it as three list elements. Adding the inner loop from cmdSplit fixes this.

.. which leads to this dense implementation in terms of the latter:

proc sl script {
    concat {*}[lmap part [cmdsplit $script {
        if {[string match #* $part]} continue
        uplevel 1 list $part
    }]
}

PYK 2014-08-01: Yes, at the time I was interested in a version that didn't split on semicolons, but now think its too brittle to be useful. I'm using this dense version more often these days.