/* This file is part of the KDE project Copyright (C) 2003,2004 Ariya Hidayat Copyright (C) 2005 Tomas Mecir This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "formula.h" #include "kspread_cell.h" #include "kspread_sheet.h" #include "kspread_doc.h" #include "kspread_util.h" #include "kspread_value.h" #include "valuecalc.h" #include "valueconverter.h" #include "valueparser.h" #include "functions.h" #include #include #include #include #include #include /* To understand how this formula engine works, please refer to the documentation in file DESIGN.html. Useful references: - "Principles of Compiler Design", A.V.Aho, J.D.Ullman, Addison Wesley, 1978 - "Writing Interactive Compilers and Interpreters", P.J. Brown, John Wiley and Sons, 1979. - "The Theory and Practice of Compiler Writing", J.Tremblay, P.G.Sorenson, McGraw-Hill, 1985. - "The Java(TM) Virtual Machine Specification", T.Lindholm, F.Yellin, Addison-Wesley, 1997. - "Java Virtual Machine", J.Meyer, T.Downing, O'Reilly, 1997. */ /* TODO - features: - handle Intersection - cell reference is made relative (absolute now) - shared formula (different owner, same data) - relative internal representation (independent of owner) - OASIS support TODO - optimizations: - handle initial formula marker = (and +) - reuse constant already in the pool - reuse references already in the pool - expression optimization (e.g. 1+2+A1 becomes 3+A1) */ namespace KSpread { class Opcode { public: enum { Nop = 0, Load, Ref, Cell, Range, Function, Add, Sub, Neg, Mul, Div, Pow, Concat, Not, Equal, Less, Greater }; unsigned type; unsigned index; Opcode(): type(Nop), index(0) {}; Opcode( unsigned t ): type(t), index(0) {}; Opcode( unsigned t, unsigned i ): type(t), index(i) {}; }; class Formula::Private { public: Formula *formula; Cell *cell; Sheet *sheet; bool dirty; bool valid; TQString expression; TQValueVector codes; TQValueVector constants; }; class TokenStack : public TQValueVector { public: TokenStack(); bool isEmpty() const; unsigned itemCount() const; void push( const Token& token ); Token pop(); const Token& top(); const Token& top( unsigned index ); private: void ensureSpace(); unsigned topIndex; }; } using namespace KSpread; // for null token const Token Token::null; // helper function: return operator of given token text // e.g. "*" yields Operator::Asterisk, and so on Token::Op KSpread::matchOperator( const TQString& text ) { Token::Op result = Token::InvalidOp; if( text.length() == 1 ) { TQChar p = text[0]; switch( p.tqunicode() ) { case '+': result = Token::Plus; break; case '-': result = Token::Minus; break; case '*': result = Token::Asterisk; break; case '/': result = Token::Slash; break; case '^': result = Token::Caret; break; case ',': result = Token::Comma; break; case ';': result = Token::Semicolon; break; case '(': result = Token::LeftPar; break; case ')': result = Token::RightPar; break; case '&': result = Token::Ampersand; break; case '=': result = Token::Equal; break; case '<': result = Token::Less; break; case '>': result = Token::Greater; break; case '%': result = Token::Percent; break; default : result = Token::InvalidOp; break; } } if( text.length() == 2 ) { if( text == "<>" ) result = Token::NotEqual; if( text == "<=" ) result = Token::LessEqual; if( text == ">=" ) result = Token::GreaterEqual; if( text == "==" ) result = Token::Equal; } return result; } // helper function: give operator precedence // e.g. "+" is 1 while "*" is 3 static int opPrecedence( Token::Op op ) { int prec = -1; switch( op ) { case Token::Percent : prec = 8; break; case Token::Caret : prec = 7; break; case Token::Asterisk : prec = 5; break; case Token::Slash : prec = 6; break; case Token::Plus : prec = 3; break; case Token::Minus : prec = 3; break; case Token::Ampersand : prec = 2; break; case Token::Equal : prec = 1; break; case Token::NotEqual : prec = 1; break; case Token::Less : prec = 1; break; case Token::Greater : prec = 1; break; case Token::LessEqual : prec = 1; break; case Token::GreaterEqual : prec = 1; break; case Token::Semicolon : prec = 0; break; case Token::RightPar : prec = 0; break; case Token::LeftPar : prec = -1; break; default: prec = -1; break; } return prec; } // helper function static Value tokenAsValue( const Token& token ) { Value value; if( token.isBoolean() ) value = Value( token.asBoolean() ); else if( token.isInteger() ) value = Value( token.asInteger() ); else if( token.isFloat() ) value = Value( token.asFloat() ); else if( token.isString() ) value = Value( token.asString() ); return value; } /********************** Token **********************/ // creates a token Token::Token( Type type, const TQString& text, int pos ) { m_type = type; m_text = text; m_pos = pos; } // copy constructor Token::Token( const Token& token ) { m_type = token.m_type; m_text = token.m_text; m_pos = token.m_pos; } // assignment operator Token& Token::operator=( const Token& token ) { m_type = token.m_type; m_text = token.m_text; m_pos = token.m_pos; return *this; } bool Token::asBoolean() const { if( !isBoolean() ) return false; return m_text.lower() == "true"; // FIXME check also for i18n version } long Token::asInteger() const { if( isInteger() ) return m_text.toLong(); else return 0; } double Token::asFloat() const { if( isFloat() ) return m_text.toDouble(); else return 0.0; } TQString Token::asString() const { if( isString() ) return m_text.mid( 1, m_text.length()-2 ); else return TQString(); } Token::Op Token::asOperator() const { if( isOperator() ) return matchOperator( m_text ); else return InvalidOp; } TQString Token::sheetName() const { if( !isCell() && !isRange() ) return TQString(); int i = m_text.find( '!' ); if( i < 0 ) return TQString(); TQString sheet = m_text.left( i ); return sheet; } TQString Token::description() const { TQString desc; switch (m_type ) { case Boolean: desc = "Boolean"; break; case Integer: desc = "Integer"; break; case Float: desc = "Float"; break; case String: desc = "String"; break; case Identifier: desc = "Identifier"; break; case Cell: desc = "Cell"; break; case Range: desc = "Range"; break; case Operator: desc = "Operator"; break; default: desc = "Unknown"; break; } while( desc.length() < 10 ) desc.prepend( ' ' ); desc.prepend( " " ); desc.prepend( TQString::number( m_pos ) ); desc.append( " : " ).append( m_text ); return desc; } /********************** TokenStack **********************/ TokenStack::TokenStack(): TQValueVector() { topIndex = 0; ensureSpace(); } bool TokenStack::isEmpty() const { return topIndex == 0; } unsigned TokenStack::itemCount() const { return topIndex; } void TokenStack::push( const Token& token ) { ensureSpace(); at( topIndex++ ) = token; } Token TokenStack::pop() { return (topIndex > 0 ) ? Token( at( --topIndex ) ) : Token(); } const Token& TokenStack::top() { return top( 0 ); } const Token& TokenStack::top( unsigned index ) { if( topIndex > index ) return at( topIndex-index-1 ); return Token::null; } void TokenStack::ensureSpace() { while( topIndex >= size() ) resize( size() + 10 ); } /********************** FormulaPrivate **********************/ // helper function: return true for valid identifier character bool KSpread::isIdentifier( TQChar ch ) { return ( ch.tqunicode() == '_' ) || (ch.tqunicode() == '$' ) || ( ch.isLetter() ); } /********************** Formula **********************/ // Constructor Formula::Formula (Sheet *sheet, Cell *cell) { d = new Private; d->cell = cell; d->sheet = sheet; clear(); } Formula::Formula() { d = new Private; d->cell = 0; d->sheet = 0; clear(); } // Destructor Formula::~Formula() { delete d; } Cell* Formula::cell() const { return d->cell; } Sheet* Formula::sheet() const { return d->sheet; } // Sets a new expression for this formula. // note that both the real lex and parse processes will happen later on // when needed (i.e. "lazy parse"), for example during formula evaluation. void Formula::setExpression( const TQString& expr ) { d->expression = expr; d->dirty = true; d->valid = false; } // Returns the expression associated with this formula. TQString Formula::expression() const { return d->expression; } // Returns the validity of the formula. // note: empty formula is always invalid. bool Formula::isValid() const { if( d->dirty ) { KLocale* locale = d->cell ? d->cell->locale() : 0; if ((!locale) && d->sheet) locale = d->sheet->doc()->locale(); Tokens tokens = scan( d->expression, locale ); if( tokens.valid() ) compile( tokens ); else d->valid = false; } return d->valid; } // Clears everything, also mark the formula as invalid. void Formula::clear() { d->expression = TQString(); d->dirty = true; d->valid = false; d->constants.clear(); d->codes.clear(); } // Returns list of token for the expression. // this triggers again the lexical analysis step. it is however preferable // (even when there's small performance penalty) because otherwise we need to // store parsed tokens all the time which serves no good purpose. Tokens Formula::tokens() const { KLocale* locale = d->cell ? d->cell->locale() : 0; if ((!locale) && d->sheet) locale = d->sheet->doc()->locale(); return scan( d->expression, locale ); } Tokens Formula::scan( const TQString& expr, KLocale* locale ) const { // to hold the result Tokens tokens; // parsing state enum { Start, Finish, Bad, InNumber, InDecimal, InExpIndicator, InExponent, InString, InIdentifier, InCell, InRange, InSheetOrAreaName } state; // use locale settings if specified TQString thousand = locale ? locale->thousandsSeparator() : ""; TQString decimal = locale ? locale->decimalSymbol() : "."; // initialize variables state = Start; unsigned int i = 0; TQString ex = expr; TQString tokenText; int tokenStart = 0; // first character must be equal sign (=) if( ex[0] != '=' ) return tokens; // but the scanner should not see this equal sign ex.remove( 0, 1 ); // force a terminator ex.append( TQChar() ); // main loop while( (state != Bad) && (state != Finish) && (i < ex.length()) ) { TQChar ch = ex[i]; switch( state ) { case Start: tokenStart = i; // skip any whitespaces if( ch.isSpace() ) i++; // check for number else if( ch.isDigit() ) { state = InNumber; } // a string ? else if ( ch == '"' ) { tokenText.append( ex[i++] ); state = InString; } // beginning with alphanumeric ? // could be identifier, cell, range, or function... else if( isIdentifier( ch ) ) { state = InIdentifier; } // aposthrophe (') marks sheet name for 3-d cell, e.g 'Sales Q3'!A4, or a named range else if ( ch.tqunicode() == '\'' ) { i++; state = InSheetOrAreaName; } // decimal dot ? else if ( ch == decimal ) { tokenText.append( ex[i++] ); state = InDecimal; } // terminator character else if ( ch == TQChar::null ) state = Finish; // look for operator match else { int op; TQString s; // check for two-chars operator, such as '<=', '>=', etc s.append( ch ).append( ex[i+1] ); op = matchOperator( s ); // check for one-char operator, such as '+', ';', etc if( op == Token::InvalidOp ) { s = TQString( ch ); op = matchOperator( s ); } // any matched operator ? if( op != Token::InvalidOp ) { int len = s.length(); i += len; tokens.append( Token( Token::Operator, s.left( len ), tokenStart ) ); } else state = Bad; } break; case InIdentifier: // consume as long as alpha, dollar sign, underscore, or digit if( isIdentifier( ch ) || ch.isDigit() ) tokenText.append( ex[i++] ); // a '!' ? then this must be sheet name, e.g "Sheet4!" else if( ch == '!' ) { tokenText.append( ex[i++] ); state = InCell; } // a '(' ? then this must be a function identifier else if( ch == '(' ) { tokens.append (Token (Token::Identifier, tokenText, tokenStart)); tokenStart = i; tokenText = ""; state = Start; } // we're done with identifier else { // check for cell reference, e.g A1, VV123, ... TQRegExp exp("(\\$?)([a-zA-Z]+)(\\$?)([0-9]+)$"); int n = exp.search( tokenText ); if( n >= 0 ) state = InCell; else { if ( isNamedArea( tokenText ) ) tokens.append (Token (Token::Range, tokenText, tokenStart)); else tokens.append (Token (Token::Identifier, tokenText, tokenStart)); tokenStart = i; tokenText = ""; state = Start; } } break; case InCell: // consume as long as alpha, dollar sign, underscore, or digit if( isIdentifier( ch ) || ch.isDigit() ) tokenText.append( ex[i++] ); // we're done with cell ref, possibly with sheet name (like "Sheet2!B2") // note that "Sheet2!TotalSales" is also possible, in which "TotalSales" is a named area else { // check if it's a cell ref like A32, not named area TQString cell; for( int j = tokenText.length()-1; j>=0; j-- ) if( tokenText[j] == '!' ) break; else cell.prepend( tokenText[j] ); TQRegExp exp("(\\$?)([a-zA-Z]+)(\\$?)([0-9]+)$"); if( exp.search( cell ) != 0 ) { // we're done with named area // (Tomas) huh? this doesn't seem to check for named areas ... tokens.append( Token( Token::Range, tokenText, tokenStart ) ); tokenText = ""; state = Start; } else { // so up to now we've got something like A2 or Sheet2!F4 // check for range reference if( ch == ':' ) { tokenText.append( ex[i++] ); state = InRange; } else { // we're done with cell reference tokens.append( Token( Token::Cell, tokenText, tokenStart ) ); tokenText = ""; state = Start; } } } break; case InRange: // consume as long as alpha, dollar sign, underscore, or digit if( isIdentifier( ch ) || ch.isDigit() ) tokenText.append( ex[i++] ); // we're done with range reference else { tokens.append( Token( Token::Range, tokenText, tokenStart ) ); tokenText = ""; state = Start; } break; case InSheetOrAreaName: // consume until ' if ( ch.tqunicode() != '\'' ) tokenText.append( ex[i++] ); else { // eat the aposthrophe itself ++i; // must be followed by '!' to be sheet name if( ex[i] == '!' ) { tokenText.append( ex[i++] ); state = InCell; } else { if ( isNamedArea( tokenText ) ) tokens.append (Token (Token::Range, tokenText, tokenStart)); else tokens.append (Token (Token::Identifier, tokenText, tokenStart)); tokenStart = i; tokenText = ""; state = Start; } } break; case InNumber: // consume as long as it's digit if( ch.isDigit() ) tokenText.append( ex[i++] ); // skip thousand separator else if( !thousand.isEmpty() && ( ch ==thousand[0] ) ) i++; // convert decimal separator to '.', also support '.' directly // we always support '.' because of bug #98455 else if(( !decimal.isEmpty() && ( ch == decimal[0] ) ) || (ch == '.')) { tokenText.append( '.' ); i++; state = InDecimal; } // exponent ? else if( ch.upper() == 'E' ) { tokenText.append( 'E' ); i++; state = InExpIndicator; } // reference sheet delimiter? else if( ch == '!' ) { tokenText.append( ex[i++] ); state = InCell; } // identifier? else if( isIdentifier( ch ) ) { // has to be a sheet or area name then state = InIdentifier; } // we're done with integer number else { tokens.append( Token( Token::Integer, tokenText, tokenStart ) ); tokenText = ""; state = Start; }; break; case InDecimal: // consume as long as it's digit if( ch.isDigit() ) tokenText.append( ex[i++] ); // exponent ? else if( ch.upper() == 'E' ) { tokenText.append( 'E' ); i++; state = InExpIndicator; } // we're done with floating-point number else { tokens.append( Token( Token::Float, tokenText, tokenStart ) ); tokenText = ""; state = Start; }; break; case InExpIndicator: // possible + or - right after E, e.g 1.23E+12 or 4.67E-8 if( ( ch == '+' ) || ( ch == '-' ) ) tokenText.append( ex[i++] ); // consume as long as it's digit else if( ch.isDigit() ) state = InExponent; // invalid thing here else state = Bad; break; case InExponent: // consume as long as it's digit if( ch.isDigit() ) tokenText.append( ex[i++] ); // we're done with floating-point number else { tokens.append( Token( Token::Float, tokenText, tokenStart ) ); tokenText = ""; state = Start; }; break; case InString: // consume until " if( ch != '"' ) tokenText.append( ex[i++] ); else { tokenText.append( ch ); i++; tokens.append( Token( Token::String, tokenText, tokenStart ) ); tokenText = ""; state = Start; } break; case Bad: default: break; }; }; if( state == Bad ) tokens.setValid( false ); return tokens; } // will affect: dirty, valid, codes, constants void Formula::compile( const Tokens& tokens ) const { // initialize variables d->dirty = false; d->valid = false; d->codes.clear(); d->constants.clear(); // sanity check if( tokens.count() == 0 ) return; TokenStack syntaxStack; TQValueStack argStack; unsigned argCount = 1; for( unsigned i = 0; i <= tokens.count(); i++ ) { // helper token: InvalidOp is end-of-formula Token token = ( i < tokens.count() ) ? tokens[i] : Token( Token::Operator ); Token::Type tokenType = token.type(); // unknown token is invalid if( tokenType == Token::Unknown ) break; // for constants, push immediately to stack // generate code to load from a constant if ( ( tokenType == Token::Integer ) || ( tokenType == Token::Float ) || ( tokenType == Token::String ) || ( tokenType == Token::Boolean ) ) { syntaxStack.push( token ); d->constants.append( tokenAsValue( token ) ); d->codes.append( Opcode( Opcode::Load, d->constants.count()-1 ) ); } // for cell, range, or identifier, push immediately to stack // generate code to load from reference if( ( tokenType == Token::Cell ) || ( tokenType == Token::Range ) || ( tokenType == Token::Identifier ) ) { syntaxStack.push( token ); d->constants.append( Value( token.text() ) ); if (tokenType == Token::Cell) d->codes.append( Opcode( Opcode::Cell, d->constants.count()-1 ) ); else if (tokenType == Token::Range) d->codes.append( Opcode( Opcode::Range, d->constants.count()-1 ) ); else d->codes.append( Opcode( Opcode::Ref, d->constants.count()-1 ) ); } // are we entering a function ? // if token is operator, and stack already has: id ( arg if( tokenType == Token::Operator ) if( syntaxStack.itemCount() >= 3 ) { Token arg = syntaxStack.top(); Token par = syntaxStack.top( 1 ); Token id = syntaxStack.top( 2 ); if( !arg.isOperator() ) if( par.asOperator() == Token::LeftPar ) if( id.isIdentifier() ) { argStack.push( argCount ); argCount = 1; } } // special case for percentage if( tokenType == Token::Operator ) if( token.asOperator() == Token::Percent ) if( syntaxStack.itemCount() >= 1 ) if( !syntaxStack.top().isOperator() ) { d->constants.append( Value( 0.01 ) ); d->codes.append( Opcode( Opcode::Load, d->constants.count()-1 ) ); d->codes.append( Opcode( Opcode::Mul ) ); } // for any other operator, try to apply all parsing rules if( tokenType == Token::Operator ) if( token.asOperator() != Token::Percent ) { // repeat until no more rule applies for( ; ; ) { bool ruleFound = false; // rule for function arguments, if token is ; or ) // id ( arg1 ; arg2 -> id ( arg if( !ruleFound ) if( syntaxStack.itemCount() >= 5 ) if( ( token.asOperator() == Token::RightPar ) || ( token.asOperator() == Token::Semicolon ) ) { Token arg2 = syntaxStack.top(); Token sep = syntaxStack.top( 1 ); Token arg1 = syntaxStack.top( 2 ); Token par = syntaxStack.top( 3 ); Token id = syntaxStack.top( 4 ); if( !arg2.isOperator() ) if( sep.asOperator() == Token::Semicolon ) if( !arg1.isOperator() ) if( par.asOperator() == Token::LeftPar ) if( id.isIdentifier() ) { ruleFound = true; syntaxStack.pop(); syntaxStack.pop(); argCount++; } } // rule for function last argument: // id ( arg ) -> arg if( !ruleFound ) if( syntaxStack.itemCount() >= 4 ) { Token par2 = syntaxStack.top(); Token arg = syntaxStack.top( 1 ); Token par1 = syntaxStack.top( 2 ); Token id = syntaxStack.top( 3 ); if( par2.asOperator() == Token::RightPar ) if( !arg.isOperator() ) if( par1.asOperator() == Token::LeftPar ) if( id.isIdentifier() ) { ruleFound = true; syntaxStack.pop(); syntaxStack.pop(); syntaxStack.pop(); syntaxStack.pop(); syntaxStack.push( arg ); d->codes.append( Opcode( Opcode::Function, argCount ) ); argCount = argStack.empty() ? 0 : argStack.pop(); } } // rule for function call with parentheses, but without argument // e.g. "2*PI()" if( !ruleFound ) if( syntaxStack.itemCount() >= 3 ) { Token par2 = syntaxStack.top(); Token par1 = syntaxStack.top( 1 ); Token id = syntaxStack.top( 2 ); if( par2.asOperator() == Token::RightPar ) if( par1.asOperator() == Token::LeftPar ) if( id.isIdentifier() ) { ruleFound = true; syntaxStack.pop(); syntaxStack.pop(); syntaxStack.pop(); syntaxStack.push( Token( Token::Integer ) ); d->codes.append( Opcode( Opcode::Function, 0 ) ); } } // rule for parenthesis: ( Y ) -> Y if( !ruleFound ) if( syntaxStack.itemCount() >= 3 ) { Token right = syntaxStack.top(); Token y = syntaxStack.top( 1 ); Token left = syntaxStack.top( 2 ); if( right.isOperator() ) if( !y.isOperator() ) if( left.isOperator() ) if( right.asOperator() == Token::RightPar ) if( left.asOperator() == Token::LeftPar ) { ruleFound = true; syntaxStack.pop(); syntaxStack.pop(); syntaxStack.pop(); syntaxStack.push( y ); } } // rule for binary operator: A (op) B -> A // conditions: precedence of op >= precedence of token // action: push (op) to result // e.g. "A * B" becomes "A" if token is operator "+" // exception: for caret (power operator), if op is another caret // then the rule doesn't apply, e.g. "2^3^2" is evaluated as "2^(3^2)" if( !ruleFound ) if( syntaxStack.itemCount() >= 3 ) { Token b = syntaxStack.top(); Token op = syntaxStack.top( 1 ); Token a = syntaxStack.top( 2 ); if( !a.isOperator() ) if( !b.isOperator() ) if( op.isOperator() ) if( token.asOperator() != Token::LeftPar ) if( token.asOperator() != Token::Caret ) if( opPrecedence( op.asOperator() ) >= opPrecedence( token.asOperator() ) ) { ruleFound = true; syntaxStack.pop(); syntaxStack.pop(); syntaxStack.pop(); syntaxStack.push( b ); switch( op.asOperator() ) { // simple binary operations case Token::Plus: d->codes.append( Opcode::Add ); break; case Token::Minus: d->codes.append( Opcode::Sub ); break; case Token::Asterisk: d->codes.append( Opcode::Mul ); break; case Token::Slash: d->codes.append( Opcode::Div ); break; case Token::Caret: d->codes.append( Opcode::Pow ); break; case Token::Ampersand: d->codes.append( Opcode::Concat ); break; // simple value comparisons case Token::Equal: d->codes.append( Opcode::Equal ); break; case Token::Less: d->codes.append( Opcode::Less ); break; case Token::Greater: d->codes.append( Opcode::Greater ); break; // NotEqual is Equal, followed by Not case Token::NotEqual: d->codes.append( Opcode::Equal ); d->codes.append( Opcode::Not ); break; // LessOrEqual is Greater, followed by Not case Token::LessEqual: d->codes.append( Opcode::Greater ); d->codes.append( Opcode::Not ); break; // GreaterOrEqual is Less, followed by Not case Token::GreaterEqual: d->codes.append( Opcode::Less ); d->codes.append( Opcode::Not ); break; default: break; }; } } // rule for unary operator: (op1) (op2) X -> (op1) X // conditions: op2 is unary, token is not '(' // action: push (op2) to result // e.g. "* - 2" becomes "*" if( !ruleFound ) if( token.asOperator() != Token::LeftPar ) if( syntaxStack.itemCount() >= 3 ) { Token x = syntaxStack.top(); Token op2 = syntaxStack.top( 1 ); Token op1 = syntaxStack.top( 2 ); if( !x.isOperator() ) if( op1.isOperator() ) if( op2.isOperator() ) if( ( op2.asOperator() == Token::Plus ) || ( op2.asOperator() == Token::Minus ) ) { ruleFound = true; syntaxStack.pop(); syntaxStack.pop(); syntaxStack.push( x ); if( op2.asOperator() == Token::Minus ) d->codes.append( Opcode( Opcode::Neg ) ); } } // auxilary rule for unary operator: (op) X -> X // conditions: op is unary, op is first in syntax stack, token is not '(' // action: push (op) to result if( !ruleFound ) if( token.asOperator() != Token::LeftPar ) if( syntaxStack.itemCount() == 2 ) { Token x = syntaxStack.top(); Token op = syntaxStack.top( 1 ); if( !x.isOperator() ) if( op.isOperator() ) if( ( op.asOperator() == Token::Plus ) || ( op.asOperator() == Token::Minus ) ) { ruleFound = true; syntaxStack.pop(); syntaxStack.pop(); syntaxStack.push( x ); if( op.asOperator() == Token::Minus ) d->codes.append( Opcode( Opcode::Neg ) ); } } if( !ruleFound ) break; } // can't apply rules anymore, push the token if( token.asOperator() != Token::Percent ) syntaxStack.push( token ); } } // syntaxStack must left only one operand and end-of-formula (i.e. InvalidOp) d->valid = false; if( syntaxStack.itemCount() == 2 ) if( syntaxStack.top().isOperator() ) if( syntaxStack.top().asOperator() == Token::InvalidOp ) if( !syntaxStack.top(1).isOperator() ) d->valid = true; // bad parsing ? clean-up everything if( !d->valid ) { d->constants.clear(); d->codes.clear(); } } bool Formula::isNamedArea( const TQString& expr ) const { TQString tokenText( expr ); // check for named areas ... if (d->sheet) { const TQValueList areas = d->sheet->doc()->listArea(); TQValueList::const_iterator it; for (it = areas.begin(); it != areas.end(); ++it) { if ((*it).ref_name.lower() == tokenText.lower()) { // we got a named area return true; } } } return false; } // Evaluates the formula, returns the result. struct stackEntry { void reset () { row1 = col1 = row2 = col2 = -1; }; Value val; int row1, col1, row2, col2; }; Value Formula::eval() const { TQValueStack stack; stackEntry entry; unsigned index; Value val1, val2; TQString c; TQValueVector args; Sheet *sheet = 0; ValueParser* parser = 0; ValueConverter* converter = 0; ValueCalc* calc = 0; if (d->sheet) { sheet = d->sheet; converter = sheet->doc()->converter(); calc = sheet->doc()->calc(); } else { parser = new ValueParser( KGlobal::locale() ); converter = new ValueConverter( parser ); calc = new ValueCalc( converter ); } Function* function; FuncExtra fe; fe.mycol = fe.myrow = 0; if (d->cell) { fe.mycol = d->cell->column(); fe.myrow = d->cell->row(); } if( d->dirty ) { Tokens tokens = scan( d->expression ); d->valid = tokens.valid(); if( tokens.valid() ) compile( tokens ); } if( !d->valid ) return Value::errorVALUE(); for( unsigned pc = 0; pc < d->codes.count(); pc++ ) { Value ret; // for the function caller Opcode& opcode = d->codes[pc]; index = opcode.index; switch( opcode.type ) { // no operation case Opcode::Nop: break; // load a constant, push to stack case Opcode::Load: entry.reset(); entry.val = d->constants[index]; stack.push (entry); break; // unary operation case Opcode::Neg: entry.reset(); entry.val = stack.pop().val; if (!entry.val.isError()) // do nothing if we got an error entry.val = calc->mul (entry.val, -1); stack.push (entry); break; // binary operation: take two values from stack, do the operation, // push the result to stack case Opcode::Add: entry.reset(); val2 = stack.pop().val; val1 = stack.pop().val; val2 = calc->add( val1, val2 ); entry.reset(); entry.val = val2; stack.push (entry); break; case Opcode::Sub: val2 = stack.pop().val; val1 = stack.pop().val; val2 = calc->sub( val1, val2 ); entry.reset(); entry.val = val2; stack.push (entry); break; case Opcode::Mul: val2 = stack.pop().val; val1 = stack.pop().val; val2 = calc->mul( val1, val2 ); entry.reset(); entry.val = val2; stack.push (entry); break; case Opcode::Div: val2 = stack.pop().val; val1 = stack.pop().val; val2 = calc->div( val1, val2 ); entry.reset(); entry.val = val2; stack.push (entry); break; case Opcode::Pow: val2 = stack.pop().val; val1 = stack.pop().val; val2 = calc->pow( val1, val2 ); entry.reset(); entry.val = val2; stack.push (entry); break; // string concatenation case Opcode::Concat: val1 = converter->asString (stack.pop().val); val2 = converter->asString (stack.pop().val); if (val1.isError() || val2.isError()) val1 = Value::errorVALUE(); else val1.setValue( val2.asString().append( val1.asString() ) ); entry.reset(); entry.val = val1; stack.push (entry); break; // logical not case Opcode::Not: val1 = converter->asBoolean (stack.pop().val); if( val1.isError() ) val1 = Value::errorVALUE(); else val1.setValue( !val1.asBoolean() ); entry.reset(); entry.val = val1; stack.push (entry); break; // comparison case Opcode::Equal: val1 = stack.pop().val; val2 = stack.pop().val; if( !val1.allowComparison( val2 ) ) val1 = Value::errorNA(); else if( val2.compare( val1 ) == 0 ) val1 = Value (true); else val1 = Value (false); entry.reset(); entry.val = val1; stack.push (entry); break; // less than case Opcode::Less: val1 = stack.pop().val; val2 = stack.pop().val; if( !val1.allowComparison( val2 ) ) val1 = Value::errorNA(); else if( val2.compare( val1 ) < 0 ) val1 = Value (true); else val1 = Value (false); entry.reset(); entry.val = val1; stack.push (entry); break; // greater than case Opcode::Greater: val1 = stack.pop().val; val2 = stack.pop().val; if( !val1.allowComparison( val2 ) ) val1 = Value::errorNA(); else if( val2.compare( val1 ) > 0 ) val1 = Value (true); else val1 = Value (false); entry.reset(); entry.val = val1; stack.push (entry); break; case Opcode::Cell: c = d->constants[index].asString(); val1 = Value::empty(); entry.reset(); if (sheet) { Point cell (c, sheet->workbook(), sheet); if (cell.isValid()) { val1 = cell.sheet()->value (cell.column(), cell.row()); // store the reference, so we can use it within functions entry.col1 = entry.col2 = cell.column(); entry.row1 = entry.row2 = cell.row(); } } entry.val = val1; stack.push (entry); break; case Opcode::Range: c = d->constants[index].asString(); val1 = Value::empty(); entry.reset(); if (sheet) { Range range (c, sheet->workbook(), sheet); if (range.isValid()) { val1 = range.sheet()->valueRange (range.startCol(), range.startRow(), range.endCol(), range.endRow()); // store the reference, so we can use it within functions entry.col1 = range.startCol(); entry.row1 = range.startRow(); entry.col2 = range.endCol(); entry.row2 = range.endRow(); } } entry.val = val1; stack.push (entry); break; case Opcode::Ref: val1 = d->constants[index]; entry.reset(); entry.val = val1; stack.push (entry); break; // calling function case Opcode::Function: if( stack.count() < index ) // (Tomas) umm, how could that be ? I mean, the index value // is computed from the stack *confused* return Value::errorVALUE(); // not enough arguments args.clear(); fe.ranges.clear (); fe.ranges.reserve (index); fe.sheet = sheet; for( ; index; index-- ) { stackEntry e = stack.pop(); args.insert (args.begin(), e.val); // TODO: create and fill a FunctionExtra object, if needed // problem: we don't know if we need it, as we don't have the // fuction name yet ... fe.ranges[index - 1].col1 = e.col1; fe.ranges[index - 1].row1 = e.row1; fe.ranges[index - 1].col2 = e.col2; fe.ranges[index - 1].row2 = e.row2; } // function name as string value val1 = converter->asString (stack.pop().val); if( val1.isError() ) return Value::errorVALUE(); function = FunctionRepository::self()->function ( val1.asString() ); if( !function ) return Value::errorVALUE(); // no such function ret = function->exec (args, calc, &fe); entry.reset(); entry.val = ret; stack.push (entry); break; default: break; } } if (!d->sheet) { delete parser; delete converter; delete calc; } // more than one value in stack ? unsuccesful execution... if( stack.count() != 1 ) return Value::errorVALUE(); return stack.pop().val; } // Debugging aid TQString Formula::dump() const { TQString result; if( d->dirty ) { Tokens tokens = scan( d->expression ); compile( tokens ); } result = TQString("Expression: [%1]\n").arg( d->expression ); #if 0 Value value = eval(); result.append( TQString("Result: %1\n").arg( converter->asString(value).asString() ) ); #endif result.append(" Constants:\n"); for( unsigned c = 0; c < d->constants.count(); c++ ) { TQString vtext; Value val = d->constants[c]; if( val.isString() ) vtext = TQString("[%1]").arg( val.asString() ); else if( val.isNumber() ) vtext = TQString("%1").arg( val.asFloat() ); else if( val.isBoolean() ) vtext = TQString("%1").arg( val.asBoolean() ? "True":"False"); else if( val.isError() ) vtext = "error"; else vtext = "???"; result += TQString(" #%1 = %2\n").arg(c).arg( vtext ); } result.append("\n"); result.append(" Code:\n"); for( unsigned i = 0; i < d->codes.count(); i++ ) { TQString ctext; switch( d->codes[i].type ) { case Opcode::Load: ctext = TQString("Load #%1").arg( d->codes[i].index ); break; case Opcode::Ref: ctext = TQString("Ref #%1").arg( d->codes[i].index ); break; case Opcode::Function: ctext = TQString("Function (%1)").arg( d->codes[i].index ); break; case Opcode::Add: ctext = "Add"; break; case Opcode::Sub: ctext = "Sub"; break; case Opcode::Mul: ctext = "Mul"; break; case Opcode::Div: ctext = "Div"; break; case Opcode::Neg: ctext = "Neg"; break; case Opcode::Concat: ctext = "Concat"; break; case Opcode::Pow: ctext = "Pow"; break; case Opcode::Equal: ctext = "Equal"; break; case Opcode::Not: ctext = "Not"; break; case Opcode::Less: ctext = "Less"; break; case Opcode::Greater: ctext = "Greater"; break; default: ctext = "Unknown"; break; } result.append( " " ).append( ctext ).append("\n"); } return result; } TQTextStream& operator<<( TQTextStream& ts, Formula formula ) { ts << formula.dump(); return ts; }