#include "interpreter.h" #include "parser.h" #include "types.h" #include "include/uthash.h" #include #include 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; } case LIST_ERROR: { printf("ListError"); break; } case STRING_ERROR: { printf("StringError"); break; } case MATH_ERROR: { printf("MathError"); 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) { freeGroundValue(&variable->value); deleteVariable(head, variable); } GroundVariable* item = malloc(sizeof(GroundVariable)); snprintf(item->id, MAX_ID_LEN, "%s", id); item->value = copyGroundValue(&data); HASH_ADD_STR(*head, id, item); } GroundFunction* createGroundFunction() { GroundFunction* gf = malloc(sizeof(GroundFunction)); gf->argSize = 0; gf->args = malloc(sizeof(GroundFunctionArgs)); gf->program = createGroundProgram(); return gf; } void addArgsToGroundFunction(GroundFunction* function, GroundValueType type, char* name) { GroundFunctionArgs arg; arg.type = type; arg.name = name; function->argSize ++; GroundFunctionArgs* ptr = realloc(function->args, function->argSize * sizeof(GroundFunctionArgs)); if (ptr == NULL) { printf("Failed to allocate memory for arg of GroundFunction"); function->argSize --; return; } function->args = ptr; function->args[function->argSize - 1] = arg; }; GroundValueType stringToValueType(char* in) { if (strcmp(in, "int") == 0) { return INT; } if (strcmp(in, "double") == 0) { return DOUBLE; } if (strcmp(in, "bool") == 0) { return BOOL; } if (strcmp(in, "string") == 0) { return STRING; } if (strcmp(in, "char") == 0) { return CHAR; } if (strcmp(in, "list") == 0) { return LIST; } if (strcmp(in, "function") == 0) { return FUNCTION; } return CUSTOM; } GroundDebugInstruction parseDebugInstruction(char* in) { GroundDebugInstruction gdi; size_t insize = strlen(in); size_t spacepos = -1; for (size_t i = 0; i < insize; i++) { if (in[i] == ' ') { spacepos = i; break; } } if (spacepos == -1) { spacepos = insize; } char* instruction = malloc(spacepos + 1); if (!instruction) { gdi.type = UNKNOWN; gdi.arg = NULL; return gdi; } strncpy(instruction, in, spacepos); instruction[spacepos] = '\0'; if (strcmp(instruction, "dump") == 0) { gdi.type = DUMP; } else if (strcmp(instruction, "inspect") == 0) { gdi.type = INSPECT; } else if (strcmp(instruction, "eval") == 0) { gdi.type = EVAL; } else if (strcmp(instruction, "continue") == 0) { gdi.type = CONTINUE; } else if (strcmp(instruction, "exit") == 0) { gdi.type = EXIT; } else if (strcmp(instruction, "help") == 0) { gdi.type = HELP; } else { gdi.type = UNKNOWN; } size_t arglen = insize - spacepos; gdi.arg = malloc(arglen + 1); if (gdi.arg) { strcpy(gdi.arg, in + spacepos + 1); } free(instruction); return gdi; } GroundValue interpretGroundProgram(GroundProgram* in, GroundScope* inScope) { GroundLabel* labels = NULL; GroundVariable* variables = NULL; GroundScope scope; if (inScope != NULL) { scope = *inScope; } else { scope.labels = &labels; scope.variables = &variables; } // Preprocess all labels and functions 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); } if (in->instructions[i].type == FUN) { GroundFunction* function = createGroundFunction(); function->startLine = i; if (in->instructions[i].args.length < 1) { runtimeError(TOO_FEW_ARGS, "Expecting 1 or more args", &in->instructions[i], i); } if (in->instructions[i].args.args[0].type != FNREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a FunctionRef for arg 1", &in->instructions[i], i); } char* functionName = in->instructions[i].args.args[0].value.refName; if (in->instructions[i].args.length < 2) { function->returnType = NONE; } else { if (in->instructions[i].args.args[1].type != TYPEREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a TypeRef for arg 2", &in->instructions[i], i); } GroundArg* args = in->instructions[i].args.args; size_t length = in->instructions[i].args.length; for (size_t j = 2; j < length; j += 2) { if (args[j].type != TYPEREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a TypeRef", &in->instructions[i], i); } if (j + 1 >= length) { runtimeError(TOO_FEW_ARGS, "Expecting a DirectRef after a TypeRef", &in->instructions[i], i); } if (args[j + 1].type != DIRREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef after a TypeRef", &in->instructions[i], i); } addArgsToGroundFunction(function, stringToValueType(args[j].value.refName), args[j + 1].value.refName); } } i++; while (in->instructions[i].type != ENDFUN) { addInstructionToProgram(&function->program, in->instructions[i]); i++; } addVariable(scope.variables, functionName, createFunctionGroundValue(function)); } } for (int i = 0; i < in->size; i++) { if (in->instructions[i].type == FUN) { int count = 1; while (count > 0) { i++; if (i >= in->size) { return createNoneGroundValue(); } if (in->instructions[i].type == FUN) { count++; } if (in->instructions[i].type == ENDFUN) { count--; } } } if (in->instructions[i].type == PAUSE) { printf("Paused execution\n"); printf("Previous instruction: "); if (i > 0) { printGroundInstruction(&in->instructions[i - 1]); } while (true) { printf("\nprompt> "); char buffer[256]; if (fgets(buffer, sizeof(buffer), stdin) != NULL) { buffer[strcspn(buffer, "\n")] = '\0'; } else { runtimeError(FIXME, "Failed to read input from console with fgets", NULL, -1); } GroundDebugInstruction gdi = parseDebugInstruction(buffer); bool shouldBreak = false; switch (gdi.type) { case CONTINUE: { shouldBreak = true; break; } case EXIT: { exit(0); break; } case DUMP: { if (scope.variables == NULL) { printf("Can't access variables"); break; } if (*scope.variables == NULL) { printf("Can't access variables"); break; } GroundVariable* entry; GroundVariable* tmp; GroundVariable* head = *scope.variables; HASH_ITER(hh, head, entry, tmp) { if (entry != NULL) { printf("%s: ", entry->id); printGroundValue(&entry->value); printf("\n"); } } break; } case INSPECT: { printf("%s: ", gdi.arg); GroundVariable* var = findVariable(*scope.variables, gdi.arg); if (var == NULL) { printf("(nothing)"); } else { printGroundValue(&var->value); } break; } case EVAL: { GroundProgram program = parseFile(gdi.arg); interpretGroundProgram(&program, &scope); freeGroundProgram(&program); break; } case HELP: { printf("Ground Debugger Help\n"); printf("Didn't mean to end up here? Type \"continue\" to escape this prompt.\n"); printf("Commands:\n"); printf(" continue: Continue execution of the program\n"); printf(" exit: Stop execution early\n"); printf(" dump: Shows all variables and their contents\n"); printf(" inspect (variable): Shows the contents of a variable\n"); printf(" eval (code): Runs Ground code in the current scope\n"); printf(" help: Shows this help message"); } case UNKNOWN: { printf("Unknown instruction (type \"help\" for help)"); break; } } if (shouldBreak) { break; } } continue; } int ci = currentInstruction; GroundValue gv = interpretGroundInstruction(in->instructions[i], &scope); if (gv.type != NONE) { return gv; } if (ci != currentInstruction) { i = currentInstruction; } currentInstruction++; } return createNoneGroundValue(); } GroundValue 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 = copyGroundValue(&variable->value); in->args.args[i].type = VALUE; } else { runtimeError(UNKNOWN_VARIABLE, NULL, in, currentInstruction); } } } switch (in->type) { // We can safely ignore any CREATELABEL, FUN, and ENDFUN instructions, these have been preprocessed case FUN: case ENDFUN: case CREATELABEL: { break; } /* * CONTROL FLOW * These instructions are for controlling how the program is executed. * Instructions: * if, jump, end */ 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; } /* * I/O * These instructions take information from the user, and print it out. * Instructions: * print, println, input */ 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++) { if (i != 0) printf(" "); printGroundArg(&in->args.args[i]); } 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++) { if (i != 0) printf(" "); printGroundArg(&in->args.args[i]); } printf("\n"); break; } case INPUT: { if (in->args.length < 1) { runtimeError(TOO_FEW_ARGS, "Expecting 1 arg", in, currentInstruction); } if (in->args.args[0].type != DIRREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef for arg 1", in, currentInstruction); } char buffer[256]; if (fgets(buffer, sizeof(buffer), stdin) != NULL) { buffer[strcspn(buffer, "\n")] = '\0'; addVariable(scope->variables, in->args.args[0].value.refName, createStringGroundValue(buffer)); } else { runtimeError(FIXME, "Failed to read input from console with fgets", in, currentInstruction); } break; } /* * VARIABLES AND LISTS * These instructions are for initializing variables and lists. * Instructions: * set, gettype, exists, setlist, setlistat, getlistat, getlistsize, listappend */ 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 GETTYPE: { 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 != VALUE) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a Value for arg 1", in, currentInstruction); } if (in->args.args[1].type != DIRREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef for arg 2", in, currentInstruction); } switch (in->args.args[0].value.value.type) { case INT: { addVariable(scope->variables, in->args.args[1].value.refName, createStringGroundValue("int")); break; } case DOUBLE: { addVariable(scope->variables, in->args.args[1].value.refName, createStringGroundValue("double")); break; } case STRING: { addVariable(scope->variables, in->args.args[1].value.refName, createStringGroundValue("string")); break; } case CHAR: { addVariable(scope->variables, in->args.args[1].value.refName, createStringGroundValue("char")); break; } case BOOL: { addVariable(scope->variables, in->args.args[1].value.refName, createStringGroundValue("bool")); break; } case LIST: { addVariable(scope->variables, in->args.args[1].value.refName, createStringGroundValue("list")); break; } case CUSTOM: { addVariable(scope->variables, in->args.args[1].value.refName, createStringGroundValue("custom")); break; } case NONE: { addVariable(scope->variables, in->args.args[1].value.refName, createStringGroundValue("none")); } } break; } case EXISTS: { 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[0].type != DIRREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef for arg 2", in, currentInstruction); } if (findVariable(*scope->variables, in->args.args[0].value.refName)) { addVariable(scope->variables, in->args.args[1].value.refName, createBoolGroundValue(true)); } else { addVariable(scope->variables, in->args.args[1].value.refName, createBoolGroundValue(false)); } break; } case SETLIST: { if (in->args.length < 1) { runtimeError(TOO_FEW_ARGS, "Expecting 1 arg", in, currentInstruction); } if (in->args.args[0].type != DIRREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef for arg 1", in, currentInstruction); } List newList = createList(); for (int i = 1; i < in->args.length; i++) { if (in->args.args[i].type != VALUE) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a Value for all args after arg 1", in, currentInstruction); } appendToList(&newList, in->args.args[i].value.value); } addVariable(scope->variables, in->args.args[0].value.refName, createListGroundValue(newList)); break; } case SETLISTAT: { if (in->args.length < 3) { runtimeError(TOO_FEW_ARGS, "Expecting 3 args", in, currentInstruction); } if (in->args.args[0].type != DIRREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef to a List for arg 1", in, currentInstruction); } if (in->args.args[1].type != VALUE && in->args.args[1].value.value.type != INT) { runtimeError(ARG_TYPE_MISMATCH, "Expecting an Int value for arg 2", in, currentInstruction); } if (in->args.args[2].type != VALUE) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a Value for arg 3", in, currentInstruction); } GroundVariable* var = findVariable(*scope->variables, in->args.args[0].value.refName); if (var == NULL) { runtimeError(UNKNOWN_VARIABLE, NULL, in, currentInstruction); } GroundValue* value = &var->value; if (value == NULL) { runtimeError(FIXME, NULL, in, currentInstruction); } if (value->type != LIST) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef to a List for arg 1", in, currentInstruction); } ListAccessStatus status = setListAt(&value->data.listVal, in->args.args[1].value.value.data.intVal, in->args.args[2].value.value); switch (status) { case LIST_OKAY: break; case LIST_OUT_OF_BOUNDS: runtimeError(LIST_ERROR, "Out of bounds index", in, currentInstruction); case LIST_FIXME: default: runtimeError(FIXME, "List related error", in, currentInstruction); } break; } case GETLISTAT: { if (in->args.length < 2) { runtimeError(TOO_FEW_ARGS, "Expecting 2 args", in, currentInstruction); } if (in->args.args[0].type != DIRREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef to a List for arg 1", in, currentInstruction); } if (in->args.args[1].type != VALUE && in->args.args[1].value.value.type != INT) { runtimeError(ARG_TYPE_MISMATCH, "Expecting an integer value for arg 2", in, currentInstruction); } if (in->args.args[2].type != DIRREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef to a List for arg 3", in, currentInstruction); } GroundVariable* var = findVariable(*scope->variables, in->args.args[0].value.refName); if (var == NULL) { runtimeError(UNKNOWN_VARIABLE, NULL, in, currentInstruction); } GroundValue* value = &var->value; if (value == NULL) { runtimeError(FIXME, NULL, in, currentInstruction); } if (value->type != LIST) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef to a List for arg 1", in, currentInstruction); } ListAccess status = getListAt(&value->data.listVal, in->args.args[1].value.value.data.intVal); switch (status.status) { case LIST_OKAY: { addVariable(scope->variables, in->args.args[2].value.refName, *status.value); break; } case LIST_OUT_OF_BOUNDS: runtimeError(LIST_ERROR, "Out of bounds index", in, currentInstruction); case LIST_FIXME: default: runtimeError(FIXME, "List related error", in, currentInstruction); } break; } case GETLISTSIZE: { if (in->args.length < 2) { runtimeError(TOO_FEW_ARGS, "Expecting 2 args", in, currentInstruction); } if (in->args.args[0].type != DIRREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef to a List for arg 1", in, currentInstruction); } if (in->args.args[1].type != DIRREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef for arg 2", in, currentInstruction); } GroundVariable* var = findVariable(*scope->variables, in->args.args[0].value.refName); if (var == NULL) { runtimeError(UNKNOWN_VARIABLE, NULL, in, currentInstruction); } GroundValue* value = &var->value; if (value == NULL) { runtimeError(FIXME, NULL, in, currentInstruction); } if (value->type != LIST) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef to a List for arg 1", in, currentInstruction); } addVariable(scope->variables, in->args.args[1].value.refName, createIntGroundValue(value->data.listVal.size)); break; } case LISTAPPEND: { if (in->args.length < 2) { runtimeError(TOO_FEW_ARGS, "Expecting 2 args", in, currentInstruction); } if (in->args.args[0].type != DIRREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef to a List for arg 1", in, currentInstruction); } if (in->args.args[1].type != VALUE) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a Value for arg 2", in, currentInstruction); } GroundVariable* var = findVariable(*scope->variables, in->args.args[0].value.refName); if (var == NULL) { runtimeError(UNKNOWN_VARIABLE, NULL, in, currentInstruction); } GroundValue* value = &var->value; if (value == NULL) { runtimeError(FIXME, NULL, in, currentInstruction); } if (value->type != LIST) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef to a List for arg 1", in, currentInstruction); } appendToList(&value->data.listVal, in->args.args[1].value.value); break; } /* * MATHS * These instructions allow running mathematical operations on values. * Instructions: * add, subtract, multiply, divide */ 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 SUBTRACT: { 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 == 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 or Double for arg 1", in, currentInstruction); } break; } case MULTIPLY: { 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 == 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 = 1.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 or Double for arg 1", in, currentInstruction); } break; } case DIVIDE: { 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 == 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 (right->type == INT && right->data.intVal == 0) { runtimeError(MATH_ERROR, "Division by zero", 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 or Double for arg 1", in, currentInstruction); } break; } /* * STRING OPERATIONS * Allows easier manipulation of strings. * Instructions: * getstrcharat, getstrsize */ case GETSTRCHARAT: { 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 && in->args.args[0].value.value.type != STRING) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a String for arg 1", in, currentInstruction); } if (in->args.args[1].type != VALUE && in->args.args[1].value.value.type != INT) { runtimeError(ARG_TYPE_MISMATCH, "Expecting an Int for arg 2", in, currentInstruction); } if (in->args.args[2].type != DIRREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef for arg 3", in, currentInstruction); } char* str = in->args.args[0].value.value.data.stringVal; int64_t idx = in->args.args[1].value.value.data.intVal; if (idx < strlen(str)) { addVariable(scope->variables, in->args.args[2].value.refName, createCharGroundValue(str[idx])); } else { runtimeError(STRING_ERROR, "Out of bounds index", in, currentInstruction); } break; } case GETSTRSIZE: { 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 != VALUE && in->args.args[0].value.value.type != STRING) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a String for arg 1", in, currentInstruction); } if (in->args.args[1].type != DIRREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef for arg 2", in, currentInstruction); } addVariable(scope->variables, in->args.args[1].value.refName, createIntGroundValue(strlen(in->args.args[0].value.value.data.stringVal))); break; } /* * COMPARISONS * Allows comparing values. * Instructions: * equal, inequal, not, greater, lesser */ 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) { if (left->type == INT && right->type == DOUBLE) { bool cond = (left->data.intVal == right->data.doubleVal); addVariable(scope->variables, in->args.args[2].value.refName, createBoolGroundValue(cond)); } else if (left->type == DOUBLE && right->type == INT) { bool cond = (left->data.doubleVal == right->data.intVal); addVariable(scope->variables, in->args.args[2].value.refName, createBoolGroundValue(cond)); } else { 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; } case INEQUAL: { 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) { if (left->type == INT && right->type == DOUBLE) { bool cond = (left->data.intVal != right->data.doubleVal); addVariable(scope->variables, in->args.args[2].value.refName, createBoolGroundValue(cond)); } else if (left->type == DOUBLE && right->type == INT) { bool cond = (left->data.doubleVal != right->data.intVal); addVariable(scope->variables, in->args.args[2].value.refName, createBoolGroundValue(cond)); } else { 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(true)); break; } } } break; } case NOT: { 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 != VALUE && in->args.args[0].value.value.type != BOOL) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a Bool for arg 1", in, currentInstruction); } if (in->args.args[1].type != DIRREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef for arg 2", in, currentInstruction); } bool condition = !in->args.args[0].value.value.data.boolVal; addVariable(scope->variables, in->args.args[1].value.refName, createBoolGroundValue(condition)); } default: { runtimeError(FIXME, "Currently unimplemented instruction", in, currentInstruction); } case GREATER: { 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 != INT && left->type != DOUBLE) { runtimeError(ARG_TYPE_MISMATCH, "Expecting an Int or Double for arg 1", in, currentInstruction); } else if (right->type != INT && right->type != DOUBLE) { runtimeError(ARG_TYPE_MISMATCH, "Expecting an Int or Double for arg 2", in, currentInstruction); } if (left->type != right->type) { if (left->type == INT && right->type == DOUBLE) { bool cond = (left->data.intVal > right->data.doubleVal); addVariable(scope->variables, in->args.args[2].value.refName, createBoolGroundValue(cond)); } else if (left->type == DOUBLE && right->type == INT) { bool cond = (left->data.doubleVal > right->data.intVal); addVariable(scope->variables, in->args.args[2].value.refName, createBoolGroundValue(cond)); } else { runtimeError(FIXME, "Uncaught invalid type", in, currentInstruction); } } 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; } default: { runtimeError(FIXME, "Uncaught invalid type", in, currentInstruction); break; } } } break; } case LESSER: { 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 != INT && left->type != DOUBLE) { runtimeError(ARG_TYPE_MISMATCH, "Expecting an Int or Double for arg 1", in, currentInstruction); } else if (right->type != INT && right->type != DOUBLE) { runtimeError(ARG_TYPE_MISMATCH, "Expecting an Int or Double for arg 2", in, currentInstruction); } if (left->type != right->type) { if (left->type == INT && right->type == DOUBLE) { bool cond = (left->data.intVal < right->data.doubleVal); addVariable(scope->variables, in->args.args[2].value.refName, createBoolGroundValue(cond)); } else if (left->type == DOUBLE && right->type == INT) { bool cond = (left->data.doubleVal < right->data.intVal); addVariable(scope->variables, in->args.args[2].value.refName, createBoolGroundValue(cond)); } else { runtimeError(FIXME, "Uncaught invalid type", in, currentInstruction); } } 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; } default: { runtimeError(FIXME, "Uncaught invalid type", in, currentInstruction); break; } } } break; } /* * FUNCTIONS * Allows execution of functions in Ground. * Instructions: * fun, return, endfun, pusharg, call */ case RETURN: { if (in->args.length < 1) { runtimeError(TOO_FEW_ARGS, "Expecting 1 arg", in, currentInstruction); } if (in->args.length > 1) { runtimeError(TOO_MANY_ARGS, "Expecting 1 arg", in, currentInstruction); } if (in->args.args[0].type != VALUE) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a Value for arg 1", in, currentInstruction); } return in->args.args[0].value.value; } case CALL: { 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 != FNREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a FunctionRef for arg 1", in, currentInstruction); } if (in->args.args[1].type != DIRREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef for arg 2", in, currentInstruction); } GroundVariable* variables = NULL; GroundLabel* labels = NULL; GroundScope newScope; newScope.variables = &variables; newScope.labels = &labels; GroundVariable* var = findVariable(*scope->variables, in->args.args[0].value.refName); if (var == NULL) { runtimeError(UNKNOWN_VARIABLE, "Function not found", in, currentInstruction); } GroundValue* value = &var->value; if (value->type != FUNCTION) { runtimeError(UNKNOWN_VARIABLE, "Provided reference does not reference a function", in, currentInstruction); } GroundFunction* function = value->data.fnVal; size_t currentCurrentInstruction = currentInstruction; currentInstruction = function->startLine; GroundValue returnValue = interpretGroundProgram(&function->program, &newScope); if (returnValue.type != function->returnType) { runtimeError(RETURN_TYPE_MISMATCH, "Unexpected return value type from function", in, currentInstruction); } addVariable(scope->variables, in->args.args[1].value.refName, returnValue); currentInstruction = currentCurrentInstruction; break; } } freeGroundInstruction(in); return createNoneGroundValue(); }