1 module dutils.math.core; 2 3 version(DLL) 4 { 5 mixin("export:"); 6 } 7 8 else 9 { 10 mixin("public:"); 11 } 12 13 import std.complex; 14 15 alias Operator = dstring function(dstring input); 16 17 //List of all operations. 18 package shared Operator[dstring] opList; 19 20 //List of all functions. 21 package shared dstring[dstring] funcList; 22 23 //Functions that may not be deleted. 24 package shared dstring[dstring] noTouch; 25 26 //Initialize the basic arithmetic operations to null: these will be handled by operator overloading. 27 shared static this() 28 { 29 opList["+"d] = null; 30 opList["-"d] = null; 31 opList["*"d] = null; 32 opList["/"d] = null; 33 opList["^^"d] = null; 34 } 35 36 //Add the neccessary functions to noTouch. 37 shared static this() 38 { 39 noTouch["ln(x) = num(num)"d] = null; 40 noTouch["sin(x) = num(num)"d] = null; 41 noTouch["cos(x) = num(num)"d] = null; 42 noTouch["tan(x) = num(num)"d] = null; 43 noTouch["csc(x) = num(num)"d] = null; 44 noTouch["sec(x) = num(num)"d] = null; 45 noTouch["cot(x) = num(num)"d] = null; 46 noTouch["Γ(x) = num(num)"d] = null; 47 noTouch["δ(x) = num(num)"d] = null; 48 } 49 //An interface that serves as the basis for all math types. 50 package interface mType 51 { 52 mType opBinary(string op)(mType rhs); 53 mType opBinary(string op)(Complex!real rhs); 54 55 mType opBinaryRight(string op)(mType lhs); 56 mType opBinaryRight(string op)(Complex!real lhs); 57 58 mType opUnary(string op); 59 } 60 61 /************************************************** 62 * Validates the math library's fucntion syntax. 63 * 64 * TODO: Make syntax rules modular. 65 * 66 * Params: 67 * funcbody = The body of the function to validate. 68 * Returns: true if the function has correct syntax, 69 * false otherwise. 70 */ 71 bool validateFunction(dstring funcbody) nothrow pure @safe @nogc 72 { 73 import std.uni : isSpace, isNumber; 74 75 long indentation = 0; //Indentation level; 76 bool isOp = false; //If there is a current operator being used. 77 bool isNum = false; //If there is currently a number in use. 78 bool isDec = false; //If there is already a decimal point in the number. 79 bool isx = false; //If the current number is 'x'. 80 bool isEverNum = false; //If there is at least one number in the function body. 81 ubyte powCount = 0; //Internal counter for ^^ operator. 82 83 foreach(c; funcbody) 84 { 85 Switch: switch(c) 86 { 87 case cast(dchar)'x': 88 isx = true; 89 isOp = false; 90 isNum = true; 91 isEverNum = true; 92 isDec = false; 93 break; 94 95 static foreach(x; [cast(dchar)'+', cast(dchar)'-', cast(dchar)'*', cast(dchar)'/']) //Iterate over validating the basic four operators. 96 { 97 case x: 98 if(isOp || !isNum) 99 return false; 100 101 isOp = true; 102 powCount = 0; 103 isNum = false; 104 isDec = false; 105 isx = false; 106 break Switch; 107 } 108 109 case cast(dchar)'^': //Take care of exponents. 110 if(isOp || !isNum) 111 return false; 112 113 switch(powCount) 114 { 115 case 0: 116 ++powCount; 117 break; 118 case 1: 119 ++powCount; 120 isOp = true; 121 break; 122 default: 123 return false; 124 } 125 isNum = false; 126 isDec = false; 127 isx = false; 128 break; 129 case cast(dchar)'(': //Indentation 130 ++indentation; 131 break; 132 case cast(dchar)')': 133 --indentation; 134 break; 135 default: 136 if(isNumber(c) || c == cast(dchar)'i') //Imaginary numbers are numbers. 137 { 138 isNum = true; 139 isx = false; 140 isEverNum = true; 141 isOp = false; 142 break; 143 } 144 else if(c == cast(dchar)'.') //Dissallow multiple decimal points. 145 { 146 if(!isDec && isNum && !isx) 147 isDec = true; 148 else 149 return false; 150 break; 151 } 152 else if(isSpace(c)) 153 break; 154 else 155 return false; 156 } 157 } 158 if(indentation != 0 || !isEverNum || powCount != 0 || isOp) //Final conditions that have to be met. 159 return false; 160 return true; 161 } 162 163 /// 164 unittest 165 { 166 dstring func = "x + 1"d; 167 assert(validateFunction(func)); 168 func = "x^^2 + 2^^2 j- 3"d; 169 assert(!validateFunction(func)); 170 func = "x.3"; 171 assert(!validateFunction(func)); 172 func = "()"; 173 assert(!validateFunction(func)); 174 func = "x ^ 3"; 175 assert(!validateFunction(func)); 176 func = "x +"; 177 assert(!validateFunction(func)); 178 assert(!validateFunction("x ++ 1"d)); 179 } 180 181 /******************************************************************* 182 * Registers a function with the given name and function body. 183 * A function cannot register if its body has invalid syntax or if 184 * its definition has already been used. 185 * 186 * Params: 187 * funcdef = The definition of the function to register. 188 * funcbody = The body of the function to register. 189 * 190 * Returns: 191 * True if the function successfuly registers, false 192 * otherwise. 193 */ 194 bool registerFunction(dstring funcdef, dstring funcbody) @safe nothrow 195 { 196 import std.string : strip; 197 198 uint index = 0; 199 bool openParam = false; 200 201 funcdef = cast(dstring)(cast(string)funcdef.strip); //Strip all whitespace, so to allow the user more freedom. 202 203 Loop: 204 for (uint i = 0; i < funcdef.length; i++) //Make sure the function is defined along the lines of a(x) = num(num). 205 { 206 switch(funcdef[i]) 207 { 208 case cast(dchar)'=': 209 if(openParam) 210 return false; 211 index = i; 212 break Loop; 213 case cast(dchar)'(': 214 if(openParam) 215 return false; 216 index = i; 217 openParam = true; 218 break; 219 case cast(dchar)')': 220 if(!openParam) 221 return false; 222 openParam = false; 223 break; 224 default: 225 if(index == i-1 && openParam) 226 { 227 if(funcdef[i] != cast(dchar)'x') 228 return false; 229 } 230 } 231 } 232 233 if(funcdef[index .. $] != "= num(num)"d) 234 return false; 235 236 if(validateFunction(funcbody) && funcdef !in funcList) //Validate the function and make sure that it isn't already defined. 237 { 238 funcList[funcdef] = funcbody; 239 return true; 240 } 241 return false; 242 } 243 244 /// 245 unittest 246 { 247 dstring func = "x + 1"d; 248 assert(registerFunction("a(x) = num(num)"d,func)); 249 func = "x^^2 + 2^^2 j- 3"d; 250 assert(!registerFunction("a(x) = num(num)"d,func)); 251 func = "x.3"; 252 assert(!registerFunction("a(x) = num(num)"d,func)); 253 func = "()"; 254 assert(!registerFunction("a(x) = num(num)"d,func)); 255 func = "x ^ 3"; 256 assert(!registerFunction("a(x) = num(num)"d,func)); 257 func = "x +"; 258 assert(!registerFunction("a(x) = num(num)"d,func)); 259 assert(!registerFunction("a(x) = num(num)"d,"x ++ 1"d)); 260 assert(registerFunction("b(x) = num(num)"d, "x"d)); 261 registerFunction("a(x) = num(num)"d,func); //Fails on MacOS without this. 262 assert(!registerFunction("a(x) = num(num)"d, "x"d)); 263 } 264 265 /******************************************************* 266 * Removes a registered function given its definition. 267 * 268 * The function being removed must be registered and must 269 * not be an untouchable function. 270 * 271 * Params: 272 * funcdef = The definition of the function to remove. 273 * Returns: 274 * True if the function is succesfully removed, false 275 * otherwise. 276 */ 277 bool removeFunction(dstring funcdef) @safe @nogc nothrow 278 { 279 if(funcdef !in funcList || funcdef in noTouch) //Make sure that the function acutally exists and isn't non-removable. 280 return false; 281 funcList.remove(funcdef); 282 return true; 283 } 284 285 /// 286 unittest 287 { 288 dstring func = "x + 1"d; 289 assert(!removeFunction("c(x) = num(num)"d)); //Apparently data is saved between tests, so a(x) has to be replaced with c(x). 290 funcList["δ(x) = num(num)"d] = null; 291 assert(!removeFunction("δ(x) = num(num)"d)); 292 funcList.remove("δ(x) = num(num)"); 293 registerFunction("a(x) = num(num)"d, func); 294 assert(removeFunction("a(x) = num(num)"d)); 295 }