Version 1.3 of Andy Ross's "NASAL" embedded scripting language.

This commit is contained in:
curt 2003-11-25 20:16:28 +00:00
parent c37afce303
commit 1786692406
17 changed files with 3913 additions and 0 deletions

19
simgear/nasal/Makefile.am Normal file
View File

@ -0,0 +1,19 @@
includedir = @includedir@/nasal
lib_LIBRARIES = libnasal.a
include_HEADERS = nasal.h
libnasal_a_SOURCES = \
code.c \
codegen.c \
debug.c \
gc.c \
hash.c \
lex.c \
lib.c \
mathlib.c \
misc.c \
parse.c \
string.c \
vector.c

588
simgear/nasal/code.c Normal file
View File

@ -0,0 +1,588 @@
#include "nasal.h"
#include "code.h"
////////////////////////////////////////////////////////////////////////
// Debugging stuff. ////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
#if !defined(DEBUG_NASAL)
# define DBG(expr) /* noop */
#else
# define DBG(expr) expr
# include <stdio.h>
# include <stdlib.h>
#endif
char* opStringDEBUG(int op);
void printOpDEBUG(int ip, int op);
void printRefDEBUG(naRef r);
void printStackDEBUG(struct Context* ctx);
////////////////////////////////////////////////////////////////////////
// FIXME: need to store a list of all contexts
struct Context globalContext;
#define ERR(c, msg) naRuntimeError((c),(msg))
void naRuntimeError(struct Context* c, char* msg)
{
c->error = msg;
longjmp(c->jumpHandle, 1);
}
int boolify(struct Context* ctx, naRef r)
{
if(IS_NIL(r)) return 0;
if(IS_NUM(r)) return r.num != 0;
if(IS_STR(r)) return 1;
ERR(ctx, "non-scalar used in boolean context");
return 0;
}
static double numify(struct Context* ctx, naRef o)
{
double n;
if(IS_NUM(o)) return o.num;
else if(IS_NIL(o)) ERR(ctx, "nil used in numeric context");
else if(!IS_STR(o)) ERR(ctx, "non-scalar in numeric context");
else if(naStr_tonum(o, &n)) return n;
else ERR(ctx, "non-numeric string in numeric context");
return 0;
}
static naRef stringify(struct Context* ctx, naRef r)
{
if(IS_STR(r)) return r;
if(IS_NUM(r)) return naStr_fromnum(naNewString(ctx), r.num);
ERR(ctx, "non-scalar in string context");
return naNil();
}
static int checkVec(struct Context* ctx, naRef vec, naRef idx)
{
int i = (int)numify(ctx, idx);
if(i < 0 || i >= vec.ref.ptr.vec->size)
ERR(ctx, "vector index out of bounds");
return i;
}
static naRef containerGet(struct Context* ctx, naRef box, naRef key)
{
naRef result = naNil();
if(!IS_SCALAR(key)) ERR(ctx, "container index not scalar");
if(IS_HASH(box)) {
if(!naHash_get(box, key, &result))
ERR(ctx, "undefined value in container");
} else if(IS_VEC(box)) {
result = naVec_get(box, checkVec(ctx, box, key));
} else {
ERR(ctx, "extract from non-container");
}
return result;
}
static void containerSet(struct Context* ctx, naRef box, naRef key, naRef val)
{
if(!IS_SCALAR(key)) ERR(ctx, "container index not scalar");
else if(IS_HASH(box)) naHash_set(box, key, val);
else if(IS_VEC(box)) naVec_set(box, checkVec(ctx, box, key), val);
else ERR(ctx, "insert into non-container");
}
static void initContext(struct Context* c)
{
int i;
for(i=0; i<NUM_NASAL_TYPES; i++)
naGC_init(&(c->pools[i]), i);
c->fTop = c->opTop = c->markTop = 0;
naBZero(c->fStack, MAX_RECURSION * sizeof(struct Frame));
naBZero(c->opStack, MAX_STACK_DEPTH * sizeof(naRef));
// Make sure the args vectors (which are static with the context)
// are initialized to nil.
for(i=0; i<MAX_RECURSION; i++)
c->fStack[i].args = naNil();
c->argPool = naNewVector(c);
// Note we can't use naNewVector() for this; it requires that
// temps exist first.
c->temps = naObj(T_VEC, naGC_get(&(c->pools[T_VEC])));
c->save = naNil();
// Cache pre-calculated "me", "arg" and "parents" scalars
c->meRef = naStr_fromdata(naNewString(c), "me", 2);
c->argRef = naStr_fromdata(naNewString(c), "arg", 3);
c->parentsRef = naStr_fromdata(naNewString(c), "parents", 7);
}
struct Context* naNewContext()
{
// FIXME: need more than one!
struct Context* c = &globalContext;
initContext(c);
return c;
}
void naGarbageCollect()
{
int i;
struct Context* c = &globalContext; // FIXME: more than one!
for(i=0; i < c->fTop; i++) {
naGC_mark(c->fStack[i].func);
naGC_mark(c->fStack[i].locals);
}
for(i=0; i < MAX_RECURSION; i++)
naGC_mark(c->fStack[i].args); // collect *all* the argument lists
for(i=0; i < c->opTop; i++)
naGC_mark(c->opStack[i]);
naGC_mark(c->argPool);
naGC_mark(c->temps);
naGC_mark(c->save);
naGC_mark(c->meRef);
naGC_mark(c->argRef);
naGC_mark(c->parentsRef);
// Finally collect all the freed objects
for(i=0; i<NUM_NASAL_TYPES; i++)
naGC_reap(&(c->pools[i]));
}
void setupFuncall(struct Context* ctx, naRef func, naRef args)
{
struct Frame* f;
f = &(ctx->fStack[ctx->fTop++]);
f->func = func;
f->ip = 0;
f->bp = ctx->opTop;
f->line = 0;
DBG(printf("Entering frame %d\n", ctx->fTop-1);)
if(!IS_REF(func))
ERR(ctx, "function/method call invoked on uncallable object");
f->args = args;
if(IS_CCODE(func.ref.ptr.func->code)) {
f->locals = naNil();
} else if(IS_CODE(func.ref.ptr.func->code)) {
f->locals = naNewHash(ctx);
naHash_set(f->locals, ctx->argRef, args);
} else {
ERR(ctx, "function/method call invoked on uncallable object");
}
}
static naRef evalAndOr(struct Context* ctx, int op, naRef ra, naRef rb)
{
int a = boolify(ctx, ra);
int b = boolify(ctx, rb);
int result;
if(op == OP_AND) result = a && b ? 1 : 0;
else result = a || b ? 1 : 0;
return naNum(result);
}
static naRef evalEquality(int op, naRef ra, naRef rb)
{
int result = naEqual(ra, rb);
return naNum((op==OP_EQ) ? result : !result);
}
static naRef evalBinaryNumeric(struct Context* ctx, int op, naRef ra, naRef rb)
{
double a = numify(ctx, ra), b = numify(ctx, rb);
switch(op) {
case OP_PLUS: return naNum(a + b);
case OP_MINUS: return naNum(a - b);
case OP_MUL: return naNum(a * b);
case OP_DIV: return naNum(a / b);
case OP_LT: return naNum(a < b ? 1 : 0);
case OP_LTE: return naNum(a <= b ? 1 : 0);
case OP_GT: return naNum(a > b ? 1 : 0);
case OP_GTE: return naNum(a >= b ? 1 : 0);
}
return naNil();
}
// When a code object comes out of the constant pool and shows up on
// the stack, it needs to be bound with the lexical context.
static naRef bindFunction(struct Context* ctx, struct Frame* f, naRef code)
{
naRef next = f->func.ref.ptr.func->closure;
naRef closure = naNewClosure(ctx, f->locals, next);
naRef result = naNewFunc(ctx, code);
result.ref.ptr.func->closure = closure;
return result;
}
static int getClosure(struct naClosure* c, naRef sym, naRef* result)
{
while(c) {
if(naHash_get(c->namespace, sym, result)) return 1;
c = c->next.ref.ptr.closure;
}
return 0;
}
// Get a local symbol, or check the closure list if it isn't there
static naRef getLocal(struct Context* ctx, struct Frame* f, naRef sym)
{
naRef result;
if(!naHash_get(f->locals, sym, &result)) {
naRef c = f->func.ref.ptr.func->closure;
if(!getClosure(c.ref.ptr.closure, sym, &result))
ERR(ctx, "undefined symbol");
}
return result;
}
static int setClosure(naRef closure, naRef sym, naRef val)
{
struct naClosure* c = closure.ref.ptr.closure;
if(c == 0) { return 0; }
else if(naHash_tryset(c->namespace, sym, val)) { return 1; }
else { return setClosure(c->next, sym, val); }
}
static naRef setLocal(struct Frame* f, naRef sym, naRef val)
{
// Try the locals first, if not already there try the closures in
// order. Finally put it in the locals if nothing matched.
if(!naHash_tryset(f->locals, sym, val))
if(!setClosure(f->func.ref.ptr.func->closure, sym, val))
naHash_set(f->locals, sym, val);
return val;
}
// Recursively descend into the parents lists
static int getMember(struct Context* ctx, naRef obj, naRef fld, naRef* result)
{
naRef p;
if(!IS_HASH(obj)) ERR(ctx, "non-objects have no members");
if(naHash_get(obj, fld, result)) {
return 1;
} else if(naHash_get(obj, ctx->parentsRef, &p)) {
int i;
if(!IS_VEC(p)) ERR(ctx, "parents field not vector");
for(i=0; i<p.ref.ptr.vec->size; i++)
if(getMember(ctx, p.ref.ptr.vec->array[i], fld, result))
return 1;
}
return 0;
}
static void PUSH(struct Context* ctx, naRef r)
{
if(ctx->opTop >= MAX_STACK_DEPTH) ERR(ctx, "stack overflow");
ctx->opStack[ctx->opTop++] = r;
}
static naRef POP(struct Context* ctx)
{
if(ctx->opTop == 0) ERR(ctx, "BUG: stack underflow");
return ctx->opStack[--ctx->opTop];
}
static naRef TOP(struct Context* ctx)
{
if(ctx->opTop == 0) ERR(ctx, "BUG: stack underflow");
return ctx->opStack[ctx->opTop-1];
}
static int ARG16(unsigned char* byteCode, struct Frame* f)
{
int arg = byteCode[f->ip]<<8 | byteCode[f->ip+1];
f->ip += 2;
return arg;
}
// OP_EACH works like a vector get, except that it leaves the vector
// and index on the stack, increments the index after use, and pops
// the arguments and pushes a nil if the index is beyond the end.
static void evalEach(struct Context* ctx)
{
int idx = (int)(ctx->opStack[ctx->opTop-1].num);
naRef vec = ctx->opStack[ctx->opTop-2];
if(idx >= vec.ref.ptr.vec->size) {
ctx->opTop -= 2; // pop two values
PUSH(ctx, naNil());
return;
}
ctx->opStack[ctx->opTop-1].num = idx+1; // modify in place
PUSH(ctx, naVec_get(vec, idx));
}
static void run1(struct Context* ctx, struct Frame* f, naRef code)
{
naRef a, b, c;
struct naCode* cd = code.ref.ptr.code;
int op, arg;
if(f->ip >= cd->nBytes) {
DBG(printf("Done with frame %d\n", ctx->fTop-1);)
ctx->fTop--;
if(ctx->fTop <= 0)
ctx->done = 1;
return;
}
op = cd->byteCode[f->ip++];
DBG(printf("Stack Depth: %d\n", ctx->opTop));
DBG(printOpDEBUG(f->ip-1, op));
switch(op) {
case OP_POP:
POP(ctx);
break;
case OP_DUP:
PUSH(ctx, ctx->opStack[ctx->opTop-1]);
break;
case OP_XCHG:
a = POP(ctx); b = POP(ctx);
PUSH(ctx, a); PUSH(ctx, b);
break;
case OP_PLUS: case OP_MUL: case OP_DIV: case OP_MINUS:
case OP_LT: case OP_LTE: case OP_GT: case OP_GTE:
a = POP(ctx); b = POP(ctx);
PUSH(ctx, evalBinaryNumeric(ctx, op, b, a));
break;
case OP_EQ: case OP_NEQ:
a = POP(ctx); b = POP(ctx);
PUSH(ctx, evalEquality(op, b, a));
break;
case OP_AND: case OP_OR:
a = POP(ctx); b = POP(ctx);
PUSH(ctx, evalAndOr(ctx, op, a, b));
break;
case OP_CAT:
a = stringify(ctx, POP(ctx)); b = stringify(ctx, POP(ctx));
c = naStr_concat(naNewString(ctx), b, a);
PUSH(ctx, c);
break;
case OP_NEG:
a = POP(ctx);
PUSH(ctx, naNum(-numify(ctx, a)));
break;
case OP_NOT:
a = POP(ctx);
PUSH(ctx, naNum(boolify(ctx, a) ? 0 : 1));
break;
case OP_PUSHCONST:
a = cd->constants[ARG16(cd->byteCode, f)];
if(IS_CODE(a)) a = bindFunction(ctx, f, a);
PUSH(ctx, a);
break;
case OP_PUSHONE:
PUSH(ctx, naNum(1));
break;
case OP_PUSHZERO:
PUSH(ctx, naNum(0));
break;
case OP_PUSHNIL:
PUSH(ctx, naNil());
break;
case OP_NEWVEC:
PUSH(ctx, naNewVector(ctx));
break;
case OP_VAPPEND:
b = POP(ctx); a = TOP(ctx);
naVec_append(a, b);
break;
case OP_NEWHASH:
PUSH(ctx, naNewHash(ctx));
break;
case OP_HAPPEND:
c = POP(ctx); b = POP(ctx); a = TOP(ctx); // a,b,c: hash, key, val
naHash_set(a, b, c);
break;
case OP_LOCAL:
a = getLocal(ctx, f, POP(ctx));
PUSH(ctx, a);
break;
case OP_SETLOCAL:
a = POP(ctx); b = POP(ctx);
PUSH(ctx, setLocal(f, b, a));
break;
case OP_MEMBER:
a = POP(ctx); b = POP(ctx);
if(!getMember(ctx, b, a, &c))
ERR(ctx, "no such member");
PUSH(ctx, c);
break;
case OP_SETMEMBER:
c = POP(ctx); b = POP(ctx); a = POP(ctx); // a,b,c: hash, key, val
if(!IS_HASH(a)) ERR(ctx, "non-objects have no members");
naHash_set(a, b, c);
PUSH(ctx, c);
break;
case OP_INSERT:
c = POP(ctx); b = POP(ctx); a = POP(ctx); // a,b,c: box, key, val
containerSet(ctx, a, b, c);
PUSH(ctx, c);
break;
case OP_EXTRACT:
b = POP(ctx); a = POP(ctx); // a,b: box, key
PUSH(ctx, containerGet(ctx, a, b));
break;
case OP_JMP:
f->ip = ARG16(cd->byteCode, f);
DBG(printf(" [Jump to: %d]\n", f->ip);)
break;
case OP_JIFNIL:
arg = ARG16(cd->byteCode, f);
a = TOP(ctx);
if(IS_NIL(a)) {
POP(ctx); // Pops **ONLY** if it's nil!
f->ip = arg;
DBG(printf(" [Jump to: %d]\n", f->ip);)
}
break;
case OP_JIFNOT:
arg = ARG16(cd->byteCode, f);
if(!boolify(ctx, POP(ctx))) {
f->ip = arg;
DBG(printf(" [Jump to: %d]\n", f->ip);)
}
break;
case OP_FCALL:
b = POP(ctx); a = POP(ctx); // a,b = func, args
setupFuncall(ctx, a, b);
break;
case OP_MCALL:
c = POP(ctx); b = POP(ctx); a = POP(ctx); // a,b,c = obj, func, args
setupFuncall(ctx, b, c);
naHash_set(ctx->fStack[ctx->fTop-1].locals, ctx->meRef, a);
break;
case OP_RETURN:
a = POP(ctx);
ctx->opTop = f->bp; // restore the correct stack frame!
ctx->fTop--;
ctx->fStack[ctx->fTop].args.ref.ptr.vec->size = 0;
naVec_append(ctx->argPool, ctx->fStack[ctx->fTop].args);
PUSH(ctx, a);
break;
case OP_LINE:
f->line = ARG16(cd->byteCode, f);
break;
case OP_EACH:
evalEach(ctx);
break;
case OP_MARK: // save stack state (e.g. "setjmp")
ctx->markStack[ctx->markTop++] = ctx->opTop;
break;
case OP_UNMARK: // pop stack state set by mark
ctx->markTop--;
break;
case OP_BREAK: // restore stack state (FOLLOW WITH JMP!)
ctx->opTop = ctx->markStack[--ctx->markTop];
break;
case OP_NEWARGS: // push a new function arg vector
PUSH(ctx, (naVec_size(ctx->argPool) ?
naVec_removelast(ctx->argPool) : naNewVector(ctx)));
break;
default:
ERR(ctx, "BUG: bad opcode");
}
if(ctx->fTop <= 0)
ctx->done = 1;
}
static void nativeCall(struct Context* ctx, struct Frame* f, naRef ccode)
{
naCFunction fptr = ccode.ref.ptr.ccode->fptr;
naRef result = (*fptr)(ctx, f->args);
ctx->fTop--;
ctx->fStack[ctx->fTop].args.ref.ptr.vec->size = 0;
PUSH(ctx, result);
}
void naSave(struct Context* ctx, naRef obj)
{
ctx->save = obj;
}
int naStackDepth(struct Context* ctx)
{
return ctx->fTop;
}
int naGetLine(struct Context* ctx, int frame)
{
return ctx->fStack[ctx->fTop-1-frame].line;
}
naRef naGetSourceFile(struct Context* ctx, int frame)
{
naRef f = ctx->fStack[ctx->fTop-1-frame].func;
f = f.ref.ptr.func->code;
return f.ref.ptr.code->srcFile;
}
char* naGetError(struct Context* ctx)
{
return ctx->error;
}
static naRef run(naContext ctx)
{
// Return early if an error occurred. It will be visible to the
// caller via naGetError().
if(setjmp(ctx->jumpHandle))
return naNil();
ctx->done = 0;
while(!ctx->done) {
struct Frame* f = &(ctx->fStack[ctx->fTop-1]);
naRef code = f->func.ref.ptr.func->code;
if(IS_CCODE(code)) nativeCall(ctx, f, code);
else run1(ctx, f, code);
ctx->temps.ref.ptr.vec->size = 0; // Reset the temporaries
DBG(printStackDEBUG(ctx);)
}
DBG(printStackDEBUG(ctx);)
return ctx->opStack[--ctx->opTop];
}
naRef naBindFunction(naContext ctx, naRef code, naRef closure)
{
naRef func = naNewFunc(ctx, code);
func.ref.ptr.func->closure = naNewClosure(ctx, closure, naNil());
return func;
}
naRef naCall(naContext ctx, naRef func, naRef args, naRef obj, naRef locals)
{
// We might have to allocate objects, which can call the GC. But
// the call isn't on the Nasal stack yet, so the GC won't find our
// C-space arguments.
naVec_append(ctx->temps, func);
naVec_append(ctx->temps, args);
naVec_append(ctx->temps, obj);
naVec_append(ctx->temps, locals);
if(IS_NIL(args))
args = naNewVector(ctx);
if(IS_NIL(locals))
locals = naNewHash(ctx);
if(!IS_FUNC(func)) {
// Generate a noop closure for bare code objects
naRef code = func;
func = naNewFunc(ctx, code);
func.ref.ptr.func->closure = naNewClosure(ctx, locals, naNil());
}
if(!IS_NIL(obj))
naHash_set(locals, ctx->meRef, obj);
ctx->fTop = ctx->opTop = ctx->markTop = 0;
setupFuncall(ctx, func, args);
ctx->fStack[ctx->fTop-1].locals = locals;
return run(ctx);
}

