1 module libdmathexpr.mathexpr;
2 /**
3  * LibDMathExpr contains functions and classes for parsing and writing math 
4  * equations.
5  *
6  * Copyright:   (c) 2009 William K. Moore, III (nyphbl8d (at) gmail (dot) com, opticron on freenode)
7  * Authors:     William K. Moore, III
8  * License:     Boost Software License - Version 1.0 - August 17th, 2003
9 
10  Permission is hereby granted, free of charge, to any person or organization
11  obtaining a copy of the software and accompanying documentation covered by
12  this license (the "Software") to use, reproduce, display, distribute,
13  execute, and transmit the Software, and to prepare derivative works of the
14  Software, and to permit third-parties to whom the Software is furnished to
15  do so, all subject to the following:
16 
17  The copyright notices in the Software and this entire statement, including
18  the above license grant, this restriction and the following disclaimer,
19  must be included in all copies of the Software, in whole or in part, and
20  all derivative works of the Software, unless such copies or derivative
21  works are solely in the form of machine-executable object code generated by
22  a source language processor.
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, TITLE AND NON-INFRINGEMENT. IN NO EVENT
27  SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
28  FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
29  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
30  DEALINGS IN THE SOFTWARE.
31 
32  */
33 import std.conv: to;
34 import std.math;
35 import std.mathspecial: gamma;
36 import std.regex;
37 import std.string: cmp, icmp, join, replace, split, strip, stripLeft, toLower;
38 
39 
40 /// An exception thrown on math parsing errors.
41 class MathParseError : Exception {
42 	// Throws an exception with an error message.
43 	this(string msg) @safe {
44 		super(msg);
45 	}
46 }
47 
48 interface IMathObject {
49 	void parse(ref string input) @safe;
50 	string toString() @safe;
51 	real evaluate(real[string]vars) @safe;
52 	IMathObject simplify(real[string]vars) @safe;
53 }
54 
55 const string tryEval = 
56 "		try {
57 			return new MONumber(evaluate(vars));
58 		} catch(Exception) {}";
59 
60 enum MOOpType {
61 	Addition,
62 	Multiplication,
63 	Exponentiation,
64 	Negation,
65 	Modulus,
66 	Inversion
67 }
68 string toString(MOOpType op) @safe {
69 	switch(op) {
70 	case MOOpType.Addition: return " + ";
71 	case MOOpType.Multiplication: return " * ";
72 	case MOOpType.Negation: return " -";
73 	case MOOpType.Inversion: return "1/";
74 	case MOOpType.Modulus: return "%";
75 	default: throw new MathParseError("Unknown operation!");
76 	}
77 }
78 
79 int getPrevElem(IMathObject[]tokens,int i) @safe {
80 	i--;
81 	for (;i>=0;i--) {
82 		if (tokens[i]) return i;
83 	}
84 	return -1;
85 }
86 
87 int getNextElem(IMathObject[]tokens,int i) @safe {
88 	i++;
89 	for (;i<tokens.length;i++) {
90 		if (tokens[i]) return i;
91 	}
92 	return -1;
93 }
94 
95 IMathObject parseMathExpr(string input) @safe {
96 	// split the string into IMathObject tokens
97 	IMathObject[]tokens = splitMathExpr(input);
98 	// go through the process of turning this thing into a tree instead of a flat list
99 	// parens are already expanded
100 	// exponentials
101 	for (int i = 0;i < tokens.length;i++) {
102 		auto caster = cast(MOOperation)tokens[i];
103 		if (caster && caster.op == MOOpType.Exponentiation) {
104 			// must not be at the beginning or end of the string
105 			if (i == 0 || i == tokens.length-1) {
106 				throw new MathParseError("Exponentiation must have 2 operands");
107 			}
108 			// subitems must be parens or numbers
109 			int prev = getPrevElem(tokens,i),next = getNextElem(tokens,i);
110 			if (prev == -1 || (!cast(MOParens)tokens[prev] && !cast(MONumber)tokens[prev] && !cast(MOIdentifier)tokens[prev])) {
111 				// the previous entity can be an operation, but only if it's also an exponential
112 				throw new MathParseError("Exponentiation operators must be parens or numbers");
113 			}
114 			if (next == -1 || (!cast(MOParens)tokens[next] && !cast(MONumber)tokens[next] && !cast(MOIdentifier)tokens[next] && !cast(MONegation)tokens[next])) {
115 				throw new MathParseError("Exponentiation operators must be parens or numbers");
116 			}
117 			// resolve negation here for more fun
118 			if (cast(MONegation)tokens[next]) {
119 				// find next non-negation node
120 				int noneg = next+1;
121 				while (noneg < tokens.length) {
122 					if (tokens[noneg] && !cast(MONegation)tokens[noneg]) {
123 						break;
124 					}
125 					noneg++;
126 				}
127 				if (!tokens[noneg] || cast(MONegation)tokens[noneg]) {
128 					throw new MathParseError("Negation slide as exponent blew up");
129 				}
130 				// fold negation slide into exponential
131 				int lastgood = noneg;
132 				noneg--;
133 				while (noneg >= next) {
134 					if (tokens[noneg]) {
135 						(cast(MONegation)tokens[noneg]).rhs = tokens[lastgood];
136 						tokens[lastgood] = null;
137 						lastgood = noneg;
138 					}
139 					noneg--;
140 				}
141 			}
142 			// pull a quick one and convert to the function call
143 			tokens[i] = new MOIdentifier("pow",[cast(IMathObject)tokens[prev],tokens[next]]);
144 			// move the adjacent objects into the tree
145 			tokens[prev] = null;
146 			tokens[next] = null;
147 		}
148 	}
149 	// evaluate inversion
150 	for (int i = cast(int)tokens.length-1;i >= 0;i--) {
151 		auto caster = cast(MOInversion)tokens[i];
152 		if (caster) {
153 			// must not be at the end of the string
154 			if (i == tokens.length-1) {
155 				throw new MathParseError("Inversion must have an operand");
156 			}
157 			// subitems must be parens or numbers
158 			int next = getNextElem(tokens,i);
159 			if (next == -1) {
160 				throw new MathParseError("Inversion must have an operand");
161 			}
162 			// if this param is an operation, both fields need to be filled
163 			auto ntmp = cast(MOOperation)tokens[next];
164 			if (ntmp) {
165 				throw new MathParseError("Double operators? really? (inversion)");
166 			}
167 			// move the adjacent objects into the tree
168 			caster.rhs = tokens[next];
169 			tokens[next] = null;
170 		}
171 	}
172 	// evaluate negation back to front to accomodate slides
173 	for (int i = cast(int)tokens.length-1;i >= 0;i--) {
174 		auto caster = cast(MONegation)tokens[i];
175 		if (caster) {
176 			// must not be at the end of the string
177 			if (i == tokens.length-1) {
178 				throw new MathParseError("Negation must have an operand");
179 			}
180 			// subitems must be parens or numbers
181 			int next = getNextElem(tokens,i);
182 			if (next == -1) {
183 				throw new MathParseError("Negation must have an operand");
184 			}
185 			// if this param is an operation, both fields need to be filled
186 			auto ntmp = cast(MOOperation)tokens[next];
187 			if (ntmp && !ntmp.validate()) {
188 				throw new MathParseError("Double operators? really? (negation)");
189 			}
190 			// move the adjacent objects into the tree
191 			caster.rhs = tokens[next];
192 			tokens[next] = null;
193 		}
194 	}
195 	// helper function
196 	void parseHelper(bool delegate(MOOpType) @safe valid) @safe {
197 		for (int i = 0;i < tokens.length;i++) {
198 			auto caster = cast(MOOperation)tokens[i];
199 			if (caster && valid(caster.op)) {
200 				// must not be at the beginning or end of the string
201 				if (i == 0 || i == tokens.length-1) {
202 					throw new MathParseError("Operators must have 2 operands");
203 				}
204 				// subitems must be parens or numbers
205 				int prev = getPrevElem(tokens,i),next = getNextElem(tokens,i);
206 				if (prev == -1) {
207 					throw new MathParseError("Operators must have 2 operands");
208 				}
209 				// if this param is an operation, both fields need to be filled
210 				auto ptmp = cast(MOOperation)tokens[prev];
211 				if (ptmp && !ptmp.validate()) {
212 					throw new MathParseError("Double operators? really?");
213 				}
214 				if (next == -1) {
215 					throw new MathParseError("Operators must have 2 operands");
216 				}
217 				// if this param is an operation, both fields need to be filled
218 				auto ntmp = cast(MOOperation)tokens[next];
219 				if (ntmp && !ntmp.validate()) {
220 					throw new MathParseError("Double operators? really?");
221 				}
222 				// move the adjacent objects into the tree
223 				caster.operands ~= tokens[prev];
224 				tokens[prev] = null;
225 				caster.operands ~= tokens[next];
226 				tokens[next] = null;
227 			}
228 		}
229 	}
230 	// multiplication
231 	parseHelper((MOOpType o){return o == MOOpType.Multiplication || o == MOOpType.Modulus;});
232 	// addition
233 	parseHelper((MOOpType o){return o == MOOpType.Addition;});
234 	// now that we have the tree fully formed simplify the tree
235 	// flatten tree
236 	foreach (token;tokens) if (token) {
237 		auto tmp = cast(MOOperation)token;
238 		if (tmp) {
239 			tmp.flatten();
240 		}
241 	}
242 	// verify that we're left with a single item in the list
243 	long count = 0;
244 	uint first;
245 	foreach(uint i,c;tokens) if (c) {count++;first=i;}
246 	if (count != 1) {
247 		throw new MathParseError("FUUUUUUUUUUU count="~count.to!string);
248 	}
249 	return tokens[first];
250 }
251 
252 enum {
253 	NUMBER,
254 	OPERATOR,
255 	PAREN,
256 	SPACE,
257 	IDENTIFIER,
258 }
259 
260 int getCharType(char i) @safe {
261 	if (i >= '0' && i <= '9') return NUMBER;
262 	if (i >= 'a' && i <= 'z' || i >= 'A' && i <= 'Z') return IDENTIFIER;
263 	switch (i) {
264 	case '+':
265 	case '-':
266 	case '*':
267 	case '/':
268 	case '^':
269 	case '%':
270 		return OPERATOR;
271 	case ' ':
272 	case '\n':
273 	case '\r':
274 		return SPACE;
275 	case '(':
276 		return PAREN;
277 	case '.':
278 		return NUMBER;
279 	default:
280 		throw new MathParseError("Invalid character: "~i);
281 	}
282 }
283 
284 IMathObject[] splitMathExpr(string input) @safe {
285 	// respect parens and pass them back into parseMathExpr for further parsing
286 	IMathObject[]tmp;
287 	void doAutoMul() @safe {
288 		if (tmp.length && (cast(MONumber)tmp[$-1] || cast(MOParens)tmp[$-1] || cast(MOIdentifier)tmp[$-1])) {
289 			tmp ~= new MOOperation("*");
290 		}
291 	}
292 	int slice = 0;
293 	while (input.length) {
294 		switch (getCharType(input[0])) {
295 		case NUMBER:
296 			// if the previous item was a number, barf, can't have adjacent numerics because it doesn't make sense
297 			if (tmp.length && cast(MONumber)tmp[$-1]) throw new MathParseError("Numerics can not be adjacent. Secondary starting at: "~input);
298 			// add mul op if necessary
299 			doAutoMul();
300 			tmp ~= new MONumber(input);
301 			break;
302 		case OPERATOR:
303 			// take care of division via mul+inversion
304 			if (input[0] == '/') {
305 				tmp ~= new MOOperation("*");
306 				tmp ~= new MOInversion(input);
307 				input = input[1..$];
308 				continue;
309 			} else if (input[0] == '-') {
310 				if (tmp.length && !cast(MOOperation)tmp[$-1] && !cast(MONegation)tmp[$-1]) {
311 					// tack on a +
312 					tmp ~= new MOOperation("+");
313 				}
314 				tmp ~= new MONegation(input);
315 				input = input[1..$];
316 				continue;
317 			}
318 			tmp ~= new MOOperation(input[0..1]);
319 			input = input[1..$];
320 			break;
321 		case PAREN:
322 			// add mul op if necessary
323 			doAutoMul();
324 			tmp ~= new MOParens(input);
325 			break;
326 		case SPACE:
327 			input = input[1..$];
328 			break;
329 		default:// assume identifier
330 		case IDENTIFIER:
331 			// add mul op if necessary
332 			doAutoMul();
333 			tmp ~= new MOIdentifier(input);
334 			break;
335 		}
336 	}
337 	return tmp;
338 }
339 
340 class MONegation:IMathObject {
341 	IMathObject rhs;
342 	this (ref string c) @safe {
343 		parse(c);
344 	}
345 	this() @safe {}
346 	/// Return the string that was used to generate this node.
347 	override string toString() @safe {
348 		return "-"~rhs.toString();
349 	}
350 	/// Return the numeric data represented by this node.
351 	real evaluate(real[string]vars) @safe {
352 		return -rhs.evaluate(vars);
353 	}
354 	/// This function parses a set of nodes out of an equation string.
355 	void parse(ref string source) @safe {
356 		// this does nothing on purpose, it will not be used by default
357 	}
358 	/// Return the simplification of this node (where possible).
359 	IMathObject simplify(real[string]vars) @safe {
360 		mixin(tryEval);
361 		// pass the simplification up the tree first
362 		rhs = rhs.simplify(vars);
363 		// see if we can wrap two negations into one
364 		auto caster = cast(MONegation)rhs;
365 		if (caster) return caster.rhs;
366 		return this;
367 	}
368 }
369 
370 class MOInversion:IMathObject {
371 	IMathObject rhs;
372 	this (ref string c) @safe {
373 		parse(c);
374 	}
375 	/// Return the string that was used to generate this node.
376 	override string toString() @safe {
377 		return "1/"~rhs.toString();
378 	}
379 	/// Return the numeric data represented by this node.
380 	real evaluate(real[string]vars) @safe {
381 		return 1/rhs.evaluate(vars);
382 	}
383 	/// This function parses a set of nodes out of an equation string.
384 	void parse(ref string source) @safe {
385 		// this does nothing on purpose, it will not be used by default
386 	}
387 	/// Return the simplification of this node (where possible).
388 	IMathObject simplify(real[string]vars) @safe {
389 		mixin(tryEval);
390 		// it only makes sense to pass the simplification up the tree here since negation is so simple
391 		rhs = rhs.simplify(vars);
392 		return this;
393 	}
394 }
395 
396 
397 // holds the builtin functions and the number of args
398 int[string]builtins;
399 static this () {
400 	builtins = ["cos"[]:1, "tan":1, "sin":1, "cosh":1, "tanh":1, "sinh":1, "acos":1, "atan":1, "asin":1, "acosh":1, "atanh":1, "asinh":1, "pow":2, "log":1, "ln":1, "int":1, "frac":1, "abs":1, "sqrt":1, "exp":1, "gamma":1, "mod":2];
401 }
402 class MOIdentifier:IMathObject {
403 	string identifier;
404 	IMathObject[]args;
405 	this (ref string c) @safe {
406 		parse(c);
407 	}
408 	this (string id,IMathObject[]inargs) @safe {
409 		identifier = id;
410 		args = inargs;
411 	}
412 	/// Return the string that was used to generate this node.
413 	override string toString() @safe {
414 		auto ret = identifier;
415 		if (args.length) {
416 			ret ~= "(";
417 			foreach(arg;args) ret ~= arg.toString();
418 			ret ~= ")";
419 		}
420 		return ret;
421 	}
422 	bool isBuiltin(string fun) @safe {
423 		return (fun in builtins) != null;
424 	}
425 	/// Return the numeric data represented by this node.
426 	real evaluate(real[string]vars) @safe {
427 		// ensure we have the right number of args if it's a function
428 		if (isBuiltin(identifier) && args.length != builtins[identifier]) {
429 			throw new MathParseError("Wrong number of arguments for "~identifier);
430 		}
431 		switch (identifier) {
432 		case "cos":
433 			return cos(args[0].evaluate(vars));
434 		case "tan":
435 			return tan(args[0].evaluate(vars));
436 		case "sin":
437 			return sin(args[0].evaluate(vars));
438 		case "cosh":
439 			return cosh(args[0].evaluate(vars));
440 		case "tanh":
441 			return tanh(args[0].evaluate(vars));
442 		case "sinh":
443 			return sinh(args[0].evaluate(vars));
444 		case "acos":
445 			return acos(args[0].evaluate(vars));
446 		case "atan":
447 			return atan(args[0].evaluate(vars));
448 		case "asin":
449 			return asin(args[0].evaluate(vars));
450 		case "acosh":
451 			return acosh(args[0].evaluate(vars));
452 		case "atanh":
453 			return atanh(args[0].evaluate(vars));
454 		case "asinh":
455 			return asinh(args[0].evaluate(vars));
456 		case "pow":
457 			return pow(args[0].evaluate(vars),args[1].evaluate(vars));
458 		case "log":
459 			return log10(args[0].evaluate(vars));
460 		case "ln":
461 			return log(args[0].evaluate(vars));
462 		case "abs":
463 			return abs(args[0].evaluate(vars));
464 		case "sqrt":
465 			return sqrt(args[0].evaluate(vars));
466 		case "exp":
467 			return exp(args[0].evaluate(vars));
468 		case "gamma":
469 			return gamma(args[0].evaluate(vars));
470 		case "int":
471 			auto tmp = args[0].evaluate(vars);
472 			return tmp-(tmp%1);
473 		case "frac":
474 			return args[0].evaluate(vars)%1;
475 		case "mod":
476 			return args[0].evaluate(vars)%args[1].evaluate(vars);
477 		case "e": return E;
478 		case "pi": return PI;
479 		default:
480 			// check variables
481 			if (identifier in vars) {
482 				auto ret = vars[identifier];
483 				if (args.length == 1) {
484 					ret *= args[0].evaluate(vars);
485 				}
486 				return ret;
487 			}
488 			throw new MathParseError("Unknown identifier: "~identifier);
489 		}
490 	}
491 	/// This function parses a set of nodes out of an equation string.
492 	void parse(ref string source) @safe {
493 		string getIden(ref string src) {
494 			foreach(i,c;src) if (!(c >= 'a' && c <= 'z') && !(c >= 'A' && c <= 'Z')) {
495 				auto tmp = src[0..i];
496 				src = src[i..$];
497 				return tmp;
498 			}
499 			auto tmp = src;
500 			src = "";
501 			return tmp;
502 		}
503 		// if the identifier is our variable, don't look for parens or args
504 		identifier = toLower(getIden(source));
505 		// strip out whitespace
506 		source = stripLeft(source);
507 		// parse out parameters if necessary
508 		if (!source.length || source[0] != '(') {
509 			if (isBuiltin(identifier)) {
510 				throw new MathParseError("Couldn't parse args for "~identifier);
511 			}
512 			// this is a variable
513 			return;
514 		}
515 		int count = 0;
516 		uint laststart = 1;
517 		foreach (uint i,c;source) {
518 			switch (c) {
519 			case '(': count++; break;
520 			case ')': count--; break;
521 			default: break;
522 			case ',':
523 				// parse a new arg if we're at the first level
524 				if (count == 1) {
525 					args ~= parseMathExpr(source[laststart..i]);
526 					laststart = i+1;
527 				}
528 				break;
529 			}
530 			if (!count) {
531 				args ~= parseMathExpr(source[laststart..i]);
532 				source = source[i+1..$];
533 				if (!isBuiltin(identifier) && args.length > 1) {
534 					throw new MathParseError("Unrecognized function: "~identifier);
535 				}
536 				return;
537 			}
538 		}
539 		throw new MathParseError("Unexpected end of input: "~source);
540 	}
541 	/// Return the simplification of this node (where possible).
542 	IMathObject simplify(real[string]vars) @safe {
543 		mixin(tryEval);
544 		if (isBuiltin(identifier)) {
545 			// a function!
546 			// attempt evaluation of subnodes
547 			foreach (ref arg;args) {
548 				arg = arg.simplify(vars);
549 			}
550 			return this;
551 		} else {
552 			// a variable?
553 			if (args.length) {
554 				// try to simplify the arg/mul
555 				args[0] = args[0].simplify(vars);
556 			}
557 			if (identifier in vars) {
558 				// since we can resolve the identifier, do so
559 				auto num = new MONumber(vars[identifier]);
560 				if (!args.length) return num;
561 				// convert into a multiplication
562 				auto op = new MOOperation(MOOpType.Multiplication, [cast(IMathObject)num, new MOParens(args[0])]);
563 				// this needs parens to ensure proper display when required
564 				// and proper reparsing upon output
565 				return op.simplify(vars);
566 			}
567 			return this;
568 		}
569 	}
570 }
571 
572 class MOOperation:IMathObject {
573 	IMathObject[]operands;
574 	MOOpType op;
575 	/// Constructor that takes pre-parsed objects and an op
576 	this (MOOpType inop, IMathObject[]ops) @safe {
577 		op = inop;
578 		operands = ops;
579 	}
580 	/// Empty constructor
581 	this () @safe {}
582 	/// Constructor taking an operation
583 	this (string c) @safe {
584 		parse(c);
585 	}
586 	private bool validate() @safe {
587 		switch(op) {
588 		case MOOpType.Addition:
589 			if (operands.length < 2) throw new MathParseError("Addition requires 2 or more operands");
590 			break;
591 		case MOOpType.Multiplication:
592 			if (operands.length < 2) throw new MathParseError("Multiplication requires 2 or more operands");
593 			break;
594 		case MOOpType.Modulus:
595 			if (operands.length != 2) throw new MathParseError("Modulus requires 2 operands");
596 			break;
597 		default: throw new MathParseError("Unknown operation!");
598 		}
599 		return true;
600 	}
601 	/// Return the string that was used to generate this node.
602 	override string toString() @safe {
603 		string[]ret;
604 		validate();
605 		foreach (oper;operands) {
606 			ret ~= oper.toString();
607 		}
608 		return ret.join(.toString(op));
609 	}
610 	real doAdd(real accum,real add) @safe {
611 		return accum+add;
612 	}
613 	real doMul(real accum,real mul) @safe {
614 		return accum*mul;
615 	}
616 	/// Return the numeric data represented by this node.
617 	real evaluate(real[string]vars) @safe {
618 		real delegate(real n1,real n2) @safe doOp;
619 		validate();
620 		real accum;
621 		switch(op) {
622 		case MOOpType.Addition: 
623 			doOp = &doAdd;
624 			accum = 0;
625 			break;
626 		case MOOpType.Multiplication:
627 			doOp = &doMul;
628 			accum = 1;
629 			break;
630 		case MOOpType.Modulus:
631 			return operands[0].evaluate(vars)%operands[1].evaluate(vars);
632 		default: throw new MathParseError("Unknown operation!");
633 		}
634 		foreach (operand;operands) {
635 			accum = doOp(accum,operand.evaluate(vars));
636 		}
637 		return accum;
638 	}
639 	/// This function parses a set of nodes out of an equation string.
640 	void parse(ref string source) {
641 		switch (source[0]) {
642 		case '+': op = MOOpType.Addition; break;
643 		case '*': op = MOOpType.Multiplication; break;
644 		case '^': op = MOOpType.Exponentiation; break;
645 		case '%': op = MOOpType.Modulus; break;
646 		default: throw new MathParseError("Operation not recognized: "~source[0]);
647 		}
648 	}
649 	/// Return the simplification of this node (where possible).
650 	IMathObject simplify(real[string]vars) {
651 		mixin(tryEval);
652 		// simplify operands
653 		foreach (ref myop;operands) {
654 			myop = myop.simplify(vars);
655 		}
656 		// combine numerics
657 		real delegate(real n1,real n2) @safe doOp;
658 		validate();
659 		real accum,orig;
660 		switch(op) {
661 		case MOOpType.Addition: 
662 			doOp = &doAdd;
663 			orig = accum = 0;
664 			break;
665 		case MOOpType.Multiplication:
666 			doOp = &doMul;
667 			orig = accum = 1;
668 			break;
669 		case MOOpType.Modulus:
670 			// no further simplification for modulus
671 			return this;
672 		default: throw new MathParseError("Unknown operation!");
673 		}
674 		for (int i = 0;i < operands.length;i++) {
675 			auto tmp = cast(MONumber)operands[i];
676 			if (tmp) {
677 				accum = doOp(accum, tmp.evaluate(vars));
678 				operands[i] = null;
679 			}
680 		}
681 		// fill in the nulls
682 		for (int i = 0;i < operands.length;i++) {
683 			if (!operands[i]) {
684 				operands[i] = operands[$-1];
685 				operands.length = operands.length - 1;
686 				i--;
687 			}
688 		}
689 		// additional optimizations for multiplication
690 		if (op == MOOpType.Multiplication) {
691 			// if mul by 0
692 			if (accum == 0) return new MONumber(0);
693 			// if mul by -1
694 			if (accum == -1) {
695 				auto neg = new MONegation();
696 				auto par = new MOParens(this);
697 				// wrap this in a MONegation and return it
698 				if (operands.length == 1) par.holder = operands[0];
699 				neg.rhs = par;
700 				return neg;
701 			}
702 		}
703 		// identity operation simplification for both addition and multiplication
704 		if (orig != accum) {
705 			operands ~= new MONumber(accum);
706 		}
707 		// if we've optimized out all but one parameter, this operation is irrelevant
708 		if (operands.length == 1) return operands[0];
709 		return this;
710 	}
711 	void flatten() @safe {
712 		// flatten everything we can
713 		for (int i = 0;i < operands.length;i++) {
714 			auto tmp = cast(MOOperation)operands[i];
715 			if (tmp) {
716 				if (tmp.op == op) {
717 					operands ~= tmp.operands;
718 					operands[i] = null;
719 				} else {
720 					tmp.flatten();
721 				}
722 			}
723 		}
724 		// fill in the nulls
725 		for (int i = 0;i < operands.length;i++) {
726 			if (!operands[i]) {
727 				operands[i] = operands[$-1];
728 				operands.length = operands.length - 1;
729 				i--;
730 			}
731 		}
732 	}
733 }
734 
735 class MOParens:IMathObject {
736 	IMathObject holder;
737 	/// Constructor that takes a pre-parsed object
738 	this(IMathObject obj) @safe {
739 		holder = obj;
740 	}
741 	/// Constructor that eats an input string
742 	this(ref string input) @safe {
743 		parse(input);
744 	}
745 	/// Return the string that was used to generate this node.
746 	override string toString() @safe {
747 		return "("~holder.toString()~")";
748 	}
749 	/// Return the numeric data represented by this node.
750 	real evaluate(real[string]vars) @safe {
751 		return holder.evaluate(vars);
752 	}
753 	/// This function parses a set of nodes out of an equation string.
754 	void parse(ref string input) @safe {
755 		// find the end of the paren that opens here
756 		if (!input.length || input[0] != '(') {
757 			throw new MathParseError("Couldn't instantiate parens");
758 		}
759 		int count = 0;
760 		foreach (i,c;input) {
761 			switch (c) {
762 			case '(': count++; break;
763 			case ')': count--; break;
764 			default: break;
765 			}
766 			if (!count) {
767 				holder = parseMathExpr(input[1..i]);
768 				// skip the closing paren
769 				input = input[i+1..$];
770 				return;
771 			}
772 		}
773 		throw new MathParseError("Unexpected end of input: "~input);
774 	}
775 	/// Return the simplification of this node (where possible).
776 	IMathObject simplify(real[string]vars) @safe {
777 		mixin(tryEval);
778 		// it only makes sense to pass the simplification up the tree here, parens perform no real ops
779 		holder = holder.simplify(vars);
780 		return this;
781 	}
782 }
783 
784 class MONumber:IMathObject {
785 	real _data;
786 	/// Empty constructor
787 	this () @safe {}
788 	/// Real constructor
789 	this (real indata) @safe {_data = indata;}
790 	/// Constructor that eats an input string
791 	this (ref string input) @safe {
792 		parse(input);
793 	}
794 	/// Return the string that was used to generate this node.
795 	override string toString() @safe {
796 		return _data.to!string;
797 	}
798 	/// Return the numeric data represented by this node.
799 	real evaluate(real[string]vars) @safe {
800 		return _data;
801 	}
802 	/// This function parses a number out of a string and eats characters as it goes, hence the ref string parameter.
803 	void parse(ref string source) @safe {
804 		// this parser sucks...
805 		int i = 0;
806 		// sift through whole numerics
807 		if (i < source.length && source[i] <= '9' && source[i] >= '0') {
808 			while (i < source.length && source[i] >= '0' && source[i] <= '9') i++;
809 		} else if (i < source.length && source[i] != '.') throw new MathParseError("A numeric parse error occurred while parsing the numeric beginning at: "~source);
810 		// if the next char is not a '.', we know we're done with fractional parts 
811 		if (i < source.length) {
812 			if (source[i] == '.') {
813 				i++;
814 				while (i < source.length && source[i] >= '0' && source[i] <= '9') i++;
815 			}
816 			// if the next char is e or E, we're poking at an exponential
817 			if (i < source.length && (source[i] == 'e' || source[i] == 'E')) {
818 				i++;
819 				if (source[i] == '-' || source[i] == '+') i++;
820 				while (i< source.length && source[i] >= '0' && source[i] <= '9') i++;
821 			}
822 		}
823 		_data = source[0..i].to!real;
824 		source = stripLeft(source[i..$]);
825 	}
826 	/// Return the simplification of this node (where possible).
827 	IMathObject simplify(real[string]vars) @safe {
828 		// this is already as simpified as it gets
829 		return this;
830 	}
831 }
832 
833 version(MATHEXPR_main) {
834 void main() {}
835 }
836 
837 @safe unittest {
838 	import std.stdio : writeln;
839 	real[string]vars;
840 	void tryGood(string expr,real expected) @safe {
841 		writeln("MathExpr: "~expr~" == "~expected.to!string);
842 		assert(approxEqual(parseMathExpr(expr).evaluate(vars), expected));
843 	}
844 	void tryBad(string expr) @safe {
845 		writeln("MathExpr: "~expr~" (intentional bad expression)");
846 		try {
847 			parseMathExpr(expr).evaluate(vars);
848 			assert(0);
849 		} catch(Exception) {}
850 	}
851 	void setVar(string var,real val) @safe {
852 		vars[var] = val;
853 		writeln("Set "~var~" to "~val.to!string);
854 	}
855 	setVar("x",3);
856 	tryGood("x+x",6);
857 	tryGood("2x",6);
858 	tryGood("x+4",7);
859 	tryGood("12/4",3);
860 	tryGood("x^x",27);
861 	tryGood("-1",-1);
862 	tryGood("-x",-3);
863 	tryGood("2*-x",-6);
864 	tryGood("-x*2",-6);
865 	tryGood("-x^2",-9);
866 	tryGood("2^-x",0.125);
867 	tryGood("2^----x",8);
868 	tryGood("2x",6);
869 	tryGood("cos(x)",cos(3.0));
870 	tryGood("5cos(x)",5*cos(3.0));
871 	tryGood("3x+2",11);
872 	tryGood("(46+36+52+41+27+56+30+24+30)/9",38);
873 	tryGood("1(-0)",0);
874 	tryGood("0/1",0);
875 	writeln("MathExpr: cos(3x+2)");
876 	real t1 = parseMathExpr("cos(3x+2)").evaluate(vars),t2 = cos(11.0);
877 	writeln("got "~t1.to!string~" == "~t2.to!string);
878 	//assert(feqrel(t1,t2));
879 	setVar("y",32);
880 	tryGood("x+y",vars["x"]+vars["y"]);
881 	setVar("toaster",5);
882 	tryGood("x(4)toaster",vars["x"]*4*vars["toaster"]);
883 	tryGood("4*3/3",4);
884 	tryGood("x3x",27);
885 	tryGood("04 / 02",2);
886 	tryGood("int(4.5)",4);
887 	tryGood("frac(4.5)",0.5);
888 	tryGood("mod(4.5,1)",0.5);
889 	tryGood("4.5*1",4.5);
890 	tryGood("4.5%1",0.5);
891 	tryGood("5*4.5%3*3+1",5.5);
892 	tryBad("blah(4)");
893 	tryBad("3++3");// should probably evaluate good...
894 	tryBad("3//3");
895 	tryBad("3**3");
896 	tryBad("3^^3");
897 	tryBad("3+-*3");
898 	tryBad("3*/3");
899 	tryBad("3/*3");
900 	tryBad("3-/3");
901 	tryBad("1..4");
902 	writeln("MathExpr: Simplify 3+4");
903 	assert(parseMathExpr("3+4").simplify(vars).toString() == "7");
904 	writeln("MathExpr: Simplify x+z");
905 	assert(parseMathExpr("x+z").simplify(vars).toString() == "z + 3");
906 	writeln("MathExpr: Simplify 7(x+z)");
907 	assert(parseMathExpr("7(x+z)").simplify(vars).toString() == "(z + 3) * 7");
908 	writeln("MathExpr: Simplify 7(x+z-2y)");
909 	assert(parseMathExpr("7(x+z-2y)").simplify(vars).toString() == "(z + -61) * 7");
910 	writeln("Completed all unit tests");
911 }