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 }