1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
|
* Section 0 - '''[Sugar]'''
* Section 1 - '''[Sugar command macros]'''
* Section 2 - '''[Sugar syntax macros]''' (what you are reading)
* Section 3 - '''[Sugar transformers]'''
'''Syntax macros and syntax sugar'''
Syntax macros are exactly like the command macros already described,
with the only difference that *every syntax macro is called for every
command in the application*. Actually you can immagine a command
macro as a syntax macro that works as an identity transformation if
the first argument does not match a given command name.
Because syntax macros are called for every command, they have
the ability to add new syntax to Tcl: they can inspect every
argument of the whole script and do substitutions on it. You can
do with syntax macros many syntax manipulations previously explored
with [unknown], with the following differences:
* Expand to native code. No performance penality.
* Don't need that the first argument is unknown. Always work.
* The expansion happens when the procedure is created, so it's possible to use syntax macros for general static checks.
[unknown] is very interesting to explore many [Radical Language Modification] ideas, but to add syntax in a reliable way and/or with
acceptable performances is often impossible.
I'll show syntax macros with the implementation of the following
syntax glue. Instead to write:
======
puts [expr {$a+$b}]
======
Let's the user write:
======
puts ($a+$b)
======
or
======
puts ( $a + $b )
======
or any mix. Well that's *NOT A GOOD IDEA* of course ;) see TIP 174 ([Math Operators as Commands])
to see another alternative to [expr], that I think is a good idea,
but this syntax glue it's a decent exercise for syntax macros:
======
sugar::syntaxmacro sugarmath args {
set argv $args
set start_idx -1
set end_idx -1
for {set i 0} {$i < [llength $argv]} {incr i} {
set tok [lindex $argv $i]
if {[string index $tok 0] eq {(}} {
set start_idx $i
}
}
if {$start_idx == -1} {
return $argv
}
set level 0
for {set i $start_idx} {$i < [llength $argv]} {incr i} {
set tok [lindex $argv $i]
foreach char [split $tok {}] {
if {$char eq {(}} {incr level}
if {$char eq {)}} {
incr level -1
}
}
if {$level == 0 && [string index $tok end] eq {)}} {
set end_idx $i
}
}
if {$end_idx == -1} {
return $argv
}
# The syntax looks ok, perform the expansion.
lset argv $start_idx "\[expr \{[string range [lindex $argv $start_idx] 1 end]"
lset argv $end_idx "[string range [lindex $argv $end_idx] 0 end-1]\}\]"
return $argv
}
======
That's it. With this macro the following code:
======
puts (($a+$b)*($a*$b))
lindex $mylist ($idx-1)
======
is translated to
======
puts [expr {($a+$b)*($a*$b)}]
lindex $mylist [expr {$idx-1}]
======
But you can't use it in string interpolation. Because this macro
expands to a command substitution form where actually there wasn't
one, it can't be used inside strings.
This will not work:
======
puts "($a+$b)"
======
Will just print ($a+$b) verbatim. Instead one should use:
======
puts "[format ($a+$b)]"
======
Third good thing about macros:
'''3) Macros allows to create syntax sugar without to cause cancer to the semicolon.'''
Macros are a good place where we can syntax-overload our preferite language
to specialize it and solve a specific problem with less typing. If, instead,
we add syntax to Tcl itself, we have a less general, more complex language,
that may make a task like the creation of a macro system much more hard.
Note that syntax sugar may expand to other macros that are recursively
processed, so adding syntax with macros does not limit the range of
actions of other macros.
'''More static source code checks'''
Another example of syntax macro is a macro to output a warning about
procedures called with a bad number of arguments. This is very short
and simple and will detect a bad usage only if the called procedure is
defined *before* of the bad usage (to detect bad usage of commands that
are defined later, it needs to be a bit more complex and to collect unresolved
calls in a global variable).
======
syntaxmacro aritychecker args {
set argv $args
set command [lindex $argv 0]
set ns [sugar::currentProcNamespace]
set procname $ns\::$command
if {[catch {info args $procname} cmdargs]} {
set procname $command
if {[catch {info args $procname} cmdargs]} {
return $argv
}
}
set call_argnum [expr {[llength $argv]-1}]
# Calculate the min and max number of arguments.
set max_argnum [llength $cmdargs]
set min_argnum [llength $cmdargs]
foreach a $cmdargs {
if {[info default $procname $a _]} {
incr min_argnum -1
} elseif {$a eq {args}} {
incr min_argnum -1
}
}
# If the last argument is 'args', set a fake max at 1000
if {[lindex $cmdargs end] eq {args}} {
set max_argnum 1000
}
# Check if it's ok.
if {$call_argnum < $min_argnum || $call_argnum > $max_argnum} {
puts stderr "*** Warning, procedure '$command' may be called with bad number of arugments in [sugar::currentProcName]"
}
return $argv
}
======
This kind of macros are pretty slow (until the parser is
implemented in C at least) being called for every command
in the script (possibly more times for every command, because
the script is re-processed if some macro expansion happened in
order to expand macros produced by macros), so it's better to
use it only during development, being not useful at all in
a finished program that's correct enough to don't produce
warnings.
Actually, if [Sugar] will be of some value in my day-to-day
programming (and I think it will), it will be reimplemented
in C once the API is stable. But for now I'll take in pure Tcl
to hack it faster if needed.
[TP] 2011/05/18 - Fixed above macros to work with the latest version of [Sugar].
Continue with '''[Sugar transformers]'''
<<categories>> Tutorial
|