Blob Blame History Raw
#define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */
#define _XOPEN_SOURCE 500 
   /* Make sure M_PI is in math.h, strdup is in string.h */
#define _BSD_SOURCE      /* Make sure strdup is in string.h (alternate) */

#include <string.h>
#include <ctype.h>
#include <assert.h>
#include <math.h>

#include "pm_c_util.h"
#include "shhopt.h"
#include "mallocvar.h"
#include "nstring.h"
#include "ppm.h"
#include "ppmdraw.h"
#include "ppmdfont.h"

static bool verbose;


static double
sindeg(double const angle) {

    return sin((double)angle / 360 * 2 * M_PI);
}


static double
cosdeg(double const angle) {

    return cos((double)angle / 360 * 2 * M_PI);
}


struct cmdlineInfo {
    /* All the information the user supplied in the command line,
       in a form easy for the program to use.
    */
    const char * inputFilename;  /* '-' if stdin */
    const char * scriptfile;     /* NULL means none. '-' means stdin */
    const char * script;         /* NULL means none */
    unsigned int verbose;
};



static void
parseCommandLine (int argc, const char ** argv,
                  struct cmdlineInfo * const cmdlineP) {
/*----------------------------------------------------------------------------
   parse program command line described in Unix standard form by argc
   and argv.  Return the information in the options as *cmdlineP.  

   If command line is internally inconsistent (invalid options, etc.),
   issue error message to stderr and abort program.

   Note that the strings we return are stored in the storage that
   was passed to us as the argv array.  We also trash *argv.
-----------------------------------------------------------------------------*/
    optEntry *option_def;
        /* Instructions to pm_optParseOptions3 on how to parse our options.
         */
    optStruct3 opt;

    unsigned int option_def_index;

    unsigned int scriptSpec, scriptfileSpec;

    MALLOCARRAY_NOFAIL(option_def, 100);

    option_def_index = 0;   /* incremented by OPTENT3 */
    OPTENT3(0, "script",      OPT_STRING,    &cmdlineP->script,
            &scriptSpec,      0);
    OPTENT3(0, "scriptfile",  OPT_STRING,    &cmdlineP->scriptfile,
            &scriptfileSpec,  0);
    OPTENT3(0, "verbose",     OPT_FLAG,      NULL,
            &cmdlineP->verbose, 0);


    opt.opt_table = option_def;
    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */

    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
    
    if (!scriptSpec && !scriptfileSpec)
        pm_error("You must specify either -script or -scriptfile");

    if (scriptSpec && scriptfileSpec)
        pm_error("You may not specify both -script and -scriptfile");

    if (!scriptSpec)
        cmdlineP->script = NULL;
    if (!scriptfileSpec)
        cmdlineP->scriptfile = NULL;

    if (argc-1 < 1) {
        if (cmdlineP->scriptfile && strcmp(cmdlineP->scriptfile, "-") == 0)
            pm_error("You can't specify Standard Input for both the "
                     "input image and the script file");
        else
            cmdlineP->inputFilename = "-";
    }
    else if (argc-1 == 1)
        cmdlineP->inputFilename = argv[1];
    else
        pm_error("Program takes at most one argument:  input file name");
}


struct pos {
    unsigned int x;
    unsigned int y;
};

struct drawState {
    struct pos currentPos;
    pixel color;
};



static void
initDrawState(struct drawState * const drawStateP,
              pixval             const maxval) {

    drawStateP->currentPos.x = 0;
    drawStateP->currentPos.y = 0;
    PPM_ASSIGN(drawStateP->color, maxval, maxval, maxval);
}



