From 559400a866fafbbe45d8c5115e0ee1e3a6724a3c Mon Sep 17 00:00:00 2001 From: hanez Date: Mon, 15 Sep 2025 03:45:26 +0200 Subject: [PATCH] Added to_string() and to_number() functions as built-ins. --- examples/arrays.fun | 2 ++ examples/arrays_advanced.fun | 2 ++ examples/arrays_iter.fun | 2 ++ examples/builtins_conversions.fun | 37 ++++++++++++++++++++++++++++ examples/nested_loops.fun | 2 ++ examples/strings_test.fun | 1 + src/bytecode.c | 2 ++ src/bytecode.h | 6 ++++- src/parser.c | 16 ++++++++++++ src/vm.c | 41 +++++++++++++++++++++++++++++++ src/vm.h | 5 ++-- 11 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 examples/builtins_conversions.fun diff --git a/examples/arrays.fun b/examples/arrays.fun index 96a70f3..a15af3e 100755 --- a/examples/arrays.fun +++ b/examples/arrays.fun @@ -1,3 +1,5 @@ +#!/usr/bin/env fun + // Arrays basics arr = [1, 2, 3] print(arr) // -> [1, 2, 3] diff --git a/examples/arrays_advanced.fun b/examples/arrays_advanced.fun index 365fbc2..d0fd602 100755 --- a/examples/arrays_advanced.fun +++ b/examples/arrays_advanced.fun @@ -1,3 +1,5 @@ +#!/usr/bin/env fun + // Advanced Arrays Demo // Start with a basic array diff --git a/examples/arrays_iter.fun b/examples/arrays_iter.fun index e69a4c0..395bfe9 100644 --- a/examples/arrays_iter.fun +++ b/examples/arrays_iter.fun @@ -1,3 +1,5 @@ +#!/usr/bin/env fun + // for-in over an array literal for x in [1, 2, 3] print(x) // prints: 1, then 2, then 3 diff --git a/examples/builtins_conversions.fun b/examples/builtins_conversions.fun new file mode 100644 index 0000000..7ca499c --- /dev/null +++ b/examples/builtins_conversions.fun @@ -0,0 +1,37 @@ +#!/usr/bin/env fun + +// Conversions and length built-ins demo + +// len on array and string +print(len([1, 2, 3])) // -> 3 +print(len("hello")) // -> 5 + +// to_number with various string inputs +print(to_number("42")) // -> 42 +print(to_number(" -7 ")) // -> -7 +print(to_number("12a")) // non-numeric suffix -> 0 +print(to_number("")) // empty -> 0 +print(to_number(" ")) // spaces -> 0 + +// to_string on numbers and arrays +print(to_string(42)) // -> "42" (printed as 42) + +arr = [1, 2] +s = to_string(arr) // -> "[array n=2]" +print(s) + +// combine to_string with concatenation +print("val=" + to_string(99)) // -> "val=99" + +/* Expected output: +3 +5 +42 +-7 +0 +0 +0 +42 +[array n=2] +val=99 +*/ diff --git a/examples/nested_loops.fun b/examples/nested_loops.fun index bacd73b..83cffed 100644 --- a/examples/nested_loops.fun +++ b/examples/nested_loops.fun @@ -1,3 +1,5 @@ +#!/usr/bin/env fun + // Nested loops: break and continue behavior // 1) Nested for range: inner continue on j==2, inner break on j==4, outer break on i==3 diff --git a/examples/strings_test.fun b/examples/strings_test.fun index 4130285..38fbc41 100755 --- a/examples/strings_test.fun +++ b/examples/strings_test.fun @@ -1,4 +1,5 @@ #!/usr/bin/env fun + // Strings test: concatenation with variables and literals, functions returning strings print("=== strings test start ===") diff --git a/src/bytecode.c b/src/bytecode.c index f8168d5..0d615e2 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -82,6 +82,8 @@ static const char *opcode_name(OpCode op) { case OP_ARR_INSERT: return "ARR_INSERT"; case OP_ARR_REMOVE: return "ARR_REMOVE"; case OP_SLICE: return "SLICE"; + case OP_TO_NUMBER: return "TO_NUMBER"; + case OP_TO_STRING: return "TO_STRING"; default: return "???"; } } diff --git a/src/bytecode.h b/src/bytecode.h index 27bbd28..ce11c81 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -57,7 +57,11 @@ typedef enum { OP_ARR_SET, // pops value, index, array; pushes value OP_ARR_INSERT, // pops value, index, array; pushes new length OP_ARR_REMOVE, // pops index, array; pushes removed element - OP_SLICE // pops end, start, array; pushes new array + OP_SLICE, // pops end, start, array; pushes new array + + // conversions + OP_TO_NUMBER, // pops any; pushes int (parse strings) + OP_TO_STRING // pops any; pushes string } OpCode; typedef struct { diff --git a/src/parser.c b/src/parser.c index 07bc23a..54fa1ba 100644 --- a/src/parser.c +++ b/src/parser.c @@ -519,6 +519,22 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) free(name); return 1; } + if (strcmp(name, "to_number") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "to_number expects 1 argument"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after to_number arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_TO_NUMBER, 0); + free(name); + return 1; + } + if (strcmp(name, "to_string") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "to_string expects 1 argument"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after to_string arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_TO_STRING, 0); + free(name); + return 1; + } /* push function value first */ if (local_idx >= 0) { diff --git a/src/vm.c b/src/vm.c index e77dab6..0395cad 100644 --- a/src/vm.c +++ b/src/vm.c @@ -682,6 +682,47 @@ void vm_run(VM *vm, Bytecode *entry) { break; } + case OP_TO_NUMBER: { + Value v = pop_value(vm); + if (v.type == VAL_INT) { + /* pass-through */ + Value out = make_int(v.i); + free_value(v); + push_value(vm, out); + } else if (v.type == VAL_STRING) { + const char *s = v.s ? v.s : ""; + /* trim spaces */ + const char *p = s; + while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p++; + char *endp = NULL; + long long parsed = strtoll(p, &endp, 10); + /* skip trailing spaces */ + while (endp && (*endp == ' ' || *endp == '\t' || *endp == '\r' || *endp == '\n')) endp++; + if (endp && *endp != '\0') { + /* non-numeric suffix -> 0 */ + push_value(vm, make_int(0)); + } else { + push_value(vm, make_int((int64_t)parsed)); + } + free_value(v); + } else { + /* nil, array, function -> 0 */ + free_value(v); + push_value(vm, make_int(0)); + } + break; + } + + case OP_TO_STRING: { + Value v = pop_value(vm); + char *s = value_to_string_alloc(&v); + Value out = make_string(s ? s : ""); + if (s) free(s); + free_value(v); + push_value(vm, out); + break; + } + case OP_LOAD_GLOBAL: { int idx = inst.operand; if (idx < 0 || idx >= VM_MAX_GLOBALS) { diff --git a/src/vm.h b/src/vm.h index 3e5f355..0773c47 100644 --- a/src/vm.h +++ b/src/vm.h @@ -16,7 +16,8 @@ static const char *opcode_names[] = { "JUMP_IF_FALSE","CALL","RETURN","PRINT","HALT", "MOD","AND","OR","NOT","DUP","SWAP", "MAKE_ARRAY","INDEX_GET","INDEX_SET", - "LEN","ARR_PUSH","ARR_POP","ARR_SET","ARR_INSERT","ARR_REMOVE","SLICE" + "LEN","ARR_PUSH","ARR_POP","ARR_SET","ARR_INSERT","ARR_REMOVE","SLICE", + "TO_NUMBER","TO_STRING" }; typedef struct { @@ -57,7 +58,7 @@ void vm_dump_globals(VM *vm); void vm_run(VM *vm, Bytecode *entry); static inline int opcode_is_valid(int op) { - return op >= OP_NOP && op <= OP_SLICE; // all current opcodes + return op >= OP_NOP && op <= OP_TO_STRING; // all current opcodes } #endif