diff --git a/examples/classes_demo.fun b/examples/classes_demo.fun new file mode 100644 index 0000000..eff722c --- /dev/null +++ b/examples/classes_demo.fun @@ -0,0 +1,67 @@ +#!/usr/bin/env fun + +// Classes demo: fields, methods (with explicit 'this'), and usage. +// +// Notes: +// - Define a class with `class Name` +// - Fields are initialized with simple assignments inside the class body. +// - Methods are written as `fun method(this, ...) { ... }` with 'this' required as the first parameter. +// - Instantiation: p = Name() +// - Method calls: load the method function from the instance into a variable, then call it with the instance as the first argument. +// Example: +// move_fn = p["move"] +// move_fn(p, dx, dy) + +class Point + x = 0 + y = 0 + fun move(this, dx, dy) + this.x = this.x + dx + this.y = this.y + dy + return 0 + fun toString(this) + return "Point(" + to_string(this.x) + ", " + to_string(this.y) + ")" + +class Counter + value = 0 + fun inc(this) + this.value = this.value + 1 + return this.value + fun add(this, n) + this.value = this.value + n + return this.value + +// Instantiate a Point +p = Point() + +// Types +print("typeof(Point) = " + typeof(Point)) // Function (factory) +print("typeof(p) = " + typeof(p)) // Map (instance) + +// Read/write fields +print("p.x = " + to_string(p["x"]) + ", p.y = " + to_string(p["y"])) +p["x"] = 10 +p["y"] = -2 +print("after set -> p.x = " + to_string(p["x"]) + ", p.y = " + to_string(p["y"])) + +// Call methods by first loading them into identifiers, then passing 'p' as first argument +to_str = p["toString"] +print("toString(p) => " + to_str(p)) + +move_fn = p["move"] +move_fn(p, 3, 5) +print("after move(3,5) -> " + to_str(p)) // expect Point(13, 3) + +// Another instance with defaults +q = Point() +print("q initially -> " + to_str(q)) // Point(0, 0) + +// Counter demo +c = Counter() +inc = c["inc"] +add = c["add"] + +print("counter inc -> " + to_string(inc(c))) // 1 +print("counter inc -> " + to_string(inc(c))) // 2 +print("counter add(5) -> " + to_string(add(c, 5))) // 7 +print("counter current value = " + to_string(c["value"])) diff --git a/play.fun b/play.fun index 4478b67..889b523 100755 --- a/play.fun +++ b/play.fun @@ -30,9 +30,9 @@ n = 100 print(n) print("Typeof n: " + typeof(n)) -fun n() - print("n()") -n() +fun n(num) + print("n(" + to_string(num) + ")") +n(42) // Typeof n MUST be of type Function here... Not Sint64. print("Typeof n: " + typeof(n)) diff --git a/src/parser.c b/src/parser.c index c0202cf..adfd7e4 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1857,6 +1857,223 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, bytecode_add_instruction(bc, OP_LINE, stmt_line); } + /* class definition -> factory function */ + if (starts_with(src, len, *pos, "class")) { + *pos += 5; + skip_spaces(src, len, pos); + /* class name */ + char *cname = NULL; + if (!read_identifier_into(src, len, pos, &cname)) { + parser_fail(*pos, "Expected class name after 'class'"); + return; + } + int cgi = sym_index(cname); + + /* end of class header line */ + skip_to_eol(src, len, pos); + + /* Build factory function: Name() -> instance map with fields and methods */ + Bytecode *ctor_bc = bytecode_new(); + /* local env for the factory to allow temp locals */ + LocalEnv ctor_env; + memset(&ctor_env, 0, sizeof(ctor_env)); + LocalEnv *prev_env = g_locals; + g_locals = &ctor_env; + + /* instance map: __this = {} */ + int l_this = local_add("__this"); + bytecode_add_instruction(ctor_bc, OP_MAKE_MAP, 0); + bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_this); + + /* Parse class body at increased indent */ + int body_indent = 0; + size_t look_body = *pos; + if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) { + /* iterate over class members at body_indent */ + for (;;) { + size_t member_line_start = *pos; + int member_indent = 0; + if (!read_line_start(src, len, pos, &member_indent)) { + /* EOF */ + break; + } + if (member_indent < body_indent) { + /* end of class body */ + *pos = member_line_start; + break; + } + if (member_indent > body_indent) { + /* skip nested blocks that are part of previous method parsing */ + parse_block(ctor_bc, src, len, pos, member_indent); + continue; + } + + /* at body_indent: member declaration (field = expr) or method 'fun name(...)' */ + if (starts_with(src, len, *pos, "fun")) { + /* method definition: fun m(this, ...) ... */ + *pos += 3; + skip_spaces(src, len, pos); + char *mname = NULL; + if (!read_identifier_into(src, len, pos, &mname)) { + parser_fail(*pos, "Expected method name after 'fun' in class"); + g_locals = prev_env; + free(cname); + return; + } + skip_spaces(src, len, pos); + if (!consume_char(src, len, pos, '(')) { + parser_fail(*pos, "Expected '(' after method name"); + free(mname); + g_locals = prev_env; + free(cname); + return; + } + + /* Build method function bytecode */ + Bytecode *m_bc = bytecode_new(); + LocalEnv m_env; + memset(&m_env, 0, sizeof(m_env)); + LocalEnv *saved = g_locals; + g_locals = &m_env; + + /* Parse params, ensure first is 'this' (insert if missing) */ + int saw_param = 0; + int param_count = 0; + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] != ')') { + for (;;) { + char *pname = NULL; + if (!read_identifier_into(src, len, pos, &pname)) { + parser_fail(*pos, "Expected parameter name"); + free(mname); + g_locals = saved; + g_locals = prev_env; + free(cname); + return; + } + if (param_count == 0 && strcmp(pname, "this") != 0) { + /* Require explicit 'this' as first parameter */ + parser_fail(*pos, "First parameter of a method must be 'this'"); + free(pname); + free(mname); + g_locals = saved; + g_locals = prev_env; + free(cname); + return; + } + local_add(pname); + free(pname); + param_count++; + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + continue; + } + break; + } + } else { + /* no params: enforce (this) */ + parser_fail(*pos, "Method must declare at least 'this' parameter"); + free(mname); + g_locals = saved; + g_locals = prev_env; + free(cname); + return; + } + + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after method parameter list"); + free(mname); + g_locals = saved; + g_locals = prev_env; + free(cname); + return; + } + /* end header line */ + skip_to_eol(src, len, pos); + + /* parse method body at increased indent */ + int m_body_indent = 0; + size_t look_m = *pos; + if (read_line_start(src, len, &look_m, &m_body_indent) && m_body_indent > body_indent) { + parse_block(m_bc, src, len, pos, m_body_indent); + } else { + /* empty method body allowed -> return */ + } + /* ensure return */ + bytecode_add_instruction(m_bc, OP_RETURN, 0); + + /* restore env to factory */ + g_locals = saved; + + /* Insert method function into instance: this["mname"] = */ + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this); + int kci = bytecode_add_constant(ctor_bc, make_string(mname)); + bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, kci); + int mci = bytecode_add_constant(ctor_bc, make_function(m_bc)); + bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, mci); + bytecode_add_instruction(ctor_bc, OP_INDEX_SET, 0); + + free(mname); + continue; + } + + /* field initializer: ident = expr */ + size_t lp = *pos; + char *fname = NULL; + if (!read_identifier_into(src, len, &lp, &fname)) { + parser_fail(*pos, "Expected field or 'fun' in class body"); + g_locals = prev_env; + free(cname); + return; + } + size_t tmp = lp; + skip_spaces(src, len, &tmp); + if (tmp >= len || src[tmp] != '=') { + free(fname); + parser_fail(tmp, "Expected '=' in field initializer"); + g_locals = prev_env; + free(cname); + return; + } + /* commit position and consume '=' */ + *pos = tmp + 1; + /* emit: this["fname"] = (expr) */ + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this); + int fkey = bytecode_add_constant(ctor_bc, make_string(fname)); + bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, fkey); + free(fname); + if (!emit_expression(ctor_bc, src, len, pos)) { + parser_fail(*pos, "Expected expression in field initializer"); + g_locals = prev_env; + free(cname); + return; + } + bytecode_add_instruction(ctor_bc, OP_INDEX_SET, 0); + /* end of line */ + skip_to_eol(src, len, pos); + } + } else { + /* empty class body allowed */ + } + + /* return instance */ + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this); + bytecode_add_instruction(ctor_bc, OP_RETURN, 0); + + /* restore outer locals env */ + g_locals = prev_env; + + /* bind factory function globally under class name */ + int cci = bytecode_add_constant(bc, make_function(ctor_bc)); + bytecode_add_instruction(bc, OP_LOAD_CONST, cci); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, cgi); + + free(cname); + continue; + } + if (starts_with(src, len, *pos, "fun")) { /* parse header: fun name(arg, ...) */ *pos += 3;