Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

A Source Code Generator for C


AUG91: A SOURCE CODE GENERATOR FOR C

Karl is a programmer for Control Data Corporation. Contact him at 2970 Presidential Dr., Suite 200, Fairborn, OH 45324.


I have been writing code for about nine years, and one thing I've noticed is that programming can usually be divided into three phases. Phase 1 (getting the idea) takes all of a millisecond and usually occurs somewhere really convenient, like in the shower. Phase 3 (running the finished program on the machine) takes a few seconds or minutes for the type of applications I usually deal with. Phase 2 (actually writing the bloody thing) usually takes a few orders of magnitude longer than Phases 1 and 3 put together. I know life's not supposed to be fair, but this is ridiculous.

Ever since I started working in a Unix shop, I've always wanted a "programmer's assistant" for C, kind of an invisible HAL 9000 sitting behind me and whispering things like "I'm sorry, Dave, but I don't think that you should cast that pointer. Malloc will barf, and your program will go west, taking me with it." I don't have a clue about how to write something like that, so the next best thing is to take the 80 percent trashwork of coding away and leave me with the 20 percent that's fun. I needed a code generator and my requirements were simple:

  • It had to be very simple to use, with a minimum amount of nonsense to remember.
  • It had to run from the command line like any other program, with a minimum of options.
  • It had to be extensible, handling different coding conventions and languages if needed.
  • It had to write the outline of a program and then put me right into the editor of my choice.
  • It had to be simple enough to put together in a few hours, because a good idea now beats a great one on the drawing board any day.
The program I came up with is called "new." The source code for "new" is shown in
Listings One through Ten. Listing One (page 102) is new.h, which holds the data structure for the command line options, plus general definitions. Listing Two (page 102) is macro.h, the header file for macro substitutions. Listing Three (page 102) shows new.c, which creates and optionally edits one or more new C programs. Listing Four (page 102) is CreateCode.c, which accepts the substitution structure, the input file, and the new C file to be created. It also reads the template file, fills it in with the contents of the substitution structure, and writes the results to the new C file.

Listing Five (page 103) lists EditCode.c, which edits the new C file using either vi or whatever you have in your EDITOR environment variable. Listing Six (page 103) is GetOptions.c, which accepts the command line arguments and a pointer to a structure that holds the options. It also parses the arguments into the structure. Listing Seven (page 104) shows Help.c, which prints help information to stdout. Listing Eight (page 106) lists OutString.c, which decides which macro string to return based on the current token character. Listing Nine (page 108), ReplaceMacros.c, accepts the macro structure holding the variables to be replaced, an input filepointer, and an output filepointer. It also does token substitution from input to output. Finally, Listing Ten (page 108) is SetMacros.c, which sets up the MACRO structure used for token replacement.

The "new" program works very much like the Unix program "nroff." The command nroff -mx file formats a file using a list of text formatting macros specified, by the -mx flag. In a similar fashion, new -mx file creates a source code file using a template which is specified by the -mx flag. In both cases, x is simply a file extension which identifies a given template file in a given directory.

There is no reason for x to be restricted to a single character, although to save typing I've chosen single-character names for my programming templates.

I have six C programming templates which manage to cover most of my needs:

  • -mf writes a generic C function other than main( ). This is the default, because I use it most often. Figure 1 shows the template itself, while Listing Four shows an example of the expanded template put to use.
  • -mm writes a generic main routine with the standard arguments argc and argv.
  • -mh writes the comment section of a basic C include file.
  • -md writes a short main routine which is nothing but a driver/testbed for a function.
  • -ms writes a short stub function which is meant to serve as a placeholder.
  • -mi writes a simple I/O routine which reads from stdin (standard input) and writes to stdout (standard output).

Figure 1: Generic function template

  #ifndef lint
  static char      *    $n_$e_rcsid =
  "$Header: $n.$e, v $r $y/$c/$d $h: $m:
                        $s $w Exp $";

  static char      * $n_$e_source =
  "$Source$";
  #endif

  /*
   * NAME:
   * $n
   *
   * SYNOPSIS:
   * $t $n ($1)
   *
   * DESCRIPTION:
   * $u
   *
   * ARGUMENTS:
   * Describe any function arguments.
   *
   * AUTHOR:
   * $x
   *
   * BUGS:
   * None noticed.
   *
   * REVISIONS:
   *
   * $Log$
   */

  $i
  $p
  $g

  $t $n ($1)
  $a{
  /*
   * Functions.
   */

  $f
  /*
   * Variables.
   */

  $v
  /*
   * Processing.
   */

  $b
  return (0);
  }


Due to space constraints, all of the templates and fully commented versions of the source files are available electronically; see "Availability" on page 3. The source code listings presented here are complete, except for comments.

In order to make this as flexible as possible, I used a simple token-substitution scheme in each template. A template consists of straight text plus reserved tokens of the form $a, $b, and so on. The dollar sign specifies the (possible) beginning of a token, and the first character after it tells the code generator what is to be inserted in place of the token. If a dollar sign is followed by any character other than one of the ones recognized by the code generator, then the dollar sign and the character are simply passed along to the program file being generated.

You don't have to live with the dollar sign as the start of a token; the code generator allows the token delimiter to be specified in one of the header files. I use the dollar sign as the token delimiter throughout this article.

Each token has a specific meaning: $a will be replaced by the argument list to a function, $d by the current day of the month, and so on. A complete list of the recognized tokens and the substituted text for each one is provided in Table 1. I did my best to keep them reasonably mnemonic.

Example 1(a) shows a template for a header file, 1(b) some sample values for the reserved tokens, and 1(c) the resulting generated source code. The line numbers in the example are for your convenience; they are not included in the template or the generated code. Making the templates was easy; in each case, I picked a coding standard I could live with, copied a C program that reflected it into the template file, and replaced the guts of the program with tokens.

