Initial sauer
[SauerbratenRemote.git] / src / engine / command.cpp
blobeab9a3f1f96c7a1b42297962fa0662e65eb14c06
1 // command.cpp: implements the parsing and execution of a tiny script language which
2 // is largely backwards compatible with the quake console language.
4 #include "pch.h"
5 #include "engine.h"
7 void itoa(char *s, int i) { s_sprintf(s)("%d", i); }
8 char *exchangestr(char *o, const char *n) { delete[] o; return newstring(n); }
10 typedef hashtable<const char *, ident> identtable;
12 identtable *idents = NULL; // contains ALL vars/commands/aliases
14 bool overrideidents = false, persistidents = true;
16 void clearstack(ident &id)
18 identstack *stack = id._stack;
19 while(stack)
21 delete[] stack->action;
22 identstack *tmp = stack;
23 stack = stack->next;
24 delete tmp;
26 id._stack = NULL;
29 void clear_command()
31 enumerate(*idents, ident, i, if(i._type==ID_ALIAS) { DELETEA(i._name); DELETEA(i._action); if(i._stack) clearstack(i); });
32 if(idents) idents->clear();
35 void clearoverrides()
37 enumerate(*idents, ident, i,
38 if(i._override!=NO_OVERRIDE)
40 switch(i._type)
42 case ID_ALIAS:
43 if(i._action[0]) i._action = exchangestr(i._action, "");
44 break;
45 case ID_VAR:
46 *i._storage = i._override;
47 i.changed();
48 break;
50 i._override = NO_OVERRIDE;
51 });
54 void pushident(ident &id, char *val)
56 if(id._type != ID_ALIAS) return;
57 identstack *stack = new identstack;
58 stack->action = id._isexecuting==id._action ? newstring(id._action) : id._action;
59 stack->next = id._stack;
60 id._stack = stack;
61 id._action = val;
64 void popident(ident &id)
66 if(id._type != ID_ALIAS || !id._stack) return;
67 if(id._action != id._isexecuting) delete[] id._action;
68 identstack *stack = id._stack;
69 id._action = stack->action;
70 id._stack = stack->next;
71 delete stack;
74 void pusha(char *name, char *action)
76 ident *id = idents->access(name);
77 if(!id)
79 name = newstring(name);
80 ident init(ID_ALIAS, name, newstring(""), persistidents);
81 id = idents->access(name, &init);
83 pushident(*id, action);
86 void push(char *name, char *action)
88 pusha(name, newstring(action));
91 void pop(char *name)
93 ident *id = idents->access(name);
94 if(id) popident(*id);
97 COMMAND(push, "ss");
98 COMMAND(pop, "s");
100 void aliasa(const char *name, char *action)
102 ident *b = idents->access(name);
103 if(!b)
105 ident b(ID_ALIAS, newstring(name), action, persistidents);
106 if(overrideidents) b._override = OVERRIDDEN;
107 idents->access(b._name, &b);
109 else if(b->_type != ID_ALIAS)
111 conoutf("cannot redefine builtin %s with an alias", name);
112 delete[] action;
114 else
116 if(b->_action != b->_isexecuting) delete[] b->_action;
117 b->_action = action;
118 if(overrideidents) b->_override = OVERRIDDEN;
119 else
121 if(b->_override != NO_OVERRIDE) b->_override = NO_OVERRIDE;
122 if(b->_persist != persistidents) b->_persist = persistidents;
127 void alias(const char *name, const char *action) { aliasa(name, newstring(action)); }
129 COMMAND(alias, "ss");
131 // variable's and commands are registered through globals, see cube.h
133 int variable(const char *name, int min, int cur, int max, int *storage, void (*fun)(), bool persist)
135 if(!idents) idents = new identtable;
136 ident v(ID_VAR, name, min, cur, max, storage, (void *)fun, persist);
137 idents->access(name, &v);
138 return cur;
141 #define GETVAR(id, name, retval) \
142 ident *id = idents->access(name); \
143 if(!id || id->_type!=ID_VAR) return retval;
144 void setvar(const char *name, int i, bool dofunc)
146 GETVAR(id, name, );
147 *id->_storage = i;
148 if(dofunc) id->changed();
150 int getvar(const char *name)
152 GETVAR(id, name, 0);
153 return *id->_storage;
155 int getvarmin(const char *name)
157 GETVAR(id, name, 0);
158 return id->_min;
160 int getvarmax(const char *name)
162 GETVAR(id, name, 0);
163 return id->_max;
165 bool identexists(const char *name) { return idents->access(name)!=NULL; }
166 ident *getident(const char *name) { return idents->access(name); }
168 const char *getalias(const char *name)
170 ident *i = idents->access(name);
171 return i && i->_type==ID_ALIAS ? i->_action : "";
174 bool addcommand(const char *name, void (*fun)(), const char *narg)
176 if(!idents) idents = new identtable;
177 ident c(ID_COMMAND, name, narg, (void *)fun);
178 idents->access(name, &c);
179 return false;
182 void addident(const char *name, ident *id)
184 if(!idents) idents = new identtable;
185 idents->access(name, id);
188 static vector<vector<char> *> wordbufs;
189 static int bufnest = 0;
191 char *parseexp(const char *&p, int right);
193 void parsemacro(const char *&p, int level, vector<char> &wordbuf)
195 int escape = 1;
196 while(*p=='@') p++, escape++;
197 if(level > escape)
199 while(escape--) wordbuf.add('@');
200 return;
202 if(*p=='(')
204 char *ret = parseexp(p, ')');
205 if(ret)
207 for(char *sub = ret; *sub; ) wordbuf.add(*sub++);
208 delete[] ret;
210 return;
212 static vector<char> ident;
213 ident.setsizenodelete(0);
214 while(isalnum(*p) || *p=='_') ident.add(*p++);
215 ident.add(0);
216 const char *alias = getalias(ident.getbuf());
217 while(*alias) wordbuf.add(*alias++);
220 char *parseexp(const char *&p, int right) // parse any nested set of () or []
222 if(bufnest++>=wordbufs.length()) wordbufs.add(new vector<char>);
223 vector<char> &wordbuf = *wordbufs[bufnest-1];
224 int left = *p++;
225 for(int brak = 1; brak; )
227 int c = *p++;
228 if(c=='\r') continue; // hack
229 if(left=='[' && c=='@')
231 parsemacro(p, brak, wordbuf);
232 continue;
234 if(c=='\"')
236 wordbuf.add(c);
237 const char *end = p+strcspn(p, "\"\r\n\0");
238 while(p < end) wordbuf.add(*p++);
239 if(*p=='\"') wordbuf.add(*p++);
240 continue;
242 if(c=='/' && *p=='/')
244 p += strcspn(p, "\n\0");
245 continue;
248 if(c==left) brak++;
249 else if(c==right) brak--;
250 else if(!c)
252 p--;
253 conoutf("missing \"%c\"", right);
254 wordbuf.setsize(0);
255 bufnest--;
256 return NULL;
258 wordbuf.add(c);
260 wordbuf.pop();
261 char *s;
262 if(left=='(')
264 wordbuf.add(0);
265 char *ret = executeret(wordbuf.getbuf()); // evaluate () exps directly, and substitute result
266 wordbuf.pop();
267 s = ret ? ret : newstring("");
269 else
271 s = newstring(wordbuf.getbuf(), wordbuf.length());
273 wordbuf.setsize(0);
274 bufnest--;
275 return s;
278 char *lookup(char *n) // find value of ident referenced with $ in exp
280 ident *id = idents->access(n+1);
281 if(id) switch(id->_type)
283 case ID_VAR: { string t; itoa(t, *(id->_storage)); return exchangestr(n, t); }
284 case ID_ALIAS: return exchangestr(n, id->_action);
286 conoutf("unknown alias lookup: %s", n+1);
287 return n;
290 char *parseword(const char *&p) // parse single argument, including expressions
292 for(;;)
294 p += strspn(p, " \t\r");
295 if(p[0]!='/' || p[1]!='/') break;
296 p += strcspn(p, "\n\0");
298 if(*p=='\"')
300 p++;
301 const char *word = p;
302 p += strcspn(p, "\"\r\n\0");
303 char *s = newstring(word, p-word);
304 if(*p=='\"') p++;
305 return s;
307 if(*p=='(') return parseexp(p, ')');
308 if(*p=='[') return parseexp(p, ']');
309 const char *word = p;
310 for(;;)
312 p += strcspn(p, "/; \t\r\n\0");
313 if(p[0]!='/' || p[1]=='/') break;
314 else if(p[1]=='\0') { p++; break; }
315 p += 2;
317 if(p-word==0) return NULL;
318 char *s = newstring(word, p-word);
319 if(*s=='$') return lookup(s); // substitute variables
320 return s;
323 char *conc(char **w, int n, bool space)
325 int len = space ? max(n-1, 0) : 0;
326 loopj(n) len += (int)strlen(w[j]);
327 char *r = newstring("", len);
328 loopi(n)
330 strcat(r, w[i]); // make string-list out of all arguments
331 if(i==n-1) break;
332 if(space) strcat(r, " ");
334 return r;
337 VARN(numargs, _numargs, 0, 0, 25);
339 #define parseint(s) strtol((s), NULL, 0)
341 char *commandret = NULL;
343 extern const char *addreleaseaction(const char *s);
345 char *executeret(const char *p) // all evaluation happens here, recursively
347 const int MAXWORDS = 25; // limit, remove
348 char *w[MAXWORDS];
349 char *retval = NULL;
350 #define setretval(v) { char *rv = v; if(rv) retval = rv; commandret = NULL; }
351 for(bool cont = true; cont;) // for each ; seperated statement
353 int numargs = MAXWORDS;
354 loopi(MAXWORDS) // collect all argument values
356 w[i] = (char *)"";
357 if(i>numargs) continue;
358 char *s = parseword(p); // parse and evaluate exps
359 if(s) w[i] = s;
360 else numargs = i;
363 p += strcspn(p, ";\n\0");
364 cont = *p++!=0; // more statements if this isn't the end of the string
365 char *c = w[0];
366 if(*c=='/') c++; // strip irc-style command prefix
367 if(!*c) continue; // empty statement
369 DELETEA(retval);
371 if(w[1][0]=='=' && !w[1][1])
373 aliasa(c, numargs>2 ? w[2] : newstring(""));
374 w[2] = NULL;
376 else
378 ident *id = idents->access(c);
379 if(!id)
381 if(!parseint(c) && *c!='0')
382 conoutf("unknown command: %s", c);
383 setretval(newstring(c));
385 else switch(id->_type)
387 case ID_CCOMMAND:
388 case ID_COMMAND: // game defined commands
390 SAUERBRATEN_COMMAND_ENTRY(c, w[1], w[2], w[3]);
391 void *v[MAXWORDS];
392 union
394 int i;
395 float f;
396 } nstor[MAXWORDS];
397 int n = 0, wn = 0;
398 char *cargs = NULL;
399 if(id->_type==ID_CCOMMAND) v[n++] = id->self;
400 for(const char *a = id->_narg; *a; a++) switch(*a)
402 case 's': v[n] = w[++wn]; n++; break;
403 case 'i': nstor[n].i = parseint(w[++wn]); v[n] = &nstor[n].i; n++; break;
404 case 'f': nstor[n].f = atof(w[++wn]); v[n] = &nstor[n].f; n++; break;
405 case 'D': nstor[n].i = addreleaseaction(id->_name) ? 1 : 0; v[n] = &nstor[n].i; n++; break;
406 case 'V': v[n++] = w+1; nstor[n].i = numargs-1; v[n] = &nstor[n].i; n++; break;
407 case 'C': if(!cargs) cargs = conc(w+1, numargs-1, true); v[n++] = cargs; break;
408 default: fatal("builtin declared with illegal type");
410 switch(n)
412 case 0: ((void (__cdecl *)() )id->_fun)(); break;
413 case 1: ((void (__cdecl *)(void *) )id->_fun)(v[0]); break;
414 case 2: ((void (__cdecl *)(void *, void *) )id->_fun)(v[0], v[1]); break;
415 case 3: ((void (__cdecl *)(void *, void *, void *) )id->_fun)(v[0], v[1], v[2]); break;
416 case 4: ((void (__cdecl *)(void *, void *, void *, void *) )id->_fun)(v[0], v[1], v[2], v[3]); break;
417 case 5: ((void (__cdecl *)(void *, void *, void *, void *, void *))id->_fun)(v[0], v[1], v[2], v[3], v[4]); break;
418 case 6: ((void (__cdecl *)(void *, void *, void *, void *, void *, void *))id->_fun)(v[0], v[1], v[2], v[3], v[4], v[5]); break;
419 case 7: ((void (__cdecl *)(void *, void *, void *, void *, void *, void *, void *))id->_fun)(v[0], v[1], v[2], v[3], v[4], v[5], v[6]); break;
420 case 8: ((void (__cdecl *)(void *, void *, void *, void *, void *, void *, void *, void *))id->_fun)(v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]); break;
421 default: fatal("builtin declared with too many args (use V?)");
423 if(cargs) delete[] cargs;
424 setretval(commandret);
425 SAUERBRATEN_COMMAND_RETURN(c);
426 break;
429 case ID_VAR: // game defined variables
430 SAUERBRATEN_VAR_ENTRY(c, w[1]);
431 if(!w[1][0]) conoutf("%s = %d", c, *id->_storage); // var with no value just prints its current value
432 else if(id->_min>id->_max) conoutf("variable %s is read-only", id->_name);
433 else
435 if(overrideidents)
437 if(id->_persist)
439 conoutf("cannot override persistent variable %s", id->_name);
440 break;
442 if(id->_override==NO_OVERRIDE) id->_override = *id->_storage;
444 else if(id->_override!=NO_OVERRIDE) id->_override = NO_OVERRIDE;
445 int i1 = parseint(w[1]);
446 if(i1<id->_min || i1>id->_max)
448 i1 = i1<id->_min ? id->_min : id->_max; // clamp to valid range
449 conoutf("valid range for %s is %d..%d", id->_name, id->_min, id->_max);
451 *id->_storage = i1;
452 id->changed(); // call trigger function if available
454 SAUERBRATEN_VAR_RETURN(c, *id->_storage);
455 break;
457 case ID_ALIAS: // alias, also used as functions and (global) variables
459 SAUERBRATEN_ALIAS_ENTRY(c, w[1], w[2], w[3]);
460 static vector<ident *> argids;
461 for(int i = 1; i<numargs; i++)
463 if(i > argids.length())
465 s_sprintfd(argname)("arg%d", i);
466 ident *id = idents->access(argname);
467 if(!id)
469 ident init(ID_ALIAS, newstring(argname), newstring(""), persistidents);
470 id = idents->access(init._name, &init);
472 argids.add(id);
474 pushident(*argids[i-1], w[i]); // set any arguments as (global) arg values so functions can access them
475 w[i] = NULL;
477 _numargs = numargs-1;
478 bool wasoverriding = overrideidents;
479 if(id->_override!=NO_OVERRIDE) overrideidents = true;
480 char *wasexecuting = id->_isexecuting;
481 id->_isexecuting = id->_action;
482 setretval(executeret(id->_action));
483 if(id->_isexecuting != id->_action && id->_isexecuting != wasexecuting) delete[] id->_isexecuting;
484 id->_isexecuting = wasexecuting;
485 overrideidents = wasoverriding;
486 for(int i = 1; i<numargs; i++) popident(*argids[i-1]);
487 SAUERBRATEN_ALIAS_RETURN(c);
488 break;
492 loopj(numargs) if(w[j]) delete[] w[j];
494 return retval;
497 int execute(const char *p)
499 char *ret = executeret(p);
500 int i = 0;
501 if(ret) { i = parseint(ret); delete[] ret; }
502 return i;
505 bool execfile(const char *cfgfile)
507 string s;
508 s_strcpy(s, cfgfile);
509 char *buf = loadfile(path(s), NULL);
510 if(!buf) return false;
511 execute(buf);
512 delete[] buf;
513 return true;
516 void exec(const char *cfgfile)
518 if(!execfile(cfgfile)) conoutf("could not read \"%s\"", cfgfile);
521 void writecfg()
523 FILE *f = openfile(path(cl->savedconfig(), true), "w");
524 if(!f) return;
525 fprintf(f, "// automatically written on exit, DO NOT MODIFY\n// delete this file to have %s overwrite these settings\n// modify settings in game, or put settings in %s to override anything\n\n", cl->defaultconfig(), cl->autoexec());
526 cc->writeclientinfo(f);
527 fprintf(f, "\n");
528 writecrosshairs(f);
529 enumerate(*idents, ident, id,
530 if(id._type==ID_VAR && id._persist)
532 fprintf(f, "%s %d\n", id._name, *id._storage);
535 fprintf(f, "\n");
536 writebinds(f);
537 fprintf(f, "\n");
538 enumerate(*idents, ident, id,
539 if(id._type==ID_ALIAS && id._persist && id._override==NO_OVERRIDE && !strstr(id._name, "nextmap_") && id._action[0])
541 fprintf(f, "\"%s\" = [%s]\n", id._name, id._action);
544 fprintf(f, "\n");
545 writecompletions(f);
546 fclose(f);
549 COMMAND(writecfg, "");
551 // below the commands that implement a small imperative language. thanks to the semantics of
552 // () and [] expressions, any control construct can be defined trivially.
554 void intset(char *name, int v) { string b; itoa(b, v); alias(name, b); }
555 void intret (int v) { string b; itoa(b, v); commandret = newstring(b); }
557 ICOMMAND(if, "sss", (char *cond, char *t, char *f), commandret = executeret(cond[0]!='0' ? t : f));
559 ICOMMAND(loop, "sis", (char *var, int *n, char *body), loopi(*n) { intset(var, i); execute(body); });
560 ICOMMAND(while, "ss", (char *cond, char *body), while(execute(cond)) execute(body)); // can't get any simpler than this :)
562 void concat(const char *s) { commandret = newstring(s); }
563 void result(const char *s) { commandret = newstring(s); }
565 void concatword(char **args, int *numargs)
567 commandret = conc(args, *numargs, false);
570 void format(char **args, int *numargs)
572 vector<char> s;
573 char *f = args[0];
574 while(*f)
576 int c = *f++;
577 if(c == '%')
579 int i = *f++;
580 if(i >= '1' && i <= '9')
582 i -= '0';
583 const char *sub = i < *numargs ? args[i] : "";
584 while(*sub) s.add(*sub++);
586 else s.add(i);
588 else s.add(c);
590 s.add('\0');
591 result(s.getbuf());
594 #define whitespaceskip s += strspn(s, "\n\t ")
595 #define elementskip *s=='"' ? (++s, s += strcspn(s, "\"\n\0"), s += *s=='"') : s += strcspn(s, "\n\t \0")
597 void explodelist(char *s, vector<char *> &elems)
599 whitespaceskip;
600 while(*s)
602 char *elem = s;
603 elementskip;
604 elems.add(*elem=='"' ? newstring(elem+1, s-elem-(s[-1]=='"' ? 2 : 1)) : newstring(elem, s-elem));
605 whitespaceskip;
609 void listlen(char *s)
611 int n = 0;
612 whitespaceskip;
613 for(; *s; n++) elementskip, whitespaceskip;
614 intret(n);
617 void at(char *s, int *pos)
619 whitespaceskip;
620 loopi(*pos) elementskip, whitespaceskip;
621 char *e = s;
622 elementskip;
623 if(*e=='"')
625 e++;
626 if(s[-1]=='"') --s;
628 *s = '\0';
629 result(e);
632 void getalias_(char *s)
634 result(getalias(s));
637 COMMAND(exec, "s");
638 COMMAND(concat, "C");
639 COMMAND(result, "s");
640 COMMAND(concatword, "V");
641 COMMAND(format, "V");
642 COMMAND(at, "si");
643 COMMAND(listlen, "s");
644 COMMANDN(getalias, getalias_, "s");
646 void add (int *a, int *b) { intret(*a + *b); } COMMANDN(+, add, "ii");
647 void mul (int *a, int *b) { intret(*a * *b); } COMMANDN(*, mul, "ii");
648 void sub (int *a, int *b) { intret(*a - *b); } COMMANDN(-, sub, "ii");
649 void divi (int *a, int *b) { intret(*b ? *a / *b : 0); } COMMANDN(div, divi, "ii");
650 void mod (int *a, int *b) { intret(*b ? *a % *b : 0); } COMMAND(mod, "ii");
651 void equal(int *a, int *b) { intret((int)(*a == *b)); } COMMANDN(=, equal, "ii");
652 void nequal(int *a, int *b) { intret((int)(*a != *b)); } COMMANDN(!=, nequal, "ii");
653 void lt (int *a, int *b) { intret((int)(*a < *b)); } COMMANDN(<, lt, "ii");
654 void gt (int *a, int *b) { intret((int)(*a > *b)); } COMMANDN(>, gt, "ii");
655 void lte (int *a, int *b) { intret((int)(*a <= *b)); } COMMANDN(<=, lte, "ii");
656 void gte (int *a, int *b) { intret((int)(*a >= *b)); } COMMANDN(>=, gte, "ii");
657 void xora (int *a, int *b) { intret(*a ^ *b); } COMMANDN(^, xora, "ii");
658 void nota (int *a) { intret(*a == 0); } COMMANDN(!, nota, "i");
659 void mina (int *a, int *b) { intret(min(*a, *b)); } COMMANDN(min, mina, "ii");
660 void maxa (int *a, int *b) { intret(max(*a, *b)); } COMMANDN(max, maxa, "ii");
662 void anda (char *a, char *b) { intret(execute(a)!=0 && execute(b)!=0); }
663 void ora (char *a, char *b) { intret(execute(a)!=0 || execute(b)!=0); }
665 COMMANDN(&&, anda, "ss");
666 COMMANDN(||, ora, "ss");
668 void rndn(int *a) { intret(*a>0 ? rnd(*a) : 0); } COMMANDN(rnd, rndn, "i");
670 void strcmpa(char *a, char *b) { intret(strcmp(a,b)==0); } COMMANDN(strcmp, strcmpa, "ss");
672 ICOMMAND(echo, "C", (char *s), conoutf("\f1%s", s));
674 void strstra(char *a, char *b) { char *s = strstr(a, b); intret(s ? s-a : -1); } COMMANDN(strstr, strstra, "ss");
676 struct sleepcmd
678 int millis;
679 char *command;
680 bool override;
682 vector<sleepcmd> sleepcmds;
684 void addsleep(int *msec, char *cmd)
686 sleepcmd &s = sleepcmds.add();
687 s.millis = *msec+lastmillis;
688 s.command = newstring(cmd);
689 s.override = overrideidents;
692 COMMANDN(sleep, addsleep, "is");
694 void checksleep(int millis)
696 loopv(sleepcmds)
698 sleepcmd &s = sleepcmds[i];
699 if(s.millis && millis>s.millis)
701 char *cmd = s.command; // execute might create more sleep commands
702 execute(cmd);
703 delete[] cmd;
704 sleepcmds.remove(i--);
709 void clearsleep(bool clearoverrides)
711 int len = 0;
712 loopv(sleepcmds)
714 if(clearoverrides && !sleepcmds[i].override) sleepcmds[len++] = sleepcmds[i];
715 else delete[] sleepcmds[i].command;
717 sleepcmds.setsize(len);
720 void clearsleep_(int *clearoverrides)
722 clearsleep(*clearoverrides!=0 || overrideidents);
725 COMMANDN(clearsleep, clearsleep_, "i");