[net.sources] Make for Lattice C

bright@dataio.UUCP (Walter Bright) (08/09/85)

Well, I got the make posted by grayson@uiucuxc.Uiuc.ARPA, and it
compiled and worked fine. However, the overhead incurred by
the program was 96k! This meant that I couldn't use much of a ramdisk.
The solution was to recompile it with the Lattice 2.14 C compiler,
which doesn't hog 64k for its data segment when it isn't necessary. Also,
the Lattice compiler dropped the size of the .EXE file from 28k (the
Microsoft version) to 20k, making the total overhead of make only
24k bytes! Obviously, for those of us with less than 640k of memory,
a large improvement.

Converting to Lattice C required numerous changes to the source:

	o Lattice can't handle unsigned longs.

	o Convert the spawnlp() call to forklp() call. WARNING: the
	  fork functions in Lattice 2.14 are screwed up, so the
	  way I wrote it is a little funny. If you have 2.15, you
	  may have to change the fork.

	o I had to write a couple of missing library routines, and
	  kludge up a realloc() function.

	o The code had a couple of spots where storage was freed,
	  but was later referenced. I fixed these.

	o I recompiled it with small model.

Anyhow, here's the new source. It's not a shell script, someday I'll
figger out how to write one.
Files are makefile, mstring.h, make.c and mstring.c.

-------------- makefile ------------------
make.exe: make.obj mstring.obj
#	link make+mstring/STACK:9000;
	link cs+make+mstring,make,make,lcs/map

*.obj : *.c
#	msc /AL /Ota $*.c;
	lc1 $* -dLATTICE
	lc2 $*

mstring.obj : mstring.h

make.obj : mstring.h
-------------- mstring.h -----------------
/* mstring.h */

typedef char * mstring;
mstring mfgets(), mstrcat(), msubstr(), talloc();
-------------- make.c --------------------
/*
 * make.c
 *
 *      An imitation of the Unix MAKE facility
 *
 *      Copyright (C) 1984 by Larry Campbell, 73 Concord St., Maynard, Mass.
 *      Rewritten w/modifications by Mike Hickey, University of DC
 *
 *      This software may be freely copied and disseminated for noncommercial 
 *      purposes, if and only if this entire copyright statement and notice 
 *      is included intact.  This software, and the information contained 
 *      herein, may not be used for commercial purposes without my prior 
 *      written permission.
 *
 *      This program runs a new shell (COMMAND.COM) for each command specified 
 *      in the makefile.  This, I recommend that you put a copy of the shell in
 *      ramdisk (if you have one).  Assuming your ramdisk is called, say, drive
 *      F:, you would:
 *      
 *
 *              COPY A:\COMMAND.COM F:
 *              SET COMSPEC=F:\COMMAND.COM
 *
 */

/* Certain portions of this software are Copyright (C) 1985 by Dan Grayson,
   2409 S. Vine St, Urbana, IL 61801, namely all those lines which differ
   between versions 2.10 and 2.11.  Qualitative descriptions of these 
   changes are described in the edit history below.
        Provided this copyright notice is not changed, and VERS211 below
   is not changed, and VERS211 is still prints when 'usage()' runs, these
   portions may be freely copied and used for any purpose, with one exception,
   namely:  those persons or corporations who claim a copyright on this
   program or a part of it may not use these portions for any commercial
   purpose whatsoever.  In particular, they may not collect royalties on
   any version of this program which includes these portions. */

