1 module calcool.expression; 2 3 import std.math; 4 import std.algorithm; 5 import std.string; 6 7 import calcool.exceptions : ParseException; 8 9 public: 10 11 interface Expression { 12 real evaluate() const; 13 string toString() @safe const; 14 } 15 16 class FuncExpression : Expression { 17 private: 18 Expression param; 19 string name; 20 21 static immutable { 22 real function(real) pure @safe nothrow @nogc[string] funcs; 23 auto trigonometry = ["sin", "cos", "tan", "asin", "acos", "atan",]; 24 auto other = ["sqrt", "floor", "ceil", "log", "log2", "log10", "exp", "round",]; 25 public auto funcNames() => trigonometry ~ other ~ ["abs"]; 26 } 27 28 shared static this() { 29 static foreach (i; trigonometry ~ other) { 30 funcs[i] = mixin("&" ~ i); 31 } 32 funcs["abs"] = &abs!real; 33 } 34 35 public: 36 37 this(string n, Expression p) { 38 param = p; 39 name = n; 40 if (cast(EolExpression) param) { 41 throw new ParseException("Operand needed"); 42 } 43 } 44 45 override real evaluate() const { 46 import std.algorithm : canFind; 47 48 if (name in funcs) { 49 if (trigonometry.canFind(name)) { 50 auto num = param.evaluate(); 51 const isArcFunc = name.startsWith('a'); 52 if (!isArcFunc) { 53 num = num * PI / 180; 54 } 55 auto ret = funcs[name](num); 56 if (ret >= -real.epsilon && ret <= real.epsilon) 57 ret = 0; 58 // Convert rad to deg for arc functions 59 if (isArcFunc) { 60 ret *= 180 / PI; 61 } 62 return ret; 63 } 64 return funcs[name](param.evaluate()); 65 } 66 throw new ParseException("Unknown function was called"); 67 } 68 69 override string toString() @safe const { 70 return name ~ "(" ~ param.toString() ~ ")"; 71 } 72 } 73 74 class OperatorExpression(string op) : Expression 75 if (["+", "-", "/", "*", "^^"].canFind(op)) { 76 77 private Expression left; 78 private Expression right; 79 80 this(Expression l, Expression r) { 81 left = l; 82 right = r; 83 if (cast(EolExpression) right) { 84 throw new ParseException("Operand needed"); 85 } 86 } 87 88 override real evaluate() const { 89 const rhs = right.evaluate(); 90 static if (op == "/") { 91 if (rhs == 0) { 92 throw new ParseException("Devide by zero"); 93 } 94 } 95 return mixin("left.evaluate()" ~ op ~ "rhs"); 96 } 97 98 override string toString() @safe const { 99 return left.toString() ~ ' ' ~ op[0] ~ ' ' ~ right.toString(); 100 } 101 } 102 103 class GroupExpression : Expression { 104 private Expression inside; 105 106 this(Expression i) { 107 inside = i; 108 } 109 110 override real evaluate() const { 111 return inside.evaluate(); 112 } 113 114 override string toString() @safe const { 115 return "(" ~ inside.toString() ~ ")"; 116 } 117 } 118 119 class NumberExpression : Expression { 120 private real num; 121 122 this(real n) { 123 num = n; 124 } 125 126 override real evaluate() const { 127 return num; 128 } 129 130 override string toString() @safe const { 131 import std.conv : to; 132 133 return num.to!string; 134 } 135 } 136 137 class ConstantExpression : Expression { 138 private string constant_name; 139 this(string c) { 140 import std.uni : toUpper; 141 142 constant_name = c.toUpper(); 143 } 144 145 override real evaluate() const { 146 import std.math.constants; 147 148 immutable CONSTANTS = [ 149 "E" : E, "PI" : PI, "INF" : real.infinity, "NAN" : real.nan 150 ]; 151 152 if (auto constant_value = constant_name in CONSTANTS) { 153 return *constant_value; 154 } 155 throw new ParseException("Unknown constant or variable"); 156 } 157 158 override string toString() @safe const { 159 return constant_name; 160 } 161 } 162 163 class NegateExpression : Expression { 164 private Expression right; 165 166 this(Expression r) { 167 right = r; 168 if (cast(EolExpression) right) { 169 throw new ParseException("Operand needed"); 170 } 171 } 172 173 override real evaluate() const { 174 return -right.evaluate(); 175 } 176 177 override string toString() @safe const { 178 return "-" ~ right.toString(); 179 } 180 } 181 182 class EolExpression : Expression { 183 import calcool.exceptions : EolException; 184 185 override real evaluate() const { 186 throw new EolException(); 187 } 188 189 override string toString() @safe const { 190 return ""; 191 } 192 }