1374 lines
28 KiB
C
1374 lines
28 KiB
C
|
|
/* +--------------------------------------------------------------------------+
|
|
** | _ _ _ |
|
|
** | | \ | | (_) |
|
|
** | | \| | ___ _ __ _ |
|
|
** | | . ` |/ _ \| |/ _` | |
|
|
** | | |\ | (_) | | (_| | |
|
|
** | |_| \_|\___/| |\__,_| |
|
|
** | _/ | |
|
|
** | |__/ |
|
|
** +--------------------------------------------------------------------------+
|
|
** | Copyright (c) 2022 Francesco Cozzuto <francesco.cozzuto@gmail.com> |
|
|
** +--------------------------------------------------------------------------+
|
|
** | This file is part of The Noja Interpreter. |
|
|
** | |
|
|
** | The Noja Interpreter is free software: you can redistribute it and/or |
|
|
** | modify it under the terms of the GNU General Public License as published |
|
|
** | by the Free Software Foundation, either version 3 of the License, or (at |
|
|
** | your option) any later version. |
|
|
** | |
|
|
** | The Noja Interpreter is distributed in the hope that it will be useful, |
|
|
** | but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
** | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
|
|
** | Public License for more details. |
|
|
** | |
|
|
** | You should have received a copy of the GNU General Public License along |
|
|
** | with The Noja Interpreter. If not, see <http://www.gnu.org/licenses/>. |
|
|
** +--------------------------------------------------------------------------+
|
|
*/
|
|
|
|
#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 xFrame Frame;
|
|
struct xFrame {
|
|
Frame *prev;
|
|
Object *locals;
|
|
Object *closure;
|
|
Executable *exe;
|
|
int index, used;
|
|
};
|
|
|
|
struct xRuntime {
|
|
void *callback_userp;
|
|
_Bool (*callback_addr)(Runtime*, void*);
|
|
_Bool free_heap;
|
|
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_New2(int stack_size, Heap *heap, _Bool free_heap, void *callback_userp, _Bool (*callback_addr)(Runtime*, void*))
|
|
{
|
|
if(stack_size < 0)
|
|
stack_size = 1024;
|
|
|
|
Runtime *runtime = malloc(sizeof(Runtime));
|
|
|
|
if(runtime != NULL)
|
|
{
|
|
runtime->heap = heap;
|
|
runtime->stack = Stack_New(stack_size);
|
|
|
|
if(runtime->stack == NULL)
|
|
{
|
|
Heap_Free(runtime->heap);
|
|
free(runtime);
|
|
}
|
|
|
|
runtime->free_heap = free_heap;
|
|
runtime->callback_userp = callback_userp;
|
|
runtime->callback_addr = callback_addr;
|
|
runtime->builtins = NULL;
|
|
runtime->frame = NULL;
|
|
runtime->depth = 0;
|
|
}
|
|
|
|
return runtime;
|
|
}
|
|
|
|
Runtime *Runtime_New(int stack_size, int heap_size, void *callback_userp, _Bool (*callback_addr)(Runtime*, void*))
|
|
{
|
|
if(heap_size < 0)
|
|
heap_size = 65536;
|
|
|
|
Heap *heap = Heap_New(heap_size);
|
|
|
|
if(heap == NULL)
|
|
return NULL;
|
|
|
|
return Runtime_New2(stack_size, heap, 1, callback_userp, callback_addr);
|
|
}
|
|
void Runtime_Free(Runtime *runtime)
|
|
{
|
|
if(runtime->free_heap)
|
|
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: \
|
|
if((y) == 0) \
|
|
{ \
|
|
Error_Report(error, 0, "Division by zero"); \
|
|
return NULL; \
|
|
} \
|
|
(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);
|
|
assert(error->occurred == 0);
|
|
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;
|
|
}
|
|
|
|
runtime->frame->index += 1;
|
|
|
|
switch(opcode)
|
|
{
|
|
case OPCODE_NOPE:
|
|
// Do nothing.
|
|
return 1;
|
|
|
|
case OPCODE_POS:
|
|
{
|
|
assert(opc == 0);
|
|
|
|
if(runtime->frame->used == 0)
|
|
{
|
|
Error_Report(error, 1, "Frame doesn't have enough items on the stack to execute POS");
|
|
return 0;
|
|
}
|
|
|
|
/* Do nothing */
|
|
return 1;
|
|
}
|
|
|
|
case OPCODE_NEG:
|
|
{
|
|
assert(opc == 0);
|
|
|
|
if(runtime->frame->used == 0)
|
|
{
|
|
Error_Report(error, 1, "Frame doesn't have enough items on the stack to execute NEG");
|
|
return 0;
|
|
}
|
|
|
|
Object *top = Stack_Top(runtime->stack, 0);
|
|
assert(top != NULL);
|
|
|
|
if(!Runtime_Pop(runtime, error, 1))
|
|
return 0;
|
|
|
|
Heap *heap = Runtime_GetHeap(runtime);
|
|
assert(heap != NULL);
|
|
|
|
if(Object_IsInt(top))
|
|
{
|
|
long long n = Object_ToInt(top, error);
|
|
|
|
if(error->occurred)
|
|
return 0;
|
|
|
|
top = Object_FromInt(-n, heap, error);
|
|
}
|
|
else if(Object_IsFloat(top))
|
|
{
|
|
double f = Object_ToFloat(top, error);
|
|
|
|
if(error->occurred)
|
|
return 0;
|
|
|
|
top = Object_FromFloat(-f, heap, error);
|
|
}
|
|
else
|
|
{
|
|
Error_Report(error, 0, "Negation operand on a non-numeric object");
|
|
return 0;
|
|
}
|
|
|
|
if(top == NULL)
|
|
return 0;
|
|
|
|
if(!Runtime_Push(runtime, error, top))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
case OPCODE_NOT:
|
|
{
|
|
assert(opc == 0);
|
|
|
|
if(runtime->frame->used == 0)
|
|
{
|
|
Error_Report(error, 1, "Frame doesn't have enough items on the stack to execute NOT");
|
|
return 0;
|
|
}
|
|
|
|
Object *top = Stack_Top(runtime->stack, 0);
|
|
|
|
if(!Runtime_Pop(runtime, error, 1))
|
|
return 0;
|
|
|
|
assert(top != NULL);
|
|
|
|
_Bool v = Object_ToBool(top, error);
|
|
|
|
if(error->occurred)
|
|
return 0;
|
|
|
|
Object *negated = Object_FromBool(!v, runtime->heap, error);
|
|
|
|
if(negated == NULL)
|
|
return 0;
|
|
|
|
if(!Runtime_Push(runtime, error, negated))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
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;
|
|
return 1;
|
|
}
|
|
|
|
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_AND:
|
|
case OPCODE_OR:
|
|
{
|
|
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 raw_rop, raw_lop, raw_res;
|
|
raw_lop = Object_ToBool(lop, error);
|
|
raw_rop = Object_ToBool(rop, error);
|
|
if(error->occurred) return 0;
|
|
|
|
switch(opcode)
|
|
{
|
|
case OPCODE_AND: raw_res = raw_lop && raw_rop; break;
|
|
case OPCODE_OR: raw_res = raw_lop || raw_rop; break;
|
|
default: assert(0); break;
|
|
}
|
|
|
|
Object *res = Object_FromBool(raw_res, 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->locals, 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 == 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);
|
|
|
|
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);
|
|
}
|
|
|
|
assert(error->occurred == 0);
|
|
(void) Runtime_Pop(runtime, error, argc+1);
|
|
assert(error->occurred == 0);
|
|
|
|
Object *rets[8];
|
|
unsigned int maxrets = sizeof(rets)/sizeof(rets[0]);
|
|
|
|
int num_rets = Object_Call(callable, argv, argc, rets, maxrets, runtime->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:
|
|
{
|
|
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 0;
|
|
}
|
|
|
|
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;
|
|
|
|
assert(error->occurred == 0);
|
|
|
|
Error dummy;
|
|
Error_Init(&dummy); // We want to catch the error reported by this Object_Select.
|
|
|
|
Object *val = Object_Select(col, key, runtime->heap, &dummy);
|
|
|
|
if(val == NULL)
|
|
{
|
|
Error_Free(&dummy);
|
|
|
|
val = Object_NewNone(runtime->heap, error);
|
|
|
|
if(val == NULL)
|
|
return 0;
|
|
}
|
|
|
|
assert(error->occurred == 0);
|
|
|
|
if(!Runtime_Push(runtime, error, val))
|
|
return 0;
|
|
|
|
assert(error->occurred == 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 0;
|
|
}
|
|
|
|
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 0;
|
|
}
|
|
|
|
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 *locations[] = {
|
|
runtime->frame->locals,
|
|
runtime->frame->closure,
|
|
Runtime_GetBuiltins(runtime),
|
|
};
|
|
|
|
Object *obj = NULL;
|
|
|
|
for(int p = 0; obj == NULL && (unsigned int) p < sizeof(locations)/sizeof(locations[0]); p += 1)
|
|
{
|
|
if(locations[p] == NULL)
|
|
continue;
|
|
|
|
obj = Object_Select(locations[p], key, Runtime_GetHeap(runtime), error);
|
|
}
|
|
|
|
if(obj == NULL)
|
|
{
|
|
if(error->occurred == 0)
|
|
// There's no such variable.
|
|
Error_Report(error, 0, "Reference to undefined variable \"%s\"", ops[0].as_string);
|
|
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 *closure = Object_NewClosure(runtime->frame->closure, runtime->frame->locals, Runtime_GetHeap(runtime), error);
|
|
|
|
if(closure == NULL)
|
|
return 0;
|
|
|
|
Object *obj = Object_FromNojaFunction(runtime, runtime->frame->exe, ops[0].as_int, ops[1].as_int, closure, 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:
|
|
{
|
|
assert(opc == 1);
|
|
assert(ops[0].type == OPTP_INT);
|
|
int retc = ops[0].as_int;
|
|
assert(retc >= 0);
|
|
assert(retc == runtime->frame->used);
|
|
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, 0, "Not a boolean");
|
|
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, 0, "Not a boolean");
|
|
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;
|
|
}
|
|
|
|
static _Bool collect(Runtime *runtime, Error *error)
|
|
{
|
|
Frame *frame = runtime->frame;
|
|
|
|
if(!Heap_StartCollection(runtime->heap, error))
|
|
return 0;
|
|
|
|
Heap_CollectReference(&runtime->builtins, runtime->heap);
|
|
|
|
while(frame)
|
|
{
|
|
Heap_CollectReference(&frame->locals, runtime->heap);
|
|
Heap_CollectReference(&frame->closure, runtime->heap);
|
|
frame = frame->prev;
|
|
}
|
|
|
|
for(unsigned int i = 0; i < Stack_Size(runtime->stack); i += 1)
|
|
{
|
|
Object **ref = (Object**) Stack_TopRef(runtime->stack, -i);
|
|
assert(ref != NULL);
|
|
|
|
Heap_CollectReference(ref, runtime->heap);
|
|
}
|
|
|
|
return Heap_StopCollection(runtime->heap);
|
|
}
|
|
|
|
int run(Runtime *runtime, Error *error, Executable *exe, int index, Object *closure, Object **argv, int argc, Object **rets, int maxretc)
|
|
{
|
|
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 -1;
|
|
}
|
|
|
|
assert(runtime->depth < MAX_FRAMES);
|
|
|
|
// Initialize the frame.
|
|
Frame frame;
|
|
{
|
|
frame.prev = NULL;
|
|
frame.closure = closure;
|
|
frame.locals = Object_NewMap(-1, runtime->heap, error);
|
|
frame.exe = Executable_Copy(exe);
|
|
frame.index = index;
|
|
frame.used = 0;
|
|
|
|
if(frame.locals == NULL)
|
|
return -1;
|
|
|
|
if(frame.exe == NULL)
|
|
{
|
|
Error_Report(error, 1, "Failed to copy executable");
|
|
return -1;
|
|
}
|
|
|
|
// Add the frame to the runtime.
|
|
frame.prev = runtime->frame;
|
|
runtime->frame = &frame;
|
|
runtime->depth += 1;
|
|
}
|
|
|
|
// This is what the function will return.
|
|
int retc = -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;
|
|
}
|
|
|
|
//printf("%2.2f%% percent.\n", Heap_GetUsagePercentage(runtime->heap));
|
|
|
|
if(Heap_GetUsagePercentage(runtime->heap) > 100)
|
|
if(!collect(runtime, error))
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
while(step(runtime, error))
|
|
{
|
|
if(Heap_GetUsagePercentage(runtime->heap) > 100)
|
|
if(!collect(runtime, error))
|
|
break;
|
|
|
|
//printf("%2.2f%% percent.\n", Heap_GetUsagePercentage(runtime->heap));
|
|
}
|
|
|
|
// If an error occurred, we want to return NULL.
|
|
if(error->occurred == 0)
|
|
{
|
|
retc = MIN(frame.used, maxretc);
|
|
|
|
for(int i = 0; i < retc; i += 1)
|
|
{
|
|
rets[i] = Stack_Top(runtime->stack, i - retc + 1);
|
|
assert(rets[i] != 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 retc;
|
|
} |