#include "object.h" #include #include /** * Returns the length of a given list Object * @param listObj The list to get the length of * @return Length of the list if non-null and of the list type. Otherwise -1 */ int listLength(const Object* listObj) { if (!listObj || !isListy(*listObj)) { return -1; } int len = 0; FOR_POINTER_IN_LIST(listObj) { len++; } return len; } Object len(Object obj1, Object o_ignore, struct Environment* e_ignore) { Object o = numberObject(listLength(&obj1)); if (o.number < 0) { return errorObject(NOT_A_LIST); } return o; } /** * Returns a pointer to the Object at the given index in a given list * @param listObj The list to fetch an Object from * @param n The index to to fetch from the list * @return A pointer to the Object, if it is found, or NULL on an error */ Object* itemAt(const Object* listObj, int n) { if (!listObj || !isListy(*listObj)) { return NULL; } FOR_POINTER_IN_LIST(listObj) { if (POINTER == NULL) { return NULL; } if (n-- == 0) { return POINTER; } } return NULL; } /** * Returns a pointer to the last Object in the given list. * @param listObj The list to find the tail of * @return A pointer to the last Object if it is found, or NULL on an error */ Object* tail(const Object* listObj) { if (!listObj || !isListy(*listObj)) { return NULL; } Object* tail = NULL; FOR_POINTER_IN_LIST(listObj) { tail = POINTER; } return tail; } /** * Checks if a given object is empty. * * BOOL, NUMBER, and ERROR types return true only when 0 * LIST, LAMBDA, and FUNC types return true only when NULL * SYMBOL returns true only when it begins with a nullchar * @param obj A pointer to the object to check * @return True only if the non-null Object is empty/non-zero */ inline int isEmpty(const Object* obj) { if (obj == NULL) { return 0; } switch (obj->type) { case TYPE_NUMBER: case TYPE_BOOL: return obj->number == 0; case TYPE_LIST: case TYPE_SLIST: return obj->list == NULL; case TYPE_LAMBDA: return obj->lambda == NULL; case TYPE_SYMBOL: case TYPE_STRING: return obj->string == NULL || obj->string[0] == '\0'; case TYPE_FUNC: return obj->func == NULL; case TYPE_OTHER: return obj->other == NULL; case TYPE_ERROR: return getErrorCode(*obj); } return 0; } /** * Allocate a copy of a given object into the given pointer. * Does nothing if `spot` is NULL * @param spot A pointer to the Object pointer that needs allocating * @param src The Object to copy from */ void allocObject(Object** spot, const Object src) { *spot = malloc(sizeof(struct Object)); **spot = src; (*spot)->forward = NULL; } /** * Inserts a given Object into a list at the given index. * Immediately returns if `dest` is NULL or not a list type * Adds to the end of the list if the index is higher than the list length * @param dest The list to insert into * @param ind The index to insert at * @param src The Object to add to the list * @warning UNTESTED */ void insertIntoList(Object* dest, int ind, const Object src) { if (!dest || !isListy(*dest)) { return; } // Merely append, when possible if (ind >= listLength(dest)) { nf_addToList(dest, src); return; } // The Objects to preceed and follow the new one Object* beforeNew = itemAt(dest, ind - 1); Object* afterNew = beforeNew->forward; // Replace the `before` Object's pointer allocObject(&beforeNew->forward, src); beforeNew->forward->forward = afterNew; } /** * Replace an Object in a list at a given index with a given Object * Attempts to clean the replaced Object before adding the new one * Immediately returns if `list` is NULL or not a list type * * @param list The list Object to replace an element of * @param i The index of the element to replace * @param src The Object to copy into the list */ void replaceListing(Object* list, int i, const Object src) { if (!list || isListy(*list)) { return; } Object* replace = itemAt(list, i); Object* oldForward = replace->forward; cleanObject(replace); *replace = src; replace->forward = oldForward; } /** * Adds an Object to the end of a list * Does nothing if `dest` is NULL or not a list type * @param dest The list to append to * @param src The Object to copy into the list */ void nf_addToList(Object* dest, const Object src) { if (!dest || !isListy(*dest)) { return; } if (isEmpty(dest)) { allocObject(&dest->list, src); return; } allocObject(&tail(dest)->forward, src); } #ifndef SIMPLE_ERRORS static const char* errorText[] = {"MISMATCHED_PARENS", "BAD_LIST_OF_SYMBOL_STRINGS", "TYPE_LIST_NOT_CAUGHT", "NULL_ENV", "EMPTY_ENV", "BUILT_IN_NOT_FOUND", "NULL_PARSE", "NULL_LAMBDA_LIST", "NULL_MAP_ARGS", "LAMBDA_ARGS_NOT_LIST", "DID_NOT_FIND_SYMBOL", "BAD_TYPE", "UNEXPECTED_FORM", "LISTS_NOT_SAME_SIZE", "BAD_NUMBER", "UNSUPPORTED_NUMBER_TYPE", "NOT_A_SYMBOL", "ONLY_ONE_ARGUMENT", "NOT_A_LIST", "SCRIPT_NOT_FOUND", "NO_CLONE_SPECIFIED", "CAN_ONLY_EVAL_STRINGS", "UNEXPECTED_EOF", "INDEX_PAST_END"}; #endif /** * Creates a string from a given list Object * Blocks out with parens * Returns immediately if `dest` is NULL, or `obj` is not a list type * @param dest The string to copy the list text into * @param obj The list Object to make a string from */ void stringList(char* dest, const Object* obj) { if (!dest || !isListy(*obj)) { return; } dest[0] = '('; dest[1] = '\0'; FOR_POINTER_IN_LIST(obj) { strcat(dest, " "); char tok[90] = ""; stringObj(tok, POINTER); strcat(dest, tok); } strcat(dest, " )"); } /** * Creates a string from a given Object * Returns NULL if either param is NULL * * Prints * -# Numbers without spaces * -# Symbols as their name * -# Bools as 'T' or 'F' * -# Strings as their string value * -# Lists using stringList() * -# Errors as the integer value, prepended with 'E' * -# Otherwise as the raw number, prepended with 'X' * * @param dest The string to copy the list text into * @param obj The list Object to make a string from */ char* stringNObj(char* dest, const Object* obj, const size_t len) { if (!dest || !obj) { return NULL; } switch (obj->type) { case TYPE_NUMBER: snprintf(dest, len, "%d", obj->number); break; case TYPE_BOOL: snprintf(dest, len, "%s", obj->number ? "T" : "F"); break; case TYPE_STRING: snprintf(dest, len, "%s", obj->string); break; case TYPE_SYMBOL: snprintf(dest, len, "`%s`", obj->string); break; case TYPE_LIST: case TYPE_SLIST: stringList(dest, obj); break; case TYPE_ERROR: { int code = getErrorCode(*obj); #ifdef SIMPLE_ERRORS snprintf(dest, len, "E[%d]", (int)code); #else if (obj->error->context && obj->error->context[0] != '\0') { snprintf(dest, len, "'%s': %s", errorText[code], obj->error->context); } else { snprintf(dest, len, "%s", errorText[code]); } #endif break; } case TYPE_FUNC: case TYPE_LAMBDA: snprintf(dest, len, "X%d", obj->number); break; case TYPE_OTHER: snprintf(dest, len, "%p", obj->other->data); break; } if (!isValidType(*obj)) { snprintf(dest, len, "BAD_TYPE(%d) X%d", obj->type, obj->number); } return dest; } char* stringObj(char* dest, const Object* obj) { return stringNObj(dest, obj, RESULT_LENGTH); } /** * Prints a given Object only if the DEBUG flag is set * @param obj The Object to debug */ void debugObj(const Object* obj) { #ifdef DEBUG printObj(obj); #endif return; } #if defined(DEBUG) || defined(STANDALONE) void printType(const Object* obj) { switch (obj->type) { case TYPE_NUMBER: printf("TYPE_NUMBER"); return; case TYPE_BOOL: printf("TYPE_BOOL"); return; case TYPE_LIST: printf("TYPE_LIST"); return; case TYPE_SLIST: printf("TYPE_SLIST"); return; case TYPE_FUNC: printf("TYPE_FUNC"); return; case TYPE_SYMBOL: printf("TYPE_SYMBOL"); return; case TYPE_STRING: printf("TYPE_STRING"); return; case TYPE_LAMBDA: printf("TYPE_LAMBDA Params:\n"); return; case TYPE_OTHER: printf("TYPE_OTHER: "); case TYPE_ERROR: printf("TYPE_ERROR: "); return; } if (!isValidType(*obj)) { printf("UNKNOWN TYPE (%d)", obj->type); } } void nestedPrintList(const Object* list, int newline); void _printObj(const Object* obj, int newline) { #ifdef DEBUG printType(obj); #endif if (obj->type == TYPE_LAMBDA) { printObj(&obj->lambda->params); printf("->"); printObj(&obj->lambda->body); return; } char temp[100] = ""; stringObj(temp, obj); if (newline) { printf("%s\n", temp); } else { printf("%s", temp); } } /** * Prints the given Object and a newline. * Uses stringObj() to create the printed string * If the DEBUG flag is set, the Object's type will be printed first * @param obj The Object to print */ inline void printObj(const Object* obj) { _printObj(obj, 1); } void nestedPrintList(const Object* list, int newline) { printf("("); if (list && isListy(*list)) { FOR_POINTER_IN_LIST(list) { printf(" "); _printObj(POINTER, 0); } } printf(" )"); if (newline) { printf("\n"); } } /** * Prints each Object in a given list, surrounded by parentheses * @param list The list Object to print */ void printList(const Object* list) { nestedPrintList(list, 1); } #endif /** * Performs appropriate free() on a given Object and NULLs its ->forward * Returns immediately if param is NULL * * Strings: The Object's ->string is freed and NULLed * Lists: deleteList() is called on the Object (may recurse) * Lambdas: The body and params are cleaned, and the lambda itself is freed * * @param target The object to clean */ void cleanObject(Object* target) { if (target == NULL) { return; } switch (target->type) { case TYPE_STRING: case TYPE_SYMBOL: free(target->string); target->string = NULL; break; case TYPE_LIST: case TYPE_SLIST: deleteList(target); break; case TYPE_LAMBDA: cleanObject(&target->lambda->params); cleanObject(&target->lambda->body); free(target->lambda); break; case TYPE_ERROR: #ifndef SIMPLE_ERRORS free(target->error->context); free(target->error); target->error = NULL; #endif break; case TYPE_OTHER: if (target->other->cleanup) { target->other->cleanup(target); } break; case TYPE_BOOL: case TYPE_NUMBER: case TYPE_FUNC: break; } target->forward = NULL; } /** * Print the given object with a newline, then clean it * @param target The object to print and clean */ #ifdef STANDALONE void printAndClean(Object* target) { printObj(target); cleanObject(target); } #endif /** * Frees all objects in a given list * Runs cleanObject() on every item in the list (may recurse) * * @param dest The list object to clean up */ void deleteList(Object* dest) { if (!dest) { return; } if (!isListy(*dest)) { printf("Tried to delete something other than a list\n"); return; } Object* march = dest->list; while (march != NULL) { Object* prevMarch = march; march = march->forward; cleanObject(prevMarch); free(prevMarch); } dest->list = NULL; } void _copyList(Object* dest, const Object* src, int delete) { if (!dest || !src) { printd("NULL\n"); return; } if (!isListy(*dest) || !isListy(*src)) { printd("NOT A LIST\n"); return; } if (delete) { deleteList(dest); } FOR_POINTER_IN_LIST(src) { if (isListy(*POINTER)) { nf_addToList(dest, *POINTER); tail(dest)->list = NULL; _copyList(tail(dest), POINTER, 0); } else if (isStringy(*POINTER)) { nf_addToList(dest, cloneString(*POINTER)); } else { nf_addToList(dest, *POINTER); } } } /** * Does a deep copy of all items from `src` to `dest` * Does nothing if either param is NULL, or not a list type * Does a shallow delete of items from `dest` before copying * May recurse into lists in the list * * @param dest The list to copy to * @param src The list to copy from */ void copyList(Object* dest, const Object* src) { _copyList(dest, src, 1); } /** * Does a deep copy of all items from `src` to the end of `dest` * Does nothing if either param is NULL, or not a list type * May recurse into lists in the list * * @param dest The list to copy to * @param src The list to copy from */ void appendList(Object* dest, const Object* src) { _copyList(dest, src, 0); } /** * Returns a basic Object with NULL forward and the given type * @param type The type of Object to create * @return The created Object */ inline Object newObject(Type type) { Object no; no.forward = NULL; no.type = type; return no; } // Returns an empty list Object inline Object listObject() { Object list = newObject(TYPE_LIST); list.list = NULL; return list; } // Returns a list Object starting with the given Object inline Object startList(const Object start) { Object list = listObject(); nf_addToList(&list, start); return list; } inline int isListy(const Object test) { return test.type == TYPE_LIST || test.type == TYPE_SLIST; } inline int isStringy(const Object test) { return test.type == TYPE_STRING || test.type == TYPE_SYMBOL; } inline int isValidType(const Object test) { switch (test.type) { case TYPE_NUMBER: case TYPE_BOOL: case TYPE_LIST: case TYPE_SLIST: case TYPE_FUNC: case TYPE_SYMBOL: case TYPE_LAMBDA: case TYPE_STRING: case TYPE_OTHER: case TYPE_ERROR: return 1; } return 0; } /** * Clones the given lambda with new allocations * Will behave unexpectedly if given something other than a lambda object! */ inline Object cloneLambda(const Object old) { return constructLambda(&old.lambda->params, &old.lambda->body); } Object cloneString(Object obj) { const char* string = obj.string; obj.string = malloc(sizeof(char) * (strlen(string) + 1)); strcpy(obj.string, string); return obj; } // Returns an Object with a deep copy of the given Object inline Object cloneList(const Object src) { Object list = listObject(); copyList(&list, &src); list.type = src.type; return list; } inline Object cloneOther(const Object src) { return src.other->clone ? src.other->clone(src.other) : src; } inline Object cloneObject(const Object src) { switch (src.type) { case TYPE_SLIST: case TYPE_LIST: return cloneList(src); case TYPE_LAMBDA: return cloneLambda(src); case TYPE_STRING: case TYPE_SYMBOL: return cloneString(src); case TYPE_ERROR: return errorWithContext(getErrorCode(src), src.error->context); case TYPE_BOOL: case TYPE_NUMBER: case TYPE_FUNC: case TYPE_OTHER: return src; } return src; } inline Object numberObject(int num) { Object o = newObject(TYPE_NUMBER); o.number = num; return o; } inline Object boolObject(int b) { Object o = newObject(TYPE_BOOL); o.number = !!b; return o; } // Skips first and last chars! Assumed to be '"' inline Object objFromSlice(const char* string, int len) { return stringFromSlice(&string[1], len - 1); } inline Object stringFromSlice(const char* string, int len) { Object o = symFromSlice(string, len); o.type = TYPE_STRING; return o; } inline Object symFromSlice(const char* string, int len) { Object o = newObject(TYPE_SYMBOL); o.string = calloc(sizeof(char), len + 1); strncpy(o.string, string, len); return o; } inline Object constructLambda(const Object* params, const Object* body) { if (!params || !body) { return errorObject(NULL_LAMBDA_LIST); } if (params->type != TYPE_LIST || body->type != TYPE_LIST) { return errorObject(LAMBDA_ARGS_NOT_LIST); } Object o = newObject(TYPE_LAMBDA); o.lambda = malloc(sizeof(struct Lambda)); o.lambda->params = listObject(); o.lambda->body = listObject(); copyList(&o.lambda->params, params); copyList(&o.lambda->body, body); return o; } inline int isError(const Object obj, const enum errorCode err) { return obj.type == TYPE_ERROR && getErrorCode(obj) == err; } inline int bothAre(const enum Type type, const Object* obj1, const Object* obj2) { return (obj1->type == type) && (obj2->type == type); } inline int eitherIs(const enum Type type, const Object* o1, const Object* o2) { return (o1->type == type) || (o2->type == type); } inline int areSameType(const Object* obj1, const Object* obj2) { return obj1->type == obj2->type; } inline Object otherObject() { Object o = newObject(TYPE_OTHER); o.other = malloc(sizeof(struct Other)); return o; } inline Object errorObject(enum errorCode err) { Object o = newObject(TYPE_ERROR); #ifdef SIMPLE_ERRORS o.error = err; #else o.error = malloc(sizeof(struct Error)); o.error->code = err; o.error->context = NULL; #endif return o; } inline enum errorCode getErrorCode(const Object obj) { #ifdef SIMPLE_ERRORS return obj.error; #else return obj.error->code; #endif } #ifndef SIMPLE_ERRORS inline void errorAddContext(Object* o, const char* context) { // printf("o: %p\n", o); // printf("o->error: %s\n", o->error); o->error->context = calloc(sizeof(char), RESULT_LENGTH); // printf("context: %p\n", context); strncpy(o->error->context, context, RESULT_LENGTH); } inline Object errorWithContext(enum errorCode code, const char* context) { Object o = errorObject(code); if (context) { errorAddContext(&o, context); } return o; } #endif struct Error noError() { struct Error err; err.context = NULL; return err; } inline Object toBool(const Object test) { if (test.number == 0) { return boolObject(0); } return boolObject(1); }