1 /**
2 This module implements script functions that are stored in the global namespace.
3 See https://pillager86.github.io/dmildew/global.html for more information.
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.global;
22 
23 import mildew.environment;
24 import mildew.interpreter;
25 import mildew.types;
26 
27 /**
28  * This is called by the interpreter's initializeStdlib method to store functions in the global namespace.
29  * Documentation for these functions can be found at https://pillager86.github.io/dmildew/global.html
30  * Params:
31  *  interpreter = The Interpreter instance to load the functions into.
32  */
33 void initializeGlobalLibrary(Interpreter interpreter)
34 {
35     // experimental: runFile
36     interpreter.forceSetGlobal("runFile", new ScriptFunction("runFile", &native_runFile));
37     interpreter.forceSetGlobal("clearTimeout", new ScriptFunction("clearTimeout", &native_clearTimeout));
38     interpreter.forceSetGlobal("isdefined", new ScriptFunction("isdefined", &native_isdefined));
39     interpreter.forceSetGlobal("isFinite", new ScriptFunction("isFinite", &native_isFinite));
40     interpreter.forceSetGlobal("isNaN", new ScriptFunction("isNaN", &native_isNaN));
41     interpreter.forceSetGlobal("parseFloat", new ScriptFunction("parseFloat", &native_parseFloat));
42     interpreter.forceSetGlobal("parseInt", new ScriptFunction("parseInt", &native_parseInt));
43     interpreter.forceSetGlobal("setTimeout", new ScriptFunction("setTimeout", &native_setTimeout));
44 }
45 
46 //
47 // Global method implementations
48 //
49 
50 // experimental DO NOT USE
51 private ScriptAny native_runFile(Environment env, ScriptAny* thisObj,
52                                  ScriptAny[] args, ref NativeFunctionError nfe)
53 {
54     if(args.length < 1)
55     {
56         nfe = NativeFunctionError.WRONG_NUMBER_OF_ARGS;
57         return ScriptAny.UNDEFINED;
58     }
59     auto fileName = args[0].toString();
60     try 
61     {
62         return env.g.interpreter.evaluateFile(fileName, false, true);
63 
64     }
65     catch(Exception ex)
66     {
67         nfe = NativeFunctionError.RETURN_VALUE_IS_EXCEPTION;
68         return ScriptAny(ex.msg);
69     }
70 }
71 
72 private ScriptAny native_clearTimeout(Environment env, ScriptAny* thisObj,
73                                       ScriptAny[] args, ref NativeFunctionError nfe)
74 {
75     import mildew.vm.virtualmachine: VirtualMachine;
76     import mildew.vm.fiber: ScriptFiber;
77 
78     if(args.length < 1)
79     {
80         nfe = NativeFunctionError.WRONG_NUMBER_OF_ARGS;
81         return ScriptAny.UNDEFINED;
82     }
83     auto sfib = args[0].toNativeObject!ScriptFiber;
84     if(sfib is null)
85     {
86         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
87         return ScriptAny.UNDEFINED;
88     }
89     if(sfib.toString() != "Timeout")
90         return ScriptAny(false);
91     return ScriptAny(env.g.interpreter.vm.removeFiber(sfib));
92 }
93 
94 private ScriptAny native_isdefined(Environment env, 
95                                    ScriptAny* thisObj, 
96                                    ScriptAny[] args, 
97                                    ref NativeFunctionError nfe)
98 {
99     if(args.length < 1)
100         return ScriptAny(false);
101     auto varToLookup = args[0].toString();
102     return ScriptAny(env.variableOrConstExists(varToLookup));
103 }
104 
105 private ScriptAny native_isFinite(Environment env, ScriptAny* thisObj, 
106                                   ScriptAny[] args, ref NativeFunctionError nfe)
107 {
108     import std.math: isFinite;
109     if(args.length < 1)
110         return ScriptAny.UNDEFINED;
111     if(!args[0].isNumber)
112         return ScriptAny.UNDEFINED;
113     immutable value = args[0].toValue!double;
114     return ScriptAny(isFinite(value));
115 }
116 
117 private ScriptAny native_isNaN(Environment env, ScriptAny* thisObj,
118                                ScriptAny[] args, ref NativeFunctionError nfe)
119 {
120     import std.math: isNaN;
121     if(args.length < 1)
122         return ScriptAny.UNDEFINED;
123     if(!args[0].isNumber)
124         return ScriptAny(true);
125     immutable value = args[0].toValue!double;
126     return ScriptAny(isNaN(value));
127 }
128 
129 private ScriptAny native_parseFloat(Environment env, ScriptAny* thisObj,
130                                     ScriptAny[] args, ref NativeFunctionError nfe)
131 {
132     import std.conv: to, ConvException;
133     if(args.length < 1)
134         return ScriptAny(double.nan);
135     auto str = args[0].toString();
136     try 
137     {
138         immutable value = to!double(str);
139         return ScriptAny(value);
140     }
141     catch(ConvException)
142     {
143         return ScriptAny(double.nan);
144     }
145 }
146 
147 private ScriptAny native_parseInt(Environment env, ScriptAny* thisObj,
148                                   ScriptAny[] args, ref NativeFunctionError nfe)
149 {
150     import std.conv: to, ConvException;
151     if(args.length < 1)
152         return ScriptAny.UNDEFINED;
153     auto str = args[0].toString();
154     immutable radix = args.length > 1 ? args[1].toValue!int : 10;
155     try 
156     {
157         immutable value = to!long(str, radix);
158         return ScriptAny(value);
159     }
160     catch(ConvException)
161     {
162         return ScriptAny.UNDEFINED;
163     }
164 }
165 
166 // experimental. TODO: subclass Fiber just for the toString and name properties.
167 // TODO setInterval and a cancel function
168 private ScriptAny native_setTimeout(Environment env, ScriptAny* thisObj,
169                                     ScriptAny[] args, ref NativeFunctionError nfe)
170 {
171     import core.thread: Thread;
172     import std.datetime: dur, Clock;
173     import std.concurrency: yield;
174     import mildew.types.bindings: getLocalThis;
175 
176     // all environments are supposed to be linked to the global one. if not, there is a bug
177     auto vm = env.g.interpreter.vm;
178     if(args.length < 2)
179     {
180         nfe = NativeFunctionError.WRONG_NUMBER_OF_ARGS;
181         return ScriptAny.UNDEFINED;
182     }
183     auto func = args[0].toValue!ScriptFunction;
184     if(func is null)
185     {
186         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
187         return ScriptAny.UNDEFINED;
188     }
189     auto timeout = args[1].toValue!size_t;
190     args = args[2..$];
191     bool _; // @suppress(dscanner.suspicious.unmodified)
192     // auto thisToUsePtr = env.lookupVariableOrConst("this", _);
193     auto thisToUse = args.length > 0 ? getLocalThis(env, args[0]) : ScriptAny.UNDEFINED;
194     ScriptFunction funcToAsync = new ScriptFunction("callback", 
195         delegate ScriptAny(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError) {
196             // Thread.sleep(dur!"msecs"(timeout));
197 
198             immutable start = Clock.currStdTime() / 10_000;
199             long current = start;
200             while(current - start <= timeout)
201             {
202                 yield();
203                 current = Clock.currStdTime() / 10_000;
204             }
205             return vm.runFunction(func, thisToUse, args);
206     });
207 
208     // auto fiber = vm.async(funcToAsync, thisToUsePtr? *thisToUsePtr: ScriptAny.UNDEFINED, args_);
209     // auto retVal = new ScriptObject("Timeout", null, fiber);
210     auto retVal = vm.async("Timeout", funcToAsync, thisToUse, args);
211 
212     return ScriptAny(retVal);
213 }