1 /**
2 This module implements functions for the "Math" namespace in the scripting language.
3 See https://pillager86.github.io/dmildew/Math.html
4 
5 ────────────────────────────────────────────────────────────────────────────────
6 
7 Copyright (C) 2021 pillager86.rf.gd
8 
9 This program is free software: you can redistribute it and/or modify it under 
10 the terms of the GNU General Public License as published by the Free Software 
11 Foundation, either version 3 of the License, or (at your option) any later 
12 version.
13 
14 This program is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
16 PARTICULAR PURPOSE.  See the GNU General Public License for more details.
17 
18 You should have received a copy of the GNU General Public License along with 
19 this program.  If not, see <https://www.gnu.org/licenses/>.
20 */
21 module mildew.stdlib.math;
22 
23 import math=std.math;
24 
25 import mildew.environment;
26 import mildew.interpreter;
27 import mildew.types;
28 
29 /**
30  * Initializes the math library. This is called by Interpreter.initializeStdlib. Functions
31  * are stored in the global Math object and are accessed such as "Math.acos". Documentation
32  * for this library can be found at https://pillager86.github.io/dmildew/Math.html
33  * Params:
34  *  interpreter = The Interpreter object to load the namespace into.
35  */
36 public void initializeMathLibrary(Interpreter interpreter)
37 {
38     // TODO rewrite this mess with mixins
39     ScriptObject mathNamespace = new ScriptObject("Math", null, null);
40     // static members
41     mathNamespace["E"] = ScriptAny(cast(double)math.E);
42     mathNamespace["LN10"] = ScriptAny(cast(double)math.LN10);
43     mathNamespace["LN2"] = ScriptAny(cast(double)math.LN2);
44     mathNamespace["LOG10E"] = ScriptAny(cast(double)math.LOG10E);
45     mathNamespace["LOG2E"] = ScriptAny(cast(double)math.LOG2E);
46     mathNamespace["PI"] = ScriptAny(cast(double)math.PI);
47     mathNamespace["SQRT1_2"] = ScriptAny(cast(double)math.SQRT1_2);
48     mathNamespace["SQRT2"] = ScriptAny(cast(double)math.SQRT2);
49     // functions
50     mathNamespace["abs"] = ScriptAny(new ScriptFunction("Math.abs", &native_Math_abs));
51     mathNamespace["acos"] = ScriptAny(new ScriptFunction("Math.acos", &native_Math_acos));
52     mathNamespace["acosh"] = ScriptAny(new ScriptFunction("Math.acosh", &native_Math_acosh));
53     mathNamespace["asin"] = ScriptAny(new ScriptFunction("Math.asin", &native_Math_asin));
54     mathNamespace["asinh"] = ScriptAny(new ScriptFunction("Math.asinh", &native_Math_asinh));
55     mathNamespace["atan"] = ScriptAny(new ScriptFunction("Math.atan", &native_Math_atan));
56     mathNamespace["atan2"] = ScriptAny(new ScriptFunction("Math.atan2", &native_Math_atan2));
57     mathNamespace["cbrt"] = ScriptAny(new ScriptFunction("Math.cbrt", &native_Math_cbrt));
58     mathNamespace["ceil"] = ScriptAny(new ScriptFunction("Math.ceil", &native_Math_ceil));
59     mathNamespace["clz32"] = ScriptAny(new ScriptFunction("Math.clz32", &native_Math_clz32));
60     mathNamespace["cos"] = ScriptAny(new ScriptFunction("Math.cos", &native_Math_cos));
61     mathNamespace["cosh"] = ScriptAny(new ScriptFunction("Math.cosh", &native_Math_cosh));
62     mathNamespace["exp"] = ScriptAny(new ScriptFunction("Math.exp", &native_Math_exp));
63     mathNamespace["expm1"] = ScriptAny(new ScriptFunction("Math.expm1", &native_Math_expm1));
64     mathNamespace["floor"] = ScriptAny(new ScriptFunction("Math.floor", &native_Math_floor));
65     mathNamespace["fround"] = ScriptAny(new ScriptFunction("Math.fround", &native_Math_fround));
66     mathNamespace["hypot"] = ScriptAny(new ScriptFunction("Math.hypot", &native_Math_hypot));
67     mathNamespace["imul"] = ScriptAny(new ScriptFunction("Math.imul", &native_Math_imul));
68     mathNamespace["log"] = ScriptAny(new ScriptFunction("Math.log", &native_Math_log));
69     mathNamespace["log10"] = ScriptAny(new ScriptFunction("Math.log10", &native_Math_log10));
70     mathNamespace["log1p"] = ScriptAny(new ScriptFunction("Math.log1p", &native_Math_log1p));
71     mathNamespace["log2"] = ScriptAny(new ScriptFunction("Math.log2", &native_Math_log2));
72     mathNamespace["max"] = ScriptAny(new ScriptFunction("Math.max", &native_Math_max));
73     mathNamespace["min"] = ScriptAny(new ScriptFunction("Math.min", &native_Math_min));
74     mathNamespace["pow"] = ScriptAny(new ScriptFunction("Math.pow", &native_Math_pow));
75     mathNamespace["random"] = ScriptAny(new ScriptFunction("Math.random", &native_Math_random));
76     mathNamespace["round"] = ScriptAny(new ScriptFunction("Math.round", &native_Math_round));
77     mathNamespace["sign"] = ScriptAny(new ScriptFunction("Math.sign", &native_Math_sign));
78     mathNamespace["sin"] = ScriptAny(new ScriptFunction("Math.sin", &native_Math_sin));
79     mathNamespace["sinh"] = ScriptAny(new ScriptFunction("Math.sinh", &native_Math_sinh));
80     mathNamespace["sqrt"] = ScriptAny(new ScriptFunction("Math.sqrt", &native_Math_sqrt));
81     mathNamespace["tan"] = ScriptAny(new ScriptFunction("Math.tan", &native_Math_tan));
82     mathNamespace["tanh"] = ScriptAny(new ScriptFunction("Math.tanh", &native_Math_tanh));
83     mathNamespace["trunc"] = ScriptAny(new ScriptFunction("Math.trunc", &native_Math_trunc));
84     interpreter.forceSetGlobal("Math", mathNamespace, true);
85 }
86 
87 // TODO rewrite half of this mess with mixins
88 
89 private ScriptAny native_Math_abs(Environment env,
90         ScriptAny* thisObj,
91         ScriptAny[] args,
92         ref NativeFunctionError nfe)
93 {
94     if(args.length < 1)
95         return ScriptAny(double.nan);
96     if(!args[0].isNumber)
97         return ScriptAny(double.nan);
98     if(args[0].type == ScriptAny.Type.INTEGER)
99         return ScriptAny(math.abs(args[0].toValue!long));
100     return ScriptAny(math.abs(args[0].toValue!double));            
101 }
102 
103 private ScriptAny native_Math_acos(Environment env,
104         ScriptAny* thisObj,
105         ScriptAny[] args,
106         ref NativeFunctionError nfe)
107 {
108     if(args.length < 1)
109         return ScriptAny(double.nan);
110     if(!args[0].isNumber)
111         return ScriptAny(double.nan);
112     return ScriptAny(math.acos(args[0].toValue!double));
113 }
114 
115 private ScriptAny native_Math_acosh(Environment env,
116         ScriptAny* thisObj,
117         ScriptAny[] args,
118         ref NativeFunctionError nfe)
119 {
120     if(args.length < 1)
121         return ScriptAny(double.nan);
122     if(!args[0].isNumber)
123         return ScriptAny(double.nan);
124     return ScriptAny(math.acosh(args[0].toValue!double));
125 }
126 
127 private ScriptAny native_Math_asin(Environment env,
128         ScriptAny* thisObj,
129         ScriptAny[] args,
130         ref NativeFunctionError nfe)
131 {
132     if(args.length < 1)
133         return ScriptAny(double.nan);
134     if(!args[0].isNumber)
135         return ScriptAny(double.nan);
136     return ScriptAny(math.asin(args[0].toValue!double));
137 }
138 
139 private ScriptAny native_Math_asinh(Environment env,
140         ScriptAny* thisObj,
141         ScriptAny[] args,
142         ref NativeFunctionError nfe)
143 {
144     if(args.length < 1)
145         return ScriptAny(double.nan);
146     if(!args[0].isNumber)
147         return ScriptAny(double.nan);
148     return ScriptAny(math.asinh(args[0].toValue!double));
149 }
150 
151 private ScriptAny native_Math_atan(Environment env,
152         ScriptAny* thisObj,
153         ScriptAny[] args,
154         ref NativeFunctionError nfe)
155 {
156     if(args.length < 1)
157         return ScriptAny(double.nan);
158     if(!args[0].isNumber)
159         return ScriptAny(double.nan);
160     return ScriptAny(math.atan(args[0].toValue!double));
161 }
162 
163 private ScriptAny native_Math_atan2(Environment env,
164         ScriptAny* thisObj,
165         ScriptAny[] args,
166         ref NativeFunctionError nfe)
167 {
168     if(args.length < 2)
169         return ScriptAny(double.nan);
170     if(!args[0].isNumber || !args[1].isNumber)
171         return ScriptAny(double.nan);
172     return ScriptAny(math.atan2(args[0].toValue!double, args[1].toValue!double));
173 }
174 
175 private ScriptAny native_Math_cbrt(Environment env,
176         ScriptAny* thisObj,
177         ScriptAny[] args,
178         ref NativeFunctionError nfe)
179 {
180     if(args.length < 1)
181         return ScriptAny(double.nan);
182     if(!args[0].isNumber)
183         return ScriptAny(double.nan);
184     return ScriptAny(math.cbrt(args[0].toValue!double));
185 }
186 
187 private ScriptAny native_Math_ceil(Environment env,
188         ScriptAny* thisObj,
189         ScriptAny[] args,
190         ref NativeFunctionError nfe)
191 {
192     if(args.length < 1)
193         return ScriptAny(double.nan);
194     if(!args[0].isNumber)
195         return ScriptAny(double.nan);
196     if(args[0].isInteger)
197         return ScriptAny(args[0].toValue!long);
198     return ScriptAny(cast(long)math.ceil(args[0].toValue!double));
199 }
200 
201 private ScriptAny native_Math_clz32(Environment env,
202         ScriptAny* thisObj,
203         ScriptAny[] args,
204         ref NativeFunctionError nfe)
205 {
206     if(args.length < 1)
207         return ScriptAny(0);
208     if(!args[0].isNumber)
209         return ScriptAny(0);
210     immutable uint num = args[0].toValue!uint;
211     return ScriptAny(CLZ1(num));
212 }
213 
214 private ScriptAny native_Math_cos(Environment env,
215         ScriptAny* thisObj,
216         ScriptAny[] args,
217         ref NativeFunctionError nfe)
218 {
219     if(args.length < 1)
220         return ScriptAny(double.nan);
221     if(!args[0].isNumber)
222         return ScriptAny(double.nan);
223     return ScriptAny(math.cos(args[0].toValue!double));
224 }
225 
226 private ScriptAny native_Math_cosh(Environment env,
227         ScriptAny* thisObj,
228         ScriptAny[] args,
229         ref NativeFunctionError nfe)
230 {
231     if(args.length < 1)
232         return ScriptAny(double.nan);
233     if(!args[0].isNumber)
234         return ScriptAny(double.nan);
235     return ScriptAny(math.cosh(args[0].toValue!double));
236 }
237 
238 private ScriptAny native_Math_exp(Environment env,
239         ScriptAny* thisObj,
240         ScriptAny[] args,
241         ref NativeFunctionError nfe)
242 {
243     if(args.length < 1)
244         return ScriptAny(double.nan);
245     if(!args[0].isNumber)
246         return ScriptAny(double.nan);
247     return ScriptAny(math.exp(args[0].toValue!double));
248 }
249 
250 private ScriptAny native_Math_expm1(Environment env,
251         ScriptAny* thisObj,
252         ScriptAny[] args,
253         ref NativeFunctionError nfe)
254 {
255     if(args.length < 1)
256         return ScriptAny(double.nan);
257     if(!args[0].isNumber)
258         return ScriptAny(double.nan);
259     return ScriptAny(math.expm1(args[0].toValue!double));
260 }
261 
262 private ScriptAny native_Math_floor(Environment env,
263         ScriptAny* thisObj,
264         ScriptAny[] args,
265         ref NativeFunctionError nfe)
266 {
267     if(args.length < 1)
268         return ScriptAny(double.nan);
269     if(!args[0].isNumber)
270         return ScriptAny(double.nan);
271     if(args[0].isInteger)
272         return ScriptAny(args[0].toValue!long);
273     return ScriptAny(cast(long)math.floor(args[0].toValue!double));
274 }
275 
276 private ScriptAny native_Math_fround(Environment env,
277         ScriptAny* thisObj,
278         ScriptAny[] args,
279         ref NativeFunctionError nfe)
280 {
281     if(args.length < 1)
282         return ScriptAny(double.nan);
283     if(!args[0].isNumber)
284         return ScriptAny(double.nan);
285     immutable float f = args[0].toValue!float;
286     return ScriptAny(f);
287 }
288 
289 private ScriptAny native_Math_hypot(Environment env,
290         ScriptAny* thisObj,
291         ScriptAny[] args,
292         ref NativeFunctionError nfe)
293 {
294     double sum = 0;
295     foreach(arg ; args)
296     {
297         if(!arg.isNumber)
298             return ScriptAny(double.nan);
299         sum += arg.toValue!double * arg.toValue!double;
300     }
301     return ScriptAny(math.sqrt(sum));
302 }
303 
304 private ScriptAny native_Math_imul(Environment env,
305         ScriptAny* thisObj,
306         ScriptAny[] args,
307         ref NativeFunctionError nfe)
308 {
309     if(args.length < 2)
310         return ScriptAny(double.nan);
311     if(!args[0].isNumber || !args[1].isNumber)
312         return ScriptAny(double.nan);
313     immutable a = args[0].toValue!int;
314     immutable b = args[1].toValue!int;
315     return ScriptAny(a * b);
316 }
317 
318 private ScriptAny native_Math_log(Environment env,
319         ScriptAny* thisObj,
320         ScriptAny[] args,
321         ref NativeFunctionError nfe)
322 {
323     if(args.length < 1)
324         return ScriptAny(double.nan);
325     if(!args[0].isNumber)
326         return ScriptAny(double.nan);
327     return ScriptAny(math.log(args[0].toValue!double));
328 }
329 
330 private ScriptAny native_Math_log10(Environment env,
331         ScriptAny* thisObj,
332         ScriptAny[] args,
333         ref NativeFunctionError nfe)
334 {
335     if(args.length < 1)
336         return ScriptAny(double.nan);
337     if(!args[0].isNumber)
338         return ScriptAny(double.nan);
339     return ScriptAny(math.log10(args[0].toValue!double));
340 }
341 
342 private ScriptAny native_Math_log1p(Environment env,
343         ScriptAny* thisObj,
344         ScriptAny[] args,
345         ref NativeFunctionError nfe)
346 {
347     if(args.length < 1)
348         return ScriptAny(double.nan);
349     if(!args[0].isNumber)
350         return ScriptAny(double.nan);
351     return ScriptAny(math.log1p(args[0].toValue!double));
352 }
353 
354 private ScriptAny native_Math_log2(Environment env,
355         ScriptAny* thisObj,
356         ScriptAny[] args,
357         ref NativeFunctionError nfe)
358 {
359     if(args.length < 1)
360         return ScriptAny(double.nan);
361     if(!args[0].isNumber)
362         return ScriptAny(double.nan);
363     return ScriptAny(math.log2(args[0].toValue!double));
364 }
365 
366 private ScriptAny native_Math_max(Environment env,
367         ScriptAny* thisObj,
368         ScriptAny[] args,
369         ref NativeFunctionError nfe)
370 {
371     import std.algorithm: max;
372     if(args.length < 1)
373         return ScriptAny(double.nan);
374     if(!args[0].isNumber)
375         return ScriptAny(double.nan);
376     double maxNumber = args[0].toValue!double;
377     for(size_t i = 1; i < args.length; ++i)
378     {
379         if(!args[i].isNumber)
380             return ScriptAny.UNDEFINED;
381         immutable temp = args[i].toValue!double;
382         if(temp > maxNumber)
383             maxNumber = temp;
384     }
385     return ScriptAny(maxNumber);
386 }
387 
388 private ScriptAny native_Math_min(Environment env,
389         ScriptAny* thisObj,
390         ScriptAny[] args,
391         ref NativeFunctionError nfe)
392 {
393     import std.algorithm: max;
394     if(args.length < 1)
395         return ScriptAny(double.nan);
396     if(!args[0].isNumber)
397         return ScriptAny(double.nan);
398     double minNumber = args[0].toValue!double;
399     for(size_t i = 1; i < args.length; ++i)
400     {
401         if(!args[i].isNumber)
402             return ScriptAny.UNDEFINED;
403         immutable temp = args[i].toValue!double;
404         if(temp < minNumber)
405             minNumber = temp;
406     }
407     return ScriptAny(minNumber);
408 }
409 
410 private ScriptAny native_Math_pow(Environment env,
411         ScriptAny* thisObj,
412         ScriptAny[] args,
413         ref NativeFunctionError nfe)
414 {
415     if(args.length < 2)
416         return ScriptAny(double.nan);
417     if(!args[0].isNumber || !args[1].isNumber)
418         return ScriptAny(double.nan);
419     return ScriptAny(math.pow(args[0].toValue!double, args[1].toValue!double));
420 }
421 
422 private ScriptAny native_Math_random(Environment env,
423         ScriptAny* thisObj,
424         ScriptAny[] args,
425         ref NativeFunctionError nfe)
426 {
427     import std.random : uniform;
428     return ScriptAny(uniform(0.0, 1.0));
429 }
430 
431 private ScriptAny native_Math_round(Environment env,
432         ScriptAny* thisObj,
433         ScriptAny[] args,
434         ref NativeFunctionError nfe)
435 {
436     if(args.length < 1)
437         return ScriptAny(double.nan);
438     if(!args[0].isNumber)
439         return ScriptAny(double.nan);
440     if(args[0].isInteger)
441         return ScriptAny(args[0].toValue!long);
442     return ScriptAny(cast(long)math.round(args[0].toValue!double));
443 }
444 
445 private ScriptAny native_Math_sign(Environment env,
446         ScriptAny* thisObj,
447         ScriptAny[] args,
448         ref NativeFunctionError nfe)
449 {
450     if(args.length < 1)
451         return ScriptAny(double.nan);
452     if(!args[0].isNumber)
453         return ScriptAny(double.nan);
454     immutable num = args[0].toValue!double;
455     if(num < 0)
456         return ScriptAny(-1);
457     else if(num > 0)
458         return ScriptAny(1);
459     else
460         return ScriptAny(0);
461 }
462 
463 private ScriptAny native_Math_sin(Environment env,
464         ScriptAny* thisObj,
465         ScriptAny[] args,
466         ref NativeFunctionError nfe)
467 {
468     if(args.length < 1)
469         return ScriptAny(double.nan);
470     if(!args[0].isNumber)
471         return ScriptAny(double.nan);
472     return ScriptAny(math.sin(args[0].toValue!double));
473 }
474 
475 private ScriptAny native_Math_sinh(Environment env,
476         ScriptAny* thisObj,
477         ScriptAny[] args,
478         ref NativeFunctionError nfe)
479 {
480     if(args.length < 1)
481         return ScriptAny(double.nan);
482     if(!args[0].isNumber)
483         return ScriptAny(double.nan);
484     return ScriptAny(math.sinh(args[0].toValue!double));
485 }
486 
487 private ScriptAny native_Math_sqrt(Environment env,
488         ScriptAny* thisObj,
489         ScriptAny[] args,
490         ref NativeFunctionError nfe)
491 {
492     if(args.length < 1)
493         return ScriptAny.UNDEFINED;
494     if(!args[0].isNumber)
495         return ScriptAny.UNDEFINED;
496     return ScriptAny(math.sqrt(args[0].toValue!double));
497 }
498 
499 private ScriptAny native_Math_tan(Environment env,
500         ScriptAny* thisObj,
501         ScriptAny[] args,
502         ref NativeFunctionError nfe)
503 {
504     if(args.length < 1)
505         return ScriptAny(double.nan);
506     if(!args[0].isNumber)
507         return ScriptAny(double.nan);
508     return ScriptAny(math.tan(args[0].toValue!double));
509 }
510 
511 private ScriptAny native_Math_tanh(Environment env,
512         ScriptAny* thisObj,
513         ScriptAny[] args,
514         ref NativeFunctionError nfe)
515 {
516     if(args.length < 1)
517         return ScriptAny(double.nan);
518     if(!args[0].isNumber)
519         return ScriptAny(double.nan);
520     return ScriptAny(math.tanh(args[0].toValue!double));
521 }
522 
523 private ScriptAny native_Math_trunc(Environment env,
524         ScriptAny* thisObj,
525         ScriptAny[] args,
526         ref NativeFunctionError nfe)
527 {
528     if(args.length < 1)
529         return ScriptAny(double.nan);
530     if(!args[0].isNumber)
531         return ScriptAny(double.nan);
532     return ScriptAny(math.trunc(args[0].toValue!double));
533 }
534 
535 
536 /// software implementation of CLZ32 because I don't know assembly nor care to lock DMildew to a specific CPU
537 /// courtesy of https://embeddedgurus.com/state-space/2014/09/fast-deterministic-and-portable-counting-leading-zeros/
538 pragma(inline, true) 
539 uint CLZ1(uint x) 
540 {
541     static immutable ubyte[] clz_lkup = [
542         32U, 31U, 30U, 30U, 29U, 29U, 29U, 29U,
543         28U, 28U, 28U, 28U, 28U, 28U, 28U, 28U,
544         27U, 27U, 27U, 27U, 27U, 27U, 27U, 27U,
545         27U, 27U, 27U, 27U, 27U, 27U, 27U, 27U,
546         26U, 26U, 26U, 26U, 26U, 26U, 26U, 26U,
547         26U, 26U, 26U, 26U, 26U, 26U, 26U, 26U,
548         26U, 26U, 26U, 26U, 26U, 26U, 26U, 26U,
549         26U, 26U, 26U, 26U, 26U, 26U, 26U, 26U,
550         25U, 25U, 25U, 25U, 25U, 25U, 25U, 25U,
551         25U, 25U, 25U, 25U, 25U, 25U, 25U, 25U,
552         25U, 25U, 25U, 25U, 25U, 25U, 25U, 25U,
553         25U, 25U, 25U, 25U, 25U, 25U, 25U, 25U,
554         25U, 25U, 25U, 25U, 25U, 25U, 25U, 25U,
555         25U, 25U, 25U, 25U, 25U, 25U, 25U, 25U,
556         25U, 25U, 25U, 25U, 25U, 25U, 25U, 25U,
557         25U, 25U, 25U, 25U, 25U, 25U, 25U, 25U,
558         24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
559         24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
560         24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
561         24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
562         24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
563         24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
564         24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
565         24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
566         24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
567         24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
568         24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
569         24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
570         24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
571         24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
572         24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U,
573         24U, 24U, 24U, 24U, 24U, 24U, 24U, 24U
574     ];
575     uint n;
576     if (x >= (1U << 16)) 
577     {
578         if (x >= (1U << 24)) 
579         {
580             n = 24U;
581         }
582         else 
583         {
584             n = 16U;
585         }
586     }
587     else 
588     {
589         if (x >= (1U << 8)) 
590         {
591             n = 8U;
592         }
593         else 
594         {
595             n = 0U;
596         }
597     }
598     return cast(uint)clz_lkup[x >> n] - n;
599 }