68
simgear/nasal/code.h Normal file
View File

@ -0,0 +1,68 @@
#ifndef _CODE_H
#define _CODE_H
#include <setjmp.h>
#include "nasal.h"
#include "data.h"
#define MAX_STACK_DEPTH 1024
#define MAX_RECURSION 128
#define MAX_MARK_DEPTH 32
enum {
OP_AND, OP_OR, OP_NOT, OP_MUL, OP_PLUS, OP_MINUS, OP_DIV, OP_NEG,
OP_CAT, OP_LT, OP_LTE, OP_GT, OP_GTE, OP_EQ, OP_NEQ, OP_EACH,
OP_JMP, OP_JIFNOT, OP_JIFNIL, OP_FCALL, OP_MCALL, OP_RETURN,
OP_PUSHCONST, OP_PUSHONE, OP_PUSHZERO, OP_PUSHNIL, OP_POP,
OP_DUP, OP_XCHG, OP_INSERT, OP_EXTRACT, OP_MEMBER, OP_SETMEMBER,
OP_LOCAL, OP_SETLOCAL, OP_NEWVEC, OP_VAPPEND, OP_NEWHASH, OP_HAPPEND,
OP_LINE, OP_MARK, OP_UNMARK, OP_BREAK, OP_NEWARGS
};
struct Frame {
naRef func; // naFunc object
naRef locals; // local per-call namespace
naRef args; // vector of arguments
int ip; // instruction pointer into code
int bp; // opStack pointer to start of frame
int line; // current line number
};
struct Context {
// Garbage collecting allocators:
struct naPool pools[NUM_NASAL_TYPES];
// Stack(s)
struct Frame fStack[MAX_RECURSION];
int fTop;
naRef opStack[MAX_STACK_DEPTH];
int opTop;
int markStack[MAX_MARK_DEPTH];
int markTop;
int done;
// Vector of arguments vectors. A LIFO cache, basically, to avoid
// thrashing the GC just for function call arguments.
naRef argPool;
// Constants
naRef meRef;
naRef argRef;
naRef parentsRef;
// Error handling
jmp_buf jumpHandle;
char* error;
// GC-findable reference point for objects that may live on the
// processor ("real") stack during execution. naNew() places them
// here, and clears the array each time we return from a C
// function.
naRef temps;
naRef save;
};
void printRefDEBUG(naRef r);
#endif // _CODE_H

540
simgear/nasal/codegen.c Normal file
View File

