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