diff --git a/include/ground.h b/include/ground.h index 8c41538..691d8c0 100644 --- a/include/ground.h +++ b/include/ground.h @@ -127,6 +127,7 @@ struct GroundError { // --- Program structure types definitions --- enum GroundTypeType { + GroundType_Undefined, GroundType_Int, GroundType_Double, GroundType_Char, GroundType_Bool, GroundType_String, GroundType_List, GroundType_Function, GroundType_Struct, GroundType_Object, diff --git a/src/Bytecode/Instruction/execute.c b/src/Bytecode/Instruction/execute.c index 6555b0d..42da74d 100644 --- a/src/Bytecode/Instruction/execute.c +++ b/src/Bytecode/Instruction/execute.c @@ -888,6 +888,137 @@ struct GroundExecutionResult _GroundBytecodeInstructionExecute(GroundBytecodeIns return CONTINUE; } CALLMETHOD: { + // bytecode: object_offset, field0_offset, ..., fieldN_offset, method_offset, arg0_offset, ..., argM_offset, output_offset + GroundSize len = instruction->args.len; + + // Navigate the object through field path to find the containing object and method + GroundBytecodeValue* current = HEAP_GET(heap, instruction->args.at[0]); + + // Walk the field path to find the containing object + GroundSize fieldPairsCount = 0; + for (GroundSize fi = 1; fi < len - 1; fi++) { + if (current->type.type != GroundType_Object) break; + GroundBytecodeValue* next = ¤t->as.Object.values[instruction->args.at[fi]]; + if (next->type.type == GroundType_Function) { + // This is the method + break; + } + current = next; + fieldPairsCount++; + } + + // The method is at index 1 + fieldPairsCount + GroundSize methodFieldIdx = 1 + fieldPairsCount; + GroundSize methodArgCount = len - methodFieldIdx - 2; // subtract method and output + + if (current->type.type != GroundType_Object) { + Ground.Log.Error("CALLMETHOD on non-object value in Ground.Bytecode.Instruction.execute"); + Ground.Flags.error = true; + return CONTINUE; + } + + GroundBytecodeFunction* method = ¤t->as.Object.values[instruction->args.at[methodFieldIdx]].as.Function; + + if (method->isNativeFunction) { + // Native method handling (same as CALL native path but with self appended) + ffi_type* argTypes[methodArgCount + 1]; + void* args[methodArgCount + 1]; + + // Self is the first arg for native methods + argTypes[0] = ffiTypeFromGroundType(&(GroundType){.type = GroundType_Object}); + args[0] = current; + + for (GroundSize i = 0; i < methodArgCount; i++) { + GroundBytecodeValue* val = HEAP_GET(heap, instruction->args.at[methodFieldIdx + 1 + i]); + argTypes[i + 1] = ffiTypeFromGroundType(&val->type); + switch (val->type.type) { + case GroundType_Int: args[i + 1] = &val->as.Int; break; + case GroundType_Double: args[i + 1] = &val->as.Double; break; + case GroundType_Char: args[i + 1] = &val->as.Char; break; + case GroundType_Bool: args[i + 1] = &val->as.Bool; break; + case GroundType_String: args[i + 1] = &val->as.String.cstr; break; + default: args[i + 1] = val; break; + } + } + + ffi_cif cif; + ffi_status status = ffi_prep_cif( + &cif, FFI_DEFAULT_ABI, methodArgCount + 1, + ffiTypeFromGroundType(method->returnType), argTypes + ); + + if (status != FFI_OK) { + Ground.Log.Error("ffi CIF preparation failed in Ground.Bytecode.Instruction.execute"); + Ground.Flags.error = true; + return CONTINUE; + } + + union { + GroundInt Int; + GroundDouble Double; + GroundChar Char; + GroundBool Bool; + char* String; + } result; + + ffi_call(&cif, method->program.native, &result, args); + + switch (method->returnType->type) { + case GroundType_Int: HEAP_SET(heap, instruction->args.at[len - 1], Ground.New.BytecodeValue(Ground.New.Value.Int(result.Int))); break; + case GroundType_Char: HEAP_SET(heap, instruction->args.at[len - 1], Ground.New.BytecodeValue(Ground.New.Value.Char(result.Char))); break; + case GroundType_Double: HEAP_SET(heap, instruction->args.at[len - 1], Ground.New.BytecodeValue(Ground.New.Value.Double(result.Double))); break; + case GroundType_Bool: HEAP_SET(heap, instruction->args.at[len - 1], Ground.New.BytecodeValue(Ground.New.Value.Bool(result.Bool))); break; + case GroundType_String: HEAP_SET(heap, instruction->args.at[len - 1], Ground.New.BytecodeValue(Ground.New.Value.String(Ground.New.String(result.String)))); break; + default: break; + } + + return CONTINUE; + } + + if (method->closure == NULL) { + Ground.Log.Error("unexpected NULL closure in Ground.Bytecode.Instruction.execute"); + Ground.Flags.error = true; + return CONTINUE; + } + + // Total args for method: methodArgCount explicit + 1 self + GroundSize totalArgs = methodArgCount + 1; + GroundSize heapSize = totalArgs + method->closure->len; + + GroundBytecodeHeap newHeap = { malloc(sizeof(GroundBytecodeValue) * heapSize), heapSize, heapSize }; + if (newHeap.heap == NULL) { + Ground.Log.Error("malloc failed in Ground.Bytecode.Instruction.execute"); + Ground.Flags.error = true; + return CONTINUE; + } + + // Load explicit method arguments into the heap + for (GroundSize i = 0; i < methodArgCount; i++) { + newHeap.heap[i] = Ground.Copy.BytecodeValue(HEAP_GET(heap, instruction->args.at[methodFieldIdx + 1 + i])); + } + + // Self (the containing object) is the last argument + newHeap.heap[methodArgCount] = Ground.Copy.BytecodeValue(current); + + // Copy closure variables after arguments + for (GroundSize i = totalArgs; i < newHeap.len; i++) { + newHeap.heap[i] = Ground.Copy.BytecodeValue(&method->closure->heap[i - totalArgs]); + } + + // Execute the method + GroundBytecodeValue result = Ground.Bytecode.Program.execute(method->program.ground, &newHeap); + + GroundBytecodeValue resultCopy = Ground.Copy.BytecodeValue(&result); + + // Clean up + for (GroundSize i = 0; i < newHeap.len; i++) { + Ground.Free.BytecodeValue(&newHeap.heap[i]); + } + free(newHeap.heap); + + // Store the result + HEAP_SET(heap, instruction->args.at[len - 1], resultCopy); + return CONTINUE; } STRUCT: { diff --git a/src/New/Bytecode.c b/src/New/Bytecode.c index d5e93cb..a166d4a 100644 --- a/src/New/Bytecode.c +++ b/src/New/Bytecode.c @@ -457,6 +457,158 @@ static inline GroundSize doOffsets(GroundProgram* gp, GroundState* state, Ground continue; } case GroundInstruction_CALLMETHOD: { + // callmethod &object -type (&field -type)... !methodName $args... &output + // Bytecode: object_offset, field0_offset, ..., fieldN_offset, method_offset, arg0_offset, ..., argM_offset, output_offset + GroundInstruction* inst = &gp->at[i]; + + // Find the methodName (FunctionRef) to determine the field path length + GroundSize methodIdx = 0; + for (GroundSize j = 0; j < inst->args.len; j++) { + if (inst->args.at[j].type == GroundArg_FunctionRef) { + methodIdx = j; + break; + } + } + + GroundSize fieldPairs = (methodIdx - 2) / 2; + GroundSize methodArgs = inst->args.len - methodIdx - 2; + GroundSize gbiSize = 1 + fieldPairs + 1 + methodArgs + 1; + + GroundBytecodeInstruction newInst = { + .type = GroundInstruction_CALLMETHOD, + .args = { + .at = malloc(sizeof(GroundSize) * gbiSize), + .capacity = gbiSize, + .len = gbiSize + } + }; + + if (newInst.args.at == NULL) { + Ground.Log.Error("malloc failed in Ground.New.Bytecode -> doOffsets"); + Ground.Flags.error = true; + return 0; + } + + // Resolve the object variable to a heap offset + GroundArg* arg = &inst->args.at[0]; + GroundVariable* item = NULL; + HASH_FIND_STR(state->variables, arg->as.ref->string, item); + if (item == NULL) { + item = malloc(sizeof(GroundVariable)); + if (item == NULL) { + Ground.Log.Error("malloc failed in Ground.New.Bytecode -> doOffsets"); + Ground.Flags.error = true; + return 0; + } + item->value = Ground.New.Value.Int(0); + strncpy(item->name, arg->as.ref->string, 2047); + item->_offset = size++; + HASH_ADD_STR(state->variables, name, item); + } + arg->_offset = item->_offset; + newInst.args.at[0] = arg->_offset; + + // Get the type of the object (type annotation is at args[1]) + GroundVariable* var = NULL; + HASH_FIND_STR(state->variables, inst->args.at[1].as.ref->string, var); + if (var == NULL || var->value.type.type != GroundType_Struct) { + Ground.Log.Error("unknown struct name in Ground.New.Bytecode -> doOffsets"); + Ground.Flags.error = true; + return 0; + } + GroundStruct* currentType = &var->value.as.Struct; + + // Resolve each field's offset within its containing struct + for (GroundSize j = 0; j < fieldPairs; j++) { + GroundArg* fieldArg = &inst->args.at[2 + (j * 2)]; + GroundObjectField* field; + HASH_FIND_STR(currentType->fields, fieldArg->as.ref->string, field); + if (field == NULL) { + Ground.Log.Error("unknown field in struct in Ground.New.Bytecode -> doOffsets"); + Ground.Flags.error = true; + return 0; + } + newInst.args.at[1 + j] = field->offset; + + // Resolve the type for the next field + GroundVariable* typeVar = NULL; + HASH_FIND_STR(state->variables, inst->args.at[3 + j * 2].as.ref->string, typeVar); + if (typeVar == NULL || typeVar->value.type.type != GroundType_Struct) { + Ground.Log.Error("field type is not a struct in Ground.New.Bytecode -> doOffsets"); + Ground.Flags.error = true; + return 0; + } + currentType = &typeVar->value.as.Struct; + } + + // Resolve the method name to a field offset + GroundArg* methodArg = &inst->args.at[methodIdx]; + GroundObjectField* methodField; + HASH_FIND_STR(currentType->fields, methodArg->as.ref->string, methodField); + if (methodField == NULL) { + Ground.Log.Error("unknown method in struct in Ground.New.Bytecode -> doOffsets"); + Ground.Flags.error = true; + return 0; + } + newInst.args.at[1 + fieldPairs] = methodField->offset; + + // Resolve the method arguments + for (GroundSize j = 0; j < methodArgs; j++) { + GroundArg* arg = &inst->args.at[methodIdx + 1 + j]; + if (arg->type == GroundArg_Value) { + GroundVariable* constItem = malloc(sizeof(GroundVariable)); + if (constItem == NULL) { + Ground.Log.Error("malloc failed in Ground.New.Bytecode -> doOffsets"); + Ground.Flags.error = true; + return 0; + } + constItem->value = Ground.Copy.Value(&arg->as.value); + if (Ground.Flags.error) return 0; + constItem->_offset = size++; + snprintf(constItem->name, sizeof(constItem->name) - 1, "_._.ground_internal_constant_%zu", constItem->_offset); + HASH_ADD_STR(state->variables, name, constItem); + newInst.args.at[2 + fieldPairs + j] = constItem->_offset; + } else { + GroundVariable* argItem = NULL; + HASH_FIND_STR(state->variables, arg->as.ref->string, argItem); + if (argItem == NULL) { + argItem = malloc(sizeof(GroundVariable)); + if (argItem == NULL) { + Ground.Log.Error("malloc failed in Ground.New.Bytecode -> doOffsets"); + Ground.Flags.error = true; + return 0; + } + argItem->value = Ground.New.Value.Int(0); + strncpy(argItem->name, arg->as.ref->string, 2047); + argItem->_offset = size++; + HASH_ADD_STR(state->variables, name, argItem); + } + arg->_offset = argItem->_offset; + newInst.args.at[2 + fieldPairs + j] = argItem->_offset; + } + } + + // Resolve the output variable + GroundArg* outputArg = &inst->args.at[inst->args.len - 1]; + GroundVariable* outputItem = NULL; + HASH_FIND_STR(state->variables, outputArg->as.ref->string, outputItem); + if (outputItem == NULL) { + outputItem = malloc(sizeof(GroundVariable)); + if (outputItem == NULL) { + Ground.Log.Error("malloc failed in Ground.New.Bytecode -> doOffsets"); + Ground.Flags.error = true; + return 0; + } + outputItem->value = Ground.New.Value.Int(0); + strncpy(outputItem->name, outputArg->as.ref->string, 2047); + outputItem->_offset = size++; + HASH_ADD_STR(state->variables, name, outputItem); + } + outputArg->_offset = outputItem->_offset; + newInst.args.at[gbiSize - 1] = outputArg->_offset; + + gbp->at[i] = newInst; + gbp->len++; continue; } diff --git a/src/Program/preprocess.c b/src/Program/preprocess.c index f3cfee2..75edd2e 100644 --- a/src/Program/preprocess.c +++ b/src/Program/preprocess.c @@ -222,15 +222,40 @@ static void doStruct(GroundProgram* program, GroundProgram* newProgram, GroundSt case GroundInstruction_FUN: { // Parse the function itself doFunction(program, newProgram, state, i); + + // Add hidden "self" parameter to the method + GroundVariable* fnVar = NULL; + HASH_FIND_STR(state->variables, newProgram->at[newProgram->len - 1].args.at[0].as.ref->string, fnVar); + if (fnVar != NULL && fnVar->value.type.type == GroundType_Function) { + Ground.Function.appendArg(&fnVar->value.as.Function, "self"); + + GroundVariable* selfVar = malloc(sizeof(GroundVariable)); + if (selfVar != NULL) { + snprintf(selfVar->name, 2047, "self"); + selfVar->value = Ground.New.Value.Int(0); + HASH_ADD_STR(fnVar->value.as.Function.closure->variables, name, selfVar); + } + } + + // Add it to the struct + GroundObjectField* field = malloc(sizeof(GroundObjectField)); + if (field == NULL) { + Ground.Log.Error("malloc failed in Ground.Program.preprocess -> doStruct"); + Ground.Flags.error = true; + return; + } + + snprintf(field->name, 2047, "%s", instruction->args.at[0].as.ref->string); + field->offset = fieldCount++; + + HASH_ADD_STR(gs.fields, name, field); + // Read and remove the emitted instruction GroundInstruction* inst = &newProgram->at[newProgram->len - 1]; Ground.Instruction.append(&output, Ground.New.Arg.FunctionRef(Ground.Copy.Identifier(inst->args.at[0].as.ref))); Ground.Free.Instruction(inst); newProgram->len--; - // TODO do the rest of this - // for some reason I haven't done it yet?? oh well who cares - // Add 3 to signal addition of closure Ground.Instruction.append(&output, (GroundArg) {.type = GroundArg_DirectRef, ._offset = 3}); break;