@ -0,0 +1,540 @@
#include "parse.h"
#include "code.h"
// These are more sensical predicate names in most contexts in this file
#define LEFT(tok) ((tok)->children)
#define RIGHT(tok) ((tok)->lastChild)
#define BINARY(tok) (LEFT(tok) && RIGHT(tok) && LEFT(tok) != RIGHT(tok))
// Forward references for recursion
static void genExpr(struct Parser* p, struct Token* t);
static void genExprList(struct Parser* p, struct Token* t);
static void emit(struct Parser* p, int byte)
{
if(p->cg->nBytes >= p->cg->codeAlloced) {
int i, sz = p->cg->codeAlloced * 2;
unsigned char* buf = naParseAlloc(p, sz);
for(i=0; i<p->cg->codeAlloced; i++) buf[i] = p->cg->byteCode[i];
p->cg->byteCode = buf;
p->cg->codeAlloced = sz;
}
p->cg->byteCode[p->cg->nBytes++] = (unsigned char)byte;
}
static void emitImmediate(struct Parser* p, int byte, int arg)
{
emit(p, byte);
emit(p, arg >> 8);
emit(p, arg & 0xff);
}
static void genBinOp(int op, struct Parser* p, struct Token* t)
{
if(!LEFT(t) || !RIGHT(t))
naParseError(p, "empty subexpression", t->line);
genExpr(p, LEFT(t));
genExpr(p, RIGHT(t));
emit(p, op);
}
static int newConstant(struct Parser* p, naRef c)
{
int i = p->cg->nConsts++;
if(i > 0xffff) naParseError(p, "too many constants in code block", 0);
naHash_set(p->cg->consts, naNum(i), c);
return i;
}
static naRef getConstant(struct Parser* p, int idx)
{
naRef c;
naHash_get(p->cg->consts, naNum(idx), &c);
return c;
}
// Interns a scalar (!) constant and returns its index
static int internConstant(struct Parser* p, naRef c)
{
naRef r;
naHash_get(p->cg->interned, c, &r);
if(!IS_NIL(r)) {
return (int)r.num;
} else {
int idx = newConstant(p, c);
naHash_set(p->cg->interned, c, naNum(idx));
return idx;
}
}
static void genScalarConstant(struct Parser* p, struct Token* t)
{
naRef c = (t->str
? naStr_fromdata(naNewString(p->context), t->str, t->strlen)
: naNum(t->num));
int idx = internConstant(p, c);
emitImmediate(p, OP_PUSHCONST, idx);
}
static int genLValue(struct Parser* p, struct Token* t)
{
if(t->type == TOK_LPAR) {
return genLValue(p, LEFT(t)); // Handle stuff like "(a) = 1"
} else if(t->type == TOK_SYMBOL) {
genScalarConstant(p, t);
return OP_SETLOCAL;
} else if(t->type == TOK_DOT && RIGHT(t) && RIGHT(t)->type == TOK_SYMBOL) {
genExpr(p, LEFT(t));
genScalarConstant(p, RIGHT(t));
return OP_SETMEMBER;
} else if(t->type == TOK_LBRA) {
genExpr(p, LEFT(t));
genExpr(p, RIGHT(t));
return OP_INSERT;
} else {
naParseError(p, "bad lvalue", t->line);
return -1;
}
}
static void genLambda(struct Parser* p, struct Token* t)
{
int idx;
struct CodeGenerator* cgSave;
naRef codeObj;
if(LEFT(t)->type != TOK_LCURL)
naParseError(p, "bad function definition", t->line);
// Save off the generator state while we do the new one
cgSave = p->cg;
codeObj = naCodeGen(p, LEFT(LEFT(t)));
p->cg = cgSave;
idx = newConstant(p, codeObj);
emitImmediate(p, OP_PUSHCONST, idx);
}
static void genList(struct Parser* p, struct Token* t)
{
if(t->type == TOK_COMMA) {
genExpr(p, LEFT(t));
emit(p, OP_VAPPEND);
genList(p, RIGHT(t));
} else if(t->type == TOK_EMPTY) {
return;
} else {
genExpr(p, t);
emit(p, OP_VAPPEND);
}
}
static void genHashElem(struct Parser* p, struct Token* t)
{
if(t->type == TOK_EMPTY)
return;
if(t->type != TOK_COLON)
naParseError(p, "bad hash/object initializer", t->line);
if(LEFT(t)->type == TOK_SYMBOL) genScalarConstant(p, LEFT(t));
else if(LEFT(t)->type == TOK_LITERAL) genExpr(p, LEFT(t));
else naParseError(p, "bad hash/object initializer", t->line);
genExpr(p, RIGHT(t));
emit(p, OP_HAPPEND);
}
static void genHash(struct Parser* p, struct Token* t)
{
if(t->type == TOK_COMMA) {
genHashElem(p, LEFT(t));
genHash(p, RIGHT(t));
} else if(t->type != TOK_EMPTY) {
genHashElem(p, t);
}
}
static void genFuncall(struct Parser* p, struct Token* t)
{
int op = OP_FCALL;
if(LEFT(t)->type == TOK_DOT) {
genExpr(p, LEFT(LEFT(t)));
emit(p, OP_DUP);
genScalarConstant(p, RIGHT(LEFT(t)));
emit(p, OP_MEMBER);
op = OP_MCALL;
} else {
genExpr(p, LEFT(t));
}
emit(p, OP_NEWARGS);
if(RIGHT(t)) genList(p, RIGHT(t));
emit(p, op);
}
static void pushLoop(struct Parser* p, struct Token* label)
{
int i = p->cg->loopTop;
p->cg->loops[i].breakIP = 0xffffff;
p->cg->loops[i].contIP = 0xffffff;
p->cg->loops[i].label = label;
p->cg->loopTop++;
emit(p, OP_MARK);
}
static void popLoop(struct Parser* p)
{
p->cg->loopTop--;
if(p->cg->loopTop < 0) naParseError(p, "BUG: loop stack underflow", -1);
emit(p, OP_UNMARK);
}
// Emit a jump operation, and return the location of the address in
// the bytecode for future fixup in fixJumpTarget
static int emitJump(struct Parser* p, int op)
{
int ip;
emit(p, op);
ip = p->cg->nBytes;
emit(p, 0xff); // dummy address
emit(p, 0xff);
return ip;
}
// Points a previous jump instruction at the current "end-of-bytecode"
static void fixJumpTarget(struct Parser* p, int spot)
{
p->cg->byteCode[spot] = p->cg->nBytes >> 8;
p->cg->byteCode[spot+1] = p->cg->nBytes & 0xff;
}
static void genShortCircuit(struct Parser* p, struct Token* t)
{
int jumpNext, jumpEnd, isAnd = (t->type == TOK_AND);
genExpr(p, LEFT(t));
if(isAnd) emit(p, OP_NOT);
jumpNext = emitJump(p, OP_JIFNOT);
emit(p, isAnd ? OP_PUSHNIL : OP_PUSHONE);
jumpEnd = emitJump(p, OP_JMP);
fixJumpTarget(p, jumpNext);
genExpr(p, RIGHT(t));
fixJumpTarget(p, jumpEnd);
}
static void genIf(struct Parser* p, struct Token* tif, struct Token* telse)
{
int jumpNext, jumpEnd;
genExpr(p, tif->children); // the test
jumpNext = emitJump(p, OP_JIFNOT);
genExprList(p, tif->children->next->children); // the body
jumpEnd = emitJump(p, OP_JMP);
fixJumpTarget(p, jumpNext);
if(telse) {
if(telse->type == TOK_ELSIF) genIf(p, telse, telse->next);
else genExprList(p, telse->children->children);
} else {
emit(p, OP_PUSHNIL);
}
fixJumpTarget(p, jumpEnd);
}
static void genIfElse(struct Parser* p, struct Token* t)
{
genIf(p, t, t->children->next->next);
}
static int countSemis(struct Token* t)
{
if(!t || t->type != TOK_SEMI) return 0;
return 1 + countSemis(RIGHT(t));
}
static void genLoop(struct Parser* p, struct Token* body,
struct Token* update, struct Token* label,
int loopTop, int jumpEnd)
{
int cont, jumpOverContinue;
p->cg->loops[p->cg->loopTop-1].breakIP = jumpEnd-1;
jumpOverContinue = emitJump(p, OP_JMP);
p->cg->loops[p->cg->loopTop-1].contIP = p->cg->nBytes;
cont = emitJump(p, OP_JMP);
fixJumpTarget(p, jumpOverContinue);
genExprList(p, body);
emit(p, OP_POP);
fixJumpTarget(p, cont);
if(update) { genExpr(p, update); emit(p, OP_POP); }
emitImmediate(p, OP_JMP, loopTop);
fixJumpTarget(p, jumpEnd);
popLoop(p);
emit(p, OP_PUSHNIL); // Leave something on the stack
}
static void genForWhile(struct Parser* p, struct Token* init,
struct Token* test, struct Token* update,
struct Token* body, struct Token* label)
{
int loopTop, jumpEnd;
if(init) { genExpr(p, init); emit(p, OP_POP); }
pushLoop(p, label);
loopTop = p->cg->nBytes;
genExpr(p, test);
jumpEnd = emitJump(p, OP_JIFNOT);
genLoop(p, body, update, label, loopTop, jumpEnd);
}
static void genWhile(struct Parser* p, struct Token* t)
{
struct Token *test=LEFT(t)->children, *body, *label=0;
int semis = countSemis(test);
if(semis == 1) {
label = LEFT(test);
if(!label || label->type != TOK_SYMBOL)
naParseError(p, "bad loop label", t->line);
test = RIGHT(test);
}
else if(semis != 0)
naParseError(p, "too many semicolons in while test", t->line);
body = LEFT(RIGHT(t));
genForWhile(p, 0, test, 0, body, label);
}
static void genFor(struct Parser* p, struct Token* t)
{
struct Token *init, *test, *body, *update, *label=0;
struct Token *h = LEFT(t)->children;
int semis = countSemis(h);
if(semis == 3) {
if(!LEFT(h) || LEFT(h)->type != TOK_SYMBOL)
naParseError(p, "bad loop label", h->line);
label = LEFT(h);
h=RIGHT(h);
} else if(semis != 2) {
naParseError(p, "wrong number of terms in for header", t->line);
}
// Parse tree hell :)
init = LEFT(h);
test = LEFT(RIGHT(h));
update = RIGHT(RIGHT(h));
body = RIGHT(t)->children;
genForWhile(p, init, test, update, body, label);
}
static void genForEach(struct Parser* p, struct Token* t)
{
int loopTop, jumpEnd, assignOp;
struct Token *elem, *body, *vec, *label=0;
struct Token *h = LEFT(LEFT(t));
int semis = countSemis(h);
if(semis == 2) {
if(!LEFT(h) || LEFT(h)->type != TOK_SYMBOL)
naParseError(p, "bad loop label", h->line);
label = LEFT(h);
h = RIGHT(h);
} else if (semis != 1) {
naParseError(p, "wrong number of terms in foreach header", t->line);
}
elem = LEFT(h);
vec = RIGHT(h);
body = RIGHT(t)->children;
pushLoop(p, label);
genExpr(p, vec);
emit(p, OP_PUSHZERO);
loopTop = p->cg->nBytes;
emit(p, OP_EACH);
jumpEnd = emitJump(p, OP_JIFNIL);
assignOp = genLValue(p, elem);
emit(p, OP_XCHG);
emit(p, assignOp);
emit(p, OP_POP);
genLoop(p, body, 0, label, loopTop, jumpEnd);
}
static int tokMatch(struct Token* a, struct Token* b)
{
int i, l = a->strlen;
if(!a || !b) return 0;
if(l != b->strlen) return 0;
for(i=0; i<l; i++) if(a->str[i] != b->str[i]) return 0;
return 1;
}
static void genBreakContinue(struct Parser* p, struct Token* t)
{
int levels = 1, loop = -1, bp, cp, i;
if(RIGHT(t)) {
if(RIGHT(t)->type != TOK_SYMBOL)
naParseError(p, "bad break/continue label", t->line);
for(i=0; i<p->cg->loopTop; i++)
if(tokMatch(RIGHT(t), p->cg->loops[i].label))
loop = i;
if(loop == -1)
naParseError(p, "no match for break/continue label", t->line);
levels = p->cg->loopTop - loop;
}
bp = p->cg->loops[p->cg->loopTop - levels].breakIP;
cp = p->cg->loops[p->cg->loopTop - levels].contIP;
for(i=0; i<levels; i++)
emit(p, OP_BREAK);
if(t->type == TOK_BREAK)
emit(p, OP_PUSHNIL); // breakIP is always a JIFNOT/JIFNIL!
emitImmediate(p, OP_JMP, t->type == TOK_BREAK ? bp : cp);
}
static void genExpr(struct Parser* p, struct Token* t)
{
int i;
if(t == 0)
naParseError(p, "BUG: null subexpression", -1);
if(t->line != p->cg->lastLine)
emitImmediate(p, OP_LINE, t->line);
p->cg->lastLine = t->line;
switch(t->type) {
case TOK_IF:
genIfElse(p, t);
break;
case TOK_WHILE:
genWhile(p, t);
break;
case TOK_FOR:
genFor(p, t);
break;
case TOK_FOREACH:
genForEach(p, t);
break;
case TOK_BREAK: case TOK_CONTINUE:
genBreakContinue(p, t);
break;
case TOK_TOP:
genExprList(p, LEFT(t));
break;
case TOK_FUNC:
genLambda(p, t);
break;
case TOK_LPAR:
if(BINARY(t) || !RIGHT(t)) genFuncall(p, t); // function invocation
else genExpr(p, LEFT(t)); // simple parenthesis
break;
case TOK_LBRA:
if(BINARY(t)) {
genBinOp(OP_EXTRACT, p, t); // a[i]
} else {
emit(p, OP_NEWVEC);
genList(p, LEFT(t));
}
break;
case TOK_LCURL:
emit(p, OP_NEWHASH);
genHash(p, LEFT(t));
break;
case TOK_ASSIGN:
i = genLValue(p, LEFT(t));
genExpr(p, RIGHT(t));
emit(p, i); // use the op appropriate to the lvalue
break;
case TOK_RETURN:
if(RIGHT(t)) genExpr(p, RIGHT(t));
else emit(p, OP_PUSHNIL);
emit(p, OP_RETURN);
break;
case TOK_NOT:
genExpr(p, RIGHT(t));
emit(p, OP_NOT);
break;
case TOK_SYMBOL:
genScalarConstant(p, t);
emit(p, OP_LOCAL);
break;
case TOK_LITERAL:
genScalarConstant(p, t);
break;
case TOK_MINUS:
if(BINARY(t)) {
genBinOp(OP_MINUS, p, t); // binary subtraction
} else if(RIGHT(t)->type == TOK_LITERAL && !RIGHT(t)->str) {
RIGHT(t)->num *= -1; // Pre-negate constants
genScalarConstant(p, RIGHT(t));
} else {
genExpr(p, RIGHT(t)); // unary negation
emit(p, OP_NEG);
}
break;
case TOK_NEG:
genExpr(p, RIGHT(t)); // unary negation (see also TOK_MINUS!)
emit(p, OP_NEG);
break;
case TOK_DOT:
genExpr(p, LEFT(t));
if(RIGHT(t)->type != TOK_SYMBOL)
naParseError(p, "object field not symbol", RIGHT(t)->line);
genScalarConstant(p, RIGHT(t));
emit(p, OP_MEMBER);
break;
case TOK_EMPTY: case TOK_NIL:
emit(p, OP_PUSHNIL); break; // *NOT* a noop!
case TOK_AND: case TOK_OR:
genShortCircuit(p, t);
break;
case TOK_MUL: genBinOp(OP_MUL, p, t); break;
case TOK_PLUS: genBinOp(OP_PLUS, p, t); break;
case TOK_DIV: genBinOp(OP_DIV, p, t); break;
case TOK_CAT: genBinOp(OP_CAT, p, t); break;
case TOK_LT: genBinOp(OP_LT, p, t); break;
case TOK_LTE: genBinOp(OP_LTE, p, t); break;
case TOK_EQ: genBinOp(OP_EQ, p, t); break;
case TOK_NEQ: genBinOp(OP_NEQ, p, t); break;
case TOK_GT: genBinOp(OP_GT, p, t); break;
case TOK_GTE: genBinOp(OP_GTE, p, t); break;
default:
naParseError(p, "parse error", t->line);
};
}
static void genExprList(struct Parser* p, struct Token* t)
{
if(t->type == TOK_SEMI) {
genExpr(p, LEFT(t));
if(RIGHT(t) && RIGHT(t)->type != TOK_EMPTY) {
emit(p, OP_POP);
genExprList(p, RIGHT(t));
}
} else {
genExpr(p, t);
}
}
naRef naCodeGen(struct Parser* p, struct Token* t)
{
int i;
naRef codeObj;
struct naCode* code;
struct CodeGenerator cg;
cg.lastLine = 0;
cg.codeAlloced = 1024; // Start fairly big, this is a cheap allocation
cg.byteCode = naParseAlloc(p, cg.codeAlloced);
cg.nBytes = 0;
cg.consts = naNewHash(p->context);
cg.interned = naNewHash(p->context);
cg.nConsts = 0;
cg.loopTop = 0;
p->cg = &cg;
genExprList(p, t);
// Now make a code object
codeObj = naNewCode(p->context);
code = codeObj.ref.ptr.code;
code->nBytes = cg.nBytes;
code->byteCode = naAlloc(cg.nBytes);
for(i=0; i < cg.nBytes; i++)
code->byteCode[i] = cg.byteCode[i];
code->nConstants = cg.nConsts;
code->constants = naAlloc(code->nConstants * sizeof(naRef));
code->srcFile = p->srcFile;
for(i=0; i<code->nConstants; i++)
code->constants[i] = getConstant(p, i);
return codeObj;
}

135
simgear/nasal/data.h Normal file
View File

@ -0,0 +1,135 @@
#ifndef _DATA_H
#define _DATA_H
#include "nasal.h"
// Notes: A CODE object is a compiled set of bytecode instructions.
// What actually gets executed at runtime is a bound FUNC object,
// which combines the raw code with a pointer to a CLOSURE chain of
// namespaces.
enum { T_STR, T_VEC, T_HASH, T_CODE, T_CLOSURE, T_FUNC, T_CCODE,
NUM_NASAL_TYPES }; // V. important that this come last!
#define IS_REF(r) ((r).ref.reftag == NASAL_REFTAG)
#define IS_NUM(r) ((r).ref.reftag != NASAL_REFTAG)
#define IS_OBJ(r) (IS_REF((r)) && (r).ref.ptr.obj != 0)
#define IS_NIL(r) (IS_REF((r)) && (r).ref.ptr.obj == 0)
#define IS_STR(r) (IS_OBJ((r)) && (r).ref.ptr.obj->type == T_STR)
#define IS_VEC(r) (IS_OBJ((r)) && (r).ref.ptr.obj->type == T_VEC)
#define IS_HASH(r) (IS_OBJ((r)) && (r).ref.ptr.obj->type == T_HASH)
#define IS_CODE(r) (IS_OBJ((r)) && (r).ref.ptr.obj->type == T_CODE)
#define IS_FUNC(r) (IS_OBJ((r)) && (r).ref.ptr.obj->type == T_FUNC)
#define IS_CLOSURE(r) (IS_OBJ((r)) && (r).ref.ptr.obj->type == T_CLOSURE)
#define IS_CCODE(r) (IS_OBJ((r)) && (r).ref.ptr.obj->type == T_CCODE)
#define IS_CONTAINER(r) (IS_VEC(r)||IS_HASH(r))
#define IS_SCALAR(r) (IS_NUM((r)) || IS_STR((r)))
// This is a macro instead of a separate struct to allow compilers to
// avoid padding. GCC on x86, at least, will always padd the size of
// an embedded struct up to 32 bits. Doing it this way allows the
// implementing objects to pack in 16 bits worth of data "for free".
#define GC_HEADER \
unsigned char mark; \
unsigned char type
struct naObj {
GC_HEADER;
};
struct naStr {
GC_HEADER;
int len;
unsigned char* data;
};
struct naVec {
GC_HEADER;
int size;
int alloced;
naRef* array;
};
struct HashNode {
naRef key;
naRef val;
struct HashNode* next;
};
struct naHash {
GC_HEADER;
int size;
int lgalloced;
struct HashNode* nodes;
struct HashNode** table;
int nextnode;
};
struct naCode {
GC_HEADER;
unsigned char* byteCode;
int nBytes;
naRef* constants;
int nConstants;
naRef srcFile;
};
struct naFunc {
GC_HEADER;
naRef code;
naRef closure;
};
struct naClosure {
GC_HEADER;
naRef namespace;
naRef next; // parent closure
};
struct naCCode {
GC_HEADER;
naCFunction fptr;
};
struct naPool {
int type;
int elemsz;
int nblocks;
struct Block* blocks;
int nfree; // number of entries in the free array
int freesz; // size of the free array
void** free; // pointers to usable elements
};
void naFree(void* m);
void* naAlloc(int n);
void naBZero(void* m, int n);
int naTypeSize(int type);
void naGarbageCollect();
naRef naObj(int type, struct naObj* o);
naRef naNew(naContext c, int type);
naRef naNewCode(naContext c);
naRef naNewClosure(naContext c, naRef namespace, naRef next);
int naStr_equal(naRef s1, naRef s2);
naRef naStr_fromnum(naRef dest, double num);
int naStr_numeric(naRef str);
int naStr_parsenum(char* str, int len, double* result);
int naStr_tonum(naRef str, double* out);
void naVec_init(naRef vec);
int naHash_tryset(naRef hash, naRef key, naRef val); // sets if exists
void naHash_init(naRef hash);
void naGC_init(struct naPool* p, int type);
struct naObj* naGC_get(struct naPool* p);
int naGC_size(struct naPool* p);
void naGC_mark(naRef r);
void naGC_reap(struct naPool* p);
void naStr_gcclean(struct naStr* s);
void naVec_gcclean(struct naVec* s);
void naHash_gcclean(struct naHash* s);
#endif // _DATA_H

211
simgear/nasal/debug.c Normal file
View File

