diff --git a/ClosedXML/Excel/CalcEngine/CalcEngine.cs b/ClosedXML/Excel/CalcEngine/CalcEngine.cs
index 1b18b15..9ba60b2 100644
--- a/ClosedXML/Excel/CalcEngine/CalcEngine.cs
+++ b/ClosedXML/Excel/CalcEngine/CalcEngine.cs
@@ -1,279 +1,279 @@
-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.
- ///
+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 ** fields
+ #region ** fields
// members
string _expr; // expression being parsed
int _len; // length of the expression being parsed
- int _ptr; // current pointer into expression
+ 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
- }
+ 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
+ #endregion
//---------------------------------------------------------------------------
- #region ** object model
+ #region ** ctor
- ///
- /// Parses a string into an .
- ///
- /// String to parse.
- /// An object that can be evaluated.
+ 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
+
+ //---------------------------------------------------------------------------
+ #region ** object model
+
+ ///
+ /// 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 (_optimize) {
- 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]
+ }
+ ///
+ /// 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 { 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];
+ }
}
- #endregion
+ #endregion
//---------------------------------------------------------------------------
- #region ** token/keyword tables
+ #region ** token/keyword tables
// build/get static token table
- 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;
- }
- 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;
+ }
+ return _tkTbl;
+ }
+ void AddToken(object symbol, TKID id, TKTYPE type) {
+ var token = new Token(symbol, id, type);
+ _tkTbl.Add(symbol, token);
}
- #endregion
+ // 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;
+ }
+
+ #endregion
//---------------------------------------------------------------------------
- #region ** private stuff
+ #region ** private stuff
Expression ParseExpression() {
GetToken();
@@ -297,7 +297,7 @@
var exprArg = ParseMulDiv();
x = new BinaryExpression(t, x, exprArg);
}
- return x;
+ return x;
}
Expression ParseMulDiv() {
var x = ParsePower();
@@ -307,7 +307,7 @@
var a = ParsePower();
x = new BinaryExpression(t, x, a);
}
- return x;
+ return x;
}
Expression ParsePower() {
var x = ParseUnary();
@@ -323,76 +323,76 @@
// unary plus and minus
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();
}
- Expression ParseAtom() {
- string id;
- Expression x = null;
+ Expression ParseAtom() {
+ string id;
+ Expression x = null;
FunctionDefinition fnDef = null;
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 (_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;
+
+ // 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
@@ -405,11 +405,11 @@
}
break;
- }
-
- // make sure we got something...
- if (x == null) {
- Throw();
+ }
+
+ // make sure we got something...
+ if (x == null) {
+ Throw();
}
// done
@@ -417,10 +417,10 @@
return x;
}
- #endregion
+ #endregion
//---------------------------------------------------------------------------
- #region ** parser
+ #region ** parser
void GetToken() {
// eat white space
@@ -429,7 +429,7 @@
}
// are we done?
- if (_ptr >= _len) {
+ if (_ptr >= _len) {
_token = new Token(null, TKID.END, TKTYPE.GROUP);
return;
}
@@ -441,29 +441,29 @@
// 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 this is a number starting with a decimal, don't parse as operator
- var nxt = _ptr + 1 < _len ? _expr[_ptr + 1] : 0;
+ 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;
+ 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;
+ 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 (_ptr < _len && (c == '>' || c == '<')) {
if (_tkTbl.TryGetValue(_expr.Substring(_ptr - 1, 2), out tk)) {
_token = tk;
_ptr++;
@@ -478,20 +478,20 @@
// 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;
+ 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;
+ 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
@@ -506,38 +506,38 @@
c = _expr[_ptr + i + 1];
if (c == '+' || c == '-') i++;
continue;
- }
-
- // percentage?
- if (c == _percent) {
- pct = true;
- i++;
- break;
+ }
+
+ // 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;
}
@@ -559,7 +559,7 @@
// 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;
}
@@ -579,38 +579,38 @@
// 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 != '_' && (_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;
+ }
+
+ // 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);
- }
- 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);
- }
+ }
+ 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 (,
@@ -618,17 +618,17 @@
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;
@@ -642,53 +642,53 @@
}
// 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;
+ }
+ 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
- static void Throw() {
- Throw("Syntax error.");
- }
- static void Throw(string msg) {
- throw new Exception(msg);
+ static void Throw() {
+ Throw("Syntax error.");
+ }
+ 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);
-}
+ #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);
+}
diff --git a/ClosedXML_Tests/Excel/CalcEngine/TextTests.cs b/ClosedXML_Tests/Excel/CalcEngine/TextTests.cs
index e794f99..013950b 100644
--- a/ClosedXML_Tests/Excel/CalcEngine/TextTests.cs
+++ b/ClosedXML_Tests/Excel/CalcEngine/TextTests.cs
@@ -270,7 +270,7 @@
public void Proper_Value()
{
Object actual = XLWorkbook.EvaluateExpr(@"Proper(""my name is francois botha"")");
- Assert.AreEqual("My Name Information Francois Botha", actual);
+ Assert.AreEqual("My Name Is Francois Botha", actual);
}
[Test]