Here document

According to Wikipedia ,

"A here document (also called a here-document or a heredoc), is a way of specifying a string literal in shells such as Bash, Windows PowerShell and the Bourne Shell, as well as programming languages such as Perl, PHP, Python and Ruby. It preserves the line breaks and other whitespace (including indentation) in the text. Some languages allow variable interpolation or even code to be evaluated inside of the string."

EKB: FB, I agree with your point. I rewrote this so it reads as documentation, rather than a discussion. Is this OK with you? FB: No problem!

Note that this is not the same as simply putting text inside curly braces. It might seem like it since, for example, the Ruby example from the Wikipedia article:

puts <<GROCERY_LIST
Grocery list
------------
1. Salad mix.
2. Strawberries.*
3. Cereal.
4. Milk.*
 
* Organic
GROCERY_LIST

can easily be implemented in Tcl:

puts {Grocery list
------------
1. Salad mix.
2. Strawberries.*
3. Cereal.
4. Milk.*

* Organic}

But a "pure" heredoc is format-agnostic. The example above will fail if the string includes an unbalanced brace, e.g.:

Ruby:

puts <<GROCERY_LIST
Grocery list }
------------
1. Salad mix.
2. Strawberries.*
3. Cereal.
4. Milk.*
 
* Organic
GROCERY_LIST

Tcl:

puts {Grocery list }
# everything below is a syntax error because of the above close brace.
------------
1. Salad mix.
2. Strawberries.*
3. Cereal.
4. Milk.*
 
* Organic}

So in Tcl you have several ways of specifying string litterals:

  • as single words; special chars need to be escaped;
  • between double quotes; special chars also need to be escaped;
  • between braces; braces must be balanced or escaped (in which case the backslash becomes part of the string, so it's not a real escape).

Tcl doesn't provide an heredoc like feature, because all quoting rules require the user to escape all significant characters. This is a need that FB tries to address in Cloverfield with the {data} word modifier (see Cloverfield - Tridekalogue, section Word modifiers, item Raw data). The above example becomes:

puts {data}GROCERY_LIST
Grocery list }
------------
1. Salad mix.
2. Strawberries.*
3. Cereal.
4. Milk.*
 
* Organic
GROCERY_LIST

which is very similar to the Ruby version. The real strength of heredoc is to let the user supply an arbitrary delimiter (here GROCERY_LIST) to circumvent the regular formatting rules.

BTW, the present Wiki provides heredoc-like markup with the six- and three-equal sign sequences (see Wiki formatting rules). Of course they are not real heredocs because one cannot use these sequences within the quoted text, but the concept is quite similar.

A preprocessor to allow here documents in Tcl

dbohdan 2015-08-06: What follows is a rough sketch of a preprocessor that lets you embed heredocs in Tcl source code. I have not seriously tested it; please fix any bugs you find. Note that with this preprocessor each heredoc must start with the text <<<(LABEL) on a line of its own.

# v0.0.1
package require fileutil

namespace eval ::heredoc {}

# Extract a heredoc from Tcl source code.
proc ::heredoc::extract {source label} {
    set lines [split $source \n]
    set heredoc {}
    set offset [lsearch -exact $lines "<<<$label"]
    if {$offset == -1} {
        error "can't find a heredoc with label $label"
    }
    set line {}
    for {set i $offset} {$line ne $label} {incr i} {
        set line [lindex $lines $i]
        lappend heredoc $line
    }
    return [join [lrange $heredoc 1 end-1] \n]
}

# Convert Tcl source code with embedded heredocs to standard Tcl.
proc ::heredoc::embed-all source {
    set lines [split $source \n]
    set result {}
    set label {}
    set on 0
    foreach line $lines {
        if {$on} {
            if {$line eq $label} {
                set on 0
                lappend result "\[join [list $acc] \\n\]"
            } else {
                lappend acc $line
            }
        } else {
            if {[regexp  ^<<<(.*)$ $line _ label]} {
                set on 1
                set acc {}
            } else {
                lappend result $line
            }
        }
    }
    return [join $result \n]
}

Use example

Input

puts \
<<<HEREDOC1
Hello, World!
{{{
}}
HEREDOC1

set a \
<<<HEREDOC2
Blah!
HEREDOC2
puts $a

::heredoc::embed-all output

puts \
[join {{Hello, World!} \{\{\{ \}\}} \n]

set a \
[join Blah! \n]
puts $a

Inline (pseudo) here-documents

aspect .. by looking at info frame, one can do (arguably) slightly better:

proc <<< args {
    if {[llength $args] < 2} {error "incorrect args: expected \"<<< cmd ?arg ...? marker\""}

    set marker [lindex $args end]
    set herecmd [lrange $args 0 end-1]
    set frame [info frame -1]
    dict with frame {}

    if {$type ne "source"} {error "Invalid location for <<<"}           ;# not safe

    set fd [open $file r]
    for {set i 0} {$i < $line} {incr i} {   ;# skip what's already been evaluated
        append script [gets $fd]
    }
    if {![info complete $script]} {error "invalid location for <<<"}    ;# definitely not safe!

    while {[gets $fd line]} {               ;# get the heredoc
        if {$line eq $marker} break
        lappend heredoc $line
    }
    set heredoc [join $heredoc \n]

    set script [read $fd]                   ;# rest of the script
    close $fd

    uplevel 1 $herecmd [list $heredoc]
    uplevel 1 $script
    return -code return                     ;# tell our caller (source) that it's done
}

puts starting
<<< set xxx _END_
Hello, World!
{{{
}}
_END_
puts $xxx
puts etc

Of course this breaks horribly if you try to put multiple here-docs in the same file, and (for the same reason) anything after the heredoc will lie in error traces about where it was defined. These could be worked around by mounting and sourcing a vfs proxy of the original file, but that's some pretty serious interpreter abuse and probably illegal in some places.


PYK 2015-08-08:

Heredocs are in the Tcl doesn't have that because it would be a misfeature category. Other languages need heredocs because their syntax is more irregular. Tcl doesn't have that problem. Anything that can be accomplished with a heredoc can be accomplished just as easily with standard Tcl syntax:

puts "
Hello, World!
{{{
}}
"

string cat or append can be used to make easy work of more complicated text:

string cat {Hello, World you owe me $5!
} "{{{
}}"