The main data structure in "new" consists of a C structure which has one entry for each reserved token. Some of the entries in the structure are filled using information from the command line, but most are not. You may notice that a lot of the entries in the C token structure are not filled from within the code generator at all. This is because someday when I have time (translation: When I get an initiative attack I can't talk myself out of), I want to write a screen-oriented version of the source code generator which would allow someone to fill some or all of the entries in the token structure interactively. Figure 2 shows pseudocode which lays out the structure of the code generator as it currently exists.

Table 1: Token substitution list

  Token  Replacement string for the token

    a    Arguments for the function.  Full declarations separated by
            semicolons and newlines.
    b    Body of the function.  Comes under the first comment after the
            variable declarations.  Can include tabs and newlines.
    c    Current month
    d    Current day
    e    File extension
    f    Functions declared internally
    g    Global variables separated by semicolons
    h    Current hour
    i    "Include" commands, #include <stdio.h>
    l    Function argument list.  Variable names separated by commas
    m    Current minute
    n    Function name
    p    Preprocessor macros -- #define commands separated by newlines
    r    Revision level for a Revision Control System, the system I
            used in  this case
    s    Current second
    t    Type of function, such as char * or int
    u    Function usage.  This comes right after the first use of the
            name, and tells what the function is supposed to do.  Not
            to be confused with the "Usage" section in the intro
            comments.  This is the only "smart" macro: It won't let
            you put too many words on a line and starts each line with
            a comment indicator.  The comment indicator is a space
            followed by an asterisk; "new" assumes that there is both a
            leading /* and a trailing */ on some other line.
    v    Internal variables separated by semicolons, tabs, and newlines
    w    Who wrote the function
    x    Full name of the function author, extracted from the comment
            field in the /etc/passwd file
    y    Current year

Example 1: (a) Template file; (b) token replacement values; (c) generated code.

  (a)  1  #ifndef lint
       2  static char  * $n_$e_rcsid =
       3      "$Header: $n.$e,v $r $y/$c/$d $h:$m:$s $w Exp $";
       4
       5  static char  * $n_$e_source  =
       6      "$Sources$";
       7  #endif
       8
       9  /*
       10  *  Header file for "$n".
       11  *
       12  * $Log$
       13  */
       14
       15  #include  <stdio.h>

  (b)  $c  "09"
        $d  "23"
        $e  "h"
        $h  "00"
        $m  "25"
        $n  "program"
        $r  "1.1"
        $s  "44"
        $w  "vogel"
        $y  "90"

  (c)  1  #ifndef lint
       2  static char  *  program_h_rcsid  =
       3      "$Header: program.h,v 1.1 90/09/23 00:25:44 vogel Exp $";
       4
       5  static char  *  program_h_source  =
       6      "$Sources$";
       7  #endif
       8
       9   /*
       10   *  Header file for "program".
       11   *
       12   *  $Log$
       13   */
       14
       15  #include  <stdio.h>

Figure 2: Pseudocode that illustrates the structure of the code generator

  begin
  look for some sensible command line options and one or more files to
   create
      if (none found)
      then

             print help
             bail out
      endif
      for (each file on the command line)
      do

             set up the structure holding the tokens to be substituted
             filter text from the template file to the output program,
                                   doing appropriate token replacement
             edit the output program

      done
  end

I'm a big believer in separating the user interface part of a program from the part that does the actual work. Keeping all of the information needed to generate a C function within a single data structure makes a special-purpose code generator much easier to write, because it doesn't matter where the information stored in the token structure comes from: an input screen, another program, a file, or any combination thereof. The code generator needs only a filled token structure.

If I ever had to write a "mass-code" generator to read a bunch of input files and generate a bunch of functions and programs, the token structure would make it possible for me to write code that looks like Example 2.

Example 2: A flexible data structure leads to code that looks like this.

  while (getinfo (input file, pointer to the C token structure))
  do
     putfunction (output file, pointer to the C token structure)
  done

In this case, getinfo would read a file and fill the token structure, and putfunction would read the stuff in the token structure and write the C code. The input file format has no necessary connection to the output file format, and this is a case of two totally dissimilar data structures which have to peacefully coexist in the same program. Whenever you have a case like this, either marry the data structures or divorce them, but don't let them live in sin. A divorce seemed the cleanest solution; input and output functions only communicate through the token structure, and otherwise know nothing about each other.

Why a Code Generator?

Whether you use this code generator, buy a commercial one, or gird up your loins and roll your own doesn't matter. What does matter is getting one and using it, and here's why:

Consistency This is one reason I can't emphasize enough. With so many projects involving multiple programmers and even multiple teams, anything that helps communication between the worker bees isn't to be sneered at. Consistent code appearance doesn't guarantee understandable code, but it doesn't hurt, and a code generator makes enforcement of coding standards easier.

As I see it, the main problem with coding standards is that they don't take into account the fact that coding usually boils down to some poor schlub typing at a terminal. If you don't at least try to make that person's life easier when imposing your standard, forget it, you'll get lip service at best and outright rebellion at worst. In the area of software, a poor solution is actually worse than none at all, because it lulls people into thinking that the "problem" is being "solved." (Isn't this how we wound up with Ada?)

Beauty There's something about a well-crafted piece of code that makes it very easy on the eyes, but occasionally appearance suffers when deadlines loom. Instead of sprucing up every function by hand, spruce up your templates and let the code generator do the boring stuff.

Generality The templates and code here are presented in C, but there is no reason for it to be that way. One code generator can handle C, Pascal, Modula-2, or whatever you have the patience to write a template for. I used one-letter template names because I'm not a typing fan, not because it's a requirement of the code generator.

Economy One of the things I admire most about Unix is what they left out. Unix handles files, processes, and nothing else. This type of code generator encourages you to develop your code one function at a time, but Unix doesn't "know" anything about functions. To teach Unix about functions, you can use a tool such as "ctags" to make editing source files easier, but "ctags" must be kept up to date every time you make a significant change to your source. The code generator encourages the idea of "one function, one file," and if you make a one-to-one correspondence between functions and files, then Unix knows about functions too. This enables you to use the power of the Unix toolset (and mindset) on each separate compilable portion of a C program, and for me that equates to finer control over the whole process.

I certainly respect companies that care enough about their programmers to buy CASE tools, but I've noticed that the current products have a bit of an attitude: "Be reasonable, write your code, and design your products OUR way." Instead of buying a CASE tool and changing your methods to suit the machine (or another vendor), why not expand the capacities of the operating system you have now?

Speed Placing one function in one file puts a permanent end to the game of "let's find the function," and in combination with a decent "make" tool for generating executables from a list of dependencies, it also ensures that you are doing the minimum amount of recompilation when you modify source during development. Goodbye "ctags"!

Program Tracing If I write any program which is going to receive widespread use by a lot of different people, I like to impress the customer by being aware of problems before I get the irate phone call, not after. One easy way to do this is to build an ErrorMsg function which accepts the arguments: ErrorMsg (file, line, format, arg1, arg2, ...) where file is the name of the function (stored in the __FILE__ preprocessor variable), line is the line number at which the problem occurred (stored in the __LINE__ preprocessor variable), format is the output format used by functions in the printf family, and the remaining arguments are variables which get referenced from within the output format. The ErrorMsg function appends an appropriate message to an error file, and just before the main routine exits, another function, AnyErrors, checks the error file to see if it contains anything. If AnyErrors returns TRUE, a third function, MailMsg, mails the error file to me.

The important point here is that with one function per file, the __FILE__ pre-processor variable always tells me exactly what function is messing up, so I don't have to look at a source listing somewhere and mutter "Let's see, line 600 is function foobar, I think, but what's at line 700?" I know immediately what functions were called in what order, and if the ErrorMsg function was called enough times with enough information, I may have everything I need to fix the problem without having to use a debugger. I've always believed that if you have to fire up a debugger to fix a problem, then you have a much more basic problem with your code that a debugger can't fix.

Flexibility Having one function per file makes writing all kinds of neat tools much easier. For example, I have another program called "doc" which looks through one or more directories, and prints the comment header from a given function, in case I forget the order of the arguments or something. It won't cure cancer but it does save time, and I didn't have to put in too many late evenings to write it, either. doc reads a function name from the argument list, appends .c to it if it's not there already, and uses the Unix access call to see if a file by that name exists in one of several directories. If such a file is found, the comment header is written to stdout.

Conclusion

Of course, you can make just as big a mess with a code generator as without, and you can do it faster; a fool with a tool is just a well-equipped fool. Like any other tool, a code generator can save beaucoup time only if it's used with some common sense. I tend to be wary of any CASE product that claims to let you write code "without thinking about it;" anyone who writes code for someone else's use without thinking is dangerous.


_A SOURCE CODE GENERATOR FOR C_
by Karl Vogel


[LISTING ONE]
<a name="01ba_000c">

/* File "new.h" */

#ifndef   lint
static char    *new_h_rcsid =
"$Header: new.h,v 1.5 91/03/29 19:23:08 vogel Exp $";

static char    *new_h_source =
"$Source: /d/cdc/vogel/source/new/RCS/new.h,v $";

#endif

/*
 * NAME:
 *   new.h
 *
 * SYNOPSIS:
 *   #include "new.h"
 *
 * DESCRIPTION:
 *   Holds the data structure for the command line options, plus
 *   general definitions.
 *
 * AUTHOR:
 *   Karl Vogel
 *
 * BUGS:
 *   None noticed.
 *
 * REVISIONS:
 *
 * $Log:   new.h,v $
 * Revision 1.5  91/03/29  19:23:08  vogel
 * 1.  Renamed the directory holding the templates.
 *
 * Revision 1.4  91/03/15  16:56:33  vogel
 * 1.  Ran indent on the code and reformatted the comment header.
 *
 * Revision 1.3  90/07/03  14:21:20  vogel
 * 1.  Added a new field:  extension of the program to be created.  This is also
 *     in "macro.h", but it can be most easily set from examining the command
 *     line options.
 *
 * Revision 1.2  90/07/03  12:07:06  vogel
 * 1.  Added a flag to the OPTIONS structure which is TRUE if the created file
 *     is to be edited, FALSE otherwise.
 *
 * Revision 1.1  90/06/29  15:32:04  vogel
 * Initial revision
 *
 */

#include   <stdio.h>

#define      YES      1
#define      NO      0

/* Error codes.  */
#define      OK      0
#define      EMALLOC      1

/* Set directory holding templates, and prefix of each template file.  */
#define      DIRECTORY   "/d/cdc/vogel/source/new/"
#define      PREFIX      "template."

/* Set up the options structure.  */
struct options_data
{
   char          **files;      /* NULL-terminated array of C files
                * to be created. */
   char           *template;   /* Full name of the desired
                * template file. */
   char            exten[5];   /* Extension of the program to be
                * created.  */
   int             edit;      /* True if the created program is to
                * be edited. */
   int             help;      /* True if help is needed. */
};

typedef struct options_data OPTIONS;




<a name="01ba_000d">
<a name="01ba_000e">
[LISTING TWO]
<a name="01ba_000e">


/* File "macro.h" */

#ifndef   lint
static char    *macro_h_rcsid =
"$Header: macro.h,v 1.2 91/03/15 16:56:28 vogel Exp $";

static char    *macro_h_source =
"$Source: /d/cdc/vogel/source/new/RCS/macro.h,v $";

#endif

/*
 * NAME:
 *   macro.h
 *
 * SYNOPSIS:
 *   #include "macro.h"
 *
 * DESCRIPTION:
 *   Header file for macro substitutions.
 *
 * AUTHOR:
 *   Karl Vogel
 *
 * BUGS:
 *   Any problems or suggestions.
 *
 * REVISIONS:
 *
 * $Log:   macro.h,v $
 * Revision 1.2  91/03/15  16:56:28  vogel
 * 1.  Ran indent on the code and reformatted the comment header.
 *
 * Revision 1.1  90/06/29  17:40:15  vogel
 * Initial revision
 *
 */

#define      BUFFER      512

struct macro_data
{
   char            args[BUFFER];   /* Arguments for the function: full
                * declarations separated by
                * semicolons and newlines. Replaces
                * $a in a template.  */
   char            body[BUFFER];   /* Body of the function.  Comes under
                * the first comment after the
                * variable declarations. Includes
                * tabs and newlines. Replaces $b in
                * a template.  */
   char            exten[5];   /* Extension of the created file,
                * usually ".c".  Replaces $e in a
                * template. */
   char            functions[BUFFER];   /* Internal function
                   * declarations. Comes just
                   * after the start of the
                   * function itself.  Includes
                   * tabs and newlines.
                   * Replaces $f in a template. */
   char            globals[BUFFER];/* External global variables. Comes
                * just before the start of the
                * function itself.  Includes tabs
                * and newlines.  Replaces $g in a
                * template. */
   char            include[BUFFER];/* External include files.  Comes
                * just before the start of the
                * function itself.  Includes tabs
                * and newlines.  Replaces $i in a
                * template.  */
   char            alist[BUFFER];   /* Short form of the argument list,
                * consisting of just the variable
                * names separated by commas.
                * Replaces $l in a template. */
   char            name[64];   /* Name of the function being
                * created.  May or may not be the
                * same as the filename. Replaces $n
                * in a template.  */
   char            define[BUFFER];   /* Holds preprocessor #define
                * statements separated by newlines.
                * Replaces $p in a template. */
   char            rev[16];   /* Current revision level of the
                * created function.  Replaces $r in
                * a template. */
   char            type[32];   /* Type of function created, i.e.
                * char * or int.  Replaces $t in a
                * template.  */
   char            usage[BUFFER];   /* Comes right after the first use of
                * the function name, and tells you
                * what the function is supposed to
                * do.  Just above the Usage section
                * in the intro comments.  This is
                * the only "smart" macro: won't let
                * you put too many words on a line,
                * and starts each line with a
                * comment indicator.  Replaces $u in
                * a template. */
   char            vars[BUFFER];   /* Holds internally-declared
                * variables, separated by tabs,
                * semicolons, and newlines. Replaces
                * $v in a template.  */
   char            userid[16];   /* Holds the author's userid.
                * Replaces $w in a template.  */
   char            username[BUFFER];   /* Holds the author's full
                   * name. Replaces $x in a
                   * template.  */
   char            token;      /* Holds the character to be used as
                * a token indicator.  Shown as a
                * dollar sign in this file.  */
   int             year;      /* Current year.  Replaces $y in a
                * template. */
   int             month;      /* Current month.  Replaces $c in a
                * template. */
   int             day;      /* Current day.  Replaces $d in a
                * template. */
   int             hour;      /* Current hour.  Replaces $h in a
                * template. */
   int             minute;      /* Current minute.  Replaces $m in a
                * template.  */
   int             second;      /* Current second.  Replaces $s in a
                * template.  */
};
typedef struct macro_data MACRO;




<a name="01ba_000f">
<a name="01ba_0010">
[LISTING THREE]
<a name="01ba_0010">

/* File "new.c" */

#ifndef   lint
static char    *new_c_rcsid =
"$Header: new.c,v 1.6 91/03/15 16:55:42 vogel Exp $";

static char    *new_c_source =
"$Source: /d/cdc/vogel/source/new/RCS/new.c,v $";

#endif

/*
 * NAME:
 *   new
 *
 * SYNOPSIS:
 *   new [-c] [-h] [-mx] file [file ...]
 *
 * DESCRIPTION:
 *   Creates and optionally edits one or more new C programs.
 *
 *   "new" makes extensive use of templates to create new C files.
 *   A template file has the name "template.x", where 'x' is the string
 *   which follows '-m' in the program arguments.
 *
 *   You can create other templates by adding template files to the
 *   template directory specified in the header file "new.h".
 *
 * OPTIONS:
 *   "-c"   creates a new program but does NOT edit it.
 *
 *   "-h"   prints help information if improper or no arguments at all
 *      are given.
 *
 *   "-mx"   creates a program of type 'x', where x is one of the following:
 *      d - function driver.
 *      f - normal function (default).
 *      h - header file.
 *      i - program to handle simple I/O.
 *      m - main routine with arguments.
 *      o - function to handle command line arguments.
 *      s - stub (placeholder) function.
 *
 *   "file"   is one or more C files to be created.  A suffix of ".c" will
 *      be appended if it isn't there already.  If the file already
 *      exists, you will be asked if you want to overwrite it.
 *
 * AUTHOR:
 *   Karl Vogel
 *
 * BUGS:
 *   None noticed.
 *
 * REVISIONS:
 *
 * $Log:   new.c,v $
 * Revision 1.6  91/03/15  16:55:42  vogel
 * 1.  Ran indent on the code and reformatted the comment header.
 *
 * Revision 1.5  90/09/24  16:42:03  vogel
 * 1.  Corrected a typo in the comments.
 *
 * Revision 1.4  90/07/03  14:20:23  vogel
 * 1.  Copied the desired file extension into the MACRO structure.
 *
 * Revision 1.3  90/07/03  12:05:23  vogel
 * 1.  Added "nextfile" variable, to hold the next file to be created.
 * 2.  Got rid of debugging print statements for the macros structure.
 * 3.  Added code to create and optionally edit each file in turn.
 *
 * Revision 1.2  90/06/29  17:40:04  vogel
 * 1.  Added header file and definitions for the macro substitution strings.
 * 2.  Added code to print help if needed.
 *
 * Revision 1.1  90/06/29  15:27:52  vogel
 * Initial revision
 *
 */

#include   "new.h"
#include   "macro.h"
#include   <strings.h>
#include   <sys/param.h>

main (argc, argv)
int             argc;
char          **argv;
{

/* Variables. */
   MACRO          *macros;
   MACRO           macbuffer;

   OPTIONS        *options;
   OPTIONS         opbuffer;

   char            nextfile[MAXPATHLEN];

   int             k;
   int             status;

/* Process command line options. If they aren't OK, offer help and exit. */
   options = &opbuffer;
   status = GetOptions (argc, argv, options);

   if (status == EMALLOC)

   {
      fprintf (stderr, "Severe memory error -- please call the ");
      fprintf (stderr, "System Administrator.\n");
      exit (1);
   }

   if (options->help)
   {
      (void) Help ();
      exit (1);
   }

/* Set up the substitution macros.  */
   macros = &macbuffer;
   SetMacros (macros);
   (void) strcpy (macros->exten, options->exten);

/* Create and optionally edit each file in turn.  */
   for (k = 0; options->files[k]; ++k)
   {
      (void) strcpy (nextfile, options->files[k]);

      if (CreateCode (macros, options->template, nextfile) == 0)
         if (options->edit)
            EditCode (nextfile);
   }

   exit (0);
}





<a name="01ba_0011">
<a name="01ba_0012">
[LISTING FOUR]
<a name="01ba_0012">

/* File "CreateCode.c" */

#ifndef   lint
static char    *CreateCode_c_rcsid =
"$Header: CreateCode.c,v 1.2 91/03/15 16:55:25 vogel Exp $";

static char    *CreateCode_c_source =
"$Source: /d/cdc/vogel/source/new/RCS/CreateCode.c,v $";

#endif

/*
 * NAME:
 *   CreateCode
 *
 * SYNOPSIS:
 *   #include "macro.h"
 *
 *   int CreateCode (macros, template, nextfile)
 *   MACRO * macros;
 *   char * template;
 *   char * nextfile;
 *
 * DESCRIPTION:
 *   Accepts the substitution structure, the input file, and the new C
 *   file to be created.  Reads the template file, fills it in with the
 *   contents of the substitution structure, and writes the results to
 *   the new C file.
 *
 *   A function value of 0 is returned if everything is OK.
 *   A function value of 1 is returned if the template could not be read.
 *   A function value of 2 is returned if the output could not be written.
 *
 * ARGUMENTS:
 *   "macros" is the structure holding the macro substitution values.
 *   "template" is the name of the input template file.
 *   "nextfile" is the name of the next output file.
 *
 * AUTHOR:
 *   Karl Vogel
 *
 * BUGS:
 *   None noticed.
 *
 * REVISIONS:
 *
 * $Log:   CreateCode.c,v $
 * Revision 1.2  91/03/15  16:55:25  vogel
 * 1.  Ran indent on the code and reformatted the comment header.
 *
 * Revision 1.1  90/07/03  11:59:45  vogel
 * Initial revision
 *
 */

#include   "macro.h"
#include   <stdio.h>
#include   <strings.h>
#include   <sys/file.h>

#define      SMALLBUF   20

int             CreateCode (macros, template, nextfile)
MACRO          *macros;
char           *template;
char           *nextfile;
{

/* Variables. */

   FILE           *in;
   FILE           *out;

   char           *base;
   char           *dot;
   char            last[SMALLBUF];
   char            prompt[SMALLBUF];

   int             length;
   int             status;

/* If filename already has default extension specified in MACRO structure, zap
 * it so we can store the function name in the MACRO structure. */
   status = 0;
   in = (FILE *) NULL;
   out = (FILE *) NULL;

   last[0] = '.';
   last[1] = '\0';

   (void) strcat (last, macros->exten);
   length = strlen (last);

   if (dot = rindex (nextfile, '.'))
      if (strcmp (dot, last) == 0)
         *dot = '\0';

   if (base = rindex (nextfile, '/'))
      base++;
   else
      base = nextfile;

   (void) strcpy (macros->name, base);
   (void) strcat (nextfile, last);

/* If output file already exists, make sure that user wants to overwrite it. */
   if (access (nextfile, F_OK) == 0)
   {
      printf ("File \"%s\" exists.  Overwrite?  ", nextfile);
      fflush (stdout);
      fgets (prompt, SMALLBUF, stdin);

      if (prompt[0] != 'y' && prompt[0] != 'Y')
      {
         printf ("\"%s\" not overwritten.\n", nextfile);
         goto done;
      }
   }

/* Open the input template file. */
   if ((in = fopen (template, "r")) == (FILE *) NULL)
   {
      printf ("Unable to open template file \"%s\"\n", template);
      status = 1;
      goto done;
   }

/* Open the output C file. */
   if ((out = fopen (nextfile, "w")) == (FILE *) NULL)
   {
      printf ("Unable to open C file \"%s\"\n", nextfile);
      status = 2;
      goto done;
   }

/* Do the macro substitution, clean up, and return. */
   ReplaceMacros (in, macros, out);
done:
   if (in)
      (void) fclose (in);
   if (out)
      (void) fclose (out);
   return (status);
}



<a name="01ba_0013">
<a name="01ba_0014">
[LISTING FIVE]
<a name="01ba_0014">

/* File "EditCode.c" */

#ifndef   lint
static char    *EditCode_c_rcsid =
"$Header: EditCode.c,v 1.2 91/03/15 16:55:28 vogel Exp $";

static char    *EditCode_c_source =
"$Source: /d/cdc/vogel/source/new/RCS/EditCode.c,v $";

#endif

/*
 * NAME:
 *   EditCode
 *
 * SYNOPSIS:
 *   int EditCode (nextfile)
 *   char * nextfile;
 *
 * DESCRIPTION:
 *   Edits the new C file using either "cvi" or whatever the user has in
 *   his "EDITOR" environment variable.
 *
 * ARGUMENTS:
 *   "nextfile" is the new C file.
 *
 * AUTHOR:
 *   Karl Vogel
 *
 * BUGS:
 *   None noticed.
 *
 * REVISIONS:
 *
 * $Log:   EditCode.c,v $
 * Revision 1.2  91/03/15  16:55:28  vogel
 * 1.  Ran indent on the code and reformatted the comment header.
 *
 * Revision 1.1  90/07/03  12:00:13  vogel
 * Initial revision
 */

#include   <stdio.h>

int             EditCode (nextfile)
char           *nextfile;
{

/* Functions.  */
   char           *getenv ();

/* Variables.  */
   char           *editor;
   char            cmd[BUFSIZ];

   int             status;

/* Create and execute the edit command.  */
   if (editor = getenv ("EDITOR"))
      (void) strcpy (cmd, editor);
   else
      (void) strcpy (cmd, "cvi");
   (void) strcat (cmd, " ");
   (void) strcat (cmd, nextfile);
   (void) system (cmd);

   return (0);
}




<a name="01ba_0015">
<a name="01ba_0016">
[LISTING SIX]
<a name="01ba_0016">

/* File "GetOptions.c" */

#ifndef   lint
static char    *GetOptions_c_rcsid =
"$Header: GetOptions.c,v 1.4 91/03/15 16:55:31 vogel Exp $";

static char    *GetOptions_c_source =
"$Source: /d/cdc/vogel/source/new/RCS/GetOptions.c,v $";

#endif

/*
 * NAME:
 *   GetOptions
 *
 * SYNOPSIS:
 *   #include "new.h"
 *
 *   int GetOptions (argc, argv, options)
 *   int argc;
 *   char ** argv;
 *   OPTIONS * options;
 *
 * DESCRIPTION:
 *   Accepts the command line arguments and a pointer to a structure which
 *   holds the options, and parses the arguments into the structure.
 *
 * ARGUMENTS:
 *   "argc" and "argv" are the normal C program variables holding the
 *   argument list.
 *
 *   "options" is the structure meant to hold the options.
 *
 * AUTHOR:
 *   Karl Vogel
 *
 * BUGS:
 *   Any problems or suggestions.
 *
 * REVISIONS:
 *
 * $Log:   GetOptions.c,v $
 * Revision 1.4  91/03/15  16:55:31  vogel
 * 1.  Ran indent on the code and reformatted the comment header.
 *
 * Revision 1.3  90/07/03  14:19:49  vogel
 * 1.  Added code to set up the file extension based on the command line options.
 *     "new -mh" gives you a file with name ending in ".h", anything else gives
 *     you a file with name ending in ".c".
 *
 * Revision 1.2  90/07/03  12:01:01  vogel
 * 1.  Added new option '-c'.
 * 2.  Added code to make sure that default template is properly set up.
 *
 * Revision 1.1  90/06/29  15:28:13  vogel
 * Initial revision
 *
 */

#include   "new.h"
#include   <strings.h>

#define      SMALL      10

int             GetOptions (argc, argv, options)
int             argc;
char          **argv;
OPTIONS        *options;
{

/* Functions. */
   char           *malloc ();

/* Variables. */
   char           *next;
   char            extension[SMALL + 1];

   int             count;
   int             flags;
   int             k;
   int             status;

   unsigned int    length;

/* Initialize options to defaults.  */
   options->files = (char **) NULL;
   options->template = (char *) NULL;
   options->edit = YES;
   options->help = NO;

   (void) strcpy (extension, "f");

   count = 0;
   status = OK;

/* Handle the flags, and count the files. */
   for (k = 1; k < argc; ++k)
   {
      next = argv[k];

      if (*next == '-')
      {
         next++;

         if (*next == 'm')
         {
            (void) strncpy (extension, next + 1, SMALL);
            extension[SMALL] = '\0';
         }

         else
         if (*next == 'c')
            options->edit = NO;

         else
         {
            options->help = YES;
            goto done;
         }
      }

      else      /* option does not start with a dash  */
         count++;
   }

/* If no files were specified, set help flag. Otherwise, store filenames. */
   if (count)
   {
      length = (unsigned) ((count + 1) * (sizeof (char *)));
      if (options->files = (char **) malloc (length))
      {
         for (k = 1, count = 0; k < argc; ++k)
            if (*argv[k] != '-')
               options->files[count++] = argv[k];
         options->files[count] = (char *) NULL;
      }
      else
      {
         status = EMALLOC;
         goto done;
      }
   }
   else
   {
      options->help = YES;
      goto done;
   }

/* Set up the file extension. */
   if (strcmp (extension, "h") == 0)
      options->exten[0] = 'h';
   else
      options->exten[0] = 'c';

   options->exten[1] = '\0';

/* Set up the template pathname.  */
   length = (unsigned) (strlen (DIRECTORY) + strlen (PREFIX) +
              strlen (extension) + 1);
   if (options->template = malloc (length))
      (void) sprintf (options->template, "%s%s%s", DIRECTORY,
            PREFIX, extension);
   else
      status = EMALLOC;
done:
   return (status);
}




<a name="01ba_0017">
<a name="01ba_0018">
[LISTING SEVEN]
<a name="01ba_0018">

/* File "Help.c" */

#ifndef   lint
static char    *Help_c_rcsid =
"$Header: Help.c,v 1.4 91/03/15 16:55:33 vogel Exp $";

static char    *Help_c_source =
"$Source: /d/cdc/vogel/source/new/RCS/Help.c,v $";

#endif

/*
 * NAME:
 *   Help
 *
 * SYNOPSIS:
 *   int Help ()
 *
 * DESCRIPTION:
 *   Prints help information to stdout.
 *
 * ARGUMENTS:
 *   None.
 *
 * AUTHOR:
 *   Karl Vogel
 *
 * BUGS:
 *   None noticed.
 *
 * REVISIONS:
 *
 * $Log:   Help.c,v $
 * Revision 1.4  91/03/15  16:55:33  vogel
 * 1.  Ran indent on the code and reformatted the comment header.
 *
 * Revision 1.3  90/09/24  16:40:47  vogel
 * 1.  Corrected a typo.
 *
 * Revision 1.2  90/07/03  12:01:42  vogel
 * 1.  Changed print strings to reflect new '-c' option.
 *
 * Revision 1.1  90/06/29  17:40:46  vogel
 * Initial revision
 *
 */

#include   <stdio.h>

int             Help ()
{

/* Variables.  */
   int             k;

   static char    *array[] =
   {
    "",
    "\tnew:\t\tcreates and optionally edits one or more new C",
    "\t\t\tprograms.",
    "",
    "\t\tUsage:\tnew [-c] [-h] [-mx] file [file ...]",
    "",
    "\t\tWhere:\t\"-c\"\tcreates a new program but does NOT edit it.",
    "",
    "\t\t\t\"-h\"\tprints a help file.  Help is also given if",
    "\t\t\t\tno arguments at all are given.",
    "",
    "\t\t\t\"-mx\"\tcreates a program of type 'x', where x is",
    "\t\t\t\tone of the following:",
    "\t",
    "\t\t\t\td - function driver.",
    "\t\t\t\tf - normal function (default).",
    "\t\t\t\th - header file.",
    "\t\t\t\ti - program to handle simple I/O.",
    "\t\t\t\tm - main routine with arguments.",
    "\t\t\t\to - function to handle command line arguments.",
    "\t\t\t\ts - stub (placeholder) function.",
    "",
    "\t\t\t\"file\"\tis one or more C files to be created.  A",
    "\t\t\t\tsuffix of \".c\" will be appended if it isn't",
    "\t\t\t\tthere already.",
    "\t",
    "\t\t\t\tIf the file already exists, you will be asked",
    "\t\t\t\tif you want to overwrite it.",
    "",
    "",
    "\t\t\"new\" makes extensive use of templates to create new C files.",
    "\t\tA template file has the name \"template.x\", where 'x' is the",
    "\t\tstring which follows '-m' in the program arguments.",
    "\t",
    "\t\tYou can create other templates by adding template files to",
   "\t\tthe template directory specified in the header file \"new.h\".",
    (char *) NULL
   };

/* Write the help information. */
   for (k = 0; array[k]; ++k)
      puts (array[k]);
   return (0);
}




<a name="01ba_0019">
<a name="01ba_001a">
[LISTING EIGHT]
<a name="01ba_001a">

/* File "OutString.c" */

#ifndef   lint
static char    *OutString_c_rcsid =
"$Header: OutString.c,v 1.2 91/03/15 16:55:35 vogel Exp $";

static char    *OutString_c_source =
"$Source: /d/cdc/vogel/source/new/RCS/OutString.c,v $";

#endif

/*
 * NAME:
 *   OutString
 *
 * SYNOPSIS:
 *   #include "new.h"
 *   #include "macro.h"
 *
 *   int OutString (macros, current, string)
 *   MACRO * macros;
 *   char current;
 *   char * string;
 *
 * DESCRIPTION:
 *   Decides which macro string to return based on the current token
 *   character.
 *
 *
 * ARGUMENTS:
 *   "macros" holds the current macro replacement values.
 *
 *   "current" is the value of the token character following a dollar sign.
 *
 *   "string" is the replacement value of that token.
 *
 * AUTHOR:
 *   Karl Vogel
 *
 * BUGS:
 *   None noticed.
 *
 * REVISIONS:
 *
 * $Log:   OutString.c,v $
 * Revision 1.2  91/03/15  16:55:35  vogel
 * 1.  Ran indent on the code and reformatted the comment header.
 *
 * Revision 1.1  90/07/03  12:02:28  vogel
 * Initial revision
 *
 */

#include   "new.h"
#include   "macro.h"
#include   <stdio.h>
#include   <ctype.h>
#include   <strings.h>

int             OutString (macros, current, string)
MACRO          *macros;
char            current;
char           *string;
{

/* Variables.  */
   char           *blank;
   char           *s;
   char           *t;
   char            temp[BUFFER];

   int             col;
   int             first;
   int             length;
   int             status;

/* Do the simple string replacements, and numeric conversion.  */
   switch (current)
   {
      case 'a':
         (void) strcpy (string, macros->args);
         break;
      case 'b':
         (void) strcpy (string, macros->body);
         break;
      case 'c':
         (void) sprintf (string, "%2.2d", macros->month);
         break;
      case 'd':
         (void) sprintf (string, "%2.2d", macros->day);
         break;
      case 'e':
         (void) strcpy (string, macros->exten);
         break;
      case 'f':
         (void) strcpy (string, macros->functions);
         break;
      case 'g':
         (void) strcpy (string, macros->globals);
         break;
      case 'h':
         (void) sprintf (string, "%2.2d", macros->hour);
         break;
      case 'i':
         break;
      case 'l':
         (void) strcpy (string, macros->alist);
         break;
      case 'm':
         (void) sprintf (string, "%2.2d", macros->minute);
         break;
      case 'n':
         (void) strcpy (string, macros->name);
         break;
      case 'p':
         (void) strcpy (string, macros->define);
         break;
      case 'r':
         (void) strcpy (string, macros->rev);
         break;
      case 's':
         (void) sprintf (string, "%2.2d", macros->second);
         break;
      case 't':
         (void) strcpy (string, macros->type);
         break;
      case 'u':
         break;
      case 'v':
         (void) strcpy (string, macros->vars);
         break;
      case 'w':
         (void) strcpy (string, macros->userid);
         break;
      case 'x':
         (void) strcpy (string, macros->username);
         break;
      case 'y':
         (void) sprintf (string, "%2.2d", macros->year);
         break;
      default:
         *string = '\0';
         break;
   }

/* Handle "Usage" string. Write no more than 50 characters/line, indented
 *3 tab spaces in. */
   if (current == 'u')
   {
      (void) strcpy (temp, macros->usage);
      t = temp;
      *string = '\0';
      first = YES;
      while (length = strlen (t))
      {
         if (!first)
            (void) strcat (string, "\n * \t\t\t");

         first = NO;
         if (length <= 50)
         {
            (void) strcat (string, t);
            break;
         }
         else
         {
            blank = (char *) NULL;
            col = 25;
            for (s = t; *s && col < 75; ++s, ++col)
               if (isspace (*s))
                  blank = s;
            if (blank)
            {
               *blank = '\0';
               (void) strcat (string, t);
               t = blank + 1;
            }
            else
            {
               (void) strcat (string, t);
               break;
            }
         }
      }
   }

/* Handle the "include" strings. */
   if (current == 'i')
   {
      for (s = string, t = macros->include; *t; ++s, ++t)
      {
         if (*t == ' ')
            *s = '\t';
         else
            *s = *t;
      }
      *s = '\0';
   }
   return (0);
}




<a name="01ba_001b">
<a name="01ba_001c">
[LISTING NINE]
<a name="01ba_001c">

/* File "ReplaceMacros.c" */

#ifndef   lint
static char    *ReplaceMacros_c_rcsid =
"$Header: ReplaceMacros.c,v 1.2 91/03/15 16:55:37 vogel Exp $";

static char    *ReplaceMacros_c_source =
"$Source: /d/cdc/vogel/source/new/RCS/ReplaceMacros.c,v $";

#endif

/*
 * NAME:
 *   ReplaceMacros
 *
 * SYNOPSIS:
 *   #include "macro.h"
 *
 *   int ReplaceMacros (in, macros, out)
 *   FILE * in;
 *   MACRO * macros;
 *   FILE * out;
 *
 * DESCRIPTION:
 *   Accepts the macro structure holding the variables to be replaced,
 *   an input filepointer, and an output filepointer.  Does token
 *   substitution from input to output.
 *
 * ARGUMENTS:
 *   Described above.
 *
 * AUTHOR:
 *   Karl Vogel
 *
 * BUGS:
 *   None noticed.
 *
 * REVISIONS:
 *
 * $Log:   ReplaceMacros.c,v $
 * Revision 1.2  91/03/15  16:55:37  vogel
 * 1.  Ran indent on the code and reformatted the comment header.
 *
 * Revision 1.1  90/07/03  12:02:42  vogel
 * Initial revision
 *
 */

#include   "macro.h"
#include   <stdio.h>
#include   <ctype.h>
#include   <strings.h>

int             ReplaceMacros (in, macros, out)
FILE           *in;
MACRO          *macros;
FILE           *out;
{

/* Variables. */
   char           *s;
   char            string[BUFFER];
   char            current;
   char            previous;

/* Start the main loop which looks ahead one character. */
   previous = getc (in);
   if (previous == EOF)
      return (0);

/* Decide what to do if we get a token character. */
   while ((current = getc (in)) != EOF)
   {
      if (previous == macros->token)
      {
         if (index ("abcdefghilmnprstuvwxy", current))
         {
            OutString (macros, current, string);
            for (s = string; *s; ++s)
               putc (*s, out);
            previous = getc (in);
         }
         else
         {
            putc (previous, out);
            previous = current;
         }
      }
      else
      {
         putc (previous, out);
         previous = current;
      }
   }

/* Don't forget to write the last character. */
   putc (previous, out);
   return (0);
}




<a name="01ba_001d">
<a name="01ba_001e">
[LISTING TEN]
<a name="01ba_001e">

/* File "SetMacros.c" */

#ifndef   lint
static char    *SetMacros_c_rcsid =
"$Header: SetMacros.c,v 1.4 91/03/15 16:55:40 vogel Exp $";

static char    *SetMacros_c_source =
"$Source: /d/cdc/vogel/source/new/RCS/SetMacros.c,v $";

#endif

#include   "macro.h"
#include   <stdio.h>
#include   <strings.h>
#include   <pwd.h>
#include   <sys/time.h>

int             SetMacros (macros)
MACRO          *macros;
{

/* Functions. */
   char           *getenv ();
   struct passwd  *getpwuid ();

/* Variables. */
   char           *s;
   int             offset;
   long            clock;
   struct passwd  *ptr;
   struct tm      *now;

/* Set up the internal C stuff.  The "name" entry will be set later. */
   macros->args[0] = '\0';
   macros->body[0] = '\0';
   macros->functions[0] = '\0';
   macros->globals[0] = '\0';
   macros->alist[0] = '\0';
   macros->name[0] = '\0';
   macros->define[0] = '\0';
   macros->usage[0] = '\0';
   macros->vars[0] = '\0';

   (void) strcpy (macros->exten, "c");
   (void) strcpy (macros->rev, "1.1");
   (void) strcpy (macros->type, "int");

   (void) strcpy (macros->include,
             "#include <stdio.h>\n#include <ctype.h>");

/* Set up userid and full name of program author. See if we can get the
 * information from the environment. If that fails, look it up from userid
 * and passwd file. */
   macros->userid[0] = '\0';
   macros->username[0] = '\0';

   if (s = getenv ("USER"))
      (void) strcpy (macros->userid, s);
   if (s = getenv ("USERNAME"))
      (void) strcpy (macros->username, s);
   if (strlen (macros->userid) == 0 || strlen (macros->username) == 0)
   {
      if (ptr = getpwuid (getuid ()))
      {
         (void) strcpy (macros->userid, ptr->pw_name);
         if (strncmp (ptr->pw_gecos, "Civ ", 4) == 0)
            offset = 4;
         else
            offset = 0;
         (void) strcpy (macros->username,
                   ptr->pw_gecos + offset);
         if (s = index (macros->username, ';'))
            *s = '\0';
      }
      else
      {
         (void) strcpy (macros->userid, "unknown");
         (void) strcpy (macros->username, "unknown");
      }
   }

/* Set the character which we will recognize as the start of a token.  */
   macros->token = '$';

/* Set up the current date and time.  */
   (void) time (&clock);
   now = localtime (&clock);
   macros->second = now->tm_sec;
   macros->minute = now->tm_min;
   macros->hour = now->tm_hour;
   macros->day = now->tm_mday;
   macros->month = now->tm_mon + 1;
   macros->year = now->tm_year;

   return (0);
}


Copyright © 1991, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.