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

Last change on this file since 26112 was 26112, checked in by sjamaan, 7 years ago

Fix feature-test page title (not "Overview")

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