. . . .

Language Design

 

updated:2016.07.13

Prev   Next   Site Map   Home  
Text Size
Please reload page for style change


ROBOT SCRIPT COMPILER BISON/YACC/BNF GRAMMAR

See sample handler robot video

/****************************************************************************
* FSQYAC.Y
* Description: Robot script compiler BNF Grammar
*
* -------------------------- NOTES -----------------------------------------
* - See y.out for YACC report, fsqyac.out for BISON.
*
* - Empty (epsilon) production parse stack error with BISON. This version of
* BISON (Jan. 7, 1989) screws up the parse stack if an empty production passes
* any value up the stack (i.e. yylval.xx = x). Any rule that generates an
* empty production must either pass values only in the non-empty productions,
* pass values around the parse stack using a global, or emit code directly.
* For examples, see unitSelectOpt and onOffOpt. Note, this is not a rule
* conflict, e.g. reduce-reduce (which is what it might look like in debugRead)
* but a bug in BISON.
*
*                  ----- type clash -----
* Bison creates a default action if no action is specified.
* This can produce irrelevant "type clash" warnings. To get rid of these, just
* put a dummy action.
*                   ---- yyval ------
* Use yyval instead of macro $$ to pass value up the parse stack, because it
* is easier to debug.
*                   ---- yyvsp ------
* To get ystack values of production elements use the parse stack pointer with
* 0 or negative indices. PSTKP[0] = right-most element. PSTKP[-1] is next
* element to the left, etc. If Bison then PSTKP = yyvsp, if YACC then yypvt.
* The actual name (e.g. yyvsp) is used instead of a macro to aid debugging,
* but it must be changed if the parser generator changes.
* For reference, macro definitions are $1 = left-most, $2 = next to right, etc.
*
*                     out-of-range UINT
* The parse stack doesn't include a long and the only commands that would
* accept one are read and write memory. The scanner converts text ints longer
* than UINT by rolling over at the UINT limit. To detect out-of-range int
* text, test the value of scanLong. Since there is only one of these, it must
* be tested before any other numbers are scanned.
*
****************************************************************************/
/*****************************************************************************
*                               YACC DECLARATIONS
*****************************************************************************/
%union                      /* value stack type directive */
{
    double          dval;
    int             ival;
    unsigned int    ui;
    unsigned char   uc;
};

/*------------------ TERMINALS ---------------------------------------------*/
/*.................. Shared by fsqyac and slyac ............................*/
/* If this section is not identical in fsqyac.y and slyac.y then slshare.c
   must be remade when alternately building fsq and sl compilers. */

                     /* General-purpose operators and operands */
%term   '='
%term   T_NEQ T_LEQ T_GEQ '<' '>'
%left   '&' '|' '^'
%left   '+' '-'
%left   '*' '/' '%'
%left   NEG       /* Unary minus pseudo-token for precedence. */
%left   T_LSHIFT T_RSHIFT
%left   '(' ')'
%term   T_ENDLINE
%term   T_SYM
%term   T_UINT   /* Scanner returns ui. Parser recognizes signed int by
    sint -> '-' T_UINT. Use T_UINT directly if possible for speed but this
    means that + T_UINT is rejected for UINT. */
%term   T_UREAL  /* Similar to T_UINT */
%term   T_TUPLE         /* n:n to identify a space:address by number */
%term   T_STRING
                        /* Script Control Commands. */
%term   T_DUMMY
%term   T_PRAGMA
%term   T_DEFINE
                        /* Script Control Parameters */
%term   T_UNIT
%term   T_UNITNAME
%term   T_HANDLE
%term   T_FAULT_HANDLER
%term   T_THROUGH
%term   T_SEQLEVEL
%term   T_SEQREPORT
%term   T_SAYDONE
%term   T_SAYSTAT
                        /* Target Commands */
%term   T_TESTSYSTEM
%term   T_DEBUGREAD
%term   T_READ
%term   T_WRITE
%term   T_BEGIN
%term   T_TERMINATE
%term   T_END
%term   T_BREAK
%term   T_FORK
%term   T_HALT
%term   T_DELETE
%term   T_LOOP
%term   T_ENDLOOP
%term   T_ECHO
%term   T_ECHOLOG
%term   T_ECHOCOMMENT
%term   T_ECHOTITLE
%term   T_ECHOSTATE
%term   T_REPORT
%term   T_IF
%term   T_VARID
%term   T_SET
%term   T_PRESET
%term   T_CLEAR

/* OPEN, UP, DOWN, ON, OFF, LOW, MEDIUM, HIGH are overloaded. Any of them
* might be a parameter associated with a particular device. OPEN can also be a
* command, while ON and OFF can be general parameters. Consequently, they are
* not collected by the scanner state machine but by explicit testing after
* context-sensitive scanning has not claimed them (see classifySymbol in
* fsql.c ). Note that the CLOSE command word is not overloaded, because a
* better parameter name is "Closed" anyway. Thus, "CLOSE" is not available as
* a parameter. */
%term   T_OPEN
%term   T_UP
%term   T_DOWN
%term   T_ON
%term   T_OFF
%term   T_LOW
%term   T_MEDIUM
%term   T_HIGH

%term   T_SLEW      /* ramp parameter but not veryCommonWord. */
%term   T_RECOIL    /* " */
%term   T_HOLD      /* " */
%term   T_CLOSE     /* Command only. */
%term   T_GET
%term   T_SEND
%term   T_TESTMOTOR
%term   T_INITIALIZE
%term   T_RAMPDEF
%term   T_RAMP
%term   T_POWER
%term   T_MOVE
%term   T_STOP
%term   T_POSITION
%term   T_RETURN
%term   T_WAIT
%term   T_SIMULATE
%term   T_WATCH
%term   T_IN
%term   T_SAMPLE
%term   T_GATHER
%term   T_COUNT
%term   T_ENDIF
%term   T_ELSE
%term   T_RESET         /* Command or parameter. */
%term   T_VERSION       /* Script control command or target (read) parameter. */

                        /* Target Command Parameters */
%term   T_LIST
%term   T_FAST
%term   T_SLOW
%term   T_REAL
%term   T_ECELL
%term   T_CYCLE
%term   T_ALL
%term   T_MOTORS
%term   T_OTHERS
%term   T_SECONDS
%term   T_POWERDOWN
%term   T_MMOVE
%term   T_WATCHCOND     /* watch */
%term   T_UNTIL
%term   T_FOR
%term   T_MAXIMUM
%term   T_GOING         /* wait going script. */
%term   T_DONE          /* wait done script */
%term   T_TIMEOUT       /* if timeout */
%term   T_GATHERDONEACK
%term   T_GATHERDONE
%term   T_ACT           /* Actuator name */
%term   T_SENSOR        /* Sensor name */
%term   T_FLAG          /* Flag name */
%term   T_STATEDEV      /* State (device) name */
%term   T_CONFIG
%term   T_MSDEV
%term   T_COMDEV
%term   T_PEEK
%term   T_CHANNELDEV    /* Channel device, e.g. Dac, Gain, Prescale */
%term   T_CHANNEL
%term   T_SYSTEMSTATUS
%term   T_FAULTS
%term   T_NOHANDLER     /* For reports and get faults */
%term   T_TIME          /* VAR = time and set time */
%term   T_MAXLEVELGOING /* VAR = MaxLevelGoing */
%term   T_TOSECONDS     /* VAR = toSeconds */
%term   T_TOMSECS       /* VAR = toMsecs */
%term   T_FOREVER       /* set time xx = forever */
%term   T_MESSAGE       /* Used with msg device */
%term   T_LINEAR        /* Rampdef parameter. */
%term   T_STEP          /* " */
%term   T_EXCLUSIVE     /* " */
%term   T_LOAD          /* " */
%term   T_LOW
%term   T_MEDIUM
%term   T_HIGH
%term   T_FAILOK
%term   T_TO
%term   T_AS
%term   T_BY
%term   T_MPOWER
%term   T_MOVING        /* IfMotor condition. */
%term   T_SLAVECOM      /* Reset parameter. */
%term   T_IDLE          /* Reset and ramp parameter. */
%term   T_HARD          /* Reset parameter. */
%term   T_LEVEL         /* Reset parameter. */
%term   T_SIZE          /* Read/write mem tsize. ui = RW_BYTE, RW_WORD, RW_LONG */
%term   T_TEXT
%term   T_BINARY

%start seqList
%{
/*****************************************************************************
*                              C FILE DECLARATIONS
*****************************************************************************/
#define FSQYAC_Y
/* #define YYDEBUG
{ yydebug = 1; } */

/*................. Run-time Library Headers ..............................*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <malloc.h>
#include <stddef.h>
#include <math.h>
/*.................. Project Headers .......................................*/
#include "cdefs.h"
#include "names.h"
#include "utils.h"
#include "futil.h"
#include "fsqapi.h"  /* Universal script message definition. */
#include "fsqcomp.h"
#include "cfgsys.h"
#include "fsqsup.h"
#include "yshare.h"
#include "seqlib.h"
#include "motor.h"
#include "cmsg.h"
#include "seqrec.h"

/*------------------ DATA --------------------------------------------------*/
static USHORT   *usPtr;
static UCHAR    *ucPtr;
static int      nCounts;
static int      optionCnt;
union { UCHAR *bp; USHORT *wp; ULONG *lp; } itemArray;
static int      itemCnt;
static int      itemCntLimit;
static UINT     tsize; /* 1,2... */
static UINT     vtype; /* MSGTEXT, MSGBINARY */
static USHORT   usVal;
static int      comDevIdx;

#define MAXFSQNAMELEN 31
char    thisFsqName[ MAXFSQNAMELEN + 1 ];

#define ERR_REMVAR "Illegal use of local (0-7) VAR in remote command.\n"

static UCHAR    waitKind;
#define WAIT_UNKNOWN 0xFF /* Anything other than WAIT_FOR or WAIT_UNTIL. */

static enum    { ARG_VAL, ARG_VAR, ARG_REP } argType;
static enum    { MINUS_ARG, PLUS_ARG } plusMinusArg;
#define MINGVAR 8
static BOOL     locVarHasVal[ MINGVAR ];
static BOOL     msgWasWritten;
static NameAlias *nameBase;
static int      rampSegment;
enum { RAMP_LINEAR, RAMP_LINLIN, RAMP_LOG, RAMP_ARB };
static int      rampSlopeType;
static double   rampEndGrade;
static USHORT   *pRampId;
static BOOL     noRecoil;
static BOOL     prevalVar;
static BOOL     seqOpen = FALSE;
static UCHAR    onOffOption; /* To pass around pass BISON stack on empty production. */
static UINT     idNum; /* #UINT for special (usually debugging) purposes. 0 = not assigned. */
enum { PE_NOTHING, PE_RAMPSEG, PE_WAITSTEPPER } parseError = PE_NOTHING;
int     reparmType[] = { REP_BYTE, REP_SHORT, REP_LONG }; /* Select by tsize
        (which is 1, 2, or 4) divided by 2 to get index = 0, 1, 2 */
int     loopTimeLine;

void    initCmdState( void );
void    checkUiSize( void );
BOOL    checkMotorController( UINT unit, int ifNotFound );
void    checkNotSlewHold( void );
void    checkLocVar( UINT varid );
UINT    checkWatchSym( void );
UINT    checkWatchUint( UINT arg );
void    checkScriptNameLen( char *name );
void    fatalSyntax( void );
void    badDeviceName( void );
void    badStateName( void );
UINT    getUiDval( double val );

enum { OO_ON, OO_OFF, OO_EMPTY }; /* onOffOpt */
UINT onOffDo[] = {
    DO_ON,      /* T_ON */
    DO_OFF,     /* T_OFF */
    DO_ONCE,    /* empty */
};  /* onOffOpt to DO conversion table for debugRead. */

%}

%%
/*****************************************************************************
*                                 GRAMMAR RULES
*****************************************************************************/

seqList : 
        seqList decList flowseq
    |   decList flowseq
    |   'y'  /* Any illegal statement */
        {   // These actions are dummies to get rid of compiler warnings.
            printf( "%s %d\n", yytname, yyrline[0] ); // For Bison.
            YYERROR;  // yyerrlab reference for both Bison and YACC.
        }
    ;

decList :
        decList decl
    |   /* Empty */
    ;

decl :  /* These appear outside of scripts. */
        T_UNIT    
        {   expect = EXPECT_UNIT; /* Tell scanner to look up unit name. */ }
        optEqual unitSelectOpt
        {   globalUnit = cmdUnit; }
        endline

    |   T_VERSION optEqual vers endline

    |   defOrPragma   
    ;

vers :
        T_UINT { srcVersion = scanLong; }
    |   notUint { fatalError( -1, "Version must be an integer from 1 to 4294967295.\n" ); }
    ;

