Wiki
Download
Manual
Eggs
API
Tests
Bugs
show
edit
history
You can edit this page using
wiki syntax
for markup.
Article contents:
== Outdated egg! This is an egg for CHICKEN 4, the unsupported old release. You're almost certainly looking for [[/eggref/5/feature-test|the CHICKEN 5 version of this egg]], if it exists. If it does not exist, there may be equivalent functionality provided by another egg; have a look at the [[https://wiki.call-cc.org/chicken-projects/egg-index-5.html|egg index]]. Otherwise, please consider porting this egg to the current version of CHICKEN. [[tags:egg]] == feature-test '''feature-test''' provides foreign feature testing macros and read-syntax. This can be used to alter code at compile-time based on system support for a particular feature. [[toc:]] === Overview Feature testing is a time-honored tradition of C programmers who wish to write portable code. First, the programmer probes the system with a utility such as {{configure}}, generating a header file with {{#defines}} such as {{HAVE_IPV6}} or {{HAVE_ADDRINFO}}. The programmer may also manually generate a header file by examining the system (e.g. with {{#ifdef}}) and then defining appropriate {{HAVE_...}} macros. Second, the {{HAVE_...}} macros are used to select the appropriate code path at compile-time. If the feature test is simple or rare, the {{HAVE_...}} defines are often omitted. In Chicken, when writing code that interfaces to C, we can naturally use the same technique inside the C code itself. But what if we want to choose a different ''Scheme'' code path based on available system features? For example, say we want to choose between defining the following procedures, depending on whether IPv6-only socket binding is available: (define (ipv6-v6-only? s) (getsockopt s IPPROTO_IPV6 IPV6_V6ONLY)) (define (ipv6-v6-only? s) (error "IPv6 is not supported")) In that example, {{IPPROTO_IPV6}} and/or {{IPV6_V6ONLY}} may not even be {{#defined}} on our system, so we cannot simply create a foreign-variable referring to them, as it could result in a compilation error. Some possible approaches are: * Write the getsockopt call entirely in C with preprocessor tests that either execute the call or return an "unsupported" value to Scheme, and throw an error based on the result. This can be clumsy and involves a lot of special-case C code. * Ensure the {{#define}} is defined and set to an illegal value, such as {{-1}} if no value may be negative, or {{0}} in the case of a bitwise flag. Then we can always use a foreign-variable to access the value and select our code path at runtime. Quick and dirty, but sometimes good enough. However, it requires a safe illegal value to exist, and may leave a lot of unused or dead code lying around, which may or may not be optimized out by the C compiler. * Do the feature test externally (such as in your {{.setup}} file) and register a corresponding feature in {{csc}} using something like {{-Dv6only}}. Then use {{cond-expand}} in your code to define the correct procedure at compile time. Can get unwieldy, but does not involve runtime checks or unused C code. * Use the {{feature-test}} egg, which provides a compact and convenient way to do feature testing prior to Scheme compilation. Feature testing with the {{feature-test}} egg proceeds in two phases: # 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. # 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). === Determining feature support To use {{feature-test}}, you # Take any C preprocessor instructions from your main Scheme module and move them into separate include file(s) like {{myegg.h}}. Source this file from your module. # Create a Scheme features file such as {{myegg-features.scm}}, sourcing the same C header file(s) as your main module (here {{myegg.h}}). # Use directives from the {{feature-test}} module in {{myegg-features.scm}} to select which features you'd like visible to your module. # Compile and execute {{myegg-features.scm}}. This will generate a Scheme file on standard output, consisting of one line for each tested feature: either {{register-feature!}} if present, or {{unregister-feature!}} if absent. Redirect this output to {{myegg-config.scm}}. # Compile your main module, adding {{-X myegg-config.scm}} to {{csc}}. Features will be registered with the compiler before your code is compiled, so they are visible at read-time and compile-time. ==== feature-test interface <syntax>(declare-foreign-features FEATURE1 FEATURE2 ...)</syntax> For each feature F, tests whether F is {{#defined}} in C, and creates a new boolean {{#define}} reflecting this. This new {{#define}} is prefixed with the {{declaration-prefix}}. For example, using the (default) declaration prefix {{HAVE_}} and the feature {{AF_UNIX}}: (declaration-prefix HAVE_) (declare-foreign-features AF_UNIX) /* generates the C code */ #ifdef AF_UNIX #define HAVE_AF_UNIX 1 #else #define HAVE_AF_UNIX 0 #endif The boolean define {{HAVE_AF_UNIX}} is now safely visible to a {{foreign-variable}}. In contrast, referring to {{AF_UNIX}} from Scheme when undefined would result in a compilation error. <syntax>(register-foreign-features FEATURE1 FEATURE2 ...)</syntax> For each feature F, accesses the corresponding boolean {{#define}} in C, usually generated by {{declare-foreign-features}}. Then, generates code to register or unregister the feature for future compiles. The boolean define is prefixed with the current {{declaration-prefix}}, and the registered feature will be prefixed with the {{registration-prefix}}. For example: (declaration-prefix "HAVE_") (registration-prefix "MYEGG_") (register-foreign-features AF_UNIX) will expand to code like: (declare-foreign-variable HAVE_AF_UNIX bool "HAVE_AF_UNIX") (if HAVE_AF_UNIX (emit-register! 'MYEGG_AF_UNIX) (emit-unregister! 'MYEGG_AF_UNIX)) And when compiled and executed, the following is printed to standard output (register-feature! 'MYEGG_AF_UNIX) ;; if AF_UNIX was defined (unregister-feature! 'MYEGG_AF_UNIX) ;; if AF_UNIX was not defined <syntax>(define-foreign-features FEATURE1 FEATURE2 ...)</syntax> Equivalent to (declare-foreign-features FEATURE1 FEATURE2 ...) (register-foreign-features FEATURE1 FEATURE2 ...) <syntax>(declaration-prefix X)</syntax> Prefix added to the base feature name when declaring a foreign feature. This can be a string or a symbol. Defaults to {{HAVE_}}. <syntax>(registration-prefix X)</syntax> Prefix added to the base feature name when registering a foreign feature. This can be a string or a symbol. Defaults to the empty string. ==== feature-test example In this simple and contrived example, we test for the presence of {{IPPROTO_IPV6}} and {{IPV6_V6ONLY}} in C, and register or unregister the corresponding features in future compiles. On Windows 2000 and XP, for example, {{IPPROTO_IPV6}} is defined but {{IPV6_V6ONLY}} is not. Our test module {{mysock.scm}} allows you to create an IPv6 socket and test its IPv6 bind-only option status; if the option is unavailable, this is detected at compile time and the test is defined to throw a Scheme error. Our little {{socket6}} procedure makes some assumptions of its own, but it's only for illustration. A real example can be found in the source code to the [[/egg/socket|socket]] egg. <enscript highlight="c"> /* mysock.h */ #ifdef _WIN32 # include <winsock2.h> # include <ws2tcpip.h> #else # include <netinet/in.h> # include <sys/socket.h> #endif </enscript> <enscript highlight="scheme"> ;;; mysock-features.scm #> #include "mysock.h" <# (use feature-test) (declaration-prefix HAVE_) ;; Exact value not important here. (registration-prefix "") (define-foreign-features IPPROTO_IPV6 IPV6_V6ONLY) </enscript> <enscript highlight="scheme"> ;;; mysock.setup (compile mysock-features.scm) (run (./mysock-features > mysock-config.scm)) (compile -sJ -X mysock-config.scm mysock.scm) ;; [install-extension is omitted for our test] </enscript> <enscript highlight="scheme"> ;;; mysock.meta ;; Can be left blank for our example ;; but needs to exist even for chicken-install -n. </enscript> <enscript highlight="scheme"> ;;; mysock-config.scm (generated on linux) (register-feature! 'IPPROTO_IPV6) (register-feature! 'IPV6_V6ONLY) </enscript> <enscript highlight="scheme"> ;;; mysock-config.scm (generated on mingw32, pre-Vista) (register-feature! 'IPPROTO_IPV6) (unregister-feature! 'IPV6_V6ONLY) </enscript> <enscript highlight="scheme"> ;;; mysock.scm #> #include "mysock.h" <# (module mysock (ipv6-v6-only? socket6) (import scheme chicken foreign) ;; Get integer socket option NAME on fd SOCK at LEVEL (define getsockopt (foreign-lambda* int ((int sock) (int level) (int name)) "int ret; socklen_t sz = sizeof(ret);" "if (getsockopt(sock, level, name, (void *)&ret, &sz) < 0)" " C_return(-1);" "C_return(ret);")) ;; Create an IPv6 TCP socket and return its file descriptor ;; for testing purposes. Assume AF_INET6 and SOCK_STREAM are defined. (define socket6 (foreign-lambda* int () "C_return(socket(AF_INET6,SOCK_STREAM,0));")) (cond-expand ((and IPPROTO_IPV6 IPV6_V6ONLY) (define-foreign-variable _ipproto_ipv6 int "IPPROTO_IPV6") (define-foreign-variable _ipv6_v6only int "IPV6_V6ONLY") (define (ipv6-v6-only? s) (getsockopt s _ipproto_ipv6 _ipv6_v6only))) (else (define (ipv6-v6-only? s) (error "IPv6 only binding is not supported"))))) </enscript> <enscript highlight="shell"> ### Build $ chicken-install -n mysock.setup ### Test on UNIX $ csi -R mysock -p "(ipv6-v6-only? (socket6))" 0 ### Test on Windows XP > csi -R mysock -p "(ipv6-v6-only? (socket6))" Error: IPv6 only binding is not supported </enscript> === Acting on feature support {{cond-expand}} is the usual way to test and act on feature support. However, {{cond-expand}} works at macroexpansion time, as does the default {{#+}} reader macro. Therefore, it generally cannot be used inside macros. To address this, reader macros that do feature testing at read-time are provided in the {{feature-test-syntax}} extension. Use it like: csc -X feature-test-syntax myegg.scm <read>#+</read> #+FEATURE EXPR Test {{FEATURE}} at read-time and, if present, expand to {{EXPR}}. {{FEATURE}} may be any feature expression permitted in a {{cond-expand}}, such as {{windows}} or {{(and windows macosx)}}. {{#+}} can be used inside macros because it is expanded when the macro form is read, prior to macroexpansion. However, this requires a Chicken version >= 4.6.7, which will omit {{EXPR}} if the feature test is false. In earlier versions, the test expands to a {{(void)}} form, like the built-in {{#+}}. {{(void)}} forms are usually illegal inside macro bodies. Here is an example that assumes Chicken is at least 4.6.7, which will omit {{EXPR}} on a false test: (cond ((eq? x _af_inet) "internet address family") #+AF_UNIX ((eq? x _af_unix) "unix address family") (else "unknown address family") If {{AF_UNIX}} is a registered feature at compile-time, it will be read as: (cond ((eq? x _af_inet) "internet address family") ((eq? x _af_unix) "unix address family") (else "unknown address family") If {{AF_UNIX}} is not a registered feature, it will be read as: (cond ((eq? x _af_inet) "internet address family") (else "unknown address family") However, if {{AF_UNIX}} is unregistered ''and'' you are using Chicken prior to 4.6.7, it will instead expand into the illegal: (cond ((eq? x _af_inet) "internet address family") (##core#undefined) ((eq? x _af_unix) "unix address family") (else "unknown address family") So be careful. <read>#-</read> Like {{#+}}, but of opposite polarity. <read>#?</read> #?(FEATURE CONSEQUENT ALTERNATE) Perform an if-then test at read-time on {{FEATURE}}, expanding to {{CONSEQUENT}} if {{FEATURE}} is present or {{ALTERNATE}} if absent. {{FEATURE}} may be any feature expression permitted in a {{cond-expand}}, such as {{windows}} or {{(and windows macosx)}}. {{#?}} can be used inside macros because it is expanded when the macro form is read, prior to macroexpansion. It expands correctly irrespective of Chicken version. {{#?}} is similar to the Common Lisp idiom #+FEATURE CONSEQUENT #-FEATURE ALTERNATE and, in Chicken versions >= 4.6.7 it is exactly equivalent, even inside macro bodies. However, in previous versions {{#+}} and {{#-}} will not work properly inside macros; see {{#+}} for further explanation. An example of {{#?}} which is essentially equivalent to {{cond-expand}}: (define af/unix #?(AF_UNIX _af_unix #f)) ;; is basically the same as (define af/unix (cond-expand (AF_UNIX _af_unix) (else #f))) A more powerful example of {{#?}}: (cond ((eq? x _af_inet) "internet address family") #?(AF_UNIX ((eq? x _af_unix) "unix address family") (#f)) (else "unknown address family") which, if {{AF_UNIX}} is a registered feature, expands into (cond ((eq? x _af_inet) "internet address family") ((eq? x _af_unix) "unix address family") (else "unknown address family") and if not, expands into (cond ((eq? x _af_inet) "internet address family") (#f) (else "unknown address family") In the latter case, the false clause cannot succeed and is hopefully optimized out by the compiler. {{#+}} would be more appropriate, but requires Chicken >= 4.6.7. This technique doesn't work with every macro, but you do what you can. === Bugs and limitations * As if it hasn't been repeated enough times, you need Chicken 4.6.7 to safely use {{#+}} and {{#-}} read syntax within macro bodies. Barring that, stick to {{#?}}. === About this egg ==== Source [[https://github.com/ursetto/feature-test-egg]] ==== Author [[http://3e8.org|Jim Ursetto]] ==== Version history ; 0.2.0 : Chicken 5 support ; 0.1.1 Internal release-info fix ; 0.1 : Initial release ==== License Copyright (c) 2011-2019 Jim Ursetto. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Description of your changes:
I would like to authenticate
Authentication
Username:
Password:
Spam control
What do you get when you add 22 to 10?