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 *"estart = listquotestart, const char *"eend = 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 = { ¯os[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)¬ypes)) && (!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