[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
What I need now, is a program that uses ‘libsic.a’, if only to give me confidence that it is working. In this section, I will write a simple shell which uses the library. But first, I’ll create a directory to put it in:
$ mkdir src $ ls -F COPYING Makefile.am aclocal.m4 configure* config/ sic/ INSTALL Makefile.in bootstrap* configure.in replace/ src/ $ cd src |
In order to put this shell together, we need to provide just a few things for integration with ‘libsic.a’...
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
In ‘sic_repl.c’(13) there is a loop for reading strings typed by the user, evaluating them and printing the results. GNU readline is ideally suited to this, but it is not always available – or sometimes people simply may not wish to use it.
With the help of GNU Autotools, it is very easy to cater for building with and without GNU readline. ‘sic_repl.c’ uses this function to read lines of input from the user:
static char * getline (FILE *in, const char *prompt) { static char *buf = NULL; /* Always allocated and freed from inside this function. */ XFREE (buf); buf = (char *) readline ((char *) prompt); #ifdef HAVE_ADD_HISTORY if (buf && *buf) add_history (buf); #endif return buf; } |
To make this work, I must write an Autoconf macro which adds an option to ‘configure’, so that when the package is installed, it will use the readline library if ‘--with-readline’ is used:
AC_DEFUN([SIC_WITH_READLINE], [AC_ARG_WITH(readline, [ --with-readline compile with the system readline library], [if test x"${withval-no}" != xno; then sic_save_LIBS=$LIBS AC_CHECK_LIB(readline, readline) if test x"${ac_cv_lib_readline_readline}" = xno; then AC_MSG_ERROR(libreadline not found) fi LIBS=$sic_save_LIBS fi]) AM_CONDITIONAL(WITH_READLINE, test x"${with_readline-no}" != xno) ]) |
Having put this macro in the file ‘config/readline.m4’, I must also
call the new macro (SIC_WITH_READLINE
) from ‘configure.in’.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The syntax of the commands in the shell I am writing is defined by a set of syntax handlers which are loaded into ‘libsic’ at startup. I can get the C preprocessor to do most of the repetitive code for me, and just fill in the function bodies:
#if HAVE_CONFIG_H # include <config.h> #endif #include "sic.h" /* List of builtin syntax. */ #define syntax_functions \ SYNTAX(escape, "\\") \ SYNTAX(space, " \f\n\r\t\v") \ SYNTAX(comment, "#") \ SYNTAX(string, "\"") \ SYNTAX(endcmd, ";") \ SYNTAX(endstr, "") /* Prototype Generator. */ #define SIC_SYNTAX(name) \ int name (Sic *sic, BufferIn *in, BufferOut *out) #define SYNTAX(name, string) \ extern SIC_SYNTAX (CONC (syntax_, name)); syntax_functions #undef SYNTAX /* Syntax handler mappings. */ Syntax syntax_table[] = { #define SYNTAX(name, string) \ { CONC (syntax_, name), string }, syntax_functions #undef SYNTAX { NULL, NULL } }; |
This code writes the prototypes for the syntax handler functions, and
creates a table which associates each with one or more characters that
might occur in the input stream. The advantage of writing the code this
way is that when I want to add a new syntax handler later, it is a simple
matter of adding a new row to the syntax_functions
macro, and
writing the function itself.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
In addition to the syntax handlers I have just added to the Sic shell, the language of this shell is also defined by the builtin commands it provides. The infrastructure for this file is built from a table of functions which is fed into various C preprocessor macros, just as I did for the syntax handlers.
One builtin handler function has special status, builtin_unknown
.
This is the builtin that is called, if the Sic library cannot find a
suitable builtin function to handle the current input command. At first
this doesn’t sound especially important – but it is the key to any
shell implementation. When there is no builtin handler for the command,
the shell will search the users command path, ‘$PATH’, to find a
suitable executable. And this is the job of builtin_unknown
:
int builtin_unknown (Sic *sic, int argc, char *const argv[]) { char *path = path_find (argv[0]); int status = SIC_ERROR; if (!path) { sic_result_append (sic, "command \""); sic_result_append (sic, argv[0]); sic_result_append (sic, "\" not found"); } else if (path_execute (sic, path, argv) != SIC_OKAY) { sic_result_append (sic, "command \""); sic_result_append (sic, argv[0]); sic_result_append (sic, "\" failed: "); sic_result_append (sic, strerror (errno)); } else status = SIC_OKAY; return status; } static char * path_find (const char *command) { char *path = xstrdup (command); if (*command == '/') { if (access (command, X_OK) < 0) goto notfound; } else { char *PATH = getenv ("PATH"); char *pbeg, *pend; size_t len; for (pbeg = PATH; *pbeg != '\0'; pbeg = pend) { pbeg += strspn (pbeg, ":"); len = strcspn (pbeg, ":"); pend = pbeg + len; path = XREALLOC (char, path, 2 + len + strlen(command)); *path = '\0'; strncat (path, pbeg, len); if (path[len -1] != '/') strcat (path, "/"); strcat (path, command); if (access (path, X_OK) == 0) break; } if (*pbeg == '\0') goto notfound; } return path; notfound: XFREE (path); return NULL; } |
Running ‘autoscan’ again at this point adds
AC_CHECK_FUNCS(strcspn strspn)
to ‘configure.scan’. This
tells me that these functions are not truly portable. As before I
provide fallback implementations for these functions in case they are
missing from the target host – and as it turns out, they are easy to
write:
/* strcspn.c -- implement strcspn() for architectures without it */ #if HAVE_CONFIG_H # include <config.h> #endif #include <sys/types.h> #if STDC_HEADERS # include <string.h> #elif HAVE_STRINGS_H # include <strings.h> #endif #if !HAVE_STRCHR # ifndef strchr # define strchr index # endif #endif size_t strcspn (const char *string, const char *reject) { size_t count = 0; while (strchr (reject, *string) == 0) ++count, ++string; return count; } |
There is no need to add any code to ‘Makefile.am’, because the
configure
script will automatically add the names of the
missing function sources to ‘@LIBOBJS@’.
This implementation uses the autoconf
generated
‘config.h’ to get information about the availability of headers and
type definitions. It is interesting that autoscan
reports
that strchr
and strrchr
, which are used in the fallback
implementations of strcspn
and strspn
respectively, are
themselves not portable! Luckily, the Autoconf manual tells me exactly
how to deal with this: by adding some code to my ‘common.h’
(paraphrased from the literal code in the manual):
#if !STDC_HEADERS # if !HAVE_STRCHR # define strchr index # define strrchr rindex # endif #endif |
And another macro in ‘configure.in’:
AC_CHECK_FUNCS(strchr strrchr) |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Since the application binary has no installed header files, there is little point in maintaining a corresponding header file for every source, all of the structures shared by these files, and non-static functions in these files are declared in ‘sic.h’:
#ifndef SIC_H #define SIC_H 1 #include <sic/common.h> #include <sic/sic.h> #include <sic/builtin.h> BEGIN_C_DECLS extern Syntax syntax_table[]; extern Builtin builtin_table[]; extern Syntax syntax_table[]; extern int evalstream (Sic *sic, FILE *stream); extern int evalline (Sic *sic, char **pline); extern int source (Sic *sic, const char *path); extern int syntax_init (Sic *sic); extern int syntax_finish (Sic *sic, BufferIn *in, BufferOut *out); END_C_DECLS #endif /* !SIC_H */ |
To hold together everything you have seen so far, the main
function creates a Sic parser and initialises it by adding syntax
handler functions and builtin functions from the two tables defined
earlier, before handing control to evalstream
which will
eventually exit when the input stream is exhausted.
int main (int argc, char * const argv[]) { int result = EXIT_SUCCESS; Sic *sic = sic_new (); /* initialise the system */ if (sic_init (sic) != SIC_OKAY) sic_fatal ("sic initialisation failed"); signal (SIGINT, SIG_IGN); setbuf (stdout, NULL); /* initial symbols */ sicstate_set (sic, "PS1", "] ", NULL); sicstate_set (sic, "PS2", "- ", NULL); /* evaluate the input stream */ evalstream (sic, stdin); exit (result); } |
Now, the shell can be built and used:
$ bootstrap ... $ ./configure --with-readline ... $ make ... make[2]: Entering directory `/tmp/sic/src' gcc -DHAVE_CONFIG_H -I. -I.. -I../sic -I.. -I../sic -g -c sic.c gcc -DHAVE_CONFIG_H -I. -I.. -I../sic -I.. -I../sic -g -c sic_builtin.c gcc -DHAVE_CONFIG_H -I. -I.. -I../sic -I.. -I../sic -g -c sic_repl.c gcc -DHAVE_CONFIG_H -I. -I.. -I../sic -I.. -I../sic -g -c sic_syntax.c gcc -g -O2 -o sic sic.o sic_builtin.o sic_repl.o sic_syntax.o \ ../sic/libsic.a ../replace/libreplace.a -lreadline make[2]: Leaving directory `/tmp/sic/src' ... $ ./src/sic ] pwd /tmp/sic ] ls -F Makefile aclocal.m4 config.cache configure* sic/ Makefile.am bootstrap* config.log configure.in src/ Makefile.in config/ config.status* replace/ ] exit $ |
This chapter has developed a solid foundation of code, which I will return to in A Large GNU Autotools Project, when Libtool will join the fray. The chapters leading up to that explain what Libtool is for, how to use it and integrate it into your own projects, and the advantages it offers over building shared libraries with Automake (or even just Make) alone.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] |
This document was generated by Ben Elliston on July 10, 2015 using texi2html 1.82.