1 // command.cpp: implements the parsing and execution of a tiny script language which
2 // is largely backwards compatible with the quake console language.
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
;
21 delete[] stack
->action
;
22 identstack
*tmp
= stack
;
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();
37 enumerate(*idents
, ident
, i
,
38 if(i
._override
!=NO_OVERRIDE
)
43 if(i
._action
[0]) i
._action
= exchangestr(i
._action
, "");
46 *i
._storage
= i
._override
;
50 i
._override
= NO_OVERRIDE
;
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
;
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
;
74 void pusha(char *name
, char *action
)
76 ident
*id
= idents
->access(name
);
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
));
93 ident
*id
= idents
->access(name
);
100 void aliasa(const char *name
, char *action
)
102 ident
*b
= idents
->access(name
);
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
);
116 if(b
->_action
!= b
->_isexecuting
) delete[] b
->_action
;
118 if(overrideidents
) b
->_override
= OVERRIDDEN
;
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
);
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
)
148 if(dofunc
) id
->changed();
150 int getvar(const char *name
)
153 return *id
->_storage
;
155 int getvarmin(const char *name
)
160 int getvarmax(const char *name
)
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
);
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
)
196 while(*p
=='@') p
++, escape
++;
199 while(escape
--) wordbuf
.add('@');
204 char *ret
= parseexp(p
, ')');
207 for(char *sub
= ret
; *sub
; ) wordbuf
.add(*sub
++);
212 static vector
<char> ident
;
213 ident
.setsizenodelete(0);
214 while(isalnum(*p
) || *p
=='_') ident
.add(*p
++);
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];
225 for(int brak
= 1; brak
; )
228 if(c
=='\r') continue; // hack
229 if(left
=='[' && c
=='@')
231 parsemacro(p
, brak
, wordbuf
);
237 const char *end
= p
+strcspn(p
, "\"\r\n\0");
238 while(p
< end
) wordbuf
.add(*p
++);
239 if(*p
=='\"') wordbuf
.add(*p
++);
242 if(c
=='/' && *p
=='/')
244 p
+= strcspn(p
, "\n\0");
249 else if(c
==right
) brak
--;
253 conoutf("missing \"%c\"", right
);
265 char *ret
= executeret(wordbuf
.getbuf()); // evaluate () exps directly, and substitute result
267 s
= ret
? ret
: newstring("");
271 s
= newstring(wordbuf
.getbuf(), wordbuf
.length());
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);
290 char *parseword(const char *&p
) // parse single argument, including expressions
294 p
+= strspn(p
, " \t\r");
295 if(p
[0]!='/' || p
[1]!='/') break;
296 p
+= strcspn(p
, "\n\0");
301 const char *word
= p
;
302 p
+= strcspn(p
, "\"\r\n\0");
303 char *s
= newstring(word
, p
-word
);
307 if(*p
=='(') return parseexp(p
, ')');
308 if(*p
=='[') return parseexp(p
, ']');
309 const char *word
= p
;
312 p
+= strcspn(p
, "/; \t\r\n\0");
313 if(p
[0]!='/' || p
[1]=='/') break;
314 else if(p
[1]=='\0') { p
++; break; }
317 if(p
-word
==0) return NULL
;
318 char *s
= newstring(word
, p
-word
);
319 if(*s
=='$') return lookup(s
); // substitute variables
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
);
330 strcat(r
, w
[i
]); // make string-list out of all arguments
332 if(space
) strcat(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
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
357 if(i
>numargs
) continue;
358 char *s
= parseword(p
); // parse and evaluate exps
363 p
+= strcspn(p
, ";\n\0");
364 cont
= *p
++!=0; // more statements if this isn't the end of the string
366 if(*c
=='/') c
++; // strip irc-style command prefix
367 if(!*c
) continue; // empty statement
371 if(w
[1][0]=='=' && !w
[1][1])
373 aliasa(c
, numargs
>2 ? w
[2] : newstring(""));
378 ident
*id
= idents
->access(c
);
381 if(!parseint(c
) && *c
!='0')
382 conoutf("unknown command: %s", c
);
383 setretval(newstring(c
));
385 else switch(id
->_type
)
388 case ID_COMMAND
: // game defined commands
390 SAUERBRATEN_COMMAND_ENTRY(c
, w
[1], w
[2], w
[3]);
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");
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
);
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
);
439 conoutf("cannot override persistent variable %s", id
->_name
);
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
);
452 id
->changed(); // call trigger function if available
454 SAUERBRATEN_VAR_RETURN(c
, *id
->_storage
);
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
);
469 ident
init(ID_ALIAS
, newstring(argname
), newstring(""), persistidents
);
470 id
= idents
->access(init
._name
, &init
);
474 pushident(*argids
[i
-1], w
[i
]); // set any arguments as (global) arg values so functions can access them
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
);
492 loopj(numargs
) if(w
[j
]) delete[] w
[j
];
497 int execute(const char *p
)
499 char *ret
= executeret(p
);
501 if(ret
) { i
= parseint(ret
); delete[] ret
; }
505 bool execfile(const char *cfgfile
)
508 s_strcpy(s
, cfgfile
);
509 char *buf
= loadfile(path(s
), NULL
);
510 if(!buf
) return false;
516 void exec(const char *cfgfile
)
518 if(!execfile(cfgfile
)) conoutf("could not read \"%s\"", cfgfile
);
523 FILE *f
= openfile(path(cl
->savedconfig(), true), "w");
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
);
529 enumerate(*idents
, ident
, id
,
530 if(id
._type
==ID_VAR
&& id
._persist
)
532 fprintf(f
, "%s %d\n", id
._name
, *id
._storage
);
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
);
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
)
580 if(i
>= '1' && i
<= '9')
583 const char *sub
= i
< *numargs
? args
[i
] : "";
584 while(*sub
) s
.add(*sub
++);
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
)
604 elems
.add(*elem
=='"' ? newstring(elem
+1, s
-elem
-(s
[-1]=='"' ? 2 : 1)) : newstring(elem
, s
-elem
));
609 void listlen(char *s
)
613 for(; *s
; n
++) elementskip
, whitespaceskip
;
617 void at(char *s
, int *pos
)
620 loopi(*pos
) elementskip
, whitespaceskip
;
632 void getalias_(char *s
)
638 COMMAND(concat
, "C");
639 COMMAND(result
, "s");
640 COMMAND(concatword
, "V");
641 COMMAND(format
, "V");
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");
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
)
698 sleepcmd
&s
= sleepcmds
[i
];
699 if(s
.millis
&& millis
>s
.millis
)
701 char *cmd
= s
.command
; // execute might create more sleep commands
704 sleepcmds
.remove(i
--);
709 void clearsleep(bool clearoverrides
)
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");