pebblisp/src/object.c

983 lines
24 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "object.h"
#include "env.h"
#include <stdio.h>
#include <string.h>
size_t bytes = 0;
int getBytes()
{
return bytes;
}
void* smalloc(size_t size)
{
bytes += size;
return malloc(size);
}
void* scalloc(size_t size, size_t count)
{
bytes += (size * count);
return calloc(size, count);
}
#define malloc(x) smalloc(x)
#define calloc(x, y) scalloc(x, y)
/**
* 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_STRUCT:
return obj->structObject == 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;
}
int allocations = 0;
int getAllocations()
{
return allocations;
}
/**
* 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)
{
#ifdef ALLOCATION_CAP
if (allocations >= 10000) {
printf("MAX ALLOCATIONS EXCEEDED\n");
*spot = NULL;
return;
}
#endif
allocations++;
*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 struct Object
* Blocks out with curly braces
*/
void stringStruct(char* dest, const Object* obj)
{
struct StructObject* so = obj->structObject;
dest[0] = '{';
dest[1] = '\0';
for (int i = 0; i < global()->structDefs[so->definition].fieldCount; i++) {
strcat(dest, " ");
strcat(dest, global()->structDefs[so->definition].names[i]);
strcat(dest, ": ");
int isString = so->fields[i].type == TYPE_STRING;
if (isString) {
strcat(dest, "\"");
}
char tok[90] = "";
stringObj(tok, &so->fields[i]);
strcat(dest, tok);
if (isString) {
strcat(dest, "\"");
}
strcat(dest, ",");
}
int i = 0;
while (dest[i]) {
i++;
}
dest[i - 1] = ' ';
dest[i + 0] = '}';
dest[i + 1] = '\0';
}
/**
* 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
*/
#ifdef STANDALONE
#define stringf(_dest, _len, _format, args...) sprintf(_dest, _format, ## args)
#else
#define stringf(_dest, _len, _format, args...) snprintf(_dest, _len, _format, ## args)
#endif
char* stringNObj(char* dest, const Object* obj, const size_t len)
{
if (!dest || !obj) {
return NULL;
}
switch (obj->type) {
case TYPE_NUMBER:
stringf(dest, len, "%d", obj->number);
break;
case TYPE_BOOL:
stringf(dest, len, "%s", obj->number ? "T" : "F");
break;
case TYPE_STRING:
stringf(dest, len, "%s", obj->string);
break;
case TYPE_SYMBOL:
stringf(dest, len, "`%s`", obj->string);
break;
case TYPE_STRUCT:
//snprintf(dest, len, "{%s}", obj->structObject->definition->names[0]);
stringStruct(dest, obj);
break;
case TYPE_LIST:
case TYPE_SLIST:
stringList(dest, obj);
break;
case TYPE_ERROR: {
int code = getErrorCode(*obj);
#ifdef SIMPLE_ERRORS
stringf(dest, len, "E[%d]", (int)code);
#else
if (obj->error->context && obj->error->context[0] != '\0') {
stringf(dest, len, "'%s': %s", errorText[code],
obj->error->context);
} else {
stringf(dest, len, "%s", errorText[code]);
}
#endif
break;
}
case TYPE_FUNC:
stringf(dest, len, "F%d", obj->number);
break;
case TYPE_LAMBDA:
#ifdef STANDALONE
dest += stringf(dest, len, "\\x%d", obj->number);
stringNObj(dest, &obj->lambda->params, len);
dest += strlen(dest);
strcat(dest, " -> ");
dest += 4;
stringNObj(dest, &obj->lambda->body, len);
dest += strlen(dest);
strcat(dest, ">");
#else
stringf(dest, len, "\\x%d", obj->number);
#endif
break;
case TYPE_OTHER:
stringf(dest, len, "%p", obj->other->data);
break;
}
if (!isValidType(*obj)) {
stringf(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)
#define SIMPLE_TYPE(_type) case _type:\
printf(#_type);\
return
void printType(const Object* obj)
{
if (!obj) {
printf("NULL OBJECT");
return;
}
switch (obj->type) {
SIMPLE_TYPE(TYPE_NUMBER);
SIMPLE_TYPE(TYPE_STRUCT);
SIMPLE_TYPE(TYPE_BOOL);
SIMPLE_TYPE(TYPE_LIST);
SIMPLE_TYPE(TYPE_SLIST);
SIMPLE_TYPE(TYPE_FUNC);
SIMPLE_TYPE(TYPE_SYMBOL);
SIMPLE_TYPE(TYPE_STRING);
SIMPLE_TYPE(TYPE_OTHER);
SIMPLE_TYPE(TYPE_ERROR);
case TYPE_LAMBDA:
printf("TYPE_LAMBDA Params:\n");
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) {
printf(newline ? "\n" : "");
return;
}
if (obj->type == TYPE_LAMBDA) {
printObj(&obj->lambda->params);
printf("->");
printObj(&obj->lambda->body);
return;
}
char temp[200] = "";
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_STRUCT:
for (int i = 0; i < global()->structDefs[target->structObject->definition].fieldCount; i++) {
cleanObject(&target->structObject->fields[i]);
}
free(target->structObject->fields);
free(target->structObject);
target->structObject = NULL;
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 an empty struct Object
inline Object structObject(int definition)
{
Object structo = newObject(TYPE_STRUCT);
structo.structObject = malloc(sizeof(struct StructObject));
structo.structObject->definition = definition;
structo.structObject->fields = malloc(sizeof(Object) * global()->structDefs[definition].fieldCount);
return structo;
}
// 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_STRUCT:
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, NULL);
}
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;
}
Object cloneStruct(const Object 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_STRUCT:
return cloneStruct(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;
}
Object cloneStruct(const Object src)
{
Object structo = structObject(src.structObject->definition);
struct StructObject* so = structo.structObject;
for (int i = 0; i < global()->structDefs[so->definition].fieldCount; i++) {
so->fields[i] = cloneObject(src.structObject->fields[i]);
}
return structo;
}
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, struct Environment* env)
{
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);
if (env) {
Object* dest = &o.lambda->body;
FOR_POINTER_IN_LIST(dest) {
if (POINTER->type == TYPE_SYMBOL) {
Object fetched = fetchFromEnvironment(POINTER->string, env);
// TODO: Figure out why lambdas in particular break when doing this.
if (!isError(fetched, DID_NOT_FIND_SYMBOL) && fetched.type != TYPE_LAMBDA) {
fetched.forward = POINTER->forward;
cleanObject(POINTER);
*POINTER = fetched;
} else {
cleanObject(&fetched);
}
}
}
}
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, int lineNo, const char* fileName)
{
// 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);
sprintf(o->error->context, "%s %s:%d", context, fileName, lineNo);
}
inline Object errorWithContextLineNo(enum errorCode code, const char* context, int lineNo, const char* fileName)
{
Object o = errorObject(code);
if (context) {
errorAddContext(&o, context, lineNo, fileName);
}
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);
}