1 /// Simple and easy mathematical equation evaluator.
2 module tern.legacy.eval;
3 
4 // TODO: Add log, ln, sin, cos, tan, and pi
5 import tern.string;
6 import std.conv;
7 import std.traits;
8 
9 public:
10 static:
11 pure:
12 /**
13  * Evaluates a simple (does not contain functions) arithmetic expression.
14  *
15  * Params:
16  *  exp = The arithmetic expression to be evaluated.
17  *  op = Specific operation to be evaluated out of `exp`.
18  *
19  * Returns:
20  *  The evaluated arithmetic expression.
21  */
22 string eval(string exp, string op)
23 {
24     immutable uint[string] priority = [
25         "*": 0,
26         "^^": 1,
27         "/": 2,
28         "%": 3,
29         "+": 4,
30         "-": 5,
31         "<<": 6,
32         ">>": 7,
33         "<": 8,
34         "<=": 9,
35         ">": 10,
36         ">=": 11,
37         "==": 12,
38         "!=": 13,
39         "&": 14,
40         "|": 15,
41         "&&": 16,
42         "||": 17
43     ];
44 
45     string[] words = exp.split(' ');
46     if (words.length < 3)
47         return exp;
48     
49     size_t findMatchingParenthesis(size_t start)
50     {
51         int depth = 0;
52         for (size_t i = start; i < words.length; i++)
53         {
54             if (words[i][0] == '(')
55                 depth++;
56             else if (words[i][$-1] == ')')
57             {
58                 depth--;
59                 if (depth == 0)
60                     return i + 1;
61             }
62         }
63         return words.length;
64     }
65 
66     for (size_t i = 0; i < words.length; i++)
67     {
68         if (words[i] == null)
69             continue;
70             
71         if (words[i][0] == '(')
72         {
73             size_t end = findMatchingParenthesis(i);
74             exp = exp.replace(words[i..end].join(' '), eval(words[i..end].join(' ')[1..$-1]));
75             i = end - 1;
76         }
77     }
78 
79     words = exp.split(' ');
80     if (words.length < 3)
81         return exp;
82 
83     foreach (i, word; words)
84     {
85         if (i >= words.length - 1)
86             return words.join(' ');
87         
88         if (i > 1 && words[i - 2] in priority && priority[words[i - 2]] < priority[op])
89             continue;
90 
91         if (word == op)
92         {
93             if (words[i - 1].startsWith('~') && words[i - 1].filter!(x => x.isDigit).range.length == words[i - 1].length - 1)
94                 words[i - 1] = (~words[i - 1][1..$].to!ulong).to!string;
95 
96             if (words[i + 1].startsWith('~') && words[i + 1].filter!(x => x.isDigit).range.length == words[i + 1].length - 1)
97                 words[i + 1] = (~words[i + 1][1..$].to!ulong).to!string;
98 
99             bool lhsNumeric = words[i - 1].filter!(x => x.isDigit).range.length == words[i - 1].length;
100             bool rhsNumeric = words[i + 1].filter!(x => x.isDigit).range.length == words[i + 1].length;
101 
102             if (!lhsNumeric || !rhsNumeric)
103                 continue;
104 
105             switch (op)
106             {
107                 case "*":
108                     words[i - 1] = (words[i - 1].to!ulong * words[i + 1].to!ulong).to!string;
109                     words = words[0..i]~words[(i + 2)..$];
110                     break;
111                 case "^^":
112                     words[i - 1] = (words[i - 1].to!ulong ^^ words[i + 1].to!ulong).to!string;
113                     words = words[0..i]~words[(i + 2)..$];
114                     break;
115                 case "/":
116                     words[i - 1] = (words[i - 1].to!ulong / words[i + 1].to!ulong).to!string;
117                     words = words[0..i]~words[(i + 2)..$];
118                     break;
119                 case "%":
120                     words[i - 1] = (words[i - 1].to!ulong % words[i + 1].to!ulong).to!string;
121                     words = words[0..i]~words[(i + 2)..$];
122                     break;
123                 case "+":
124                     words[i - 1] = (words[i - 1].to!ulong + words[i + 1].to!ulong).to!string;
125                     words = words[0..i]~words[(i + 2)..$];
126                     break;
127                 case "-":
128                     words[i - 1] = (words[i - 1].to!ulong - words[i + 1].to!ulong).to!string;
129                     words = words[0..i]~words[(i + 2)..$];
130                     break;
131                 case "<<":
132                     words[i - 1] = (words[i - 1].to!ulong << words[i + 1].to!ulong).to!string;
133                     words = words[0..i]~words[(i + 2)..$];
134                     break;
135                 case ">>":
136                     words[i - 1] = (words[i - 1].to!ulong >> words[i + 1].to!ulong).to!string;
137                     words = words[0..i]~words[(i + 2)..$];
138                     break;
139                 case "<":
140                     words[i - 1] = (words[i - 1].to!ulong < words[i + 1].to!ulong).to!string;
141                     words = words[0..i]~words[(i + 2)..$];
142                     break;
143                 case "<=":
144                     words[i - 1] = (words[i - 1].to!ulong <= words[i + 1].to!ulong).to!string;
145                     words = words[0..i]~words[(i + 2)..$];
146                     break;
147                 case ">":
148                     words[i - 1] = (words[i - 1].to!ulong > words[i + 1].to!ulong).to!string;
149                     words = words[0..i]~words[(i + 2)..$];
150                     break;
151                 case ">=":
152                     words[i - 1] = (words[i - 1].to!ulong >= words[i + 1].to!ulong).to!string;
153                     words = words[0..i]~words[(i + 2)..$];
154                     break;
155                 case "==":
156                     words[i - 1] = (words[i - 1].to!ulong == words[i + 1].to!ulong).to!string;
157                     words = words[0..i]~words[(i + 2)..$];
158                     break;
159                 case "!=":
160                     words[i - 1] = (words[i - 1].to!ulong != words[i + 1].to!ulong).to!string;
161                     words = words[0..i]~words[(i + 2)..$];
162                     break;
163                 case "^":
164                     words[i - 1] = (words[i - 1].to!ulong ^ words[i + 1].to!ulong).to!string;
165                     words = words[0..i]~words[(i + 2)..$];
166                     break;
167                 case "&":
168                     words[i - 1] = (words[i - 1].to!ulong & words[i + 1].to!ulong).to!string;
169                     words = words[0..i]~words[(i + 2)..$];
170                     break;
171                 case "|":
172                     words[i - 1] = (words[i - 1].to!ulong | words[i + 1].to!ulong).to!string;
173                     words = words[0..i]~words[(i + 2)..$];
174                     break;
175                 case "&&":
176                     words[i - 1] = (words[i - 1].to!ulong && words[i + 1].to!ulong).to!string;
177                     words = words[0..i]~words[(i + 2)..$];
178                     break;
179                 case "||":
180                     words[i - 1] = (words[i - 1].to!ulong || words[i + 1].to!ulong).to!string;
181                     words = words[0..i]~words[(i + 2)..$];
182                     break;
183                 default:
184                     assert(0);
185             }
186         }
187     }
188     return words.join(' ');
189 }
190 
191 /**
192  * Evaluates a simple (does not contain functions) arithmetic expression.
193  *
194  * Params:
195  *  exp = The arithmetic expression to be evaluated.
196  *
197  * Returns:
198  *  The evaluated arithmetic expression.
199  *
200  * Example:
201  *  ```d
202  *  auto result = eval("(5 + 3) * 2");
203  *  assert(result == "16");
204  *
205  *  auto expr = "3 * (4 + 2) / 3";
206  *  auto simplifiedExpr = eval(expr);
207  *  assert(simplifiedExpr == "6");
208  *  ```
209  */
210 string eval(string exp)
211 {
212     static foreach (op; ["*", "^^", "/", "%", "+", "-", "<<", ">>", "<", "<=", ">", ">=", "==", "!=", "&", "|", "&&", "||"])
213         exp = eval(exp, op);
214     return exp;
215 }