unit UEngine;

interface

  uses
    SANE, ObjIntf, UGoof, UGoofCalc;

implementation

  const

  {Instruction opcodes}

    opLbl0 = 0;
    opLble = 19;
    opGto0 = 20;
    opGtoe = 39;
    opGsb0 = 40;
    opGsbe = 59;
    opSto0 = 60;
    opSto9 = 69;
    opStoA = 70;
    opStoE = 74;
    opRcl0 = 75;
    opRcl9 = 84;
    opRclA = 85;
    opRclE = 89;
    opStoPlus0 = 90;
    opStoPlus9 = 99;
    opStoMinus0 = 100;
    opStoMinus9 = 109;
    opStoTimes0 = 110;
    opStoTimes9 = 119;
    opStoDivide0 = 120;
    opStoDivide9 = 129;
    opDsp0 = 130;
    opDsp9 = 139;

    opGtoInd = 140;
    opGsbInd = 141;
    opStoInd = 142;
    opRclInd = 143;
    opStoMinusInd = 144;
    opStoPlusInd = 145;
    opStoTimesInd = 146;
    opStoDivideInd = 147;
    opDspInd = 148;
    opRtn = 149;

    op0 = 150;
    op9 = 159;

    opPercent = 160;
    opToRectangular = 161;
    opToDegrees = 162;
    opToHours = 163;
    opSin = 164;
    opCos = 165;
    opTan = 166;
    opLn = 167;
    opLog = 168;
    opRootX = 169;

    opPercentChange = 170;
    opToPolar = 171;
    opToRadians = 172;
    opToHMS = 173;
    opArcSin = 174;
    opArcCos = 175;
    opArcTan = 176;
    opEToTheX = 177;
    op10ToTheX = 178;
    opXSquared = 179;

    opLastX = 180;
    opPause = 181;
    opPi = 182;
    opPrintReg = 183;
    op1OverX = 184;
    opYToTheX = 185;
    opAbs = 186;
    opXExchY = 187;
    opRollDown = 188;
    opRollUp = 189;

    opDecPoint = 190;
    opRunStop = 191;
    opEnter = 192;
    opSigmaPlus = 193;
    opMinus = 194;
    opPlus = 195;
    opTimes = 196;
    opDivide = 197;
    opDsz = 198;
    opIsz = 199;

    opInt = 200;
    opPrintX = 201;
    opWriteDATA = 202;
    opXBar = 203;
    opXeq0 = 204;
    opXne0 = 205;
    opXlt0 = 206;
    opXgt0 = 207;
    opDszInd = 208;
    opIszInd = 209;

    opFrac = 210;
    opPrintStk = 211;
    opMerge = 212;
    opStdDev = 213;
    opXeqY = 214;
    opXneY = 215;
    opXleY = 216;
    opXgtY = 217;
    opStI = 218;
    opRcI = 219;

    opHMSPlus = 220;
    opPrintSpace = 221;
    opDeg = 222;
    opSigmaMinus = 223;
    opSetFlag0 = 224;
    opSetFlag3 = 227;
    opSst = 228;

    opChs = 230;
    opEex = 231;
    opClx = 232;
    opFix = 233;
    opClearFlag0 = 234;
    opClearFlag3 = 237;
    opBst = 238;

    opPExchS = 240;
    opClReg = 241;
    opClPrgm = 242;
    opSci = 243;
    opTestFlag0 = 244;
    opTestFlag3 = 247;
    opNFactorial = 248;

    opRad = 250;
    opGrd = 251;
    opDel = 252;
    opEng = 253;
    opRnd = 254;
    opXExchI = 255;

{------------ Procs and Funcs -----------}

  function int (a: Double): Double;
  begin
    int := trunc(a);
  end;

  function frac (a: Double): Double;
  begin
    frac := a - int(a);
  end;

  function asin (a: Double): Double;
  begin
    if a <> 1.0 then
      asin := arctan(a / sqrt(1 - sqr(a)))
    else
      asin := pi / 2.0;
  end;

  function acos (a: Double): Double;
  begin
    if a <> 0.0 then
      acos := arctan(sqrt(1 - sqr(a)) / a)
    else
      acos := pi / 2.0;
  end;

  function NumToDigits (number, numDigits: integer): DisplayString;
    var
      digits: packed array[1..15] of char;
      i: integer;
  begin
    i := numDigits;
    while i > 0 do begin
        digits[i] := chr(number mod 10 + ord('0'));
        number := number div 10;
        i := i - 1;
      end;
    NumToDigits := copy(digits, 1, numDigits);
  end;

  function SignChar (number: integer): char;
  begin
    if number < 0 then
      SignChar := '-'
    else
      SignChar := ' ';
  end;

  procedure DisplayNumber (x: real);
    var
      dec: Decimal;
      f: DecForm;
      displayBuffer: string[15];

    procedure PutDisplay (where: integer; what: string);
      var
        i: integer;
    begin
      for i := 1 to length(what) do
        displayBuffer[where + i - 1] := what[i];
    end;

    function AllZero (s: string): boolean;
      var
        i: integer;
    begin
      for i := 1 to length(s) do
        if s[i] <> '0' then begin
            AllZero := false;
            exit(AllZero);
          end;
      AllZero := true;
    end;

    function Format (style: DecStyle; digits: integer): boolean;
    begin
      f.style := style;
      f.digits := digits;
      Num2Dec(f, x, dec);
{if not (dec.sig[1] in ['0'..'9', '?']) then}
{Fail; -- NaN}
      if AllZero(dec.sig) then
        dec.sgn := 0;
      case style of
        fixedDecimal:  begin
            if dec.sig = '0' then
              dec.exp := -f.digits; {Make sure exponent valid}
            if (dec.sig[1] = '?') | (dec.exp <> -f.digits) | (length(dec.sig) + dec.exp > 10) then begin
                Format := false; {Overflow}
                exit(Format);
              end;
            while length(dec.sig) < digits + 1 do
              insert('0', dec.sig, 1); {Pad with zeroes on left if necessary}
          end;
        floatDecimal:  begin
            if dec.sig = '0' then
              dec.exp := 0; {Make sure exponent valid}
            while length(dec.sig) < digits do begin
                insert('0', dec.sig, length(dec.sig) + 1); {Pad with zeroes on right if necessary}
                dec.exp := dec.exp - 1;
              end;
          end;
      end;
      Format := true;
    end;

    function Modulo (i, j: integer): integer;
      var
        m: integer;
    begin
      m := i mod j;
      if m < 0 then
        m := j + m;
      Modulo := m;
    end;

    procedure FormatFloat (sigFigs: integer);
      label
        1;
      var
        expAdjustment: integer;
        junk: boolean;
    begin
