[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Q interpreter provides an interface to the C programming language,
which allows you to extend the interpreter with your own collections of
"built-ins" in C modules, in order to access functions in C libraries
and to take advantage of the higher processing speed of C functions for
time-critical applications. We also refer to such C modules interfacing
to the Q interpreter as external modules. On most systems
supporting "shared" libraries (a.k.a. "dll"s), external modules are
loaded dynamically at runtime, otherwise they have to be linked
statically with the interpreter's main program to create a new
interpreter executable. The Q interpreter has its own external module
support for MS Windows; on UNIX systems, it uses the libtool
package to implement this functionality. See GNU Libtool: (libtool)Top section `Shared library support for GNU' in GNU Libtool.
Conversely, Q scripts can also be executed from C, which allows you to use Q as an embedded macro or programming language or a term rewriting engine in your C/C++ applications.
In the following we first discuss Q's C module interface, libq
,
in some detail. In the final section of this appendix we then give a
brief description of the libqint
interface which is used to embed
Q in C/C++ applications.
NOTE: As of version 6.0, Q now also has support for SWIG, the "Simplified Wrapper and Interface Generator", see http://www.swig.org. SWIG considerably simplifies the process of interfacing to existing C and C++ code. This feature still needs to be documented; for the time being, please refer to the README-SWIG file in the distribution for more information.
C.1 Compiling a Module | ||
C.2 Writing a Module | ||
C.3 Linking and Debugging a Module | ||
C.4 Embedding Q in C/C++ Applications |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
To provide for a platform-independent way of compiling and linking
external modules, the Q programming system includes two tiny additional
utilities which take care of the necessary and sometimes messy
compilation details: qcc
and qld
.
Qcc
is the module compiler. After you have prepared an external
module (as explained in Writing a Module), you run qcc
to
compile your module to a library, a binary file which can be
loaded by or linked into the interpreter.
The qcc
program will compile each given source file and then
invoke the linker on all object files to produce the output library. The
synopsis of the qcc
program is as follows:
qcc [options] [source-or-object-file …] [-- cc-options … [--link ld-options…]] |
As indicated, qcc
can process both C source and object files, and
extra options can be passed on to compiler and linker after the
--
option. All options understood by the qcc
utility are
listed below:
--dry-run, -n
Only print compilation commands, do not actually execute them.
--ext
Print the default output file extension and exit. This is usually
`.la' on UNIX systems which denotes a libtool
library, or
`.dll' on Windows.
--help, -h
Print a short help message and exit.
--keep, -k
Do not delete intermediate files (object files and such) produced during the compilation process.
--mingw
Select the mingw compiler driver. Mingw is the native Windows port of the well-known GNU C compiler, gcc. This option is the default for dll compilation on MS Windows.
--msc
Select the MS Visual C compiler driver. Use this if you want to compile your module with the Visual C compiler on Windows.
--output=file, -o file
Specify the name of the library output file.
--verbose, -v
Echo compilation commands as they are processed.
--version, -V
As with the other Q programming utilities, this option causes version number and copyright information to be displayed, after which the program terminates.
The Q module linker, qld
, is implemented as a shell script (thus
it is not supported on Windows) which simply invokes libtool
with
the specified -dlopen
and -dlpreopen
options to create a
new interpreter executable. This is only necessary if your system does
not support shared libraries. Qld
is invoked as follows:
qld --help | --version | -o progname [-dlopen module …] |
The --help
and --version
options (which may also be
abbreviated as -h
and -V
, respectively) work as usual. All
other options are simply passed on to the libtool
program, thus
you can actually use any option recognized in libtool's "link"
mode. (See GNU Libtool: (libtool)Invoking libtool section `Invoking libtool' in GNU Libtool, for details.) The interpreter executable is written to the
specified output file progname (there is no default for the output
file name, so the `-o' option must always be specified).
For a closer description of how qld
is used see Linking and Debugging a Module, below.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Writing a real external module can be a complicated task, just like any bit of C programming which goes beyond mere exercising. We cannot cover this process in detail here, although we hopefully provide enough information to get you started. You should take a look at the external modules bundled with the Q distribution for more substantial examples.
The procedure for making a C function callable from the Q interpreter is
fairly straightforward. You first need a Q script (called a stub)
which declares the external C functions you would like to use in your Q
program. Next you have to write a C module which implements these
functions. Finally, you run qcc
to translate the C module to a
library file which can be loaded by or linked into the interpreter. We
discuss each of these steps in turn.
As a running example, let us implement list reversal in C. Our C version
of this function will be called creverse
. We first create a Q
script creverse.q
, which declares the creverse
function as
extern
:
// creverse stub public extern creverse Xs; |
This tells the interpreter that when the creverse
function is
applied to a single argument, it should invoke the corresponding C
function, which is assumed to be found in a shared library named after
the stub script.
To implement the creverse
function, we create a C module
cerverse.c
. The Q programming system comes with an interface
library called libq
which provides the necessary operations to
inspect Q expressions given as argument values, and to construct new
expressions which can be returned as the result of the function
invocation. The interface operations are declared in the libq.h
header file which we include in our C module. The C module must provide
the necessary code for being initialized by the interpreter, which is
done by putting a MODULE
"header" at the beginning of the
source file. The MODULE
macro takes one argument, the name of the
module. The external functions are then declared with the
FUNCTION
macro, which takes four arguments: the name of the
module, the name of the external function (as given in the stub script),
the name of the argument count variable, and the name of the argument
vector variable. The FUNCTION
macro is followed by the C block
giving the definition of the function. In our example, the
creverse.c
module contains the following C code:
#include <libq.h> MODULE(creverse) FUNCTION(creverse,creverse,argc,argv) { /* to be sure, check number of arguments */ if (argc == 1) { /* expr is the data type used by libq to represent Q expressions; x is set to the argument expression, y is initialized to the empty Q list (mknil value) */ expr x = argv[0], y = mknil, hd, tl; /* iscons(x,...) checks that x is a [|] expression and returns its head element and tail list */ while (y && iscons(x, &hd, &tl)) { /* use mkcons to prepend the head element to the list y constructed so far */ expr z = mkcons(hd, y); y = z; x = tl; } if (!y) /* signal error condition */ return __ERROR; else if (isnil(x)) /* well-formed list argument, return the constructed reversal */ return y; else { /* argument was not a well-formed list; throw away the constructed value and indicate failure */ dispose(y); return __FAIL; } } else return __FAIL; } |
A description of the various macros and functions provided by the
libq
library can be found in the libq.h
header
file. Extern functions are treated very much like the built-in functions
provided by the interpreter. The external definition may be thought of
as an "external rule" being applied to a Q expression. The definition
may either return a Q expression, in which case the rule applies, or
__FAIL
, in which case the rule fails, and the interpreter goes on
to try other equations supplied by the programmer. As a third
alternative, the external definition may also return __ERROR
,
which causes evaluation to be aborted with an error message. Also note
that built-in functions always take priority. Thus the interpreter first
checks for a built-in rule, then for an external definition, and finally
considers the equations in the Q script. Therefore it is possible to
override an equational function definition with an external
function. For instance, we might rename the creverse
function in
the declaration in creverse.q
to stdlib::reverse
, and to
reverse
in creverse.c
. This makes our definition override
the definition of reverse
in stdlib.q
. The latter
definition will then only be applied when the external definition
fails. (This is what the clib
module actually does to override
the definition of reverse
as well as other operations in
stdlib.q
, see Clib.)
Also note that in order to prevent name clashes between external
functions and ordinary C function names, the external function names are
stropped with a special prefix (this is taken care of by the
FUNCTION
macro). To call an external function declared with
FUNCTION
from within your C module, you must therefore use the
FUNCALL
macro, which is invoked with the module name, function
name and the argc
and argv
parameters as arguments. For
instance, you would call the creverse
function defined above as
follows:
FUNCALL(creverse,creverse,argc,argv) |
Back to our example. Before we can actually use the above function in
the interpreter, we have to run qcc
to translate the C module to
a library object. In our example the process is fairly simple:
$ qcc creverse.c |
If all went well, we now have a libtool
library named
creverse.la
which is accompanied by some other
libtool
-generated files. (If you are trying this on a Windows
system, you will see a dll file named creverse.dll
instead.) If
your system supports shared libraries then you can now simply run the
creverse.q
script with the Q interpreter as usual:
$ q creverse.q |
(If at startup the interpreter complains that it could not load the module then your system probably does not support shared libraries. In this case you will have to create a new interpreter executable which links the module into the interpreter, as explained in Linking and Debugging a Module.)
Let's try it: You probably want to compare the running time of our list
reversal function against a Q version of the same operation, which can
be defined as follows (include this in the creverse.q
script):
public qreverse Xs; qreverse Xs:List = foldl push [] Xs; |
The following results were obtained on an Intel PIII-800 PC running Linux:
==> var l = [1..50000] ==> var r = creverse l; stats 0.033 secs, 1 reduction, 50000 cells ==> def r = qreverse l; stats 0.471 secs, 100002 reductions, 50004 cells |
Not very surprisingly, the C function is indeed much faster, which is due to the extra pattern matching, value extraction and function call overhead of the interpreter.
It is also possible to declare a Q type as extern
, and
realize the type in C. Such a type must always be abstract (i.e., it
must not have any constructor symbols, cf. Types), and the only
way to get a value of such a type is by means of corresponding extern
functions. For this purpose, the libq
library provides the
mkobj
operation, which takes as its argument a pointer to the
corresponding C object. For instance, consider an extern type Bar
and a corresponding construction function bar
declared as:
public extern type Bar; public extern bar I J; // I and J integer |
We might implement this type as a C struct containing a pair of integers as follows:
#include <libq.h> MODULE(ctype) typedef struct { long i, j; } Bar; FUNCTION(ctype,bar,argc,argv) { long i, j; if (argc != 2 || !isint(argv[0], &i) || !isint(argv[1], &j)) return __FAIL; else { Bar *v = malloc(sizeof(Bar)); expr x; if (!v) return __ERROR; v->i = i; v->j = j; return mkobj(type(Bar), v); } } |
Note that objects of an external type are completely "opaque" as far
as the interpreter is concerned (just like file values). In particular,
they will be printed using the notation <<typeid>>
in the
interpreter:
==> bar 1 2 <<Bar>> |
The only way to access the contents of an external object is by means of
corresponding C functions. For instance, an extern function which
converts a Bar
object to a tuple may be implemented as
follows. The declaration:
public extern bartuple B; |
The corresponding definition in the C module:
FUNCTION(ctype,bartuple,argc,argv) { Bar *v; if (argc == 1 && isobj(argv[0], type(Bar), (void**)&v)) return mktuplel(2, mkint(v->i), mkint(v->j)); else return __FAIL; } |
Compile and load the script, using the same procedure as above, and try the following:
==> bartuple (bar 1 2) (1,2) |
(It is worth noting here that the isint
and mkint
functions used above only allow you to access and create integer values
fitting into a machine integer. Integers of arbitrary sizes, which are
represented as GMP mpz_t
values, can be dealt with using the
ismpz
and mkmpz
functions, see the libq.h
header
files for details.)
As indicated in our example, external objects are usually allocated dynamically, and will be freed automatically by the interpreter when they are no longer needed. This default behaviour is appropriate in most cases. However, you can also explicitly define a destructor function for objects of an external type in your C module as follows:
DESTRUCTOR(ctype,Bar,v) { /* we could perform any other necessary cleanup here */ free(v); } |
If such a destructor function is present, it will be used instead of the
interpreter's default action to call free()
when it disposes a Q
expression of the corresponding type.
Occasionally, a module will also have some global internal data
structures which have to be initialized and/or finalized. For this
purpose, you can declare parameterless functions using the INIT
and FINI
macros, which will be executed before the script's
initialization code, and just before the interpreter exits,
respectively. Both macros take the module name as their single argument:
INIT(name) { /* any code to be executed at startup goes here */ } FINI(name) { /* any code to be executed at termination goes here */ } |
Note that the actual order in which the initialization/finalization
routines of different modules are executed is unspecified. Furthermore,
initialization routines will be executed before any of the script's
initialization code (def
and undef
). Thus these routines
should only be used to perform initializations and cleanup of private
data structures of the module. Other initializations can be performed
using appropriate def
statements in the stub script.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
If your system does not support shared libraries, or provides no means
to dynamically load a shared library at runtime, you must run the
qld
program to produce a new interpreter executable which links
in the required module. For instance, we could link the creverse
module from the preceding section into the interpreter as follows:
$ qld -o myq -dlopen creverse.la |
(You can also use the -dlpreopen
option instead of -dlopen
to force the module to be linked at load time, even if your system
supports runtime loading. See GNU Libtool: (libtool)Link mode section `Link mode' in GNU Libtool, for more information.)
You then run the script as usual, but using the custom interpreter
executable built with qld
instead of the standard `q'
program:
$ ./myq creverse.q |
Building such a "preloaded" interpreter is also required when you want
to debug a module, in which case the module usually must be
linked statically into the interpreter. You can do this as follows
(assuming that your C compiler understands the -g
debugging
flag):
$ qcc creverse.c -- -g $ qld -o myq -static -g -dlopen creverse.la |
If you now let your debugger execute `myq creverse.q', you should
be able to debug creverse.c
at the source level.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
To call the Q interpreter from a C/C++ application, your program must
link to the Q interpreter interface library, libqint
. This
library provides a number of operations with which you can load a script
into the interpreter and then evaluate Q expressions. A closer
description of the operations can be found in the qint.h
header
file of the library.
Before you can evaluate anything, you have to invoke the qexecv()
or qexecl()
routine to load a script or byte code file into the
interpreter. These two routines work pretty much like the run
command in the interpreter. The qexecv()
function takes the
script parameters as a vector of `char*' values, while
qexecl()
allows you to pass the parameters directly as
char*
arguments; otherwise the two functions operate in exactly
the same way. As with the interpreter's run
command, only one
script can be loaded at any one time; if a new script is loaded with
qexecv()
or qexecl()
, it replaces an existing one.
Two additional routines are provided, qexecvx()
and
qexeclx()
, which work exactly like qexecv()
and
qexecl()
, respectively, but take the script (or byte code file)
itself as a binary string in the first argument. This allows you to
specify the script to be executed directly in your application, without
relying on some external script file.
Once a script has been loaded, you can repeatedly evaluate expressions
with the qeval()
routine, which takes the expression to be
evaluated as a string argument. The contents of the string must be in Q
expression syntax. The result is returned as a malloc'd string which
must be freed by the caller; it contains the unparsed evaluated
expression in the same format as it would be printed by the interpreter.
Here is a complete C example which employs the qeval()
routine to
implement a simple read-eval-print loop. The name of the script to be
loaded and its parameters are taken from the command line of the
program.
/* poor man's Q interpreter */ #include <stdlib.h> #include <stdio.h> #include <string.h> #include <qint.h> #define BUFSIZE 1024 int main(int argc, char **argv) { int status; char s[BUFSIZE], *t; /* First load the script with args as given on the command line. */ if ((status = qexecv(argv[1], argc-1, argv+1))) { char msg[1000]; sprintf(msg, qstrerror(status), argv[1]); fprintf(stderr, "Error starting interpreter: %d: %s\n", status, msg); exit(1); } /* The read/eval/print loop. */ printf("%s loaded, ready to rumble!\n", argv[1]?argv[1]:"empty script"); printf("\nin> "); fflush(stdout); while (fgets(s, BUFSIZE, stdin)) { int l = strlen(s); if (l > 0 && s[l-1] == '\n') s[l-1] = 0; t = qeval(s, &status); if (status) printf("err> %d: %s\n", status, qstrerror(status)); if (t) { printf("out> %s\n", t); free(t); } printf("\nin> "); fflush(stdout); } printf("\n"); exit(0); } |
On Linux and most other Unix-like systems you should be able to compile
and then run this program as follows (assuming that the C source is in a
file named myq.c
):
$ gcc -o myq myq.c -lqint $ ./myq /usr/share/q/examples/fac.q |
(On some systems it might be necessary to add more linker options to resolve additional dependencies. E.g., under Cygwin you'll need something like `-lqint -liconv -lgmp -lq'.)
A sample session with the program is shown below.
/usr/share/q/examples/basics.q loaded, ready to rumble! in> 1+ err> 17: Syntax error in> 1+1 out> 2 in> fact 12 out> 479001600 in> foldl (*) 1 [1..12] out> 479001600 |
Obviously, the qeval()
function is of limited usefulness if your
application needs to construct the input expression from a C data
structure, and/or inspect the evaluated result, e.g., to translate the
result back to another C structure. For such applications libqint
provides the qevalx()
function which receives a binary expression
object (of type qexpr
) as input and returns the evaluated
expression as another qexpr
object. Operations are provided to
construct and inspect qexpr
objects which are analogous to the
corresponding libq
routines. Here is a simple example which
constructs and evaluates a qexpr
object using qevalx()
and
then checks the result value. It also illustrates the use of
qexeclx()
to load an "inlined" script into the interpreter.
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <qint.h> int main(int argc, char **argv) { int status; char *t; qexpr x; /* Our little hello world script. */ char *script = "hello = writes \"Hello, world!\\n\";"; /* Load the script into the interpreter, also pass arguments (the first argument is always the (fake) script name which becomes ARGS!0). */ qexeclx(script, strlen(script), 2, "Hello!", "Hello world example."); /* Just for fun, print the arguments passed to the script. (We don't do any error checking, since there's not much that can go wrong here.) */ t = qeval("ARGS", &status); printf("ARGS ==> %s\n", t); free(t); /* Evaluate the `hello' function from the script and print the result. */ t = qeval("hello", &status); printf("hello ==> %s\n", t); free(t); /* Here's how to employ qevalx() to evaluate binary expression objects instead of their string representations. We construct the function application `writes "Hello, world #2!\n"' and evaluate it. Note that the string value *must* be allocated dynamically since it is taken over by the interpreter (and freed automatically together with the rest of the input expression when qevalx() is finished with it). */ x = qevalx(qmkapp(qmksym(qsym(writes)), qmkstr(strdup("Hello, world #2!\n"))), &status); /* Check the result. */ if (status) printf("evalx() returned error code %d: %s\n", status, qstrerror(status)); else if (qisvoid(x)) printf("writes was executed successfully\n"); else { t = qprint(x, &status); printf("writes returned some unexpected value: %s\n", t); free(t); } /* Get rid of the result. */ qdispose(x); exit(0); } |
The program produces the following output:
ARGS ==> ["Hello!","Hello world example."] Hello, world! hello ==> () Hello, world #2! writes was executed successfully |
Using the qdef()
function, it is also possible to set variables
in the interpreter. You can either change existing variables of the
script or create new variables in the global scope. Here is a simple
example:
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <qint.h> int main(int argc, char **argv) { int status; char *script = "def TEST = 99;"; qexeclx(script, strlen(script), 0); /* change the value of an existing variable */ printf("TEST ==> %s\n", qeval("TEST", &status)); status = qdef("TEST", qevalx(qparse("TEST+2", &status), &status)); printf("TEST ==> %s\n", qeval("TEST", &status)); /* create a new variable in the global scope */ printf("MYTEST ==> %s\n", qeval("MYTEST", &status)); status = qdef("MYTEST", qevalx(qparse("TEST+2", &status), &status)); printf("MYTEST ==> %s\n", qeval("MYTEST", &status)); exit(0); } |
The program produces the following output:
TEST ==> 99 TEST ==> 101 MYTEST ==> MYTEST MYTEST ==> 103 |
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This document was generated by Albert Gräf on February, 23 2008 using texi2html 1.76.