Version 14 of Macro Facility for Tcl

Updated 2005-12-18 00:42:41

Please stop me if you've heard this one before...

Not heard but have actually built this one Tmac - a Tcl macro processor package - Roy Terry, 16Apr2003.

What would it take to add a macro facility to Tcl? What use would such a facility be?

Well, for starters, it would make syntatical sugar (attrocities) such as:

 proc first {l} {
   lindex $l 0
 }

 proc rest {l} {
   lrange $l 1 end
 }

cost less in terms of performance. Yes, yes, I know -- if performance really mattered I shouldn't be doing this in Tcl. But the difference between calling my own procs vs internal ones (especially in deep loops) count, right?

Well, what about immediate macros (or macros that have a compile time behavior). Common Lisp should come to mind, but I can't help thinking about Forth immediate words. So, I could do something like:

 mac log {msg} {
    global debug
    if {$debug} {
        return "puts stderr $msg"
    } else {
        return "\;"
    }
 }

and use it thusly:

 set debug 0 
 ...
 proc something {} {
   set stuff "contents not available for introspection by macro yet." 
   log "Some noise I may not want to see depending on the value of debug"
   log $stuff; # if debug was 1, then the macro would expand to: puts stderr $stuff
   ...
 }

The above assumes that I have redefined proc to point to a macro evaluator to execute all macros before actually defining the real proc. The return value of the macro evaluation is what replaces the macro in the procedure. In this example, I can do conditional compilation!

This could all be done in plain Tcl if I gave Tcl access to the Tcl C parser code (to properly locate the arguments for the expanding macro). Note: You are limited by what context is available when executing the macro (you can for instance look into the surrounding proc's variables since we are in compile mode --- there aren't any values for the variables yet so the arguments to the macros are not eval'd!).

Another use for an immediate macro facility:

 mac  mloop {idx cnt cmd} {
    return "for {set $idx 0} {\$[set $idx] < $cnt} {incr $idx} {$cmd}"
 }

 proc something {} {
   mloop i 5 {puts "hello $i"}
   # above expands to:  for {set i 0} {$i < 5} {incr i} {puts "hello $i"}
 }

My mind is often polluted by such sickness...

-- Todd Coram


Well the log use of macro you had above looks like an assert function, which I believe has been addressed by cleverness in the core that optimizes null functions out of existence.

I've used macros in a cpp-ish fashion, to eliminate big repeated blocks of code. Here's the macro function I wrote:

  # procedure to create macros that operate in caller's frame, with arguments
  # no default args yet
  proc macro {name formal_args body} {
      proc $name $formal_args [subst -nocommands {
      # locally save all formal variables, and set them in parent conext
      foreach _v [list $formal_args] {
          if {[uplevel 1 info exists \$_v]} {
          set __shadow__\$_v [uplevel 1 set \$_v]
          }
          uplevel 1 set \$_v [set \$_v]
      }
      uplevel 1 { $body }
      # undo formal variables
      foreach _v [list $formal_args] {
          if {[info exists __shadow__\$_v]} {
          uplevel 1 set \$_v [set __shadow__\$_v]
          } else {
          uplevel 1 unset \$_v
          }
      }
      }]
  }

So you can do something like

  set text "hello"
  macro foo {a} {
    puts "$text $a"
  }
  foo world
  foo everybody

  =>

  hello world
  hello everybody

Of course this makes more sense when the body of the macro is 70 lines long and it's used in 8 different files, so it replaces a whole bunch of identical (except for a few bits) code with something a lot more readable.


RS has devised this very simple argument-less "micro-macro" instigated by Literate programming in a wiki. Beware that spaces in proc names, as implemented here, may at some time in the future be no more possible:

 proc @ {name {body -}} {
    if {$body!="-"} {
        proc $name {} [list uplevel 1 $body]
    } else {uplevel 1 [list $name]}
 }

# Macro definitions:

 @ "Prepare input"  {set x 2}
 @ "Produce result" {puts [expr sqrt($x)]}

# Macro testing:

 % @ "Prepare input"
 2
 % @ "Produce result"
 1.41421356237

Beware that spaces in proc names, as implemented here, may at some time in the future be no more possible - what do you mean?

Perhaps a reference to [L1 ] item 5?


jcw is more data oriented than procedural, and adds:

 proc @ {name {body -}} {
   if {$body ne "-"} {
     set ::snippets($name) $body 
   } else {
     uplevel 1 $::snippets($name) 
   }
 }

SS: 27Mar2004: Sugar implements a macro facility very similar to what the original author of this page described (Lisp alike).


Category String Processing