diff --git a/CMakeLists.txt b/CMakeLists.txt index 38894bf..9715f56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(fun VERSION 0.23.0 LANGUAGES C) +project(fun VERSION 0.24.0 LANGUAGES C) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/examples/boolean_decl.fun b/examples/boolean_decl.fun index 9d5e8c3..66bff98 100755 --- a/examples/boolean_decl.fun +++ b/examples/boolean_decl.fun @@ -1,5 +1,16 @@ #!/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-04 + */ + boolean b = false print("b => " + to_string(b)) /* Try typeof via built-in function if available */ diff --git a/examples/booleans.fun b/examples/booleans.fun index df53a0d..c66b515 100755 --- a/examples/booleans.fun +++ b/examples/booleans.fun @@ -7,6 +7,8 @@ * Copyright 2025 Johannes Findeisen * Licensed under the terms of the Apache-2.0 license. * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-04 */ // Boolean datatype usage examples (first-class booleans) diff --git a/examples/floats.fun b/examples/floats.fun new file mode 100755 index 0000000..277cd17 --- /dev/null +++ b/examples/floats.fun @@ -0,0 +1,78 @@ +#!/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-04 + */ + +// Float type demonstration Literals: decimal and scientific notation +float a = 1.23 +float b = 4.0 +float c = 1e2 +float d = 5E-1 + +// Arithmetic with promotion +float s1 = a + b // 1.23 + 4.0 = 5.23 +float s2 = c - 50 // 100.0 - 50 -> 50.0 +float p = 2 * d // 2 * 0.5 -> 1.0 +number q = 7 / 2 // int division -> 3 +float r = 7 / 2.0 // float division -> 3.5 + +print("a=" + to_string(a)) +print("a: " + typeof(a)) +print("b=" + to_string(b)) +print(typeof(b)) +print("c=" + to_string(c)) +print(typeof(c)) +print("d=" + to_string(d)) +print(typeof(d)) +print("s1=" + to_string(s1)) +print(typeof(s1)) +print("s2=" + to_string(s2)) +print(typeof(s2)) +print("p=" + to_string(p)) +print(typeof(p)) +print("q=" + to_string(q)) +print(typeof(q)) +print("r=" + to_string(r)) +print(typeof(r)) + +// to_number behavior +string s = "42.0" +number t = to_number(s) // preserves int when exact -> 42 +print("to_number(\"42.0\")=" + to_string(t)) +print(typeof(t)) +float u = to_number("3.14") +print("to_number(\"3.14\")=" + to_string(u)) +print(typeof(u)) + +/* Expected output: +a=1.23 +Float +b=4 +Float +c=100 +Float +d=0.5 +Float +s1=5.2300000000000004 +Float +s2=50 +Float +p=1 +Float +q=3 +Number +r=3.5 +Float +to_number("42.0")=42 +Number +to_number("3.14")=3.1400000000000001 +Float +*/ diff --git a/examples/signed_ints.fun b/examples/signed_ints.fun index 627fa48..dd48d55 100755 --- a/examples/signed_ints.fun +++ b/examples/signed_ints.fun @@ -11,10 +11,10 @@ // Signed integer type examples with two's complement wrapping -sint8 s8 = -1 // should be -1 within sint8 range -sint16 s16 = -32768 // min sint16 -sint32 s32 = -2147483648 -sint64 s64 = -9223372036854775808 +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)) @@ -36,3 +36,15 @@ print("wrap s64 -> " + to_string(s64)) s8 = -120 s8 = s8 - 20 // -140 -> wraps into int8 range print("s8 after -20 wrap -> " + to_string(s8)) + +/* Expected output: +s8 = -1 :: Number +s16 = -32768 :: Number +s32 = -2147483648 :: Number +s64 = -9223372036854775808 :: Number +wrap s8 -> -126 +wrap s16 -> 4464 +wrap s32 -> 0 +wrap s64 -> 0 +s8 after -20 wrap -> 116 +*/ diff --git a/examples/uint_types.fun b/examples/uint_types.fun index 7e69c4f..facfbec 100755 --- a/examples/uint_types.fun +++ b/examples/uint_types.fun @@ -40,3 +40,16 @@ 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)) + +/* Exprected result: +u8 = 255 :: Number +u16 = 65535 :: Number +u32 = 4294967295 :: Number +u64 = -1 :: Number +wrap u8 -> 0 +wrap u16 -> 0 +wrap u32 -> 0 +wrap u64 -> 0 +u8 after +10 wrap -> 4 +number n = -1 :: Number +*/ diff --git a/src/parser.c b/src/parser.c index f5a49a8..cf48c37 100644 --- a/src/parser.c +++ b/src/parser.c @@ -72,6 +72,7 @@ static int g_temp_counter = 0; #define TYPE_META_BOOLEAN 10002 #define TYPE_META_NIL 10003 #define TYPE_META_CLASS 10004 +#define TYPE_META_FLOAT 10005 static void parser_fail(size_t pos, const char *fmt, ...) { g_has_error = 1; @@ -387,9 +388,45 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) return 1; } - /* number */ + /* number (prefer float first to consume cases like 1.23 or 1e2) */ int ok = 0; size_t save = *pos; + /* float literal */ + double fval = parse_float_literal_value(src, len, pos, &ok); + if (ok) { + int ci = bytecode_add_constant(bc, make_float(fval)); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci); + /* postfix indexing or slice (not typical for floats but keep consistency) */ + for (;;) { + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] == '[') { + (*pos)++; + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected start expression"); return 0; } + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] == ':') { + (*pos)++; + skip_spaces(src, len, pos); + size_t savep2 = *pos; + if (!emit_expression(bc, src, len, pos)) { + *pos = savep2; + int ci2 = bytecode_add_constant(bc, make_int(-1)); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci2); + } + if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after slice"); return 0; } + bytecode_add_instruction(bc, OP_SLICE, 0); + continue; + } else { + if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after index"); return 0; } + bytecode_add_instruction(bc, OP_INDEX_GET, 0); + continue; + } + } + break; + } + return 1; + } + *pos = save; + /* integer literal */ int64_t ival = parse_int_literal_value(src, len, pos, &ok); if (ok) { int ci = bytecode_add_constant(bc, make_int(ival)); @@ -426,7 +463,6 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) } return 1; } - *pos = save; /* identifier or keyword */ char *name = NULL; @@ -2131,7 +2167,7 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si 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, "Class") == 0 || strcmp(name, "float") == 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) { @@ -2140,6 +2176,7 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si int is_boolean = (strcmp(name, "boolean") == 0); int is_nil = (strcmp(name, "nil") == 0); int is_class_tkn = (strcmp(name, "Class") == 0); + int is_float_tkn = (strcmp(name, "float") == 0); int is_byte = (strcmp(name, "byte") == 0); int is_u8 = (strcmp(name, "uint8") == 0) || is_byte; int is_u16 = (strcmp(name, "uint16") == 0); @@ -2155,7 +2192,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/Class use special markers */ + /* declared type metadata: integers use decl_bits; string/boolean/nil/Class/float use special markers */ int decl_meta = decl_bits; if (is_string) { decl_meta = TYPE_META_STRING; @@ -2165,6 +2202,8 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si decl_meta = TYPE_META_NIL; } else if (is_class_tkn) { decl_meta = TYPE_META_CLASS; + } else if (is_float_tkn) { + decl_meta = TYPE_META_FLOAT; } free(name); @@ -2255,6 +2294,23 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si } /* Continue after checks */ bytecode_set_operand(bc, j_ok, bc->instr_count); + } else if (decl_meta == TYPE_META_FLOAT) { + /* expect Float */ + bytecode_add_instruction(bc, OP_DUP, 0); + bytecode_add_instruction(bc, OP_TYPEOF, 0); + int ciF = bytecode_add_constant(bc, make_string("Float")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciF); + bytecode_add_instruction(bc, OP_EQ, 0); + int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0); + bytecode_set_operand(bc, j_to_error, bc->instr_count); + { + int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Float")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_HALT, 0); + } + bytecode_set_operand(bc, j_skip_err, bc->instr_count); } else if (decl_meta == TYPE_META_BOOLEAN) { /* accept Boolean literal or Number; if Number, clamp to 0/1 */ /* check if value is Boolean */ @@ -2581,6 +2637,23 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si } /* Continue after checks */ bytecode_set_operand(bc, j_ok, bc->instr_count); + } else if (meta == TYPE_META_FLOAT) { + /* expect Float */ + bytecode_add_instruction(bc, OP_DUP, 0); + bytecode_add_instruction(bc, OP_TYPEOF, 0); + int ciF = bytecode_add_constant(bc, make_string("Float")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciF); + bytecode_add_instruction(bc, OP_EQ, 0); + int j_to_error = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0); + bytecode_set_operand(bc, j_to_error, bc->instr_count); + { + int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Float")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_HALT, 0); + } + bytecode_set_operand(bc, j_skip_err, bc->instr_count); } else if (meta == TYPE_META_BOOLEAN) { /* expect Number then clamp to 1 bit (unsigned) */ bytecode_add_instruction(bc, OP_DUP, 0); diff --git a/src/parser_utils.c b/src/parser_utils.c index 857a207..fcc954d 100644 --- a/src/parser_utils.c +++ b/src/parser_utils.c @@ -697,3 +697,61 @@ static char *preprocess_includes_internal(const char *src, int depth) { static char *preprocess_includes(const char *src) { return preprocess_includes_internal(src, 0); } + + +/* Float literal parser: supports decimal and scientific notation. Returns parsed double and advances pos on success. */ +static double parse_float_literal_value(const char *src, size_t len, size_t *pos, int *ok) { + size_t p = *pos; + skip_spaces(src, len, &p); + size_t start = p; + int saw_digit = 0; + int saw_dot = 0; + int saw_exp = 0; + + /* optional sign */ + if (p < len && (src[p] == '+' || src[p] == '-')) p++; + + /* integer part */ + while (p < len && isdigit((unsigned char)src[p])) { p++; saw_digit = 1; } + + /* fractional part */ + if (p < len && src[p] == '.') { + saw_dot = 1; + p++; + while (p < len && isdigit((unsigned char)src[p])) { p++; saw_digit = 1; } + } + + /* exponent part */ + if (p < len && (src[p] == 'e' || src[p] == 'E')) { + saw_exp = 1; + size_t epos = p + 1; + if (epos < len && (src[epos] == '+' || src[epos] == '-')) epos++; + size_t digits_start = epos; + while (epos < len && isdigit((unsigned char)src[epos])) { epos++; } + if (epos == digits_start) { + /* no digits after exponent -> not a float */ + *ok = 0; return 0.0; + } + p = epos; + } + + if (!saw_digit || (!saw_dot && !saw_exp)) { + *ok = 0; return 0.0; + } + + /* Create temporary buffer to parse with strtod safely */ + size_t n = p - start; + char *tmp = (char*)malloc(n + 1); + if (!tmp) { *ok = 0; return 0.0; } + memcpy(tmp, src + start, n); + tmp[n] = '\0'; + + char *endp = NULL; + double dv = strtod(tmp, &endp); + if (!endp || *endp != '\0') { free(tmp); *ok = 0; return 0.0; } + + *pos = p; + *ok = 1; + free(tmp); + return dv; +} diff --git a/src/value.c b/src/value.c index f864f42..6008d06 100644 --- a/src/value.c +++ b/src/value.c @@ -37,6 +37,13 @@ Value make_int(int64_t v) { return val; } +Value make_float(double v) { + Value val; + val.type = VAL_FLOAT; + val.d = v; + return val; +} + Value make_bool(int v) { Value val; val.type = VAL_BOOL; @@ -229,6 +236,9 @@ Value copy_value(const Value *v) { case VAL_INT: out.i = v->i; break; + case VAL_FLOAT: + out.d = v->d; + break; case VAL_BOOL: out.i = v->i ? 1 : 0; break; @@ -262,6 +272,8 @@ Value deep_copy_value(const Value *v) { switch (v->type) { case VAL_INT: return make_int(v->i); + case VAL_FLOAT: + return make_float(v->d); case VAL_BOOL: return make_bool(v->i); case VAL_STRING: @@ -334,6 +346,9 @@ void print_value(const Value *v) { case VAL_INT: printf("%" PRId64, v->i); break; + case VAL_FLOAT: + printf("%.17g", v->d); + break; case VAL_STRING: printf("%s", v->s ? v->s : ""); break; @@ -379,6 +394,8 @@ int value_is_truthy(const Value *v) { switch (v->type) { case VAL_INT: return v->i != 0; + case VAL_FLOAT: + return v->d != 0.0; case VAL_BOOL: return v->i != 0; case VAL_STRING: @@ -405,6 +422,11 @@ char *value_to_string_alloc(const Value *v) { snprintf(tmp, sizeof(tmp), "%" PRId64, v->i); return strdup(tmp); } + case VAL_FLOAT: { + char tmp[64]; + snprintf(tmp, sizeof(tmp), "%.17g", v->d); + return strdup(tmp); + } case VAL_STRING: return strdup(v->s ? v->s : ""); case VAL_BOOL: @@ -426,6 +448,12 @@ char *value_to_string_alloc(const Value *v) { } int value_equals(const Value *a, const Value *b) { + // Numeric cross-type equality: int vs float compares numerically + if ((a->type == VAL_INT || a->type == VAL_FLOAT) && (b->type == VAL_INT || b->type == VAL_FLOAT)) { + double da = (a->type == VAL_INT) ? (double)a->i : a->d; + double db = (b->type == VAL_INT) ? (double)b->i : b->d; + return da == db; + } if (a->type != b->type) return 0; switch (a->type) { case VAL_INT: return a->i == b->i; diff --git a/src/value.h b/src/value.h index b1dcaac..9aa6c4c 100644 --- a/src/value.h +++ b/src/value.h @@ -51,13 +51,15 @@ typedef enum { VAL_FUNCTION, VAL_ARRAY, VAL_MAP, - VAL_NIL + VAL_NIL, + VAL_FLOAT } ValueType; typedef struct { ValueType type; union { int64_t i; + double d; char *s; struct Bytecode *fn; struct Array *arr; @@ -71,6 +73,7 @@ Value make_bool(int v); Value make_string(const char *s); Value make_function(struct Bytecode *fn); Value make_nil(void); +Value make_float(double v); /* arrays */ Value make_array_from_values(const Value *vals, int count); /* deep-copies vals */ diff --git a/src/vm.c b/src/vm.c index a9790cb..6f1a2ba 100644 --- a/src/vm.c +++ b/src/vm.c @@ -119,6 +119,10 @@ static const char* value_type_name(ValueType t) { switch (t) { case VAL_FUNCTION: return "function"; case VAL_INT: return "int"; + case VAL_FLOAT: return "float"; + case VAL_BOOL: return "boolean"; + case VAL_ARRAY: return "array"; + case VAL_MAP: return "map"; case VAL_NIL: return "nil"; case VAL_STRING: return "string"; default: return "unknown"; diff --git a/src/vm/arithmetic/add.c b/src/vm/arithmetic/add.c index 8e97946..46c044a 100644 --- a/src/vm/arithmetic/add.c +++ b/src/vm/arithmetic/add.c @@ -38,11 +38,20 @@ case OP_ADD: { Value b = pop_value(vm); Value a = pop_value(vm); - if (a.type == VAL_INT && b.type == VAL_INT) { - Value res = make_int(a.i + b.i); - free_value(a); - free_value(b); - push_value(vm, res); + if ((a.type == VAL_INT || a.type == VAL_FLOAT) && (b.type == VAL_INT || b.type == VAL_FLOAT)) { + if (a.type == VAL_FLOAT || b.type == VAL_FLOAT) { + double da = (a.type == VAL_FLOAT) ? a.d : (double)a.i; + double db = (b.type == VAL_FLOAT) ? b.d : (double)b.i; + Value res = make_float(da + db); + free_value(a); + free_value(b); + push_value(vm, res); + } else { + Value res = make_int(a.i + b.i); + free_value(a); + free_value(b); + push_value(vm, res); + } } else if (a.type == VAL_STRING && b.type == VAL_STRING) { const char *sa = a.s ? a.s : ""; const char *sb = b.s ? b.s : ""; @@ -68,7 +77,7 @@ case OP_ADD: { free_value(b); push_value(vm, res); } else { - fprintf(stderr, "Runtime type error: ADD expects both ints, both strings, or both arrays, got %s and %s\n", + fprintf(stderr, "Runtime type error: ADD expects both numbers, both strings, or both arrays, got %s and %s\n", value_type_name(a.type), value_type_name(b.type)); exit(1); } diff --git a/src/vm/arithmetic/div.c b/src/vm/arithmetic/div.c index c6dad0b..007fa5b 100644 --- a/src/vm/arithmetic/div.c +++ b/src/vm/arithmetic/div.c @@ -35,18 +35,32 @@ case OP_DIV: { Value b = pop_value(vm); Value a = pop_value(vm); - if (a.type != VAL_INT || b.type != VAL_INT) { - fprintf(stderr, "Runtime type error: DIV expects ints, got %s and %s\n", + if ((a.type == VAL_INT || a.type == VAL_FLOAT) && (b.type == VAL_INT || b.type == VAL_FLOAT)) { + if (a.type == VAL_FLOAT || b.type == VAL_FLOAT) { + double da = (a.type == VAL_FLOAT) ? a.d : (double)a.i; + double db = (b.type == VAL_FLOAT) ? b.d : (double)b.i; + if (db == 0.0) { + fprintf(stderr, "Runtime error: division by zero\n"); + exit(1); + } + Value res = make_float(da / db); + free_value(a); + free_value(b); + push_value(vm, res); + } else { + if (b.i == 0) { + fprintf(stderr, "Runtime error: division by zero\n"); + exit(1); + } + Value res = make_int(a.i / b.i); + free_value(a); + free_value(b); + push_value(vm, res); + } + } else { + fprintf(stderr, "Runtime type error: DIV expects numbers, got %s and %s\n", value_type_name(a.type), value_type_name(b.type)); exit(1); } - if (b.i == 0) { - fprintf(stderr, "Runtime error: division by zero\n"); - exit(1); - } - Value res = make_int(a.i / b.i); - free_value(a); - free_value(b); - push_value(vm, res); break; } diff --git a/src/vm/arithmetic/mul.c b/src/vm/arithmetic/mul.c index 8e0139e..94decfd 100644 --- a/src/vm/arithmetic/mul.c +++ b/src/vm/arithmetic/mul.c @@ -34,14 +34,24 @@ case OP_MUL: { Value b = pop_value(vm); Value a = pop_value(vm); - if (a.type != VAL_INT || b.type != VAL_INT) { - fprintf(stderr, "Runtime type error: MUL expects ints, got %s and %s\n", + if ((a.type == VAL_INT || a.type == VAL_FLOAT) && (b.type == VAL_INT || b.type == VAL_FLOAT)) { + if (a.type == VAL_FLOAT || b.type == VAL_FLOAT) { + double da = (a.type == VAL_FLOAT) ? a.d : (double)a.i; + double db = (b.type == VAL_FLOAT) ? b.d : (double)b.i; + Value res = make_float(da * db); + free_value(a); + free_value(b); + push_value(vm, res); + } else { + Value res = make_int(a.i * b.i); + free_value(a); + free_value(b); + push_value(vm, res); + } + } else { + fprintf(stderr, "Runtime type error: MUL expects numbers, got %s and %s\n", value_type_name(a.type), value_type_name(b.type)); exit(1); } - Value res = make_int(a.i * b.i); - free_value(a); - free_value(b); - push_value(vm, res); break; } diff --git a/src/vm/arithmetic/sub.c b/src/vm/arithmetic/sub.c index b9e1d79..e1867c8 100644 --- a/src/vm/arithmetic/sub.c +++ b/src/vm/arithmetic/sub.c @@ -34,14 +34,24 @@ case OP_SUB: { Value b = pop_value(vm); Value a = pop_value(vm); - if (a.type != VAL_INT || b.type != VAL_INT) { - fprintf(stderr, "Runtime type error: SUB expects ints, got %s and %s\n", + if ((a.type == VAL_INT || a.type == VAL_FLOAT) && (b.type == VAL_INT || b.type == VAL_FLOAT)) { + if (a.type == VAL_FLOAT || b.type == VAL_FLOAT) { + double da = (a.type == VAL_FLOAT) ? a.d : (double)a.i; + double db = (b.type == VAL_FLOAT) ? b.d : (double)b.i; + Value res = make_float(da - db); + free_value(a); + free_value(b); + push_value(vm, res); + } else { + Value res = make_int(a.i - b.i); + free_value(a); + free_value(b); + push_value(vm, res); + } + } else { + fprintf(stderr, "Runtime type error: SUB expects numbers, got %s and %s\n", value_type_name(a.type), value_type_name(b.type)); exit(1); } - Value res = make_int(a.i - b.i); - free_value(a); - free_value(b); - push_value(vm, res); break; } diff --git a/src/vm/sclamp.c b/src/vm/sclamp.c index d688323..d540321 100644 --- a/src/vm/sclamp.c +++ b/src/vm/sclamp.c @@ -8,27 +8,35 @@ */ case OP_SCLAMP: { - /* Saturating clamp to signed N-bit range: [-2^(N-1) .. 2^(N-1)-1] */ + /* Two's complement wrap to signed N-bit range: + - Mask to N bits + - If sign bit is set, sign-extend to 64-bit + This yields values in [-2^(N-1) .. 2^(N-1)-1]. */ Value v = pop_value(vm); int bits = inst.operand; int64_t vi = (v.type == VAL_INT) ? v.i : 0; - int64_t smin, smax; + int64_t out = 0; if (bits <= 0) { - smin = 0; smax = 0; - } else if (bits >= 63) { - /* cover full int64_t domain for 63+ bits */ - smin = INT64_MIN; - smax = INT64_MAX; + out = 0; } else { - smin = -(1LL << (bits - 1)); - smax = (1LL << (bits - 1)) - 1LL; + uint64_t mask = (bits >= 64) ? UINT64_MAX : ((1ULL << bits) - 1ULL); + uint64_t wrapped = ((uint64_t)vi) & mask; + if (bits >= 64) { + /* 64-bit: already full width; interpret as signed */ + out = (int64_t)wrapped; + } else { + uint64_t sign_bit = 1ULL << (bits - 1); + if (wrapped & sign_bit) { + /* sign-extend */ + out = (int64_t)(wrapped | (~mask)); + } else { + out = (int64_t)wrapped; + } + } } - if (vi < smin) vi = smin; - else if (vi > smax) vi = smax; - - push_value(vm, make_int(vi)); + push_value(vm, make_int(out)); free_value(v); break; } diff --git a/src/vm/to_number.c b/src/vm/to_number.c index 1e58b19..1f2d0cd 100644 --- a/src/vm/to_number.c +++ b/src/vm/to_number.c @@ -34,22 +34,56 @@ case OP_TO_NUMBER: { Value v = pop_value(vm); if (v.type == VAL_INT) { - Value out = make_int(v.i); + push_value(vm, make_int(v.i)); + free_value(v); + } else if (v.type == VAL_FLOAT) { + double d = v.d; + if (d >= (double)INT64_MIN && d <= (double)INT64_MAX) { + int64_t ii = (int64_t)d; + if ((double)ii == d) { + push_value(vm, make_int(ii)); + } else { + push_value(vm, make_float(d)); + } + } else { + push_value(vm, make_float(d)); + } free_value(v); - push_value(vm, out); } else if (v.type == VAL_STRING) { const char *s = v.s ? v.s : ""; const char *p = s; while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p++; char *endp = NULL; - long long parsed = strtoll(p, &endp, 10); + /* Try float first to support decimals and scientific notation */ + double dval = strtod(p, &endp); while (endp && (*endp == ' ' || *endp == '\t' || *endp == '\r' || *endp == '\n')) endp++; - if (endp && *endp != '\0') { - push_value(vm, make_int(0)); + if (!endp || *endp != '\0') { + /* Fallback to integer-only parse */ + endp = NULL; + long long parsed = strtoll(p, &endp, 10); + while (endp && (*endp == ' ' || *endp == '\t' || *endp == '\r' || *endp == '\n')) endp++; + if (endp && *endp == '\0') { + push_value(vm, make_int((int64_t)parsed)); + } else { + push_value(vm, make_int(0)); + } } else { - push_value(vm, make_int((int64_t)parsed)); + /* Preserve int when exact; else float */ + if (dval >= (double)INT64_MIN && dval <= (double)INT64_MAX) { + int64_t ii = (int64_t)dval; + if ((double)ii == dval) { + push_value(vm, make_int(ii)); + } else { + push_value(vm, make_float(dval)); + } + } else { + push_value(vm, make_float(dval)); + } } free_value(v); + } else if (v.type == VAL_BOOL) { + push_value(vm, make_int(v.i ? 1 : 0)); + free_value(v); } else { free_value(v); push_value(vm, make_int(0)); diff --git a/src/vm/typeof.c b/src/vm/typeof.c index 44117dc..2240bfb 100644 --- a/src/vm/typeof.c +++ b/src/vm/typeof.c @@ -12,6 +12,7 @@ case OP_TYPEOF: { const char *tname = "Unknown"; switch (v.type) { case VAL_INT: tname = "Number"; break; + case VAL_FLOAT: tname = "Float"; break; case VAL_BOOL: tname = "Boolean"; break; case VAL_STRING: tname = "String"; break; case VAL_FUNCTION: tname = "Function"; break; diff --git a/src/vm/uclamp.c b/src/vm/uclamp.c index 954dc33..18f6271 100644 --- a/src/vm/uclamp.c +++ b/src/vm/uclamp.c @@ -8,27 +8,22 @@ */ case OP_UCLAMP: { - /* Saturating clamp to unsigned N-bit range: [0 .. 2^N - 1] */ + /* Unsigned wrap to N bits: mask lower N bits (operand = bits) */ Value v = pop_value(vm); int bits = inst.operand; int64_t vi = (v.type == VAL_INT) ? v.i : 0; - uint64_t umax; + uint64_t mask; if (bits <= 0) { - /* treat as clamp to 0..0 */ - umax = 0; + mask = 0ULL; } else if (bits >= 64) { - umax = UINT64_MAX; + mask = UINT64_MAX; } else { - umax = (1ULL << bits) - 1ULL; + mask = (1ULL << bits) - 1ULL; } - 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)); + uint64_t wrapped = ((uint64_t)vi) & mask; + push_value(vm, make_int((int64_t)wrapped)); free_value(v); break; }