static void
readScriptFile(const char *  const scriptFileName,
               const char ** const scriptP) {

    FILE * scriptFileP;
    char * script;
    size_t scriptAllocation;
    size_t bytesReadSoFar;

    scriptAllocation = 4096;

    MALLOCARRAY(script, scriptAllocation);

    if (script == NULL)
        pm_error("out of memory reading script from file");

    scriptFileP = pm_openr(scriptFileName);

    bytesReadSoFar = 0;
    while (!feof(scriptFileP)) {
        size_t bytesRead;

        if (scriptAllocation - bytesReadSoFar < 2) {
            scriptAllocation += 4096;
            REALLOCARRAY(script, scriptAllocation);
            if (script == NULL)
                pm_error("out of memory reading script from file");
        }
        bytesRead = fread(script + bytesReadSoFar, 1,
                          scriptAllocation - bytesReadSoFar - 1, scriptFileP);
        bytesReadSoFar += bytesRead;
    }
    pm_close(scriptFileP);

    {
        unsigned int i;
        for (i = 0; i < bytesReadSoFar; ++i)
            if (!isprint(script[i]) && !isspace(script[i]))
                pm_error("Script contains byte that is not printable ASCII "
                         "character: 0x%02x", script[i]);
    }
    script[bytesReadSoFar] = '\0';  /* terminating NUL */

    *scriptP = script;
}



enum drawVerb {
    VERB_SETPOS,
    VERB_SETLINETYPE,
    VERB_SETLINECLIP,
    VERB_SETCOLOR,
    VERB_SETFONT,
    VERB_LINE,
    VERB_LINE_HERE,
    VERB_SPLINE3,
    VERB_CIRCLE,
    VERB_FILLEDCIRCLE,
    VERB_FILLEDRECTANGLE,
    VERB_TEXT,
    VERB_TEXT_HERE
};

struct setposArg {
    int x;
    int y;
};

struct setlinetypeArg {
    int type;
};

struct setlineclipArg {
    unsigned int clip;
};

struct setcolorArg {
    const char * colorName;
};

struct setfontArg {
    const char * fontFileName;
};

struct lineArg {
    int x0;
    int y0;
    int x1;
    int y1;
};

struct lineHereArg {
    int right;
    int down;
};

struct spline3Arg {
    int x0;
    int y0;
    int x1;
    int y1;
    int x2;
    int y2;
};

struct circleArg {
    int cx;
    int cy;
    unsigned int radius;
};

struct filledrectangleArg {
    int x;
    int y;
    unsigned int width;
    unsigned int height;
};

struct textArg {
    int xpos;
    int ypos;
    unsigned int height;
    int angle;
    const char * text;
};


struct drawCommand {
    enum drawVerb verb;
    union {
        struct setposArg          setposArg;
        struct setlinetypeArg     setlinetypeArg;
        struct setlineclipArg     setlineclipArg;
        struct setcolorArg        setcolorArg;
        struct setfontArg         setfontArg;
        struct lineArg            lineArg;
        struct lineHereArg        lineHereArg;
        struct spline3Arg         spline3Arg;
        struct circleArg          circleArg;
        struct filledrectangleArg filledrectangleArg;
        struct textArg            textArg;
    } u;
};



static void
freeDrawCommand(const struct drawCommand * const commandP) {
    
    switch (commandP->verb) {
    case VERB_SETPOS:
        break;
    case VERB_SETLINETYPE:
        break;
    case VERB_SETLINECLIP:
        break;
    case VERB_SETCOLOR:
        pm_strfree(commandP->u.setcolorArg.colorName);
        break;
    case VERB_SETFONT:
        pm_strfree(commandP->u.setfontArg.fontFileName);
        break;
    case VERB_LINE:
        break;
    case VERB_LINE_HERE:
        break;
    case VERB_SPLINE3:
        break;
    case VERB_CIRCLE:
        break;
    case VERB_FILLEDCIRCLE:
        break;
    case VERB_FILLEDRECTANGLE:
        break;
    case VERB_TEXT:
    case VERB_TEXT_HERE:
        pm_strfree(commandP->u.textArg.text);
        break;
    }
    

    free((void *) commandP);
}



struct commandListElt {
    struct commandListElt * nextP;
    const struct drawCommand * commandP;
};


struct script {
    struct commandListElt * commandListHeadP;
    struct commandListElt * commandListTailP;
};



static void
freeScript(struct script * const scriptP) {

    struct commandListElt * p;
    struct commandListElt * nextP;

    for (p = scriptP->commandListHeadP; p; p = nextP) {
        freeDrawCommand(p->commandP);
        nextP = p->nextP;
        free(p);
    }

    free(scriptP);
}



