-- Last edited by Chi Yung, April 23, 1982  5:06 PM
-- Last edited by Chi Yung, April 30, 1982  2:05 PM
-- Last edited by Chi Yung, May 21, 1982  12:23 PM
-- Last edited by Chi Yung, September 1, 1983  11:21 AM
-- Last edited by JWhite,   7-May-85  9:05:37

DIRECTORY
  Ascii,
  BruceDefs,
  MachineParseDefs,
  String,
  Storage,
  InlineDefs,
  inD: FROM "interactordefs",
  exD: FROM "exceptiondefs",
  vmD: FROM "VirtualMgrDefs";

BruceCompiler: PROGRAM
  IMPORTS BruceDefs, MachineParseDefs, String, Storage, InlineDefs, exD, inD
  EXPORTS BruceDefs =
BEGIN

OPEN MachineParseDefs;

DescribeTable: TYPE = DESCRIPTOR FOR ARRAY [1..20]  OF CARDINAL;
--The number of indexing elements (20 here) should be changed when there 
--are more than 20 calibration points for either the temperature correction 
--table or the boat speed conversion table or the flow rate coversion 
--table.
Record: TYPE = RECORD[a,b: DescribeTable];
--DescribeTable and Record allow arrays of different sizes to use or call 
--the same procedures,such as the procedure known as Interpolation2 or 
--FillArray.It is an efficient and compact way to transmit informations, 
--such as the contents of a big array, into a procedure.

-- The following 4 constants are compiled time constants. They can not be 
-- changed or updated by the constant file.  If one or more of these 
-- constants has to be changed, one must compile the BruceCompiler program 
-- and then bind it by bind BruceTalk.
NumberOfProfiledTemp: CARDINAL = 5;
-- This number should be changed if you want to expand the correctable 
-- range of temperature or if you want to increase the accuracy of the 
-- interpolation.
NumberOfGasType: CARDINAL = 12;
-- This number should be changed if there are more than 12 types of gases.
NumberOfCalibratedGasRate: CARDINAL = 11;
-- This number should be changed if you want to increase the accuracy of 
-- the interpolation.
NumberOfCalibratedboatRate: CARDINAL = 10;
-- This number should be changed if you want to increase the accuracy of 
-- the interpolation.
-- GasOneToGasTwoSafetyRatioHole:CARDINAL;
GasOneToGasTwoSafetyRatio: CARDINAL = 250;
-- The safety ratio of Hydrogen (Gas1) to Oxygen (Gas2) is set to be 2.5.  
-- If the ratio of H2 to O2 mixture in any of our steps is larger 
-- than this saftey ratio, the program will stop. The number, 2.5, is 
-- multiplied by a factor 100 to improve the accuracy of comparision.
GasOne: STRING = "H2";
GasTwo: STRING = "O2";

tcaON: BOOLEAN ← FALSE;
O2ON: BOOLEAN ← FALSE;
POCl3ON: BOOLEAN ← FALSE;

boatScaledFactor:CARDINAL ← 1;
TempScaledFactor:CARDINAL ← 1;
GasScaledFactor:CARDINAL ← 2;
-- ScaledFactor is the number of digits allowed after the decimal.
-- Temperatures are scaled by a factor of 10.
-- Gas flow rates are scaled by a factor of 100.
-- The reason for the factor of a 100 is because the parsing of the 
-- gas flow rate is done with a multiplication factor of a 100 so as 
-- to talk with the DDC microcontroller of the Bruce furnaces. For 
-- similar reason temperature has be scaled by a factor of 10 to 
-- talk with the DDC.

boatSpeedInchUpperBound:CARDINAL ← 1000;
--Max boat speed rate is 100 inches/min. Since the scale factor for boat 
--speed is 1 therefore 100 becomes 1000.
boatSpeedPercentUpperBound:CARDINAL ← 990;
--Max boat speed rate is 99%. Since the scale factor for boat speed is 1 
--therefore 99% becomes 990.
GasFlowLitreUpperBound: CARDINAL ← 2000;
--Max gas flow rate is 20 l/m.Since the scale factor for gas flow is 2 
--therefore 20 becomes 2000.
GasFlowPercentUpperBound:CARDINAL ← 9999;
--Max gas flow rate is 99.99%.Since the scale factor for gas flow is 2 
--therefore 99.99% becomes 9999.

NumberOfGasController: CARDINAL = 7;
-- At present, each of the Bruce 8-stack furnace has a maximum capability 
-- of controlling 7 gas controllers.

MinProfiledTemp: ARRAY BruceDefs.TubeNumber OF Index51;
Index51: TYPE =ARRAY Reaction OF CARDINAL;
MaxProfiledTemp: ARRAY BruceDefs.TubeNumber OF Index61;
Index61: TYPE =ARRAY Reaction OF CARDINAL;

maxRampRateUp:CARDINAL; 
maxRampRateDown:CARDINAL; 

MinGasFlowConversion: CARDINAL;
MaxGasFlowConversion: CARDINAL;

boatSpeedLoBound: CARDINAL; 
boatSpeedHiBound: CARDINAL;
boatSpeedSafetyLimit: CARDINAL;

ProfiledTemp: TYPE = CARDINAL;
ProfiledTempIndex: TYPE = [1..NumberOfProfiledTemp];
ProfiledTempTable: TYPE = ARRAY BruceDefs.TubeNumber OF Index11;
Index11: TYPE = ARRAY Reaction OF Index12;
Index12: TYPE = ARRAY ProfiledTempIndex OF ProfiledTemp;
ptt:ProfiledTempTable;
-- The first and the last components of ptt[BruceDefs.TubeNumber][Reaction] 
-- are automtically the MinProfiledTemp[BruceDefs.TubeNumber][Reaction] and 
-- the MaxProfiledTemp[BruceDefs.TubeNumber][Reaction].
ProfiledTempCorrectionTable: TYPE = ARRAY BruceDefs.TubeNumber OF Index1;
Index1: TYPE = ARRAY Reaction OF Index2;
Index2: TYPE = ARRAY BruceDefs.ZoneIndex  OF Index3;
Index3: TYPE = ARRAY ProfiledTempIndex OF CARDINAL;
ptct: ProfiledTempCorrectionTable;

--tables for tube dependent coefficient informaltion
FurnaceControlTable: TYPE = ARRAY BruceDefs.TubeNumber OF RECORD[
     controlTable: BruceDefs.ControlTable,
     tempRangeTable: BruceDefs.TempRangeTable];
fct:FurnaceControlTable;

CalibratedGasRate: TYPE = CARDINAL; --this rate is in litres per minute x 100
CalibratedGasRateIndex: TYPE = [1..NumberOfCalibratedGasRate];
CalibratedGasRateTable: TYPE = ARRAY CalibratedGasRateIndex OF 
CalibratedGasRate;
cgrt: CalibratedGasRateTable;
-- The first and the last components of cgrt are automatically the 
-- MinGasFlowConversion and the MaxGasFlowConversion.
CailibratedGasRateConversionTable: TYPE = ARRAY  GasTypeIndex OF Index31;
Index31: TYPE = ARRAY CalibratedGasRateIndex OF CARDINAL;
cgrct: CailibratedGasRateConversionTable;

Gas: TYPE = STRING;
GasControllerIndex: TYPE = [1..NumberOfGasController];
GasTypeIndex: TYPE = [1..NumberOfGasType];
GasPlumbingTable: TYPE = ARRAY BruceDefs.TubeNumber OF Index21;
Index21: TYPE = ARRAY GasControllerIndex OF Gas;
GasTypeTable: TYPE = ARRAY GasTypeIndex OF Gas;
gpt: GasPlumbingTable;
gtt: GasTypeTable;
-- gtt is where you enter the names of the gases that you are going to use 
-- in all eight furnace tubes.

CalibratedboatRate: TYPE = CARDINAL; --this rate is in inches per minute
CalibratedboatRateIndex: TYPE = [1..NumberOfCalibratedboatRate];
CalibratedboatRateTable: TYPE = ARRAY CalibratedboatRateIndex OF 
CalibratedboatRate;
cbrt: CalibratedboatRateTable;
-- The first and the last components cbrt are automatically the 
-- boatSpeedLoBound and the boatSpeedHiBound.
CalibratedboatRateConversionTable: TYPE = ARRAY  CalibratedboatRateIndex OF 
CARDINAL;
cbrct: CalibratedboatRateConversionTable;

Reaction: TYPE ={Dry, Wet};
R: Reaction;
s:STRING ← [10];
sDry:STRING = "dry";
sWet:STRING = "wet";

j: BruceDefs.GasColIndex;
l: GasTypeIndex;
WriteGasRow: BOOLEAN ← TRUE;
WriteTempRow:BOOLEAN ← TRUE;

GasOneFlow: CARDINAL ← 0;-- Gas1 now is H2.
GasTwoFlow: CARDINAL ← 0;-- Gas2 now is O2.

tNumberOfDigits, gNumberOfDigits, bNumberOfDigits: CARDINAL;
-- tNumberOfFDigits is the number of digits plus decimal if applicable in 
-- the temperature parsing. ( eg. tNumberOfDigits for 899.9 is 5.) 
-- Similarly gNumberOfDigits and bNumberOfDigits are for gas and boat.

InitTube: ARRAY BruceDefs.TubeNumber OF BOOLEAN;
-- If the idle conitions for a tube (eg. tube 5 ) is specified in the idle 
-- condition table , then the corresponding entry in the InitTube array is 
-- set to be TRUE. This allows the checking of whether the idle conditions 
-- of the tube were set when we parse the tube number in the 
-- BruceRecipe.text. ( InitTube[5]← TRUE )

CompileData: TYPE=RECORD[
  brp:BruceDefs.RecipePtr,
  parsedTube:BOOLEAN,
  tube:BruceDefs.TubeNumber];
CompileHandle: TYPE=POINTER TO CompileData;