1:
      junk := Format(floatDecimal, sigFigs);
      dec.exp := dec.exp + length(dec.sig) - 1;
      if dec.exp < -99 then begin
          x := 0;
          goto 1;
        end;
      if gEngine.fDispMode = engMode then begin
          expAdjustment := Modulo(dec.exp, 3);
          dec.exp := dec.exp - expAdjustment;
        end
      else
        expAdjustment := 0;
      Insert('.', dec.sig, 2 + expAdjustment);
      displayBuffer[13] := SignChar(dec.exp);
      PutDisplay(14, NumToDigits(abs(dec.exp), 2));
    end;

    procedure FormatFix;
    begin
      if Format(fixedDecimal, gEngine.fDispDigits) then begin
          insert('.', dec.sig, length(dec.sig) + dec.exp + 1);
          dec.sig := copy(dec.sig, 1, 11);
        end
      else
        FormatFloat(10);
    end;

  begin {DisplayNumber}
    displayBuffer := space15;
    case gEngine.fDispMode of
      fixMode: 
        FormatFix;
      sciMode, engMode: 
        FormatFloat(gEngine.fDispDigits + 1);
    end;
    displayBuffer[1] := SignChar(-dec.sgn);
    PutDisplay(2, dec.sig);
    gDlog.Display(displayBuffer);
  end; {DisplayNumber}

  procedure FlashNumber (x: real; duration: longint);
    var
      stop: longint;
  begin
    DisplayNumber(x);
    stop := TickCount + duration;
    while TickCount < stop do
      ;
  end;

