1 /*
2  *  Copyright (C) 2005-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "InfoExpression.h"
10 
11 #include "GUIInfoManager.h"
12 #include "ServiceBroker.h"
13 #include "guilib/GUIComponent.h"
14 #include "utils/log.h"
15 
16 #include <list>
17 #include <memory>
18 #include <stack>
19 
20 using namespace INFO;
21 
Initialize()22 void InfoSingle::Initialize()
23 {
24   m_condition = CServiceBroker::GetGUI()->GetInfoManager().TranslateSingleString(m_expression, m_listItemDependent);
25 }
26 
Update(const CGUIListItem * item)27 void InfoSingle::Update(const CGUIListItem *item)
28 {
29   m_value = CServiceBroker::GetGUI()->GetInfoManager().GetBool(m_condition, m_context, item);
30 }
31 
Initialize()32 void InfoExpression::Initialize()
33 {
34   if (!Parse(m_expression))
35   {
36     CLog::Log(LOGERROR, "Error parsing boolean expression %s", m_expression.c_str());
37     m_expression_tree = std::make_shared<InfoLeaf>(CServiceBroker::GetGUI()->GetInfoManager().Register("false", 0), false);
38   }
39 }
40 
Update(const CGUIListItem * item)41 void InfoExpression::Update(const CGUIListItem *item)
42 {
43   m_value = m_expression_tree->Evaluate(item);
44 }
45 
46 /* Expressions are rewritten at parse time into a form which favours the
47  * formation of groups of associative nodes. These groups are then reordered at
48  * evaluation time such that nodes whose value renders the evaluation of the
49  * remainder of the group unnecessary tend to be evaluated first (these are
50  * true nodes for OR subexpressions, or false nodes for AND subexpressions).
51  * The end effect is to minimise the number of leaf nodes that need to be
52  * evaluated in order to determine the value of the expression. The runtime
53  * adaptability has the advantage of not being customised for any particular skin.
54  *
55  * The modifications to the expression at parse time fall into two groups:
56  * 1) Moving logical NOTs so that they are only applied to leaf nodes.
57  *    For example, rewriting ![A+B]|C as !A|!B|C allows reordering such that
58  *    any of the three leaves can be evaluated first.
59  * 2) Combining adjacent AND or OR operations such that each path from the root
60  *    to a leaf encounters a strictly alternating pattern of AND and OR
61  *    operations. So [A|B]|[C|D+[[E|F]|G] becomes A|B|C|[D+[E|F|G]].
62  */
63 
Evaluate(const CGUIListItem * item)64 bool InfoExpression::InfoLeaf::Evaluate(const CGUIListItem *item)
65 {
66   return m_invert ^ m_info->Get(item);
67 }
68 
InfoAssociativeGroup(node_type_t type,const InfoSubexpressionPtr & left,const InfoSubexpressionPtr & right)69 InfoExpression::InfoAssociativeGroup::InfoAssociativeGroup(
70     node_type_t type,
71     const InfoSubexpressionPtr &left,
72     const InfoSubexpressionPtr &right)
73     : m_type(type)
74 {
75   AddChild(right);
76   AddChild(left);
77 }
78 
AddChild(const InfoSubexpressionPtr & child)79 void InfoExpression::InfoAssociativeGroup::AddChild(const InfoSubexpressionPtr &child)
80 {
81   m_children.push_front(child); // largely undoes the effect of parsing right-associative
82 }
83 
Merge(const std::shared_ptr<InfoAssociativeGroup> & other)84 void InfoExpression::InfoAssociativeGroup::Merge(const std::shared_ptr<InfoAssociativeGroup>& other)
85 {
86   m_children.splice(m_children.end(), other->m_children);
87 }
88 
Evaluate(const CGUIListItem * item)89 bool InfoExpression::InfoAssociativeGroup::Evaluate(const CGUIListItem *item)
90 {
91   /* Handle either AND or OR by using the relation
92    * A AND B == !(!A OR !B)
93    * to convert ANDs into ORs
94    */
95   std::list<InfoSubexpressionPtr>::iterator last = m_children.end();
96   std::list<InfoSubexpressionPtr>::iterator it = m_children.begin();
97   bool use_and = (m_type == NODE_AND);
98   bool result = use_and ^ (*it)->Evaluate(item);
99   while (!result && ++it != last)
100   {
101     result = use_and ^ (*it)->Evaluate(item);
102     if (result)
103     {
104       /* Move this child to the head of the list so we evaluate faster next time */
105       m_children.push_front(*it);
106       m_children.erase(it);
107     }
108   }
109   return use_and ^ result;
110 }
111 
112 /* Expressions are parsed using the shunting-yard algorithm. Binary operators
113  * (AND/OR) are treated as right-associative so that we don't need to make a
114  * special case for the unary NOT operator. This has no effect upon the answers
115  * generated, though the initial sequence of evaluation of leaves may be
116  * different from what you might expect.
117  */
118 
GetOperator(char ch)119 InfoExpression::operator_t InfoExpression::GetOperator(char ch)
120 {
121   if (ch == '[')
122     return OPERATOR_LB;
123   else if (ch == ']')
124     return OPERATOR_RB;
125   else if (ch == '!')
126     return OPERATOR_NOT;
127   else if (ch == '+')
128     return OPERATOR_AND;
129   else if (ch == '|')
130     return OPERATOR_OR;
131   else
132     return OPERATOR_NONE;
133 }
134 
OperatorPop(std::stack<operator_t> & operator_stack,bool & invert,std::stack<InfoSubexpressionPtr> & nodes)135 void InfoExpression::OperatorPop(std::stack<operator_t> &operator_stack, bool &invert, std::stack<InfoSubexpressionPtr> &nodes)
136 {
137   operator_t op2 = operator_stack.top();
138   operator_stack.pop();
139   if (op2 == OPERATOR_NOT)
140   {
141     invert = !invert;
142   }
143   else
144   {
145     // At this point, it can only be OPERATOR_AND or OPERATOR_OR
146     if (invert)
147       op2 = (operator_t) (OPERATOR_AND ^ OPERATOR_OR ^ op2);
148     node_type_t new_type = op2 == OPERATOR_AND ? NODE_AND : NODE_OR;
149 
150     InfoSubexpressionPtr right = nodes.top();
151     nodes.pop();
152     InfoSubexpressionPtr left = nodes.top();
153 
154     node_type_t right_type = right->Type();
155     node_type_t left_type = left->Type();
156 
157     // Combine associative operations into the same node where possible
158     if (left_type == new_type && right_type == new_type)
159       /* For example:        AND
160        *                   /     \                ____ AND ____
161        *                AND       AND     ->     /    /   \    \
162        *               /   \     /   \         leaf leaf leaf leaf
163        *             leaf leaf leaf leaf
164        */
165       std::static_pointer_cast<InfoAssociativeGroup>(left)->Merge(std::static_pointer_cast<InfoAssociativeGroup>(right));
166     else if (left_type == new_type)
167       /* For example:        AND                    AND
168        *                   /     \                /  |  \
169        *                AND       OR      ->   leaf leaf OR
170        *               /   \     /   \                  /   \
171        *             leaf leaf leaf leaf              leaf leaf
172        */
173       std::static_pointer_cast<InfoAssociativeGroup>(left)->AddChild(right);
174     else
175     {
176       nodes.pop();
177       if (right_type == new_type)
178       {
179         /* For example:        AND                       AND
180          *                   /     \                   /  |  \
181          *                OR        AND     ->      OR  leaf leaf
182          *               /   \     /   \           /   \
183          *             leaf leaf leaf leaf       leaf leaf
184          */
185         std::static_pointer_cast<InfoAssociativeGroup>(right)->AddChild(left);
186         nodes.push(right);
187       }
188       else
189         /* For example:        AND              which can't be simplified, and
190          *                   /     \            requires a new AND node to be
191          *                OR        OR          created with the two OR nodes
192          *               /   \     /   \        as children
193          *             leaf leaf leaf leaf
194          */
195         nodes.push(std::make_shared<InfoAssociativeGroup>(new_type, left, right));
196     }
197   }
198 }
199 
Parse(const std::string & expression)200 bool InfoExpression::Parse(const std::string &expression)
201 {
202   const char *s = expression.c_str();
203   std::string operand;
204   std::stack<operator_t> operator_stack;
205   bool invert = false;
206   std::stack<InfoSubexpressionPtr> nodes;
207   // The next two are for syntax-checking purposes
208   bool after_binaryoperator = true;
209   int bracket_count = 0;
210 
211   CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager();
212 
213   char c;
214   // Skip leading whitespace - don't want it to count as an operand if that's all there is
215   while (isspace((unsigned char)(c=*s)))
216     s++;
217 
218   while ((c = *s++) != '\0')
219   {
220     operator_t op;
221     if ((op = GetOperator(c)) != OPERATOR_NONE)
222     {
223       // Character is an operator
224       if ((!after_binaryoperator && (c == '!' || c == '[')) ||
225           (after_binaryoperator && (c == ']' || c == '+' || c == '|')))
226       {
227         CLog::Log(LOGERROR, "Misplaced %c", c);
228         return false;
229       }
230       if (c == '[')
231         bracket_count++;
232       else if (c == ']' && bracket_count-- == 0)
233       {
234         CLog::Log(LOGERROR, "Unmatched ]");
235         return false;
236       }
237       if (!operand.empty())
238       {
239         InfoPtr info = infoMgr.Register(operand, m_context);
240         if (!info)
241         {
242           CLog::Log(LOGERROR, "Bad operand '%s'", operand.c_str());
243           return false;
244         }
245         /* Propagate any listItem dependency from the operand to the expression */
246         m_listItemDependent |= info->ListItemDependent();
247         nodes.push(std::make_shared<InfoLeaf>(info, invert));
248         /* Reuse operand string for next operand */
249         operand.clear();
250       }
251 
252       // Handle any higher-priority stacked operators, except when the new operator is left-bracket.
253       // For a right-bracket, this will stop with the matching left-bracket at the top of the operator stack.
254       if (op != OPERATOR_LB)
255       {
256         while (!operator_stack.empty() && operator_stack.top() > op)
257           OperatorPop(operator_stack, invert, nodes);
258       }
259       if (op == OPERATOR_RB)
260         operator_stack.pop(); // remove the matching left-bracket
261       else
262         operator_stack.push(op);
263       if (op == OPERATOR_NOT)
264         invert = !invert;
265 
266       if (c == '+' || c == '|')
267         after_binaryoperator = true;
268       // Skip trailing whitespace - don't want it to count as an operand if that's all there is
269       while (isspace((unsigned char)(c=*s))) s++;
270     }
271     else
272     {
273       // Character is part of operand
274       operand += c;
275       after_binaryoperator = false;
276     }
277   }
278   if (bracket_count > 0)
279   {
280     CLog::Log(LOGERROR, "Unmatched [");
281     return false;
282   }
283   if (after_binaryoperator)
284   {
285     CLog::Log(LOGERROR, "Missing operand");
286     return false;
287   }
288   if (!operand.empty())
289   {
290     InfoPtr info = infoMgr.Register(operand, m_context);
291     if (!info)
292     {
293       CLog::Log(LOGERROR, "Bad operand '%s'", operand.c_str());
294       return false;
295     }
296     /* Propagate any listItem dependency from the operand to the expression */
297     m_listItemDependent |= info->ListItemDependent();
298     nodes.push(std::make_shared<InfoLeaf>(info, invert));
299   }
300   while (!operator_stack.empty())
301     OperatorPop(operator_stack, invert, nodes);
302 
303   m_expression_tree = nodes.top();
304   return true;
305 }
306