more cleanup and deglobalization

master
Daniel Kolesa 2015-08-06 21:43:36 +01:00
parent f83560cad1
commit f28fab7a97
2 changed files with 126 additions and 110 deletions

View File

@ -4,10 +4,6 @@
CsState cstate;
static constexpr int MAX_ARGUMENTS = 25;
static constexpr int MAX_RESULTS = 7;
static constexpr int MAX_COMARGS = 12;
const struct NullValue: TaggedValue {
NullValue() { set_null(); }
} null_value;
@ -174,78 +170,48 @@ void Ident::clean_code() {
}
}
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;
ostd::ConstCharRange debugline(CsState &cs, ostd::ConstCharRange p,
ostd::ConstCharRange fmt,
ostd::CharRange buf) {
if (cs.src_str.empty()) return fmt;
ostd::Size num = 1;
ostd::ConstCharRange line(cs.src_str);
for (;;) {
const char *end = strchr(line, '\n');
if (!end) end = line + strlen(line);
if (p >= line && p <= end) {
static char buf[256];
auto r = ostd::iter(buf);
if (sourcefile) ostd::format(r, "%s:%d: %s", sourcefile, num, fmt);
else ostd::format(r, "%d: %s", num, fmt);
ostd::ConstCharRange end = ostd::find(line, '\n');
if (!end.empty())
line = line.slice(0, line.distance_front(end));
if (&p[0] >= &line[0] && &p[0] <= &line[line.size()]) {
ostd::CharRange r(buf);
if (!cs.src_file.empty())
ostd::format(r, "%s:%d: %s", cs.src_file, num, fmt);
else
ostd::format(r, "%d: %s", num, fmt);
r.put('\0');
return buf;
}
if (!*end) break;
line = end + 1;
num++;
if (end.empty()) break;
line = end;
line.pop_front();
++num;
}
return fmt;
}
static struct IdentLink {
Ident *id;
IdentLink *next;
int usedargs;
IdentStack *argstack;
} noalias = { nullptr, nullptr, (1 << MAX_ARGUMENTS) - 1, nullptr }, *aliasstack = &noalias;
int dbgalias = variable("dbgalias", 0, 4, 1000, &dbgalias, nullptr, 0);
static void debugalias() {
void debugalias(CsState &cs) {
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) {
for (IdentLink *l = cs.stack; l != &cs.noalias; l = l->next) total++;
for (IdentLink *l = cs.stack; l != &cs.noalias; l = l->next) {
Ident *id = l->id;
++depth;
if (depth < dbgalias) fprintf(stderr, " %d) %s\n", total - depth + 1, id->name.data());
else if (l->next == &noalias) fprintf(stderr, depth == dbgalias ? " %d) %s\n" : " ..%d) %s\n", total - depth + 1, id->name.data());
else if (l->next == &cs.noalias) fprintf(stderr, depth == dbgalias ? " %d) %s\n" : " ..%d) %s\n", total - depth + 1, id->name.data());
}
}
static int nodebug = 0;
static void debugcode(const char *fmt, ...) {
if (nodebug) return;
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
fputc('\n', stderr);
va_end(args);
debugalias();
}
static void debugcodeline(const char *p, const char *fmt, ...) {
if (nodebug) return;
va_list args;
va_start(args, fmt);
vfprintf(stderr, debugline(p, fmt), args);
fputc('\n', stderr);
va_end(args);
debugalias();
}
ICOMMAND(nodebug, "e", (CsState &cs, ostd::uint *body), { nodebug++; executeret(body, *cs.result); nodebug--; });
ICOMMAND(nodebug, "e", (CsState &cs, ostd::uint *body), { cs.nodebug++; executeret(body, *cs.result); cs.nodebug--; });
void pusharg(Ident &id, const TaggedValue &v, IdentStack &stack) {
stack.val = id.val;
@ -277,11 +243,11 @@ static inline void undoarg(Ident &id, IdentStack &stack) {
#define UNDOARGS \
IdentStack argstack[MAX_ARGUMENTS]; \
for(int argmask = aliasstack->usedargs, i = 0; argmask; argmask >>= 1, i++) if(argmask&1) \
for(int argmask = cstate.stack->usedargs, i = 0; argmask; argmask >>= 1, i++) if(argmask&1) \
undoarg(*cstate.identmap[i], argstack[i]); \
IdentLink *prevstack = aliasstack->next; \
IdentLink aliaslink = { aliasstack->id, aliasstack, prevstack->usedargs, prevstack->argstack }; \
aliasstack = &aliaslink;
IdentLink *prevstack = cstate.stack->next; \
IdentLink aliaslink = { cstate.stack->id, cstate.stack, prevstack->usedargs, prevstack->argstack }; \
cstate.stack = &aliaslink;
static inline void redoarg(Ident &id, const IdentStack &stack) {
IdentStack *prev = stack.next;
@ -294,8 +260,8 @@ static inline void redoarg(Ident &id, const IdentStack &stack) {
#define REDOARGS \
prevstack->usedargs = aliaslink.usedargs; \
aliasstack = aliaslink.next; \
for(int argmask = aliasstack->usedargs, i = 0; argmask; argmask >>= 1, i++) if(argmask&1) \
cstate.stack = aliaslink.next; \
for(int argmask = cstate.stack->usedargs, i = 0; argmask; argmask >>= 1, i++) if(argmask&1) \
redoarg(*cstate.identmap[i], argstack[i]);
ICOMMAND(push, "rTe", (CsState &cs, Ident *id, TaggedValue *v, ostd::uint *code), {
@ -338,7 +304,7 @@ Ident *CsState::new_ident(ostd::ConstCharRange name, int flags) {
Ident *id = idents.at(name);
if (!id) {
if (checknumber(name.data())) {
debugcode("number %.*s is not a valid identifier name", int(name.size()), name.data());
debug_code("number %.*s is not a valid identifier name", int(name.size()), name.data());
return dummy;
}
id = add_ident(ID_ALIAS, name, flags);
@ -372,7 +338,7 @@ bool CsState::reset_var(ostd::ConstCharRange name) {
Ident *id = idents.at(name);
if (!id) return false;
if (id->flags & IDF_READONLY) {
debugcode("variable %s is read only", id->name.data());
debug_code("variable %s is read only", id->name.data());
return false;
}
clear_override(*id);
@ -393,13 +359,13 @@ void CsState::touch_var(ostd::ConstCharRange name) {
}
static inline void setarg(Ident &id, TaggedValue &v) {
if (aliasstack->usedargs & (1 << id.index)) {
if (cstate.stack->usedargs & (1 << id.index)) {
if (id.valtype == VAL_STR) delete[] id.val.s;
id.setval(v);
id.clean_code();
} else {
pusharg(id, v, aliasstack->argstack[id.index]);
aliasstack->usedargs |= 1 << id.index;
pusharg(id, v, cstate.stack->argstack[id.index]);
cstate.stack->usedargs |= 1 << id.index;
}
}
@ -428,12 +394,12 @@ static void setalias(const char *name, TaggedValue &v) {
setsvarchecked(id, v.get_str());
break;
default:
debugcode("cannot redefine builtin %s with an alias", id->name.data());
cstate.debug_code("cannot redefine builtin %s with an alias", id->name.data());
break;
}
v.cleanup();
} else if (checknumber(name)) {
debugcode("cannot alias number %s", name);
cstate.debug_code("cannot alias number %s", name);
v.cleanup();
} else {
cstate.add_ident(ID_ALIAS, name, v, cstate.identflags);
@ -479,7 +445,7 @@ char *svariable(const char *name, const char *cur, char **storage, IdentFunc fun
{ \
if(id->flags&IDF_PERSIST) \
{ \
debugcode("cannot override persistent variable %s", id->name.data()); \
cstate.debug_code("cannot override persistent variable %s", id->name.data()); \
errorval; \
} \
if(!(id->flags&IDF_OVERRIDDEN)) { saveval; id->flags |= IDF_OVERRIDDEN; } \
@ -541,7 +507,7 @@ ICOMMAND(identexists, "s", (CsState &cs, char *s), cs.result->set_int(cstate.hav
const char *getalias(const char *name) {
Ident *i = cstate.idents.at(name);
return i && i->type == ID_ALIAS && (i->index >= MAX_ARGUMENTS || aliasstack->usedargs & (1 << i->index)) ? i->get_str() : "";
return i && i->type == ID_ALIAS && (i->index >= MAX_ARGUMENTS || cstate.stack->usedargs & (1 << i->index)) ? i->get_str() : "";
}
ICOMMAND(getalias, "s", (CsState &, char *s), result(getalias(s)));
@ -550,7 +516,7 @@ 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 ?
cstate.debug_code(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.data(), minval, maxval);
@ -558,7 +524,7 @@ int clampvar(Ident *id, int val, int minval, int maxval) {
}
void setvarchecked(Ident *id, int val) {
if (id->flags & IDF_READONLY) debugcode("variable %s is read-only", id->name.data());
if (id->flags & IDF_READONLY) cstate.debug_code("variable %s is read-only", id->name.data());
else {
OVERRIDEVAR(return, id->overrideval.i = *id->storage.i, , )
if (val < id->minval || val > id->maxval) val = clampvar(id, val, id->minval, id->maxval);
@ -580,12 +546,12 @@ 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.data(), floatstr(minval), floatstr(maxval));
cstate.debug_code("valid range for %s is %s..%s", id->name.data(), 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.data());
if (id->flags & IDF_READONLY) cstate.debug_code("variable %s is read-only", id->name.data());
else {
OVERRIDEVAR(return, id->overrideval.f = *id->storage.f, , );
if (val < id->minvalf || val > id->maxvalf) val = clampfvar(id, val, id->minvalf, id->maxvalf);
@ -595,7 +561,7 @@ void setfvarchecked(Ident *id, float val) {
}
void setsvarchecked(Ident *id, const char *val) {
if (id->flags & IDF_READONLY) debugcode("variable %s is read-only", id->name.data());
if (id->flags & IDF_READONLY) cstate.debug_code("variable %s is read-only", id->name.data());
else {
OVERRIDEVAR(return, id->overrideval.s = *id->storage.s, delete[] id->overrideval.s, delete[] * id->storage.s);
*id->storage.s = dup_ostr(val);
@ -1421,7 +1387,7 @@ static void compileblockmain(ostd::Vector<ostd::uint> &code, const char *&p, int
int c = *p++;
switch (c) {
case '\0':
debugcodeline(line, "missing \"]\"");
cstate.debug_code_line(line, "missing \"]\"");
p--;
goto done;
case '\"':
@ -1442,7 +1408,7 @@ static void compileblockmain(ostd::Vector<ostd::uint> &code, const char *&p, int
while (*p == '@') p++;
int level = p - (esc - 1);
if (brak > level) continue;
else if (brak < level) debugcodeline(line, "too many @s");
else if (brak < level) cstate.debug_code_line(line, "too many @s");
if (!concs && prevargs >= MAX_RESULTS) code.push(CODE_ENTER);
if (concs + 2 > MAX_ARGUMENTS) {
code.push(CODE_CONCW | RET_STR | (concs << 8));
@ -1963,14 +1929,14 @@ endstatement:
int c = *p++;
switch (c) {
case '\0':
if (c != brak) debugcodeline(line, "missing \"%c\"", brak);
if (c != brak) cstate.debug_code_line(line, "missing \"%c\"", brak);
p--;
return;
case ')':
case ']':
if (c == brak) return;
debugcodeline(line, "unexpected \"%c\"", c);
cstate.debug_code_line(line, "unexpected \"%c\"", c);
break;
case '/':
@ -2277,7 +2243,7 @@ static int rundepth = 0;
static const ostd::uint *runcode(const ostd::uint *code, TaggedValue &result) {
result.set_null();
if (rundepth >= MAXRUNDEPTH) {
debugcode("exceeded recursion limit");
cstate.debug_code("exceeded recursion limit");
return skipcode(code, result);
}
++rundepth;
@ -2364,7 +2330,7 @@ static const ostd::uint *runcode(const ostd::uint *code, TaggedValue &result) {
case CODE_DOARGS|RET_STR:
case CODE_DOARGS|RET_INT:
case CODE_DOARGS|RET_FLOAT:
if (aliasstack != &noalias) {
if (cstate.stack != &cstate.noalias) {
UNDOARGS
result.cleanup();
runcode(args[--numargs].code, result);
@ -2575,9 +2541,9 @@ static const ostd::uint *runcode(const ostd::uint *code, TaggedValue &result) {
continue;
case CODE_IDENTARG: {
Ident *id = cstate.identmap[op >> 8];
if (!(aliasstack->usedargs & (1 << id->index))) {
pusharg(*id, null_value, aliasstack->argstack[id->index]);
aliasstack->usedargs |= 1 << id->index;
if (!(cstate.stack->usedargs & (1 << id->index))) {
pusharg(*id, null_value, cstate.stack->argstack[id->index]);
cstate.stack->usedargs |= 1 << id->index;
}
args[numargs++].set_ident(id);
continue;
@ -2585,9 +2551,9 @@ static const ostd::uint *runcode(const ostd::uint *code, TaggedValue &result) {
case CODE_IDENTU: {
TaggedValue &arg = args[numargs - 1];
Ident *id = arg.type == VAL_STR || arg.type == VAL_MACRO || arg.type == VAL_CSTR ? cstate.new_ident(arg.cstr, IDF_UNKNOWN) : cstate.dummy;
if (id->index < MAX_ARGUMENTS && !(aliasstack->usedargs & (1 << id->index))) {
pusharg(*id, null_value, aliasstack->argstack[id->index]);
aliasstack->usedargs |= 1 << id->index;
if (id->index < MAX_ARGUMENTS && !(cstate.stack->usedargs & (1 << id->index))) {
pusharg(*id, null_value, cstate.stack->argstack[id->index]);
cstate.stack->usedargs |= 1 << id->index;
}
arg.cleanup();
arg.set_ident(id);
@ -2604,7 +2570,7 @@ static const ostd::uint *runcode(const ostd::uint *code, TaggedValue &result) {
case ID_ALIAS: \
if(id->flags&IDF_UNKNOWN) break; \
arg.cleanup(); \
if(id->index < MAX_ARGUMENTS && !(aliasstack->usedargs&(1<<id->index))) { nval; continue; } \
if(id->index < MAX_ARGUMENTS && !(cstate.stack->usedargs&(1<<id->index))) { nval; continue; } \
aval; \
continue; \
case ID_SVAR: arg.cleanup(); sval; continue; \
@ -2623,7 +2589,7 @@ static const ostd::uint *runcode(const ostd::uint *code, TaggedValue &result) {
} \
default: arg.cleanup(); nval; continue; \
} \
debugcode("unknown alias lookup: %s", arg.s); \
cstate.debug_code("unknown alias lookup: %s", arg.s); \
arg.cleanup(); \
nval; \
continue; \
@ -2636,7 +2602,7 @@ static const ostd::uint *runcode(const ostd::uint *code, TaggedValue &result) {
case CODE_LOOKUP|RET_STR:
#define LOOKUP(aval) { \
Ident *id = cstate.identmap[op>>8]; \
if(id->flags&IDF_UNKNOWN) debugcode("unknown alias lookup: %s", id->name.data()); \
if(id->flags&IDF_UNKNOWN) cstate.debug_code("unknown alias lookup: %s", id->name.data()); \
aval; \
continue; \
}
@ -2644,7 +2610,7 @@ static const ostd::uint *runcode(const ostd::uint *code, TaggedValue &result) {
case CODE_LOOKUPARG|RET_STR:
#define LOOKUPARG(aval, nval) { \
Ident *id = cstate.identmap[op>>8]; \
if(!(aliasstack->usedargs&(1<<id->index))) { nval; continue; } \
if(!(cstate.stack->usedargs&(1<<id->index))) { nval; continue; } \
aval; \
continue; \
}
@ -2858,15 +2824,15 @@ static const ostd::uint *runcode(const ostd::uint *code, TaggedValue &result) {
_numargs = callargs; \
int oldflags = cstate.identflags; \
cstate.identflags |= id->flags&IDF_OVERRIDDEN; \
IdentLink aliaslink = { id, aliasstack, (1<<callargs)-1, argstack }; \
aliasstack = &aliaslink; \
IdentLink aliaslink = { id, cstate.stack, (1<<callargs)-1, argstack }; \
cstate.stack = &aliaslink; \
if(!id->code) id->code = compilecode(id->get_str()); \
ostd::uint *code = id->code; \
code[0] += 0x100; \
runcode(code+1, result); \
code[0] -= 0x100; \
if(int(code[0]) < 0x100) delete[] code; \
aliasstack = aliaslink.next; \
cstate.stack = aliaslink.next; \
cstate.identflags = oldflags; \
for(int i = 0; i < callargs; i++) \
poparg(*cstate.identmap[i]); \
@ -2880,7 +2846,7 @@ static const ostd::uint *runcode(const ostd::uint *code, TaggedValue &result) {
Ident *id = cstate.identmap[op >> 13];
int callargs = (op >> 8) & 0x1F, offset = numargs - callargs;
if (id->flags & IDF_UNKNOWN) {
debugcode("unknown command: %s", id->name.data());
cstate.debug_code("unknown command: %s", id->name.data());
FORCERESULT;
}
CALLALIAS;
@ -2893,7 +2859,7 @@ static const ostd::uint *runcode(const ostd::uint *code, TaggedValue &result) {
result.force_null();
Ident *id = cstate.identmap[op >> 13];
int callargs = (op >> 8) & 0x1F, offset = numargs - callargs;
if (!(aliasstack->usedargs & (1 << id->index))) FORCERESULT;
if (!(cstate.stack->usedargs & (1 << id->index))) FORCERESULT;
CALLALIAS;
continue;
}
@ -2918,7 +2884,7 @@ litval:
if (!id) {
noid:
if (checknumber(idarg.s)) goto litval;
debugcode("unknown command: %s", idarg.s);
cstate.debug_code("unknown command: %s", idarg.s);
result.force_null();
FORCERESULT;
}
@ -2954,7 +2920,7 @@ noid:
else setsvarchecked(id, args[offset].force_str());
FORCERESULT;
case ID_ALIAS:
if (id->index < MAX_ARGUMENTS && !(aliasstack->usedargs & (1 << id->index))) FORCERESULT;
if (id->index < MAX_ARGUMENTS && !(cstate.stack->usedargs & (1 << id->index))) FORCERESULT;
if (id->valtype == VAL_NULL) goto noid;
idarg.cleanup();
CALLALIAS;
@ -2987,7 +2953,7 @@ void executeret(Ident *id, TaggedValue *args, int numargs, TaggedValue &result)
++rundepth;
TaggedValue *prevret = cstate.result;
cstate.result = &result;
if (rundepth > MAXRUNDEPTH) debugcode("exceeded recursion limit");
if (rundepth > MAXRUNDEPTH) cstate.debug_code("exceeded recursion limit");
else if (id) switch (id->type) {
default:
if (!id->fun) break;
@ -3013,7 +2979,7 @@ void executeret(Ident *id, TaggedValue *args, int numargs, TaggedValue &result)
else setsvarchecked(id, args[0].force_str());
break;
case ID_ALIAS:
if (id->index < MAX_ARGUMENTS && !(aliasstack->usedargs & (1 << id->index))) break;
if (id->index < MAX_ARGUMENTS && !(cstate.stack->usedargs & (1 << id->index))) break;
if (id->valtype == VAL_NULL) break;
#define callargs numargs
#define offset 0
@ -3139,7 +3105,7 @@ bool CsState::run_bool(Ident *id, ostd::PointerRange<TaggedValue> args) {
}
bool CsState::run_file(ostd::ConstCharRange fname, bool msg) {
const char *oldsourcefile = sourcefile, *oldsourcestr = sourcestr;
ostd::ConstCharRange oldsrcfile = cstate.src_file, oldsrcstr = cstate.src_str;
char *buf = nullptr;
ostd::Size len;
@ -3154,11 +3120,11 @@ bool CsState::run_file(ostd::ConstCharRange fname, bool msg) {
}
buf[len] = '\0';
sourcefile = fname.data();
sourcestr = buf;
cstate.src_file = fname;
cstate.src_str = ostd::ConstCharRange(buf, len);
cstate.run_int(buf);
sourcefile = oldsourcefile;
sourcestr = oldsourcestr;
cstate.src_file = oldsrcfile;
cstate.src_str = oldsrcstr;
delete[] buf;
return true;
@ -3167,7 +3133,11 @@ error:
return false;
}
ICOMMAND(exec, "sb", (CsState &cs, char *file, int *msg), cs.result->set_int(cs.run_file(file, *msg != 0) ? 1 : 0));
void init_lib_io(CsState &cs) {
cs.add_command("exec", "sb", [](CsState &cs, char *file, int *msg) {
cs.result->set_int(cs.run_file(file, *msg != 0) ? 1 : 0);
});
}
const char *escapestring(const char *s) {
stridx = (stridx + 1) % 4;
@ -3272,7 +3242,7 @@ const char *floatstr(float v) {
ICOMMANDK(do, ID_DO, "e", (CsState &cs, ostd::uint *body), executeret(body, *cs.result));
static void doargs(CsState &cs, ostd::uint *body) {
if (aliasstack != &noalias) {
if (cstate.stack != &cstate.noalias) {
UNDOARGS
executeret(body, *cs.result);
REDOARGS

View File

@ -8,6 +8,7 @@
#include <math.h>
#include <ostd/algorithm.hh>
#include <ostd/array.hh>
#include <ostd/vector.hh>
#include <ostd/string.hh>
#include <ostd/keyset.hh>
@ -23,6 +24,10 @@ inline char *dup_ostr(ostd::ConstCharRange s) {
return r;
}
static constexpr int MAX_ARGUMENTS = 25;
static constexpr int MAX_RESULTS = 7;
static constexpr int MAX_COMARGS = 12;
enum {
VAL_NULL = 0, VAL_INT, VAL_FLOAT, VAL_STR,
VAL_ANY, VAL_CODE, VAL_MACRO, VAL_IDENT, VAL_CSTR,
@ -274,6 +279,18 @@ struct Ident {
void clean_code();
};
struct IdentLink {
Ident *id;
IdentLink *next;
int usedargs;
IdentStack *argstack;
};
void debugalias(CsState &cs);
ostd::ConstCharRange debugline(CsState &cs, ostd::ConstCharRange p,
ostd::ConstCharRange fmt,
ostd::CharRange buf);
struct CsState {
ostd::Keyset<Ident> idents;
ostd::Vector<Ident *> identmap;
@ -281,7 +298,16 @@ struct CsState {
Ident *dummy = nullptr;
TaggedValue *result = nullptr;
IdentLink noalias = {
nullptr, nullptr, (1 << MAX_ARGUMENTS) - 1, nullptr
};
IdentLink *stack = &noalias;
ostd::ConstCharRange src_file;
ostd::ConstCharRange src_str;
int identflags = 0;
int nodebug = 0;
CsState();
~CsState();
@ -337,6 +363,25 @@ struct CsState {
bool run_bool(Ident *id, ostd::PointerRange<TaggedValue> args);
bool run_file(ostd::ConstCharRange fname, bool msg = true);
template<typename ...A>
void debug_code(ostd::ConstCharRange fmt, A &&...args) {
if (nodebug) return;
ostd::err.writefln(fmt, ostd::forward<A>(args)...);
debugalias(*this);
}
template<typename ...A>
void debug_code_line(ostd::ConstCharRange p,
ostd::ConstCharRange fmt, A &&...args) {
if (nodebug) return;
ostd::Array<char, 256> buf;
ostd::err.writefln(debugline(*this, p, fmt,
ostd::CharRange(buf.data(),
buf.size())),
ostd::forward<A>(args)...);
debugalias(*this);
}
};
extern CsState cstate;
@ -552,5 +597,6 @@ void poparg(Ident &id);
#define ICOMMAND(name, nargs, proto, b) ICOMMANDN(name, ICOMMANDNAME(name), nargs, proto, b)
#define ICOMMANDS(name, nargs, proto, b) ICOMMANDNS(name, ICOMMANDSNAME, nargs, proto, b)
void init_lib_io(CsState &cs);
void init_lib_math(CsState &cs);
void init_lib_shell(CsState &cs);