See Also edit
Description edit
Todd Coram: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 likeset text "hello"
macro foo {a} {
puts "$text $a"
}
foo world
foo everybodyoutput:hello world hello everybodyOf 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 [1] 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?

