Version 15 of Here document

Updated 2015-08-06 16:40:27 by dbohdan

From the Wikipedia article [L1 ]:

"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