static void
doFilledCircle(pixel **                   const pixels,
               unsigned int               const cols,
               unsigned int               const rows,
               pixval                     const maxval,
               const struct drawCommand * const commandP,
               const struct drawState *   const drawStateP) {

    struct fillobj * fhP;

    fhP = ppmd_fill_create();
            
    ppmd_circle(pixels, cols, rows, maxval,
                commandP->u.circleArg.cx,
                commandP->u.circleArg.cy,
                commandP->u.circleArg.radius,
                ppmd_fill_drawproc,
                fhP);
            
    ppmd_fill(pixels, cols, rows, maxval,
              fhP,
              PPMD_NULLDRAWPROC,
              &drawStateP->color);

    ppmd_fill_destroy(fhP);
} 



static void
doTextHere(pixel **                   const pixels,
           unsigned int               const cols,
           unsigned int               const rows,
           pixval                     const maxval,
           const struct drawCommand * const commandP,
           struct drawState *         const drawStateP) {
    
    ppmd_text(pixels, cols, rows, maxval,
              drawStateP->currentPos.x,
              drawStateP->currentPos.y,
              commandP->u.textArg.height,
              commandP->u.textArg.angle,
              commandP->u.textArg.text,
              PPMD_NULLDRAWPROC,
              &drawStateP->color);
    
    {
        int left, top, right, bottom;
        
        ppmd_text_box(commandP->u.textArg.height, 0,
                      commandP->u.textArg.text,
                      &left, &top, &right, &bottom);
        

        drawStateP->currentPos.x +=
            ROUND((right-left) * cosdeg(commandP->u.textArg.angle));
        drawStateP->currentPos.y -=
            ROUND((right-left) * sindeg(commandP->u.textArg.angle));
    }
}



static void
executeScript(struct script * const scriptP,
              pixel **        const pixels,
              unsigned int    const cols,
              unsigned int    const rows,
              pixval          const maxval) {

    struct drawState drawState;
    unsigned int seq;
        /* Sequence number of current command (0 = first, etc.) */
    struct commandListElt * p;
        /* Pointer to current element in command list */

    initDrawState(&drawState, maxval);

    for (p = scriptP->commandListHeadP, seq = 0; p; p = p->nextP, ++seq) {
        const struct drawCommand * const commandP = p->commandP;

        if (verbose)
            pm_message("Command %u: %u", seq, commandP->verb);

        switch (commandP->verb) {
        case VERB_SETPOS:
            drawState.currentPos.x = commandP->u.setposArg.x;
            drawState.currentPos.y = commandP->u.setposArg.y;
            break;
        case VERB_SETLINETYPE:
            ppmd_setlinetype(commandP->u.setlinetypeArg.type);
            break;
        case VERB_SETLINECLIP:
            ppmd_setlineclip(commandP->u.setlineclipArg.clip);
            break;
        case VERB_SETCOLOR:
            drawState.color =
                ppm_parsecolor2(commandP->u.setcolorArg.colorName,
                                maxval, TRUE);
            break;
        case VERB_SETFONT: {
            FILE * ifP;
            const struct ppmd_font * fontP;
            ifP = pm_openr(commandP->u.setfontArg.fontFileName);
            ppmd_read_font(ifP, &fontP);
            ppmd_set_font(fontP);
            pm_close(ifP);
        } break;
        case VERB_LINE:
            ppmd_line(pixels, cols, rows, maxval,
                      commandP->u.lineArg.x0, commandP->u.lineArg.y0,
                      commandP->u.lineArg.x1, commandP->u.lineArg.y1,
                      PPMD_NULLDRAWPROC,
                      &drawState.color);
            break;
        case VERB_LINE_HERE: {
            struct pos endPos;

            endPos.x = drawState.currentPos.x + commandP->u.lineHereArg.right;
            endPos.y = drawState.currentPos.y + commandP->u.lineHereArg.down;

            ppmd_line(pixels, cols, rows, maxval,
                      drawState.currentPos.x, drawState.currentPos.y,
                      endPos.x, endPos.y,
                      PPMD_NULLDRAWPROC,
                      &drawState.color);
            drawState.currentPos = endPos;
        } break;
        case VERB_SPLINE3:
            ppmd_spline3(pixels, cols, rows, maxval,
                         commandP->u.spline3Arg.x0,
                         commandP->u.spline3Arg.y0,
                         commandP->u.spline3Arg.x1,
                         commandP->u.spline3Arg.y1,
                         commandP->u.spline3Arg.x2,
                         commandP->u.spline3Arg.y2,
                         PPMD_NULLDRAWPROC,
                         &drawState.color);
            break;
        case VERB_CIRCLE:
            ppmd_circle(pixels, cols, rows, maxval,
                        commandP->u.circleArg.cx,
                        commandP->u.circleArg.cy,
                        commandP->u.circleArg.radius,
                        PPMD_NULLDRAWPROC,
                        &drawState.color);
            break;
        case VERB_FILLEDCIRCLE:
            doFilledCircle(pixels, cols, rows, maxval, commandP, &drawState);
            break;
        case VERB_FILLEDRECTANGLE:
            ppmd_filledrectangle(pixels, cols, rows, maxval,
                                 commandP->u.filledrectangleArg.x,
                                 commandP->u.filledrectangleArg.y,
                                 commandP->u.filledrectangleArg.width,
                                 commandP->u.filledrectangleArg.height,
                                 PPMD_NULLDRAWPROC,
                                 &drawState.color);
            break;
        case VERB_TEXT:
            ppmd_text(pixels, cols, rows, maxval,
                      commandP->u.textArg.xpos,
                      commandP->u.textArg.ypos,
                      commandP->u.textArg.height,
                      commandP->u.textArg.angle,
                      commandP->u.textArg.text,
                      PPMD_NULLDRAWPROC,
                      &drawState.color);
            break;
        case VERB_TEXT_HERE:
            doTextHere(pixels, cols, rows, maxval, commandP, &drawState);
            break;
        }
    }
}



