~ubuntu-branches/ubuntu/vivid/critcl/vivid

« back to all changes in this revision

Viewing changes to doc/include/using_etclfallback.inc

  • Committer: Package Import Robot
  • Author(s): Andrew Shadura
  • Date: 2013-05-11 00:08:06 UTC
  • Revision ID: package-import@ubuntu.com-20130511000806-7hq1zc3fnn0gat79
Tags: upstream-3.1.9
ImportĀ upstreamĀ versionĀ 3.1.9

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
[subsection {Using C with Tcl functionality as fallback}]
 
2
 
 
3
There is one special case of
 
4
[sectref {Having both C and Tcl functionality}]
 
5
which deserves its own section.
 
6
 
 
7
[para] The possibility of not having the fast C code on some platform,
 
8
and using a slower Tcl implementation of the functionality. In other
 
9
words, a fallback which keeps the package working in the face of
 
10
failure to build the C parts. A more concrete example of this would be
 
11
a module implementing the SHA hash, in both C and Tcl, and using the
 
12
latter if and only if the C implementation is not available.
 
13
 
 
14
[para] There two major possibilities in handling such a situation.
 
15
 
 
16
[list_begin enumerated]
 
17
 
 
18
[comment {======================================================}]
 
19
[enum] Keep all the pieces separated. In that scenario our concrete
 
20
example would be spread over three packages. Two low-level packages
 
21
[package sha::c] and [package sha::tcl] containing the two
 
22
implementations of the algorithm, and, thirdly, a coordinator package
 
23
[package sha] which loads either of them, based on availability.
 
24
 
 
25
[para] The Tcllib bundle of packages contains a number of packages
 
26
structured in this manner, mostly in the [term struct] module.
 
27
 
 
28
[para] Writing the C and Tcl parts should be simple by now, with all
 
29
the examples we had so far. The only non-trivial part is the
 
30
coordinator, and even that if and only if we wish to make it easy to
 
31
write a testsuite which can check both branches, C, and Tcl without
 
32
gymnastics. So, the most basic coordinator would be
 
33
 
 
34
[example {
 
35
    set sha::version 1
 
36
    if {[catch {
 
37
        package require sha::c $sha::version
 
38
    }]} {
 
39
        package require sha::tcl $sha::version
 
40
    }
 
41
    package provide sha $sha::version
 
42
}]
 
43
 
 
44
It tries to load the C implementation first, and falls back to the Tcl
 
45
implementation if that fails. The code as is assumes that both
 
46
implementations create exactly the same command names, leaving the
 
47
caller unaware of the choice of implementations.
 
48
 
 
49
[para] A concrete example of this scheme can be found in Tcllib's
 
50
[package md5] package. While it actually uses ythe [package Trf] as
 
51
its accelerator, and not a critcl-based package the principle is the
 
52
same. It also demonstrates the need for additional glue code when the
 
53
C implementation doesn't exactly match the signature and semantics of
 
54
the Tcl implementation.
 
55
 
 
56
[para] This basic coordinator can be easily extended to try more than
 
57
two packages to get the needed implementation. for example, the C
 
58
implementation may not just exist in a sha::c package, but also
 
59
bundled somewhere else. Tcllib, for example, has a tcllibc package
 
60
which bundles all the C parts of its packages which have them in a
 
61
single binary.
 
62
 
 
63
[para] Another direction to take it in is to write code which allows
 
64
the loading of multiple implementations at the same time, and then
 
65
switching between them at runtime. Doing this requires effort to keep
 
66
the implementations out of each others way, i.e. they cannot provide
 
67
the same command names anymore, and a more complex coordinator as
 
68
well, which is able to map from the public command names to whatever
 
69
is provided by the implementation.
 
70
 
 
71
[para] The main benefit of this extension is that it makes testing the
 
72
two different implementations easier, simply run through the same set
 
73
of tests multiple times, each time with different implementation
 
74
active. The disadvantage is the additional complexity of the
 
75
coordinator's internals. As a larger example of this technique here is
 
76
the coordinator [file modules/struct/queue.tcl] handling the C and Tcl
 
77
implementations of Tcllib's [package struct::queue] package:
 
