1
0
Fork 0
forked from fun/fun

Classes are a real type class now and some fixes. (0.10.0)

This commit is contained in:
Johannes Findeisen 2025-09-28 23:56:45 +02:00
commit 5d3cfd9f65
12 changed files with 203 additions and 41 deletions

View file

@ -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)

0
examples/byte_for_demo.fun Normal file → Executable file
View file

0
examples/classes_demo.fun Normal file → Executable file
View file

0
examples/include_lib.fun Normal file → Executable file
View file

0
examples/include_local.fun Normal file → Executable file
View file

0
examples/os_env.fun Normal file → Executable file
View file

0
examples/type_safety.fun Normal file → Executable file
View file

0
examples/type_safety_fails.fun Normal file → Executable file
View file

79
examples/types_overview.fun Executable file
View file

@ -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 <you@hanez.org>
* 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 ===")

View file

@ -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 <ident> (= 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 <ident> (= 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;
}
}

View file

@ -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;
}

View file

@ -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);