struct tokenSet {
    
    const char * token[10];
    unsigned int count;
    
};



static void
parseDrawCommand(struct tokenSet             const commandTokens,
                 const struct drawCommand ** const drawCommandPP) {

    struct drawCommand * drawCommandP;

    if (commandTokens.count < 1)
        pm_error("No tokens in command.");
    else {
        const char * const verb = commandTokens.token[0];

        MALLOCVAR(drawCommandP);
        if (drawCommandP == NULL)
            pm_error("Out of memory to parse '%s' command", verb);

        if (streq(verb, "setpos")) {
            drawCommandP->verb = VERB_SETPOS;
            if (commandTokens.count < 3)
                pm_error("Not enough tokens for a 'setpos' command.  "
                         "Need %u.  Got %u", 3, commandTokens.count);
            else {
                drawCommandP->u.setposArg.x = atoi(commandTokens.token[1]);
                drawCommandP->u.setposArg.y = atoi(commandTokens.token[2]);
            }
        } else if (streq(verb, "setlinetype")) {
            drawCommandP->verb = VERB_SETLINETYPE;
            if (commandTokens.count < 2)
                pm_error("Not enough tokens for a 'setlinetype' command.  "
                         "Need %u.  Got %u", 2, commandTokens.count);
            else {
                const char * const typeArg = commandTokens.token[1];
                if (streq(typeArg, "normal"))
                    drawCommandP->u.setlinetypeArg.type = PPMD_LINETYPE_NORMAL;
                else if (streq(typeArg, "normal"))
                    drawCommandP->u.setlinetypeArg.type = 
                        PPMD_LINETYPE_NODIAGS;
                else
                    pm_error("Invalid type");
            }
        } else if (streq(verb, "setlineclip")) {
            drawCommandP->verb = VERB_SETLINECLIP;
            if (commandTokens.count < 2)
                pm_error("Not enough tokens for a 'setlineclip' command.  "
                         "Need %u.  Got %u", 2, commandTokens.count);
            else
                drawCommandP->u.setlineclipArg.clip =
                    atoi(commandTokens.token[1]);
        } else if (streq(verb, "setcolor")) {
            drawCommandP->verb = VERB_SETCOLOR;
            if (commandTokens.count < 2)
                pm_error("Not enough tokens for a 'setcolor' command.  "
                         "Need %u.  Got %u", 2, commandTokens.count);
            else
                drawCommandP->u.setcolorArg.colorName =
                    strdup(commandTokens.token[1]);
        } else if (streq(verb, "setfont")) {
            drawCommandP->verb = VERB_SETFONT;
            if (commandTokens.count < 2)
                pm_error("Not enough tokens for a 'setfont' command.  "
                         "Need %u.  Got %u", 2, commandTokens.count);
            else
                drawCommandP->u.setfontArg.fontFileName =
                    strdup(commandTokens.token[1]);
        } else if (streq(verb, "line")) {
            drawCommandP->verb = VERB_LINE;
            if (commandTokens.count < 5)
                pm_error("Not enough tokens for a 'line' command.  "
                         "Need %u.  Got %u", 5, commandTokens.count);
            else {
                drawCommandP->u.lineArg.x0 = atoi(commandTokens.token[1]);
                drawCommandP->u.lineArg.y0 = atoi(commandTokens.token[2]);
                drawCommandP->u.lineArg.x1 = atoi(commandTokens.token[3]);
                drawCommandP->u.lineArg.y1 = atoi(commandTokens.token[4]);
            } 
        } else if (streq(verb, "line_here")) {
            drawCommandP->verb = VERB_LINE_HERE;
            if (commandTokens.count < 3)
                pm_error("Not enough tokens for a 'line_here' command.  "
                         "Need %u.  Got %u", 3, commandTokens.count);
            else {
                struct lineHereArg * const argP =
                    &drawCommandP->u.lineHereArg;
                argP->right = atoi(commandTokens.token[1]);
                argP->down = atoi(commandTokens.token[2]);
            } 
       } else if (streq(verb, "spline3")) {
            drawCommandP->verb = VERB_SPLINE3;
            if (commandTokens.count < 7)
                pm_error("Not enough tokens for a 'spline3' command.  "
                         "Need %u.  Got %u", 7, commandTokens.count);
            else {
                struct spline3Arg * const argP =
                    &drawCommandP->u.spline3Arg;
                argP->x0 = atoi(commandTokens.token[1]);
                argP->y0 = atoi(commandTokens.token[2]);
                argP->x1 = atoi(commandTokens.token[3]);
                argP->y1 = atoi(commandTokens.token[4]);
                argP->x2 = atoi(commandTokens.token[5]);
                argP->y2 = atoi(commandTokens.token[6]);
            } 
        } else if (streq(verb, "circle")) {
            drawCommandP->verb = VERB_CIRCLE;
            if (commandTokens.count < 4)
                pm_error("Not enough tokens for a 'circle' command.  "
                         "Need %u.  Got %u", 4, commandTokens.count);
            else {
                struct circleArg * const argP = &drawCommandP->u.circleArg;
                argP->cx     = atoi(commandTokens.token[1]);
                argP->cy     = atoi(commandTokens.token[2]);
                argP->radius = atoi(commandTokens.token[3]);
            } 
        } else if (streq(verb, "filledcircle")) {
            drawCommandP->verb = VERB_FILLEDCIRCLE;
            if (commandTokens.count < 4)
                pm_error("Not enough tokens for a 'filledcircle' command.  "
                         "Need %u.  Got %u", 4, commandTokens.count);
            else {
                struct circleArg * const argP = &drawCommandP->u.circleArg;
                argP->cx     = atoi(commandTokens.token[1]);
                argP->cy     = atoi(commandTokens.token[2]);
                argP->radius = atoi(commandTokens.token[3]);
            } 
        } else if (streq(verb, "filledrectangle")) {
            drawCommandP->verb = VERB_FILLEDRECTANGLE;
            if (commandTokens.count < 5)
                pm_error("Not enough tokens for a 'filledrectangle' command.  "
                         "Need %u.  Got %u", 4, commandTokens.count);
            else {
                struct filledrectangleArg * const argP =
                    &drawCommandP->u.filledrectangleArg;
                argP->x      = atoi(commandTokens.token[1]);
                argP->y      = atoi(commandTokens.token[2]);
                argP->width  = atoi(commandTokens.token[3]);
                argP->height = atoi(commandTokens.token[4]);
            } 
        } else if (streq(verb, "text")) {
            drawCommandP->verb = VERB_TEXT;
            if (commandTokens.count < 6)
                pm_error("Not enough tokens for a 'text' command.  "
                         "Need %u.  Got %u", 6, commandTokens.count);
            else {
                drawCommandP->u.textArg.xpos  = atoi(commandTokens.token[1]);
                drawCommandP->u.textArg.ypos  = atoi(commandTokens.token[2]);
                drawCommandP->u.textArg.height= atoi(commandTokens.token[3]);
                drawCommandP->u.textArg.angle = atoi(commandTokens.token[4]);
                drawCommandP->u.textArg.text  = strdup(commandTokens.token[5]);
                if (drawCommandP->u.textArg.text == NULL)
                    pm_error("Out of storage parsing 'text' command");
            }
        } else if (streq(verb, "text_here")) {
            drawCommandP->verb = VERB_TEXT_HERE;
            if (commandTokens.count < 4)
                pm_error("Not enough tokens for a 'text_here' command.  "
                         "Need %u.  Got %u", 4, commandTokens.count);
            else {
                drawCommandP->u.textArg.height= atoi(commandTokens.token[1]);
                drawCommandP->u.textArg.angle = atoi(commandTokens.token[2]);
                drawCommandP->u.textArg.text  = strdup(commandTokens.token[3]);
                if (drawCommandP->u.textArg.text == NULL)
                    pm_error("Out of storage parsing 'text_here' command");
            }
        } else
            pm_error("Unrecognized verb '%s'", verb);
    }
    *drawCommandPP = drawCommandP;
}



