home *** CD-ROM | disk | FTP | other *** search
/ Chip Hitware 6 B / CHIP_HITWARE6_B.iso / biuro / BaseCalculator / Sources / Calc.pas < prev    next >
Pascal/Delphi Source File  |  1996-11-06  |  18KB  |  648 lines

  1. unit Calc;
  2.  
  3. {************************************************************************
  4. *                                                                       *
  5. *                         Calculator State Machine                      *
  6. *                                                                       *
  7. ************************************************************************}
  8.  
  9. { Author:    John Zaitseff <J.Zaitseff@unsw.edu.au>
  10.   Date:      6th November, 1996.
  11.   Version:   1.2
  12.  
  13.   This file provides an implementation of a state machine for a base
  14.   integer calculator (ie, one that operates in decimal, hexadecimal,
  15.   octal or binary), in either 8, 16 or 32 bit size.  As well as keeping
  16.   the necessary values that the calculator needs, the calculator class
  17.   also keeps the string representation of the value.
  18.  
  19.   This program, including this file, is under the terms of the GNU
  20.   General Public License.
  21. }
  22.  
  23. interface
  24.  
  25. const
  26.   { The following constants are internal to TCalculator }
  27.   FCalc_StackSize = 3;      { Needs to be set to the highest precedence
  28.                               number }
  29.  
  30. type
  31.   TCalcMode   = (Decimal, Hexadecimal, Binary, Octal);
  32.   TCalcSize   = (Size8, Size16, Size32);
  33.   TCalcKey    = (kNeg, kNot,
  34.                  kMul, kDiv, kMod, kAnd,
  35.                  kAdd, kSub, kOr,  kXor, kEqv,
  36.                  kEquals);
  37.  
  38.   { The following types are internal to TCalculator }
  39.   TCalc_State = (csFirstKey, csNextKey, csError);
  40.   TCalc_Stack = record
  41.                   FValue  : longint;
  42.                   FOp     : TCalcKey;
  43.                   FOpUsed : boolean
  44.                 end;
  45.  
  46.   { The actual calculator class }
  47.   TCalculator = class
  48.                   private
  49.                     FMode        : TCalcMode;
  50.                     FSigned      : boolean;
  51.                     FSize        : TCalcSize;
  52.  
  53.                     FChanged     : boolean;
  54.  
  55.                     FString      : string;
  56.                     FStrOK       : boolean;
  57.  
  58.                     FStack       : array [1..FCalc_StackSize] of TCalc_Stack;
  59.                     FStackPtr    : integer;
  60.  
  61.                     FMemory      : longint;
  62.  
  63.                     FEntryState  : TCalc_State;
  64.  
  65.                     procedure SetCalcMode (Mode : TCalcMode);
  66.                     procedure SetCalcSigned (Signed : boolean);
  67.                     procedure SetCalcSize (Size : TCalcSize);
  68.  
  69.                   public
  70.                     constructor Create;
  71.  
  72.                     property Mode : TCalcMode read FMode write SetCalcMode;
  73.                     property Signed : boolean read FSigned write SetCalcSigned;
  74.                     property Size : TCalcSize read FSize write SetCalcSize;
  75.  
  76.                     function CurrentValue : longint;
  77.                     function CurrentString : string;
  78.                     function Changed : boolean;
  79.                     function InError : boolean;
  80.                     function MemoryValue : longint;
  81.                     function MemoryOccupied : boolean;
  82.  
  83.                     { Actual calculator functions }
  84.                     procedure ClearAll;
  85.                     procedure ClearOperations;    { Clear key }
  86.                     procedure ClearMemory;
  87.  
  88.                     function AppendDigit (Digit : integer) : boolean;
  89.                     function Backspace : boolean;
  90.  
  91.                     procedure StoreCurrentInMem;
  92.                     procedure RetrieveMemory;
  93.                     function AddToMemoryKey : boolean;
  94.  
  95.                     function HandleKey (Key : TCalcKey) : boolean;
  96.                 end;
  97.  
  98. { Convert a value to a string }
  99. function ValToStr (Value : longint; Mode : TCalcMode;
  100.                    Signed : boolean; Size : TCalcSize) : string;
  101.  
  102. implementation
  103.  
  104. { Round a value to 8, 16 or 32 bits, to the appropriate sign }
  105. function RoundVal (Value : longint; Size : TCalcSize; Signed : boolean) : longint;
  106.  
  107. begin
  108.   case Size of
  109.     Size8  : begin
  110.                Result := Value and $000000FF;
  111.                if Signed and (Result > $7F) then
  112.                  Result :=  Result - $00000100
  113.              end;
  114.     Size16 : begin
  115.                Result := Value and $0000FFFF;
  116.                if Signed and (Result > $7FFF) then
  117.                  Result :=  Result - $00010000
  118.              end;
  119.     Size32 : Result := Value
  120.   end
  121. end;
  122.  
  123. { Convert a value to its representation.  Only decimal numbers are to
  124.   show a sign. }
  125. function ValToStr (Value : longint; Mode : TCalcMode;
  126.                    Signed : boolean; Size : TCalcSize) : string;
  127.  
  128. var
  129.   Mult : integer;
  130.   Neg  : boolean;
  131.   I    : integer;
  132.   R    : real;
  133.  
  134. const
  135.   Table : array [0..15] of char = '0123456789ABCDEF';
  136.  
  137. begin
  138.   Result := '';
  139.   Neg := False;
  140.  
  141.   case Mode of
  142.     Decimal     : Mult := 10;
  143.     Hexadecimal : Mult := 16;
  144.     Binary      : Mult := 2;
  145.     Octal       : Mult := 8
  146.   end;
  147.  
  148.   { Round the value, just in case }
  149.   Value := RoundVal(Value, Size, Signed);
  150.  
  151.   { Display negative numbers as unsigned, except for signed decimals }
  152.   if Value < 0 then
  153.     begin
  154.       if (Mode = Decimal) and Signed then
  155.         begin
  156.           Value := -Value;  { This will still be negative if $80000000 }
  157.           Neg := True
  158.         end
  159.       else
  160.         Value := RoundVal(Value, Size, False)
  161.     end;
  162.  
  163.   { If bit 31 is set, Value is less than 0 }
  164.   if Value < 0 then
  165.     begin
  166.       R := Value + 4294967296.0;
  167.       I := Round(Frac(R / Mult) * Mult);
  168.       Value := Trunc(R / Mult);
  169.       Result := Table[I]
  170.     end;
  171.  
  172.   repeat
  173.     I := Value mod Mult;
  174.     Value := Value div Mult;
  175.     Result := Table[I] + Result
  176.   until Value = 0;
  177.  
  178.   if Neg then
  179.     Result := '-' + Result
  180. end;
  181.  
  182.  
  183. { Create the calculator object }
  184. constructor TCalculator.Create;
  185.  
  186. begin
  187.   inherited Create;
  188.   ClearAll
  189. end;
  190.  
  191. { Clear the calculator to its startup values }
  192. procedure TCalculator.ClearAll;
  193.  
  194. begin
  195.   { Set the default values }
  196.   FMode := Decimal;
  197.   FSigned := True;
  198.   FSize := Size32;
  199.  
  200.   ClearOperations;
  201.   ClearMemory
  202. end;
  203.  
  204. { Clear the calculator operations }
  205. procedure TCalculator.ClearOperations;
  206.  
  207. var
  208.   I : integer;
  209.  
  210. begin
  211.   FStackPtr := 1;
  212.   for I := 1 to FCalc_StackSize do
  213.     with FStack[I] do
  214.       begin
  215.         FValue := 0;
  216.         FOpUsed := False
  217.       end;
  218.   FString := '0';
  219.   FStrOK := True;
  220.   FEntryState := csFirstKey;
  221.   FChanged := False
  222. end;
  223.  
  224. { Clear the calculator's memory }
  225. procedure TCalculator.ClearMemory;
  226.  
  227. begin
  228.   FMemory := 0
  229. end;
  230.  
  231. { Set the calculator mode (decimal, hexdecimal, binary, octal).  This
  232.   affects the internal state machine FEntryState.  This procedure must
  233.   NOT be called if InError returns True. }
  234. procedure TCalculator.SetCalcMode (Mode : TCalcMode);
  235.  
  236. begin
  237.   if FEntryState <> csError then
  238.     begin
  239.       FEntryState := csFirstKey;
  240.       FMode := Mode;
  241.       FStrOK := False;
  242.       FChanged := True;
  243.       { The representation, FString, will be updated in CurrentString }
  244.     end
  245. end;
  246.  
  247. { Set signed or unsigned operation.  This changes FEntryState. This
  248.   procedure must NOT be called if InError returns True. }
  249. procedure TCalculator.SetCalcSigned (Signed : boolean);
  250.  
  251. var
  252.   I : integer;
  253.  
  254. begin
  255.   if FEntryState <> csError then
  256.     begin
  257.       FEntryState := csFirstKey;
  258.       FSigned := Signed;
  259.       FStrOK := False;
  260.       FChanged := True;
  261.  
  262.       for I := 1 to FCalc_StackSize do
  263.         with FStack[I] do
  264.           FValue := RoundVal(FValue, FSize, FSigned);
  265.       FMemory := RoundVal(FMemory, FSize, FSigned);
  266.       { FString will be updated in CurrentString }
  267.     end
  268. end;
  269.  
  270. { Set the size of the calculator operands.  Note that this permanently
  271.   alters the contents of registers/memory, ie, bits are permanently
  272.   lost in moving from a larger to smaller size.  This also changes
  273.   TEntryState.  This procedure must NOT be called if InError returns
  274.   True. }
  275. procedure TCalculator.SetCalcSize (Size : TCalcSize);
  276.  
  277. var
  278.   I : integer;
  279.  
  280. begin
  281.   if FEntryState <> csError then
  282.     begin
  283.       { Make sure FValue is of the correct sign (using old FSize) }
  284.       with FStack[FStackPtr] do
  285.         FValue := RoundVal(FValue, FSize, FSigned);
  286.  
  287.       FEntryState := csFirstKey;
  288.       FSize := Size;
  289.       FStrOK := False;
  290.       FChanged := True;
  291.  
  292.       for I := 1 to FCalc_StackSize do
  293.         with FStack[I] do
  294.           FValue := RoundVal(FValue, FSize, FSigned);
  295.       FMemory := RoundVal(FMemory, FSize, FSigned);
  296.       { FString will be updated in CurrentString }
  297.     end
  298. end;
  299.  
  300. { Return the calculator's current value.  If InError returns True,
  301.   this function returns a meaningless result. }
  302. function TCalculator.CurrentValue : longint;
  303.  
  304. begin
  305.   Result := FStack[FStackPtr].FValue
  306. end;
  307.  
  308. { Return the current value as a string.  If InError returns True,
  309.   this function returns a meaningless result. }
  310. function TCalculator.CurrentString : string;
  311.  
  312. begin
  313.   if not FStrOK then
  314.     begin
  315.       FStrOK := True;
  316.       FString := ValToStr(FStack[FStackPtr].FValue, FMode, FSigned, FSize)
  317.     end;
  318.   FChanged := False;
  319.   Result := FString
  320. end;
  321.  
  322. { Return whether the calculator has been changed since the last
  323.   display operation. }
  324. function TCalculator.Changed : boolean;
  325.  
  326. begin
  327.   Result := FChanged
  328. end;
  329.  
  330. { Return True if the calculator is in an error state and needs to
  331.   be cleared by calling ClearOperation. }
  332. function TCalculator.InError : boolean;
  333.  
  334. begin
  335.   Result := (FEntryState = csError)
  336. end;
  337.  
  338. { Return the current memory value }
  339. function TCalculator.MemoryValue : longint;
  340.  
  341. begin
  342.   Result := FMemory
  343. end;
  344.  
  345. { Return True if memory is occupied }
  346. function TCalculator.MemoryOccupied : boolean;
  347.  
  348. begin
  349.   Result := (FMemory <> 0)
  350. end;
  351.  
  352. { Handle a digit key '0' to 'F' (passed as an integer 0-15).  This
  353.   affects the current value.  The state machine used is incremented
  354.   from csFirstKey to csNextKey on receipt of the first digit key.
  355.   True is returned if the digit was successfully appended. }
  356. function TCalculator.AppendDigit (Digit : integer) : boolean;
  357.  
  358. var
  359.   Mult     : integer;
  360.   C        : char;
  361.   MaxValue : longint;
  362.  
  363. const
  364.   MaxLen : array [Decimal..Octal, Size8..Size32] of byte
  365.          = (( 3,  5, 10),         { Decimal }
  366.             ( 2,  4,  8),         { Hexadecimal }
  367.             ( 8, 16, 32),         { Binary }
  368.             ( 3,  6, 11));        { Octal }
  369.  
  370. begin
  371.   { Set up various scratch values }
  372.   case FMode of
  373.     Decimal     : Mult := 10;
  374.     Hexadecimal : Mult := 16;
  375.     Binary      : Mult := 2;
  376.     Octal       : Mult := 8
  377.   end;
  378.  
  379.   { Check for some common error conditions }
  380.   if (FEntryState = csError) or (Digit < 0) or (Digit >= Mult) then
  381.     begin
  382.       Result := False;
  383.       exit
  384.     end;
  385.  
  386.   case FSize of
  387.     Size8  : MaxValue := $000000FF;
  388.     Size16 : MaxValue := $0000FFFF;
  389.     Size32 : MaxValue := $7FFFFFFF   { NB: $FFFFFFFF is -1 }
  390.   end;
  391.  
  392.   if Digit <= 9 then
  393.     C := Chr(Digit + Ord('0'))
  394.   else
  395.     C := Chr(Digit + Ord('A') - 10);
  396.  
  397.   if FEntryState = csFirstKey then
  398.     begin
  399.       if Digit <> 0 then
  400.         FEntryState := csNextKey;
  401.       FStack[FStackPtr].FValue := Digit;
  402.       FString := C;
  403.       FStrOK := True
  404.     end
  405.   else { FEntryState = csNextKey }
  406.     with FStack[FStackPtr] do
  407.       begin
  408.         { NB: String representation will ALWAYS be OK when FEntryState =
  409.               csNextKey.  This is because any other function will alter
  410.               FEntryState. }
  411.         if (length(FString) >= MaxLen[FMode, FSize]) or
  412.            (FValue * Mult + Digit > MaxValue) then
  413.           begin
  414.             Result := False;
  415.             exit
  416.           end;
  417.         FValue := FValue * Mult + Digit;
  418.         FString := FString + C;
  419.         { FStrOK := True    --- already implicit }
  420.       end;
  421.  
  422.   FChanged := True;
  423.   Result := True
  424. end;
  425.  
  426. { Handle the Backspace key.  This only works if a digit key has already
  427.   been pressed (ie, FEntryState is csNextKey). }
  428. function TCalculator.Backspace : boolean;
  429.  
  430. var
  431.   Mult : integer;
  432.   
  433. begin
  434.   if FEntryState <> csNextKey then
  435.     begin
  436.       Result := False;
  437.       exit
  438.     end;
  439.  
  440.   case Mode of
  441.     Decimal     : Mult := 10;
  442.     Hexadecimal : Mult := 16;
  443.     Binary      : Mult := 2;
  444.     Octal       : Mult := 8
  445.   end;
  446.  
  447.   { While TEntryState = csNextKey, FValue must be positive (if possible).
  448.     The string representation is already positive; FStrOK is True. }
  449.   with FStack[FStackPtr] do
  450.     begin
  451.       FValue := RoundVal(FValue, FSize, False);
  452.  
  453.       if FValue < 0 then
  454.         FValue := Trunc((FValue + 4294967296.0) / Mult)
  455.       else
  456.         FValue := FValue div Mult
  457.     end;
  458.  
  459.   Delete(FString, Length(FString), 1);      { Delete last digit }
  460.   if (FString = '') or (FString = '-') then
  461.     begin
  462.       FEntryState := csFirstKey;
  463.       FString := '0'
  464.     end;
  465.  
  466.   FChanged := True;
  467.   Result := True
  468. end;
  469.  
  470. { Store the current value in memory.  This must NOT be called if InError
  471.   returns True. }
  472. procedure TCalculator.StoreCurrentInMem;
  473.  
  474. begin
  475.   if FEntryState <> csError then
  476.     begin
  477.       FEntryState := csFirstKey;
  478.       FStrOK := False;
  479.       FChanged := True;
  480.  
  481.       with FStack[FStackPtr] do
  482.         begin
  483.           FValue := RoundVal(FValue, FSize, FSigned);
  484.           { FString will be updated in CurrentString }
  485.  
  486.           FMemory := FValue
  487.         end
  488.     end
  489. end;
  490.  
  491. { Store the value in memory into the current value.  This procedure must
  492.   NOT be called if InError returns True. }
  493. procedure TCalculator.RetrieveMemory;
  494.  
  495. begin
  496.   if FEntryState <> csError then
  497.     begin
  498.       FEntryState := csFirstKey;
  499.       FStrOK := False;
  500.       FChanged := True;
  501.  
  502.       FStack[FStackPtr].FValue := FMemory;
  503.       { FString will be updated in CurrentString }
  504.     end
  505. end;
  506.  
  507. { Add the result of the calculation to the contents of memory and store
  508.   it there.  Before this is done, the calculator simulates the Equals key
  509.   being pressed.  If the result of this is an error, the memory value is
  510.   NOT modified, and this function returns False; note that the display will
  511.   still need to be updated if this is the case. }
  512. function TCalculator.AddToMemoryKey : boolean;
  513.  
  514. begin
  515.   if FEntryState = csError then
  516.     begin
  517.       Result := False;
  518.       exit
  519.     end;
  520.  
  521.   Result := HandleKey(kEquals);
  522.  
  523.   if Result = True then
  524.     begin
  525.       FMemory := FMemory + FStack[FStackPtr].FValue;
  526.       FMemory := RoundVal(FMemory, FSize, FSigned)
  527.     end
  528. end;
  529.  
  530. { Handle a function key (eg, Equals, Plus, Minus, ...).  This function
  531.   returns True if the key could be handled.  Note that the display may
  532.   still need to be updated if this function returns False. }
  533. function TCalculator.HandleKey (Key : TCalcKey) : boolean;
  534.  
  535.   { Internal function:  Return the precedence of an operator.  A higher number
  536.     means a higher precedence. }
  537.   function Precedence (Op : TCalcKey) : integer;
  538.  
  539.   begin
  540.     case Op of
  541.       kNeg, kNot                  : Result := 3;
  542.       kMul, kDiv, kMod, kAnd      : Result := 2;
  543.       kAdd, kSub, kOr, kXor, kEqv : Result := 1;
  544.       kEquals                     : Result := 0
  545.     end
  546.   end;
  547.  
  548.   { Internal procedure:  Perform all operations on the stack which are
  549.     higher in precedence than the current operation (in "Key").  This
  550.     procedure sets FEntryState to csError if an error occurrs. }
  551.   procedure PerformPrevOps;
  552.  
  553.   begin
  554.     while FStack[FStackPtr].FOpUsed and
  555.           (Precedence(FStack[FStackPtr].FOp) >= Precedence(Key)) do
  556.       begin
  557.         FStackPtr := FStackPtr - 1;
  558.         with FStack[FStackPtr] do
  559.           begin
  560.             case FStack[FStackPtr + 1].FOp of
  561.               kMul : FValue := FValue * FStack[FStackPtr + 1].FValue;
  562.               kDiv : if FStack[FStackPtr + 1].FValue <> 0 then
  563.                        FValue := FValue div FStack[FStackPtr + 1].FValue
  564.                      else
  565.                        begin
  566.                          ClearOperations;
  567.                          FStrOK := False;
  568.                          FChanged := True;
  569.                          FEntryState := csError;
  570.                          exit
  571.                        end;
  572.               kMod : if FStack[FStackPtr + 1].FValue <> 0 then
  573.                        FValue := FValue mod FStack[FStackPtr + 1].FValue
  574.                      else
  575.                        begin
  576.                          ClearOperations;
  577.                          FStrOK := False;
  578.                          FChanged := True;
  579.                          FEntryState := csError;
  580.                          exit
  581.                        end;
  582.               kAnd : FValue := FValue and FStack[FStackPtr + 1].FValue;
  583.               kAdd : FValue := FValue + FStack[FStackPtr + 1].FValue;
  584.               kSub : FValue := FValue - FStack[FStackPtr + 1].FValue;
  585.               kOr  : FValue := FValue or FStack[FStackPtr + 1].FValue;
  586.               kXor : FValue := FValue xor FStack[FStackPtr + 1].FValue;
  587.               kEqv : FValue := not (FValue xor FStack[FStackPtr + 1].FValue);
  588.             end;
  589.             FValue := RoundVal(FValue, FSize, FSigned)
  590.           end;
  591.         FStack[FStackPtr + 1].FOpUsed := False
  592.       end
  593.   end;
  594.  
  595.  
  596. begin { TCalculator.HandleKey }
  597.   if FEntryState = csError then
  598.     begin
  599.       Result := False;
  600.       exit
  601.     end;
  602.  
  603.   FEntryState := csFirstKey;
  604.   FStrOK := False;
  605.   FChanged := True;
  606.  
  607.   Result := True;
  608.  
  609.   with FStack[FStackPtr] do
  610.     FValue := RoundVal(FValue, FSize, FSigned);
  611.  
  612.   if Key in [kNeg, kNot] then
  613.     begin
  614.       with FStack[FStackPtr] do
  615.         case Key of
  616.           kNeg : FValue := -FValue;
  617.           kNot : FValue := not FValue
  618.         end
  619.     end
  620.   else
  621.     begin
  622.       PerformPrevOps;
  623.       if FEntryState = csError then
  624.         begin
  625.           Result := False;
  626.           exit
  627.         end;
  628.  
  629.       if Key <> kEquals then
  630.         begin
  631.           FStackPtr := FStackPtr + 1;
  632.           with FStack[FStackPtr] do
  633.             begin
  634.               FOpUsed := True;
  635.               FOp := Key;
  636.               FValue := FStack[FStackPtr - 1].FValue
  637.             end
  638.         end
  639.     end;
  640.  
  641.   with FStack[FStackPtr] do
  642.     FValue := RoundVal(FValue, FSize, FSigned);
  643.   { FString will be updated in CurrentString }
  644. end;
  645.  
  646. end.
  647.  
  648.