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