From 5d3cfd9f65eb11e7306788ec6fea8d4412cb4d2e Mon Sep 17 00:00:00 2001 From: hanez Date: Sun, 28 Sep 2025 23:56:45 +0200 Subject: [PATCH] Classes are a real type class now and some fixes. (0.10.0) --- CMakeLists.txt | 2 +- examples/byte_for_demo.fun | 0 examples/classes_demo.fun | 0 examples/include_lib.fun | 0 examples/include_local.fun | 0 examples/os_env.fun | 0 examples/type_safety.fun | 0 examples/type_safety_fails.fun | 0 examples/types_overview.fun | 79 +++++++++++++++++++++++++ src/parser.c | 105 ++++++++++++++++++++++++++++++--- src/vm/sclamp.c | 33 +++++------ src/vm/uclamp.c | 25 ++++---- 12 files changed, 203 insertions(+), 41 deletions(-) mode change 100644 => 100755 examples/byte_for_demo.fun mode change 100644 => 100755 examples/classes_demo.fun mode change 100644 => 100755 examples/include_lib.fun mode change 100644 => 100755 examples/include_local.fun mode change 100644 => 100755 examples/os_env.fun mode change 100644 => 100755 examples/type_safety.fun mode change 100644 => 100755 examples/type_safety_fails.fun create mode 100755 examples/types_overview.fun diff --git a/CMakeLists.txt b/CMakeLists.txt index ee525fe..5837cbd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(fun VERSION 0.9.3 LANGUAGES C) +project(fun VERSION 0.10.0 LANGUAGES C) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/examples/byte_for_demo.fun b/examples/byte_for_demo.fun old mode 100644 new mode 100755 diff --git a/examples/classes_demo.fun b/examples/classes_demo.fun old mode 100644 new mode 100755 diff --git a/examples/include_lib.fun b/examples/include_lib.fun old mode 100644 new mode 100755 diff --git a/examples/include_local.fun b/examples/include_local.fun old mode 100644 new mode 100755 diff --git a/examples/os_env.fun b/examples/os_env.fun old mode 100644 new mode 100755 diff --git a/examples/type_safety.fun b/examples/type_safety.fun old mode 100644 new mode 100755 diff --git a/examples/type_safety_fails.fun b/examples/type_safety_fails.fun old mode 100644 new mode 100755 diff --git a/examples/types_overview.fun b/examples/types_overview.fun new file mode 100755 index 0000000..adb4384 --- /dev/null +++ b/examples/types_overview.fun @@ -0,0 +1,79 @@ +#!/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 ISC license. + * https://opensource.org/license/isc-license-txt + */ + +print("=== Dynamic (untyped) ===") +x = 123 +print(typeof(x)) // -> "Number" +x = "ok" +print(typeof(x)) // -> "String" + +print("") +print("=== Numbers and integer subtypes ===") +number n = 42 +print(typeof(n)) // -> "Number" +print(n) // -> 42 + +int8 s8 = 130 // clamps to 127 +print(s8) // -> 127 +uint16 u16 = 70000 // clamps to 65535 +print(u16) // -> 65535 +byte b = 0xFF // 255 +print(b) // -> 255 +boolean flag = 2 // clamps to 1 +print(flag) // -> 1 + +print("") +print("=== Strings ===") +string s = "hello" +print(typeof(s)) // -> "String" +print(s) // -> "hello" + +print("") +print("=== Nil ===") +nil nn = nil +print(typeof(nn)) // -> "Nil" +print(nn) // -> nil + +print("") +print("=== Arrays ===") +arr = [1, 2, 3] +print(typeof(arr)) // -> "Array" +print(len(arr)) // -> 3 +print(arr[1]) // -> 2 +print(arr[0:2]) // -> [1, 2] + +print("") +print("=== Maps ===") +m = { "k": "v", "n": 1 } +print(typeof(m)) // -> "Map" +print(m["k"]) // -> "v" + +print("") +print("=== Functions ===") +fun add(a, b) + return a + b +print(typeof(add)) // -> "Function" +print(add(5, 7)) // -> 12 + +print("") +print("=== Classes and Class-typed variables ===") +class Person(number age, string name) + // methods must declare 'this' as first parameter + fun toString(this) + // typeof(instance) will call this and return it + return "Class" + +Class p = Person(33, "Jo") +print(typeof(p)) // -> "Class" +// p = "nope" // Uncomment to see TypeError: expected Class + +print("") +print("=== Done ===") diff --git a/src/parser.c b/src/parser.c index 2f9b0cc..36e5c75 100644 --- a/src/parser.c +++ b/src/parser.c @@ -65,10 +65,12 @@ static int g_temp_counter = 0; /* Declared type metadata encoding in types[]: 0 = dynamic/untyped; positive/negative 8/16/32/64 = integers (negative means signed); - TYPE_META_STRING/BOOLEAN/NIL mark non-integer enforced types. */ + TYPE_META_STRING/BOOLEAN/NIL mark non-integer enforced types; + TYPE_META_CLASS marks class instances (Map with "__class"). */ #define TYPE_META_STRING 10001 #define TYPE_META_BOOLEAN 10002 #define TYPE_META_NIL 10003 +#define TYPE_META_CLASS 10004 static void parser_fail(size_t pos, const char *fmt, ...) { g_has_error = 1; @@ -489,16 +491,28 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) } } - if (decl_bits != 0) { + /* Map declared metadata to human-readable typeof */ + if (decl_bits == TYPE_META_STRING) { + int ci = bytecode_add_constant(bc, make_string("String")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci); + } else if (decl_bits == TYPE_META_NIL) { + int ci = bytecode_add_constant(bc, make_string("Nil")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci); + } else if (decl_bits == TYPE_META_CLASS || is_class_name) { + int ci = bytecode_add_constant(bc, make_string("Class")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci); + } else if (decl_bits == TYPE_META_BOOLEAN) { + /* Booleans are numeric 0/1 in Fun */ + int ci = bytecode_add_constant(bc, make_string("Number")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci); + } else if (decl_bits == 8 || decl_bits == 16 || decl_bits == 32 || decl_bits == 64 + || decl_bits == -8 || decl_bits == -16 || decl_bits == -32 || decl_bits == -64) { int bits = decl_bits < 0 ? -decl_bits : decl_bits; int is_signed = (decl_bits < 0); char tbuf[16]; snprintf(tbuf, sizeof(tbuf), "%s%d", is_signed ? "Sint" : "Uint", bits); int ci = bytecode_add_constant(bc, make_string(tbuf)); bytecode_add_instruction(bc, OP_LOAD_CONST, ci); - } else if (is_class_name) { - int ci = bytecode_add_constant(bc, make_string("Class")); - bytecode_add_instruction(bc, OP_LOAD_CONST, ci); } else { /* Fallback: load the variable, but if it's a class instance (Map with "__class"), return "Class" */ if (lidx2 >= 0) { @@ -1752,10 +1766,11 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si } /* typed declarations: - number|string|boolean|nil|byte|uint8|uint16|uint32|uint64|int8|int16|int32|int64 (= expr)? - Note: 'number' maps to signed 64-bit here. 'byte' is an alias of unsigned 8-bit. + number|string|boolean|nil|Class|byte|uint8|uint16|uint32|uint64|int8|int16|int32|int64 (= expr)? + Note: 'number' maps to signed 64-bit here. 'byte' is an alias of unsigned 8-bit. 'Class' restricts to class instances. */ if (strcmp(name, "number") == 0 || strcmp(name, "string") == 0 || strcmp(name, "boolean") == 0 || strcmp(name, "nil") == 0 + || strcmp(name, "Class") == 0 || strcmp(name, "byte") == 0 || strcmp(name, "uint8") == 0 || strcmp(name, "uint16") == 0 || strcmp(name, "uint32") == 0 || strcmp(name, "uint64") == 0 || strcmp(name, "int8") == 0 || strcmp(name, "int16") == 0 || strcmp(name, "int32") == 0 || strcmp(name, "int64") == 0) { @@ -1763,6 +1778,7 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si int is_string = (strcmp(name, "string") == 0); int is_boolean = (strcmp(name, "boolean") == 0); int is_nil = (strcmp(name, "nil") == 0); + int is_class_tkn = (strcmp(name, "Class") == 0); int is_byte = (strcmp(name, "byte") == 0); int is_u8 = (strcmp(name, "uint8") == 0) || is_byte; int is_u16 = (strcmp(name, "uint16") == 0); @@ -1778,7 +1794,7 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si /* store decl bits with sign encoded: negative means signed (number is signed 64-bit) */ if (decl_signed) decl_bits = -decl_bits; - /* declared type metadata: integers use decl_bits; string/boolean/nil use special markers */ + /* declared type metadata: integers use decl_bits; string/boolean/nil/Class use special markers */ int decl_meta = decl_bits; if (is_string) { decl_meta = TYPE_META_STRING; @@ -1786,6 +1802,8 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si decl_meta = TYPE_META_BOOLEAN; } else if (is_nil) { decl_meta = TYPE_META_NIL; + } else if (is_class_tkn) { + decl_meta = TYPE_META_CLASS; } free(name); @@ -1843,6 +1861,39 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si bytecode_add_instruction(bc, OP_HALT, 0); } bytecode_set_operand(bc, j_skip_err, bc->instr_count); + } else if (decl_meta == TYPE_META_CLASS) { + /* expect Class instance: Map with "__class" key */ + /* Check typeof(v) == "Map" */ + bytecode_add_instruction(bc, OP_DUP, 0); + bytecode_add_instruction(bc, OP_TYPEOF, 0); + { + int ciMap = bytecode_add_constant(bc, make_string("Map")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciMap); + } + bytecode_add_instruction(bc, OP_EQ, 0); + int j_err1 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + /* Check v has "__class" */ + bytecode_add_instruction(bc, OP_DUP, 0); + { + int kci = bytecode_add_constant(bc, make_string("__class")); + bytecode_add_instruction(bc, OP_LOAD_CONST, kci); + } + bytecode_add_instruction(bc, OP_HAS_KEY, 0); + int j_err2 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + /* Success path jumps over error block */ + int j_ok = bytecode_add_instruction(bc, OP_JUMP, 0); + /* Error block */ + int err_lbl = bc->instr_count; + bytecode_set_operand(bc, j_err1, err_lbl); + bytecode_set_operand(bc, j_err2, err_lbl); + { + int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Class")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_HALT, 0); + } + /* Continue after checks */ + bytecode_set_operand(bc, j_ok, bc->instr_count); } else if (decl_meta == TYPE_META_BOOLEAN) { /* expect Number then clamp to 1 bit (unsigned) */ bytecode_add_instruction(bc, OP_DUP, 0); @@ -1916,6 +1967,9 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si ci = bytecode_add_constant(bc, make_string("")); } else if (is_nil) { ci = bytecode_add_constant(bc, make_nil()); + } else if (is_class_tkn) { + /* Class-typed variable defaults to Nil until assigned an instance */ + ci = bytecode_add_constant(bc, make_nil()); } else if (is_number || is_boolean || (decl_bits != 0)) { /* integers/booleans default to 0 */ ci = bytecode_add_constant(bc, make_int(0)); @@ -2117,6 +2171,39 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si bytecode_add_instruction(bc, OP_HALT, 0); } bytecode_set_operand(bc, j_skip_err, bc->instr_count); + } else if (meta == TYPE_META_CLASS) { + /* expect Class instance: Map with "__class" key */ + /* Check typeof(v) == "Map" */ + bytecode_add_instruction(bc, OP_DUP, 0); + bytecode_add_instruction(bc, OP_TYPEOF, 0); + { + int ciMap = bytecode_add_constant(bc, make_string("Map")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciMap); + } + bytecode_add_instruction(bc, OP_EQ, 0); + int j_err1 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + /* Check v has "__class" */ + bytecode_add_instruction(bc, OP_DUP, 0); + { + int kci = bytecode_add_constant(bc, make_string("__class")); + bytecode_add_instruction(bc, OP_LOAD_CONST, kci); + } + bytecode_add_instruction(bc, OP_HAS_KEY, 0); + int j_err2 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + /* Success path jumps over error block */ + int j_ok = bytecode_add_instruction(bc, OP_JUMP, 0); + /* Error block */ + int err_lbl = bc->instr_count; + bytecode_set_operand(bc, j_err1, err_lbl); + bytecode_set_operand(bc, j_err2, err_lbl); + { + int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Class")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_HALT, 0); + } + /* Continue after checks */ + bytecode_set_operand(bc, j_ok, bc->instr_count); } else if (meta == TYPE_META_BOOLEAN) { /* expect Number then clamp to 1 bit (unsigned) */ bytecode_add_instruction(bc, OP_DUP, 0); @@ -3274,4 +3361,4 @@ int parser_last_error(char *msgBuf, unsigned long msgCap, int *outLine, int *out if (outLine) *outLine = g_err_line; if (outCol) *outCol = g_err_col; return 1; -} \ No newline at end of file +} diff --git a/src/vm/sclamp.c b/src/vm/sclamp.c index bf49d00..0f6bc30 100644 --- a/src/vm/sclamp.c +++ b/src/vm/sclamp.c @@ -8,32 +8,27 @@ */ case OP_SCLAMP: { - /* Clamp/wrap to signed N-bit range: [-2^(N-1), 2^(N-1)-1] */ + /* Saturating clamp to signed N-bit range: [-2^(N-1) .. 2^(N-1)-1] */ Value v = pop_value(vm); int bits = inst.operand; - uint64_t u = 0; - - if (v.type == VAL_INT) { - u = (uint64_t)v.i; - } else { - u = 0; - } + int64_t vi = (v.type == VAL_INT) ? v.i : 0; + int64_t smin, smax; if (bits <= 0) { - /* treat as no-op for invalid widths */ - } else if (bits >= 64) { - /* 64-bit signed: nothing to mask, keep as is */ + smin = 0; smax = 0; + } else if (bits >= 63) { + /* cover full int64_t domain for 63+ bits */ + smin = INT64_MIN; + smax = INT64_MAX; } else { - uint64_t mask = (1ULL << bits) - 1ULL; - u &= mask; - uint64_t sign_bit = 1ULL << (bits - 1); - if (u & sign_bit) { - /* negative value in two's complement */ - u -= (1ULL << bits); - } + smin = -(1LL << (bits - 1)); + smax = (1LL << (bits - 1)) - 1LL; } - push_value(vm, make_int((int64_t)u)); + if (vi < smin) vi = smin; + else if (vi > smax) vi = smax; + + push_value(vm, make_int(vi)); free_value(v); break; } diff --git a/src/vm/uclamp.c b/src/vm/uclamp.c index 858847a..e22bc21 100644 --- a/src/vm/uclamp.c +++ b/src/vm/uclamp.c @@ -8,24 +8,25 @@ */ case OP_UCLAMP: { - /* Clamp/wrap the integer on top of the stack to 'bits' width (unsigned) */ + /* Saturating clamp to unsigned N-bit range: [0 .. 2^N - 1] */ Value v = pop_value(vm); int bits = inst.operand; - uint64_t u = 0; + int64_t vi = (v.type == VAL_INT) ? v.i : 0; - if (v.type == VAL_INT) { - u = (uint64_t) v.i; + uint64_t umax; + if (bits <= 0) { + /* treat as clamp to 0..0 */ + umax = 0; + } else if (bits >= 64) { + umax = UINT64_MAX; } else { - /* Non-integers clamp to 0 */ - u = 0; + umax = (1ULL << bits) - 1ULL; } - if (bits > 0 && bits < 64) { - uint64_t mask = (1ULL << bits) - 1ULL; - u &= mask; - } else { - /* bits >= 64 -> no-op; bits <= 0 -> no-op */ - } + uint64_t u; + if (vi < 0) u = 0; + else if ((uint64_t)vi > umax) u = umax; + else u = (uint64_t)vi; push_value(vm, make_int((int64_t)u)); free_value(v);