static void
disposeOfCommandTokens(struct tokenSet * const tokenSetP,
                       struct script *   const scriptP) {

    /* We've got a whole command in 'tokenSet'.  Parse it into *scriptP
       and reset tokenSet to empty.
    */
    
    struct commandListElt * commandListEltP;
    
    MALLOCVAR(commandListEltP);
    if (commandListEltP == NULL)
        pm_error("Out of memory allocating command list element frame");

    parseDrawCommand(*tokenSetP, &commandListEltP->commandP);

    {
        unsigned int i;
        for (i = 0; i < tokenSetP->count; ++i)
            pm_strfree(tokenSetP->token[i]);
        tokenSetP->count = 0;
    }
    /* Put the list element for this command at the tail of the list */
    commandListEltP->nextP = NULL;
    if (scriptP->commandListTailP)
        scriptP->commandListTailP->nextP = commandListEltP;
    else
        scriptP->commandListHeadP = commandListEltP;

    scriptP->commandListTailP = commandListEltP;
}



static void
processToken(const char *      const scriptText,
             unsigned int      const cursor,
             unsigned int      const tokenStart,
             struct script *   const scriptP,
             struct tokenSet * const tokenSetP) {

    char * token;
    unsigned int const tokenLength = cursor - tokenStart;
    MALLOCARRAY_NOFAIL(token, tokenLength + 1);
    memcpy(token, &scriptText[tokenStart], tokenLength);
    token[tokenLength] = '\0';
    
    if (streq(token, ";")) {
        disposeOfCommandTokens(tokenSetP, scriptP);
        free(token);
    } else {
        if (tokenSetP->count >= ARRAY_SIZE(tokenSetP->token))
            pm_error("too many tokens");
        else
            tokenSetP->token[tokenSetP->count++] = token;
    }
}



