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 }