/*
 * Edit history:
 *
 *  2.12	Made it compilable by Lattice C version 2.14. Use '-dLATTICE'
 *		on the command line for this. Default is Microsoft C.
 *  2.11        Fixed breakout_symbols, which tried to return a pointer to
                        a local variable!
                Made symbol substitution occur in all lines, not just shell
                        command lines.
                Fixed breakout_symbols, which blew up when a symbol was 
                        undefined.
                Allowed blank lines, which are ignored now.
                Change command line length to 1000.
                Fixed it so MAKE, when no targets are specified on the command
                        line, will simply make the first target in the makefile.
                Remove the command line symbol definition option.
                Changed the line continuation character to \ (it was - )
                Now symbol definition lines do NOT begin with $.
                Fixed numerous bugs dealing with character arrays and their
                        lengths.
                Now a shell command line which begins with @ is not echoed.
                A shell command line beginning with + is executed through
                        command.com ; this makes io redirection and pipes
                        available, but the exit code of the program cannot
                        be checked due to a misfeature of command.com.
                A shell command line beginning with - may return a nonzero
                        exit code without halting 'make'.
                Fixed it so a target:prerequisite line followed by no how-to
                        lines is interpreted not as an error, and not as 
                        sharing the how-to lines following the next
                        target:prerequisite line, but is considered fulfilled
                        by no action other than making all the 
                        prerequisites.
                Fixed the bug which meant the return code from the commmand
                        was never dicovered.  This resulted from using 
                        "system", which uses "command.com", which hides the
                        return code of the program it runs.  Resident 
                        commands can still be used, nevertheless.
                Error messages now include the line number of the makefile,
                        if relevant.
                Made the return code of the command print out if nonzero.
                Now the copyright notice only prints when the usage appears.
                Convert to Microsoft vers 3.00, large memory model.
                                - dan grayson
 *  2.10        Fix bug in abort routine, update copyright notice
 *  2.09        Set up for command line parsing of macros
 *  2.08        Remove edit 2.05; keep debug a compile-time option
 *  2.07        Finish macro parsing
 *  2.06        Add initial code for macro handling
 *  2.05        Add -d (debug) switch
 *  2.04        Add error message handling (doserror).
 *  2.03        Add -i (ignore errors) switch.
 *  2.02        Add -s (silent run) switch.
 *  2.01        Convert to Lattice 2.14.  Clean up code for faster execution.
 *  1.11        Make default makefilename be MAKEFILE.
 *  1.10        Add -n (trace) switch.
 *  1.09        Allow multiple targets before colon.
 *  1.08        Cleanup and speedup of makefile parsing.
 *  1.07        Add continuation lines (hyphen as last char of line)
 *  1.06        Don't need to make dummy FCBs, zero seems to work OK.
 *  1.05        Fix bug finding COMSPEC when it's not first env variable
 *  1.04        Wordier usage text, including copyright notice.
 *              Remove printf's (except when DEBUG on) to shrink EXE
 *  1.03        Don't uppercase shell command and fix datetime bug
 *  1.02        Random cleanup
 *  1.01        The beginning
 */

#define VERSION "MAKE ver. 2.10 Copyright (C) 1984 by Larry Campbell, Maynard Mass."
#define VERS211 "MAKE ver. 2.12 Portions copyright (C) 1985 by Dan Grayson, Urbana IL."

#ifdef LATTICE
#define assert(a)
#define perror error
#undef LATTICE
#include <dos.h>
#include <stdio.h>
#include <ctype.h>
#include "mstring.h"
extern char *strchr();
typedef long ulong;
#else
#define LINT_ARGS       /* for Microsoft v. 3.00 */
#include <assert.h>
#include <process.h>
#include <dos.h>
#include <stdio.h>
#include <malloc.h>
#include <ctype.h>
#include "mstring.h"
#include <string.h>
typedef unsigned long ulong;
#endif

char *dos_commands[] = {
      "dir", "type", "rem", "pause", "date", "time",
      "ren", "rename", "ver", "vol", "break", "verify",
      "mkdir", "md", "exit", "ctty", "echo", "if", "cls",
      "chdir", "cd", "rmdir", "rd", "copy", "del", "erase",
                         NULL };

#define PREREQ_MAGIC 123
#define FILE_MAGIC 543
#define SHELL_MAGIC 678
#define TARG_MAGIC 987
#define SYMBOL_MAGIC 653
#define MAXLIN 1000
#define SYMLEN 1000
#define MAXTARGETS 100

#ifndef TRUE
#define TRUE 1
#define FALSE 0
#define EOS '\0'                /* End Of String */
#endif

#define LINESIZE 1000            /* max length of input line */
#define MAXCOMMON 8             /* max no. of targets with common prereqs */

#define tfree(x) if (x) free(x),x=NULL

#ifdef LATTICE
extern char _dos;
#else
extern unsigned char _osmajor;           /* in MS ver 3, gives version of OS to left of point */
extern int _doserrno;
#endif

