home *** CD-ROM | disk | FTP | other *** search
/ Piper's Pit BBS/FTP: ibm 0000 - 0009 / ibm0000-0009 / ibm0003.tar / ibm0003 / TPOWER54.ZIP / DEMOSRC.ARC / PCALC.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1989-07-10  |  35.6 KB  |  1,335 lines

  1. {$S-,R-,V-,I-,B-,F-}
  2. {$M 2048,0,200}
  3.  
  4. {*********************************************************}
  5. {*                   PCALC.PAS 5.07                      *}
  6. {*               Programmer's Calculator                 *}
  7. {*     An example program for Turbo Professional 5.0     *}
  8. {*        Copyright (c) TurboPower Software 1987.        *}
  9. {* Portions copyright (c) Sunny Hill Software 1985, 1986 *}
  10. {*     and used under license to TurboPower Software     *}
  11. {*                 All rights reserved.                  *}
  12. {*********************************************************}
  13. {* Define KB5151 to set up for Keytronics KB5151         *}
  14. {* Lew Paper, 3/18/88                                    *}
  15. {*********************************************************}
  16.  
  17. program PCalc;
  18.   {-Programmer's calculator}
  19.  
  20. uses
  21.   Dos,                       {standard DOS/BIOS routines}
  22.   TpBCD,                     {Turbo Professional BCD math routines}
  23.   TpString,                  {Turbo Professional string handling routines}
  24.   TpCrt,                     {Turbo Professional CRT unit}
  25.   TpInt,                     {Turbo Professional ISR management}
  26.   TpTsr;                     {Turbo Professional TSR management}
  27.  
  28. const
  29.   {** keep the following together to allow easy patching **}
  30.   ModuleName : string[5] = 'PCALC'; {module name for standard interface}
  31.   OurHotKey : Word = $052E;  {Ctrl + RightShift, 'C'}
  32.   Div0Handle : Byte = 15;
  33.   {******************* end of patch area ******************}
  34.  
  35.   DisableOurselves : Boolean = False; {if true, disable the TSR}
  36.  
  37. type
  38.   String80 = string[80];
  39.   VideoWord = record
  40.                 Ch : Char; Attr : Byte;
  41.               end;
  42.   ScreenType = array[1..43, 1..80] of VideoWord; {43 rows * 80 columns}
  43.   ScreenBuffer = array[1..10, 1..45] of VideoWord; {10 rows * 45 columns}
  44.   CalcType = (None, Add, Subtract, Multiply, Divide,
  45.     AndOp, ModOp, NotOp, OrOp, XorOp, ShlOp, ShrOp);
  46.   CalcMode = (Decimal, Hexadecimal, Binary, FloatPt, Exponential);
  47.   CalcStatus = (Cleared, Done, Num1, Num2);
  48. const
  49.   ProgName : string[35] = 'PCALC: Programmer''s Calculator 5.07';
  50.   Copyright : string[41] = 'Copyright (c) 1987 by TurboPower Software';
  51.   CommandLabel1 : string[56] =
  52.   ' Clr Entry Binary Dec Hex Float exP Key ';
  53.   CommandLabel2 : string[58] =
  54.   ' Save Insert And Mod Not Or Xor shL shR ';
  55.   ModeStrings : array[CalcMode] of string[7] =
  56.   ('─ Dec ─', '─ Hex ─', '─ Bin ─', ' Float ', '─ Exp ─');
  57.   CalcChars : array[CalcType] of string[3] =
  58.   ('   ', '+  ', '-  ', '*  ', '÷  ', 'and', 'mod', 'not', 'or ', 'xor',
  59.     'shl', 'shr');
  60.   DoneChars : array[Boolean] of string[1] = (' ', '=');
  61.   LoadError : string[23] = 'Unable to install PCALC';
  62.  
  63.   {** key codes **}
  64.   Esc = #27;
  65.   F5 = 63;
  66.   F6 = 64;
  67.   F7 = 65;
  68.   F8 = 66;
  69.   F9 = 67;
  70.   F10 = 68;
  71.   DisableCode = 22;          {scan code for Alt-U, press twice in succession
  72.                               to disable the popup}
  73.  
  74.   {** screen stuff **}
  75. const
  76.   LeftCol : Byte = 36;       {leftmost col on screen}
  77.   RtCol : Byte = 80;         {rightmost col on screen}
  78.   TopRow : Byte = 1;         {top row of window border}
  79.   BotRow : Byte = 10;        {bottom row of window border}
  80.   SaveRow : Byte = 6;        {row where saved number is displayed}
  81.   LabelRow : Byte = 8;       {row where 1st line of command labels is displayed}
  82.   NumRow : Byte = 3;         {row where numbers are entered}
  83.   NumCol : Byte = 38;        {col where numbers are displayed}
  84.   SymCol : Byte = 76;        {col where calculation symbols are displayed}
  85.  
  86.   CrossBar = '─';            {if changed, ModeStrings must also be changed}
  87.   LeftTee = '├';
  88.   RightTee = '┤';
  89. var
  90.   OurScreenBuffer : ScreenBuffer; {for saving the screen}
  91.   OurScreenPtr : Pointer;
  92.   ScreenPtr : ^ScreenType;
  93.   Span : String80;
  94.   SpLen : Byte absolute Span; {its length}
  95.   Reverse,                   {reverse video attribute}
  96.   Command,                   {video attribute for commands}
  97.   Bright,                    {bright video attribute}
  98.   Dim : Byte;                {dim video attribute}
  99.   FxAttrs : FlexAttrs;       {attributes for FlexWrite}
  100.  
  101.   {macro stuff}
  102. const
  103.   SmacsName : string[10] = 'SUPER MACS';
  104.   {for SMACS function calls}
  105.   GetMacroState = 3;
  106.   SetMacroState = 4;
  107.   MacroDefinedCheck = 5;
  108.   DefineMacroFunc = 6;
  109.  
  110.   {calculator stuff}
  111. type
  112.   ExpStateType = (NoExp, DoingExp, HaveExp);
  113. const
  114.   DefaultMode = FloatPt;
  115.   Digits : array[0..$F] of Char = '0123456789ABCDEF';
  116.   HighDigit :                {highest digit allowed in given CalcMode}
  117.   array[CalcMode] of Byte = (9, $F, 1, 9, 9);
  118.   MaxDigits :                {number of digits allowed in given CalcMode}
  119.   array[CalcMode] of Byte = (10, 8, 32, 35, 35);
  120.   Base : array[Decimal..Binary] of LongInt = (10, 16, 2);
  121.   ZeroString : string[1] = '0';
  122.   MinusZero : string[2] = '-0';
  123.   NullString : string[1] = '';
  124.   NumStringWidth = 37;
  125.   MaxLongIntDiv10 = 214748364;
  126. var
  127.   BcdMode,
  128.   HaveDecimalPoint,
  129.   MinusPending,
  130.   DivideByZero,
  131.   MathError : Boolean;
  132.   CurrentCalcMode : CalcMode;
  133.   CurrentCalcType : CalcType;
  134.   DigitCount : Byte;
  135.   NumString1,
  136.   NumString2,
  137.   SaveString : string[NumStringWidth];
  138.   Result : string;
  139.   Long1, Long2,
  140.   LongResult,
  141.   SaveLong : LongInt;
  142.   Bcd1, Bcd2,
  143.   BcdResult,
  144.   SaveBcd : BCD;
  145.   Status : CalcStatus;
  146.   Exponent : Integer;
  147.   ExponentSign,
  148.   NegativeExponent : Boolean;
  149.   ExponentState : ExpStateType;
  150.  
  151.   procedure Beep;
  152.     {-Ring that obnoxious bell}
  153.   begin
  154.     Write(^G);
  155.   end;
  156.  
  157.   procedure SetAttributes;
  158.     {-Set video attribute variables based on the current video mode}
  159.   begin
  160.     case CurrentMode of
  161.       2,                     {BW80}
  162.       7 :                    {MONOCHROME}
  163.         begin
  164.           Bright := $F;      {white on black}
  165.           Dim := $7;         {light gray on black}
  166.           Command := $7;     {light gray on black}
  167.           Reverse := $70;    {black on light gray}
  168.         end;
  169.     else                     {COLOR}
  170.       begin
  171.         Bright := $1F;       {white on blue}
  172.         Dim := $1E;          {yellow on blue}
  173.         Command := $13;      {light cyan on blue}
  174.         Reverse := $71;      {blue on light gray}
  175.       end;
  176.     end;
  177.  
  178.     {set attributes for FlexWrite}
  179.     FxAttrs[0] := Command;
  180.     FxAttrs[1] := Bright;
  181.   end;
  182.  
  183.   procedure DrawScreen;
  184.     {-Draw initial screen}
  185.   begin
  186.     {draw main box, title, and command labels}
  187.     FrameWindow(LeftCol, TopRow, RtCol, BotRow, Bright, Reverse,
  188.       ' '+ProgName+' ');
  189.  
  190.     {draw crossbars}
  191.     SpLen := Succ(RtCol-LeftCol);
  192.     FillChar(Span[1], SpLen, CrossBar);
  193.     Span[1] := LeftTee;
  194.     Span[SpLen] := RightTee;
  195.     FastWrite(Span, Pred(SaveRow), LeftCol, Bright);
  196.     FastWrite(' Saved ', Pred(SaveRow), RtCol-11, Bright);
  197.     FastWrite(Span, Pred(LabelRow), LeftCol, Bright);
  198.     FastWrite(' Commands ', Pred(LabelRow), LeftCol+2, Bright);
  199.  
  200.     {draw command labels}
  201.     FlexWrite(CommandLabel1, LabelRow, Succ(LeftCol), FxAttrs);
  202.     FlexWrite(CommandLabel2, Succ(LabelRow), Succ(LeftCol), FxAttrs);
  203.   end;
  204.  
  205.   procedure UpdateDisplay;
  206.     {-Update the calculator display}
  207.   begin
  208.     FastWrite(LeftPad(NumString1, NumStringWidth), Pred(NumRow), NumCol, Dim);
  209.     FastWrite(LeftPad(NumString2, NumStringWidth), NumRow, NumCol, Dim);
  210.     FastWrite(CalcChars[CurrentCalcType], NumRow, SymCol, Bright);
  211.     FastWrite(LeftPad(Result, NumStringWidth), Succ(NumRow), NumCol, Dim);
  212.     FastWrite(DoneChars[Status = Done], Succ(NumRow), SymCol, Bright);
  213.     FastWrite(LeftPad(SaveString, NumStringWidth), SaveRow, NumCol, Dim);
  214.   end;
  215.  
  216.   procedure HighlightNumber(Which : Byte);
  217.     {-Highlight the number specified by Which:
  218.       1 = NumString1
  219.       2 = NumString2
  220.       3 = Result
  221.       4 = SaveString}
  222.   var
  223.     Row : Byte;
  224.   begin
  225.     case Which of
  226.       1 : Row := Pred(NumRow);
  227.       2 : Row := NumRow;
  228.       3 : Row := Succ(NumRow);
  229.       4 : Row := SaveRow;
  230.     end;
  231.     ChangeAttribute(NumStringWidth, Row, NumCol, Bright);
  232.   end;
  233.  
  234.   procedure ShowMode;
  235.     {-Show the current calculation mode}
  236.   begin
  237.     FastWrite(ModeStrings[CurrentCalcMode], Pred(SaveRow), LeftCol+2, Bright);
  238.   end;
  239.  
  240.   procedure ClearPromptLine;
  241.     {-Clear the prompt line}
  242.   begin
  243.     SpLen := Pred(RtCol-LeftCol);
  244.     FillChar(Span[1], SpLen, ' ');
  245.     FastWrite(Span, SaveRow, Succ(LeftCol), Dim);
  246.   end;
  247.  
  248.   procedure Prompt(Msg : String80);
  249.     {-Display a prompt}
  250.   begin
  251.     ClearPromptLine;
  252.     FastWrite(Msg, SaveRow, LeftCol+2, Dim);
  253.   end;
  254.  
  255.   procedure PressAnyKey(Msg : String80);
  256.     {-Display a message and wait for a keystroke}
  257.   var
  258.     I : Word;
  259.   begin
  260.     Prompt(Msg+'. Press any key...');
  261.     I := ReadKeyWord;
  262.   end;
  263.  
  264.   function YesNo(Msg : String80) : Boolean;
  265.     {-Display a yes/no message and return true if Y is pressed}
  266.   var
  267.     ChWord : Word;
  268.     Ch : Char absolute ChWord;
  269.   begin
  270.     Prompt(Msg);
  271.     repeat
  272.       ChWord := ReadKeyWord;
  273.       Ch := Upcase(Ch);
  274.     until (Ch = 'Y') or (Ch = 'N');
  275.     YesNo := (Ch = 'Y');
  276.   end;
  277.  
  278.   procedure GetBcdVal(var S : string; var B : BCD);
  279.     {-Convert string S to a BCD real}
  280.   var
  281.     Code : Word;
  282.   begin
  283.     ValBcd(S, B, Code);
  284.     if Code <> 0 then begin
  285.       S := ZeroString;
  286.       B := ZeroBCD;
  287.     end;
  288.   end;
  289.  
  290.   procedure GetExpString(var B : BCD; var BcdSt : string);
  291.     {-Convert a BCD to a string with an exponent, and delete 0's just before
  292.       the 'E'}
  293.   var
  294.     I : Word;
  295.   begin
  296.     {convert to string}
  297.     BcdSt := StrExpBcd(B, 0);
  298.  
  299.     {delete initial space, if any}
  300.     if BcdSt[1] = ' ' then
  301.       Delete(BcdSt, 1, 1);
  302.  
  303.     {delete 0's just before the 'E', if any}
  304.     I := Pos('E', BcdSt);
  305.     while BcdSt[Pred(I)] = '0' do begin
  306.       Dec(I);
  307.       Delete(BcdSt, I, 1);
  308.     end;
  309.  
  310.     {delete '.' just before the 'E', if any}
  311.     Dec(I);
  312.     if BcdSt[I] = '.' then
  313.       Delete(BcdSt, I, 1);
  314.   end;
  315.  
  316.   procedure GetBcdString(var B : BCD; var BcdSt : string);
  317.     {-Try to convert a BCD to a string without the exponent showing}
  318.   var
  319.     S : string;
  320.     SLen : Byte absolute S;
  321.   begin
  322.     {convert B to a string}
  323.     S := StrBcd(B, 0, 81);
  324.  
  325.     {delete any trailing 0's}
  326.     while S[SLen] = '0' do
  327.       Dec(SLen);
  328.  
  329.     {delete '.' at the end, if any}
  330.     if S[SLen] = '.' then
  331.       Dec(SLen);
  332.  
  333.     {if the string is still too large, convert to exponential format}
  334.     if SLen > NumStringWidth then
  335.       GetExpString(B, BcdSt)
  336.     else
  337.       BcdSt := S;
  338.   end;
  339.  
  340.   function MakeBcdConstant : string;
  341.     {-Return a typed constant representing the current BCD real of interest}
  342.   var
  343.     I : Word;
  344.     B : BCD;
  345.     S : string[42];
  346.   begin
  347.     if Status = Done then
  348.       B := BcdResult
  349.     else begin
  350.       GetBcdVal(NumString2, Bcd2);
  351.       B := Bcd2;
  352.     end;
  353.     S := '(';
  354.     for I := 1 to 10 do
  355.       S := S+'$'+HexB(B[I])+',';
  356.     S[Length(S)] := ')';
  357.     MakeBcdConstant := S;
  358.   end;
  359.  
  360.   procedure MakeMacro;
  361.     {-Turn a string into a macro}
  362.   var
  363.     MacroKey : Word;
  364.     Ch : Char absolute MacroKey;
  365.     Regs : IntRegisters;
  366.     P : IfcPtr;
  367.     DoTypedConstant,
  368.     SaveMacroState : Boolean;
  369.     S : string;
  370.   label
  371.     ExitPoint;
  372.   begin
  373.     {check for presence of SMACS}
  374.     P := ModulePtrByName(SmacsName);
  375.     if P = nil then begin
  376.       PressAnyKey('Requires SMACS');
  377.       goto ExitPoint;
  378.     end;
  379.  
  380.     if Status = Done then
  381.       HighlightNumber(3)
  382.     else
  383.       HighlightNumber(2);
  384.  
  385.     {save the current macro state and turn macros off -- we don't want a
  386.      macro played back!}
  387.     Regs.AH := GetMacroState;
  388.     EmulateInt(Regs, P^.CmdEntryPtr);
  389.     SaveMacroState := Boolean(Regs.AL);
  390.  
  391.     {now we can get the key}
  392.     Prompt('Press the key for the SMACS macro');
  393.     MacroKey := ReadKeyWord;
  394.     if Ch = Esc then
  395.       goto ExitPoint;
  396.  
  397.     {restore macro state}
  398.     Regs.AH := SetMacroState;
  399.     Regs.AL := Byte(SaveMacroState);
  400.     EmulateInt(Regs, P^.CmdEntryPtr);
  401.  
  402.     {see if the key is already defined}
  403.     Regs.AH := MacroDefinedCheck;
  404.     Regs.BX := MacroKey;
  405.     EmulateInt(Regs, P^.CmdEntryPtr);
  406.     if Boolean(Regs.AL) then
  407.       {see what to do about it}
  408.       if not YesNo('Overwrite existing macro (Y/N)?') then
  409.         goto ExitPoint;
  410.  
  411.     {if in BCD mode, see if user wants to create a typed constant array}
  412.     if BcdMode then
  413.       DoTypedConstant := YesNo('Create a BCD typed constant (Y/N)?')
  414.     else
  415.       DoTypedConstant := False;
  416.  
  417.     {define the macro}
  418.     Regs.AH := DefineMacroFunc;
  419.     {DS:DX points to a string to turn into a macro}
  420.     if DoTypedConstant then
  421.       S := MakeBcdConstant
  422.     else begin
  423.       if (Status = Done) then
  424.         S := Result
  425.       else
  426.         S := NumString2;
  427.  
  428.       {add radix symbols for Turbo/MASM}
  429.       case CurrentCalcMode of
  430.         Hexadecimal : S := '$'+S; {presumably for Turbo}
  431.         Binary : S := S+'b'; {presumably for MASM}
  432.       end;
  433.     end;
  434.     Regs.DS := Seg(S);
  435.     Regs.DX := Ofs(S);
  436.  
  437.     {ES:DI points to the name of the macro, BX has the Key}
  438.     Regs.ES := Seg(NullString);
  439.     Regs.DI := Ofs(NullString);
  440.     Regs.BX := MacroKey;
  441.     EmulateInt(Regs, P^.CmdEntryPtr);
  442.  
  443.     {check result in AL}
  444.     if Regs.AL = 0 then
  445.       Prompt('Macro defined')
  446.     else
  447.       Prompt('Unable to create macro');
  448.     Delay(1000);
  449. ExitPoint:
  450.     ClearPromptLine;
  451.   end;
  452.  
  453.   procedure ResetDigitCount;
  454.     {-Reset the digit count, etc.}
  455.   var
  456.     Epos : Word;
  457.     Estr : string[4];
  458.   begin
  459.     {get digit count}
  460.     if (NumString2 = ZeroString) or (NumString2 = MinusZero) then
  461.       DigitCount := 0
  462.     else begin
  463.       DigitCount := Length(NumString2);
  464.       MinusPending := NumString2[1] = '-';
  465.       if MinusPending then begin
  466.         case CurrentCalcMode of
  467.           Hexadecimal..Binary :
  468.             begin
  469.               {minus sign not allowed in Hex or Binary modes}
  470.               Delete(NumString2, 1, 1);
  471.               MinusPending := False;
  472.             end;
  473.         end;
  474.         {doesn't count toward total number of digits in any case}
  475.         Dec(DigitCount);
  476.       end;
  477.     end;
  478.  
  479.     {get exponent}
  480.     if BcdMode then begin
  481.       HaveDecimalPoint := Pos('.', NumString2) <> 0;
  482.       Epos := Pos('E', NumString2);
  483.       if (Epos = 0) then
  484.         ExponentState := NoExp
  485.       else begin
  486.         {get exponent}
  487.         Estr := Copy(NumString2, Succ(Epos), Length(NumString2));
  488.         if not Str2Int(Estr, Exponent) then
  489.           Exponent := 0;
  490.         if Abs(Exponent) < 10 then
  491.           ExponentState := DoingExp
  492.         else
  493.           ExponentState := HaveExp;
  494.         NegativeExponent := (Pos('-', Estr) <> 0);
  495.         ExponentSign := NegativeExponent or (Pos('+', Estr) <> 0);
  496.       end;
  497.     end;
  498.   end;
  499.  
  500.   procedure TrimZeros(var S : string);
  501.     {-Trim initial 0's from S}
  502.   var
  503.     SLen : Byte absolute S;
  504.   begin
  505.     while (S[1] = '0') and (SLen > 1) do begin
  506.       Dec(SLen);
  507.       Move(S[2], S[1], SLen);
  508.     end;
  509.   end;
  510.  
  511.   procedure UpdateOneString(Which : Byte);
  512.     {-Update the string specified by Which:
  513.       1 = NumString1
  514.       2 = NumString2
  515.       3 = Result
  516.       4 = SaveString}
  517.   var
  518.     StPtr : ^String80;
  519.     LongPtr : ^LongInt;
  520.     BcdPtr : ^BCD;
  521.   begin
  522.     {set string, number pointers}
  523.     case Which of
  524.       1 : if (CurrentCalcType = NotOp) then begin
  525.             NumString1 := NullString;
  526.             Exit;
  527.           end
  528.           else begin
  529.             StPtr := @NumString1;
  530.             LongPtr := @Long1;
  531.             BcdPtr := @Bcd1;
  532.           end;
  533.       2 : begin
  534.             StPtr := @NumString2;
  535.             LongPtr := @Long2;
  536.             BcdPtr := @Bcd2;
  537.           end;
  538.       3 : begin
  539.             StPtr := @Result;
  540.             LongPtr := @LongResult;
  541.             BcdPtr := @BcdResult;
  542.           end;
  543.       4 : begin
  544.             StPtr := @SaveString;
  545.             LongPtr := @SaveLong;
  546.             BcdPtr := @SaveBcd;
  547.           end;
  548.     end;
  549.  
  550.     {convert string}
  551.     if BcdMode and EqualBcd(BcdPtr^, ZeroBCD) then
  552.       StPtr^ := ZeroString
  553.     else
  554.       case CurrentCalcMode of
  555.         Decimal :
  556.           StPtr^ := Long2Str(LongPtr^);
  557.         Hexadecimal :
  558.           StPtr^ := HexL(LongPtr^);
  559.         Binary :
  560.           StPtr^ := BinaryL(LongPtr^);
  561.         FloatPt :
  562.           GetBcdString(BcdPtr^, StPtr^);
  563.         Exponential :
  564.           GetExpString(BcdPtr^, StPtr^);
  565.       end;
  566.  
  567.     {trim any initial 0's}
  568.     if not BcdMode then
  569.       TrimZeros(StPtr^);
  570.   end;
  571.  
  572.   procedure UpdateStrings;
  573.     {-Update all the strings that currently have values associated with them}
  574.   begin
  575.     case Status of
  576.       Done :
  577.         begin
  578.           UpdateOneString(1);
  579.           UpdateOneString(3);
  580.         end;
  581.       Num2 :
  582.         UpdateOneString(1);
  583.     end;
  584.     if Status <> Cleared then
  585.       UpdateOneString(2);
  586.     UpdateOneString(4);
  587.   end;
  588.  
  589.   function CheckMathError : Boolean;
  590.     {-Returns true if a math error occurred}
  591.   begin
  592.     CheckMathError := MathError;
  593.     if MathError then begin
  594.       if DivideByZero then
  595.         PressAnyKey('Result is undefined')
  596.       else
  597.         PressAnyKey('Overflow error');
  598.       UpdateDisplay;
  599.       MathError := False;
  600.       DivideByZero := False;
  601.     end;
  602.   end;
  603.  
  604.   procedure SwitchMode(Mode : CalcMode);
  605.     {-Switch calculation modes}
  606.   begin
  607.     {do nothing if we're already in correct mode}
  608.     if Mode = CurrentCalcMode then
  609.       Exit;
  610.  
  611.     {translate data types if necessary}
  612.     if (Mode >= FloatPt) then begin
  613.       {reject certain calculation types for real numbers}
  614.       case CurrentCalcType of
  615.         AndOp..ShrOp : if Status <> Done then
  616.                          Exit;
  617.       end;
  618.       if not BcdMode then begin
  619.         LongIntToBcd(Long1, Bcd1);
  620.         LongIntToBcd(Long2, Bcd2);
  621.         LongIntToBcd(LongResult, BcdResult);
  622.       end
  623.       else
  624.         GetBcdVal(NumString2, Bcd2);
  625.     end
  626.     else
  627.       if BcdMode then begin
  628.         Long1 := RoundBcd(Bcd1);
  629.         GetBcdVal(NumString2, Bcd2);
  630.         Long2 := RoundBcd(Bcd2);
  631.         LongResult := RoundBcd(BcdResult);
  632.       end;
  633.  
  634.     {exit in case of error}
  635.     if CheckMathError then
  636.       Exit;
  637.  
  638.     {change the mode setting}
  639.     CurrentCalcMode := Mode;
  640.     BcdMode := (Mode >= FloatPt);
  641.     ShowMode;
  642.  
  643.     {update strings, digit count, etc.}
  644.     UpdateStrings;
  645.     ResetDigitCount;
  646.   end;
  647.  
  648.   procedure ClearCurrentEntry;
  649.     {-Clear the current entry and reset related variables}
  650.   begin
  651.     DigitCount := 0;
  652.     Long2 := 0;
  653.     Bcd2 := ZeroBCD;
  654.     NumString2 := ZeroString;
  655.     MinusPending := False;
  656.     HaveDecimalPoint := False;
  657.     ExponentState := NoExp;
  658.   end;
  659.  
  660.   procedure ClearAll;
  661.     {-Reset everything}
  662.   begin
  663.     {clear the current entry}
  664.     ClearCurrentEntry;
  665.     Status := Num1;
  666.  
  667.     {indicate that we're all clear}
  668.     CurrentCalcType := None;
  669.     Status := Cleared;
  670.  
  671.     {clear numeric variables}
  672.     Long1 := 0;
  673.     LongResult := 0;
  674.     Bcd1 := ZeroBCD;
  675.     BcdResult := ZeroBCD;
  676.  
  677.     {clear strings}
  678.     NumString1 := NullString;
  679.     Result := NullString;
  680.   end;
  681.  
  682.   procedure ClearEntry;
  683.     {-Reset the current entry}
  684.   begin
  685.     {if Done with a calculation, clear everything...}
  686.     if (Status = Done) then
  687.       ClearAll
  688.     else
  689.       {otherwise just the current entry}
  690.       ClearCurrentEntry;
  691.   end;
  692.  
  693.   procedure PerformCalc;
  694.     {-Perform a calculation of type CurrentCalcMode}
  695.   begin
  696.     if BcdMode then begin
  697.       GetBcdVal(NumString2, Bcd2);
  698.       case CurrentCalcType of
  699.         Add :
  700.           AddBcd(Bcd1, Bcd2, BcdResult);
  701.         Subtract :
  702.           SubBcd(Bcd1, Bcd2, BcdResult);
  703.         Multiply :
  704.           MultBcd(Bcd1, Bcd2, BcdResult);
  705.         Divide :
  706.           if EqualBcd(Bcd2, ZeroBCD) then begin
  707.             MathError := True;
  708.             DivideByZero := True;
  709.             Exit;
  710.           end
  711.           else
  712.             DivBcd(Bcd1, Bcd2, BcdResult);
  713.       end;
  714.     end
  715.     else
  716.       case CurrentCalcType of
  717.         Add :
  718.           LongResult := Long1+Long2;
  719.         Subtract :
  720.           LongResult := Long1-Long2;
  721.         Multiply :
  722.           LongResult := Long1*Long2;
  723.         Divide :
  724.           if (Long2 = 0) then begin
  725.             MathError := True;
  726.             DivideByZero := True;
  727.             Exit;
  728.           end
  729.           else
  730.             LongResult := Long1 div Long2;
  731.         AndOp :
  732.           LongResult := Long1 and Long2;
  733.         ModOp :
  734.           LongResult := Long1 mod Long2;
  735.         OrOp :
  736.           LongResult := Long1 or Long2;
  737.         XorOp :
  738.           LongResult := Long1 xor Long2;
  739.         ShlOp :
  740.           if (Long2 > 31) or (Long2 < 0) then
  741.             LongResult := 0
  742.           else
  743.             LongResult := Long1 shl Long2;
  744.         ShrOp :
  745.           if (Long2 > 31) or (Long2 < 0) then
  746.             LongResult := 0
  747.           else
  748.             LongResult := Long1 shr Long2;
  749.       end;
  750.  
  751.     {convert the result to a string}
  752.     UpdateOneString(3);
  753.   end;
  754.  
  755.   procedure DoCalc(CT : CalcType);
  756.     {-Prepare for a calculation of the specified type}
  757.   begin
  758.     if (DigitCount = 0) and (Status <> Done) then
  759.       Exit;
  760.  
  761.     {reject certain calculation types for real numbers}
  762.     if BcdMode then
  763.       case CT of
  764.         AndOp..ShrOp : Exit;
  765.       end;
  766.  
  767.     {NOT is a special case}
  768.     if (CT = NotOp) then begin
  769.       if (Status <> Num2) then begin
  770.         CurrentCalcType := NotOp;
  771.         if Status <> Num1 then
  772.           Long2 := LongResult;
  773.         LongResult := not Long2;
  774.         Status := Done;
  775.         UpdateOneString(1);
  776.         UpdateOneString(2);
  777.         UpdateOneString(3);
  778.       end;
  779.       Exit;
  780.     end;
  781.  
  782.     {move strings and values as necessary}
  783.     case Status of
  784.       Done :
  785.         begin
  786.           {move Result to Num1}
  787.           Long1 := LongResult;
  788.           Bcd1 := BcdResult;
  789.           NumString1 := Result;
  790.           LongResult := 0;
  791.           BcdResult := ZeroBCD;
  792.           Result := NullString;
  793.         end;
  794.       Num1 :
  795.         begin
  796.           {move 1st number up}
  797.           Long1 := Long2;
  798.           if BcdMode then
  799.             GetBcdVal(NumString2, Bcd2);
  800.           Bcd1 := Bcd2;
  801.           NumString1 := NumString2;
  802.         end;
  803.       Num2 :
  804.         begin
  805.           {do the calculation, then move result to first number}
  806.           PerformCalc;
  807.  
  808.           {handle errors}
  809.           if CheckMathError then begin
  810.             ClearAll;
  811.             Exit;
  812.           end;
  813.  
  814.           Long1 := LongResult;
  815.           Bcd1 := BcdResult;
  816.           NumString1 := Result;
  817.           LongResult := 0;
  818.           BcdResult := ZeroBCD;
  819.           Result := NullString;
  820.         end;
  821.     end;
  822.  
  823.     {reset}
  824.     ClearCurrentEntry;
  825.  
  826.     {store calculation type}
  827.     CurrentCalcType := CT;
  828.     Status := Num2;
  829.   end;
  830.  
  831.   procedure FinishCalc;
  832.     {-Finish the current calculation}
  833.   begin
  834.     {exit if the status is wrong}
  835.     if Status <> Num2 then
  836.       Exit;
  837.  
  838.     {perform the actual calculation}
  839.     PerformCalc;
  840.  
  841.     {handle errors}
  842.     if CheckMathError then begin
  843.       ClearAll;
  844.       Exit;
  845.     end;
  846.  
  847.     ExponentState := NoExp;
  848.  
  849.     {change the status}
  850.     Status := Done;
  851.   end;
  852.  
  853.   procedure SaveValue;
  854.     {-Save the current entry. Or, if we've just finished a calculation, save
  855.       the result.}
  856.   begin
  857.     case Status of
  858.       Cleared : {do nothing} ;
  859.       Done :                 {save result}
  860.         begin
  861.           if BcdMode then
  862.             SaveBcd := BcdResult
  863.           else
  864.             SaveLong := LongResult;
  865.           SaveString := Result;
  866.         end;
  867.     else                     {save 2nd num}
  868.       begin
  869.         if BcdMode then begin
  870.           GetBcdVal(NumString2, Bcd2);
  871.           SaveBcd := Bcd2;
  872.         end
  873.         else
  874.           SaveLong := Long2;
  875.         SaveString := NumString2;
  876.       end;
  877.     end;
  878.   end;
  879.  
  880.   procedure InsertSavedValue;
  881.     {-Insert a saved value into the current entry}
  882.   begin
  883.     case Status of
  884.       {if Cleared or Done, clear all and change status}
  885.       Cleared..Done :
  886.         begin
  887.           ClearAll;
  888.           Status := Num1;
  889.         end;
  890.     else
  891.       {just clear the current entry}
  892.       ClearCurrentEntry;
  893.     end;
  894.  
  895.     {insert the saved value}
  896.     Long2 := SaveLong;
  897.     Bcd2 := SaveBcd;
  898.  
  899.     {update the current entry string, digit count, etc.}
  900.     UpdateOneString(2);
  901.     ResetDigitCount;
  902.   end;
  903.  
  904.   function AppendChar(Ch : Char) : Boolean;
  905.     {-Append a character to NumString2}
  906.   begin
  907.     {check for overflow of digits}
  908.     if DigitCount >= MaxDigits[CurrentCalcMode] then begin
  909.       AppendChar := False;
  910.       Exit;
  911.     end
  912.     else
  913.       AppendChar := True;
  914.  
  915.     if (DigitCount = 0) and (Ch <> '.') then begin
  916.       NumString2[Length(NumString2)] := Ch;
  917.       DigitCount := 1;
  918.     end
  919.     else begin
  920.       Inc(NumString2[0]);
  921.       NumString2[Length(NumString2)] := Ch;
  922.       Inc(DigitCount);
  923.     end;
  924.   end;
  925.  
  926.   procedure AddDigit(Digit : Integer);
  927.     {-Add a digit to the current entry}
  928.   var
  929.     AbsLong2 : LongInt;
  930.     AbsExp,
  931.     DigitToAdd : Integer;
  932.   begin
  933.     {check for illegal digit}
  934.     if (DigitCount >= MaxDigits[CurrentCalcMode]) or (Digit > HighDigit[CurrentCalcMode]) then
  935.       Exit;
  936.  
  937.     {reject extra digits in an exponent}
  938.     if ExponentState = HaveExp then
  939.       Exit;
  940.  
  941.     {reset if ...}
  942.     if Status = Done then
  943.       ClearAll;
  944.  
  945.     if Status = Cleared then
  946.       Status := Num1;
  947.  
  948.     {don't insert extra zeros}
  949.     if ((DigitCount = 0) or (NumString2 = MinusZero)) and (Digit = 0) then
  950.       Exit;
  951.  
  952.     case CurrentCalcMode of
  953.       Decimal..Binary :      {longint operation}
  954.         begin
  955.           {check for potential overflow if we're in Decimal mode}
  956.           if CurrentCalcMode = Decimal then begin
  957.             AbsLong2 := Abs(Long2);
  958.             if (AbsLong2 > MaxLongIntDiv10) or ((AbsLong2 = MaxLongIntDiv10) and (Digit > 7)) then
  959.               Exit;
  960.           end;
  961.  
  962.           {get digit to add}
  963.           if MinusPending then
  964.             DigitToAdd := -Digit
  965.           else
  966.             DigitToAdd := Digit;
  967.  
  968.           {add it}
  969.           Long2 := (Long2*Base[CurrentCalcMode])+LongInt(DigitToAdd);
  970.         end;
  971.     else                     {floating point operation}
  972.       if (ExponentState = DoingExp) then begin
  973.         AbsExp := Abs(Exponent);
  974.         if (AbsExp > 6) or ((AbsExp = 6) and (Digit > 3)) then
  975.           Exit;
  976.         if NegativeExponent then
  977.           DigitToAdd := -Digit
  978.         else
  979.           DigitToAdd := Digit;
  980.         if (Exponent = 0) then
  981.           Exponent := DigitToAdd
  982.         else begin
  983.           Exponent := (Exponent*10)+DigitToAdd;
  984.           ExponentState := HaveExp;
  985.         end;
  986.       end;
  987.     end;
  988.  
  989.     {append the digit}
  990.     if AppendChar(Digits[Digit]) then
  991.       {won't fail -- error checking already done} ;
  992.   end;
  993.  
  994.   procedure StartExponent;
  995.     {-Handle entry of 'E'}
  996.   begin
  997.     if (ExponentState <> NoExp) or (DigitCount = 0) then
  998.       Exit;
  999.     if not AppendChar('E') then
  1000.       Exit;
  1001.     ExponentState := DoingExp;
  1002.     NegativeExponent := False;
  1003.     ExponentSign := False;
  1004.     Exponent := 0;
  1005.   end;
  1006.  
  1007.   procedure DoMinus;
  1008.     {-Handle entry of '-'}
  1009.   begin
  1010.     if (ExponentState = DoingExp) then begin
  1011.       if (Exponent = 0) and not ExponentSign then
  1012.         if AppendChar('-') then begin
  1013.           ExponentSign := True;
  1014.           NegativeExponent := True;
  1015.         end;
  1016.     end
  1017.     else
  1018.       if (CurrentCalcType <> ShlOp) and (CurrentCalcType <> ShrOp) and
  1019.       (DigitCount = 0) and (HighDigit[CurrentCalcMode] = 9) then begin
  1020.         if not MinusPending then
  1021.           NumString2 := '-'+NumString2;
  1022.         MinusPending := True;
  1023.       end
  1024.       else
  1025.         DoCalc(Subtract);
  1026.   end;
  1027.  
  1028.   procedure DoPlus;
  1029.     {-Handle entry of '+'}
  1030.   begin
  1031.     if (ExponentState = DoingExp) then begin
  1032.       if (Exponent = 0) and not ExponentSign then
  1033.         if AppendChar('+') then
  1034.           ExponentSign := True;
  1035.     end
  1036.     else
  1037.       DoCalc(Add);
  1038.   end;
  1039.  
  1040.   procedure DoDecimalPoint;
  1041.     {-Handle entry of '.'}
  1042.   begin
  1043.     if (not BcdMode) or HaveDecimalPoint or (ExponentState <> NoExp) then
  1044.       Exit;
  1045.     if AppendChar('.') then
  1046.       HaveDecimalPoint := True;
  1047.   end;
  1048.  
  1049.   procedure DoBackSpace;
  1050.     {-Handle entry of ^H (BkSp)}
  1051.   var
  1052.     Ch : Char;
  1053.   begin
  1054.     if (NumString2 = ZeroString) or (Status = Done) then
  1055.       Exit;
  1056.     if (Length(NumString2) = 1) then begin
  1057.       NumString2 := ZeroString;
  1058.       Long2 := 0;
  1059.     end
  1060.     else
  1061.       if (Length(NumString2) = 2) and (NumString2[1] = '-') then begin
  1062.         if NumString2 = MinusZero then begin
  1063.           NumString2 := ZeroString;
  1064.           MinusPending := False;
  1065.         end
  1066.         else
  1067.           NumString2 := MinusZero;
  1068.         Long2 := 0;
  1069.       end
  1070.       else begin
  1071.         Ch := NumString2[Length(NumString2)];
  1072.         Dec(NumString2[0]);
  1073.         if not BcdMode then
  1074.           case Ch of
  1075.             '0'..'9' : Long2 := Long2 div Base[CurrentCalcMode];
  1076.           end;
  1077.       end;
  1078.     ResetDigitCount;
  1079.   end;
  1080.  
  1081.   procedure Calculator;
  1082.     {-Main program loop}
  1083.   var
  1084.     ChWord : Word;
  1085.     Ch : Char absolute ChWord;
  1086.   begin
  1087.     {initialize screen stuff}
  1088.     HiddenCursor;
  1089.     TextAttr := Bright;
  1090.  
  1091.     {draw initial screen}
  1092.     Window(LeftCol, TopRow, RtCol, BotRow);
  1093.     ClrScr;
  1094.     DrawScreen;
  1095.     ShowMode;
  1096.  
  1097.     {loop until Escape key pressed}
  1098.     repeat
  1099.       {update the screen}
  1100.       UpdateDisplay;
  1101.  
  1102.       {get the next key}
  1103.       ChWord := ReadKeyWord;
  1104.       Ch := Upcase(Ch);
  1105.       if (Ch = #0) then
  1106.         case Hi(ChWord) of
  1107.           {map function keys to hex digits like SideKick}
  1108.           F5 :               {F5 -> $A}
  1109.             AddDigit($A);
  1110.           F6 :               {F6 -> $B}
  1111.             AddDigit($B);
  1112.           F7 :               {F7 -> $C}
  1113.             AddDigit($C);
  1114.           F8 :               {F8 -> $D}
  1115.             AddDigit($D);
  1116.           F9 :               {F9 -> $E}
  1117.             AddDigit($E);
  1118.           F10 :              {F10 -> $F}
  1119.             AddDigit($F);
  1120.  
  1121.           DisableCode :      {AltU}
  1122.             {must be pressed twice in succession}
  1123.             if Hi(ReadKeyWord) = DisableCode then begin
  1124.               Ch := Esc;
  1125.               DisableOurselves := True;
  1126.             end;
  1127.         end
  1128.       else
  1129.         case Ch of
  1130.           {arithmetic operators}
  1131.           '*' : DoCalc(Multiply);
  1132.           '/' : DoCalc(Divide);
  1133.  
  1134.           {'+' and '-' are special cases}
  1135.           '+' : DoPlus;
  1136.           '-' : DoMinus;
  1137.  
  1138.           {normal digits}
  1139.           '0'..'9' :
  1140.             AddDigit(Ord(Ch) and $0F);
  1141.  
  1142.           {calculation modes}
  1143.           'B' :              {Binary}
  1144.             SwitchMode(Binary);
  1145.           'D' :              {Decimal}
  1146.             SwitchMode(Decimal);
  1147.           'F' :              {Floating point}
  1148.             SwitchMode(FloatPt);
  1149.           'H' :              {Hexadecimal}
  1150.             SwitchMode(Hexadecimal);
  1151.           'P' :              {exPonent (floating point)}
  1152.             SwitchMode(Exponential);
  1153.  
  1154.           {arithmetic/logical operations}
  1155.           'A' :              {And}
  1156.             DoCalc(AndOp);
  1157.           'L' :              {shL}
  1158.             DoCalc(ShlOp);
  1159.           'M' :              {Mod}
  1160.             DoCalc(ModOp);
  1161.           'N' :              {Not}
  1162.             DoCalc(NotOp);
  1163.           'O' :              {Or}
  1164.             DoCalc(OrOp);
  1165.           'R' :              {shR}
  1166.             DoCalc(ShrOp);
  1167.           'X' :              {Xor}
  1168.             DoCalc(XorOp);
  1169.  
  1170.           {commands}
  1171.           'C' :              {Clear all}
  1172.             ClearAll;
  1173.           'E' :              {clear Entry (or exponential 'E')}
  1174.             if BcdMode and (ExponentState = NoExp) and (Status <> Done) then
  1175.               StartExponent
  1176.             else
  1177.               ClearEntry;
  1178.           'I' :              {Insert saved value}
  1179.             InsertSavedValue;
  1180.           'K' :              {assign value to Key}
  1181.             MakeMacro;
  1182.           'S' :              {Save current value}
  1183.             SaveValue;
  1184.  
  1185.           {treat ^A-^F as hexadecimal digits}
  1186.           ^A : AddDigit($A);
  1187.           ^B : AddDigit($B);
  1188.           ^C : AddDigit($C);
  1189.           ^D : AddDigit($D);
  1190.           ^E : AddDigit($E);
  1191.           ^F : AddDigit($F);
  1192.  
  1193.           {other}
  1194.           '.' :              {decimal point}
  1195.             DoDecimalPoint;
  1196.           ^H :               {backspace}
  1197.             DoBackSpace;
  1198.           ^M, '=' :          {equals}
  1199.             FinishCalc;
  1200.         end;
  1201.     until (Ch = Esc);        {Escape}
  1202.  
  1203.     {check to see if we're disabling the TSR}
  1204.     if DisableOurselves then
  1205.       if not DisableTSR then begin
  1206.         {no go, exit but stay resident and active}
  1207.         DisableOurselves := False;
  1208.         Write(^G);
  1209.       end;
  1210.   end;
  1211.  
  1212.   procedure Div0Int(BP : Word); interrupt;
  1213.     {-Traps INT 0 for divide by zero and BCD math errors}
  1214.   begin
  1215.     MathError := True;
  1216.   end;
  1217.  
  1218.   {$F+}
  1219.   procedure PopupEntryPoint(var Regs : Registers);
  1220.     {-This is the entry point for the popup}
  1221.     {$IFNDEF KB5151}
  1222.   const
  1223.     NumLockBit = $20;
  1224.     {$ENDIF}
  1225.   var
  1226.     {$IFNDEF KB5151}
  1227.     KeyboardFlags : Byte absolute $40 : $17;
  1228.     SaveNumLock : Boolean;
  1229.     {$ENDIF}
  1230.     SaveXY, SaveSL : Word;   {for saving cursor position and shape}
  1231.   begin
  1232.     {reinitialize CRT}
  1233.     ReinitCrt;
  1234.  
  1235.     {don't pop up if not in 80-column text mode}
  1236.     if InTextMode and (ScreenWidth = 80) then begin
  1237.       {initialize screen stuff}
  1238.       SetAttributes;
  1239.       GetCursorState(SaveXY, SaveSL);
  1240.       ScreenPtr := Ptr(VideoSegment, 0);
  1241.       if SaveWindow(LeftCol, TopRow, RtCol, BotRow, False, OurScreenPtr) then
  1242.         {won't fail -- no memory being allocated} ;
  1243.  
  1244.       {$IFNDEF KB5151}
  1245.       {save NumLock state and force it on}
  1246.       SaveNumLock := (KeyboardFlags and NumLockBit) <> 0;
  1247.       KeyboardFlags := KeyboardFlags or NumLockBit;
  1248.       {$ENDIF}
  1249.  
  1250.       {trap INT 0 and call the calculator routine}
  1251.       if InitVector(0, Div0Handle, @Div0Int) then
  1252.         Calculator;
  1253.  
  1254.       {restore previous INT 0 handler}
  1255.       RestoreVector(Div0Handle);
  1256.  
  1257.       {$IFNDEF KB5151}
  1258.       {restore previous NumLock state}
  1259.       if SaveNumLock then
  1260.         KeyboardFlags := KeyboardFlags or NumLockBit
  1261.       else
  1262.         KeyboardFlags := KeyboardFlags and (not NumLockBit);
  1263.       {$ENDIF}
  1264.  
  1265.       {restore cursor and screen}
  1266.       RestoreCursorState(SaveXY, SaveSL);
  1267.       RestoreWindow(LeftCol, TopRow, RtCol, BotRow, False, OurScreenPtr);
  1268.     end
  1269.     else
  1270.       Beep;
  1271.   end;
  1272.   {$F-}
  1273.  
  1274.   procedure Abort(Message : string);
  1275.     {-Display message and Halt}
  1276.   begin
  1277.     WriteLn(Message);
  1278.     Halt(1);
  1279.   end;
  1280.  
  1281.   procedure InitPCalc;
  1282.     {-Basic initialization stuff}
  1283.   begin
  1284.     OurScreenPtr := @OurScreenBuffer;
  1285.     CurrentCalcMode := DefaultMode;
  1286.     BcdMode := (CurrentCalcMode >= FloatPt);
  1287.     MathError := False;
  1288.     DivideByZero := False;
  1289.     SaveLong := 0;
  1290.     SaveBcd := ZeroBCD;
  1291.     SaveString := ZeroString;
  1292.     ClearAll;
  1293.   end;
  1294.  
  1295. begin
  1296.   {signon message}
  1297.   HighVideo;
  1298.   WriteLn(ProgName, ^M^J, Copyright, ^M^J);
  1299.   LowVideo;
  1300.  
  1301.   {initialize}
  1302.   InitPCalc;
  1303.  
  1304.   {check to see if SideKick is loaded}
  1305.   if SideKickLoaded then
  1306.     Abort('Can''t be loaded after SideKick!');
  1307.  
  1308.   {check to see if we're already installed}
  1309.   if ModuleInstalled(ModuleName) then
  1310.     Abort('PCALC is already loaded. Aborting...');
  1311.  
  1312.   {install the module}
  1313.   InstallModule(ModuleName, nil);
  1314.  
  1315.   {go resident}
  1316.   if DefinePop(OurHotKey, @PopupEntryPoint, Ptr(SSeg, SPtr), True) then begin
  1317.     WriteLn('PCALC loaded, press Ctrl-RightShift-C to activate.');
  1318.  
  1319.     {Enable popups}
  1320.     PopupsOn;
  1321.  
  1322.     {$IFDEF Ver40}
  1323.     {restore INT $1B, captured by TPCRT}
  1324.     SetIntVec($1B, SaveInt1B);
  1325.     {$ENDIF}
  1326.  
  1327.     {terminate and stay resident}
  1328.     if not TerminateAndStayResident(ParagraphsToKeep, 0) then {} ;
  1329.   end;
  1330.  
  1331.   {if we get here we failed}
  1332.   Abort(LoadError);
  1333. end.
  1334.  
  1335.