1 /*
2 * Copyright (C) 2012-2019 Daniel Scharrer
3 *
4 * This software is provided 'as-is', without any express or implied
5 * warranty. In no event will the author(s) be held liable for any damages
6 * arising from the use of this software.
7 *
8 * Permission is granted to anyone to use this software for any purpose,
9 * including commercial applications, and to alter it and redistribute it
10 * freely, subject to the following restrictions:
11 *
12 * 1. The origin of this software must not be misrepresented; you must not
13 * claim that you wrote the original software. If you use this software
14 * in a product, an acknowledgment in the product documentation would be
15 * appreciated but is not required.
16 * 2. Altered source versions must be plainly marked as such, and must not be
17 * misrepresented as being the original software.
18 * 3. This notice may not be removed or altered from any source distribution.
19 */
20
21 #include "setup/expression.hpp"
22
23 #include <stddef.h>
24 #include <cstring>
25 #include <vector>
26 #include <stdexcept>
27
28 #include "util/log.hpp"
29
30 namespace setup {
31
32 namespace {
33
is_identifier_start(char c)34 bool is_identifier_start(char c) {
35 return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '-';
36 }
37
is_identifier(char c)38 bool is_identifier(char c) {
39 return is_identifier_start(c) || (c >= '0' && c <= '9') || c == '\\';
40 }
41
42 struct evaluator {
43
44 const std::string & test;
45 const char * expr;
46
47 enum token_type {
48 end,
49 op_or,
50 op_and,
51 op_not,
52 paren_left,
53 paren_right,
54 identifier
55 } token;
56 const char * token_start;
57 size_t token_length;
58
evaluatorsetup::__anon61915e590111::evaluator59 evaluator(const std::string & expression, const std::string & variable)
60 : test(variable), expr(expression.c_str()), token(end) { }
61
nextsetup::__anon61915e590111::evaluator62 token_type next() {
63
64 // Ignore whitespace
65 while(*expr > 0 && *expr <= 32) {
66 expr++;
67 }
68
69 if(!*expr) {
70 return (token = end);
71
72 } else if(*expr == '(') {
73 return (expr++, token = paren_left);
74
75 } else if(*expr == ')') {
76 return (expr++, token = paren_right);
77
78 } else if(is_identifier_start(*expr)) {
79
80 const char * start = expr++;
81 while(is_identifier(*expr)) {
82 expr++;
83 }
84
85 if(expr - start == 3 && !memcmp(start, "not", 3)) {
86 return (token = op_not);
87 } else if(expr - start == 3 && !memcmp(start, "and", 3)) {
88 return (token = op_and);
89 } else if(expr - start == 2 && !memcmp(start, "or", 2)) {
90 return (token = op_or);
91 }
92
93 token_start = start;
94 token_length = size_t(expr - start);
95 return (token = identifier);
96
97 } else {
98 throw std::runtime_error(std::string("unexpected symbol: ") + *expr);
99 }
100 }
101
eval_identifiersetup::__anon61915e590111::evaluator102 bool eval_identifier(bool lazy) {
103 bool result = lazy || test.compare(0, std::string::npos, token_start, token_length) == 0;
104 next();
105 return result;
106 }
107
eval_factorsetup::__anon61915e590111::evaluator108 bool eval_factor(bool lazy) {
109 if(token == paren_left) {
110 next();
111 bool result = eval_expression(lazy);
112 if(token != paren_right) {
113 throw std::runtime_error("expected closing parenthesis");
114 }
115 next();
116 return result;
117 } else if(token == op_not) {
118 next();
119 return !eval_factor(lazy);
120 } else if(token == identifier) {
121 return eval_identifier(lazy);
122 } else {
123 throw std::runtime_error("unexpected token");
124 }
125 }
126
eval_termsetup::__anon61915e590111::evaluator127 bool eval_term(bool lazy) {
128 bool result = eval_factor(lazy);
129 while(token == op_and) {
130 next();
131 result = eval_factor(lazy || !result) && result;
132 }
133 return result;
134 }
135
eval_expressionsetup::__anon61915e590111::evaluator136 bool eval_expression(bool lazy, bool inner = true) {
137 bool result = eval_term(lazy);
138 if(result && !inner) {
139 return result;
140 }
141 while(token == op_or || token == identifier) {
142 if(token == op_or) {
143 next();
144 }
145 result = eval_term(lazy || result) || result;
146 if(result && !inner) {
147 return result;
148 }
149 }
150 return result;
151 }
152
evalsetup::__anon61915e590111::evaluator153 bool eval() {
154 next();
155 return eval_expression(false, false);
156 }
157
158 };
159
160 } // anonymous namespace
161
expression_match(const std::string & test,const std::string & expression)162 bool expression_match(const std::string & test, const std::string & expression) {
163 try {
164 return evaluator(expression, test).eval();
165 } catch(const std::runtime_error & error) {
166 log_warning << "Error evaluating \"" << expression << "\": " << error.what();
167 return true;
168 }
169 }
170
is_simple_expression(const std::string & expression)171 bool is_simple_expression(const std::string & expression) {
172 if(expression.empty()) {
173 return true;
174 }
175 const char * c = expression.c_str();
176 if(!is_identifier_start(*c)) {
177 return false;
178 }
179 while(*c) {
180 if(!is_identifier(*c)) {
181 return false;
182 }
183 c++;
184 }
185 return true;
186 }
187
188 } // namespace setup
189