{ Copyright (c) 1985, 88 by Borland International, Inc. } unit MCPARSER; interface uses Crt, Dos, MCVars, MCUtil, MCDisply; function CellValue(Col, Row : Word) : Real; { Finds the Value of a particular cell } function Parse(S : String; var Att : Word) : Real; { Parses the string s - returns the Value of the evaluated string, and puts the attribute in Att: TXT = 0, CONSTANT = 1, FORMULA = 2, +4 = ERROR. } implementation const PLUS = 0; MINUS = 1; TIMES = 2; DIVIDE = 3; EXPO = 4; COLON = 5; OPAREN = 6; CPAREN = 7; NUM = 8; CELLT = 9; FUNC = 10; EOL = 11; BAD = 12; MAXFUNCNAMELEN = 5; type TokenRec = record State : Byte; case Byte of 0 : (Value : Real); 1 : (Row, Col : Word); 2 : (FuncName : String[MAXFUNCNAMELEN]); end; var Stack : array [1..PARSERSTACKSIZE] of TokenRec; CurToken : TokenRec; StackTop, TokenType : Word; MathError, TokenError, IsFormula : Boolean; Input : IString; function IsFunc(S : String) : Boolean; { Checks to see if the start of the Input string is a legal function. Returns TRUE if it is, FALSE otherwise. } var Len : Word; begin Len := Length(S); if Pos(S, Input) = 1 then begin CurToken.FuncName := Copy(Input, 1, Len); Delete(Input, 1, Len); IsFunc := True; end else IsFunc := False; end; { IsFunc } function NextToken : Word; { Gets the next Token from the Input stream } var NumString : String[80]; FormLen, Place, Len, NumLen, Check : Word; FirstChar : Char; Decimal : Boolean; begin if Input = '' then begin NextToken := EOL; Exit; end; while (Input <> '') and (Input[1] = ' ') do Delete(Input, 1, 1); if Input[1] in ['0'..'9', '.'] then begin NumString := ''; Len := 1; Decimal := False; while (Len <= Length(Input)) and ((Input[Len] in ['0'..'9']) or ((Input[Len] = '.') and (not Decimal))) do begin NumString := NumString + Input[Len]; if Input[1] = '.' then Decimal := True; Inc(Len); end; if (Len = 2) and (Input[1] = '.') then begin NextToken := BAD; Exit; end; if (Len <= Length(Input)) and (Input[Len] = 'E') then begin NumString := NumString + 'E'; Inc(Len); if Input[Len] in ['+', '-'] then begin NumString := NumString + Input[Len]; Inc(Len); end; NumLen := 1; while (Len <= Length(Input)) and (Input[Len] in ['0'..'9']) and (NumLen <= MAXEXPLEN) do begin NumString := NumString + Input[Len]; Inc(NumLen); Inc(Len); end; end; if NumString[1] = '.' then NumString := '0' + NumString; Val(NumString, CurToken.Value, Check); if Check <> 0 then MathError := True; NextToken := NUM; Delete(Input, 1, Length(NumString)); Exit; end else if Input[1] in LETTERS then begin if IsFunc('ABS') or IsFunc('ATAN') or IsFunc('COS') or IsFunc('EXP') or IsFunc('LN') or IsFunc('ROUND') or IsFunc('SIN') or IsFunc('SQRT') or IsFunc('SQR') or IsFunc('TRUNC') then begin NextToken := FUNC; Exit; end; if FormulaStart(Input, 1, CurToken.Col, CurToken.Row, FormLen) then begin Delete(Input, 1, FormLen); IsFormula := True; NextToken := CELLT; Exit; end else begin NextToken := BAD; Exit; end; end else begin case Input[1] of '+' : NextToken := PLUS; '-' : NextToken := MINUS; '*' : NextToken := TIMES; '/' : NextToken := DIVIDE; '^' : NextToken := EXPO; ':' : NextToken := COLON; '(' : NextToken := OPAREN; ')' : NextToken := CPAREN; else NextToken := BAD; end; Delete(Input, 1, 1); Exit; end; { case } end; { NextToken } procedure Push(Token : TokenRec); { Pushes a new Token onto the stack } begin if StackTop = PARSERSTACKSIZE then begin ErrorMsg(MSGSTACKERROR); TokenError := True; end else begin Inc(StackTop); Stack[StackTop] := Token; end; end; { Push } procedure Pop(var Token : TokenRec); { Pops the top Token off of the stack } begin Token := Stack[StackTop]; Dec(StackTop); end; { Pop } function GotoState(Production : Word) : Word; { Finds the new state based on the just-completed production and the top state. } var State : Word; begin State := Stack[StackTop].State; if (Production <= 3) then begin case State of 0 : GotoState := 1; 9 : GotoState := 19; 20 : GotoState := 28; end; { case } end else if Production <= 6 then begin case State of 0, 9, 20 : GotoState := 2; 12 : GotoState := 21; 13 : GotoState := 22; end; { case } end else if Production <= 8 then begin case State of 0, 9, 12, 13, 20 : GotoState := 3; 14 : GotoState := 23; 15 : GotoState := 24; 16 : GotoState := 25; end; { case } end else if Production <= 10 then begin case State of 0, 9, 12..16, 20 : GotoState := 4; end; { case } end else if Production <= 12 then begin case State of 0, 9, 12..16, 20 : GotoState := 6; 5 : GotoState := 17; end; { case } end else begin case State of 0, 5, 9, 12..16, 20 : GotoState := 8; end; { case } end; end; { GotoState } function CellValue; var CPtr : CellPtr; begin CPtr := Cell[Col, Row]; if (CPtr = nil) then CellValue := 0 else begin if (CPtr^.Error) or (CPtr^.Attrib = TXT) then MathError := True; if CPtr^.Attrib = FORMULA then CellValue := CPtr^.FValue else CellValue := CPtr^.Value; end; end; { CellValue } procedure Shift(State : Word); { Shifts a Token onto the stack } begin CurToken.State := State; Push(CurToken); TokenType := NextToken; end; { Shift } procedure Reduce(Reduction : Word); { Completes a reduction } var Token1, Token2 : TokenRec; Counter : Word; begin case Reduction of 1 : begin Pop(Token1); Pop(Token2); Pop(Token2); CurToken.Value := Token1.Value + Token2.Value; end; 2 : begin Pop(Token1); Pop(Token2); Pop(Token2); CurToken.Value := Token2.Value - Token1.Value; end; 4 : begin Pop(Token1); Pop(Token2); Pop(Token2); CurToken.Value := Token1.Value * Token2.Value; end; 5 : begin Pop(Token1); Pop(Token2); Pop(Token2); if Token1.Value = 0 then MathError := True else CurToken.Value := Token2.Value / Token1.Value; end; 7 : begin Pop(Token1); Pop(Token2); Pop(Token2); if Token2.Value <= 0 then MathError := True else if (Token1.Value * Ln(Token2.Value) < -EXPLIMIT) or (Token1.Value * Ln(Token2.Value) > EXPLIMIT) then MathError := True else CurToken.Value := Exp(Token1.Value * Ln(Token2.Value)); end; 9 : begin Pop(Token1); Pop(Token2); CurToken.Value := -Token1.Value; end; 11 : begin Pop(Token1); Pop(Token2); Pop(Token2); CurToken.Value := 0; if Token1.Row = Token2.Row then begin if Token1.Col < Token2.Col then TokenError := True else begin for Counter := Token2.Col to Token1.Col do CurToken.Value := CurToken.Value + CellValue(Counter, Token1.Row); end; end else if Token1.Col = Token2.Col then begin if Token1.Row < Token2.Row then TokenError := True else begin for Counter := Token2.Row to Token1.Row do CurToken.Value := CurToken.Value + CellValue(Token1.Col, Counter); end; end else TokenError := True; end; 13 : begin Pop(CurToken); CurToken.Value := CellValue(CurToken.Col, CurToken.Row); end; 14 : begin Pop(Token1); Pop(CurToken); Pop(Token1); end; 16 : begin Pop(Token1); Pop(CurToken); Pop(Token1); Pop(Token1); if Token1.FuncName = 'ABS' then CurToken.Value := Abs(CurToken.Value) else if Token1.FuncName = 'ATAN' then CurToken.Value := ArcTan(CurToken.Value) else if Token1.FuncName = 'COS' then CurToken.Value := Cos(CurToken.Value) else if Token1.FuncName = 'EXP' then begin if (CurToken.Value < -EXPLIMIT) or (CurToken.Value > EXPLIMIT) then MathError := True else CurToken.Value := Exp(CurToken.Value); end else if Token1.FuncName = 'LN' then begin if CurToken.Value <= 0 then MathError := True else CurToken.Value := Ln(CurToken.Value); end else if Token1.FuncName = 'ROUND' then begin if (CurToken.Value < -1E9) or (CurToken.Value > 1E9) then MathError := True else CurToken.Value := Round(CurToken.Value); end else if Token1.FuncName = 'SIN' then CurToken.Value := Sin(CurToken.Value) else if Token1.FuncName = 'SQRT' then begin if CurToken.Value < 0 then MathError := True else CurToken.Value := Sqrt(CurToken.Value); end else if Token1.FuncName = 'SQR' then begin if (CurToken.Value < -SQRLIMIT) or (CurToken.Value > SQRLIMIT) then MathError := True else CurToken.Value := Sqr(CurToken.Value); end else if Token1.FuncName = 'TRUNC' then begin if (CurToken.Value < -1E9) or (CurToken.Value > 1E9) then MathError := True else CurToken.Value := Trunc(CurToken.Value); end; end; 3, 6, 8, 10, 12, 15 : Pop(CurToken); end; { case } CurToken.State := GotoState(Reduction); Push(CurToken); end; { Reduce } function Parse; var FirstToken : TokenRec; Accepted : Boolean; Counter : Word; begin Accepted := False; TokenError := False; MathError := False; IsFormula := False; Input := UpperCase(S); StackTop := 0; FirstToken.State := 0; FirstToken.Value := 0; Push(FirstToken); TokenType := NextToken; repeat case Stack[StackTop].State of 0, 9, 12..16, 20 : begin if TokenType = NUM then Shift(10) else if TokenType = CELLT then Shift(7) else if TokenType = FUNC then Shift(11) else if TokenType = MINUS then Shift(5) else if TokenType = OPAREN then Shift(9) else TokenError := True; end; 1 : begin if TokenType = EOL then Accepted := True else if TokenType = PLUS then Shift(12) else if TokenType = MINUS then Shift(13) else TokenError := True; end; 2 : begin if TokenType = TIMES then Shift(14) else if TokenType = DIVIDE then Shift(15) else Reduce(3); end; 3 : Reduce(6); 4 : begin if TokenType = EXPO then Shift(16) else Reduce(8); end; 5 : begin if TokenType = NUM then Shift(10) else if TokenType = CELLT then Shift(7) else if TokenType = FUNC then Shift(11) else if TokenType = OPAREN then Shift(9) else TokenError := True; end; 6 : Reduce(10); 7 : begin if TokenType = COLON then Shift(18) else Reduce(13); end; 8 : Reduce(12); 10 : Reduce(15); 11 : begin if TokenType = OPAREN then Shift(20) else TokenError := True; end; 17 : Reduce(9); 18 : begin if TokenType = CELLT then Shift(26) else TokenError := True; end; 19 : begin if TokenType = PLUS then Shift(12) else if TokenType = MINUS then Shift(13) else if TokenType = CPAREN then Shift(27) else TokenError := True; end; 21 : begin if TokenType = TIMES then Shift(14) else if TokenType = DIVIDE then Shift(15) else Reduce(1); end; 22 : begin if TokenType = TIMES then Shift(14) else if TokenType = DIVIDE then Shift(15) else Reduce(2); end; 23 : Reduce(4); 24 : Reduce(5); 25 : Reduce(7); 26 : Reduce(11); 27 : Reduce(14); 28 : begin if TokenType = PLUS then Shift(12) else if TokenType = MINUS then Shift(13) else if TokenType = CPAREN then Shift(29) else TokenError := True; end; 29 : Reduce(16); end; { case } until Accepted or TokenError; if TokenError then begin Att := TXT; Parse := 0; Exit; end; if IsFormula then Att := FORMULA else Att := VALUE; if MathError then begin Inc(Att, 4); Parse := 0; Exit; end; Parse := Stack[StackTop].Value; end; { Parse } end.