Macro Facility for Tcl
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 (atrocities) 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"} }
PYK 2013-10-26: These days, such a thing could be implemented using tailcall:
proc mloop {idx cnt cmd} { set script [string map [ list \${idx} [list $idx] \${cnt} [list $cnt] \${cmd} [list $cmd]] { for {set ${idx} 0} {[set ${idx}] < ${cnt}} {incr ${idx}} ${cmd} }] tailcall {*}$script } proc mloop2 {idx cnt cmd} { tailcall for [list set $idx 0] "\[set [list $idx]] < [list $cnt]" [list incr $idx] $cmd } proc something {} { mloop {funny name} 5 {puts "hello [set {funny name}]"} mloop2 {funny name} 5 {puts "hello [set {funny name}]"} }
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
output:
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" {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 2004-03-27: Sugar implements a macro facility very similar to what the original author of this page described (Lisp alike).
__LINE__ a la c would be nice?