78
 
 
79
[example {
 
80
    # queue.tcl --
 
81
    #       Implementation of a queue data structure for Tcl.
 
82
 
 
83
    package require Tcl 8.4
 
84
    namespace eval ::struct::queue {}
 
85
 
 
86
    ## Management of queue implementations.
 
87
 
 
88
    # ::struct::queue::LoadAccelerator --
 
89
    #       Loads a named implementation, if possible.
 
90
 
 
91
    proc ::struct::queue::LoadAccelerator {key} {
 
92
        variable accel
 
93
        set r 0
 
94
        switch -exact -- $key {
 
95
            critcl {
 
96
                # Critcl implementation of queue requires Tcl 8.4.
 
97
                if {![package vsatisfies [package provide Tcl] 8.4]} {return 0}
 
98
                if {[catch {package require tcllibc}]} {return 0}
 
99
                set r [llength [info commands ::struct::queue_critcl]]
 
100
            }
 
101
            tcl {
 
102
                variable selfdir
 
103
                if {
 
104
                    [package vsatisfies [package provide Tcl] 8.5] &&
 
105
                    ![catch {package require TclOO}]
 
106
                } {
 
107
                    source [file join $selfdir queue_oo.tcl]
 
108
                } else {
 
109
                    source [file join $selfdir queue_tcl.tcl]
 
110
                }
 
111
                set r 1
 
112
            }
 
113
            default {
 
114
                return -code error "invalid accelerator/impl. package $key:\
 
115
                    must be one of [join [KnownImplementations] {, }]"
 
116
            }
 
117
        }
 
118
        set accel($key) $r
 
119
        return $r
 
120
    }
 
121
 
 
122
    # ::struct::queue::SwitchTo --
 
123
    #       Activates a loaded named implementation.
 
124
 
 
125
    proc ::struct::queue::SwitchTo {key} {
 
126
        variable accel
 
127
        variable loaded
 
128
 
 
129
        if {[string equal $key $loaded]} {
 
130
            # No change, nothing to do.
 
131
            return
 
132
        } elseif {![string equal $key ""]} {
 
133
            # Validate the target implementation of the switch.
 
134
 
 
135
            if {![info exists accel($key)]} {
 
136
                return -code error "Unable to activate unknown implementation \"$key\""
 
137
            } elseif {![info exists accel($key)] || !$accel($key)} {
 
138
                return -code error "Unable to activate missing implementation \"$key\""
 
139
            }
 
140
        }
 
141
 
 
142
        # Deactivate the previous implementation, if there was any.
 
143
 
 
144
        if {![string equal $loaded ""]} {
 
145
            rename ::struct::queue ::struct::queue_$loaded
 
146
        }
 
147
 
 
148
        # Activate the new implementation, if there is any.
 
149
 
 
150
        if {![string equal $key ""]} {
 
151
            rename ::struct::queue_$key ::struct::queue
 
152
        }
 
153
 
 
154
        # Remember the active implementation, for deactivation by future
 
155
        # switches.
 
156
 
 
157
        set loaded $key
 
158
        return
 
159
    }
 
160
 
 
161
    # ::struct::queue::Implementations --
 
162
    #       Determines which implementations are
 
163
    #       present, i.e. loaded.
 
164
 
 
165
    proc ::struct::queue::Implementations {} {
 
166
        variable accel
 
167
        set res {}
 
168
        foreach n [array names accel] {
 
169
            if {!$accel($n)} continue
 
170
            lappend res $n
 
171
        }
 
172
        return $res
 
173
    }
 
174
 
 
175
    # ::struct::queue::KnownImplementations --
 
176
    #       Determines which implementations are known
 
177
    #       as possible implementations.
 
178
 
 
179
    proc ::struct::queue::KnownImplementations {} {
 
180
        return {critcl tcl}
 
181
    }
 
182
 
 
183
    proc ::struct::queue::Names {} {
 
184
        return {
 
185
            critcl {tcllibc based}
 
186
            tcl    {pure Tcl}
 
187
        }
 
188
    }
 
189
 
 
190
    ## Initialization: Data structures.
 
191
 
 
192
    namespace eval ::struct::queue {
 
193
        variable  selfdir [file dirname [info script]]
 
194
        variable  accel
 
195
        array set accel   {tcl 0 critcl 0}
 
196
        variable  loaded  {}
 
197
    }
 
198
 
 
199
    ## Initialization: Choose an implementation,
 
200
    ## most preferred first. Loads only one of the
 
201
    ## possible implementations. And activates it.
 
202
 
 
203
    namespace eval ::struct::queue {
 
204
        variable e
 
205
        foreach e [KnownImplementations] {
 
206
            if {[LoadAccelerator $e]} {
 
207
                SwitchTo $e
 
208
                break
 
209
            }
 
210
        }
 
211
        unset e
 
212
    }
 
213
 
 
214
    ## Ready
 
215
 
 
216
    namespace eval ::struct {
 
217
        # Export the constructor command.
 
218
        namespace export queue
 
219
    }
 
220
 
 
221
    package provide struct::queue 1.4.2
 
222
}]
 
223
 
 
224
In this implementation the coordinator renames the commands of the
 
225
low-level packages to the public commands, making the future dispatch
 
226
as fast as if the commands had these names anyway, but also forcing a
 
227
spike of bytecode recompilation if switching is ever done at the
 
228
runtime of an application, and not just used for testing, and possibly
 
229
disrupting introspection by the commands, especially if they move
 
230
between different namespaces.
 
231
 
 
232
[para] A different implementation would be to provide the public
 
233
commands as procedures which consult a variable to determine which of
 
234
the loaded implementations is active, and then call on its
 
235
commands. This doesn't disrupt introspection, nor does it trigger
 
236
bytecode recompilation on switching. But it takes more time to
 
237
dispatch to the actual implementation, in every call of the public API
 
238
for the package in question.
 
239
 
 
240
[para] A concrete example of this scheme can be found in Tcllib's
 
241
[package crc32] package.
 
242
 
 
243
[comment {======================================================}]
 
244
[enum] Mix the pieces together. Please note that while I am describing
 
245
how to make this work I strongly prefer and recommend to use the
 
246
previously shown approach using separate files/packages. It is much
 
247
easier to understand and maintain. With this warning done, lets go
 
248
into the nuts and bolts.
 
249
 
 
250
[para] If we care only about mode "compile & run" things are easy:
 
251
 
 
252
[example {
 
253
    package require critcl
 
254
 
 
255
    if {![critcl::compiling]} {
 
256
        proc mycommand {...} {
 
257
            ...
 
258
        }
 
259
 
 
260
    } else {
 
261
        critcl::cproc mycommand {...} {
 
262
            ...
 
263
        }
 
264
    }
 
265
}]
 
266
 
 
267
The command [cmd critcl::compiling] tells us whether we have a
 
268
compiler available or not, and in the latter case we implement our
 
269
command in Tcl.
 
270
 
 
271
[para] Now what happens when we invoke mode "generate package" ?
 
272
 
 
273
... compiler failure ...
 
274
... ok   - C code - everything fine
 
275
... fail - no package ? or just no C code ? declare self as tsource, to be used ?
 
276
... platform-specific C/Tcl -- uuid.
 
277
 
 
278
[list_end]
 
279
 
 
280
 
 
281