From 3f5fabae926981243050cf1750e56d1101fbb625 Mon Sep 17 00:00:00 2001 From: hanez Date: Tue, 16 Sep 2025 12:38:47 +0200 Subject: [PATCH] Signed Integers. --- examples/signed_ints.fun | 29 +++++++++++++++++++++++++++++ src/bytecode.h | 1 + src/parser.c | 37 ++++++++++++++++++++++++++----------- src/vm.c | 1 + src/vm/sclamp.c | 30 ++++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 11 deletions(-) create mode 100644 examples/signed_ints.fun create mode 100644 src/vm/sclamp.c diff --git a/examples/signed_ints.fun b/examples/signed_ints.fun new file mode 100644 index 0000000..ed1b524 --- /dev/null +++ b/examples/signed_ints.fun @@ -0,0 +1,29 @@ +#!/usr/bin/env fun + +// Signed integer type examples with two's complement wrapping + +int8 s8 = -1 // should be -1 within int8 range +int16 s16 = -32768 // min int16 +int32 s32 = -2147483648 +int64 s64 = -9223372036854775808 + +print("s8 = " + to_string(s8) + " :: " + typeof(s8)) +print("s16 = " + to_string(s16) + " :: " + typeof(s16)) +print("s32 = " + to_string(s32) + " :: " + typeof(s32)) +print("s64 = " + to_string(s64) + " :: " + typeof(s64)) + +// Demonstrate wrapping on assignment beyond range +s8 = 130 // 130 -> wraps to -126 in int8 +s16 = 70000 // wraps into int16 range +s32 = 4294967296 // wraps into int32 range +s64 = 18446744073709551616 // wraps into int64 range + +print("wrap s8 -> " + to_string(s8)) +print("wrap s16 -> " + to_string(s16)) +print("wrap s32 -> " + to_string(s32)) +print("wrap s64 -> " + to_string(s64)) + +// Arithmetic with signed values then wrapping +s8 = -120 +s8 = s8 - 20 // -140 -> wraps into int8 range +print("s8 after -20 wrap -> " + to_string(s8)) diff --git a/src/bytecode.h b/src/bytecode.h index de57bed..9033ada 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -76,6 +76,7 @@ typedef enum { 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) + OP_SCLAMP, // pops number; pushes number clamped to signed N-bit range (operand = bits) // string ops OP_SPLIT, // pops sep, string; pushes array of strings diff --git a/src/parser.c b/src/parser.c index 2f6b841..60c8e56 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1454,9 +1454,13 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si return; } - /* typed declarations: number|string|boolean|nil|uint8|uint16|uint32|uint64 (= expr)? */ + /* typed declarations: + number|string|boolean|nil|uint8|uint16|uint32|uint64|int8|int16|int32|int64 (= expr)? + Note: 'number' maps to unsigned 64-bit here. + */ 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) { + || 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) { int is_number = (strcmp(name, "number") == 0); int is_string = (strcmp(name, "string") == 0); int is_boolean = (strcmp(name, "boolean") == 0); @@ -1465,7 +1469,15 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si 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 */ + int is_s8 = (strcmp(name, "int8") == 0); + int is_s16 = (strcmp(name, "int16") == 0); + int is_s32 = (strcmp(name, "int32") == 0); + int is_s64 = (strcmp(name, "int64") == 0); + int decl_bits = is_u8 ? 8 : is_u16 ? 16 : is_u32 ? 32 : is_u64 ? 64 + : is_s8 ? 8 : is_s16 ? 16 : is_s32 ? 32 : is_s64 ? 64 : 0; + int decl_signed = (is_s8 || is_s16 || is_s32 || is_s64) ? 1 : 0; + /* store decl bits with sign encoded: negative means signed */ + if (decl_signed) decl_bits = -decl_bits; free(name); /* read variable name */ @@ -1484,7 +1496,7 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si 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) */ + g_locals->types[lidx] = decl_bits; /* negative = signed, positive = unsigned width, 0 = non-integer */ } } else { gi = sym_index(varname); @@ -1502,8 +1514,9 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si return; } /* clamp if integer type was declared */ - if (decl_bits > 0) { - bytecode_add_instruction(bc, OP_UCLAMP, decl_bits); + int abs_bits = decl_bits < 0 ? -decl_bits : decl_bits; + if (abs_bits > 0) { + bytecode_add_instruction(bc, (decl_bits < 0) ? OP_SCLAMP : OP_UCLAMP, abs_bits); } if (lidx >= 0) { bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); @@ -1517,14 +1530,15 @@ 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_number || is_boolean || decl_bits > 0) { + } 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); + int abs_bits2 = decl_bits < 0 ? -decl_bits : decl_bits; + if (abs_bits2 > 0) { + bytecode_add_instruction(bc, (decl_bits < 0) ? OP_SCLAMP : OP_UCLAMP, abs_bits2); } if (lidx >= 0) { bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); @@ -1699,8 +1713,9 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si } else if (gi >= 0) { decl_bits = G.types[gi]; } - if (decl_bits > 0) { - bytecode_add_instruction(bc, OP_UCLAMP, decl_bits); + int abs_bits = decl_bits < 0 ? -decl_bits : decl_bits; + if (abs_bits > 0) { + bytecode_add_instruction(bc, (decl_bits < 0) ? OP_SCLAMP : OP_UCLAMP, abs_bits); } if (lidx >= 0) { bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); diff --git a/src/vm.c b/src/vm.c index 62f86fb..d2d63a3 100644 --- a/src/vm.c +++ b/src/vm.c @@ -302,6 +302,7 @@ void vm_run(VM *vm, Bytecode *entry) { #include "vm/to_string.c" #include "vm/typeof.c" #include "vm/uclamp.c" + #include "vm/sclamp.c" default: if (!opcode_is_valid(inst.op)) { diff --git a/src/vm/sclamp.c b/src/vm/sclamp.c new file mode 100644 index 0000000..e085f5c --- /dev/null +++ b/src/vm/sclamp.c @@ -0,0 +1,30 @@ +case OP_SCLAMP: { + /* Clamp/wrap 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; + } + + if (bits <= 0) { + /* treat as no-op for invalid widths */ + } else if (bits >= 64) { + /* 64-bit signed: nothing to mask, keep as is */ + } 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); + } + } + + push_value(vm, make_int((int64_t)u)); + free_value(v); + break; +}