@ -0,0 +1,211 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "nasal.h"
#include "parse.h"
#include "code.h"
// Bytecode operator to string
char* opStringDEBUG(int op)
{
static char buf[256];
switch(op) {
case OP_AND: return "AND";
case OP_OR: return "OR";
case OP_NOT: return "NOT";
case OP_MUL: return "MUL";
case OP_PLUS: return "PLUS";
case OP_MINUS: return "MINUS";
case OP_DIV: return "DIV";
case OP_NEG: return "NEG";
case OP_CAT: return "CAT";
case OP_LT: return "LT";
case OP_LTE: return "LTE";
case OP_GT: return "GT";
case OP_GTE: return "GTE";
case OP_EQ: return "EQ";
case OP_NEQ: return "NEQ";
case OP_EACH: return "EACH";
case OP_JMP: return "JMP";
case OP_JIFNOT: return "JIFNOT";
case OP_JIFNIL: return "JIFNIL";
case OP_FCALL: return "FCALL";
case OP_MCALL: return "MCALL";
case OP_RETURN: return "RETURN";
case OP_PUSHCONST: return "PUSHCONST";
case OP_PUSHONE: return "PUSHONE";
case OP_PUSHZERO: return "PUSHZERO";
case OP_PUSHNIL: return "PUSHNIL";
case OP_POP: return "POP";
case OP_DUP: return "DUP";
case OP_XCHG: return "XCHG";
case OP_INSERT: return "INSERT";
case OP_EXTRACT: return "EXTRACT";
case OP_MEMBER: return "MEMBER";
case OP_SETMEMBER: return "SETMEMBER";
case OP_LOCAL: return "LOCAL";
case OP_SETLOCAL: return "SETLOCAL";
case OP_NEWVEC: return "NEWVEC";
case OP_VAPPEND: return "VAPPEND";
case OP_NEWHASH: return "NEWHASH";
case OP_HAPPEND: return "HAPPEND";
case OP_LINE: return "LINE";
case OP_MARK: return "MARK";
case OP_UNMARK: return "UNMARK";
case OP_BREAK: return "BREAK";
}
sprintf(buf, "<bad opcode: %d>\n", op);
return buf;
}
// Print a bytecode operator
void printOpDEBUG(int ip, int op)
{
printf("IP: %d OP: %s\n", ip, opStringDEBUG(op));
}
// Print a naRef
void printRefDEBUG(naRef r)
{
int i;
if(IS_NUM(r)) {
printf("%f\n", r.num);
} else if(IS_NIL(r)) {
printf("<nil>\n");
} else if(IS_STR(r)) {
printf("\"");
for(i=0; i<r.ref.ptr.str->len; i++)
printf("%c", r.ref.ptr.str->data[i]);
printf("\"\n");
} else if(IS_VEC(r)) {
printf("<vec>\n");
} else if(IS_HASH(r)) {
printf("<hash>\n");
} else if(IS_FUNC(r)) {
printf("<func>\n");
} else if(IS_CLOSURE(r)) {
printf("DEBUG: closure object on stack!\n");
} else if(IS_CODE(r)) {
printf("DEBUG: code object on stack!\n");
} else printf("DEBUG ACK\n");
}
// Print the operand stack of the specified context
void printStackDEBUG(struct Context* ctx)
{
int i;
printf("\n");
for(i=ctx->opTop-1; i>=0; i--) {
printf("] ");
printRefDEBUG(ctx->opStack[i]);
}
printf("\n");
}
// Token type to string
char* tokString(int tok)
{
switch(tok) {
case TOK_TOP: return "TOK_TOP";
case TOK_AND: return "TOK_AND";
case TOK_OR: return "TOK_OR";
case TOK_NOT: return "TOK_NOT";
case TOK_LPAR: return "TOK_LPAR";
case TOK_RPAR: return "TOK_RPAR";
case TOK_LBRA: return "TOK_LBRA";
case TOK_RBRA: return "TOK_RBRA";
case TOK_LCURL: return "TOK_LCURL";
case TOK_RCURL: return "TOK_RCURL";
case TOK_MUL: return "TOK_MUL";
case TOK_PLUS: return "TOK_PLUS";
case TOK_MINUS: return "TOK_MINUS";
case TOK_NEG: return "TOK_NEG";
case TOK_DIV: return "TOK_DIV";
case TOK_CAT: return "TOK_CAT";
case TOK_COLON: return "TOK_COLON";
case TOK_DOT: return "TOK_DOT";
case TOK_COMMA: return "TOK_COMMA";
case TOK_SEMI: return "TOK_SEMI";
case TOK_ASSIGN: return "TOK_ASSIGN";
case TOK_LT: return "TOK_LT";
case TOK_LTE: return "TOK_LTE";
case TOK_EQ: return "TOK_EQ";
case TOK_NEQ: return "TOK_NEQ";
case TOK_GT: return "TOK_GT";
case TOK_GTE: return "TOK_GTE";
case TOK_IF: return "TOK_IF";
case TOK_ELSIF: return "TOK_ELSIF";
case TOK_ELSE: return "TOK_ELSE";
case TOK_FOR: return "TOK_FOR";
case TOK_FOREACH: return "TOK_FOREACH";
case TOK_WHILE: return "TOK_WHILE";
case TOK_RETURN: return "TOK_RETURN";
case TOK_BREAK: return "TOK_BREAK";
case TOK_CONTINUE: return "TOK_CONTINUE";
case TOK_FUNC: return "TOK_FUNC";
case TOK_SYMBOL: return "TOK_SYMBOL";
case TOK_LITERAL: return "TOK_LITERAL";
case TOK_EMPTY: return "TOK_EMPTY";
case TOK_NIL: return "TOK_NIL";
}
return 0;
}
// Diagnostic: check all list pointers for sanity
void ack()
{
printf("Bad token list!\n");
exit(1);
}
void checkList(struct Token* start, struct Token* end)
{
struct Token* t = start;
while(t) {
if(t->next && t->next->prev != t) ack();
if(t->next==0 && t != end) ack();
t = t->next;
}
t = end;
while(t) {
if(t->prev && t->prev->next != t) ack();
if(t->prev==0 && t != start) ack();
t = t->prev;
};
}
// Prints a single parser token to stdout
void printToken(struct Token* t, char* prefix)
{
int i;
printf("%sline %d %s ", prefix, t->line, tokString(t->type));
if(t->type == TOK_LITERAL || t->type == TOK_SYMBOL) {
if(t->str) {
printf("\"");
for(i=0; i<t->strlen; i++) printf("%c", t->str[i]);
printf("\" (len: %d)", t->strlen);
} else {
printf("%f ", t->num);
}
}
printf("\n");
}
// Prints a parse tree to stdout
void dumpTokenList(struct Token* t, int prefix)
{
char prefstr[128];
int i;
prefstr[0] = 0;
for(i=0; i<prefix; i++)
strcat(prefstr, ". ");
while(t) {
printToken(t, prefstr);
dumpTokenList(t->children, prefix+1);
t = t->next;
}
}

214
simgear/nasal/gc.c Normal file
View File

@ -0,0 +1,214 @@
#include "nasal.h"
#include "data.h"
#define MIN_BLOCK_SIZE 256
// "type" for an object freed by the collector
#define T_GCFREED 123 // DEBUG
struct Block {
int size;
char* block;
};
// Decremented every allocation. When it reaches zero, we do a
// garbage collection. The value is reset to 1/2 of the total object
// count each collection, which is sane: it ensures that no more than
// 50% growth can happen between collections, and ensures that garbage
// collection work is constant with allocation work (i.e. that O(N)
// work is done only every O(1/2N) allocations).
static int GlobalAllocCount = 256;
static void appendfree(struct naPool*p, struct naObj* o)
{
// Need more space?
if(p->freesz <= p->nfree) {
int i, n = 1+((3*p->nfree)>>1);
void** newf = naAlloc(n * sizeof(void*));
for(i=0; i<p->nfree; i++)
newf[i] = p->free[i];
naFree(p->free);
p->free = newf;
p->freesz = n;
}
p->free[p->nfree++] = o;
}
static void naCode_gcclean(struct naCode* o)
{
naFree(o->byteCode); o->byteCode = 0;
naFree(o->constants); o->constants = 0;
}
static void freeelem(struct naPool* p, struct naObj* o)
{
// Mark the object as "freed" for debugging purposes
o->type = T_GCFREED; // DEBUG
// Free any intrinsic (i.e. non-garbage collected) storage the
// object might have
switch(p->type) {
case T_STR:
naStr_gcclean((struct naStr*)o);
break;
case T_VEC:
naVec_gcclean((struct naVec*)o);
break;
case T_HASH:
naHash_gcclean((struct naHash*)o);
break;
case T_CODE:
naCode_gcclean((struct naCode*)o);
break;
}
// And add it to the free list
appendfree(p, o);
}
static void newBlock(struct naPool* p, int need)
{
int i;
char* buf;
struct Block* newblocks;
if(need < MIN_BLOCK_SIZE)
need = MIN_BLOCK_SIZE;
newblocks = naAlloc((p->nblocks+1) * sizeof(struct Block));
for(i=0; i<p->nblocks; i++) newblocks[i] = p->blocks[i];
naFree(p->blocks);
p->blocks = newblocks;
buf = naAlloc(need * p->elemsz);
naBZero(buf, need * p->elemsz);
p->blocks[p->nblocks].size = need;
p->blocks[p->nblocks].block = buf;
p->nblocks++;
for(i=0; i<need; i++) {
struct naObj* o = (struct naObj*)(buf + i*p->elemsz);
o->mark = 0;
o->type = p->type;
appendfree(p, o);
}
}
void naGC_init(struct naPool* p, int type)
{
p->type = type;
p->elemsz = naTypeSize(type);
p->nblocks = 0;
p->blocks = 0;
p->nfree = 0;
p->freesz = 0;
p->free = 0;
naGC_reap(p);
}
int naGC_size(struct naPool* p)
{
int i, total=0;
for(i=0; i<p->nblocks; i++)
total += ((struct Block*)(p->blocks + i))->size;
return total;
}
struct naObj* naGC_get(struct naPool* p)
{
// Collect every GlobalAllocCount allocations.
// This gets set to ~50% of the total object count each
// collection (it's incremented in naGC_reap()).
if(--GlobalAllocCount < 0) {
GlobalAllocCount = 0;
naGarbageCollect();
}
// If we're out, then allocate an extra 12.5%
if(p->nfree == 0)
newBlock(p, naGC_size(p)/8);
return p->free[--p->nfree];
}
// Sets the reference bit on the object, and recursively on all
// objects reachable from it. Clumsy: uses C stack recursion, which
// is slower than it need be and may cause problems on some platforms
// due to the very large stack depths that result.
void naGC_mark(naRef r)
{
int i;
if(IS_NUM(r) || IS_NIL(r))
return;
if(r.ref.ptr.obj->mark == 1)
return;
// Verify that the object hasn't been freed incorrectly:
if(r.ref.ptr.obj->type == T_GCFREED) *(int*)0=0; // DEBUG
r.ref.ptr.obj->mark = 1;
switch(r.ref.ptr.obj->type) {
case T_VEC:
for(i=0; i<r.ref.ptr.vec->size; i++)
naGC_mark(r.ref.ptr.vec->array[i]);
break;
case T_HASH:
if(r.ref.ptr.hash->table == 0)
break;
for(i=0; i < (1<<r.ref.ptr.hash->lgalloced); i++) {
struct HashNode* hn = r.ref.ptr.hash->table[i];
while(hn) {
naGC_mark(hn->key);
naGC_mark(hn->val);
hn = hn->next;
}
}
break;
case T_CODE:
naGC_mark(r.ref.ptr.code->srcFile);
for(i=0; i<r.ref.ptr.code->nConstants; i++)
naGC_mark(r.ref.ptr.code->constants[i]);
break;
case T_CLOSURE:
naGC_mark(r.ref.ptr.closure->namespace);
naGC_mark(r.ref.ptr.closure->next);
break;
case T_FUNC:
naGC_mark(r.ref.ptr.func->code);
naGC_mark(r.ref.ptr.func->closure);
break;
}
}
// Collects all the unreachable objects into a free list, and
// allocates more space if needed.
void naGC_reap(struct naPool* p)
{
int i, elem, total = 0;
p->nfree = 0;
for(i=0; i<p->nblocks; i++) {
struct Block* b = p->blocks + i;
total += b->size;
for(elem=0; elem < b->size; elem++) {
struct naObj* o = (struct naObj*)(b->block + elem * p->elemsz);
if(o->mark == 0)
freeelem(p, o);
o->mark = 0;
}
}
// Add 50% of our total to the global count
GlobalAllocCount += total/2;
// Allocate more if necessary (try to keep 25-50% of the objects
// available)
if(p->nfree < total/4) {
int used = total - p->nfree;
int avail = total - used;
int need = used/2 - avail;
if(need > 0)
newBlock(p, need);
}
}

223
simgear/nasal/hash.c Normal file
View File

