-- 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 -- This program has 52,000 bytes of codes or 60,900 bytes of codes -- including comments. 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; 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 .. 6) OF FieldRec ← [["BoatRateCalibration"L, ParseBoatRateCalibration], ["Constants"L, ParseConstants], ["GasFlowCalibration"L, ParseGasFlowCalibration], ["GasPlumbing"L, ParseGasPlumbing], ["TemperatureCalibration"L, ParseTemperatureCalibration], ["IdleConditions"L, ParseIdleConditions]]; 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 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; 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. 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.