492 lines
16 KiB
C++
492 lines
16 KiB
C++
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License.
|
|
|
|
#include "pch.h"
|
|
#pragma once
|
|
#include "Header Files/CalcEngine.h"
|
|
#include "Command.h"
|
|
#include "CalculatorVector.h"
|
|
#include "ExpressionCommand.h"
|
|
#include "CalcException.h"
|
|
|
|
constexpr int ASCII_0 = 48;
|
|
|
|
using namespace std;
|
|
using namespace CalcEngine;
|
|
|
|
void CHistoryCollector::ReinitHistory()
|
|
{
|
|
m_lastOpStartIndex = -1;
|
|
m_lastBinOpStartIndex = -1;
|
|
m_curOperandIndex = 0;
|
|
m_bLastOpndBrace = false;
|
|
if (m_spTokens != nullptr)
|
|
{
|
|
m_spTokens->Clear();
|
|
}
|
|
if (m_spCommands != nullptr)
|
|
{
|
|
m_spCommands->Clear();
|
|
}
|
|
}
|
|
|
|
// Constructor
|
|
// Can throw Out of memory error
|
|
CHistoryCollector::CHistoryCollector(ICalcDisplay *pCalcDisplay, std::shared_ptr<IHistoryDisplay> pHistoryDisplay, wchar_t decimalSymbol) :
|
|
m_pHistoryDisplay(pHistoryDisplay),
|
|
m_pCalcDisplay(pCalcDisplay),
|
|
m_decimalSymbol(decimalSymbol),
|
|
m_iCurLineHistStart(-1)
|
|
{
|
|
ReinitHistory();
|
|
}
|
|
|
|
CHistoryCollector::~CHistoryCollector()
|
|
{
|
|
m_pHistoryDisplay = nullptr;
|
|
m_pCalcDisplay = nullptr;
|
|
|
|
if (m_spTokens != nullptr)
|
|
{
|
|
m_spTokens->Clear();
|
|
}
|
|
}
|
|
|
|
void CHistoryCollector::AddOpndToHistory(wstring_view numStr, Rational const& rat, bool fRepetition)
|
|
{
|
|
std::shared_ptr<CalculatorVector<int>> commands = std::make_shared<CalculatorVector<int>>();
|
|
// Check for negate
|
|
bool fNegative = (numStr[0] == L'-');
|
|
bool fSciFmt = false;
|
|
bool fDecimal = false;
|
|
|
|
for (size_t i = (fNegative ? 1 : 0); i < numStr.length(); i++)
|
|
{
|
|
if (numStr[i] == m_decimalSymbol)
|
|
{
|
|
IFT(commands->Append(IDC_PNT));
|
|
if (!fSciFmt)
|
|
{
|
|
fDecimal = true;
|
|
}
|
|
}
|
|
else if (numStr[i] == L'e')
|
|
{
|
|
IFT(commands->Append(IDC_EXP));
|
|
fSciFmt = true;
|
|
}
|
|
else if (numStr[i] == L'-')
|
|
{
|
|
IFT(commands->Append(IDC_SIGN));
|
|
}
|
|
else if (numStr[i] == L'+')
|
|
{
|
|
// Ignore.
|
|
}
|
|
// Number
|
|
else
|
|
{
|
|
int num = static_cast<int>(numStr[i]) - ASCII_0;
|
|
num += IDC_0;
|
|
IFT(commands->Append(num));
|
|
}
|
|
}
|
|
|
|
auto operandCommand = std::make_shared<COpndCommand>(commands, fNegative, fDecimal, fSciFmt);
|
|
operandCommand->Initialize(rat);
|
|
int iCommandEnd = AddCommand(operandCommand);
|
|
m_lastOpStartIndex = IchAddSzToEquationSz(numStr, iCommandEnd);
|
|
|
|
if (fRepetition)
|
|
{
|
|
SetExpressionDisplay();
|
|
}
|
|
m_bLastOpndBrace = false;
|
|
m_lastBinOpStartIndex = -1;
|
|
}
|
|
|
|
void CHistoryCollector::RemoveLastOpndFromHistory()
|
|
{
|
|
TruncateEquationSzFromIch(m_lastOpStartIndex);
|
|
SetExpressionDisplay();
|
|
m_lastOpStartIndex = -1;
|
|
// This will not restore the m_lastBinOpStartIndex, as it isn't possible to remove that also later
|
|
}
|
|
|
|
void CHistoryCollector::AddBinOpToHistory(int nOpCode, bool fNoRepetition)
|
|
{
|
|
int iCommandEnd = AddCommand(std::make_shared<CBinaryCommand>(nOpCode));
|
|
m_lastBinOpStartIndex = IchAddSzToEquationSz(L" ", -1);
|
|
|
|
IchAddSzToEquationSz(CCalcEngine::OpCodeToString(nOpCode), iCommandEnd);
|
|
IchAddSzToEquationSz(L" ", -1);
|
|
|
|
if (fNoRepetition)
|
|
{
|
|
SetExpressionDisplay();
|
|
}
|
|
m_lastOpStartIndex = -1;
|
|
}
|
|
|
|
// This is expected to be called when a binary op in the last say 1+2+ is changing to another one say 1+2* (+ changed to *)
|
|
// It needs to know by this change a Precedence inversion happenned. i.e. previous op was lower or equal to its previous op, but the new
|
|
// one isn't. (Eg. 1*2* to 1*2^). It can add explicit brackets to ensure the precedence is inverted. (Eg. (1*2) ^)
|
|
void CHistoryCollector::ChangeLastBinOp(int nOpCode, bool fPrecInvToHigher)
|
|
{
|
|
TruncateEquationSzFromIch(m_lastBinOpStartIndex);
|
|
if (fPrecInvToHigher)
|
|
{
|
|
EnclosePrecInvertionBrackets();
|
|
}
|
|
AddBinOpToHistory(nOpCode);
|
|
}
|
|
|
|
void CHistoryCollector::PushLastOpndStart(int ichOpndStart)
|
|
{
|
|
int ich = (ichOpndStart == -1) ? m_lastOpStartIndex : ichOpndStart;
|
|
|
|
if (m_curOperandIndex < static_cast<int>(m_operandIndices.size()))
|
|
{
|
|
m_operandIndices[m_curOperandIndex++] = ich;
|
|
}
|
|
}
|
|
|
|
void CHistoryCollector::PopLastOpndStart()
|
|
{
|
|
if (m_curOperandIndex > 0)
|
|
{
|
|
m_lastOpStartIndex = m_operandIndices[--m_curOperandIndex];
|
|
}
|
|
}
|
|
|
|
void CHistoryCollector::AddOpenBraceToHistory()
|
|
{
|
|
int iCommandEnd = AddCommand(std::make_shared<CParentheses>(IDC_OPENP));
|
|
int ichOpndStart = IchAddSzToEquationSz(CCalcEngine::OpCodeToString(IDC_OPENP), -1);
|
|
PushLastOpndStart(ichOpndStart);
|
|
|
|
SetExpressionDisplay();
|
|
m_lastBinOpStartIndex = -1;
|
|
}
|
|
|
|
void CHistoryCollector::AddCloseBraceToHistory()
|
|
{
|
|
int iCommandEnd = AddCommand(std::make_shared<CParentheses>(IDC_CLOSEP));
|
|
IchAddSzToEquationSz(CCalcEngine::OpCodeToString(IDC_CLOSEP), -1);
|
|
SetExpressionDisplay();
|
|
PopLastOpndStart();
|
|
|
|
m_lastBinOpStartIndex = -1;
|
|
m_bLastOpndBrace = true;
|
|
}
|
|
|
|
void CHistoryCollector::EnclosePrecInvertionBrackets()
|
|
{
|
|
// Top of the Opnd starts index or 0 is nothing is in top
|
|
int ichStart = (m_curOperandIndex > 0) ? m_operandIndices[m_curOperandIndex - 1] : 0;
|
|
|
|
InsertSzInEquationSz(CCalcEngine::OpCodeToString(IDC_OPENP), -1, ichStart);
|
|
IchAddSzToEquationSz(CCalcEngine::OpCodeToString(IDC_CLOSEP), -1);
|
|
}
|
|
|
|
bool CHistoryCollector::FOpndAddedToHistory()
|
|
{
|
|
return (-1 != m_lastOpStartIndex);
|
|
}
|
|
|
|
// AddUnaryOpToHistory
|
|
//
|
|
// This is does the postfix to prefix transalation of the input and adds the text to the history. Eg. doing 2 + 4 (sqrt),
|
|
// this routine will ensure the last sqrt call unary operator, actually goes back in history and wraps 4 in sqrt(4)
|
|
//
|
|
void CHistoryCollector::AddUnaryOpToHistory(int nOpCode, bool fInv, ANGLE_TYPE angletype)
|
|
{
|
|
int iCommandEnd;
|
|
// When successfully applying a unary op, there should be an opnd already
|
|
// A very special case of % which is a funny post op unary op.
|
|
if (IDC_PERCENT == nOpCode)
|
|
{
|
|
iCommandEnd = AddCommand(std::make_shared<CUnaryCommand>(nOpCode));
|
|
IchAddSzToEquationSz(CCalcEngine::OpCodeToString(nOpCode), iCommandEnd);
|
|
}
|
|
else // all the other unary ops
|
|
{
|
|
std::shared_ptr<IOperatorCommand> spExpressionCommand;
|
|
if (IDC_SIGN == nOpCode)
|
|
{
|
|
spExpressionCommand = std::make_shared<CUnaryCommand>(nOpCode);
|
|
}
|
|
else
|
|
{
|
|
CalculationManager::Command angleOpCode;
|
|
if (angletype == ANGLE_DEG)
|
|
{
|
|
angleOpCode = CalculationManager::Command::CommandDEG;
|
|
}
|
|
if (angletype == ANGLE_RAD)
|
|
{
|
|
angleOpCode = CalculationManager::Command::CommandRAD;
|
|
}
|
|
if (angletype == ANGLE_GRAD)
|
|
{
|
|
angleOpCode = CalculationManager::Command::CommandGRAD;
|
|
}
|
|
|
|
int command = nOpCode;
|
|
switch (nOpCode)
|
|
{
|
|
case IDC_SIN:
|
|
command = fInv ? static_cast<int>(CalculationManager::Command::CommandASIN) : IDC_SIN;
|
|
spExpressionCommand = std::make_shared<CUnaryCommand>(static_cast<int>(angleOpCode), command);
|
|
break;
|
|
case IDC_COS:
|
|
command = fInv ? static_cast<int>(CalculationManager::Command::CommandACOS) : IDC_COS;
|
|
spExpressionCommand = std::make_shared<CUnaryCommand>(static_cast<int>(angleOpCode), command);
|
|
break;
|
|
case IDC_TAN:
|
|
command = fInv ? static_cast<int>(CalculationManager::Command::CommandATAN) : IDC_TAN;
|
|
spExpressionCommand = std::make_shared<CUnaryCommand>(static_cast<int>(angleOpCode), command);
|
|
break;
|
|
case IDC_SINH:
|
|
command = fInv ? static_cast<int>(CalculationManager::Command::CommandASINH) : IDC_SINH;
|
|
spExpressionCommand = std::make_shared<CUnaryCommand>(command);
|
|
break;
|
|
case IDC_COSH:
|
|
command = fInv ? static_cast<int>(CalculationManager::Command::CommandACOSH) : IDC_COSH;
|
|
spExpressionCommand = std::make_shared<CUnaryCommand>(command);
|
|
break;
|
|
case IDC_TANH:
|
|
command = fInv ? static_cast<int>(CalculationManager::Command::CommandATANH) : IDC_TANH;
|
|
spExpressionCommand = std::make_shared<CUnaryCommand>(command);
|
|
break;
|
|
case IDC_LN:
|
|
command = fInv ? static_cast<int>(CalculationManager::Command::CommandPOWE) : IDC_LN;
|
|
spExpressionCommand = std::make_shared<CUnaryCommand>(command);
|
|
break;
|
|
default:
|
|
spExpressionCommand = std::make_shared<CUnaryCommand>(nOpCode);
|
|
}
|
|
}
|
|
|
|
iCommandEnd = AddCommand(spExpressionCommand);
|
|
|
|
wstring operandStr{ CCalcEngine::OpCodeToUnaryString(nOpCode, fInv, angletype) };
|
|
if (!m_bLastOpndBrace) // The opnd is already covered in braces. No need for additional braces around it
|
|
{
|
|
operandStr.append(CCalcEngine::OpCodeToString(IDC_OPENP));
|
|
}
|
|
InsertSzInEquationSz(operandStr, iCommandEnd, m_lastOpStartIndex);
|
|
|
|
if (!m_bLastOpndBrace)
|
|
{
|
|
IchAddSzToEquationSz(CCalcEngine::OpCodeToString(IDC_CLOSEP), -1);
|
|
}
|
|
}
|
|
|
|
SetExpressionDisplay();
|
|
m_bLastOpndBrace = false;
|
|
// m_lastOpStartIndex remains the same as last opnd is just replaced by unaryop(lastopnd)
|
|
m_lastBinOpStartIndex = -1;
|
|
}
|
|
|
|
// Called after = with the result of the equation
|
|
// Responsible for clearing the top line of current running history display, as well as adding yet another element to
|
|
// history of equations
|
|
void CHistoryCollector::CompleteHistoryLine(wstring_view numStr)
|
|
{
|
|
if (nullptr != m_pCalcDisplay)
|
|
{
|
|
m_pCalcDisplay->SetExpressionDisplay(std::make_shared<CalculatorVector<std::pair<std::wstring, int>>>(), std::make_shared<CalculatorVector<std::shared_ptr<IExpressionCommand>>>());
|
|
}
|
|
|
|
if (nullptr != m_pHistoryDisplay)
|
|
{
|
|
unsigned int addedItemIndex = m_pHistoryDisplay->AddToHistory(m_spTokens, m_spCommands, numStr);
|
|
m_pCalcDisplay->OnHistoryItemAdded(addedItemIndex);
|
|
}
|
|
|
|
m_spTokens = nullptr;
|
|
m_spCommands = nullptr;
|
|
m_iCurLineHistStart = -1; // It will get recomputed at the first Opnd
|
|
ReinitHistory();
|
|
}
|
|
|
|
void CHistoryCollector::ClearHistoryLine(wstring_view errStr)
|
|
{
|
|
if (errStr.empty()) // in case of error let the display stay as it is
|
|
{
|
|
if (nullptr != m_pCalcDisplay)
|
|
{
|
|
m_pCalcDisplay->SetExpressionDisplay(std::make_shared<CalculatorVector<std::pair<std::wstring, int>>>(), std::make_shared<CalculatorVector<std::shared_ptr<IExpressionCommand>>>());
|
|
}
|
|
m_iCurLineHistStart = -1; // It will get recomputed at the first Opnd
|
|
ReinitHistory();
|
|
}
|
|
}
|
|
|
|
|
|
// Adds the given string psz to the globally maintained current equation string at the end.
|
|
// Also returns the 0 based index in the string just added. Can throw out of memory error
|
|
int CHistoryCollector::IchAddSzToEquationSz(wstring_view str, int icommandIndex)
|
|
{
|
|
if (m_spTokens == nullptr)
|
|
{
|
|
m_spTokens = std::make_shared<CalculatorVector<std::pair<std::wstring, int>>>();
|
|
}
|
|
|
|
if (FAILED(m_spTokens->Append(std::make_pair(wstring(str), icommandIndex))))
|
|
{
|
|
throw(CALC_E_OUTOFMEMORY);
|
|
}
|
|
|
|
unsigned int nTokens;
|
|
m_spTokens->GetSize(&nTokens);
|
|
return nTokens - 1;
|
|
}
|
|
|
|
// Inserts a given string into the global m_pszEquation at the given index ich taking care of reallocations etc.
|
|
void CHistoryCollector::InsertSzInEquationSz(wstring_view str, int icommandIndex, int ich)
|
|
{
|
|
if (FAILED(m_spTokens->InsertAt(ich, std::make_pair(wstring(str), icommandIndex))))
|
|
{
|
|
throw(CALC_E_OUTOFMEMORY);
|
|
}
|
|
}
|
|
|
|
// Chops off the current equation string from the given index
|
|
void CHistoryCollector::TruncateEquationSzFromIch(int ich)
|
|
{
|
|
// Truncate commands
|
|
int minIdx = -1;
|
|
unsigned int nTokens = 0;
|
|
std::pair<std::wstring, int> currentPair;
|
|
m_spTokens->GetSize(&nTokens);
|
|
|
|
for (unsigned int i = ich; i < nTokens; i++)
|
|
{
|
|
IFT(m_spTokens->GetAt(i, ¤tPair));
|
|
int curTokenId = currentPair.second;
|
|
if (curTokenId != -1)
|
|
{
|
|
if ((minIdx != -1) || (curTokenId < minIdx))
|
|
{
|
|
minIdx = curTokenId;
|
|
IFT(m_spCommands->Truncate(minIdx));
|
|
}
|
|
}
|
|
}
|
|
|
|
IFT(m_spTokens->Truncate(ich));
|
|
}
|
|
|
|
// Adds the m_pszEquation into the running history text
|
|
void CHistoryCollector::SetExpressionDisplay()
|
|
{
|
|
if (nullptr != m_pCalcDisplay)
|
|
{
|
|
m_pCalcDisplay->SetExpressionDisplay(m_spTokens, m_spCommands);
|
|
}
|
|
|
|
}
|
|
|
|
int CHistoryCollector::AddCommand(_In_ const std::shared_ptr<IExpressionCommand> & spCommand)
|
|
{
|
|
if (m_spCommands == nullptr)
|
|
{
|
|
m_spCommands = std::make_shared <CalculatorVector<std::shared_ptr<IExpressionCommand>>>();
|
|
}
|
|
|
|
if (FAILED(m_spCommands->Append(spCommand)))
|
|
{
|
|
throw(CALC_E_OUTOFMEMORY);
|
|
}
|
|
|
|
unsigned int nCommmands = 0;
|
|
m_spCommands->GetSize(&nCommmands);
|
|
return nCommmands - 1;
|
|
}
|
|
|
|
//To Update the operands in the Expression according to the current Radix
|
|
void CHistoryCollector::UpdateHistoryExpression(uint32_t radix, int32_t precision)
|
|
{
|
|
if (m_spTokens != nullptr)
|
|
{
|
|
unsigned int size;
|
|
IFT(m_spTokens->GetSize(&size));
|
|
|
|
for (unsigned int i = 0; i < size; ++i)
|
|
{
|
|
std::pair<std::wstring, int> token;
|
|
IFT(m_spTokens->GetAt(i, &token));
|
|
int commandPosition = token.second;
|
|
if (commandPosition != -1)
|
|
{
|
|
std::shared_ptr<IExpressionCommand> expCommand;
|
|
IFT(m_spCommands->GetAt(commandPosition, &expCommand));
|
|
if (expCommand != nullptr && CalculationManager::CommandType::OperandCommand == expCommand->GetCommandType())
|
|
{
|
|
std::shared_ptr<COpndCommand> opndCommand = std::static_pointer_cast<COpndCommand>(expCommand);
|
|
if (opndCommand != nullptr)
|
|
{
|
|
token.first = opndCommand->GetString(radix, precision, m_decimalSymbol);
|
|
IFT(m_spTokens->SetAt(i, token));
|
|
opndCommand->SetCommands(GetOperandCommandsFromString(token.first));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
SetExpressionDisplay();
|
|
}
|
|
}
|
|
|
|
void CHistoryCollector::SetDecimalSymbol(wchar_t decimalSymbol)
|
|
{
|
|
m_decimalSymbol = decimalSymbol;
|
|
}
|
|
|
|
//Update the commands corresponding to the passed string Number
|
|
std::shared_ptr<CalculatorVector<int>> CHistoryCollector::GetOperandCommandsFromString(wstring_view numStr)
|
|
{
|
|
std::shared_ptr<CalculatorVector<int>> commands = std::make_shared<CalculatorVector<int>>();
|
|
// Check for negate
|
|
bool fNegative = (numStr[0] == L'-');
|
|
bool fSciFmt = false;
|
|
bool fDecimal = false;
|
|
|
|
for (size_t i = (fNegative ? 1 : 0); i < numStr.length(); i++)
|
|
{
|
|
if (numStr[i] == m_decimalSymbol)
|
|
{
|
|
IFT(commands->Append(IDC_PNT));
|
|
fDecimal = true;
|
|
}
|
|
else if (numStr[i] == L'e')
|
|
{
|
|
IFT(commands->Append(IDC_EXP));
|
|
fSciFmt = true;
|
|
}
|
|
else if (numStr[i] == L'-')
|
|
{
|
|
IFT(commands->Append(IDC_SIGN));
|
|
}
|
|
else if (numStr[i] == L'+')
|
|
{
|
|
// Ignore.
|
|
}
|
|
// Number
|
|
else
|
|
{
|
|
int num = static_cast<int>(numStr[i]) - ASCII_0;
|
|
num += IDC_0;
|
|
IFT(commands->Append(num));
|
|
}
|
|
}
|
|
|
|
// If the number is negative, append a sign command at the end.
|
|
if (fNegative)
|
|
{
|
|
IFT(commands->Append(IDC_SIGN));
|
|
}
|
|
return commands;
|
|
}
|