diff --git a/ClosedXML/Excel/CalcEngine/CalcEngine.cs b/ClosedXML/Excel/CalcEngine/CalcEngine.cs index 07a5447..1b18b15 100644 --- a/ClosedXML/Excel/CalcEngine/CalcEngine.cs +++ b/ClosedXML/Excel/CalcEngine/CalcEngine.cs @@ -1,307 +1,287 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using ClosedXML.Excel.CalcEngine.Functions; - -namespace ClosedXML.Excel.CalcEngine -{ - /// - /// CalcEngine parses strings and returns Expression objects that can - /// be evaluated. - /// - /// - /// This class has three extensibility points: - /// Use the DataContext property to add an object's properties to the engine scope. - /// Use the RegisterFunction method to define custom functions. - /// Override the GetExternalObject method to add arbitrary variables to the engine scope. - /// - internal class CalcEngine - { +using System; +using System.Reflection; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Diagnostics; +using System.Text; +using System.Text.RegularExpressions; +using ClosedXML.Excel.CalcEngine; +using ClosedXML.Excel.CalcEngine.Functions; + +namespace ClosedXML.Excel.CalcEngine { + /// + /// CalcEngine parses strings and returns Expression objects that can + /// be evaluated. + /// + /// + /// This class has three extensibility points: + /// Use the DataContext property to add an object's properties to the engine scope. + /// Use the RegisterFunction method to define custom functions. + /// Override the GetExternalObject method to add arbitrary variables to the engine scope. + /// + internal class CalcEngine { //--------------------------------------------------------------------------- - - #region ** ctor - - public CalcEngine() - { - CultureInfo = CultureInfo.InvariantCulture; - _tkTbl = GetSymbolTable(); - Functions = GetFunctionTable(); - Variables = new Dictionary(StringComparer.OrdinalIgnoreCase); - _cache = new ExpressionCache(this); - OptimizeExpressions = true; -#if DEBUG - //this.Test(); -#endif - } - - #endregion - - //--------------------------------------------------------------------------- - - #region ** fields + #region ** fields // members - private string _expr; // expression being parsed - private int _len; // length of the expression being parsed - private int _ptr; // current pointer into expression - private Token _token; // current token being parsed - private Dictionary _tkTbl; // table with tokens (+, -, etc) - private ExpressionCache _cache; // cache with parsed expressions - private CultureInfo _ci; // culture info used to parse numbers/dates - private char _decimal; // localized decimal separator, list separator, percent sign - private char _listSep; // localized decimal separator, list separator, percent sign - private char _percent; // localized decimal separator, list separator, percent sign + string _expr; // expression being parsed + int _len; // length of the expression being parsed + int _ptr; // current pointer into expression + string _idChars; // valid characters in identifiers (besides alpha and digits) + Token _token; // current token being parsed + Dictionary _tkTbl; // table with tokens (+, -, etc) + Dictionary _fnTbl; // table with constants and functions (pi, sin, etc) + Dictionary _vars; // table with variables + object _dataContext; // object with properties + bool _optimize; // optimize expressions when parsing + ExpressionCache _cache; // cache with parsed expressions + CultureInfo _ci; // culture info used to parse numbers/dates + char _decimal, _listSep, _percent; // localized decimal separator, list separator, percent sign + + #endregion + + //--------------------------------------------------------------------------- + #region ** ctor + + public CalcEngine() { + CultureInfo = CultureInfo.InvariantCulture; + _tkTbl = GetSymbolTable(); + _fnTbl = GetFunctionTable(); + _vars = new Dictionary(StringComparer.OrdinalIgnoreCase); + _cache = new ExpressionCache(this); + _optimize = true; +#if DEBUG + //this.Test(); +#endif + } - #endregion + #endregion //--------------------------------------------------------------------------- + #region ** object model - #region ** object model - - /// - /// Parses a string into an . - /// - /// String to parse. - /// An object that can be evaluated. - public Expression Parse(string expression) - { + /// + /// Parses a string into an . + /// + /// String to parse. + /// An object that can be evaluated. + public Expression Parse(string expression) { // initialize _expr = expression; _len = _expr.Length; - _ptr = 0; - - // skip leading equals sign - if (_len > 0 && _expr[0] == '=') - { - _ptr++; + _ptr = 0; + + // skip leading equals sign + if (_len > 0 && _expr[0] == '=') { + _ptr++; } // parse the expression var expr = ParseExpression(); // check for errors - if (_token.ID != TKID.END) - { + if (_token.ID != TKID.END) { Throw(); - } - - // optimize expression - if (OptimizeExpressions) - { - expr = expr.Optimize(); + } + + // optimize expression + if (_optimize) { + expr = expr.Optimize(); } // done return expr; - } - - /// - /// Evaluates a string. - /// - /// Expression to evaluate. - /// The value of the expression. - /// - /// If you are going to evaluate the same expression several times, - /// it is more efficient to parse it only once using the - /// method and then using the Expression.Evaluate method to evaluate - /// the parsed expression. - /// - public object Evaluate(string expression) - { - var x = //Parse(expression); - _cache != null - ? _cache[expression] - : Parse(expression); + } + /// + /// Evaluates a string. + /// + /// Expression to evaluate. + /// The value of the expression. + /// + /// If you are going to evaluate the same expression several times, + /// it is more efficient to parse it only once using the + /// method and then using the Expression.Evaluate method to evaluate + /// the parsed expression. + /// + public object Evaluate(string expression) { + var x = //Parse(expression); + _cache != null + ? _cache[expression] + : Parse(expression); return x.Evaluate(); + } + /// + /// Gets or sets whether the calc engine should keep a cache with parsed + /// expressions. + /// + public bool CacheExpressions { + get { return _cache != null; } + set { + if (value != CacheExpressions) { + _cache = value + ? new ExpressionCache(this) + : null; + } + } + } + /// + /// Gets or sets whether the calc engine should optimize expressions when + /// they are parsed. + /// + public bool OptimizeExpressions { + get { return _optimize; } + set { _optimize = value; } + } + /// + /// Gets or sets a string that specifies special characters that are valid for identifiers. + /// + /// + /// Identifiers must start with a letter or an underscore, which may be followed by + /// additional letters, underscores, or digits. This string allows you to specify + /// additional valid characters such as ':' or '!' (used in Excel range references + /// for example). + /// + public string IdentifierChars { + get { return _idChars; } + set { _idChars = value; } + } + /// + /// Registers a function that can be evaluated by this . + /// + /// Function name. + /// Minimum parameter count. + /// Maximum parameter count. + /// Delegate that evaluates the function. + public void RegisterFunction(string functionName, int parmMin, int parmMax, CalcEngineFunction fn) { + _fnTbl.Add(functionName, new FunctionDefinition(parmMin, parmMax, fn)); + } + /// + /// Registers a function that can be evaluated by this . + /// + /// Function name. + /// Parameter count. + /// Delegate that evaluates the function. + public void RegisterFunction(string functionName, int parmCount, CalcEngineFunction fn) { + RegisterFunction(functionName, parmCount, parmCount, fn); + } + /// + /// Gets an external object based on an identifier. + /// + /// + /// This method is useful when the engine needs to create objects dynamically. + /// For example, a spreadsheet calc engine would use this method to dynamically create cell + /// range objects based on identifiers that cannot be enumerated at design time + /// (such as "AB12", "A1:AB12", etc.) + /// + public virtual object GetExternalObject(string identifier) { + return null; + } + /// + /// Gets or sets the DataContext for this . + /// + /// + /// Once a DataContext is set, all public properties of the object become available + /// to the CalcEngine, including sub-properties such as "Address.Street". These may + /// be used with expressions just like any other constant. + /// + public virtual object DataContext { + get { return _dataContext; } + set { _dataContext = value; } + } + /// + /// Gets the dictionary that contains function definitions. + /// + public Dictionary Functions { + get { return _fnTbl; } + } + /// + /// Gets the dictionary that contains simple variables (not in the DataContext). + /// + public Dictionary Variables { + get { return _vars; } + } + /// + /// Gets or sets the to use when parsing numbers and dates. + /// + public CultureInfo CultureInfo { + get { return _ci; } + set { + _ci = value; + var nf = _ci.NumberFormat; + _decimal = nf.NumberDecimalSeparator[0]; + _percent = nf.PercentSymbol[0]; + _listSep = _ci.TextInfo.ListSeparator[0]; + } } - /// - /// Gets or sets whether the calc engine should keep a cache with parsed - /// expressions. - /// - public bool CacheExpressions - { - get { return _cache != null; } - set - { - if (value != CacheExpressions) - { - _cache = value - ? new ExpressionCache(this) - : null; - } - } - } - - /// - /// Gets or sets whether the calc engine should optimize expressions when - /// they are parsed. - /// - public bool OptimizeExpressions { get; set; } - - /// - /// Gets or sets a string that specifies special characters that are valid for identifiers. - /// - /// - /// Identifiers must start with a letter or an underscore, which may be followed by - /// additional letters, underscores, or digits. This string allows you to specify - /// additional valid characters such as ':' or '!' (used in Excel range references - /// for example). - /// - public string IdentifierChars { get; set; } - - /// - /// Registers a function that can be evaluated by this . - /// - /// Function name. - /// Minimum parameter count. - /// Maximum parameter count. - /// Delegate that evaluates the function. - public void RegisterFunction(string functionName, int parmMin, int parmMax, CalcEngineFunction fn) - { - Functions.Add(functionName, new FunctionDefinition(parmMin, parmMax, fn)); - } - - /// - /// Registers a function that can be evaluated by this . - /// - /// Function name. - /// Parameter count. - /// Delegate that evaluates the function. - public void RegisterFunction(string functionName, int parmCount, CalcEngineFunction fn) - { - RegisterFunction(functionName, parmCount, parmCount, fn); - } - - /// - /// Gets an external object based on an identifier. - /// - /// - /// This method is useful when the engine needs to create objects dynamically. - /// For example, a spreadsheet calc engine would use this method to dynamically create cell - /// range objects based on identifiers that cannot be enumerated at design time - /// (such as "AB12", "A1:AB12", etc.) - /// - public virtual object GetExternalObject(string identifier) - { - return null; - } - - /// - /// Gets or sets the DataContext for this . - /// - /// - /// Once a DataContext is set, all public properties of the object become available - /// to the CalcEngine, including sub-properties such as "Address.Street". These may - /// be used with expressions just like any other constant. - /// - public virtual object DataContext { get; set; } - - /// - /// Gets the dictionary that contains function definitions. - /// - public Dictionary Functions { get; private set; } - - /// - /// Gets the dictionary that contains simple variables (not in the DataContext). - /// - public Dictionary Variables { get; } - - /// - /// Gets or sets the to use when parsing numbers and dates. - /// - public CultureInfo CultureInfo - { - get { return _ci; } - set - { - _ci = value; - var nf = _ci.NumberFormat; - _decimal = nf.NumberDecimalSeparator[0]; - _percent = nf.PercentSymbol[0]; - _listSep = _ci.TextInfo.ListSeparator[0]; - } - } - - #endregion + #endregion //--------------------------------------------------------------------------- - - #region ** token/keyword tables + #region ** token/keyword tables // build/get static token table - private Dictionary GetSymbolTable() - { - if (_tkTbl == null) - { - _tkTbl = new Dictionary(); - AddToken('&', TKID.CONCAT, TKTYPE.ADDSUB); - AddToken('+', TKID.ADD, TKTYPE.ADDSUB); - AddToken('-', TKID.SUB, TKTYPE.ADDSUB); - AddToken('(', TKID.OPEN, TKTYPE.GROUP); - AddToken(')', TKID.CLOSE, TKTYPE.GROUP); - AddToken('*', TKID.MUL, TKTYPE.MULDIV); - AddToken('.', TKID.PERIOD, TKTYPE.GROUP); - AddToken('/', TKID.DIV, TKTYPE.MULDIV); - AddToken('\\', TKID.DIVINT, TKTYPE.MULDIV); - AddToken('=', TKID.EQ, TKTYPE.COMPARE); - AddToken('>', TKID.GT, TKTYPE.COMPARE); - AddToken('<', TKID.LT, TKTYPE.COMPARE); - AddToken('^', TKID.POWER, TKTYPE.POWER); - AddToken("<>", TKID.NE, TKTYPE.COMPARE); - AddToken(">=", TKID.GE, TKTYPE.COMPARE); + Dictionary GetSymbolTable() { + if (_tkTbl == null) { + _tkTbl = new Dictionary(); + AddToken('&', TKID.CONCAT, TKTYPE.ADDSUB); + AddToken('+', TKID.ADD, TKTYPE.ADDSUB); + AddToken('-', TKID.SUB, TKTYPE.ADDSUB); + AddToken('(', TKID.OPEN, TKTYPE.GROUP); + AddToken(')', TKID.CLOSE, TKTYPE.GROUP); + AddToken('*', TKID.MUL, TKTYPE.MULDIV); + AddToken('.', TKID.PERIOD, TKTYPE.GROUP); + AddToken('/', TKID.DIV, TKTYPE.MULDIV); + AddToken('\\', TKID.DIVINT, TKTYPE.MULDIV); + AddToken('=', TKID.EQ, TKTYPE.COMPARE); + AddToken('>', TKID.GT, TKTYPE.COMPARE); + AddToken('<', TKID.LT, TKTYPE.COMPARE); + AddToken('^', TKID.POWER, TKTYPE.POWER); + AddToken("<>", TKID.NE, TKTYPE.COMPARE); + AddToken(">=", TKID.GE, TKTYPE.COMPARE); AddToken("<=", TKID.LE, TKTYPE.COMPARE); // list separator is localized, not necessarily a comma // so it can't be on the static table //AddToken(',', TKID.COMMA, TKTYPE.GROUP); - } - return _tkTbl; + } + return _tkTbl; + } + void AddToken(object symbol, TKID id, TKTYPE type) { + var token = new Token(symbol, id, type); + _tkTbl.Add(symbol, token); + } + + // build/get static keyword table + Dictionary GetFunctionTable() { + if (_fnTbl == null) { + // create table + _fnTbl = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + + // register built-in functions (and constants) + Information.Register(this); + Logical.Register(this); + Lookup.Register(this); + MathTrig.Register(this); + Text.Register(this); + Statistical.Register(this); + DateAndTime.Register(this); + } + return _fnTbl; } - private void AddToken(object symbol, TKID id, TKTYPE type) - { - var token = new Token(symbol, id, type); - _tkTbl.Add(symbol, token); - } - - // build/get static keyword table - private Dictionary GetFunctionTable() - { - if (Functions == null) - { - // create table - Functions = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - - // register built-in functions (and constants) - Information.Register(this); - Logical.Register(this); - Lookup.Register(this); - MathTrig.Register(this); - Text.Register(this); - Statistical.Register(this); - DateAndTime.Register(this); - } - return Functions; - } - - #endregion + #endregion //--------------------------------------------------------------------------- + #region ** private stuff - #region ** private stuff - - private Expression ParseExpression() - { + Expression ParseExpression() { GetToken(); return ParseCompare(); } - - private Expression ParseCompare() - { + Expression ParseCompare() { var x = ParseAddSub(); - while (_token.Type == TKTYPE.COMPARE) - { + while (_token.Type == TKTYPE.COMPARE) { var t = _token; GetToken(); var exprArg = ParseAddSub(); @@ -309,38 +289,29 @@ } return x; } - - private Expression ParseAddSub() - { + Expression ParseAddSub() { var x = ParseMulDiv(); - while (_token.Type == TKTYPE.ADDSUB) - { + while (_token.Type == TKTYPE.ADDSUB) { var t = _token; GetToken(); var exprArg = ParseMulDiv(); x = new BinaryExpression(t, x, exprArg); } - return x; + return x; } - - private Expression ParseMulDiv() - { + Expression ParseMulDiv() { var x = ParsePower(); - while (_token.Type == TKTYPE.MULDIV) - { + while (_token.Type == TKTYPE.MULDIV) { var t = _token; GetToken(); var a = ParsePower(); x = new BinaryExpression(t, x, a); } - return x; + return x; } - - private Expression ParsePower() - { + Expression ParsePower() { var x = ParseUnary(); - while (_token.Type == TKTYPE.POWER) - { + while (_token.Type == TKTYPE.POWER) { var t = _token; GetToken(); var a = ParseUnary(); @@ -348,94 +319,80 @@ } return x; } - - private Expression ParseUnary() - { + Expression ParseUnary() { // unary plus and minus - if (_token.ID == TKID.ADD || _token.ID == TKID.SUB) - { + if (_token.ID == TKID.ADD || _token.ID == TKID.SUB) { var t = _token; - GetToken(); - var a = ParseAtom(); + GetToken(); + var a = ParseAtom(); return new UnaryExpression(t, a); } // not unary, return atom return ParseAtom(); } - - private Expression ParseAtom() - { - string id; - Expression x = null; + Expression ParseAtom() { + string id; + Expression x = null; FunctionDefinition fnDef = null; - switch (_token.Type) - { + switch (_token.Type) { // literals case TKTYPE.LITERAL: x = new Expression(_token); - break; - - // identifiers - case TKTYPE.IDENTIFIER: - - // get identifier - id = (string) _token.Value; - - // look for functions - if (Functions.TryGetValue(id, out fnDef)) - { - var p = GetParameters(); - var pCnt = p == null ? 0 : p.Count; - if (fnDef.ParmMin != -1 && pCnt < fnDef.ParmMin) - { - Throw("Too few parameters."); - } - if (fnDef.ParmMax != -1 && pCnt > fnDef.ParmMax) - { - Throw("Too many parameters."); - } - x = new FunctionExpression(fnDef, p); - break; - } - - // look for simple variables (much faster than binding!) - if (Variables.ContainsKey(id)) - { - x = new VariableExpression(Variables, id); - break; - } - - // look for external objects - var xObj = GetExternalObject(id); - if (xObj != null) - { - x = new XObjectExpression(xObj); - break; - } - - // look for bindings - if (DataContext != null) - { - var list = new List(); - for (var t = _token; t != null; t = GetMember()) - { - list.Add(new BindingInfo((string) t.Value, GetParameters())); - } - x = new BindingExpression(this, list, _ci); - break; - } - Throw("Unexpected identifier"); + break; + + // identifiers + case TKTYPE.IDENTIFIER: + + // get identifier + id = (string)_token.Value; + + // look for functions + if (_fnTbl.TryGetValue(id, out fnDef)) { + var p = GetParameters(); + var pCnt = p == null ? 0 : p.Count; + if (fnDef.ParmMin != -1 && pCnt < fnDef.ParmMin) { + Throw("Too few parameters."); + } + if (fnDef.ParmMax != -1 && pCnt > fnDef.ParmMax) { + Throw("Too many parameters."); + } + x = new FunctionExpression(fnDef, p); + break; + } + + // look for simple variables (much faster than binding!) + if (_vars.ContainsKey(id)) { + x = new VariableExpression(_vars, id); + break; + } + + // look for external objects + var xObj = GetExternalObject(id); + if (xObj != null) { + x = new XObjectExpression(xObj); + break; + } + + // look for bindings + if (DataContext != null) { + var list = new List(); + for (var t = _token; t != null; t = GetMember()) { + list.Add(new BindingInfo((string)t.Value, GetParameters())); + } + x = new BindingExpression(this, list, _ci); + break; + } + Throw("Unexpected identifier"); break; // sub-expressions case TKTYPE.GROUP: // anything other than opening parenthesis is illegal here - if (_token.ID != TKID.OPEN) - { - Throw("Expression expected."); + if (_token.ID != TKID.OPEN) { + Throw("Expression expected."); } // get expression @@ -443,18 +400,16 @@ x = ParseCompare(); // check that the parenthesis was closed - if (_token.ID != TKID.CLOSE) - { + if (_token.ID != TKID.CLOSE) { Throw("Unbalanced parenthesis."); } break; - } - - // make sure we got something... - if (x == null) - { - Throw(); + } + + // make sure we got something... + if (x == null) { + Throw(); } // done @@ -462,23 +417,19 @@ return x; } - #endregion + #endregion //--------------------------------------------------------------------------- + #region ** parser - #region ** parser - - private void GetToken() - { + void GetToken() { // eat white space - while (_ptr < _len && _expr[_ptr] <= ' ') - { + while (_ptr < _len && _expr[_ptr] <= ' ') { _ptr++; } // are we done? - if (_ptr >= _len) - { + if (_ptr >= _len) { _token = new Token(null, TKID.END, TKTYPE.GROUP); return; } @@ -490,36 +441,30 @@ // operators // this gets called a lot, so it's pretty optimized. // note that operators must start with non-letter/digit characters. - var isLetter = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + var isLetter = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); var isDigit = c >= '0' && c <= '9'; - if (!isLetter && !isDigit) - { + if (!isLetter && !isDigit) { // if this is a number starting with a decimal, don't parse as operator - var nxt = _ptr + 1 < _len ? _expr[_ptr + 1] : 0; - var isNumber = c == _decimal && nxt >= '0' && nxt <= '9'; - if (!isNumber) - { - // look up localized list separator - if (c == _listSep) - { - _token = new Token(c, TKID.COMMA, TKTYPE.GROUP); - _ptr++; - return; + var nxt = _ptr + 1 < _len ? _expr[_ptr + 1] : 0; + bool isNumber = c == _decimal && nxt >= '0' && nxt <= '9'; + if (!isNumber) { + // look up localized list separator + if (c == _listSep) { + _token = new Token(c, TKID.COMMA, TKTYPE.GROUP); + _ptr++; + return; } // look up single-char tokens on table - Token tk; - if (_tkTbl.TryGetValue(c, out tk)) - { + Token tk; + if (_tkTbl.TryGetValue(c, out tk)) { // save token we found _token = tk; _ptr++; // look for double-char tokens (special case) - if (_ptr < _len && (c == '>' || c == '<')) - { - if (_tkTbl.TryGetValue(_expr.Substring(_ptr - 1, 2), out tk)) - { + if (_ptr < _len && (c == '>' || c == '<')) { + if (_tkTbl.TryGetValue(_expr.Substring(_ptr - 1, 2), out tk)) { _token = tk; _ptr++; } @@ -532,184 +477,158 @@ } // parse numbers - if (isDigit || c == _decimal) - { - var sci = false; - var pct = false; - var div = -1.0; // use double, not int (this may get really big) - var val = 0.0; - for (i = 0; i + _ptr < _len; i++) - { - c = _expr[_ptr + i]; - - // digits always OK - if (c >= '0' && c <= '9') - { - val = val*10 + (c - '0'); - if (div > -1) - { - div *= 10; - } - continue; + if (isDigit || c == _decimal) { + var sci = false; + var pct = false; + var div = -1.0; // use double, not int (this may get really big) + var val = 0.0; + for (i = 0; i + _ptr < _len; i++) { + c = _expr[_ptr + i]; + + // digits always OK + if (c >= '0' && c <= '9') { + val = val * 10 + (c - '0'); + if (div > -1) { + div *= 10; + } + continue; } // one decimal is OK - if (c == _decimal && div < 0) - { + if (c == _decimal && div < 0) { div = 1; continue; } // scientific notation? - if ((c == 'E' || c == 'e') && !sci) - { + if ((c == 'E' || c == 'e') && !sci) { sci = true; c = _expr[_ptr + i + 1]; if (c == '+' || c == '-') i++; continue; - } - - // percentage? - if (c == _percent) - { - pct = true; - i++; + } + + // percentage? + if (c == _percent) { + pct = true; + i++; + break; } // end of literal break; - } - - // end of number, get value - if (!sci) - { - // much faster than ParseDouble - if (div > 1) - { - val /= div; - } - if (pct) - { - val /= 100.0; - } - } - else - { - var lit = _expr.Substring(_ptr, i); - val = ParseDouble(lit, _ci); - } - - // build token - _token = new Token(val, TKID.ATOM, TKTYPE.LITERAL); - - // advance pointer and return - _ptr += i; + } + + // end of number, get value + if (!sci) { + // much faster than ParseDouble + if (div > 1) { + val /= div; + } + if (pct) { + val /= 100.0; + } + } else { + var lit = _expr.Substring(_ptr, i); + val = ParseDouble(lit, _ci); + } + + // build token + _token = new Token(val, TKID.ATOM, TKTYPE.LITERAL); + + // advance pointer and return + _ptr += i; return; } // parse strings - if (c == '\"') - { + if (c == '\"') { // look for end quote, skip double quotes - for (i = 1; i + _ptr < _len; i++) - { + for (i = 1; i + _ptr < _len; i++) { c = _expr[_ptr + i]; if (c != '\"') continue; - var cNext = i + _ptr < _len - 1 ? _expr[_ptr + i + 1] : ' '; + char cNext = i + _ptr < _len - 1 ? _expr[_ptr + i + 1] : ' '; if (cNext != '\"') break; i++; } // check that we got the end of the string - if (c != '\"') - { + if (c != '\"') { Throw("Can't find final quote."); } // end of string var lit = _expr.Substring(_ptr + 1, i - 1); - _ptr += i + 1; + _ptr += i + 1; _token = new Token(lit.Replace("\"\"", "\""), TKID.ATOM, TKTYPE.LITERAL); return; } // parse dates (review) - if (c == '#') - { + if (c == '#') { // look for end # - for (i = 1; i + _ptr < _len; i++) - { + for (i = 1; i + _ptr < _len; i++) { c = _expr[_ptr + i]; if (c == '#') break; } // check that we got the end of the date - if (c != '#') - { + if (c != '#') { Throw("Can't find final date delimiter ('#')."); } // end of date var lit = _expr.Substring(_ptr + 1, i - 1); - _ptr += i + 1; + _ptr += i + 1; _token = new Token(DateTime.Parse(lit, _ci), TKID.ATOM, TKTYPE.LITERAL); return; - } - - // identifiers (functions, objects) must start with alpha or underscore - if (!isLetter && c != '_' && (IdentifierChars == null || IdentifierChars.IndexOf(c) < 0)) - { - Throw("Identifier expected."); - } - - // and must contain only letters/digits/_idChars - for (i = 1; i + _ptr < _len; i++) - { - c = _expr[_ptr + i]; - isLetter = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); - isDigit = c >= '0' && c <= '9'; - if (!isLetter && !isDigit && c != '_' && (IdentifierChars == null || IdentifierChars.IndexOf(c) < 0)) - { - break; - } - } - - // got identifier - var id = _expr.Substring(_ptr, i); - _ptr += i; + } + + // identifiers (functions, objects) must start with alpha or underscore + if (!isLetter && c != '_' && (_idChars == null || _idChars.IndexOf(c) < 0)) { + Throw("Identifier expected."); + } + + // and must contain only letters/digits/_idChars + for (i = 1; i + _ptr < _len; i++) { + c = _expr[_ptr + i]; + isLetter = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + isDigit = c >= '0' && c <= '9'; + if (!isLetter && !isDigit && c != '_' && (_idChars == null || _idChars.IndexOf(c) < 0)) { + break; + } + } + + // got identifier + var id = _expr.Substring(_ptr, i); + _ptr += i; _token = new Token(id, TKID.ATOM, TKTYPE.IDENTIFIER); - } - - private static double ParseDouble(string str, CultureInfo ci) - { - if (str.Length > 0 && str[str.Length - 1] == ci.NumberFormat.PercentSymbol[0]) - { - str = str.Substring(0, str.Length - 1); - return double.Parse(str, NumberStyles.Any, ci)/100.0; - } - return double.Parse(str, NumberStyles.Any, ci); - } - - private List GetParameters() // e.g. myfun(a, b, c+2) + } + static double ParseDouble(string str, CultureInfo ci) { + if (str.Length > 0 && str[str.Length - 1] == ci.NumberFormat.PercentSymbol[0]) { + str = str.Substring(0, str.Length - 1); + return double.Parse(str, NumberStyles.Any, ci) / 100.0; + } + return double.Parse(str, NumberStyles.Any, ci); + } + List GetParameters() // e.g. myfun(a, b, c+2) { // check whether next token is a (, // restore state and bail if it's not var pos = _ptr; var tk = _token; GetToken(); - if (_token.ID != TKID.OPEN) - { - _ptr = pos; + if (_token.ID != TKID.OPEN) { + _ptr = pos; _token = tk; return null; } // check for empty Parameter list pos = _ptr; - GetToken(); - if (_token.ID == TKID.CLOSE) - { - return null; + GetToken(); + if (_token.ID == TKID.CLOSE) { + return null; } _ptr = pos; @@ -717,71 +636,59 @@ var parms = new List(); var expr = ParseExpression(); parms.Add(expr); - while (_token.ID == TKID.COMMA) - { + while (_token.ID == TKID.COMMA) { expr = ParseExpression(); parms.Add(expr); } // make sure the list was closed correctly - if (_token.ID != TKID.CLOSE) - { + if (_token.ID != TKID.CLOSE) { Throw(); } // done return parms; + } + Token GetMember() { + // check whether next token is a MEMBER token ('.'), + // restore state and bail if it's not + var pos = _ptr; + var tk = _token; + GetToken(); + if (_token.ID != TKID.PERIOD) { + _ptr = pos; + _token = tk; + return null; + } + + // skip member token + GetToken(); + if (_token.Type != TKTYPE.IDENTIFIER) { + Throw("Identifier expected"); + } + return _token; } - private Token GetMember() - { - // check whether next token is a MEMBER token ('.'), - // restore state and bail if it's not - var pos = _ptr; - var tk = _token; - GetToken(); - if (_token.ID != TKID.PERIOD) - { - _ptr = pos; - _token = tk; - return null; - } - - // skip member token - GetToken(); - if (_token.Type != TKTYPE.IDENTIFIER) - { - Throw("Identifier expected"); - } - return _token; - } - - #endregion + #endregion //--------------------------------------------------------------------------- + #region ** static helpers - #region ** static helpers - - private static void Throw() - { - Throw("Syntax error."); + static void Throw() { + Throw("Syntax error."); + } + static void Throw(string msg) { + throw new Exception(msg); } - private static void Throw(string msg) - { - throw new Exception(msg); - } - - #endregion - } - - /// - /// Delegate that represents CalcEngine functions. - /// - /// - /// List of objects that represent the - /// parameters to be used in the function call. - /// - /// The function result. - internal delegate object CalcEngineFunction(List parms); -} \ No newline at end of file + #endregion + } + + /// + /// Delegate that represents CalcEngine functions. + /// + /// List of objects that represent the + /// parameters to be used in the function call. + /// The function result. + internal delegate object CalcEngineFunction(List parms); +}