Scripted List

A scripted list is a Tcl script in which every word of every command becomes a value in the resulting list, with the first word of each command being considered just another value instead of being used as the command name.

See Also

scripted dict
Scripting a deep dict.
ycl list deep
Provides scripted for scripting a deep list.
scripted templates
a twist on scripted lists
Convenient list arguments - larg
a similar idea from jcw

Download

In addition to the full implementation below, sl is also available as ycl list sl.

Description

A routine that implements a scripted list is a good candidate to be a built-in Tcl routine, in which case it might be called lsubst.

A scripted list is like a regular list, except that the standard substitutions are performed on the words to form the value of each word. This provides a way to represent a list using multiple lines without having to escape the newlines, and also allows for comments between words in the list.

For example with sl, switch can contain comments between its blocks:

set grail 42
switch $answer [sl {
    #set course for Traal
    {a lurgid bee} {
        lindex {Do not be alarmed} 
    }
    #By Agrajag!
    $grail {
        lindex {Oh no, not again.} 
    }
    #clueless Golgafrinchams 
    default {
        puts [list whoa $answer]
        error {get your cave redecorated!}
    }
}]

the following procedure employs commands to provide the described behaviour:

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

Larry Smith It occurs to me a little Sugar that substituted the [sl from 'begin' or 'do' and 'end' to ']' makes this concept quite readable:

set grail 42
switch $answer do {
    #set course for Traal
    {a lurgid bee} {
        lindex {Do not be alarmed} 
    }
    #By Agrajag!
    $grail {
        lindex {Oh no, not again.} 
    }
    #clueless Golgafrinchams 
    default {
        puts [list whoa $answer]
        error {get your cave redecorated!}
    }
} end

or even do-> "[sl {" and end->"} ]"

set grail 42
switch $answer do
    #set course for Traal
    {a lurgid bee} {
        lindex {Do not be alarmed} 
    }
    #By Agrajag!
    $grail {
        lindex {Oh no, not again.} 
    }
    #clueless Golgafrinchams 
    default {
        puts [list whoa $answer]
        error {get your cave redecorated!}
    }
end

Since the scripted list command "sl" itself can be coded to expand macros, this makes for a convenient escape from the strictures of escaped newlines or lots of braces. It does kind of change the feel of the language and I'm not sure how far one should take this, but if I were inclined to add macros built-in to the language this might be a nice way to go.

jsuntheimer72 applauds wildly for this idea ^

Another use for a scripted list is in parsing data formatted as a Tcl script:

proc configure script {
    foreach command [cmdSplit $script] {
        set command [namespace eval configns [
            list [namespace which sl] $script]]
        set cmdargs [lassign $command name]
        switch $name {
            someparameter {
                do something with $cmdargs
            }
            someotherparameter {
                do something with $cmdargs
            }
        }
    }
}

A Twisted Scripted List

In this twist on the sl, only the first command in a series of semicolon-separated commands becomes part of the result. The additional commands in such a series of commands might have side effects that influence the composition of the list. The current examples don't show a compelling use case, but perhaps someone will do something interesting with it.

Example

set mylist [twsl {
    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 [twsl {
    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 gets the special scripted list treatment, \
    but then only the value of the last command in a line counts, which \
    means that this line is like a comment with [side effects], and its \
    result that of the last command in the line; set var2 
}]

puts $mylist

Output:

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

The Twisted Implementation

proc twsl 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
}

History

The following was migrated here from Additional string functions:

Substitution that Preserves Grouping

RHS 2005-02-23: There have been a number of times where I have wanted a version of subst that preserves grouping. For example, I want to be able to do the following:

% array set myarr [subst {
    a $x
    b [getConfigValue something]
    c {
        a b
        c d
    }
}]
% array get myArr
a 1 b blah c {
        a b
        c d
    }

My main reason for this is, when the dataset get large, I tend to find \ line continuations fairly ugly... That, and my emacs config doesn't indent well for multiple levels of line continuations if there's sub levels.

In pursuit of the above, I came up with the following:

proc gsubst input {
    set data {}
    foreach line [split $input \n] {
        if {[info complete $data]} {
            append data " $line"
        } else {
            append data "\n$line"
        }
    }
    uplevel 1 list $data
}

The above proc should, in theory, cause the following two to work exactly the same

list a 1 \
    b $x \
    c {
        1
        2
    } \
    d [somecommand]
gsubst {
    a 1
    b $x
    c {
        1
        2
    }
    d [somecommand]
}

PYK 2015-10-23: Because it's using a less robust implementation of commands, gsubst chokes on semicolon-delimited commands and does not ignore comments. Also, to properly split a script into commands, it should be [info complete $data]\n rather than [info complete $data] but the false positive doesn't cause any problems because the newlines that delimit commands are just getting filtered out anyway.

Page Authors

RHS
PYK
aspect