Scripted List

Difference between version 26 and 27 - Previous - Next
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%|%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%|%ycl list sl]`.



** Description **

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 `[cmdSplit%|%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 **

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''':

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



** The Twisted Implementation **

======
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 **

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

======none
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
`[cmdSplit%|%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]:   



<<categories>> list