Click here to Skip to main content
15,881,248 members
Articles / Desktop Programming / Windows Forms

A Tiny Expression Evaluator

Rate me:
Please Sign up or sign in to vote.
4.96/5 (71 votes)
18 Aug 2011CPOL10 min read 148.4K   4.1K   124  
A utility that allows you to enter simple and more complex mathematical formulas which will be evaluated and calculated on the spot
// Generated by TinyPG v1.3 available at www.codeproject.com

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Xml.Serialization;

namespace calculator
{
    #region Scanner

    public partial class Scanner
    {
        public string Input;
        public int StartPos = 0;
        public int EndPos = 0;
        public int CurrentLine;
        public int CurrentColumn;
        public int CurrentPosition;
        public List<Token> Skipped; // tokens that were skipped
        public Dictionary<TokenType, Regex> Patterns;

        private Token LookAheadToken;
        private List<TokenType> Tokens;
        private List<TokenType> SkipList; // tokens to be skipped

        public Scanner()
        {
            Regex regex;
            Patterns = new Dictionary<TokenType, Regex>();
            Tokens = new List<TokenType>();
            LookAheadToken = null;
            Skipped = new List<Token>();

            SkipList = new List<TokenType>();
            SkipList.Add(TokenType.WHITESPACE);

            regex = new Regex(@"true|false", RegexOptions.Compiled);
            Patterns.Add(TokenType.BOOLEANLITERAL, regex);
            Tokens.Add(TokenType.BOOLEANLITERAL);

            regex = new Regex(@"[0-9]+(UL|Ul|uL|ul|LU|Lu|lU|lu|U|u|L|l)?", RegexOptions.Compiled);
            Patterns.Add(TokenType.DECIMALINTEGERLITERAL, regex);
            Tokens.Add(TokenType.DECIMALINTEGERLITERAL);

            regex = new Regex(@"([0-9]+\.[0-9]+([eE][+-]?[0-9]+)?([fFdDMm]?)?)|(\.[0-9]+([eE][+-]?[0-9]+)?([fFdDMm]?)?)|([0-9]+([eE][+-]?[0-9]+)([fFdDMm]?)?)|([0-9]+([fFdDMm]?))", RegexOptions.Compiled);
            Patterns.Add(TokenType.REALLITERAL, regex);
            Tokens.Add(TokenType.REALLITERAL);

            regex = new Regex(@"0(x|X)[0-9a-fA-F]+", RegexOptions.Compiled);
            Patterns.Add(TokenType.HEXINTEGERLITERAL, regex);
            Tokens.Add(TokenType.HEXINTEGERLITERAL);

            regex = new Regex(@"\""(\""\""|[^\""])*\""", RegexOptions.Compiled);
            Patterns.Add(TokenType.STRINGLITERAL, regex);
            Tokens.Add(TokenType.STRINGLITERAL);

            regex = new Regex(@"[a-zA-Z_][a-zA-Z0-9_]*(?=\s*\()", RegexOptions.Compiled);
            Patterns.Add(TokenType.FUNCTION, regex);
            Tokens.Add(TokenType.FUNCTION);

            regex = new Regex(@"(?i)pi|e", RegexOptions.Compiled);
            Patterns.Add(TokenType.CONSTANT, regex);
            Tokens.Add(TokenType.CONSTANT);

            regex = new Regex(@"{\s*", RegexOptions.Compiled);
            Patterns.Add(TokenType.BRACEOPEN, regex);
            Tokens.Add(TokenType.BRACEOPEN);

            regex = new Regex(@"\s*}", RegexOptions.Compiled);
            Patterns.Add(TokenType.BRACECLOSE, regex);
            Tokens.Add(TokenType.BRACECLOSE);

            regex = new Regex(@"\(\s*", RegexOptions.Compiled);
            Patterns.Add(TokenType.BRACKETOPEN, regex);
            Tokens.Add(TokenType.BRACKETOPEN);

            regex = new Regex(@"\s*\)", RegexOptions.Compiled);
            Patterns.Add(TokenType.BRACKETCLOSE, regex);
            Tokens.Add(TokenType.BRACKETCLOSE);

            regex = new Regex(@";", RegexOptions.Compiled);
            Patterns.Add(TokenType.SEMICOLON, regex);
            Tokens.Add(TokenType.SEMICOLON);

            regex = new Regex(@"\+\+", RegexOptions.Compiled);
            Patterns.Add(TokenType.PLUSPLUS, regex);
            Tokens.Add(TokenType.PLUSPLUS);

            regex = new Regex(@"--", RegexOptions.Compiled);
            Patterns.Add(TokenType.MINUSMINUS, regex);
            Tokens.Add(TokenType.MINUSMINUS);

            regex = new Regex(@"\|\|", RegexOptions.Compiled);
            Patterns.Add(TokenType.PIPEPIPE, regex);
            Tokens.Add(TokenType.PIPEPIPE);

            regex = new Regex(@"&&", RegexOptions.Compiled);
            Patterns.Add(TokenType.AMPAMP, regex);
            Tokens.Add(TokenType.AMPAMP);

            regex = new Regex(@"&(?!&)", RegexOptions.Compiled);
            Patterns.Add(TokenType.AMP, regex);
            Tokens.Add(TokenType.AMP);

            regex = new Regex(@"\^", RegexOptions.Compiled);
            Patterns.Add(TokenType.POWER, regex);
            Tokens.Add(TokenType.POWER);

            regex = new Regex(@"\+", RegexOptions.Compiled);
            Patterns.Add(TokenType.PLUS, regex);
            Tokens.Add(TokenType.PLUS);

            regex = new Regex(@"-", RegexOptions.Compiled);
            Patterns.Add(TokenType.MINUS, regex);
            Tokens.Add(TokenType.MINUS);

            regex = new Regex(@"=", RegexOptions.Compiled);
            Patterns.Add(TokenType.EQUAL, regex);
            Tokens.Add(TokenType.EQUAL);

            regex = new Regex(@"!=", RegexOptions.Compiled);
            Patterns.Add(TokenType.NOTEQUAL, regex);
            Tokens.Add(TokenType.NOTEQUAL);

            regex = new Regex(@"!", RegexOptions.Compiled);
            Patterns.Add(TokenType.NOT, regex);
            Tokens.Add(TokenType.NOT);

            regex = new Regex(@"\*", RegexOptions.Compiled);
            Patterns.Add(TokenType.ASTERIKS, regex);
            Tokens.Add(TokenType.ASTERIKS);

            regex = new Regex(@"/", RegexOptions.Compiled);
            Patterns.Add(TokenType.SLASH, regex);
            Tokens.Add(TokenType.SLASH);

            regex = new Regex(@"%", RegexOptions.Compiled);
            Patterns.Add(TokenType.PERCENT, regex);
            Tokens.Add(TokenType.PERCENT);

            regex = new Regex(@"\?", RegexOptions.Compiled);
            Patterns.Add(TokenType.QUESTIONMARK, regex);
            Tokens.Add(TokenType.QUESTIONMARK);

            regex = new Regex(@",", RegexOptions.Compiled);
            Patterns.Add(TokenType.COMMA, regex);
            Tokens.Add(TokenType.COMMA);

            regex = new Regex(@"<=", RegexOptions.Compiled);
            Patterns.Add(TokenType.LESSEQUAL, regex);
            Tokens.Add(TokenType.LESSEQUAL);

            regex = new Regex(@">=", RegexOptions.Compiled);
            Patterns.Add(TokenType.GREATEREQUAL, regex);
            Tokens.Add(TokenType.GREATEREQUAL);

            regex = new Regex(@"<", RegexOptions.Compiled);
            Patterns.Add(TokenType.LESSTHAN, regex);
            Tokens.Add(TokenType.LESSTHAN);

            regex = new Regex(@">", RegexOptions.Compiled);
            Patterns.Add(TokenType.GREATERTHAN, regex);
            Tokens.Add(TokenType.GREATERTHAN);

            regex = new Regex(@":", RegexOptions.Compiled);
            Patterns.Add(TokenType.COLON, regex);
            Tokens.Add(TokenType.COLON);

            regex = new Regex(@"^$", RegexOptions.Compiled);
            Patterns.Add(TokenType.EOF, regex);
            Tokens.Add(TokenType.EOF);

            regex = new Regex(@"\s+", RegexOptions.Compiled);
            Patterns.Add(TokenType.WHITESPACE, regex);
            Tokens.Add(TokenType.WHITESPACE);


        }

        public void Init(string input)
        {
            this.Input = input;
            StartPos = 0;
            EndPos = 0;
            CurrentLine = 0;
            CurrentColumn = 0;
            CurrentPosition = 0;
            LookAheadToken = null;
        }

        public Token GetToken(TokenType type)
        {
            Token t = new Token(this.StartPos, this.EndPos);
            t.Type = type;
            return t;
        }

         /// <summary>
        /// executes a lookahead of the next token
        /// and will advance the scan on the input string
        /// </summary>
        /// <returns></returns>
        public Token Scan(params TokenType[] expectedtokens)
        {
            Token tok = LookAhead(expectedtokens); // temporarely retrieve the lookahead
            LookAheadToken = null; // reset lookahead token, so scanning will continue
            StartPos = tok.EndPos;
            EndPos = tok.EndPos; // set the tokenizer to the new scan position
            return tok;
        }

        /// <summary>
        /// returns token with longest best match
        /// </summary>
        /// <returns></returns>
        public Token LookAhead(params TokenType[] expectedtokens)
        {
            int i;
            int startpos = StartPos;
            Token tok = null;
            List<TokenType> scantokens;


            // this prevents double scanning and matching
            // increased performance
            if (LookAheadToken != null 
                && LookAheadToken.Type != TokenType._UNDETERMINED_ 
                && LookAheadToken.Type != TokenType._NONE_) return LookAheadToken;

            // if no scantokens specified, then scan for all of them (= backward compatible)
            if (expectedtokens.Length == 0)
                scantokens = Tokens;
            else
            {
                scantokens = new List<TokenType>(expectedtokens);
                scantokens.AddRange(SkipList);
            }

            do
            {

                int len = -1;
                TokenType index = (TokenType)int.MaxValue;
                string input = Input.Substring(startpos);

                tok = new Token(startpos, EndPos);

                for (i = 0; i < scantokens.Count; i++)
                {
                    Regex r = Patterns[scantokens[i]];
                    Match m = r.Match(input);
                    if (m.Success && m.Index == 0 && ((m.Length > len) || (scantokens[i] < index && m.Length == len )))
                    {
                        len = m.Length;
                        index = scantokens[i];  
                    }
                }

                if (index >= 0 && len >= 0)
                {
                    tok.EndPos = startpos + len;
                    tok.Text = Input.Substring(tok.StartPos, len);
                    tok.Type = index;
                }
                else if (tok.StartPos < tok.EndPos - 1)
                {
                    tok.Text = Input.Substring(tok.StartPos, 1);
                }

                if (SkipList.Contains(tok.Type))
                {
                    startpos = tok.EndPos;
                    Skipped.Add(tok);
                }
                else
                {
                    // only assign to non-skipped tokens
                    tok.Skipped = Skipped; // assign prior skips to this token
                    Skipped = new List<Token>(); //reset skips
                }
            }
            while (SkipList.Contains(tok.Type));

            LookAheadToken = tok;
            return tok;
        }
    }

    #endregion

    #region Token

    public enum TokenType
    {

            //Non terminal tokens:
            _NONE_  = 0,
            _UNDETERMINED_= 1,

            //Non terminal tokens:
            Start   = 2,
            UnaryExpression= 3,
            Function= 4,
            PrimaryExpression= 5,
            ParenthesizedExpression= 6,
            PowerExpression= 7,
            MultiplicativeExpression= 8,
            AdditiveExpression= 9,
            ConcatEpression= 10,
            RelationalExpression= 11,
            EqualityExpression= 12,
            ConditionalAndExpression= 13,
            ConditionalOrExpression= 14,
            Expression= 15,
            Params  = 16,
            Literal = 17,
            IntegerLiteral= 18,
            RealLiteral= 19,
            StringLiteral= 20,

            //Terminal tokens:
            BOOLEANLITERAL= 21,
            DECIMALINTEGERLITERAL= 22,
            REALLITERAL= 23,
            HEXINTEGERLITERAL= 24,
            STRINGLITERAL= 25,
            FUNCTION= 26,
            CONSTANT= 27,
            BRACEOPEN= 28,
            BRACECLOSE= 29,
            BRACKETOPEN= 30,
            BRACKETCLOSE= 31,
            SEMICOLON= 32,
            PLUSPLUS= 33,
            MINUSMINUS= 34,
            PIPEPIPE= 35,
            AMPAMP  = 36,
            AMP     = 37,
            POWER   = 38,
            PLUS    = 39,
            MINUS   = 40,
            EQUAL   = 41,
            NOTEQUAL= 42,
            NOT     = 43,
            ASTERIKS= 44,
            SLASH   = 45,
            PERCENT = 46,
            QUESTIONMARK= 47,
            COMMA   = 48,
            LESSEQUAL= 49,
            GREATEREQUAL= 50,
            LESSTHAN= 51,
            GREATERTHAN= 52,
            COLON   = 53,
            EOF     = 54,
            WHITESPACE= 55
    }

    public class Token
    {
        private int startpos;
        private int endpos;
        private string text;
        private object value;

        // contains all prior skipped symbols
        private List<Token> skipped;

        public int StartPos { 
            get { return startpos;} 
            set { startpos = value; }
        }

        public int Length { 
            get { return endpos - startpos;} 
        }

        public int EndPos { 
            get { return endpos;} 
            set { endpos = value; }
        }

        public string Text { 
            get { return text;} 
            set { text = value; }
        }

        public List<Token> Skipped { 
            get { return skipped;} 
            set { skipped = value; }
        }
        public object Value { 
            get { return value;} 
            set { this.value = value; }
        }

        [XmlAttribute]
        public TokenType Type;

        public Token()
            : this(0, 0)
        {
        }

        public Token(int start, int end)
        {
            Type = TokenType._UNDETERMINED_;
            startpos = start;
            endpos = end;
            Text = ""; // must initialize with empty string, may cause null reference exceptions otherwise
            Value = null;
        }

        public void UpdateRange(Token token)
        {
            if (token.StartPos < startpos) startpos = token.StartPos;
            if (token.EndPos > endpos) endpos = token.EndPos;
        }

        public override string ToString()
        {
            if (Text != null)
                return Type.ToString() + " '" + Text + "'";
            else
                return Type.ToString();
        }
    }

    #endregion
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Rubicon
Netherlands Netherlands
Currently Herre Kuijpers is employed at Rubicon. During his career he developed skills with all kinds of technologies, methodologies and programming languages such as c#, ASP.Net, .Net Core, VC++, Javascript, SQL, Agile, Scrum, DevOps, ALM. Currently he fulfills the role of software architect in various projects.

Herre Kuijpers is a very experienced software architect with deep knowledge of software design and development on the Microsoft .Net platform. He has a broad knowledge of Microsoft products and knows how these, in combination with custom software, can be optimally implemented in the often complex environment of the customer.

Comments and Discussions