home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / pmos2002.zip / SRC / CALCULAT.MOD next >
Text File  |  1998-01-26  |  38KB  |  1,051 lines

  1. IMPLEMENTATION MODULE Calculator;
  2.  
  3.         (********************************************************)
  4.         (*                                                      *)
  5.         (*      Simple calculator (Algebraic notation version)  *)
  6.         (*                                                      *)
  7.         (*  Programmer:         P. Moylan                       *)
  8.         (*  Last edited:        26 January 1998                 *)
  9.         (*  Status:             Working                         *)
  10.         (*                                                      *)
  11.         (********************************************************)
  12.  
  13. FROM Keyboard IMPORT
  14.     (* proc *)  InKey, PutBack, StuffKeyboardBuffer, LockStatus, SetLocks;
  15.  
  16. FROM KBdriver IMPORT
  17.     (* const*)  NumLockLED;
  18.  
  19. FROM SoundEffects IMPORT
  20.     (* proc *)  Beep;
  21.  
  22. FROM TextLines IMPORT
  23.     (* type *)  LineType,
  24.     (* proc *)  Box;
  25.  
  26. FROM Windows IMPORT
  27.     (* type *)  Window, Colour, FrameType, DividerType, Rectangle,
  28.                 RowRange, ColumnRange,
  29.     (* proc *)  OpenWindow, OpenWindowHidden, CloseWindow, ChangeScrollingRegion,
  30.                 WriteChar, WriteString, WriteLn, SetCursor,
  31.                 ShiftWindowRel, WindowLocation, Hide, PutOnTop,
  32.                 IdentifyTopWindow, CurrentPage;
  33.  
  34. FROM Mouse IMPORT
  35.     (* type *)  Buttons, ButtonSet,
  36.     (* proc *)  HideMouseCursor, ShowMouseCursor;
  37.  
  38. FROM UserInterface IMPORT
  39.     (* type *)  UIWindow, Capability, CapabilitySet,
  40.     (* proc *)  AllowMouseControl, AddActiveRegion;
  41.  
  42. FROM Menus IMPORT
  43.     (* type *)  Menu, ItemText,
  44.     (* proc *)  CreateMenu, PositionMenu, SelectFromMenu, DestroyMenu;
  45.  
  46. FROM RealIO IMPORT
  47.     (* proc *)  WriteLongReal;
  48.  
  49. FROM Strings IMPORT
  50.     (* proc *)  Assign;
  51.  
  52. FROM LowLevel IMPORT
  53.     (* proc *)  IAND;
  54.  
  55. FROM MATHLIB IMPORT
  56.     (* proc *)  Pow, Sqrt, Exp, Log, Log10, Sin, Cos,
  57.                 Tan, ASin, ACos, ATan, SinH, CosH, TanH;
  58.  
  59. (************************************************************************)
  60. (*                  MISCELLANEOUS GLOBAL DEFINITIONS                    *)
  61. (************************************************************************)
  62.  
  63. TYPE CharSet = SET OF CHAR;
  64.  
  65. CONST
  66.     EmptyMarker = CHR(0);  EndMarker = ' ';
  67.     Enter = CHR(0DH);  Esc = CHR(01BH);
  68.     UnaryOperatorSet = CharSet {'%', ')', 's', 'S'};
  69.     UnknownOperatorPriority = 255;
  70.     numberstart = 8;    (* the x cursor position for displaying numbers *)
  71.     numberwidth = 9;    (* The field size for displaying numbers        *)
  72.  
  73. VAR calc, help: Window;
  74.     baserow, basecol, helprow, helpcol: CARDINAL;
  75.  
  76. (************************************************************************)
  77. (*                      THE CALCULATOR STATE                            *)
  78. (************************************************************************)
  79.  
  80. CONST MaxRegisterNumber = 6;
  81.       MaxMemoryNumber = 3;
  82.       DisplayedRegisters = 3;
  83.  
  84. TYPE RegisterNumber = [0..MaxRegisterNumber];
  85.      MemoryNumber = [0..MaxMemoryNumber];
  86.  
  87. VAR
  88.     (* Array "Register" is a combined operand and operator stack.  It   *)
  89.     (* would be more conventional to have separate stacks for the       *)
  90.     (* operators and operands, but we adopt this slightly unusual stack *)
  91.     (* format because it makes it easier to maintain a user-friendly    *)
  92.     (* screen display.                                                  *)
  93.  
  94.     Register: ARRAY RegisterNumber OF
  95.                 RECORD
  96.                     operator: CHAR;
  97.                     ParenCount: CARDINAL;
  98.                     value: LONGREAL;
  99.                 END (*RECORD*);
  100.  
  101.     (* NumberPresent = FALSE means that Register[0].value is displayed  *)
  102.     (* as space characters rather than as a numeric string.             *)
  103.  
  104.     NumberPresent: BOOLEAN;
  105.  
  106.     (* In addition to the stack, there is a set of "memory" registers   *)
  107.     (* in which the user can save calculation results.                  *)
  108.  
  109.     MemoryValue: ARRAY MemoryNumber OF LONGREAL;
  110.  
  111. (************************************************************************)
  112. (*                      THE PREFIX UNARY FUNCTIONS                      *)
  113. (************************************************************************)
  114.  
  115. (* To simplify the calculator logic, and to keep the display readable,  *)
  116. (* all prefix unary functions are stored in the calculator stack in     *)
  117. (* terms of the all-purpose binary function "f".  The first argument    *)
  118. (* of f is the function number, and the second argument of f is the     *)
  119. (* true argument of the original unary function.                        *)
  120.  
  121. CONST
  122.     MaxFunctionNumber = 13;     (* number of built-in functions         *)
  123.     functionnamewidth = 5;      (* # of characters in a function name   *)
  124.  
  125. TYPE
  126.     FunctionType = [0..MaxFunctionNumber];
  127.     NameText = ARRAY [0..functionnamewidth-1] OF CHAR;
  128.     NameArray = ARRAY FunctionType OF NameText;
  129.     MathProc = PROCEDURE (LONGREAL): LONGREAL;
  130.     FunctionArray = ARRAY FunctionType OF MathProc;
  131.  
  132. PROCEDURE Negative (x: LONGREAL): LONGREAL;
  133.     BEGIN RETURN -x; END Negative;
  134.  
  135. CONST
  136.     (* Array FunctionName gives the function names as displayed.        *)
  137.  
  138.     FunctionName = NameArray {'     ', 'SQRT ', 'EXP  ', 'LN   ', 'LOG10',
  139.                                 'SIN  ', 'COS  ', 'TAN  ', 'ASIN ', 'ACOS ',
  140.                                 'ATAN ', 'SINH ', 'COSH ', 'TANH '};
  141.  
  142.     (* Array Function is the set of built-in functions. *)
  143.  
  144.     CONST Function = FunctionArray {Negative, Sqrt, Exp, Log, Log10,
  145.                         Sin, Cos, Tan, ASin, ACos, ATan, SinH, CosH, TanH};
  146.  
  147. (************************************************************************)
  148. (*                          DISPLAY ROUTINES                            *)
  149. (************************************************************************)
  150.  
  151. PROCEDURE MoveWindow (code: CHAR;  w: Window);
  152.  
  153.     (* Parameter "code" is the second character of a function key       *)
  154.     (* sequence.  This procedure moves window "w" on the screen if      *)
  155.     (* the key turns out to be an arrow key (and does nothing if it     *)
  156.     (* isn't).                                                          *)
  157.  
  158.     BEGIN
  159.         IF code = "H" THEN      (* cursor up *)
  160.             ShiftWindowRel (w, -1, 0);
  161.         ELSIF code = "P" THEN   (* cursor down *)
  162.             ShiftWindowRel (w, 1, 0);
  163.         ELSIF code = "M" THEN   (* cursor right *)
  164.             ShiftWindowRel (w, 0, 1);
  165.         ELSIF code = "K" THEN   (* cursor left *)
  166.             ShiftWindowRel (w, 0, -1)
  167.         END (*IF*);
  168.     END MoveWindow;
  169.  
  170. (************************************************************************)
  171.  
  172. PROCEDURE CreateHelpWindow;
  173.  
  174.     (* Puts a help message on the screen. *)
  175.  
  176.     VAR dummy: UIWindow;
  177.  
  178.     BEGIN
  179.         OpenWindowHidden (help, intensewhite, blue, helprow, helprow+10,
  180.                                 helpcol, helpcol+28, simpleframe, nodivider);
  181.  
  182.         dummy := AllowMouseControl (help, "Help for calculator",
  183.                                 CapabilitySet {wshow, wmove, whide});
  184.  
  185.         WriteString (help, "Operators: + - * / ^ % ( )");
  186.         WriteLn (help);
  187.         WriteString (help, "  S   store to memory");
  188.         WriteLn (help);
  189.         WriteString (help, "  M   load from memory");
  190.         WriteLn (help);
  191.         WriteString (help, "  F   function (from menu)");
  192.         WriteLn (help);
  193.         WriteString (help, "  P   3.14159...");
  194.         WriteLn (help);
  195.         WriteString (help, "Arrow keys: move calculator");
  196.         WriteLn (help);
  197.         WriteString (help, "Backspace: delete last");
  198.         WriteLn (help);
  199.         WriteString (help, "= or Enter: evaluate result");
  200.         WriteLn (help);
  201.         WriteString (help, "Esc: exit from calculator");
  202.  
  203.     END CreateHelpWindow;
  204.  
  205. (************************************************************************)
  206.  
  207. PROCEDURE HelpVisible(): BOOLEAN;
  208.  
  209.     (* Returns TRUE iff some part of the help window is visible on the  *)
  210.     (* screen.  We have to work this out each time, because the         *)
  211.     (* visibility of the help window can be affected by things that     *)
  212.     (* this module doesn't know about.                                  *)
  213.  
  214.     VAR R: Rectangle;  w: Window;
  215.         r, row: RowRange;  c, col: ColumnRange;
  216.  
  217.     BEGIN
  218.         R := WindowLocation (help);
  219.         row := R.top;
  220.         LOOP
  221.             col := R.left;
  222.             LOOP
  223.                 r := row;  c := col;
  224.                 IF IdentifyTopWindow(w, CurrentPage(), r, c) AND (w = help) THEN
  225.                     RETURN TRUE;
  226.                 ELSIF col = R.right THEN
  227.                     EXIT (*LOOP*);
  228.                 ELSE
  229.                     INC (col);
  230.                 END (*IF*);
  231.             END (*LOOP*);
  232.             IF row = R.bottom THEN
  233.                 RETURN FALSE;
  234.             END (*IF*);
  235.             INC (row);
  236.         END (*LOOP*);
  237.     END HelpVisible;
  238.  
  239. (************************************************************************)
  240.  
  241. PROCEDURE LiteralAccumulatorDisplay (VAR (*IN*) Buffer: ARRAY OF CHAR);
  242.  
  243.     (* Displays "Buffer" as a text string at the screen location        *)
  244.     (* reserved for the accumulator.                                    *)
  245.  
  246.     BEGIN
  247.         SetCursor (calc, DisplayedRegisters+4, numberstart);
  248.         WriteString (calc, Buffer);
  249.     END LiteralAccumulatorDisplay;
  250.  
  251. (************************************************************************)
  252.  
  253. PROCEDURE WriteSpaces (N: CARDINAL);
  254.  
  255.     (* Writes a string of N spaces at the current cursor location.      *)
  256.  
  257.     VAR k: CARDINAL;
  258.  
  259.     BEGIN
  260.         FOR k := 1 TO N DO
  261.             WriteChar (calc, " ");
  262.         END (*FOR*);
  263.     END WriteSpaces;
  264.  
  265. (************************************************************************)
  266.  
  267. PROCEDURE WriteParentheses (count: CARDINAL);
  268.  
  269.     (* Writes "count" left parentheses on the screen, with an   *)
  270.     (* abbreviated display if count > 4.                        *)
  271.  
  272.     VAR j: CARDINAL;
  273.  
  274.     BEGIN
  275.         IF count > 4 THEN
  276.             WriteString (calc, "(..(");
  277.         ELSE
  278.             FOR j := 1 TO count DO
  279.                 WriteChar (calc, "(");
  280.             END (*FOR*);
  281.             WriteSpaces (4-count);
  282.         END (*IF*);
  283.     END WriteParentheses;
  284.  
  285. (************************************************************************)
  286.  
  287. PROCEDURE DisplayRegister (j: RegisterNumber);
  288.  
  289.     (* Refreshes the display of the left parentheses, the value, and    *)
  290.     (* the trailing operator for register j.                            *)
  291.  
  292.     VAR row: CARDINAL;  operator: CHAR;  f: FunctionType;
  293.  
  294.     BEGIN
  295.         IF j = 0 THEN
  296.             row := DisplayedRegisters+4;
  297.         ELSE
  298.             row := DisplayedRegisters+3-j;
  299.         END (*IF*);
  300.         SetCursor (calc, row, 2);
  301.         IF Register[j].operator = EmptyMarker THEN
  302.             WriteSpaces (numberwidth+6);
  303.         ELSE
  304.             IF Register[j].operator = "f" THEN
  305.                 IF Register[j+1].value = 0.0 THEN
  306.                     operator := "-";
  307.                 ELSE
  308.                     operator := " ";
  309.                 END (*IF*);
  310.             ELSE
  311.                 operator := Register[j].operator;
  312.             END (*IF*);
  313.             WriteChar (calc, operator);
  314.             WriteParentheses (Register[j].ParenCount);
  315.             SetCursor (calc, row, numberstart);
  316.             IF (j > 0) AND (Register[j-1].operator = "f") THEN
  317.                 WriteSpaces (numberwidth - functionnamewidth);
  318.                 f := VAL(FunctionType,Register[j].value);
  319.                 WriteString (calc, FunctionName[f]);
  320.             ELSIF (j = 0) AND NOT NumberPresent THEN
  321.                 WriteSpaces (numberwidth);
  322.             ELSE
  323.                 WriteLongReal (calc, Register[j].value, numberwidth);
  324.             END (*IF*);
  325.         END (*IF*);
  326.     END DisplayRegister;
  327.  
  328. (************************************************************************)
  329.  
  330. PROCEDURE DisplayStack;
  331.  
  332.     (* Refreshes the display of the stack of registers. *)
  333.  
  334.     VAR j: RegisterNumber;
  335.  
  336.     BEGIN
  337.         FOR j := 0 TO DisplayedRegisters DO
  338.             DisplayRegister (j);
  339.         END (*FOR*);
  340.  
  341.         (* If stack is empty, display help message *)
  342.  
  343.         IF Register[1].operator = EmptyMarker THEN
  344.             SetCursor (calc, 3, numberwidth DIV 2 - 1);
  345.             WriteString (calc, "Press F1 key");
  346.             SetCursor (calc, 4, numberwidth DIV 2 + 1);
  347.             WriteString (calc, "for help");
  348.         END (*IF*);
  349.  
  350.     END DisplayStack;
  351.  
  352. (************************************************************************)
  353.  
  354. PROCEDURE DisplayMemory (j: MemoryNumber);
  355.  
  356.     (* Refreshes the display of "memory" register j.    *)
  357.  
  358.     BEGIN
  359.         SetCursor (calc, DisplayedRegisters+j+6, numberstart);
  360.         WriteLongReal (calc, MemoryValue[j], numberwidth);
  361.     END DisplayMemory;
  362.  
  363. (************************************************************************)
  364.  
  365. PROCEDURE InitialDisplay;
  366.  
  367.     (* Assumption: the calculator window calc is already open.  This    *)
  368.     (* procedure puts the initial picture of the calculator onto the    *)
  369.     (* screen.                                                          *)
  370.  
  371.     VAR mem: MemoryNumber;
  372.  
  373.     BEGIN
  374.         (* Draw a box for the accumulator.      *)
  375.  
  376.         Box (calc, DisplayedRegisters+3, numberstart-1,
  377.                                         numberwidth+1, 2, single);
  378.  
  379.         (* Display the register contents.       *)
  380.  
  381.         DisplayStack;
  382.  
  383.         (* Display the memory values.   *)
  384.  
  385.         FOR mem := 0 TO MAX(MemoryNumber) DO
  386.             SetCursor (calc, DisplayedRegisters+mem+6, 2);
  387.             WriteChar (calc, "M");  WriteChar (calc, CHR(ORD("0")+mem));
  388.             DisplayMemory (mem);
  389.         END (*FOR*);
  390.  
  391.     END InitialDisplay;
  392.  
  393. (************************************************************************)
  394. (*                          NUMERIC INPUT                               *)
  395. (************************************************************************)
  396.  
  397. PROCEDURE AcceptNumber (nextchar: CHAR);
  398.  
  399.     (* Reads a number from the keyboard.  On entry, nextchar holds the  *)
  400.     (* first digit or the decimal point.  On exit, the input value is   *)
  401.     (* in Register[0].value.                                            *)
  402.  
  403.     TYPE BufferSubscript = [1..numberwidth];
  404.  
  405.     VAR placevalue: LONGREAL;
  406.         j: BufferSubscript;
  407.         Buffer: ARRAY BufferSubscript OF CHAR;
  408.         BufferFull: BOOLEAN;
  409.  
  410.     (********************************************************************)
  411.  
  412.     PROCEDURE GetNextChar;
  413.  
  414.         (* Displays the input so far (as a text string if it will fit,  *)
  415.         (* otherwise by a call to WriteReal), and then reads nextchar.  *)
  416.  
  417.         VAR j: BufferSubscript;
  418.  
  419.         BEGIN
  420.             IF BufferFull THEN
  421.                 DisplayRegister(0);
  422.             ELSE
  423.                 LiteralAccumulatorDisplay (Buffer);
  424.             END (*IF*);
  425.             nextchar := InKey();
  426.             IF NOT BufferFull THEN
  427.                 IF Buffer[1] <> " " THEN
  428.                     BufferFull := TRUE;
  429.                 ELSE
  430.                     FOR j := 1 TO numberwidth-1 DO
  431.                         Buffer[j] := Buffer[j+1];
  432.                     END (*FOR*);
  433.                     Buffer[numberwidth] := nextchar;
  434.                 END (*IF*);
  435.             END (*IF*);
  436.         END GetNextChar;
  437.  
  438.     (********************************************************************)
  439.  
  440.     BEGIN
  441.         Register[0].value := 0.0;  BufferFull := FALSE;
  442.         FOR j := 1 TO numberwidth-1 DO
  443.             Buffer[j] := " ";
  444.         END (*FOR*);
  445.         NumberPresent := TRUE;
  446.         Buffer[numberwidth] := nextchar;
  447.  
  448.         (* Read the part before the decimal point.      *)
  449.  
  450.         WITH Register[0] DO
  451.             WHILE nextchar IN CharSet {"0".."9"} DO
  452.                 value := 10.0*value + VAL(LONGREAL,ORD(nextchar) - ORD("0"));
  453.                 GetNextChar;
  454.             END (*WHILE*);
  455.         END (*WITH*);
  456.  
  457.         (* Now the part after the decimal point, if any.        *)
  458.  
  459.         IF nextchar = "." THEN
  460.             GetNextChar;  placevalue := 0.1;
  461.             WHILE nextchar IN CharSet {"0".."9"} DO
  462.                 Register[0].value := Register[0].value
  463.                         + placevalue*VAL(LONGREAL,ORD(nextchar) - ORD("0"));
  464.                 placevalue := 0.1*placevalue;
  465.                 GetNextChar;
  466.             END (*WHILE*);
  467.         END (*IF*);
  468.  
  469.         (* Correct for overshoot in input.      *)
  470.  
  471.         PutBack (nextchar);
  472.  
  473.     END AcceptNumber;
  474.  
  475. (************************************************************************)
  476.  
  477. PROCEDURE priority (operator: CHAR): CARDINAL;
  478.  
  479.     (* Returns the priority of an operator.     *)
  480.  
  481.     BEGIN
  482.         CASE operator OF
  483.                 EndMarker:      RETURN 0;
  484.             |
  485.                 Enter,"=":      RETURN 1;
  486.             |
  487.                 "+","-":        RETURN 2;
  488.             |
  489.                 "*","/":        RETURN 3;
  490.             |
  491.                 "f":            IF Register[1].value = 0.0 THEN RETURN 7
  492.                                 ELSE RETURN 4;
  493.                                 END (*IF*);
  494.             |
  495.                 "x":            RETURN 5;
  496.             |
  497.                 "^":            RETURN 6;
  498.             |
  499.                 ELSE
  500.                                 RETURN UnknownOperatorPriority;
  501.         END (*CASE*);
  502.     END priority;
  503.  
  504. (************************************************************************)
  505.  
  506. PROCEDURE TopOperatorPriority(): CARDINAL;
  507.  
  508.     (* TopOperatorPriority is normally the priority of the operator in  *)
  509.     (* Register[0].  However any left parenthesis in Register[0]        *)
  510.     (* overrides this; in that case we return an answer of 0.           *)
  511.  
  512.     BEGIN
  513.         IF Register[0].ParenCount > 0 THEN RETURN 0
  514.         ELSE RETURN priority (Register[0].operator)
  515.         END (*IF*);
  516.     END TopOperatorPriority;
  517.  
  518. (************************************************************************)
  519. (*                          STACK MANIPULATION                          *)
  520. (************************************************************************)
  521.  
  522. PROCEDURE PushStack (LatestOperator: CHAR);
  523.  
  524.     (* Pushes the register stack, clearing the top one.  The argument   *)
  525.     (* ends up as the operator in Register[0].  If the stack overflows  *)
  526.     (* we give an audible alarm, but perform the push anyway.           *)
  527.  
  528.     VAR j: RegisterNumber;
  529.  
  530.     BEGIN
  531.         IF Register[MaxRegisterNumber].operator <> EmptyMarker THEN
  532.             Register[MaxRegisterNumber-1].operator := EndMarker;
  533.             (*Beep;*)
  534.         END (*IF*);
  535.         FOR j := MaxRegisterNumber TO 1 BY -1 DO
  536.             Register[j] := Register[j-1];
  537.         END (*FOR*);
  538.         WITH Register[0] DO
  539.             operator := LatestOperator;  value := 0.0;  ParenCount := 0;
  540.         END (*WITH*);
  541.         NumberPresent := FALSE;
  542.         DisplayStack;
  543.     END PushStack;
  544.  
  545. (************************************************************************)
  546.  
  547. PROCEDURE PopStack;
  548.  
  549.     (* Pops the register stack, clearing the bottom register.   *)
  550.  
  551.     VAR j: RegisterNumber;
  552.  
  553.     BEGIN
  554.         FOR j := 0 TO MaxRegisterNumber-1 DO
  555.             Register[j] := Register[j+1];
  556.         END (*FOR*);
  557.         WITH Register[MaxRegisterNumber] DO
  558.             ParenCount := 0;  value := 0.0;  operator := EmptyMarker;
  559.         END (*WITH*);
  560.         DisplayStack;
  561.     END PopStack;
  562.  
  563. (************************************************************************)
  564. (*                      OPERATIONS ON THE MEMORIES                      *)
  565. (************************************************************************)
  566.  
  567. PROCEDURE GetMemoryNumber (): MemoryNumber;
  568.  
  569.     (* Returns the value of a one-digit memory number typed from the    *)
  570.     (* keyboard.  Also wipes the "memory number" prompt on the display. *)
  571.     (* Assumes memory number 0 (and does not consume the typed key) if  *)
  572.     (* no valid memory number is specified.                             *)
  573.  
  574.     VAR ch: CHAR;
  575.  
  576.     BEGIN
  577.         ch := InKey();
  578.         SetCursor (calc, DisplayedRegisters+5, 3);
  579.         WriteString (calc, " ");
  580.         IF ch IN CharSet{"0"..CHR(ORD("0")+MaxMemoryNumber)} THEN
  581.             RETURN ORD(ch) - ORD("0");
  582.         ELSE
  583.             PutBack(ch);  RETURN 0;
  584.         END (*IF*);
  585.     END GetMemoryNumber;
  586.  
  587. (************************************************************************)
  588.  
  589. PROCEDURE StoreToMemory;
  590.  
  591.     (* Gets a memory number from the keyboard, stores the accumulator   *)
  592.     (* value in that memory register.                                   *)
  593.  
  594.     VAR mem: MemoryNumber;
  595.  
  596.     BEGIN
  597.         SetCursor (calc, DisplayedRegisters+5, 3);
  598.         WriteChar (calc, CHR(25));
  599.         mem := GetMemoryNumber();
  600.         MemoryValue[mem] := Register[0].value;  DisplayMemory(mem);
  601.     END StoreToMemory;
  602.  
  603. (************************************************************************)
  604. (*                              OPERATIONS                              *)
  605. (************************************************************************)
  606.  
  607. PROCEDURE Divide0 (first, second: LONGREAL): LONGREAL;
  608.  
  609.     (* Computes first/second, except that division by zero gives 0.0.   *)
  610.  
  611.     BEGIN
  612.         IF second = 0.0 THEN RETURN 0.0
  613.         ELSE RETURN first/second;
  614.         END (*IF*);
  615.     END Divide0;
  616.  
  617. (************************************************************************)
  618.  
  619. PROCEDURE BinaryOperation;
  620.  
  621.     (* Performs the binary operation requested by Register[0].operator. *)
  622.  
  623.     VAR x, y, result: LONGREAL;
  624.         command: CHAR;  f: FunctionType;
  625.  
  626.     BEGIN
  627.         command := Register[0].operator;
  628.         x := Register[1].value;  y := Register[0].value;
  629.         result := x;
  630.         IF command = "+" THEN result := result + y
  631.         ELSIF command = "-" THEN result := result - y
  632.         ELSIF (command = "*") OR (command = "x") THEN result := result * y
  633.         ELSIF command = "/" THEN result := Divide0 (x, y)
  634.         ELSIF command = "^" THEN result := Pow (x, y)
  635.         ELSIF command = "f" THEN
  636.             f := VAL(FunctionType,x);
  637.             result := Function[f] (y);
  638.         ELSE Beep;
  639.         END (*IF*);
  640.         Register[1].value := result;
  641.         PopStack;
  642.     EXCEPT
  643.         Beep;  Beep;  Beep;
  644.         Register[1].value := 0.0;
  645.         PopStack;
  646.         RETURN;
  647.     END BinaryOperation;
  648.  
  649. (************************************************************************)
  650.  
  651. PROCEDURE PostfixUnaryOperation (code: CHAR);
  652.  
  653.     (* Performs the unary operation requested by code.  *)
  654.  
  655.     BEGIN
  656.         IF code = "%" THEN
  657.             Register[0].value := 0.01*Register[0].value*Register[1].value;
  658.         ELSIF code = ")" THEN
  659.             IF Register[0].ParenCount > 0 THEN
  660.                 DEC (Register[0].ParenCount);
  661.             ELSIF Register[0].operator <> EndMarker THEN
  662.                 BinaryOperation;  PutBack (")");
  663.             ELSE
  664.                 (*Beep*);
  665.             END (*IF*);
  666.         ELSIF (code="s") OR (code="S") THEN
  667.             StoreToMemory;
  668.         ELSE
  669.             Beep;
  670.         END (*IF*);
  671.         DisplayRegister(0);
  672.     END PostfixUnaryOperation;
  673.  
  674. (************************************************************************)
  675. (*              GETTING A FUNCTION NAME BY MENU SELECTION               *)
  676. (************************************************************************)
  677.  
  678. PROCEDURE ReadBuiltinFunctionName;
  679.  
  680.     (* Allows the user to select a function name from a menu.  We then  *)
  681.     (* load the stack with the function number, and the special         *)
  682.     (* "binary operator" f.                                             *)
  683.  
  684.     VAR funcmenu: Menu;  menutext: ARRAY FunctionType OF ItemText;
  685.         function: FunctionType;
  686.  
  687.     BEGIN
  688.         menutext[0] := "    Function";
  689.         FOR function := 1 TO MaxFunctionNumber DO
  690.             Assign (FunctionName[function], menutext[function]);
  691.         END (*FOR*);
  692.         CreateMenu (funcmenu, 3, menutext, MaxFunctionNumber);
  693.         PositionMenu (funcmenu, 14, 22, 60, 78);
  694.         function := SelectFromMenu (funcmenu);
  695.         DestroyMenu (funcmenu);
  696.         IF function <> 0 THEN
  697.             Register[0].value := VAL(LONGREAL, function);
  698.             PushStack ("f");
  699.         END (*IF*);
  700.     END ReadBuiltinFunctionName;
  701.  
  702. (************************************************************************)
  703. (*                      THE CALCULATOR CONTROL LOGIC                    *)
  704. (************************************************************************)
  705.  
  706. PROCEDURE HandleFunctionKey;
  707.  
  708.     (* Looks after the cases where the keyboard input code was CHR(0).  *)
  709.     (* In the present version, the arrow keys and F1 key are looked     *)
  710.     (* after, and all other function keys are ignored.                  *)
  711.  
  712.     VAR code: CHAR;
  713.  
  714.     BEGIN
  715.         code := InKey();
  716.         IF (code = ";") OR (code = "T") THEN    (* F1 or Shift/F1 *)
  717.             PutOnTop (help);
  718.         ELSIF HelpVisible() THEN                (* check for arrow key *)
  719.             MoveWindow (code, help);
  720.         ELSE                                    (* check for arrow key *)
  721.             MoveWindow (code, calc);
  722.         END (*IF*);
  723.     END HandleFunctionKey;
  724.  
  725. (************************************************************************)
  726.  
  727. PROCEDURE LoadAccumulator (VAR (*OUT*) nextchar: CHAR);
  728.  
  729.     (* Loads the accumulator with a number, also accepting and keeping  *)
  730.     (* track of any opening parentheses.  Unary operations are also     *)
  731.     (* dealt with by this procedure; and this could lead to the         *)
  732.     (* evaluation of entire subexpressions, because we treat a closing  *)
  733.     (* parenthesis as a unary postfix operator.  On return, nextchar    *)
  734.     (* holds the following keyboard character (usually an operator, but *)
  735.     (* it could also be Esc, Return, or an illegal keystroke).  Most of *)
  736.     (* the complexity of this procedure lies in the fact that the user  *)
  737.     (* can also type Backspace at any time, which has the effect of     *)
  738.     (* cancelling the latest number, left parenthesis, or unevaluated   *)
  739.     (* operator, as appropriate.                                        *)
  740.  
  741.     (* It is possible that the user will enter no value before the      *)
  742.     (* operator.  In this case, the previous accumulator contents are   *)
  743.     (* retained, unless they have been wiped out by a backspace.        *)
  744.     (* Conversely, the user can override a number which is already      *)
  745.     (* present.  We try to give a legal meaning, wherever possible, to  *)
  746.     (* any user input.                                                  *)
  747.  
  748.     CONST Backspace = CHR(8);
  749.           Starters = CharSet {"(", ".", "0".."9", "f", "F", "m", "M",
  750.                                 "p", "P"};
  751.           Misc = CharSet {CHR(0), "e", "E", EndMarker, Backspace};
  752.           HandledHere = Starters + Misc + UnaryOperatorSet;
  753.  
  754.     BEGIN
  755.         LOOP
  756.             nextchar := InKey();
  757.  
  758.             (* If the input is such as to imply that a new number is    *)
  759.             (* to be entered, discard any number already in the         *)
  760.             (* accumulator - i.e. allow the user to override any        *)
  761.             (* previous input.                                          *)
  762.  
  763.             IF nextchar IN Starters THEN
  764.                 NumberPresent := FALSE;
  765.                 DisplayRegister(0);
  766.             END (*IF*);
  767.  
  768.             (* On seeing a "-", we have to decide whether it is a       *)
  769.             (* unary minus (if so, handle it here) or a binary minus.   *)
  770.  
  771.             IF nextchar = "-" THEN
  772.                 IF NumberPresent THEN EXIT(*LOOP*)
  773.                 ELSE
  774.                     Register[0].value := 0.0;
  775.                     PushStack ("f");
  776.                 END (*IF*);
  777.  
  778.             ELSIF NOT (nextchar IN HandledHere) THEN
  779.                 EXIT (*LOOP*);
  780.  
  781.             (* Any character which, by coincidence, has the     *)
  782.             (* same character code as EndMarker is ignored.     *)
  783.  
  784.             ELSIF nextchar = EndMarker THEN (* do nothing *)
  785.  
  786.             (* Function key? *)
  787.  
  788.             ELSIF nextchar = CHR(0) THEN HandleFunctionKey
  789.  
  790.             (* Read prefix unary operator.  We don't evaluate it here;  *)
  791.             (* it's put on the stack to look like a binary operator.    *)
  792.  
  793.             ELSIF CAP(nextchar) = "F" THEN ReadBuiltinFunctionName;
  794.  
  795.             (* Handle postfix unary operator.   *)
  796.  
  797.             ELSIF nextchar IN UnaryOperatorSet THEN
  798.                 PostfixUnaryOperation (nextchar);
  799.                 NumberPresent := TRUE;
  800.  
  801.             (* Handle opening parenthesis.      *)
  802.  
  803.             ELSIF nextchar = "(" THEN
  804.                 INC (Register[0].ParenCount);
  805.                 DisplayRegister(0);
  806.  
  807.             (* P means the constant PI. *)
  808.  
  809.             ELSIF CAP(nextchar) = "P" THEN
  810.                 Register[0].value := 3.14159265359;
  811.                 NumberPresent := TRUE;
  812.                 DisplayRegister(0);
  813.  
  814.             (* Fetch a number.  *)
  815.  
  816.             ELSIF nextchar IN CharSet {"0".."9", "."} THEN
  817.                 AcceptNumber (nextchar);
  818.  
  819.             (* We use the calculator itself to evaluate "E" notation.   *)
  820.  
  821.             ELSIF CAP(nextchar) = "E" THEN
  822.                 IF NOT NumberPresent THEN
  823.                     Register[0].value := 1.0;
  824.                     NumberPresent := TRUE;
  825.                 END (*IF*);
  826.                 PushStack ("x");
  827.                 Register[0].value := 10.0;
  828.                 PutBack ("^");
  829.  
  830.             (* Or an operand from memory.       *)
  831.  
  832.             ELSIF CAP(nextchar) ="M" THEN
  833.                 SetCursor (calc, DisplayedRegisters+5, 3);
  834.                 WriteChar (calc, CHR(24));
  835.                 Register[0].value := MemoryValue[GetMemoryNumber()];
  836.                 NumberPresent := TRUE;
  837.                 DisplayRegister(0);
  838.  
  839.             (* Now the hard part: handle Backspace.     *)
  840.  
  841.             ELSIF nextchar = Backspace THEN
  842.  
  843.                 (* The effect of a backspace depends on whether the     *)
  844.                 (* accumulator holds a user-supplied number at this     *)
  845.                 (* stage.  This depends on things like whether the      *)
  846.                 (* user has typed several backspaces in a row.          *)
  847.  
  848.                 IF NumberPresent THEN
  849.  
  850.                     (* Delete the number in the accumulator.    *)
  851.  
  852.                     Register[0].value := 0.0;  NumberPresent := FALSE;
  853.  
  854.                 ELSIF Register[0].ParenCount > 0 THEN
  855.  
  856.                     (* Remove one left parenthesis.     *)
  857.  
  858.                     DEC (Register[0].ParenCount);
  859.  
  860.                 ELSE    (* Delete the last outstanding operator, if any. *)
  861.  
  862.                     IF Register[0].operator = EndMarker THEN
  863.                         (*Beep*);
  864.                     ELSIF Register[0].operator = "f" THEN
  865.                         PopStack;
  866.                         Register[0].value := 0.0;
  867.                         NumberPresent := FALSE;
  868.                     ELSE
  869.                         PopStack;
  870.                         NumberPresent := TRUE;
  871.                     END (*IF*);
  872.  
  873.                 END (*IF*);
  874.  
  875.                 DisplayRegister(0);
  876.  
  877.             END (*IF*);
  878.  
  879.         END (*LOOP*);
  880.  
  881.     END LoadAccumulator;
  882.  
  883. (************************************************************************)
  884.  
  885. PROCEDURE PerformCalculation;
  886.  
  887.     (* This procedure consists of a loop which is repeated until an     *)
  888.     (* Esc character is encountered.  Each time around the loop, we     *)
  889.     (* pick up an operand followed by an operator.  (Fetching the       *)
  890.     (* operand, which is done by procedure LoadAccumulator, may itself  *)
  891.     (* involve some subexpression evaluation, because the operand can   *)
  892.     (* include things like opening and closing parentheses, prefix and  *)
  893.     (* postfix functions, and the like.  Procedure LoadAccumulator also *)
  894.     (* allows some of the preceding input to be deleted via the         *)
  895.     (* Backspace key.)  The operator may be a binary operator, or Esc,  *)
  896.     (* or Enter, or '='.  (These last two are considered to be          *)
  897.     (* equivalent.)  Anything else is considered to be an unknown       *)
  898.     (* operator, and results in an audible Beep.                        *)
  899.     (* A calculation step, or possibly a whole sequence of steps, is    *)
  900.     (* triggered if there are more closing parentheses than opening     *)
  901.     (* parentheses, or if the operator has lower priority than the last *)
  902.     (* stacked operator.                                                *)
  903.  
  904.     VAR operator: CHAR;
  905.  
  906.     BEGIN
  907.         LOOP
  908.             LoadAccumulator (operator);
  909.  
  910.             (* The Esc key drops us out of the calculator - unless the  *)
  911.             (* help window is on display, in which case Esc simply      *)
  912.             (* removes the help window from the display.                *)
  913.  
  914.             IF operator = Esc THEN
  915.                 IF HelpVisible() THEN
  916.                     Hide (help);  operator := Enter;
  917.                 ELSE
  918.                     EXIT (*LOOP*);
  919.                 END (*IF*);
  920.             END (*IF*);
  921.  
  922.             (* Perform any pending operations. *)
  923.  
  924.             NumberPresent := TRUE;
  925.             WHILE TopOperatorPriority() >= priority(operator) DO
  926.                 BinaryOperation;
  927.             END (*WHILE*);
  928.  
  929.             (* Push the latest operator, unless it marks the end        *)
  930.             (* of the calculation.                                      *)
  931.  
  932.             IF priority(operator) = UnknownOperatorPriority THEN
  933.                 (*Beep*);
  934.             ELSIF (operator <> Enter) AND (operator <> "=") THEN
  935.                 PushStack (operator);
  936.             END(*IF*);
  937.  
  938.         END (*LOOP*);
  939.  
  940.     END PerformCalculation;
  941.  
  942. (************************************************************************)
  943.  
  944. PROCEDURE ForceExit (w: Window;  row: RowRange;  col: ColumnRange);
  945.  
  946.     (* This procedure is triggered by a mouse click on the "hide"       *)
  947.     (* button.  We turn this into a keyboard Esc character, or two Esc  *)
  948.     (* characters if the help window is visible.                        *)
  949.  
  950.     BEGIN
  951.         IF HelpVisible() THEN StuffKeyboardBuffer (Esc) END(*IF*);
  952.         StuffKeyboardBuffer (Esc);
  953.     END ForceExit;
  954.  
  955. (************************************************************************)
  956.  
  957. PROCEDURE ForceF1 (w: Window;  row: RowRange;  col: ColumnRange);
  958.  
  959.     (* This procedure is triggered by a mouse click on the "F1"         *)
  960.     (* message.  We turn this into a keyboard F1 keypress.              *)
  961.  
  962.     BEGIN
  963.         StuffKeyboardBuffer (CHR(0));
  964.         StuffKeyboardBuffer (';');
  965.     END ForceF1;
  966.  
  967. (************************************************************************)
  968. (*                      INTERFACE TO THE CALLER                         *)
  969. (************************************************************************)
  970.  
  971. PROCEDURE RunCalculator;
  972.  
  973.     (* Displays a calculator window on the screen; this can be operated *)
  974.     (* from the numeric keypad.  On exit, the screen window is closed,  *)
  975.     (* but calculation results are saved for the next invocation of     *)
  976.     (* this procedure.                                                  *)
  977.  
  978.     VAR KeyboardLocks: CARDINAL;  R: Rectangle;
  979.         UIW: UIWindow;
  980.  
  981.     BEGIN
  982.         (* Set the NumLock state, if not already set.   *)
  983.  
  984.         KeyboardLocks := LockStatus();
  985.         IF ORD(IAND (KeyboardLocks, NumLockLED)) = 0 THEN
  986.             SetLocks (KeyboardLocks + NumLockLED);
  987.         END (*IF*);
  988.  
  989.         OpenWindow (calc, yellow, blue,
  990.                         baserow, baserow+DisplayedRegisters+MaxMemoryNumber+7,
  991.                             basecol, basecol+numberwidth+9,
  992.                                 simpleframe, doubledivider);
  993.         WriteString (calc, "   Calculator");
  994.         ChangeScrollingRegion (calc, 3, DisplayedRegisters+MaxMemoryNumber+6);
  995.  
  996.         UIW := AllowMouseControl (calc, "Calculator",
  997.                                 CapabilitySet {wshow, wmove, whide});
  998.         AddActiveRegion (UIW, 0, 0, numberwidth+7, numberwidth+7,
  999.                                 ButtonSet {LeftButton}, ForceExit);
  1000.         AddActiveRegion (UIW,3,3, numberwidth DIV 2 + 5, numberwidth DIV 2 + 6,
  1001.                                 ButtonSet {LeftButton}, ForceF1);
  1002.  
  1003.         CreateHelpWindow;
  1004.         InitialDisplay;
  1005.         PerformCalculation;
  1006.         R := WindowLocation (help);
  1007.         helprow := R.top;  helpcol := R.left;
  1008.         HideMouseCursor;
  1009.         CloseWindow (help);
  1010.         R := WindowLocation (calc);
  1011.         baserow := R.top;  basecol := R.left;
  1012.         CloseWindow (calc);
  1013.         ShowMouseCursor;
  1014.  
  1015.         SetLocks (KeyboardLocks);
  1016.  
  1017.     END RunCalculator;
  1018.  
  1019. (************************************************************************)
  1020. (*                          INITIALISATION                              *)
  1021. (************************************************************************)
  1022.  
  1023. PROCEDURE ClearCalculatorState;
  1024.  
  1025.     (* Clears all of the working registers of the calculator.   *)
  1026.  
  1027.     VAR j: RegisterNumber;  mem: MemoryNumber;
  1028.  
  1029.     BEGIN
  1030.         FOR j := 0 TO MaxRegisterNumber DO
  1031.             WITH Register[j] DO
  1032.                 ParenCount := 0;
  1033.                 value := 0.0;
  1034.                 operator := EmptyMarker;
  1035.             END (*WITH*);
  1036.         END (*FOR*);
  1037.         Register[0].operator := EndMarker;
  1038.         NumberPresent := FALSE;
  1039.         FOR mem := 0 TO MAX(MemoryNumber) DO
  1040.             MemoryValue[mem] := 0.0;
  1041.         END (*FOR*);
  1042.     END ClearCalculatorState;
  1043.  
  1044. (************************************************************************)
  1045.  
  1046. BEGIN
  1047.     baserow := 0;  basecol := 70 - numberwidth;
  1048.     helprow := 7;  helpcol := 26;
  1049.     ClearCalculatorState;
  1050. END Calculator.
  1051.