Files
Noja/src/runtime/runtime.c
T

1136 lines
22 KiB
C

#include <stdlib.h>
#include "../utils/defs.h"
#include "../utils/stack.h"
#include "runtime.h"
#define MAX_FRAME_STACK 16
#define MAX_FRAMES 16
typedef struct Frame Frame;
struct Frame {
Frame *prev;
Object *vars;
Executable *exe;
int index, used;
};
struct xRuntime {
void *callback_userp;
_Bool (*callback_addr)(Runtime*, void*);
Object *builtins;
int depth;
Frame *frame;
Stack *stack;
Heap *heap;
};
Stack *Runtime_GetStack(Runtime *runtime)
{
return Stack_Copy(runtime->stack, 1);
}
Heap *Runtime_GetHeap(Runtime *runtime)
{
return runtime->heap;
}
int Runtime_GetCurrentIndex(Runtime *runtime)
{
if(runtime->depth == 0)
return -1;
else
return runtime->frame->index;
}
Executable *Runtime_GetCurrentExecutable(Runtime *runtime)
{
if(runtime->depth == 0)
return NULL;
else
return runtime->frame->exe;
}
Runtime *Runtime_New(int stack_size, int heap_size, void *callback_userp, _Bool (*callback_addr)(Runtime*, void*))
{
if(stack_size < 0)
stack_size = 1024;
if(heap_size < 0)
heap_size = 65536;
Runtime *runtime;
{
runtime = malloc(sizeof(Runtime));
if(runtime == NULL)
return NULL;
runtime->heap = Heap_New(heap_size);
if(runtime->heap == NULL)
{
free(runtime);
return NULL;
}
runtime->stack = Stack_New(stack_size);
if(runtime->stack == NULL)
{
Heap_Free(runtime->heap);
free(runtime);
}
runtime->callback_userp = callback_userp;
runtime->callback_addr = callback_addr;
runtime->builtins = NULL;
runtime->frame = NULL;
runtime->depth = 0;
}
return runtime;
}
void Runtime_Free(Runtime *runtime)
{
Heap_Free(runtime->heap);
Stack_Free(runtime->stack);
free(runtime);
}
Object *Runtime_GetBuiltins(Runtime *runtime)
{
return runtime->builtins;
}
void Runtime_SetBuiltins(Runtime *runtime, Object *builtins)
{
runtime->builtins = builtins;
}
_Bool Runtime_Push(Runtime *runtime, Error *error, Object *obj)
{
assert(runtime != NULL);
assert(error != NULL);
assert(obj != NULL);
if(runtime->depth == 0)
{
Error_Report(error, 0, "There are no frames on the stack");
return 0;
}
assert(runtime->frame->used <= MAX_FRAME_STACK);
if(runtime->frame->used == MAX_FRAME_STACK)
{
Error_Report(error, 0, "Frame stack limit of %d reached", MAX_FRAME_STACK);
return 0;
}
if(!Stack_Push(runtime->stack, obj))
{
Error_Report(error, 0, "Out of stack");
return 0;
}
runtime->frame->used += 1;
return 1;
}
_Bool Runtime_Pop(Runtime *runtime, Error *error, unsigned int n)
{
assert(runtime != NULL);
assert(error != NULL);
if(runtime->depth == 0)
{
Error_Report(error, 0, "There are no frames on the stack");
return 0;
}
assert(runtime->frame->used >= 0);
if((unsigned int) runtime->frame->used < n)
{
Error_Report(error, 0, "Frame has not enough values on the stack");
return 0;
}
// The frame has something on the stack,
// this means that the stack isn't empty
// and popping won't fail.
(void) Stack_Pop(runtime->stack, n);
runtime->frame->used -= n;
assert(runtime->frame->used >= 0);
return 1;
}
typedef struct {
Executable *exe;
int index;
} SnapshotNode;
struct xSnapshot {
int depth;
SnapshotNode nodes[];
};
Snapshot *Snapshot_New(Runtime *runtime)
{
assert(runtime->depth >= 0);
Snapshot *snapshot = malloc(sizeof(Snapshot) + sizeof(SnapshotNode) * runtime->depth);
if(snapshot == NULL)
return NULL;
{
Frame *f = runtime->frame;
snapshot->depth = 0;
while(snapshot->depth < runtime->depth)
{
assert(f != NULL);
SnapshotNode *node = snapshot->nodes + snapshot->depth;
node->exe = Executable_Copy(f->exe);
node->index = f->index;
if(node->exe == NULL)
goto abort;
f = f->prev;
snapshot->depth += 1;
}
assert(f == NULL);
}
return snapshot;
abort:
Snapshot_Free(snapshot);
return NULL;
}
void Snapshot_Free(Snapshot *snapshot)
{
for(int i = 0; i < snapshot->depth; i += 1)
{
Executable *exe = snapshot->nodes[i].exe;
Executable_Free(exe);
}
free(snapshot);
}
void Snapshot_Print(Snapshot *snapshot, FILE *fp)
{
assert(snapshot != NULL);
assert(fp != NULL);
fprintf(fp, "Stack trace:\n");
for(int i = 0; i < snapshot->depth; i += 1)
{
SnapshotNode node = snapshot->nodes[i];
Executable *exe = node.exe;
Source *src = Executable_GetSource(exe);
const char *name;
{
name = NULL;
if(src != NULL)
name = Source_GetName(src);
if(name == NULL)
name = "(unnamed)";
}
int line;
{
if(src == NULL)
line = 0;
else
{
line = 1;
const char *body = Source_GetBody(src);
int offset = Executable_GetInstrOffset(exe, node.index);
int i = 0;
while(i < offset)
{
if(body[i] == '\n')
line += 1;
i += 1;
}
}
}
if(line == 0)
fprintf(fp, "\t#%d %s\n", i, name);
else
fprintf(fp, "\t#%d %s:%d\n", i, name, line);
}
//fprintf(fp, " (Snapshot can't be printed yet)\n");
}
static Object *do_math_op(Object *lop, Object *rop, Opcode opcode, Heap *heap, Error *error)
{
assert(lop != NULL);
assert(rop != NULL);
#define APPLY(x, y, z, id) \
switch(opcode) \
{ \
case OPCODE_ADD: (z) = (x) + (y); break; \
case OPCODE_SUB: (z) = (x) - (y); break; \
case OPCODE_MUL: (z) = (x) * (y); break; \
case OPCODE_DIV: (z) = (x) / (y); break; \
default: assert(0); break; \
}
Object *res;
if(Object_IsInt(lop))
{
long long int raw_lop = Object_ToInt(lop, error);
if(error->occurred)
return NULL;
if(Object_IsInt(rop))
{
// int + int
long long int raw_rop = Object_ToInt(rop, error);
if(error->occurred)
return NULL;
long long int raw_res;
APPLY(raw_lop, raw_rop, raw_res, id)
res = Object_FromInt(raw_res, heap, error);
}
else if(Object_IsFloat(rop))
{
// int + float
double raw_rop = Object_ToFloat(rop, error);
if(error->occurred)
return NULL;
double raw_res;
APPLY((double) raw_lop, raw_rop, raw_res, id)
res = Object_FromFloat(raw_res, heap, error);
}
else
{
Error_Report(error, 0, "Arithmetic operation on a non-numeric object");
return NULL;
}
}
else if(Object_IsFloat(lop))
{
double raw_lop = Object_ToFloat(lop, error);
if(error->occurred)
return NULL;
if(Object_IsInt(rop))
{
// float + int
long long int raw_rop = Object_ToInt(rop, error);
if(error->occurred)
return NULL;
double raw_res;
APPLY(raw_lop, (double) raw_rop, raw_res, id)
res = Object_FromFloat(raw_res, heap, error);
}
else if(Object_IsFloat(rop))
{
// float + float
double raw_rop = Object_ToFloat(rop, error);
if(error->occurred)
return NULL;
double raw_res;
APPLY(raw_lop, raw_rop, raw_res, id)
res = Object_FromFloat(raw_res, heap, error);
}
else
{
Error_Report(error, 0, "Arithmetic operation on a non-numeric object");
return NULL;
}
}
else
{
Error_Report(error, 0, "Arithmetic operation on a non-numeric object");
return NULL;
}
#undef APPLY
return res;
}
static Object *do_relational_op(Object *lop, Object *rop, Opcode opcode, Heap *heap, Error *error)
{
assert(lop != NULL);
assert(rop != NULL);
#define APPLY(x, y, z, id) \
switch(opcode) \
{ \
case OPCODE_LSS: (z) = (x) < (y); break; \
case OPCODE_GRT: (z) = (x) > (y); break; \
case OPCODE_LEQ: (z) = (x) <= (y); break; \
case OPCODE_GEQ: (z) = (x) >= (y); break; \
default: assert(0); break; \
}
_Bool res;
if(Object_IsInt(lop))
{
long long int raw_lop = Object_ToInt(lop, error);
if(error->occurred)
return NULL;
if(Object_IsInt(rop))
{
// int + int
long long int raw_rop = Object_ToInt(rop, error);
if(error->occurred)
return NULL;
APPLY(raw_lop, raw_rop, res, id)
}
else if(Object_IsFloat(rop))
{
// int + float
double raw_rop = Object_ToFloat(rop, error);
if(error->occurred)
return NULL;
APPLY((double) raw_lop, raw_rop, res, id)
}
else
{
Error_Report(error, 0, "Relational operation on a non-numeric object");
return NULL;
}
}
else if(Object_IsFloat(lop))
{
double raw_lop = Object_ToFloat(lop, error);
if(error->occurred)
return NULL;
if(Object_IsInt(rop))
{
// float + int
long long int raw_rop = Object_ToInt(rop, error);
if(error->occurred)
return NULL;
APPLY(raw_lop, (double) raw_rop, res, id)
}
else if(Object_IsFloat(rop))
{
// float + float
double raw_rop = Object_ToFloat(rop, error);
if(error->occurred)
return NULL;
APPLY(raw_lop, raw_rop, res, id)
}
else
{
Error_Report(error, 0, "Relational operation on a non-numeric object");
return NULL;
}
}
else
{
Error_Report(error, 0, "Relational operation on a non-numeric object");
return NULL;
}
#undef APPLY
return Object_FromBool(res, heap, error);
}
static _Bool step(Runtime *runtime, Error *error)
{
assert(runtime != NULL);
Opcode opcode;
Operand ops[3];
int opc = sizeof(ops) / sizeof(ops[0]);
if(!Executable_Fetch(runtime->frame->exe, runtime->frame->index, &opcode, ops, &opc))
{
Error_Report(error, 1, "Invalid instruction index");
return 0;
}
/*
#warning "TEMPORARY"
{
int index = runtime->frame->index;
Executable *exe = runtime->frame->exe;
Source *src = Executable_GetSource(exe);
int offset = Executable_GetInstrOffset(exe, index);
int length = Executable_GetInstrLength(exe, index);
const char *str = Source_GetBody(src);
printf("Executing [%.*s]\n", length, str + offset);
}
*/
runtime->frame->index += 1;
switch(opcode)
{
case OPCODE_NOPE:
// Do nothing.
return 1;
case OPCODE_POS:
Error_Report(error, 1, "POS not implemented");
return 0;
case OPCODE_NEG:
Error_Report(error, 1, "NEG not implemented");
return 0;
case OPCODE_ADD:
case OPCODE_SUB:
case OPCODE_MUL:
case OPCODE_DIV:
{
assert(opc == 0);
Object *rop = Stack_Top(runtime->stack, 0);
Object *lop = Stack_Top(runtime->stack, -1);
if(!Runtime_Pop(runtime, error, 2))
return 0;
// We managed to pop rop and lop,
// so we know they're not NULL.
assert(rop != NULL);
assert(lop != NULL);
Object *res = do_math_op(lop, rop, opcode, runtime->heap, error);
if(res == NULL)
return 0;
if(!Runtime_Push(runtime, error, res))
return 0;
break;
}
case OPCODE_EQL:
case OPCODE_NQL:
{
assert(opc == 0);
Object *rop = Stack_Top(runtime->stack, 0);
Object *lop = Stack_Top(runtime->stack, -1);
if(!Runtime_Pop(runtime, error, 2))
return 0;
// We managed to pop rop and lop,
// so we know they're not NULL.
assert(rop != NULL);
assert(lop != NULL);
_Bool rawres = Object_Compare(lop, rop, error);
if(error->occurred == 1)
return 0;
if(opcode == OPCODE_NQL)
rawres = !rawres;
Object *res = Object_FromBool(rawres, runtime->heap, error);
if(res == NULL)
return 0;
if(!Runtime_Push(runtime, error, res))
return 0;
return 1;
}
case OPCODE_LSS:
case OPCODE_GRT:
case OPCODE_LEQ:
case OPCODE_GEQ:
{
assert(opc == 0);
Object *rop = Stack_Top(runtime->stack, 0);
Object *lop = Stack_Top(runtime->stack, -1);
if(!Runtime_Pop(runtime, error, 2))
return 0;
// We managed to pop rop and lop,
// so we know they're not NULL.
assert(rop != NULL);
assert(lop != NULL);
Object *res = do_relational_op(lop, rop, opcode, runtime->heap, error);
if(res == NULL)
return 0;
if(!Runtime_Push(runtime, error, res))
return 0;
return 1;
}
case OPCODE_ASS:
{
assert(opc == 1);
assert(ops[0].type == OPTP_STRING);
if(runtime->frame->used == 0)
{
Error_Report(error, 0, "Frame has not enough values on the stack");
return 0;
}
Object *val = Stack_Top(runtime->stack, 0);
assert(val != NULL);
Object *key = Object_FromString(ops[0].as_string, -1, runtime->heap, error);
if(key == NULL)
return 0;
if(!Object_Insert(runtime->frame->vars, key, val, runtime->heap, error))
return 0;
return 1;
}
case OPCODE_POP:
{
assert(opc == 1);
if(!Runtime_Pop(runtime, error, ops[0].as_int))
return 0;
return 1;
}
case OPCODE_CALL:
{
assert(opc == 1);
assert(ops[0].type == OPTP_INT);
int argc = ops[0].as_int;
assert(argc >= 0);
if(runtime->frame->used < argc + 1)
{
Error_Report(error, 1, "Frame doesn't own enough objects to execute call");
return 0;
}
Object *callable = Stack_Top(runtime->stack, 0);
assert(callable != NULL);
Object *argv[8];
int max_argc = sizeof(argv) / sizeof(argv[0]);
if(argc > max_argc)
{
Error_Report(error, 1, "Static buffer only allows function calls with up to %d arguments", max_argc);
return 0;
}
for(int i = 0; i < argc; i += 1)
{
argv[i] = Stack_Top(runtime->stack, -(i+1));
assert(argv[i] != NULL);
}
(void) Runtime_Pop(runtime, error, argc+1);
assert(error->occurred == 0);
Object *obj = Object_Call(callable, argv, argc, runtime->heap, error);
if(obj == NULL)
return 0;
if(!Runtime_Push(runtime, error, obj))
return 0;
return 1;
}
case OPCODE_SELECT:
{
assert(opc == 0);
if(runtime->frame->used < 2)
{
Error_Report(error, 1, "Frame has not enough values on the stack to run SELECT instruction");
return NULL;
}
Object *col = Stack_Top(runtime->stack, -1);
Object *key = Stack_Top(runtime->stack, 0);
assert(col != NULL && key != NULL);
if(!Runtime_Pop(runtime, error, 2))
return 0;
Object *val = Object_Select(col, key, runtime->heap, error);
if(val == NULL)
return 0;
if(!Runtime_Push(runtime, error, val))
return 0;
return 1;
}
case OPCODE_INSERT:
{
assert(opc == 0);
if(runtime->frame->used < 3)
{
Error_Report(error, 1, "Frame has not enough values on the stack to run INSERT instruction");
return NULL;
}
Object *col = Stack_Top(runtime->stack, -2);
Object *key = Stack_Top(runtime->stack, -1);
Object *val = Stack_Top(runtime->stack, 0);
assert(col != NULL && key != NULL && val != NULL);
if(!Runtime_Pop(runtime, error, 2))
return 0;
if(!Object_Insert(col, key, val, runtime->heap, error))
return 0;
return 1;
}
case OPCODE_INSERT2:
{
assert(opc == 0);
if(runtime->frame->used < 3)
{
Error_Report(error, 1, "Frame has not enough values on the stack to run INSERT2 instruction");
return NULL;
}
Object *val = Stack_Top(runtime->stack, -2);
Object *col = Stack_Top(runtime->stack, -1);
Object *key = Stack_Top(runtime->stack, 0);
assert(col != NULL && key != NULL && val != NULL);
if(!Runtime_Pop(runtime, error, 2))
return 0;
if(!Object_Insert(col, key, val, runtime->heap, error))
return 0;
return 1;
}
case OPCODE_PUSHINT:
{
assert(opc == 1);
assert(ops[0].type == OPTP_INT);
Object *obj = Object_FromInt(ops[0].as_int, runtime->heap, error);
if(obj == NULL)
return 0;
if(!Runtime_Push(runtime, error, obj))
return 0;
return 1;
}
case OPCODE_PUSHFLT:
{
assert(opc == 1);
assert(ops[0].type == OPTP_FLOAT);
Object *obj = Object_FromFloat(ops[0].as_float, runtime->heap, error);
if(obj == NULL)
return 0;
if(!Runtime_Push(runtime, error, obj))
return 0;
return 1;
}
case OPCODE_PUSHSTR:
{
assert(opc == 1);
assert(ops[0].type == OPTP_STRING);
Object *obj = Object_FromString(ops[0].as_string, -1, runtime->heap, error);
if(obj == NULL)
return 0;
if(!Runtime_Push(runtime, error, obj))
return 0;
return 1;
}
case OPCODE_PUSHVAR:
{
assert(opc == 1);
assert(ops[0].type == OPTP_STRING);
Object *key = Object_FromString(ops[0].as_string, -1, runtime->heap, error);
if(key == NULL)
return 0;
Object *obj = Object_Select(runtime->frame->vars, key, runtime->heap, error);
if(obj == NULL && error->occurred == 0)
{
// Variable not defined locally.
Object *builtins = Runtime_GetBuiltins(runtime);
if(builtins != NULL)
obj = Object_Select(builtins, key, runtime->heap, error);
if(obj == NULL)
{
if(error->occurred == 0)
// There's no such variable.
Error_Report(error, 1, "Reference to undefined variable \"%s\"", ops[0].as_string);
return 0;
}
}
else
if(obj == NULL)
return 0;
if(!Runtime_Push(runtime, error, obj))
return 0;
return 1;
}
case OPCODE_PUSHNNE:
{
assert(opc == 0);
Object *obj = Object_NewNone(runtime->heap, error);
if(obj == NULL)
return 0;
if(!Runtime_Push(runtime, error, obj))
return 0;
return 1;
}
case OPCODE_PUSHTRU:
{
assert(opc == 0);
Object *obj = Object_FromBool(1, runtime->heap, error);
if(obj == NULL)
return 0;
if(!Runtime_Push(runtime, error, obj))
return 0;
return 1;
}
case OPCODE_PUSHFLS:
{
assert(opc == 0);
Object *obj = Object_FromBool(0, runtime->heap, error);
if(obj == NULL)
return 0;
if(!Runtime_Push(runtime, error, obj))
return 0;
return 1;
}
case OPCODE_PUSHFUN:
{
assert(opc == 2);
assert(ops[0].type == OPTP_INT);
assert(ops[1].type == OPTP_INT);
Object *obj = Object_FromNojaFunction(runtime, runtime->frame->exe, ops[0].as_int, ops[1].as_int, runtime->heap, error);
if(obj == NULL)
return 0;
if(!Runtime_Push(runtime, error, obj))
return 0;
return 1;
}
case OPCODE_PUSHLST:
{
assert(opc == 1);
assert(ops[0].type == OPTP_INT);
Object *obj = Object_NewList(ops[0].as_int, runtime->heap, error);
if(obj == NULL)
return 0;
if(!Runtime_Push(runtime, error, obj))
return 0;
return 1;
}
case OPCODE_PUSHMAP:
{
assert(opc == 1);
assert(ops[0].type == OPTP_INT);
Object *obj = Object_NewMap(ops[0].as_int, runtime->heap, error);
if(obj == NULL)
return 0;
if(!Runtime_Push(runtime, error, obj))
return 0;
return 1;
}
case OPCODE_RETURN:
return 0;
case OPCODE_JUMP:
assert(opc == 1);
assert(ops[0].type == OPTP_INT);
runtime->frame->index = ops[0].as_int;
return 1;
case OPCODE_JUMPIFANDPOP:
{
assert(opc == 1);
assert(ops[0].type == OPTP_INT);
long long int target = ops[0].as_int;
if(runtime->frame->used == 0)
{
Error_Report(error, 1, "Frame doesn't have enough items on the stack to execute JUMPIFNOTANDPOP");
return 0;
}
Object *top = Stack_Top(runtime->stack, 0);
if(!Runtime_Pop(runtime, error, 1))
return 0;
assert(top != NULL);
if(!Object_IsBool(top))
{
Error_Report(error, 1, "JUMPIFNOTANDPOP expected a boolean on the stack");
return 0;
}
if(Object_ToBool(top, error)) // This can't fail because we know it's a bool.
runtime->frame->index = target;
return 1;
}
case OPCODE_JUMPIFNOTANDPOP:
{
assert(opc == 1);
assert(ops[0].type == OPTP_INT);
long long int target = ops[0].as_int;
if(runtime->frame->used == 0)
{
Error_Report(error, 1, "Frame doesn't have enough items on the stack to execute JUMPIFNOTANDPOP");
return 0;
}
Object *top = Stack_Top(runtime->stack, 0);
if(!Runtime_Pop(runtime, error, 1))
return 0;
assert(top != NULL);
if(!Object_IsBool(top))
{
Error_Report(error, 1, "JUMPIFNOTANDPOP expected a boolean on the stack");
return 0;
}
if(!Object_ToBool(top, error)) // This can't fail because we know it's a bool.
runtime->frame->index = target;
return 1;
}
default:
UNREACHABLE;
return 0;
}
return 1;
}
Object *run(Runtime *runtime, Error *error, Executable *exe, int index, Object **argv, int argc)
{
assert(runtime != NULL);
assert(error != NULL);
assert(exe != NULL);
assert(index >= 0);
assert(argc >= 0);
if(runtime->depth == MAX_FRAMES)
{
Error_Report(error, 1, "Maximum nested call limit of %d was reached", MAX_FRAMES);
return NULL;
}
assert(runtime->depth < MAX_FRAMES);
// Initialize the frame.
Frame frame;
{
frame.prev = NULL;
frame.vars = Object_NewMap(-1, runtime->heap, error);
frame.exe = Executable_Copy(exe);
frame.index = index;
frame.used = 0;
if(frame.vars == NULL)
return NULL;
if(frame.exe == NULL)
{
Error_Report(error, 1, "Failed to copy executable");
return NULL;
}
// Add the frame to the runtime.
frame.prev = runtime->frame;
runtime->frame = &frame;
runtime->depth += 1;
}
// Push the initial values of the frame.
for(int i = 0; i < argc; i += 1)
if(!Runtime_Push(runtime, error, argv[i]))
goto cleanup;
// Run the code.
if(runtime->callback_addr != NULL)
{
if(!runtime->callback_addr(runtime, runtime->callback_userp))
Error_Report(error, 0, "Forced abortion");
else
while(step(runtime, error))
if(!runtime->callback_addr(runtime, runtime->callback_userp))
{
Error_Report(error, 0, "Forced abortion");
break;
}
}
else
while(step(runtime, error));
// This is what the function will return.
Object *result = NULL;
// If an error occurred, we want to return NULL.
if(error->occurred == 0)
{
// If the step function left something
// on the stack, we return that. If it
// didn't, we return some other default
// value like "none".
if(frame.used == 0)
{
// Nothing to return on the stack. Set to none.
result = Object_NewNone(runtime->heap, error);
}
else
{
result = Stack_Top(runtime->stack, 0);
assert(result != NULL);
}
}
cleanup:
// Remove the frame-owned items from the stack.
// This can't fail.
(void) Stack_Pop(runtime->stack, frame.used);
// Deinitialize the frame.
{
// Remove the frame from the runtime.
runtime->frame = runtime->frame->prev;
runtime->depth -= 1;
// Deallocate the fields.
Executable_Free(frame.exe);
}
return result;
}