extern int linenumber;   /* defined in mstring.c */
char  *talloc(), *strperm();
ulong getdatetime ();

    static int dontworry=0;

union REGS inregs, outregs;
struct SREGS segregs;
struct SREGS seg;

struct {
    char reserved[21];
    char attr;
    unsigned time, date, size_l, size_h;
    char pname[13];
} 
find_buf;

/*
 * MAKE parses the make file and constructs a somewhat tangled
 * directed graph describing dependencies.  There is a list of
 * TargNode structures, each of which points to a list of
 * prereq_node structures.  These both point to FileNode structures,
 * which contain information about files such as date-time of write
 * and the filename.  To keep things simple, MAKE insures that only
 * one FileNode exists for any given file.
 */

typedef struct FileNode {
#ifndef NDEBUG
    int magic;
#endif
    char *fname;
    struct FileNode *chain;
    }    *fileptr;
fileptr  FileNodeList, NewFileNode ();

typedef struct TargNode {
#ifndef NDEBUG
    int magic;
#endif
    struct TargNode *next;
    fileptr file;
    struct prereq_node *PreqList;
    struct shell_node *shell_list;
    }    *targptr;
targptr target_list, new_target (), lookup_target ();

typedef struct prereq_node {
#ifndef NDEBUG
    int magic;
#endif
    struct prereq_node *next;
    fileptr file;
    } *preqptr;
preqptr NewPreqNode ();

typedef struct shell_node {
#ifndef NDEBUG
    int magic;
#endif
    struct shell_node *next;
    char *command;
    unsigned quiet : 1, ignore : 1, useshell : 1;
    } *shellptr;

typedef struct symbol_node {
#ifndef NDEBUG
    int magic;
#endif
    char *token, *value;
    struct symbol_node *next;
    }    *symbptr;
symbptr SymbolList;

static char *makefilename;
static int status, tracing, quietly, ignore_errors;

usage ()
{
    puts (VERS211);
    puts (VERSION);
    puts ("This program may be copied freely for noncommercial purposes.  It may");
    puts ("not be copied for commercial use without the author's written permission.\n");
    puts ("This  program is an imitation of the MAKE program supplied with Unix.");
    puts ("It works somewhat like Unix MAKE (and VAX/VMS MMS) except that it  has");
    puts ("no default rules.\n");
    puts ("Usage: make [target ...] [options ...]");
    puts ("Options:");
    puts ("        -f filename specify makefile, default is MAKEFILE");
    puts ("        -i          ignore errors while processing");
    puts ("        -n          trace and print, but don't execute commands");
    puts ("        -s          suppress MAKE echoing commands");
    exit (1);
}

main (argc, argv)
int argc;
char **argv;
{
    int
        argi,
        targi,
        linlen;

    char  *targname[MAXTARGETS];

#ifdef LATTICE
    if (_dos < 2) error ("Must have DOS 2.XX or higher");
#else
    if (_osmajor < 2) error ("Must have DOS 2.XX or higher");
#endif
    makefilename = "MAKEFILE";
    tracing = quietly = ignore_errors = FALSE;
    target_list = 0;
    SymbolList = 0;
    FileNodeList = 0;
    targi = 0;
    for (argi = 1; argi < argc; argi++) {
        if (argv[argi][0] == '-')               /* switch */
            switch (argv[argi][1]) {            /* switch character */
            case 'f':
                if (argi < (argc - 1))      makefilename = argv[++argi];
                else                        usage ();
                break;
            case 'i':                ignore_errors = TRUE;             break;
            case 'n':                tracing = TRUE;                   break;
            case 's':                quietly = TRUE;                   break;
            default:                 usage ();
            }
        else {                          /* target name */
            if (targi == MAXTARGETS)    error ("Too many target files");
            targname[targi] = strperm (argv[argi]);
            targi++;
        }
    }
    if (tracing && quietly) quietly = 0;

    parse (makefilename);

    if (target_list == NULL) error ("No targets in makefile");

    if (targi)
            for (argi = 0; argi < targi; argi++) make (targname[argi]);
    else {
            targptr p;
            for (p=target_list; p->next ; p = p->next );
            assert (p->magic == TARG_MAGIC);
            make(p->file->fname);  /* make first target in makefile
                                        i.e. the last one on the list */
            }
    return 0;           /* need good return code */
}