flowseq :
        begin       { seqOpen = TRUE; }
        statementList
        T_END
        {
            if( ifLevel > 0 )
                fatalError( -1, "END without ENDIF for IF on line %d.\n", ifLine );
            if( loopLevel > 0 )
                fatalError( -1, "END without ENDLOOP for LOOP on line %d.\n", loopLine );
            writeNoArgFsqMsg( CM_END );
            lastSeqRec->sid.crc = getCrc(); // Record CRC of this script and reset CRC calculator.
            //addForkSymbol( NULL, ENDFSQ );
            seqOpen = FALSE;  // Allows acceptance of end of file without newline.
        }
        endline

    |   begin T_END  /* Empty is OK during development. */
        {   //addForkSymbol(NULL, ENDFSQ); }

    |   T_DUMMY endline  /* Bracketing comment outside of a flowseq. */
    ;

begin :
        T_BEGIN
        T_SYM
        {
            beginLine = srcLine + 1;
            seqUnit = globalUnit; /* Unless changed by seqDefOption */
            initCmdState();
            checkScriptNameLen( yytext );
            strcpy( thisFsqName, yytext );
            /* storeFsqId( thisFsqName ); for listing. */
            fmsg.msgType = CM_BEGIN;
            fmsg.arg.begin.level = SEQLEVEL_MECHANICAL;
            fmsg.arg.begin.report = SEQ_NOREPORT;
            fmsg.arg.begin.sayDone = MSGCHAR_FALSE;
            //addForkSymbol( yytext, BEGINFSQ );
        }
        seqDefOptionList
        {   /* Option list may change unit, so this is the soonest that id can be known. */
            int     len;
            BOOL    newSeq;

            seqId = addSeqIfNew( &newSeq, thisFsqName, seqUnit );
            fmsg.arg.begin.id = swapUS( seqId );
            // cpyCsToPs( fmsg.arg.begin.name, thisFsqName );
            fmsg.arg.begin.name[0] = len = strlen( thisFsqName ) + 1;
            memcpy( fmsg.arg.begin.name + 1, thisFsqName, len );

            getCrc(); // Reset CRC calculator for this script.
            writeFsqMsg( sizeof( BeginCmd ) + fmsg.arg.begin.name[0]);

            memset( locVarHasVal, 0, sizeof( locVarHasVal )); /* All local
                            VARS have unknown value when a script begins. */
            if( fmsg.arg.begin.evLow != PADWORD )
            {
                locVarHasVal[0] = TRUE; /* Event passed via VAR0 */
                locVarHasVal[1] = TRUE; /* Two other args may also be passed. */
                locVarHasVal[2] = TRUE;
            }
            else
                fmsg.arg.begin.evLow = 0xFFFF; /* HANDLER_NO_FAULTS */
        }
        endline
    ;

seqDefOptionList :
        seqDefOptionList seqDefOption
    |   /* Empty */
    |   error   { fatalSyntax(); }
    ;

seqDefOption :
        T_HANDLE faultType
        {
            if( yyvsp[0].ui == HANDLER_ALL_FAULTS )
            {   /* set range to 1 to HANDLER_ALL_FAULTS */
                fmsg.arg.begin.evLow = 0x0100;
                fmsg.arg.begin.evHigh = swapUS( HANDLER_ALL_FAULTS );
            }
            else
            {   /* evLow = evHigh if handles one fault. */
                fmsg.arg.begin.evLow = swapUS( yyvsp[0].ui );
                fmsg.arg.begin.evHigh = swapUS( yyvsp[0].ui );
            }
        }
    |   T_HANDLE faultType T_THROUGH faultType
        {
            if( yyvsp[-2].ui > yyvsp[0].ui )
                fatalError( -1, "The lower event is higher than the upper.\n" );
            fmsg.arg.begin.evLow = swapUS( yyvsp[-2].ui );
            fmsg.arg.begin.evHigh = swapUS( yyvsp[0].ui );
        }
    |   T_UNIT optEqual unitSelectOpt { seqUnit = cmdUnit; }
    |   T_SEQLEVEL  { fmsg.arg.begin.level = yyvsp[0].ui; }
    |   T_SEQREPORT { fmsg.arg.begin.report = yyvsp[0].ui; }
    |   T_SAYDONE   { fmsg.arg.begin.sayDone = MSGCHAR_TRUE; }
    ;

faultType :
        T_UINT
        {   yyval.ui = yyvsp[0].ui; }
    |   T_FAULT_HANDLER
        {   yyval.ui = yyvsp[0].ui; }
    |   T_SYM
        {   yyval.ui = getFaultNumber(); }
/* Reserved words may also be used as fault types but each one must be
* explicitly named here but still searched for in name list in order to get
* the correct id number (0-127) without embedding in the scanner. e.g.
* BARCODE, SAMPLECOVER, RACKPOSITION, MIXERLIFTDOWN, MIXERLISTUP, MIXERTOP,
* MIXERVERTICAL, GS1HOME, GS2HOME. */
    ;

statementList :
        statementList statement
    |   statement
    ;

statement :
        command endline
    |   defOrPragma
    |   error
        {   /* Syntax (parse) error in one statement. */
            if( srcLine != errLine )
            {
                errLine = srcLine;
                if( *yytext == '\n' )
                    fatalError( -1, "Incomplete statement needs additional arguments.\n" );
                else if( *yytext == 0 )
                {
                    if( seqOpen == FALSE )
                        return 0;  // Accept end of file w/o newline.
                    fatalError( -1,
"End of file without end of script started at line %d.\n", beginLine );
                    return -1;
                }
                else
                    switch( parseError )
                    {
                    case PE_RAMPSEG:
                        fatalError( -1, "\"%s\" is not a ramp segment type. \
Types are UP, SLEW, DOWN, RECOIL, HOLD, and = rampname.\n", yytext );
                        break;

                    case PE_WAITSTEPPER:
                        fatalError( -1,
                          "Illegal wait for more than one stepper.\n" );
                        break;

                    default:
                        fatalSyntax();
                    }
            }
            yyerrok;
            yyclearin;
        }
    ;

defOrPragma :
        T_DEFINE T_SYM
        {
            lessLine = TRUE;
            createLocalDef();
            lessLine = FALSE;
        }
        emptyOrEndline /* DEFINE statement followed by "/n" or "//... /n" needs this. */

    |   T_PRAGMA onOffOpt T_SYM
        {
            setPragma( onOffOption != OO_OFF ); /* On and Empty = TRUE. Only Off = FALSE. */
        }
        emptyOrEndline
    ;

emptyOrEndline : /* Empty */ | T_ENDLINE

endline :
        T_ENDLINE 
        {
            initCmdState();
            expect = EXPECT_UNIT; /* for unitName VAR varop */
        }
    ;

/*************************  commands ****************************************/
command :
        T_TESTSYSTEM unitSelectOpt T_SYM
        {
            int idx;

            usPtr = fmsg.arg.sys.args;

            /* Is SYM a native testsystem function? Note, analyz.ini can't
               overload any of these names, because we check here first. */
            if(( idx = getsByName( yytext, testSystemFuncs,
              CASE_INSENSITIVE )) != -1 )
                fmsg.arg.sys.op = TS_BUSFAULT + idx;

            /* Is SYM an extended (defined in analyz.ini) system test? */
            else if(( namedev.nam = findNameAlias( systemTests, yytext )) != 0 )
                fmsg.arg.sys.op = FIRST_EXT_TSFUNC_CHAR + namedev.nam->val;

            else if(( namedev.mtr =
              (MotorAlias *)findAlias((BaseAlias*)motorDevs, yytext,
              CASE_INSENSITIVE, (BaseAlias*)namedev.mtr, UNIT_NONE )) != 0 )
            {
                if( namedev.mtr->mtype == MT_TRIMOTOR )
                {
                    fmsg.arg.sys.op = TS_TRIMOTOR;
                    fmsg.arg.sys.args[0] = swapUS( namedev.mtr->motorNumber );
                    usPtr = fmsg.arg.sys.args + 1;
                }
                else
                    fatalError( -1,
"There are no testSystem functions for motor \"%s\".\n", namedev.mtr->name );
            }
            else
                fatalError( -1, "Undefined testSystem function \"%s\".\n", yytext );
        }
        usList
        {   /* This accepts an argument list, which, in most cases, is a
               single 0/FALSE/1/TRUE value. */
            if( usPtr == fmsg.arg.sys.args )
                *usPtr++ = 0x0101; /* Default all TRUE/FALSE functions to
                TRUE, i.e. turn them on if statement doesn't specify. */
            fmsg.msgType = CM_SYSTEM;
            writeFsqMsg( sizeof( SysCmd ) +
              2 * ( usPtr - &fmsg.arg.sys.args[1] ));
        }

    |   T_DEBUGREAD
        {
            cmdType = T_IF;
            expUnitOr( EXPECT_BITDEV );
        }
        unitSelectOpt
        onOffOpt
        {
            fmsg.arg.debugRead.op = onOffDo[ onOffOption ]; /* DO_ONCE/ON/OFF */
        }
        debugReadArg
        {
            fmsg.msgType = CM_DEBUGREAD;
            writeFsqMsg( sizeof( DebugReadCmd ));
        }

    |   T_DUMMY

    |   T_READ unitSelectOpt readType

    |   T_WRITE unitSelectOpt writeType

    |   T_BEGIN
        {
            fatalError( -1,
        "Begin without closing the script started at line %d.\n", beginLine );
        }

    |   T_TERMINATE
        {
            if( ifLevel < 1 )
                fatalError( -1,
                 "Terminate outside IF block (use END to close script).\n" );
            writeNoArgFsqMsg( CM_TERMINATE );
        }

    |   T_BREAK
        {
            if( loopLevel == 0 )
                fatalError( -1, "BREAK can only occur in a loop block.\n" );
            else
                writeNoArgFsqMsg( CM_BREAK );
        }

    |   T_RETURN returnArg
        {
            fmsg.msgType = CM_RETURN;
            writeFsqMsg( sizeof( ReturnCmd ));
        }

    |   T_FORK unitSelectOpt T_SYM
        {   strcpy( tokenName, yytext ); }
        returnVar optFailOk
        {
            writeSeqCmd( T_FORK, tokenName, yyvsp[-1].ui );
            //addForkSymbol( tokenName, FORKFSQ );
        }

    |   fsqCmdType   /* Halt or Delete */
        {   expUnitOr( EXPECT_FSQNAME ); }
        unitSelectOpt
        fsqOp        /* name, OTHERS, or ALL */
        {
            if( fmsg.arg.fsq.who == FSQ_ID )
            {
                checkScriptNameLen( yytext );
                strcpy( tokenName, yytext );
            }
            else
                tokenName[0] = 0;
        }
        seqLevelOpt   /* SAFETY, MONITOR, CONFIG, MECHANICAL, PROCESS, or empty. */
        sayWhat
        {   writeSeqCmd( T_HALT, tokenName, 0 ); } /* write halt or delete. */

    |   T_LOOP
        loop
        {
            UCHAR   kind;

            if( loopLevel == 4 )
                fatalError( -1, "Loops can only be nested 4 deep.\n" );
            kind = fmsg.arg.loop.kind;
            if( loopLevel == 0 )
                loopTimeLine = kind == LOOP_CNT ? 0 : srcLine + 1;
            else if( kind == LOOP_TIME )
            {
                if( loopTimeLine != 0 )
                    fatalError( -1, "Loop for time nesting illegal. \
Line %d is already looping for time.\n", loopTimeLine );
                loopTimeLine = srcLine + 1;
            }
            fmsg.msgType = CM_LOOP;
            fmsg.arg.loop.cnt = swapUS( yyvsp[0].ui );
            writeFsqMsg( sizeof( LoopCmd ));
            if( loopLevel++ == 0 )
                loopLine = srcLine + 1;
        }

    |   T_ENDLOOP
        {
            writeNoArgFsqMsg( CM_ENDLOOP );
            if( --loopLevel < 0 )
                fatalError( -1, "ENDLOOP without matching LOOP\n" );
        }

    |   T_ECHO
        {
            fmsg.msgType = CM_ECHO;
            expect = EXPECT_COMDEV;
        }
        echo

    |   T_ECHOLOG
        {   fmsg.msgType = CM_ECHOLOG; }
        echo

    |   T_ECHOCOMMENT echoArg
        {
            fmsg.msgType = CM_ECHOCOMMENT; 
            writeFsqMsg( sizeof( EchoCmd ) + yyvsp[0].ui - 1 );
        }

    |   T_ECHOTITLE
        {
            nameBase = echoTitles;
            fmsg.msgType = CM_ECHOTITLE;
        }
        echoTs

    |   T_ECHOSTATE
        {
            nameBase = echoStates;
            fmsg.msgType = CM_ECHOSTATE;
        }
        echoTs

    |   T_REPORT reportID fullReport optNoHandler
        {
            fmsg.msgType = CM_REPORT;
            fmsg.arg.report.handle = yyvsp[0].ui;
            writeFsqMsg( sizeof( ReportCmd ) + yyvsp[-1].ui );
        }

    |   T_IF
        {
            cmdType = T_IF;
            if( ifLevel++ == 0 )
                ifLine = srcLine + 1;
            countOnes = 0;
            countZeros = 0;
            xAndOr = XANDOR_NOTHING; // To detect illegal mixing of AND and OR.
        }
        unitSelectOpt
        predicate

/*.................. SET TYPE COMMANDS .....................................*/
    |   T_SET
        {
            cmdType = T_SET;
            hasPowerDown = FALSE; /* Assume unless named device or overridden
* by POWERDOWN in source statement. This is meaningful only for the setList
* setType, but here is the last opportunity that we have before the list might
* begin and assigning hasPowerDown would wipe out a named device's pd. */
            countZeros = 0;
            countOnes = 0;
        }
        unitSelectOpt
        setType

    |   T_CLEAR
        {
            cmdType = T_CLEAR;
            hasPowerDown = FALSE; /* Assume unless named device or overridden
* by POWERDOWN in source statement. This assignment can't be pushed down to
* presetOrClear because the first device is scanned as a lookahead token
* before the presetOrClear production. */
        }
        presetOrClearOrComdev

    |   T_PRESET
        {
            cmdType = T_PRESET;
            hasPowerDown = FALSE;
        }
        presetOrClear
/* Can't use SET name... because this has a shift-reduce or reduce-reduce
   conflict with SET name = value ... */

    |   T_CLOSE
        {   cmdType = T_CLOSE; }
        openCloseAct

    |   T_OPEN
        {   cmdType = T_OPEN; }
        openCloseAct

    |   unitSelectOpt T_VARID arithop /* VAR = * + / - % */
        {
            UCHAR varid;

            if(( varid = yyvsp[-1].ui ) < MINGVAR )
            {
                if( cmdUnit != seqUnit )
                    fatalError( -1, ERR_REMVAR );
                else if( yyvsp[0].ui == OPASSIGN )
                    locVarHasVal[ varid ] = TRUE;
                else
                    checkLocVar( varid );
            }
            fmsg.msgType = CM_SETVAR;
            fmsg.arg.var.dvar = varid; /* wordVars destination index. */
            fmsg.arg.var.op = yyvsp[0].ui; /* OPASSIGN, OPOR, OPXOR, etc. */
            if( fmsg.arg.var.op == OPASSIGN )
                expect |= EXPECT_CHANDEV+EXPECT_COMDEV;
            msgWasWritten = FALSE;
            /* fmsg.arg.var.op2type and operand will be supplied by varop. */
        }
        varopOrExpr
        {   // In case of VARxx = channel device, msg is already written.
            if( !msgWasWritten )
                writeFsqMsg( sizeof( VarCmd ));
        }

    |   T_MESSAGE T_SYM
        {   findMsgDevice(); }
        optMsgArg
        {   writeMsgDevice( yyvsp[0].ui ); }

/*.................. GET ..................................................*/
    |   T_GET unitSelectOpt T_SYM { badDeviceName(); }
    |   T_GET unitSelectOpt T_CHANNELDEV chanNum
        {   writeGetChanDev( yyvsp[0].ui, NO_VAR ); }

    |   T_GET unitSelectOpt T_SYSTEMSTATUS
        {   writeNoArgFsqMsg( CM_SYSSTAT ); }

    |   T_GET unitSelectOpt T_FAULTS
        {   writeNoArgFsqMsg( CM_GETFAULTS ); }

    |   T_GET T_COMDEV T_SYM
        {
            comDevIdx = checkComDevItem();
        }
        optPeek
        {
            fmsg.arg.comDev.cmd = yyvsp[0].ui == 1 ? COMDEV_GETPEEK : COMDEV_GET;
            fmsg.arg.comDev.ucArg = namedev.com->itemIds[ comDevIdx ];
            writeComDev( FALSE );
        }

/* ........................................................................ */
    |   T_SEND T_COMDEV
        {
            fmsg.msgType = CM_SENDCOMDEV;
            cmdUnit = namedev.com->unit;
            fmsg.arg.sendComDev.id = namedev.com->space;
            itemArray.bp = fmsg.arg.sendComDev.msg;
            itemCntLimit = itemCnt = MAX_CMDAT - 1;
            tsize = 1;
        }
        charArray
        {
            writeFsqMsg( sizeof( SendComDevCmd ) - 1 + itemCntLimit - itemCnt );
        }

    |   T_SEND varOrUint optAs T_COMDEV T_SYM
        { /* Note that this generates CM_COMDEV, not CM_SENDCOMDEV. */
            fmsg.arg.comDev.cmd =
              argType == ARG_VAR ? COMDEV_SENDVARAS : COMDEV_SENDUIAS;
            comDevIdx = checkComDevItem(); // Is T_SYM an item of T_COMDEV?
            fmsg.arg.comDev.ucArg = namedev.com->itemIds[ comDevIdx ];
            fmsg.arg.comDev.usArgs[0] = swapUS( yyvsp[-3].ui );
            writeComDev( FALSE );
        }

/*.................. MOTOR COMMANDS ........................................*/
    |   T_POSITION oneMotor
        {
            if( namedev.mtr->mtype != MT_STEPPER )
                fatalError( -1,
"Position inapplicable to %s, which is not a stepper.\n", namedev.mtr->name );
            fmsg.msgType = CM_POSITION;
            fmsg.arg.position.motor = mtrIdx;
        }
        optPosition  /* Empty is position query, else set. */
        {
            writeFsqMsg( sizeof( PositionCmd ));
        }

    |   T_RAMPDEF
        {
            cmdType = T_RAMPDEF; // Tells WriteRampdef to select ramp IDs from "permanent" range.
            if( !checkMotorController( seqUnit, NOTFOUND_OK ))
                fatalError( -1, "%s missing STEP_RATE definition. \
Rampdef can only be executed by a direct motor controller.\n",
                  getUnitName( seqUnit ));
        }
        T_SYM
        {
            prepRampList( "Rampdef" ); /* Clear rampCmd and write rampdef
                name if listing. Rampdef shares rampCmd with ramp command. */
            startRampdef( yytext ); /* Start rampdef addition to dictionary. */
        }
        rampList /* Rampdefs are generated individually by lower productions. */
        {
            endRampdef();
            if( rampListFile != 0 )
                showRampCmd( rampListFile, &rampCmd );
        }

    |   T_RAMP
        T_SYM    /* Motor name */
        {
            cmdType = T_RAMP; // Tells WriteRampdef to select ramp IDs from "temporary" range.
            if( getMotorType( NOTFOUND_FATAL ) != MT_STEPPER )
                fatalError( -1,
              "Ramp inapplicable to %s, which is not a stepper.\n", yytext );
            checkMotorController( namedev.mtr->unit, NOTFOUND_FATAL );
            prepRampList( "Motor" );  /* Clear rampCmd and write motor name if listing. */
        }
        rampList
        {   /* This may be preceded by one to three rampdef command messages,
               generated by lower productions. */
            fmsg.arg.ramp = rampCmd; /* Copy assign, slew, hold, and ramps. */
            fmsg.msgType = CM_RAMP;
            fmsg.arg.ramp.motor = namedev.mtr->motorNumber;
            writeFsqMsg( sizeof( RampCmd ));
            if( rampListFile != 0 )
                showRampCmd( rampListFile, &rampCmd );
        }

    |   T_POWER
        {   fmsg.msgType = CM_POWER ; }
        oneMotor
        {
            fmsg.arg.power.motor = mtrIdx;
            if( namedev.mtr->mtype != MT_STEPPER )
                fatalError( -1,
"Power is inapplicable to %s, which is not a stepper.\n", namedev.mtr->name );
            fmsg.arg.power.assign = 0;
        }
        powerList   { writeFsqMsg( sizeof(PowerCmd) ); }

    |   T_MOVE
        {   fmsg.msgType = CM_MOVE; // Do now for context. }
        oneMotor { fmsg.arg.move.motor = mtrIdx; }
        moveArg  

    |   T_STOP
        {   fmsg.msgType = CM_MOVE; /* This is to tell the motor lookup that
* the motor is being controlled, i.e. not wait or position test. This
* information is used only when writing the extended database. */
        }
        stopType

    |   T_TESTMOTOR
        {   fmsg.msgType = CM_TESTMOTOR; }
        T_UREAL
        {
            if( yyvsp[0].dval < 0.1 || yyvsp[0].dval > 1.5 )
                fatalError( -1, "%f is out of legal range 0.1 to 1.5.\n", yyvsp[0].dval );
            fmsg.arg.testMotor.value = yyvsp[ 0 ].dval * 10;
            writeFsqMsg( sizeof( TestMotorCmd ));
        }

    |   T_INITIALIZE  
        {
            cmdType = T_INITIALIZE;                 
            expect = EXPECT_COMMAND | EXPECT_UNIT;
        }
        unitSelectOpt 
        T_DUMMY /* Scanner outputs a variety of command messages depending
           on the device argument. (see fsqsup.c-isCmdParm) */

/*..........................................................................*/

    |   T_WAIT  /* for Motor, Time, Count, Gather, GATHERDONEACK, Done (script) */
        {
            fmsg.msgType = CM_WAIT; /* May be changed to CM_WAITSTEPPER or
                      CM_WAITMOTOR in waitType=>timeOrMotor=>waitMotor. */
        }
        waitType

    |   T_SIMULATE T_LIST
        {
            memset( &fmsg.arg.simList, 0, sizeof( SimListCmd ));
            optionCnt = 0;
        }
        simListOptions
        {
            switch( fmsg.arg.simList.src )
            {
            case 0:
                fatalError( -1, "Data source required (real, ecell, fast, slow)\n" );
                break;
            case LD_REAL:
                if( optionCnt > 0 )
                    showWarning( "All options ignored if real data source.\n" );
                break;
            }
            fmsg.msgType = CM_SIMLIST;
            writeFsqMsg( sizeof( SimListCmd ) +
              ( fmsg.arg.simList.patCnt == 0 ? 0 :
              ( fmsg.arg.simList.patCnt - 1 ) * 2 ));
        }

    |   T_SIMULATE T_WATCH
        {
            fmsg.msgType = CM_SIMWATCH;
            fmsg.arg.simWatch.kind = SIM_FLUID;
            nCounts = 0;
        }
        watchDev
        simWatchList
        {
            fmsg.arg.simWatch.cnt = nCounts;
            writeFsqMsg( sizeof( SimWatchCmd ) +
            ( nCounts - 1 ) * sizeof( SimEvent ));
        }

    |   T_WATCH
        {
            fmsg.msgType = CM_WATCH;
            fmsg.arg.watch.condition = NO_WATCH_CONDITION;
            fmsg.arg.watch.time = WATCH_VAR_TIMEOUT; // Default use VAR unless specified.
            fmsg.arg.watch.var = NO_VAR;
/* Can't pre-specify default watch.samp because the default value depends on
* whether the watch is bubble or stable. For bubble, it is defBubbleCnt. For
* stable watch it is defWatchFilter. */
            prevalVar = TRUE; /* Assume that either no VAR is named or it already has value. */
        }
        watchDev T_FOR watchConditions
        {
            if( fmsg.arg.watch.condition == NO_WATCH_CONDITION )
                fatalError( -1, "Some watch condition \
(0,1,Empty,NoFluid,Full,Fluid,Rising,Falling,Bubbles,Leading,Trailing) \
must be specified.\n" );
            if( fmsg.arg.watch.condition == WATCH_BUBBLES &&
              fmsg.arg.watch.var == NO_VAR )
                fatalError( -1, "Watch bubbles requires a result VAR.\n" );

            if( fmsg.arg.watch.time == WATCH_VAR_TIMEOUT )
            {
                if( fmsg.arg.watch.var == NO_VAR )
                    fatalError( -1,
                      "Timeout or VAR required (use 0.0 for no timeout)\n" );
                if( prevalVar == FALSE )
                    showWarning( "Uninitialized local VAR%d used for timeout.\n", fmsg.arg.watch.var );
            }

/* Use default for samp unless Watch device with Filter and/or Bubble Count
* is specified in the statement (which is not yet supported). */
            fmsg.arg.watch.samp =
              fmsg.arg.watch.condition == WATCH_BUBBLES ? defBubbleCnt :
              LOBYTE( timeToTicks( defWatchFilter, cmdUnit, NO_SWAP ));

            writeFsqMsg( sizeof( WatchCmd ));
        }

    |   T_SAMPLE sampleState
        {
            fmsg.msgType = CM_SAMPLE;
            writeFsqMsg( sizeof( SampleCmd ));
        }

    |   T_COUNT T_SYM
        {
            fmsg.arg.count.cell = scanCellName();
            fmsg.arg.count.trigger = swapUS( namedev.gather->trigger );
        }
        optFor fnum
        {
            fmsg.arg.count.time = timeToTicks( yyvsp[0].dval, cmdUnit, SWAP );
            fmsg.msgType = CM_COUNT;
        }
        optSeconds
        optCountRate
        {
            fmsg.arg.count.rate = yyvsp[0].uc;
            writeFsqMsg( sizeof( CountCmd ));
        }

    |   T_GATHER T_SYM
        {   fmsg.arg.gather.cell = scanCellName(); }
        optFor optFnum
        {   fmsg.arg.gather.time = timeToTicks( yyvsp[0].dval, cmdUnit, SWAP ); }
        optSeconds
        optCountRate
        {
            fmsg.arg.gather.rate = yyvsp[0].uc;
            writeGatherMsg();
        }

    |   T_ELSE
        {
            if( ifLevel == 0 )
                fatalError( -1, "Else without matching If.\n" );
            writeNoArgFsqMsg( CM_ELSE );
        }

    |   T_ENDIF
        {
            if( ifLevel-- == 0 )
                fatalError( -1, "Endif without matching If.\n" );
            writeNoArgFsqMsg( CM_ENDIF );
        }

    |   T_RESET
        {
            expect = EXPECT_CHANDEV+EXPECT_UNIT; 
            fmsg.msgType = CM_RESET;
        }
        unitSelectOpt
        resetType
        {
            if( fmsg.msgType == CM_RESET )
            {   
                fmsg.arg.reset.level = yyvsp[0].ui;
                writeFsqMsg( sizeof( ResetCmd ));
            }
/* Lower-level productions indicate that they have generated their own
   message by changing msgType to != CM_RESET. */
        }

    ;   /******************** End of Command ****************************/

fsqCmdType :
        T_HALT
        {   fmsg.arg.fsq.what = FSQ_HALT; }
    |   T_DELETE
        {   fmsg.arg.fsq.what = FSQ_DELETE; }
    ;

fsqOp :
        T_SYM
        {   fmsg.arg.fsq.who = FSQ_ID; }
    |   T_OTHERS
        {   fmsg.arg.fsq.who = FSQ_OTHERS; }
    |   T_ALL
        {   fmsg.arg.fsq.who = FSQ_ALL; }
    ;

seqLevelOpt :
        T_SEQLEVEL  { fmsg.arg.fsq.level = yyvsp[0].uc; }
    |   /* empty */ { fmsg.arg.fsq.level = SEQLEVEL_MONITOR; }
    ;

sayWhat :
        T_SAYDONE
        {   fmsg.arg.fsq.say = SAY_DONE; }
    |   T_SAYSTAT
        {   fmsg.arg.fsq.say = SAY_STAT; }
    |   /* Empty */
        {   fmsg.arg.fsq.say = SAY_NOTHING; }
    ;

loop :
        T_UINT
        {
            yyval.ui = yyvsp[0].ui;
            fmsg.arg.loop.kind = LOOP_CNT;
        }
    |   T_FOR fnum optSeconds
        {
            yyval.ui = timeToTicks( yyvsp[-1].dval, cmdUnit, NO_SWAP );
            fmsg.arg.loop.kind = LOOP_TIME;
        }
    ;

onOffOpt :
        T_ON        { onOffOption = OO_ON; }
    |   T_OFF       { onOffOption = OO_OFF; }
    |   /* empty */ { onOffOption = OO_EMPTY; }
    ;   /* This must pass value via onOffOption because of a bug in Bison's
handling of a value passed up the yystack from an empty production. */

varOrUint :
        T_VARID
        {
            argType = ARG_VAR;
            checkLocVar( yyvsp[0].ui );
            yyval.ui = yyvsp[0].ui;
        }
    |   T_UINT
        {
            checkUiSize();
            argType = ARG_VAL;
            yyval.ui = yyvsp[0].ui;
        }
    ;

debugReadArg :
        bitId
        {
            fmsg.arg.debugRead.space = deviceSpace;
            fmsg.arg.debugRead.addr = yyvsp[0].ui;
        }
    |   T_ALL
        {
            if( fmsg.arg.debugRead.op != DO_OFF )
                fatalError( -1, "Only OFF ALL allowed.\n" );
            fmsg.arg.debugRead.op = DO_ALLOFF;
        }
    ;

sampleState :
        T_BEGIN { fmsg.arg.sample.state = SAMPLE_BEGIN; }
    |   T_END   { fmsg.arg.sample.state = SAMPLE_END; }
    ;

optCountRate :
        '@'  T_UREAL
        {
            if( yyvsp[0].dval < 0.1 || yyvsp[0].dval > 25.5 )
                fatalError( -1, "Illegal count sample rate %f. Range is 0.1 to 25.5\n",
                  yyvsp[0].dval );
            yyval.uc = 10.0 * yyvsp[0].dval;
        }
    |   /* empty */ { yyval.uc = 5 }
    ;

simListOptions : simListOptions optComma simList | /* */ ;

simList :
        simListSrc
        {
            if( fmsg.arg.simList.src != 0 )
                fatalError( -1, "Only one source selection is allowed.\n" );
            fmsg.arg.simList.src = yyvsp[0].ui;
        }
    |   T_SYM
        {
            if( stricmp( yytext, randomNames[ ALPHA ] ) != 0 )
                fatalError( -1, "Unrecognized simulation pattern name %s.\n", yytext );
            fmsg.arg.simList.type = LD_ALPHA; /* Use canned alpha pattern. */
            ++optionCnt;
        }
    |   T_BEGIN optEqual T_UINT
        {
            fmsg.arg.simList.begin = yyvsp[0].ui;
            ++optionCnt;
        }
    |   T_COUNT optEqual T_UINT
        {
            fmsg.arg.simList.cellCnt = swapUL( scanLong );
            ++optionCnt;
        }
    |   T_CYCLE optEqual T_UINT
        {
            fmsg.arg.simList.len = yyvsp[0].ui;
            ++optionCnt;
        }
    |   T_UINT  /* One element of numeric pattern list. */
        {
            fmsg.arg.simList.pat[ fmsg.arg.simList.patCnt++ ] =
              swapUS( yyvsp[0].ui );
            fmsg.arg.simList.type = LD_LIST;
            ++optionCnt;
        }
    ;

simListSrc :
        T_REAL
        {   yyval.ui = LD_REAL; }
    |   T_ECELL
        {   yyval.ui = LD_ECELL; }
    |   T_FAST
        {   yyval.ui = LD_FAST; }
    |   T_SLOW
        {   yyval.ui = LD_SLOW; }
    ;

stopType :
        motorMotors motorStopType
        {
            fmsg.arg.move.motor = yyvsp[-1].uc;
            writeNamedMove( yyvsp[0].uc );
        }

    |   T_GATHER optBench
        {   fmsg.arg.stopGather.bench = yyvsp[0].uc; }
        stopGather
        {
            fmsg.msgType = CM_STOPGATHER;
            writeFsqMsg( sizeof( StopGatherCmd ));
        }

    |   T_WATCH
        {
            fmsg.msgType = CM_STOPWATCH;
            fmsg.arg.stopWatch.addr = PADCHAR;
            fmsg.arg.stopWatch.condition = NO_WATCH_CONDITION;
        }
        stopWatchDev
        {   writeFsqMsg( sizeof( StopWatchCmd )); }
    ;

stopGather :
        T_TERMINATE
        {   fmsg.arg.stopGather.kind = STOP_GATHER_TERMINATE; }
    |   /* empty */
        {   fmsg.arg.stopGather.kind = STOP_GATHER; } 
    ;

stopWatchDev :
        watchDev watchConditions
    |   /* empty */
        {   fmsg.arg.stopWatch.space = STOP_PROC_WATCH; }
    |   T_ALL
        {   fmsg.arg.stopWatch.space = STOP_ALL_WATCH; }
    ;

watchDev : T_SYM
    {
        UCHAR   space;

        if(( namedev.bit = (BitFlagAlias *)findAlias((BaseAlias*)sensorDevs,
          yytext, CASE_INSENSITIVE, 0, cmdUnit )) == 0 )
            fatalError( -1, "Unrecognized watch device %s\n", yytext );
        space = namedev.bit->space;
        if( getFluidSens())
            space |= WATCH_FLUID_SENS;

/* Generate space and addr for CM_WATCH, CM_STOPWATCH, and CM_SIMWATCH.
* These all share watchDev and have identical space and addr elements at the
* beginning of their message structures. If this situation changes then switch
* on fmsg.msgType to generate specific code. */
        fmsg.arg.watch.space = space;
        fmsg.arg.watch.addr = namedev.bit->bitAddr;
    }
    ;

simWatchList : simWatchList optComma simWatch | /* */ ;

simWatch :
        simWatchCondition optForTime
        {
            fmsg.arg.simWatch.events[ nCounts ].event = yyvsp[-1].ui;
            fmsg.arg.simWatch.events[ nCounts ].timext = 0;
            fmsg.arg.simWatch.events[ nCounts ].time = yyvsp[0].dval == -1.0 ?
              0 : timeToTicks( yyvsp[0].dval, cmdUnit, SWAP );
            nCounts++;
        }

simWatchCondition :
        T_WATCHCOND
        {
            checkFluidAliasing();
            if( yyvsp[0].ui != WATCH_BUBBLES )
                fatalError( -1, "Only Bubbles is recognized here.\n" );
            yyval.ui = WATCH_BUBBLES;
        }
    |   T_SYM
        {   yyval.ui = checkWatchSym(); }
    |   T_UINT
        {   yyval.ui = checkWatchUint( yyvsp[0].ui ); }
    ;

watchConditions : watchConditions optComma watchCondition | /* */ ;

watchCondition :
        T_WATCHCOND /* WATCH_RISING, _FALLING, _BUBBLES, _LEADING, _TRAILING */
        {   emitWatchCond( yyvsp[0].ui ); }

    |   T_SYM
        {
            *( fmsg.msgType == CM_WATCH ? &fmsg.arg.watch.condition :
              &fmsg.arg.stopWatch.condition ) = checkWatchSym();
        }

    |   T_UINT
        {
            *( fmsg.msgType == CM_WATCH ? &fmsg.arg.watch.condition :
              &fmsg.arg.stopWatch.condition ) = checkWatchUint( yyvsp[0].ui );
            /* 0 -> WATCH_0, 1 -> WATCH_1 */
        }

    |   T_VARID
        {
            if( fmsg.msgType == CM_STOPWATCH )
                fatalError( -1,
          "VAR is illegal. Only sensor condition is legal for stopwatch.\n" );
            fmsg.arg.watch.var = yyvsp[0].ui;
            if( yyvsp[0].ui < MINGVAR && locVarHasVal[ yyvsp[0].ui ] == FALSE )
            {
                prevalVar = FALSE;
                locVarHasVal[ yyvsp[0].ui ] = TRUE; /* Mark it as having
* a value (which the watch process will provide). Otherwise, we will abort on
* subsequent If test of uninitialized local VAR. */
            }
        }

    |   T_UREAL /*fnum->7 red/red conflicts. */ optSeconds
        {
            if( fmsg.msgType == CM_STOPWATCH )
                fatalError( -1,
      "Timeout is illegal. Only sensor condition is legal for stopwatch.\n" );
            fmsg.arg.watch.time = timeToTicks( yyvsp[-1].dval, cmdUnit, SWAP );
        }
    ;

resetType :
        T_SYM { badDeviceName(); }
    |   T_CHANNELDEV
        {
            fmsg.msgType = CM_SETCHANDEV; 
            fmsg.arg.setChanDev.devNum = namedev.chan->devNum + CHANDEV_VAR;
            fmsg.arg.setChanDev.chan = namedev.chan->minChan;
            fmsg.arg.setChanDev.typeId = namedev.chan->typeId;
            fmsg.arg.setChanDev.val = swapUS( 0xFF00 + namedev.chan->maxChan );
/* devNum b7 = 1 and val = 0xFFxx means reset. In this case, maxChan is
* conveyed in val low byte (swapUS is used instead of 0xFF + maxChan * 256 to
* be able to support but big- and little-endian execution units-- swapUS will
* decide whether to swap or leave the order unchanged. */
            writeFsqMsg( sizeof( SetChanDevCmd ));
        }

    |   T_GATHER    {   yyval.ui = RESET_GATHER; }

    |   T_SLAVECOM  {   yyval.ui = RESET_SLAVECOM; }

    |   T_IDLE      {   yyval.ui = RESET_IDLE; }

    |   T_HARD      {   yyval.ui = RESET_HARD; }

    |   T_LEVEL T_UINT
        {
            if( yyvsp[0].ui > RESET_HARD - RESET_IDLE )
                fatalError( -1, "%d out of range. Reset levels are 0 (IDLE) \
through %d (HARD).\n", yyvsp[0].ui, RESET_HARD - RESET_IDLE );
            yyval.ui = yyvsp[0].ui + RESET_IDLE;
        }
    ;

unitSelectOpt :
        T_UNITNAME
        {   cmdUnit = selectedUnit = yyvsp[0].ui; /* Output generators can
        check selectedUnit to determine whether to override default. If not
        UNIT_NONE (80) then the statement contains explicit unit. */ }
    |   /* Empty */
    ;

optMsgArg :
        optEqual T_SYM
        {   yyval.ui = 1; }
    |   /* Empty */
        {   yyval.ui = 0; }
    ;

readType :
        memSpaceAddr
        T_UINT          /* Number of bytes/words/longs to read. */
        tsizeOpt        /* 1, 2, or 4 */
        {
            fmsg.msgType = CM_READMEM; 
            fmsg.arg.readMem.cnt = yyvsp[-1].ui;
            fmsg.arg.readMem.tsize = yyvsp[0].ui + '0'; /* '1', '2', '4' */
            fmsg.msgType = CM_READMEM;
            vtype = MSGTEXT; /* Default reply type. */
        }
        vtypeOpt
        {
            fmsg.arg.readMem.vtype = vtype;
            writeFsqMsg( sizeof( ReadMemCmd ));
        }

    |   T_VERSION versionType
        {
            fmsg.msgType = CM_READVERSION;
            fmsg.arg.readVersion.vtype = yyvsp[0].ui + VERSION_TYPE;
            writeFsqMsg( sizeof( ReadVersionCmd ));
        }

    |   T_SYM  /* Read an item identified in analyz.ini-CONFIGS section. */
        {
            namedev.nam = findNameAlias( configs, yytext );
            if( namedev.nam == 0 )
                fatalError( -1, "Illegal config item name %s.\n", yytext );
            fmsg.arg.readCfg.cfgtype = namedev.nam->val;
            fmsg.msgType = CM_READCFG;
            writeFsqMsg( sizeof( ReadCfgCmd ));
        }
    ;

versionType :
        /* empty  */
        {   yyval.ui = 0 }
    |   T_UINT
        {   yyval.ui = yyvsp[0].ui; }
    ;

writeType :
        memSpaceAddr
        tsizeOpt          /* 1, 2, or 4 */
        {
            #define MAX_WRITE_MEM 100
            fmsg.msgType = CM_WRITEMEM; 
            tsize = yyvsp[0].ui; /* To tell charArray how to handle text. */
            fmsg.arg.writeMem.tsize = tsize + '0'; /* '1', '2', '4' */
            itemArray.bp = fmsg.arg.writeMem.bytes;
            itemCntLimit = itemCnt = MAX_WRITE_MEM / tsize ;
        }
        writeMemData    /* Single value, array, or one replaceable parameter. */

    |   T_SYM  /* Write an item identified in analyz.ini-CONFIGS section. */
        {
            namedev.nam = findNameAlias( configs, yytext );
            if( namedev.nam == 0 )
                fatalError( -1, "Illegal config item name %s.\n", yytext );
            fmsg.arg.writeIfCfg.op = PADCHAR;
            fmsg.arg.writeIfCfg.cfgtype = namedev.nam->val;
            fmsg.msgType = CM_WRITECFG;
            itemArray.bp = fmsg.arg.writeIfCfg.bytes;
            itemCntLimit = itemCnt = namedev.nam->space; // Default 100 or length specified in analyz.ini.
        }
        configCharArray
    ;

memSpaceAddr :
/* - READ and WRITE can share this because ReadMemCmd and WriteMemCmd structs
* (see analyz.ini) have identical space and addr elements.
* - yyvsp[0].ui is wrong if > 0xFFFF. scanLong is always correct but must be
* read before another number is scanned.
*/
        T_TUPLE 
        {
            if( uintTuple.w1 > 0xFF )
                fatalError( -1, "%u is larger than max space 255.\n", uintTuple.w1 );
            fmsg.arg.writeMem.space = uintTuple.w1;
            fmsg.arg.writeMem.addr = 0;
            ((USHORT*)&fmsg.arg.writeMem.addr)[1] = swapUS( uintTuple.w2 );
        }
    |   T_UINT
        {
            fmsg.arg.writeMem.space = MEMSPACE;
            fmsg.arg.writeMem.addr = swapUL( scanLong );
        }
    ;

writeMemData :
        charArray
        {
            nCounts = itemCntLimit - itemCnt;
            fmsg.arg.writeMem.cnt = nCounts;
            writeFsqMsg( sizeof( WriteMemCmd ) + nCounts * tsize - 1 );
/* The extensible array size is not ( nCounts - 1 ) * tsize because the
* writeMem.bytes array is a really a byte array and not an array of union
* byte- word-long. A union is not used here because it would waste message
* space in the byte and word cases. */
        }
    |   '@' T_SYM
        {
            getReparm( offsetof( WriteMemCmd, bytes ), tsize,
              reparmType[ tsize / 2 ]);
            checkEmitReparm();
            fmsg.arg.writeMem.cnt = 1;
            writeFsqMsg( sizeof( WriteMemCmd ) + tsize - 1 );
        }
    ;

tsizeOpt :
        T_SIZE   { yyval.ui = yyvsp[0].ui; /* 1, 2, 4 */ }
    |   /* */    { yyval.ui = 1; }
    ;

vtypeOpt :
        T_TEXT      { vtype = MSGTEXT; }
    |   T_BINARY    { vtype = MSGBINARY; }
    |   /* */       {}
    ;

/*------------------ TEST AND SET GROUPS -----------------------------------*/
                    /*..... If Group ...... */
predicate :
        singlePred
        {
            if( !trueCondition )
            {   /* Scanner swallows NOT but set trueCondition = FALSE. */
                fmsg.arg.ifStateCmd.op = MSGCHAR_FALSE;
                trueCondition = TRUE; /* Reset the flag. */
            }
            else
                fmsg.arg.ifStateCmd.op = MSGCHAR_TRUE;
            fmsg.msgType = CM_IFSTATE;
            checkEmitReparm();
            writeFsqMsg( sizeof( IfStateCmd ));
        }

    |   T_COMDEV
        {
            cmdUnit = namedev.com->unit;
            fmsg.msgType = CM_IFSTATE;
            fmsg.arg.ifStateCmd.idx = IF_COMDEV;
            fmsg.arg.ifStateCmd.val.uc = namedev.com->space;
            fmsg.arg.ifStateCmd.op = trueCondition == TRUE ? MSGCHAR_TRUE : MSGCHAR_FALSE;
            writeFsqMsg( sizeof( IfStateCmd ));
        }

    |   T_CONFIG compOp
        {
            if( yyvsp[0].ui == OPEQUAL )
                fmsg.arg.writeIfCfg.op = CFG_IF;
            else if( yyvsp[0].ui == OPNOTEQUAL )
                fmsg.arg.writeIfCfg.op = CFG_IFNOT;
            else
                fatalError( -1, "Only = and != allow for If Config.\n" );
            fmsg.arg.writeIfCfg.cfgtype = namedev.nam->val;
            fmsg.msgType = CM_IFCFG;
            itemArray.bp = fmsg.arg.writeIfCfg.bytes;
            itemCntLimit = itemCnt = namedev.nam->space; // Default 100 or length specified in analyz.ini.
        }
        configCharArray

    /* Bit-wise, i.e. multi-predicate (potentially-- could be just one). */
    |   conditionList optInTime { writeBitIf(); }

    |   T_VARID
        {
            if( cmdUnit != seqUnit && yyvsp[0].ui < MINGVAR )
                fatalError( -1, ERR_REMVAR );
            checkLocVar( yyvsp[0].ui );
            fmsg.msgType = CM_IFVAR;
            fmsg.arg.var.dvar = yyvsp[0].ui; /* wordVars index. */
        }
        compOp      { fmsg.arg.var.op = yyvsp[0].ui; }
        varop       { writeFsqMsg( sizeof( VarCmd )); }

    |   ifMotor
    ;

configCharArray :   { tsize = 1; }
        charArray
        {
            int len = namedev.nam->space - itemCnt;
            if( namedev.nam->range[0] != 0 &&
              !isArrayInSet( fmsg.arg.writeIfCfg.bytes, namedev.nam->range,
              len ))
                fatalError( -1, "%s contains illegal values for this resource.\n",
                  yytext );
            fmsg.arg.writeIfCfg.cnt = len;
            writeFsqMsg( sizeof( WriteIfCfgCmd ) + len - 1 );
        }
        ;

charArray :
        T_STRING
        {
            int     len;
            int     val;
            int     val2;
            char    *sp;

            if( tsize != 1 )
                fatalError( -1, "Only byte strings are legal.\n" );
            for( sp = yytext + 1, len = 0 ; *sp != '"' ; )
            {
                if( len >= itemCntLimit )
                    fatalError( -1, "%s contains more than %d characters.\n",
                        yytext, itemCntLimit );
                if( *sp == '\\' && toupper( sp[1] ) == 'X' )
                {
                    val = hexCharToInt( sp[2] );
                    if( val >= 0 )
                    {
                        val2 = hexCharToInt( sp[3] );
                        if( val2 >= 0 )
                        {
                            val = val * 16 + val2;
                            sp += 4;
                        }
                        else
                            sp += 3;
                        itemArray.bp[ len++ ] = val;
                    }
                    continue;
                }
                itemArray.bp[ len++ ] = *sp++;
            }
            if( len < itemCntLimit )
                itemArray.bp[ len ] = 0; // For debug display.
            itemCnt -= len;
        }

    |   numList
    ;

numList :
        numList optComma T_UINT
        {
            if( itemCnt-- < 0 )
                fatalError( -1, "Item count exceeds %d.\n", itemCntLimit );
            switch( tsize )
            {
            case 1 :
                if( yyvsp[0].ui > 255 )
                    fatalError( -1, "%d is larger than a byte.\n", yyvsp[0].ui );
                *itemArray.bp++ = yyvsp[0].ui;
                break;

            case 2 :
                checkUiSize();
                *itemArray.wp++ = swapUS( yyvsp[0].ui );
                break;

            case 4 :
/* yyvsp[0].uival is wrong if > 0xFFFF. scanLong is always correct but must
* be read before another number is scanned. Therefore, in the case of longs,
* the optComma is not optional, as it provides the one token lookahead that
* prevents the scanner from overwriting the previous scanLong value. */
                *itemArray.lp++ = swapUL( scanLong );
                break;
            }
        }
    | /* */
    ;

singlePred :
        T_STATEDEV
        {
            fmsg.arg.ifStateCmd.idx = yyvsp[ 0 ].ui;
            fmsg.arg.ifStateCmd.val.uc = namedev.idxv->val;
        }
    |   '@' T_SYM
        {   getReparm( offsetof( IfStateCmd, idx ), 1, REP_BOOL ); }
    |   T_TIMEOUT
        {
            if( cmdUnit != seqUnit )
                fatalError( -1, "Remote IF TIMEOUT is illegal.\n" );
            fmsg.arg.ifStateCmd.idx = IF_TIMEOUT;
        }
    |   T_UREAL /* fnum->1 red/red conflict */ optSeconds
        {   /* If time. Periodic predicate */
            if( cmdUnit != seqUnit )
                fatalError( -1, "Remote IF TIME is illegal.\n" );
            fmsg.arg.ifStateCmd.idx = IF_TIMEPERIOD;
            fmsg.arg.ifStateCmd.val.us = timeToTicks( yyvsp[-1].dval, cmdUnit, SWAP );
        }
    ;

varopOrExpr :
        T_COMDEV optPeek
        {   /* Note that this generates a ComDev message, not SetVar. */
            fmsg.arg.comDev.ucArg = fmsg.arg.var.dvar; /* wordVars destination index. */
            fmsg.arg.comDev.id = namedev.com->space;
            fmsg.arg.comDev.cmd = yyvsp[0].ui == 1 ? COMDEV_VAREQPEEK : COMDEV_VAREQ;
            writeComDev( TRUE );
            msgWasWritten = TRUE;
        }
    |   varop
    |   T_TIME          /* VAR = time */
        {
            fmsg.arg.var.op2type = VAROP_TIME;
            fmsg.arg.var.operand = PADWORD;
        }
    |   T_MAXLEVELGOING
        {
            fmsg.arg.var.op2type = VAROP_LEVEL;
            fmsg.arg.var.operand = PADWORD;
        }
    |   '@' T_SYM
        {
            getReparm( offsetof( VarCmd, operand ), 2, REP_SHORT );
            checkEmitReparm();
            fmsg.arg.var.op2type = VAROP_IMMEDIATE;
        }
    |   T_TOSECONDS     /* VAR = toSeconds */
        {
            #define MDSCALE_LIMIT ((double)0xFFFF / (double)MULTDIV_SCALE )
            double val;

            fmsg.arg.var.op2type = VAROP_IMMEDIATE;
            val = (double)1000 / (double)unitDes[ cmdUnit ].tickPeriod;
            if( val < MDSCALE_LIMIT )
            {
                fmsg.arg.var.op = OPDIV_SCALED;
                fmsg.arg.var.operand = swapUS( val * MULTDIV_SCALE );
            }
            else
            {
                fmsg.arg.var.op = OPDIV;
                fmsg.arg.var.operand =
                  swapUS( 1000 / unitDes[ cmdUnit ].tickPeriod );
            }
        }
    |   T_TOMSECS       /* VAR = toMsecs */
        {   /* This works only if tickPeriod >= 1 msec. Otherwise, needs more
               run-time analysis to determine the appropriate operation. */
            fmsg.arg.var.op2type = VAROP_IMMEDIATE;
            fmsg.arg.var.op = OPMULT_SCALED;
            fmsg.arg.var.operand =
              swapUS( unitDes[ cmdUnit ].tickPeriod * MULTDIV_SCALE );
        }
    |   T_UREAL T_SECONDS
        {
            fmsg.arg.var.op2type = VAROP_IMMEDIATE;
            fmsg.arg.var.operand = timeToTicks( yyvsp[-1].dval, cmdUnit, SWAP );
        }

    |   expr    /* VAR op floatingPointExpression (simple UINT is in varop) */
        {
            USHORT  arg;
            double  val;

            val = yyvsp[0].dval;
            fmsg.arg.var.op2type = VAROP_IMMEDIATE;

/* If negative val and PLUS or MINUS then use inverse operation and negated
   operand; if any other operation then error. */
            if( val < 0.0 )
            {
                if( fmsg.arg.var.op == OPPLUS )
                    fmsg.arg.var.op = OPMINUS;
                else if( fmsg.arg.var.op == OPMINUS )
                    fmsg.arg.var.op = OPPLUS;
                else
                    fatalError( -1, "Illegal negative operand %f.\n", val );
                val = -val;
            }
/* If val < 1.0 and MULT or DIV then use inverse op and operand reciprocol; if
   any other operation then error, because the operand is meaningless. */
            if( val < 1.0 )
            {   // If * or / then use inverse op and reciprocal of operand.
                if( fmsg.arg.var.op == OPMULT )
                    fmsg.arg.var.op = OPDIV;
                else if( fmsg.arg.var.op == OPDIV )
                    fmsg.arg.var.op = OPMULT;
                else
                    fatalError( -1, "Illegal fraction operand %f.\n", val );
                val = 1.0 / val;
            }
/* If op is MULT or DIV and operand is under scaling threshold and contains
* a fractional component at least as large as the scaling resolution then
* switch to scaled operation to improve resolution. */
            if(( fmsg.arg.var.op == OPMULT || fmsg.arg.var.op == OPDIV ) &&
              val < MDSCALE_LIMIT &&
              val - floor( val ) >= 1.0 / (double)MULTDIV_SCALE )
            {
                fmsg.arg.var.op = fmsg.arg.var.op == OPMULT ?
                  OPMULT_SCALED : OPDIV_SCALED;
                val *= MULTDIV_SCALE;
            }
/* If operand not at maximum then round up or down (otherwise, automatic
   conversion from double to UINT would truncate) */
            if( val < 0xFFFF )
                arg = val + 0.5;
            fmsg.arg.var.operand = swapUS( arg );
        }
    ;

optPeek :
        T_PEEK  { yyval.ui = 1; }
    |   /* */   { yyval.ui = 0; }
    ;

varop : /* Generates fmsg.arg.var.op2type and operand. */
        T_VARID
        {
            checkLocVar( yyvsp[0].ui );
            fmsg.arg.var.op2type = VAROP_VAR;
            fmsg.arg.var.operand = swapUS( yyvsp[0].ui );
        }

    |   T_CHANNELDEV chanNum
        {
            writeGetChanDev( yyvsp[0].ui, fmsg.arg.var.dvar );
            msgWasWritten = TRUE;
        }

    |   T_SYM
        {
/* Assume that any ID is a fault name. This is useful in event-triggered
* script, e.g. if TRIGGER_EVENT != UNLOAD_NOT_FULL. VARID is picked up
* directly and VAR aliases are converted and rescanned as VAR so they are not
* blocked by ID. However, if we want to include other names as varops, we will
* have to try alternate look-ups similar to getFaultNumber and each will
* need an argument telling whether to exit if not found. Currently,
* getFaultNumber assumes that the ID must be a fault name and exits if it
* isn't found. */
            fmsg.arg.var.op2type = VAROP_IMMEDIATE;
            fmsg.arg.var.operand = swapUS( getFaultNumber());
        }

    |   T_SEQLEVEL  { goto varuint; }
    |   T_UINT
        {
varuint:
            fmsg.arg.var.op2type = VAROP_IMMEDIATE;
            fmsg.arg.var.operand = swapUS( yyvsp[0].ui );
        }
    ;

conditionList :
        conditionList '&' oneCondition
        {   verifyXandOr( XANDOR_AND ); }
    |   conditionList '|' oneCondition
        {   verifyXandOr( XANDOR_OR ); }
    |   oneCondition
    ;

oneCondition :
        bitId testCondition
        {
            if( !trueCondition )
            {
                zeroIdxs[ countZeros++ ] = yyvsp[ -1 ].ui;
                trueCondition = TRUE; /* Reset the flag. */
            }
            else
                oneIdxs[ countOnes++ ] = yyvsp[ -1 ].ui;
            namedev.bit = 0; /* Tell scanner that we have reduced to
                       oneCondition so this guy is no longer valid. */
        }
    ;

testCondition :
        /* Empty, i.e. "if device" is equivalent to "if device = 1" */
    |   optEqual boolVal
        {
            if( yyvsp[0].ui == 0 )
                trueCondition = FALSE;
        }
    |   optEqual error { badStateName(); }
    ;

                    /*........ Set Group ...........*/
setType :
        T_SYM { badDeviceName(); }
    |   setList         /* Supports any bit device but warns if Sensor */
        optForTime      /* For time or empty. */
        optPowerDown
        {   writeBitSet(); }

    |   T_STATEDEV
        {   writeSetStateDev( yyvsp[ 0 ].ui ); }

    |   T_MSDEV { expect = EXPECT_SYM; }
        optToEq             /* blank, to, or = */
        T_SYM
        {   writeSetMs(); }

    |   T_TIME timeDevice optEqual time
        {
            fmsg.msgType = CM_SETTIME;
            fmsg.arg.setTime.device = yyvsp[-2].uc;
            fmsg.arg.setTime.val = swapUS( yyvsp[0].ui );
            writeFsqMsg( sizeof( SetTimeCmd ));
        }

    |   T_CHANNELDEV chanNum
        {
            if( namedev.chan->access == RO )
                fatalError( -1, "%s is Read Only.\n", namedev.chan->name );
            fmsg.msgType = CM_SETCHANDEV; /* Tell uintVarEsc */
            fmsg.arg.setChanDev.chan = yyvsp[0].ui;
            fmsg.arg.setChanDev.devNum = namedev.chan->devNum;
            fmsg.arg.setChanDev.typeId = namedev.chan->typeId;
        }
        toOrEq uintVarEsc
        {
            if( argType == ARG_VAL )
                checkChanDevVal( yyvsp[0].ui );
            else if( argType == ARG_VAR )
                fmsg.arg.setChanDev.devNum |= CHANDEV_VAR;
            fmsg.arg.setChanDev.val = swapUS( yyvsp[0].ui );
            writeFsqMsg( sizeof( SetChanDevCmd ));
        }

       /* ............ Open/Close Actuator Argument Group......... */
openCloseAct :
        {
            countZeros = 0;
            countOnes = 0;
            hasPowerDown = TRUE; /* Assume unless named device or overridden
                                    by NOT POWERDOWN in source statement. */
        }
        unitSelectOpt
        ocActList
        optForTime      /* For time or empty. */
        optPowerDown
        {   writeBitSet(); }
    ;

ocActList : ocActList optComma ocAct | ocAct ;

ocAct : actuator /* ID by name or number. */
        {
            int  act;
            if( namedDevice )
            {
                act = checkActOc(); // Fatal exit if not an open/close type.
            }
            else
                act = 0; // Devices identified by number are assumed to be Normally Open.
            if( cmdType == T_OPEN && act == 0 ||  // Want open and 0 opens or
                cmdType == T_CLOSE && act == 1 )  // ..want close and 1 opens.
                zeroIdxs[ countZeros++ ] = yyvsp[ 0 ].ui;
            else
                oneIdxs[ countOnes++ ] = yyvsp[ 0 ].ui;
        }
    |   '@' T_SYM  { showWarning( "Replaceable parm actuator not supported.\n" ); }
/* The following was used in SL1 sub-script to SLB that opened one valve at
* a time and then closed it, either for diagnostics or perhaps at a periodic
* wake up to avoid sticking during extended shutdown. It depends on
* replaceable parameters and doesn't fit well into any regular scheme. And it
* isn't really needed. We can simply open and close each valve in a longer
* flow script.
*        {
*            OUT_CMD2(( expect & EXPECT_CLOSE ) ? CMD_CLOSE : CMD_OPEN );
*            fprintf( fpOut, REP_PARM "%s", yytext );
*        }
*/
    ;

       /* ................ Set List Argument Group ..................*/

setList : setList optComma setBit | setBit ;

setBit :
        bitId optToEq boolVal
        {
            /* Add 0'd actuators to zeroIdxs and 1'd actuators to oneIdxs. */
            if( yyvsp[0].ui == 0 )
                zeroIdxs[ countZeros++ ] = yyvsp[ -2 ].ui;
            else
                oneIdxs[ countOnes++ ] = yyvsp[ -2 ].ui;
            namedev.base = NULL;
        }
    |   bitId optEqual error { badStateName(); }
    ;

bitId :
        T_SENSOR
        {
            if( cmdType != T_IF && warnSensorWrite )
                showWarning( "Sensor \"%s\" may be read only.\n",
                  namedev.bit->name );
            yyval.ui = yyvsp[0].ui;
        }
    |   T_ACT
        {
            if( cmdType == T_IF && warnActuatorRead )
                showWarning( "Actuator \"%s\" may be unreadable.\n",
                  namedev.bit->name );
            yyval.ui = yyvsp[0].ui;
        }
    |   T_FLAG
        {
            yyval.ui = yyvsp[ 0 ].ui;
        }
    |   T_TUPLE
        {
            namedev.base = NULL;
            yyval.ui = checkNumberedDevice( CND_TOLD );
        }
    |   T_UINT
        {
            namedev.base = NULL;
            yyval.ui = yyvsp[0].ui;
            checkNumberedDevice( cmdType == T_IF ? CND_SENS : CND_ACT );
            /* Note SET number defaults to first actuator space. */
        }
    |   error   { badDeviceName(); }
    ;

presetOrClearOrComdev :
        presetOrClear
    |   T_COMDEV
        {
            fmsg.arg.comDev.cmd = COMDEV_CLEAR;
            writeComDev( FALSE );
        }
    ;

presetOrClear :
        {
            countZeros = 0;
            countOnes = 0;
        }
        unitSelectOpt
        clearList
        optForTime      /* For time or empty. */
        optPowerDown
        {   writeBitSet(); }
    ;

clearList : clearList optComma clearBit | clearBit ;

clearBit : flagOrTuple
        {
            if( cmdType == T_PRESET )
                oneIdxs[ countOnes++ ] = yyvsp[ 0 ].ui;
            else  /* T_CLEAR */
                zeroIdxs[ countZeros++ ] = yyvsp[ 0 ].ui;
        }
    ;

flagOrTuple :   /* clear/preset can also be applied to actuator or sensor. */
        T_ACT
        {   yyval.ui = yyvsp[ 0 ].ui; }
    |   T_SENSOR
        {
            if( warnSensorWrite )
                showWarning( "Sensor \"%s\" may be read only.\n",
                  namedev.bit->name );
            yyval.ui = yyvsp[ 0 ].ui;
        }
    |   T_FLAG
        {   yyval.ui = yyvsp[ 0 ].ui; }
    |   T_TUPLE
        {   yyval.ui = checkNumberedDevice( CND_TOLD ); }
    |   T_UINT
        {
            yyval.ui = yyvsp[0].ui; checkNumberedDevice( CND_FLAG );
            /* Note preset/clear number defaults to first FLAG space */ }
    |   error { badDeviceName(); }
    ;

actuator :
        T_ACT
        {   namedDevice = TRUE; yyval.ui = yyvsp[0].ui; }
    |   T_TUPLE
        {   yyval.ui = checkNumberedDevice( CND_TOLD ); }
    |   T_UINT
        {   yyval.ui = yyvsp[0].ui; checkNumberedDevice( CND_ACT ); }
    |   error { badDeviceName(); }
    ;

optPowerDown :          /* POWERDOWN or NOT POWERDOWN or nothing. */
        T_POWERDOWN
        {
            if( anyNamedDevice )
            {   /* Allow "[not] powerdown" with named devices but warn under
* certain circumstances: if superfluous because the clause simply duplicates
* the devices' power declaration in analyz.ini; or if the statement tells to
* use powerdown with devices that are declared as not powerdown. The opposite,
* not powerdown with power down devices is not warned because the script
* programmer may want to control power explicitly */
                if( hasPowerDown ^ !trueCondition )
                    showWarning( "Superfluous POWERDOWN argument.\n" );
                else if( trueCondition )
                    showWarning( "Potentially dangerous POWERDOWN used with named device.\n" );
            }
            hasPowerDown = trueCondition; /* FALSE if "not powerdown", TRUE if "powerdown". */
        }
    |   /* */
    ;

timeDevice : T_SYM
        {
            if(( namedev.base = findAlias( times, yytext, CASE_INSENSITIVE,
              0, cmdUnit )) == 0 )
                fatalError( -1, "Time \"%s\" isn't defined.\n", yytext );
            yyval.uc = namedev.base->space;
        };

time :
        fnum optSeconds
        {   yyval.ui = timeToTicks( yyvsp[-1].dval, cmdUnit, NO_SWAP ); }
    |   T_FOREVER
        {   yyval.ui = 0xFFFF; }
    ;

/*------------------ END OF SET AND TEST GROUPS ----------------------------*/
chanNum :
        optChannel T_UINT
        {
            if( yyvsp[0].ui < namedev.chan->minChan ||
              yyvsp[0].ui > namedev.chan->maxChan )
                fatalError( -1, "Channel %d is out of legal range %d - %d.\n",
                  yyvsp[0].ui, namedev.chan->minChan, namedev.chan->maxChan );
            yyval.ui = yyvsp[0].ui;
        }
    |   /* Empty */
        {
            if( namedev.chan->minChan != namedev.chan->maxChan )
                fatalError( -1, "Channel number (address) required.\n" );
            yyval.ui = namedev.chan->minChan;
        }
    ;

waitType :
        T_RESET
        {
            fmsg.arg.wait.kind = WAIT_RESET;
            writeFsqMsg( sizeof( WaitCmd ));
        }

    |   waitGatherType
        optBench
        {   fmsg.arg.wait.bench = yyvsp[0].uc; }
        optMaxTime /* Note ornamental optForUntil. */
        {
            fmsg.arg.wait.ticks = timeToTicks( yyvsp[0].dval, cmdUnit, SWAP );
            writeFsqMsg( sizeof( WaitCmd ));
        }

    |   optForUntil timeOrMotor /* optForUntil serves semantic purpose here. */

    |   T_RETURN    {   fmsg.arg.waitFsq.type = WAITFSQ_RETURN; }
        waitFsq

    |   T_DONE      {   fmsg.arg.waitFsq.type = WAITFSQ_DONE;   }
        waitFsq

    |   T_GOING     {   fmsg.arg.waitFsq.type = WAITFSQ_GOING;   }
        waitFsq
    ;

waitFsq :
        { expUnitOr( EXPECT_FSQNAME ); }
        unitSelectOpt
        T_SYM
        { strcpy( tokenName, yytext ); }
        optMaxTime
        {
            fmsg.arg.waitFsq.ticks = timeToTicks( yyvsp[0].dval, cmdUnit, SWAP );
            writeSeqCmd( T_WAIT, tokenName, 0 );
            //addForkSymbol( tokenName, WAITFSQ );
        }
    ;

optForUntil : /* For some phrases, this is only ornamental. For time or motors
                 it serves a semantic purpose. */
        T_UNTIL     { waitKind = WAIT_UNTIL; }
    |   T_FOR       { waitKind = WAIT_FOR; }
    |   /* */       { waitKind = WAIT_UNKNOWN; }
    ;

waitGatherType :
        T_COUNT
        {   fmsg.arg.wait.kind = WAIT_COUNT; }
    |   T_GATHER
        {   fmsg.arg.wait.kind = WAIT_GATHER; }
    |   T_GATHERDONE
        {   fmsg.arg.wait.kind = WAIT_GATHERDONE; }
    |   T_GATHERDONEACK
        {   fmsg.arg.wait.kind = WAIT_GATHERDONEACK; }
    ;

optBench :  /* Wait gather/gatherDoneAck or Stop gather cellType. */
        T_SYM
        {
            scanCellName();
            yyval.uc = namedev.gather->bench;
        }
    |   /* empty */
        {   yyval.uc = BENCH_ALL; }   
        ;

timeOrMotor :
        fnum optSeconds         /* Time */
        {
            if( waitKind == WAIT_UNKNOWN )
                fatalError( -1, "FOR or UNTIL must be specified for wait time.\n" );
            fmsg.arg.wait.kind = waitKind;
            fmsg.arg.wait.ticks = timeToTicks( yyvsp[-1].dval, cmdUnit, SWAP );
            writeFsqMsg( sizeof( WaitCmd ));
        }

    |   T_VARID                 /* Time */
        {
            if( waitKind == WAIT_UNTIL )
                fmsg.arg.wait.kind = WAIT_UNTIL_VAR;
            else if( waitKind == WAIT_FOR )
                fmsg.arg.wait.kind = WAIT_FOR_VAR;
            else
                fatalError( -1, "FOR or UNTIL must be specified for wait VAR.\n" );
            checkLocVar( yyvsp[0].ui );
            fmsg.arg.wait.ticks = swapUS( yyvsp[0].ui );
            writeFsqMsg( sizeof( WaitCmd ));
        }

    |   waitMotor optMaxTime    /* Motor */
        {
            fmsg.arg.waitMotor.ticks = yyvsp[0].dval == 0.0 ? 0 :
              timeToTicks( yyvsp[0].dval, cmdUnit, SWAP ); /* waitStepper and
                                            waitMotor can share this. */
            writeFsqMsg( fmsg.msgType == CM_WAITSTEPPER ?
              sizeof( WaitStepperCmd ) : sizeof( WaitMotorCmd ));
        }
    ;

waitMotor :
        oneMotor
        {
            switch( namedev.mtr->mtype )
            {
            case MT_STEPPER :
                fmsg.msgType = CM_WAITSTEPPER;
                fmsg.arg.waitStepper.reqUnit = seqUnit + MSGCHAR_MASTER + 1;
                parseError = PE_WAITSTEPPER; // To catch > 1 stepper.
                break;
            case MT_TRIMOTOR :
                fmsg.msgType = CM_WAITMOTOR;
                break;
            default :
fatalError( -1, "%s is a motor type that cannot be waited for.\n", yytext );
            }
            fmsg.arg.waitMotor.motor = mtrIdx; /* waitMotor and waitStepper
                                                can share this. */
        }
        waitMotorCondition
        {
            if( yyvsp[0].ui == 1 && fmsg.msgType == CM_WAITMOTOR )
                fatalError( -1, "Illegal condition for non-stepper %s.\n",
                  namedev.mtr->name );
        }
    ;

waitMotorCondition :
        lessOrGreater uiExpr
        {
            fmsg.arg.waitStepper.condition =
              yyvsp[-1].ui == '<' ? WAITMTR_LESS : WAITMTR_GREATER;
            usVal = getUiDval( yyvsp[0].dval ); /* Includes fatal range check. */
            fmsg.arg.waitStepper.position = swapUS( usVal );
            yyval.ui = 1; /* Indicate stepper condition for shared check. */
        }
    |   T_IDLE
        {
            fmsg.arg.waitStepper.condition = WAITMTR_IDLE;
            yyval.ui = 1; /* Indicate stepper condition for shared check. */
        }
    |   /* */
        {
            fmsg.arg.waitStepper.condition = WAITMTR_STOP; /* In case it is a stepper. */
            yyval.ui = 0; /* Indicate any motor type for shared check. */
        }
    ;

ifMotor :
        oneMotor
        {
            if( namedev.mtr == 0 )
                badDeviceName();
        }
        ifMotorCondition
        {
            switch( namedev.mtr->mtype )
            {
            case MT_STEPPER :
                if( yyvsp[0].ui != 1 )
                    fatalError( -1, "Illegal condition for stepper %s.\n",
                      namedev.mtr->name );
                break;
            case MT_TRIMOTOR :
                if( yyvsp[0].ui != 0 )
                    fatalError( -1, "Illegal condition for TriMotor %s.\n",
                      namedev.mtr->name );
                break;
            default :
                fatalError( -1, "%s is a motor type whose condition can't be tested.\n",
                    namedev.mtr->name );
            }
            fmsg.msgType = CM_IFMOTOR;
            fmsg.arg.ifMotor.motor = mtrIdx;
            fmsg.arg.ifMotor.truePred = trueCondition == TRUE;
            writeFsqMsg( sizeof( IfMotorCmd ));
        }
        ;

ifMotorCondition :
        lessOrGreater uiExpr
        {
            fmsg.arg.ifMotor.condition =
              yyvsp[-1].ui == '<' ? IFMTR_LESS : IFMTR_GREATER;
            usVal = getUiDval( yyvsp[0].dval ); /* Includes fatal range check. */
            fmsg.arg.ifMotor.position = swapUS( usVal );
            yyval.ui = 1; /* Indicate stepper condition for shared check. */
        }

    |   T_IDLE
        {
            fmsg.arg.ifMotor.condition = IFMTR_IDLE;
            yyval.ui = 1; /* Indicate stepper condition for shared check. */
        }

    |   T_MOVING
        {
            fmsg.arg.ifMotor.condition = IFMTR_MOVING;
            yyval.ui = 1; /* Indicate stepper condition for shared check. */
        }

    |   T_MMOVE
        {
            switch( yylval.ui )
            {
            case MOVE_CW :
                fmsg.arg.ifMotor.condition = IFMTR_CW;
                break;
            case MOVE_CCW :
                fmsg.arg.ifMotor.condition = IFMTR_CCW;
                break;
            case MOVE_CENTER :
                fmsg.arg.ifMotor.condition = IFMTR_CENTER;
                break;
            }
            yyval.ui = 0; /* Indicate TriMotor condition for shared check. */
        }
    ;

motorMotors :
        oneMotor { yyval.uc = mtrIdx; }
    |   T_MOTORS { yyval.uc = MOVE_ALL; }
    ;

oneMotor :  T_SYM
            { getMotorType( cmdType == T_IF ? NOTFOUND_OK : NOTFOUND_FATAL ); } ;

motorStopType :
        /* */   { yyval.uc = MOVE_STOP; }
    |   T_HARD  { yyval.uc = MOVE_STOP_HARD; }
    |   T_OFF   { yyval.uc = MOVE_STOP_OFF; }
    ;

moveArg :
        T_TO uintVarEsc
        {
            writePositionMove( argType == ARG_VAR ? MOVE_ABS | MOVE_VAR :
              MOVE_ABS, yyvsp[0].ui );
        }

    |   plusOrMinus uintVarEsc
        {
            UINT    arg;
            arg = plusMinusArg == PLUS_ARG ? MOVE_PLUS : MOVE_MINUS;
            writePositionMove( argType == ARG_VAR ? arg | MOVE_VAR :
              arg, yyvsp[0].ui );
        }

    |   plusOrMinus T_FOREVER
        {   writePositionMove( plusMinusArg == PLUS_ARG ? MOVE_PLUS :
                                               MOVE_MINUS, 0xFFFF ); }

    |   optTo T_MMOVE optFraction optForTime   
        {   writeNamedMove( yyvsp[-2].ui ); // CW, CCW, or CENTER }

    |   error
        {   fatalError( -1, "Motor move requires %s.\n",
              namedev.mtr->mtype == MT_STEPPER ?  "TO, +, or -" :
              namedev.mtr->mtype == MT_TRIMOTOR ? "CW, CCW, or CENTER" :
                                                  "CW or CCW" );
        }
    ;

uintVarEsc :
        uiExpr /* T_UINT */
        {
            argType = ARG_VAL;
            yyval.ui = getUiDval( yyvsp[0].dval ); /* Includes fatal range check. */
        }
    |   T_VARID
        {
            checkLocVar( yyvsp[0].ui );
            yyval.ui = yyvsp[0].ui;
            argType = ARG_VAR;
        }
    |   '@' T_SYM
        {
            argType = ARG_REP;
            yyval.ui = PADWORD; /* To avoid random value that could trigger
range check error (PADWORD = ~~ = 32382 < 32767) and/or make inconsistent
output file. */
            getReparm( fmsg.msgType == CM_SETCHANDEV ?
                       offsetof( SetChanDevCmd, val ) :
                       offsetof( MoveCmd, position ), 2, REP_SHORT );
            checkEmitReparm(); /* Share with all users of this production. Ok
                  as long as only one rep parm in the command is allowed. */
        }
    |   plusOrMinus
        {   fatalError( -1, "Sign (+ or -) is not allowed here.\n" ); }
    ;

optPosition :
        T_UINT
        {
            if( scanLong < 1 || scanLong > 0xFFFE )
                fatalError( -1,
"%lu is out of range (1 to 65534) for stepper position.\n", scanLong );
            fmsg.arg.position.position = swapUS( yyvsp[0].ui );
            fmsg.arg.position.cmd = POSITION_WRITE;
        }
    |   /* */
        {
            fmsg.arg.position.position = PADWORD;
            fmsg.arg.position.cmd = POSITION_READ;
        }
    ;

optForTime :
        T_FOR fnum optSeconds
        {   yyval.dval = timeParm = yyvsp[ -1 ].dval; }
    |   /* empty */
        {   yyval.dval = timeParm = -1.0; }
    ;

optInTime :
        T_IN fnum optSeconds
        {
            if( cmdUnit != seqUnit )
                fatalError( -1, "Remote timed IF not supported.\n" );
            yyval.dval = timeParm = yyvsp[ -1 ].dval;
        }
    |   /* empty */
        {   yyval.dval = timeParm = -1.0; }
    ;

returnArg :
        T_UINT
        {
            fmsg.arg.ret.kind = RETURN_VAL;
            fmsg.arg.ret.val = swapUS( yyvsp[0].ui );
        }
    |   T_VARID
        {
            checkLocVar( yyvsp[0].ui );
            fmsg.arg.ret.kind = RETURN_VAR;
            fmsg.arg.ret.val = swapUS( yyvsp[0].ui );
        }
    ;

returnVar :
        T_RETURN T_VARID
        {   yyval.ui = yyvsp[0].ui; }
    |   /* Empty */
        {   yyval.ui = NO_VAR; }
    ;

/* ................... motor command arguments ...................*/
plusOrMinus :
        '-'
        {   plusMinusArg = MINUS_ARG; }
    |   '+'
        {   plusMinusArg = PLUS_ARG; }
    ;

optSign :
        '-'
        {   plusMinusArg = MINUS_ARG; }
    |   '+'
        {   plusMinusArg = PLUS_ARG; }
    |   /* empty */
        {   plusMinusArg = PLUS_ARG; }
    ;

rampList : rampList rampSeg | rampSeg ;

rampSeg :
        segType /* UP, SLEW, DOWN, RECOIL, HOLD, or = rampName */
        {   stepPtr = fmsg.arg.rampdef.steps; }
        optIdNum /* Forced ramp ID to bypass dictionary during development. */
        segDescriptor
        {
            switch( rampSegment )
            {
            case '=' : /* Inherit segment(s) from rampdef */
                break;
            case T_SLEW :
                rampCmd.slew = fmsg.arg.rampdef.steps[0];
                break;
            case T_HOLD :
                break;  /* rampCmd.hold assigned in lower production. */
            case T_RECOIL :
                if( noRecoil )
                {
                    if( stepPtr > fmsg.arg.rampdef.steps + 1 )
                        showWarning( "Superfluous steps in RECOIL ramp that "\
                          "is removed by 0 step.\n" );
                    rampCmd.recoil = 0;
                    break; // Special case "recoil 0" to remove recoil segment.
                }
                // else fall through to process normal recoil rampdef.
            default: /* T_UP, T_DOWN, or non-0 T_RECOIL. */
                *pRampId = writeRampdef( idNum ); /* Write rampdef message
* into fsq file and store ID (returned by writeRampdef in MOTO form) into ramp
* command in case this segment definition is a phrase in a ramp statememt
* instead of in a rampdef. */
            }
        }
    ;

segType : { parseError = PE_RAMPSEG; }
        T_UP
        {
            if( rampListFile != 0 )
                fputs( "UP: ", rampListFile );
            rampCmd.assign |= RAMP_UP;
            rampSegment = T_UP;
            pRampId = &rampCmd.up;
        }
    |   T_DOWN
        {
            if( rampListFile != 0 )
                fputs( "DOWN: ", rampListFile );
            rampCmd.assign |= RAMP_DOWN;
            rampSegment = T_DOWN;
            pRampId = &rampCmd.down;
        }
    |   T_RECOIL
        {
            if( rampListFile != 0 )
                fputs( "RECOIL: ", rampListFile );
            rampCmd.assign |= RAMP_RECOIL;
            rampSegment = T_RECOIL;
            pRampId = &rampCmd.recoil;
            noRecoil = FALSE; // Flag for special case of "recoil 0"
        }
    |   T_SLEW      { rampCmd.assign |= RAMP_SLEW; rampSegment = T_SLEW; }
    |   T_HOLD      { rampCmd.assign |= RAMP_HOLD; rampSegment = T_HOLD; }
    |   '=' T_SYM   { rampSegment = '='; assignRampdef( yytext ); }
/*  |   error       { fatalError( -1, "\"%s\" illegal. \
* Ramp segments are UP, SLEW, DOWN, RECOIL, and HOLD.\n", yytext ); } This
* works correctly, treating any incorrect word, including reserve ones, as a
* bad segment name. However, the parser seems unable to recover, which screws
* up the syntax.f validation test. This problem doesn't appear with the
* badStateName, which also uses embedded error token. The workaround is to let
* general error catch the problem but report according to parseError. */
    ;

segDescriptor :
        segComponentList
        {
            if( rampSegment == '=' )
                fatalError( -1, "Unspecified ramp segment type.\n" );
        }
    |   /* */
        {
            if( rampSegment != '=' )
                fatalError( -1, "Ramp segment missing description.\n" );
        }
    ;

segComponentList : segComponentList '+' segComponent | segComponent;

segComponent :
        T_UINT optExclusive T_TO T_UINT optExclusive slopeType
        {
            checkNotSlewHold();
            if( yyvsp[ -5 ].ui == 0 || yyvsp[ -2 ].ui == 0 )
                reportStep0(); /* This returns iff compiling syntax.f, but we
don't subsequently call the ramp emitter, which would crash. */
            else
                switch( rampSlopeType )
                {
                case RAMP_LINEAR :
                    emitLinearRamp( yyvsp[ -5 ].ui, // start step rate.
                        yyvsp[ -2 ].ui,             // end step rate.
                        yyvsp[ 0 ].dval,            // gradient.
                        yyvsp[ -4 ].ui * 2 + yyvsp[ -1 ].ui ); /* incflag:
                        00 = exclude both ends, 10 = include start only,
                        01 = include end only, 11 = include both. */
                    break;

                case RAMP_LINLIN :
                    emitLinLinRamp( yyvsp[ -5 ].ui, // start step rate.
                        yyvsp[ -2 ].ui,             // end step rate.
                        yyvsp[ 0 ].dval,            // Begin gradient.
                        rampEndGrade );             // End gradient.
                    break;
                
                default : // RAMP_LOG
                    emitLogRamp( yyvsp[ -5 ].ui,    // start step rate. 
                        yyvsp[ -2 ].ui,             // end step rate. */
                        yyvsp[ 0 ].ui,              // step count.
                        yyvsp[ -4 ].ui * 2 + yyvsp[ -1 ].ui ); // incflag.
                    break;
                }
        }
    |   stepList /* Ramp list, slew, or hold */
    ;

optExclusive :
        T_EXCLUSIVE { yyval.ui = 0; }
    |   /* */       { yyval.ui = 1; }
    ;

slopeType :
        linearSlope fnum optPercent
        {
            rampSlopeType = RAMP_LINEAR;
            yyval.dval = yyvsp[-1].dval;
        }

    |   linearSlope fnum optPercent T_TO fnum optPercent
        {
            double diff;

            yyval.dval = yyvsp[-4].dval;
            rampEndGrade = yyvsp[-1].dval;
            diff = rampEndGrade - yyval.dval;
            if( diff > -0.2 && diff < 0.2 )
            {
                showWarning( "Start and end gradients too similar.\n" );
                rampSlopeType = RAMP_LINEAR;
            }
            else
                rampSlopeType = RAMP_LINLIN;
        }

    |   T_IN T_UINT optSteps
        {
            rampSlopeType = RAMP_LOG;
            yyval.ui = yyvsp[-1].ui;
        }
    ;

linearSlope : T_LINEAR | '@' ;

stepList :
        fnum  /* First or only step. */
        {
            double count;

            if( rampSegment == T_HOLD )
            {
                if(( count = motorStepRate * yyvsp[0].dval ) > 0xFFFF )
                    fatalError( -1, "Hold time %f exceeds %f maximum.\n",
                    yyvsp[0].dval, (double)0xFFFF / (double)motorStepRate );
                rampCmd.hold = swapUS( (USHORT)count );
            }
            goto addStep;
        }
    |   stepList optComma fnum  /* Subsequent step. */
        {
            checkNotSlewHold(); /* > 1 step illegal for SLEW or HOLD. */
        addStep:
            if( yyvsp[0].dval == 0.0 )
                switch( rampSegment )
                {
                case T_RECOIL :
                    noRecoil = TRUE;
                    break; /* Go record this step even though it makes any
* other steps superfluous. The purpose is to count the steps so that a higher
* level production can issue a warning if a recoil ramp is defined with a 0
* and more than one step. */
                case T_HOLD :
                    break; /* Record for multi-step detection. */
                default :
                    reportStep0(); /* Only recoil and hold can have a 0 step. */
                }
            checkStepCount( stepPtr );
            *stepPtr++ =
              swapUS((USHORT)((double)motorStepRate / yyvsp[0].dval + 0.5));
        }
    ;

powerList : powerList optComma power | power ;

power : powerType optEqual powerState

powerState :
        T_OFF       { *ucPtr = PWR_OFF; }
    |   T_LOW       { *ucPtr = PWR_LOW; }
    |   T_MEDIUM    { *ucPtr = PWR_MEDIUM; }
    |   T_HIGH      { *ucPtr = PWR_HIGH; }
    ;

powerType :
        T_UP
        {
            fmsg.arg.power.assign |= RAMP_UP;
            ucPtr = &fmsg.arg.power.pow[ IDX_UP ];
        }
    |   T_DOWN
        {
            fmsg.arg.power.assign |= RAMP_DOWN;
            ucPtr = &fmsg.arg.power.pow[ IDX_DOWN ];
        }
    |   T_RECOIL
        {
            fmsg.arg.power.assign |= RAMP_RECOIL;
            ucPtr = &fmsg.arg.power.pow[ IDX_RECOIL ];
        }
    |   T_SLEW
        {
            fmsg.arg.power.assign |= RAMP_SLEW;
            ucPtr = &fmsg.arg.power.pow[ IDX_SLEW ];
        }
    |   T_HOLD
        {
            fmsg.arg.power.assign |= RAMP_HOLD;
            ucPtr = &fmsg.arg.power.pow[ IDX_HOLD ];
        }
    |   T_IDLE
        {
            fmsg.arg.power.assign |= RAMP_IDLE;
            ucPtr = &fmsg.arg.power.pow[ IDX_IDLE ];
        }
    ;
/* ............. end of motor command arguments ....................*/

echo :  /* CM_ECHO and CM_ECHOLOG */
        echoArg
        {
            if( fmsg.msgType != CM_ECHO || hideEcho == FALSE )
            {
                checkEmitReparm();
                writeFsqMsg( sizeof( EchoCmd ) + yyvsp[0].ui - 1 );
            }
        }
    |   T_COMDEV onOffOpt
        {
            fmsg.arg.comDev.cmd = COMDEV_ECHO;
            fmsg.arg.comDev.ucArg = onOffDo[ onOffOption ]; /* DO_ONCE/ON/OFF */
            writeComDev( FALSE );
        }
    ;

echoArg :
        T_STRING
        {
            yyval.ui = strlen( yytext ) - 2;
            memcpy( fmsg.arg.echo.msg, yytext+1, yyval.ui );
            fmsg.arg.echo.msg[ yyval.ui ] = 0;
            yyval.ui++;
        }
    |   '@' T_SYM
        {
            getReparm( offsetof( EchoCmd, msg ), 1, REP_STRING );
            yyval.ui = 1; /* msg comprises one byte even if no string. */
        }
    ;

echoTs : echoNames          /* CM_ECHOTITLE and CM_ECHOSTATE */
        {
            fmsg.arg.echoTs.id = LOBYTE( yyvsp[0].ui );
            writeFsqMsg( sizeof( EchoTsCmd ));
        }
    ;

echoNames :
        T_UINT
        {   yyval.ui = yyvsp[0].ui; }
    |   T_SYM
        {
            namedev.nam = findNameAlias( nameBase, yytext );
            if( namedev.nam == 0 )
                fatalError( -1, "Echo name %s doesn't exist.\n", yytext );
            yyval.ui = namedev.nam->val;
        }
    ;

reportID :
        T_UINT
        {   fmsg.arg.report.id = swapUS( yyvsp[0].ui ); }
    |   T_SYM
        {   fmsg.arg.report.id = swapUS( getFaultNumber()); }
    ;

fullReport :
        T_UINT
        {   fmsg.arg.report.level = yyvsp[0].ui; }
        T_STRING
        {
            int len;
            len = strlen( yytext + 1 ) - 1;
            memcpy( fmsg.arg.report.text, yytext + 1, len );
            fmsg.arg.report.text[ len ] = 0;
            yyval.ui = len;
        }
    |   /* Empty */       /* In case of short form "Fault name" */
        {
            fmsg.arg.report.level = NO_REPORT_LEVEL;
            fmsg.arg.report.text[0] = 0;
            yyval.ui = 0;
        }
    ;

optNoHandler :
        T_NOHANDLER
        {   yyval.ui = MSGCHAR_FALSE; }
    |   /* Empty */
        {   yyval.ui = MSGCHAR_TRUE; }
    ;

/*.................. GENERAL COMPONENTS ....................................*/

optFailOk :
        T_FAILOK
        {   failOk = TRUE; }
    |   /* Empty */
        {   failOk = FALSE; }
    ;

optFraction :
        T_UREAL     { fractionParm = yyvsp[0].dval; }
    |   /*  */      { fractionParm = 0.0; }
    ;

optMaxTime :
        T_MAXIMUM fnum optSeconds
                    { yyval.dval = yyvsp[-1].dval; }
    |   /* */       { yyval.dval = 0.0; }
    ;

optIdNum : /* The intended use is to assign a literal number to something for
              debugging and development. */
        '#' T_UINT
        {
            if( yyvsp[0].ui == 0 )
                fatalError( -1, "0 is reserved for unassigned ID. Use 1 to 65534.\n" );
            idNum = yyvsp[0].ui;
        }
    |    /* */      { idNum = 0; }
    ;

optTo :         /* */ | T_TO ;
optAs :         /* */ | T_AS ;
optEqual :      /* */ | '=' ;
optToEq :       /* */ | T_TO | '=';
toOrEq :        '='   | T_TO ;
optFor :        /* */ | T_FOR ;
optComma :      /* */ | ',' ;
optSeconds :    /* */ | T_SECONDS ;
optChannel :    /* */ | T_CHANNEL ;
optLevel :      /* */ | T_LEVEL;
optPercent :    /* */ | '%';
optSteps :      /* */ | T_STEP;

expr :  /* Constant floating point expression folded into one value by compiler. */
        T_UREAL
        {   yyval.dval = yyvsp[0].dval; }
    |   expr '*' expr
        {   yyval.dval = yyvsp[-2].dval * yyvsp[0].dval; }
    |   expr '/' expr
        {   yyval.dval = yyvsp[-2].dval / yyvsp[0].dval; }
    |   expr '+' expr
        {   yyval.dval = yyvsp[-2].dval + yyvsp[0].dval; }
    |   expr '-' expr
        {   yyval.dval = yyvsp[-2].dval - yyvsp[0].dval; }
    |   '-' expr %prec NEG
        {   yyval.dval = -yyvsp[0].dval; }
    |   '(' expr ')'
        {   yyval.dval = yyvsp[-1].dval; }
    ;

uiExpr :  /* Constant UINT expression folded into one value by compiler.
* Unlike floating point expr, this doesn't accept unary minus because uiExpr
* is used in contexts where the minus sign is part of the command rather than
* part of this expression. */
        T_UINT
        {
            if( scanLong > 65535 )
                fatalError( -1, "%ld is out of 0-65535 range.\n", scanLong );
            yyval.dval = yyvsp[0].ui;
        }
    |   T_UREAL
        {   yyval.dval = yyvsp[0].dval; } 
    |   uiExpr '*' uiExpr
        {   yyval.dval = yyvsp[-2].dval * yyvsp[0].dval; }
    |   uiExpr '/' uiExpr
        {   yyval.dval = yyvsp[-2].dval / yyvsp[0].dval; }
    |   uiExpr '+' uiExpr
        {   yyval.dval = yyvsp[-2].dval + yyvsp[0].dval; }
    |   uiExpr '-' uiExpr
        {   yyval.dval = yyvsp[-2].dval - yyvsp[0].dval; }
    |   '(' uiExpr ')'
        {   yyval.dval = yyvsp[-1].dval; }
    ;

usList :
        usList usOrBool
        {
            *usPtr = swapUS( yyvsp[0].ui );
            usPtr++;
        }
    |   /* */
    ;

usOrBool :
        T_UINT
        {   yyval.ui = yyvsp[0].ui; }
    |   'T'
        {   yyval.ui = 1; }
    |   'F'
        {   yyval.ui = 0; }
    ;

boolVal :
        'T'
        {   yyval.ui = 1; }
    |   'F'
        {   yyval.ui = 0; }
    |   T_UINT
        {   yyval.ui = yyvsp[0].ui == 0 ? 0 : 1 }
    ;

ubyte : T_UINT
        {
            yyval.ui = yyvsp[0].ui;
            if( yyval.ui > 0xff )
                printf( "line %d: %d over range\n", srcLine+1, yyval.ui );
        }
    ;

lessOrGreater :
        '<' { yyval.ui = '<'; }
    |   '>' { yyval.ui = '>'; }
    ;

arithop :
        '='
        {   yyval.ui = OPASSIGN; }
    |   '|'
        {   yyval.ui = OPOR;    }
    |   '^'
        {   yyval.ui = OPXOR;    }
    |   '&'
        {   yyval.ui = OPAND;   }
    |   T_LSHIFT
        {   yyval.ui = OPLSHIFT; }
    |   T_RSHIFT
        {   yyval.ui = OPRSHIFT; }
    |   '+'
        {   yyval.ui = OPPLUS; }
    |   '-'
        {   yyval.ui = OPMINUS; }
    |   '*'
        {   yyval.ui = OPMULT;  }
    |   '/'
        {   yyval.ui = OPDIV;   }
    |   '%'
        {   yyval.ui = OPMOD;   }
    ;

compOp :
        '='
        {   yyval.ui = OPEQUAL; }
    |   T_NEQ
        {   yyval.ui = OPNOTEQUAL; }
    |   '>'
        {   yyval.ui = OPGREATER; }
    |   '<'
        {   yyval.ui = OPLESS; }
    |   T_GEQ
        {   yyval.ui = OPGREATER_EQ; }
    |   T_LEQ
        {   yyval.ui = OPLESS_EQ; }
    ;

sint :
        '-' T_UINT
        {   yyval.ival = -yyvsp[0].ui; }
    |   T_UINT
        {   yyval.ival = yyvsp[0].ui; }
    |   '+' T_UINT
        {   yyval.ival = yyvsp[0].ui; }
    ;

optFnum : fnum | /* */ { yyval.dval = 0.0; } ;
        
fnum :  /* Float required but integer accepted for user convenience. */
        T_UREAL { yyval.dval = yyvsp[0].dval; }
    |   T_UINT  { yyval.dval = yyvsp[0].ui; }
    ;

notUint : T_TUPLE | T_UREAL | T_SYM ;

%%
/************************* end of rules ***************************/

/****************************************************************************
* Function:     initCmdState
* Description:  Initialize translation control variables. This is called prior
* to parsing each command statement. Many of the variables are relevant only
* to specific command types, but we don't know which ones until in mid-parse.
* We avoid a lot of code duplication by initializing all of them here, but
* there is a minor run-time penalty compared to doing it only as needed in
* mid-parse.
* Returns:      Nothing
* Arguments:    None
*..........................................................................*/
void initCmdState( void )
{
    memset( &fmsg.arg, PADCHAR, 20 );
    freshCommand = TRUE;
    cmdType = 0;
    cmdUnit = seqUnit;
    selectedUnit = UNIT_NONE;
    anyNamedDevice = FALSE; /* Only needed after SET,CLEAR,PRESET,OPEN,CLOSE. */
    namedev.base = NULL;
    aliasCount = 0;
    trueCondition = TRUE;
    parseError = PE_NOTHING;
    timeParm = -1.0;
}
/****************************************************************************
* Function:     checkUiSize
* Description:  Called immediately after scanning a supposed UINT to determine
* if it was too long.
* Returns:      Nothing
* Arguments:    None
*..........................................................................*/
void checkUiSize( void )
{
    if( scanLong > 0xFFFF )
        fatalError( -1, "%ld is larger than a word.\n", scanLong );
}
/****************************************************************************
* Function:     checkMotorController
* Description:  Check to see whether the given unit is a direct step motor
* controller. If it isn't and caller says fatal if no found then exit with
* fatal error. Assign motor step rate to motorStepRate. This is 0 if the unit
* is not a stepper controller.
* Returns:      FALSE if the unit is not a direct stepper controller.
* Arguments:
* - UINT unit is the unit to check. This is usually the script execution unit.
* - int ifNotFound tells what to do if the unit is not a direct stepper
* controller. If NOTFOUND_FATAL then error exit. If NOTFOUND_OK then return
* FALSE.
* Globals:      motorStepRate, unitDes, motorController.
*..........................................................................*/
BOOL checkMotorController( UINT unit, int ifNotFound )
{
    if(( motorStepRate = unitDes[ unit ].stepRate ) == 0 )
    {
        if( ifNotFound == NOTFOUND_FATAL )
            fatalError( -1, "%s missing STEP_RATE definition.\n", getUnitName( unit ));
        return FALSE;
    }
    motorController = unit;
    return TRUE;
}

void checkNotSlewHold( void )
{
    if( rampSegment == T_SLEW || rampSegment == T_HOLD )
        fatalError( -1, "Slew and Hold are allowed only one number.\n" );
}

void checkLocVar( UINT varid )
{
    if( varid < MINGVAR && !locVarHasVal[ varid ])
        showWarning( "Local VAR%d used for value before being assigned a value.\n", varid );
}

UINT checkWatchSym( void )
{
    if( stricmp( yytext, namedev.bit->alias0.p ) == 0 )
        return WATCH_0;
    else if( stricmp( yytext, namedev.bit->alias1.p ) == 0 )
        return WATCH_1;
    return fatalError( -1, "%s is not a state of %s.\n", yytext,
      namedev.base->name );
}

UINT checkWatchUint( UINT arg )
{
    if( arg > 1 )
        fatalError( -1, "Watch for %d is illegal (0 and 1 are ok).\n", arg );
    return WATCH_0 + arg;
}

void checkScriptNameLen( char *name )
{
    if( strlen( name ) > MAXFSQNAMELEN )
        fatalError( -1, "Script name %.20s... contains more than %d characters.\n",
          name, MAXFSQNAMELEN );
}

void fatalSyntax( void )
{
    fatalError( -1, "Syntax error at \"%s\".\n", yytext );
}

void badDeviceName( void )
{
    fatalError( -1, "Unrecognized device name \"%s\".\n", yytext );
}

UINT getUiDval( double val )
{
    if( val < 0.0 || val > 65535.0 )
        fatalError( -1, "%.2f is out of 0-65535 range.\n", val );
    return val + 0.5;
}

/****************************************************************************
* Function:     badStateName
* Description:  Report bad state name as reserved word, named state used with
* unnamed device, or not matching any of the named device's states. Reserved
* word has been detected by error production rather than by the parser as a
* general syntax error in order to be able to issue this more useful error
* report. Reserved word misuse may be due to an error in the analyz.ini file
* as well in the script. The ini file parser does not check for this kind of
* error in order to avoid wasting time on something that can be caught when
* used in a script. The other two error types can only be caused by script
* error.
* Returns:      Nothing
* Arguments:    Nothing
*..........................................................................*/
void badStateName( void )
{
    if( isReservedWord( yytext ))
        fatalError( -1,
"Reserved word \"%s\" can't be used as a state name.\n", yytext );
    else if( namedev.bit == NULL )
        fatalError( -1,
"Named state \"%s\" can't be used with an unnamed device.\n", yytext );
    else if( isspace( *yytext ))
        fatalError( -1, cmdType == T_SET ?
"Missing state. Use PRESET if the intent is to set %s to 1.\n" :
"Missing test state for %s.\n", namedev.bit->name );
    else
        fatalError( -1, "\"%s\" has no state \"%s\", only 0, 1, %s, or %s.\n",
        namedev.bit->name, yytext, namedev.bit->alias0, namedev.bit->alias1 );
}
/****************************************************************************
* Function Name:    main
* Description:      Main subroutine for Analyzer script compiler
*-------------------------- Notes -------------------------------------------
* - Locate shell here to provide easy debug access to fsqyac.y; body in
* fsqsup.c to  reduce yacc source size.
*
****************************************************************************/
int main(int argc, char **argv)
{
   return fsqMain( argc, argv );
}


ROBOT SCRIPT COMPILER FLEX/LEX SCANNER

/****************************************************************************
* FSQLEX.LEX
* Description:  Robot script compiler scanner FLEX source.
* ------------------------------- Notes ------------------------------------
*                     ---- Scanner Generator -------
* Use old FLEX version 04b 30sep87 with flex.sk with DM's change of static int
* unput to global void flexUnput for the skeleton. Or use the "NEWFLEX"
* version 2.4 and FLEX.SKL with DM's similer changes.
*
****************************************************************************/ 
%{
/*****************************************************************************
*                              C FILE DECLARATIONS
*****************************************************************************/
#define FSQLEX_LEX
#define NEWFLEX
/*................. Run-time Library Headers ..............................*/
#include <ctype.h>             
#include <io.h>             
#include <stdio.h>             
#include <stdlib.h>             
#include <string.h>             
/*.................. Project Headers .......................................*/
#include "cdefs.h"
#include "utils.h"
#include "futil.h"
#include "fsqyac.h"
#ifndef yylval
extern YYSTYPE yylval; /* YACC includes this in .h output but BISON does not.*/
#endif
#include "fsqapi.h"  /* Universal script message definition. */
#include "cfgsys.h"
#include "fsqcomp.h"
#include "fsqsup.h"

static  char *str;
static  BOOL dumComment;
static  BOOL commentBlock = FALSE;
void    unputString( char *str, int start ); 

%}
/*****************************************************************************
*                                 GRAMMAR RULES
*****************************************************************************/
digit   [0-9]
hex     [0-9A-Fa-f]
letter  [A-Za-z_]
alnum   [0-9A-Za-z_]
space   [\x20]
white   [\x09\x20]     /* space, tab */
/* Declare multi-line comment start condition.. */
%x comment
%%

 /* --------------- Context-Sensitive Commands -------------------------*/
DEFINE  { expect = EXPECT_DEFINITION;               return T_DEFINE; } 
BEGIN   { expUnitOr( EXPECT_FSQNAME );              return T_BEGIN; }
FORK    { expUnitOr( EXPECT_FSQNAME );              return T_FORK; }
IF      { expUnitOr( EXPECT_BITDEV | EXPECT_STATE | EXPECT_TF |
                     EXPECT_CONFIG | EXPECT_COMDEV ); return T_IF; }   
CLOSE   { expUnitOr( EXPECT_ACT );                  return T_CLOSE; }
 /* OPEN command is context-sensitive. Found in classifySymbol */

SET     { if( (expect & EXPECT_FSQNAME) != 0 ) /* Allow a script to be called SET */
              return T_SYM;
          expUnitOr( EXPECT_BITDEV | EXPECT_STATE | EXPECT_TF |
                     EXPECT_CHANDEV | EXPECT_MSDEV );       return T_SET; }

CLEAR   { expUnitOr( EXPECT_FLAG | EXPECT_ACT | EXPECT_SENSOR |
                     EXPECT_COMDEV );                       return T_CLEAR; }
PRESET  { expUnitOr( EXPECT_FLAG | EXPECT_ACT | EXPECT_SENSOR ); return T_PRESET; }
GET     { expUnitOr( EXPECT_CHANDEV | EXPECT_COMDEV );      return T_GET; }
SEND    { expUnitOr( EXPECT_COMDEV );                       return T_SEND; }

 /* The following require unit name-sensitive scanning */
RESET           { return T_RESET; }     /* Command or parameter. */
READ            { return T_READ; }
WRITE           { return T_WRITE; }

 /* --------------- Context-Free Commands -------------------------------*/
TESTSYSTEM      { return T_TESTSYSTEM; /* Context resolved in parser. */ }
UNIT            { return T_UNIT; } /* Can be command or parameter. */
PRAGMA          { return T_PRAGMA; }
TERMINATE       { return T_TERMINATE; }
END             { return T_END; }
BREAK           { return T_BREAK; }
HALT            { return T_HALT; }
DELETE          { return T_DELETE; }
ENDIF           { return T_ENDIF; }   
ELSE            { return T_ELSE; }   
LOOP            { return T_LOOP; }
ENDLOOP         { return T_ENDLOOP; }
ECHO            { return T_ECHO; }
ECHOLOG         { return T_ECHOLOG; }
ECHOCOMMENT     { return T_ECHOCOMMENT; }
ECHOTITLE       { return T_ECHOTITLE; }
ECHOSTATE       { return T_ECHOSTATE; }
REPORT          { return T_REPORT; }
MESSAGE         { return T_MESSAGE; }
TESTMOTOR       { return T_TESTMOTOR; }   
POSITION        { return T_POSITION;}
RAMPDEF         { return T_RAMPDEF; }
RAMP            { return T_RAMP; }
POWER           { return T_POWER; }   
MOVE            { return T_MOVE; }
WAIT            { return T_WAIT; }
INITIALIZE      { return T_INITIALIZE; }   
SIMULATE        { return T_SIMULATE; }
SAMPLE          { return T_SAMPLE; }
GATHER          { return T_GATHER; }
GATHERDONE      { return T_GATHERDONE; }
COUNT           { return T_COUNT; }   
DEBUGREAD       { return T_DEBUGREAD; }

BEGINPARAMETERS { /* Used by getlex to back up and get dual-use names */ }

 /* ------------ Command or Parameter -------------*/
WATCH           { return T_WATCH; }
STOP            { return T_STOP; }      /* Command or parameter */
RETURN          { return T_RETURN; }    /* Command or parameter. */

ENDOFCOMMANDS   { /* Used by getlex to alphabetize commands and parameters separately. */ }

 /*------------- Parameter Names --------------------------------------*/
NOT             { trueCondition = FALSE; /* Swallow it. */
/* NOT presents a problem for LALR1 parsers because it is useful in different
* situations that can't be translated without further look-ahead.  Flagging
* the NOT condition instead of reporting it to the parser avoids clumsy
* disambiguation in the parse actions. I used the negative of not, i.e.
* trueCondition instead of notCondition in order to simplify situations where
* we can generate TRUE/FALSE output by simply assigning the trueCondition
* value. */ }
TRUE            { return 'T'; }
FALSE           { return 'F'; }
LIST            { return T_LIST; }
FAST            { return T_FAST; }
SLOW            { return T_SLOW; }
REAL            { return T_REAL; }
ECELL           { return T_ECELL; }
CYCLE           { return T_CYCLE; }
ALL             { return T_ALL; }
MOTORS          { return T_MOTORS; }
RECOIL          { return T_RECOIL; }
SLEW            { return T_SLEW; }
HOLD            { return T_HOLD; }
LINEAR          { return T_LINEAR; }
EXCLUSIVE       { return T_EXCLUSIVE; }
STEP            { return T_STEP; }
STEPS           { return T_STEP; }
LOAD            { return T_LOAD; }
OTHERS          { return T_OTHERS; }
SECOND          { return T_SECONDS; }
SECONDS         { return T_SECONDS; }
VERSION         { return T_VERSION; }
HANDLE          { return T_HANDLE; }
THROUGH         { return T_THROUGH; }
ALLFAULTS       { yylval.ui = HANDLER_ALL_FAULTS;   return T_FAULT_HANDLER; }
SAFETY          { yylval.ui = SEQLEVEL_SAFETY;      return T_SEQLEVEL; }
MONITOR         { yylval.ui = SEQLEVEL_MONITOR;     return T_SEQLEVEL; }
CONFIG          { yylval.ui = SEQLEVEL_CONFIG;      return T_SEQLEVEL; }
MECHANICAL      { yylval.ui = SEQLEVEL_MECHANICAL;  return T_SEQLEVEL; }
PROCESS         { yylval.ui = SEQLEVEL_PROCESS;     return T_SEQLEVEL; }
REPORTFAULTS    { yylval.ui = SEQ_REPORTFAULTS;     return T_SEQREPORT; }
FAULTREPORTER   { yylval.ui = SEQ_FAULTREPORTER;     return T_SEQREPORT; }
SAYDONE         { return T_SAYDONE; }
SAYSTAT         { return T_SAYSTAT; }
UNTIL           { return T_UNTIL; }
FOR             { return T_FOR; }
MAX             { return T_MAXIMUM; }
MAXIMUM         { return T_MAXIMUM; }
GOING           { return T_GOING; }
DONE            { return T_DONE; }
TIMEOUT         { return T_TIMEOUT; }
FAILOK          { return T_FAILOK; }
IN              { return T_IN; }
TO              { return T_TO; }
AS              { return T_AS; }
GATHERDONEACK   { return T_GATHERDONEACK; }
PEEK            { return T_PEEK; }
CHANNEL         { return T_CHANNEL; }
SYSTEMSTATUS    { return T_SYSTEMSTATUS; }
FAULTS          { return T_FAULTS; }
NOHANDLER       { return T_NOHANDLER; }
TIME            { return T_TIME; }              /* VAR = time and set time. */
MAXLEVELGOING   { return T_MAXLEVELGOING; }     /* VAR = MaxLevelGoing */   
TOSECONDS       { return T_TOSECONDS; }         /* VAR = toSeconds */
TOMSECS         { return T_TOMSECS; }           /* VAR = toMsecs */
FOREVER         { return T_FOREVER; }           /* Set time xx = forever */
POWERDOWN       { return T_POWERDOWN; }
CW              { yylval.ui = MOVE_CW;              return T_MMOVE; }             
CCW             { yylval.ui = MOVE_CCW;             return T_MMOVE; }             
CENTER          { yylval.ui = MOVE_CENTER;          return T_MMOVE; }            
RISING          { yylval.ui = WATCH_RISING;         return T_WATCHCOND; }
FALLING         { yylval.ui = WATCH_FALLING;        return T_WATCHCOND; }
BUBBLES         { yylval.ui = WATCH_BUBBLES;        return T_WATCHCOND; }
LEADING         { yylval.ui = WATCH_LEADING;        return T_WATCHCOND; }
TRAILING        { yylval.ui = WATCH_TRAILING;       return T_WATCHCOND; }
MOVING          { return T_MOVING; }
SLAVECOM        { return T_SLAVECOM; }
IDLE            { return T_IDLE; }
HARD            { return T_HARD; }
LEVEL           { return T_LEVEL; }
BYTE            { yylval.ui = 1;                    return T_SIZE; }
BYTES           { yylval.ui = 1;                    return T_SIZE; }
WORD            { yylval.ui = 2;                    return T_SIZE; }
WORDS           { yylval.ui = 2;                    return T_SIZE; }
LONG            { yylval.ui = 4;                    return T_SIZE; }
LONGS           { yylval.ui = 4;                    return T_SIZE; }
TEXT            { return T_TEXT; }
BINARY          { return T_BINARY; }

 /* -------------- Multi-line comment group ----------------------------- */
^[ \t]*COMMENT          {
                            BEGIN( comment );
                            commentBlock = TRUE;
                        }

<comment>^[ \t]*ENDCOMMENT.*\n {    ++srcLine;
                                    if( commentBlock )
                                    {
                                        commentBlock = FALSE;
                                        BEGIN( INITIAL );
                                    }
                                }

^[ \t]*"/*"             { BEGIN( comment ); dumComment = TRUE;}
"/*"                    { BEGIN( comment ); dumComment = FALSE; }
<comment>[^*\n]*
<comment>[^*\n]*\n      { ++srcLine; }
<comment>"*"+[^*/\n]*
<comment>"*"+[^*/\n]*\n { ++srcLine; }
<comment>"*"+"/"        {   if( commentBlock < 1 )
                            {
                                BEGIN( INITIAL );
                                if( dumComment )
                                    return T_DUMMY;
                            }
                        }

 /* ----------------------------------------------------------------------*/

^[ \t]*("//"|;).*\n { ++srcLine; } /* comment line with leading spaces or tabs */
^[ \t]*\n           { ++srcLine; } /* null line */
("//"|;).*\n        { ++srcLine; return T_ENDLINE; } /* trailing comment line */
\n                  { ++srcLine; return T_ENDLINE; }
"~"                 { return T_ENDLINE; /* Line compaction operator. See
                        note in unputString. */ }
[ \t]*          /* ignore white space */

 /*-------------- Composite Operators ----------------*/ 
\".+\"          { return T_STRING; }  /* Rejects " in trailing comment. */
"<<"            { return T_LSHIFT; }
">>"            { return T_RSHIFT; }
"!="            { return T_NEQ; }
"<="            { return T_LEQ; } 
">="            { return T_GEQ; } 

\\              {   /* Line continuation */ 
                    ++srcLine;
                    *SCANBUFF = 0; 
                    scanRemainder( SCANBUFF, SCANBUFF_LEN );
                    break;
                }

 /*---------------- Complex Pattern Expressions -------------------------*/ 
\'.\'           { yylval.ui = yytext[1];            return T_UINT; }
{digit}+        { yylval.ui = decToUi( yytext );    return T_UINT; }
[1|0]+B         { yylval.ui = binToUi( yytext );    return T_UINT; }
{digit}{hex}+H  { yylval.ui = hexToUi( yytext );    return T_UINT; }  
{digit}+\:{digit}+  { scanTuple();                  return T_TUPLE; }
{digit}+\.{digit}*  { yylval.dval = atof( yytext ); return T_UREAL; }

{letter}{alnum}* {
    strupr( yytext );
    if( expect & EXPECT_DEFINITION )
        return( classifySymbol());
    if( toupper( yytext[0] ) == 'V' && isVar())
        return T_VARID;
    if(( str = getYytextAlias()) != 0 )
        unputString( str, 0 );
    else
        return( classifySymbol());
    }

. { if( *yytext == '.' )
        fatalError( -1, "Fractional numbers require 0 before decimal point.\n" );
    return *yytext; /* Let parser decide what to do with any other char. */
  }
%%

/*****************************************************************************
*                               SUPPORT FUNCTIONS
*****************************************************************************/
#ifdef NEWFLEX
#define DO_AFTER_SCANOUT    yytext[yyleng] = yy_hold_char; /* See gen.c and flex.skl */
#define UNPUT_SCAN_CHAR     unput                  /* See flex.skl */
int yywrap( void )
{
    return 1; /* Tell scanner to terminate at end of
         one file. We have a more universal way of starting the next file. */
}
#else
/* See flexsdef.h */
#define DO_AFTER_SCANOUT    YY_DO_BEFORE_SCAN /* yytext[yyleng] = yy_hold_char; */
#define UNPUT_SCAN_CHAR     flexUnput /* David's modified unput in flex.sk */

#endif

void resetScanner( void )
{
    commentBlock = FALSE;
#ifdef NEWFLEX
    YY_NEW_FILE; 
#else
    yy_saw_eof = 0;
    yy_init = 1;
    /* YY_INIT ?? */ 
}
#endif
}

/****************************************************************************
* Function Name:    scanRemainder 
* Description:      Scan the remainder of the source line, up to and including
* newline into the given buffer. Then reset the scanner. Newline preceded by
* a single backslash is discarded, i.e. line continuation is supported.
* Returns:          Nothing.
* Arguments:        Destination buffer. See first char 0 note.
*                       NOTES
*- This requires a newline. It does not detect EOF.
*- The text is concatenated to the destination buffer. To get only the
*  remaining text, the caller should 0 the first character of the buffer.
...........................................................................*/
void scanRemainder( char *buf, int blen )
{
    char    *cp;
    char    inpc;
    BOOL    hasSpace;

    hasSpace = FALSE;
    cp = buf + strlen( buf ); // Append remainder to existing contents.
    while( 1 )
    {
        if( cp >= buf + blen )
            fatalError( -1, "Source line too long.\n" );
        inpc = input();
        if( inpc == '\\' )
        {
            do {
                inpc = input();
            }   while( inpc == ' ' || inpc == '\t' );

            if( inpc == '/' )
            {
                while( input() != '\n' )
                    ;
                ++srcLine;
                continue;
            }
                
            if( inpc == '\n' )
            {
                ++srcLine;
                continue;
            }
            *cp++ = '\\';
        }
        if( inpc == '\n' )
        {
            *cp = inpc;
            break; // newline outside of lineContinue is end.
        }
        if( isspace( inpc ))
        {
            if( hasSpace )
                continue;    // Squeeze all space down to one.
            else
                hasSpace = TRUE;
        }
        else
            hasSpace = FALSE;
        *cp++ = inpc;
    }
    DO_AFTER_SCANOUT
/* Caller may want to set expect = 0 at this point, but we can't do it here. */
}
/****************************************************************************
* Function:     unputString 
* Description:      Function replacement for FLEX macro PUT_BACK_STRING, which
*   isn't defined in all FLEX skeletons and wouldn't work anyway because of
*   unput name change. We could define a new PUT_BACK_STRING macro, but this
*   should be a function anyway because the calling overhead is insignificant.
* Returns:          Nothing, since it depends on flexUnput, which returns void.
* Arguments:        String to be unput. Index of first char of substring to be
    unput, e.g. 0 if the entire string.
* Globals:          Calls the changed FLEX unput function renamed flexUnput.
* ................ notes ...................................................
* - Line compaction is normally used only in a macro. The simple way to handle
* it is to keep the operator in the substitute string and let the scanner
* return T_ENDLINE but not count srcLine (see rules above). However, this
* doesn't support echo command plus line compaction. That is supported by the
* commented out code here, where the compaction operator is replaced by
* newline (0xA) as the macro is expanded. But this complicates srcLine
* handling.
...........................................................................*/
void unputString( char *str, int start )
{
    int     cnt;

    for( cnt = strlen( str ) - 1 ; cnt >= start ; cnt-- )
/*
        if( str[ cnt ] == '~' )
            UNPUT_SCAN_CHAR( 31 );
        else
*/
            UNPUT_SCAN_CHAR( str[ cnt ] );
}



GRAMMAR EFFICIENCY STUDY

Even a grammatically simple language like Lisp can be modeled by many different grammars. The most efficient grammar for LR bottom-up parsing is the one that has the largest ratio of reductions to shifts. There are some obvious guidelines. A right-recursive grammar is more efficient for a left-recursive language and vice versa. This makes sense because a language’s recursion is defined top-down. Precedence and associativity rules can be used disambiguate within a single level rather than making a deeper parse tree.

As part of a study of various grammars for lisp, I designed these two grammars, both right-cursive but one mimics the CAR-CDR recursion of lisp itself and the other splitting each parse level into two roughly equal sentential forms. The latter, completely ignoring lisp’s own inherent recursion, produces a significantly more efficent parser.

/* carcdr.y is a lisp parser */
/* This version is similar to mlsp but uses the standard 
  cons cell, consisting of car and cdr. */
%{
#include <stdio.h>

extern yylex() ;
extern yyerror() ;

union ipval {
    int ival ;
    char *pval ; /* Pointer to list "value" */
} ;

struct symnode {
    char *name ;
    char valtype ;
    union ipval val ; /* integer value or pointer to list 
                         or function */
    char *prop ; /* Pointer to property list. NULL = none */
    struct symnode *left ;
    struct symnode *right ;
} ;

union parval {
    int iv ; /* Integer or char atom value */
    char *strp ; /* Pointer for string atom */
    struct symnode *symp ; /* Pointer for symbol atom */
    struct parnode *pnp ; /* Non-atomic node pointer */
} ;

struct parnode {  /* Parse tree node */
    int pntyp ;
    int subtyp ;
    union parval child1 ;
    struct parnode *child2 ;
} ;

typedef union {
    int ival ;
    char *strp ;
    struct symnode *symp ;
    struct parnode *parp ;
} YYSTYPE ; 

char *malloc() ;
#define PALL (struct parnode *)malloc(sizeof(struct parnode))  
                                    /* Allocate a parnode */
static struct parnode *proot ;
 
/* The following production type IDs are assigned values in 
order of number of children to simplify tree utilities */
#define    PNIL     0  /* NO CHILDREN */
#define    PT           1  /* NO CHILDREN */
#define    PCHAR    2  /* 1 CHILD */
#define    PNUM     3  /* 1 CHILD */
#define    PSTR     4  /* 1 CHILD */
#define    PSYM     5  /* 1 CHILD */
#define    PATOM    6  /* 1 CHILD */
#define    PCAR     7  /* 1 CHILD */
#define    PCDR     8  /* 2 CHILDREN */
#define    PLIST    9  /* 2 CHILDREN */

struct parnode *inatom(styp) /* Install an atom parsenode */
int styp ; /* Subtype */
{
    struct parnode *ptr ;

    (ptr = PALL)->pntyp = PATOM ;
    ptr->subtyp = styp ;
    return (ptr) ;
}

struct parnode *innt(mtyp, styp, ccnt, ch1, ch2)
int mtyp, styp, ccnt ;
struct parnode *ch1, *ch2 ;
{
    struct parnode *ptr ;

    (ptr = PALL)->pntyp = mtyp ;
    ptr->subtyp = styp ;
    ptr->child1.pnp = ch1 ;
    if (ccnt == 2)
        ptr->child2 = ch2 ;
    return (ptr) ;
}

%}
%type <parp> atom list car cdr
%token <symp> SYM 
%token <ival> CHAR NUM NIL T
%token <strp> STR
%token BPCR  /* Statement is terminated when scanner 
                  recognizes CR and balanced parens */
%token NIL   /* The scanner returns nil and () as token */

%start interp 

%%   /* begin rules section */

interp  :   /* empty */
    |   interp stat BPCR
            { parenb = nqflag = 0 ; }
    |   interp error 
            { while (getchar() != '\n') ; 
              parenb = nqflag = 0 ; 
              yyerrok ; /* Reenable strict checking on next 
                            input */
              yyclearin ; }
    ;
stat    :   '\'' list 
           {    prntr($2) ; }
    |   atom
           {    prntr($1) ; }
    |   '\'' atom
           {    prntr($2) ; }
    |   list
           {    prntr($1) ; }
    ;
list    :   '(' car cdr ')'
            {   $$ = innt(PLIST, PCAR, 2, $2, $3) ; } 
                     /* Note Irrelevant subtype */
    ;
cdr :   /* Empty is required to mark end of recursive list */
            {   $$ = innt(PCDR, PNIL, 1, 0) ; } 
                    /* Note irrelevant pointer */
    |   car cdr
            {   $$ = innt(PCDR, PCAR, 2, $1, $2) ; }
    ;
car :   atom
            {   $$ = innt(PCAR, PATOM, 1, $1) ; }
    |   list
            {   $$ = innt(PCAR, PLIST, 1, $1) ; }
    ;
atom    :   SYM
            {   ($$ = inatom(PSYM))->child1.symp = $1 ; }
/* All atoms other than SYM are constants, so semantic 
  analyzer need only check for SYM/not-SYM */
    |   CHAR
            {   ($$ = inatom(PCHAR))->child1.iv = $1 ; }
    |   STR
            {   ($$ = inatom(PSTR))->child1.strp = $1 ; }
    |   NUM
            {   ($$ = inatom(PNUM))->child1.iv = $1 ; }
    |   NIL
            {   $$ = inatom(PNIL) ; }
    |   T
            {   $$ = inatom(PT) ; }
    ;

%%  /* start of programs */
static nodecount ; /* For counting nodes while printing tree */
static int indent ;  /* Indent counter for printing parse tree */

prntree(pnp, prtr)
struct parnode *pnp ;
int prtr ; /* True means print, false only clear the tree */
{
    int ic ;

    nodecount++ ;
    if (prtr) {
        putchar('\n') ;
        for (ic = 0 ; ic < indent ; ic++)
            putchar(' ') ;
        indent += 4 ;
        switch (pnp->pntyp) {
        case PATOM :
            printf("atom:") ;
            switch (pnp->subtyp) {
            case PSYM :
                printf(" sym=%s",pnp->child1.symp->name) ;
                break ;
            case PNUM :
                printf(" num=%d",pnp->child1.iv) ;
                break ;
            case PSTR :
                printf(" str=%s",pnp->child1.strp) ;
                break ;
            case PCHAR :
                printf(" char=%c",pnp->child1.iv) ;
                break ;
            case PNIL :
                printf(" NIL ") ;
                break ;
            default :
                break ;
            }
            break ;
        case PCAR :
            printf("car:") ;
            if (pnp->subtyp == PATOM) {
                printf(" atom") ;
                prntree(pnp->child1.pnp, 1) ;
            }
            else {
                printf(" list") ;
                prntree(pnp->child1.pnp, 1) ;
            }
            break ;
        case PCDR :
            printf("cdr:") ;
            if (pnp->subtyp == PCAR) {           
                printf(" car cdr ") ;
                prntree(pnp->child1.pnp, 1) ;
                prntree(pnp->child2, 1) ;
            }
            else
                printf(" nil ") ;
            break ;
        case PLIST :
            printf("list: ( car cdr )") ;
            prntree(pnp->child1.pnp, 1) ;
            prntree(pnp->child2, 1) ;
            break ;
        }
        indent -= 4 ;
    }
    free(pnp) ;
}

prntr(pnp) /* Gateway to tree printer */
struct parnode *pnp ;
{
    nodecount = 0 ;

    prntree(pnp, 1) ; /* Second arg turns on print vs. only 
                         memory dealloc */  
    printf("\nThe parse tree has %d nodes\n", nodecount) ;
}

int parenb ;  /* Left-right paren balance is used by 
                      scanner to find terminating nl */
int nqflag ;  /* Not just quote. If 0 then there have been 
                      no chars except \' */
main()
{
    parenb = nqflag = indent = 0 ;
    yyparse() ;
}
/* mlsp.y is a lisp parser */
/* This is a right-recursive grammer that emulates the LISP 
list structure, but it uses a single mid-list element, 
mlist, instead of the car-cdr combination. This results in 
50% smaller parse trees */

%{
#include <stdio.h>

extern yylex() ;
extern yyerror() ;

union ipval {
    int ival ;
    char *pval ; /* Pointer to list "value" */
} ;

struct symnode {
    char *name ;
    char valtype ;
    union ipval val ; /* integer value or pointer to list 
      or function */
    char *prop ; /* Pointer to property list. NULL = none */
    struct symnode *left ;
    struct symnode *right ;
} ;

union parval {
    int iv ; /* Integer or char atom value */
    char *strp ; /* Pointer for string atom */
    struct symnode *symp ; /* Pointer for symbol atom */
    struct parnode *pnp ; /* Non-atomic node pointer */
} ;

struct parnode {  /* Parse tree node */
    int pntyp ;
    int subtyp ;
    union parval child1 ;
    union parval child2 ;
} ;

typedef union {
    int ival ;
    char *strp ;
    struct symnode *symp ;
    struct parnode *parp ;
} YYSTYPE ; 

char *malloc() ;
#define PALL (struct parnode *)malloc(sizeof(struct parnode))
                                   /* Allocate a parnode */
static struct parnode *proot ;
 
/* The following production type IDs are assigned values in 
order of number of children to simplify tree utilities */
#define    PNIL     0  /* NO CHILDREN */
#define    PT           1  /* NO CHILDREN */
#define    PCHAR    2  /* 1 CHILD */
#define    PNUM     3  /* 1 CHILD */
#define    PSTR     4  /* 1 CHILD */
#define    PSYM     5  /* 1 CHILD */
#define    PATOM    6  /* 1 CHILD */
#define    PLIST    7  /* list: 1 CHILD */
#define    PMLIST   8  /* mlist: 1 CHILD */
#define    PATML    9  /* atom mlist : 2 CHILDREN */
#define    PLML     10 /* list mlist : 2 CHILDREN */

struct parnode *inatom(styp) /* Install an atom parsenode */
int styp ; /* Subtype */
{
    struct parnode *ptr ;

    (ptr = PALL)->pntyp = PATOM ;
    ptr->subtyp = styp ;
    return (ptr) ;
}

struct parnode *innt(mtyp, styp, ccnt, ch1, ch2)
int mtyp, styp, ccnt ;
struct parnode *ch1, *ch2 ;
{
    struct parnode *ptr ;

    (ptr = PALL)->pntyp = mtyp ;
    ptr->subtyp = styp ;
    ptr->child1.pnp = ch1 ;
    if (ccnt == 2)
        ptr->child2.pnp = ch2 ;
    return (ptr) ;
}

%}
%type <parp> atom list mlist
%token <symp> SYM 
%token <ival> CHAR NUM NIL T
%token <strp> STR
%token BPCR  /* Statement is terminated when scanner 
                 recognizes CR and balanced parens */
%token NIL   /* The scanner returns nil and () as token */

%start interp 

%%   /* begin rules section */

interp  :   /* empty */
    |   interp stat BPCR
            { parenb = nqflag = 0 ; }
    |   interp error 
            { while (getchar() != '\n') ; 
              parenb = nqflag = 0 ; 
              yyerrok ; /* Reenable strict 
                 checking on next input */
              yyclearin ; }
    ;
stat    :   '\'' list 
           {    prntr($2) ; }
    |   atom
           {    prntr($1) ; }
    |   '\'' atom
           {    prntr($2) ; }
    |   list
           {    prntr($1) ; }
    ;
list    :   '(' mlist ')'
            {   $$ = innt(PLIST, PATOM, 1, $2) ; } 
                              /* Note Irrelevant subtype */
    ;
mlist   :   atom
            {   $$ = innt(PMLIST, PATOM, 1, $1) ; }
    |   list
            {   $$ = innt(PMLIST, PLIST, 1, $1) ; }
    |   atom mlist
            {   $$ = innt(PMLIST, PATML, 2, $1, $2) ; }
    |   list mlist
            {   $$ = innt(PMLIST, PLML, 2, $1, $2) ; }
    ;
atom    :   SYM
            {   ($$ = inatom(PSYM))->child1.symp = $1 ; }
/* All atoms other than SYM are constants, so semantic 
analyzer need only check for SYM/not-SYM */
    |   CHAR
            {   ($$ = inatom(PCHAR))->child1.iv = $1 ; }
    |   STR
            {   ($$ = inatom(PSTR))->child1.strp = $1 ; }
    |   NUM
            {   ($$ = inatom(PNUM))->child1.iv = $1 ; }
    |   NIL
            {   $$ = inatom(PNIL) ; }
    |   T
            {   $$ = inatom(PT) ; }
    ;

%%  /* start of programs */
static nodecount ; /* For counting nodes while printing tree */
static int indent ;  /* Indent counter for printing parse tree */

prntree(pnp, prtr)
struct parnode *pnp ;
int prtr ; /* True means print, false only clear the tree */
{
    int ic ;

    nodecount++ ;
    if (prtr) {
        putchar('\n') ;
        for (ic = 0 ; ic < indent ; ic++)
            putchar(' ') ;
        indent += 4 ;
        switch (pnp->pntyp) {
        case PATOM :
            printf("atom:") ;
            switch (pnp->subtyp) {
            case PSYM :
                printf(" sym=%s",pnp->child1.symp->name) ;
                break ;
            case PNUM :
                printf(" num=%d",pnp->child1.iv) ;
                break ;
            case PSTR :
                printf(" str=%s",pnp->child1.strp) ;
                break ;
            case PCHAR :
                printf(" char=%c",pnp->child1.iv) ;
                break ;
            case PNIL :
                printf(" NIL ") ;
                break ;
            default :
                break ;
            }
            break ;
        case PMLIST :
            printf("mlist:") ;
            switch (pnp->subtyp) {
            case PATOM :
                printf(" atom") ;
                prntree(pnp->child1.pnp, 1) ;
                break ;
            case PLIST :
                printf(" list") ;
                prntree(pnp->child1.pnp, 1) ;
                break ;
            case PATML :
                printf(" atom mlist") ;
                prntree(pnp->child1.pnp, 1) ;
                prntree(pnp->child2.pnp, 1) ;
                break ;
            case PLML :
                printf(" list mlist") ;
                prntree(pnp->child1.pnp, 1) ;
                prntree(pnp->child2.pnp, 1) ;
                break ;
            }
            break ;
        case PLIST :
            printf("list: ( mlist )") ;
            prntree(pnp->child1.pnp, 1) ;
            break ;
        }
        indent -= 4 ;
    }
    free(pnp) ;
}

prntr(pnp) /* Gateway to tree printer */
struct parnode *pnp ;
{
    nodecount = 0 ;

    prntree(pnp, 1) ; /* Second arg turns on print vs. only 
                                   memory dealloc */    
    printf("\nThe parse tree has %d nodes\n", nodecount) ;
}

int parenb ;  /* Left-right paren balance is used by 
                 scanner to find terminating nl */
int nqflag ;  /* Not just quote. If 0 then there have been 
                 no chars except \' */
main()
{
    parenb = nqflag = indent = 0 ;
    yyparse() ;
}



AUTOMATA AND LANGUAGES

My work designing microprocessor development systems began as hardware experiments, as I described in Hybrid Tool For Universal Microprocessor Development and In-Situ Emulation Paces New Micros. To make this work useful to other engineers I had to provide supporting software, as I did for the MDT20. In those days assembly language was sufficient for most embedded microcontrollers. However, developers were increasingly using compiled languages even in embedded applications and, in any case, I wanted to support a wider variety of processors and applications.

Whether I would actually write and distribute my own compilers or not, I felt that my systems would not properly support them without my full understanding of compiler technology. I had developed my own ad hoc techniques for writing assemblers and debuggers and I knew that some compiler writers worked the same way. However, I was convinced that seat of the pants hacking could not substitute for formal language theory and compiler design.

AUTOMATA THEORY

I began studying compiler design in books and journal articles. One comment repeatedly appeared; that automata theory was the basis for all formal language design but was too abstract for mere programmers. This was a red flag for me. I was not going to be told that I was too dumb for anything. When, perusing the UCSC Computer Science Department courses, I saw that Frank DeRemer, a luminary in the history of LALR(1) parsing, would be teaching the graduate course in automata theory, I signed up.

As an electrical engineer with few computer science classes, I was terribly unqualified for this course but, being an instructor at another state college, I didn’t need to meet the prerequisites. It was a computer science Ph.D. requirement, usually the last class reluctantly taken by students who had been hoping that it would be dropped as a requirement. In the first lecture, the professor (not DeRemer but good anyway) was speaking Greek-- literally. Automata theory uses several alphabets, especially Greek, in addition to English. I memorized the Greek alphabet (upper and lower) as a self-imposed first homework assignment so that I might have some hope of following the next day’s lecture. A few weeks into the class, the professor informed me that I was not her worst student; she had one who was worse.

PUMPING LEMMA

I steadily improved relative to the other nine students until, near the end of the course, I joined the elite group of two students whose homework the professor would occasionally cite in her lectures. She presented my linear languages pumping lemma proof. That she chose my proof was especially gratifying because I contradicted the authors of our book. The book, infamously entitled Introduction to Automata Theory, Languages, and Computation, is not just an “introduction” but delves deeply into some of the most advanced topics in computer science. The authors, Hopcroft and Ullman, posed the following problem:

6.11 Prove the following pumping lemma for linear languages. If L is a linear language, then there is a constant L such that if z in L is of length n or greater, then we may write z = uvwxy, such that |uvxy| < n, |vx| > 1, and for all i > 0, uviwxiy is in L.

For my proof, I first define a “Linear Normal Form” and explain how any linear grammar can be converted to it. Anything that I can prove for my LNF grammar is automatically proved for any equivalent grammar, i.e. for any linear language. The benefit of my LNF is that it has only one structural form. The diagram of this structure (supported by my analysis) in my proof makes it obvious that the authors’ contention that |uvxy| < n is incorrect. As my proof states, the correct relationship must be |uvxy| < n. This has no practical consequence. Of importance to me is that I had learned how to reduce the complexity of a problem by first showing a more tractable equivalent problem and I was clearly understanding the subject.
My Linear Language Pumping Lemma

COMPILER DESIGN

At the end of the automata class, when I asked the professor what class to take next, she replied that the only “next” was to finish my thesis and get my Ph.D. I continued studying compiler design anyway. As I had already begun to realize, automata provides the theoretical foundation for language design but a deep understanding of it is not necessary for writing compilers.

LANGUAGE AND SYSTEM DESIGN

Although overkill for writing compilers, my study of automata theory has influenced my thinking about system design and has been surprisingly practical. The correlation between the Chomsky hierarchy and state machines as well as the more general equivalence of languages and computing machines gives me a solid theoretical foundation for some of my opinions about system design and implementation. For example, I hold the heretical view that modeling languages like UML cannot describe complete systems because they have the computational power only of a DFA, which can recognize only regular expressions, the lowest language form in the Chomsky hierarchy. As for day-to-day practical application, I have been lucky enough to get assignments where my understanding of parsing principles has been valuable. Several of these, such as the BM-Hitachi 747, have included the need to develop simple languages as part of the user interface, but this could be done just knowing basic parsing techniques like operator precedence. However, the most important thing that I did in the Abbott Clinical Instrument Development System depended on a deep understanding of languages and parsing. In that project, the scripting language is both configurable and not context-free, yet I was able base the compiler on an LALR(1) parser by separating the grammar into context- free and context-sensitive elements. This enabled me to describe most of the grammar declaratively (in BNF), to use a standard parser generator (Bison) for much of the program, and to reap the maintenance and documentation benefits of a formal regular design as opposed to an ad hoc program.




Prev   Next   Site Map   Home   Top   Valid HTML   Valid CSS