1 // command.cpp: implements the parsing and execution of a tiny script language which
2 // is largely backwards compatible with the quake console language.
3 
4 #include "engine.h"
5 
6 hashnameset<ident> idents; // contains ALL vars/commands/aliases
7 vector<ident *> identmap;
8 ident *dummyident = NULL;
9 
10 int identflags = 0;
11 
12 enum
13 {
14     MAXARGS = 25,
15     MAXRESULTS = 7,
16     MAXCOMARGS = 12
17 };
18 
19 VARN(numargs, _numargs, MAXARGS, 0, 0);
20 
freearg(tagval & v)21 static inline void freearg(tagval &v)
22 {
23     switch(v.type)
24     {
25         case VAL_STR: delete[] v.s; break;
26         case VAL_CODE: if(v.code[-1] == CODE_START) delete[] (uchar *)&v.code[-1]; break;
27     }
28 }
29 
forcenull(tagval & v)30 static inline void forcenull(tagval &v)
31 {
32     switch(v.type)
33     {
34         case VAL_NULL: return;
35     }
36     freearg(v);
37     v.setnull();
38 }
39 
forcefloat(tagval & v)40 static inline float forcefloat(tagval &v)
41 {
42     float f = 0.0f;
43     switch(v.type)
44     {
45         case VAL_INT: f = v.i; break;
46         case VAL_STR: case VAL_MACRO: case VAL_CSTR: f = parsefloat(v.s); break;
47         case VAL_FLOAT: return v.f;
48     }
49     freearg(v);
50     v.setfloat(f);
51     return f;
52 }
53 
forceint(tagval & v)54 static inline int forceint(tagval &v)
55 {
56     int i = 0;
57     switch(v.type)
58     {
59         case VAL_FLOAT: i = v.f; break;
60         case VAL_STR: case VAL_MACRO: case VAL_CSTR: i = parseint(v.s); break;
61         case VAL_INT: return v.i;
62     }
63     freearg(v);
64     v.setint(i);
65     return i;
66 }
67 
forcestr(tagval & v)68 static inline const char *forcestr(tagval &v)
69 {
70     const char *s = "";
71     switch(v.type)
72     {
73         case VAL_FLOAT: s = floatstr(v.f); break;
74         case VAL_INT: s = intstr(v.i); break;
75         case VAL_MACRO: case VAL_CSTR: s = v.s; break;
76         case VAL_STR: return v.s;
77     }
78     freearg(v);
79     v.setstr(newstring(s));
80     return s;
81 }
82 
forcearg(tagval & v,int type)83 static inline void forcearg(tagval &v, int type)
84 {
85     switch(type)
86     {
87         case RET_STR: if(v.type != VAL_STR) forcestr(v); break;
88         case RET_INT: if(v.type != VAL_INT) forceint(v); break;
89         case RET_FLOAT: if(v.type != VAL_FLOAT) forcefloat(v); break;
90     }
91 }
92 
cleanup()93 void tagval::cleanup()
94 {
95     freearg(*this);
96 }
97 
freeargs(tagval * args,int & oldnum,int newnum)98 static inline void freeargs(tagval *args, int &oldnum, int newnum)
99 {
100     for(int i = newnum; i < oldnum; i++) freearg(args[i]);
101     oldnum = newnum;
102 }
103 
cleancode(ident & id)104 static inline void cleancode(ident &id)
105 {
106     if(id.code)
107     {
108         id.code[0] -= 0x100;
109         if(int(id.code[0]) < 0x100) delete[] id.code;
110         id.code = NULL;
111     }
112 }
113 
114 struct nullval : tagval
115 {
nullvalnullval116     nullval() { setnull(); }
117 } nullval;
118 tagval noret = nullval, *commandret = &noret;
119 
clear_command()120 void clear_command()
121 {
122     enumerate(idents, ident, i,
123     {
124         if(i.type==ID_ALIAS)
125         {
126             DELETEA(i.name);
127             i.forcenull();
128             DELETEA(i.code);
129         }
130     });
131 }
132 
clearoverride(ident & i)133 void clearoverride(ident &i)
134 {
135     if(!(i.flags&IDF_OVERRIDDEN)) return;
136     switch(i.type)
137     {
138         case ID_ALIAS:
139             if(i.valtype==VAL_STR)
140             {
141                 if(!i.val.s[0]) break;
142                 delete[] i.val.s;
143             }
144             cleancode(i);
145             i.valtype = VAL_STR;
146             i.val.s = newstring("");
147             break;
148         case ID_VAR:
149             *i.storage.i = i.overrideval.i;
150             i.changed();
151             break;
152         case ID_FVAR:
153             *i.storage.f = i.overrideval.f;
154             i.changed();
155             break;
156         case ID_SVAR:
157             delete[] *i.storage.s;
158             *i.storage.s = i.overrideval.s;
159             i.changed();
160             break;
161     }
162     i.flags &= ~IDF_OVERRIDDEN;
163 }
164 
clearoverrides()165 void clearoverrides()
166 {
167     enumerate(idents, ident, i, clearoverride(i));
168 }
169 
170 static bool initedidents = false;
171 static vector<ident> *identinits = NULL;
172 
addident(const ident & id)173 static inline ident *addident(const ident &id)
174 {
175     if(!initedidents)
176     {
177         if(!identinits) identinits = new vector<ident>;
178         identinits->add(id);
179         return NULL;
180     }
181     ident &def = idents.access(id.name, id);
182     def.index = identmap.length();
183     return identmap.add(&def);
184 }
185 
initidents()186 static bool initidents()
187 {
188     initedidents = true;
189     for(int i = 0; i < MAXARGS; i++)
190     {
191         defformatstring(argname, "arg%d", i+1);
192         newident(argname, IDF_ARG);
193     }
194     dummyident = newident("//dummy", IDF_UNKNOWN);
195     if(identinits)
196     {
197         loopv(*identinits) addident((*identinits)[i]);
198         DELETEP(identinits);
199     }
200     return true;
201 }
202 UNUSED static bool forceinitidents = initidents();
203 
204 static const char *sourcefile = NULL, *sourcestr = NULL;
205 
debugline(const char * p,const char * fmt)206 static const char *debugline(const char *p, const char *fmt)
207 {
208     if(!sourcestr) return fmt;
209     int num = 1;
210     const char *line = sourcestr;
211     for(;;)
212     {
213         const char *end = strchr(line, '\n');
214         if(!end) end = line + strlen(line);
215         if(p >= line && p <= end)
216         {
217             static string buf;
218             if(sourcefile) formatstring(buf, "%s:%d: %s", sourcefile, num, fmt);
219             else formatstring(buf, "%d: %s", num, fmt);
220             return buf;
221         }
222         if(!*end) break;
223         line = end + 1;
224         num++;
225     }
226     return fmt;
227 }
228 
229 static struct identlink
230 {
231     ident *id;
232     identlink *next;
233     int usedargs;
234     identstack *argstack;
235 } noalias = { NULL, NULL, (1<<MAXARGS)-1, NULL }, *aliasstack = &noalias;
236 
237 VAR(dbgalias, 0, 4, 1000);
238 
debugalias()239 static void debugalias()
240 {
241     if(!dbgalias) return;
242     int total = 0, depth = 0;
243     for(identlink *l = aliasstack; l != &noalias; l = l->next) total++;
244     for(identlink *l = aliasstack; l != &noalias; l = l->next)
245     {
246         ident *id = l->id;
247         ++depth;
248         if(depth < dbgalias) conoutf(CON_ERROR, "  %d) %s", total-depth+1, id->name);
249         else if(l->next == &noalias) conoutf(CON_ERROR, depth == dbgalias ? "  %d) %s" : "  ..%d) %s", total-depth+1, id->name);
250     }
251 }
252 
253 static int nodebug = 0;
254 
255 static void debugcode(const char *fmt, ...) PRINTFARGS(1, 2);
256 
debugcode(const char * fmt,...)257 static void debugcode(const char *fmt, ...)
258 {
259     if(nodebug) return;
260 
261     va_list args;
262     va_start(args, fmt);
263     conoutfv(CON_ERROR, fmt, args);
264     va_end(args);
265 
266     debugalias();
267 }
268 
269 static void debugcodeline(const char *p, const char *fmt, ...) PRINTFARGS(2, 3);
270 
debugcodeline(const char * p,const char * fmt,...)271 static void debugcodeline(const char *p, const char *fmt, ...)
272 {
273     if(nodebug) return;
274 
275     va_list args;
276     va_start(args, fmt);
277     conoutfv(CON_ERROR, debugline(p, fmt), args);
278     va_end(args);
279 
280     debugalias();
281 }
282 
283 ICOMMAND(nodebug, "e", (uint *body), { nodebug++; executeret(body, *commandret); nodebug--; });
284 
addident(ident * id)285 void addident(ident *id)
286 {
287     addident(*id);
288 }
289 
pusharg(ident & id,const tagval & v,identstack & stack)290 static inline void pusharg(ident &id, const tagval &v, identstack &stack)
291 {
292     stack.val = id.val;
293     stack.valtype = id.valtype;
294     stack.next = id.stack;
295     id.stack = &stack;
296     id.setval(v);
297     cleancode(id);
298 }
299 
poparg(ident & id)300 static inline void poparg(ident &id)
301 {
302     if(!id.stack) return;
303     identstack *stack = id.stack;
304     if(id.valtype == VAL_STR) delete[] id.val.s;
305     id.setval(*stack);
306     cleancode(id);
307     id.stack = stack->next;
308 }
309 
undoarg(ident & id,identstack & stack)310 static inline void undoarg(ident &id, identstack &stack)
311 {
312     identstack *prev = id.stack;
313     stack.val = id.val;
314     stack.valtype = id.valtype;
315     stack.next = prev;
316     id.stack = prev->next;
317     id.setval(*prev);
318     cleancode(id);
319 }
320 
321 #define UNDOARGS \
322     identstack argstack[MAXARGS]; \
323     for(int argmask = aliasstack->usedargs, i = 0; argmask; argmask >>= 1, i++) if(argmask&1) \
324         undoarg(*identmap[i], argstack[i]); \
325     identlink *prevstack = aliasstack->next; \
326     identlink aliaslink = { aliasstack->id, aliasstack, prevstack->usedargs, prevstack->argstack }; \
327     aliasstack = &aliaslink;
328 
redoarg(ident & id,const identstack & stack)329 static inline void redoarg(ident &id, const identstack &stack)
330 {
331     identstack *prev = stack.next;
332     prev->val = id.val;
333     prev->valtype = id.valtype;
334     id.stack = prev;
335     id.setval(stack);
336     cleancode(id);
337 }
338 
339 #define REDOARGS \
340     prevstack->usedargs = aliaslink.usedargs; \
341     aliasstack = aliaslink.next; \
342     for(int argmask = aliasstack->usedargs, i = 0; argmask; argmask >>= 1, i++) if(argmask&1) \
343         redoarg(*identmap[i], argstack[i]);
344 
345 ICOMMAND(push, "rTe", (ident *id, tagval *v, uint *code),
346 {
347     if(id->type != ID_ALIAS || id->index < MAXARGS) return;
348     identstack stack;
349     pusharg(*id, *v, stack);
350     v->type = VAL_NULL;
351     id->flags &= ~IDF_UNKNOWN;
352     executeret(code, *commandret);
353     poparg(*id);
354 });
355 
pushalias(ident & id,identstack & stack)356 static inline void pushalias(ident &id, identstack &stack)
357 {
358     if(id.type == ID_ALIAS && id.index >= MAXARGS)
359     {
360         pusharg(id, nullval, stack);
361         id.flags &= ~IDF_UNKNOWN;
362     }
363 }
364 
popalias(ident & id)365 static inline void popalias(ident &id)
366 {
367     if(id.type == ID_ALIAS && id.index >= MAXARGS) poparg(id);
368 }
369 
370 KEYWORD(local, ID_LOCAL);
371 
checknumber(const char * s)372 static inline bool checknumber(const char *s)
373 {
374     if(isdigit(s[0])) return true;
375     else switch(s[0])
376     {
377         case '+': case '-': return isdigit(s[1]) || (s[1] == '.' && isdigit(s[2]));
378         case '.': return isdigit(s[1]) != 0;
379         default: return false;
380     }
381 }
checknumber(const stringslice & s)382 static inline bool checknumber(const stringslice &s) { return checknumber(s.str); }
383 
newident(const T & name,int flags)384 template<class T> static inline ident *newident(const T &name, int flags)
385 {
386     ident *id = idents.access(name);
387     if(!id)
388     {
389         if(checknumber(name))
390         {
391             debugcode("number %.*s is not a valid identifier name", stringlen(name), stringptr(name));
392             return dummyident;
393         }
394         id = addident(ident(ID_ALIAS, newstring(name), flags));
395     }
396     return id;
397 }
398 
forceident(tagval & v)399 static inline ident *forceident(tagval &v)
400 {
401     switch(v.type)
402     {
403         case VAL_IDENT: return v.id;
404         case VAL_MACRO: case VAL_CSTR:
405         {
406             ident *id = newident(v.s, IDF_UNKNOWN);
407             v.setident(id);
408             return id;
409         }
410         case VAL_STR:
411         {
412             ident *id = newident(v.s, IDF_UNKNOWN);
413             delete[] v.s;
414             v.setident(id);
415             return id;
416         }
417     }
418     freearg(v);
419     v.setident(dummyident);
420     return dummyident;
421 }
422 
newident(const char * name,int flags)423 ident *newident(const char *name, int flags)
424 {
425     return newident<const char *>(name, flags);
426 }
427 
writeident(const char * name,int flags)428 ident *writeident(const char *name, int flags)
429 {
430     ident *id = newident(name, flags);
431     if(id->index < MAXARGS && !(aliasstack->usedargs&(1<<id->index)))
432     {
433         pusharg(*id, nullval, aliasstack->argstack[id->index]);
434         aliasstack->usedargs |= 1<<id->index;
435     }
436     return id;
437 }
438 
readident(const char * name)439 ident *readident(const char *name)
440 {
441     ident *id = idents.access(name);
442     if(id && id->index < MAXARGS && !(aliasstack->usedargs&(1<<id->index)))
443        return NULL;
444     return id;
445 }
446 
resetvar(char * name)447 void resetvar(char *name)
448 {
449     ident *id = idents.access(name);
450     if(!id) return;
451     if(id->flags&IDF_READONLY) debugcode("variable %s is read-only", id->name);
452     else clearoverride(*id);
453 }
454 
455 COMMAND(resetvar, "s");
456 
setarg(ident & id,tagval & v)457 static inline void setarg(ident &id, tagval &v)
458 {
459     if(aliasstack->usedargs&(1<<id.index))
460     {
461         if(id.valtype == VAL_STR) delete[] id.val.s;
462         id.setval(v);
463         cleancode(id);
464     }
465     else
466     {
467         pusharg(id, v, aliasstack->argstack[id.index]);
468         aliasstack->usedargs |= 1<<id.index;
469     }
470 }
471 
setalias(ident & id,tagval & v)472 static inline void setalias(ident &id, tagval &v)
473 {
474     if(id.valtype == VAL_STR) delete[] id.val.s;
475     id.setval(v);
476     cleancode(id);
477     id.flags = (id.flags & identflags) | identflags;
478 }
479 
setalias(const char * name,tagval & v)480 static void setalias(const char *name, tagval &v)
481 {
482     ident *id = idents.access(name);
483     if(id)
484     {
485         switch(id->type)
486         {
487             case ID_ALIAS:
488                 if(id->index < MAXARGS) setarg(*id, v); else setalias(*id, v);
489                 return;
490             case ID_VAR:
491                 setvarchecked(id, v.getint());
492                 break;
493             case ID_FVAR:
494                 setfvarchecked(id, v.getfloat());
495                 break;
496             case ID_SVAR:
497                 setsvarchecked(id, v.getstr());
498                 break;
499             default:
500                 debugcode("cannot redefine builtin %s with an alias", id->name);
501                 break;
502         }
503         freearg(v);
504     }
505     else if(checknumber(name))
506     {
507         debugcode("cannot alias number %s", name);
508         freearg(v);
509     }
510     else
511     {
512         addident(ident(ID_ALIAS, newstring(name), v, identflags));
513     }
514 }
515 
alias(const char * name,const char * str)516 void alias(const char *name, const char *str)
517 {
518     tagval v;
519     v.setstr(newstring(str));
520     setalias(name, v);
521 }
522 
alias(const char * name,tagval & v)523 void alias(const char *name, tagval &v)
524 {
525     setalias(name, v);
526 }
527 
528 ICOMMAND(alias, "sT", (const char *name, tagval *v),
529 {
530     setalias(name, *v);
531     v->type = VAL_NULL;
532 });
533 
534 // variable's and commands are registered through globals, see cube.h
535 
variable(const char * name,int min,int cur,int max,int * storage,identfun fun,int flags)536 int variable(const char *name, int min, int cur, int max, int *storage, identfun fun, int flags)
537 {
538     addident(ident(ID_VAR, name, min, max, storage, (void *)fun, flags));
539     return cur;
540 }
541 
fvariable(const char * name,float min,float cur,float max,float * storage,identfun fun,int flags)542 float fvariable(const char *name, float min, float cur, float max, float *storage, identfun fun, int flags)
543 {
544     addident(ident(ID_FVAR, name, min, max, storage, (void *)fun, flags));
545     return cur;
546 }
547 
svariable(const char * name,const char * cur,char ** storage,identfun fun,int flags)548 char *svariable(const char *name, const char *cur, char **storage, identfun fun, int flags)
549 {
550     addident(ident(ID_SVAR, name, storage, (void *)fun, flags));
551     return newstring(cur);
552 }
553 
554 struct defvar : identval
555 {
556     char *name;
557     uint *onchange;
558 
defvardefvar559     defvar() : name(NULL), onchange(NULL) {}
560 
~defvardefvar561     ~defvar()
562     {
563         DELETEA(name);
564         if(onchange) freecode(onchange);
565     }
566 
changeddefvar567     static void changed(ident *id)
568     {
569         defvar *v = (defvar *)id->storage.p;
570         if(v->onchange) execute(v->onchange);
571     }
572 };
573 
574 hashnameset<defvar> defvars;
575 
576 #define DEFVAR(cmdname, fmt, args, body) \
577     ICOMMAND(cmdname, fmt, args, \
578     { \
579         if(idents.access(name)) { debugcode("cannot redefine %s as a variable", name); return; } \
580         name = newstring(name); \
581         defvar &def = defvars[name]; \
582         def.name = name; \
583         def.onchange = onchange[0] ? compilecode(onchange) : NULL; \
584         body; \
585     });
586 #define DEFIVAR(cmdname, flags) \
587     DEFVAR(cmdname, "siiis", (char *name, int *min, int *cur, int *max, char *onchange), \
588         def.i = variable(name, *min, *cur, *max, &def.i, def.onchange ? defvar::changed : NULL, flags))
589 #define DEFFVAR(cmdname, flags) \
590     DEFVAR(cmdname, "sfffs", (char *name, float *min, float *cur, float *max, char *onchange), \
591         def.f = fvariable(name, *min, *cur, *max, &def.f, def.onchange ? defvar::changed : NULL, flags))
592 #define DEFSVAR(cmdname, flags) \
593     DEFVAR(cmdname, "sss", (char *name, char *cur, char *onchange), \
594         def.s = svariable(name, cur, &def.s, def.onchange ? defvar::changed : NULL, flags))
595 
596 DEFIVAR(defvar, 0);
597 DEFIVAR(defvarp, IDF_PERSIST);
598 DEFFVAR(deffvar, 0);
599 DEFFVAR(deffvarp, IDF_PERSIST);
600 DEFSVAR(defsvar, 0);
601 DEFSVAR(defsvarp, IDF_PERSIST);
602 
603 #define _GETVAR(id, vartype, name, retval) \
604     ident *id = idents.access(name); \
605     if(!id || id->type!=vartype) return retval;
606 #define GETVAR(id, name, retval) _GETVAR(id, ID_VAR, name, retval)
607 #define OVERRIDEVAR(errorval, saveval, resetval, clearval) \
608     if(identflags&IDF_OVERRIDDEN || id->flags&IDF_OVERRIDE) \
609     { \
610         if(id->flags&IDF_PERSIST) \
611         { \
612             debugcode("cannot override persistent variable %s", id->name); \
613             errorval; \
614         } \
615         if(!(id->flags&IDF_OVERRIDDEN)) { saveval; id->flags |= IDF_OVERRIDDEN; } \
616         else { clearval; } \
617     } \
618     else \
619     { \
620         if(id->flags&IDF_OVERRIDDEN) { resetval; id->flags &= ~IDF_OVERRIDDEN; } \
621         clearval; \
622     }
623 
setvar(const char * name,int i,bool dofunc,bool doclamp)624 void setvar(const char *name, int i, bool dofunc, bool doclamp)
625 {
626     GETVAR(id, name, );
627     OVERRIDEVAR(return, id->overrideval.i = *id->storage.i, , )
628     if(doclamp) *id->storage.i = clamp(i, id->minval, id->maxval);
629     else *id->storage.i = i;
630     if(dofunc) id->changed();
631 }
setfvar(const char * name,float f,bool dofunc,bool doclamp)632 void setfvar(const char *name, float f, bool dofunc, bool doclamp)
633 {
634     _GETVAR(id, ID_FVAR, name, );
635     OVERRIDEVAR(return, id->overrideval.f = *id->storage.f, , );
636     if(doclamp) *id->storage.f = clamp(f, id->minvalf, id->maxvalf);
637     else *id->storage.f = f;
638     if(dofunc) id->changed();
639 }
setsvar(const char * name,const char * str,bool dofunc)640 void setsvar(const char *name, const char *str, bool dofunc)
641 {
642     _GETVAR(id, ID_SVAR, name, );
643     OVERRIDEVAR(return, id->overrideval.s = *id->storage.s, delete[] id->overrideval.s, delete[] *id->storage.s);
644     *id->storage.s = newstring(str);
645     if(dofunc) id->changed();
646 }
getvar(const char * name)647 int getvar(const char *name)
648 {
649     GETVAR(id, name, 0);
650     return *id->storage.i;
651 }
getvarmin(const char * name)652 int getvarmin(const char *name)
653 {
654     GETVAR(id, name, 0);
655     return id->minval;
656 }
getvarmax(const char * name)657 int getvarmax(const char *name)
658 {
659     GETVAR(id, name, 0);
660     return id->maxval;
661 }
getfvarmin(const char * name)662 float getfvarmin(const char *name)
663 {
664     _GETVAR(id, ID_FVAR, name, 0);
665     return id->minvalf;
666 }
getfvarmax(const char * name)667 float getfvarmax(const char *name)
668 {
669     _GETVAR(id, ID_FVAR, name, 0);
670     return id->maxvalf;
671 }
672 
673 ICOMMAND(getvarmin, "s", (char *s), intret(getvarmin(s)));
674 ICOMMAND(getvarmax, "s", (char *s), intret(getvarmax(s)));
675 ICOMMAND(getfvarmin, "s", (char *s), floatret(getfvarmin(s)));
676 ICOMMAND(getfvarmax, "s", (char *s), floatret(getfvarmax(s)));
677 
identexists(const char * name)678 bool identexists(const char *name) { return idents.access(name)!=NULL; }
679 ICOMMAND(identexists, "s", (char *s), intret(identexists(s) ? 1 : 0));
680 
getident(const char * name)681 ident *getident(const char *name) { return idents.access(name); }
682 
touchvar(const char * name)683 void touchvar(const char *name)
684 {
685     ident *id = idents.access(name);
686     if(id) switch(id->type)
687     {
688         case ID_VAR:
689         case ID_FVAR:
690         case ID_SVAR:
691             id->changed();
692             break;
693     }
694 }
695 
getalias(const char * name)696 const char *getalias(const char *name)
697 {
698     ident *i = idents.access(name);
699     return i && i->type==ID_ALIAS && (i->index >= MAXARGS || aliasstack->usedargs&(1<<i->index)) ? i->getstr() : "";
700 }
701 
702 ICOMMAND(getalias, "s", (char *s), result(getalias(s)));
703 
clampvar(ident * id,int val,int minval,int maxval)704 int clampvar(ident *id, int val, int minval, int maxval)
705 {
706     if(val < minval) val = minval;
707     else if(val > maxval) val = maxval;
708     else return val;
709     debugcode(id->flags&IDF_HEX ?
710             (minval <= 255 ? "valid range for %s is %d..0x%X" : "valid range for %s is 0x%X..0x%X") :
711             "valid range for %s is %d..%d",
712         id->name, minval, maxval);
713     return val;
714 }
715 
setvarchecked(ident * id,int val)716 void setvarchecked(ident *id, int val)
717 {
718     if(id->flags&IDF_READONLY) debugcode("variable %s is read-only", id->name);
719 #ifndef STANDALONE
720     else if(!(id->flags&IDF_OVERRIDE) || identflags&IDF_OVERRIDDEN || game::allowedittoggle())
721 #else
722     else
723 #endif
724     {
725         OVERRIDEVAR(return, id->overrideval.i = *id->storage.i, , )
726         if(val < id->minval || val > id->maxval) val = clampvar(id, val, id->minval, id->maxval);
727         *id->storage.i = val;
728         id->changed();                                             // call trigger function if available
729 #ifndef STANDALONE
730         if(id->flags&IDF_OVERRIDE && !(identflags&IDF_OVERRIDDEN)) game::vartrigger(id);
731 #endif
732     }
733 }
734 
setvarchecked(ident * id,tagval * args,int numargs)735 static inline void setvarchecked(ident *id, tagval *args, int numargs)
736 {
737     int val = forceint(args[0]);
738     if(id->flags&IDF_HEX && numargs > 1)
739     {
740         val = (val << 16) | (forceint(args[1])<<8);
741         if(numargs > 2) val |= forceint(args[2]);
742     }
743     setvarchecked(id, val);
744 }
745 
clampfvar(ident * id,float val,float minval,float maxval)746 float clampfvar(ident *id, float val, float minval, float maxval)
747 {
748     if(val < minval) val = minval;
749     else if(val > maxval) val = maxval;
750     else return val;
751     debugcode("valid range for %s is %s..%s", id->name, floatstr(minval), floatstr(maxval));
752     return val;
753 }
754 
setfvarchecked(ident * id,float val)755 void setfvarchecked(ident *id, float val)
756 {
757     if(id->flags&IDF_READONLY) debugcode("variable %s is read-only", id->name);
758 #ifndef STANDALONE
759     else if(!(id->flags&IDF_OVERRIDE) || identflags&IDF_OVERRIDDEN || game::allowedittoggle())
760 #else
761     else
762 #endif
763     {
764         OVERRIDEVAR(return, id->overrideval.f = *id->storage.f, , );
765         if(val < id->minvalf || val > id->maxvalf) val = clampfvar(id, val, id->minvalf, id->maxvalf);
766         *id->storage.f = val;
767         id->changed();
768 #ifndef STANDALONE
769         if(id->flags&IDF_OVERRIDE && !(identflags&IDF_OVERRIDDEN)) game::vartrigger(id);
770 #endif
771     }
772 }
773 
setsvarchecked(ident * id,const char * val)774 void setsvarchecked(ident *id, const char *val)
775 {
776     if(id->flags&IDF_READONLY) debugcode("variable %s is read-only", id->name);
777 #ifndef STANDALONE
778     else if(!(id->flags&IDF_OVERRIDE) || identflags&IDF_OVERRIDDEN || game::allowedittoggle())
779 #else
780     else
781 #endif
782     {
783         OVERRIDEVAR(return, id->overrideval.s = *id->storage.s, delete[] id->overrideval.s, delete[] *id->storage.s);
784         *id->storage.s = newstring(val);
785         id->changed();
786 #ifndef STANDALONE
787         if(id->flags&IDF_OVERRIDE && !(identflags&IDF_OVERRIDDEN)) game::vartrigger(id);
788 #endif
789     }
790 }
791 
addcommand(const char * name,identfun fun,const char * args,int type)792 bool addcommand(const char *name, identfun fun, const char *args, int type)
793 {
794     uint argmask = 0;
795     int numargs = 0;
796     bool limit = true;
797     if(args) for(const char *fmt = args; *fmt; fmt++) switch(*fmt)
798     {
799         case 'i': case 'b': case 'f': case 'F': case 't': case 'T': case 'E': case 'N': case 'D': if(numargs < MAXARGS) numargs++; break;
800         case 'S': case 's': case 'e': case 'r': case '$': if(numargs < MAXARGS) { argmask |= 1<<numargs; numargs++; } break;
801         case '1': case '2': case '3': case '4': if(numargs < MAXARGS) fmt -= *fmt-'0'+1; break;
802         case 'C': case 'V': limit = false; break;
803         default: fatal("builtin %s declared with illegal type: %s", name, args); break;
804     }
805     if(limit && numargs > MAXCOMARGS) fatal("builtin %s declared with too many args: %d", name, numargs);
806     addident(ident(type, name, args, argmask, numargs, (void *)fun));
807     return false;
808 }
809 
parsestring(const char * p)810 const char *parsestring(const char *p)
811 {
812     for(; *p; p++) switch(*p)
813     {
814         case '\r':
815         case '\n':
816         case '\"':
817             return p;
818         case '^':
819             if(*++p) break;
820             return p;
821     }
822     return p;
823 }
824 
unescapestring(char * dst,const char * src,const char * end)825 int unescapestring(char *dst, const char *src, const char *end)
826 {
827     char *start = dst;
828     while(src < end)
829     {
830         int c = *src++;
831         if(c == '^')
832         {
833             if(src >= end) break;
834             int e = *src++;
835             switch(e)
836             {
837                 case 'n': *dst++ = '\n'; break;
838                 case 't': *dst++ = '\t'; break;
839                 case 'f': *dst++ = '\f'; break;
840                 default: *dst++ = e; break;
841             }
842         }
843         else *dst++ = c;
844     }
845     *dst = '\0';
846     return dst - start;
847 }
848 
conc(vector<char> & buf,tagval * v,int n,bool space,const char * prefix=NULL,int prefixlen=0)849 static char *conc(vector<char> &buf, tagval *v, int n, bool space, const char *prefix = NULL, int prefixlen = 0)
850 {
851     if(prefix)
852     {
853         buf.put(prefix, prefixlen);
854         if(space && n) buf.add(' ');
855     }
856     loopi(n)
857     {
858         const char *s = "";
859         int len = 0;
860         switch(v[i].type)
861         {
862             case VAL_INT: s = intstr(v[i].i); break;
863             case VAL_FLOAT: s = floatstr(v[i].f); break;
864             case VAL_STR: case VAL_CSTR: s = v[i].s; break;
865             case VAL_MACRO: s = v[i].s; len = v[i].code[-1]>>8; goto haslen;
866         }
867         len = int(strlen(s));
868     haslen:
869         buf.put(s, len);
870         if(i == n-1) break;
871         if(space) buf.add(' ');
872     }
873     buf.add('\0');
874     return buf.getbuf();
875 }
876 
conc(tagval * v,int n,bool space,const char * prefix,int prefixlen)877 static char *conc(tagval *v, int n, bool space, const char *prefix, int prefixlen)
878 {
879     static int vlen[MAXARGS];
880     static char numbuf[3*MAXSTRLEN];
881     int len = prefixlen, numlen = 0, i = 0;
882     for(; i < n; i++) switch(v[i].type)
883     {
884         case VAL_MACRO: len += (vlen[i] = v[i].code[-1]>>8); break;
885         case VAL_STR: case VAL_CSTR: len += (vlen[i] = int(strlen(v[i].s))); break;
886         case VAL_INT:
887             if(numlen + MAXSTRLEN > int(sizeof(numbuf))) goto overflow;
888             intformat(&numbuf[numlen], v[i].i);
889             numlen += (vlen[i] = strlen(&numbuf[numlen]));
890             break;
891         case VAL_FLOAT:
892             if(numlen + MAXSTRLEN > int(sizeof(numbuf))) goto overflow;
893             floatformat(&numbuf[numlen], v[i].f);
894             numlen += (vlen[i] = strlen(&numbuf[numlen]));
895             break;
896         default: vlen[i] = 0; break;
897     }
898 overflow:
899     if(space) len += max(prefix ? i : i-1, 0);
900     char *buf = newstring(len + numlen);
901     int offset = 0, numoffset = 0;
902     if(prefix)
903     {
904         memcpy(buf, prefix, prefixlen);
905         offset += prefixlen;
906         if(space && i) buf[offset++] = ' ';
907     }
908     loopj(i)
909     {
910         if(v[j].type == VAL_INT || v[j].type == VAL_FLOAT)
911         {
912             memcpy(&buf[offset], &numbuf[numoffset], vlen[j]);
913             numoffset += vlen[j];
914         }
915         else if(vlen[j]) memcpy(&buf[offset], v[j].s, vlen[j]);
916         offset += vlen[j];
917         if(j==i-1) break;
918         if(space) buf[offset++] = ' ';
919     }
920     buf[offset] = '\0';
921     if(i < n)
922     {
923         char *morebuf = conc(&v[i], n-i, space, buf, offset);
924         delete[] buf;
925         return morebuf;
926     }
927     return buf;
928 }
929 
conc(tagval * v,int n,bool space)930 static inline char *conc(tagval *v, int n, bool space)
931 {
932     return conc(v, n, space, NULL, 0);
933 }
934 
conc(tagval * v,int n,bool space,const char * prefix)935 static inline char *conc(tagval *v, int n, bool space, const char *prefix)
936 {
937     return conc(v, n, space, prefix, strlen(prefix));
938 }
939 
skipcomments(const char * & p)940 static inline void skipcomments(const char *&p)
941 {
942     for(;;)
943     {
944         p += strspn(p, " \t\r");
945         if(p[0]!='/' || p[1]!='/') break;
946         p += strcspn(p, "\n\0");
947     }
948 }
949 
950 static vector<char> strbuf[4];
951 static int stridx = 0;
952 
cutstring(const char * & p,stringslice & s)953 static inline void cutstring(const char *&p, stringslice &s)
954 {
955     p++;
956     const char *end = parsestring(p);
957     int maxlen = int(end-p) + 1;
958 
959     stridx = (stridx + 1)%4;
960     vector<char> &buf = strbuf[stridx];
961     if(buf.alen < maxlen) buf.growbuf(maxlen);
962 
963     s.str = buf.buf;
964     s.len = unescapestring(buf.buf, p, end);
965     p = end;
966     if(*p=='\"') p++;
967 }
968 
cutstring(const char * & p)969 static inline char *cutstring(const char *&p)
970 {
971     p++;
972     const char *end = parsestring(p);
973     char *buf = newstring(end-p);
974     unescapestring(buf, p, end);
975     p = end;
976     if(*p=='\"') p++;
977     return buf;
978 }
979 
parseword(const char * p)980 static inline const char *parseword(const char *p)
981 {
982     const int maxbrak = 100;
983     static char brakstack[maxbrak];
984     int brakdepth = 0;
985     for(;; p++)
986     {
987         p += strcspn(p, "\"/;()[] \t\r\n\0");
988         switch(p[0])
989         {
990             case '"': case ';': case ' ': case '\t': case '\r': case '\n': case '\0': return p;
991             case '/': if(p[1] == '/') return p; break;
992             case '[': case '(': if(brakdepth >= maxbrak) return p; brakstack[brakdepth++] = p[0]; break;
993             case ']': if(brakdepth <= 0 || brakstack[--brakdepth] != '[') return p; break;
994             case ')': if(brakdepth <= 0 || brakstack[--brakdepth] != '(') return p; break;
995         }
996     }
997     return p;
998 }
999 
cutword(const char * & p,stringslice & s)1000 static inline void cutword(const char *&p, stringslice &s)
1001 {
1002     s.str = p;
1003     p = parseword(p);
1004     s.len = int(p-s.str);
1005 }
1006 
cutword(const char * & p)1007 static inline char *cutword(const char *&p)
1008 {
1009     const char *word = p;
1010     p = parseword(p);
1011     return p!=word ? newstring(word, p-word) : NULL;
1012 }
1013 
1014 #define retcode(type, defaultret) ((type) >= VAL_ANY ? ((type) == VAL_CSTR ? RET_STR : (defaultret)) : (type) << CODE_RET)
1015 #define retcodeint(type) retcode(type, RET_INT)
1016 #define retcodefloat(type) retcode(type, RET_FLOAT)
1017 #define retcodeany(type) retcode(type, 0)
1018 #define retcodestr(type) ((type) >= VAL_ANY ? RET_STR : (type) << CODE_RET)
1019 
compilestr(vector<uint> & code,const char * word,int len,bool macro=false)1020 static inline void compilestr(vector<uint> &code, const char *word, int len, bool macro = false)
1021 {
1022     if(len <= 3 && !macro)
1023     {
1024         uint op = CODE_VALI|RET_STR;
1025         loopi(len) op |= uint(uchar(word[i]))<<((i+1)*8);
1026         code.add(op);
1027         return;
1028     }
1029     code.add((macro ? CODE_MACRO : CODE_VAL|RET_STR)|(len<<8));
1030     code.put((const uint *)word, len/sizeof(uint));
1031     size_t endlen = len%sizeof(uint);
1032     union
1033     {
1034         char c[sizeof(uint)];
1035         uint u;
1036     } end;
1037     end.u = 0;
1038     memcpy(end.c, word + len - endlen, endlen);
1039     code.add(end.u);
1040 }
1041 
compilestr(vector<uint> & code)1042 static inline void compilestr(vector<uint> &code) { code.add(CODE_VALI|RET_STR); }
compilestr(vector<uint> & code,const stringslice & word,bool macro=false)1043 static inline void compilestr(vector<uint> &code, const stringslice &word, bool macro = false) { compilestr(code, word.str, word.len, macro); }
compilestr(vector<uint> & code,const char * word,bool macro=false)1044 static inline void compilestr(vector<uint> &code, const char *word, bool macro = false) { compilestr(code, word, int(strlen(word)), macro); }
1045 
compileunescapestring(vector<uint> & code,const char * & p,bool macro=false)1046 static inline void compileunescapestring(vector<uint> &code, const char *&p, bool macro = false)
1047 {
1048     p++;
1049     const char *end = parsestring(p);
1050     code.add(macro ? CODE_MACRO : CODE_VAL|RET_STR);
1051     char *buf = (char *)code.reserve(int(end-p)/sizeof(uint) + 1).buf;
1052     int len = unescapestring(buf, p, end);
1053     memset(&buf[len], 0, sizeof(uint) - len%sizeof(uint));
1054     code.last() |= len<<8;
1055     code.advance(len/sizeof(uint) + 1);
1056     p = end;
1057     if(*p == '\"') p++;
1058 }
1059 
compileint(vector<uint> & code,int i=0)1060 static inline void compileint(vector<uint> &code, int i = 0)
1061 {
1062     if(i >= -0x800000 && i <= 0x7FFFFF)
1063         code.add(CODE_VALI|RET_INT|(i<<8));
1064     else
1065     {
1066         code.add(CODE_VAL|RET_INT);
1067         code.add(i);
1068     }
1069 }
1070 
compilenull(vector<uint> & code)1071 static inline void compilenull(vector<uint> &code)
1072 {
1073     code.add(CODE_VALI|RET_NULL);
1074 }
1075 
1076 static uint emptyblock[VAL_ANY][2] =
1077 {
1078     { CODE_START + 0x100, CODE_EXIT|RET_NULL },
1079     { CODE_START + 0x100, CODE_EXIT|RET_INT },
1080     { CODE_START + 0x100, CODE_EXIT|RET_FLOAT },
1081     { CODE_START + 0x100, CODE_EXIT|RET_STR }
1082 };
1083 
compileblock(vector<uint> & code)1084 static inline void compileblock(vector<uint> &code)
1085 {
1086     code.add(CODE_EMPTY);
1087 }
1088 
1089 static void compilestatements(vector<uint> &code, const char *&p, int rettype, int brak = '\0', int prevargs = 0);
1090 
compileblock(vector<uint> & code,const char * p,int rettype=RET_NULL,int brak='\\0')1091 static inline const char *compileblock(vector<uint> &code, const char *p, int rettype = RET_NULL, int brak = '\0')
1092 {
1093     int start = code.length();
1094     code.add(CODE_BLOCK);
1095     code.add(CODE_OFFSET|((start+2)<<8));
1096     if(p) compilestatements(code, p, VAL_ANY, brak);
1097     if(code.length() > start + 2)
1098     {
1099         code.add(CODE_EXIT|rettype);
1100         code[start] |= uint(code.length() - (start + 1))<<8;
1101     }
1102     else
1103     {
1104         code.setsize(start);
1105         code.add(CODE_EMPTY|rettype);
1106     }
1107     return p;
1108 }
1109 
compileident(vector<uint> & code,ident * id=dummyident)1110 static inline void compileident(vector<uint> &code, ident *id = dummyident)
1111 {
1112     code.add((id->index < MAXARGS ? CODE_IDENTARG : CODE_IDENT)|(id->index<<8));
1113 }
1114 
compileident(vector<uint> & code,const stringslice & word)1115 static inline void compileident(vector<uint> &code, const stringslice &word)
1116 {
1117     compileident(code, newident(word, IDF_UNKNOWN));
1118 }
1119 
compileint(vector<uint> & code,const stringslice & word)1120 static inline void compileint(vector<uint> &code, const stringslice &word)
1121 {
1122     compileint(code, word.len ? parseint(word.str) : 0);
1123 }
1124 
compilefloat(vector<uint> & code,float f=0.0f)1125 static inline void compilefloat(vector<uint> &code, float f = 0.0f)
1126 {
1127     if(int(f) == f && f >= -0x800000 && f <= 0x7FFFFF)
1128         code.add(CODE_VALI|RET_FLOAT|(int(f)<<8));
1129     else
1130     {
1131         union { float f; uint u; } conv;
1132         conv.f = f;
1133         code.add(CODE_VAL|RET_FLOAT);
1134         code.add(conv.u);
1135     }
1136 }
1137 
compilefloat(vector<uint> & code,const stringslice & word)1138 static inline void compilefloat(vector<uint> &code, const stringslice &word)
1139 {
1140     compilefloat(code, word.len ? parsefloat(word.str) : 0.0f);
1141 }
1142 
getbool(const char * s)1143 static inline bool getbool(const char *s)
1144 {
1145     switch(s[0])
1146     {
1147         case '+': case '-':
1148             switch(s[1])
1149             {
1150                 case '0': break;
1151                 case '.': return !isdigit(s[2]) || parsefloat(s) != 0;
1152                 default: return true;
1153             }
1154             // fall-through
1155         case '0':
1156         {
1157             char *end;
1158             int val = int(strtoul((char *)s, &end, 0));
1159             if(val) return true;
1160             switch(*end)
1161             {
1162                 case 'e': case '.': return parsefloat(s) != 0;
1163                 default: return false;
1164             }
1165         }
1166         case '.': return !isdigit(s[1]) || parsefloat(s) != 0;
1167         case '\0': return false;
1168         default: return true;
1169     }
1170 }
1171 
getbool(const tagval & v)1172 static inline bool getbool(const tagval &v)
1173 {
1174     switch(v.type)
1175     {
1176         case VAL_FLOAT: return v.f!=0;
1177         case VAL_INT: return v.i!=0;
1178         case VAL_STR: case VAL_MACRO: case VAL_CSTR: return getbool(v.s);
1179         default: return false;
1180     }
1181 }
1182 
compileval(vector<uint> & code,int wordtype,const stringslice & word=stringslice (NULL,0))1183 static inline void compileval(vector<uint> &code, int wordtype, const stringslice &word = stringslice(NULL, 0))
1184 {
1185     switch(wordtype)
1186     {
1187         case VAL_CANY: if(word.len) compilestr(code, word, true); else compilenull(code); break;
1188         case VAL_CSTR: compilestr(code, word, true); break;
1189         case VAL_ANY: if(word.len) compilestr(code, word); else compilenull(code); break;
1190         case VAL_STR: compilestr(code, word); break;
1191         case VAL_FLOAT: compilefloat(code, word); break;
1192         case VAL_INT: compileint(code, word); break;
1193         case VAL_COND: if(word.len) compileblock(code, word.str); else compilenull(code); break;
1194         case VAL_CODE: compileblock(code, word.str); break;
1195         case VAL_IDENT: compileident(code, word); break;
1196         default: break;
1197     }
1198 }
1199 
1200 static stringslice unusedword(NULL, 0);
1201 static bool compilearg(vector<uint> &code, const char *&p, int wordtype, int prevargs = MAXRESULTS, stringslice &word = unusedword);
1202 
compilelookup(vector<uint> & code,const char * & p,int ltype,int prevargs=MAXRESULTS)1203 static void compilelookup(vector<uint> &code, const char *&p, int ltype, int prevargs = MAXRESULTS)
1204 {
1205     stringslice lookup;
1206     switch(*++p)
1207     {
1208         case '(':
1209         case '[':
1210             if(!compilearg(code, p, VAL_CSTR, prevargs)) goto invalid;
1211             break;
1212         case '$':
1213             compilelookup(code, p, VAL_CSTR, prevargs);
1214             break;
1215         case '\"':
1216             cutstring(p, lookup);
1217             goto lookupid;
1218         default:
1219         {
1220             cutword(p, lookup);
1221             if(!lookup.len) goto invalid;
1222         lookupid:
1223             ident *id = newident(lookup, IDF_UNKNOWN);
1224             if(id) switch(id->type)
1225             {
1226                 case ID_VAR:
1227                     code.add(CODE_IVAR|retcodeint(ltype)|(id->index<<8));
1228                     switch(ltype)
1229                     {
1230                         case VAL_POP: code.pop(); break;
1231                         case VAL_CODE: code.add(CODE_COMPILE); break;
1232                         case VAL_IDENT: code.add(CODE_IDENTU); break;
1233                     }
1234                     return;
1235                 case ID_FVAR:
1236                     code.add(CODE_FVAR|retcodefloat(ltype)|(id->index<<8));
1237                     switch(ltype)
1238                     {
1239                         case VAL_POP: code.pop(); break;
1240                         case VAL_CODE: code.add(CODE_COMPILE); break;
1241                         case VAL_IDENT: code.add(CODE_IDENTU); break;
1242                     }
1243                     return;
1244                 case ID_SVAR:
1245                     switch(ltype)
1246                     {
1247                         case VAL_POP: return;
1248                         case VAL_CANY: case VAL_CSTR: case VAL_CODE: case VAL_IDENT: case VAL_COND:
1249                             code.add(CODE_SVARM|(id->index<<8));
1250                             break;
1251                         default:
1252                             code.add(CODE_SVAR|retcodestr(ltype)|(id->index<<8));
1253                             break;
1254                     }
1255                     goto done;
1256                 case ID_ALIAS:
1257                     switch(ltype)
1258                     {
1259                         case VAL_POP: return;
1260                         case VAL_CANY: case VAL_COND:
1261                             code.add((id->index < MAXARGS ? CODE_LOOKUPMARG : CODE_LOOKUPM)|(id->index<<8));
1262                             break;
1263                         case VAL_CSTR: case VAL_CODE: case VAL_IDENT:
1264                             code.add((id->index < MAXARGS ? CODE_LOOKUPMARG : CODE_LOOKUPM)|RET_STR|(id->index<<8));
1265                             break;
1266                         default:
1267                             code.add((id->index < MAXARGS ? CODE_LOOKUPARG : CODE_LOOKUP)|retcodestr(ltype)|(id->index<<8));
1268                             break;
1269                     }
1270                     goto done;
1271                 case ID_COMMAND:
1272                 {
1273                     int comtype = CODE_COM, numargs = 0;
1274                     if(prevargs >= MAXRESULTS) code.add(CODE_ENTER);
1275                     for(const char *fmt = id->args; *fmt; fmt++) switch(*fmt)
1276                     {
1277                         case 'S': compilestr(code); numargs++; break;
1278                         case 's': compilestr(code, NULL, 0, true); numargs++; break;
1279                         case 'i': compileint(code); numargs++; break;
1280                         case 'b': compileint(code, INT_MIN); numargs++; break;
1281                         case 'f': compilefloat(code); numargs++; break;
1282                         case 'F': code.add(CODE_DUP|RET_FLOAT); numargs++; break;
1283                         case 'E':
1284                         case 'T':
1285                         case 't': compilenull(code); numargs++; break;
1286                         case 'e': compileblock(code); numargs++; break;
1287                         case 'r': compileident(code); numargs++; break;
1288                         case '$': compileident(code, id); numargs++; break;
1289                         case 'N': compileint(code, -1); numargs++; break;
1290 #ifndef STANDALONE
1291                         case 'D': comtype = CODE_COMD; numargs++; break;
1292 #endif
1293                         case 'C': comtype = CODE_COMC; goto compilecomv;
1294                         case 'V': comtype = CODE_COMV; goto compilecomv;
1295                         case '1': case '2': case '3': case '4': break;
1296                     }
1297                     code.add(comtype|retcodeany(ltype)|(id->index<<8));
1298                     code.add((prevargs >= MAXRESULTS ? CODE_EXIT : CODE_RESULT_ARG) | retcodeany(ltype));
1299                     goto done;
1300                 compilecomv:
1301                     code.add(comtype|retcodeany(ltype)|(numargs<<8)|(id->index<<13));
1302                     code.add((prevargs >= MAXRESULTS ? CODE_EXIT : CODE_RESULT_ARG) | retcodeany(ltype));
1303                     goto done;
1304                 }
1305                 default: goto invalid;
1306             }
1307             compilestr(code, lookup, true);
1308             break;
1309         }
1310     }
1311     switch(ltype)
1312     {
1313     case VAL_CANY: case VAL_COND:
1314         code.add(CODE_LOOKUPMU);
1315         break;
1316     case VAL_CSTR: case VAL_CODE: case VAL_IDENT:
1317         code.add(CODE_LOOKUPMU|RET_STR);
1318         break;
1319     default:
1320         code.add(CODE_LOOKUPU|retcodeany(ltype));
1321         break;
1322     }
1323 done:
1324     switch(ltype)
1325     {
1326         case VAL_POP: code.add(CODE_POP); break;
1327         case VAL_CODE: code.add(CODE_COMPILE); break;
1328         case VAL_COND: code.add(CODE_COND); break;
1329         case VAL_IDENT: code.add(CODE_IDENTU); break;
1330     }
1331     return;
1332 invalid:
1333     switch(ltype)
1334     {
1335         case VAL_POP: break;
1336         case VAL_NULL: case VAL_ANY: case VAL_CANY: case VAL_WORD: case VAL_COND: compilenull(code); break;
1337         default: compileval(code, ltype); break;
1338     }
1339 }
1340 
compileblockstr(vector<uint> & code,const char * str,const char * end,bool macro)1341 static bool compileblockstr(vector<uint> &code, const char *str, const char *end, bool macro)
1342 {
1343     int start = code.length();
1344     code.add(macro ? CODE_MACRO : CODE_VAL|RET_STR);
1345     char *buf = (char *)code.reserve((end-str)/sizeof(uint)+1).buf;
1346     int len = 0;
1347     while(str < end)
1348     {
1349         int n = strcspn(str, "\r/\"@]\0");
1350         memcpy(&buf[len], str, n);
1351         len += n;
1352         str += n;
1353         switch(*str)
1354         {
1355             case '\r': str++; break;
1356             case '\"':
1357             {
1358                 const char *start = str;
1359                 str = parsestring(str+1);
1360                 if(*str=='\"') str++;
1361                 memcpy(&buf[len], start, str-start);
1362                 len += str-start;
1363                 break;
1364             }
1365             case '/':
1366                 if(str[1] == '/') str += strcspn(str, "\n\0");
1367                 else buf[len++] = *str++;
1368                 break;
1369             case '@':
1370             case ']':
1371                 if(str < end) { buf[len++] = *str++; break; }
1372             case '\0': goto done;
1373         }
1374     }
1375 done:
1376     memset(&buf[len], '\0', sizeof(uint)-len%sizeof(uint));
1377     code.advance(len/sizeof(uint)+1);
1378     code[start] |= len<<8;
1379     return true;
1380 }
1381 
compileblocksub(vector<uint> & code,const char * & p,int prevargs)1382 static bool compileblocksub(vector<uint> &code, const char *&p, int prevargs)
1383 {
1384     stringslice lookup;
1385     switch(*p)
1386     {
1387         case '(':
1388             if(!compilearg(code, p, VAL_CANY, prevargs)) return false;
1389             break;
1390         case '[':
1391             if(!compilearg(code, p, VAL_CSTR, prevargs)) return false;
1392             code.add(CODE_LOOKUPMU);
1393             break;
1394         case '\"':
1395             cutstring(p, lookup);
1396             goto lookupid;
1397         default:
1398         {
1399 
1400             lookup.str = p;
1401             while(iscubealnum(*p) || *p=='_') p++;
1402             lookup.len = int(p-lookup.str);
1403             if(!lookup.len) return false;
1404         lookupid:
1405             ident *id = newident(lookup, IDF_UNKNOWN);
1406             if(id) switch(id->type)
1407             {
1408             case ID_VAR: code.add(CODE_IVAR|(id->index<<8)); goto done;
1409             case ID_FVAR: code.add(CODE_FVAR|(id->index<<8)); goto done;
1410             case ID_SVAR: code.add(CODE_SVARM|(id->index<<8)); goto done;
1411             case ID_ALIAS: code.add((id->index < MAXARGS ? CODE_LOOKUPMARG : CODE_LOOKUPM)|(id->index<<8)); goto done;
1412             }
1413             compilestr(code, lookup, true);
1414             code.add(CODE_LOOKUPMU);
1415         done:
1416             break;
1417         }
1418     }
1419     return true;
1420 }
1421 
compileblockmain(vector<uint> & code,const char * & p,int wordtype,int prevargs)1422 static void compileblockmain(vector<uint> &code, const char *&p, int wordtype, int prevargs)
1423 {
1424     const char *line = p, *start = p;
1425     int concs = 0;
1426     for(int brak = 1; brak;)
1427     {
1428         p += strcspn(p, "@\"/[]\0");
1429         int c = *p++;
1430         switch(c)
1431         {
1432             case '\0':
1433                 debugcodeline(line, "missing \"]\"");
1434                 p--;
1435                 goto done;
1436             case '\"':
1437                 p = parsestring(p);
1438                 if(*p=='\"') p++;
1439                 break;
1440             case '/':
1441                 if(*p=='/') p += strcspn(p, "\n\0");
1442                 break;
1443             case '[': brak++; break;
1444             case ']': brak--; break;
1445             case '@':
1446             {
1447                 const char *esc = p;
1448                 while(*p == '@') p++;
1449                 int level = p - (esc - 1);
1450                 if(brak > level) continue;
1451                 else if(brak < level) debugcodeline(line, "too many @s");
1452                 if(!concs && prevargs >= MAXRESULTS) code.add(CODE_ENTER);
1453                 if(concs + 2 > MAXARGS)
1454                 {
1455                     code.add(CODE_CONCW|RET_STR|(concs<<8));
1456                     concs = 1;
1457                 }
1458                 if(compileblockstr(code, start, esc-1, true)) concs++;
1459                 if(compileblocksub(code, p, prevargs + concs)) concs++;
1460                 if(concs) start = p;
1461                 else if(prevargs >= MAXRESULTS) code.pop();
1462                 break;
1463             }
1464         }
1465     }
1466 done:
1467     if(p-1 > start)
1468     {
1469         if(!concs) switch(wordtype)
1470         {
1471             case VAL_POP:
1472                 return;
1473             case VAL_CODE: case VAL_COND:
1474                 p = compileblock(code, start, RET_NULL, ']');
1475                 return;
1476             case VAL_IDENT:
1477                 compileident(code, stringslice(start, p-1));
1478                 return;
1479         }
1480         switch(wordtype)
1481         {
1482             case VAL_CSTR: case VAL_CODE: case VAL_IDENT: case VAL_CANY: case VAL_COND:
1483                 compileblockstr(code, start, p-1, true);
1484                 break;
1485             default:
1486                 compileblockstr(code, start, p-1, concs > 0);
1487                 break;
1488         }
1489         if(concs > 1) concs++;
1490     }
1491     if(concs)
1492     {
1493         if(prevargs >= MAXRESULTS)
1494         {
1495             code.add(CODE_CONCM|retcodeany(wordtype)|(concs<<8));
1496             code.add(CODE_EXIT|retcodeany(wordtype));
1497         }
1498         else code.add(CODE_CONCW|retcodeany(wordtype)|(concs<<8));
1499     }
1500     switch(wordtype)
1501     {
1502         case VAL_POP: if(concs || p-1 > start) code.add(CODE_POP); break;
1503         case VAL_COND: if(!concs && p-1 <= start) compilenull(code); else code.add(CODE_COND); break;
1504         case VAL_CODE: if(!concs && p-1 <= start) compileblock(code); else code.add(CODE_COMPILE); break;
1505         case VAL_IDENT: if(!concs && p-1 <= start) compileident(code); else code.add(CODE_IDENTU); break;
1506         case VAL_CSTR: case VAL_CANY:
1507             if(!concs && p-1 <= start) compilestr(code, NULL, 0, true);
1508             break;
1509         case VAL_STR: case VAL_NULL: case VAL_ANY: case VAL_WORD:
1510             if(!concs && p-1 <= start) compilestr(code);
1511             break;
1512         default:
1513             if(!concs)
1514             {
1515                 if(p-1 <= start) compileval(code, wordtype);
1516                 else code.add(CODE_FORCE|(wordtype<<CODE_RET));
1517             }
1518             break;
1519     }
1520 }
1521 
compilearg(vector<uint> & code,const char * & p,int wordtype,int prevargs,stringslice & word)1522 static bool compilearg(vector<uint> &code, const char *&p, int wordtype, int prevargs, stringslice &word)
1523 {
1524     skipcomments(p);
1525     switch(*p)
1526     {
1527         case '\"':
1528             switch(wordtype)
1529             {
1530                 case VAL_POP:
1531                     p = parsestring(p+1);
1532                     if(*p == '\"') p++;
1533                     break;
1534                 case VAL_COND:
1535                 {
1536                     char *s = cutstring(p);
1537                     if(s[0]) compileblock(code, s);
1538                     else compilenull(code);
1539                     delete[] s;
1540                     break;
1541                 }
1542                 case VAL_CODE:
1543                 {
1544                     char *s = cutstring(p);
1545                     compileblock(code, s);
1546                     delete[] s;
1547                     break;
1548                 }
1549                 case VAL_WORD:
1550                     cutstring(p, word);
1551                     break;
1552                 case VAL_ANY:
1553                 case VAL_STR:
1554                     compileunescapestring(code, p);
1555                     break;
1556                 case VAL_CANY:
1557                 case VAL_CSTR:
1558                     compileunescapestring(code, p, true);
1559                     break;
1560                 default:
1561                 {
1562                     stringslice s;
1563                     cutstring(p, s);
1564                     compileval(code, wordtype, s);
1565                     break;
1566                 }
1567             }
1568             return true;
1569         case '$': compilelookup(code, p, wordtype, prevargs); return true;
1570         case '(':
1571             p++;
1572             if(prevargs >= MAXRESULTS)
1573             {
1574                 code.add(CODE_ENTER);
1575                 compilestatements(code, p, wordtype > VAL_ANY ? VAL_CANY : VAL_ANY, ')');
1576                 code.add(CODE_EXIT|retcodeany(wordtype));
1577             }
1578             else
1579             {
1580                 int start = code.length();
1581                 compilestatements(code, p, wordtype > VAL_ANY ? VAL_CANY : VAL_ANY, ')', prevargs);
1582                 if(code.length() > start) code.add(CODE_RESULT_ARG|retcodeany(wordtype));
1583                 else { compileval(code, wordtype); return true; }
1584             }
1585             switch(wordtype)
1586             {
1587                 case VAL_POP: code.add(CODE_POP); break;
1588                 case VAL_COND: code.add(CODE_COND); break;
1589                 case VAL_CODE: code.add(CODE_COMPILE); break;
1590                 case VAL_IDENT: code.add(CODE_IDENTU); break;
1591             }
1592             return true;
1593         case '[':
1594             p++;
1595             compileblockmain(code, p, wordtype, prevargs);
1596             return true;
1597         default:
1598             switch(wordtype)
1599             {
1600                 case VAL_POP:
1601                 {
1602                     const char *s = p;
1603                     p = parseword(p);
1604                     return p != s;
1605                 }
1606                 case VAL_COND:
1607                 {
1608                     char *s = cutword(p);
1609                     if(!s) return false;
1610                     compileblock(code, s);
1611                     delete[] s;
1612                     return true;
1613                 }
1614                 case VAL_CODE:
1615                 {
1616                     char *s = cutword(p);
1617                     if(!s) return false;
1618                     compileblock(code, s);
1619                     delete[] s;
1620                     return true;
1621                 }
1622                 case VAL_WORD:
1623                     cutword(p, word);
1624                     return word.len!=0;
1625                 default:
1626                 {
1627                     stringslice s;
1628                     cutword(p, s);
1629                     if(!s.len) return false;
1630                     compileval(code, wordtype, s);
1631                     return true;
1632                 }
1633             }
1634     }
1635 }
1636 
compilestatements(vector<uint> & code,const char * & p,int rettype,int brak,int prevargs)1637 static void compilestatements(vector<uint> &code, const char *&p, int rettype, int brak, int prevargs)
1638 {
1639     const char *line = p;
1640     stringslice idname;
1641     int numargs;
1642     for(;;)
1643     {
1644         skipcomments(p);
1645         idname.str = NULL;
1646         bool more = compilearg(code, p, VAL_WORD, prevargs, idname);
1647         if(!more) goto endstatement;
1648         skipcomments(p);
1649         if(p[0] == '=') switch(p[1])
1650         {
1651             case '/':
1652                 if(p[2] != '/') break;
1653             case ';': case ' ': case '\t': case '\r': case '\n': case '\0':
1654                 p++;
1655                 if(idname.str)
1656                 {
1657                     ident *id = newident(idname, IDF_UNKNOWN);
1658                     if(id) switch(id->type)
1659                     {
1660                         case ID_ALIAS:
1661                             if(!(more = compilearg(code, p, VAL_ANY, prevargs))) compilestr(code);
1662                             code.add((id->index < MAXARGS ? CODE_ALIASARG : CODE_ALIAS)|(id->index<<8));
1663                             goto endstatement;
1664                         case ID_VAR:
1665                             if(!(more = compilearg(code, p, VAL_INT, prevargs))) compileint(code);
1666                             code.add(CODE_IVAR1|(id->index<<8));
1667                             goto endstatement;
1668                         case ID_FVAR:
1669                             if(!(more = compilearg(code, p, VAL_FLOAT, prevargs))) compilefloat(code);
1670                             code.add(CODE_FVAR1|(id->index<<8));
1671                             goto endstatement;
1672                         case ID_SVAR:
1673                             if(!(more = compilearg(code, p, VAL_CSTR, prevargs))) compilestr(code);
1674                             code.add(CODE_SVAR1|(id->index<<8));
1675                             goto endstatement;
1676                     }
1677                     compilestr(code, idname, true);
1678                 }
1679                 if(!(more = compilearg(code, p, VAL_ANY))) compilestr(code);
1680                 code.add(CODE_ALIASU);
1681                 goto endstatement;
1682         }
1683         numargs = 0;
1684         if(!idname.str)
1685         {
1686         noid:
1687             while(numargs < MAXARGS && (more = compilearg(code, p, VAL_CANY, prevargs+numargs))) numargs++;
1688             code.add(CODE_CALLU|(numargs<<8));
1689         }
1690         else
1691         {
1692             ident *id = idents.access(idname);
1693             if(!id)
1694             {
1695                 if(!checknumber(idname)) { compilestr(code, idname, true); goto noid; }
1696                 switch(rettype)
1697                 {
1698                 case VAL_ANY:
1699                 case VAL_CANY:
1700                 {
1701                     char *end = (char *)idname.str;
1702                     int val = int(strtoul(idname.str, &end, 0));
1703                     if(end < idname.end()) compilestr(code, idname, rettype==VAL_CANY);
1704                     else compileint(code, val);
1705                     break;
1706                 }
1707                 default:
1708                     compileval(code, rettype, idname);
1709                     break;
1710                 }
1711                 code.add(CODE_RESULT);
1712             }
1713             else switch(id->type)
1714             {
1715                 case ID_ALIAS:
1716                     while(numargs < MAXARGS && (more = compilearg(code, p, VAL_ANY, prevargs+numargs))) numargs++;
1717                     code.add((id->index < MAXARGS ? CODE_CALLARG : CODE_CALL)|(numargs<<8)|(id->index<<13));
1718                     break;
1719                 case ID_COMMAND:
1720                 {
1721                     int comtype = CODE_COM, fakeargs = 0;
1722                     bool rep = false;
1723                     for(const char *fmt = id->args; *fmt; fmt++) switch(*fmt)
1724                     {
1725                     case 'S':
1726                     case 's':
1727                         if(more) more = compilearg(code, p, *fmt == 's' ? VAL_CSTR : VAL_STR, prevargs+numargs);
1728                         if(!more)
1729                         {
1730                             if(rep) break;
1731                             compilestr(code, NULL, 0, *fmt=='s');
1732                             fakeargs++;
1733                         }
1734                         else if(!fmt[1])
1735                         {
1736                             int numconc = 1;
1737                             while(numargs + numconc < MAXARGS && (more = compilearg(code, p, VAL_CSTR, prevargs+numargs+numconc))) numconc++;
1738                             if(numconc > 1) code.add(CODE_CONC|RET_STR|(numconc<<8));
1739                         }
1740                         numargs++;
1741                         break;
1742                     case 'i': if(more) more = compilearg(code, p, VAL_INT, prevargs+numargs); if(!more) { if(rep) break; compileint(code); fakeargs++; } numargs++; break;
1743                     case 'b': if(more) more = compilearg(code, p, VAL_INT, prevargs+numargs); if(!more) { if(rep) break; compileint(code, INT_MIN); fakeargs++; } numargs++; break;
1744                     case 'f': if(more) more = compilearg(code, p, VAL_FLOAT, prevargs+numargs); if(!more) { if(rep) break; compilefloat(code); fakeargs++; } numargs++; break;
1745                     case 'F': if(more) more = compilearg(code, p, VAL_FLOAT, prevargs+numargs); if(!more) { if(rep) break; code.add(CODE_DUP|RET_FLOAT); fakeargs++; } numargs++; break;
1746                     case 'T':
1747                     case 't': if(more) more = compilearg(code, p, *fmt == 't' ? VAL_CANY : VAL_ANY, prevargs+numargs); if(!more) { if(rep) break; compilenull(code); fakeargs++; } numargs++; break;
1748                     case 'E': if(more) more = compilearg(code, p, VAL_COND, prevargs+numargs); if(!more) { if(rep) break; compilenull(code); fakeargs++; } numargs++; break;
1749                     case 'e': if(more) more = compilearg(code, p, VAL_CODE, prevargs+numargs); if(!more) { if(rep) break; compileblock(code); fakeargs++; } numargs++; break;
1750                     case 'r': if(more) more = compilearg(code, p, VAL_IDENT, prevargs+numargs); if(!more) { if(rep) break; compileident(code); fakeargs++; } numargs++; break;
1751                     case '$': compileident(code, id); numargs++; break;
1752                     case 'N': compileint(code, numargs-fakeargs); numargs++; break;
1753 #ifndef STANDALONE
1754                     case 'D': comtype = CODE_COMD; numargs++; break;
1755 #endif
1756                     case 'C': comtype = CODE_COMC; if(more) while(numargs < MAXARGS && (more = compilearg(code, p, VAL_CANY, prevargs+numargs))) numargs++; goto compilecomv;
1757                     case 'V': comtype = CODE_COMV; if(more) while(numargs < MAXARGS && (more = compilearg(code, p, VAL_CANY, prevargs+numargs))) numargs++; goto compilecomv;
1758                     case '1': case '2': case '3': case '4': if(more) { fmt -= *fmt-'0'+1; rep = true; } break;
1759                     }
1760                     code.add(comtype|retcodeany(rettype)|(id->index<<8));
1761                     break;
1762                 compilecomv:
1763                     code.add(comtype|retcodeany(rettype)|(numargs<<8)|(id->index<<13));
1764                     break;
1765                 }
1766                 case ID_LOCAL:
1767                     if(more) while(numargs < MAXARGS && (more = compilearg(code, p, VAL_IDENT, prevargs+numargs))) numargs++;
1768                     if(more) while((more = compilearg(code, p, VAL_POP)));
1769                     code.add(CODE_LOCAL|(numargs<<8));
1770                     break;
1771                 case ID_DO:
1772                     if(more) more = compilearg(code, p, VAL_CODE, prevargs);
1773                     code.add((more ? CODE_DO : CODE_NULL) | retcodeany(rettype));
1774                     break;
1775                 case ID_DOARGS:
1776                     if(more) more = compilearg(code, p, VAL_CODE, prevargs);
1777                     code.add((more ? CODE_DOARGS : CODE_NULL) | retcodeany(rettype));
1778                     break;
1779                 case ID_IF:
1780                     if(more) more = compilearg(code, p, VAL_CANY, prevargs);
1781                     if(!more) code.add(CODE_NULL | retcodeany(rettype));
1782                     else
1783                     {
1784                         int start1 = code.length();
1785                         more = compilearg(code, p, VAL_CODE, prevargs+1);
1786                         if(!more) { code.add(CODE_POP); code.add(CODE_NULL | retcodeany(rettype)); }
1787                         else
1788                         {
1789                             int start2 = code.length();
1790                             more = compilearg(code, p, VAL_CODE, prevargs+2);
1791                             uint inst1 = code[start1], op1 = inst1&~CODE_RET_MASK, len1 = start2 - (start1+1);
1792                             if(!more)
1793                             {
1794                                 if(op1 == (CODE_BLOCK|(len1<<8)))
1795                                 {
1796                                     code[start1] = (len1<<8) | CODE_JUMP_FALSE;
1797                                     code[start1+1] = CODE_ENTER_RESULT;
1798                                     code[start1+len1] = (code[start1+len1]&~CODE_RET_MASK) | retcodeany(rettype);
1799                                     break;
1800                                 }
1801                                 compileblock(code);
1802                             }
1803                             else
1804                             {
1805                                 uint inst2 = code[start2], op2 = inst2&~CODE_RET_MASK, len2 = code.length() - (start2+1);
1806                                 if(op2 == (CODE_BLOCK|(len2<<8)))
1807                                 {
1808                                     if(op1 == (CODE_BLOCK|(len1<<8)))
1809                                     {
1810                                         code[start1] = ((start2-start1)<<8) | CODE_JUMP_FALSE;
1811                                         code[start1+1] = CODE_ENTER_RESULT;
1812                                         code[start1+len1] = (code[start1+len1]&~CODE_RET_MASK) | retcodeany(rettype);
1813                                         code[start2] = (len2<<8) | CODE_JUMP;
1814                                         code[start2+1] = CODE_ENTER_RESULT;
1815                                         code[start2+len2] = (code[start2+len2]&~CODE_RET_MASK) | retcodeany(rettype);
1816                                         break;
1817                                     }
1818                                     else if(op1 == (CODE_EMPTY|(len1<<8)))
1819                                     {
1820                                         code[start1] = CODE_NULL | (inst2&CODE_RET_MASK);
1821                                         code[start2] = (len2<<8) | CODE_JUMP_TRUE;
1822                                         code[start2+1] = CODE_ENTER_RESULT;
1823                                         code[start2+len2] = (code[start2+len2]&~CODE_RET_MASK) | retcodeany(rettype);
1824                                         break;
1825                                     }
1826                                 }
1827                             }
1828                             code.add(CODE_COM|retcodeany(rettype)|(id->index<<8));
1829                         }
1830                     }
1831                     break;
1832                 case ID_RESULT:
1833                     if(more) more = compilearg(code, p, VAL_ANY, prevargs);
1834                     code.add((more ? CODE_RESULT : CODE_NULL) | retcodeany(rettype));
1835                     break;
1836                 case ID_NOT:
1837                     if(more) more = compilearg(code, p, VAL_CANY, prevargs);
1838                     code.add((more ? CODE_NOT : CODE_TRUE) | retcodeany(rettype));
1839                     break;
1840                 case ID_AND:
1841                 case ID_OR:
1842                     if(more) more = compilearg(code, p, VAL_COND, prevargs);
1843                     if(!more) { code.add((id->type == ID_AND ? CODE_TRUE : CODE_FALSE) | retcodeany(rettype)); }
1844                     else
1845                     {
1846                         numargs++;
1847                         int start = code.length(), end = start;
1848                         while(numargs < MAXARGS)
1849                         {
1850                             more = compilearg(code, p, VAL_COND, prevargs+numargs);
1851                             if(!more) break;
1852                             numargs++;
1853                             if((code[end]&~CODE_RET_MASK) != (CODE_BLOCK|(uint(code.length()-(end+1))<<8))) break;
1854                             end = code.length();
1855                         }
1856                         if(more)
1857                         {
1858                             while(numargs < MAXARGS && (more = compilearg(code, p, VAL_COND, prevargs+numargs))) numargs++;
1859                             code.add(CODE_COMV|retcodeany(rettype)|(numargs<<8)|(id->index<<13));
1860                         }
1861                         else
1862                         {
1863                             uint op = id->type == ID_AND ? CODE_JUMP_RESULT_FALSE : CODE_JUMP_RESULT_TRUE;
1864                             code.add(op);
1865                             end = code.length();
1866                             while(start+1 < end)
1867                             {
1868                                 uint len = code[start]>>8;
1869                                 code[start] = ((end-(start+1))<<8) | op;
1870                                 code[start+1] = CODE_ENTER;
1871                                 code[start+len] = (code[start+len]&~CODE_RET_MASK) | retcodeany(rettype);
1872                                 start += len+1;
1873                             }
1874                         }
1875                     }
1876                     break;
1877                 case ID_VAR:
1878                     if(!(more = compilearg(code, p, VAL_INT, prevargs))) code.add(CODE_PRINT|(id->index<<8));
1879                     else if(!(id->flags&IDF_HEX) || !(more = compilearg(code, p, VAL_INT, prevargs+1))) code.add(CODE_IVAR1|(id->index<<8));
1880                     else if(!(more = compilearg(code, p, VAL_INT, prevargs+2))) code.add(CODE_IVAR2|(id->index<<8));
1881                     else code.add(CODE_IVAR3|(id->index<<8));
1882                     break;
1883                 case ID_FVAR:
1884                     if(!(more = compilearg(code, p, VAL_FLOAT, prevargs))) code.add(CODE_PRINT|(id->index<<8));
1885                     else code.add(CODE_FVAR1|(id->index<<8));
1886                     break;
1887                 case ID_SVAR:
1888                     if(!(more = compilearg(code, p, VAL_CSTR, prevargs))) code.add(CODE_PRINT|(id->index<<8));
1889                     else
1890                     {
1891                         do ++numargs;
1892                         while(numargs < MAXARGS && (more = compilearg(code, p, VAL_CANY, prevargs+numargs)));
1893                         if(numargs > 1) code.add(CODE_CONC|RET_STR|(numargs<<8));
1894                         code.add(CODE_SVAR1|(id->index<<8));
1895                     }
1896                     break;
1897             }
1898         }
1899     endstatement:
1900         if(more) while(compilearg(code, p, VAL_POP));
1901         p += strcspn(p, ")];/\n\0");
1902         int c = *p++;
1903         switch(c)
1904         {
1905             case '\0':
1906                 if(c != brak) debugcodeline(line, "missing \"%c\"", brak);
1907                 p--;
1908                 return;
1909 
1910             case ')':
1911             case ']':
1912                 if(c == brak) return;
1913                 debugcodeline(line, "unexpected \"%c\"", c);
1914                 break;
1915 
1916             case '/':
1917                 if(*p == '/') p += strcspn(p, "\n\0");
1918                 goto endstatement;
1919         }
1920     }
1921 }
1922 
compilemain(vector<uint> & code,const char * p,int rettype=VAL_ANY)1923 static void compilemain(vector<uint> &code, const char *p, int rettype = VAL_ANY)
1924 {
1925     code.add(CODE_START);
1926     compilestatements(code, p, VAL_ANY);
1927     code.add(CODE_EXIT|(rettype < VAL_ANY ? rettype<<CODE_RET : 0));
1928 }
1929 
compilecode(const char * p)1930 uint *compilecode(const char *p)
1931 {
1932     vector<uint> buf;
1933     buf.reserve(64);
1934     compilemain(buf, p);
1935     uint *code = new uint[buf.length()];
1936     memcpy(code, buf.getbuf(), buf.length()*sizeof(uint));
1937     code[0] += 0x100;
1938     return code;
1939 }
1940 
forcecode(tagval & v)1941 static inline const uint *forcecode(tagval &v)
1942 {
1943     if(v.type != VAL_CODE)
1944     {
1945         vector<uint> buf;
1946         buf.reserve(64);
1947         compilemain(buf, v.getstr());
1948         freearg(v);
1949         v.setcode(buf.disown()+1);
1950     }
1951     return v.code;
1952 }
1953 
forcecond(tagval & v)1954 static inline void forcecond(tagval &v)
1955 {
1956     switch(v.type)
1957     {
1958         case VAL_STR: case VAL_MACRO: case VAL_CSTR:
1959             if(v.s[0]) forcecode(v);
1960             else v.setint(0);
1961             break;
1962     }
1963 }
1964 
keepcode(uint * code)1965 void keepcode(uint *code)
1966 {
1967     if(!code) return;
1968     switch(*code&CODE_OP_MASK)
1969     {
1970         case CODE_START:
1971             *code += 0x100;
1972             return;
1973     }
1974     switch(code[-1]&CODE_OP_MASK)
1975     {
1976         case CODE_START:
1977             code[-1] += 0x100;
1978             break;
1979         case CODE_OFFSET:
1980             code -= int(code[-1]>>8);
1981             *code += 0x100;
1982             break;
1983     }
1984 }
1985 
freecode(uint * code)1986 void freecode(uint *code)
1987 {
1988     if(!code) return;
1989     switch(*code&CODE_OP_MASK)
1990     {
1991         case CODE_START:
1992             *code -= 0x100;
1993             if(int(*code) < 0x100) delete[] code;
1994             return;
1995     }
1996     switch(code[-1]&CODE_OP_MASK)
1997     {
1998         case CODE_START:
1999             code[-1] -= 0x100;
2000             if(int(code[-1]) < 0x100) delete[] &code[-1];
2001             break;
2002         case CODE_OFFSET:
2003             code -= int(code[-1]>>8);
2004             *code -= 0x100;
2005             if(int(*code) < 0x100) delete[] code;
2006             break;
2007     }
2008 }
2009 
printvar(ident * id,int i)2010 void printvar(ident *id, int i)
2011 {
2012     if(i < 0) conoutf("%s = %d", id->name, i);
2013     else if(id->flags&IDF_HEX && id->maxval==0xFFFFFF)
2014         conoutf("%s = 0x%.6X (%d, %d, %d)", id->name, i, (i>>16)&0xFF, (i>>8)&0xFF, i&0xFF);
2015     else
2016         conoutf(id->flags&IDF_HEX ? "%s = 0x%X" : "%s = %d", id->name, i);
2017 }
2018 
printfvar(ident * id,float f)2019 void printfvar(ident *id, float f)
2020 {
2021     conoutf("%s = %s", id->name, floatstr(f));
2022 }
2023 
printsvar(ident * id,const char * s)2024 void printsvar(ident *id, const char *s)
2025 {
2026     conoutf(strchr(s, '"') ? "%s = [%s]" : "%s = \"%s\"", id->name, s);
2027 }
2028 
printvar(ident * id)2029 void printvar(ident *id)
2030 {
2031     switch(id->type)
2032     {
2033         case ID_VAR: printvar(id, *id->storage.i); break;
2034         case ID_FVAR: printfvar(id, *id->storage.f); break;
2035         case ID_SVAR: printsvar(id, *id->storage.s); break;
2036     }
2037 }
2038 
2039 typedef void (__cdecl *comfun)();
2040 typedef void (__cdecl *comfun1)(void *);
2041 typedef void (__cdecl *comfun2)(void *, void *);
2042 typedef void (__cdecl *comfun3)(void *, void *, void *);
2043 typedef void (__cdecl *comfun4)(void *, void *, void *, void *);
2044 typedef void (__cdecl *comfun5)(void *, void *, void *, void *, void *);
2045 typedef void (__cdecl *comfun6)(void *, void *, void *, void *, void *, void *);
2046 typedef void (__cdecl *comfun7)(void *, void *, void *, void *, void *, void *, void *);
2047 typedef void (__cdecl *comfun8)(void *, void *, void *, void *, void *, void *, void *, void *);
2048 typedef void (__cdecl *comfun9)(void *, void *, void *, void *, void *, void *, void *, void *, void *);
2049 typedef void (__cdecl *comfun10)(void *, void *, void *, void *, void *, void *, void *, void *, void *, void *);
2050 typedef void (__cdecl *comfun11)(void *, void *, void *, void *, void *, void *, void *, void *, void *, void *, void *);
2051 typedef void (__cdecl *comfun12)(void *, void *, void *, void *, void *, void *, void *, void *, void *, void *, void *, void *);
2052 typedef void (__cdecl *comfunv)(tagval *, int);
2053 
skipcode(const uint * code,tagval & result=noret)2054 static const uint *skipcode(const uint *code, tagval &result = noret)
2055 {
2056     int depth = 0;
2057     for(;;)
2058     {
2059         uint op = *code++;
2060         switch(op&0xFF)
2061         {
2062             case CODE_MACRO:
2063             case CODE_VAL|RET_STR:
2064             {
2065                 uint len = op>>8;
2066                 code += len/sizeof(uint) + 1;
2067                 continue;
2068             }
2069             case CODE_BLOCK:
2070             case CODE_JUMP:
2071             case CODE_JUMP_TRUE:
2072             case CODE_JUMP_FALSE:
2073             case CODE_JUMP_RESULT_TRUE:
2074             case CODE_JUMP_RESULT_FALSE:
2075             {
2076                 uint len = op>>8;
2077                 code += len;
2078                 continue;
2079             }
2080             case CODE_ENTER:
2081             case CODE_ENTER_RESULT:
2082                 ++depth;
2083                 continue;
2084             case CODE_EXIT|RET_NULL: case CODE_EXIT|RET_STR: case CODE_EXIT|RET_INT: case CODE_EXIT|RET_FLOAT:
2085                 if(depth <= 0)
2086                 {
2087                     if(&result != &noret) forcearg(result, op&CODE_RET_MASK);
2088                     return code;
2089                 }
2090                 --depth;
2091                 continue;
2092         }
2093     }
2094 }
2095 
2096 #ifndef STANDALONE
copycode(const uint * src)2097 static inline uint *copycode(const uint *src)
2098 {
2099     const uint *end = skipcode(src);
2100     size_t len = end - src;
2101     uint *dst = new uint[len + 1];
2102     *dst++ = CODE_START;
2103     memcpy(dst, src, len*sizeof(uint));
2104     return dst;
2105 }
2106 
copyarg(tagval & dst,const tagval & src)2107 static inline void copyarg(tagval &dst, const tagval &src)
2108 {
2109     switch(src.type)
2110     {
2111         case VAL_INT:
2112         case VAL_FLOAT:
2113         case VAL_IDENT:
2114             dst = src;
2115             break;
2116         case VAL_STR:
2117         case VAL_MACRO:
2118         case VAL_CSTR:
2119             dst.setstr(newstring(src.s));
2120             break;
2121         case VAL_CODE:
2122             dst.setcode(copycode(src.code));
2123             break;
2124         default:
2125             dst.setnull();
2126             break;
2127     }
2128 }
2129 
addreleaseaction(ident * id,tagval * args,int numargs)2130 static inline void addreleaseaction(ident *id, tagval *args, int numargs)
2131 {
2132     tagval *dst = addreleaseaction(id, numargs+1);
2133     if(dst) { args[numargs].setint(1); loopi(numargs+1) copyarg(dst[i], args[i]); }
2134     else args[numargs].setint(0);
2135 }
2136 #endif
2137 
callcommand(ident * id,tagval * args,int numargs,bool lookup=false)2138 static inline void callcommand(ident *id, tagval *args, int numargs, bool lookup = false)
2139 {
2140     int i = -1, fakeargs = 0;
2141     bool rep = false;
2142     for(const char *fmt = id->args; *fmt; fmt++) switch(*fmt)
2143     {
2144         case 'i': if(++i >= numargs) { if(rep) break; args[i].setint(0); fakeargs++; } else forceint(args[i]); break;
2145         case 'b': if(++i >= numargs) { if(rep) break; args[i].setint(INT_MIN); fakeargs++; } else forceint(args[i]); break;
2146         case 'f': if(++i >= numargs) { if(rep) break; args[i].setfloat(0.0f); fakeargs++; } else forcefloat(args[i]); break;
2147         case 'F': if(++i >= numargs) { if(rep) break; args[i].setfloat(args[i-1].getfloat()); fakeargs++; } else forcefloat(args[i]); break;
2148         case 'S': if(++i >= numargs) { if(rep) break; args[i].setstr(newstring("")); fakeargs++; } else forcestr(args[i]); break;
2149         case 's': if(++i >= numargs) { if(rep) break; args[i].setcstr(""); fakeargs++; } else forcestr(args[i]); break;
2150         case 'T':
2151         case 't': if(++i >= numargs) { if(rep) break; args[i].setnull(); fakeargs++; } break;
2152         case 'E': if(++i >= numargs) { if(rep) break; args[i].setnull(); fakeargs++; } else forcecond(args[i]); break;
2153         case 'e':
2154             if(++i >= numargs)
2155             {
2156                 if(rep) break;
2157                 args[i].setcode(emptyblock[VAL_NULL]+1);
2158                 fakeargs++;
2159             }
2160             else forcecode(args[i]);
2161             break;
2162         case 'r': if(++i >= numargs) { if(rep) break; args[i].setident(dummyident); fakeargs++; } else forceident(args[i]); break;
2163         case '$': if(++i < numargs) freearg(args[i]); args[i].setident(id); break;
2164         case 'N': if(++i < numargs) freearg(args[i]); args[i].setint(lookup ? -1 : i-fakeargs); break;
2165 #ifndef STANDALONE
2166         case 'D': if(++i < numargs) freearg(args[i]); addreleaseaction(id, args, i); fakeargs++; break;
2167 #endif
2168         case 'C': { i = max(i+1, numargs); vector<char> buf; ((comfun1)id->fun)(conc(buf, args, i, true)); goto cleanup; }
2169         case 'V': i = max(i+1, numargs); ((comfunv)id->fun)(args, i); goto cleanup;
2170         case '1': case '2': case '3': case '4': if(i+1 < numargs) { fmt -= *fmt-'0'+1; rep = true; } break;
2171     }
2172     ++i;
2173     #define OFFSETARG(n) n
2174     #define ARG(n) (id->argmask&(1<<(n)) ? (void *)args[OFFSETARG(n)].s : (void *)&args[OFFSETARG(n)].i)
2175     #define CALLCOM(n) \
2176         switch(n) \
2177         { \
2178             case 0: ((comfun)id->fun)(); break; \
2179             case 1: ((comfun1)id->fun)(ARG(0)); break; \
2180             case 2: ((comfun2)id->fun)(ARG(0), ARG(1)); break; \
2181             case 3: ((comfun3)id->fun)(ARG(0), ARG(1), ARG(2)); break; \
2182             case 4: ((comfun4)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3)); break; \
2183             case 5: ((comfun5)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4)); break; \
2184             case 6: ((comfun6)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5)); break; \
2185             case 7: ((comfun7)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6)); break; \
2186             case 8: ((comfun8)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6), ARG(7)); break; \
2187             case 9: ((comfun9)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6), ARG(7), ARG(8)); break; \
2188             case 10: ((comfun10)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6), ARG(7), ARG(8), ARG(9)); break; \
2189             case 11: ((comfun11)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6), ARG(7), ARG(8), ARG(9), ARG(10)); break; \
2190             case 12: ((comfun12)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6), ARG(7), ARG(8), ARG(9), ARG(10), ARG(11)); break; \
2191         }
2192     CALLCOM(i)
2193     #undef OFFSETARG
2194 cleanup:
2195     loopk(i) freearg(args[k]);
2196     for(; i < numargs; i++) freearg(args[i]);
2197 }
2198 
2199 #define MAXRUNDEPTH 255
2200 static int rundepth = 0;
2201 
runcode(const uint * code,tagval & result)2202 static const uint *runcode(const uint *code, tagval &result)
2203 {
2204     result.setnull();
2205     if(rundepth >= MAXRUNDEPTH)
2206     {
2207         debugcode("exceeded recursion limit");
2208         return skipcode(code, result);
2209     }
2210     ++rundepth;
2211     int numargs = 0;
2212     tagval args[MAXARGS+MAXRESULTS], *prevret = commandret;
2213     commandret = &result;
2214     for(;;)
2215     {
2216         uint op = *code++;
2217         switch(op&0xFF)
2218         {
2219             case CODE_START: case CODE_OFFSET: continue;
2220 
2221             #define RETOP(op, val) \
2222                 case op: \
2223                     freearg(result); \
2224                     val; \
2225                     continue;
2226 
2227             RETOP(CODE_NULL|RET_NULL, result.setnull())
2228             RETOP(CODE_NULL|RET_STR, result.setstr(newstring("")))
2229             RETOP(CODE_NULL|RET_INT, result.setint(0))
2230             RETOP(CODE_NULL|RET_FLOAT, result.setfloat(0.0f))
2231 
2232             RETOP(CODE_FALSE|RET_STR, result.setstr(newstring("0")))
2233             case CODE_FALSE|RET_NULL:
2234             RETOP(CODE_FALSE|RET_INT, result.setint(0))
2235             RETOP(CODE_FALSE|RET_FLOAT, result.setfloat(0.0f))
2236 
2237             RETOP(CODE_TRUE|RET_STR, result.setstr(newstring("1")))
2238             case CODE_TRUE|RET_NULL:
2239             RETOP(CODE_TRUE|RET_INT, result.setint(1))
2240             RETOP(CODE_TRUE|RET_FLOAT, result.setfloat(1.0f))
2241 
2242             #define RETPOP(op, val) \
2243                 RETOP(op, { --numargs; val; freearg(args[numargs]); })
2244 
2245             RETPOP(CODE_NOT|RET_STR, result.setstr(newstring(getbool(args[numargs]) ? "0" : "1")))
2246             case CODE_NOT|RET_NULL:
2247             RETPOP(CODE_NOT|RET_INT, result.setint(getbool(args[numargs]) ? 0 : 1))
2248             RETPOP(CODE_NOT|RET_FLOAT, result.setfloat(getbool(args[numargs]) ? 0.0f : 1.0f))
2249 
2250             case CODE_POP:
2251                 freearg(args[--numargs]);
2252                 continue;
2253             case CODE_ENTER:
2254                 code = runcode(code, args[numargs++]);
2255                 continue;
2256             case CODE_ENTER_RESULT:
2257                 freearg(result);
2258                 code = runcode(code, result);
2259                 continue;
2260             case CODE_EXIT|RET_STR: case CODE_EXIT|RET_INT: case CODE_EXIT|RET_FLOAT:
2261                 forcearg(result, op&CODE_RET_MASK);
2262                 // fall-through
2263             case CODE_EXIT|RET_NULL:
2264                 goto exit;
2265             case CODE_RESULT_ARG|RET_STR: case CODE_RESULT_ARG|RET_INT: case CODE_RESULT_ARG|RET_FLOAT:
2266                 forcearg(result, op&CODE_RET_MASK);
2267                 // fall-through
2268             case CODE_RESULT_ARG|RET_NULL:
2269                 args[numargs++] = result;
2270                 result.setnull();
2271                 continue;
2272             case CODE_PRINT:
2273                 printvar(identmap[op>>8]);
2274                 continue;
2275 
2276             case CODE_LOCAL:
2277             {
2278                 freearg(result);
2279                 int numlocals = op>>8, offset = numargs-numlocals;
2280                 identstack locals[MAXARGS];
2281                 loopi(numlocals) pushalias(*args[offset+i].id, locals[i]);
2282                 code = runcode(code, result);
2283                 for(int i = offset; i < numargs; i++) popalias(*args[i].id);
2284                 goto exit;
2285             }
2286 
2287             case CODE_DOARGS|RET_NULL: case CODE_DOARGS|RET_STR: case CODE_DOARGS|RET_INT: case CODE_DOARGS|RET_FLOAT:
2288                 if(aliasstack != &noalias)
2289                 {
2290                     UNDOARGS
2291                     freearg(result);
2292                     runcode(args[--numargs].code, result);
2293                     freearg(args[numargs]);
2294                     forcearg(result, op&CODE_RET_MASK);
2295                     REDOARGS
2296                     continue;
2297                 }
2298                 // fall-through
2299 
2300             case CODE_DO|RET_NULL: case CODE_DO|RET_STR: case CODE_DO|RET_INT: case CODE_DO|RET_FLOAT:
2301                 freearg(result);
2302                 runcode(args[--numargs].code, result);
2303                 freearg(args[numargs]);
2304                 forcearg(result, op&CODE_RET_MASK);
2305                 continue;
2306 
2307             case CODE_JUMP:
2308             {
2309                 uint len = op>>8;
2310                 code += len;
2311                 continue;
2312             }
2313             case CODE_JUMP_TRUE:
2314             {
2315                 uint len = op>>8;
2316                 if(getbool(args[--numargs])) code += len;
2317                 freearg(args[numargs]);
2318                 continue;
2319             }
2320             case CODE_JUMP_FALSE:
2321             {
2322                 uint len = op>>8;
2323                 if(!getbool(args[--numargs])) code += len;
2324                 freearg(args[numargs]);
2325                 continue;
2326             }
2327             case CODE_JUMP_RESULT_TRUE:
2328             {
2329                 uint len = op>>8;
2330                 freearg(result);
2331                 --numargs;
2332                 if(args[numargs].type == VAL_CODE) { runcode(args[numargs].code, result); freearg(args[numargs]); }
2333                 else result = args[numargs];
2334                 if(getbool(result)) code += len;
2335                 continue;
2336             }
2337             case CODE_JUMP_RESULT_FALSE:
2338             {
2339                 uint len = op>>8;
2340                 freearg(result);
2341                 --numargs;
2342                 if(args[numargs].type == VAL_CODE) { runcode(args[numargs].code, result); freearg(args[numargs]); }
2343                 else result = args[numargs];
2344                 if(!getbool(result)) code += len;
2345                 continue;
2346             }
2347 
2348             case CODE_MACRO:
2349             {
2350                 uint len = op>>8;
2351                 args[numargs++].setmacro(code);
2352                 code += len/sizeof(uint) + 1;
2353                 continue;
2354             }
2355 
2356             case CODE_VAL|RET_STR:
2357             {
2358                 uint len = op>>8;
2359                 args[numargs++].setstr(newstring((const char *)code, len));
2360                 code += len/sizeof(uint) + 1;
2361                 continue;
2362             }
2363             case CODE_VALI|RET_STR:
2364             {
2365                 char s[4] = { char((op>>8)&0xFF), char((op>>16)&0xFF), char((op>>24)&0xFF), '\0' };
2366                 args[numargs++].setstr(newstring(s));
2367                 continue;
2368             }
2369             case CODE_VAL|RET_NULL:
2370             case CODE_VALI|RET_NULL: args[numargs++].setnull(); continue;
2371             case CODE_VAL|RET_INT: args[numargs++].setint(int(*code++)); continue;
2372             case CODE_VALI|RET_INT: args[numargs++].setint(int(op)>>8); continue;
2373             case CODE_VAL|RET_FLOAT: args[numargs++].setfloat(*(const float *)code++); continue;
2374             case CODE_VALI|RET_FLOAT: args[numargs++].setfloat(float(int(op)>>8)); continue;
2375 
2376             case CODE_DUP|RET_NULL: args[numargs-1].getval(args[numargs]); numargs++; continue;
2377             case CODE_DUP|RET_INT: args[numargs].setint(args[numargs-1].getint()); numargs++; continue;
2378             case CODE_DUP|RET_FLOAT: args[numargs].setfloat(args[numargs-1].getfloat()); numargs++; continue;
2379             case CODE_DUP|RET_STR: args[numargs].setstr(newstring(args[numargs-1].getstr())); numargs++; continue;
2380 
2381             case CODE_FORCE|RET_STR: forcestr(args[numargs-1]); continue;
2382             case CODE_FORCE|RET_INT: forceint(args[numargs-1]); continue;
2383             case CODE_FORCE|RET_FLOAT: forcefloat(args[numargs-1]); continue;
2384 
2385             case CODE_RESULT|RET_NULL:
2386                 freearg(result);
2387                 result = args[--numargs];
2388                 continue;
2389             case CODE_RESULT|RET_STR: case CODE_RESULT|RET_INT: case CODE_RESULT|RET_FLOAT:
2390                 freearg(result);
2391                 result = args[--numargs];
2392                 forcearg(result, op&CODE_RET_MASK);
2393                 continue;
2394 
2395             case CODE_EMPTY|RET_NULL: args[numargs++].setcode(emptyblock[VAL_NULL]+1); break;
2396             case CODE_EMPTY|RET_STR: args[numargs++].setcode(emptyblock[VAL_STR]+1); break;
2397             case CODE_EMPTY|RET_INT: args[numargs++].setcode(emptyblock[VAL_INT]+1); break;
2398             case CODE_EMPTY|RET_FLOAT: args[numargs++].setcode(emptyblock[VAL_FLOAT]+1); break;
2399             case CODE_BLOCK:
2400             {
2401                 uint len = op>>8;
2402                 args[numargs++].setcode(code+1);
2403                 code += len;
2404                 continue;
2405             }
2406             case CODE_COMPILE:
2407             {
2408                 tagval &arg = args[numargs-1];
2409                 vector<uint> buf;
2410                 switch(arg.type)
2411                 {
2412                     case VAL_INT: buf.reserve(8); buf.add(CODE_START); compileint(buf, arg.i); buf.add(CODE_RESULT); buf.add(CODE_EXIT); break;
2413                     case VAL_FLOAT: buf.reserve(8); buf.add(CODE_START); compilefloat(buf, arg.f); buf.add(CODE_RESULT); buf.add(CODE_EXIT); break;
2414                     case VAL_STR: case VAL_MACRO: case VAL_CSTR: buf.reserve(64); compilemain(buf, arg.s); freearg(arg); break;
2415                     default: buf.reserve(8); buf.add(CODE_START); compilenull(buf); buf.add(CODE_RESULT); buf.add(CODE_EXIT); break;
2416                 }
2417                 arg.setcode(buf.disown()+1);
2418                 continue;
2419             }
2420             case CODE_COND:
2421             {
2422                 tagval &arg = args[numargs-1];
2423                 switch(arg.type)
2424                 {
2425                     case VAL_STR: case VAL_MACRO: case VAL_CSTR:
2426                         if(arg.s[0])
2427                         {
2428                             vector<uint> buf;
2429                             buf.reserve(64);
2430                             compilemain(buf, arg.s);
2431                             freearg(arg);
2432                             arg.setcode(buf.disown()+1);
2433                         }
2434                         else forcenull(arg);
2435                         break;
2436                 }
2437                 continue;
2438             }
2439 
2440             case CODE_IDENT:
2441                 args[numargs++].setident(identmap[op>>8]);
2442                 continue;
2443             case CODE_IDENTARG:
2444             {
2445                 ident *id = identmap[op>>8];
2446                 if(!(aliasstack->usedargs&(1<<id->index)))
2447                 {
2448                     pusharg(*id, nullval, aliasstack->argstack[id->index]);
2449                     aliasstack->usedargs |= 1<<id->index;
2450                 }
2451                 args[numargs++].setident(id);
2452                 continue;
2453             }
2454             case CODE_IDENTU:
2455             {
2456                 tagval &arg = args[numargs-1];
2457                 ident *id = arg.type == VAL_STR || arg.type == VAL_MACRO || arg.type == VAL_CSTR ? newident(arg.s, IDF_UNKNOWN) : dummyident;
2458                 if(id->index < MAXARGS && !(aliasstack->usedargs&(1<<id->index)))
2459                 {
2460                     pusharg(*id, nullval, aliasstack->argstack[id->index]);
2461                     aliasstack->usedargs |= 1<<id->index;
2462                 }
2463                 freearg(arg);
2464                 arg.setident(id);
2465                 continue;
2466             }
2467 
2468             case CODE_LOOKUPU|RET_STR:
2469                 #define LOOKUPU(aval, sval, ival, fval, nval) { \
2470                     tagval &arg = args[numargs-1]; \
2471                     if(arg.type != VAL_STR && arg.type != VAL_MACRO && arg.type != VAL_CSTR) continue; \
2472                     ident *id = idents.access(arg.s); \
2473                     if(id) switch(id->type) \
2474                     { \
2475                         case ID_ALIAS: \
2476                             if(id->flags&IDF_UNKNOWN) break; \
2477                             freearg(arg); \
2478                             if(id->index < MAXARGS && !(aliasstack->usedargs&(1<<id->index))) { nval; continue; } \
2479                             aval; \
2480                             continue; \
2481                         case ID_SVAR: freearg(arg); sval; continue; \
2482                         case ID_VAR: freearg(arg); ival; continue; \
2483                         case ID_FVAR: freearg(arg); fval; continue; \
2484                         case ID_COMMAND: \
2485                         { \
2486                             freearg(arg); \
2487                             arg.setnull(); \
2488                             commandret = &arg; \
2489                             tagval buf[MAXARGS]; \
2490                             callcommand(id, buf, 0, true); \
2491                             forcearg(arg, op&CODE_RET_MASK); \
2492                             commandret = &result; \
2493                             continue; \
2494                         } \
2495                         default: freearg(arg); nval; continue; \
2496                     } \
2497                     debugcode("unknown alias lookup: %s", arg.s); \
2498                     freearg(arg); \
2499                     nval; \
2500                     continue; \
2501                 }
2502                 LOOKUPU(arg.setstr(newstring(id->getstr())),
2503                         arg.setstr(newstring(*id->storage.s)),
2504                         arg.setstr(newstring(intstr(*id->storage.i))),
2505                         arg.setstr(newstring(floatstr(*id->storage.f))),
2506                         arg.setstr(newstring("")));
2507             case CODE_LOOKUP|RET_STR:
2508                 #define LOOKUP(aval) { \
2509                     ident *id = identmap[op>>8]; \
2510                     if(id->flags&IDF_UNKNOWN) debugcode("unknown alias lookup: %s", id->name); \
2511                     aval; \
2512                     continue; \
2513                 }
2514                 LOOKUP(args[numargs++].setstr(newstring(id->getstr())));
2515             case CODE_LOOKUPARG|RET_STR:
2516                 #define LOOKUPARG(aval, nval) { \
2517                     ident *id = identmap[op>>8]; \
2518                     if(!(aliasstack->usedargs&(1<<id->index))) { nval; continue; } \
2519                     aval; \
2520                     continue; \
2521                 }
2522                 LOOKUPARG(args[numargs++].setstr(newstring(id->getstr())), args[numargs++].setstr(newstring("")));
2523             case CODE_LOOKUPU|RET_INT:
2524                 LOOKUPU(arg.setint(id->getint()),
2525                         arg.setint(parseint(*id->storage.s)),
2526                         arg.setint(*id->storage.i),
2527                         arg.setint(int(*id->storage.f)),
2528                         arg.setint(0));
2529             case CODE_LOOKUP|RET_INT:
2530                 LOOKUP(args[numargs++].setint(id->getint()));
2531             case CODE_LOOKUPARG|RET_INT:
2532                 LOOKUPARG(args[numargs++].setint(id->getint()), args[numargs++].setint(0));
2533             case CODE_LOOKUPU|RET_FLOAT:
2534                 LOOKUPU(arg.setfloat(id->getfloat()),
2535                         arg.setfloat(parsefloat(*id->storage.s)),
2536                         arg.setfloat(float(*id->storage.i)),
2537                         arg.setfloat(*id->storage.f),
2538                         arg.setfloat(0.0f));
2539             case CODE_LOOKUP|RET_FLOAT:
2540                 LOOKUP(args[numargs++].setfloat(id->getfloat()));
2541             case CODE_LOOKUPARG|RET_FLOAT:
2542                 LOOKUPARG(args[numargs++].setfloat(id->getfloat()), args[numargs++].setfloat(0.0f));
2543             case CODE_LOOKUPU|RET_NULL:
2544                 LOOKUPU(id->getval(arg),
2545                         arg.setstr(newstring(*id->storage.s)),
2546                         arg.setint(*id->storage.i),
2547                         arg.setfloat(*id->storage.f),
2548                         arg.setnull());
2549             case CODE_LOOKUP|RET_NULL:
2550                 LOOKUP(id->getval(args[numargs++]));
2551             case CODE_LOOKUPARG|RET_NULL:
2552                 LOOKUPARG(id->getval(args[numargs++]), args[numargs++].setnull());
2553 
2554             case CODE_LOOKUPMU|RET_STR:
2555                 LOOKUPU(id->getcstr(arg),
2556                         arg.setcstr(*id->storage.s),
2557                         arg.setstr(newstring(intstr(*id->storage.i))),
2558                         arg.setstr(newstring(floatstr(*id->storage.f))),
2559                         arg.setcstr(""));
2560             case CODE_LOOKUPM|RET_STR:
2561                 LOOKUP(id->getcstr(args[numargs++]));
2562             case CODE_LOOKUPMARG|RET_STR:
2563                 LOOKUPARG(id->getcstr(args[numargs++]), args[numargs++].setcstr(""));
2564             case CODE_LOOKUPMU|RET_NULL:
2565                 LOOKUPU(id->getcval(arg),
2566                         arg.setcstr(*id->storage.s),
2567                         arg.setint(*id->storage.i),
2568                         arg.setfloat(*id->storage.f),
2569                         arg.setnull());
2570             case CODE_LOOKUPM|RET_NULL:
2571                 LOOKUP(id->getcval(args[numargs++]));
2572             case CODE_LOOKUPMARG|RET_NULL:
2573                 LOOKUPARG(id->getcval(args[numargs++]), args[numargs++].setnull());
2574 
2575             case CODE_SVAR|RET_STR: case CODE_SVAR|RET_NULL: args[numargs++].setstr(newstring(*identmap[op>>8]->storage.s)); continue;
2576             case CODE_SVAR|RET_INT: args[numargs++].setint(parseint(*identmap[op>>8]->storage.s)); continue;
2577             case CODE_SVAR|RET_FLOAT: args[numargs++].setfloat(parsefloat(*identmap[op>>8]->storage.s)); continue;
2578             case CODE_SVARM: args[numargs++].setcstr(*identmap[op>>8]->storage.s); continue;
2579             case CODE_SVAR1: setsvarchecked(identmap[op>>8], args[--numargs].s); freearg(args[numargs]); continue;
2580 
2581             case CODE_IVAR|RET_INT: case CODE_IVAR|RET_NULL: args[numargs++].setint(*identmap[op>>8]->storage.i); continue;
2582             case CODE_IVAR|RET_STR: args[numargs++].setstr(newstring(intstr(*identmap[op>>8]->storage.i))); continue;
2583             case CODE_IVAR|RET_FLOAT: args[numargs++].setfloat(float(*identmap[op>>8]->storage.i)); continue;
2584             case CODE_IVAR1: setvarchecked(identmap[op>>8], args[--numargs].i); continue;
2585             case CODE_IVAR2: numargs -= 2; setvarchecked(identmap[op>>8], (args[numargs].i<<16)|(args[numargs+1].i<<8)); continue;
2586             case CODE_IVAR3: numargs -= 3; setvarchecked(identmap[op>>8], (args[numargs].i<<16)|(args[numargs+1].i<<8)|args[numargs+2].i); continue;
2587 
2588             case CODE_FVAR|RET_FLOAT: case CODE_FVAR|RET_NULL: args[numargs++].setfloat(*identmap[op>>8]->storage.f); continue;
2589             case CODE_FVAR|RET_STR: args[numargs++].setstr(newstring(floatstr(*identmap[op>>8]->storage.f))); continue;
2590             case CODE_FVAR|RET_INT: args[numargs++].setint(int(*identmap[op>>8]->storage.f)); continue;
2591             case CODE_FVAR1: setfvarchecked(identmap[op>>8], args[--numargs].f); continue;
2592 
2593             #define OFFSETARG(n) offset+n
2594             case CODE_COM|RET_NULL: case CODE_COM|RET_STR: case CODE_COM|RET_FLOAT: case CODE_COM|RET_INT:
2595             {
2596                 ident *id = identmap[op>>8];
2597                 int offset = numargs-id->numargs;
2598                 forcenull(result);
2599                 CALLCOM(id->numargs)
2600                 forcearg(result, op&CODE_RET_MASK);
2601                 freeargs(args, numargs, offset);
2602                 continue;
2603             }
2604 #ifndef STANDALONE
2605             case CODE_COMD|RET_NULL: case CODE_COMD|RET_STR: case CODE_COMD|RET_FLOAT: case CODE_COMD|RET_INT:
2606             {
2607                 ident *id = identmap[op>>8];
2608                 int offset = numargs-(id->numargs-1);
2609                 addreleaseaction(id, &args[offset], id->numargs-1);
2610                 CALLCOM(id->numargs)
2611                 forcearg(result, op&CODE_RET_MASK);
2612                 freeargs(args, numargs, offset);
2613                 continue;
2614             }
2615 #endif
2616             #undef OFFSETARG
2617 
2618             case CODE_COMV|RET_NULL: case CODE_COMV|RET_STR: case CODE_COMV|RET_FLOAT: case CODE_COMV|RET_INT:
2619             {
2620                 ident *id = identmap[op>>13];
2621                 int callargs = (op>>8)&0x1F, offset = numargs-callargs;
2622                 forcenull(result);
2623                 ((comfunv)id->fun)(&args[offset], callargs);
2624                 forcearg(result, op&CODE_RET_MASK);
2625                 freeargs(args, numargs, offset);
2626                 continue;
2627             }
2628             case CODE_COMC|RET_NULL: case CODE_COMC|RET_STR: case CODE_COMC|RET_FLOAT: case CODE_COMC|RET_INT:
2629             {
2630                 ident *id = identmap[op>>13];
2631                 int callargs = (op>>8)&0x1F, offset = numargs-callargs;
2632                 forcenull(result);
2633                 {
2634                     vector<char> buf;
2635                     buf.reserve(MAXSTRLEN);
2636                     ((comfun1)id->fun)(conc(buf, &args[offset], callargs, true));
2637                 }
2638                 forcearg(result, op&CODE_RET_MASK);
2639                 freeargs(args, numargs, offset);
2640                 continue;
2641             }
2642 
2643             case CODE_CONC|RET_NULL: case CODE_CONC|RET_STR: case CODE_CONC|RET_FLOAT: case CODE_CONC|RET_INT:
2644             case CODE_CONCW|RET_NULL: case CODE_CONCW|RET_STR: case CODE_CONCW|RET_FLOAT: case CODE_CONCW|RET_INT:
2645             {
2646                 int numconc = op>>8;
2647                 char *s = conc(&args[numargs-numconc], numconc, (op&CODE_OP_MASK)==CODE_CONC);
2648                 freeargs(args, numargs, numargs-numconc);
2649                 args[numargs].setstr(s);
2650                 forcearg(args[numargs], op&CODE_RET_MASK);
2651                 numargs++;
2652                 continue;
2653             }
2654 
2655             case CODE_CONCM|RET_NULL: case CODE_CONCM|RET_STR: case CODE_CONCM|RET_FLOAT: case CODE_CONCM|RET_INT:
2656             {
2657                 int numconc = op>>8;
2658                 char *s = conc(&args[numargs-numconc], numconc, false);
2659                 freeargs(args, numargs, numargs-numconc);
2660                 result.setstr(s);
2661                 forcearg(result, op&CODE_RET_MASK);
2662                 continue;
2663             }
2664 
2665             case CODE_ALIAS:
2666                 setalias(*identmap[op>>8], args[--numargs]);
2667                 continue;
2668             case CODE_ALIASARG:
2669                 setarg(*identmap[op>>8], args[--numargs]);
2670                 continue;
2671             case CODE_ALIASU:
2672                 numargs -= 2;
2673                 setalias(args[numargs].getstr(), args[numargs+1]);
2674                 freearg(args[numargs]);
2675                 continue;
2676 
2677             #define SKIPARGS(offset) offset
2678             case CODE_CALL|RET_NULL: case CODE_CALL|RET_STR: case CODE_CALL|RET_FLOAT: case CODE_CALL|RET_INT:
2679             {
2680                 #define FORCERESULT { \
2681                     freeargs(args, numargs, SKIPARGS(offset)); \
2682                     forcearg(result, op&CODE_RET_MASK); \
2683                     continue; \
2684                 }
2685                 #define CALLALIAS { \
2686                     identstack argstack[MAXARGS]; \
2687                     for(int i = 0; i < callargs; i++) \
2688                         pusharg(*identmap[i], args[offset + i], argstack[i]); \
2689                     int oldargs = _numargs; \
2690                     _numargs = callargs; \
2691                     int oldflags = identflags; \
2692                     identflags |= id->flags&IDF_OVERRIDDEN; \
2693                     identlink aliaslink = { id, aliasstack, (1<<callargs)-1, argstack }; \
2694                     aliasstack = &aliaslink; \
2695                     if(!id->code) id->code = compilecode(id->getstr()); \
2696                     uint *code = id->code; \
2697                     code[0] += 0x100; \
2698                     runcode(code+1, result); \
2699                     code[0] -= 0x100; \
2700                     if(int(code[0]) < 0x100) delete[] code; \
2701                     aliasstack = aliaslink.next; \
2702                     identflags = oldflags; \
2703                     for(int i = 0; i < callargs; i++) \
2704                         poparg(*identmap[i]); \
2705                     for(int argmask = aliaslink.usedargs&(~0<<callargs), i = callargs; argmask; i++) \
2706                         if(argmask&(1<<i)) { poparg(*identmap[i]); argmask &= ~(1<<i); } \
2707                     forcearg(result, op&CODE_RET_MASK); \
2708                     _numargs = oldargs; \
2709                     numargs = SKIPARGS(offset); \
2710                 }
2711                 forcenull(result);
2712                 ident *id = identmap[op>>13];
2713                 int callargs = (op>>8)&0x1F, offset = numargs-callargs;
2714                 if(id->flags&IDF_UNKNOWN)
2715                 {
2716                     debugcode("unknown command: %s", id->name);
2717                     FORCERESULT;
2718                 }
2719                 CALLALIAS;
2720                 continue;
2721             }
2722             case CODE_CALLARG|RET_NULL: case CODE_CALLARG|RET_STR: case CODE_CALLARG|RET_FLOAT: case CODE_CALLARG|RET_INT:
2723             {
2724                 forcenull(result);
2725                 ident *id = identmap[op>>13];
2726                 int callargs = (op>>8)&0x1F, offset = numargs-callargs;
2727                 if(!(aliasstack->usedargs&(1<<id->index))) FORCERESULT;
2728                 CALLALIAS;
2729                 continue;
2730             }
2731             #undef SKIPARGS
2732 
2733             #define SKIPARGS(offset) offset-1
2734             case CODE_CALLU|RET_NULL: case CODE_CALLU|RET_STR: case CODE_CALLU|RET_FLOAT: case CODE_CALLU|RET_INT:
2735             {
2736                 int callargs = op>>8, offset = numargs-callargs;
2737                 tagval &idarg = args[offset-1];
2738                 if(idarg.type != VAL_STR && idarg.type != VAL_MACRO && idarg.type != VAL_CSTR)
2739                 {
2740                 litval:
2741                     freearg(result);
2742                     result = idarg;
2743                     forcearg(result, op&CODE_RET_MASK);
2744                     while(--numargs >= offset) freearg(args[numargs]);
2745                     continue;
2746                 }
2747                 ident *id = idents.access(idarg.s);
2748                 if(!id)
2749                 {
2750                 noid:
2751                     if(checknumber(idarg.s)) goto litval;
2752                     debugcode("unknown command: %s", idarg.s);
2753                     forcenull(result);
2754                     FORCERESULT;
2755                 }
2756                 forcenull(result);
2757                 switch(id->type)
2758                 {
2759                     default:
2760                         if(!id->fun) FORCERESULT;
2761                         // fall-through
2762                     case ID_COMMAND:
2763                         freearg(idarg);
2764                         callcommand(id, &args[offset], callargs);
2765                         forcearg(result, op&CODE_RET_MASK);
2766                         numargs = offset - 1;
2767                         continue;
2768                     case ID_LOCAL:
2769                     {
2770                         identstack locals[MAXARGS];
2771                         freearg(idarg);
2772                         loopj(callargs) pushalias(*forceident(args[offset+j]), locals[j]);
2773                         code = runcode(code, result);
2774                         loopj(callargs) popalias(*args[offset+j].id);
2775                         goto exit;
2776                     }
2777                     case ID_VAR:
2778                         if(callargs <= 0) printvar(id); else setvarchecked(id, &args[offset], callargs);
2779                         FORCERESULT;
2780                     case ID_FVAR:
2781                         if(callargs <= 0) printvar(id); else setfvarchecked(id, forcefloat(args[offset]));
2782                         FORCERESULT;
2783                     case ID_SVAR:
2784                         if(callargs <= 0) printvar(id); else setsvarchecked(id, forcestr(args[offset]));
2785                         FORCERESULT;
2786                     case ID_ALIAS:
2787                         if(id->index < MAXARGS && !(aliasstack->usedargs&(1<<id->index))) FORCERESULT;
2788                         if(id->valtype==VAL_NULL) goto noid;
2789                         freearg(idarg);
2790                         CALLALIAS;
2791                         continue;
2792                 }
2793             }
2794             #undef SKIPARGS
2795         }
2796     }
2797 exit:
2798     commandret = prevret;
2799     --rundepth;
2800     return code;
2801 }
2802 
executeret(const uint * code,tagval & result)2803 void executeret(const uint *code, tagval &result)
2804 {
2805     runcode(code, result);
2806 }
2807 
executeret(const char * p,tagval & result)2808 void executeret(const char *p, tagval &result)
2809 {
2810     vector<uint> code;
2811     code.reserve(64);
2812     compilemain(code, p, VAL_ANY);
2813     runcode(code.getbuf()+1, result);
2814     if(int(code[0]) >= 0x100) code.disown();
2815 }
2816 
executeret(ident * id,tagval * args,int numargs,bool lookup,tagval & result)2817 void executeret(ident *id, tagval *args, int numargs, bool lookup, tagval &result)
2818 {
2819     result.setnull();
2820     ++rundepth;
2821     tagval *prevret = commandret;
2822     commandret = &result;
2823     if(rundepth > MAXRUNDEPTH) debugcode("exceeded recursion limit");
2824     else if(id) switch(id->type)
2825     {
2826         default:
2827             if(!id->fun) break;
2828             // fall-through
2829         case ID_COMMAND:
2830             if(numargs < id->numargs)
2831             {
2832                 tagval buf[MAXARGS];
2833                 memcpy(buf, args, numargs*sizeof(tagval));
2834                 callcommand(id, buf, numargs, lookup);
2835             }
2836             else callcommand(id, args, numargs, lookup);
2837             numargs = 0;
2838             break;
2839         case ID_VAR:
2840             if(numargs <= 0) printvar(id); else setvarchecked(id, args, numargs);
2841             break;
2842         case ID_FVAR:
2843             if(numargs <= 0) printvar(id); else setfvarchecked(id, forcefloat(args[0]));
2844             break;
2845         case ID_SVAR:
2846             if(numargs <= 0) printvar(id); else setsvarchecked(id, forcestr(args[0]));
2847             break;
2848         case ID_ALIAS:
2849             if(id->index < MAXARGS && !(aliasstack->usedargs&(1<<id->index))) break;
2850             if(id->valtype==VAL_NULL) break;
2851             #define callargs numargs
2852             #define offset 0
2853             #define op RET_NULL
2854             #define SKIPARGS(offset) offset
2855             CALLALIAS;
2856             #undef callargs
2857             #undef offset
2858             #undef op
2859             #undef SKIPARGS
2860             break;
2861     }
2862     freeargs(args, numargs, 0);
2863     commandret = prevret;
2864     --rundepth;
2865 }
2866 
executestr(const uint * code)2867 char *executestr(const uint *code)
2868 {
2869     tagval result;
2870     runcode(code, result);
2871     if(result.type == VAL_NULL) return NULL;
2872     forcestr(result);
2873     return result.s;
2874 }
2875 
executestr(const char * p)2876 char *executestr(const char *p)
2877 {
2878     tagval result;
2879     executeret(p, result);
2880     if(result.type == VAL_NULL) return NULL;
2881     forcestr(result);
2882     return result.s;
2883 }
2884 
executestr(ident * id,tagval * args,int numargs,bool lookup)2885 char *executestr(ident *id, tagval *args, int numargs, bool lookup)
2886 {
2887     tagval result;
2888     executeret(id, args, numargs, lookup, result);
2889     if(result.type == VAL_NULL) return NULL;
2890     forcestr(result);
2891     return result.s;
2892 }
2893 
execidentstr(const char * name,bool lookup)2894 char *execidentstr(const char *name, bool lookup)
2895 {
2896     ident *id = idents.access(name);
2897     return id ? executestr(id, NULL, 0, lookup) : NULL;
2898 }
2899 
execute(const uint * code)2900 int execute(const uint *code)
2901 {
2902     tagval result;
2903     runcode(code, result);
2904     int i = result.getint();
2905     freearg(result);
2906     return i;
2907 }
2908 
execute(const char * p)2909 int execute(const char *p)
2910 {
2911     vector<uint> code;
2912     code.reserve(64);
2913     compilemain(code, p, VAL_INT);
2914     tagval result;
2915     runcode(code.getbuf()+1, result);
2916     if(int(code[0]) >= 0x100) code.disown();
2917     int i = result.getint();
2918     freearg(result);
2919     return i;
2920 }
2921 
execute(ident * id,tagval * args,int numargs,bool lookup)2922 int execute(ident *id, tagval *args, int numargs, bool lookup)
2923 {
2924     tagval result;
2925     executeret(id, args, numargs, lookup, result);
2926     int i = result.getint();
2927     freearg(result);
2928     return i;
2929 }
2930 
execident(const char * name,int noid,bool lookup)2931 int execident(const char *name, int noid, bool lookup)
2932 {
2933     ident *id = idents.access(name);
2934     return id ? execute(id, NULL, 0, lookup) : noid;
2935 }
2936 
executefloat(const uint * code)2937 float executefloat(const uint *code)
2938 {
2939     tagval result;
2940     runcode(code, result);
2941     float f = result.getfloat();
2942     freearg(result);
2943     return f;
2944 }
2945 
executefloat(const char * p)2946 float executefloat(const char *p)
2947 {
2948     tagval result;
2949     executeret(p, result);
2950     float f = result.getfloat();
2951     freearg(result);
2952     return f;
2953 }
2954 
executefloat(ident * id,tagval * args,int numargs,bool lookup)2955 float executefloat(ident *id, tagval *args, int numargs, bool lookup)
2956 {
2957     tagval result;
2958     executeret(id, args, numargs, lookup, result);
2959     float f = result.getfloat();
2960     freearg(result);
2961     return f;
2962 }
2963 
execidentfloat(const char * name,float noid,bool lookup)2964 float execidentfloat(const char *name, float noid, bool lookup)
2965 {
2966     ident *id = idents.access(name);
2967     return id ? executefloat(id, NULL, 0, lookup) : noid;
2968 }
2969 
executebool(const uint * code)2970 bool executebool(const uint *code)
2971 {
2972     tagval result;
2973     runcode(code, result);
2974     bool b = getbool(result);
2975     freearg(result);
2976     return b;
2977 }
2978 
executebool(const char * p)2979 bool executebool(const char *p)
2980 {
2981     tagval result;
2982     executeret(p, result);
2983     bool b = getbool(result);
2984     freearg(result);
2985     return b;
2986 }
2987 
executebool(ident * id,tagval * args,int numargs,bool lookup)2988 bool executebool(ident *id, tagval *args, int numargs, bool lookup)
2989 {
2990     tagval result;
2991     executeret(id, args, numargs, lookup, result);
2992     bool b = getbool(result);
2993     freearg(result);
2994     return b;
2995 }
2996 
execidentbool(const char * name,bool noid,bool lookup)2997 bool execidentbool(const char *name, bool noid, bool lookup)
2998 {
2999     ident *id = idents.access(name);
3000     return id ? executebool(id, NULL, 0, lookup) : noid;
3001 }
3002 
execfile(const char * cfgfile,bool msg)3003 bool execfile(const char *cfgfile, bool msg)
3004 {
3005     string s;
3006     copystring(s, cfgfile);
3007     char *buf = loadfile(path(s), NULL);
3008     if(!buf)
3009     {
3010         if(msg) conoutf(CON_ERROR, "could not read \"%s\"", cfgfile);
3011         return false;
3012     }
3013     const char *oldsourcefile = sourcefile, *oldsourcestr = sourcestr;
3014     sourcefile = cfgfile;
3015     sourcestr = buf;
3016     execute(buf);
3017     sourcefile = oldsourcefile;
3018     sourcestr = oldsourcestr;
3019     delete[] buf;
3020     return true;
3021 }
3022 ICOMMAND(exec, "sb", (char *file, int *msg), intret(execfile(file, *msg != 0) ? 1 : 0));
3023 
escapestring(const char * s)3024 const char *escapestring(const char *s)
3025 {
3026     stridx = (stridx + 1)%4;
3027     vector<char> &buf = strbuf[stridx];
3028     buf.setsize(0);
3029     buf.add('"');
3030     for(; *s; s++) switch(*s)
3031     {
3032         case '\n': buf.put("^n", 2); break;
3033         case '\t': buf.put("^t", 2); break;
3034         case '\f': buf.put("^f", 2); break;
3035         case '"': buf.put("^\"", 2); break;
3036         case '^': buf.put("^^", 2); break;
3037         default: buf.add(*s); break;
3038     }
3039     buf.put("\"\0", 2);
3040     return buf.getbuf();
3041 }
3042 
3043 ICOMMAND(escape, "s", (char *s), result(escapestring(s)));
3044 ICOMMAND(unescape, "s", (char *s),
3045 {
3046     int len = strlen(s);
3047     char *d = newstring(len);
3048     unescapestring(d, s, &s[len]);
3049     stringret(d);
3050 });
3051 
escapeid(const char * s)3052 const char *escapeid(const char *s)
3053 {
3054     const char *end = s + strcspn(s, "\"/;()[]@ \f\t\r\n\0");
3055     return *end ? escapestring(s) : s;
3056 }
3057 
validateblock(const char * s)3058 bool validateblock(const char *s)
3059 {
3060     const int maxbrak = 100;
3061     static char brakstack[maxbrak];
3062     int brakdepth = 0;
3063     for(; *s; s++) switch(*s)
3064     {
3065         case '[': case '(': if(brakdepth >= maxbrak) return false; brakstack[brakdepth++] = *s; break;
3066         case ']': if(brakdepth <= 0 || brakstack[--brakdepth] != '[') return false; break;
3067         case ')': if(brakdepth <= 0 || brakstack[--brakdepth] != '(') return false; break;
3068         case '"': s = parsestring(s + 1); if(*s != '"') return false; break;
3069         case '/': if(s[1] == '/') return false; break;
3070         case '@': case '\f': return false;
3071     }
3072     return brakdepth == 0;
3073 }
3074 
3075 #ifndef STANDALONE
writecfg(const char * name)3076 void writecfg(const char *name)
3077 {
3078     stream *f = openutf8file(path(name && name[0] ? name : game::savedconfig(), true), "w");
3079     if(!f) return;
3080     f->printf("// automatically written on exit, DO NOT MODIFY\n// delete this file to have %s overwrite these settings\n// modify settings in game, or put settings in %s to override anything\n\n", game::defaultconfig(), game::autoexec());
3081     game::writeclientinfo(f);
3082     f->printf("\n");
3083     writecrosshairs(f);
3084     vector<ident *> ids;
3085     enumerate(idents, ident, id, ids.add(&id));
3086     ids.sortname();
3087     loopv(ids)
3088     {
3089         ident &id = *ids[i];
3090         if(id.flags&IDF_PERSIST) switch(id.type)
3091         {
3092             case ID_VAR: f->printf("%s %d\n", escapeid(id), *id.storage.i); break;
3093             case ID_FVAR: f->printf("%s %s\n", escapeid(id), floatstr(*id.storage.f)); break;
3094             case ID_SVAR: f->printf("%s %s\n", escapeid(id), escapestring(*id.storage.s)); break;
3095         }
3096     }
3097     f->printf("\n");
3098     writebinds(f);
3099     f->printf("\n");
3100     loopv(ids)
3101     {
3102         ident &id = *ids[i];
3103         if(id.type==ID_ALIAS && id.flags&IDF_PERSIST && !(id.flags&IDF_OVERRIDDEN)) switch(id.valtype)
3104         {
3105         case VAL_STR:
3106             if(!id.val.s[0]) break;
3107             if(!validateblock(id.val.s)) { f->printf("%s = %s\n", escapeid(id), escapestring(id.val.s)); break; }
3108         case VAL_FLOAT:
3109         case VAL_INT:
3110             f->printf("%s = [%s]\n", escapeid(id), id.getstr()); break;
3111         }
3112     }
3113     f->printf("\n");
3114     writecompletions(f);
3115     delete f;
3116 }
3117 
3118 COMMAND(writecfg, "s");
3119 #endif
3120 
3121 // below the commands that implement a small imperative language. thanks to the semantics of
3122 // () and [] expressions, any control construct can be defined trivially.
3123 
3124 static string retbuf[4];
3125 static int retidx = 0;
3126 
intstr(int v)3127 const char *intstr(int v)
3128 {
3129     retidx = (retidx + 1)%4;
3130     intformat(retbuf[retidx], v);
3131     return retbuf[retidx];
3132 }
3133 
intret(int v)3134 void intret(int v)
3135 {
3136     commandret->setint(v);
3137 }
3138 
floatstr(float v)3139 const char *floatstr(float v)
3140 {
3141     retidx = (retidx + 1)%4;
3142     floatformat(retbuf[retidx], v);
3143     return retbuf[retidx];
3144 }
3145 
floatret(float v)3146 void floatret(float v)
3147 {
3148     commandret->setfloat(v);
3149 }
3150 
numberstr(double v)3151 const char *numberstr(double v)
3152 {
3153     retidx = (retidx + 1)%4;
3154     numberformat(retbuf[retidx], v);
3155     return retbuf[retidx];
3156 }
3157 
numberret(double v)3158 void numberret(double v)
3159 {
3160     int i = int(v);
3161     if(v == i) commandret->setint(i);
3162     else commandret->setfloat(v);
3163 }
3164 
3165 #undef ICOMMANDNAME
3166 #define ICOMMANDNAME(name) _stdcmd
3167 #undef ICOMMANDSNAME
3168 #define ICOMMANDSNAME _stdcmd
3169 
3170 ICOMMANDK(do, ID_DO, "e", (uint *body), executeret(body, *commandret));
3171 
doargs(uint * body)3172 static void doargs(uint *body)
3173 {
3174     if(aliasstack != &noalias)
3175     {
3176         UNDOARGS
3177         executeret(body, *commandret);
3178         REDOARGS
3179     }
3180     else executeret(body, *commandret);
3181 }
3182 COMMANDK(doargs, ID_DOARGS, "e");
3183 
3184 ICOMMANDK(if, ID_IF, "tee", (tagval *cond, uint *t, uint *f), executeret(getbool(*cond) ? t : f, *commandret));
3185 ICOMMAND(?, "tTT", (tagval *cond, tagval *t, tagval *f), result(*(getbool(*cond) ? t : f)));
3186 
3187 ICOMMAND(pushif, "rTe", (ident *id, tagval *v, uint *code),
3188 {
3189     if(id->type != ID_ALIAS || id->index < MAXARGS) return;
3190     if(getbool(*v))
3191     {
3192         identstack stack;
3193         pusharg(*id, *v, stack);
3194         v->type = VAL_NULL;
3195         id->flags &= ~IDF_UNKNOWN;
3196         executeret(code, *commandret);
3197         poparg(*id);
3198     }
3199 });
3200 
loopiter(ident * id,identstack & stack,const tagval & v)3201 void loopiter(ident *id, identstack &stack, const tagval &v)
3202 {
3203     if(id->stack != &stack)
3204     {
3205         pusharg(*id, v, stack);
3206         id->flags &= ~IDF_UNKNOWN;
3207     }
3208     else
3209     {
3210         if(id->valtype == VAL_STR) delete[] id->val.s;
3211         cleancode(*id);
3212         id->setval(v);
3213     }
3214 }
3215 
loopend(ident * id,identstack & stack)3216 void loopend(ident *id, identstack &stack)
3217 {
3218     if(id->stack == &stack) poparg(*id);
3219 }
3220 
setiter(ident & id,int i,identstack & stack)3221 static inline void setiter(ident &id, int i, identstack &stack)
3222 {
3223     if(i)
3224     {
3225         if(id.valtype != VAL_INT)
3226         {
3227             if(id.valtype == VAL_STR) delete[] id.val.s;
3228             cleancode(id);
3229             id.valtype = VAL_INT;
3230         }
3231         id.val.i = i;
3232     }
3233     else
3234     {
3235         tagval zero;
3236         zero.setint(0);
3237         pusharg(id, zero, stack);
3238         id.flags &= ~IDF_UNKNOWN;
3239     }
3240 }
3241 
doloop(ident & id,int offset,int n,int step,uint * body)3242 static inline void doloop(ident &id, int offset, int n, int step, uint *body)
3243 {
3244     if(n <= 0 || id.type != ID_ALIAS) return;
3245     identstack stack;
3246     loopi(n)
3247     {
3248         setiter(id, offset + i*step, stack);
3249         execute(body);
3250     }
3251     poparg(id);
3252 }
3253 ICOMMAND(loop, "rie", (ident *id, int *n, uint *body), doloop(*id, 0, *n, 1, body));
3254 ICOMMAND(loop+, "riie", (ident *id, int *offset, int *n, uint *body), doloop(*id, *offset, *n, 1, body));
3255 ICOMMAND(loop*, "riie", (ident *id, int *step, int *n, uint *body), doloop(*id, 0, *n, *step, body));
3256 ICOMMAND(loop+*, "riiie", (ident *id, int *offset, int *step, int *n, uint *body), doloop(*id, *offset, *n, *step, body));
3257 
loopwhile(ident & id,int offset,int n,int step,uint * cond,uint * body)3258 static inline void loopwhile(ident &id, int offset, int n, int step, uint *cond, uint *body)
3259 {
3260     if(n <= 0 || id.type!=ID_ALIAS) return;
3261     identstack stack;
3262     loopi(n)
3263     {
3264         setiter(id, offset + i*step, stack);
3265         if(!executebool(cond)) break;
3266         execute(body);
3267     }
3268     poparg(id);
3269 }
3270 ICOMMAND(loopwhile, "riee", (ident *id, int *n, uint *cond, uint *body), loopwhile(*id, 0, *n, 1, cond, body));
3271 ICOMMAND(loopwhile+, "riiee", (ident *id, int *offset, int *n, uint *cond, uint *body), loopwhile(*id, *offset, *n, 1, cond, body));
3272 ICOMMAND(loopwhile*, "riiee", (ident *id, int *step, int *n, uint *cond, uint *body), loopwhile(*id, 0, *n, *step, cond, body));
3273 ICOMMAND(loopwhile+*, "riiiee", (ident *id, int *offset, int *step, int *n, uint *cond, uint *body), loopwhile(*id, *offset, *n, *step, cond, body));
3274 
3275 ICOMMAND(while, "ee", (uint *cond, uint *body), while(executebool(cond)) execute(body));
3276 
loopconc(ident & id,int offset,int n,int step,uint * body,bool space)3277 static inline void loopconc(ident &id, int offset, int n, int step, uint *body, bool space)
3278 {
3279     if(n <= 0 || id.type != ID_ALIAS) return;
3280     identstack stack;
3281     vector<char> s;
3282     loopi(n)
3283     {
3284         setiter(id, offset + i*step, stack);
3285         tagval v;
3286         executeret(body, v);
3287         const char *vstr = v.getstr();
3288         int len = strlen(vstr);
3289         if(space && i) s.add(' ');
3290         s.put(vstr, len);
3291         freearg(v);
3292     }
3293     if(n > 0) poparg(id);
3294     s.add('\0');
3295     commandret->setstr(s.disown());
3296 }
3297 ICOMMAND(loopconcat, "rie", (ident *id, int *n, uint *body), loopconc(*id, 0, *n, 1, body, true));
3298 ICOMMAND(loopconcat+, "riie", (ident *id, int *offset, int *n, uint *body), loopconc(*id, *offset, *n, 1, body, true));
3299 ICOMMAND(loopconcat*, "riie", (ident *id, int *step, int *n, uint *body), loopconc(*id, 0, *n, *step, body, true));
3300 ICOMMAND(loopconcat+*, "riiie", (ident *id, int *offset, int *step, int *n, uint *body), loopconc(*id, *offset, *n, *step, body, true));
3301 ICOMMAND(loopconcatword, "rie", (ident *id, int *n, uint *body), loopconc(*id, 0, *n, 1, body, false));
3302 ICOMMAND(loopconcatword+, "riie", (ident *id, int *offset, int *n, uint *body), loopconc(*id, *offset, *n, 1, body, false));
3303 ICOMMAND(loopconcatword*, "riie", (ident *id, int *step, int *n, uint *body), loopconc(*id, 0, *n, *step, body, false));
3304 ICOMMAND(loopconcatword+*, "riiie", (ident *id, int *offset, int *step, int *n, uint *body), loopconc(*id, *offset, *n, *step, body, false));
3305 
concat(tagval * v,int n)3306 void concat(tagval *v, int n)
3307 {
3308     commandret->setstr(conc(v, n, true));
3309 }
3310 COMMAND(concat, "V");
3311 
concatword(tagval * v,int n)3312 void concatword(tagval *v, int n)
3313 {
3314     commandret->setstr(conc(v, n, false));
3315 }
3316 COMMAND(concatword, "V");
3317 
result(tagval & v)3318 void result(tagval &v)
3319 {
3320     *commandret = v;
3321     v.type = VAL_NULL;
3322 }
3323 
stringret(char * s)3324 void stringret(char *s)
3325 {
3326     commandret->setstr(s);
3327 }
3328 
result(const char * s)3329 void result(const char *s)
3330 {
3331     commandret->setstr(newstring(s));
3332 }
3333 
3334 ICOMMANDK(result, ID_RESULT, "T", (tagval *v),
3335 {
3336     *commandret = *v;
3337     v->type = VAL_NULL;
3338 });
3339 
format(tagval * args,int numargs)3340 void format(tagval *args, int numargs)
3341 {
3342     vector<char> s;
3343     const char *f = args[0].getstr();
3344     while(*f)
3345     {
3346         int c = *f++;
3347         if(c == '%')
3348         {
3349             int i = *f++;
3350             if(i >= '1' && i <= '9')
3351             {
3352                 i -= '0';
3353                 const char *sub = i < numargs ? args[i].getstr() : "";
3354                 while(*sub) s.add(*sub++);
3355             }
3356             else s.add(i);
3357         }
3358         else s.add(c);
3359     }
3360     s.add('\0');
3361     commandret->setstr(s.disown());
3362 }
3363 COMMAND(format, "V");
3364 
3365 static const char *liststart = NULL, *listend = NULL, *listquotestart = NULL, *listquoteend = NULL;
3366 
skiplist(const char * & p)3367 static inline void skiplist(const char *&p)
3368 {
3369     for(;;)
3370     {
3371         p += strspn(p, " \t\r\n");
3372         if(p[0]!='/' || p[1]!='/') break;
3373         p += strcspn(p, "\n\0");
3374     }
3375 }
3376 
parselist(const char * & s,const char * & start=liststart,const char * & end=listend,const char * & quotestart=listquotestart,const char * & quoteend=listquoteend)3377 static bool parselist(const char *&s, const char *&start = liststart, const char *&end = listend, const char *&quotestart = listquotestart, const char *&quoteend = listquoteend)
3378 {
3379     skiplist(s);
3380     switch(*s)
3381     {
3382         case '"': quotestart = s++; start = s; s = parsestring(s); end = s; if(*s == '"') s++; quoteend = s; break;
3383         case '(': case '[':
3384             quotestart = s;
3385             start = s+1;
3386             for(int braktype = *s++, brak = 1;;)
3387             {
3388                 s += strcspn(s, "\"/;()[]\0");
3389                 int c = *s++;
3390                 switch(c)
3391                 {
3392                     case '\0': s--; quoteend = end = s; return true;
3393                     case '"': s = parsestring(s); if(*s == '"') s++; break;
3394                     case '/': if(*s == '/') s += strcspn(s, "\n\0"); break;
3395                     case '(': case '[': if(c == braktype) brak++; break;
3396                     case ')': if(braktype == '(' && --brak <= 0) goto endblock; break;
3397                     case ']': if(braktype == '[' && --brak <= 0) goto endblock; break;
3398                 }
3399             }
3400         endblock:
3401             end = s-1;
3402             quoteend = s;
3403             break;
3404         case '\0': case ')': case ']': return false;
3405         default: quotestart = start = s; s = parseword(s); quoteend = end = s; break;
3406     }
3407     skiplist(s);
3408     if(*s == ';') s++;
3409     return true;
3410 }
3411 
listelem(const char * start=liststart,const char * end=listend,const char * quotestart=listquotestart)3412 static inline char *listelem(const char *start = liststart, const char *end = listend, const char *quotestart = listquotestart)
3413 {
3414     size_t len = end-start;
3415     char *s = newstring(len);
3416     if(*quotestart == '"') unescapestring(s, start, end);
3417     else copystring(s, start, len+1);
3418     return s;
3419 }
3420 
explodelist(const char * s,vector<char * > & elems,int limit)3421 void explodelist(const char *s, vector<char *> &elems, int limit)
3422 {
3423     const char *start, *end, *qstart;
3424     while((limit < 0 || elems.length() < limit) && parselist(s, start, end, qstart))
3425         elems.add(listelem(start, end, qstart));
3426 }
3427 
indexlist(const char * s,int pos)3428 char *indexlist(const char *s, int pos)
3429 {
3430     loopi(pos) if(!parselist(s)) return newstring("");
3431     const char *start, *end, *qstart;
3432     return parselist(s, start, end, qstart) ? listelem(start, end, qstart) : newstring("");
3433 }
3434 
listlen(const char * s)3435 int listlen(const char *s)
3436 {
3437     int n = 0;
3438     while(parselist(s)) n++;
3439     return n;
3440 }
3441 ICOMMAND(listlen, "s", (char *s), intret(listlen(s)));
3442 
at(tagval * args,int numargs)3443 void at(tagval *args, int numargs)
3444 {
3445     if(!numargs) return;
3446     const char *start = args[0].getstr(), *end = start + strlen(start), *qstart = "";
3447     for(int i = 1; i < numargs; i++)
3448     {
3449         const char *list = start;
3450         int pos = args[i].getint();
3451         for(; pos > 0; pos--) if(!parselist(list)) break;
3452         if(pos > 0 || !parselist(list, start, end, qstart)) start = end = qstart = "";
3453     }
3454     commandret->setstr(listelem(start, end, qstart));
3455 }
3456 COMMAND(at, "si1V");
3457 
substr(char * s,int * start,int * count,int * numargs)3458 void substr(char *s, int *start, int *count, int *numargs)
3459 {
3460     int len = strlen(s), offset = clamp(*start, 0, len);
3461     commandret->setstr(newstring(&s[offset], *numargs >= 3 ? clamp(*count, 0, len - offset) : len - offset));
3462 }
3463 COMMAND(substr, "siiN");
3464 
sublist(const char * s,int * skip,int * count,int * numargs)3465 void sublist(const char *s, int *skip, int *count, int *numargs)
3466 {
3467     int offset = max(*skip, 0), len = *numargs >= 3 ? max(*count, 0) : -1;
3468     loopi(offset) if(!parselist(s)) break;
3469     if(len < 0) { if(offset > 0) skiplist(s); commandret->setstr(newstring(s)); return; }
3470     const char *list = s, *start, *end, *qstart, *qend = s;
3471     if(len > 0 && parselist(s, start, end, list, qend)) while(--len > 0 && parselist(s, start, end, qstart, qend));
3472     commandret->setstr(newstring(list, qend - list));
3473 }
3474 COMMAND(sublist, "siiN");
3475 
3476 ICOMMAND(stripcolors, "s", (char *s),
3477 {
3478     int len = strlen(s);
3479     char *d = newstring(len);
3480     filtertext(d, s, true, len);
3481     stringret(d);
3482 });
3483 
setiter(ident & id,char * val,identstack & stack)3484 static inline void setiter(ident &id, char *val, identstack &stack)
3485 {
3486     if(id.stack == &stack)
3487     {
3488         if(id.valtype == VAL_STR) delete[] id.val.s;
3489         else id.valtype = VAL_STR;
3490         cleancode(id);
3491         id.val.s = val;
3492     }
3493     else
3494     {
3495         tagval t;
3496         t.setstr(val);
3497         pusharg(id, t, stack);
3498         id.flags &= ~IDF_UNKNOWN;
3499     }
3500 }
3501 
listfind(ident * id,const char * list,const uint * body)3502 void listfind(ident *id, const char *list, const uint *body)
3503 {
3504     if(id->type!=ID_ALIAS) { intret(-1); return; }
3505     identstack stack;
3506     int n = -1;
3507     for(const char *s = list, *start, *end; parselist(s, start, end);)
3508     {
3509         ++n;
3510         setiter(*id, newstring(start, end-start), stack);
3511         if(executebool(body)) { intret(n); goto found; }
3512     }
3513     intret(-1);
3514 found:
3515     if(n >= 0) poparg(*id);
3516 }
3517 COMMAND(listfind, "rse");
3518 
listassoc(ident * id,const char * list,const uint * body)3519 void listassoc(ident *id, const char *list, const uint *body)
3520 {
3521     if(id->type!=ID_ALIAS) return;
3522     identstack stack;
3523     int n = -1;
3524     for(const char *s = list, *start, *end, *qstart; parselist(s, start, end);)
3525     {
3526         ++n;
3527         setiter(*id, newstring(start, end-start), stack);
3528         if(executebool(body)) { if(parselist(s, start, end, qstart)) stringret(listelem(start, end, qstart)); break; }
3529         if(!parselist(s)) break;
3530     }
3531     if(n >= 0) poparg(*id);
3532 }
3533 COMMAND(listassoc, "rse");
3534 
3535 #define LISTFIND(name, fmt, type, init, cmp) \
3536     ICOMMAND(name, "s" fmt "i", (char *list, type *val, int *skip), \
3537     { \
3538         int n = 0; \
3539         init; \
3540         for(const char *s = list, *start, *end, *qstart; parselist(s, start, end, qstart); n++) \
3541         { \
3542             if(cmp) { intret(n); return; } \
3543             loopi(*skip) { if(!parselist(s)) goto notfound; n++; } \
3544         } \
3545     notfound: \
3546         intret(-1); \
3547     });
3548 LISTFIND(listfind=, "i", int, , parseint(start) == *val);
3549 LISTFIND(listfind=f, "f", float, , parsefloat(start) == *val);
3550 LISTFIND(listfind=s, "s", char, int len = (int)strlen(val), int(end-start) == len && !memcmp(start, val, len));
3551 
3552 #define LISTASSOC(name, fmt, type, init, cmp) \
3553     ICOMMAND(name, "s" fmt, (char *list, type *val), \
3554     { \
3555         init; \
3556         for(const char *s = list, *start, *end, *qstart; parselist(s, start, end);) \
3557         { \
3558             if(cmp) { if(parselist(s, start, end, qstart)) stringret(listelem(start, end, qstart)); return; } \
3559             if(!parselist(s)) break; \
3560         } \
3561     });
3562 LISTASSOC(listassoc=, "i", int, , parseint(start) == *val);
3563 LISTASSOC(listassoc=f, "f", float, , parsefloat(start) == *val);
3564 LISTASSOC(listassoc=s, "s", char, int len = (int)strlen(val), int(end-start) == len && !memcmp(start, val, len));
3565 
looplist(ident * id,const char * list,const uint * body)3566 void looplist(ident *id, const char *list, const uint *body)
3567 {
3568     if(id->type!=ID_ALIAS) return;
3569     identstack stack;
3570     int n = 0;
3571     for(const char *s = list, *start, *end, *qstart; parselist(s, start, end, qstart); n++)
3572     {
3573         setiter(*id, listelem(start, end, qstart), stack);
3574         execute(body);
3575     }
3576     if(n) poparg(*id);
3577 }
3578 COMMAND(looplist, "rse");
3579 
looplist2(ident * id,ident * id2,const char * list,const uint * body)3580 void looplist2(ident *id, ident *id2, const char *list, const uint *body)
3581 {
3582     if(id->type!=ID_ALIAS || id2->type!=ID_ALIAS) return;
3583     identstack stack, stack2;
3584     int n = 0;
3585     for(const char *s = list, *start, *end, *qstart; parselist(s, start, end, qstart); n += 2)
3586     {
3587         setiter(*id, listelem(start, end, qstart), stack);
3588         setiter(*id2, parselist(s, start, end, qstart) ? listelem(start, end, qstart) : newstring(""), stack2);
3589         execute(body);
3590     }
3591     if(n) { poparg(*id); poparg(*id2); }
3592 }
3593 COMMAND(looplist2, "rrse");
3594 
looplist3(ident * id,ident * id2,ident * id3,const char * list,const uint * body)3595 void looplist3(ident *id, ident *id2, ident *id3, const char *list, const uint *body)
3596 {
3597     if(id->type!=ID_ALIAS || id2->type!=ID_ALIAS || id3->type!=ID_ALIAS) return;
3598     identstack stack, stack2, stack3;
3599     int n = 0;
3600     for(const char *s = list, *start, *end, *qstart; parselist(s, start, end, qstart); n += 3)
3601     {
3602         setiter(*id, listelem(start, end, qstart), stack);
3603         setiter(*id2, parselist(s, start, end, qstart) ? listelem(start, end, qstart) : newstring(""), stack2);
3604         setiter(*id3, parselist(s, start, end, qstart) ? listelem(start, end, qstart) : newstring(""), stack3);
3605         execute(body);
3606     }
3607     if(n) { poparg(*id); poparg(*id2); poparg(*id3); }
3608 }
3609 COMMAND(looplist3, "rrrse");
3610 
looplistconc(ident * id,const char * list,const uint * body,bool space)3611 void looplistconc(ident *id, const char *list, const uint *body, bool space)
3612 {
3613     if(id->type!=ID_ALIAS) return;
3614     identstack stack;
3615     vector<char> r;
3616     int n = 0;
3617     for(const char *s = list, *start, *end, *qstart; parselist(s, start, end, qstart); n++)
3618     {
3619         char *val = listelem(start, end, qstart);
3620         setiter(*id, val, stack);
3621 
3622         if(n && space) r.add(' ');
3623 
3624         tagval v;
3625         executeret(body, v);
3626         const char *vstr = v.getstr();
3627         int len = strlen(vstr);
3628         r.put(vstr, len);
3629         freearg(v);
3630     }
3631     if(n) poparg(*id);
3632     r.add('\0');
3633     commandret->setstr(r.disown());
3634 }
3635 ICOMMAND(looplistconcat, "rse", (ident *id, char *list, uint *body), looplistconc(id, list, body, true));
3636 ICOMMAND(looplistconcatword, "rse", (ident *id, char *list, uint *body), looplistconc(id, list, body, false));
3637 
listfilter(ident * id,const char * list,const uint * body)3638 void listfilter(ident *id, const char *list, const uint *body)
3639 {
3640     if(id->type!=ID_ALIAS) return;
3641     identstack stack;
3642     vector<char> r;
3643     int n = 0;
3644     for(const char *s = list, *start, *end, *qstart, *qend; parselist(s, start, end, qstart, qend); n++)
3645     {
3646         char *val = newstring(start, end-start);
3647         setiter(*id, val, stack);
3648 
3649         if(executebool(body))
3650         {
3651             if(r.length()) r.add(' ');
3652             r.put(qstart, qend-qstart);
3653         }
3654     }
3655     if(n) poparg(*id);
3656     r.add('\0');
3657     commandret->setstr(r.disown());
3658 }
3659 COMMAND(listfilter, "rse");
3660 
listcount(ident * id,const char * list,const uint * body)3661 void listcount(ident *id, const char *list, const uint *body)
3662 {
3663     if(id->type!=ID_ALIAS) return;
3664     identstack stack;
3665     int n = 0, r = 0;
3666     for(const char *s = list, *start, *end; parselist(s, start, end); n++)
3667     {
3668         char *val = newstring(start, end-start);
3669         setiter(*id, val, stack);
3670         if(executebool(body)) r++;
3671     }
3672     if(n) poparg(*id);
3673     intret(r);
3674 }
3675 COMMAND(listcount, "rse");
3676 
prettylist(const char * s,const char * conj)3677 void prettylist(const char *s, const char *conj)
3678 {
3679     vector<char> p;
3680     const char *start, *end, *qstart;
3681     for(int len = listlen(s), n = 0; parselist(s, start, end, qstart); n++)
3682     {
3683         if(*qstart == '"') p.advance(unescapestring(p.reserve(end - start).buf, start, end));
3684         else p.put(start, end - start);
3685         if(n+1 < len)
3686         {
3687             if(len > 2 || !conj[0]) p.add(',');
3688             if(n+2 == len && conj[0])
3689             {
3690                 p.add(' ');
3691                 p.put(conj, strlen(conj));
3692             }
3693             p.add(' ');
3694         }
3695     }
3696     p.add('\0');
3697     commandret->setstr(p.disown());
3698 }
3699 COMMAND(prettylist, "ss");
3700 
listincludes(const char * list,const char * needle,int needlelen)3701 int listincludes(const char *list, const char *needle, int needlelen)
3702 {
3703     int offset = 0;
3704     for(const char *s = list, *start, *end; parselist(s, start, end);)
3705     {
3706         int len = end - start;
3707         if(needlelen == len && !strncmp(needle, start, len)) return offset;
3708         offset++;
3709     }
3710     return -1;
3711 }
3712 ICOMMAND(indexof, "ss", (char *list, char *elem), intret(listincludes(list, elem, strlen(elem))));
3713 
3714 #define LISTMERGECMD(name, init, iter, filter, dir) \
3715     ICOMMAND(name, "ss", (const char *list, const char *elems), \
3716     { \
3717         vector<char> p; \
3718         init; \
3719         for(const char *start, *end, *qstart, *qend; parselist(iter, start, end, qstart, qend);) \
3720         { \
3721             int len = end - start; \
3722             if(listincludes(filter, start, len) dir 0) \
3723             { \
3724                 if(!p.empty()) p.add(' '); \
3725                 p.put(qstart, qend-qstart); \
3726             } \
3727         } \
3728         p.add('\0'); \
3729         commandret->setstr(p.disown()); \
3730     })
3731 
3732 LISTMERGECMD(listdel, , list, elems, <);
3733 LISTMERGECMD(listintersect, , list, elems, >=);
3734 LISTMERGECMD(listunion, p.put(list, strlen(list)), elems, list, <);
3735 
listsplice(const char * s,const char * vals,int * skip,int * count,int * numargs)3736 void listsplice(const char *s, const char *vals, int *skip, int *count, int *numargs)
3737 {
3738     int offset = max(*skip, 0), len = *numargs >= 4 ? max(*count, 0) : -1;
3739     const char *list = s, *start, *end, *qstart, *qend = s;
3740     loopi(offset) if(!parselist(s, start, end, qstart, qend)) break;
3741     vector<char> p;
3742     if(qend > list) p.put(list, qend-list);
3743     if(*vals)
3744     {
3745         if(!p.empty()) p.add(' ');
3746         p.put(vals, strlen(vals));
3747     }
3748     while(len-- > 0) if(!parselist(s)) break;
3749     skiplist(s);
3750     switch(*s)
3751     {
3752         case '\0': case ')': case ']': break;
3753         default:
3754             if(!p.empty()) p.add(' ');
3755             p.put(s, strlen(s));
3756             break;
3757     }
3758     p.add('\0');
3759     commandret->setstr(p.disown());
3760 }
3761 COMMAND(listsplice, "ssiiN");
3762 
3763 ICOMMAND(loopfiles, "rsse", (ident *id, char *dir, char *ext, uint *body),
3764 {
3765     if(id->type!=ID_ALIAS) return;
3766     identstack stack;
3767     vector<char *> files;
3768     listfiles(dir, ext[0] ? ext : NULL, files);
3769     files.sort();
3770     files.uniquedeletearrays();
3771     loopv(files)
3772     {
3773         setiter(*id, files[i], stack);
3774         execute(body);
3775     }
3776     if(files.length()) poparg(*id);
3777 });
3778 
3779 ICOMMAND(findfile, "s", (char *name),
3780 {
3781     string fname;
3782     copystring(fname, name);
3783     path(fname);
3784     intret(fileexists(fname, "e") || findfile(fname, "e") ? 1 : 0);
3785 });
3786 
3787 struct sortitem
3788 {
3789     const char *str, *quotestart, *quoteend;
3790 
quotelengthsortitem3791     int quotelength() const { return int(quoteend-quotestart); }
3792 };
3793 
3794 struct sortfun
3795 {
3796     ident *x, *y;
3797     uint *body;
3798 
operator ()sortfun3799     bool operator()(const sortitem &xval, const sortitem &yval)
3800     {
3801         if(x->valtype != VAL_CSTR) x->valtype = VAL_CSTR;
3802         cleancode(*x);
3803         x->val.code = (const uint *)xval.str;
3804         if(y->valtype != VAL_CSTR) y->valtype = VAL_CSTR;
3805         cleancode(*y);
3806         y->val.code = (const uint *)yval.str;
3807         return executebool(body);
3808     }
3809 };
3810 
sortlist(char * list,ident * x,ident * y,uint * body,uint * unique)3811 void sortlist(char *list, ident *x, ident *y, uint *body, uint *unique)
3812 {
3813     if(x == y || x->type != ID_ALIAS || y->type != ID_ALIAS) return;
3814 
3815     vector<sortitem> items;
3816     int clen = strlen(list), total = 0;
3817     char *cstr = newstring(list, clen);
3818     const char *curlist = list, *start, *end, *quotestart, *quoteend;
3819     while(parselist(curlist, start, end, quotestart, quoteend))
3820     {
3821         cstr[end - list] = '\0';
3822         sortitem item = { &cstr[start - list], quotestart, quoteend };
3823         items.add(item);
3824         total += item.quotelength();
3825     }
3826 
3827     if(items.empty())
3828     {
3829         commandret->setstr(cstr);
3830         return;
3831     }
3832 
3833     identstack xstack, ystack;
3834     pusharg(*x, nullval, xstack); x->flags &= ~IDF_UNKNOWN;
3835     pusharg(*y, nullval, ystack); y->flags &= ~IDF_UNKNOWN;
3836 
3837     int totalunique = total, numunique = items.length();
3838     if(body)
3839     {
3840         sortfun f = { x, y, body };
3841         items.sort(f);
3842         if((*unique&CODE_OP_MASK) != CODE_EXIT)
3843         {
3844             f.body = unique;
3845             totalunique = items[0].quotelength();
3846             numunique = 1;
3847             for(int i = 1; i < items.length(); i++)
3848             {
3849                 sortitem &item = items[i];
3850                 if(f(items[i-1], item)) item.quotestart = NULL;
3851                 else { totalunique += item.quotelength(); numunique++; }
3852             }
3853         }
3854     }
3855     else
3856     {
3857         sortfun f = { x, y, unique };
3858         totalunique = items[0].quotelength();
3859         numunique = 1;
3860         for(int i = 1; i < items.length(); i++)
3861         {
3862             sortitem &item = items[i];
3863             loopj(i)
3864             {
3865                 sortitem &prev = items[j];
3866                 if(prev.quotestart && f(item, prev)) { item.quotestart = NULL; break; }
3867             }
3868             if(item.quotestart) { totalunique += item.quotelength(); numunique++; }
3869         }
3870     }
3871 
3872     poparg(*x);
3873     poparg(*y);
3874 
3875     char *sorted = cstr;
3876     int sortedlen = totalunique + max(numunique - 1, 0);
3877     if(clen < sortedlen)
3878     {
3879         delete[] cstr;
3880         sorted = newstring(sortedlen);
3881     }
3882 
3883     int offset = 0;
3884     loopv(items)
3885     {
3886         sortitem &item = items[i];
3887         if(!item.quotestart) continue;
3888         int len = item.quotelength();
3889         if(i) sorted[offset++] = ' ';
3890         memcpy(&sorted[offset], item.quotestart, len);
3891         offset += len;
3892     }
3893     sorted[offset] = '\0';
3894 
3895     commandret->setstr(sorted);
3896 }
3897 COMMAND(sortlist, "srree");
3898 ICOMMAND(uniquelist, "srre", (char *list, ident *x, ident *y, uint *body), sortlist(list, x, y, NULL, body));
3899 
3900 #define MATHCMD(name, fmt, type, op, initval, unaryop) \
3901     ICOMMANDS(name, #fmt "1V", (tagval *args, int numargs), \
3902     { \
3903         type val; \
3904         if(numargs >= 2) \
3905         { \
3906             val = args[0].fmt; \
3907             type val2 = args[1].fmt; \
3908             op; \
3909             for(int i = 2; i < numargs; i++) { val2 = args[i].fmt; op; } \
3910         } \
3911         else { val = numargs > 0 ? args[0].fmt : initval; unaryop; } \
3912         type##ret(val); \
3913     })
3914 #define MATHICMDN(name, op, initval, unaryop) MATHCMD(#name, i, int, val = val op val2, initval, unaryop)
3915 #define MATHICMD(name, initval, unaryop) MATHICMDN(name, name, initval, unaryop)
3916 #define MATHFCMDN(name, op, initval, unaryop) MATHCMD(#name "f", f, float, val = val op val2, initval, unaryop)
3917 #define MATHFCMD(name, initval, unaryop) MATHFCMDN(name, name, initval, unaryop)
3918 
3919 #define CMPCMD(name, fmt, type, op) \
3920     ICOMMANDS(name, #fmt "1V", (tagval *args, int numargs), \
3921     { \
3922         bool val; \
3923         if(numargs >= 2) \
3924         { \
3925             val = args[0].fmt op args[1].fmt; \
3926             for(int i = 2; i < numargs && val; i++) val = args[i-1].fmt op args[i].fmt; \
3927         } \
3928         else val = (numargs > 0 ? args[0].fmt : 0) op 0; \
3929         intret(int(val)); \
3930     })
3931 #define CMPICMDN(name, op) CMPCMD(#name, i, int, op)
3932 #define CMPICMD(name) CMPICMDN(name, name)
3933 #define CMPFCMDN(name, op) CMPCMD(#name "f", f, float, op)
3934 #define CMPFCMD(name) CMPFCMDN(name, name)
3935 
3936 MATHICMD(+, 0, );
3937 MATHICMD(*, 1, );
3938 MATHICMD(-, 0, val = -val);
3939 CMPICMDN(=, ==);
3940 CMPICMD(!=);
3941 CMPICMD(<);
3942 CMPICMD(>);
3943 CMPICMD(<=);
3944 CMPICMD(>=);
3945 MATHICMD(^, 0, val = ~val);
3946 MATHICMDN(~, ^, 0, val = ~val);
3947 MATHICMD(&, 0, );
3948 MATHICMD(|, 0, );
3949 MATHICMD(^~, 0, );
3950 MATHICMD(&~, 0, );
3951 MATHICMD(|~, 0, );
3952 MATHICMD(<<, 0, );
3953 MATHICMD(>>, 0, );
3954 
3955 MATHFCMD(+, 0, );
3956 MATHFCMD(*, 1, );
3957 MATHFCMD(-, 0, val = -val);
3958 CMPFCMDN(=, ==);
3959 CMPFCMD(!=);
3960 CMPFCMD(<);
3961 CMPFCMD(>);
3962 CMPFCMD(<=);
3963 CMPFCMD(>=);
3964 
3965 ICOMMANDK(!, ID_NOT, "t", (tagval *a), intret(getbool(*a) ? 0 : 1));
3966 ICOMMANDK(&&, ID_AND, "E1V", (tagval *args, int numargs),
3967 {
3968     if(!numargs) intret(1);
3969     else loopi(numargs)
3970     {
3971         if(i) freearg(*commandret);
3972         if(args[i].type == VAL_CODE) executeret(args[i].code, *commandret);
3973         else *commandret = args[i];
3974         if(!getbool(*commandret)) break;
3975     }
3976 });
3977 ICOMMANDK(||, ID_OR, "E1V", (tagval *args, int numargs),
3978 {
3979     if(!numargs) intret(0);
3980     else loopi(numargs)
3981     {
3982         if(i) freearg(*commandret);
3983         if(args[i].type == VAL_CODE) executeret(args[i].code, *commandret);
3984         else *commandret = args[i];
3985         if(getbool(*commandret)) break;
3986     }
3987 });
3988 
3989 
3990 #define DIVCMD(name, fmt, type, op) MATHCMD(#name, fmt, type, { if(val2) op; else val = 0; }, 0, )
3991 
3992 DIVCMD(div, i, int, val /= val2);
3993 DIVCMD(mod, i, int, val %= val2);
3994 DIVCMD(divf, f, float, val /= val2);
3995 DIVCMD(modf, f, float, val = fmod(val, val2));
3996 MATHCMD("pow", f, float, val = pow(val, val2), 0, );
3997 
3998 ICOMMAND(sin, "f", (float *a), floatret(sin(*a*RAD)));
3999 ICOMMAND(cos, "f", (float *a), floatret(cos(*a*RAD)));
4000 ICOMMAND(tan, "f", (float *a), floatret(tan(*a*RAD)));
4001 ICOMMAND(asin, "f", (float *a), floatret(asin(*a)/RAD));
4002 ICOMMAND(acos, "f", (float *a), floatret(acos(*a)/RAD));
4003 ICOMMAND(atan, "f", (float *a), floatret(atan(*a)/RAD));
4004 ICOMMAND(sqrt, "f", (float *a), floatret(sqrt(*a)));
4005 ICOMMAND(loge, "f", (float *a), floatret(log(*a)));
4006 ICOMMAND(log2, "f", (float *a), floatret(log(*a)/M_LN2));
4007 ICOMMAND(log10, "f", (float *a), floatret(log10(*a)));
4008 ICOMMAND(exp, "f", (float *a), floatret(exp(*a)));
4009 
4010 #define MINMAXCMD(name, fmt, type, op) \
4011     ICOMMAND(name, #fmt "1V", (tagval *args, int numargs), \
4012     { \
4013         type val = numargs > 0 ? args[0].fmt : 0; \
4014         for(int i = 1; i < numargs; i++) val = op(val, args[i].fmt); \
4015         type##ret(val); \
4016     })
4017 
4018 MINMAXCMD(min, i, int, min);
4019 MINMAXCMD(max, i, int, max);
4020 MINMAXCMD(minf, f, float, min);
4021 MINMAXCMD(maxf, f, float, max);
4022 
4023 ICOMMAND(abs, "i", (int *n), intret(abs(*n)));
4024 ICOMMAND(absf, "f", (float *n), floatret(fabs(*n)));
4025 
4026 ICOMMAND(floor, "f", (float *n), floatret(floor(*n)));
4027 ICOMMAND(ceil, "f", (float *n), floatret(ceil(*n)));
4028 ICOMMAND(round, "f", (float *n), floatret(floor(*n + 0.5)));
4029 
4030 ICOMMAND(cond, "ee2V", (tagval *args, int numargs),
4031 {
4032     for(int i = 0; i < numargs; i += 2)
4033     {
4034         if(i+1 < numargs)
4035         {
4036             if(executebool(args[i].code))
4037             {
4038                 executeret(args[i+1].code, *commandret);
4039                 break;
4040             }
4041         }
4042         else
4043         {
4044             executeret(args[i].code, *commandret);
4045             break;
4046         }
4047     }
4048 });
4049 
4050 #define CASECOMMAND(name, fmt, type, acc, compare) \
4051     ICOMMAND(name, fmt "te2V", (tagval *args, int numargs), \
4052     { \
4053         type val = acc; \
4054         int i; \
4055         for(i = 1; i+1 < numargs; i += 2) \
4056         { \
4057             if(compare) \
4058             { \
4059                 executeret(args[i+1].code, *commandret); \
4060                 return; \
4061             } \
4062         } \
4063     })
4064 
4065 CASECOMMAND(case, "i", int, args[0].getint(), args[i].type == VAL_NULL || args[i].getint() == val);
4066 CASECOMMAND(casef, "f", float, args[0].getfloat(), args[i].type == VAL_NULL || args[i].getfloat() == val);
4067 CASECOMMAND(cases, "s", const char *, args[0].getstr(), args[i].type == VAL_NULL || !strcmp(args[i].getstr(), val));
4068 
4069 ICOMMAND(rnd, "ii", (int *a, int *b), intret(*a - *b > 0 ? rnd(*a - *b) + *b : *b));
4070 
4071 ICOMMAND(tohex, "ii", (int *n, int *p),
4072 {
4073     const int len = 20;
4074     char *buf = newstring(len);
4075     nformatstring(buf, len, "0x%.*X", max(*p, 1), *n);
4076     stringret(buf);
4077 });
4078 
4079 #define CMPSCMD(name, op) \
4080     ICOMMAND(name, "s1V", (tagval *args, int numargs), \
4081     { \
4082         bool val; \
4083         if(numargs >= 2) \
4084         { \
4085             val = strcmp(args[0].s, args[1].s) op 0; \
4086             for(int i = 2; i < numargs && val; i++) val = strcmp(args[i-1].s, args[i].s) op 0; \
4087         } \
4088         else val = (numargs > 0 ? args[0].s[0] : 0) op 0; \
4089         intret(int(val)); \
4090     })
4091 
4092 CMPSCMD(strcmp, ==);
4093 CMPSCMD(=s, ==);
4094 CMPSCMD(!=s, !=);
4095 CMPSCMD(<s, <);
4096 CMPSCMD(>s, >);
4097 CMPSCMD(<=s, <=);
4098 CMPSCMD(>=s, >=);
4099 
4100 ICOMMAND(echo, "C", (char *s), conoutf("\f1%s", s));
4101 ICOMMAND(error, "C", (char *s), conoutf(CON_ERROR, "%s", s));
4102 ICOMMAND(strstr, "ss", (char *a, char *b), { char *s = strstr(a, b); intret(s ? s-a : -1); });
4103 ICOMMAND(strlen, "s", (char *s), intret(strlen(s)));
4104 ICOMMAND(strcode, "si", (char *s, int *i), intret(*i > 0 ? (memchr(s, 0, *i) ? 0 : uchar(s[*i])) : uchar(s[0])));
4105 ICOMMAND(codestr, "i", (int *i), { char *s = newstring(1); s[0] = char(*i); s[1] = '\0'; stringret(s); });
4106 ICOMMAND(struni, "si", (char *s, int *i), intret(*i > 0 ? (memchr(s, 0, *i) ? 0 : cube2uni(s[*i])) : cube2uni(s[0])));
4107 ICOMMAND(unistr, "i", (int *i), { char *s = newstring(1); s[0] = uni2cube(*i); s[1] = '\0'; stringret(s); });
4108 
4109 #define STRMAPCOMMAND(name, map) \
4110     ICOMMAND(name, "s", (char *s), \
4111     { \
4112         int len = strlen(s); \
4113         char *m = newstring(len); \
4114         loopi(len) m[i] = map(s[i]); \
4115         m[len] = '\0'; \
4116         stringret(m); \
4117     })
4118 
4119 STRMAPCOMMAND(strlower, cubelower);
4120 STRMAPCOMMAND(strupper, cubeupper);
4121 
strreplace(const char * s,const char * oldval,const char * newval,const char * newval2)4122 char *strreplace(const char *s, const char *oldval, const char *newval, const char *newval2)
4123 {
4124     vector<char> buf;
4125 
4126     int oldlen = strlen(oldval);
4127     if(!oldlen) return newstring(s);
4128     for(int i = 0;; i++)
4129     {
4130         const char *found = strstr(s, oldval);
4131         if(found)
4132         {
4133             while(s < found) buf.add(*s++);
4134             for(const char *n = i&1 ? newval2 : newval; *n; n++) buf.add(*n);
4135             s = found + oldlen;
4136         }
4137         else
4138         {
4139             while(*s) buf.add(*s++);
4140             buf.add('\0');
4141             return newstring(buf.getbuf(), buf.length());
4142         }
4143     }
4144 }
4145 
4146 ICOMMAND(strreplace, "ssss", (char *s, char *o, char *n, char *n2), commandret->setstr(strreplace(s, o, n, n2[0] ? n2 : n)));
4147 
4148 #ifndef STANDALONE
4149 ICOMMAND(getmillis, "i", (int *total), intret(*total ? totalmillis : lastmillis));
4150 
4151 struct sleepcmd
4152 {
4153     int delay, millis, flags;
4154     char *command;
4155 };
4156 vector<sleepcmd> sleepcmds;
4157 
addsleep(int * msec,char * cmd)4158 void addsleep(int *msec, char *cmd)
4159 {
4160     sleepcmd &s = sleepcmds.add();
4161     s.delay = max(*msec, 1);
4162     s.millis = lastmillis;
4163     s.command = newstring(cmd);
4164     s.flags = identflags;
4165 }
4166 
4167 COMMANDN(sleep, addsleep, "is");
4168 
checksleep(int millis)4169 void checksleep(int millis)
4170 {
4171     loopv(sleepcmds)
4172     {
4173         sleepcmd &s = sleepcmds[i];
4174         if(millis - s.millis >= s.delay)
4175         {
4176             char *cmd = s.command; // execute might create more sleep commands
4177             s.command = NULL;
4178             int oldflags = identflags;
4179             identflags = s.flags;
4180             execute(cmd);
4181             identflags = oldflags;
4182             delete[] cmd;
4183             if(sleepcmds.inrange(i) && !sleepcmds[i].command) sleepcmds.remove(i--);
4184         }
4185     }
4186 }
4187 
clearsleep(bool clearoverrides)4188 void clearsleep(bool clearoverrides)
4189 {
4190     int len = 0;
4191     loopv(sleepcmds) if(sleepcmds[i].command)
4192     {
4193         if(clearoverrides && !(sleepcmds[i].flags&IDF_OVERRIDDEN)) sleepcmds[len++] = sleepcmds[i];
4194         else delete[] sleepcmds[i].command;
4195     }
4196     sleepcmds.shrink(len);
4197 }
4198 
clearsleep_(int * clearoverrides)4199 void clearsleep_(int *clearoverrides)
4200 {
4201     clearsleep(*clearoverrides!=0 || identflags&IDF_OVERRIDDEN);
4202 }
4203 
4204 COMMANDN(clearsleep, clearsleep_, "i");
4205 #endif
4206 
4207