File indexing completed on 2024-05-19 04:56:01
0001 /** 0002 * \file expressionparser.h 0003 * Simple parser for expressions. 0004 * 0005 * \b Project: Kid3 0006 * \author Urs Fleisch 0007 * \date 23 Jan 2008 0008 * 0009 * Copyright (C) 2008-2024 Urs Fleisch 0010 * 0011 * This file is part of Kid3. 0012 * 0013 * Kid3 is free software; you can redistribute it and/or modify 0014 * it under the terms of the GNU General Public License as published by 0015 * the Free Software Foundation; either version 2 of the License, or 0016 * (at your option) any later version. 0017 * 0018 * Kid3 is distributed in the hope that it will be useful, 0019 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0020 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0021 * GNU General Public License for more details. 0022 * 0023 * You should have received a copy of the GNU General Public License 0024 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0025 * 0026 * The RPN tokenizer is based on ExprEvaluator, 0027 * Copyright (C) 2004 the VideoLAN team, under the same license. 0028 */ 0029 0030 #include "expressionparser.h" 0031 0032 namespace { 0033 0034 /** 0035 * Convert a string to a boolean. 0036 * 0037 * @param str string 0038 * @param b the boolean is returned here 0039 * 0040 * @return true if ok. 0041 */ 0042 bool stringToBool(const QString& str, bool& b) 0043 { 0044 if (str == QLatin1String("1") || str == QLatin1String("true") || 0045 str == QLatin1String("on") || str == QLatin1String("yes")) { 0046 b = true; 0047 return true; 0048 } 0049 if (str == QLatin1String("0") || str == QLatin1String("false") || 0050 str == QLatin1String("off") || str == QLatin1String("no")) { 0051 b = false; 0052 return true; 0053 } 0054 return false; 0055 } 0056 0057 /** 0058 * Convert a boolean to a string. 0059 * 0060 * @param b boolean to convert 0061 * 0062 * @return "1" or "0". 0063 */ 0064 QString boolToString(bool b) 0065 { 0066 return b ? QLatin1String("1") : QLatin1String("0"); 0067 } 0068 0069 } 0070 0071 /** 0072 * Constructor. 0073 * 0074 * @param operators additional operators (besides not, and, or), 0075 * highest priority first 0076 */ 0077 ExpressionParser::ExpressionParser(QStringList operators) : // clazy:exclude=function-args-by-ref 0078 m_operators(operators << QLatin1String("not") << QLatin1String("and") 0079 << QLatin1String("or")), 0080 m_error(false) 0081 { 0082 } 0083 0084 /** 0085 * Compare operator priority. 0086 * 0087 * @return true if op1 has less priority than op2. 0088 */ 0089 bool ExpressionParser::lessPriority(const QString& op1, 0090 const QString& op2) const 0091 { 0092 int index1 = m_operators.indexOf(op1); 0093 int index2 = m_operators.indexOf(op2); 0094 if (op1 == QLatin1String("(")) return true; 0095 if (index1 >= 0 && index2 >= 0) return index1 >= index2; 0096 return false; 0097 } 0098 0099 /** 0100 * Tokenize an expression in reverse polish notation. 0101 * 0102 * @param expr with strings, operators, not, and, or, (, ). 0103 */ 0104 void ExpressionParser::tokenizeRpn(const QString& expr) 0105 { 0106 m_rpnStack.clear(); 0107 0108 QStringList operatorStack; 0109 QString token; 0110 int begin = 0, end = 0, len = expr.length(); 0111 while (begin < len) { 0112 // skip spaces 0113 while (expr[begin] == QLatin1Char(' ')) { 0114 ++begin; 0115 } 0116 0117 if (expr[begin] == QLatin1Char('(')) { 0118 // push '(' on operator stack and continue 0119 operatorStack.push_back(QLatin1String("(")); 0120 ++begin; 0121 } else if (expr[begin] == QLatin1Char(')')) { 0122 // after ')', pop operator stack until '(' is found 0123 while (!operatorStack.empty()) { 0124 QString lastOp = operatorStack.back(); 0125 operatorStack.pop_back(); 0126 if (lastOp == QLatin1String("(")) { 0127 break; 0128 } 0129 m_rpnStack.push_back(lastOp); 0130 } 0131 ++begin; 0132 } else { 0133 if (expr[begin] == QLatin1Char('"')) { 0134 // skip quoted string 0135 end = begin + 1; 0136 while (end < len && 0137 !(expr[end] == QLatin1Char('"') && 0138 (end <= 0 || expr[end - 1] != QLatin1Char('\\')))) { 0139 ++end; 0140 } 0141 token = expr.mid(begin + 1, end - begin - 1); 0142 token.replace(QLatin1String("\\\""), QLatin1String("\"")); 0143 begin = end + 1; 0144 } else { 0145 // skip spaces 0146 end = begin; 0147 while (end < len && expr[end] != QLatin1Char(' ') && 0148 expr[end] != QLatin1Char(')')) { 0149 ++end; 0150 } 0151 token = expr.mid(begin, end - begin); 0152 begin = end; 0153 } 0154 if (m_operators.contains(token)) { 0155 // pop the operator stack while the token has lower priority 0156 while (!operatorStack.empty() && 0157 lessPriority(token, operatorStack.back())) { 0158 QString lastOp = operatorStack.back(); 0159 operatorStack.pop_back(); 0160 m_rpnStack.push_back(lastOp); 0161 } 0162 operatorStack.push_back(token); 0163 } else { 0164 m_rpnStack.push_back(token); 0165 } 0166 } 0167 } 0168 // pop operator stack 0169 while (!operatorStack.empty()) { 0170 QString lastOp = operatorStack.back(); 0171 operatorStack.pop_back(); 0172 m_rpnStack.push_back(lastOp); 0173 } 0174 m_rpnIterator = m_rpnStack.constBegin(); 0175 } 0176 0177 /** 0178 * Clear the variable stack before restarting an evaluation. 0179 */ 0180 void ExpressionParser::clearEvaluation() 0181 { 0182 m_rpnIterator = m_rpnStack.constBegin(); 0183 m_varStack.clear(); 0184 m_error = false; 0185 } 0186 0187 /** 0188 * Pop a boolean from the variable stack. 0189 * Can be used to get the result after evaluate() returns false and 0190 * no error occurred. 0191 * 0192 * @param var the boolean is returned here 0193 * 0194 * @return true if ok. 0195 */ 0196 bool ExpressionParser::popBool(bool& var) 0197 { 0198 if (m_varStack.empty() || !stringToBool(m_varStack.back(), var)) { 0199 return false; 0200 } 0201 m_varStack.pop_back(); 0202 return true; 0203 } 0204 0205 /** 0206 * Push a boolean to the variable stack. 0207 * Can be used to push the result of the operation returned by 0208 * evaluate() back onto the variable stack. 0209 * 0210 * @param var boolean to push 0211 */ 0212 void ExpressionParser::pushBool(bool var) 0213 { 0214 m_varStack.push_back(boolToString(var)); 0215 } 0216 0217 /** 0218 * Pop two booleans from the variable stack. 0219 * 0220 * @param var1 first boolean 0221 * @param var2 second boolean 0222 * 0223 * @return true if ok. 0224 */ 0225 bool ExpressionParser::popTwoBools(bool& var1, bool& var2) 0226 { 0227 if (m_varStack.empty() || !stringToBool(m_varStack.back(), var1)) { 0228 return false; 0229 } 0230 m_varStack.pop_back(); 0231 if (m_varStack.empty() || !stringToBool(m_varStack.back(), var2)) { 0232 return false; 0233 } 0234 m_varStack.pop_back(); 0235 return true; 0236 } 0237 0238 /** 0239 * Evaluate the RPN stack. 0240 * Boolean operations and, or, not are performed automatically. If another 0241 * operation has to be performed, the method stops and returns operator 0242 * and variables. The result can then be pushed onto the stack using 0243 * pushBool() and then the method can be called again. 0244 * 0245 * @param op the operator is returned here 0246 * @param var1 the first variable is returned here 0247 * @param var2 the second variable is returned here 0248 * 0249 * @return true if the RPN stack has more to evaluate, 0250 * if false, the evaluation is finished. 0251 */ 0252 bool ExpressionParser::evaluate(QString& op, QString& var1, QString& var2) 0253 { 0254 while (m_rpnIterator != m_rpnStack.constEnd()) { 0255 if (QString token = *m_rpnIterator++; token == QLatin1String("and")) { 0256 bool b1, b2; 0257 if (!popTwoBools(b1, b2)) { 0258 m_error = true; 0259 break; 0260 } 0261 pushBool(b1 && b2); 0262 } else if (token == QLatin1String("or")) { 0263 bool b1, b2; 0264 if (!popTwoBools(b1, b2)) { 0265 m_error = true; 0266 break; 0267 } 0268 pushBool(b1 || b2); 0269 } else if (token == QLatin1String("not")) { 0270 bool var; 0271 if (!popBool(var)) { 0272 m_error = true; 0273 break; 0274 } 0275 pushBool(!var); 0276 } else if (m_operators.contains(token)) { 0277 if (m_varStack.empty()) { 0278 m_error = true; 0279 break; 0280 } 0281 var1 = m_varStack.back(); 0282 m_varStack.pop_back(); 0283 if (m_varStack.empty()) { 0284 m_error = true; 0285 break; 0286 } 0287 var2 = m_varStack.back(); 0288 m_varStack.pop_back(); 0289 op = token; 0290 return true; 0291 } else { 0292 m_varStack.push_back(token); 0293 } 0294 } 0295 return false; 0296 }