Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

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

0.00/5 (No votes)
28 Apr 2006 1  
How to start programming a parser.

The complete VisualCalc calculator project (GUI + parser included):

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

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 doubles. 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 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 AnswerItems. 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:

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.

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 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.

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.

  1. Copy VCalcParser.h, VCalcParser.cpp, VCalcParserExceptions.h, VCalcParserExceptions.cpp, and VCalcParserTypes.h in your sources.
  2. 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 and m_E visibility to public 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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here