1
0
Fork 0
forked from fun/fun

Added the Float type and many fixes. (0.24.0)

This commit is contained in:
Johannes Findeisen 2025-10-04 20:35:59 +02:00
commit 41fa946f1c
19 changed files with 432 additions and 69 deletions

View file

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

View file

@ -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 <you@hanez.org>
* 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 */

View file

@ -7,6 +7,8 @@
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* 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)

78
examples/floats.fun Executable file
View file

@ -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 <you@hanez.org>
* 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
*/

View file

@ -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
*/

View file

@ -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
*/

View file

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

View file

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

View file

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

View file

@ -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 */

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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