File failed to load: https://isis.astrogeology.usgs.gov/6.0.0/Object/assets/jax/output/NativeMML/config.js
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 
15 using namespace std;
16 
17 namespace Isis {
19  InfixToPostfix::InfixToPostfix() {
20  initialize();
21  }
22 
23  InfixToPostfix::~InfixToPostfix() {
24  uninitialize();
25  }
26 
31  void InfixToPostfix::initialize() {
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 
89  void InfixToPostfix::uninitialize() {
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 &) {
202  throw IException(IException::User,
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 == "(") {
228  throw IException(IException::User,
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) {
332  throw IException(IException::User,
333  "There are too many closing parentheses (')') in the equation.",
334  _FILEINFO_);
335  }
336  }
337 
347  InfixOperator *InfixToPostfix::findOperator(QString representation) {
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(" ") != ")") {
522  throw IException(IException::User,
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(" ") != "(") {
539  throw IException(IException::User,
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() != "--") {
558  throw IException(IException::User,
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 == "") {
597  throw IException(IException::User,
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 == "") {
633  throw IException(IException::User,
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()) {
659  throw IException(IException::User,
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) {
678  throw IException(IException::User,
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 == "") {
698  throw IException(IException::User,
699  "Argument " + toString(argNum + 1) + " in function " + funcName + " must not be empty.",
700  _FILEINFO_);
701  }
702  }
703 }
Isis::IString::DownCase
IString DownCase()
Converts all upper case letters in the object IString into lower case characters.
Definition: IString.cpp:644
Isis::InfixFunction
InfixOperator and InfixFunction are helper classes for InfixToPostfix.
Definition: InfixToPostfix.h:130
Isis::toString
QString toString(bool boolToConvert)
Global function to convert a boolean to a string.
Definition: IString.cpp:211
Isis::InfixOperator
InfixOperator and InfixFunction are helper classes for InfixToPostfix.
Definition: InfixToPostfix.h:81
Isis::IException
Isis exception class.
Definition: IException.h:91
Isis::toDouble
double toDouble(const QString &string)
Global function to convert from a string to a double.
Definition: IString.cpp:149
std
Namespace for the standard library.
Isis::IString::Token
IString Token(const IString &separator)
Returns the first token in the IString.
Definition: IString.cpp:897
Isis::IString
Adds specific functionality to C++ strings.
Definition: IString.h:165
Isis::IString::Remove
IString Remove(const std::string &del)
Remove all instances of any character in the string from the IString.
Definition: IString.cpp:1266
Isis
This is free and unencumbered software released into the public domain.
Definition: Apollo.h:16
Isis::IString::ToQt
QString ToQt() const
Retuns the object string as a QString.
Definition: IString.cpp:869

U.S. Department of the Interior | U.S. Geological Survey
ISIS | Privacy & Disclaimers | Astrogeology Research Program
To contact us, please post comments and questions on the USGS Astrogeology Discussion Board
To report a bug, or suggest a feature go to: ISIS Github
File Modified: 07/13/2023 15:16:41