Skip to content

Commit e9f3c76

Browse files
committed
bind struct methods
1 parent d3bac76 commit e9f3c76

2 files changed

Lines changed: 96 additions & 9 deletions

File tree

eval.c

Lines changed: 82 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <string.h>
77

88
#include "agent.h"
9+
#include "builtin_modules.h"
910
#include "cbcvm.h"
1011
#include "cb_util.h"
1112
#include "compiler.h"
@@ -296,6 +297,70 @@ static void debug_state(size_t sp, size_t pc, struct cb_frame *frame)
296297
}
297298
#endif
298299

300+
/* FIXME: hacky as */
301+
static int method_caller(size_t argc, struct cb_value *argv,
302+
struct cb_value *result)
303+
{
304+
struct cb_value receiver, func;
305+
struct cb_frame *frame, new_frame;
306+
struct cb_user_function *ufunc;
307+
struct cb_code *code;
308+
309+
receiver = cb_cfunc_load_upvalue(0);
310+
func = cb_cfunc_load_upvalue(1);
311+
assert(func.val.as_function->type == CB_FUNCTION_USER);
312+
ufunc = &func.val.as_function->value.as_user;
313+
code = ufunc->code;
314+
315+
frame = cb_vm_state.frame;
316+
ensure_stack(code->stack_size + 1, *frame->sp - cb_vm_state.stack);
317+
memmove(argv, argv + 1, argc);
318+
319+
cb_vm_state.stack[frame->bp] = receiver;
320+
cb_vm_state.stack[frame->bp + 1] = func;
321+
322+
new_frame.parent = frame;
323+
new_frame.module_id = cb_modspec_id(code->modspec);
324+
new_frame.is_function = 1;
325+
new_frame.is_native = 0;
326+
new_frame.num_args = argc;
327+
new_frame.code = code;
328+
new_frame.bp = frame->bp + 1;
329+
330+
int failed = cb_eval(&new_frame);
331+
cb_vm_state.frame = frame;
332+
*result = cb_vm_state.stack[new_frame.bp];
333+
return failed;
334+
}
335+
336+
static void set_upvalue(struct cb_upvalue **uv, struct cb_value value)
337+
{
338+
(*uv) = malloc(sizeof(struct cb_upvalue));
339+
(*uv)->refcount = 1;
340+
(*uv)->is_closed = 1;
341+
(*uv)->v.value = value;
342+
}
343+
344+
static struct cb_value make_method_caller(struct cb_value receiver,
345+
struct cb_function *method)
346+
{
347+
struct cb_value bound_method = cb_cfunc_new(method->name, method->arity,
348+
method_caller);
349+
const int num_upvalues = 2;
350+
struct cb_upvalue **method_upvalues = malloc(
351+
num_upvalues * sizeof(struct cb_upvalue *));
352+
bound_method.val.as_function->nupvalues = num_upvalues;
353+
bound_method.val.as_function->upvalues = method_upvalues;
354+
355+
set_upvalue(&method_upvalues[0], receiver);
356+
set_upvalue(&method_upvalues[1], (struct cb_value) {
357+
.type = CB_VALUE_FUNCTION,
358+
.val = { .as_function = method },
359+
});
360+
361+
return bound_method;
362+
}
363+
299364
int eval_depth = 0;
300365

301366
static int cb_eval(struct cb_frame *frame)
@@ -1085,20 +1150,28 @@ DO_OP_LOAD_STRUCT: {
10851150
}
10861151
ssize_t idx;
10871152
val = cb_struct_get_field(s, fname, &idx);
1088-
if (!val) {
1089-
cb_str fname_str, specname_str;
1090-
1091-
fname_str = cb_agent_get_string(fname);
1092-
specname_str = cb_agent_get_string(s->spec->name);
1093-
ERROR("No such field '%s' on struct '%s'",
1094-
cb_strptr(&fname_str),
1095-
cb_strptr(&specname_str));
1153+
struct cb_value result;
1154+
if (val) {
1155+
result = *val;
1156+
} else {
1157+
struct cb_function *method =
1158+
cb_struct_spec_get_method(s->spec, fname);
1159+
if (method == NULL) {
1160+
cb_str fname_str, specname_str;
1161+
1162+
fname_str = cb_agent_get_string(fname);
1163+
specname_str = cb_agent_get_string(s->spec->name);
1164+
ERROR("No such field '%s' on struct '%s'",
1165+
cb_strptr(&fname_str),
1166+
cb_strptr(&specname_str));
1167+
}
1168+
result = make_method_caller(recv, method);
10961169
}
10971170
if (ic->index != -1) {
10981171
ic->spec = s->spec;
10991172
ic->index = idx;
11001173
}
1101-
PUSH(*val);
1174+
PUSH(result);
11021175
DISPATCH();
11031176
}
11041177

tests/test_methods.cb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import test;
2+
3+
function test_method_call() {
4+
let s = struct {
5+
function hello(name) {
6+
return string_concat("Hello, ", name, "!");
7+
}
8+
};
9+
10+
test::assert(s.hello("World") == "Hello, World!");
11+
12+
let hello = s.hello;
13+
test::assert(hello("World") == "Hello, World!");
14+
}

0 commit comments

Comments
 (0)