File: Sugar_syntax_macros.txt

package info (click to toggle)
tcl-sugar 0.1-1
  • links: PTS
  • area: main
  • in suites: buster, jessie, jessie-kfreebsd, stretch
  • size: 176 kB
  • ctags: 29
  • sloc: tcl: 647; sh: 13; makefile: 2
file content (180 lines) | stat: -rw-r--r-- 6,159 bytes parent folder | download | duplicates (2)
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