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 }