parse (makefilename)
char *makefilename;
{
    FILE  *fd;
    int targi=0,   i;
    char  c,  *sp,  *dp;
    mstring input=NULL;
    targptr targ[MAXTARGETS];

    fd = fopen (makefilename, "r");
    if (fd == NULL) error ("Can't open makefile");
    while (1) {
        tfree(input);
        input = mfgets(fd);
        if (input==NULL) break;
#if DEBUG
        printf ("Makefile Input: \"%s\"\n", input);
#endif
        sp = input;
        passpace(&sp);
        if (*sp==0 || *sp=='!' || *sp=='#') continue;
           /* ignore comment lines and blank lines */

        if (isspace(*input)) {   /* if leading space, then this is a shell line */
                if (targi == 0) error ("Target line must come before shell lines");
                sp = input; passpace(&sp);
                for (i = 0; i < targi; i++)  NewShellLine (targ[i], sp);
                continue;
        }

        {               /* substitute for symbols - this will be done later
                           for shell lines, to take special symbols like $*
                           into account, which can only be known at run time
                           */
              breakout_symbols(&input);
        }

        {       /*** check for the form 'name = value'   ***/
                char *endword;
                sp=input;
                password(&sp);    endword = sp;
                passpace(&sp);
                if (*sp == '=') {
                    targi=0;
                    sp++;
                    *endword = EOS;
                    SetSymbol (input, sp) ;
                    continue;
                }
        }                               /* end of macro parsing */


        /**** now we know this is a 'targets : prerequisite' line ***/


        targi=0;
        for ( dp = sp = input; 1 ; sp++)        /*** collect the targets ***/
            if (*sp == ':' || isspace (*sp)) { /* space or colon ends target name */
                if (targi == MAXTARGETS) error ("Too many targets in one line");
                c = *sp;  *sp = EOS;
                targ[targi++] = new_target (dp);
                *sp = c;
                passpace(&sp);
                if (*sp == ':') break;
                dp = sp;
            }
            else if (*sp == EOS) error ("no colon");

        sp++;
        if (targi == 0) error ("No target file before ':' ");
        while(1) {              /*** collect the prerequisites ***/
            passpace(&sp);
            if (*sp == EOS) break;            /* end of line */
            dp = sp;                    /* beginning of prereq word */
            passnonsp(&sp);
            c = *sp;
            *sp = EOS ;                /* end of prereq word */
            for (i = 0; i < targi; i++) LinkPreq (targ[i], NewFileNode(dp) );
            *sp = c;
        }
    }   /* end while */
    tfree(input);
    fclose (fd);
    linenumber = 0;
}

/*
 * new_target
 *
 *      We think we have a new target;  this routine scans the
 *      target list and if we've already seen this name returns
 *      the node we already built.  Otherwise, a new node is
 *      allocated and linked.
 */

targptr new_target (name)
char *name;
{
    targptr targ ;

#if DEBUG
    printf ("new_target (\"%s\")\n", name);
#endif
    for ( targ = target_list; targ ; targ = targ->next ) {
        assert (targ->magic == TARG_MAGIC);
        if (strcmp (targ->file->fname, name) == 0)  return targ;
    }
    targ = (targptr) talloc (sizeof (struct TargNode));
#ifndef NDEBUG
    targ->magic = TARG_MAGIC;
#endif
    targ->file = NewFileNode (name);
    targ->next = target_list;
    targ->shell_list = NULL;
    targ->PreqList = NULL;
    target_list = targ;
    return targ;
}

SetSymbol (name, value)
char *name, *value;
{
    symbptr sym;
    for (sym = SymbolList; sym; sym = sym->next)
         if (0==strcmp(sym->token,name)) {
                free(sym->value);
                sym->value = strperm(value) ;
                return;
                }
    sym = (symbptr) talloc (sizeof (struct symbol_node));
#ifndef NDEBUG
    sym->magic = SYMBOL_MAGIC;
#endif
    sym->token = strperm (name);
    sym->value = strperm (value);
    sym->next = SymbolList;
    SymbolList = sym;
}

