void CCalcEngine::ProcessCommandWorker()

in src/CalcManager/CEngine/scicomm.cpp [98:792]


void CCalcEngine::ProcessCommandWorker(OpCode wParam)
{
    // Save the last command.  Some commands are not saved in this manor, these
    // commands are:
    // Inv, Deg, Rad, Grad, Stat, FE, MClear, Back, and Exp.  The excluded
    // commands are not
    // really mathematical operations, rather they are GUI mode settings.

    if (!IsGuiSettingOpCode(wParam))
    {
        m_nLastCom = m_nTempCom;
        m_nTempCom = (int)wParam;
    }

    // Clear expression shown after = sign, when user do any action.
    if (!m_bNoPrevEqu)
    {
        ClearDisplay();
    }

    if (m_bError)
    {
        if (wParam == IDC_CLEAR)
        {
            // handle "C" normally
        }
        else if (wParam == IDC_CENTR)
        {
            // treat "CE" as "C"
            wParam = IDC_CLEAR;
        }
        else
        {
            HandleErrorCommand(wParam);
            return;
        }
    }

    // Toggle Record/Display mode if appropriate.
    if (m_bRecord)
    {
        if (IsBinOpCode(wParam) || IsUnaryOpCode(wParam) || IsOpInRange(wParam, IDC_FE, IDC_MMINUS) || IsOpInRange(wParam, IDC_OPENP, IDC_CLOSEP)
            || IsOpInRange(wParam, IDM_HEX, IDM_BIN) || IsOpInRange(wParam, IDM_QWORD, IDM_BYTE) || IsOpInRange(wParam, IDM_DEG, IDM_GRAD)
            || IsOpInRange(wParam, IDC_BINEDITSTART, IDC_BINEDITEND) || (IDC_INV == wParam) || (IDC_SIGN == wParam && 10 != m_radix) || (IDC_RAND == wParam)
            || (IDC_EULER == wParam))
        {
            m_bRecord = false;
            m_currentVal = m_input.ToRational(m_radix, m_precision);
            DisplayNum(); // Causes 3.000 to shrink to 3. on first op.
        }
    }
    else if (IsDigitOpCode(wParam) || wParam == IDC_PNT)
    {
        m_bRecord = true;
        m_input.Clear();
        CheckAndAddLastBinOpToHistory();
    }

    // Interpret digit keys.
    if (IsDigitOpCode(wParam))
    {
        unsigned int iValue = static_cast<unsigned int>(wParam - IDC_0);

        // this is redundant, illegal keys are disabled
        if (iValue >= static_cast<unsigned int>(m_radix))
        {
            HandleErrorCommand(wParam);
            return;
        }

        if (!m_input.TryAddDigit(iValue, m_radix, m_fIntegerMode, GetMaxDecimalValueString(), m_dwWordBitWidth, m_cIntDigitsSav))
        {
            HandleErrorCommand(wParam);
            HandleMaxDigitsReached();
            return;
        }

        DisplayNum();

        return;
    }

    // BINARY OPERATORS:
    if (IsBinOpCode(wParam))
    {
        // Change the operation if last input was operation.
        if (IsBinOpCode(m_nLastCom))
        {
            bool fPrecInvToHigher = false; // Is Precedence Inversion from lower to higher precedence happening ??

            m_nOpCode = (int)wParam;

            // Check to see if by changing this binop, a Precedence inversion is happening.
            // Eg. 1 * 2  + and + is getting changed to ^. The previous precedence rules would have already computed
            // 1*2, so we will put additional brackets to cover for precedence inversion and it will become (1 * 2) ^
            // Here * is m_nPrevOpCode, m_currentVal is 2  (by 1*2), m_nLastCom is +, m_nOpCode is ^
            if (m_fPrecedence && 0 != m_nPrevOpCode)
            {
                int nPrev = NPrecedenceOfOp(m_nPrevOpCode);
                int nx = NPrecedenceOfOp(m_nLastCom);
                int ni = NPrecedenceOfOp(m_nOpCode);
                if (nx <= nPrev && ni > nPrev) // condition for Precedence Inversion
                {
                    fPrecInvToHigher = true;
                    m_nPrevOpCode = 0; // Once the precedence inversion has put additional brackets, its no longer required
                }
            }
            m_HistoryCollector.ChangeLastBinOp(m_nOpCode, fPrecInvToHigher, m_fIntegerMode);
            DisplayAnnounceBinaryOperator();
            return;
        }

        if (!m_HistoryCollector.FOpndAddedToHistory())
        {
            // if the prev command was ) or unop then it is already in history as a opnd form (...)
            m_HistoryCollector.AddOpndToHistory(m_numberString, m_currentVal);
        }

        /* m_bChangeOp is true if there was an operation done and the   */
        /* current m_currentVal is the result of that operation.  This is so */
        /* entering 3+4+5= gives 7 after the first + and 12 after the */
        /* the =.  The rest of this stuff attempts to do precedence in*/
        /* Scientific mode.                                           */
        if (m_bChangeOp)
        {
        DoPrecedenceCheckAgain:

            int nx = NPrecedenceOfOp((int)wParam);
            int ni = NPrecedenceOfOp(m_nOpCode);

            if ((nx > ni) && m_fPrecedence)
            {
                if (m_precedenceOpCount < MAXPRECDEPTH)
                {
                    m_precedenceVals[m_precedenceOpCount] = m_lastVal;

                    m_nPrecOp[m_precedenceOpCount] = m_nOpCode;
                    m_HistoryCollector.PushLastOpndStart(); // Eg. 1 + 2  *, Need to remember the start of 2 to do Precedence inversion if need to
                }
                else
                {
                    m_precedenceOpCount = MAXPRECDEPTH - 1;
                    HandleErrorCommand(wParam);
                }
                m_precedenceOpCount++;
            }
            else
            {
                /* do the last operation and then if the precedence array is not
                 * empty or the top is not the '(' demarcator then pop the top
                 * of the array and recheck precedence against the new operator
                 */
                m_currentVal = DoOperation(m_nOpCode, m_currentVal, m_lastVal);
                m_nPrevOpCode = m_nOpCode;

                if (!m_bError)
                {
                    DisplayNum();
                    if (!m_fPrecedence)
                    {
                        wstring groupedString = GroupDigitsPerRadix(m_numberString, m_radix);
                        m_HistoryCollector.CompleteEquation(groupedString);
                        m_HistoryCollector.AddOpndToHistory(m_numberString, m_currentVal);
                    }
                }

                if ((m_precedenceOpCount != 0) && (m_nPrecOp[m_precedenceOpCount - 1]))
                {
                    m_precedenceOpCount--;
                    m_nOpCode = m_nPrecOp[m_precedenceOpCount];

                    m_lastVal = m_precedenceVals[m_precedenceOpCount];

                    nx = NPrecedenceOfOp(m_nOpCode);
                    // Precedence Inversion Higher to lower can happen which needs explicit enclosure of brackets
                    // Eg.  1 + 2 * Or 3 Or.  We would have pushed 1+ before, and now last + forces 2 Or 3 to be evaluated
                    // because last Or is less or equal to first + (after 1). But we see that 1+ is in stack and we evaluated to 2 Or 3
                    // This is precedence inversion happened because of operator changed in between. We put extra brackets like
                    // 1 + (2 Or 3)
                    if (ni <= nx)
                    {
                        m_HistoryCollector.EnclosePrecInversionBrackets();
                    }
                    m_HistoryCollector.PopLastOpndStart();
                    goto DoPrecedenceCheckAgain;
                }
            }
        }

        DisplayAnnounceBinaryOperator();
        m_lastVal = m_currentVal;
        m_nOpCode = (int)wParam;
        m_HistoryCollector.AddBinOpToHistory(m_nOpCode, m_fIntegerMode);
        m_bNoPrevEqu = m_bChangeOp = true;
        return;
    }

    // UNARY OPERATORS:
    if (IsUnaryOpCode(wParam) || (wParam == IDC_DEGREES))
    {
        /* Functions are unary operations.                            */
        /* If the last thing done was an operator, m_currentVal was cleared. */
        /* In that case we better use the number before the operator  */
        /* was entered, otherwise, things like 5+ 1/x give Divide By  */
        /* zero.  This way 5+=gives 10 like most calculators do.      */
        if (IsBinOpCode(m_nLastCom))
        {
            m_currentVal = m_lastVal;
        }

        // we do not add percent sign to history or to two line display.
        // instead, we add the result of applying %.
        if (wParam != IDC_PERCENT)
        {
            if (!m_HistoryCollector.FOpndAddedToHistory())
            {
                m_HistoryCollector.AddOpndToHistory(m_numberString, m_currentVal);
            }

            m_HistoryCollector.AddUnaryOpToHistory((int)wParam, m_bInv, m_angletype);
        }

        if ((wParam == IDC_SIN) || (wParam == IDC_COS) || (wParam == IDC_TAN) || (wParam == IDC_SINH) || (wParam == IDC_COSH) || (wParam == IDC_TANH)
            || (wParam == IDC_SEC) || (wParam == IDC_CSC) || (wParam == IDC_COT) || (wParam == IDC_SECH) || (wParam == IDC_CSCH) || (wParam == IDC_COTH))
        {
            if (IsCurrentTooBigForTrig())
            {
                m_currentVal = 0;
                DisplayError(CALC_E_DOMAIN);
                return;
            }
        }

        m_currentVal = SciCalcFunctions(m_currentVal, (uint32_t)wParam);

        if (m_bError)
            return;

        /* Display the result, reset flags, and reset indicators.     */
        DisplayNum();

        if (wParam == IDC_PERCENT)
        {
            CheckAndAddLastBinOpToHistory();
            m_HistoryCollector.AddOpndToHistory(m_numberString, m_currentVal, true /* Add to primary and secondary display */);
        }

        /* reset the m_bInv flag and indicators if it is set
        and have been used */

        if (m_bInv
            && ((wParam == IDC_CHOP) || (wParam == IDC_SIN) || (wParam == IDC_COS) || (wParam == IDC_TAN) || (wParam == IDC_LN) || (wParam == IDC_DMS)
                || (wParam == IDC_DEGREES) || (wParam == IDC_SINH) || (wParam == IDC_COSH) || (wParam == IDC_TANH) || (wParam == IDC_SEC) || (wParam == IDC_CSC)
                || (wParam == IDC_COT) || (wParam == IDC_SECH) || (wParam == IDC_CSCH) || (wParam == IDC_COTH)))
        {
            m_bInv = false;
        }

        return;
    }

    // Tiny binary edit windows clicked. Toggle that bit and update display
    if (IsOpInRange(wParam, IDC_BINEDITSTART, IDC_BINEDITEND))
    {
        // Same reasoning as for unary operators. We need to seed it previous number
        if (IsBinOpCode(m_nLastCom))
        {
            m_currentVal = m_lastVal;
        }

        CheckAndAddLastBinOpToHistory();

        if (TryToggleBit(m_currentVal, (uint32_t)wParam - IDC_BINEDITSTART))
        {
            DisplayNum();
        }

        return;
    }

    /* Now branch off to do other commands and functions.                 */
    switch (wParam)
    {
    case IDC_CLEAR: /* Total clear.                                       */
    {
        if (!m_bChangeOp)
        {
            // Preserve history, if everything done before was a series of unary operations.
            CheckAndAddLastBinOpToHistory(false);
        }

        m_lastVal = 0;

        m_bChangeOp = false;
        m_openParenCount = 0;
        m_precedenceOpCount = m_nTempCom = m_nLastCom = m_nOpCode = 0;
        m_nPrevOpCode = 0;
        m_bNoPrevEqu = true;
        m_carryBit = 0;

        /* clear the parenthesis status box indicator, this will not be
        cleared for CENTR */
        if (nullptr != m_pCalcDisplay)
        {
            m_pCalcDisplay->SetParenthesisNumber(0);
            ClearDisplay();
        }

        m_HistoryCollector.ClearHistoryLine(wstring());
        ClearTemporaryValues();
    }
    break;

    case IDC_CENTR: /* Clear only temporary values.                       */
    {
        // Clear the INV & leave (=xx indicator active
        ClearTemporaryValues();
    }

    break;

    case IDC_BACK:
        // Divide number by the current radix and truncate.
        // Only allow backspace if we're recording.
        if (m_bRecord)
        {
            m_input.Backspace();
            DisplayNum();
        }
        else
        {
            HandleErrorCommand(wParam);
        }
        break;

        /* EQU enables the user to press it multiple times after and      */
        /* operation to enable repeats of the last operation.             */
    case IDC_EQU:
        while (m_openParenCount > 0)
        {
            // when m_bError is set and m_ParNum is non-zero it goes into infinite loop
            if (m_bError)
            {
                break;
            }
            // automatic closing of all the parenthesis to get a meaningful result as well as ensure data integrity
            m_nTempCom = m_nLastCom; // Put back this last saved command to the prev state so ) can be handled properly
            ProcessCommand(IDC_CLOSEP);
            m_nLastCom = m_nTempCom;  // Actually this is IDC_CLOSEP
            m_nTempCom = (int)wParam; // put back in the state where last op seen was IDC_CLOSEP, and current op is IDC_EQU
        }

        if (!m_bNoPrevEqu)
        {
            // It is possible now unary op changed the num in screen, but still m_lastVal hasn't changed.
            m_lastVal = m_currentVal;
        }

        /* Last thing keyed in was an operator.  Lets do the op on*/
        /* a duplicate of the last entry.                     */
        if (IsBinOpCode(m_nLastCom))
        {
            m_currentVal = m_lastVal;
        }

        if (!m_HistoryCollector.FOpndAddedToHistory())
        {
            m_HistoryCollector.AddOpndToHistory(m_numberString, m_currentVal);
        }

        // Evaluate the precedence stack.
        ResolveHighestPrecedenceOperation();
        while (m_fPrecedence && m_precedenceOpCount > 0)
        {
            m_precedenceOpCount--;
            m_nOpCode = m_nPrecOp[m_precedenceOpCount];
            m_lastVal = m_precedenceVals[m_precedenceOpCount];

            // Precedence Inversion check
            int ni = NPrecedenceOfOp(m_nPrevOpCode);
            int nx = NPrecedenceOfOp(m_nOpCode);
            if (ni <= nx)
            {
                m_HistoryCollector.EnclosePrecInversionBrackets();
            }
            m_HistoryCollector.PopLastOpndStart();

            m_bNoPrevEqu = true;

            ResolveHighestPrecedenceOperation();
        }

        if (!m_bError)
        {
            wstring groupedString = GroupDigitsPerRadix(m_numberString, m_radix);
            m_HistoryCollector.CompleteEquation(groupedString);
        }

        m_bChangeOp = false;
        m_nPrevOpCode = 0;

        break;

    case IDC_OPENP:
    case IDC_CLOSEP:

        // -IF- the Paren holding array is full and we try to add a paren
        // -OR- the paren holding array is empty and we try to remove a
        //      paren
        // -OR- the precedence holding array is full
        if ((m_openParenCount >= MAXPRECDEPTH && (wParam == IDC_OPENP)) || (!m_openParenCount && (wParam != IDC_OPENP))
            || ((m_precedenceOpCount >= MAXPRECDEPTH && m_nPrecOp[m_precedenceOpCount - 1] != 0)))
        {
            if (!m_openParenCount && (wParam != IDC_OPENP))
            {
                m_pCalcDisplay->OnNoRightParenAdded();
            }

            HandleErrorCommand(wParam);
            break;
        }

        if (wParam == IDC_OPENP)
        {
            // if there's an omitted multiplication sign 
            if (IsDigitOpCode(m_nLastCom) || IsUnaryOpCode(m_nLastCom) || m_nLastCom == IDC_PNT || m_nLastCom == IDC_CLOSEP)
            {
                ProcessCommand(IDC_MUL);
            }

            CheckAndAddLastBinOpToHistory();
            m_HistoryCollector.AddOpenBraceToHistory();

            // Open level of parentheses, save number and operation.
            m_parenVals[m_openParenCount] = m_lastVal;

            m_nOp[m_openParenCount++] = (m_bChangeOp ? m_nOpCode : 0);

            /* save a special marker on the precedence array */
            if (m_precedenceOpCount < m_nPrecOp.size())
            {
                m_nPrecOp[m_precedenceOpCount++] = 0;
            }

            m_lastVal = 0;
            if (IsBinOpCode(m_nLastCom))
            {
                // We want 1 + ( to start as 1 + (0. Any number you type replaces 0. But if it is 1 + 3 (, it is
                // treated as 1 + (3
                m_currentVal = 0;
            }
            m_nTempCom = 0;
            m_nOpCode = 0;
            m_bChangeOp = false; // a ( is like starting a fresh sub equation
        }
        else
        {
            // Last thing keyed in was an operator. Lets do the op on a duplicate of the last entry.
            if (IsBinOpCode(m_nLastCom))
            {
                m_currentVal = m_lastVal;
            }

            if (!m_HistoryCollector.FOpndAddedToHistory())
            {
                m_HistoryCollector.AddOpndToHistory(m_numberString, m_currentVal);
            }

            // Get the operation and number and return result.
            m_currentVal = DoOperation(m_nOpCode, m_currentVal, m_lastVal);
            m_nPrevOpCode = m_nOpCode;

            // Now process the precedence stack till we get to an opcode which is zero.
            for (m_nOpCode = m_nPrecOp[--m_precedenceOpCount]; m_nOpCode; m_nOpCode = m_nPrecOp[--m_precedenceOpCount])
            {
                // Precedence Inversion check
                int ni = NPrecedenceOfOp(m_nPrevOpCode);
                int nx = NPrecedenceOfOp(m_nOpCode);
                if (ni <= nx)
                {
                    m_HistoryCollector.EnclosePrecInversionBrackets();
                }
                m_HistoryCollector.PopLastOpndStart();

                m_lastVal = m_precedenceVals[m_precedenceOpCount];

                m_currentVal = DoOperation(m_nOpCode, m_currentVal, m_lastVal);
                m_nPrevOpCode = m_nOpCode;
            }

            m_HistoryCollector.AddCloseBraceToHistory();

            // Now get back the operation and opcode at the beginning of this parenthesis pair

            m_openParenCount -= 1;
            m_lastVal = m_parenVals[m_openParenCount];
            m_nOpCode = m_nOp[m_openParenCount];

            // m_bChangeOp should be true if m_nOpCode is valid
            m_bChangeOp = (m_nOpCode != 0);
        }

        // Set the "(=xx" indicator.
        if (nullptr != m_pCalcDisplay)
        {
            m_pCalcDisplay->SetParenthesisNumber(static_cast<unsigned int>(m_openParenCount));
        }

        if (!m_bError)
        {
            DisplayNum();
        }

        break;

        // BASE CHANGES:
    case IDM_HEX:
    case IDM_DEC:
    case IDM_OCT:
    case IDM_BIN:
    {
        SetRadixTypeAndNumWidth((RadixType)(wParam - IDM_HEX), (NUM_WIDTH)-1);
        m_HistoryCollector.UpdateHistoryExpression(m_radix, m_precision);
        break;
    }

    case IDM_QWORD:
    case IDM_DWORD:
    case IDM_WORD:
    case IDM_BYTE:
        if (m_bRecord)
        {
            m_currentVal = m_input.ToRational(m_radix, m_precision);
            m_bRecord = false;
        }

        // Compat. mode BaseX: Qword, Dword, Word, Byte
        SetRadixTypeAndNumWidth((RadixType)-1, (NUM_WIDTH)(wParam - IDM_QWORD));
        break;

    case IDM_DEG:
    case IDM_RAD:
    case IDM_GRAD:
        m_angletype = static_cast<AngleType>(wParam - IDM_DEG);
        break;

    case IDC_SIGN:
    {
        if (m_bRecord)
        {
            if (m_input.TryToggleSign(m_fIntegerMode, GetMaxDecimalValueString()))
            {
                DisplayNum();
            }
            else
            {
                HandleErrorCommand(wParam);
            }
            break;
        }

        // Doing +/- while in Record mode is not a unary operation
        if (IsBinOpCode(m_nLastCom))
        {
            m_currentVal = m_lastVal;
        }

        if (!m_HistoryCollector.FOpndAddedToHistory())
        {
            m_HistoryCollector.AddOpndToHistory(m_numberString, m_currentVal);
        }

        m_currentVal = -(m_currentVal);

        DisplayNum();
        m_HistoryCollector.AddUnaryOpToHistory(IDC_SIGN, m_bInv, m_angletype);
    }
    break;

    case IDC_RECALL:

        if (m_bSetCalcState)
        {
            // Not a Memory recall. set the result
            m_bSetCalcState = false;
        }
        else
        {
            // Recall immediate memory value.
            m_currentVal = *m_memoryValue;
        }
        CheckAndAddLastBinOpToHistory();
        DisplayNum();
        break;

    case IDC_MPLUS:
    {
        /* MPLUS adds m_currentVal to immediate memory and kills the "mem"   */
        /* indicator if the result is zero.                           */
        Rational result = *m_memoryValue + m_currentVal;
        m_memoryValue = make_unique<Rational>(TruncateNumForIntMath(result)); // Memory should follow the current int mode

        break;
    }
    case IDC_MMINUS:
    {
        /* MMINUS subtracts m_currentVal to immediate memory and kills the "mem"   */
        /* indicator if the result is zero.                           */
        Rational result = *m_memoryValue - m_currentVal;
        m_memoryValue = make_unique<Rational>(TruncateNumForIntMath(result));

        break;
    }
    case IDC_STORE:
    case IDC_MCLEAR:
        m_memoryValue = make_unique<Rational>(wParam == IDC_STORE ? TruncateNumForIntMath(m_currentVal) : 0);
        break;
    case IDC_PI:
        if (!m_fIntegerMode)
        {
            CheckAndAddLastBinOpToHistory(); // pi is like entering the number
            m_currentVal = Rational{ (m_bInv ? two_pi : pi) };

            DisplayNum();
            m_bInv = false;
            break;
        }
        HandleErrorCommand(wParam);
        break;
    case IDC_RAND:
        if (!m_fIntegerMode)
        {
            CheckAndAddLastBinOpToHistory(); // rand is like entering the number

            wstringstream str;
            str << fixed << setprecision(m_precision) << GenerateRandomNumber();

            auto rat = StringToRat(false, str.str(), false, L"", m_radix, m_precision);
            if (rat != nullptr)
            {
                m_currentVal = Rational{ rat };
            }
            else
            {
                m_currentVal = Rational{ 0 };
            }
            destroyrat(rat);

            DisplayNum();
            m_bInv = false;
            break;
        }
        HandleErrorCommand(wParam);
        break;
    case IDC_EULER:
        if (!m_fIntegerMode)
        {
            CheckAndAddLastBinOpToHistory(); // e is like entering the number
            m_currentVal = Rational{ rat_exp };

            DisplayNum();
            m_bInv = false;
            break;
        }
        HandleErrorCommand(wParam);
        break;
    case IDC_FE:
        // Toggle exponential notation display.
        m_nFE = NumberFormat(!(int)m_nFE);
        DisplayNum();
        break;

    case IDC_EXP:
        if (m_bRecord && !m_fIntegerMode && m_input.TryBeginExponent())
        {
            DisplayNum();
            break;
        }
        HandleErrorCommand(wParam);
        break;

    case IDC_PNT:
        if (m_bRecord && !m_fIntegerMode && m_input.TryAddDecimalPt())
        {
            DisplayNum();
            break;
        }
        HandleErrorCommand(wParam);
        break;

    case IDC_INV:
        m_bInv = !m_bInv;
        break;
    }
}