OctaCore/src/engine/command.cc

4329 lines
141 KiB
C++

// command.cpp: implements the parsing and execution of a tiny script language which
// is largely backwards compatible with the quake console language.
#include "command.hh"
#include <cctype>
#include <climits>
#include <algorithm>
#include <sauerlib/encoding.hh>
#include <sauerlib/stream.hh>
#include <shared/igame.hh>
#include <shared/zip.hh>
#include "console.hh"
#include "main.hh" // fatal, timings
hashnameset<ident> idents; // contains ALL vars/commands/aliases
vector<ident *> identmap;
ident *dummyident = nullptr;
int identflags = 0;
enum
{
MAXARGS = 25,
MAXRESULTS = 7,
MAXCOMARGS = 12
};
VARN(numargs, _numargs, MAXARGS, 0, 0);
static inline void freearg(tagval &v)
{
switch(v.type)
{
case VAL_STR: delete[] v.s; break;
case VAL_CODE: if(v.code[-1] == CODE_START) delete[] (uchar *)&v.code[-1]; break;
}
}
static inline void forcenull(tagval &v)
{
switch(v.type)
{
case VAL_NULL: return;
}
freearg(v);
v.setnull();
}
static inline float forcefloat(tagval &v)
{
float f = 0.0f;
switch(v.type)
{
case VAL_INT: f = v.i; break;
case VAL_STR: case VAL_MACRO: case VAL_CSTR: f = parsefloat(v.s); break;
case VAL_FLOAT: return v.f;
}
freearg(v);
v.setfloat(f);
return f;
}
static inline int forceint(tagval &v)
{
int i = 0;
switch(v.type)
{
case VAL_FLOAT: i = v.f; break;
case VAL_STR: case VAL_MACRO: case VAL_CSTR: i = parseint(v.s); break;
case VAL_INT: return v.i;
}
freearg(v);
v.setint(i);
return i;
}
static inline const char *forcestr(tagval &v)
{
const char *s = "";
switch(v.type)
{
case VAL_FLOAT: s = floatstr(v.f); break;
case VAL_INT: s = intstr(v.i); break;
case VAL_MACRO: case VAL_CSTR: s = v.s; break;
case VAL_STR: return v.s;
}
freearg(v);
v.setstr(newstring(s));
return s;
}
static inline void forcearg(tagval &v, int type)
{
switch(type)
{
case RET_STR: if(v.type != VAL_STR) forcestr(v); break;
case RET_INT: if(v.type != VAL_INT) forceint(v); break;
case RET_FLOAT: if(v.type != VAL_FLOAT) forcefloat(v); break;
}
}
void tagval::cleanup()
{
freearg(*this);
}
static inline void freeargs(tagval *args, int &oldnum, int newnum)
{
for(int i = newnum; i < oldnum; i++) freearg(args[i]);
oldnum = newnum;
}
static inline void cleancode(ident &id)
{
if(id.code)
{
id.code[0] -= 0x100;
if(int(id.code[0]) < 0x100) delete[] id.code;
id.code = nullptr;
}
}
struct nullval : tagval
{
nullval() { setnull(); }
} nullval;
tagval noret = nullval, *commandret = &noret;
void clear_command()
{
enumerate(idents, ident, i,
{
if(i.type==ID_ALIAS)
{
DELETEA(i.name);
i.forcenull();
DELETEA(i.code);
}
});
}
void clearoverride(ident &i)
{
if(!(i.flags&IDF_OVERRIDDEN)) return;
switch(i.type)
{
case ID_ALIAS:
if(i.valtype==VAL_STR)
{
if(!i.val.s[0]) break;
delete[] i.val.s;
}
cleancode(i);
i.valtype = VAL_STR;
i.val.s = 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.flags &= ~IDF_OVERRIDDEN;
}
void clearoverrides()
{
enumerate(idents, ident, i, clearoverride(i));
}
static bool initedidents = false;
static vector<ident> *identinits = nullptr;
static inline ident *addident(const ident &id)
{
if(!initedidents)
{
if(!identinits) identinits = new vector<ident>;
identinits->add(id);
return nullptr;
}
ident &def = idents.access(id.name, id);
def.index = identmap.length();
return identmap.add(&def);
}
static bool initidents()
{
initedidents = true;
for(int i = 0; i < MAXARGS; i++)
{
defformatstring(argname, "arg%d", i+1);
newident(argname, IDF_ARG);
}
dummyident = newident("//dummy", IDF_UNKNOWN);
if(identinits)
{
loopv(*identinits) addident((*identinits)[i]);
DELETEP(identinits);
}
return true;
}
[[maybe_unused]] static bool forceinitidents = initidents();
static const char *sourcefile = nullptr, *sourcestr = nullptr;
static const char *debugline(const char *p, const char *fmt)
{
if(!sourcestr) return fmt;
int num = 1;
const char *line = sourcestr;
for(;;)
{
const char *end = strchr(line, '\n');
if(!end) end = line + strlen(line);
if(p >= line && p <= end)
{
static string buf;
if(sourcefile) formatstring(buf, "%s:%d: %s", sourcefile, num, fmt);
else formatstring(buf, "%d: %s", num, fmt);
return buf;
}
if(!*end) break;
line = end + 1;
num++;
}
return fmt;
}
static struct identlink
{
ident *id;
identlink *next;
int usedargs;
identstack *argstack;
} noalias = { nullptr, nullptr, (1<<MAXARGS)-1, nullptr }, *aliasstack = &noalias;
VAR(dbgalias, 0, 4, 1000);
static void debugalias()
{
if(!dbgalias) return;
int total = 0, depth = 0;
for(identlink *l = aliasstack; l != &noalias; l = l->next) total++;
for(identlink *l = aliasstack; l != &noalias; l = l->next)
{
ident *id = l->id;
++depth;
if(depth < dbgalias) conoutf(CON_ERROR, " %d) %s", total-depth+1, id->name);
else if(l->next == &noalias) conoutf(CON_ERROR, depth == dbgalias ? " %d) %s" : " ..%d) %s", total-depth+1, id->name);
}
}
static int nodebug = 0;
static void debugcode(const char *fmt, ...) PRINTFARGS(1, 2);
static void debugcode(const char *fmt, ...)
{
if(nodebug) return;
va_list args;
va_start(args, fmt);
conoutfv(CON_ERROR, fmt, args);
va_end(args);
debugalias();
}
static void debugcodeline(const char *p, const char *fmt, ...) PRINTFARGS(2, 3);
static void debugcodeline(const char *p, const char *fmt, ...)
{
if(nodebug) return;
va_list args;
va_start(args, fmt);
conoutfv(CON_ERROR, debugline(p, fmt), args);
va_end(args);
debugalias();
}
ICOMMAND(nodebug, "e", (uint *body), { nodebug++; executeret(body, *commandret); nodebug--; });
void addident(ident *id)
{
addident(*id);
}
static inline void pusharg(ident &id, const tagval &v, identstack &stack)
{
stack.val = id.val;
stack.valtype = id.valtype;
stack.next = id.stack;
id.stack = &stack;
id.setval(v);
cleancode(id);
}
static inline void poparg(ident &id)
{
if(!id.stack) return;
identstack *stack = id.stack;
if(id.valtype == VAL_STR) delete[] id.val.s;
id.setval(*stack);
cleancode(id);
id.stack = stack->next;
}
static inline void undoarg(ident &id, identstack &stack)
{
identstack *prev = id.stack;
stack.val = id.val;
stack.valtype = id.valtype;
stack.next = prev;
id.stack = prev->next;
id.setval(*prev);
cleancode(id);
}
#define UNDOFLAG (1<<MAXARGS)
#define UNDOARGS \
identstack argstack[MAXARGS]; \
identlink *prevstack = aliasstack; \
identlink aliaslink; \
for(int undos = 0; prevstack != &noalias; prevstack = prevstack->next) \
{ \
if(prevstack->usedargs & UNDOFLAG) ++undos; \
else if(undos > 0) --undos; \
else \
{ \
prevstack = prevstack->next; \
for(int argmask = aliasstack->usedargs & ~UNDOFLAG, i = 0; argmask; argmask >>= 1, i++) if(argmask&1) \
undoarg(*identmap[i], argstack[i]); \
aliaslink.id = aliasstack->id; \
aliaslink.next = aliasstack; \
aliaslink.usedargs = UNDOFLAG | prevstack->usedargs; \
aliaslink.argstack = prevstack->argstack; \
aliasstack = &aliaslink; \
break; \
} \
} \
static inline void redoarg(ident &id, const identstack &stack)
{
identstack *prev = stack.next;
prev->val = id.val;
prev->valtype = id.valtype;
id.stack = prev;
id.setval(stack);
cleancode(id);
}
#define REDOARGS \
if(aliasstack == &aliaslink) \
{ \
prevstack->usedargs |= aliaslink.usedargs & ~UNDOFLAG; \
aliasstack = aliaslink.next; \
for(int argmask = aliasstack->usedargs & ~UNDOFLAG, i = 0; argmask; argmask >>= 1, i++) if(argmask&1) \
redoarg(*identmap[i], argstack[i]); \
}
ICOMMAND(push, "rTe", (ident *id, tagval *v, uint *code),
{
if(id->type != ID_ALIAS || id->index < MAXARGS) return;
identstack stack;
pusharg(*id, *v, stack);
v->type = VAL_NULL;
id->flags &= ~IDF_UNKNOWN;
executeret(code, *commandret);
poparg(*id);
});
static inline void pushalias(ident &id, identstack &stack)
{
if(id.type == ID_ALIAS && id.index >= MAXARGS)
{
pusharg(id, nullval, stack);
id.flags &= ~IDF_UNKNOWN;
}
}
static inline void popalias(ident &id)
{
if(id.type == ID_ALIAS && id.index >= MAXARGS) poparg(id);
}
KEYWORD(local, ID_LOCAL);
static inline bool checknumber(const char *s)
{
if(isdigit(s[0])) return true;
else switch(s[0])
{
case '+': case '-': return isdigit(s[1]) || (s[1] == '.' && isdigit(s[2]));
case '.': return isdigit(s[1]) != 0;
default: return false;
}
}
static inline bool checknumber(const stringslice &s) { return checknumber(s.str); }
template<class T> static inline ident *newident(const T &name, int flags)
{
ident *id = idents.access(name);
if(!id)
{
if(checknumber(name))
{
debugcode("number %.*s is not a valid identifier name", stringlen(name), stringptr(name));
return dummyident;
}
id = addident(ident(ID_ALIAS, newstring(name), flags));
}
return id;
}
static inline ident *forceident(tagval &v)
{
switch(v.type)
{
case VAL_IDENT: return v.id;
case VAL_MACRO: case VAL_CSTR:
{
ident *id = newident(v.s, IDF_UNKNOWN);
v.setident(id);
return id;
}
case VAL_STR:
{
ident *id = newident(v.s, IDF_UNKNOWN);
delete[] v.s;
v.setident(id);
return id;
}
}
freearg(v);
v.setident(dummyident);
return dummyident;
}
ident *newident(const char *name, int flags)
{
return newident<const char *>(name, flags);
}
ident *writeident(const char *name, int flags)
{
ident *id = newident(name, flags);
if(id->index < MAXARGS && !(aliasstack->usedargs&(1<<id->index)))
{
pusharg(*id, nullval, aliasstack->argstack[id->index]);
aliasstack->usedargs |= 1<<id->index;
}
return id;
}
ident *readident(const char *name)
{
ident *id = idents.access(name);
if(id && id->index < MAXARGS && !(aliasstack->usedargs&(1<<id->index)))
return nullptr;
return id;
}
void resetvar(char *name)
{
ident *id = idents.access(name);
if(!id) return;
if(id->flags&IDF_READONLY) debugcode("variable %s is read-only", id->name);
else clearoverride(*id);
}
COMMAND(resetvar, "s");
static inline void setarg(ident &id, tagval &v)
{
if(aliasstack->usedargs&(1<<id.index))
{
if(id.valtype == VAL_STR) delete[] id.val.s;
id.setval(v);
cleancode(id);
}
else
{
pusharg(id, v, aliasstack->argstack[id.index]);
aliasstack->usedargs |= 1<<id.index;
}
}
static inline void setalias(ident &id, tagval &v)
{
if(id.valtype == VAL_STR) delete[] id.val.s;
id.setval(v);
cleancode(id);
id.flags = (id.flags & identflags) | identflags;
}
static void setalias(const char *name, tagval &v)
{
ident *id = idents.access(name);
if(id)
{
switch(id->type)
{
case ID_ALIAS:
if(id->index < MAXARGS) setarg(*id, v); else setalias(*id, v);
return;
case ID_VAR:
setvarchecked(id, v.getint());
break;
case ID_FVAR:
setfvarchecked(id, v.getfloat());
break;
case ID_SVAR:
setsvarchecked(id, v.getstr());
break;
default:
debugcode("cannot redefine builtin %s with an alias", id->name);
break;
}
freearg(v);
}
else if(checknumber(name))
{
debugcode("cannot alias number %s", name);
freearg(v);
}
else
{
addident(ident(ID_ALIAS, newstring(name), v, identflags));
}
}
void alias(const char *name, const char *str)
{
tagval v;
v.setstr(newstring(str));
setalias(name, v);
}
void alias(const char *name, tagval &v)
{
setalias(name, v);
}
ICOMMAND(alias, "sT", (const char *name, tagval *v),
{
setalias(name, *v);
v->type = VAL_NULL;
});
// variable's and commands are registered through globals, see cube.h
int variable(const char *name, int min, int cur, int max, int *storage, identfun fun, int flags)
{
addident(ident(ID_VAR, name, min, max, storage, (void *)fun, flags));
return cur;
}
float fvariable(const char *name, float min, float cur, float max, float *storage, identfun fun, int flags)
{
addident(ident(ID_FVAR, name, min, max, storage, (void *)fun, flags));
return cur;
}
char *svariable(const char *name, const char *cur, char **storage, identfun fun, int flags)
{
addident(ident(ID_SVAR, name, storage, (void *)fun, flags));
return newstring(cur);
}
struct defvar : identval
{
char *name;
uint *onchange;
defvar() : name(nullptr), onchange(nullptr) {}
~defvar()
{
DELETEA(name);
if(onchange) freecode(onchange);
}
static void changed(ident *id)
{
defvar *v = (defvar *)id->storage.p;
if(v->onchange) execute(v->onchange);
}
};
hashnameset<defvar> defvars;
#define DEFVAR(cmdname, fmt, args, body) \
ICOMMAND(cmdname, fmt, args, \
{ \
if(idents.access(name)) { debugcode("cannot redefine %s as a variable", name); return; } \
name = newstring(name); \
defvar &def = defvars[name]; \
def.name = name; \
def.onchange = onchange[0] ? compilecode(onchange) : nullptr; \
body; \
});
#define DEFIVAR(cmdname, flags) \
DEFVAR(cmdname, "siiis", (char *name, int *min, int *cur, int *max, char *onchange), \
def.i = variable(name, *min, *cur, *max, &def.i, def.onchange ? defvar::changed : nullptr, flags))
#define DEFFVAR(cmdname, flags) \
DEFVAR(cmdname, "sfffs", (char *name, float *min, float *cur, float *max, char *onchange), \
def.f = fvariable(name, *min, *cur, *max, &def.f, def.onchange ? defvar::changed : nullptr, flags))
#define DEFSVAR(cmdname, flags) \
DEFVAR(cmdname, "sss", (char *name, char *cur, char *onchange), \
def.s = svariable(name, cur, &def.s, def.onchange ? defvar::changed : nullptr, flags))
DEFIVAR(defvar, 0);
DEFIVAR(defvarp, IDF_PERSIST);
DEFFVAR(deffvar, 0);
DEFFVAR(deffvarp, IDF_PERSIST);
DEFSVAR(defsvar, 0);
DEFSVAR(defsvarp, IDF_PERSIST);
#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(identflags&IDF_OVERRIDDEN || id->flags&IDF_OVERRIDE) \
{ \
if(id->flags&IDF_PERSIST) \
{ \
debugcode("cannot override persistent variable %s", id->name); \
errorval; \
} \
if(!(id->flags&IDF_OVERRIDDEN)) { saveval; id->flags |= IDF_OVERRIDDEN; } \
else { clearval; } \
} \
else \
{ \
if(id->flags&IDF_OVERRIDDEN) { resetval; id->flags &= ~IDF_OVERRIDDEN; } \
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 = std::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 = std::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;
}
float getfvar(const char *name)
{
_GETVAR(id, ID_FVAR, name, 0);
return *id->storage.f;
}
float getfvarmin(const char *name)
{
_GETVAR(id, ID_FVAR, name, 0);
return id->minvalf;
}
float getfvarmax(const char *name)
{
_GETVAR(id, ID_FVAR, name, 0);
return id->maxvalf;
}
ICOMMAND(getvarmin, "s", (char *s), intret(getvarmin(s)));
ICOMMAND(getvarmax, "s", (char *s), intret(getvarmax(s)));
ICOMMAND(getfvarmin, "s", (char *s), floatret(getfvarmin(s)));
ICOMMAND(getfvarmax, "s", (char *s), floatret(getfvarmax(s)));
bool identexists(const char *name) { return idents.access(name)!=nullptr; }
ICOMMAND(identexists, "s", (char *s), intret(identexists(s) ? 1 : 0));
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->index >= MAXARGS || aliasstack->usedargs&(1<<i->index)) ? i->getstr() : "";
}
ICOMMAND(getalias, "s", (char *s), result(getalias(s)));
int clampvar(ident *id, int val, int minval, int maxval)
{
if(val < minval) val = minval;
else if(val > maxval) val = maxval;
else return val;
debugcode(id->flags&IDF_HEX ?
(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, minval, maxval);
return val;
}
void setvarchecked(ident *id, int val)
{
if(id->flags&IDF_READONLY) debugcode("variable %s is read-only", id->name);
#ifndef STANDALONE
else if(!(id->flags&IDF_OVERRIDE) || identflags&IDF_OVERRIDDEN || game::allowedittoggle())
#else
else
#endif
{
OVERRIDEVAR(return, id->overrideval.i = *id->storage.i, , )
if(val < id->minval || val > id->maxval) val = clampvar(id, val, id->minval, id->maxval);
*id->storage.i = val;
id->changed(); // call trigger function if available
#ifndef STANDALONE
if(id->flags&IDF_OVERRIDE && !(identflags&IDF_OVERRIDDEN)) game::vartrigger(id);
#endif
}
}
static inline void setvarchecked(ident *id, tagval *args, int numargs)
{
int val = forceint(args[0]);
if(id->flags&IDF_HEX && numargs > 1)
{
val = (val << 16) | (forceint(args[1])<<8);
if(numargs > 2) val |= forceint(args[2]);
}
setvarchecked(id, val);
}
float clampfvar(ident *id, float val, float minval, float maxval)
{
if(val < minval) val = minval;
else if(val > maxval) val = maxval;
else return val;
debugcode("valid range for %s is %s..%s", id->name, floatstr(minval), floatstr(maxval));
return val;
}
void setfvarchecked(ident *id, float val)
{
if(id->flags&IDF_READONLY) debugcode("variable %s is read-only", id->name);
#ifndef STANDALONE
else if(!(id->flags&IDF_OVERRIDE) || identflags&IDF_OVERRIDDEN || game::allowedittoggle())
#else
else
#endif
{
OVERRIDEVAR(return, id->overrideval.f = *id->storage.f, , );
if(val < id->minvalf || val > id->maxvalf) val = clampfvar(id, val, id->minvalf, id->maxvalf);
*id->storage.f = val;
id->changed();
#ifndef STANDALONE
if(id->flags&IDF_OVERRIDE && !(identflags&IDF_OVERRIDDEN)) game::vartrigger(id);
#endif
}
}
void setsvarchecked(ident *id, const char *val)
{
if(id->flags&IDF_READONLY) debugcode("variable %s is read-only", id->name);
#ifndef STANDALONE
else if(!(id->flags&IDF_OVERRIDE) || identflags&IDF_OVERRIDDEN || 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 && !(identflags&IDF_OVERRIDDEN)) game::vartrigger(id);
#endif
}
}
bool addcommand(const char *name, identfun fun, const char *args, int type)
{
uint argmask = 0;
int numargs = 0;
bool limit = true;
if(args) for(const char *fmt = args; *fmt; fmt++) switch(*fmt)
{
case 'i': case 'b': case 'f': case 'F': case 't': case 'T': case 'E': case 'N': case 'D': if(numargs < MAXARGS) numargs++; break;
case 'S': case 's': case 'e': case 'r': case '$': if(numargs < MAXARGS) { argmask |= 1<<numargs; numargs++; } break;
case '1': case '2': case '3': case '4': if(numargs < MAXARGS) fmt -= *fmt-'0'+1; break;
case 'C': case 'V': limit = false; break;
default: fatal("builtin %s declared with illegal type: %s", name, args); break;
}
if(limit && numargs > MAXCOMARGS) fatal("builtin %s declared with too many args: %d", name, numargs);
addident(ident(type, name, args, argmask, numargs, (void *)fun));
return false;
}
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 unescapestring(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;
}
*dst = '\0';
return dst - start;
}
static char *conc(vector<char> &buf, tagval *v, int n, bool space, const char *prefix = nullptr, int prefixlen = 0)
{
if(prefix)
{
buf.put(prefix, prefixlen);
if(space && n) buf.add(' ');
}
loopi(n)
{
const char *s = "";
int len = 0;
switch(v[i].type)
{
case VAL_INT: s = intstr(v[i].i); break;
case VAL_FLOAT: s = floatstr(v[i].f); break;
case VAL_STR: case VAL_CSTR: s = v[i].s; break;
case VAL_MACRO: s = v[i].s; len = v[i].code[-1]>>8; goto haslen;
}
len = int(strlen(s));
haslen:
buf.put(s, len);
if(i == n-1) break;
if(space) buf.add(' ');
}
buf.add('\0');
return buf.getbuf();
}
static char *conc(tagval *v, int n, bool space, const char *prefix, int prefixlen)
{
static int vlen[MAXARGS];
static char numbuf[3*MAXSTRLEN];
int len = prefixlen, numlen = 0, i = 0;
for(; i < n; i++) switch(v[i].type)
{
case VAL_MACRO: len += (vlen[i] = v[i].code[-1]>>8); break;
case VAL_STR: case VAL_CSTR: len += (vlen[i] = int(strlen(v[i].s))); break;
case VAL_INT:
if(numlen + MAXSTRLEN > int(sizeof(numbuf))) goto overflow;
intformat(&numbuf[numlen], v[i].i);
numlen += (vlen[i] = strlen(&numbuf[numlen]));
break;
case VAL_FLOAT:
if(numlen + MAXSTRLEN > int(sizeof(numbuf))) goto overflow;
floatformat(&numbuf[numlen], v[i].f);
numlen += (vlen[i] = strlen(&numbuf[numlen]));
break;
default: vlen[i] = 0; break;
}
overflow:
if(space) len += std::max(prefix ? i : i-1, 0);
char *buf = newstring(len + numlen);
int offset = 0, numoffset = 0;
if(prefix)
{
memcpy(buf, prefix, prefixlen);
offset += prefixlen;
if(space && i) buf[offset++] = ' ';
}
loopj(i)
{
if(v[j].type == VAL_INT || v[j].type == VAL_FLOAT)
{
memcpy(&buf[offset], &numbuf[numoffset], vlen[j]);
numoffset += vlen[j];
}
else if(vlen[j]) memcpy(&buf[offset], v[j].s, vlen[j]);
offset += vlen[j];
if(j==i-1) break;
if(space) buf[offset++] = ' ';
}
buf[offset] = '\0';
if(i < n)
{
char *morebuf = conc(&v[i], n-i, space, buf, offset);
delete[] buf;
return morebuf;
}
return buf;
}
static inline char *conc(tagval *v, int n, bool space)
{
return conc(v, n, space, nullptr, 0);
}
static inline char *conc(tagval *v, int n, bool space, const char *prefix)
{
return conc(v, n, space, prefix, strlen(prefix));
}
static inline void skipcomments(const char *&p)
{
for(;;)
{
p += strspn(p, " \t\r");
if(p[0]!='/' || p[1]!='/') break;
p += strcspn(p, "\n\0");
}
}
static vector<char> strbuf[4];
static int stridx = 0;
static inline void cutstring(const char *&p, stringslice &s)
{
p++;
const char *end = parsestring(p);
int maxlen = int(end-p) + 1;
stridx = (stridx + 1)%4;
vector<char> &buf = strbuf[stridx];
if(buf.alen < maxlen) buf.growbuf(maxlen);
s.str = buf.buf;
s.len = unescapestring(buf.buf, p, end);
p = end;
if(*p=='\"') p++;
}
static inline char *cutstring(const char *&p)
{
p++;
const char *end = parsestring(p);
char *buf = newstring(end-p);
unescapestring(buf, p, end);
p = end;
if(*p=='\"') p++;
return buf;
}
static inline const char *parseword(const char *p)
{
const int maxbrak = 100;
static char brakstack[maxbrak];
int brakdepth = 0;
for(;; p++)
{
p += strcspn(p, "\"/;()[] \t\r\n\0");
switch(p[0])
{
case '"': case ';': case ' ': case '\t': case '\r': case '\n': case '\0': return p;
case '/': if(p[1] == '/') return p; break;
case '[': case '(': if(brakdepth >= maxbrak) return p; brakstack[brakdepth++] = p[0]; break;
case ']': if(brakdepth <= 0 || brakstack[--brakdepth] != '[') return p; break;
case ')': if(brakdepth <= 0 || brakstack[--brakdepth] != '(') return p; break;
}
}
return p;
}
static inline void cutword(const char *&p, stringslice &s)
{
s.str = p;
p = parseword(p);
s.len = int(p-s.str);
}
static inline char *cutword(const char *&p)
{
const char *word = p;
p = parseword(p);
return p!=word ? newstring(word, p-word) : nullptr;
}
#define retcode(type, defaultret) ((type) >= VAL_ANY ? ((type) == VAL_CSTR ? RET_STR : (defaultret)) : (type) << CODE_RET)
#define retcodeint(type) retcode(type, RET_INT)
#define retcodefloat(type) retcode(type, RET_FLOAT)
#define retcodeany(type) retcode(type, 0)
#define retcodestr(type) ((type) >= VAL_ANY ? RET_STR : (type) << CODE_RET)
static inline void compilestr(vector<uint> &code, const char *word, int len, bool macro = false)
{
if(len <= 3 && !macro)
{
uint op = CODE_VALI|RET_STR;
loopi(len) op |= uint(uchar(word[i]))<<((i+1)*8);
code.add(op);
return;
}
code.add((macro ? CODE_MACRO : CODE_VAL|RET_STR)|(len<<8));
code.put((const uint *)word, len/sizeof(uint));
size_t endlen = len%sizeof(uint);
union
{
char c[sizeof(uint)];
uint u;
} end;
end.u = 0;
memcpy(end.c, word + len - endlen, endlen);
code.add(end.u);
}
static inline void compilestr(vector<uint> &code) { code.add(CODE_VALI|RET_STR); }
static inline void compilestr(vector<uint> &code, const stringslice &word, bool macro = false) { compilestr(code, word.str, word.len, macro); }
static inline void compilestr(vector<uint> &code, const char *word, bool macro = false) { compilestr(code, word, int(strlen(word)), macro); }
static inline void compileunescapestring(vector<uint> &code, const char *&p, bool macro = false)
{
p++;
const char *end = parsestring(p);
code.add(macro ? CODE_MACRO : CODE_VAL|RET_STR);
char *buf = (char *)code.reserve(int(end-p)/sizeof(uint) + 1).buf;
int len = unescapestring(buf, p, end);
memset(&buf[len], 0, sizeof(uint) - len%sizeof(uint));
code.last() |= len<<8;
code.advance(len/sizeof(uint) + 1);
p = end;
if(*p == '\"') p++;
}
static inline void compileint(vector<uint> &code, int i = 0)
{
if(i >= -0x800000 && i <= 0x7FFFFF)
code.add(CODE_VALI|RET_INT|(i<<8));
else
{
code.add(CODE_VAL|RET_INT);
code.add(i);
}
}
static inline void compilenull(vector<uint> &code)
{
code.add(CODE_VALI|RET_NULL);
}
static uint emptyblock[VAL_ANY][2] =
{
{ CODE_START + 0x100, CODE_EXIT|RET_NULL },
{ CODE_START + 0x100, CODE_EXIT|RET_INT },
{ CODE_START + 0x100, CODE_EXIT|RET_FLOAT },
{ CODE_START + 0x100, CODE_EXIT|RET_STR }
};
static inline void compileblock(vector<uint> &code)
{
code.add(CODE_EMPTY);
}
static void compilestatements(vector<uint> &code, const char *&p, int rettype, int brak = '\0', int prevargs = 0);
static inline const char *compileblock(vector<uint> &code, const char *p, int rettype = RET_NULL, int brak = '\0')
{
int start = code.length();
code.add(CODE_BLOCK);
code.add(CODE_OFFSET|((start+2)<<8));
if(p) compilestatements(code, p, VAL_ANY, brak);
if(code.length() > start + 2)
{
code.add(CODE_EXIT|rettype);
code[start] |= uint(code.length() - (start + 1))<<8;
}
else
{
code.setsize(start);
code.add(CODE_EMPTY|rettype);
}
return p;
}
static inline void compileident(vector<uint> &code, ident *id = dummyident)
{
code.add((id->index < MAXARGS ? CODE_IDENTARG : CODE_IDENT)|(id->index<<8));
}
static inline void compileident(vector<uint> &code, const stringslice &word)
{
compileident(code, newident(word, IDF_UNKNOWN));
}
static inline void compileint(vector<uint> &code, const stringslice &word)
{
compileint(code, word.len ? parseint(word.str) : 0);
}
static inline void compilefloat(vector<uint> &code, float f = 0.0f)
{
if(int(f) == f && f >= -0x800000 && f <= 0x7FFFFF)
code.add(CODE_VALI|RET_FLOAT|(int(f)<<8));
else
{
union { float f; uint u; } conv;
conv.f = f;
code.add(CODE_VAL|RET_FLOAT);
code.add(conv.u);
}
}
static inline void compilefloat(vector<uint> &code, const stringslice &word)
{
compilefloat(code, word.len ? parsefloat(word.str) : 0.0f);
}
static inline bool getbool(const char *s)
{
switch(s[0])
{
case '+': case '-':
switch(s[1])
{
case '0': break;
case '.': return !isdigit(s[2]) || parsefloat(s) != 0;
default: return true;
}
// fall-through
case '0':
{
char *end;
int val = int(strtoul((char *)s, &end, 0));
if(val) return true;
switch(*end)
{
case 'e': case '.': return parsefloat(s) != 0;
default: return false;
}
}
case '.': return !isdigit(s[1]) || parsefloat(s) != 0;
case '\0': return false;
default: return true;
}
}
static inline bool getbool(const tagval &v)
{
switch(v.type)
{
case VAL_FLOAT: return v.f!=0;
case VAL_INT: return v.i!=0;
case VAL_STR: case VAL_MACRO: case VAL_CSTR: return getbool(v.s);
default: return false;
}
}
static inline void compileval(vector<uint> &code, int wordtype, const stringslice &word = stringslice(nullptr, 0))
{
switch(wordtype)
{
case VAL_CANY: if(word.len) compilestr(code, word, true); else compilenull(code); break;
case VAL_CSTR: compilestr(code, word, true); break;
case VAL_ANY: if(word.len) compilestr(code, word); else compilenull(code); break;
case VAL_STR: compilestr(code, word); break;
case VAL_FLOAT: compilefloat(code, word); break;
case VAL_INT: compileint(code, word); break;
case VAL_COND: if(word.len) compileblock(code, word.str); else compilenull(code); break;
case VAL_CODE: compileblock(code, word.str); break;
case VAL_IDENT: compileident(code, word); break;
default: break;
}
}
static stringslice unusedword(nullptr, 0);
static bool compilearg(vector<uint> &code, const char *&p, int wordtype, int prevargs = MAXRESULTS, stringslice &word = unusedword);
static void compilelookup(vector<uint> &code, const char *&p, int ltype, int prevargs = MAXRESULTS)
{
stringslice lookup;
switch(*++p)
{
case '(':
case '[':
if(!compilearg(code, p, VAL_CSTR, prevargs)) goto invalid;
break;
case '$':
compilelookup(code, p, VAL_CSTR, prevargs);
break;
case '\"':
cutstring(p, lookup);
goto lookupid;
default:
{
cutword(p, lookup);
if(!lookup.len) goto invalid;
lookupid:
ident *id = newident(lookup, IDF_UNKNOWN);
if(id) switch(id->type)
{
case ID_VAR:
code.add(CODE_IVAR|retcodeint(ltype)|(id->index<<8));
switch(ltype)
{
case VAL_POP: code.pop(); break;
case VAL_CODE: code.add(CODE_COMPILE); break;
case VAL_IDENT: code.add(CODE_IDENTU); break;
}
return;
case ID_FVAR:
code.add(CODE_FVAR|retcodefloat(ltype)|(id->index<<8));
switch(ltype)
{
case VAL_POP: code.pop(); break;
case VAL_CODE: code.add(CODE_COMPILE); break;
case VAL_IDENT: code.add(CODE_IDENTU); break;
}
return;
case ID_SVAR:
switch(ltype)
{
case VAL_POP: return;
case VAL_CANY: case VAL_CSTR: case VAL_CODE: case VAL_IDENT: case VAL_COND:
code.add(CODE_SVARM|(id->index<<8));
break;
default:
code.add(CODE_SVAR|retcodestr(ltype)|(id->index<<8));
break;
}
goto done;
case ID_ALIAS:
switch(ltype)
{
case VAL_POP: return;
case VAL_CANY: case VAL_COND:
code.add((id->index < MAXARGS ? CODE_LOOKUPMARG : CODE_LOOKUPM)|(id->index<<8));
break;
case VAL_CSTR: case VAL_CODE: case VAL_IDENT:
code.add((id->index < MAXARGS ? CODE_LOOKUPMARG : CODE_LOOKUPM)|RET_STR|(id->index<<8));
break;
default:
code.add((id->index < MAXARGS ? CODE_LOOKUPARG : CODE_LOOKUP)|retcodestr(ltype)|(id->index<<8));
break;
}
goto done;
case ID_COMMAND:
{
int comtype = CODE_COM, numargs = 0;
if(prevargs >= MAXRESULTS) code.add(CODE_ENTER);
for(const char *fmt = id->args; *fmt; fmt++) switch(*fmt)
{
case 'S': compilestr(code); numargs++; break;
case 's': compilestr(code, nullptr, 0, true); numargs++; break;
case 'i': compileint(code); numargs++; break;
case 'b': compileint(code, INT_MIN); numargs++; break;
case 'f': compilefloat(code); numargs++; break;
case 'F': code.add(CODE_DUP|RET_FLOAT); numargs++; break;
case 'E':
case 'T':
case 't': compilenull(code); numargs++; break;
case 'e': compileblock(code); numargs++; break;
case 'r': compileident(code); numargs++; break;
case '$': compileident(code, id); numargs++; break;
case 'N': compileint(code, -1); numargs++; break;
#ifndef STANDALONE
case 'D': comtype = CODE_COMD; numargs++; break;
#endif
case 'C': comtype = CODE_COMC; goto compilecomv;
case 'V': comtype = CODE_COMV; goto compilecomv;
case '1': case '2': case '3': case '4': break;
}
code.add(comtype|retcodeany(ltype)|(id->index<<8));
code.add((prevargs >= MAXRESULTS ? CODE_EXIT : CODE_RESULT_ARG) | retcodeany(ltype));
goto done;
compilecomv:
code.add(comtype|retcodeany(ltype)|(numargs<<8)|(id->index<<13));
code.add((prevargs >= MAXRESULTS ? CODE_EXIT : CODE_RESULT_ARG) | retcodeany(ltype));
goto done;
}
default: goto invalid;
}
compilestr(code, lookup, true);
break;
}
}
switch(ltype)
{
case VAL_CANY: case VAL_COND:
code.add(CODE_LOOKUPMU);
break;
case VAL_CSTR: case VAL_CODE: case VAL_IDENT:
code.add(CODE_LOOKUPMU|RET_STR);
break;
default:
code.add(CODE_LOOKUPU|retcodeany(ltype));
break;
}
done:
switch(ltype)
{
case VAL_POP: code.add(CODE_POP); break;
case VAL_CODE: code.add(CODE_COMPILE); break;
case VAL_COND: code.add(CODE_COND); break;
case VAL_IDENT: code.add(CODE_IDENTU); break;
}
return;
invalid:
switch(ltype)
{
case VAL_POP: break;
case VAL_NULL: case VAL_ANY: case VAL_CANY: case VAL_WORD: case VAL_COND: compilenull(code); break;
default: compileval(code, ltype); break;
}
}
static bool compileblockstr(vector<uint> &code, const char *str, const char *end, bool macro)
{
int start = code.length();
code.add(macro ? CODE_MACRO : CODE_VAL|RET_STR);
char *buf = (char *)code.reserve((end-str)/sizeof(uint)+1).buf;
int len = 0;
while(str < end)
{
int n = strcspn(str, "\r/\"@]\0");
memcpy(&buf[len], str, n);
len += n;
str += n;
switch(*str)
{
case '\r': str++; break;
case '\"':
{
const char *start = str;
str = parsestring(str+1);
if(*str=='\"') str++;
memcpy(&buf[len], start, str-start);
len += str-start;
break;
}
case '/':
if(str[1] == '/')
{
size_t comment = strcspn(str, "\n\0");
if (iscubepunct(str[2]))
{
memcpy(&buf[len], str, comment);
len += comment;
}
str += comment;
}
else buf[len++] = *str++;
break;
case '@':
case ']':
if(str < end) { buf[len++] = *str++; break; }
case '\0': goto done;
}
}
done:
memset(&buf[len], '\0', sizeof(uint)-len%sizeof(uint));
code.advance(len/sizeof(uint)+1);
code[start] |= len<<8;
return true;
}
static bool compileblocksub(vector<uint> &code, const char *&p, int prevargs)
{
stringslice lookup;
switch(*p)
{
case '(':
if(!compilearg(code, p, VAL_CANY, prevargs)) return false;
break;
case '[':
if(!compilearg(code, p, VAL_CSTR, prevargs)) return false;
code.add(CODE_LOOKUPMU);
break;
case '\"':
cutstring(p, lookup);
goto lookupid;
default:
{
lookup.str = p;
while(iscubealnum(*p) || *p=='_') p++;
lookup.len = int(p-lookup.str);
if(!lookup.len) return false;
lookupid:
ident *id = newident(lookup, IDF_UNKNOWN);
if(id) switch(id->type)
{
case ID_VAR: code.add(CODE_IVAR|(id->index<<8)); goto done;
case ID_FVAR: code.add(CODE_FVAR|(id->index<<8)); goto done;
case ID_SVAR: code.add(CODE_SVARM|(id->index<<8)); goto done;
case ID_ALIAS: code.add((id->index < MAXARGS ? CODE_LOOKUPMARG : CODE_LOOKUPM)|(id->index<<8)); goto done;
}
compilestr(code, lookup, true);
code.add(CODE_LOOKUPMU);
done:
break;
}
}
return true;
}
static void compileblockmain(vector<uint> &code, const char *&p, int wordtype, int prevargs)
{
const char *line = p, *start = p;
int concs = 0;
for(int brak = 1; brak;)
{
p += strcspn(p, "@\"/[]\0");
int c = *p++;
switch(c)
{
case '\0':
debugcodeline(line, "missing \"]\"");
p--;
goto done;
case '\"':
p = parsestring(p);
if(*p=='\"') p++;
break;
case '/':
if(*p=='/') p += strcspn(p, "\n\0");
break;
case '[': brak++; break;
case ']': brak--; break;
case '@':
{
const char *esc = p;
while(*p == '@') p++;
int level = p - (esc - 1);
if(brak > level) continue;
else if(brak < level) debugcodeline(line, "too many @s");
if(!concs && prevargs >= MAXRESULTS) code.add(CODE_ENTER);
if(concs + 2 > MAXARGS)
{
code.add(CODE_CONCW|RET_STR|(concs<<8));
concs = 1;
}
if(compileblockstr(code, start, esc-1, true)) concs++;
if(compileblocksub(code, p, prevargs + concs)) concs++;
if(concs) start = p;
else if(prevargs >= MAXRESULTS) code.pop();
break;
}
}
}
done:
if(p-1 > start)
{
if(!concs) switch(wordtype)
{
case VAL_POP:
return;
case VAL_CODE: case VAL_COND:
p = compileblock(code, start, RET_NULL, ']');
return;
case VAL_IDENT:
compileident(code, stringslice(start, p-1));
return;
}
switch(wordtype)
{
case VAL_CSTR: case VAL_CODE: case VAL_IDENT: case VAL_CANY: case VAL_COND:
compileblockstr(code, start, p-1, true);
break;
default:
compileblockstr(code, start, p-1, concs > 0);
break;
}
if(concs > 1) concs++;
}
if(concs)
{
if(prevargs >= MAXRESULTS)
{
code.add(CODE_CONCM|retcodeany(wordtype)|(concs<<8));
code.add(CODE_EXIT|retcodeany(wordtype));
}
else code.add(CODE_CONCW|retcodeany(wordtype)|(concs<<8));
}
switch(wordtype)
{
case VAL_POP: if(concs || p-1 > start) code.add(CODE_POP); break;
case VAL_COND: if(!concs && p-1 <= start) compilenull(code); else code.add(CODE_COND); break;
case VAL_CODE: if(!concs && p-1 <= start) compileblock(code); else code.add(CODE_COMPILE); break;
case VAL_IDENT: if(!concs && p-1 <= start) compileident(code); else code.add(CODE_IDENTU); break;
case VAL_CSTR: case VAL_CANY:
if(!concs && p-1 <= start) compilestr(code, nullptr, 0, true);
break;
case VAL_STR: case VAL_NULL: case VAL_ANY: case VAL_WORD:
if(!concs && p-1 <= start) compilestr(code);
break;
default:
if(!concs)
{
if(p-1 <= start) compileval(code, wordtype);
else code.add(CODE_FORCE|(wordtype<<CODE_RET));
}
break;
}
}
static bool compilearg(vector<uint> &code, const char *&p, int wordtype, int prevargs, stringslice &word)
{
skipcomments(p);
switch(*p)
{
case '\"':
switch(wordtype)
{
case VAL_POP:
p = parsestring(p+1);
if(*p == '\"') p++;
break;
case VAL_COND:
{
char *s = cutstring(p);
if(s[0]) compileblock(code, s);
else compilenull(code);
delete[] s;
break;
}
case VAL_CODE:
{
char *s = cutstring(p);
compileblock(code, s);
delete[] s;
break;
}
case VAL_WORD:
cutstring(p, word);
break;
case VAL_ANY:
case VAL_STR:
compileunescapestring(code, p);
break;
case VAL_CANY:
case VAL_CSTR:
compileunescapestring(code, p, true);
break;
default:
{
stringslice s;
cutstring(p, s);
compileval(code, wordtype, s);
break;
}
}
return true;
case '$': compilelookup(code, p, wordtype, prevargs); return true;
case '(':
p++;
if(prevargs >= MAXRESULTS)
{
code.add(CODE_ENTER);
compilestatements(code, p, wordtype > VAL_ANY ? VAL_CANY : VAL_ANY, ')');
code.add(CODE_EXIT|retcodeany(wordtype));
}
else
{
int start = code.length();
compilestatements(code, p, wordtype > VAL_ANY ? VAL_CANY : VAL_ANY, ')', prevargs);
if(code.length() > start) code.add(CODE_RESULT_ARG|retcodeany(wordtype));
else { compileval(code, wordtype); return true; }
}
switch(wordtype)
{
case VAL_POP: code.add(CODE_POP); break;
case VAL_COND: code.add(CODE_COND); break;
case VAL_CODE: code.add(CODE_COMPILE); break;
case VAL_IDENT: code.add(CODE_IDENTU); break;
}
return true;
case '[':
p++;
compileblockmain(code, p, wordtype, prevargs);
return true;
default:
switch(wordtype)
{
case VAL_POP:
{
const char *s = p;
p = parseword(p);
return p != s;
}
case VAL_COND:
{
char *s = cutword(p);
if(!s) return false;
compileblock(code, s);
delete[] s;
return true;
}
case VAL_CODE:
{
char *s = cutword(p);
if(!s) return false;
compileblock(code, s);
delete[] s;
return true;
}
case VAL_WORD:
cutword(p, word);
return word.len!=0;
default:
{
stringslice s;
cutword(p, s);
if(!s.len) return false;
compileval(code, wordtype, s);
return true;
}
}
}
}
static void compilestatements(vector<uint> &code, const char *&p, int rettype, int brak, int prevargs)
{
const char *line = p;
stringslice idname;
int numargs;
for(;;)
{
skipcomments(p);
idname.str = nullptr;
bool more = compilearg(code, p, VAL_WORD, prevargs, idname);
if(!more) goto endstatement;
skipcomments(p);
if(p[0] == '=') switch(p[1])
{
case '/':
if(p[2] != '/') break;
case ';': case ' ': case '\t': case '\r': case '\n': case '\0':
p++;
if(idname.str)
{
ident *id = newident(idname, IDF_UNKNOWN);
if(id) switch(id->type)
{
case ID_ALIAS:
if(!(more = compilearg(code, p, VAL_ANY, prevargs))) compilestr(code);
code.add((id->index < MAXARGS ? CODE_ALIASARG : CODE_ALIAS)|(id->index<<8));
goto endstatement;
case ID_VAR:
if(!(more = compilearg(code, p, VAL_INT, prevargs))) compileint(code);
code.add(CODE_IVAR1|(id->index<<8));
goto endstatement;
case ID_FVAR:
if(!(more = compilearg(code, p, VAL_FLOAT, prevargs))) compilefloat(code);
code.add(CODE_FVAR1|(id->index<<8));
goto endstatement;
case ID_SVAR:
if(!(more = compilearg(code, p, VAL_CSTR, prevargs))) compilestr(code);
code.add(CODE_SVAR1|(id->index<<8));
goto endstatement;
}
compilestr(code, idname, true);
}
if(!(more = compilearg(code, p, VAL_ANY))) compilestr(code);
code.add(CODE_ALIASU);
goto endstatement;
}
numargs = 0;
if(!idname.str)
{
noid:
while(numargs < MAXARGS && (more = compilearg(code, p, VAL_CANY, prevargs+numargs))) numargs++;
code.add(CODE_CALLU|(numargs<<8));
}
else
{
ident *id = idents.access(idname);
if(!id)
{
if(!checknumber(idname)) { compilestr(code, idname, true); goto noid; }
switch(rettype)
{
case VAL_ANY:
case VAL_CANY:
{
char *end = (char *)idname.str;
int val = int(strtoul(idname.str, &end, 0));
if(end < idname.end()) compilestr(code, idname, rettype==VAL_CANY);
else compileint(code, val);
break;
}
default:
compileval(code, rettype, idname);
break;
}
code.add(CODE_RESULT);
}
else switch(id->type)
{
case ID_ALIAS:
while(numargs < MAXARGS && (more = compilearg(code, p, VAL_ANY, prevargs+numargs))) numargs++;
code.add((id->index < MAXARGS ? CODE_CALLARG : CODE_CALL)|(numargs<<8)|(id->index<<13));
break;
case ID_COMMAND:
{
int comtype = CODE_COM, fakeargs = 0;
bool rep = false;
for(const char *fmt = id->args; *fmt; fmt++) switch(*fmt)
{
case 'S':
case 's':
if(more) more = compilearg(code, p, *fmt == 's' ? VAL_CSTR : VAL_STR, prevargs+numargs);
if(!more)
{
if(rep) break;
compilestr(code, nullptr, 0, *fmt=='s');
fakeargs++;
}
else if(!fmt[1])
{
int numconc = 1;
while(numargs + numconc < MAXARGS && (more = compilearg(code, p, VAL_CSTR, prevargs+numargs+numconc))) numconc++;
if(numconc > 1) code.add(CODE_CONC|RET_STR|(numconc<<8));
}
numargs++;
break;
case 'i': if(more) more = compilearg(code, p, VAL_INT, prevargs+numargs); if(!more) { if(rep) break; compileint(code); fakeargs++; } numargs++; break;
case 'b': if(more) more = compilearg(code, p, VAL_INT, prevargs+numargs); if(!more) { if(rep) break; compileint(code, INT_MIN); fakeargs++; } numargs++; break;
case 'f': if(more) more = compilearg(code, p, VAL_FLOAT, prevargs+numargs); if(!more) { if(rep) break; compilefloat(code); fakeargs++; } numargs++; break;
case 'F': if(more) more = compilearg(code, p, VAL_FLOAT, prevargs+numargs); if(!more) { if(rep) break; code.add(CODE_DUP|RET_FLOAT); fakeargs++; } numargs++; break;
case 'T':
case 't': if(more) more = compilearg(code, p, *fmt == 't' ? VAL_CANY : VAL_ANY, prevargs+numargs); if(!more) { if(rep) break; compilenull(code); fakeargs++; } numargs++; break;
case 'E': if(more) more = compilearg(code, p, VAL_COND, prevargs+numargs); if(!more) { if(rep) break; compilenull(code); fakeargs++; } numargs++; break;
case 'e': if(more) more = compilearg(code, p, VAL_CODE, prevargs+numargs); if(!more) { if(rep) break; compileblock(code); fakeargs++; } numargs++; break;
case 'r': if(more) more = compilearg(code, p, VAL_IDENT, prevargs+numargs); if(!more) { if(rep) break; compileident(code); fakeargs++; } numargs++; break;
case '$': compileident(code, id); numargs++; break;
case 'N': compileint(code, numargs-fakeargs); numargs++; break;
#ifndef STANDALONE
case 'D': comtype = CODE_COMD; numargs++; break;
#endif
case 'C': comtype = CODE_COMC; if(more) while(numargs < MAXARGS && (more = compilearg(code, p, VAL_CANY, prevargs+numargs))) numargs++; goto compilecomv;
case 'V': comtype = CODE_COMV; if(more) while(numargs < MAXARGS && (more = compilearg(code, p, VAL_CANY, prevargs+numargs))) numargs++; goto compilecomv;
case '1': case '2': case '3': case '4':
if(more && numargs < MAXARGS)
{
int numrep = *fmt-'0'+1;
fmt -= numrep;
rep = true;
}
else for(; numargs > MAXARGS; numargs--) code.add(CODE_POP);
break;
}
code.add(comtype|retcodeany(rettype)|(id->index<<8));
break;
compilecomv:
code.add(comtype|retcodeany(rettype)|(numargs<<8)|(id->index<<13));
break;
}
case ID_LOCAL:
if(more) while(numargs < MAXARGS && (more = compilearg(code, p, VAL_IDENT, prevargs+numargs))) numargs++;
if(more) while((more = compilearg(code, p, VAL_POP)));
code.add(CODE_LOCAL|(numargs<<8));
break;
case ID_DO:
if(more) more = compilearg(code, p, VAL_CODE, prevargs);
code.add((more ? CODE_DO : CODE_NULL) | retcodeany(rettype));
break;
case ID_DOARGS:
if(more) more = compilearg(code, p, VAL_CODE, prevargs);
code.add((more ? CODE_DOARGS : CODE_NULL) | retcodeany(rettype));
break;
case ID_IF:
if(more) more = compilearg(code, p, VAL_CANY, prevargs);
if(!more) code.add(CODE_NULL | retcodeany(rettype));
else
{
int start1 = code.length();
more = compilearg(code, p, VAL_CODE, prevargs+1);
if(!more) { code.add(CODE_POP); code.add(CODE_NULL | retcodeany(rettype)); }
else
{
int start2 = code.length();
more = compilearg(code, p, VAL_CODE, prevargs+2);
uint inst1 = code[start1], op1 = inst1&~CODE_RET_MASK, len1 = start2 - (start1+1);
if(!more)
{
if(op1 == (CODE_BLOCK|(len1<<8)))
{
code[start1] = (len1<<8) | CODE_JUMP_FALSE;
code[start1+1] = CODE_ENTER_RESULT;
code[start1+len1] = (code[start1+len1]&~CODE_RET_MASK) | retcodeany(rettype);
break;
}
compileblock(code);
}
else
{
uint inst2 = code[start2], op2 = inst2&~CODE_RET_MASK, len2 = code.length() - (start2+1);
if(op2 == (CODE_BLOCK|(len2<<8)))
{
if(op1 == (CODE_BLOCK|(len1<<8)))
{
code[start1] = ((start2-start1)<<8) | CODE_JUMP_FALSE;
code[start1+1] = CODE_ENTER_RESULT;
code[start1+len1] = (code[start1+len1]&~CODE_RET_MASK) | retcodeany(rettype);
code[start2] = (len2<<8) | CODE_JUMP;
code[start2+1] = CODE_ENTER_RESULT;
code[start2+len2] = (code[start2+len2]&~CODE_RET_MASK) | retcodeany(rettype);
break;
}
else if(op1 == (CODE_EMPTY|(len1<<8)))
{
code[start1] = CODE_NULL | (inst2&CODE_RET_MASK);
code[start2] = (len2<<8) | CODE_JUMP_TRUE;
code[start2+1] = CODE_ENTER_RESULT;
code[start2+len2] = (code[start2+len2]&~CODE_RET_MASK) | retcodeany(rettype);
break;
}
}
}
code.add(CODE_COM|retcodeany(rettype)|(id->index<<8));
}
}
break;
case ID_RESULT:
if(more) more = compilearg(code, p, VAL_ANY, prevargs);
code.add((more ? CODE_RESULT : CODE_NULL) | retcodeany(rettype));
break;
case ID_NOT:
if(more) more = compilearg(code, p, VAL_CANY, prevargs);
code.add((more ? CODE_NOT : CODE_TRUE) | retcodeany(rettype));
break;
case ID_AND:
case ID_OR:
if(more) more = compilearg(code, p, VAL_COND, prevargs);
if(!more) { code.add((id->type == ID_AND ? CODE_TRUE : CODE_FALSE) | retcodeany(rettype)); }
else
{
numargs++;
int start = code.length(), end = start;
while(numargs < MAXARGS)
{
more = compilearg(code, p, VAL_COND, prevargs+numargs);
if(!more) break;
numargs++;
if((code[end]&~CODE_RET_MASK) != (CODE_BLOCK|(uint(code.length()-(end+1))<<8))) break;
end = code.length();
}
if(more)
{
while(numargs < MAXARGS && (more = compilearg(code, p, VAL_COND, prevargs+numargs))) numargs++;
code.add(CODE_COMV|retcodeany(rettype)|(numargs<<8)|(id->index<<13));
}
else
{
uint op = id->type == ID_AND ? CODE_JUMP_RESULT_FALSE : CODE_JUMP_RESULT_TRUE;
code.add(op);
end = code.length();
while(start+1 < end)
{
uint len = code[start]>>8;
code[start] = ((end-(start+1))<<8) | op;
code[start+1] = CODE_ENTER;
code[start+len] = (code[start+len]&~CODE_RET_MASK) | retcodeany(rettype);
start += len+1;
}
}
}
break;
case ID_VAR:
if(!(more = compilearg(code, p, VAL_INT, prevargs))) code.add(CODE_PRINT|(id->index<<8));
else if(!(id->flags&IDF_HEX) || !(more = compilearg(code, p, VAL_INT, prevargs+1))) code.add(CODE_IVAR1|(id->index<<8));
else if(!(more = compilearg(code, p, VAL_INT, prevargs+2))) code.add(CODE_IVAR2|(id->index<<8));
else code.add(CODE_IVAR3|(id->index<<8));
break;
case ID_FVAR:
if(!(more = compilearg(code, p, VAL_FLOAT, prevargs))) code.add(CODE_PRINT|(id->index<<8));
else code.add(CODE_FVAR1|(id->index<<8));
break;
case ID_SVAR:
if(!(more = compilearg(code, p, VAL_CSTR, prevargs))) code.add(CODE_PRINT|(id->index<<8));
else
{
do ++numargs;
while(numargs < MAXARGS && (more = compilearg(code, p, VAL_CANY, prevargs+numargs)));
if(numargs > 1) code.add(CODE_CONC|RET_STR|(numargs<<8));
code.add(CODE_SVAR1|(id->index<<8));
}
break;
}
}
endstatement:
if(more) while(compilearg(code, p, VAL_POP));
p += strcspn(p, ")];/\n\0");
int c = *p++;
switch(c)
{
case '\0':
if(c != brak) debugcodeline(line, "missing \"%c\"", brak);
p--;
return;
case ')':
case ']':
if(c == brak) return;
debugcodeline(line, "unexpected \"%c\"", c);
break;
case '/':
if(*p == '/') p += strcspn(p, "\n\0");
goto endstatement;
}
}
}
static void compilemain(vector<uint> &code, const char *p, int rettype = VAL_ANY)
{
code.add(CODE_START);
compilestatements(code, p, VAL_ANY);
code.add(CODE_EXIT|(rettype < VAL_ANY ? rettype<<CODE_RET : 0));
}
uint *compilecode(const char *p)
{
vector<uint> buf;
buf.reserve(64);
compilemain(buf, p);
uint *code = new uint[buf.length()];
memcpy(code, buf.getbuf(), buf.length()*sizeof(uint));
code[0] += 0x100;
return code;
}
static inline const uint *forcecode(tagval &v)
{
if(v.type != VAL_CODE)
{
vector<uint> buf;
buf.reserve(64);
compilemain(buf, v.getstr());
freearg(v);
v.setcode(buf.disown()+1);
}
return v.code;
}
static inline void forcecond(tagval &v)
{
switch(v.type)
{
case VAL_STR: case VAL_MACRO: case VAL_CSTR:
if(v.s[0]) forcecode(v);
else v.setint(0);
break;
}
}
void keepcode(uint *code)
{
if(!code) return;
switch(*code&CODE_OP_MASK)
{
case CODE_START:
*code += 0x100;
return;
}
switch(code[-1]&CODE_OP_MASK)
{
case CODE_START:
code[-1] += 0x100;
break;
case CODE_OFFSET:
code -= int(code[-1]>>8);
*code += 0x100;
break;
}
}
void freecode(uint *code)
{
if(!code) return;
switch(*code&CODE_OP_MASK)
{
case CODE_START:
*code -= 0x100;
if(int(*code) < 0x100) delete[] code;
return;
}
switch(code[-1]&CODE_OP_MASK)
{
case CODE_START:
code[-1] -= 0x100;
if(int(code[-1]) < 0x100) delete[] &code[-1];
break;
case CODE_OFFSET:
code -= int(code[-1]>>8);
*code -= 0x100;
if(int(*code) < 0x100) delete[] code;
break;
}
}
void printvar(ident *id, int i)
{
if(i < 0) conoutf("%s = %d", id->name, i);
else if(id->flags&IDF_HEX && id->maxval==0xFFFFFF)
conoutf("%s = 0x%.6X (%d, %d, %d)", id->name, i, (i>>16)&0xFF, (i>>8)&0xFF, i&0xFF);
else
conoutf(id->flags&IDF_HEX ? "%s = 0x%X" : "%s = %d", id->name, i);
}
void printfvar(ident *id, float f)
{
conoutf("%s = %s", id->name, floatstr(f));
}
void printsvar(ident *id, const char *s)
{
conoutf(strchr(s, '"') ? "%s = [%s]" : "%s = \"%s\"", id->name, s);
}
void printvar(ident *id)
{
switch(id->type)
{
case ID_VAR: printvar(id, *id->storage.i); break;
case ID_FVAR: printfvar(id, *id->storage.f); break;
case ID_SVAR: printsvar(id, *id->storage.s); break;
}
}
typedef void (__cdecl *comfun)();
typedef void (__cdecl *comfun1)(void *);
typedef void (__cdecl *comfun2)(void *, void *);
typedef void (__cdecl *comfun3)(void *, void *, void *);
typedef void (__cdecl *comfun4)(void *, void *, void *, void *);
typedef void (__cdecl *comfun5)(void *, void *, void *, void *, void *);
typedef void (__cdecl *comfun6)(void *, void *, void *, void *, void *, void *);
typedef void (__cdecl *comfun7)(void *, void *, void *, void *, void *, void *, void *);
typedef void (__cdecl *comfun8)(void *, void *, void *, void *, void *, void *, void *, void *);
typedef void (__cdecl *comfun9)(void *, void *, void *, void *, void *, void *, void *, void *, void *);
typedef void (__cdecl *comfun10)(void *, void *, void *, void *, void *, void *, void *, void *, void *, void *);
typedef void (__cdecl *comfun11)(void *, void *, void *, void *, void *, void *, void *, void *, void *, void *, void *);
typedef void (__cdecl *comfun12)(void *, void *, void *, void *, void *, void *, void *, void *, void *, void *, void *, void *);
typedef void (__cdecl *comfunv)(tagval *, int);
static const uint *skipcode(const uint *code, tagval &result = noret)
{
int depth = 0;
for(;;)
{
uint op = *code++;
switch(op&0xFF)
{
case CODE_MACRO:
case CODE_VAL|RET_STR:
{
uint len = op>>8;
code += len/sizeof(uint) + 1;
continue;
}
case CODE_BLOCK:
case CODE_JUMP:
case CODE_JUMP_TRUE:
case CODE_JUMP_FALSE:
case CODE_JUMP_RESULT_TRUE:
case CODE_JUMP_RESULT_FALSE:
{
uint len = op>>8;
code += len;
continue;
}
case CODE_ENTER:
case CODE_ENTER_RESULT:
++depth;
continue;
case CODE_EXIT|RET_NULL: case CODE_EXIT|RET_STR: case CODE_EXIT|RET_INT: case CODE_EXIT|RET_FLOAT:
if(depth <= 0)
{
if(&result != &noret) forcearg(result, op&CODE_RET_MASK);
return code;
}
--depth;
continue;
}
}
}
#ifndef STANDALONE
static inline uint *copycode(const uint *src)
{
const uint *end = skipcode(src);
size_t len = end - src;
uint *dst = new uint[len + 1];
*dst++ = CODE_START;
memcpy(dst, src, len*sizeof(uint));
return dst;
}
static inline void copyarg(tagval &dst, const tagval &src)
{
switch(src.type)
{
case VAL_INT:
case VAL_FLOAT:
case VAL_IDENT:
dst = src;
break;
case VAL_STR:
case VAL_MACRO:
case VAL_CSTR:
dst.setstr(newstring(src.s));
break;
case VAL_CODE:
dst.setcode(copycode(src.code));
break;
default:
dst.setnull();
break;
}
}
static inline void addreleaseaction(ident *id, tagval *args, int numargs)
{
tagval *dst = addreleaseaction(id, numargs+1);
if(dst) { args[numargs].setint(1); loopi(numargs+1) copyarg(dst[i], args[i]); }
else args[numargs].setint(0);
}
#endif
static inline void callcommand(ident *id, tagval *args, int numargs, bool lookup = false)
{
int i = -1, fakeargs = 0;
bool rep = false;
for(const char *fmt = id->args; *fmt; fmt++) switch(*fmt)
{
case 'i': if(++i >= numargs) { if(rep) break; args[i].setint(0); fakeargs++; } else forceint(args[i]); break;
case 'b': if(++i >= numargs) { if(rep) break; args[i].setint(INT_MIN); fakeargs++; } else forceint(args[i]); break;
case 'f': if(++i >= numargs) { if(rep) break; args[i].setfloat(0.0f); fakeargs++; } else forcefloat(args[i]); break;
case 'F': if(++i >= numargs) { if(rep) break; args[i].setfloat(args[i-1].getfloat()); fakeargs++; } else forcefloat(args[i]); break;
case 'S': if(++i >= numargs) { if(rep) break; args[i].setstr(newstring("")); fakeargs++; } else forcestr(args[i]); break;
case 's': if(++i >= numargs) { if(rep) break; args[i].setcstr(""); fakeargs++; } else forcestr(args[i]); break;
case 'T':
case 't': if(++i >= numargs) { if(rep) break; args[i].setnull(); fakeargs++; } break;
case 'E': if(++i >= numargs) { if(rep) break; args[i].setnull(); fakeargs++; } else forcecond(args[i]); break;
case 'e':
if(++i >= numargs)
{
if(rep) break;
args[i].setcode(emptyblock[VAL_NULL]+1);
fakeargs++;
}
else forcecode(args[i]);
break;
case 'r': if(++i >= numargs) { if(rep) break; args[i].setident(dummyident); fakeargs++; } else forceident(args[i]); break;
case '$': if(++i < numargs) freearg(args[i]); args[i].setident(id); break;
case 'N': if(++i < numargs) freearg(args[i]); args[i].setint(lookup ? -1 : i-fakeargs); break;
#ifndef STANDALONE
case 'D': if(++i < numargs) freearg(args[i]); addreleaseaction(id, args, i); fakeargs++; break;
#endif
case 'C': { i = std::max(i+1, numargs); vector<char> buf; ((comfun1)id->fun)(conc(buf, args, i, true)); goto cleanup; }
case 'V': i = std::max(i+1, numargs); ((comfunv)id->fun)(args, i); goto cleanup;
case '1': case '2': case '3': case '4': if(i+1 < numargs) { fmt -= *fmt-'0'+1; rep = true; } break;
}
++i;
#define OFFSETARG(n) n
#define ARG(n) (id->argmask&(1<<(n)) ? (void *)args[OFFSETARG(n)].s : (void *)&args[OFFSETARG(n)].i)
#define CALLCOM(n) \
switch(n) \
{ \
case 0: ((comfun)id->fun)(); break; \
case 1: ((comfun1)id->fun)(ARG(0)); break; \
case 2: ((comfun2)id->fun)(ARG(0), ARG(1)); break; \
case 3: ((comfun3)id->fun)(ARG(0), ARG(1), ARG(2)); break; \
case 4: ((comfun4)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3)); break; \
case 5: ((comfun5)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4)); break; \
case 6: ((comfun6)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5)); break; \
case 7: ((comfun7)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6)); break; \
case 8: ((comfun8)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6), ARG(7)); break; \
case 9: ((comfun9)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6), ARG(7), ARG(8)); break; \
case 10: ((comfun10)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6), ARG(7), ARG(8), ARG(9)); break; \
case 11: ((comfun11)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6), ARG(7), ARG(8), ARG(9), ARG(10)); break; \
case 12: ((comfun12)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6), ARG(7), ARG(8), ARG(9), ARG(10), ARG(11)); break; \
}
CALLCOM(i)
#undef OFFSETARG
cleanup:
loopk(i) freearg(args[k]);
for(; i < numargs; i++) freearg(args[i]);
}
#define MAXRUNDEPTH 255
static int rundepth = 0;
static const uint *runcode(const uint *code, tagval &result)
{
result.setnull();
if(rundepth >= MAXRUNDEPTH)
{
debugcode("exceeded recursion limit");
return skipcode(code, result);
}
++rundepth;
int numargs = 0;
tagval args[MAXARGS+MAXRESULTS], *prevret = commandret;
commandret = &result;
for(;;)
{
uint op = *code++;
switch(op&0xFF)
{
case CODE_START: case CODE_OFFSET: continue;
#define RETOP(op, val) \
case op: \
freearg(result); \
val; \
continue;
RETOP(CODE_NULL|RET_NULL, result.setnull())
RETOP(CODE_NULL|RET_STR, result.setstr(newstring("")))
RETOP(CODE_NULL|RET_INT, result.setint(0))
RETOP(CODE_NULL|RET_FLOAT, result.setfloat(0.0f))
RETOP(CODE_FALSE|RET_STR, result.setstr(newstring("0")))
case CODE_FALSE|RET_NULL:
RETOP(CODE_FALSE|RET_INT, result.setint(0))
RETOP(CODE_FALSE|RET_FLOAT, result.setfloat(0.0f))
RETOP(CODE_TRUE|RET_STR, result.setstr(newstring("1")))
case CODE_TRUE|RET_NULL:
RETOP(CODE_TRUE|RET_INT, result.setint(1))
RETOP(CODE_TRUE|RET_FLOAT, result.setfloat(1.0f))
#define RETPOP(op, val) \
RETOP(op, { --numargs; val; freearg(args[numargs]); })
RETPOP(CODE_NOT|RET_STR, result.setstr(newstring(getbool(args[numargs]) ? "0" : "1")))
case CODE_NOT|RET_NULL:
RETPOP(CODE_NOT|RET_INT, result.setint(getbool(args[numargs]) ? 0 : 1))
RETPOP(CODE_NOT|RET_FLOAT, result.setfloat(getbool(args[numargs]) ? 0.0f : 1.0f))
case CODE_POP:
freearg(args[--numargs]);
continue;
case CODE_ENTER:
code = runcode(code, args[numargs++]);
continue;
case CODE_ENTER_RESULT:
freearg(result);
code = runcode(code, result);
continue;
case CODE_EXIT|RET_STR: case CODE_EXIT|RET_INT: case CODE_EXIT|RET_FLOAT:
forcearg(result, op&CODE_RET_MASK);
// fall-through
case CODE_EXIT|RET_NULL:
goto exit;
case CODE_RESULT_ARG|RET_STR: case CODE_RESULT_ARG|RET_INT: case CODE_RESULT_ARG|RET_FLOAT:
forcearg(result, op&CODE_RET_MASK);
// fall-through
case CODE_RESULT_ARG|RET_NULL:
args[numargs++] = result;
result.setnull();
continue;
case CODE_PRINT:
printvar(identmap[op>>8]);
continue;