@ -0,0 +1,223 @@
#include "nasal.h"
#include "data.h"
static void realloc(naRef hash)
{
struct naHash* h = hash.ref.ptr.hash;
int i, sz, oldsz = h->size;
int oldcols = h->table ? 1 << h->lgalloced : 0;
// Keep a handle to our original objects
struct HashNode* oldnodes = h->nodes;
struct HashNode** oldtable = h->table;
// Figure out how big we need to be (start with a minimum size of
// 16 entries)
for(i=3; 1<<i < oldsz; i++);
h->lgalloced = i+1;
// Allocate new ones (note that all the records are allocated in a
// single chunk, to avoid zillions of tiny node allocations)
sz = 1<<h->lgalloced;
h->nodes = naAlloc(sz * (sizeof(struct HashNode) + sizeof(void*)));
h->table = (struct HashNode**)(((char*)h->nodes) + sz*sizeof(struct HashNode));
naBZero(h->table, sz * sizeof(void*));
h->nextnode = 0;
h->size = 0;
// Re-insert everything from scratch
for(i=0; i<oldcols; i++) {
struct HashNode* hn = oldtable[i];
while(hn) {
naHash_set(hash, hn->key, hn->val);
hn = hn->next;
}
}
// Free the old memory
naFree(oldnodes);
}
// Computes a hash code for a given scalar
static unsigned int hashcode(naRef r)
{
if(IS_NUM(r))
{
// Numbers get the number as a hash. Just use the bits and
// xor them together. Note assumption that sizeof(double) >=
// 2*sizeof(int).
unsigned int* p = (unsigned int*)&(r.num);
return p[0] ^ p[1];
} else {
// This is Daniel Bernstein's djb2 hash function that I found
// on the web somewhere. It appears to work pretty well.
unsigned int i, hash = 5831;
for(i=0; i<r.ref.ptr.str->len; i++)
hash = (hash * 33) ^ r.ref.ptr.str->data[i];
return hash;
}
}
// Which column in a given hash does the key correspond to.
static unsigned int hashcolumn(struct naHash* h, naRef key)
{
// Multiply by a big number, and take the top N bits. Note
// assumption that sizeof(unsigned int) == 4.
return (2654435769u * hashcode(key)) >> (32 - h->lgalloced);
}
struct HashNode* find(struct naHash* h, naRef key)
{
struct HashNode* hn;
if(h->table == 0)
return 0;
hn = h->table[hashcolumn(h, key)];
while(hn) {
if(naEqual(key, hn->key))
return hn;
hn = hn->next;
}
return 0;
}
void naHash_init(naRef hash)
{
struct naHash* h = hash.ref.ptr.hash;
h->size = 0;
h->lgalloced = 0;
h->table = 0;
h->nodes = 0;
}
// Make a temporary string on the stack
static naRef tmpStr(struct naStr* str, char* key)
{
char* p = key;
while(*p) { p++; }
str->len = p - key;
str->data = key;
return naObj(T_STR, (struct naObj*)str);
}
naRef naHash_cget(naRef hash, char* key)
{
struct naStr str;
naRef result, key2 = tmpStr(&str, key);
if(naHash_get(hash, key2, &result))
return result;
return naNil();
}
void naHash_cset(naRef hash, char* key, naRef val)
{
struct naStr str;
naRef key2 = tmpStr(&str, key);
naHash_tryset(hash, key2, val);
}
int naHash_get(naRef hash, naRef key, naRef* out)
{
struct naHash* h = hash.ref.ptr.hash;
struct HashNode* n;
if(!IS_HASH(hash)) return 0;
n = find(h, key);
if(n) {
*out = n->val;
return 1;
} else {
*out = naNil();
return 0;
}
}
// Simpler version. Don't create a new node if the value isn't there
int naHash_tryset(naRef hash, naRef key, naRef val)
{
struct HashNode* n;
if(!IS_HASH(hash)) return 0;
n = find(hash.ref.ptr.hash, key);
if(n) n->val = val;
return n != 0;
}
void naHash_set(naRef hash, naRef key, naRef val)
{
struct naHash* h = hash.ref.ptr.hash;
unsigned int col;
struct HashNode* n;
if(!IS_HASH(hash)) return;
n = find(h, key);
if(n) {
n->val = val;
return;
}
if(h->size+1 >= 1<<h->lgalloced)
realloc(hash);
col = hashcolumn(h, key);
n = h->nodes + h->nextnode++;
n->key = key;
n->val = val;
n->next = h->table[col];
h->table[col] = n;
h->size++;
}
// FIXME: this implementation does a realloc() after each delete, and
// is therefore needlessly O(N). (The reason is that this avoids the
// need to keep a free list around for the much more common case of
// adding a new value. Modifying an existing value is O(1), of
// course.)
void naHash_delete(naRef hash, naRef key)
{
struct naHash* h = hash.ref.ptr.hash;
int col;
struct HashNode *last=0, *hn;
if(!IS_HASH(hash)) return;
col = hashcolumn(h, key);
hn = h->table[col];
while(hn) {
if(naEqual(hn->key, key)) {
if(last == 0) h->table[col] = hn->next;
else last->next = hn->next;
h->size--;
realloc(hash);
return;
}
last = hn;
hn = hn->next;
}
}
void naHash_keys(naRef dst, naRef hash)
{
struct naHash* h = hash.ref.ptr.hash;
int i;
if(!IS_HASH(hash)) return;
for(i=0; i<(1<<h->lgalloced); i++) {
struct HashNode* hn = h->table[i];
while(hn) {
naVec_append(dst, hn->key);
hn = hn->next;
}
}
}
int naHash_size(naRef h)
{
if(!IS_HASH(h)) return 0;
return h.ref.ptr.hash->size;
}
void naHash_gcclean(struct naHash* h)
{
naFree(h->nodes);
h->nodes = 0;
h->size = 0;
h->lgalloced = 0;
h->table = 0;
h->nextnode = 0;
}

332
simgear/nasal/lex.c Normal file
View File

@ -0,0 +1,332 @@
#include "parse.h"
// Static table of recognized lexemes in the language
struct Lexeme {
char* str;
int tok;
} LEXEMES[] = {
{"and", TOK_AND},
{"or", TOK_OR},
{"!", TOK_NOT},
{"(", TOK_LPAR},
{")", TOK_RPAR},
{"[", TOK_LBRA},
{"]", TOK_RBRA},
{"{", TOK_LCURL},
{"}", TOK_RCURL},
{"*", TOK_MUL},
{"+", TOK_PLUS},
{"-", TOK_MINUS},
{"/", TOK_DIV},
{"~", TOK_CAT},
{":", TOK_COLON},
{".", TOK_DOT},
{",", TOK_COMMA},
{";", TOK_SEMI},
{"=", TOK_ASSIGN},
{"<", TOK_LT},
{"<=", TOK_LTE},
{"==", TOK_EQ},
{"!=", TOK_NEQ},
{">", TOK_GT},
{">=", TOK_GTE},
{"nil", TOK_NIL},
{"if", TOK_IF},
{"elsif", TOK_ELSIF},
{"else", TOK_ELSE},
{"for", TOK_FOR},
{"foreach", TOK_FOREACH},
{"while", TOK_WHILE},
{"return", TOK_RETURN},
{"break", TOK_BREAK},
{"continue", TOK_CONTINUE},
{"func", TOK_FUNC}
};
// Build a table of where each line ending is
static int* findLines(struct Parser* p)
{
char* buf = p->buf;
int sz = p->len/10 + 16;
int* lines = naParseAlloc(p, (sizeof(int) * sz));
int i, j, n=0;
for(i=0; i<p->len; i++) {
// Not a line ending at all
if(buf[i] != '\n' && buf[i] != '\r')
continue;
// Skip over the \r of a \r\n pair.
if(buf[i] == '\r' && (i+1)<p->len && buf[i+1] == '\n') {
i++;
continue;
}
// Reallocate if necessary
if(n == sz) {
int* nl;
sz *= 2;
nl = naParseAlloc(p, sizeof(int) * sz);
for(j=0; j<n; j++) nl[j] = lines[j];
lines = nl;
}
lines[n++] = i;
}
p->lines = lines;
p->nLines = n;
return lines;
}
// What line number is the index on?
static int getLine(struct Parser* p, int index)
{
int i;
for(i=0; i<p->nLines; i++)
if(p->lines[i] > index)
return (p->firstLine-1) + i+1;
return (p->firstLine-1) + p->nLines+1;
}
static void error(struct Parser* p, char* msg, int index)
{
naParseError(p, msg, getLine(p, index));
}
// End index (the newline character) of the given line
static int lineEnd(struct Parser* p, int line)
{
if(line > p->nLines) return p->len;
return p->lines[line-1];
}
static void newToken(struct Parser* p, int pos, int type,
char* str, int slen, double num)
{
struct Token* tok;
tok = naParseAlloc(p, sizeof(struct Token));
tok->type = type;
tok->line = getLine(p, pos);
tok->str = str;
tok->strlen = slen;
tok->num = num;
tok->parent = &p->tree;
tok->next = 0;
tok->prev = p->tree.lastChild;
tok->children = 0;
tok->lastChild = 0;
// Context sensitivity hack: a "-" following a binary operator of
// higher precedence (MUL and DIV, basically) must be a unary
// negation. Needed to get precedence right in the parser for
// expressiong like "a * -2"
if(type == TOK_MINUS && tok->prev)
if(tok->prev->type == TOK_MUL || tok->prev->type == TOK_DIV)
tok->type = type = TOK_NEG;
if(!p->tree.children) p->tree.children = tok;
if(p->tree.lastChild) p->tree.lastChild->next = tok;
p->tree.lastChild = tok;
}
// Parse a hex nibble
static int hexc(char c, struct Parser* p, int index)
{
if(c >= '0' && c <= '9') return c - '0';
if(c >= 'A' && c <= 'F') return c - 'a' + 10;
if(c >= 'a' && c <= 'f') return c - 'a' + 10;
error(p, "bad hex constant", index);
return 0;
}
// Escape and returns a single backslashed expression in a single
// quoted string. Trivial, just escape \' and leave everything else
// alone.
static void sqEscape(char* buf, int len, int index, struct Parser* p,
char* cOut, int* eatenOut)
{
if(len < 2) error(p, "unterminated string", index);
if(buf[1] == '\'') {
*cOut = '\'';
*eatenOut = 2;
} else {
*cOut = '\\';
*eatenOut = 1;
}
}
// Ditto, but more complicated for double quotes.
static void dqEscape(char* buf, int len, int index, struct Parser* p,
char* cOut, int* eatenOut)
{
if(len < 2) error(p, "unterminated string", index);
*eatenOut = 2;
switch(buf[1]) {
case '"': *cOut = '"'; break;
case 'r': *cOut = '\r'; break;
case 'n': *cOut = '\n'; break;
case 't': *cOut = '\t'; break;
case '\\': *cOut = '\\'; break;
case 'x':
if(len < 4) error(p, "unterminated string", index);
*cOut = (char)((hexc(buf[2], p, index)<<4) | hexc(buf[3], p, index));
*eatenOut = 4;
default:
// Unhandled, put the backslash back
*cOut = '\\';
*eatenOut = 1;
}
}
// Read in a string literal
static int lexStringLiteral(struct Parser* p, int index, int singleQuote)
{
int i, j, len, iteration;
char* out = 0;
char* buf = p->buf;
char endMark = singleQuote ? '\'' : '"';
for(iteration = 0; iteration<2; iteration++) {
i = index+1;
j = len = 0;
while(i < p->len) {
char c = buf[i];
int eaten = 1;
if(c == endMark)
break;
if(c == '\\') {
if(singleQuote) sqEscape(buf+i, p->len-i, i, p, &c, &eaten);
else dqEscape(buf+i, p->len-i, i, p, &c, &eaten);
}
if(iteration == 1) out[j++] = c;
i += eaten;
len++;
}
// Finished stage one -- allocate the buffer for stage two
if(iteration == 0) out = naParseAlloc(p, len);
}
newToken(p, index, TOK_LITERAL, out, len, 0);
return i+1;
}
static int lexNumLiteral(struct Parser* p, int index)
{
int len = p->len, i = index;
unsigned char* buf = p->buf;
double d;
while(i<len && buf[i] >= '0' && buf[i] <= '9') i++;
if(i<len && buf[i] == '.') {
i++;
while(i<len && buf[i] >= '0' && buf[i] <= '9') i++;
}
if(i<len && (buf[i] == 'e' || buf[i] == 'E')) {
i++;
if(i<len
&& (buf[i] == '-' || buf[i] == '+')
&& (i+1<len && buf[i+1] >= '0' && buf[i+1] <= '9')) i++;
while(i<len && buf[i] >= '0' && buf[i] <= '9') i++;
}
naStr_parsenum(p->buf + index, i - index, &d);
newToken(p, index, TOK_LITERAL, 0, 0, d);
return i;
}
static int trySymbol(struct Parser* p, int start)
{
int i = start;
while((i < p->len) &&
((p->buf[i] >= 'A' && p->buf[i] <= 'Z') ||
(p->buf[i] >= 'a' && p->buf[i] <= 'z') ||
(p->buf[i] >= '0' && p->buf[i] <= '9')))
{ i++; }
return i-start;
}
// Returns the length of lexeme l if the buffer prefix matches, or
// else zero.
static int matchLexeme(char* buf, int len, char* l)
{
int i;
for(i=0; i<len; i++) {
if(l[i] == 0) return i;
if(l[i] != buf[i]) return 0;
}
// Ran out of buffer. This is still OK if we're also at the end
// of the lexeme.
if(l[i] == 0) return i;
return 0;
}
// This is dumb and algorithmically slow. It would be much more
// elegant to sort and binary search the lexeme list, but that's a lot
// more code and this really isn't very slow in practice; it checks
// every byte of every lexeme for each input byte. There are less
// than 100 bytes of lexemes in the grammar. Returns the number of
// bytes in the lexeme read (or zero if none was recognized)
static int tryLexemes(struct Parser* p, int index, int* lexemeOut)
{
int i, n, best, bestIndex=-1;
char* start = p->buf + index;
int len = p->len - index;
n = sizeof(LEXEMES) / sizeof(struct Lexeme);
best = 0;
for(i=0; i<n; i++) {
int l = matchLexeme(start, len, LEXEMES[i].str);
if(l > best) {
best = l;
bestIndex = i;
}
}
if(best > 0) *lexemeOut = bestIndex;
return best;
}
void naLex(struct Parser* p)
{
int i = 0;
findLines(p);
while(i<p->len) {
char c = p->buf[i];
// Whitespace, comments and string literals have obvious
// markers and can be handled by a switch:
int handled = 1;
switch(c) {
case ' ': case '\t': case '\n': case '\r': case '\f': case '\v':
i++;
break;
case '#':
i = lineEnd(p, getLine(p, i));
break;
case '\'': case '"':
i = lexStringLiteral(p, i, (c=='"' ? 0 : 1));
break;
default:
if(c >= '0' && c <= '9') i = lexNumLiteral(p, i);
else handled = 0;
}
// Lexemes and symbols are a little more complicated. Pick
// the longest one that matches. Since some lexemes look like
// symbols (e.g. "or") they need a higher precedence, but we
// don't want a lexeme match to clobber the beginning of a
// symbol (e.g. "orchid"). If neither match, we have a bad
// character in the mix.
if(!handled) {
int symlen=0, lexlen=0, lexeme;
lexlen = tryLexemes(p, i, &lexeme);
if((c>='A' && c<='Z') || (c>='a' && c<='z'))
symlen = trySymbol(p, i);
if(lexlen && lexlen >= symlen) {
newToken(p, i, LEXEMES[lexeme].tok, 0, 0, 0);
i += lexlen;
} else if(symlen) {
newToken(p, i, TOK_SYMBOL, p->buf+i, symlen, 0);
i += symlen;
} else {
error(p, "illegal character", i);
}
}
}
}

149
simgear/nasal/lib.c Normal file
View File

