. | . | . | . | David McCracken |
Language Designupdated:2016.07.13 |
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 ); }
/**************************************************************************** * 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 ] ); }
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() ; }
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.
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.
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
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.
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.