1 /***************************************************************************
2  *  Copyright 2014 Marcelo Y. Matuda                                       *
3  *                                                                         *
4  *  This program is free software: you can redistribute it and/or modify   *
5  *  it under the terms of the GNU General Public License as published by   *
6  *  the Free Software Foundation, either version 3 of the License, or      *
7  *  (at your option) any later version.                                    *
8  *                                                                         *
9  *  This program is distributed in the hope that it will be useful,        *
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
12  *  GNU General Public License for more details.                           *
13  *                                                                         *
14  *  You should have received a copy of the GNU General Public License      *
15  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.  *
16  ***************************************************************************/
17 
18 #include "Equation.h"
19 
20 #include <cctype> /* isspace */
21 
22 #include "Exception.h"
23 #include "Log.h"
24 #include "Text.h"
25 
26 
27 
28 namespace {
29 
30 using namespace GS::TRMControlModel;
31 
32 const char        addChar = '+';
33 const char        subChar = '-';
34 const char       multChar = '*';
35 const char        divChar = '/';
36 const char rightParenChar = ')';
37 const char  leftParenChar = '(';
38 
39 FormulaSymbol formulaSymbol;
40 
41 class FormulaNodeParser {
42 public:
FormulaNodeParser(const std::string & s)43 	FormulaNodeParser(const std::string& s)
44 				: formulaSymbolMap_(formulaSymbol.codeMap)
45 				, s_(GS::Text::trim(s))
46 				, pos_(0)
47 				, symbolType_(SYMBOL_TYPE_INVALID) {
48 		if (s_.empty()) {
49 			THROW_EXCEPTION(GS::TRMControlModelException, "Formula expression parser error: Empty string.");
50 		}
51 		nextSymbol();
52 	}
53 
54 	FormulaNode_ptr parse();
55 private:
56 	enum SymbolType {
57 		SYMBOL_TYPE_INVALID,
58 		SYMBOL_TYPE_ADD,
59 		SYMBOL_TYPE_SUB,
60 		SYMBOL_TYPE_MULT,
61 		SYMBOL_TYPE_DIV,
62 		SYMBOL_TYPE_RIGHT_PAREN,
63 		SYMBOL_TYPE_LEFT_PAREN,
64 		SYMBOL_TYPE_STRING
65 	};
66 
67 	void throwException(const char* errorDescription) const;
68 	template<typename T> void throwException(const char* errorDescription, const T& complement) const;
finished() const69 	bool finished() const {
70 		return pos_ >= s_.size();
71 	}
72 	void nextSymbol();
73 	FormulaNode_ptr parseFactor();
74 	FormulaNode_ptr parseTerm();
75 	FormulaNode_ptr parseExpression();
76 	void skipSpaces();
77 
isSeparator(char c)78 	static bool isSeparator(char c) {
79 		switch (c) {
80 		case rightParenChar: return true;
81 		case leftParenChar:  return true;
82 		default:             return std::isspace(c) != 0;
83 		}
84 	}
85 
86 	const FormulaSymbol::CodeMap& formulaSymbolMap_;
87 	const std::string s_;
88 	std::string::size_type pos_;
89 	std::string symbol_;
90 	SymbolType symbolType_;
91 };
92 
93 void
throwException(const char * errorDescription) const94 FormulaNodeParser::throwException(const char* errorDescription) const
95 {
96 	THROW_EXCEPTION(GS::TRMControlModelException, "Formula expression parser error: "
97 				<< errorDescription
98 				<< " at position " << (pos_ - symbol_.size()) << " of string [" << s_ << "].");
99 }
100 
101 template<typename T>
102 void
throwException(const char * errorDescription,const T & complement) const103 FormulaNodeParser::throwException(const char* errorDescription, const T& complement) const
104 {
105 	THROW_EXCEPTION(GS::TRMControlModelException, "Formula expression parser error: "
106 				<< errorDescription << complement
107 				<< " at position " << (pos_ - symbol_.size()) << " of string [" << s_ << "].");
108 }
109 
110 void
skipSpaces()111 FormulaNodeParser::skipSpaces()
112 {
113 	while (!finished() && std::isspace(s_[pos_])) ++pos_;
114 }
115 
116 void
nextSymbol()117 FormulaNodeParser::nextSymbol()
118 {
119 	skipSpaces();
120 
121 	symbol_.resize(0);
122 
123 	if (finished()) {
124 		symbolType_ = SYMBOL_TYPE_INVALID;
125 		return;
126 	}
127 
128 	char c = s_[pos_++];
129 	symbol_ = c;
130 	switch (c) {
131 	case addChar:
132 		symbolType_ = SYMBOL_TYPE_ADD;
133 		break;
134 	case subChar:
135 		symbolType_ = SYMBOL_TYPE_SUB;
136 		break;
137 	case multChar:
138 		symbolType_ = SYMBOL_TYPE_MULT;
139 		break;
140 	case divChar:
141 		symbolType_ = SYMBOL_TYPE_DIV;
142 		break;
143 	case rightParenChar:
144 		symbolType_ = SYMBOL_TYPE_RIGHT_PAREN;
145 		break;
146 	case leftParenChar:
147 		symbolType_ = SYMBOL_TYPE_LEFT_PAREN;
148 		break;
149 	default:
150 		symbolType_ = SYMBOL_TYPE_STRING;
151 		while ( !finished() && !isSeparator(c = s_[pos_]) ) {
152 			symbol_ += c;
153 			++pos_;
154 		}
155 	}
156 }
157 
158 /*******************************************************************************
159  * FACTOR -> "(" EXPRESSION ")" | SYMBOL | CONST | ADD_OP FACTOR
160  */
161 FormulaNode_ptr
parseFactor()162 FormulaNodeParser::parseFactor()
163 {
164 	switch (symbolType_) {
165 	case SYMBOL_TYPE_LEFT_PAREN: // (expression)
166 	{
167 		nextSymbol();
168 		FormulaNode_ptr res(parseExpression());
169 		if (symbolType_ != SYMBOL_TYPE_RIGHT_PAREN) {
170 			throwException("Right parenthesis not found");
171 		}
172 		nextSymbol();
173 		return res;
174 	}
175 	case SYMBOL_TYPE_ADD: // unary plus
176 		nextSymbol();
177 		return parseFactor();
178 	case SYMBOL_TYPE_SUB: // unary minus
179 		nextSymbol();
180 		return FormulaNode_ptr(new FormulaMinusUnaryOp(parseFactor()));
181 	case SYMBOL_TYPE_STRING: // const / symbol
182 	{
183 		std::string symbolTmp = symbol_;
184 		nextSymbol();
185 		FormulaSymbol::CodeMap::const_iterator iter = formulaSymbolMap_.find(symbolTmp);
186 		if (iter == formulaSymbolMap_.end()) {
187 			// It's not a symbol.
188 			return FormulaNode_ptr(new FormulaConst(GS::Text::parseString<float>(symbolTmp)));
189 		} else {
190 			return FormulaNode_ptr(new FormulaSymbolValue(iter->second));
191 		}
192 	}
193 	case SYMBOL_TYPE_RIGHT_PAREN:
194 		throwException("Unexpected symbol: ", rightParenChar);
195 	case SYMBOL_TYPE_MULT:
196 		throwException("Unexpected symbol: ", multChar);
197 	case SYMBOL_TYPE_DIV:
198 		throwException("Unexpected symbol: ", divChar);
199 	default:
200 		throwException("Invalid symbol");
201 	}
202 	return FormulaNode_ptr(); // unreachable
203 }
204 
205 /*******************************************************************************
206  * TERM -> FACTOR { MULT_OP FACTOR }
207  */
parseTerm()208 FormulaNode_ptr FormulaNodeParser::parseTerm()
209 {
210 	FormulaNode_ptr term1 = parseFactor();
211 
212 	SymbolType type = symbolType_;
213 	while (type == SYMBOL_TYPE_MULT || type == SYMBOL_TYPE_DIV) {
214 		nextSymbol();
215 		FormulaNode_ptr term2 = parseFactor();
216 		FormulaNode_ptr expr;
217 		if (type == SYMBOL_TYPE_MULT) {
218 			expr.reset(new FormulaMultBinaryOp(std::move(term1), std::move(term2)));
219 		} else {
220 			expr.reset(new FormulaDivBinaryOp(std::move(term1), std::move(term2)));
221 		}
222 		term1.swap(expr);
223 		type = symbolType_;
224 	}
225 
226 	return term1;
227 }
228 
229 /*******************************************************************************
230  * EXPRESSION -> TERM { ADD_OP TERM }
231  */
232 FormulaNode_ptr
parseExpression()233 FormulaNodeParser::parseExpression()
234 {
235 	FormulaNode_ptr term1 = parseTerm();
236 
237 	SymbolType type = symbolType_;
238 	while (type == SYMBOL_TYPE_ADD || type == SYMBOL_TYPE_SUB) {
239 
240 		nextSymbol();
241 		FormulaNode_ptr term2 = parseTerm();
242 
243 		FormulaNode_ptr expr;
244 		if (type == SYMBOL_TYPE_ADD) {
245 			expr.reset(new FormulaAddBinaryOp(std::move(term1), std::move(term2)));
246 		} else {
247 			expr.reset(new FormulaSubBinaryOp(std::move(term1), std::move(term2)));
248 		}
249 
250 		term1 = std::move(expr);
251 		type = symbolType_;
252 	}
253 
254 	return term1;
255 }
256 
257 /*******************************************************************************
258  *
259  */
260 FormulaNode_ptr
parse()261 FormulaNodeParser::parse()
262 {
263 	FormulaNode_ptr formulaRoot = parseExpression();
264 	if (symbolType_ != SYMBOL_TYPE_INVALID) { // there is a symbol available
265 		throwException("Invalid text");
266 	}
267 	return formulaRoot;
268 }
269 
270 } /* namespace */
271 
272 //==============================================================================
273 
274 namespace GS {
275 namespace TRMControlModel {
276 
277 float
eval(const FormulaSymbolList & symbolList) const278 FormulaMinusUnaryOp::eval(const FormulaSymbolList& symbolList) const
279 {
280 	return -(child_->eval(symbolList));
281 }
282 
283 void
print(std::ostream & out,int level) const284 FormulaMinusUnaryOp::print(std::ostream& out, int level) const
285 {
286 	std::string prefix(level * 8, ' ');
287 	out << prefix << "- [\n";
288 
289 	child_->print(out, level + 1);
290 
291 	out << prefix << "]" << std::endl;
292 }
293 
294 float
eval(const FormulaSymbolList & symbolList) const295 FormulaAddBinaryOp::eval(const FormulaSymbolList& symbolList) const
296 {
297 	return child1_->eval(symbolList) + child2_->eval(symbolList);
298 }
299 
300 void
print(std::ostream & out,int level) const301 FormulaAddBinaryOp::print(std::ostream& out, int level) const
302 {
303 	std::string prefix(level * 8, ' ');
304 	out << prefix << "+ [\n";
305 
306 	child1_->print(out, level + 1);
307 	child2_->print(out, level + 1);
308 
309 	out << prefix << "]" << std::endl;
310 }
311 
312 float
eval(const FormulaSymbolList & symbolList) const313 FormulaSubBinaryOp::eval(const FormulaSymbolList& symbolList) const
314 {
315 	return child1_->eval(symbolList) - child2_->eval(symbolList);
316 }
317 
318 void
print(std::ostream & out,int level) const319 FormulaSubBinaryOp::print(std::ostream& out, int level) const
320 {
321 	std::string prefix(level * 8, ' ');
322 	out << prefix << "- [\n";
323 
324 	child1_->print(out, level + 1);
325 	child2_->print(out, level + 1);
326 
327 	out << prefix << "]" << std::endl;
328 }
329 
330 float
eval(const FormulaSymbolList & symbolList) const331 FormulaMultBinaryOp::eval(const FormulaSymbolList& symbolList) const
332 {
333 	return child1_->eval(symbolList) * child2_->eval(symbolList);
334 }
335 
336 void
print(std::ostream & out,int level) const337 FormulaMultBinaryOp::print(std::ostream& out, int level) const
338 {
339 	std::string prefix(level * 8, ' ');
340 	out << prefix << "* [\n";
341 
342 	child1_->print(out, level + 1);
343 	child2_->print(out, level + 1);
344 
345 	out << prefix << "]" << std::endl;
346 }
347 
348 float
eval(const FormulaSymbolList & symbolList) const349 FormulaDivBinaryOp::eval(const FormulaSymbolList& symbolList) const
350 {
351 	return child1_->eval(symbolList) / child2_->eval(symbolList);
352 }
353 
354 void
print(std::ostream & out,int level) const355 FormulaDivBinaryOp::print(std::ostream& out, int level) const
356 {
357 	std::string prefix(level * 8, ' ');
358 	out << prefix << "/ [\n";
359 
360 	child1_->print(out, level + 1);
361 	child2_->print(out, level + 1);
362 
363 	out << prefix << "]" << std::endl;
364 }
365 
366 float
eval(const FormulaSymbolList &) const367 FormulaConst::eval(const FormulaSymbolList& /*symbolList*/) const
368 {
369 	return value_;
370 }
371 
372 void
print(std::ostream & out,int level) const373 FormulaConst::print(std::ostream& out, int level) const
374 {
375 	out << std::string(level * 8, ' ') << "const=" << value_ << std::endl;
376 }
377 
378 float
eval(const FormulaSymbolList & symbolList) const379 FormulaSymbolValue::eval(const FormulaSymbolList& symbolList) const
380 {
381 	return symbolList[symbol_];
382 }
383 
384 void
print(std::ostream & out,int level) const385 FormulaSymbolValue::print(std::ostream& out, int level) const
386 {
387 	out << std::string(level * 8, ' ') << "symbol=" << symbol_ << std::endl;
388 }
389 
390 /*******************************************************************************
391  *
392  */
393 void
setFormula(const std::string & formula)394 Equation::setFormula(const std::string& formula)
395 {
396 	FormulaNodeParser p(formula);
397 	FormulaNode_ptr tempFormulaRoot = p.parse();
398 
399 	formula_ = formula;
400 	std::swap(tempFormulaRoot, formulaRoot_);
401 }
402 
403 /*******************************************************************************
404  *
405  */
406 float
evalFormula(const FormulaSymbolList & symbolList) const407 Equation::evalFormula(const FormulaSymbolList& symbolList) const
408 {
409 	if (!formulaRoot_) {
410 		THROW_EXCEPTION(InvalidStateException, "Empty formula.");
411 	}
412 
413 	return formulaRoot_->eval(symbolList);
414 }
415 
416 /*******************************************************************************
417  *
418  */
419 std::ostream&
operator <<(std::ostream & out,const Equation & equation)420 operator<<(std::ostream& out, const Equation& equation)
421 {
422 	if (equation.formulaRoot_) {
423 		equation.formulaRoot_->print(out);
424 	}
425 	return out;
426 }
427 
428 } /* namespace TRMControlModel */
429 } /* namespace GS */
430