#include "interpreter.h" #include "parser.h" #include "types.h" #include "include/uthash.h" #include #include #include #include #include #include int currentInstruction = 0; bool isMainScopeGlobal = true; [[noreturn]] void customError(GroundArg type, GroundArg what, GroundInstruction* where, int whereLine, int exitCode) { printf("Ground runtime error:\n ErrorType: "); printGroundArg(&type); printf("\n ErrorContext: "); printGroundArg(&what); printf("\n"); if (where != NULL) { printf(" ErrorInstruction: "); printGroundInstruction(where); printf("\n"); } if (whereLine > -1) { printf(" ErrorLine: %d\n", whereLine + 1); } exit(exitCode); } [[noreturn]] 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; } case RETURN_TYPE_MISMATCH: { printf("ReturnTypeMismatch"); break; } case PREMATURE_EOF: { printf("PrematureEof"); break; } case INVALID_INSTRUCTION: { printf("InvalidInstruction"); 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); } [[noreturn]] void throwError(GroundError* error) { printf("Uncaught Ground runtime error:\n ErrorType: %s\n", error->type); if (error->what != NULL) { printf(" ErrorContext: %s\n", error->what); } if (error->where != NULL) { printf(" ErrorInstruction: "); printGroundInstruction(error->where); printf("\n"); } if (error->hasLine) { printf(" ErrorLine: %zu\n", error->line + 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(); gf->isNative = false; gf->nativeFn = NULL; 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 == (size_t) -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, "step") == 0) { gdi.type = STEP; } else if (strcmp(instruction, "view") == 0) { gdi.type = VIEW; } 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; } void groundAddNativeFunction(GroundScope* scope, char* name, NativeGroundFunction fn, GroundValueType returnType, int argCount, ...) { GroundFunction* gf = createGroundFunction(); gf->isNative = true; gf->nativeFn = fn; gf->returnType = returnType; va_list args; va_start(args, argCount); for(int i = 0; i < argCount; i++) { GroundValueType type = va_arg(args, GroundValueType); char* argName = va_arg(args, char*); addArgsToGroundFunction(gf, type, argName); } va_end(args); addVariable(scope->variables, name, createFunctionGroundValue(gf)); } GroundFunction* parseFunction(GroundProgram* in, size_t errorOffset) { GroundFunction* function = createGroundFunction(); for (size_t i = 0; i < in->size; i++) { 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], errorOffset + i); } GroundArg* args = in->instructions[i].args.args; function->returnType = stringToValueType(args[1].value.refName); 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], errorOffset + i); } if (j + 1 >= length) { runtimeError(TOO_FEW_ARGS, "Expecting a DirectRef after a TypeRef", &in->instructions[i], errorOffset + i); } if (args[j + 1].type != DIRREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef after a TypeRef", &in->instructions[i], errorOffset + i); } addArgsToGroundFunction(function, stringToValueType(args[j].value.refName), args[j + 1].value.refName); } } i++; while (i < in->size) { addInstructionToProgram(&function->program, in->instructions[i]); i++; } break; } return function; } GroundStruct parseStruct(GroundProgram* in, GroundScope* scope, size_t errorOffset) { GroundStruct gstruct = createStruct(); for (size_t i = 0; i < in->size; i++) { switch (in->instructions[i].type) { case SET: { if (in->instructions[i].args.length < 2) { runtimeError(TOO_FEW_ARGS, "Expecting 2 args", &in->instructions[i], i + errorOffset); } if (in->instructions[i].args.length > 2) { runtimeError(TOO_MANY_ARGS, "Expecting 2 args", &in->instructions[i], i + errorOffset); } if (in->instructions[i].args.args[0].type != DIRREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef for arg 1", &in->instructions[i], i + errorOffset); } if (in->instructions[i].args.args[1].type != VALUE) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a Value for arg 2", &in->instructions[i], i + errorOffset); } addFieldToStruct(&gstruct, in->instructions[i].args.args[0].value.refName, in->instructions[i].args.args[1].value.value); break; } case INIT: { if (in->instructions[i].args.length < 2) { runtimeError(TOO_FEW_ARGS, "Expecting 2 args", &in->instructions[i], currentInstruction); } if (in->instructions[i].args.length > 2) { runtimeError(TOO_MANY_ARGS, "Expecting 2 args", &in->instructions[i], currentInstruction); } if (in->instructions[i].args.args[0].type != DIRREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef for arg 1", &in->instructions[i], currentInstruction); } if (in->instructions[i].args.args[1].type != TYPEREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a TypeRef for arg 2", &in->instructions[i], currentInstruction); } GroundValue gv; switch (stringToValueType(in->instructions[i].args.args[0].value.refName)) { case INT: { gv = createIntGroundValue(0); break; } case DOUBLE: { gv = createDoubleGroundValue(0); break; } case STRING: { gv = createStringGroundValue(""); break; } case CHAR: { gv = createCharGroundValue('\0'); break; } case BOOL: { gv = createBoolGroundValue(false); break; } case LIST: { gv = createListGroundValue(createList()); break; } case FUNCTION: { gv = createFunctionGroundValue(createGroundFunction()); break; } case STRUCTVAL: { gv.type = STRUCTVAL; gv.data.structVal = malloc(sizeof(GroundStruct)); *gv.data.structVal = createStruct(); break; } case CUSTOM: { GroundVariable* var = findVariable(*scope->variables, in->instructions[i].args.args[1].value.refName); if (var == NULL) { runtimeError(UNKNOWN_VARIABLE, "Couldn't find the specified type", &in->instructions[i], currentInstruction); } if (var->value.type != STRUCTVAL) { runtimeError(ARG_TYPE_MISMATCH, "TypeRef does not reference a struct", &in->instructions[i], currentInstruction); } GroundStruct* gstruct = var->value.data.structVal; gv.type = CUSTOM; gv.data.customVal = malloc(sizeof(GroundObject)); *gv.data.customVal = createObject(*gstruct); gv.customType = gstruct; break; } case NONE: { gv.type = NONE; break; } default: { runtimeError(FIXME, "Reached should-be-impossible state", &in->instructions[i], currentInstruction); break; } } addFieldToStruct(&gstruct, in->instructions[i].args.args[0].value.refName, gv); break; } case FUN: { 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* name = malloc(strlen(in->instructions[i].args.args[0].value.refName) + 1); strcpy(name, in->instructions[i].args.args[0].value.refName); size_t counter = 1; GroundProgram gp = createGroundProgram(); addInstructionToProgram(&gp, in->instructions[i]); size_t errorOffset = i; i++; while (counter > 0) { if (i >= in->size) { runtimeError(PREMATURE_EOF, "Reached end of scope before function definition ended", &in->instructions[i - 1], i - 1); } if (in->instructions[i].type == FUN) { counter++; } if (in->instructions[i].type == ENDFUN) { counter--; } addInstructionToProgram(&gp, in->instructions[i]); i++; } GroundFunction* function = parseFunction(&gp, errorOffset); function->startLine = i; GroundValue gv = createFunctionGroundValue(function); addFieldToStruct(&gstruct, name, gv); break; } case ENDSTRUCT: { break; } default: { runtimeError(INVALID_INSTRUCTION, "Unsupported instruction while inside struct", &in->instructions[i], errorOffset + i); } } } return gstruct; } GroundValue interpretGroundProgram(GroundProgram* in, GroundScope* inScope) { GroundLabel* labels = NULL; GroundVariable* variables = NULL; int instructionsToPause = -1; GroundScope scope; if (inScope != NULL) { scope = *inScope; } else { scope.labels = &labels; scope.variables = &variables; } scope.isMainScope = isMainScopeGlobal; isMainScopeGlobal = false; // Preprocess all labels, structs and functions for (size_t 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) { 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* name = malloc(strlen(in->instructions[i].args.args[0].value.refName) + 1); strcpy(name, in->instructions[i].args.args[0].value.refName); size_t counter = 1; GroundProgram gp = createGroundProgram(); addInstructionToProgram(&gp, in->instructions[i]); size_t errorOffset = i; i++; while (counter > 0) { if (i >= in->size) { runtimeError(PREMATURE_EOF, "Reached end of scope before function definition ended", &in->instructions[i - 1], i - 1); } if (in->instructions[i].type == FUN) { counter++; } if (in->instructions[i].type == ENDFUN) { counter--; } addInstructionToProgram(&gp, in->instructions[i]); i++; } GroundFunction* function = parseFunction(&gp, errorOffset); function->startLine = i; GroundValue gv = createFunctionGroundValue(function); addVariable(scope.variables, name, gv); } if (in->instructions[i].type == STRUCT) { if (in->instructions[i].args.length < 1) { runtimeError(TOO_FEW_ARGS, "Expecting 1 arg", &in->instructions[i], i); } if (in->instructions[i].args.length > 1) { runtimeError(TOO_MANY_ARGS, "Expecting 1 arg", &in->instructions[i], i); } if (in->instructions[i].args.args[0].type != TYPEREF) { runtimeError(ARG_TYPE_MISMATCH, "Expected arg 1 to be a typeref", &in->instructions[i], i); } char* name = malloc(strlen(in->instructions[i].args.args[0].value.refName) + 1); strcpy(name, in->instructions[i].args.args[0].value.refName); i++; size_t counter = 1; GroundProgram gp = createGroundProgram(); size_t errorOffset = i; while (counter > 0) { if (i >= in->size) { runtimeError(PREMATURE_EOF, "Reached end of scope before struct definition ended", &in->instructions[i - 1], i - 1); } if (in->instructions[i].type == STRUCT) { counter++; } if (in->instructions[i].type == ENDSTRUCT) { counter--; } addInstructionToProgram(&gp, in->instructions[i]); i++; } GroundValue gv = { .type = STRUCTVAL, .data.structVal = malloc(sizeof(GroundStruct)) }; *gv.data.structVal = parseStruct(&gp, &scope, errorOffset); addVariable(scope.variables, name, gv); } } for (size_t 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 == STRUCT) { int count = 1; while (count > 0) { i++; if (i >= in->size) { return createNoneGroundValue(); } if (in->instructions[i].type == STRUCT) { count++; } if (in->instructions[i].type == ENDSTRUCT) { count--; } } } if (in->instructions[i].type == PAUSE || instructionsToPause == 0) { 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 STEP: { if (gdi.arg != NULL && strlen(gdi.arg) > 0) { instructionsToPause = atoi(gdi.arg); } else { instructionsToPause = 1; } instructionsToPause++; shouldBreak = true; break; } case VIEW: { 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"); break; } case UNKNOWN: { printf("Unknown instruction (type \"help\" for help)"); break; } } if (shouldBreak) { break; } } if (in->instructions[i].type == PAUSE) { continue; } } instructionsToPause --; 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 (size_t 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 these instructions, as they have been preprocessed case FUN: case ENDFUN: case STRUCT: case ENDSTRUCT: 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 (size_t 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 (size_t 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; } case ERRORCMD: { if (in->args.length < 3) { runtimeError(TOO_FEW_ARGS, "Expecting 3 args", in, currentInstruction); } else if (in->args.length > 3) { runtimeError(TOO_MANY_ARGS, "Expecting 3 args", in, currentInstruction); } if (in->args.args[2].value.value.type != INT ) { runtimeError(ARG_TYPE_MISMATCH, "Expecting an int for arg 2", in, currentInstruction); } customError(in->args.args[0], in->args.args[1], in, currentInstruction, in->args.args[2].value.value.data.intVal); 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 FUNCTION: { addVariable(scope->variables, in->args.args[1].value.refName, createStringGroundValue("function")); break; } case STRUCTVAL: { addVariable(scope->variables, in->args.args[1].value.refName, createStringGroundValue("struct")); break; } case NONE: { addVariable(scope->variables, in->args.args[1].value.refName, createStringGroundValue("none")); break; } case ERROR: { addVariable(scope->variables, in->args.args[1].value.refName, createStringGroundValue("error")); break; } } 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 (size_t 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; } case STOI: { 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[2].value.refName, createIntGroundValue(atoll(in->args.args[0].value.value.data.stringVal))); break; } case STOD: { 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(atof(in->args.args[0].value.value.data.stringVal))); break; } case TOSTRING: { 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); } GroundValue* value = &in->args.args[0].value.value; switch (value->type) { case INT: { char* buf = malloc(sizeof(char) * 256); snprintf(buf, sizeof(char) * 256, "%" PRId64, value->data.intVal); addVariable(scope->variables, in->args.args[1].value.refName, createStringGroundValue(buf)); break; } case DOUBLE: { char* buf = malloc(sizeof(char) * 256); snprintf(buf, sizeof(char) * 256, "%f", value->data.doubleVal); addVariable(scope->variables, in->args.args[1].value.refName, createStringGroundValue(buf)); break; } case STRING: { addVariable(scope->variables, in->args.args[1].value.refName, createStringGroundValue(value->data.stringVal)); break; } case CHAR: { char* buf = malloc(sizeof(char) * 2); buf[0] = value->data.charVal; buf[1] = '\0'; addVariable(scope->variables, in->args.args[1].value.refName, createStringGroundValue(buf)); break; } case BOOL: { if (value->data.boolVal) { addVariable(scope->variables, in->args.args[1].value.refName, createStringGroundValue("true")); } else { addVariable(scope->variables, in->args.args[1].value.refName, createStringGroundValue("false")); } break; } case LIST: { addVariable(scope->variables, in->args.args[1].value.refName, createStringGroundValue("")); break; } case FUNCTION: { addVariable(scope->variables, in->args.args[1].value.refName, createStringGroundValue("")); break; } case CUSTOM: { addVariable(scope->variables, in->args.args[1].value.refName, createStringGroundValue("")); break; } case NONE: default: { addVariable(scope->variables, in->args.args[1].value.refName, createStringGroundValue("")); break; } } 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; size_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)); break; } 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 or more 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[in->args.length - 1].type != DIRREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef as the last arg", 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; if (function->argSize < in->args.length - 2) { runtimeError(TOO_FEW_ARGS, "Incorrect amount of arguments provided for function", in, currentInstruction); } if (function->argSize > in->args.length - 2) { runtimeError(TOO_MANY_ARGS, "Incorrect amount of arguments provided for function", in, currentInstruction); } if (function->isNative) { List argsList = createList(); for (size_t i = 0; i < function->argSize; i++) { if (in->args.args[i + 1].type != VALUE) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a Value", in, currentInstruction); } //if (in->args.args[i + 1].value.value.type != function->args[i].type) { if (!checkFnTypes(&in->args.args[i + 1].value.value, &function->args[i])) { runtimeError(ARG_TYPE_MISMATCH, "Mismatched function argument types", in, currentInstruction); } appendToList(&argsList, in->args.args[i + 1].value.value); } if (function->nativeFn) { GroundValue returnValue = function->nativeFn(scope, argsList); if (returnValue.type == ERROR) { returnValue.data.errorVal.line = currentInstruction; returnValue.data.errorVal.hasLine = true; returnValue.data.errorVal.where = in; if (scope->isMainScope) { throwError(&returnValue.data.errorVal); } return returnValue; } if (returnValue.type != function->returnType) { runtimeError(RETURN_TYPE_MISMATCH, "Unexpected return value type from native function", in, currentInstruction); } addVariable(scope->variables, in->args.args[in->args.length - 1].value.refName, returnValue); } free(argsList.values); } else { for (size_t i = 0; i < function->argSize; i++) { if (in->args.args[i + 1].type != VALUE) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a Value", in, currentInstruction); } //if (in->args.args[i + 1].value.value.type != function->args[i].type) { if (!checkFnTypes(&in->args.args[i + 1].value.value, &function->args[i])) { runtimeError(ARG_TYPE_MISMATCH, "Mismatched function argument types", in, currentInstruction); } addVariable(newScope.variables, function->args[i].name, in->args.args[i + 1].value.value); } size_t currentCurrentInstruction = currentInstruction; currentInstruction = function->startLine; GroundValue returnValue = interpretGroundProgram(&function->program, &newScope); if (returnValue.type == ERROR) { return returnValue; } if (returnValue.type != function->returnType) { runtimeError(RETURN_TYPE_MISMATCH, "Unexpected return value type from function", in, currentInstruction); } addVariable(scope->variables, in->args.args[in->args.length - 1].value.refName, returnValue); currentInstruction = currentCurrentInstruction; } break; } case USE: { if (in->args.length < 1) { runtimeError(TOO_FEW_ARGS, "Expecting 1 arg", 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); } char* libName = in->args.args[0].value.value.data.stringVal; char path[1024]; int found = 0; char* envPath = getenv("GROUND_LIBS"); if (envPath) { snprintf(path, sizeof(path), "%s/%s", envPath, libName); if (access(path, F_OK) == 0) found = 1; } if (!found) { snprintf(path, sizeof(path), "/usr/lib/ground/%s", libName); if (access(path, F_OK) == 0) found = 1; } if (!found) { snprintf(path, sizeof(path), "%s", libName); if (access(path, F_OK) == 0) found = 1; } if (!found) { char errorMsg[1100]; snprintf(errorMsg, sizeof(errorMsg), "Could not find library: %s", libName); runtimeError(FIXME, errorMsg, in, currentInstruction); } FILE* f = fopen(path, "r"); if (!f) { runtimeError(FIXME, "Failed to open file", in, currentInstruction); } fseek(f, 0, SEEK_END); long fsize = ftell(f); fseek(f, 0, SEEK_SET); char* content = malloc(fsize + 1); if (!content) { fclose(f); runtimeError(FIXME, "Failed to allocate memory for file content", in, currentInstruction); } fread(content, 1, fsize, f); fclose(f); content[fsize] = 0; GroundProgram program = parseFile(content); free(content); interpretGroundProgram(&program, scope); freeGroundProgram(&program); break; } case EXTERN: { if (in->args.length < 1) { runtimeError(TOO_FEW_ARGS, "Expecting 1 arg", 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); } char* libName = in->args.args[0].value.value.data.stringVal; char path[1024]; int found = 0; // care about mac and windows later char* extension = "so"; char* envPath = getenv("GROUND_LIBS"); if (envPath) { snprintf(path, sizeof(path), "%s/%s.%s", envPath, libName, extension); if (access(path, F_OK) == 0) found = 1; } if (!found) { snprintf(path, sizeof(path), "/usr/lib/ground/%s.%s", libName, extension); if (access(path, F_OK) == 0) found = 1; } if (!found) { snprintf(path, sizeof(path), "./%s.%s", libName, extension); if (access(path, F_OK) == 0) found = 1; } if (!found) { char errorMsg[1100]; snprintf(errorMsg, sizeof(errorMsg), "Could not find external library: %s, in path: %s", libName, path); runtimeError(FIXME, errorMsg, in, currentInstruction); } void* handle = dlopen(path, RTLD_LAZY); if (!handle) { runtimeError(FIXME, dlerror(), in, currentInstruction); } typedef void (*GroundInitFn)(GroundScope*); GroundInitFn initFn = (GroundInitFn)dlsym(handle, "ground_init"); if (!initFn) { runtimeError(FIXME, "Could not find 'ground_init' symbol in library", in, currentInstruction); } initFn(scope); break; } case INIT: { 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 != TYPEREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a TypeRef for arg 2", in, currentInstruction); } GroundValue gv; switch (stringToValueType(in->args.args[1].value.refName)) { case INT: { gv = createIntGroundValue(0); break; } case DOUBLE: { gv = createDoubleGroundValue(0); break; } case STRING: { gv = createStringGroundValue(""); break; } case CHAR: { gv = createCharGroundValue('\0'); break; } case BOOL: { gv = createBoolGroundValue(false); break; } case LIST: { gv = createListGroundValue(createList()); break; } case FUNCTION: { GroundFunction* gf = createGroundFunction(); gf->returnType = NONE; gv = createFunctionGroundValue(gf); break; } case STRUCTVAL: { gv.type = STRUCTVAL; gv.data.structVal = malloc(sizeof(GroundStruct)); *gv.data.structVal = createStruct(); break; } case CUSTOM: { GroundVariable* var = findVariable(*scope->variables, in->args.args[1].value.refName); if (var == NULL) { runtimeError(UNKNOWN_VARIABLE, "Couldn't find the specified type", in, currentInstruction); } if (var->value.type != STRUCTVAL) { runtimeError(ARG_TYPE_MISMATCH, "TypeRef does not reference a struct", in, currentInstruction); } GroundStruct* gstruct = var->value.data.structVal; gv.type = CUSTOM; gv.data.customVal = malloc(sizeof(GroundObject)); *gv.data.customVal = createObject(*gstruct); gv.customType = gstruct; break; } case NONE: { gv.type = NONE; break; } default: { runtimeError(FIXME, "Reached should-be-impossible state", in, currentInstruction); break; } } addVariable(scope->variables, in->args.args[0].value.refName, gv); break; } case DROP: { 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 != DIRREF) { runtimeError(ARG_TYPE_MISMATCH, "Expecting a DirectRef for arg 1", in, currentInstruction); } GroundVariable* var = findVariable(*scope->variables, in->args.args[0].value.refName); if (!var) { runtimeError(UNKNOWN_VARIABLE, NULL, in, currentInstruction); } deleteVariable(scope->variables, var); break; } default: { runtimeError(FIXME, "Currently unimplemented instruction", in, currentInstruction); } } freeGroundInstruction(in); return createNoneGroundValue(); }