1 /**
2  * @file    FormulaFormatter.cpp
3  * @brief   Formats an AST formula tree as an SBML formula string.
4  * @author  Ben Bornstein
5  *
6  * <!--------------------------------------------------------------------------
7  * This file is part of libSBML.  Please visit http://sbml.org for more
8  * information about SBML, and the latest version of libSBML.
9  *
10  * Copyright (C) 2020 jointly by the following organizations:
11  *     1. California Institute of Technology, Pasadena, CA, USA
12  *     2. University of Heidelberg, Heidelberg, Germany
13  *     3. University College London, London, UK
14  *
15  * Copyright (C) 2019 jointly by the following organizations:
16  *     1. California Institute of Technology, Pasadena, CA, USA
17  *     2. University of Heidelberg, Heidelberg, Germany
18  *
19  * Copyright (C) 2013-2018 jointly by the following organizations:
20  *     1. California Institute of Technology, Pasadena, CA, USA
21  *     2. EMBL European Bioinformatics Institute (EMBL-EBI), Hinxton, UK
22  *     3. University of Heidelberg, Heidelberg, Germany
23  *
24  * Copyright (C) 2009-2013 jointly by the following organizations:
25  *     1. California Institute of Technology, Pasadena, CA, USA
26  *     2. EMBL European Bioinformatics Institute (EMBL-EBI), Hinxton, UK
27  *
28  * Copyright (C) 2006-2008 by the California Institute of Technology,
29  *     Pasadena, CA, USA
30  *
31  * Copyright (C) 2002-2005 jointly by the following organizations:
32  *     1. California Institute of Technology, Pasadena, CA, USA
33  *     2. Japan Science and Technology Agency, Japan
34  *
35  * This library is free software; you can redistribute it and/or modify it
36  * under the terms of the GNU Lesser General Public License as published by
37  * the Free Software Foundation.  A copy of the license agreement is provided
38  * in the file named "LICENSE.txt" included with this software distribution and
39  * also available online as http://sbml.org/software/libsbml/license.html
40  * ---------------------------------------------------------------------- -->*/
41 
42 #include <sbml/common/common.h>
43 #include <sbml/math/FormulaFormatter.h>
44 #include <sbml/math/ASTNodeType.h>
45 
46 #include <sbml/util/util.h>
47 LIBSBML_CPP_NAMESPACE_BEGIN
48 
49 /**
50  * @if conly
51  * @memberof ASTNode_t
52  * @endif
53  */
54 LIBSBML_EXTERN
55 char *
SBML_formulaToString(const ASTNode_t * tree)56 SBML_formulaToString (const ASTNode_t *tree)
57 {
58   char           *s;
59 
60   if (tree == NULL)
61   {
62     s = NULL;
63   }
64   else
65   {
66     StringBuffer_t *sb = StringBuffer_create(128);
67 
68     FormulaFormatter_visit(NULL, tree, sb);
69     s = StringBuffer_getBuffer(sb);
70     safe_free(sb);
71   }
72   return s;
73 }
74 
75 
76 /**
77  * @cond doxygenLibsbmlInternal
78  * The rest of this file is internal code.
79  */
80 
81 
82 /**
83  * @return true (nonzero) if the given ASTNode is to formatted as a
84  * function.
85  */
86 int
FormulaFormatter_isFunction(const ASTNode_t * node)87 FormulaFormatter_isFunction (const ASTNode_t *node)
88 {
89   return
90     ASTNode_isFunction  (node) ||
91     ASTNode_isLambda    (node) ||
92     ASTNode_isLogical   (node) ||
93     ASTNode_isRelational(node);
94 }
95 
96 
97 /**
98  * @return true (nonzero) if the given child ASTNode should be grouped
99  * (with parenthesis), false (0) otherwise.
100  *
101  * A node should be group if it is not an argument to a function and
102  * either:
103  *
104  *   - The parent node has higher precedence than the child, or
105  *
106  *   - If parent node has equal precedence with the child and the child is
107  *     to the right.  In this case, operator associativity and right-most
108  *     AST derivation enforce the grouping.
109  */
110 int
FormulaFormatter_isGrouped(const ASTNode_t * parent,const ASTNode_t * child)111 FormulaFormatter_isGrouped (const ASTNode_t *parent, const ASTNode_t *child)
112 {
113   int pp, cp;
114   int pt, ct;
115   int group = 0;
116 
117 
118   if (parent != NULL)
119   {
120     if (!FormulaFormatter_isFunction(parent))
121     {
122       pp = ASTNode_getPrecedence(parent);
123       cp = ASTNode_getPrecedence(child);
124 
125       if (pp > cp)
126       {
127         group = 1;
128       }
129       else if (pp == cp)
130       {
131         /**
132          * Group only if i) child is to the right and ii) both parent and
133          * child are either not the same, or if they are the same, they
134          * should be non-associative operators (i.e. AST_MINUS or
135          * AST_DIVIDE).  That is, do not group a parent and right child
136          * that are either both AST_PLUS or both AST_TIMES operators.
137          */
138         if (ASTNode_getRightChild(parent) == child)
139         {
140           pt = ASTNode_getType(parent);
141           ct = ASTNode_getType(child);
142 
143           group = ((pt != ct) || (pt == AST_MINUS || pt == AST_DIVIDE));
144         }
145       }
146     }
147   }
148 
149   return group;
150 }
151 
152 
153 /**
154  * Formats the given ASTNode as an SBML L1 token and appends the result to
155  * the given StringBuffer.
156  */
157 void
FormulaFormatter_format(StringBuffer_t * sb,const ASTNode_t * node)158 FormulaFormatter_format (StringBuffer_t *sb, const ASTNode_t *node)
159 {
160   if (sb == NULL) return;
161   if (ASTNode_isOperator(node))
162   {
163     FormulaFormatter_formatOperator(sb, node);
164   }
165   else if (ASTNode_isFunction(node))
166   {
167     FormulaFormatter_formatFunction(sb, node);
168   }
169   else if (ASTNode_isInteger(node))
170   {
171     StringBuffer_appendInt(sb, ASTNode_getInteger(node));
172   }
173   else if (ASTNode_isRational(node))
174   {
175     FormulaFormatter_formatRational(sb, node);
176   }
177   else if (ASTNode_isReal(node))
178   {
179     FormulaFormatter_formatReal(sb, node);
180   }
181   else if ( !ASTNode_isUnknown(node) )
182   {
183     StringBuffer_append(sb, ASTNode_getName(node));
184   }
185 }
186 
187 
188 /**
189  * Formats the given ASTNode as an SBML L1 function name and appends the
190  * result to the given StringBuffer.
191  */
192 void
FormulaFormatter_formatFunction(StringBuffer_t * sb,const ASTNode_t * node)193 FormulaFormatter_formatFunction (StringBuffer_t *sb, const ASTNode_t *node)
194 {
195   ASTNodeType_t type = ASTNode_getType(node);
196 
197 
198   switch (type)
199   {
200     case AST_FUNCTION_ARCCOS:
201       StringBuffer_append(sb, "acos");
202       break;
203 
204     case AST_FUNCTION_ARCSIN:
205       StringBuffer_append(sb, "asin");
206       break;
207 
208     case AST_FUNCTION_ARCTAN:
209       StringBuffer_append(sb, "atan");
210       break;
211 
212     case AST_FUNCTION_CEILING:
213       StringBuffer_append(sb, "ceil");
214       break;
215 
216     case AST_FUNCTION_LN:
217       StringBuffer_append(sb, "log");
218       break;
219 
220     case AST_FUNCTION_POWER:
221       StringBuffer_append(sb, "pow");
222       break;
223 
224     default:
225       StringBuffer_append(sb, ASTNode_getName(node));
226       break;
227   }
228 }
229 
230 
231 /**
232  * Formats the given ASTNode as an SBML L1 operator and appends the result
233  * to the given StringBuffer.
234  */
235 void
FormulaFormatter_formatOperator(StringBuffer_t * sb,const ASTNode_t * node)236 FormulaFormatter_formatOperator (StringBuffer_t *sb, const ASTNode_t *node)
237 {
238   ASTNodeType_t type = ASTNode_getType(node);
239 
240 
241   if (type != AST_POWER)
242   {
243     StringBuffer_appendChar(sb, ' ');
244   }
245 
246   StringBuffer_appendChar(sb, ASTNode_getCharacter(node));
247 
248   if (type != AST_POWER)
249   {
250     StringBuffer_appendChar(sb, ' ');
251   }
252 }
253 
254 
255 /**
256  * Formats the given ASTNode as a rational number and appends the result to
257  * the given StringBuffer.  For SBML L1 this amounts to:
258  *
259  *   "(numerator/denominator)"
260  */
261 void
FormulaFormatter_formatRational(StringBuffer_t * sb,const ASTNode_t * node)262 FormulaFormatter_formatRational (StringBuffer_t *sb, const ASTNode_t *node)
263 {
264   StringBuffer_appendChar( sb, '(');
265   StringBuffer_appendInt ( sb, ASTNode_getNumerator(node)   );
266   StringBuffer_appendChar( sb, '/');
267   StringBuffer_appendInt ( sb, ASTNode_getDenominator(node) );
268   StringBuffer_appendChar( sb, ')');
269 }
270 
271 
272 /**
273  * Formats the given ASTNode as a real number and appends the result to
274  * the given StringBuffer.
275  */
276 void
FormulaFormatter_formatReal(StringBuffer_t * sb,const ASTNode_t * node)277 FormulaFormatter_formatReal (StringBuffer_t *sb, const ASTNode_t *node)
278 {
279   double value = ASTNode_getReal(node);
280   int    sign;
281 
282 
283   if (util_isNaN(value))
284   {
285     StringBuffer_append(sb, "NaN");
286   }
287   else if ((sign = util_isInf(value)) != 0)
288   {
289     if (sign == -1)
290     {
291       StringBuffer_appendChar(sb, '-');
292     }
293 
294     StringBuffer_append(sb, "INF");
295   }
296   else if (util_isNegZero(value))
297   {
298     StringBuffer_append(sb, "-0");
299   }
300   else
301   {
302     if (ASTNode_getType(node) == AST_REAL_E)
303     {
304       StringBuffer_appendFullExp(sb, ASTNode_getMantissa(node), ASTNode_getExponent(node), value);
305     }
306     else
307     {
308       StringBuffer_appendReal(sb, value);
309     }
310   }
311 }
312 
313 /**
314  * Visits the given ASTNode node.  This function is really just a
315  * dispatcher to either SBML_formulaToString_visitFunction() or
316  * SBML_formulaToString_visitOther().
317  */
318 void
FormulaFormatter_visit(const ASTNode_t * parent,const ASTNode_t * node,StringBuffer_t * sb)319 FormulaFormatter_visit ( const ASTNode_t *parent,
320                          const ASTNode_t *node,
321                          StringBuffer_t  *sb )
322 {
323 
324   if (ASTNode_isLog10(node))
325   {
326     FormulaFormatter_visitLog10(parent, node, sb);
327   }
328   else if (ASTNode_isSqrt(node))
329   {
330     FormulaFormatter_visitSqrt(parent, node, sb);
331   }
332   else if (FormulaFormatter_isFunction(node))
333   {
334     FormulaFormatter_visitFunction(parent, node, sb);
335   }
336   else if (ASTNode_hasTypeAndNumChildren(node, AST_MINUS, 1))
337   {
338     FormulaFormatter_visitUMinus(parent, node, sb);
339   }
340   else if (ASTNode_hasTypeAndNumChildren(node, AST_PLUS, 1) || ASTNode_hasTypeAndNumChildren(node, AST_TIMES, 1))
341   {
342     FormulaFormatter_visit(node, ASTNode_getChild(node, 0), sb);
343   }
344   else if (ASTNode_hasTypeAndNumChildren(node, AST_PLUS, 0))
345   {
346     StringBuffer_appendInt(sb, 0);
347   }
348   else if (ASTNode_hasTypeAndNumChildren(node, AST_TIMES, 0))
349   {
350     StringBuffer_appendInt(sb, 1);
351   }
352   else
353   {
354     FormulaFormatter_visitOther(parent, node, sb);
355   }
356 }
357 
358 
359 /**
360  * Visits the given ASTNode as a function.  For this node only the
361  * traversal is preorder.
362  */
363 void
FormulaFormatter_visitFunction(const ASTNode_t * parent,const ASTNode_t * node,StringBuffer_t * sb)364 FormulaFormatter_visitFunction ( const ASTNode_t *parent,
365                                  const ASTNode_t *node,
366                                  StringBuffer_t  *sb )
367 {
368   unsigned int numChildren = ASTNode_getNumChildren(node);
369   unsigned int n;
370 
371 
372   FormulaFormatter_format(sb, node);
373   StringBuffer_appendChar(sb, '(');
374 
375   if (numChildren > 0)
376   {
377     FormulaFormatter_visit( node, ASTNode_getChild(node, 0), sb );
378   }
379 
380   for (n = 1; n < numChildren; n++)
381   {
382     StringBuffer_appendChar(sb, ',');
383     StringBuffer_appendChar(sb, ' ');
384     FormulaFormatter_visit( node, ASTNode_getChild(node, n), sb );
385   }
386 
387   StringBuffer_appendChar(sb, ')');
388 }
389 
390 
391 /**
392  * Visits the given ASTNode as the function "log(10, x)" and in doing so,
393  * formats it as "log10(x)" (where x is any subexpression).
394  */
395 void
FormulaFormatter_visitLog10(const ASTNode_t * parent,const ASTNode_t * node,StringBuffer_t * sb)396 FormulaFormatter_visitLog10 ( const ASTNode_t *parent,
397                               const ASTNode_t *node,
398                               StringBuffer_t  *sb )
399 {
400   StringBuffer_append(sb, "log10(");
401   FormulaFormatter_visit(node, ASTNode_getChild(node, 1), sb);
402   StringBuffer_appendChar(sb, ')');
403 }
404 
405 
406 /**
407  * Visits the given ASTNode as the function "root(2, x)" and in doing so,
408  * formats it as "sqrt(x)" (where x is any subexpression).
409  */
410 void
FormulaFormatter_visitSqrt(const ASTNode_t * parent,const ASTNode_t * node,StringBuffer_t * sb)411 FormulaFormatter_visitSqrt ( const ASTNode_t *parent,
412                              const ASTNode_t *node,
413                              StringBuffer_t  *sb )
414 {
415   StringBuffer_append(sb, "sqrt(");
416   FormulaFormatter_visit(node, ASTNode_getChild(node, 1), sb);
417   StringBuffer_appendChar(sb, ')');
418 }
419 
420 
421 /**
422  * Visits the given ASTNode as a unary minus.  For this node only the
423  * traversal is preorder.
424  */
425 void
FormulaFormatter_visitUMinus(const ASTNode_t * parent,const ASTNode_t * node,StringBuffer_t * sb)426 FormulaFormatter_visitUMinus ( const ASTNode_t *parent,
427                                const ASTNode_t *node,
428                                StringBuffer_t  *sb )
429 {
430   StringBuffer_appendChar(sb, '-');
431   FormulaFormatter_visit ( node, ASTNode_getLeftChild(node), sb );
432 }
433 
434 
435 /**
436  * Visits the given ASTNode and continues the inorder traversal.
437  */
438 void
FormulaFormatter_visitOther(const ASTNode_t * parent,const ASTNode_t * node,StringBuffer_t * sb)439 FormulaFormatter_visitOther ( const ASTNode_t *parent,
440                               const ASTNode_t *node,
441                               StringBuffer_t  *sb )
442 {
443   unsigned int numChildren = ASTNode_getNumChildren(node);
444   int group       = FormulaFormatter_isGrouped(parent, node);
445   unsigned int n;
446 
447 
448   if (group)
449   {
450     StringBuffer_appendChar(sb, '(');
451   }
452 
453   if (numChildren == 0) {
454     FormulaFormatter_format(sb, node);
455   }
456 
457   else if (numChildren == 1)
458   {
459     //I believe this would only be called for invalid ASTNode setups,
460     // but this could in theory occur.  This is the safest
461     // behavior I can think of.
462     FormulaFormatter_format(sb, node);
463     StringBuffer_appendChar(sb, '(');
464     FormulaFormatter_visit( node, ASTNode_getChild(node, 0), sb );
465     StringBuffer_appendChar(sb, ')');
466   }
467 
468   else {
469     FormulaFormatter_visit( node, ASTNode_getChild(node, 0), sb );
470 
471     for (n = 1; n < numChildren; n++)
472     {
473       FormulaFormatter_format(sb, node);
474       FormulaFormatter_visit( node, ASTNode_getChild(node, n), sb );
475     }
476   }
477 
478   if (group)
479   {
480     StringBuffer_appendChar(sb, ')');
481   }
482 }
483 /** @endcond */
484 
485 LIBSBML_CPP_NAMESPACE_END
486 
487