#include "toy_scope.h" #include "toy_memory.h" //run up the ancestor chain, freeing anything with 0 references left static void freeAncestorChain(Toy_Scope* scope) { while (scope != NULL) { Toy_Scope* next = scope->ancestor; scope->references--; if (scope->references <= 0) { Toy_freeLiteralDictionary(&scope->variables); Toy_freeLiteralDictionary(&scope->types); TOY_FREE(Toy_Scope, scope); } scope = next; } } //return false if invalid type static bool checkType(Toy_Literal typeLiteral, Toy_Literal original, Toy_Literal value, bool constCheck) { //for constants, fail if original != value if (constCheck && TOY_AS_TYPE(typeLiteral).constant && !Toy_literalsAreEqual(original, value)) { return false; } //for any types if (TOY_AS_TYPE(typeLiteral).typeOf == TOY_LITERAL_ANY) { return true; } //don't allow null types if (TOY_AS_TYPE(typeLiteral).typeOf == TOY_LITERAL_NULL) { return false; } //always allow null values if (TOY_IS_NULL(value)) { return true; } //for each type, if a mismatch is found, return false if (TOY_AS_TYPE(typeLiteral).typeOf == TOY_LITERAL_BOOLEAN && !TOY_IS_BOOLEAN(value)) { return false; } if (TOY_AS_TYPE(typeLiteral).typeOf == TOY_LITERAL_INTEGER && !TOY_IS_INTEGER(value)) { return false; } if (TOY_AS_TYPE(typeLiteral).typeOf == TOY_LITERAL_FLOAT && !TOY_IS_FLOAT(value)) { return false; } if (TOY_AS_TYPE(typeLiteral).typeOf == TOY_LITERAL_STRING && !TOY_IS_STRING(value)) { return false; } if (TOY_AS_TYPE(typeLiteral).typeOf == TOY_LITERAL_ARRAY && !TOY_IS_ARRAY(value)) { return false; } if (TOY_IS_ARRAY(value)) { //check value's type if (TOY_AS_TYPE(typeLiteral).typeOf != TOY_LITERAL_ARRAY) { return false; } //if null, assume it's a new array variable that needs checking if (TOY_IS_NULL(original)) { for (int i = 0; i < TOY_AS_ARRAY(value)->count; i++) { if (!checkType( ((Toy_Literal*)(TOY_AS_TYPE(typeLiteral).subtypes))[0], TOY_TO_NULL_LITERAL, TOY_AS_ARRAY(value)->literals[i], constCheck)) { return false; } } return true; } //check children for (int i = 0; i < TOY_AS_ARRAY(value)->count; i++) { if (TOY_AS_ARRAY(original)->count <= i) { return true; //assume new entry pushed } if (!checkType(((Toy_Literal*)(TOY_AS_TYPE(typeLiteral).subtypes))[0], TOY_AS_ARRAY(original)->literals[i], TOY_AS_ARRAY(value)->literals[i], constCheck)) { return false; } } } if (TOY_AS_TYPE(typeLiteral).typeOf == TOY_LITERAL_DICTIONARY && !TOY_IS_DICTIONARY(value)) { return false; } if (TOY_IS_DICTIONARY(value)) { //check value's type if (TOY_AS_TYPE(typeLiteral).typeOf != TOY_LITERAL_DICTIONARY) { return false; } //if null, assume it's a new dictionary variable that needs checking if (TOY_IS_NULL(original)) { for (int i = 0; i < TOY_AS_DICTIONARY(value)->capacity; i++) { //check the type of key and value if (!checkType(((Toy_Literal*)(TOY_AS_TYPE(typeLiteral).subtypes))[0], TOY_TO_NULL_LITERAL, TOY_AS_DICTIONARY(value)->entries[i].key, constCheck)) { return false; } if (!checkType(((Toy_Literal*)(TOY_AS_TYPE(typeLiteral).subtypes))[1], TOY_TO_NULL_LITERAL, TOY_AS_DICTIONARY(value)->entries[i].value, constCheck)) { return false; } } return true; } //check each child of value against the child of original for (int i = 0; i < TOY_AS_DICTIONARY(value)->capacity; i++) { if (TOY_IS_NULL(TOY_AS_DICTIONARY(value)->entries[i].key)) { //only non-tombstones continue; } //find the internal child of original that matches this child of value Toy_private_dictionary_entry* ptr = NULL; for (int j = 0; j < TOY_AS_DICTIONARY(original)->capacity; j++) { if (Toy_literalsAreEqual(TOY_AS_DICTIONARY(original)->entries[j].key, TOY_AS_DICTIONARY(value)->entries[i].key)) { ptr = &TOY_AS_DICTIONARY(original)->entries[j]; break; } } //if not found, assume it's a new entry if (!ptr) { continue; } //check the type of key and value if (!checkType(((Toy_Literal*)(TOY_AS_TYPE(typeLiteral).subtypes))[0], ptr->key, TOY_AS_DICTIONARY(value)->entries[i].key, constCheck)) { return false; } if (!checkType(((Toy_Literal*)(TOY_AS_TYPE(typeLiteral).subtypes))[1], ptr->value, TOY_AS_DICTIONARY(value)->entries[i].value, constCheck)) { return false; } } } if (TOY_AS_TYPE(typeLiteral).typeOf == TOY_LITERAL_FUNCTION && !TOY_IS_FUNCTION(value)) { return false; } if (TOY_AS_TYPE(typeLiteral).typeOf == TOY_LITERAL_TYPE && !TOY_IS_TYPE(value)) { return false; } if (TOY_AS_TYPE(typeLiteral).typeOf == TOY_LITERAL_OPAQUE && !TOY_IS_OPAQUE(value)) { return false; } return true; } //exposed functions Toy_Scope* Toy_pushScope(Toy_Scope* ancestor) { Toy_Scope* scope = TOY_ALLOCATE(Toy_Scope, 1); scope->ancestor = ancestor; Toy_initLiteralDictionary(&scope->variables); Toy_initLiteralDictionary(&scope->types); //tick up all scope reference counts scope->references = 0; for (Toy_Scope* ptr = scope; ptr != NULL; ptr = ptr->ancestor) { ptr->references++; } return scope; } Toy_Scope* Toy_popScope(Toy_Scope* scope) { if (scope == NULL) { //CAN pop a null return NULL; } Toy_Scope* ret = scope->ancestor; //BUGFIX: when freeing a scope, free the functions' scopes manually - I *think* this is related to the closure hack-in for (int i = 0; i < scope->variables.capacity; i++) { //handle keys, just in case if (TOY_IS_FUNCTION(scope->variables.entries[i].key)) { Toy_popScope(TOY_AS_FUNCTION(scope->variables.entries[i].key).scope); TOY_AS_FUNCTION(scope->variables.entries[i].key).scope = NULL; } if (TOY_IS_FUNCTION(scope->variables.entries[i].value)) { Toy_popScope(TOY_AS_FUNCTION(scope->variables.entries[i].value).scope); TOY_AS_FUNCTION(scope->variables.entries[i].value).scope = NULL; } } freeAncestorChain(scope); return ret; } Toy_Scope* Toy_copyScope(Toy_Scope* original) { if (original == NULL) { return NULL; } Toy_Scope* scope = TOY_ALLOCATE(Toy_Scope, 1); scope->ancestor = original->ancestor; Toy_initLiteralDictionary(&scope->variables); Toy_initLiteralDictionary(&scope->types); //tick up all scope reference counts scope->references = 0; for (Toy_Scope* ptr = scope; ptr != NULL; ptr = ptr->ancestor) { ptr->references++; } //copy the contents of the dictionaries for (int i = 0; i < original->variables.capacity; i++) { if (!TOY_IS_NULL(original->variables.entries[i].key)) { Toy_setLiteralDictionary(&scope->variables, original->variables.entries[i].key, original->variables.entries[i].value); } } for (int i = 0; i < original->types.capacity; i++) { if (!TOY_IS_NULL(original->types.entries[i].key)) { Toy_setLiteralDictionary(&scope->types, original->types.entries[i].key, original->types.entries[i].value); } } return scope; } //returns false if error bool Toy_declareScopeVariable(Toy_Scope* scope, Toy_Literal key, Toy_Literal type) { //don't redefine a variable within this scope if (Toy_existsLiteralDictionary(&scope->variables, key)) { return false; } if (!TOY_IS_TYPE(type)) { return false; } //store the type, for later checking on assignment Toy_setLiteralDictionary(&scope->types, key, type); Toy_setLiteralDictionary(&scope->variables, key, TOY_TO_NULL_LITERAL); return true; } bool Toy_isDelcaredScopeVariable(Toy_Scope* scope, Toy_Literal key) { while (scope != NULL) { if (Toy_existsLiteralDictionary(&scope->variables, key)) { return true; } scope = scope->ancestor; } return false; } //return false if undefined, or can't be assigned bool Toy_setScopeVariable(Toy_Scope* scope, Toy_Literal key, Toy_Literal value, bool constCheck) { while (scope != NULL) { //if it's not in this scope, keep searching up the chain if (!Toy_existsLiteralDictionary(&scope->variables, key)) { scope = scope->ancestor; continue; } //type checking Toy_Literal typeLiteral = Toy_getLiteralDictionary(&scope->types, key); Toy_Literal original = Toy_getLiteralDictionary(&scope->variables, key); if (!checkType(typeLiteral, original, value, constCheck)) { Toy_freeLiteral(typeLiteral); Toy_freeLiteral(original); return false; } //actually assign Toy_setLiteralDictionary(&scope->variables, key, value); //key & value are copied here Toy_freeLiteral(typeLiteral); Toy_freeLiteral(original); return true; } return false; } bool Toy_getScopeVariable(Toy_Scope* scope, Toy_Literal key, Toy_Literal* valueHandle) { //optimized to reduce call stack while (scope != NULL) { if (Toy_existsLiteralDictionary(&scope->variables, key)) { *valueHandle = Toy_getLiteralDictionary(&scope->variables, key); return true; } scope = scope->ancestor; } return false; } Toy_Literal Toy_getScopeType(Toy_Scope* scope, Toy_Literal key) { while (scope != NULL) { if (Toy_existsLiteralDictionary(&scope->types, key)) { return Toy_getLiteralDictionary(&scope->types, key); } scope = scope->ancestor; } return TOY_TO_NULL_LITERAL; }