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