From a56a351da611038952b336be8b41d833c5592752 Mon Sep 17 00:00:00 2001 From: hanez Date: Wed, 1 Oct 2025 01:16:52 +0200 Subject: [PATCH] Added class inheritance and constructors. (0.16.0) --- CMakeLists.txt | 2 +- examples/class_constructor.fun | 45 +++++++++++ examples/inheritance_demo.fun | 59 ++++++++++++++ examples/namespaced_mod.fun | 2 +- examples/threads_demo.fun | 0 src/parser.c | 135 ++++++++++++++++++++++++++++++++- 6 files changed, 238 insertions(+), 5 deletions(-) create mode 100755 examples/class_constructor.fun create mode 100755 examples/inheritance_demo.fun mode change 100644 => 100755 examples/namespaced_mod.fun mode change 100644 => 100755 examples/threads_demo.fun diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e51a38..6e7d882 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(fun VERSION 0.15.4 LANGUAGES C) +project(fun VERSION 0.16.0 LANGUAGES C) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/examples/class_constructor.fun b/examples/class_constructor.fun new file mode 100755 index 0000000..09db7fc --- /dev/null +++ b/examples/class_constructor.fun @@ -0,0 +1,45 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-01 + */ + +/* + * Demonstrates class constructors named _construct(this, ...) + * The constructor is called automatically after instance creation, + * receiving 'this' and all header parameters as arguments. + */ + +print("=== class constructor demo ===") + +class Counter(number start) + // default field value (will be overwritten by _construct) + value = 0 + + // Runs automatically with (this, start) + fun _construct(this, s) + this.value = s + + fun inc(this) + this.value = this.value + 1 + return this.value + +c = Counter(10) +print("initial value=" + to_string(c.value)) // expect 10 +print("after inc=" + to_string(c.inc())) // expect 11 + +print("=== done ===") + +/* Expected output: +=== class constructor demo === +initial value=10 +after inc=11 +=== done === +*/ diff --git a/examples/inheritance_demo.fun b/examples/inheritance_demo.fun new file mode 100755 index 0000000..8518c3e --- /dev/null +++ b/examples/inheritance_demo.fun @@ -0,0 +1,59 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-01 + */ + +/* + * Inheritance demo: + * - Child extends Parent + * - Parent initializes fields in its constructor + * - Child overrides/extends behavior and fields + */ + +print("=== inheritance demo ===") + +class Parent(number start) + value = 0 + fun _construct(this, s) + this.value = s + + fun describe(this) + return "Parent(value=" + to_string(this.value) + ")" + + fun hello(this) + return "Hello from Parent()" + +class Child(number start) extends Parent + bonus = 5 + + fun _construct(this, s) + // child's constructor runs after parent's fields were merged + this.value = this.value + this.bonus + + fun describe(this) + return "Child(value=" + to_string(this.value) + ", bonus=" + to_string(this.bonus) + ")" + +p = Parent(10) +print(p.describe()) // expect: Parent(value=10) + +c = Child(10) +print(c.describe()) // expect: Child(value=15, bonus=5) +print(c.hello()) + +print("=== done ===") + +/* Expected output: +=== inheritance demo === +Parent(value=10) +Child(value=15, bonus=5) +Hello from Parent() +=== done === +*/ diff --git a/examples/namespaced_mod.fun b/examples/namespaced_mod.fun old mode 100644 new mode 100755 index 3918737..0752e01 --- a/examples/namespaced_mod.fun +++ b/examples/namespaced_mod.fun @@ -3,7 +3,7 @@ // never be executed, but it shows that it is not wrong. /* -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://hanez.org/project/fun/ * * Copyright 2025 Johannes Findeisen diff --git a/examples/threads_demo.fun b/examples/threads_demo.fun old mode 100644 new mode 100755 diff --git a/src/parser.c b/src/parser.c index 132a71d..c3235ec 100644 --- a/src/parser.c +++ b/src/parser.c @@ -40,7 +40,7 @@ * } * * @author Johannes Findeisen - * @date 2025-10-16 + * @date 2025-09-16 */ #include "parser.h" @@ -2422,6 +2422,9 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, } int cgi = sym_index(cname); + /* optional extends Parent */ + char *parent_name = NULL; + /* Optional typed parameter list: class Name(type ident, ...) */ char *param_names[64]; int param_kind[64]; int pcount = 0; memset(param_names, 0, sizeof(param_names)); @@ -2484,6 +2487,19 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, } } + /* optional 'extends Parent' after parameter list */ + skip_spaces(src, len, pos); + if (starts_with(src, len, *pos, "extends")) { + *pos += 7; /* consume 'extends' */ + skip_spaces(src, len, pos); + if (!read_identifier_into(src, len, pos, &parent_name)) { + parser_fail(*pos, "Expected parent class name after 'extends'"); + for (int i = 0; i < pcount; ++i) free(param_names[i]); + free(cname); + return; + } + } + /* end of class header line */ skip_to_eol(src, len, pos); @@ -2495,6 +2511,9 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, LocalEnv *prev_env = g_locals; g_locals = &ctor_env; + /* track if _construct is defined in this class */ + int ctor_present = 0; + /* Register parameter locals first so args land at 0..pcount-1 */ for (int i = 0; i < pcount; ++i) { local_add(param_names[i]); @@ -2593,6 +2612,78 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, } bytecode_add_instruction(ctor_bc, OP_INDEX_SET, 0); + /* Inheritance: if extends Parent, create Parent(header args...) and merge its keys into this */ + if (parent_name) { + /* parent_inst = Parent(args...) */ + int parent_gi = sym_index(parent_name); + bytecode_add_instruction(ctor_bc, OP_LOAD_GLOBAL, parent_gi); + for (int i = 0; i < pcount; ++i) { + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, i); + } + bytecode_add_instruction(ctor_bc, OP_CALL, pcount); + int l_parent = local_add("__parent_inst"); + bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_parent); + + /* keys = keys(parent_inst) */ + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_parent); + bytecode_add_instruction(ctor_bc, OP_KEYS, 0); + int l_keys = local_add("__parent_keys"); + bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_keys); + + /* i = 0 */ + int c0_inh = bytecode_add_constant(ctor_bc, make_int(0)); + bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, c0_inh); + int l_i = local_add("__inh_i"); + bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_i); + + /* loop: while (i < len(keys)) */ + int loop_start = ctor_bc->instr_count; + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_i); + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_keys); + bytecode_add_instruction(ctor_bc, OP_LEN, 0); + bytecode_add_instruction(ctor_bc, OP_LT, 0); + int jmp_false = bytecode_add_instruction(ctor_bc, OP_JUMP_IF_FALSE, 0); + + /* key = keys[i] */ + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_keys); + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_i); + bytecode_add_instruction(ctor_bc, OP_INDEX_GET, 0); + int l_k = local_add("__inh_k"); + bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_k); + + /* if this has key -> skip set */ + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this); + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_k); + bytecode_add_instruction(ctor_bc, OP_HAS_KEY, 0); + int j_skip_set = bytecode_add_instruction(ctor_bc, OP_JUMP_IF_FALSE, 0); + /* has key -> nothing to do, jump over set sequence */ + int j_after_maybe_set = bytecode_add_instruction(ctor_bc, OP_JUMP, 0); + + /* not has key: set this[key] = parent_inst[key] */ + bytecode_set_operand(ctor_bc, j_skip_set, ctor_bc->instr_count); + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this); + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_k); + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_parent); + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_k); + bytecode_add_instruction(ctor_bc, OP_INDEX_GET, 0); + bytecode_add_instruction(ctor_bc, OP_INDEX_SET, 0); + + /* continue after maybe-set */ + bytecode_set_operand(ctor_bc, j_after_maybe_set, ctor_bc->instr_count); + + /* i++ */ + int c1_inh = bytecode_add_constant(ctor_bc, make_int(1)); + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_i); + bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, c1_inh); + bytecode_add_instruction(ctor_bc, OP_ADD, 0); + bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_i); + + /* back to loop */ + bytecode_add_instruction(ctor_bc, OP_JUMP, loop_start); + /* end loop */ + bytecode_set_operand(ctor_bc, jmp_false, ctor_bc->instr_count); + } + /* Parse class body at increased indent */ int body_indent = 0; size_t look_body = *pos; @@ -2628,6 +2719,8 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, free(cname); return; } + int is_ctor_method = (strcmp(mname, "_construct") == 0); + skip_spaces(src, len, pos); if (!consume_char(src, len, pos, '(')) { parser_fail(*pos, "Expected '(' after method name"); @@ -2661,7 +2754,11 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, } 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'"); + if (is_ctor_method) { + parser_fail(*pos, "Constructor '_construct' must declare 'this' as its first parameter"); + } else { + parser_fail(*pos, "First parameter of a method must be 'this'"); + } free(pname); free(mname); g_locals = saved; @@ -2682,7 +2779,11 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, } } else { /* no params: enforce (this) */ - parser_fail(*pos, "Method must declare at least 'this' parameter"); + if (is_ctor_method) { + parser_fail(*pos, "Constructor '_construct' must declare 'this' as its first parameter"); + } else { + parser_fail(*pos, "Method must declare at least 'this' parameter"); + } free(mname); g_locals = saved; g_locals = prev_env; @@ -2723,6 +2824,11 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, mci); bytecode_add_instruction(ctor_bc, OP_INDEX_SET, 0); + /* mark constructor presence if name matches */ + if (strcmp(mname, "_construct") == 0) { + ctor_present = 1; + } + free(mname); continue; } @@ -2775,6 +2881,28 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, bytecode_add_instruction(ctor_bc, OP_INDEX_SET, 0); } + /* If a constructor exists, invoke: this._construct(this, params...) and drop its return */ + if (ctor_present) { + /* fetch method: duplicate 'this' so we keep it for the call */ + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this); + bytecode_add_instruction(ctor_bc, OP_DUP, 0); /* -> this, this */ + { + int kci_ctor = bytecode_add_constant(ctor_bc, make_string("_construct")); + bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, kci_ctor); + } + bytecode_add_instruction(ctor_bc, OP_INDEX_GET, 0); /* -> this, func */ + bytecode_add_instruction(ctor_bc, OP_SWAP, 0); /* -> func, this */ + + /* push all header parameters as additional args in order */ + for (int i = 0; i < pcount; ++i) { + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, i); + } + /* call with implicit 'this' (+1) plus pcount params */ + bytecode_add_instruction(ctor_bc, OP_CALL, pcount + 1); + /* discard any return value from constructor */ + bytecode_add_instruction(ctor_bc, OP_POP, 0); + } + /* return instance */ bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this); bytecode_add_instruction(ctor_bc, OP_RETURN, 0); @@ -2790,6 +2918,7 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, G.is_class[cgi] = 1; for (int i = 0; i < pcount; ++i) free(param_names[i]); + if (parent_name) free(parent_name); free(cname); continue; }