source: project/wiki/eggref/4/feature-test @ 22913

Last change on this file since 22913 was 22913, checked in by zbigniew, 8 years ago

might want to add a TOC, dummy

File size: 14.7 KB
Line 
1[[tags:egg]]
2
3'''feature-test''' provides foreign feature testing macros and read-syntax.  This can
4be used to alter code at compile-time based on system support for a particular feature.
5
6[[toc:]]
7
8== Overview
9
10Feature testing is a time-honored tradition of C programmers who wish
11to write portable code.
12
13First, the programmer probes the system with a utility such as
14{{configure}}, generating a header file with {{#defines}} such as
15{{HAVE_IPV6}} or {{HAVE_ADDRINFO}}.  The programmer may also manually
16generate a header file by examining the system (e.g. with {{#ifdef}})
17and then defining appropriate {{HAVE_...}} macros.
18
19Second, the {{HAVE_...}} macros are used to select the appropriate
20code path at compile-time.  If the feature test is simple or rare, the
21{{HAVE_...}} defines are often omitted.
22
23In Chicken, when writing code that interfaces to C, we can naturally
24use the same technique inside the C code itself.  But what if we want
25to choose a different ''Scheme'' code path based on available system
26features?  For example, say we want to choose between defining the
27following procedures, depending on whether IPv6-only socket binding is
28available:
29
30 (define (ipv6-v6-only? s) (getsockopt s IPPROTO_IPV6 IPV6_V6ONLY))
31 (define (ipv6-v6-only? s) (error "IPv6 is not supported"))
32
33In that example, {{IPPROTO_IPV6}} and/or {{IPV6_V6ONLY}} may not even be {{#defined}}
34on our system, so we cannot simply create a foreign-variable referring
35to them, as it could result in a compilation error.
36
37Some possible approaches are:
38
39* Write the getsockopt call entirely in C with preprocessor tests that
40  either execute the call or return an "unsupported" value to Scheme,
41  and throw an error based on the result.  This can be clumsy
42  and involves a lot of special-case C code.
43* Ensure the {{#define}} is defined and set to an illegal value, such
44  as {{-1}} if no value may be negative, or {{0}} in the case of
45  a bitwise flag.  Then we can always use a foreign-variable to
46  access the value and select our code path at runtime.  Quick and dirty,
47  but sometimes good enough.  However, it requires a safe illegal
48  value to exist, and may leave a lot of unused or dead code lying around,
49  which may or may not be optimized out by the C compiler.
50* Do the feature test externally (such as in your {{.setup}} file) and register
51  a corresponding feature in {{csc}} using something like {{-Dv6only}}.
52  Then use {{cond-expand}} in your code to define the correct procedure
53  at compile time.  Can get unwieldy, but does not involve runtime checks
54  or unused C code.
55* Use the {{feature-test}} egg, which provides a compact and convenient way
56  to do feature testing prior to Scheme compilation.
57
58Feature testing with the {{feature-test}} egg proceeds in two phases:
59
60# Determine which foreign features are supported by your system by using C header files and the {{feature-test}} extension to register features before compile-time.
61
62# Alter your generated Scheme code by using {{cond-expand}} (effective at compile-time), or by using read syntax from the {{feature-test-syntax}} extension (effective at read-time).
63
64== Determining feature support
65
66To use {{feature-test}}, you
67
68# Take any C preprocessor instructions from your main Scheme module
69  and move them into separate include file(s) like {{myegg.h}}.
70  Source this file from your module.
71# Create a Scheme features file such as {{myegg-features.scm}},
72  sourcing the same C header file(s) as your main module (here {{myegg.h}}).
73# Use directives from the {{feature-test}} module in {{myegg-features.scm}}
74  to select which features you'd like visible to your module.
75# Compile and execute {{myegg-features.scm}}.  This will generate a Scheme
76  file on standard output, consisting of one line for each tested feature:
77  either {{register-feature!}} if present, or {{unregister-feature!}}
78  if absent.  Redirect this output to {{myegg-config.scm}}.
79# Compile your main module, adding {{-X myegg-config.scm}} to {{csc}}.
80  Features will be registered with the compiler before your code
81  is compiled, so they are visible at read-time and compile-time.
82
83=== feature-test interface
84
85<syntax>(declare-foreign-features FEATURE1 FEATURE2 ...)</syntax>
86
87For each feature F, tests whether F is {{#defined}} in C, and creates
88a new boolean {{#define}} reflecting this.  This new {{#define}} is prefixed
89with the {{declaration-prefix}}.
90
91For example, using the (default) declaration prefix {{HAVE_}} and the
92feature {{AF_UNIX}}:
93
94 (declaration-prefix HAVE_)
95 (declare-foreign-features AF_UNIX)
96
97 /* generates the C code */
98 #ifdef AF_UNIX
99 #define HAVE_AF_UNIX 1
100 #else
101 #define HAVE_AF_UNIX 0
102 #endif
103
104The boolean define {{HAVE_AF_UNIX}} is now safely visible to a
105{{foreign-variable}}.  In contrast, referring to {{AF_UNIX}} from
106Scheme when undefined would result in a compilation error.
107
108<syntax>(register-foreign-features FEATURE1 FEATURE2 ...)</syntax>
109
110For each feature F, accesses the corresponding boolean {{#define}} in
111C, usually generated by {{declare-foreign-features}}.  Then, generates
112code to register or unregister the feature for future compiles.
113
114The boolean define is prefixed with the current
115{{declaration-prefix}}, and the registered feature will be prefixed
116with the {{registration-prefix}}.
117
118For example:
119
120 (declaration-prefix "HAVE_")
121 (registration-prefix "MYEGG_")
122 (register-foreign-features AF_UNIX)
123
124will expand to code like:
125
126 (declare-foreign-variable HAVE_AF_UNIX bool "HAVE_AF_UNIX")
127 (if HAVE_AF_UNIX (emit-register!   'MYEGG_AF_UNIX)
128                  (emit-unregister! 'MYEGG_AF_UNIX))
129
130And when compiled and executed, the following is printed
131to standard output
132
133 (register-feature! 'MYEGG_AF_UNIX)     ;; if AF_UNIX was defined
134 (unregister-feature! 'MYEGG_AF_UNIX)   ;; if AF_UNIX was not defined
135
136<syntax>(define-foreign-features FEATURE1 FEATURE2 ...)</syntax>
137
138Equivalent to
139
140 (declare-foreign-features FEATURE1 FEATURE2 ...)
141 (register-foreign-features FEATURE1 FEATURE2 ...)
142
143<syntax>(declaration-prefix X)</syntax>
144
145Prefix added to the base feature name when declaring a foreign feature.
146This can be a string or a symbol.
147
148Defaults to {{HAVE_}}.
149
150<syntax>(registration-prefix X)</syntax>
151
152Prefix added to the base feature name when registering a foreign feature.
153This can be a string or a symbol.
154
155Defaults to the empty string.
156
157=== feature-test example
158
159In this simple and contrived example, we test for the presence of
160{{IPPROTO_IPV6}} and {{IPV6_V6ONLY}} in C, and register or unregister
161the corresponding features in future compiles.  On Windows 2000 and
162XP, for example, {{IPPROTO_IPV6}} is defined but {{IPV6_V6ONLY}} is
163not.
164
165Our test module {{mysock.scm}} allows you to create an IPv6 socket and
166test its IPv6 bind-only option status; if the option is unavailable,
167this is detected at compile time and the test is defined to throw a
168Scheme error.  Our little {{socket6}} procedure makes some assumptions
169of its own, but it's only for illustration.
170
171A real example can be found in the source code to the
172[[/egg/socket|socket]] egg.
173
174<enscript highlight="c">
175/* mysock.h */
176#ifdef _WIN32
177# include <winsock2.h>
178# include <ws2tcpip.h>
179#else
180# include <netinet/in.h>
181# include <sys/socket.h>
182#endif
183</enscript>
184
185<enscript highlight="scheme">
186;;; mysock-features.scm
187#> #include "mysock.h" <#
188(use feature-test)
189(declaration-prefix HAVE_)   ;; Exact value not important here.
190(registration-prefix "")
191(define-foreign-features IPPROTO_IPV6 IPV6_V6ONLY)
192</enscript>
193
194<enscript highlight="scheme">
195;;; mysock.setup
196(compile mysock-features.scm)
197(run (./mysock-features > mysock-config.scm))
198(compile -sJ -X mysock-config.scm mysock.scm)
199;; [install-extension is omitted for our test]
200</enscript>
201
202<enscript highlight="scheme">
203;;; mysock.meta
204;; Can be left blank for our example
205;; but needs to exist even for chicken-install -n.
206</enscript>
207
208<enscript highlight="scheme">
209;;; mysock-config.scm (generated on linux)
210(register-feature! 'IPPROTO_IPV6)
211(register-feature! 'IPV6_V6ONLY)
212</enscript>
213
214<enscript highlight="scheme">
215;;; mysock-config.scm (generated on mingw32, pre-Vista)
216(register-feature! 'IPPROTO_IPV6)
217(unregister-feature! 'IPV6_V6ONLY)
218</enscript>
219
220<enscript highlight="scheme">
221;;; mysock.scm
222#> #include "mysock.h" <#
223(module mysock (ipv6-v6-only? socket6)
224 (import scheme chicken foreign)
225 ;; Get integer socket option NAME on fd SOCK at LEVEL
226 (define getsockopt
227  (foreign-lambda* int ((int sock) (int level) (int name))
228                   "int ret; socklen_t sz = sizeof(ret);"
229                   "if (getsockopt(sock, level, name, (void *)&ret, &sz) < 0)"
230                   "  C_return(-1);"
231                   "C_return(ret);"))
232 ;; Create an IPv6 TCP socket and return its file descriptor
233 ;; for testing purposes.  Assume AF_INET6 and SOCK_STREAM are defined.
234 (define socket6 (foreign-lambda* int ()
235                   "C_return(socket(AF_INET6,SOCK_STREAM,0));"))
236
237 (cond-expand
238  ((and IPPROTO_IPV6 IPV6_V6ONLY)
239   (define-foreign-variable _ipproto_ipv6 int "IPPROTO_IPV6")
240   (define-foreign-variable _ipv6_v6only int "IPV6_V6ONLY")
241   (define (ipv6-v6-only? s)
242     (getsockopt s _ipproto_ipv6 _ipv6_v6only)))
243  (else
244   (define (ipv6-v6-only? s)
245     (error "IPv6 only binding is not supported")))))
246</enscript>
247
248<enscript highlight="shell">
249### Build
250$ chicken-install -n mysock.setup
251
252### Test on UNIX
253$ csi -R mysock -p "(ipv6-v6-only? (socket6))"
2540
255
256### Test on Windows XP
257> csi -R mysock -p "(ipv6-v6-only? (socket6))"
258Error: IPv6 only binding is not supported
259</enscript>
260
261== Acting on feature support
262
263{{cond-expand}} is the usual way to test and act on feature support.
264However, {{cond-expand}} works at macroexpansion time, as does the
265default {{#+}} reader macro.  Therefore, it generally cannot be
266used inside macros.
267
268To address this, reader macros that do feature testing at read-time
269are provided in the {{feature-test-syntax}} extension.  Use it like:
270
271 csc -X feature-test-syntax myegg.scm
272
273<read>#+</read>
274
275 #+FEATURE EXPR
276
277Test {{FEATURE}} at read-time and, if present, expand to {{EXPR}}.
278{{FEATURE}} may be any feature expression permitted in a
279{{cond-expand}}, such as {{windows}} or {{(and windows macosx)}}.
280
281{{#+}} can be used inside macros because it is expanded when the
282macro form is read, prior to macroexpansion.  However, this requires
283a Chicken version >= 4.6.7, which will omit {{EXPR}} if the
284feature test is false.  In earlier versions, the test expands
285to a {{(void)}} form, like the built-in {{#+}}.  {{(void)}} forms are
286usually illegal inside macro bodies.
287
288Here is an example that assumes Chicken is at least 4.6.7, which will
289omit {{EXPR}} on a false test:
290
291 (cond ((eq? x _af_inet) "internet address family")
292       #+AF_UNIX
293       ((eq? x _af_unix) "unix address family")
294       (else "unknown address family")
295
296If {{AF_UNIX}} is a registered feature at compile-time, it will be read
297as:
298
299 (cond ((eq? x _af_inet) "internet address family")
300       ((eq? x _af_unix) "unix address family")
301       (else "unknown address family")
302
303If {{AF_UNIX}} is not a registered feature, it will be read as:
304
305 (cond ((eq? x _af_inet) "internet address family")
306       (else "unknown address family")
307
308However, if {{AF_UNIX}} is unregistered ''and'' you are using
309Chicken prior to 4.6.7, it will instead expand into the illegal:
310
311 (cond ((eq? x _af_inet) "internet address family")
312       (##core#undefined)
313       ((eq? x _af_unix) "unix address family")
314       (else "unknown address family")
315
316So be careful.
317
318<read>#-</read>
319
320Like {{#+}}, but of opposite polarity.
321
322<read>#?</read>
323
324 #?(FEATURE CONSEQUENT ALTERNATE)
325
326Perform an if-then test at read-time on {{FEATURE}}, expanding
327to {{CONSEQUENT}} if {{FEATURE}} is present or {{ALTERNATE}}
328if absent.  {{FEATURE}} may be any feature expression permitted in a
329{{cond-expand}}, such as {{windows}} or {{(and windows macosx)}}.
330
331{{#?}} can be used inside macros because it is expanded when the
332macro form is read, prior to macroexpansion.  It expands correctly
333irrespective of Chicken version.
334
335{{#?}} is similar to the Common Lisp idiom
336
337 #+FEATURE CONSEQUENT
338 #-FEATURE ALTERNATE
339
340and, in Chicken versions >= 4.6.7 it is exactly equivalent, even
341inside macro bodies.  However, in previous versions {{#+}} and {{#-}}
342will not work properly inside macros; see {{#+}} for further
343explanation.
344
345An example of {{#?}} which is essentially equivalent to
346{{cond-expand}}:
347
348 (define af/unix #?(AF_UNIX _af_unix #f))
349 ;; is basically the same as
350 (define af/unix (cond-expand (AF_UNIX _af_unix) (else #f)))
351
352A more powerful example of {{#?}}:
353
354 (cond ((eq? x _af_inet) "internet address family")
355       #?(AF_UNIX
356          ((eq? x _af_unix) "unix address family")
357          (#f))
358       (else "unknown address family")
359
360which, if {{AF_UNIX}} is a registered feature, expands into
361
362 (cond ((eq? x _af_inet) "internet address family")
363       ((eq? x _af_unix) "unix address family")
364       (else "unknown address family")
365
366and if not, expands into
367
368 (cond ((eq? x _af_inet) "internet address family")
369       (#f)
370       (else "unknown address family")
371
372In the latter case, the false clause cannot succeed and is hopefully
373optimized out by the compiler. {{#+}} would be more appropriate, but
374requires Chicken >= 4.6.7.  This technique doesn't work with every
375macro, but you do what you can.
376
377== Bugs and limitations
378
379* As if it hasn't been repeated enough times, you need Chicken 4.6.7
380to safely use {{#+}} and {{#-}} read syntax within macro bodies.
381Barring that, stick to {{#?}}.
382
383== About this egg
384
385=== Author
386
387[[http://3e8.org|Jim Ursetto]]
388
389=== Version history
390
391; 0.1 : Initial release
392
393=== License
394
395 Copyright (c) 2011 Jim Ursetto.  All rights reserved.
396 
397 Redistribution and use in source and binary forms, with or without
398 modification, are permitted provided that the following conditions are met:
399 
400   Redistributions of source code must retain the above copyright notice,
401   this list of conditions and the following disclaimer. Redistributions in
402   binary form must reproduce the above copyright notice, this list of
403   conditions and the following disclaimer in the documentation and/or
404   other materials provided with the distribution. Neither the name of the
405   author nor the names of its contributors may be used to endorse or
406   promote products derived from this software without specific prior
407   written permission.
408 
409 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
410 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
411 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
412 PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
413 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
414 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
415 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
416 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
417 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
418 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
419 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
420
Note: See TracBrowser for help on using the repository browser.