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.exceptions;
25 import mildew.interpreter;
26 import mildew.types;
27 
28 /**
29  * This is called by the interpreter's initializeStdlib method to store functions in the global namespace.
30  * Documentation for these functions can be found at https://pillager86.github.io/dmildew/global.html
31  * Params:
32  *  interpreter = The Interpreter instance to load the functions into.
33  */
34 void initializeGlobalLibrary(Interpreter interpreter)
35 {
36     // experimental: runFile
37     interpreter.forceSetGlobal("runFile", new ScriptFunction("runFile", &native_runFile));
38     interpreter.forceSetGlobal("clearImmediate", new ScriptFunction("clearImmediate", &native_clearImmediate));
39     interpreter.forceSetGlobal("clearTimeout", new ScriptFunction("clearTimeout", &native_clearTimeout));
40     interpreter.forceSetGlobal("decodeURI", new ScriptFunction("decodeURI", &native_decodeURI));
41     interpreter.forceSetGlobal("decodeURIComponent", new ScriptFunction("decodeURIComponent", 
42             &native_decodeURIComponent));
43     interpreter.forceSetGlobal("encodeURI", new ScriptFunction("encodeURI", &native_encodeURI));
44     interpreter.forceSetGlobal("encodeURIComponent", new ScriptFunction("encodeURIComponent", 
45             &native_encodeURIComponent));
46     interpreter.forceSetGlobal("isdefined", new ScriptFunction("isdefined", &native_isdefined));
47     interpreter.forceSetGlobal("isFinite", new ScriptFunction("isFinite", &native_isFinite));
48     interpreter.forceSetGlobal("isNaN", new ScriptFunction("isNaN", &native_isNaN));
49     interpreter.forceSetGlobal("parseFloat", new ScriptFunction("parseFloat", &native_parseFloat));
50     interpreter.forceSetGlobal("parseInt", new ScriptFunction("parseInt", &native_parseInt));
51     interpreter.forceSetGlobal("setImmediate", new ScriptFunction("setImmediate", &native_setImmediate));
52     interpreter.forceSetGlobal("setTimeout", new ScriptFunction("setTimeout", &native_setTimeout));
53 }
54 
55 //
56 // Global method implementations
57 //
58 
59 // experimental DO NOT USE
60 private ScriptAny native_runFile(Environment env, ScriptAny* thisObj,
61                                  ScriptAny[] args, ref NativeFunctionError nfe)
62 {
63     if(args.length < 1)
64     {
65         nfe = NativeFunctionError.WRONG_NUMBER_OF_ARGS;
66         return ScriptAny.UNDEFINED;
67     }
68     auto fileName = args[0].toString();
69     try 
70     {
71         return env.g.interpreter.evaluateFile(fileName);
72 
73     }
74     catch(Exception ex)
75     {
76         nfe = NativeFunctionError.RETURN_VALUE_IS_EXCEPTION;
77         return ScriptAny(ex.msg);
78     }
79 }
80 
81 private ScriptAny native_clearImmediate(Environment env, ScriptAny* thisObj,
82                                       ScriptAny[] args, ref NativeFunctionError nfe)
83 {
84     import mildew.vm.virtualmachine: VirtualMachine;
85     import mildew.vm.fiber: ScriptFiber;
86 
87     if(args.length < 1)
88     {
89         nfe = NativeFunctionError.WRONG_NUMBER_OF_ARGS;
90         return ScriptAny.UNDEFINED;
91     }
92     auto sfib = args[0].toNativeObject!ScriptFiber;
93     if(sfib is null)
94     {
95         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
96         return ScriptAny.UNDEFINED;
97     }
98     if(sfib.toString() != "Immediate")
99         return ScriptAny(false);
100     return ScriptAny(env.g.interpreter.vm.removeFiber(sfib));
101 }
102 
103 private ScriptAny native_clearTimeout(Environment env, ScriptAny* thisObj,
104                                       ScriptAny[] args, ref NativeFunctionError nfe)
105 {
106     import mildew.vm.virtualmachine: VirtualMachine;
107     import mildew.vm.fiber: ScriptFiber;
108 
109     if(args.length < 1)
110     {
111         nfe = NativeFunctionError.WRONG_NUMBER_OF_ARGS;
112         return ScriptAny.UNDEFINED;
113     }
114     auto sfib = args[0].toNativeObject!ScriptFiber;
115     if(sfib is null)
116     {
117         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
118         return ScriptAny.UNDEFINED;
119     }
120     if(sfib.toString() != "Timeout")
121         return ScriptAny(false);
122     return ScriptAny(env.g.interpreter.vm.removeFiber(sfib));
123 }
124 
125 private ScriptAny native_decodeURI(Environment env, ScriptAny* thisObj,
126                                    ScriptAny[] args, ref NativeFunctionError nfe)
127 {
128     import std.uri: decode, URIException;
129     if(args.length < 1)
130         return ScriptAny.UNDEFINED;
131     try 
132     {
133         return ScriptAny(decode(args[0].toString()));
134     }
135     catch(URIException ex)
136     {
137         throw new ScriptRuntimeException(ex.msg);
138     }
139 }
140 
141 private ScriptAny native_decodeURIComponent(Environment env, ScriptAny* thisObj,
142                                             ScriptAny[] args, ref NativeFunctionError nfe)
143 {
144     import std.uri: decodeComponent, URIException;
145     if(args.length < 1)
146         return ScriptAny.UNDEFINED;
147     try 
148     {
149         return ScriptAny(decodeComponent(args[0].toString()));
150     }
151     catch(URIException ex)
152     {
153         throw new ScriptRuntimeException(ex.msg);
154     }
155 }
156 
157 private ScriptAny native_encodeURI(Environment env, ScriptAny* thisObj,
158                                    ScriptAny[] args, ref NativeFunctionError nfe)
159 {
160     import std.uri: encode, URIException;
161     if(args.length < 1)
162         return ScriptAny.UNDEFINED;
163     try 
164     {
165         return ScriptAny(encode(args[0].toString()));
166     }
167     catch(URIException ex)
168     {
169         throw new ScriptRuntimeException(ex.msg);
170     }
171 }
172 
173 private ScriptAny native_encodeURIComponent(Environment env, ScriptAny* thisObj,
174                                             ScriptAny[] args, ref NativeFunctionError nfe)
175 {
176     import std.uri: encodeComponent, URIException;
177     if(args.length < 1)
178         return ScriptAny.UNDEFINED;
179     try 
180     {
181         return ScriptAny(encodeComponent(args[0].toString()));
182     }
183     catch(URIException ex)
184     {
185         throw new ScriptRuntimeException(ex.msg);
186     }
187 }
188 
189 private ScriptAny native_isdefined(Environment env, 
190                                    ScriptAny* thisObj, 
191                                    ScriptAny[] args, 
192                                    ref NativeFunctionError nfe)
193 {
194     if(args.length < 1)
195         return ScriptAny(false);
196     auto varToLookup = args[0].toString();
197     return ScriptAny(env.variableOrConstExists(varToLookup));
198 }
199 
200 private ScriptAny native_isFinite(Environment env, ScriptAny* thisObj, 
201                                   ScriptAny[] args, ref NativeFunctionError nfe)
202 {
203     import std.math: isFinite;
204     if(args.length < 1)
205         return ScriptAny.UNDEFINED;
206     if(!args[0].isNumber)
207         return ScriptAny.UNDEFINED;
208     immutable value = args[0].toValue!double;
209     return ScriptAny(isFinite(value));
210 }
211 
212 private ScriptAny native_isNaN(Environment env, ScriptAny* thisObj,
213                                ScriptAny[] args, ref NativeFunctionError nfe)
214 {
215     import std.math: isNaN;
216     if(args.length < 1)
217         return ScriptAny.UNDEFINED;
218     if(!args[0].isNumber)
219         return ScriptAny(true);
220     immutable value = args[0].toValue!double;
221     return ScriptAny(isNaN(value));
222 }
223 
224 private ScriptAny native_parseFloat(Environment env, ScriptAny* thisObj,
225                                     ScriptAny[] args, ref NativeFunctionError nfe)
226 {
227     import std.conv: to, ConvException;
228     if(args.length < 1)
229         return ScriptAny(double.nan);
230     auto str = args[0].toString();
231     try 
232     {
233         immutable value = to!double(str);
234         return ScriptAny(value);
235     }
236     catch(ConvException)
237     {
238         return ScriptAny(double.nan);
239     }
240 }
241 
242 private ScriptAny native_parseInt(Environment env, ScriptAny* thisObj,
243                                   ScriptAny[] args, ref NativeFunctionError nfe)
244 {
245     import std.conv: to, ConvException;
246     if(args.length < 1)
247         return ScriptAny.UNDEFINED;
248     auto str = args[0].toString();
249     immutable radix = args.length > 1 ? args[1].toValue!int : 10;
250     try 
251     {
252         immutable value = to!long(str, radix);
253         return ScriptAny(value);
254     }
255     catch(ConvException)
256     {
257         return ScriptAny.UNDEFINED;
258     }
259 }
260 
261 private ScriptAny native_setImmediate(Environment env, ScriptAny* thisObj,
262                                       ScriptAny[] args, ref NativeFunctionError nfe)
263 {
264     import mildew.types.bindings: getLocalThis;
265     // all environments are supposed to be linked to the global one. if not, there is a bug
266     auto vm = env.g.interpreter.vm;
267     if(args.length < 1)
268     {
269         nfe = NativeFunctionError.WRONG_NUMBER_OF_ARGS;
270         return ScriptAny.UNDEFINED;
271     }
272     auto func = args[0].toValue!ScriptFunction;
273     if(func is null)
274     {
275         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
276         return ScriptAny.UNDEFINED;
277     }
278     args = args[1..$];
279     auto thisToUse = args.length > 0 ? getLocalThis(env, args[0]) : ScriptAny.UNDEFINED;
280     ScriptFunction funcToAsync = new ScriptFunction(func.functionName, 
281         delegate ScriptAny(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError) {
282             return vm.runFunction(func, thisToUse, args);
283     });
284     auto retVal = vm.addFiberFirst("Immediate", funcToAsync, thisToUse, args);
285     return ScriptAny(retVal);
286 }
287 
288 private ScriptAny native_setTimeout(Environment env, ScriptAny* thisObj,
289                                     ScriptAny[] args, ref NativeFunctionError nfe)
290 {
291     import std.datetime: dur, Clock;
292     import std.concurrency: yield;
293     import mildew.types.bindings: getLocalThis;
294 
295     // all environments are supposed to be linked to the global one. if not, there is a bug
296     auto vm = env.g.interpreter.vm;
297     if(args.length < 2)
298     {
299         nfe = NativeFunctionError.WRONG_NUMBER_OF_ARGS;
300         return ScriptAny.UNDEFINED;
301     }
302     auto func = args[0].toValue!ScriptFunction;
303     if(func is null)
304     {
305         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
306         return ScriptAny.UNDEFINED;
307     }
308     auto timeout = args[1].toValue!size_t;
309     args = args[2..$];
310     auto thisToUse = args.length > 0 ? getLocalThis(env, args[0]) : ScriptAny.UNDEFINED;
311     ScriptFunction funcToAsync = new ScriptFunction(func.functionName, 
312         delegate ScriptAny(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError) {
313             immutable start = Clock.currStdTime() / 10_000;
314             long current = start;
315             while(current - start <= timeout)
316             {
317                 yield();
318                 current = Clock.currStdTime() / 10_000;
319             }
320             return vm.runFunction(func, thisToUse, args);
321     });
322 
323     auto retVal = vm.addFiber("Timeout", funcToAsync, thisToUse, args);
324     return ScriptAny(retVal);
325 }