diff --git a/examples/types_integers.fun b/examples/types_integers.fun new file mode 100755 index 0000000..0f8783b --- /dev/null +++ b/examples/types_integers.fun @@ -0,0 +1,75 @@ +#!/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 + */ + +// Integer (number) and boolean usage examples + +// Basic typed integers +number a = 42 +number b = -7 +number zero = 0 + +print("a = " + to_string(a) + " :: " + typeof(a)) // "Number" +print("b = " + to_string(b) + " :: " + typeof(b)) // "Number" +print("zero = " + to_string(zero) + " :: " + typeof(zero)) + +// Arithmetic with integers +number sum = a + b +number diff = a - b +number prod = a * 3 +number quot = a / 2 +number rem = a % 5 + +print("sum (a + b) = " + to_string(sum)) +print("diff (a - b) = " + to_string(diff)) +print("prod (a * 3) = " + to_string(prod)) +print("quot (a / 2) = " + to_string(quot)) +print("rem (a % 5) = " + to_string(rem)) + +// Comparisons (results are numbers 1/0 in Fun) +print("a > b = " + to_string(a > b)) +print("a >= b = " + to_string(a >= b)) +print("a < b = " + to_string(a < b)) +print("a <= b = " + to_string(a <= b)) +print("a == b = " + to_string(a == b)) +print("a != b = " + to_string(a != b)) + +// Booleans are numeric 1/0; typeof returns "Number" +boolean t = true +boolean f = false +print("t = " + to_string(t) + " :: " + typeof(t)) +print("f = " + to_string(f) + " :: " + typeof(f)) + +// Short-circuit logic with numeric truthiness +print("t && (a > 0) = " + to_string(t && (a > 0))) +print("f || (a < 0) = " + to_string(f || (a < 0))) + +// Using integers in a for-range loop: sum 1..5 +fun sum_range(lo, hi) + number acc = 0 + for i in range(lo, hi + 1) + acc = acc + i + return acc + +print("sum 1..5 = " + to_string(sum_range(1, 5))) + +// Arrays of integers +nums = [1, 2, 3, 4, 5] +print("nums length = " + to_string(len(nums))) +print("nums[2] = " + to_string(nums[2])) + +// Map with numeric fields +pt = { "x": 10, "y": -3 } +print("pt['x'] = " + to_string(pt["x"]) + " :: " + typeof(pt["x"])) +print("pt['y'] = " + to_string(pt["y"]) + " :: " + typeof(pt["y"])) + +// Conversions +print("to_string(123) => " + to_string(123)) +print("to_number(\"456\") => " + to_string(to_number("456"))) diff --git a/examples/uint_types.fun b/examples/uint_types.fun new file mode 100755 index 0000000..0becafc --- /dev/null +++ b/examples/uint_types.fun @@ -0,0 +1,42 @@ +#!/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 + */ + +// Unsigned integer type examples with wrapping behavior + +uint8 u8 = 255 +uint16 u16 = 65535 +uint32 u32 = 4294967295 +uint64 u64 = 18446744073709551615 // will wrap to 64-bit + +print("u8 = " + to_string(u8) + " :: " + typeof(u8)) +print("u16 = " + to_string(u16) + " :: " + typeof(u16)) +print("u32 = " + to_string(u32) + " :: " + typeof(u32)) +print("u64 = " + to_string(u64) + " :: " + typeof(u64)) + +// Demonstrate wrapping on assignment +u8 = 256 // wraps to 0 +u16 = 65536 // wraps to 0 +u32 = 4294967296 // wraps to 0 +u64 = 18446744073709551616 // wraps to 0 + +print("wrap u8 -> " + to_string(u8)) +print("wrap u16 -> " + to_string(u16)) +print("wrap u32 -> " + to_string(u32)) +print("wrap u64 -> " + to_string(u64)) + +// Mixed arithmetic then wrapping +u8 = 250 +u8 = u8 + 10 // 260 -> wrap 260 % 256 = 4 +print("u8 after +10 wrap -> " + to_string(u8)) + +// 'number' maps to 64-bit unsigned behavior +number n = -1 // will clamp/wrap to 64-bit (implementation-defined display) +print("number n = " + to_string(n) + " :: " + typeof(n)) diff --git a/src/bytecode.h b/src/bytecode.h index df2e44a..de57bed 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -75,6 +75,7 @@ typedef enum { OP_TO_NUMBER, // pops any; pushes int (parse strings) OP_TO_STRING, // pops any; pushes string OP_TYPEOF, // pops any; pushes string name of type + OP_UCLAMP, // pops number; pushes number masked to N bits (operand = bits) // string ops OP_SPLIT, // pops sep, string; pushes array of strings diff --git a/src/parser.c b/src/parser.c index d1cd362..2f6b841 100644 --- a/src/parser.c +++ b/src/parser.c @@ -89,8 +89,9 @@ static void calc_line_col(const char *src, size_t len, size_t pos, int *out_line /* very small global symbol table for LOAD_GLOBAL/STORE_GLOBAL */ static struct { char *names[MAX_GLOBALS]; + int types[MAX_GLOBALS]; /* 0=untyped/number default; else bit width: 8/16/32/64 */ int count; -} G = { {0}, 0 }; +} G = { {0}, {0}, 0 }; static int sym_index(const char *name) { for (int i = 0; i < G.count; ++i) { @@ -101,12 +102,14 @@ static int sym_index(const char *name) { return 0; } G.names[G.count] = strdup(name); + G.types[G.count] = 0; /* default: untyped (treat as 64-bit number) */ return G.count++; } /* ---- locals environment for functions ---- */ typedef struct { char *names[MAX_FRAME_LOCALS]; + int types[MAX_FRAME_LOCALS]; /* 0=untyped; else bit width: 8/16/32/64 */ int count; } LocalEnv; @@ -1451,12 +1454,18 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si return; } - /* typed declarations: number|string|boolean|nil (= expr)? */ - if (strcmp(name, "number") == 0 || strcmp(name, "string") == 0 || strcmp(name, "boolean") == 0 || strcmp(name, "nil") == 0) { + /* typed declarations: number|string|boolean|nil|uint8|uint16|uint32|uint64 (= expr)? */ + if (strcmp(name, "number") == 0 || strcmp(name, "string") == 0 || strcmp(name, "boolean") == 0 || strcmp(name, "nil") == 0 + || strcmp(name, "uint8") == 0 || strcmp(name, "uint16") == 0 || strcmp(name, "uint32") == 0 || strcmp(name, "uint64") == 0) { int is_number = (strcmp(name, "number") == 0); int is_string = (strcmp(name, "string") == 0); int is_boolean = (strcmp(name, "boolean") == 0); int is_nil = (strcmp(name, "nil") == 0); + int is_u8 = (strcmp(name, "uint8") == 0); + int is_u16 = (strcmp(name, "uint16") == 0); + int is_u32 = (strcmp(name, "uint32") == 0); + int is_u64 = (strcmp(name, "uint64") == 0) || is_number; /* number maps to uint64 */ + int decl_bits = is_u8 ? 8 : is_u16 ? 16 : is_u32 ? 32 : is_u64 ? 64 : 0; /* 0 for non-integer types */ free(name); /* read variable name */ @@ -1474,8 +1483,14 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si int existing = local_find(varname); if (existing >= 0) lidx = existing; else lidx = local_add(varname); + if (lidx >= 0) { + g_locals->types[lidx] = decl_bits; /* record declared bits (0 for non-integer types) */ + } } else { gi = sym_index(varname); + if (gi >= 0) { + G.types[gi] = decl_bits; + } } free(varname); @@ -1486,6 +1501,10 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si parser_fail(local_pos, "Expected initializer expression after '='"); return; } + /* clamp if integer type was declared */ + if (decl_bits > 0) { + bytecode_add_instruction(bc, OP_UCLAMP, decl_bits); + } if (lidx >= 0) { bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); } else { @@ -1494,15 +1513,19 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si } else { /* default initialize if no '=' given */ int ci = -1; - if (is_number || is_boolean) { - ci = bytecode_add_constant(bc, make_int(0)); - } else if (is_string) { + if (is_string) { ci = bytecode_add_constant(bc, make_string("")); } else if (is_nil) { 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)); } if (ci >= 0) { bytecode_add_instruction(bc, OP_LOAD_CONST, ci); + if (decl_bits > 0) { + bytecode_add_instruction(bc, OP_UCLAMP, decl_bits); + } if (lidx >= 0) { bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); } else { @@ -1669,6 +1692,16 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si if (local_pos < len && src[local_pos] == '=') { local_pos++; /* '=' */ if (emit_expression(bc, src, len, &local_pos)) { + /* clamp to declared integer width if present */ + int decl_bits = 0; + if (lidx >= 0 && g_locals) { + decl_bits = g_locals->types[lidx]; + } else if (gi >= 0) { + decl_bits = G.types[gi]; + } + if (decl_bits > 0) { + bytecode_add_instruction(bc, OP_UCLAMP, decl_bits); + } if (lidx >= 0) { bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); } else { diff --git a/src/vm.c b/src/vm.c index 1d9b8f2..62f86fb 100644 --- a/src/vm.c +++ b/src/vm.c @@ -301,6 +301,7 @@ void vm_run(VM *vm, Bytecode *entry) { #include "vm/to_number.c" #include "vm/to_string.c" #include "vm/typeof.c" + #include "vm/uclamp.c" default: if (!opcode_is_valid(inst.op)) { diff --git a/src/vm/uclamp.c b/src/vm/uclamp.c new file mode 100644 index 0000000..7294033 --- /dev/null +++ b/src/vm/uclamp.c @@ -0,0 +1,24 @@ +case OP_UCLAMP: { + /* Clamp/wrap the integer on top of the stack to 'bits' width (unsigned) */ + Value v = pop_value(vm); + int bits = inst.operand; + uint64_t u = 0; + + if (v.type == VAL_INT) { + u = (uint64_t) v.i; + } else { + /* Non-integers clamp to 0 */ + u = 0; + } + + if (bits > 0 && bits < 64) { + uint64_t mask = (1ULL << bits) - 1ULL; + u &= mask; + } else { + /* bits >= 64 -> no-op; bits <= 0 -> no-op */ + } + + push_value(vm, make_int((int64_t)u)); + free_value(v); + break; +}