1426 строки
43 KiB
C++
1426 строки
43 KiB
C++
// command.cpp: implements the parsing and execution of a tiny script language which
|
|
// is largely backwards compatible with the quake console language.
|
|
|
|
// XXX Emscripten
|
|
#define STANDALONE
|
|
|
|
#include "limits.h"
|
|
#include "stdarg.h"
|
|
#include "string.h"
|
|
#include "stdio.h"
|
|
#include "stdlib.h"
|
|
#include "ctype.h"
|
|
#include "math.h"
|
|
#include "time.h"
|
|
#include <new>
|
|
|
|
#include "tools.h"
|
|
#include "command.h"
|
|
|
|
// console
|
|
enum
|
|
{
|
|
CON_INFO = 1<<0,
|
|
CON_WARN = 1<<1,
|
|
CON_ERROR = 1<<2,
|
|
CON_DEBUG = 1<<3,
|
|
CON_INIT = 1<<4,
|
|
CON_ECHO = 1<<5
|
|
};
|
|
extern void conoutf(const char *s, ...);
|
|
extern void conoutf(int type, const char *s, ...);
|
|
|
|
// command
|
|
extern int variable(const char *name, int min, int cur, int max, int *storage, void (*fun)(), int flags);
|
|
extern float fvariable(const char *name, float min, float cur, float max, float *storage, void (*fun)(), int flags);
|
|
extern char *svariable(const char *name, const char *cur, char **storage, void (*fun)(), int flags);
|
|
extern void setvar(const char *name, int i, bool dofunc = true, bool doclamp = true);
|
|
extern void setfvar(const char *name, float f, bool dofunc = true, bool doclamp = true);
|
|
extern void setsvar(const char *name, const char *str, bool dofunc = true);
|
|
extern void setvarchecked(ident *id, int val);
|
|
extern void setfvarchecked(ident *id, float val);
|
|
extern void setsvarchecked(ident *id, const char *val);
|
|
extern void touchvar(const char *name);
|
|
extern int getvar(const char *name);
|
|
extern int getvarmin(const char *name);
|
|
extern int getvarmax(const char *name);
|
|
extern bool identexists(const char *name);
|
|
extern ident *getident(const char *name);
|
|
extern ident *newident(const char *name);
|
|
extern bool addcommand(const char *name, void (*fun)(), const char *narg);
|
|
extern int execute(const char *p);
|
|
extern char *executeret(const char *p);
|
|
extern bool execfile(const char *cfgfile, bool msg = true);
|
|
extern void alias(const char *name, const char *action);
|
|
extern const char *getalias(const char *name);
|
|
|
|
// main
|
|
extern void fatal(const char *s, ...);
|
|
|
|
extern char *path(char *s);
|
|
extern char *path(const char *s, bool copy);
|
|
extern const char *parentdir(const char *directory);
|
|
extern bool fileexists(const char *path, const char *mode);
|
|
extern bool createdir(const char *path);
|
|
extern size_t fixpackagedir(char *dir);
|
|
extern void sethomedir(const char *dir);
|
|
extern void addpackagedir(const char *dir);
|
|
extern const char *findfile(const char *filename, const char *mode);
|
|
extern stream *openrawfile(const char *filename, const char *mode);
|
|
extern stream *openzipfile(const char *filename, const char *mode);
|
|
extern stream *openfile(const char *filename, const char *mode);
|
|
extern stream *opentempfile(const char *filename, const char *mode);
|
|
extern char *loadfile(const char *fn, int *size);
|
|
extern bool listdir(const char *dir, const char *ext, vector<char *> &files);
|
|
extern int listfiles(const char *dir, const char *ext, vector<char *> &files);
|
|
extern int listzipfiles(const char *dir, const char *ext, vector<char *> &files);
|
|
extern void seedMT(uint seed);
|
|
extern uint randomMT(void);
|
|
// XXX =========================
|
|
|
|
char *exchangestr(char *o, const char *n) { delete[] o; return newstring(n); }
|
|
|
|
typedef hashtable<const char *, ident> identtable;
|
|
|
|
identtable *idents = NULL; // contains ALL vars/commands/aliases
|
|
|
|
bool overrideidents = false, persistidents = true;
|
|
|
|
void clearstack(ident &id)
|
|
{
|
|
identstack *stack = id.stack;
|
|
while(stack)
|
|
{
|
|
delete[] stack->action;
|
|
identstack *tmp = stack;
|
|
stack = stack->next;
|
|
delete tmp;
|
|
}
|
|
id.stack = NULL;
|
|
}
|
|
|
|
void clear_command()
|
|
{
|
|
enumerate(*idents, ident, i, if(i.type==ID_ALIAS) { DELETEA(i.name); DELETEA(i.action); if(i.stack) clearstack(i); });
|
|
if(idents) idents->clear();
|
|
}
|
|
|
|
void clearoverride(ident &i)
|
|
{
|
|
if(i.override==NO_OVERRIDE) return;
|
|
switch(i.type)
|
|
{
|
|
case ID_ALIAS:
|
|
if(i.action[0])
|
|
{
|
|
if(i.action != i.isexecuting) delete[] i.action;
|
|
i.action = newstring("");
|
|
}
|
|
break;
|
|
case ID_VAR:
|
|
*i.storage.i = i.overrideval.i;
|
|
i.changed();
|
|
break;
|
|
case ID_FVAR:
|
|
*i.storage.f = i.overrideval.f;
|
|
i.changed();
|
|
break;
|
|
case ID_SVAR:
|
|
delete[] *i.storage.s;
|
|
*i.storage.s = i.overrideval.s;
|
|
i.changed();
|
|
break;
|
|
}
|
|
i.override = NO_OVERRIDE;
|
|
}
|
|
|
|
void clearoverrides()
|
|
{
|
|
enumerate(*idents, ident, i, clearoverride(i));
|
|
}
|
|
|
|
void pushident(ident &id, char *val)
|
|
{
|
|
if(id.type != ID_ALIAS) return;
|
|
identstack *stack = new identstack;
|
|
stack->action = id.isexecuting==id.action ? newstring(id.action) : id.action;
|
|
stack->next = id.stack;
|
|
id.stack = stack;
|
|
id.action = val;
|
|
}
|
|
|
|
void popident(ident &id)
|
|
{
|
|
if(id.type != ID_ALIAS || !id.stack) return;
|
|
if(id.action != id.isexecuting) delete[] id.action;
|
|
identstack *stack = id.stack;
|
|
id.action = stack->action;
|
|
id.stack = stack->next;
|
|
delete stack;
|
|
}
|
|
|
|
ident *newident(const char *name)
|
|
{
|
|
ident *id = idents->access(name);
|
|
if(!id)
|
|
{
|
|
ident init(ID_ALIAS, newstring(name), newstring(""), persistidents ? IDF_PERSIST : 0);
|
|
id = &idents->access(init.name, init);
|
|
}
|
|
return id;
|
|
}
|
|
|
|
void pusha(const char *name, char *action)
|
|
{
|
|
pushident(*newident(name), action);
|
|
}
|
|
|
|
void push(char *name, char *action)
|
|
{
|
|
pusha(name, newstring(action));
|
|
}
|
|
|
|
void pop(char *name)
|
|
{
|
|
ident *id = idents->access(name);
|
|
if(id) popident(*id);
|
|
}
|
|
|
|
void resetvar(char *name)
|
|
{
|
|
ident *id = idents->access(name);
|
|
if(!id) return;
|
|
if(id->flags&IDF_READONLY) conoutf(CON_ERROR, "variable %s is read-only", id->name);
|
|
else clearoverride(*id);
|
|
}
|
|
|
|
COMMAND(push, "ss");
|
|
COMMAND(pop, "s");
|
|
COMMAND(resetvar, "s");
|
|
|
|
void aliasa(const char *name, char *action)
|
|
{
|
|
ident *b = idents->access(name);
|
|
if(!b)
|
|
{
|
|
ident b(ID_ALIAS, newstring(name), action, persistidents ? IDF_PERSIST : 0);
|
|
if(overrideidents) b.override = OVERRIDDEN;
|
|
idents->access(b.name, b);
|
|
}
|
|
else if(b->type != ID_ALIAS)
|
|
{
|
|
conoutf(CON_ERROR, "cannot redefine builtin %s with an alias", name);
|
|
delete[] action;
|
|
}
|
|
else
|
|
{
|
|
if(b->action != b->isexecuting) delete[] b->action;
|
|
b->action = action;
|
|
if(overrideidents) b->override = OVERRIDDEN;
|
|
else
|
|
{
|
|
if(b->override != NO_OVERRIDE) b->override = NO_OVERRIDE;
|
|
if(persistidents)
|
|
{
|
|
if(!(b->flags & IDF_PERSIST)) b->flags |= IDF_PERSIST;
|
|
}
|
|
else if(b->flags & IDF_PERSIST) b->flags &= ~IDF_PERSIST;
|
|
}
|
|
}
|
|
}
|
|
|
|
void alias(const char *name, const char *action) { aliasa(name, newstring(action)); }
|
|
|
|
COMMAND(alias, "ss");
|
|
|
|
// variable's and commands are registered through globals, see cube.h
|
|
|
|
int variable(const char *name, int min, int cur, int max, int *storage, void (*fun)(), int flags)
|
|
{
|
|
if(!idents) idents = new identtable;
|
|
ident v(ID_VAR, name, min, cur, max, storage, (void *)fun, flags);
|
|
idents->access(name, v);
|
|
return cur;
|
|
}
|
|
|
|
float fvariable(const char *name, float min, float cur, float max, float *storage, void (*fun)(), int flags)
|
|
{
|
|
if(!idents) idents = new identtable;
|
|
ident v(ID_FVAR, name, min, cur, max, storage, (void *)fun, flags);
|
|
idents->access(name, v);
|
|
return cur;
|
|
}
|
|
|
|
char *svariable(const char *name, const char *cur, char **storage, void (*fun)(), int flags)
|
|
{
|
|
if(!idents) idents = new identtable;
|
|
ident v(ID_SVAR, name, newstring(cur), storage, (void *)fun, flags);
|
|
idents->access(name, v);
|
|
return v.val.s;
|
|
}
|
|
|
|
#define _GETVAR(id, vartype, name, retval) \
|
|
ident *id = idents->access(name); \
|
|
if(!id || id->type!=vartype) return retval;
|
|
#define GETVAR(id, name, retval) _GETVAR(id, ID_VAR, name, retval)
|
|
#define OVERRIDEVAR(errorval, saveval, resetval, clearval) \
|
|
if(overrideidents || id->flags&IDF_OVERRIDE) \
|
|
{ \
|
|
if(id->flags&IDF_PERSIST) \
|
|
{ \
|
|
conoutf(CON_ERROR, "cannot override persistent variable %s", id->name); \
|
|
errorval; \
|
|
} \
|
|
if(id->override==NO_OVERRIDE) { saveval; id->override = OVERRIDDEN; } \
|
|
else { clearval; } \
|
|
} \
|
|
else \
|
|
{ \
|
|
if(id->override!=NO_OVERRIDE) { resetval; id->override = NO_OVERRIDE; } \
|
|
clearval; \
|
|
}
|
|
|
|
void setvar(const char *name, int i, bool dofunc, bool doclamp)
|
|
{
|
|
GETVAR(id, name, );
|
|
OVERRIDEVAR(return, id->overrideval.i = *id->storage.i, , )
|
|
if(doclamp) *id->storage.i = clamp(i, id->minval, id->maxval);
|
|
else *id->storage.i = i;
|
|
if(dofunc) id->changed();
|
|
}
|
|
void setfvar(const char *name, float f, bool dofunc, bool doclamp)
|
|
{
|
|
_GETVAR(id, ID_FVAR, name, );
|
|
OVERRIDEVAR(return, id->overrideval.f = *id->storage.f, , );
|
|
if(doclamp) *id->storage.f = clamp(f, id->minvalf, id->maxvalf);
|
|
else *id->storage.f = f;
|
|
if(dofunc) id->changed();
|
|
}
|
|
void setsvar(const char *name, const char *str, bool dofunc)
|
|
{
|
|
_GETVAR(id, ID_SVAR, name, );
|
|
OVERRIDEVAR(return, id->overrideval.s = *id->storage.s, delete[] id->overrideval.s, delete[] *id->storage.s);
|
|
*id->storage.s = newstring(str);
|
|
if(dofunc) id->changed();
|
|
}
|
|
int getvar(const char *name)
|
|
{
|
|
GETVAR(id, name, 0);
|
|
return *id->storage.i;
|
|
}
|
|
int getvarmin(const char *name)
|
|
{
|
|
GETVAR(id, name, 0);
|
|
return id->minval;
|
|
}
|
|
int getvarmax(const char *name)
|
|
{
|
|
GETVAR(id, name, 0);
|
|
return id->maxval;
|
|
}
|
|
bool identexists(const char *name) { return idents->access(name)!=NULL; }
|
|
ident *getident(const char *name) { return idents->access(name); }
|
|
|
|
void touchvar(const char *name)
|
|
{
|
|
ident *id = idents->access(name);
|
|
if(id) switch(id->type)
|
|
{
|
|
case ID_VAR:
|
|
case ID_FVAR:
|
|
case ID_SVAR:
|
|
id->changed();
|
|
break;
|
|
}
|
|
}
|
|
|
|
const char *getalias(const char *name)
|
|
{
|
|
ident *i = idents->access(name);
|
|
return i && i->type==ID_ALIAS ? i->action : "";
|
|
}
|
|
|
|
void setvarchecked(ident *id, int val)
|
|
{
|
|
if(id->flags&IDF_READONLY) conoutf(CON_ERROR, "variable %s is read-only", id->name);
|
|
#ifndef STANDALONE
|
|
else if(!(id->flags&IDF_OVERRIDE) || overrideidents || game::allowedittoggle())
|
|
#else
|
|
else
|
|
#endif
|
|
{
|
|
OVERRIDEVAR(return, id->overrideval.i = *id->storage.i, , )
|
|
if(val<id->minval || val>id->maxval)
|
|
{
|
|
val = val<id->minval ? id->minval : id->maxval; // clamp to valid range
|
|
conoutf(CON_ERROR,
|
|
id->flags&IDF_HEX ?
|
|
(id->minval <= 255 ? "valid range for %s is %d..0x%X" : "valid range for %s is 0x%X..0x%X") :
|
|
"valid range for %s is %d..%d",
|
|
id->name, id->minval, id->maxval);
|
|
}
|
|
*id->storage.i = val;
|
|
id->changed(); // call trigger function if available
|
|
#ifndef STANDALONE
|
|
if(id->flags&IDF_OVERRIDE && !overrideidents) game::vartrigger(id);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void setfvarchecked(ident *id, float val)
|
|
{
|
|
if(id->flags&IDF_READONLY) conoutf(CON_ERROR, "variable %s is read-only", id->name);
|
|
#ifndef STANDALONE
|
|
else if(!(id->flags&IDF_OVERRIDE) || overrideidents || game::allowedittoggle())
|
|
#else
|
|
else
|
|
#endif
|
|
{
|
|
OVERRIDEVAR(return, id->overrideval.f = *id->storage.f, , );
|
|
if(val<id->minvalf || val>id->maxvalf)
|
|
{
|
|
val = val<id->minvalf ? id->minvalf : id->maxvalf; // clamp to valid range
|
|
conoutf(CON_ERROR, "valid range for %s is %s..%s", id->name, floatstr(id->minvalf), floatstr(id->maxvalf));
|
|
}
|
|
*id->storage.f = val;
|
|
id->changed();
|
|
#ifndef STANDALONE
|
|
if(id->flags&IDF_OVERRIDE && !overrideidents) game::vartrigger(id);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void setsvarchecked(ident *id, const char *val)
|
|
{
|
|
if(id->flags&IDF_READONLY) conoutf(CON_ERROR, "variable %s is read-only", id->name);
|
|
#ifndef STANDALONE
|
|
else if(!(id->flags&IDF_OVERRIDE) || overrideidents || game::allowedittoggle())
|
|
#else
|
|
else
|
|
#endif
|
|
{
|
|
OVERRIDEVAR(return, id->overrideval.s = *id->storage.s, delete[] id->overrideval.s, delete[] *id->storage.s);
|
|
*id->storage.s = newstring(val);
|
|
id->changed();
|
|
#ifndef STANDALONE
|
|
if(id->flags&IDF_OVERRIDE && !overrideidents) game::vartrigger(id);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
bool addcommand(const char *name, void (*fun)(), const char *narg)
|
|
{
|
|
if(!idents) idents = new identtable;
|
|
ident c(ID_COMMAND, name, narg, (void *)fun);
|
|
idents->access(name, c);
|
|
return false;
|
|
}
|
|
|
|
void addident(const char *name, ident *id)
|
|
{
|
|
if(!idents) idents = new identtable;
|
|
idents->access(name, *id);
|
|
}
|
|
|
|
static vector<vector<char> *> wordbufs;
|
|
static int bufnest = 0;
|
|
|
|
char *parseexp(const char *&p, int right);
|
|
|
|
void parsemacro(const char *&p, int level, vector<char> &wordbuf)
|
|
{
|
|
int escape = 1;
|
|
while(*p=='@') p++, escape++;
|
|
if(level > escape)
|
|
{
|
|
while(escape--) wordbuf.add('@');
|
|
return;
|
|
}
|
|
if(*p=='(')
|
|
{
|
|
char *ret = parseexp(p, ')');
|
|
if(ret)
|
|
{
|
|
for(char *sub = ret; *sub; ) wordbuf.add(*sub++);
|
|
delete[] ret;
|
|
}
|
|
return;
|
|
}
|
|
static vector<char> ident;
|
|
ident.setsize(0);
|
|
while(isalnum(*p) || *p=='_') ident.add(*p++);
|
|
ident.add(0);
|
|
const char *alias = getalias(ident.getbuf());
|
|
while(*alias) wordbuf.add(*alias++);
|
|
}
|
|
|
|
const char *parsestring(const char *p)
|
|
{
|
|
for(; *p; p++) switch(*p)
|
|
{
|
|
case '\r':
|
|
case '\n':
|
|
case '\"':
|
|
return p;
|
|
case '^':
|
|
if(*++p) break;
|
|
return p;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
int escapestring(char *dst, const char *src, const char *end)
|
|
{
|
|
char *start = dst;
|
|
while(src < end)
|
|
{
|
|
int c = *src++;
|
|
if(c == '^')
|
|
{
|
|
if(src >= end) break;
|
|
int e = *src++;
|
|
switch(e)
|
|
{
|
|
case 'n': *dst++ = '\n'; break;
|
|
case 't': *dst++ = '\t'; break;
|
|
case 'f': *dst++ = '\f'; break;
|
|
default: *dst++ = e; break;
|
|
}
|
|
}
|
|
else *dst++ = c;
|
|
}
|
|
return dst - start;
|
|
}
|
|
|
|
char *parseexp(const char *&p, int right) // parse any nested set of () or []
|
|
{
|
|
if(bufnest++>=wordbufs.length()) wordbufs.add(new vector<char>);
|
|
vector<char> &wordbuf = *wordbufs[bufnest-1];
|
|
int left = *p++;
|
|
for(int brak = 1; brak; )
|
|
{
|
|
size_t n = strcspn(p, "\r@\"/()[]");
|
|
wordbuf.put(p, n);
|
|
p += n;
|
|
|
|
int c = *p++;
|
|
switch(c)
|
|
{
|
|
case '\r': continue;
|
|
case '@':
|
|
if(left == '[') { parsemacro(p, brak, wordbuf); continue; }
|
|
break;
|
|
case '\"':
|
|
{
|
|
wordbuf.add(c);
|
|
const char *end = parsestring(p);
|
|
wordbuf.put(p, end - p);
|
|
p = end;
|
|
if(*p=='\"') wordbuf.add(*p++);
|
|
continue;
|
|
}
|
|
case '/':
|
|
if(*p=='/')
|
|
{
|
|
p += strcspn(p, "\n\0");
|
|
continue;
|
|
}
|
|
break;
|
|
case '\0':
|
|
p--;
|
|
conoutf(CON_ERROR, "missing \"%c\"", right);
|
|
wordbuf.setsize(0);
|
|
bufnest--;
|
|
return NULL;
|
|
case '(': case '[': if(c==left) brak++; break;
|
|
case ')': case ']': if(c==right) brak--; break;
|
|
}
|
|
wordbuf.add(c);
|
|
}
|
|
wordbuf.pop();
|
|
char *s;
|
|
if(left=='(')
|
|
{
|
|
wordbuf.add(0);
|
|
char *ret = executeret(wordbuf.getbuf()); // evaluate () exps directly, and substitute result
|
|
wordbuf.pop();
|
|
s = ret ? ret : newstring("");
|
|
}
|
|
else
|
|
{
|
|
s = newstring(wordbuf.getbuf(), wordbuf.length());
|
|
}
|
|
wordbuf.setsize(0);
|
|
bufnest--;
|
|
return s;
|
|
}
|
|
|
|
char *lookup(char *n) // find value of ident referenced with $ in exp
|
|
{
|
|
ident *id = idents->access(n+1);
|
|
if(id) switch(id->type)
|
|
{
|
|
case ID_VAR: return exchangestr(n, intstr(*id->storage.i));
|
|
case ID_FVAR: return exchangestr(n, floatstr(*id->storage.f));
|
|
case ID_SVAR: return exchangestr(n, *id->storage.s);
|
|
case ID_ALIAS: return exchangestr(n, id->action);
|
|
}
|
|
conoutf(CON_ERROR, "unknown alias lookup: %s", n+1);
|
|
return n;
|
|
}
|
|
|
|
char *parseword(const char *&p, int arg, int &infix) // parse single argument, including expressions
|
|
{
|
|
for(;;)
|
|
{
|
|
p += strspn(p, " \t\r");
|
|
if(p[0]!='/' || p[1]!='/') break;
|
|
p += strcspn(p, "\n\0");
|
|
}
|
|
if(*p=='\"')
|
|
{
|
|
p++;
|
|
const char *end = parsestring(p);
|
|
char *s = newstring(end - p);
|
|
s[escapestring(s, p, end)] = '\0';
|
|
p = end;
|
|
if(*p=='\"') p++;
|
|
return s;
|
|
}
|
|
if(*p=='(') return parseexp(p, ')');
|
|
if(*p=='[') return parseexp(p, ']');
|
|
const char *word = p;
|
|
for(;;)
|
|
{
|
|
p += strcspn(p, "/; \t\r\n\0");
|
|
if(p[0]!='/' || p[1]=='/') break;
|
|
else if(p[1]=='\0') { p++; break; }
|
|
p += 2;
|
|
}
|
|
if(p-word==0) return NULL;
|
|
if(arg==1 && p-word==1) switch(*word)
|
|
{
|
|
case '=': infix = *word; break;
|
|
}
|
|
char *s = newstring(word, p-word);
|
|
if(*s=='$') return lookup(s); // substitute variables
|
|
return s;
|
|
}
|
|
|
|
char *conc(char **w, int n, bool space)
|
|
{
|
|
int len = space ? max(n-1, 0) : 0;
|
|
loopj(n) len += (int)strlen(w[j]);
|
|
char *r = newstring("", len);
|
|
loopi(n)
|
|
{
|
|
strcat(r, w[i]); // make string-list out of all arguments
|
|
if(i==n-1) break;
|
|
if(space) strcat(r, " ");
|
|
}
|
|
return r;
|
|
}
|
|
|
|
VARN(numargs, _numargs, 25, 0, 0);
|
|
|
|
static inline bool isinteger(char *c)
|
|
{
|
|
return isdigit(c[0]) || ((c[0]=='+' || c[0]=='-' || c[0]=='.') && isdigit(c[1]));
|
|
}
|
|
|
|
char *commandret = NULL;
|
|
|
|
char *executeret(const char *p) // all evaluation happens here, recursively
|
|
{
|
|
const int MAXWORDS = 25; // limit, remove
|
|
char *w[MAXWORDS];
|
|
char *retval = NULL;
|
|
#define setretval(v) { char *rv = v; if(rv) retval = rv; }
|
|
for(bool cont = true; cont;) // for each ; seperated statement
|
|
{
|
|
int numargs = MAXWORDS, infix = 0;
|
|
loopi(MAXWORDS) // collect all argument values
|
|
{
|
|
w[i] = parseword(p, i, infix); // parse and evaluate exps
|
|
if(!w[i]) { numargs = i; break; }
|
|
}
|
|
|
|
p += strcspn(p, ";\n\0");
|
|
cont = *p++!=0; // more statements if this isn't the end of the string
|
|
char *c = w[0];
|
|
if(!c || !*c) continue; // empty statement
|
|
|
|
DELETEA(retval);
|
|
|
|
if(infix)
|
|
{
|
|
switch(infix)
|
|
{
|
|
case '=':
|
|
aliasa(c, numargs>2 ? w[2] : newstring(""));
|
|
w[2] = NULL;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ident *id = idents->access(c);
|
|
if(!id)
|
|
{
|
|
if(!isinteger(c))
|
|
conoutf(CON_ERROR, "unknown command: %s", c);
|
|
setretval(newstring(c));
|
|
}
|
|
else switch(id->type)
|
|
{
|
|
case ID_CCOMMAND:
|
|
case ID_COMMAND: // game defined commands
|
|
{
|
|
void *v[MAXWORDS];
|
|
union
|
|
{
|
|
int i;
|
|
float f;
|
|
} nstor[MAXWORDS];
|
|
int n = 0, wn = 0;
|
|
char *cargs = NULL;
|
|
if(id->type==ID_CCOMMAND) v[n++] = id->self;
|
|
for(const char *a = id->narg; *a; a++, n++) switch(*a)
|
|
{
|
|
case 's': v[n] = ++wn < numargs ? w[wn] : (char *)""; break;
|
|
case 'i': nstor[n].i = ++wn < numargs ? parseint(w[wn]) : 0; v[n] = &nstor[n].i; break;
|
|
case 'f': nstor[n].f = ++wn < numargs ? parsefloat(w[wn]) : 0.0f; v[n] = &nstor[n].f; break;
|
|
#ifndef STANDALONE
|
|
case 'D': nstor[n].i = addreleaseaction(id->name) ? 1 : 0; v[n] = &nstor[n].i; break;
|
|
#endif
|
|
case 'V': v[n++] = w+1; nstor[n].i = numargs-1; v[n] = &nstor[n].i; break;
|
|
case 'C': if(!cargs) cargs = conc(w+1, numargs-1, true); v[n] = cargs; break;
|
|
default: fatal("builtin declared with illegal type");
|
|
}
|
|
switch(n)
|
|
{
|
|
case 0: ((void (__cdecl *)() )id->fun)(); break;
|
|
case 1: ((void (__cdecl *)(void *) )id->fun)(v[0]); break;
|
|
case 2: ((void (__cdecl *)(void *, void *) )id->fun)(v[0], v[1]); break;
|
|
case 3: ((void (__cdecl *)(void *, void *, void *) )id->fun)(v[0], v[1], v[2]); break;
|
|
case 4: ((void (__cdecl *)(void *, void *, void *, void *) )id->fun)(v[0], v[1], v[2], v[3]); break;
|
|
case 5: ((void (__cdecl *)(void *, void *, void *, void *, void *))id->fun)(v[0], v[1], v[2], v[3], v[4]); break;
|
|
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;
|
|
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;
|
|
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;
|
|
default: fatal("builtin declared with too many args (use V?)");
|
|
}
|
|
if(cargs) delete[] cargs;
|
|
setretval(commandret);
|
|
commandret = NULL;
|
|
break;
|
|
}
|
|
|
|
case ID_VAR: // game defined variables
|
|
if(numargs <= 1)
|
|
{
|
|
if(id->flags&IDF_HEX && id->maxval==0xFFFFFF)
|
|
conoutf("%s = 0x%.6X (%d, %d, %d)", c, *id->storage.i, (*id->storage.i>>16)&0xFF, (*id->storage.i>>8)&0xFF, *id->storage.i&0xFF);
|
|
else
|
|
conoutf(id->flags&IDF_HEX ? "%s = 0x%X" : "%s = %d", c, *id->storage.i); // var with no value just prints its current value
|
|
}
|
|
else
|
|
{
|
|
int val = parseint(w[1]);
|
|
if(id->flags&IDF_HEX && numargs > 2)
|
|
{
|
|
val <<= 16;
|
|
val |= parseint(w[2])<<8;
|
|
if(numargs > 3) val |= parseint(w[3]);
|
|
}
|
|
setvarchecked(id, val);
|
|
}
|
|
break;
|
|
|
|
case ID_FVAR:
|
|
if(numargs <= 1) conoutf("%s = %s", c, floatstr(*id->storage.f));
|
|
else setfvarchecked(id, parsefloat(w[1]));
|
|
break;
|
|
|
|
case ID_SVAR:
|
|
if(numargs <= 1) conoutf(strchr(*id->storage.s, '"') ? "%s = [%s]" : "%s = \"%s\"", c, *id->storage.s);
|
|
else setsvarchecked(id, w[1]);
|
|
break;
|
|
|
|
case ID_ALIAS: // alias, also used as functions and (global) variables
|
|
{
|
|
delete[] w[0];
|
|
static vector<ident *> argids;
|
|
for(int i = 1; i<numargs; i++)
|
|
{
|
|
if(i > argids.length())
|
|
{
|
|
defformatstring(argname)("arg%d", i);
|
|
argids.add(newident(argname));
|
|
}
|
|
pushident(*argids[i-1], w[i]); // set any arguments as (global) arg values so functions can access them
|
|
}
|
|
_numargs = numargs-1;
|
|
bool wasoverriding = overrideidents;
|
|
if(id->override!=NO_OVERRIDE) overrideidents = true;
|
|
char *wasexecuting = id->isexecuting;
|
|
id->isexecuting = id->action;
|
|
setretval(executeret(id->action));
|
|
if(id->isexecuting != id->action && id->isexecuting != wasexecuting) delete[] id->isexecuting;
|
|
id->isexecuting = wasexecuting;
|
|
overrideidents = wasoverriding;
|
|
for(int i = 1; i<numargs; i++) popident(*argids[i-1]);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
loopj(numargs) if(w[j]) delete[] w[j];
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
int execute(const char *p)
|
|
{
|
|
char *ret = executeret(p);
|
|
int i = 0;
|
|
if(ret) { i = parseint(ret); delete[] ret; }
|
|
return i;
|
|
}
|
|
|
|
#ifndef STANDALONE
|
|
static int sortidents(ident **x, ident **y)
|
|
{
|
|
return strcmp((*x)->name, (*y)->name);
|
|
}
|
|
|
|
void writeescapedstring(stream *f, const char *s)
|
|
{
|
|
f->putchar('"');
|
|
for(; *s; s++) switch(*s)
|
|
{
|
|
case '\n': f->write("^n", 2); break;
|
|
case '\t': f->write("^t", 2); break;
|
|
case '\f': f->write("^f", 2); break;
|
|
case '"': f->write("^\"", 2); break;
|
|
default: f->putchar(*s); break;
|
|
}
|
|
f->putchar('"');
|
|
}
|
|
#endif
|
|
|
|
// below the commands that implement a small imperative language. thanks to the semantics of
|
|
// () and [] expressions, any control construct can be defined trivially.
|
|
|
|
static string retbuf[3];
|
|
static int retidx = 0;
|
|
|
|
const char *intstr(int v)
|
|
{
|
|
retidx = (retidx + 1)%3;
|
|
formatstring(retbuf[retidx])("%d", v);
|
|
return retbuf[retidx];
|
|
}
|
|
|
|
void intret(int v)
|
|
{
|
|
commandret = newstring(intstr(v));
|
|
}
|
|
|
|
const char *floatstr(float v)
|
|
{
|
|
retidx = (retidx + 1)%3;
|
|
formatstring(retbuf[retidx])(v==int(v) ? "%.1f" : "%.7g", v);
|
|
return retbuf[retidx];
|
|
}
|
|
|
|
void floatret(float v)
|
|
{
|
|
commandret = newstring(floatstr(v));
|
|
}
|
|
|
|
#undef ICOMMANDNAME
|
|
#define ICOMMANDNAME(name) _stdcmd
|
|
|
|
ICOMMAND(if, "sss", (char *cond, char *t, char *f), commandret = executeret(cond[0] && (!isinteger(cond) || parseint(cond)) ? t : f));
|
|
ICOMMAND(?, "sss", (char *cond, char *t, char *f), result(cond[0] && (!isinteger(cond) || parseint(cond)) ? t : f));
|
|
ICOMMAND(loop, "sis", (char *var, int *n, char *body),
|
|
{
|
|
if(*n<=0) return;
|
|
ident *id = newident(var);
|
|
if(id->type!=ID_ALIAS) return;
|
|
loopi(*n)
|
|
{
|
|
if(i) sprintf(id->action, "%d", i);
|
|
else pushident(*id, newstring("0", 16));
|
|
execute(body);
|
|
}
|
|
popident(*id);
|
|
});
|
|
ICOMMAND(loopwhile, "siss", (char *var, int *n, char *cond, char *body),
|
|
{
|
|
if(*n<=0) return;
|
|
ident *id = newident(var);
|
|
if(id->type!=ID_ALIAS) return;
|
|
loopi(*n)
|
|
{
|
|
if(i) sprintf(id->action, "%d", i);
|
|
else pushident(*id, newstring("0", 16));
|
|
if(!execute(cond)) break;
|
|
execute(body);
|
|
}
|
|
popident(*id);
|
|
});
|
|
ICOMMAND(while, "ss", (char *cond, char *body), while(execute(cond)) execute(body)); // can't get any simpler than this :)
|
|
|
|
void concat(const char *s) { commandret = newstring(s); }
|
|
void result(const char *s) { commandret = newstring(s); }
|
|
|
|
void concatword(char **args, int *numargs)
|
|
{
|
|
commandret = conc(args, *numargs, false);
|
|
}
|
|
|
|
void format(char **args, int *numargs)
|
|
{
|
|
vector<char> s;
|
|
char *f = args[0];
|
|
while(*f)
|
|
{
|
|
int c = *f++;
|
|
if(c == '%')
|
|
{
|
|
int i = *f++;
|
|
if(i >= '1' && i <= '9')
|
|
{
|
|
i -= '0';
|
|
const char *sub = i < *numargs ? args[i] : "";
|
|
while(*sub) s.add(*sub++);
|
|
}
|
|
else s.add(i);
|
|
}
|
|
else s.add(c);
|
|
}
|
|
s.add('\0');
|
|
result(s.getbuf());
|
|
}
|
|
|
|
#define whitespaceskip s += strspn(s, "\n\t ")
|
|
#define elementskip *s=='"' ? (++s, s += strcspn(s, "\"\n\0"), s += *s=='"') : s += strcspn(s, "\n\t \0")
|
|
|
|
void explodelist(const char *s, vector<char *> &elems)
|
|
{
|
|
whitespaceskip;
|
|
while(*s)
|
|
{
|
|
const char *elem = s;
|
|
elementskip;
|
|
elems.add(*elem=='"' ? newstring(elem+1, s-elem-(s[-1]=='"' ? 2 : 1)) : newstring(elem, s-elem));
|
|
whitespaceskip;
|
|
}
|
|
}
|
|
|
|
char *indexlist(const char *s, int pos)
|
|
{
|
|
whitespaceskip;
|
|
loopi(pos)
|
|
{
|
|
elementskip;
|
|
whitespaceskip;
|
|
if(!*s) break;
|
|
}
|
|
const char *e = s;
|
|
elementskip;
|
|
if(*e=='"')
|
|
{
|
|
e++;
|
|
if(s[-1]=='"') --s;
|
|
}
|
|
return newstring(e, s-e);
|
|
}
|
|
|
|
int listlen(const char *s)
|
|
{
|
|
int n = 0;
|
|
whitespaceskip;
|
|
for(; *s; n++) elementskip, whitespaceskip;
|
|
return n;
|
|
}
|
|
|
|
void at(char *s, int *pos)
|
|
{
|
|
commandret = indexlist(s, *pos);
|
|
}
|
|
|
|
void substr(char *s, int *start, char *count)
|
|
{
|
|
int len = strlen(s), offset = clamp(*start, 0, len);
|
|
commandret = newstring(&s[offset], count[0] ? clamp(parseint(count), 0, len - offset) : len - offset);
|
|
}
|
|
|
|
void getalias_(char *s)
|
|
{
|
|
result(getalias(s));
|
|
}
|
|
|
|
COMMAND(concat, "C");
|
|
COMMAND(result, "s");
|
|
COMMAND(concatword, "V");
|
|
COMMAND(format, "V");
|
|
COMMAND(at, "si");
|
|
COMMAND(substr, "sis");
|
|
ICOMMAND(listlen, "s", (char *s), intret(listlen(s)));
|
|
COMMANDN(getalias, getalias_, "s");
|
|
|
|
void looplist(const char *var, const char *list, const char *body, bool search)
|
|
{
|
|
ident *id = newident(var);
|
|
if(id->type!=ID_ALIAS) { if(search) intret(-1); return; }
|
|
int n = 0;
|
|
for(const char *s = list;;)
|
|
{
|
|
whitespaceskip;
|
|
if(!*s) { if(search) intret(-1); break; }
|
|
const char *start = s;
|
|
elementskip;
|
|
const char *end = s;
|
|
if(*start=='"') { start++; if(end[-1]=='"') --end; }
|
|
char *val = newstring(start, end-start);
|
|
if(n++) aliasa(id->name, val);
|
|
else pushident(*id, val);
|
|
if(execute(body) && search) { intret(n-1); break; }
|
|
}
|
|
if(n) popident(*id);
|
|
}
|
|
|
|
void prettylist(const char *s, const char *conj)
|
|
{
|
|
vector<char> p;
|
|
whitespaceskip;
|
|
for(int len = listlen(s), n = 0; *s; n++)
|
|
{
|
|
const char *elem = s;
|
|
elementskip;
|
|
p.put(elem, s - elem);
|
|
if(n+1 < len)
|
|
{
|
|
if(len > 2 || !conj[0]) p.add(',');
|
|
if(n+2 == len && conj[0])
|
|
{
|
|
p.add(' ');
|
|
p.put(conj, strlen(conj));
|
|
}
|
|
p.add(' ');
|
|
}
|
|
whitespaceskip;
|
|
}
|
|
p.add('\0');
|
|
result(p.getbuf());
|
|
}
|
|
COMMAND(prettylist, "ss");
|
|
|
|
int listincludes(const char *list, const char *needle, int needlelen)
|
|
{
|
|
const char *s = list;
|
|
whitespaceskip;
|
|
int offset = 0;
|
|
while(*s)
|
|
{
|
|
const char *elem = s;
|
|
elementskip;
|
|
int len = s-elem;
|
|
if(*elem=='"')
|
|
{
|
|
elem++;
|
|
len -= s[-1]=='"' ? 2 : 1;
|
|
}
|
|
if(needlelen == len && !strncmp(needle, elem, len)) return offset;
|
|
whitespaceskip;
|
|
offset++;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
char *listdel(const char *s, const char *del)
|
|
{
|
|
vector<char> p;
|
|
whitespaceskip;
|
|
while(*s)
|
|
{
|
|
const char *elem = s;
|
|
elementskip;
|
|
int len = s-elem;
|
|
if(*elem=='"')
|
|
{
|
|
elem++;
|
|
len -= s[-1]=='"' ? 2 : 1;
|
|
}
|
|
if(listincludes(del, elem, len) < 0)
|
|
{
|
|
if(!p.empty()) p.add(' ');
|
|
p.put(elem, len);
|
|
}
|
|
whitespaceskip;
|
|
}
|
|
p.add('\0');
|
|
return newstring(p.getbuf());
|
|
}
|
|
|
|
ICOMMAND(listdel, "ss", (char *list, char *del), commandret = listdel(list, del));
|
|
ICOMMAND(indexof, "ss", (char *list, char *elem), intret(listincludes(list, elem, strlen(elem))));
|
|
ICOMMAND(listfind, "sss", (char *var, char *list, char *body), looplist(var, list, body, true));
|
|
ICOMMAND(looplist, "sss", (char *var, char *list, char *body), looplist(var, list, body, false));
|
|
|
|
ICOMMAND(+, "ii", (int *a, int *b), intret(*a + *b));
|
|
ICOMMAND(*, "ii", (int *a, int *b), intret(*a * *b));
|
|
ICOMMAND(-, "ii", (int *a, int *b), intret(*a - *b));
|
|
ICOMMAND(+f, "ff", (float *a, float *b), floatret(*a + *b));
|
|
ICOMMAND(*f, "ff", (float *a, float *b), floatret(*a * *b));
|
|
ICOMMAND(-f, "ff", (float *a, float *b), floatret(*a - *b));
|
|
ICOMMAND(=, "ii", (int *a, int *b), intret((int)(*a == *b)));
|
|
ICOMMAND(!=, "ii", (int *a, int *b), intret((int)(*a != *b)));
|
|
ICOMMAND(<, "ii", (int *a, int *b), intret((int)(*a < *b)));
|
|
ICOMMAND(>, "ii", (int *a, int *b), intret((int)(*a > *b)));
|
|
ICOMMAND(<=, "ii", (int *a, int *b), intret((int)(*a <= *b)));
|
|
ICOMMAND(>=, "ii", (int *a, int *b), intret((int)(*a >= *b)));
|
|
ICOMMAND(=f, "ff", (float *a, float *b), intret((int)(*a == *b)));
|
|
ICOMMAND(!=f, "ff", (float *a, float *b), intret((int)(*a != *b)));
|
|
ICOMMAND(<f, "ff", (float *a, float *b), intret((int)(*a < *b)));
|
|
ICOMMAND(>f, "ff", (float *a, float *b), intret((int)(*a > *b)));
|
|
ICOMMAND(<=f, "ff", (float *a, float *b), intret((int)(*a <= *b)));
|
|
ICOMMAND(>=f, "ff", (float *a, float *b), intret((int)(*a >= *b)));
|
|
ICOMMAND(^, "ii", (int *a, int *b), intret(*a ^ *b));
|
|
ICOMMAND(!, "i", (int *a), intret(*a == 0));
|
|
ICOMMAND(&, "ii", (int *a, int *b), intret(*a & *b));
|
|
ICOMMAND(|, "ii", (int *a, int *b), intret(*a | *b));
|
|
ICOMMAND(~, "i", (int *a), intret(~*a));
|
|
ICOMMAND(^~, "ii", (int *a, int *b), intret(*a ^ ~*b));
|
|
ICOMMAND(&~, "ii", (int *a, int *b), intret(*a & ~*b));
|
|
ICOMMAND(|~, "ii", (int *a, int *b), intret(*a | ~*b));
|
|
ICOMMAND(<<, "ii", (int *a, int *b), intret(*a << *b));
|
|
ICOMMAND(>>, "ii", (int *a, int *b), intret(*a >> *b));
|
|
ICOMMAND(&&, "V", (char **args, int *numargs),
|
|
{
|
|
int val = 1;
|
|
loopi(*numargs) { val = execute(args[i]); if(!val) break; }
|
|
intret(val);
|
|
});
|
|
ICOMMAND(||, "V", (char **args, int *numargs),
|
|
{
|
|
int val = 0;
|
|
loopi(*numargs) { val = execute(args[i]); if(val) break; }
|
|
intret(val);
|
|
});
|
|
|
|
ICOMMAND(div, "ii", (int *a, int *b), intret(*b ? *a / *b : 0));
|
|
ICOMMAND(mod, "ii", (int *a, int *b), intret(*b ? *a % *b : 0));
|
|
ICOMMAND(divf, "ff", (float *a, float *b), floatret(*b ? *a / *b : 0));
|
|
ICOMMAND(modf, "ff", (float *a, float *b), floatret(*b ? fmod(*a, *b) : 0));
|
|
ICOMMAND(sin, "f", (float *a), floatret(sin(*a*RAD)));
|
|
ICOMMAND(cos, "f", (float *a), floatret(cos(*a*RAD)));
|
|
ICOMMAND(tan, "f", (float *a), floatret(tan(*a*RAD)));
|
|
ICOMMAND(asin, "f", (float *a), floatret(asin(*a)/RAD));
|
|
ICOMMAND(acos, "f", (float *a), floatret(acos(*a)/RAD));
|
|
ICOMMAND(atan, "f", (float *a), floatret(atan(*a)/RAD));
|
|
ICOMMAND(sqrt, "f", (float *a), floatret(sqrt(*a)));
|
|
ICOMMAND(pow, "ff", (float *a, float *b), floatret(pow(*a, *b)));
|
|
ICOMMAND(loge, "f", (float *a), floatret(log(*a)));
|
|
ICOMMAND(log2, "f", (float *a), floatret(log(*a)/M_LN2));
|
|
ICOMMAND(log10, "f", (float *a), floatret(log10(*a)));
|
|
ICOMMAND(exp, "f", (float *a), floatret(exp(*a)));
|
|
ICOMMAND(min, "V", (char **args, int *numargs),
|
|
{
|
|
int val = *numargs > 0 ? parseint(args[*numargs - 1]) : 0;
|
|
loopi(*numargs - 1) val = min(val, parseint(args[i]));
|
|
intret(val);
|
|
});
|
|
ICOMMAND(max, "V", (char **args, int *numargs),
|
|
{
|
|
int val = *numargs > 0 ? parseint(args[*numargs - 1]) : 0;
|
|
loopi(*numargs - 1) val = max(val, parseint(args[i]));
|
|
intret(val);
|
|
});
|
|
ICOMMAND(minf, "V", (char **args, int *numargs),
|
|
{
|
|
float val = *numargs > 0 ? parsefloat(args[*numargs - 1]) : 0.0f;
|
|
loopi(*numargs - 1) val = min(val, parsefloat(args[i]));
|
|
floatret(val);
|
|
});
|
|
ICOMMAND(maxf, "V", (char **args, int *numargs),
|
|
{
|
|
float val = *numargs > 0 ? parsefloat(args[*numargs - 1]) : 0.0f;
|
|
loopi(*numargs - 1) val = max(val, parsefloat(args[i]));
|
|
floatret(val);
|
|
});
|
|
|
|
ICOMMAND(cond, "V", (char **args, int *numargs),
|
|
{
|
|
for(int i = 0; i < *numargs; i += 2)
|
|
{
|
|
if(execute(args[i]))
|
|
{
|
|
if(i+1 < *numargs) commandret = executeret(args[i+1]);
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
#define CASECOMMAND(name, fmt, type, compare) \
|
|
ICOMMAND(name, fmt "V", (type *val, char **args, int *numargs), \
|
|
{ \
|
|
int i; \
|
|
for(i = 1; i+1 < *numargs; i += 2) \
|
|
{ \
|
|
if(compare) \
|
|
{ \
|
|
commandret = executeret(args[i+1]); \
|
|
return; \
|
|
} \
|
|
} \
|
|
if(i < *numargs) commandret = executeret(args[i]); \
|
|
})
|
|
CASECOMMAND(case, "i", int, parseint(args[i]) == *val);
|
|
CASECOMMAND(casef, "f", float, parsefloat(args[i]) == *val);
|
|
CASECOMMAND(cases, "s", char, !strcmp(args[i], val));
|
|
|
|
ICOMMAND(rnd, "ii", (int *a, int *b), intret(*a - *b > 0 ? rnd(*a - *b) + *b : *b));
|
|
ICOMMAND(strcmp, "ss", (char *a, char *b), intret(strcmp(a,b)==0));
|
|
ICOMMAND(=s, "ss", (char *a, char *b), intret(strcmp(a,b)==0));
|
|
ICOMMAND(!=s, "ss", (char *a, char *b), intret(strcmp(a,b)!=0));
|
|
ICOMMAND(<s, "ss", (char *a, char *b), intret(strcmp(a,b)<0));
|
|
ICOMMAND(>s, "ss", (char *a, char *b), intret(strcmp(a,b)>0));
|
|
ICOMMAND(<=s, "ss", (char *a, char *b), intret(strcmp(a,b)<=0));
|
|
ICOMMAND(>=s, "ss", (char *a, char *b), intret(strcmp(a,b)>=0));
|
|
ICOMMAND(echo, "C", (char *s), conoutf("%s", s));
|
|
ICOMMAND(error, "C", (char *s), conoutf(CON_ERROR, s));
|
|
ICOMMAND(strstr, "ss", (char *a, char *b), { char *s = strstr(a, b); intret(s ? s-a : -1); });
|
|
ICOMMAND(strlen, "s", (char *s), intret(strlen(s)));
|
|
|
|
char *strreplace(const char *s, const char *oldval, const char *newval)
|
|
{
|
|
vector<char> buf;
|
|
|
|
int oldlen = strlen(oldval);
|
|
if(!oldlen) return newstring(s);
|
|
for(;;)
|
|
{
|
|
const char *found = strstr(s, oldval);
|
|
if(found)
|
|
{
|
|
while(s < found) buf.add(*s++);
|
|
for(const char *n = newval; *n; n++) buf.add(*n);
|
|
s = found + oldlen;
|
|
}
|
|
else
|
|
{
|
|
while(*s) buf.add(*s++);
|
|
buf.add('\0');
|
|
return newstring(buf.getbuf(), buf.length());
|
|
}
|
|
}
|
|
}
|
|
|
|
ICOMMAND(strreplace, "sss", (char *s, char *o, char *n), commandret = strreplace(s, o, n));
|
|
|
|
#ifndef STANDALONE
|
|
ICOMMAND(getmillis, "i", (int *total), intret(*total ? totalmillis : lastmillis));
|
|
|
|
struct sleepcmd
|
|
{
|
|
int delay, millis;
|
|
char *command;
|
|
bool override, persist;
|
|
};
|
|
vector<sleepcmd> sleepcmds;
|
|
|
|
void addsleep(int *msec, char *cmd)
|
|
{
|
|
sleepcmd &s = sleepcmds.add();
|
|
s.delay = max(*msec, 1);
|
|
s.millis = lastmillis;
|
|
s.command = newstring(cmd);
|
|
s.override = overrideidents;
|
|
s.persist = persistidents;
|
|
}
|
|
|
|
COMMANDN(sleep, addsleep, "is");
|
|
|
|
void checksleep(int millis)
|
|
{
|
|
loopv(sleepcmds)
|
|
{
|
|
sleepcmd &s = sleepcmds[i];
|
|
if(millis - s.millis >= s.delay)
|
|
{
|
|
char *cmd = s.command; // execute might create more sleep commands
|
|
s.command = NULL;
|
|
bool waspersisting = persistidents, wasoverriding = overrideidents;
|
|
persistidents = s.persist;
|
|
overrideidents = s.override;
|
|
execute(cmd);
|
|
persistidents = waspersisting;
|
|
overrideidents = wasoverriding;
|
|
delete[] cmd;
|
|
if(sleepcmds.inrange(i) && !sleepcmds[i].command) sleepcmds.remove(i--);
|
|
}
|
|
}
|
|
}
|
|
|
|
void clearsleep(bool clearoverrides)
|
|
{
|
|
int len = 0;
|
|
loopv(sleepcmds) if(sleepcmds[i].command)
|
|
{
|
|
if(clearoverrides && !sleepcmds[i].override) sleepcmds[len++] = sleepcmds[i];
|
|
else delete[] sleepcmds[i].command;
|
|
}
|
|
sleepcmds.shrink(len);
|
|
}
|
|
|
|
void clearsleep_(int *clearoverrides)
|
|
{
|
|
clearsleep(*clearoverrides!=0 || overrideidents);
|
|
}
|
|
|
|
COMMANDN(clearsleep, clearsleep_, "i");
|
|
#endif
|
|
|
|
|
|
// XXX Emscripten: console.cpp
|
|
|
|
struct cline { char *line; int type, outtime; };
|
|
vector<cline> conlines;
|
|
|
|
int commandmillis = -1;
|
|
string commandbuf;
|
|
char *commandaction = NULL, *commandprompt = NULL;
|
|
int commandpos = -1;
|
|
int totalmillis = -1; // XXX Emscripten
|
|
|
|
VARFP(maxcon, 10, 200, 1000, { while(conlines.length() > maxcon) delete[] conlines.pop().line; });
|
|
|
|
#define CONSTRLEN 512
|
|
|
|
void conline(int type, const char *sf) // add a line to the console buffer
|
|
{
|
|
cline cl;
|
|
cl.line = conlines.length()>maxcon ? conlines.pop().line : newstring("", CONSTRLEN-1); // constrain the buffer size
|
|
cl.type = type;
|
|
cl.outtime = totalmillis; // for how long to keep line on screen
|
|
conlines.insert(0, cl);
|
|
copystring(cl.line, sf, CONSTRLEN);
|
|
}
|
|
|
|
void conoutfv(int type, const char *fmt, va_list args)
|
|
{
|
|
static char buf[CONSTRLEN];
|
|
vformatstring(buf, fmt, args, sizeof(char)*CONSTRLEN);
|
|
conline(type, buf);
|
|
//filtertext(buf, buf); // XXX Emscripten
|
|
puts(buf);
|
|
}
|
|
|
|
void conoutf(const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
conoutfv(CON_INFO, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void conoutf(int type, const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
conoutfv(type, fmt, args);
|
|
va_end(args);
|
|
}
|
|
// XXX =======================================
|
|
|
|
// XXX Emscripten: tools.cpp
|
|
|
|
#define N (624)
|
|
#define M (397)
|
|
#define K (0x9908B0DFU)
|
|
#define hiBit(u) ((u) & 0x80000000U)
|
|
#define loBit(u) ((u) & 0x00000001U)
|
|
#define loBits(u) ((u) & 0x7FFFFFFFU)
|
|
#define mixBits(u, v) (hiBit(u)|loBits(v))
|
|
|
|
static uint state[N+1];
|
|
static uint *next;
|
|
static int left = -1;
|
|
|
|
void seedMT(uint seed)
|
|
{
|
|
register uint x = (seed | 1U) & 0xFFFFFFFFU, *s = state;
|
|
register int j;
|
|
for(left=0, *s++=x, j=N; --j; *s++ = (x*=69069U) & 0xFFFFFFFFU);
|
|
}
|
|
|
|
uint reloadMT(void)
|
|
{
|
|
register uint *p0=state, *p2=state+2, *pM=state+M, s0, s1;
|
|
register int j;
|
|
if(left < -1) seedMT(time(NULL));
|
|
left=N-1, next=state+1;
|
|
for(s0=state[0], s1=state[1], j=N-M+1; --j; s0=s1, s1=*p2++) *p0++ = *pM++ ^ (mixBits(s0, s1) >> 1) ^ (loBit(s1) ? K : 0U);
|
|
for(pM=state, j=M; --j; s0=s1, s1=*p2++) *p0++ = *pM++ ^ (mixBits(s0, s1) >> 1) ^ (loBit(s1) ? K : 0U);
|
|
s1=state[0], *p0 = *pM ^ (mixBits(s0, s1) >> 1) ^ (loBit(s1) ? K : 0U);
|
|
s1 ^= (s1 >> 11);
|
|
s1 ^= (s1 << 7) & 0x9D2C5680U;
|
|
s1 ^= (s1 << 15) & 0xEFC60000U;
|
|
return(s1 ^ (s1 >> 18));
|
|
}
|
|
|
|
uint randomMT(void)
|
|
{
|
|
uint y;
|
|
if(--left < 0) return(reloadMT());
|
|
y = *next++;
|
|
y ^= (y >> 11);
|
|
y ^= (y << 7) & 0x9D2C5680U;
|
|
y ^= (y << 15) & 0xEFC60000U;
|
|
return(y ^ (y >> 18));
|
|
}
|
|
|
|
// XXX ==============================================
|
|
|
|
// XXX Emscripten: main.cpp
|
|
|
|
void fatal(const char *s, ...) // failure exit
|
|
{
|
|
static int errors = 0;
|
|
errors++;
|
|
|
|
if(errors <= 2) // print up to one extra recursive error
|
|
{
|
|
defvformatstring(msg,s,s);
|
|
puts(msg);
|
|
}
|
|
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
VARP(somevar, 0, 0, 1024);
|
|
|
|
int main()
|
|
{
|
|
printf("*\n");
|
|
execute("somevar 9");
|
|
execute("temp = (+ 22 $somevar)");
|
|
execute("if (> $temp 30) [ temp = (+ $temp 1) ] [ temp = (* $temp 2) ]");
|
|
execute("if (< $temp 30) [ temp = 0 ] [ temp = (+ $temp 1) ]");
|
|
execute("echo [Temp is] $temp");
|
|
printf("%d\n", getvar("somevar"));
|
|
execute("x = 2");
|
|
execute("push x 5");
|
|
execute("push x 11");
|
|
execute("pop x");
|
|
execute("echo $x");
|
|
execute("greet = [ echo hello, $arg1 ]");
|
|
execute("greet everyone");
|
|
printf("*\n");
|
|
return 0;
|
|
}
|
|
|
|
// XXX ===============================
|