@ -0,0 +1,149 @@
#include "nasal.h"
// No need to include <string.h> just for this:
static int strlen(char* s)
{
char* s0 = s;
while(*s) s++;
return s - s0;
}
static naRef size(naContext c, naRef args)
{
naRef r;
if(naVec_size(args) == 0) return naNil();
r = naVec_get(args, 0);
if(naIsString(r)) return naNum(naStr_len(r));
if(naIsVector(r)) return naNum(naVec_size(r));
if(naIsHash(r)) return naNum(naHash_size(r));
return naNil();
}
static naRef keys(naContext c, naRef args)
{
naRef v, h = naVec_get(args, 0);
if(!naIsHash(h)) return naNil();
v = naNewVector(c);
naHash_keys(v, h);
return v;
}
static naRef append(naContext c, naRef args)
{
naRef v = naVec_get(args, 0);
naRef e = naVec_get(args, 1);
if(!naIsVector(v)) return naNil();
naVec_append(v, e);
return v;
}
static naRef pop(naContext c, naRef args)
{
naRef v = naVec_get(args, 0);
if(!naIsVector(v)) return naNil();
return naVec_removelast(v);
}
static naRef delete(naContext c, naRef args)
{
naRef h = naVec_get(args, 0);
naRef k = naVec_get(args, 1);
if(naIsHash(h)) naHash_delete(h, k);
return naNil();
}
static naRef intf(naContext c, naRef args)
{
naRef n = naNumValue(naVec_get(args, 0));
if(!naIsNil(n)) n.num = (int)n.num;
return n;
}
static naRef num(naContext c, naRef args)
{
return naNumValue(naVec_get(args, 0));
}
static naRef streq(naContext c, naRef args)
{
int i;
naRef a = naVec_get(args, 0);
naRef b = naVec_get(args, 1);
if(!naIsString(a) || !naIsString(b)) return naNil();
if(naStr_len(a) != naStr_len(b)) return naNum(0);
for(i=0; i<naStr_len(a); i++)
if(naStr_data(a)[i] != naStr_data(b)[i])
return naNum(0);
return naNum(1);
}
static naRef substr(naContext c, naRef args)
{
naRef src = naVec_get(args, 0);
naRef startR = naVec_get(args, 1);
naRef lenR = naVec_get(args, 2);
int start, len;
if(!naIsString(src)) return naNil();
startR = naNumValue(startR);
if(naIsNil(startR)) return naNil();
start = (int)startR.num;
if(naIsNil(lenR)) {
len = naStr_len(src) - start;
} else {
lenR = naNumValue(lenR);
if(naIsNil(lenR)) return naNil();
len = (int)lenR.num;
}
return naStr_substr(naNewString(c), src, start, len);
}
static naRef contains(naContext c, naRef args)
{
naRef hash = naVec_get(args, 0);
naRef key = naVec_get(args, 1);
if(naIsNil(hash) || naIsNil(key)) return naNil();
if(!naIsHash(hash)) return naNil();
return naHash_get(hash, key, &key) ? naNum(1) : naNum(0);
}
static naRef typeOf(naContext c, naRef args)
{
naRef r = naVec_get(args, 0);
char* t = "unknown";
if(naIsNil(r)) t = "nil";
else if(naIsNum(r)) t = "scalar";
else if(naIsString(r)) t = "scalar";
else if(naIsVector(r)) t = "vector";
else if(naIsHash(r)) t = "hash";
else if(naIsFunc(r)) t = "func";
r = naStr_fromdata(naNewString(c), t, strlen(t));
return r;
}
struct func { char* name; naCFunction func; };
static struct func funcs[] = {
{ "size", size },
{ "keys", keys },
{ "append", append },
{ "pop", pop },
{ "delete", delete },
{ "int", intf },
{ "num", num },
{ "streq", streq },
{ "substr", substr },
{ "contains", contains },
{ "typeof", typeOf },
};
naRef naStdLib(naContext c)
{
naRef namespace = naNewHash(c);
int i, n = sizeof(funcs)/sizeof(struct func);
for(i=0; i<n; i++) {
naRef code = naNewCCode(c, funcs[i].func);
naRef name = naStr_fromdata(naNewString(c),
funcs[i].name, strlen(funcs[i].name));
naHash_set(namespace, name, naNewFunc(c, code));
}
return namespace;
}

89
simgear/nasal/mathlib.c Normal file
View File

@ -0,0 +1,89 @@
#include <math.h>
#include <string.h>
#include "nasal.h"
static naRef f_sin(naContext c, naRef args)
{
naRef a = naNumValue(naVec_get(args, 0));
if(naIsNil(a))
naRuntimeError(c, "non numeric argument to sin()");
a.num = sin(a.num);
return a;
}
static naRef f_cos(naContext c, naRef args)
{
naRef a = naNumValue(naVec_get(args, 0));
if(naIsNil(a))
naRuntimeError(c, "non numeric argument to cos()");
a.num = cos(a.num);
return a;
}
static naRef f_exp(naContext c, naRef args)
{
naRef a = naNumValue(naVec_get(args, 0));
if(naIsNil(a))
naRuntimeError(c, "non numeric argument to exp()");
a.num = exp(a.num);
return a;
}
static naRef f_ln(naContext c, naRef args)
{
naRef a = naNumValue(naVec_get(args, 0));
if(naIsNil(a))
naRuntimeError(c, "non numeric argument to ln()");
a.num = log(a.num);
return a;
}
static naRef f_sqrt(naContext c, naRef args)
{
naRef a = naNumValue(naVec_get(args, 0));
if(naIsNil(a))
naRuntimeError(c, "non numeric argument to sqrt()");
a.num = sqrt(a.num);
return a;
}
static naRef f_atan2(naContext c, naRef args)
{
naRef a = naNumValue(naVec_get(args, 0));
naRef b = naNumValue(naVec_get(args, 1));
if(naIsNil(a) || naIsNil(b))
naRuntimeError(c, "non numeric argument to atan2()");
a.num = atan2(a.num, b.num);
return a;
}
static struct func { char* name; naCFunction func; } funcs[] = {
{ "sin", f_sin },
{ "cos", f_cos },
{ "exp", f_exp },
{ "ln", f_ln },
{ "sqrt", f_sqrt },
{ "atan2", f_atan2 },
};
naRef naMathLib(naContext c)
{
naRef name, namespace = naNewHash(c);
int i, n = sizeof(funcs)/sizeof(struct func);
for(i=0; i<n; i++) {
naRef code = naNewCCode(c, funcs[i].func);
naRef name = naStr_fromdata(naNewString(c),
funcs[i].name, strlen(funcs[i].name));
naHash_set(namespace, name, naNewFunc(c, code));
}
// Set up constants for math.pi and math.e
name = naStr_fromdata(naNewString(c), "pi", 2);
naHash_set(namespace, name, naNum(M_PI));
name = naStr_fromdata(naNewString(c), "e", 1);
naHash_set(namespace, name, naNum(M_E));
return namespace;
}

202
simgear/nasal/misc.c Normal file
View File

@ -0,0 +1,202 @@
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "nasal.h"
#include "code.h"
void naFree(void* m) { free(m); }
void* naAlloc(int n) { return malloc(n); }
void naBZero(void* m, int n) { memset(m, 0, n); }
naRef naObj(int type, struct naObj* o)
{
naRef r;
r.ref.reftag = NASAL_REFTAG;
r.ref.ptr.obj = o;
o->type = type;
return r;
}
int naTrue(naRef r)
{
if(IS_NIL(r)) return 0;
if(IS_NUM(r)) return r.num != 0;
if(IS_STR(r)) return 1;
return 0;
}
naRef naNumValue(naRef n)
{
double d;
if(IS_NUM(n)) return n;
if(IS_NIL(n)) return naNil();
if(IS_STR(n) && naStr_tonum(n, &d))
return naNum(d);
return naNil();
}
naRef naStringValue(naContext c, naRef r)
{
if(IS_NIL(r) || IS_STR(r)) return r;
if(IS_NUM(r)) {
naRef s = naNewString(c);
naStr_fromnum(s, r.num);
return s;
}
return naNil();
}
naRef naNew(struct Context* c, int type)
{
naRef result = naObj(type, naGC_get(&(c->pools[type])));
naVec_append(c->temps, result);
return result;
}
naRef naNewString(struct Context* c)
{
naRef s = naNew(c, T_STR);
s.ref.ptr.str->len = 0;
s.ref.ptr.str->data = 0;
return s;
}
naRef naNewVector(struct Context* c)
{
naRef r = naNew(c, T_VEC);
naVec_init(r);
return r;
}
naRef naNewHash(struct Context* c)
{
naRef r = naNew(c, T_HASH);
naHash_init(r);
return r;
}
naRef naNewCode(struct Context* c)
{
return naNew(c, T_CODE);
}
naRef naNewCCode(struct Context* c, naCFunction fptr)
{
naRef r = naNew(c, T_CCODE);
r.ref.ptr.ccode->fptr = fptr;
return r;
}
naRef naNewFunc(struct Context* c, naRef code)
{
naRef func = naNew(c, T_FUNC);
func.ref.ptr.func->code = code;
func.ref.ptr.func->closure = naNil();
return func;
}
naRef naNewClosure(struct Context* c, naRef namespace, naRef next)
{
naRef closure = naNew(c, T_CLOSURE);
closure.ref.ptr.closure->namespace = namespace;
closure.ref.ptr.closure->next = next;
return closure;
}
naRef naNil()
{
naRef r;
r.ref.reftag = NASAL_REFTAG;
r.ref.ptr.obj = 0;
return r;
}
naRef naNum(double num)
{
naRef r;
r.num = num;
return r;
}
int naEqual(naRef a, naRef b)
{
double na=0, nb=0;
if(IS_REF(a) && IS_REF(b) && a.ref.ptr.obj == b.ref.ptr.obj)
return 1; // Object identity (and nil == nil)
if(IS_NIL(a) || IS_NIL(b))
return 0;
if(IS_NUM(a) && IS_NUM(b) && a.num == b.num)
return 1; // Numeric equality
if(IS_STR(a) && IS_STR(b) && naStr_equal(a, b))
return 1; // String equality
// Numeric equality after conversion
if(IS_NUM(a)) { na = a.num; }
else if(!(IS_STR(a) && naStr_tonum(a, &na))) { return 0; }
if(IS_NUM(b)) { nb = b.num; }
else if(!(IS_STR(b) && naStr_tonum(b, &nb))) { return 0; }
return na == nb ? 1 : 0;
}
int naTypeSize(int type)
{
switch(type) {
case T_STR: return sizeof(struct naStr);
case T_VEC: return sizeof(struct naVec);
case T_HASH: return sizeof(struct naHash);
case T_CODE: return sizeof(struct naCode);
case T_FUNC: return sizeof(struct naFunc);
case T_CLOSURE: return sizeof(struct naClosure);
case T_CCODE: return sizeof(struct naCCode);
};
return 0x7fffffff; // Make sure the answer is nonsense :)
}
int naIsNil(naRef r)
{
return IS_NIL(r);
}
int naIsNum(naRef r)
{
return IS_NUM(r);
}
int naIsString(naRef r)
{
return (!IS_NIL(r))&&IS_STR(r);
}
int naIsScalar(naRef r)
{
return IS_SCALAR(r);
}
int naIsVector(naRef r)
{
return (!IS_NIL(r))&&IS_VEC(r);
}
int naIsHash(naRef r)
{
return (!IS_NIL(r))&&IS_HASH(r);
}
int naIsFunc(naRef r)
{
return (!IS_NIL(r))&&IS_FUNC(r);
}
int naIsCode(naRef r)
{
return IS_CODE(r);
}
int naIsCCode(naRef r)
{
return IS_CCODE(r);
}

145
simgear/nasal/nasal.h Normal file
View File

@ -0,0 +1,145 @@
#ifndef _NASAL_H
#define _NASAL_H
#ifdef __cplusplus
extern "C" {
#endif
// This is a nasal "reference". They are always copied by value, and
// contain either a pointer to a garbage-collectable nasal object
// (string, vector, hash) or a floating point number. Keeping the
// number here is an optimization to prevent the generation of
// zillions of tiny "number" object that have to be collected. Note
// sneaky hack: on little endian systems, placing reftag after ptr and
// putting 1's in the top 13 (except the sign bit) bits makes the
// double value a NaN, and thus unmistakable (no actual number can
// appear as a reference, and vice versa). Swap the structure order
// on 32 bit big-endian systems. On 64 bit sytems of either
// endianness, reftag and the double won't be coincident anyway.
#define NASAL_REFTAG 0x7ff56789 // == 2,146,789,257 decimal
typedef union {
double num;
struct {
#ifdef NASAL_BIG_ENDIAN_32_BIT
int reftag; // Big-endian systems need this here!
#endif
union {
struct naObj* obj;
struct naStr* str;
struct naVec* vec;
struct naHash* hash;
struct naCode* code;
struct naFunc* func;
struct naClosure* closure;
struct naCCode* ccode;
} ptr;
#ifndef NASAL_BIG_ENDIAN_32_BIT
int reftag; // Little-endian and 64 bit systems need this here!
#endif
} ref;
} naRef;
typedef struct Context* naContext;
// The function signature for an extension function:
typedef naRef (*naCFunction)(naContext ctx, naRef args);
// All Nasal code runs under the watch of a naContext:
naContext naNewContext();
// Save this object in the context, preventing it (and objects
// referenced by it) from being garbage collected.
void naSave(naContext ctx, naRef obj);
// Parse a buffer in memory into a code object.
naRef naParseCode(naContext c, naRef srcFile, int firstLine,
char* buf, int len, int* errLine);
// Binds a bare code object (as returned from naParseCode) with a
// closure object (a hash) to act as the outer scope / namespace.
// FIXME: this API is weak. It should expose the recursive nature of
// closures, and allow for extracting the closure and namespace
// information from function objects.
naRef naBindFunction(naContext ctx, naRef code, naRef closure);
// Call a code or function object with the specifed arguments "on" the
// specified object and using the specified hash for the local
// variables. Any of args, obj or locals may be nil.
naRef naCall(naContext ctx, naRef func, naRef args, naRef obj, naRef locals);
// Throw an error from the current call stack. This function makes a
// longjmp call to a handler in naCall() and DOES NOT RETURN. It is
// intended for use in library code that cannot otherwise report an
// error via the return value, and MUST be used carefully. If in
// doubt, return naNil() as your error condition.
void naRuntimeError(naContext ctx, char* msg);
// Call a method on an object (NOTE: func is a function binding, *not*
// a code object as returned from naParseCode).
naRef naMethod(naContext ctx, naRef func, naRef object);
// Returns a hash containing functions from the Nasal standard library
// Useful for passing as a namespace to an initial function call
naRef naStdLib(naContext c);
// Ditto, with math functions
naRef naMathLib(naContext c);
// Current line number & error message
int naStackDepth(naContext ctx);
int naGetLine(naContext ctx, int frame);
naRef naGetSourceFile(naContext ctx, int frame);
char* naGetError(naContext ctx);
// Type predicates
int naIsNil(naRef r);
int naIsNum(naRef r);
int naIsString(naRef r);
int naIsScalar(naRef r);
int naIsVector(naRef r);
int naIsHash(naRef r);
int naIsCode(naRef r);
int naIsFunc(naRef r);
int naIsCCode(naRef r);
// Allocators/generators:
naRef naNil();
naRef naNum(double num);
naRef naNewString(naContext c);
naRef naNewVector(naContext c);
naRef naNewHash(naContext c);
naRef naNewFunc(naContext c, naRef code);
naRef naNewCCode(naContext c, naCFunction fptr);
// Some useful conversion/comparison routines
int naEqual(naRef a, naRef b);
int naTrue(naRef b);
naRef naNumValue(naRef n);
naRef naStringValue(naContext c, naRef n);
// String utilities:
int naStr_len(naRef s);
char* naStr_data(naRef s);
naRef naStr_fromdata(naRef dst, char* data, int len);
naRef naStr_concat(naRef dest, naRef s1, naRef s2);
naRef naStr_substr(naRef dest, naRef str, int start, int len);
// Vector utilities:
int naVec_size(naRef v);
naRef naVec_get(naRef v, int i);
void naVec_set(naRef vec, int i, naRef o);
int naVec_append(naRef vec, naRef o);
naRef naVec_removelast(naRef vec);
// Hash utilities:
int naHash_size(naRef h);
int naHash_get(naRef hash, naRef key, naRef* out);
naRef naHash_cget(naRef hash, char* key);
void naHash_set(naRef hash, naRef key, naRef val);
void naHash_cset(naRef hash, char* key, naRef val);
void naHash_delete(naRef hash, naRef key);
void naHash_keys(naRef dst, naRef hash);
#ifdef __cplusplus
} // extern "C"
#endif
#endif // _NASAL_H

