Line data Source code
1 : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 : * -------------------------------------------------------------------------- *
3 : * Lepton *
4 : * -------------------------------------------------------------------------- *
5 : * This is part of the Lepton expression parser originating from *
6 : * Simbios, the NIH National Center for Physics-Based Simulation of *
7 : * Biological Structures at Stanford, funded under the NIH Roadmap for *
8 : * Medical Research, grant U54 GM072970. See https://simtk.org. *
9 : * *
10 : * Portions copyright (c) 2013-2016 Stanford University and the Authors. *
11 : * Authors: Peter Eastman *
12 : * Contributors: *
13 : * *
14 : * Permission is hereby granted, free of charge, to any person obtaining a *
15 : * copy of this software and associated documentation files (the "Software"), *
16 : * to deal in the Software without restriction, including without limitation *
17 : * the rights to use, copy, modify, merge, publish, distribute, sublicense, *
18 : * and/or sell copies of the Software, and to permit persons to whom the *
19 : * Software is furnished to do so, subject to the following conditions: *
20 : * *
21 : * The above copyright notice and this permission notice shall be included in *
22 : * all copies or substantial portions of the Software. *
23 : * *
24 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
25 : * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
26 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *
27 : * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, *
28 : * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR *
29 : * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE *
30 : * USE OR OTHER DEALINGS IN THE SOFTWARE. *
31 : * -------------------------------------------------------------------------- *
32 : +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
33 : /* -------------------------------------------------------------------------- *
34 : * lepton *
35 : * -------------------------------------------------------------------------- *
36 : * This is part of the lepton expression parser originating from *
37 : * Simbios, the NIH National Center for Physics-Based Simulation of *
38 : * Biological Structures at Stanford, funded under the NIH Roadmap for *
39 : * Medical Research, grant U54 GM072970. See https://simtk.org. *
40 : * *
41 : * Portions copyright (c) 2009 Stanford University and the Authors. *
42 : * Authors: Peter Eastman *
43 : * Contributors: *
44 : * *
45 : * Permission is hereby granted, free of charge, to any person obtaining a *
46 : * copy of this software and associated documentation files (the "Software"), *
47 : * to deal in the Software without restriction, including without limitation *
48 : * the rights to use, copy, modify, merge, publish, distribute, sublicense, *
49 : * and/or sell copies of the Software, and to permit persons to whom the *
50 : * Software is furnished to do so, subject to the following conditions: *
51 : * *
52 : * The above copyright notice and this permission notice shall be included in *
53 : * all copies or substantial portions of the Software. *
54 : * *
55 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
56 : * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
57 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *
58 : * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, *
59 : * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR *
60 : * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE *
61 : * USE OR OTHER DEALINGS IN THE SOFTWARE. *
62 : * -------------------------------------------------------------------------- */
63 :
64 : #include "ParsedExpression.h"
65 : #include "CompiledExpression.h"
66 : #include "ExpressionProgram.h"
67 : #include "Operation.h"
68 : #include <limits>
69 : #include <vector>
70 :
71 : namespace PLMD {
72 : using namespace lepton;
73 : using namespace std;
74 :
75 0 : ParsedExpression::ParsedExpression() : rootNode(ExpressionTreeNode()) {
76 0 : }
77 :
78 2859 : ParsedExpression::ParsedExpression(const ExpressionTreeNode& rootNode) : rootNode(rootNode) {
79 2859 : }
80 :
81 3657 : const ExpressionTreeNode& ParsedExpression::getRootNode() const {
82 3657 : if (&rootNode.getOperation() == NULL)
83 0 : throw Exception("Illegal call to an initialized ParsedExpression");
84 3657 : return rootNode;
85 : }
86 :
87 0 : double ParsedExpression::evaluate() const {
88 0 : return evaluate(getRootNode(), map<string, double>());
89 : }
90 :
91 4 : double ParsedExpression::evaluate(const map<string, double>& variables) const {
92 4 : return evaluate(getRootNode(), variables);
93 : }
94 :
95 17938 : double ParsedExpression::evaluate(const ExpressionTreeNode& node, const map<string, double>& variables) {
96 35876 : int numArgs = (int) node.getChildren().size();
97 35876 : vector<double> args(max(numArgs, 1));
98 30660 : for (int i = 0; i < numArgs; i++)
99 12722 : args[i] = evaluate(node.getChildren()[i], variables);
100 53814 : return node.getOperation().evaluate(&args[0], variables);
101 : }
102 :
103 818 : ParsedExpression ParsedExpression::optimize() const {
104 1636 : ExpressionTreeNode result = precalculateConstantSubexpressions(getRootNode());
105 : while (true) {
106 818 : ExpressionTreeNode simplified = substituteSimplerExpression(result);
107 818 : if (simplified == result)
108 : break;
109 0 : result = simplified;
110 : }
111 1636 : return ParsedExpression(result);
112 : }
113 :
114 798 : ParsedExpression ParsedExpression::optimize(const map<string, double>& variables) const {
115 1596 : ExpressionTreeNode result = preevaluateVariables(getRootNode(), variables);
116 798 : result = precalculateConstantSubexpressions(result);
117 : while (true) {
118 2672 : ExpressionTreeNode simplified = substituteSimplerExpression(result);
119 1735 : if (simplified == result)
120 : break;
121 937 : result = simplified;
122 : }
123 1596 : return ParsedExpression(result);
124 : }
125 :
126 27945 : ExpressionTreeNode ParsedExpression::preevaluateVariables(const ExpressionTreeNode& node, const map<string, double>& variables) {
127 27945 : if (node.getOperation().getId() == Operation::VARIABLE) {
128 5137 : const Operation::Variable& var = dynamic_cast<const Operation::Variable&>(node.getOperation());
129 10274 : map<string, double>::const_iterator iter = variables.find(var.getName());
130 5137 : if (iter == variables.end())
131 4356 : return node;
132 1562 : return ExpressionTreeNode(new Operation::Constant(iter->second));
133 : }
134 68424 : vector<ExpressionTreeNode> children(node.getChildren().size());
135 77102 : for (int i = 0; i < (int) children.size(); i++)
136 81441 : children[i] = preevaluateVariables(node.getChildren()[i], variables);
137 22808 : return ExpressionTreeNode(node.getOperation().clone(), children);
138 : }
139 :
140 37382 : ExpressionTreeNode ParsedExpression::precalculateConstantSubexpressions(const ExpressionTreeNode& node) {
141 112146 : vector<ExpressionTreeNode> children(node.getChildren().size());
142 108914 : for (int i = 0; i < (int) children.size(); i++)
143 107298 : children[i] = precalculateConstantSubexpressions(node.getChildren()[i]);
144 74764 : ExpressionTreeNode result = ExpressionTreeNode(node.getOperation().clone(), children);
145 37382 : if (node.getOperation().getId() == Operation::VARIABLE || node.getOperation().getId() == Operation::CUSTOM)
146 7011 : return result;
147 46261 : for (int i = 0; i < (int) children.size(); i++)
148 53486 : if (children[i].getOperation().getId() != Operation::CONSTANT)
149 18798 : return result;
150 34719 : return ExpressionTreeNode(new Operation::Constant(evaluate(result, map<string, double>())));
151 : }
152 :
153 44985 : ExpressionTreeNode ParsedExpression::substituteSimplerExpression(const ExpressionTreeNode& node) {
154 134955 : vector<ExpressionTreeNode> children(node.getChildren().size());
155 129849 : for (int i = 0; i < (int) children.size(); i++)
156 127296 : children[i] = substituteSimplerExpression(node.getChildren()[i]);
157 44985 : switch (node.getOperation().getId()) {
158 : case Operation::ADD:
159 : {
160 4049 : double first = getConstantValue(children[0]);
161 4049 : double second = getConstantValue(children[1]);
162 4049 : if (first == 0.0) // Add 0
163 473 : return children[1];
164 3576 : if (second == 0.0) // Add 0
165 858 : return children[0];
166 2718 : if (first == first) // Add a constant
167 52 : return ExpressionTreeNode(new Operation::AddConstant(first), children[1]);
168 2692 : if (second == second) // Add a constant
169 930 : return ExpressionTreeNode(new Operation::AddConstant(second), children[0]);
170 2227 : if (children[1].getOperation().getId() == Operation::NEGATE) // a+(-b) = a-b
171 0 : return ExpressionTreeNode(new Operation::Subtract(), children[0], children[1].getChildren()[0]);
172 2227 : if (children[0].getOperation().getId() == Operation::NEGATE) // (-a)+b = b-a
173 0 : return ExpressionTreeNode(new Operation::Subtract(), children[1], children[0].getChildren()[0]);
174 : break;
175 : }
176 : case Operation::SUBTRACT:
177 : {
178 1972 : if (children[0] == children[1])
179 24 : return ExpressionTreeNode(new Operation::Constant(0.0)); // Subtracting anything from itself is 0
180 1960 : double first = getConstantValue(children[0]);
181 1960 : if (first == 0.0) // Subtract from 0
182 80 : return ExpressionTreeNode(new Operation::Negate(), children[1]);
183 1920 : double second = getConstantValue(children[1]);
184 1920 : if (second == 0.0) // Subtract 0
185 32 : return children[0];
186 1888 : if (second == second) // Subtract a constant
187 922 : return ExpressionTreeNode(new Operation::AddConstant(-second), children[0]);
188 1427 : if (children[1].getOperation().getId() == Operation::NEGATE) // a-(-b) = a+b
189 0 : return ExpressionTreeNode(new Operation::Add(), children[0], children[1].getChildren()[0]);
190 : break;
191 : }
192 : case Operation::MULTIPLY:
193 : {
194 6867 : double first = getConstantValue(children[0]);
195 6867 : double second = getConstantValue(children[1]);
196 6867 : if (first == 0.0 || second == 0.0) // Multiply by 0
197 2382 : return ExpressionTreeNode(new Operation::Constant(0.0));
198 5676 : if (first == 1.0) // Multiply by 1
199 17 : return children[1];
200 5659 : if (second == 1.0) // Multiply by 1
201 908 : return children[0];
202 4751 : if (children[0].getOperation().getId() == Operation::CONSTANT) { // Multiply by a constant
203 1336 : if (children[1].getOperation().getId() == Operation::MULTIPLY_CONSTANT) // Combine two multiplies into a single one
204 80 : return ExpressionTreeNode(new Operation::MultiplyConstant(first*dynamic_cast<const Operation::MultiplyConstant*>(&children[1].getOperation())->getValue()), children[1].getChildren()[0]);
205 2632 : return ExpressionTreeNode(new Operation::MultiplyConstant(first), children[1]);
206 : }
207 3415 : if (children[1].getOperation().getId() == Operation::CONSTANT) { // Multiply by a constant
208 15 : if (children[0].getOperation().getId() == Operation::MULTIPLY_CONSTANT) // Combine two multiplies into a single one
209 0 : return ExpressionTreeNode(new Operation::MultiplyConstant(second*dynamic_cast<const Operation::MultiplyConstant*>(&children[0].getOperation())->getValue()), children[0].getChildren()[0]);
210 30 : return ExpressionTreeNode(new Operation::MultiplyConstant(second), children[0]);
211 : }
212 3403 : if (children[0].getOperation().getId() == Operation::NEGATE && children[1].getOperation().getId() == Operation::NEGATE) // The two negations cancel
213 0 : return ExpressionTreeNode(new Operation::Multiply(), children[0].getChildren()[0], children[1].getChildren()[0]);
214 6803 : if (children[0].getOperation().getId() == Operation::NEGATE && children[1].getOperation().getId() == Operation::MULTIPLY_CONSTANT) // Negate the constant
215 7 : return ExpressionTreeNode(new Operation::Multiply(), children[0].getChildren()[0], ExpressionTreeNode(new Operation::MultiplyConstant(-dynamic_cast<const Operation::MultiplyConstant*>(&children[1].getOperation())->getValue()), children[1].getChildren()[0]));
216 6990 : if (children[1].getOperation().getId() == Operation::NEGATE && children[0].getOperation().getId() == Operation::MULTIPLY_CONSTANT) // Negate the constant
217 1152 : return ExpressionTreeNode(new Operation::Multiply(), ExpressionTreeNode(new Operation::MultiplyConstant(-dynamic_cast<const Operation::MultiplyConstant*>(&children[0].getOperation())->getValue()), children[0].getChildren()[0]), children[1].getChildren()[0]);
218 6414 : if (children[0].getOperation().getId() == Operation::NEGATE) // Pull the negation out so it can possibly be optimized further
219 8 : return ExpressionTreeNode(new Operation::Negate(), ExpressionTreeNode(new Operation::Multiply(), children[0].getChildren()[0], children[1]));
220 3205 : if (children[1].getOperation().getId() == Operation::NEGATE) // Pull the negation out so it can possibly be optimized further
221 0 : return ExpressionTreeNode(new Operation::Negate(), ExpressionTreeNode(new Operation::Multiply(), children[0], children[1].getChildren()[0]));
222 3205 : if (children[1].getOperation().getId() == Operation::RECIPROCAL) // a*(1/b) = a/b
223 0 : return ExpressionTreeNode(new Operation::Divide(), children[0], children[1].getChildren()[0]);
224 3205 : if (children[0].getOperation().getId() == Operation::RECIPROCAL) // (1/a)*b = b/a
225 12 : return ExpressionTreeNode(new Operation::Divide(), children[1], children[0].getChildren()[0]);
226 3201 : if (children[0] == children[1])
227 684 : return ExpressionTreeNode(new Operation::Square(), children[0]); // x*x = square(x)
228 2881 : if (children[0].getOperation().getId() == Operation::SQUARE && children[0].getChildren()[0] == children[1])
229 0 : return ExpressionTreeNode(new Operation::Cube(), children[1]); // x*x*x = cube(x)
230 5816 : if (children[1].getOperation().getId() == Operation::SQUARE && children[1].getChildren()[0] == children[0])
231 0 : return ExpressionTreeNode(new Operation::Cube(), children[0]); // x*x*x = cube(x)
232 : break;
233 : }
234 : case Operation::DIVIDE:
235 : {
236 214 : if (children[0] == children[1])
237 0 : return ExpressionTreeNode(new Operation::Constant(1.0)); // Dividing anything from itself is 0
238 214 : double numerator = getConstantValue(children[0]);
239 214 : if (numerator == 0.0) // 0 divided by something
240 0 : return ExpressionTreeNode(new Operation::Constant(0.0));
241 214 : if (numerator == 1.0) // 1 divided by something
242 24 : return ExpressionTreeNode(new Operation::Reciprocal(), children[1]);
243 202 : double denominator = getConstantValue(children[1]);
244 202 : if (denominator == 1.0) // Divide by 1
245 0 : return children[0];
246 202 : if (children[1].getOperation().getId() == Operation::CONSTANT) {
247 42 : if (children[0].getOperation().getId() == Operation::MULTIPLY_CONSTANT) // Combine a multiply and a divide into one multiply
248 0 : return ExpressionTreeNode(new Operation::MultiplyConstant(dynamic_cast<const Operation::MultiplyConstant*>(&children[0].getOperation())->getValue()/denominator), children[0].getChildren()[0]);
249 84 : return ExpressionTreeNode(new Operation::MultiplyConstant(1.0/denominator), children[0]); // Replace a divide with a multiply
250 : }
251 188 : if (children[0].getOperation().getId() == Operation::NEGATE && children[1].getOperation().getId() == Operation::NEGATE) // The two negations cancel
252 0 : return ExpressionTreeNode(new Operation::Divide(), children[0].getChildren()[0], children[1].getChildren()[0]);
253 320 : if (children[1].getOperation().getId() == Operation::NEGATE && children[0].getOperation().getId() == Operation::MULTIPLY_CONSTANT) // Negate the constant
254 0 : return ExpressionTreeNode(new Operation::Divide(), ExpressionTreeNode(new Operation::MultiplyConstant(-dynamic_cast<const Operation::MultiplyConstant*>(&children[0].getOperation())->getValue()), children[0].getChildren()[0]), children[1].getChildren()[0]);
255 320 : if (children[0].getOperation().getId() == Operation::NEGATE) // Pull the negation out so it can possibly be optimized further
256 112 : return ExpressionTreeNode(new Operation::Negate(), ExpressionTreeNode(new Operation::Divide(), children[0].getChildren()[0], children[1]));
257 132 : if (children[1].getOperation().getId() == Operation::NEGATE) // Pull the negation out so it can possibly be optimized further
258 0 : return ExpressionTreeNode(new Operation::Negate(), ExpressionTreeNode(new Operation::Divide(), children[0], children[1].getChildren()[0]));
259 132 : if (children[1].getOperation().getId() == Operation::RECIPROCAL) // a/(1/b) = a*b
260 0 : return ExpressionTreeNode(new Operation::Multiply(), children[0], children[1].getChildren()[0]);
261 : break;
262 : }
263 : case Operation::POWER:
264 : {
265 362 : double base = getConstantValue(children[0]);
266 362 : if (base == 0.0) // 0 to any power is 0
267 0 : return ExpressionTreeNode(new Operation::Constant(0.0));
268 362 : if (base == 1.0) // 1 to any power is 1
269 0 : return ExpressionTreeNode(new Operation::Constant(1.0));
270 362 : double exponent = getConstantValue(children[1]);
271 362 : if (exponent == 0.0) // x^0 = 1
272 0 : return ExpressionTreeNode(new Operation::Constant(1.0));
273 362 : if (exponent == 1.0) // x^1 = x
274 49 : return children[0];
275 313 : if (exponent == -1.0) // x^-1 = recip(x)
276 0 : return ExpressionTreeNode(new Operation::Reciprocal(), children[0]);
277 313 : if (exponent == 2.0) // x^2 = square(x)
278 290 : return ExpressionTreeNode(new Operation::Square(), children[0]);
279 168 : if (exponent == 3.0) // x^3 = cube(x)
280 138 : return ExpressionTreeNode(new Operation::Cube(), children[0]);
281 99 : if (exponent == 0.5) // x^0.5 = sqrt(x)
282 10 : return ExpressionTreeNode(new Operation::Sqrt(), children[0]);
283 94 : if (exponent == exponent) // Constant power
284 188 : return ExpressionTreeNode(new Operation::PowerConstant(exponent), children[0]);
285 : break;
286 : }
287 : case Operation::NEGATE:
288 : {
289 1243 : if (children[0].getOperation().getId() == Operation::MULTIPLY_CONSTANT) // Combine a multiply and a negate into a single multiply
290 8 : return ExpressionTreeNode(new Operation::MultiplyConstant(-dynamic_cast<const Operation::MultiplyConstant*>(&children[0].getOperation())->getValue()), children[0].getChildren()[0]);
291 1241 : if (children[0].getOperation().getId() == Operation::CONSTANT) // Negate a constant
292 0 : return ExpressionTreeNode(new Operation::Constant(-getConstantValue(children[0])));
293 1241 : if (children[0].getOperation().getId() == Operation::NEGATE) // The two negations cancel
294 0 : return children[0].getChildren()[0];
295 : break;
296 : }
297 : case Operation::MULTIPLY_CONSTANT:
298 : {
299 3453 : if (children[0].getOperation().getId() == Operation::MULTIPLY_CONSTANT) // Combine two multiplies into a single one
300 0 : return ExpressionTreeNode(new Operation::MultiplyConstant(dynamic_cast<const Operation::MultiplyConstant*>(&node.getOperation())->getValue()*dynamic_cast<const Operation::MultiplyConstant*>(&children[0].getOperation())->getValue()), children[0].getChildren()[0]);
301 3453 : if (children[0].getOperation().getId() == Operation::CONSTANT) // Multiply two constants
302 36 : return ExpressionTreeNode(new Operation::Constant(dynamic_cast<const Operation::MultiplyConstant*>(&node.getOperation())->getValue()*getConstantValue(children[0])));
303 3441 : if (children[0].getOperation().getId() == Operation::NEGATE) // Combine a multiply and a negate into a single multiply
304 1748 : return ExpressionTreeNode(new Operation::MultiplyConstant(-dynamic_cast<const Operation::MultiplyConstant*>(&node.getOperation())->getValue()), children[0].getChildren()[0]);
305 : break;
306 : }
307 : default:
308 : {
309 : // If operation ID is not one of the above,
310 : // we don't substitute a simpler expression.
311 : break;
312 : }
313 :
314 : }
315 37715 : return ExpressionTreeNode(node.getOperation().clone(), children);
316 : }
317 :
318 441 : ParsedExpression ParsedExpression::differentiate(const string& variable) const {
319 441 : return differentiate(getRootNode(), variable);
320 : }
321 :
322 6374 : ExpressionTreeNode ParsedExpression::differentiate(const ExpressionTreeNode& node, const string& variable) {
323 19122 : vector<ExpressionTreeNode> childDerivs(node.getChildren().size());
324 18240 : for (int i = 0; i < (int) childDerivs.size(); i++)
325 17799 : childDerivs[i] = differentiate(node.getChildren()[i], variable);
326 12748 : return node.getOperation().differentiate(node.getChildren(),childDerivs, variable);
327 : }
328 :
329 26864 : double ParsedExpression::getConstantValue(const ExpressionTreeNode& node) {
330 26864 : if (node.getOperation().getId() == Operation::CONSTANT)
331 13496 : return dynamic_cast<const Operation::Constant&>(node.getOperation()).getValue();
332 : return numeric_limits<double>::quiet_NaN();
333 : }
334 :
335 0 : ExpressionProgram ParsedExpression::createProgram() const {
336 0 : return ExpressionProgram(*this);
337 : }
338 :
339 818 : CompiledExpression ParsedExpression::createCompiledExpression() const {
340 818 : return CompiledExpression(*this);
341 : }
342 :
343 0 : ParsedExpression ParsedExpression::renameVariables(const map<string, string>& replacements) const {
344 0 : return ParsedExpression(renameNodeVariables(getRootNode(), replacements));
345 : }
346 :
347 0 : ExpressionTreeNode ParsedExpression::renameNodeVariables(const ExpressionTreeNode& node, const map<string, string>& replacements) {
348 0 : if (node.getOperation().getId() == Operation::VARIABLE) {
349 0 : map<string, string>::const_iterator replace = replacements.find(node.getOperation().getName());
350 0 : if (replace != replacements.end())
351 0 : return ExpressionTreeNode(new Operation::Variable(replace->second));
352 : }
353 0 : vector<ExpressionTreeNode> children;
354 0 : for (int i = 0; i < (int) node.getChildren().size(); i++)
355 0 : children.push_back(renameNodeVariables(node.getChildren()[i], replacements));
356 0 : return ExpressionTreeNode(node.getOperation().clone(), children);
357 : }
358 :
359 9149 : ostream& lepton::operator<<(ostream& out, const ExpressionTreeNode& node) {
360 11067 : if (node.getOperation().isInfixOperator() && node.getChildren().size() == 2) {
361 11292 : out << "(" << node.getChildren()[0] << ")" << node.getOperation().getName() << "(" << node.getChildren()[1] << ")";
362 : }
363 7339 : else if (node.getOperation().isInfixOperator() && node.getChildren().size() == 1) {
364 144 : out << "(" << node.getChildren()[0] << ")" << node.getOperation().getName();
365 : }
366 : else {
367 14462 : out << node.getOperation().getName();
368 14462 : if (node.getChildren().size() > 0) {
369 4567 : out << "(";
370 22847 : for (int i = 0; i < (int) node.getChildren().size(); i++) {
371 4571 : if (i > 0)
372 4 : out << ", ";
373 9142 : out << node.getChildren()[i];
374 : }
375 4567 : out << ")";
376 : }
377 : }
378 9149 : return out;
379 : }
380 :
381 778 : ostream& lepton::operator<<(ostream& out, const ParsedExpression& exp) {
382 778 : out << exp.getRootNode();
383 778 : return out;
384 : }
385 : }
|