File indexing completed on 2024-06-23 04:27:02

0001 /* This file is part of the KDE project
0002  * SPDX-FileCopyrightText: 2007 Jan Hambrecht <jaham@gmx.net>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "EnhancedPathFormula.h"
0008 #include "EnhancedPathShape.h"
0009 #include <QStack>
0010 #include <math.h>
0011 
0012 #include <QDebug>
0013 
0014 /*
0015     The formula parsing, compiling and evaluating is based on
0016     kspreads formula engine written by Ariya Hidayat.
0017     There is a DESIGN.html file in the kspreads directory which
0018     explains how the engine is working.
0019     The engine was stripped down a little to only support the
0020     operations needed for the odf enhanced path formula spec.
0021 */
0022 
0023 // helper function: return operator of given token text
0024 FormulaToken::Operator matchOperator(const QString &text);
0025 // helper function: return true for valid identifier character
0026 bool isIdentifier(QChar ch);
0027 // helper function: give operator precedence
0028 // e.g. '+' is 1 while '*' is 3
0029 int opPrecedence(FormulaToken::Operator op);
0030 // helper function: return function of given token text
0031 EnhancedPathFormula::Function matchFunction(const QString &text);
0032 // helper function: return function name from function identifier
0033 QString matchFunction(EnhancedPathFormula::Function function);
0034 
0035 class FormulaToken;
0036 
0037 class FormulaTokenStack : public QVector<FormulaToken>
0038 {
0039 public:
0040     FormulaTokenStack()
0041         : QVector<FormulaToken>(), topIndex(0)
0042     {
0043         ensureSpace();
0044     }
0045 
0046     bool isEmpty() const
0047     {
0048         return topIndex == 0;
0049     }
0050     unsigned itemCount() const
0051     {
0052         return topIndex;
0053     }
0054     void push(const FormulaToken &token)
0055     {
0056         ensureSpace();
0057         insert(topIndex++, token);
0058     }
0059     FormulaToken pop()
0060     {
0061         return (topIndex > 0) ? FormulaToken(at(--topIndex)) : FormulaToken();
0062     }
0063     const FormulaToken &top()
0064     {
0065         return top(0);
0066     }
0067     const FormulaToken &top(unsigned index)
0068     {
0069         static FormulaToken null;
0070         if (topIndex > index) {
0071             return at(topIndex - index - 1);
0072         }
0073         return null;
0074     }
0075 private:
0076     void ensureSpace()
0077     {
0078         while ((int) topIndex >= size()) {
0079             resize(size() + 10);
0080         }
0081     }
0082     unsigned topIndex;
0083 };
0084 
0085 class Opcode
0086 {
0087 public:
0088 
0089     enum { Nop = 0, Load, Ref, Function, Add, Sub, Neg, Mul, Div };
0090 
0091     unsigned type;
0092     unsigned index;
0093 
0094     Opcode(): type(Nop), index(0) {}
0095     Opcode(unsigned t): type(t), index(0) {}
0096     Opcode(unsigned t, unsigned i): type(t), index(i) {}
0097 };
0098 
0099 EnhancedPathFormula::EnhancedPathFormula(const QString &text, EnhancedPathShape *parent)
0100     : m_valid(false)
0101     , m_compiled(false)
0102     , m_error(ErrorNone)
0103     , m_text(text)
0104     , m_parent(parent)
0105 {
0106     Q_ASSERT(m_parent);
0107 }
0108 
0109 EnhancedPathFormula::~EnhancedPathFormula()
0110 {
0111 }
0112 
0113 qreal EnhancedPathFormula::evaluate()
0114 {
0115     // shortcut
0116     if (m_error != ErrorNone) {
0117         return 0.0;
0118     }
0119 
0120     // lazy evaluation
0121     if (!m_compiled) {
0122         TokenList tokens = scan(m_text);
0123         if (!compile(tokens)) {
0124             debugOpcodes();
0125             m_error = ErrorCompile;
0126             return 0.0;
0127         }
0128         m_compiled = true;
0129     }
0130 
0131     QStack<QVariant> stack;
0132     // stack.reserve(3) here so that the stack is not resized all the time
0133     // this reduces the number of a/de/re-llocations for documents with
0134     // a lot of enhanced path shapes quite a lot.
0135     stack.reserve(3);
0136     int index = 0;
0137 
0138     if (!m_valid) {
0139         m_error = ErrorParse;
0140         return 0.0;
0141     }
0142 
0143     for (int pc = 0; pc < m_codes.count(); pc++) {
0144         QVariant ret;   // for the function caller
0145         Opcode &opcode = m_codes[pc];
0146         index = opcode.index;
0147         switch (opcode.type) {
0148         // no operation
0149         case Opcode::Nop:
0150             break;
0151 
0152         // load a constant, push to stack
0153         case Opcode::Load:
0154             stack.push(m_constants[index]);
0155             break;
0156 
0157         // unary operation
0158         case Opcode::Neg: {
0159             bool success = false;
0160             qreal value = stack.pop().toDouble(&success);
0161             if (success) { // do nothing if we got an error
0162                 value *= -1.0;
0163             }
0164             stack.push(QVariant(value));
0165             break;
0166         }
0167 
0168         // binary operation: take two values from stack, do the operation,
0169         // push the result to stack
0170         case Opcode::Add: {
0171             qreal val2 = stack.pop().toDouble();
0172             qreal val1 = stack.pop().toDouble();
0173             stack.push(QVariant(val1 + val2));
0174             break;
0175         }
0176 
0177         case Opcode::Sub: {
0178             qreal val2 = stack.pop().toDouble();
0179             qreal val1 = stack.pop().toDouble();
0180             stack.push(QVariant(val1 - val2));
0181             break;
0182         }
0183 
0184         case Opcode::Mul: {
0185             qreal val2 = stack.pop().toDouble();
0186             qreal val1 = stack.pop().toDouble();
0187             stack.push(QVariant(val1 * val2));
0188             break;
0189         }
0190 
0191         case Opcode::Div: {
0192             qreal val2 = stack.pop().toDouble();
0193             qreal val1 = stack.pop().toDouble();
0194             stack.push(QVariant(val1 / val2));
0195             break;
0196         }
0197 
0198         case Opcode::Ref: {
0199             QString reference = m_constants[index].toString();
0200             // push function name if it is a function, else push evaluated reference
0201             Function function = matchFunction(reference);
0202             if (FunctionUnknown == function) {
0203                 stack.push(QVariant(m_parent->evaluateReference(reference)));
0204             } else {
0205                 stack.push(function);
0206             }
0207             break;
0208         }
0209 
0210         // calling function
0211         case Opcode::Function: {
0212             // sanity check, this should not happen unless opcode is wrong
0213             // (i.e. there's a bug in the compile() function)
0214             if (stack.count() < index) {
0215                 qWarning() << "not enough arguments for function " << m_text;
0216                 m_error = ErrorValue; // not enough arguments
0217                 return 0.0;
0218             }
0219 
0220             /// prepare function arguments
0221             QList<qreal> args;
0222             for (; index; index--) {
0223                 qreal value = stack.pop().toDouble();
0224                 args.push_front(value);
0225             }
0226 
0227             // function identifier as int value
0228             int function = stack.pop().toInt();
0229             stack.push(QVariant(evaluateFunction((Function)function, args)));
0230             break;
0231         }
0232 
0233         default:
0234             break;
0235         }
0236     }
0237 
0238     // more than one value in stack ? unsuccessful execution...
0239     if (stack.count() != 1) {
0240         m_error = ErrorValue;
0241         return 0.0;
0242     }
0243 
0244     return stack.pop().toDouble();
0245 }
0246 
0247 qreal EnhancedPathFormula::evaluateFunction(Function function, const QList<qreal> &arguments) const
0248 {
0249     switch (function) {
0250     case EnhancedPathFormula::FunctionAbs:
0251         return fabs(arguments[0]);
0252         break;
0253     case EnhancedPathFormula::FunctionSqrt:
0254         return sqrt(arguments[0]);
0255         break;
0256     case EnhancedPathFormula::FunctionSin:
0257         return sin(arguments[0]);
0258         break;
0259     case EnhancedPathFormula::FunctionCos:
0260         return cos(arguments[0]);
0261         break;
0262     case EnhancedPathFormula::FunctionTan:
0263         return tan(arguments[0]);
0264         break;
0265     case EnhancedPathFormula::FunctionAtan:
0266         return atan(arguments[0]);
0267         break;
0268     case EnhancedPathFormula::FunctionAtan2:
0269         // TODO atan2 with one argument as in odf spec ???
0270         return atan2(arguments[0], arguments[1]);
0271         break;
0272     case EnhancedPathFormula::FunctionMin:
0273         return qMin(arguments[0], arguments[1]);
0274         break;
0275     case EnhancedPathFormula::FunctionMax:
0276         return qMax(arguments[0], arguments[1]);
0277         break;
0278     case EnhancedPathFormula::FunctionIf:
0279         if (arguments[0] > 0.0) {
0280             return arguments[1];
0281         } else {
0282             return arguments[2];
0283         }
0284         break;
0285     default:
0286         ;
0287     }
0288 
0289     return 0.0;
0290 }
0291 
0292 TokenList EnhancedPathFormula::scan(const QString &formula) const
0293 {
0294     // parsing state
0295     enum {
0296         Start,
0297         Finish,
0298         Bad,
0299         InNumber,
0300         InDecimal,
0301         InExpIndicator,
0302         InExponent,
0303         InString,
0304         InIdentifier
0305     } state;
0306 
0307     TokenList tokens;
0308 
0309     int i = 0;
0310     state = Start;
0311     int tokenStart = 0;
0312     QString tokenText;
0313     QString expr = formula + QChar();
0314 
0315     // main loop
0316     while ((state != Bad) && (state != Finish) && (i < expr.length())) {
0317         QChar ch = expr[i];
0318 
0319         switch (state) {
0320         case Start:
0321             tokenStart = i;
0322 
0323             // skip any whitespaces
0324             if (ch.isSpace()) {
0325                 i++;
0326             } else if (ch.isDigit()) { // check for number
0327                 state = InNumber;
0328             }
0329             // beginning with alphanumeric ?
0330             // could be identifier, function, function reference, modifier reference
0331             else if (isIdentifier(ch)) {
0332                 state = InIdentifier;
0333             } else if (ch == '.') { // decimal dot ?
0334                 tokenText.append(expr[i++]);
0335                 state = InDecimal;
0336             } else if (ch == QChar::Null) { // terminator character
0337                 state = Finish;
0338             } else { // look for operator match
0339                 QString opString(ch);
0340                 int op = matchOperator(opString);
0341 
0342                 // any matched operator ?
0343                 if (op != FormulaToken::OperatorInvalid) {
0344                     i++;
0345                     tokens.append(FormulaToken(FormulaToken::TypeOperator, opString, tokenStart));
0346                 } else {
0347                     state = Bad;
0348                 }
0349             }
0350             break;
0351         case InIdentifier:
0352             // consume as long as alpha, dollar sign, question mark, or digit
0353             if (isIdentifier(ch) || ch.isDigit()) {
0354                 tokenText.append(expr[i++]);
0355             } else if (ch == '(') { // a '(' ? then this must be a function identifier
0356                 tokens.append(FormulaToken(FormulaToken::TypeFunction, tokenText, tokenStart));
0357                 tokenStart = i;
0358                 tokenText.clear();
0359                 state = Start;
0360             } else { // we're done with identifier
0361                 tokens.append(FormulaToken(FormulaToken::TypeReference, tokenText, tokenStart));
0362                 tokenStart = i;
0363                 tokenText.clear();
0364                 state = Start;
0365             }
0366             break;
0367         case InNumber:
0368             // consume as long as it's digit
0369             if (ch.isDigit()) {
0370                 tokenText.append(expr[i++]);
0371             } else if (ch == '.') { // decimal dot ?
0372                 tokenText.append('.');
0373                 i++;
0374                 state = InDecimal;
0375             } else if (ch.toUpper() == 'E') { // exponent ?
0376                 tokenText.append('E');
0377                 i++;
0378                 state = InExpIndicator;
0379             } else { // we're done with integer number
0380                 tokens.append(FormulaToken(FormulaToken::TypeNumber, tokenText, tokenStart));
0381                 tokenText.clear();
0382                 state = Start;
0383             };
0384             break;
0385         case InDecimal:
0386             // consume as long as it's digit
0387             if (ch.isDigit()) {
0388                 tokenText.append(expr[i++]);
0389             } else if (ch.toUpper() == 'E') { // exponent ?
0390                 tokenText.append('E');
0391                 i++;
0392                 state = InExpIndicator;
0393             } else { // we're done with floating-point number
0394                 tokens.append(FormulaToken(FormulaToken::TypeNumber, tokenText, tokenStart));
0395                 tokenText.clear();
0396                 state = Start;
0397             };
0398             break;
0399         case InExpIndicator:
0400             // possible + or - right after E, e.g 1.23E+12 or 4.67E-8
0401             if ((ch == '+') || (ch == '-')) {
0402                 tokenText.append(expr[i++]);
0403             } else if (ch.isDigit()) { // consume as long as it's digit
0404                 state = InExponent;
0405             } else { // invalid thing here
0406                 state = Bad;
0407             }
0408             break;
0409         case InExponent:
0410             // consume as long as it's digit
0411             if (ch.isDigit()) {
0412                 tokenText.append(expr[i++]);
0413             } else { // we're done with floating-point number
0414                 tokens.append(FormulaToken(FormulaToken::TypeNumber, tokenText, tokenStart));
0415                 tokenText.clear();
0416                 state = Start;
0417             }
0418             break;
0419         default:
0420             break;
0421         }
0422     }
0423     return tokens;
0424 }
0425 
0426 bool EnhancedPathFormula::compile(const TokenList &tokens)
0427 {
0428     // sanity check
0429     if (tokens.count() == 0) {
0430         return false;
0431     }
0432 
0433     FormulaTokenStack syntaxStack;
0434     QStack<int> argStack;
0435     unsigned argCount = 1;
0436 
0437     for (int i = 0; i <= tokens.count(); i++) {
0438         // helper token: InvalidOp is end-of-formula
0439         FormulaToken token = (i < tokens.count()) ? tokens[i] : FormulaToken(FormulaToken::TypeOperator);
0440         FormulaToken::Type tokenType = token.type();
0441 
0442         // unknown token is invalid
0443         if (tokenType == FormulaToken::TypeUnknown) {
0444             break;
0445         }
0446 
0447         // for constants, push immediately to stack
0448         // generate code to load from a constant
0449         if (tokenType == FormulaToken::TypeNumber) {
0450             syntaxStack.push(token);
0451             m_constants.append(QVariant(token.asNumber()));
0452             m_codes.append(Opcode(Opcode::Load, m_constants.count() - 1));
0453         }
0454         // for identifier, push immediately to stack
0455         // generate code to load from reference
0456         if (tokenType == FormulaToken::TypeFunction || tokenType == FormulaToken::TypeReference) {
0457             syntaxStack.push(token);
0458             m_constants.append(QVariant(token.text()));
0459             m_codes.append(Opcode(Opcode::Ref, m_constants.count() - 1));
0460         }
0461         // are we entering a function ?
0462         // if token is operator, and stack already has: id (arg
0463         if (tokenType == FormulaToken::TypeOperator && syntaxStack.itemCount() >= 3) {
0464             FormulaToken arg = syntaxStack.top();
0465             FormulaToken par = syntaxStack.top(1);
0466             FormulaToken id = syntaxStack.top(2);
0467             if (!arg.isOperator() &&
0468                     par.asOperator() == FormulaToken::OperatorLeftPar &&
0469                     id.isFunction()) {
0470                 argStack.push(argCount);
0471                 argCount = 1;
0472             }
0473         }
0474         // for any other operator, try to apply all parsing rules
0475         if (tokenType == FormulaToken::TypeOperator) {
0476             // repeat until no more rule applies
0477             for (;;) {
0478                 bool ruleFound = false;
0479 
0480                 // rule for function arguments, if token is , or)
0481                 // id (arg1 , arg2 -> id (arg
0482                 if (!ruleFound)
0483                     if (syntaxStack.itemCount() >= 5)
0484                         if ((token.asOperator() == FormulaToken::OperatorRightPar) ||
0485                                 (token.asOperator() == FormulaToken::OperatorComma)) {
0486                             FormulaToken arg2 = syntaxStack.top();
0487                             FormulaToken sep = syntaxStack.top(1);
0488                             FormulaToken arg1 = syntaxStack.top(2);
0489                             FormulaToken par = syntaxStack.top(3);
0490                             FormulaToken id = syntaxStack.top(4);
0491                             if (!arg2.isOperator())
0492                                 if (sep.asOperator() == FormulaToken::OperatorComma)
0493                                     if (!arg1.isOperator())
0494                                         if (par.asOperator() == FormulaToken::OperatorLeftPar)
0495                                             if (id.isFunction()) {
0496                                                 ruleFound = true;
0497                                                 syntaxStack.pop();
0498                                                 syntaxStack.pop();
0499                                                 argCount++;
0500                                             }
0501                         }
0502                 // rule for function last argument:
0503                 //  id (arg) -> arg
0504                 if (!ruleFound)
0505                     if (syntaxStack.itemCount() >= 4) {
0506                         FormulaToken par2 = syntaxStack.top();
0507                         FormulaToken arg = syntaxStack.top(1);
0508                         FormulaToken par1 = syntaxStack.top(2);
0509                         FormulaToken id = syntaxStack.top(3);
0510                         if (par2.asOperator() == FormulaToken::OperatorRightPar)
0511                             if (!arg.isOperator())
0512                                 if (par1.asOperator() == FormulaToken::OperatorLeftPar)
0513                                     if (id.isFunction()) {
0514                                         ruleFound = true;
0515                                         syntaxStack.pop();
0516                                         syntaxStack.pop();
0517                                         syntaxStack.pop();
0518                                         syntaxStack.pop();
0519                                         syntaxStack.push(arg);
0520                                         m_codes.append(Opcode(Opcode::Function, argCount));
0521                                         argCount = argStack.empty() ? 0 : argStack.pop();
0522                                     }
0523                     }
0524 
0525                 // rule for parenthesis:  (Y) -> Y
0526                 if (!ruleFound)
0527                     if (syntaxStack.itemCount() >= 3) {
0528                         FormulaToken right = syntaxStack.top();
0529                         FormulaToken y = syntaxStack.top(1);
0530                         FormulaToken left = syntaxStack.top(2);
0531                         if (right.isOperator())
0532                             if (!y.isOperator())
0533                                 if (left.isOperator())
0534                                     if (right.asOperator() == FormulaToken::OperatorRightPar)
0535                                         if (left.asOperator() == FormulaToken::OperatorLeftPar) {
0536                                             ruleFound = true;
0537                                             syntaxStack.pop();
0538                                             syntaxStack.pop();
0539                                             syntaxStack.pop();
0540                                             syntaxStack.push(y);
0541                                         }
0542                     }
0543 
0544                 // rule for binary operator:  A (op) B -> A
0545                 // conditions: precedence of op >= precedence of token
0546                 // action: push (op) to result
0547                 // e.g. "A * B" becomes 'A' if token is operator '+'
0548                 if (!ruleFound)
0549                     if (syntaxStack.itemCount() >= 3) {
0550                         FormulaToken b = syntaxStack.top();
0551                         FormulaToken op = syntaxStack.top(1);
0552                         FormulaToken a = syntaxStack.top(2);
0553                         if (!a.isOperator())
0554                             if (!b.isOperator())
0555                                 if (op.isOperator())
0556                                     if (token.asOperator() != FormulaToken::OperatorLeftPar)
0557                                         if (opPrecedence(op.asOperator()) >= opPrecedence(token.asOperator())) {
0558                                             ruleFound = true;
0559                                             syntaxStack.pop();
0560                                             syntaxStack.pop();
0561                                             syntaxStack.pop();
0562                                             syntaxStack.push(b);
0563                                             switch (op.asOperator()) {
0564                                             // simple binary operations
0565                                             case FormulaToken::OperatorAdd: m_codes.append(Opcode::Add); break;
0566                                             case FormulaToken::OperatorSub: m_codes.append(Opcode::Sub); break;
0567                                             case FormulaToken::OperatorMul: m_codes.append(Opcode::Mul); break;
0568                                             case FormulaToken::OperatorDiv: m_codes.append(Opcode::Div); break;
0569                                             default: break;
0570                                             }
0571                                         }
0572                     }
0573 
0574                 // rule for unary operator:  (op1) (op2) X -> (op1) X
0575                 // conditions: op2 is unary, token is not '('
0576                 // action: push (op2) to result
0577                 // e.g.  "* - 2" becomes '*'
0578                 if (!ruleFound)
0579                     if (token.asOperator() != FormulaToken::OperatorLeftPar)
0580                         if (syntaxStack.itemCount() >= 3) {
0581                             FormulaToken x = syntaxStack.top();
0582                             FormulaToken op2 = syntaxStack.top(1);
0583                             FormulaToken op1 = syntaxStack.top(2);
0584                             if (!x.isOperator())
0585                                 if (op1.isOperator())
0586                                     if (op2.isOperator())
0587                                         if ((op2.asOperator() == FormulaToken::OperatorAdd)
0588                                                 || (op2.asOperator() == FormulaToken::OperatorSub)) {
0589                                             ruleFound = true;
0590                                             syntaxStack.pop();
0591                                             syntaxStack.pop();
0592                                             syntaxStack.push(x);
0593                                             if (op2.asOperator() == FormulaToken::OperatorSub) {
0594                                                 m_codes.append(Opcode(Opcode::Neg));
0595                                             }
0596                                         }
0597                         }
0598 
0599                 // auxiliary rule for unary operator:  (op) X -> X
0600                 // conditions: op is unary, op is first in syntax stack, token is not '('
0601                 // action: push (op) to result
0602                 if (!ruleFound)
0603                     if (token.asOperator() != FormulaToken::OperatorLeftPar)
0604                         if (syntaxStack.itemCount() == 2) {
0605                             FormulaToken x = syntaxStack.top();
0606                             FormulaToken op = syntaxStack.top(1);
0607                             if (!x.isOperator())
0608                                 if (op.isOperator())
0609                                     if ((op.asOperator() == FormulaToken::OperatorAdd)
0610                                             || (op.asOperator() == FormulaToken::OperatorSub)) {
0611                                         ruleFound = true;
0612                                         syntaxStack.pop();
0613                                         syntaxStack.pop();
0614                                         syntaxStack.push(x);
0615                                         if (op.asOperator() == FormulaToken::OperatorSub) {
0616                                             m_codes.append(Opcode(Opcode::Neg));
0617                                         }
0618                                     }
0619                         }
0620 
0621                 if (!ruleFound) {
0622                     break;
0623                 }
0624             }
0625 
0626             syntaxStack.push(token);
0627         }
0628     }
0629 
0630     // syntaxStack must left only one operand and end-of-formula (i.e. InvalidOp)
0631     m_valid = false;
0632     if (syntaxStack.itemCount() == 2)
0633         if (syntaxStack.top().isOperator())
0634             if (syntaxStack.top().asOperator() == FormulaToken::OperatorInvalid)
0635                 if (!syntaxStack.top(1).isOperator()) {
0636                     m_valid = true;
0637                 }
0638 
0639     // bad parsing ? clean-up everything
0640     if (!m_valid) {
0641         m_constants.clear();
0642         m_codes.clear();
0643         qWarning() << "compiling of " << m_text << " failed";
0644     }
0645 
0646     return m_valid;
0647 }
0648 
0649 QString EnhancedPathFormula::toString() const
0650 {
0651     return m_text;
0652 }
0653 
0654 FormulaToken::FormulaToken(Type type, const QString &text, int position)
0655     : m_type(type), m_text(text), m_position(position)
0656 {
0657 }
0658 
0659 FormulaToken::FormulaToken(const FormulaToken &token)
0660 {
0661     if (this != &token) {
0662         *this = token;
0663     }
0664 }
0665 
0666 FormulaToken &FormulaToken::operator=(const FormulaToken &rhs)
0667 {
0668     if (this == &rhs) {
0669         return *this;
0670     }
0671 
0672     m_type = rhs.m_type;
0673     m_text = rhs.m_text;
0674     m_position = rhs.m_position;
0675 
0676     return *this;
0677 }
0678 
0679 qreal FormulaToken::asNumber() const
0680 {
0681     if (isNumber()) {
0682         return m_text.toDouble();
0683     } else {
0684         return 0.0;
0685     }
0686 }
0687 
0688 FormulaToken::Operator FormulaToken::asOperator() const
0689 {
0690     if (isOperator()) {
0691         return matchOperator(m_text);
0692     } else {
0693         return OperatorInvalid;
0694     }
0695 }
0696 
0697 // helper function: return operator of given token text
0698 FormulaToken::Operator matchOperator(const QString &text)
0699 {
0700     if (text.length() != 1) {
0701         return FormulaToken::OperatorInvalid;
0702     }
0703 
0704     const char c = text[0].toLatin1();
0705     switch (c) {
0706     case '+': return FormulaToken::OperatorAdd; break;
0707     case '-': return FormulaToken::OperatorSub; break;
0708     case '*': return FormulaToken::OperatorMul; break;
0709     case '/': return FormulaToken::OperatorDiv; break;
0710     case '(': return FormulaToken::OperatorLeftPar; break;
0711     case ')': return FormulaToken::OperatorRightPar; break;
0712     case ',': return FormulaToken::OperatorComma; break;
0713     default : return FormulaToken::OperatorInvalid; break;
0714     }
0715 }
0716 
0717 // helper function: return true for valid identifier character
0718 bool isIdentifier(QChar ch)
0719 {
0720     return (ch.unicode() == '?') || (ch.unicode() == '$') || (ch.isLetter());
0721 }
0722 
0723 // helper function: give operator precedence
0724 // e.g. '+' is 1 while '*' is 3
0725 int opPrecedence(FormulaToken::Operator op)
0726 {
0727     int prec = -1;
0728     switch (op) {
0729     case FormulaToken::OperatorMul: prec = 5; break;
0730     case FormulaToken::OperatorDiv: prec = 6; break;
0731     case FormulaToken::OperatorAdd: prec = 3; break;
0732     case FormulaToken::OperatorSub: prec = 3; break;
0733     case FormulaToken::OperatorComma: prec = 0; break;
0734     case FormulaToken::OperatorRightPar: prec = 0; break;
0735     case FormulaToken::OperatorLeftPar: prec = -1; break;
0736     default: prec = -1; break;
0737     }
0738     return prec;
0739 }
0740 
0741 EnhancedPathFormula::Function matchFunction(const QString &text)
0742 {
0743     if (text == "abs") {
0744         return EnhancedPathFormula::FunctionAbs;
0745     }
0746     if (text == "sqrt") {
0747         return EnhancedPathFormula::FunctionSqrt;
0748     }
0749     if (text == "sin") {
0750         return EnhancedPathFormula::FunctionSin;
0751     }
0752     if (text == "cos") {
0753         return EnhancedPathFormula::FunctionCos;
0754     }
0755     if (text == "tan") {
0756         return EnhancedPathFormula::FunctionTan;
0757     }
0758     if (text == "atan") {
0759         return EnhancedPathFormula::FunctionAtan;
0760     }
0761     if (text == "atan2") {
0762         return EnhancedPathFormula::FunctionAtan2;
0763     }
0764     if (text == "min") {
0765         return EnhancedPathFormula::FunctionMin;
0766     }
0767     if (text == "max") {
0768         return EnhancedPathFormula::FunctionMax;
0769     }
0770     if (text == "if") {
0771         return EnhancedPathFormula::FunctionIf;
0772     }
0773 
0774     return EnhancedPathFormula::FunctionUnknown;
0775 }
0776 
0777 QString matchFunction(EnhancedPathFormula::Function function)
0778 {
0779     switch (function) {
0780     case EnhancedPathFormula::FunctionAbs:
0781         return "fabs";
0782         break;
0783     case EnhancedPathFormula::FunctionSqrt:
0784         return "sqrt";
0785         break;
0786     case EnhancedPathFormula::FunctionSin:
0787         return "sin";
0788         break;
0789     case EnhancedPathFormula::FunctionCos:
0790         return "cos";
0791         break;
0792     case EnhancedPathFormula::FunctionTan:
0793         return "tan";
0794         break;
0795     case EnhancedPathFormula::FunctionAtan:
0796         return "atan";
0797         break;
0798     case EnhancedPathFormula::FunctionAtan2:
0799         return "atan2";
0800         break;
0801     case EnhancedPathFormula::FunctionMin:
0802         return "min";
0803         break;
0804     case EnhancedPathFormula::FunctionMax:
0805         return "max";
0806         break;
0807     case EnhancedPathFormula::FunctionIf:
0808         return "if";
0809         break;
0810     default:
0811         break;
0812     }
0813 
0814     return "unknown";
0815 }
0816 
0817 void EnhancedPathFormula::debugTokens(const TokenList &tokens)
0818 {
0819 #ifndef NDEBUG
0820     for (int i = 0; i < tokens.count(); i++) {
0821         qDebug() << tokens[i].text();
0822     }
0823 #else
0824     Q_UNUSED(tokens);
0825 #endif
0826 }
0827 
0828 void EnhancedPathFormula::debugOpcodes()
0829 {
0830 #ifndef NDEBUG
0831     foreach (const Opcode &c, m_codes) {
0832         QString ctext;
0833         switch (c.type) {
0834         case Opcode::Load: ctext = QString("Load #%1").arg(c.index); break;
0835         case Opcode::Ref: ctext = QString("Ref #%1").arg(c.index); break;
0836         case Opcode::Function: ctext = QString("Function (%1)").arg(c.index); break;
0837         case Opcode::Add: ctext = "Add"; break;
0838         case Opcode::Sub: ctext = "Sub"; break;
0839         case Opcode::Mul: ctext = "Mul"; break;
0840         case Opcode::Div: ctext = "Div"; break;
0841         case Opcode::Neg: ctext = "Neg"; break;
0842         default: ctext = "Unknown"; break;
0843         }
0844         qDebug() << ctext;
0845     }
0846 #endif
0847 }