527
simgear/nasal/parse.c Normal file
View File

@ -0,0 +1,527 @@
#include <setjmp.h>
#include "parse.h"
// Static precedence table, from low (loose binding, do first) to high
// (tight binding, do last).
enum { PREC_BINARY, PREC_REVERSE, PREC_PREFIX, PREC_SUFFIX };
#define MAX_PREC_TOKS 5
struct precedence {
int toks[MAX_PREC_TOKS];
int rule;
} PRECEDENCE[] = {
{ { TOK_SEMI, TOK_COMMA }, PREC_REVERSE },
{ { TOK_COLON }, PREC_BINARY },
{ { TOK_RETURN, TOK_BREAK, TOK_CONTINUE }, PREC_PREFIX },
{ { TOK_ASSIGN }, PREC_REVERSE },
{ { TOK_OR }, PREC_BINARY },
{ { TOK_AND }, PREC_BINARY },
{ { TOK_EQ, TOK_NEQ }, PREC_BINARY },
{ { TOK_LT, TOK_LTE, TOK_GT, TOK_GTE }, PREC_BINARY },
{ { TOK_PLUS, TOK_MINUS, TOK_CAT }, PREC_REVERSE },
{ { TOK_MUL, TOK_DIV }, PREC_BINARY },
{ { TOK_MINUS, TOK_NEG, TOK_NOT }, PREC_PREFIX },
{ { TOK_LPAR, TOK_LBRA }, PREC_SUFFIX },
{ { TOK_DOT }, PREC_BINARY },
};
#define PRECEDENCE_LEVELS (sizeof(PRECEDENCE)/sizeof(struct precedence))
void naParseError(struct Parser* p, char* msg, int line)
{
p->err = msg;
p->errLine = line;
longjmp(p->jumpHandle, 1);
}
// A "generic" (too obfuscated to describe) parser error
static void oops(struct Parser* p, struct Token* t)
{
naParseError(p, "parse error", t->line);
}
void naParseInit(struct Parser* p)
{
p->buf = 0;
p->len = 0;
p->lines = 0;
p->nLines = 0;
p->chunks = 0;
p->chunkSizes = 0;
p->nChunks = 0;
p->leftInChunk = 0;
p->cg = 0;
p->tree.type = TOK_TOP;
p->tree.line = 1;
p->tree.str = 0;
p->tree.strlen = 0;
p->tree.num = 0;
p->tree.next = 0;
p->tree.prev = 0;
p->tree.children = 0;
p->tree.lastChild = 0;
}
void naParseDestroy(struct Parser* p)
{
int i;
for(i=0; i<p->nChunks; i++) naFree(p->chunks[i]);
naFree(p->chunks);
naFree(p->chunkSizes);
p->buf = 0;
}
void* naParseAlloc(struct Parser* p, int bytes)
{
char* result;
// Round up to 8 byte chunks for alignment
if(bytes & 0x7) bytes = ((bytes>>3) + 1) << 3;
// Need a new chunk?
if(p->leftInChunk < bytes) {
void* newChunk;
void** newChunks;
int* newChunkSizes;
int sz, i;
sz = p->len;
if(sz < bytes) sz = bytes;
newChunk = naAlloc(sz);
p->nChunks++;
newChunks = naAlloc(p->nChunks * sizeof(void*));
for(i=1; i<p->nChunks; i++) newChunks[i] = p->chunks[i-1];
newChunks[0] = newChunk;
naFree(p->chunks);
p->chunks = newChunks;
newChunkSizes = naAlloc(p->nChunks * sizeof(int));
for(i=1; i<p->nChunks; i++) newChunkSizes[i] = p->chunkSizes[i-1];
newChunkSizes[0] = sz;
naFree(p->chunkSizes);
p->chunkSizes = newChunkSizes;
p->leftInChunk = sz;
}
result = p->chunks[0] + p->chunkSizes[0] - p->leftInChunk;
p->leftInChunk -= bytes;
return (void*)result;
}
// Remove the child from the list where it exists, and insert it at
// the end of the parents child list.
static void addNewChild(struct Token* p, struct Token* c)
{
if(c->prev) c->prev->next = c->next;
if(c->next) c->next->prev = c->prev;
if(c == c->parent->children)
c->parent->children = c->next;
if(c == c->parent->lastChild)
c->parent->lastChild = c->prev;
c->parent = p;
c->next = 0;
c->prev = p->lastChild;
if(p->lastChild) p->lastChild->next = c;
if(!p->children) p->children = c;
p->lastChild = c;
}
// Follows the token list from start (which must be a left brace of
// some type), placing all tokens found into start's child list until
// it reaches the matching close brace.
static void collectBrace(struct Parser* p, struct Token* start)
{
struct Token* t;
int closer = -1;
if(start->type == TOK_LPAR) closer = TOK_RPAR;
if(start->type == TOK_LBRA) closer = TOK_RBRA;
if(start->type == TOK_LCURL) closer = TOK_RCURL;
t = start->next;
while(t) {
struct Token* next;
switch(t->type) {
case TOK_LPAR: case TOK_LBRA: case TOK_LCURL:
collectBrace(p, t);
break;
case TOK_RPAR: case TOK_RBRA: case TOK_RCURL:
if(t->type != closer)
naParseError(p, "mismatched closing brace", t->line);
// Drop this node on the floor, stitch up the list and return
if(start->parent->lastChild == t)
start->parent->lastChild = t->prev;
start->next = t->next;
if(t->next) t->next->prev = start;
return;
}
// Snip t out of the existing list, and append it to start's
// children.
next = t->next;
addNewChild(start, t);
t = next;
}
naParseError(p, "unterminated brace", start->line);
}
// Recursively find the contents of all matching brace pairs in the
// token list and turn them into children of the left token. The
// right token disappears.
static void braceMatch(struct Parser* p, struct Token* start)
{
struct Token* t = start;
while(t) {
switch(t->type) {
case TOK_LPAR: case TOK_LBRA: case TOK_LCURL:
collectBrace(p, t);
break;
case TOK_RPAR: case TOK_RBRA: case TOK_RCURL:
if(start->type != TOK_LBRA)
naParseError(p, "stray closing brace", t->line);
break;
}
t = t->next;
}
}
// Allocate and return an "empty" token as a parsing placeholder.
static struct Token* emptyToken(struct Parser* p)
{
struct Token* t = naParseAlloc(p, sizeof(struct Token));
t->type = TOK_EMPTY;
t->line = -1;
t->strlen = 0;
t->num = 0;
t->str = 0;
t->next = t->prev = t->children = t->lastChild = 0;
t->parent = 0;
return t;
}
// Fixes up parenting for obvious parsing situations, like code blocks
// being the child of a func keyword, etc...
static void fixBlockStructure(struct Parser* p, struct Token* start)
{
struct Token *t, *c;
t = start;
while(t) {
switch(t->type) {
case TOK_ELSE: case TOK_FUNC:
// These guys precede a single curly block
if(!t->next || t->next->type != TOK_LCURL) oops(p, t);
c = t->next;
addNewChild(t, c);
fixBlockStructure(p, c);
break;
case TOK_FOR: case TOK_FOREACH: case TOK_WHILE:
case TOK_IF: case TOK_ELSIF:
// Expect a paren and then a curly
if(!t->next || t->next->type != TOK_LPAR) oops(p, t);
c = t->next;
addNewChild(t, c);
fixBlockStructure(p, c);
if(!t->next || t->next->type != TOK_LCURL) oops(p, t);
c = t->next;
addNewChild(t, c);
fixBlockStructure(p, c);
break;
case TOK_LPAR: case TOK_LBRA: case TOK_LCURL:
fixBlockStructure(p, t->children);
break;
}
t = t->next;
}
// Another pass to hook up the elsif/else chains.
t = start;
while(t) {
if(t->type == TOK_IF) {
while(t->next && t->next->type == TOK_ELSIF)
addNewChild(t, t->next);
if(t->next && t->next->type == TOK_ELSE)
addNewChild(t, t->next);
}
t = t->next;
}
// And a final one to add semicolons. Always add one after
// for/foreach/while expressions. Add one after a function lambda
// if it immediately follows an assignment, and add one after an
// if/elsif/else if it is the first token in an expression list
// (i.e has no previous token, or is preceded by a ';' or '{').
// This mimicks common usage and avoids a conspicuous difference
// between this grammar and more common languages. It can be
// "escaped" with extra parenthesis if necessary, e.g.:
// a = (func { join(" ", arg) })(1, 2, 3, 4);
t = start;
while(t) {
int addSemi = 0;
switch(t->type) {
case TOK_IF:
if(!t->prev
|| t->prev->type == TOK_SEMI
|| t->prev->type == TOK_LCURL)
addSemi = 1;
break;
case TOK_FOR: case TOK_FOREACH: case TOK_WHILE:
addSemi = 1;
break;
case TOK_FUNC:
if(t->prev && t->prev->type == TOK_ASSIGN)
addSemi = 1;
break;
}
if(addSemi) {
struct Token* semi = emptyToken(p);
semi->type = TOK_SEMI;
semi->line = t->line;
semi->next = t->next;
semi->prev = t;
semi->parent = t->parent;
if(semi->next) semi->next->prev = semi;
t->next = semi;
t = semi; // don't bother checking the new one
}
t = t->next;
}
}
// True if the token's type exists in the precedence level.
static int tokInLevel(struct Token* tok, int level)
{
int i;
for(i=0; i<MAX_PREC_TOKS; i++)
if(PRECEDENCE[level].toks[i] == tok->type)
return 1;
return 0;
}
static int isBrace(int type)
{
return type == TOK_LPAR || type == TOK_LBRA || type == TOK_LCURL;
}
static int isBlock(int t)
{
return t == TOK_IF || t == TOK_ELSIF || t == TOK_ELSE
|| t == TOK_FOR || t == TOK_FOREACH || t == TOK_WHILE
|| t == TOK_FUNC;
}
static void precChildren(struct Parser* p, struct Token* t);
static void precBlock(struct Parser* p, struct Token* t);
static struct Token* parsePrecedence(struct Parser* p,
struct Token* start, struct Token* end,
int level)
{
int rule;
struct Token *t, *top, *left, *right;
struct Token *a, *b, *c, *d; // temporaries
// This is an error. No "siblings" are allowed at the bottom level.
if(level >= PRECEDENCE_LEVELS && start != end)
oops(p, start);
// Synthesize an empty token if necessary
if(end == 0 && start == 0)
return emptyToken(p);
// Sanify the list. This is OK, since we're recursing into the
// list structure; stuff to the left and right has already been
// handled somewhere above.
if(end == 0) end = start;
if(start == 0) start = end;
if(start->prev) start->prev->next = 0;
if(end->next) end->next->prev = 0;
start->prev = end->next = 0;
// Single tokens parse as themselves. Recurse into braces, and
// parse children of block structure.
if(start == end) {
if(isBrace(start->type)) {
precChildren(p, start);
} else if(isBlock(start->type)) {
precBlock(p, start);
}
return start;
}
// A context-sensitivity: we want to parse ';' and ',' as binary
// operators, but want them to be legal at the beginning and end
// of a list (unlike, say, '+' where we want a parse error).
// Generate empties as necessary.
if(start->type == TOK_SEMI || start->type == TOK_COMMA) {
t = emptyToken(p);
start->prev = t;
t->next = start;
start = t;
}
if(end->type == TOK_SEMI || end->type == TOK_COMMA) {
t = emptyToken(p);
end->next = t;
t->prev = end;
end = t;
}
// Another one: the "." and (postfix) "[]/()" operators should
// really be the same precendence level, but the existing
// implementation doesn't allow for it. Bump us up a level if we
// are parsing for DOT but find a LPAR/LBRA at the end of the
// list.
if(PRECEDENCE[level].toks[0] == TOK_DOT)
if(end->type == TOK_LPAR || end->type == TOK_LBRA)
level--;
top = left = right = 0;
rule = PRECEDENCE[level].rule;
switch(rule) {
case PREC_PREFIX:
if(tokInLevel(start, level) && start->next) {
a = start->children;
b = start->lastChild;
c = start->next;
d = end;
top = start;
if(a) left = parsePrecedence(p, a, b, 0);
right = parsePrecedence(p, c, d, level);
}
break;
case PREC_SUFFIX:
if(tokInLevel(end, level) && end->prev) {
a = start;
b = end->prev;
c = end->children;
d = end->lastChild;
top = end;
left = parsePrecedence(p, a, b, level);
if(c) right = parsePrecedence(p, c, d, 0);
}
break;
case PREC_BINARY:
t = end->prev;
while(t->prev) {
if(tokInLevel(t, level)) {
a = t->prev ? start : 0;
b = t->prev;
c = t->next;
d = t->next ? end : 0;
top = t;
left = parsePrecedence(p, a, b, level);
right = parsePrecedence(p, c, d, level+1);
break;
}
t = t->prev;
}
break;
case PREC_REVERSE:
t = start->next;
while(t->next) {
if(tokInLevel(t, level)) {
a = t->prev ? start : 0;
b = t->prev;
c = t->next;
d = t->next ? end : 0;
top = t;
left = parsePrecedence(p, a, b, level+1);
right = parsePrecedence(p, c, d, level);
break;
}
t = t->next;
}
break;
}
// Found nothing, try the next level
if(!top)
return parsePrecedence(p, start, end, level+1);
if(left) {
left->next = right;
left->prev = 0;
left->parent = top;
}
top->children = left;
if(right) {
right->next = 0;
right->prev = left;
right->parent = top;
}
top->lastChild = right;
top->next = top->prev = 0;
return top;
}
static void precChildren(struct Parser* p, struct Token* t)
{
struct Token* top = parsePrecedence(p, t->children, t->lastChild, 0);
t->children = top;
t->lastChild = top;
}
// Run a "block structure" node (if/elsif/else/for/while/foreach)
// through the precedence parser. The funny child structure makes
// this a little more complicated than it should be.
static void precBlock(struct Parser* p, struct Token* block)
{
struct Token* t = block->children;
while(t) {
if(isBrace(t->type))
precChildren(p, t);
else if(isBlock(t->type))
precBlock(p, t);
t = t->next;
}
}
naRef naParseCode(struct Context* c, naRef srcFile, int firstLine,
char* buf, int len, int* errLine)
{
naRef codeObj;
struct Token* t;
struct Parser p;
// Catch parser errors here.
*errLine = 0;
if(setjmp(p.jumpHandle)) {
c->error = p.err;
*errLine = p.errLine;
return naNil();
}
naParseInit(&p);
p.context = c;
p.srcFile = srcFile;
p.firstLine = firstLine;
p.buf = buf;
p.len = len;
// Lexify, match brace structure, fixup if/for/etc...
naLex(&p);
braceMatch(&p, p.tree.children);
fixBlockStructure(&p, p.tree.children);
// Recursively run the precedence parser, and fixup the treetop
t = parsePrecedence(&p, p.tree.children, p.tree.lastChild, 0);
t->prev = t->next = 0;
p.tree.children = t;
p.tree.lastChild = t;
// Generate code!
codeObj = naCodeGen(&p, &(p.tree));
// Clean up our mess
naParseDestroy(&p);
return codeObj;
}