/*
 * NewShellLine
 *
 *      Add a shell command to the list of commands needed to update
 *      a target file
 */

NewShellLine (targ, line)
targptr targ;
char *line;
{
    shellptr snode, new;

#if DEBUG
    printf ("NewShellLine (%x, \"%s\")\n", targ, line);
#endif
    new = (shellptr) talloc (sizeof (struct shell_node));
    new->next = 0;
#ifndef NDEBUG
    new->magic = SHELL_MAGIC;
#endif
    new->useshell = new->ignore = 0;
    new -> quiet = quietly ;
    for ( ; 1 ; line++, passpace(&line) )
       if      (line[0] == '@')  new->quiet = 1;
       else if (line[0] == '+')  new->useshell = 1;
       else if (line[0] == '-')  new->ignore = 1;
       else break;
    new->command = strperm (line);
    snode = targ->shell_list;
    if (snode) {
        assert (snode->magic == SHELL_MAGIC);
        while (snode->next) {
                snode = snode->next;
                assert (snode->magic == SHELL_MAGIC);
                }
        snode->next = new;
    }
    else
        targ->shell_list = new;
}

/*
 * LinkPreq
 *
 *      Link a new prerequisite file onto prereq list for a target.
 */

LinkPreq (targ, fnode)
   targptr targ;  fileptr fnode; {  preqptr p;

#if DEBUG
    printf ("LinkPreq (\"%s\")\n", fnode->fname );
#endif

    p = targ->PreqList;
    ( targ->PreqList = NewPreqNode(fnode) ) -> next = p;
    }

/*
 * NewPreqNode
 *
 *      Allocate and return a new prerequisite node
 */

        preqptr 
NewPreqNode (fnode)
        fileptr fnode;
{
    preqptr new;

#if DEBUG
    printf ("NewPreqNode (struct FileNode *%x, \"%s\")\n",fnode,fnode->fname);
#endif
    new = (preqptr) talloc (sizeof (struct prereq_node));
    new->next = NULL;
#ifndef NDEBUG
    new->magic = PREREQ_MAGIC;
#endif
    new->file = fnode;
    return new;
}

/*
 * NewFileNode
 *
 *      Return FileNode pointer for a file;  returns pointer to
 *      existing FileNode if this file already seen, else allocates
 *      and inits a new node
 */

        fileptr
NewFileNode (name)
        char *name;
{
    fileptr fnode;

#if DEBUG
    printf ("NewFileNode (\"%s\")\n", name);
#endif
    for ( fnode = FileNodeList; fnode; fnode = fnode->chain) {
        assert (fnode->magic == FILE_MAGIC);
        if (strcmp (name, fnode->fname) == 0) {
#if DEBUG
            printf ("NewFileNode returning existing node\n");
#endif
            return fnode;
        }
        
    }
    fnode = (fileptr) talloc (sizeof (struct FileNode));
    fnode->fname = strperm (name);
    fnode->chain = 0;
#ifndef NDEBUG
    fnode->magic = FILE_MAGIC;
#endif
    fnode -> chain = FileNodeList;
    FileNodeList = fnode;
    return fnode;
}

/*
 * getdatetime
 *
 *      Return date-time of a file squished into a ulong so compares
 *      are easy
 */

