// --------------------------- // C Headers / Local Headers // --------------------------- #include #include #include #include #include "entity.h" #include "memory.h" #include "token.h" // --------------------------- // makeEntityArray // --------------------------- EntityArray makeEntityArray(void) { EntityArray theArray = { 0, 0, NULL }; return(theArray); } // --------------------------- // makeEntityObject // --------------------------- EntityObject makeEntityObject(void) { EntityObject theObject = { 0, 0, NULL }; return(theObject); } // --------------------------- // freeEntityObject // --------------------------- void freeEntityObject(EntityObject *object) { if(object) { for(int i = 0; i < object->mCount; i++) freeEntity(&object->mEntries[i].mEntity); if(object->mEntries) Memory.deallocate(object->mEntries); object->mEntries = NULL; object->mCount = 0; object->mCapacity = 0; } } // --------------------------- // freeEntityArray // --------------------------- void freeEntityArray(EntityArray *array) { if(array) { for(int i = 0; i < array->mCount; i++) freeEntity(array->mEntities + i); if(array->mEntities) Memory.deallocate(array->mEntities); array->mCount = 0; array->mCapacity = 0; array->mEntities = NULL; } } #if 0 static void printEntityArray(EntityArray *array) { printf("===================================\n"); for(int i = 0; i < array->mCount; i++) { printf("entity[%d] (%s)\n", i, getTokenTypeString(&array->mEntities[i].mToken)); printEntity(array->mEntities[i]); } printf("===================================\n\n"); } static void printEntityObject(EntityObject *object) { printf("===================================\n"); for(int i = 0; i < object->mCount; i++) { printf("entry[%d] (%s)\n", i, object->mEntries[i].mKey.mData); printEntity(object->mEntries[i].mEntity); } printf("===================================\n\n"); } #endif // --------------------------- // makeCJString // --------------------------- CJString makeCJString(void) { CJString theString = { 0 }; return(theString); } // --------------------------- // makeEntity // --------------------------- Entity makeEntity(void) { Entity theEntity = { 0 }; theEntity.mType = CJ_ET_NONE; theEntity.mString = makeCJString(); theEntity.mValue = 0.0; theEntity.mBool = 0; theEntity.mObject = NULL; theEntity.mArray = NULL; theEntity.mToken = makeToken(); return(theEntity); } // --------------------------- // makeEntityObjectEntry // --------------------------- EntityObjectEntry makeEntityObjectEntry(void) { EntityObjectEntry theEntry = { 0 }; theEntry.mKey = makeCJString(); theEntry.mEntity = makeEntity(); return(theEntry); } // --------------------------- // printEntityEx // --------------------------- static void printEntityEx(int indent, Entity *entityPtr) { static const int INDENT_STEP = 3; switch (entityPtr->mType) { case CJ_ET_NONE: { printf("%*sNone\n", indent, ""); } break; case CJ_ET_OBJECT: { printf("%*sObject\n", indent, ""); printf("%*s{\n", indent, ""); for(int i = 0; i < entityPtr->mObject->mCount; i++) { printf( "%*s%s:\n", indent + INDENT_STEP, "", entityPtr->mObject->mEntries[i].mKey.mData ); printEntityEx( indent + 2 * INDENT_STEP, &entityPtr->mObject->mEntries[i].mEntity ); } printf("%*s}\n", indent, ""); } break; case CJ_ET_ARRAY: { printf("%*sArray\n", indent, ""); printf("%*s[\n", indent, ""); for(int i = 0; i < entityPtr->mArray->mCount; i++) printEntityEx(indent + INDENT_STEP, &entityPtr->mArray->mEntities[i]); printf("%*s]\n", indent, ""); } break; case CJ_ET_STRING: { printf("%*sString: %s\n", indent, "", entityPtr->mString.mData); } break; case CJ_ET_NUMBER: { printf("%*sNumber: %f\n", indent, "", entityPtr->mValue); } break; case CJ_ET_BOOLEAN: { printf("%*sBoolean: %s\n", indent, "", entityPtr->mBool ? "true" : "false"); } break; case CJ_ET_NULL: { printf("%*sNull\n", indent, ""); } break; } } // --------------------------- // printEntity // --------------------------- void printEntity(Entity *entityPtr) { printEntityEx(0, entityPtr); } // --------------------------- // freeEntity // --------------------------- void freeEntity(Entity *ptr) { bool showType = false; if(showType) { char *typeString = NULL; switch (ptr->mType) { case CJ_ET_NONE : typeString = "None"; break; case CJ_ET_OBJECT : typeString = "Object"; break; case CJ_ET_ARRAY : typeString = "Array"; break; case CJ_ET_STRING : typeString = "String"; break; case CJ_ET_NUMBER : typeString = "Number"; break; case CJ_ET_BOOLEAN: typeString = "Boolean"; break; case CJ_ET_NULL : typeString = "Null"; break; } if(typeString) printf("Freeing entity of type: %s\n", typeString); } freeEntityObject(ptr->mObject); Memory.deallocate(ptr->mObject); freeEntityArray(ptr->mArray); Memory.deallocate(ptr->mArray); } // --------------------------- // addToEntityArray // --------------------------- void addToEntityArray(Entity *entityPtr, EntityArray *array) { if(array->mCount == array->mCapacity) { if(!array->mCapacity) array->mCapacity = 256; else array->mCapacity *= 2; size_t bytes = array->mCapacity * sizeof(*entityPtr); if(!array->mEntities) array->mEntities = Memory.allocate(bytes); else array->mEntities = Memory.reallocate(array->mEntities, bytes); Entity empty = makeEntity(); for(int i = array->mCount + 1; i < array->mCapacity; i++) array->mEntities[i] = empty; } array->mEntities[array->mCount++] = *entityPtr; } // --------------------------- // addToEntityObject // --------------------------- void addToEntityObject(EntityObjectEntry *entryPtr, EntityObject *objectPtr) { if(objectPtr->mCount == objectPtr->mCapacity) { if(!objectPtr->mCapacity) objectPtr->mCapacity = 256; else objectPtr->mCapacity *= 2; size_t bytes = objectPtr->mCapacity * sizeof(*entryPtr); if(!objectPtr->mEntries) objectPtr->mEntries = Memory.allocate(bytes); else objectPtr->mEntries = Memory.reallocate(objectPtr->mEntries, bytes); EntityObjectEntry empty = { 0 }; empty.mKey = makeCJString(); empty.mEntity = makeEntity(); for(int i = objectPtr->mCount + 1; i < objectPtr->mCapacity; i++) objectPtr->mEntries[i] = empty; } objectPtr->mEntries[objectPtr->mCount++] = *entryPtr; } // --------------------------- // getNextComma // --------------------------- static int getNextComma(EntityArray *array, int current) { // We only consider commas that are outside arrays or objects int leftCurly = 0; int rightCurly = 0; int leftSquare = 0; int rightSquare = 0; for(int i = current; i < array->mCount; i++) { switch (array->mEntities[i].mToken.mType) { case CJ_TT_LEFT_CURLY : leftCurly++; break; case CJ_TT_RIGHT_CURLY : rightCurly++; break; case CJ_TT_LEFT_SQUARE : leftSquare++; break; case CJ_TT_RIGHT_SQUARE: rightSquare++; break; case CJ_TT_COMMA: { if(leftCurly == rightCurly && leftSquare == rightSquare) return(i); } break; default: break; } } return(-1); } // --------------------------- // getMatchingIndex // --------------------------- static int getMatchingIndex(EntityArray *array, int current) { int leftCount = 0; int rightCount = 0; TokenType beginType = array->mEntities[current].mToken.mType; TokenType endType = CJ_TT_NONE; switch (beginType) { case CJ_TT_LEFT_CURLY : endType = CJ_TT_RIGHT_CURLY; break; case CJ_TT_LEFT_SQUARE: endType = CJ_TT_RIGHT_SQUARE; break; default: { // Char at 'current' is neither '{' nor '[' return(-1); } break; } leftCount++; for(int i = current + 1; i < array->mCount; i++) { TokenType theType = array->mEntities[i].mToken.mType; if(theType == beginType) { leftCount++; } else if(theType == endType) { if(++rightCount == leftCount) return(i); } } return(-1); } // --------------------------- // getEntityArray // --------------------------- static bool getEntityArray( EntityArray *input, int leftIndex, int rightIndex, EntityArray *output, CJString *errMsg ) { int index = leftIndex + 1; bool go = true; while(go) { int stop = getNextComma(input, index); if(stop < 0 || stop > rightIndex) { stop = rightIndex; go = false; } if(index < stop) { Entity theEntity = makeEntity(); if(stop - index == 1 && input->mEntities[stop - 1].mType != CJ_ET_NONE) { theEntity = input->mEntities[stop - 1]; } else { EntityArray theArray = makeEntityArray(); for(int i = index; i < stop; i++) addToEntityArray(&input->mEntities[i], &theArray); if(!getEntity(&theArray, &theEntity, errMsg)) { freeEntityArray(output); return(false); } } addToEntityArray(&theEntity, output); } index = stop + 1; } return(true); } // --------------------------- // getEntityObject // --------------------------- static bool getEntityObject( EntityArray *input, int leftIndex, int rightIndex, EntityObject *output, CJString *errMsg ) { int index = leftIndex + 1; bool go = true; while(go) { int stop = getNextComma(input, index); if(stop < 0 || stop > rightIndex) { stop = rightIndex; go = false; } if(index < stop) { if(stop - index < 3 || input->mEntities[index].mToken.mType != CJ_TT_STRING || input->mEntities[index + 1].mToken.mType != CJ_TT_COLON) { size_t n = sizeof(errMsg->mData); char *ptr = errMsg->mData; snprintf( ptr, n, "%s", "Object fields require: { string ':' value }, not: " ); for(int i = index; i < stop; i++) { if(i > index) snprintf(ptr + strlen(ptr), n - strlen(ptr), "%s ", ","); snprintf( ptr + strlen(ptr), n - strlen(ptr), "%s", getTokenTypeString(&input->mEntities[i].mToken) ); } snprintf( ptr + strlen(ptr), n - strlen(ptr), ", line: %d", input->mEntities[index - 1].mToken.mLineNumber ); freeEntityObject(output); return(false); } Entity theEntity = makeEntity(); // If there's only one entity after the ':' and its // type is already known, just use it directly. if(stop - index == 3 && input->mEntities[stop - 1].mType != CJ_ET_NONE) { theEntity = input->mEntities[stop - 1]; } else { EntityArray theArray = makeEntityArray(); for(int i = index + 2; i < stop; i++) addToEntityArray(&input->mEntities[i], &theArray); if(!getEntity(&theArray, &theEntity, errMsg)) { freeEntityObject(output); return(false); } } EntityObjectEntry theEntry = makeEntityObjectEntry(); theEntry.mKey = input->mEntities[index].mString; theEntry.mEntity = theEntity; addToEntityObject(&theEntry, output); } index = stop + 1; } return(true); } // --------------------------- // getEntity // --------------------------- bool getEntity(EntityArray *array, Entity *entityPtr, CJString *errMsg) { // Deal with our aggregate types, CJ_ET_OBJECT and CJ_ET_ARRAY for(;;) { int leftIndex = -1; int rightIndex = -1; for(int i = 0; leftIndex == -1 && i < array->mCount; i++) if(array->mEntities[i].mToken.mType == CJ_TT_LEFT_SQUARE) leftIndex = i; if(leftIndex == -1) break; if((rightIndex = getMatchingIndex(array, leftIndex)) == -1) { Token theToken = array->mEntities[leftIndex].mToken; snprintf( errMsg->mData, sizeof(errMsg->mData), "No match for: '%s', line: %d", theToken.mText.mData, theToken.mLineNumber ); freeEntityArray(array); return(false); } EntityArray *output = Memory.allocate(sizeof(*output)); *output = makeEntityArray(); if(!getEntityArray(array, leftIndex, rightIndex, output, errMsg)) { Memory.deallocate(output); freeEntityArray(array); return(false); } // Now, we want to replace everything from 'leftIndex' up to // and including 'rightIndex' with our array entity element int delta = (rightIndex - leftIndex); for(int i = leftIndex + 1; i < array->mCount; i++) { if(i + delta < array->mCount) array->mEntities[i] = array->mEntities[i + delta]; else array->mEntities[i] = makeEntity(); } array->mCount -= delta; array->mEntities[leftIndex].mType = CJ_ET_ARRAY; array->mEntities[leftIndex].mArray = output; array->mEntities[leftIndex].mToken = makeToken(); } for(;;) { int leftIndex = -1; int rightIndex = -1; for(int i = 0; leftIndex == -1 && i < array->mCount; i++) if(array->mEntities[i].mToken.mType == CJ_TT_LEFT_CURLY) leftIndex = i; if(leftIndex == -1) break; if((rightIndex = getMatchingIndex(array, leftIndex)) == -1) { Token theToken = array->mEntities[leftIndex].mToken; snprintf( errMsg->mData, sizeof(errMsg->mData), "No match for: '%s', line: %d", theToken.mText.mData, theToken.mLineNumber ); freeEntityArray(array); return(false); } EntityObject *output = Memory.allocate(sizeof(*output)); *output = makeEntityObject(); if(!getEntityObject(array, leftIndex, rightIndex, output, errMsg)) { Memory.deallocate(output); freeEntityArray(array); return(false); } // Now, we want to replace everything from 'leftIndex' up to // and including 'rightIndex' with our object entity element int delta = (rightIndex - leftIndex); for(int i = leftIndex + 1; i < array->mCount; i++) { if(i + delta < array->mCount) array->mEntities[i] = array->mEntities[i + delta]; else array->mEntities[i] = makeEntity(); } array->mCount -= delta; array->mEntities[leftIndex].mType = CJ_ET_OBJECT; array->mEntities[leftIndex].mObject = output; array->mEntities[leftIndex].mToken = makeToken(); } if(array->mCount > 1) { size_t n = sizeof(errMsg->mData); char *ptr = errMsg->mData; snprintf( ptr, n, "Expected one entity found: %d ", array->mCount ); for(int i = 0; i < array->mCount; i++) { if(i > 0) snprintf(ptr + strlen(ptr), n - strlen(ptr), "%s ", ","); snprintf( ptr + strlen(ptr), n - strlen(ptr), "%s", getTokenTypeString(&array->mEntities[i].mToken) ); } freeEntityArray(array); return(false); } *entityPtr = array->mEntities[0]; Memory.deallocate(array->mEntities); return(true); } // --------------------------- // tokensToEntities // --------------------------- bool tokensToEntities(TokenList *tokenList, EntityArray *array, CJString *errMsg) { for(int i = 0; i < tokenList->mCount; i++) { Entity theEntity = makeEntity(); theEntity.mToken = tokenList->mTokens[i]; // Some types of tokens can be dealt with immediately, // see if this token is one of those types switch(theEntity.mToken.mType) { case CJ_TT_NUMBER: { char *endPtr = NULL; double value = strtod(theEntity.mToken.mText.mData, &endPtr); if(*endPtr != '\0') { snprintf( errMsg->mData, sizeof(errMsg->mData), "Invalid number: (%s), line: %d", theEntity.mToken.mText.mData, theEntity.mToken.mLineNumber ); freeEntityArray(array); return(false); } theEntity.mValue = value; theEntity.mType = CJ_ET_NUMBER; } break; case CJ_TT_TRUE: { theEntity.mBool = true; theEntity.mType = CJ_ET_BOOLEAN; } break; case CJ_TT_FALSE: { theEntity.mBool = false; theEntity.mType = CJ_ET_BOOLEAN; } break; case CJ_TT_NULL: { theEntity.mType = CJ_ET_NULL; } break; case CJ_TT_STRING: { theEntity.mType = CJ_ET_STRING; theEntity.mString = theEntity.mToken.mText; } break; default: break; } addToEntityArray(&theEntity, array); } return(true); }