Branch data Line data Source code
1 : : //Don't try using this in your own project, it's got a lot of Asar-specific tweaks. Use mathlib.cpp instead.
2 : : #include "platform/file-helpers.h"
3 : : #include "std-includes.h"
4 : : #include "autoarray.h"
5 : : #include "assocarr.h"
6 : : #include "libstr.h"
7 : : #include "libsmw.h"
8 : : #include "asar.h"
9 : : #include "virtualfile.h"
10 : : #include "assembleblock.h"
11 : : #include "macro.h"
12 : : #include "asar_math.h"
13 : : #include "warnings.h"
14 : : #include <math.h>
15 : : #include <functional>
16 : : #include <algorithm>
17 : :
18 : : bool math_pri=true;
19 : : bool math_round=false;
20 : : extern bool suppress_all_warnings;
21 : :
22 : : static const char * str;
23 : : //save before calling eval if needed after
24 : : static const char * current_user_function_name;
25 : :
26 : : static double getnumcore();
27 : : static double getnum();
28 : : static double eval(int depth);
29 : :
30 : : //label (bool foundLabel) (bool forwardLabel)
31 : : //userfunction
32 : :
33 : : bool foundlabel;
34 : : bool foundlabel_static;
35 : : bool forwardlabel;
36 : :
37 : : struct cachedfile {
38 : : string filename;
39 : : virtual_file_handle filehandle;
40 : : size_t filesize;
41 : : bool used;
42 : : };
43 : :
44 : : #define numcachedfiles 16
45 : :
46 : : static cachedfile cachedfiles[numcachedfiles];
47 : : static int cachedfileindex = 0;
48 : :
49 : : // Opens a file, trying to open it from cache first
50 : :
51 : 42 : static cachedfile * opencachedfile(string fname, bool should_error)
52 : : {
53 : : cachedfile * cachedfilehandle = nullptr;
54 : :
55 : : // RPG Hacker: Only using a combined path here because that should
56 : : // hopefully result in a unique string for every file, whereas
57 : : // fname could be a relative path, which isn't guaranteed to be unique.
58 : : // Note that this does not affect how we open the file - this is
59 : : // handled by the filesystem and uses our include paths etc.
60 : 42 : string combinedname = filesystem->create_absolute_path(dir(thisfilename), fname);
61 : :
62 [ + + ]: 280 : for (int i = 0; i < numcachedfiles; i++)
63 : : {
64 [ + + + + ]: 266 : if (cachedfiles[i].used && cachedfiles[i].filename == combinedname)
65 : : {
66 : 28 : cachedfilehandle = &cachedfiles[i];
67 : : break;
68 : : }
69 : : }
70 : :
71 : : if (cachedfilehandle == nullptr)
72 : : {
73 : :
74 [ - + ]: 14 : if (cachedfiles[cachedfileindex].used)
75 : : {
76 : 0 : filesystem->close_file(cachedfiles[cachedfileindex].filehandle);
77 : 0 : cachedfiles[cachedfileindex].filehandle = INVALID_VIRTUAL_FILE_HANDLE;
78 : 0 : cachedfiles[cachedfileindex].used = false;
79 : : }
80 : :
81 : 14 : cachedfilehandle = &cachedfiles[cachedfileindex];
82 : : }
83 : :
84 : : if (cachedfilehandle != nullptr)
85 : : {
86 [ + + ]: 42 : if (!cachedfilehandle->used)
87 : : {
88 : 14 : cachedfilehandle->filehandle = filesystem->open_file(fname, thisfilename);
89 [ + + ]: 14 : if (cachedfilehandle->filehandle != INVALID_VIRTUAL_FILE_HANDLE)
90 : : {
91 : 4 : cachedfilehandle->used = true;
92 : 4 : cachedfilehandle->filename = combinedname;
93 : 4 : cachedfilehandle->filesize = filesystem->get_file_size(cachedfilehandle->filehandle);
94 : 4 : cachedfileindex++;
95 : : // randomdude999: when we run out of cached files, just start overwriting ones from the start
96 [ - + ]: 4 : if (cachedfileindex >= numcachedfiles) cachedfileindex = 0;
97 : : }
98 : : }
99 : : }
100 : :
101 [ + + - + ]: 42 : if ((cachedfilehandle == nullptr || cachedfilehandle->filehandle == INVALID_VIRTUAL_FILE_HANDLE) && should_error)
102 : : {
103 : 0 : asar_throw_error(2, error_type_block, vfile_error_to_error_id(asar_get_last_io_error()), fname.data());
104 : : }
105 : :
106 : 42 : return cachedfilehandle;
107 : 42 : }
108 : :
109 : :
110 : 188 : void closecachedfiles()
111 : : {
112 [ + + ]: 3196 : for (int i = 0; i < numcachedfiles; i++)
113 : : {
114 [ + + ]: 3008 : if (cachedfiles[i].used)
115 : : {
116 [ + - ]: 4 : if (cachedfiles[i].filehandle != INVALID_VIRTUAL_FILE_HANDLE)
117 : : {
118 : 4 : filesystem->close_file(cachedfiles[i].filehandle);
119 : 4 : cachedfiles[i].filehandle = INVALID_VIRTUAL_FILE_HANDLE;
120 : : }
121 : :
122 : 4 : cachedfiles[i].used = false;
123 : : }
124 : : }
125 : :
126 : 188 : cachedfileindex = 0;
127 : 188 : }
128 : :
129 : :
130 : 8 : static int struct_size(const char *name)
131 : : {
132 [ + - - + ]: 8 : if(pass && !structs.exists(name)) asar_throw_error(2, error_type_block, error_id_struct_not_found, name);
133 [ + - ]: 8 : else if(!structs.exists(name)) return 0;
134 : 8 : return structs.find(name).struct_size;
135 : : }
136 : :
137 : 38 : static int object_size(const char *name)
138 : : {
139 [ + + - + ]: 38 : if(pass && !structs.exists(name)) asar_throw_error(2, error_type_block, error_id_struct_not_found, name);
140 [ + - ]: 38 : else if(!structs.exists(name)) return 0;
141 : 38 : return structs.find(name).object_size;
142 : : }
143 : :
144 : 10 : static int data_size(const char *name)
145 : : {
146 : : unsigned int label;
147 : 10 : unsigned int next_label = 0xFFFFFF;
148 [ - + ]: 10 : if(!labels.exists(name)) asar_throw_error(2, error_type_block, error_id_label_not_found, name);
149 : 10 : foundlabel = true;
150 : 10 : snes_label label_data = labels.find(name);
151 : 10 : foundlabel_static &= label_data.is_static;
152 : 10 : label = label_data.pos & 0xFFFFFF;
153 : 10 : labels.each([&next_label, label](const char *key, snes_label current_label){
154 : 30 : current_label.pos &= 0xFFFFFF;
155 [ + + + + ]: 30 : if(label < current_label.pos && current_label.pos < next_label){
156 : 6 : next_label = current_label.pos;
157 : : }
158 : 30 : });
159 [ + + ]: 10 : if(next_label == 0xFFFFFF) asar_throw_warning(2, warning_id_datasize_last_label, name);
160 [ + + ]: 10 : if(next_label-label > 0xFFFF) asar_throw_warning(2, warning_id_datasize_exceeds_size, name);
161 : 10 : return next_label-label;
162 : : }
163 : :
164 : :
165 : 148 : string get_string_argument()
166 : : {
167 [ - + ]: 148 : while (*str==' ') str++;
168 [ + - ]: 148 : if (*str=='"')
169 : : {
170 : : const char * strpos = str;
171 : 148 : str++;
172 [ + + + - ]: 2042 : while (*str!='"' && *str!='\0' && *str!='\n') str++;
173 [ + - ]: 148 : if (*str == '"')
174 : : {
175 : 148 : string tempname(strpos, (int)(str - strpos + 1));
176 : 148 : str++;
177 [ - + ]: 148 : while (*str==' ') str++; //eat space
178 : 148 : return string(safedequote(tempname.temp_raw()));
179 : 148 : }
180 : : // RPG Hacker: AFAIK, this is never actually triggered, since unmatched quotes are already detected earlier,
181 : : // but since it does no harm here, I'll keep it in, just to be safe
182 : 0 : else asar_throw_error(2, error_type_block, error_id_string_literal_not_terminated);
183 : : }//make this error a better one later
184 : :
185 : 0 : asar_throw_error(2, error_type_block, error_id_string_literal_not_terminated);
186 : 0 : return ""; //never actually called, but I don't feel like figuring out __attribute__ ((noreturn)) on MSVC
187 : : }
188 : :
189 : : //only returns alphanumeric (and _) starting with alpha or _
190 : 128 : string get_symbol_argument()
191 : : {
192 [ - + ]: 128 : while (*str==' ') str++; //is this proper? Dunno yet.
193 : : const char * strpos = str;
194 : : // hack: for backwards compat, allow strings as symbols
195 [ + + ]: 128 : if(*str=='"') {
196 : 4 : asar_throw_warning(2, warning_id_feature_deprecated, "quoted symbolic arguments", "Remove the quotations");
197 : 4 : string arg = get_string_argument();
198 : : int i = 0;
199 [ - + - - ]: 4 : if(is_alpha(arg[i]) || arg[i] == '_') i++;
200 [ + + + - : 28 : while(is_alnum(arg[i]) || arg[i] == '_' || arg[i] == '.') i++;
+ + ]
201 [ - + - - ]: 4 : if(arg[i] != '\0') asar_throw_error(2, error_type_block, error_id_invalid_label_name);
202 : 4 : return arg;
203 : 4 : }
204 [ + + - + ]: 124 : if(is_alpha(*str) || *str == '_') str++;
205 [ + + + + : 588 : while (is_alnum(*str) || *str == '_' || *str == '.') str++;
+ + ]
206 [ - + ]: 124 : if(strpos == str){
207 : : //error nothing was read, this is a placeholder error
208 : 0 : asar_throw_error(2, error_type_block, error_id_string_literal_not_terminated);
209 : : }
210 : :
211 : 124 : string symbol = string(strpos, (int)(str - strpos));
212 [ - + ]: 124 : while (*str==' ') str++; //eat spaces
213 : 124 : return symbol;
214 : 124 : }
215 : :
216 : 284 : double get_double_argument()
217 : : {
218 [ - + ]: 284 : while (*str==' ') str++;
219 : 284 : double result = eval(0);
220 [ - + ]: 284 : while (*str==' ') str++; //eat spaces
221 : 284 : return result;
222 : : }
223 : :
224 : : //will eat the comma
225 : 49 : bool has_next_parameter()
226 : : {
227 [ + + ]: 49 : if (*str==',')
228 : : {
229 : 15 : str++;
230 : 15 : return true;
231 : : }
232 : : return false;
233 : : }
234 : :
235 : 117 : void require_next_parameter()
236 : : {
237 [ + - ]: 117 : if (*str==',')
238 : : {
239 : 117 : str++;
240 : 117 : return;
241 : : }
242 : 0 : asar_throw_error(2, error_type_block, error_id_require_parameter);
243 : : }
244 : :
245 : 2 : template <typename F> double asar_unary_wrapper()
246 : : {
247 : 2 : return F()(get_double_argument());
248 : : }
249 : :
250 : 2 : template <double (*F)(double)> double asar_unary_wrapper()
251 : : {
252 : 2 : return F(get_double_argument());
253 : : }
254 : :
255 : : //possibly restrict type T in the future....
256 : : //first a case for functors
257 : 22 : template <typename F> double asar_binary_wrapper()
258 : : {
259 : 22 : double first = get_double_argument();
260 : 22 : require_next_parameter();
261 : 22 : return F()(first, get_double_argument());
262 : : }
263 : : //this could be DRY with if constexpr....oh well
264 : 13 : template <double (*F)(double, double)> double asar_binary_wrapper()
265 : : {
266 : 13 : double first = get_double_argument();
267 : 13 : require_next_parameter();
268 : 13 : return F(first, get_double_argument());
269 : : }
270 : :
271 : 0 : double asar_bank(double a)
272 : : {
273 : 2 : return (int)a >> 16;
274 : : }
275 : :
276 : :
277 : 0 : double asar_logical_nand(double a, double b)
278 : : {
279 : 3 : return !(a && b);
280 : : }
281 : :
282 : :
283 : 0 : double asar_logical_nor(double a, double b)
284 : : {
285 : 3 : return !(a || b);
286 : : }
287 : :
288 : :
289 : 0 : double asar_logical_xor(double a, double b)
290 : : {
291 : 3 : return !!a ^ !!b;
292 : : }
293 : :
294 : 0 : double asar_max(double a, double b)
295 : : {
296 [ + + + + : 5 : return a > b ? a : b;
- - ]
297 : : }
298 : :
299 : 0 : double asar_min(double a, double b)
300 : : {
301 [ + + + + : 5 : return a < b ? a : b;
- - ]
302 : : }
303 : :
304 : 3 : double asar_clamp()
305 : : {
306 : 3 : double value = get_double_argument();
307 : 3 : require_next_parameter();
308 : 3 : double low = get_double_argument();
309 : 3 : require_next_parameter();
310 : 3 : double high = get_double_argument();
311 : :
312 : 3 : return asar_max(low, asar_min(high, value));
313 : : }
314 : :
315 : 2 : double asar_safediv()
316 : : {
317 : 2 : double dividend = get_double_argument();
318 : 2 : require_next_parameter();
319 : 2 : double divisor = get_double_argument();
320 : 2 : require_next_parameter();
321 : 2 : double default_value = get_double_argument();
322 : :
323 [ + + ]: 2 : return divisor == 0.0 ? default_value : dividend / divisor;
324 : : }
325 : :
326 : 11 : double asar_select()
327 : : {
328 : 11 : double selector = get_double_argument();
329 : 11 : require_next_parameter();
330 : 11 : double a = get_double_argument();
331 : 11 : require_next_parameter();
332 : 11 : double b = get_double_argument();
333 : :
334 [ + + ]: 11 : return selector == 0.0 ? b : a;
335 : : }
336 : :
337 : 57 : double asar_snestopc_wrapper()
338 : : {
339 : 57 : return snestopc(get_double_argument());
340 : : }
341 : :
342 : 41 : double asar_pctosnes_wrapper()
343 : : {
344 : 41 : return pctosnes(get_double_argument());
345 : : }
346 : :
347 : 6 : double asar_realbase_wrapper()
348 : : {
349 : : //need a better way to do this to make sure it is useful...
350 : : //foundlabel=true; //Going to consider this an implicit label because we don't really have a better system
351 : 6 : return realsnespos;
352 : : }
353 : :
354 : 9 : template <int count> double asar_read()
355 : : {
356 : 9 : int target = get_double_argument();
357 : 9 : int addr=snestopc_pick(target);
358 [ - + ]: 9 : if(has_next_parameter())
359 : : {
360 : 0 : double default_value = get_double_argument();
361 [ # # ]: 0 : if (addr<0) return default_value;
362 [ # # ]: 0 : else if (addr+count>romlen_r) return default_value;
363 : : }
364 : : else
365 : : {
366 [ - + - - ]: 9 : if (addr<0) asar_throw_error(2, error_type_block, error_id_snes_address_doesnt_map_to_rom, (hex6((unsigned int)target) + " in read function").data());
367 [ + + - + ]: 11 : else if (addr+count>romlen_r) asar_throw_error(2, error_type_block, error_id_snes_address_out_of_bounds, (hex6(target) + " in read function").data());
368 : : }
369 : :
370 : : unsigned int value = 0;
371 [ + + ]: 28 : for(int i = 0; i < count; i++)
372 : : {
373 : 20 : value |= romdata_r[addr+i] << (8 * i);
374 : : }
375 : 8 : return value;
376 : : }
377 : :
378 : 12 : template <int count> double asar_canread()
379 : : {
380 : : int length = count;
381 : : if(!length)
382 : : {
383 : 0 : length = get_double_argument();
384 : : }
385 : 12 : int addr=snestopc_pick(get_double_argument());
386 [ + - + + ]: 12 : if (addr<0 || addr+length-1>=romlen_r) return 0;
387 : 6 : else return 1;
388 : : }
389 : :
390 : 19 : template <size_t count> double asar_readfile()
391 : : {
392 : : static_assert(count && count <= 4, "invalid count"); //1-4 inclusive
393 : :
394 : 19 : string name = get_string_argument();
395 : 19 : require_next_parameter();
396 : 19 : size_t offset = get_double_argument();
397 : 19 : bool should_error = !has_next_parameter();
398 : 19 : cachedfile * fhandle = opencachedfile(name, should_error);
399 [ + + ]: 19 : if(!should_error)
400 : : {
401 : 9 : double default_value = get_double_argument();
402 [ + - + + ]: 9 : if (fhandle == nullptr || fhandle->filehandle == INVALID_VIRTUAL_FILE_HANDLE) return default_value;
403 [ + + ]: 7 : if (offset < 0 || offset + count > fhandle->filesize) return default_value;
404 : : }
405 : : else
406 : : {
407 [ + - - + : 10 : if (fhandle == nullptr || fhandle->filehandle == INVALID_VIRTUAL_FILE_HANDLE) asar_throw_error(2, error_type_block, vfile_error_to_error_id(asar_get_last_io_error()), name.data());
- - - - -
- ]
408 [ - + - - ]: 10 : if (offset < 0 || offset + count > fhandle->filesize) asar_throw_error(2, error_type_block, error_id_file_offset_out_of_bounds, dec(offset).data(), name.data());
409 : : }
410 : :
411 : 14 : unsigned char data[4] = { 0, 0, 0, 0 };
412 : 14 : filesystem->read_file(fhandle->filehandle, data, offset, count);
413 : :
414 : : unsigned int value = 0;
415 [ + + ]: 35 : for(size_t i = 0; i < count; i++)
416 : : {
417 : 21 : value |= data[i] << (8 * i);
418 : : }
419 : :
420 : 14 : return value;
421 : 19 : }
422 : :
423 : 9 : template <size_t count> double asar_canreadfile()
424 : : {
425 : 9 : string name = get_string_argument();
426 : 9 : require_next_parameter();
427 : 9 : size_t offset = get_double_argument();
428 : : size_t length = count;
429 : : if(!count)
430 : : {
431 : 2 : require_next_parameter();
432 : 2 : length = get_double_argument();
433 : : }
434 : 9 : cachedfile * fhandle = opencachedfile(name, false);
435 [ + - + + ]: 9 : if (fhandle == nullptr || fhandle->filehandle == INVALID_VIRTUAL_FILE_HANDLE) return 0;
436 [ + + ]: 7 : if (offset < 0 || offset + length > fhandle->filesize) return 0;
437 : : return 1;
438 : 9 : }
439 : :
440 : : // returns 0 if the file is OK, 1 if the file doesn't exist, 2 if it couldn't be opened for some other reason
441 : 12 : static double asar_filestatus()
442 : : {
443 : 12 : cachedfile * fhandle = opencachedfile(get_string_argument(), false);
444 [ + - + + ]: 12 : if (fhandle == nullptr || fhandle->filehandle == INVALID_VIRTUAL_FILE_HANDLE)
445 : : {
446 [ - + ]: 6 : if (filesystem->get_last_error() == vfe_doesnt_exist)
447 : : {
448 : : return 1;
449 : : }
450 : : else
451 : : {
452 : 0 : return 2;
453 : : }
454 : : }
455 : : return 0;
456 : : }
457 : :
458 : : // Returns the size of the specified file.
459 : 2 : static double asar_filesize()
460 : : {
461 : 2 : string name = get_string_argument();
462 : 2 : cachedfile * fhandle = opencachedfile(name, false);
463 [ + - - + : 2 : if (fhandle == nullptr || fhandle->filehandle == INVALID_VIRTUAL_FILE_HANDLE) asar_throw_error(2, error_type_block, vfile_error_to_error_id(asar_get_last_io_error()), name.data());
- - - - -
- ]
464 : 2 : return (double)fhandle->filesize;
465 : 2 : }
466 : :
467 : : // Checks whether the specified define is defined.
468 : 62 : static double asar_isdefined()
469 : : {
470 : 62 : return defines.exists(get_string_argument());
471 : : }
472 : :
473 : : // RPG Hacker: What exactly makes this function overly complicated, you ask?
474 : : // Well, it converts a double to a string and then back to a double.
475 : : // It was the quickest reliable solution I could find, though, so there's that.
476 : 2 : static double asar_round()
477 : : {
478 : 2 : double number = get_double_argument();
479 : 2 : require_next_parameter();
480 : :
481 : : // Hue hue hue... ass!
482 : : // OK, sorry, I apologize.
483 : 2 : string asstring = ftostrvar(number, get_double_argument());
484 : :
485 : : // Some hacky shenanigans with variables going on here
486 : 2 : const char * strbackup = str;
487 : 2 : str = asstring;
488 : 2 : double asdouble = (double)getnum();
489 : 2 : str = strbackup;
490 : :
491 : 2 : return asdouble;
492 : 2 : }
493 : :
494 : 110 : static double asar_structsize_wrapper()
495 : : {
496 : 110 : string symbol = get_symbol_argument();
497 [ + + ]: 110 : if(symbol == "..."){
498 [ + + - + ]: 102 : if(!inmacro) asar_throw_error(2, error_type_block, error_id_vararg_sizeof_nomacro);
499 [ + + - + ]: 101 : if(numvarargs == -1) asar_throw_error(2, error_type_block, error_id_macro_not_varadic);
500 : 100 : return numvarargs;
501 : : }
502 : 8 : return (double)struct_size(symbol);
503 : 110 : }
504 : :
505 : 8 : static double asar_objectsize_wrapper()
506 : : {
507 : 8 : return (double)object_size(get_symbol_argument());
508 : : }
509 : :
510 : 10 : static double asar_datasize_wrapper()
511 : : {
512 : 10 : return (double)data_size(get_symbol_argument());
513 : : }
514 : :
515 : 12 : static double asar_stringsequal()
516 : : {
517 : 12 : string string1 = get_string_argument();
518 : 12 : require_next_parameter();
519 [ + - + + ]: 30 : return (strcmp(string1, get_string_argument()) == 0 ? 1.0 : 0.0);
520 : 12 : }
521 : :
522 : 6 : static double asar_stringsequalnocase()
523 : : {
524 : 6 : string string1 = get_string_argument();
525 : 6 : require_next_parameter();
526 [ + - - + ]: 12 : return (stricmp(string1, get_string_argument()) == 0 ? 1.0 : 0.0);
527 : 6 : }
528 : :
529 : 21 : string copy_arg()
530 : : {
531 [ + + ]: 21 : if(*str == '"')
532 : : {
533 : 4 : string t = "\"";
534 : 8 : return (t += get_string_argument() + "\"");
535 : 4 : }
536 : :
537 : 17 : string result;
538 : : bool is_symbolic = true;
539 : : int parlevel=0;
540 : : int i = 0;
541 [ - + + + : 34 : while(parlevel > 0 || (str[i] != ',' && str[i] != ')'))
+ + ]
542 : : {
543 : 17 : is_symbolic &= is_ualnum(str[i]);
544 [ - + ]: 17 : if(str[i] == '(') parlevel++;
545 [ - + ]: 17 : else if(str[i] == ')') parlevel--;
546 : 17 : i++;
547 : : }
548 : 17 : result += string(str, i);
549 : 17 : str += i;
550 : :
551 [ - + ]: 17 : if(!is_symbolic)
552 : : {
553 : : const char *oldstr=str;
554 : 0 : str = (const char *)result;
555 : 0 : result = ftostr(eval(0));
556 : 0 : str = oldstr;
557 : : }
558 : 17 : return result;
559 : 17 : }
560 : :
561 : : assocarr<double (*)()> builtin_functions =
562 : : {
563 : : {"sqrt", asar_unary_wrapper<sqrt>},
564 : : {"sin", asar_unary_wrapper<sin>},
565 : : {"cos", asar_unary_wrapper<cos>},
566 : : {"tan", asar_unary_wrapper<tan>},
567 : : {"asin", asar_unary_wrapper<asin>},
568 : : {"acos", asar_unary_wrapper<acos>},
569 : : {"atan", asar_unary_wrapper<atan>},
570 : : {"arcsin", asar_unary_wrapper<asin>},
571 : : {"arccos", asar_unary_wrapper<acos>},
572 : : {"arctan", asar_unary_wrapper<atan>},
573 : : {"log", asar_unary_wrapper<log>},
574 : : {"log10", asar_unary_wrapper<log10>},
575 : : {"log2", asar_unary_wrapper<log2>},
576 : :
577 : : {"ceil", asar_unary_wrapper<ceil>},
578 : : {"floor", asar_unary_wrapper<floor>},
579 : :
580 : : {"read1", asar_read<1>}, //This handles the safe and unsafe variant
581 : : {"read2", asar_read<2>},
582 : : {"read3", asar_read<3>},
583 : : {"read4", asar_read<4>},
584 : : {"canread", asar_canread<0>},
585 : : {"canread1", asar_canread<1>},
586 : : {"canread2", asar_canread<2>},
587 : : {"canread3", asar_canread<3>},
588 : : {"canread4", asar_canread<4>},
589 : :
590 : : {"readfile1", asar_readfile<1>},
591 : : {"readfile2", asar_readfile<2>},
592 : : {"readfile3", asar_readfile<3>},
593 : : {"readfile4", asar_readfile<4>},
594 : : {"canreadfile", asar_canreadfile<0>},
595 : : {"canreadfile1", asar_canreadfile<1>},
596 : : {"canreadfile2", asar_canreadfile<2>},
597 : : {"canreadfile3", asar_canreadfile<3>},
598 : : {"canreadfile4", asar_canreadfile<4>},
599 : :
600 : : {"filesize", asar_filesize},
601 : : {"getfilestatus", asar_filestatus},
602 : :
603 : : {"defined", asar_isdefined},
604 : :
605 : : {"snestopc", asar_snestopc_wrapper},
606 : : {"pctosnes", asar_pctosnes_wrapper},
607 : : {"realbase", asar_realbase_wrapper},
608 : :
609 : : {"max", asar_binary_wrapper<asar_max>},
610 : : {"min", asar_binary_wrapper<asar_min>},
611 : : {"clamp", asar_clamp},
612 : :
613 : : {"safediv", asar_safediv},
614 : :
615 : : {"select", asar_select},
616 : : {"bank", asar_unary_wrapper<asar_bank>},
617 : : {"not", asar_unary_wrapper<std::logical_not<unsigned int>>},
618 : : {"equal", asar_binary_wrapper<std::equal_to<double>>},
619 : : {"notequal", asar_binary_wrapper<std::not_equal_to<double>>},
620 : : {"less", asar_binary_wrapper<std::less<double>>},
621 : : {"lessequal", asar_binary_wrapper<std::less_equal<double>>},
622 : : {"greater", asar_binary_wrapper<std::greater<double>>},
623 : : {"greaterequal", asar_binary_wrapper<std::greater_equal<double>>},
624 : :
625 : : {"and", asar_binary_wrapper<std::logical_and<unsigned int>>},
626 : : {"or", asar_binary_wrapper<std::logical_or<unsigned int>>},
627 : : {"nand", asar_binary_wrapper<asar_logical_nand>},
628 : : {"nor", asar_binary_wrapper<asar_logical_nor>},
629 : : {"xor", asar_binary_wrapper<asar_logical_xor>},
630 : :
631 : : {"round", asar_round},
632 : :
633 : : {"sizeof", asar_structsize_wrapper},
634 : : {"objectsize", asar_objectsize_wrapper},
635 : : {"datasize", asar_datasize_wrapper},
636 : :
637 : : {"stringsequal", asar_stringsequal},
638 : : {"stringsequalnocase", asar_stringsequalnocase}
639 : : };
640 : :
641 : : assocarr<double (*)()> functions;
642 : :
643 : : struct funcdat {
644 : : autoptr<char*> name;
645 : : int numargs;
646 : : autoptr<char*> argbuf;//this one isn't used, it's just to free it up
647 : : autoptr<char**> arguments;
648 : : autoptr<char*> content;
649 : : };
650 : : static assocarr<funcdat> user_functions;
651 : :
652 : 15 : static double asar_call_user_function()
653 : : {
654 : 15 : autoarray<string> args;
655 : 15 : funcdat &user_function = user_functions[current_user_function_name];
656 : 15 : string real_content;
657 : :
658 [ - + ]: 15 : while (*str==' ') str++;
659 : 15 : bool has_next = *str != ')';
660 : :
661 [ + + ]: 36 : for (int i=0;i<user_function.numargs;i++)
662 : : {
663 [ - + ]: 21 : if(!has_next)
664 : : {
665 : 0 : asar_throw_error(2, error_type_block, error_id_expected_parameter, current_user_function_name);
666 : : }
667 : 42 : args[i] = copy_arg();
668 : 21 : has_next = has_next_parameter();
669 : : }
670 : :
671 [ - + ]: 15 : if(has_next)
672 : : {
673 : 0 : asar_throw_error(2, error_type_block, error_id_unexpected_parameter, current_user_function_name);
674 : : }
675 : :
676 [ + + ]: 77 : for(int i=0; user_function.content[i]; i++)
677 : : {
678 [ + + - + ]: 62 : if(!is_alpha(user_function.content[i]) && user_function.content[i] != '_')
679 : : {
680 : 37 : real_content += user_function.content[i];
681 : 37 : continue;
682 : : }
683 : : bool found = false;
684 [ + + ]: 66 : for (int j=0;user_function.arguments[j];j++)
685 : : {
686 : : //this should *always* have a null term or another character after
687 : 41 : bool potential_arg = stribegin(user_function.content+i, user_function.arguments[j]);
688 : 41 : int next_char = i+strlen(user_function.arguments[j]);
689 [ + + + - : 41 : if(potential_arg && (!is_alnum(user_function.content[next_char]) && user_function.content[next_char] != '_'))
+ - ]
690 : : {
691 : 21 : real_content += args[j];
692 : 21 : i = next_char - 1;
693 : : found = true;
694 : : }
695 : : }
696 : :
697 [ + + ]: 25 : if(!found){
698 [ + + ]: 40 : for(; is_ualnum(user_function.content[i]); i++){
699 : 36 : real_content += user_function.content[i];
700 : : }
701 : 4 : real_content += user_function.content[i];
702 : : }
703 : : }
704 : 15 : const char * oldstr=str;
705 : 15 : str = (const char *)real_content;
706 : 15 : double result = eval(0);
707 : 15 : str = oldstr;
708 : 15 : return result;
709 : 15 : }
710 : :
711 : 15 : void createuserfunc(const char * name, const char * arguments, const char * content)
712 : : {
713 [ - + ]: 15 : if (!confirmqpar(content)) asar_throw_error(0, error_type_block, error_id_mismatched_parentheses);
714 [ - + ]: 15 : if(functions.exists(name)) //functions holds both types.
715 : : {
716 : : //asar_throw_error(0, error_type_block, error_id_function_redefined, name);
717 : 0 : asar_throw_warning(1, warning_id_feature_deprecated, "overwriting a previously defined function", "change the function name");
718 : : }
719 : 15 : funcdat& user_function=user_functions[name];
720 : 15 : user_function.name= duplicate_string(name);
721 : 15 : user_function.argbuf= duplicate_string(arguments);
722 : 15 : user_function.arguments=qsplit(user_function.argbuf, ",", &(user_function.numargs));
723 : 15 : user_function.content= duplicate_string(content);
724 [ + + ]: 36 : for (int i=0;user_function.arguments[i];i++)
725 : : {
726 [ + + ]: 54 : for(int j=0;user_function.arguments[j];j++)
727 : : {
728 [ + + - + ]: 33 : if(i!=j && !stricmp(user_function.arguments[i], user_function.arguments[j]))
729 : : {
730 : 0 : asar_throw_error(0, error_type_block, error_id_duplicate_param_name, user_function.arguments[i], name);
731 : : }
732 : : }
733 [ - + ]: 21 : if (!confirmname(user_function.arguments[i]))
734 : : {
735 : 0 : user_functions.remove(name);
736 : 0 : asar_throw_error(0, error_type_block, error_id_invalid_param_name);
737 : : }
738 : : }
739 : :
740 : 15 : functions[name] = asar_call_user_function;
741 : 15 : }
742 : :
743 : 918844 : static double getnumcore()
744 : : {
745 [ + + ]: 918844 : if (*str=='(')
746 : : {
747 : 268 : str++;
748 : 268 : double rval=eval(0);
749 [ - + ]: 268 : if (*str != ')') asar_throw_error(2, error_type_block, error_id_mismatched_parentheses);
750 : 268 : str++;
751 : 268 : return rval;
752 : : }
753 [ + + ]: 918576 : if (*str=='$')
754 : : {
755 [ - + ]: 3449 : if (!is_xdigit(str[1])) asar_throw_error(2, error_type_block, error_id_invalid_hex_value);
756 [ + - ]: 3449 : if (to_lower(str[2])=='x') return -42;//let str get an invalid value so it'll throw an invalid operator later on
757 : 3449 : return strtoull(str+1, const_cast<char**>(&str), 16);
758 : : }
759 [ + + ]: 915127 : if (*str=='%')
760 : : {
761 [ - + ]: 24 : if (str[1] != '0' && str[1] != '1') asar_throw_error(2, error_type_block, error_id_invalid_binary_value);
762 : 24 : return strtoull(str+1, const_cast<char**>(&str), 2);
763 : : }
764 [ + + ]: 915103 : if (*str=='\'')
765 : : {
766 [ + - - + ]: 24 : if (!str[1] || str[2] != '\'') asar_throw_error(2, error_type_block, error_id_invalid_character);
767 : 24 : unsigned int rval=table.table[(unsigned char)str[1]];
768 : 24 : str+=3;
769 : 24 : return rval;
770 : : }
771 [ + + ]: 915079 : if (is_digit(*str))
772 : : {
773 : : const char* end = str;
774 [ + + + + ]: 4113243 : while (is_digit(*end) || *end == '.') end++;
775 : 914183 : string number;
776 : 914183 : number.assign(str, (int)(end - str));
777 : 914183 : str = end;
778 : 914183 : return atof(number);
779 : 914183 : }
780 [ + + ]: 896 : if (is_alpha(*str) || *str=='_' || *str=='.' || *str=='?')
781 : : {
782 : 893 : const char * start=str;
783 [ + + + + : 8512 : while (is_alnum(*str) || *str == '_' || *str == '.') str++;
+ + ]
784 : 893 : int len=(int)(str-start);
785 [ - + ]: 893 : while (*str==' ') str++;
786 [ + + ]: 893 : if (*str=='(')
787 : : {
788 : 447 : str++;
789 : : // RPG Hacker: This is only here to assure that all strings are still
790 : : // alive in memory when we call our functions further down
791 : : double result;
792 : : while (true)
793 : : {
794 [ - + ]: 447 : while (*str==' ') str++;
795 : 447 : string function_name = string(start, len);
796 [ + - ]: 447 : if(functions.exists(function_name))
797 : : {
798 : 447 : current_user_function_name = function_name;
799 : 447 : result = functions[function_name]();
800 : : }
801 : : else
802 : : {
803 : 0 : str++;
804 : : break;
805 : : }
806 : :
807 [ + - ]: 444 : if (*str==')')
808 : : {
809 : 444 : str++;
810 : : return result;
811 : : }
812 : 0 : asar_throw_error(2, error_type_block, error_id_malformed_function_call);
813 : 447 : }
814 : :
815 : 0 : asar_throw_error(2, error_type_block, error_id_function_not_found, start);
816 : : }
817 : : else
818 : : {
819 : 446 : foundlabel=true;
820 : :
821 : : const char *old_start = start;
822 : 446 : snes_label label_data = labelval(&start);
823 : 446 : int i=(int)label_data.pos;
824 : 446 : foundlabel_static &= label_data.is_static;
825 : : bool scope_passed = false;
826 : : bool subscript_passed = false;
827 [ + + ]: 1192 : while (str < start)
828 : : {
829 [ + + ]: 300 : if (*str == '.') scope_passed = true;
830 [ + + ]: 300 : if (*(str++) == '[')
831 : : {
832 [ - + ]: 30 : if (subscript_passed)
833 : : {
834 : 0 : asar_throw_error(2, error_type_block, error_id_multiple_subscript_operators);
835 : 0 : break;
836 : : }
837 : : subscript_passed = true;
838 [ - + ]: 30 : if (scope_passed)
839 : : {
840 : 0 : asar_throw_error(2, error_type_block, error_id_invalid_subscript);
841 : : break;
842 : : }
843 : 30 : string struct_name = substr(old_start, (int)(str - old_start - 1));
844 : 30 : i += (int)(eval(0) * object_size(struct_name));
845 : 30 : }
846 : : }
847 : :
848 : 446 : str=start;
849 [ + + ]: 446 : if (i==-1) forwardlabel=true;
850 : 446 : return (int)i&0xFFFFFF;
851 : : }
852 : : }
853 : 3 : asar_throw_error(2, error_type_block, error_id_invalid_number);
854 : 0 : return 0.0;
855 : : }
856 : :
857 : 1129607 : static double sanitize(double val)
858 : : {
859 [ - + ]: 1129607 : if (val != val) asar_throw_error(2, error_type_block, error_id_nan);
860 [ + - + + ]: 1129607 : if (math_round && !default_math_round_off) return trunc(val); // originally used int cast, but that broke numbers > $8000_0000
861 : : return val;
862 : : }
863 : :
864 : 918878 : static double getnum()
865 : : {
866 [ - + ]: 918878 : while (*str==' ') str++;
867 : : #define prefix(sym, func) if (*str == sym) { str+=1; double val=getnum(); return sanitize(func); }
868 : : #define prefix_dep(sym, func) if (*str == sym) { str+=1; asar_throw_warning(2, warning_id_feature_deprecated, "xkas style numbers ", "remove the #"); double val=getnum(); return sanitize(func); }
869 : : #define prefix2(sym, sym2, func) if (*str == sym && *(str+1) == sym2) { str+=2; double val=getnum(); return sanitize(func); }
870 : 918878 : prefix('-', -val);
871 : 918850 : prefix('~', ~(int)val);
872 [ + + + - ]: 918848 : prefix2('<', ':', (int)val>>16);
873 : 918844 : prefix('+', val);
874 [ + + - + ]: 918844 : prefix_dep('#' && emulatexkas, val);
875 : : #undef prefix
876 : 918844 : return sanitize(getnumcore());
877 : : }
878 : :
879 : 37573 : int64_t getnum(const char* instr)
880 : : {
881 : 37573 : return getnum64(instr);
882 : :
883 : : // randomdude999: perform manual bounds-checking and 2's complement,
884 : : // to prevent depending on UB
885 : : double num = math(instr);
886 : : if(num < 0) {
887 : : // manual 2's complement
888 : : if((-num) > (double)UINT32_MAX) {
889 : : // out of bounds, return closest inbounds value
890 : : // (this value is the most negative value possible)
891 : : return ((uint32_t)INT32_MAX)+1;
892 : : }
893 : : return ~((uint32_t)(-num))+1;
894 : : } else {
895 : : if(num > (double)UINT32_MAX) {
896 : : // out of bounds, return closest inbounds value
897 : : return UINT32_MAX;
898 : : }
899 : : return (uint32_t)num;
900 : : }
901 : : }
902 : :
903 : 37777 : int64_t getnum64(const char* instr)
904 : : {
905 : : // randomdude999: perform manual bounds-checking
906 : : // to prevent depending on UB
907 : 37777 : double num = math(instr);
908 [ + - ]: 37765 : if(num < (double)INT64_MIN) {
909 : : return INT64_MIN;
910 [ + - ]: 37765 : } else if(num > (double)INT64_MAX) {
911 : : return INT64_MAX;
912 : : }
913 : 37765 : return (int64_t)num;
914 : : }
915 : :
916 : : // RPG Hacker: Same function as above, but doesn't truncate our number via int conversion
917 : 316776 : double getnumdouble(const char * instr)
918 : : {
919 : 316776 : return math(instr);
920 : : }
921 : :
922 : :
923 : 0 : static double oper_wrapped_throw(asar_error_id errid)
924 : : {
925 : 0 : asar_throw_error(2, error_type_block, errid);
926 : 0 : return 0.0;
927 : : }
928 : :
929 : 813694 : static double eval(int depth)
930 : : {
931 : 813694 : const char* posneglabel = str;
932 : 813694 : string posnegname = posneglabelname(&posneglabel, false);
933 : :
934 [ + + ]: 813694 : if (posnegname.length() > 0)
935 : : {
936 [ + + + - ]: 56 : if (*posneglabel != '\0' && *posneglabel != ')') goto notposneglabel;
937 : :
938 : 28 : str = posneglabel;
939 : :
940 : 28 : foundlabel=true;
941 [ + + ]: 28 : if (*(posneglabel-1) == '+') forwardlabel=true;
942 : 28 : snes_label label_data = labelval(posnegname);
943 : 28 : foundlabel_static &= label_data.is_static;
944 : 28 : return label_data.pos & 0xFFFFFF;
945 : : }
946 : 813638 : notposneglabel:
947 : 1627332 : recurseblock rec;
948 : 813666 : double left=getnum();
949 : : double right;
950 [ - + ]: 813660 : while (*str==' ') str++;
951 [ + + + + : 1024395 : while (*str && *str != ')' && *str != ','&& *str != ']')
+ + ]
952 : : {
953 [ - + ]: 210744 : while (*str==' ') str++;
954 : : // why was this an int cast
955 [ + - + + ]: 210744 : if (math_round && !default_math_round_off) left=trunc(left);
956 : : #define oper(name, thisdepth, contents) \
957 : : if (!strncmp(str, name, strlen(name))) \
958 : : { \
959 : : if (math_pri || default_math_pri) \
960 : : { \
961 : : if (depth<=thisdepth) \
962 : : { \
963 : : str+=strlen(name); \
964 : : right=eval(thisdepth+1); \
965 : : } \
966 : : else return left; \
967 : : } \
968 : : else \
969 : : { \
970 : : str+=strlen(name); \
971 : : right=getnum(); \
972 : : } \
973 : : left=sanitize(contents); \
974 : : continue; \
975 : : }
976 : 210744 : oper("**", 4, pow((double)left, (double)right));
977 : 210738 : oper("*", 3, left*right);
978 [ + + + + : 210726 : oper("/", 3, right != 0.0 ? left / right : oper_wrapped_throw(error_id_division_by_zero));
- + + - +
- + - + -
- - + - ]
979 [ - + - - : 210720 : oper("%", 3, right != 0.0 ? fmod((double)left, (double)right) : oper_wrapped_throw(error_id_modulo_by_zero));
- - - - -
- - - - -
- - - - ]
980 : 210720 : oper("+", 2, left+right);
981 : 92 : oper("-", 2, left-right);
982 [ - + - - : 3 : oper("<<", 1, right >= 0.0 ? (int64_t)left<<(uint64_t)right : oper_wrapped_throw(error_id_negative_shift));
- - - - -
- - - - -
- - - - ]
983 [ - + - - : 3 : oper(">>", 1, right >= 0.0 ? (int64_t)left>>(uint64_t)right : oper_wrapped_throw(error_id_negative_shift));
- - - - -
- - - - -
- - - - ]
984 : 3 : oper("&", 0, (int64_t)left&(int64_t)right);
985 : 3 : oper("|", 0, (int64_t)left|(int64_t)right);
986 : 3 : oper("^", 0, (int64_t)left^(int64_t)right);
987 : 3 : asar_throw_error(2, error_type_block, error_id_unknown_operator);
988 : : #undef oper
989 : : }
990 : : return left;
991 : 813694 : }
992 : :
993 : : //static autoptr<char*> freeme;
994 : 354553 : double math(const char * s)
995 : : {
996 : : //free(freeme);
997 : : //freeme=NULL;
998 : 354553 : foundlabel=false;
999 : 354553 : foundlabel_static=true;
1000 : 354553 : forwardlabel=false;
1001 : : double rval;
1002 : :
1003 [ + - + + ]: 354553 : if(math_pri || default_math_pri)
1004 : : {
1005 : 1559 : str = s;
1006 : 1559 : rval = eval(0);
1007 : : }
1008 : : else
1009 : : {
1010 : 352994 : str = s;
1011 : 352994 : rval = eval(0);
1012 : :
1013 : : double pri_rval = NAN;
1014 : :
1015 : 352985 : suppress_all_warnings = true;
1016 : 352985 : math_pri = true;
1017 : : try
1018 : : {
1019 : 352985 : str = s;
1020 : 352985 : pri_rval = eval(0);
1021 : : }
1022 : 0 : catch (errfatal&) {}
1023 : 352985 : suppress_all_warnings = false;
1024 : 352985 : math_pri = false;
1025 : :
1026 [ + - ]: 352985 : if (pri_rval != rval)
1027 : : {
1028 : 0 : asar_throw_warning(2, warning_id_feature_deprecated, "xkas style left to right math ", "apply order of operations");
1029 : : }
1030 : : }
1031 [ + + ]: 354544 : if (*str)
1032 : : {
1033 [ - + ]: 3 : if (*str == ',') asar_throw_error(2, error_type_block, error_id_invalid_input);
1034 : 3 : else asar_throw_error(2, error_type_block, error_id_mismatched_parentheses);
1035 : : }
1036 : 354541 : return rval;
1037 : : }
1038 : :
1039 : 281 : void initmathcore()
1040 : : {
1041 : 281 : functions.reset();
1042 : 281 : builtin_functions.each([](const char* key, double (*val)()) {
1043 : 17703 : functions[key] = val;
1044 : 35406 : functions[STR "_" + key] = val;
1045 : 17703 : });
1046 : 281 : user_functions.reset();
1047 : 281 : }
1048 : :
1049 : 279 : void deinitmathcore()
1050 : : {
1051 : : //not needed
1052 : 279 : }
|