So after some thinking i came up with this system. it's not exactly idiomatic form pov of C programming, but it does exactly what i want and seems to work well with limited number of arguments (0-7 arguments, 0-1 return value). Key insights:
limitations:
below is the prototype i've ended up with for now:
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
// type system. for now only numeric types,
// but can be extended to arrays, strings & custom types
typedef union TPun TPun;
typedef struct Obj Obj;
union TPun{ // for type punning
uintptr_t Any; void* P; Obj (*Fn)(Obj *arg7);
uint8_t U8; uint16_t U16; uint32_t U32; uint64_t U64;
int8_t I8; int16_t I16; int32_t I32; int64_t I64;
float F32; double F64;
};
struct Obj {uint16_t T; TPun V;};
typedef enum {
T_Unit = 0, T_Any=1, T_P = 2, T_Fn=3,
T_U8=4, T_U16=5, T_U32=6, T_U64=7,
T_I8=8, T_I16=9, T_I32=10, T_I64=11,
T_F32=12, T_F64=13,
} TBase;
const char * TBaseNames[14] = {
"Unit", "Any", "Ptr", "Fn",
"U8", "U16", "U32", "U64",
"I8", "I16", "I32", "I64",
"F32", "F64",
};
// dispatch system
typedef struct {uint16_t retT; uint16_t aT[7]; void* fn;} Method7;
typedef struct {uint16_t len; uint16_t cap; Method7*methods;} Dispatch7;
// Dispatch is a dynamic array of signatures + pointers
int find_method7(Dispatch7 *disp, uint16_t sig[8]){
for (int i=0; i<(int) disp->len; i++) {
Method7*m = &disp->methods[i];
uint16_t* msig = (uint16_t*) m;
int sig_match = 1;
for (int a = 0; a<8; a++) {
sig_match &= (sig[a] == T_Any) | (msig[a] == T_Any) | (msig[a] == sig[a]);
// any type is expected | returns untyped | types match
}
if (sig_match) {return i;}
}
return -1;
}
int put_method7(Dispatch7 *disp, Method7*meth){
int i = find_method7(disp, (uint16_t*) meth);
if (i != -1) { // substitute
disp->methods[i] = *meth;
return i;
} else { // append
if ((disp->cap-disp->len)==0) {// realloc
uint32_t newcap;
if (disp->cap == 0) newcap=1;
else if (disp->cap==1) newcap=4;
else newcap=disp->cap+4;
disp->methods = reallocarray(disp->methods, disp->cap+4, sizeof(Method7));
assert(disp->methods && "don't forget to buy some RAM");
disp->cap = newcap;
} // append
i = disp->len;
disp->len++;
disp->methods[i] = *meth;
return i;
}
}
int call7(Dispatch7*disp, Obj args[8]){
uint16_t sig[8];
for (int i=0; i<8; i++) {sig[i] = args[i].T;}
int i = find_method7(disp, sig);
if (i == -1) return 1;
TPun fn; fn.Fn = disp->methods[i].fn;
args[0] = fn.Fn(&args[1]);
return 0;
}
void clear_args7(Obj args[8]){
for (int i = 0; i<8; i++){
args[i].T = T_Unit;//0
args[i].V.Any = 0;
}
args[0].T = T_Any; // by default no expectation of return type,
// if particular type should be matched, it must be set explicitly
}
// example functions
Obj f_int(Obj *args){
printf("int x = %ld\n", args[0].V.I64);
return ((Obj) {T_Unit,{.Any=0}});
}
Obj int_f_int(Obj *args){
printf("int x = %ld ; return int x = %ld \n", args[0].V.I64, args[0].V.I64+5);
return (Obj) {T_I64,{.I64=args[0].V.I64+5}}; // to test returns
}
Obj f_float(Obj *args) {
printf("float x = %f\n", args[0].V.F32);
return (Obj) {T_Unit,{.Any=0}};
}
Obj f_int_float(Obj *args) {
printf("int x = %ld ; float y = %f\n", args[0].V.I64, args[1].V.F32);
return (Obj) {T_Unit,{.Any=0}};
}
Method7 ms[4] = {
{0, T_I64, 0,0,0,0,0,0, f_int},
{T_I64, T_I64, 0,0,0,0,0,0, int_f_int},
{0, T_F32, 0,0,0,0,0,0, f_float},
{0, T_I64, T_F32, 0,0,0,0,0, f_int_float},
};
Dispatch7 f = {4, 4, ms};
int main(){
Obj args[8];
clear_args7(args);
args[1] = (Obj) {T_I64,{.I64=5}};
call7(&f, args); // int x = 5
assert((args[0].T == T_Unit) && "void_f_int should be called");
clear_args7(args);
args[0].T = T_I64;
args[1] = (Obj) {T_I64,{.I64=5}};
call7(&f, args); // int x = 5
assert((args[0].T == T_I64) && "inf_f_int should be called");
assert((args[0].V.I64 == 10) && "int_f_int should return 10");
clear_args7(args);
args[1] = (Obj) {T_F32,{.F32=6.5}};
call7(&f, args); // float y = 6.50000
clear_args7(args);
args[1] = (Obj) {T_I64, {.I64=7}};
args[2] = (Obj) {T_F32,{.F32=8.5}};
call7(&f, args); // int x = 7 ; float y = 8.50000
clear_args7(args);
args[1] = (Obj) {T_F32,{.F32=9.5}};
args[2] = (Obj) {T_I64, {.I64=10}};
int i = call7(&f, args);
assert((i != 0) && "should fail");
}