'''Macro Facility for Tcl''' ** See Also ** [Tmac - a Tcl macro processor package], [Roy Terry]: ** Description ** [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 like ====== set text "hello" macro foo {a} { puts "$text $a" } foo world foo everybody ====== output: ======none 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 [http://wiki.tcl.tk/883] 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). <> Category String Processing | Category Development | Category Parsing