static void
parseScript(const char *     const scriptText,
            struct script ** const scriptPP) {

    struct script * scriptP;
    unsigned int cursor;  /* cursor in scriptText[] */
    bool intoken;      /* Cursor is inside token */
    unsigned int tokenStart;
        /* Position in 'scriptText' where current token starts.
           Meaningless if 'intoken' is false.
        */
    bool quotedToken;
        /* Current token is a quoted string.  Meaningless if 'intoken'
           is false 
        */
    struct tokenSet tokenSet;

    MALLOCVAR_NOFAIL(scriptP);

    scriptP->commandListHeadP = NULL;
    scriptP->commandListTailP = NULL;

    /* A token begins with a non-whitespace character.  A token ends before
       a whitespace character or semicolon or end of script, except that if
       the token starts with a double quote, whitespace and semicolon don't
       end it and another double quote does.

       Semicolon (unquoted) is a token by itself.
    */

    tokenSet.count = 0;
    intoken = FALSE;
    tokenStart = 0;
    cursor = 0;

    while (scriptText[cursor] != '\0') {
        char const scriptChar = scriptText[cursor];
        
        if (intoken) {
            if ((quotedToken && scriptChar == '"') ||
                (!quotedToken && (isspace(scriptChar) || scriptChar == ';'))) {
                /* We've passed a token. */

                processToken(scriptText, cursor, tokenStart, scriptP,
                             &tokenSet);

                intoken = FALSE;
                if (scriptChar != ';')
                    ++cursor;
            } else
                ++cursor;
        } else {
            if (!isspace(scriptChar)) {
                /* A token starts here */

                if (scriptChar == ';')
                    /* It ends here too -- semicolon is token by itself */
                    processToken(scriptText, cursor+1, cursor, scriptP,
                                 &tokenSet);
                else {
                    intoken = TRUE;
                    quotedToken = (scriptChar == '"');
                    if (quotedToken)
                        tokenStart = cursor + 1;
                    else
                        tokenStart = cursor;
                }
            }
            ++cursor;
        }            
    }

    if (intoken) {
        /* Parse the last token, which was terminated by end of string */
        if (quotedToken)
            pm_error("Script ends in the middle of a quoted string");
        processToken(scriptText, cursor, tokenStart, scriptP, &tokenSet);
    }

    if (tokenSet.count > 0) {
        /* Parse the last command, which was not terminated with a semicolon.
         */
        disposeOfCommandTokens(&tokenSet, scriptP);
    }

    *scriptPP = scriptP;
}



