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