diff --git a/examples/objects_basic.fun b/examples/objects_basic.fun new file mode 100755 index 0000000..fda4d76 --- /dev/null +++ b/examples/objects_basic.fun @@ -0,0 +1,30 @@ +#!/usr/bin/env fun + +// Objects via maps: bracket access and method calls with explicit self + +// Construct an object as a map (attach methods later) +obj = { "x": 1 } + +// Property read +print(obj["x"]) // -> 1 + +// Property write +obj["x"] = 5 +print(obj["x"]) // -> 5 + +// Define a method function (expects self as first parameter) +fun inc(self, d) + // Update a field using bracket notation + self["x"] = self["x"] + d + return self["x"] + +// Attach method and call it (explicit self) +obj["inc"] = inc +inc(obj, 3) +print(obj["x"]) // -> 8 + +/* Expected output: +1 +5 +8 +*/ diff --git a/src/parser.c b/src/parser.c index 4d09d72..0f473cd 100644 --- a/src/parser.c +++ b/src/parser.c @@ -852,9 +852,11 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) printf("compile: CALL %s with %d arg(s)\n", name, argc); #endif bytecode_add_instruction(bc, OP_CALL, argc); - /* postfix indexing or slice */ + /* postfix indexing, slice, and dot access/method calls */ for (;;) { skip_spaces(src, len, pos); + + /* index/slice */ if (*pos < len && src[*pos] == '[') { (*pos)++; if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected start expression"); free(name); return 0; } @@ -877,6 +879,20 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) continue; } } + + /* dot property access: obj.field -> obj["field"] */ + if (*pos < len && src[*pos] == '.') { + (*pos)++; /* '.' */ + skip_spaces(src, len, pos); + char *mname = NULL; + if (!read_identifier_into(src, len, pos, &mname)) { parser_fail(*pos, "Expected identifier after '.'"); free(name); return 0; } + int kci = bytecode_add_constant(bc, make_string(mname)); + free(mname); + bytecode_add_instruction(bc, OP_LOAD_CONST, kci); + bytecode_add_instruction(bc, OP_INDEX_GET, 0); + continue; + } + break; } free(name); @@ -888,9 +904,11 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) int gi = sym_index(name); bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); } - /* postfix indexing or slice */ + /* postfix indexing, slice, and dot access/method calls */ for (;;) { skip_spaces(src, len, pos); + + /* index/slice */ if (*pos < len && src[*pos] == '[') { (*pos)++; if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected start expression"); free(name); return 0; } @@ -913,6 +931,20 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) continue; } } + + /* dot property access: obj.field -> obj["field"] */ + if (*pos < len && src[*pos] == '.') { + (*pos)++; + skip_spaces(src, len, pos); + char *mname = NULL; + if (!read_identifier_into(src, len, pos, &mname)) { parser_fail(*pos, "Expected identifier after '.'"); free(name); return 0; } + int kci = bytecode_add_constant(bc, make_string(mname)); + free(mname); + bytecode_add_instruction(bc, OP_LOAD_CONST, kci); + bytecode_add_instruction(bc, OP_INDEX_GET, 0); + continue; + } + break; } free(name); @@ -1404,6 +1436,59 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si int gi = (lidx < 0) ? sym_index(name) : -1; skip_spaces(src, len, &local_pos); + /* object field assignment: name.field = expr (only if '=' follows) */ + if (local_pos < len && src[local_pos] == '.') { + size_t stmt_start = *pos; /* for expression fallback */ + size_t look = local_pos + 1; /* point after '.' */ + skip_spaces(src, len, &look); + char *fname = NULL; + if (!read_identifier_into(src, len, &look, &fname)) { + parser_fail(look, "Expected field name after '.'"); + free(name); + return; + } + skip_spaces(src, len, &look); + if (look >= len || src[look] != '=') { + /* Not an assignment: treat as expression statement (e.g., obj.method(...)) */ + free(fname); + free(name); + size_t expr_pos = stmt_start; + if (emit_expression(bc, src, len, &expr_pos)) { + bytecode_add_instruction(bc, OP_POP, 0); + } + *pos = expr_pos; + skip_to_eol(src, len, pos); + return; + } + + /* Confirmed assignment: emit container, key, value, then INDEX_SET */ + /* Load container variable */ + if (lidx >= 0) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, lidx); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); + } + /* Push key */ + int kci = bytecode_add_constant(bc, make_string(fname)); + free(fname); + bytecode_add_instruction(bc, OP_LOAD_CONST, kci); + + /* Advance local_pos to after '=' and parse value expression */ + local_pos = look + 1; /* skip '=' */ + if (!emit_expression(bc, src, len, &local_pos)) { + parser_fail(local_pos, "Expected expression after '='"); + free(name); + return; + } + /* perform set: pops value, key, container (in that order) */ + bytecode_add_instruction(bc, OP_INDEX_SET, 0); + + free(name); + *pos = local_pos; + skip_to_eol(src, len, pos); + return; + } + /* array element assignment: name[expr] = expr */ if (local_pos < len && src[local_pos] == '[') { /* load array variable */ diff --git a/src/vm_case_index_get.inc b/src/vm_case_index_get.inc index 0d7f5f8..71c194d 100644 --- a/src/vm_case_index_get.inc +++ b/src/vm_case_index_get.inc @@ -1,6 +1,10 @@ case OP_INDEX_GET: { Value idx = pop_value(vm); Value container = pop_value(vm); +#ifdef FUN_DEBUG + fprintf(stderr, "DEBUG INDEX_GET: container.type=%d idx.type=%d\n", + container.type, idx.type); +#endif if (container.type == VAL_ARRAY) { if (idx.type != VAL_INT) { fprintf(stderr, "INDEX_GET index must be int for array\n"); exit(1); } Value elem; diff --git a/src/vm_case_index_set.inc b/src/vm_case_index_set.inc index 2da30b4..24f3e77 100644 --- a/src/vm_case_index_set.inc +++ b/src/vm_case_index_set.inc @@ -2,6 +2,10 @@ case OP_INDEX_SET: { Value v = pop_value(vm); Value idx = pop_value(vm); Value container = pop_value(vm); +#ifdef FUN_DEBUG + fprintf(stderr, "DEBUG INDEX_SET: container.type=%d idx.type=%d value.type=%d\n", + container.type, idx.type, v.type); +#endif if (container.type == VAL_ARRAY) { if (idx.type != VAL_INT) { fprintf(stderr, "INDEX_SET index must be int for array\n"); exit(1); } if (!array_set(&container, (int)idx.i, v)) { diff --git a/src/vm_case_load_global.inc b/src/vm_case_load_global.inc index 07749c0..3276090 100644 --- a/src/vm_case_load_global.inc +++ b/src/vm_case_load_global.inc @@ -4,6 +4,9 @@ case OP_LOAD_GLOBAL: { fprintf(stderr, "Runtime error: global index out of range\n"); exit(1); } +#ifdef FUN_DEBUG + fprintf(stderr, "DEBUG LOAD_GLOBAL[%d]: type=%d\n", idx, vm->globals[idx].type); +#endif push_value(vm, copy_value(&vm->globals[idx])); break; } diff --git a/src/vm_case_store_global.inc b/src/vm_case_store_global.inc index 6b8a18c..a49bb31 100644 --- a/src/vm_case_store_global.inc +++ b/src/vm_case_store_global.inc @@ -5,6 +5,9 @@ case OP_STORE_GLOBAL: { exit(1); } Value v = pop_value(vm); +#ifdef FUN_DEBUG + fprintf(stderr, "DEBUG STORE_GLOBAL[%d]: new.type=%d\n", idx, v.type); +#endif free_value(vm->globals[idx]); vm->globals[idx] = v; break;