Cosmo/src/cvm.c

1302 lines
49 KiB
C

#include "cvm.h"
#include "cdebug.h"
#include "cmem.h"
#include "cparse.h"
#include "cstate.h"
#include "cundump.h"
#include <math.h>
#include <stdarg.h>
#include <string.h>
#define cosmoV_protect(panic) setjmp(panic->jmp) == 0
void cosmoV_pushFString(CState *state, const char *format, ...)
{
va_list args;
va_start(args, format);
cosmoO_pushVFString(state, format, args);
va_end(args);
}
// inserts val at state->top - indx - 1, moving everything else up
void cosmoV_insert(CState *state, int indx, CValue val)
{
StkPtr tmp = cosmoV_getTop(state, indx);
// moves everything up
for (StkPtr i = state->top; i > tmp; i--) {
*i = *(i - 1);
}
*tmp = val;
state->top++;
}
bool cosmoV_undump(CState *state, cosmo_Reader reader, const void *ud)
{
CObjFunction *func;
if (cosmoD_undump(state, reader, ud, &func)) {
return false;
};
// #ifdef VM_DEBUG
disasmChunk(&func->chunk, func->name ? func->name->str : UNNAMEDCHUNK, 0);
// #endif
// push function onto the stack so it doesn't it cleaned up by the GC, at the same stack
// location put our closure
cosmoV_pushRef(state, (CObj *)func);
*(cosmoV_getTop(state, 0)) = cosmoV_newRef(cosmoO_newClosure(state, func));
return true;
}
// returns false if failed, error will be on the top of the stack. true if successful, closure will
// be on the top of the stack
bool cosmoV_compileString(CState *state, const char *src, const char *name)
{
CObjFunction *func;
CPanic *panic = cosmoV_newPanic(state);
if (cosmoV_protect(panic)) {
func = cosmoP_compileString(state, src, name);
#ifdef VM_DEBUG
disasmChunk(&func->chunk, func->module->str, 0);
#endif
cosmoV_freePanic(state);
// push function onto the stack so it doesn't it cleaned up by the GC, at the same stack
// location put our closure
cosmoV_pushRef(state, (CObj *)func);
*(cosmoV_getTop(state, 0)) = cosmoV_newRef(cosmoO_newClosure(state, func));
return true;
}
cosmoV_freePanic(state);
return false;
}
void cosmoV_printBacktrace(CState *state, CObjError *err)
{
// print stack trace
for (int i = 0; i < err->frameCount; i++) {
CCallFrame *frame = &err->frames[i];
CObjFunction *function = frame->closure->function;
CChunk *chunk = &function->chunk;
int line = chunk->lineInfo[frame->pc - chunk->buf - 1];
if (i == err->frameCount - 1 &&
!err->parserError) // it's the last call frame (and not a parser error), prepare for the
// objection to be printed
fprintf(stderr, "Objection in %.*s on [line %d] in ", function->module->length,
function->module->str, line);
else
fprintf(stderr, "[line %d] in ", line);
if (function->name == NULL) { // unnamed chunk
fprintf(stderr, "%s\n", UNNAMEDCHUNK);
} else {
fprintf(stderr, "%.*s()\n", function->name->length, function->name->str);
}
}
if (err->parserError)
fprintf(stderr, "Objection while parsing on [line %d]\n", err->line);
// finally, print the error message
CObjString *errString = cosmoV_toString(state, err->err);
printf("\t%.*s\n", errString->length, errString->str);
}
/*
takes value on top of the stack and wraps an CObjError around it, then throws it
*/
void cosmoV_throw(CState *state)
{
StkPtr temp = cosmoV_getTop(state, 0);
CObjError *error = cosmoO_newError(state, *temp);
CValue val = cosmoV_newRef((CObj *)cosmoO_newError(state, *temp));
if (state->panic) {
state->top = state->panic->top;
state->frameCount = state->panic->frameCount;
state->freezeGC = state->panic->freezeGC;
cosmoV_pushValue(state, val);
longjmp(state->panic->jmp, 1);
} else {
cosmoV_pushValue(state, val);
fprintf(stderr, "Unhandled panic! ");
cosmoV_printBacktrace(state, error);
exit(1);
}
}
void cosmoV_error(CState *state, const char *format, ...)
{
// format the error string and push it onto the stack
va_list args;
va_start(args, format);
cosmoO_pushVFString(state, format, args);
va_end(args);
// throw the error onto the state
cosmoV_throw(state);
}
CObjUpval *captureUpvalue(CState *state, CValue *local)
{
CObjUpval *prev = NULL;
CObjUpval *upvalue = state->openUpvalues;
while (upvalue != NULL &&
upvalue->val > local) { // while upvalue exists and is higher on the stack than local
prev = upvalue;
upvalue = upvalue->next;
}
if (upvalue != NULL && upvalue->val == local) { // we found the local we were going to capture
return upvalue;
}
CObjUpval *newUpval = cosmoO_newUpvalue(state, local);
newUpval->next = upvalue;
// the list is sorted, so insert it at our found upvalue
if (prev == NULL) {
state->openUpvalues = newUpval;
} else {
prev->next = newUpval;
}
return newUpval;
}
void closeUpvalues(CState *state, CValue *local)
{
while (state->openUpvalues != NULL &&
state->openUpvalues->val >=
local) { // for every upvalue that points to the local or anything above it
CObjUpval *upvalue = state->openUpvalues;
upvalue->closed = *upvalue->val;
upvalue->val = &upvalue->closed; // upvalue now points to itself :P
state->openUpvalues = upvalue->next;
}
}
void pushCallFrame(CState *state, CObjClosure *closure, int args)
{
#ifdef SAFE_STACK
if (state->frameCount >= FRAME_MAX) {
cosmoV_error(state, "Callframe overflow!");
return;
}
#endif
CCallFrame *frame = &state->callFrame[state->frameCount++];
frame->base = state->top - args - 1; // - 1 for the function
frame->pc = closure->function->chunk.buf;
frame->closure = closure;
}
// offset is the offset of the callframe base we set the state->top back too (useful for passing
// values in the stack as arguments, like methods)
void popCallFrame(CState *state, int offset)
{
closeUpvalues(state,
state->callFrame[state->frameCount - 1].base); // close any upvalue still open
state->top = state->callFrame[state->frameCount - 1].base + offset; // resets the stack
state->frameCount--;
}
void cosmoV_concat(CState *state, int vals)
{
StkPtr start = state->top - vals;
StkPtr end = cosmoV_getTop(state, 0);
CObjString *result = cosmoV_toString(state, *start);
for (StkPtr current = start + 1; current <= end; current++) {
cosmoV_pushRef(state, (CObj *)result); // so our GC can find our current result string
CObjString *otherStr = cosmoV_toString(state, *current);
cosmoV_pushRef(state, (CObj *)otherStr); // also so our GC won't free otherStr
// concat the two strings together
size_t sz = result->length + otherStr->length;
char *buf = cosmoM_xmalloc(state, sz + 1); // +1 for null terminator
memcpy(buf, result->str, result->length);
memcpy(buf + result->length, otherStr->str, otherStr->length);
buf[sz] = '\0';
result = cosmoO_takeString(state, buf, sz);
cosmoV_setTop(state, 2); // pop result & otherStr off the stack
}
state->top = start;
cosmoV_pushRef(state, (CObj *)result);
}
int cosmoV_execute(CState *state);
void invokeMethod(CState *state, CObj *obj, CValue func, int args, int nresults, int offset);
/*
calls a native C Function with # args on the stack, nresults are pushed onto the stack upon
return.
state->top is moved to base + offset + nresults, with nresults pushed onto the stack
from base + offset
*/
static void callCFunction(CState *state, CosmoCFunction cfunc, int args, int nresults, int offset)
{
StkPtr savedBase = cosmoV_getTop(state, args);
int nres = cfunc(state, args, savedBase + 1);
// caller function wasn't expecting this many return values, cap it
if (nres > nresults)
nres = nresults;
// remember where the return values are
StkPtr results = cosmoV_getTop(state, nres - 1);
state->top = savedBase + offset; // set stack
// push the return value back onto the stack
memmove(state->top, results,
sizeof(CValue) * nres); // copies the return values to the top of the stack
state->top += nres; // and make sure to move state->top to match
// now, if the caller function expected more return values, push nils onto the stack
for (int i = nres; i < nresults; i++) {
cosmoV_pushValue(state, cosmoV_newNil());
}
}
/*
calls a raw closure object with # args on the stack, nresults are pushed onto the stack upon
return.
stack->top is moved to base + offset + nresults, with nresults pushed onto the stack
from base + offset
*/
static void rawCall(CState *state, CObjClosure *closure, int args, int nresults, int offset)
{
CObjFunction *func = closure->function;
// if the function is variadic and theres more args than parameters, push the args into a table
if (func->variadic && args >= func->args) {
int extraArgs = args - func->args;
StkPtr variStart = cosmoV_getTop(state, extraArgs - 1);
// push key & value pairs
for (int i = 0; i < extraArgs; i++) {
cosmoV_pushNumber(state, i);
cosmoV_pushValue(state, *(variStart + i));
}
cosmoV_makeTable(state, extraArgs);
*variStart = *cosmoV_getTop(state, 0); // move table on the stack to the vari local
state->top -= extraArgs;
pushCallFrame(state, closure, func->args + 1);
} else if (args != func->args) { // mismatched args
cosmoV_error(state, "Expected %d arguments for %s, got %d!", closure->function->args,
closure->function->name == NULL ? UNNAMEDCHUNK : closure->function->name->str,
args);
} else {
// load function into callframe
pushCallFrame(state, closure, func->args);
}
// execute
int nres = cosmoV_execute(state);
if (nres > nresults) // caller function wasn't expecting this many return values, cap it
nres = nresults;
// remember where the return values are
StkPtr results = cosmoV_getTop(state, nres - 1);
// pop the callframe and return results :)
popCallFrame(state, offset);
// push the return values back onto the stack
for (int i = 0; i < nres; i++) {
state->top[i] = results[i];
}
state->top += nres; // and make sure to move state->top to match
// now, if the caller function expected more return values, push nils onto the stack
for (int i = nres; i < nresults; i++) {
cosmoV_pushValue(state, cosmoV_newNil());
}
}
// returns true if successful, false if error
void callCValue(CState *state, CValue func, int args, int nresults, int offset)
{
#ifdef VM_DEBUG
printf("\n");
printIndent(state->frameCount - 1);
cosmoV_printValue(func);
printf("(%d args)\n", args);
#endif
if (!IS_REF(func)) {
cosmoV_error(state, "Cannot call non-callable type %s!", cosmoV_typeStr(func));
}
switch (cosmoV_readRef(func)->type) {
case COBJ_CLOSURE:
rawCall(state, cosmoV_readClosure(func), args, nresults, offset);
break;
case COBJ_CFUNCTION:
callCFunction(state, cosmoV_readCFunction(func), args, nresults, offset);
break;
case COBJ_METHOD: {
CObjMethod *method = (CObjMethod *)cosmoV_readRef(func);
invokeMethod(state, method->obj, method->func, args, nresults, offset + 1);
break;
}
case COBJ_OBJECT: { // object is being instantiated, making another object
CObjObject *protoObj = (CObjObject *)cosmoV_readRef(func);
CValue ret;
cosmoV_pushRef(state, (CObj *)protoObj); // push proto to stack for GC to find
CObjObject *newObj = cosmoO_newObject(state);
newObj->_obj.proto = protoObj;
cosmoV_pop(state); // pop proto
// check if they defined an initializer (we accept 0 return values)
if (cosmoO_getIString(state, protoObj, ISTRING_INIT, &ret)) {
invokeMethod(state, (CObj *)newObj, ret, args, 0, offset + 1);
} else {
// no default initializer
cosmoV_error(state, "Expected __init() in proto, object cannot be instantiated!");
}
if (nresults > 0) {
cosmoV_pushRef(state, (CObj *)newObj);
// push the nils to fill up the expected return values.
// -1 since the we already pushed the important value
for (int i = 0; i < nresults - 1; i++) {
cosmoV_pushValue(state, cosmoV_newNil());
}
}
break;
}
default:
cosmoV_error(state, "Cannot call non-callable type %s!", cosmoV_typeStr(func));
}
}
void invokeMethod(CState *state, CObj *obj, CValue func, int args, int nresults, int offset)
{
// first, set the first argument to the object
StkPtr temp = cosmoV_getTop(state, args);
*temp = cosmoV_newRef(obj);
callCValue(state, func, args + 1, nresults, offset);
}
// wraps cosmoV_call in a protected state, CObjError will be pushed onto the stack if function call
// failed, else return values are passed
// returns false if function call failed, true if function call succeeded
bool cosmoV_pcall(CState *state, int args, int nresults)
{
CPanic *panic = cosmoV_newPanic(state);
if (cosmoV_protect(panic)) {
cosmoV_call(state, args, nresults);
cosmoV_freePanic(state);
return true;
} else {
// if cosmoV_protect returns false, the error is already on the top of the stack
if (nresults > 0) {
// push other expected results onto the stack
for (int i = 0; i < nresults - 1; i++) {
cosmoV_pushValue(state, cosmoV_newNil());
}
}
cosmoV_freePanic(state);
return false;
}
}
// calls a callable object at stack->top - args - 1, passing the # of args to the callable, and
// ensuring nresults are returned
// returns false if an error was thrown, else true if successful
void cosmoV_call(CState *state, int args, int nresults)
{
StkPtr val = cosmoV_getTop(state, args); // function will always be right above the args
callCValue(state, *val, args, nresults, 0);
}
static inline bool isFalsey(StkPtr val)
{
return IS_NIL(*val) || (IS_BOOLEAN(*val) && !cosmoV_readBoolean(*val));
}
CObjObject *cosmoV_makeObject(CState *state, int pairs)
{
StkPtr key, val;
CObjObject *newObj = cosmoO_newObject(state);
cosmoV_pushRef(state, (CObj *)newObj); // so our GC doesn't free our new object
for (int i = 0; i < pairs; i++) {
val = cosmoV_getTop(state, (i * 2) + 1);
key = cosmoV_getTop(state, (i * 2) + 2);
// set key/value pair
CValue *newVal = cosmoT_insert(state, &newObj->tbl, *key);
*newVal = *val;
}
// once done, pop everything off the stack + push new object
cosmoV_setTop(state, (pairs * 2) + 1); // + 1 for our object
cosmoV_pushRef(state, (CObj *)newObj);
return newObj;
}
bool cosmoV_registerProtoObject(CState *state, CObjType objType, CObjObject *obj)
{
bool replaced = state->protoObjects[objType] != NULL;
state->protoObjects[objType] = obj;
// walk through the object list
CObj *curr = state->objects;
while (curr != NULL) {
// update the proto
if (curr != (CObj *)obj && curr->type == objType && curr->proto != NULL) {
curr->proto = obj;
}
curr = curr->next;
}
return replaced;
}
void cosmoV_makeTable(CState *state, int pairs)
{
StkPtr key, val;
CObjTable *newObj = cosmoO_newTable(state);
cosmoV_pushRef(state, (CObj *)newObj); // so our GC doesn't free our new table
for (int i = 0; i < pairs; i++) {
val = cosmoV_getTop(state, (i * 2) + 1);
key = cosmoV_getTop(state, (i * 2) + 2);
// set key/value pair
CValue *newVal = cosmoT_insert(state, &newObj->tbl, *key);
*newVal = *val;
}
// once done, pop everything off the stack + push new table
cosmoV_setTop(state, (pairs * 2) + 1); // + 1 for our table
cosmoV_pushRef(state, (CObj *)newObj);
}
void cosmoV_rawget(CState *state, CObj *_obj, CValue key, CValue *val)
{
CObjObject *object = cosmoO_grabProto(_obj);
// no proto to get from
if (object == NULL) {
CObjString *field = cosmoV_toString(state, key);
cosmoV_error(state, "No proto defined! Couldn't get field '%s' from type %s", field->str,
cosmoO_typeStr(_obj));
}
// push the object onto the stack so the GC can find it
cosmoV_pushRef(state, (CObj *)object);
cosmoO_getRawObject(state, object, key, val, _obj);
cosmoV_pop(state);
}
void cosmoV_rawset(CState *state, CObj *_obj, CValue key, CValue val)
{
CObjObject *object = cosmoO_grabProto(_obj);
// no proto to set to
if (object == NULL) {
CObjString *field = cosmoV_toString(state, key);
cosmoV_error(state, "No proto defined! Couldn't set field '%s' to type %s", field->str,
cosmoO_typeStr(_obj));
}
cosmoO_setRawObject(state, object, key, val, _obj);
}
void cosmoV_get(CState *state)
{
CValue val;
StkPtr obj = cosmoV_getTop(state, 1); // object was pushed first
StkPtr key = cosmoV_getTop(state, 0); // then the key
if (!IS_REF(*obj)) {
cosmoV_error(state, "Couldn't get field from type %s!", cosmoV_typeStr(*obj));
}
cosmoV_rawget(state, cosmoV_readRef(*obj), *key, &val);
// pop the obj & key, push the value
cosmoV_setTop(state, 2);
cosmoV_pushValue(state, val);
}
// yes, this would technically make it possible to set fields of types other than <string>. go crazy
void cosmoV_set(CState *state)
{
StkPtr obj = cosmoV_getTop(state, 2); // object was pushed first
StkPtr key = cosmoV_getTop(state, 1); // then the key
StkPtr val = cosmoV_getTop(state, 0); // and finally the value
if (!IS_REF(*obj)) {
cosmoV_error(state, "Couldn't set field on type %s!", cosmoV_typeStr(*obj));
}
cosmoV_rawset(state, cosmoV_readRef(*obj), *key, *val);
// pop the obj, key & value
cosmoV_setTop(state, 3);
}
void cosmoV_getMethod(CState *state, CObj *obj, CValue key, CValue *val)
{
cosmoV_rawget(state, obj, key, val);
// if the result is callable, wrap it in an method
if (IS_CALLABLE(*val)) {
// push object to stack so the GC can find it
cosmoV_pushRef(state, (CObj *)obj);
CObjMethod *method = cosmoO_newMethod(state, *val, obj);
cosmoV_pop(state); // pop the object
*val = cosmoV_newRef(method);
}
}
bool cosmoV_isValueUserType(CState *state, CValue val, int userType)
{
if (!IS_OBJECT(val)) {
return false;
}
CObjObject *obj = cosmoV_readObject(val);
if (obj->userT != userType) {
return false;
}
return true;
}
int _tbl__next(CState *state, int nargs, CValue *args)
{
if (nargs != 1) {
cosmoV_error(state, "Expected 1 parameter, %d received!", nargs);
return 0;
}
if (!IS_OBJECT(args[0])) {
cosmoV_error(state, "Expected iterable object, %s received!", cosmoV_typeStr(args[0]));
return 0;
}
CObjObject *obj = cosmoV_readObject(args[0]);
int index = cosmoO_getUserI(obj); // we store the index in the userdata
CValue val;
cosmoO_getIString(state, obj, ISTRING_RESERVED, &val);
if (!IS_TABLE(val)) {
return 0; // someone set the __reserved member to something else. this will exit the
// iterator loop
}
CObjTable *table = (CObjTable *)cosmoV_readRef(val);
// while the entry is invalid, go to the next entry
int cap = cosmoT_getCapacity(&table->tbl);
CTableEntry *entry;
do {
entry = &table->tbl.table[index++];
} while (IS_NIL(entry->key) && index < cap);
cosmoO_setUserI(obj, index); // update the userdata
if (index < cap &&
!IS_NIL(entry->key)) { // if the entry is valid, return it's key and value pair
cosmoV_pushValue(state, entry->key);
cosmoV_pushValue(state, entry->val);
return 2; // we pushed 2 values onto the stack for the return values
} else {
return 0; // we have nothing to return, this should exit the iterator loop
}
}
#define NUMBEROP(typeConst, op) \
StkPtr valA = cosmoV_getTop(state, 1); \
StkPtr valB = cosmoV_getTop(state, 0); \
if (IS_NUMBER(*valA) && IS_NUMBER(*valB)) { \
cosmoV_setTop(state, 2); /* pop the 2 values */ \
cosmoV_pushValue(state, typeConst(cosmoV_readNumber(*valA) op cosmoV_readNumber(*valB))); \
} else { \
cosmoV_error(state, "Expected numbers, got %s and %s!", cosmoV_typeStr(*valA), \
cosmoV_typeStr(*valB)); \
return -1; \
}
static inline uint8_t READBYTE(CCallFrame *frame)
{
return *frame->pc++;
}
static inline uint16_t READUINT(CCallFrame *frame)
{
frame->pc += 2;
return *(uint16_t *)(&frame->pc[-2]);
}
#ifdef VM_JUMPTABLE
# define DISPATCH goto *cosmoV_dispatchTable[READBYTE(frame)]
# define CASE(op) \
DISPATCH; \
JMP_##op
# define JMPLABEL(op) &&JMP_##op
# define SWITCH \
static void *cosmoV_dispatchTable[] = { \
JMPLABEL(OP_LOADCONST), JMPLABEL(OP_SETGLOBAL), JMPLABEL(OP_GETGLOBAL), \
JMPLABEL(OP_SETLOCAL), JMPLABEL(OP_GETLOCAL), JMPLABEL(OP_GETUPVAL), \
JMPLABEL(OP_SETUPVAL), JMPLABEL(OP_PEJMP), JMPLABEL(OP_EJMP), \
JMPLABEL(OP_JMP), JMPLABEL(OP_JMPBACK), JMPLABEL(OP_POP), \
JMPLABEL(OP_CALL), JMPLABEL(OP_CLOSURE), JMPLABEL(OP_CLOSE), \
JMPLABEL(OP_NEWTABLE), JMPLABEL(OP_NEWARRAY), JMPLABEL(OP_INDEX), \
JMPLABEL(OP_NEWINDEX), JMPLABEL(OP_NEWOBJECT), JMPLABEL(OP_SETOBJECT), \
JMPLABEL(OP_GETOBJECT), JMPLABEL(OP_GETMETHOD), JMPLABEL(OP_INVOKE), \
JMPLABEL(OP_ITER), JMPLABEL(OP_NEXT), JMPLABEL(OP_ADD), \
JMPLABEL(OP_SUB), JMPLABEL(OP_MULT), JMPLABEL(OP_DIV), \
JMPLABEL(OP_MOD), JMPLABEL(OP_POW), JMPLABEL(OP_NOT), \
JMPLABEL(OP_NEGATE), JMPLABEL(OP_COUNT), JMPLABEL(OP_CONCAT), \
JMPLABEL(OP_INCLOCAL), JMPLABEL(OP_INCGLOBAL), JMPLABEL(OP_INCUPVAL), \
JMPLABEL(OP_INCINDEX), JMPLABEL(OP_INCOBJECT), JMPLABEL(OP_EQUAL), \
JMPLABEL(OP_LESS), JMPLABEL(OP_GREATER), JMPLABEL(OP_LESS_EQUAL), \
JMPLABEL(OP_GREATER_EQUAL), JMPLABEL(OP_TRUE), JMPLABEL(OP_FALSE), \
JMPLABEL(OP_NIL), JMPLABEL(OP_RETURN), \
}; \
DISPATCH;
# define DEFAULT DISPATCH /* no-op */
#else
# define CASE(op) \
continue; \
case op
# define SWITCH switch (READBYTE(frame))
# define DEFAULT \
default: \
printf("[ERROR] unknown opcode!"); \
exit(0)
#endif
// returns -1 if panic
int cosmoV_execute(CState *state)
{
CCallFrame *frame = &state->callFrame[state->frameCount - 1]; // grabs the current frame
CValue *constants = frame->closure->function->chunk.constants.values; // cache the pointer :)
for (;;) {
#ifdef VM_DEBUG
cosmoV_printStack(state);
disasmInstr(&frame->closure->function->chunk,
frame->pc - frame->closure->function->chunk.buf, state->frameCount - 1);
printf("\n");
#endif
SWITCH
{
CASE(OP_LOADCONST) :
{ // push const[uint] to stack
uint16_t indx = READUINT(frame);
cosmoV_pushValue(state, constants[indx]);
}
CASE(OP_SETGLOBAL) :
{
uint16_t indx = READUINT(frame);
CValue ident = constants[indx]; // grabs identifier
CValue *val = cosmoT_insert(state, &state->globals->tbl, ident);
*val = *cosmoV_pop(state); // sets the value in the hash table
}
CASE(OP_GETGLOBAL) :
{
uint16_t indx = READUINT(frame);
CValue ident = constants[indx]; // grabs identifier
CValue val; // to hold our value
cosmoT_get(state, &state->globals->tbl, ident, &val);
cosmoV_pushValue(state, val); // pushes the value to the stack
}
CASE(OP_SETLOCAL) :
{
uint8_t indx = READBYTE(frame);
// set base to top of stack & pop
frame->base[indx] = *cosmoV_pop(state);
}
CASE(OP_GETLOCAL) :
{
uint8_t indx = READBYTE(frame);
cosmoV_pushValue(state, frame->base[indx]);
}
CASE(OP_GETUPVAL) :
{
uint8_t indx = READBYTE(frame);
cosmoV_pushValue(state, *frame->closure->upvalues[indx]->val);
}
CASE(OP_SETUPVAL) :
{
uint8_t indx = READBYTE(frame);
*frame->closure->upvalues[indx]->val = *cosmoV_pop(state);
}
CASE(OP_PEJMP) :
{ // pop equality jump
uint16_t offset = READUINT(frame);
if (isFalsey(cosmoV_pop(state))) { // pop, if the condition is false, jump!
frame->pc += offset;
}
}
CASE(OP_EJMP) :
{ // equality jump
uint16_t offset = READUINT(frame);
if (isFalsey(cosmoV_getTop(state, 0))) { // if the condition is false, jump!
frame->pc += offset;
}
}
CASE(OP_JMP) :
{ // jump
uint16_t offset = READUINT(frame);
frame->pc += offset;
}
CASE(OP_JMPBACK) :
{
uint16_t offset = READUINT(frame);
frame->pc -= offset;
}
CASE(OP_POP) :
{ // pops value off the stack
cosmoV_setTop(state, READBYTE(frame));
}
CASE(OP_CALL) :
{
uint8_t args = READBYTE(frame);
uint8_t nres = READBYTE(frame);
cosmoV_call(state, args, nres);
}
CASE(OP_CLOSURE) :
{
uint16_t index = READUINT(frame);
CObjFunction *func = cosmoV_readFunction(constants[index]);
CObjClosure *closure = cosmoO_newClosure(state, func);
cosmoV_pushRef(state, (CObj *)closure);
for (int i = 0; i < closure->upvalueCount; i++) {
uint8_t encoding = READBYTE(frame);
uint8_t index = READBYTE(frame);
if (encoding == OP_GETUPVAL) {
// capture upvalue from current frame's closure
closure->upvalues[i] = frame->closure->upvalues[index];
} else {
// capture local
closure->upvalues[i] = captureUpvalue(state, frame->base + index);
}
}
}
CASE(OP_CLOSE) :
{
closeUpvalues(state, state->top - 1);
cosmoV_pop(state);
}
CASE(OP_NEWTABLE) :
{
uint16_t pairs = READUINT(frame);
cosmoV_makeTable(state, pairs);
}
CASE(OP_NEWARRAY) :
{
uint16_t pairs = READUINT(frame);
StkPtr val;
CObjTable *newObj = cosmoO_newTable(state);
cosmoV_pushRef(state, (CObj *)newObj); // so our GC doesn't free our new table
for (int i = 0; i < pairs; i++) {
val = cosmoV_getTop(state, i + 1);
// set key/value pair
CValue *newVal =
cosmoT_insert(state, &newObj->tbl, cosmoV_newNumber(pairs - i - 1));
*newVal = *val;
}
// once done, pop everything off the stack + push new table
cosmoV_setTop(state, pairs + 1); // + 1 for our table
cosmoV_pushRef(state, (CObj *)newObj);
}
CASE(OP_INDEX) :
{
StkPtr key = cosmoV_getTop(state, 0); // key should be the top of the stack
StkPtr temp = cosmoV_getTop(state, 1); // after that should be the table
// sanity check
if (!IS_REF(*temp)) {
cosmoV_error(state, "Couldn't index type %s!", cosmoV_typeStr(*temp));
}
CObj *obj = cosmoV_readRef(*temp);
CObjObject *proto = cosmoO_grabProto(obj);
CValue val = cosmoV_newNil(); // to hold our value
if (proto != NULL) {
// check for __index metamethod
cosmoO_indexObject(state, proto, *key, &val);
} else if (obj->type == COBJ_TABLE) {
CObjTable *tbl = (CObjTable *)obj;
cosmoT_get(state, &tbl->tbl, *key, &val);
} else {
cosmoV_error(state, "No proto defined! Couldn't __index from type %s",
cosmoV_typeStr(*temp));
}
cosmoV_setTop(state, 2); // pops the table & the key
cosmoV_pushValue(state, val); // pushes the field result
}
CASE(OP_NEWINDEX) :
{
StkPtr value = cosmoV_getTop(state, 0); // value is at the top of the stack
StkPtr key = cosmoV_getTop(state, 1);
StkPtr temp = cosmoV_getTop(state, 2); // table is after the key
// sanity check
if (!IS_REF(*temp)) {
cosmoV_error(state, "Couldn't set index with type %s!", cosmoV_typeStr(*temp));
}
CObj *obj = cosmoV_readRef(*temp);
CObjObject *proto = cosmoO_grabProto(obj);
if (proto != NULL) {
cosmoO_newIndexObject(state, proto, *key, *value);
} else if (obj->type == COBJ_TABLE) {
CObjTable *tbl = (CObjTable *)obj;
CValue *newVal = cosmoT_insert(state, &tbl->tbl, *key);
*newVal = *value; // set the index
} else {
cosmoV_error(state, "No proto defined! Couldn't __newindex from type %s",
cosmoV_typeStr(*temp));
}
// pop everything off the stack
cosmoV_setTop(state, 3);
}
CASE(OP_NEWOBJECT) :
{
uint16_t pairs = READUINT(frame);
cosmoV_makeObject(state, pairs);
}
CASE(OP_SETOBJECT) :
{
StkPtr value = cosmoV_getTop(state, 0); // value is at the top of the stack
StkPtr temp = cosmoV_getTop(state, 1); // object is after the value
uint16_t ident = READUINT(frame); // use for the key
// sanity check
if (IS_REF(*temp)) {
cosmoV_rawset(state, cosmoV_readRef(*temp), constants[ident], *value);
} else {
CObjString *field = cosmoV_toString(state, constants[ident]);
cosmoV_error(state, "Couldn't set field '%s' on type %s!", field->str,
cosmoV_typeStr(*temp));
}
// pop everything off the stack
cosmoV_setTop(state, 2);
}
CASE(OP_GETOBJECT) :
{
CValue val = cosmoV_newNil(); // to hold our value
StkPtr temp = cosmoV_getTop(state, 0); // that should be the object
uint16_t ident = READUINT(frame); // use for the key
// sanity check
if (IS_REF(*temp)) {
cosmoV_rawget(state, cosmoV_readRef(*temp), constants[ident], &val);
} else {
CObjString *field = cosmoV_toString(state, constants[ident]);
cosmoV_error(state, "Couldn't get field '%s' from type %s!", field->str,
cosmoV_typeStr(*temp));
}
cosmoV_setTop(state, 1); // pops the object
cosmoV_pushValue(state, val); // pushes the field result
}
CASE(OP_GETMETHOD) :
{
CValue val = cosmoV_newNil(); // to hold our value
StkPtr temp = cosmoV_getTop(state, 0); // that should be the object
uint16_t ident = READUINT(frame); // use for the key
// this is almost identical to GETOBJECT, however cosmoV_getMethod is used instead
// of just cosmoV_get
if (IS_REF(*temp)) {
cosmoV_getMethod(state, cosmoV_readRef(*temp), constants[ident], &val);
} else {
CObjString *field = cosmoV_toString(state, constants[ident]);
cosmoV_error(state, "Couldn't get field '%s' from type %s!", field->str,
cosmoV_typeStr(*temp));
}
cosmoV_setTop(state, 1); // pops the object
cosmoV_pushValue(state, val); // pushes the field result
}
CASE(OP_INVOKE) :
{
uint8_t args = READBYTE(frame);
uint8_t nres = READBYTE(frame);
uint16_t ident = READUINT(frame);
StkPtr temp = cosmoV_getTop(state, args); // grabs object from stack
CValue val; // to hold our value
// sanity check
if (IS_REF(*temp)) {
// get the field from the object
cosmoV_rawget(state, cosmoV_readRef(*temp), constants[ident], &val);
// now invoke the method!
invokeMethod(state, cosmoV_readRef(*temp), val, args, nres, 1);
} else {
cosmoV_error(state, "Couldn't get from type %s!", cosmoV_typeStr(*temp));
}
}
CASE(OP_ITER) :
{
StkPtr temp = cosmoV_getTop(state, 0); // should be the object/table
if (!IS_REF(*temp)) {
cosmoV_error(state, "Couldn't iterate over non-iterator type %s!",
cosmoV_typeStr(*temp));
}
CObj *obj = cosmoV_readRef(*temp);
CObjObject *proto = cosmoO_grabProto(obj);
CValue val;
if (proto != NULL) {
// grab __iter & call it
if (cosmoO_getIString(state, proto, ISTRING_ITER, &val)) {
cosmoV_pop(state); // pop the object from the stack
cosmoV_pushValue(state, val);
cosmoV_pushRef(state, (CObj *)obj);
cosmoV_call(
state, 1,
1); // we expect 1 return value on the stack, the iterable object
StkPtr iObj = cosmoV_getTop(state, 0);
if (!IS_OBJECT(*iObj)) {
cosmoV_error(state,
"Expected iterable object! '__iter' returned %s, expected "
"<object>!",
cosmoV_typeStr(*iObj));
}
// get __next method and place it at the top of the stack
cosmoV_getMethod(state, cosmoV_readRef(*iObj),
cosmoV_newRef(state->iStrings[ISTRING_NEXT]), iObj);
} else {
cosmoV_error(state, "Expected iterable object! '__iter' not defined!");
}
} else if (obj->type == COBJ_TABLE) {
CObjTable *tbl = (CObjTable *)obj;
cosmoV_pushRef(state, (CObj *)state->iStrings[ISTRING_RESERVED]); // key
cosmoV_pushRef(state, (CObj *)tbl); // value
cosmoV_pushString(state, "__next"); // key
CObjCFunction *tbl_next = cosmoO_newCFunction(state, _tbl__next);
cosmoV_pushRef(state, (CObj *)tbl_next); // value
CObjObject *obj =
cosmoV_makeObject(state, 2); // pushes the new object to the stack
cosmoO_setUserI(obj, 0); // increment for iterator
// make our CObjMethod for OP_NEXT to call
CObjMethod *method =
cosmoO_newMethod(state, cosmoV_newRef(tbl_next), (CObj *)obj);
cosmoV_setTop(state, 2); // pops the object & the tbl
cosmoV_pushRef(state, (CObj *)method); // pushes the method for OP_NEXT
} else {
cosmoV_error(state, "No proto defined! Couldn't get from type %s",
cosmoO_typeStr(obj));
}
}
CASE(OP_NEXT) :
{
uint8_t nresults = READBYTE(frame);
uint16_t jump = READUINT(frame);
StkPtr temp = cosmoV_getTop(state, 0); // we don't actually pop this off the stack
if (!IS_METHOD(*temp)) {
cosmoV_error(state, "Expected '__next' to be a method, got type %s!",
cosmoV_typeStr(*temp));
}
cosmoV_pushValue(state, *temp);
cosmoV_call(state, 0, nresults);
if (IS_NIL(*(cosmoV_getTop(
state, 0)))) { // __next returned a nil, which means to exit the loop
cosmoV_setTop(state, nresults); // pop the return values
frame->pc += jump;
}
}
CASE(OP_ADD) :
{
// pop 2 values off the stack & try to add them together
NUMBEROP(cosmoV_newNumber, +);
}
CASE(OP_SUB) :
{
// pop 2 values off the stack & try to subtracts them
NUMBEROP(cosmoV_newNumber, -);
}
CASE(OP_MULT) :
{
// pop 2 values off the stack & try to multiplies them together
NUMBEROP(cosmoV_newNumber, *);
}
CASE(OP_DIV) :
{
// pop 2 values off the stack & try to divides them
NUMBEROP(cosmoV_newNumber, /);
}
CASE(OP_MOD) :
{
StkPtr valA = cosmoV_getTop(state, 1);
StkPtr valB = cosmoV_getTop(state, 0);
if (IS_NUMBER(*valA) && IS_NUMBER(*valB)) {
cosmoV_setTop(state, 2); /* pop the 2 values */
cosmoV_pushValue(state, cosmoV_newNumber(fmod(cosmoV_readNumber(*valA),
cosmoV_readNumber(*valB))));
} else {
cosmoV_error(state, "Expected numbers, got %s and %s!", cosmoV_typeStr(*valA),
cosmoV_typeStr(*valB));
}
}
CASE(OP_POW) :
{
StkPtr valA = cosmoV_getTop(state, 1);
StkPtr valB = cosmoV_getTop(state, 0);
if (IS_NUMBER(*valA) && IS_NUMBER(*valB)) {
cosmoV_setTop(state, 2); /* pop the 2 values */
cosmoV_pushValue(state, cosmoV_newNumber(pow(cosmoV_readNumber(*valA),
cosmoV_readNumber(*valB))));
} else {
cosmoV_error(state, "Expected numbers, got %s and %s!", cosmoV_typeStr(*valA),
cosmoV_typeStr(*valB));
}
}
CASE(OP_NOT) :
{
cosmoV_pushBoolean(state, isFalsey(cosmoV_pop(state)));
}
CASE(OP_NEGATE) :
{ // pop 1 value off the stack & try to negate
StkPtr val = cosmoV_getTop(state, 0);
if (IS_NUMBER(*val)) {
cosmoV_pop(state);
cosmoV_pushNumber(state, -(cosmoV_readNumber(*val)));
} else {
cosmoV_error(state, "Expected number, got %s!", cosmoV_typeStr(*val));
}
}
CASE(OP_COUNT) :
{
StkPtr temp = cosmoV_getTop(state, 0);
if (!IS_REF(*temp)) {
cosmoV_error(state, "Expected non-primitive, got %s!", cosmoV_typeStr(*temp));
}
int count = cosmoO_count(state, cosmoV_readRef(*temp));
cosmoV_pop(state);
cosmoV_pushNumber(state, count); // pushes the count onto the stack
}
CASE(OP_CONCAT) :
{
uint8_t vals = READBYTE(frame);
cosmoV_concat(state, vals);
}
CASE(OP_INCLOCAL) :
{ // this leaves the value on the stack
int8_t inc = READBYTE(frame) - 128; // amount we're incrementing by
uint8_t indx = READBYTE(frame);
StkPtr val = &frame->base[indx];
// check that it's a number value
if (IS_NUMBER(*val)) {
cosmoV_pushValue(state, *val); // pushes old value onto the stack :)
*val = cosmoV_newNumber(cosmoV_readNumber(*val) + inc);
} else {
cosmoV_error(state, "Expected number, got %s!", cosmoV_typeStr(*val));
}
}
CASE(OP_INCGLOBAL) :
{
int8_t inc = READBYTE(frame) - 128; // amount we're incrementing by
uint16_t indx = READUINT(frame);
CValue ident = constants[indx]; // grabs identifier
CValue *val = cosmoT_insert(state, &state->globals->tbl, ident);
// check that it's a number value
if (IS_NUMBER(*val)) {
cosmoV_pushValue(state, *val); // pushes old value onto the stack :)
*val = cosmoV_newNumber(cosmoV_readNumber(*val) + inc);
} else {
cosmoV_error(state, "Expected number, got %s!", cosmoV_typeStr(*val));
}
}
CASE(OP_INCUPVAL) :
{
int8_t inc = READBYTE(frame) - 128; // amount we're incrementing by
uint8_t indx = READBYTE(frame);
CValue *val = frame->closure->upvalues[indx]->val;
// check that it's a number value
if (IS_NUMBER(*val)) {
cosmoV_pushValue(state, *val); // pushes old value onto the stack :)
*val = cosmoV_newNumber(cosmoV_readNumber(*val) + inc);
} else {
cosmoV_error(state, "Expected number, got %s!", cosmoV_typeStr(*val));
}
}
CASE(OP_INCINDEX) :
{
int8_t inc = READBYTE(frame) - 128; // amount we're incrementing by
StkPtr temp = cosmoV_getTop(state, 1); // object should be above the key
StkPtr key = cosmoV_getTop(state, 0); // grabs key
if (!IS_REF(*temp)) {
cosmoV_error(state, "Couldn't index non-indexable type %s!",
cosmoV_typeStr(*temp));
}
CObj *obj = cosmoV_readRef(*temp);
CObjObject *proto = cosmoO_grabProto(obj);
CValue val;
// call __index if the proto was found
if (proto != NULL) {
cosmoO_indexObject(state, proto, *key, &val);
if (!IS_NUMBER(val)) {
cosmoV_error(state, "Expected number, got %s!", cosmoV_typeStr(val));
}
cosmoV_pushValue(state, val); // pushes old value onto the stack :)
// call __newindex
cosmoO_newIndexObject(state, proto, *key,
cosmoV_newNumber(cosmoV_readNumber(val) + inc));
} else if (obj->type == COBJ_TABLE) {
CObjTable *tbl = (CObjTable *)obj;
CValue *val = cosmoT_insert(state, &tbl->tbl, *key);
if (!IS_NUMBER(*val)) {
cosmoV_error(state, "Expected number, got %s!", cosmoV_typeStr(*val));
}
// pops tbl & key from stack
cosmoV_setTop(state, 2);
cosmoV_pushValue(state, *val); // pushes old value onto the stack :)
*val = cosmoV_newNumber(cosmoV_readNumber(*val) + inc); // sets table index
} else {
cosmoV_error(state, "No proto defined! Couldn't __index from type %s",
cosmoV_typeStr(*temp));
}
}
CASE(OP_INCOBJECT) :
{
int8_t inc = READBYTE(frame) - 128; // amount we're incrementing by
uint16_t indx = READUINT(frame);
StkPtr temp = cosmoV_getTop(state, 0); // object should be at the top of the stack
CValue ident = constants[indx]; // grabs identifier
// sanity check
if (IS_REF(*temp)) {
CObj *obj = cosmoV_readRef(*temp);
CValue val;
cosmoV_rawget(state, obj, ident, &val);
// pop the object off the stack
cosmoV_pop(state);
// check that it's a number value
if (IS_NUMBER(val)) {
cosmoV_pushValue(state, val); // pushes old value onto the stack :)
cosmoV_rawset(state, obj, ident,
cosmoV_newNumber(cosmoV_readNumber(val) + inc));
} else {
cosmoV_error(state, "Expected number, got %s!", cosmoV_typeStr(val));
}
} else {
cosmoV_error(state, "Couldn't set a field on type %s!", cosmoV_typeStr(*temp));
}
}
CASE(OP_EQUAL) :
{
// pop vals
StkPtr valB = cosmoV_pop(state);
StkPtr valA = cosmoV_pop(state);
// compare & push
cosmoV_pushBoolean(state, cosmoV_equal(state, *valA, *valB));
}
CASE(OP_LESS) :
{
NUMBEROP(cosmoV_newBoolean, <);
}
CASE(OP_GREATER) :
{
NUMBEROP(cosmoV_newBoolean, >);
}
CASE(OP_LESS_EQUAL) :
{
NUMBEROP(cosmoV_newBoolean, <=);
}
CASE(OP_GREATER_EQUAL) :
{
NUMBEROP(cosmoV_newBoolean, >=);
}
CASE(OP_TRUE) : cosmoV_pushBoolean(state, true);
CASE(OP_FALSE) : cosmoV_pushBoolean(state, false);
CASE(OP_NIL) : cosmoV_pushValue(state, cosmoV_newNil());
CASE(OP_RETURN) :
{
uint8_t res = READBYTE(frame);
return res;
}
DEFAULT;
}
}
return -1;
}
#undef NUMBEROP