Updated 2016-09-24 14:45:38 by pooryorick

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  edit

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

Download  edit

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

Description  edit

One way to look at a Tcl script is as a sequence of words that are grouped into commands. The sl command presented below extracts those words into a single list while obeying the semantics of a Tcl script. Representing a list in this way, as a Tcl script, allows for elements on multiple lines without having to escape the newline, and also allows for comments between words in the list.

For example a 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.

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  edit

This list is a twist on the standard scripted list in which 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 [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 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  edit

proc sl_twisted 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  edit

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  edit

RHS
PYK
aspect