15
votes

How does one list values in multiple lines without a backslash at the end of each line?

One can't create a list in multiple lines without having a backslash at the end.
For example, the following (wrong) code:

set pets [list 
    cat
    dog
    elephant
]

gives an error:

invalid command name "cat"
    while executing
"cat"
    invoked from within
"set pets [list
        cat
        dog
        elephant
]"

It can be fixed by appending a backslash at the end of the line:

set pets [list \
    cat \
    dog \
    elephant \
]

Which is ugly and prone to errors.

Please note that:

  • I'm aware of using the curly braces ({ & }), but it doesn't allows executing commands and also keeps redundant whitespace characters.
  • Any other command may be used (e.g. dict create), not only list as in my example.

Using Tcl 8.5

3

3 Answers

19
votes

Tcl uses newline (and semicolon) as a command separator. This is a core part of the basic syntax; you can't work around it so you must use either double quotes or braces to avoid backslash-itis. Let's look at the possibilities (remember, list separators can be any non-empty whitespace sequence).

Ugly, error prone list with backslashes

set pets [list \
    cat \
    dog \
    $elephant \
]

With braces, no substitutions

set pets {
    cat
    dog
    $elephant
}

(Note that in above, $elephant is just a sequence of characters, not a variable read.)

With double quotes, substitutions but be careful!

set pets "
    cat
    dog
    $elephant
"

By “be careful!” I mean that where you have a multi-word member of the list, you need an inner [list …] or other quoting:

set pets "
    cat
    dog
    [list $elephant]
    [list "elephant's child"]
"

But this would be true with the list+backslashes at the top.

Using subst

set pets [subst {
    cat
    dog
    $elephant
    "elephant's child"
}]

I might “clean that up” (and avoid other potential problems) with:

set pets [list {*}[subst {
    cat
    dog
    [list $elephant]
    [list "elephant's child"]
}]]

But frankly, if things are getting really complex then I actually do this:

Construct with several commands

set pets {
    cat
    dog
}
lappend pets $elephant "elephant's child"

No point in bashing yourself over the head to use one command when two or more will do everything with fewer problems.

2
votes

Sometimes just making multiple lappends to split lines can be "neat".

set pets ""
lappend pets cat
lappend pets dog
lappend pets elephant

foreach pet $pets {
   puts "I like to eat ${pet}s"
}
1
votes

Here's a proc called lines that might do what you're looking for—

proc lines {lines} {
    foreach item [uplevel [list subst -nobackslash $lines]] {
        lappend list $item
    }
    return $list
}

Here's a demonstration of its use—

set another_pet fish;

set pets [lines {
    cat
    [string range hotdog 3 end]
    elephant
    $another_pet
    "african pygmy hedgehog"
    snapping\ turtle
    "\"henry\" the bengali tiger"
}]

puts $pets

It outputs, as desired,

cat dog elephant fish {african pygmy hedgehog} {snapping turtle} {"henry" the bengali tiger}

An Ideone is here if you'd like to play around with a fork.