aboutsummaryrefslogtreecommitdiff
path: root/tests/cubescript
diff options
context:
space:
mode:
authorAlon Zakai <azakai@mozilla.com>2010-11-13 14:45:22 -0800
committerAlon Zakai <azakai@mozilla.com>2010-11-13 14:45:22 -0800
commit1b153b74662b198f99a1b7e21d8bc60f562f6feb (patch)
treec82af930836346c2da71d62ca10b5db749865e9f /tests/cubescript
parentcc66f5d1e8e2aa9c0fc57a78b1b896f0a6cb8581 (diff)
rename sauer to cubescript
--HG-- rename : tests/sauer/README => tests/cubescript/README rename : tests/sauer/command.cpp => tests/cubescript/command.cpp rename : tests/sauer/command.h => tests/cubescript/command.h rename : tests/sauer/tools.h => tests/cubescript/tools.h
Diffstat (limited to 'tests/cubescript')
-rw-r--r--tests/cubescript/README32
-rw-r--r--tests/cubescript/command.cpp1427
-rw-r--r--tests/cubescript/command.h204
-rw-r--r--tests/cubescript/tools.h878
4 files changed, 2541 insertions, 0 deletions
diff --git a/tests/cubescript/README b/tests/cubescript/README
new file mode 100644
index 00000000..c3a09528
--- /dev/null
+++ b/tests/cubescript/README
@@ -0,0 +1,32 @@
+This directory contains files from Sauerbraten/Cube 2, http://sauerbraten.org/, whose license is:
+
+Sauerbraten source code license, usage, and documentation.
+
+You may use the Sauerbraten source code if you abide by the ZLIB license
+http://www.opensource.org/licenses/zlib-license.php
+(very similar to the BSD license):
+
+
+LICENSE
+=======
+
+Sauerbraten game engine source code, any release.
+
+Copyright (C) 2001-2010 Wouter van Oortmerssen, Lee Salzman, Mike Dysart, Robert Pointon, and Quinton Reeves
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+
diff --git a/tests/cubescript/command.cpp b/tests/cubescript/command.cpp
new file mode 100644
index 00000000..cfd3d116
--- /dev/null
+++ b/tests/cubescript/command.cpp
@@ -0,0 +1,1427 @@
+// command.cpp: implements the parsing and execution of a tiny script language which
+// is largely backwards compatible with the quake console language.
+
+// XXX Emscripten: changed all sizeof to ES_SIZEOF
+
+// 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, ES_SIZEOF(char)*CONSTRLEN); // XXX Emscripten
+ 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 ===============================
diff --git a/tests/cubescript/command.h b/tests/cubescript/command.h
new file mode 100644
index 00000000..86b10885
--- /dev/null
+++ b/tests/cubescript/command.h
@@ -0,0 +1,204 @@
+// script binding functionality
+
+
+enum { ID_VAR, ID_FVAR, ID_SVAR, ID_COMMAND, ID_CCOMMAND, ID_ALIAS };
+
+enum { NO_OVERRIDE = INT_MAX, OVERRIDDEN = 0 };
+
+enum { IDF_PERSIST = 1<<0, IDF_OVERRIDE = 1<<1, IDF_HEX = 1<<2, IDF_READONLY = 1<<3 };
+
+struct identstack
+{
+ char *action;
+ identstack *next;
+};
+
+union identval
+{
+ int i; // ID_VAR
+ float f; // ID_FVAR
+ char *s; // ID_SVAR
+};
+
+union identvalptr
+{
+ int *i; // ID_VAR
+ float *f; // ID_FVAR
+ char **s; // ID_SVAR
+};
+
+struct ident
+{
+ int type; // one of ID_* above
+ const char *name;
+ union
+ {
+ int minval; // ID_VAR
+ float minvalf; // ID_FVAR
+ };
+ union
+ {
+ int maxval; // ID_VAR
+ float maxvalf; // ID_FVAR
+ };
+ int override; // either NO_OVERRIDE, OVERRIDDEN, or value
+ union
+ {
+ void (__cdecl *fun)(); // ID_VAR, ID_COMMAND, ID_CCOMMAND
+ identstack *stack; // ID_ALIAS
+ };
+ union
+ {
+ const char *narg; // ID_COMMAND, ID_CCOMMAND
+ char *action; // ID_ALIAS
+ identval val; // ID_VAR, ID_FVAR, ID_SVAR
+ };
+ union
+ {
+ void *self; // ID_COMMAND, ID_CCOMMAND
+ char *isexecuting; // ID_ALIAS
+ identval overrideval; // ID_VAR, ID_FVAR, ID_SVAR
+ };
+ identvalptr storage; // ID_VAR, ID_FVAR, ID_SVAR
+ int flags;
+
+ ident() {}
+ // ID_VAR
+ ident(int t, const char *n, int m, int c, int x, int *s, void *f = NULL, int flags = 0)
+ : type(t), name(n), minval(m), maxval(x), override(NO_OVERRIDE), fun((void (__cdecl *)())f), flags(flags | (m > x ? IDF_READONLY : 0))
+ { val.i = c; storage.i = s; }
+ // ID_FVAR
+ ident(int t, const char *n, float m, float c, float x, float *s, void *f = NULL, int flags = 0)
+ : type(t), name(n), minvalf(m), maxvalf(x), override(NO_OVERRIDE), fun((void (__cdecl *)())f), flags(flags | (m > x ? IDF_READONLY : 0))
+ { val.f = c; storage.f = s; }
+ // ID_SVAR
+ ident(int t, const char *n, char *c, char **s, void *f = NULL, int flags = 0)
+ : type(t), name(n), override(NO_OVERRIDE), fun((void (__cdecl *)())f), flags(flags)
+ { val.s = c; storage.s = s; }
+ // ID_ALIAS
+ ident(int t, const char *n, char *a, int flags)
+ : type(t), name(n), override(NO_OVERRIDE), stack(NULL), action(a), isexecuting(NULL), flags(flags) {}
+ // ID_COMMAND, ID_CCOMMAND
+ ident(int t, const char *n, const char *narg, void *f = NULL, void *s = NULL, int flags = 0)
+ : type(t), name(n), override(NO_OVERRIDE), fun((void (__cdecl *)(void))f), narg(narg), self(s), flags(flags) {}
+
+ virtual ~ident() {}
+
+ ident &operator=(const ident &o) { memcpy(this, &o, ES_SIZEOF(ident)); return *this; } // force vtable copy, ugh
+
+ virtual void changed() { if(fun) fun(); }
+};
+
+extern void addident(const char *name, ident *id);
+extern const char *intstr(int v);
+extern void intret(int v);
+extern const char *floatstr(float v);
+extern void floatret(float v);
+extern void result(const char *s);
+
+static inline int parseint(const char *s)
+{
+ return int(strtol(s, NULL, 0));
+}
+
+static inline float parsefloat(const char *s)
+{
+ // not all platforms (windows) can parse hexadecimal integers via strtod
+ char *end;
+ double val = strtod(s, &end);
+ return val || end==s || (*end!='x' && *end!='X') ? float(val) : float(parseint(s));
+}
+
+// nasty macros for registering script functions, abuses globals to avoid excessive infrastructure
+#define COMMANDN(name, fun, nargs) static bool __dummy_##fun = addcommand(#name, (void (*)())fun, nargs)
+#define COMMAND(name, nargs) COMMANDN(name, name, nargs)
+
+#define _VAR(name, global, min, cur, max, persist) int global = variable(#name, min, cur, max, &global, NULL, persist)
+#define VARN(name, global, min, cur, max) _VAR(name, global, min, cur, max, 0)
+#define VARNP(name, global, min, cur, max) _VAR(name, global, min, cur, max, IDF_PERSIST)
+#define VARNR(name, global, min, cur, max) _VAR(name, global, min, cur, max, IDF_OVERRIDE)
+#define VAR(name, min, cur, max) _VAR(name, name, min, cur, max, 0)
+#define VARP(name, min, cur, max) _VAR(name, name, min, cur, max, IDF_PERSIST)
+#define VARR(name, min, cur, max) _VAR(name, name, min, cur, max, IDF_OVERRIDE)
+#define _VARF(name, global, min, cur, max, body, persist) void var_##name(); int global = variable(#name, min, cur, max, &global, var_##name, persist); void var_##name() { body; }
+#define VARFN(name, global, min, cur, max, body) _VARF(name, global, min, cur, max, body, 0)
+#define VARF(name, min, cur, max, body) _VARF(name, name, min, cur, max, body, 0)
+#define VARFP(name, min, cur, max, body) _VARF(name, name, min, cur, max, body, IDF_PERSIST)
+#define VARFR(name, min, cur, max, body) _VARF(name, name, min, cur, max, body, IDF_OVERRIDE)
+
+#define _HVAR(name, global, min, cur, max, persist) int global = variable(#name, min, cur, max, &global, NULL, persist | IDF_HEX)
+#define HVARN(name, global, min, cur, max) _HVAR(name, global, min, cur, max, 0)
+#define HVARNP(name, global, min, cur, max) _HVAR(name, global, min, cur, max, IDF_PERSIST)
+#define HVARNR(name, global, min, cur, max) _HVAR(name, global, min, cur, max, IDF_OVERRIDE)
+#define HVAR(name, min, cur, max) _HVAR(name, name, min, cur, max, 0)
+#define HVARP(name, min, cur, max) _HVAR(name, name, min, cur, max, IDF_PERSIST)
+#define HVARR(name, min, cur, max) _HVAR(name, name, min, cur, max, IDF_OVERRIDE)
+#define _HVARF(name, global, min, cur, max, body, persist) void var_##name(); int global = variable(#name, min, cur, max, &global, var_##name, persist | IDF_HEX); void var_##name() { body; }
+#define HVARFN(name, global, min, cur, max, body) _HVARF(name, global, min, cur, max, body, 0)
+#define HVARF(name, min, cur, max, body) _HVARF(name, name, min, cur, max, body, 0)
+#define HVARFP(name, min, cur, max, body) _HVARF(name, name, min, cur, max, body, IDF_PERSIST)
+#define HVARFR(name, min, cur, max, body) _HVARF(name, name, min, cur, max, body, IDF_OVERRIDE)
+
+#define _FVAR(name, global, min, cur, max, persist) float global = fvariable(#name, min, cur, max, &global, NULL, persist)
+#define FVARN(name, global, min, cur, max) _FVAR(name, global, min, cur, max, 0)
+#define FVARNP(name, global, min, cur, max) _FVAR(name, global, min, cur, max, IDF_PERSIST)
+#define FVARNR(name, global, min, cur, max) _FVAR(name, global, min, cur, max, IDF_OVERRIDE)
+#define FVAR(name, min, cur, max) _FVAR(name, name, min, cur, max, 0)
+#define FVARP(name, min, cur, max) _FVAR(name, name, min, cur, max, IDF_PERSIST)
+#define FVARR(name, min, cur, max) _FVAR(name, name, min, cur, max, IDF_OVERRIDE)
+#define _FVARF(name, global, min, cur, max, body, persist) void var_##name(); float global = fvariable(#name, min, cur, max, &global, var_##name, persist); void var_##name() { body; }
+#define FVARFN(name, global, min, cur, max, body) _FVARF(name, global, min, cur, max, body, 0)
+#define FVARF(name, min, cur, max, body) _FVARF(name, name, min, cur, max, body, 0)
+#define FVARFP(name, min, cur, max, body) _FVARF(name, name, min, cur, max, body, IDF_PERSIST)
+#define FVARFR(name, min, cur, max, body) _FVARF(name, name, min, cur, max, body, IDF_OVERRIDE)
+
+#define _SVAR(name, global, cur, persist) char *global = svariable(#name, cur, &global, NULL, persist)
+#define SVARN(name, global, cur) _SVAR(name, global, cur, 0)
+#define SVARNP(name, global, cur) _SVAR(name, global, cur, IDF_PERSIST)
+#define SVARNR(name, global, cur) _SVAR(name, global, cur, IDF_OVERRIDE)
+#define SVAR(name, cur) _SVAR(name, name, cur, 0)
+#define SVARP(name, cur) _SVAR(name, name, cur, IDF_PERSIST)
+#define SVARR(name, cur) _SVAR(name, name, cur, IDF_OVERRIDE)
+#define _SVARF(name, global, cur, body, persist) void var_##name(); char *global = svariable(#name, cur, &global, var_##name, persist); void var_##name() { body; }
+#define SVARFN(name, global, cur, body) _SVARF(name, global, cur, body, 0)
+#define SVARF(name, cur, body) _SVARF(name, name, cur, body, 0)
+#define SVARFP(name, cur, body) _SVARF(name, name, cur, body, IDF_PERSIST)
+#define SVARFR(name, cur, body) _SVARF(name, name, cur, body, IDF_OVERRIDE)
+
+// new style macros, have the body inline, and allow binds to happen anywhere, even inside class constructors, and access the surrounding class
+#define _CCOMMAND(idtype, tv, n, g, proto, b) \
+ struct _ccmd_##n : ident \
+ { \
+ _ccmd_##n(void *self = NULL) : ident(idtype, #n, g, (void *)run, self) \
+ { \
+ addident(name, this); \
+ } \
+ static void run proto { b; } \
+ } __ccmd_##n tv
+#define CCOMMAND(n, g, proto, b) _CCOMMAND(ID_CCOMMAND, (this), n, g, proto, b)
+
+// anonymous inline commands, uses nasty template trick with line numbers to keep names unique
+#define _ICOMMAND(cmdname, name, nargs, proto, b) template<int N> struct cmdname; template<> struct cmdname<__LINE__> { static bool init; static void run proto; }; bool cmdname<__LINE__>::init = addcommand(name, (void (*)())cmdname<__LINE__>::run, nargs); void cmdname<__LINE__>::run proto \
+ { b; }
+#define ICOMMANDNAME(name) _icmd_##name
+#define ICOMMAND(name, nargs, proto, b) _ICOMMAND(ICOMMANDNAME(name), #name, nargs, proto, b)
+#define ICOMMANDSNAME _icmds_
+#define ICOMMANDS(name, nargs, proto, b) _ICOMMAND(ICOMMANDSNAME, name, nargs, proto, b)
+
+#define _IVAR(n, m, c, x, b, p) \
+ struct var_##n : ident \
+ { \
+ var_##n() : ident(ID_VAR, #n, m, c, x, &val.i, NULL, p) \
+ { \
+ addident(name, this); \
+ } \
+ int operator()() { return val.i; } \
+ b \
+ } n
+#define IVAR(n, m, c, x) _IVAR(n, m, c, x, , 0)
+#define IVARF(n, m, c, x, b) _IVAR(n, m, c, x, void changed() { b; }, 0)
+#define IVARP(n, m, c, x) _IVAR(n, m, c, x, , IDF_PERSIST)
+#define IVARR(n, m, c, x) _IVAR(n, m, c, x, , IDF_OVERRIDE)
+#define IVARFP(n, m, c, x, b) _IVAR(n, m, c, x, void changed() { b; }, IDF_PERSIST)
+#define IVARFR(n, m, c, x, b) _IVAR(n, m, c, x, void changed() { b; }, IDF_OVERRIDE)
+
diff --git a/tests/cubescript/tools.h b/tests/cubescript/tools.h
new file mode 100644
index 00000000..08b528e2
--- /dev/null
+++ b/tests/cubescript/tools.h
@@ -0,0 +1,878 @@
+// generic useful stuff for any C++ program
+
+#ifndef _TOOLS_H
+#define _TOOLS_H
+
+#include "emscripten.h" // XXX Emscripten
+
+#ifdef NULL
+#undef NULL
+#endif
+#define NULL 0
+
+typedef unsigned char uchar;
+typedef unsigned short ushort;
+typedef unsigned int uint;
+
+#ifdef _DEBUG
+#ifdef __GNUC__
+#define ASSERT(c) if(!(c)) { asm("int $3"); }
+#else
+#define ASSERT(c) if(!(c)) { __asm int 3 }
+#endif
+#else
+#define ASSERT(c) if(c) {}
+#endif
+
+#if defined(__GNUC__) || (defined(_MSC_VER) && _MSC_VER >= 1400)
+#define RESTRICT __restrict
+#else
+#define RESTRICT
+#endif
+
+#ifdef swap
+#undef swap
+#endif
+template<class T>
+static inline void swap(T &a, T &b)
+{
+ T t = a;
+ a = b;
+ b = t;
+}
+#ifdef max
+#undef max
+#endif
+#ifdef min
+#undef min
+#endif
+template<class T>
+static inline T max(T a, T b)
+{
+ return a > b ? a : b;
+}
+template<class T>
+static inline T min(T a, T b)
+{
+ return a < b ? a : b;
+}
+
+#define clamp(a,b,c) (max(b, min(a, c)))
+#define rnd(x) ((int)(randomMT()&0xFFFFFF)%(x))
+#define rndscale(x) (float((randomMT()&0xFFFFFF)*double(x)/double(0xFFFFFF)))
+#define detrnd(s, x) ((int)(((((uint)(s))*1103515245+12345)>>16)%(x)))
+
+#define loop(v,m) for(int v = 0; v<int(m); v++)
+#define loopi(m) loop(i,m)
+#define loopj(m) loop(j,m)
+#define loopk(m) loop(k,m)
+#define loopl(m) loop(l,m)
+
+
+#define DELETEP(p) if(p) { delete p; p = 0; }
+#define DELETEA(p) if(p) { delete[] p; p = 0; }
+
+#define PI (3.1415927f)
+#define PI2 (2*PI)
+#define SQRT2 (1.4142136f)
+#define SQRT3 (1.7320508f)
+#define RAD (PI / 180.0f)
+
+#ifdef WIN32
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+#ifndef M_LN2
+#define M_LN2 0.693147180559945309417
+#endif
+
+#ifndef __GNUC__
+#pragma warning (3: 4189) // local variable is initialized but not referenced
+#pragma warning (disable: 4244) // conversion from 'int' to 'float', possible loss of data
+#pragma warning (disable: 4267) // conversion from 'size_t' to 'int', possible loss of data
+#pragma warning (disable: 4355) // 'this' : used in base member initializer list
+#pragma warning (disable: 4996) // 'strncpy' was declared deprecated
+#endif
+
+#define strcasecmp _stricmp
+#define PATHDIV '\\'
+#else
+#define __cdecl
+#define _vsnprintf vsnprintf
+#define PATHDIV '/'
+#endif
+
+// easy safe strings
+
+#define MAXSTRLEN 260
+typedef char string[MAXSTRLEN];
+
+inline void vformatstring(char *d, const char *fmt, va_list v, int len = MAXSTRLEN) { _vsnprintf(d, len, fmt, v); d[len-1] = 0; }
+inline char *copystring(char *d, const char *s, size_t len = MAXSTRLEN) { strncpy(d, s, len); d[len-1] = 0; return d; }
+inline char *concatstring(char *d, const char *s, size_t len = MAXSTRLEN) { size_t used = strlen(d); return used < len ? copystring(d+used, s, len-used) : d; }
+
+struct stringformatter
+{
+ char *buf;
+ stringformatter(char *buf): buf((char *)buf) {}
+ void operator()(const char *fmt, ...)
+ {
+ va_list v;
+ va_start(v, fmt);
+ vformatstring(buf, fmt, v);
+ va_end(v);
+ }
+};
+
+#define formatstring(d) stringformatter((char *)d)
+#define defformatstring(d) string d; formatstring(d)
+#define defvformatstring(d,last,fmt) string d; { va_list ap; va_start(ap, last); vformatstring(d, fmt, ap); va_end(ap); }
+
+#define loopv(v) for(int i = 0; i<(v).length(); i++)
+#define loopvj(v) for(int j = 0; j<(v).length(); j++)
+#define loopvk(v) for(int k = 0; k<(v).length(); k++)
+#define loopvrev(v) for(int i = (v).length()-1; i>=0; i--)
+
+template <class T>
+struct databuf
+{
+ enum
+ {
+ OVERREAD = 1<<0,
+ OVERWROTE = 1<<1
+ };
+
+ T *buf;
+ int len, maxlen;
+ uchar flags;
+
+ databuf() : buf(NULL), len(0), maxlen(0), flags(0) {}
+
+ template<class U>
+ databuf(T *buf, U maxlen) : buf(buf), len(0), maxlen((int)maxlen), flags(0) {}
+
+ const T &get()
+ {
+ static T overreadval;
+ if(len<maxlen) return buf[len++];
+ flags |= OVERREAD;
+ return overreadval;
+ }
+
+ databuf subbuf(int sz)
+ {
+ sz = min(sz, maxlen-len);
+ len += sz;
+ return databuf(&buf[len-sz], sz);
+ }
+
+ void put(const T &val)
+ {
+ if(len<maxlen) buf[len++] = val;
+ else flags |= OVERWROTE;
+ }
+
+ void put(const T *vals, int numvals)
+ {
+ if(maxlen-len<numvals) flags |= OVERWROTE;
+ memcpy(&buf[len], vals, min(maxlen-len, numvals)*ES_SIZEOF(T));
+ len += min(maxlen-len, numvals);
+ }
+
+ int get(T *vals, int numvals)
+ {
+ int read = min(maxlen-len, numvals);
+ if(read<numvals) flags |= OVERREAD;
+ memcpy(vals, &buf[len], read*ES_SIZEOF(T));
+ len += read;
+ return read;
+ }
+
+ int length() const { return len; }
+ int remaining() const { return maxlen-len; }
+ bool overread() const { return (flags&OVERREAD)!=0; }
+ bool overwrote() const { return (flags&OVERWROTE)!=0; }
+
+ void forceoverread()
+ {
+ len = maxlen;
+ flags |= OVERREAD;
+ }
+};
+
+typedef databuf<char> charbuf;
+typedef databuf<uchar> ucharbuf;
+
+template<class T>
+static inline float heapscore(const T &n) { return n; }
+
+template<class T, class U>
+static inline void quicksort(T *buf, int n, int (__cdecl *func)(U *, U *))
+{
+ qsort(buf, n, ES_SIZEOF(T), (int (__cdecl *)(const void *,const void *))func);
+}
+
+template <class T> struct vector
+{
+ static const int MINSIZE = 8;
+
+ T *buf;
+ int alen, ulen;
+
+ vector() : buf(NULL), alen(0), ulen(0)
+ {
+ }
+
+ vector(const vector &v) : buf(NULL), alen(0), ulen(0)
+ {
+ *this = v;
+ }
+
+ ~vector() { shrink(0); if(buf) delete[] (uchar *)buf; }
+
+ vector<T> &operator=(const vector<T> &v)
+ {
+ shrink(0);
+ if(v.length() > alen) growbuf(v.length());
+ loopv(v) add(v[i]);
+ return *this;
+ }
+
+ T &add(const T &x)
+ {
+ if(ulen==alen) growbuf(ulen+1);
+ new (&buf[ulen]) T(x);
+ return buf[ulen++];
+ }
+
+ T &add()
+ {
+ if(ulen==alen) growbuf(ulen+1);
+ new (&buf[ulen]) T;
+ return buf[ulen++];
+ }
+
+ T &dup()
+ {
+ if(ulen==alen) growbuf(ulen+1);
+ new (&buf[ulen]) T(buf[ulen-1]);
+ return buf[ulen++];
+ }
+
+ void move(vector<T> &v)
+ {
+ if(!ulen)
+ {
+ swap(buf, v.buf);
+ swap(ulen, v.ulen);
+ swap(alen, v.alen);
+ }
+ else
+ {
+ growbuf(ulen+v.ulen);
+ if(v.ulen) memcpy(&buf[ulen], v.buf, v.ulen*ES_SIZEOF(T));
+ ulen += v.ulen;
+ v.ulen = 0;
+ }
+ }
+
+ bool inrange(size_t i) const { return i<size_t(ulen); }
+ bool inrange(int i) const { return i>=0 && i<ulen; }
+
+ T &pop() { return buf[--ulen]; }
+ T &last() { return buf[ulen-1]; }
+ void drop() { ulen--; buf[ulen].~T(); }
+ bool empty() const { return ulen==0; }
+
+ int capacity() const { return alen; }
+ int length() const { return ulen; }
+ T &operator[](int i) { ASSERT(i>=0 && i<ulen); return buf[i]; }
+ const T &operator[](int i) const { ASSERT(i >= 0 && i<ulen); return buf[i]; }
+
+ void shrink(int i) { ASSERT(i<=ulen); while(ulen>i) drop(); }
+ void setsize(int i) { ASSERT(i<=ulen); ulen = i; }
+
+ void deletecontents() { while(!empty()) delete pop(); }
+ void deletearrays() { while(!empty()) delete[] pop(); }
+
+ T *getbuf() { return buf; }
+ const T *getbuf() const { return buf; }
+ bool inbuf(const T *e) const { return e >= buf && e < &buf[ulen]; }
+
+ template<class ST>
+ void sort(int (__cdecl *cf)(ST *, ST *), int i = 0, int n = -1)
+ {
+ quicksort(&buf[i], n < 0 ? ulen : n, cf);
+ }
+
+ void growbuf(int sz)
+ {
+ int olen = alen;
+ if(!alen) alen = max(MINSIZE, sz);
+ else while(alen < sz) alen *= 2;
+ if(alen <= olen) return;
+ uchar *newbuf = new uchar[alen*ES_SIZEOF(T)];
+ if(olen > 0)
+ {
+ memcpy(newbuf, buf, olen*ES_SIZEOF(T));
+ delete[] (uchar *)buf;
+ }
+ buf = (T *)newbuf;
+ }
+
+ databuf<T> reserve(int sz)
+ {
+ if(ulen+sz > alen) growbuf(ulen+sz);
+ return databuf<T>(&buf[ulen], sz);
+ }
+
+ void advance(int sz)
+ {
+ ulen += sz;
+ }
+
+ void addbuf(const databuf<T> &p)
+ {
+ advance(p.length());
+ }
+
+ T *pad(int n)
+ {
+ T *buf = reserve(n).buf;
+ advance(n);
+ return buf;
+ }
+
+ void put(const T &v) { add(v); }
+
+ void put(const T *v, int n)
+ {
+ databuf<T> buf = reserve(n);
+ buf.put(v, n);
+ addbuf(buf);
+ }
+
+ void remove(int i, int n)
+ {
+ for(int p = i+n; p<ulen; p++) buf[p-n] = buf[p];
+ ulen -= n;
+ }
+
+ T remove(int i)
+ {
+ T e = buf[i];
+ for(int p = i+1; p<ulen; p++) buf[p-1] = buf[p];
+ ulen--;
+ return e;
+ }
+
+ T removeunordered(int i)
+ {
+ T e = buf[i];
+ ulen--;
+ if(ulen>0) buf[i] = buf[ulen];
+ return e;
+ }
+
+ template<class U>
+ int find(const U &o)
+ {
+ loopi(ulen) if(buf[i]==o) return i;
+ return -1;
+ }
+
+ void removeobj(const T &o)
+ {
+ loopi(ulen) if(buf[i]==o) remove(i--);
+ }
+
+ void replacewithlast(const T &o)
+ {
+ if(!ulen) return;
+ loopi(ulen-1) if(buf[i]==o)
+ {
+ buf[i] = buf[ulen-1];
+ }
+ ulen--;
+ }
+
+ T &insert(int i, const T &e)
+ {
+ add(T());
+ for(int p = ulen-1; p>i; p--) buf[p] = buf[p-1];
+ buf[i] = e;
+ return buf[i];
+ }
+
+ T *insert(int i, const T *e, int n)
+ {
+ if(ulen+n>alen) growbuf(ulen+n);
+ loopj(n) add(T());
+ for(int p = ulen-1; p>=i+n; p--) buf[p] = buf[p-n];
+ loopj(n) buf[i+j] = e[j];
+ return &buf[i];
+ }
+
+ void reverse()
+ {
+ loopi(ulen/2) swap(buf[i], buf[ulen-1-i]);
+ }
+
+ static int heapparent(int i) { return (i - 1) >> 1; }
+ static int heapchild(int i) { return (i << 1) + 1; }
+
+ void buildheap()
+ {
+ for(int i = ulen/2; i >= 0; i--) downheap(i);
+ }
+
+ int upheap(int i)
+ {
+ float score = heapscore(buf[i]);
+ while(i > 0)
+ {
+ int pi = heapparent(i);
+ if(score >= heapscore(buf[pi])) break;
+ swap(buf[i], buf[pi]);
+ i = pi;
+ }
+ return i;
+ }
+
+ T &addheap(const T &x)
+ {
+ add(x);
+ return buf[upheap(ulen-1)];
+ }
+
+ int downheap(int i)
+ {
+ float score = heapscore(buf[i]);
+ for(;;)
+ {
+ int ci = heapchild(i);
+ if(ci >= ulen) break;
+ float cscore = heapscore(buf[ci]);
+ if(score > cscore)
+ {
+ if(ci+1 < ulen && heapscore(buf[ci+1]) < cscore) { swap(buf[ci+1], buf[i]); i = ci+1; }
+ else { swap(buf[ci], buf[i]); i = ci; }
+ }
+ else if(ci+1 < ulen && heapscore(buf[ci+1]) < score) { swap(buf[ci+1], buf[i]); i = ci+1; }
+ else break;
+ }
+ return i;
+ }
+
+ T removeheap()
+ {
+ T e = removeunordered(0);
+ if(ulen) downheap(0);
+ return e;
+ }
+};
+
+static inline uint hthash(const char *key)
+{
+ uint h = 5381;
+ for(int i = 0, k; (k = key[i]); i++) h = ((h<<5)+h)^k; // bernstein k=33 xor
+ return h;
+}
+
+static inline bool htcmp(const char *x, const char *y)
+{
+ return !strcmp(x, y);
+}
+
+static inline uint hthash(int key)
+{
+ return key;
+}
+
+static inline bool htcmp(int x, int y)
+{
+ return x==y;
+}
+
+template<class T> struct hashset
+{
+ typedef T elem;
+ typedef const T const_elem;
+
+ enum { CHUNKSIZE = 64 };
+
+ struct chain { T elem; chain *next; };
+ struct chainchunk { chain chains[CHUNKSIZE]; chainchunk *next; };
+
+ int size;
+ int numelems;
+ chain **chains;
+
+ chainchunk *chunks;
+ chain *unused;
+
+ hashset(int size = 1<<10)
+ : size(size)
+ {
+ numelems = 0;
+ chunks = NULL;
+ unused = NULL;
+ chains = new chain *[size];
+ loopi(size) chains[i] = NULL;
+ }
+
+ ~hashset()
+ {
+ DELETEA(chains);
+ deletechunks();
+ }
+
+ chain *insert(uint h)
+ {
+ if(!unused)
+ {
+ chainchunk *chunk = new chainchunk;
+ chunk->next = chunks;
+ chunks = chunk;
+ loopi(CHUNKSIZE-1) chunk->chains[i].next = &chunk->chains[i+1];
+ chunk->chains[CHUNKSIZE-1].next = unused;
+ unused = chunk->chains;
+ }
+ chain *c = unused;
+ unused = unused->next;
+ c->next = chains[h];
+ chains[h] = c;
+ numelems++;
+ return c;
+ }
+
+ #define HTFIND(key, success, fail) \
+ uint h = hthash(key)&(this->size-1); \
+ for(chain *c = this->chains[h]; c; c = c->next) \
+ { \
+ if(htcmp(key, c->elem)) return (success); \
+ } \
+ return (fail);
+
+ template<class K>
+ T *access(const K &key)
+ {
+ HTFIND(key, &c->elem, NULL);
+ }
+
+ template<class K>
+ T &access(const K &key, const T &elem)
+ {
+ HTFIND(key, c->elem, insert(h)->elem = elem);
+ }
+
+ template<class K>
+ T &operator[](const K &key)
+ {
+ HTFIND(key, c->elem, insert(h)->elem);
+ }
+
+ template<class K>
+ bool remove(const K &key)
+ {
+ uint h = hthash(key)&(size-1);
+ for(chain **p = &chains[h], *c = chains[h]; c; p = &c->next, c = c->next)
+ {
+ if(htcmp(key, c->elem))
+ {
+ *p = c->next;
+ c->elem.~T();
+ new (&c->elem) T;
+ c->next = unused;
+ unused = c;
+ numelems--;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void deletechunks()
+ {
+ for(chainchunk *nextchunk; chunks; chunks = nextchunk)
+ {
+ nextchunk = chunks->next;
+ delete chunks;
+ }
+ }
+
+ void clear()
+ {
+ if(!numelems) return;
+ loopi(size) chains[i] = NULL;
+ numelems = 0;
+ unused = NULL;
+ deletechunks();
+ }
+
+ static inline chain *getnext(void *i) { return ((chain *)i)->next; }
+ static inline T &getdata(void *i) { return ((chain *)i)->elem; }
+};
+
+template<class K, class T> struct hashtableentry
+{
+ K key;
+ T data;
+
+ hashtableentry() {}
+ hashtableentry(const K &key, const T &data) : key(key), data(data) {}
+};
+
+template<class U, class K, class T>
+static inline bool htcmp(const U *x, const hashtableentry<K, T> &y)
+{
+ return htcmp(x, y.key);
+}
+
+template<class U, class K, class T>
+static inline bool htcmp(const U &x, const hashtableentry<K, T> &y)
+{
+ return htcmp(x, y.key);
+}
+
+template<class K, class T> struct hashtable : hashset<hashtableentry<K, T> >
+{
+ typedef hashtableentry<K, T> entry;
+ typedef struct hashset<entry>::chain chain;
+ typedef K key;
+ typedef T value;
+
+ hashtable(int size = 1<<10) : hashset<entry>(size) {}
+
+ entry &insert(const K &key, uint h)
+ {
+ chain *c = hashset<entry>::insert(h);
+ c->elem.key = key;
+ return c->elem;
+ }
+
+ T *access(const K &key)
+ {
+ HTFIND(key, &c->elem.data, NULL);
+ }
+
+ T &access(const K &key, const T &data)
+ {
+ HTFIND(key, c->elem.data, insert(key, h).data = data);
+ }
+
+ T &operator[](const K &key)
+ {
+ HTFIND(key, c->elem.data, insert(key, h).data);
+ }
+
+ static inline chain *getnext(void *i) { return ((chain *)i)->next; }
+ static inline K &getkey(void *i) { return ((chain *)i)->elem.key; }
+ static inline T &getdata(void *i) { return ((chain *)i)->elem.data; }
+};
+
+#define enumerates(ht,t,e,b) loopi((ht).size) for(hashset<t>::chain *enumc = (ht).chains[i]; enumc;) { t &e = enumc->elem; enumc = enumc->next; b; }
+#define enumeratekt(ht,k,e,t,f,b) loopi((ht).size) for(hashtable<k,t>::chain *enumc = (ht).chains[i]; enumc;) { const hashtable<k,t>::key &e = enumc->elem.key; t &f = enumc->elem.data; enumc = enumc->next; b; }
+#define enumerate(ht,t,e,b) loopi((ht).size) for(void *enumc = (ht).chains[i]; enumc;) { t &e = (ht).getdata(enumc); enumc = (ht).getnext(enumc); b; }
+
+struct unionfind
+{
+ struct ufval
+ {
+ int rank, next;
+
+ ufval() : rank(0), next(-1) {}
+ };
+
+ vector<ufval> ufvals;
+
+ int find(int k)
+ {
+ if(k>=ufvals.length()) return k;
+ while(ufvals[k].next>=0) k = ufvals[k].next;
+ return k;
+ }
+
+ int compressfind(int k)
+ {
+ if(ufvals[k].next<0) return k;
+ return ufvals[k].next = compressfind(ufvals[k].next);
+ }
+
+ void unite (int x, int y)
+ {
+ while(ufvals.length() <= max(x, y)) ufvals.add();
+ x = compressfind(x);
+ y = compressfind(y);
+ if(x==y) return;
+ ufval &xval = ufvals[x], &yval = ufvals[y];
+ if(xval.rank < yval.rank) xval.next = y;
+ else
+ {
+ yval.next = x;
+ if(xval.rank==yval.rank) yval.rank++;
+ }
+ }
+};
+
+template <class T, int SIZE> struct ringbuf
+{
+ int index, len;
+ T data[SIZE];
+
+ ringbuf() { clear(); }
+
+ void clear()
+ {
+ index = len = 0;
+ }
+
+ bool empty() const { return !len; }
+
+ const int length() const { return len; }
+
+ T &add(const T &e)
+ {
+ T &t = (data[index] = e);
+ index++;
+ if(index >= SIZE) index -= SIZE;
+ if(len < SIZE) len++;
+ return t;
+ }
+
+ T &add() { return add(T()); }
+
+ T &operator[](int i)
+ {
+ i += index - len;
+ return data[i < 0 ? i + SIZE : i%SIZE];
+ }
+
+ const T &operator[](int i) const
+ {
+ i += index - len;
+ return data[i < 0 ? i + SIZE : i%SIZE];
+ }
+};
+
+template <class T, int SIZE> struct queue
+{
+ int head, tail, len;
+ T data[SIZE];
+
+ queue() { clear(); }
+
+ void clear() { head = tail = len = 0; }
+
+ int length() const { return len; }
+ bool empty() const { return !len; }
+ bool full() const { return len == SIZE; }
+
+ T &added() { return data[tail > 0 ? tail-1 : SIZE-1]; }
+ T &added(int offset) { return data[tail-offset > 0 ? tail-offset-1 : tail-offset-1 + SIZE]; }
+ T &adding() { return data[tail]; }
+ T &adding(int offset) { return data[tail+offset >= SIZE ? tail+offset - SIZE : tail+offset]; }
+ T &add()
+ {
+ ASSERT(len < SIZE);
+ T &t = data[tail];
+ tail = (tail + 1)%SIZE;
+ len++;
+ return t;
+ }
+
+ T &removing() { return data[head]; }
+ T &removing(int offset) { return data[head+offset >= SIZE ? head+offset - SIZE : head+offset]; }
+ T &remove()
+ {
+ ASSERT(len > 0);
+ T &t = data[head];
+ head = (head + 1)%SIZE;
+ len--;
+ return t;
+ }
+};
+
+inline char *newstring(size_t l) { return new char[l+1]; }
+inline char *newstring(const char *s, size_t l) { return copystring(newstring(l), s, l+1); }
+inline char *newstring(const char *s) { return newstring(s, strlen(s)); }
+inline char *newstringbuf(const char *s) { return newstring(s, MAXSTRLEN-1); }
+
+#if defined(WIN32) && !defined(__GNUC__)
+#ifdef _DEBUG
+//#define _CRTDBG_MAP_ALLOC
+#include <crtdbg.h>
+inline void *__cdecl operator new(size_t n, const char *fn, int l) { return ::operator new(n, 1, fn, l); }
+inline void __cdecl operator delete(void *p, const char *fn, int l) { ::operator delete(p, 1, fn, l); }
+#define new new(__FILE__,__LINE__)
+#endif
+#endif
+
+const int islittleendian = 1;
+#ifdef SDL_BYTEORDER
+#define endianswap16 SDL_Swap16
+#define endianswap32 SDL_Swap32
+#else
+inline ushort endianswap16(ushort n) { return (n<<8) | (n>>8); }
+inline uint endianswap32(uint n) { return (n<<24) | (n>>24) | ((n>>8)&0xFF00) | ((n<<8)&0xFF0000); }
+#endif
+template<class T> inline T endianswap(T n) { union { T t; uint i; } conv; conv.t = n; conv.i = endianswap32(conv.i); return conv.t; }
+template<> inline ushort endianswap<ushort>(ushort n) { return endianswap16(n); }
+template<> inline short endianswap<short>(short n) { return endianswap16(n); }
+template<> inline uint endianswap<uint>(uint n) { return endianswap32(n); }
+template<> inline int endianswap<int>(int n) { return endianswap32(n); }
+template<class T> inline void endianswap(T *buf, int len) { for(T *end = &buf[len]; buf < end; buf++) *buf = endianswap(*buf); }
+template<class T> inline T endiansame(T n) { return n; }
+template<class T> inline void endiansame(T *buf, int len) {}
+#ifdef SDL_BYTEORDER
+#if SDL_BYTEORDER == SDL_LIL_ENDIAN
+#define lilswap endiansame
+#define bigswap endianswap
+#else
+#define lilswap endianswap
+#define bigswap endiansame
+#endif
+#else
+template<class T> inline T lilswap(T n) { return *(const uchar *)&islittleendian ? n : endianswap(n); }
+template<class T> inline void lilswap(T *buf, int len) { if(!*(const uchar *)&islittleendian) endianswap(buf, len); }
+template<class T> inline T bigswap(T n) { return *(const uchar *)&islittleendian ? endianswap(n) : n; }
+template<class T> inline void bigswap(T *buf, int len) { if(*(const uchar *)&islittleendian) endianswap(buf, len); }
+#endif
+
+/* workaround for some C platforms that have these two functions as macros - not used anywhere */
+#ifdef getchar
+#undef getchar
+#endif
+#ifdef putchar
+#undef putchar
+#endif
+
+struct stream
+{
+ virtual ~stream() {}
+ virtual void close() = 0;
+ virtual bool end() = 0;
+ virtual long tell() { return -1; }
+ virtual bool seek(long offset, int whence = SEEK_SET) { return false; }
+ virtual long size();
+ virtual int read(void *buf, int len) { return 0; }
+ virtual int write(const void *buf, int len) { return 0; }
+ virtual int getchar() { uchar c; return read(&c, 1) == 1 ? c : -1; }
+ virtual bool putchar(int n) { uchar c = n; return write(&c, 1) == 1; }
+ virtual bool getline(char *str, int len);
+ virtual bool putstring(const char *str) { int len = (int)strlen(str); return write(str, len) == len; }
+ virtual bool putline(const char *str) { return putstring(str) && putchar('\n'); }
+ virtual int printf(const char *fmt, ...) { return -1; }
+ virtual uint getcrc() { return 0; }
+
+ template<class T> bool put(T n) { return write(&n, ES_SIZEOV(n)) == ES_SIZEOV(n); }
+ template<class T> bool putlil(T n) { return put<T>(lilswap(n)); }
+ template<class T> bool putbig(T n) { return put<T>(bigswap(n)); }
+
+ template<class T> T get() { T n; return read(&n, ES_SIZEOV(n)) == ES_SIZEOV(n) ? n : 0; }
+ template<class T> T getlil() { return lilswap(get<T>()); }
+ template<class T> T getbig() { return bigswap(get<T>()); }
+};
+
+#endif
+