forked from ground/cground
354 lines
13 KiB
C
354 lines
13 KiB
C
#include "interpreter.h"
|
|
#include "parser.h"
|
|
#include "types.h"
|
|
#include "include/uthash.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
int currentInstruction = 0;
|
|
|
|
void runtimeError(GroundRuntimeError error, char* what, GroundInstruction* where, int whereLine) {
|
|
printf("Ground runtime error:\n ErrorType: ");
|
|
switch (error) {
|
|
case ARG_TYPE_MISMATCH: {
|
|
printf("ArgTypeMismatch");
|
|
break;
|
|
}
|
|
case TOO_FEW_ARGS: {
|
|
printf("TooFewArgs");
|
|
break;
|
|
}
|
|
case TOO_MANY_ARGS: {
|
|
printf("TooManyArgs");
|
|
break;
|
|
}
|
|
case UNKNOWN_LABEL: {
|
|
printf("UnknownLabel");
|
|
break;
|
|
}
|
|
case UNKNOWN_VARIABLE: {
|
|
printf("UnknownVariable");
|
|
break;
|
|
}
|
|
default:
|
|
case FIXME: {
|
|
printf("FIXME (please report issue to https://chsp.au/ground/cground)");
|
|
}
|
|
}
|
|
printf("\n");
|
|
if (what != NULL) {
|
|
printf(" ErrorContext: %s\n", what);
|
|
}
|
|
if (where != NULL) {
|
|
printf(" ErrorInstruction: ");
|
|
printGroundInstruction(where);
|
|
printf("\n");
|
|
}
|
|
if (whereLine > -1) {
|
|
printf(" ErrorLine: %d\n", whereLine + 1);
|
|
}
|
|
exit(1);
|
|
}
|
|
|
|
GroundLabel* findLabel(GroundLabel* head, const char *id) {
|
|
GroundLabel *item;
|
|
HASH_FIND_STR(head, id, item);
|
|
return item;
|
|
}
|
|
|
|
void deleteLabel(GroundLabel** head, GroundLabel *item) {
|
|
HASH_DEL(*head, item);
|
|
free(item);
|
|
}
|
|
|
|
void addLabel(GroundLabel **head, const char *id, int data) {
|
|
GroundLabel* label = findLabel(*head, id);
|
|
if (label) {
|
|
deleteLabel(head, label);
|
|
}
|
|
GroundLabel* item = malloc(sizeof(GroundLabel));
|
|
snprintf(item->id, MAX_ID_LEN, "%s", id);
|
|
item->lineNum = data;
|
|
HASH_ADD_STR(*head, id, item);
|
|
}
|
|
|
|
GroundVariable* findVariable(GroundVariable* head, const char *id) {
|
|
GroundVariable *item;
|
|
HASH_FIND_STR(head, id, item);
|
|
return item;
|
|
}
|
|
|
|
void deleteVariable(GroundVariable** head, GroundVariable *item) {
|
|
HASH_DEL(*head, item);
|
|
free(item);
|
|
}
|
|
|
|
void addVariable(GroundVariable **head, const char *id, GroundValue data) {
|
|
GroundVariable* variable = findVariable(*head, id);
|
|
if (variable) {
|
|
deleteVariable(head, variable);
|
|
}
|
|
GroundVariable* item = malloc(sizeof(GroundVariable));
|
|
snprintf(item->id, MAX_ID_LEN, "%s", id);
|
|
item->value = data;
|
|
HASH_ADD_STR(*head, id, item);
|
|
}
|
|
|
|
void interpretGroundProgram(GroundProgram* in) {
|
|
GroundLabel* labels = NULL;
|
|
GroundVariable* variables = NULL;
|
|
|
|
GroundScope scope;
|
|
scope.labels = &labels;
|
|
scope.variables = &variables;
|
|
// Preprocess all labels
|
|
for (int i = 0; i < in->size; i++) {
|
|
if (in->instructions[i].type == CREATELABEL) {
|
|
addLabel(scope.labels, in->instructions[i].args.args[0].value.refName, i);
|
|
}
|
|
}
|
|
while (currentInstruction < in->size) {
|
|
interpretGroundInstruction(in->instructions[currentInstruction], &scope);
|
|
currentInstruction++;
|
|
}
|
|
|
|
}
|
|
|
|
void interpretGroundInstruction(GroundInstruction inst, GroundScope* scope) {
|
|
GroundInstruction copied_inst = copyGroundInstruction(&inst);
|
|
GroundInstruction* in = &copied_inst;
|
|
|
|
// Insert variables prefixed with $
|
|
for (int i = 0; i < in->args.length; i++) {
|
|
if (in->args.args[i].type == VALREF) {
|
|
GroundVariable* variable = findVariable(*scope->variables, in->args.args[i].value.refName);
|
|
if (variable) {
|
|
free(in->args.args[i].value.refName); // Free the strdup'd refName
|
|
in->args.args[i].value.value = variable->value;
|
|
in->args.args[i].type = VALUE;
|
|
} else {
|
|
runtimeError(UNKNOWN_VARIABLE, NULL, in, currentInstruction);
|
|
}
|
|
}
|
|
}
|
|
switch (in->type) {
|
|
// We can safely ignore any CREATELABEL instructions, these have been preprocessed
|
|
case CREATELABEL: {
|
|
break;
|
|
}
|
|
case IF: {
|
|
if (in->args.length < 2) {
|
|
runtimeError(TOO_FEW_ARGS, "Expecting 2 arguments", in, currentInstruction);
|
|
}
|
|
if (in->args.length > 2) {
|
|
runtimeError(TOO_MANY_ARGS, "Expecting 2 arguments", in, currentInstruction);
|
|
}
|
|
if (in->args.args[0].type != VALUE || in->args.args[0].value.value.type != BOOL) {
|
|
runtimeError(ARG_TYPE_MISMATCH, "Expecting a bool for arg 0", in, currentInstruction);
|
|
}
|
|
if (in->args.args[1].type != LINEREF) {
|
|
runtimeError(ARG_TYPE_MISMATCH, "Expecting a LineRef for arg 1", in, currentInstruction);
|
|
}
|
|
|
|
if (in->args.args[0].value.value.data.boolVal) {
|
|
GroundLabel* label = findLabel(*scope->labels, in->args.args[1].value.refName);
|
|
if (label) {
|
|
currentInstruction = label->lineNum;
|
|
} else {
|
|
runtimeError(UNKNOWN_LABEL, NULL, in, currentInstruction);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case JUMP: {
|
|
if (in->args.length < 1) {
|
|
runtimeError(TOO_FEW_ARGS, "Expecting 1 argument", in, currentInstruction);
|
|
}
|
|
if (in->args.length > 1) {
|
|
runtimeError(TOO_MANY_ARGS, "Expecting 1 argument", in, currentInstruction);
|
|
}
|
|
if (in->args.args[0].type != LINEREF) {
|
|
runtimeError(ARG_TYPE_MISMATCH, "Expecting a LineRef for arg 1", in, currentInstruction);
|
|
}
|
|
|
|
GroundLabel* label = findLabel(*scope->labels, in->args.args[0].value.refName);
|
|
if (label) {
|
|
currentInstruction = label->lineNum;
|
|
} else {
|
|
runtimeError(UNKNOWN_LABEL, NULL, in, currentInstruction);
|
|
}
|
|
break;
|
|
}
|
|
case END: {
|
|
if (in->args.length < 1 || in->args.args[0].type != VALUE || in->args.args[0].value.value.type != INT) {
|
|
exit(0);
|
|
} else {
|
|
exit(in->args.args[0].value.value.data.intVal);
|
|
}
|
|
break;
|
|
}
|
|
case PRINT: {
|
|
if (in->args.length < 1) {
|
|
runtimeError(TOO_FEW_ARGS, "Expecting 1 or more args", in, currentInstruction);
|
|
}
|
|
for (int i = 0; i < in->args.length; i++) {
|
|
printGroundArg(&in->args.args[i]);
|
|
printf(" ");
|
|
}
|
|
break;
|
|
}
|
|
case PRINTLN: {
|
|
if (in->args.length < 1) {
|
|
runtimeError(TOO_FEW_ARGS, "Expecting 1 or more args", in, currentInstruction);
|
|
}
|
|
for (int i = 0; i < in->args.length; i++) {
|
|
printGroundArg(&in->args.args[i]);
|
|
printf(" ");
|
|
}
|
|
printf("\n");
|
|
break;
|
|
}
|
|
|
|
case SET: {
|
|
if (in->args.length < 2) {
|
|
runtimeError(TOO_FEW_ARGS, "Expecting 2 args", in, currentInstruction);
|
|
}
|
|
if (in->args.length > 2) {
|
|
runtimeError(TOO_MANY_ARGS, "Expecting 2 args", in, currentInstruction);
|
|
}
|
|
if (in->args.args[0].type != DIRREF) {
|
|
runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef for arg 1", in, currentInstruction);
|
|
}
|
|
if (in->args.args[1].type != VALUE) {
|
|
runtimeError(ARG_TYPE_MISMATCH, "Expecting a Value for arg 2", in, currentInstruction);
|
|
}
|
|
|
|
addVariable(scope->variables, in->args.args[0].value.refName, in->args.args[1].value.value);
|
|
break;
|
|
}
|
|
|
|
case ADD: {
|
|
if (in->args.length < 3) {
|
|
runtimeError(TOO_FEW_ARGS, "Expecting 3 args", in, currentInstruction);
|
|
}
|
|
if (in->args.length > 3) {
|
|
runtimeError(TOO_MANY_ARGS, "Expecting 3 args", in, currentInstruction);
|
|
}
|
|
if (in->args.args[0].type != VALUE) {
|
|
runtimeError(ARG_TYPE_MISMATCH, "Expecting a Value for arg 1", in, currentInstruction);
|
|
}
|
|
if (in->args.args[1].type != VALUE) {
|
|
runtimeError(ARG_TYPE_MISMATCH, "Expecting a Value for arg 2", in, currentInstruction);
|
|
}
|
|
if (in->args.args[2].type != DIRREF) {
|
|
runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef for arg 3", in, currentInstruction);
|
|
}
|
|
|
|
GroundValue* left = &in->args.args[0].value.value;
|
|
GroundValue* right = &in->args.args[1].value.value;
|
|
|
|
if (left->type == STRING) {
|
|
if (right->type != STRING) {
|
|
runtimeError(ARG_TYPE_MISMATCH, "Expecting a String for arg 2", in, currentInstruction);
|
|
}
|
|
char* newString = malloc(strlen(left->data.stringVal) + strlen(right->data.stringVal));
|
|
strcpy(newString, left->data.stringVal);
|
|
strcat(newString, right->data.stringVal);
|
|
|
|
addVariable(scope->variables, in->args.args[2].value.refName, createStringGroundValue(newString));
|
|
}
|
|
else if (left->type == INT || left->type == DOUBLE) {
|
|
if (right->type != INT && right->type != DOUBLE) {
|
|
runtimeError(ARG_TYPE_MISMATCH, "Expecting an Int or Double for arg 2", in, currentInstruction);
|
|
}
|
|
|
|
if (left->type == DOUBLE || right->type == DOUBLE) {
|
|
double result = 0;
|
|
|
|
if (left->type == INT) {
|
|
result += left->data.intVal;
|
|
} else if (left->type == DOUBLE) {
|
|
result += left->data.doubleVal;
|
|
}
|
|
|
|
if (right->type == INT) {
|
|
result += right->data.intVal;
|
|
} else if (right->type == DOUBLE) {
|
|
result += right->data.doubleVal;
|
|
}
|
|
|
|
addVariable(scope->variables, in->args.args[2].value.refName, createDoubleGroundValue(result));
|
|
} else {
|
|
int64_t result = left->data.intVal + right->data.intVal;
|
|
addVariable(scope->variables, in->args.args[2].value.refName, createIntGroundValue(result));
|
|
}
|
|
} else {
|
|
runtimeError(ARG_TYPE_MISMATCH, "Expecting an Int, Double, or String for arg 1", in, currentInstruction);
|
|
}
|
|
break;
|
|
}
|
|
case EQUAL: {
|
|
if (in->args.length < 3) {
|
|
runtimeError(TOO_FEW_ARGS, "Expecting 3 args", in, currentInstruction);
|
|
}
|
|
if (in->args.length > 3) {
|
|
runtimeError(TOO_MANY_ARGS, "Expecting 3 args", in, currentInstruction);
|
|
}
|
|
if (in->args.args[0].type != VALUE) {
|
|
runtimeError(ARG_TYPE_MISMATCH, "Expecting a Value for arg 1", in, currentInstruction);
|
|
}
|
|
if (in->args.args[1].type != VALUE) {
|
|
runtimeError(ARG_TYPE_MISMATCH, "Expecting a Value for arg 2", in, currentInstruction);
|
|
}
|
|
if (in->args.args[2].type != DIRREF) {
|
|
runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef for arg 3", in, currentInstruction);
|
|
}
|
|
|
|
GroundValue* left = &in->args.args[0].value.value;
|
|
GroundValue* right = &in->args.args[1].value.value;
|
|
|
|
if (left->type != right->type) {
|
|
addVariable(scope->variables, in->args.args[2].value.refName, createBoolGroundValue(false));
|
|
} else {
|
|
switch (left->type) {
|
|
case INT: {
|
|
bool cond = (left->data.intVal == right->data.intVal);
|
|
addVariable(scope->variables, in->args.args[2].value.refName, createBoolGroundValue(cond));
|
|
break;
|
|
}
|
|
case DOUBLE: {
|
|
bool cond = (left->data.doubleVal == right->data.doubleVal);
|
|
addVariable(scope->variables, in->args.args[2].value.refName, createBoolGroundValue(cond));
|
|
break;
|
|
}
|
|
case STRING: {
|
|
bool cond = (strcmp(left->data.stringVal, right->data.stringVal) == 0);
|
|
addVariable(scope->variables, in->args.args[2].value.refName, createBoolGroundValue(cond));
|
|
break;
|
|
}
|
|
case CHAR: {
|
|
bool cond = (left->data.charVal == right->data.charVal);
|
|
addVariable(scope->variables, in->args.args[2].value.refName, createBoolGroundValue(cond));
|
|
break;
|
|
}
|
|
case BOOL: {
|
|
bool cond = (left->data.boolVal == right->data.boolVal);
|
|
addVariable(scope->variables, in->args.args[2].value.refName, createBoolGroundValue(cond));
|
|
break;
|
|
}
|
|
default: {
|
|
addVariable(scope->variables, in->args.args[2].value.refName, createBoolGroundValue(false));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
runtimeError(FIXME, "Currently unimplemented instruction", in, currentInstruction);
|
|
}
|
|
}
|
|
|
|
freeGroundInstruction(in);
|
|
}
|