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 }