[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

9.3 A Sample Shell Application

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] [ ? ]

9.3.1 ‘sic_repl.c

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] [ ? ]

9.3.2 ‘sic_syntax.c

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] [ ? ]

9.3.3 ‘sic_builtin.c

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] [ ? ]

9.3.4 ‘sic.c’ & ‘sic.h

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.