Version 12 of An indentation syntax for Tcl

Updated 2005-11-09 03:00:09

Here's a little Tcl script which lets you use indentation as optional subsitute for braces, as pioneered by Python -jcw


First a sample of the new syntax (note that this is NOT standard Tcl!):


  # A little package to deal with an alternate Tcl source code syntax,
  # using optional indentation as a substitute for braces.
  #
  # by Jean-Claude Wippler, June 2002

  package provide indentcl 0.3

  namespace eval indentcl
    namespace export iconvert ieval isource

    namespace eval v
      variable lb \{
      variable rb \}

    proc emit {line}
      if {[llength $v::last] > 0}
        lappend v::result [join $v::last " "]
      foreach x $v::keep
        lappend v::result $x
      set v::keep {}
      set v::last [list $line]

    proc dedent {i}
      while {$i < [lindex $v::levels end]}
        set v::levels [lreplace $v::levels end end]
        lappend v::last $v::rb

    proc iconvert {text}
      set v::last {}
      set v::levels 0
      set v::keep {}
      set v::result {}

      foreach x [split $text \n]
        regexp {^(\s*)(.*)$} $x - a b
        if {$b == "" || [string index $b 0] == "#"}
          lappend v::keep $x
          continue
        set i 0
        foreach y [split $a ""]
          switch $y
            " "  { incr i }
            "\t" { incr i [expr {8 - $i%8}] }
        if {$i < [lindex $v::levels end]}
          dedent $i
          if {$i != [lindex $v::levels end] || [regsub {^(\s*)\\:} $x {\1} x]}
            lappend v::last \\
        \:elseif {$i > [lindex $v::levels end]}
          lappend v::last $v::lb
          lappend v::levels $i
        emit $x

      dedent 0
      emit ""
      set r [join $v::result "\n"]
      while {[llength $r] == 1 && [lindex $r 0] ne $r}
        set r [lindex $r 0] ;# strip top level indentation
      return $r

    proc ieval {script}
      uplevel 1 [iconvert $script]

    proc isource {file}
      set fd [open $file]
      set data [read $fd]
      close $fd
      set prev [info source]
      info source $file
      set result [uplevel 1 [iconvert $data]]
      info source $prev
      return $result

  if {$argv0 == [info script]}
    if {[llength $argv] != 2}
      puts "usage: $argv0 infile outfile"
      exit

    set fd [open [lindex $argv 0]]
    set data [read $fd]
    close $fd

    set data [indentcl::iconvert $data]

    set fd [open [lindex $argv 1] w]
    puts -nonewline $fd $data
    close $fd

And here's the Tcl script that can deal with the above input file:


  # A little package to deal with an alternate Tcl source code syntax,
  # using optional indentation as a substitute for braces.
  #
  # by Jean-Claude Wippler, June 2002

  package provide indentcl 0.3

  namespace eval indentcl {
    namespace export iconvert ieval isource

    namespace eval v {
      variable lb \{
      variable rb \} }

    proc emit {line} {
      if {[llength $v::last] > 0} {
        lappend v::result [join $v::last " "] }
      foreach x $v::keep {
        lappend v::result $x }
      set v::keep {}
      set v::last [list $line] }

    proc dedent {i} {
      while {$i < [lindex $v::levels end]} {
        set v::levels [lreplace $v::levels end end]
        lappend v::last $v::rb } }

    proc iconvert {text} {
      set v::last {}
      set v::levels 0
      set v::keep {}
      set v::result {}

      foreach x [split $text \n] {
        regexp {^(\s*)(.*)$} $x - a b
        if {$b == "" || [string index $b 0] == "#"} {
          lappend v::keep $x
          continue }
        set i 0
        foreach y [split $a ""] {
          switch $y {
            " "  { incr i }
            "\t" { incr i [expr {8 - $i%8}] } } }
        if {$i < [lindex $v::levels end]} {
          dedent $i
          if {$i != [lindex $v::levels end] || [regsub {^(\s*)\\:} $x {\1} x]} {
            lappend v::last \\ } } \
        elseif {$i > [lindex $v::levels end]} {
          lappend v::last $v::lb
          lappend v::levels $i }
        emit $x }

      dedent 0
      emit ""
      set r [join $v::result "\n"]
      while {[llength $r] == 1 && [lindex $r 0] ne $r}  {
        set r [lindex $r 0] ;# strip top level indentation }
      return $r }

    proc ieval {script} {
      uplevel 1 [iconvert $script] }

    proc isource {file} {
      set fd [open $file]
      set data [read $fd]
      close $fd
      set prev [info source]
      info source $file
      set result [uplevel 1 [iconvert $data]]
      info source $prev
      return $result } }

  if {$argv0 == [info script]} {
    if {[llength $argv] != 2} {
      puts "usage: $argv0 infile outfile"
      exit }

    set fd [open [lindex $argv 0]]
    set data [read $fd]
    close $fd

    set data [indentcl::iconvert $data]

    set fd [open [lindex $argv 1] w]
    puts -nonewline $fd $data
    close $fd }

If you run that second script with the first as input file... you get the second script again :o)

14apr03 jcw - Adjusted to deal with "re-indentation" by starting a line with "\:". There is one example of it in the above. It is needed to support mixed indents/dedents of the form:

  if {...} {
    ...
  } else {
    ...
  }

This can now be written as:

  if {...}
    ...
  \:else
    ...

In essence, "\:" means: "dedent, but keep on continuing last block". Ugly, but this gets around the issue. Note that "\:" is not just for else's.


How's this for a twisted example:

  package require indentcl
  indentcl::ieval {
    package require critcl

    critcl::ccode fraction {int n} double
      double f = 1;
      while (--n >= 0)
        f /= 2;
      return f;

    puts [fraction 6]
  }

Category Syntax