98
simgear/nasal/parse.h Normal file
View File

@ -0,0 +1,98 @@
#ifndef _PARSE_H
#define _PARSE_H
#include <setjmp.h>
#include "nasal.h"
#include "data.h"
#include "code.h"
enum {
TOK_TOP=1, TOK_AND, TOK_OR, TOK_NOT, TOK_LPAR, TOK_RPAR, TOK_LBRA,
TOK_RBRA, TOK_LCURL, TOK_RCURL, TOK_MUL, TOK_PLUS, TOK_MINUS, TOK_NEG,
TOK_DIV, TOK_CAT, TOK_COLON, TOK_DOT, TOK_COMMA, TOK_SEMI,
TOK_ASSIGN, TOK_LT, TOK_LTE, TOK_EQ, TOK_NEQ, TOK_GT, TOK_GTE,
TOK_IF, TOK_ELSIF, TOK_ELSE, TOK_FOR, TOK_FOREACH, TOK_WHILE,
TOK_RETURN, TOK_BREAK, TOK_CONTINUE, TOK_FUNC, TOK_SYMBOL,
TOK_LITERAL, TOK_EMPTY, TOK_NIL
};
struct Token {
int type;
int line;
char* str;
int strlen;
double num;
struct Token* parent;
struct Token* next;
struct Token* prev;
struct Token* children;
struct Token* lastChild;
};
struct Parser {
// Handle to the Nasal interpreter
struct Context* context;
char* err;
int errLine;
jmp_buf jumpHandle;
// The parse tree ubernode
struct Token tree;
// The input buffer
char* buf;
int len;
// Input file parameters (for generating pretty stack dumps)
naRef srcFile;
int firstLine;
// Chunk allocator. Throw away after parsing.
void** chunks;
int* chunkSizes;
int nChunks;
int leftInChunk;
// Computed line number table for the lexer
int* lines;
int nLines;
struct CodeGenerator* cg;
};
struct CodeGenerator {
int lastLine;
// Accumulated byte code array
unsigned char* byteCode;
int nBytes;
int codeAlloced;
// Stack of "loop" frames for break/continue statements
struct {
int breakIP;
int contIP;
struct Token* label;
} loops[MAX_MARK_DEPTH];
int loopTop;
// Dynamic storage for constants, to be compiled into a static table
naRef consts; // index -> naRef
naRef interned; // naRef -> index (scalars only!)
int nConsts;
};
void naParseError(struct Parser* p, char* msg, int line);
void naParseInit(struct Parser* p);
void* naParseAlloc(struct Parser* p, int bytes);
void naParseDestroy(struct Parser* p);
void naLex(struct Parser* p);
naRef naCodeGen(struct Parser* p, struct Token* tok);
void naParse(struct Parser* p);
#endif // _PARSE_H

302
simgear/nasal/string.c Normal file
View File

@ -0,0 +1,302 @@
#include <math.h>
#include <string.h>
#include "nasal.h"
#include "data.h"
// The maximum number of significant (decimal!) figures in an IEEE
// double.
#define DIGITS 16
// The minimum size we'll allocate for a string. Since a string
// structure is already 12 bytes, and each naRef that points to it is
// 8, there isn't much point in being stingy.
#define MINLEN 16
static int tonum(unsigned char* s, int len, double* result);
static int fromnum(double val, unsigned char* s);
int naStr_len(naRef s)
{
if(!IS_STR(s)) return 0;
return s.ref.ptr.str->len;
}
char* naStr_data(naRef s)
{
if(!IS_STR(s)) return 0;
return s.ref.ptr.str->data;
}
static void setlen(struct naStr* s, int sz)
{
int currSz, waste;
sz += 1; // Allow for an extra nul terminator
currSz = s->len+1 < MINLEN ? MINLEN : s->len+1;
waste = currSz - sz; // how much extra if we don't reallocate?
if(s->data == 0 || waste < 0 || waste > MINLEN) {
naFree(s->data);
s->data = naAlloc(sz < MINLEN ? MINLEN : sz);
}
s->len = sz - 1;
s->data[s->len] = 0; // nul terminate
}
naRef naStr_fromdata(naRef dst, char* data, int len)
{
if(!IS_STR(dst)) return naNil();
setlen(dst.ref.ptr.str, len);
memcpy(dst.ref.ptr.str->data, data, len);
return dst;
}
naRef naStr_concat(naRef dest, naRef s1, naRef s2)
{
struct naStr* dst = dest.ref.ptr.str;
struct naStr* a = s1.ref.ptr.str;
struct naStr* b = s2.ref.ptr.str;
if(!(IS_STR(s1)&&IS_STR(s2)&&IS_STR(dest))) return naNil();
setlen(dst, a->len + b->len);
memcpy(dst->data, a->data, a->len);
memcpy(dst->data + a->len, b->data, b->len);
return dest;
}
naRef naStr_substr(naRef dest, naRef str, int start, int len)
{
struct naStr* dst = dest.ref.ptr.str;
struct naStr* s = str.ref.ptr.str;
if(!(IS_STR(dest)&&IS_STR(str))) return naNil();
if(start + len > s->len) { dst->len = 0; dst->data = 0; return naNil(); }
setlen(dst, len);
memcpy(dst->data, s->data + start, len);
return dest;
}
int naStr_equal(naRef s1, naRef s2)
{
struct naStr* a = s1.ref.ptr.str;
struct naStr* b = s2.ref.ptr.str;
if(a->data == b->data) return 1;
if(a->len != b->len) return 0;
if(memcmp(a->data, b->data, a->len) == 0) return 1;
return 0;
}
naRef naStr_fromnum(naRef dest, double num)
{
struct naStr* dst = dest.ref.ptr.str;
unsigned char buf[DIGITS+8];
setlen(dst, fromnum(num, buf));
memcpy(dst->data, buf, dst->len);
return dest;
}
int naStr_parsenum(char* str, int len, double* result)
{
return tonum(str, len, result);
}
int naStr_tonum(naRef str, double* out)
{
return tonum(str.ref.ptr.str->data, str.ref.ptr.str->len, out);
}
int naStr_numeric(naRef str)
{
double dummy;
return tonum(str.ref.ptr.str->data, str.ref.ptr.str->len, &dummy);
}
void naStr_gcclean(struct naStr* str)
{
if(str->len > MINLEN) {
naFree(str->data);
str->data = 0;
}
str->len = 0;
}
////////////////////////////////////////////////////////////////////////
// Below is a custom double<->string conversion library. Why not
// simply use sprintf and atof? Because they aren't acceptably
// platform independant, sadly. I've seen some very strange results.
// This works the same way everywhere, although it is tied to an
// assumption of standard IEEE 64 bit floating point doubles.
//
// In practice, these routines work quite well. Testing conversions
// of random doubles to strings and back, this routine is beaten by
// glibc on roundoff error 23% of the time, beats glibc in 10% of
// cases, and ties (usually with an error of exactly zero) the
// remaining 67%.
////////////////////////////////////////////////////////////////////////
// Reads an unsigned decimal out of the scalar starting at i, stores
// it in v, and returns the next index to start at. Zero-length
// decimal numbers are allowed, and are returned as zero.
static int readdec(unsigned char* s, int len, int i, double* v)
{
*v = 0;
if(i >= len) return len;
while(i < len && s[i] >= '0' && s[i] <= '9') {
*v= (*v) * 10 + (s[i] - '0');
i++;
}
return i;
}
// Reads a signed integer out of the string starting at i, stores it
// in v, and returns the next index to start at. Zero-length
// decimal numbers (and length-1 strings like '+') are allowed, and
// are returned as zero.
static int readsigned(unsigned char* s, int len, int i, double* v)
{
double sgn=1, val;
if(i >= len) { *v = 0; return len; }
if(s[i] == '+') { i++; }
else if(s[i] == '-') { i++; sgn = -1; }
i = readdec(s, len, i, &val);
*v = sgn*val;
return i;
}
// Integer decimal power utility, with a tweak that enforces
// integer-exactness for arguments where that is possible.
static double decpow(int exp)
{
double v = 1;
int absexp;
if(exp < 0 || exp >= DIGITS)
return pow(10, exp);
else
absexp = exp < 0 ? -exp : exp;
while(absexp--) v *= 10.0;
return v;
}
static int tonum(unsigned char* s, int len, double* result)
{
int i=0, fraclen=0;
double sgn=1, val, frac=0, exp=0;
// Read the integer part
i = readsigned(s, len, i, &val);
if(val < 0) { sgn = -1; val = -val; }
// Read the fractional part, if any
if(i < len && s[i] == '.') {
i++;
fraclen = readdec(s, len, i, &frac) - i;
i += fraclen;
}
// Read the exponent, if any
if(i < len && (s[i] == 'e' || s[i] == 'E'))
i = readsigned(s, len, i+1, &exp);
// compute the result
*result = sgn * (val + frac * decpow(-fraclen)) * decpow(exp);
// if we didn't use the whole string, return failure
if(i < len) return 0;
return 1;
}
// Very simple positive (!) integer print routine. Puts the result in
// s and returns the number of characters written. Does not null
// terminate the result.
static int decprint(int val, unsigned char* s)
{
int p=1, i=0;
if(val == 0) { *s = '0'; return 1; }
while(p <= val) p *= 10;
p /= 10;
while(p > 0) {
int count = 0;
while(val >= p) { val -= p; count++; }
s[i++] = '0' + count;
p /= 10;
}
return i;
}
// Takes a positive (!) floating point numbers, and returns exactly
// DIGITS decimal numbers in the buffer pointed to by s, and an
// integer exponent as the return value. For example, printing 1.0
// will result in "1000000000000000" in the buffer and -15 as the
// exponent. The caller can then place the floating point as needed.
static int rawprint(double val, unsigned char* s)
{
int exponent = (int)floor(log10(val));
double mantissa = val / pow(10, exponent);
int i, c;
for(i=0; i<DIGITS-1; i++) {
int digit = (int)floor(mantissa);
s[i] = '0' + digit;
mantissa -= digit;
mantissa *= 10.0;
}
// Round (i.e. don't floor) the last digit
c = (int)floor(mantissa);
if(mantissa - c >= 0.5) c++;
if(c < 0) c = 0;
if(c > 9) c = 9;
s[i] = '0' + c;
return exponent - DIGITS + 1;
}
static int fromnum(double val, unsigned char* s)
{
unsigned char raw[DIGITS];
unsigned char* ptr = s;
int exp, digs, i=0;
// Handle negatives
if(val < 0) { *ptr++ = '-'; val = -val; }
// Exactly an integer is a special case
if(val == (int)val) {
ptr += decprint(val, ptr);
*ptr = 0;
return ptr - s;
}
// Get the raw digits
exp = rawprint(val, raw);
// Examine trailing zeros to get a significant digit count
for(i=DIGITS-1; i>0; i--)
if(raw[i] != '0') break;
digs = i+1;
if(exp > 0 || exp < -(DIGITS+2)) {
// Standard scientific notation
exp += DIGITS-1;
*ptr++ = raw[0];
if(digs > 1) {
*ptr++ = '.';
for(i=1; i<digs; i++) *ptr++ = raw[i];
}
*ptr++ = 'e';
if(exp < 0) { exp = -exp; *ptr++ = '-'; }
else { *ptr++ = '+'; }
if(exp < 10) *ptr++ = '0';
ptr += decprint(exp, ptr);
} else if(exp < 1-DIGITS) {
// Fraction with insignificant leading zeros
*ptr++ = '0'; *ptr++ = '.';
for(i=0; i<-(exp+DIGITS); i++) *ptr++ = '0';
for(i=0; i<digs; i++) *ptr++ = raw[i];
} else {
// Integer part
for(i=0; i<DIGITS+exp; i++) *ptr++ = raw[i];
if(i < digs) {
// Fraction, if any
*ptr++ = '.';
while(i<digs) *ptr++ = raw[i++];
}
}
*ptr = 0;
return ptr - s;
}

71
simgear/nasal/vector.c Normal file
View File

@ -0,0 +1,71 @@
#include "nasal.h"
#include "data.h"
static void realloc(struct naVec* v)
{
int i, newsz = 1 + ((v->size*3)>>1);
naRef* na = naAlloc(sizeof(naRef) * newsz);
v->alloced = newsz;
for(i=0; i<v->size; i++)
na[i] = v->array[i];
naFree(v->array);
v->array = na;
}
void naVec_init(naRef vec)
{
struct naVec* v = vec.ref.ptr.vec;
v->array = 0;
v->size = 0;
v->alloced = 0;
}
void naVec_gcclean(struct naVec* v)
{
naFree(v->array);
v->size = 0;
v->alloced = 0;
v->array = 0;
}
naRef naVec_get(naRef v, int i)
{
if(!IS_VEC(v)) return naNil();
if(i >= v.ref.ptr.vec->size) return naNil();
return v.ref.ptr.vec->array[i];
}
void naVec_set(naRef vec, int i, naRef o)
{
struct naVec* v = vec.ref.ptr.vec;
if(!IS_VEC(vec) || i >= v->size) return;
v->array[i] = o;
}
int naVec_size(naRef v)
{
if(!IS_VEC(v)) return 0;
return v.ref.ptr.vec->size;
}
int naVec_append(naRef vec, naRef o)
{
struct naVec* v = vec.ref.ptr.vec;
if(!IS_VEC(vec)) return 0;
if(v->size >= v->alloced)
realloc(v);
v->array[v->size] = o;
return v->size++;
}
naRef naVec_removelast(naRef vec)
{
naRef o;
struct naVec* v = vec.ref.ptr.vec;
if(!IS_VEC(vec) || v->size == 0) return naNil();
o = v->array[v->size - 1];
v->size--;
if(v->size < (v->alloced >> 1))
realloc(v);
return o;
}