From fc19ddcbcbb476284dd5c53feb64d7b5da27e327 Mon Sep 17 00:00:00 2001 From: Pepe Rivera Date: Wed, 25 Mar 2020 15:18:34 -0700 Subject: [PATCH] Improve error handling by displaying an error message (#1075) * wire up error messages * more errors * fix crash on render error * Always show copy and cut * PR comments * Fix spelling --- .../GraphingCalculator/EquationViewModel.cpp | 160 +++++++++++++- .../GraphingCalculator/EquationViewModel.h | 2 + src/Calculator/Controls/EquationTextBox.cpp | 5 +- src/Calculator/Controls/EquationTextBox.h | 1 + src/Calculator/Resources/en-US/Resources.resw | 158 +++++++++++++- .../GraphingCalculator/EquationInputArea.xaml | 33 +-- src/GraphControl/Control/Grapher.cpp | 25 ++- src/GraphControl/Control/Grapher.h | 2 + src/GraphControl/DirectX/RenderMain.cpp | 9 +- src/GraphControl/DirectX/RenderMain.h | 4 + src/GraphControl/Models/Equation.h | 196 ++++++++++++++++++ src/GraphingImpl/Mocks/Graph.h | 5 + src/GraphingImpl/Mocks/MathSolver.h | 6 +- src/GraphingInterfaces/IGraph.h | 2 + src/GraphingInterfaces/IMathSolver.h | 6 +- 15 files changed, 582 insertions(+), 32 deletions(-) diff --git a/src/CalcViewModel/GraphingCalculator/EquationViewModel.cpp b/src/CalcViewModel/GraphingCalculator/EquationViewModel.cpp index 94b9779..4319fed 100644 --- a/src/CalcViewModel/GraphingCalculator/EquationViewModel.cpp +++ b/src/CalcViewModel/GraphingCalculator/EquationViewModel.cpp @@ -11,6 +11,7 @@ using namespace Graphing; using namespace Platform; using namespace Platform::Collections; using namespace std; +using namespace Windows::ApplicationModel::Resources; using namespace Windows::UI; using namespace Windows::UI::Xaml; using namespace Windows::Foundation::Collections; @@ -36,7 +37,7 @@ namespace CalculatorApp::ViewModel : m_AnalysisErrorVisible{ false } , m_FunctionLabelIndex{ functionLabelIndex } , m_KeyGraphFeaturesItems{ ref new Vector() } - , m_resourceLoader{ Windows::ApplicationModel::Resources::ResourceLoader::GetForCurrentView() } + , m_resourceLoader{ ::ResourceLoader::GetForCurrentView() } { if (equation == nullptr) { @@ -72,11 +73,14 @@ namespace CalculatorApp::ViewModel AddKeyGraphFeature(m_resourceLoader->GetString(L"YIntercept"), graphEquation->YIntercept, m_resourceLoader->GetString(L"KGFYInterceptNone")); AddKeyGraphFeature(m_resourceLoader->GetString(L"Minima"), graphEquation->Minima, m_resourceLoader->GetString(L"KGFMinimaNone")); AddKeyGraphFeature(m_resourceLoader->GetString(L"Maxima"), graphEquation->Maxima, m_resourceLoader->GetString(L"KGFMaximaNone")); - AddKeyGraphFeature(m_resourceLoader->GetString(L"InflectionPoints"), graphEquation->InflectionPoints, m_resourceLoader->GetString(L"KGFInflectionPointsNone")); + AddKeyGraphFeature( + m_resourceLoader->GetString(L"InflectionPoints"), graphEquation->InflectionPoints, m_resourceLoader->GetString(L"KGFInflectionPointsNone")); AddKeyGraphFeature( m_resourceLoader->GetString(L"VerticalAsymptotes"), graphEquation->VerticalAsymptotes, m_resourceLoader->GetString(L"KGFVerticalAsymptotesNone")); AddKeyGraphFeature( - m_resourceLoader->GetString(L"HorizontalAsymptotes"), graphEquation->HorizontalAsymptotes, m_resourceLoader->GetString(L"KGFHorizontalAsymptotesNone")); + m_resourceLoader->GetString(L"HorizontalAsymptotes"), + graphEquation->HorizontalAsymptotes, + m_resourceLoader->GetString(L"KGFHorizontalAsymptotesNone")); AddKeyGraphFeature( m_resourceLoader->GetString(L"ObliqueAsymptotes"), graphEquation->ObliqueAsymptotes, m_resourceLoader->GetString(L"KGFObliqueAsymptotesNone")); AddParityKeyGraphFeature(graphEquation); @@ -299,4 +303,154 @@ namespace CalculatorApp::ViewModel KeyGraphFeaturesItems->Append(tooComplexItem); } + + String ^ EquationViewModel::EquationErrorText(ErrorType errorType, int errorCode) + { + auto resLoader = ResourceLoader::GetForCurrentView(); + if (errorType == ::ErrorType::Evaluation) + { + switch (static_cast(errorCode)) + { + case (EvaluationErrorCode::Overflow): + return resLoader->GetString(L"Overflow"); + break; + case (EvaluationErrorCode::RequireRadiansMode): + return resLoader->GetString(L"RequireRadiansMode"); + break; + case (EvaluationErrorCode::TooComplexToSolve): + return resLoader->GetString(L"TooComplexToSolve"); + break; + case (EvaluationErrorCode::RequireDegreesMode): + return resLoader->GetString(L"RequireDegreesMode"); + break; + case (EvaluationErrorCode::FactorialInvalidArgument): + case (EvaluationErrorCode::Factorial2InvalidArgument): + return resLoader->GetString(L"FactorialInvalidArgument"); + break; + case (EvaluationErrorCode::FactorialCannotPerformOnLargeNumber): + return resLoader->GetString(L"FactorialCannotPerformOnLargeNumber"); + break; + case (EvaluationErrorCode::ModuloCannotPerformOnFloat): + return resLoader->GetString(L"ModuloCannotPerformOnFloat"); + break; + case (EvaluationErrorCode::EquationTooComplexToSolve): + case (EvaluationErrorCode::EquationTooComplexToSolveSymbolic): + case (EvaluationErrorCode::EquationTooComplexToPlot): + case (EvaluationErrorCode::InequalityTooComplexToSolve): + case (EvaluationErrorCode::GE_TooComplexToSolve): + return resLoader->GetString(L"TooComplexToSolve"); + break; + case (EvaluationErrorCode::EquationHasNoSolution): + case (EvaluationErrorCode::InequalityHasNoSolution): + return resLoader->GetString(L"EquationHasNoSolution"); + break; + case (EvaluationErrorCode::DivideByZero): + return resLoader->GetString(L"DivideByZero"); + break; + case (EvaluationErrorCode::MutuallyExclusiveConditions): + return resLoader->GetString(L"MutuallyExclusiveConditions"); + break; + case (EvaluationErrorCode::OutOfDomain): + return resLoader->GetString(L"OutOfDomain"); + break; + case (EvaluationErrorCode::GE_NotSupported): + return resLoader->GetString(L"GE_NotSupported"); + break; + default: + return resLoader->GetString(L"GeneralError"); + break; + } + } + else if (errorType == ::ErrorType::Syntax) + { + switch (static_cast(errorCode)) + { + case (SyntaxErrorCode::ParenthesisMismatch): + return resLoader->GetString(L"ParenthesisMismatch"); + break; + case (SyntaxErrorCode::UnmatchedParenthesis): + return resLoader->GetString(L"UnmatchedParenthesis"); + break; + case (SyntaxErrorCode::TooManyDecimalPoints): + return resLoader->GetString(L"TooManyDecimalPoints"); + break; + case (SyntaxErrorCode::DecimalPointWithoutDigits): + return resLoader->GetString(L"DecimalPointWithoutDigits"); + break; + case (SyntaxErrorCode::UnexpectedEndOfExpression): + return resLoader->GetString(L"UnexpectedEndOfExpression"); + break; + case (SyntaxErrorCode::UnexpectedToken): + return resLoader->GetString(L"UnexpectedToken"); + break; + case (SyntaxErrorCode::InvalidToken): + return resLoader->GetString(L"InvalidToken"); + break; + case (SyntaxErrorCode::TooManyEquals): + return resLoader->GetString(L"TooManyEquals"); + break; + case (SyntaxErrorCode::EqualWithoutGraphVariable): + return resLoader->GetString(L"EqualWithoutGraphVariable"); + break; + case (SyntaxErrorCode::InvalidEquationSyntax): + case (SyntaxErrorCode::InvalidEquationFormat): + return resLoader->GetString(L"InvalidEquationSyntax"); + break; + case (SyntaxErrorCode::EmptyExpression): + return resLoader->GetString(L"EmptyExpression"); + break; + case (SyntaxErrorCode::EqualWithoutEquation): + return resLoader->GetString(L"EqualWithoutEquation"); + break; + case (SyntaxErrorCode::ExpectParenthesisAfterFunctionName): + return resLoader->GetString(L"ExpectParenthesisAfterFunctionName"); + break; + case (SyntaxErrorCode::IncorrectNumParameter): + return resLoader->GetString(L"IncorrectNumParameter"); + break; + case (SyntaxErrorCode::InvalidVariableNameFormat): + return resLoader->GetString(L"InvalidVariableNameFormat"); + break; + case (SyntaxErrorCode::BracketMismatch): + return resLoader->GetString(L"BracketMismatch"); + break; + case (SyntaxErrorCode::UnmatchedBracket): + return resLoader->GetString(L"UnmatchedBracket"); + break; + case (SyntaxErrorCode::CannotUseIInReal): + return resLoader->GetString(L"CannotUseIInReal"); + break; + case (SyntaxErrorCode::InvalidNumberDigit): + return resLoader->GetString(L"InvalidNumberDigit"); + break; + case (SyntaxErrorCode::InvalidNumberBase): + return resLoader->GetString(L"InvalidNumberBase"); + break; + case (SyntaxErrorCode::InvalidVariableSpecification): + return resLoader->GetString(L"InvalidVariableSpecification"); + break; + case (SyntaxErrorCode::ExpectingLogicalOperands): + case (SyntaxErrorCode::ExpectingScalarOperands): + return resLoader->GetString(L"ExpectingLogicalOperands"); + break; + case (SyntaxErrorCode::CannotUseIndexVarInOpLimits): + return resLoader->GetString(L"CannotUseIndexVarInOpLimits"); + break; + case (SyntaxErrorCode::CannotUseIndexVarInLimPoint): + return resLoader->GetString(L"Overflow"); + break; + case (SyntaxErrorCode::CannotUseComplexInfinityInReal): + return resLoader->GetString(L"CannotUseComplexInfinityInReal"); + break; + case (SyntaxErrorCode::CannotUseIInInequalitySolving): + return resLoader->GetString(L"CannotUseIInInequalitySolving"); + break; + default: + return resLoader->GetString(L"GeneralError"); + break; + } + } + + return resLoader->GetString(L"GeneralError"); + } } diff --git a/src/CalcViewModel/GraphingCalculator/EquationViewModel.h b/src/CalcViewModel/GraphingCalculator/EquationViewModel.h index 143254b..e836f79 100644 --- a/src/CalcViewModel/GraphingCalculator/EquationViewModel.h +++ b/src/CalcViewModel/GraphingCalculator/EquationViewModel.h @@ -103,6 +103,8 @@ public void PopulateKeyGraphFeatures(GraphControl::KeyGraphFeaturesInfo ^ info); + static Platform::String ^ EquationErrorText(GraphControl::ErrorType errorType, int errorCode); + private: void AddKeyGraphFeature(Platform::String ^ title, Platform::String ^ expression, Platform::String ^ errorString); void AddKeyGraphFeature( diff --git a/src/Calculator/Controls/EquationTextBox.cpp b/src/Calculator/Controls/EquationTextBox.cpp index 5a607eb..3c4f147 100644 --- a/src/Calculator/Controls/EquationTextBox.cpp +++ b/src/Calculator/Controls/EquationTextBox.cpp @@ -27,6 +27,7 @@ DEPENDENCY_PROPERTY_INITIALIZATION(EquationTextBox, EquationButtonContentIndex); DEPENDENCY_PROPERTY_INITIALIZATION(EquationTextBox, HasError); DEPENDENCY_PROPERTY_INITIALIZATION(EquationTextBox, IsAddEquationMode); DEPENDENCY_PROPERTY_INITIALIZATION(EquationTextBox, MathEquation); +DEPENDENCY_PROPERTY_INITIALIZATION(EquationTextBox, ErrorText); DEPENDENCY_PROPERTY_INITIALIZATION(EquationTextBox, IsEquationLineDisabled); EquationTextBox::EquationTextBox() @@ -432,12 +433,12 @@ void EquationTextBox::OnRichEditMenuOpened(Object ^ /*sender*/, Object ^ /*args* if (m_richEditBox != nullptr && m_cutMenuItem != nullptr) { - m_cutMenuItem->IsEnabled = m_richEditBox->TextDocument->CanCopy() && m_richEditBox->TextDocument->Selection->Length > 0; + m_cutMenuItem->IsEnabled = m_richEditBox->TextDocument->CanCopy(); } if (m_richEditBox != nullptr && m_copyMenuItem != nullptr) { - m_copyMenuItem->IsEnabled = m_richEditBox->TextDocument->CanCopy() && m_richEditBox->TextDocument->Selection->Length > 0; + m_copyMenuItem->IsEnabled = m_richEditBox->TextDocument->CanCopy(); } if (m_richEditBox != nullptr && m_pasteMenuItem != nullptr) diff --git a/src/Calculator/Controls/EquationTextBox.h b/src/Calculator/Controls/EquationTextBox.h index cb768a6..a9fcc26 100644 --- a/src/Calculator/Controls/EquationTextBox.h +++ b/src/Calculator/Controls/EquationTextBox.h @@ -25,6 +25,7 @@ namespace CalculatorApp DEPENDENCY_PROPERTY(Platform::String ^, MathEquation); DEPENDENCY_PROPERTY_WITH_CALLBACK(bool, HasError); DEPENDENCY_PROPERTY_WITH_CALLBACK(bool, IsAddEquationMode); + DEPENDENCY_PROPERTY(Platform::String ^, ErrorText); DEPENDENCY_PROPERTY(bool, IsEquationLineDisabled); PROPERTY_R(bool, HasFocus); diff --git a/src/Calculator/Resources/en-US/Resources.resw b/src/Calculator/Resources/en-US/Resources.resw index 3bbd502..4752336 100644 --- a/src/Calculator/Resources/en-US/Resources.resw +++ b/src/Calculator/Resources/en-US/Resources.resw @@ -4106,7 +4106,163 @@ Unable to calculate the range for this function. Error displayed when Range is not returned from the analyzer. - + + Overflow (the number is too large) + Error that occurs during graphing when the number is too large. To see this error, assign a large number to variable a, then keep doing "a:=a*a" until it happens. + + + Radians mode is required to graph this equation. + Error that occurs during graphing when radians is required. + + + This function is too complex to graph + Error that occurs during graphing when the equation is too complex. + + + Degrees mode is required to graph this function + Error that occurs during graphing when degrees is required + + + The factorial function has an invalid argument + Error that occurs during graphing when a factorial function has an invalid argument. + + + The factorial function has an argument that is too large to graph + Error that occurs during graphing when a factorial has a large n + + + Modulo can only be used with whole numbers + Error that occurs during graphing when modulo is used with a float. + + + The equation has no solution + Error that occurs during graphing when the equation has no solution. + + + Cannot divide by zero + Error that occurs during graphing when a divison by zero occurs. + + + The equation contains logical conditions that are mutually exclusive + Error that occurs during graphing when mutually exclusive conditions are used. + + + Equation is out of domain + Error that occurs during graphing when the equation is out of domain. + + + Graphing this equation is not supported + Error that occurs during graphing when the equation is not supported. + + + The equation is missing an opening parenthesis + Error that occurs during graphing when the equation is missing a ( + + + The equation is missing a closing parenthesis + Error that occurs during graphing when the equation is missing a ) + + + There are too many decimal points in a number + Error that occurs during graphing when a number has too many decimals. Ex: 1.2.3 + + + A decimal point is missing digits + Error that occurs during graphing with a decimal point without digits + + + Unexpected end of expression + Error that occurs during graphing when the expression ends unexpectedly. Ex: 3-4* + + + Unexpected characters in the expression + Error that occurs during graphing when there is an unexpected token. + + + Invalid characters in the expression + Error that occurs during graphing when there is an invalid token. + + + There are too many equal signs + Error that occurs during graphing when there are too many equals. + + + The function must contain at least one x or y variable + Error that occurs during graphing when the equation is missing x or y. + + + Invalid expression + Error that occurs during graphing when an invalid syntax is used. + + + The expression is empty + Error that occurs during graphing when the expression is empty + + + Equal was used without an equation + Error that occurs during graphing when equal is used without an equation. Ex: sin(x=y) + + + Parenthesis missing after function name + Error that occurs during graphing when parenthesis are missing after a function. + + + A mathematical operation has the incorrect number of parameters + Error that occurs during graphing when a function has the wrong number of parameters + + + A variable name is invalid + Error that occurs during graphing when a variable name is invalid. + + + The equation is missing an opening bracket + Error that occurs during graphing when a { is missing + + + The equation is missing a closing bracket + Error that occurs during graphing when a } is missing. + + + "i" and "I" cannot be used as variable names + Error that occurs during graphing when i or I is used. + + + The equation could not be graphed + General error that occurs during graphing. + + + The digit could not be resolved for the given base + Error that occurs during graphing when trying to use bases incorrect. Ex: base(2,1020). + + + The base must be greater than 2 and less than 36 + Error that occurs during graphing when the base is out of range. + + + A mathematical operation requires one of its paramaters to be a variable + Error that occurs during graphing when a function requires a variable in a particular position. Ex: 2nd argument of deriv. + + + Equation is mixing logical and scalar operands + Error that occurs during graphing when operands are mixed. Such as true and 1. + + + x or y cannot be used in the upper or lower limits + Error that occurs during graphing when x or y is used in integral upper limits. + + + x or y cannot be used in the limit point + Error that occurs during graphing when x or y is used in the limit point. + + + Cannot use complex infinity + Error that occurs during graphing when complex infinity is used + + + Cannot use complex numbers in inequalities + Error that occurs during graphing when complex numbers are used in inequalities. + + Back This is the tooltip for the back button in the equation analysis page in the graphing calculator diff --git a/src/Calculator/Views/GraphingCalculator/EquationInputArea.xaml b/src/Calculator/Views/GraphingCalculator/EquationInputArea.xaml index 13fd008..c3a0cc3 100644 --- a/src/Calculator/Views/GraphingCalculator/EquationInputArea.xaml +++ b/src/Calculator/Views/GraphingCalculator/EquationInputArea.xaml @@ -324,6 +324,7 @@ + @@ -344,7 +345,7 @@ - + @@ -568,6 +569,7 @@ + - + + - @@ -823,7 +826,8 @@ - >> initResult = nullopt; bool successful = false; + m_errorCode = 0; + m_errorType = 0; if (m_renderMain && m_graph != nullptr) { @@ -345,7 +347,7 @@ namespace GraphControl parsableEquation += s_getGraphClosingTags; // Wire up the corresponding error to an error message in the UI at some point - if (!(expr = m_solver->ParseInput(parsableEquation))) + if (!(expr = m_solver->ParseInput(parsableEquation, m_errorCode, m_errorType))) { co_return false; } @@ -356,7 +358,7 @@ namespace GraphControl request += s_getGraphClosingTags; } - if (graphExpression = m_solver->ParseInput(request)) + if (graphExpression = m_solver->ParseInput(request, m_errorCode, m_errorType)) { initResult = TryInitializeGraph(keepCurrentView, graphExpression.get()); @@ -386,8 +388,13 @@ namespace GraphControl // If we failed to render then we have already lost the previous graph shouldKeepPreviousGraph = false; initResult = nullopt; + m_solver->HRErrorToErrorInfo(m_renderMain->GetRenderError(), m_errorCode, m_errorType); } } + else + { + m_solver->HRErrorToErrorInfo(m_graph->GetInitializationError(), m_errorCode, m_errorType); + } } if (initResult == nullopt) @@ -398,7 +405,7 @@ namespace GraphControl initResult = m_graph->TryInitialize(); if (initResult != nullopt) { - UpdateGraphOptions(m_graph->GetOptions(), validEqs); + UpdateGraphOptions(m_graph->GetOptions(), vector()); SetGraphArgs(m_graph); m_renderMain->Graph = m_graph; @@ -431,6 +438,8 @@ namespace GraphControl { if (!eq->IsValidated) { + eq->GraphErrorType = static_cast(m_errorType); + eq->GraphErrorCode = m_errorCode; eq->HasGraphError = true; } } @@ -457,7 +466,7 @@ namespace GraphControl request += equation->GetRequest()->Data(); request += s_getGraphClosingTags; - if (unique_ptr graphExpression = m_solver->ParseInput(request)) + if (unique_ptr graphExpression = m_solver->ParseInput(request, m_errorCode, m_errorType)) { if (graph->TryInitialize(graphExpression.get())) { @@ -524,7 +533,7 @@ namespace GraphControl m_graph->SetArgValue(variableName->Data(), newValue); m_renderMain->GetCriticalSection().unlock(); - m_renderMain->RunRenderPassAsync(); + m_renderMain->RunRenderPass(); }); ThreadPool::RunAsync(workItemHandler, WorkItemPriority::High, WorkItemOptions::None); @@ -549,7 +558,7 @@ namespace GraphControl auto lineColor = eq->LineColor; graphColors.emplace_back(lineColor.R, lineColor.G, lineColor.B, lineColor.A); - if (eq->IsSelected) + if (eq->GraphedEquation != nullptr && !eq->HasGraphError && eq->IsSelected) { eq->GraphedEquation->TrySelectEquation(); } @@ -1001,7 +1010,7 @@ String ^ Grapher::ConvertToLinear(String ^ mmlString) { m_solver->FormatOptions().SetFormatType(FormatType::LinearInput); - auto expression = m_solver->ParseInput(mmlString->Data()); + auto expression = m_solver->ParseInput(mmlString->Data(), m_errorCode, m_errorType); auto linearExpression = m_solver->Serialize(expression.get()); m_solver->FormatOptions().SetFormatType(s_defaultFormatType); @@ -1011,7 +1020,7 @@ String ^ Grapher::ConvertToLinear(String ^ mmlString) String ^ Grapher::FormatMathML(String ^ mmlString) { - auto expression = m_solver->ParseInput(mmlString->Data()); + auto expression = m_solver->ParseInput(mmlString->Data(), m_errorCode, m_errorType); auto formattedExpression = m_solver->Serialize(expression.get()); return ref new String(formattedExpression.c_str()); } diff --git a/src/GraphControl/Control/Grapher.h b/src/GraphControl/Control/Grapher.h index 5ca446a..a2d833f 100644 --- a/src/GraphControl/Control/Grapher.h +++ b/src/GraphControl/Control/Grapher.h @@ -335,6 +335,8 @@ public bool m_Moving; Windows::UI::Xaml::DispatcherTimer ^ m_TracingTrackingTimer; Windows::UI::Core::CoreCursor ^ m_cachedCursor; + int m_errorType; + int m_errorCode; public: Windows::Storage::Streams::RandomAccessStreamReference ^ GetGraphBitmapStream(); diff --git a/src/GraphControl/DirectX/RenderMain.cpp b/src/GraphControl/DirectX/RenderMain.cpp index 8f27478..e07cb0b 100644 --- a/src/GraphControl/DirectX/RenderMain.cpp +++ b/src/GraphControl/DirectX/RenderMain.cpp @@ -274,7 +274,9 @@ namespace GraphControl::DX pRenderTarget->BeginDraw(); bool hasMissingData = false; - successful = SUCCEEDED(renderer->DrawD2D1(pFactory, pRenderTarget, hasMissingData)); + m_HResult = renderer->DrawD2D1(pFactory, pRenderTarget, hasMissingData); + + successful = SUCCEEDED(m_HResult); // We ignore D2DERR_RECREATE_TARGET here. This error indicates that the device // is lost. It will be handled during the next call to Present. @@ -345,6 +347,11 @@ namespace GraphControl::DX return successful; } + HRESULT RenderMain::GetRenderError() + { + return m_HResult; + } + void RenderMain::OnLoaded(Object ^ sender, RoutedEventArgs ^ e) { RunRenderPass(); diff --git a/src/GraphControl/DirectX/RenderMain.h b/src/GraphControl/DirectX/RenderMain.h index 96f18c0..31accf8 100644 --- a/src/GraphControl/DirectX/RenderMain.h +++ b/src/GraphControl/DirectX/RenderMain.h @@ -61,6 +61,8 @@ namespace GraphControl::DX return m_isRenderPassSuccesful; } + HRESULT GetRenderError(); + // Indicates if we are in active tracing mode (the tracing box is being used and controlled through keyboard input) property bool ActiveTracing { @@ -189,5 +191,7 @@ namespace GraphControl::DX Windows::Foundation::IAsyncAction ^ m_renderPass = nullptr; bool m_isRenderPassSuccesful; + + HRESULT m_HResult; }; } diff --git a/src/GraphControl/Models/Equation.h b/src/GraphControl/Models/Equation.h index baa66c7..03b847b 100644 --- a/src/GraphControl/Models/Equation.h +++ b/src/GraphControl/Models/Equation.h @@ -7,6 +7,200 @@ namespace GraphControl { + public enum class ErrorType + { + Evaluation, + Syntax, + Abort, + }; + + public enum class EvaluationErrorCode + { + // Number is too large. To see this error, assign a large number to variable a, then keep doing "a:=a*a" until it happens + Overflow = 2, + + // doing trig in calculus and current mode is not Radians + RequireRadiansMode = 3, + + // This is actually abort code generated as time limit reached. + TooComplexToSolve = 4, + + // degree sign present and the current mode is not Degrees + RequireDegreesMode = 5, + + // n! and n is not an non-negative integer or m/2 + FactorialInvalidArgument = -1, + + // n!! and n is not an non-negative integer or odd negative integers + Factorial2InvalidArgument = -2, + + // n! and n is too big + FactorialCannotPerformOnLargeNumber = -3, + + // x mod y, and one of them is not an integer + ModuloCannotPerformOnFloat = -5, + + // below are equation solving specific errors + + // example1: sin(x)+x=2 example2: x^4-x^3-8x=9 + EquationTooComplexToSolveSymbolic = -7, + + // example1: x-x=3 example2: x^2=-1 + EquationHasNoSolution = -8, + + // only used for numeric solve where the algorithm doesn't converge + EquationTooComplexToSolve = -9, + + // used when equation cannot be ploted + EquationTooComplexToPlot = -10, + + // example1: 1/0 + DivideByZero = -15, + + // the inequality is too complex for the engine to solve + InequalityTooComplexToSolve = -41, + + // the inequality has no solution + InequalityHasNoSolution = -42, + + // An exression contains logical conditions that are mutually exclusive + MutuallyExclusiveConditions = -43, + + // Arbitrary Precision Evaluation input is out of domain + OutOfDomain = -101, + + // Not supported. + GE_NotSupported = -503, + + // General error for graphing engine + GE_GeneralError = -504, + + // Failed to calculate + GE_TooComplexToSolve = -506, + }; + + public enum class SyntaxErrorCode + { + // found ) without matching ( + ParenthesisMismatch = 1, + + // found ( without matching ) + UnmatchedParenthesis = 2, + + // more than 1 decimal point in a number. Example: 7.3.2 + TooManyDecimalPoints = 3, + + // decimal point on its own without any digits surrounding it. Example: 3+.+4 + DecimalPointWithoutDigits = 4, + + // example: 3-4* + UnexpectedEndOfExpression = 5, + + // example: 3-*4 + UnexpectedToken = 6, + + // example: [ (or many other special characters), another example: "3,5" (comma is invalid here) + InvalidToken = 7, + + // example: solve(x+3=8=x) + TooManyEquals = 8, + + // example: ploteq(4+83=9) + EqualWithoutGraphVariable = 10, + + // example: ploteq(x+y) (expecting "=" in equation ploting) + // example2: Solve(5*x+9) (expecting = in the equation solving) + InvalidEquationSyntax = 11, + + // there is nothing in the expression + EmptyExpression = 12, + + // example: factor(x=3) (expecting solve(x=3)). + EqualWithoutEquation = 14, + + // example: solve( (x=3)*2 ) + InvalidEquationFormat = 15, + + // This error only occurs when CasContext.ParsingOptions.AllowImplicitParentheses == false. + // example: sin a (expecting sin(a)) + ExpectParenthesisAfterFunctionName = 25, + + // example: root(a) (expecting 2 parameters) + IncorrectNumParameter = 26, + + // exmaple: "x_", "x_@", "x__1" + InvalidVariableNameFormat = 32, + + // found } without matching { + BracketMismatch = 34, + + // found { without matching } + UnmatchedBracket = 35, + + // syntax error in MathML format. Used only if CasContext.ParsingOptions.FormatType is MathML or MathMLNoWrapper + InvalidMathMLFormat = 40, + + // The input has an unknown MathML entity. Used only if CasContext.ParsingOptions.FormatType is MathML or MathMLNoWrapper + UnknownMathMLEntity = 41, + + // The input has an unknown MathML element. Used only if CasContext.ParsingOptions.FormatType is MathML or MathMLNoWrapper + UnknownMathMLElement = 42, + + // "i" and "I" cannot be used as variable names in real number field + CannotUseIInReal = 48, + + // General error + GeneralError = 52, + + // used in parsing numbers with arbitrary bases. example: base(2, 1020), base(16, 1AG) + InvalidNumberDigit = 55, + + // a valid number base must be an integer >=2 and <=36 + InvalidNumberBase = 56, + + // some functions require a variable in certain argument position. e.g. 2nd argument of deriv, integral, limit, etc. + // this error code is used if the argument at the position is not a variable + InvalidVariableSpecification = 57, + + // all operands of logical operators must be logical. example: "true and 1" + ExpectingLogicalOperands = 58, + + // all operands of a non-logical operator must not be logical. example: "sin(true)" + ExpectingScalarOperands = 59, + + // a list can contain logicals or scalars, but not both. + CannotMixLogicalScalarInList = 60, + + // in definite integral, seriesSum and seriesProduct, the index variable is used in the lower/upper limits. + // example: integral(sin(x), x, 0, x) + CannotUseIndexVarInOpLimits = 61, + + // in limit, the index variable is used in the limit point + // example: limit(sin(x), x, x-1) + CannotUseIndexVarInLimPoint = 62, + + /// ComplexInfinity cannot be used in real number field + CannotUseComplexInfinityInReal = 72, + + // complex numbers are not allowed in inequality solving + CannotUseIInInequalitySolving = 123, + + // Indicate a bug in the MathRichEdit serializer + RichEditSerializationError = 201, + + // can't initialize math zone in richedit, meaning it's the wrong version richedit dll, need reinstall + RichEditInitialization = 202, + + // indicate bug in either richedit or richedit wrapper + RichEditInlineObjectStructure = 203, + + // in a structure like integral, sum, product, one of the boxes is not filled + RichEditMissingArgument = 204, + + // errors in richedit wrapper that are not specifically handled for + RichEditGeneralError = 210, + }; + [Windows::UI::Xaml::Data::Bindable] public ref class Equation sealed : public Windows::UI::Xaml::Data::INotifyPropertyChanged { public: @@ -18,6 +212,8 @@ namespace GraphControl OBSERVABLE_NAMED_PROPERTY_RW(bool, IsValidated); OBSERVABLE_NAMED_PROPERTY_RW(bool, HasGraphError); OBSERVABLE_NAMED_PROPERTY_RW(bool, IsSelected); + OBSERVABLE_NAMED_PROPERTY_RW(ErrorType, GraphErrorType); + OBSERVABLE_NAMED_PROPERTY_RW(int, GraphErrorCode); property Windows::UI::Color LineColor { diff --git a/src/GraphingImpl/Mocks/Graph.h b/src/GraphingImpl/Mocks/Graph.h index ae10c35..8819e94 100644 --- a/src/GraphingImpl/Mocks/Graph.h +++ b/src/GraphingImpl/Mocks/Graph.h @@ -29,6 +29,11 @@ namespace MockGraphingImpl return std::nullopt; } + HRESULT GetInitializationError() + { + return S_OK; + } + virtual Graphing::IGraphingOptions& GetOptions() { return m_graphingOptions; diff --git a/src/GraphingImpl/Mocks/MathSolver.h b/src/GraphingImpl/Mocks/MathSolver.h index 5706555..e6c18df 100644 --- a/src/GraphingImpl/Mocks/MathSolver.h +++ b/src/GraphingImpl/Mocks/MathSolver.h @@ -115,7 +115,7 @@ namespace MockGraphingImpl return m_formatOptions; } - std::unique_ptr ParseInput(const std::wstring& input) override + std::unique_ptr ParseInput(const std::wstring& input, int& errorCodeOut, int& errorTypeOut) override { if (input.empty()) { @@ -125,6 +125,10 @@ namespace MockGraphingImpl return std::make_unique(MockExpression{}); } + void HRErrorToErrorInfo(HRESULT hr, int& errorCodeOut, int& errorTypeOut) + { + } + std::shared_ptr CreateGrapher(const Graphing::IExpression* expression) override; std::shared_ptr CreateGrapher() override; diff --git a/src/GraphingInterfaces/IGraph.h b/src/GraphingInterfaces/IGraph.h index 9eca236..81a04ce 100644 --- a/src/GraphingInterfaces/IGraph.h +++ b/src/GraphingInterfaces/IGraph.h @@ -18,6 +18,8 @@ namespace Graphing virtual std::optional>> TryInitialize(const IExpression* graphingExp = nullptr) = 0; + virtual HRESULT GetInitializationError() = 0; + virtual IGraphingOptions& GetOptions() = 0; virtual std::vector> GetVariables() = 0; diff --git a/src/GraphingInterfaces/IMathSolver.h b/src/GraphingInterfaces/IMathSolver.h index 8f1ddaa..70f728d 100644 --- a/src/GraphingInterfaces/IMathSolver.h +++ b/src/GraphingInterfaces/IMathSolver.h @@ -64,9 +64,11 @@ namespace Graphing virtual IEvalOptions& EvalOptions() = 0; virtual IFormatOptions& FormatOptions() = 0; - virtual std::unique_ptr ParseInput(const std::wstring& input) = 0; - virtual std::shared_ptr CreateGrapher(const IExpression* expression) = 0; + virtual std::unique_ptr ParseInput(const std::wstring& input, int& errorCodeOut, int& errorTypeOut) = 0; + virtual void HRErrorToErrorInfo(HRESULT hr, int& errorCodeOut, int& errorTypeOut) = 0; + + virtual std::shared_ptr CreateGrapher(const IExpression* expression) = 0; virtual std::shared_ptr CreateGrapher() = 0; virtual std::wstring Serialize(const IExpression* expression) = 0;