IdleData: TYPE = RECORD[
  IdleTube:BruceDefs.TubeNumber,
  IdleReaction: ARRAY BruceDefs.TubeNumber OF Reaction,
  IdleTempTable: ARRAY BruceDefs.TubeNumber OF BruceDefs.TempRow 
  ← ALL[ALL[0]],
  IdleGasTable: ARRAY BruceDefs.TubeNumber OF BruceDefs.GasRow 
  ← ALL[ALL[0]],
  IdleFunction1Table: ARRAY BruceDefs.TubeNumber OF BruceDefs.Function1Bits];
IdleHandle: TYPE = POINTER TO IdleData;

id: IdleData;



InitTables: PUBLIC PROCEDURE [constantsdata: vmD.VirtualMessagePtr] =
-- This procedure calls on six other procedures to intake the informations 
-- from the BruceConstants.text file to initialize the furnaces tubes.
BEGIN
  ph:ParseHandle ← @pd;
  pd:ParseData;
  fields: ARRAY [0 .. 7) OF FieldRec ←
    [["BoatRateCalibration"L, ParseBoatRateCalibration],
     ["Constants"L, ParseConstants], 
     ["GasFlowCalibration"L, ParseGasFlowCalibration], 
     ["GasPlumbing"L, ParseGasPlumbing],
     ["TemperatureCalibration"L, ParseTemperatureCalibration],
     ["IdleConditions"L, ParseIdleConditions],
     ["FurnaceControlCoefficients"L, ParseFurnaceCoefficients]];
  pd.breakSet ← ":.@""+/"L;
  pd.blankSet ← " 	
"L;
  ph.message ← constantsdata;
  pd.userData ← @id;
  
  
  FOR i: CARDINAL IN GasTypeIndex DO
    gtt[i] ← Storage.String[10];
  ENDLOOP;
  -- This loop initializes gtt, the gas type table, so that each component  
  -- ,which is the name of the name of a gas, is a string of at most ten 
  -- characters.
  
  FOR i: CARDINAL IN GasTypeIndex DO
      String.AppendString[gtt[i], "GasName"];
  ENDLOOP;
  -- This loop sets up the dummy names for the gas type table to be 
  -- GasName. This implies that the real name of the gas cannot be GasName.
  
  FOR i: CARDINAL IN BruceDefs.TubeNumber DO
    FOR k: CARDINAL IN GasControllerIndex DO
      gpt[i][k] ← Storage.String[10];
    ENDLOOP;
  ENDLOOP;
  -- This double loop initializes gpt, the gas plumbing table, so that each 
  -- component , which is the name of a gas, is a string of at most ten   
  -- characters.
  
  FOR i: CARDINAL IN BruceDefs.TubeNumber DO
    FOR k: CARDINAL IN GasControllerIndex DO
      String.AppendString[gpt[i][k], "*"];
    ENDLOOP;
  ENDLOOP;
  -- This double loop sets up the gas plumbing table in such a way that if  
  -- the gas controller of the furance tube is not plumbed then a * is  
  -- assigned to the corresponding component. This implies that the 
  -- real name of a gas cannot have the symbol *.
  
  ParseMessage[ph, DESCRIPTOR[fields]];
  exD.DisplayBothExceptionLines
  ["Hi!  I am ready.  Get your favorite recipe and click the word compile inside", 0, "the lower menu.  Let's start cooking!  I am hungry!  Aren't you?", 0, FALSE];
  
END; -- of InitTables



ParseBoatRateCalibration: FieldParseProc=
BEGIN
  ih:IdleHandle ← ph.userData;
  dtcbrt: DescribeTable ← DESCRIPTOR[cbrt];
  -- This DesribeTable, dtcbrt, describes the CalibratedBoatRateTable.
  dtcbrct: DescribeTable ← DESCRIPTOR[cbrct];
  cbrt ←ALL[0]; -- All the entries for cbrt is initialized to be zero.
  cbrct ← ALL[0];
  IF ParseToLeadingChar[ph] THEN BEGIN
    CheckWord[ph, "InchesPerMin"];
    FillArray[ph, dtcbrt, boatScaledFactor, boatSpeedInchUpperBound];
    CheckWord[ph, "Percentage"];
    FillArray[ph, dtcbrct, boatScaledFactor, boatSpeedPercentUpperBound];
    boatSpeedLoBound ← cbrt[1];
    boatSpeedHiBound ← cbrt[LENGTH[cbrt]];    
    ParseToEndOfField[ph];
  END ELSE ERROR
      ParseFault["BoatRateCalibration field is missing.", ph.currentChar-1, ph.currentChar-1];
END; -- of BoatRateCalibration



