The complete VisualCalc calculator project (GUI + parser included):
VisualCalc parser project (calculation engine only):
- Separation of the parser into its own DLL.
- Migration of the project for Microsoft Visual C++ .NET 2003.
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...
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.
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:
const VALUES_TYPE m_PI;
const VALUES_TYPE m_E;
TokenValue m_CurrentToken;
std::string m_Source;
std::string m_strIdentifierValue;
std::string m_strWarningMsg;
VALUES_TYPE m_valNumberValue;
bool m_bWarningFlag;
bool m_bEndEncountered;
int m_iCurrentIndex;
std::list<std::string> m_lstFunctions;
std::map<std::string, VALUES_TYPE> m_mapVariables;
std::deque<AnswerItem> m_dqeAnswersHistory;
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:
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();
VALUES_TYPE Evaluate(const std::string& Source)
throw(CVCalcParserException);
private:
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);
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
:
typedef long double 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 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:
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 the GetToken()
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 to eof()
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:
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.
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:
Levels |
Operators |
9 |
() |
parenthesis |
8 |
+ |
unary plus |
- |
unary minus |
7 |
! |
factorial |
� |
Degrees to radians conversion |
6 |
% |
modulus |
5 |
^ |
power |
4 |
* |
multiplication |
/ |
division |
3 |
+ |
addition |
- |
subtraction |
2 |
= |
assign |
1 |
, |
sequence | |
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 history deque
.
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 for rad()
. 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 of switch
statements which test the following m_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
or e
) 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 of 2 * x
.
foo(3!)
instead of foo * (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 in m_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 the m_bEndEncountered
flag.
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
).
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 as Cos()
.
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 in n | .
nCp |
nCp(n, p) |
returns the combination of p elements in n . |
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.
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.
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.
You can either recompile the parser source files in your project or import the parser from its DLL. Follow the guide.
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).
- 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).
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);
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()) {
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");
}
m_peOutput->SetWindowText(strDest);
this->UpdateAnswersList();
this->UpdateVariablesList();
this->UpdateFunctionsList();
m_peInput->SetFocus();
}
Here we are then. The keys are in your hands...
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...
- 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
and m_E
visibility to public static const
.
- New
TV_PERIOD
TokenValue
enumerator for localization.
- Hyperlinks inside the About dialog box.
- 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.