Start working on CALLMETHOD

This commit is contained in:
2026-07-16 14:39:39 +10:00
parent 8e4267d500
commit 7a53105eca
4 changed files with 312 additions and 3 deletions

View File

@@ -127,6 +127,7 @@ struct GroundError {
// --- Program structure types definitions --- // --- Program structure types definitions ---
enum GroundTypeType { enum GroundTypeType {
GroundType_Undefined,
GroundType_Int, GroundType_Double, GroundType_Char, GroundType_Bool, GroundType_Int, GroundType_Double, GroundType_Char, GroundType_Bool,
GroundType_String, GroundType_List, GroundType_String, GroundType_List,
GroundType_Function, GroundType_Struct, GroundType_Object, GroundType_Function, GroundType_Struct, GroundType_Object,

View File

@@ -888,6 +888,137 @@ struct GroundExecutionResult _GroundBytecodeInstructionExecute(GroundBytecodeIns
return CONTINUE; return CONTINUE;
} }
CALLMETHOD: { 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 = &current->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 = &current->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; return CONTINUE;
} }
STRUCT: { STRUCT: {

View File

@@ -457,6 +457,158 @@ static inline GroundSize doOffsets(GroundProgram* gp, GroundState* state, Ground
continue; continue;
} }
case GroundInstruction_CALLMETHOD: { 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; continue;
} }

View File

@@ -222,15 +222,40 @@ static void doStruct(GroundProgram* program, GroundProgram* newProgram, GroundSt
case GroundInstruction_FUN: { case GroundInstruction_FUN: {
// Parse the function itself // Parse the function itself
doFunction(program, newProgram, state, i); 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 // Read and remove the emitted instruction
GroundInstruction* inst = &newProgram->at[newProgram->len - 1]; 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.Instruction.append(&output, Ground.New.Arg.FunctionRef(Ground.Copy.Identifier(inst->args.at[0].as.ref)));
Ground.Free.Instruction(inst); Ground.Free.Instruction(inst);
newProgram->len--; 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 // Add 3 to signal addition of closure
Ground.Instruction.append(&output, (GroundArg) {.type = GroundArg_DirectRef, ._offset = 3}); Ground.Instruction.append(&output, (GroundArg) {.type = GroundArg_DirectRef, ._offset = 3});
break; break;