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