ParseConstants: FieldParseProc=
BEGIN
  SubFieldRecord: TYPE = RECORD
  [name: STRING, var: CARDINAL, ScaleFactor: CARDINAL, 
  LowBound: CARDINAL, HiBound: CARDINAL, Unit: CHARACTER, 
  found: BOOLEAN ← FALSE];
  ih:IdleHandle ← ph.userData;
  curPosition, lastPosition: vmD.CharIndex;
  word: STRING ← [30];
  SubFieldName: STRING ← [30];
  ErrorMessage: STRING ← [100];
  SubField: ARRAY [0..3) OF SubFieldRecord ←
    [["boatSpeedSafetyLimit"L , 0, boatScaledFactor,      
     boatSpeedLoBound, boatSpeedHiBound, '"],
     ["maxRampRateUp"L, 0, TempScaledFactor, 0, 1000, 
     Ascii.ControlD],
     ["maxRampRateDown"L, 0, TempScaledFactor, 0, 1000, 
     Ascii.ControlD]];
  
  --The following procedure is an internal procedure called within this 
  --ParseConstants procedure.
  ReadConstantLine: PROCEDURE[ph: ParseHandle, ScaleFactor: CARDINAL, 
  LowBound: CARDINAL, HiBound: CARDINAL, Unit: CHARACTER]  
  RETURNS[Value: CARDINAL]=
  BEGIN
    ParseToNonWhite[ph]; CheckChar[ph, '←];
    MissingValueCheck[ph];
    Value ← ParseScaledNumber[ph, ScaleFactor, LowBound, HiBound].n;
    ParseToNonWhite[ph]; CheckChar[ph, Unit];
    ParseToNonWhite[ph]; CheckChar[ph,'/];
    CheckWord[ph,"min"];
    ParseToEndOfLine[ph];
  END;-- Of ReadConstantLine
  
  IF ParseToLeadingChar[ph] THEN BEGIN
    WHILE ParseToLeadingChar[ph] DO
      curPosition ← ph.currentChar;
      SubFieldName.length ← 0;
      DO
        ParseWord[ph,word];
        IF word[0] ~IN ['a..'z]  THEN 
        BEGIN
          ph.currentChar ← ph.currentChar-word.length;
          EXIT;
        END;
        String.AppendString[SubFieldName, word];
      ENDLOOP;
      lastPosition ← ph.currentChar;
      FOR i: CARDINAL IN [0..3) DO 
        IF String.EquivalentString[SubFieldName,SubField[i].name] THEN 
        BEGIN  
          IF SubField[i].found = FALSE THEN BEGIN
            SubField[i].var ←ReadConstantLine
            [ph,SubField[i].ScaleFactor,SubField[i].LowBound,  
            SubField[i].HiBound, SubField[i].Unit].Value;
            SubField[i].found ← TRUE;
            EXIT;
          END ELSE ERROR ParseFault["Multiple defined subfield illegal",
          curPosition,lastPosition];
        END 
      REPEAT
      FINISHED =>
        ParseFault["Unrecogonizable constant!",curPosition,lastPosition];
      ENDLOOP;
    ENDLOOP;
    boatSpeedSafetyLimit ← SubField[0].var;
    maxRampRateUp ← SubField[1].var;
    maxRampRateDown ← SubField[2].var;
    FOR i: CARDINAL IN [0..3) DO
      IF SubField[i].found = FALSE THEN BEGIN
        ErrorMessage.length ← 0;
        String.AppendString[ErrorMessage, SubField[i].name];
        String.AppendString[ErrorMessage," subfield is missing."];
        ParseFault[ErrorMessage, ph.currentChar, ph.currentChar];
      END;
    ENDLOOP;
  END ELSE ERROR
    ParseFault["Constants field is missing.", ph.currentChar-1, 
    ph.currentChar-1];
END; -- of ParseConstants



ParseGasFlowCalibration: FieldParseProc=
BEGIN
  dtcgrct: DescribeTable;
  i: CARDINAL ← 0;
  ih:IdleHandle ← ph.userData;
  dtcgrt: DescribeTable ← DESCRIPTOR[cgrt];
  -- This DesribeTable, dtcgrt, describes the CalibratedGasRateTable.
  GasTypeName: STRING ← [10];
  ErrorMessage: STRING ← [100];
  cgrt ← ALL [0];
  cgrct ← ALL[ALL[0]];
  -- ALL the components of the cgrt and cgrct are initialized to be zero.
  IF ParseToLeadingChar[ph] THEN BEGIN 
   CheckWord[ph, "Gas"];
   FillArray[ph, dtcgrt, GasScaledFactor, GasFlowLitreUpperBound];
   MinGasFlowConversion ← cgrt[1];
   MaxGasFlowConversion ← cgrt[LENGTH[cgrt]];  
    
   WHILE ParseToLeadingChar[ph] DO
       IF i >= NumberOfGasType THEN 
        BEGIN
          ErrorMessage.length ← 0;
          String.AppendString[ErrorMessage, "> "];
          String.AppendNumber[ErrorMessage, NumberOfGasType, 10];
          String.AppendString[ErrorMessage, 
          " gas types. Change in the BruceCompiler.mesa is needed."];
          ERROR  ParseFault[ErrorMessage, ph.currentChar, ph.currentChar];
       END;
       ParseWord[ph,GasTypeName];
       FOR m: CARDINAL IN [1..i] DO
         IF String.EquivalentString[GasTypeName, gtt[m]] THEN ERROR
           ParseFault["This gas has already been calibrated.", 
           ph.currentChar - GasTypeName.length, ph.currentChar];
       ENDLOOP;  
       i ← i +1;
       gtt[i].length ← 0;
       -- This reintializes the name of the gas to be nil so that we can 
       -- use String.AppendString
       String.AppendString[gtt[i], GasTypeName];
       dtcgrct ← DESCRIPTOR[cgrct[i]];
       FillArray[ph, dtcgrct, GasScaledFactor,GasFlowPercentUpperBound];
  ENDLOOP; -- End of the WHILE statement.

END ELSE 
   ParseFault["GasFlowCalibration field is missing.", ph.currentChar-1, 
   ph.currentChar-1];
END; -- Of GasFlowCalibration



ParseGasPlumbing: FieldParseProc=
BEGIN
i, m, n: CARDINAL;
p: CARDINAL ← 0;
GasPlumbingName: STRING ←[10];
ErrorMessage: STRING ← [100];
ih:IdleHandle ← ph.userData;
IF ParseToLeadingChar[ph] THEN BEGIN
  
  WHILE ParseToLeadingChar[ph] DO
    IF p>=8 THEN ERROR
    ParseFault["There are only 8 furnace tubes.",ph.currentChar, 
    ph.currentChar + 1];
    i ← ParseDecimal[ph,1,8];
    p ← p +1;
    
    -- The following codes check whether the tube is plumbed or not.
    n ← 0;
    FOR q: [1..NumberOfGasController] IN [1..NumberOfGasController] DO
      IF String.EquivalentString[gpt[i][q], "*"] THEN n←n+1 ELSE EXIT;
    ENDLOOP;
    IF n # NumberOfGasController THEN 
    BEGIN
       ErrorMessage.length ← 0;
       String.AppendString[ErrorMessage, "Tube number "];
       String.AppendNumber[ErrorMessage, i, 10];
       String.AppendString[ErrorMessage, " has already been plumbed."];
       ERROR  ParseFault[ErrorMessage, ph.currentChar -1, ph.currentChar];
    END;-- of tube plumbing check.
    
    FOR k: [1..NumberOfGasController] IN [1..NumberOfGasController] DO
      ParseWord[ph, GasPlumbingName];
      -- The following codes check the name of the gas with the names in 
      -- the gas type table.
      IF ~String.EquivalentString[GasPlumbingName, "*"] THEN BEGIN
        m ← 0;
        FOR r: [1..LENGTH[gtt]] IN [1..LENGTH[gtt]] DO
          IF ~String.EquivalentString[GasPlumbingName, gtt[r]] THEN m←m+1 
          ELSE EXIT;
        ENDLOOP;
        IF m = LENGTH[gtt] THEN ERROR
        ParseFault["The gas you specified has not be calibrated at all.", 
        ph.currentChar-GasPlumbingName.length, ph.currentChar];
      END;  -- of gas name check.
      gpt[i][k].length ←0;
      -- this initializes the string to be nil so that we can use 
      -- String.AppendString.
      String.AppendString[gpt[i][k], GasPlumbingName];
    ENDLOOP;
    
    IF ParseToLeadingCharOnLine[ph] THEN BEGIN
      ParseWord[ph, GasPlumbingName];
      ParseFault["You can't have more than 7 gases plumbed.", 
      ph.currentChar-GasPlumbingName.length, ph.currentChar];
    END;
  ENDLOOP; -- End of the WHILE statement.

END ELSE 
ParseFault["GasPlumbing field is missing.", ph.currentChar-1, ph.currentChar-1];
END; --Of GasPlumbing



ParseTemperatureCalibration: FieldParseProc=
BEGIN
ReactionIndex: TYPE = Reaction[Dry..Wet];
RecordTemp: TYPE = RECORD[a, b,c,d : CARDINAL];
c, i, m, e, w, NoOfDigits: CARDINAL;
pArray: ARRAY [1.. NumberOfProfiledTemp] OF POINTER TO RecordTemp;
pptt:ARRAY [1.. NumberOfProfiledTemp] OF POINTER TO RecordTemp;
RecordArray: ARRAY [1.. NumberOfProfiledTemp]  OF RecordTemp;
TemStore: ARRAY [1.. NumberOfProfiledTemp]  OF RecordTemp;
curPosition: vmD.CharIndex;
st: STRING ← [10];
ExtraNumber: STRING ← [10];
ErrorMessage: STRING ← [100];
ih:IdleHandle ← ph.userData;
ptt ← ALL[ALL[ALL[0]]]; -- initialize ptt.
ptct ← ALL[ALL[ALL[ALL[0]]]]; -- initialize ptct.
MinProfiledTemp ← ALL[ALL[0]];-- initialize MinProfiledTemp
MaxProfiledTemp ← ALL[ALL[0]];-- initialize MaxProfiledTemp
IF ParseToLeadingChar[ph] THEN BEGIN
  WHILE ParseToLeadingChar[ph] DO
    curPosition ← ph.currentChar; 
    -- This provides a "marker" for the error message.
    i ← ParseDecimal[ph,1,8];
    ParseWord[ph, st];-- This provides the word in the error message below.
    ph.currentChar ← ph.currentChar - st.length;
    -- this allow the handle to go back in front of the word so that 
    -- we can use the procedure GetReaction.
    R ← GetReaction[ph];
     
    -- The following codes fill the ptt[i][R] and check for full ptt[i][R].
    FOR m IN [1..NumberOfProfiledTemp] DO
      IF ptt[i][R][m] = 0 THEN BEGIN
        MissingValueCheck[ph];
        [ptt[i][R][m], NoOfDigits] ← 
        ParseScaledNumber[ph,TempScaledFactor,0,13000];
        -- 1300 degree C is the absolute maximum temperature allowed.
        EXIT;
      END;
    REPEAT
    FINISHED => BEGIN
      ErrorMessage.length ← 0;
      String.AppendString[ErrorMessage, "Entries for tube "];
      String.AppendNumber[ErrorMessage, i, 10];
      String.AppendString[ErrorMessage, ", "];
      String.AppendString[ErrorMessage, st];
      String.AppendString[ErrorMessage, 
      " process profiled temp table is full."];
       ERROR ParseFault[ErrorMessage, curPosition, curPosition];
    END;
    ENDLOOP;-- End of fill ptt[i][R].
     
    -- The following codes fill the ptct.
    FOR k: BruceDefs.ZoneIndex IN BruceDefs.ZoneIndex DO
      IF ~ParseToLeadingCharOnLine[ph] THEN BEGIN
        ErrorMessage.length ← 0;
        String.AppendNumber[ErrorMessage, 4-k , 10];
        String.AppendString[ErrorMessage, 
        " of the 3 entries for the 3 zones are missing."];
        ERROR ParseFault[ErrorMessage, ph.currentChar , ph.currentChar];
      END;
      [ptct[i][R][k][m], NoOfDigits]  ← 
      ParseScaledNumber[ph,TempScaledFactor,0,13000]
    ENDLOOP; -- End of fill ptct.
     
    -- The following codes take care of the extra number on the line.
    IF ParseToLeadingCharOnLine[ph] THEN BEGIN
      ParseWord[ph, ExtraNumber];
      e ← ExtraNumber.length;
      IF ParseCharIfCharIs[ph, '.] THEN BEGIN
        ParseWord[ph, ExtraNumber];
        e ← e +1 +ExtraNumber.length;
      END;
      ERROR ParseFault["There are only three zones in the furnace.",
      ph.currentChar-e, ph.currentChar];
    END; -- of taking care of the extra number on the line.
  
  ENDLOOP; --End of the WHILE statement.
  
  --  The following codes check for partially filled table.
  FOR u : BruceDefs.TubeNumber IN BruceDefs.TubeNumber DO
    FOR v: ReactionIndex IN ReactionIndex DO
      IF ptt[u][v][1] # 0  AND ptt[u][v][NumberOfProfiledTemp] = 0 THEN        
      BEGIN
        FOR w IN [2..NumberOfProfiledTemp] DO
          IF ptt[u][v][w] = 0 THEN EXIT;
        ENDLOOP;
        ErrorMessage.length ← 0;
        String.AppendString[ErrorMessage, "Profiled temp table needs "];
        String.AppendNumber[ErrorMessage, NumberOfProfiledTemp - w +1, 10];
        String.AppendString[ErrorMessage, " more rows for tube "];
        String.AppendNumber[ErrorMessage, u, 10];
        IF v = Dry THEN 
        String.AppendString[ErrorMessage, " dry process."] 
        ELSE 
        String.AppendString[ErrorMessage, " wet process."];
        ERROR  ParseFault[ErrorMessage, ph.currentChar-1, 
        ph.currentChar-1];
      END;
    ENDLOOP;
  ENDLOOP; -- End of check for partially filled table.

  -- The following loops sort out the order of the profiled temperatures. 
  -- This subroutine is NOT based on binary search.
  FOR u : BruceDefs.TubeNumber IN BruceDefs.TubeNumber DO
    FOR v: ReactionIndex IN ReactionIndex DO
      IF ptt[u][v][1] # 0 THEN  BEGIN
        FOR g: CARDINAL IN [1..NumberOfProfiledTemp] DO
          RecordArray[g] ←[ ptt[u][v][g] , ptct[u][v][1][g] , 
          ptct[u][v][2][g], ptct[u][v][3][g]];
          pptt[g] ← @RecordArray[g];
        ENDLOOP;
        pArray ← pptt;
        FOR h: CARDINAL IN [2..NumberOfProfiledTemp] DO
          c ←h;
          UNTIL c = 1 DO
            IF pptt[c]↑.a < pptt[c-1]↑.a THEN BEGIN
              pArray[c]←pptt[c-1];
              pArray[c-1]←pptt[c];
              c ← c-1;
              pptt ← pArray;
            END ELSE BEGIN
              c ← c-1;
            END;
          ENDLOOP;
        ENDLOOP;
        FOR f: CARDINAL IN [1..NumberOfProfiledTemp] DO
          TemStore[f] ← pArray[f]↑;
        ENDLOOP;
        FOR f: CARDINAL IN [1..NumberOfProfiledTemp] DO
          ptt[u][v] [f]← TemStore[f].a;
          ptct[u][v][1][f]← TemStore[f].b;
          ptct[u][v][2][f]← TemStore[f].c;
          ptct[u][v][3][f]← TemStore[f].d;
        ENDLOOP;
      END;
    ENDLOOP;
  ENDLOOP;
  -- End of the sorting routine.
  
  FOR u : BruceDefs.TubeNumber IN BruceDefs.TubeNumber DO
    FOR v: ReactionIndex IN ReactionIndex DO
      FOR z: BruceDefs.ZoneIndex IN BruceDefs.ZoneIndex DO
        FOR f: CARDINAL IN [2..NumberOfProfiledTemp] DO
          IF ptct[u][v][z][f]< ptct[u][v][z][f-1] THEN BEGIN
            ErrorMessage.length ← 0;
            String.AppendString[ErrorMessage, "Profiled temp "];          
            AppendScaleNumber[ptt[u][v][f], TempScaledFactor,ErrorMessage];
            String.AppendString[ErrorMessage, " for tube # "];
            String.AppendNumber[ErrorMessage, u, 10];
            IF v = Dry THEN 
            String.AppendString[ErrorMessage, " dry process"]
            ELSE String.AppendString[ErrorMessage, " wet process"];
            String.AppendString[ErrorMessage, " zone # "];
            String.AppendNumber[ErrorMessage, z, 10];
            String.AppendString[ErrorMessage, 
            " is not in ascending order. "];
            exD.DisplayBothExceptionLines[ ErrorMessage,0, "", 0, FALSE];
            IF  ~inD.Confirm[2] THEN ERROR ParseFault
           ["Change profiled temps and click InitTables in the lower menu.",
            ph.currentChar, ph.currentChar];
          END;
        ENDLOOP;
      ENDLOOP;
    ENDLOOP;
  ENDLOOP;

  FOR u : BruceDefs.TubeNumber IN BruceDefs.TubeNumber DO
    FOR v: ReactionIndex IN ReactionIndex DO
      MinProfiledTemp[u][v] ← ptt[u][v][1];
      MaxProfiledTemp[u][v] ← ptt[u][v][NumberOfProfiledTemp] ;
    ENDLOOP;
  ENDLOOP;

  END ELSE 
    ParseFault["TemperatureCalibration field is missing.", 
    ph.currentChar-1, ph.currentChar-1];
END; --Of TemperatureCalibration



ParseIdleConditions: FieldParseProc=
BEGIN
  k,m,n: CARDINAL;
  temp: CARDINAL;
  gas: CARDINAL;
  TempCorrectArray:ARRAY[1..3] OF CARDINAL;
  gasName: STRING ← [10];
  ErrorMessage: STRING ← [100];
  curPosition: vmD.CharIndex;
  ih: IdleHandle ←ph.userData;
  id.IdleTempTable ← ALL[ALL[0]];
  id.IdleGasTable← ALL[ALL[0]];
  id.IdleFunction1Table ← ALL[ALL[0]];
  
  FOR q:CARDINAL IN BruceDefs.TubeNumber DO
    InitTube[q] ← FALSE;  -- this loop initializes the InitTube.
  ENDLOOP;
  
  IF ParseToLeadingChar[ph] THEN BEGIN
    WHILE  ParseToLeadingChar[ph] DO
      curPosition ← ph.currentChar;
      MissingValueCheck[ph];
      id.IdleTube ← ParseDecimal[ph, 1, 8];
      IF id.IdleTempTable[id.IdleTube][1] # 0 THEN BEGIN
        ErrorMessage.length ← 0;
        String.AppendString[ErrorMessage, "Idle conditions for tube no. "];
        String.AppendNumber[ErrorMessage, id.IdleTube, 10];
        String.AppendString[ErrorMessage, " have already been entered "];
        ERROR ParseFault[ErrorMessage, curPosition, curPosition];
      END;
      R ← GetReaction [ph];
      id.IdleReaction[id.IdleTube] ← R;
      IF ptt[id.IdleTube][R][1] = 0 THEN BEGIN
        ErrorMessage.length ← 0;
        String.AppendString[ErrorMessage, "Temperatures for tube no. "];
        String.AppendNumber[ErrorMessage, id.IdleTube, 10];
        IF R = Dry THEN String.AppendString[ErrorMessage, " dry process"]
        ELSE String.AppendString[ErrorMessage, " wet process"];
        String.AppendString[ErrorMessage, " have not been profiled."];
        ERROR ParseFault[ErrorMessage, curPosition, curPosition];
      END;
      
      -- Check for plumbing.
      n ← 0;
      FOR q: [1..NumberOfGasController] IN [1..NumberOfGasController] DO
        IF String.EquivalentString[gpt[id.IdleTube][q], "*"] THEN n←n+1 
        ELSE EXIT;
      ENDLOOP;
      IF n = NumberOfGasController THEN 
      BEGIN
        ErrorMessage.length ← 0;
        String.AppendString[ErrorMessage, "Tube number "];
        String.AppendNumber[ErrorMessage, id.IdleTube, 10];
        String.AppendString[ErrorMessage, " has not been plumbed."];
        ERROR  ParseFault[ErrorMessage, curPosition, curPosition];
      END; -- End of check for plumbing.
      
      InitTube[id.IdleTube] ← TRUE;
      
      MissingValueCheck[ph];
  
      -- The following codes take care of the temperature.
      curPosition ← ph.currentChar;
      [temp,tNumberOfDigits] ←BruceDefs.ParseTemp
      [ph,MinProfiledTemp[id.IdleTube][R],MaxProfiledTemp[id.IdleTube][R]];
      FOR p:BruceDefs.TempColIndex IN BruceDefs.TempColIndex DO
        id.IdleTempTable[id.IdleTube][p] ← temp 
      ENDLOOP;
      FOR u: ProfiledTempIndex IN ProfiledTempIndex DO
        IF temp  # ptt[id.IdleTube][R][u] THEN m←u ELSE EXIT;
      ENDLOOP;
      IF m = NumberOfProfiledTemp  THEN BEGIN
        exD.DisplayBothExceptionLines
        ["Temp not profiled. Program will proceed with temp obtained by interpolation.", 0, "", 0, FALSE];
        -- There are only two lines in the bottom message window.
        BruceDefs.WarningCarat[curPosition, curPosition + tNumberOfDigits];
        IF  ~inD.Confirm[2] THEN ERROR ParseFault
        ["Change temperature value and compile again.", curPosition, 
        curPosition + tNumberOfDigits];
      END;
      FOR zo: BruceDefs.ZoneIndex IN BruceDefs.ZoneIndex DO
        table1: DescribeTable ← DESCRIPTOR[ptt[id.IdleTube][R]];
        table2: DescribeTable ← DESCRIPTOR[ptct[id.IdleTube][R][zo]];
        recordtable: Record ← [table1, table2];
        TempCorrectArray[zo] ← Interpolation2 [temp, recordtable]; 
      ENDLOOP;
      id.IdleTempTable[id.IdleTube][1]  ← TempCorrectArray[1];
      id.IdleTempTable[id.IdleTube][2]  ← TempCorrectArray[2];
      id.IdleTempTable[id.IdleTube][3]  ← TempCorrectArray[3];
     
      -- The following codes take care of the gas.
      IF id.IdleTube IN [1..4] THEN
      id.IdleFunction1Table[id.IdleTube][3]←1;
      --function switch # 6 will be 1 for interval 0 for tube 1 to 4.

DO
        ParseWord[ph,gasName];
        IF gasName[0] IN ['0..'9] OR gasName[0] ='. OR gasName[0] ='@  THEN  
        ERROR ParseFault["Please specify the name of gas.",
        ph.currentChar-gasName.length -1, 
        ph.currentChar-gasName.length -1];
        FOR i:GasControllerIndex IN GasControllerIndex DO
          IF String.EquivalentString[gasName,gpt[id.IdleTube][i]] THEN 
          BEGIN
            k ← i;
            EXIT;
          END;
        REPEAT
        FINISHED => ERROR 
          ParseFault["The system was not hooked up with the type of gas you specified.", 
          ph.currentChar-gasName.length, ph.currentChar];
        ENDLOOP;
        ParseToNonWhite[ph];
        CheckChar[ph,'@];
        MissingValueCheck[ph];
        [gas, gNumberOfDigits] ← BruceDefs.ParseGas[ph, 
        MinGasFlowConversion,MaxGasFlowConversion];
        IF String.EquivalentString[gasName,GasOne] THEN GasOneFlow ← gas;
        IF String.EquivalentString[gasName,GasTwo] THEN GasTwoFlow ← gas;
        IF String.EquivalentString[gasName,"tca"] THEN tcaON ← TRUE;
        IF String.EquivalentString[gasName,"O2"] THEN O2ON ← TRUE;
        IF String.EquivalentString[gasName,"POCl3"] THEN POCl3ON ← TRUE;
        IF String.EquivalentString[gasName,"N2"] AND id.IdleTube IN [1..4] 
        THEN BEGIN
          id.IdleFunction1Table[id.IdleTube][4] ← 1;
          k ← 4;
        END;
        IF String.EquivalentString[gasName,"N2"] AND id.IdleTube IN [5..6] 
        THEN BEGIN
          id.IdleFunction1Table[id.IdleTube][4] ← 1;
          k ← 3;
        END;
        id.IdleGasTable[id.IdleTube][k]←
        GetGasFlow[ph, gasName, gas, gNumberOfDigits] ;
        id.IdleFunction1Table[id.IdleTube][9-k] ← 1;
        IF ~ParseToLeadingCharOnLine[ph] THEN EXIT;
        IF ~ParseCharIfCharIs[ph,'+] THEN EXIT;
      ENDLOOP; -- End of the gas DO loop.
    
      IF GasOneFlow # 0 AND GasTwoFlow # 0 THEN BEGIN
      IF (LONG[GasOneFlow] *LONG[100])/LONG[GasTwoFlow] >= 
      LONG[GasOneToGasTwoSafetyRatio] THEN BEGIN
        GasOneFlow ← 0;
        GasTwoFlow ← 0;
        ErrorMessage.length ← 0;
        String.AppendString[ErrorMessage, " The ratio of "];
        String.AppendString[ErrorMessage, GasOne]; 
        String.AppendString[ErrorMessage, " to "];
        String.AppendString[ErrorMessage, GasTwo]; 
        String.AppendString[ErrorMessage, " >= "];
        AppendScaleNumber[GasOneToGasTwoSafetyRatio, 
        GasScaledFactor, ErrorMessage];            
        String.AppendString[ErrorMessage, 
        ". Explosion may occur. Process rejected "];
        ParseFault[ErrorMessage, ph.currentChar, ph.currentChar];
      END;
    END;
    
    GasOneFlow ← 0;
    GasTwoFlow ← 0;

    IF tcaON = TRUE AND O2ON = FALSE THEN BEGIN 
    tcaON ← FALSE;
    ParseFault
    ["You have your tca on without the O2. Please turn on the O2",  
    ph.currentChar, ph.currentChar];
    END;
    
    tcaON ← FALSE;
    O2ON ← FALSE;

    IF POCl3ON = TRUE AND O2ON = FALSE THEN BEGIN 
    POCl3ON ← FALSE;
    ParseFault
    ["You have your POCl3 on without the O2. Please turn on the O2",  
    ph.currentChar, ph.currentChar];
    END;

    POCl3ON ← FALSE;
    O2ON ← FALSE;


  ENDLOOP;
  END ELSE 
  ParseFault["IdleConditions field is missing.", ph.currentChar-1,    
  ph.currentChar-1];
END; --Of IdleConditions

ParseFurnaceCoefficients: FieldParseProc=
BEGIN
b,c,d:CHARACTER;
tubeErrMsg:  ARRAY[1..8] OF STRING ←[
  "Tube 1 Control Coefficients missing",
  "Tube 2 Control Coefficients missing",
  "Tube 3 Control Coefficients missing",
  "Tube 4 Control Coefficients missing",
  "Tube 5 Control Coefficients missing",
  "Tube 6 Control Coefficients missing",
  "Tube 7 Control Coefficients missing",
  "Tube 8 Control Coefficients missing"];	

rp:BruceDefs.RecipePtr ← ph.userData;
FOR k: BruceDefs.TubeNumber IN BruceDefs.TubeNumber DO
  IF ~ParseToLeadingChar[ph] THEN
  ERROR ParseFault["Data for Coefficient Missing", ph.currentChar-1,
  ph.currentChar];
  d ← ParseChar[ph];
  IF k # d - '0 THEN ERROR ParseFault[tubeErrMsg[k] ,ph.currentChar - 1, ph.currentChar];
  FOR i:BruceDefs.FactorIndex IN BruceDefs.FactorIndex DO
    IF ~ParseToLeadingChar[ph] THEN
    ERROR ParseFault["Data for Coefficient Missing", ph.currentChar-1,
    ph.currentChar];
    b ← ParseChar[ph];
    IF i # b - '@ THEN
    SELECT i FROM
      = 1 => ERROR ParseFault["Row A of Coefficient Table is missing",
                               ph.currentChar - 2, ph.currentChar];
      = 2 => ERROR ParseFault["Row B of Coefficient Table is missing",
                               ph.currentChar - 2, ph.currentChar];
      = 3 => ERROR ParseFault["Row C of Coefficient Table is missing",
                               ph.currentChar - 2, ph.currentChar]; 
      = 4 => ERROR ParseFault["Row D of Coefficient Table is missing",
                               ph.currentChar - 2, ph.currentChar]; 
      ENDCASE; 
    FOR j:BruceDefs.ZoneIndex IN BruceDefs.ZoneIndex DO
      IF ~ParseToLeadingChar[ph] THEN
      ERROR ParseFault
      ["Part of the Data for the Coefficient Table is missing",
      ph.currentChar-1, ph.currentChar];
      c ← ParseChar[ph];
      IF j # c - '0 THEN
      SELECT j FROM
        = 1 => ERROR ParseFault["Zone 1 of Coefficient Table is missing",
                               ph.currentChar - 1, ph.currentChar];
        = 2 => ERROR ParseFault["Zone 2 of Coefficient Table is missing",
                               ph.currentChar - 1, ph.currentChar];
        = 3 => ERROR ParseFault["Zone 3 of Coefficient Table is missing",
                               ph.currentChar - 1, ph.currentChar]; 
        ENDCASE; 
      fct[k].controlTable.factorArray[i].calibration[j] ← ParseInteger[ph];
      fct[k].controlTable.factorArray[i].latency[j] ← ParseDecimal[ph];
      fct[k].controlTable.factorArray[i].outerLoopRate[j] ← ParseDecimal[ph];
      fct[k].controlTable.factorArray[i].rate[j] ← ParseDecimal[ph];
      fct[k].controlTable.factorArray[i].reset[j] ← ParseDecimal[ph];
      fct[k].controlTable.factorArray[i].proportionalBand[j] ← ParseDecimal[ph];
      IF j=FIRST[BruceDefs.ZoneIndex] THEN 
        fct[k].tempRangeTable[i] ← ParseScaledNumber[ph, TempScaledFactor,
                                   0, 15000].n;
      ENDLOOP;
    ENDLOOP;
  IF ~ParseToLeadingChar[ph] THEN
  ERROR ParseFault["WierdIndex is missing from the Coefficient Table", 
  ph.currentChar-5, ph.currentChar];
  FOR i:BruceDefs.WierdIndex IN BruceDefs.WierdIndex DO
    fct[k].controlTable.wierdStuff[i] ← ParseDecimal[ph];
    ENDLOOP;
  ENDLOOP;
ParseToEndOfField[ph];
END; --Of ParseFurnaceCoefficients

SpecToBinary: PUBLIC PROCEDURE [data, spec: vmD.VirtualMessagePtr,
  brp:BruceDefs.RecipePtr] RETURNS[tube:BruceDefs.TubeNumber]=
BEGIN
  cd:CompileData;
  ph:ParseHandle ← @pd;
  pd:ParseData;
  fields: ARRAY [0 .. 2) OF FieldRec ←
    [["Tube"L, ParseTube], ["Recipe"L, ParseRecipe]];
  pd.breakSet ← ":.@""+/"L;
  pd.blankSet ← " 	
"L;
  ph.message ← spec;
  pd.userData ← @cd;
  cd.brp ← brp;
  cd.parsedTube ← FALSE;
  ParseMessage[ph, DESCRIPTOR[fields]];
  RETURN[cd.tube];
END; -- of SpecToBinary


FillArray: PROCEDURE[ph:ParseHandle, dt:DescribeTable, ScaledFactor:CARDINAL, UpperBound:CARDINAL]=
BEGIN
  i: CARDINAL;
  k: CARDINAL ← 0;
  ExtraNumber: STRING ← [10];
  ErrorMessage: STRING ← [100];
  FOR i IN [1..LENGTH[dt]] DO
    IF ~ParseToLeadingCharOnLine[ph] THEN BEGIN
      ErrorMessage.length ← 0;
      String.AppendString[ErrorMessage, "There should be "];
      String.AppendNumber[ErrorMessage, LENGTH[dt] - i +1, 10];
      String.AppendString[ErrorMessage, " more entries on this line."];
      ERROR  ParseFault[ErrorMessage, ph.currentChar, ph.currentChar];
    END;
    MissingValueCheck[ph];
    dt[i] ← ParseScaledNumber[ph, ScaledFactor, 0, 
    UpperBound].n;
  ENDLOOP;
  IF ParseToLeadingCharOnLine[ph] THEN  
  BEGIN
    ParseWord[ph, ExtraNumber];
    k ← ExtraNumber.length;
    IF ParseCharIfCharIs[ph, '.] THEN BEGIN
      ParseWord[ph, ExtraNumber];
      k ← k +1 +ExtraNumber.length;
    END;
    ErrorMessage.length ← 0;
    String.AppendString[ErrorMessage, "> "];     
    String.AppendNumber[ErrorMessage, LENGTH[dt], 10];
    String.AppendString[ErrorMessage, 
    " entries. Change in the BruceCompiler.mesa is needed."];
    ERROR  ParseFault[ErrorMessage, ph.currentChar-k, ph.currentChar];
  END;
END; -- of FillArray



CheckWord: PROCEDURE[ph:ParseHandle, Word: STRING] =
BEGIN
  st: STRING ← [40];
  ErrorMessage: STRING ← [100];
  IF ParseToLeadingChar[ph] THEN BEGIN
    ParseWord[ph,st];
    IF ~String.EquivalentString[st, Word] THEN BEGIN
      ErrorMessage.length ← 0;
      String.AppendString[ErrorMessage, "The word should be "];
      String.AppendString[ErrorMessage, Word];
      String.AppendString[ErrorMessage, ". No space in between."];
      ERROR  ParseFault[ErrorMessage, ph.currentChar- st.length , 
      ph.currentChar];
    END; 
  END ELSE BEGIN
    ErrorMessage.length ← 0;
    String.AppendString[ErrorMessage, "The word, "];
    String.AppendString[ErrorMessage, Word];
    String.AppendString[ErrorMessage, 
    ", and the corresponding entries are missing."];
    ERROR  ParseFault[ErrorMessage, ph.currentChar-1, ph.currentChar-1];
  END;
END; -- of CheckWord



CheckAndInc: PROCEDURE[ph:ParseHandle, val:CARDINAL, max:CARDINAL]
RETURNS[newval:CARDINAL]=
BEGIN
  IF val=max THEN ERROR ParseFault["Table overflow", ph.currentChar,
    ph.currentChar];
  newval ← val+1;
END; -- of CheckAndInc



Interpolation2: PROCEDURE[ value:CARDINAL, recordtable: Record] RETURNS[returnValue:CARDINAL] =
BEGIN
  a, b, c, d, e: LONG CARDINAL;
  FOR i: CARDINAL IN [2..LENGTH[recordtable.a]] DO
    IF  value <= recordtable.a[i] THEN BEGIN
      a ← recordtable.a[i - 1];
      b ← recordtable.a[i];
      c ← recordtable.b[i - 1];
      d ← recordtable.b[i];
      IF d >= c THEN
      e ←( ( d - c ) * ( value - a ) * 10 / ( b - a ) + 5 ) / 10  + c ELSE
      e ← c -( ( c - d ) * ( value - a ) * 10 / ( b - a ) + 5 ) / 10  ;
      returnValue ← InlineDefs.LowHalf [e];
      EXIT;
    END; 
  ENDLOOP;
END; 



GetReaction: PROCEDURE[ph: ParseHandle] RETURNS[R: Reaction] =
BEGIN
  ParseToNonWhite[ph];
  ParseWord[ph, s];
  DO
    IF s[s.length-1] IN ['0..'9]  THEN BEGIN
      s.length ← s.length -1;
      ph.currentChar ← ph.currentChar - 1;
    END ELSE EXIT; 
  ENDLOOP;
  IF String.EquivalentString[s,sDry] OR String.EquivalentString[s,"d"] 
  THEN R ←Dry ELSE
  IF String.EquivalentString[s,sWet] OR String.EquivalentString[s,"w"] 
  THEN R ←Wet ELSE
  IF s[0] IN ['0..'9] THEN ERROR
  ParseFault
  ["Specify your process by typing in the word Dry(orD) or Wet(orW) at the blinking carat",
  ph.currentChar - s.length -1 , ph.currentChar - s.length -1] 
  ELSE ERROR 
  ParseFault
  ["Unknown word. Type  in the word Dry(orD) or Wet(orW) at the blinking carat",
  ph.currentChar - s.length , ph.currentChar]; 
END; -- GetReaction



TempCorrection: PROCEDURE [ph:ParseHandle, rp: BruceDefs.RecipePtr, t:CARDINAL, tn:BruceDefs.TubeNumber, R:Reaction, temp:CARDINAL, tNumberOfDigits:CARDINAL]
RETURNS[a, b, c:  CARDINAL] =
-- This procedure corrects the temperatures to be used in columns 1, 2 and 
-- 3 of the temperature setpoint tables due to the different thermal 
-- behavior of the different tubes,the different zones within each tube , 
-- the different temperatures and the different reactions (exothermic or 
-- endothermic-) used when one is using the normal control mode.Note that 
-- all the temperatures are multiplied by a factor of 10 in order to 
-- communicate with the DDC microcontroller of the Bruce furnaces.
BEGIN
  m: CARDINAL;
  k: CARDINAL ←0;
  TempCorrectArray:ARRAY[1..3] OF CARDINAL;
  FOR i: ProfiledTempIndex IN ProfiledTempIndex DO
    IF temp  # ptt[tn][R][i] THEN m←i ELSE EXIT;
  ENDLOOP;
  FOR i: CARDINAL IN [1..t-1] DO
    IF temp  = rp.temp[i][6] THEN BEGIN 
      k←i; 
      EXIT; 
    END; 
  ENDLOOP;
  IF k = 0  AND m = NumberOfProfiledTemp  THEN BEGIN
    exD.DisplayBothExceptionLines["Temp not profiled. Program will proceed with temp obtained by interpolation.", 0, "", 0, FALSE];
    -- There are only two lines in the bottom message window.
    BruceDefs.WarningCarat
    [ph.currentChar-tNumberOfDigits-2, ph.currentChar-2];
    IF  ~inD.Confirm[2] THEN ERROR ParseFault[" Change temperature value and compile again.", ph.currentChar-tNumberOfDigits-2, ph.currentChar-2];
  END;
  FOR zo: BruceDefs.ZoneIndex IN BruceDefs.ZoneIndex DO
    table1: DescribeTable ← DESCRIPTOR[ptt[tn][R]];
    table2: DescribeTable ← DESCRIPTOR[ptct[tn][R][zo]];
    recordtable: Record ← [table1, table2];
    TempCorrectArray[zo] ← Interpolation2 [temp, recordtable]; 
  ENDLOOP;
  a ← TempCorrectArray[1];
  b ← TempCorrectArray[2];
  c ← TempCorrectArray[3];
END; -- of TempCorrection



GetGasFlow: PROCEDURE [ ph:ParseHandle, gasName:STRING, gasRate: CARDINAL,
gNumberOfDigits: CARDINAL] RETURNS [gasFlow: CARDINAL] =
-- This procedure converts the gas rate in litres per minute (l/m) into 
-- percentages of the full flow.
BEGIN
  k:CARDINAL ← 0;
  table1: DescribeTable ← DESCRIPTOR[cgrt];
  table2: DescribeTable;
  recordtable: Record ;
  FOR i:GasTypeIndex IN GasTypeIndex DO
    IF String.EquivalentString[gasName,gtt[i]] THEN BEGIN
      l←i; 
      -- l is a global variable defined at the beginning of this program.
      EXIT;
    END;
  ENDLOOP;
  FOR i: CalibratedGasRateIndex IN CalibratedGasRateIndex DO
    IF gasRate  # cgrt[i] THEN k←i ELSE EXIT;
  ENDLOOP;
  IF k = NumberOfCalibratedGasRate  THEN BEGIN
    exD.DisplayBothExceptionLines
    ["Gas rate not profiled. Program will proceed by interpolation.", 
    0, "", 0, FALSE];
    -- There are only two lines in the bottom message window.
    BruceDefs.WarningCarat
    [ph.currentChar- gNumberOfDigits-4, ph.currentChar-4];
    IF  ~inD.Confirm[2] THEN 
    ERROR ParseFault[" Change the gas rate value and compile again.", 
    ph.currentChar- gNumberOfDigits-4, ph.currentChar-4];
  END;
  table2 ← DESCRIPTOR[cgrct[l]];
  recordtable ←  [table1, table2];
  gasFlow ← Interpolation2[gasRate, recordtable];
END; -- of GetGasFlow



BoatRateConversion: PROCEDURE [ ph:ParseHandle, boatRate: CARDINAL,
bNumberOfDigits: CARDINAL] RETURNS [boatRatePercent:CARDINAL] =
-- This procedure convert the boat speed in inches per minute into 
-- percentages of the full speed which is              inches per minute
BEGIN
  k: CARDINAL ←0;
  table1: DescribeTable ← DESCRIPTOR[cbrt];
  table2: DescribeTable ← DESCRIPTOR[cbrct];
  recordtable: Record ← [table1, table2];
  FOR i: CalibratedboatRateIndex IN CalibratedboatRateIndex DO
    IF boatRate  # cbrt[i] THEN k←i ELSE EXIT;
  ENDLOOP;
  IF k = NumberOfCalibratedboatRate  THEN BEGIN
    exD.DisplayBothExceptionLines
    ["Boat rate not profiled. Program will proceed by interpolation.", 
    0, "", 0, FALSE];
    -- There are only two lines in the bottom message window.
    BruceDefs.WarningCarat
    [ph.currentChar- bNumberOfDigits, ph.currentChar];
    IF  ~inD.Confirm[2] THEN 
    ERROR ParseFault[" Change the boat rate value and compile again.", 
    ph.currentChar- bNumberOfDigits, ph.currentChar];
  END;
  boatRatePercent ← Interpolation2[boatRate, recordtable];
END; -- of BoatRateConversion:



ParseTube: FieldParseProc=
BEGIN
  ErrorMessage: STRING ← [100];
  ch:CompileHandle ← ph.userData;
  IF ~ParseToLeadingCharOnLine[ph] THEN ERROR
  ParseFault["Tube number is missing.", ph.currentChar, ph.currentChar];
  ch.tube ← ParseDecimal[ph,1,8];
  IF InitTube[ch.tube] = FALSE THEN BEGIN
    ErrorMessage.length ← 0;
    String.AppendString[ErrorMessage, 
    "Go back to the constant table. Idle conditions for tube no. "];
    String.AppendNumber[ErrorMessage, ch.tube, 10];
    String.AppendString[ErrorMessage, " are missing."];
    ERROR ParseFault[ErrorMessage, ph.currentChar, ph.currentChar];
  END;
  ch.parsedTube ← TRUE;
  ParseToEndOfField[ph];
END; -- of ParseTube



ParseRecipe: FieldParseProc=
BEGIN
  ch:CompileHandle ← ph.userData;
  rp:BruceDefs.RecipePtr ← ch.brp;
  i:BruceDefs.IntervalIndex ← 0;
  t:CARDINAL ← 1;
  tt:CARDINAL ← 1;
  g:CARDINAL ← 1;
  prevCtl:CARDINAL;  
  prevTemp:CARDINAL;  
  prevReaction: Reaction;
  temp:CARDINAL;
  diffTemp: CARDINAL;
  diffTempLong : LONG CARDINAL;
  correctiveFactor: LONG CARDINAL ← 600;
  -- The correctiveFactor is 60 x 10. The number 60 came from the 
  -- sixty seconds of a minute. The factor 10 came from the fact 
  -- that we had to scale up the number so as to avoid   
  -- truncation when we do a division. 
  maxRampRate:CARDINAL ;
  time,hour,min,sec,m:CARDINAL;
  timeString1:STRING ← [100];
  timeString2:STRING ← [100];
  timeString3:STRING ← [100];
  ErrorMessage: STRING ← [100];
  KeyWord: STRING ← [30];
  boatSpeedSafetyString: STRING ← [100];
  gas: CARDINAL;
  newRow:BruceDefs.GasRow ← ALL[0];
  tempnewRow:BruceDefs.TempRow ← ALL[0];
  gasName:STRING ← [10];
  pushed:BOOLEAN ← FALSE;
  pulled:BOOLEAN ← FALSE;
  curPosition: vmD.CharIndex;

  IF ~ch.parsedTube THEN
  ERROR ParseFault["Tube number must be specified before recipe",0,0];
  
  --The following codes take care of the idle conditions.
  rp.temp[1] ← id.IdleTempTable[ch.tube];
  rp.gas[1]  ← id.IdleGasTable[ch.tube];
  rp.interval[0].controlAlg ← 0;
  rp.interval[0].tempAlarm ← 13;  -- disable temperature alarm
  prevCtl ← rp.interval[0].controlAlg;
  prevTemp ← id.IdleTempTable[ch.tube][4];
  prevReaction ← id.IdleReaction[ch.tube];
  rp.interval[0].gasAlarm ← 13;  -- disable gas alarm
  rp.interval[0].controlCoeff ← 0;  -- default the control coefficients
  rp.interval[0].abortGroup ← 0;  -- don't allow any aborts
  rp.interval[0].gasTempControl ← 11; -- set temp and gas control row
  rp.interval[0].function1  ← id.IdleFunction1Table[ch.tube];
  -- End of the idle conditions codes.
  
  WHILE ParseToLeadingChar[ph] DO
    curPosition ← ph.currentChar;
    i ← CheckAndInc[ph,i,30];
    [rp.interval[i].hours, rp.interval[i].minutes, rp.interval[i].seconds]
    ← ParseTime[ph];
    R ← GetReaction [ph];
    
    -- The following codes detect whether the temperatuers of the 
    -- dry or wet process of the tube have been profiled or not.
    IF MinProfiledTemp[ch.tube][Dry] = 0 AND MinProfiledTemp[ch.tube][Wet] 
    = 0 THEN
    BEGIN
      ErrorMessage.length ← 0;
      String.AppendString[ErrorMessage,"Temperatures for tube # "];
      String.AppendNumber[ErrorMessage, ch.tube, 10];
      String.AppendString
      [ErrorMessage, " dry and wet processes have not been profiled."];
      ERROR ParseFault[ErrorMessage, ph.currentChar, ph.currentChar];
    END;
    IF MinProfiledTemp[ch.tube][R] = 0 THEN
    BEGIN
      ErrorMessage.length ← 0;
      String.AppendString[ErrorMessage,"Tube # "];
      String.AppendNumber[ErrorMessage, ch.tube, 10];
      IF R = Dry THEN String.AppendString[ErrorMessage, " dry proc"]
      ELSE String.AppendString[ErrorMessage, " wet proc"];
      String.AppendString[ErrorMessage, " not profiled."];
      String.AppendString
      [ErrorMessage, "Can use the profiled temps from"];
      IF R = Dry THEN String.AppendString
      [ErrorMessage, " wet proc instead."]
      ELSE String.AppendString[ErrorMessage, " dry proc instead."];
      exD.DisplayBothExceptionLines[ErrorMessage, 0, "", 0, FALSE];
      BruceDefs.WarningCarat[curPosition - 1, curPosition];
      IF ~inD.Confirm[2] THEN
      ERROR ParseFault["Profile the process and fill in the temperature calibration table.", ph.currentChar, ph.currentChar];
      IF R = Dry THEN R ← Wet ELSE R ← Dry;
    END;-- of detection of profiled temperatures.
          
    DO
      ParseWord[ph, KeyWord];
      DO
        IF KeyWord.length = 0 THEN EXIT;
        IF KeyWord[KeyWord.length-1] IN ['0..'9]  THEN BEGIN
          KeyWord.length ← KeyWord.length -1;
          ph.currentChar ← ph.currentChar - 1;
        END ELSE EXIT; 
      ENDLOOP;
      IF KeyWord[0] IN ['0..'9] THEN BEGIN
        ph.currentChar ← ph.currentChar - KeyWord.length;
        KeyWord.length ← 0;
        EXIT;
      END ELSE KeyWord.length ← 0;
    ENDLOOP;

    MissingValueCheck[ph];
  
    [temp,tNumberOfDigits] ← BruceDefs.ParseTemp
    [ph,MinProfiledTemp[ch.tube][R],MaxProfiledTemp[ch.tube][R]];
      
    --The following codes take care of the temp ramp situation.
    IF temp # prevTemp THEN BEGIN
      timeString1.length ← 0;
      timeString2.length ← 0;
      timeString3.length ← 0;
      IF temp > prevTemp THEN BEGIN
        maxRampRate ← maxRampRateUp;
        diffTemp ← temp - prevTemp;
        String.AppendString[timeString1, "Up"];
        String.AppendString[timeString3, "Up"];
      END ELSE BEGIN
        maxRampRate ← maxRampRateDown;
        diffTemp ← prevTemp -temp;
        String.AppendString[timeString1, "Down"];
        String.AppendString[timeString3, "Down"];
      END;
      time ← rp.interval[i].hours*3660+rp.interval[i].minutes*60+rp.interval[i].seconds;
      diffTempLong ←(LONG[diffTemp]*correctiveFactor)/LONG[time];
      IF diffTempLong > maxRampRate*10  AND i > 1 THEN BEGIN
        -- the 10 in the maxRampRate*10 arised from the fact that the 
        -- diffTempLong has a multipication factor 10 through the 
        -- correctiveFactor
        time  ← ((diffTemp)*10 / maxRampRate );
        sec ← (time - (time/10)*10)*6;
        -- Because temperatures are scaled by a factor of 10, therfore 
        -- time here is in minute, scaled also by a factor of 10.
        min ← (diffTemp/maxRampRate) MOD 60;
        hour ← ((diffTemp/maxRampRate)/60);
        String.AppendString[timeString1, 
        "Ramp rate too high. Advise to change time to "];
        String.AppendNumber[timeString2, hour, 10];
        String.AppendString[timeString2, "hr "];
        String.AppendNumber[timeString2, min, 10];
        String.AppendString[timeString2, "min "];
        String.AppendNumber[timeString2, sec, 10];
        String.AppendString[timeString2, "sec."];
        String.AppendString[timeString1, timeString2];        
        exD.DisplayBothExceptionLines[timeString1,0,"",0,FALSE];
        BruceDefs.WarningCarat[curPosition - 1, curPosition];
        String.AppendString[timeString3, " time interval to "];        
        String.AppendString[timeString3, timeString2];        
        IF  ~inD.Confirm[2] THEN ERROR ParseFault[timeString3,  
        ph.currentChar -16, ph.currentChar-16];
      END;
    END;-- of temp ramp situation.
    
    --The following codes take care of the temperature.
    --IF ~(i>1 AND temp=prevTemp AND R=prevReaction AND prevCtl=8) THEN BEGIN
      --BEGIN
        --t ← CheckAndInc[ph,t,8];
        --FOR h:BruceDefs.TempColIndex IN BruceDefs.TempColIndex DO
          --rp.temp[t][h] ← temp;
        --ENDLOOP;
        --[rp.temp[t][1] ,rp.temp[t][2],rp.temp[t][3]]
        --← TempCorrection[ph,rp, t, ch.tube, R, temp, tNumberOfDigits];
      --provide temp corrections for column 1 to 3 
      --END;
    
     t←CheckAndInc[ph,t,8];
     FOR h:BruceDefs.TempColIndex IN BruceDefs.TempColIndex DO
       tempnewRow[h]←temp;
     ENDLOOP;
     [tempnewRow[1],tempnewRow[2],tempnewRow[3]]
     ← TempCorrection[ph,rp, t,ch.tube, R,temp, tNumberOfDigits];
    FOR m IN [1..t) DO
     FOR n: BruceDefs.TempColIndex IN BruceDefs.TempColIndex  DO
       IF rp.temp[m][n] # tempnewRow[n] THEN EXIT;
     REPEAT
       FINISHED => WriteTempRow ← FALSE;
     ENDLOOP;
      IF WriteTempRow = FALSE THEN EXIT;
    ENDLOOP;
    IF WriteTempRow= TRUE THEN BEGIN
    rp.temp[t] ← tempnewRow;
    tt ← t;
    END ELSE BEGIN
    tt ← m;
    t ← t-1;
    END;


    rp.interval[i].controlAlg ← IF temp=prevTemp THEN 8 ELSE 0;
    rp.interval[i].tempAlarm ← 13;  -- disable temperature alarm
    prevCtl ← rp.interval[i].controlAlg;
    prevTemp ← temp;
    -- End of temperature related codes.
  
    -- The following codes take care of the gas table and 
    -- the gas switches in the interval table.
    GasOneFlow ← 0;
    GasTwoFlow ← 0;
    tcaON ← FALSE;
    O2ON ← FALSE;
    POCl3ON ← FALSE;
    DO
      ParseWord[ph,gasName];
      IF gasName[0] IN ['0..'9] OR gasName[0] ='. OR gasName[0] ='@  
      THEN ERROR 
      ParseFault["Please specify the name of gas.",
      ph.currentChar-gasName.length -1, ph.currentChar-gasName.length -1];
      FOR p:GasControllerIndex IN GasControllerIndex DO
        IF String.EquivalentString[gasName,gpt[ch.tube][p]] THEN BEGIN 
          j←p; 
          -- j is a global variable defined at the beginning of 
          -- this program.
          EXIT;
        END;
      REPEAT
       FINISHED => ERROR 
       ParseFault
       ["The system was not hooked up with the type of gas you specified.", 
       ph.currentChar-gasName.length, ph.currentChar];
      ENDLOOP;
      FOR q:GasTypeIndex IN GasTypeIndex DO
        IF String.EquivalentString[gasName,gtt[q]] THEN EXIT;
      REPEAT
        FINISHED => ERROR 
        ParseFault
        ["The type of gas you specified has not been profiled at all.", 
        ph.currentChar-gasName.length, ph.currentChar];
      ENDLOOP;
      IF String.EquivalentString[gasName,"Ar"] THEN 
        rp.interval[i].function1[2] ← 0; 
      IF String.EquivalentString[gasName,"N2"] THEN 
        rp.interval[i].function1[2] ← 0; 
      ParseToNonWhite[ph];
      [] ← ParseCharIfCharIs[ph,'@];
      MissingValueCheck[ph];
  
      [gas, gNumberOfDigits] 
      ← BruceDefs.ParseGas[ph, MinGasFlowConversion,MaxGasFlowConversion];
      IF String.EquivalentString[gasName,GasOne] THEN GasOneFlow ← gas;
      IF String.EquivalentString[gasName,GasTwo] THEN GasTwoFlow ← gas;
      IF String.EquivalentString[gasName,"tca"] THEN tcaON ← TRUE;
      IF String.EquivalentString[gasName,"O2"] THEN O2ON ← TRUE;
      IF String.EquivalentString[gasName,"POCl3"] THEN POCl3ON ← TRUE;
      IF String.EquivalentString[gasName,"N2"] AND ch.tube IN [1..4] THEN     
      BEGIN
        rp.interval[i].function1[4] ← 1;
        j ← 4;
      END;
      IF String.EquivalentString[gasName,"N2"] AND ch.tube IN [5..6] THEN     
      BEGIN
        rp.interval[i].function1[4] ← 1;
        j ← 3;
      END;
      IF String.EquivalentString[gasName,"N2"] AND ch.tube IN [7..8] THEN     
      BEGIN
        rp.interval[i].function1[4] ← 1;
        j ← 1;
      END;
      newRow[j] ←
      GetGasFlow[ph, gasName, gas, gNumberOfDigits] ;
      rp.interval[i].function1[9-j] ← 1;
      -- The order of the switches in the table form and in the 
      -- DDC microcontroller is opposite. 123456789 in the function1 format 
      -- of the DDC microcontroller corresponds to 876543210 in the 
      -- table format seen on the Alto after you click the word compile.
      IF ~ParseToLeadingCharOnLine[ph] THEN EXIT;
      IF ~ParseCharIfCharIs[ph,'+] THEN EXIT;
    ENDLOOP;
    
    prevReaction ← R;

    IF GasOneFlow # 0 AND GasTwoFlow # 0 THEN BEGIN
      IF (LONG[GasOneFlow] *LONG[100])/LONG[GasTwoFlow] >= 
      LONG[GasOneToGasTwoSafetyRatio] THEN BEGIN
        GasOneFlow ← 0;
        GasTwoFlow ← 0;
        ErrorMessage.length ← 0;
        String.AppendString[ErrorMessage, " The ratio of "];
        String.AppendString[ErrorMessage, GasOne]; 
        String.AppendString[ErrorMessage, " to "];
        String.AppendString[ErrorMessage, GasTwo]; 
        String.AppendString[ErrorMessage, " >= "];
        AppendScaleNumber[GasOneToGasTwoSafetyRatio, 
        GasScaledFactor, ErrorMessage];            
        String.AppendString[ErrorMessage, 
        ". EXPLOSION may occur. Process rejected. "];
        ParseFault[ErrorMessage, ph.currentChar, ph.currentChar];
      END;
    END;
    
    GasTwoFlow ← 0;

    IF tcaON = TRUE AND O2ON = TRUE THEN rp.interval[i].function1[2] ← 1; 
    IF tcaON = TRUE AND O2ON = FALSE THEN BEGIN 
    ParseFault
    ["You have your tca on without the O2. Please turn on the O2",  
    ph.currentChar, ph.currentChar];
    END;

    --tcaON ← FALSE;

    IF POCl3ON = TRUE AND O2ON = FALSE THEN BEGIN 
    --POCl3ON ← FALSE;
    ParseFault
    ["You have your POCl3 on without the O2. Please turn on the O2",  
    ph.currentChar, ph.currentChar];
    END;

    --POCl3ON ← FALSE;
    --O2ON ← FALSE;

    IF ch.tube IN [1..4] THEN rp.interval[i].function1[3] ← 1;
       -- The above line of codes will cause the function switch # 6 in the
    --interval table to take on the value 1 if the tube specified in the
    -- recipe is either tube 1, 2, 3 or 4.

    -- End of taking care of the gas table and the gas switches 
    -- in the interval table.
    
    -- The following codes take care of the temp and gas set in 
    -- the interval table.
    g ← CheckAndInc[ph,g,8];
    FOR m  IN [1..g) DO 
      FOR n: BruceDefs.GasColIndex IN BruceDefs.GasColIndex DO 
        IF rp.gas[m][n]# newRow[n] THEN EXIT;
      REPEAT
        FINISHED => WriteGasRow ←FALSE;
      ENDLOOP;
      IF WriteGasRow = FALSE THEN EXIT;
    ENDLOOP;
    IF WriteGasRow=TRUE THEN BEGIN
      rp.gas[g] ← newRow;
      rp.interval[i].gasTempControl ← 10*tt +g; 
      -- set temp and gas control row
    END
    ELSE BEGIN
      rp.interval[i].gasTempControl ← 10*tt +m;
      -- set temp and gas control row
      g ← g -1;
    END;
    WriteGasRow ←TRUE;
    WriteTempRow ←TRUE;
    newRow ← ALL[0];
    tempnewRow ← ALL[0];
    -- End of temp and gas set.
   
    -- The following codes take care of the temp and gas alarm in 
    -- the interval table.
    rp.interval[i].gasAlarm ← 13;  -- disable gas alarm
    rp.interval[i].controlCoeff ← 0;  -- default the control coefficients
    rp.interval[i].abortGroup ← 0;  -- don't allow any aborts
    -- End of temp and gas alarm.

    -- The following codes take care of the boat conditions.
    IF ParseToLeadingCharOnLine[ph] THEN BEGIN
      w:STRING ← [10];
      boatRate:CARDINAL;
      ParseWord[ph,w];
      IF w[0] IN ['0..'9] OR w[0] ='. OR w[0] ='@  THEN ERROR 
      ParseFault
      ["Please specify the boat condition here by the word Push or Pull.",
      ph.currentChar-w.length -1, ph.currentChar-w.length -1];
      DO
        IF w[w.length-1] IN ['0..'9]  THEN BEGIN
          w.length ← w.length -1;
          ph.currentChar ← ph.currentChar - 1;
        END ELSE EXIT; 
      ENDLOOP;
      IF ~String.EquivalentString["Push"L,w] AND
      ~String.EquivalentString["Pull"L,w]  
      THEN ERROR ParseFault
      ["Wrong word - Please specify the boat condition here by the word Push or Pull.", ph.currentChar-w.length, ph.currentChar];
      IF String.EquivalentString["Push"L,w] THEN BEGIN
        IF pushed THEN ERROR ParseFault["Can't push more than once",
        ph.currentChar-4, ph.currentChar];
        pushed ← TRUE;
      END  ELSE BEGIN
        IF pulled THEN ERROR ParseFault["Can't pull more than once",
        ph.currentChar-4, ph.currentChar];
        IF pulled = FALSE AND pushed = FALSE THEN ERROR
        ParseFault["You have to PUSH boat in FIRST then pull it out.", 
        ph.currentChar-4,ph.currentChar];
        pulled ← TRUE;
      END;
      
      -- Get boat speed and check for boat safety.
      ParseToNonWhite[ph]; 
      [] ← ParseCharIfCharIs[ph, '@];
            
      MissingValueCheck[ph];
  
      [boatRate, bNumberOfDigits] 
      ← ParseScaledNumber[ph, boatScaledFactor, 
      boatSpeedLoBound, boatSpeedHiBound];
      IF boatRate > boatSpeedSafetyLimit THEN BEGIN
        String.AppendString[boatSpeedSafetyString,
        "Boat speed is too fast -- > "];
        AppendScaleNumber[boatSpeedSafetyLimit,boatScaledFactor,
        boatSpeedSafetyString]; 
        String.AppendString[boatSpeedSafetyString,
        " inches per min. May destroy your wafers."];
        exD.DisplayBothExceptionLines
        [boatSpeedSafetyString, 0, "", 0, FALSE];
        -- There are only two lines in the bottom message window.
        BruceDefs.WarningCarat
        [ph.currentChar -bNumberOfDigits, ph.currentChar];
        IF  ~inD.Confirm[2] THEN 
        ERROR ParseFault[" Change boat speed and compile again.",
        ph.currentChar -bNumberOfDigits, ph.currentChar];
        boatSpeedSafetyString.length ← 0;
      END; --of boat speed and safety check.
      
      -- Set interval table for boat in and out and fill boat table.
      IF String.EquivalentString["Push"L,w] THEN BEGIN
        rp.boat.in [1][1].speed 
        ← (BoatRateConversion[ ph, boatRate,bNumberOfDigits]+5)/10;
        rp.boat.in [2][1].speed 
        ← (BoatRateConversion[ ph, boatRate,bNumberOfDigits]+5)/10;
        rp.boat.in [1][1].distance ← 99;
        rp.boat.in [2][1].distance ← 99;
        rp.interval[i].function2[2] ← 1;
        rp.interval[i].function2[1] ← 0;
        rp.interval[i].function2[3] ← 0;
                      --This set function switch 10 to 1 when the boat is pushing in.
    END ELSE BEGIN
        rp.boat.out[1][1].speed 
        ← (BoatRateConversion[ ph, boatRate,bNumberOfDigits]+5)/10;
        rp.boat.out[2][1].speed 
        ← (BoatRateConversion[ ph, boatRate,bNumberOfDigits]+5)/10;
        rp.boat.out[1][1].distance ← 0;
        rp.interval[i].function2[2] ← 0;
        rp.interval[i].function2[1] ← 1;
        rp.interval[i].function2[3] ← 1;
     END;-- interval table for boat in and out and fill boat table.
      
      ParseToNonWhite[ph]; CheckChar[ph,'"];
      ParseToNonWhite[ph]; CheckChar[ph,'/];
      ParseWord[ph,w];
      IF ~String.EquivalentString["min"L,w] THEN ERROR
      ParseFault["Boat rates must given as ""/min",
      ph.currentChar-w.length, ph.currentChar];
      ParseToEndOfLine[ph];
    
    END ELSE BEGIN
      rp.interval[i].function2[2] ← 0;
      rp.interval[i].function2[1] ← 0;
    END;-- of the codes that take care of the boat conditions.
  
  ENDLOOP; 
  -- corresponds to the DO in the WHILE ParseToLeadingChar[ph] statement.
  
  -- Move Bruce Furnace Control Coefficients from this tube into recipe

  rp.control ← fct[ch.tube].controlTable;
  rp.tempRange ← fct[ch.tube].tempRangeTable;

  IF pulled = FALSE THEN BEGIN
    IF pushed = TRUE THEN ERROR 
    ParseFault["You have forgotten to pull the boat out.", 
    ph.currentChar,  ph.currentChar]
    ELSE ERROR 
    ParseFault
    ["You have forgotten to push the boat in AND pull the boat out.",  
    ph.currentChar, ph.currentChar];
  END;
  
END; -- of ParseRecipe



END.