Visual Calc v3.0 - A new dimension for the desktop calculator






3.62/5 (113 votes)
How to start programming a parser.
The complete VisualCalc calculator project (GUI + parser included):
- Download executable project - 50.6 Kb
- Download libraries (if needed) - 945 Kb
- Download source files - 79.4 Kb
VisualCalc parser project (calculation engine only):
Latest updates
- Separation of the parser into its own DLL.
- Migration of the project for Microsoft Visual C++ .NET 2003.
Contents
- Introduction
- Disclaimer
- I] The parser
- II] The user interface
- III] How to use the parser in your application
- Conclusion and Thanks
- Future features
- History
Introduction
My first idea was to create a parser to color some text. As I didn't know how to start seriously, I remembered an example I read from Bjarne Stroustrup's The C++ Language. The sample was a simple calculator that took characters from the standard input stream (console mode) to parse them for mathematical expressions. I reproduced it, improved it, and here is what I did. Enjoy...
Parsing is an interesting part of programming because we always need to analyze a stream to guess what the user wants the program to do. On "graphical windows", this is very well simplified because, in general, there is no need for full sentences on the input stream such as a command line. Anyway, that's what my calculator does, and I'll do my best to explain how.
At first, I'll present the parser, its members, its functions, and how they interact with each other. Then, I'll present my graphical interface, and in the end, I'll present how to use the parser in your own code or at least in your project.
VisualCalc was built at first with Microsoft Visual C++ 6. As this compiler is getting old seriously, I had to make that hard decision to migrate for a more recent version of Visual Studio, knowing that many people in the audience are still using it. I'll do my best to support VisualCalc for the ones who still don't have other compilers than VC6. If you are interested only in the use of the parser in your code, then there wouldn't be any problems to make it compile under Visual C++ 6 yet; maybe for future versions...
Disclaimer
The VisualCalc project is going bigger and bigger, and I still have plenty of ideas to improve it again. Unfortunately, I work on it in my free times only, so the updates are done at a reasonable rhythm. However, since I've been working seriously since its first private release in August 2004, I request you to use my work with interest. That's why I place some conditions to using it:
This code may be used in compiled form in any way you desire (including commercial use). The code may be redistributed unmodified by any means providing it is not sold for profit without the author's written consent, and providing that this notice and the author's name and all copyright notices remain intact. However, this file and the accompanying source code may not be hosted on a website or bulletin board without the author's written permission. This software is provided "as is" without express or implied warranty. Use it at your own risk! Whilst I have made every effort to remove any undesirable "features", I cannot be held responsible if it causes any damage or loss of time or data.
Hopefully, that isn't too much to ask considering the amount of work that went into this. If you do use it in a commercial application, then please send me an email letting me know.
I] The parser (version 3.0)
To be compliant with the MVC (Model - View - Controller) architecture, the most important point I improved in the parser version 2.3 was to split the parser methods from the main dialog box class and create its own class. Basically, the MVC architecture requires the main layers of the application (business objects, background treatments, and graphical displays) to be separated. Now, not only the parser exists in its own class, but it has been physically separated as it remains in its own DLL project. The parser code is commented for the Doxygen documentation tool, and the generated HTML document can be downloaded from this page (see the downloads section at the top of the article).
The parser is implemented in the following files:
VCalcParser.h: | The parser definition |
VCalcParser.cpp: | The parser implementation |
VCalcParserExceptions.h: | The exceptions definitions |
VCalcParserExceptions.cpp: | The exceptions implementations |
VCalcParserTypes.h: | The types used by both the parser and the user interface |
Here is the CVCalcParser
class definition:
#include "VCalcParserTypes.h" class CVCalcParser { private: /****************** Mathematic constants ******************/ const VALUES_TYPE m_PI; // 3.1415926535897932384626433832795 const VALUES_TYPE m_E; // 2.7182818284590452353602874713527 /************* Members for parsing treatment **************/ // Token extracted from the input stream TokenValue m_CurrentToken; // Reference to the input stream std::string m_Source; // Identifier extracted as an IDENTIFIER token std::string m_strIdentifierValue; // Describe message of the warning std::string m_strWarningMsg; // Number extracted as a NUMBER token VALUES_TYPE m_valNumberValue; // Non-interrupting low level message bool m_bWarningFlag; // The end of the input stream is reached bool m_bEndEncountered; // Index in the input stream int m_iCurrentIndex; // Supported functions std::list<std::string> m_lstFunctions; // User defined variables table mapping the // identifier with a value std::map<std::string, VALUES_TYPE> m_mapVariables; // Answers History mapping a formula with a result std::deque<AnswerItem> m_dqeAnswersHistory; /***** Locally defined/redefined mathematic functions *****/ VALUES_TYPE ffactor (VALUES_TYPE); VALUES_TYPE nCp (VALUES_TYPE, VALUES_TYPE); VALUES_TYPE nAp (VALUES_TYPE, VALUES_TYPE); AnswerItem Ans (VALUES_TYPE valIndex); VALUES_TYPE abs (VALUES_TYPE); VALUES_TYPE cos (VALUES_TYPE); VALUES_TYPE sin (VALUES_TYPE); VALUES_TYPE tan (VALUES_TYPE); VALUES_TYPE cosh (VALUES_TYPE); VALUES_TYPE sinh (VALUES_TYPE); VALUES_TYPE tanh (VALUES_TYPE); VALUES_TYPE Acos (VALUES_TYPE); VALUES_TYPE Asin (VALUES_TYPE); VALUES_TYPE Atan (VALUES_TYPE); VALUES_TYPE deg (VALUES_TYPE); VALUES_TYPE rad (VALUES_TYPE); VALUES_TYPE exp (VALUES_TYPE); VALUES_TYPE ln (VALUES_TYPE); VALUES_TYPE log (VALUES_TYPE); VALUES_TYPE logn (VALUES_TYPE, VALUES_TYPE); VALUES_TYPE sqrt (VALUES_TYPE); VALUES_TYPE sqrtn (VALUES_TYPE, VALUES_TYPE); VALUES_TYPE pow (VALUES_TYPE, VALUES_TYPE); VALUES_TYPE mod (VALUES_TYPE, VALUES_TYPE); VALUES_TYPE sum (std::string expr, std::string var, VALUES_TYPE low, VALUES_TYPE high); VALUES_TYPE product (std::string expr, std::string var, VALUES_TYPE low, VALUES_TYPE high); public: /************* Public interface of the Parser *************/ CVCalcParser(); virtual ~CVCalcParser(); void ResetParserMembers(const std::string); void ResetFunctions(); void ResetVariables(); void ResetAnswersHistory(); const std::list<std::string>& GetFunctions(); const std::map<std::string, VALUES_TYPE>& GetVariables(); const std::deque<AnswerItem>& GetAnswersHistory(); bool HasWarning(); std::string GetWarningMsg(); /** Only function to call to start a calculation process **/ VALUES_TYPE Evaluate(const std::string& Source) throw(CVCalcParserException); private: // Parsing functions following (in the // recursive-descending order)... VALUES_TYPE Level_1 (void); // + , - VALUES_TYPE Level_2 (void); // * , / VALUES_TYPE Level_3 (void); // ^ VALUES_TYPE Level_4 (void); // % VALUES_TYPE Level_5 (void); // ! , ° VALUES_TYPE Primary (void); // ( ) , Unary + , Unary - TokenValue GetToken (void); bool StepIndexForward(void); };
The parser is using pure standard C++ so that it can be used on any platform. VCalcParserTypes
defines several types, which will be presented later in the paragraph. One of the most important one is VALUES_TYPE
:
// Type for the handled operands and results typedef long double VALUES_TYPE; // But why not this: //typedef CMyBigDecimalType VALUES_TYPE;
The values handled by the parser and returned as results are currently long double
s. I made the choice of using an alias so that I can more easily change the type for one that handles a lot of digits. Besides, I am working in parallel on my own "big decimal" data type, which would be able to store some floating point numbers bigger than what native types can. The difficulty in this part is to re-create the mathematic function that operates on those decimals. This is also the reason why I overloaded the mathematical functions (cos, sqrt, log, ...) so that the switch between the data types used will be eased.
The members
The parser members are set to allow different functions to talk together. Here are their uses:
const VALUES_TYPE m_PI;
This member stores the value of the constant pi with a precision of 32 digits.
const VALUES_TYPE m_E;
This member stores the value of the constant e with the same precision of 32 digits.
TokenValue m_CurrentToken;
Stores a token which will be switched by the lower level
GetToken()
function.TokenValue
is a type defined in VCalcParserTypes.h like this:// Type for recognized separation tokens typedef enum { TV_NUMBER, TV_IDENTIFIER, TV_END, TV_SEQ = ',', TV_PLUS = '+', TV_MINUS = '-', TV_MUL = '*', TV_DIV = '/', TV_POW = '^', TV_MOD = '%', TV_FACT = '!', TV_DEG = '°', TV_LP = '(', TV_RP = ')', TV_ASSIGN = '=' } TokenValue;
The
TokenValue
type allows theGetToken()
function to return the token read from the input stream, to the calling functions. It will grow up consequently when I have new operators in the calculator.std::string m_Source;
Contains the entire input string. This is the effective stream that will be read by
GetToken()
to be parsed.std::string m_strIdentifierValue;
Contains the name of a valid identifier. Whether the user sets a new variable, modifies or just recalls one, the string of the variable name will be stored here. This member also contains the function names the user calls.
std::string m_strWarningMsg;
This member stores a non blocking warning description. It can be printed or ignored by the user interface. Mine just prints it after outputting the result (for example, 0^0 gives the warning so that it is replaced by 1).
VALUES_TYPE m_valNumberValue;
Stores the value of a number (integer or floating point number) when a valid number is read from the input stream.
bool m_bWarningFlag;
This flag tells the function that ran the parser that the result is valid/printable, but a message may be added to notify the user that he made a questionable operation.
bool m_bEndEncountered;
This flag used by
GetToken()
is not really indispensable, but quite useful. It tells that all the input stream has been read (equivalent toeof()
for files).int m_iCurrentIndex;
Counter variable to indicate the reading position in the stream. It may be between 0 and "input string length"-1.
std::list<std::string> m_lstFunctions;
This member stores the list of the functions designed for the user.
std::map<std::string, VALUES_TYPE> m_mapVariables;
This is the table where the variables set by the user are associated with their values.
std::deque<AnswerItem> m_dqeAnswersHistory;
This list contains a set of last
AnswerItem
s.AnswerItem
is a type defined in VCalcParserTypes.h like this:// Type for an entry in the answers history typedef struct { std::string m_strFormula; VALUES_TYPE m_valResult; } AnswerItem;
It associates the formula the user typed to get his answer with the effective result.
The functions
As we can see in the definition of the class, the parser is a set of seven private
recursive-descendant functions plus a public
function for the user interface to start the calculation. The calling tree is called "Recursive-Descendant" because each function is associated to an operator level; a function is normally called by the immediate lower level function and calls itself the immediate upper level function. The calls are explained in the following figure. In some exceptional cases, a function can call itself or another whose level is lower in the hierarchy:
Figure 1: Regular parser function call hierarchy.
As we know, the mathematical operators have priorities (for example, the basics teach us that multiplication is prior to addition). We also know another approach of the operator levels with the programming languages. So I had to parse what the user entered, taking care of the priorities. This part is implicitly improved by the recurrence of the functions. For example, the operators developed in this calculator are the following, in decreasing order of priority. The grouped operators have the same level:
| ||||||||||||||||||||||||||||||||||||||
Figure 2 : Supported operators levels |
Let's take an example of the priorities with the operator =
. As it has almost the lowest level, it assigns to the variable (left operand) the result of the expression on its right. Be careful on this:
Figure 3: Be careful on operator priorities.
All the functions presented below run in the same way. First, they call the function to the upper level and get back its result. Then, m_CurrentToken
is tested in a switch
statement. If the token is recognized by the function, the associated operation is performed, otherwise, it returns to the lower level function.
VALUES_TYPE Evaluate(const std::string& Source) throw(CVCalcParserException);
This is the only
public
function the user interface can call to parse a mathematical expression. It initializes the parser members for a new calculation. It also ensures that the last answer (formula + result) is pushed in front of the answers historydeque
.VALUES_TYPE Level_1(void) throw(CVCalcParserException);
This function is the lower level one. It manages the addition and subtraction operations by adding/subtracting its left operand to the value returned by
Level_2()
. It also takes care when the user tries to use the assign operator on a literal.VALUES_TYPE Level_2(void) throw(CVCalcParserException);
This function manages multiplication and division. It multiplies/divides its left operand to the value returned by
Level_2()
(we can see here the recursive call). An exception is thrown if the dividend of a division is set to 0.VALUES_TYPE Level_3(void) throw(CVCalcParserException);
This function manages the power operation. It raises its left operand to the power of the value returned by
Level_4()
. A warning is raised if the user asks for 0^0 telling that it is replaced by 1. In this case, I wanted to emit a warning to the user telling him that he asked for a special operation to the calculator. For the current version 2.24 of VisualCalc, the following cases are still not managed:- infinite^0 replaced by 1,
- undef^0 replaced by 1,
- 1^infinite replaced by 1,
- 1^undef replaced by 1.
In general, positive and negative infinites and NaN cases are not supported yet.
VALUES_TYPE Level_4(void) throw(CVCalcParserException);
This function manages the modulus operation. It returns the modulus of the division of its left operand by the value returned by
Level_5()
.VALUES_TYPE Level_5(void) throw(CVCalcParserException);
This function manages two postfix unary operators currently implemented by parser version 2.1 : the
!
and the°
operators.!
, returns the factorial of its left operand. For example, 5! = 5 x 4 x 3 x 2 x 1.°
, is an operator that converts degrees to radians. Its main use is in trigonometric functions that need radians as parameters and is a shortcut forrad()
. For example: cos(45°) = cos(rad(45)) = cos(pi/2).
VALUES_TYPE Primary(void) throw(CVCalcParserException);
One of the biggest functions of the parser. As it is near the highest level function, its work becomes heavy. An important note, it doesn't manage any operator that needs a left and a right operand. It calls the function at the higher level (
get_token()
) but doesn't get the value returned as a left operand. It is the compound ofswitch
statements which test the followingm_CurrentToken
possible values:TV_NUMBER
Returns the literal extracted from the input string.
TV_IDENTIFIER
It first tries to recognize the identifier in the functions list. If so, some tests are performed to check if the user is calling the function properly (parenthesis, with the correct number of parameters - depending on the function called ; not using the name of a function as a variable). Then, it calls the associated function and returns its value.
Secondly, the identifier may be a variable name (already existing, or just being created). It adds the value into the variables table if such a variable exists (already assigned). If the identifier is assigned (next token is
=
operator), it tests if the user is not trying to assign a constant (pi
ore
) and either affects the variable or returns an error. If the user just recalls a value previously stored in the identifier, the value is returned to the lower level calling function. If the variable is not found, an exception is thrown. It is also not allowed to implicitly multiply a variable with an expression. For example, these cases throw an exception:2x
instead of2 * x
.foo(3!)
instead offoo * (3!)
.
The functions available for the user in VisualCalc are detailed in the Help Dialog Box, in the Functions tab.
TV_PLUS
/TV_MINUS
Returns the next
Primary()
with the associated sign.TV_LP
This token is returned when an opening parenthesis is found to begin a new entire expression (not a function parameters list).
Level_1()
is called to evaluate the expression until the closing parenthesis is found on the input stream, otherwise, an exception is thrown.
TokenValue GetToken(void) throw(CVCalcParserException);
It is the most important function of the parser, the highest level one. It reads the characters from the input steam one by one, ignoring white spaces. We can type space/tab characters between significant characters without changing the meaning of the expression typed. Then, the usual
switch
tests the following cases:*
,/
,+
,-
,^
,%
,!
,°
,(
,)
,=
,,
Returns the related token to the character extracted. The token read is also stored into
m_CurrentToken
..
,1
,2
,3
,4
,5
,6
,7
,8
,9
,0
Such characters can result in
GetToken()
returning a constant number if the characters following make a valid integer/floating point number. If two dots ('.
') are found in the same number, the related exception is thrown. The number is stored inm_valNumberValue
._
a
,b
,c
,d
,e
,f
,g
,h
,i
,j
,k
,l
,m
,n
,o
,p
,q
,r
,s
,t
,u
,v
,w
,x
,y
,z
A
,B
,C
,D
,E
,F
,G
,H
,I
,J
,K
,L
,M
,N
,O
,P
,Q
,R
,S
,T
,U
,V
,W
,X
,Y
,Z
If one of these characters is found in the stream, the following identifier is extracted and stored in
m_strIdentifierValue
.
bool StepIndexForward(void) throw();
This function was created to factorize a piece a code called several times by
GetToken()
. Its only goal is to increase the index which is used to read the string to be parsed and then inform that the end of the string is reached, by setting them_bEndEncountered
flag.
The exceptions
As an error, during the parser's treatment, can occur anywhere and at any time, exceptions were the language functionality to use. The parser can throw seven types of exceptions, each inherited from CVCalcParserException
. In the diagram below, the green and blue classes are abstract
, that means they cannot be instantiated directly in the code:
Figure 4 : Parser exceptions hierarchy.
CVCalcParserException
actually provides the public
member functions to be used when catching an exception (GetExceptionNumber()
, GetMessage()
, GetErrorPos()
). Then, each exception has its own class which inherits necessarily from one of the seven exception groups (CSyntaxException
, CMathematicException
, CFunctionException
, CParameterException
, CVariableException
, CDomainException
, CParserException
).
II] The user interface
The calculator
The parser doesn't edit itself the controls of the graphical interface. The dialog box calls the public
functions that return the current list of states.
Here are the four main areas:
- The typing and the result edit boxes.
This is where the user writes the expression to be parsed and where the result appears. When an error/warning message is about to be displayed, it is printed in the result field.
- The last answers/formulas list boxes.
When VisualCalc returns a correct answer (without error), the value and the formula typed are added to their respective lists. The most recent answers are inserted at the top of the list. It is possible to recall a result previously calculated, by double-clicking on it; it will be inserted at the cursor's position, or will replace the selection if many characters are selected in the input edit box. It is also possible to switch from one list to the other by clicking their
button.
Figure 5: Last answers switch button.
- The variables list box.
When a user assigns a new identifier, the name of the variable is added to the list. If he just modifies one, the value is internally modified, but there is no change in this window. The variables are stored in alphabetical order. Here again, it is possible to re-use a variable in a new formula by double-clicking on its name. It will be added at the cursor's position or will replace the selected characters in the input edit box. A
button has been added for the variables since v2.23 to switch between the variable names and their values.
Figure 6: Variables switch button.
- The functions list box
This feature is added to allow users to use the common mathematical functions. When you need a function, you can either click on its name (it is inserted at the cursor's position) or type the identifier. Be careful, the parser is case-sensitive!
cos()
is not the same asCos()
.Here are the functions implemented:
Functions Use Description abs
abs(expr)
returns the absolute value of the expression. Acos
Acos(expr)
returns the arc cosine of the expression. expr
is expected in radians.Ans
Ans(expr)
returns an answer in the history. expr
must be between 1 and the number of answers in the list.Asin
Asin(expr)
returns the arc sine of the expression. expr
is expected in radians.Atan
Atan(expr)
returns the arc tangent of the expression. expr
is expected in radians.cos
cos(expr)
returns the cosine of the expression. expr
is expected in radians.cosh
cosh(expr)
returns the hyperbolic cosine of the expression. expr
is expected in radians.deg
deg(expr)
returns the equivalence in degrees of the expression. expr
is expected in radians.exp
exp(expr)
returns the exponential of the expression. ln
ln(expr)
returns the natural (Neperian, base-e) logarithm of the expression. log
log(expr)
returns the decimal (base-10) logarithm of the expression. logn
logn(expr, n)
returns the base-n logarithm of the expression. nAp
nAp(n, p)
returns the arrangement of .p
elements inn
nCp
nCp(n, p)
returns the combination of p
elements inn
.rad
rad(expr)
returns the equivalence in radians of the expression. expr
is expected in degrees. The same as the°
operator.sin
sin(expr)
returns the sine of the expression. expr
is expected in radians.sinh
sinh(expr)
returns the hyperbolic sine of the expression. expr
is expected in radians.sqrt
sqrt(expr)
returns the squared root of the expression. sqrtn
sqrtn(expr, n)
returns the n-order root of the expression. tan
tan(expr)
returns the tangent of the expression. expr
is expected in radians.tanh
tanh(expr)
returns the hyperbolic tangent of the expression. expr
is expected in radians.sum
sum(expr(var), var, low, high)
returns the sum of the expression when var
goes from low to high.var
must be an integer (not implemented yet).product
product(expr(var), var, low, high)
returns the product of the expression when var
goes from low to high.var
must be an integer (not implemented yet).Figure 7: Functions interfaced to the users.
I added a set of OnFocus()
calls at the end of each handling function to avoid the user using his/her mouse too much. This way, when you finish writing the expression to be calculated, you just have to push Enter on your keyboard. If an error occurs, the cursor will be placed at the error position (when possible), otherwise the entire input text will be selected. This also happens when you double-click on the (last answers/variables) lists or on the Erase (clear list) buttons.
All the operators and their syntax are summed up in the Help dialog box, in the right tabs. Use the Help button or the system menu (Alt+SpaceBar) to get it.
The Help dialog box
This dialog presents, in tabbed views, the different functionalities available to the user. The three existing tabs show the functions, the operators and the error codes, as shown below:
Figure 8: Help dialog box tabs.
This dialog is provided as an informative tooltip for those who need to find quickly the functions and the operators available, their syntax and their meaning without opening the parser doc. The error codes are also summarized with an overview of the different error cases that may occur.
The About dialog box
What to say about a dialog box that nobody knows about?
Figure 9: About dialog box.
It simply gives credit to all those who deserve it, and maintains the version numbers of both the VisualCalc and the parser projects.
III] How to use the parser in your application in very easy steps
You can either recompile the parser source files in your project or import the parser from its DLL. Follow the guide.
Recompiling the source files
As I said previously, the parser is a compound of five files to be included in your project.
- Copy VCalcParser.h, VCalcParser.cpp, VCalcParserExceptions.h, VCalcParserExceptions.cpp, and VCalcParserTypes.h in your sources.
- Then, only
#include "VCalcParser.h"
in your file which has to call the parser and launch the calculations. Of course, be careful to include it after stdafx.h if you are in an MFC project.
...That's it. What you have to do now is use it like any other class of your project (see "Using the code" below for an example).
Calling the DLL
- The DLL is already built, why not reuse it?
- Yeah, sure, but how?
- Follow me... ready?
- OK, let's go !
Here also, the first thing to do is to include the parser's header in your source (still after stdafx.h if compiling in an MFC project that uses precompiled headers):
#include "stdafx.h" #define VCALCPARSER_DLL #include "VCalcParser.h"
- Hey, but what is this macro defined here?
Well, the VCALCPARSER_DLL
macro has been defined to ease the reuse of the header in either "DLL-imports" or "source code use" contexts. It tells the compiler that you import the symbols from a DLL instead of defining them by yourself with a set of internal __declspec(dllimport)
. As this macro implicitly makes your project load the VCalcParser.lib into your project, you have to make it available in your source, either by copying it in your project sources folder or by changing your project settings.
One last point when using the DLL method is to set the following settings in your project:
Project settings > C/C++ | Code Generation | runtime library : - Multithreaded DLL (/MD) in release mode compilation - Multithreaded Debug DLL (/MDd) in debug mode compilation
This will prevent your code from crashing when it will try to deallocate some memory on the heap which was actually allocated by the DLL (the exception objects for example).
Using the code
Once the parser is inserted in your project using whatever way you chose, you can use it then. You can use your new parser as you like: as a global object (never say I told you to!), as a local variable in the function that needs to get the result of a formula, or as a member of a class (much appreciated - I personally declare a CVCalcParser m_Parser
in my CVisualCalcDlg
dialog box class). You don't even have to initialize it in your constructor (or wherever else).
When you have to start the parser, just call its CVCalcParser::Evaluate(std::string)
member and pass to it the string to parse.
This could be simply done as follows:
StrDest.Format("%f", m_Parser.Evaluate(" 2 + 2 - 1 " );
I, however, emit an objection. The parser can throw exceptions which have to be caught so as not to terminate your program. So, there must be at least a basic try/catch
statement around the previous instruction. The best would be to test each exception category plus the warnings.
Here is how my dialog box calls the parser in VisualCalc:
void CVisualCalcDlg::OnCalculate() { CString strSource, strDest; m_peInput->GetWindowText(strSource); // Initializes and calls the Parser try { VALUES_TYPE valResult = m_Parser.Evaluate((LPCTSTR)strSource); strDest.Format("%.8f", valResult); strDest = this->RemoveTrailingZeros(strDest); strDest = this->InsertThousandSpaces(strDest); if (m_Parser.HasWarning()) { // Adds the warning after the result strDest.Format("%s ; %s", strDest, m_Parser.GetWarningMsg().c_str()); } m_peInput->SetSel(0, -1); } catch (CSyntaxException& ex) { strDest.Format("Syntax error %d : %s", ex.GetExceptionNumber(), ex.GetMessage().c_str()); m_peInput->SetSel(ex.GetErrorPos(), ex.GetErrorPos()); } catch (CMathematicException& ex) { strDest.Format("Mathematic error %d : %s", ex.GetExceptionNumber(), ex.GetMessage().c_str()); m_peInput->SetSel(ex.GetErrorPos(), ex.GetErrorPos()); } catch (CFunctionException& ex) { strDest.Format("Function error %d : %s", ex.GetExceptionNumber(), ex.GetMessage().c_str()); m_peInput->SetSel(ex.GetErrorPos(), ex.GetErrorPos()); } catch (CParameterException& ex) { strDest.Format("Parameter error %d : %s", ex.GetExceptionNumber(), ex.GetMessage().c_str()); m_peInput->SetSel(ex.GetErrorPos(), ex.GetErrorPos()); } catch (CVariableException& ex) { strDest.Format("Variable error %d : %s", ex.GetExceptionNumber(), ex.GetMessage().c_str()); m_peInput->SetSel(ex.GetErrorPos(), ex.GetErrorPos()); } catch (CDomainException& ex) { strDest.Format("Domain error %d : %s", ex.GetExceptionNumber(), ex.GetMessage().c_str()); m_peInput->SetSel(ex.GetErrorPos(), ex.GetErrorPos()); } catch (CParserException& ex) { strDest.Format("Parser error %d : %s", ex.GetExceptionNumber(), ex.GetMessage().c_str()); m_peInput->SetSel(ex.GetErrorPos(), ex.GetErrorPos()); } catch (...) { strDest.Format("Unknown parser internal error"); } // Prints the result... m_peOutput->SetWindowText(strDest); // Updates the listboxes' contents this->UpdateAnswersList(); this->UpdateVariablesList(); this->UpdateFunctionsList(); // Give the focus back to the input Edit Box m_peInput->SetFocus(); }
Here we are then. The keys are in your hands...
Conclusion and Thanks
VisualCalc has grown up from my version 1.0 (never distributed), to the actual version 3.0, passing through some private releases. It became a modular project which could be easily inserted into a whole calculation project. It also provides, as the subtitle tells, a starting point to write a recursive-descendent parser.
So, I'd like to thank all of you who contributed in little and big ways to this project with your questions, suggestions, helps etc. (thank you Cedric Moonen for your very valuable help on the /MD compiler option, and thank you VuNic for having pointed me in the right direction in the darkness of DLLs). I invite all of you to continue your participation in this project providing new ideas, new features, and giving some concurrent implementations too...
Future features
- Fix operators priority bug.
- Fix unknown exception (2999 exits the program without exception).
- Manage positive/negative infinites.
- Popup menus on Erase list buttons.
- Fix reading end of stream.
- Change
m_PI
andm_E
visibility topublic static const
. - New
TV_PERIOD
TokenValue
enumerator for localization. - Hyperlinks inside the About dialog box.
History
- 16/04/2006: Release of VisualCalc v3.0.
- Separation of the parser into its own DLL.
- Migration of the project for MS Visual C++ .NET 2003.
- 06/02/2006: Release of VisualCalc v2.24.
- Parser reading stream fixed.
- Parser exception case fixed (implicit multiplication).
- Parser
Pow()
calculation fixed (x-n). - Removed the zeros trailing at the end of the results.
- Added a thousand separation in the result display.
- New button to switch between the variable names and their values.
- New
sqrtn(x, n)
function for n-order root. - Parser code commented for Doxygen documentation generator.
- Moved all GUI string into resource string table.
- 19/10/2005: Release of VisualCalc v2.20.
- The parser has been split into its own class.
- The parser is now standard C++ compliant.
- Turned value type to
long double
. - Errors are at last managed by exceptions and the codes were redefined.
- New button to switch between last answer's results and formulas.
- The result field has been changed to an edit box to allow copying its content.
- 26/01/2005: Release of VisualCalc v2.13.
- Errors are now managed by codes.
- New tab in Help dialog box for error codes.
- 28/12/2004: Release of VisualCalc v2.12
- Addition of some more functions.
- Addition of some operators.
- New Help dialog box with tabs for both functions and operators.
- 13/11/2004: Release of VisualCalc v2.0.
- Addition of the functions list.
- New sequence operator (',').
- Addition of catalog (initial version of the help tabs).
- Better controls management in the source of the dialog box.
- Source code cleaned (yes, it wasn't done yet...).
- New About dialog box.
- 05/10/2004: Release of VisualCalc v1.0.
- List of the last answers.
- List of the variables (assigned + constants).
- Little documentation on the operators available in the About dialog box.