2
* \file expressionparser.h
3
* Simple parser for expressions.
9
* Copyright (C) 2008 Urs Fleisch
11
* This file is part of Kid3.
13
* Kid3 is free software; you can redistribute it and/or modify
14
* it under the terms of the GNU General Public License as published by
15
* the Free Software Foundation; either version 2 of the License, or
16
* (at your option) any later version.
18
* Kid3 is distributed in the hope that it will be useful,
19
* but WITHOUT ANY WARRANTY; without even the implied warranty of
20
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
* GNU General Public License for more details.
23
* You should have received a copy of the GNU General Public License
24
* along with this program. If not, see <http://www.gnu.org/licenses/>.
26
* The RPN tokenizer is based on ExprEvaluator,
27
* Copyright (C) 2004 the VideoLAN team, under the same license.
30
#include "expressionparser.h"
31
#include "qtcompatmac.h"
36
* @param operators additional operators (besides not, and, or),
37
* highest priority first
39
ExpressionParser::ExpressionParser(QStringList operators) :
40
m_operators(operators << "not" << "and" << "or"),
48
ExpressionParser::~ExpressionParser()
53
* Compare operator priority.
55
* @return true if op1 has less priority than op2.
57
bool ExpressionParser::lessPriority(const QString& op1,
58
const QString& op2) const
60
#if QT_VERSION >= 0x040000
61
int index1 = m_operators.indexOf(op1);
62
int index2 = m_operators.indexOf(op2);
64
int index1 = m_operators.findIndex(op1);
65
int index2 = m_operators.findIndex(op2);
67
if (op1 == "(") return true;
68
if (index1 >= 0 && index2 >= 0) return index1 >= index2;
73
* Tokenize an expression in reverse polish notation.
75
* @param expr with strings, operators, not, and, or, (, ).
77
void ExpressionParser::tokenizeRpn(const QString& expr)
81
QStringList operatorStack;
83
int begin = 0, end = 0, len = expr.length();
86
while (expr[begin] == ' ') {
90
if (expr[begin] == '(') {
91
// push '(' on operator stack and continue
92
operatorStack.push_back("(");
94
} else if (expr[begin] == ')') {
95
// after ')', pop operator stack until '(' is found
96
while (!operatorStack.empty()) {
97
QString lastOp = operatorStack.back();
98
operatorStack.pop_back();
102
m_rpnStack.push_back(lastOp);
106
if (expr[begin] == '"') {
107
// skip quoted string
110
!(expr[end] == '"' && (end <= 0 || expr[end - 1] != '\\'))) {
113
token = expr.mid(begin + 1, end - begin - 1);
114
token.replace("\\\"", "\"");
119
while (end < len && expr[end] != ' ' && expr[end] != ')') {
122
token = expr.mid(begin, end - begin);
125
if (m_operators.contains(token)) {
126
// pop the operator stack while the token has lower priority
127
while (!operatorStack.empty() &&
128
lessPriority(token, operatorStack.back())) {
129
QString lastOp = operatorStack.back();
130
operatorStack.pop_back();
131
m_rpnStack.push_back(lastOp);
133
operatorStack.push_back(token);
135
m_rpnStack.push_back(token);
139
// pop operator stack
140
while (!operatorStack.empty()) {
141
QString lastOp = operatorStack.back();
142
operatorStack.pop_back();
143
m_rpnStack.push_back(lastOp);
145
m_rpnIterator = m_rpnStack.begin();
149
* Get the next token from the RPN stack.
151
* @return token, QString::null if stack is empty.
153
QString ExpressionParser::getToken()
155
if (!m_rpnStack.empty()) {
156
QString token = m_rpnStack.front();
157
m_rpnStack.pop_front();
160
return QString::null;
165
* Convert a string to a boolean.
168
* @param b the boolean is returned here
170
* @return true if ok.
172
static bool stringToBool(const QString& str, bool& b)
174
if (str == "1" || str == "true" || str == "on" || str == "yes") {
177
} else if (str == "0" || str == "false" || str == "off" || str == "no") {
185
* Convert a boolean to a string.
187
* @param b boolean to convert
189
* @return "1" or "0".
191
static QString boolToString(bool b)
193
return b ? "1" : "0";
197
* Clear the variable stack before restarting an evaluation.
199
void ExpressionParser::clearEvaluation()
201
m_rpnIterator = m_rpnStack.begin();
207
* Pop a boolean from the variable stack.
208
* Can be used to get the result after evaluate() returns false and
211
* @param var the boolean is returned here
213
* @return true if ok.
215
bool ExpressionParser::popBool(bool& var)
217
if (m_varStack.empty() || !stringToBool(m_varStack.back(), var)) {
220
m_varStack.pop_back();
225
* Push a boolean to the variable stack.
226
* Can be used to push the result of the operation returned by
227
* evaluate() back onto the variable stack.
229
* @param var boolean to push
231
void ExpressionParser::pushBool(bool var)
233
m_varStack.push_back(boolToString(var));
237
* Pop two booleans from the variable stack.
239
* @param var1 first boolean
240
* @param var2 second boolean
242
* @return true if ok.
244
bool ExpressionParser::popTwoBools(bool& var1, bool& var2)
246
if (m_varStack.empty() || !stringToBool(m_varStack.back(), var1)) {
249
m_varStack.pop_back();
250
if (m_varStack.empty() || !stringToBool(m_varStack.back(), var2)) {
253
m_varStack.pop_back();
258
* Evaluate the RPN stack.
259
* Boolean operations and, or, not are performed automatically. If another
260
* operation has to be performed, the method stops and returns operator
261
* and variables. The result can then be pushed onto the stack using
262
* pushBool() and then the method can be called again.
264
* @param op the operator is returned here
265
* @param var1 the first variable is returned here
266
* @param var2 the second variable is returned here
268
* @return true if the RPN stack has more to evaluate,
269
* if false, the evaluation is finished.
271
bool ExpressionParser::evaluate(QString& op, QString& var1, QString& var2)
273
while (m_rpnIterator != m_rpnStack.end()) {
274
QString token = *m_rpnIterator++;
275
if (token == "and") {
277
if (!popTwoBools(var1, var2)) {
281
pushBool(var1 && var2);
282
} else if (token == "or") {
284
if (!popTwoBools(var1, var2)) {
288
pushBool(var1 || var2);
289
} else if (token == "not") {
296
} else if (m_operators.contains(token)) {
297
if (m_varStack.empty()) {
301
var1 = m_varStack.back();
302
m_varStack.pop_back();
303
if (m_varStack.empty()) {
307
var2 = m_varStack.back();
308
m_varStack.pop_back();
312
m_varStack.push_back(token);