ulong getdatetime (name)
        char *name;
{
    ulong datetime;
    int dma_off, dma_seg;

#ifdef LATTICE
	struct SREGS sregs;
	segread(&sregs);
#define FP_SEG(p)	(sregs.ds)
#define FP_OFF(p)	((int)(p))
#endif

#ifdef DEBUG
    printf("getdatetime(\"%s\")\n",name);
#endif

    inregs.x.ax = 0x2F00;            /* get current DMA address */
    intdosx (&inregs, &outregs, &segregs);   /*  .. */
    dma_off = outregs.x.bx;           /* and save for later restoration */
    dma_seg = segregs.es;
    {  char *p = (char *) & find_buf;
       segregs.ds = FP_SEG(p);
       inregs.x.dx = FP_OFF(p);  /* set DMA to GNJFN block */
    }
    inregs.x.ax = 0x1A00;
    intdosx (&inregs, &outregs, &segregs);

    segregs.ds = FP_SEG(name);
    inregs.x.dx = FP_OFF(name);       /* pathname */
    inregs.x.cx = 0;                 /* attributes */
    inregs.x.ax = 0x4E00;            /* GTJFN */
#ifdef LATTICE
    status = intdosx (&inregs, &outregs, &segregs);
    if (status & 1) {
#else
    outregs.x.cflag = 0;
    status = intdosx (&inregs, &outregs, &segregs);
    if (outregs.x.cflag || (status & 1)) {
#endif
#if DEBUG
        printf ("File \"%s\" does not exist\n", name);
#endif
        return 0L;
    }

    segregs.ds = dma_seg;           /* restore DMA address */
    inregs.x.dx = dma_off;
    inregs.x.ax = 0x1A00;
    intdosx (&inregs, &outregs, &segregs);

#if DEBUG
    printf ("filespec = \"%s\", date = %4x, time = %4x, sizel = %d\n",
        find_buf.pname, find_buf.date, find_buf.time, find_buf.size_l);
#endif
    datetime = (ulong) find_buf.date;
    datetime = datetime << 16;
    datetime = datetime + find_buf.time;
printf("'%s' %ld\n",find_buf.pname,datetime);
    return datetime;
}

/*
 * make (name)
 *
 *      This routine actually does the work.  It scans the list of
 *      targets parsed from the makefile, and checks the target's
 *      prerequisites date/time values against the target's.  If
 *      the prerequisite is itself a target (present in target_list),
 *      we call make recursively to check it.  Then, if any of our
 *      prerequisites are newer than we are, we execute all our shell
 *      commands.  If there are no prerequisites specified at all, then
 *      also execute all our shell commands.
 */

        int
make (targname)         /* use fnode instead of fname here */
    char *targname;
{          
    targptr targ;
    preqptr prereq;
    ulong NewestPreq=0;

#if DEBUG
    printf ("Making %s\n", targname);
#endif

    if ((targ = lookup_target (targname)) == 0)
       return TryDefault( targname );
    prereq = targ->PreqList; 
    if (prereq)
        {
        for ( ; prereq; prereq = prereq->next) {
            ulong date;
            make (prereq->file->fname);             /* recursively make */
            date =  getdatetime(prereq->file->fname);
            if (date > NewestPreq) NewestPreq = date;
        }
#if DEBUG
        printf ("Target \"%s\" datetime is %08lx, newest prereq is %08lx\n",
                targ->file->fname, getdatetime(targ->file->fname), NewestPreq);
#endif
        if (getdatetime(targ->file->fname) < NewestPreq) build (targ);
    }
    else build(targ);   /* if no prerequisites were listed, do it ! */

    if (targ->shell_list == NULL) {
        int i;
        dontworry ++;
        i = TryDefault( targname );
        dontworry --;
        return i;
        }
    return 1;
}


TryDefault(targname)
        char *targname;{
        targptr targ;
        char * ext = strchr (targname, '.');
        dontworry ++;
        if (ext != NULL)
            for (targ = target_list ; targ ; targ = targ -> next )
                if (targ->file->fname[0] == '*' &&
                    0 == strcmp ( ext , targ->file->fname+1 ) ) {
                        char * root = msubstr( targname , 0 , ext-targname );
                        char *cname;
                        int worked;
                        cname = mstrcat( root ,targ->PreqList->file->fname+1 );
                        worked = make ( cname ) ;
                        SetSymbol ( "*" , root ) ;
                        free(root);
                        if (!worked) {free(cname);  goto ret0;}
                        if (getdatetime(cname) <= getdatetime(targname))
			{	free(cname);
				goto ret1;
			}
			free(cname);
                        build ( targ ) ;
                        goto ret1;
                        }

        if (getdatetime(targname) > 0) goto ret1;

        ret0:                   /* unsuccessful return */
            if (--dontworry) return 0;
            else error ("Don't know how to make %s",targname);

        ret1:                   /* successful return */
            dontworry--;
            return 1;
        }




/*
 * build
 *
 *      Invoke shell commands to build a target file
 */

build (targ)
    targptr targ;
    {
    shellptr snode;
    char *cmd;
    int  runsts = 0;

#if DEBUG
    printf ("Building \"%s\"\n", targ->file->fname);
#endif
    for ( snode = targ->shell_list; snode; snode = snode->next, free(cmd) ) {
        char *p, **q, *cmdname;

        assert (snode->magic == SHELL_MAGIC);
        cmd = strperm(snode->command);
        breakout_symbols(&cmd);           /* notice that this may introduce a space at the beginning of the command line */
        cmdname = cmd; passpace(&cmdname);

        if (!snode->quiet)  fputs (cmdname, stdout);
        if (tracing) {
               puts ("");   /* EXEC does newline, so must be faked */
               continue;
               }

        p = cmdname ; passnonsp(&p);
        if (*p)  *p++ = EOS ;       /* terminate the name of the cmd */

                                  /* find whether it is a dos command */
        strlwr(cmdname);          /* lower  case for comparison */
        for (q=dos_commands ; *q ; q++) if (0==strcmp(*q,cmdname)) break;

        if (*q || snode->useshell)        /* must we use command.com ? */
#ifndef LATTICE
                if (0==strcmp(cmdname,"chdir") || 0==strcmp(cmdname,"cd"))
                        if (passpace (&p) , *p) {   /* chdir with arg */
                            char *q=p;
                            passnonsp(&q);  *q = EOS;
                            runsts = chdir(p);
                            }
                        else {                      /* chdir without arg */
                            char name[200];
                            if (getcwd(name,200)) {
                                if (!snode->quiet) putchar('\n');
                                fputs(name,stdout);
                                }
                            else error("path name too long");
                             }
                else
#endif
		        {                           /* resident command */
                        if (*p) *--p = ' ';         /* splice command line */
                        if (strlen(cmdname) > 128) error("shell command line too long");
                        runsts = system(cmdname);
                        }
        else    {                                   /* transient command */
                if (strlen(p)+1 > 128) error("shell command line too long");
                if (!snode->quiet) putchar ('\n');
#ifdef LATTICE
		runsts = forklp(cmdname,cmdname,"",p,NULL);
		if (runsts)			/* error		*/
			runsts = -1;
		else
			runsts = wait();	/* get return status	*/
#else
                runsts = spawnlp (P_WAIT, cmdname, "", p, NULL);
#endif
                /* can't use 'system()' here, because command.com does not
                   return the exit code of the program */
                }
        putchar('\n');      /* some programs do not end with return */
        if (runsts == -1) perror("program not found : "), exit(1);
        if (runsts > 0 && !snode->ignore && !ignore_errors)
              printf ( " --- return code %d ---\7", runsts),
              exit(runsts);
                     
    }
}

        targptr 
lookup_target (name)
char *name;
{
    targptr targ;
    for ( targ = target_list; targ ; targ = targ->next)
        if (strcmp (name, targ->file->fname) == 0) break;
    return targ;
}

breakout_symbols (cmdlinptr)
char **cmdlinptr; {
    char *cmdlin = *cmdlinptr, *cmd = talloc(LINESIZE+100);
    symbptr sym;
    char   symcmp[SYMLEN];
    int i, paren, cmdptr;

#if DEBUG
    printf("breakout_symbols (\"%s\")\n", cmdlin);
#endif
    /* this routine doesn't check for overflow of the line ! */

    strcpy ( cmd, "");
    cmdptr = 0;
    while (1) {
        while (*cmdlin != '$' && *cmdlin != EOS) {
                if (cmdptr >= LINESIZE) error ("Line too long after symbol substitution");
                cmd[cmdptr++] = *cmdlin++;
                }
        if (cmdptr >= LINESIZE) error ("Line too long after symbol substitution");
        cmd[cmdptr] = EOS;
        if (0==*cmdlin) break;            /* end of line */
        cmdlin++;               /* pass the $ */
        /* now we know we are looking at a symbol */
        if (*cmdlin == '(') paren = 1, cmdlin++; else paren=0;
        for (i = 0; i < SYMLEN-1 && (*cmdlin == '*' || isalnum (*cmdlin)); )
            symcmp[i++] = *cmdlin++;
        symcmp[i] = EOS;
        if (paren)
           if (*cmdlin == ')') cmdlin++;
           else puts ("No closing paren on shell line macro");
        for ( sym = SymbolList; 1 ; sym = sym->next) {
            if (sym==NULL)  error ("Undefined symbol %s", symcmp );
            assert (sym->magic == SYMBOL_MAGIC);
            if (strcmp (sym->token, symcmp) == 0) break;
        }
        strcpy ( cmd + cmdptr , sym->value );
        cmdptr = strlen ( cmd ) ;
    }

    free(*cmdlinptr);
    *cmdlinptr = strperm(cmd);
    free(cmd);

#if DEBUG
    printf ("breakout_symbols returning (\"%s\")\n", *cmdlinptr);
#endif
}

#ifdef LATTICE
strlwr(p)
char *p;
{
	while (*p)
	{	*p = tolower(*p);
		p++;
	}
}
#endif

-------------- mstring.c -----------------
/* mstring.c */

/* The purpose of this file is to provide subroutines for handling
   strings whose space is allocated with malloc - in this way we remove
   all limitations on length of strings */

#ifdef LATTICE
#include "mstring.h"
#include <ctype.h>
#include <stdio.h>
#else
#include "mstring.h"
#include <ctype.h>
#include <malloc.h>
#include <stdio.h>
#endif

#define DLEN 80

int linenumber = 0;

#ifdef LATTICE
char *realloc(p,newsize)
char *p;
int newsize;
{	char *pnew,*malloc();

	pnew = malloc(newsize);
	if (pnew)
		strcpy(pnew,p);
	free(p);
	return pnew;
}
#endif

        char
lastchar(p)
        char *p;{
        char c=0;
        while (*p) c = *p++;
        return c;
        }

        char *
endptr(p)
        char *p;{
        while (*p) p++;
        return p;
        }

        mstring
mfgets (stream)
        FILE *stream;{
        mstring p; int plen;
        if (feof(stream)) return NULL;
        p = talloc(plen = DLEN);
        p[0] = '\0';
        while (1) {
                if (strlen(p) + DLEN > plen) {
                        p = realloc(p, plen += DLEN);
                        if (p==NULL) puts("no more memory (mfgets)"), exit(1);
                        }
                if (NULL == fgets(endptr(p),DLEN,stream))
                        if (*p) return p;
                        else {
                                free(p);
                                return NULL;
                                }
                if (lastchar(p) != '\n') continue;
                linenumber++;
                endptr(p)[-1] = 0;
                if (lastchar(p) != '\\') {
                        p = realloc (p,strlen(p)+1) ;
                        if (p==NULL) puts("no more memory (mfgets)"), exit(1);
                        return p;
                        }
                endptr(p)[-1] = 0;
                }
        }

        mstring
msubstr(p,i,l)  /* creates a string from p[i],p[i+1],...,p[i+l-1] */
        mstring p;{
        mstring q;
        q = talloc(l+1);
        strncpy(q,p+i,l);
        q[l] = '\0';
        return q;
        }

        mstring
mstrcat(p,q)
        mstring p,q;{
        mstring r = talloc (strlen(p) + strlen(q) + 1);
        strcpy(r,p);
        strcat(r,q);
        return r;
        }

        mstring
strperm(s)
        char *s;{       /* allocate space for s, return new pointer */
        char *t = talloc(strlen(s)+1);
        strcpy(t,s);
        return t;
        }

passpace(p)
        char **p;{
        while (isspace (**p)) (*p)++;
        }

passnonsp(p)
        char **p;{
        while (**p && !isspace(**p)) (*p)++;
        }

password(p)
        char **p;{
        while (isalnum(**p)) (*p)++;
        }

error (errmsg,a,b,c,d,e,f,g,h)
char *errmsg;long a,b,c,d,e,f,g,h;
{
    /* unfortunately, this assumes only one file is being used */
    if (linenumber) fprintf(stderr,"at line %d : ",linenumber);
    fprintf(stderr,errmsg,a,b,c,d,e,f,g,h);
    exit (1);
}

        mstring
talloc(i)
        int i;{
        char *p;
        char *malloc();
        p = malloc(i);
        if (p==NULL) error ("no more memory");
        return p;
        }