1 module calcool.expression;
2 
3 import std.math;
4 import std.algorithm;
5 
6 import calcool.exceptions : ParseException;
7 
8 public:
9 
10 interface Expression {
11     real evaluate() const;
12     string toString() @safe const;
13 }
14 
15 class FuncExpression : Expression {
16 private:
17     Expression param;
18     string name;
19 
20     static immutable {
21         real function(real) pure @safe nothrow @nogc[string] funcs;
22         auto trigonometry = ["sin", "cos", "tan",];
23         auto other = ["sqrt", "floor", "ceil", "log", "log2", "log10", "exp"];
24     }
25 
26     shared static this() {
27         static foreach (i; trigonometry ~ other) {
28             funcs[i] = mixin("&" ~ i);
29         }
30         funcs["abs"] = &abs!real;
31     }
32 
33 public:
34 
35     this(string n, Expression p) {
36         param = p;
37         name = n;
38         if (cast(EolExpression) param) {
39             throw new ParseException("Operand needed");
40         }
41     }
42 
43     override real evaluate() const {
44         import std.algorithm : canFind;
45 
46         if (name in funcs) {
47             if (trigonometry.canFind(name)) {
48                 auto ret = funcs[name](param.evaluate() * PI / 180);
49                 if (ret > -1.0e-17 && ret < 1.0e-17)
50                     ret = 0;
51                 return ret;
52             }
53             return funcs[name](param.evaluate());
54         }
55         throw new ParseException("Unknown function call");
56     }
57 
58     override string toString() @safe const {
59         return name ~ "(" ~ param.toString() ~ ")";
60     }
61 }
62 
63 class OperatorExpression(string op) : Expression
64         if (["+", "-", "/", "*", "^^"].canFind(op)) {
65 
66     private Expression left;
67     private Expression right;
68 
69     this(Expression l, Expression r) {
70         left = l;
71         right = r;
72         if (cast(EolExpression) right) {
73             throw new ParseException("Operand needed");
74         }
75     }
76 
77     override real evaluate() const {
78         const rhs = right.evaluate();
79         static if (op == "/") {
80             if (rhs == 0) {
81                 throw new ParseException("Devide by zero");
82             }
83         }
84         return mixin("left.evaluate()" ~ op ~ "rhs");
85     }
86 
87     override string toString() @safe const {
88         return left.toString() ~ op ~ right.toString();
89     }
90 }
91 
92 class GroupExpression : Expression {
93     private Expression inside;
94 
95     this(Expression i) {
96         inside = i;
97     }
98 
99     override real evaluate() const {
100         return inside.evaluate();
101     }
102 
103     override string toString() @safe const {
104         return "(" ~ inside.toString() ~ ")";
105     }
106 }
107 
108 class NumberExpression : Expression {
109     private real num;
110 
111     this(real n) {
112         num = n;
113     }
114 
115     override real evaluate() const {
116         return num;
117     }
118 
119     override string toString() @safe const {
120         import std.conv : to;
121 
122         return num.to!string;
123     }
124 }
125 
126 class NegateExpression : Expression {
127     private Expression right;
128 
129     this(Expression r) {
130         right = r;
131         if (cast(EolExpression) right) {
132             throw new ParseException("Operand needed");
133         }
134     }
135 
136     override real evaluate() const {
137         return -right.evaluate();
138     }
139 
140     override string toString() @safe const {
141         return "-" ~ right.toString();
142     }
143 }
144 
145 class EolExpression : Expression {
146     import calcool.exceptions : EolException;
147 
148     override real evaluate() const {
149         throw new EolException();
150     }
151 
152     override string toString() @safe const {
153         return "";
154     }
155 }