1287 lines
32 KiB
C
1287 lines
32 KiB
C
#include <time.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "runtime.h"
|
|
#include "utils/defs.h"
|
|
#include "utils/path.h"
|
|
#include "compiler/compile.h"
|
|
#include "assembler/assemble.h"
|
|
|
|
static int runExecutableAtIndex(Runtime *runtime, Error *error,
|
|
Executable *exe, int index,
|
|
Object *closure,
|
|
Object *rets[static MAX_RETS],
|
|
Object *argv[], int argc);
|
|
|
|
typedef struct {
|
|
Object base;
|
|
const char *name;
|
|
Runtime *runtime;
|
|
Executable *exe;
|
|
int index, argc;
|
|
Object *closure;
|
|
TimingID timing_id;
|
|
} FunctionObject;
|
|
|
|
static _Bool func_free(Object *self, Error *error)
|
|
{
|
|
(void) error;
|
|
|
|
FunctionObject *func = (FunctionObject*) self;
|
|
Executable_Free(func->exe);
|
|
return 1;
|
|
}
|
|
|
|
static void func_walk(Object *self, void (*callback)(Object **referer, void *userp), void *userp)
|
|
{
|
|
FunctionObject *func = (FunctionObject*) self;
|
|
callback(&func->closure, userp);
|
|
}
|
|
|
|
static int func_call(Object *self, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Heap *heap, Error *error)
|
|
{
|
|
ASSERT(self != NULL && heap != NULL && error != NULL);
|
|
|
|
FunctionObject *func = (FunctionObject*) self;
|
|
|
|
ASSERT(func->exe != NULL);
|
|
ASSERT(func->argc >= 0);
|
|
ASSERT(func->index >= 0);
|
|
|
|
// Make sure the right amount of arguments is provided.
|
|
|
|
Object **argv2;
|
|
|
|
int expected_argc = func->argc;
|
|
|
|
if(expected_argc < (int) argc)
|
|
{
|
|
// Nothing to be done. By using
|
|
// the right argc the additional
|
|
// arguments are ignored implicitly.
|
|
argv2 = argv;
|
|
}
|
|
else if(expected_argc > (int) argc)
|
|
{
|
|
// Some arguments are missing.
|
|
argv2 = malloc(sizeof(Object*) * expected_argc);
|
|
|
|
if(argv2 == NULL)
|
|
{
|
|
Error_Report(error, ErrorType_INTERNAL, "No memory");
|
|
return -1;
|
|
}
|
|
|
|
// Copy the provided arguments.
|
|
for(int i = 0; i < (int) argc; i += 1)
|
|
argv2[i] = argv[i];
|
|
|
|
// Set the unspecified arguments to none.
|
|
for(int i = argc; i < expected_argc; i += 1)
|
|
{
|
|
argv2[i] = Object_NewNone(heap, error);
|
|
|
|
if(argv2[i] == NULL)
|
|
return -1;
|
|
}
|
|
}
|
|
else
|
|
// The right amount of arguments was provided.
|
|
argv2 = argv;
|
|
|
|
clock_t begin;
|
|
TimingID timing_id;
|
|
TimingTable *timing_table = Runtime_GetTimingTable(func->runtime);
|
|
if (timing_table != NULL) {
|
|
begin = clock();
|
|
|
|
// Need to save the object's member
|
|
// before the run function since it
|
|
// may trigger a GC cycle invalidating
|
|
// the object pointer.
|
|
timing_id = func->timing_id;
|
|
}
|
|
|
|
int retc = runExecutableAtIndex(func->runtime, error, func->exe, func->index, func->closure, rets, argv2, expected_argc);
|
|
|
|
if (timing_table != NULL) {
|
|
double time = (double) (clock() - begin) / CLOCKS_PER_SEC;
|
|
TimingTable_sumCallTime(timing_table, timing_id, time);
|
|
}
|
|
|
|
// NOTE: Every object reference is invalidated from here.
|
|
|
|
if(argv2 != argv)
|
|
free(argv2);
|
|
|
|
return retc;
|
|
}
|
|
|
|
static TypeObject t_func = {
|
|
.base = (Object) { .type = &t_type, .flags = Object_STATIC },
|
|
.name = "function",
|
|
.size = sizeof (FunctionObject),
|
|
.call = func_call,
|
|
.walk = func_walk,
|
|
.free = func_free,
|
|
};
|
|
|
|
/* Symbol: Object_FromNojaFunction
|
|
*
|
|
* Creates an object from a noja executable structure.
|
|
*
|
|
* Args:
|
|
* - runtime: The reference to an instanciated Runtime.
|
|
*
|
|
* - exe: A noja executable.
|
|
*
|
|
* - index: The index of the first bytecode instruction
|
|
* of the noja function within the executable.
|
|
*
|
|
* - argc: The number of arguments the function expects.
|
|
* It must be positive (unlike [Object_FromNativeFunction],
|
|
* where -1 means variadic).
|
|
*
|
|
* - closure: An object containing variables that will be
|
|
* accessible from the noja function other than
|
|
* the ones that will be defined inside it.
|
|
*
|
|
* - heap: The heap that will be used to allocate the object.
|
|
* It can't be NULL.
|
|
*
|
|
* - error: Output parameter where error information is stored.
|
|
* It can't be NULL.
|
|
*
|
|
* Returns:
|
|
* The newly created object. If an error occurred, NULL is returned
|
|
* and information about the error is stored in the [error] argument.
|
|
*/
|
|
Object *Object_FromNojaFunction(Runtime *runtime, const char *name, Executable *exe, int index, int argc, Object *closure, Heap *heap, Error *error)
|
|
{
|
|
ASSERT(runtime != NULL);
|
|
ASSERT(exe != NULL);
|
|
ASSERT(index >= 0);
|
|
ASSERT(argc >= 0);
|
|
ASSERT(heap != NULL);
|
|
ASSERT(error != NULL);
|
|
|
|
FunctionObject *func = (FunctionObject*) Heap_Malloc(heap, &t_func, error);
|
|
|
|
if(func == NULL)
|
|
return NULL;
|
|
|
|
Executable *exe_copy = Executable_Copy(exe);
|
|
|
|
if(exe_copy == NULL)
|
|
{
|
|
Error_Report(error, ErrorType_INTERNAL, "Failed to copy executable");
|
|
return NULL;
|
|
}
|
|
|
|
func->runtime = runtime;
|
|
func->name = name; // Should this be copied?
|
|
func->exe = exe_copy;
|
|
func->index = index;
|
|
func->argc = argc;
|
|
func->closure = closure;
|
|
|
|
TimingTable *table = Runtime_GetTimingTable(runtime);
|
|
if (table != NULL) {
|
|
#warning "TODO: Calculate line number"
|
|
size_t line = 0;
|
|
Source *src = Executable_GetSource(exe);
|
|
func->timing_id = TimingTable_newEntry(table, src, line, name);
|
|
}
|
|
|
|
return (Object*) func;
|
|
}
|
|
|
|
|
|
typedef struct {
|
|
Object base;
|
|
Runtime *runtime;
|
|
int (*callback)(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[MAX_RETS], Error *error);
|
|
int argc;
|
|
} NativeFunctionObject;
|
|
|
|
static int native_func_call(Object *self, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Heap *heap, Error *error)
|
|
{
|
|
ASSERT(self != NULL);
|
|
ASSERT(heap != NULL);
|
|
ASSERT(error != NULL);
|
|
|
|
NativeFunctionObject *func = (NativeFunctionObject*) self;
|
|
|
|
// If the function isn't variadic, make sure
|
|
// the right amount of arguments is provided.
|
|
|
|
Object **argv2;
|
|
int argc2;
|
|
|
|
int expected_argc = func->argc;
|
|
|
|
if(expected_argc < 0 || expected_argc == (int) argc)
|
|
{
|
|
// The function is variadic or the right
|
|
// amount of arguments was provided.
|
|
argv2 = argv;
|
|
argc2 = argc;
|
|
}
|
|
else if(expected_argc < (int) argc)
|
|
{
|
|
// Nothing to be done. By using
|
|
// the right argc the additional
|
|
// arguments are ignored implicitly.
|
|
argv2 = argv;
|
|
argc2 = expected_argc;
|
|
}
|
|
else if(expected_argc > (int) argc)
|
|
{
|
|
// Some arguments are missing.
|
|
argv2 = malloc(sizeof(Object*) * expected_argc);
|
|
argc2 = expected_argc;
|
|
|
|
if(argv2 == NULL)
|
|
{
|
|
Error_Report(error, 1, "No memory");
|
|
return -1;
|
|
}
|
|
|
|
// Copy the provided arguments.
|
|
for(int i = 0; i < (int) argc; i += 1)
|
|
argv2[i] = argv[i];
|
|
|
|
// Set the unspecified arguments to none.
|
|
for(int i = argc; i < expected_argc; i += 1)
|
|
{
|
|
argv2[i] = Object_NewNone(heap, error);
|
|
|
|
if(argv2[i] == NULL)
|
|
{
|
|
free(argv2);
|
|
return -1;
|
|
}
|
|
}
|
|
} else {
|
|
UNREACHABLE;
|
|
argv2 = NULL;
|
|
argc2 = -1;
|
|
}
|
|
|
|
if (!Runtime_PushNativeFrame(func->runtime, error))
|
|
return -1;
|
|
|
|
ASSERT(func->callback != NULL);
|
|
int retc = func->callback(func->runtime, argv2, argc2, rets, error);
|
|
|
|
// NOTE: Since the callback may have executed some bytecode, a GC
|
|
// cycle may have been triggered, therefore we must assume
|
|
// every object reference that was locally saved is invalidated
|
|
// from here (the returned object is good tho).
|
|
|
|
if(argv2 != argv)
|
|
free(argv2);
|
|
|
|
if (retc >= 0 && !Runtime_PopFrame(func->runtime))
|
|
return -1;
|
|
|
|
return retc;
|
|
}
|
|
|
|
static TypeObject t_nfunc = {
|
|
.base = (Object) { .type = &t_type, .flags = Object_STATIC },
|
|
.name = "native function",
|
|
.size = sizeof (NativeFunctionObject),
|
|
.call = native_func_call,
|
|
};
|
|
|
|
/* Symbol: Object_FromNativeFunction
|
|
*
|
|
* Creates an object from a function pointer.
|
|
*
|
|
* Args:
|
|
* - runtime: The reference to an instanciated Runtime. This must be
|
|
* provided so that the callback can also access it.
|
|
*
|
|
* - callback: The native function to be executed when this object
|
|
* is called.
|
|
*
|
|
* - argc: The number of arguments the function expects. If -1 is
|
|
* provided, then the function is considered to be variadic.
|
|
*
|
|
* - heap: The heap that will be used to allocate the object.
|
|
* It can't be NULL.
|
|
*
|
|
* - error: Output parameter where error information is stored.
|
|
* It can't be NULL.
|
|
*
|
|
* Returns:
|
|
* The newly created object. If an error occurred, NULL is returned
|
|
* and information about the error is stored in the [error] argument.
|
|
*/
|
|
Object *Object_FromNativeFunction(Runtime *runtime, int (*callback)(Runtime*, Object**, unsigned int, Object*[static MAX_RETS], Error*), int argc, Heap *heap, Error *error)
|
|
{
|
|
ASSERT(callback != NULL);
|
|
|
|
NativeFunctionObject *func = (NativeFunctionObject*) Heap_Malloc(heap, &t_nfunc, error);
|
|
|
|
if(func == NULL)
|
|
return NULL;
|
|
|
|
func->runtime = runtime;
|
|
func->callback = callback;
|
|
func->argc = argc;
|
|
|
|
return (Object*) func;
|
|
}
|
|
|
|
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: \
|
|
if((y) == 0) \
|
|
{ \
|
|
Error_Report(error, ErrorType_RUNTIME, "Division by zero"); \
|
|
return NULL; \
|
|
} \
|
|
(z) = (x) / (y); \
|
|
break; \
|
|
default: UNREACHABLE; break; \
|
|
}
|
|
|
|
Object *res;
|
|
|
|
if(Object_IsInt(lop))
|
|
{
|
|
long long int raw_lop = Object_GetInt(lop);
|
|
|
|
if(Object_IsInt(rop))
|
|
{
|
|
// int + int
|
|
long long int raw_rop = Object_GetInt(rop);
|
|
long long int raw_res = 0;
|
|
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_GetFloat(rop);
|
|
double raw_res = 0;
|
|
APPLY((double) raw_lop, raw_rop, raw_res, id)
|
|
res = Object_FromFloat(raw_res, heap, error);
|
|
}
|
|
else
|
|
{
|
|
Error_Report(error, ErrorType_RUNTIME, "Arithmetic operation on a non-numeric object");
|
|
return NULL;
|
|
}
|
|
}
|
|
else if(Object_IsFloat(lop))
|
|
{
|
|
double raw_lop = Object_GetFloat(lop);
|
|
|
|
if(Object_IsInt(rop))
|
|
{
|
|
// float + int
|
|
long long int raw_rop = Object_GetInt(rop);
|
|
double raw_res = 0;
|
|
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_GetFloat(rop);
|
|
double raw_res = 0;
|
|
APPLY(raw_lop, raw_rop, raw_res, id)
|
|
res = Object_FromFloat(raw_res, heap, error);
|
|
}
|
|
else
|
|
{
|
|
Error_Report(error, ErrorType_RUNTIME, "Arithmetic operation on a non-numeric object");
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Error_Report(error, ErrorType_RUNTIME, "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: UNREACHABLE; break; \
|
|
}
|
|
|
|
_Bool res = 0;
|
|
|
|
if(Object_IsInt(lop))
|
|
{
|
|
long long int raw_lop = Object_GetInt(lop);
|
|
if(Object_IsInt(rop))
|
|
{
|
|
// int + int
|
|
long long int raw_rop = Object_GetInt(rop);
|
|
APPLY(raw_lop, raw_rop, res, id)
|
|
}
|
|
else if(Object_IsFloat(rop))
|
|
{
|
|
// int + float
|
|
double raw_rop = Object_GetFloat(rop);
|
|
APPLY((double) raw_lop, raw_rop, res, id)
|
|
}
|
|
else
|
|
{
|
|
Error_Report(error, ErrorType_RUNTIME, "Relational operation on a non-numeric object");
|
|
return NULL;
|
|
}
|
|
}
|
|
else if(Object_IsFloat(lop))
|
|
{
|
|
double raw_lop = Object_GetFloat(lop);
|
|
if(Object_IsInt(rop))
|
|
{
|
|
// float + int
|
|
long long int raw_rop = Object_GetInt(rop);
|
|
APPLY(raw_lop, (double) raw_rop, res, id)
|
|
}
|
|
else if(Object_IsFloat(rop))
|
|
{
|
|
// float + float
|
|
double raw_rop = Object_GetFloat(rop);
|
|
APPLY(raw_lop, raw_rop, res, id)
|
|
}
|
|
else
|
|
{
|
|
Error_Report(error, ErrorType_RUNTIME, "Relational operation on a non-numeric object");
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Error_Report(error, ErrorType_RUNTIME, "Relational operation on a non-numeric object");
|
|
return NULL;
|
|
}
|
|
|
|
#undef APPLY
|
|
|
|
return Object_FromBool(res, heap, error);
|
|
}
|
|
|
|
static _Bool runInstruction(Runtime *runtime, Error *error)
|
|
{
|
|
ASSERT(runtime != NULL);
|
|
ASSERT(error->occurred == 0);
|
|
|
|
Stack *stack = Runtime_GetStack(runtime);
|
|
Heap *heap = Runtime_GetHeap(runtime);
|
|
Executable *exe = Runtime_GetCurrentExecutable(runtime);
|
|
ASSERT(exe != NULL);
|
|
int index = Runtime_GetCurrentIndex(runtime);
|
|
Opcode opcode;
|
|
Operand ops[3];
|
|
int opc = sizeof(ops) / sizeof(ops[0]);
|
|
|
|
if(!Executable_Fetch(exe, index, &opcode, ops, &opc))
|
|
{
|
|
Error_Report(error, ErrorType_INTERNAL, "Invalid instruction index %d", index);
|
|
return 0;
|
|
}
|
|
|
|
Runtime_SetInstructionIndex(runtime, index+1);
|
|
|
|
switch(opcode)
|
|
{
|
|
case OPCODE_NOPE:
|
|
// Do nothing.
|
|
return 1;
|
|
|
|
case OPCODE_POS:
|
|
{
|
|
ASSERT(opc == 0);
|
|
|
|
if(Runtime_Top(runtime, 0) == NULL)
|
|
{
|
|
Error_Report(error, ErrorType_INTERNAL, "Frame doesn't have enough items on the stack to execute POS");
|
|
return 0;
|
|
}
|
|
|
|
/* Do nothing */
|
|
return 1;
|
|
}
|
|
|
|
case OPCODE_NEG:
|
|
{
|
|
ASSERT(opc == 0);
|
|
Object *top;
|
|
if(!Runtime_Pop(runtime, error, &top, 1))
|
|
return 0;
|
|
|
|
if(Object_IsInt(top))
|
|
{
|
|
long long n = Object_GetInt(top);
|
|
top = Object_FromInt(-n, heap, error);
|
|
}
|
|
else if(Object_IsFloat(top))
|
|
{
|
|
double f = Object_GetFloat(top);
|
|
top = Object_FromFloat(-f, heap, error);
|
|
}
|
|
else
|
|
{
|
|
Error_Report(error, ErrorType_RUNTIME, "Negation operand on a non-numeric object");
|
|
return 0;
|
|
}
|
|
|
|
if(top == NULL)
|
|
return 0;
|
|
|
|
return Runtime_Push(runtime, error, top);
|
|
}
|
|
|
|
case OPCODE_NOT:
|
|
{
|
|
ASSERT(opc == 0);
|
|
|
|
Object *top;
|
|
if(!Runtime_Pop(runtime, error, &top, 1))
|
|
return 0;
|
|
|
|
if(!Object_IsBool(top))
|
|
{
|
|
Error_Report(error, ErrorType_RUNTIME, "NOT operand isn't a boolean");
|
|
return 0;
|
|
}
|
|
|
|
_Bool v = Object_GetBool(top);
|
|
|
|
Object *negated = Object_FromBool(!v, heap, error);
|
|
if(negated == NULL)
|
|
return 0;
|
|
|
|
return Runtime_Push(runtime, error, negated);
|
|
}
|
|
|
|
case OPCODE_NLB:
|
|
{
|
|
ASSERT(opc == 0);
|
|
|
|
Object *top;
|
|
if(!Runtime_Pop(runtime, error, &top, 1))
|
|
return 0;
|
|
|
|
Object *nullable = Object_NewNullable(top, heap, error);
|
|
if(nullable == NULL)
|
|
return 0;
|
|
|
|
return Runtime_Push(runtime, error, nullable);
|
|
}
|
|
|
|
case OPCODE_STP:
|
|
{
|
|
ASSERT(opc == 0);
|
|
Object *objs[2];
|
|
if(!Runtime_Pop(runtime, error, objs, 2))
|
|
return 0;
|
|
Object *res = Object_NewSum(objs[1], objs[0], heap, error);
|
|
if(res == NULL)
|
|
return 0;
|
|
return Runtime_Push(runtime, error, res);
|
|
}
|
|
|
|
case OPCODE_ADD:
|
|
case OPCODE_SUB:
|
|
case OPCODE_MUL:
|
|
case OPCODE_DIV:
|
|
{
|
|
ASSERT(opc == 0);
|
|
Object *objs[2];
|
|
if(!Runtime_Pop(runtime, error, objs, 2))
|
|
return 0;
|
|
Object *res = do_math_op(objs[1], objs[0], opcode, heap, error);
|
|
if(res == NULL)
|
|
return 0;
|
|
return Runtime_Push(runtime, error, res);
|
|
}
|
|
|
|
case OPCODE_MOD:
|
|
{
|
|
ASSERT(opc == 0);
|
|
Object *objs[2];
|
|
if(!Runtime_Pop(runtime, error, objs, 2))
|
|
return 0;
|
|
if (!Object_IsInt(objs[0]) || !Object_IsInt(objs[1])) {
|
|
Error_Report(error, ErrorType_RUNTIME, "Arithmetic operation on a non-numeric object");
|
|
return 0;
|
|
}
|
|
|
|
long long int x, y, z;
|
|
y = Object_GetInt(objs[0]);
|
|
x = Object_GetInt(objs[1]);
|
|
z = x % y;
|
|
|
|
Object *res = Object_FromInt(z, heap, error);
|
|
if(res == NULL)
|
|
return 0;
|
|
|
|
return Runtime_Push(runtime, error, res);
|
|
}
|
|
|
|
case OPCODE_EQL:
|
|
case OPCODE_NQL:
|
|
{
|
|
ASSERT(opc == 0);
|
|
Object *objs[2];
|
|
if(!Runtime_Pop(runtime, error, objs, 2))
|
|
return 0;
|
|
|
|
_Bool rawres = Object_Compare(objs[1], objs[0], error);
|
|
if(error->occurred == 1)
|
|
return 0;
|
|
|
|
if(opcode == OPCODE_NQL)
|
|
rawres = !rawres;
|
|
|
|
Object *res = Object_FromBool(rawres, heap, error);
|
|
if(res == NULL)
|
|
return 0;
|
|
|
|
return Runtime_Push(runtime, error, res);
|
|
}
|
|
|
|
case OPCODE_LSS:
|
|
case OPCODE_GRT:
|
|
case OPCODE_LEQ:
|
|
case OPCODE_GEQ:
|
|
{
|
|
ASSERT(opc == 0);
|
|
|
|
Object *objs[2];
|
|
if(!Runtime_Pop(runtime, error, objs, 2))
|
|
return 0;
|
|
|
|
Object *res = do_relational_op(objs[1], objs[0], opcode, heap, error);
|
|
if(res == NULL)
|
|
return 0;
|
|
|
|
return Runtime_Push(runtime, error, res);
|
|
}
|
|
|
|
case OPCODE_ASS:
|
|
{
|
|
ASSERT(opc == 1);
|
|
ASSERT(ops[0].type == OPTP_STRING);
|
|
const char *name = ops[0].as_string;
|
|
|
|
Object *value = Runtime_Top(runtime, 0);
|
|
if(value == NULL) {
|
|
Error_Report(error, ErrorType_INTERNAL, "Frame has not enough values on the stack");
|
|
return 0;
|
|
}
|
|
return Runtime_SetVariable(runtime, error, name, value);
|
|
}
|
|
|
|
case OPCODE_POP:
|
|
{
|
|
ASSERT(opc == 1);
|
|
return Runtime_Pop(runtime, error, NULL, ops[0].as_int);
|
|
}
|
|
|
|
case OPCODE_CHECKTYPE:
|
|
{
|
|
ASSERT(opc == 2);
|
|
ASSERT(ops[0].type == OPTP_INT);
|
|
ASSERT(ops[1].type == OPTP_STRING);
|
|
|
|
const char *arg_name;
|
|
int arg_index;
|
|
|
|
arg_index = ops[0].as_int;
|
|
arg_name = ops[1].as_string;
|
|
ASSERT(arg_name != NULL);
|
|
|
|
Object *typ = Runtime_Top(runtime, 0);
|
|
Object *arg = Runtime_Top(runtime, -1);
|
|
if(typ == NULL || arg == NULL)
|
|
{
|
|
Error_Report(error, ErrorType_INTERNAL, "Frame doesn't own enough objects to execute CHECKTYPE");
|
|
return 0;
|
|
}
|
|
|
|
// Pop type
|
|
if(!Runtime_Pop(runtime, error, NULL, 1))
|
|
return 0;
|
|
|
|
if (!Object_IsTypeOf(typ, arg, heap, error)) {
|
|
char provided[512];
|
|
char allowed[512];
|
|
FILE *provided_fp = fmemopen(provided, sizeof(provided), "wb");
|
|
FILE *allowed_fp = fmemopen(allowed, sizeof(allowed), "wb");
|
|
// TODO: Check for errors from [fmemopen]
|
|
Object_Print(typ, allowed_fp);
|
|
Object_Print(arg, provided_fp);
|
|
fclose(allowed_fp);
|
|
fclose(provided_fp);
|
|
Error_Report(error, ErrorType_RUNTIME, "Argument %d \"%s\" has an unallowed type. Was expected something with type %s but was provided %s",
|
|
arg_index+1, arg_name, allowed, provided);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
case OPCODE_CALL:
|
|
{
|
|
ASSERT(opc == 2);
|
|
ASSERT(ops[0].type == OPTP_INT);
|
|
ASSERT(ops[1].type == OPTP_INT);
|
|
|
|
int argc = ops[0].as_int;
|
|
int retc = ops[1].as_int;
|
|
ASSERT(argc >= 0 && retc > 0);
|
|
|
|
Object *callable;
|
|
if (!Runtime_Pop(runtime, error, &callable, 1)) {
|
|
Error_Report(error, ErrorType_INTERNAL, "Frame doesn't own enough objects to execute call");
|
|
return 0;
|
|
}
|
|
|
|
Object *argv[32];
|
|
|
|
int max_argc = sizeof(argv) / sizeof(argv[0]);
|
|
if(argc > max_argc) {
|
|
Error_Report(error, ErrorType_INTERNAL, "Static buffer only allows function calls with up to %d arguments", max_argc);
|
|
return 0;
|
|
}
|
|
if (!Runtime_Pop(runtime, error, argv, argc))
|
|
return 0;
|
|
|
|
Object *rets[8];
|
|
int num_rets = Object_Call(callable, argv, argc, rets, heap, error);
|
|
if(num_rets < 0)
|
|
return 0;
|
|
|
|
// NOTE: Every local object reference is invalidated from here.
|
|
|
|
ASSERT(error->occurred == 0);
|
|
|
|
for(int g = 0; g < MIN(num_rets, retc); g += 1)
|
|
if(!Runtime_Push(runtime, error, rets[g]))
|
|
return 0;
|
|
|
|
for(int g = 0; g < retc - num_rets; g += 1)
|
|
{
|
|
Object *temp = Object_NewNone(Runtime_GetHeap(runtime), error);
|
|
if(temp == NULL)
|
|
return NULL;
|
|
|
|
if(!Runtime_Push(runtime, error, temp))
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
case OPCODE_SELECT:
|
|
case OPCODE_SELECT2:
|
|
{
|
|
ASSERT(opc == 0);
|
|
|
|
int to_be_popped = (opcode == OPCODE_SELECT) ? 2 : 1;
|
|
|
|
Object *col = Runtime_Top(runtime, -1);
|
|
Object *key = Runtime_Top(runtime, 0);
|
|
if (col == NULL || key == NULL) {
|
|
const char *name = "SELECT";
|
|
if (opcode == OPCODE_SELECT2)
|
|
name = "SELECT2";
|
|
Error_Report(error, ErrorType_INTERNAL, "Frame has not enough values on the stack to run %s instruction", name);
|
|
return 0;
|
|
}
|
|
if(!Runtime_Pop(runtime, error, NULL, to_be_popped))
|
|
return 0;
|
|
|
|
Error dummy;
|
|
Error_Init(&dummy); // We want to catch the error reported by this Object_Select.
|
|
|
|
Object *val = Object_Select(col, key, heap, &dummy);
|
|
|
|
if(val == NULL) {
|
|
Error_Free(&dummy);
|
|
|
|
val = Object_NewNone(heap, error);
|
|
if(val == NULL)
|
|
return 0;
|
|
}
|
|
|
|
return Runtime_Push(runtime, error, val);
|
|
}
|
|
|
|
case OPCODE_INSERT:
|
|
{
|
|
ASSERT(opc == 0);
|
|
|
|
Object *col = Runtime_Top(runtime, -2);
|
|
Object *key = Runtime_Top(runtime, -1);
|
|
Object *val = Runtime_Top(runtime, 0);
|
|
if (col == NULL || key == NULL || val == NULL) {
|
|
Error_Report(error, ErrorType_INTERNAL, "Frame has not enough values on the stack to run INSERT instruction");
|
|
return 0;
|
|
}
|
|
if(!Runtime_Pop(runtime, error, NULL, 2))
|
|
return 0;
|
|
|
|
return Object_Insert(col, key, val, heap, error);
|
|
}
|
|
|
|
case OPCODE_INSERT2:
|
|
{
|
|
ASSERT(opc == 0);
|
|
|
|
Object *val = Stack_Top(stack, -2);
|
|
Object *col = Stack_Top(stack, -1);
|
|
Object *key = Stack_Top(stack, 0);
|
|
if (val == NULL || col == NULL || key == NULL) {
|
|
Error_Report(error, ErrorType_INTERNAL, "Frame has not enough values on the stack to run INSERT2 instruction");
|
|
return 0;
|
|
}
|
|
if(!Runtime_Pop(runtime, error, NULL, 2))
|
|
return 0;
|
|
|
|
return Object_Insert(col, key, val, heap, error);
|
|
}
|
|
|
|
case OPCODE_PUSHINT:
|
|
{
|
|
ASSERT(opc == 1);
|
|
ASSERT(ops[0].type == OPTP_INT);
|
|
|
|
Object *obj = Object_FromInt(ops[0].as_int, heap, error);
|
|
if(obj == NULL)
|
|
return 0;
|
|
|
|
return Runtime_Push(runtime, error, obj);
|
|
}
|
|
|
|
case OPCODE_PUSHFLT:
|
|
{
|
|
ASSERT(opc == 1);
|
|
ASSERT(ops[0].type == OPTP_FLOAT);
|
|
|
|
Object *obj = Object_FromFloat(ops[0].as_float, heap, error);
|
|
if(obj == NULL)
|
|
return 0;
|
|
|
|
return Runtime_Push(runtime, error, obj);
|
|
}
|
|
|
|
case OPCODE_PUSHSTR:
|
|
{
|
|
ASSERT(opc == 1);
|
|
ASSERT(ops[0].type == OPTP_STRING);
|
|
|
|
Object *obj = Object_FromString(ops[0].as_string, -1, heap, error);
|
|
if(obj == NULL)
|
|
return 0;
|
|
|
|
return Runtime_Push(runtime, error, obj);
|
|
}
|
|
|
|
case OPCODE_PUSHVAR:
|
|
{
|
|
ASSERT(opc == 1);
|
|
ASSERT(ops[0].type == OPTP_STRING);
|
|
|
|
Object *value;
|
|
if (!Runtime_GetVariable(runtime, error, ops[0].as_string, &value))
|
|
return 0;
|
|
|
|
if (value == NULL) {
|
|
Error_Report(error, ErrorType_RUNTIME, "Reference to undefined variable \"%s\"", ops[0].as_string);
|
|
return 0;
|
|
}
|
|
|
|
return Runtime_Push(runtime, error, value);
|
|
}
|
|
|
|
case OPCODE_PUSHNNE:
|
|
{
|
|
ASSERT(opc == 0);
|
|
Object *obj = Object_NewNone(heap, error);
|
|
if(obj == NULL)
|
|
return 0;
|
|
return Runtime_Push(runtime, error, obj);
|
|
}
|
|
|
|
case OPCODE_PUSHTRU:
|
|
{
|
|
ASSERT(opc == 0);
|
|
Object *obj = Object_FromBool(1, heap, error);
|
|
if(obj == NULL)
|
|
return 0;
|
|
return Runtime_Push(runtime, error, obj);
|
|
}
|
|
|
|
case OPCODE_PUSHFLS:
|
|
{
|
|
ASSERT(opc == 0);
|
|
Object *obj = Object_FromBool(0, heap, error);
|
|
if(obj == NULL)
|
|
return 0;
|
|
return Runtime_Push(runtime, error, obj);
|
|
}
|
|
|
|
case OPCODE_PUSHFUN:
|
|
{
|
|
ASSERT(opc == 3);
|
|
ASSERT(ops[0].type == OPTP_IDX);
|
|
ASSERT(ops[1].type == OPTP_INT);
|
|
ASSERT(ops[2].type == OPTP_STRING);
|
|
|
|
Object *locals = Runtime_GetLocals(runtime);
|
|
Object *old_closure = Runtime_GetClosure(runtime);
|
|
Object *new_closure = Object_NewClosure(old_closure, locals, heap, error); // Should old_closure and locals be in the reverse order?
|
|
if(new_closure == NULL)
|
|
return 0;
|
|
|
|
Object *func = Object_FromNojaFunction(runtime, ops[2].as_string, exe, ops[0].as_int, ops[1].as_int, new_closure, heap, error);
|
|
if(func == NULL)
|
|
return 0;
|
|
|
|
return Runtime_Push(runtime, error, func);
|
|
}
|
|
|
|
case OPCODE_PUSHLST:
|
|
{
|
|
ASSERT(opc == 1);
|
|
ASSERT(ops[0].type == OPTP_INT);
|
|
|
|
Object *obj = Object_NewList(ops[0].as_int, heap, error);
|
|
if(obj == NULL)
|
|
return 0;
|
|
|
|
return Runtime_Push(runtime, error, obj);
|
|
}
|
|
|
|
case OPCODE_PUSHMAP:
|
|
{
|
|
ASSERT(opc == 1);
|
|
ASSERT(ops[0].type == OPTP_INT);
|
|
|
|
Object *obj = Object_NewMap(ops[0].as_int, heap, error);
|
|
if(obj == NULL)
|
|
return 0;
|
|
|
|
return Runtime_Push(runtime, error, obj);
|
|
}
|
|
|
|
case OPCODE_PUSHNNETYP:
|
|
{
|
|
ASSERT(opc == 0);
|
|
|
|
Object *obj = (Object*) Object_GetNoneType();
|
|
ASSERT(obj != NULL);
|
|
|
|
return Runtime_Push(runtime, error, obj);
|
|
}
|
|
|
|
case OPCODE_PUSHTYP:
|
|
{
|
|
ASSERT(opc == 0);
|
|
|
|
Object *top = Runtime_Top(runtime, 0);
|
|
if (top == NULL) {
|
|
Error_Report(error, ErrorType_INTERNAL, "Frame has not enough values on the stack to run PUSHTYP instruction");
|
|
return 0;
|
|
}
|
|
|
|
Object *typ = (Object*) Object_GetType(top);
|
|
ASSERT(typ != NULL);
|
|
|
|
return Runtime_Push(runtime, error, typ);
|
|
}
|
|
|
|
case OPCODE_EXIT:
|
|
{
|
|
ASSERT(opc == 0);
|
|
Object *vars = Runtime_GetLocals(runtime);
|
|
ASSERT(vars != NULL);
|
|
Runtime_Push(runtime, error, vars);
|
|
return 0;
|
|
}
|
|
|
|
case OPCODE_RETURN:
|
|
{
|
|
ASSERT(opc == 1);
|
|
ASSERT(ops[0].type == OPTP_INT);
|
|
int retc = ops[0].as_int;
|
|
UNUSED(retc);
|
|
ASSERT(retc >= 0);
|
|
ASSERT(retc <= MAX_RETS);
|
|
ASSERT((size_t) retc == Runtime_GetFrameStackUsage(runtime));
|
|
return 0;
|
|
}
|
|
|
|
case OPCODE_JUMP:
|
|
ASSERT(opc == 1);
|
|
ASSERT(ops[0].type == OPTP_IDX);
|
|
Runtime_SetInstructionIndex(runtime, ops[0].as_int);
|
|
return 1;
|
|
|
|
case OPCODE_JUMPIFANDPOP:
|
|
case OPCODE_JUMPIFNOTANDPOP:
|
|
{
|
|
ASSERT(opc == 1);
|
|
ASSERT(ops[0].type == OPTP_IDX);
|
|
long long int target = ops[0].as_int;
|
|
|
|
Object *top;
|
|
if(!Runtime_Pop(runtime, error, &top, 1))
|
|
return 0;
|
|
|
|
if(!Object_IsBool(top)) {
|
|
Error_Report(error, ErrorType_RUNTIME, "Not a boolean");
|
|
return 0;
|
|
}
|
|
|
|
if(( Object_GetBool(top) && opcode == OPCODE_JUMPIFANDPOP)
|
|
|| (!Object_GetBool(top) && opcode == OPCODE_JUMPIFNOTANDPOP))
|
|
Runtime_SetInstructionIndex(runtime, target);
|
|
return 1;
|
|
}
|
|
|
|
default:
|
|
UNREACHABLE;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static bool runInstructionsUntilSomethingHappens(Runtime *runtime, Error *error)
|
|
{
|
|
Heap *heap = Runtime_GetHeap(runtime);
|
|
RuntimeCallback callback = Runtime_GetCallback(runtime);
|
|
|
|
if(Runtime_WasInterrupted(runtime) || (callback.func != NULL && !callback.func(runtime, callback.data)))
|
|
Error_Report(error, ErrorType_RUNTIME, "Forced abortion");
|
|
else
|
|
while(runInstruction(runtime, error))
|
|
{
|
|
if(Runtime_WasInterrupted(runtime) || (callback.func != NULL && !callback.func(runtime, callback.data)))
|
|
{
|
|
Error_Report(error, ErrorType_RUNTIME, "Forced abortion");
|
|
break;
|
|
}
|
|
|
|
if(Heap_GetUsagePercentage(heap) > 100)
|
|
if(!Runtime_CollectGarbage(runtime, error))
|
|
break;
|
|
}
|
|
|
|
// If an error occurred, we want to return NULL.
|
|
return !error->occurred;
|
|
}
|
|
|
|
static int runExecutableAtIndex(Runtime *runtime, Error *error,
|
|
Executable *exe, int index,
|
|
Object *closure,
|
|
Object *rets[static MAX_RETS],
|
|
Object *argv[], int argc)
|
|
{
|
|
if (!Runtime_PushFrame(runtime, error, closure, exe, index))
|
|
return -1;
|
|
|
|
for (int i = 0; i < argc; i++)
|
|
if (!Runtime_Push(runtime, error, argv[i]))
|
|
return -1;
|
|
|
|
if (!runInstructionsUntilSomethingHappens(runtime, error))
|
|
return -1;
|
|
|
|
// Get return values
|
|
int retc = 0;
|
|
while (retc < MAX_RETS && (rets[retc] = Runtime_Top(runtime, -retc)))
|
|
retc++;
|
|
|
|
if (!Runtime_PopFrame(runtime))
|
|
return -1;
|
|
|
|
return retc;
|
|
}
|
|
|
|
int runSource(Runtime *runtime, Source *source, Object *rets[static MAX_RETS], Error *error)
|
|
{
|
|
int error_offset;
|
|
Executable *exe = compile(source, error, &error_offset);
|
|
if(exe == NULL) {
|
|
Error suberror;
|
|
Error_Init(&suberror);
|
|
Runtime_PushFailedFrame(runtime, &suberror, source, error_offset); // If this fails, there's nothing we can do
|
|
Error_Free(&suberror);
|
|
return -1;
|
|
}
|
|
|
|
int retc = runExecutableAtIndex(runtime, error, exe, 0, NULL, rets, NULL, 0);
|
|
|
|
Executable_Free(exe);
|
|
return retc;
|
|
}
|
|
|
|
int runBytecodeSource(Runtime *runtime, Source *source, Object *rets[static MAX_RETS], Error *error)
|
|
{
|
|
int error_offset;
|
|
Executable *exe = assemble(source, error, &error_offset);
|
|
if(exe == NULL) {
|
|
Error suberror;
|
|
Error_Init(&suberror);
|
|
Runtime_PushFailedFrame(runtime, &suberror, source, error_offset); // If this fails, there's nothing we can do
|
|
Error_Free(&suberror);
|
|
return -1;
|
|
}
|
|
|
|
int retc = runExecutableAtIndex(runtime, error, exe, 0, NULL, rets, NULL, 0);
|
|
|
|
Executable_Free(exe);
|
|
return retc;
|
|
}
|
|
|
|
int runFileEx(Runtime *runtime, const char *file, Object *rets[static MAX_RETS], Error *error)
|
|
{
|
|
Source *source = Source_FromFile(file, error);
|
|
if (source == NULL)
|
|
return -1;
|
|
|
|
int retc = runSource(runtime, source, rets, error);
|
|
|
|
Source_Free(source);
|
|
return retc;
|
|
}
|
|
|
|
int runStringEx(Runtime *runtime, const char *name, const char *string, Object *rets[static MAX_RETS], Error *error)
|
|
{
|
|
Source *source = Source_FromString(name, string, -1, error);
|
|
if (source == NULL)
|
|
return -1;
|
|
|
|
int retc = runSource(runtime, source, rets, error);
|
|
|
|
Source_Free(source);
|
|
return retc;
|
|
}
|
|
|
|
int runBytecodeFileEx(Runtime *runtime, const char *file, Object *rets[static MAX_RETS], Error *error)
|
|
{
|
|
Source *source = Source_FromFile(file, error);
|
|
if (source == NULL)
|
|
return -1;
|
|
|
|
int retc = runBytecodeSource(runtime, source, rets, error);
|
|
|
|
Source_Free(source);
|
|
return retc;
|
|
}
|
|
|
|
int runBytecodeStringEx(Runtime *runtime, const char *name, const char *string, Object *rets[static MAX_RETS], Error *error)
|
|
{
|
|
Source *source = Source_FromString(name, string, -1, error);
|
|
if (source == NULL)
|
|
return -1;
|
|
|
|
int retc = runBytecodeSource(runtime, source, rets, error);
|
|
|
|
Source_Free(source);
|
|
return retc;
|
|
}
|
|
|
|
bool runFile(Runtime *runtime, const char *file, Error *error)
|
|
{
|
|
Object *rets[MAX_RETS];
|
|
return runFileEx(runtime, file, rets, error) >= 0;
|
|
}
|
|
|
|
bool runString(Runtime *runtime, const char *string, Error *error)
|
|
{
|
|
Object *rets[MAX_RETS];
|
|
return runStringEx(runtime, "(unnamed)", string, rets, error) >= 0;
|
|
}
|
|
|
|
bool runBytecodeFile(Runtime *runtime, const char *file, Error *error)
|
|
{
|
|
Object *rets[MAX_RETS];
|
|
return runBytecodeFileEx(runtime, file, rets, error) >= 0;
|
|
}
|
|
|
|
bool runBytecodeString(Runtime *runtime, const char *string, Error *error)
|
|
{
|
|
Object *rets[MAX_RETS];
|
|
return runBytecodeStringEx(runtime, "(unnamed)", string, rets, error) >= 0;
|
|
}
|
|
|
|
static bool makePathRelativeToScript(Runtime *runtime, const char *src_path, char *dst_path, size_t dst_size)
|
|
{
|
|
size_t src_size = strlen(src_path);
|
|
|
|
if(Path_IsAbsolute(src_path)) {
|
|
|
|
if(src_size >= dst_size)
|
|
return false;
|
|
|
|
strcpy(dst_path, src_path);
|
|
|
|
} else {
|
|
|
|
size_t written = Runtime_GetCurrentScriptFolder(runtime, dst_path, dst_size);
|
|
if(written == 0)
|
|
return false;
|
|
|
|
if(written + src_size >= dst_size)
|
|
return false;
|
|
|
|
memcpy(dst_path + written, src_path, src_size);
|
|
dst_path[written + src_size] = '\0';
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int runFileRelativeToScript(Runtime *runtime, const char *file, Object *rets[static MAX_RETS], Error *error)
|
|
{
|
|
char full[1024];
|
|
if (!makePathRelativeToScript(runtime, file, full, sizeof(full))) {
|
|
Error_Report(error, ErrorType_INTERNAL, "Internal buffer is too small");
|
|
return -1;
|
|
}
|
|
|
|
Source *source = Source_FromFile(full, error);
|
|
if (source == NULL)
|
|
return -1;
|
|
|
|
int retc = runSource(runtime, source, rets, error);
|
|
|
|
Source_Free(source);
|
|
return retc;
|
|
}
|