static void
getScript(struct cmdlineInfo const cmdline,
          struct script **   const scriptPP) {

    const char * scriptText;

    if (cmdline.script) {
        scriptText = strdup(cmdline.script);
        if (scriptText == NULL)
            pm_error("Out of memory creating script");
    } else if (cmdline.scriptfile)
        readScriptFile(cmdline.scriptfile, &scriptText);
    else
        pm_error("INTERNAL ERROR: no script");

    if (verbose)
        pm_message("Executing script '%s'", scriptText);

    parseScript(scriptText, scriptPP);

    pm_strfree(scriptText);
}

          

static void
doOneImage(FILE *          const ifP,
           struct script * const scriptP)  {

    pixel ** pixels;
    pixval maxval;
    int rows, cols;
    
    pixels = ppm_readppm(ifP, &cols, &rows, &maxval);
    
    executeScript(scriptP, pixels, cols, rows, maxval);
    
    ppm_writeppm(stdout, pixels, cols, rows, maxval, 0);
    
    ppm_freearray(pixels, rows);
}



int
main(int argc, const char * argv[]) {

    struct cmdlineInfo cmdline;
    FILE * ifP;
    struct script * scriptP;
    int eof;

    pm_proginit(&argc, argv);

    parseCommandLine(argc, argv, &cmdline);

    verbose = cmdline.verbose;

    ifP = pm_openr(cmdline.inputFilename);

    getScript(cmdline, &scriptP);

    eof = FALSE;
    while (!eof) {
        doOneImage(ifP, scriptP);
        ppm_nextimage(ifP, &eof);
    }

    freeScript(scriptP);

    pm_close(ifP);

    /* If the program failed, it previously aborted with nonzero completion
       code, via various function calls.
    */
    return 0;
}