Isis 3 Programmer Reference
InfixToPostfix.cpp
1
6/* SPDX-License-Identifier: CC0-1.0 */
7
8#include "InfixToPostfix.h"
9
10#include <iostream>
11
12#include "IException.h"
13#include "IString.h"
14
15using namespace std;
16
17namespace Isis {
22
23 InfixToPostfix::~InfixToPostfix() {
25 }
26
32 p_operators.push_back(new InfixOperator(7, "^"));
33 p_operators.push_back(new InfixOperator(5, "/"));
34 p_operators.push_back(new InfixOperator(5, "*"));
35 p_operators.push_back(new InfixOperator(3, "<<"));
36 p_operators.push_back(new InfixOperator(3, ">>"));
37 p_operators.push_back(new InfixOperator(2, "+"));
38 p_operators.push_back(new InfixOperator(2, "-"));
39 p_operators.push_back(new InfixOperator(1, ">"));
40 p_operators.push_back(new InfixOperator(1, "<"));
41 p_operators.push_back(new InfixOperator(1, ">="));
42 p_operators.push_back(new InfixOperator(1, "<="));
43 p_operators.push_back(new InfixOperator(1, "=="));
44 p_operators.push_back(new InfixOperator(1, "!="));
45 p_operators.push_back(new InfixOperator(-1, "("));
46
47 // This makes multiple argument functions work
48 p_operators.push_back(new InfixOperator(-1, ","));
49
50 p_operators.push_back(new InfixFunction("--", 1));
51 p_operators.push_back(new InfixFunction("neg", 1));
52 p_operators.push_back(new InfixFunction("sqrt", 1));
53 p_operators.push_back(new InfixFunction("abs", 1));
54 p_operators.push_back(new InfixFunction("sin", 1));
55 p_operators.push_back(new InfixFunction("cos", 1));
56 p_operators.push_back(new InfixFunction("tan", 1));
57 p_operators.push_back(new InfixFunction("csc", 1));
58 p_operators.push_back(new InfixFunction("sec", 1));
59 p_operators.push_back(new InfixFunction("cot", 1));
60 p_operators.push_back(new InfixFunction("asin", 1));
61 p_operators.push_back(new InfixFunction("acos", 1));
62 p_operators.push_back(new InfixFunction("atan", 1));
63 p_operators.push_back(new InfixFunction("atan2", 2));
64 p_operators.push_back(new InfixFunction("sinh", 1));
65 p_operators.push_back(new InfixFunction("cosh", 1));
66 p_operators.push_back(new InfixFunction("tanh", 1));
67 p_operators.push_back(new InfixFunction("asinh", 1));
68 p_operators.push_back(new InfixFunction("acosh", 1));
69 p_operators.push_back(new InfixFunction("atanh", 1));
70 p_operators.push_back(new InfixFunction("log", 1));
71 p_operators.push_back(new InfixFunction("log10", 1));
72 p_operators.push_back(new InfixFunction("ln", 1));
73 p_operators.push_back(new InfixFunction("degs", 1));
74 p_operators.push_back(new InfixFunction("rads", 1));
75 p_operators.push_back(new InfixFunction("linemin", 1));
76 p_operators.push_back(new InfixFunction("linemax", 1));
77 p_operators.push_back(new InfixFunction("min", 2));
78 p_operators.push_back(new InfixFunction("max", 2));
79 p_operators.push_back(new InfixFunction("line", 0));
80 p_operators.push_back(new InfixFunction("sample", 0));
81 p_operators.push_back(new InfixFunction("band", 0));
82 p_operators.push_back(new InfixFunction("pi", 0));
83 p_operators.push_back(new InfixFunction("e", 0));
84 }
85
90 for(int i = 0; i < p_operators.size(); i ++) {
91 delete p_operators[i];
92 }
93
94 p_operators.clear();
95 }
96
109 QString InfixToPostfix::cleanSpaces(QString equation) {
110 IString equationIStr = equation;
111 IString clean = "";
112 while(!equationIStr.empty()) {
113 IString data = equationIStr.Token(" ");
114 if(data.empty()) {
115 continue;
116 }
117
118 if(clean.empty()) {
119 clean = data;
120 }
121 else {
122 clean += " " + data;
123 }
124 }
125
126 return clean.ToQt();
127 }
128
139 QString InfixToPostfix::convert(const QString &infix) {
140 // Prep our equation for the conversion
141 IString equation = tokenizeEquation(infix);
142 IString postfix = "";
143
144 // The algorithm uses a stack
145 std::stack<InfixOperator> theStack;
146
147 // Use this to look for two operands in a row. If we find such,
148 // then we know the infix equation is bogus.
149 int numConsecutiveOperands = 0;
150
151 // Use this to look for two operators in a row. If we find such,
152 // then we know the infix equation is bogus.
153 int numConsecutiveOperators = 0;
154
155 // We'll use tokens to get through the entire equation, which is space-delimited
156 // because of TokenizeEquation. So continue processing until we're out of tokens
157 // and the string is empty.
158 while(!equation.empty()) {
159
160 // There will be no empty tokens, so don't worry about checking for it.
161 // TokenizeEquation cleans excess spaces in it's return value.
162 QString data = equation.Token(" ").ToQt();
163
164 if(data.compare("(") == 0) {
165 theStack.push(*findOperator(data));
166 }
167 else if(data.compare(")") == 0) {
168 QString postfixQStr = postfix.ToQt();
169 closeParenthesis(postfixQStr, theStack);
170 postfix = postfixQStr;
171 }
172 else if(isKnownSymbol(data)) {
173 QString postfixQStr = postfix.ToQt();
174 addOperator(postfixQStr, *findOperator(data), theStack);
175 postfix = postfixQStr;
176
177 if(isFunction(data)) {
178 // For a general check, zero single argument functions the
179 // same as an operand.
180 if(((InfixFunction *)findOperator(data))->argumentCount() == 0) {
181 numConsecutiveOperators = 0;
182 numConsecutiveOperands ++;
183 }
184 else {
185 // We have a function with arguments, an operand or function call is expected next
186 numConsecutiveOperators = 1;
187 numConsecutiveOperands = 0;
188 }
189 }
190 else {
191 // We found an operator, not a function, so we expect a number next
192 numConsecutiveOperators ++;
193 numConsecutiveOperands = 0;
194 }
195 }
196 else {
197 try {
198 // Make sure this is truly an operand and not an operator by casting it
199 toDouble(data);
200 }
201 catch(IException &) {
203 "The operator '" + data + "' is not recognized.",
204 _FILEINFO_);
205 }
206
207 // This was clearly an operand at this point
208 numConsecutiveOperators = 0;
209 numConsecutiveOperands ++;
210
211 postfix += IString(' ' + data + ' ');
212 }
213
214 // If we found consecutive operators or operands, tell the user
215 if(numConsecutiveOperators > 1) {
216 throw IException(IException::User, "Missing an operand near the operator '" + data + "'.", _FILEINFO_);
217 }
218 else if(numConsecutiveOperands > 1) {
219 throw IException(IException::User, "Missing an operator before " + data + ".", _FILEINFO_);
220 }
221 }
222
223 while(!theStack.empty()) {
224 IString op = theStack.top().outputString();
225
226 // Any opening parentheses here are invalid at this point
227 if(op == "(") {
229 "There are too many opening parentheses ('(') in the equation.",
230 _FILEINFO_);
231 }
232
233 postfix += ' ' + op + ' ';
234 theStack.pop();
235 }
236
237 // The , is set to an operator that causes multiple-argument functions to work, but
238 // it needs to be stripped out for postfix. This is the correct way to do it.
239 postfix = postfix.Remove(",");
240
241 // Clean spaces just to double check and return our postfix answer
242 return cleanSpaces(postfix.ToQt());
243 }
244
253 bool InfixToPostfix::isKnownSymbol(QString representation) {
254 for(int i = 0; i < p_operators.size(); i++) {
255 if(representation.compare(p_operators[i]->inputString()) == 0) {
256 return true;
257 }
258 }
259
260 return false;
261 }
262
270 bool InfixToPostfix::isFunction(QString representation) {
271 if(isKnownSymbol(representation)) {
272 return findOperator(representation)->isFunction();
273 }
274 else {
275 return false;
276 }
277 }
278
287 void InfixToPostfix::addOperator(QString &postfix, const InfixOperator &op, std::stack<InfixOperator> &theStack) {
288 while(!theStack.empty()) {
289 InfixOperator top = theStack.top();
290 theStack.pop();
291
292 if(top.inputString().compare("(") == 0) {
293 theStack.push(top);
294 break;
295 }
296
297 if(top.precedence() < op.precedence()) {
298 theStack.push(top);
299 break;
300 }
301 else {
302 postfix += ' ' + top.outputString() + ' ';
303 }
304 }
305
306 theStack.push(op);
307 }
308
316 void InfixToPostfix::closeParenthesis(QString &postfix, std::stack<InfixOperator> &theStack) {
317 bool openingFound = false;
318 while(!theStack.empty()) {
319 InfixOperator op = theStack.top();
320 theStack.pop();
321
322 if(op.inputString().compare("(") == 0) {
323 openingFound = true;
324 break;
325 }
326 else {
327 postfix += ' ' + op.outputString() + ' ';
328 }
329 }
330
331 if(!openingFound) {
333 "There are too many closing parentheses (')') in the equation.",
334 _FILEINFO_);
335 }
336 }
337
348 for(int i = 0; i < p_operators.size(); i++) {
349 if(representation.compare(p_operators[i]->inputString()) == 0) {
350 return p_operators[i];
351 }
352 }
353
354 // Nothing found
355 throw IException(IException::User, "The operator '" + representation + "' is not recognized.", _FILEINFO_);
356 }
357
369 QString InfixToPostfix::tokenizeEquation(const QString &equation) {
370 IString output = "";
371
372 // Insert whitespace, make everything lowercase, and change all braces to
373 // parenthesis
374 for(int i = 0; i < equation.size(); i++) {
375 // Ensure there is whitespace in the equation
376 if(!equation[i].isLetterOrNumber() && !equation[i].isSpace() &&
377 equation[i] != '.' && equation[i] != '_') {
378 // Convert all braces to parens
379 if(equation[i] == '[' || equation[i] == '{') {
380 output += " ( ";
381 }
382 else if(equation[i] == ']' || equation[i] == '}') {
383 output += " ) ";
384 }
385 // Test for multicharacter operators
386 else if(i < equation.size() - 1 && equation[i] == '-' && equation[i+1] == '-') {
387 output += " -- ";
388 i++;
389 }
390 else if(i < equation.size() - 1 && equation[i] == '<' && equation[i+1] == '<') {
391 output += " << ";
392 i++;
393 }
394 else if(i < equation.size() - 1 && equation[i] == '>' && equation[i+1] == '>') {
395 output += " >> ";
396 i++;
397 }
398 else if(i < equation.size() - 1 && equation[i] == '>' && equation[i+1] == '=') {
399 output += " >= ";
400 i++;
401 }
402 else if(i < equation.size() - 1 && equation[i] == '<' && equation[i+1] == '=') {
403 output += " <= ";
404 i++;
405 }
406 else if(i < equation.size() - 1 && equation[i] == '=' && equation[i+1] == '=') {
407 output += " == ";
408 i++;
409 }
410 else if(i < equation.size() - 1 && equation[i] == '!' && equation[i+1] == '=') {
411 output += " != ";
412 i++;
413 }
414 else if(i < equation.size() - 1 && equation[i] == '|' && equation[i+1] == '|') {
415 output += " || ";
416 i++;
417 }
418 else if(i < equation.size() - 1 && equation[i] == '&' && equation[i+1] == '&') {
419 output += " && ";
420 i++;
421 }
422 // Take care of scientific notiation where the exponent is negative
423 else if((i > 1) && equation[i] == '-' && equation[i-1].toLower() == 'e' && equation[i-2].isLetterOrNumber()) {
424 output += equation[i].toLatin1();
425 }
426 // Look for negative operator disguised as '-'
427 else if(equation[i] == '-') {
428 bool isNegative = true;
429
430 // If we run into a '(' or the beginning, then the '-' must mean '--' really.
431 for(int index = i - 1; index >= 0; index --) {
432 if(equation[index] == ' ') {
433 continue;
434 }
435
436 if(equation[index] != '(' && equation[index] != '/' &&
437 equation[index] != '*' && equation[index] != '+') {
438 isNegative = false;
439 break;
440 }
441
442 break;
443 }
444
445 if(isNegative) {
446 output += " -- ";
447 }
448 else {
449 output += " - ";
450 }
451 }
452 else {
453 output += ' ';
454 output += equation[i].toLatin1();
455 output += ' ';
456 }
457 }
458 else {
459 output += equation[i].toLatin1();
460 }
461 }
462
463 QString cleanedEquation = cleanSpaces(formatFunctionCalls(output.DownCase().ToQt()));
464
465 return cleanedEquation;
466 }
467
479 QString InfixToPostfix::formatFunctionCalls(QString equation) {
480 // Clean our space-delimited equation
481 equation = cleanSpaces(equation);
482 QString output = "";
483
484 // We'll use tokens to get through the entire equation, which is space-delimited.
485 // So continue processing until we're out of tokens and the string is empty.
486 while(!equation.isEmpty()) {
487 IString tmp = equation;
488
489 QString element = tmp.Token(" ").ToQt();
490 equation = tmp.ToQt();
491
492 // Did we find a function? Figure out what it is!
493 if(isFunction(element)) {
494 // Point to the function. We know if IsFunction returned true,
495 // the result is really a InfixFunction*
496 InfixFunction *func = (InfixFunction *)findOperator(element);
497
498 // We want to wrap the entire thing in parentheses, and it's argument string.
499 // So sin(.5)^2 becomes (sin(.5))^2
500 output += " ( " + func->inputString() + " (";
501
502 // Deal with 0-argument functions
503 if(func->argumentCount() == 0) {
504 IString tmp = equation;
505 QString next = tmp.Token(" ").ToQt();
506 equation = tmp.ToQt();
507
508 // If they didn't add parentheses around the zero-argument
509 // function, we still know what they mean. Close the arguments
510 // and the wrapping parentheses.
511 if(next != "(") {
512 output += " ) ) ";
513
514 // Step back, we did too much
515 equation = next + " " + equation;
516 }
517 else {
518 IString tmp = equation;
519 // We see a zero-arg function, and we grabbed an open parenthesis from it.
520 // Make sure the next thing is a close or we have a problem.
521 if(tmp.Token(" ") != ")") {
523 "The function " + func->inputString() + " should not have any arguments.",
524 _FILEINFO_);
525 }
526 equation = tmp.ToQt();
527
528 // Close the arguments and the wrapping parentheses. They wrote their call correct :)
529 output += " ) ) ";
530 }
531 }
532 else {
533 // Deal with 1+ argument functions by parsing out the arguments
534
535 IString tmp = equation;
536
537 // Make sure the user put parentheses around these, otherwise we're left in the dark.
538 if (func->argumentCount() > 1 && tmp.Token(" ") != "(") {
540 "Missing parenthesis after " + func->inputString(),
541 _FILEINFO_);
542 }
543
544 equation = tmp.ToQt();
545
546 // Single argument missing parenthesis?
547 if(func->argumentCount() == 1) {
548
549 IString tmp = equation;
550 QString argument = tmp.Token(" ").ToQt();
551 equation = tmp.ToQt();
552
553 if(argument != "(") {
554 // We might have a problem. They're calling a function without adding parentheses....
555 // unless it's a negate, because we insert those, tell them their mistake. It's not
556 // my job to figure out what they mean!
557 if(func->inputString() != "--") {
559 "Missing parenthesis after " + func->inputString(),
560 _FILEINFO_);
561 }
562
563 // It's a negate without parentheses, so they mean the next term?
564 if(!isFunction(argument)) {
565 // If it isn't a function, it's safe to just append
566 output += " " + formatFunctionCalls(argument) + " ) ) ";
567 continue;
568 }
569 else {
570 // We are negating a function result. We must do a mini-parse to figure out
571 // the function and resursively call, then append the negation.
572 QString functionName = argument;
573
574 IString tmp = equation;
575 QString openParen = tmp.Token(" ").ToQt();
576 equation = tmp.ToQt();
577
578 // No open parens? Call ourself again with this supposed function
579 if(openParen != "(") {
580 output += " " + formatFunctionCalls(functionName) + " ) ) ";
581 equation = openParen + " " + equation;
582 continue;
583 }
584 else {
585 functionName += " (";
586 }
587
588 // Parse out our equation quickly, call ourself with it to properly handle it,
589 // and append the negation.
590 int numParens = 0;
591 while(numParens > -1) {
592 IString tmp = equation;
593 QString newElem = tmp.Token(" ").ToQt();
594 equation = tmp.ToQt();
595
596 if(newElem == "") {
598 "Missing closing parentheses after '" + argument + "'.",
599 _FILEINFO_);
600 }
601
602 if(newElem == "(") numParens++;
603 else if(newElem == ")") numParens--;
604
605 functionName += " " + newElem;
606 }
607
608 output += " " + formatFunctionCalls(functionName) + " ) ) ";
609 continue;
610 }
611 }
612 }
613
623 QString argument = "";
624 int numParens = 0;
625 int argNum = 0;
626 while(argNum < func->argumentCount()) {
627 IString tmp = equation;
628 QString elem = tmp.Token(" ").ToQt();
629 equation = tmp.ToQt();
630
631 // Ran out of data, the function call is not complete.
632 if(elem == "") {
634 "The definition of '" + func->inputString() + "' is not complete.",
635 _FILEINFO_);
636 }
637
638 if(elem == "(") {
639 numParens ++;
640 argument += " (";
641 }
642 else if(elem == ")") {
643 numParens --;
644
645 // Ignore last close, it's not part of the argument and will be added later
646 if(numParens != -1) {
647 argument += " )";
648 }
649 }
650 else if(elem == "," && numParens == 0) {
651 checkArgument(func->inputString(), argNum, argument);
652 argument = formatFunctionCalls(argument);
653 output += " ( " + argument + " ) , ";
654 argNum++;
655 argument = "";
656
657 // Too many arguments? We don't expect a comma delimiter on the last argument.
658 if(argNum == func->argumentCount()) {
660 "There were too many arguments supplied to the function '" + func->inputString() + "'.",
661 _FILEINFO_);
662 }
663 }
664 else {
665 argument += " " + elem;
666 }
667
668 if(argNum == func->argumentCount() - 1 && numParens == -1) {
669 checkArgument(func->inputString(), argNum, argument);
670 argument = formatFunctionCalls(argument);
671 // close function call & function wrap
672 output += " " + argument + " ) ) ";
673 argNum++;
674 argument = "";
675 }
676 // Closed the function early?
677 else if(numParens == -1) {
679 "There were not enough arguments supplied to the function '" + func->inputString() + "'.",
680 _FILEINFO_);
681 }
682 }
683 }
684 }
685 // Not a function, preserve & ignore
686 else {
687 output = output + " " + element;
688 }
689 }
690
691 return output;
692 }
693
694 void InfixToPostfix::checkArgument(QString funcName, int argNum, QString argument) {
695 argument = argument.remove(QRegExp("[ ()]"));
696
697 if(argument == "") {
699 "Argument " + toString(argNum + 1) + " in function " + funcName + " must not be empty.",
700 _FILEINFO_);
701 }
702 }
703}
Isis exception class.
Definition IException.h:91
@ User
A type of error that could only have occurred due to a mistake on the user's part (e....
Definition IException.h:126
Adds specific functionality to C++ strings.
Definition IString.h:165
IString Token(const IString &separator)
Returns the first token in the IString.
Definition IString.cpp:897
QString ToQt() const
Retuns the object string as a QString.
Definition IString.cpp:869
InfixOperator and InfixFunction are helper classes for InfixToPostfix.
InfixOperator and InfixFunction are helper classes for InfixToPostfix.
virtual InfixOperator * findOperator(QString representation)
This method will return a pointer to the operator represented by 'representation.
QString convert(const QString &infix)
This method converts infix to postfix.
bool isFunction(QString representation)
This method will return true if 'representation' is a known function.
QString cleanSpaces(QString equation)
This function takes a space-delimited string and removes empty delimiters.
void initialize()
This populates the known operators/functions list.
virtual bool isKnownSymbol(QString representation)
This method will return true if it believes the argument represents a valid function or operator.
void uninitialize()
This cleans the known operators/functions list.
InfixToPostfix()
Constructor.
QString tokenizeEquation(const QString &equation)
This method will add spaces between all operators and numbers, making it possible to get each element...
void closeParenthesis(QString &postfix, std::stack< InfixOperator > &theStack)
This is straight from the algorithm found on page 159 of "Data Structures & Algorithms in Java" Secon...
void addOperator(QString &postfix, const InfixOperator &op, std::stack< InfixOperator > &theStack)
This is straight from the algorithm found on page 159 of "Data Structures & Algorithms in Java" Secon...
QString formatFunctionCalls(QString equation)
This method looks through equation for function calls, parenthesizes them, and calls itself again wit...
This is free and unencumbered software released into the public domain.
Definition Apollo.h:16
QString toString(bool boolToConvert)
Global function to convert a boolean to a string.
Definition IString.cpp:211
double toDouble(const QString &string)
Global function to convert from a string to a double.
Definition IString.cpp:149
Namespace for the standard library.