{------------ Methods ---------------}

  procedure TEngine.IEngine;
    var
      i: integer;
  begin
    fMode := haltMode;
    fEntryMode := executeMode;
    fDispMode := fixMode;
    fDispDigits := 2;
    fAngleMode := degMode;
    for i := 0 to 3 do
      fStack.r[i] := 0;
    fLastXReg := 0;
    for i := 0 to maxRegNum do
      fReg[i] := 0;
    fPC := 0;
    fLastStep := 0;
    fRtnStackPtr := 0;
    for i := 0 to maxFlagNum do
      fFlags[i] := false;
    fEnterFlag := false;
    fCardOperation := cardIdle;
    fCardSidesRemaining := [];
    for i := 0 to 25 do
      fDecodeTable[i] := CheckGetResource('STR#', gIDBase + i);
  end;

  procedure TEngine.Free;
    var
      i: integer;
  begin
    for i := 0 to 25 do
      ReleaseResource(fDecodeTable[i]);
    inherited Free;
  end;

  procedure TEngine.Run;
  begin
    case fMode of
      runMode: 
        FetchAndExecute;
      pauseMode: 
        if TickCount >= fPauseEndTime then begin
            fMode := fLastMode;
            UpdateDisplay;
          end;
      otherwise
        ;
    end;
  end;

  procedure TEngine.FetchAndExecute;
    var
      opcode: integer;
  begin
    if fPC = 0 then
      fPC := 1;
    if fPC <= fLastStep then begin
        opcode := fProgMem[fPC];
        fPC := fPC + 1;
        DoOpcode(opcode);
      end;
    if fPC > fLastStep then
      DoOpcode(opRtn);
  end;

  procedure TEngine.Execute (opcode: integer);
  begin
    case fMode of
      haltMode: 
        DoOpcode(opcode);
      runMode: 
        if opcode = opRunStop then
          fMode := haltMode;
      pauseMode:  begin
          DoOpcode(opcode);
          fPauseEndTime := TickCount + 60;
        end;
      wPrgmMode: 
        EditProgram(opcode);
      cardIOMode: 
        if opcode = opRunStop then begin
            CardIOError;
            fCardOperation := cardIdle;
            fMode := haltMode;
          end;
      otherwise
    end;
    UpdateDisplay;
  end;

  procedure TEngine.EditProgram (opcode: integer);

    procedure EditSst;
    begin
      fPC := fPC + 1;
      if fPC > fLastStep then
        fPC := 0;
    end;

    procedure EditBst;
    begin
      fPC := fPC - 1;
      if fPC < 0 then
        fPC := fLastStep;
    end;

    procedure EditDel;
      var
        i: integer;
    begin
      if fPC > 0 then begin
          for i := fPC to fLastStep - 1 do
            fProgMem[i] := fProgMem[i + 1];
          fLastStep := fLastStep - 1;
          fPC := fPC - 1;
        end;
    end;

    procedure EditClPrgm;
    begin
      fLastStep := 0;
      fPC := 0;
    end;

    procedure EditInsert (opcode: integer);
      var
        i: integer;
    begin
      if fLastStep < maxProgStep then begin
          fPC := fPC + 1;
          fLastStep := fLastStep + 1;
          for i := fLastStep downto fPC + 1 do
            fProgMem[i] := fProgMem[i - 1];
          fProgMem[fPC] := opcode;
        end;
    end;

  begin {TEngine.EditProgram}
    case opcode of
      opSst: 
        EditSst;
      opBst: 
        EditBst;
      opDel: 
        EditDel;
      opClPrgm: 
        EditClPrgm;
      otherwise
        EditInsert(opcode);
    end;
  end; {TEngine.EditProgram}

  procedure TEngine.RunProgram;
  begin
    fMode := runMode;
  end;

  procedure TEngine.HaltProgram;
  begin
    if fMode <> haltMode then begin
        fMode := haltMode;
        UpdateDisplay;
      end;
  end;

  procedure TEngine.DoOpcode (opcode: integer);
  begin
    case opcode of
      opSst: 
        SingleStep;
      opBst: 
        BackStep;
      opChs: 
        DoChs;
      op0..op9, opDecPoint, opEex: 
        DoDigitEntry(opcode);
      otherwise begin
          EndDigitEntry;
          DoFunction(opcode);
        end;
    end;
  end;

  procedure TEngine.SingleStep;
  begin
    if fPC = 0 then
      fPC := 1;
    DisplayCurrentStep;
    while StillDown do
      ;
    gDlog.ReleaseKey;
    FetchAndExecute;
  end;

  procedure TEngine.BackStep;
  begin
    if fPC = 0 then
      fPC := fLastStep
    else
      fPC := fPC - 1;
    DisplayCurrentStep;
    while StillDown do
      ;
    gDlog.ReleaseKey;
  end;

  procedure TEngine.DoDigitEntry (opcode: integer);
  begin
    if fMode = pauseMode then
      fFlags[3] := true;
    if fEntryMode = executeMode then
      BeginMantissaEntry;
    case fEntryMode of
      mantissaEntryMode: 
        case opcode of
          op0..op9: 
            EnterMantissaDigit(opcode - op0);
          opDecPoint: 
            if not fDecPtEntered then
              EnterDecPt;
          opEex: 
            BeginExponentEntry;
        end;
      exponentEntryMode: 
        case opcode of
          op0..op9: 
            EnterExponentDigit(opcode - op0);
          otherwise
            ;
        end;
    end;
    fEnterFlag := false;
  end;

  procedure TEngine.BeginMantissaEntry;
  begin
    if not fEnterFlag then
      PushUpStack;
    fLastXReg := fStack.x;
    fEntryMode := mantissaEntryMode;
    fDisplayBuffer := space15;
    fDigitCount := 0;
    fDisplayBuffer[2] := '.';
    fDecPtEntered := false;
    with fEntryBuffer do begin
        sgn := 0;
        exp := 0;
        sig := '';
      end;
  end;

  procedure TEngine.EnterMantissaDigit (digit: integer);
    var
      c: char;
      i: integer;
  begin
    if fDigitCount < 10 then begin
        c := chr(digit + ord('0'));
        fDigitCount := fDigitCount + 1;
        insert(c, fEntryBuffer.sig, fDigitCount);
        i := fDigitCount + 1;
        if fDecPtEntered then
          i := i + 1;
        fDisplayBuffer[i] := c;
        if fDecPtEntered then
          fEntryBuffer.exp := fEntryBuffer.exp - 1
        else
          fDisplayBuffer[i + 1] := '.';
      end;
  end;

  procedure TEngine.EnterDecPt;
  begin
    fDecPtEntered := true;
  end;

  procedure TEngine.BeginExponentEntry;
  begin
    if fDigitCount = 0 then
      EnterMantissaDigit(1);
    fEntryMode := exponentEntryMode;
    fDisplayBuffer[14] := '0';
    fDisplayBuffer[15] := '0';
  end;

  procedure TEngine.EnterExponentDigit (digit: integer);
  begin
    fDisplayBuffer[14] := fDisplayBuffer[15];
    fDisplayBuffer[15] := chr(digit + ord('0'));
  end;

  procedure TEngine.DoChs;
    var
      i: integer;

    procedure ToggleDisplaySign (i: integer);
      var
        c: char;
    begin
      c := fDisplayBuffer[i];
      if c = '-' then
        c := ' '
      else
        c := '-';
      fDisplayBuffer[i] := c;
    end;

  begin {TEngine.DoChs}
    case fEntryMode of
      executeMode: 
        DoFunction(opCHS);
      mantissaEntryMode: 
        ToggleDisplaySign(1);
      exponentEntryMode: 
        ToggleDisplaySign(13);
    end;
  end; {TEngine.DoChs}

  procedure TEngine.EndDigitEntry;
    var
      enteredExponent: longint;
  begin
    if fEntryMode <> executeMode then begin
        if fEntryMode = exponentEntryMode then begin
            StringToNum(copy(fDisplayBuffer, 13, 3), enteredExponent);
            fEntryBuffer.exp := fEntryBuffer.exp + enteredExponent;
          end;
        if fDisplayBuffer[1] = '-' then
          fEntryBuffer.sgn := 1;
        while (length(fEntryBuffer.sig) > 0) & (fEntryBuffer.sig[1] = '0') do
          Delete(fEntryBuffer.sig, 1, 1);
        fStack.x := Dec2Num(fEntryBuffer);
        fEntryMode := executeMode;
      end;
  end;

  procedure TEngine.PushUpStack;
  begin
    with fStack do begin
        t := z;
        z := y;
        y := x;
      end;
  end;

  procedure TEngine.DoFunction (opcode: integer);

    procedure Fail;
    begin
      Error;
      exit(DoFunction);
      ;
    end;

    procedure DoPause;
    begin
      fLastMode := fMode;
      fMode := pauseMode;
      fPauseEndTime := TickCount + 60;
      UpdateDisplay;
    end;

    procedure Push (a: Double);
    begin
      if not fEnterFlag then
        PushUpStack;
      fStack.x := a;
    end;

    procedure Pop;
    begin
      with fStack do begin
          x := y;
          y := z;
          z := t;
        end;
    end;

    procedure ReplaceX (newX: Double);
    begin
      fLastXReg := fStack.x;
      fStack.x := newX;
    end;

    procedure ReplaceXY (newX: Double);
    begin
      fLastXReg := fStack.x;
      Pop;
      fStack.x := newX;
    end;

    function IndirectLabel: integer;
      var
        address: integer;
    begin
      address := trunc(fReg[IReg]);
      if (address > 19) or (address < -999) then
        Fail;
      IndirectLabel := address;
    end;

    function IndirectReg: integer;
      var
        address: integer;
    begin
      address := trunc(fReg[IReg]);
      if (address < 0) or (address > maxRegNum) then
        Fail;
      IndirectReg := address;
    end;

    function Indirect0to9: integer;
      var
        value: integer;
    begin
      value := trunc(fReg[IReg]);
      if (value < 0) or (value > 9) then
        Fail;
      Indirect0to9 := value;
    end;

    function FindStep (address: integer): integer;
      var
        i0, i: integer;
    begin
      if address >= 0 then begin
          i0 := fPC;
          if i0 = 0 then
            i0 := 1;
          for i := i0 to fLastStep do
            if fProgMem[i] = address then begin
                FindStep := i;
                exit(FindStep);
              end;
          for i := 1 to fPC - 1 do
            if fProgMem[i] = address then begin
                FindStep := i;
                exit(FindStep);
              end;
          FindStep := -1;
        end
      else begin
          i := fPC + address;
          while i < 0 do
            i := i + (maxProgStep + 1);
          FindStep := i;
        end;
    end;

    procedure DoGto (address: integer);
      var
        step: integer;
    begin
      step := FindStep(address);
      if step < 0 then
        Fail;
      fPC := step;
    end;

    procedure Do1OverX;
    forward;
    procedure DoRootX;
    forward;
    procedure DoYToTheX;
    forward;
    procedure DoRollDown;
    forward;
    procedure DoXExchY;
    forward;

    procedure DefaultUserKey (address: integer);
    begin
      case address of
        10: 
          Do1OverX;
        11: 
          DoRootX;
        12: 
          DoYToTheX;
        13: 
          DoRollDown;
        14: 
          DoXExchY;
        otherwise
          Fail;
      end;
    end;

    procedure PushRtnAddr (rtnAddr: integer);
    begin
      if fRtnStackPtr > rtnStackSize then
        Fail;
      fRtnStackPtr := fRtnStackPtr + 1;
      fRtnStack[fRtnStackPtr] := rtnAddr;
    end;

    procedure DoGsb (address: integer);
      var
        step: integer;
    begin
      step := FindStep(address);
      if step >= 0 then begin
          if fMode = runMode then
            PushRtnAddr(fPC);
          fPC := step;
          RunProgram;
        end
      else
        DefaultUserKey(address);
    end;

    procedure DoSto (address: integer);
    begin
      fReg[address] := fStack.x;
    end;

    procedure DoRcl (address: integer);
    begin
      Push(fReg[address]);
    end;

    procedure DoStoPlus (address: integer);
    begin
      fReg[address] := fReg[address] + fStack.x;
    end;

    procedure DoStoMinus (address: integer);
    begin
      fReg[address] := fReg[address] - fStack.x;
    end;

    procedure DoStoTimes (address: integer);
    begin
      fReg[address] := fReg[address] * fStack.x;
    end;

    procedure DoStoDivide (address: integer);
    begin
      if fStack.x = 0 then
        Fail;
      fReg[address] := fReg[address] / fStack.x;
    end;

    procedure DoDsp (digits: integer);
    begin
      fDispDigits := digits;
    end;

    function Radians (a: Double): Double;
    begin
      case fAngleMode of
        degMode: 
          Radians := a * (pi / 180);
        radMode: 
          Radians := a;
        grdMode: 
          Radians := a * (pi / 200);
      end;
    end;

    function Angle (a: Double): Double;
    begin
      case fAngleMode of
        degMode: 
          Angle := a * (180 / pi);
        radMode: 
          Angle := a;
        grdMode: 
          Angle := a * (200 / pi);
      end;
    end;

    procedure Skip;
    begin
      if fPC <= fLastStep then begin
          fPC := fPC + 1;
          if fPC > fLastStep then
            fPC := 0;
        end;
    end;

    function StdDev (n, sum, sumsq: Double): Double;
    begin
      if (n <> 0.0) and (n <> 1.0) then
        StdDev := sqrt((sumsq - sqr(sum) / n) / (n - 1.0))
      else
        Fail;
    end;

    function FromHMS (a: Double): Double;
    begin
      FromHMS := int(a) + int(frac(a) * 100.0) / 60.0 + frac(a * 100.0) / 36.0;
    end;

    function ToHMS (a: Double): Double;
    begin
      ToHMS := int(a) + int(frac(a) * 60.0) / 100.0 + frac(a * 60.0) * 0.006;
    end;

    procedure DoDsz (address: integer);
    begin
      fReg[address] := fReg[address] - 1;
      if fReg[address] = 0 then
        Skip;
    end;

    procedure DoIsz (address: integer);
    begin
      fReg[address] := fReg[address] + 1;
      if fReg[address] = 0 then
        Skip;
    end;

    procedure DoRnd;
      var
        f: DecForm;
        dec: Decimal;
    begin
      f.digits := fDispDigits;
      case fDispMode of
        fixMode: 
          f.style := FixedDecimal;
        sciMode, engMode: 
          f.style := FloatDecimal;
      end;
      Num2Dec(f, fStack.x, dec);
      ReplaceX(Dec2Num(dec));
    end;

    procedure DoRtn;
    begin
      if (fMode <> haltMode) and (fRtnStackPtr > 0) then begin
          fPC := fRtnStack[fRtnStackPtr];
          fRtnStackPtr := fRtnStackPtr - 1;
        end
      else begin
          HaltProgram;
          fPC := 0;
          fRtnStackPtr := 0;
        end;
    end;

    procedure DoToRectangular;
      var
        h, a: Double;
    begin
      with fStack do begin
          fLastXReg := x;
          h := x;
          a := Radians(y);
          x := h * cos(a);
          y := h * sin(a);
        end;
    end;

    procedure DoTan;
      var
        a, sinX, cosX: double;
    begin
      a := Radians(fStack.x);
      sinX := sin(a);
      cosX := cos(a);
      if cosX = 0 then
        Fail;
      ReplaceX(sinX / cosX);
    end;

    procedure DoLn;
    begin
      if fStack.x = 0 then
        Fail;
      ReplaceX(ln(fStack.x));
    end;

    procedure DoLog;
    begin
      if fStack.x <= 0 then
        Fail;
      ReplaceX(ln(fStack.x) / ln10);
    end;

    procedure DoRootX;
    begin
      if fStack.x < 0 then
        Fail;
      ReplaceX(sqrt(fStack.x));
    end;

    procedure DoToPolar;
      var
        h, a: Double;
    begin
      with fStack do begin
          fLastXReg := x;
          h := sqrt(sqr(x) + sqr(y));
          if x <> 0 then
            a := arctan(y / x)
          else
            a := pi / 2;
          if x < 0 then
            a := a + pi;
          y := Angle(a);
          x := h;
        end;
    end;

    procedure DoPrintReg;
      var
        i: integer;
    begin
      for i := 0 to 9 do
        FlashNumber(fReg[i], 12);
      for i := 20 to 25 do
        FlashNumber(fReg[i], 12);
      UpdateDisplay;
    end;

    procedure Do1OverX;
    begin
      if fStack.x = 0 then
        Fail;
      ReplaceX(1 / fStack.x);
    end;

    procedure DoYToTheX;
    begin
      if fStack.y <= 0 then
        Fail;
      ReplaceX(exp(ln(fStack.y) * fStack.x));
    end;

    procedure DoXExchY;
      var
        temp: Double;
    begin
      with fStack do begin
          temp := x;
          x := y;
          y := temp;
        end;
    end;

    procedure DoRollDown;
      var
        temp: Double;
    begin
      with fStack do begin
          temp := x;
          x := y;
          y := z;
          z := t;
          t := temp;
        end;
    end;

    procedure DoRollUp;
      var
        temp: Double;
    begin
      with fStack do begin
          temp := t;
          t := z;
          z := y;
          y := x;
          x := temp;
        end;
    end;

    procedure DoRunStop;
    begin
      if fMode = haltMode then
        RunProgram
      else
        HaltProgram;
    end;

    procedure DoEnter;
    begin
      PushUpStack;
    end;

    procedure AccumulateStats (sign: integer);
    begin
      with fStack do begin
          fReg[10] := fReg[10] + sign;
          fReg[11] := fReg[11] + sign * x;
          fReg[12] := fReg[12] + sign * y;
          fReg[13] := fReg[13] + sign * sqr(x);
          fReg[14] := fReg[14] + sign * sqr(y);
        end;
    end;

    procedure DoSigmaPlus;
    begin
      AccumulateStats(1);
    end;

    procedure DoSigmaMinus;
    begin
      AccumulateStats(-1);
    end;

    procedure DoDivide;
    begin
      if fStack.x = 0 then
        Fail;
      ReplaceX(fStack.y / fStack.x);
    end;

    procedure DoPrintX;
    begin
      FlashNumber(fStack.x, 60);
      UpdateDisplay;
    end;

    procedure DoWData;
      var
        i: integer;
        twoSided: boolean;
    begin
      twoSided := false;
      for i := 10 to 19 do
        if fReg[i] <> 0 then begin
            twoSided := true;
            Leave;
          end;
      if twoSided then
        fCardSidesRemaining := [1, 2]
      else
        fCardSidesRemaining := [1];
      RequestCardOperation(cardWriteData);
    end;

    procedure DoXBar;
      var
        temp: Double;
    begin
      temp := fReg[10];
      if temp = 0 then
        Fail;
      Push(fReg[12] / temp);
      Push(fReg[11] / temp);
    end;

    procedure DoPrintStk;
      var
        i: integer;
    begin
      for i := 3 downto 0 do
        FlashNumber(fStack.r[i], 15);
      UpdateDisplay;
    end;

    procedure DoMerge;
    begin
      if (fStack.x < 0) or (fStack.x > 225) then
        Error
      else begin
          fReadStart := trunc(fStack.x) + 1;
          RequestCardOperation(cardReadProg);
        end;
    end;

    procedure DoStdDev;
    begin
      Push(StdDev(fReg[10], fReg[12], fReg[14]));
      Push(StdDev(fReg[10], fReg[11], fReg[13]));
    end;

    procedure DoPrintSpace;
    begin
    end;

    procedure DoPExchS;
      var
        i: integer;
        temp: Double;
    begin
      for i := 0 to 9 do begin
          temp := fReg[i];
          fReg[i] := fReg[i + 10];
          fReg[i + 10] := temp;
        end;
    end;

    procedure DoClReg;
      var
        i: integer;
    begin
      for i := 0 to 9 do
        fReg[i] := 0;
      for i := 20 to 25 do
        fReg[i] := 0;
    end;

    procedure DoNFactorial;
      var
        i: integer;
        a: Double;
    begin
      with fStack do begin
          if (x < 0) or (frac(x) <> 0) then
            Fail;
          a := 1.0;
          for i := 2 to trunc(x) do
            a := a * i;
          x := a;
        end;
    end;

    procedure DoXExchI;
      var
        temp: Double;
    begin
      temp := fStack.x;
      fStack.x := fReg[25];
      fReg[25] := temp;
    end;

    procedure DoClx;
    begin
      ReplaceX(0);
    end;

    procedure DoTestFlag;
      var
        flagNum: integer;
    begin
      flagNum := opcode - opTestFlag0;
      if not fFlags[flagNum] then
        Skip;
      if (flagNum >= 2) & (flagNum <= 3) then
        fFlags[flagNum] := false;
    end;

  begin {DoFunction}
    with fStack do
      case opcode of
        opLbl0..opLble: 
          ;
        opGto0..opGtoe: 
          DoGto(opcode - opGto0);
        opGsb0..opGsbe: 
          DoGsb(opcode - opGsb0);
        opSto0..opSto9: 
          DoSto(opcode - opSto0);
        opStoA..opStoE: 
          DoSto(opcode - opStoA + 20);
        opRcl0..opRcl9: 
          DoRcl(opcode - opRcl0);
        opRclA..opRclE: 
          DoRcl(opcode - opRclA + 20);
        opStoPlus0..opStoPlus9: 
          DoStoPlus(opcode - opStoPlus0);
        opStoMinus0..opStoMinus9: 
          DoStoMinus(opcode - opStoMinus0);
        opStoTimes0..opStoTimes9: 
          DoStoTimes(opcode - opStoMinus0);
        opStoDivide0..opStoDivide9: 
          DoStoDivide(opcode - opStoDivide0);
        opDsp0..opDsp9: 
          DoDsp(opcode - opDsp0);
        opGtoInd: 
          DoGto(IndirectLabel);
        opGsbInd: 
          DoGsb(IndirectLabel);
        opStoInd: 
          DoSto(IndirectReg);
        opRclInd: 
          DoRcl(IndirectReg);
        opStoPlusInd: 
          DoStoPlus(IndirectReg);
        opStoMinusInd: 
          DoStoMinus(IndirectReg);
        opStoTimesInd: 
          DoStoTimes(IndirectReg);
        opStoDivideInd: 
          DoStoDivide(IndirectReg);
        opDspInd: 
          DoDsp(Indirect0to9);
        opRtn: 
          DoRtn;
        opPercent: 
          ReplaceXY(y * x / 100);
        opToRectangular: 
          DoToRectangular;
        opToDegrees: 
          ReplaceX(x * (180 / pi));
        opToHours: 
          ReplaceX(FromHMS(x));
        opSin: 
          ReplaceX(sin(Radians(x)));
        opCos: 
          ReplaceX(cos(Radians(x)));
        opTan: 
          DoTan;
        opLn: 
          DoLn;
        opLog: 
          DoLog;
        opRootX: 
          DoRootX;
        opPercentChange: 
          ReplaceXY((x - y) * 100 / y);
        opToPolar: 
          DoToPolar;
        opToRadians: 
          ReplaceX(x * (pi / 180));
        opToHMS: 
          ReplaceX(ToHMS(x));
        opArcSin: 
          ReplaceX(Angle(asin(x)));
        opArcCos: 
          ReplaceX(Angle(acos(x)));
        opArcTan: 
          ReplaceX(Angle(arctan(x)));
        opEToTheX: 
          ReplaceX(exp(x));
        op10ToTheX: 
          ReplaceX(exp(ln10 * x));
        opXSquared: 
          ReplaceX(sqr(x));
        opLastX: 
          Push(fLastXReg);
        opPause: 
          DoPause;
        opPi: 
          Push(pi);
        opPrintReg: 
          DoPrintReg;
        op1OverX: 
          Do1OverX;
        opYToTheX: 
          DoYToTheX;
        opAbs: 
          ReplaceX(abs(x));
        opXExchY: 
          DoXExchY;
        opRollDown: 
          DoRollDown;
        opRollUp: 
          DoRollUp;
        opRunStop: 
          DoRunStop;
        opEnter: 
          DoEnter;
        opSigmaPlus: 
          DoSigmaPlus;
        opMinus: 
          ReplaceXY(y - x);
        opPlus: 
          ReplaceXY(y + x);
        opTimes: 
          ReplaceXY(y * x);
        opDivide: 
          DoDivide;
        opDsz: 
          DoDsz(25);
        opIsz: 
          DoIsz(25);
        opInt: 
          ReplaceX(int(x));
        opPrintX: 
          DoPrintX;
        opWriteData: 
          DoWData;
        opXBar: 
          DoXBar;
        opXeq0: 
          if x <> 0 then
            Skip;
        opXne0: 
          if x = 0 then
            Skip;
        opXlt0: 
          if x >= 0 then
            Skip;
        opXgt0: 
          if x <= 0 then
            Skip;
        opDszInd: 
          DoDsz(IndirectReg);
        opIszInd: 
          DoIsz(IndirectReg);
        opFrac: 
          ReplaceX(frac(x));
        opPrintStk: 
          DoPrintStk;
        opMerge: 
          DoMerge;
        opStdDev: 
          DoStdDev;
        opXeqY: 
          if x <> y then
            Skip;
        opXneY: 
          if x = y then
            Skip;
        opXleY: 
          if x > y then
            Skip;
        opXgtY: 
          if x <= y then
            Skip;
        opStI: 
          fReg[IReg] := x;
        opRcI: 
          Push(fReg[IReg]);
        opHMSPlus: 
          ReplaceXY(FromHMS(y) + FromHMS(x));
        opPrintSpace: 
          DoPrintSpace;
        opDeg: 
          fAngleMode := degMode;
        opSigmaMinus: 
          DoSigmaMinus;
        opSetFlag0..opSetFlag3: 
          fFlags[opcode - opSetFlag0] := true;
        opChs: 
          ReplaceX(-x);
        opClx: 
          DoClx;
        opFix: 
          fDispMode := fixMode;
        opClearFlag0..opClearFlag3: 
          fFlags[opcode - opClearFlag0] := false;
        opBst: 
          ;
        opPExchS: 
          DoPExchS;
        opClReg: 
          DoClReg;
        opClPrgm: 
          ;
        opSci: 
          fDispMode := sciMode;
        opTestFlag0..opTestFlag3: 
          DoTestFlag;
        opNFactorial: 
          DoNFactorial;
        opRad: 
          fAngleMode := radMode;
        opGrd: 
          fAngleMode := grdMode;
        opEng: 
          fDispMode := engMode;
        opRnd: 
          DoRnd;
        opXExchI: 
          DoXExchI;
      end;
    if (fStack.x > 9.999999999e99) | (fStack.x < -9.999999999e99) then
      Error;
    fEnterFlag := opcode in [opEnter, opClx];
  end; {DoFunction}

  procedure TEngine.UpdateDisplay;
  begin
    case fMode of
      haltMode, pauseMode: 
        if fEntryMode = executeMode then
          DisplayXRegister
        else
          gDlog.Display(fDisplayBuffer);
      runMode: 
        gDlog.Display(space15);
      wPrgmMode: 
        DisplayCurrentStep;
      otherwise
        ;
    end;
  end;

  procedure TEngine.DisplayXRegister;
  begin
    DisplayNumber(fStack.x);
  end;

  procedure TEngine.DisplayCurrentStep;
    var
      keyCode: Str255;
  begin {TEngine.DisplayCurrentStep}
    if fPC > 0 then
      OpcodeToKeyCode(fProgMem[fPC], keyCode)
    else
      keyCode := '';
    insert(copy('            ', 1, 12 - length(keyCode)), keyCode, 1);
    fDisplayBuffer := concat(NumToDigits(fPC, 3), keyCode);
    gDlog.Display(fDisplayBuffer);
  end; {TEngine.DisplayCurrentStep}

  procedure TEngine.OpcodeToKeyCode (opcode: integer; var keyCode: Str255);
  begin
    GetIndString(keyCode, gIDBase + opcode div 10, opcode mod 10 + 1);
  end;

  procedure TEngine.Error;
  begin
    fMode := haltMode;
    gDlog.Error;
  end;

  procedure TEngine.ImmediateGTO (newPC: integer);
  begin
    if (newPC >= 0) and (newPC <= maxProgStep) then begin
        fPC := newPC;
        if fMode = wPrgmMode then
          UpdateDisplay;
      end
    else
      Error;
  end;

  procedure TEngine.EnterWPrgmMode;
  begin
    fMode := wPrgmMode;
    UpdateDisplay;
  end;

  procedure TEngine.EnterRunMode;
  begin
    fMode := haltMode;
    UpdateDisplay;
  end;

  procedure TEngine.RequestCardOperation (whichOperation: CardOperation);
  begin
    fLastMode := fMode;
    fMode := cardIOMode;
    fCardOperation := whichOperation;
    RequestCard(false);
    DispatchCardOperation;
  end;

  procedure TEngine.CardInserted;
  begin
    EndDigitEntry;
    DispatchCardOperation;
  end;

  procedure TEngine.DispatchCardOperation;
  begin
    if gDlog.CardPresent then begin
        if fCardOperation in [cardIdle, cardReadProg, cardReadData] then
          gDlog.ReadCard(fCardBuffer);
        case fMode of
          haltMode: 
            ReadCard;
          runMode: 
            ;
          pauseMode:  begin
              fMode := runMode;
              ReadCard;
            end;
          cardIOMode: 
            DoCardIO;
          wPrgmMode: 
            WriteProgram;
        end;
      end;
  end;

  procedure TEngine.ReadCard;
    var
      typ: CardType;
  begin
    typ := fCardBuffer.theType;
    fLastMode := fMode;
    fMode := cardIOMode;
    case typ of
      blankCard: 
        CardIOError;
      progCard, progCard1, progCard2:  begin
          fCardOperation := cardReadProg;
          if typ = progCard then
            fCardSidesRemaining := [1]
          else
            fCardSidesRemaining := [1, 2];
          fReadStart := 1;
        end;
      dataCard, dataCard1, dataCard2:  begin
          fCardOperation := cardReadData;
          if typ = dataCard then
            fCardSidesRemaining := [1]
          else
            fCardSidesRemaining := [1, 2];
          SetDataReadRange;
        end;
    end;
    DoCardIO;
  end;

  procedure TEngine.SetDataReadRange;
  begin
    if fStack.x = 0 then begin
        fReadStart := 0;
        fReadLength := 26;
      end
    else begin
        if (fStack.y < 0) or (fStack.y >= 26) or (fStack.x < 0) or (fStack.x >= 27) then begin
            CardIOError;
            exit(SetDataReadRange);
          end;
        fReadStart := trunc(fStack.y);
        fReadLength := trunc(fStack.x);
      end;
  end;

  procedure TEngine.WriteProgram;
  begin
    fLastMode := fMode;
    fMode := cardIOMode;
    fCardOperation := cardWriteProg;
    if fLastStep <= 112 then
      fCardSidesRemaining := [1]
    else
      fCardSidesRemaining := [1, 2];
    DoCardIO;
  end;

  procedure TEngine.DoCardIO;
  begin
    case fCardOperation of
      cardReadProg: 
        DoReadProgram;
      cardWriteProg: 
        DoWriteProgram;
      cardReadData: 
        DoReadData;
      cardWriteData: 
        DoWriteData;
      otherwise
        ;
    end;
  end;

  procedure TEngine.CardPassed;
  begin
    if fCardOperation = cardError then begin
        fCardOperation := cardIdle;
        fCardSidesRemaining := [];
        Error;
      end
    else if fCardSidesRemaining <> [] then
      RequestCard(true)
    else begin
        fCardOperation := cardIdle;
        fMode := fLastMode;
        UpdateDisplay;
      end;
  end;

  procedure TEngine.CardIOError;
  begin
    if fCardOperation = cardReadProg then begin
        fLastStep := 0;
        fPC := 0;
      end;
    fCardOperation := cardError;
  end;

  procedure TEngine.RequestCard (inverted: boolean);
  begin
    gDlog.RequestCard(inverted);
  end;

  procedure TEngine.DoReadProgram;
    var
      side: integer;
  begin
    case fCardBuffer.theType of
      progCard, progCard1: 
        side := 1;
      progCard2: 
        side := 2;
      otherwise
        CardIOError;
    end;
    if side in fCardSidesRemaining then begin
        ReadProgSide(side);
        fCardSidesRemaining := fCardSidesRemaining - [side];
        if fMode = pauseMode then
          fFlags[3] := true;
      end;
  end;

  procedure TEngine.ReadProgSide (side: integer);
    var
      firstStep, numSteps, i: integer;
  begin
    with fCardBuffer do begin
        if side = 1 then begin
            fDispMode := theDispMode;
            fAngleMode := theAngleMode;
            fFlags := theFlags;
            fLastStep := theProgSize;
            firstStep := fReadStart;
          end
        else
          firstStep := fReadStart + 112;
        numSteps := theSideSize;
        if firstStep + numSteps > maxProgStep then
          numSteps := maxProgStep - firstStep + 1;
        for i := 0 to numSteps - 1 do
          fProgMem[firstStep + i] := theSteps[i];
      end;
  end;

  procedure TEngine.DoWriteProgram;
    var
      i: integer;
  begin
    for i := 1 to 2 do
      if i in fCardSidesRemaining then begin
          WriteProgSide(i);
          fCardSidesRemaining := fCardSidesRemaining - [i];
          Leave;
        end;
  end;

  procedure TEngine.WriteProgSide (side: integer);
    var
      firstStep, numSteps, i: integer;
  begin
    with fCardBuffer do begin
        case side of
          1:  begin
              if 2 in fCardSidesRemaining then
                theType := progCard1
              else
                theType := progCard;
              theDispMode := fDispMode;
              theAngleMode := fAngleMode;
              fFlags := theFlags;
              theProgSize := fLastStep;
              firstStep := 1;
            end;
          2:  begin
              theType := progCard2;
              firstStep := 113;
            end;
        end;
        numSteps := fLastStep - firstStep + 1;
        if numSteps > 112 then
          numSteps := 112;
        theSideSize := numSteps;
        for i := 0 to numSteps - 1 do
          theSteps[i] := fProgMem[firstStep + i];
      end;
    if not gDlog.WriteCard(fCardBuffer) then
      CardIOError;
  end;

  procedure TEngine.DoReadData;
    var
      side: integer;
  begin
    case fCardBuffer.theType of
      dataCard, dataCard1: 
        side := 1;
      dataCard2: 
        side := 2;
      otherwise
        CardIOError;
    end;
    if side in fCardSidesRemaining then begin
        ReadDataSide(side);
        fCardSidesRemaining := fCardSidesRemaining - [side];
        if fMode = pauseMode then
          fFlags[3] := true;
      end;
  end;

  procedure TEngine.ReadDataSide (side: integer);

    procedure ReadDataReg (reg, slot: integer);
    begin
      if (reg >= fReadStart) and (reg < fReadStart + fReadLength) then
        fReg[reg] := fCardBuffer.theData[slot];
    end;

    var
      i: integer;
  begin
    case side of
      1:  begin
          for i := 0 to 9 do
            ReadDataReg(i, i);
          for i := 0 to 5 do
            ReadDataReg(20 + i, 10 + i);
        end;
      2: 
        for i := 0 to 9 do
          ReadDataReg(10 + i, i);
    end;
  end;

  procedure TEngine.DoWriteData;
    var
      i: integer;
  begin
    for i := 1 to 2 do
      if i in fCardSidesRemaining then begin
          WriteDataSide(i);
          fCardSidesRemaining := fCardSidesRemaining - [i];
          Leave;
        end;
  end;

  procedure TEngine.WriteDataSide (side: integer);

    procedure WriteDataReg (reg, slot: integer);
    begin
      fCardBuffer.theData[slot] := fReg[reg];
    end;

    var
      i: integer;
  begin
    with fCardBuffer do
      case side of
        1:  begin
            if 2 in fCardSidesRemaining then
              theType := dataCard1
            else
              theType := dataCard;
            for i := 0 to 9 do
              WriteDataReg(i, i);
            for i := 0 to 5 do
              WriteDataReg(20 + i, 10 + i);
          end;
        2:  begin
            theType := dataCard2;
            for i := 0 to 9 do
              WriteDataReg(10 + i, i);
          end;
      end;
    if not gDlog.WriteCard(fCardBuffer) then
      CardIOError;
  end;

end.


