diff --git a/CMakeLists.txt b/CMakeLists.txt index a935d11..2c71943 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.38.16 LANGUAGES C) +project(fun VERSION 0.39.0 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) @@ -46,7 +46,16 @@ message(STATUS "===========================") # Convenience aggregate target (like 'build' in Makefile) add_custom_target(build - DEPENDS fun fun_test test_opcodes + DEPENDS fun funstx fun_test test_opcodes +) + +# Formatting helper target: run clang-format over C/C++ sources using .clang-format +add_custom_target(format + COMMAND ${CMAKE_COMMAND} -E echo "Running clang-format on sources..." + COMMAND /bin/sh -c "command -v clang-format >/dev/null 2>&1 || { echo 'clang-format not found in PATH' >&2; exit 1; }" + COMMAND /bin/sh -c "set -e; for pat in '*.c' '*.h' '*.cpp' '*.hpp'; do find ${CMAKE_SOURCE_DIR}/src -type f -name \"\$pat\" -print0; done | xargs -0 -n 50 clang-format -i" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Apply .clang-format (IndentWidth=2) to all source files" ) # --- Rust (Cargo) integration: optionally build and link a staticlib with opcode examples --- @@ -400,6 +409,11 @@ install(TARGETS fun RUNTIME DESTINATION /usr/bin ) +# Also install the syntax-checker CLI by default +install(TARGETS funstx + RUNTIME DESTINATION /usr/bin +) + # Libs install(DIRECTORY lib/ DESTINATION /usr/share/fun/lib diff --git a/cmake/Targets.cmake b/cmake/Targets.cmake index 5ac9dbe..2184b30 100644 --- a/cmake/Targets.cmake +++ b/cmake/Targets.cmake @@ -139,6 +139,12 @@ if(FUN_WITH_REPL) endif() target_link_libraries(fun PRIVATE fun_core) +# Executable: funstx (syntax checker CLI) +add_executable(funstx + ${CMAKE_SOURCE_DIR}/src/funstx.c +) +target_link_libraries(funstx PRIVATE fun_core) + # Internal test programs add_executable(fun_test ${CMAKE_SOURCE_DIR}/src/fun_test.c) diff --git a/examples/arrays.fun b/examples/arrays.fun index c6fd414..231f7f5 100755 --- a/examples/arrays.fun +++ b/examples/arrays.fun @@ -1,24 +1,24 @@ #!/usr/bin/env fun /* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - */ + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + */ /* - * Arrays Example - * - * Demonstrates basic array operations including: - * - Array declaration and initialization - * - Accessing elements via indexing - * - Iterating through arrays with `for` loops - * - Creating subarrays (slices) - *- Length calculation - */ + * Arrays Example + * + * Demonstrates basic array operations including: + * - Array declaration and initialization + * - Accessing elements via indexing + * - Iterating through arrays with `for` loops + * - Creating subarrays (slices) + *- Length calculation + */ // Arrays basics arr = [1, 2, 3] diff --git a/examples/arrays_iter.fun b/examples/arrays_iter.fun index 738cafd..71b5013 100755 --- a/examples/arrays_iter.fun +++ b/examples/arrays_iter.fun @@ -1,24 +1,24 @@ #!/usr/bin/env fun /* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - */ + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + */ /* - * Array Iteration Examples - * - * Focuses specifically on: - * - Various iteration methods (`for`, `while`) - * - Using array mapping functions - * - Filtering arrays based on conditions - * - Reducing arrays to computed values - *- Converting between different collection types during iteration - */ + * Array Iteration Examples + * + * Focuses specifically on: + * - Various iteration methods (`for`, `while`) + * - Using array mapping functions + * - Filtering arrays based on conditions + * - Reducing arrays to computed values + *- Converting between different collection types during iteration + */ // for-in over an array literal for x in [1, 2, 3] diff --git a/examples/base64_demo.fun b/examples/base64_demo.fun index d80f87b..4ed58e8 100755 --- a/examples/base64_demo.fun +++ b/examples/base64_demo.fun @@ -1,26 +1,26 @@ #!/usr/bin/env fun /* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2026 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - * - * Added: 2026-01-13 - */ + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-13 + */ /* - * Base64 usage demo (RFC 4648, standard alphabet) - * - * Run without installing: - * FUN_LIB_DIR="$(pwd)/lib" ./build/fun examples/base64_demo.fun - */ + * Base64 usage demo (RFC 4648, standard alphabet) + * + * Run without installing: + * FUN_LIB_DIR="$(pwd)/lib" ./build/fun examples/base64_demo.fun + */ #include -print("=== Base64 demo ===") + print("=== Base64 demo ===") // Bytes for the ASCII string "Hello" bytes = [0x48, 0x65, 0x6c, 0x6c, 0x6f] diff --git a/examples/cpp_add.fun b/examples/cpp_add.fun index 52a5dfa..d301670 100755 --- a/examples/cpp_add.fun +++ b/examples/cpp_add.fun @@ -1,15 +1,15 @@ #!/usr/bin/env fun /* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2026 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - * - * Added: 2026-01-29 - */ + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-29 + */ /* Build: diff --git a/examples/datetime_timer.fun b/examples/datetime_timer.fun index be1d21c..74f3b59 100755 --- a/examples/datetime_timer.fun +++ b/examples/datetime_timer.fun @@ -1,15 +1,15 @@ #!/usr/bin/env fun /* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - * - * Added: 2025-12-09 - */ + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-12-09 + */ // Simple stopwatch using DateTime helpers #include diff --git a/examples/random_demo.fun b/examples/random_demo.fun index 6ec0a36..510ff25 100755 --- a/examples/random_demo.fun +++ b/examples/random_demo.fun @@ -1,15 +1,15 @@ #!/usr/bin/env fun /* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - * - * Added: 2025-12-11 - */ + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-12-11 + */ // Demonstrates usage of RANDOM_SEED and RANDOM_INT opcodes via // the built-ins: random_seed(seed) and random_int(lo, hiExclusive). @@ -76,6 +76,6 @@ true true Max seen in [10,20) over 100 samples: 19 -Is max_seen < hi? +Is max_seen < hi? 1 */ diff --git a/examples/rust_hello.fun b/examples/rust_hello.fun index 434da0e..71cf60a 100755 --- a/examples/rust_hello.fun +++ b/examples/rust_hello.fun @@ -1,36 +1,36 @@ #!/usr/bin/env fun /* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2026 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - * - * Added: 2026-01-27 - */ + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-27 + */ /* - * Rust-backed opcode demo: rust_hello() - * - * Build instructions: - * - Default builds disable Rust integration. - * - Enable it via: cmake -S . -B build_debug -DFUN_WITH_RUST=ON - * - Then: cmake --build build_debug --target fun - * - * Run: - * build_debug/fun examples/rust_hello.fun - * - * Expected output (with FUN_WITH_RUST=ON): - * Hello from Rust ops! - * - * If built without Rust, calling rust_hello() will raise a runtime error - * explaining that Rust integration is disabled. - */ + * Rust-backed opcode demo: rust_hello() + * + * Build instructions: + * - Default builds disable Rust integration. + * - Enable it via: cmake -S . -B build_debug -DFUN_WITH_RUST=ON + * - Then: cmake --build build_debug --target fun + * + * Run: + * build_debug/fun examples/rust_hello.fun + * + * Expected output (with FUN_WITH_RUST=ON): + * Hello from Rust ops! + * + * If built without Rust, calling rust_hello() will raise a runtime error + * explaining that Rust integration is disabled. + */ print(rust_hello()) /* Expected output: Hello from Rust ops! -*/ +*/ diff --git a/examples/short_circuit_test.fun b/examples/short_circuit_test.fun index 5da8c00..7c86f96 100755 --- a/examples/short_circuit_test.fun +++ b/examples/short_circuit_test.fun @@ -1,13 +1,13 @@ #!/usr/bin/env fun /* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - */ + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + */ // Short-circuit demo for || and && diff --git a/examples/test_bits.fun b/examples/test_bits.fun index ab810b2..8ac9174 100755 --- a/examples/test_bits.fun +++ b/examples/test_bits.fun @@ -1,15 +1,15 @@ #!/usr/bin/env fun /* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - * - * Added: 2025-12-27 - */ + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-12-27 + */ print(bxor(0x80000000, 0x00000001)) print(bor(0x80000000, 0x00000001)) diff --git a/examples/threads_demo.fun b/examples/threads_demo.fun index c8c1eea..7a5c602 100755 --- a/examples/threads_demo.fun +++ b/examples/threads_demo.fun @@ -1,15 +1,15 @@ #!/usr/bin/env fun /* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - * - * Added: 2025-09-30 - */ + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-09-30 + */ // Threading demo for Fun // Run without installing: diff --git a/src/array_utils.c b/src/array_utils.c index e12e0c5..d9377ec 100644 --- a/src/array_utils.c +++ b/src/array_utils.c @@ -12,46 +12,46 @@ /* array utilities */ int array_contains(const Value *v, const Value *needle) { - if (!v || v->type != VAL_ARRAY || !v->arr) return 0; - int n = array_length(v); - for (int i = 0; i < n; ++i) { - Value item; - if (array_get_copy(v, i, &item)) { - int eq = value_equals(&item, needle); - free_value(item); - if (eq) return 1; - } + if (!v || v->type != VAL_ARRAY || !v->arr) return 0; + int n = array_length(v); + for (int i = 0; i < n; ++i) { + Value item; + if (array_get_copy(v, i, &item)) { + int eq = value_equals(&item, needle); + free_value(item); + if (eq) return 1; } - return 0; + } + return 0; } int array_index_of(const Value *v, const Value *needle) { - if (!v || v->type != VAL_ARRAY || !v->arr) return -1; - int n = array_length(v); - for (int i = 0; i < n; ++i) { - Value item; - if (array_get_copy(v, i, &item)) { - int eq = value_equals(&item, needle); - free_value(item); - if (eq) return i; - } + if (!v || v->type != VAL_ARRAY || !v->arr) return -1; + int n = array_length(v); + for (int i = 0; i < n; ++i) { + Value item; + if (array_get_copy(v, i, &item)) { + int eq = value_equals(&item, needle); + free_value(item); + if (eq) return i; } - return -1; + } + return -1; } void array_clear(Value *v) { - if (!v || v->type != VAL_ARRAY || !v->arr) return; - int n = array_length(v); - for (int i = 0; i < n; ++i) { - Value item; - if (array_get_copy(v, i, &item)) { - free_value(item); - } - } - /* internal clear uses public API to reset to zero length */ - /* Since we don't expose capacity, emulate by popping elements */ - Value out; - while (array_pop(v, &out)) { - free_value(out); + if (!v || v->type != VAL_ARRAY || !v->arr) return; + int n = array_length(v); + for (int i = 0; i < n; ++i) { + Value item; + if (array_get_copy(v, i, &item)) { + free_value(item); } + } + /* internal clear uses public API to reset to zero length */ + /* Since we don't expose capacity, emulate by popping elements */ + Value out; + while (array_pop(v, &out)) { + free_value(out); + } } diff --git a/src/bytecode.c b/src/bytecode.c index 01629c8..7173f91 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -8,235 +8,400 @@ */ #include "bytecode.h" -#include #include +#include Bytecode *bytecode_new(void) { - Bytecode *bc = (Bytecode*)malloc(sizeof(Bytecode)); - bc->instructions = NULL; - bc->instr_count = 0; - bc->constants = NULL; - bc->const_count = 0; - bc->name = NULL; - bc->source_file = NULL; - return bc; + Bytecode *bc = (Bytecode *)malloc(sizeof(Bytecode)); + bc->instructions = NULL; + bc->instr_count = 0; + bc->constants = NULL; + bc->const_count = 0; + bc->name = NULL; + bc->source_file = NULL; + return bc; } int bytecode_add_constant(Bytecode *bc, Value v) { - bc->constants = (Value*)realloc(bc->constants, sizeof(Value) * (bc->const_count + 1)); - bc->constants[bc->const_count] = copy_value(&v); - return bc->const_count++; + bc->constants = (Value *)realloc(bc->constants, sizeof(Value) * (bc->const_count + 1)); + bc->constants[bc->const_count] = copy_value(&v); + return bc->const_count++; } int bytecode_add_instruction(Bytecode *bc, OpCode op, int32_t operand) { - bc->instructions = (Instruction*)realloc(bc->instructions, sizeof(Instruction) * (bc->instr_count + 1)); - bc->instructions[bc->instr_count].op = op; - bc->instructions[bc->instr_count].operand = operand; - return bc->instr_count++; + bc->instructions = (Instruction *)realloc(bc->instructions, sizeof(Instruction) * (bc->instr_count + 1)); + bc->instructions[bc->instr_count].op = op; + bc->instructions[bc->instr_count].operand = operand; + return bc->instr_count++; } void bytecode_set_operand(Bytecode *bc, int idx, int32_t operand) { - if (idx >= 0 && idx < bc->instr_count) { - bc->instructions[idx].operand = operand; - } + if (idx >= 0 && idx < bc->instr_count) { + bc->instructions[idx].operand = operand; + } } void bytecode_free(Bytecode *bc) { - if (!bc) return; - for (int i = 0; i < bc->const_count; ++i) { - free_value(bc->constants[i]); - } - free(bc->constants); - free(bc->instructions); - if (bc->name) free((void*)bc->name); - if (bc->source_file) free((void*)bc->source_file); - free(bc); + if (!bc) return; + for (int i = 0; i < bc->const_count; ++i) { + free_value(bc->constants[i]); + } + free(bc->constants); + free(bc->instructions); + if (bc->name) free((void *)bc->name); + if (bc->source_file) free((void *)bc->source_file); + free(bc); } static const char *opcode_name(OpCode op) { - switch (op) { - case OP_NOP: return "NOP"; - case OP_LOAD_CONST: return "LOAD_CONST"; - case OP_LOAD_LOCAL: return "LOAD_LOCAL"; - case OP_STORE_LOCAL: return "STORE_LOCAL"; - case OP_LOAD_GLOBAL: return "LOAD_GLOBAL"; - case OP_STORE_GLOBAL: return "STORE_GLOBAL"; - case OP_ADD: return "ADD"; - case OP_SUB: return "SUB"; - case OP_MUL: return "MUL"; - case OP_DIV: return "DIV"; - case OP_LT: return "LT"; - case OP_LTE: return "LTE"; - case OP_GT: return "GT"; - case OP_GTE: return "GTE"; - case OP_EQ: return "EQ"; - case OP_NEQ: return "NEQ"; - case OP_POP: return "POP"; - case OP_JUMP: return "JUMP"; - case OP_JUMP_IF_FALSE: return "JUMP_IF_FALSE"; - case OP_CALL: return "CALL"; - case OP_RETURN: return "RETURN"; - case OP_PRINT: return "PRINT"; - case OP_ECHO: return "ECHO"; - case OP_HALT: return "HALT"; - case OP_MOD: return "MOD"; - case OP_AND: return "AND"; - case OP_OR: return "OR"; - case OP_NOT: return "NOT"; - case OP_DUP: return "DUP"; - case OP_SWAP: return "SWAP"; - case OP_MAKE_ARRAY: return "MAKE_ARRAY"; - case OP_INDEX_GET: return "INDEX_GET"; - case OP_INDEX_SET: return "INDEX_SET"; - case OP_LEN: return "LEN"; - case OP_PUSH: return "ARR_PUSH"; - case OP_APOP: return "ARR_POP"; - case OP_SET: return "ARR_SET"; - case OP_INSERT: return "ARR_INSERT"; - case OP_REMOVE: return "ARR_REMOVE"; - case OP_SLICE: return "SLICE"; - case OP_TO_NUMBER: return "TO_NUMBER"; - case OP_TO_STRING: return "TO_STRING"; - case OP_TYPEOF: return "TYPEOF"; - case OP_CAST: return "CAST"; - case OP_SPLIT: return "SPLIT"; - case OP_JOIN: return "JOIN"; - case OP_SUBSTR: return "SUBSTR"; - case OP_FIND: return "FIND"; - case OP_REGEX_MATCH: return "REGEX_MATCH"; - case OP_REGEX_SEARCH: return "REGEX_SEARCH"; - case OP_REGEX_REPLACE: return "REGEX_REPLACE"; - case OP_CONTAINS: return "CONTAINS"; - case OP_INDEX_OF: return "INDEX_OF"; - case OP_CLEAR: return "CLEAR"; - case OP_ENUMERATE: return "ENUMERATE"; - case OP_ZIP: return "ZIP"; - case OP_MIN: return "MIN"; - case OP_MAX: return "MAX"; - case OP_CLAMP: return "CLAMP"; - case OP_ABS: return "ABS"; - case OP_POW: return "POW"; - case OP_RANDOM_SEED: return "RANDOM_SEED"; - case OP_RANDOM_INT: return "RANDOM_INT"; - case OP_MAKE_MAP: return "MAKE_MAP"; - case OP_KEYS: return "KEYS"; - case OP_VALUES: return "VALUES"; - case OP_HAS_KEY: return "HAS_KEY"; - case OP_READ_FILE: return "READ_FILE"; - case OP_WRITE_FILE: return "WRITE_FILE"; - case OP_ENV: return "ENV"; - case OP_INPUT_LINE: return "INPUT_LINE"; - case OP_PROC_RUN: return "PROC_RUN"; - case OP_PROC_SYSTEM: return "PROC_SYSTEM"; - case OP_TIME_NOW_MS: return "TIME_NOW_MS"; - case OP_CLOCK_MONO_MS: return "CLOCK_MONO_MS"; - case OP_DATE_FORMAT: return "DATE_FORMAT"; - case OP_ENV_ALL: return "ENV_ALL"; - case OP_FUN_VERSION: return "FUN_VERSION"; - case OP_THREAD_SPAWN: return "THREAD_SPAWN"; - case OP_THREAD_JOIN: return "THREAD_JOIN"; - case OP_SLEEP_MS: return "SLEEP_MS"; - case OP_RANDOM_NUMBER: return "RANDOM_NUMBER"; - case OP_BAND: return "BAND"; - case OP_BOR: return "BOR"; - case OP_BXOR: return "BXOR"; - case OP_BNOT: return "BNOT"; - case OP_SHL: return "SHL"; - case OP_SHR: return "SHR"; - case OP_ROTL: return "ROTL"; - case OP_ROTR: return "ROTR"; - case OP_JSON_PARSE: return "JSON_PARSE"; - case OP_JSON_STRINGIFY: return "JSON_STRINGIFY"; - case OP_JSON_FROM_FILE: return "JSON_FROM_FILE"; - case OP_JSON_TO_FILE: return "JSON_TO_FILE"; - case OP_CURL_GET: return "CURL_GET"; - case OP_CURL_POST: return "CURL_POST"; - case OP_CURL_DOWNLOAD: return "CURL_DOWNLOAD"; - case OP_SQLITE_OPEN: return "SQLITE_OPEN"; - case OP_SQLITE_CLOSE: return "SQLITE_CLOSE"; - case OP_SQLITE_EXEC: return "SQLITE_EXEC"; - case OP_SQLITE_QUERY: return "SQLITE_QUERY"; - case OP_LIBSQL_OPEN: return "LIBSQL_OPEN"; - case OP_LIBSQL_CLOSE: return "LIBSQL_CLOSE"; - case OP_LIBSQL_EXEC: return "LIBSQL_EXEC"; - case OP_LIBSQL_QUERY: return "LIBSQL_QUERY"; - case OP_PCSC_ESTABLISH: return "PCSC_ESTABLISH"; - case OP_PCSC_RELEASE: return "PCSC_RELEASE"; - case OP_PCSC_LIST_READERS: return "PCSC_LIST_READERS"; - case OP_PCSC_CONNECT: return "PCSC_CONNECT"; - case OP_PCSC_DISCONNECT: return "PCSC_DISCONNECT"; - case OP_PCSC_TRANSMIT: return "PCSC_TRANSMIT"; - case OP_PCRE2_TEST: return "PCRE2_TEST"; - case OP_PCRE2_MATCH: return "PCRE2_MATCH"; - case OP_PCRE2_FINDALL: return "PCRE2_FINDALL"; - case OP_OPENSSL_MD5: return "OPENSSL_MD5"; - case OP_OPENSSL_SHA256: return "OPENSSL_SHA256"; - case OP_OPENSSL_SHA512: return "OPENSSL_SHA512"; - case OP_OPENSSL_RIPEMD160: return "OPENSSL_RIPEMD160"; - case OP_LIBRESSL_MD5: return "LIBRESSL_MD5"; - case OP_LIBRESSL_SHA256: return "LIBRESSL_SHA256"; - case OP_LIBRESSL_SHA512: return "LIBRESSL_SHA512"; - case OP_LIBRESSL_RIPEMD160: return "LIBRESSL_RIPEMD160"; - case OP_INI_LOAD: return "INI_LOAD"; - case OP_INI_FREE: return "INI_FREE"; - case OP_INI_GET_STRING: return "INI_GET_STRING"; - case OP_INI_GET_INT: return "INI_GET_INT"; - case OP_INI_GET_DOUBLE: return "INI_GET_DOUBLE"; - case OP_INI_GET_BOOL: return "INI_GET_BOOL"; - case OP_INI_SET: return "INI_SET"; - case OP_INI_UNSET: return "INI_UNSET"; - case OP_INI_SAVE: return "INI_SAVE"; - case OP_XML_PARSE: return "XML_PARSE"; - case OP_XML_ROOT: return "XML_ROOT"; - case OP_XML_NAME: return "XML_NAME"; - case OP_XML_TEXT: return "XML_TEXT"; - case OP_TK_EVAL: return "TK_EVAL"; - case OP_TK_RESULT: return "TK_RESULT"; - case OP_TK_LOOP: return "TK_LOOP"; - case OP_TK_WM_TITLE: return "TK_WM_TITLE"; - case OP_TK_LABEL: return "TK_LABEL"; - case OP_TK_BUTTON: return "TK_BUTTON"; - case OP_TK_PACK: return "TK_PACK"; - case OP_FLOOR: return "FLOOR"; - case OP_CEIL: return "CEIL"; - case OP_TRUNC: return "TRUNC"; - case OP_ROUND: return "ROUND"; - case OP_SIN: return "SIN"; - case OP_COS: return "COS"; - case OP_TAN: return "TAN"; - case OP_EXP: return "EXP"; - case OP_LOG: return "LOG"; - case OP_LOG10: return "LOG10"; - case OP_SQRT: return "SQRT"; - case OP_GCD: return "GCD"; - case OP_LCM: return "LCM"; - case OP_ISQRT: return "ISQRT"; - case OP_SIGN: return "SIGN"; - case OP_FMIN: return "FMIN"; - case OP_FMAX: return "FMAX"; - case OP_RUST_HELLO: return "RUST_HELLO"; - case OP_RUST_HELLO_ARGS: return "RUST_HELLO_ARGS"; - case OP_RUST_HELLO_ARGS_RETURN: return "RUST_HELLO_ARGS_RETURN"; - case OP_RUST_GET_SP: return "RUST_GET_SP"; - case OP_RUST_SET_EXIT: return "RUST_SET_EXIT"; - default: return "???"; - } + switch (op) { + case OP_NOP: + return "NOP"; + case OP_LOAD_CONST: + return "LOAD_CONST"; + case OP_LOAD_LOCAL: + return "LOAD_LOCAL"; + case OP_STORE_LOCAL: + return "STORE_LOCAL"; + case OP_LOAD_GLOBAL: + return "LOAD_GLOBAL"; + case OP_STORE_GLOBAL: + return "STORE_GLOBAL"; + case OP_ADD: + return "ADD"; + case OP_SUB: + return "SUB"; + case OP_MUL: + return "MUL"; + case OP_DIV: + return "DIV"; + case OP_LT: + return "LT"; + case OP_LTE: + return "LTE"; + case OP_GT: + return "GT"; + case OP_GTE: + return "GTE"; + case OP_EQ: + return "EQ"; + case OP_NEQ: + return "NEQ"; + case OP_POP: + return "POP"; + case OP_JUMP: + return "JUMP"; + case OP_JUMP_IF_FALSE: + return "JUMP_IF_FALSE"; + case OP_CALL: + return "CALL"; + case OP_RETURN: + return "RETURN"; + case OP_PRINT: + return "PRINT"; + case OP_ECHO: + return "ECHO"; + case OP_HALT: + return "HALT"; + case OP_MOD: + return "MOD"; + case OP_AND: + return "AND"; + case OP_OR: + return "OR"; + case OP_NOT: + return "NOT"; + case OP_DUP: + return "DUP"; + case OP_SWAP: + return "SWAP"; + case OP_MAKE_ARRAY: + return "MAKE_ARRAY"; + case OP_INDEX_GET: + return "INDEX_GET"; + case OP_INDEX_SET: + return "INDEX_SET"; + case OP_LEN: + return "LEN"; + case OP_PUSH: + return "ARR_PUSH"; + case OP_APOP: + return "ARR_POP"; + case OP_SET: + return "ARR_SET"; + case OP_INSERT: + return "ARR_INSERT"; + case OP_REMOVE: + return "ARR_REMOVE"; + case OP_SLICE: + return "SLICE"; + case OP_TO_NUMBER: + return "TO_NUMBER"; + case OP_TO_STRING: + return "TO_STRING"; + case OP_TYPEOF: + return "TYPEOF"; + case OP_CAST: + return "CAST"; + case OP_SPLIT: + return "SPLIT"; + case OP_JOIN: + return "JOIN"; + case OP_SUBSTR: + return "SUBSTR"; + case OP_FIND: + return "FIND"; + case OP_REGEX_MATCH: + return "REGEX_MATCH"; + case OP_REGEX_SEARCH: + return "REGEX_SEARCH"; + case OP_REGEX_REPLACE: + return "REGEX_REPLACE"; + case OP_CONTAINS: + return "CONTAINS"; + case OP_INDEX_OF: + return "INDEX_OF"; + case OP_CLEAR: + return "CLEAR"; + case OP_ENUMERATE: + return "ENUMERATE"; + case OP_ZIP: + return "ZIP"; + case OP_MIN: + return "MIN"; + case OP_MAX: + return "MAX"; + case OP_CLAMP: + return "CLAMP"; + case OP_ABS: + return "ABS"; + case OP_POW: + return "POW"; + case OP_RANDOM_SEED: + return "RANDOM_SEED"; + case OP_RANDOM_INT: + return "RANDOM_INT"; + case OP_MAKE_MAP: + return "MAKE_MAP"; + case OP_KEYS: + return "KEYS"; + case OP_VALUES: + return "VALUES"; + case OP_HAS_KEY: + return "HAS_KEY"; + case OP_READ_FILE: + return "READ_FILE"; + case OP_WRITE_FILE: + return "WRITE_FILE"; + case OP_ENV: + return "ENV"; + case OP_INPUT_LINE: + return "INPUT_LINE"; + case OP_PROC_RUN: + return "PROC_RUN"; + case OP_PROC_SYSTEM: + return "PROC_SYSTEM"; + case OP_TIME_NOW_MS: + return "TIME_NOW_MS"; + case OP_CLOCK_MONO_MS: + return "CLOCK_MONO_MS"; + case OP_DATE_FORMAT: + return "DATE_FORMAT"; + case OP_ENV_ALL: + return "ENV_ALL"; + case OP_FUN_VERSION: + return "FUN_VERSION"; + case OP_THREAD_SPAWN: + return "THREAD_SPAWN"; + case OP_THREAD_JOIN: + return "THREAD_JOIN"; + case OP_SLEEP_MS: + return "SLEEP_MS"; + case OP_RANDOM_NUMBER: + return "RANDOM_NUMBER"; + case OP_BAND: + return "BAND"; + case OP_BOR: + return "BOR"; + case OP_BXOR: + return "BXOR"; + case OP_BNOT: + return "BNOT"; + case OP_SHL: + return "SHL"; + case OP_SHR: + return "SHR"; + case OP_ROTL: + return "ROTL"; + case OP_ROTR: + return "ROTR"; + case OP_JSON_PARSE: + return "JSON_PARSE"; + case OP_JSON_STRINGIFY: + return "JSON_STRINGIFY"; + case OP_JSON_FROM_FILE: + return "JSON_FROM_FILE"; + case OP_JSON_TO_FILE: + return "JSON_TO_FILE"; + case OP_CURL_GET: + return "CURL_GET"; + case OP_CURL_POST: + return "CURL_POST"; + case OP_CURL_DOWNLOAD: + return "CURL_DOWNLOAD"; + case OP_SQLITE_OPEN: + return "SQLITE_OPEN"; + case OP_SQLITE_CLOSE: + return "SQLITE_CLOSE"; + case OP_SQLITE_EXEC: + return "SQLITE_EXEC"; + case OP_SQLITE_QUERY: + return "SQLITE_QUERY"; + case OP_LIBSQL_OPEN: + return "LIBSQL_OPEN"; + case OP_LIBSQL_CLOSE: + return "LIBSQL_CLOSE"; + case OP_LIBSQL_EXEC: + return "LIBSQL_EXEC"; + case OP_LIBSQL_QUERY: + return "LIBSQL_QUERY"; + case OP_PCSC_ESTABLISH: + return "PCSC_ESTABLISH"; + case OP_PCSC_RELEASE: + return "PCSC_RELEASE"; + case OP_PCSC_LIST_READERS: + return "PCSC_LIST_READERS"; + case OP_PCSC_CONNECT: + return "PCSC_CONNECT"; + case OP_PCSC_DISCONNECT: + return "PCSC_DISCONNECT"; + case OP_PCSC_TRANSMIT: + return "PCSC_TRANSMIT"; + case OP_PCRE2_TEST: + return "PCRE2_TEST"; + case OP_PCRE2_MATCH: + return "PCRE2_MATCH"; + case OP_PCRE2_FINDALL: + return "PCRE2_FINDALL"; + case OP_OPENSSL_MD5: + return "OPENSSL_MD5"; + case OP_OPENSSL_SHA256: + return "OPENSSL_SHA256"; + case OP_OPENSSL_SHA512: + return "OPENSSL_SHA512"; + case OP_OPENSSL_RIPEMD160: + return "OPENSSL_RIPEMD160"; + case OP_LIBRESSL_MD5: + return "LIBRESSL_MD5"; + case OP_LIBRESSL_SHA256: + return "LIBRESSL_SHA256"; + case OP_LIBRESSL_SHA512: + return "LIBRESSL_SHA512"; + case OP_LIBRESSL_RIPEMD160: + return "LIBRESSL_RIPEMD160"; + case OP_INI_LOAD: + return "INI_LOAD"; + case OP_INI_FREE: + return "INI_FREE"; + case OP_INI_GET_STRING: + return "INI_GET_STRING"; + case OP_INI_GET_INT: + return "INI_GET_INT"; + case OP_INI_GET_DOUBLE: + return "INI_GET_DOUBLE"; + case OP_INI_GET_BOOL: + return "INI_GET_BOOL"; + case OP_INI_SET: + return "INI_SET"; + case OP_INI_UNSET: + return "INI_UNSET"; + case OP_INI_SAVE: + return "INI_SAVE"; + case OP_XML_PARSE: + return "XML_PARSE"; + case OP_XML_ROOT: + return "XML_ROOT"; + case OP_XML_NAME: + return "XML_NAME"; + case OP_XML_TEXT: + return "XML_TEXT"; + case OP_TK_EVAL: + return "TK_EVAL"; + case OP_TK_RESULT: + return "TK_RESULT"; + case OP_TK_LOOP: + return "TK_LOOP"; + case OP_TK_WM_TITLE: + return "TK_WM_TITLE"; + case OP_TK_LABEL: + return "TK_LABEL"; + case OP_TK_BUTTON: + return "TK_BUTTON"; + case OP_TK_PACK: + return "TK_PACK"; + case OP_FLOOR: + return "FLOOR"; + case OP_CEIL: + return "CEIL"; + case OP_TRUNC: + return "TRUNC"; + case OP_ROUND: + return "ROUND"; + case OP_SIN: + return "SIN"; + case OP_COS: + return "COS"; + case OP_TAN: + return "TAN"; + case OP_EXP: + return "EXP"; + case OP_LOG: + return "LOG"; + case OP_LOG10: + return "LOG10"; + case OP_SQRT: + return "SQRT"; + case OP_GCD: + return "GCD"; + case OP_LCM: + return "LCM"; + case OP_ISQRT: + return "ISQRT"; + case OP_SIGN: + return "SIGN"; + case OP_FMIN: + return "FMIN"; + case OP_FMAX: + return "FMAX"; + case OP_RUST_HELLO: + return "RUST_HELLO"; + case OP_RUST_HELLO_ARGS: + return "RUST_HELLO_ARGS"; + case OP_RUST_HELLO_ARGS_RETURN: + return "RUST_HELLO_ARGS_RETURN"; + case OP_RUST_GET_SP: + return "RUST_GET_SP"; + case OP_RUST_SET_EXIT: + return "RUST_SET_EXIT"; + default: + return "???"; + } } void bytecode_dump(const Bytecode *bc) { - if (!bc) { - printf("\n"); - return; - } - printf("Constants (%d):\n", bc->const_count); - for (int i = 0; i < bc->const_count; ++i) { - printf(" [%d] ", i); - print_value(&bc->constants[i]); - printf("\n"); - } - printf("Instructions (%d):\n", bc->instr_count); - for (int i = 0; i < bc->instr_count; ++i) { - const Instruction *ins = &bc->instructions[i]; - printf(" %3d: %-15s %d\n", i, opcode_name(ins->op), ins->operand); - } + if (!bc) { + printf("\n"); + return; + } + printf("Constants (%d):\n", bc->const_count); + for (int i = 0; i < bc->const_count; ++i) { + printf(" [%d] ", i); + print_value(&bc->constants[i]); + printf("\n"); + } + printf("Instructions (%d):\n", bc->instr_count); + for (int i = 0; i < bc->instr_count; ++i) { + const Instruction *ins = &bc->instructions[i]; + printf(" %3d: %-15s %d\n", i, opcode_name(ins->op), ins->operand); + } } diff --git a/src/bytecode.h b/src/bytecode.h index ac173af..7710e12 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -10,304 +10,304 @@ #ifndef FUN_BYTECODE_H #define FUN_BYTECODE_H -#include #include "value.h" +#include // VM opcodes typedef enum { - OP_NOP, - OP_LOAD_CONST, // operand = constant index - OP_LOAD_LOCAL, // operand = local slot index - OP_STORE_LOCAL, // operand = local slot index + OP_NOP, + OP_LOAD_CONST, // operand = constant index + OP_LOAD_LOCAL, // operand = local slot index + OP_STORE_LOCAL, // operand = local slot index - OP_LOAD_GLOBAL, // - OP_STORE_GLOBAL, // + OP_LOAD_GLOBAL, // + OP_STORE_GLOBAL, // - OP_ADD, // - OP_SUB, // - OP_MUL, // - OP_DIV, // + OP_ADD, // + OP_SUB, // + OP_MUL, // + OP_DIV, // - OP_LT, // a < b -> push 1/0 - OP_LTE, // a <= b -> push 1/0 - OP_GT, // a > b -> push 1/0 - OP_GTE, // a >= b -> push 1/0 - OP_EQ, // a == b -> push 1/0 - OP_NEQ, // a != b -> push 1/0 + OP_LT, // a < b -> push 1/0 + OP_LTE, // a <= b -> push 1/0 + OP_GT, // a > b -> push 1/0 + OP_GTE, // a >= b -> push 1/0 + OP_EQ, // a == b -> push 1/0 + OP_NEQ, // a != b -> push 1/0 - OP_POP, // discard top of stack - OP_JUMP, // unconditional jump - OP_JUMP_IF_FALSE, // jump if top of stack is false (0) + OP_POP, // discard top of stack + OP_JUMP, // unconditional jump + OP_JUMP_IF_FALSE, // jump if top of stack is false (0) - OP_CALL, // operand = arg count; pops fn + args and enters fn - OP_RETURN, // pop optional return value and return to caller + OP_CALL, // operand = arg count; pops fn + args and enters fn + OP_RETURN, // pop optional return value and return to caller - OP_PRINT, - OP_ECHO, // like print but does not append a newline; prints immediately - OP_HALT, + OP_PRINT, + OP_ECHO, // like print but does not append a newline; prints immediately + OP_HALT, - OP_LINE, // operand = source line number (debug marker) + OP_LINE, // operand = source line number (debug marker) - // add after existing opcodes - OP_MOD, // a % b - OP_AND, // logical AND - OP_OR, // logical OR - OP_NOT, // logical NOT + // add after existing opcodes + OP_MOD, // a % b + OP_AND, // logical AND + OP_OR, // logical OR + OP_NOT, // logical NOT - OP_DUP, // duplicate top of stack - OP_SWAP, // swap top two stack values + OP_DUP, // duplicate top of stack + OP_SWAP, // swap top two stack values - // arrays - OP_MAKE_ARRAY, // operand = element count; pops N values, pushes array - OP_INDEX_GET, // pops index, array; pushes element copy - OP_INDEX_SET, // pops value, index, array; sets in place + // arrays + OP_MAKE_ARRAY, // operand = element count; pops N values, pushes array + OP_INDEX_GET, // pops index, array; pushes element copy + OP_INDEX_SET, // pops value, index, array; sets in place - // array and builtin helpers - OP_LEN, // pops array or string; pushes length - OP_PUSH, // pops value, array; pushes new length - OP_APOP, // pops array; pushes removed element - OP_SET, // pops value, index, array; pushes value - OP_INSERT, // pops value, index, array; pushes new length - OP_REMOVE, // pops index, array; pushes removed element - OP_SLICE, // pops end, start, array; pushes new array + // array and builtin helpers + OP_LEN, // pops array or string; pushes length + OP_PUSH, // pops value, array; pushes new length + OP_APOP, // pops array; pushes removed element + OP_SET, // pops value, index, array; pushes value + OP_INSERT, // pops value, index, array; pushes new length + OP_REMOVE, // pops index, array; pushes removed element + 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 - OP_CAST, // pops typeName, value; pushes casted value (see vm/cast.c) - 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) + // conversions + OP_TO_NUMBER, // pops any; pushes int (parse strings) + OP_TO_STRING, // pops any; pushes string + OP_CAST, // pops typeName, value; pushes casted value (see vm/cast.c) + 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 - OP_JOIN, // pops sep, array; pushes string - OP_SUBSTR, // pops len, start, string; pushes string - OP_FIND, // pops needle, haystack; pushes int index or -1 + // string ops + OP_SPLIT, // pops sep, string; pushes array of strings + OP_JOIN, // pops sep, array; pushes string + OP_SUBSTR, // pops len, start, string; pushes string + OP_FIND, // pops needle, haystack; pushes int index or -1 - // regex ops (POSIX) - OP_REGEX_MATCH, // pops pattern, string; pushes 1/0 for full match - OP_REGEX_SEARCH, // pops pattern, string; pushes map {"match":str, "start":int, "end":int, "groups":array} - OP_REGEX_REPLACE, // pops repl, pattern, string; pushes string with global replacements + // regex ops (POSIX) + OP_REGEX_MATCH, // pops pattern, string; pushes 1/0 for full match + OP_REGEX_SEARCH, // pops pattern, string; pushes map {"match":str, "start":int, "end":int, "groups":array} + OP_REGEX_REPLACE, // pops repl, pattern, string; pushes string with global replacements - // array utils - OP_CONTAINS, // pops value, array; pushes 1/0 - OP_INDEX_OF, // pops value, array; pushes index or -1 - OP_CLEAR, // pops array; clears it; pushes nothing (we'll push 0) + // array utils + OP_CONTAINS, // pops value, array; pushes 1/0 + OP_INDEX_OF, // pops value, array; pushes index or -1 + OP_CLEAR, // pops array; clears it; pushes nothing (we'll push 0) - // iteration helpers - OP_ENUMERATE, // pops array; pushes array of [index, value] - OP_ZIP, // pops b, a; pushes array of [a[i], b[i]] + // iteration helpers + OP_ENUMERATE, // pops array; pushes array of [index, value] + OP_ZIP, // pops b, a; pushes array of [a[i], b[i]] - // math - OP_MIN, // pops b, a; pushes min(a,b) - OP_MAX, // pops b, a; pushes max(a,b) - OP_CLAMP, // pops hi, lo, x; pushes clamped - OP_ABS, // pops x; pushes |x| - OP_POW, // pops b, a; pushes a^b - OP_RANDOM_SEED, // pops seed; sets RNG seed; pushes nothing (we'll push 0) - OP_RANDOM_INT, // pops hi, lo; pushes random int in [lo, hi) + // math + OP_MIN, // pops b, a; pushes min(a,b) + OP_MAX, // pops b, a; pushes max(a,b) + OP_CLAMP, // pops hi, lo, x; pushes clamped + OP_ABS, // pops x; pushes |x| + OP_POW, // pops b, a; pushes a^b + OP_RANDOM_SEED, // pops seed; sets RNG seed; pushes nothing (we'll push 0) + OP_RANDOM_INT, // pops hi, lo; pushes random int in [lo, hi) - // maps - OP_MAKE_MAP, // operand = pair count; pops 2*n (key,value)..., pushes map - OP_KEYS, // pops map; pushes array of keys - OP_VALUES, // pops map; pushes array of values - OP_HAS_KEY, // pops key, map; pushes 1/0 + // maps + OP_MAKE_MAP, // operand = pair count; pops 2*n (key,value)..., pushes map + OP_KEYS, // pops map; pushes array of keys + OP_VALUES, // pops map; pushes array of values + OP_HAS_KEY, // pops key, map; pushes 1/0 - // file I/O - OP_READ_FILE, // pops path string; pushes content string (or "") - OP_WRITE_FILE, // pops data string, path string; pushes 1/0 + // file I/O + OP_READ_FILE, // pops path string; pushes content string (or "") + OP_WRITE_FILE, // pops data string, path string; pushes 1/0 - // OS - OP_ENV, // pops name string; pushes value string (or "") - OP_INPUT_LINE, // operand: 0=no prompt; 1=has prompt. Pops [prompt?]; pushes input string (no trailing newline) - OP_PROC_RUN, // pops command string; pushes map {"out": string, "code": int} - OP_PROC_SYSTEM, // pops command string; pushes exit code number - OP_TIME_NOW_MS, // pushes current wall-clock time in milliseconds since Unix epoch - OP_CLOCK_MONO_MS, // pushes monotonic clock in milliseconds (not wall time) - OP_DATE_FORMAT, // pops fmt string, ms epoch (int); pushes formatted date string using strftime - OP_ENV_ALL, // pushes map of all environment variables - OP_FUN_VERSION, // pushes version string + // OS + OP_ENV, // pops name string; pushes value string (or "") + OP_INPUT_LINE, // operand: 0=no prompt; 1=has prompt. Pops [prompt?]; pushes input string (no trailing newline) + OP_PROC_RUN, // pops command string; pushes map {"out": string, "code": int} + OP_PROC_SYSTEM, // pops command string; pushes exit code number + OP_TIME_NOW_MS, // pushes current wall-clock time in milliseconds since Unix epoch + OP_CLOCK_MONO_MS, // pushes monotonic clock in milliseconds (not wall time) + OP_DATE_FORMAT, // pops fmt string, ms epoch (int); pushes formatted date string using strftime + OP_ENV_ALL, // pushes map of all environment variables + OP_FUN_VERSION, // pushes version string - // Threads - OP_THREAD_SPAWN, // operand: 0=no args, 1=has args; pops [args?], fn; pushes thread id (int>0) - OP_THREAD_JOIN, // pops thread id; waits; pushes result value (or Nil) - OP_SLEEP_MS, // pops milliseconds; sleeps; pushes Nil (for statement POP safety) - OP_RANDOM_NUMBER, // pops length; pushes hex string of that length from OS RNG (hex-encoded) + // Threads + OP_THREAD_SPAWN, // operand: 0=no args, 1=has args; pops [args?], fn; pushes thread id (int>0) + OP_THREAD_JOIN, // pops thread id; waits; pushes result value (or Nil) + OP_SLEEP_MS, // pops milliseconds; sleeps; pushes Nil (for statement POP safety) + OP_RANDOM_NUMBER, // pops length; pushes hex string of that length from OS RNG (hex-encoded) - // Bitwise (32-bit) and shifts/rotates - OP_BAND, // pops b, a; pushes (uint32_t)(a & b) - OP_BOR, // pops b, a; pushes (uint32_t)(a | b) - OP_BXOR, // pops b, a; pushes (uint32_t)(a ^ b) - OP_BNOT, // pops a; pushes (uint32_t)(~a) - OP_SHL, // pops s, a; pushes (uint32_t)(a << (s&31)) - OP_SHR, // pops s, a; pushes (uint32_t)(a >> (s&31)) logical - OP_ROTL, // pops s, a; pushes rotl32(a, s) - OP_ROTR, // pops s, a; pushes rotr32(a, s) + // Bitwise (32-bit) and shifts/rotates + OP_BAND, // pops b, a; pushes (uint32_t)(a & b) + OP_BOR, // pops b, a; pushes (uint32_t)(a | b) + OP_BXOR, // pops b, a; pushes (uint32_t)(a ^ b) + OP_BNOT, // pops a; pushes (uint32_t)(~a) + OP_SHL, // pops s, a; pushes (uint32_t)(a << (s&31)) + OP_SHR, // pops s, a; pushes (uint32_t)(a >> (s&31)) logical + OP_ROTL, // pops s, a; pushes rotl32(a, s) + OP_ROTR, // pops s, a; pushes rotr32(a, s) - // JSON (json-c) - OP_JSON_PARSE, // pops text string; pushes value (or Nil on error) - OP_JSON_STRINGIFY, // pops pretty(bool), value; pushes string - OP_JSON_FROM_FILE, // pops path string; pushes value (or Nil) - OP_JSON_TO_FILE, // pops pretty(bool), value, path; pushes 1/0 + // JSON (json-c) + OP_JSON_PARSE, // pops text string; pushes value (or Nil on error) + OP_JSON_STRINGIFY, // pops pretty(bool), value; pushes string + OP_JSON_FROM_FILE, // pops path string; pushes value (or Nil) + OP_JSON_TO_FILE, // pops pretty(bool), value, path; pushes 1/0 - // CURL (libcurl) - OP_CURL_GET, // pops [headers map?], url; pushes response string (or "") - OP_CURL_POST, // pops [headers map?], body string, url; pushes response string (or "") - OP_CURL_DOWNLOAD, // pops [headers map?], path, url; pushes 1/0 + // CURL (libcurl) + OP_CURL_GET, // pops [headers map?], url; pushes response string (or "") + OP_CURL_POST, // pops [headers map?], body string, url; pushes response string (or "") + OP_CURL_DOWNLOAD, // pops [headers map?], path, url; pushes 1/0 - // SQLite (optional) - OP_SQLITE_OPEN, // pops path; pushes handle (>0) or 0 - OP_SQLITE_CLOSE, // pops handle; pushes Nil - OP_SQLITE_EXEC, // pops sql, handle; pushes sqlite rc (0=OK) - OP_SQLITE_QUERY, // pops sql, handle; pushes array + // SQLite (optional) + OP_SQLITE_OPEN, // pops path; pushes handle (>0) or 0 + OP_SQLITE_CLOSE, // pops handle; pushes Nil + OP_SQLITE_EXEC, // pops sql, handle; pushes sqlite rc (0=OK) + OP_SQLITE_QUERY, // pops sql, handle; pushes array - // libsql (optional, independent) - OP_LIBSQL_OPEN, // pops url/path; pushes handle (>0) or 0 - OP_LIBSQL_CLOSE, // pops handle; pushes Nil - OP_LIBSQL_EXEC, // pops sql, handle; pushes rc (0=OK) - OP_LIBSQL_QUERY, // pops sql, handle; pushes array + // libsql (optional, independent) + OP_LIBSQL_OPEN, // pops url/path; pushes handle (>0) or 0 + OP_LIBSQL_CLOSE, // pops handle; pushes Nil + OP_LIBSQL_EXEC, // pops sql, handle; pushes rc (0=OK) + OP_LIBSQL_QUERY, // pops sql, handle; pushes array - // PCSC (smart card) opcodes - OP_PCSC_ESTABLISH, // returns context id (>0) or 0 - OP_PCSC_RELEASE, // pops ctx id; returns 1/0 - OP_PCSC_LIST_READERS, // pops ctx id; returns array of reader names (possibly empty) - OP_PCSC_CONNECT, // pops reader, ctx id; returns handle id (>0) or 0 - OP_PCSC_DISCONNECT, // pops handle id; returns 1/0 - OP_PCSC_TRANSMIT, // pops apdu array, handle id; returns map {"data":[],"sw1":n,"sw2":n,"code":n} + // PCSC (smart card) opcodes + OP_PCSC_ESTABLISH, // returns context id (>0) or 0 + OP_PCSC_RELEASE, // pops ctx id; returns 1/0 + OP_PCSC_LIST_READERS, // pops ctx id; returns array of reader names (possibly empty) + OP_PCSC_CONNECT, // pops reader, ctx id; returns handle id (>0) or 0 + OP_PCSC_DISCONNECT, // pops handle id; returns 1/0 + OP_PCSC_TRANSMIT, // pops apdu array, handle id; returns map {"data":[],"sw1":n,"sw2":n,"code":n} - // PCRE2 regex ops (optional) - OP_PCRE2_TEST, // pops flags, text, pattern; pushes 1/0 - OP_PCRE2_MATCH, // pops flags, text, pattern; pushes match map or Nil - OP_PCRE2_FINDALL, // pops flags, text, pattern; pushes array of match maps + // PCRE2 regex ops (optional) + OP_PCRE2_TEST, // pops flags, text, pattern; pushes 1/0 + OP_PCRE2_MATCH, // pops flags, text, pattern; pushes match map or Nil + OP_PCRE2_FINDALL, // pops flags, text, pattern; pushes array of match maps - // OpenSSL (optional) - OP_OPENSSL_MD5, // pops data string; pushes md5 hex string - OP_OPENSSL_SHA256, // pops data string; pushes sha256 hex string - OP_OPENSSL_SHA512, // pops data string; pushes sha512 hex string - OP_OPENSSL_RIPEMD160, // pops data string; pushes ripemd160 hex string + // OpenSSL (optional) + OP_OPENSSL_MD5, // pops data string; pushes md5 hex string + OP_OPENSSL_SHA256, // pops data string; pushes sha256 hex string + OP_OPENSSL_SHA512, // pops data string; pushes sha512 hex string + OP_OPENSSL_RIPEMD160, // pops data string; pushes ripemd160 hex string - // LibreSSL (optional; same API as OpenSSL but different toggle) - OP_LIBRESSL_MD5, // pops data string; pushes md5 hex string - OP_LIBRESSL_SHA256, // pops data string; pushes sha256 hex string - OP_LIBRESSL_SHA512, // pops data string; pushes sha512 hex string - OP_LIBRESSL_RIPEMD160, // pops data string; pushes ripemd160 hex string + // LibreSSL (optional; same API as OpenSSL but different toggle) + OP_LIBRESSL_MD5, // pops data string; pushes md5 hex string + OP_LIBRESSL_SHA256, // pops data string; pushes sha256 hex string + OP_LIBRESSL_SHA512, // pops data string; pushes sha512 hex string + OP_LIBRESSL_RIPEMD160, // pops data string; pushes ripemd160 hex string - // INI (iniparser 4.2.6) optional - OP_INI_LOAD, // pops path; pushes handle (>0) or 0 - OP_INI_FREE, // pops handle; pushes 1/0 - OP_INI_GET_STRING, // pops def, key, section, handle; pushes string - OP_INI_GET_INT, // pops def, key, section, handle; pushes int - OP_INI_GET_DOUBLE, // pops def, key, section, handle; pushes float - OP_INI_GET_BOOL, // pops def, key, section, handle; pushes int (0/1) - OP_INI_SET, // pops value, key, section, handle; pushes 1/0 - OP_INI_UNSET, // pops key, section, handle; pushes 1/0 - OP_INI_SAVE, // pops path, handle; pushes 1/0 + // INI (iniparser 4.2.6) optional + OP_INI_LOAD, // pops path; pushes handle (>0) or 0 + OP_INI_FREE, // pops handle; pushes 1/0 + OP_INI_GET_STRING, // pops def, key, section, handle; pushes string + OP_INI_GET_INT, // pops def, key, section, handle; pushes int + OP_INI_GET_DOUBLE, // pops def, key, section, handle; pushes float + OP_INI_GET_BOOL, // pops def, key, section, handle; pushes int (0/1) + OP_INI_SET, // pops value, key, section, handle; pushes 1/0 + OP_INI_UNSET, // pops key, section, handle; pushes 1/0 + OP_INI_SAVE, // pops path, handle; pushes 1/0 - // XML (libxml2) optional minimal API - OP_XML_PARSE, // pops text string; pushes doc handle (>0) or 0 - OP_XML_ROOT, // pops doc handle; pushes node handle (>0) or 0 - OP_XML_NAME, // pops node handle; pushes string (node name) - OP_XML_TEXT, // pops node handle; pushes string (node text) + // XML (libxml2) optional minimal API + OP_XML_PARSE, // pops text string; pushes doc handle (>0) or 0 + OP_XML_ROOT, // pops doc handle; pushes node handle (>0) or 0 + OP_XML_NAME, // pops node handle; pushes string (node name) + OP_XML_TEXT, // pops node handle; pushes string (node text) - // Sockets (UNIX platforms) - OP_SOCK_TCP_LISTEN, // pops backlog, port; returns listen fd (>0) or 0 - OP_SOCK_TCP_ACCEPT, // pops listen fd; returns client fd (>0) or 0 - OP_SOCK_TCP_CONNECT, // pops port, host; returns fd (>0) or 0 - OP_SOCK_SEND, // pops data string, fd; returns bytes sent (>=0) or -1 - OP_SOCK_RECV, // pops maxlen, fd; returns data string ("" on EOF/error) - OP_SOCK_CLOSE, // pops fd; returns 1/0 - OP_SOCK_UNIX_LISTEN, // pops backlog, path; returns listen fd (>0) or 0 - OP_SOCK_UNIX_CONNECT, // pops path; returns fd (>0) or 0 + // Sockets (UNIX platforms) + OP_SOCK_TCP_LISTEN, // pops backlog, port; returns listen fd (>0) or 0 + OP_SOCK_TCP_ACCEPT, // pops listen fd; returns client fd (>0) or 0 + OP_SOCK_TCP_CONNECT, // pops port, host; returns fd (>0) or 0 + OP_SOCK_SEND, // pops data string, fd; returns bytes sent (>=0) or -1 + OP_SOCK_RECV, // pops maxlen, fd; returns data string ("" on EOF/error) + OP_SOCK_CLOSE, // pops fd; returns 1/0 + OP_SOCK_UNIX_LISTEN, // pops backlog, path; returns listen fd (>0) or 0 + OP_SOCK_UNIX_CONNECT, // pops path; returns fd (>0) or 0 - // process control - OP_EXIT, // pops code (or uses operand) and terminates script with exit code + // process control + OP_EXIT, // pops code (or uses operand) and terminates script with exit code - // OS additions - OP_OS_LIST_DIR, // pops path string; pushes array of strings + // OS additions + OP_OS_LIST_DIR, // pops path string; pushes array of strings - // Tk additions - OP_TK_BIND, // pops command, event, id; binds event to command + // Tk additions + OP_TK_BIND, // pops command, event, id; binds event to command - // Serial communication (termios) - OP_SERIAL_OPEN, // pops baud_rate (int), path (string); returns fd (int) or 0 - OP_SERIAL_CONFIG, // pops flow_control, stop_bits, parity, data_bits, fd; returns 1/0 - OP_SERIAL_SEND, // pops data (string), fd; returns bytes sent (int) - OP_SERIAL_RECV, // pops maxlen (int), fd; returns data (string) - OP_SERIAL_CLOSE, // pops fd; returns 1/0 + // Serial communication (termios) + OP_SERIAL_OPEN, // pops baud_rate (int), path (string); returns fd (int) or 0 + OP_SERIAL_CONFIG, // pops flow_control, stop_bits, parity, data_bits, fd; returns 1/0 + OP_SERIAL_SEND, // pops data (string), fd; returns bytes sent (int) + OP_SERIAL_RECV, // pops maxlen (int), fd; returns data (string) + OP_SERIAL_CLOSE, // pops fd; returns 1/0 - // Tk (Tcl/Tk) optional minimal API - OP_TK_EVAL, // pops script string; pushes int rc (0 = OK) - OP_TK_RESULT, // pushes string: last Tcl result - OP_TK_LOOP, // enters Tk event loop; pushes Nil when done - OP_TK_WM_TITLE, // pops title string; sets window title; pushes rc - OP_TK_LABEL, // pops text, id; creates/updates label .id; pushes rc - OP_TK_BUTTON, // pops text, id; creates/updates button .id; pushes rc - OP_TK_PACK, // pops id; packs .id; pushes rc + // Tk (Tcl/Tk) optional minimal API + OP_TK_EVAL, // pops script string; pushes int rc (0 = OK) + OP_TK_RESULT, // pushes string: last Tcl result + OP_TK_LOOP, // enters Tk event loop; pushes Nil when done + OP_TK_WM_TITLE, // pops title string; sets window title; pushes rc + OP_TK_LABEL, // pops text, id; creates/updates label .id; pushes rc + OP_TK_BUTTON, // pops text, id; creates/updates button .id; pushes rc + OP_TK_PACK, // pops id; packs .id; pushes rc - // exceptions (minimal) - OP_TRY_PUSH, // operand = handler ip; push handler onto try-stack - OP_TRY_POP, // pop current handler - OP_THROW, // pops error value; if handler -> jump to it (push err), else print and terminate + // exceptions (minimal) + OP_TRY_PUSH, // operand = handler ip; push handler onto try-stack + OP_TRY_POP, // pop current handler + OP_THROW, // pops error value; if handler -> jump to it (push err), else print and terminate - // C99 math.h rounding family (float-aware) - OP_FLOOR, // pops x (int/float); pushes floor(x) (int if integral else float) - OP_CEIL, // pops x (int/float); pushes ceil(x) (int if integral else float) - OP_TRUNC, // pops x (int/float); pushes trunc(x) (int if integral else float) - OP_ROUND, // pops x (int/float); pushes round(x) (half away from zero) + // C99 math.h rounding family (float-aware) + OP_FLOOR, // pops x (int/float); pushes floor(x) (int if integral else float) + OP_CEIL, // pops x (int/float); pushes ceil(x) (int if integral else float) + OP_TRUNC, // pops x (int/float); pushes trunc(x) (int if integral else float) + OP_ROUND, // pops x (int/float); pushes round(x) (half away from zero) - // C99 math.h transcendentals (float-aware) - OP_SIN, // pops x (int/float); pushes sin(x) - OP_COS, // pops x (int/float); pushes cos(x) - OP_TAN, // pops x (int/float); pushes tan(x) - OP_EXP, // pops x (int/float); pushes exp(x) - OP_LOG, // pops x (int/float); pushes natural log ln(x) - OP_LOG10, // pops x (int/float); pushes log10(x) - OP_SQRT, // pops x (int/float); pushes sqrt(x) + // C99 math.h transcendentals (float-aware) + OP_SIN, // pops x (int/float); pushes sin(x) + OP_COS, // pops x (int/float); pushes cos(x) + OP_TAN, // pops x (int/float); pushes tan(x) + OP_EXP, // pops x (int/float); pushes exp(x) + OP_LOG, // pops x (int/float); pushes natural log ln(x) + OP_LOG10, // pops x (int/float); pushes log10(x) + OP_SQRT, // pops x (int/float); pushes sqrt(x) - // Integer math helpers - OP_GCD, // pops b, a; pushes gcd(|a|,|b|) - OP_LCM, // pops b, a; pushes lcm(|a|,|b|) (0 if either is 0) - OP_ISQRT, // pops x; pushes floor(sqrt(max(0,x))) for integers - OP_SIGN, // pops x; pushes -1, 0, or 1 depending on the sign + // Integer math helpers + OP_GCD, // pops b, a; pushes gcd(|a|,|b|) + OP_LCM, // pops b, a; pushes lcm(|a|,|b|) (0 if either is 0) + OP_ISQRT, // pops x; pushes floor(sqrt(max(0,x))) for integers + OP_SIGN, // pops x; pushes -1, 0, or 1 depending on the sign - // Min/Max variants (float-aware, C99 semantics) - OP_FMIN, // pops b, a (int/float); pushes fmin(a,b) (NaN handling per C99) - OP_FMAX, // pops b, a (int/float); pushes fmax(a,b) (NaN handling per C99) + // Min/Max variants (float-aware, C99 semantics) + OP_FMIN, // pops b, a (int/float); pushes fmin(a,b) (NaN handling per C99) + OP_FMAX, // pops b, a (int/float); pushes fmax(a,b) (NaN handling per C99) - // Rust FFI demo opcode(s) - OP_RUST_HELLO, // pushes string returned from Rust (hello world) - OP_RUST_HELLO_ARGS, // pops message string; prints it via Rust; pushes Nil - OP_RUST_HELLO_ARGS_RETURN, // pops message string; returns it from Rust without printing; pushes returned string - OP_RUST_GET_SP, // pushes current VM stack pointer (via Rust reading VM memory) - OP_RUST_SET_EXIT, // pops int and sets VM exit_code (via Rust writing VM memory) + // Rust FFI demo opcode(s) + OP_RUST_HELLO, // pushes string returned from Rust (hello world) + OP_RUST_HELLO_ARGS, // pops message string; prints it via Rust; pushes Nil + OP_RUST_HELLO_ARGS_RETURN, // pops message string; returns it from Rust without printing; pushes returned string + OP_RUST_GET_SP, // pushes current VM stack pointer (via Rust reading VM memory) + OP_RUST_SET_EXIT, // pops int and sets VM exit_code (via Rust writing VM memory) - // C++ demo opcode(s) - OP_CPP_ADD, // pops b, a; pushes (a + b) + // C++ demo opcode(s) + OP_CPP_ADD, // pops b, a; pushes (a + b) - /* Notcurses TUI (optional) */ - OP_NC_INIT, // initializes Notcurses; returns 1 on success, 0 on failure - OP_NC_SHUTDOWN, // shuts down Notcurses; returns 0 - OP_NC_CLEAR, // clears screen/plane; returns 0 - OP_NC_DRAW_TEXT, // pops text, x, y; draws; returns 0 - OP_NC_GETCH // pops timeout_ms; returns codepoint or -1 on timeout/error + /* Notcurses TUI (optional) */ + OP_NC_INIT, // initializes Notcurses; returns 1 on success, 0 on failure + OP_NC_SHUTDOWN, // shuts down Notcurses; returns 0 + OP_NC_CLEAR, // clears screen/plane; returns 0 + OP_NC_DRAW_TEXT, // pops text, x, y; draws; returns 0 + OP_NC_GETCH // pops timeout_ms; returns codepoint or -1 on timeout/error } OpCode; typedef struct { - OpCode op; - int32_t operand; + OpCode op; + int32_t operand; } Instruction; typedef struct Bytecode { - Instruction *instructions; - int instr_count; + Instruction *instructions; + int instr_count; - Value *constants; - int const_count; + Value *constants; + int const_count; - /* debug metadata */ - const char *name; /* function or module name (optional) */ - const char *source_file; /* originating source filename (optional) */ + /* debug metadata */ + const char *name; /* function or module name (optional) */ + const char *source_file; /* originating source filename (optional) */ } Bytecode; // constructors / manipulation diff --git a/src/external/curl.c b/src/external/curl.c index 16dfea8..bf400ce 100644 --- a/src/external/curl.c +++ b/src/external/curl.c @@ -12,18 +12,23 @@ /* Ensure libcurl headers and helpers are defined at file scope (not inside vm_run) */ #ifdef FUN_WITH_CURL #include -typedef struct { char *d; size_t n; } FunCurlBuf; +typedef struct { + char *d; + size_t n; +} FunCurlBuf; static size_t fun_curl_write_cb(void *ptr, size_t sz, size_t nm, void *ud) { size_t add = sz * nm; - FunCurlBuf *b = (FunCurlBuf*)ud; - char *p = (char*)realloc(b->d, b->n + add + 1); + FunCurlBuf *b = (FunCurlBuf *)ud; + char *p = (char *)realloc(b->d, b->n + add + 1); if (!p) return 0; memcpy(p + b->n, ptr, add); - b->d = p; b->n += add; b->d[b->n] = '\0'; + b->d = p; + b->n += add; + b->d[b->n] = '\0'; return add; } static size_t fun_curl_file_write_cb(void *ptr, size_t sz, size_t nm, void *ud) { - FILE *f = (FILE*)ud; + FILE *f = (FILE *)ud; return fwrite(ptr, sz, nm, f); } #endif diff --git a/src/external/ini.c b/src/external/ini.c index 974d542..6dc40d3 100644 --- a/src/external/ini.c +++ b/src/external/ini.c @@ -11,18 +11,18 @@ #ifdef FUN_WITH_INI #if defined(__has_include) -# if __has_include() -# include -# include -# elif __has_include() -# include -# include -# else -# error "iniparser headers not found" -# endif +#if __has_include() +#include +#include +#elif __has_include() +#include +#include #else -# include -# include +#error "iniparser headers not found" +#endif +#else +#include +#include #endif #include "vm/ini/handles.h" #endif diff --git a/src/external/json.c b/src/external/json.c index 331fec7..a9f6685 100644 --- a/src/external/json.c +++ b/src/external/json.c @@ -9,7 +9,7 @@ * Added: 2025-12-11 (2025-12-11 migrated from src/vm.c) */ - /* json-c helpers and VM opcode cases (included from vm.c) */ +/* json-c helpers and VM opcode cases (included from vm.c) */ #ifdef FUN_WITH_JSON #include "value.h" @@ -20,88 +20,99 @@ /* --- Conversion helpers between json-c and Fun Value --- */ static Value json_to_fun(json_object *j) { - if (!j) return make_nil(); - enum json_type t = json_object_get_type(j); - switch (t) { - case json_type_null: return make_nil(); - case json_type_boolean: return make_bool(json_object_get_boolean(j)); - case json_type_double: return make_float(json_object_get_double(j)); - case json_type_int: return make_int((int64_t)json_object_get_int64(j)); - case json_type_string: return make_string(json_object_get_string(j)); - case json_type_array: { - size_t n = json_object_array_length(j); - if (n == 0) { - return make_array_from_values(NULL, 0); - } - Value *vals = (Value*)malloc(sizeof(Value) * n); - if (!vals) return make_array_from_values(NULL, 0); - for (size_t i = 0; i < n; ++i) { - json_object *item = json_object_array_get_idx(j, (int)i); - vals[i] = json_to_fun(item); - } - Value arr = make_array_from_values(vals, (int)n); - for (size_t i = 0; i < n; ++i) free_value(vals[i]); - free(vals); - return arr; - } - case json_type_object: { - Value map = make_map_empty(); - json_object_object_foreach(j, key, val) { - (void)map_set(&map, key, json_to_fun(val)); - } - return map; - } - default: - return make_nil(); + if (!j) return make_nil(); + enum json_type t = json_object_get_type(j); + switch (t) { + case json_type_null: + return make_nil(); + case json_type_boolean: + return make_bool(json_object_get_boolean(j)); + case json_type_double: + return make_float(json_object_get_double(j)); + case json_type_int: + return make_int((int64_t)json_object_get_int64(j)); + case json_type_string: + return make_string(json_object_get_string(j)); + case json_type_array: { + size_t n = json_object_array_length(j); + if (n == 0) { + return make_array_from_values(NULL, 0); } + Value *vals = (Value *)malloc(sizeof(Value) * n); + if (!vals) return make_array_from_values(NULL, 0); + for (size_t i = 0; i < n; ++i) { + json_object *item = json_object_array_get_idx(j, (int)i); + vals[i] = json_to_fun(item); + } + Value arr = make_array_from_values(vals, (int)n); + for (size_t i = 0; i < n; ++i) + free_value(vals[i]); + free(vals); + return arr; + } + case json_type_object: { + Value map = make_map_empty(); + json_object_object_foreach(j, key, val) { + (void)map_set(&map, key, json_to_fun(val)); + } + return map; + } + default: + return make_nil(); + } } -static json_object* fun_to_json(const Value *v) { - switch (v->type) { - case VAL_NIL: return json_object_new_null(); - case VAL_BOOL: return json_object_new_boolean(v->i ? 1 : 0); - case VAL_INT: return json_object_new_int64(v->i); - case VAL_FLOAT: return json_object_new_double(v->d); - case VAL_STRING: return json_object_new_string(v->s ? v->s : ""); - case VAL_ARRAY: { - json_object *arr = json_object_new_array(); - int n = array_length(v); - for (int i = 0; i < n; ++i) { - Value item; - if (array_get_copy(v, i, &item)) { - json_object_array_add(arr, fun_to_json(&item)); - free_value(item); - } else { - json_object_array_add(arr, json_object_new_null()); - } - } - return arr; - } - case VAL_MAP: { - json_object *obj = json_object_new_object(); - /* We don't have an iterator API; use keys() helper */ - Value keys = map_keys_array(v); - int kn = array_length(&keys); - for (int i = 0; i < kn; ++i) { - Value k; - if (!array_get_copy(&keys, i, &k)) continue; - if (k.type == VAL_STRING && k.s) { - Value val; - if (map_get_copy(v, k.s, &val)) { - json_object_object_add(obj, k.s, fun_to_json(&val)); - free_value(val); - } else { - json_object_object_add(obj, k.s, json_object_new_null()); - } - } - free_value(k); - } - free_value(keys); - return obj; - } - default: - /* Fallback: stringify unsupported types */ - return json_object_new_string(""); +static json_object *fun_to_json(const Value *v) { + switch (v->type) { + case VAL_NIL: + return json_object_new_null(); + case VAL_BOOL: + return json_object_new_boolean(v->i ? 1 : 0); + case VAL_INT: + return json_object_new_int64(v->i); + case VAL_FLOAT: + return json_object_new_double(v->d); + case VAL_STRING: + return json_object_new_string(v->s ? v->s : ""); + case VAL_ARRAY: { + json_object *arr = json_object_new_array(); + int n = array_length(v); + for (int i = 0; i < n; ++i) { + Value item; + if (array_get_copy(v, i, &item)) { + json_object_array_add(arr, fun_to_json(&item)); + free_value(item); + } else { + json_object_array_add(arr, json_object_new_null()); + } } + return arr; + } + case VAL_MAP: { + json_object *obj = json_object_new_object(); + /* We don't have an iterator API; use keys() helper */ + Value keys = map_keys_array(v); + int kn = array_length(&keys); + for (int i = 0; i < kn; ++i) { + Value k; + if (!array_get_copy(&keys, i, &k)) continue; + if (k.type == VAL_STRING && k.s) { + Value val; + if (map_get_copy(v, k.s, &val)) { + json_object_object_add(obj, k.s, fun_to_json(&val)); + free_value(val); + } else { + json_object_object_add(obj, k.s, json_object_new_null()); + } + } + free_value(k); + } + free_value(keys); + return obj; + } + default: + /* Fallback: stringify unsupported types */ + return json_object_new_string(""); + } } #endif diff --git a/src/external/libressl.c b/src/external/libressl.c index 78ad66a..2764157 100644 --- a/src/external/libressl.c +++ b/src/external/libressl.c @@ -1,5 +1,5 @@ /* -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2026 Johannes Findeisen @@ -33,140 +33,164 @@ int EVP_MD_get_size(const EVP_MD *md); /* Compute MD5 hex of input buffer; returns malloc'ed C string (lowercase hex). */ static char *fun_libressl_md5_hex(const unsigned char *data, size_t len) { - static const char hexdig[] = "0123456789abcdef"; - if (!data && len != 0) return NULL; + static const char hexdig[] = "0123456789abcdef"; + if (!data && len != 0) return NULL; #ifdef FUN_WITH_LIBRESSL - const EVP_MD *md = EVP_md5(); - if (!md) return NULL; - int dlen = EVP_MD_get_size(md); - if (dlen <= 0) return NULL; - unsigned char *digest = (unsigned char*)malloc((size_t)dlen); - if (!digest) return NULL; - unsigned int out_len = 0; - int ok; - if (len == 0) { - ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); - } else { - ok = EVP_Digest(data, len, digest, &out_len, md, NULL); - } - if (ok != 1 || (int)out_len != dlen) { free(digest); return NULL; } - char *hex = (char*)malloc((size_t)dlen * 2 + 1); - if (!hex) { free(digest); return NULL; } - for (int i = 0; i < dlen; ++i) { - hex[2*i] = hexdig[(digest[i] >> 4) & 0xF]; - hex[2*i+1] = hexdig[digest[i] & 0xF]; - } - hex[dlen * 2] = '\0'; + const EVP_MD *md = EVP_md5(); + if (!md) return NULL; + int dlen = EVP_MD_get_size(md); + if (dlen <= 0) return NULL; + unsigned char *digest = (unsigned char *)malloc((size_t)dlen); + if (!digest) return NULL; + unsigned int out_len = 0; + int ok; + if (len == 0) { + ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); + } else { + ok = EVP_Digest(data, len, digest, &out_len, md, NULL); + } + if (ok != 1 || (int)out_len != dlen) { free(digest); - return hex; + return NULL; + } + char *hex = (char *)malloc((size_t)dlen * 2 + 1); + if (!hex) { + free(digest); + return NULL; + } + for (int i = 0; i < dlen; ++i) { + hex[2 * i] = hexdig[(digest[i] >> 4) & 0xF]; + hex[2 * i + 1] = hexdig[digest[i] & 0xF]; + } + hex[dlen * 2] = '\0'; + free(digest); + return hex; #else - char *hex = (char*)malloc(1); - if (hex) hex[0] = '\0'; - return hex; + char *hex = (char *)malloc(1); + if (hex) hex[0] = '\0'; + return hex; #endif } /* Compute SHA-256 hex of input buffer; returns malloc'ed C string (lowercase hex). */ static char *fun_libressl_sha256_hex(const unsigned char *data, size_t len) { - static const char hexdig[] = "0123456789abcdef"; - if (!data && len != 0) return NULL; + static const char hexdig[] = "0123456789abcdef"; + if (!data && len != 0) return NULL; #ifdef FUN_WITH_LIBRESSL - const EVP_MD *md = EVP_sha256(); - if (!md) return NULL; - int dlen = EVP_MD_get_size(md); - if (dlen <= 0) return NULL; - unsigned char *digest = (unsigned char*)malloc((size_t)dlen); - if (!digest) return NULL; - unsigned int out_len = 0; - int ok; - if (len == 0) { - ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); - } else { - ok = EVP_Digest(data, len, digest, &out_len, md, NULL); - } - if (ok != 1 || (int)out_len != dlen) { free(digest); return NULL; } - char *hex = (char*)malloc((size_t)dlen * 2 + 1); - if (!hex) { free(digest); return NULL; } - for (int i = 0; i < dlen; ++i) { - hex[2*i] = hexdig[(digest[i] >> 4) & 0xF]; - hex[2*i+1] = hexdig[digest[i] & 0xF]; - } - hex[dlen * 2] = '\0'; + const EVP_MD *md = EVP_sha256(); + if (!md) return NULL; + int dlen = EVP_MD_get_size(md); + if (dlen <= 0) return NULL; + unsigned char *digest = (unsigned char *)malloc((size_t)dlen); + if (!digest) return NULL; + unsigned int out_len = 0; + int ok; + if (len == 0) { + ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); + } else { + ok = EVP_Digest(data, len, digest, &out_len, md, NULL); + } + if (ok != 1 || (int)out_len != dlen) { free(digest); - return hex; + return NULL; + } + char *hex = (char *)malloc((size_t)dlen * 2 + 1); + if (!hex) { + free(digest); + return NULL; + } + for (int i = 0; i < dlen; ++i) { + hex[2 * i] = hexdig[(digest[i] >> 4) & 0xF]; + hex[2 * i + 1] = hexdig[digest[i] & 0xF]; + } + hex[dlen * 2] = '\0'; + free(digest); + return hex; #else - char *hex = (char*)malloc(1); - if (hex) hex[0] = '\0'; - return hex; + char *hex = (char *)malloc(1); + if (hex) hex[0] = '\0'; + return hex; #endif } /* Compute SHA-512 hex of input buffer; returns malloc'ed C string (lowercase hex). */ static char *fun_libressl_sha512_hex(const unsigned char *data, size_t len) { - static const char hexdig[] = "0123456789abcdef"; - if (!data && len != 0) return NULL; + static const char hexdig[] = "0123456789abcdef"; + if (!data && len != 0) return NULL; #ifdef FUN_WITH_LIBRESSL - const EVP_MD *md = EVP_sha512(); - if (!md) return NULL; - int dlen = EVP_MD_get_size(md); - if (dlen <= 0) return NULL; - unsigned char *digest = (unsigned char*)malloc((size_t)dlen); - if (!digest) return NULL; - unsigned int out_len = 0; - int ok; - if (len == 0) { - ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); - } else { - ok = EVP_Digest(data, len, digest, &out_len, md, NULL); - } - if (ok != 1 || (int)out_len != dlen) { free(digest); return NULL; } - char *hex = (char*)malloc((size_t)dlen * 2 + 1); - if (!hex) { free(digest); return NULL; } - for (int i = 0; i < dlen; ++i) { - hex[2*i] = hexdig[(digest[i] >> 4) & 0xF]; - hex[2*i+1] = hexdig[digest[i] & 0xF]; - } - hex[dlen * 2] = '\0'; + const EVP_MD *md = EVP_sha512(); + if (!md) return NULL; + int dlen = EVP_MD_get_size(md); + if (dlen <= 0) return NULL; + unsigned char *digest = (unsigned char *)malloc((size_t)dlen); + if (!digest) return NULL; + unsigned int out_len = 0; + int ok; + if (len == 0) { + ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); + } else { + ok = EVP_Digest(data, len, digest, &out_len, md, NULL); + } + if (ok != 1 || (int)out_len != dlen) { free(digest); - return hex; + return NULL; + } + char *hex = (char *)malloc((size_t)dlen * 2 + 1); + if (!hex) { + free(digest); + return NULL; + } + for (int i = 0; i < dlen; ++i) { + hex[2 * i] = hexdig[(digest[i] >> 4) & 0xF]; + hex[2 * i + 1] = hexdig[digest[i] & 0xF]; + } + hex[dlen * 2] = '\0'; + free(digest); + return hex; #else - char *hex = (char*)malloc(1); - if (hex) hex[0] = '\0'; - return hex; + char *hex = (char *)malloc(1); + if (hex) hex[0] = '\0'; + return hex; #endif } /* Compute RIPEMD-160 hex of input buffer; returns malloc'ed C string (lowercase hex). */ static char *fun_libressl_ripemd160_hex(const unsigned char *data, size_t len) { - static const char hexdig[] = "0123456789abcdef"; - if (!data && len != 0) return NULL; + static const char hexdig[] = "0123456789abcdef"; + if (!data && len != 0) return NULL; #ifdef FUN_WITH_LIBRESSL - const EVP_MD *md = EVP_ripemd160(); - if (!md) return NULL; - int dlen = EVP_MD_get_size(md); - if (dlen <= 0) return NULL; - unsigned char *digest = (unsigned char*)malloc((size_t)dlen); - if (!digest) return NULL; - unsigned int out_len = 0; - int ok; - if (len == 0) { - ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); - } else { - ok = EVP_Digest(data, len, digest, &out_len, md, NULL); - } - if (ok != 1 || (int)out_len != dlen) { free(digest); return NULL; } - char *hex = (char*)malloc((size_t)dlen * 2 + 1); - if (!hex) { free(digest); return NULL; } - for (int i = 0; i < dlen; ++i) { - hex[2*i] = hexdig[(digest[i] >> 4) & 0xF]; - hex[2*i+1] = hexdig[digest[i] & 0xF]; - } - hex[dlen * 2] = '\0'; + const EVP_MD *md = EVP_ripemd160(); + if (!md) return NULL; + int dlen = EVP_MD_get_size(md); + if (dlen <= 0) return NULL; + unsigned char *digest = (unsigned char *)malloc((size_t)dlen); + if (!digest) return NULL; + unsigned int out_len = 0; + int ok; + if (len == 0) { + ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); + } else { + ok = EVP_Digest(data, len, digest, &out_len, md, NULL); + } + if (ok != 1 || (int)out_len != dlen) { free(digest); - return hex; + return NULL; + } + char *hex = (char *)malloc((size_t)dlen * 2 + 1); + if (!hex) { + free(digest); + return NULL; + } + for (int i = 0; i < dlen; ++i) { + hex[2 * i] = hexdig[(digest[i] >> 4) & 0xF]; + hex[2 * i + 1] = hexdig[digest[i] & 0xF]; + } + hex[dlen * 2] = '\0'; + free(digest); + return hex; #else - char *hex = (char*)malloc(1); - if (hex) hex[0] = '\0'; - return hex; + char *hex = (char *)malloc(1); + if (hex) hex[0] = '\0'; + return hex; #endif } diff --git a/src/external/libsql.c b/src/external/libsql.c index 898d286..e133d7d 100644 --- a/src/external/libsql.c +++ b/src/external/libsql.c @@ -10,46 +10,49 @@ */ #ifdef FUN_WITH_LIBSQL +#include /* libsql provides a sqlite3-compatible C API */ +#include #include #include -#include -#include /* libsql provides a sqlite3-compatible C API */ typedef struct LibSqlHandle { - int id; - sqlite3 *db; - struct LibSqlHandle *next; + int id; + sqlite3 *db; + struct LibSqlHandle *next; } LibSqlHandle; static LibSqlHandle *g_libsql_handles = NULL; static int g_libsql_next_id = 1; static LibSqlHandle *libsql_reg_add(sqlite3 *db) { - LibSqlHandle *h = (LibSqlHandle*)malloc(sizeof(LibSqlHandle)); - if (!h) return NULL; - h->id = g_libsql_next_id++; - h->db = db; - h->next = g_libsql_handles; - g_libsql_handles = h; - return h; + LibSqlHandle *h = (LibSqlHandle *)malloc(sizeof(LibSqlHandle)); + if (!h) return NULL; + h->id = g_libsql_next_id++; + h->db = db; + h->next = g_libsql_handles; + g_libsql_handles = h; + return h; } static LibSqlHandle *libsql_reg_get(int id) { - LibSqlHandle *p = g_libsql_handles; - while (p) { if (p->id == id) return p; p = p->next; } - return NULL; + LibSqlHandle *p = g_libsql_handles; + while (p) { + if (p->id == id) return p; + p = p->next; + } + return NULL; } static void libsql_reg_del(int id) { - LibSqlHandle **pp = &g_libsql_handles; - while (*pp) { - if ((*pp)->id == id) { - LibSqlHandle *dead = *pp; - *pp = (*pp)->next; - free(dead); - return; - } - pp = &((*pp)->next); + LibSqlHandle **pp = &g_libsql_handles; + while (*pp) { + if ((*pp)->id == id) { + LibSqlHandle *dead = *pp; + *pp = (*pp)->next; + free(dead); + return; } + pp = &((*pp)->next); + } } #endif diff --git a/src/external/openssl.c b/src/external/openssl.c index ac0301e..d2c6988 100644 --- a/src/external/openssl.c +++ b/src/external/openssl.c @@ -1,5 +1,5 @@ /* -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2026 Johannes Findeisen @@ -9,7 +9,7 @@ * Added: 2026-02-19 */ - /* +/* * OpenSSL integration helpers (MD5, RIPEMD-160, SHA-256, SHA-512) */ @@ -28,109 +28,127 @@ int EVP_MD_get_size(const EVP_MD *md); * returns an allocated empty string ("") to keep behavior consistent with * other optional extensions. */ static char *fun_openssl_md5_hex(const unsigned char *data, size_t len) { - static const char hexdig[] = "0123456789abcdef"; - if (!data && len != 0) return NULL; + static const char hexdig[] = "0123456789abcdef"; + if (!data && len != 0) return NULL; #ifdef FUN_WITH_OPENSSL - const EVP_MD *md = EVP_md5(); - if (!md) return NULL; - int dlen = EVP_MD_get_size(md); - if (dlen <= 0) return NULL; - unsigned char *digest = (unsigned char*)malloc((size_t)dlen); - if (!digest) return NULL; - unsigned int out_len = 0; - int ok; - if (len == 0) { - // EVP_Digest handles zero-length fine as well - ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); - } else { - ok = EVP_Digest(data, len, digest, &out_len, md, NULL); - } - if (ok != 1 || (int)out_len != dlen) { free(digest); return NULL; } - char *hex = (char*)malloc((size_t)dlen * 2 + 1); - if (!hex) { free(digest); return NULL; } - for (int i = 0; i < dlen; ++i) { - hex[2*i] = hexdig[(digest[i] >> 4) & 0xF]; - hex[2*i+1] = hexdig[digest[i] & 0xF]; - } - hex[dlen * 2] = '\0'; + const EVP_MD *md = EVP_md5(); + if (!md) return NULL; + int dlen = EVP_MD_get_size(md); + if (dlen <= 0) return NULL; + unsigned char *digest = (unsigned char *)malloc((size_t)dlen); + if (!digest) return NULL; + unsigned int out_len = 0; + int ok; + if (len == 0) { + // EVP_Digest handles zero-length fine as well + ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); + } else { + ok = EVP_Digest(data, len, digest, &out_len, md, NULL); + } + if (ok != 1 || (int)out_len != dlen) { free(digest); - return hex; + return NULL; + } + char *hex = (char *)malloc((size_t)dlen * 2 + 1); + if (!hex) { + free(digest); + return NULL; + } + for (int i = 0; i < dlen; ++i) { + hex[2 * i] = hexdig[(digest[i] >> 4) & 0xF]; + hex[2 * i + 1] = hexdig[digest[i] & 0xF]; + } + hex[dlen * 2] = '\0'; + free(digest); + return hex; #else - char *hex = (char*)malloc(1); - if (hex) hex[0] = '\0'; - return hex; + char *hex = (char *)malloc(1); + if (hex) hex[0] = '\0'; + return hex; #endif } /* Compute SHA-256 hex of input buffer; returns malloc'ed C string (lowercase hex). * Fallback when OpenSSL disabled: empty string. */ static char *fun_openssl_sha256_hex(const unsigned char *data, size_t len) { - static const char hexdig[] = "0123456789abcdef"; - if (!data && len != 0) return NULL; + static const char hexdig[] = "0123456789abcdef"; + if (!data && len != 0) return NULL; #ifdef FUN_WITH_OPENSSL - const EVP_MD *md = EVP_sha256(); - if (!md) return NULL; - int dlen = EVP_MD_get_size(md); - if (dlen <= 0) return NULL; - unsigned char *digest = (unsigned char*)malloc((size_t)dlen); - if (!digest) return NULL; - unsigned int out_len = 0; - int ok; - if (len == 0) { - ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); - } else { - ok = EVP_Digest(data, len, digest, &out_len, md, NULL); - } - if (ok != 1 || (int)out_len != dlen) { free(digest); return NULL; } - char *hex = (char*)malloc((size_t)dlen * 2 + 1); - if (!hex) { free(digest); return NULL; } - for (int i = 0; i < dlen; ++i) { - hex[2*i] = hexdig[(digest[i] >> 4) & 0xF]; - hex[2*i+1] = hexdig[digest[i] & 0xF]; - } - hex[dlen * 2] = '\0'; + const EVP_MD *md = EVP_sha256(); + if (!md) return NULL; + int dlen = EVP_MD_get_size(md); + if (dlen <= 0) return NULL; + unsigned char *digest = (unsigned char *)malloc((size_t)dlen); + if (!digest) return NULL; + unsigned int out_len = 0; + int ok; + if (len == 0) { + ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); + } else { + ok = EVP_Digest(data, len, digest, &out_len, md, NULL); + } + if (ok != 1 || (int)out_len != dlen) { free(digest); - return hex; + return NULL; + } + char *hex = (char *)malloc((size_t)dlen * 2 + 1); + if (!hex) { + free(digest); + return NULL; + } + for (int i = 0; i < dlen; ++i) { + hex[2 * i] = hexdig[(digest[i] >> 4) & 0xF]; + hex[2 * i + 1] = hexdig[digest[i] & 0xF]; + } + hex[dlen * 2] = '\0'; + free(digest); + return hex; #else - char *hex = (char*)malloc(1); - if (hex) hex[0] = '\0'; - return hex; + char *hex = (char *)malloc(1); + if (hex) hex[0] = '\0'; + return hex; #endif } /* Compute SHA-512 hex of input buffer; returns malloc'ed C string (lowercase hex). * Fallback when OpenSSL disabled: empty string. */ static char *fun_openssl_sha512_hex(const unsigned char *data, size_t len) { - static const char hexdig[] = "0123456789abcdef"; - if (!data && len != 0) return NULL; + static const char hexdig[] = "0123456789abcdef"; + if (!data && len != 0) return NULL; #ifdef FUN_WITH_OPENSSL - const EVP_MD *md = EVP_sha512(); - if (!md) return NULL; - int dlen = EVP_MD_get_size(md); - if (dlen <= 0) return NULL; - unsigned char *digest = (unsigned char*)malloc((size_t)dlen); - if (!digest) return NULL; - unsigned int out_len = 0; - int ok; - if (len == 0) { - ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); - } else { - ok = EVP_Digest(data, len, digest, &out_len, md, NULL); - } - if (ok != 1 || (int)out_len != dlen) { free(digest); return NULL; } - char *hex = (char*)malloc((size_t)dlen * 2 + 1); - if (!hex) { free(digest); return NULL; } - for (int i = 0; i < dlen; ++i) { - hex[2*i] = hexdig[(digest[i] >> 4) & 0xF]; - hex[2*i+1] = hexdig[digest[i] & 0xF]; - } - hex[dlen * 2] = '\0'; + const EVP_MD *md = EVP_sha512(); + if (!md) return NULL; + int dlen = EVP_MD_get_size(md); + if (dlen <= 0) return NULL; + unsigned char *digest = (unsigned char *)malloc((size_t)dlen); + if (!digest) return NULL; + unsigned int out_len = 0; + int ok; + if (len == 0) { + ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); + } else { + ok = EVP_Digest(data, len, digest, &out_len, md, NULL); + } + if (ok != 1 || (int)out_len != dlen) { free(digest); - return hex; + return NULL; + } + char *hex = (char *)malloc((size_t)dlen * 2 + 1); + if (!hex) { + free(digest); + return NULL; + } + for (int i = 0; i < dlen; ++i) { + hex[2 * i] = hexdig[(digest[i] >> 4) & 0xF]; + hex[2 * i + 1] = hexdig[digest[i] & 0xF]; + } + hex[dlen * 2] = '\0'; + free(digest); + return hex; #else - char *hex = (char*)malloc(1); - if (hex) hex[0] = '\0'; - return hex; + char *hex = (char *)malloc(1); + if (hex) hex[0] = '\0'; + return hex; #endif } @@ -139,35 +157,41 @@ static char *fun_openssl_sha512_hex(const unsigned char *data, size_t len) { * EVP_ripemd160() can return NULL. In that case we return NULL and the VM opcode * will fall back to empty string behavior. When OpenSSL is disabled, return empty string. */ static char *fun_openssl_ripemd160_hex(const unsigned char *data, size_t len) { - static const char hexdig[] = "0123456789abcdef"; - if (!data && len != 0) return NULL; + static const char hexdig[] = "0123456789abcdef"; + if (!data && len != 0) return NULL; #ifdef FUN_WITH_OPENSSL - const EVP_MD *md = EVP_ripemd160(); - if (!md) return NULL; - int dlen = EVP_MD_get_size(md); - if (dlen <= 0) return NULL; - unsigned char *digest = (unsigned char*)malloc((size_t)dlen); - if (!digest) return NULL; - unsigned int out_len = 0; - int ok; - if (len == 0) { - ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); - } else { - ok = EVP_Digest(data, len, digest, &out_len, md, NULL); - } - if (ok != 1 || (int)out_len != dlen) { free(digest); return NULL; } - char *hex = (char*)malloc((size_t)dlen * 2 + 1); - if (!hex) { free(digest); return NULL; } - for (int i = 0; i < dlen; ++i) { - hex[2*i] = hexdig[(digest[i] >> 4) & 0xF]; - hex[2*i+1] = hexdig[digest[i] & 0xF]; - } - hex[dlen * 2] = '\0'; + const EVP_MD *md = EVP_ripemd160(); + if (!md) return NULL; + int dlen = EVP_MD_get_size(md); + if (dlen <= 0) return NULL; + unsigned char *digest = (unsigned char *)malloc((size_t)dlen); + if (!digest) return NULL; + unsigned int out_len = 0; + int ok; + if (len == 0) { + ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); + } else { + ok = EVP_Digest(data, len, digest, &out_len, md, NULL); + } + if (ok != 1 || (int)out_len != dlen) { free(digest); - return hex; + return NULL; + } + char *hex = (char *)malloc((size_t)dlen * 2 + 1); + if (!hex) { + free(digest); + return NULL; + } + for (int i = 0; i < dlen; ++i) { + hex[2 * i] = hexdig[(digest[i] >> 4) & 0xF]; + hex[2 * i + 1] = hexdig[digest[i] & 0xF]; + } + hex[dlen * 2] = '\0'; + free(digest); + return hex; #else - char *hex = (char*)malloc(1); - if (hex) hex[0] = '\0'; - return hex; + char *hex = (char *)malloc(1); + if (hex) hex[0] = '\0'; + return hex; #endif } diff --git a/src/external/pcre2.c b/src/external/pcre2.c index 0d8ec38..0e2f2fa 100644 --- a/src/external/pcre2.c +++ b/src/external/pcre2.c @@ -8,8 +8,8 @@ * * Added: 2025-12-11 (2025-12-11 migrated from src/vm/libsql/common.c) */ - - /* Ensure PCRE2 is configured consistently across the whole translation unit. + +/* Ensure PCRE2 is configured consistently across the whole translation unit. * vm.c includes many opcode implementation .c files; some use PCRE2. For PCRE2 * headers to expose the correct typedefs (e.g., pcre2_code, PCRE2_SPTR), the * PCRE2_CODE_UNIT_WIDTH macro must be defined before the first inclusion of diff --git a/src/external/pcsc.c b/src/external/pcsc.c index aa128c1..9508cc9 100644 --- a/src/external/pcsc.c +++ b/src/external/pcsc.c @@ -14,63 +14,72 @@ PCSC helpers: registries and helper functions. Included the file scope from vm.c. */ -#ifdef FUN_WITH_PCSC +#ifdef FUN_WITH_PCSC #if defined(__has_include) - #if __has_include() - #include - #include - #elif __has_include() - #include - #else - #error "FUN_WITH_PCSC is enabled but PCSC headers were not found" - #endif - #else - #include - #include - #endif - #include - - typedef struct { - SCARDCONTEXT ctx; - int in_use; - } pcsc_ctx_entry; +#if __has_include() +#include +#include +#elif __has_include() +#include +#else +#error "FUN_WITH_PCSC is enabled but PCSC headers were not found" +#endif +#else +#include +#include +#endif +#include typedef struct { - SCARDHANDLE h; - DWORD proto; - int in_use; + SCARDCONTEXT ctx; + int in_use; +} pcsc_ctx_entry; + +typedef struct { + SCARDHANDLE h; + DWORD proto; + int in_use; } pcsc_card_entry; static pcsc_ctx_entry g_pcsc_ctx[8]; static pcsc_card_entry g_pcsc_card[32]; static int pcsc_alloc_ctx_slot(void) { - for (int i = 0; i < (int)(sizeof(g_pcsc_ctx)/sizeof(g_pcsc_ctx[0])); ++i) { - if (!g_pcsc_ctx[i].in_use) { g_pcsc_ctx[i].in_use = 1; g_pcsc_ctx[i].ctx = 0; return i + 1; } + for (int i = 0; i < (int)(sizeof(g_pcsc_ctx) / sizeof(g_pcsc_ctx[0])); ++i) { + if (!g_pcsc_ctx[i].in_use) { + g_pcsc_ctx[i].in_use = 1; + g_pcsc_ctx[i].ctx = 0; + return i + 1; } - return 0; + } + return 0; } static int pcsc_alloc_card_slot(void) { - for (int i = 0; i < (int)(sizeof(g_pcsc_card)/sizeof(g_pcsc_card[0])); ++i) { - if (!g_pcsc_card[i].in_use) { g_pcsc_card[i].in_use = 1; g_pcsc_card[i].h = 0; g_pcsc_card[i].proto = 0; return i + 1; } + for (int i = 0; i < (int)(sizeof(g_pcsc_card) / sizeof(g_pcsc_card[0])); ++i) { + if (!g_pcsc_card[i].in_use) { + g_pcsc_card[i].in_use = 1; + g_pcsc_card[i].h = 0; + g_pcsc_card[i].proto = 0; + return i + 1; } - return 0; + } + return 0; } -static pcsc_ctx_entry* pcsc_get_ctx(int id) { - if (id <= 0) return NULL; - int idx = id - 1; - if (idx < 0 || idx >= (int)(sizeof(g_pcsc_ctx)/sizeof(g_pcsc_ctx[0]))) return NULL; - if (!g_pcsc_ctx[idx].in_use) return NULL; - return &g_pcsc_ctx[idx]; +static pcsc_ctx_entry *pcsc_get_ctx(int id) { + if (id <= 0) return NULL; + int idx = id - 1; + if (idx < 0 || idx >= (int)(sizeof(g_pcsc_ctx) / sizeof(g_pcsc_ctx[0]))) return NULL; + if (!g_pcsc_ctx[idx].in_use) return NULL; + return &g_pcsc_ctx[idx]; } -static pcsc_card_entry* pcsc_get_card(int id) { - if (id <= 0) return NULL; - int idx = id - 1; - if (idx < 0 || idx >= (int)(sizeof(g_pcsc_card)/sizeof(g_pcsc_card[0]))) return NULL; - if (!g_pcsc_card[idx].in_use) return NULL; - return &g_pcsc_card[idx]; +static pcsc_card_entry *pcsc_get_card(int id) { + if (id <= 0) return NULL; + int idx = id - 1; + if (idx < 0 || idx >= (int)(sizeof(g_pcsc_card) / sizeof(g_pcsc_card[0]))) return NULL; + if (!g_pcsc_card[idx].in_use) return NULL; + return &g_pcsc_card[idx]; } #endif diff --git a/src/external/sqlite.c b/src/external/sqlite.c index 82ace26..90dc332 100644 --- a/src/external/sqlite.c +++ b/src/external/sqlite.c @@ -16,34 +16,40 @@ #include typedef struct SqlHandle { - int id; - sqlite3 *db; - struct SqlHandle *next; + int id; + sqlite3 *db; + struct SqlHandle *next; } SqlHandle; static SqlHandle *g_sql_handles = NULL; static int g_sql_next_id = 1; -static SqlHandle* sql_reg_add(sqlite3 *db) { - SqlHandle *h = (SqlHandle*)calloc(1, sizeof(SqlHandle)); - if (!h) return NULL; - h->id = g_sql_next_id++; - h->db = db; - h->next = g_sql_handles; - g_sql_handles = h; - return h; +static SqlHandle *sql_reg_add(sqlite3 *db) { + SqlHandle *h = (SqlHandle *)calloc(1, sizeof(SqlHandle)); + if (!h) return NULL; + h->id = g_sql_next_id++; + h->db = db; + h->next = g_sql_handles; + g_sql_handles = h; + return h; } -static SqlHandle* sql_reg_get(int id) { - for (SqlHandle *p = g_sql_handles; p; p = p->next) if (p->id == id) return p; - return NULL; +static SqlHandle *sql_reg_get(int id) { + for (SqlHandle *p = g_sql_handles; p; p = p->next) + if (p->id == id) return p; + return NULL; } static void sql_reg_del(int id) { - SqlHandle **pp = &g_sql_handles; - while (*pp) { - if ((*pp)->id == id) { SqlHandle *d = *pp; *pp = d->next; free(d); return; } - pp = &(*pp)->next; + SqlHandle **pp = &g_sql_handles; + while (*pp) { + if ((*pp)->id == id) { + SqlHandle *d = *pp; + *pp = d->next; + free(d); + return; } + pp = &(*pp)->next; + } } #endif diff --git a/src/external/tcltk.c b/src/external/tcltk.c index d9c39b3..78e48a6 100644 --- a/src/external/tcltk.c +++ b/src/external/tcltk.c @@ -12,58 +12,68 @@ #ifdef FUN_WITH_TCLTK #include #include -static Tcl_Interp* g_fun_tcl_interp = NULL; +static Tcl_Interp *g_fun_tcl_interp = NULL; static void fun_tk_init_once(void) { - if (g_fun_tcl_interp) return; - Tcl_FindExecutable(NULL); - g_fun_tcl_interp = Tcl_CreateInterp(); - if (!g_fun_tcl_interp) return; - if (Tcl_Init(g_fun_tcl_interp) != TCL_OK) { - fprintf(stderr, "Tcl_Init failed: %s\n", Tcl_GetStringResult(g_fun_tcl_interp)); - } - if (Tk_Init(g_fun_tcl_interp) != TCL_OK) { - fprintf(stderr, "Tk_Init failed: %s\n", Tcl_GetStringResult(g_fun_tcl_interp)); - } - /* Ensure the app terminates if the main window is closed via window manager */ - /* Best-effort: set WM_DELETE_WINDOW handler to exit the process. */ - Tcl_Eval(g_fun_tcl_interp, "wm protocol . WM_DELETE_WINDOW {exit 0}"); + if (g_fun_tcl_interp) return; + Tcl_FindExecutable(NULL); + g_fun_tcl_interp = Tcl_CreateInterp(); + if (!g_fun_tcl_interp) return; + if (Tcl_Init(g_fun_tcl_interp) != TCL_OK) { + fprintf(stderr, "Tcl_Init failed: %s\n", Tcl_GetStringResult(g_fun_tcl_interp)); + } + if (Tk_Init(g_fun_tcl_interp) != TCL_OK) { + fprintf(stderr, "Tk_Init failed: %s\n", Tcl_GetStringResult(g_fun_tcl_interp)); + } + /* Ensure the app terminates if the main window is closed via window manager */ + /* Best-effort: set WM_DELETE_WINDOW handler to exit the process. */ + Tcl_Eval(g_fun_tcl_interp, "wm protocol . WM_DELETE_WINDOW {exit 0}"); } static int fun_tk_eval_script(const char *script) { - fun_tk_init_once(); - if (!g_fun_tcl_interp) return -1; - int rc = Tcl_Eval(g_fun_tcl_interp, script ? script : ""); - return rc; /* TCL_OK = 0 */ + fun_tk_init_once(); + if (!g_fun_tcl_interp) return -1; + int rc = Tcl_Eval(g_fun_tcl_interp, script ? script : ""); + return rc; /* TCL_OK = 0 */ } -static const char* fun_tk_get_result(void) { - fun_tk_init_once(); - if (!g_fun_tcl_interp) return ""; - return Tcl_GetStringResult(g_fun_tcl_interp); +static const char *fun_tk_get_result(void) { + fun_tk_init_once(); + if (!g_fun_tcl_interp) return ""; + return Tcl_GetStringResult(g_fun_tcl_interp); } static void fun_tk_loop(void) { - fun_tk_init_once(); - if (!g_fun_tcl_interp) return; - /* Drive Tk event loop until all main windows are closed */ - while (Tk_GetNumMainWindows() > 0) { - while (Tcl_DoOneEvent(0)) {} - /* tiny sleep to avoid busy spin */ + fun_tk_init_once(); + if (!g_fun_tcl_interp) return; + /* Drive Tk event loop until all main windows are closed */ + while (Tk_GetNumMainWindows() > 0) { + while (Tcl_DoOneEvent(0)) { + } + /* tiny sleep to avoid busy spin */ #ifdef _WIN32 #include - Sleep(1); + Sleep(1); #else #include - struct timespec ts = {0, 1000000}; /* 1 ms */ - nanosleep(&ts, NULL); + struct timespec ts = {0, 1000000}; /* 1 ms */ + nanosleep(&ts, NULL); #endif - } + } } #else /* Stubs when Tcl/Tk is disabled */ -static void fun_tk_init_once(void) { (void)0; } -static int fun_tk_eval_script(const char *script) { (void)script; return -1; } -static const char* fun_tk_get_result(void) { return ""; } -static void fun_tk_loop(void) { (void)0; } +static void fun_tk_init_once(void) { + (void)0; +} +static int fun_tk_eval_script(const char *script) { + (void)script; + return -1; +} +static const char *fun_tk_get_result(void) { + return ""; +} +static void fun_tk_loop(void) { + (void)0; +} #endif diff --git a/src/external/xml2.c b/src/external/xml2.c index 8f0efbb..34e9563 100644 --- a/src/external/xml2.c +++ b/src/external/xml2.c @@ -13,24 +13,34 @@ #include #include -typedef struct { xmlDocPtr doc; int in_use; } XmlDocSlot; -typedef struct { xmlNodePtr node; int in_use; } XmlNodeSlot; +typedef struct { + xmlDocPtr doc; + int in_use; +} XmlDocSlot; +typedef struct { + xmlNodePtr node; + int in_use; +} XmlNodeSlot; static XmlDocSlot g_xml_docs[64]; static XmlNodeSlot g_xml_nodes[256]; static int xml_doc_alloc(xmlDocPtr d) { - for (int i = 1; i < (int)(sizeof(g_xml_docs)/sizeof(g_xml_docs[0])); ++i) { - if (!g_xml_docs[i].in_use) { g_xml_docs[i].in_use = 1; g_xml_docs[i].doc = d; return i; } + for (int i = 1; i < (int)(sizeof(g_xml_docs) / sizeof(g_xml_docs[0])); ++i) { + if (!g_xml_docs[i].in_use) { + g_xml_docs[i].in_use = 1; + g_xml_docs[i].doc = d; + return i; + } } return 0; } static xmlDocPtr xml_doc_get(int h) { - if (h > 0 && h < (int)(sizeof(g_xml_docs)/sizeof(g_xml_docs[0])) && g_xml_docs[h].in_use) return g_xml_docs[h].doc; + if (h > 0 && h < (int)(sizeof(g_xml_docs) / sizeof(g_xml_docs[0])) && g_xml_docs[h].in_use) return g_xml_docs[h].doc; return NULL; } static int xml_doc_free_handle(int h) { - if (h <= 0 || h >= (int)(sizeof(g_xml_docs)/sizeof(g_xml_docs[0])) || !g_xml_docs[h].in_use) return 0; + if (h <= 0 || h >= (int)(sizeof(g_xml_docs) / sizeof(g_xml_docs[0])) || !g_xml_docs[h].in_use) return 0; if (g_xml_docs[h].doc) xmlFreeDoc(g_xml_docs[h].doc); g_xml_docs[h].doc = NULL; g_xml_docs[h].in_use = 0; @@ -38,17 +48,21 @@ static int xml_doc_free_handle(int h) { } static int xml_node_alloc(xmlNodePtr n) { - for (int i = 1; i < (int)(sizeof(g_xml_nodes)/sizeof(g_xml_nodes[0])); ++i) { - if (!g_xml_nodes[i].in_use) { g_xml_nodes[i].in_use = 1; g_xml_nodes[i].node = n; return i; } + for (int i = 1; i < (int)(sizeof(g_xml_nodes) / sizeof(g_xml_nodes[0])); ++i) { + if (!g_xml_nodes[i].in_use) { + g_xml_nodes[i].in_use = 1; + g_xml_nodes[i].node = n; + return i; + } } return 0; } static xmlNodePtr xml_node_get(int h) { - if (h > 0 && h < (int)(sizeof(g_xml_nodes)/sizeof(g_xml_nodes[0])) && g_xml_nodes[h].in_use) return g_xml_nodes[h].node; + if (h > 0 && h < (int)(sizeof(g_xml_nodes) / sizeof(g_xml_nodes[0])) && g_xml_nodes[h].in_use) return g_xml_nodes[h].node; return NULL; } static int xml_node_free_handle(int h) { - if (h <= 0 || h >= (int)(sizeof(g_xml_nodes)/sizeof(g_xml_nodes[0])) || !g_xml_nodes[h].in_use) return 0; + if (h <= 0 || h >= (int)(sizeof(g_xml_nodes) / sizeof(g_xml_nodes[0])) || !g_xml_nodes[h].in_use) return 0; /* nodes are owned by their document; do not free here */ g_xml_nodes[h].node = NULL; g_xml_nodes[h].in_use = 0; diff --git a/src/fun.c b/src/fun.c index 08ae886..22f5587 100644 --- a/src/fun.c +++ b/src/fun.c @@ -1,5 +1,5 @@ /* -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -7,15 +7,15 @@ * https://opensource.org/license/apache-2-0 */ - /* +/* * Main entry point for the Fun language interpreter. * Builds a CLI that runs a script file if provided; otherwise starts the REPL * when compiled with FUN_WITH_REPL enabled. */ #include "bytecode.h" -#include "vm.h" #include "parser.h" +#include "vm.h" #include #include #include @@ -30,127 +30,127 @@ #endif static void print_usage(const char *prog) { - printf("Fun %s\n", FUN_VERSION); - printf("Usage:\n"); + printf("Fun %s\n", FUN_VERSION); + printf("Usage:\n"); #ifdef FUN_WITH_REPL - printf(" %s [--trace|-t] [--repl-on-error] [script.fun]\n", prog ? prog : "fun"); - printf(" %s --help | -h\n", prog ? prog : "fun"); - printf(" %s --version | -V\n", prog ? prog : "fun"); - printf("\n"); - printf("Options:\n"); - printf(" --trace, -t Print executed ops and stack tops during run\n"); - printf(" --repl-on-error Enter interactive REPL on runtime error with stack preserved\n\n"); - printf("When no script is provided, a REPL starts. Submit an empty line to execute the buffer.\n"); + printf(" %s [--trace|-t] [--repl-on-error] [script.fun]\n", prog ? prog : "fun"); + printf(" %s --help | -h\n", prog ? prog : "fun"); + printf(" %s --version | -V\n", prog ? prog : "fun"); + printf("\n"); + printf("Options:\n"); + printf(" --trace, -t Print executed ops and stack tops during run\n"); + printf(" --repl-on-error Enter interactive REPL on runtime error with stack preserved\n\n"); + printf("When no script is provided, a REPL starts. Submit an empty line to execute the buffer.\n"); #else - printf(" %s [--trace|-t] \n", prog ? prog : "fun"); - printf(" %s --help | -h\n", prog ? prog : "fun"); - printf(" %s --version | -V\n", prog ? prog : "fun"); - printf("\n"); - printf("Options:\n --trace, -t Print executed ops and stack tops during run\n\n"); - printf("REPL is disabled in this build. Please provide a script file to run.\n"); + printf(" %s [--trace|-t] \n", prog ? prog : "fun"); + printf(" %s --help | -h\n", prog ? prog : "fun"); + printf(" %s --version | -V\n", prog ? prog : "fun"); + printf("\n"); + printf("Options:\n --trace, -t Print executed ops and stack tops during run\n\n"); + printf("REPL is disabled in this build. Please provide a script file to run.\n"); #endif } int main(int argc, char **argv) { - /* Set FUN_EXECUTABLE environment variable to the path of this binary */ - setenv("FUN_EXECUTABLE", argv[0], 1); + /* Set FUN_EXECUTABLE environment variable to the path of this binary */ + setenv("FUN_EXECUTABLE", argv[0], 1); - VM vm; - vm_init(&vm); + VM vm; + vm_init(&vm); - int argi = 1; - for (; argi < argc; ++argi) { - const char *arg = argv[argi]; - if (strcmp(arg, "--help") == 0 || strcmp(arg, "-h") == 0) { - print_usage(argv[0]); - return 0; - } - if (strcmp(arg, "--version") == 0 || strcmp(arg, "-V") == 0) { - printf("Fun %s\n", FUN_VERSION); - return 0; - } - if (strcmp(arg, "--trace") == 0 || strcmp(arg, "-t") == 0) { - vm.trace_enabled = 1; - continue; - } -#ifdef FUN_WITH_REPL - if (strcmp(arg, "--repl-on-error") == 0) { - vm.repl_on_error = 1; - vm.on_error_repl = fun_run_repl; /* provide REPL entry to core VM */ - continue; - } -#endif - /* first non-option assumed to be script path */ - break; + int argi = 1; + for (; argi < argc; ++argi) { + const char *arg = argv[argi]; + if (strcmp(arg, "--help") == 0 || strcmp(arg, "-h") == 0) { + print_usage(argv[0]); + return 0; } + if (strcmp(arg, "--version") == 0 || strcmp(arg, "-V") == 0) { + printf("Fun %s\n", FUN_VERSION); + return 0; + } + if (strcmp(arg, "--trace") == 0 || strcmp(arg, "-t") == 0) { + vm.trace_enabled = 1; + continue; + } +#ifdef FUN_WITH_REPL + if (strcmp(arg, "--repl-on-error") == 0) { + vm.repl_on_error = 1; + vm.on_error_repl = fun_run_repl; /* provide REPL entry to core VM */ + continue; + } +#endif + /* first non-option assumed to be script path */ + break; + } #ifndef FUN_WITH_REPL - if (argi >= argc) { - fprintf(stderr, "Error: REPL is disabled. Please provide a script to run.\n"); - print_usage(argv[0]); - return 2; - } + if (argi >= argc) { + fprintf(stderr, "Error: REPL is disabled. Please provide a script to run.\n"); + print_usage(argv[0]); + return 2; + } #endif - if (argi < argc) { - const char *path = argv[argi]; + if (argi < argc) { + const char *path = argv[argi]; - /* Collect script arguments (everything after script path) and expose via env vars */ - int sargi = argi + 1; /* first script arg following the script path */ - int sargc = (sargi < argc) ? (argc - sargi) : 0; + /* Collect script arguments (everything after script path) and expose via env vars */ + int sargi = argi + 1; /* first script arg following the script path */ + int sargc = (sargi < argc) ? (argc - sargi) : 0; - /* Export FUN_ARGC */ - { - char buf[32]; - snprintf(buf, sizeof(buf), "%d", sargc); - setenv("FUN_ARGC", buf, 1); - } - - /* Export FUN_ARGV_i */ - for (int i = 0; i < sargc; ++i) { - char key[32]; - snprintf(key, sizeof(key), "FUN_ARGV_%d", i); - setenv(key, argv[sargi + i], 1); - } - - /* Optional: space-joined convenience string FUN_ARGS */ - if (sargc > 0) { - size_t total = 0; - for (int i = 0; i < sargc; ++i) { - total += strlen(argv[sargi + i]) + 1; /* +1 for space or NUL */ - } - char *joined = (char*)malloc(total); - if (joined) { - joined[0] = '\0'; - for (int i = 0; i < sargc; ++i) { - strcat(joined, argv[sargi + i]); - if (i + 1 < sargc) strcat(joined, " "); - } - setenv("FUN_ARGS", joined, 1); - free(joined); - } - } else { - /* Ensure FUN_ARGS is at least cleared for consistency */ - setenv("FUN_ARGS", "", 1); - } - - Bytecode *bc = parse_file_to_bytecode(path); - if (!bc) { - fprintf(stderr, "Failed to compile script: %s\n", path); - return 1; - } - - vm_run(&vm, bc); - vm_print_output(&vm); - vm_clear_output(&vm); - bytecode_free(bc); - return vm.exit_code; + /* Export FUN_ARGC */ + { + char buf[32]; + snprintf(buf, sizeof(buf), "%d", sargc); + setenv("FUN_ARGC", buf, 1); } + /* Export FUN_ARGV_i */ + for (int i = 0; i < sargc; ++i) { + char key[32]; + snprintf(key, sizeof(key), "FUN_ARGV_%d", i); + setenv(key, argv[sargi + i], 1); + } + + /* Optional: space-joined convenience string FUN_ARGS */ + if (sargc > 0) { + size_t total = 0; + for (int i = 0; i < sargc; ++i) { + total += strlen(argv[sargi + i]) + 1; /* +1 for space or NUL */ + } + char *joined = (char *)malloc(total); + if (joined) { + joined[0] = '\0'; + for (int i = 0; i < sargc; ++i) { + strcat(joined, argv[sargi + i]); + if (i + 1 < sargc) strcat(joined, " "); + } + setenv("FUN_ARGS", joined, 1); + free(joined); + } + } else { + /* Ensure FUN_ARGS is at least cleared for consistency */ + setenv("FUN_ARGS", "", 1); + } + + Bytecode *bc = parse_file_to_bytecode(path); + if (!bc) { + fprintf(stderr, "Failed to compile script: %s\n", path); + return 1; + } + + vm_run(&vm, bc); + vm_print_output(&vm); + vm_clear_output(&vm); + bytecode_free(bc); + return vm.exit_code; + } + #ifdef FUN_WITH_REPL - return fun_run_repl(&vm); + return fun_run_repl(&vm); #else - fprintf(stderr, "Internal error: REPL not available in this build.\n"); - return 2; + fprintf(stderr, "Internal error: REPL not available in this build.\n"); + return 2; #endif } diff --git a/src/fun_test.c b/src/fun_test.c index fa28b2c..fbf8765 100644 --- a/src/fun_test.c +++ b/src/fun_test.c @@ -10,302 +10,302 @@ #include "bytecode.h" #include "value.h" #include "vm.h" -#include #include +#include -#define ASSERT_EQ(val, expected) \ - if ((val).type != VAL_INT || (val).i != (expected)) { \ - fprintf(stderr, "Assertion failed: expected %lld, got ", (long long)(expected)); \ - print_value(&(val)); \ - printf("\n"); \ - return 1; \ - } +#define ASSERT_EQ(val, expected) \ + if ((val).type != VAL_INT || (val).i != (expected)) { \ + fprintf(stderr, "Assertion failed: expected %lld, got ", (long long)(expected)); \ + print_value(&(val)); \ + printf("\n"); \ + return 1; \ + } int main(void) { - VM vm; - vm_init(&vm); + VM vm; + vm_init(&vm); - Bytecode *bc = bytecode_new(); + Bytecode *bc = bytecode_new(); - // constants - int c0 = bytecode_add_constant(bc, make_int(0)); - int c1 = bytecode_add_constant(bc, make_int(1)); - int c2 = bytecode_add_constant(bc, make_int(2)); - int c3 = bytecode_add_constant(bc, make_int(3)); - int c10 = bytecode_add_constant(bc, make_int(10)); - int c42 = bytecode_add_constant(bc, make_int(42)); - int cf3_2 = bytecode_add_constant(bc, make_float(3.2)); - int cf3_5 = bytecode_add_constant(bc, make_float(3.5)); - int cf3_8 = bytecode_add_constant(bc, make_float(3.8)); - int cfn3_2 = bytecode_add_constant(bc, make_float(-3.2)); - int cfn3_5 = bytecode_add_constant(bc, make_float(-3.5)); - int cfn3_8 = bytecode_add_constant(bc, make_float(-3.8)); - int cf0 = bytecode_add_constant(bc, make_float(0.0)); - int cf1 = bytecode_add_constant(bc, make_float(1.0)); - int cf5 = bytecode_add_constant(bc, make_float(5.0)); - int c4 = bytecode_add_constant(bc, make_int(4)); - int c9 = bytecode_add_constant(bc, make_int(9)); - int c48 = bytecode_add_constant(bc, make_int(48)); - int c18 = bytecode_add_constant(bc, make_int(18)); - int c21 = bytecode_add_constant(bc, make_int(21)); - int c6 = bytecode_add_constant(bc, make_int(6)); - int c15 = bytecode_add_constant(bc, make_int(15)); - int c16 = bytecode_add_constant(bc, make_int(16)); - int cneg5 = bytecode_add_constant(bc, make_int(-5)); - int c7 = bytecode_add_constant(bc, make_int(7)); + // constants + int c0 = bytecode_add_constant(bc, make_int(0)); + int c1 = bytecode_add_constant(bc, make_int(1)); + int c2 = bytecode_add_constant(bc, make_int(2)); + int c3 = bytecode_add_constant(bc, make_int(3)); + int c10 = bytecode_add_constant(bc, make_int(10)); + int c42 = bytecode_add_constant(bc, make_int(42)); + int cf3_2 = bytecode_add_constant(bc, make_float(3.2)); + int cf3_5 = bytecode_add_constant(bc, make_float(3.5)); + int cf3_8 = bytecode_add_constant(bc, make_float(3.8)); + int cfn3_2 = bytecode_add_constant(bc, make_float(-3.2)); + int cfn3_5 = bytecode_add_constant(bc, make_float(-3.5)); + int cfn3_8 = bytecode_add_constant(bc, make_float(-3.8)); + int cf0 = bytecode_add_constant(bc, make_float(0.0)); + int cf1 = bytecode_add_constant(bc, make_float(1.0)); + int cf5 = bytecode_add_constant(bc, make_float(5.0)); + int c4 = bytecode_add_constant(bc, make_int(4)); + int c9 = bytecode_add_constant(bc, make_int(9)); + int c48 = bytecode_add_constant(bc, make_int(48)); + int c18 = bytecode_add_constant(bc, make_int(18)); + int c21 = bytecode_add_constant(bc, make_int(21)); + int c6 = bytecode_add_constant(bc, make_int(6)); + int c15 = bytecode_add_constant(bc, make_int(15)); + int c16 = bytecode_add_constant(bc, make_int(16)); + int cneg5 = bytecode_add_constant(bc, make_int(-5)); + int c7 = bytecode_add_constant(bc, make_int(7)); - // ---------- Arithmetic ---------- - bytecode_add_instruction(bc, OP_LOAD_CONST, c42); - bytecode_add_instruction(bc, OP_LOAD_CONST, c1); - bytecode_add_instruction(bc, OP_ADD, 0); // 42+1=43 - bytecode_add_instruction(bc, OP_PRINT, 0); + // ---------- Arithmetic ---------- + bytecode_add_instruction(bc, OP_LOAD_CONST, c42); + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_ADD, 0); // 42+1=43 + bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, c10); - bytecode_add_instruction(bc, OP_LOAD_CONST, c3); - bytecode_add_instruction(bc, OP_SUB, 0); // 10-3=7 - bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, c10); + bytecode_add_instruction(bc, OP_LOAD_CONST, c3); + bytecode_add_instruction(bc, OP_SUB, 0); // 10-3=7 + bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, c2); - bytecode_add_instruction(bc, OP_LOAD_CONST, c3); - bytecode_add_instruction(bc, OP_MUL, 0); // 2*3=6 - bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_LOAD_CONST, c3); + bytecode_add_instruction(bc, OP_MUL, 0); // 2*3=6 + bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, c10); - bytecode_add_instruction(bc, OP_LOAD_CONST, c2); - bytecode_add_instruction(bc, OP_DIV, 0); // 10/2=5 - bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, c10); + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_DIV, 0); // 10/2=5 + bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, c10); - bytecode_add_instruction(bc, OP_LOAD_CONST, c3); - bytecode_add_instruction(bc, OP_MOD, 0); // 10%3=1 - bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, c10); + bytecode_add_instruction(bc, OP_LOAD_CONST, c3); + bytecode_add_instruction(bc, OP_MOD, 0); // 10%3=1 + bytecode_add_instruction(bc, OP_PRINT, 0); - // ---------- Comparisons ---------- - bytecode_add_instruction(bc, OP_LOAD_CONST, c1); - bytecode_add_instruction(bc, OP_LOAD_CONST, c2); - bytecode_add_instruction(bc, OP_LT, 0); // 1<2=1 - bytecode_add_instruction(bc, OP_PRINT, 0); + // ---------- Comparisons ---------- + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_LT, 0); // 1<2=1 + bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, c2); - bytecode_add_instruction(bc, OP_LOAD_CONST, c2); - bytecode_add_instruction(bc, OP_LTE, 0); // 2<=2=1 - bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_LTE, 0); // 2<=2=1 + bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, c3); - bytecode_add_instruction(bc, OP_LOAD_CONST, c2); - bytecode_add_instruction(bc, OP_GT, 0); // 3>2=1 - bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, c3); + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_GT, 0); // 3>2=1 + bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, c2); - bytecode_add_instruction(bc, OP_LOAD_CONST, c2); - bytecode_add_instruction(bc, OP_GTE, 0); // 2>=2=1 - bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_GTE, 0); // 2>=2=1 + bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, c2); - bytecode_add_instruction(bc, OP_LOAD_CONST, c2); - bytecode_add_instruction(bc, OP_EQ, 0); // 2==2=1 - bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_EQ, 0); // 2==2=1 + bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, c2); - bytecode_add_instruction(bc, OP_LOAD_CONST, c3); - bytecode_add_instruction(bc, OP_NEQ, 0); // 2!=3=1 - bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_LOAD_CONST, c3); + bytecode_add_instruction(bc, OP_NEQ, 0); // 2!=3=1 + bytecode_add_instruction(bc, OP_PRINT, 0); - // ---------- Logical ---------- - bytecode_add_instruction(bc, OP_LOAD_CONST, c1); - bytecode_add_instruction(bc, OP_LOAD_CONST, c0); - bytecode_add_instruction(bc, OP_AND, 0); // 1&&0=0 - bytecode_add_instruction(bc, OP_PRINT, 0); + // ---------- Logical ---------- + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_LOAD_CONST, c0); + bytecode_add_instruction(bc, OP_AND, 0); // 1&&0=0 + bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, c1); - bytecode_add_instruction(bc, OP_LOAD_CONST, c0); - bytecode_add_instruction(bc, OP_OR, 0); // 1||0=1 - bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_LOAD_CONST, c0); + bytecode_add_instruction(bc, OP_OR, 0); // 1||0=1 + bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, c0); - bytecode_add_instruction(bc, OP_NOT, 0); // !0=1 - bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, c0); + bytecode_add_instruction(bc, OP_NOT, 0); // !0=1 + bytecode_add_instruction(bc, OP_PRINT, 0); - // ---------- Stack ---------- - bytecode_add_instruction(bc, OP_LOAD_CONST, c1); - bytecode_add_instruction(bc, OP_DUP, 0); // duplicate 1 - bytecode_add_instruction(bc, OP_ADD, 0); // 1+1=2 - bytecode_add_instruction(bc, OP_PRINT, 0); + // ---------- Stack ---------- + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_DUP, 0); // duplicate 1 + bytecode_add_instruction(bc, OP_ADD, 0); // 1+1=2 + bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, c1); - bytecode_add_instruction(bc, OP_LOAD_CONST, c2); - bytecode_add_instruction(bc, OP_SWAP, 0); // swap top two - bytecode_add_instruction(bc, OP_PRINT, 0); // top=1 + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_SWAP, 0); // swap top two + bytecode_add_instruction(bc, OP_PRINT, 0); // top=1 - bytecode_add_instruction(bc, OP_LOAD_CONST, c1); - bytecode_add_instruction(bc, OP_POP, 0); // dApache-2.0ard 1 (stack now empty) + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_POP, 0); // dApache-2.0ard 1 (stack now empty) - // ---------- Rounding (math.h) demo ---------- - // floor/ceil/trunc/round on representative values - int start_round_demo = bc->instr_count; - (void)start_round_demo; + // ---------- Rounding (math.h) demo ---------- + // floor/ceil/trunc/round on representative values + int start_round_demo = bc->instr_count; + (void)start_round_demo; - // +3.2 - bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); - bytecode_add_instruction(bc, OP_FLOOR, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); - bytecode_add_instruction(bc, OP_CEIL, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); - bytecode_add_instruction(bc, OP_TRUNC, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); - bytecode_add_instruction(bc, OP_ROUND, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); + // +3.2 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); + bytecode_add_instruction(bc, OP_FLOOR, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); + bytecode_add_instruction(bc, OP_CEIL, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); + bytecode_add_instruction(bc, OP_TRUNC, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); + bytecode_add_instruction(bc, OP_ROUND, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); - // +3.5 - bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_5); - bytecode_add_instruction(bc, OP_ROUND, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); + // +3.5 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_5); + bytecode_add_instruction(bc, OP_ROUND, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); - // -3.5 - bytecode_add_instruction(bc, OP_LOAD_CONST, cfn3_5); - bytecode_add_instruction(bc, OP_ROUND, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); + // -3.5 + bytecode_add_instruction(bc, OP_LOAD_CONST, cfn3_5); + bytecode_add_instruction(bc, OP_ROUND, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); - // -3.2 - bytecode_add_instruction(bc, OP_LOAD_CONST, cfn3_2); - bytecode_add_instruction(bc, OP_FLOOR, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, cfn3_2); - bytecode_add_instruction(bc, OP_CEIL, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); + // -3.2 + bytecode_add_instruction(bc, OP_LOAD_CONST, cfn3_2); + bytecode_add_instruction(bc, OP_FLOOR, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, cfn3_2); + bytecode_add_instruction(bc, OP_CEIL, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); - // integers should be unchanged - bytecode_add_instruction(bc, OP_LOAD_CONST, c10); - bytecode_add_instruction(bc, OP_FLOOR, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); + // integers should be unchanged + bytecode_add_instruction(bc, OP_LOAD_CONST, c10); + bytecode_add_instruction(bc, OP_FLOOR, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); - // ---------- Transcendentals demo ---------- - // sin(0)=0 - bytecode_add_instruction(bc, OP_LOAD_CONST, cf0); - bytecode_add_instruction(bc, OP_SIN, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); - // cos(0)=1 - bytecode_add_instruction(bc, OP_LOAD_CONST, cf0); - bytecode_add_instruction(bc, OP_COS, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); - // tan(0)=0 - bytecode_add_instruction(bc, OP_LOAD_CONST, cf0); - bytecode_add_instruction(bc, OP_TAN, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); - // exp(0)=1 - bytecode_add_instruction(bc, OP_LOAD_CONST, cf0); - bytecode_add_instruction(bc, OP_EXP, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); - // log(1)=0 - bytecode_add_instruction(bc, OP_LOAD_CONST, cf1); - bytecode_add_instruction(bc, OP_LOG, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); - // log10(1)=0 - bytecode_add_instruction(bc, OP_LOAD_CONST, cf1); - bytecode_add_instruction(bc, OP_LOG10, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); - // sqrt(9)=3 - bytecode_add_instruction(bc, OP_LOAD_CONST, c9); - bytecode_add_instruction(bc, OP_SQRT, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); + // ---------- Transcendentals demo ---------- + // sin(0)=0 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf0); + bytecode_add_instruction(bc, OP_SIN, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // cos(0)=1 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf0); + bytecode_add_instruction(bc, OP_COS, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // tan(0)=0 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf0); + bytecode_add_instruction(bc, OP_TAN, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // exp(0)=1 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf0); + bytecode_add_instruction(bc, OP_EXP, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // log(1)=0 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf1); + bytecode_add_instruction(bc, OP_LOG, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // log10(1)=0 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf1); + bytecode_add_instruction(bc, OP_LOG10, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // sqrt(9)=3 + bytecode_add_instruction(bc, OP_LOAD_CONST, c9); + bytecode_add_instruction(bc, OP_SQRT, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); - // ---------- Integer math (gcd/lcm/isqrt/sign) demo ---------- - // gcd(48,18)=6 - bytecode_add_instruction(bc, OP_LOAD_CONST, c48); - bytecode_add_instruction(bc, OP_LOAD_CONST, c18); - bytecode_add_instruction(bc, OP_GCD, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); + // ---------- Integer math (gcd/lcm/isqrt/sign) demo ---------- + // gcd(48,18)=6 + bytecode_add_instruction(bc, OP_LOAD_CONST, c48); + bytecode_add_instruction(bc, OP_LOAD_CONST, c18); + bytecode_add_instruction(bc, OP_GCD, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); - // lcm(21,6)=42 - bytecode_add_instruction(bc, OP_LOAD_CONST, c21); - bytecode_add_instruction(bc, OP_LOAD_CONST, c6); - bytecode_add_instruction(bc, OP_LCM, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); + // lcm(21,6)=42 + bytecode_add_instruction(bc, OP_LOAD_CONST, c21); + bytecode_add_instruction(bc, OP_LOAD_CONST, c6); + bytecode_add_instruction(bc, OP_LCM, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); - // isqrt cases: 0, 1, 15->3, 16->4 - bytecode_add_instruction(bc, OP_LOAD_CONST, c0); - bytecode_add_instruction(bc, OP_ISQRT, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, c1); - bytecode_add_instruction(bc, OP_ISQRT, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, c15); - bytecode_add_instruction(bc, OP_ISQRT, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, c16); - bytecode_add_instruction(bc, OP_ISQRT, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); + // isqrt cases: 0, 1, 15->3, 16->4 + bytecode_add_instruction(bc, OP_LOAD_CONST, c0); + bytecode_add_instruction(bc, OP_ISQRT, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_ISQRT, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, c15); + bytecode_add_instruction(bc, OP_ISQRT, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, c16); + bytecode_add_instruction(bc, OP_ISQRT, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); - // sign(-5)=-1, sign(0)=0, sign(7)=1 - bytecode_add_instruction(bc, OP_LOAD_CONST, cneg5); - bytecode_add_instruction(bc, OP_SIGN, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, c0); - bytecode_add_instruction(bc, OP_SIGN, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, c7); - bytecode_add_instruction(bc, OP_SIGN, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); + // sign(-5)=-1, sign(0)=0, sign(7)=1 + bytecode_add_instruction(bc, OP_LOAD_CONST, cneg5); + bytecode_add_instruction(bc, OP_SIGN, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, c0); + bytecode_add_instruction(bc, OP_SIGN, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, c7); + bytecode_add_instruction(bc, OP_SIGN, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); - // ---------- fmin/fmax demo ---------- - // fmin(3.2, 4) -> 3.2 - bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); - bytecode_add_instruction(bc, OP_LOAD_CONST, c4); - bytecode_add_instruction(bc, OP_FMIN, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); - // fmax(3.2, 4) -> 4 - bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); - bytecode_add_instruction(bc, OP_LOAD_CONST, c4); - bytecode_add_instruction(bc, OP_FMAX, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); - // NaN cases - double nanv = NAN; - int cNaN = bytecode_add_constant(bc, make_float(nanv)); - // fmin(NaN, 5.0) -> 5.0 - bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN); - bytecode_add_instruction(bc, OP_LOAD_CONST, cf5); - bytecode_add_instruction(bc, OP_FMIN, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); - // fmax(NaN, 5.0) -> 5.0 - bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN); - bytecode_add_instruction(bc, OP_LOAD_CONST, cf5); - bytecode_add_instruction(bc, OP_FMAX, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); - // fmin(5.0, NaN) -> 5.0 - bytecode_add_instruction(bc, OP_LOAD_CONST, cf5); - bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN); - bytecode_add_instruction(bc, OP_FMIN, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); - // fmax(5.0, NaN) -> 5.0 - bytecode_add_instruction(bc, OP_LOAD_CONST, cf5); - bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN); - bytecode_add_instruction(bc, OP_FMAX, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); - // fmin(NaN, NaN) -> NaN (prints as nan) - bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN); - bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN); - bytecode_add_instruction(bc, OP_FMIN, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); + // ---------- fmin/fmax demo ---------- + // fmin(3.2, 4) -> 3.2 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); + bytecode_add_instruction(bc, OP_LOAD_CONST, c4); + bytecode_add_instruction(bc, OP_FMIN, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // fmax(3.2, 4) -> 4 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); + bytecode_add_instruction(bc, OP_LOAD_CONST, c4); + bytecode_add_instruction(bc, OP_FMAX, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // NaN cases + double nanv = NAN; + int cNaN = bytecode_add_constant(bc, make_float(nanv)); + // fmin(NaN, 5.0) -> 5.0 + bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN); + bytecode_add_instruction(bc, OP_LOAD_CONST, cf5); + bytecode_add_instruction(bc, OP_FMIN, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // fmax(NaN, 5.0) -> 5.0 + bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN); + bytecode_add_instruction(bc, OP_LOAD_CONST, cf5); + bytecode_add_instruction(bc, OP_FMAX, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // fmin(5.0, NaN) -> 5.0 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf5); + bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN); + bytecode_add_instruction(bc, OP_FMIN, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // fmax(5.0, NaN) -> 5.0 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf5); + bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN); + bytecode_add_instruction(bc, OP_FMAX, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // fmin(NaN, NaN) -> NaN (prints as nan) + bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN); + bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN); + bytecode_add_instruction(bc, OP_FMIN, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); - // ---------- HALT ---------- - bytecode_add_instruction(bc, OP_HALT, 0); + // ---------- HALT ---------- + bytecode_add_instruction(bc, OP_HALT, 0); - printf("=== Bytecode dump ===\n"); - for (int i = 0; i < bc->instr_count; ++i) { - Instruction instr = bc->instructions[i]; - printf("instr %3d: opcode=%2d operand=%d\n", i, instr.op, instr.operand); - } - printf("=====================\n"); + printf("=== Bytecode dump ===\n"); + for (int i = 0; i < bc->instr_count; ++i) { + Instruction instr = bc->instructions[i]; + printf("instr %3d: opcode=%2d operand=%d\n", i, instr.op, instr.operand); + } + printf("=====================\n"); - // run VM - vm_run(&vm, bc); + // run VM + vm_run(&vm, bc); - printf("All tests executed. Output count: %d\n", vm.output_count); + printf("All tests executed. Output count: %d\n", vm.output_count); - vm_clear_output(&vm); - bytecode_free(bc); - return 0; + vm_clear_output(&vm); + bytecode_free(bc); + return 0; } diff --git a/src/iter.c b/src/iter.c index 0586344..dc20dfc 100644 --- a/src/iter.c +++ b/src/iter.c @@ -12,49 +12,51 @@ /* enumerate(arr) -> [[0, v0], [1, v1], ...] */ Value bi_enumerate(const Value *arr) { - int n = array_length(arr); - if (n <= 0) return make_array_from_values(NULL, 0); - Value *pairs = (Value*)malloc(sizeof(Value) * n); - if (!pairs) return make_array_from_values(NULL, 0); - for (int i = 0; i < n; ++i) { - Value elem; - array_get_copy(arr, i, &elem); - Value kv_vals[2]; - kv_vals[0] = make_int(i); - kv_vals[1] = elem; - Value kv = make_array_from_values(kv_vals, 2); - free_value(kv_vals[0]); - free_value(kv_vals[1]); - pairs[i] = kv; - } - Value out = make_array_from_values(pairs, n); - for (int i = 0; i < n; ++i) free_value(pairs[i]); - free(pairs); - return out; + int n = array_length(arr); + if (n <= 0) return make_array_from_values(NULL, 0); + Value *pairs = (Value *)malloc(sizeof(Value) * n); + if (!pairs) return make_array_from_values(NULL, 0); + for (int i = 0; i < n; ++i) { + Value elem; + array_get_copy(arr, i, &elem); + Value kv_vals[2]; + kv_vals[0] = make_int(i); + kv_vals[1] = elem; + Value kv = make_array_from_values(kv_vals, 2); + free_value(kv_vals[0]); + free_value(kv_vals[1]); + pairs[i] = kv; + } + Value out = make_array_from_values(pairs, n); + for (int i = 0; i < n; ++i) + free_value(pairs[i]); + free(pairs); + return out; } /* zip(a, b) -> [[a0,b0], [a1,b1], ...] up to min(len(a),len(b)) */ Value bi_zip(const Value *a, const Value *b) { - int na = array_length(a); - int nb = array_length(b); - int n = na < nb ? na : nb; - if (n <= 0) return make_array_from_values(NULL, 0); - Value *pairs = (Value*)malloc(sizeof(Value) * n); - if (!pairs) return make_array_from_values(NULL, 0); - for (int i = 0; i < n; ++i) { - Value av, bv; - array_get_copy(a, i, &av); - array_get_copy(b, i, &bv); - Value kv_vals[2]; - kv_vals[0] = av; - kv_vals[1] = bv; - Value kv = make_array_from_values(kv_vals, 2); - free_value(kv_vals[0]); - free_value(kv_vals[1]); - pairs[i] = kv; - } - Value out = make_array_from_values(pairs, n); - for (int i = 0; i < n; ++i) free_value(pairs[i]); - free(pairs); - return out; + int na = array_length(a); + int nb = array_length(b); + int n = na < nb ? na : nb; + if (n <= 0) return make_array_from_values(NULL, 0); + Value *pairs = (Value *)malloc(sizeof(Value) * n); + if (!pairs) return make_array_from_values(NULL, 0); + for (int i = 0; i < n; ++i) { + Value av, bv; + array_get_copy(a, i, &av); + array_get_copy(b, i, &bv); + Value kv_vals[2]; + kv_vals[0] = av; + kv_vals[1] = bv; + Value kv = make_array_from_values(kv_vals, 2); + free_value(kv_vals[0]); + free_value(kv_vals[1]); + pairs[i] = kv; + } + Value out = make_array_from_values(pairs, n); + for (int i = 0; i < n; ++i) + free_value(pairs[i]); + free(pairs); + return out; } diff --git a/src/map.c b/src/map.c index a7c2260..b74b7d0 100644 --- a/src/map.c +++ b/src/map.c @@ -13,104 +13,113 @@ /* Internal Map definition; Value holds struct Map* */ typedef struct Map { - int refcount; - int count; - int cap; - char **keys; /* each key owned here */ - Value *vals; /* each value owned here */ + int refcount; + int count; + int cap; + char **keys; /* each key owned here */ + Value *vals; /* each value owned here */ } Map; Value make_map_empty(void) { - Map *m = (Map*)malloc(sizeof(Map)); - if (!m) return make_nil(); - m->refcount = 1; - m->count = 0; - m->cap = 0; - m->keys = NULL; - m->vals = NULL; - Value v; - v.type = VAL_MAP; - v.map = (struct Map*)m; - return v; + Map *m = (Map *)malloc(sizeof(Map)); + if (!m) return make_nil(); + m->refcount = 1; + m->count = 0; + m->cap = 0; + m->keys = NULL; + m->vals = NULL; + Value v; + v.type = VAL_MAP; + v.map = (struct Map *)m; + return v; } static int map_ensure_cap(Map *m, int need) { - if (m->cap >= need) return 1; - int ncap = m->cap == 0 ? 4 : m->cap * 2; - while (ncap < need) ncap *= 2; - char **nkeys = (char**)realloc(m->keys, sizeof(char*) * ncap); - Value *nvals = (Value*)realloc(m->vals, sizeof(Value) * ncap); - if (!nkeys || !nvals) return 0; - m->keys = nkeys; - m->vals = nvals; - m->cap = ncap; - return 1; + if (m->cap >= need) return 1; + int ncap = m->cap == 0 ? 4 : m->cap * 2; + while (ncap < need) + ncap *= 2; + char **nkeys = (char **)realloc(m->keys, sizeof(char *) * ncap); + Value *nvals = (Value *)realloc(m->vals, sizeof(Value) * ncap); + if (!nkeys || !nvals) return 0; + m->keys = nkeys; + m->vals = nvals; + m->cap = ncap; + return 1; } int map_set(Value *vm, const char *key, Value v) { - if (!vm || vm->type != VAL_MAP || !vm->map || !key) { free_value(v); return 0; } - Map *m = (Map*)vm->map; - for (int i = 0; i < m->count; ++i) { - if (strcmp(m->keys[i], key) == 0) { - free_value(m->vals[i]); - m->vals[i] = v; - return 1; - } + if (!vm || vm->type != VAL_MAP || !vm->map || !key) { + free_value(v); + return 0; + } + Map *m = (Map *)vm->map; + for (int i = 0; i < m->count; ++i) { + if (strcmp(m->keys[i], key) == 0) { + free_value(m->vals[i]); + m->vals[i] = v; + return 1; } - if (!map_ensure_cap(m, m->count + 1)) { free_value(v); return 0; } - m->keys[m->count] = strdup(key); - m->vals[m->count] = v; - m->count++; - return 1; + } + if (!map_ensure_cap(m, m->count + 1)) { + free_value(v); + return 0; + } + m->keys[m->count] = strdup(key); + m->vals[m->count] = v; + m->count++; + return 1; } int map_get_copy(const Value *vm, const char *key, Value *out) { - if (!vm || vm->type != VAL_MAP || !vm->map || !key) return 0; - Map *m = (Map*)vm->map; - for (int i = 0; i < m->count; ++i) { - if (strcmp(m->keys[i], key) == 0) { - if (out) *out = copy_value(&m->vals[i]); - return 1; - } + if (!vm || vm->type != VAL_MAP || !vm->map || !key) return 0; + Map *m = (Map *)vm->map; + for (int i = 0; i < m->count; ++i) { + if (strcmp(m->keys[i], key) == 0) { + if (out) *out = copy_value(&m->vals[i]); + return 1; } - return 0; + } + return 0; } int map_has(const Value *vm, const char *key) { - if (!vm || vm->type != VAL_MAP || !vm->map || !key) return 0; - Map *m = (Map*)vm->map; - for (int i = 0; i < m->count; ++i) { - if (strcmp(m->keys[i], key) == 0) return 1; - } - return 0; + if (!vm || vm->type != VAL_MAP || !vm->map || !key) return 0; + Map *m = (Map *)vm->map; + for (int i = 0; i < m->count; ++i) { + if (strcmp(m->keys[i], key) == 0) return 1; + } + return 0; } Value map_keys_array(const Value *vm) { - if (!vm || vm->type != VAL_MAP || !vm->map) return make_array_from_values(NULL, 0); - Map *m = (Map*)vm->map; - if (m->count <= 0) return make_array_from_values(NULL, 0); - Value *tmp = (Value*)malloc(sizeof(Value) * m->count); - if (!tmp) return make_array_from_values(NULL, 0); - for (int i = 0; i < m->count; ++i) { - tmp[i] = make_string(m->keys[i]); - } - Value arr = make_array_from_values(tmp, m->count); - for (int i = 0; i < m->count; ++i) free_value(tmp[i]); - free(tmp); - return arr; + if (!vm || vm->type != VAL_MAP || !vm->map) return make_array_from_values(NULL, 0); + Map *m = (Map *)vm->map; + if (m->count <= 0) return make_array_from_values(NULL, 0); + Value *tmp = (Value *)malloc(sizeof(Value) * m->count); + if (!tmp) return make_array_from_values(NULL, 0); + for (int i = 0; i < m->count; ++i) { + tmp[i] = make_string(m->keys[i]); + } + Value arr = make_array_from_values(tmp, m->count); + for (int i = 0; i < m->count; ++i) + free_value(tmp[i]); + free(tmp); + return arr; } Value map_values_array(const Value *vm) { - if (!vm || vm->type != VAL_MAP || !vm->map) return make_array_from_values(NULL, 0); - Map *m = (Map*)vm->map; - if (m->count <= 0) return make_array_from_values(NULL, 0); - Value *tmp = (Value*)malloc(sizeof(Value) * m->count); - if (!tmp) return make_array_from_values(NULL, 0); - for (int i = 0; i < m->count; ++i) { - tmp[i] = copy_value(&m->vals[i]); - } - Value arr = make_array_from_values(tmp, m->count); - for (int i = 0; i < m->count; ++i) free_value(tmp[i]); - free(tmp); - return arr; + if (!vm || vm->type != VAL_MAP || !vm->map) return make_array_from_values(NULL, 0); + Map *m = (Map *)vm->map; + if (m->count <= 0) return make_array_from_values(NULL, 0); + Value *tmp = (Value *)malloc(sizeof(Value) * m->count); + if (!tmp) return make_array_from_values(NULL, 0); + for (int i = 0; i < m->count; ++i) { + tmp[i] = copy_value(&m->vals[i]); + } + Value arr = make_array_from_values(tmp, m->count); + for (int i = 0; i < m->count; ++i) + free_value(tmp[i]); + free(tmp); + return arr; } diff --git a/src/parser.c b/src/parser.c index a0f22e4..cfa7963 100644 --- a/src/parser.c +++ b/src/parser.c @@ -8,7 +8,7 @@ */ /** -* @file parser.c + * @file parser.c * @brief Implements the Fun language parser that converts source code to bytecode. * * This file contains the main parsing logic for the Fun programming language. @@ -46,11 +46,11 @@ #include "parser.h" #include "value.h" #include "vm.h" +#include +#include #include #include #include -#include -#include /* ---- parser error state ---- */ static const char *g_current_source_path = NULL; /* for propagating filename into nested bytecodes */ @@ -58,7 +58,7 @@ static int g_has_error = 0; static size_t g_err_pos = 0; static char g_err_msg[256]; static int g_err_line = 0; -static int g_err_col = 0; +static int g_err_col = 0; /* ---- compiler-generated temporary counter ---- */ static int g_temp_counter = 0; @@ -68,31 +68,35 @@ static int g_temp_counter = 0; positive/negative 8/16/32/64 = integers (negative means signed); TYPE_META_STRING/BOOLEAN/NIL mark non-integer enforced types; TYPE_META_CLASS marks class instances (Map with "__class"). */ -#define TYPE_META_STRING 10001 +#define TYPE_META_STRING 10001 #define TYPE_META_BOOLEAN 10002 -#define TYPE_META_NIL 10003 -#define TYPE_META_CLASS 10004 -#define TYPE_META_FLOAT 10005 -#define TYPE_META_ARRAY 10006 +#define TYPE_META_NIL 10003 +#define TYPE_META_CLASS 10004 +#define TYPE_META_FLOAT 10005 +#define TYPE_META_ARRAY 10006 static void parser_fail(size_t pos, const char *fmt, ...) { - g_has_error = 1; - g_err_pos = pos; - va_list ap; - va_start(ap, fmt); - vsnprintf(g_err_msg, sizeof(g_err_msg), fmt, ap); - va_end(ap); + g_has_error = 1; + g_err_pos = pos; + va_list ap; + va_start(ap, fmt); + vsnprintf(g_err_msg, sizeof(g_err_msg), fmt, ap); + va_end(ap); } static void calc_line_col(const char *src, size_t len, size_t pos, int *out_line, int *out_col) { - int line = 1, col = 1; - size_t limit = pos < len ? pos : len; - for (size_t i = 0; i < limit; ++i) { - if (src[i] == '\n') { line++; col = 1; } - else { col++; } + int line = 1, col = 1; + size_t limit = pos < len ? pos : len; + for (size_t i = 0; i < limit; ++i) { + if (src[i] == '\n') { + line++; + col = 1; + } else { + col++; } - if (out_line) *out_line = line; - if (out_col) *out_col = col; + } + if (out_line) *out_line = line; + if (out_col) *out_col = col; } /* --------------------------- */ @@ -101,120 +105,123 @@ static void calc_line_col(const char *src, size_t len, size_t pos, int *out_line We scan preprocessed source for lines starting with "// __ns_alias__: " and treat dot-calls on those identifiers as plain function calls (no implicit 'this'). */ static char *g_ns_aliases[64]; -static int g_ns_alias_count = 0; +static int g_ns_alias_count = 0; static void ns_aliases_reset(void) { - for (int i = 0; i < g_ns_alias_count; ++i) { - free(g_ns_aliases[i]); - g_ns_aliases[i] = NULL; - } - g_ns_alias_count = 0; + for (int i = 0; i < g_ns_alias_count; ++i) { + free(g_ns_aliases[i]); + g_ns_aliases[i] = NULL; + } + g_ns_alias_count = 0; } static void ns_aliases_scan(const char *src, size_t len) { - const char *marker = "// __ns_alias__: "; - size_t mlen = strlen(marker); - size_t i = 0; - while (i < len) { - /* find start of line */ - size_t ls = i; - /* move to end of line first */ - while (i < len && src[i] != '\n') i++; - size_t le = i; - /* include trailing '\n' in next iteration */ - if (i < len && src[i] == '\n') i++; + const char *marker = "// __ns_alias__: "; + size_t mlen = strlen(marker); + size_t i = 0; + while (i < len) { + /* find start of line */ + size_t ls = i; + /* move to end of line first */ + while (i < len && src[i] != '\n') + i++; + size_t le = i; + /* include trailing '\n' in next iteration */ + if (i < len && src[i] == '\n') i++; - if (le - ls >= mlen && strncmp(src + ls, marker, mlen) == 0) { - size_t p = ls + mlen; - /* read identifier */ - size_t start = p; - while (p < le && (src[p] == ' ' || src[p] == '\t')) p++; - if (p < le && (isalpha((unsigned char)src[p]) || src[p] == '_')) { - size_t q = p + 1; - while (q < le && (isalnum((unsigned char)src[q]) || src[q] == '_')) q++; - size_t n = q - p; - if (n > 0 && g_ns_alias_count < (int)(sizeof(g_ns_aliases)/sizeof(g_ns_aliases[0]))) { - char *name = (char*)malloc(n + 1); - if (name) { - memcpy(name, src + p, n); - name[n] = '\0'; - g_ns_aliases[g_ns_alias_count++] = name; - } - } - } + if (le - ls >= mlen && strncmp(src + ls, marker, mlen) == 0) { + size_t p = ls + mlen; + /* read identifier */ + size_t start = p; + while (p < le && (src[p] == ' ' || src[p] == '\t')) + p++; + if (p < le && (isalpha((unsigned char)src[p]) || src[p] == '_')) { + size_t q = p + 1; + while (q < le && (isalnum((unsigned char)src[q]) || src[q] == '_')) + q++; + size_t n = q - p; + if (n > 0 && g_ns_alias_count < (int)(sizeof(g_ns_aliases) / sizeof(g_ns_aliases[0]))) { + char *name = (char *)malloc(n + 1); + if (name) { + memcpy(name, src + p, n); + name[n] = '\0'; + g_ns_aliases[g_ns_alias_count++] = name; + } } + } } + } } static int is_ns_alias(const char *name) { - if (!name) return 0; - for (int i = 0; i < g_ns_alias_count; ++i) { - if (strcmp(g_ns_aliases[i], name) == 0) return 1; - } - return 0; + if (!name) return 0; + for (int i = 0; i < g_ns_alias_count; ++i) { + if (strcmp(g_ns_aliases[i], name) == 0) return 1; + } + return 0; } #include "parser_utils.c" /* 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; negative for signed */ - int is_class[MAX_GLOBALS];/* 1 if this global name denotes a class factory */ - int count; -} G = { {0}, {0}, {0}, 0 }; + char *names[MAX_GLOBALS]; + int types[MAX_GLOBALS]; /* 0=untyped/number default; else bit width: 8/16/32/64; negative for signed */ + int is_class[MAX_GLOBALS]; /* 1 if this global name denotes a class factory */ + int count; +} G = {{0}, {0}, {0}, 0}; static int sym_index(const char *name) { - for (int i = 0; i < G.count; ++i) { - if (strcmp(G.names[i], name) == 0) return i; - } - if (G.count >= MAX_GLOBALS) { - parser_fail(0, "Too many globals (max %d)", MAX_GLOBALS); - return 0; - } - G.names[G.count] = strdup(name); - G.types[G.count] = 0; /* default: untyped */ - G.is_class[G.count] = 0;/* default: not a class */ - return G.count++; + for (int i = 0; i < G.count; ++i) { + if (strcmp(G.names[i], name) == 0) return i; + } + if (G.count >= MAX_GLOBALS) { + parser_fail(0, "Too many globals (max %d)", MAX_GLOBALS); + return 0; + } + G.names[G.count] = strdup(name); + G.types[G.count] = 0; /* default: untyped */ + G.is_class[G.count] = 0; /* default: not a class */ + 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; + char *names[MAX_FRAME_LOCALS]; + int types[MAX_FRAME_LOCALS]; /* 0=untyped; else bit width: 8/16/32/64 */ + int count; } LocalEnv; static LocalEnv *g_locals = NULL; /* loop context for break/continue patching */ typedef struct LoopCtx { - int break_jumps[64]; - int break_count; - int continue_jumps[64]; - int cont_count; - struct LoopCtx *prev; + int break_jumps[64]; + int break_count; + int continue_jumps[64]; + int cont_count; + struct LoopCtx *prev; } LoopCtx; static LoopCtx *g_loop_ctx = NULL; static int local_find(const char *name) { - if (!g_locals) return -1; - for (int i = 0; i < g_locals->count; ++i) { - if (strcmp(g_locals->names[i], name) == 0) return i; - } - return -1; + if (!g_locals) return -1; + for (int i = 0; i < g_locals->count; ++i) { + if (strcmp(g_locals->names[i], name) == 0) return i; + } + return -1; } static int local_add(const char *name) { - if (!g_locals) return -1; - if (g_locals->count >= MAX_FRAME_LOCALS) { - parser_fail(0, "Too many local variables/parameters (max %d)", MAX_FRAME_LOCALS); - return -1; - } - int idx = g_locals->count++; - g_locals->names[idx] = strdup(name); - return idx; + if (!g_locals) return -1; + if (g_locals->count >= MAX_FRAME_LOCALS) { + parser_fail(0, "Too many local variables/parameters (max %d)", MAX_FRAME_LOCALS); + return -1; + } + int idx = g_locals->count++; + g_locals->names[idx] = strdup(name); + return idx; } /* forward declaration so helpers can recurse */ @@ -222,2521 +229,4591 @@ static int emit_expression(Bytecode *bc, const char *src, size_t len, size_t *po /* primary: (expr) | string | number | true/false | identifier */ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) { - skip_spaces(src, len, pos); + skip_spaces(src, len, pos); - /* parenthesized */ - if (*pos < len && src[*pos] == '(') { + /* parenthesized */ + if (*pos < len && src[*pos] == '(') { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression after '('"); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')'"); + return 0; + } + /* postfix indexing or slice */ + 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 savep4 = *pos; + if (!emit_expression(bc, src, len, pos)) { + *pos = savep4; + int ci4 = bytecode_add_constant(bc, make_int(-1)); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci4); + } + 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; + } + + /* string */ + char *s = parse_string_literal_any_quote(src, len, pos); + if (s) { + int ci = bytecode_add_constant(bc, make_string(s)); + free(s); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci); + /* postfix indexing or slice */ + 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); + /* end is optional; if missing, use -1 (till end) */ + int has_end = 0; + size_t savep = *pos; + if (emit_expression(bc, src, len, pos)) { + has_end = 1; + } else { + *pos = savep; + int ci = bytecode_add_constant(bc, make_int(-1)); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci); + } + 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; + } + + /* array literal: [expr, expr, ...] */ + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] == '[') { + (*pos)++; /* '[' */ + int count = 0; + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] != ']') { + for (;;) { + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression in array literal"); + return 0; + } + count++; + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + continue; + } + break; + } + } + if (!consume_char(src, len, pos, ']')) { + parser_fail(*pos, "Expected ']' to close array literal"); + return 0; + } + bytecode_add_instruction(bc, OP_MAKE_ARRAY, count); + /* postfix indexing or slice */ + 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 savep3 = *pos; + if (!emit_expression(bc, src, len, pos)) { + *pos = savep3; + int ci3 = bytecode_add_constant(bc, make_int(-1)); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci3); + } + 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; + } + + /* map literal: { "key": expr, ... } */ + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] == '{') { + (*pos)++; /* '{' */ + int pairs = 0; + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] != '}') { + for (;;) { + /* key must be a string literal */ + char *k = parse_string_literal_any_quote(src, len, pos); + if (!k) { + parser_fail(*pos, "Expected string key in map literal"); + return 0; + } + int kci = bytecode_add_constant(bc, make_string(k)); + free(k); + bytecode_add_instruction(bc, OP_LOAD_CONST, kci); + skip_spaces(src, len, pos); + if (!consume_char(src, len, pos, ':')) { + parser_fail(*pos, "Expected ':' after map key"); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "Expected value expression in map literal"); + return 0; + } + pairs++; + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + continue; + } + break; + } + } + if (!consume_char(src, len, pos, '}')) { + parser_fail(*pos, "Expected '}' to close map literal"); + return 0; + } + bytecode_add_instruction(bc, OP_MAKE_MAP, pairs); + return 1; + } + + /* 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)); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci); + /* postfix indexing or slice */ + 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); + int has_end = 0; + size_t savep2 = *pos; + if (emit_expression(bc, src, len, pos)) { + has_end = 1; + } else { + *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; + } + + /* identifier or keyword */ + char *name = NULL; + if (read_identifier_into(src, len, pos, &name)) { + if (strcmp(name, "true") == 0 || strcmp(name, "false") == 0) { + int ci = bytecode_add_constant(bc, make_bool(strcmp(name, "true") == 0 ? 1 : 0)); + free(name); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci); + return 1; + } + + /* call or variable load (locals preferred) */ + skip_spaces(src, len, pos); + int local_idx = local_find(name); + int is_call = (*pos < len && src[*pos] == '('); + + if (is_call) { + /* builtins */ + if (strcmp(name, "len") == 0) { (*pos)++; /* '(' */ if (!emit_expression(bc, src, len, pos)) { - parser_fail(*pos, "Expected expression after '('"); - return 0; + parser_fail(*pos, "len expects 1 argument"); + free(name); + return 0; } if (!consume_char(src, len, pos, ')')) { - parser_fail(*pos, "Expected ')'"); - return 0; - } - /* postfix indexing or slice */ - 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 savep4 = *pos; - if (!emit_expression(bc, src, len, pos)) { - *pos = savep4; - int ci4 = bytecode_add_constant(bc, make_int(-1)); - bytecode_add_instruction(bc, OP_LOAD_CONST, ci4); - } - 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; + parser_fail(*pos, "Expected ')' after len arg"); + free(name); + return 0; } + bytecode_add_instruction(bc, OP_LEN, 0); + free(name); return 1; - } - - /* string */ - char *s = parse_string_literal_any_quote(src, len, pos); - if (s) { - int ci = bytecode_add_constant(bc, make_string(s)); - free(s); - bytecode_add_instruction(bc, OP_LOAD_CONST, ci); - /* postfix indexing or slice */ - 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); - /* end is optional; if missing, use -1 (till end) */ - int has_end = 0; - size_t savep = *pos; - if (emit_expression(bc, src, len, pos)) { - has_end = 1; - } else { - *pos = savep; - int ci = bytecode_add_constant(bc, make_int(-1)); - bytecode_add_instruction(bc, OP_LOAD_CONST, ci); - } - 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; + } + if (strcmp(name, "push") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "push expects array"); + free(name); + return 0; } + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + } else { + parser_fail(*pos, "push expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "push expects value"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after push args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_PUSH, 0); + free(name); return 1; - } - - /* array literal: [expr, expr, ...] */ - skip_spaces(src, len, pos); - if (*pos < len && src[*pos] == '[') { - (*pos)++; /* '[' */ - int count = 0; - skip_spaces(src, len, pos); - if (*pos < len && src[*pos] != ']') { - for (;;) { - if (!emit_expression(bc, src, len, pos)) { - parser_fail(*pos, "Expected expression in array literal"); - return 0; - } - count++; - skip_spaces(src, len, pos); - if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); continue; } - break; - } + } + if (strcmp(name, "pop") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "pop expects array"); + free(name); + return 0; } - if (!consume_char(src, len, pos, ']')) { - parser_fail(*pos, "Expected ']' to close array literal"); - return 0; - } - bytecode_add_instruction(bc, OP_MAKE_ARRAY, count); - /* postfix indexing or slice */ - 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 savep3 = *pos; - if (!emit_expression(bc, src, len, pos)) { - *pos = savep3; - int ci3 = bytecode_add_constant(bc, make_int(-1)); - bytecode_add_instruction(bc, OP_LOAD_CONST, ci3); - } - 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; + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after pop arg"); + free(name); + return 0; } + bytecode_add_instruction(bc, OP_APOP, 0); + free(name); return 1; - } - - /* map literal: { "key": expr, ... } */ - skip_spaces(src, len, pos); - if (*pos < len && src[*pos] == '{') { - (*pos)++; /* '{' */ - int pairs = 0; - skip_spaces(src, len, pos); - if (*pos < len && src[*pos] != '}') { - for (;;) { - /* key must be a string literal */ - char *k = parse_string_literal_any_quote(src, len, pos); - if (!k) { parser_fail(*pos, "Expected string key in map literal"); return 0; } - int kci = bytecode_add_constant(bc, make_string(k)); - free(k); - bytecode_add_instruction(bc, OP_LOAD_CONST, kci); - skip_spaces(src, len, pos); - if (!consume_char(src, len, pos, ':')) { parser_fail(*pos, "Expected ':' after map key"); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected value expression in map literal"); return 0; } - pairs++; - skip_spaces(src, len, pos); - if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); continue; } - break; - } + } + if (strcmp(name, "set") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "set expects array"); + free(name); + return 0; } - if (!consume_char(src, len, pos, '}')) { - parser_fail(*pos, "Expected '}' to close map literal"); - return 0; + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + } else { + parser_fail(*pos, "set expects 3 args"); + free(name); + return 0; } - bytecode_add_instruction(bc, OP_MAKE_MAP, pairs); + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "set expects index"); + free(name); + return 0; + } + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + } else { + parser_fail(*pos, "set expects 3 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "set expects value"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after set args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SET, 0); + free(name); return 1; - } - - /* 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; + } + if (strcmp(name, "insert") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "insert expects array"); + free(name); + return 0; } + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + } else { + parser_fail(*pos, "insert expects 3 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "insert expects index"); + free(name); + return 0; + } + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + } else { + parser_fail(*pos, "insert expects 3 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "insert expects value"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after insert args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_INSERT, 0); + free(name); 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)); - bytecode_add_instruction(bc, OP_LOAD_CONST, ci); - /* postfix indexing or slice */ - 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); - int has_end = 0; - size_t savep2 = *pos; - if (emit_expression(bc, src, len, pos)) { - has_end = 1; - } else { - *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; + } + if (strcmp(name, "remove") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "remove expects array"); + free(name); + return 0; } + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + } else { + parser_fail(*pos, "remove expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "remove expects index"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after remove args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_REMOVE, 0); + free(name); return 1; - } - - /* identifier or keyword */ - char *name = NULL; - if (read_identifier_into(src, len, pos, &name)) { - if (strcmp(name, "true") == 0 || strcmp(name, "false") == 0) { - int ci = bytecode_add_constant(bc, make_bool(strcmp(name, "true") == 0 ? 1 : 0)); - free(name); - bytecode_add_instruction(bc, OP_LOAD_CONST, ci); - 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; } - - /* call or variable load (locals preferred) */ - skip_spaces(src, len, pos); - int local_idx = local_find(name); - int is_call = (*pos < len && src[*pos] == '('); - - if (is_call) { - /* builtins */ - if (strcmp(name, "len") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "len expects 1 argument"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after len arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_LEN, 0); - free(name); - return 1; - } - if (strcmp(name, "push") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "push expects array"); free(name); return 0; } - if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "push expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "push expects value"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after push args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_PUSH, 0); - free(name); - return 1; - } - if (strcmp(name, "pop") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pop expects array"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after pop arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_APOP, 0); - free(name); - return 1; - } - if (strcmp(name, "set") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "set expects array"); free(name); return 0; } - if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "set expects 3 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "set expects index"); free(name); return 0; } - if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "set expects 3 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "set expects value"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after set args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SET, 0); - free(name); - return 1; - } - if (strcmp(name, "insert") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "insert expects array"); free(name); return 0; } - if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "insert expects 3 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "insert expects index"); free(name); return 0; } - if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "insert expects 3 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "insert expects value"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after insert args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_INSERT, 0); - free(name); - return 1; - } - if (strcmp(name, "remove") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "remove expects array"); free(name); return 0; } - if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "remove expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "remove expects index"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after remove args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_REMOVE, 0); - 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; - } - if (strcmp(name, "cast") == 0) { - (*pos)++; /* '(' */ - /* cast(value, typeName) */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "cast expects (value, typeName)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "cast expects (value, typeName)"); free(name); return 0; } - bytecode_add_instruction(bc, OP_CAST, 0); - free(name); - return 1; - } - if (strcmp(name, "typeof") == 0) { - (*pos)++; /* '(' */ - /* Special handling for typeof() to return declared subtype for integers */ - size_t peek = *pos; - char *vname = NULL; - int handled = 0; - if (read_identifier_into(src, len, &peek, &vname)) { - skip_spaces(src, len, &peek); - if (peek < len && src[peek] == ')') { - int meta = 0; - int lidx = local_find(vname); - if (lidx >= 0) { - meta = g_locals->types[lidx]; - } else { - int gi = sym_index(vname); - if (gi >= 0) meta = G.types[gi]; - } - - if (meta != 0 && meta != TYPE_META_STRING && meta != TYPE_META_BOOLEAN && meta != TYPE_META_NIL && meta != TYPE_META_CLASS && meta != TYPE_META_FLOAT) { - /* Integer subtype: ±bits */ - int abs_bits = meta < 0 ? -meta : meta; - const char *tname = (meta < 0) - ? (abs_bits==64? "Sint64" : (abs_bits==32? "Sint32" : (abs_bits==16? "Sint16" : "Sint8"))) - : (abs_bits==64? "Uint64" : (abs_bits==32? "Uint32" : (abs_bits==16? "Uint16" : "Uint8"))); - int ci = bytecode_add_constant(bc, make_string(tname)); - bytecode_add_instruction(bc, OP_LOAD_CONST, ci); - *pos = peek + 1; /* consume name and ')' */ - handled = 1; - } - free(vname); - } else { - free(vname); - } - } - - if (!handled) { - /* General case: typeof(expression) - If the value is a Map with "__class" key, return that string; else return base typeof. - */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "typeof expects 1 argument"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after typeof arg"); free(name); return 0; } - - /* [v] */ - bytecode_add_instruction(bc, OP_DUP, 0); /* [v, v] */ - bytecode_add_instruction(bc, OP_TYPEOF, 0); /* [v, tname] */ - { - int ciMap = bytecode_add_constant(bc, make_string("Map")); - bytecode_add_instruction(bc, OP_LOAD_CONST, ciMap); /* [v, tname, "Map"] */ - } - bytecode_add_instruction(bc, OP_EQ, 0); /* [v, isMap] */ - int j_if_not_map = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); - - /* Map branch: [v] */ - bytecode_add_instruction(bc, OP_DUP, 0); /* [v, v] */ - { - int kci = bytecode_add_constant(bc, make_string("__class")); - bytecode_add_instruction(bc, OP_LOAD_CONST, kci); /* [v, v, "__class"] */ - } - bytecode_add_instruction(bc, OP_HAS_KEY, 0); /* [v, has] */ - int j_no_meta = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); - /* has __class: try toString() -> return its result */ - bytecode_add_instruction(bc, OP_DUP, 0); /* [v, v] */ - { - int kcits = bytecode_add_constant(bc, make_string("toString")); - bytecode_add_instruction(bc, OP_LOAD_CONST, kcits); /* [v, v, "toString"] */ - } - bytecode_add_instruction(bc, OP_INDEX_GET, 0); /* [v, func] */ - bytecode_add_instruction(bc, OP_SWAP, 0); /* [func, v] */ - bytecode_add_instruction(bc, OP_CALL, 1); /* [string] */ - int j_end = bytecode_add_instruction(bc, OP_JUMP, 0); - - /* no meta: drop v and return "Map" */ - bytecode_set_operand(bc, j_no_meta, bc->instr_count); - bytecode_add_instruction(bc, OP_POP, 0); /* [] */ - { - int ciMap2 = bytecode_add_constant(bc, make_string("Map")); - bytecode_add_instruction(bc, OP_LOAD_CONST, ciMap2); /* ["Map"] */ - } - int j_end2 = bytecode_add_instruction(bc, OP_JUMP, 0); - int after_map = bc->instr_count; - - /* not map: compute typeof(v) */ - bytecode_set_operand(bc, j_if_not_map, after_map); - bytecode_add_instruction(bc, OP_TYPEOF, 0); /* [tname] */ - - /* end */ - bytecode_set_operand(bc, j_end, bc->instr_count); - bytecode_set_operand(bc, j_end2, bc->instr_count); - } - - free(name); - return 1; - } - if (strcmp(name, "keys") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "keys expects 1 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_KEYS, 0); - free(name); - return 1; - } - if (strcmp(name, "values") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "values expects 1 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_VALUES, 0); - free(name); - return 1; - } - if (strcmp(name, "has") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "has expects (map, key)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "has expects (map, key)"); free(name); return 0; } - bytecode_add_instruction(bc, OP_HAS_KEY, 0); - free(name); - return 1; - } - if (strcmp(name, "read_file") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "read_file expects 1 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_READ_FILE, 0); - free(name); - return 1; - } - if (strcmp(name, "write_file") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "write_file expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "write_file expects 2 args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_WRITE_FILE, 0); - free(name); - return 1; - } - if (strcmp(name, "input") == 0) { - (*pos)++; /* '(' */ - int hasPrompt = 0; - skip_spaces(src, len, pos); - if (*pos < len && src[*pos] != ')') { - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "input expects 0 or 1 argument"); free(name); return 0; } - hasPrompt = 1; - } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after input arg(s)"); free(name); return 0; } - bytecode_add_instruction(bc, OP_INPUT_LINE, hasPrompt ? 1 : 0); - free(name); - return 1; - } - if (strcmp(name, "input_hidden") == 0) { - (*pos)++; /* '(' */ - int hasPrompt = 0; - skip_spaces(src, len, pos); - if (*pos < len && src[*pos] != ')') { - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "input_hidden expects 0 or 1 argument"); free(name); return 0; } - hasPrompt = 1; - } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after input_hidden arg(s)"); free(name); return 0; } - /* operand bit0 = hasPrompt, bit1 = hidden */ - bytecode_add_instruction(bc, OP_INPUT_LINE, (hasPrompt ? 1 : 0) | 2); - free(name); - return 1; - } - if (strcmp(name, "proc_run") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "proc_run expects 1 argument (command string)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after proc_run arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_PROC_RUN, 0); - free(name); - return 1; - } - if (strcmp(name, "system") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "system expects 1 argument (command string)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after system arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_PROC_SYSTEM, 0); - free(name); - return 1; - } - if (strcmp(name, "time_now_ms") == 0) { - (*pos)++; /* '(' */ - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "time_now_ms expects ()"); free(name); return 0; } - bytecode_add_instruction(bc, OP_TIME_NOW_MS, 0); - free(name); - return 1; - } - if (strcmp(name, "clock_mono_ms") == 0) { - (*pos)++; /* '(' */ - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "clock_mono_ms expects ()"); free(name); return 0; } - bytecode_add_instruction(bc, OP_CLOCK_MONO_MS, 0); - free(name); - return 1; - } - if (strcmp(name, "date_format") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "date_format expects (ms:int, fmt:string)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "date_format expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "date_format expects (ms:int, fmt:string)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after date_format args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_DATE_FORMAT, 0); - free(name); - return 1; - } - if (strcmp(name, "env") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "env expects 1 argument"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after env arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_ENV, 0); - free(name); - return 1; - } - if (strcmp(name, "env_all") == 0) { - (*pos)++; /* '(' */ - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "env_all expects ()"); free(name); return 0; } - bytecode_add_instruction(bc, OP_ENV_ALL, 0); - free(name); - return 1; - } - if (strcmp(name, "fun_version") == 0) { - (*pos)++; /* '(' */ - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "fun_version expects ()"); free(name); return 0; } - bytecode_add_instruction(bc, OP_FUN_VERSION, 0); - free(name); - return 1; - } - if (strcmp(name, "rust_hello") == 0) { - (*pos)++; /* '(' */ - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "rust_hello expects ()"); free(name); return 0; } - bytecode_add_instruction(bc, OP_RUST_HELLO, 0); - free(name); - return 1; - } - if (strcmp(name, "rust_hello_args") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "rust_hello_args expects (message:string)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after rust_hello_args arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_RUST_HELLO_ARGS, 0); - free(name); - return 1; - } - if (strcmp(name, "rust_hello_args_return") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "rust_hello_args_return expects (message:string)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after rust_hello_args_return arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_RUST_HELLO_ARGS_RETURN, 0); - free(name); - return 1; - } - if (strcmp(name, "rust_get_sp") == 0) { - (*pos)++; /* '(' */ - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "rust_get_sp expects ()"); free(name); return 0; } - bytecode_add_instruction(bc, OP_RUST_GET_SP, 0); - free(name); - return 1; - } - if (strcmp(name, "rust_set_exit") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "rust_set_exit expects (code:int)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after rust_set_exit arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_RUST_SET_EXIT, 0); - free(name); - return 1; - } - if (strcmp(name, "cpp_add") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "cpp_add expects (a:int, b:int)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "cpp_add expects two arguments"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "cpp_add expects (a:int, b:int)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after cpp_add args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_CPP_ADD, 0); - free(name); - return 1; - } - if (strcmp(name, "os_list_dir") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "os_list_dir expects (path)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after os_list_dir arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_OS_LIST_DIR, 0); - free(name); - return 1; - } - /* JSON builtins */ - if (strcmp(name, "json_parse") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "json_parse expects (text)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after json_parse arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_JSON_PARSE, 0); - free(name); - return 1; - } - /* XML builtins (minimal) */ - if (strcmp(name, "xml_parse") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "xml_parse expects (text)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after xml_parse arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_XML_PARSE, 0); - free(name); - return 1; - } - if (strcmp(name, "xml_root") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "xml_root expects (doc_handle)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after xml_root arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_XML_ROOT, 0); - free(name); - return 1; - } - if (strcmp(name, "xml_name") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "xml_name expects (node_handle)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after xml_name arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_XML_NAME, 0); - free(name); - return 1; - } - if (strcmp(name, "xml_text") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "xml_text expects (node_handle)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after xml_text arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_XML_TEXT, 0); - free(name); - return 1; - } - if (strcmp(name, "json_stringify") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "json_stringify expects (value, pretty)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "json_stringify expects (value, pretty)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "json_stringify expects (value, pretty)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after json_stringify args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_JSON_STRINGIFY, 0); - free(name); - return 1; - } - /* Tk (GUI) builtins (no raw Tcl exposed) */ - if (strcmp(name, "tk_loop") == 0) { - (*pos)++; /* '(' */ - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "tk_loop expects ()"); free(name); return 0; } - bytecode_add_instruction(bc, OP_TK_LOOP, 0); - free(name); - return 1; - } - if (strcmp(name, "tk_title") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tk_title expects (title:string)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after tk_title arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_TK_WM_TITLE, 0); - free(name); - return 1; - } - if (strcmp(name, "tk_label") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tk_label expects (id:string, text:string)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "tk_label expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tk_label expects (id:string, text:string)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after tk_label args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_TK_LABEL, 0); - free(name); - return 1; - } - if (strcmp(name, "tk_button") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tk_button expects (id:string, text:string)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "tk_button expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tk_button expects (id:string, text:string)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after tk_button args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_TK_BUTTON, 0); - free(name); - return 1; - } - if (strcmp(name, "tk_pack") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tk_pack expects (id:string)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after tk_pack arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_TK_PACK, 0); - free(name); - return 1; - } - if (strcmp(name, "tk_bind") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tk_bind expects (id, event, cmd)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "tk_bind expects 3 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tk_bind expects 3 args"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "tk_bind expects 3 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tk_bind expects 3 args"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after tk_bind args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_TK_BIND, 0); - free(name); - return 1; - } - if (strcmp(name, "tk_eval") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tk_eval expects (script)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after tk_eval arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_TK_EVAL, 0); - free(name); - return 1; - } - if (strcmp(name, "tk_result") == 0) { - (*pos)++; /* '(' */ - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "tk_result expects ()"); free(name); return 0; } - bytecode_add_instruction(bc, OP_TK_RESULT, 0); - free(name); - return 1; - } - if (strcmp(name, "json_from_file") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "json_from_file expects (path)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after json_from_file arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_JSON_FROM_FILE, 0); - free(name); - return 1; - } - if (strcmp(name, "json_to_file") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "json_to_file expects (path, value, pretty)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "json_to_file expects (path, value, pretty)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "json_to_file expects (path, value, pretty)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "json_to_file expects (path, value, pretty)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "json_to_file expects (path, value, pretty)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after json_to_file args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_JSON_TO_FILE, 0); - free(name); - return 1; - } - /* INI (iniparser 4.2.6) builtins */ - if (strcmp(name, "ini_load") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_load expects (path)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after ini_load arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_INI_LOAD, 0); - free(name); - return 1; - } - if (strcmp(name, "ini_free") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_free expects (handle)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after ini_free arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_INI_FREE, 0); - free(name); - return 1; - } - if (strcmp(name, "ini_get_string") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_string expects (handle, section, key, default)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_get_string expects 4 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_string expects 4 args"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_get_string expects 4 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_string expects 4 args"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_get_string expects 4 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_string expects 4 args"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after ini_get_string args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_INI_GET_STRING, 0); - free(name); - return 1; - } - if (strcmp(name, "ini_get_int") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_int expects (handle, section, key, default)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_get_int expects 4 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_int expects 4 args"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_get_int expects 4 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_int expects 4 args"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_get_int expects 4 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_int expects 4 args"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after ini_get_int args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_INI_GET_INT, 0); - free(name); - return 1; - } - if (strcmp(name, "ini_get_double") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_double expects (handle, section, key, default)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_get_double expects 4 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_double expects 4 args"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_get_double expects 4 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_double expects 4 args"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_get_double expects 4 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_double expects 4 args"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after ini_get_double args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_INI_GET_DOUBLE, 0); - free(name); - return 1; - } - if (strcmp(name, "ini_get_bool") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_bool expects (handle, section, key, default)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_get_bool expects 4 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_bool expects 4 args"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_get_bool expects 4 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_bool expects 4 args"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_get_bool expects 4 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_bool expects 4 args"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after ini_get_bool args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_INI_GET_BOOL, 0); - free(name); - return 1; - } - if (strcmp(name, "ini_set") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_set expects (handle, section, key, value)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_set expects 4 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_set expects 4 args"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_set expects 4 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_set expects 4 args"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_set expects 4 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_set expects 4 args"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after ini_set args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_INI_SET, 0); - free(name); - return 1; - } - if (strcmp(name, "ini_unset") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_unset expects (handle, section, key)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_unset expects 3 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_unset expects 3 args"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_unset expects 3 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_unset expects 3 args"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after ini_unset args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_INI_UNSET, 0); - free(name); - return 1; - } - if (strcmp(name, "ini_save") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_save expects (handle, path)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_save expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_save expects 2 args"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after ini_save args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_INI_SAVE, 0); - free(name); - return 1; - } - /* CURL builtins (minimal interface like JSON) */ - if (strcmp(name, "curl_get") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "curl_get expects (url)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after curl_get arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_CURL_GET, 0); - free(name); - return 1; - } - /* SQLite builtins */ - if (strcmp(name, "sqlite_open") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "sqlite_open expects (path)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after sqlite_open arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SQLITE_OPEN, 0); - free(name); - return 1; - } - if (strcmp(name, "sqlite_close") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "sqlite_close expects (handle)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after sqlite_close arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SQLITE_CLOSE, 0); - free(name); - return 1; - } - if (strcmp(name, "sqlite_exec") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "sqlite_exec expects (handle, sql)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "sqlite_exec expects (handle, sql)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "sqlite_exec expects (handle, sql)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after sqlite_exec args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SQLITE_EXEC, 0); - free(name); - return 1; - } - if (strcmp(name, "sqlite_query") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "sqlite_query expects (handle, sql)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "sqlite_query expects (handle, sql)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "sqlite_query expects (handle, sql)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after sqlite_query args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SQLITE_QUERY, 0); - free(name); - return 1; - } - /* libsql builtins (independent extension) */ - if (strcmp(name, "libsql_open") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "libsql_open expects (url_or_path)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after libsql_open arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_LIBSQL_OPEN, 0); - free(name); - return 1; - } - if (strcmp(name, "libsql_close") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "libsql_close expects (handle)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after libsql_close arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_LIBSQL_CLOSE, 0); - free(name); - return 1; - } - if (strcmp(name, "libsql_exec") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "libsql_exec expects (handle, sql)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "libsql_exec expects (handle, sql)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "libsql_exec expects (handle, sql)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after libsql_exec args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_LIBSQL_EXEC, 0); - free(name); - return 1; - } - if (strcmp(name, "libsql_query") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "libsql_query expects (handle, sql)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "libsql_query expects (handle, sql)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "libsql_query expects (handle, sql)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after libsql_query args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_LIBSQL_QUERY, 0); - free(name); - return 1; - } - if (strcmp(name, "curl_post") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "curl_post expects (url, body)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "curl_post expects (url, body)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "curl_post expects (url, body)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after curl_post args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_CURL_POST, 0); - free(name); - return 1; - } - if (strcmp(name, "curl_download") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "curl_download expects (url, path)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "curl_download expects (url, path)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "curl_download expects (url, path)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after curl_download args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_CURL_DOWNLOAD, 0); - free(name); - return 1; - } - /* OpenSSL (md5/sha256/sha512) */ - if (strcmp(name, "openssl_md5") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "openssl_md5 expects (data)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after openssl_md5 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_OPENSSL_MD5, 0); - free(name); - return 1; - } - if (strcmp(name, "openssl_sha256") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "openssl_sha256 expects (data)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after openssl_sha256 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_OPENSSL_SHA256, 0); - free(name); - return 1; - } - if (strcmp(name, "openssl_sha512") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "openssl_sha512 expects (data)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after openssl_sha512 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_OPENSSL_SHA512, 0); - free(name); - return 1; - } - if (strcmp(name, "openssl_ripemd160") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "openssl_ripemd160 expects (data)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after openssl_ripemd160 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_OPENSSL_RIPEMD160, 0); - free(name); - return 1; - } - /* LibreSSL (md5/sha256/sha512/ripemd160) */ - if (strcmp(name, "libressl_md5") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "libressl_md5 expects (data)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after libressl_md5 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_LIBRESSL_MD5, 0); - free(name); - return 1; - } - if (strcmp(name, "libressl_sha256") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "libressl_sha256 expects (data)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after libressl_sha256 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_LIBRESSL_SHA256, 0); - free(name); - return 1; - } - if (strcmp(name, "libressl_sha512") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "libressl_sha512 expects (data)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after libressl_sha512 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_LIBRESSL_SHA512, 0); - free(name); - return 1; - } - if (strcmp(name, "libressl_ripemd160") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "libressl_ripemd160 expects (data)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after libressl_ripemd160 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_LIBRESSL_RIPEMD160, 0); - free(name); - return 1; - } - /* PCSC builtins */ - if (strcmp(name, "pcsc_establish") == 0) { - (*pos)++; /* '(' */ - /* no args */ - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "pcsc_establish expects ()"); free(name); return 0; } - bytecode_add_instruction(bc, OP_PCSC_ESTABLISH, 0); - free(name); - return 1; - } - /* PCRE2 builtins */ - if (strcmp(name, "pcre2_test") == 0) { - (*pos)++; /* '(' */ - /* (pattern, text, flags) */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_test expects (pattern, text, flags)" ); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "pcre2_test expects (pattern, text, flags)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_test expects (pattern, text, flags)" ); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "pcre2_test expects (pattern, text, flags)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_test expects (pattern, text, flags)" ); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after pcre2_test args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_PCRE2_TEST, 0); - free(name); - return 1; - } - if (strcmp(name, "pcre2_match") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_match expects (pattern, text, flags)" ); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "pcre2_match expects (pattern, text, flags)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_match expects (pattern, text, flags)" ); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "pcre2_match expects (pattern, text, flags)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_match expects (pattern, text, flags)" ); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after pcre2_match args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_PCRE2_MATCH, 0); - free(name); - return 1; - } - if (strcmp(name, "pcre2_findall") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)" ); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)" ); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)" ); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after pcre2_findall args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_PCRE2_FINDALL, 0); - free(name); - return 1; - } - /* Notcurses builtins (optional) */ - if (strcmp(name, "nc_init") == 0) { - (*pos)++; /* '(' */ - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "nc_init expects ()"); free(name); return 0; } - bytecode_add_instruction(bc, OP_NC_INIT, 0); - free(name); - return 1; - } - if (strcmp(name, "nc_shutdown") == 0) { - (*pos)++; /* '(' */ - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "nc_shutdown expects ()"); free(name); return 0; } - bytecode_add_instruction(bc, OP_NC_SHUTDOWN, 0); - free(name); - return 1; - } - if (strcmp(name, "nc_clear") == 0) { - (*pos)++; /* '(' */ - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "nc_clear expects ()"); free(name); return 0; } - bytecode_add_instruction(bc, OP_NC_CLEAR, 0); - free(name); - return 1; - } - if (strcmp(name, "nc_draw_text") == 0) { - (*pos)++; /* '(' */ - /* (y, x, text) -> push y, x, text, then opcode will pop text,x,y */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_text expects (y, x, text)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_draw_text expects (y, x, text)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_text expects (y, x, text)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "nc_draw_text expects (y, x, text)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_draw_text expects (y, x, text)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after nc_draw_text args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_NC_DRAW_TEXT, 0); - free(name); - return 1; - } - if (strcmp(name, "nc_getch") == 0) { - (*pos)++; /* '(' */ - /* (timeout_ms) */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "nc_getch expects (timeout_ms)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after nc_getch arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_NC_GETCH, 0); - free(name); - return 1; - } - if (strcmp(name, "pcsc_release") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcsc_release expects 1 argument (ctx)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after pcsc_release arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_PCSC_RELEASE, 0); - free(name); - return 1; - } - if (strcmp(name, "pcsc_list_readers") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcsc_list_readers expects 1 argument (ctx)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after pcsc_list_readers arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_PCSC_LIST_READERS, 0); - free(name); - return 1; - } - if (strcmp(name, "pcsc_connect") == 0) { - (*pos)++; /* '(' */ - /* ctx, reader */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcsc_connect expects (ctx, reader)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "pcsc_connect expects (ctx, reader)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcsc_connect expects (ctx, reader)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after pcsc_connect args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_PCSC_CONNECT, 0); - free(name); - return 1; - } - if (strcmp(name, "pcsc_disconnect") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcsc_disconnect expects 1 argument (handle)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after pcsc_disconnect arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_PCSC_DISCONNECT, 0); - free(name); - return 1; - } - if (strcmp(name, "pcsc_transmit") == 0) { - (*pos)++; /* '(' */ - /* handle, bytes */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcsc_transmit expects (handle, bytes)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "pcsc_transmit expects (handle, bytes)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcsc_transmit expects (handle, bytes)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after pcsc_transmit args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_PCSC_TRANSMIT, 0); - free(name); - return 1; - } - /* Socket builtins */ - if (strcmp(name, "tcp_listen") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tcp_listen expects (port, backlog)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "tcp_listen expects (port, backlog)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tcp_listen expects (port, backlog)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after tcp_listen args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SOCK_TCP_LISTEN, 0); - free(name); - return 1; - } - if (strcmp(name, "tcp_accept") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tcp_accept expects (listen_fd)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after tcp_accept arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SOCK_TCP_ACCEPT, 0); - free(name); - return 1; - } - if (strcmp(name, "tcp_connect") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tcp_connect expects (host, port)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "tcp_connect expects (host, port)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tcp_connect expects (host, port)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after tcp_connect args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SOCK_TCP_CONNECT, 0); - free(name); - return 1; - } - if (strcmp(name, "sock_send") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "sock_send expects (fd, data)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "sock_send expects (fd, data)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "sock_send expects (fd, data)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after sock_send args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SOCK_SEND, 0); - free(name); - return 1; - } - if (strcmp(name, "sock_recv") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "sock_recv expects (fd, maxlen)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "sock_recv expects (fd, maxlen)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "sock_recv expects (fd, maxlen)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after sock_recv args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SOCK_RECV, 0); - free(name); - return 1; - } - if (strcmp(name, "sock_close") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "sock_close expects (fd)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after sock_close arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SOCK_CLOSE, 0); - free(name); - return 1; - } - if (strcmp(name, "unix_listen") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "unix_listen expects (path, backlog)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "unix_listen expects (path, backlog)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "unix_listen expects (path, backlog)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after unix_listen args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SOCK_UNIX_LISTEN, 0); - free(name); - return 1; - } - if (strcmp(name, "unix_connect") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "unix_connect expects (path)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after unix_connect arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SOCK_UNIX_CONNECT, 0); - free(name); - return 1; - } - /* Serial builtins */ - if (strcmp(name, "serial_open") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "serial_open expects (path, baud)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "serial_open expects (path, baud)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "serial_open expects (path, baud)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after serial_open args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SERIAL_OPEN, 0); - free(name); - return 1; - } - if (strcmp(name, "serial_config") == 0) { - (*pos)++; /* '(' */ - // fd, data_bits, parity, stop_bits, flow_control - for (int i = 0; i < 5; ++i) { - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "serial_config expects 5 arguments"); free(name); return 0; } - if (i < 4) { - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "serial_config expects 5 arguments"); free(name); return 0; } - } - } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after serial_config args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SERIAL_CONFIG, 0); - free(name); - return 1; - } - if (strcmp(name, "serial_send") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "serial_send expects (fd, data)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "serial_send expects (fd, data)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "serial_send expects (fd, data)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after serial_send args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SERIAL_SEND, 0); - free(name); - return 1; - } - if (strcmp(name, "serial_recv") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "serial_recv expects (fd, maxlen)"); free(name); return 0; } - if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "serial_recv expects (fd, maxlen)"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "serial_recv expects (fd, maxlen)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after serial_recv args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SERIAL_RECV, 0); - free(name); - return 1; - } - if (strcmp(name, "serial_close") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "serial_close expects (fd)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after serial_close arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SERIAL_CLOSE, 0); - free(name); - return 1; - } - /* string ops */ - if (strcmp(name, "split") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "split expects string"); free(name); return 0; } - if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "split expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "split expects separator"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after split args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SPLIT, 0); - free(name); - return 1; - } - if (strcmp(name, "join") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "join expects array"); free(name); return 0; } - if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "join expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "join expects separator"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after join args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_JOIN, 0); - free(name); - return 1; - } - if (strcmp(name, "substr") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "substr expects string"); free(name); return 0; } - if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "substr expects 3 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "substr expects start"); free(name); return 0; } - if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "substr expects 3 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "substr expects len"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after substr args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SUBSTR, 0); - free(name); - return 1; - } - if (strcmp(name, "find") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "find expects haystack"); free(name); return 0; } - if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "find expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "find expects needle"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after find args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_FIND, 0); - free(name); - return 1; - } - /* regex ops */ - if (strcmp(name, "regex_match") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "regex_match expects text"); free(name); return 0; } - if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "regex_match expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "regex_match expects pattern"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after regex_match args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_REGEX_MATCH, 0); - free(name); - return 1; - } - if (strcmp(name, "regex_search") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "regex_search expects text"); free(name); return 0; } - if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "regex_search expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "regex_search expects pattern"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after regex_search args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_REGEX_SEARCH, 0); - free(name); - return 1; - } - if (strcmp(name, "regex_replace") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "regex_replace expects text"); free(name); return 0; } - if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "regex_replace expects 3 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "regex_replace expects pattern"); free(name); return 0; } - if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "regex_replace expects 3 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "regex_replace expects replacement"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after regex_replace args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_REGEX_REPLACE, 0); - free(name); - return 1; - } - /* array utils */ - if (strcmp(name, "contains") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "contains expects array"); free(name); return 0; } - if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "contains expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "contains expects value"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after contains args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_CONTAINS, 0); - free(name); - return 1; - } - if (strcmp(name, "indexOf") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "indexOf expects array"); free(name); return 0; } - if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "indexOf expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "indexOf expects value"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after indexOf args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_INDEX_OF, 0); - free(name); - return 1; - } - if (strcmp(name, "clear") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "clear expects array"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after clear arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_CLEAR, 0); - free(name); - return 1; - } - /* iteration helpers */ - if (strcmp(name, "enumerate") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "enumerate expects array"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after enumerate arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_ENUMERATE, 0); - free(name); - return 1; - } - if (strcmp(name, "map") == 0) { - (*pos)++; /* '(' */ - /* arr */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "map expects (array, function)"); free(name); return 0; } - /* store arr -> __map_arr */ - char tarr[64]; snprintf(tarr, sizeof(tarr), "__map_arr_%d", g_temp_counter++); - int larr = -1, garr = -1; - if (g_locals) { larr = local_add(tarr); bytecode_add_instruction(bc, OP_STORE_LOCAL, larr); } - else { garr = sym_index(tarr); bytecode_add_instruction(bc, OP_STORE_GLOBAL, garr); } - /* func */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "map expects (array, function)"); free(name); return 0; } - char tfn[64]; snprintf(tfn, sizeof(tfn), "__map_fn_%d", g_temp_counter++); - int lfn = -1, gfn = -1; - if (g_locals) { lfn = local_add(tfn); bytecode_add_instruction(bc, OP_STORE_LOCAL, lfn); } - else { gfn = sym_index(tfn); bytecode_add_instruction(bc, OP_STORE_GLOBAL, gfn); } - /* res array */ - bytecode_add_instruction(bc, OP_MAKE_ARRAY, 0); - char tres[64]; snprintf(tres, sizeof(tres), "__map_res_%d", g_temp_counter++); - int lres = -1, gres = -1; - if (g_locals) { lres = local_add(tres); bytecode_add_instruction(bc, OP_STORE_LOCAL, lres); } - else { gres = sym_index(tres); bytecode_add_instruction(bc, OP_STORE_GLOBAL, gres); } - /* i=0 */ - int c0 = bytecode_add_constant(bc, make_int(0)); - bytecode_add_instruction(bc, OP_LOAD_CONST, c0); - char ti[64]; snprintf(ti, sizeof(ti), "__map_i_%d", g_temp_counter++); - int li = -1, gi = -1; - if (g_locals) { li = local_add(ti); bytecode_add_instruction(bc, OP_STORE_LOCAL, li); } - else { gi = sym_index(ti); bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); } - /* loop start */ - int loop_start = bc->instr_count; - /* i < len(arr) */ - if (g_locals) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr); } - else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr); } - bytecode_add_instruction(bc, OP_LEN, 0); - bytecode_add_instruction(bc, OP_LT, 0); - int jf = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); - /* elem = arr[i] */ - if (g_locals) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr); bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); } - else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr); bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); } - bytecode_add_instruction(bc, OP_INDEX_GET, 0); - /* call fn(elem) */ - if (g_locals) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, lfn); } - else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gfn); } - /* reorder: we need fn below arg -> push fn first then arg already on stack? We currently have elem on stack; push fn now results top=fn. We want top args then function; OP_CALL expects fn then pops args? Our OP_CALL pops function after args; earlier compile of calls: they push function then args then OP_CALL. So we need to swap: push fn, then swap to make function below arg */ - bytecode_add_instruction(bc, OP_SWAP, 0); - bytecode_add_instruction(bc, OP_CALL, 1); - /* Append to result via indexed assignment: res[len(res)] = value */ - /* Store computed value to a temp */ - char tv[64]; snprintf(tv, sizeof(tv), "__map_v_%d", g_temp_counter++); - int lv = -1, gv = -1; - if (g_locals) { lv = local_add(tv); bytecode_add_instruction(bc, OP_STORE_LOCAL, lv); } - else { gv = sym_index(tv); bytecode_add_instruction(bc, OP_STORE_GLOBAL, gv); } - - /* Push array (for INDEX_SET we need stack: value, index, array; we will build array, index, then value) */ - if (g_locals) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, lres); } - else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gres); } - - /* Compute index = len(res) */ - if (g_locals) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, lres); } - else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gres); } - bytecode_add_instruction(bc, OP_LEN, 0); - - /* Load value back on top */ - if (g_locals) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, lv); } - else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gv); } - - /* Append via insert: res.insert(index=len(res), value) */ - bytecode_add_instruction(bc, OP_INSERT, 0); - /* dApache-2.0ard returned new length */ - bytecode_add_instruction(bc, OP_POP, 0); - - /* i++ */ - int c1 = bytecode_add_constant(bc, make_int(1)); - if (g_locals) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); bytecode_add_instruction(bc, OP_LOAD_CONST, c1); bytecode_add_instruction(bc, OP_ADD, 0); bytecode_add_instruction(bc, OP_STORE_LOCAL, li); } - else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); bytecode_add_instruction(bc, OP_LOAD_CONST, c1); bytecode_add_instruction(bc, OP_ADD, 0); bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); } - bytecode_add_instruction(bc, OP_JUMP, loop_start); - bytecode_set_operand(bc, jf, bc->instr_count); - /* result value on stack */ - if (g_locals) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, lres); } - else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gres); } - free(name); - return 1; - } - if (strcmp(name, "filter") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "filter expects (array, function)"); free(name); return 0; } - char tarr[64]; snprintf(tarr, sizeof(tarr), "__flt_arr_%d", g_temp_counter++); - int larr = -1, garr = -1; - if (g_locals) { larr = local_add(tarr); bytecode_add_instruction(bc, OP_STORE_LOCAL, larr); } - else { garr = sym_index(tarr); bytecode_add_instruction(bc, OP_STORE_GLOBAL, garr); } - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "filter expects (array, function)"); free(name); return 0; } - char tfn[64]; snprintf(tfn, sizeof(tfn), "__flt_fn_%d", g_temp_counter++); - int lfn = -1, gfn = -1; - if (g_locals) { lfn = local_add(tfn); bytecode_add_instruction(bc, OP_STORE_LOCAL, lfn); } - else { gfn = sym_index(tfn); bytecode_add_instruction(bc, OP_STORE_GLOBAL, gfn); } - bytecode_add_instruction(bc, OP_MAKE_ARRAY, 0); - char tres[64]; snprintf(tres, sizeof(tres), "__flt_res_%d", g_temp_counter++); - int lres = -1, gres = -1; - if (g_locals) { lres = local_add(tres); bytecode_add_instruction(bc, OP_STORE_LOCAL, lres); } - else { gres = sym_index(tres); bytecode_add_instruction(bc, OP_STORE_GLOBAL, gres); } - int c0 = bytecode_add_constant(bc, make_int(0)); - bytecode_add_instruction(bc, OP_LOAD_CONST, c0); - char ti[64]; snprintf(ti, sizeof(ti), "__flt_i_%d", g_temp_counter++); - int li = -1, gi = -1; - if (g_locals) { li = local_add(ti); bytecode_add_instruction(bc, OP_STORE_LOCAL, li); } - else { gi = sym_index(ti); bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); } - int loop_start = bc->instr_count; - if (g_locals) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr); } - else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr); } - bytecode_add_instruction(bc, OP_LEN, 0); - bytecode_add_instruction(bc, OP_LT, 0); - int jf = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); - if (g_locals) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr); bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); } - else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr); bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); } - bytecode_add_instruction(bc, OP_INDEX_GET, 0); - if (g_locals) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, lfn); } - else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gfn); } - bytecode_add_instruction(bc, OP_SWAP, 0); - bytecode_add_instruction(bc, OP_CALL, 1); - /* if truthy then push elem to res */ - int jskip = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); - /* Append element to result: res[len(res)] = elem */ - /* Reload element into a temp */ - if (g_locals) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr); bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); } - else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr); bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); } - bytecode_add_instruction(bc, OP_INDEX_GET, 0); - - char tvf[64]; snprintf(tvf, sizeof(tvf), "__flt_v_%d", g_temp_counter++); - int lvf = -1, gvf = -1; - if (g_locals) { lvf = local_add(tvf); bytecode_add_instruction(bc, OP_STORE_LOCAL, lvf); } - else { gvf = sym_index(tvf); bytecode_add_instruction(bc, OP_STORE_GLOBAL, gvf); } - - /* Push array */ - if (g_locals) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, lres); } - else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gres); } - - /* index = len(res) */ - if (g_locals) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, lres); } - else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gres); } - bytecode_add_instruction(bc, OP_LEN, 0); - - /* value */ - if (g_locals) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, lvf); } - else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gvf); } - - /* Append via insert: res.insert(index=len(res), value) */ - bytecode_add_instruction(bc, OP_INSERT, 0); - /* dApache-2.0ard returned new length */ - bytecode_add_instruction(bc, OP_POP, 0); - - int c1 = bytecode_add_constant(bc, make_int(1)); - /* patch skip over append */ - bytecode_set_operand(bc, jskip, bc->instr_count); - /* i++ */ - if (g_locals) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); bytecode_add_instruction(bc, OP_LOAD_CONST, c1); bytecode_add_instruction(bc, OP_ADD, 0); bytecode_add_instruction(bc, OP_STORE_LOCAL, li); } - else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); bytecode_add_instruction(bc, OP_LOAD_CONST, c1); bytecode_add_instruction(bc, OP_ADD, 0); bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); } - bytecode_add_instruction(bc, OP_JUMP, loop_start); - bytecode_set_operand(bc, jf, bc->instr_count); - if (g_locals) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, lres); } - else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gres); } - free(name); - return 1; - } - if (strcmp(name, "reduce") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "reduce expects (array, init, function)"); free(name); return 0; } - char tarr[64]; snprintf(tarr, sizeof(tarr), "__red_arr_%d", g_temp_counter++); - int larr = -1, garr = -1; - if (g_locals) { larr = local_add(tarr); bytecode_add_instruction(bc, OP_STORE_LOCAL, larr); } - else { garr = sym_index(tarr); bytecode_add_instruction(bc, OP_STORE_GLOBAL, garr); } - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "reduce expects (array, init, function)"); free(name); return 0; } - char tacc[64]; snprintf(tacc, sizeof(tacc), "__red_acc_%d", g_temp_counter++); - int lacc = -1, gacc = -1; - if (g_locals) { lacc = local_add(tacc); bytecode_add_instruction(bc, OP_STORE_LOCAL, lacc); } - else { gacc = sym_index(tacc); bytecode_add_instruction(bc, OP_STORE_GLOBAL, gacc); } - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "reduce expects (array, init, function)"); free(name); return 0; } - char tfn[64]; snprintf(tfn, sizeof(tfn), "__red_fn_%d", g_temp_counter++); - int lfn = -1, gfn = -1; - if (g_locals) { lfn = local_add(tfn); bytecode_add_instruction(bc, OP_STORE_LOCAL, lfn); } - else { gfn = sym_index(tfn); bytecode_add_instruction(bc, OP_STORE_GLOBAL, gfn); } - /* loop */ - int c0 = bytecode_add_constant(bc, make_int(0)); - bytecode_add_instruction(bc, OP_LOAD_CONST, c0); - char ti[64]; snprintf(ti, sizeof(ti), "__red_i_%d", g_temp_counter++); - int li = -1, gi = -1; - if (g_locals) { li = local_add(ti); bytecode_add_instruction(bc, OP_STORE_LOCAL, li); } - else { gi = sym_index(ti); bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); } - int loop_start = bc->instr_count; - if (g_locals) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr); } - else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr); } - bytecode_add_instruction(bc, OP_LEN, 0); - bytecode_add_instruction(bc, OP_LT, 0); - int jf = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); - /* elem */ - if (g_locals) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr); bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); } - else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr); bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); } - bytecode_add_instruction(bc, OP_INDEX_GET, 0); - - /* Store elem to a temp so we can build stack as: fn, acc, elem */ - char telem[64]; snprintf(telem, sizeof(telem), "__red_elem_%d", g_temp_counter++); - int lelem = -1, gelem = -1; - if (g_locals) { lelem = local_add(telem); bytecode_add_instruction(bc, OP_STORE_LOCAL, lelem); } - else { gelem = sym_index(telem); bytecode_add_instruction(bc, OP_STORE_GLOBAL, gelem); } - - /* push function */ - if (g_locals) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, lfn); } - else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gfn); } - - /* push accumulator (arg1) */ - if (g_locals) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, lacc); } - else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gacc); } - - /* push element (arg2) */ - if (g_locals) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, lelem); } - else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gelem); } - - /* call fn(acc, elem) -> result */ - bytecode_add_instruction(bc, OP_CALL, 2); - /* store to acc */ - if (g_locals) { bytecode_add_instruction(bc, OP_STORE_LOCAL, lacc); } - else { bytecode_add_instruction(bc, OP_STORE_GLOBAL, gacc); } - int c1 = bytecode_add_constant(bc, make_int(1)); - if (g_locals) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); bytecode_add_instruction(bc, OP_LOAD_CONST, c1); bytecode_add_instruction(bc, OP_ADD, 0); bytecode_add_instruction(bc, OP_STORE_LOCAL, li); } - else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); bytecode_add_instruction(bc, OP_LOAD_CONST, c1); bytecode_add_instruction(bc, OP_ADD, 0); bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); } - bytecode_add_instruction(bc, OP_JUMP, loop_start); - bytecode_set_operand(bc, jf, bc->instr_count); - /* result = acc on stack */ - if (g_locals) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, lacc); } - else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gacc); } - free(name); - return 1; - } - if (strcmp(name, "zip") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "zip expects first array"); free(name); return 0; } - if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "zip expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "zip expects second array"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after zip args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_ZIP, 0); - free(name); - return 1; - } - /* math */ - if (strcmp(name, "min") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "min expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "min expects 2 args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_MIN, 0); - free(name); - return 1; - } - if (strcmp(name, "max") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "max expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "max expects 2 args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_MAX, 0); - free(name); - return 1; - } - if (strcmp(name, "fmin") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "fmin expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "fmin expects 2 args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_FMIN, 0); - free(name); - return 1; - } - if (strcmp(name, "fmax") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "fmax expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "fmax expects 2 args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_FMAX, 0); - free(name); - return 1; - } - if (strcmp(name, "clamp") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "clamp expects 3 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "clamp expects 3 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "clamp expects 3 args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_CLAMP, 0); - free(name); - return 1; - } - if (strcmp(name, "abs") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "abs expects 1 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_ABS, 0); - free(name); - return 1; - } - if (strcmp(name, "floor") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "floor expects 1 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_FLOOR, 0); - free(name); - return 1; - } - if (strcmp(name, "ceil") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "ceil expects 1 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_CEIL, 0); - free(name); - return 1; - } - if (strcmp(name, "trunc") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "trunc expects 1 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_TRUNC, 0); - free(name); - return 1; - } - if (strcmp(name, "round") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "round expects 1 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_ROUND, 0); - free(name); - return 1; - } - if (strcmp(name, "sin") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "sin expects 1 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SIN, 0); - free(name); - return 1; - } - if (strcmp(name, "cos") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "cos expects 1 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_COS, 0); - free(name); - return 1; - } - if (strcmp(name, "tan") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "tan expects 1 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_TAN, 0); - free(name); - return 1; - } - if (strcmp(name, "exp") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "exp expects 1 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_EXP, 0); - free(name); - return 1; - } - if (strcmp(name, "log") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "log expects 1 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_LOG, 0); - free(name); - return 1; - } - if (strcmp(name, "log10") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "log10 expects 1 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_LOG10, 0); - free(name); - return 1; - } - if (strcmp(name, "sqrt") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "sqrt expects 1 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SQRT, 0); - free(name); - return 1; - } - if (strcmp(name, "gcd") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "gcd expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "gcd expects 2 args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_GCD, 0); - free(name); - return 1; - } - if (strcmp(name, "lcm") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "lcm expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "lcm expects 2 args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_LCM, 0); - free(name); - return 1; - } - if (strcmp(name, "isqrt") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "isqrt expects 1 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_ISQRT, 0); - free(name); - return 1; - } - if (strcmp(name, "sign") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "sign expects 1 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SIGN, 0); - free(name); - return 1; - } - if (strcmp(name, "pow") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "pow expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "pow expects 2 args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_POW, 0); - free(name); - return 1; - } - - if (strcmp(name, "random_seed") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "random_seed expects 1 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_RANDOM_SEED, 0); - free(name); - return 1; - } - if (strcmp(name, "random_int") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "random_int expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "random_int expects 2 args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_RANDOM_INT, 0); - free(name); - return 1; - } - - if (strcmp(name, "random_number") == 0) { - (*pos)++; /* '(' */ - /* expects exactly 1 arg: length */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "random_number expects 1 arg (length)"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after random_number arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_RANDOM_NUMBER, 0); - free(name); - return 1; - } - - /* threading */ - if (strcmp(name, "thread_spawn") == 0) { - (*pos)++; /* '(' */ - /* thread_spawn(fn [, args]) */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "thread_spawn expects function as first arg"); free(name); return 0; } - int hasArgs = 0; - skip_spaces(src, len, pos); - if (*pos < len && src[*pos] == ',') { - (*pos)++; - skip_spaces(src, len, pos); - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "thread_spawn second arg must be array or value"); free(name); return 0; } - hasArgs = 1; - } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after thread_spawn args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_THREAD_SPAWN, hasArgs ? 1 : 0); - free(name); - return 1; - } - if (strcmp(name, "thread_join") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "thread_join expects 1 arg (thread id)"); free(name); return 0; } - bytecode_add_instruction(bc, OP_THREAD_JOIN, 0); - free(name); - return 1; - } - if (strcmp(name, "sleep") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "sleep expects 1 arg (milliseconds)"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SLEEP_MS, 0); - free(name); - return 1; - } - - /* bitwise ops (32-bit) */ - if (strcmp(name, "band") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "band expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "band expects 2 args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_BAND, 0); - free(name); - return 1; - } - if (strcmp(name, "bor") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "bor expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "bor expects 2 args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_BOR, 0); - free(name); - return 1; - } - if (strcmp(name, "bxor") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "bxor expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "bxor expects 2 args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_BXOR, 0); - free(name); - return 1; - } - if (strcmp(name, "bnot") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "bnot expects 1 arg"); free(name); return 0; } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "bnot expects 1 arg"); free(name); return 0; } - bytecode_add_instruction(bc, OP_BNOT, 0); - free(name); - return 1; - } - if (strcmp(name, "shl") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "shl expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "shl expects 2 args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SHL, 0); - free(name); - return 1; - } - if (strcmp(name, "shr") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "shr expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "shr expects 2 args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SHR, 0); - free(name); - return 1; - } - if (strcmp(name, "rol") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "rol expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "rol expects 2 args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_ROTL, 0); - free(name); - return 1; - } - if (strcmp(name, "ror") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "ror expects 2 args"); free(name); return 0; } - if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "ror expects 2 args"); free(name); return 0; } - bytecode_add_instruction(bc, OP_ROTR, 0); - free(name); - return 1; - } - - /* push function value first */ - if (local_idx >= 0) { - bytecode_add_instruction(bc, OP_LOAD_LOCAL, local_idx); + 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; + } + if (strcmp(name, "cast") == 0) { + (*pos)++; /* '(' */ + /* cast(value, typeName) */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { + parser_fail(*pos, "cast expects (value, typeName)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "cast expects (value, typeName)"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_CAST, 0); + free(name); + return 1; + } + if (strcmp(name, "typeof") == 0) { + (*pos)++; /* '(' */ + /* Special handling for typeof() to return declared subtype for integers */ + size_t peek = *pos; + char *vname = NULL; + int handled = 0; + if (read_identifier_into(src, len, &peek, &vname)) { + skip_spaces(src, len, &peek); + if (peek < len && src[peek] == ')') { + int meta = 0; + int lidx = local_find(vname); + if (lidx >= 0) { + meta = g_locals->types[lidx]; } else { - int gi = sym_index(name); - bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); + int gi = sym_index(vname); + if (gi >= 0) meta = G.types[gi]; } - /* Track namespace alias only for the initial receiver; after any call it's no longer an alias value */ - int __ns_ctx = is_ns_alias(name); - /* parse arguments */ - (*pos)++; /* '(' */ + + if (meta != 0 && meta != TYPE_META_STRING && meta != TYPE_META_BOOLEAN && meta != TYPE_META_NIL && meta != TYPE_META_CLASS && meta != TYPE_META_FLOAT) { + /* Integer subtype: ±bits */ + int abs_bits = meta < 0 ? -meta : meta; + const char *tname = (meta < 0) + ? (abs_bits == 64 ? "Sint64" : (abs_bits == 32 ? "Sint32" : (abs_bits == 16 ? "Sint16" : "Sint8"))) + : (abs_bits == 64 ? "Uint64" : (abs_bits == 32 ? "Uint32" : (abs_bits == 16 ? "Uint16" : "Uint8"))); + int ci = bytecode_add_constant(bc, make_string(tname)); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci); + *pos = peek + 1; /* consume name and ')' */ + handled = 1; + } + free(vname); + } else { + free(vname); + } + } + + if (!handled) { + /* General case: typeof(expression) + If the value is a Map with "__class" key, return that string; else return base typeof. + */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "typeof expects 1 argument"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after typeof arg"); + free(name); + return 0; + } + + /* [v] */ + bytecode_add_instruction(bc, OP_DUP, 0); /* [v, v] */ + bytecode_add_instruction(bc, OP_TYPEOF, 0); /* [v, tname] */ + { + int ciMap = bytecode_add_constant(bc, make_string("Map")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciMap); /* [v, tname, "Map"] */ + } + bytecode_add_instruction(bc, OP_EQ, 0); /* [v, isMap] */ + int j_if_not_map = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + + /* Map branch: [v] */ + bytecode_add_instruction(bc, OP_DUP, 0); /* [v, v] */ + { + int kci = bytecode_add_constant(bc, make_string("__class")); + bytecode_add_instruction(bc, OP_LOAD_CONST, kci); /* [v, v, "__class"] */ + } + bytecode_add_instruction(bc, OP_HAS_KEY, 0); /* [v, has] */ + int j_no_meta = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + /* has __class: try toString() -> return its result */ + bytecode_add_instruction(bc, OP_DUP, 0); /* [v, v] */ + { + int kcits = bytecode_add_constant(bc, make_string("toString")); + bytecode_add_instruction(bc, OP_LOAD_CONST, kcits); /* [v, v, "toString"] */ + } + bytecode_add_instruction(bc, OP_INDEX_GET, 0); /* [v, func] */ + bytecode_add_instruction(bc, OP_SWAP, 0); /* [func, v] */ + bytecode_add_instruction(bc, OP_CALL, 1); /* [string] */ + int j_end = bytecode_add_instruction(bc, OP_JUMP, 0); + + /* no meta: drop v and return "Map" */ + bytecode_set_operand(bc, j_no_meta, bc->instr_count); + bytecode_add_instruction(bc, OP_POP, 0); /* [] */ + { + int ciMap2 = bytecode_add_constant(bc, make_string("Map")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciMap2); /* ["Map"] */ + } + int j_end2 = bytecode_add_instruction(bc, OP_JUMP, 0); + int after_map = bc->instr_count; + + /* not map: compute typeof(v) */ + bytecode_set_operand(bc, j_if_not_map, after_map); + bytecode_add_instruction(bc, OP_TYPEOF, 0); /* [tname] */ + + /* end */ + bytecode_set_operand(bc, j_end, bc->instr_count); + bytecode_set_operand(bc, j_end2, bc->instr_count); + } + + free(name); + return 1; + } + if (strcmp(name, "keys") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "keys expects 1 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_KEYS, 0); + free(name); + return 1; + } + if (strcmp(name, "values") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "values expects 1 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_VALUES, 0); + free(name); + return 1; + } + if (strcmp(name, "has") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { + parser_fail(*pos, "has expects (map, key)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "has expects (map, key)"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_HAS_KEY, 0); + free(name); + return 1; + } + if (strcmp(name, "read_file") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "read_file expects 1 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_READ_FILE, 0); + free(name); + return 1; + } + if (strcmp(name, "write_file") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { + parser_fail(*pos, "write_file expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "write_file expects 2 args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_WRITE_FILE, 0); + free(name); + return 1; + } + if (strcmp(name, "input") == 0) { + (*pos)++; /* '(' */ + int hasPrompt = 0; + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] != ')') { + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "input expects 0 or 1 argument"); + free(name); + return 0; + } + hasPrompt = 1; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after input arg(s)"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_INPUT_LINE, hasPrompt ? 1 : 0); + free(name); + return 1; + } + if (strcmp(name, "input_hidden") == 0) { + (*pos)++; /* '(' */ + int hasPrompt = 0; + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] != ')') { + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "input_hidden expects 0 or 1 argument"); + free(name); + return 0; + } + hasPrompt = 1; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after input_hidden arg(s)"); + free(name); + return 0; + } + /* operand bit0 = hasPrompt, bit1 = hidden */ + bytecode_add_instruction(bc, OP_INPUT_LINE, (hasPrompt ? 1 : 0) | 2); + free(name); + return 1; + } + if (strcmp(name, "proc_run") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "proc_run expects 1 argument (command string)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after proc_run arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_PROC_RUN, 0); + free(name); + return 1; + } + if (strcmp(name, "system") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "system expects 1 argument (command string)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after system arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_PROC_SYSTEM, 0); + free(name); + return 1; + } + if (strcmp(name, "time_now_ms") == 0) { + (*pos)++; /* '(' */ + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "time_now_ms expects ()"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_TIME_NOW_MS, 0); + free(name); + return 1; + } + if (strcmp(name, "clock_mono_ms") == 0) { + (*pos)++; /* '(' */ + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "clock_mono_ms expects ()"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_CLOCK_MONO_MS, 0); + free(name); + return 1; + } + if (strcmp(name, "date_format") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "date_format expects (ms:int, fmt:string)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "date_format expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "date_format expects (ms:int, fmt:string)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after date_format args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_DATE_FORMAT, 0); + free(name); + return 1; + } + if (strcmp(name, "env") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "env expects 1 argument"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after env arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_ENV, 0); + free(name); + return 1; + } + if (strcmp(name, "env_all") == 0) { + (*pos)++; /* '(' */ + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "env_all expects ()"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_ENV_ALL, 0); + free(name); + return 1; + } + if (strcmp(name, "fun_version") == 0) { + (*pos)++; /* '(' */ + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "fun_version expects ()"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_FUN_VERSION, 0); + free(name); + return 1; + } + if (strcmp(name, "rust_hello") == 0) { + (*pos)++; /* '(' */ + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "rust_hello expects ()"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_RUST_HELLO, 0); + free(name); + return 1; + } + if (strcmp(name, "rust_hello_args") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "rust_hello_args expects (message:string)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after rust_hello_args arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_RUST_HELLO_ARGS, 0); + free(name); + return 1; + } + if (strcmp(name, "rust_hello_args_return") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "rust_hello_args_return expects (message:string)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after rust_hello_args_return arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_RUST_HELLO_ARGS_RETURN, 0); + free(name); + return 1; + } + if (strcmp(name, "rust_get_sp") == 0) { + (*pos)++; /* '(' */ + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "rust_get_sp expects ()"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_RUST_GET_SP, 0); + free(name); + return 1; + } + if (strcmp(name, "rust_set_exit") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "rust_set_exit expects (code:int)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after rust_set_exit arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_RUST_SET_EXIT, 0); + free(name); + return 1; + } + if (strcmp(name, "cpp_add") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "cpp_add expects (a:int, b:int)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "cpp_add expects two arguments"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "cpp_add expects (a:int, b:int)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after cpp_add args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_CPP_ADD, 0); + free(name); + return 1; + } + if (strcmp(name, "os_list_dir") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "os_list_dir expects (path)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after os_list_dir arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_OS_LIST_DIR, 0); + free(name); + return 1; + } + /* JSON builtins */ + if (strcmp(name, "json_parse") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "json_parse expects (text)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after json_parse arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_JSON_PARSE, 0); + free(name); + return 1; + } + /* XML builtins (minimal) */ + if (strcmp(name, "xml_parse") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "xml_parse expects (text)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after xml_parse arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_XML_PARSE, 0); + free(name); + return 1; + } + if (strcmp(name, "xml_root") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "xml_root expects (doc_handle)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after xml_root arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_XML_ROOT, 0); + free(name); + return 1; + } + if (strcmp(name, "xml_name") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "xml_name expects (node_handle)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after xml_name arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_XML_NAME, 0); + free(name); + return 1; + } + if (strcmp(name, "xml_text") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "xml_text expects (node_handle)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after xml_text arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_XML_TEXT, 0); + free(name); + return 1; + } + if (strcmp(name, "json_stringify") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "json_stringify expects (value, pretty)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "json_stringify expects (value, pretty)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "json_stringify expects (value, pretty)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after json_stringify args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_JSON_STRINGIFY, 0); + free(name); + return 1; + } + /* Tk (GUI) builtins (no raw Tcl exposed) */ + if (strcmp(name, "tk_loop") == 0) { + (*pos)++; /* '(' */ + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "tk_loop expects ()"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_TK_LOOP, 0); + free(name); + return 1; + } + if (strcmp(name, "tk_title") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "tk_title expects (title:string)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after tk_title arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_TK_WM_TITLE, 0); + free(name); + return 1; + } + if (strcmp(name, "tk_label") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "tk_label expects (id:string, text:string)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "tk_label expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "tk_label expects (id:string, text:string)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after tk_label args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_TK_LABEL, 0); + free(name); + return 1; + } + if (strcmp(name, "tk_button") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "tk_button expects (id:string, text:string)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "tk_button expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "tk_button expects (id:string, text:string)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after tk_button args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_TK_BUTTON, 0); + free(name); + return 1; + } + if (strcmp(name, "tk_pack") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "tk_pack expects (id:string)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after tk_pack arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_TK_PACK, 0); + free(name); + return 1; + } + if (strcmp(name, "tk_bind") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "tk_bind expects (id, event, cmd)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "tk_bind expects 3 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "tk_bind expects 3 args"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "tk_bind expects 3 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "tk_bind expects 3 args"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after tk_bind args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_TK_BIND, 0); + free(name); + return 1; + } + if (strcmp(name, "tk_eval") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "tk_eval expects (script)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after tk_eval arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_TK_EVAL, 0); + free(name); + return 1; + } + if (strcmp(name, "tk_result") == 0) { + (*pos)++; /* '(' */ + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "tk_result expects ()"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_TK_RESULT, 0); + free(name); + return 1; + } + if (strcmp(name, "json_from_file") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "json_from_file expects (path)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after json_from_file arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_JSON_FROM_FILE, 0); + free(name); + return 1; + } + if (strcmp(name, "json_to_file") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "json_to_file expects (path, value, pretty)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "json_to_file expects (path, value, pretty)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "json_to_file expects (path, value, pretty)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "json_to_file expects (path, value, pretty)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "json_to_file expects (path, value, pretty)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after json_to_file args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_JSON_TO_FILE, 0); + free(name); + return 1; + } + /* INI (iniparser 4.2.6) builtins */ + if (strcmp(name, "ini_load") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_load expects (path)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after ini_load arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_INI_LOAD, 0); + free(name); + return 1; + } + if (strcmp(name, "ini_free") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_free expects (handle)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after ini_free arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_INI_FREE, 0); + free(name); + return 1; + } + if (strcmp(name, "ini_get_string") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_get_string expects (handle, section, key, default)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "ini_get_string expects 4 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_get_string expects 4 args"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "ini_get_string expects 4 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_get_string expects 4 args"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "ini_get_string expects 4 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_get_string expects 4 args"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after ini_get_string args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_INI_GET_STRING, 0); + free(name); + return 1; + } + if (strcmp(name, "ini_get_int") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_get_int expects (handle, section, key, default)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "ini_get_int expects 4 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_get_int expects 4 args"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "ini_get_int expects 4 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_get_int expects 4 args"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "ini_get_int expects 4 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_get_int expects 4 args"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after ini_get_int args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_INI_GET_INT, 0); + free(name); + return 1; + } + if (strcmp(name, "ini_get_double") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_get_double expects (handle, section, key, default)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "ini_get_double expects 4 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_get_double expects 4 args"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "ini_get_double expects 4 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_get_double expects 4 args"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "ini_get_double expects 4 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_get_double expects 4 args"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after ini_get_double args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_INI_GET_DOUBLE, 0); + free(name); + return 1; + } + if (strcmp(name, "ini_get_bool") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_get_bool expects (handle, section, key, default)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "ini_get_bool expects 4 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_get_bool expects 4 args"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "ini_get_bool expects 4 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_get_bool expects 4 args"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "ini_get_bool expects 4 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_get_bool expects 4 args"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after ini_get_bool args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_INI_GET_BOOL, 0); + free(name); + return 1; + } + if (strcmp(name, "ini_set") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_set expects (handle, section, key, value)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "ini_set expects 4 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_set expects 4 args"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "ini_set expects 4 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_set expects 4 args"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "ini_set expects 4 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_set expects 4 args"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after ini_set args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_INI_SET, 0); + free(name); + return 1; + } + if (strcmp(name, "ini_unset") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_unset expects (handle, section, key)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "ini_unset expects 3 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_unset expects 3 args"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "ini_unset expects 3 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_unset expects 3 args"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after ini_unset args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_INI_UNSET, 0); + free(name); + return 1; + } + if (strcmp(name, "ini_save") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_save expects (handle, path)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "ini_save expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "ini_save expects 2 args"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after ini_save args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_INI_SAVE, 0); + free(name); + return 1; + } + /* CURL builtins (minimal interface like JSON) */ + if (strcmp(name, "curl_get") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "curl_get expects (url)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after curl_get arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_CURL_GET, 0); + free(name); + return 1; + } + /* SQLite builtins */ + if (strcmp(name, "sqlite_open") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "sqlite_open expects (path)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after sqlite_open arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SQLITE_OPEN, 0); + free(name); + return 1; + } + if (strcmp(name, "sqlite_close") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "sqlite_close expects (handle)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after sqlite_close arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SQLITE_CLOSE, 0); + free(name); + return 1; + } + if (strcmp(name, "sqlite_exec") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "sqlite_exec expects (handle, sql)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "sqlite_exec expects (handle, sql)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "sqlite_exec expects (handle, sql)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after sqlite_exec args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SQLITE_EXEC, 0); + free(name); + return 1; + } + if (strcmp(name, "sqlite_query") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "sqlite_query expects (handle, sql)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "sqlite_query expects (handle, sql)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "sqlite_query expects (handle, sql)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after sqlite_query args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SQLITE_QUERY, 0); + free(name); + return 1; + } + /* libsql builtins (independent extension) */ + if (strcmp(name, "libsql_open") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "libsql_open expects (url_or_path)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after libsql_open arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_LIBSQL_OPEN, 0); + free(name); + return 1; + } + if (strcmp(name, "libsql_close") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "libsql_close expects (handle)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after libsql_close arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_LIBSQL_CLOSE, 0); + free(name); + return 1; + } + if (strcmp(name, "libsql_exec") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "libsql_exec expects (handle, sql)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "libsql_exec expects (handle, sql)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "libsql_exec expects (handle, sql)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after libsql_exec args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_LIBSQL_EXEC, 0); + free(name); + return 1; + } + if (strcmp(name, "libsql_query") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "libsql_query expects (handle, sql)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "libsql_query expects (handle, sql)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "libsql_query expects (handle, sql)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after libsql_query args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_LIBSQL_QUERY, 0); + free(name); + return 1; + } + if (strcmp(name, "curl_post") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "curl_post expects (url, body)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "curl_post expects (url, body)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "curl_post expects (url, body)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after curl_post args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_CURL_POST, 0); + free(name); + return 1; + } + if (strcmp(name, "curl_download") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "curl_download expects (url, path)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "curl_download expects (url, path)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "curl_download expects (url, path)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after curl_download args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_CURL_DOWNLOAD, 0); + free(name); + return 1; + } + /* OpenSSL (md5/sha256/sha512) */ + if (strcmp(name, "openssl_md5") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "openssl_md5 expects (data)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after openssl_md5 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_OPENSSL_MD5, 0); + free(name); + return 1; + } + if (strcmp(name, "openssl_sha256") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "openssl_sha256 expects (data)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after openssl_sha256 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_OPENSSL_SHA256, 0); + free(name); + return 1; + } + if (strcmp(name, "openssl_sha512") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "openssl_sha512 expects (data)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after openssl_sha512 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_OPENSSL_SHA512, 0); + free(name); + return 1; + } + if (strcmp(name, "openssl_ripemd160") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "openssl_ripemd160 expects (data)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after openssl_ripemd160 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_OPENSSL_RIPEMD160, 0); + free(name); + return 1; + } + /* LibreSSL (md5/sha256/sha512/ripemd160) */ + if (strcmp(name, "libressl_md5") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "libressl_md5 expects (data)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after libressl_md5 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_LIBRESSL_MD5, 0); + free(name); + return 1; + } + if (strcmp(name, "libressl_sha256") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "libressl_sha256 expects (data)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after libressl_sha256 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_LIBRESSL_SHA256, 0); + free(name); + return 1; + } + if (strcmp(name, "libressl_sha512") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "libressl_sha512 expects (data)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after libressl_sha512 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_LIBRESSL_SHA512, 0); + free(name); + return 1; + } + if (strcmp(name, "libressl_ripemd160") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "libressl_ripemd160 expects (data)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after libressl_ripemd160 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_LIBRESSL_RIPEMD160, 0); + free(name); + return 1; + } + /* PCSC builtins */ + if (strcmp(name, "pcsc_establish") == 0) { + (*pos)++; /* '(' */ + /* no args */ + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "pcsc_establish expects ()"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_PCSC_ESTABLISH, 0); + free(name); + return 1; + } + /* PCRE2 builtins */ + if (strcmp(name, "pcre2_test") == 0) { + (*pos)++; /* '(' */ + /* (pattern, text, flags) */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "pcre2_test expects (pattern, text, flags)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "pcre2_test expects (pattern, text, flags)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "pcre2_test expects (pattern, text, flags)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "pcre2_test expects (pattern, text, flags)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "pcre2_test expects (pattern, text, flags)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after pcre2_test args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_PCRE2_TEST, 0); + free(name); + return 1; + } + if (strcmp(name, "pcre2_match") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "pcre2_match expects (pattern, text, flags)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "pcre2_match expects (pattern, text, flags)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "pcre2_match expects (pattern, text, flags)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "pcre2_match expects (pattern, text, flags)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "pcre2_match expects (pattern, text, flags)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after pcre2_match args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_PCRE2_MATCH, 0); + free(name); + return 1; + } + if (strcmp(name, "pcre2_findall") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after pcre2_findall args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_PCRE2_FINDALL, 0); + free(name); + return 1; + } + /* Notcurses builtins (optional) */ + if (strcmp(name, "nc_init") == 0) { + (*pos)++; /* '(' */ + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "nc_init expects ()"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_NC_INIT, 0); + free(name); + return 1; + } + if (strcmp(name, "nc_shutdown") == 0) { + (*pos)++; /* '(' */ + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "nc_shutdown expects ()"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_NC_SHUTDOWN, 0); + free(name); + return 1; + } + if (strcmp(name, "nc_clear") == 0) { + (*pos)++; /* '(' */ + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "nc_clear expects ()"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_NC_CLEAR, 0); + free(name); + return 1; + } + if (strcmp(name, "nc_draw_text") == 0) { + (*pos)++; /* '(' */ + /* (y, x, text) -> push y, x, text, then opcode will pop text,x,y */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "nc_draw_text expects (y, x, text)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "nc_draw_text expects (y, x, text)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "nc_draw_text expects (y, x, text)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "nc_draw_text expects (y, x, text)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "nc_draw_text expects (y, x, text)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after nc_draw_text args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_NC_DRAW_TEXT, 0); + free(name); + return 1; + } + if (strcmp(name, "nc_getch") == 0) { + (*pos)++; /* '(' */ + /* (timeout_ms) */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "nc_getch expects (timeout_ms)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after nc_getch arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_NC_GETCH, 0); + free(name); + return 1; + } + if (strcmp(name, "pcsc_release") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "pcsc_release expects 1 argument (ctx)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after pcsc_release arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_PCSC_RELEASE, 0); + free(name); + return 1; + } + if (strcmp(name, "pcsc_list_readers") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "pcsc_list_readers expects 1 argument (ctx)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after pcsc_list_readers arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_PCSC_LIST_READERS, 0); + free(name); + return 1; + } + if (strcmp(name, "pcsc_connect") == 0) { + (*pos)++; /* '(' */ + /* ctx, reader */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "pcsc_connect expects (ctx, reader)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "pcsc_connect expects (ctx, reader)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "pcsc_connect expects (ctx, reader)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after pcsc_connect args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_PCSC_CONNECT, 0); + free(name); + return 1; + } + if (strcmp(name, "pcsc_disconnect") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "pcsc_disconnect expects 1 argument (handle)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after pcsc_disconnect arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_PCSC_DISCONNECT, 0); + free(name); + return 1; + } + if (strcmp(name, "pcsc_transmit") == 0) { + (*pos)++; /* '(' */ + /* handle, bytes */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "pcsc_transmit expects (handle, bytes)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "pcsc_transmit expects (handle, bytes)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "pcsc_transmit expects (handle, bytes)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after pcsc_transmit args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_PCSC_TRANSMIT, 0); + free(name); + return 1; + } + /* Socket builtins */ + if (strcmp(name, "tcp_listen") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "tcp_listen expects (port, backlog)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "tcp_listen expects (port, backlog)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "tcp_listen expects (port, backlog)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after tcp_listen args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SOCK_TCP_LISTEN, 0); + free(name); + return 1; + } + if (strcmp(name, "tcp_accept") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "tcp_accept expects (listen_fd)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after tcp_accept arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SOCK_TCP_ACCEPT, 0); + free(name); + return 1; + } + if (strcmp(name, "tcp_connect") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "tcp_connect expects (host, port)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "tcp_connect expects (host, port)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "tcp_connect expects (host, port)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after tcp_connect args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SOCK_TCP_CONNECT, 0); + free(name); + return 1; + } + if (strcmp(name, "sock_send") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "sock_send expects (fd, data)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "sock_send expects (fd, data)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "sock_send expects (fd, data)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after sock_send args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SOCK_SEND, 0); + free(name); + return 1; + } + if (strcmp(name, "sock_recv") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "sock_recv expects (fd, maxlen)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "sock_recv expects (fd, maxlen)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "sock_recv expects (fd, maxlen)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after sock_recv args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SOCK_RECV, 0); + free(name); + return 1; + } + if (strcmp(name, "sock_close") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "sock_close expects (fd)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after sock_close arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SOCK_CLOSE, 0); + free(name); + return 1; + } + if (strcmp(name, "unix_listen") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "unix_listen expects (path, backlog)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "unix_listen expects (path, backlog)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "unix_listen expects (path, backlog)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after unix_listen args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SOCK_UNIX_LISTEN, 0); + free(name); + return 1; + } + if (strcmp(name, "unix_connect") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "unix_connect expects (path)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after unix_connect arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SOCK_UNIX_CONNECT, 0); + free(name); + return 1; + } + /* Serial builtins */ + if (strcmp(name, "serial_open") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "serial_open expects (path, baud)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "serial_open expects (path, baud)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "serial_open expects (path, baud)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after serial_open args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SERIAL_OPEN, 0); + free(name); + return 1; + } + if (strcmp(name, "serial_config") == 0) { + (*pos)++; /* '(' */ + // fd, data_bits, parity, stop_bits, flow_control + for (int i = 0; i < 5; ++i) { + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "serial_config expects 5 arguments"); + free(name); + return 0; + } + if (i < 4) { + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "serial_config expects 5 arguments"); + free(name); + return 0; + } + } + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after serial_config args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SERIAL_CONFIG, 0); + free(name); + return 1; + } + if (strcmp(name, "serial_send") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "serial_send expects (fd, data)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "serial_send expects (fd, data)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "serial_send expects (fd, data)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after serial_send args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SERIAL_SEND, 0); + free(name); + return 1; + } + if (strcmp(name, "serial_recv") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "serial_recv expects (fd, maxlen)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ',')) { + parser_fail(*pos, "serial_recv expects (fd, maxlen)"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "serial_recv expects (fd, maxlen)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after serial_recv args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SERIAL_RECV, 0); + free(name); + return 1; + } + if (strcmp(name, "serial_close") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "serial_close expects (fd)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after serial_close arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SERIAL_CLOSE, 0); + free(name); + return 1; + } + /* string ops */ + if (strcmp(name, "split") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "split expects string"); + free(name); + return 0; + } + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + } else { + parser_fail(*pos, "split expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "split expects separator"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after split args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SPLIT, 0); + free(name); + return 1; + } + if (strcmp(name, "join") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "join expects array"); + free(name); + return 0; + } + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + } else { + parser_fail(*pos, "join expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "join expects separator"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after join args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_JOIN, 0); + free(name); + return 1; + } + if (strcmp(name, "substr") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "substr expects string"); + free(name); + return 0; + } + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + } else { + parser_fail(*pos, "substr expects 3 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "substr expects start"); + free(name); + return 0; + } + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + } else { + parser_fail(*pos, "substr expects 3 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "substr expects len"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after substr args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SUBSTR, 0); + free(name); + return 1; + } + if (strcmp(name, "find") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "find expects haystack"); + free(name); + return 0; + } + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + } else { + parser_fail(*pos, "find expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "find expects needle"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after find args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_FIND, 0); + free(name); + return 1; + } + /* regex ops */ + if (strcmp(name, "regex_match") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "regex_match expects text"); + free(name); + return 0; + } + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + } else { + parser_fail(*pos, "regex_match expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "regex_match expects pattern"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after regex_match args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_REGEX_MATCH, 0); + free(name); + return 1; + } + if (strcmp(name, "regex_search") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "regex_search expects text"); + free(name); + return 0; + } + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + } else { + parser_fail(*pos, "regex_search expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "regex_search expects pattern"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after regex_search args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_REGEX_SEARCH, 0); + free(name); + return 1; + } + if (strcmp(name, "regex_replace") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "regex_replace expects text"); + free(name); + return 0; + } + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + } else { + parser_fail(*pos, "regex_replace expects 3 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "regex_replace expects pattern"); + free(name); + return 0; + } + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + } else { + parser_fail(*pos, "regex_replace expects 3 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "regex_replace expects replacement"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after regex_replace args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_REGEX_REPLACE, 0); + free(name); + return 1; + } + /* array utils */ + if (strcmp(name, "contains") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "contains expects array"); + free(name); + return 0; + } + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + } else { + parser_fail(*pos, "contains expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "contains expects value"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after contains args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_CONTAINS, 0); + free(name); + return 1; + } + if (strcmp(name, "indexOf") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "indexOf expects array"); + free(name); + return 0; + } + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + } else { + parser_fail(*pos, "indexOf expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "indexOf expects value"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after indexOf args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_INDEX_OF, 0); + free(name); + return 1; + } + if (strcmp(name, "clear") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "clear expects array"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after clear arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_CLEAR, 0); + free(name); + return 1; + } + /* iteration helpers */ + if (strcmp(name, "enumerate") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "enumerate expects array"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after enumerate arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_ENUMERATE, 0); + free(name); + return 1; + } + if (strcmp(name, "map") == 0) { + (*pos)++; /* '(' */ + /* arr */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { + parser_fail(*pos, "map expects (array, function)"); + free(name); + return 0; + } + /* store arr -> __map_arr */ + char tarr[64]; + snprintf(tarr, sizeof(tarr), "__map_arr_%d", g_temp_counter++); + int larr = -1, garr = -1; + if (g_locals) { + larr = local_add(tarr); + bytecode_add_instruction(bc, OP_STORE_LOCAL, larr); + } else { + garr = sym_index(tarr); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, garr); + } + /* func */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "map expects (array, function)"); + free(name); + return 0; + } + char tfn[64]; + snprintf(tfn, sizeof(tfn), "__map_fn_%d", g_temp_counter++); + int lfn = -1, gfn = -1; + if (g_locals) { + lfn = local_add(tfn); + bytecode_add_instruction(bc, OP_STORE_LOCAL, lfn); + } else { + gfn = sym_index(tfn); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gfn); + } + /* res array */ + bytecode_add_instruction(bc, OP_MAKE_ARRAY, 0); + char tres[64]; + snprintf(tres, sizeof(tres), "__map_res_%d", g_temp_counter++); + int lres = -1, gres = -1; + if (g_locals) { + lres = local_add(tres); + bytecode_add_instruction(bc, OP_STORE_LOCAL, lres); + } else { + gres = sym_index(tres); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gres); + } + /* i=0 */ + int c0 = bytecode_add_constant(bc, make_int(0)); + bytecode_add_instruction(bc, OP_LOAD_CONST, c0); + char ti[64]; + snprintf(ti, sizeof(ti), "__map_i_%d", g_temp_counter++); + int li = -1, gi = -1; + if (g_locals) { + li = local_add(ti); + bytecode_add_instruction(bc, OP_STORE_LOCAL, li); + } else { + gi = sym_index(ti); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); + } + /* loop start */ + int loop_start = bc->instr_count; + /* i < len(arr) */ + if (g_locals) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); + bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr); + } + bytecode_add_instruction(bc, OP_LEN, 0); + bytecode_add_instruction(bc, OP_LT, 0); + int jf = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + /* elem = arr[i] */ + if (g_locals) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr); + bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr); + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); + } + bytecode_add_instruction(bc, OP_INDEX_GET, 0); + /* call fn(elem) */ + if (g_locals) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, lfn); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gfn); + } + /* reorder: we need fn below arg -> push fn first then arg already on stack? We currently have elem on stack; push fn now results top=fn. We want top args then function; OP_CALL expects fn then pops args? Our OP_CALL pops function after args; earlier compile of calls: they push function then args then OP_CALL. So we need to swap: push fn, then swap to make function below arg */ + bytecode_add_instruction(bc, OP_SWAP, 0); + bytecode_add_instruction(bc, OP_CALL, 1); + /* Append to result via indexed assignment: res[len(res)] = value */ + /* Store computed value to a temp */ + char tv[64]; + snprintf(tv, sizeof(tv), "__map_v_%d", g_temp_counter++); + int lv = -1, gv = -1; + if (g_locals) { + lv = local_add(tv); + bytecode_add_instruction(bc, OP_STORE_LOCAL, lv); + } else { + gv = sym_index(tv); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gv); + } + + /* Push array (for INDEX_SET we need stack: value, index, array; we will build array, index, then value) */ + if (g_locals) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, lres); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gres); + } + + /* Compute index = len(res) */ + if (g_locals) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, lres); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gres); + } + bytecode_add_instruction(bc, OP_LEN, 0); + + /* Load value back on top */ + if (g_locals) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, lv); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gv); + } + + /* Append via insert: res.insert(index=len(res), value) */ + bytecode_add_instruction(bc, OP_INSERT, 0); + /* dApache-2.0ard returned new length */ + bytecode_add_instruction(bc, OP_POP, 0); + + /* i++ */ + int c1 = bytecode_add_constant(bc, make_int(1)); + if (g_locals) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_ADD, 0); + bytecode_add_instruction(bc, OP_STORE_LOCAL, li); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_ADD, 0); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); + } + bytecode_add_instruction(bc, OP_JUMP, loop_start); + bytecode_set_operand(bc, jf, bc->instr_count); + /* result value on stack */ + if (g_locals) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, lres); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gres); + } + free(name); + return 1; + } + if (strcmp(name, "filter") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { + parser_fail(*pos, "filter expects (array, function)"); + free(name); + return 0; + } + char tarr[64]; + snprintf(tarr, sizeof(tarr), "__flt_arr_%d", g_temp_counter++); + int larr = -1, garr = -1; + if (g_locals) { + larr = local_add(tarr); + bytecode_add_instruction(bc, OP_STORE_LOCAL, larr); + } else { + garr = sym_index(tarr); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, garr); + } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "filter expects (array, function)"); + free(name); + return 0; + } + char tfn[64]; + snprintf(tfn, sizeof(tfn), "__flt_fn_%d", g_temp_counter++); + int lfn = -1, gfn = -1; + if (g_locals) { + lfn = local_add(tfn); + bytecode_add_instruction(bc, OP_STORE_LOCAL, lfn); + } else { + gfn = sym_index(tfn); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gfn); + } + bytecode_add_instruction(bc, OP_MAKE_ARRAY, 0); + char tres[64]; + snprintf(tres, sizeof(tres), "__flt_res_%d", g_temp_counter++); + int lres = -1, gres = -1; + if (g_locals) { + lres = local_add(tres); + bytecode_add_instruction(bc, OP_STORE_LOCAL, lres); + } else { + gres = sym_index(tres); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gres); + } + int c0 = bytecode_add_constant(bc, make_int(0)); + bytecode_add_instruction(bc, OP_LOAD_CONST, c0); + char ti[64]; + snprintf(ti, sizeof(ti), "__flt_i_%d", g_temp_counter++); + int li = -1, gi = -1; + if (g_locals) { + li = local_add(ti); + bytecode_add_instruction(bc, OP_STORE_LOCAL, li); + } else { + gi = sym_index(ti); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); + } + int loop_start = bc->instr_count; + if (g_locals) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); + bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr); + } + bytecode_add_instruction(bc, OP_LEN, 0); + bytecode_add_instruction(bc, OP_LT, 0); + int jf = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + if (g_locals) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr); + bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr); + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); + } + bytecode_add_instruction(bc, OP_INDEX_GET, 0); + if (g_locals) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, lfn); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gfn); + } + bytecode_add_instruction(bc, OP_SWAP, 0); + bytecode_add_instruction(bc, OP_CALL, 1); + /* if truthy then push elem to res */ + int jskip = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + /* Append element to result: res[len(res)] = elem */ + /* Reload element into a temp */ + if (g_locals) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr); + bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr); + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); + } + bytecode_add_instruction(bc, OP_INDEX_GET, 0); + + char tvf[64]; + snprintf(tvf, sizeof(tvf), "__flt_v_%d", g_temp_counter++); + int lvf = -1, gvf = -1; + if (g_locals) { + lvf = local_add(tvf); + bytecode_add_instruction(bc, OP_STORE_LOCAL, lvf); + } else { + gvf = sym_index(tvf); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gvf); + } + + /* Push array */ + if (g_locals) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, lres); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gres); + } + + /* index = len(res) */ + if (g_locals) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, lres); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gres); + } + bytecode_add_instruction(bc, OP_LEN, 0); + + /* value */ + if (g_locals) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, lvf); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gvf); + } + + /* Append via insert: res.insert(index=len(res), value) */ + bytecode_add_instruction(bc, OP_INSERT, 0); + /* dApache-2.0ard returned new length */ + bytecode_add_instruction(bc, OP_POP, 0); + + int c1 = bytecode_add_constant(bc, make_int(1)); + /* patch skip over append */ + bytecode_set_operand(bc, jskip, bc->instr_count); + /* i++ */ + if (g_locals) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_ADD, 0); + bytecode_add_instruction(bc, OP_STORE_LOCAL, li); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_ADD, 0); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); + } + bytecode_add_instruction(bc, OP_JUMP, loop_start); + bytecode_set_operand(bc, jf, bc->instr_count); + if (g_locals) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, lres); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gres); + } + free(name); + return 1; + } + if (strcmp(name, "reduce") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { + parser_fail(*pos, "reduce expects (array, init, function)"); + free(name); + return 0; + } + char tarr[64]; + snprintf(tarr, sizeof(tarr), "__red_arr_%d", g_temp_counter++); + int larr = -1, garr = -1; + if (g_locals) { + larr = local_add(tarr); + bytecode_add_instruction(bc, OP_STORE_LOCAL, larr); + } else { + garr = sym_index(tarr); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, garr); + } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { + parser_fail(*pos, "reduce expects (array, init, function)"); + free(name); + return 0; + } + char tacc[64]; + snprintf(tacc, sizeof(tacc), "__red_acc_%d", g_temp_counter++); + int lacc = -1, gacc = -1; + if (g_locals) { + lacc = local_add(tacc); + bytecode_add_instruction(bc, OP_STORE_LOCAL, lacc); + } else { + gacc = sym_index(tacc); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gacc); + } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "reduce expects (array, init, function)"); + free(name); + return 0; + } + char tfn[64]; + snprintf(tfn, sizeof(tfn), "__red_fn_%d", g_temp_counter++); + int lfn = -1, gfn = -1; + if (g_locals) { + lfn = local_add(tfn); + bytecode_add_instruction(bc, OP_STORE_LOCAL, lfn); + } else { + gfn = sym_index(tfn); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gfn); + } + /* loop */ + int c0 = bytecode_add_constant(bc, make_int(0)); + bytecode_add_instruction(bc, OP_LOAD_CONST, c0); + char ti[64]; + snprintf(ti, sizeof(ti), "__red_i_%d", g_temp_counter++); + int li = -1, gi = -1; + if (g_locals) { + li = local_add(ti); + bytecode_add_instruction(bc, OP_STORE_LOCAL, li); + } else { + gi = sym_index(ti); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); + } + int loop_start = bc->instr_count; + if (g_locals) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); + bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr); + } + bytecode_add_instruction(bc, OP_LEN, 0); + bytecode_add_instruction(bc, OP_LT, 0); + int jf = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + /* elem */ + if (g_locals) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr); + bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr); + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); + } + bytecode_add_instruction(bc, OP_INDEX_GET, 0); + + /* Store elem to a temp so we can build stack as: fn, acc, elem */ + char telem[64]; + snprintf(telem, sizeof(telem), "__red_elem_%d", g_temp_counter++); + int lelem = -1, gelem = -1; + if (g_locals) { + lelem = local_add(telem); + bytecode_add_instruction(bc, OP_STORE_LOCAL, lelem); + } else { + gelem = sym_index(telem); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gelem); + } + + /* push function */ + if (g_locals) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, lfn); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gfn); + } + + /* push accumulator (arg1) */ + if (g_locals) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, lacc); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gacc); + } + + /* push element (arg2) */ + if (g_locals) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, lelem); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gelem); + } + + /* call fn(acc, elem) -> result */ + bytecode_add_instruction(bc, OP_CALL, 2); + /* store to acc */ + if (g_locals) { + bytecode_add_instruction(bc, OP_STORE_LOCAL, lacc); + } else { + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gacc); + } + int c1 = bytecode_add_constant(bc, make_int(1)); + if (g_locals) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_ADD, 0); + bytecode_add_instruction(bc, OP_STORE_LOCAL, li); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_ADD, 0); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); + } + bytecode_add_instruction(bc, OP_JUMP, loop_start); + bytecode_set_operand(bc, jf, bc->instr_count); + /* result = acc on stack */ + if (g_locals) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, lacc); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gacc); + } + free(name); + return 1; + } + if (strcmp(name, "zip") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "zip expects first array"); + free(name); + return 0; + } + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + } else { + parser_fail(*pos, "zip expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "zip expects second array"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after zip args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_ZIP, 0); + free(name); + return 1; + } + /* math */ + if (strcmp(name, "min") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { + parser_fail(*pos, "min expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "min expects 2 args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_MIN, 0); + free(name); + return 1; + } + if (strcmp(name, "max") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { + parser_fail(*pos, "max expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "max expects 2 args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_MAX, 0); + free(name); + return 1; + } + if (strcmp(name, "fmin") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { + parser_fail(*pos, "fmin expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "fmin expects 2 args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_FMIN, 0); + free(name); + return 1; + } + if (strcmp(name, "fmax") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { + parser_fail(*pos, "fmax expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "fmax expects 2 args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_FMAX, 0); + free(name); + return 1; + } + if (strcmp(name, "clamp") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { + parser_fail(*pos, "clamp expects 3 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { + parser_fail(*pos, "clamp expects 3 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "clamp expects 3 args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_CLAMP, 0); + free(name); + return 1; + } + if (strcmp(name, "abs") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "abs expects 1 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_ABS, 0); + free(name); + return 1; + } + if (strcmp(name, "floor") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "floor expects 1 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_FLOOR, 0); + free(name); + return 1; + } + if (strcmp(name, "ceil") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "ceil expects 1 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_CEIL, 0); + free(name); + return 1; + } + if (strcmp(name, "trunc") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "trunc expects 1 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_TRUNC, 0); + free(name); + return 1; + } + if (strcmp(name, "round") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "round expects 1 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_ROUND, 0); + free(name); + return 1; + } + if (strcmp(name, "sin") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "sin expects 1 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SIN, 0); + free(name); + return 1; + } + if (strcmp(name, "cos") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "cos expects 1 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_COS, 0); + free(name); + return 1; + } + if (strcmp(name, "tan") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "tan expects 1 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_TAN, 0); + free(name); + return 1; + } + if (strcmp(name, "exp") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "exp expects 1 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_EXP, 0); + free(name); + return 1; + } + if (strcmp(name, "log") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "log expects 1 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_LOG, 0); + free(name); + return 1; + } + if (strcmp(name, "log10") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "log10 expects 1 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_LOG10, 0); + free(name); + return 1; + } + if (strcmp(name, "sqrt") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "sqrt expects 1 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SQRT, 0); + free(name); + return 1; + } + if (strcmp(name, "gcd") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { + parser_fail(*pos, "gcd expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "gcd expects 2 args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_GCD, 0); + free(name); + return 1; + } + if (strcmp(name, "lcm") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { + parser_fail(*pos, "lcm expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "lcm expects 2 args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_LCM, 0); + free(name); + return 1; + } + if (strcmp(name, "isqrt") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "isqrt expects 1 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_ISQRT, 0); + free(name); + return 1; + } + if (strcmp(name, "sign") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "sign expects 1 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SIGN, 0); + free(name); + return 1; + } + if (strcmp(name, "pow") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { + parser_fail(*pos, "pow expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "pow expects 2 args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_POW, 0); + free(name); + return 1; + } + + if (strcmp(name, "random_seed") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "random_seed expects 1 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_RANDOM_SEED, 0); + free(name); + return 1; + } + if (strcmp(name, "random_int") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { + parser_fail(*pos, "random_int expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "random_int expects 2 args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_RANDOM_INT, 0); + free(name); + return 1; + } + + if (strcmp(name, "random_number") == 0) { + (*pos)++; /* '(' */ + /* expects exactly 1 arg: length */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "random_number expects 1 arg (length)"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after random_number arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_RANDOM_NUMBER, 0); + free(name); + return 1; + } + + /* threading */ + if (strcmp(name, "thread_spawn") == 0) { + (*pos)++; /* '(' */ + /* thread_spawn(fn [, args]) */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "thread_spawn expects function as first arg"); + free(name); + return 0; + } + int hasArgs = 0; + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "thread_spawn second arg must be array or value"); + free(name); + return 0; + } + hasArgs = 1; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after thread_spawn args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_THREAD_SPAWN, hasArgs ? 1 : 0); + free(name); + return 1; + } + if (strcmp(name, "thread_join") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "thread_join expects 1 arg (thread id)"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_THREAD_JOIN, 0); + free(name); + return 1; + } + if (strcmp(name, "sleep") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "sleep expects 1 arg (milliseconds)"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SLEEP_MS, 0); + free(name); + return 1; + } + + /* bitwise ops (32-bit) */ + if (strcmp(name, "band") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { + parser_fail(*pos, "band expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "band expects 2 args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_BAND, 0); + free(name); + return 1; + } + if (strcmp(name, "bor") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { + parser_fail(*pos, "bor expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "bor expects 2 args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_BOR, 0); + free(name); + return 1; + } + if (strcmp(name, "bxor") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { + parser_fail(*pos, "bxor expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "bxor expects 2 args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_BXOR, 0); + free(name); + return 1; + } + if (strcmp(name, "bnot") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "bnot expects 1 arg"); + free(name); + return 0; + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "bnot expects 1 arg"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_BNOT, 0); + free(name); + return 1; + } + if (strcmp(name, "shl") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { + parser_fail(*pos, "shl expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "shl expects 2 args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SHL, 0); + free(name); + return 1; + } + if (strcmp(name, "shr") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { + parser_fail(*pos, "shr expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "shr expects 2 args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SHR, 0); + free(name); + return 1; + } + if (strcmp(name, "rol") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { + parser_fail(*pos, "rol expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "rol expects 2 args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_ROTL, 0); + free(name); + return 1; + } + if (strcmp(name, "ror") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { + parser_fail(*pos, "ror expects 2 args"); + free(name); + return 0; + } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { + parser_fail(*pos, "ror expects 2 args"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_ROTR, 0); + free(name); + return 1; + } + + /* push function value first */ + if (local_idx >= 0) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, local_idx); + } else { + int gi = sym_index(name); + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); + } + /* Track namespace alias only for the initial receiver; after any call it's no longer an alias value */ + int __ns_ctx = is_ns_alias(name); + /* parse arguments */ + (*pos)++; /* '(' */ + int argc = 0; + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] != ')') { + do { + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression as function argument"); + free(name); + return 0; + } + argc++; + skip_spaces(src, len, pos); + } while (*pos < len && src[*pos] == ',' && (++(*pos), skip_spaces(src, len, pos), 1)); + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after arguments"); + free(name); + return 0; + } +#ifdef FUN_DEBUG + /* DEBUG: show compiled call */ + printf("compile: CALL %s with %d arg(s)\n", name, argc); +#endif + bytecode_add_instruction(bc, OP_CALL, argc); + /* postfix indexing, slice, and dot access/method calls */ + for (;;) { + skip_spaces(src, len, pos); + + /* index/slice */ + if (*pos < len && src[*pos] == '[') { + (*pos)++; + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "Expected start expression"); + free(name); + return 0; + } + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] == ':') { + (*pos)++; + skip_spaces(src, len, pos); + size_t svp = *pos; + if (!emit_expression(bc, src, len, pos)) { + *pos = svp; + int ci = bytecode_add_constant(bc, make_int(-1)); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci); + } + if (!consume_char(src, len, pos, ']')) { + parser_fail(*pos, "Expected ']' after slice"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SLICE, 0); + continue; + } else { + if (!consume_char(src, len, pos, ']')) { + parser_fail(*pos, "Expected ']' after index"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_INDEX_GET, 0); + continue; + } + } + + /* dot property access and method-call sugar: obj.field or obj.method(...) */ + if (*pos < len && src[*pos] == '.') { + (*pos)++; /* '.' */ + skip_spaces(src, len, pos); + char *mname = NULL; + if (!read_identifier_into(src, len, pos, &mname)) { + parser_fail(*pos, "Expected identifier after '.'"); + free(name); + return 0; + } + int is_private = (mname && mname[0] == '_'); + int kci = bytecode_add_constant(bc, make_string(mname)); + + /* Peek for immediate call: obj.method( ... ) */ + size_t callp = *pos; + skip_spaces(src, len, &callp); + if (callp < len && src[callp] == '(') { + /* If private method on non-'this' receiver in this context -> error */ + if (is_private) { + char msg[160]; + snprintf(msg, sizeof(msg), "AccessError: private method '%s' is not accessible here", mname); + int ci = bytecode_add_constant(bc, make_string(msg)); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_HALT, 0); + free(mname); + continue; + } + + /* After a function call, the receiver is a value (not a namespace alias); + always treat dot-call as a method with implicit 'this'. */ + int is_ns = 0; + + /* Method sugar with implicit 'this' */ + bytecode_add_instruction(bc, OP_DUP, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, kci); + bytecode_add_instruction(bc, OP_INDEX_GET, 0); /* -> stack: obj, func */ + bytecode_add_instruction(bc, OP_SWAP, 0); /* -> stack: func, obj (this) */ + + /* Consume '(' and parse args */ + *pos = callp + 1; int argc = 0; skip_spaces(src, len, pos); if (*pos < len && src[*pos] != ')') { - do { - if (!emit_expression(bc, src, len, pos)) { - parser_fail(*pos, "Expected expression as function argument"); - free(name); - return 0; - } - argc++; - skip_spaces(src, len, pos); - } while (*pos < len && src[*pos] == ',' && (++(*pos), skip_spaces(src, len, pos), 1)); + do { + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression as method argument"); + free(mname); + free(name); + return 0; + } + argc++; + skip_spaces(src, len, pos); + } while (*pos < len && src[*pos] == ',' && (++(*pos), skip_spaces(src, len, pos), 1)); } if (!consume_char(src, len, pos, ')')) { - parser_fail(*pos, "Expected ')' after arguments"); - free(name); - return 0; + parser_fail(*pos, "Expected ')' after arguments"); + free(mname); + free(name); + return 0; } -#ifdef FUN_DEBUG - /* DEBUG: show compiled call */ - printf("compile: CALL %s with %d arg(s)\n", name, argc); -#endif - bytecode_add_instruction(bc, OP_CALL, argc); - /* postfix indexing, slice, and dot access/method calls */ - for (;;) { - skip_spaces(src, len, pos); - /* index/slice */ - if (*pos < len && src[*pos] == '[') { - (*pos)++; - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected start expression"); free(name); return 0; } - skip_spaces(src, len, pos); - if (*pos < len && src[*pos] == ':') { - (*pos)++; - skip_spaces(src, len, pos); - size_t svp = *pos; - if (!emit_expression(bc, src, len, pos)) { - *pos = svp; - int ci = bytecode_add_constant(bc, make_int(-1)); - bytecode_add_instruction(bc, OP_LOAD_CONST, ci); - } - if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after slice"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SLICE, 0); - continue; - } else { - if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after index"); free(name); return 0; } - bytecode_add_instruction(bc, OP_INDEX_GET, 0); - continue; - } - } - - /* dot property access and method-call sugar: obj.field or obj.method(...) */ - if (*pos < len && src[*pos] == '.') { - (*pos)++; /* '.' */ - skip_spaces(src, len, pos); - char *mname = NULL; - if (!read_identifier_into(src, len, pos, &mname)) { parser_fail(*pos, "Expected identifier after '.'"); free(name); return 0; } - int is_private = (mname && mname[0] == '_'); - int kci = bytecode_add_constant(bc, make_string(mname)); - - /* Peek for immediate call: obj.method( ... ) */ - size_t callp = *pos; - skip_spaces(src, len, &callp); - if (callp < len && src[callp] == '(') { - /* If private method on non-'this' receiver in this context -> error */ - if (is_private) { - char msg[160]; - snprintf(msg, sizeof(msg), "AccessError: private method '%s' is not accessible here", mname); - int ci = bytecode_add_constant(bc, make_string(msg)); - bytecode_add_instruction(bc, OP_LOAD_CONST, ci); - bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_HALT, 0); - free(mname); - continue; - } - - /* After a function call, the receiver is a value (not a namespace alias); - always treat dot-call as a method with implicit 'this'. */ - int is_ns = 0; - - /* Method sugar with implicit 'this' */ - bytecode_add_instruction(bc, OP_DUP, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, kci); - bytecode_add_instruction(bc, OP_INDEX_GET, 0); /* -> stack: obj, func */ - bytecode_add_instruction(bc, OP_SWAP, 0); /* -> stack: func, obj (this) */ - - /* Consume '(' and parse args */ - *pos = callp + 1; - int argc = 0; - skip_spaces(src, len, pos); - if (*pos < len && src[*pos] != ')') { - do { - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected expression as method argument"); free(mname); free(name); return 0; } - argc++; - skip_spaces(src, len, pos); - } while (*pos < len && src[*pos] == ',' && (++(*pos), skip_spaces(src, len, pos), 1)); - } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after arguments"); free(mname); free(name); return 0; } - - /* Call */ - bytecode_add_instruction(bc, OP_CALL, is_ns ? argc : (argc + 1)); - free(mname); - continue; - } else { - /* Plain property get: obj["field"] */ - bytecode_add_instruction(bc, OP_LOAD_CONST, kci); - bytecode_add_instruction(bc, OP_INDEX_GET, 0); - free(mname); - continue; - } - } - - break; - } - free(name); - return 1; - } else { - if (local_idx >= 0) { - bytecode_add_instruction(bc, OP_LOAD_LOCAL, local_idx); - } else { - int gi = sym_index(name); - bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); - } - /* track namespace alias only for the initial receiver; after any call it's no longer an alias value */ - int __ns_ctx = is_ns_alias(name); - /* postfix indexing, slice, and dot access/method calls */ - for (;;) { - skip_spaces(src, len, pos); - - /* index/slice */ - if (*pos < len && src[*pos] == '[') { - (*pos)++; - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected start expression"); free(name); return 0; } - skip_spaces(src, len, pos); - if (*pos < len && src[*pos] == ':') { - (*pos)++; - skip_spaces(src, len, pos); - size_t svp = *pos; - if (!emit_expression(bc, src, len, pos)) { - *pos = svp; - int ci = bytecode_add_constant(bc, make_int(-1)); - bytecode_add_instruction(bc, OP_LOAD_CONST, ci); - } - if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after slice"); free(name); return 0; } - bytecode_add_instruction(bc, OP_SLICE, 0); - continue; - } else { - if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after index"); free(name); return 0; } - bytecode_add_instruction(bc, OP_INDEX_GET, 0); - continue; - } - } - - /* dot property access and method-call sugar */ - if (*pos < len && src[*pos] == '.') { - (*pos)++; - skip_spaces(src, len, pos); - char *mname = NULL; - if (!read_identifier_into(src, len, pos, &mname)) { parser_fail(*pos, "Expected identifier after '.'"); free(name); return 0; } - int is_private = (mname && mname[0] == '_'); - int kci = bytecode_add_constant(bc, make_string(mname)); - - /* Peek for call */ - size_t callp = *pos; - skip_spaces(src, len, &callp); - if (callp < len && src[callp] == '(') { - /* If private and receiver is not 'this' -> error */ - if (is_private && !(strcmp(name, "this") == 0)) { - char msg[160]; - snprintf(msg, sizeof(msg), "AccessError: private method '%s' is not accessible", mname); - int ci = bytecode_add_constant(bc, make_string(msg)); - bytecode_add_instruction(bc, OP_LOAD_CONST, ci); - bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_HALT, 0); - free(mname); - continue; - } - - /* Use initial alias context for only the first dot-call; reset after call */ - int is_ns = __ns_ctx; - - if (!is_ns) { - /* Method sugar with implicit 'this' */ - bytecode_add_instruction(bc, OP_DUP, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, kci); - bytecode_add_instruction(bc, OP_INDEX_GET, 0); /* -> obj, func */ - bytecode_add_instruction(bc, OP_SWAP, 0); /* -> func, obj */ - } else { - /* Plain property function call */ - bytecode_add_instruction(bc, OP_LOAD_CONST, kci); - bytecode_add_instruction(bc, OP_INDEX_GET, 0); /* -> func */ - } - - *pos = callp + 1; - int argc = 0; - skip_spaces(src, len, pos); - if (*pos < len && src[*pos] != ')') { - do { - if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected expression as method argument"); free(mname); free(name); return 0; } - argc++; - skip_spaces(src, len, pos); - } while (*pos < len && src[*pos] == ',' && (++(*pos), skip_spaces(src, len, pos), 1)); - } - if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after arguments"); free(mname); free(name); return 0; } - - bytecode_add_instruction(bc, OP_CALL, is_ns ? argc : (argc + 1)); - /* After any call, the receiver is now a value, not a namespace alias */ - __ns_ctx = 0; - - free(mname); - continue; - } else { - /* plain property get (allowed even for private name) */ - bytecode_add_instruction(bc, OP_LOAD_CONST, kci); - bytecode_add_instruction(bc, OP_INDEX_GET, 0); - free(mname); - continue; - } - } - - break; - } - free(name); - return 1; + /* Call */ + bytecode_add_instruction(bc, OP_CALL, is_ns ? argc : (argc + 1)); + free(mname); + continue; + } else { + /* Plain property get: obj["field"] */ + bytecode_add_instruction(bc, OP_LOAD_CONST, kci); + bytecode_add_instruction(bc, OP_INDEX_GET, 0); + free(mname); + continue; + } } - } - return 0; + break; + } + free(name); + return 1; + } else { + if (local_idx >= 0) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, local_idx); + } else { + int gi = sym_index(name); + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); + } + /* track namespace alias only for the initial receiver; after any call it's no longer an alias value */ + int __ns_ctx = is_ns_alias(name); + /* postfix indexing, slice, and dot access/method calls */ + for (;;) { + skip_spaces(src, len, pos); + + /* index/slice */ + if (*pos < len && src[*pos] == '[') { + (*pos)++; + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "Expected start expression"); + free(name); + return 0; + } + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] == ':') { + (*pos)++; + skip_spaces(src, len, pos); + size_t svp = *pos; + if (!emit_expression(bc, src, len, pos)) { + *pos = svp; + int ci = bytecode_add_constant(bc, make_int(-1)); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci); + } + if (!consume_char(src, len, pos, ']')) { + parser_fail(*pos, "Expected ']' after slice"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_SLICE, 0); + continue; + } else { + if (!consume_char(src, len, pos, ']')) { + parser_fail(*pos, "Expected ']' after index"); + free(name); + return 0; + } + bytecode_add_instruction(bc, OP_INDEX_GET, 0); + continue; + } + } + + /* dot property access and method-call sugar */ + if (*pos < len && src[*pos] == '.') { + (*pos)++; + skip_spaces(src, len, pos); + char *mname = NULL; + if (!read_identifier_into(src, len, pos, &mname)) { + parser_fail(*pos, "Expected identifier after '.'"); + free(name); + return 0; + } + int is_private = (mname && mname[0] == '_'); + int kci = bytecode_add_constant(bc, make_string(mname)); + + /* Peek for call */ + size_t callp = *pos; + skip_spaces(src, len, &callp); + if (callp < len && src[callp] == '(') { + /* If private and receiver is not 'this' -> error */ + if (is_private && !(strcmp(name, "this") == 0)) { + char msg[160]; + snprintf(msg, sizeof(msg), "AccessError: private method '%s' is not accessible", mname); + int ci = bytecode_add_constant(bc, make_string(msg)); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_HALT, 0); + free(mname); + continue; + } + + /* Use initial alias context for only the first dot-call; reset after call */ + int is_ns = __ns_ctx; + + if (!is_ns) { + /* Method sugar with implicit 'this' */ + bytecode_add_instruction(bc, OP_DUP, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, kci); + bytecode_add_instruction(bc, OP_INDEX_GET, 0); /* -> obj, func */ + bytecode_add_instruction(bc, OP_SWAP, 0); /* -> func, obj */ + } else { + /* Plain property function call */ + bytecode_add_instruction(bc, OP_LOAD_CONST, kci); + bytecode_add_instruction(bc, OP_INDEX_GET, 0); /* -> func */ + } + + *pos = callp + 1; + int argc = 0; + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] != ')') { + do { + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression as method argument"); + free(mname); + free(name); + return 0; + } + argc++; + skip_spaces(src, len, pos); + } while (*pos < len && src[*pos] == ',' && (++(*pos), skip_spaces(src, len, pos), 1)); + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after arguments"); + free(mname); + free(name); + return 0; + } + + bytecode_add_instruction(bc, OP_CALL, is_ns ? argc : (argc + 1)); + /* After any call, the receiver is now a value, not a namespace alias */ + __ns_ctx = 0; + + free(mname); + continue; + } else { + /* plain property get (allowed even for private name) */ + bytecode_add_instruction(bc, OP_LOAD_CONST, kci); + bytecode_add_instruction(bc, OP_INDEX_GET, 0); + free(mname); + continue; + } + } + + break; + } + free(name); + return 1; + } + } + + return 0; } /* unary: '!' unary | '-' unary | primary */ static int emit_unary(Bytecode *bc, const char *src, size_t len, size_t *pos) { - skip_spaces(src, len, pos); - if (*pos < len && src[*pos] == '!') { - (*pos)++; - if (!emit_unary(bc, src, len, pos)) { - parser_fail(*pos, "Expected expression after '!'"); - return 0; - } - bytecode_add_instruction(bc, OP_NOT, 0); - return 1; + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] == '!') { + (*pos)++; + if (!emit_unary(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression after '!'"); + return 0; } - if (*pos < len && src[*pos] == '-') { - (*pos)++; - /* unary minus -> 0 - expr */ - int ci = bytecode_add_constant(bc, make_int(0)); - bytecode_add_instruction(bc, OP_LOAD_CONST, ci); - if (!emit_unary(bc, src, len, pos)) { - parser_fail(*pos, "Expected expression after unary '-'"); - return 0; - } - bytecode_add_instruction(bc, OP_SUB, 0); - return 1; + bytecode_add_instruction(bc, OP_NOT, 0); + return 1; + } + if (*pos < len && src[*pos] == '-') { + (*pos)++; + /* unary minus -> 0 - expr */ + int ci = bytecode_add_constant(bc, make_int(0)); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci); + if (!emit_unary(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression after unary '-'"); + return 0; } - return emit_primary(bc, src, len, pos); + bytecode_add_instruction(bc, OP_SUB, 0); + return 1; + } + return emit_primary(bc, src, len, pos); } /* multiplicative: unary (('*' | '/' | '%') unary)* */ static int emit_multiplicative(Bytecode *bc, const char *src, size_t len, size_t *pos) { - if (!emit_unary(bc, src, len, pos)) return 0; - for (;;) { - skip_spaces(src, len, pos); + if (!emit_unary(bc, src, len, pos)) return 0; + for (;;) { + skip_spaces(src, len, pos); - /* Stop expression at start of inline comment */ - if (*pos + 1 < len && src[*pos] == '/' && src[*pos + 1] == '/') { - break; - } - /* Skip block comments inside expressions */ - if (*pos + 1 < len && src[*pos] == '/' && src[*pos + 1] == '*') { - size_t p = *pos + 2; - while (p + 1 < len && !(src[p] == '*' && src[p + 1] == '/')) { - p++; - } - if (p + 1 < len) p += 2; /* consume closing marker */ - *pos = p; - continue; - } - - if (*pos < len && src[*pos] == '*') { - (*pos)++; - if (!emit_unary(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '*'"); return 0; } - bytecode_add_instruction(bc, OP_MUL, 0); - continue; - } - if (*pos < len && src[*pos] == '/') { - (*pos)++; - if (!emit_unary(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '/'"); return 0; } - bytecode_add_instruction(bc, OP_DIV, 0); - continue; - } - if (*pos < len && src[*pos] == '%') { - (*pos)++; - if (!emit_unary(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '%'"); return 0; } - bytecode_add_instruction(bc, OP_MOD, 0); - continue; - } - break; + /* Stop expression at start of inline comment */ + if (*pos + 1 < len && src[*pos] == '/' && src[*pos + 1] == '/') { + break; } - return 1; + /* Skip block comments inside expressions */ + if (*pos + 1 < len && src[*pos] == '/' && src[*pos + 1] == '*') { + size_t p = *pos + 2; + while (p + 1 < len && !(src[p] == '*' && src[p + 1] == '/')) { + p++; + } + if (p + 1 < len) p += 2; /* consume closing marker */ + *pos = p; + continue; + } + + if (*pos < len && src[*pos] == '*') { + (*pos)++; + if (!emit_unary(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression after '*'"); + return 0; + } + bytecode_add_instruction(bc, OP_MUL, 0); + continue; + } + if (*pos < len && src[*pos] == '/') { + (*pos)++; + if (!emit_unary(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression after '/'"); + return 0; + } + bytecode_add_instruction(bc, OP_DIV, 0); + continue; + } + if (*pos < len && src[*pos] == '%') { + (*pos)++; + if (!emit_unary(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression after '%'"); + return 0; + } + bytecode_add_instruction(bc, OP_MOD, 0); + continue; + } + break; + } + return 1; } /* additive: multiplicative (('+' | '-') multiplicative)* */ static int emit_additive(Bytecode *bc, const char *src, size_t len, size_t *pos) { - if (!emit_multiplicative(bc, src, len, pos)) return 0; - for (;;) { - skip_spaces(src, len, pos); - if (*pos < len && src[*pos] == '+') { - (*pos)++; - if (!emit_multiplicative(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '+'"); return 0; } - bytecode_add_instruction(bc, OP_ADD, 0); - continue; - } - if (*pos < len && src[*pos] == '-') { - (*pos)++; - if (!emit_multiplicative(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '-'"); return 0; } - bytecode_add_instruction(bc, OP_SUB, 0); - continue; - } - break; + if (!emit_multiplicative(bc, src, len, pos)) return 0; + for (;;) { + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] == '+') { + (*pos)++; + if (!emit_multiplicative(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression after '+'"); + return 0; + } + bytecode_add_instruction(bc, OP_ADD, 0); + continue; } - return 1; + if (*pos < len && src[*pos] == '-') { + (*pos)++; + if (!emit_multiplicative(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression after '-'"); + return 0; + } + bytecode_add_instruction(bc, OP_SUB, 0); + continue; + } + break; + } + return 1; } /* relational: additive (('<' | '<=' | '>' | '>=') additive)* */ static int emit_relational(Bytecode *bc, const char *src, size_t len, size_t *pos) { - if (!emit_additive(bc, src, len, pos)) return 0; - for (;;) { - skip_spaces(src, len, pos); - if (*pos + 1 < len && src[*pos] == '<' && src[*pos + 1] == '=') { - *pos += 2; - if (!emit_additive(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '<='"); return 0; } - bytecode_add_instruction(bc, OP_LTE, 0); - continue; - } - if (*pos + 1 < len && src[*pos] == '>' && src[*pos + 1] == '=') { - *pos += 2; - if (!emit_additive(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '>='"); return 0; } - bytecode_add_instruction(bc, OP_GTE, 0); - continue; - } - if (*pos < len && src[*pos] == '<') { - (*pos)++; - if (!emit_additive(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '<'"); return 0; } - bytecode_add_instruction(bc, OP_LT, 0); - continue; - } - if (*pos < len && src[*pos] == '>') { - (*pos)++; - if (!emit_additive(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '>'"); return 0; } - bytecode_add_instruction(bc, OP_GT, 0); - continue; - } - break; + if (!emit_additive(bc, src, len, pos)) return 0; + for (;;) { + skip_spaces(src, len, pos); + if (*pos + 1 < len && src[*pos] == '<' && src[*pos + 1] == '=') { + *pos += 2; + if (!emit_additive(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression after '<='"); + return 0; + } + bytecode_add_instruction(bc, OP_LTE, 0); + continue; } - return 1; + if (*pos + 1 < len && src[*pos] == '>' && src[*pos + 1] == '=') { + *pos += 2; + if (!emit_additive(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression after '>='"); + return 0; + } + bytecode_add_instruction(bc, OP_GTE, 0); + continue; + } + if (*pos < len && src[*pos] == '<') { + (*pos)++; + if (!emit_additive(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression after '<'"); + return 0; + } + bytecode_add_instruction(bc, OP_LT, 0); + continue; + } + if (*pos < len && src[*pos] == '>') { + (*pos)++; + if (!emit_additive(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression after '>'"); + return 0; + } + bytecode_add_instruction(bc, OP_GT, 0); + continue; + } + break; + } + return 1; } /* equality: relational (('==' | '!=') relational)* */ static int emit_equality(Bytecode *bc, const char *src, size_t len, size_t *pos) { - if (!emit_relational(bc, src, len, pos)) return 0; - for (;;) { - skip_spaces(src, len, pos); - if (*pos + 1 < len && src[*pos] == '=' && src[*pos + 1] == '=') { - *pos += 2; - if (!emit_relational(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '=='"); return 0; } - bytecode_add_instruction(bc, OP_EQ, 0); - continue; - } - if (*pos + 1 < len && src[*pos] == '!' && src[*pos + 1] == '=') { - *pos += 2; - if (!emit_relational(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '!='"); return 0; } - bytecode_add_instruction(bc, OP_NEQ, 0); - continue; - } - break; + if (!emit_relational(bc, src, len, pos)) return 0; + for (;;) { + skip_spaces(src, len, pos); + if (*pos + 1 < len && src[*pos] == '=' && src[*pos + 1] == '=') { + *pos += 2; + if (!emit_relational(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression after '=='"); + return 0; + } + bytecode_add_instruction(bc, OP_EQ, 0); + continue; } - return 1; + if (*pos + 1 < len && src[*pos] == '!' && src[*pos + 1] == '=') { + *pos += 2; + if (!emit_relational(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression after '!='"); + return 0; + } + bytecode_add_instruction(bc, OP_NEQ, 0); + continue; + } + break; + } + return 1; } /* logical AND with short-circuit: equality ( '&&' equality )* */ static int emit_and_expr(Bytecode *bc, const char *src, size_t len, size_t *pos) { - int jf_idxs[64]; - int jf_count = 0; - int has_and = 0; + int jf_idxs[64]; + int jf_count = 0; + int has_and = 0; - /* first operand */ - if (!emit_equality(bc, src, len, pos)) return 0; + /* first operand */ + if (!emit_equality(bc, src, len, pos)) return 0; - for (;;) { - skip_spaces(src, len, pos); - if (!(*pos + 1 < len && src[*pos] == '&' && src[*pos + 1] == '&')) break; - *pos += 2; - has_and = 1; + for (;;) { + skip_spaces(src, len, pos); + if (!(*pos + 1 < len && src[*pos] == '&' && src[*pos + 1] == '&')) break; + *pos += 2; + has_and = 1; - /* if current value is false -> jump to false label (patched later) */ - if (jf_count < (int)(sizeof(jf_idxs) / sizeof(jf_idxs[0]))) { - jf_idxs[jf_count++] = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); - } else { - parser_fail(*pos, "Too many operands in '&&' chain"); - return 0; - } - - /* evaluate next operand */ - if (!emit_equality(bc, src, len, pos)) { - parser_fail(*pos, "Expected expression after '&&'"); - return 0; - } + /* if current value is false -> jump to false label (patched later) */ + if (jf_count < (int)(sizeof(jf_idxs) / sizeof(jf_idxs[0]))) { + jf_idxs[jf_count++] = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + } else { + parser_fail(*pos, "Too many operands in '&&' chain"); + return 0; } - if (has_and) { - /* final: if last operand is false -> jump false */ - if (jf_count < (int)(sizeof(jf_idxs) / sizeof(jf_idxs[0]))) { - jf_idxs[jf_count++] = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); - } else { - parser_fail(*pos, "Too many operands in '&&' chain"); - return 0; - } + /* evaluate next operand */ + if (!emit_equality(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression after '&&'"); + return 0; + } + } - /* all were truthy -> result true */ - int c1 = bytecode_add_constant(bc, make_bool(1)); - bytecode_add_instruction(bc, OP_LOAD_CONST, c1); - int j_end = bytecode_add_instruction(bc, OP_JUMP, 0); - - /* false label: patch all false jumps here, result false */ - int l_false = bc->instr_count; - for (int i = 0; i < jf_count; ++i) { - bytecode_set_operand(bc, jf_idxs[i], l_false); - } - int c0 = bytecode_add_constant(bc, make_bool(0)); - bytecode_add_instruction(bc, OP_LOAD_CONST, c0); - - /* end */ - int l_end = bc->instr_count; - bytecode_set_operand(bc, j_end, l_end); + if (has_and) { + /* final: if last operand is false -> jump false */ + if (jf_count < (int)(sizeof(jf_idxs) / sizeof(jf_idxs[0]))) { + jf_idxs[jf_count++] = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + } else { + parser_fail(*pos, "Too many operands in '&&' chain"); + return 0; } - return 1; + /* all were truthy -> result true */ + int c1 = bytecode_add_constant(bc, make_bool(1)); + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + int j_end = bytecode_add_instruction(bc, OP_JUMP, 0); + + /* false label: patch all false jumps here, result false */ + int l_false = bc->instr_count; + for (int i = 0; i < jf_count; ++i) { + bytecode_set_operand(bc, jf_idxs[i], l_false); + } + int c0 = bytecode_add_constant(bc, make_bool(0)); + bytecode_add_instruction(bc, OP_LOAD_CONST, c0); + + /* end */ + int l_end = bc->instr_count; + bytecode_set_operand(bc, j_end, l_end); + } + + return 1; } /* logical OR with short-circuit: and_expr ( '||' and_expr )* */ static int emit_or_expr(Bytecode *bc, const char *src, size_t len, size_t *pos) { - int true_jumps[64]; - int tj_count = 0; - int has_or = 0; + int true_jumps[64]; + int tj_count = 0; + int has_or = 0; - /* first operand */ - if (!emit_and_expr(bc, src, len, pos)) return 0; + /* first operand */ + if (!emit_and_expr(bc, src, len, pos)) return 0; - for (;;) { - skip_spaces(src, len, pos); - if (!(*pos + 1 < len && src[*pos] == '|' && src[*pos + 1] == '|')) break; - *pos += 2; - has_or = 1; + for (;;) { + skip_spaces(src, len, pos); + if (!(*pos + 1 < len && src[*pos] == '|' && src[*pos + 1] == '|')) break; + *pos += 2; + has_or = 1; - /* if current value is false -> proceed to next; else -> result true */ - int jf_proceed = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + /* if current value is false -> proceed to next; else -> result true */ + int jf_proceed = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); - /* true path: push true and jump to end */ - int c1 = bytecode_add_constant(bc, make_bool(1)); - bytecode_add_instruction(bc, OP_LOAD_CONST, c1); - if (tj_count < (int)(sizeof(true_jumps) / sizeof(true_jumps[0]))) { - true_jumps[tj_count++] = bytecode_add_instruction(bc, OP_JUMP, 0); - } else { - parser_fail(*pos, "Too many operands in '||' chain"); - return 0; - } - - /* patch to start of next operand */ - bytecode_set_operand(bc, jf_proceed, bc->instr_count); - - /* evaluate next operand */ - if (!emit_and_expr(bc, src, len, pos)) { - parser_fail(*pos, "Expected expression after '||'"); - return 0; - } + /* true path: push true and jump to end */ + int c1 = bytecode_add_constant(bc, make_bool(1)); + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + if (tj_count < (int)(sizeof(true_jumps) / sizeof(true_jumps[0]))) { + true_jumps[tj_count++] = bytecode_add_instruction(bc, OP_JUMP, 0); + } else { + parser_fail(*pos, "Too many operands in '||' chain"); + return 0; } - if (has_or) { - /* After evaluating the last operand: test it and produce 1/0 */ - int jf_last = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + /* patch to start of next operand */ + bytecode_set_operand(bc, jf_proceed, bc->instr_count); - int c1 = bytecode_add_constant(bc, make_int(1)); - bytecode_add_instruction(bc, OP_LOAD_CONST, c1); - int j_end_single = bytecode_add_instruction(bc, OP_JUMP, 0); - - int l_false = bc->instr_count; - bytecode_set_operand(bc, jf_last, l_false); - int c0 = bytecode_add_constant(bc, make_int(0)); - bytecode_add_instruction(bc, OP_LOAD_CONST, c0); - - int l_end = bc->instr_count; - bytecode_set_operand(bc, j_end_single, l_end); - for (int i = 0; i < tj_count; ++i) { - bytecode_set_operand(bc, true_jumps[i], l_end); - } + /* evaluate next operand */ + if (!emit_and_expr(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression after '||'"); + return 0; } + } - return 1; + if (has_or) { + /* After evaluating the last operand: test it and produce 1/0 */ + int jf_last = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + + int c1 = bytecode_add_constant(bc, make_int(1)); + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + int j_end_single = bytecode_add_instruction(bc, OP_JUMP, 0); + + int l_false = bc->instr_count; + bytecode_set_operand(bc, jf_last, l_false); + int c0 = bytecode_add_constant(bc, make_int(0)); + bytecode_add_instruction(bc, OP_LOAD_CONST, c0); + + int l_end = bc->instr_count; + bytecode_set_operand(bc, j_end_single, l_end); + for (int i = 0; i < tj_count; ++i) { + bytecode_set_operand(bc, true_jumps[i], l_end); + } + } + + return 1; } /* conditional operator (ternary) with right associativity: Parses: logical_or ('?' conditional ':' conditional)? */ static int emit_conditional(Bytecode *bc, const char *src, size_t len, size_t *pos) { - /* parse condition (logical OR precedence or higher) */ - if (!emit_or_expr(bc, src, len, pos)) return 0; + /* parse condition (logical OR precedence or higher) */ + if (!emit_or_expr(bc, src, len, pos)) return 0; - for (;;) { - skip_spaces(src, len, pos); - if (!(*pos < len && src[*pos] == '?')) break; - (*pos)++; /* consume '?' */ + for (;;) { + skip_spaces(src, len, pos); + if (!(*pos < len && src[*pos] == '?')) break; + (*pos)++; /* consume '?' */ - /* If condition is false -> jump to false arm */ - int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + /* If condition is false -> jump to false arm */ + int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); - /* true arm (right-assoc: allow nested ternaries) */ - skip_spaces(src, len, pos); - if (!emit_conditional(bc, src, len, pos)) { - parser_fail(*pos, "Expected expression after '?'"); - return 0; - } - - /* After true arm, unconditionally skip false arm */ - int jmp_end = bytecode_add_instruction(bc, OP_JUMP, 0); - - /* false arm label */ - bytecode_set_operand(bc, jmp_false, bc->instr_count); - - /* require ':' */ - skip_spaces(src, len, pos); - if (!(*pos < len && src[*pos] == ':')) { - parser_fail(*pos, "Expected ':' in conditional expression"); - return 0; - } - (*pos)++; /* consume ':' */ - - /* false arm (right-assoc) */ - skip_spaces(src, len, pos); - if (!emit_conditional(bc, src, len, pos)) { - parser_fail(*pos, "Expected expression after ':'"); - return 0; - } - - /* end label */ - bytecode_set_operand(bc, jmp_end, bc->instr_count); - /* loop to allow chaining like a ? b : c ? d : e (right-assoc) */ + /* true arm (right-assoc: allow nested ternaries) */ + skip_spaces(src, len, pos); + if (!emit_conditional(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression after '?'"); + return 0; } - return 1; + + /* After true arm, unconditionally skip false arm */ + int jmp_end = bytecode_add_instruction(bc, OP_JUMP, 0); + + /* false arm label */ + bytecode_set_operand(bc, jmp_false, bc->instr_count); + + /* require ':' */ + skip_spaces(src, len, pos); + if (!(*pos < len && src[*pos] == ':')) { + parser_fail(*pos, "Expected ':' in conditional expression"); + return 0; + } + (*pos)++; /* consume ':' */ + + /* false arm (right-assoc) */ + skip_spaces(src, len, pos); + if (!emit_conditional(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression after ':'"); + return 0; + } + + /* end label */ + bytecode_set_operand(bc, jmp_end, bc->instr_count); + /* loop to allow chaining like a ? b : c ? d : e (right-assoc) */ + } + return 1; } /* top-level expression */ static int emit_expression(Bytecode *bc, const char *src, size_t len, size_t *pos) { - return emit_conditional(bc, src, len, pos); + return emit_conditional(bc, src, len, pos); } /* @@ -2749,120 +4826,145 @@ static int emit_expression(Bytecode *bc, const char *src, size_t len, size_t *po /* line/indent utilities */ static void skip_to_eol(const char *src, size_t len, size_t *pos) { - /* Strict mode: only allow trailing spaces and comments until end-of-line. */ - size_t p = *pos; + /* Strict mode: only allow trailing spaces and comments until end-of-line. */ + size_t p = *pos; - for (;;) { - /* skip spaces */ - while (p < len && src[p] == ' ') p++; + for (;;) { + /* skip spaces */ + while (p < len && src[p] == ' ') + p++; - if (p >= len) { *pos = p; return; } - /* Treat CR, LF, and CRLF as end-of-line */ - if (src[p] == '\r') { - p++; - if (p < len && src[p] == '\n') p++; - *pos = p; - return; - } - if (src[p] == '\n') { - *pos = p + 1; - return; - } + if (p >= len) { + *pos = p; + return; + } + /* Treat CR, LF, and CRLF as end-of-line */ + if (src[p] == '\r') { + p++; + if (p < len && src[p] == '\n') p++; + *pos = p; + return; + } + if (src[p] == '\n') { + *pos = p + 1; + return; + } - /* line or block comments allowed */ - if (p + 1 < len && src[p] == '/' && src[p + 1] == '/') { - /* consume rest of line up to CR or LF */ - p += 2; - while (p < len && src[p] != '\n' && src[p] != '\r') p++; - if (p < len && src[p] == '\r') { - p++; - if (p < len && src[p] == '\n') p++; - } else if (p < len && src[p] == '\n') { - p++; - } - *pos = p; - return; - } + /* line or block comments allowed */ + if (p + 1 < len && src[p] == '/' && src[p + 1] == '/') { + /* consume rest of line up to CR or LF */ + p += 2; + while (p < len && src[p] != '\n' && src[p] != '\r') + p++; + if (p < len && src[p] == '\r') { + p++; + if (p < len && src[p] == '\n') p++; + } else if (p < len && src[p] == '\n') { + p++; + } + *pos = p; + return; + } - if (p + 1 < len && src[p] == '/' && src[p + 1] == '*') { - /* consume block comment, then loop again for spaces till EOL */ - p += 2; - while (p + 1 < len && !(src[p] == '*' && src[p + 1] == '/')) { - p++; - } - if (p + 1 < len) { - p += 2; /* consume closing */ - continue; - } else { - parser_fail(p, "Unterminated block comment at end of file"); - *pos = p; - return; - } - } - - /* Any other character here is unexpected trailing garbage */ - parser_fail(p, "Unexpected trailing characters at end of line"); + if (p + 1 < len && src[p] == '/' && src[p + 1] == '*') { + /* consume block comment, then loop again for spaces till EOL */ + p += 2; + while (p + 1 < len && !(src[p] == '*' && src[p + 1] == '/')) { + p++; + } + if (p + 1 < len) { + p += 2; /* consume closing */ + continue; + } else { + parser_fail(p, "Unterminated block comment at end of file"); *pos = p; return; + } } + + /* Any other character here is unexpected trailing garbage */ + parser_fail(p, "Unexpected trailing characters at end of line"); + *pos = p; + return; + } } static int read_line_start(const char *src, size_t len, size_t *pos, int *out_indent) { - while (*pos < len) { - size_t p = *pos; - int spaces = 0; - while (p < len && src[p] == ' ') { spaces++; p++; } - if (p < len && src[p] == '\t') { - parser_fail(p, "Tabs are forbidden for indentation"); - return 0; - } - if (p >= len) { *pos = p; return 0; } - - /* empty line: handle CR, LF, and CRLF */ - if (src[p] == '\r') { - p++; - if (p < len && src[p] == '\n') p++; - *pos = p; - continue; - } - if (src[p] == '\n') { p++; *pos = p; continue; } - - /* // comment-only line */ - if (p + 1 < len && src[p] == '/' && src[p + 1] == '/') { - /* skip entire line up to CR/LF */ - p += 2; - while (p < len && src[p] != '\n' && src[p] != '\r') p++; - if (p < len && src[p] == '\r') { p++; if (p < len && src[p] == '\n') p++; } - else if (p < len && src[p] == '\n') { p++; } - *pos = p; - continue; - } - - // block comment starting at line (treat as comment-only line) - if (p + 1 < len && src[p] == '/' && src[p + 1] == '*') { - p += 2; - /* advance until we find closing block comment marker */ - while (p + 1 < len && !(src[p] == '*' && src[p + 1] == '/')) { - p++; - } - if (p + 1 < len) p += 2; /* consume closing block comment marker */ - /* consume to end of current line (if any leftover) */ - while (p < len && src[p] != '\n' && src[p] != '\r') p++; - if (p < len && src[p] == '\r') { p++; if (p < len && src[p] == '\n') p++; } - else if (p < len && src[p] == '\n') { p++; } - *pos = p; - continue; - } - - if (spaces % 2 != 0) { - parser_fail(p, "Indentation must be multiples of two spaces"); - return 0; - } - *out_indent = spaces / 2; - *pos = p; /* point to first code char */ - return 1; + while (*pos < len) { + size_t p = *pos; + int spaces = 0; + while (p < len && src[p] == ' ') { + spaces++; + p++; } - return 0; + if (p < len && src[p] == '\t') { + parser_fail(p, "Tabs are forbidden for indentation"); + return 0; + } + if (p >= len) { + *pos = p; + return 0; + } + + /* empty line: handle CR, LF, and CRLF */ + if (src[p] == '\r') { + p++; + if (p < len && src[p] == '\n') p++; + *pos = p; + continue; + } + if (src[p] == '\n') { + p++; + *pos = p; + continue; + } + + /* // comment-only line */ + if (p + 1 < len && src[p] == '/' && src[p + 1] == '/') { + /* skip entire line up to CR/LF */ + p += 2; + while (p < len && src[p] != '\n' && src[p] != '\r') + p++; + if (p < len && src[p] == '\r') { + p++; + if (p < len && src[p] == '\n') p++; + } else if (p < len && src[p] == '\n') { + p++; + } + *pos = p; + continue; + } + + // block comment starting at line (treat as comment-only line) + if (p + 1 < len && src[p] == '/' && src[p + 1] == '*') { + p += 2; + /* advance until we find closing block comment marker */ + while (p + 1 < len && !(src[p] == '*' && src[p + 1] == '/')) { + p++; + } + if (p + 1 < len) p += 2; /* consume closing block comment marker */ + /* consume to end of current line (if any leftover) */ + while (p < len && src[p] != '\n' && src[p] != '\r') + p++; + if (p < len && src[p] == '\r') { + p++; + if (p < len && src[p] == '\n') p++; + } else if (p < len && src[p] == '\n') { + p++; + } + *pos = p; + continue; + } + + if (spaces % 2 != 0) { + parser_fail(p, "Indentation must be multiples of two spaces"); + return 0; + } + *out_indent = spaces / 2; + *pos = p; /* point to first code char */ + return 1; + } + return 0; } /* forward decl */ @@ -2870,2182 +4972,2219 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, /* parse and emit a single simple (non-if) statement on the current line */ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, size_t *pos) { - size_t local_pos = *pos; - char *name = NULL; - if (read_identifier_into(src, len, &local_pos, &name)) { + size_t local_pos = *pos; + char *name = NULL; + if (read_identifier_into(src, len, &local_pos, &name)) { - /* alias: accept 'sint*' as synonyms for 'int*' */ - if (strcmp(name, "sint8") == 0) { free(name); name = strdup("int8"); } - else if (strcmp(name, "sint16") == 0) { free(name); name = strdup("int16"); } - else if (strcmp(name, "sint32") == 0) { free(name); name = strdup("int32"); } - else if (strcmp(name, "sint64") == 0) { free(name); name = strdup("int64"); } - - /* return statement */ - if (strcmp(name, "return") == 0) { - free(name); - skip_spaces(src, len, &local_pos); - /* optional expression */ - size_t save_pos = local_pos; - if (emit_expression(bc, src, len, &local_pos)) { - /* expression result already on stack */ - } else { - /* no expression: return nil */ - local_pos = save_pos; - int ci = bytecode_add_constant(bc, make_nil()); - bytecode_add_instruction(bc, OP_LOAD_CONST, ci); - } - bytecode_add_instruction(bc, OP_RETURN, 0); - *pos = local_pos; - skip_to_eol(src, len, pos); - return; - } - - /* exit statement: exit [expr]? */ - if (strcmp(name, "exit") == 0) { - free(name); - skip_spaces(src, len, &local_pos); - size_t save_pos = local_pos; - if (emit_expression(bc, src, len, &local_pos)) { - /* expression result already on stack */ - } else { - /* default exit code 0 */ - local_pos = save_pos; - int ci = bytecode_add_constant(bc, make_int(0)); - bytecode_add_instruction(bc, OP_LOAD_CONST, ci); - } - bytecode_add_instruction(bc, OP_EXIT, 0); - *pos = local_pos; - skip_to_eol(src, len, pos); - return; - } - - /* break / continue */ - if (strcmp(name, "break") == 0) { - free(name); - if (!g_loop_ctx) { - parser_fail(local_pos, "break used outside of loop"); - return; - } - int j = bytecode_add_instruction(bc, OP_JUMP, 0); - if (g_loop_ctx->break_count < (int)(sizeof(g_loop_ctx->break_jumps) / sizeof(g_loop_ctx->break_jumps[0]))) { - g_loop_ctx->break_jumps[g_loop_ctx->break_count++] = j; - } else { - parser_fail(local_pos, "Too many 'break' in one loop"); - return; - } - *pos = local_pos; - skip_to_eol(src, len, pos); - return; - } - if (strcmp(name, "continue") == 0) { - free(name); - if (!g_loop_ctx) { - parser_fail(local_pos, "continue used outside of loop"); - return; - } - int j = bytecode_add_instruction(bc, OP_JUMP, 0); - if (g_loop_ctx->cont_count < (int)(sizeof(g_loop_ctx->continue_jumps) / sizeof(g_loop_ctx->continue_jumps[0]))) { - g_loop_ctx->continue_jumps[g_loop_ctx->cont_count++] = j; - } else { - parser_fail(local_pos, "Too many 'continue' in one loop"); - return; - } - *pos = local_pos; - skip_to_eol(src, len, pos); - return; - } - - /* typed declarations: - number|string|boolean|nil|Class|byte|uint8|uint16|uint32|uint64|int8|int16|int32|int64 (= expr)? - 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, "float") == 0 - || strcmp(name, "array") == 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) { - 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_class = (strcmp(name, "class") == 0); - int is_float = (strcmp(name, "float") == 0); - int is_array = (strcmp(name, "array") == 0); - int is_byte = (strcmp(name, "byte") == 0); - int is_u8 = (strcmp(name, "uint8") == 0) || is_byte; - int is_u16 = (strcmp(name, "uint16") == 0); - int is_u32 = (strcmp(name, "uint32") == 0); - int is_u64 = (strcmp(name, "uint64") == 0); - 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) || is_number; /* number maps to int64 (signed) */ - 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 (number is signed 64-bit) */ - if (decl_signed) decl_bits = -decl_bits; - - /* declared type metadata: integers use decl_bits; string/boolean/nil/Class/float/array use special markers */ - int decl_meta = decl_bits; - if (is_string) { - decl_meta = TYPE_META_STRING; - } else if (is_boolean) { - decl_meta = TYPE_META_BOOLEAN; - } else if (is_nil) { - decl_meta = TYPE_META_NIL; - } else if (is_class) { - decl_meta = TYPE_META_CLASS; - } else if (is_float) { - decl_meta = TYPE_META_FLOAT; - } else if (is_array) { - decl_meta = TYPE_META_ARRAY; - } - - free(name); - - /* read variable name */ - char *varname = NULL; - skip_spaces(src, len, &local_pos); - if (!read_identifier_into(src, len, &local_pos, &varname)) { - parser_fail(local_pos, "Expected identifier after type declaration"); - return; - } - - /* decide local vs global */ - int lidx = -1; - int gi = -1; - if (g_locals) { - int existing = local_find(varname); - if (existing >= 0) lidx = existing; - else lidx = local_add(varname); - if (lidx >= 0) { - g_locals->types[lidx] = decl_meta; /* encoding: ±bits for integers; TYPE_META_* for non-integer enforced types; 0 = dynamic */ - } - } else { - gi = sym_index(varname); - if (gi >= 0) { - G.types[gi] = decl_meta; - } - } - free(varname); - - skip_spaces(src, len, &local_pos); - if (local_pos < len && src[local_pos] == '=') { - local_pos++; /* '=' */ - if (!emit_expression(bc, src, len, &local_pos)) { - parser_fail(local_pos, "Expected initializer expression after '='"); - return; - } - - /* Enforce declared type on initializer */ - if (decl_meta == TYPE_META_STRING) { - /* expect String */ - bytecode_add_instruction(bc, OP_DUP, 0); - bytecode_add_instruction(bc, OP_TYPEOF, 0); - int ciExp = bytecode_add_constant(bc, make_string("String")); - bytecode_add_instruction(bc, OP_LOAD_CONST, ciExp); - 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); - /* error block */ - bytecode_set_operand(bc, j_to_error, bc->instr_count); - { - int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected String")); - 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_CLASS) { - /* expect Class instance: Map with "__class" key */ - /* Check typeof(v) == "Map" */ - bytecode_add_instruction(bc, OP_DUP, 0); - bytecode_add_instruction(bc, OP_TYPEOF, 0); - { - int ciMap = bytecode_add_constant(bc, make_string("Map")); - bytecode_add_instruction(bc, OP_LOAD_CONST, ciMap); - } - bytecode_add_instruction(bc, OP_EQ, 0); - int j_err1 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); - /* Check v has "__class" */ - bytecode_add_instruction(bc, OP_DUP, 0); - { - int kci = bytecode_add_constant(bc, make_string("__class")); - bytecode_add_instruction(bc, OP_LOAD_CONST, kci); - } - bytecode_add_instruction(bc, OP_HAS_KEY, 0); - int j_err2 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); - /* Success path jumps over error block */ - int j_ok = bytecode_add_instruction(bc, OP_JUMP, 0); - /* Error block */ - int err_lbl = bc->instr_count; - bytecode_set_operand(bc, j_err1, err_lbl); - bytecode_set_operand(bc, j_err2, err_lbl); - { - int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Class")); - bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg); - bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_HALT, 0); - } - /* 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_ARRAY) { - /* expect Array */ - bytecode_add_instruction(bc, OP_DUP, 0); - bytecode_add_instruction(bc, OP_TYPEOF, 0); - int ciArr = bytecode_add_constant(bc, make_string("Array")); - bytecode_add_instruction(bc, OP_LOAD_CONST, ciArr); - 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 Array")); - 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 */ - bytecode_add_instruction(bc, OP_DUP, 0); - bytecode_add_instruction(bc, OP_TYPEOF, 0); - int ciBool = bytecode_add_constant(bc, make_string("Boolean")); - bytecode_add_instruction(bc, OP_LOAD_CONST, ciBool); - bytecode_add_instruction(bc, OP_EQ, 0); - int j_not_bool = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); - /* it is Boolean -> OK, skip number check */ - int j_done = bytecode_add_instruction(bc, OP_JUMP, 0); - /* not Boolean: check Number */ - bytecode_set_operand(bc, j_not_bool, bc->instr_count); - bytecode_add_instruction(bc, OP_DUP, 0); - bytecode_add_instruction(bc, OP_TYPEOF, 0); - int ciNum = bytecode_add_constant(bc, make_string("Number")); - bytecode_add_instruction(bc, OP_LOAD_CONST, ciNum); - 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 Boolean or Number for boolean")); - 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); - /* if Number, clamp to 0/1 */ - bytecode_add_instruction(bc, OP_UCLAMP, 1); - /* common continuation */ - bytecode_set_operand(bc, j_done, bc->instr_count); - } else if (decl_meta == TYPE_META_NIL) { - /* expect Nil */ - bytecode_add_instruction(bc, OP_DUP, 0); - bytecode_add_instruction(bc, OP_TYPEOF, 0); - int ciNil = bytecode_add_constant(bc, make_string("Nil")); - bytecode_add_instruction(bc, OP_LOAD_CONST, ciNil); - 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 Nil")); - 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 { - /* integer widths: expect Number then range-check */ - int abs_bits = decl_bits < 0 ? -decl_bits : decl_bits; - if (abs_bits > 0) { - /* typeof == Number */ - bytecode_add_instruction(bc, OP_DUP, 0); - bytecode_add_instruction(bc, OP_TYPEOF, 0); - int ciNum = bytecode_add_constant(bc, make_string("Number")); - bytecode_add_instruction(bc, OP_LOAD_CONST, ciNum); - 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 Number")); - 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); - - 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); - } else { - bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); - } - } else { - /* default initialize if no '=' given */ - int ci = -1; - if (is_string) { - ci = bytecode_add_constant(bc, make_string("")); - } else if (is_nil) { - ci = bytecode_add_constant(bc, make_nil()); - } else if (is_class) { - /* Class-typed variable defaults to Nil until assigned an instance */ - ci = bytecode_add_constant(bc, make_nil()); - } else if (is_boolean) { - /* booleans default to false */ - ci = bytecode_add_constant(bc, make_bool(0)); - } else if (is_number || (decl_bits != 0)) { - /* integers default to 0 */ - ci = bytecode_add_constant(bc, make_int(0)); - } - if (ci >= 0) { - bytecode_add_instruction(bc, OP_LOAD_CONST, ci); - 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); - } else { - bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); - } - } - } - *pos = local_pos; - skip_to_eol(src, len, pos); - return; - } - - if (strcmp(name, "print") == 0) { - free(name); - skip_spaces(src, len, &local_pos); - (void)consume_char(src, len, &local_pos, '('); - if (emit_expression(bc, src, len, &local_pos)) { - (void)consume_char(src, len, &local_pos, ')'); - bytecode_add_instruction(bc, OP_PRINT, 0); - } else { - (void)consume_char(src, len, &local_pos, ')'); - } - *pos = local_pos; - skip_to_eol(src, len, pos); - return; - } - - if (strcmp(name, "echo") == 0) { - free(name); - skip_spaces(src, len, &local_pos); - (void)consume_char(src, len, &local_pos, '('); - if (emit_expression(bc, src, len, &local_pos)) { - (void)consume_char(src, len, &local_pos, ')'); - bytecode_add_instruction(bc, OP_ECHO, 0); - } else { - (void)consume_char(src, len, &local_pos, ')'); - } - *pos = local_pos; - skip_to_eol(src, len, pos); - return; - } - - /* assignment or simple call */ - int lidx = local_find(name); - int gi = (lidx < 0) ? sym_index(name) : -1; - skip_spaces(src, len, &local_pos); - - /* object field assignment: name.field = expr (only if '=' follows) */ - if (local_pos < len && src[local_pos] == '.') { - size_t stmt_start = *pos; /* for expression fallback */ - size_t look = local_pos + 1; /* point after '.' */ - skip_spaces(src, len, &look); - char *fname = NULL; - if (!read_identifier_into(src, len, &look, &fname)) { - parser_fail(look, "Expected field name after '.'"); - free(name); - return; - } - skip_spaces(src, len, &look); - if (look >= len || src[look] != '=') { - /* Not an assignment: treat as expression statement (e.g., obj.method(...)) */ - free(fname); - free(name); - size_t expr_pos = stmt_start; - if (emit_expression(bc, src, len, &expr_pos)) { - bytecode_add_instruction(bc, OP_POP, 0); - } - *pos = expr_pos; - skip_to_eol(src, len, pos); - return; - } - - /* Confirmed assignment: emit container, key, value, then INDEX_SET */ - /* Load container variable */ - if (lidx >= 0) { - bytecode_add_instruction(bc, OP_LOAD_LOCAL, lidx); - } else { - bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); - } - /* Push key */ - int kci = bytecode_add_constant(bc, make_string(fname)); - free(fname); - bytecode_add_instruction(bc, OP_LOAD_CONST, kci); - - /* Advance local_pos to after '=' and parse value expression */ - local_pos = look + 1; /* skip '=' */ - if (!emit_expression(bc, src, len, &local_pos)) { - parser_fail(local_pos, "Expected expression after '='"); - free(name); - return; - } - /* perform set: pops value, key, container (in that order) */ - bytecode_add_instruction(bc, OP_INDEX_SET, 0); - - free(name); - *pos = local_pos; - skip_to_eol(src, len, pos); - return; - } - - /* array element assignment: name[expr] = expr and nested: name[expr1][expr2] = expr */ - if (local_pos < len && src[local_pos] == '[') { - /* load array/map variable */ - if (lidx >= 0) { - bytecode_add_instruction(bc, OP_LOAD_LOCAL, lidx); - } else { - bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); - } - local_pos++; /* '[' */ - if (!emit_expression(bc, src, len, &local_pos)) { - parser_fail(local_pos, "Expected index expression after '['"); - free(name); - return; - } - if (!consume_char(src, len, &local_pos, ']')) { - parser_fail(local_pos, "Expected ']' after index"); - free(name); - return; - } - skip_spaces(src, len, &local_pos); - - /* Nested index: name[expr1][expr2] = value */ - if (local_pos < len && src[local_pos] == '[') { - /* Reduce base: stack currently has container, index1 -> get inner container */ - bytecode_add_instruction(bc, OP_INDEX_GET, 0); - - local_pos++; /* second '[' */ - if (!emit_expression(bc, src, len, &local_pos)) { - parser_fail(local_pos, "Expected nested index expression after '['"); - free(name); - return; - } - if (!consume_char(src, len, &local_pos, ']')) { - parser_fail(local_pos, "Expected ']' after nested index"); - free(name); - return; - } - skip_spaces(src, len, &local_pos); - if (local_pos >= len || src[local_pos] != '=') { - parser_fail(local_pos, "Expected '=' after nested array index"); - free(name); - return; - } - local_pos++; /* '=' */ - if (!emit_expression(bc, src, len, &local_pos)) { - parser_fail(local_pos, "Expected expression after '='"); - free(name); - return; - } - /* perform set into inner container */ - bytecode_add_instruction(bc, OP_INDEX_SET, 0); - free(name); - *pos = local_pos; - skip_to_eol(src, len, pos); - return; - } - - /* Single-level: name[expr] = value */ - if (local_pos >= len || src[local_pos] != '=') { - parser_fail(local_pos, "Expected '=' after array index"); - free(name); - return; - } - local_pos++; /* '=' */ - if (!emit_expression(bc, src, len, &local_pos)) { - parser_fail(local_pos, "Expected expression after '='"); - free(name); - return; - } - /* perform set */ - bytecode_add_instruction(bc, OP_INDEX_SET, 0); - free(name); - *pos = local_pos; - skip_to_eol(src, len, pos); - return; - } - - free(name); - if (local_pos < len && src[local_pos] == '=') { - local_pos++; /* '=' */ - if (emit_expression(bc, src, len, &local_pos)) { - /* enforce declared type if present (0 = dynamic) */ - int meta = 0; - if (lidx >= 0 && g_locals) { - meta = g_locals->types[lidx]; - } else if (gi >= 0) { - meta = G.types[gi]; - } - - if (meta == TYPE_META_STRING) { - /* expect String */ - bytecode_add_instruction(bc, OP_DUP, 0); - bytecode_add_instruction(bc, OP_TYPEOF, 0); - int ciStr = bytecode_add_constant(bc, make_string("String")); - bytecode_add_instruction(bc, OP_LOAD_CONST, ciStr); - 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 String")); - 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_CLASS) { - /* expect Class instance: Map with "__class" key */ - /* Check typeof(v) == "Map" */ - bytecode_add_instruction(bc, OP_DUP, 0); - bytecode_add_instruction(bc, OP_TYPEOF, 0); - { - int ciMap = bytecode_add_constant(bc, make_string("Map")); - bytecode_add_instruction(bc, OP_LOAD_CONST, ciMap); - } - bytecode_add_instruction(bc, OP_EQ, 0); - int j_err1 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); - /* Check v has "__class" */ - bytecode_add_instruction(bc, OP_DUP, 0); - { - int kci = bytecode_add_constant(bc, make_string("__class")); - bytecode_add_instruction(bc, OP_LOAD_CONST, kci); - } - bytecode_add_instruction(bc, OP_HAS_KEY, 0); - int j_err2 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); - /* Success path jumps over error block */ - int j_ok = bytecode_add_instruction(bc, OP_JUMP, 0); - /* Error block */ - int err_lbl = bc->instr_count; - bytecode_set_operand(bc, j_err1, err_lbl); - bytecode_set_operand(bc, j_err2, err_lbl); - { - int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Class")); - bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg); - bytecode_add_instruction(bc, OP_PRINT, 0); - bytecode_add_instruction(bc, OP_HALT, 0); - } - /* 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_ARRAY) { - /* expect Array */ - bytecode_add_instruction(bc, OP_DUP, 0); - bytecode_add_instruction(bc, OP_TYPEOF, 0); - int ciArr = bytecode_add_constant(bc, make_string("Array")); - bytecode_add_instruction(bc, OP_LOAD_CONST, ciArr); - 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 Array")); - 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); - bytecode_add_instruction(bc, OP_TYPEOF, 0); - int ciNum = bytecode_add_constant(bc, make_string("Number")); - bytecode_add_instruction(bc, OP_LOAD_CONST, ciNum); - 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 Number for boolean")); - 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); - bytecode_add_instruction(bc, OP_UCLAMP, 1); - } else if (meta == TYPE_META_NIL) { - /* expect Nil */ - bytecode_add_instruction(bc, OP_DUP, 0); - bytecode_add_instruction(bc, OP_TYPEOF, 0); - int ciNil = bytecode_add_constant(bc, make_string("Nil")); - bytecode_add_instruction(bc, OP_LOAD_CONST, ciNil); - 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 Nil")); - 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 != 0) { - /* integer widths: expect Number then range-check to declared width */ - int abs_bits = meta < 0 ? -meta : meta; - /* typeof == Number */ - bytecode_add_instruction(bc, OP_DUP, 0); - bytecode_add_instruction(bc, OP_TYPEOF, 0); - int ciNum = bytecode_add_constant(bc, make_string("Number")); - bytecode_add_instruction(bc, OP_LOAD_CONST, ciNum); - 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 Number")); - 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); - - if (abs_bits > 0) { - bytecode_add_instruction(bc, (meta < 0) ? OP_SCLAMP : OP_UCLAMP, abs_bits); - } - } - /* dynamic (meta==0): no enforcement */ - - if (lidx >= 0) { - bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); - } else { - bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); - } - } - *pos = local_pos; - skip_to_eol(src, len, pos); - return; - } else if (local_pos < len && src[local_pos] == '(') { - /* call as a statement: compile full call expression and drop result */ - /* rewind to statement start and emit as expression */ - local_pos = *pos; - if (emit_expression(bc, src, len, &local_pos)) { - bytecode_add_instruction(bc, OP_POP, 0); /* dApache-2.0ard return value */ - } - *pos = local_pos; - skip_to_eol(src, len, pos); - return; - } else { - /* invalid statement: identifier not followed by assignment or call */ - parser_fail(local_pos, "Expected assignment '=' or call '(...)' after identifier"); - return; - } + /* alias: accept 'sint*' as synonyms for 'int*' */ + if (strcmp(name, "sint8") == 0) { + free(name); + name = strdup("int8"); + } else if (strcmp(name, "sint16") == 0) { + free(name); + name = strdup("int16"); + } else if (strcmp(name, "sint32") == 0) { + free(name); + name = strdup("int32"); + } else if (strcmp(name, "sint64") == 0) { + free(name); + name = strdup("int64"); } - /* fallback: print(expr) without identifier read (unlikely) */ - if (starts_with(src, len, *pos, "print")) { - *pos += 5; - skip_spaces(src, len, pos); - (void)consume_char(src, len, pos, '('); - if (emit_expression(bc, src, len, pos)) { - (void)consume_char(src, len, pos, ')'); + /* return statement */ + if (strcmp(name, "return") == 0) { + free(name); + skip_spaces(src, len, &local_pos); + /* optional expression */ + size_t save_pos = local_pos; + if (emit_expression(bc, src, len, &local_pos)) { + /* expression result already on stack */ + } else { + /* no expression: return nil */ + local_pos = save_pos; + int ci = bytecode_add_constant(bc, make_nil()); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci); + } + bytecode_add_instruction(bc, OP_RETURN, 0); + *pos = local_pos; + skip_to_eol(src, len, pos); + return; + } + + /* exit statement: exit [expr]? */ + if (strcmp(name, "exit") == 0) { + free(name); + skip_spaces(src, len, &local_pos); + size_t save_pos = local_pos; + if (emit_expression(bc, src, len, &local_pos)) { + /* expression result already on stack */ + } else { + /* default exit code 0 */ + local_pos = save_pos; + int ci = bytecode_add_constant(bc, make_int(0)); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci); + } + bytecode_add_instruction(bc, OP_EXIT, 0); + *pos = local_pos; + skip_to_eol(src, len, pos); + return; + } + + /* break / continue */ + if (strcmp(name, "break") == 0) { + free(name); + if (!g_loop_ctx) { + parser_fail(local_pos, "break used outside of loop"); + return; + } + int j = bytecode_add_instruction(bc, OP_JUMP, 0); + if (g_loop_ctx->break_count < (int)(sizeof(g_loop_ctx->break_jumps) / sizeof(g_loop_ctx->break_jumps[0]))) { + g_loop_ctx->break_jumps[g_loop_ctx->break_count++] = j; + } else { + parser_fail(local_pos, "Too many 'break' in one loop"); + return; + } + *pos = local_pos; + skip_to_eol(src, len, pos); + return; + } + if (strcmp(name, "continue") == 0) { + free(name); + if (!g_loop_ctx) { + parser_fail(local_pos, "continue used outside of loop"); + return; + } + int j = bytecode_add_instruction(bc, OP_JUMP, 0); + if (g_loop_ctx->cont_count < (int)(sizeof(g_loop_ctx->continue_jumps) / sizeof(g_loop_ctx->continue_jumps[0]))) { + g_loop_ctx->continue_jumps[g_loop_ctx->cont_count++] = j; + } else { + parser_fail(local_pos, "Too many 'continue' in one loop"); + return; + } + *pos = local_pos; + skip_to_eol(src, len, pos); + return; + } + + /* typed declarations: + number|string|boolean|nil|Class|byte|uint8|uint16|uint32|uint64|int8|int16|int32|int64 (= expr)? + 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, "float") == 0 || strcmp(name, "array") == 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) { + 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_class = (strcmp(name, "class") == 0); + int is_float = (strcmp(name, "float") == 0); + int is_array = (strcmp(name, "array") == 0); + int is_byte = (strcmp(name, "byte") == 0); + int is_u8 = (strcmp(name, "uint8") == 0) || is_byte; + int is_u16 = (strcmp(name, "uint16") == 0); + int is_u32 = (strcmp(name, "uint32") == 0); + int is_u64 = (strcmp(name, "uint64") == 0); + 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) || is_number; /* number maps to int64 (signed) */ + 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 (number is signed 64-bit) */ + if (decl_signed) decl_bits = -decl_bits; + + /* declared type metadata: integers use decl_bits; string/boolean/nil/Class/float/array use special markers */ + int decl_meta = decl_bits; + if (is_string) { + decl_meta = TYPE_META_STRING; + } else if (is_boolean) { + decl_meta = TYPE_META_BOOLEAN; + } else if (is_nil) { + decl_meta = TYPE_META_NIL; + } else if (is_class) { + decl_meta = TYPE_META_CLASS; + } else if (is_float) { + decl_meta = TYPE_META_FLOAT; + } else if (is_array) { + decl_meta = TYPE_META_ARRAY; + } + + free(name); + + /* read variable name */ + char *varname = NULL; + skip_spaces(src, len, &local_pos); + if (!read_identifier_into(src, len, &local_pos, &varname)) { + parser_fail(local_pos, "Expected identifier after type declaration"); + return; + } + + /* decide local vs global */ + int lidx = -1; + int gi = -1; + if (g_locals) { + int existing = local_find(varname); + if (existing >= 0) + lidx = existing; + else + lidx = local_add(varname); + if (lidx >= 0) { + g_locals->types[lidx] = decl_meta; /* encoding: ±bits for integers; TYPE_META_* for non-integer enforced types; 0 = dynamic */ + } + } else { + gi = sym_index(varname); + if (gi >= 0) { + G.types[gi] = decl_meta; + } + } + free(varname); + + skip_spaces(src, len, &local_pos); + if (local_pos < len && src[local_pos] == '=') { + local_pos++; /* '=' */ + if (!emit_expression(bc, src, len, &local_pos)) { + parser_fail(local_pos, "Expected initializer expression after '='"); + return; + } + + /* Enforce declared type on initializer */ + if (decl_meta == TYPE_META_STRING) { + /* expect String */ + bytecode_add_instruction(bc, OP_DUP, 0); + bytecode_add_instruction(bc, OP_TYPEOF, 0); + int ciExp = bytecode_add_constant(bc, make_string("String")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciExp); + 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); + /* error block */ + bytecode_set_operand(bc, j_to_error, bc->instr_count); + { + int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected String")); + 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_CLASS) { + /* expect Class instance: Map with "__class" key */ + /* Check typeof(v) == "Map" */ + bytecode_add_instruction(bc, OP_DUP, 0); + bytecode_add_instruction(bc, OP_TYPEOF, 0); + { + int ciMap = bytecode_add_constant(bc, make_string("Map")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciMap); + } + bytecode_add_instruction(bc, OP_EQ, 0); + int j_err1 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + /* Check v has "__class" */ + bytecode_add_instruction(bc, OP_DUP, 0); + { + int kci = bytecode_add_constant(bc, make_string("__class")); + bytecode_add_instruction(bc, OP_LOAD_CONST, kci); + } + bytecode_add_instruction(bc, OP_HAS_KEY, 0); + int j_err2 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + /* Success path jumps over error block */ + int j_ok = bytecode_add_instruction(bc, OP_JUMP, 0); + /* Error block */ + int err_lbl = bc->instr_count; + bytecode_set_operand(bc, j_err1, err_lbl); + bytecode_set_operand(bc, j_err2, err_lbl); + { + int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Class")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_HALT, 0); + } + /* 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_ARRAY) { + /* expect Array */ + bytecode_add_instruction(bc, OP_DUP, 0); + bytecode_add_instruction(bc, OP_TYPEOF, 0); + int ciArr = bytecode_add_constant(bc, make_string("Array")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciArr); + 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 Array")); + 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 */ + bytecode_add_instruction(bc, OP_DUP, 0); + bytecode_add_instruction(bc, OP_TYPEOF, 0); + int ciBool = bytecode_add_constant(bc, make_string("Boolean")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciBool); + bytecode_add_instruction(bc, OP_EQ, 0); + int j_not_bool = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + /* it is Boolean -> OK, skip number check */ + int j_done = bytecode_add_instruction(bc, OP_JUMP, 0); + /* not Boolean: check Number */ + bytecode_set_operand(bc, j_not_bool, bc->instr_count); + bytecode_add_instruction(bc, OP_DUP, 0); + bytecode_add_instruction(bc, OP_TYPEOF, 0); + int ciNum = bytecode_add_constant(bc, make_string("Number")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciNum); + 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 Boolean or Number for boolean")); + 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); + /* if Number, clamp to 0/1 */ + bytecode_add_instruction(bc, OP_UCLAMP, 1); + /* common continuation */ + bytecode_set_operand(bc, j_done, bc->instr_count); + } else if (decl_meta == TYPE_META_NIL) { + /* expect Nil */ + bytecode_add_instruction(bc, OP_DUP, 0); + bytecode_add_instruction(bc, OP_TYPEOF, 0); + int ciNil = bytecode_add_constant(bc, make_string("Nil")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciNil); + 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 Nil")); + 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 { - (void)consume_char(src, len, pos, ')'); + /* integer widths: expect Number then range-check */ + int abs_bits = decl_bits < 0 ? -decl_bits : decl_bits; + if (abs_bits > 0) { + /* typeof == Number */ + bytecode_add_instruction(bc, OP_DUP, 0); + bytecode_add_instruction(bc, OP_TYPEOF, 0); + int ciNum = bytecode_add_constant(bc, make_string("Number")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciNum); + 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 Number")); + 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); + + if (abs_bits > 0) { + bytecode_add_instruction(bc, (decl_bits < 0) ? OP_SCLAMP : OP_UCLAMP, abs_bits); + } + } } - skip_to_eol(src, len, pos); - return; + + if (lidx >= 0) { + bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); + } else { + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); + } + } else { + /* default initialize if no '=' given */ + int ci = -1; + if (is_string) { + ci = bytecode_add_constant(bc, make_string("")); + } else if (is_nil) { + ci = bytecode_add_constant(bc, make_nil()); + } else if (is_class) { + /* Class-typed variable defaults to Nil until assigned an instance */ + ci = bytecode_add_constant(bc, make_nil()); + } else if (is_boolean) { + /* booleans default to false */ + ci = bytecode_add_constant(bc, make_bool(0)); + } else if (is_number || (decl_bits != 0)) { + /* integers default to 0 */ + ci = bytecode_add_constant(bc, make_int(0)); + } + if (ci >= 0) { + bytecode_add_instruction(bc, OP_LOAD_CONST, ci); + 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); + } else { + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); + } + } + } + *pos = local_pos; + skip_to_eol(src, len, pos); + return; } - /* echo(expr): like print but does not add a newline (immediate output) */ - if (starts_with(src, len, *pos, "echo")) { - *pos += 4; - skip_spaces(src, len, pos); - (void)consume_char(src, len, pos, '('); - if (emit_expression(bc, src, len, pos)) { - (void)consume_char(src, len, pos, ')'); - bytecode_add_instruction(bc, OP_ECHO, 0); - } else { - (void)consume_char(src, len, pos, ')'); - } - skip_to_eol(src, len, pos); - return; + if (strcmp(name, "print") == 0) { + free(name); + skip_spaces(src, len, &local_pos); + (void)consume_char(src, len, &local_pos, '('); + if (emit_expression(bc, src, len, &local_pos)) { + (void)consume_char(src, len, &local_pos, ')'); + bytecode_add_instruction(bc, OP_PRINT, 0); + } else { + (void)consume_char(src, len, &local_pos, ')'); + } + *pos = local_pos; + skip_to_eol(src, len, pos); + return; } - /* unknown token: report error */ - parser_fail(*pos, "Unknown token at start of statement"); + if (strcmp(name, "echo") == 0) { + free(name); + skip_spaces(src, len, &local_pos); + (void)consume_char(src, len, &local_pos, '('); + if (emit_expression(bc, src, len, &local_pos)) { + (void)consume_char(src, len, &local_pos, ')'); + bytecode_add_instruction(bc, OP_ECHO, 0); + } else { + (void)consume_char(src, len, &local_pos, ')'); + } + *pos = local_pos; + skip_to_eol(src, len, pos); + return; + } + + /* assignment or simple call */ + int lidx = local_find(name); + int gi = (lidx < 0) ? sym_index(name) : -1; + skip_spaces(src, len, &local_pos); + + /* object field assignment: name.field = expr (only if '=' follows) */ + if (local_pos < len && src[local_pos] == '.') { + size_t stmt_start = *pos; /* for expression fallback */ + size_t look = local_pos + 1; /* point after '.' */ + skip_spaces(src, len, &look); + char *fname = NULL; + if (!read_identifier_into(src, len, &look, &fname)) { + parser_fail(look, "Expected field name after '.'"); + free(name); + return; + } + skip_spaces(src, len, &look); + if (look >= len || src[look] != '=') { + /* Not an assignment: treat as expression statement (e.g., obj.method(...)) */ + free(fname); + free(name); + size_t expr_pos = stmt_start; + if (emit_expression(bc, src, len, &expr_pos)) { + bytecode_add_instruction(bc, OP_POP, 0); + } + *pos = expr_pos; + skip_to_eol(src, len, pos); + return; + } + + /* Confirmed assignment: emit container, key, value, then INDEX_SET */ + /* Load container variable */ + if (lidx >= 0) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, lidx); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); + } + /* Push key */ + int kci = bytecode_add_constant(bc, make_string(fname)); + free(fname); + bytecode_add_instruction(bc, OP_LOAD_CONST, kci); + + /* Advance local_pos to after '=' and parse value expression */ + local_pos = look + 1; /* skip '=' */ + if (!emit_expression(bc, src, len, &local_pos)) { + parser_fail(local_pos, "Expected expression after '='"); + free(name); + return; + } + /* perform set: pops value, key, container (in that order) */ + bytecode_add_instruction(bc, OP_INDEX_SET, 0); + + free(name); + *pos = local_pos; + skip_to_eol(src, len, pos); + return; + } + + /* array element assignment: name[expr] = expr and nested: name[expr1][expr2] = expr */ + if (local_pos < len && src[local_pos] == '[') { + /* load array/map variable */ + if (lidx >= 0) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, lidx); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); + } + local_pos++; /* '[' */ + if (!emit_expression(bc, src, len, &local_pos)) { + parser_fail(local_pos, "Expected index expression after '['"); + free(name); + return; + } + if (!consume_char(src, len, &local_pos, ']')) { + parser_fail(local_pos, "Expected ']' after index"); + free(name); + return; + } + skip_spaces(src, len, &local_pos); + + /* Nested index: name[expr1][expr2] = value */ + if (local_pos < len && src[local_pos] == '[') { + /* Reduce base: stack currently has container, index1 -> get inner container */ + bytecode_add_instruction(bc, OP_INDEX_GET, 0); + + local_pos++; /* second '[' */ + if (!emit_expression(bc, src, len, &local_pos)) { + parser_fail(local_pos, "Expected nested index expression after '['"); + free(name); + return; + } + if (!consume_char(src, len, &local_pos, ']')) { + parser_fail(local_pos, "Expected ']' after nested index"); + free(name); + return; + } + skip_spaces(src, len, &local_pos); + if (local_pos >= len || src[local_pos] != '=') { + parser_fail(local_pos, "Expected '=' after nested array index"); + free(name); + return; + } + local_pos++; /* '=' */ + if (!emit_expression(bc, src, len, &local_pos)) { + parser_fail(local_pos, "Expected expression after '='"); + free(name); + return; + } + /* perform set into inner container */ + bytecode_add_instruction(bc, OP_INDEX_SET, 0); + free(name); + *pos = local_pos; + skip_to_eol(src, len, pos); + return; + } + + /* Single-level: name[expr] = value */ + if (local_pos >= len || src[local_pos] != '=') { + parser_fail(local_pos, "Expected '=' after array index"); + free(name); + return; + } + local_pos++; /* '=' */ + if (!emit_expression(bc, src, len, &local_pos)) { + parser_fail(local_pos, "Expected expression after '='"); + free(name); + return; + } + /* perform set */ + bytecode_add_instruction(bc, OP_INDEX_SET, 0); + free(name); + *pos = local_pos; + skip_to_eol(src, len, pos); + return; + } + + free(name); + if (local_pos < len && src[local_pos] == '=') { + local_pos++; /* '=' */ + if (emit_expression(bc, src, len, &local_pos)) { + /* enforce declared type if present (0 = dynamic) */ + int meta = 0; + if (lidx >= 0 && g_locals) { + meta = g_locals->types[lidx]; + } else if (gi >= 0) { + meta = G.types[gi]; + } + + if (meta == TYPE_META_STRING) { + /* expect String */ + bytecode_add_instruction(bc, OP_DUP, 0); + bytecode_add_instruction(bc, OP_TYPEOF, 0); + int ciStr = bytecode_add_constant(bc, make_string("String")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciStr); + 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 String")); + 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_CLASS) { + /* expect Class instance: Map with "__class" key */ + /* Check typeof(v) == "Map" */ + bytecode_add_instruction(bc, OP_DUP, 0); + bytecode_add_instruction(bc, OP_TYPEOF, 0); + { + int ciMap = bytecode_add_constant(bc, make_string("Map")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciMap); + } + bytecode_add_instruction(bc, OP_EQ, 0); + int j_err1 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + /* Check v has "__class" */ + bytecode_add_instruction(bc, OP_DUP, 0); + { + int kci = bytecode_add_constant(bc, make_string("__class")); + bytecode_add_instruction(bc, OP_LOAD_CONST, kci); + } + bytecode_add_instruction(bc, OP_HAS_KEY, 0); + int j_err2 = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + /* Success path jumps over error block */ + int j_ok = bytecode_add_instruction(bc, OP_JUMP, 0); + /* Error block */ + int err_lbl = bc->instr_count; + bytecode_set_operand(bc, j_err1, err_lbl); + bytecode_set_operand(bc, j_err2, err_lbl); + { + int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Class")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_HALT, 0); + } + /* 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_ARRAY) { + /* expect Array */ + bytecode_add_instruction(bc, OP_DUP, 0); + bytecode_add_instruction(bc, OP_TYPEOF, 0); + int ciArr = bytecode_add_constant(bc, make_string("Array")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciArr); + 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 Array")); + 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); + bytecode_add_instruction(bc, OP_TYPEOF, 0); + int ciNum = bytecode_add_constant(bc, make_string("Number")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciNum); + 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 Number for boolean")); + 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); + bytecode_add_instruction(bc, OP_UCLAMP, 1); + } else if (meta == TYPE_META_NIL) { + /* expect Nil */ + bytecode_add_instruction(bc, OP_DUP, 0); + bytecode_add_instruction(bc, OP_TYPEOF, 0); + int ciNil = bytecode_add_constant(bc, make_string("Nil")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciNil); + 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 Nil")); + 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 != 0) { + /* integer widths: expect Number then range-check to declared width */ + int abs_bits = meta < 0 ? -meta : meta; + /* typeof == Number */ + bytecode_add_instruction(bc, OP_DUP, 0); + bytecode_add_instruction(bc, OP_TYPEOF, 0); + int ciNum = bytecode_add_constant(bc, make_string("Number")); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciNum); + 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 Number")); + 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); + + if (abs_bits > 0) { + bytecode_add_instruction(bc, (meta < 0) ? OP_SCLAMP : OP_UCLAMP, abs_bits); + } + } + /* dynamic (meta==0): no enforcement */ + + if (lidx >= 0) { + bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); + } else { + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); + } + } + *pos = local_pos; + skip_to_eol(src, len, pos); + return; + } else if (local_pos < len && src[local_pos] == '(') { + /* call as a statement: compile full call expression and drop result */ + /* rewind to statement start and emit as expression */ + local_pos = *pos; + if (emit_expression(bc, src, len, &local_pos)) { + bytecode_add_instruction(bc, OP_POP, 0); /* dApache-2.0ard return value */ + } + *pos = local_pos; + skip_to_eol(src, len, pos); + return; + } else { + /* invalid statement: identifier not followed by assignment or call */ + parser_fail(local_pos, "Expected assignment '=' or call '(...)' after identifier"); + return; + } + } + + /* fallback: print(expr) without identifier read (unlikely) */ + if (starts_with(src, len, *pos, "print")) { + *pos += 5; + skip_spaces(src, len, pos); + (void)consume_char(src, len, pos, '('); + if (emit_expression(bc, src, len, pos)) { + (void)consume_char(src, len, pos, ')'); + bytecode_add_instruction(bc, OP_PRINT, 0); + } else { + (void)consume_char(src, len, pos, ')'); + } + skip_to_eol(src, len, pos); + return; + } + + /* echo(expr): like print but does not add a newline (immediate output) */ + if (starts_with(src, len, *pos, "echo")) { + *pos += 4; + skip_spaces(src, len, pos); + (void)consume_char(src, len, pos, '('); + if (emit_expression(bc, src, len, pos)) { + (void)consume_char(src, len, pos, ')'); + bytecode_add_instruction(bc, OP_ECHO, 0); + } else { + (void)consume_char(src, len, pos, ')'); + } + skip_to_eol(src, len, pos); + return; + } + + /* unknown token: report error */ + parser_fail(*pos, "Unknown token at start of statement"); } /* parse a block with lines at indentation >= current_indent; stop at dedent */ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, int current_indent) { - while (*pos < len) { - if (g_has_error) return; - size_t line_start = *pos; - int indent = 0; - if (!read_line_start(src, len, pos, &indent)) { - /* EOF or error */ - return; - } - if (indent < current_indent) { - /* dedent -> let caller handle this line */ - *pos = line_start; - return; - } - if (indent > current_indent) { - /* nested block without a header (tolerate by parsing it and continuing) */ - parse_block(bc, src, len, pos, indent); - continue; - } + while (*pos < len) { + if (g_has_error) return; + size_t line_start = *pos; + int indent = 0; + if (!read_line_start(src, len, pos, &indent)) { + /* EOF or error */ + return; + } + if (indent < current_indent) { + /* dedent -> let caller handle this line */ + *pos = line_start; + return; + } + if (indent > current_indent) { + /* nested block without a header (tolerate by parsing it and continuing) */ + parse_block(bc, src, len, pos, indent); + continue; + } - /* at same indent -> parse statement */ - /* Insert a line marker for better runtime error reporting */ + /* at same indent -> parse statement */ + /* Insert a line marker for better runtime error reporting */ + { + int stmt_line = 1, stmt_col = 1; + calc_line_col(src, len, line_start, &stmt_line, &stmt_col); + bytecode_add_instruction(bc, OP_LINE, stmt_line); + } + + /* class definition -> factory function */ + if (starts_with(src, len, *pos, "class")) { + *pos += 5; + skip_spaces(src, len, pos); + /* class name */ + char *cname = NULL; + if (!read_identifier_into(src, len, pos, &cname)) { + parser_fail(*pos, "Expected class name after 'class'"); + return; + } + int cgi = sym_index(cname); + + /* optional extends Parent */ + char *parent_name = NULL; + + /* Optional typed parameter list: class Name(type ident, ...) */ + char *param_names[64]; + int param_kind[64]; + int pcount = 0; + memset(param_names, 0, sizeof(param_names)); + memset(param_kind, 0, sizeof(param_kind)); + +/* kind: 1=Number (numeric types incl. boolean), 2=String, 3=Nil */ +/* helper macro instead of nested function (C99 compliant) */ +#define MAP_TYPE_KIND(t) ( \ + ((t) && strcmp((t), "string") == 0) ? 2 : ((t) && strcmp((t), "nil") == 0) ? 3 \ + : ((t) && (strcmp((t), "boolean") == 0 || strcmp((t), "number") == 0 || strcmp((t), "byte") == 0 || strncmp((t), "uint", 4) == 0 || strncmp((t), "sint", 4) == 0 || strncmp((t), "int", 3) == 0)) ? 1 \ + : 0) + + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] == '(') { + (*pos)++; + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] != ')') { + for (;;) { + /* read type token */ + char *tname = NULL; + if (!read_identifier_into(src, len, pos, &tname)) { + parser_fail(*pos, "Expected type in class parameter list"); + free(cname); + return; + } + /* read param name */ + skip_spaces(src, len, pos); + char *pname = NULL; + if (!read_identifier_into(src, len, pos, &pname)) { + parser_fail(*pos, "Expected parameter name after type"); + free(tname); + free(cname); + return; + } + if (pcount >= (int)(sizeof(param_names) / sizeof(param_names[0]))) { + parser_fail(*pos, "Too many class parameters"); + free(tname); + free(pname); + free(cname); + return; + } + param_names[pcount] = pname; + param_kind[pcount] = MAP_TYPE_KIND(tname); + free(tname); + pcount++; + + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + continue; + } + break; + } + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after class parameter list"); + for (int i = 0; i < pcount; ++i) + free(param_names[i]); + free(cname); + return; + } + } + + /* optional 'extends Parent' after parameter list */ + skip_spaces(src, len, pos); + if (starts_with(src, len, *pos, "extends")) { + *pos += 7; /* consume 'extends' */ + skip_spaces(src, len, pos); + if (!read_identifier_into(src, len, pos, &parent_name)) { + parser_fail(*pos, "Expected parent class name after 'extends'"); + for (int i = 0; i < pcount; ++i) + free(param_names[i]); + free(cname); + return; + } + } + + /* end of class header line */ + skip_to_eol(src, len, pos); + + /* Build factory function: Name(...) -> instance map with fields and methods */ + Bytecode *ctor_bc = bytecode_new(); + /* set debug metadata for class factory */ + if (ctor_bc) { + if (ctor_bc->name) free((void *)ctor_bc->name); + ctor_bc->name = strdup(cname); + if (ctor_bc->source_file) free((void *)ctor_bc->source_file); + if (g_current_source_path) ctor_bc->source_file = strdup(g_current_source_path); + } + /* local env for the factory to allow temp locals */ + LocalEnv ctor_env; + memset(&ctor_env, 0, sizeof(ctor_env)); + LocalEnv *prev_env = g_locals; + g_locals = &ctor_env; + + /* track if _construct is defined in this class */ + int ctor_present = 0; + + /* Register parameter locals first so args land at 0..pcount-1 */ + for (int i = 0; i < pcount; ++i) { + local_add(param_names[i]); + } + + /* Guard local to detect extra argument at index == pcount */ + int l_extra = local_add("__extra"); + + /* Runtime checks: missing args and type checks */ + for (int i = 0; i < pcount; ++i) { + /* missing arg: local i must not be Nil */ + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, i); + bytecode_add_instruction(ctor_bc, OP_TYPEOF, 0); + int ci_nil = bytecode_add_constant(ctor_bc, make_string("Nil")); + bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, ci_nil); + bytecode_add_instruction(ctor_bc, OP_EQ, 0); + int j_ok_present = bytecode_add_instruction(ctor_bc, OP_JUMP_IF_FALSE, 0); + /* then -> error */ { - int stmt_line = 1, stmt_col = 1; - calc_line_col(src, len, line_start, &stmt_line, &stmt_col); - bytecode_add_instruction(bc, OP_LINE, stmt_line); + char msg[128]; + snprintf(msg, sizeof(msg), "TypeError: missing argument '%s' in %s()", param_names[i], cname); + int ci_msg = bytecode_add_constant(ctor_bc, make_string(msg)); + bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, ci_msg); + bytecode_add_instruction(ctor_bc, OP_PRINT, 0); + bytecode_add_instruction(ctor_bc, OP_HALT, 0); } + /* patch continue if present */ + bytecode_set_operand(ctor_bc, j_ok_present, ctor_bc->instr_count); - /* class definition -> factory function */ - if (starts_with(src, len, *pos, "class")) { - *pos += 5; - skip_spaces(src, len, pos); - /* class name */ - char *cname = NULL; - if (!read_identifier_into(src, len, pos, &cname)) { - parser_fail(*pos, "Expected class name after 'class'"); - return; - } - int cgi = sym_index(cname); + /* type check for known kinds */ + int kind = param_kind[i]; + if (kind == 1 || kind == 2 || kind == 3) { + /* typeof(local i) == Expected ? skip error : go to error */ + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, i); + bytecode_add_instruction(ctor_bc, OP_TYPEOF, 0); + const char *exp = (kind == 1) ? "Number" : (kind == 2) ? "String" + : "Nil"; + int ci_exp = bytecode_add_constant(ctor_bc, make_string(exp)); + bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, ci_exp); + bytecode_add_instruction(ctor_bc, OP_EQ, 0); + /* if false -> jump to error */ + int j_to_error = bytecode_add_instruction(ctor_bc, OP_JUMP_IF_FALSE, 0); + /* on success, skip error block */ + int j_skip_err = bytecode_add_instruction(ctor_bc, OP_JUMP, 0); + /* error block */ + { + int err_label = ctor_bc->instr_count; + bytecode_set_operand(ctor_bc, j_to_error, err_label); + char msg2[160]; + snprintf(msg2, sizeof(msg2), "TypeError: %s() expects %s for '%s'", cname, exp, param_names[i]); + int ci_msg2 = bytecode_add_constant(ctor_bc, make_string(msg2)); + bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, ci_msg2); + bytecode_add_instruction(ctor_bc, OP_PRINT, 0); + bytecode_add_instruction(ctor_bc, OP_HALT, 0); + } + /* continue label after error block */ + bytecode_set_operand(ctor_bc, j_skip_err, ctor_bc->instr_count); + } + } - /* optional extends Parent */ - char *parent_name = NULL; + /* Extra args check: guard local must be Nil; if not Nil -> error */ + { + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_extra); + bytecode_add_instruction(ctor_bc, OP_TYPEOF, 0); + int ci_nil2 = bytecode_add_constant(ctor_bc, make_string("Nil")); + bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, ci_nil2); + bytecode_add_instruction(ctor_bc, OP_EQ, 0); + /* if false (not Nil) -> jump to error */ + int j_to_error = bytecode_add_instruction(ctor_bc, OP_JUMP_IF_FALSE, 0); + /* if true (Nil) -> skip error */ + int j_skip_err = bytecode_add_instruction(ctor_bc, OP_JUMP, 0); + { + int err_label = ctor_bc->instr_count; + bytecode_set_operand(ctor_bc, j_to_error, err_label); + char msg3[128]; + snprintf(msg3, sizeof(msg3), "TypeError: %s() received too many arguments", cname); + int ci_msg3 = bytecode_add_constant(ctor_bc, make_string(msg3)); + bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, ci_msg3); + bytecode_add_instruction(ctor_bc, OP_PRINT, 0); + bytecode_add_instruction(ctor_bc, OP_HALT, 0); + } + bytecode_set_operand(ctor_bc, j_skip_err, ctor_bc->instr_count); + } - /* Optional typed parameter list: class Name(type ident, ...) */ - char *param_names[64]; int param_kind[64]; int pcount = 0; - memset(param_names, 0, sizeof(param_names)); - memset(param_kind, 0, sizeof(param_kind)); + /* instance map: __this = {} (placed after param guard) */ + int l_this = local_add("__this"); + bytecode_add_instruction(ctor_bc, OP_MAKE_MAP, 0); + bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_this); - /* kind: 1=Number (numeric types incl. boolean), 2=String, 3=Nil */ - /* helper macro instead of nested function (C99 compliant) */ - #define MAP_TYPE_KIND(t) ( \ - ((t) && strcmp((t), "string")==0) ? 2 : \ - ((t) && strcmp((t), "nil")==0) ? 3 : \ - ((t) && (strcmp((t), "boolean")==0 || strcmp((t), "number")==0 || strcmp((t), "byte")==0 || strncmp((t), "uint", 4)==0 || strncmp((t), "sint", 4)==0 || strncmp((t), "int", 3)==0)) ? 1 : \ - 0 ) + /* tag instance with its class name: this["__class"] = "" */ + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this); + { + int kci_cls = bytecode_add_constant(ctor_bc, make_string("__class")); + bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, kci_cls); + int vci_cls = bytecode_add_constant(ctor_bc, make_string(cname)); + bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, vci_cls); + } + bytecode_add_instruction(ctor_bc, OP_INDEX_SET, 0); - skip_spaces(src, len, pos); - if (*pos < len && src[*pos] == '(') { - (*pos)++; - skip_spaces(src, len, pos); - if (*pos < len && src[*pos] != ')') { - for (;;) { - /* read type token */ - char *tname = NULL; - if (!read_identifier_into(src, len, pos, &tname)) { - parser_fail(*pos, "Expected type in class parameter list"); - free(cname); - return; - } - /* read param name */ - skip_spaces(src, len, pos); - char *pname = NULL; - if (!read_identifier_into(src, len, pos, &pname)) { - parser_fail(*pos, "Expected parameter name after type"); - free(tname); - free(cname); - return; - } - if (pcount >= (int)(sizeof(param_names)/sizeof(param_names[0]))) { - parser_fail(*pos, "Too many class parameters"); - free(tname); free(pname); free(cname); - return; - } - param_names[pcount] = pname; - param_kind[pcount] = MAP_TYPE_KIND(tname); - free(tname); - pcount++; + /* Inheritance: if extends Parent, create Parent(header args...) and merge its keys into this */ + if (parent_name) { + /* parent_inst = Parent(args...) */ + int parent_gi = sym_index(parent_name); + bytecode_add_instruction(ctor_bc, OP_LOAD_GLOBAL, parent_gi); + for (int i = 0; i < pcount; ++i) { + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, i); + } + bytecode_add_instruction(ctor_bc, OP_CALL, pcount); + int l_parent = local_add("__parent_inst"); + bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_parent); - skip_spaces(src, len, pos); - if (*pos < len && src[*pos] == ',') { - (*pos)++; - skip_spaces(src, len, pos); - continue; - } - break; - } - } - if (!consume_char(src, len, pos, ')')) { - parser_fail(*pos, "Expected ')' after class parameter list"); - for (int i = 0; i < pcount; ++i) free(param_names[i]); - free(cname); - return; - } - } + /* keys = keys(parent_inst) */ + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_parent); + bytecode_add_instruction(ctor_bc, OP_KEYS, 0); + int l_keys = local_add("__parent_keys"); + bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_keys); - /* optional 'extends Parent' after parameter list */ - skip_spaces(src, len, pos); - if (starts_with(src, len, *pos, "extends")) { - *pos += 7; /* consume 'extends' */ - skip_spaces(src, len, pos); - if (!read_identifier_into(src, len, pos, &parent_name)) { - parser_fail(*pos, "Expected parent class name after 'extends'"); - for (int i = 0; i < pcount; ++i) free(param_names[i]); - free(cname); - return; - } - } + /* i = 0 */ + int c0_inh = bytecode_add_constant(ctor_bc, make_int(0)); + bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, c0_inh); + int l_i = local_add("__inh_i"); + bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_i); - /* end of class header line */ - skip_to_eol(src, len, pos); + /* loop: while (i < len(keys)) */ + int loop_start = ctor_bc->instr_count; + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_i); + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_keys); + bytecode_add_instruction(ctor_bc, OP_LEN, 0); + bytecode_add_instruction(ctor_bc, OP_LT, 0); + int jmp_false = bytecode_add_instruction(ctor_bc, OP_JUMP_IF_FALSE, 0); - /* Build factory function: Name(...) -> instance map with fields and methods */ - Bytecode *ctor_bc = bytecode_new(); - /* set debug metadata for class factory */ - if (ctor_bc) { - if (ctor_bc->name) free((void*)ctor_bc->name); - ctor_bc->name = strdup(cname); - if (ctor_bc->source_file) free((void*)ctor_bc->source_file); - if (g_current_source_path) ctor_bc->source_file = strdup(g_current_source_path); - } - /* local env for the factory to allow temp locals */ - LocalEnv ctor_env; - memset(&ctor_env, 0, sizeof(ctor_env)); - LocalEnv *prev_env = g_locals; - g_locals = &ctor_env; + /* key = keys[i] */ + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_keys); + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_i); + bytecode_add_instruction(ctor_bc, OP_INDEX_GET, 0); + int l_k = local_add("__inh_k"); + bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_k); - /* track if _construct is defined in this class */ - int ctor_present = 0; + /* if this has key -> skip set */ + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this); + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_k); + bytecode_add_instruction(ctor_bc, OP_HAS_KEY, 0); + int j_skip_set = bytecode_add_instruction(ctor_bc, OP_JUMP_IF_FALSE, 0); + /* has key -> nothing to do, jump over set sequence */ + int j_after_maybe_set = bytecode_add_instruction(ctor_bc, OP_JUMP, 0); - /* Register parameter locals first so args land at 0..pcount-1 */ - for (int i = 0; i < pcount; ++i) { - local_add(param_names[i]); - } + /* not has key: set this[key] = parent_inst[key] */ + bytecode_set_operand(ctor_bc, j_skip_set, ctor_bc->instr_count); + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this); + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_k); + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_parent); + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_k); + bytecode_add_instruction(ctor_bc, OP_INDEX_GET, 0); + bytecode_add_instruction(ctor_bc, OP_INDEX_SET, 0); - /* Guard local to detect extra argument at index == pcount */ - int l_extra = local_add("__extra"); + /* continue after maybe-set */ + bytecode_set_operand(ctor_bc, j_after_maybe_set, ctor_bc->instr_count); - /* Runtime checks: missing args and type checks */ - for (int i = 0; i < pcount; ++i) { - /* missing arg: local i must not be Nil */ - bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, i); - bytecode_add_instruction(ctor_bc, OP_TYPEOF, 0); - int ci_nil = bytecode_add_constant(ctor_bc, make_string("Nil")); - bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, ci_nil); - bytecode_add_instruction(ctor_bc, OP_EQ, 0); - int j_ok_present = bytecode_add_instruction(ctor_bc, OP_JUMP_IF_FALSE, 0); - /* then -> error */ - { - char msg[128]; - snprintf(msg, sizeof(msg), "TypeError: missing argument '%s' in %s()", param_names[i], cname); - int ci_msg = bytecode_add_constant(ctor_bc, make_string(msg)); - bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, ci_msg); - bytecode_add_instruction(ctor_bc, OP_PRINT, 0); - bytecode_add_instruction(ctor_bc, OP_HALT, 0); - } - /* patch continue if present */ - bytecode_set_operand(ctor_bc, j_ok_present, ctor_bc->instr_count); + /* i++ */ + int c1_inh = bytecode_add_constant(ctor_bc, make_int(1)); + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_i); + bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, c1_inh); + bytecode_add_instruction(ctor_bc, OP_ADD, 0); + bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_i); - /* type check for known kinds */ - int kind = param_kind[i]; - if (kind == 1 || kind == 2 || kind == 3) { - /* typeof(local i) == Expected ? skip error : go to error */ - bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, i); - bytecode_add_instruction(ctor_bc, OP_TYPEOF, 0); - const char *exp = (kind == 1) ? "Number" : (kind == 2) ? "String" : "Nil"; - int ci_exp = bytecode_add_constant(ctor_bc, make_string(exp)); - bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, ci_exp); - bytecode_add_instruction(ctor_bc, OP_EQ, 0); - /* if false -> jump to error */ - int j_to_error = bytecode_add_instruction(ctor_bc, OP_JUMP_IF_FALSE, 0); - /* on success, skip error block */ - int j_skip_err = bytecode_add_instruction(ctor_bc, OP_JUMP, 0); - /* error block */ - { - int err_label = ctor_bc->instr_count; - bytecode_set_operand(ctor_bc, j_to_error, err_label); - char msg2[160]; - snprintf(msg2, sizeof(msg2), "TypeError: %s() expects %s for '%s'", cname, exp, param_names[i]); - int ci_msg2 = bytecode_add_constant(ctor_bc, make_string(msg2)); - bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, ci_msg2); - bytecode_add_instruction(ctor_bc, OP_PRINT, 0); - bytecode_add_instruction(ctor_bc, OP_HALT, 0); - } - /* continue label after error block */ - bytecode_set_operand(ctor_bc, j_skip_err, ctor_bc->instr_count); - } - } + /* back to loop */ + bytecode_add_instruction(ctor_bc, OP_JUMP, loop_start); + /* end loop */ + bytecode_set_operand(ctor_bc, jmp_false, ctor_bc->instr_count); + } - /* Extra args check: guard local must be Nil; if not Nil -> error */ - { - bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_extra); - bytecode_add_instruction(ctor_bc, OP_TYPEOF, 0); - int ci_nil2 = bytecode_add_constant(ctor_bc, make_string("Nil")); - bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, ci_nil2); - bytecode_add_instruction(ctor_bc, OP_EQ, 0); - /* if false (not Nil) -> jump to error */ - int j_to_error = bytecode_add_instruction(ctor_bc, OP_JUMP_IF_FALSE, 0); - /* if true (Nil) -> skip error */ - int j_skip_err = bytecode_add_instruction(ctor_bc, OP_JUMP, 0); - { - int err_label = ctor_bc->instr_count; - bytecode_set_operand(ctor_bc, j_to_error, err_label); - char msg3[128]; - snprintf(msg3, sizeof(msg3), "TypeError: %s() received too many arguments", cname); - int ci_msg3 = bytecode_add_constant(ctor_bc, make_string(msg3)); - bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, ci_msg3); - bytecode_add_instruction(ctor_bc, OP_PRINT, 0); - bytecode_add_instruction(ctor_bc, OP_HALT, 0); - } - bytecode_set_operand(ctor_bc, j_skip_err, ctor_bc->instr_count); - } - - /* instance map: __this = {} (placed after param guard) */ - int l_this = local_add("__this"); - bytecode_add_instruction(ctor_bc, OP_MAKE_MAP, 0); - bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_this); - - /* tag instance with its class name: this["__class"] = "" */ - bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this); - { - int kci_cls = bytecode_add_constant(ctor_bc, make_string("__class")); - bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, kci_cls); - int vci_cls = bytecode_add_constant(ctor_bc, make_string(cname)); - bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, vci_cls); - } - bytecode_add_instruction(ctor_bc, OP_INDEX_SET, 0); - - /* Inheritance: if extends Parent, create Parent(header args...) and merge its keys into this */ - if (parent_name) { - /* parent_inst = Parent(args...) */ - int parent_gi = sym_index(parent_name); - bytecode_add_instruction(ctor_bc, OP_LOAD_GLOBAL, parent_gi); - for (int i = 0; i < pcount; ++i) { - bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, i); - } - bytecode_add_instruction(ctor_bc, OP_CALL, pcount); - int l_parent = local_add("__parent_inst"); - bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_parent); - - /* keys = keys(parent_inst) */ - bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_parent); - bytecode_add_instruction(ctor_bc, OP_KEYS, 0); - int l_keys = local_add("__parent_keys"); - bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_keys); - - /* i = 0 */ - int c0_inh = bytecode_add_constant(ctor_bc, make_int(0)); - bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, c0_inh); - int l_i = local_add("__inh_i"); - bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_i); - - /* loop: while (i < len(keys)) */ - int loop_start = ctor_bc->instr_count; - bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_i); - bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_keys); - bytecode_add_instruction(ctor_bc, OP_LEN, 0); - bytecode_add_instruction(ctor_bc, OP_LT, 0); - int jmp_false = bytecode_add_instruction(ctor_bc, OP_JUMP_IF_FALSE, 0); - - /* key = keys[i] */ - bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_keys); - bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_i); - bytecode_add_instruction(ctor_bc, OP_INDEX_GET, 0); - int l_k = local_add("__inh_k"); - bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_k); - - /* if this has key -> skip set */ - bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this); - bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_k); - bytecode_add_instruction(ctor_bc, OP_HAS_KEY, 0); - int j_skip_set = bytecode_add_instruction(ctor_bc, OP_JUMP_IF_FALSE, 0); - /* has key -> nothing to do, jump over set sequence */ - int j_after_maybe_set = bytecode_add_instruction(ctor_bc, OP_JUMP, 0); - - /* not has key: set this[key] = parent_inst[key] */ - bytecode_set_operand(ctor_bc, j_skip_set, ctor_bc->instr_count); - bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this); - bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_k); - bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_parent); - bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_k); - bytecode_add_instruction(ctor_bc, OP_INDEX_GET, 0); - bytecode_add_instruction(ctor_bc, OP_INDEX_SET, 0); - - /* continue after maybe-set */ - bytecode_set_operand(ctor_bc, j_after_maybe_set, ctor_bc->instr_count); - - /* i++ */ - int c1_inh = bytecode_add_constant(ctor_bc, make_int(1)); - bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_i); - bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, c1_inh); - bytecode_add_instruction(ctor_bc, OP_ADD, 0); - bytecode_add_instruction(ctor_bc, OP_STORE_LOCAL, l_i); - - /* back to loop */ - bytecode_add_instruction(ctor_bc, OP_JUMP, loop_start); - /* end loop */ - bytecode_set_operand(ctor_bc, jmp_false, ctor_bc->instr_count); - } - - /* Parse class body at increased indent */ - int body_indent = 0; - size_t look_body = *pos; - if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) { - /* iterate over class members at body_indent */ - for (;;) { - size_t member_line_start = *pos; - int member_indent = 0; - if (!read_line_start(src, len, pos, &member_indent)) { - /* EOF */ - break; - } - if (member_indent < body_indent) { - /* end of class body */ - *pos = member_line_start; - break; - } - if (member_indent > body_indent) { - /* skip nested blocks that are part of previous method parsing */ - parse_block(ctor_bc, src, len, pos, member_indent); - continue; - } - - /* at body_indent: member declaration (field = expr) or method 'fun name(...)' */ - if (starts_with(src, len, *pos, "fun")) { - /* method definition: fun m(this, ...) ... */ - *pos += 3; - skip_spaces(src, len, pos); - char *mname = NULL; - if (!read_identifier_into(src, len, pos, &mname)) { - parser_fail(*pos, "Expected method name after 'fun' in class"); - g_locals = prev_env; - free(cname); - return; - } - int is_ctor_method = (strcmp(mname, "_construct") == 0); - - skip_spaces(src, len, pos); - if (!consume_char(src, len, pos, '(')) { - parser_fail(*pos, "Expected '(' after method name"); - free(mname); - g_locals = prev_env; - free(cname); - return; - } - - /* Build method function bytecode */ - Bytecode *m_bc = bytecode_new(); - if (m_bc) { - if (m_bc->name) free((void*)m_bc->name); - /* method qualified name: Class.method */ - size_t qlen = strlen(cname) + 1 + strlen(mname) + 1; - char *q = (char*)malloc(qlen); - if (q) { - snprintf(q, qlen, "%s.%s", cname, mname); - m_bc->name = q; - } - if (m_bc->source_file) free((void*)m_bc->source_file); - if (g_current_source_path) m_bc->source_file = strdup(g_current_source_path); - } - LocalEnv m_env; - memset(&m_env, 0, sizeof(m_env)); - LocalEnv *saved = g_locals; - g_locals = &m_env; - - /* Parse params, ensure first is 'this' (insert if missing) */ - int saw_param = 0; - int param_count = 0; - skip_spaces(src, len, pos); - if (*pos < len && src[*pos] != ')') { - for (;;) { - char *pname = NULL; - if (!read_identifier_into(src, len, pos, &pname)) { - parser_fail(*pos, "Expected parameter name"); - free(mname); - g_locals = saved; - g_locals = prev_env; - free(cname); - return; - } - if (param_count == 0 && strcmp(pname, "this") != 0) { - /* Require explicit 'this' as first parameter */ - if (is_ctor_method) { - parser_fail(*pos, "Constructor '_construct' must declare 'this' as its first parameter"); - } else { - parser_fail(*pos, "First parameter of a method must be 'this'"); - } - free(pname); - free(mname); - g_locals = saved; - g_locals = prev_env; - free(cname); - return; - } - local_add(pname); - free(pname); - param_count++; - skip_spaces(src, len, pos); - if (*pos < len && src[*pos] == ',') { - (*pos)++; - skip_spaces(src, len, pos); - continue; - } - break; - } - } else { - /* no params: enforce (this) */ - if (is_ctor_method) { - parser_fail(*pos, "Constructor '_construct' must declare 'this' as its first parameter"); - } else { - parser_fail(*pos, "Method must declare at least 'this' parameter"); - } - free(mname); - g_locals = saved; - g_locals = prev_env; - free(cname); - return; - } - - if (!consume_char(src, len, pos, ')')) { - parser_fail(*pos, "Expected ')' after method parameter list"); - free(mname); - g_locals = saved; - g_locals = prev_env; - free(cname); - return; - } - /* end header line */ - skip_to_eol(src, len, pos); - - /* parse method body at increased indent */ - int m_body_indent = 0; - size_t look_m = *pos; - if (read_line_start(src, len, &look_m, &m_body_indent) && m_body_indent > body_indent) { - parse_block(m_bc, src, len, pos, m_body_indent); - } else { - /* empty method body allowed -> return */ - } - /* ensure return */ - bytecode_add_instruction(m_bc, OP_RETURN, 0); - - /* restore env to factory */ - g_locals = saved; - - /* Insert method function into instance: this["mname"] = */ - bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this); - int kci = bytecode_add_constant(ctor_bc, make_string(mname)); - bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, kci); - int mci = bytecode_add_constant(ctor_bc, make_function(m_bc)); - bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, mci); - bytecode_add_instruction(ctor_bc, OP_INDEX_SET, 0); - - /* mark constructor presence if name matches */ - if (strcmp(mname, "_construct") == 0) { - ctor_present = 1; - } - - free(mname); - continue; - } - - /* field initializer: ident = expr */ - size_t lp = *pos; - char *fname = NULL; - if (!read_identifier_into(src, len, &lp, &fname)) { - parser_fail(*pos, "Expected field or 'fun' in class body"); - g_locals = prev_env; - free(cname); - return; - } - size_t tmp = lp; - skip_spaces(src, len, &tmp); - if (tmp >= len || src[tmp] != '=') { - free(fname); - parser_fail(tmp, "Expected '=' in field initializer"); - g_locals = prev_env; - free(cname); - return; - } - /* commit position and consume '=' */ - *pos = tmp + 1; - /* emit: this["fname"] = (expr) */ - bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this); - int fkey = bytecode_add_constant(ctor_bc, make_string(fname)); - bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, fkey); - free(fname); - if (!emit_expression(ctor_bc, src, len, pos)) { - parser_fail(*pos, "Expected expression in field initializer"); - g_locals = prev_env; - free(cname); - return; - } - bytecode_add_instruction(ctor_bc, OP_INDEX_SET, 0); - /* end of line */ - skip_to_eol(src, len, pos); - } - } else { - /* empty class body allowed */ - } - - /* Override defaults with constructor parameters: this["name"] = local i */ - for (int i = 0; i < pcount; ++i) { - bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this); - int kci = bytecode_add_constant(ctor_bc, make_string(param_names[i])); - bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, kci); - bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, i); - bytecode_add_instruction(ctor_bc, OP_INDEX_SET, 0); - } - - /* If a constructor exists, invoke: this._construct(this, params...) and drop its return */ - if (ctor_present) { - /* fetch method: duplicate 'this' so we keep it for the call */ - bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this); - bytecode_add_instruction(ctor_bc, OP_DUP, 0); /* -> this, this */ - { - int kci_ctor = bytecode_add_constant(ctor_bc, make_string("_construct")); - bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, kci_ctor); - } - bytecode_add_instruction(ctor_bc, OP_INDEX_GET, 0); /* -> this, func */ - bytecode_add_instruction(ctor_bc, OP_SWAP, 0); /* -> func, this */ - - /* push all header parameters as additional args in order */ - for (int i = 0; i < pcount; ++i) { - bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, i); - } - /* call with implicit 'this' (+1) plus pcount params */ - bytecode_add_instruction(ctor_bc, OP_CALL, pcount + 1); - /* discard any return value from constructor */ - bytecode_add_instruction(ctor_bc, OP_POP, 0); - } - - /* return instance */ - bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this); - bytecode_add_instruction(ctor_bc, OP_RETURN, 0); - - /* restore outer locals env */ - g_locals = prev_env; - - /* bind factory function globally under class name */ - int cci = bytecode_add_constant(bc, make_function(ctor_bc)); - bytecode_add_instruction(bc, OP_LOAD_CONST, cci); - bytecode_add_instruction(bc, OP_STORE_GLOBAL, cgi); - /* mark this global as a class for typeof(identifier) */ - G.is_class[cgi] = 1; - - for (int i = 0; i < pcount; ++i) free(param_names[i]); - if (parent_name) free(parent_name); - free(cname); + /* Parse class body at increased indent */ + int body_indent = 0; + size_t look_body = *pos; + if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) { + /* iterate over class members at body_indent */ + for (;;) { + size_t member_line_start = *pos; + int member_indent = 0; + if (!read_line_start(src, len, pos, &member_indent)) { + /* EOF */ + break; + } + if (member_indent < body_indent) { + /* end of class body */ + *pos = member_line_start; + break; + } + if (member_indent > body_indent) { + /* skip nested blocks that are part of previous method parsing */ + parse_block(ctor_bc, src, len, pos, member_indent); continue; - } + } - if (starts_with(src, len, *pos, "fun")) { - /* parse header: fun name(arg, ...) */ + /* at body_indent: member declaration (field = expr) or method 'fun name(...)' */ + if (starts_with(src, len, *pos, "fun")) { + /* method definition: fun m(this, ...) ... */ *pos += 3; skip_spaces(src, len, pos); - char *fname = NULL; - if (!read_identifier_into(src, len, pos, &fname)) { - parser_fail(*pos, "Expected function name after 'fun'"); - return; + char *mname = NULL; + if (!read_identifier_into(src, len, pos, &mname)) { + parser_fail(*pos, "Expected method name after 'fun' in class"); + g_locals = prev_env; + free(cname); + return; } - int fgi = sym_index(fname); + int is_ctor_method = (strcmp(mname, "_construct") == 0); + skip_spaces(src, len, pos); if (!consume_char(src, len, pos, '(')) { - parser_fail(*pos, "Expected '(' after function name"); - free(fname); - return; + parser_fail(*pos, "Expected '(' after method name"); + free(mname); + g_locals = prev_env; + free(cname); + return; } - /* build locals from parameters */ - LocalEnv env = { {0}, 0 }; - LocalEnv *prev = g_locals; - g_locals = &env; + /* Build method function bytecode */ + Bytecode *m_bc = bytecode_new(); + if (m_bc) { + if (m_bc->name) free((void *)m_bc->name); + /* method qualified name: Class.method */ + size_t qlen = strlen(cname) + 1 + strlen(mname) + 1; + char *q = (char *)malloc(qlen); + if (q) { + snprintf(q, qlen, "%s.%s", cname, mname); + m_bc->name = q; + } + if (m_bc->source_file) free((void *)m_bc->source_file); + if (g_current_source_path) m_bc->source_file = strdup(g_current_source_path); + } + LocalEnv m_env; + memset(&m_env, 0, sizeof(m_env)); + LocalEnv *saved = g_locals; + g_locals = &m_env; + /* Parse params, ensure first is 'this' (insert if missing) */ + int saw_param = 0; + int param_count = 0; skip_spaces(src, len, pos); if (*pos < len && src[*pos] != ')') { - for (;;) { - char *pname = NULL; - if (!read_identifier_into(src, len, pos, &pname)) { - parser_fail(*pos, "Expected parameter name"); - g_locals = prev; - free(fname); - return; - } - if (local_find(pname) >= 0) { - parser_fail(*pos, "Duplicate parameter name '%s'", pname); - free(pname); - g_locals = prev; - free(fname); - return; - } - local_add(pname); - free(pname); - skip_spaces(src, len, pos); - if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); continue; } - break; + for (;;) { + char *pname = NULL; + if (!read_identifier_into(src, len, pos, &pname)) { + parser_fail(*pos, "Expected parameter name"); + free(mname); + g_locals = saved; + g_locals = prev_env; + free(cname); + return; } + if (param_count == 0 && strcmp(pname, "this") != 0) { + /* Require explicit 'this' as first parameter */ + if (is_ctor_method) { + parser_fail(*pos, "Constructor '_construct' must declare 'this' as its first parameter"); + } else { + parser_fail(*pos, "First parameter of a method must be 'this'"); + } + free(pname); + free(mname); + g_locals = saved; + g_locals = prev_env; + free(cname); + return; + } + local_add(pname); + free(pname); + param_count++; + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + continue; + } + break; + } + } else { + /* no params: enforce (this) */ + if (is_ctor_method) { + parser_fail(*pos, "Constructor '_construct' must declare 'this' as its first parameter"); + } else { + parser_fail(*pos, "Method must declare at least 'this' parameter"); + } + free(mname); + g_locals = saved; + g_locals = prev_env; + free(cname); + return; } + if (!consume_char(src, len, pos, ')')) { - parser_fail(*pos, "Expected ')' after parameter list"); - g_locals = prev; - free(fname); - return; + parser_fail(*pos, "Expected ')' after method parameter list"); + free(mname); + g_locals = saved; + g_locals = prev_env; + free(cname); + return; } /* end header line */ skip_to_eol(src, len, pos); - /* compile body into separate Bytecode */ - Bytecode *fn_bc = bytecode_new(); - if (fn_bc) { - if (fn_bc->name) free((void*)fn_bc->name); - fn_bc->name = strdup(fname); - if (fn_bc->source_file) free((void*)fn_bc->source_file); - if (g_current_source_path) fn_bc->source_file = strdup(g_current_source_path); - } - - /* parse body at increased indent if present */ - int body_indent = 0; - size_t look_body = *pos; - if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) { - /* do not advance pos here; let parse_block consume the line */ - parse_block(fn_bc, src, len, pos, body_indent); + /* parse method body at increased indent */ + int m_body_indent = 0; + size_t look_m = *pos; + if (read_line_start(src, len, &look_m, &m_body_indent) && m_body_indent > body_indent) { + parse_block(m_bc, src, len, pos, m_body_indent); } else { - /* empty body: ok */ + /* empty method body allowed -> return */ } - /* ensure function returns */ - bytecode_add_instruction(fn_bc, OP_RETURN, 0); + /* ensure return */ + bytecode_add_instruction(m_bc, OP_RETURN, 0); -#ifdef FUN_DEBUG - /* DEBUG: dump compiled function bytecode */ - printf("=== compiled function %s (%d params) ===\n", fname, env.count); - bytecode_dump(fn_bc); - printf("=== end function %s ===\n", fname); -#endif + /* restore env to factory */ + g_locals = saved; - /* bind function to global: LOAD_CONST ; STORE_GLOBAL fgi */ - int fci = bytecode_add_constant(bc, make_function(fn_bc)); - bytecode_add_instruction(bc, OP_LOAD_CONST, fci); - bytecode_add_instruction(bc, OP_STORE_GLOBAL, fgi); + /* Insert method function into instance: this["mname"] = */ + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this); + int kci = bytecode_add_constant(ctor_bc, make_string(mname)); + bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, kci); + int mci = bytecode_add_constant(ctor_bc, make_function(m_bc)); + bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, mci); + bytecode_add_instruction(ctor_bc, OP_INDEX_SET, 0); + /* mark constructor presence if name matches */ + if (strcmp(mname, "_construct") == 0) { + ctor_present = 1; + } + + free(mname); + continue; + } + + /* field initializer: ident = expr */ + size_t lp = *pos; + char *fname = NULL; + if (!read_identifier_into(src, len, &lp, &fname)) { + parser_fail(*pos, "Expected field or 'fun' in class body"); + g_locals = prev_env; + free(cname); + return; + } + size_t tmp = lp; + skip_spaces(src, len, &tmp); + if (tmp >= len || src[tmp] != '=') { + free(fname); + parser_fail(tmp, "Expected '=' in field initializer"); + g_locals = prev_env; + free(cname); + return; + } + /* commit position and consume '=' */ + *pos = tmp + 1; + /* emit: this["fname"] = (expr) */ + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this); + int fkey = bytecode_add_constant(ctor_bc, make_string(fname)); + bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, fkey); + free(fname); + if (!emit_expression(ctor_bc, src, len, pos)) { + parser_fail(*pos, "Expected expression in field initializer"); + g_locals = prev_env; + free(cname); + return; + } + bytecode_add_instruction(ctor_bc, OP_INDEX_SET, 0); + /* end of line */ + skip_to_eol(src, len, pos); + } + } else { + /* empty class body allowed */ + } + + /* Override defaults with constructor parameters: this["name"] = local i */ + for (int i = 0; i < pcount; ++i) { + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this); + int kci = bytecode_add_constant(ctor_bc, make_string(param_names[i])); + bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, kci); + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, i); + bytecode_add_instruction(ctor_bc, OP_INDEX_SET, 0); + } + + /* If a constructor exists, invoke: this._construct(this, params...) and drop its return */ + if (ctor_present) { + /* fetch method: duplicate 'this' so we keep it for the call */ + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this); + bytecode_add_instruction(ctor_bc, OP_DUP, 0); /* -> this, this */ + { + int kci_ctor = bytecode_add_constant(ctor_bc, make_string("_construct")); + bytecode_add_instruction(ctor_bc, OP_LOAD_CONST, kci_ctor); + } + bytecode_add_instruction(ctor_bc, OP_INDEX_GET, 0); /* -> this, func */ + bytecode_add_instruction(ctor_bc, OP_SWAP, 0); /* -> func, this */ + + /* push all header parameters as additional args in order */ + for (int i = 0; i < pcount; ++i) { + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, i); + } + /* call with implicit 'this' (+1) plus pcount params */ + bytecode_add_instruction(ctor_bc, OP_CALL, pcount + 1); + /* discard any return value from constructor */ + bytecode_add_instruction(ctor_bc, OP_POP, 0); + } + + /* return instance */ + bytecode_add_instruction(ctor_bc, OP_LOAD_LOCAL, l_this); + bytecode_add_instruction(ctor_bc, OP_RETURN, 0); + + /* restore outer locals env */ + g_locals = prev_env; + + /* bind factory function globally under class name */ + int cci = bytecode_add_constant(bc, make_function(ctor_bc)); + bytecode_add_instruction(bc, OP_LOAD_CONST, cci); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, cgi); + /* mark this global as a class for typeof(identifier) */ + G.is_class[cgi] = 1; + + for (int i = 0; i < pcount; ++i) + free(param_names[i]); + if (parent_name) free(parent_name); + free(cname); + continue; + } + + if (starts_with(src, len, *pos, "fun")) { + /* parse header: fun name(arg, ...) */ + *pos += 3; + skip_spaces(src, len, pos); + char *fname = NULL; + if (!read_identifier_into(src, len, pos, &fname)) { + parser_fail(*pos, "Expected function name after 'fun'"); + return; + } + int fgi = sym_index(fname); + skip_spaces(src, len, pos); + if (!consume_char(src, len, pos, '(')) { + parser_fail(*pos, "Expected '(' after function name"); + free(fname); + return; + } + + /* build locals from parameters */ + LocalEnv env = {{0}, 0}; + LocalEnv *prev = g_locals; + g_locals = &env; + + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] != ')') { + for (;;) { + char *pname = NULL; + if (!read_identifier_into(src, len, pos, &pname)) { + parser_fail(*pos, "Expected parameter name"); g_locals = prev; free(fname); - continue; - } - - /* for-sugar: - * - for in range(a, b) - * - for in - */ - if (starts_with(src, len, *pos, "for")) { - *pos += 3; + return; + } + if (local_find(pname) >= 0) { + parser_fail(*pos, "Duplicate parameter name '%s'", pname); + free(pname); + g_locals = prev; + free(fname); + return; + } + local_add(pname); + free(pname); + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] == ',') { + (*pos)++; skip_spaces(src, len, pos); - - /* loop variable name */ - char *ivar = NULL; - if (!read_identifier_into(src, len, pos, &ivar)) { - parser_fail(*pos, "Expected loop variable after 'for'"); - return; - } - - skip_spaces(src, len, pos); - if (!starts_with(src, len, *pos, "in")) { - parser_fail(*pos, "Expected 'in' after loop variable"); - free(ivar); - return; - } - *pos += 2; - skip_spaces(src, len, pos); - - if (starts_with(src, len, *pos, "range")) { - /* ===== range(a, b) variant ===== */ - *pos += 5; - if (!consume_char(src, len, pos, '(')) { - parser_fail(*pos, "Expected '(' after range"); - free(ivar); - return; - } - - /* Parse start expression */ - if (!emit_expression(bc, src, len, pos)) { - parser_fail(*pos, "Expected start expression in range"); - free(ivar); - return; - } - - /* Determine loop variable storage and store start value */ - int lidx = local_find(ivar); - int gi = -1; - if (lidx < 0) { - if (g_locals) lidx = local_add(ivar); - else gi = sym_index(ivar); - } - if (lidx >= 0) { - bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); - } else { - bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); - } - - /* comma */ - skip_spaces(src, len, pos); - if (*pos >= len || src[*pos] != ',') { - parser_fail(*pos, "Expected ',' between range start and end"); - free(ivar); - return; - } - (*pos)++; /* consume ',' */ - skip_spaces(src, len, pos); - - /* Parse end expression and store in a temp (local or global) */ - if (!emit_expression(bc, src, len, pos)) { - parser_fail(*pos, "Expected end expression in range"); - free(ivar); - return; - } - - char tmpname[64]; - snprintf(tmpname, sizeof(tmpname), "__for_end_%d", g_temp_counter++); - - int lend = -1, gend = -1; - if (g_locals) { - lend = local_add(tmpname); - bytecode_add_instruction(bc, OP_STORE_LOCAL, lend); - } else { - gend = sym_index(tmpname); - bytecode_add_instruction(bc, OP_STORE_GLOBAL, gend); - } - - if (!consume_char(src, len, pos, ')')) { - parser_fail(*pos, "Expected ')' after range arguments"); - free(ivar); - return; - } - - /* end of header line */ - skip_to_eol(src, len, pos); - - /* emit loop */ - int loop_start = bc->instr_count; - - /* condition: ivar < end_tmp */ - if (lidx >= 0) { - bytecode_add_instruction(bc, OP_LOAD_LOCAL, lidx); - } else { - bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); - } - if (lend >= 0) { - bytecode_add_instruction(bc, OP_LOAD_LOCAL, lend); - } else { - bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gend); - } - bytecode_add_instruction(bc, OP_LT, 0); - int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); - - /* enter loop context for break/continue */ - LoopCtx ctx = { {0}, 0, {0}, 0, g_loop_ctx }; - g_loop_ctx = &ctx; - - /* parse body at increased indent (peek) */ - int body_indent = 0; - size_t look_body = *pos; - if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) { - parse_block(bc, src, len, pos, body_indent); - } else { - /* empty body ok */ - } - - /* continue target: start of increment */ - int cont_label = bc->instr_count; - - /* i = i + 1 */ - int c1 = bytecode_add_constant(bc, make_int(1)); - if (lidx >= 0) { - bytecode_add_instruction(bc, OP_LOAD_LOCAL, lidx); - bytecode_add_instruction(bc, OP_LOAD_CONST, c1); - bytecode_add_instruction(bc, OP_ADD, 0); - bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); - } else { - bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); - bytecode_add_instruction(bc, OP_LOAD_CONST, c1); - bytecode_add_instruction(bc, OP_ADD, 0); - bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); - } - - /* back edge and patch */ - bytecode_add_instruction(bc, OP_JUMP, loop_start); - - /* end label (after loop) */ - int end_label = bc->instr_count; - bytecode_set_operand(bc, jmp_false, end_label); - - /* patch continue/break jumps */ - for (int bi = 0; bi < ctx.cont_count; ++bi) { - bytecode_set_operand(bc, ctx.continue_jumps[bi], cont_label); - } - for (int bi = 0; bi < ctx.break_count; ++bi) { - bytecode_set_operand(bc, ctx.break_jumps[bi], end_label); - } - g_loop_ctx = ctx.prev; - - free(ivar); - continue; - } else { - /* ===== array iteration: for ivar in ===== */ - /* Evaluate the iterable once and store in a temp */ - if (!emit_expression(bc, src, len, pos)) { - parser_fail(*pos, "Expected iterable expression after 'in'"); - free(ivar); - return; - } - char arrname[64]; - snprintf(arrname, sizeof(arrname), "__for_arr_%d", g_temp_counter++); - int larr = -1, garr = -1; - if (g_locals) { - larr = local_add(arrname); - bytecode_add_instruction(bc, OP_STORE_LOCAL, larr); - } else { - garr = sym_index(arrname); - bytecode_add_instruction(bc, OP_STORE_GLOBAL, garr); - } - - /* Compute length once: len(arr) -> store temp */ - if (larr >= 0) { - bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr); - } else { - bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr); - } - bytecode_add_instruction(bc, OP_LEN, 0); - char lenname[64]; - snprintf(lenname, sizeof(lenname), "__for_len_%d", g_temp_counter++); - int llen = -1, glen = -1; - if (g_locals) { - llen = local_add(lenname); - bytecode_add_instruction(bc, OP_STORE_LOCAL, llen); - } else { - glen = sym_index(lenname); - bytecode_add_instruction(bc, OP_STORE_GLOBAL, glen); - } - - /* Index temp: i = 0 */ - int c0 = bytecode_add_constant(bc, make_int(0)); - bytecode_add_instruction(bc, OP_LOAD_CONST, c0); - char iname[64]; - snprintf(iname, sizeof(iname), "__for_i_%d", g_temp_counter++); - int li = -1, gi = -1; - if (g_locals) { - li = local_add(iname); - bytecode_add_instruction(bc, OP_STORE_LOCAL, li); - } else { - gi = sym_index(iname); - bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); - } - - /* end of header line */ - skip_to_eol(src, len, pos); - - /* loop start label */ - int loop_start = bc->instr_count; - - /* condition: i < len */ - if (li >= 0) { - bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); - } else { - bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); - } - if (llen >= 0) { - bytecode_add_instruction(bc, OP_LOAD_LOCAL, llen); - } else { - bytecode_add_instruction(bc, OP_LOAD_GLOBAL, glen); - } - bytecode_add_instruction(bc, OP_LT, 0); - int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); - - /* element: ivar = arr[i] */ - if (larr >= 0) { - bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr); - } else { - bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr); - } - if (li >= 0) { - bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); - } else { - bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); - } - bytecode_add_instruction(bc, OP_INDEX_GET, 0); - - /* assign to ivar (local preferred) */ - int ldst = local_find(ivar); - int gdst = -1; - if (ldst < 0) { - if (g_locals) ldst = local_add(ivar); - else gdst = sym_index(ivar); - } - if (ldst >= 0) { - bytecode_add_instruction(bc, OP_STORE_LOCAL, ldst); - } else { - bytecode_add_instruction(bc, OP_STORE_GLOBAL, gdst); - } - - /* enter loop context for break/continue */ - LoopCtx ctx = { {0}, 0, {0}, 0, g_loop_ctx }; - g_loop_ctx = &ctx; - - /* parse body at increased indent */ - int body_indent = 0; - size_t look_body = *pos; - if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) { - parse_block(bc, src, len, pos, body_indent); - } else { - /* empty body ok */ - } - - /* continue target: start of increment */ - int cont_label = bc->instr_count; - - /* i = i + 1 */ - int c1 = bytecode_add_constant(bc, make_int(1)); - if (li >= 0) { - bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); - bytecode_add_instruction(bc, OP_LOAD_CONST, c1); - bytecode_add_instruction(bc, OP_ADD, 0); - bytecode_add_instruction(bc, OP_STORE_LOCAL, li); - } else { - bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); - bytecode_add_instruction(bc, OP_LOAD_CONST, c1); - bytecode_add_instruction(bc, OP_ADD, 0); - bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); - } - - /* back edge and patch */ - bytecode_add_instruction(bc, OP_JUMP, loop_start); - - /* end label (after loop) */ - int end_label = bc->instr_count; - bytecode_set_operand(bc, jmp_false, end_label); - - /* patch continue/break jumps */ - for (int bi = 0; bi < ctx.cont_count; ++bi) { - bytecode_set_operand(bc, ctx.continue_jumps[bi], cont_label); - } - for (int bi = 0; bi < ctx.break_count; ++bi) { - bytecode_set_operand(bc, ctx.break_jumps[bi], end_label); - } - g_loop_ctx = ctx.prev; - - free(ivar); - continue; - } - } - - if (starts_with(src, len, *pos, "if")) { - int end_jumps[64]; - int end_count = 0; - - for (;;) { - /* consume 'if' or 'else if' condition */ - if (starts_with(src, len, *pos, "if")) { - *pos += 2; - } else { - /* for 'else if' we arrive here with *pos already after 'if' */ - } - - /* require at least one space before condition if present */ - skip_spaces(src, len, pos); - if (!emit_expression(bc, src, len, pos)) { - /* no condition -> treat as false */ - int ci = bytecode_add_constant(bc, make_int(0)); - bytecode_add_instruction(bc, OP_LOAD_CONST, ci); - } - - /* Decide between inline single-statement and indented block on next line */ - size_t ppeek = *pos; - /* skip spaces after condition */ - while (ppeek < len && src[ppeek] == ' ') ppeek++; - int inline_stmt = 0; - if (ppeek < len) { - if (src[ppeek] == '\r' || src[ppeek] == '\n') { - inline_stmt = 0; /* EOL -> no inline body */ - } else if (ppeek + 1 < len && src[ppeek] == '/' && src[ppeek + 1] == '/') { - inline_stmt = 0; /* line comment -> no inline body */ - } else if (ppeek + 1 < len && src[ppeek] == '/' && src[ppeek + 1] == '*') { - inline_stmt = 0; /* block comment at EOL -> no inline body */ - } else { - inline_stmt = 1; /* there's code after condition on same line */ - } - } - - /* conditional jump over this clause's inline/body */ - int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); - - if (inline_stmt) { - /* Compile a single inline statement on the same line: - if (cond) */ - *pos = ppeek; - parse_simple_statement(bc, src, len, pos); - /* Skip the inline body when condition is false */ - bytecode_set_operand(bc, jmp_false, bc->instr_count); - /* one-liner form has no else/elseif on the same line; end the chain */ - break; - } - - /* No inline statement on this line: consume up to EOL and parse indented block or else-if/else */ - skip_to_eol(src, len, pos); - - /* parse nested block if next line is indented */ - int next_indent = 0; - size_t look_next = *pos; - if (read_line_start(src, len, &look_next, &next_indent)) { - if (next_indent > current_indent) { - /* parse body at increased indent (let parse_block consume the line) */ - parse_block(bc, src, len, pos, next_indent); - } else { - /* empty body; keep *pos at start of that line */ - } - } else { - /* EOF -> empty body */ - } - - /* after body, unconditionally jump to end of the whole chain */ - int jmp_end = bytecode_add_instruction(bc, OP_JUMP, 0); - if (end_count < (int)(sizeof(end_jumps) / sizeof(end_jumps[0]))) { - end_jumps[end_count++] = jmp_end; - } else { - parser_fail(*pos, "Too many chained else/if clauses"); - return; - } - - /* patch false-jump target to start of next clause (or fallthrough) */ - bytecode_set_operand(bc, jmp_false, bc->instr_count); - - /* look for else or else if at the same indentation */ - size_t look = *pos; - int look_indent = 0; - if (!read_line_start(src, len, &look, &look_indent)) { - /* EOF: break and patch end jumps */ - break; - } - if (look_indent != current_indent) { - /* dedent or deeper indent means no 'else' clause here */ - break; - } - if (starts_with(src, len, look, "else")) { - /* consume 'else' */ - *pos = look + 4; - skip_spaces(src, len, pos); - - if (starts_with(src, len, *pos, "if")) { - /* else if -> consume 'if' token and continue loop to parse condition */ - *pos += 2; - continue; - } else { - /* plain else: parse its block and finish the chain */ - skip_to_eol(src, len, pos); - int else_indent = 0; - size_t look_else = *pos; - if (read_line_start(src, len, &look_else, &else_indent) && else_indent > current_indent) { - /* let parse_block consume the line */ - parse_block(bc, src, len, pos, else_indent); - } else { - /* empty else-body */ - } - /* end of chain after else */ - break; - } - } else { - /* next line is not an else/else if */ - break; - } - } - - /* patch all end-of-clause jumps to the end of the chain */ - for (int i = 0; i < end_count; ++i) { - bytecode_set_operand(bc, end_jumps[i], bc->instr_count); - } continue; + } + break; } + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after parameter list"); + g_locals = prev; + free(fname); + return; + } + /* end header line */ + skip_to_eol(src, len, pos); - /* while loop */ - if (starts_with(src, len, *pos, "while")) { - *pos += 5; - skip_spaces(src, len, pos); + /* compile body into separate Bytecode */ + Bytecode *fn_bc = bytecode_new(); + if (fn_bc) { + if (fn_bc->name) free((void *)fn_bc->name); + fn_bc->name = strdup(fname); + if (fn_bc->source_file) free((void *)fn_bc->source_file); + if (g_current_source_path) fn_bc->source_file = strdup(g_current_source_path); + } - int loop_start = bc->instr_count; + /* parse body at increased indent if present */ + int body_indent = 0; + size_t look_body = *pos; + if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) { + /* do not advance pos here; let parse_block consume the line */ + parse_block(fn_bc, src, len, pos, body_indent); + } else { + /* empty body: ok */ + } + /* ensure function returns */ + bytecode_add_instruction(fn_bc, OP_RETURN, 0); - /* condition */ - if (!emit_expression(bc, src, len, pos)) { - int ci = bytecode_add_constant(bc, make_int(0)); - bytecode_add_instruction(bc, OP_LOAD_CONST, ci); - } - /* end of condition */ - skip_to_eol(src, len, pos); +#ifdef FUN_DEBUG + /* DEBUG: dump compiled function bytecode */ + printf("=== compiled function %s (%d params) ===\n", fname, env.count); + bytecode_dump(fn_bc); + printf("=== end function %s ===\n", fname); +#endif - /* jump over body if false */ - int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + /* bind function to global: LOAD_CONST ; STORE_GLOBAL fgi */ + int fci = bytecode_add_constant(bc, make_function(fn_bc)); + bytecode_add_instruction(bc, OP_LOAD_CONST, fci); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, fgi); - /* enter loop context (continue -> condition) */ - LoopCtx ctx = { {0}, 0, {0}, 0, g_loop_ctx }; - g_loop_ctx = &ctx; - - /* parse body at increased indent (peek indent without advancing pos) */ - int body_indent = 0; - size_t look_body = *pos; - if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) { - parse_block(bc, src, len, pos, body_indent); - } else { - /* empty body allowed */ - } - - /* patch pending continues to loop_start (re-evaluate condition) */ - for (int bi = 0; bi < ctx.cont_count; ++bi) { - bytecode_set_operand(bc, ctx.continue_jumps[bi], loop_start); - } - - /* back edge to loop start */ - bytecode_add_instruction(bc, OP_JUMP, loop_start); - - /* end label and patches */ - int end_label = bc->instr_count; - bytecode_set_operand(bc, jmp_false, end_label); - for (int bi = 0; bi < ctx.break_count; ++bi) { - bytecode_set_operand(bc, ctx.break_jumps[bi], end_label); - } - g_loop_ctx = ctx.prev; - continue; - } - - /* try/catch/finally */ - if (starts_with(src, len, *pos, "try")) { - /* consume 'try' */ - *pos += 3; - /* end of header line */ - skip_to_eol(src, len, pos); - - /* Install a handler placeholder; will be patched to catch label (or a rethrow stub) */ - int try_push_idx = bytecode_add_instruction(bc, OP_TRY_PUSH, 0); - - /* parse try body at increased indent (if any) */ - int try_body_indent = 0; - size_t look_try = *pos; - if (read_line_start(src, len, &look_try, &try_body_indent) && try_body_indent > current_indent) { - parse_block(bc, src, len, pos, try_body_indent); - } else { - /* empty try body allowed */ - } - - /* After try body, pop handler for normal (non-exceptional) flow */ - bytecode_add_instruction(bc, OP_TRY_POP, 0); - - /* on normal completion, jump over catch body */ - int jmp_over_catch_finally = bytecode_add_instruction(bc, OP_JUMP, 0); - - /* Optional: catch and/or finally clauses at same indentation */ - int seen_catch = 0; - int seen_finally = 0; - int catch_label = -1; - for (;;) { - size_t look = *pos; - int look_indent = 0; - if (!read_line_start(src, len, &look, &look_indent)) break; /* EOF */ - if (look_indent != current_indent) break; /* different indentation -> stop */ - - if (!seen_catch && starts_with(src, len, look, "catch")) { - /* consume 'catch' */ - *pos = look + 5; - /* optional variable name */ - skip_spaces(src, len, pos); - char *ex_name = NULL; - size_t tmp = *pos; - int have_name = 0; - if (read_identifier_into(src, len, &tmp, &ex_name)) { - *pos = tmp; - have_name = 1; - } - /* end of header line */ - skip_to_eol(src, len, pos); - - /* Mark catch label and patch try handler target */ - catch_label = bc->instr_count; - bytecode_set_operand(bc, try_push_idx, catch_label); - - /* On entering catch, the thrown error is on stack. Bind to name if provided, else pop. */ - if (have_name) { - int lidx = -1, gi = -1; - if (g_locals) { - int existing = local_find(ex_name); - if (existing >= 0) lidx = existing; else lidx = local_add(ex_name); - } else { - gi = sym_index(ex_name); - } - if (lidx >= 0) bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); - else if (gi >= 0) bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); - else bytecode_add_instruction(bc, OP_POP, 0); - } else { - bytecode_add_instruction(bc, OP_POP, 0); - } - if (ex_name) free(ex_name); - - /* parse catch body at increased indent (if any) */ - int catch_indent = 0; - size_t look_catch = *pos; - if (read_line_start(src, len, &look_catch, &catch_indent) && catch_indent > current_indent) { - parse_block(bc, src, len, pos, catch_indent); - } else { - /* empty catch body allowed */ - } - seen_catch = 1; - continue; - } - - if (!seen_finally && starts_with(src, len, look, "finally")) { - /* consume 'finally' */ - *pos = look + 7; - /* end of header line */ - skip_to_eol(src, len, pos); - - /* parse finally body at increased indent (if any) */ - int finally_indent = 0; - size_t look_fin = *pos; - if (read_line_start(src, len, &look_fin, &finally_indent) && finally_indent > current_indent) { - parse_block(bc, src, len, pos, finally_indent); - } else { - /* empty finally body allowed */ - } - - seen_finally = 1; - continue; - } - - /* no recognized clause at this indentation */ - break; - } - - /* If no catch clause was present, make handler rethrow */ - if (!seen_catch) { - int rethrow_label = bc->instr_count; - bytecode_set_operand(bc, try_push_idx, rethrow_label); - /* at handler: immediately rethrow the incoming error */ - bytecode_add_instruction(bc, OP_THROW, 0); - } - - /* patch normal-flow jump to here (after catch/finally) */ - bytecode_set_operand(bc, jmp_over_catch_finally, bc->instr_count); - continue; - } - - /* otherwise: simple statement on this line */ - parse_simple_statement(bc, src, len, pos); + g_locals = prev; + free(fname); + continue; } + + /* for-sugar: + * - for in range(a, b) + * - for in + */ + if (starts_with(src, len, *pos, "for")) { + *pos += 3; + skip_spaces(src, len, pos); + + /* loop variable name */ + char *ivar = NULL; + if (!read_identifier_into(src, len, pos, &ivar)) { + parser_fail(*pos, "Expected loop variable after 'for'"); + return; + } + + skip_spaces(src, len, pos); + if (!starts_with(src, len, *pos, "in")) { + parser_fail(*pos, "Expected 'in' after loop variable"); + free(ivar); + return; + } + *pos += 2; + skip_spaces(src, len, pos); + + if (starts_with(src, len, *pos, "range")) { + /* ===== range(a, b) variant ===== */ + *pos += 5; + if (!consume_char(src, len, pos, '(')) { + parser_fail(*pos, "Expected '(' after range"); + free(ivar); + return; + } + + /* Parse start expression */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "Expected start expression in range"); + free(ivar); + return; + } + + /* Determine loop variable storage and store start value */ + int lidx = local_find(ivar); + int gi = -1; + if (lidx < 0) { + if (g_locals) + lidx = local_add(ivar); + else + gi = sym_index(ivar); + } + if (lidx >= 0) { + bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); + } else { + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); + } + + /* comma */ + skip_spaces(src, len, pos); + if (*pos >= len || src[*pos] != ',') { + parser_fail(*pos, "Expected ',' between range start and end"); + free(ivar); + return; + } + (*pos)++; /* consume ',' */ + skip_spaces(src, len, pos); + + /* Parse end expression and store in a temp (local or global) */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "Expected end expression in range"); + free(ivar); + return; + } + + char tmpname[64]; + snprintf(tmpname, sizeof(tmpname), "__for_end_%d", g_temp_counter++); + + int lend = -1, gend = -1; + if (g_locals) { + lend = local_add(tmpname); + bytecode_add_instruction(bc, OP_STORE_LOCAL, lend); + } else { + gend = sym_index(tmpname); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gend); + } + + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after range arguments"); + free(ivar); + return; + } + + /* end of header line */ + skip_to_eol(src, len, pos); + + /* emit loop */ + int loop_start = bc->instr_count; + + /* condition: ivar < end_tmp */ + if (lidx >= 0) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, lidx); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); + } + if (lend >= 0) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, lend); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gend); + } + bytecode_add_instruction(bc, OP_LT, 0); + int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + + /* enter loop context for break/continue */ + LoopCtx ctx = {{0}, 0, {0}, 0, g_loop_ctx}; + g_loop_ctx = &ctx; + + /* parse body at increased indent (peek) */ + int body_indent = 0; + size_t look_body = *pos; + if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) { + parse_block(bc, src, len, pos, body_indent); + } else { + /* empty body ok */ + } + + /* continue target: start of increment */ + int cont_label = bc->instr_count; + + /* i = i + 1 */ + int c1 = bytecode_add_constant(bc, make_int(1)); + if (lidx >= 0) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, lidx); + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_ADD, 0); + bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_ADD, 0); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); + } + + /* back edge and patch */ + bytecode_add_instruction(bc, OP_JUMP, loop_start); + + /* end label (after loop) */ + int end_label = bc->instr_count; + bytecode_set_operand(bc, jmp_false, end_label); + + /* patch continue/break jumps */ + for (int bi = 0; bi < ctx.cont_count; ++bi) { + bytecode_set_operand(bc, ctx.continue_jumps[bi], cont_label); + } + for (int bi = 0; bi < ctx.break_count; ++bi) { + bytecode_set_operand(bc, ctx.break_jumps[bi], end_label); + } + g_loop_ctx = ctx.prev; + + free(ivar); + continue; + } else { + /* ===== array iteration: for ivar in ===== */ + /* Evaluate the iterable once and store in a temp */ + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "Expected iterable expression after 'in'"); + free(ivar); + return; + } + char arrname[64]; + snprintf(arrname, sizeof(arrname), "__for_arr_%d", g_temp_counter++); + int larr = -1, garr = -1; + if (g_locals) { + larr = local_add(arrname); + bytecode_add_instruction(bc, OP_STORE_LOCAL, larr); + } else { + garr = sym_index(arrname); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, garr); + } + + /* Compute length once: len(arr) -> store temp */ + if (larr >= 0) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr); + } + bytecode_add_instruction(bc, OP_LEN, 0); + char lenname[64]; + snprintf(lenname, sizeof(lenname), "__for_len_%d", g_temp_counter++); + int llen = -1, glen = -1; + if (g_locals) { + llen = local_add(lenname); + bytecode_add_instruction(bc, OP_STORE_LOCAL, llen); + } else { + glen = sym_index(lenname); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, glen); + } + + /* Index temp: i = 0 */ + int c0 = bytecode_add_constant(bc, make_int(0)); + bytecode_add_instruction(bc, OP_LOAD_CONST, c0); + char iname[64]; + snprintf(iname, sizeof(iname), "__for_i_%d", g_temp_counter++); + int li = -1, gi = -1; + if (g_locals) { + li = local_add(iname); + bytecode_add_instruction(bc, OP_STORE_LOCAL, li); + } else { + gi = sym_index(iname); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); + } + + /* end of header line */ + skip_to_eol(src, len, pos); + + /* loop start label */ + int loop_start = bc->instr_count; + + /* condition: i < len */ + if (li >= 0) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); + } + if (llen >= 0) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, llen); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, glen); + } + bytecode_add_instruction(bc, OP_LT, 0); + int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + + /* element: ivar = arr[i] */ + if (larr >= 0) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr); + } + if (li >= 0) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); + } + bytecode_add_instruction(bc, OP_INDEX_GET, 0); + + /* assign to ivar (local preferred) */ + int ldst = local_find(ivar); + int gdst = -1; + if (ldst < 0) { + if (g_locals) + ldst = local_add(ivar); + else + gdst = sym_index(ivar); + } + if (ldst >= 0) { + bytecode_add_instruction(bc, OP_STORE_LOCAL, ldst); + } else { + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gdst); + } + + /* enter loop context for break/continue */ + LoopCtx ctx = {{0}, 0, {0}, 0, g_loop_ctx}; + g_loop_ctx = &ctx; + + /* parse body at increased indent */ + int body_indent = 0; + size_t look_body = *pos; + if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) { + parse_block(bc, src, len, pos, body_indent); + } else { + /* empty body ok */ + } + + /* continue target: start of increment */ + int cont_label = bc->instr_count; + + /* i = i + 1 */ + int c1 = bytecode_add_constant(bc, make_int(1)); + if (li >= 0) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_ADD, 0); + bytecode_add_instruction(bc, OP_STORE_LOCAL, li); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_ADD, 0); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); + } + + /* back edge and patch */ + bytecode_add_instruction(bc, OP_JUMP, loop_start); + + /* end label (after loop) */ + int end_label = bc->instr_count; + bytecode_set_operand(bc, jmp_false, end_label); + + /* patch continue/break jumps */ + for (int bi = 0; bi < ctx.cont_count; ++bi) { + bytecode_set_operand(bc, ctx.continue_jumps[bi], cont_label); + } + for (int bi = 0; bi < ctx.break_count; ++bi) { + bytecode_set_operand(bc, ctx.break_jumps[bi], end_label); + } + g_loop_ctx = ctx.prev; + + free(ivar); + continue; + } + } + + if (starts_with(src, len, *pos, "if")) { + int end_jumps[64]; + int end_count = 0; + + for (;;) { + /* consume 'if' or 'else if' condition */ + if (starts_with(src, len, *pos, "if")) { + *pos += 2; + } else { + /* for 'else if' we arrive here with *pos already after 'if' */ + } + + /* require at least one space before condition if present */ + skip_spaces(src, len, pos); + if (!emit_expression(bc, src, len, pos)) { + /* no condition -> treat as false */ + int ci = bytecode_add_constant(bc, make_int(0)); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci); + } + + /* Decide between inline single-statement and indented block on next line */ + size_t ppeek = *pos; + /* skip spaces after condition */ + while (ppeek < len && src[ppeek] == ' ') + ppeek++; + int inline_stmt = 0; + if (ppeek < len) { + if (src[ppeek] == '\r' || src[ppeek] == '\n') { + inline_stmt = 0; /* EOL -> no inline body */ + } else if (ppeek + 1 < len && src[ppeek] == '/' && src[ppeek + 1] == '/') { + inline_stmt = 0; /* line comment -> no inline body */ + } else if (ppeek + 1 < len && src[ppeek] == '/' && src[ppeek + 1] == '*') { + inline_stmt = 0; /* block comment at EOL -> no inline body */ + } else { + inline_stmt = 1; /* there's code after condition on same line */ + } + } + + /* conditional jump over this clause's inline/body */ + int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + + if (inline_stmt) { + /* Compile a single inline statement on the same line: + if (cond) */ + *pos = ppeek; + parse_simple_statement(bc, src, len, pos); + /* Skip the inline body when condition is false */ + bytecode_set_operand(bc, jmp_false, bc->instr_count); + /* one-liner form has no else/elseif on the same line; end the chain */ + break; + } + + /* No inline statement on this line: consume up to EOL and parse indented block or else-if/else */ + skip_to_eol(src, len, pos); + + /* parse nested block if next line is indented */ + int next_indent = 0; + size_t look_next = *pos; + if (read_line_start(src, len, &look_next, &next_indent)) { + if (next_indent > current_indent) { + /* parse body at increased indent (let parse_block consume the line) */ + parse_block(bc, src, len, pos, next_indent); + } else { + /* empty body; keep *pos at start of that line */ + } + } else { + /* EOF -> empty body */ + } + + /* after body, unconditionally jump to end of the whole chain */ + int jmp_end = bytecode_add_instruction(bc, OP_JUMP, 0); + if (end_count < (int)(sizeof(end_jumps) / sizeof(end_jumps[0]))) { + end_jumps[end_count++] = jmp_end; + } else { + parser_fail(*pos, "Too many chained else/if clauses"); + return; + } + + /* patch false-jump target to start of next clause (or fallthrough) */ + bytecode_set_operand(bc, jmp_false, bc->instr_count); + + /* look for else or else if at the same indentation */ + size_t look = *pos; + int look_indent = 0; + if (!read_line_start(src, len, &look, &look_indent)) { + /* EOF: break and patch end jumps */ + break; + } + if (look_indent != current_indent) { + /* dedent or deeper indent means no 'else' clause here */ + break; + } + if (starts_with(src, len, look, "else")) { + /* consume 'else' */ + *pos = look + 4; + skip_spaces(src, len, pos); + + if (starts_with(src, len, *pos, "if")) { + /* else if -> consume 'if' token and continue loop to parse condition */ + *pos += 2; + continue; + } else { + /* plain else: parse its block and finish the chain */ + skip_to_eol(src, len, pos); + int else_indent = 0; + size_t look_else = *pos; + if (read_line_start(src, len, &look_else, &else_indent) && else_indent > current_indent) { + /* let parse_block consume the line */ + parse_block(bc, src, len, pos, else_indent); + } else { + /* empty else-body */ + } + /* end of chain after else */ + break; + } + } else { + /* next line is not an else/else if */ + break; + } + } + + /* patch all end-of-clause jumps to the end of the chain */ + for (int i = 0; i < end_count; ++i) { + bytecode_set_operand(bc, end_jumps[i], bc->instr_count); + } + continue; + } + + /* while loop */ + if (starts_with(src, len, *pos, "while")) { + *pos += 5; + skip_spaces(src, len, pos); + + int loop_start = bc->instr_count; + + /* condition */ + if (!emit_expression(bc, src, len, pos)) { + int ci = bytecode_add_constant(bc, make_int(0)); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci); + } + /* end of condition */ + skip_to_eol(src, len, pos); + + /* jump over body if false */ + int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + + /* enter loop context (continue -> condition) */ + LoopCtx ctx = {{0}, 0, {0}, 0, g_loop_ctx}; + g_loop_ctx = &ctx; + + /* parse body at increased indent (peek indent without advancing pos) */ + int body_indent = 0; + size_t look_body = *pos; + if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) { + parse_block(bc, src, len, pos, body_indent); + } else { + /* empty body allowed */ + } + + /* patch pending continues to loop_start (re-evaluate condition) */ + for (int bi = 0; bi < ctx.cont_count; ++bi) { + bytecode_set_operand(bc, ctx.continue_jumps[bi], loop_start); + } + + /* back edge to loop start */ + bytecode_add_instruction(bc, OP_JUMP, loop_start); + + /* end label and patches */ + int end_label = bc->instr_count; + bytecode_set_operand(bc, jmp_false, end_label); + for (int bi = 0; bi < ctx.break_count; ++bi) { + bytecode_set_operand(bc, ctx.break_jumps[bi], end_label); + } + g_loop_ctx = ctx.prev; + continue; + } + + /* try/catch/finally */ + if (starts_with(src, len, *pos, "try")) { + /* consume 'try' */ + *pos += 3; + /* end of header line */ + skip_to_eol(src, len, pos); + + /* Install a handler placeholder; will be patched to catch label (or a rethrow stub) */ + int try_push_idx = bytecode_add_instruction(bc, OP_TRY_PUSH, 0); + + /* parse try body at increased indent (if any) */ + int try_body_indent = 0; + size_t look_try = *pos; + if (read_line_start(src, len, &look_try, &try_body_indent) && try_body_indent > current_indent) { + parse_block(bc, src, len, pos, try_body_indent); + } else { + /* empty try body allowed */ + } + + /* After try body, pop handler for normal (non-exceptional) flow */ + bytecode_add_instruction(bc, OP_TRY_POP, 0); + + /* on normal completion, jump over catch body */ + int jmp_over_catch_finally = bytecode_add_instruction(bc, OP_JUMP, 0); + + /* Optional: catch and/or finally clauses at same indentation */ + int seen_catch = 0; + int seen_finally = 0; + int catch_label = -1; + for (;;) { + size_t look = *pos; + int look_indent = 0; + if (!read_line_start(src, len, &look, &look_indent)) break; /* EOF */ + if (look_indent != current_indent) break; /* different indentation -> stop */ + + if (!seen_catch && starts_with(src, len, look, "catch")) { + /* consume 'catch' */ + *pos = look + 5; + /* optional variable name */ + skip_spaces(src, len, pos); + char *ex_name = NULL; + size_t tmp = *pos; + int have_name = 0; + if (read_identifier_into(src, len, &tmp, &ex_name)) { + *pos = tmp; + have_name = 1; + } + /* end of header line */ + skip_to_eol(src, len, pos); + + /* Mark catch label and patch try handler target */ + catch_label = bc->instr_count; + bytecode_set_operand(bc, try_push_idx, catch_label); + + /* On entering catch, the thrown error is on stack. Bind to name if provided, else pop. */ + if (have_name) { + int lidx = -1, gi = -1; + if (g_locals) { + int existing = local_find(ex_name); + if (existing >= 0) + lidx = existing; + else + lidx = local_add(ex_name); + } else { + gi = sym_index(ex_name); + } + if (lidx >= 0) + bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); + else if (gi >= 0) + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); + else + bytecode_add_instruction(bc, OP_POP, 0); + } else { + bytecode_add_instruction(bc, OP_POP, 0); + } + if (ex_name) free(ex_name); + + /* parse catch body at increased indent (if any) */ + int catch_indent = 0; + size_t look_catch = *pos; + if (read_line_start(src, len, &look_catch, &catch_indent) && catch_indent > current_indent) { + parse_block(bc, src, len, pos, catch_indent); + } else { + /* empty catch body allowed */ + } + seen_catch = 1; + continue; + } + + if (!seen_finally && starts_with(src, len, look, "finally")) { + /* consume 'finally' */ + *pos = look + 7; + /* end of header line */ + skip_to_eol(src, len, pos); + + /* parse finally body at increased indent (if any) */ + int finally_indent = 0; + size_t look_fin = *pos; + if (read_line_start(src, len, &look_fin, &finally_indent) && finally_indent > current_indent) { + parse_block(bc, src, len, pos, finally_indent); + } else { + /* empty finally body allowed */ + } + + seen_finally = 1; + continue; + } + + /* no recognized clause at this indentation */ + break; + } + + /* If no catch clause was present, make handler rethrow */ + if (!seen_catch) { + int rethrow_label = bc->instr_count; + bytecode_set_operand(bc, try_push_idx, rethrow_label); + /* at handler: immediately rethrow the incoming error */ + bytecode_add_instruction(bc, OP_THROW, 0); + } + + /* patch normal-flow jump to here (after catch/finally) */ + bytecode_set_operand(bc, jmp_over_catch_finally, bc->instr_count); + continue; + } + + /* otherwise: simple statement on this line */ + parse_simple_statement(bc, src, len, pos); + } } static Bytecode *compile_minimal(const char *src, size_t len) { - Bytecode *bc = bytecode_new(); - size_t pos = 0; + Bytecode *bc = bytecode_new(); + size_t pos = 0; - /* Refresh namespace alias table for this compilation unit */ - ns_aliases_reset(); - ns_aliases_scan(src, len); + /* Refresh namespace alias table for this compilation unit */ + ns_aliases_reset(); + ns_aliases_scan(src, len); - skip_shebang_if_present(src, len, &pos); + skip_shebang_if_present(src, len, &pos); - /* Allow top-of-file function definitions; do not skip any leading 'fun' line */ - skip_comments(src, len, &pos); - skip_ws(src, len, &pos); + /* Allow top-of-file function definitions; do not skip any leading 'fun' line */ + skip_comments(src, len, &pos); + skip_ws(src, len, &pos); - /* parse the top-level block at indent 0 */ - parse_block(bc, src, len, &pos, 0); + /* parse the top-level block at indent 0 */ + parse_block(bc, src, len, &pos, 0); - bytecode_add_instruction(bc, OP_HALT, 0); - return bc; + bytecode_add_instruction(bc, OP_HALT, 0); + return bc; } Bytecode *parse_file_to_bytecode(const char *path) { - size_t len = 0; - char *src = read_file_all(path, &len); - if (!src) { - fprintf(stderr, "Error: cannot read file: %s\n", path); - return NULL; + size_t len = 0; + char *src = read_file_all(path, &len); + if (!src) { + fprintf(stderr, "Error: cannot read file: %s\n", path); + return NULL; + } + + /* Preprocess includes before compiling */ + char *prep = preprocess_includes(src); + const char *compile_src = prep ? prep : src; + size_t compile_len = strlen(compile_src); + + /* reset error state */ + g_has_error = 0; + g_err_pos = 0; + g_err_msg[0] = '\0'; + g_err_line = 0; + g_err_col = 0; + + /* Set current source path for nested bytecodes to inherit */ + const char *prev_source = g_current_source_path; + g_current_source_path = path; + Bytecode *bc = compile_minimal(compile_src, compile_len); + /* assign debug metadata to module bytecode */ + if (bc) { + if (bc->source_file) free((void *)bc->source_file); + bc->source_file = path ? strdup(path) : strdup(""); + if (bc->name) free((void *)bc->name); + /* derive name from basename of path */ + const char *bn = path ? strrchr(path, '/') : NULL; + const char *base = bn ? bn + 1 : (path ? path : ""); + bc->name = strdup(base); + } + /* restore previous */ + g_current_source_path = prev_source; + + if (g_has_error) { + int line = 1, col = 1; + calc_line_col(compile_src, compile_len, g_err_pos, &line, &col); + g_err_line = line; + g_err_col = col; + + /* Try to locate include context marker preceding error */ + const char *marker = "// __include_begin__: "; + size_t mlen = strlen(marker); + int inner_line = -1; + char inc_path[512]; + inc_path[0] = '\0'; + /* scan backward to find last marker line */ + size_t scan = g_err_pos; + while (scan > 0) { + /* find start of current line */ + size_t ls = scan; + while (ls > 0 && compile_src[ls - 1] != '\n') + ls--; + /* check if this line starts with marker */ + if (ls + mlen <= compile_len && strncmp(compile_src + ls, marker, mlen) == 0) { + /* extract path until end-of-line */ + size_t p = ls + mlen; + size_t pe = p; + while (pe < compile_len && compile_src[pe] != '\n' && (pe - p) < sizeof(inc_path) - 1) + pe++; + memcpy(inc_path, compile_src + p, pe - p); + inc_path[pe - p] = '\0'; + /* compute inner line as number of newlines from (pe+1) to error position */ + int count = 1; + size_t q = (pe < compile_len && compile_src[pe] == '\n') ? (pe + 1) : pe; + while (q < g_err_pos) { + if (compile_src[q] == '\n') count++; + q++; + } + inner_line = count; + break; + } + /* move to previous line */ + if (ls == 0) break; + scan = ls - 1; } - /* Preprocess includes before compiling */ - char *prep = preprocess_includes(src); - const char *compile_src = prep ? prep : src; - size_t compile_len = strlen(compile_src); - - /* reset error state */ - g_has_error = 0; - g_err_pos = 0; - g_err_msg[0] = '\0'; - g_err_line = 0; - g_err_col = 0; - - /* Set current source path for nested bytecodes to inherit */ - const char *prev_source = g_current_source_path; - g_current_source_path = path; - Bytecode *bc = compile_minimal(compile_src, compile_len); - /* assign debug metadata to module bytecode */ - if (bc) { - if (bc->source_file) free((void*)bc->source_file); - bc->source_file = path ? strdup(path) : strdup(""); - if (bc->name) free((void*)bc->name); - /* derive name from basename of path */ - const char *bn = path ? strrchr(path, '/') : NULL; - const char *base = bn ? bn + 1 : (path ? path : ""); - bc->name = strdup(base); - } - /* restore previous */ - g_current_source_path = prev_source; - - if (g_has_error) { - int line = 1, col = 1; - calc_line_col(compile_src, compile_len, g_err_pos, &line, &col); - g_err_line = line; - g_err_col = col; - - /* Try to locate include context marker preceding error */ - const char *marker = "// __include_begin__: "; - size_t mlen = strlen(marker); - int inner_line = -1; - char inc_path[512]; inc_path[0] = '\0'; - /* scan backward to find last marker line */ - size_t scan = g_err_pos; - while (scan > 0) { - /* find start of current line */ - size_t ls = scan; - while (ls > 0 && compile_src[ls - 1] != '\n') ls--; - /* check if this line starts with marker */ - if (ls + mlen <= compile_len && strncmp(compile_src + ls, marker, mlen) == 0) { - /* extract path until end-of-line */ - size_t p = ls + mlen; - size_t pe = p; - while (pe < compile_len && compile_src[pe] != '\n' && (pe - p) < sizeof(inc_path) - 1) pe++; - memcpy(inc_path, compile_src + p, pe - p); - inc_path[pe - p] = '\0'; - /* compute inner line as number of newlines from (pe+1) to error position */ - int count = 1; - size_t q = (pe < compile_len && compile_src[pe] == '\n') ? (pe + 1) : pe; - while (q < g_err_pos) { - if (compile_src[q] == '\n') count++; - q++; - } - inner_line = count; - break; - } - /* move to previous line */ - if (ls == 0) break; - scan = ls - 1; - } - - if (inner_line > 0 && inc_path[0] != '\0') { - fprintf(stderr, "Parse error %s:%d:%d: %s (in %s:%d)\n", - path ? path : "", line, col, g_err_msg, inc_path, inner_line); - } else { - fprintf(stderr, "Parse error %s:%d:%d: %s\n", path ? path : "", line, col, g_err_msg); - } - - if (bc) bytecode_free(bc); - if (prep) free(prep); - free(src); - return NULL; + if (inner_line > 0 && inc_path[0] != '\0') { + fprintf(stderr, "Parse error %s:%d:%d: %s (in %s:%d)\n", + path ? path : "", line, col, g_err_msg, inc_path, inner_line); + } else { + fprintf(stderr, "Parse error %s:%d:%d: %s\n", path ? path : "", line, col, g_err_msg); } + if (bc) bytecode_free(bc); if (prep) free(prep); free(src); - return bc; + return NULL; + } + + if (prep) free(prep); + free(src); + return bc; } Bytecode *parse_string_to_bytecode(const char *source) { - if (!source) { - fprintf(stderr, "Error: null source provided\n"); - return NULL; - } + if (!source) { + fprintf(stderr, "Error: null source provided\n"); + return NULL; + } - /* Preprocess includes before compiling */ - char *prep = preprocess_includes(source); - const char *compile_src = prep ? prep : source; - size_t len = strlen(compile_src); + /* Preprocess includes before compiling */ + char *prep = preprocess_includes(source); + const char *compile_src = prep ? prep : source; + size_t len = strlen(compile_src); - /* reset error state */ - g_has_error = 0; - g_err_pos = 0; - g_err_msg[0] = '\0'; - g_err_line = 0; - g_err_col = 0; + /* reset error state */ + g_has_error = 0; + g_err_pos = 0; + g_err_msg[0] = '\0'; + g_err_line = 0; + g_err_col = 0; - /* Set current source path to for nested bytecodes */ - const char *prev_src = g_current_source_path; - g_current_source_path = NULL; - Bytecode *bc = compile_minimal(compile_src, len); - if (bc) { - if (bc->source_file) free((void*)bc->source_file); - bc->source_file = strdup(""); - if (bc->name) free((void*)bc->name); - bc->name = strdup(""); - } - g_current_source_path = prev_src; + /* Set current source path to for nested bytecodes */ + const char *prev_src = g_current_source_path; + g_current_source_path = NULL; + Bytecode *bc = compile_minimal(compile_src, len); + if (bc) { + if (bc->source_file) free((void *)bc->source_file); + bc->source_file = strdup(""); + if (bc->name) free((void *)bc->name); + bc->name = strdup(""); + } + g_current_source_path = prev_src; - if (g_has_error) { - int line = 1, col = 1; - calc_line_col(compile_src, len, g_err_pos, &line, &col); - g_err_line = line; - g_err_col = col; - if (bc) bytecode_free(bc); - if (prep) free(prep); - return NULL; - } + if (g_has_error) { + int line = 1, col = 1; + calc_line_col(compile_src, len, g_err_pos, &line, &col); + g_err_line = line; + g_err_col = col; + if (bc) bytecode_free(bc); if (prep) free(prep); - return bc; + return NULL; + } + if (prep) free(prep); + return bc; } int parser_last_error(char *msgBuf, unsigned long msgCap, int *outLine, int *outCol) { - if (!g_has_error) return 0; - if (msgBuf && msgCap > 0) { - snprintf(msgBuf, msgCap, "%s", g_err_msg); - } - if (outLine) *outLine = g_err_line; - if (outCol) *outCol = g_err_col; - return 1; + if (!g_has_error) return 0; + if (msgBuf && msgCap > 0) { + snprintf(msgBuf, msgCap, "%s", g_err_msg); + } + if (outLine) *outLine = g_err_line; + if (outCol) *outCol = g_err_col; + return 1; } diff --git a/src/parser_utils.c b/src/parser_utils.c index 477f965..7a97bf1 100644 --- a/src/parser_utils.c +++ b/src/parser_utils.c @@ -7,197 +7,251 @@ * https://opensource.org/license/apache-2-0 */ +#include "parser.h" +#include #include #include #include -#include -#include "parser.h" static char *read_file_all(const char *path, size_t *out_len) { - FILE *f = fopen(path, "rb"); - if (!f) return NULL; - if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return NULL; } - long sz = ftell(f); - if (sz < 0) { fclose(f); return NULL; } - rewind(f); - char *buf = (char*)malloc((size_t)sz + 1); - if (!buf) { fclose(f); return NULL; } - size_t n = fread(buf, 1, (size_t)sz, f); + FILE *f = fopen(path, "rb"); + if (!f) return NULL; + if (fseek(f, 0, SEEK_END) != 0) { fclose(f); - buf[n] = '\0'; - if (out_len) *out_len = n; - return buf; + return NULL; + } + long sz = ftell(f); + if (sz < 0) { + fclose(f); + return NULL; + } + rewind(f); + char *buf = (char *)malloc((size_t)sz + 1); + if (!buf) { + fclose(f); + return NULL; + } + size_t n = fread(buf, 1, (size_t)sz, f); + fclose(f); + buf[n] = '\0'; + if (out_len) *out_len = n; + return buf; } static void skip_ws(const char *src, size_t len, size_t *pos) { - while (*pos < len) { - char c = src[*pos]; - if (c == ' ' || c == '\t' || c == '\r' || c == '\n') { (*pos)++; continue; } - break; + while (*pos < len) { + char c = src[*pos]; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') { + (*pos)++; + continue; } + break; + } } static void skip_line(const char *src, size_t len, size_t *pos) { - while (*pos < len && src[*pos] != '\n') (*pos)++; - if (*pos < len && src[*pos] == '\n') (*pos)++; + while (*pos < len && src[*pos] != '\n') + (*pos)++; + if (*pos < len && src[*pos] == '\n') (*pos)++; } static void skip_comments(const char *src, size_t len, size_t *pos) { - for (;;) { - skip_ws(src, len, pos); - if (*pos + 1 < len && src[*pos] == '/' && src[*pos + 1] == '/') { - *pos += 2; - skip_line(src, len, pos); - continue; - } - if (*pos + 1 < len && src[*pos] == '/' && src[*pos + 1] == '*') { - *pos += 2; - while (*pos + 1 < len && !(src[*pos] == '*' && src[*pos + 1] == '/')) (*pos)++; - if (*pos + 1 < len) *pos += 2; - continue; - } - break; + for (;;) { + skip_ws(src, len, pos); + if (*pos + 1 < len && src[*pos] == '/' && src[*pos + 1] == '/') { + *pos += 2; + skip_line(src, len, pos); + continue; } + if (*pos + 1 < len && src[*pos] == '/' && src[*pos + 1] == '*') { + *pos += 2; + while (*pos + 1 < len && !(src[*pos] == '*' && src[*pos + 1] == '/')) + (*pos)++; + if (*pos + 1 < len) *pos += 2; + continue; + } + break; + } } static int starts_with(const char *src, size_t len, size_t pos, const char *kw) { - size_t klen = strlen(kw); - if (pos + klen > len) return 0; - return strncmp(src + pos, kw, klen) == 0; + size_t klen = strlen(kw); + if (pos + klen > len) return 0; + return strncmp(src + pos, kw, klen) == 0; } static void skip_shebang_if_present(const char *src, size_t len, size_t *pos) { - if (*pos == 0 && starts_with(src, len, *pos, "#!")) { - skip_line(src, len, pos); - } + if (*pos == 0 && starts_with(src, len, *pos, "#!")) { + skip_line(src, len, pos); + } } static void skip_identifier(const char *src, size_t len, size_t *pos) { - size_t p = *pos; - if (p < len && (isalpha((unsigned char)src[p]) || src[p] == '_')) { - p++; - while (p < len && (isalnum((unsigned char)src[p]) || src[p] == '_')) p++; - } - *pos = p; + size_t p = *pos; + if (p < len && (isalpha((unsigned char)src[p]) || src[p] == '_')) { + p++; + while (p < len && (isalnum((unsigned char)src[p]) || src[p] == '_')) + p++; + } + *pos = p; } static int consume_char(const char *src, size_t len, size_t *pos, char expected) { - skip_ws(src, len, pos); - if (*pos < len && src[*pos] == expected) { (*pos)++; return 1; } - return 0; + skip_ws(src, len, pos); + if (*pos < len && src[*pos] == expected) { + (*pos)++; + return 1; + } + return 0; } static char *parse_string_literal_any_quote(const char *src, size_t len, size_t *pos) { - skip_ws(src, len, pos); - if (*pos >= len) return NULL; - char quote = src[*pos]; - if (quote != '"' && quote != '\'') return NULL; - (*pos)++; // skip opening quote - size_t cap = 64, out_len = 0; - char *out = (char*)malloc(cap); - if (!out) return NULL; - while (*pos < len) { - char c = src[*pos]; - if (c == quote) { (*pos)++; break; } - if (c == '\\') { - (*pos)++; - if (*pos >= len) break; - char e = src[*pos]; - switch (e) { - case 'n': c = '\n'; break; - case 'r': c = '\r'; break; - case 't': c = '\t'; break; - case '\\': c = '\\'; break; - case '"': c = '"'; break; - case '\'': c = '\''; break; - default: c = e; break; - } - } - if (out_len + 1 >= cap) { - cap *= 2; - char *tmp = (char*)realloc(out, cap); - if (!tmp) { free(out); return NULL; } - out = tmp; - } - out[out_len++] = c; - (*pos)++; + skip_ws(src, len, pos); + if (*pos >= len) return NULL; + char quote = src[*pos]; + if (quote != '"' && quote != '\'') return NULL; + (*pos)++; // skip opening quote + size_t cap = 64, out_len = 0; + char *out = (char *)malloc(cap); + if (!out) return NULL; + while (*pos < len) { + char c = src[*pos]; + if (c == quote) { + (*pos)++; + break; + } + if (c == '\\') { + (*pos)++; + if (*pos >= len) break; + char e = src[*pos]; + switch (e) { + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case '\\': + c = '\\'; + break; + case '"': + c = '"'; + break; + case '\'': + c = '\''; + break; + default: + c = e; + break; + } } if (out_len + 1 >= cap) { - char *tmp = (char*)realloc(out, cap + 1); - if (!tmp) { free(out); return NULL; } - out = tmp; + cap *= 2; + char *tmp = (char *)realloc(out, cap); + if (!tmp) { + free(out); + return NULL; + } + out = tmp; } - out[out_len] = '\0'; - return out; + out[out_len++] = c; + (*pos)++; + } + if (out_len + 1 >= cap) { + char *tmp = (char *)realloc(out, cap + 1); + if (!tmp) { + free(out); + return NULL; + } + out = tmp; + } + out[out_len] = '\0'; + return out; } /* === Helpers for identifiers, numbers, booleans, and globals === */ static void skip_spaces(const char *src, size_t len, size_t *pos) { - while (*pos < len) { - char c = src[*pos]; - if (c == ' ' || c == '\t' || c == '\r') { (*pos)++; continue; } - break; + while (*pos < len) { + char c = src[*pos]; + if (c == ' ' || c == '\t' || c == '\r') { + (*pos)++; + continue; } + break; + } } static int read_identifier_into(const char *src, size_t len, size_t *pos, char **out_name) { - size_t p = *pos; - if (p < len && (isalpha((unsigned char)src[p]) || src[p] == '_')) { - size_t start = p; - p++; - while (p < len && (isalnum((unsigned char)src[p]) || src[p] == '_')) p++; - size_t n = p - start; - char *name = (char*)malloc(n + 1); - if (!name) return 0; - memcpy(name, src + start, n); - name[n] = '\0'; - *pos = p; - *out_name = name; - return 1; - } - return 0; + size_t p = *pos; + if (p < len && (isalpha((unsigned char)src[p]) || src[p] == '_')) { + size_t start = p; + p++; + while (p < len && (isalnum((unsigned char)src[p]) || src[p] == '_')) + p++; + size_t n = p - start; + char *name = (char *)malloc(n + 1); + if (!name) return 0; + memcpy(name, src + start, n); + name[n] = '\0'; + *pos = p; + *out_name = name; + return 1; + } + return 0; } static uint64_t parse_int_literal_value(const char *src, size_t len, size_t *pos, int *ok) { - size_t p = *pos; - skip_spaces(src, len, &p); - int sign = 1; - if (p < len && (src[p] == '+' || src[p] == '-')) { - if (src[p] == '-') sign = -1; - p++; - } - if (p >= len) { *ok = 0; return 0; } + size_t p = *pos; + skip_spaces(src, len, &p); + int sign = 1; + if (p < len && (src[p] == '+' || src[p] == '-')) { + if (src[p] == '-') sign = -1; + p++; + } + if (p >= len) { + *ok = 0; + return 0; + } - /* Hexadecimal: 0x... or 0X... */ - if ((p + 1) < len && src[p] == '0' && (src[p + 1] == 'x' || src[p + 1] == 'X')) { - p += 2; - if (p >= len || !isxdigit((unsigned char)src[p])) { *ok = 0; return 0; } - uint64_t val = 0; - while (p < len && isxdigit((unsigned char)src[p])) { - char c = src[p]; - int d = (c >= '0' && c <= '9') ? (c - '0') - : (c >= 'a' && c <= 'f') ? (c - 'a' + 10) - : (c >= 'A' && c <= 'F') ? (c - 'A' + 10) - : 0; - val = (val << 4) + (uint64_t)d; - p++; - } - *pos = p; - *ok = 1; - return (uint64_t)((int64_t)sign * (int64_t)val); + /* Hexadecimal: 0x... or 0X... */ + if ((p + 1) < len && src[p] == '0' && (src[p + 1] == 'x' || src[p + 1] == 'X')) { + p += 2; + if (p >= len || !isxdigit((unsigned char)src[p])) { + *ok = 0; + return 0; } - - /* Decimal fallback */ - if (!isdigit((unsigned char)src[p])) { *ok = 0; return 0; } uint64_t val = 0; - while (p < len && isdigit((unsigned char)src[p])) { - val = val * 10 + (uint64_t)(src[p] - '0'); - p++; + while (p < len && isxdigit((unsigned char)src[p])) { + char c = src[p]; + int d = (c >= '0' && c <= '9') ? (c - '0') + : (c >= 'a' && c <= 'f') ? (c - 'a' + 10) + : (c >= 'A' && c <= 'F') ? (c - 'A' + 10) + : 0; + val = (val << 4) + (uint64_t)d; + p++; } *pos = p; *ok = 1; return (uint64_t)((int64_t)sign * (int64_t)val); + } + + /* Decimal fallback */ + if (!isdigit((unsigned char)src[p])) { + *ok = 0; + return 0; + } + uint64_t val = 0; + while (p < len && isdigit((unsigned char)src[p])) { + val = val * 10 + (uint64_t)(src[p] - '0'); + p++; + } + *pos = p; + *ok = 1; + return (uint64_t)((int64_t)sign * (int64_t)val); } /* === Include preprocessor === @@ -209,549 +263,635 @@ static uint64_t parse_int_literal_value(const char *src, size_t len, size_t *pos */ static void *xrealloc(void *ptr, size_t newcap) { - void *np = realloc(ptr, newcap); - return np; + void *np = realloc(ptr, newcap); + return np; } typedef struct { - char *buf; - size_t len; - size_t cap; + char *buf; + size_t len; + size_t cap; } StrBuf; static void sb_init(StrBuf *sb) { - sb->buf = (char*)malloc(256); - sb->cap = sb->buf ? 256 : 0; - sb->len = 0; - if (sb->buf) sb->buf[0] = '\0'; + sb->buf = (char *)malloc(256); + sb->cap = sb->buf ? 256 : 0; + sb->len = 0; + if (sb->buf) sb->buf[0] = '\0'; } static void sb_reserve(StrBuf *sb, size_t need) { - if (need <= sb->cap) return; - size_t nc = sb->cap ? sb->cap : 256; - while (nc < need) nc *= 2; - char *nb = (char*)xrealloc(sb->buf, nc); - if (!nb) return; - sb->buf = nb; - sb->cap = nc; + if (need <= sb->cap) return; + size_t nc = sb->cap ? sb->cap : 256; + while (nc < need) + nc *= 2; + char *nb = (char *)xrealloc(sb->buf, nc); + if (!nb) return; + sb->buf = nb; + sb->cap = nc; } static void sb_append_n(StrBuf *sb, const char *s, size_t n) { - if (n == 0) return; - sb_reserve(sb, sb->len + n + 1); - if (!sb->buf) return; - memcpy(sb->buf + sb->len, s, n); - sb->len += n; - sb->buf[sb->len] = '\0'; + if (n == 0) return; + sb_reserve(sb, sb->len + n + 1); + if (!sb->buf) return; + memcpy(sb->buf + sb->len, s, n); + sb->len += n; + sb->buf[sb->len] = '\0'; } static void sb_append(StrBuf *sb, const char *s) { - sb_append_n(sb, s, strlen(s)); + sb_append_n(sb, s, strlen(s)); } static void sb_append_ch(StrBuf *sb, char c) { - sb_reserve(sb, sb->len + 2); - if (!sb->buf) return; - sb->buf[sb->len++] = c; - sb->buf[sb->len] = '\0'; + sb_reserve(sb, sb->len + 2); + if (!sb->buf) return; + sb->buf[sb->len++] = c; + sb->buf[sb->len] = '\0'; } /* ---- Export collection for include-as namespaces ---- */ typedef struct { - char **names; - int count; - int cap; + char **names; + int count; + int cap; } NameList; static void nl_init(NameList *nl) { - nl->names = NULL; - nl->count = 0; - nl->cap = 0; + nl->names = NULL; + nl->count = 0; + nl->cap = 0; } static void nl_add(NameList *nl, const char *name) { - if (!name || !name[0]) return; - if (nl->count >= nl->cap) { - int ncap = nl->cap ? nl->cap * 2 : 8; - char **nn = (char**)realloc(nl->names, (size_t)ncap * sizeof(char*)); - if (!nn) return; - nl->names = nn; - nl->cap = ncap; - } - nl->names[nl->count++] = strdup(name); + if (!name || !name[0]) return; + if (nl->count >= nl->cap) { + int ncap = nl->cap ? nl->cap * 2 : 8; + char **nn = (char **)realloc(nl->names, (size_t)ncap * sizeof(char *)); + if (!nn) return; + nl->names = nn; + nl->cap = ncap; + } + nl->names[nl->count++] = strdup(name); } static void nl_free(NameList *nl) { - if (!nl) return; - for (int i = 0; i < nl->count; ++i) free(nl->names[i]); - free(nl->names); - nl->names = NULL; - nl->count = nl->cap = 0; + if (!nl) return; + for (int i = 0; i < nl->count; ++i) + free(nl->names[i]); + free(nl->names); + nl->names = NULL; + nl->count = nl->cap = 0; } /* Collect top-level (indent=0) exported symbols: function and class names. Ignores lines inside comments/strings and ignores nested indent. */ static void collect_exports_top_level(const char *text, NameList *out) { - if (!text || !out) return; - size_t len = strlen(text); - int in_line = 0, in_block = 0, in_sq = 0, in_dq = 0, esc = 0; - int bol = 1; - for (size_t i = 0; i < len; ) { - char c = text[i]; + if (!text || !out) return; + size_t len = strlen(text); + int in_line = 0, in_block = 0, in_sq = 0, in_dq = 0, esc = 0; + int bol = 1; + for (size_t i = 0; i < len;) { + char c = text[i]; - if (in_line) { - if (c == '\n') { in_line = 0; bol = 1; } - else { bol = 0; } - i++; - continue; - } - if (in_block) { - if (c == '*' && (i + 1) < len && text[i + 1] == '/') { - i += 2; - bol = 0; - in_block = 0; - continue; - } - bol = (c == '\n'); - i++; - continue; - } - if (in_sq) { - if (!esc && c == '\\') { esc = 1; i++; bol = 0; continue; } - if (!esc && c == '\'') { in_sq = 0; } - esc = 0; - bol = (c == '\n'); - i++; - continue; - } - if (in_dq) { - if (!esc && c == '\\') { esc = 1; i++; bol = 0; continue; } - if (!esc && c == '"') { in_dq = 0; } - esc = 0; - bol = (c == '\n'); - i++; - continue; - } - - if (c == '/' && (i + 1) < len && text[i + 1] == '/') { - in_line = 1; - bol = 0; - i += 2; - continue; - } - if (c == '/' && (i + 1) < len && text[i + 1] == '*') { - in_block = 1; - bol = 0; - i += 2; - continue; - } - if (c == '\'') { in_sq = 1; bol = 0; i++; continue; } - if (c == '"') { in_dq = 1; bol = 0; i++; continue; } - - if (bol) { - /* Compute leading spaces to filter out indented constructs */ - size_t j = i; - int spaces = 0; - while (j < len && text[j] == ' ') { spaces++; j++; } - if (j < len && text[j] == '\t') { - /* tabs not allowed for indentation; treat as not top-level */ - bol = 0; - i = j + 1; - continue; - } - /* Only consider top-level (indent == 0) */ - if (spaces == 0) { - /* Check for 'fun ' or 'class ' */ - const char *kw1 = "fun"; - const char *kw2 = "class"; - if (j + 3 <= len && strncmp(text + j, kw1, 3) == 0 && (j + 3 == len || isspace((unsigned char)text[j + 3]))) { - size_t p = j + 3; - while (p < len && (text[p] == ' ' || text[p] == '\t')) p++; - /* read identifier */ - size_t start = p; - if (p < len && (isalpha((unsigned char)text[p]) || text[p] == '_')) { - p++; - while (p < len && (isalnum((unsigned char)text[p]) || text[p] == '_')) p++; - size_t n = p - start; - if (n > 0) { - char tmp[256]; - size_t copy = (n < sizeof(tmp) - 1) ? n : (sizeof(tmp) - 1); - memcpy(tmp, text + start, copy); - tmp[copy] = '\0'; - nl_add(out, tmp); - } - } - } else if (j + 5 <= len && strncmp(text + j, kw2, 5) == 0 && (j + 5 == len || isspace((unsigned char)text[j + 5]))) { - size_t p = j + 5; - while (p < len && (text[p] == ' ' || text[p] == '\t')) p++; - /* read identifier */ - size_t start = p; - if (p < len && (isalpha((unsigned char)text[p]) || text[p] == '_')) { - p++; - while (p < len && (isalnum((unsigned char)text[p]) || text[p] == '_')) p++; - size_t n = p - start; - if (n > 0) { - char tmp[256]; - size_t copy = (n < sizeof(tmp) - 1) ? n : (sizeof(tmp) - 1); - memcpy(tmp, text + start, copy); - tmp[copy] = '\0'; - nl_add(out, tmp); - } - } - } - } - } - - /* move forward one char */ - bol = (c == '\n'); - i++; + if (in_line) { + if (c == '\n') { + in_line = 0; + bol = 1; + } else { + bol = 0; + } + i++; + continue; } + if (in_block) { + if (c == '*' && (i + 1) < len && text[i + 1] == '/') { + i += 2; + bol = 0; + in_block = 0; + continue; + } + bol = (c == '\n'); + i++; + continue; + } + if (in_sq) { + if (!esc && c == '\\') { + esc = 1; + i++; + bol = 0; + continue; + } + if (!esc && c == '\'') { + in_sq = 0; + } + esc = 0; + bol = (c == '\n'); + i++; + continue; + } + if (in_dq) { + if (!esc && c == '\\') { + esc = 1; + i++; + bol = 0; + continue; + } + if (!esc && c == '"') { + in_dq = 0; + } + esc = 0; + bol = (c == '\n'); + i++; + continue; + } + + if (c == '/' && (i + 1) < len && text[i + 1] == '/') { + in_line = 1; + bol = 0; + i += 2; + continue; + } + if (c == '/' && (i + 1) < len && text[i + 1] == '*') { + in_block = 1; + bol = 0; + i += 2; + continue; + } + if (c == '\'') { + in_sq = 1; + bol = 0; + i++; + continue; + } + if (c == '"') { + in_dq = 1; + bol = 0; + i++; + continue; + } + + if (bol) { + /* Compute leading spaces to filter out indented constructs */ + size_t j = i; + int spaces = 0; + while (j < len && text[j] == ' ') { + spaces++; + j++; + } + if (j < len && text[j] == '\t') { + /* tabs not allowed for indentation; treat as not top-level */ + bol = 0; + i = j + 1; + continue; + } + /* Only consider top-level (indent == 0) */ + if (spaces == 0) { + /* Check for 'fun ' or 'class ' */ + const char *kw1 = "fun"; + const char *kw2 = "class"; + if (j + 3 <= len && strncmp(text + j, kw1, 3) == 0 && (j + 3 == len || isspace((unsigned char)text[j + 3]))) { + size_t p = j + 3; + while (p < len && (text[p] == ' ' || text[p] == '\t')) + p++; + /* read identifier */ + size_t start = p; + if (p < len && (isalpha((unsigned char)text[p]) || text[p] == '_')) { + p++; + while (p < len && (isalnum((unsigned char)text[p]) || text[p] == '_')) + p++; + size_t n = p - start; + if (n > 0) { + char tmp[256]; + size_t copy = (n < sizeof(tmp) - 1) ? n : (sizeof(tmp) - 1); + memcpy(tmp, text + start, copy); + tmp[copy] = '\0'; + nl_add(out, tmp); + } + } + } else if (j + 5 <= len && strncmp(text + j, kw2, 5) == 0 && (j + 5 == len || isspace((unsigned char)text[j + 5]))) { + size_t p = j + 5; + while (p < len && (text[p] == ' ' || text[p] == '\t')) + p++; + /* read identifier */ + size_t start = p; + if (p < len && (isalpha((unsigned char)text[p]) || text[p] == '_')) { + p++; + while (p < len && (isalnum((unsigned char)text[p]) || text[p] == '_')) + p++; + size_t n = p - start; + if (n > 0) { + char tmp[256]; + size_t copy = (n < sizeof(tmp) - 1) ? n : (sizeof(tmp) - 1); + memcpy(tmp, text + start, copy); + tmp[copy] = '\0'; + nl_add(out, tmp); + } + } + } + } + } + + /* move forward one char */ + bol = (c == '\n'); + i++; + } } static char *preprocess_includes_internal(const char *src, int depth) { - if (!src) return NULL; - if (depth > 64) { - fprintf(stderr, "Include error: include nesting too deep\n"); - return strdup(""); - } + if (!src) return NULL; + if (depth > 64) { + fprintf(stderr, "Include error: include nesting too deep\n"); + return strdup(""); + } - /* Build-time default, can be overridden by compiler define -DDEFAULT_LIB_DIR=".../" */ - #ifndef DEFAULT_LIB_DIR - #define DEFAULT_LIB_DIR "/usr/share/fun/lib/" - #endif +/* Build-time default, can be overridden by compiler define -DDEFAULT_LIB_DIR=".../" */ +#ifndef DEFAULT_LIB_DIR +#define DEFAULT_LIB_DIR "/usr/share/fun/lib/" +#endif - const char *env_lib = getenv("FUN_LIB_DIR"); - size_t len = strlen(src); - StrBuf out; - sb_init(&out); - int in_line = 0, in_block = 0, in_sq = 0, in_dq = 0, esc = 0; - int bol = 1; /* beginning of line */ + const char *env_lib = getenv("FUN_LIB_DIR"); + size_t len = strlen(src); + StrBuf out; + sb_init(&out); + int in_line = 0, in_block = 0, in_sq = 0, in_dq = 0, esc = 0; + int bol = 1; /* beginning of line */ - for (size_t i = 0; i < len; ) { - char c = src[i]; + for (size_t i = 0; i < len;) { + char c = src[i]; - /* Detect include directive at BOL, outside comments/strings */ - if (bol && !in_block && !in_sq && !in_dq) { - size_t j = i; - /* skip leading spaces/tabs */ - while (j < len && (src[j] == ' ' || src[j] == '\t')) j++; - size_t k = j; - if (k < len && src[k] == '#') k++; - const char *kw = "include"; - size_t kwlen = 7; - if (k + kwlen <= len && strncmp(src + k, kw, kwlen) == 0) { - k += kwlen; - /* next must be space/tab or delimiter */ - while (k < len && (src[k] == ' ' || src[k] == '\t')) k++; - if (k < len && (src[k] == '"' || src[k] == '<')) { - char opener = src[k]; - char closer = (opener == '"') ? '"' : '>'; - k++; - size_t path_start = k; - while (k < len && src[k] != closer) k++; - if (k < len && src[k] == closer) { - size_t path_len = k - path_start; - char *path = (char*)malloc(path_len + 1); - if (path) { - memcpy(path, src + path_start, path_len); - path[path_len] = '\0'; + /* Detect include directive at BOL, outside comments/strings */ + if (bol && !in_block && !in_sq && !in_dq) { + size_t j = i; + /* skip leading spaces/tabs */ + while (j < len && (src[j] == ' ' || src[j] == '\t')) + j++; + size_t k = j; + if (k < len && src[k] == '#') k++; + const char *kw = "include"; + size_t kwlen = 7; + if (k + kwlen <= len && strncmp(src + k, kw, kwlen) == 0) { + k += kwlen; + /* next must be space/tab or delimiter */ + while (k < len && (src[k] == ' ' || src[k] == '\t')) + k++; + if (k < len && (src[k] == '"' || src[k] == '<')) { + char opener = src[k]; + char closer = (opener == '"') ? '"' : '>'; + k++; + size_t path_start = k; + while (k < len && src[k] != closer) + k++; + if (k < len && src[k] == closer) { + size_t path_len = k - path_start; + char *path = (char *)malloc(path_len + 1); + if (path) { + memcpy(path, src + path_start, path_len); + path[path_len] = '\0'; - /* parse optional 'as ' then advance to end of line */ - k++; - char ns[64]; ns[0] = '\0'; - /* skip spaces/tabs */ - size_t ap = k; - while (ap < len && (src[ap] == ' ' || src[ap] == '\t')) ap++; - /* optional 'as' */ - const char *askw = "as"; - if (ap + 2 <= len && strncmp(src + ap, askw, 2) == 0 && (ap + 2 == len || isspace((unsigned char)src[ap + 2]))) { - ap += 2; - while (ap < len && (src[ap] == ' ' || src[ap] == '\t')) ap++; - /* read identifier [A-Za-z_][A-Za-z0-9_]* */ - size_t start = ap; - if (ap < len && (isalpha((unsigned char)src[ap]) || src[ap] == '_')) { - ap++; - while (ap < len && (isalnum((unsigned char)src[ap]) || src[ap] == '_')) ap++; - size_t n = ap - start; - size_t copy = (n < sizeof(ns) - 1) ? n : (sizeof(ns) - 1); - memcpy(ns, src + start, copy); - ns[copy] = '\0'; - } - /* ignore anything else on line */ - } - /* advance to end of line */ - k = ap; - while (k < len && src[k] != '\n') k++; - if (k < len && src[k] == '\n') k++; - - /* resolve file path; for <...> try FUN_LIB_DIR first, then default */ - char resolved[1024]; - char resolved2[1024]; - size_t inc_len = 0; - char *inc = NULL; - - if (opener == '<') { - /* Angle-bracket include resolution order: - * 1) FUN_LIB_DIR (env), respecting '/' or '\' endings - * 2) DEFAULT_LIB_DIR (compile-time define) - * 3) "lib/" under current working directory (developer fallback) - * - * Always assign 'resolved' to the last attempted candidate so errors are informative. - */ - resolved[0] = '\0'; - - /* 1) FUN_LIB_DIR */ - if (env_lib && env_lib[0]) { - size_t elen = strlen(env_lib); - char last = env_lib[elen ? (elen - 1) : 0]; - int needs_sep = !(last == '/' || last == '\\'); - char sep = (last == '\\') ? '\\' : '/'; - if (needs_sep) - snprintf(resolved, sizeof(resolved), "%s%c%s", env_lib, sep, path); - else - snprintf(resolved, sizeof(resolved), "%s%s", env_lib, path); - inc = read_file_all(resolved, &inc_len); - } - - /* 2) DEFAULT_LIB_DIR */ - if (!inc) { - snprintf(resolved, sizeof(resolved), "%s%s", DEFAULT_LIB_DIR, path); - inc = read_file_all(resolved, &inc_len); - } - - /* 3) project-local dev fallback: lib/ */ - if (!inc) { - snprintf(resolved, sizeof(resolved), "lib/%s", path); - inc = read_file_all(resolved, &inc_len); - } - } else { - /* quoted include: relative path (cwd) */ - snprintf(resolved, sizeof(resolved), "%s", path); - inc = read_file_all(resolved, &inc_len); - } - free(path); - - if (!inc) { - fprintf(stderr, "Include error: cannot read '%s'\n", resolved[0] ? resolved : "(unresolved)"); - sb_append(&out, "// include error: cannot read "); - sb_append(&out, resolved[0] ? resolved : "(unresolved)"); - sb_append(&out, "\n"); - } else { - /* Strip optional UTF-8 BOM and top-of-file shebang from included text before preprocessing */ - const char *startp = inc; - size_t off = 0; - if ((unsigned char)inc[0] == 0xEF && (unsigned char)inc[1] == 0xBB && (unsigned char)inc[2] == 0xBF) { - off = 3; - } - startp = inc + off; - if (startp[0] == '#' && startp[1] == '!') { - /* skip until end of line, handling CR, LF, CRLF */ - const char *q = startp; - while (*q && *q != '\n' && *q != '\r') q++; - if (*q == '\r') { q++; if (*q == '\n') q++; } - else if (*q == '\n') { q++; } - startp = q; - } - char *inc_clean = strdup(startp); - char *exp = preprocess_includes_internal(inc_clean, depth + 1); - free(inc); - free(inc_clean); - if (exp) { - /* If alias requested, initialize namespace map before including content */ - if (ns[0] != '\0') { - /* Announce alias so the parser can treat dot-call without implicit 'this' */ - sb_append(&out, "// __ns_alias__: "); - sb_append(&out, ns); - sb_append(&out, "\n"); - - sb_append(&out, ns); - sb_append(&out, " = {}\n"); - } - - /* mark file origin for better error messages */ - sb_append(&out, "// __include_begin__: "); - sb_append(&out, resolved); - if (ns[0] != '\0') { - sb_append(&out, " as "); - sb_append(&out, ns); - } - sb_append(&out, "\n"); - - /* append expanded included content */ - sb_append(&out, exp); - /* ensure included chunk ends with newline to preserve line structure */ - if (out.len == 0 || out.buf[out.len - 1] != '\n') sb_append_ch(&out, '\n'); - - /* If alias is present, export top-level fun/class into alias map */ - if (ns[0] != '\0') { - NameList nl; nl_init(&nl); - collect_exports_top_level(exp, &nl); - for (int ei = 0; ei < nl.count; ++ei) { - sb_append(&out, ns); - sb_append(&out, "."); - sb_append(&out, nl.names[ei]); - sb_append(&out, " = "); - sb_append(&out, nl.names[ei]); - sb_append(&out, "\n"); - } - nl_free(&nl); - } - - free(exp); - } - } - - /* move input pointer to start of next line */ - i = k; - bol = 1; - continue; - } - } + /* parse optional 'as ' then advance to end of line */ + k++; + char ns[64]; + ns[0] = '\0'; + /* skip spaces/tabs */ + size_t ap = k; + while (ap < len && (src[ap] == ' ' || src[ap] == '\t')) + ap++; + /* optional 'as' */ + const char *askw = "as"; + if (ap + 2 <= len && strncmp(src + ap, askw, 2) == 0 && (ap + 2 == len || isspace((unsigned char)src[ap + 2]))) { + ap += 2; + while (ap < len && (src[ap] == ' ' || src[ap] == '\t')) + ap++; + /* read identifier [A-Za-z_][A-Za-z0-9_]* */ + size_t start = ap; + if (ap < len && (isalpha((unsigned char)src[ap]) || src[ap] == '_')) { + ap++; + while (ap < len && (isalnum((unsigned char)src[ap]) || src[ap] == '_')) + ap++; + size_t n = ap - start; + size_t copy = (n < sizeof(ns) - 1) ? n : (sizeof(ns) - 1); + memcpy(ns, src + start, copy); + ns[copy] = '\0'; } + /* ignore anything else on line */ + } + /* advance to end of line */ + k = ap; + while (k < len && src[k] != '\n') + k++; + if (k < len && src[k] == '\n') k++; + + /* resolve file path; for <...> try FUN_LIB_DIR first, then default */ + char resolved[1024]; + char resolved2[1024]; + size_t inc_len = 0; + char *inc = NULL; + + if (opener == '<') { + /* Angle-bracket include resolution order: + * 1) FUN_LIB_DIR (env), respecting '/' or '\' endings + * 2) DEFAULT_LIB_DIR (compile-time define) + * 3) "lib/" under current working directory (developer fallback) + * + * Always assign 'resolved' to the last attempted candidate so errors are informative. + */ + resolved[0] = '\0'; + + /* 1) FUN_LIB_DIR */ + if (env_lib && env_lib[0]) { + size_t elen = strlen(env_lib); + char last = env_lib[elen ? (elen - 1) : 0]; + int needs_sep = !(last == '/' || last == '\\'); + char sep = (last == '\\') ? '\\' : '/'; + if (needs_sep) + snprintf(resolved, sizeof(resolved), "%s%c%s", env_lib, sep, path); + else + snprintf(resolved, sizeof(resolved), "%s%s", env_lib, path); + inc = read_file_all(resolved, &inc_len); + } + + /* 2) DEFAULT_LIB_DIR */ + if (!inc) { + snprintf(resolved, sizeof(resolved), "%s%s", DEFAULT_LIB_DIR, path); + inc = read_file_all(resolved, &inc_len); + } + + /* 3) project-local dev fallback: lib/ */ + if (!inc) { + snprintf(resolved, sizeof(resolved), "lib/%s", path); + inc = read_file_all(resolved, &inc_len); + } + } else { + /* quoted include: relative path (cwd) */ + snprintf(resolved, sizeof(resolved), "%s", path); + inc = read_file_all(resolved, &inc_len); + } + free(path); + + if (!inc) { + fprintf(stderr, "Include error: cannot read '%s'\n", resolved[0] ? resolved : "(unresolved)"); + sb_append(&out, "// include error: cannot read "); + sb_append(&out, resolved[0] ? resolved : "(unresolved)"); + sb_append(&out, "\n"); + } else { + /* Strip optional UTF-8 BOM and top-of-file shebang from included text before preprocessing */ + const char *startp = inc; + size_t off = 0; + if ((unsigned char)inc[0] == 0xEF && (unsigned char)inc[1] == 0xBB && (unsigned char)inc[2] == 0xBF) { + off = 3; + } + startp = inc + off; + if (startp[0] == '#' && startp[1] == '!') { + /* skip until end of line, handling CR, LF, CRLF */ + const char *q = startp; + while (*q && *q != '\n' && *q != '\r') + q++; + if (*q == '\r') { + q++; + if (*q == '\n') q++; + } else if (*q == '\n') { + q++; + } + startp = q; + } + char *inc_clean = strdup(startp); + char *exp = preprocess_includes_internal(inc_clean, depth + 1); + free(inc); + free(inc_clean); + if (exp) { + /* If alias requested, initialize namespace map before including content */ + if (ns[0] != '\0') { + /* Announce alias so the parser can treat dot-call without implicit 'this' */ + sb_append(&out, "// __ns_alias__: "); + sb_append(&out, ns); + sb_append(&out, "\n"); + + sb_append(&out, ns); + sb_append(&out, " = {}\n"); + } + + /* mark file origin for better error messages */ + sb_append(&out, "// __include_begin__: "); + sb_append(&out, resolved); + if (ns[0] != '\0') { + sb_append(&out, " as "); + sb_append(&out, ns); + } + sb_append(&out, "\n"); + + /* append expanded included content */ + sb_append(&out, exp); + /* ensure included chunk ends with newline to preserve line structure */ + if (out.len == 0 || out.buf[out.len - 1] != '\n') sb_append_ch(&out, '\n'); + + /* If alias is present, export top-level fun/class into alias map */ + if (ns[0] != '\0') { + NameList nl; + nl_init(&nl); + collect_exports_top_level(exp, &nl); + for (int ei = 0; ei < nl.count; ++ei) { + sb_append(&out, ns); + sb_append(&out, "."); + sb_append(&out, nl.names[ei]); + sb_append(&out, " = "); + sb_append(&out, nl.names[ei]); + sb_append(&out, "\n"); + } + nl_free(&nl); + } + + free(exp); + } + } + + /* move input pointer to start of next line */ + i = k; + bol = 1; + continue; } + } } - - /* normal stateful copy with comment/string tracking */ - if (in_line) { - sb_append_ch(&out, c); - if (c == '\n') { in_line = 0; bol = 1; } else { bol = 0; } - i++; - continue; - } - if (in_block) { - sb_append_ch(&out, c); - if (c == '*' && (i + 1) < len && src[i + 1] == '/') { - sb_append_ch(&out, '/'); - i += 2; - bol = 0; - in_block = 0; - continue; - } - bol = (c == '\n') ? 1 : 0; - i++; - continue; - } - if (in_sq) { - sb_append_ch(&out, c); - if (!esc && c == '\\') { esc = 1; i++; bol = 0; continue; } - if (!esc && c == '\'') { in_sq = 0; } - esc = 0; - bol = (c == '\n') ? 1 : 0; - i++; - continue; - } - if (in_dq) { - sb_append_ch(&out, c); - if (!esc && c == '\\') { esc = 1; i++; bol = 0; continue; } - if (!esc && c == '"') { in_dq = 0; } - esc = 0; - bol = (c == '\n') ? 1 : 0; - i++; - continue; - } - - /* outside any special state */ - if (c == '/' && (i + 1) < len && src[i + 1] == '/') { - sb_append_ch(&out, '/'); - sb_append_ch(&out, '/'); - i += 2; - in_line = 1; - bol = 0; - continue; - } - if (c == '/' && (i + 1) < len && src[i + 1] == '*') { - sb_append_ch(&out, '/'); - sb_append_ch(&out, '*'); - i += 2; - in_block = 1; - bol = 0; - continue; - } - if (c == '\'') { - sb_append_ch(&out, c); - in_sq = 1; - bol = 0; - i++; - continue; - } - if (c == '"') { - sb_append_ch(&out, c); - in_dq = 1; - bol = 0; - i++; - continue; - } - - sb_append_ch(&out, c); - bol = (c == '\n') ? 1 : 0; - i++; + } } - if (!out.buf) return strdup(""); - /* ensure NUL-terminated */ - if (out.cap == out.len) sb_reserve(&out, out.len + 1); - if (out.buf) out.buf[out.len] = '\0'; - return out.buf; + /* normal stateful copy with comment/string tracking */ + if (in_line) { + sb_append_ch(&out, c); + if (c == '\n') { + in_line = 0; + bol = 1; + } else { + bol = 0; + } + i++; + continue; + } + if (in_block) { + sb_append_ch(&out, c); + if (c == '*' && (i + 1) < len && src[i + 1] == '/') { + sb_append_ch(&out, '/'); + i += 2; + bol = 0; + in_block = 0; + continue; + } + bol = (c == '\n') ? 1 : 0; + i++; + continue; + } + if (in_sq) { + sb_append_ch(&out, c); + if (!esc && c == '\\') { + esc = 1; + i++; + bol = 0; + continue; + } + if (!esc && c == '\'') { + in_sq = 0; + } + esc = 0; + bol = (c == '\n') ? 1 : 0; + i++; + continue; + } + if (in_dq) { + sb_append_ch(&out, c); + if (!esc && c == '\\') { + esc = 1; + i++; + bol = 0; + continue; + } + if (!esc && c == '"') { + in_dq = 0; + } + esc = 0; + bol = (c == '\n') ? 1 : 0; + i++; + continue; + } + + /* outside any special state */ + if (c == '/' && (i + 1) < len && src[i + 1] == '/') { + sb_append_ch(&out, '/'); + sb_append_ch(&out, '/'); + i += 2; + in_line = 1; + bol = 0; + continue; + } + if (c == '/' && (i + 1) < len && src[i + 1] == '*') { + sb_append_ch(&out, '/'); + sb_append_ch(&out, '*'); + i += 2; + in_block = 1; + bol = 0; + continue; + } + if (c == '\'') { + sb_append_ch(&out, c); + in_sq = 1; + bol = 0; + i++; + continue; + } + if (c == '"') { + sb_append_ch(&out, c); + in_dq = 1; + bol = 0; + i++; + continue; + } + + sb_append_ch(&out, c); + bol = (c == '\n') ? 1 : 0; + i++; + } + + if (!out.buf) return strdup(""); + /* ensure NUL-terminated */ + if (out.cap == out.len) sb_reserve(&out, out.len + 1); + if (out.buf) out.buf[out.len] = '\0'; + return out.buf; } char *preprocess_includes(const char *src) { - return preprocess_includes_internal(src, 0); + 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; + 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++; + /* optional sign */ + if (p < len && (src[p] == '+' || src[p] == '-')) p++; - /* integer part */ - while (p < len && isdigit((unsigned char)src[p])) { p++; saw_digit = 1; } + /* 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; } + /* 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; + /* 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 (!saw_digit || (!saw_dot && !saw_exp)) { - *ok = 0; return 0.0; + if (epos == digits_start) { + /* no digits after exponent -> not a float */ + *ok = 0; + return 0.0; } + p = epos; + } - /* 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'; + if (!saw_digit || (!saw_dot && !saw_exp)) { + *ok = 0; + return 0.0; + } - char *endp = NULL; - double dv = strtod(tmp, &endp); - if (!endp || *endp != '\0') { free(tmp); *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'; - *pos = p; - *ok = 1; + char *endp = NULL; + double dv = strtod(tmp, &endp); + if (!endp || *endp != '\0') { free(tmp); - return dv; + *ok = 0; + return 0.0; + } + + *pos = p; + *ok = 1; + free(tmp); + return dv; } diff --git a/src/repl.c b/src/repl.c index 81ebcef..29de060 100644 --- a/src/repl.c +++ b/src/repl.c @@ -8,30 +8,30 @@ * * Added: 2025-10-05 */ - - /** + +/** * REPL implementation for the Fun programming language. * Built only when FUN_WITH_REPL is defined. */ +#include "repl.h" #include "bytecode.h" +#include "parser.h" #include "value.h" #include "vm.h" -#include "parser.h" -#include "repl.h" -#include -#include -#include -#include #include +#include +#include +#include +#include #ifndef _WIN32 +#include +#include +#include #include #include -#include -#include -#include #endif #ifdef FUN_WITH_REPL @@ -46,46 +46,47 @@ static char *rl_hist[RL_HIST_MAX]; static int rl_count = 0; /* last history entry (or NULL) */ -static const char* rl_hist_last(void) { - if (rl_count <= 0) return NULL; - return rl_hist[rl_count - 1]; +static const char *rl_hist_last(void) { + if (rl_count <= 0) return NULL; + return rl_hist[rl_count - 1]; } /* add one line to in-memory history (without trailing newline), dedup consecutive */ static void rl_hist_add(const char *s) { - if (!s) return; - size_t n = strlen(s); - /* strip single trailing newline if present */ - while (n > 0 && (s[n-1] == '\n' || s[n-1] == '\r')) n--; - if (n == 0) return; - /* dedupe consecutive identical */ - const char *last = rl_hist_last(); - if (last && strncmp(last, s, n) == 0 && last[n] == '\0') return; + if (!s) return; + size_t n = strlen(s); + /* strip single trailing newline if present */ + while (n > 0 && (s[n - 1] == '\n' || s[n - 1] == '\r')) + n--; + if (n == 0) return; + /* dedupe consecutive identical */ + const char *last = rl_hist_last(); + if (last && strncmp(last, s, n) == 0 && last[n] == '\0') return; - /* allocate and copy */ - char *cpy = (char*)malloc(n + 1); - if (!cpy) return; - memcpy(cpy, s, n); - cpy[n] = '\0'; + /* allocate and copy */ + char *cpy = (char *)malloc(n + 1); + if (!cpy) return; + memcpy(cpy, s, n); + cpy[n] = '\0'; - if (rl_count == RL_HIST_MAX) { - free(rl_hist[0]); - memmove(&rl_hist[0], &rl_hist[1], sizeof(rl_hist[0]) * (RL_HIST_MAX - 1)); - rl_count--; - } - rl_hist[rl_count++] = cpy; + if (rl_count == RL_HIST_MAX) { + free(rl_hist[0]); + memmove(&rl_hist[0], &rl_hist[1], sizeof(rl_hist[0]) * (RL_HIST_MAX - 1)); + rl_count--; + } + rl_hist[rl_count++] = cpy; } /* preload history from file (one line per entry) */ static void rl_hist_load_file(const char *path) { - if (!path) return; - FILE *f = fopen(path, "r"); - if (!f) return; - char line[4096]; - while (fgets(line, sizeof(line), f)) { - rl_hist_add(line); - } - fclose(f); + if (!path) return; + FILE *f = fopen(path, "r"); + if (!f) return; + char line[4096]; + while (fgets(line, sizeof(line), f)) { + rl_hist_add(line); + } + fclose(f); } #ifndef _WIN32 @@ -93,196 +94,224 @@ static struct termios g_orig_tios; static int g_raw_enabled = 0; static void repl_disable_raw(void) { - if (g_raw_enabled) { - tcsetattr(STDIN_FILENO, TCSAFLUSH, &g_orig_tios); - g_raw_enabled = 0; - } + if (g_raw_enabled) { + tcsetattr(STDIN_FILENO, TCSAFLUSH, &g_orig_tios); + g_raw_enabled = 0; + } } static int repl_enable_raw(void) { - if (!isatty(STDIN_FILENO)) return 0; - if (g_raw_enabled) return 1; - if (tcgetattr(STDIN_FILENO, &g_orig_tios) == -1) return 0; - struct termios raw = g_orig_tios; - raw.c_lflag &= ~(ICANON | ECHO); - raw.c_cc[VMIN] = 1; - raw.c_cc[VTIME] = 0; - if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) return 0; - g_raw_enabled = 1; - return 1; + if (!isatty(STDIN_FILENO)) return 0; + if (g_raw_enabled) return 1; + if (tcgetattr(STDIN_FILENO, &g_orig_tios) == -1) return 0; + struct termios raw = g_orig_tios; + raw.c_lflag &= ~(ICANON | ECHO); + raw.c_cc[VMIN] = 1; + raw.c_cc[VTIME] = 0; + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) return 0; + g_raw_enabled = 1; + return 1; } /* return 1 if path is a directory, else 0 */ static int is_dir_path(const char *path) { - struct stat st; - if (stat(path, &st) != 0) return 0; - return S_ISDIR(st.st_mode); + struct stat st; + if (stat(path, &st) != 0) return 0; + return S_ISDIR(st.st_mode); } /* Compute longest common prefix of a set of strings (starting from offset base_len) */ static size_t lcp_suffix(const char **names, int count, size_t base_len) { - if (count <= 0) return base_len; - size_t lcp = (size_t)-1; - for (int i = 0; i < count; ++i) { - size_t nlen = strlen(names[i]); - size_t cur = 0; - /* compare suffix part starting at base_len */ - while (base_len + cur < nlen) { - char c = names[i][base_len + cur]; - int ok = 1; - for (int j = 0; j < count; ++j) { - size_t jlen = strlen(names[j]); - if (base_len + cur >= jlen || names[j][base_len + cur] != c) { ok = 0; break; } - } - if (!ok) break; - cur++; + if (count <= 0) return base_len; + size_t lcp = (size_t)-1; + for (int i = 0; i < count; ++i) { + size_t nlen = strlen(names[i]); + size_t cur = 0; + /* compare suffix part starting at base_len */ + while (base_len + cur < nlen) { + char c = names[i][base_len + cur]; + int ok = 1; + for (int j = 0; j < count; ++j) { + size_t jlen = strlen(names[j]); + if (base_len + cur >= jlen || names[j][base_len + cur] != c) { + ok = 0; + break; } - if (lcp == (size_t)-1 || cur < lcp) lcp = cur; + } + if (!ok) break; + cur++; } - return base_len + (lcp == (size_t)-1 ? 0 : lcp); + if (lcp == (size_t)-1 || cur < lcp) lcp = cur; + } + return base_len + (lcp == (size_t)-1 ? 0 : lcp); } /* Word-jump helpers (used by Ctrl+Left/Right in the REPL editor). * Words are runs of non-space characters; separators are spaces. */ static void rl_word_left(const char *out, size_t len, size_t *pos) { - (void)len; - if (!out || !pos) return; - if (*pos == 0) return; - while (*pos > 0 && out[*pos - 1] == ' ') (*pos)--; - while (*pos > 0 && out[*pos - 1] != ' ') (*pos)--; + (void)len; + if (!out || !pos) return; + if (*pos == 0) return; + while (*pos > 0 && out[*pos - 1] == ' ') + (*pos)--; + while (*pos > 0 && out[*pos - 1] != ' ') + (*pos)--; } static void rl_word_right(const char *out, size_t len, size_t *pos) { - if (!out || !pos) return; - if (*pos >= len) return; - while (*pos < len && out[*pos] != ' ') (*pos)++; - while (*pos < len && out[*pos] == ' ') (*pos)++; + if (!out || !pos) return; + if (*pos >= len) return; + while (*pos < len && out[*pos] != ' ') + (*pos)++; + while (*pos < len && out[*pos] == ' ') + (*pos)++; } /* Expand file path for path-taking REPL commands (e.g., :load, :run) in-place; returns 1 if buffer changed (redraw) */ static int complete_load_path(char *buf, size_t *len_io) { - size_t len = *len_io; - if (len < 3) return 0; /* minimally ":x" */ - const char *p = buf; - while (*p == ' ') p++; - if (*p != ':') return 0; + size_t len = *len_io; + if (len < 3) return 0; /* minimally ":x" */ + const char *p = buf; + while (*p == ' ') p++; - /* Parse command token (letters only) */ - const char *cmd_start = p; - while (*p && ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z'))) p++; - size_t cmd_len = (size_t)(p - cmd_start); - if (cmd_len == 0) return 0; - /* Accept both full and short aliases for :load and :run */ - int is_supported = 0; - if ((cmd_len == 4 && strncmp(cmd_start, "load", 4) == 0) || - (cmd_len == 2 && strncmp(cmd_start, "lo", 2) == 0) || - (cmd_len == 3 && strncmp(cmd_start, "run", 3) == 0) || - (cmd_len == 2 && strncmp(cmd_start, "ru", 2) == 0)) { - is_supported = 1; - } - if (!is_supported) return 0; - while (*p == ' ' || *p == '\t') p++; - size_t arg_off = (size_t)(p - buf); - if (arg_off > len) return 0; + if (*p != ':') return 0; + p++; + /* Parse command token (letters only) */ + const char *cmd_start = p; + while (*p && ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z'))) + p++; + size_t cmd_len = (size_t)(p - cmd_start); + if (cmd_len == 0) return 0; + /* Accept both full and short aliases for :load and :run */ + int is_supported = 0; + if ((cmd_len == 4 && strncmp(cmd_start, "load", 4) == 0) || + (cmd_len == 2 && strncmp(cmd_start, "lo", 2) == 0) || + (cmd_len == 3 && strncmp(cmd_start, "run", 3) == 0) || + (cmd_len == 2 && strncmp(cmd_start, "ru", 2) == 0)) { + is_supported = 1; + } + if (!is_supported) return 0; + while (*p == ' ' || *p == '\t') + p++; + size_t arg_off = (size_t)(p - buf); + if (arg_off > len) return 0; - char prefix[PATH_MAX]; - size_t plen = 0; - if (len - arg_off >= sizeof(prefix)) plen = sizeof(prefix) - 1; - else plen = len - arg_off; - memcpy(prefix, buf + arg_off, plen); - prefix[plen] = '\0'; + char prefix[PATH_MAX]; + size_t plen = 0; + if (len - arg_off >= sizeof(prefix)) + plen = sizeof(prefix) - 1; + else + plen = len - arg_off; + memcpy(prefix, buf + arg_off, plen); + prefix[plen] = '\0'; - char expanded[PATH_MAX]; - if (prefix[0] == '~') { - const char *home = getenv("HOME"); - if (!home) home = getenv("USERPROFILE"); - if (home) snprintf(expanded, sizeof(expanded), "%s%s", home, prefix + 1); - else snprintf(expanded, sizeof(expanded), "%s", prefix); + char expanded[PATH_MAX]; + if (prefix[0] == '~') { + const char *home = getenv("HOME"); + if (!home) home = getenv("USERPROFILE"); + if (home) + snprintf(expanded, sizeof(expanded), "%s%s", home, prefix + 1); + else + snprintf(expanded, sizeof(expanded), "%s", prefix); + } else { + snprintf(expanded, sizeof(expanded), "%s", prefix); + } + + char dirpart[PATH_MAX], base[PATH_MAX]; + const char *slash = strrchr(expanded, '/'); + if (slash) { + size_t dlen = (size_t)(slash - expanded); + if (dlen == 0) { + strcpy(dirpart, "/"); } else { - snprintf(expanded, sizeof(expanded), "%s", prefix); + memcpy(dirpart, expanded, dlen); + dirpart[dlen] = '\0'; } + snprintf(base, sizeof(base), "%s", slash + 1); + } else { + strcpy(dirpart, "."); + snprintf(base, sizeof(base), "%s", expanded); + } - char dirpart[PATH_MAX], base[PATH_MAX]; - const char *slash = strrchr(expanded, '/'); - if (slash) { - size_t dlen = (size_t)(slash - expanded); - if (dlen == 0) { strcpy(dirpart, "/"); } - else { memcpy(dirpart, expanded, dlen); dirpart[dlen] = '\0'; } - snprintf(base, sizeof(base), "%s", slash + 1); - } else { - strcpy(dirpart, "."); - snprintf(base, sizeof(base), "%s", expanded); + DIR *dp = opendir(dirpart); + if (!dp) { + fputc('\a', stdout); + fflush(stdout); + return 0; + } + + const char *names[1024]; + char storage[1024][NAME_MAX + 1]; + int count = 0; + struct dirent *de; + size_t blen = strlen(base); + while ((de = readdir(dp)) != NULL) { + if (blen == 0 || strncmp(de->d_name, base, blen) == 0) { + if (count < (int)(sizeof(names) / sizeof(names[0]))) { + snprintf(storage[count], sizeof(storage[count]), "%s", de->d_name); + names[count] = storage[count]; + count++; + } } + } + closedir(dp); - DIR *dp = opendir(dirpart); - if (!dp) { fputc('\a', stdout); fflush(stdout); return 0; } + if (count == 0) { + fputc('\a', stdout); + fflush(stdout); + return 0; + } - const char *names[1024]; - char storage[1024][NAME_MAX + 1]; - int count = 0; - struct dirent *de; - size_t blen = strlen(base); - while ((de = readdir(dp)) != NULL) { - if (blen == 0 || strncmp(de->d_name, base, blen) == 0) { - if (count < (int)(sizeof(names)/sizeof(names[0]))) { - snprintf(storage[count], sizeof(storage[count]), "%s", de->d_name); - names[count] = storage[count]; - count++; - } - } + size_t new_pref_len = lcp_suffix(names, count, blen); + int changed = 0; + + char completed[PATH_MAX]; + if (slash) { + char head[PATH_MAX]; + memcpy(head, expanded, (size_t)(slash - expanded + 1)); + head[slash - expanded + 1] = '\0'; + strncpy(completed, head, sizeof(completed)); + } else { + snprintf(completed, sizeof(completed), "%s", ""); + } + + strncat(completed, base, sizeof(completed) - strlen(completed) - 1); + if (new_pref_len > blen) { + strncat(completed, names[0] + blen, (new_pref_len - blen)); + changed = 1; + } else if (count == 1) { + size_t extra = strlen(names[0]) - blen; + strncat(completed, names[0] + blen, extra); + changed = 1; + } + + if (count == 1) { + char test[PATH_MAX]; + if (slash) + snprintf(test, sizeof(test), "%.*s/%s", (int)(slash - expanded), expanded, names[0]); + else + snprintf(test, sizeof(test), "%s", names[0]); + if (is_dir_path(test)) { + strncat(completed, "/", sizeof(completed) - strlen(completed) - 1); } - closedir(dp); + } - if (count == 0) { fputc('\a', stdout); fflush(stdout); return 0; } - - size_t new_pref_len = lcp_suffix(names, count, blen); - int changed = 0; - - char completed[PATH_MAX]; - if (slash) { - char head[PATH_MAX]; - memcpy(head, expanded, (size_t)(slash - expanded + 1)); - head[slash - expanded + 1] = '\0'; - strncpy(completed, head, sizeof(completed)); - } else { - snprintf(completed, sizeof(completed), "%s", ""); + if (!changed && count > 1) { + putchar('\n'); + for (int i = 0; i < count; ++i) { + fputs(names[i], stdout); + fputc(((i + 1) % 6 == 0) ? '\n' : '\t', stdout); } + if (count % 6 != 0) putchar('\n'); + fflush(stdout); + return 0; + } - strncat(completed, base, sizeof(completed) - strlen(completed) - 1); - if (new_pref_len > blen) { - strncat(completed, names[0] + blen, (new_pref_len - blen)); - changed = 1; - } else if (count == 1) { - size_t extra = strlen(names[0]) - blen; - strncat(completed, names[0] + blen, extra); - changed = 1; - } - - if (count == 1) { - char test[PATH_MAX]; - if (slash) snprintf(test, sizeof(test), "%.*s/%s", (int)(slash - expanded), expanded, names[0]); - else snprintf(test, sizeof(test), "%s", names[0]); - if (is_dir_path(test)) { - strncat(completed, "/", sizeof(completed) - strlen(completed) - 1); - } - } - - if (!changed && count > 1) { - putchar('\n'); - for (int i = 0; i < count; ++i) { - fputs(names[i], stdout); - fputc(((i + 1) % 6 == 0) ? '\n' : '\t', stdout); - } - if (count % 6 != 0) putchar('\n'); - fflush(stdout); - return 0; - } - - size_t comp_len = strlen(completed); - if (arg_off + comp_len >= PATH_MAX - 1) comp_len = PATH_MAX - 2 - arg_off; - memcpy(buf + arg_off, completed, comp_len); - *len_io = arg_off + comp_len; - buf[*len_io] = '\0'; - return 1; + size_t comp_len = strlen(completed); + if (arg_off + comp_len >= PATH_MAX - 1) comp_len = PATH_MAX - 2 - arg_off; + memcpy(buf + arg_off, completed, comp_len); + *len_io = arg_off + comp_len; + buf[*len_io] = '\0'; + return 1; } /* ---------- Stdlib symbol completion ---------- */ @@ -290,397 +319,468 @@ static char **g_std_syms = NULL; static int g_std_syms_count = 0; static int g_std_syms_cap = 0; -static int is_ident_start(int c) { return (c == '_' || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); } -static int is_ident_char(int c) { return is_ident_start(c) || (c >= '0' && c <= '9'); } +static int is_ident_start(int c) { + return (c == '_' || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); +} +static int is_ident_char(int c) { + return is_ident_start(c) || (c >= '0' && c <= '9'); +} static void std_syms_add(const char *name) { - if (!name || !*name) return; - /* dedupe */ - for (int i = 0; i < g_std_syms_count; ++i) { - if (strcmp(g_std_syms[i], name) == 0) return; - } - if (g_std_syms_count == g_std_syms_cap) { - int ncap = g_std_syms_cap == 0 ? 256 : g_std_syms_cap * 2; - char **nn = (char**)realloc(g_std_syms, (size_t)ncap * sizeof(char*)); - if (!nn) return; - g_std_syms = nn; - g_std_syms_cap = ncap; - } - size_t n = strlen(name); - char *cpy = (char*)malloc(n + 1); - if (!cpy) return; - memcpy(cpy, name, n + 1); - g_std_syms[g_std_syms_count++] = cpy; + if (!name || !*name) return; + /* dedupe */ + for (int i = 0; i < g_std_syms_count; ++i) { + if (strcmp(g_std_syms[i], name) == 0) return; + } + if (g_std_syms_count == g_std_syms_cap) { + int ncap = g_std_syms_cap == 0 ? 256 : g_std_syms_cap * 2; + char **nn = (char **)realloc(g_std_syms, (size_t)ncap * sizeof(char *)); + if (!nn) return; + g_std_syms = nn; + g_std_syms_cap = ncap; + } + size_t n = strlen(name); + char *cpy = (char *)malloc(n + 1); + if (!cpy) return; + memcpy(cpy, name, n + 1); + g_std_syms[g_std_syms_count++] = cpy; } static void scan_symbols_from_file(const char *path) { - FILE *f = fopen(path, "r"); - if (!f) return; - char line[4096]; - while (fgets(line, sizeof(line), f)) { - const char *p = line; - while (*p == ' ' || *p == '\t') p++; - /* Patterns: fun NAME( ; class NAME ; const NAME ; var NAME */ - const char *kw = NULL; - if (strncmp(p, "fun ", 4) == 0) { kw = "fun"; p += 4; } - else if (strncmp(p, "class ", 6) == 0) { kw = "class"; p += 6; } - else if (strncmp(p, "const ", 6) == 0) { kw = "const"; p += 6; } - else if (strncmp(p, "var ", 4) == 0) { kw = "var"; p += 4; } - if (!kw) continue; - while (*p == ' ' || *p == '\t') p++; - if (!is_ident_start((unsigned char)*p)) continue; - char name[256]; - size_t ni = 0; - while (is_ident_char((unsigned char)*p) && ni + 1 < sizeof(name)) { - name[ni++] = *p++; - } - name[ni] = '\0'; - if (ni > 0) std_syms_add(name); + FILE *f = fopen(path, "r"); + if (!f) return; + char line[4096]; + while (fgets(line, sizeof(line), f)) { + const char *p = line; + while (*p == ' ' || *p == '\t') + p++; + /* Patterns: fun NAME( ; class NAME ; const NAME ; var NAME */ + const char *kw = NULL; + if (strncmp(p, "fun ", 4) == 0) { + kw = "fun"; + p += 4; + } else if (strncmp(p, "class ", 6) == 0) { + kw = "class"; + p += 6; + } else if (strncmp(p, "const ", 6) == 0) { + kw = "const"; + p += 6; + } else if (strncmp(p, "var ", 4) == 0) { + kw = "var"; + p += 4; } - fclose(f); + if (!kw) continue; + while (*p == ' ' || *p == '\t') + p++; + if (!is_ident_start((unsigned char)*p)) continue; + char name[256]; + size_t ni = 0; + while (is_ident_char((unsigned char)*p) && ni + 1 < sizeof(name)) { + name[ni++] = *p++; + } + name[ni] = '\0'; + if (ni > 0) std_syms_add(name); + } + fclose(f); } static void scan_dir_recursive(const char *dir) { - DIR *dp = opendir(dir); - if (!dp) return; - struct dirent *de; - char path[PATH_MAX]; - while ((de = readdir(dp)) != NULL) { - const char *n = de->d_name; - if (strcmp(n, ".") == 0 || strcmp(n, "..") == 0) continue; - snprintf(path, sizeof(path), "%s/%s", dir, n); - struct stat st; - if (stat(path, &st) != 0) continue; - if (S_ISDIR(st.st_mode)) { - scan_dir_recursive(path); - } else { - size_t L = strlen(n); - if (L >= 4 && strcmp(n + (L - 4), ".fun") == 0) { - scan_symbols_from_file(path); - } - } + DIR *dp = opendir(dir); + if (!dp) return; + struct dirent *de; + char path[PATH_MAX]; + while ((de = readdir(dp)) != NULL) { + const char *n = de->d_name; + if (strcmp(n, ".") == 0 || strcmp(n, "..") == 0) continue; + snprintf(path, sizeof(path), "%s/%s", dir, n); + struct stat st; + if (stat(path, &st) != 0) continue; + if (S_ISDIR(st.st_mode)) { + scan_dir_recursive(path); + } else { + size_t L = strlen(n); + if (L >= 4 && strcmp(n + (L - 4), ".fun") == 0) { + scan_symbols_from_file(path); + } } - closedir(dp); + } + closedir(dp); } static void load_stdlib_symbols(const char *libdir) { - if (!libdir || !*libdir) return; - scan_dir_recursive(libdir); + if (!libdir || !*libdir) return; + scan_dir_recursive(libdir); } static void free_stdlib_symbols(void) { - for (int i = 0; i < g_std_syms_count; ++i) free(g_std_syms[i]); - free(g_std_syms); - g_std_syms = NULL; - g_std_syms_count = g_std_syms_cap = 0; + for (int i = 0; i < g_std_syms_count; ++i) + free(g_std_syms[i]); + free(g_std_syms); + g_std_syms = NULL; + g_std_syms_count = g_std_syms_cap = 0; } /* Complete the trailing identifier in 'buf' using stdlib symbols. Returns 1 if buffer changed, 2 if a menu was printed, 0 otherwise. */ static int complete_stdlib_ident(char *buf, size_t *len_io, size_t *pos_io, size_t cap) { - size_t len = *len_io; - size_t pos = *pos_io; - if (pos != len) return 0; /* complete only at end */ - if (len == 0) return 0; + size_t len = *len_io; + size_t pos = *pos_io; + if (pos != len) return 0; /* complete only at end */ + if (len == 0) return 0; - /* find start of identifier */ - size_t start = len; - while (start > 0 && is_ident_char((unsigned char)buf[start - 1])) start--; - if (start == len) return 0; + /* find start of identifier */ + size_t start = len; + while (start > 0 && is_ident_char((unsigned char)buf[start - 1])) + start--; + if (start == len) return 0; - const char *prefix = buf + start; - size_t plen = len - start; + const char *prefix = buf + start; + size_t plen = len - start; - /* collect matches */ - const char *matches[1024]; - int mcount = 0; - for (int i = 0; i < g_std_syms_count && mcount < (int)(sizeof(matches)/sizeof(matches[0])); ++i) { - if (strncmp(g_std_syms[i], prefix, plen) == 0) { - matches[mcount++] = g_std_syms[i]; - } + /* collect matches */ + const char *matches[1024]; + int mcount = 0; + for (int i = 0; i < g_std_syms_count && mcount < (int)(sizeof(matches) / sizeof(matches[0])); ++i) { + if (strncmp(g_std_syms[i], prefix, plen) == 0) { + matches[mcount++] = g_std_syms[i]; } - if (mcount == 0) { fputc('\a', stdout); fflush(stdout); return 0; } + } + if (mcount == 0) { + fputc('\a', stdout); + fflush(stdout); + return 0; + } - /* compute longest common extension */ - size_t lcp = (size_t)-1; + /* compute longest common extension */ + size_t lcp = (size_t)-1; + for (int i = 0; i < mcount; ++i) { + size_t nlen = strlen(matches[i]); + size_t cur = 0; + while (plen + cur < nlen) { + char c = matches[i][plen + cur]; + int ok = 1; + for (int j = 0; j < mcount; ++j) { + size_t jlen = strlen(matches[j]); + if (plen + cur >= jlen || matches[j][plen + cur] != c) { + ok = 0; + break; + } + } + if (!ok) break; + cur++; + } + if (lcp == (size_t)-1 || cur < lcp) lcp = cur; + } + size_t new_pref_len = plen + (lcp == (size_t)-1 ? 0 : lcp); + + /* build completion text */ + char comp[512]; + size_t write_len = 0; + if (mcount == 1) { + snprintf(comp, sizeof(comp), "%s", matches[0]); + write_len = strlen(comp); + } else if (new_pref_len > plen) { + strncpy(comp, prefix, plen); + comp[plen] = '\0'; + strncat(comp, matches[0] + plen, new_pref_len - plen); + write_len = strlen(comp); + } else { + /* list candidates */ + putchar('\n'); for (int i = 0; i < mcount; ++i) { - size_t nlen = strlen(matches[i]); - size_t cur = 0; - while (plen + cur < nlen) { - char c = matches[i][plen + cur]; - int ok = 1; - for (int j = 0; j < mcount; ++j) { - size_t jlen = strlen(matches[j]); - if (plen + cur >= jlen || matches[j][plen + cur] != c) { ok = 0; break; } - } - if (!ok) break; - cur++; - } - if (lcp == (size_t)-1 || cur < lcp) lcp = cur; + fputs(matches[i], stdout); + fputc(((i + 1) % 6 == 0) ? '\n' : '\t', stdout); } - size_t new_pref_len = plen + (lcp == (size_t)-1 ? 0 : lcp); + if (mcount % 6 != 0) putchar('\n'); + fflush(stdout); + return 2; + } - /* build completion text */ - char comp[512]; - size_t write_len = 0; - if (mcount == 1) { - snprintf(comp, sizeof(comp), "%s", matches[0]); - write_len = strlen(comp); - } else if (new_pref_len > plen) { - strncpy(comp, prefix, plen); - comp[plen] = '\0'; - strncat(comp, matches[0] + plen, new_pref_len - plen); - write_len = strlen(comp); - } else { - /* list candidates */ - putchar('\n'); - for (int i = 0; i < mcount; ++i) { - fputs(matches[i], stdout); - fputc(((i + 1) % 6 == 0) ? '\n' : '\t', stdout); - } - if (mcount % 6 != 0) putchar('\n'); - fflush(stdout); - return 2; - } - - /* replace tail from 'start' with comp */ - if (start + write_len >= cap) write_len = cap - 1 - start; - memmove(buf + start, comp, write_len); - *len_io = start + write_len; - buf[*len_io] = '\0'; - *pos_io = *len_io; - return 1; + /* replace tail from 'start' with comp */ + if (start + write_len >= cap) write_len = cap - 1 - start; + memmove(buf + start, comp, write_len); + *len_io = start + write_len; + buf[*len_io] = '\0'; + *pos_io = *len_io; + return 1; } /* Read one line with prompt, handling backspace, Up/Down history and :load path completion (now with multi-line editing via Ctrl+O) */ static int read_line_edit(char *out, size_t out_cap, const char *prompt) { #ifdef _WIN32 - if (prompt) fputs(prompt, stdout), fflush(stdout); + if (prompt) fputs(prompt, stdout), fflush(stdout); + if (!fgets(out, (int)out_cap, stdin)) return 0; + return 1; +#else + if (prompt) fputs(prompt, stdout), fflush(stdout); + if (!repl_enable_raw()) { if (!fgets(out, (int)out_cap, stdin)) return 0; return 1; -#else - if (prompt) fputs(prompt, stdout), fflush(stdout); - if (!repl_enable_raw()) { - if (!fgets(out, (int)out_cap, stdin)) return 0; - return 1; + } + + size_t len = 0; + size_t pos = 0; + int hist_pos = rl_count; + char saved_current[4096]; + int has_saved = 0; + + /* How many terminal rows were drawn in the previous repaint (prompt + lines in 'out'). + We assume no automatic wrapping (only explicit '\n'). */ + static int prev_rows = 1; + +#define RL_REDRAW() \ + do { \ + /* Count explicit newlines to estimate rows to draw (prompt line + content lines) */ \ + int rows = 1; \ + int has_nl = 0; \ + for (size_t __i = 0; __i < len; ++__i) { \ + if (out[__i] == '\n') { \ + rows++; \ + has_nl = 1; \ + } \ + } \ + /* Move cursor to the start (top) of the previous render block */ \ + if (prev_rows > 1) { \ + fprintf(stdout, "\x1b[%dA", prev_rows - 1); \ + } \ + fputc('\r', stdout); \ + /* Repaint prompt + content */ \ + if (prompt) fputs(prompt, stdout); \ + fwrite(out, 1, len, stdout); \ + /* Clear everything below the current cursor (removes remnants of older, longer renders) */ \ + fputs("\x1b[J", stdout); \ + /* Cursor policy: keep at end for multi-line; for single-line respect pos */ \ + if (!has_nl && pos < len) { \ + size_t back = (size_t)(len - pos); \ + if (back > 0) fprintf(stdout, "\x1b[%zuD", back); \ + } else { \ + pos = len; \ + } \ + prev_rows = rows; \ + fflush(stdout); \ + } while (0) + + for (;;) { + int ch = getchar(); + if (ch == EOF) { + repl_disable_raw(); + return 0; } - size_t len = 0; - size_t pos = 0; - int hist_pos = rl_count; - char saved_current[4096]; - int has_saved = 0; - - /* How many terminal rows were drawn in the previous repaint (prompt + lines in 'out'). - We assume no automatic wrapping (only explicit '\n'). */ - static int prev_rows = 1; - - #define RL_REDRAW() do { \ - /* Count explicit newlines to estimate rows to draw (prompt line + content lines) */ \ - int rows = 1; \ - int has_nl = 0; \ - for (size_t __i = 0; __i < len; ++__i) { \ - if (out[__i] == '\n') { rows++; has_nl = 1; } \ - } \ - /* Move cursor to the start (top) of the previous render block */ \ - if (prev_rows > 1) { fprintf(stdout, "\x1b[%dA", prev_rows - 1); } \ - fputc('\r', stdout); \ - /* Repaint prompt + content */ \ - if (prompt) fputs(prompt, stdout); \ - fwrite(out, 1, len, stdout); \ - /* Clear everything below the current cursor (removes remnants of older, longer renders) */ \ - fputs("\x1b[J", stdout); \ - /* Cursor policy: keep at end for multi-line; for single-line respect pos */ \ - if (!has_nl && pos < len) { \ - size_t back = (size_t)(len - pos); \ - if (back > 0) fprintf(stdout, "\x1b[%zuD", back); \ - } else { \ - pos = len; \ - } \ - prev_rows = rows; \ - fflush(stdout); \ - } while(0) - - for (;;) { - int ch = getchar(); - if (ch == EOF) { repl_disable_raw(); return 0; } - - if (ch == '\r' || ch == '\n') { - fputc('\n', stdout); - if (len + 1 < out_cap) { - out[len++] = '\n'; - out[len] = '\0'; - } else { - out[len] = '\0'; - } - rl_hist_add(out); - repl_disable_raw(); - return 1; - } else if (ch == 27) { - int c1 = getchar(); - if (c1 == '[') { - char params[16]; - int pi = 0; - int final = 0; - for (;;) { - int cx = getchar(); - if (cx == EOF) break; - if ((cx >= 'A' && cx <= 'Z') || (cx >= 'a' && cx <= 'z') || cx == '~') { - final = cx; - break; - } - if (pi + 1 < (int)sizeof(params)) { - params[pi++] = (char)cx; - params[pi] = '\0'; - } - } - int ctrl = 0; - if (pi > 0) { - if (strstr(params, ";5") != NULL || strcmp(params, "5") == 0 || strstr(params, "1;5") != NULL) { - ctrl = 1; - } - } - - /* disallow fine-grained horizontal movement in multi-line mode (cursor stays at end) */ - int has_nl = 0; for (size_t __i = 0; __i < len; ++__i) { if (out[__i] == '\n') { has_nl = 1; break; } } - - if (final == 'A') { - if (!has_saved) { - size_t sl = len < sizeof(saved_current) - 1 ? len : sizeof(saved_current) - 1; - memcpy(saved_current, out, sl); - saved_current[sl] = '\0'; - has_saved = 1; - } - if (hist_pos > 0) { - hist_pos--; - const char *h = rl_hist[hist_pos]; - size_t hl = strlen(h); - if (hl >= out_cap) hl = out_cap - 1; - memcpy(out, h, hl); - out[hl] = '\0'; - len = hl; - pos = len; - RL_REDRAW(); - } else { - fputc('\a', stdout); fflush(stdout); - } - } else if (final == 'B') { - if (hist_pos < rl_count) { - hist_pos++; - if (hist_pos == rl_count) { - if (has_saved) { - size_t hl = strlen(saved_current); - if (hl >= out_cap) hl = out_cap - 1; - memcpy(out, saved_current, hl); - out[hl] = '\0'; - len = hl; - } else { - len = 0; out[0] = '\0'; - } - } else { - const char *h = rl_hist[hist_pos]; - size_t hl = strlen(h); - if (hl >= out_cap) hl = out_cap - 1; - memcpy(out, h, hl); - out[hl] = '\0'; - len = hl; - } - pos = len; - RL_REDRAW(); - } else { - fputc('\a', stdout); fflush(stdout); - } - } else if (final == 'C') { - if (has_nl) { fputc('\a', stdout); fflush(stdout); pos = len; RL_REDRAW(); } - else if (ctrl) { - size_t old = pos; - rl_word_right(out, len, &pos); - if (pos != old) RL_REDRAW(); - else { fputc('\a', stdout); fflush(stdout); } - } else { - if (pos < len) { pos++; fputs("\x1b[C", stdout); fflush(stdout); } - else { fputc('\a', stdout); fflush(stdout); } - } - } else if (final == 'D') { - if (has_nl) { fputc('\a', stdout); fflush(stdout); pos = len; RL_REDRAW(); } - else if (ctrl) { - size_t old = pos; - rl_word_left(out, len, &pos); - if (pos != old) RL_REDRAW(); - else { fputc('\a', stdout); fflush(stdout); } - } else { - if (pos > 0) { pos--; fputs("\x1b[D", stdout); fflush(stdout); } - else { fputc('\a', stdout); fflush(stdout); } - } - } else { - /* ignore other CSI sequences */ - } - } - } else if (ch == 127 || ch == 8) { - if (pos > 0) { - memmove(out + pos - 1, out + pos, len - pos); - len--; - pos--; - out[len] = '\0'; - RL_REDRAW(); - } else { - fputc('\a', stdout); - fflush(stdout); - } - } else if (ch == '\t') { - if (pos != len) { - fputc('\a', stdout); - fflush(stdout); - } else { - out[len] = '\0'; - size_t newlen = len; - int changed = complete_load_path(out, &newlen); - if (changed) { - len = newlen; - pos = len; - } else { - /* try stdlib symbol completion */ - int res = complete_stdlib_ident(out, &newlen, &pos, out_cap); - if (res == 1) { - len = newlen; - } else if (res == 2) { - /* menu printed — do not modify buffer, just redraw prompt+line */ - } else { - fputc('\a', stdout); - fflush(stdout); - } - } - RL_REDRAW(); - } - } else if (ch == 15) { /* Ctrl+O -> insert newline at cursor (multi-line editing) */ - if (len + 1 < out_cap) { - memmove(out + pos + 1, out + pos, len - pos); - out[pos] = '\n'; - len++; - pos++; - out[len] = '\0'; - hist_pos = rl_count; - RL_REDRAW(); - } else { - fputc('\a', stdout); fflush(stdout); - } - } else if (ch >= 32 && ch <= 126) { - if (len + 1 < out_cap) { - memmove(out + pos + 1, out + pos, len - pos); - out[pos] = (char)ch; - len++; - pos++; - out[len] = '\0'; - hist_pos = rl_count; - RL_REDRAW(); - } else { - fputc('\a', stdout); - fflush(stdout); - } - } else { - /* ignore other control characters */ + if (ch == '\r' || ch == '\n') { + fputc('\n', stdout); + if (len + 1 < out_cap) { + out[len++] = '\n'; + out[len] = '\0'; + } else { + out[len] = '\0'; + } + rl_hist_add(out); + repl_disable_raw(); + return 1; + } else if (ch == 27) { + int c1 = getchar(); + if (c1 == '[') { + char params[16]; + int pi = 0; + int final = 0; + for (;;) { + int cx = getchar(); + if (cx == EOF) break; + if ((cx >= 'A' && cx <= 'Z') || (cx >= 'a' && cx <= 'z') || cx == '~') { + final = cx; + break; + } + if (pi + 1 < (int)sizeof(params)) { + params[pi++] = (char)cx; + params[pi] = '\0'; + } } + int ctrl = 0; + if (pi > 0) { + if (strstr(params, ";5") != NULL || strcmp(params, "5") == 0 || strstr(params, "1;5") != NULL) { + ctrl = 1; + } + } + + /* disallow fine-grained horizontal movement in multi-line mode (cursor stays at end) */ + int has_nl = 0; + for (size_t __i = 0; __i < len; ++__i) { + if (out[__i] == '\n') { + has_nl = 1; + break; + } + } + + if (final == 'A') { + if (!has_saved) { + size_t sl = len < sizeof(saved_current) - 1 ? len : sizeof(saved_current) - 1; + memcpy(saved_current, out, sl); + saved_current[sl] = '\0'; + has_saved = 1; + } + if (hist_pos > 0) { + hist_pos--; + const char *h = rl_hist[hist_pos]; + size_t hl = strlen(h); + if (hl >= out_cap) hl = out_cap - 1; + memcpy(out, h, hl); + out[hl] = '\0'; + len = hl; + pos = len; + RL_REDRAW(); + } else { + fputc('\a', stdout); + fflush(stdout); + } + } else if (final == 'B') { + if (hist_pos < rl_count) { + hist_pos++; + if (hist_pos == rl_count) { + if (has_saved) { + size_t hl = strlen(saved_current); + if (hl >= out_cap) hl = out_cap - 1; + memcpy(out, saved_current, hl); + out[hl] = '\0'; + len = hl; + } else { + len = 0; + out[0] = '\0'; + } + } else { + const char *h = rl_hist[hist_pos]; + size_t hl = strlen(h); + if (hl >= out_cap) hl = out_cap - 1; + memcpy(out, h, hl); + out[hl] = '\0'; + len = hl; + } + pos = len; + RL_REDRAW(); + } else { + fputc('\a', stdout); + fflush(stdout); + } + } else if (final == 'C') { + if (has_nl) { + fputc('\a', stdout); + fflush(stdout); + pos = len; + RL_REDRAW(); + } else if (ctrl) { + size_t old = pos; + rl_word_right(out, len, &pos); + if (pos != old) + RL_REDRAW(); + else { + fputc('\a', stdout); + fflush(stdout); + } + } else { + if (pos < len) { + pos++; + fputs("\x1b[C", stdout); + fflush(stdout); + } else { + fputc('\a', stdout); + fflush(stdout); + } + } + } else if (final == 'D') { + if (has_nl) { + fputc('\a', stdout); + fflush(stdout); + pos = len; + RL_REDRAW(); + } else if (ctrl) { + size_t old = pos; + rl_word_left(out, len, &pos); + if (pos != old) + RL_REDRAW(); + else { + fputc('\a', stdout); + fflush(stdout); + } + } else { + if (pos > 0) { + pos--; + fputs("\x1b[D", stdout); + fflush(stdout); + } else { + fputc('\a', stdout); + fflush(stdout); + } + } + } else { + /* ignore other CSI sequences */ + } + } + } else if (ch == 127 || ch == 8) { + if (pos > 0) { + memmove(out + pos - 1, out + pos, len - pos); + len--; + pos--; + out[len] = '\0'; + RL_REDRAW(); + } else { + fputc('\a', stdout); + fflush(stdout); + } + } else if (ch == '\t') { + if (pos != len) { + fputc('\a', stdout); + fflush(stdout); + } else { + out[len] = '\0'; + size_t newlen = len; + int changed = complete_load_path(out, &newlen); + if (changed) { + len = newlen; + pos = len; + } else { + /* try stdlib symbol completion */ + int res = complete_stdlib_ident(out, &newlen, &pos, out_cap); + if (res == 1) { + len = newlen; + } else if (res == 2) { + /* menu printed — do not modify buffer, just redraw prompt+line */ + } else { + fputc('\a', stdout); + fflush(stdout); + } + } + RL_REDRAW(); + } + } else if (ch == 15) { /* Ctrl+O -> insert newline at cursor (multi-line editing) */ + if (len + 1 < out_cap) { + memmove(out + pos + 1, out + pos, len - pos); + out[pos] = '\n'; + len++; + pos++; + out[len] = '\0'; + hist_pos = rl_count; + RL_REDRAW(); + } else { + fputc('\a', stdout); + fflush(stdout); + } + } else if (ch >= 32 && ch <= 126) { + if (len + 1 < out_cap) { + memmove(out + pos + 1, out + pos, len - pos); + out[pos] = (char)ch; + len++; + pos++; + out[len] = '\0'; + hist_pos = rl_count; + RL_REDRAW(); + } else { + fputc('\a', stdout); + fflush(stdout); + } + } else { + /* ignore other control characters */ } + } #endif } #endif /* !_WIN32 */ @@ -688,1134 +788,1346 @@ static int read_line_edit(char *out, size_t out_cap, const char *prompt) { /* ---------- Small utilities ---------- */ static int is_blank_line(const char *s) { - for (const char *p = s; *p; ++p) { - if (*p != ' ' && *p != '\t' && *p != '\r' && *p != '\n') return 0; - } - return 1; + for (const char *p = s; *p; ++p) { + if (*p != ' ' && *p != '\t' && *p != '\r' && *p != '\n') return 0; + } + return 1; } -static const char* lstrip(const char *s) { - while (*s == ' ' || *s == '\t') s++; - return s; +static const char *lstrip(const char *s) { + while (*s == ' ' || *s == '\t') + s++; + return s; } static int ends_with_opener(const char *line) { - size_t n = strlen(line); - while (n > 0 && (line[n-1] == ' ' || line[n-1] == '\t' || line[n-1] == '\r' || line[n-1] == '\n')) n--; - if (n == 0) return 0; - char c = line[n-1]; - if (c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || - c == '<' || c == '>' || c == '=' || c == '!' || c == '&' || c == '|' || c == ',') { - return 1; - } - return 0; + size_t n = strlen(line); + while (n > 0 && (line[n - 1] == ' ' || line[n - 1] == '\t' || line[n - 1] == '\r' || line[n - 1] == '\n')) + n--; + if (n == 0) return 0; + char c = line[n - 1]; + if (c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || + c == '<' || c == '>' || c == '=' || c == '!' || c == '&' || c == '|' || c == ',') { + return 1; + } + return 0; } /* Compute how many indentation levels (2 spaces per level) are still open. */ static int compute_open_indent_blocks(const char *buf) { - int in_block_comment = 0; - int open = 0; - int have_baseline = 0; - int cur = 0; + int in_block_comment = 0; + int open = 0; + int have_baseline = 0; + int cur = 0; - const char *p = buf; - while (*p) { - const char *line = p; - while (*p && *p != '\n') p++; - const char *line_end = p; - if (*p == '\n') p++; + const char *p = buf; + while (*p) { + const char *line = p; + while (*p && *p != '\n') + p++; + const char *line_end = p; + if (*p == '\n') p++; - if (in_block_comment) { - const char *q = line; - while (q < line_end) { - if (q + 1 < line_end && q[0] == '*' && q[1] == '/') { in_block_comment = 0; q += 2; break; } - q++; - } - if (in_block_comment) continue; + if (in_block_comment) { + const char *q = line; + while (q < line_end) { + if (q + 1 < line_end && q[0] == '*' && q[1] == '/') { + in_block_comment = 0; + q += 2; + break; } - - const char *s = line; - int spaces = 0; - while (s < line_end && *s == ' ') { spaces++; s++; } - while (s < line_end && *s == '\t') { s++; } - - const char *t = s; - if (t >= line_end) continue; - - if ((t + 1) <= line_end && t[0] == '/' && (t + 1 < line_end && (t[1] == '/' || t[1] == '*'))) { - if (t[1] == '/') { - continue; - } else if (t[1] == '*') { - in_block_comment = 1; - continue; - } - } - - int lvl = spaces / 2; - if (!have_baseline) { - cur = lvl; - have_baseline = 1; - continue; - } - if (lvl > cur) { - open += (lvl - cur); - } else if (lvl < cur) { - int dec = (cur - lvl); - if (dec > open) open = 0; - else open -= dec; - } - cur = lvl; + q++; + } + if (in_block_comment) continue; } - return open; + + const char *s = line; + int spaces = 0; + while (s < line_end && *s == ' ') { + spaces++; + s++; + } + while (s < line_end && *s == '\t') { + s++; + } + + const char *t = s; + if (t >= line_end) continue; + + if ((t + 1) <= line_end && t[0] == '/' && (t + 1 < line_end && (t[1] == '/' || t[1] == '*'))) { + if (t[1] == '/') { + continue; + } else if (t[1] == '*') { + in_block_comment = 1; + continue; + } + } + + int lvl = spaces / 2; + if (!have_baseline) { + cur = lvl; + have_baseline = 1; + continue; + } + if (lvl > cur) { + open += (lvl - cur); + } else if (lvl < cur) { + int dec = (cur - lvl); + if (dec > open) + open = 0; + else + open -= dec; + } + cur = lvl; + } + return open; } /* Detect if current buffer looks incomplete. */ static int buffer_looks_incomplete(const char *buf) { - int in_single = 0, in_double = 0, escape = 0; - int in_block_comment = 0, in_line_comment = 0; - int paren = 0; + int in_single = 0, in_double = 0, escape = 0; + int in_block_comment = 0, in_line_comment = 0; + int paren = 0; - const char *p = buf; - const char *last_sig_line = NULL; + const char *p = buf; + const char *last_sig_line = NULL; - while (*p) { - char c = *p; + while (*p) { + char c = *p; - if (in_line_comment) { - if (c == '\n') in_line_comment = 0; - p++; - continue; - } - if (in_block_comment) { - if (c == '*' && p[1] == '/') { in_block_comment = 0; p += 2; continue; } - p++; - continue; - } + if (in_line_comment) { + if (c == '\n') in_line_comment = 0; + p++; + continue; + } + if (in_block_comment) { + if (c == '*' && p[1] == '/') { + in_block_comment = 0; + p += 2; + continue; + } + p++; + continue; + } - if (!in_single && !in_double) { - if (c == '/' && p[1] == '/') { in_line_comment = 1; p += 2; continue; } - if (c == '/' && p[1] == '*') { in_block_comment = 1; p += 2; continue; } - } + if (!in_single && !in_double) { + if (c == '/' && p[1] == '/') { + in_line_comment = 1; + p += 2; + continue; + } + if (c == '/' && p[1] == '*') { + in_block_comment = 1; + p += 2; + continue; + } + } - if (in_single) { - if (!escape && c == '\\') { escape = 1; p++; continue; } - if (!escape && c == '\'') { in_single = 0; p++; continue; } - escape = 0; p++; continue; - } else if (in_double) { - if (!escape && c == '\\') { escape = 1; p++; continue; } - if (!escape && c == '"') { in_double = 0; p++; continue; } - escape = 0; p++; continue; - } else { - if (c == '\'') { in_single = 1; p++; continue; } - if (c == '"') { in_double = 1; p++; continue; } - if (c == '(') { paren++; p++; continue; } - if (c == ')') { if (paren > 0) paren--; p++; continue; } - } - - if (c == '\n') { - const char *q = p + 1; - while (*q == ' ' || *q == '\t') q++; - if (*q && *q != '\n') last_sig_line = q; - } + if (in_single) { + if (!escape && c == '\\') { + escape = 1; p++; + continue; + } + if (!escape && c == '\'') { + in_single = 0; + p++; + continue; + } + escape = 0; + p++; + continue; + } else if (in_double) { + if (!escape && c == '\\') { + escape = 1; + p++; + continue; + } + if (!escape && c == '"') { + in_double = 0; + p++; + continue; + } + escape = 0; + p++; + continue; + } else { + if (c == '\'') { + in_single = 1; + p++; + continue; + } + if (c == '"') { + in_double = 1; + p++; + continue; + } + if (c == '(') { + paren++; + p++; + continue; + } + if (c == ')') { + if (paren > 0) paren--; + p++; + continue; + } } - if (!last_sig_line) { - const char *q = buf; - const char *candidate = NULL; - while (*q) { - const char *line_start = q; - while (*q && *q != '\n') q++; - const char *t = line_start; - while (*t == ' ' || *t == '\t') t++; - if (*t && *t != '\n' && *t != '\r') candidate = t; - if (*q == '\n') q++; - } - last_sig_line = candidate; + if (c == '\n') { + const char *q = p + 1; + while (*q == ' ' || *q == '\t') + q++; + if (*q && *q != '\n') last_sig_line = q; } + p++; + } - if (in_single || in_double || in_block_comment || paren > 0) return 1; - - if (last_sig_line) { - if (strncmp(lstrip(last_sig_line), "if", 2) == 0 || - strncmp(lstrip(last_sig_line), "else", 4) == 0 || - strncmp(lstrip(last_sig_line), "while", 5) == 0 || - strncmp(lstrip(last_sig_line), "for", 3) == 0 || - strncmp(lstrip(last_sig_line), "fun", 3) == 0) { - return 1; - } - if (ends_with_opener(last_sig_line)) return 1; + if (!last_sig_line) { + const char *q = buf; + const char *candidate = NULL; + while (*q) { + const char *line_start = q; + while (*q && *q != '\n') + q++; + const char *t = line_start; + while (*t == ' ' || *t == '\t') + t++; + if (*t && *t != '\n' && *t != '\r') candidate = t; + if (*q == '\n') q++; } + last_sig_line = candidate; + } - return 0; + if (in_single || in_double || in_block_comment || paren > 0) return 1; + + if (last_sig_line) { + if (strncmp(lstrip(last_sig_line), "if", 2) == 0 || + strncmp(lstrip(last_sig_line), "else", 4) == 0 || + strncmp(lstrip(last_sig_line), "while", 5) == 0 || + strncmp(lstrip(last_sig_line), "for", 3) == 0 || + strncmp(lstrip(last_sig_line), "fun", 3) == 0) { + return 1; + } + if (ends_with_opener(last_sig_line)) return 1; + } + + return 0; } static void show_repl_help(void) { - printf("Commands:\n"); - printf(" :help | :h Show this help\n"); - printf(" :quit | :q | :exit Exit the REPL\n"); - printf(" :reset | :re Reset VM state (clears globals)\n"); - printf(" :dump | :du | :globals | :gl Dump current globals\n"); - printf(" :globals [pattern] Dump globals filtering by value substring\n"); - printf(" :vars | :v [pattern] Alias for :globals\n"); - printf(" :clear | :cl Clear current input buffer\n"); - printf(" :print | :pr Show current buffer\n"); - printf(" :run | :ru [file] Execute current buffer or the given file immediately\n"); - printf(" :profile | :pf Execute buffer and show timing + instruction count\n"); - printf(" :save | :sa Save current buffer to file\n"); - printf(" :load | :lo Load file into buffer (does not run)\n"); - printf(" :paste | :pa [run] Enter paste mode; end with a single '.' line (optional 'run')\n"); - printf(" :history | :hi [N] Show last N lines of history (default 50)\n"); - printf(" :time | :ti on|off|toggle Toggle/enable/disable timing\n"); - printf(" :env | :en [NAME[=VALUE]] Get or set environment variable\n"); - printf(" :backtrace | :bt | :ba Show backtrace of VM frames (most recent first)\n"); - printf(" :frame | :fr N Select frame N for :locals/:list/:disasm (default: top)\n"); - printf(" :list | :li [±K] Show K lines of source around current frame line (default 5)\n"); - printf(" :disasm | :di [±N] Disassemble around current frame ip (default 5)\n"); - printf(" :mdump | :md WHAT [offset [len]] [raw] [to ] Dump VM memory region\n"); - printf(" WHAT = code | stack | globals | consts\n"); - printf(" 'raw' writes binary bytes instead of a formatted hexdump\n"); - printf(" :stack | :st [N] Show top N (default all) stack values\n"); - printf(" :top | :to Show the top of the VM stack\n"); - printf(" :locals | :lc [FRAME] Show locals of frame (default: selected frame)\n"); - printf(" :printv | :pv WHAT Print value: local[i] | stack[i] | global[i]\n"); - printf(" :break | :br [file:]line Set a breakpoint (default file = current frame file)\n"); - printf(" :info | :in breaks List breakpoints\n"); - printf(" :delete | :de ID Delete breakpoint by ID\n"); - printf(" :clear breaks | :cb Remove all breakpoints\n"); - printf(" :cont | :co Continue execution (exit REPL if in debug stop)\n"); - printf(" :step | :sp Step one instruction\n"); - printf(" :next | :ne Step over (current frame)\n"); - printf(" :finish | :fi Run until the current frame returns\n"); + printf("Commands:\n"); + printf(" :help | :h Show this help\n"); + printf(" :quit | :q | :exit Exit the REPL\n"); + printf(" :reset | :re Reset VM state (clears globals)\n"); + printf(" :dump | :du | :globals | :gl Dump current globals\n"); + printf(" :globals [pattern] Dump globals filtering by value substring\n"); + printf(" :vars | :v [pattern] Alias for :globals\n"); + printf(" :clear | :cl Clear current input buffer\n"); + printf(" :print | :pr Show current buffer\n"); + printf(" :run | :ru [file] Execute current buffer or the given file immediately\n"); + printf(" :profile | :pf Execute buffer and show timing + instruction count\n"); + printf(" :save | :sa Save current buffer to file\n"); + printf(" :load | :lo Load file into buffer (does not run)\n"); + printf(" :paste | :pa [run] Enter paste mode; end with a single '.' line (optional 'run')\n"); + printf(" :history | :hi [N] Show last N lines of history (default 50)\n"); + printf(" :time | :ti on|off|toggle Toggle/enable/disable timing\n"); + printf(" :env | :en [NAME[=VALUE]] Get or set environment variable\n"); + printf(" :backtrace | :bt | :ba Show backtrace of VM frames (most recent first)\n"); + printf(" :frame | :fr N Select frame N for :locals/:list/:disasm (default: top)\n"); + printf(" :list | :li [±K] Show K lines of source around current frame line (default 5)\n"); + printf(" :disasm | :di [±N] Disassemble around current frame ip (default 5)\n"); + printf(" :mdump | :md WHAT [offset [len]] [raw] [to ] Dump VM memory region\n"); + printf(" WHAT = code | stack | globals | consts\n"); + printf(" 'raw' writes binary bytes instead of a formatted hexdump\n"); + printf(" :stack | :st [N] Show top N (default all) stack values\n"); + printf(" :top | :to Show the top of the VM stack\n"); + printf(" :locals | :lc [FRAME] Show locals of frame (default: selected frame)\n"); + printf(" :printv | :pv WHAT Print value: local[i] | stack[i] | global[i]\n"); + printf(" :break | :br [file:]line Set a breakpoint (default file = current frame file)\n"); + printf(" :info | :in breaks List breakpoints\n"); + printf(" :delete | :de ID Delete breakpoint by ID\n"); + printf(" :clear breaks | :cb Remove all breakpoints\n"); + printf(" :cont | :co Continue execution (exit REPL if in debug stop)\n"); + printf(" :step | :sp Step one instruction\n"); + printf(" :next | :ne Step over (current frame)\n"); + printf(" :finish | :fi Run until the current frame returns\n"); } static char *read_entire_file(const char *path, size_t *out_len) { - FILE *f = fopen(path, "rb"); - if (!f) return NULL; - if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return NULL; } - long sz = ftell(f); - if (sz < 0) { fclose(f); return NULL; } - rewind(f); - char *buf = (char*)malloc((size_t)sz + 1); - if (!buf) { fclose(f); return NULL; } - size_t n = fread(buf, 1, (size_t)sz, f); + FILE *f = fopen(path, "rb"); + if (!f) return NULL; + if (fseek(f, 0, SEEK_END) != 0) { fclose(f); - buf[n] = '\0'; - if (out_len) *out_len = n; - return buf; + return NULL; + } + long sz = ftell(f); + if (sz < 0) { + fclose(f); + return NULL; + } + rewind(f); + char *buf = (char *)malloc((size_t)sz + 1); + if (!buf) { + fclose(f); + return NULL; + } + size_t n = fread(buf, 1, (size_t)sz, f); + fclose(f); + buf[n] = '\0'; + if (out_len) *out_len = n; + return buf; } static int write_entire_file(const char *path, const char *data, size_t len) { - FILE *f = fopen(path, "wb"); - if (!f) return 0; - size_t n = fwrite(data, 1, len, f); - fclose(f); - return n == len; + FILE *f = fopen(path, "wb"); + if (!f) return 0; + size_t n = fwrite(data, 1, len, f); + fclose(f); + return n == len; } /* ---------- REPL command matching helper ---------- */ static int cmd_is_one_of(const char *cmd, const char *const names[]) { - if (!cmd || !*cmd) return 0; - for (int i = 0; names[i] != NULL; ++i) { - if (strcmp(cmd, names[i]) == 0) return 1; - } - return 0; + if (!cmd || !*cmd) return 0; + for (int i = 0; names[i] != NULL; ++i) { + if (strcmp(cmd, names[i]) == 0) return 1; + } + return 0; } /* ---------- Hexdump helper ---------- */ static void hexdump_to(FILE *out, const unsigned char *data, size_t len, size_t base_off) { - if (!out || !data || len == 0) return; - const size_t width = 16; - char ascii[17]; - ascii[16] = '\0'; - for (size_t i = 0; i < len; i += width) { - size_t chunk = (len - i < width) ? (len - i) : width; - /* address */ - fprintf(out, "%08zx ", base_off + i); - /* hex bytes */ - for (size_t j = 0; j < width; ++j) { - if (j < chunk) { - fprintf(out, "%02x ", data[i + j]); - unsigned char c = data[i + j]; - ascii[j] = (c >= 32 && c <= 126) ? (char)c : '.'; - } else { - fputs(" ", out); - ascii[j] = ' '; - } - if (j == 7) fputc(' ', out); /* extra space between 8-byte halves */ - } - ascii[chunk < width ? chunk : width] = '\0'; - fprintf(out, " |%s|\n", ascii); + if (!out || !data || len == 0) return; + const size_t width = 16; + char ascii[17]; + ascii[16] = '\0'; + for (size_t i = 0; i < len; i += width) { + size_t chunk = (len - i < width) ? (len - i) : width; + /* address */ + fprintf(out, "%08zx ", base_off + i); + /* hex bytes */ + for (size_t j = 0; j < width; ++j) { + if (j < chunk) { + fprintf(out, "%02x ", data[i + j]); + unsigned char c = data[i + j]; + ascii[j] = (c >= 32 && c <= 126) ? (char)c : '.'; + } else { + fputs(" ", out); + ascii[j] = ' '; + } + if (j == 7) fputc(' ', out); /* extra space between 8-byte halves */ } + ascii[chunk < width ? chunk : width] = '\0'; + fprintf(out, " |%s|\n", ascii); + } } static void print_last_n_lines(const char *path, int n) { - if (n <= 0) n = 50; - size_t flen = 0; - char *content = read_entire_file(path, &flen); - if (!content) { - printf("No history available.\n"); + if (n <= 0) n = 50; + size_t flen = 0; + char *content = read_entire_file(path, &flen); + if (!content) { + printf("No history available.\n"); + return; + } + int lines = 0; + for (size_t i = flen; i > 0; --i) { + if (content[i - 1] == '\n') { + lines++; + if (lines > n) { + content[i] = '\0'; + printf("%s", content + i); + free(content); return; + } } - int lines = 0; - for (size_t i = flen; i > 0; --i) { - if (content[i-1] == '\n') { - lines++; - if (lines > n) { content[i] = '\0'; printf("%s", content + i); free(content); return; } - } - } - printf("%s", content); - free(content); + } + printf("%s", content); + free(content); } static void append_history(FILE *hist, const char *buffer) { - if (!hist || !buffer) return; - fputs(buffer, hist); - if (buffer[0] && buffer[strlen(buffer)-1] != '\n') fputc('\n', hist); - fflush(hist); + if (!hist || !buffer) return; + fputs(buffer, hist); + if (buffer[0] && buffer[strlen(buffer) - 1] != '\n') fputc('\n', hist); + fflush(hist); } /* ---------- Env helpers ---------- */ static void env_show_usage(void) { - printf("Usage:\n"); - printf(" :env NAME Show environment variable NAME\n"); - printf(" :env NAME=VALUE Set environment variable NAME to VALUE\n"); - printf(" :env Show this usage\n"); + printf("Usage:\n"); + printf(" :env NAME Show environment variable NAME\n"); + printf(" :env NAME=VALUE Set environment variable NAME to VALUE\n"); + printf(" :env Show this usage\n"); } static void env_get(const char *name) { - const char *v = getenv(name); - if (v) printf("%s=%s\n", name, v); - else printf("%s is not set\n", name); + const char *v = getenv(name); + if (v) + printf("%s=%s\n", name, v); + else + printf("%s is not set\n", name); } static void env_set(const char *name, const char *value) { #ifdef _WIN32 - if (_putenv_s(name, value ? value : "") != 0) { - printf("Failed to set %s\n", name); - } + if (_putenv_s(name, value ? value : "") != 0) { + printf("Failed to set %s\n", name); + } #else - if (setenv(name, value ? value : "", 1) != 0) { - printf("Failed to set %s\n", name); - } + if (setenv(name, value ? value : "", 1) != 0) { + printf("Failed to set %s\n", name); + } #endif } /* ---------- REPL Entry ---------- */ int fun_run_repl(VM *vm) { - int repl_timing = 0; - int selected_frame = -1; /* -1 means use current top frame */ + int repl_timing = 0; + int selected_frame = -1; /* -1 means use current top frame */ - printf("Fun %s REPL\n", FUN_VERSION); - printf("Type :help for commands. Submit an empty line to run.\n"); + printf("Fun %s REPL\n", FUN_VERSION); + printf("Type :help for commands. Submit an empty line to run.\n"); - /* Load stdlib symbols for completion */ - { - char libdir[PATH_MAX]; - const char *envlib = getenv("FUN_LIB_DIR"); - if (envlib && *envlib) { - snprintf(libdir, sizeof(libdir), "%s", envlib); - } else { -#ifdef DEFAULT_LIB_DIR - snprintf(libdir, sizeof(libdir), "%s", DEFAULT_LIB_DIR); -#else - snprintf(libdir, sizeof(libdir), "%s", "lib"); -#endif - } - /* strip trailing slash if present, not strictly necessary */ - size_t L = strlen(libdir); - if (L > 1 && libdir[L - 1] == '/') libdir[L - 1] = '\0'; - load_stdlib_symbols(libdir); - } - - char *buffer = NULL; - size_t bufcap = 0; - size_t buflen = 0; - - /* History setup */ - char hist_path[1024]; - FILE *hist = NULL; - const char *home = getenv("HOME"); - if (!home) home = getenv("USERPROFILE"); - if (home) { - snprintf(hist_path, sizeof(hist_path), "%s/.fun_history", home); - rl_hist_load_file(hist_path); - hist = fopen(hist_path, "a+"); + /* Load stdlib symbols for completion */ + { + char libdir[PATH_MAX]; + const char *envlib = getenv("FUN_LIB_DIR"); + if (envlib && *envlib) { + snprintf(libdir, sizeof(libdir), "%s", envlib); } else { - snprintf(hist_path, sizeof(hist_path), ".fun_history"); - rl_hist_load_file(hist_path); - hist = fopen(hist_path, "a+"); +#ifdef DEFAULT_LIB_DIR + snprintf(libdir, sizeof(libdir), "%s", DEFAULT_LIB_DIR); +#else + snprintf(libdir, sizeof(libdir), "%s", "lib"); +#endif + } + /* strip trailing slash if present, not strictly necessary */ + size_t L = strlen(libdir); + if (L > 1 && libdir[L - 1] == '/') libdir[L - 1] = '\0'; + load_stdlib_symbols(libdir); + } + + char *buffer = NULL; + size_t bufcap = 0; + size_t buflen = 0; + + /* History setup */ + char hist_path[1024]; + FILE *hist = NULL; + const char *home = getenv("HOME"); + if (!home) home = getenv("USERPROFILE"); + if (home) { + snprintf(hist_path, sizeof(hist_path), "%s/.fun_history", home); + rl_hist_load_file(hist_path); + hist = fopen(hist_path, "a+"); + } else { + snprintf(hist_path, sizeof(hist_path), ".fun_history"); + rl_hist_load_file(hist_path); + hist = fopen(hist_path, "a+"); + } + + for (;;) { + if (buflen + 1 > bufcap) { + size_t newcap = bufcap == 0 ? 1024 : bufcap * 2; + while (newcap < buflen + 1) + newcap *= 2; + buffer = (char *)realloc(buffer, newcap); + bufcap = newcap; + } + buffer[buflen] = '\0'; + int indent_debt = (buflen > 0) ? compute_open_indent_blocks(buffer) : 0; + + char prompt[64]; + if (buflen == 0) { + snprintf(prompt, sizeof(prompt), "fun> "); + } else if (indent_debt > 0) { + snprintf(prompt, sizeof(prompt), "...%d> ", indent_debt); + } else { + snprintf(prompt, sizeof(prompt), "... "); } - for (;;) { - if (buflen + 1 > bufcap) { - size_t newcap = bufcap == 0 ? 1024 : bufcap * 2; - while (newcap < buflen + 1) newcap *= 2; - buffer = (char*)realloc(buffer, newcap); - bufcap = newcap; - } - buffer[buflen] = '\0'; - int indent_debt = (buflen > 0) ? compute_open_indent_blocks(buffer) : 0; - - char prompt[64]; - if (buflen == 0) { - snprintf(prompt, sizeof(prompt), "fun> "); - } else if (indent_debt > 0) { - snprintf(prompt, sizeof(prompt), "...%d> ", indent_debt); - } else { - snprintf(prompt, sizeof(prompt), "... "); - } - - char line[4096]; + char line[4096]; #ifndef _WIN32 - if (!read_line_edit(line, sizeof(line), prompt)) { - puts(""); - break; // EOF - } + if (!read_line_edit(line, sizeof(line), prompt)) { + puts(""); + break; // EOF + } #else - fputs(prompt, stdout); - fflush(stdout); - if (!fgets(line, sizeof(line), stdin)) { + fputs(prompt, stdout); + fflush(stdout); + if (!fgets(line, sizeof(line), stdin)) { + puts(""); + break; + } +#endif + if (hist) { + const char *lp = line; + int only_nl = 1; + while (*lp) { + if (*lp != '\n' && *lp != '\r') { + only_nl = 0; + break; + } + lp++; + } + if (!only_nl) append_history(hist, line); + } + + if (line[0] == ':') { + char cmd[64] = {0}; + char arg[2048] = {0}; + sscanf(line, ":%63s %2047[^\n]", cmd, arg); + + if (cmd_is_one_of(cmd, (const char *[]){"quit", "q", "qu", "exit", NULL})) { + break; + } else if (cmd_is_one_of(cmd, (const char *[]){"help", "h", NULL})) { + show_repl_help(); + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"reset", "re", NULL})) { + vm_reset(vm); + printf("VM state reset.\n"); + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"dump", "du", NULL})) { + vm_dump_globals(vm); + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"globals", "vars", "gl", "v", "va", NULL})) { + const char *pattern = lstrip(arg); + int filtered = (pattern && *pattern); + printf("=== globals%s%s ===\n", filtered ? " matching '" : "", filtered ? pattern : ""); + if (filtered) printf("'\n"); + for (int i = 0; i < MAX_GLOBALS; ++i) { + if (vm->globals[i].type == VAL_NIL) continue; + char *sv = value_to_string_alloc(&vm->globals[i]); + if (!filtered || (sv && strstr(sv, pattern))) { + printf("[%d] %s\n", i, sv ? sv : "nil"); + } + free(sv); + } + printf("===============\n"); + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"clear", "cl", NULL})) { + buflen = 0; + printf("(buffer cleared)\n"); + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"print", "pr", NULL})) { + if (buflen == 0) + printf("(buffer empty)\n"); + else { + if (buflen >= bufcap) { + buffer = (char *)realloc(buffer, buflen + 1); + bufcap = buflen + 1; + } + buffer[buflen] = '\0'; + printf("%s", buffer); + if (buflen > 0 && buffer[buflen - 1] != '\n') printf("\n"); + } + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"run", "ru", "profile", "pf", NULL})) { + int is_profile = cmd_is_one_of(cmd, (const char *[]){"profile", "pf", NULL}); + // 'arg' is a fixed-size local array, so its address is always non-null. + // Only check whether it contains a non-empty string. + int from_file = (arg[0] != '\0'); + + const char *src = NULL; + char *filebuf = NULL; + if (from_file) { + size_t flen = 0; + filebuf = read_entire_file(arg, &flen); + if (!filebuf) { + printf("Failed to load '%s'\n", arg); + continue; + } + src = filebuf; + } else { + if (buflen == 0) { + printf("(buffer empty)\n"); + continue; + } + if (buflen + 1 > bufcap) { + buffer = (char *)realloc(buffer, buflen + 1); + bufcap = buflen + 1; + } + buffer[buflen] = '\0'; + src = buffer; + } + + clock_t t_parse0 = 0, t_parse1 = 0, t_run0 = 0, t_run1 = 0; + if (is_profile) t_parse0 = clock(); + Bytecode *bc = parse_string_to_bytecode(src); + if (is_profile) t_parse1 = clock(); + + if (bc) { + if (repl_timing || is_profile) t_run0 = clock(); + vm_run(vm, bc); + if (repl_timing || is_profile) t_run1 = clock(); + + if (is_profile) { + double ms_parse = (double)(t_parse1 - t_parse0) * 1000.0 / (double)CLOCKS_PER_SEC; + double ms_run = (double)(t_run1 - t_run0) * 1000.0 / (double)CLOCKS_PER_SEC; + printf("[profile] parse: %.2f ms, run: %.2f ms, total: %.2f ms, instr: %lld\n", + ms_parse, ms_run, ms_parse + ms_run, vm->instr_count); + } else if (repl_timing) { + double ms = (double)(t_run1 - t_run0) * 1000.0 / (double)CLOCKS_PER_SEC; + printf("[time] %.2f ms\n", ms); + } + + vm_print_output(vm); + vm_clear_output(vm); + bytecode_free(bc); + if (!from_file) append_history(hist, buffer); + } else { + int line_no = 0, col_no = 0; + char emsg[256]; + if (parser_last_error(emsg, sizeof(emsg), &line_no, &col_no)) { + printf("Parse error at %d:%d: %s\n", line_no, col_no, emsg); + int cur_line = 1; + const char *p = src ? src : buffer; + while (*p && cur_line < line_no) { + if (*p == '\n') cur_line++; + p++; + } + const char *line_start = p; + while (*p && *p != '\n') + p++; + fwrite(line_start, 1, (size_t)(p - line_start), stdout); + printf("\n"); + for (int i = 1; i < col_no; ++i) + putchar(' '); + printf("^\n"); +#ifdef FUN_DEBUG + if (hist && !from_file) { + fprintf(hist, "// ERROR %d:%d: %s\n", line_no, col_no, emsg); + fflush(hist); + } +#endif + } else { + printf("Parse error.\n"); +#ifdef FUN_DEBUG + if (hist && !from_file) { + fprintf(hist, "// ERROR: parse error\n"); + fflush(hist); + } +#endif + } + } + if (from_file) { + free(filebuf); + } else { + buflen = 0; /* keep behavior: clear buffer only when running current buffer */ + } + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"save", "sa", NULL})) { + if (arg[0] == '\0') { + printf("Usage: :save \n"); + continue; + } + if (buflen == 0) { + printf("(buffer empty)\n"); + continue; + } + if (!write_entire_file(arg, buffer, buflen)) { + printf("Failed to save to '%s'\n", arg); + } else { + printf("Saved %zu bytes to '%s'\n", buflen, arg); + } + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"load", "lo", NULL})) { + if (arg[0] == '\0') { + printf("Usage: :load \n"); + continue; + } + size_t flen = 0; + char *filebuf = read_entire_file(arg, &flen); + if (!filebuf) { + printf("Failed to load '%s'\n", arg); + continue; + } + if (flen + 1 > bufcap) { + size_t newcap = flen + 1; + buffer = (char *)realloc(buffer, newcap); + bufcap = newcap; + } + memcpy(buffer, filebuf, flen); + buflen = flen; + buffer[buflen] = '\0'; + free(filebuf); + printf("Loaded %zu bytes into buffer. Use :run or submit an empty line to execute.\n", buflen); + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"paste", "pa", NULL})) { + int run_after = 0; + const char *opt = lstrip(arg); + if (opt && (strcmp(opt, "run") == 0 || strcmp(opt, "exec") == 0)) run_after = 1; + printf("(paste mode: end with single '.' line)%s\n", run_after ? " [will run]" : ""); + for (;;) { + fputs("... paste> ", stdout); + fflush(stdout); + char pline[8192]; + if (!fgets(pline, sizeof(pline), stdin)) { puts(""); break; - } -#endif - if (hist) { - const char *lp = line; - int only_nl = 1; - while (*lp) { if (*lp != '\n' && *lp != '\r') { only_nl = 0; break; } lp++; } - if (!only_nl) append_history(hist, line); - } - - if (line[0] == ':') { - char cmd[64] = {0}; - char arg[2048] = {0}; - sscanf(line, ":%63s %2047[^\n]", cmd, arg); - - if (cmd_is_one_of(cmd, (const char*[]){"quit","q","qu","exit", NULL})) { - break; - } else if (cmd_is_one_of(cmd, (const char*[]){"help","h", NULL})) { - show_repl_help(); - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"reset","re", NULL})) { - vm_reset(vm); - printf("VM state reset.\n"); - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"dump","du", NULL})) { - vm_dump_globals(vm); - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"globals","vars","gl","v","va", NULL})) { - const char *pattern = lstrip(arg); - int filtered = (pattern && *pattern); - printf("=== globals%s%s ===\n", filtered ? " matching '" : "", filtered ? pattern : ""); - if (filtered) printf("'\n"); - for (int i = 0; i < MAX_GLOBALS; ++i) { - if (vm->globals[i].type == VAL_NIL) continue; - char *sv = value_to_string_alloc(&vm->globals[i]); - if (!filtered || (sv && strstr(sv, pattern))) { - printf("[%d] %s\n", i, sv ? sv : "nil"); - } - free(sv); - } - printf("===============\n"); - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"clear","cl", NULL})) { - buflen = 0; - printf("(buffer cleared)\n"); - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"print","pr", NULL})) { - if (buflen == 0) printf("(buffer empty)\n"); - else { - if (buflen >= bufcap) { - buffer = (char*)realloc(buffer, buflen + 1); - bufcap = buflen + 1; - } - buffer[buflen] = '\0'; - printf("%s", buffer); - if (buflen > 0 && buffer[buflen-1] != '\n') printf("\n"); - } - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"run","ru","profile","pf", NULL})) { - int is_profile = cmd_is_one_of(cmd, (const char*[]){"profile","pf", NULL}); - // 'arg' is a fixed-size local array, so its address is always non-null. - // Only check whether it contains a non-empty string. - int from_file = (arg[0] != '\0'); - - const char *src = NULL; - char *filebuf = NULL; - if (from_file) { - size_t flen = 0; - filebuf = read_entire_file(arg, &flen); - if (!filebuf) { - printf("Failed to load '%s'\n", arg); - continue; - } - src = filebuf; - } else { - if (buflen == 0) { - printf("(buffer empty)\n"); - continue; - } - if (buflen + 1 > bufcap) { - buffer = (char*)realloc(buffer, buflen + 1); - bufcap = buflen + 1; - } - buffer[buflen] = '\0'; - src = buffer; - } - - clock_t t_parse0 = 0, t_parse1 = 0, t_run0 = 0, t_run1 = 0; - if (is_profile) t_parse0 = clock(); - Bytecode *bc = parse_string_to_bytecode(src); - if (is_profile) t_parse1 = clock(); - - if (bc) { - if (repl_timing || is_profile) t_run0 = clock(); - vm_run(vm, bc); - if (repl_timing || is_profile) t_run1 = clock(); - - if (is_profile) { - double ms_parse = (double)(t_parse1 - t_parse0) * 1000.0 / (double)CLOCKS_PER_SEC; - double ms_run = (double)(t_run1 - t_run0) * 1000.0 / (double)CLOCKS_PER_SEC; - printf("[profile] parse: %.2f ms, run: %.2f ms, total: %.2f ms, instr: %lld\n", - ms_parse, ms_run, ms_parse + ms_run, vm->instr_count); - } else if (repl_timing) { - double ms = (double)(t_run1 - t_run0) * 1000.0 / (double)CLOCKS_PER_SEC; - printf("[time] %.2f ms\n", ms); - } - - vm_print_output(vm); - vm_clear_output(vm); - bytecode_free(bc); - if (!from_file) append_history(hist, buffer); - } else { - int line_no = 0, col_no = 0; - char emsg[256]; - if (parser_last_error(emsg, sizeof(emsg), &line_no, &col_no)) { - printf("Parse error at %d:%d: %s\n", line_no, col_no, emsg); - int cur_line = 1; - const char *p = src ? src : buffer; - while (*p && cur_line < line_no) { - if (*p == '\n') cur_line++; - p++; - } - const char *line_start = p; - while (*p && *p != '\n') p++; - fwrite(line_start, 1, (size_t)(p - line_start), stdout); - printf("\n"); - for (int i = 1; i < col_no; ++i) putchar(' '); - printf("^\n"); -#ifdef FUN_DEBUG - if (hist && !from_file) { - fprintf(hist, "// ERROR %d:%d: %s\n", line_no, col_no, emsg); - fflush(hist); - } -#endif - } else { - printf("Parse error.\n"); -#ifdef FUN_DEBUG - if (hist && !from_file) { - fprintf(hist, "// ERROR: parse error\n"); - fflush(hist); - } -#endif - } - } - if (from_file) { - free(filebuf); - } else { - buflen = 0; /* keep behavior: clear buffer only when running current buffer */ - } - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"save","sa", NULL})) { - if (arg[0] == '\0') { printf("Usage: :save \n"); continue; } - if (buflen == 0) { printf("(buffer empty)\n"); continue; } - if (!write_entire_file(arg, buffer, buflen)) { - printf("Failed to save to '%s'\n", arg); - } else { - printf("Saved %zu bytes to '%s'\n", buflen, arg); - } - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"load","lo", NULL})) { - if (arg[0] == '\0') { printf("Usage: :load \n"); continue; } - size_t flen = 0; - char *filebuf = read_entire_file(arg, &flen); - if (!filebuf) { - printf("Failed to load '%s'\n", arg); - continue; - } - if (flen + 1 > bufcap) { - size_t newcap = flen + 1; - buffer = (char*)realloc(buffer, newcap); - bufcap = newcap; - } - memcpy(buffer, filebuf, flen); - buflen = flen; - buffer[buflen] = '\0'; - free(filebuf); - printf("Loaded %zu bytes into buffer. Use :run or submit an empty line to execute.\n", buflen); - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"paste","pa", NULL})) { - int run_after = 0; - const char *opt = lstrip(arg); - if (opt && (strcmp(opt, "run") == 0 || strcmp(opt, "exec") == 0)) run_after = 1; - printf("(paste mode: end with single '.' line)%s\n", run_after ? " [will run]" : ""); - for (;;) { - fputs("... paste> ", stdout); - fflush(stdout); - char pline[8192]; - if (!fgets(pline, sizeof(pline), stdin)) { puts(""); break; } - if ((strcmp(pline, ".\n") == 0) || (strcmp(pline, ".\r\n") == 0) || (strcmp(pline, ".") == 0)) { - break; - } - size_t pl = strlen(pline); - if (buflen + pl + 1 > bufcap) { - size_t newcap = bufcap == 0 ? 1024 : bufcap * 2; - while (newcap < buflen + pl + 1) newcap *= 2; - buffer = (char*)realloc(buffer, newcap); - bufcap = newcap; - } - memcpy(buffer + buflen, pline, pl); - buflen += pl; - } - if (run_after) { - if (buflen + 1 > bufcap) { buffer = (char*)realloc(buffer, buflen + 1); bufcap = buflen + 1; } - buffer[buflen] = '\0'; - Bytecode *bc = parse_string_to_bytecode(buffer); - if (bc) { - clock_t t0 = clock(); - vm_run(vm, bc); - clock_t t1 = clock(); - double ms = (double)(t1 - t0) * 1000.0 / (double)CLOCKS_PER_SEC; - printf("[time] %.2f ms\n", ms); - vm_print_output(vm); - vm_clear_output(vm); - bytecode_free(bc); - append_history(hist, buffer); - } else { - int line_no = 0, col_no = 0; - char emsg[256]; - if (parser_last_error(emsg, sizeof(emsg), &line_no, &col_no)) { - printf("Parse error at %d:%d: %s\n", line_no, col_no, emsg); -#ifdef FUN_DEBUG - if (hist) { - fprintf(hist, "// ERROR %d:%d: %s\n", line_no, col_no, emsg); - fflush(hist); - } -#endif - } else { - printf("Parse error.\n"); -#ifdef FUN_DEBUG - if (hist) { - fprintf(hist, "// ERROR: parse error\n"); - fflush(hist); - } -#endif - } - } - buflen = 0; - } else { - printf("(pasted %zu bytes into buffer)\n", buflen); - } - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"history","hi", NULL})) { - int n = 50; - if (arg[0] != '\0') n = atoi(arg); - if (n <= 0) n = 50; - print_last_n_lines(hist_path, n); - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"time","ti", NULL})) { - if (strcmp(lstrip(arg), "on") == 0) repl_timing = 1; - else if (strcmp(lstrip(arg), "off") == 0) repl_timing = 0; - else if (strcmp(lstrip(arg), "toggle") == 0) repl_timing = !repl_timing; - else { - printf("Usage: :time on|off|toggle (currently %s)\n", repl_timing ? "on" : "off"); - continue; - } - printf("Timing %s\n", repl_timing ? "enabled" : "disabled"); - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"env","en", NULL})) { - const char *spec = lstrip(arg); - if (!spec || *spec == '\0') { - env_show_usage(); - continue; - } - const char *eq = strchr(spec, '='); - if (!eq) { - env_get(spec); - } else { - char name[256]; - size_t nlen = (size_t)(eq - spec); - if (nlen >= sizeof(name)) nlen = sizeof(name) - 1; - memcpy(name, spec, nlen); - name[nlen] = '\0'; - const char *val = eq + 1; - env_set(name, val); - } - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"backtrace","bt","ba", NULL})) { - if (vm->fp < 0) { printf("(no frames)\n"); continue; } - printf("Backtrace (most recent call first):\n"); - for (int i = vm->fp; i >= 0; --i) { - Frame *f = &vm->frames[i]; - const char *fname = (f->fn && f->fn->name) ? f->fn->name : ""; - const char *sfile = (f->fn && f->fn->source_file) ? f->fn->source_file : ""; - int ip = f->ip - 1; - printf(" #%d %s at %s ip=%d line=%d\n", i, fname, sfile, ip, vm->current_line); - } - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"stack","st", NULL})) { - int n = -1; - const char *p = lstrip(arg); - if (p && *p) n = atoi(p); - int count = vm->sp + 1; - if (count <= 0) { printf("(stack empty)\n"); continue; } - int start = 0; - if (n > 0 && n < count) start = count - n; - printf("Stack size=%d\n", count); - for (int i = start; i < count; ++i) { - char *sv = value_to_string_alloc(&vm->stack[i]); - printf("[%d] %s\n", i, sv ? sv : "nil"); - free(sv); - } - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"locals","lc", NULL})) { - int idx = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp; - const char *p = lstrip(arg); - if (p && *p) { - int v = atoi(p); - if (v >= 0 && v <= vm->fp) idx = v; - } - if (idx < 0) { printf("(no current frame)\n"); continue; } - Frame *f = &vm->frames[idx]; - const char *fname = (f->fn && f->fn->name) ? f->fn->name : ""; - printf("Locals in frame #%d (%s):\n", idx, fname); - int any = 0; - for (int i = 0; i < MAX_FRAME_LOCALS; ++i) { - if (f->locals[i].type != VAL_NIL) { - char *sv = value_to_string_alloc(&f->locals[i]); - printf(" %d: %s\n", i, sv ? sv : "nil"); - free(sv); - any = 1; - } - } - if (!any) printf(" (no non-nil locals)\n"); - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"frame","fr", NULL})) { - const char *p = lstrip(arg); - if (!p || !*p) { printf("Usage: :frame N\n"); continue; } - int v = atoi(p); - if (v < 0 || v > vm->fp) { printf("Invalid frame index. Current top is %d\n", vm->fp); continue; } - selected_frame = v; - Frame *f = &vm->frames[selected_frame]; - const char *fname = (f->fn && f->fn->name) ? f->fn->name : ""; - const char *sfile = (f->fn && f->fn->source_file) ? f->fn->source_file : ""; - printf("Selected frame #%d: %s (%s)\n", selected_frame, fname, sfile); - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"list","li", NULL})) { - int k = 5; - const char *p = lstrip(arg); - if (p && *p) k = atoi(p); - if (k <= 0) k = 5; - int idx = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp; - if (idx < 0) { printf("(no current frame)\n"); continue; } - Frame *f = &vm->frames[idx]; - if (!f->fn || !f->fn->source_file) { printf("(no source info)\n"); continue; } - /* derive current line for this frame by scanning LINE markers up to ip-1 */ - int line = vm->current_line; - int upto = f->ip - 1; - if (upto < 0) upto = 0; - for (int i = 0; i <= upto && i < f->fn->instr_count; ++i) { - Instruction ins = f->fn->instructions[i]; - if (ins.op == OP_LINE) line = ins.operand; - } - const char *path = f->fn->source_file; - size_t flen = 0; - char *src = read_entire_file(path, &flen); - if (!src) { printf("Unable to read %s\n", path); continue; } - int start = line - k; if (start < 1) start = 1; - int end = line + k; - int cur = 1; - const char *s = src; - while (*s && cur <= end) { - const char *ls = s; - while (*s && *s != '\n') s++; - int print = (cur >= start && cur <= end); - if (print) { - printf("%c %5d | ", (cur == line ? '>' : ' '), cur); - fwrite(ls, 1, (size_t)(s - ls), stdout); - printf("\n"); - } - if (*s == '\n') s++; - cur++; - } - free(src); - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"disasm","disassemble","di", NULL})) { - int n = 5; - const char *p = lstrip(arg); - if (p && *p) n = atoi(p); - if (n <= 0) n = 5; - int idx = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp; - if (idx < 0) { printf("(no current frame)\n"); continue; } - Frame *f = &vm->frames[idx]; - if (!f->fn) { printf("(no function)\n"); continue; } - - /* Ensure we have a valid instruction buffer */ - if (f->fn->instr_count <= 0 || f->fn->instructions == NULL) { - printf("(no instructions)\n"); - continue; - } - - int count = f->fn->instr_count; - int curip = f->ip - 1; - if (curip < 0) curip = 0; - if (curip >= count) curip = count - 1; - - int from = curip - n; if (from < 0) from = 0; - int to = curip + n; if (to >= count) to = count - 1; - if (to < from) { /* nothing to show */ continue; } - - for (int i = from; i <= to; ++i) { - Instruction ins = f->fn->instructions[i]; - const char *opname = opcode_is_valid(ins.op) ? opcode_names[ins.op] : "???"; - printf("%c %6d: %-14s %d\n", (i == curip ? '>' : ' '), i, opname, ins.operand); - } - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"mdump","md", NULL})) { - /* Syntax: :mdump WHAT [offset [len]] [raw] [to ] - WHAT: code | stack | globals | consts - 'raw' writes binary bytes instead of a formatted hexdump */ - const char *p = lstrip(arg); - if (!p || !*p) { - printf("Usage: :mdump WHAT [offset [len]] [raw] [to ]\n"); - printf(" WHAT = code | stack | globals | consts\n"); - printf(" 'raw' writes binary bytes instead of a formatted hexdump\n"); - continue; - } - - char what[32]; - int consumed = 0; - if (sscanf(p, "%31s %n", what, &consumed) != 1) { - printf("Usage: :mdump WHAT [offset [len]] [raw] [to ]\n"); - continue; - } - p += consumed; - - size_t off = 0; - size_t len = (size_t)-1; /* default later to clamp */ - int want_raw = 0; /* output raw bytes instead of hexdump */ - - /* parse optional off */ - while (*p == ' ' || *p == '\t') p++; - if (*p && (isdigit((unsigned char)*p))) { - char *endp = NULL; - long long v = strtoll(p, &endp, 10); - if (endp && endp != p && v >= 0) { - off = (size_t)v; - p = endp; - } - } - /* parse optional len */ - while (*p == ' ' || *p == '\t') p++; - if (*p && (isdigit((unsigned char)*p))) { - char *endp = NULL; - long long v = strtoll(p, &endp, 10); - if (endp && endp != p && v >= 0) { - len = (size_t)v; - p = endp; - } - } - - /* optional: 'raw' keyword */ - while (*p == ' ' || *p == '\t') p++; - if (strncmp(p, "raw", 3) == 0 && (p[3] == '\0' || isspace((unsigned char)p[3]))) { - want_raw = 1; - p += 3; - } - - /* optional: to */ - while (*p == ' ' || *p == '\t') p++; - int to_file = 0; - char path[PATH_MAX]; - path[0] = '\0'; - if (strncmp(p, "to", 2) == 0 && (p[2] == '\0' || isspace((unsigned char)p[2]))) { - p += 2; - while (*p == ' ' || *p == '\t') p++; - if (!*p) { printf("Missing after 'to'\n"); continue; } - /* read until whitespace end or end of string; simple paths without spaces */ - size_t i = 0; - while (*p && !isspace((unsigned char)*p) && i + 1 < sizeof(path)) path[i++] = *p++; - path[i] = '\0'; - if (path[0] == '\0') { printf("Invalid file path\n"); continue; } - to_file = 1; - } - - const unsigned char *base = NULL; - size_t total = 0; - int ok_region = 1; - - if (strcmp(what, "code") == 0) { - int idx = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp; - if (idx < 0) { printf("(no current frame)\n"); continue; } - Frame *fr = &vm->frames[idx]; - if (!fr->fn || fr->fn->instr_count <= 0 || fr->fn->instructions == NULL) { - printf("(no code to dump)\n"); continue; - } - base = (const unsigned char*)fr->fn->instructions; - total = (size_t)fr->fn->instr_count * sizeof(Instruction); - } else if (strcmp(what, "consts") == 0) { - int idx = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp; - if (idx < 0) { printf("(no current frame)\n"); continue; } - Frame *fr = &vm->frames[idx]; - if (!fr->fn || fr->fn->const_count <= 0 || fr->fn->constants == NULL) { - printf("(no constants to dump)\n"); continue; - } - base = (const unsigned char*)fr->fn->constants; - total = (size_t)fr->fn->const_count * sizeof(Value); - } else if (strcmp(what, "stack") == 0) { - int count = vm->sp + 1; - if (count <= 0) { printf("(stack empty)\n"); continue; } - base = (const unsigned char*)vm->stack; - total = (size_t)count * sizeof(Value); - } else if (strcmp(what, "globals") == 0) { - base = (const unsigned char*)vm->globals; - total = (size_t)MAX_GLOBALS * sizeof(Value); - } else { - ok_region = 0; - } - - if (!ok_region) { - printf("Unknown region '%s'. Use one of: code, stack, globals, consts\n", what); - continue; - } - if (!base || total == 0) { printf("(nothing to dump)\n"); continue; } - - if (off >= total) { printf("(empty range: offset beyond end)\n"); continue; } - size_t avail = total - off; - if (len == (size_t)-1 || len == 0 || len > avail) { - /* default to min(256, avail) */ - len = avail < 256 ? avail : 256; - } - - if (!to_file) { - if (want_raw) { - /* Write raw bytes directly to stdout with no header */ - fwrite(base + off, 1, len, stdout); - fflush(stdout); - } else { - printf("Hexdump %s: total=%zu, offset=%zu, len=%zu\n", what, total, off, len); - hexdump_to(stdout, base + off, len, off); - } - } else { - FILE *fout = fopen(path, "wb"); - if (!fout) { printf("Failed to open '%s' for writing\n", path); continue; } - if (want_raw) { - fwrite(base + off, 1, len, fout); - fclose(fout); - printf("Wrote raw bytes (%zu from %s) to %s\n", len, what, path); - } else { - hexdump_to(fout, base + off, len, off); - fclose(fout); - printf("Wrote hexdump (%zu bytes from %s) to %s\n", len, what, path); - } - } - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"printv","pv", NULL})) { - const char *spec = lstrip(arg); - if (!spec || !*spec) { printf("Usage: :printv local[i] | stack[i] | global[i]\n"); continue; } - int idx = -1; - if (sscanf(spec, "local[%d]", &idx) == 1) { - int fidx = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp; - if (fidx < 0 || idx < 0 || idx >= MAX_FRAME_LOCALS) { printf("(out of range)\n"); continue; } - Frame *f = &vm->frames[fidx]; - char *sv = value_to_string_alloc(&f->locals[idx]); - printf("%s\n", sv ? sv : "nil"); - free(sv); - } else if (sscanf(spec, "stack[%d]", &idx) == 1) { - if (idx < 0 || idx > vm->sp) { printf("(out of range)\n"); continue; } - char *sv = value_to_string_alloc(&vm->stack[idx]); - printf("%s\n", sv ? sv : "nil"); - free(sv); - } else if (sscanf(spec, "global[%d]", &idx) == 1) { - if (idx < 0 || idx >= MAX_GLOBALS) { printf("(out of range)\n"); continue; } - char *sv = value_to_string_alloc(&vm->globals[idx]); - printf("%s\n", sv ? sv : "nil"); - free(sv); - } else { - printf("Usage: :printv local[i] | stack[i] | global[i]\n"); - } - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"top","to", NULL})) { - if (vm->sp < 0) { printf("(stack empty)\n"); continue; } - char *sv = value_to_string_alloc(&vm->stack[vm->sp]); - printf("%s\n", sv ? sv : "nil"); - free(sv); - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"break","br", NULL})) { - const char *p = lstrip(arg); - if (!p || !*p) { printf("Usage: :break [file:]line\n"); continue; } - const char *colon = strchr(p, ':'); - char filebuf[1024]; - int line = 0; - if (colon) { - size_t fl = (size_t)(colon - p); - if (fl >= sizeof(filebuf)) fl = sizeof(filebuf) - 1; - memcpy(filebuf, p, fl); - filebuf[fl] = '\0'; - line = atoi(colon + 1); - } else { - int idxf = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp; - if (idxf < 0) { printf("(no current frame)\n"); continue; } - Frame *f = &vm->frames[idxf]; - const char *sf = (f->fn && f->fn->source_file) ? f->fn->source_file : NULL; - if (!sf) { printf("(no current source file)\n"); continue; } - snprintf(filebuf, sizeof(filebuf), "%s", sf); - line = atoi(p); - } - if (line <= 0) { printf("Invalid line\n"); continue; } - int id = vm_debug_add_breakpoint(vm, filebuf, line); - if (id >= 0) printf("Breakpoint %d set at %s:%d\n", id, filebuf, line); - else printf("Failed to set breakpoint\n"); - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"info","in", NULL})) { - const char *what = lstrip(arg); - if (what && strcmp(what, "breaks") == 0) { - vm_debug_list_breakpoints(vm); - } else { - printf("Usage: :info breaks\n"); - } - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"delete","de", NULL})) { - int id = atoi(lstrip(arg)); - if (vm_debug_delete_breakpoint(vm, id)) printf("Deleted breakpoint %d\n", id); - else printf("No such breakpoint %d\n", id); - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"cb", NULL})) { - vm_debug_clear_breakpoints(vm); - printf("Cleared all breakpoints\n"); - continue; - } else if (strcmp(cmd, "clear") == 0) { - const char *what = lstrip(arg); - if (what && strcmp(what, "breaks") == 0) { - vm_debug_clear_breakpoints(vm); - printf("Cleared all breakpoints\n"); - } else { - printf("Usage: :clear breaks\n"); - } - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"cont","continue","co", NULL})) { - vm_debug_request_continue(vm); - printf("Continuing...\n"); - if (vm->on_error_repl) return 0; /* exit REPL to continue execution */ - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"step","sp", NULL})) { - vm_debug_request_step(vm); - printf("Stepping one instruction...\n"); - if (vm->on_error_repl) return 0; - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"next","ne", NULL})) { - vm_debug_request_next(vm); - printf("Stepping over...\n"); - if (vm->on_error_repl) return 0; - continue; - } else if (cmd_is_one_of(cmd, (const char*[]){"finish","fi", NULL})) { - vm_debug_request_finish(vm); - printf("Running until current frame returns...\n"); - if (vm->on_error_repl) return 0; - continue; - } else { - printf("Unknown command. Use :help\n"); - continue; - } - } - - if (is_blank_line(line)) { - if (buflen == 0) continue; - - if (buflen + 1 > bufcap) { - buffer = (char*)realloc(buffer, buflen + 1); - bufcap = buflen + 1; - } - buffer[buflen] = '\0'; - - int indent_debt2 = compute_open_indent_blocks(buffer); - if (buffer_looks_incomplete(buffer) || indent_debt2 > 0) { - if (indent_debt2 > 0) { - printf("(incomplete, open block indent +%d)\n", indent_debt2); - } else { - printf("(incomplete, continue typing)\n"); - } - continue; - } - - Bytecode *bc = parse_string_to_bytecode(buffer); - if (bc) { - clock_t t0 = 0, t1 = 0; - if (repl_timing) t0 = clock(); - vm_run(vm, bc); - if (repl_timing) { - t1 = clock(); - double ms = (double)(t1 - t0) * 1000.0 / (double)CLOCKS_PER_SEC; - printf("[time] %.2f ms\n", ms); - } - vm_print_output(vm); - vm_clear_output(vm); - bytecode_free(bc); - append_history(hist, buffer); - } else { - int line_no = 0, col_no = 0; - char emsg[256]; - if (parser_last_error(emsg, sizeof(emsg), &line_no, &col_no)) { - printf("Parse error at %d:%d: %s\n", line_no, col_no, emsg); - int cur_line = 1; - const char *p = buffer; - while (*p && cur_line < line_no) { - if (*p == '\n') cur_line++; - p++; - } - const char *line_start = p; - while (*p && *p != '\n') p++; - fwrite(line_start, 1, (size_t)(p - line_start), stdout); - printf("\n"); - for (int i = 1; i < col_no; ++i) putchar(' '); - printf("^\n"); -#ifdef FUN_DEBUG - if (hist) { - fprintf(hist, "// ERROR %d:%d: %s\n", line_no, col_no, emsg); - fflush(hist); - } -#endif - } else { - printf("Parse error.\n"); -#ifdef FUN_DEBUG - if (hist) { - fprintf(hist, "// ERROR: parse error\n"); - fflush(hist); - } -#endif - } - } - buflen = 0; - continue; - } - - size_t linelen = strlen(line); - if (buflen + linelen + 1 > bufcap) { + } + if ((strcmp(pline, ".\n") == 0) || (strcmp(pline, ".\r\n") == 0) || (strcmp(pline, ".") == 0)) { + break; + } + size_t pl = strlen(pline); + if (buflen + pl + 1 > bufcap) { size_t newcap = bufcap == 0 ? 1024 : bufcap * 2; - while (newcap < buflen + linelen + 1) newcap *= 2; - buffer = (char*)realloc(buffer, newcap); + while (newcap < buflen + pl + 1) + newcap *= 2; + buffer = (char *)realloc(buffer, newcap); bufcap = newcap; + } + memcpy(buffer + buflen, pline, pl); + buflen += pl; } - memcpy(buffer + buflen, line, linelen); - buflen += linelen; + if (run_after) { + if (buflen + 1 > bufcap) { + buffer = (char *)realloc(buffer, buflen + 1); + bufcap = buflen + 1; + } + buffer[buflen] = '\0'; + Bytecode *bc = parse_string_to_bytecode(buffer); + if (bc) { + clock_t t0 = clock(); + vm_run(vm, bc); + clock_t t1 = clock(); + double ms = (double)(t1 - t0) * 1000.0 / (double)CLOCKS_PER_SEC; + printf("[time] %.2f ms\n", ms); + vm_print_output(vm); + vm_clear_output(vm); + bytecode_free(bc); + append_history(hist, buffer); + } else { + int line_no = 0, col_no = 0; + char emsg[256]; + if (parser_last_error(emsg, sizeof(emsg), &line_no, &col_no)) { + printf("Parse error at %d:%d: %s\n", line_no, col_no, emsg); +#ifdef FUN_DEBUG + if (hist) { + fprintf(hist, "// ERROR %d:%d: %s\n", line_no, col_no, emsg); + fflush(hist); + } +#endif + } else { + printf("Parse error.\n"); +#ifdef FUN_DEBUG + if (hist) { + fprintf(hist, "// ERROR: parse error\n"); + fflush(hist); + } +#endif + } + } + buflen = 0; + } else { + printf("(pasted %zu bytes into buffer)\n", buflen); + } + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"history", "hi", NULL})) { + int n = 50; + if (arg[0] != '\0') n = atoi(arg); + if (n <= 0) n = 50; + print_last_n_lines(hist_path, n); + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"time", "ti", NULL})) { + if (strcmp(lstrip(arg), "on") == 0) + repl_timing = 1; + else if (strcmp(lstrip(arg), "off") == 0) + repl_timing = 0; + else if (strcmp(lstrip(arg), "toggle") == 0) + repl_timing = !repl_timing; + else { + printf("Usage: :time on|off|toggle (currently %s)\n", repl_timing ? "on" : "off"); + continue; + } + printf("Timing %s\n", repl_timing ? "enabled" : "disabled"); + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"env", "en", NULL})) { + const char *spec = lstrip(arg); + if (!spec || *spec == '\0') { + env_show_usage(); + continue; + } + const char *eq = strchr(spec, '='); + if (!eq) { + env_get(spec); + } else { + char name[256]; + size_t nlen = (size_t)(eq - spec); + if (nlen >= sizeof(name)) nlen = sizeof(name) - 1; + memcpy(name, spec, nlen); + name[nlen] = '\0'; + const char *val = eq + 1; + env_set(name, val); + } + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"backtrace", "bt", "ba", NULL})) { + if (vm->fp < 0) { + printf("(no frames)\n"); + continue; + } + printf("Backtrace (most recent call first):\n"); + for (int i = vm->fp; i >= 0; --i) { + Frame *f = &vm->frames[i]; + const char *fname = (f->fn && f->fn->name) ? f->fn->name : ""; + const char *sfile = (f->fn && f->fn->source_file) ? f->fn->source_file : ""; + int ip = f->ip - 1; + printf(" #%d %s at %s ip=%d line=%d\n", i, fname, sfile, ip, vm->current_line); + } + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"stack", "st", NULL})) { + int n = -1; + const char *p = lstrip(arg); + if (p && *p) n = atoi(p); + int count = vm->sp + 1; + if (count <= 0) { + printf("(stack empty)\n"); + continue; + } + int start = 0; + if (n > 0 && n < count) start = count - n; + printf("Stack size=%d\n", count); + for (int i = start; i < count; ++i) { + char *sv = value_to_string_alloc(&vm->stack[i]); + printf("[%d] %s\n", i, sv ? sv : "nil"); + free(sv); + } + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"locals", "lc", NULL})) { + int idx = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp; + const char *p = lstrip(arg); + if (p && *p) { + int v = atoi(p); + if (v >= 0 && v <= vm->fp) idx = v; + } + if (idx < 0) { + printf("(no current frame)\n"); + continue; + } + Frame *f = &vm->frames[idx]; + const char *fname = (f->fn && f->fn->name) ? f->fn->name : ""; + printf("Locals in frame #%d (%s):\n", idx, fname); + int any = 0; + for (int i = 0; i < MAX_FRAME_LOCALS; ++i) { + if (f->locals[i].type != VAL_NIL) { + char *sv = value_to_string_alloc(&f->locals[i]); + printf(" %d: %s\n", i, sv ? sv : "nil"); + free(sv); + any = 1; + } + } + if (!any) printf(" (no non-nil locals)\n"); + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"frame", "fr", NULL})) { + const char *p = lstrip(arg); + if (!p || !*p) { + printf("Usage: :frame N\n"); + continue; + } + int v = atoi(p); + if (v < 0 || v > vm->fp) { + printf("Invalid frame index. Current top is %d\n", vm->fp); + continue; + } + selected_frame = v; + Frame *f = &vm->frames[selected_frame]; + const char *fname = (f->fn && f->fn->name) ? f->fn->name : ""; + const char *sfile = (f->fn && f->fn->source_file) ? f->fn->source_file : ""; + printf("Selected frame #%d: %s (%s)\n", selected_frame, fname, sfile); + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"list", "li", NULL})) { + int k = 5; + const char *p = lstrip(arg); + if (p && *p) k = atoi(p); + if (k <= 0) k = 5; + int idx = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp; + if (idx < 0) { + printf("(no current frame)\n"); + continue; + } + Frame *f = &vm->frames[idx]; + if (!f->fn || !f->fn->source_file) { + printf("(no source info)\n"); + continue; + } + /* derive current line for this frame by scanning LINE markers up to ip-1 */ + int line = vm->current_line; + int upto = f->ip - 1; + if (upto < 0) upto = 0; + for (int i = 0; i <= upto && i < f->fn->instr_count; ++i) { + Instruction ins = f->fn->instructions[i]; + if (ins.op == OP_LINE) line = ins.operand; + } + const char *path = f->fn->source_file; + size_t flen = 0; + char *src = read_entire_file(path, &flen); + if (!src) { + printf("Unable to read %s\n", path); + continue; + } + int start = line - k; + if (start < 1) start = 1; + int end = line + k; + int cur = 1; + const char *s = src; + while (*s && cur <= end) { + const char *ls = s; + while (*s && *s != '\n') + s++; + int print = (cur >= start && cur <= end); + if (print) { + printf("%c %5d | ", (cur == line ? '>' : ' '), cur); + fwrite(ls, 1, (size_t)(s - ls), stdout); + printf("\n"); + } + if (*s == '\n') s++; + cur++; + } + free(src); + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"disasm", "disassemble", "di", NULL})) { + int n = 5; + const char *p = lstrip(arg); + if (p && *p) n = atoi(p); + if (n <= 0) n = 5; + int idx = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp; + if (idx < 0) { + printf("(no current frame)\n"); + continue; + } + Frame *f = &vm->frames[idx]; + if (!f->fn) { + printf("(no function)\n"); + continue; + } + + /* Ensure we have a valid instruction buffer */ + if (f->fn->instr_count <= 0 || f->fn->instructions == NULL) { + printf("(no instructions)\n"); + continue; + } + + int count = f->fn->instr_count; + int curip = f->ip - 1; + if (curip < 0) curip = 0; + if (curip >= count) curip = count - 1; + + int from = curip - n; + if (from < 0) from = 0; + int to = curip + n; + if (to >= count) to = count - 1; + if (to < from) { /* nothing to show */ + continue; + } + + for (int i = from; i <= to; ++i) { + Instruction ins = f->fn->instructions[i]; + const char *opname = opcode_is_valid(ins.op) ? opcode_names[ins.op] : "???"; + printf("%c %6d: %-14s %d\n", (i == curip ? '>' : ' '), i, opname, ins.operand); + } + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"mdump", "md", NULL})) { + /* Syntax: :mdump WHAT [offset [len]] [raw] [to ] + WHAT: code | stack | globals | consts + 'raw' writes binary bytes instead of a formatted hexdump */ + const char *p = lstrip(arg); + if (!p || !*p) { + printf("Usage: :mdump WHAT [offset [len]] [raw] [to ]\n"); + printf(" WHAT = code | stack | globals | consts\n"); + printf(" 'raw' writes binary bytes instead of a formatted hexdump\n"); + continue; + } + + char what[32]; + int consumed = 0; + if (sscanf(p, "%31s %n", what, &consumed) != 1) { + printf("Usage: :mdump WHAT [offset [len]] [raw] [to ]\n"); + continue; + } + p += consumed; + + size_t off = 0; + size_t len = (size_t)-1; /* default later to clamp */ + int want_raw = 0; /* output raw bytes instead of hexdump */ + + /* parse optional off */ + while (*p == ' ' || *p == '\t') + p++; + if (*p && (isdigit((unsigned char)*p))) { + char *endp = NULL; + long long v = strtoll(p, &endp, 10); + if (endp && endp != p && v >= 0) { + off = (size_t)v; + p = endp; + } + } + /* parse optional len */ + while (*p == ' ' || *p == '\t') + p++; + if (*p && (isdigit((unsigned char)*p))) { + char *endp = NULL; + long long v = strtoll(p, &endp, 10); + if (endp && endp != p && v >= 0) { + len = (size_t)v; + p = endp; + } + } + + /* optional: 'raw' keyword */ + while (*p == ' ' || *p == '\t') + p++; + if (strncmp(p, "raw", 3) == 0 && (p[3] == '\0' || isspace((unsigned char)p[3]))) { + want_raw = 1; + p += 3; + } + + /* optional: to */ + while (*p == ' ' || *p == '\t') + p++; + int to_file = 0; + char path[PATH_MAX]; + path[0] = '\0'; + if (strncmp(p, "to", 2) == 0 && (p[2] == '\0' || isspace((unsigned char)p[2]))) { + p += 2; + while (*p == ' ' || *p == '\t') + p++; + if (!*p) { + printf("Missing after 'to'\n"); + continue; + } + /* read until whitespace end or end of string; simple paths without spaces */ + size_t i = 0; + while (*p && !isspace((unsigned char)*p) && i + 1 < sizeof(path)) + path[i++] = *p++; + path[i] = '\0'; + if (path[0] == '\0') { + printf("Invalid file path\n"); + continue; + } + to_file = 1; + } + + const unsigned char *base = NULL; + size_t total = 0; + int ok_region = 1; + + if (strcmp(what, "code") == 0) { + int idx = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp; + if (idx < 0) { + printf("(no current frame)\n"); + continue; + } + Frame *fr = &vm->frames[idx]; + if (!fr->fn || fr->fn->instr_count <= 0 || fr->fn->instructions == NULL) { + printf("(no code to dump)\n"); + continue; + } + base = (const unsigned char *)fr->fn->instructions; + total = (size_t)fr->fn->instr_count * sizeof(Instruction); + } else if (strcmp(what, "consts") == 0) { + int idx = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp; + if (idx < 0) { + printf("(no current frame)\n"); + continue; + } + Frame *fr = &vm->frames[idx]; + if (!fr->fn || fr->fn->const_count <= 0 || fr->fn->constants == NULL) { + printf("(no constants to dump)\n"); + continue; + } + base = (const unsigned char *)fr->fn->constants; + total = (size_t)fr->fn->const_count * sizeof(Value); + } else if (strcmp(what, "stack") == 0) { + int count = vm->sp + 1; + if (count <= 0) { + printf("(stack empty)\n"); + continue; + } + base = (const unsigned char *)vm->stack; + total = (size_t)count * sizeof(Value); + } else if (strcmp(what, "globals") == 0) { + base = (const unsigned char *)vm->globals; + total = (size_t)MAX_GLOBALS * sizeof(Value); + } else { + ok_region = 0; + } + + if (!ok_region) { + printf("Unknown region '%s'. Use one of: code, stack, globals, consts\n", what); + continue; + } + if (!base || total == 0) { + printf("(nothing to dump)\n"); + continue; + } + + if (off >= total) { + printf("(empty range: offset beyond end)\n"); + continue; + } + size_t avail = total - off; + if (len == (size_t)-1 || len == 0 || len > avail) { + /* default to min(256, avail) */ + len = avail < 256 ? avail : 256; + } + + if (!to_file) { + if (want_raw) { + /* Write raw bytes directly to stdout with no header */ + fwrite(base + off, 1, len, stdout); + fflush(stdout); + } else { + printf("Hexdump %s: total=%zu, offset=%zu, len=%zu\n", what, total, off, len); + hexdump_to(stdout, base + off, len, off); + } + } else { + FILE *fout = fopen(path, "wb"); + if (!fout) { + printf("Failed to open '%s' for writing\n", path); + continue; + } + if (want_raw) { + fwrite(base + off, 1, len, fout); + fclose(fout); + printf("Wrote raw bytes (%zu from %s) to %s\n", len, what, path); + } else { + hexdump_to(fout, base + off, len, off); + fclose(fout); + printf("Wrote hexdump (%zu bytes from %s) to %s\n", len, what, path); + } + } + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"printv", "pv", NULL})) { + const char *spec = lstrip(arg); + if (!spec || !*spec) { + printf("Usage: :printv local[i] | stack[i] | global[i]\n"); + continue; + } + int idx = -1; + if (sscanf(spec, "local[%d]", &idx) == 1) { + int fidx = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp; + if (fidx < 0 || idx < 0 || idx >= MAX_FRAME_LOCALS) { + printf("(out of range)\n"); + continue; + } + Frame *f = &vm->frames[fidx]; + char *sv = value_to_string_alloc(&f->locals[idx]); + printf("%s\n", sv ? sv : "nil"); + free(sv); + } else if (sscanf(spec, "stack[%d]", &idx) == 1) { + if (idx < 0 || idx > vm->sp) { + printf("(out of range)\n"); + continue; + } + char *sv = value_to_string_alloc(&vm->stack[idx]); + printf("%s\n", sv ? sv : "nil"); + free(sv); + } else if (sscanf(spec, "global[%d]", &idx) == 1) { + if (idx < 0 || idx >= MAX_GLOBALS) { + printf("(out of range)\n"); + continue; + } + char *sv = value_to_string_alloc(&vm->globals[idx]); + printf("%s\n", sv ? sv : "nil"); + free(sv); + } else { + printf("Usage: :printv local[i] | stack[i] | global[i]\n"); + } + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"top", "to", NULL})) { + if (vm->sp < 0) { + printf("(stack empty)\n"); + continue; + } + char *sv = value_to_string_alloc(&vm->stack[vm->sp]); + printf("%s\n", sv ? sv : "nil"); + free(sv); + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"break", "br", NULL})) { + const char *p = lstrip(arg); + if (!p || !*p) { + printf("Usage: :break [file:]line\n"); + continue; + } + const char *colon = strchr(p, ':'); + char filebuf[1024]; + int line = 0; + if (colon) { + size_t fl = (size_t)(colon - p); + if (fl >= sizeof(filebuf)) fl = sizeof(filebuf) - 1; + memcpy(filebuf, p, fl); + filebuf[fl] = '\0'; + line = atoi(colon + 1); + } else { + int idxf = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp; + if (idxf < 0) { + printf("(no current frame)\n"); + continue; + } + Frame *f = &vm->frames[idxf]; + const char *sf = (f->fn && f->fn->source_file) ? f->fn->source_file : NULL; + if (!sf) { + printf("(no current source file)\n"); + continue; + } + snprintf(filebuf, sizeof(filebuf), "%s", sf); + line = atoi(p); + } + if (line <= 0) { + printf("Invalid line\n"); + continue; + } + int id = vm_debug_add_breakpoint(vm, filebuf, line); + if (id >= 0) + printf("Breakpoint %d set at %s:%d\n", id, filebuf, line); + else + printf("Failed to set breakpoint\n"); + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"info", "in", NULL})) { + const char *what = lstrip(arg); + if (what && strcmp(what, "breaks") == 0) { + vm_debug_list_breakpoints(vm); + } else { + printf("Usage: :info breaks\n"); + } + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"delete", "de", NULL})) { + int id = atoi(lstrip(arg)); + if (vm_debug_delete_breakpoint(vm, id)) + printf("Deleted breakpoint %d\n", id); + else + printf("No such breakpoint %d\n", id); + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"cb", NULL})) { + vm_debug_clear_breakpoints(vm); + printf("Cleared all breakpoints\n"); + continue; + } else if (strcmp(cmd, "clear") == 0) { + const char *what = lstrip(arg); + if (what && strcmp(what, "breaks") == 0) { + vm_debug_clear_breakpoints(vm); + printf("Cleared all breakpoints\n"); + } else { + printf("Usage: :clear breaks\n"); + } + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"cont", "continue", "co", NULL})) { + vm_debug_request_continue(vm); + printf("Continuing...\n"); + if (vm->on_error_repl) return 0; /* exit REPL to continue execution */ + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"step", "sp", NULL})) { + vm_debug_request_step(vm); + printf("Stepping one instruction...\n"); + if (vm->on_error_repl) return 0; + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"next", "ne", NULL})) { + vm_debug_request_next(vm); + printf("Stepping over...\n"); + if (vm->on_error_repl) return 0; + continue; + } else if (cmd_is_one_of(cmd, (const char *[]){"finish", "fi", NULL})) { + vm_debug_request_finish(vm); + printf("Running until current frame returns...\n"); + if (vm->on_error_repl) return 0; + continue; + } else { + printf("Unknown command. Use :help\n"); + continue; + } } - if (hist) fclose(hist); - free_stdlib_symbols(); - free(buffer); - return 0; + if (is_blank_line(line)) { + if (buflen == 0) continue; + + if (buflen + 1 > bufcap) { + buffer = (char *)realloc(buffer, buflen + 1); + bufcap = buflen + 1; + } + buffer[buflen] = '\0'; + + int indent_debt2 = compute_open_indent_blocks(buffer); + if (buffer_looks_incomplete(buffer) || indent_debt2 > 0) { + if (indent_debt2 > 0) { + printf("(incomplete, open block indent +%d)\n", indent_debt2); + } else { + printf("(incomplete, continue typing)\n"); + } + continue; + } + + Bytecode *bc = parse_string_to_bytecode(buffer); + if (bc) { + clock_t t0 = 0, t1 = 0; + if (repl_timing) t0 = clock(); + vm_run(vm, bc); + if (repl_timing) { + t1 = clock(); + double ms = (double)(t1 - t0) * 1000.0 / (double)CLOCKS_PER_SEC; + printf("[time] %.2f ms\n", ms); + } + vm_print_output(vm); + vm_clear_output(vm); + bytecode_free(bc); + append_history(hist, buffer); + } else { + int line_no = 0, col_no = 0; + char emsg[256]; + if (parser_last_error(emsg, sizeof(emsg), &line_no, &col_no)) { + printf("Parse error at %d:%d: %s\n", line_no, col_no, emsg); + int cur_line = 1; + const char *p = buffer; + while (*p && cur_line < line_no) { + if (*p == '\n') cur_line++; + p++; + } + const char *line_start = p; + while (*p && *p != '\n') + p++; + fwrite(line_start, 1, (size_t)(p - line_start), stdout); + printf("\n"); + for (int i = 1; i < col_no; ++i) + putchar(' '); + printf("^\n"); +#ifdef FUN_DEBUG + if (hist) { + fprintf(hist, "// ERROR %d:%d: %s\n", line_no, col_no, emsg); + fflush(hist); + } +#endif + } else { + printf("Parse error.\n"); +#ifdef FUN_DEBUG + if (hist) { + fprintf(hist, "// ERROR: parse error\n"); + fflush(hist); + } +#endif + } + } + buflen = 0; + continue; + } + + size_t linelen = strlen(line); + if (buflen + linelen + 1 > bufcap) { + size_t newcap = bufcap == 0 ? 1024 : bufcap * 2; + while (newcap < buflen + linelen + 1) + newcap *= 2; + buffer = (char *)realloc(buffer, newcap); + bufcap = newcap; + } + memcpy(buffer + buflen, line, linelen); + buflen += linelen; + } + + if (hist) fclose(hist); + free_stdlib_symbols(); + free(buffer); + return 0; } #endif /* FUN_WITH_REPL */ diff --git a/src/str_utils.c b/src/str_utils.c index ee00330..ca969c1 100644 --- a/src/str_utils.c +++ b/src/str_utils.c @@ -14,121 +14,126 @@ /* string helpers returning newly allocated C strings or arrays */ char *string_substr(const char *s, int start, int len) { - if (!s) return strdup(""); - int n = (int)strlen(s); - if (start < 0) start = 0; - if (start > n) start = n; - if (len < 0) len = 0; - if (start + len > n) len = n - start; - char *out = (char*)malloc((size_t)len + 1); - if (!out) return strdup(""); - memcpy(out, s + start, (size_t)len); - out[len] = '\0'; - return out; + if (!s) return strdup(""); + int n = (int)strlen(s); + if (start < 0) start = 0; + if (start > n) start = n; + if (len < 0) len = 0; + if (start + len > n) len = n - start; + char *out = (char *)malloc((size_t)len + 1); + if (!out) return strdup(""); + memcpy(out, s + start, (size_t)len); + out[len] = '\0'; + return out; } int string_find(const char *hay, const char *needle) { - if (!hay || !needle) return -1; - const char *p = strstr(hay, needle); - if (!p) return -1; - return (int)(p - hay); + if (!hay || !needle) return -1; + const char *p = strstr(hay, needle); + if (!p) return -1; + return (int)(p - hay); } Value string_split_to_array(const char *s, const char *sep) { - if (!s) s = ""; - if (!sep) sep = ""; - int seplen = (int)strlen(sep); - if (seplen == 0) { - /* split into characters */ - int n = (int)strlen(s); - if (n <= 0) return make_array_from_values(NULL, 0); - Value *tmp = (Value*)malloc(sizeof(Value) * n); - if (!tmp) return make_array_from_values(NULL, 0); - for (int i = 0; i < n; ++i) { - char ch[2] = { s[i], 0 }; - tmp[i] = make_string(ch); - } - Value arr = make_array_from_values(tmp, n); - for (int i = 0; i < n; ++i) free_value(tmp[i]); - free(tmp); - return arr; + if (!s) s = ""; + if (!sep) sep = ""; + int seplen = (int)strlen(sep); + if (seplen == 0) { + /* split into characters */ + int n = (int)strlen(s); + if (n <= 0) return make_array_from_values(NULL, 0); + Value *tmp = (Value *)malloc(sizeof(Value) * n); + if (!tmp) return make_array_from_values(NULL, 0); + for (int i = 0; i < n; ++i) { + char ch[2] = {s[i], 0}; + tmp[i] = make_string(ch); } - /* split by separator */ - Value *parts = NULL; - int count = 0; - int cap = 0; - const char *cur = s; - const char *pos = NULL; - while ((pos = strstr(cur, sep)) != NULL) { - int len = (int)(pos - cur); - char *piece = (char*)malloc((size_t)len + 1); - if (!piece) break; - memcpy(piece, cur, (size_t)len); - piece[len] = '\0'; - if (count >= cap) { - cap = cap == 0 ? 4 : cap * 2; - parts = (Value*)realloc(parts, sizeof(Value) * cap); - } - parts[count++] = make_string(piece); - free(piece); - cur = pos + seplen; - } - /* tail */ - char *tail = strdup(cur ? cur : ""); - if (count >= cap) { - cap = cap == 0 ? 1 : cap + 1; - parts = (Value*)realloc(parts, sizeof(Value) * cap); - } - parts[count++] = make_string(tail ? tail : ""); - free(tail); - - Value arr = make_array_from_values(parts, count); - for (int i = 0; i < count; ++i) free_value(parts[i]); - free(parts); + Value arr = make_array_from_values(tmp, n); + for (int i = 0; i < n; ++i) + free_value(tmp[i]); + free(tmp); return arr; + } + /* split by separator */ + Value *parts = NULL; + int count = 0; + int cap = 0; + const char *cur = s; + const char *pos = NULL; + while ((pos = strstr(cur, sep)) != NULL) { + int len = (int)(pos - cur); + char *piece = (char *)malloc((size_t)len + 1); + if (!piece) break; + memcpy(piece, cur, (size_t)len); + piece[len] = '\0'; + if (count >= cap) { + cap = cap == 0 ? 4 : cap * 2; + parts = (Value *)realloc(parts, sizeof(Value) * cap); + } + parts[count++] = make_string(piece); + free(piece); + cur = pos + seplen; + } + /* tail */ + char *tail = strdup(cur ? cur : ""); + if (count >= cap) { + cap = cap == 0 ? 1 : cap + 1; + parts = (Value *)realloc(parts, sizeof(Value) * cap); + } + parts[count++] = make_string(tail ? tail : ""); + free(tail); + + Value arr = make_array_from_values(parts, count); + for (int i = 0; i < count; ++i) + free_value(parts[i]); + free(parts); + return arr; } char *array_join_with_sep(const Value *v, const char *sep) { - if (!v || v->type != VAL_ARRAY || !v->arr) return strdup(""); - if (!sep) sep = ""; - /* Array is defined in value.c; we only need safe public access. */ - const int n = array_length(v); - if (n <= 0) return strdup(""); + if (!v || v->type != VAL_ARRAY || !v->arr) return strdup(""); + if (!sep) sep = ""; + /* Array is defined in value.c; we only need safe public access. */ + const int n = array_length(v); + if (n <= 0) return strdup(""); - char **parts = (char**)malloc(sizeof(char*) * n); - if (!parts) return strdup(""); + char **parts = (char **)malloc(sizeof(char *) * n); + if (!parts) return strdup(""); - size_t total = 0; - for (int i = 0; i < n; ++i) { - Value item; - if (!array_get_copy(v, i, &item)) { - parts[i] = strdup(""); - } else { - parts[i] = value_to_string_alloc(&item); - free_value(item); - } - total += strlen(parts[i]); - if (i + 1 < n) total += strlen(sep); + size_t total = 0; + for (int i = 0; i < n; ++i) { + Value item; + if (!array_get_copy(v, i, &item)) { + parts[i] = strdup(""); + } else { + parts[i] = value_to_string_alloc(&item); + free_value(item); } + total += strlen(parts[i]); + if (i + 1 < n) total += strlen(sep); + } - char *out = (char*)malloc(total + 1); - if (!out) { - for (int i = 0; i < n; ++i) free(parts[i]); - free(parts); - return strdup(""); - } - - size_t off = 0; - for (int i = 0; i < n; ++i) { - size_t li = strlen(parts[i]); - memcpy(out + off, parts[i], li); off += li; - if (i + 1 < n) { - size_t ls = strlen(sep); - memcpy(out + off, sep, ls); off += ls; - } - free(parts[i]); - } + char *out = (char *)malloc(total + 1); + if (!out) { + for (int i = 0; i < n; ++i) + free(parts[i]); free(parts); - out[off] = '\0'; - return out; + return strdup(""); + } + + size_t off = 0; + for (int i = 0; i < n; ++i) { + size_t li = strlen(parts[i]); + memcpy(out + off, parts[i], li); + off += li; + if (i + 1 < n) { + size_t ls = strlen(sep); + memcpy(out + off, sep, ls); + off += ls; + } + free(parts[i]); + } + free(parts); + out[off] = '\0'; + return out; } diff --git a/src/string.c b/src/string.c index 46805e5..ccaa3b7 100644 --- a/src/string.c +++ b/src/string.c @@ -13,29 +13,29 @@ /* String built-ins wrappers used by VM opcodes */ Value bi_split(const Value *str, const Value *sep) { - const char *s = (str && str->type == VAL_STRING && str->s) ? str->s : ""; - const char *p = (sep && sep->type == VAL_STRING && sep->s) ? sep->s : ""; - return string_split_to_array(s, p); + const char *s = (str && str->type == VAL_STRING && str->s) ? str->s : ""; + const char *p = (sep && sep->type == VAL_STRING && sep->s) ? sep->s : ""; + return string_split_to_array(s, p); } Value bi_join(const Value *arr, const Value *sep) { - const char *p = (sep && sep->type == VAL_STRING && sep->s) ? sep->s : ""; - char *s = array_join_with_sep(arr, p); - Value out = make_string(s ? s : ""); - if (s) free(s); - return out; + const char *p = (sep && sep->type == VAL_STRING && sep->s) ? sep->s : ""; + char *s = array_join_with_sep(arr, p); + Value out = make_string(s ? s : ""); + if (s) free(s); + return out; } Value bi_substr(const Value *str, int start, int len) { - const char *s = (str && str->type == VAL_STRING && str->s) ? str->s : ""; - char *sub = string_substr(s, start, len); - Value out = make_string(sub ? sub : ""); - if (sub) free(sub); - return out; + const char *s = (str && str->type == VAL_STRING && str->s) ? str->s : ""; + char *sub = string_substr(s, start, len); + Value out = make_string(sub ? sub : ""); + if (sub) free(sub); + return out; } int bi_find(const Value *hay, const Value *needle) { - const char *h = (hay && hay->type == VAL_STRING && hay->s) ? hay->s : ""; - const char *n = (needle && needle->type == VAL_STRING && needle->s) ? needle->s : ""; - return string_find(h, n); + const char *h = (hay && hay->type == VAL_STRING && hay->s) ? hay->s : ""; + const char *n = (needle && needle->type == VAL_STRING && needle->s) ? needle->s : ""; + return string_find(h, n); } diff --git a/src/test_opcodes.c b/src/test_opcodes.c index db79283..45758d8 100644 --- a/src/test_opcodes.c +++ b/src/test_opcodes.c @@ -7,69 +7,69 @@ * https://opensource.org/license/apache-2-0 */ -#include "vm.h" #include "bytecode.h" #include "value.h" +#include "vm.h" #include int main() { - VM vm; - vm_init(&vm); - - Bytecode *bc = bytecode_new(); + VM vm; + vm_init(&vm); - // Example: test OP_ADD - int c1 = bytecode_add_constant(bc, make_int(5)); - int c2 = bytecode_add_constant(bc, make_int(3)); - bytecode_add_instruction(bc, OP_LOAD_CONST, c1); - bytecode_add_instruction(bc, OP_LOAD_CONST, c2); - bytecode_add_instruction(bc, OP_ADD, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); + Bytecode *bc = bytecode_new(); - printf("=== Bytecode dump ===\n"); - for (int i = 0; i < bc->instr_count; ++i) { - Instruction instr = bc->instructions[i]; - printf("instr %3d: opcode=%2d operand=%d\n", i, instr.op, instr.operand); - } - printf("=====================\n"); - bytecode_dump(bc); - printf("=====================\n"); - - vm_run(&vm, bc); + // Example: test OP_ADD + int c1 = bytecode_add_constant(bc, make_int(5)); + int c2 = bytecode_add_constant(bc, make_int(3)); + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_LOAD_CONST, c2); + bytecode_add_instruction(bc, OP_ADD, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); - printf("Output count: %d\n", vm.output_count); - for (int i = 0; i < vm.output_count; i++) { - printf("Output[%d] = ", i); - print_value(&vm.output[i]); - printf("\n"); - } + printf("=== Bytecode dump ===\n"); + for (int i = 0; i < bc->instr_count; ++i) { + Instruction instr = bc->instructions[i]; + printf("instr %3d: opcode=%2d operand=%d\n", i, instr.op, instr.operand); + } + printf("=====================\n"); + bytecode_dump(bc); + printf("=====================\n"); - vm_clear_output(&vm); + vm_run(&vm, bc); - /* --- Rust FFI demo: call a Rust opcode and string function --- */ + printf("Output count: %d\n", vm.output_count); + for (int i = 0; i < vm.output_count; i++) { + printf("Output[%d] = ", i); + print_value(&vm.output[i]); + printf("\n"); + } + + vm_clear_output(&vm); + + /* --- Rust FFI demo: call a Rust opcode and string function --- */ #ifdef FUN_WITH_RUST - extern int fun_op_radd(VM *vm); - extern const char *fun_rust_get_string(void); + extern int fun_op_radd(VM * vm); + extern const char *fun_rust_get_string(void); - printf("=== Rust FFI demo ===\n"); - const char *rs = fun_rust_get_string(); - if (rs) { - printf("Rust says: %s\n", rs); - } + printf("=== Rust FFI demo ===\n"); + const char *rs = fun_rust_get_string(); + if (rs) { + printf("Rust says: %s\n", rs); + } - /* prepare stack: push 10 and 32, then call Rust add -> expect 42 */ - vm_push_i64(&vm, 10); - vm_push_i64(&vm, 32); - int rc = fun_op_radd(&vm); - printf("fun_op_radd rc=%d\n", rc); - long long sum = (long long)vm_pop_i64(&vm); - printf("Rust op result: %lld\n", sum); + /* prepare stack: push 10 and 32, then call Rust add -> expect 42 */ + vm_push_i64(&vm, 10); + vm_push_i64(&vm, 32); + int rc = fun_op_radd(&vm); + printf("fun_op_radd rc=%d\n", rc); + long long sum = (long long)vm_pop_i64(&vm); + printf("Rust op result: %lld\n", sum); #else - printf("=== Rust FFI demo (disabled; build with -DFUN_WITH_RUST=ON) ===\n"); + printf("=== Rust FFI demo (disabled; build with -DFUN_WITH_RUST=ON) ===\n"); #endif - vm_free(&vm); - bytecode_free(bc); - return 0; + vm_free(&vm); + bytecode_free(bc); + return 0; } diff --git a/src/value.c b/src/value.c index 9bae143..c6b2fa8 100644 --- a/src/value.c +++ b/src/value.c @@ -8,470 +8,487 @@ */ #include "value.h" +#include #include #include -#include /* Compile helper implementations into this TU to avoid build system changes */ -#include "str_utils.c" #include "array_utils.c" +#include "str_utils.c" typedef struct Array { - int refcount; - int count; - Value *items; /* owns items; each item owned by array */ + int refcount; + int count; + Value *items; /* owns items; each item owned by array */ } Array; typedef struct Map { - int refcount; - int count; - int cap; - char **keys; /* each key owned here */ - Value *vals; /* each value owned here */ + int refcount; + int count; + int cap; + char **keys; /* each key owned here */ + Value *vals; /* each value owned here */ } Map; Value make_int(int64_t v) { - Value val; - val.type = VAL_INT; - val.i = v; - return val; + Value val; + val.type = VAL_INT; + val.i = v; + return val; } Value make_float(double v) { - Value val; - val.type = VAL_FLOAT; - val.d = v; - return val; + Value val; + val.type = VAL_FLOAT; + val.d = v; + return val; } Value make_bool(int v) { - Value val; - val.type = VAL_BOOL; - val.i = v ? 1 : 0; - return val; + Value val; + val.type = VAL_BOOL; + val.i = v ? 1 : 0; + return val; } Value make_string(const char *s) { - Value val; - val.type = VAL_STRING; - if (s) val.s = strdup(s); - else val.s = strdup(""); - return val; + Value val; + val.type = VAL_STRING; + if (s) + val.s = strdup(s); + else + val.s = strdup(""); + return val; } Value make_function(struct Bytecode *fn) { - Value val; - val.type = VAL_FUNCTION; - val.fn = fn; - return val; + Value val; + val.type = VAL_FUNCTION; + val.fn = fn; + return val; } Value make_nil(void) { - Value v; - v.type = VAL_NIL; - return v; + Value v; + v.type = VAL_NIL; + return v; } - Value make_array_from_values(const Value *vals, int count) { - if (count < 0) count = 0; - Array *arr = (Array*)malloc(sizeof(Array)); - if (!arr) { - Value nil = make_nil(); - return nil; + if (count < 0) count = 0; + Array *arr = (Array *)malloc(sizeof(Array)); + if (!arr) { + Value nil = make_nil(); + return nil; + } + arr->refcount = 1; + arr->count = count; + if (count > 0) { + arr->items = (Value *)malloc(sizeof(Value) * count); + if (!arr->items) { + free(arr); + Value nil = make_nil(); + return nil; } - arr->refcount = 1; - arr->count = count; - if (count > 0) { - arr->items = (Value*)malloc(sizeof(Value) * count); - if (!arr->items) { - free(arr); - Value nil = make_nil(); - return nil; - } - for (int i = 0; i < count; ++i) { - arr->items[i] = copy_value(&vals[i]); - } - } else { - arr->items = NULL; + for (int i = 0; i < count; ++i) { + arr->items[i] = copy_value(&vals[i]); } - Value v; - v.type = VAL_ARRAY; - v.arr = (struct Array*)arr; - return v; + } else { + arr->items = NULL; + } + Value v; + v.type = VAL_ARRAY; + v.arr = (struct Array *)arr; + return v; } int array_length(const Value *v) { - if (!v || v->type != VAL_ARRAY || !v->arr) return -1; - const Array *a = (const Array*)v->arr; - return a->count; + if (!v || v->type != VAL_ARRAY || !v->arr) return -1; + const Array *a = (const Array *)v->arr; + return a->count; } int array_get_copy(const Value *v, int index, Value *out) { - if (!v || v->type != VAL_ARRAY || !v->arr) return 0; - const Array *a = (const Array*)v->arr; - if (index < 0 || index >= a->count) return 0; - if (out) *out = copy_value(&a->items[index]); - return 1; + if (!v || v->type != VAL_ARRAY || !v->arr) return 0; + const Array *a = (const Array *)v->arr; + if (index < 0 || index >= a->count) return 0; + if (out) *out = copy_value(&a->items[index]); + return 1; } int array_set(Value *v, int index, Value newElem) { - if (!v || v->type != VAL_ARRAY || !v->arr) return 0; - Array *a = (Array*)v->arr; - if (index < 0 || index >= a->count) return 0; - free_value(a->items[index]); - a->items[index] = newElem; /* take ownership */ - return 1; + if (!v || v->type != VAL_ARRAY || !v->arr) return 0; + Array *a = (Array *)v->arr; + if (index < 0 || index >= a->count) return 0; + free_value(a->items[index]); + a->items[index] = newElem; /* take ownership */ + return 1; } static int ensure_array_capacity(Array *a, int newCount) { - if (newCount <= a->count) return 1; - /* grow to at least newCount; double strategy */ - int curr = a->count; - int cap = curr; - if (cap < 4) cap = 4; - while (cap < newCount) cap *= 2; - Value *newItems = (Value*)realloc(a->items, sizeof(Value) * cap); - if (!newItems) return 0; - /* if growing beyond current count, initialize new slots to nil */ - if (cap > a->count) { - for (int i = a->count; i < cap; ++i) { - newItems[i] = make_nil(); - } + if (newCount <= a->count) return 1; + /* grow to at least newCount; double strategy */ + int curr = a->count; + int cap = curr; + if (cap < 4) cap = 4; + while (cap < newCount) + cap *= 2; + Value *newItems = (Value *)realloc(a->items, sizeof(Value) * cap); + if (!newItems) return 0; + /* if growing beyond current count, initialize new slots to nil */ + if (cap > a->count) { + for (int i = a->count; i < cap; ++i) { + newItems[i] = make_nil(); } - a->items = newItems; - return 1; + } + a->items = newItems; + return 1; } int array_push(Value *v, Value newElem) { - if (!v || v->type != VAL_ARRAY || !v->arr) return -1; - Array *a = (Array*)v->arr; - /* ensure capacity for count+1 by reallocating items array to at least count+1 elements */ - Value *newItems = (Value*)realloc(a->items, sizeof(Value) * (a->count + 1)); - if (!newItems) { free_value(newElem); return -1; } - a->items = newItems; - a->items[a->count] = newElem; /* take ownership */ - a->count += 1; - return a->count; + if (!v || v->type != VAL_ARRAY || !v->arr) return -1; + Array *a = (Array *)v->arr; + /* ensure capacity for count+1 by reallocating items array to at least count+1 elements */ + Value *newItems = (Value *)realloc(a->items, sizeof(Value) * (a->count + 1)); + if (!newItems) { + free_value(newElem); + return -1; + } + a->items = newItems; + a->items[a->count] = newElem; /* take ownership */ + a->count += 1; + return a->count; } int array_pop(Value *v, Value *out) { - if (!v || v->type != VAL_ARRAY || !v->arr) return 0; - Array *a = (Array*)v->arr; - if (a->count <= 0) return 0; - int idx = a->count - 1; - if (out) *out = a->items[idx]; /* transfer ownership */ - else free_value(a->items[idx]); - a->count -= 1; - return 1; + if (!v || v->type != VAL_ARRAY || !v->arr) return 0; + Array *a = (Array *)v->arr; + if (a->count <= 0) return 0; + int idx = a->count - 1; + if (out) + *out = a->items[idx]; /* transfer ownership */ + else + free_value(a->items[idx]); + a->count -= 1; + return 1; } int array_insert(Value *v, int index, Value newElem) { - if (!v || v->type != VAL_ARRAY || !v->arr) return -1; - Array *a = (Array*)v->arr; - if (index < 0) index = 0; - if (index > a->count) index = a->count; - Value *newItems = (Value*)realloc(a->items, sizeof(Value) * (a->count + 1)); - if (!newItems) { free_value(newElem); return -1; } - a->items = newItems; - /* shift right */ - for (int i = a->count; i > index; --i) { - a->items[i] = a->items[i - 1]; - } - a->items[index] = newElem; /* take ownership */ - a->count += 1; - return a->count; + if (!v || v->type != VAL_ARRAY || !v->arr) return -1; + Array *a = (Array *)v->arr; + if (index < 0) index = 0; + if (index > a->count) index = a->count; + Value *newItems = (Value *)realloc(a->items, sizeof(Value) * (a->count + 1)); + if (!newItems) { + free_value(newElem); + return -1; + } + a->items = newItems; + /* shift right */ + for (int i = a->count; i > index; --i) { + a->items[i] = a->items[i - 1]; + } + a->items[index] = newElem; /* take ownership */ + a->count += 1; + return a->count; } int array_remove(Value *v, int index, Value *out) { - if (!v || v->type != VAL_ARRAY || !v->arr) return 0; - Array *a = (Array*)v->arr; - if (index < 0 || index >= a->count) return 0; - if (out) *out = a->items[index]; /* transfer ownership */ - else free_value(a->items[index]); - /* shift left */ - for (int i = index; i < a->count - 1; ++i) { - a->items[i] = a->items[i + 1]; - } - a->count -= 1; - return 1; + if (!v || v->type != VAL_ARRAY || !v->arr) return 0; + Array *a = (Array *)v->arr; + if (index < 0 || index >= a->count) return 0; + if (out) + *out = a->items[index]; /* transfer ownership */ + else + free_value(a->items[index]); + /* shift left */ + for (int i = index; i < a->count - 1; ++i) { + a->items[i] = a->items[i + 1]; + } + a->count -= 1; + return 1; } Value array_slice(const Value *v, int start, int end) { - if (!v || v->type != VAL_ARRAY || !v->arr) return make_nil(); - const Array *a = (const Array*)v->arr; - int n = a->count; - if (start < 0) start = 0; - if (end < 0 || end > n) end = n; - if (start > end) start = end; - int m = end - start; - if (m <= 0) { - return make_array_from_values(NULL, 0); - } - return make_array_from_values(a->items + start, m); + if (!v || v->type != VAL_ARRAY || !v->arr) return make_nil(); + const Array *a = (const Array *)v->arr; + int n = a->count; + if (start < 0) start = 0; + if (end < 0 || end > n) end = n; + if (start > end) start = end; + int m = end - start; + if (m <= 0) { + return make_array_from_values(NULL, 0); + } + return make_array_from_values(a->items + start, m); } Value array_concat(const Value *av, const Value *bv) { - if (!av || !bv || av->type != VAL_ARRAY || bv->type != VAL_ARRAY) return make_nil(); - const Array *a = (const Array*)av->arr; - const Array *b = (const Array*)bv->arr; - int na = a ? a->count : 0; - int nb = b ? b->count : 0; - int total = na + nb; - if (total <= 0) return make_array_from_values(NULL, 0); - Value *tmp = (Value*)malloc(sizeof(Value) * total); - if (!tmp) return make_nil(); - for (int i = 0; i < na; ++i) tmp[i] = a->items[i]; - for (int j = 0; j < nb; ++j) tmp[na + j] = b->items[j]; - Value out = make_array_from_values(tmp, total); - /* free temporaries we copied from (deep copy in make_array_from_values) */ - free(tmp); - return out; + if (!av || !bv || av->type != VAL_ARRAY || bv->type != VAL_ARRAY) return make_nil(); + const Array *a = (const Array *)av->arr; + const Array *b = (const Array *)bv->arr; + int na = a ? a->count : 0; + int nb = b ? b->count : 0; + int total = na + nb; + if (total <= 0) return make_array_from_values(NULL, 0); + Value *tmp = (Value *)malloc(sizeof(Value) * total); + if (!tmp) return make_nil(); + for (int i = 0; i < na; ++i) + tmp[i] = a->items[i]; + for (int j = 0; j < nb; ++j) + tmp[na + j] = b->items[j]; + Value out = make_array_from_values(tmp, total); + /* free temporaries we copied from (deep copy in make_array_from_values) */ + free(tmp); + return out; } Value copy_value(const Value *v) { - Value out; - out.type = v->type; - switch (v->type) { - 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; - case VAL_STRING: - out.s = v->s ? strdup(v->s) : strdup(""); - break; - case VAL_FUNCTION: - out.fn = v->fn; /* shallow copy pointer */ - break; - case VAL_ARRAY: { - Array *a = (Array*)v->arr; - out.arr = (struct Array*)a; - if (a) a->refcount++; - break; - } - case VAL_MAP: { - Map *m = (Map*)v->map; - out.map = (struct Map*)m; - if (m) m->refcount++; - break; - } - case VAL_NIL: - default: - break; - } - return out; + Value out; + out.type = v->type; + switch (v->type) { + 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; + case VAL_STRING: + out.s = v->s ? strdup(v->s) : strdup(""); + break; + case VAL_FUNCTION: + out.fn = v->fn; /* shallow copy pointer */ + break; + case VAL_ARRAY: { + Array *a = (Array *)v->arr; + out.arr = (struct Array *)a; + if (a) a->refcount++; + break; + } + case VAL_MAP: { + Map *m = (Map *)v->map; + out.map = (struct Map *)m; + if (m) m->refcount++; + break; + } + case VAL_NIL: + default: + break; + } + return out; } /* deep copy including arrays (recursively copies items) */ 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: - return make_string(v->s ? v->s : ""); - case VAL_FUNCTION: - return make_function(v->fn); /* shallow pointer for function bytecode */ - case VAL_ARRAY: { - const Array *a = (const Array*)v->arr; - if (!a || a->count <= 0) { - return make_array_from_values(NULL, 0); - } - /* copy items deeply */ - Value *tmp = (Value*)malloc(sizeof(Value) * a->count); - if (!tmp) return make_nil(); - for (int i = 0; i < a->count; ++i) { - tmp[i] = deep_copy_value(&a->items[i]); - } - Value out = make_array_from_values(tmp, a->count); - for (int i = 0; i < a->count; ++i) { - free_value(tmp[i]); - } - free(tmp); - return out; - } - case VAL_MAP: { - const Map *m = (const Map*)v->map; - if (!m || m->count <= 0) return make_map_empty(); - Value out = make_map_empty(); - for (int i = 0; i < m->count; ++i) { - Value dv = deep_copy_value(&m->vals[i]); - map_set(&out, m->keys[i], dv); - } - return out; - } - case VAL_NIL: - default: - return make_nil(); + 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: + return make_string(v->s ? v->s : ""); + case VAL_FUNCTION: + return make_function(v->fn); /* shallow pointer for function bytecode */ + case VAL_ARRAY: { + const Array *a = (const Array *)v->arr; + if (!a || a->count <= 0) { + return make_array_from_values(NULL, 0); } + /* copy items deeply */ + Value *tmp = (Value *)malloc(sizeof(Value) * a->count); + if (!tmp) return make_nil(); + for (int i = 0; i < a->count; ++i) { + tmp[i] = deep_copy_value(&a->items[i]); + } + Value out = make_array_from_values(tmp, a->count); + for (int i = 0; i < a->count; ++i) { + free_value(tmp[i]); + } + free(tmp); + return out; + } + case VAL_MAP: { + const Map *m = (const Map *)v->map; + if (!m || m->count <= 0) return make_map_empty(); + Value out = make_map_empty(); + for (int i = 0; i < m->count; ++i) { + Value dv = deep_copy_value(&m->vals[i]); + map_set(&out, m->keys[i], dv); + } + return out; + } + case VAL_NIL: + default: + return make_nil(); + } } void free_value(Value v) { - if (v.type == VAL_STRING && v.s) { - free(v.s); - } else if (v.type == VAL_ARRAY && v.arr) { - Array *a = (Array*)v.arr; - if (--a->refcount == 0) { - for (int i = 0; i < a->count; ++i) { - free_value(a->items[i]); - } - free(a->items); - free(a); - } - } else if (v.type == VAL_MAP && v.map) { - Map *m = (Map*)v.map; - if (--m->refcount == 0) { - for (int i = 0; i < m->count; ++i) { - if (m->keys[i]) free(m->keys[i]); - free_value(m->vals[i]); - } - free(m->keys); - free(m->vals); - free(m); - } + if (v.type == VAL_STRING && v.s) { + free(v.s); + } else if (v.type == VAL_ARRAY && v.arr) { + Array *a = (Array *)v.arr; + if (--a->refcount == 0) { + for (int i = 0; i < a->count; ++i) { + free_value(a->items[i]); + } + free(a->items); + free(a); } - /* VAL_FUNCTION: we *do not* free the Bytecode here (caller frees it) */ + } else if (v.type == VAL_MAP && v.map) { + Map *m = (Map *)v.map; + if (--m->refcount == 0) { + for (int i = 0; i < m->count; ++i) { + if (m->keys[i]) free(m->keys[i]); + free_value(m->vals[i]); + } + free(m->keys); + free(m->vals); + free(m); + } + } + /* VAL_FUNCTION: we *do not* free the Bytecode here (caller frees it) */ } void print_value(const Value *v) { - switch (v->type) { - 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; - case VAL_BOOL: - printf("%s", v->i ? "true" : "false"); - break; - case VAL_FUNCTION: - printf("", (void*)v->fn); - break; - case VAL_ARRAY: { - const Array *a = (const Array*)v->arr; - printf("["); - if (a) { - for (int i = 0; i < a->count; ++i) { - if (i > 0) printf(", "); - print_value(&a->items[i]); - } - } - printf("]"); - break; - } - case VAL_MAP: { - const Map *m = (const Map*)v->map; - printf("{"); - if (m) { - for (int i = 0; i < m->count; ++i) { - if (i > 0) printf(", "); - printf("\"%s\": ", m->keys[i] ? m->keys[i] : ""); - print_value(&m->vals[i]); - } - } - printf("}"); - break; - } - case VAL_NIL: - default: - printf("nil"); - break; + switch (v->type) { + 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; + case VAL_BOOL: + printf("%s", v->i ? "true" : "false"); + break; + case VAL_FUNCTION: + printf("", (void *)v->fn); + break; + case VAL_ARRAY: { + const Array *a = (const Array *)v->arr; + printf("["); + if (a) { + for (int i = 0; i < a->count; ++i) { + if (i > 0) printf(", "); + print_value(&a->items[i]); + } } + printf("]"); + break; + } + case VAL_MAP: { + const Map *m = (const Map *)v->map; + printf("{"); + if (m) { + for (int i = 0; i < m->count; ++i) { + if (i > 0) printf(", "); + printf("\"%s\": ", m->keys[i] ? m->keys[i] : ""); + print_value(&m->vals[i]); + } + } + printf("}"); + break; + } + case VAL_NIL: + default: + printf("nil"); + break; + } } 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: - return v->s && v->s[0] != '\0'; - case VAL_FUNCTION: - return 1; - case VAL_ARRAY: { - const Array *a = (const Array*)v->arr; - return a && a->count > 0; - } - case VAL_NIL: - default: - return 0; - } + 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: + return v->s && v->s[0] != '\0'; + case VAL_FUNCTION: + return 1; + case VAL_ARRAY: { + const Array *a = (const Array *)v->arr; + return a && a->count > 0; + } + case VAL_NIL: + default: + return 0; + } } /* allocate a printable C string for the value; caller must free */ char *value_to_string_alloc(const Value *v) { - if (!v) return strdup("nil"); - char buf[128]; - switch (v->type) { - case VAL_INT: { - char tmp[64]; - 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: - return strdup(v->i ? "true" : "false"); - case VAL_FUNCTION: { - snprintf(buf, sizeof(buf), "", (void*)v->fn); - return strdup(buf); - } - case VAL_ARRAY: { - int n = array_length(v); - if (n < 0) n = 0; - snprintf(buf, sizeof(buf), "[array n=%d]", n); - return strdup(buf); - } - case VAL_MAP: { - int n = 0; - if (v->type == VAL_MAP && v->map) { - const Map *m = (const Map*)v->map; - n = m ? m->count : 0; - } - snprintf(buf, sizeof(buf), "{map n=%d}", n); - return strdup(buf); - } - case VAL_NIL: - default: - return strdup("nil"); + if (!v) return strdup("nil"); + char buf[128]; + switch (v->type) { + case VAL_INT: { + char tmp[64]; + 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: + return strdup(v->i ? "true" : "false"); + case VAL_FUNCTION: { + snprintf(buf, sizeof(buf), "", (void *)v->fn); + return strdup(buf); + } + case VAL_ARRAY: { + int n = array_length(v); + if (n < 0) n = 0; + snprintf(buf, sizeof(buf), "[array n=%d]", n); + return strdup(buf); + } + case VAL_MAP: { + int n = 0; + if (v->type == VAL_MAP && v->map) { + const Map *m = (const Map *)v->map; + n = m ? m->count : 0; } + snprintf(buf, sizeof(buf), "{map n=%d}", n); + return strdup(buf); + } + case VAL_NIL: + default: + return strdup("nil"); + } } 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; - case VAL_BOOL: return (a->i != 0) == (b->i != 0); - case VAL_STRING: { - const char *sa = a->s ? a->s : ""; - const char *sb = b->s ? b->s : ""; - return strcmp(sa, sb) == 0; - } - default: return 0; - } + // 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; + case VAL_BOOL: + return (a->i != 0) == (b->i != 0); + case VAL_STRING: { + const char *sa = a->s ? a->s : ""; + const char *sb = b->s ? b->s : ""; + return strcmp(sa, sb) == 0; + } + default: + return 0; + } } diff --git a/src/value.h b/src/value.h index f6445e2..a0db138 100644 --- a/src/value.h +++ b/src/value.h @@ -8,7 +8,7 @@ */ /** -* @file value.h + * @file value.h * @brief Defines the Value type and associated functions for the Fun VM. * * This file defines the `Value` type, which represents all possible data types @@ -45,26 +45,26 @@ struct Array; /* forward */ struct Map; /* forward */ typedef enum { - VAL_INT, - VAL_BOOL, - VAL_STRING, - VAL_FUNCTION, - VAL_ARRAY, - VAL_MAP, - VAL_NIL, - VAL_FLOAT + VAL_INT, + VAL_BOOL, + VAL_STRING, + VAL_FUNCTION, + VAL_ARRAY, + VAL_MAP, + VAL_NIL, + VAL_FLOAT } ValueType; typedef struct { - ValueType type; - union { - int64_t i; - double d; - char *s; - struct Bytecode *fn; - struct Array *arr; - struct Map *map; - }; + ValueType type; + union { + int64_t i; + double d; + char *s; + struct Bytecode *fn; + struct Array *arr; + struct Map *map; + }; } Value; /* constructors / helpers */ @@ -77,46 +77,46 @@ Value make_float(double v); /* arrays */ Value make_array_from_values(const Value *vals, int count); /* deep-copies vals */ -int array_length(const Value *v); /* returns -1 if not array */ -int array_get_copy(const Value *v, int index, Value *out); /* returns 0 on error; out = copy_value(item) */ -int array_set(Value *v, int index, Value newElem); /* returns 0 on error; takes ownership of newElem */ -int array_push(Value *v, Value newElem); /* returns new length or -1 on error */ -int array_pop(Value *v, Value *out); /* returns 1 on success, out takes ownership */ -int array_insert(Value *v, int index, Value newElem); /* returns new length or -1 */ -int array_remove(Value *v, int index, Value *out); /* returns 1 on success */ -Value array_slice(const Value *v, int start, int end); /* negative end means till end */ -Value array_concat(const Value *a, const Value *b); /* returns new array */ +int array_length(const Value *v); /* returns -1 if not array */ +int array_get_copy(const Value *v, int index, Value *out); /* returns 0 on error; out = copy_value(item) */ +int array_set(Value *v, int index, Value newElem); /* returns 0 on error; takes ownership of newElem */ +int array_push(Value *v, Value newElem); /* returns new length or -1 on error */ +int array_pop(Value *v, Value *out); /* returns 1 on success, out takes ownership */ +int array_insert(Value *v, int index, Value newElem); /* returns new length or -1 */ +int array_remove(Value *v, int index, Value *out); /* returns 1 on success */ +Value array_slice(const Value *v, int start, int end); /* negative end means till end */ +Value array_concat(const Value *a, const Value *b); /* returns new array */ /* maps (string keys) */ -Value make_map_empty(void); /* new empty map */ -int map_set(Value *m, const char *key, Value v); /* 1 on ok (takes ownership of v) */ -int map_get_copy(const Value *m, const char *key, Value *out);/* 1 on found, out=copy */ -int map_has(const Value *m, const char *key); /* 1/0 */ -Value map_keys_array(const Value *m); /* array of strings */ -Value map_values_array(const Value *m); /* array of values (copies) */ +Value make_map_empty(void); /* new empty map */ +int map_set(Value *m, const char *key, Value v); /* 1 on ok (takes ownership of v) */ +int map_get_copy(const Value *m, const char *key, Value *out); /* 1 on found, out=copy */ +int map_has(const Value *m, const char *key); /* 1/0 */ +Value map_keys_array(const Value *m); /* array of strings */ +Value map_values_array(const Value *m); /* array of values (copies) */ /* copy/free */ -Value copy_value(const Value *v); /* deep for strings, RC for arrays/maps, shallow fn */ +Value copy_value(const Value *v); /* deep for strings, RC for arrays/maps, shallow fn */ Value deep_copy_value(const Value *v); /* deep copy including arrays/maps */ -void free_value(Value v); /* frees owned resources */ +void free_value(Value v); /* frees owned resources */ /* utilities */ void print_value(const Value *v); int value_is_truthy(const Value *v); -int value_equals(const Value *a, const Value *b); /* int/string equality */ +int value_equals(const Value *a, const Value *b); /* int/string equality */ /* stringify into a newly-allocated C string; caller must free */ char *value_to_string_alloc(const Value *v); /* array utils */ -int array_contains(const Value *arr, const Value *needle); /* 1/0 */ -int array_index_of(const Value *arr, const Value *needle); /* idx or -1 */ -void array_clear(Value *arr); /* free elements, count=0 */ +int array_contains(const Value *arr, const Value *needle); /* 1/0 */ +int array_index_of(const Value *arr, const Value *needle); /* idx or -1 */ +void array_clear(Value *arr); /* free elements, count=0 */ /* string helpers returning newly allocated C strings or arrays */ char *string_substr(const char *s, int start, int len); /* clamps bounds */ -int string_find(const char *hay, const char *needle); /* index or -1 */ -Value string_split_to_array(const char *s, const char *sep); /* array of strings */ -char *array_join_with_sep(const Value *arr, const char *sep); /* join items as strings */ +int string_find(const char *hay, const char *needle); /* index or -1 */ +Value string_split_to_array(const char *s, const char *sep); /* array of strings */ +char *array_join_with_sep(const Value *arr, const char *sep); /* join items as strings */ #endif diff --git a/src/vm.c b/src/vm.c index 6fa2f2f..4406d3b 100644 --- a/src/vm.c +++ b/src/vm.c @@ -18,25 +18,25 @@ #endif #endif +#include +#include +#include #include #include #include -#include -#include #include -#include #ifdef __unix__ -#include -#include -#include -#include #include +#include +#include +#include #include +#include #include /* For hidden input (password) handling in OP_INPUT_LINE */ #include -//#include +// #include #endif #ifdef _WIN32 @@ -52,8 +52,8 @@ /* Shared Notcurses state (optional) */ #ifdef FUN_WITH_NOTCURSES -# include /* ensure wcwidth/wcswidth prototypes present before notcurses.h on some systems */ -# include +#include +#include /* ensure wcwidth/wcswidth prototypes present before notcurses.h on some systems */ #endif #include "vm/notcurses/common.h" @@ -67,14 +67,14 @@ /* Note: INI opcode handlers are included below; changes in vm/ini/ .c files * require vm.c to rebuild. */ #include "external/json.c" +#include "external/libressl.c" #include "external/libsql.c" -#include "external/pcsc.c" +#include "external/openssl.c" #include "external/pcre2.c" +#include "external/pcsc.c" #include "external/sqlite.c" #include "external/tcltk.c" #include "external/xml2.c" -#include "external/openssl.c" -#include "external/libressl.c" /* forward declarations for include mapping used in error reporting */ extern char *preprocess_includes(const char *src); @@ -88,130 +88,150 @@ static VM *g_active_vm = NULL; /* fprintf wrapper that appends source line info for stderr messages */ static int fun_vm_vfprintf(FILE *stream, const char *fmt, va_list ap) { - int written = vfprintf(stream, fmt, ap); - if (stream == stderr && g_active_vm) { - /* Determine current frame and instruction pointer of the faulting op */ - const char *opname = "unknown"; - const char *fname = NULL; - const char *sfile = NULL; - int ip = -1; - int line = -1; - if (g_active_vm->fp >= 0) { - Frame *f = &g_active_vm->frames[g_active_vm->fp]; - ip = f->ip - 1; /* last executed instruction */ - if (f->fn) { - fname = f->fn->name; - sfile = f->fn->source_file; - /* derive opcode name */ - if (ip >= 0 && ip < f->fn->instr_count) { - int op = f->fn->instructions[ip].op; - if (op >= 0 && op < (int)(sizeof(opcode_names)/sizeof(opcode_names[0]))) { - opname = opcode_names[op]; - } - } - /* derive source line by scanning back to the most recent OP_LINE marker */ - for (int i = ip; i >= 0; --i) { - Instruction prev = f->fn->instructions[i]; - if (prev.op == OP_LINE) { line = prev.operand; break; } - } - /* fallback to VM's last recorded line if no marker found */ - if (line <= 0) line = g_active_vm->current_line > 0 ? g_active_vm->current_line : 1; - - /* If this function was compiled from an include-expanded source, map to the included file */ - if (sfile && line > 0) { - char mapped_path[1024]; int mapped_line = line; - if (map_expanded_line_to_include(sfile, line, mapped_path, sizeof(mapped_path), &mapped_line)) { - sfile = strdup(mapped_path); /* leak on purpose for simplicity; this is rare */ - line = mapped_line; - } - } - } + int written = vfprintf(stream, fmt, ap); + if (stream == stderr && g_active_vm) { + /* Determine current frame and instruction pointer of the faulting op */ + const char *opname = "unknown"; + const char *fname = NULL; + const char *sfile = NULL; + int ip = -1; + int line = -1; + if (g_active_vm->fp >= 0) { + Frame *f = &g_active_vm->frames[g_active_vm->fp]; + ip = f->ip - 1; /* last executed instruction */ + if (f->fn) { + fname = f->fn->name; + sfile = f->fn->source_file; + /* derive opcode name */ + if (ip >= 0 && ip < f->fn->instr_count) { + int op = f->fn->instructions[ip].op; + if (op >= 0 && op < (int)(sizeof(opcode_names) / sizeof(opcode_names[0]))) { + opname = opcode_names[op]; + } } - fprintf(stream == stderr ? stderr : stream, - " (at %s:%d in %s, op %s @ip %d)\n", - sfile ? sfile : "", - line > 0 ? line : 1, - fname ? fname : "", - opname, - ip); + /* derive source line by scanning back to the most recent OP_LINE marker */ + for (int i = ip; i >= 0; --i) { + Instruction prev = f->fn->instructions[i]; + if (prev.op == OP_LINE) { + line = prev.operand; + break; + } + } + /* fallback to VM's last recorded line if no marker found */ + if (line <= 0) line = g_active_vm->current_line > 0 ? g_active_vm->current_line : 1; + + /* If this function was compiled from an include-expanded source, map to the included file */ + if (sfile && line > 0) { + char mapped_path[1024]; + int mapped_line = line; + if (map_expanded_line_to_include(sfile, line, mapped_path, sizeof(mapped_path), &mapped_line)) { + sfile = strdup(mapped_path); /* leak on purpose for simplicity; this is rare */ + line = mapped_line; + } + } + } } - return written; + fprintf(stream == stderr ? stderr : stream, + " (at %s:%d in %s, op %s @ip %d)\n", + sfile ? sfile : "", + line > 0 ? line : 1, + fname ? fname : "", + opname, + ip); + } + return written; } static int fun_vm_fprintf(FILE *stream, const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - int r = fun_vm_vfprintf(stream, fmt, ap); - va_end(ap); - return r; + va_list ap; + va_start(ap, fmt); + int r = fun_vm_vfprintf(stream, fmt, ap); + va_end(ap); + return r; } /* Helper: try to map expanded source line to included file:line using preprocessor markers */ static int map_expanded_line_to_include(const char *path, int line, char *out_path, size_t out_path_cap, int *out_line) { - if (!path || line <= 0 || !out_path || out_path_cap == 0 || !out_line) return 0; - out_path[0] = '\0'; - *out_line = line; + if (!path || line <= 0 || !out_path || out_path_cap == 0 || !out_line) return 0; + out_path[0] = '\0'; + *out_line = line; - /* read original file */ - FILE *f = fopen(path, "rb"); - if (!f) return 0; - fseek(f, 0, SEEK_END); - long sz = ftell(f); - if (sz < 0) { fclose(f); return 0; } - rewind(f); - char *buf = (char*)malloc((size_t)sz + 1); - if (!buf) { fclose(f); return 0; } - size_t n = fread(buf, 1, (size_t)sz, f); + /* read original file */ + FILE *f = fopen(path, "rb"); + if (!f) return 0; + fseek(f, 0, SEEK_END); + long sz = ftell(f); + if (sz < 0) { fclose(f); - buf[n] = '\0'; + return 0; + } + rewind(f); + char *buf = (char *)malloc((size_t)sz + 1); + if (!buf) { + fclose(f); + return 0; + } + size_t n = fread(buf, 1, (size_t)sz, f); + fclose(f); + buf[n] = '\0'; - char *prep = preprocess_includes(buf); - free(buf); - if (!prep) return 0; - - /* find start offset of requested 1-based line */ - size_t len = strlen(prep); - size_t pos = 0; int cur = 1; - while (pos < len && cur < line) { - if (prep[pos] == '\n') cur++; - pos++; - } - if (cur != line) { free(prep); return 0; } - - /* scan backward to find last include marker line */ - const char *marker = "// __include_begin__: "; - size_t mlen = strlen(marker); - size_t scan = pos; - while (scan > 0) { - /* find start of this line */ - size_t ls = scan; - while (ls > 0 && prep[ls - 1] != '\n') ls--; - /* check marker */ - if (ls + mlen <= len && strncmp(prep + ls, marker, mlen) == 0) { - /* extract included path up to EOL or ' as ' */ - size_t p = ls + mlen; - size_t pe = p; - while (pe < len && prep[pe] != '\n' && !(prep[pe] == ' ' && pe + 3 < len && strncmp(prep + pe, " as ", 4) == 0)) pe++; - size_t copy = (pe - p) < (out_path_cap - 1) ? (pe - p) : (out_path_cap - 1); - memcpy(out_path, prep + p, copy); - out_path[copy] = '\0'; - /* compute inner line as number of newlines from end of marker line to current pos */ - int inner = 1; - size_t q = pe; - /* skip to next line start */ - while (q < len && prep[q] != '\n') q++; - if (q < len && prep[q] == '\n') q++; - while (q < pos) { if (prep[q] == '\n') inner++; q++; } - *out_line = inner; - free(prep); - return 1; - } - if (ls == 0) break; - scan = (ls > 0) ? (ls - 1) : 0; - } + char *prep = preprocess_includes(buf); + free(buf); + if (!prep) return 0; + /* find start offset of requested 1-based line */ + size_t len = strlen(prep); + size_t pos = 0; + int cur = 1; + while (pos < len && cur < line) { + if (prep[pos] == '\n') cur++; + pos++; + } + if (cur != line) { free(prep); return 0; + } + + /* scan backward to find last include marker line */ + const char *marker = "// __include_begin__: "; + size_t mlen = strlen(marker); + size_t scan = pos; + while (scan > 0) { + /* find start of this line */ + size_t ls = scan; + while (ls > 0 && prep[ls - 1] != '\n') + ls--; + /* check marker */ + if (ls + mlen <= len && strncmp(prep + ls, marker, mlen) == 0) { + /* extract included path up to EOL or ' as ' */ + size_t p = ls + mlen; + size_t pe = p; + while (pe < len && prep[pe] != '\n' && !(prep[pe] == ' ' && pe + 3 < len && strncmp(prep + pe, " as ", 4) == 0)) + pe++; + size_t copy = (pe - p) < (out_path_cap - 1) ? (pe - p) : (out_path_cap - 1); + memcpy(out_path, prep + p, copy); + out_path[copy] = '\0'; + /* compute inner line as number of newlines from end of marker line to current pos */ + int inner = 1; + size_t q = pe; + /* skip to next line start */ + while (q < len && prep[q] != '\n') + q++; + if (q < len && prep[q] == '\n') q++; + while (q < pos) { + if (prep[q] == '\n') inner++; + q++; + } + *out_line = inner; + free(prep); + return 1; + } + if (ls == 0) break; + scan = (ls > 0) ? (ls - 1) : 0; + } + + free(prep); + return 0; } /* Redirect fprintf within this translation unit so opcode handlers use our wrapper */ @@ -223,15 +243,15 @@ static int map_expanded_line_to_include(const char *path, int line, char *out_pa static jmp_buf g_vm_err_jmp; static void fun_vm_exit(int code) { - if (g_active_vm && g_active_vm->repl_on_error) { - /* Jump back to vm_run to allow dropping into the REPL with intact VM state */ - longjmp(g_vm_err_jmp, code ? code : 1); - } - /* Fallback: terminate immediately if not in REPL-on-error mode */ + if (g_active_vm && g_active_vm->repl_on_error) { + /* Jump back to vm_run to allow dropping into the REPL with intact VM state */ + longjmp(g_vm_err_jmp, code ? code : 1); + } + /* Fallback: terminate immediately if not in REPL-on-error mode */ #ifdef _WIN32 - _exit(code); + _exit(code); #else - _Exit(code); + _Exit(code); #endif } @@ -245,29 +265,29 @@ static void push_value(VM *vm, Value v); * If a handler is installed for the current frame, jump to it and push * an error string for the catch clause. Otherwise, print and stop VM. */ void vm_raise_error(VM *vm, const char *msg) { - if (!vm || vm->fp < 0) { - fprintf(stderr, "Runtime error: %s\n", msg ? msg : ""); - return; - } - Frame *f = &vm->frames[vm->fp]; - if (f->try_sp >= 0) { - char buf[256]; - if (msg) { - snprintf(buf, sizeof(buf), "Runtime error: %s", msg); - } else { - snprintf(buf, sizeof(buf), "Runtime error"); - } - /* push error value and transfer control to handler target */ - Value err = make_string(buf); - push_value(vm, err); - int try_idx = f->try_stack[f->try_sp--]; - int target = f->fn->instructions[try_idx].operand; - f->ip = target; - return; - } - /* No handler: print annotated message and terminate VM */ + if (!vm || vm->fp < 0) { fprintf(stderr, "Runtime error: %s\n", msg ? msg : ""); - vm->fp = -1; /* stop execution */ + return; + } + Frame *f = &vm->frames[vm->fp]; + if (f->try_sp >= 0) { + char buf[256]; + if (msg) { + snprintf(buf, sizeof(buf), "Runtime error: %s", msg); + } else { + snprintf(buf, sizeof(buf), "Runtime error"); + } + /* push error value and transfer control to handler target */ + Value err = make_string(buf); + push_value(vm, err); + int try_idx = f->try_stack[f->try_sp--]; + int target = f->fn->instructions[try_idx].operand; + f->ip = target; + return; + } + /* No handler: print annotated message and terminate VM */ + fprintf(stderr, "Runtime error: %s\n", msg ? msg : ""); + vm->fp = -1; /* stop execution */ } /* @@ -308,686 +328,701 @@ Dev tips: - You can run scripts/run_examples.sh to sanity-check examples quickly. */ -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"; - } +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"; + } } void vm_clear_output(VM *vm) { - for (int i = 0; i < vm->output_count; ++i) { - free_value(vm->output[i]); - } - vm->output_count = 0; - // reset partial flags - for (int i = 0; i < OUTPUT_SIZE; ++i) vm->output_is_partial[i] = 0; + for (int i = 0; i < vm->output_count; ++i) { + free_value(vm->output[i]); + } + vm->output_count = 0; + // reset partial flags + for (int i = 0; i < OUTPUT_SIZE; ++i) + vm->output_is_partial[i] = 0; } void vm_free(VM *vm) { - // currently nothing persistent allocated inside VM itself + // currently nothing persistent allocated inside VM itself } /* forward declaration for helper used in vm_reset */ static void vm_pop_frame(VM *vm); void vm_reset(VM *vm) { - // Pop all frames (free locals) - while (vm->fp >= 0) { - vm_pop_frame(vm); - } - // Clear stack - vm->sp = -1; - // Free globals - for (int i = 0; i < MAX_GLOBALS; ++i) { - free_value(vm->globals[i]); - vm->globals[i] = make_nil(); - } - // Clear output buffer - vm_clear_output(vm); - // Reset exit code - vm->exit_code = 0; + // Pop all frames (free locals) + while (vm->fp >= 0) { + vm_pop_frame(vm); + } + // Clear stack + vm->sp = -1; + // Free globals + for (int i = 0; i < MAX_GLOBALS; ++i) { + free_value(vm->globals[i]); + vm->globals[i] = make_nil(); + } + // Clear output buffer + vm_clear_output(vm); + // Reset exit code + vm->exit_code = 0; - // Reset debugger state (breakpoints, stepping) - vm_debug_reset(vm); + // Reset debugger state (breakpoints, stepping) + vm_debug_reset(vm); } void vm_dump_globals(VM *vm) { - printf("=== globals ===\n"); - for (int i = 0; i < MAX_GLOBALS; ++i) { - if (vm->globals[i].type != VAL_NIL) { - printf("[%d] ", i); - print_value(&vm->globals[i]); - printf("\n"); - } + printf("=== globals ===\n"); + for (int i = 0; i < MAX_GLOBALS; ++i) { + if (vm->globals[i].type != VAL_NIL) { + printf("[%d] ", i); + print_value(&vm->globals[i]); + printf("\n"); } - printf("===============\n"); + } + printf("===============\n"); } /* --- Debugger API impl --- */ void vm_debug_reset(VM *vm) { - for (int i = 0; i < vm->break_count; ++i) { - if (vm->breakpoints[i].file) { - free(vm->breakpoints[i].file); - vm->breakpoints[i].file = NULL; - } - vm->breakpoints[i].active = 0; - vm->breakpoints[i].line = 0; + for (int i = 0; i < vm->break_count; ++i) { + if (vm->breakpoints[i].file) { + free(vm->breakpoints[i].file); + vm->breakpoints[i].file = NULL; } - vm->break_count = 0; - vm->debug_step_mode = 0; - vm->debug_step_target_fp = -1; - vm->debug_step_start_ic = vm->instr_count; - vm->debug_stop_requested = 0; + vm->breakpoints[i].active = 0; + vm->breakpoints[i].line = 0; + } + vm->break_count = 0; + vm->debug_step_mode = 0; + vm->debug_step_target_fp = -1; + vm->debug_step_start_ic = vm->instr_count; + vm->debug_stop_requested = 0; } int vm_debug_add_breakpoint(VM *vm, const char *file, int line) { - if (!file || line <= 0) return -1; - if (vm->break_count >= (int)(sizeof(vm->breakpoints)/sizeof(vm->breakpoints[0]))) return -1; - int id = vm->break_count++; - vm->breakpoints[id].file = strdup(file); - vm->breakpoints[id].line = line; - vm->breakpoints[id].active = 1; - return id; + if (!file || line <= 0) return -1; + if (vm->break_count >= (int)(sizeof(vm->breakpoints) / sizeof(vm->breakpoints[0]))) return -1; + int id = vm->break_count++; + vm->breakpoints[id].file = strdup(file); + vm->breakpoints[id].line = line; + vm->breakpoints[id].active = 1; + return id; } int vm_debug_delete_breakpoint(VM *vm, int id) { - if (id < 0 || id >= vm->break_count) return 0; - if (vm->breakpoints[id].file) free(vm->breakpoints[id].file); - for (int i = id + 1; i < vm->break_count; ++i) { - vm->breakpoints[i - 1] = vm->breakpoints[i]; - } - vm->break_count--; - if (vm->break_count >= 0) { - vm->breakpoints[vm->break_count].file = NULL; - vm->breakpoints[vm->break_count].line = 0; - vm->breakpoints[vm->break_count].active = 0; - } - return 1; + if (id < 0 || id >= vm->break_count) return 0; + if (vm->breakpoints[id].file) free(vm->breakpoints[id].file); + for (int i = id + 1; i < vm->break_count; ++i) { + vm->breakpoints[i - 1] = vm->breakpoints[i]; + } + vm->break_count--; + if (vm->break_count >= 0) { + vm->breakpoints[vm->break_count].file = NULL; + vm->breakpoints[vm->break_count].line = 0; + vm->breakpoints[vm->break_count].active = 0; + } + return 1; } void vm_debug_clear_breakpoints(VM *vm) { - vm_debug_reset(vm); + vm_debug_reset(vm); } void vm_debug_list_breakpoints(VM *vm) { - if (vm->break_count <= 0) { - printf("(no breakpoints)\n"); - return; - } - for (int i = 0; i < vm->break_count; ++i) { - if (!vm->breakpoints[i].active) continue; - printf(" [%d] %s:%d\n", i, vm->breakpoints[i].file ? vm->breakpoints[i].file : "", vm->breakpoints[i].line); - } + if (vm->break_count <= 0) { + printf("(no breakpoints)\n"); + return; + } + for (int i = 0; i < vm->break_count; ++i) { + if (!vm->breakpoints[i].active) continue; + printf(" [%d] %s:%d\n", i, vm->breakpoints[i].file ? vm->breakpoints[i].file : "", vm->breakpoints[i].line); + } } void vm_debug_request_step(VM *vm) { - vm->debug_step_mode = 1; // step - vm->debug_step_start_ic = vm->instr_count; - vm->debug_stop_requested = 0; + vm->debug_step_mode = 1; // step + vm->debug_step_start_ic = vm->instr_count; + vm->debug_stop_requested = 0; } void vm_debug_request_next(VM *vm) { - vm->debug_step_mode = 2; // next (step over) - vm->debug_step_target_fp = vm->fp; - vm->debug_step_start_ic = vm->instr_count; - vm->debug_stop_requested = 0; + vm->debug_step_mode = 2; // next (step over) + vm->debug_step_target_fp = vm->fp; + vm->debug_step_start_ic = vm->instr_count; + vm->debug_stop_requested = 0; } void vm_debug_request_finish(VM *vm) { - vm->debug_step_mode = 3; // finish (until return) - vm->debug_step_target_fp = vm->fp; - vm->debug_stop_requested = 0; + vm->debug_step_mode = 3; // finish (until return) + vm->debug_step_target_fp = vm->fp; + vm->debug_stop_requested = 0; } void vm_debug_request_continue(VM *vm) { - vm->debug_step_mode = 0; - vm->debug_stop_requested = 0; + vm->debug_step_mode = 0; + vm->debug_stop_requested = 0; } static void push_value(VM *vm, Value v) { - if (vm->sp >= STACK_SIZE - 1) { - fprintf(stderr, "Runtime error: stack overflow\n"); - exit(1); - } - vm->stack[++vm->sp] = v; /* take ownership of v */ + if (vm->sp >= STACK_SIZE - 1) { + fprintf(stderr, "Runtime error: stack overflow\n"); + exit(1); + } + vm->stack[++vm->sp] = v; /* take ownership of v */ } static Value pop_value(VM *vm) { - if (vm->sp < 0) { - fprintf(stderr, "Runtime error: stack underflow\n"); - exit(1); - } - return vm->stack[vm->sp--]; /* caller owns returned Value */ + if (vm->sp < 0) { + fprintf(stderr, "Runtime error: stack underflow\n"); + exit(1); + } + return vm->stack[vm->sp--]; /* caller owns returned Value */ } /* --- C ABI helpers for Rust FFI --- */ int64_t vm_pop_i64(VM *vm) { - Value v = pop_value(vm); - int64_t out = 0; - if (v.type == VAL_INT) { - out = v.i; - } else if (v.type == VAL_FLOAT) { - out = (int64_t) v.d; - } else { - fprintf(stderr, "Runtime type error: expected int/float on stack, got %s\n", value_type_name(v.type)); - free_value(v); - exit(1); - } - /* free any dynamic payload (no-op for int/float) */ + Value v = pop_value(vm); + int64_t out = 0; + if (v.type == VAL_INT) { + out = v.i; + } else if (v.type == VAL_FLOAT) { + out = (int64_t)v.d; + } else { + fprintf(stderr, "Runtime type error: expected int/float on stack, got %s\n", value_type_name(v.type)); free_value(v); - return out; + exit(1); + } + /* free any dynamic payload (no-op for int/float) */ + free_value(v); + return out; } void vm_push_i64(VM *vm, int64_t v) { - push_value(vm, make_int(v)); + push_value(vm, make_int(v)); } /* --- Extended C ABI for Rust to access VM internals (unsafe) --- */ size_t vm_sizeof(void) { - return sizeof(VM); + return sizeof(VM); } size_t vm_value_sizeof(void) { - return sizeof(Value); + return sizeof(Value); } void *vm_as_mut_ptr(VM *vm) { - return (void*)vm; + return (void *)vm; } size_t vm_offset_of_exit_code(void) { - return offsetof(VM, exit_code); + return offsetof(VM, exit_code); } size_t vm_offset_of_sp(void) { - return offsetof(VM, sp); + return offsetof(VM, sp); } size_t vm_offset_of_stack(void) { - return offsetof(VM, stack); + return offsetof(VM, stack); } size_t vm_offset_of_globals(void) { - return offsetof(VM, globals); + return offsetof(VM, globals); } static void frame_init(Frame *f) { - f->fn = NULL; - f->ip = 0; - for (int i = 0; i < MAX_FRAME_LOCALS; ++i) f->locals[i] = make_nil(); - f->try_sp = -1; + f->fn = NULL; + f->ip = 0; + for (int i = 0; i < MAX_FRAME_LOCALS; ++i) + f->locals[i] = make_nil(); + f->try_sp = -1; } void vm_init(VM *vm) { - vm->sp = -1; - vm->fp = -1; - vm->output_count = 0; - for (int i = 0; i < OUTPUT_SIZE; ++i) vm->output_is_partial[i] = 0; - vm->instr_count = 0; - vm->exit_code = 0; - vm->trace_enabled = 0; - vm->repl_on_error = 0; - vm->on_error_repl = NULL; + vm->sp = -1; + vm->fp = -1; + vm->output_count = 0; + for (int i = 0; i < OUTPUT_SIZE; ++i) + vm->output_is_partial[i] = 0; + vm->instr_count = 0; + vm->exit_code = 0; + vm->trace_enabled = 0; + vm->repl_on_error = 0; + vm->on_error_repl = NULL; - /* Debugger state */ - vm->debug_step_mode = 0; - vm->debug_step_target_fp = -1; - vm->debug_step_start_ic = 0; - vm->debug_stop_requested = 0; - vm->break_count = 0; - for (int i = 0; i < (int)(sizeof(vm->breakpoints)/sizeof(vm->breakpoints[0])); ++i) { - vm->breakpoints[i].file = NULL; - vm->breakpoints[i].line = 0; - vm->breakpoints[i].active = 0; - } + /* Debugger state */ + vm->debug_step_mode = 0; + vm->debug_step_target_fp = -1; + vm->debug_step_start_ic = 0; + vm->debug_stop_requested = 0; + vm->break_count = 0; + for (int i = 0; i < (int)(sizeof(vm->breakpoints) / sizeof(vm->breakpoints[0])); ++i) { + vm->breakpoints[i].file = NULL; + vm->breakpoints[i].line = 0; + vm->breakpoints[i].active = 0; + } - for (int i = 0; i < MAX_GLOBALS; ++i) - vm->globals[i] = make_nil(); + for (int i = 0; i < MAX_GLOBALS; ++i) + vm->globals[i] = make_nil(); } /* push a new frame, transferring ownership of args[] into frame->locals[0..argc-1] */ static void vm_push_frame(VM *vm, Bytecode *fn, int argc, Value *args) { - if (vm->fp >= MAX_FRAMES - 1) { - fprintf(stderr, "Runtime error: too many frames\n"); - exit(1); - } - Frame *f = &vm->frames[++vm->fp]; - frame_init(f); - f->fn = fn; - f->ip = 0; - /* move args into locals 0..argc-1 */ - for (int i = 0; i < argc && i < MAX_FRAME_LOCALS; ++i) { - f->locals[i] = args[i]; /* transfer ownership */ - } + if (vm->fp >= MAX_FRAMES - 1) { + fprintf(stderr, "Runtime error: too many frames\n"); + exit(1); + } + Frame *f = &vm->frames[++vm->fp]; + frame_init(f); + f->fn = fn; + f->ip = 0; + /* move args into locals 0..argc-1 */ + for (int i = 0; i < argc && i < MAX_FRAME_LOCALS; ++i) { + f->locals[i] = args[i]; /* transfer ownership */ + } } /* pop current frame and free its locals */ static void vm_pop_frame(VM *vm) { - if (vm->fp < 0) { - fprintf(stderr, "Runtime error: pop frame with empty frame stack\n"); - exit(1); - } - Frame *f = &vm->frames[vm->fp]; - for (int i = 0; i < MAX_FRAME_LOCALS; ++i) { - free_value(f->locals[i]); - f->locals[i] = make_nil(); - } - vm->fp--; + if (vm->fp < 0) { + fprintf(stderr, "Runtime error: pop frame with empty frame stack\n"); + exit(1); + } + Frame *f = &vm->frames[vm->fp]; + for (int i = 0; i < MAX_FRAME_LOCALS; ++i) { + free_value(f->locals[i]); + f->locals[i] = make_nil(); + } + vm->fp--; } void vm_print_output(VM *vm) { - for (int i = 0; i < vm->output_count; ++i) { - print_value(&vm->output[i]); - if (!vm->output_is_partial[i]) { - printf("\n"); - } + for (int i = 0; i < vm->output_count; ++i) { + print_value(&vm->output[i]); + if (!vm->output_is_partial[i]) { + printf("\n"); } + } } void vm_run(VM *vm, Bytecode *entry) { - /* reset instruction count for this run */ - vm->instr_count = 0; - vm->current_line = 1; - g_active_vm = vm; + /* reset instruction count for this run */ + vm->instr_count = 0; + vm->current_line = 1; + g_active_vm = vm; - /* set error trap if REPL-on-error is enabled */ - if (vm->repl_on_error) { - int jcode = setjmp(g_vm_err_jmp); - if (jcode != 0) { - /* We got here from a trapped exit() in an error path */ - fprintf(stderr, "Entering REPL due to runtime error (code %d)\n", jcode); - if (vm->on_error_repl) { - vm->on_error_repl(vm); - } - g_active_vm = NULL; - return; - } + /* set error trap if REPL-on-error is enabled */ + if (vm->repl_on_error) { + int jcode = setjmp(g_vm_err_jmp); + if (jcode != 0) { + /* We got here from a trapped exit() in an error path */ + fprintf(stderr, "Entering REPL due to runtime error (code %d)\n", jcode); + if (vm->on_error_repl) { + vm->on_error_repl(vm); + } + g_active_vm = NULL; + return; + } + } + + /* start with entry frame (no args) */ + vm_push_frame(vm, entry, 0, NULL); + + while (vm->fp >= 0) { + Frame *f = &vm->frames[vm->fp]; + + /* Stop conditions at top of loop (stepping/finish) */ + if (vm->on_error_repl) { + int should_stop = 0; + if (vm->debug_stop_requested) { + should_stop = 1; + } else if (vm->debug_step_mode == 1 && vm->instr_count > vm->debug_step_start_ic) { /* step */ + should_stop = 1; + vm->debug_step_mode = 0; + } else if (vm->debug_step_mode == 2 && vm->instr_count > vm->debug_step_start_ic && vm->fp <= vm->debug_step_target_fp) { /* next */ + should_stop = 1; + vm->debug_step_mode = 0; + } else if (vm->debug_step_mode == 3 && vm->fp < vm->debug_step_target_fp) { /* finish */ + should_stop = 1; + vm->debug_step_mode = 0; + } + if (should_stop) { + vm->debug_stop_requested = 0; + fprintf(stderr, "Paused (debug)\n"); + vm->on_error_repl(vm); + /* Frame pointer might have changed (reset/cont); refresh f */ + if (vm->fp < 0) break; + f = &vm->frames[vm->fp]; + } } - /* start with entry frame (no args) */ - vm_push_frame(vm, entry, 0, NULL); - - while (vm->fp >= 0) { - Frame *f = &vm->frames[vm->fp]; - - /* Stop conditions at top of loop (stepping/finish) */ - if (vm->on_error_repl) { - int should_stop = 0; - if (vm->debug_stop_requested) { - should_stop = 1; - } else if (vm->debug_step_mode == 1 && vm->instr_count > vm->debug_step_start_ic) { /* step */ - should_stop = 1; - vm->debug_step_mode = 0; - } else if (vm->debug_step_mode == 2 && vm->instr_count > vm->debug_step_start_ic && vm->fp <= vm->debug_step_target_fp) { /* next */ - should_stop = 1; - vm->debug_step_mode = 0; - } else if (vm->debug_step_mode == 3 && vm->fp < vm->debug_step_target_fp) { /* finish */ - should_stop = 1; - vm->debug_step_mode = 0; - } - if (should_stop) { - vm->debug_stop_requested = 0; - fprintf(stderr, "Paused (debug)\n"); - vm->on_error_repl(vm); - /* Frame pointer might have changed (reset/cont); refresh f */ - if (vm->fp < 0) break; - f = &vm->frames[vm->fp]; - } - } - - if (f->ip < 0 || f->ip >= f->fn->instr_count) { - /* no more instructions in this frame -> implicit return nil */ - Value nilv = make_nil(); - vm_pop_frame(vm); - push_value(vm, nilv); - continue; - } - - Instruction inst = f->fn->instructions[f->ip++]; - vm->instr_count++; /* count each executed instruction */ - - if (vm->trace_enabled) { - const char *opname = (inst.op >= 0 && inst.op < (int)(sizeof(opcode_names)/sizeof(opcode_names[0]))) - ? opcode_names[inst.op] : "???"; - const char *fname = f->fn && f->fn->name ? f->fn->name : ""; - const char *sfile = f->fn && f->fn->source_file ? f->fn->source_file : ""; - /* Dump up to top 4 stack values */ - int count = vm->sp + 1; - int start = count - 4; if (start < 0) start = 0; - fprintf(stdout, "TRACE %s:%d %s ip=%d %-14s %d | stack[%d]=[", sfile, vm->current_line, fname, f->ip - 1, opname, inst.operand, count); - for (int i = start; i < count; ++i) { - char *sv = value_to_string_alloc(&vm->stack[i]); - if (!sv) sv = strdup(""); - fprintf(stdout, "%s%s", sv, (i == count - 1 ? "" : ", ")); - free(sv); - } - fprintf(stdout, "]\n"); - } - - /* Breakpoint hit detection: breakpoints are set on source_file:line via LINE markers */ - if (vm->on_error_repl && inst.op == OP_LINE && vm->break_count > 0) { - const char *sfile = (f->fn && f->fn->source_file) ? f->fn->source_file : NULL; - int line = inst.operand; - for (int bi = 0; bi < vm->break_count; ++bi) { - if (!vm->breakpoints[bi].active) continue; - if (vm->breakpoints[bi].line != line) continue; - if (!sfile || !vm->breakpoints[bi].file) continue; - if (strcmp(vm->breakpoints[bi].file, sfile) != 0) continue; - fprintf(stderr, "Breakpoint %d hit at %s:%d\n", bi, sfile, line); - vm->on_error_repl(vm); - /* After returning, refresh frame pointer and frame */ - if (vm->fp < 0) break; - f = &vm->frames[vm->fp]; - break; - } - } - - switch (inst.op) { - /* All opcode handlers as .c includes */ - #include "vm/arithmetic/add.c" - #include "vm/arithmetic/div.c" - #include "vm/arithmetic/mul.c" - #include "vm/arithmetic/sub.c" - - #include "vm/arrays/apop.c" - #include "vm/arrays/clear.c" - #include "vm/arrays/contains.c" - #include "vm/arrays/enumerate.c" - #include "vm/arrays/index_get.c" - #include "vm/arrays/index_of.c" - #include "vm/arrays/index_set.c" - #include "vm/arrays/insert.c" - #include "vm/arrays/join.c" - #include "vm/arrays/make_array.c" - #include "vm/arrays/push.c" - #include "vm/arrays/remove.c" - #include "vm/arrays/set.c" - #include "vm/arrays/slice.c" - #include "vm/arrays/zip.c" - - /* Bitwise and shifts/rotates */ - #include "vm/bitwise/band.c" - #include "vm/bitwise/bor.c" - #include "vm/bitwise/bxor.c" - #include "vm/bitwise/bnot.c" - #include "vm/bitwise/shl.c" - #include "vm/bitwise/shr.c" - #include "vm/bitwise/rol.c" - #include "vm/bitwise/ror.c" - - #include "vm/core/call.c" - #include "vm/core/dup.c" - #include "vm/core/exit.c" - #include "vm/core/halt.c" - #include "vm/core/jump.c" - #include "vm/core/jump_if_false.c" - #include "vm/core/load_const.c" - #include "vm/core/load_global.c" - #include "vm/core/load_local.c" - #include "vm/core/nop.c" - #include "vm/core/pop.c" - #include "vm/core/return.c" - #include "vm/core/store_global.c" - #include "vm/core/store_local.c" - #include "vm/core/swap.c" - #include "vm/core/throw.c" - #include "vm/core/try_pop.c" - #include "vm/core/try_push.c" - - #include "vm/io/read_file.c" - #include "vm/io/write_file.c" - #include "vm/io/input_line.c" - - #include "vm/logic/and.c" - #include "vm/logic/eq.c" - #include "vm/logic/gt.c" - #include "vm/logic/gte.c" - #include "vm/logic/lt.c" - #include "vm/logic/lte.c" - #include "vm/logic/neq.c" - #include "vm/logic/not.c" - #include "vm/logic/or.c" - - #include "vm/maps/has_key.c" - #include "vm/maps/keys.c" - #include "vm/maps/make_map.c" - #include "vm/maps/values.c" - - #include "vm/math/abs.c" - #include "vm/math/clamp.c" - #include "vm/math/max.c" - #include "vm/math/min.c" - #include "vm/math/fmax.c" - #include "vm/math/fmin.c" - #include "vm/math/mod.c" - #include "vm/math/pow.c" - #include "vm/math/floor.c" - #include "vm/math/ceil.c" - #include "vm/math/trunc.c" - #include "vm/math/round.c" - #include "vm/math/sin.c" - #include "vm/math/cos.c" - #include "vm/math/tan.c" - #include "vm/math/exp.c" - #include "vm/math/log.c" - #include "vm/math/log10.c" - #include "vm/math/sqrt.c" - #include "vm/math/random_int.c" - #include "vm/math/random_seed.c" - #include "vm/math/gcd.c" - #include "vm/math/lcm.c" - #include "vm/math/isqrt.c" - #include "vm/math/sign.c" - - /* Rust FFI demo opcode(s) */ - #include "vm/rust/hello.c" - #include "vm/rust/hello_args.c" - #include "vm/rust/hello_args_return.c" - #include "vm/rust/get_sp.c" - #include "vm/rust/set_exit.c" - - #include "vm/os/env.c" - #include "vm/os/env_all.c" - #include "vm/os/fun_version.c" - #include "vm/os/sleep_ms.c" - #include "vm/os/thread_join.c" - #include "vm/os/thread_spawn.c" - #include "vm/os/proc_run.c" - #include "vm/os/proc_system.c" - #include "vm/os/time_now_ms.c" - #include "vm/os/clock_mono_ms.c" - #include "vm/os/date_format.c" - #include "vm/os/random_number.c" - #include "vm/os/serial_open.c" - #include "vm/os/serial_config.c" - #include "vm/os/serial_send.c" - #include "vm/os/serial_recv.c" - #include "vm/os/serial_close.c" - - /* Socket ops */ - #include "vm/os/socket_tcp_listen.c" - #include "vm/os/socket_tcp_accept.c" - #include "vm/os/socket_tcp_connect.c" - #include "vm/os/socket_send.c" - #include "vm/os/socket_recv.c" - #include "vm/os/socket_close.c" - #include "vm/os/socket_unix_listen.c" - #include "vm/os/socket_unix_connect.c" - - #ifdef FUN_WITH_PCSC - #include "vm/pcsc/establish.c" - #include "vm/pcsc/release.c" - #include "vm/pcsc/list_readers.c" - #include "vm/pcsc/connect.c" - #include "vm/pcsc/disconnect.c" - #include "vm/pcsc/transmit.c" - #endif - - /* JSON ops (implemented in jsonc.c, included above) */ - #ifdef FUN_WITH_JSON - #include "vm/json/parse.c" - #include "vm/json/stringify.c" - #include "vm/json/from_file.c" - #include "vm/json/to_file.c" - #endif - - /* XML ops (libxml2) */ - #ifdef FUN_WITH_XML2 - #include "vm/xml/parse.c" - #include "vm/xml/root.c" - #include "vm/xml/name.c" - #include "vm/xml/text.c" - #endif - - /* INI ops (iniparser 4.2.6) */ - #ifdef FUN_WITH_INI - #include "vm/ini/load.c" - #include "vm/ini/free.c" - #include "vm/ini/get_string.c" - #include "vm/ini/get_int.c" - #include "vm/ini/get_double.c" - #include "vm/ini/get_bool.c" - #include "vm/ini/set.c" - #include "vm/ini/unset.c" - #include "vm/ini/save.c" - #else - #include "vm/ini/stubs.c" - #endif - - /* CURL ops */ - #ifdef FUN_WITH_CURL - #include "vm/curl/get.c" - #include "vm/curl/post.c" - #include "vm/curl/download.c" - #endif - - /* OpenSSL ops (md5/sha256/sha512/ripemd160) */ - #ifdef FUN_WITH_OPENSSL - #include "vm/openssl/md5.c" - #include "vm/openssl/sha256.c" - #include "vm/openssl/sha512.c" - #include "vm/openssl/ripemd160.c" - #endif - - /* LibreSSL ops (md5/sha256/sha512/ripemd160) */ - #ifdef FUN_WITH_LIBRESSL - #include "vm/libressl/md5.c" - #include "vm/libressl/sha256.c" - #include "vm/libressl/sha512.c" - #include "vm/libressl/ripemd160.c" - #endif - - /* Tk (Tcl/Tk) ops */ - #ifdef FUN_WITH_TCLTK - #include "vm/tk/eval.c" - #include "vm/tk/result.c" - #include "vm/tk/loop.c" - #include "vm/tk/wm_title.c" - #include "vm/tk/label.c" - #include "vm/tk/button.c" - #include "vm/tk/pack.c" - #include "vm/tk/bind.c" - #endif - - /* Notcurses TUI ops (optional) */ - #ifdef FUN_WITH_NOTCURSES - #include "vm/notcurses/init.c" - #include "vm/notcurses/shutdown.c" - #include "vm/notcurses/clear.c" - #include "vm/notcurses/draw_text.c" - #include "vm/notcurses/getch.c" - #endif - - /* SQLite ops */ - #ifdef FUN_WITH_SQLITE - #include "vm/sqlite/open.c" - #include "vm/sqlite/close.c" - #include "vm/sqlite/exec.c" - #include "vm/sqlite/query.c" - #endif - - /* C++ demo opcodes (guarded) */ - #if defined(FUN_WITH_CPP) - case OP_CPP_ADD: { - int rc = fun_op_cpp_add(vm); - if (rc != 0) { - vm_raise_error(vm, "cpp_add failed"); - } - break; - } - #else - case OP_CPP_ADD: { - vm_raise_error(vm, "CPP support is not enabled (build with -DFUN_WITH_CPP=ON)"); - break; - } - #endif - - /* libsql ops (independent) */ - #ifdef FUN_WITH_LIBSQL - #include "vm/libsql/open.c" - #include "vm/libsql/close.c" - #include "vm/libsql/exec.c" - #include "vm/libsql/query.c" - #endif - - /* PCRE2 ops */ - #ifdef FUN_WITH_PCRE2 - #include "vm/pcre2/test.c" - #include "vm/pcre2/match.c" - #include "vm/pcre2/findall.c" - #endif - - #include "vm/strings/find.c" - #include "vm/strings/regex_match.c" - #include "vm/strings/regex_search.c" - #include "vm/strings/regex_replace.c" - #include "vm/strings/split.c" - #include "vm/strings/substr.c" - - #include "vm/len.c" - #include "vm/line.c" - #include "vm/print.c" - #include "vm/echo.c" - #include "vm/to_number.c" - #include "vm/to_string.c" - #include "vm/cast.c" - #include "vm/typeof.c" - #include "vm/uclamp.c" - #include "vm/sclamp.c" - #include "vm/os/list_dir.c" - - default: - if (!opcode_is_valid(inst.op)) { - fprintf(stderr, "Runtime error: unknown opcode %d (%s) at instruction %d\n", - inst.op, - (inst.op >= 0 && inst.op < sizeof(opcode_names)/sizeof(opcode_names[0])) - ? opcode_names[inst.op] : "???", - f->ip - 1); - exit(1); - } - break; - } - - /* Stream console output in realtime for scripts: - * When PRINT/ECHO pushed items into the VM's output buffer, flush them - * immediately to stdout and clear the buffer to avoid end-of-run bursts. - * This keeps REPL compatibility (REPL still prints after each submit), - * while regular script execution shows progress bars live. - */ - if (inst.op == OP_PRINT || inst.op == OP_ECHO) { - vm_print_output(vm); - vm_clear_output(vm); - fflush(stdout); - } + if (f->ip < 0 || f->ip >= f->fn->instr_count) { + /* no more instructions in this frame -> implicit return nil */ + Value nilv = make_nil(); + vm_pop_frame(vm); + push_value(vm, nilv); + continue; } - g_active_vm = NULL; + + Instruction inst = f->fn->instructions[f->ip++]; + vm->instr_count++; /* count each executed instruction */ + + if (vm->trace_enabled) { + const char *opname = (inst.op >= 0 && inst.op < (int)(sizeof(opcode_names) / sizeof(opcode_names[0]))) + ? opcode_names[inst.op] + : "???"; + const char *fname = f->fn && f->fn->name ? f->fn->name : ""; + const char *sfile = f->fn && f->fn->source_file ? f->fn->source_file : ""; + /* Dump up to top 4 stack values */ + int count = vm->sp + 1; + int start = count - 4; + if (start < 0) start = 0; + fprintf(stdout, "TRACE %s:%d %s ip=%d %-14s %d | stack[%d]=[", sfile, vm->current_line, fname, f->ip - 1, opname, inst.operand, count); + for (int i = start; i < count; ++i) { + char *sv = value_to_string_alloc(&vm->stack[i]); + if (!sv) sv = strdup(""); + fprintf(stdout, "%s%s", sv, (i == count - 1 ? "" : ", ")); + free(sv); + } + fprintf(stdout, "]\n"); + } + + /* Breakpoint hit detection: breakpoints are set on source_file:line via LINE markers */ + if (vm->on_error_repl && inst.op == OP_LINE && vm->break_count > 0) { + const char *sfile = (f->fn && f->fn->source_file) ? f->fn->source_file : NULL; + int line = inst.operand; + for (int bi = 0; bi < vm->break_count; ++bi) { + if (!vm->breakpoints[bi].active) continue; + if (vm->breakpoints[bi].line != line) continue; + if (!sfile || !vm->breakpoints[bi].file) continue; + if (strcmp(vm->breakpoints[bi].file, sfile) != 0) continue; + fprintf(stderr, "Breakpoint %d hit at %s:%d\n", bi, sfile, line); + vm->on_error_repl(vm); + /* After returning, refresh frame pointer and frame */ + if (vm->fp < 0) break; + f = &vm->frames[vm->fp]; + break; + } + } + + switch (inst.op) { +/* All opcode handlers as .c includes */ +#include "vm/arithmetic/add.c" +#include "vm/arithmetic/div.c" +#include "vm/arithmetic/mul.c" +#include "vm/arithmetic/sub.c" + +#include "vm/arrays/apop.c" +#include "vm/arrays/clear.c" +#include "vm/arrays/contains.c" +#include "vm/arrays/enumerate.c" +#include "vm/arrays/index_get.c" +#include "vm/arrays/index_of.c" +#include "vm/arrays/index_set.c" +#include "vm/arrays/insert.c" +#include "vm/arrays/join.c" +#include "vm/arrays/make_array.c" +#include "vm/arrays/push.c" +#include "vm/arrays/remove.c" +#include "vm/arrays/set.c" +#include "vm/arrays/slice.c" +#include "vm/arrays/zip.c" + +/* Bitwise and shifts/rotates */ +#include "vm/bitwise/band.c" +#include "vm/bitwise/bnot.c" +#include "vm/bitwise/bor.c" +#include "vm/bitwise/bxor.c" +#include "vm/bitwise/rol.c" +#include "vm/bitwise/ror.c" +#include "vm/bitwise/shl.c" +#include "vm/bitwise/shr.c" + +#include "vm/core/call.c" +#include "vm/core/dup.c" +#include "vm/core/exit.c" +#include "vm/core/halt.c" +#include "vm/core/jump.c" +#include "vm/core/jump_if_false.c" +#include "vm/core/load_const.c" +#include "vm/core/load_global.c" +#include "vm/core/load_local.c" +#include "vm/core/nop.c" +#include "vm/core/pop.c" +#include "vm/core/return.c" +#include "vm/core/store_global.c" +#include "vm/core/store_local.c" +#include "vm/core/swap.c" +#include "vm/core/throw.c" +#include "vm/core/try_pop.c" +#include "vm/core/try_push.c" + +#include "vm/io/input_line.c" +#include "vm/io/read_file.c" +#include "vm/io/write_file.c" + +#include "vm/logic/and.c" +#include "vm/logic/eq.c" +#include "vm/logic/gt.c" +#include "vm/logic/gte.c" +#include "vm/logic/lt.c" +#include "vm/logic/lte.c" +#include "vm/logic/neq.c" +#include "vm/logic/not.c" +#include "vm/logic/or.c" + +#include "vm/maps/has_key.c" +#include "vm/maps/keys.c" +#include "vm/maps/make_map.c" +#include "vm/maps/values.c" + +#include "vm/math/abs.c" +#include "vm/math/ceil.c" +#include "vm/math/clamp.c" +#include "vm/math/cos.c" +#include "vm/math/exp.c" +#include "vm/math/floor.c" +#include "vm/math/fmax.c" +#include "vm/math/fmin.c" +#include "vm/math/gcd.c" +#include "vm/math/isqrt.c" +#include "vm/math/lcm.c" +#include "vm/math/log.c" +#include "vm/math/log10.c" +#include "vm/math/max.c" +#include "vm/math/min.c" +#include "vm/math/mod.c" +#include "vm/math/pow.c" +#include "vm/math/random_int.c" +#include "vm/math/random_seed.c" +#include "vm/math/round.c" +#include "vm/math/sign.c" +#include "vm/math/sin.c" +#include "vm/math/sqrt.c" +#include "vm/math/tan.c" +#include "vm/math/trunc.c" + +/* Rust FFI demo opcode(s) */ +#include "vm/rust/get_sp.c" +#include "vm/rust/hello.c" +#include "vm/rust/hello_args.c" +#include "vm/rust/hello_args_return.c" +#include "vm/rust/set_exit.c" + +#include "vm/os/clock_mono_ms.c" +#include "vm/os/date_format.c" +#include "vm/os/env.c" +#include "vm/os/env_all.c" +#include "vm/os/fun_version.c" +#include "vm/os/proc_run.c" +#include "vm/os/proc_system.c" +#include "vm/os/random_number.c" +#include "vm/os/serial_close.c" +#include "vm/os/serial_config.c" +#include "vm/os/serial_open.c" +#include "vm/os/serial_recv.c" +#include "vm/os/serial_send.c" +#include "vm/os/sleep_ms.c" +#include "vm/os/thread_join.c" +#include "vm/os/thread_spawn.c" +#include "vm/os/time_now_ms.c" + +/* Socket ops */ +#include "vm/os/socket_close.c" +#include "vm/os/socket_recv.c" +#include "vm/os/socket_send.c" +#include "vm/os/socket_tcp_accept.c" +#include "vm/os/socket_tcp_connect.c" +#include "vm/os/socket_tcp_listen.c" +#include "vm/os/socket_unix_connect.c" +#include "vm/os/socket_unix_listen.c" + +#ifdef FUN_WITH_PCSC +#include "vm/pcsc/connect.c" +#include "vm/pcsc/disconnect.c" +#include "vm/pcsc/establish.c" +#include "vm/pcsc/list_readers.c" +#include "vm/pcsc/release.c" +#include "vm/pcsc/transmit.c" +#endif + +/* JSON ops (implemented in jsonc.c, included above) */ +#ifdef FUN_WITH_JSON +#include "vm/json/from_file.c" +#include "vm/json/parse.c" +#include "vm/json/stringify.c" +#include "vm/json/to_file.c" +#endif + +/* XML ops (libxml2) */ +#ifdef FUN_WITH_XML2 +#include "vm/xml/name.c" +#include "vm/xml/parse.c" +#include "vm/xml/root.c" +#include "vm/xml/text.c" +#endif + +/* INI ops (iniparser 4.2.6) */ +#ifdef FUN_WITH_INI +#include "vm/ini/free.c" +#include "vm/ini/get_bool.c" +#include "vm/ini/get_double.c" +#include "vm/ini/get_int.c" +#include "vm/ini/get_string.c" +#include "vm/ini/load.c" +#include "vm/ini/save.c" +#include "vm/ini/set.c" +#include "vm/ini/unset.c" +#else +#include "vm/ini/stubs.c" +#endif + +/* CURL ops */ +#ifdef FUN_WITH_CURL +#include "vm/curl/download.c" +#include "vm/curl/get.c" +#include "vm/curl/post.c" +#endif + +/* OpenSSL ops (md5/sha256/sha512/ripemd160) */ +#ifdef FUN_WITH_OPENSSL +#include "vm/openssl/md5.c" +#include "vm/openssl/ripemd160.c" +#include "vm/openssl/sha256.c" +#include "vm/openssl/sha512.c" +#endif + +/* LibreSSL ops (md5/sha256/sha512/ripemd160) */ +#ifdef FUN_WITH_LIBRESSL +#include "vm/libressl/md5.c" +#include "vm/libressl/ripemd160.c" +#include "vm/libressl/sha256.c" +#include "vm/libressl/sha512.c" +#endif + +/* Tk (Tcl/Tk) ops */ +#ifdef FUN_WITH_TCLTK +#include "vm/tk/bind.c" +#include "vm/tk/button.c" +#include "vm/tk/eval.c" +#include "vm/tk/label.c" +#include "vm/tk/loop.c" +#include "vm/tk/pack.c" +#include "vm/tk/result.c" +#include "vm/tk/wm_title.c" +#endif + +/* Notcurses TUI ops (optional) */ +#ifdef FUN_WITH_NOTCURSES +#include "vm/notcurses/clear.c" +#include "vm/notcurses/draw_text.c" +#include "vm/notcurses/getch.c" +#include "vm/notcurses/init.c" +#include "vm/notcurses/shutdown.c" +#endif + +/* SQLite ops */ +#ifdef FUN_WITH_SQLITE +#include "vm/sqlite/close.c" +#include "vm/sqlite/exec.c" +#include "vm/sqlite/open.c" +#include "vm/sqlite/query.c" +#endif + +/* C++ demo opcodes (guarded) */ +#if defined(FUN_WITH_CPP) + case OP_CPP_ADD: { + int rc = fun_op_cpp_add(vm); + if (rc != 0) { + vm_raise_error(vm, "cpp_add failed"); + } + break; + } +#else + case OP_CPP_ADD: { + vm_raise_error(vm, "CPP support is not enabled (build with -DFUN_WITH_CPP=ON)"); + break; + } +#endif + +/* libsql ops (independent) */ +#ifdef FUN_WITH_LIBSQL +#include "vm/libsql/close.c" +#include "vm/libsql/exec.c" +#include "vm/libsql/open.c" +#include "vm/libsql/query.c" +#endif + +/* PCRE2 ops */ +#ifdef FUN_WITH_PCRE2 +#include "vm/pcre2/findall.c" +#include "vm/pcre2/match.c" +#include "vm/pcre2/test.c" +#endif + +#include "vm/strings/find.c" +#include "vm/strings/regex_match.c" +#include "vm/strings/regex_replace.c" +#include "vm/strings/regex_search.c" +#include "vm/strings/split.c" +#include "vm/strings/substr.c" + +#include "vm/cast.c" +#include "vm/echo.c" +#include "vm/len.c" +#include "vm/line.c" +#include "vm/os/list_dir.c" +#include "vm/print.c" +#include "vm/sclamp.c" +#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)) { + fprintf(stderr, "Runtime error: unknown opcode %d (%s) at instruction %d\n", + inst.op, + (inst.op >= 0 && inst.op < sizeof(opcode_names) / sizeof(opcode_names[0])) + ? opcode_names[inst.op] + : "???", + f->ip - 1); + exit(1); + } + break; + } + + /* Stream console output in realtime for scripts: + * When PRINT/ECHO pushed items into the VM's output buffer, flush them + * immediately to stdout and clear the buffer to avoid end-of-run bursts. + * This keeps REPL compatibility (REPL still prints after each submit), + * while regular script execution shows progress bars live. + */ + if (inst.op == OP_PRINT || inst.op == OP_ECHO) { + vm_print_output(vm); + vm_clear_output(vm); + fflush(stdout); + } + } + g_active_vm = NULL; } diff --git a/src/vm.h b/src/vm.h index fdca9b6..37ac3b7 100644 --- a/src/vm.h +++ b/src/vm.h @@ -20,94 +20,93 @@ #define STACK_SIZE 1024 static const char *opcode_names[] = { - "NOP","LOAD_CONST","LOAD_LOCAL","STORE_LOCAL", - "LOAD_GLOBAL","STORE_GLOBAL","ADD","SUB","MUL","DIV", - "LT","LTE","GT","GTE","EQ","NEQ","POP","JUMP", - "JUMP_IF_FALSE","CALL","RETURN","PRINT","ECHO","HALT", - "LINE", - "MOD","AND","OR","NOT","DUP","SWAP", - "MAKE_ARRAY","INDEX_GET","INDEX_SET", - "LEN","PUSH","APOP","SET","INSERT","REMOVE","SLICE", - "TO_NUMBER","TO_STRING","CAST","TYPEOF", - "SPLIT","JOIN","SUBSTR","FIND", - "REGEX_MATCH","REGEX_SEARCH","REGEX_REPLACE", - "CONTAINS","INDEX_OF","CLEAR", - "ENUMERATE","ZIP", - "MIN","MAX","CLAMP","ABS","POW","RANDOM_SEED","RANDOM_INT", - "MAKE_MAP","KEYS","VALUES","HAS_KEY", - "READ_FILE","WRITE_FILE","ENV","INPUT_LINE","PROC_RUN","PROC_SYSTEM", - "TIME_NOW_MS","CLOCK_MONO_MS","DATE_FORMAT", - "THREAD_SPAWN","THREAD_JOIN","SLEEP_MS", - "RANDOM_NUMBER", - "BAND","BOR","BXOR","BNOT","SHL","SHR","ROTL","ROTR", - "JSON_PARSE","JSON_STRINGIFY","JSON_FROM_FILE","JSON_TO_FILE", - "CURL_GET","CURL_POST","CURL_DOWNLOAD", - "SQLITE_OPEN","SQLITE_CLOSE","SQLITE_EXEC","SQLITE_QUERY", - "LIBSQL_OPEN","LIBSQL_CLOSE","LIBSQL_EXEC","LIBSQL_QUERY", - "PCSC_ESTABLISH","PCSC_RELEASE","PCSC_LIST_READERS","PCSC_CONNECT","PCSC_DISCONNECT","PCSC_TRANSMIT", - "PCRE2_TEST","PCRE2_MATCH","PCRE2_FINDALL", - "INI_LOAD","INI_FREE","INI_GET_STRING","INI_GET_INT","INI_GET_DOUBLE","INI_GET_BOOL","INI_SET","INI_UNSET","INI_SAVE", - "XML_PARSE","XML_ROOT","XML_NAME","XML_TEXT", - "SOCK_TCP_LISTEN","SOCK_TCP_ACCEPT","SOCK_TCP_CONNECT","SOCK_SEND","SOCK_RECV","SOCK_CLOSE","SOCK_UNIX_LISTEN","SOCK_UNIX_CONNECT", - "EXIT", - "OS_LIST_DIR", - "TK_BIND", - "SERIAL_OPEN","SERIAL_CONFIG","SERIAL_SEND","SERIAL_RECV","SERIAL_CLOSE", - "TK_EVAL","TK_RESULT","TK_LOOP","TK_WM_TITLE","TK_LABEL","TK_BUTTON","TK_PACK", - "TRY_PUSH","TRY_POP","THROW", - "FMIN","FMAX", - /* Rust FFI demo */ - "RUST_HELLO","RUST_HELLO_ARGS","RUST_HELLO_ARGS_RETURN","RUST_GET_SP","RUST_SET_EXIT", - /* C++ demo */ - "CPP_ADD", - /* Notcurses TUI (optional) */ - "NC_INIT","NC_SHUTDOWN","NC_CLEAR","NC_DRAW_TEXT","NC_GETCH" -}; + "NOP", "LOAD_CONST", "LOAD_LOCAL", "STORE_LOCAL", + "LOAD_GLOBAL", "STORE_GLOBAL", "ADD", "SUB", "MUL", "DIV", + "LT", "LTE", "GT", "GTE", "EQ", "NEQ", "POP", "JUMP", + "JUMP_IF_FALSE", "CALL", "RETURN", "PRINT", "ECHO", "HALT", + "LINE", + "MOD", "AND", "OR", "NOT", "DUP", "SWAP", + "MAKE_ARRAY", "INDEX_GET", "INDEX_SET", + "LEN", "PUSH", "APOP", "SET", "INSERT", "REMOVE", "SLICE", + "TO_NUMBER", "TO_STRING", "CAST", "TYPEOF", + "SPLIT", "JOIN", "SUBSTR", "FIND", + "REGEX_MATCH", "REGEX_SEARCH", "REGEX_REPLACE", + "CONTAINS", "INDEX_OF", "CLEAR", + "ENUMERATE", "ZIP", + "MIN", "MAX", "CLAMP", "ABS", "POW", "RANDOM_SEED", "RANDOM_INT", + "MAKE_MAP", "KEYS", "VALUES", "HAS_KEY", + "READ_FILE", "WRITE_FILE", "ENV", "INPUT_LINE", "PROC_RUN", "PROC_SYSTEM", + "TIME_NOW_MS", "CLOCK_MONO_MS", "DATE_FORMAT", + "THREAD_SPAWN", "THREAD_JOIN", "SLEEP_MS", + "RANDOM_NUMBER", + "BAND", "BOR", "BXOR", "BNOT", "SHL", "SHR", "ROTL", "ROTR", + "JSON_PARSE", "JSON_STRINGIFY", "JSON_FROM_FILE", "JSON_TO_FILE", + "CURL_GET", "CURL_POST", "CURL_DOWNLOAD", + "SQLITE_OPEN", "SQLITE_CLOSE", "SQLITE_EXEC", "SQLITE_QUERY", + "LIBSQL_OPEN", "LIBSQL_CLOSE", "LIBSQL_EXEC", "LIBSQL_QUERY", + "PCSC_ESTABLISH", "PCSC_RELEASE", "PCSC_LIST_READERS", "PCSC_CONNECT", "PCSC_DISCONNECT", "PCSC_TRANSMIT", + "PCRE2_TEST", "PCRE2_MATCH", "PCRE2_FINDALL", + "INI_LOAD", "INI_FREE", "INI_GET_STRING", "INI_GET_INT", "INI_GET_DOUBLE", "INI_GET_BOOL", "INI_SET", "INI_UNSET", "INI_SAVE", + "XML_PARSE", "XML_ROOT", "XML_NAME", "XML_TEXT", + "SOCK_TCP_LISTEN", "SOCK_TCP_ACCEPT", "SOCK_TCP_CONNECT", "SOCK_SEND", "SOCK_RECV", "SOCK_CLOSE", "SOCK_UNIX_LISTEN", "SOCK_UNIX_CONNECT", + "EXIT", + "OS_LIST_DIR", + "TK_BIND", + "SERIAL_OPEN", "SERIAL_CONFIG", "SERIAL_SEND", "SERIAL_RECV", "SERIAL_CLOSE", + "TK_EVAL", "TK_RESULT", "TK_LOOP", "TK_WM_TITLE", "TK_LABEL", "TK_BUTTON", "TK_PACK", + "TRY_PUSH", "TRY_POP", "THROW", + "FMIN", "FMAX", + /* Rust FFI demo */ + "RUST_HELLO", "RUST_HELLO_ARGS", "RUST_HELLO_ARGS_RETURN", "RUST_GET_SP", "RUST_SET_EXIT", + /* C++ demo */ + "CPP_ADD", + /* Notcurses TUI (optional) */ + "NC_INIT", "NC_SHUTDOWN", "NC_CLEAR", "NC_DRAW_TEXT", "NC_GETCH"}; typedef struct { - Bytecode *fn; - int ip; - Value locals[MAX_FRAME_LOCALS]; - /* exception handling (per-frame) */ - int try_stack[16]; - int try_sp; /* -1 when empty */ + Bytecode *fn; + int ip; + Value locals[MAX_FRAME_LOCALS]; + /* exception handling (per-frame) */ + int try_stack[16]; + int try_sp; /* -1 when empty */ } Frame; struct VM { - Value stack[STACK_SIZE]; - int sp; + Value stack[STACK_SIZE]; + int sp; - Frame frames[MAX_FRAMES]; - int fp; // frame pointer, -1 when no frame + Frame frames[MAX_FRAMES]; + int fp; // frame pointer, -1 when no frame - Value globals[MAX_GLOBALS]; + Value globals[MAX_GLOBALS]; - Value output[OUTPUT_SIZE]; // store printed values - int output_count; - int output_is_partial[OUTPUT_SIZE]; // 1 when the corresponding output entry should not end with newline (echo) + Value output[OUTPUT_SIZE]; // store printed values + int output_count; + int output_is_partial[OUTPUT_SIZE]; // 1 when the corresponding output entry should not end with newline (echo) - long long instr_count; // executed instructions in the last vm_run + long long instr_count; // executed instructions in the last vm_run - int current_line; // last executed source line (debug) + int current_line; // last executed source line (debug) - int exit_code; // process exit code set by OP_EXIT + int exit_code; // process exit code set by OP_EXIT - int trace_enabled; // when non-zero, print executed ops and stack - int repl_on_error; // when non-zero, enter REPL on runtime error (preserve stack) - int (*on_error_repl)(struct VM *vm); // optional hook to run REPL on error + int trace_enabled; // when non-zero, print executed ops and stack + int repl_on_error; // when non-zero, enter REPL on runtime error (preserve stack) + int (*on_error_repl)(struct VM *vm); // optional hook to run REPL on error - /* --- Debugger state --- */ - int debug_step_mode; // 0 none, 1 step, 2 next, 3 finish - int debug_step_target_fp; // target frame pointer for next/finish - long long debug_step_start_ic; // instruction count snapshot when step/next requested - int debug_stop_requested; // force a pause at loop top + /* --- Debugger state --- */ + int debug_step_mode; // 0 none, 1 step, 2 next, 3 finish + int debug_step_target_fp; // target frame pointer for next/finish + long long debug_step_start_ic; // instruction count snapshot when step/next requested + int debug_stop_requested; // force a pause at loop top - struct { - char *file; // strdup'ed file path - int line; // 1-based line - int active; // 1 if active - } breakpoints[64]; - int break_count; // number of active breakpoints + struct { + char *file; // strdup'ed file path + int line; // 1-based line + int active; // 1 if active + } breakpoints[64]; + int break_count; // number of active breakpoints }; typedef struct VM VM; @@ -136,8 +135,8 @@ void vm_raise_error(VM *vm, const char *msg); /* --- Debugger API --- */ void vm_debug_reset(VM *vm); -int vm_debug_add_breakpoint(VM *vm, const char *file, int line); // returns id >=0 or -1 -int vm_debug_delete_breakpoint(VM *vm, int id); // returns 1 on success +int vm_debug_add_breakpoint(VM *vm, const char *file, int line); // returns id >=0 or -1 +int vm_debug_delete_breakpoint(VM *vm, int id); // returns 1 on success void vm_debug_clear_breakpoints(VM *vm); void vm_debug_list_breakpoints(VM *vm); void vm_debug_request_step(VM *vm); @@ -146,7 +145,7 @@ void vm_debug_request_finish(VM *vm); void vm_debug_request_continue(VM *vm); static inline int opcode_is_valid(int op) { - return op >= OP_NOP && op <= OP_NC_GETCH; // all current opcodes (including optional NC_*) + return op >= OP_NOP && op <= OP_NC_GETCH; // all current opcodes (including optional NC_*) } /* --- Minimal C ABI helpers for FFI (Rust opcode experiments) --- */ diff --git a/src/vm/arithmetic/add.c b/src/vm/arithmetic/add.c index 42590df..360b210 100644 --- a/src/vm/arithmetic/add.c +++ b/src/vm/arithmetic/add.c @@ -8,7 +8,7 @@ */ /** -* @file add.c + * @file add.c * @brief Implements the OP_ADD opcode for arithmetic and string concatenation in the VM. * * This file handles the OP_ADD instruction, which performs addition or concatenation @@ -36,50 +36,50 @@ */ case OP_ADD: { - Value b = pop_value(vm); - Value a = pop_value(vm); - 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 : ""; - size_t la = strlen(sa); - size_t lb = strlen(sb); - char *buf = (char*)malloc(la + lb + 1); - if (!buf) { - fprintf(stderr, "Runtime error: out of memory during string concatenation\n"); - exit(1); - } - memcpy(buf, sa, la); - memcpy(buf + la, sb, lb); - buf[la + lb] = '\0'; - Value res; - res.type = VAL_STRING; - res.s = buf; - free_value(a); - free_value(b); - push_value(vm, res); - } else if (a.type == VAL_ARRAY && b.type == VAL_ARRAY) { - Value res = array_concat(&a, &b); - free_value(a); - free_value(b); - push_value(vm, res); + Value b = pop_value(vm); + Value a = pop_value(vm); + 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 { - 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); + Value res = make_int(a.i + b.i); + free_value(a); + free_value(b); + push_value(vm, res); } - break; + } else if (a.type == VAL_STRING && b.type == VAL_STRING) { + const char *sa = a.s ? a.s : ""; + const char *sb = b.s ? b.s : ""; + size_t la = strlen(sa); + size_t lb = strlen(sb); + char *buf = (char *)malloc(la + lb + 1); + if (!buf) { + fprintf(stderr, "Runtime error: out of memory during string concatenation\n"); + exit(1); + } + memcpy(buf, sa, la); + memcpy(buf + la, sb, lb); + buf[la + lb] = '\0'; + Value res; + res.type = VAL_STRING; + res.s = buf; + free_value(a); + free_value(b); + push_value(vm, res); + } else if (a.type == VAL_ARRAY && b.type == VAL_ARRAY) { + Value res = array_concat(&a, &b); + free_value(a); + free_value(b); + push_value(vm, res); + } else { + 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); + } + break; } diff --git a/src/vm/arithmetic/div.c b/src/vm/arithmetic/div.c index fa37615..26f7fbd 100644 --- a/src/vm/arithmetic/div.c +++ b/src/vm/arithmetic/div.c @@ -8,7 +8,7 @@ */ /** -* @file div.c + * @file div.c * @brief Implements the OP_DIV opcode for integer division in the VM. * * This file handles the OP_DIV instruction, which performs integer division @@ -33,34 +33,34 @@ */ case OP_DIV: { - Value b = pop_value(vm); - Value a = pop_value(vm); - 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) { - vm_raise_error(vm, "division by zero"); - break; - } - Value res = make_float(da / db); - free_value(a); - free_value(b); - push_value(vm, res); - } else { - if (b.i == 0) { - vm_raise_error(vm, "division by zero"); - break; - } - Value res = make_int(a.i / b.i); - free_value(a); - free_value(b); - push_value(vm, res); - } + Value b = pop_value(vm); + Value a = pop_value(vm); + 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) { + vm_raise_error(vm, "division by zero"); + break; + } + Value res = make_float(da / db); + 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) { + vm_raise_error(vm, "division by zero"); + break; + } + Value res = make_int(a.i / b.i); + free_value(a); + free_value(b); + push_value(vm, res); } - break; + } 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); + } + break; } diff --git a/src/vm/arithmetic/mul.c b/src/vm/arithmetic/mul.c index 203cad2..b4eafec 100644 --- a/src/vm/arithmetic/mul.c +++ b/src/vm/arithmetic/mul.c @@ -8,7 +8,7 @@ */ /** -* @file mul.c + * @file mul.c * @brief Implements the OP_MUL opcode for integer multiplication in the VM. * * This file handles the OP_MUL instruction, which performs integer multiplication @@ -32,26 +32,26 @@ */ case OP_MUL: { - Value b = pop_value(vm); - Value a = pop_value(vm); - 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); - } + Value b = pop_value(vm); + Value a = pop_value(vm); + 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 { - 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; + } 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); + } + break; } diff --git a/src/vm/arithmetic/sub.c b/src/vm/arithmetic/sub.c index 6f7ac1b..e0939d1 100644 --- a/src/vm/arithmetic/sub.c +++ b/src/vm/arithmetic/sub.c @@ -8,7 +8,7 @@ */ /** -* @file sub.c + * @file sub.c * @brief Implements the OP_SUB opcode for integer subtraction in the VM. * * This file handles the OP_SUB instruction, which performs integer subtraction @@ -32,26 +32,26 @@ */ case OP_SUB: { - Value b = pop_value(vm); - Value a = pop_value(vm); - 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); - } + Value b = pop_value(vm); + Value a = pop_value(vm); + 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 { - 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; + } 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); + } + break; } diff --git a/src/vm/arrays/apop.c b/src/vm/arrays/apop.c index e3a8a47..40ce90b 100644 --- a/src/vm/arrays/apop.c +++ b/src/vm/arrays/apop.c @@ -8,7 +8,7 @@ */ /** -* @file apop.c + * @file apop.c * @brief Implements the OP_APOP opcode for removing elements from arrays in the VM. * * This file handles the OP_APOP instruction, which removes the last element from an array @@ -33,17 +33,17 @@ */ case OP_APOP: { - Value arr = pop_value(vm); - if (arr.type != VAL_ARRAY) { - fprintf(stderr, "Runtime type error: ARR_APOP expects array\n"); - exit(1); - } - Value out; - if (!array_pop(&arr, &out)) { - fprintf(stderr, "Runtime error: pop from empty array\n"); - exit(1); - } - free_value(arr); - push_value(vm, out); - break; + Value arr = pop_value(vm); + if (arr.type != VAL_ARRAY) { + fprintf(stderr, "Runtime type error: ARR_APOP expects array\n"); + exit(1); + } + Value out; + if (!array_pop(&arr, &out)) { + fprintf(stderr, "Runtime error: pop from empty array\n"); + exit(1); + } + free_value(arr); + push_value(vm, out); + break; } diff --git a/src/vm/arrays/clear.c b/src/vm/arrays/clear.c index 2ebb66d..acaf1b7 100644 --- a/src/vm/arrays/clear.c +++ b/src/vm/arrays/clear.c @@ -32,13 +32,13 @@ */ case OP_CLEAR: { - Value arr = pop_value(vm); - if (arr.type != VAL_ARRAY) { - fprintf(stderr, "Runtime type error: CLEAR expects array\n"); - exit(1); - } - array_clear(&arr); - free_value(arr); - push_value(vm, make_int(0)); - break; + Value arr = pop_value(vm); + if (arr.type != VAL_ARRAY) { + fprintf(stderr, "Runtime type error: CLEAR expects array\n"); + exit(1); + } + array_clear(&arr); + free_value(arr); + push_value(vm, make_int(0)); + break; } diff --git a/src/vm/arrays/contains.c b/src/vm/arrays/contains.c index 892805d..71b9d6f 100644 --- a/src/vm/arrays/contains.c +++ b/src/vm/arrays/contains.c @@ -8,7 +8,7 @@ */ /** -* @file contains.c + * @file contains.c * @brief Implements the OP_CONTAINS opcode for checking array membership in the VM. * * This file handles the OP_CONTAINS instruction, which checks if a value is present in an array. @@ -32,15 +32,15 @@ */ case OP_CONTAINS: { - Value needle = pop_value(vm); - Value arr = pop_value(vm); - if (arr.type != VAL_ARRAY) { - fprintf(stderr, "Runtime type error: CONTAINS expects (array, value)\n"); - exit(1); - } - int ok = array_contains(&arr, &needle); - free_value(arr); - free_value(needle); - push_value(vm, make_int(ok ? 1 : 0)); - break; + Value needle = pop_value(vm); + Value arr = pop_value(vm); + if (arr.type != VAL_ARRAY) { + fprintf(stderr, "Runtime type error: CONTAINS expects (array, value)\n"); + exit(1); + } + int ok = array_contains(&arr, &needle); + free_value(arr); + free_value(needle); + push_value(vm, make_int(ok ? 1 : 0)); + break; } diff --git a/src/vm/arrays/enumerate.c b/src/vm/arrays/enumerate.c index 6b7dfb2..716e8b0 100644 --- a/src/vm/arrays/enumerate.c +++ b/src/vm/arrays/enumerate.c @@ -8,7 +8,7 @@ */ /** -* @file enumerate.c + * @file enumerate.c * @brief Implements the OP_ENUMERATE opcode for enumerating arrays in the VM. * * This file handles the OP_ENUMERATE instruction, which creates an array of [index, value] pairs @@ -32,13 +32,13 @@ */ case OP_ENUMERATE: { - Value arr = pop_value(vm); - if (arr.type != VAL_ARRAY) { - fprintf(stderr, "Runtime type error: ENUMERATE expects array\n"); - exit(1); - } - Value out = bi_enumerate(&arr); - free_value(arr); - push_value(vm, out); - break; + Value arr = pop_value(vm); + if (arr.type != VAL_ARRAY) { + fprintf(stderr, "Runtime type error: ENUMERATE expects array\n"); + exit(1); + } + Value out = bi_enumerate(&arr); + free_value(arr); + push_value(vm, out); + break; } diff --git a/src/vm/arrays/index_get.c b/src/vm/arrays/index_get.c index 7da0c5d..69da0f5 100644 --- a/src/vm/arrays/index_get.c +++ b/src/vm/arrays/index_get.c @@ -8,7 +8,7 @@ */ /** -* @file index_get.c + * @file index_get.c * @brief Implements the OP_INDEX_GET opcode for array and map indexing in the VM. * * This file handles the OP_INDEX_GET instruction, which retrieves an element from @@ -34,34 +34,41 @@ */ case OP_INDEX_GET: { - Value idx = pop_value(vm); - Value container = pop_value(vm); + Value idx = pop_value(vm); + Value container = pop_value(vm); #ifdef FUN_DEBUG - fprintf(stderr, "DEBUG INDEX_GET: container.type=%d idx.type=%d\n", - container.type, idx.type); + fprintf(stderr, "DEBUG INDEX_GET: container.type=%d idx.type=%d\n", + container.type, idx.type); #endif - if (container.type == VAL_ARRAY) { - if (idx.type != VAL_INT) { fprintf(stderr, "INDEX_GET index must be int for array\n"); exit(1); } - Value elem; - if (!array_get_copy(&container, (int)idx.i, &elem)) { - fprintf(stderr, "Runtime error: index out of range\n"); exit(1); - } - free_value(container); - free_value(idx); - push_value(vm, elem); - } else if (container.type == VAL_MAP) { - if (idx.type != VAL_STRING) { fprintf(stderr, "INDEX_GET key must be string for map\n"); exit(1); } - Value out; - if (!map_get_copy(&container, idx.s ? idx.s : "", &out)) { - out = make_nil(); - } - free_value(container); - free_value(idx); - push_value(vm, out); - } else { - fprintf(stderr, "Runtime type error: INDEX_GET expects array or map (got container=%s, index=%s)\n", - value_type_name(container.type), value_type_name(idx.type)); - exit(1); + if (container.type == VAL_ARRAY) { + if (idx.type != VAL_INT) { + fprintf(stderr, "INDEX_GET index must be int for array\n"); + exit(1); } - break; + Value elem; + if (!array_get_copy(&container, (int)idx.i, &elem)) { + fprintf(stderr, "Runtime error: index out of range\n"); + exit(1); + } + free_value(container); + free_value(idx); + push_value(vm, elem); + } else if (container.type == VAL_MAP) { + if (idx.type != VAL_STRING) { + fprintf(stderr, "INDEX_GET key must be string for map\n"); + exit(1); + } + Value out; + if (!map_get_copy(&container, idx.s ? idx.s : "", &out)) { + out = make_nil(); + } + free_value(container); + free_value(idx); + push_value(vm, out); + } else { + fprintf(stderr, "Runtime type error: INDEX_GET expects array or map (got container=%s, index=%s)\n", + value_type_name(container.type), value_type_name(idx.type)); + exit(1); + } + break; } diff --git a/src/vm/arrays/index_of.c b/src/vm/arrays/index_of.c index 57f3c99..6ce8c7e 100644 --- a/src/vm/arrays/index_of.c +++ b/src/vm/arrays/index_of.c @@ -8,7 +8,7 @@ */ /** -* @file index_of.c + * @file index_of.c * @brief Implements the OP_INDEX_OF opcode for finding the index of a value in an array in the VM. * * This file handles the OP_INDEX_OF instruction, which finds the index of a value in an array. @@ -32,15 +32,15 @@ */ case OP_INDEX_OF: { - Value needle = pop_value(vm); - Value arr = pop_value(vm); - if (arr.type != VAL_ARRAY) { - fprintf(stderr, "Runtime type error: INDEX_OF expects (array, value)\n"); - exit(1); - } - int idx = array_index_of(&arr, &needle); - free_value(arr); - free_value(needle); - push_value(vm, make_int(idx)); - break; + Value needle = pop_value(vm); + Value arr = pop_value(vm); + if (arr.type != VAL_ARRAY) { + fprintf(stderr, "Runtime type error: INDEX_OF expects (array, value)\n"); + exit(1); + } + int idx = array_index_of(&arr, &needle); + free_value(arr); + free_value(needle); + push_value(vm, make_int(idx)); + break; } diff --git a/src/vm/arrays/index_set.c b/src/vm/arrays/index_set.c index 5af096e..1ebc783 100644 --- a/src/vm/arrays/index_set.c +++ b/src/vm/arrays/index_set.c @@ -8,7 +8,7 @@ */ /** -* @file index_set.c + * @file index_set.c * @brief Implements the OP_INDEX_SET opcode for array and map assignment in the VM. * * This file handles the OP_INDEX_SET instruction, which assigns a value to an @@ -32,32 +32,39 @@ * @date 2025-10-16 */ - case OP_INDEX_SET: { - Value v = pop_value(vm); - Value idx = pop_value(vm); - Value container = pop_value(vm); + Value v = pop_value(vm); + Value idx = pop_value(vm); + Value container = pop_value(vm); #ifdef FUN_DEBUG - fprintf(stderr, "DEBUG INDEX_SET: container.type=%d idx.type=%d value.type=%d\n", - container.type, idx.type, v.type); + fprintf(stderr, "DEBUG INDEX_SET: container.type=%d idx.type=%d value.type=%d\n", + container.type, idx.type, v.type); #endif - if (container.type == VAL_ARRAY) { - if (idx.type != VAL_INT) { fprintf(stderr, "INDEX_SET index must be int for array\n"); exit(1); } - if (!array_set(&container, (int)idx.i, v)) { - fprintf(stderr, "Runtime error: index out of range\n"); exit(1); - } - free_value(container); - free_value(idx); - } else if (container.type == VAL_MAP) { - if (idx.type != VAL_STRING) { fprintf(stderr, "INDEX_SET key must be string for map\n"); exit(1); } - if (!map_set(&container, idx.s ? idx.s : "", v)) { - fprintf(stderr, "Runtime error: map set failed\n"); exit(1); - } - free_value(container); - free_value(idx); - } else { - fprintf(stderr, "Runtime type error: INDEX_SET expects array or map\n"); - exit(1); + if (container.type == VAL_ARRAY) { + if (idx.type != VAL_INT) { + fprintf(stderr, "INDEX_SET index must be int for array\n"); + exit(1); } - break; + if (!array_set(&container, (int)idx.i, v)) { + fprintf(stderr, "Runtime error: index out of range\n"); + exit(1); + } + free_value(container); + free_value(idx); + } else if (container.type == VAL_MAP) { + if (idx.type != VAL_STRING) { + fprintf(stderr, "INDEX_SET key must be string for map\n"); + exit(1); + } + if (!map_set(&container, idx.s ? idx.s : "", v)) { + fprintf(stderr, "Runtime error: map set failed\n"); + exit(1); + } + free_value(container); + free_value(idx); + } else { + fprintf(stderr, "Runtime type error: INDEX_SET expects array or map\n"); + exit(1); + } + break; } diff --git a/src/vm/arrays/insert.c b/src/vm/arrays/insert.c index 6bfc4cb..fb73d5b 100644 --- a/src/vm/arrays/insert.c +++ b/src/vm/arrays/insert.c @@ -8,7 +8,7 @@ */ /** -* @file insert.c + * @file insert.c * @brief Implements the OP_INSERT opcode for inserting elements into arrays in the VM. * * This file handles the OP_INSERT instruction, which inserts a value into an array @@ -34,20 +34,20 @@ */ case OP_INSERT: { - Value v = pop_value(vm); - Value idx = pop_value(vm); - Value arr = pop_value(vm); - if (arr.type != VAL_ARRAY || idx.type != VAL_INT) { - fprintf(stderr, "Runtime type error: ARR_INSERT expects (array, int, value)\n"); - exit(1); - } - int n = array_insert(&arr, (int)idx.i, v); - if (n < 0) { - fprintf(stderr, "Runtime error: insert failed (OOM?)\n"); - exit(1); - } - free_value(arr); - free_value(idx); - push_value(vm, make_int(n)); - break; + Value v = pop_value(vm); + Value idx = pop_value(vm); + Value arr = pop_value(vm); + if (arr.type != VAL_ARRAY || idx.type != VAL_INT) { + fprintf(stderr, "Runtime type error: ARR_INSERT expects (array, int, value)\n"); + exit(1); + } + int n = array_insert(&arr, (int)idx.i, v); + if (n < 0) { + fprintf(stderr, "Runtime error: insert failed (OOM?)\n"); + exit(1); + } + free_value(arr); + free_value(idx); + push_value(vm, make_int(n)); + break; } diff --git a/src/vm/arrays/join.c b/src/vm/arrays/join.c index 494746d..eddcfff 100644 --- a/src/vm/arrays/join.c +++ b/src/vm/arrays/join.c @@ -8,7 +8,7 @@ */ /** -* @file join.c + * @file join.c * @brief Implements the OP_JOIN opcode for joining array elements into a string in the VM. * * This file handles the OP_JOIN instruction, which joins the elements of an array into a string @@ -27,21 +27,21 @@ * // Bytecode: OP_JOIN * // Stack before: [", ", ["a", "b", "c"]] * // Stack after: ["a, b, c"] - * + * * @author Johannes Findeisen * @date 2025-10-16 */ case OP_JOIN: { - Value sep = pop_value(vm); - Value arr = pop_value(vm); - if (arr.type != VAL_ARRAY || sep.type != VAL_STRING) { - fprintf(stderr, "Runtime type error: JOIN expects (array, string)\n"); - exit(1); - } - Value out = bi_join(&arr, &sep); - free_value(arr); - free_value(sep); - push_value(vm, out); - break; + Value sep = pop_value(vm); + Value arr = pop_value(vm); + if (arr.type != VAL_ARRAY || sep.type != VAL_STRING) { + fprintf(stderr, "Runtime type error: JOIN expects (array, string)\n"); + exit(1); + } + Value out = bi_join(&arr, &sep); + free_value(arr); + free_value(sep); + push_value(vm, out); + break; } diff --git a/src/vm/arrays/make_array.c b/src/vm/arrays/make_array.c index 816c4c0..b205b06 100644 --- a/src/vm/arrays/make_array.c +++ b/src/vm/arrays/make_array.c @@ -8,7 +8,7 @@ */ /** -* @file make_array.c + * @file make_array.c * @brief Implements the OP_MAKE_ARRAY opcode for creating arrays in the VM. * * This file handles the OP_MAKE_ARRAY instruction, which pops `n` values from the stack, @@ -33,23 +33,26 @@ */ case OP_MAKE_ARRAY: { - int n = inst.operand; - if (n < 0 || vm->sp + 1 < n) { - fprintf(stderr, "Runtime error: invalid element count for MAKE_ARRAY\n"); - exit(1); - } - /* pop n values into temp array preserving original order */ - Value *vals = (Value*)malloc(sizeof(Value) * n); - if (!vals) { fprintf(stderr, "Runtime error: OOM in MAKE_ARRAY\n"); exit(1); } - for (int i = n - 1; i >= 0; --i) { - vals[i] = pop_value(vm); /* take ownership */ - } - /* build array by copying values, then free originals */ - Value arr = make_array_from_values(vals, n); - for (int i = 0; i < n; ++i) { - free_value(vals[i]); - } - free(vals); - push_value(vm, arr); - break; + int n = inst.operand; + if (n < 0 || vm->sp + 1 < n) { + fprintf(stderr, "Runtime error: invalid element count for MAKE_ARRAY\n"); + exit(1); + } + /* pop n values into temp array preserving original order */ + Value *vals = (Value *)malloc(sizeof(Value) * n); + if (!vals) { + fprintf(stderr, "Runtime error: OOM in MAKE_ARRAY\n"); + exit(1); + } + for (int i = n - 1; i >= 0; --i) { + vals[i] = pop_value(vm); /* take ownership */ + } + /* build array by copying values, then free originals */ + Value arr = make_array_from_values(vals, n); + for (int i = 0; i < n; ++i) { + free_value(vals[i]); + } + free(vals); + push_value(vm, arr); + break; } diff --git a/src/vm/arrays/push.c b/src/vm/arrays/push.c index 1ffbf42..68555cd 100644 --- a/src/vm/arrays/push.c +++ b/src/vm/arrays/push.c @@ -8,7 +8,7 @@ */ /** -* @file arr_push.c + * @file arr_push.c * @brief Implements the OP_ARR_PUSH opcode for appending elements to arrays in the VM. * * This file handles the OP_ARR_PUSH instruction, which appends a value to the end of an array. @@ -33,18 +33,18 @@ */ case OP_PUSH: { - Value v = pop_value(vm); - Value arr = pop_value(vm); - if (arr.type != VAL_ARRAY) { - fprintf(stderr, "Runtime type error: ARR_PUSH expects array\n"); - exit(1); - } - int n = array_push(&arr, v); - if (n < 0) { - fprintf(stderr, "Runtime error: push failed (OOM?)\n"); - exit(1); - } - free_value(arr); - push_value(vm, make_int(n)); - break; + Value v = pop_value(vm); + Value arr = pop_value(vm); + if (arr.type != VAL_ARRAY) { + fprintf(stderr, "Runtime type error: ARR_PUSH expects array\n"); + exit(1); + } + int n = array_push(&arr, v); + if (n < 0) { + fprintf(stderr, "Runtime error: push failed (OOM?)\n"); + exit(1); + } + free_value(arr); + push_value(vm, make_int(n)); + break; } diff --git a/src/vm/arrays/remove.c b/src/vm/arrays/remove.c index f088a35..05e810f 100644 --- a/src/vm/arrays/remove.c +++ b/src/vm/arrays/remove.c @@ -8,7 +8,7 @@ */ /** -* @file arr_remove.c + * @file arr_remove.c * @brief Implements the OP_ARR_REMOVE opcode for removing elements from arrays in the VM. * * This file handles the OP_ARR_REMOVE instruction, which removes an element from an array @@ -34,19 +34,19 @@ */ case OP_REMOVE: { - Value idx = pop_value(vm); - Value arr = pop_value(vm); - if (arr.type != VAL_ARRAY || idx.type != VAL_INT) { - fprintf(stderr, "Runtime type error: ARR_REMOVE expects (array, int)\n"); - exit(1); - } - Value out; - if (!array_remove(&arr, (int)idx.i, &out)) { - fprintf(stderr, "Runtime error: remove index out of range\n"); - exit(1); - } - free_value(arr); - free_value(idx); - push_value(vm, out); - break; + Value idx = pop_value(vm); + Value arr = pop_value(vm); + if (arr.type != VAL_ARRAY || idx.type != VAL_INT) { + fprintf(stderr, "Runtime type error: ARR_REMOVE expects (array, int)\n"); + exit(1); + } + Value out; + if (!array_remove(&arr, (int)idx.i, &out)) { + fprintf(stderr, "Runtime error: remove index out of range\n"); + exit(1); + } + free_value(arr); + free_value(idx); + push_value(vm, out); + break; } diff --git a/src/vm/arrays/set.c b/src/vm/arrays/set.c index a230a10..4066741 100644 --- a/src/vm/arrays/set.c +++ b/src/vm/arrays/set.c @@ -8,7 +8,7 @@ */ /** -* @file arr_set.c + * @file arr_set.c * @brief Implements the OP_ARR_SET opcode for setting elements in arrays in the VM. * * This file handles the OP_ARR_SET instruction, which sets a value at a specified index in an array. @@ -33,21 +33,21 @@ */ case OP_SET: { - Value v = pop_value(vm); - Value idx = pop_value(vm); - Value arr = pop_value(vm); - if (arr.type != VAL_ARRAY || idx.type != VAL_INT) { - fprintf(stderr, "Runtime type error: ARR_SET expects (array, int, value)\n"); - exit(1); - } - if (!array_set(&arr, (int)idx.i, v)) { - fprintf(stderr, "Runtime error: set index out of range\n"); - exit(1); - } - free_value(arr); - free_value(idx); - /* v already owned by array; push copy for return value */ - push_value(vm, copy_value(&v)); - free_value(v); - break; + Value v = pop_value(vm); + Value idx = pop_value(vm); + Value arr = pop_value(vm); + if (arr.type != VAL_ARRAY || idx.type != VAL_INT) { + fprintf(stderr, "Runtime type error: ARR_SET expects (array, int, value)\n"); + exit(1); + } + if (!array_set(&arr, (int)idx.i, v)) { + fprintf(stderr, "Runtime error: set index out of range\n"); + exit(1); + } + free_value(arr); + free_value(idx); + /* v already owned by array; push copy for return value */ + push_value(vm, copy_value(&v)); + free_value(v); + break; } diff --git a/src/vm/arrays/slice.c b/src/vm/arrays/slice.c index 4e0d8f9..9afdcca 100644 --- a/src/vm/arrays/slice.c +++ b/src/vm/arrays/slice.c @@ -8,7 +8,7 @@ */ /** -* @file slice.c + * @file slice.c * @brief Implements the OP_SLICE opcode for array slicing in the VM. * * This file handles the OP_SLICE instruction, which creates a new array containing @@ -33,17 +33,17 @@ */ case OP_SLICE: { - Value end = pop_value(vm); - Value start = pop_value(vm); - Value arr = pop_value(vm); - if (arr.type != VAL_ARRAY || start.type != VAL_INT || end.type != VAL_INT) { - fprintf(stderr, "Runtime type error: SLICE expects (array, int, int)\n"); - exit(1); - } - Value out = array_slice(&arr, (int)start.i, (int)end.i); - free_value(arr); - free_value(start); - free_value(end); - push_value(vm, out); - break; + Value end = pop_value(vm); + Value start = pop_value(vm); + Value arr = pop_value(vm); + if (arr.type != VAL_ARRAY || start.type != VAL_INT || end.type != VAL_INT) { + fprintf(stderr, "Runtime type error: SLICE expects (array, int, int)\n"); + exit(1); + } + Value out = array_slice(&arr, (int)start.i, (int)end.i); + free_value(arr); + free_value(start); + free_value(end); + push_value(vm, out); + break; } diff --git a/src/vm/arrays/zip.c b/src/vm/arrays/zip.c index 75b7e31..142179f 100644 --- a/src/vm/arrays/zip.c +++ b/src/vm/arrays/zip.c @@ -8,7 +8,7 @@ */ /** -* @file zip.c + * @file zip.c * @brief Implements the OP_ZIP opcode for array zipping in the VM. * * This file handles the OP_ZIP instruction, which combines two arrays into @@ -24,7 +24,7 @@ * - Exits with error if arguments aren't arrays * * Example: - * // Bytecode: OP_ZIP + * // Bytecode: OP_ZIP * // Stack before: [[1,2], ['a','b']] * // Stack after: [[[1,'a'], [2,'b']]] * @@ -33,15 +33,15 @@ */ case OP_ZIP: { - Value b = pop_value(vm); - Value a = pop_value(vm); - if (a.type != VAL_ARRAY || b.type != VAL_ARRAY) { - fprintf(stderr, "Runtime type error: ZIP expects (array, array)\n"); - exit(1); - } - Value out = bi_zip(&a, &b); - free_value(a); - free_value(b); - push_value(vm, out); - break; + Value b = pop_value(vm); + Value a = pop_value(vm); + if (a.type != VAL_ARRAY || b.type != VAL_ARRAY) { + fprintf(stderr, "Runtime type error: ZIP expects (array, array)\n"); + exit(1); + } + Value out = bi_zip(&a, &b); + free_value(a); + free_value(b); + push_value(vm, out); + break; } diff --git a/src/vm/bitwise/band.c b/src/vm/bitwise/band.c index 0a21f6b..230225b 100644 --- a/src/vm/bitwise/band.c +++ b/src/vm/bitwise/band.c @@ -1,5 +1,5 @@ /** -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -15,13 +15,13 @@ * pushes: (uint32_t)(a & b) */ case OP_BAND: { - Value vb = pop_value(vm); - Value va = pop_value(vm); - uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u; - uint32_t b = (vb.type == VAL_INT) ? (uint32_t)vb.i : 0u; - uint32_t r = a & b; - free_value(vb); - free_value(va); - push_value(vm, make_int((int64_t)(uint64_t)r)); - break; + Value vb = pop_value(vm); + Value va = pop_value(vm); + uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u; + uint32_t b = (vb.type == VAL_INT) ? (uint32_t)vb.i : 0u; + uint32_t r = a & b; + free_value(vb); + free_value(va); + push_value(vm, make_int((int64_t)(uint64_t)r)); + break; } diff --git a/src/vm/bitwise/bnot.c b/src/vm/bitwise/bnot.c index 520449b..29e2a9a 100644 --- a/src/vm/bitwise/bnot.c +++ b/src/vm/bitwise/bnot.c @@ -1,5 +1,5 @@ /** -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -15,10 +15,10 @@ * pushes: (uint32_t)(~a) */ case OP_BNOT: { - Value va = pop_value(vm); - uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u; - uint32_t r = ~a; - free_value(va); - push_value(vm, make_int((int64_t)(uint64_t)r)); - break; + Value va = pop_value(vm); + uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u; + uint32_t r = ~a; + free_value(va); + push_value(vm, make_int((int64_t)(uint64_t)r)); + break; } diff --git a/src/vm/bitwise/bor.c b/src/vm/bitwise/bor.c index 68f53d6..3e7605b 100644 --- a/src/vm/bitwise/bor.c +++ b/src/vm/bitwise/bor.c @@ -1,5 +1,5 @@ /** -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -15,13 +15,13 @@ * pushes: (uint32_t)(a | b) */ case OP_BOR: { - Value vb = pop_value(vm); - Value va = pop_value(vm); - uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u; - uint32_t b = (vb.type == VAL_INT) ? (uint32_t)vb.i : 0u; - uint32_t r = a | b; - free_value(vb); - free_value(va); - push_value(vm, make_int((int64_t)(uint64_t)r)); - break; + Value vb = pop_value(vm); + Value va = pop_value(vm); + uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u; + uint32_t b = (vb.type == VAL_INT) ? (uint32_t)vb.i : 0u; + uint32_t r = a | b; + free_value(vb); + free_value(va); + push_value(vm, make_int((int64_t)(uint64_t)r)); + break; } diff --git a/src/vm/bitwise/bxor.c b/src/vm/bitwise/bxor.c index 10ceb7f..f886edc 100644 --- a/src/vm/bitwise/bxor.c +++ b/src/vm/bitwise/bxor.c @@ -1,5 +1,5 @@ /** -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -15,13 +15,13 @@ * pushes: (uint32_t)(a ^ b) */ case OP_BXOR: { - Value vb = pop_value(vm); - Value va = pop_value(vm); - uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u; - uint32_t b = (vb.type == VAL_INT) ? (uint32_t)vb.i : 0u; - uint32_t r = a ^ b; - free_value(vb); - free_value(va); - push_value(vm, make_int((int64_t)(uint64_t)r)); - break; + Value vb = pop_value(vm); + Value va = pop_value(vm); + uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u; + uint32_t b = (vb.type == VAL_INT) ? (uint32_t)vb.i : 0u; + uint32_t r = a ^ b; + free_value(vb); + free_value(va); + push_value(vm, make_int((int64_t)(uint64_t)r)); + break; } diff --git a/src/vm/bitwise/rol.c b/src/vm/bitwise/rol.c index 95a4315..46ff010 100644 --- a/src/vm/bitwise/rol.c +++ b/src/vm/bitwise/rol.c @@ -1,5 +1,5 @@ /** -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -15,14 +15,14 @@ * pushes: rotl32(a, s) */ case OP_ROTL: { - Value vs = pop_value(vm); - Value va = pop_value(vm); - uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u; - uint32_t s = (vs.type == VAL_INT) ? (uint32_t)vs.i : 0u; - s &= 31u; - uint32_t r = (s == 0u) ? a : ((a << s) | (a >> (32u - s))); - free_value(vs); - free_value(va); - push_value(vm, make_int((int64_t)(uint64_t)r)); - break; + Value vs = pop_value(vm); + Value va = pop_value(vm); + uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u; + uint32_t s = (vs.type == VAL_INT) ? (uint32_t)vs.i : 0u; + s &= 31u; + uint32_t r = (s == 0u) ? a : ((a << s) | (a >> (32u - s))); + free_value(vs); + free_value(va); + push_value(vm, make_int((int64_t)(uint64_t)r)); + break; } diff --git a/src/vm/bitwise/ror.c b/src/vm/bitwise/ror.c index 7834d04..26274b2 100644 --- a/src/vm/bitwise/ror.c +++ b/src/vm/bitwise/ror.c @@ -1,5 +1,5 @@ /** -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -15,14 +15,14 @@ * pushes: rotr32(a, s) */ case OP_ROTR: { - Value vs = pop_value(vm); - Value va = pop_value(vm); - uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u; - uint32_t s = (vs.type == VAL_INT) ? (uint32_t)vs.i : 0u; - s &= 31u; - uint32_t r = (s == 0u) ? a : ((a >> s) | (a << (32u - s))); - free_value(vs); - free_value(va); - push_value(vm, make_int((int64_t)(uint64_t)r)); - break; + Value vs = pop_value(vm); + Value va = pop_value(vm); + uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u; + uint32_t s = (vs.type == VAL_INT) ? (uint32_t)vs.i : 0u; + s &= 31u; + uint32_t r = (s == 0u) ? a : ((a >> s) | (a << (32u - s))); + free_value(vs); + free_value(va); + push_value(vm, make_int((int64_t)(uint64_t)r)); + break; } diff --git a/src/vm/bitwise/shl.c b/src/vm/bitwise/shl.c index 2e660ab..7ad1053 100644 --- a/src/vm/bitwise/shl.c +++ b/src/vm/bitwise/shl.c @@ -1,5 +1,5 @@ /** -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -15,14 +15,14 @@ * pushes: (uint32_t)(a << (s&31)) */ case OP_SHL: { - Value vs = pop_value(vm); - Value va = pop_value(vm); - uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u; - uint32_t s = (vs.type == VAL_INT) ? (uint32_t)vs.i : 0u; - s &= 31u; - uint32_t r = (s == 0u) ? a : (a << s); - free_value(vs); - free_value(va); - push_value(vm, make_int((int64_t)(uint64_t)r)); - break; + Value vs = pop_value(vm); + Value va = pop_value(vm); + uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u; + uint32_t s = (vs.type == VAL_INT) ? (uint32_t)vs.i : 0u; + s &= 31u; + uint32_t r = (s == 0u) ? a : (a << s); + free_value(vs); + free_value(va); + push_value(vm, make_int((int64_t)(uint64_t)r)); + break; } diff --git a/src/vm/bitwise/shr.c b/src/vm/bitwise/shr.c index ec776db..7a9bfc2 100644 --- a/src/vm/bitwise/shr.c +++ b/src/vm/bitwise/shr.c @@ -1,5 +1,5 @@ /** -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -15,14 +15,14 @@ * pushes: (uint32_t)(a >> (s&31)) using logical shift */ case OP_SHR: { - Value vs = pop_value(vm); - Value va = pop_value(vm); - uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u; - uint32_t s = (vs.type == VAL_INT) ? (uint32_t)vs.i : 0u; - s &= 31u; - uint32_t r = (s == 0u) ? a : (a >> s); - free_value(vs); - free_value(va); - push_value(vm, make_int((int64_t)(uint64_t)r)); - break; + Value vs = pop_value(vm); + Value va = pop_value(vm); + uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u; + uint32_t s = (vs.type == VAL_INT) ? (uint32_t)vs.i : 0u; + s &= 31u; + uint32_t r = (s == 0u) ? a : (a >> s); + free_value(vs); + free_value(va); + push_value(vm, make_int((int64_t)(uint64_t)r)); + break; } diff --git a/src/vm/cast.c b/src/vm/cast.c index 9bc103f..c075e9a 100644 --- a/src/vm/cast.c +++ b/src/vm/cast.c @@ -1,5 +1,5 @@ /** -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -17,73 +17,77 @@ */ case OP_CAST: { - /* pop type then value (args pushed in this order: value, typeName) */ - Value t = pop_value(vm); - Value v = pop_value(vm); + /* pop type then value (args pushed in this order: value, typeName) */ + Value t = pop_value(vm); + Value v = pop_value(vm); - const char *tn = (t.type == VAL_STRING && t.s) ? t.s : NULL; - Value out = make_nil(); + const char *tn = (t.type == VAL_STRING && t.s) ? t.s : NULL; + Value out = make_nil(); - /* Normalize target name to lowercase into a small buffer */ - char target[32]; - int k = 0; - if (tn) { - const char *p = tn; - while (*p && k < (int)sizeof(target) - 1) { - char c = *p++; - if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a'); - target[k++] = c; - } + /* Normalize target name to lowercase into a small buffer */ + char target[32]; + int k = 0; + if (tn) { + const char *p = tn; + while (*p && k < (int)sizeof(target) - 1) { + char c = *p++; + if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a'); + target[k++] = c; } - target[k] = '\0'; + } + target[k] = '\0'; - if (!tn) { - out = make_nil(); - } else if (strcmp(target, "number") == 0) { - if (v.type == VAL_INT) { - out = make_int(v.i); - } 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); - while (endp && (*endp == ' ' || *endp == '\t' || *endp == '\r' || *endp == '\n')) endp++; - if (endp && *endp != '\0') out = make_int(0); - else out = make_int((int64_t)parsed); - } else { - out = make_int(0); - } - } else if (strcmp(target, "string") == 0) { - char *s = value_to_string_alloc(&v); - out = make_string(s ? s : ""); - if (s) free(s); - } else if (strcmp(target, "array") == 0) { - if (v.type == VAL_ARRAY) { - out = copy_value(&v); - } else { - Value tmp = deep_copy_value(&v); - out = make_array_from_values(&tmp, 1); - free_value(tmp); - } - } else if (strcmp(target, "map") == 0) { - if (v.type == VAL_MAP) { - out = copy_value(&v); - } else { - out = make_map_empty(); - } - } else if (strcmp(target, "nil") == 0) { - out = make_nil(); - } else if (strcmp(target, "function") == 0) { - out = (v.type == VAL_FUNCTION) ? copy_value(&v) : make_nil(); - } else if (strcmp(target, "boolean") == 0) { - out = make_int(value_is_truthy(&v) ? 1 : 0); + if (!tn) { + out = make_nil(); + } else if (strcmp(target, "number") == 0) { + if (v.type == VAL_INT) { + out = make_int(v.i); + } 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); + while (endp && (*endp == ' ' || *endp == '\t' || *endp == '\r' || *endp == '\n')) + endp++; + if (endp && *endp != '\0') + out = make_int(0); + else + out = make_int((int64_t)parsed); } else { - out = make_nil(); + out = make_int(0); } + } else if (strcmp(target, "string") == 0) { + char *s = value_to_string_alloc(&v); + out = make_string(s ? s : ""); + if (s) free(s); + } else if (strcmp(target, "array") == 0) { + if (v.type == VAL_ARRAY) { + out = copy_value(&v); + } else { + Value tmp = deep_copy_value(&v); + out = make_array_from_values(&tmp, 1); + free_value(tmp); + } + } else if (strcmp(target, "map") == 0) { + if (v.type == VAL_MAP) { + out = copy_value(&v); + } else { + out = make_map_empty(); + } + } else if (strcmp(target, "nil") == 0) { + out = make_nil(); + } else if (strcmp(target, "function") == 0) { + out = (v.type == VAL_FUNCTION) ? copy_value(&v) : make_nil(); + } else if (strcmp(target, "boolean") == 0) { + out = make_int(value_is_truthy(&v) ? 1 : 0); + } else { + out = make_nil(); + } - free_value(t); - free_value(v); - push_value(vm, out); - break; + free_value(t); + free_value(v); + push_value(vm, out); + break; } diff --git a/src/vm/core/call.c b/src/vm/core/call.c index be925a9..aec2279 100644 --- a/src/vm/core/call.c +++ b/src/vm/core/call.c @@ -8,7 +8,7 @@ */ /** -* @file call.c + * @file call.c * @brief Implements the OP_CALL opcode for function calls in the VM. * * This file handles the OP_CALL instruction, which calls a function with arguments. @@ -28,27 +28,27 @@ */ case OP_CALL: { - int argc = inst.operand; - if (argc < 0) argc = 0; - /* collect args in reverse (preserve order) */ - Value *args = NULL; - if (argc > 0) { - args = (Value*)malloc(sizeof(Value) * argc); - /* pop args into array in reverse */ - for (int i = argc - 1; i >= 0; --i) { - args[i] = pop_value(vm); - } + int argc = inst.operand; + if (argc < 0) argc = 0; + /* collect args in reverse (preserve order) */ + Value *args = NULL; + if (argc > 0) { + args = (Value *)malloc(sizeof(Value) * argc); + /* pop args into array in reverse */ + for (int i = argc - 1; i >= 0; --i) { + args[i] = pop_value(vm); } - /* pop function value */ - Value fnv = pop_value(vm); - if (fnv.type != VAL_FUNCTION) { - fprintf(stderr, "Runtime type error: CALL expects function\n"); - exit(1); - } - /* push new frame and transfer args */ - vm_push_frame(vm, fnv.fn, argc, args); - /* free args array (locals moved), free fnv (no-op for function) */ - free(args); - /* note: fnv contains a pointer to the Bytecode, don't free here */ - break; + } + /* pop function value */ + Value fnv = pop_value(vm); + if (fnv.type != VAL_FUNCTION) { + fprintf(stderr, "Runtime type error: CALL expects function\n"); + exit(1); + } + /* push new frame and transfer args */ + vm_push_frame(vm, fnv.fn, argc, args); + /* free args array (locals moved), free fnv (no-op for function) */ + free(args); + /* note: fnv contains a pointer to the Bytecode, don't free here */ + break; } diff --git a/src/vm/core/dup.c b/src/vm/core/dup.c index 62a55af..55dd134 100644 --- a/src/vm/core/dup.c +++ b/src/vm/core/dup.c @@ -8,7 +8,7 @@ */ /** -* @file dup.c + * @file dup.c * @brief Implements the OP_DUP opcode for duplicating the top stack value in the VM. * * This file handles the OP_DUP instruction, which duplicates the top value on the stack. @@ -25,17 +25,17 @@ * // Bytecode: OP_DUP * // Stack before: [42] * // Stack after: [42, 42] -* + * * @author Johannes Findeisen * @date 2025-10-16 */ case OP_DUP: { - if (vm->sp < 0) { - fprintf(stderr, "Runtime error: stack underflow for DUP\n"); - exit(1); - } - Value top = vm->stack[vm->sp]; - push_value(vm, copy_value(&top)); - break; + if (vm->sp < 0) { + fprintf(stderr, "Runtime error: stack underflow for DUP\n"); + exit(1); + } + Value top = vm->stack[vm->sp]; + push_value(vm, copy_value(&top)); + break; } diff --git a/src/vm/core/exit.c b/src/vm/core/exit.c index 1c2509e..7abf45f 100644 --- a/src/vm/core/exit.c +++ b/src/vm/core/exit.c @@ -10,7 +10,7 @@ */ /** -* @file exit.c + * @file exit.c * @brief Implements the OP_EXIT opcode to terminate the script with an exit code. * * Behavior: @@ -20,22 +20,22 @@ */ case OP_EXIT: { - int code = 0; - if (vm->sp >= 0) { - Value v = pop_value(vm); - if (v.type == VAL_INT) { - code = (int)v.i; - } else if (v.type == VAL_STRING) { - /* best-effort parse number from string */ - code = (int)strtoll(v.s, NULL, 10); - } else if (v.type == VAL_NIL) { - code = 0; - } else { - /* unsupported type for exit; default to 0 */ - code = 0; - } - free_value(v); + int code = 0; + if (vm->sp >= 0) { + Value v = pop_value(vm); + if (v.type == VAL_INT) { + code = (int)v.i; + } else if (v.type == VAL_STRING) { + /* best-effort parse number from string */ + code = (int)strtoll(v.s, NULL, 10); + } else if (v.type == VAL_NIL) { + code = 0; + } else { + /* unsupported type for exit; default to 0 */ + code = 0; } - vm->exit_code = code; - return; + free_value(v); + } + vm->exit_code = code; + return; } diff --git a/src/vm/core/halt.c b/src/vm/core/halt.c index 345e3c0..0c37ad1 100644 --- a/src/vm/core/halt.c +++ b/src/vm/core/halt.c @@ -8,7 +8,7 @@ */ /** -* @file halt.c + * @file halt.c * @brief Implements the OP_HALT opcode for stopping VM execution. * * This file handles the OP_HALT instruction, which stops the execution of the VM. @@ -27,4 +27,4 @@ */ case OP_HALT: - return; +return; diff --git a/src/vm/core/jump.c b/src/vm/core/jump.c index 2f6c4d7..67caab7 100644 --- a/src/vm/core/jump.c +++ b/src/vm/core/jump.c @@ -8,7 +8,7 @@ */ /** -* @file jump.c + * @file jump.c * @brief Implements the OP_JUMP opcode for unconditional jumps in the VM. * * This file handles the OP_JUMP instruction, which performs an unconditional @@ -28,6 +28,6 @@ */ case OP_JUMP: { - f->ip = inst.operand; - break; + f->ip = inst.operand; + break; } diff --git a/src/vm/core/jump_if_false.c b/src/vm/core/jump_if_false.c index fa52943..78c1534 100644 --- a/src/vm/core/jump_if_false.c +++ b/src/vm/core/jump_if_false.c @@ -8,7 +8,7 @@ */ /** -* @file jump_if_false.c + * @file jump_if_false.c * @brief Implements the OP_JUMP_IF_FALSE opcode for conditional jumps in the VM. * * This file handles the OP_JUMP_IF_FALSE instruction, which jumps if the top @@ -29,11 +29,11 @@ */ case OP_JUMP_IF_FALSE: { - Value cond = pop_value(vm); - int truthy = value_is_truthy(&cond); - free_value(cond); - if (!truthy) { - f->ip = inst.operand; - } - break; + Value cond = pop_value(vm); + int truthy = value_is_truthy(&cond); + free_value(cond); + if (!truthy) { + f->ip = inst.operand; + } + break; } diff --git a/src/vm/core/load_const.c b/src/vm/core/load_const.c index 522ea44..61d2d41 100644 --- a/src/vm/core/load_const.c +++ b/src/vm/core/load_const.c @@ -8,7 +8,7 @@ */ /** -* @file load_const.c + * @file load_const.c * @brief Implements the OP_LOAD_CONST opcode for loading constants in the VM. * * This file handles the OP_LOAD_CONST instruction, which loads a constant value @@ -26,12 +26,12 @@ */ case OP_LOAD_CONST: { - int idx = inst.operand; - if (idx < 0 || idx >= f->fn->const_count) { - fprintf(stderr, "Runtime error: constant index out of range\n"); - exit(1); - } - Value c = copy_value(&f->fn->constants[idx]); - push_value(vm, c); - break; + int idx = inst.operand; + if (idx < 0 || idx >= f->fn->const_count) { + fprintf(stderr, "Runtime error: constant index out of range\n"); + exit(1); + } + Value c = copy_value(&f->fn->constants[idx]); + push_value(vm, c); + break; } diff --git a/src/vm/core/load_global.c b/src/vm/core/load_global.c index 4e11010..3a0f08c 100644 --- a/src/vm/core/load_global.c +++ b/src/vm/core/load_global.c @@ -8,7 +8,7 @@ */ /** -* @file load_global.c + * @file load_global.c * @brief Implements the OP_LOAD_GLOBAL opcode for loading global variables in the VM. * * This file handles the OP_LOAD_GLOBAL instruction, which loads a global variable @@ -31,14 +31,14 @@ */ case OP_LOAD_GLOBAL: { - int idx = inst.operand; - if (idx < 0 || idx >= MAX_GLOBALS) { - fprintf(stderr, "Runtime error: global index out of range\n"); - exit(1); - } + int idx = inst.operand; + if (idx < 0 || idx >= MAX_GLOBALS) { + fprintf(stderr, "Runtime error: global index out of range\n"); + exit(1); + } #ifdef FUN_DEBUG - fprintf(stderr, "DEBUG LOAD_GLOBAL[%d]: type=%d\n", idx, vm->globals[idx].type); + fprintf(stderr, "DEBUG LOAD_GLOBAL[%d]: type=%d\n", idx, vm->globals[idx].type); #endif - push_value(vm, copy_value(&vm->globals[idx])); - break; + push_value(vm, copy_value(&vm->globals[idx])); + break; } diff --git a/src/vm/core/load_local.c b/src/vm/core/load_local.c index b6524e2..2a16f1b 100644 --- a/src/vm/core/load_local.c +++ b/src/vm/core/load_local.c @@ -8,7 +8,7 @@ */ /** -* @file load_local.c + * @file load_local.c * @brief Implements the OP_LOAD_LOCAL opcode for loading local variables in the VM. * * This file handles the OP_LOAD_LOCAL instruction, which loads a local variable @@ -31,12 +31,12 @@ */ case OP_LOAD_LOCAL: { - int slot = inst.operand; - if (slot < 0 || slot >= MAX_FRAME_LOCALS) { - fprintf(stderr, "Runtime error: local slot out of range\n"); - exit(1); - } - Value val = copy_value(&f->locals[slot]); - push_value(vm, val); - break; + int slot = inst.operand; + if (slot < 0 || slot >= MAX_FRAME_LOCALS) { + fprintf(stderr, "Runtime error: local slot out of range\n"); + exit(1); + } + Value val = copy_value(&f->locals[slot]); + push_value(vm, val); + break; } diff --git a/src/vm/core/nop.c b/src/vm/core/nop.c index 81f0716..37779a9 100644 --- a/src/vm/core/nop.c +++ b/src/vm/core/nop.c @@ -8,7 +8,7 @@ */ /** -* @file nop.c + * @file nop.c * @brief Implements the OP_NOP opcode for no operation in the VM. * * This file handles the OP_NOP instruction, which performs no operation. @@ -27,4 +27,4 @@ */ case OP_NOP: - break; +break; diff --git a/src/vm/core/pop.c b/src/vm/core/pop.c index c5a21ca..de20120 100644 --- a/src/vm/core/pop.c +++ b/src/vm/core/pop.c @@ -8,7 +8,7 @@ */ /** -* @file pop.c + * @file pop.c * @brief Implements the OP_POP opcode for removing the top stack value in the VM. * * This file handles the OP_POP instruction, which removes the top value from the stack. @@ -29,11 +29,11 @@ */ case OP_POP: { - if (vm->sp < 0) { - fprintf(stderr, "Runtime error: stack underflow for POP\n"); - exit(1); - } - Value v = pop_value(vm); - free_value(v); - break; + if (vm->sp < 0) { + fprintf(stderr, "Runtime error: stack underflow for POP\n"); + exit(1); + } + Value v = pop_value(vm); + free_value(v); + break; } diff --git a/src/vm/core/return.c b/src/vm/core/return.c index c6c7a0e..9ff29ed 100644 --- a/src/vm/core/return.c +++ b/src/vm/core/return.c @@ -8,7 +8,7 @@ */ /** -* @file return.c + * @file return.c * @brief Implements the OP_RETURN opcode for returning from a function in the VM. * * This file handles the OP_RETURN instruction, which returns from the current function @@ -32,10 +32,12 @@ */ case OP_RETURN: { - Value retv; - if (vm->sp >= 0) retv = pop_value(vm); - else retv = make_nil(); - vm_pop_frame(vm); - push_value(vm, retv); - break; + Value retv; + if (vm->sp >= 0) + retv = pop_value(vm); + else + retv = make_nil(); + vm_pop_frame(vm); + push_value(vm, retv); + break; } diff --git a/src/vm/core/store_global.c b/src/vm/core/store_global.c index bcb4817..fe46065 100644 --- a/src/vm/core/store_global.c +++ b/src/vm/core/store_global.c @@ -8,7 +8,7 @@ */ /** -* @file store_global.c + * @file store_global.c * @brief Implements the OP_STORE_GLOBAL opcode for storing global variables in the VM. * * This file handles the OP_STORE_GLOBAL instruction, which stores a value into a global variable @@ -31,16 +31,16 @@ */ case OP_STORE_GLOBAL: { - int idx = inst.operand; - if (idx < 0 || idx >= MAX_GLOBALS) { - fprintf(stderr, "Runtime error: global index out of range\n"); - exit(1); - } - Value v = pop_value(vm); + int idx = inst.operand; + if (idx < 0 || idx >= MAX_GLOBALS) { + fprintf(stderr, "Runtime error: global index out of range\n"); + exit(1); + } + Value v = pop_value(vm); #ifdef FUN_DEBUG - fprintf(stderr, "DEBUG STORE_GLOBAL[%d]: new.type=%d\n", idx, v.type); + fprintf(stderr, "DEBUG STORE_GLOBAL[%d]: new.type=%d\n", idx, v.type); #endif - free_value(vm->globals[idx]); - vm->globals[idx] = v; - break; + free_value(vm->globals[idx]); + vm->globals[idx] = v; + break; } diff --git a/src/vm/core/store_local.c b/src/vm/core/store_local.c index f8fba99..0243b05 100644 --- a/src/vm/core/store_local.c +++ b/src/vm/core/store_local.c @@ -8,7 +8,7 @@ */ /** -* @file store_local.c + * @file store_local.c * @brief Implements the OP_STORE_LOCAL opcode for storing local variables in the VM. * * This file handles the OP_STORE_LOCAL instruction, which stores a value into a local variable @@ -31,14 +31,14 @@ */ case OP_STORE_LOCAL: { - int slot = inst.operand; - if (slot < 0 || slot >= MAX_FRAME_LOCALS) { - fprintf(stderr, "Runtime error: local slot out of range\n"); - exit(1); - } - Value v = pop_value(vm); - /* free previous local then move v into it */ - free_value(f->locals[slot]); - f->locals[slot] = v; - break; + int slot = inst.operand; + if (slot < 0 || slot >= MAX_FRAME_LOCALS) { + fprintf(stderr, "Runtime error: local slot out of range\n"); + exit(1); + } + Value v = pop_value(vm); + /* free previous local then move v into it */ + free_value(f->locals[slot]); + f->locals[slot] = v; + break; } diff --git a/src/vm/core/swap.c b/src/vm/core/swap.c index 91699fc..78e98f6 100644 --- a/src/vm/core/swap.c +++ b/src/vm/core/swap.c @@ -8,7 +8,7 @@ */ /** -* @file swap.c + * @file swap.c * @brief Implements the OP_SWAP opcode for stack manipulation in the VM. * * This file handles the OP_SWAP instruction, which swaps the top two values @@ -26,13 +26,13 @@ */ case OP_SWAP: { - if (vm->sp < 1) { - fprintf(stderr, "Runtime error: stack underflow for SWAP\n"); - exit(1); - } - Value a = vm->stack[vm->sp]; - Value b = vm->stack[vm->sp - 1]; - vm->stack[vm->sp] = b; - vm->stack[vm->sp - 1] = a; - break; + if (vm->sp < 1) { + fprintf(stderr, "Runtime error: stack underflow for SWAP\n"); + exit(1); + } + Value a = vm->stack[vm->sp]; + Value b = vm->stack[vm->sp - 1]; + vm->stack[vm->sp] = b; + vm->stack[vm->sp - 1] = a; + break; } diff --git a/src/vm/core/throw.c b/src/vm/core/throw.c index f734bfa..c4da59c 100644 --- a/src/vm/core/throw.c +++ b/src/vm/core/throw.c @@ -8,26 +8,26 @@ */ case OP_THROW: { - Value err = pop_value(vm); - /* if there is a handler in this frame, jump to it and push err for catch */ - if (f->try_sp >= 0) { - int try_idx = f->try_stack[f->try_sp--]; - int target = f->fn->instructions[try_idx].operand; - /* push error for catch block */ - push_value(vm, err); /* transfer ownership to stack */ - f->ip = target; - break; - } - /* Unhandled: print error and terminate */ - char *s = value_to_string_alloc(&err); - if (s) { - fprintf(stdout, "%s\n", s); - free(s); - } else { - fprintf(stdout, "\n"); - } - free_value(err); - /* clear frames to stop execution */ - vm->fp = -1; + Value err = pop_value(vm); + /* if there is a handler in this frame, jump to it and push err for catch */ + if (f->try_sp >= 0) { + int try_idx = f->try_stack[f->try_sp--]; + int target = f->fn->instructions[try_idx].operand; + /* push error for catch block */ + push_value(vm, err); /* transfer ownership to stack */ + f->ip = target; break; + } + /* Unhandled: print error and terminate */ + char *s = value_to_string_alloc(&err); + if (s) { + fprintf(stdout, "%s\n", s); + free(s); + } else { + fprintf(stdout, "\n"); + } + free_value(err); + /* clear frames to stop execution */ + vm->fp = -1; + break; } diff --git a/src/vm/core/try_pop.c b/src/vm/core/try_pop.c index d30dfd2..3ac70ec 100644 --- a/src/vm/core/try_pop.c +++ b/src/vm/core/try_pop.c @@ -8,6 +8,6 @@ */ case OP_TRY_POP: { - if (f->try_sp >= 0) f->try_sp--; - break; + if (f->try_sp >= 0) f->try_sp--; + break; } diff --git a/src/vm/core/try_push.c b/src/vm/core/try_push.c index a224383..f8a62f6 100644 --- a/src/vm/core/try_push.c +++ b/src/vm/core/try_push.c @@ -8,11 +8,11 @@ */ case OP_TRY_PUSH: { - /* push index of this TRY instruction; handler ip is in its operand (may be patched later) */ - if (f->try_sp >= (int)(sizeof(f->try_stack)/sizeof(f->try_stack[0])) - 1) { - fprintf(stderr, "Runtime error: try depth exceeded\n"); - exit(1); - } - f->try_stack[++f->try_sp] = f->ip - 1; /* index of TRY_PUSH instruction */ - break; + /* push index of this TRY instruction; handler ip is in its operand (may be patched later) */ + if (f->try_sp >= (int)(sizeof(f->try_stack) / sizeof(f->try_stack[0])) - 1) { + fprintf(stderr, "Runtime error: try depth exceeded\n"); + exit(1); + } + f->try_stack[++f->try_sp] = f->ip - 1; /* index of TRY_PUSH instruction */ + break; } diff --git a/src/vm/cpp/add.cpp b/src/vm/cpp/add.cpp index 4a49a5a..49b503a 100644 --- a/src/vm/cpp/add.cpp +++ b/src/vm/cpp/add.cpp @@ -1,5 +1,5 @@ /* -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2026 Johannes Findeisen @@ -21,8 +21,8 @@ extern "C" { } extern "C" int fun_op_cpp_add(VM *vm) { - int64_t a = vm_pop_i64(vm); - int64_t b = vm_pop_i64(vm); - vm_push_i64(vm, a + b); - return 0; // success + int64_t a = vm_pop_i64(vm); + int64_t b = vm_pop_i64(vm); + vm_push_i64(vm, a + b); + return 0; // success } diff --git a/src/vm/curl/download.c b/src/vm/curl/download.c index 51d9db6..8c93f0b 100644 --- a/src/vm/curl/download.c +++ b/src/vm/curl/download.c @@ -3,45 +3,53 @@ */ case OP_CURL_DOWNLOAD: { #ifdef FUN_WITH_CURL - Value vpath = pop_value(vm); - Value vurl = pop_value(vm); - char *url = value_to_string_alloc(&vurl); - char *path = value_to_string_alloc(&vpath); - free_value(vurl); - free_value(vpath); - if (!url || !path) { - if (url) free(url); - if (path) free(path); - push_value(vm, make_int(0)); - break; - } - FILE *fp = fopen(path, "wb"); - if (!fp) { - free(url); free(path); - push_value(vm, make_int(0)); - break; - } - CURL *h = curl_easy_init(); - if (!h) { - fclose(fp); - free(url); free(path); - push_value(vm, make_int(0)); - break; - } - curl_easy_setopt(h, CURLOPT_URL, url); - curl_easy_setopt(h, CURLOPT_FOLLOWLOCATION, 1L); - curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, fun_curl_file_write_cb); - curl_easy_setopt(h, CURLOPT_WRITEDATA, fp); - CURLcode rc = curl_easy_perform(h); - curl_easy_cleanup(h); - fclose(fp); - free(url); free(path); - if (rc != CURLE_OK) { push_value(vm, make_int(0)); break; } - push_value(vm, make_int(1)); -#else - Value a = pop_value(vm); free_value(a); - Value b = pop_value(vm); free_value(b); + Value vpath = pop_value(vm); + Value vurl = pop_value(vm); + char *url = value_to_string_alloc(&vurl); + char *path = value_to_string_alloc(&vpath); + free_value(vurl); + free_value(vpath); + if (!url || !path) { + if (url) free(url); + if (path) free(path); push_value(vm, make_int(0)); -#endif break; + } + FILE *fp = fopen(path, "wb"); + if (!fp) { + free(url); + free(path); + push_value(vm, make_int(0)); + break; + } + CURL *h = curl_easy_init(); + if (!h) { + fclose(fp); + free(url); + free(path); + push_value(vm, make_int(0)); + break; + } + curl_easy_setopt(h, CURLOPT_URL, url); + curl_easy_setopt(h, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, fun_curl_file_write_cb); + curl_easy_setopt(h, CURLOPT_WRITEDATA, fp); + CURLcode rc = curl_easy_perform(h); + curl_easy_cleanup(h); + fclose(fp); + free(url); + free(path); + if (rc != CURLE_OK) { + push_value(vm, make_int(0)); + break; + } + push_value(vm, make_int(1)); +#else + Value a = pop_value(vm); + free_value(a); + Value b = pop_value(vm); + free_value(b); + push_value(vm, make_int(0)); +#endif + break; } diff --git a/src/vm/curl/get.c b/src/vm/curl/get.c index 7d15b17..729ef73 100644 --- a/src/vm/curl/get.c +++ b/src/vm/curl/get.c @@ -3,31 +3,39 @@ */ case OP_CURL_GET: { #ifdef FUN_WITH_CURL - Value vurl = pop_value(vm); - char *url = value_to_string_alloc(&vurl); - free_value(vurl); - if (!url) { push_value(vm, make_string("")); break; } - FunCurlBuf buf = { NULL, 0 }; - CURL *h = curl_easy_init(); - if (!h) { free(url); push_value(vm, make_string("")); break; } - curl_easy_setopt(h, CURLOPT_URL, url); - curl_easy_setopt(h, CURLOPT_FOLLOWLOCATION, 1L); - curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, fun_curl_write_cb); - curl_easy_setopt(h, CURLOPT_WRITEDATA, &buf); - CURLcode rc = curl_easy_perform(h); - curl_easy_cleanup(h); - free(url); - if (rc != CURLE_OK) { - if (buf.d) free(buf.d); - push_value(vm, make_string("")); - break; - } - Value s = make_string(buf.d ? buf.d : ""); - if (buf.d) free(buf.d); - push_value(vm, s); -#else - Value v = pop_value(vm); free_value(v); + Value vurl = pop_value(vm); + char *url = value_to_string_alloc(&vurl); + free_value(vurl); + if (!url) { push_value(vm, make_string("")); -#endif break; + } + FunCurlBuf buf = {NULL, 0}; + CURL *h = curl_easy_init(); + if (!h) { + free(url); + push_value(vm, make_string("")); + break; + } + curl_easy_setopt(h, CURLOPT_URL, url); + curl_easy_setopt(h, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, fun_curl_write_cb); + curl_easy_setopt(h, CURLOPT_WRITEDATA, &buf); + CURLcode rc = curl_easy_perform(h); + curl_easy_cleanup(h); + free(url); + if (rc != CURLE_OK) { + if (buf.d) free(buf.d); + push_value(vm, make_string("")); + break; + } + Value s = make_string(buf.d ? buf.d : ""); + if (buf.d) free(buf.d); + push_value(vm, s); +#else + Value v = pop_value(vm); + free_value(v); + push_value(vm, make_string("")); +#endif + break; } diff --git a/src/vm/curl/post.c b/src/vm/curl/post.c index 08f0b15..b8e1526 100644 --- a/src/vm/curl/post.c +++ b/src/vm/curl/post.c @@ -3,39 +3,50 @@ */ case OP_CURL_POST: { #ifdef FUN_WITH_CURL - Value vbody = pop_value(vm); - Value vurl = pop_value(vm); - char *url = value_to_string_alloc(&vurl); - char *body = value_to_string_alloc(&vbody); - free_value(vurl); - free_value(vbody); - if (!url) { if (body) free(body); push_value(vm, make_string("")); break; } - if (!body) body = strdup(""); - FunCurlBuf buf = { NULL, 0 }; - CURL *h = curl_easy_init(); - if (!h) { free(url); free(body); push_value(vm, make_string("")); break; } - curl_easy_setopt(h, CURLOPT_URL, url); - curl_easy_setopt(h, CURLOPT_FOLLOWLOCATION, 1L); - curl_easy_setopt(h, CURLOPT_POST, 1L); - curl_easy_setopt(h, CURLOPT_POSTFIELDS, body); - curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, fun_curl_write_cb); - curl_easy_setopt(h, CURLOPT_WRITEDATA, &buf); - CURLcode rc = curl_easy_perform(h); - curl_easy_cleanup(h); + Value vbody = pop_value(vm); + Value vurl = pop_value(vm); + char *url = value_to_string_alloc(&vurl); + char *body = value_to_string_alloc(&vbody); + free_value(vurl); + free_value(vbody); + if (!url) { + if (body) free(body); + push_value(vm, make_string("")); + break; + } + if (!body) body = strdup(""); + FunCurlBuf buf = {NULL, 0}; + CURL *h = curl_easy_init(); + if (!h) { free(url); free(body); - if (rc != CURLE_OK) { - if (buf.d) free(buf.d); - push_value(vm, make_string("")); - break; - } - Value s = make_string(buf.d ? buf.d : ""); - if (buf.d) free(buf.d); - push_value(vm, s); -#else - Value a = pop_value(vm); free_value(a); - Value b = pop_value(vm); free_value(b); push_value(vm, make_string("")); -#endif break; + } + curl_easy_setopt(h, CURLOPT_URL, url); + curl_easy_setopt(h, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(h, CURLOPT_POST, 1L); + curl_easy_setopt(h, CURLOPT_POSTFIELDS, body); + curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, fun_curl_write_cb); + curl_easy_setopt(h, CURLOPT_WRITEDATA, &buf); + CURLcode rc = curl_easy_perform(h); + curl_easy_cleanup(h); + free(url); + free(body); + if (rc != CURLE_OK) { + if (buf.d) free(buf.d); + push_value(vm, make_string("")); + break; + } + Value s = make_string(buf.d ? buf.d : ""); + if (buf.d) free(buf.d); + push_value(vm, s); +#else + Value a = pop_value(vm); + free_value(a); + Value b = pop_value(vm); + free_value(b); + push_value(vm, make_string("")); +#endif + break; } diff --git a/src/vm/echo.c b/src/vm/echo.c index b29d8e6..0d40dcc 100644 --- a/src/vm/echo.c +++ b/src/vm/echo.c @@ -1,5 +1,5 @@ /* -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -7,25 +7,25 @@ * https://opensource.org/license/apache-2-0 */ - /** +/** * Implements OP_ECHO: print top-of-stack value without trailing newline. * Now stores the value into the VM's output buffer and marks it as partial, * so the CLI can render echo output together with following print output. */ case OP_ECHO: { - Value v = pop_value(vm); - Value snap = deep_copy_value(&v); - free_value(v); - if (vm->output_count < OUTPUT_SIZE) { - int idx = vm->output_count; - vm->output[idx] = snap; - vm->output_is_partial[idx] = 1; // ECHO does not end the line - vm->output_count++; - } else { - free_value(snap); - fprintf(stderr, "Runtime error: output buffer overflow\n"); - exit(1); - } - break; + Value v = pop_value(vm); + Value snap = deep_copy_value(&v); + free_value(v); + if (vm->output_count < OUTPUT_SIZE) { + int idx = vm->output_count; + vm->output[idx] = snap; + vm->output_is_partial[idx] = 1; // ECHO does not end the line + vm->output_count++; + } else { + free_value(snap); + fprintf(stderr, "Runtime error: output buffer overflow\n"); + exit(1); + } + break; } diff --git a/src/vm/ini/free.c b/src/vm/ini/free.c index 5271bc6..a385a38 100644 --- a/src/vm/ini/free.c +++ b/src/vm/ini/free.c @@ -12,11 +12,11 @@ /* OP_INI_FREE: pops handle; pushes 1/0 */ #ifdef FUN_WITH_INI case OP_INI_FREE: { - Value vh = pop_value(vm); - int h = (vh.type == VAL_INT) ? (int)vh.i : 0; - free_value(vh); - int ok = ini_free_handle(h); - push_value(vm, make_int(ok)); - break; + Value vh = pop_value(vm); + int h = (vh.type == VAL_INT) ? (int)vh.i : 0; + free_value(vh); + int ok = ini_free_handle(h); + push_value(vm, make_int(ok)); + break; } #endif diff --git a/src/vm/ini/get_bool.c b/src/vm/ini/get_bool.c index 302e287..c33233e 100644 --- a/src/vm/ini/get_bool.c +++ b/src/vm/ini/get_bool.c @@ -12,51 +12,67 @@ /* OP_INI_GET_BOOL */ #ifdef FUN_WITH_INI case OP_INI_GET_BOOL: { - Value vdef = pop_value(vm); - Value vkey = pop_value(vm); - Value vsec = pop_value(vm); - Value vh = pop_value(vm); - int def = (vdef.type==VAL_INT||vdef.type==VAL_BOOL) ? (int)vdef.i : 0; - const char *key = (vkey.type==VAL_STRING) ? vkey.s : NULL; - const char *sec = (vsec.type==VAL_STRING) ? vsec.s : NULL; - int h = (vh.type==VAL_INT) ? (int)vh.i : 0; - dictionary *d = ini_get(h); - int outb = def; - if (d && sec && key) { - char full[1024]; char alt[1024]; - ini_make_full_key(full, sizeof(full), sec, key); - memcpy(alt, full, sizeof(alt)); - for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) { if (alt[i] == ':') { alt[i] = '.'; break; } } - const char *s = iniparser_getstring(d, full, NULL); - if (!s) s = iniparser_getstring(d, alt, NULL); - if (s) { - /* normalize and parse boolean */ - char buf[256]; - size_t n = strlen(s); - if (n >= 2 && ((s[0]=='"' && s[n-1]=='"') || (s[0]=='\'' && s[n-1]=='\''))) { - size_t copy = (n-2) < sizeof(buf)-1 ? (n-2) : sizeof(buf)-1; - memcpy(buf, s+1, copy); buf[copy] = '\0'; - s = buf; - } - /* trim spaces */ - while (*s && (unsigned char)*s <= ' ') s++; - /* lower copy for textual booleans */ - char lb[256]; size_t li=0; for (; s[li] && li < sizeof(lb)-1; ++li) lb[li] = (char)tolower((unsigned char)s[li]); lb[li]='\0'; - if (strcmp(lb, "true")==0 || strcmp(lb, "yes")==0 || strcmp(lb, "on")==0) { - outb = 1; - } else if (strcmp(lb, "false")==0 || strcmp(lb, "no")==0 || strcmp(lb, "off")==0) { - outb = 0; - } else { - /* numeric */ - char *endp=NULL; long v = strtol(lb, &endp, 10); - outb = (endp && endp!=lb) ? (v!=0) : def; - } - } else { - outb = def; - } + Value vdef = pop_value(vm); + Value vkey = pop_value(vm); + Value vsec = pop_value(vm); + Value vh = pop_value(vm); + int def = (vdef.type == VAL_INT || vdef.type == VAL_BOOL) ? (int)vdef.i : 0; + const char *key = (vkey.type == VAL_STRING) ? vkey.s : NULL; + const char *sec = (vsec.type == VAL_STRING) ? vsec.s : NULL; + int h = (vh.type == VAL_INT) ? (int)vh.i : 0; + dictionary *d = ini_get(h); + int outb = def; + if (d && sec && key) { + char full[1024]; + char alt[1024]; + ini_make_full_key(full, sizeof(full), sec, key); + memcpy(alt, full, sizeof(alt)); + for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) { + if (alt[i] == ':') { + alt[i] = '.'; + break; + } } - free_value(vdef); free_value(vkey); free_value(vsec); free_value(vh); - push_value(vm, make_int(outb ? 1 : 0)); - break; + const char *s = iniparser_getstring(d, full, NULL); + if (!s) s = iniparser_getstring(d, alt, NULL); + if (s) { + /* normalize and parse boolean */ + char buf[256]; + size_t n = strlen(s); + if (n >= 2 && ((s[0] == '"' && s[n - 1] == '"') || (s[0] == '\'' && s[n - 1] == '\''))) { + size_t copy = (n - 2) < sizeof(buf) - 1 ? (n - 2) : sizeof(buf) - 1; + memcpy(buf, s + 1, copy); + buf[copy] = '\0'; + s = buf; + } + /* trim spaces */ + while (*s && (unsigned char)*s <= ' ') + s++; + /* lower copy for textual booleans */ + char lb[256]; + size_t li = 0; + for (; s[li] && li < sizeof(lb) - 1; ++li) + lb[li] = (char)tolower((unsigned char)s[li]); + lb[li] = '\0'; + if (strcmp(lb, "true") == 0 || strcmp(lb, "yes") == 0 || strcmp(lb, "on") == 0) { + outb = 1; + } else if (strcmp(lb, "false") == 0 || strcmp(lb, "no") == 0 || strcmp(lb, "off") == 0) { + outb = 0; + } else { + /* numeric */ + char *endp = NULL; + long v = strtol(lb, &endp, 10); + outb = (endp && endp != lb) ? (v != 0) : def; + } + } else { + outb = def; + } + } + free_value(vdef); + free_value(vkey); + free_value(vsec); + free_value(vh); + push_value(vm, make_int(outb ? 1 : 0)); + break; } #endif diff --git a/src/vm/ini/get_double.c b/src/vm/ini/get_double.c index 7896621..8f14e97 100644 --- a/src/vm/ini/get_double.c +++ b/src/vm/ini/get_double.c @@ -12,41 +12,55 @@ /* OP_INI_GET_DOUBLE */ #ifdef FUN_WITH_INI case OP_INI_GET_DOUBLE: { - Value vdef = pop_value(vm); - Value vkey = pop_value(vm); - Value vsec = pop_value(vm); - Value vh = pop_value(vm); - double def = (vdef.type==VAL_FLOAT) ? vdef.d : (vdef.type==VAL_INT ? (double)vdef.i : 0.0); - const char *key = (vkey.type==VAL_STRING) ? vkey.s : NULL; - const char *sec = (vsec.type==VAL_STRING) ? vsec.s : NULL; - int h = (vh.type==VAL_INT) ? (int)vh.i : 0; - dictionary *d = ini_get(h); - double outd = def; - if (d && sec && key) { - char full[1024]; char alt[1024]; - ini_make_full_key(full, sizeof(full), sec, key); - memcpy(alt, full, sizeof(alt)); - for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) { if (alt[i] == ':') { alt[i] = '.'; break; } } - const char *s = iniparser_getstring(d, full, NULL); - if (!s) s = iniparser_getstring(d, alt, NULL); - if (s) { - char buf[256]; - size_t n = strlen(s); - if (n >= 2 && ((s[0]=='"' && s[n-1]=='"') || (s[0]=='\'' && s[n-1]=='\''))) { - size_t copy = (n-2) < sizeof(buf)-1 ? (n-2) : sizeof(buf)-1; - memcpy(buf, s+1, copy); buf[copy] = '\0'; - s = buf; - } - while (*s && (unsigned char)*s <= ' ') s++; - char *endp = NULL; - double v = strtod(s, &endp); - if (endp && endp != s) outd = v; else outd = def; - } else { - outd = def; - } + Value vdef = pop_value(vm); + Value vkey = pop_value(vm); + Value vsec = pop_value(vm); + Value vh = pop_value(vm); + double def = (vdef.type == VAL_FLOAT) ? vdef.d : (vdef.type == VAL_INT ? (double)vdef.i : 0.0); + const char *key = (vkey.type == VAL_STRING) ? vkey.s : NULL; + const char *sec = (vsec.type == VAL_STRING) ? vsec.s : NULL; + int h = (vh.type == VAL_INT) ? (int)vh.i : 0; + dictionary *d = ini_get(h); + double outd = def; + if (d && sec && key) { + char full[1024]; + char alt[1024]; + ini_make_full_key(full, sizeof(full), sec, key); + memcpy(alt, full, sizeof(alt)); + for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) { + if (alt[i] == ':') { + alt[i] = '.'; + break; + } } - free_value(vdef); free_value(vkey); free_value(vsec); free_value(vh); - push_value(vm, make_float(outd)); - break; + const char *s = iniparser_getstring(d, full, NULL); + if (!s) s = iniparser_getstring(d, alt, NULL); + if (s) { + char buf[256]; + size_t n = strlen(s); + if (n >= 2 && ((s[0] == '"' && s[n - 1] == '"') || (s[0] == '\'' && s[n - 1] == '\''))) { + size_t copy = (n - 2) < sizeof(buf) - 1 ? (n - 2) : sizeof(buf) - 1; + memcpy(buf, s + 1, copy); + buf[copy] = '\0'; + s = buf; + } + while (*s && (unsigned char)*s <= ' ') + s++; + char *endp = NULL; + double v = strtod(s, &endp); + if (endp && endp != s) + outd = v; + else + outd = def; + } else { + outd = def; + } + } + free_value(vdef); + free_value(vkey); + free_value(vsec); + free_value(vh); + push_value(vm, make_float(outd)); + break; } #endif diff --git a/src/vm/ini/get_int.c b/src/vm/ini/get_int.c index 4577ca3..ac564ad 100644 --- a/src/vm/ini/get_int.c +++ b/src/vm/ini/get_int.c @@ -12,43 +12,57 @@ /* OP_INI_GET_INT */ #ifdef FUN_WITH_INI case OP_INI_GET_INT: { - Value vdef = pop_value(vm); - Value vkey = pop_value(vm); - Value vsec = pop_value(vm); - Value vh = pop_value(vm); - int def = (vdef.type==VAL_INT) ? (int)vdef.i : 0; - const char *key = (vkey.type==VAL_STRING) ? vkey.s : NULL; - const char *sec = (vsec.type==VAL_STRING) ? vsec.s : NULL; - int h = (vh.type==VAL_INT) ? (int)vh.i : 0; - dictionary *d = ini_get(h); - int outi = def; - if (d && sec && key) { - char full[1024]; char alt[1024]; - ini_make_full_key(full, sizeof(full), sec, key); - memcpy(alt, full, sizeof(alt)); - for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) { if (alt[i] == ':') { alt[i] = '.'; break; } } - const char *s = iniparser_getstring(d, full, NULL); - if (!s) s = iniparser_getstring(d, alt, NULL); - if (s) { - /* strip optional quotes and parse */ - char buf[256]; - size_t n = strlen(s); - if (n >= 2 && ((s[0]=='"' && s[n-1]=='"') || (s[0]=='\'' && s[n-1]=='\''))) { - size_t copy = (n-2) < sizeof(buf)-1 ? (n-2) : sizeof(buf)-1; - memcpy(buf, s+1, copy); buf[copy] = '\0'; - s = buf; - } - /* skip leading spaces */ - while (*s && (unsigned char)*s <= ' ') s++; - char *endp = NULL; - long v = strtol(s, &endp, 10); - if (endp && endp != s) outi = (int)v; else outi = def; - } else { - outi = def; - } + Value vdef = pop_value(vm); + Value vkey = pop_value(vm); + Value vsec = pop_value(vm); + Value vh = pop_value(vm); + int def = (vdef.type == VAL_INT) ? (int)vdef.i : 0; + const char *key = (vkey.type == VAL_STRING) ? vkey.s : NULL; + const char *sec = (vsec.type == VAL_STRING) ? vsec.s : NULL; + int h = (vh.type == VAL_INT) ? (int)vh.i : 0; + dictionary *d = ini_get(h); + int outi = def; + if (d && sec && key) { + char full[1024]; + char alt[1024]; + ini_make_full_key(full, sizeof(full), sec, key); + memcpy(alt, full, sizeof(alt)); + for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) { + if (alt[i] == ':') { + alt[i] = '.'; + break; + } } - free_value(vdef); free_value(vkey); free_value(vsec); free_value(vh); - push_value(vm, make_int(outi)); - break; + const char *s = iniparser_getstring(d, full, NULL); + if (!s) s = iniparser_getstring(d, alt, NULL); + if (s) { + /* strip optional quotes and parse */ + char buf[256]; + size_t n = strlen(s); + if (n >= 2 && ((s[0] == '"' && s[n - 1] == '"') || (s[0] == '\'' && s[n - 1] == '\''))) { + size_t copy = (n - 2) < sizeof(buf) - 1 ? (n - 2) : sizeof(buf) - 1; + memcpy(buf, s + 1, copy); + buf[copy] = '\0'; + s = buf; + } + /* skip leading spaces */ + while (*s && (unsigned char)*s <= ' ') + s++; + char *endp = NULL; + long v = strtol(s, &endp, 10); + if (endp && endp != s) + outi = (int)v; + else + outi = def; + } else { + outi = def; + } + } + free_value(vdef); + free_value(vkey); + free_value(vsec); + free_value(vh); + push_value(vm, make_int(outi)); + break; } #endif diff --git a/src/vm/ini/get_string.c b/src/vm/ini/get_string.c index ef44106..460ff76 100644 --- a/src/vm/ini/get_string.c +++ b/src/vm/ini/get_string.c @@ -12,34 +12,42 @@ /* OP_INI_GET_STRING */ #ifdef FUN_WITH_INI case OP_INI_GET_STRING: { - Value vdef = pop_value(vm); - Value vkey = pop_value(vm); - Value vsec = pop_value(vm); - Value vh = pop_value(vm); - const char *def = (vdef.type==VAL_STRING && vdef.s) ? vdef.s : ""; - const char *key = (vkey.type==VAL_STRING) ? vkey.s : NULL; - const char *sec = (vsec.type==VAL_STRING) ? vsec.s : NULL; - int h = (vh.type==VAL_INT) ? (int)vh.i : 0; - dictionary *d = ini_get(h); - const char *res = def; - if (d && sec && key) { - char full[1024]; char alt[1024]; - ini_make_full_key(full, sizeof(full), sec, key); - /* Build alternate with dot separator for robustness */ - ini_make_full_key(alt, sizeof(alt), sec, key); - size_t flen = strlen(full); - if (flen < sizeof(alt) && flen > 0) { /* create dot version in alt */ - memcpy(alt, full, flen + 1); - for (size_t i = 0; i < flen; ++i) if (alt[i] == ':') { alt[i] = '.'; break; } + Value vdef = pop_value(vm); + Value vkey = pop_value(vm); + Value vsec = pop_value(vm); + Value vh = pop_value(vm); + const char *def = (vdef.type == VAL_STRING && vdef.s) ? vdef.s : ""; + const char *key = (vkey.type == VAL_STRING) ? vkey.s : NULL; + const char *sec = (vsec.type == VAL_STRING) ? vsec.s : NULL; + int h = (vh.type == VAL_INT) ? (int)vh.i : 0; + dictionary *d = ini_get(h); + const char *res = def; + if (d && sec && key) { + char full[1024]; + char alt[1024]; + ini_make_full_key(full, sizeof(full), sec, key); + /* Build alternate with dot separator for robustness */ + ini_make_full_key(alt, sizeof(alt), sec, key); + size_t flen = strlen(full); + if (flen < sizeof(alt) && flen > 0) { /* create dot version in alt */ + memcpy(alt, full, flen + 1); + for (size_t i = 0; i < flen; ++i) + if (alt[i] == ':') { + alt[i] = '.'; + break; } - const char *s = iniparser_getstring(d, full, def); - if (s == def) { /* not found, try alternate dot form */ - s = iniparser_getstring(d, alt, def); - } - res = s ? s : ""; } - free_value(vdef); free_value(vkey); free_value(vsec); free_value(vh); - push_value(vm, make_string(res)); - break; + const char *s = iniparser_getstring(d, full, def); + if (s == def) { /* not found, try alternate dot form */ + s = iniparser_getstring(d, alt, def); + } + res = s ? s : ""; + } + free_value(vdef); + free_value(vkey); + free_value(vsec); + free_value(vh); + push_value(vm, make_string(res)); + break; } #endif diff --git a/src/vm/ini/handles.c b/src/vm/ini/handles.c index 2515410..86cb13b 100644 --- a/src/vm/ini/handles.c +++ b/src/vm/ini/handles.c @@ -9,55 +9,59 @@ #ifdef FUN_WITH_INI #if defined(__has_include) -# if __has_include() -# include -# include -# elif __has_include() -# include -# include -# else -# error "iniparser headers not found" -# endif +#if __has_include() +#include +#include +#elif __has_include() +#include +#include #else -# include -# include +#error "iniparser headers not found" +#endif +#else +#include +#include #endif #include -#include #include +#include #include "handles.h" IniSlot g_ini[64]; int ini_alloc_handle(dictionary *d) { - if (!d) return 0; - for (int i = 1; i < (int)(sizeof(g_ini)/sizeof(g_ini[0])); ++i) { - if (!g_ini[i].in_use) { g_ini[i].in_use = 1; g_ini[i].dict = d; return i; } + if (!d) return 0; + for (int i = 1; i < (int)(sizeof(g_ini) / sizeof(g_ini[0])); ++i) { + if (!g_ini[i].in_use) { + g_ini[i].in_use = 1; + g_ini[i].dict = d; + return i; } - return 0; + } + return 0; } -dictionary* ini_get(int h) { - if (h > 0 && h < (int)(sizeof(g_ini)/sizeof(g_ini[0])) && g_ini[h].in_use) return g_ini[h].dict; - return NULL; +dictionary *ini_get(int h) { + if (h > 0 && h < (int)(sizeof(g_ini) / sizeof(g_ini[0])) && g_ini[h].in_use) return g_ini[h].dict; + return NULL; } int ini_free_handle(int h) { - if (h <= 0 || h >= (int)(sizeof(g_ini)/sizeof(g_ini[0])) || !g_ini[h].in_use) return 0; - if (g_ini[h].dict) iniparser_freedict(g_ini[h].dict); - g_ini[h].dict = NULL; - g_ini[h].in_use = 0; - return 1; + if (h <= 0 || h >= (int)(sizeof(g_ini) / sizeof(g_ini[0])) || !g_ini[h].in_use) return 0; + if (g_ini[h].dict) iniparser_freedict(g_ini[h].dict); + g_ini[h].dict = NULL; + g_ini[h].in_use = 0; + return 1; } void ini_make_full_key(char *buf, size_t cap, const char *sec, const char *key) { - if (!buf || cap == 0) return; - if (!sec) sec = ""; - if (!key) key = ""; - /* iniparser expects section:key; lookup is case-insensitive internally */ - snprintf(buf, cap, "%s:%s", sec, key); + if (!buf || cap == 0) return; + if (!sec) sec = ""; + if (!key) key = ""; + /* iniparser expects section:key; lookup is case-insensitive internally */ + snprintf(buf, cap, "%s:%s", sec, key); } #endif /* FUN_WITH_INI */ diff --git a/src/vm/ini/handles.h b/src/vm/ini/handles.h index 831936c..c18ab62 100644 --- a/src/vm/ini/handles.h +++ b/src/vm/ini/handles.h @@ -14,29 +14,32 @@ #ifdef FUN_WITH_INI #if defined(__has_include) -# if __has_include() -# include -# include -# elif __has_include() -# include -# include -# else -# error "iniparser headers not found" -# endif +#if __has_include() +#include +#include +#elif __has_include() +#include +#include #else -# include -# include +#error "iniparser headers not found" +#endif +#else +#include +#include #endif #include -typedef struct { dictionary *dict; int in_use; } IniSlot; +typedef struct { + dictionary *dict; + int in_use; +} IniSlot; /* Single global registry (defined in handles.c) */ extern IniSlot g_ini[64]; /* Registry API (implemented in handles.c) */ int ini_alloc_handle(dictionary *d); -dictionary* ini_get(int h); +dictionary *ini_get(int h); int ini_free_handle(int h); /* Helper to build section:key string safely into provided buffer (implemented in handles.c) */ diff --git a/src/vm/ini/load.c b/src/vm/ini/load.c index ac401fb..37936e6 100644 --- a/src/vm/ini/load.c +++ b/src/vm/ini/load.c @@ -12,18 +12,20 @@ /* OP_INI_LOAD: pops path string; pushes handle (>0) or 0 */ #ifdef FUN_WITH_INI case OP_INI_LOAD: { - Value vpath = pop_value(vm); - const char *path = (vpath.type == VAL_STRING && vpath.s) ? vpath.s : NULL; - int h = 0; - if (path) { - dictionary *d = iniparser_load(path); - if (d) { - h = ini_alloc_handle(d); - if (!h) { iniparser_freedict(d); } - } + Value vpath = pop_value(vm); + const char *path = (vpath.type == VAL_STRING && vpath.s) ? vpath.s : NULL; + int h = 0; + if (path) { + dictionary *d = iniparser_load(path); + if (d) { + h = ini_alloc_handle(d); + if (!h) { + iniparser_freedict(d); + } } - free_value(vpath); - push_value(vm, make_int(h)); - break; + } + free_value(vpath); + push_value(vm, make_int(h)); + break; } #endif diff --git a/src/vm/ini/save.c b/src/vm/ini/save.c index ba67f60..779a207 100644 --- a/src/vm/ini/save.c +++ b/src/vm/ini/save.c @@ -12,17 +12,22 @@ /* OP_INI_SAVE */ #ifdef FUN_WITH_INI case OP_INI_SAVE: { - Value vpath = pop_value(vm); - Value vh = pop_value(vm); - const char *path = (vpath.type==VAL_STRING)?vpath.s:NULL; - dictionary *d = ini_get((vh.type==VAL_INT)?(int)vh.i:0); - int ok = 0; - if (d && path) { - FILE *f = fopen(path, "w"); - if (f) { iniparser_dump_ini(d, f); fclose(f); ok = 1; } + Value vpath = pop_value(vm); + Value vh = pop_value(vm); + const char *path = (vpath.type == VAL_STRING) ? vpath.s : NULL; + dictionary *d = ini_get((vh.type == VAL_INT) ? (int)vh.i : 0); + int ok = 0; + if (d && path) { + FILE *f = fopen(path, "w"); + if (f) { + iniparser_dump_ini(d, f); + fclose(f); + ok = 1; } - free_value(vpath); free_value(vh); - push_value(vm, make_int(ok)); - break; + } + free_value(vpath); + free_value(vh); + push_value(vm, make_int(ok)); + break; } #endif diff --git a/src/vm/ini/set.c b/src/vm/ini/set.c index eee9660..04a756c 100644 --- a/src/vm/ini/set.c +++ b/src/vm/ini/set.c @@ -12,32 +12,41 @@ /* OP_INI_SET */ #ifdef FUN_WITH_INI case OP_INI_SET: { - Value vval = pop_value(vm); - Value vkey = pop_value(vm); - Value vsec = pop_value(vm); - Value vh = pop_value(vm); - dictionary *d = ini_get((vh.type==VAL_INT)?(int)vh.i:0); - const char *key = (vkey.type==VAL_STRING)?vkey.s:NULL; - const char *sec = (vsec.type==VAL_STRING)?vsec.s:NULL; - int ok = 0; - if (d && sec && key) { - char *valstr = value_to_string_alloc(&vval); - if (valstr) { - char full[1024]; char alt[1024]; - ini_make_full_key(full, sizeof(full), sec, key); - memcpy(alt, full, sizeof(alt)); - for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) { if (alt[i] == ':') { alt[i] = '.'; break; } } - /* iniparser 4.x does not expose iniparser_set; use dictionary_set */ - if (dictionary_set(d, full, valstr) == 0) { - ok = 1; /* 0 means success */ - } else if (dictionary_set(d, alt, valstr) == 0) { - ok = 1; - } - free(valstr); + Value vval = pop_value(vm); + Value vkey = pop_value(vm); + Value vsec = pop_value(vm); + Value vh = pop_value(vm); + dictionary *d = ini_get((vh.type == VAL_INT) ? (int)vh.i : 0); + const char *key = (vkey.type == VAL_STRING) ? vkey.s : NULL; + const char *sec = (vsec.type == VAL_STRING) ? vsec.s : NULL; + int ok = 0; + if (d && sec && key) { + char *valstr = value_to_string_alloc(&vval); + if (valstr) { + char full[1024]; + char alt[1024]; + ini_make_full_key(full, sizeof(full), sec, key); + memcpy(alt, full, sizeof(alt)); + for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) { + if (alt[i] == ':') { + alt[i] = '.'; + break; } + } + /* iniparser 4.x does not expose iniparser_set; use dictionary_set */ + if (dictionary_set(d, full, valstr) == 0) { + ok = 1; /* 0 means success */ + } else if (dictionary_set(d, alt, valstr) == 0) { + ok = 1; + } + free(valstr); } - free_value(vval); free_value(vkey); free_value(vsec); free_value(vh); - push_value(vm, make_int(ok)); - break; + } + free_value(vval); + free_value(vkey); + free_value(vsec); + free_value(vh); + push_value(vm, make_int(ok)); + break; } #endif diff --git a/src/vm/ini/stubs.c b/src/vm/ini/stubs.c index 1d13fb7..2b53f2b 100644 --- a/src/vm/ini/stubs.c +++ b/src/vm/ini/stubs.c @@ -5,100 +5,114 @@ /* OP_INI_LOAD: pops path string; pushes 0 (invalid handle) */ case OP_INI_LOAD: { - Value vpath = pop_value(vm); - (void)vpath; /* unused */ - free_value(vpath); - fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); - push_value(vm, make_int(0)); - break; + Value vpath = pop_value(vm); + (void)vpath; /* unused */ + free_value(vpath); + fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); + push_value(vm, make_int(0)); + break; } /* OP_INI_FREE: pops handle; pushes 0 */ case OP_INI_FREE: { - Value vh = pop_value(vm); - free_value(vh); - fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); - push_value(vm, make_int(0)); - break; + Value vh = pop_value(vm); + free_value(vh); + fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); + push_value(vm, make_int(0)); + break; } /* Getters: pop args; push defaults (string:"", int:0, double:0.0, bool:0) */ case OP_INI_GET_STRING: { - Value vdef = pop_value(vm); - Value vkey = pop_value(vm); - Value vsec = pop_value(vm); - Value vh = pop_value(vm); - free_value(vh); free_value(vsec); free_value(vkey); - /* cannot convert here; return empty string */ - push_value(vm, make_string("")); - free_value(vdef); - fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); - break; + Value vdef = pop_value(vm); + Value vkey = pop_value(vm); + Value vsec = pop_value(vm); + Value vh = pop_value(vm); + free_value(vh); + free_value(vsec); + free_value(vkey); + /* cannot convert here; return empty string */ + push_value(vm, make_string("")); + free_value(vdef); + fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); + break; } case OP_INI_GET_INT: { - Value vdef = pop_value(vm); - Value vkey = pop_value(vm); - Value vsec = pop_value(vm); - Value vh = pop_value(vm); - free_value(vh); free_value(vsec); free_value(vkey); - (void)vdef; /* unused */ - push_value(vm, make_int(0)); - fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); - break; + Value vdef = pop_value(vm); + Value vkey = pop_value(vm); + Value vsec = pop_value(vm); + Value vh = pop_value(vm); + free_value(vh); + free_value(vsec); + free_value(vkey); + (void)vdef; /* unused */ + push_value(vm, make_int(0)); + fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); + break; } case OP_INI_GET_DOUBLE: { - Value vdef = pop_value(vm); - Value vkey = pop_value(vm); - Value vsec = pop_value(vm); - Value vh = pop_value(vm); - free_value(vh); free_value(vsec); free_value(vkey); - (void)vdef; /* unused */ - push_value(vm, make_float(0.0)); - fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); - break; + Value vdef = pop_value(vm); + Value vkey = pop_value(vm); + Value vsec = pop_value(vm); + Value vh = pop_value(vm); + free_value(vh); + free_value(vsec); + free_value(vkey); + (void)vdef; /* unused */ + push_value(vm, make_float(0.0)); + fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); + break; } case OP_INI_GET_BOOL: { - Value vdef = pop_value(vm); - Value vkey = pop_value(vm); - Value vsec = pop_value(vm); - Value vh = pop_value(vm); - free_value(vh); free_value(vsec); free_value(vkey); - (void)vdef; /* unused */ - push_value(vm, make_int(0)); - fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); - break; + Value vdef = pop_value(vm); + Value vkey = pop_value(vm); + Value vsec = pop_value(vm); + Value vh = pop_value(vm); + free_value(vh); + free_value(vsec); + free_value(vkey); + (void)vdef; /* unused */ + push_value(vm, make_int(0)); + fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); + break; } /* Mutators: return 0 */ case OP_INI_SET: { - Value vval = pop_value(vm); - Value vkey = pop_value(vm); - Value vsec = pop_value(vm); - Value vh = pop_value(vm); - free_value(vh); free_value(vsec); free_value(vkey); free_value(vval); - fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); - push_value(vm, make_int(0)); - break; + Value vval = pop_value(vm); + Value vkey = pop_value(vm); + Value vsec = pop_value(vm); + Value vh = pop_value(vm); + free_value(vh); + free_value(vsec); + free_value(vkey); + free_value(vval); + fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); + push_value(vm, make_int(0)); + break; } case OP_INI_UNSET: { - Value vkey = pop_value(vm); - Value vsec = pop_value(vm); - Value vh = pop_value(vm); - free_value(vh); free_value(vsec); free_value(vkey); - fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); - push_value(vm, make_int(0)); - break; + Value vkey = pop_value(vm); + Value vsec = pop_value(vm); + Value vh = pop_value(vm); + free_value(vh); + free_value(vsec); + free_value(vkey); + fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); + push_value(vm, make_int(0)); + break; } case OP_INI_SAVE: { - Value vpath = pop_value(vm); - Value vh = pop_value(vm); - free_value(vh); free_value(vpath); - fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); - push_value(vm, make_int(0)); - break; + Value vpath = pop_value(vm); + Value vh = pop_value(vm); + free_value(vh); + free_value(vpath); + fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); + push_value(vm, make_int(0)); + break; } diff --git a/src/vm/ini/unset.c b/src/vm/ini/unset.c index 8c339c7..807aa23 100644 --- a/src/vm/ini/unset.c +++ b/src/vm/ini/unset.c @@ -12,25 +12,33 @@ /* OP_INI_UNSET */ #ifdef FUN_WITH_INI case OP_INI_UNSET: { - Value vkey = pop_value(vm); - Value vsec = pop_value(vm); - Value vh = pop_value(vm); - dictionary *d = ini_get((vh.type==VAL_INT)?(int)vh.i:0); - const char *key = (vkey.type==VAL_STRING)?vkey.s:NULL; - const char *sec = (vsec.type==VAL_STRING)?vsec.s:NULL; - int ok = 0; - if (d && sec && key) { - char full[1024]; char alt[1024]; - ini_make_full_key(full, sizeof(full), sec, key); - memcpy(alt, full, sizeof(alt)); - for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) { if (alt[i] == ':') { alt[i] = '.'; break; } } - /* iniparser 4.2.6 dictionary_unset returns void; remove both forms */ - dictionary_unset(d, full); - dictionary_unset(d, alt); - ok = 1; + Value vkey = pop_value(vm); + Value vsec = pop_value(vm); + Value vh = pop_value(vm); + dictionary *d = ini_get((vh.type == VAL_INT) ? (int)vh.i : 0); + const char *key = (vkey.type == VAL_STRING) ? vkey.s : NULL; + const char *sec = (vsec.type == VAL_STRING) ? vsec.s : NULL; + int ok = 0; + if (d && sec && key) { + char full[1024]; + char alt[1024]; + ini_make_full_key(full, sizeof(full), sec, key); + memcpy(alt, full, sizeof(alt)); + for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) { + if (alt[i] == ':') { + alt[i] = '.'; + break; + } } - free_value(vkey); free_value(vsec); free_value(vh); - push_value(vm, make_int(ok)); - break; + /* iniparser 4.2.6 dictionary_unset returns void; remove both forms */ + dictionary_unset(d, full); + dictionary_unset(d, alt); + ok = 1; + } + free_value(vkey); + free_value(vsec); + free_value(vh); + push_value(vm, make_int(ok)); + break; } #endif diff --git a/src/vm/io/input_line.c b/src/vm/io/input_line.c index 938dda9..a8b34dd 100644 --- a/src/vm/io/input_line.c +++ b/src/vm/io/input_line.c @@ -1,137 +1,137 @@ case OP_INPUT_LINE: { - /* operand bit flags: - * bit0 (1): has prompt (string or any value convertible to string) — top of stack holds prompt when set - * bit1 (2): hidden input (do not echo typed characters) - */ - int has_prompt = (inst.operand & 1) ? 1 : 0; - int hidden = (inst.operand & 2) ? 1 : 0; - if (has_prompt) { - /* pop prompt value and print without newline */ - Value pv = pop_value(vm); - char *pstr = value_to_string_alloc(&pv); - if (pstr) { - fputs(pstr, stdout); - fflush(stdout); - free(pstr); - } - free_value(pv); + /* operand bit flags: + * bit0 (1): has prompt (string or any value convertible to string) — top of stack holds prompt when set + * bit1 (2): hidden input (do not echo typed characters) + */ + int has_prompt = (inst.operand & 1) ? 1 : 0; + int hidden = (inst.operand & 2) ? 1 : 0; + if (has_prompt) { + /* pop prompt value and print without newline */ + Value pv = pop_value(vm); + char *pstr = value_to_string_alloc(&pv); + if (pstr) { + fputs(pstr, stdout); + fflush(stdout); + free(pstr); } + free_value(pv); + } - /* For hidden input, temporarily disable terminal echo if possible */ - int echo_disabled = 0; + /* For hidden input, temporarily disable terminal echo if possible */ + int echo_disabled = 0; #ifdef _WIN32 - if (hidden) { - HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); - if (hStdin != INVALID_HANDLE_VALUE) { - DWORD mode; - if (GetConsoleMode(hStdin, &mode)) { - DWORD newMode = mode & ~(ENABLE_ECHO_INPUT); - if (SetConsoleMode(hStdin, newMode)) { - echo_disabled = 1; - } - } + if (hidden) { + HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); + if (hStdin != INVALID_HANDLE_VALUE) { + DWORD mode; + if (GetConsoleMode(hStdin, &mode)) { + DWORD newMode = mode & ~(ENABLE_ECHO_INPUT); + if (SetConsoleMode(hStdin, newMode)) { + echo_disabled = 1; } + } } + } #else - if (hidden) { - /* POSIX termios */ - struct termios oldt; - if (tcgetattr(STDIN_FILENO, &oldt) == 0) { - struct termios newt = oldt; - newt.c_lflag &= ~(ECHO); - if (tcsetattr(STDIN_FILENO, TCSANOW, &newt) == 0) { - echo_disabled = 1; - } - } + if (hidden) { + /* POSIX termios */ + struct termios oldt; + if (tcgetattr(STDIN_FILENO, &oldt) == 0) { + struct termios newt = oldt; + newt.c_lflag &= ~(ECHO); + if (tcsetattr(STDIN_FILENO, TCSANOW, &newt) == 0) { + echo_disabled = 1; + } } + } #endif - /* read a line from stdin, dynamically grow buffer */ - size_t cap = 128; - size_t len = 0; - char *buf = (char*)malloc(cap); - if (!buf) { + /* read a line from stdin, dynamically grow buffer */ + size_t cap = 128; + size_t len = 0; + char *buf = (char *)malloc(cap); + if (!buf) { + fprintf(stderr, "Runtime error: out of memory reading input"); + push_value(vm, make_string("")); + /* On early exit, try to restore echo if we turned it off */ + goto restore_echo_and_break; + } + + int ch; + while ((ch = fgetc(stdin)) != EOF) { + if (ch == '\r') { + /* Handle CRLF by consuming optional following '\n' */ + int next = fgetc(stdin); + if (next != EOF && next != '\n') { + ungetc(next, stdin); + } + break; + } + if (ch == '\n') { + break; + } + if (len + 1 >= cap) { + cap *= 2; + char *nb = (char *)realloc(buf, cap); + if (!nb) { + free(buf); fprintf(stderr, "Runtime error: out of memory reading input"); push_value(vm, make_string("")); - /* On early exit, try to restore echo if we turned it off */ - goto restore_echo_and_break; + goto push_done; + } + buf = nb; } + buf[len++] = (char)ch; + } - int ch; - while ((ch = fgetc(stdin)) != EOF) { - if (ch == '\r') { - /* Handle CRLF by consuming optional following '\n' */ - int next = fgetc(stdin); - if (next != EOF && next != '\n') { - ungetc(next, stdin); - } - break; - } - if (ch == '\n') { - break; - } - if (len + 1 >= cap) { - cap *= 2; - char *nb = (char*)realloc(buf, cap); - if (!nb) { - free(buf); - fprintf(stderr, "Runtime error: out of memory reading input"); - push_value(vm, make_string("")); - goto push_done; - } - buf = nb; - } - buf[len++] = (char)ch; + /* null-terminate */ + if (len + 1 >= cap) { + char *nb = (char *)realloc(buf, len + 1); + if (!nb) { + free(buf); + fprintf(stderr, "Runtime error: out of memory finalizing input"); + push_value(vm, make_string("")); + goto push_done; } + buf = nb; + } + buf[len] = '\0'; - /* null-terminate */ - if (len + 1 >= cap) { - char *nb = (char*)realloc(buf, len + 1); - if (!nb) { - free(buf); - fprintf(stderr, "Runtime error: out of memory finalizing input"); - push_value(vm, make_string("")); - goto push_done; - } - buf = nb; - } - buf[len] = '\0'; - - /* push as Fun string */ - push_value(vm, make_string(buf)); - free(buf); + /* push as Fun string */ + push_value(vm, make_string(buf)); + free(buf); push_done: - /* If we disabled echo, restore terminal settings and print a newline for UX */ + /* If we disabled echo, restore terminal settings and print a newline for UX */ #ifdef _WIN32 - if (echo_disabled) { - HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); - if (hStdin != INVALID_HANDLE_VALUE) { - DWORD mode; - if (GetConsoleMode(hStdin, &mode)) { - /* Re-enable ECHO flag */ - mode |= ENABLE_ECHO_INPUT; - SetConsoleMode(hStdin, mode); - } - } - if (has_prompt) { - fputc('\n', stdout); - fflush(stdout); - } + if (echo_disabled) { + HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); + if (hStdin != INVALID_HANDLE_VALUE) { + DWORD mode; + if (GetConsoleMode(hStdin, &mode)) { + /* Re-enable ECHO flag */ + mode |= ENABLE_ECHO_INPUT; + SetConsoleMode(hStdin, mode); + } } + if (has_prompt) { + fputc('\n', stdout); + fflush(stdout); + } + } #else - if (echo_disabled) { - struct termios t; - if (tcgetattr(STDIN_FILENO, &t) == 0) { - t.c_lflag |= ECHO; - tcsetattr(STDIN_FILENO, TCSANOW, &t); - } - if (has_prompt) { - fputc('\n', stdout); - fflush(stdout); - } + if (echo_disabled) { + struct termios t; + if (tcgetattr(STDIN_FILENO, &t) == 0) { + t.c_lflag |= ECHO; + tcsetattr(STDIN_FILENO, TCSANOW, &t); } + if (has_prompt) { + fputc('\n', stdout); + fflush(stdout); + } + } #endif restore_echo_and_break: - break; + break; } diff --git a/src/vm/io/read_file.c b/src/vm/io/read_file.c index 0621db2..f0b10a1 100644 --- a/src/vm/io/read_file.c +++ b/src/vm/io/read_file.c @@ -8,7 +8,7 @@ */ /** -* @file read_file.c + * @file read_file.c * @brief Implements the OP_READ_FILE opcode for reading file contents in the VM. * * This file handles the OP_READ_FILE instruction, which reads the contents of a file @@ -32,23 +32,44 @@ */ case OP_READ_FILE: { - Value path = pop_value(vm); - if (path.type != VAL_STRING) { fprintf(stderr, "READ_FILE expects string\n"); exit(1); } - const char *p = path.s ? path.s : ""; - FILE *f = fopen(p, "rb"); - if (!f) { free_value(path); push_value(vm, make_string("")); break; } - if (fseek(f, 0, SEEK_END) != 0) { fclose(f); free_value(path); push_value(vm, make_string("")); break; } - long sz = ftell(f); - if (sz < 0) { fclose(f); free_value(path); push_value(vm, make_string("")); break; } - rewind(f); - char *buf = (char*)malloc((size_t)sz + 1); - size_t n = buf ? fread(buf, 1, (size_t)sz, f) : 0; - fclose(f); - if (!buf) { free_value(path); push_value(vm, make_string("")); break; } - buf[n] = '\0'; - Value out = make_string(buf); - free(buf); + Value path = pop_value(vm); + if (path.type != VAL_STRING) { + fprintf(stderr, "READ_FILE expects string\n"); + exit(1); + } + const char *p = path.s ? path.s : ""; + FILE *f = fopen(p, "rb"); + if (!f) { free_value(path); - push_value(vm, out); + push_value(vm, make_string("")); break; + } + if (fseek(f, 0, SEEK_END) != 0) { + fclose(f); + free_value(path); + push_value(vm, make_string("")); + break; + } + long sz = ftell(f); + if (sz < 0) { + fclose(f); + free_value(path); + push_value(vm, make_string("")); + break; + } + rewind(f); + char *buf = (char *)malloc((size_t)sz + 1); + size_t n = buf ? fread(buf, 1, (size_t)sz, f) : 0; + fclose(f); + if (!buf) { + free_value(path); + push_value(vm, make_string("")); + break; + } + buf[n] = '\0'; + Value out = make_string(buf); + free(buf); + free_value(path); + push_value(vm, out); + break; } diff --git a/src/vm/io/write_file.c b/src/vm/io/write_file.c index 69ee253..7f1e0c7 100644 --- a/src/vm/io/write_file.c +++ b/src/vm/io/write_file.c @@ -8,7 +8,7 @@ */ /** -* @file write_file.c + * @file write_file.c * @brief Implements the OP_WRITE_FILE opcode for writing to a file in the VM. * * This file handles the OP_WRITE_FILE instruction, which writes data to a file. @@ -32,19 +32,22 @@ */ case OP_WRITE_FILE: { - Value data = pop_value(vm); - Value path = pop_value(vm); - if (path.type != VAL_STRING || data.type != VAL_STRING) { fprintf(stderr, "WRITE_FILE expects (string, string)\n"); exit(1); } - const char *p = path.s ? path.s : ""; - FILE *f = fopen(p, "wb"); - int ok = 0; - if (f) { - size_t len = data.s ? strlen(data.s) : 0; - ok = (fwrite(data.s ? data.s : "", 1, len, f) == len); - fclose(f); - } - free_value(path); - free_value(data); - push_value(vm, make_int(ok ? 1 : 0)); - break; + Value data = pop_value(vm); + Value path = pop_value(vm); + if (path.type != VAL_STRING || data.type != VAL_STRING) { + fprintf(stderr, "WRITE_FILE expects (string, string)\n"); + exit(1); + } + const char *p = path.s ? path.s : ""; + FILE *f = fopen(p, "wb"); + int ok = 0; + if (f) { + size_t len = data.s ? strlen(data.s) : 0; + ok = (fwrite(data.s ? data.s : "", 1, len, f) == len); + fclose(f); + } + free_value(path); + free_value(data); + push_value(vm, make_int(ok ? 1 : 0)); + break; } diff --git a/src/vm/json/from_file.c b/src/vm/json/from_file.c index 370bec0..c3c8027 100644 --- a/src/vm/json/from_file.c +++ b/src/vm/json/from_file.c @@ -1,5 +1,5 @@ /** -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -12,19 +12,26 @@ /* JSON_FROM_FILE */ case OP_JSON_FROM_FILE: { #ifdef FUN_WITH_JSON - Value vpath = pop_value(vm); - char *path = value_to_string_alloc(&vpath); - free_value(vpath); - if (!path) { push_value(vm, make_nil()); break; } - json_object *root = json_object_from_file(path); - free(path); - if (!root) { push_value(vm, make_nil()); break; } - Value v = json_to_fun(root); - push_value(vm, v); - json_object_put(root); -#else - Value vpath = pop_value(vm); free_value(vpath); + Value vpath = pop_value(vm); + char *path = value_to_string_alloc(&vpath); + free_value(vpath); + if (!path) { push_value(vm, make_nil()); -#endif break; + } + json_object *root = json_object_from_file(path); + free(path); + if (!root) { + push_value(vm, make_nil()); + break; + } + Value v = json_to_fun(root); + push_value(vm, v); + json_object_put(root); +#else + Value vpath = pop_value(vm); + free_value(vpath); + push_value(vm, make_nil()); +#endif + break; } diff --git a/src/vm/json/parse.c b/src/vm/json/parse.c index 90cc5cc..4083abb 100644 --- a/src/vm/json/parse.c +++ b/src/vm/json/parse.c @@ -1,5 +1,5 @@ /** -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -12,27 +12,30 @@ /* JSON_PARSE */ case OP_JSON_PARSE: { #ifdef FUN_WITH_JSON - Value text = pop_value(vm); - char *s = value_to_string_alloc(&text); - free_value(text); - if (!s) { push_value(vm, make_nil()); break; } - struct json_tokener *tok = json_tokener_new(); - json_object *root = json_tokener_parse_ex(tok, s, (int)strlen(s)); - enum json_tokener_error jerr = json_tokener_get_error(tok); - json_tokener_free(tok); - free(s); - if (jerr != json_tokener_success) { - push_value(vm, make_nil()); - } else { - Value v = json_to_fun(root); - push_value(vm, v); - json_object_put(root); - } -#else - /* Fallback when JSON is disabled: consume arg, push Nil */ - Value drop = pop_value(vm); - free_value(drop); + Value text = pop_value(vm); + char *s = value_to_string_alloc(&text); + free_value(text); + if (!s) { push_value(vm, make_nil()); -#endif break; + } + struct json_tokener *tok = json_tokener_new(); + json_object *root = json_tokener_parse_ex(tok, s, (int)strlen(s)); + enum json_tokener_error jerr = json_tokener_get_error(tok); + json_tokener_free(tok); + free(s); + if (jerr != json_tokener_success) { + push_value(vm, make_nil()); + } else { + Value v = json_to_fun(root); + push_value(vm, v); + json_object_put(root); + } +#else + /* Fallback when JSON is disabled: consume arg, push Nil */ + Value drop = pop_value(vm); + free_value(drop); + push_value(vm, make_nil()); +#endif + break; } diff --git a/src/vm/json/stringify.c b/src/vm/json/stringify.c index 1c5a45b..a6347f6 100644 --- a/src/vm/json/stringify.c +++ b/src/vm/json/stringify.c @@ -1,5 +1,5 @@ /** -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -12,21 +12,23 @@ /* JSON_STRINGIFY */ case OP_JSON_STRINGIFY: { #ifdef FUN_WITH_JSON - Value vpretty = pop_value(vm); - Value any = pop_value(vm); - int pretty = (vpretty.type == VAL_BOOL || vpretty.type == VAL_INT) ? (vpretty.i != 0) : 0; - json_object *j = fun_to_json(&any); - int flags = pretty ? JSON_C_TO_STRING_PRETTY : JSON_C_TO_STRING_PLAIN; - const char *js = json_object_to_json_string_ext(j, flags); - push_value(vm, make_string(js ? js : "")); - json_object_put(j); - free_value(vpretty); - free_value(any); + Value vpretty = pop_value(vm); + Value any = pop_value(vm); + int pretty = (vpretty.type == VAL_BOOL || vpretty.type == VAL_INT) ? (vpretty.i != 0) : 0; + json_object *j = fun_to_json(&any); + int flags = pretty ? JSON_C_TO_STRING_PRETTY : JSON_C_TO_STRING_PLAIN; + const char *js = json_object_to_json_string_ext(j, flags); + push_value(vm, make_string(js ? js : "")); + json_object_put(j); + free_value(vpretty); + free_value(any); #else - /* Fallback: consume two args, push "null" */ - Value vpretty = pop_value(vm); free_value(vpretty); - Value any = pop_value(vm); free_value(any); - push_value(vm, make_string("null")); + /* Fallback: consume two args, push "null" */ + Value vpretty = pop_value(vm); + free_value(vpretty); + Value any = pop_value(vm); + free_value(any); + push_value(vm, make_string("null")); #endif - break; + break; } diff --git a/src/vm/json/to_file.c b/src/vm/json/to_file.c index d0a9857..c21610f 100644 --- a/src/vm/json/to_file.c +++ b/src/vm/json/to_file.c @@ -1,5 +1,5 @@ /** -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -12,26 +12,33 @@ /* JSON_TO_FILE */ case OP_JSON_TO_FILE: { #ifdef FUN_WITH_JSON - Value vpretty = pop_value(vm); - Value any = pop_value(vm); - Value vpath = pop_value(vm); - char *path = value_to_string_alloc(&vpath); - int pretty = (vpretty.type == VAL_BOOL || vpretty.type == VAL_INT) ? (vpretty.i != 0) : 0; - free_value(vpretty); - free_value(vpath); - if (!path) { free_value(any); push_value(vm, make_int(0)); break; } - json_object *j = fun_to_json(&any); - int flags = pretty ? JSON_C_TO_STRING_PRETTY : JSON_C_TO_STRING_PLAIN; - int rc = json_object_to_file_ext(path, j, flags); - json_object_put(j); - free(path); + Value vpretty = pop_value(vm); + Value any = pop_value(vm); + Value vpath = pop_value(vm); + char *path = value_to_string_alloc(&vpath); + int pretty = (vpretty.type == VAL_BOOL || vpretty.type == VAL_INT) ? (vpretty.i != 0) : 0; + free_value(vpretty); + free_value(vpath); + if (!path) { free_value(any); - push_value(vm, make_int(rc == 0 ? 1 : 0)); -#else - Value vpretty = pop_value(vm); free_value(vpretty); - Value any = pop_value(vm); free_value(any); - Value vpath = pop_value(vm); free_value(vpath); push_value(vm, make_int(0)); -#endif break; + } + json_object *j = fun_to_json(&any); + int flags = pretty ? JSON_C_TO_STRING_PRETTY : JSON_C_TO_STRING_PLAIN; + int rc = json_object_to_file_ext(path, j, flags); + json_object_put(j); + free(path); + free_value(any); + push_value(vm, make_int(rc == 0 ? 1 : 0)); +#else + Value vpretty = pop_value(vm); + free_value(vpretty); + Value any = pop_value(vm); + free_value(any); + Value vpath = pop_value(vm); + free_value(vpath); + push_value(vm, make_int(0)); +#endif + break; } diff --git a/src/vm/len.c b/src/vm/len.c index 7adb446..67101b2 100644 --- a/src/vm/len.c +++ b/src/vm/len.c @@ -8,7 +8,7 @@ */ /** -* @file len.c + * @file len.c * @brief Implements the OP_LEN opcode for getting the length of arrays or strings in the VM. * * This file handles the OP_LEN instruction, which retrieves the length of an array or string. @@ -32,18 +32,18 @@ */ case OP_LEN: { - Value a = pop_value(vm); - int len = 0; - if (a.type == VAL_STRING) { - len = (int)(a.s ? (int)strlen(a.s) : 0); - } else if (a.type == VAL_ARRAY) { - len = array_length(&a); - if (len < 0) len = 0; - } else { - /* Be lenient: for non-array/non-string, treat length as 0 */ - push_value(vm, make_int(0)); - } - free_value(a); - push_value(vm, make_int(len)); - break; + Value a = pop_value(vm); + int len = 0; + if (a.type == VAL_STRING) { + len = (int)(a.s ? (int)strlen(a.s) : 0); + } else if (a.type == VAL_ARRAY) { + len = array_length(&a); + if (len < 0) len = 0; + } else { + /* Be lenient: for non-array/non-string, treat length as 0 */ + push_value(vm, make_int(0)); + } + free_value(a); + push_value(vm, make_int(len)); + break; } diff --git a/src/vm/libressl/md5.c b/src/vm/libressl/md5.c index 1ffc998..7fcf177 100644 --- a/src/vm/libressl/md5.c +++ b/src/vm/libressl/md5.c @@ -10,18 +10,24 @@ */ /** -* LibreSSL MD5 builtin -*/ + * LibreSSL MD5 builtin + */ case OP_LIBRESSL_MD5: { - Value vdata = pop_value(vm); - char *s = value_to_string_alloc(&vdata); - free_value(vdata); - if (!s) { push_value(vm, make_string("")); break; } - char *hex = fun_libressl_md5_hex((const unsigned char*)s, strlen(s)); - free(s); - if (!hex) { push_value(vm, make_string("")); break; } - Value out = make_string(hex); - free(hex); - push_value(vm, out); + Value vdata = pop_value(vm); + char *s = value_to_string_alloc(&vdata); + free_value(vdata); + if (!s) { + push_value(vm, make_string("")); break; + } + char *hex = fun_libressl_md5_hex((const unsigned char *)s, strlen(s)); + free(s); + if (!hex) { + push_value(vm, make_string("")); + break; + } + Value out = make_string(hex); + free(hex); + push_value(vm, out); + break; } diff --git a/src/vm/libressl/ripemd160.c b/src/vm/libressl/ripemd160.c index 6dd766c..9bd7ae3 100644 --- a/src/vm/libressl/ripemd160.c +++ b/src/vm/libressl/ripemd160.c @@ -13,15 +13,21 @@ * LibreSSL RIPEMD-160 builtin */ case OP_LIBRESSL_RIPEMD160: { - Value vdata = pop_value(vm); - char *s = value_to_string_alloc(&vdata); - free_value(vdata); - if (!s) { push_value(vm, make_string("")); break; } - char *hex = fun_libressl_ripemd160_hex((const unsigned char*)s, strlen(s)); - free(s); - if (!hex) { push_value(vm, make_string("")); break; } - Value out = make_string(hex); - free(hex); - push_value(vm, out); + Value vdata = pop_value(vm); + char *s = value_to_string_alloc(&vdata); + free_value(vdata); + if (!s) { + push_value(vm, make_string("")); break; + } + char *hex = fun_libressl_ripemd160_hex((const unsigned char *)s, strlen(s)); + free(s); + if (!hex) { + push_value(vm, make_string("")); + break; + } + Value out = make_string(hex); + free(hex); + push_value(vm, out); + break; } diff --git a/src/vm/libressl/sha256.c b/src/vm/libressl/sha256.c index 1b27be0..0864852 100644 --- a/src/vm/libressl/sha256.c +++ b/src/vm/libressl/sha256.c @@ -13,15 +13,21 @@ * LibreSSL SHA-256 builtin */ case OP_LIBRESSL_SHA256: { - Value vdata = pop_value(vm); - char *s = value_to_string_alloc(&vdata); - free_value(vdata); - if (!s) { push_value(vm, make_string("")); break; } - char *hex = fun_libressl_sha256_hex((const unsigned char*)s, strlen(s)); - free(s); - if (!hex) { push_value(vm, make_string("")); break; } - Value out = make_string(hex); - free(hex); - push_value(vm, out); + Value vdata = pop_value(vm); + char *s = value_to_string_alloc(&vdata); + free_value(vdata); + if (!s) { + push_value(vm, make_string("")); break; + } + char *hex = fun_libressl_sha256_hex((const unsigned char *)s, strlen(s)); + free(s); + if (!hex) { + push_value(vm, make_string("")); + break; + } + Value out = make_string(hex); + free(hex); + push_value(vm, out); + break; } diff --git a/src/vm/libressl/sha512.c b/src/vm/libressl/sha512.c index 67abcc8..17b7190 100644 --- a/src/vm/libressl/sha512.c +++ b/src/vm/libressl/sha512.c @@ -13,15 +13,21 @@ * LibreSSL SHA-512 builtin */ case OP_LIBRESSL_SHA512: { - Value vdata = pop_value(vm); - char *s = value_to_string_alloc(&vdata); - free_value(vdata); - if (!s) { push_value(vm, make_string("")); break; } - char *hex = fun_libressl_sha512_hex((const unsigned char*)s, strlen(s)); - free(s); - if (!hex) { push_value(vm, make_string("")); break; } - Value out = make_string(hex); - free(hex); - push_value(vm, out); + Value vdata = pop_value(vm); + char *s = value_to_string_alloc(&vdata); + free_value(vdata); + if (!s) { + push_value(vm, make_string("")); break; + } + char *hex = fun_libressl_sha512_hex((const unsigned char *)s, strlen(s)); + free(s); + if (!hex) { + push_value(vm, make_string("")); + break; + } + Value out = make_string(hex); + free(hex); + push_value(vm, out); + break; } diff --git a/src/vm/libsql/close.c b/src/vm/libsql/close.c index 0a4e566..adc291c 100644 --- a/src/vm/libsql/close.c +++ b/src/vm/libsql/close.c @@ -14,19 +14,20 @@ */ case OP_LIBSQL_CLOSE: { #ifdef FUN_WITH_LIBSQL - Value vh = pop_value(vm); - int hid = (int)vh.i; - free_value(vh); - LibSqlHandle *h = libsql_reg_get(hid); - if (h && h->db) { - sqlite3_close(h->db); - h->db = NULL; - libsql_reg_del(hid); - } - push_value(vm, make_nil()); + Value vh = pop_value(vm); + int hid = (int)vh.i; + free_value(vh); + LibSqlHandle *h = libsql_reg_get(hid); + if (h && h->db) { + sqlite3_close(h->db); + h->db = NULL; + libsql_reg_del(hid); + } + push_value(vm, make_nil()); #else - Value v = pop_value(vm); free_value(v); - push_value(vm, make_nil()); + Value v = pop_value(vm); + free_value(v); + push_value(vm, make_nil()); #endif - break; + break; } diff --git a/src/vm/libsql/exec.c b/src/vm/libsql/exec.c index 0326ae9..77d4643 100644 --- a/src/vm/libsql/exec.c +++ b/src/vm/libsql/exec.c @@ -14,23 +14,29 @@ */ case OP_LIBSQL_EXEC: { #ifdef FUN_WITH_LIBSQL - Value vsql = pop_value(vm); - Value vh = pop_value(vm); - int hid = (int)vh.i; - char *sql = value_to_string_alloc(&vsql); - free_value(vh); - free_value(vsql); - LibSqlHandle *h = libsql_reg_get(hid); - if (!h || !h->db || !sql) { if (sql) free(sql); push_value(vm, make_int(SQLITE_MISUSE)); break; } - char *errmsg = NULL; - int rc = sqlite3_exec(h->db, sql, NULL, NULL, &errmsg); - if (errmsg) sqlite3_free(errmsg); - free(sql); - push_value(vm, make_int(rc)); -#else - Value v1 = pop_value(vm); free_value(v1); - Value v2 = pop_value(vm); free_value(v2); - push_value(vm, make_int(-1)); -#endif + Value vsql = pop_value(vm); + Value vh = pop_value(vm); + int hid = (int)vh.i; + char *sql = value_to_string_alloc(&vsql); + free_value(vh); + free_value(vsql); + LibSqlHandle *h = libsql_reg_get(hid); + if (!h || !h->db || !sql) { + if (sql) free(sql); + push_value(vm, make_int(SQLITE_MISUSE)); break; + } + char *errmsg = NULL; + int rc = sqlite3_exec(h->db, sql, NULL, NULL, &errmsg); + if (errmsg) sqlite3_free(errmsg); + free(sql); + push_value(vm, make_int(rc)); +#else + Value v1 = pop_value(vm); + free_value(v1); + Value v2 = pop_value(vm); + free_value(v2); + push_value(vm, make_int(-1)); +#endif + break; } diff --git a/src/vm/libsql/open.c b/src/vm/libsql/open.c index 7b3f177..cead1b0 100644 --- a/src/vm/libsql/open.c +++ b/src/vm/libsql/open.c @@ -14,24 +14,32 @@ */ case OP_LIBSQL_OPEN: { #ifdef FUN_WITH_LIBSQL - Value vpath = pop_value(vm); - char *path = value_to_string_alloc(&vpath); - free_value(vpath); - if (!path) { push_value(vm, make_int(0)); break; } - sqlite3 *db = NULL; - int rc = sqlite3_open(path, &db); - free(path); - if (rc != SQLITE_OK || !db) { - if (db) sqlite3_close(db); - push_value(vm, make_int(0)); - break; - } - LibSqlHandle *h = libsql_reg_add(db); - if (!h) { sqlite3_close(db); push_value(vm, make_int(0)); break; } - push_value(vm, make_int(h->id)); -#else - Value v = pop_value(vm); free_value(v); + Value vpath = pop_value(vm); + char *path = value_to_string_alloc(&vpath); + free_value(vpath); + if (!path) { push_value(vm, make_int(0)); -#endif break; + } + sqlite3 *db = NULL; + int rc = sqlite3_open(path, &db); + free(path); + if (rc != SQLITE_OK || !db) { + if (db) sqlite3_close(db); + push_value(vm, make_int(0)); + break; + } + LibSqlHandle *h = libsql_reg_add(db); + if (!h) { + sqlite3_close(db); + push_value(vm, make_int(0)); + break; + } + push_value(vm, make_int(h->id)); +#else + Value v = pop_value(vm); + free_value(v); + push_value(vm, make_int(0)); +#endif + break; } diff --git a/src/vm/libsql/query.c b/src/vm/libsql/query.c index 559c6f4..54c356e 100644 --- a/src/vm/libsql/query.c +++ b/src/vm/libsql/query.c @@ -14,47 +14,63 @@ */ case OP_LIBSQL_QUERY: { #ifdef FUN_WITH_LIBSQL - Value vsql = pop_value(vm); - Value vh = pop_value(vm); - int hid = (int)vh.i; - char *sql = value_to_string_alloc(&vsql); - free_value(vh); - free_value(vsql); - LibSqlHandle *h = libsql_reg_get(hid); - if (!h || !h->db || !sql) { if (sql) free(sql); push_value(vm, make_array_from_values(NULL, 0)); break; } - sqlite3_stmt *stmt = NULL; - if (sqlite3_prepare_v2(h->db, sql, -1, &stmt, NULL) != SQLITE_OK) { - free(sql); - push_value(vm, make_array_from_values(NULL, 0)); - break; - } - free(sql); - Value rows = make_array_from_values(NULL, 0); - int ncols = sqlite3_column_count(stmt); - while (sqlite3_step(stmt) == SQLITE_ROW) { - Value row = make_map_empty(); - for (int i = 0; i < ncols; i++) { - const char *name = sqlite3_column_name(stmt, i); - int type = sqlite3_column_type(stmt, i); - Value kv; - switch (type) { - case SQLITE_INTEGER: kv = make_int((int64_t)sqlite3_column_int64(stmt, i)); break; - case SQLITE_FLOAT: kv = make_float(sqlite3_column_double(stmt, i)); break; - case SQLITE_TEXT: kv = make_string((const char*)sqlite3_column_text(stmt, i)); break; - case SQLITE_NULL: kv = make_nil(); break; - default: kv = make_nil(); break; /* ignore blobs for now */ - } - (void)map_set(&row, name ? name : "", kv); - } - (void)array_push(&rows, row); - /* Do NOT free 'row' here; owned by rows array. */ - } - sqlite3_finalize(stmt); - push_value(vm, rows); -#else - Value v1 = pop_value(vm); free_value(v1); - Value v2 = pop_value(vm); free_value(v2); + Value vsql = pop_value(vm); + Value vh = pop_value(vm); + int hid = (int)vh.i; + char *sql = value_to_string_alloc(&vsql); + free_value(vh); + free_value(vsql); + LibSqlHandle *h = libsql_reg_get(hid); + if (!h || !h->db || !sql) { + if (sql) free(sql); push_value(vm, make_array_from_values(NULL, 0)); -#endif break; + } + sqlite3_stmt *stmt = NULL; + if (sqlite3_prepare_v2(h->db, sql, -1, &stmt, NULL) != SQLITE_OK) { + free(sql); + push_value(vm, make_array_from_values(NULL, 0)); + break; + } + free(sql); + Value rows = make_array_from_values(NULL, 0); + int ncols = sqlite3_column_count(stmt); + while (sqlite3_step(stmt) == SQLITE_ROW) { + Value row = make_map_empty(); + for (int i = 0; i < ncols; i++) { + const char *name = sqlite3_column_name(stmt, i); + int type = sqlite3_column_type(stmt, i); + Value kv; + switch (type) { + case SQLITE_INTEGER: + kv = make_int((int64_t)sqlite3_column_int64(stmt, i)); + break; + case SQLITE_FLOAT: + kv = make_float(sqlite3_column_double(stmt, i)); + break; + case SQLITE_TEXT: + kv = make_string((const char *)sqlite3_column_text(stmt, i)); + break; + case SQLITE_NULL: + kv = make_nil(); + break; + default: + kv = make_nil(); + break; /* ignore blobs for now */ + } + (void)map_set(&row, name ? name : "", kv); + } + (void)array_push(&rows, row); + /* Do NOT free 'row' here; owned by rows array. */ + } + sqlite3_finalize(stmt); + push_value(vm, rows); +#else + Value v1 = pop_value(vm); + free_value(v1); + Value v2 = pop_value(vm); + free_value(v2); + push_value(vm, make_array_from_values(NULL, 0)); +#endif + break; } diff --git a/src/vm/line.c b/src/vm/line.c index 6728109..540ce19 100644 --- a/src/vm/line.c +++ b/src/vm/line.c @@ -1,5 +1,5 @@ /** -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -8,7 +8,7 @@ */ case OP_LINE: { - /* operand holds the source line number */ - vm->current_line = inst.operand; - break; + /* operand holds the source line number */ + vm->current_line = inst.operand; + break; } diff --git a/src/vm/logic/and.c b/src/vm/logic/and.c index d8cafda..32e1fb7 100644 --- a/src/vm/logic/and.c +++ b/src/vm/logic/and.c @@ -8,7 +8,7 @@ */ /** -* @file and.c + * @file and.c * @brief Implements the OP_AND opcode for logical AND in the VM. * * This file handles the OP_AND instruction, which performs a logical AND operation @@ -32,11 +32,11 @@ */ case OP_AND: { - Value b = pop_value(vm); - Value a = pop_value(vm); - int res = value_is_truthy(&a) && value_is_truthy(&b); - free_value(a); - free_value(b); - push_value(vm, make_bool(res)); - break; + Value b = pop_value(vm); + Value a = pop_value(vm); + int res = value_is_truthy(&a) && value_is_truthy(&b); + free_value(a); + free_value(b); + push_value(vm, make_bool(res)); + break; } diff --git a/src/vm/logic/eq.c b/src/vm/logic/eq.c index 9d6bf6c..26fda7c 100644 --- a/src/vm/logic/eq.c +++ b/src/vm/logic/eq.c @@ -8,7 +8,7 @@ */ /** -* @file eq.c + * @file eq.c * @brief Implements the OP_EQ opcode for equality comparison in the VM. * * This file handles the OP_EQ instruction, which checks if two values are equal. @@ -32,29 +32,42 @@ */ case OP_EQ: { - Value b = pop_value(vm); - Value a = pop_value(vm); - int eq = 0; - if (a.type == b.type) { - switch (a.type) { - case VAL_INT: eq = (a.i == b.i); break; - case VAL_BOOL: eq = ((a.i != 0) == (b.i != 0)); break; - case VAL_STRING: eq = (a.s && b.s) ? (strcmp(a.s, b.s) == 0) : (a.s == b.s); break; - case VAL_FUNCTION: eq = (a.fn == b.fn); break; - case VAL_NIL: eq = 1; break; - default: eq = 0; break; - } - } else { - /* interop: bool vs int (0/1) */ - if ((a.type == VAL_BOOL && b.type == VAL_INT) || (a.type == VAL_INT && b.type == VAL_BOOL)) { - int ai = (a.type == VAL_BOOL) ? (a.i != 0) : (a.i != 0); - int bi = (b.type == VAL_BOOL) ? (b.i != 0) : (b.i != 0); - eq = (ai == bi); - } else { - eq = 0; - } + Value b = pop_value(vm); + Value a = pop_value(vm); + int eq = 0; + if (a.type == b.type) { + switch (a.type) { + case VAL_INT: + eq = (a.i == b.i); + break; + case VAL_BOOL: + eq = ((a.i != 0) == (b.i != 0)); + break; + case VAL_STRING: + eq = (a.s && b.s) ? (strcmp(a.s, b.s) == 0) : (a.s == b.s); + break; + case VAL_FUNCTION: + eq = (a.fn == b.fn); + break; + case VAL_NIL: + eq = 1; + break; + default: + eq = 0; + break; } - push_value(vm, make_bool(eq)); - free_value(a); free_value(b); - break; + } else { + /* interop: bool vs int (0/1) */ + if ((a.type == VAL_BOOL && b.type == VAL_INT) || (a.type == VAL_INT && b.type == VAL_BOOL)) { + int ai = (a.type == VAL_BOOL) ? (a.i != 0) : (a.i != 0); + int bi = (b.type == VAL_BOOL) ? (b.i != 0) : (b.i != 0); + eq = (ai == bi); + } else { + eq = 0; + } + } + push_value(vm, make_bool(eq)); + free_value(a); + free_value(b); + break; } diff --git a/src/vm/logic/gt.c b/src/vm/logic/gt.c index e2cbb5f..ef34675 100644 --- a/src/vm/logic/gt.c +++ b/src/vm/logic/gt.c @@ -8,7 +8,7 @@ */ /** -* @file gt.c + * @file gt.c * @brief Implements the OP_GT opcode for greater-than comparison in the VM. * * This file handles the OP_GT instruction, which checks if the first value is greater than the second. @@ -33,13 +33,14 @@ */ case OP_GT: { - Value b = pop_value(vm); - Value a = pop_value(vm); - if (a.type != VAL_INT || b.type != VAL_INT) { - fprintf(stderr, "Runtime type error: GT expects ints\n"); - exit(1); - } - push_value(vm, make_int(a.i > b.i ? 1 : 0)); - free_value(a); free_value(b); - break; + Value b = pop_value(vm); + Value a = pop_value(vm); + if (a.type != VAL_INT || b.type != VAL_INT) { + fprintf(stderr, "Runtime type error: GT expects ints\n"); + exit(1); + } + push_value(vm, make_int(a.i > b.i ? 1 : 0)); + free_value(a); + free_value(b); + break; } diff --git a/src/vm/logic/gte.c b/src/vm/logic/gte.c index 254dbd4..b77989b 100644 --- a/src/vm/logic/gte.c +++ b/src/vm/logic/gte.c @@ -8,7 +8,7 @@ */ /** -* @file gte.c + * @file gte.c * @brief Implements the OP_GTE opcode for greater-than-or-equal comparison in the VM. * * This file handles the OP_GTE instruction, which checks if the first value is greater than or equal to the second. @@ -32,13 +32,14 @@ */ case OP_GTE: { - Value b = pop_value(vm); - Value a = pop_value(vm); - if (a.type != VAL_INT || b.type != VAL_INT) { - fprintf(stderr, "Runtime type error: GTE expects ints\n"); - exit(1); - } - push_value(vm, make_int(a.i >= b.i ? 1 : 0)); - free_value(a); free_value(b); - break; + Value b = pop_value(vm); + Value a = pop_value(vm); + if (a.type != VAL_INT || b.type != VAL_INT) { + fprintf(stderr, "Runtime type error: GTE expects ints\n"); + exit(1); + } + push_value(vm, make_int(a.i >= b.i ? 1 : 0)); + free_value(a); + free_value(b); + break; } diff --git a/src/vm/logic/lt.c b/src/vm/logic/lt.c index 3e233f0..291dec5 100644 --- a/src/vm/logic/lt.c +++ b/src/vm/logic/lt.c @@ -8,7 +8,7 @@ */ /** -* @file lt.c + * @file lt.c * @brief Implements the OP_LT opcode for less-than comparison in the VM. * * This file handles the OP_LT instruction, which checks if the first value is less than the second. @@ -32,16 +32,16 @@ */ case OP_LT: { - Value b = pop_value(vm); - Value a = pop_value(vm); - if (a.type != VAL_INT || b.type != VAL_INT) { - fprintf(stderr, "Runtime type error: LT expects ints, 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 ? 1 : 0); - free_value(a); - free_value(b); - push_value(vm, res); - break; + Value b = pop_value(vm); + Value a = pop_value(vm); + if (a.type != VAL_INT || b.type != VAL_INT) { + fprintf(stderr, "Runtime type error: LT expects ints, 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 ? 1 : 0); + free_value(a); + free_value(b); + push_value(vm, res); + break; } diff --git a/src/vm/logic/lte.c b/src/vm/logic/lte.c index eb8d7c9..7dff85d 100644 --- a/src/vm/logic/lte.c +++ b/src/vm/logic/lte.c @@ -8,7 +8,7 @@ */ /** -* @file lte.c + * @file lte.c * @brief Implements the OP_LTE opcode for less-than-or-equal comparison in the VM. * * This file handles the OP_LTE instruction, which checks if the first value is less than or equal to the second. @@ -32,15 +32,15 @@ */ case OP_LTE: { - Value b = pop_value(vm); - Value a = pop_value(vm); - if (a.type != VAL_INT || b.type != VAL_INT) { - fprintf(stderr, "Runtime type error: LTE expects ints\n"); - exit(1); - } - Value res = make_int(a.i <= b.i ? 1 : 0); - free_value(a); - free_value(b); - push_value(vm, res); - break; + Value b = pop_value(vm); + Value a = pop_value(vm); + if (a.type != VAL_INT || b.type != VAL_INT) { + fprintf(stderr, "Runtime type error: LTE expects ints\n"); + exit(1); + } + Value res = make_int(a.i <= b.i ? 1 : 0); + free_value(a); + free_value(b); + push_value(vm, res); + break; } diff --git a/src/vm/logic/neq.c b/src/vm/logic/neq.c index 6c58491..2bc30db 100644 --- a/src/vm/logic/neq.c +++ b/src/vm/logic/neq.c @@ -8,7 +8,7 @@ */ /** -* @file neq.c + * @file neq.c * @brief Implements the OP_NEQ opcode for inequality comparison in the VM. * * This file handles the OP_NEQ instruction, which checks if two values are not equal. @@ -32,29 +32,42 @@ */ case OP_NEQ: { - Value b = pop_value(vm); - Value a = pop_value(vm); - int neq = 1; - if (a.type == b.type) { - switch (a.type) { - case VAL_INT: neq = (a.i != b.i); break; - case VAL_BOOL: neq = ((a.i != 0) != (b.i != 0)); break; - case VAL_STRING: neq = (a.s && b.s) ? (strcmp(a.s, b.s) != 0) : (a.s != b.s); break; - case VAL_FUNCTION: neq = (a.fn != b.fn); break; - case VAL_NIL: neq = 0; break; - default: neq = 1; break; - } - } else { - /* interop: bool vs int (0/1) */ - if ((a.type == VAL_BOOL && b.type == VAL_INT) || (a.type == VAL_INT && b.type == VAL_BOOL)) { - int ai = (a.type == VAL_BOOL) ? (a.i != 0) : (a.i != 0); - int bi = (b.type == VAL_BOOL) ? (b.i != 0) : (b.i != 0); - neq = (ai != bi); - } else { - neq = 1; - } + Value b = pop_value(vm); + Value a = pop_value(vm); + int neq = 1; + if (a.type == b.type) { + switch (a.type) { + case VAL_INT: + neq = (a.i != b.i); + break; + case VAL_BOOL: + neq = ((a.i != 0) != (b.i != 0)); + break; + case VAL_STRING: + neq = (a.s && b.s) ? (strcmp(a.s, b.s) != 0) : (a.s != b.s); + break; + case VAL_FUNCTION: + neq = (a.fn != b.fn); + break; + case VAL_NIL: + neq = 0; + break; + default: + neq = 1; + break; } - push_value(vm, make_bool(neq)); - free_value(a); free_value(b); - break; + } else { + /* interop: bool vs int (0/1) */ + if ((a.type == VAL_BOOL && b.type == VAL_INT) || (a.type == VAL_INT && b.type == VAL_BOOL)) { + int ai = (a.type == VAL_BOOL) ? (a.i != 0) : (a.i != 0); + int bi = (b.type == VAL_BOOL) ? (b.i != 0) : (b.i != 0); + neq = (ai != bi); + } else { + neq = 1; + } + } + push_value(vm, make_bool(neq)); + free_value(a); + free_value(b); + break; } diff --git a/src/vm/logic/not.c b/src/vm/logic/not.c index 4c73f76..d23ceef 100644 --- a/src/vm/logic/not.c +++ b/src/vm/logic/not.c @@ -8,7 +8,7 @@ */ /** -* @file not.c + * @file not.c * @brief Implements the OP_NOT opcode for logical NOT in the VM. * * This file handles the OP_NOT instruction, which performs a logical NOT operation @@ -32,9 +32,9 @@ */ case OP_NOT: { - Value v = pop_value(vm); - int res = !value_is_truthy(&v); - free_value(v); - push_value(vm, make_bool(res)); - break; + Value v = pop_value(vm); + int res = !value_is_truthy(&v); + free_value(v); + push_value(vm, make_bool(res)); + break; } diff --git a/src/vm/logic/or.c b/src/vm/logic/or.c index a110409..3f7c8b6 100644 --- a/src/vm/logic/or.c +++ b/src/vm/logic/or.c @@ -8,7 +8,7 @@ */ /** -* @file or.c + * @file or.c * @brief Implements the OP_OR opcode for logical OR in the VM. * * This file handles the OP_OR instruction, which performs a logical OR operation @@ -32,11 +32,11 @@ */ case OP_OR: { - Value b = pop_value(vm); - Value a = pop_value(vm); - int res = value_is_truthy(&a) || value_is_truthy(&b); - free_value(a); - free_value(b); - push_value(vm, make_bool(res)); - break; + Value b = pop_value(vm); + Value a = pop_value(vm); + int res = value_is_truthy(&a) || value_is_truthy(&b); + free_value(a); + free_value(b); + push_value(vm, make_bool(res)); + break; } diff --git a/src/vm/maps/has_key.c b/src/vm/maps/has_key.c index 09be732..5b8ddb3 100644 --- a/src/vm/maps/has_key.c +++ b/src/vm/maps/has_key.c @@ -8,7 +8,7 @@ */ /** -* @file has_key.c + * @file has_key.c * @brief Implements the OP_HAS_KEY opcode for map key checking in the VM. * * This file handles the OP_HAS_KEY instruction, which checks if a map contains @@ -26,11 +26,15 @@ */ case OP_HAS_KEY: { - Value key = pop_value(vm); - Value m = pop_value(vm); - if (m.type != VAL_MAP || key.type != VAL_STRING) { fprintf(stderr, "HAS_KEY expects (map, string)\n"); exit(1); } - int ok = map_has(&m, key.s ? key.s : ""); - free_value(m); free_value(key); - push_value(vm, make_int(ok ? 1 : 0)); - break; + Value key = pop_value(vm); + Value m = pop_value(vm); + if (m.type != VAL_MAP || key.type != VAL_STRING) { + fprintf(stderr, "HAS_KEY expects (map, string)\n"); + exit(1); + } + int ok = map_has(&m, key.s ? key.s : ""); + free_value(m); + free_value(key); + push_value(vm, make_int(ok ? 1 : 0)); + break; } diff --git a/src/vm/maps/keys.c b/src/vm/maps/keys.c index 244fd00..84b66ad 100644 --- a/src/vm/maps/keys.c +++ b/src/vm/maps/keys.c @@ -8,7 +8,7 @@ */ /** -* @file keys.c + * @file keys.c * @brief Implements the OP_KEYS opcode for retrieving map keys in the VM. * * This file handles the OP_KEYS instruction, which retrieves the keys of a map @@ -32,10 +32,13 @@ */ case OP_KEYS: { - Value m = pop_value(vm); - if (m.type != VAL_MAP) { fprintf(stderr, "KEYS expects map\n"); exit(1); } - Value arr = map_keys_array(&m); - free_value(m); - push_value(vm, arr); - break; + Value m = pop_value(vm); + if (m.type != VAL_MAP) { + fprintf(stderr, "KEYS expects map\n"); + exit(1); + } + Value arr = map_keys_array(&m); + free_value(m); + push_value(vm, arr); + break; } diff --git a/src/vm/maps/make_map.c b/src/vm/maps/make_map.c index 24f9631..6bb199a 100644 --- a/src/vm/maps/make_map.c +++ b/src/vm/maps/make_map.c @@ -8,7 +8,7 @@ */ /** -* @file make_map.c + * @file make_map.c * @brief Implements the OP_MAKE_MAP opcode for creating maps in the VM. * * This file handles the OP_MAKE_MAP instruction, which pops `pairs` key-value pairs @@ -33,20 +33,26 @@ * @date 2025-10-16 */ - case OP_MAKE_MAP: { - int pairs = inst.operand; - if (pairs < 0) { fprintf(stderr, "MAKE_MAP invalid pair count\n"); exit(1); } - Value m = make_map_empty(); - for (int i = 0; i < pairs; ++i) { - Value val = pop_value(vm); - Value key = pop_value(vm); - if (key.type != VAL_STRING) { fprintf(stderr, "Map literal keys must be strings\n"); exit(1); } - if (!map_set(&m, key.s ? key.s : "", val)) { - fprintf(stderr, "Map literal set failed\n"); exit(1); - } - free_value(key); + int pairs = inst.operand; + if (pairs < 0) { + fprintf(stderr, "MAKE_MAP invalid pair count\n"); + exit(1); + } + Value m = make_map_empty(); + for (int i = 0; i < pairs; ++i) { + Value val = pop_value(vm); + Value key = pop_value(vm); + if (key.type != VAL_STRING) { + fprintf(stderr, "Map literal keys must be strings\n"); + exit(1); } - push_value(vm, m); - break; + if (!map_set(&m, key.s ? key.s : "", val)) { + fprintf(stderr, "Map literal set failed\n"); + exit(1); + } + free_value(key); + } + push_value(vm, m); + break; } diff --git a/src/vm/maps/values.c b/src/vm/maps/values.c index 47dba63..7dca58c 100644 --- a/src/vm/maps/values.c +++ b/src/vm/maps/values.c @@ -8,7 +8,7 @@ */ /** -* @file values.c + * @file values.c * @brief Implements the OP_VALUES opcode for retrieving map values in the VM. * * This file handles the OP_VALUES instruction, which retrieves the values of a map @@ -32,10 +32,13 @@ */ case OP_VALUES: { - Value m = pop_value(vm); - if (m.type != VAL_MAP) { fprintf(stderr, "VALUES expects map\n"); exit(1); } - Value arr = map_values_array(&m); - free_value(m); - push_value(vm, arr); - break; + Value m = pop_value(vm); + if (m.type != VAL_MAP) { + fprintf(stderr, "VALUES expects map\n"); + exit(1); + } + Value arr = map_values_array(&m); + free_value(m); + push_value(vm, arr); + break; } diff --git a/src/vm/math/abs.c b/src/vm/math/abs.c index ef9a5e3..e0a4671 100644 --- a/src/vm/math/abs.c +++ b/src/vm/math/abs.c @@ -8,7 +8,7 @@ */ /** -* @file abs.c + * @file abs.c * @brief Implements the OP_ABS opcode for absolute value in the VM. * * This file handles the OP_ABS instruction, which computes the absolute value @@ -26,11 +26,14 @@ */ case OP_ABS: { - Value x = pop_value(vm); - if (x.type != VAL_INT) { fprintf(stderr, "ABS expects int\n"); exit(1); } - int64_t v = x.i; - if (v < 0) v = -v; - push_value(vm, make_int(v)); - free_value(x); - break; + Value x = pop_value(vm); + if (x.type != VAL_INT) { + fprintf(stderr, "ABS expects int\n"); + exit(1); + } + int64_t v = x.i; + if (v < 0) v = -v; + push_value(vm, make_int(v)); + free_value(x); + break; } diff --git a/src/vm/math/ceil.c b/src/vm/math/ceil.c index bda8d29..04d71bc 100644 --- a/src/vm/math/ceil.c +++ b/src/vm/math/ceil.c @@ -10,34 +10,34 @@ */ /** -* @file ceil.c + * @file ceil.c * @brief Implements the OP_CEIL opcode using C99 math.h ceil(). */ #include case OP_CEIL: { - Value v = pop_value(vm); - if (v.type == VAL_INT) { - /* ceil(n) == n for integers */ - push_value(vm, make_int(v.i)); - free_value(v); - } else if (v.type == VAL_FLOAT) { - double r = ceil(v.d); - if (r >= (double)INT64_MIN && r <= (double)INT64_MAX) { - int64_t ii = (int64_t)r; - if ((double)ii == r) { - push_value(vm, make_int(ii)); - } else { - push_value(vm, make_float(r)); - } - } else { - push_value(vm, make_float(r)); - } - free_value(v); + Value v = pop_value(vm); + if (v.type == VAL_INT) { + /* ceil(n) == n for integers */ + push_value(vm, make_int(v.i)); + free_value(v); + } else if (v.type == VAL_FLOAT) { + double r = ceil(v.d); + if (r >= (double)INT64_MIN && r <= (double)INT64_MAX) { + int64_t ii = (int64_t)r; + if ((double)ii == r) { + push_value(vm, make_int(ii)); + } else { + push_value(vm, make_float(r)); + } } else { - fprintf(stderr, "Runtime type error: CEIL expects number, got %s\n", value_type_name(v.type)); - exit(1); + push_value(vm, make_float(r)); } - break; + free_value(v); + } else { + fprintf(stderr, "Runtime type error: CEIL expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; } diff --git a/src/vm/math/clamp.c b/src/vm/math/clamp.c index e4cfd8a..bb27104 100644 --- a/src/vm/math/clamp.c +++ b/src/vm/math/clamp.c @@ -8,7 +8,7 @@ */ /** -* @file clamp.c + * @file clamp.c * @brief Implements the OP_CLAMP opcode for value clamping in the VM. * * This file handles the OP_CLAMP instruction, which clamps a value between @@ -27,19 +27,19 @@ */ case OP_CLAMP: { - Value hi = pop_value(vm); - Value lo = pop_value(vm); - Value x = pop_value(vm); - if (x.type != VAL_INT || lo.type != VAL_INT || hi.type != VAL_INT) { - fprintf(stderr, "CLAMP expects ints\n"); - exit(1); - } - int64_t v = x.i; - if (v < lo.i) v = lo.i; - if (v > hi.i) v = hi.i; - push_value(vm, make_int(v)); - free_value(x); - free_value(lo); - free_value(hi); - break; + Value hi = pop_value(vm); + Value lo = pop_value(vm); + Value x = pop_value(vm); + if (x.type != VAL_INT || lo.type != VAL_INT || hi.type != VAL_INT) { + fprintf(stderr, "CLAMP expects ints\n"); + exit(1); + } + int64_t v = x.i; + if (v < lo.i) v = lo.i; + if (v > hi.i) v = hi.i; + push_value(vm, make_int(v)); + free_value(x); + free_value(lo); + free_value(hi); + break; } diff --git a/src/vm/math/cos.c b/src/vm/math/cos.c index b5651f0..fc71582 100644 --- a/src/vm/math/cos.c +++ b/src/vm/math/cos.c @@ -10,22 +10,22 @@ */ /** -* @file cos.c + * @file cos.c * @brief Implements the OP_COS opcode using C99 math.h cos(). */ #include case OP_COS: { - Value v = pop_value(vm); - if (v.type == VAL_INT || v.type == VAL_FLOAT) { - double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; - double r = cos(x); - push_value(vm, make_float(r)); - free_value(v); - } else { - fprintf(stderr, "Runtime type error: COS expects number, got %s\n", value_type_name(v.type)); - exit(1); - } - break; + Value v = pop_value(vm); + if (v.type == VAL_INT || v.type == VAL_FLOAT) { + double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; + double r = cos(x); + push_value(vm, make_float(r)); + free_value(v); + } else { + fprintf(stderr, "Runtime type error: COS expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; } diff --git a/src/vm/math/exp.c b/src/vm/math/exp.c index 8ad99c2..e15d8c1 100644 --- a/src/vm/math/exp.c +++ b/src/vm/math/exp.c @@ -10,22 +10,22 @@ */ /** -* @file exp.c + * @file exp.c * @brief Implements the OP_EXP opcode using C99 math.h exp(). */ #include case OP_EXP: { - Value v = pop_value(vm); - if (v.type == VAL_INT || v.type == VAL_FLOAT) { - double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; - double r = exp(x); - push_value(vm, make_float(r)); - free_value(v); - } else { - fprintf(stderr, "Runtime type error: EXP expects number, got %s\n", value_type_name(v.type)); - exit(1); - } - break; + Value v = pop_value(vm); + if (v.type == VAL_INT || v.type == VAL_FLOAT) { + double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; + double r = exp(x); + push_value(vm, make_float(r)); + free_value(v); + } else { + fprintf(stderr, "Runtime type error: EXP expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; } diff --git a/src/vm/math/floor.c b/src/vm/math/floor.c index 72e2440..ceaec7d 100644 --- a/src/vm/math/floor.c +++ b/src/vm/math/floor.c @@ -10,34 +10,34 @@ */ /** -* @file floor.c + * @file floor.c * @brief Implements the OP_FLOOR opcode using C99 math.h floor(). */ #include case OP_FLOOR: { - Value v = pop_value(vm); - if (v.type == VAL_INT) { - /* floor(n) == n for integers */ - push_value(vm, make_int(v.i)); - free_value(v); - } else if (v.type == VAL_FLOAT) { - double r = floor(v.d); - if (r >= (double)INT64_MIN && r <= (double)INT64_MAX) { - int64_t ii = (int64_t)r; - if ((double)ii == r) { - push_value(vm, make_int(ii)); - } else { - push_value(vm, make_float(r)); - } - } else { - push_value(vm, make_float(r)); - } - free_value(v); + Value v = pop_value(vm); + if (v.type == VAL_INT) { + /* floor(n) == n for integers */ + push_value(vm, make_int(v.i)); + free_value(v); + } else if (v.type == VAL_FLOAT) { + double r = floor(v.d); + if (r >= (double)INT64_MIN && r <= (double)INT64_MAX) { + int64_t ii = (int64_t)r; + if ((double)ii == r) { + push_value(vm, make_int(ii)); + } else { + push_value(vm, make_float(r)); + } } else { - fprintf(stderr, "Runtime type error: FLOOR expects number, got %s\n", value_type_name(v.type)); - exit(1); + push_value(vm, make_float(r)); } - break; + free_value(v); + } else { + fprintf(stderr, "Runtime type error: FLOOR expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; } diff --git a/src/vm/math/fmax.c b/src/vm/math/fmax.c index b8504ba..0da3de4 100644 --- a/src/vm/math/fmax.c +++ b/src/vm/math/fmax.c @@ -10,7 +10,7 @@ */ /** -* @file fmax.c + * @file fmax.c * @brief Implements the OP_FMAX opcode using C99 math.h fmax(). * Accepts int or float; follows IEEE-754 NaN handling per fmax. */ @@ -18,24 +18,28 @@ #include case OP_FMAX: { - Value b = pop_value(vm); - Value a = pop_value(vm); - if (!((a.type == VAL_INT || a.type == VAL_FLOAT) && (b.type == VAL_INT || b.type == VAL_FLOAT))) { - fprintf(stderr, "Runtime type error: FMAX expects numbers, got %s and %s\n", - value_type_name(a.type), value_type_name(b.type)); - exit(1); - } - double da = (a.type == VAL_FLOAT) ? a.d : (double)a.i; - double db = (b.type == VAL_FLOAT) ? b.d : (double)b.i; - double r = fmax(da, db); - Value out; - if (!isnan(r) && !isinf(r) && r >= (double)INT64_MIN && r <= (double)INT64_MAX) { - int64_t ii = (int64_t)r; - if ((double)ii == r) out = make_int(ii); else out = make_float(r); - } else { - out = make_float(r); - } - free_value(a); free_value(b); - push_value(vm, out); - break; + Value b = pop_value(vm); + Value a = pop_value(vm); + if (!((a.type == VAL_INT || a.type == VAL_FLOAT) && (b.type == VAL_INT || b.type == VAL_FLOAT))) { + fprintf(stderr, "Runtime type error: FMAX expects numbers, got %s and %s\n", + value_type_name(a.type), value_type_name(b.type)); + exit(1); + } + double da = (a.type == VAL_FLOAT) ? a.d : (double)a.i; + double db = (b.type == VAL_FLOAT) ? b.d : (double)b.i; + double r = fmax(da, db); + Value out; + if (!isnan(r) && !isinf(r) && r >= (double)INT64_MIN && r <= (double)INT64_MAX) { + int64_t ii = (int64_t)r; + if ((double)ii == r) + out = make_int(ii); + else + out = make_float(r); + } else { + out = make_float(r); + } + free_value(a); + free_value(b); + push_value(vm, out); + break; } diff --git a/src/vm/math/fmin.c b/src/vm/math/fmin.c index ded0719..a407975 100644 --- a/src/vm/math/fmin.c +++ b/src/vm/math/fmin.c @@ -10,7 +10,7 @@ */ /** -* @file fmin.c + * @file fmin.c * @brief Implements the OP_FMIN opcode using C99 math.h fmin(). * Accepts int or float; follows IEEE-754 NaN handling per fmin. */ @@ -18,24 +18,28 @@ #include case OP_FMIN: { - Value b = pop_value(vm); - Value a = pop_value(vm); - if (!((a.type == VAL_INT || a.type == VAL_FLOAT) && (b.type == VAL_INT || b.type == VAL_FLOAT))) { - fprintf(stderr, "Runtime type error: FMIN expects numbers, got %s and %s\n", - value_type_name(a.type), value_type_name(b.type)); - exit(1); - } - double da = (a.type == VAL_FLOAT) ? a.d : (double)a.i; - double db = (b.type == VAL_FLOAT) ? b.d : (double)b.i; - double r = fmin(da, db); - Value out; - if (!isnan(r) && !isinf(r) && r >= (double)INT64_MIN && r <= (double)INT64_MAX) { - int64_t ii = (int64_t)r; - if ((double)ii == r) out = make_int(ii); else out = make_float(r); - } else { - out = make_float(r); - } - free_value(a); free_value(b); - push_value(vm, out); - break; + Value b = pop_value(vm); + Value a = pop_value(vm); + if (!((a.type == VAL_INT || a.type == VAL_FLOAT) && (b.type == VAL_INT || b.type == VAL_FLOAT))) { + fprintf(stderr, "Runtime type error: FMIN expects numbers, got %s and %s\n", + value_type_name(a.type), value_type_name(b.type)); + exit(1); + } + double da = (a.type == VAL_FLOAT) ? a.d : (double)a.i; + double db = (b.type == VAL_FLOAT) ? b.d : (double)b.i; + double r = fmin(da, db); + Value out; + if (!isnan(r) && !isinf(r) && r >= (double)INT64_MIN && r <= (double)INT64_MAX) { + int64_t ii = (int64_t)r; + if ((double)ii == r) + out = make_int(ii); + else + out = make_float(r); + } else { + out = make_float(r); + } + free_value(a); + free_value(b); + push_value(vm, out); + break; } diff --git a/src/vm/math/gcd.c b/src/vm/math/gcd.c index 106a5e4..dbc1aa4 100644 --- a/src/vm/math/gcd.c +++ b/src/vm/math/gcd.c @@ -10,31 +10,37 @@ */ /** -* @file gcd.c + * @file gcd.c * @brief Implements the OP_GCD opcode for greatest common divisor. */ case OP_GCD: { - Value vb = pop_value(vm); - Value va = pop_value(vm); - if (!((va.type == VAL_INT) || (va.type == VAL_FLOAT)) || - !((vb.type == VAL_INT) || (vb.type == VAL_FLOAT))) { - fprintf(stderr, "Runtime type error: GCD expects numbers, got %s and %s\n", - value_type_name(va.type), value_type_name(vb.type)); - exit(1); - } - int64_t a = (va.type == VAL_INT) ? va.i : (int64_t)va.d; - int64_t b = (vb.type == VAL_INT) ? vb.i : (int64_t)vb.d; - if (a == INT64_MIN) a = (int64_t)INT64_MAX; else if (a < 0) a = -a; - if (b == INT64_MIN) b = (int64_t)INT64_MAX; else if (b < 0) b = -b; - while (b != 0) { - int64_t t = a % b; - a = b; - b = t; - } - Value res = make_int(a); - free_value(va); - free_value(vb); - push_value(vm, res); - break; + Value vb = pop_value(vm); + Value va = pop_value(vm); + if (!((va.type == VAL_INT) || (va.type == VAL_FLOAT)) || + !((vb.type == VAL_INT) || (vb.type == VAL_FLOAT))) { + fprintf(stderr, "Runtime type error: GCD expects numbers, got %s and %s\n", + value_type_name(va.type), value_type_name(vb.type)); + exit(1); + } + int64_t a = (va.type == VAL_INT) ? va.i : (int64_t)va.d; + int64_t b = (vb.type == VAL_INT) ? vb.i : (int64_t)vb.d; + if (a == INT64_MIN) + a = (int64_t)INT64_MAX; + else if (a < 0) + a = -a; + if (b == INT64_MIN) + b = (int64_t)INT64_MAX; + else if (b < 0) + b = -b; + while (b != 0) { + int64_t t = a % b; + a = b; + b = t; + } + Value res = make_int(a); + free_value(va); + free_value(vb); + push_value(vm, res); + break; } diff --git a/src/vm/math/isqrt.c b/src/vm/math/isqrt.c index 6c60f29..ba2b8e1 100644 --- a/src/vm/math/isqrt.c +++ b/src/vm/math/isqrt.c @@ -10,38 +10,39 @@ */ /** -* @file isqrt.c + * @file isqrt.c * @brief Implements the OP_ISQRT opcode for integer square root (floor). */ case OP_ISQRT: { - Value v = pop_value(vm); - if (!((v.type == VAL_INT) || (v.type == VAL_FLOAT))) { - fprintf(stderr, "Runtime type error: ISQRT expects number, got %s\n", value_type_name(v.type)); - exit(1); - } - int64_t a = (v.type == VAL_INT) ? v.i : (int64_t)v.d; - if (a <= 0) { - free_value(v); - push_value(vm, make_int(0)); - break; - } - /* Binary isqrt inline */ - uint64_t n = (uint64_t)a; - uint64_t x = 0; - uint64_t bit = (uint64_t)1 << 62; /* highest even bit set */ - while (bit > n) bit >>= 2; - while (bit != 0) { - if (n >= x + bit) { - n -= x + bit; - x = (x >> 1) + bit; - } else { - x >>= 1; - } - bit >>= 2; - } - int64_t r = (int64_t)x; + Value v = pop_value(vm); + if (!((v.type == VAL_INT) || (v.type == VAL_FLOAT))) { + fprintf(stderr, "Runtime type error: ISQRT expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + int64_t a = (v.type == VAL_INT) ? v.i : (int64_t)v.d; + if (a <= 0) { free_value(v); - push_value(vm, make_int(r)); + push_value(vm, make_int(0)); break; + } + /* Binary isqrt inline */ + uint64_t n = (uint64_t)a; + uint64_t x = 0; + uint64_t bit = (uint64_t)1 << 62; /* highest even bit set */ + while (bit > n) + bit >>= 2; + while (bit != 0) { + if (n >= x + bit) { + n -= x + bit; + x = (x >> 1) + bit; + } else { + x >>= 1; + } + bit >>= 2; + } + int64_t r = (int64_t)x; + free_value(v); + push_value(vm, make_int(r)); + break; } diff --git a/src/vm/math/lcm.c b/src/vm/math/lcm.c index b477c3e..b24e622 100644 --- a/src/vm/math/lcm.c +++ b/src/vm/math/lcm.c @@ -10,39 +10,48 @@ */ /** -* @file lcm.c + * @file lcm.c * @brief Implements the OP_LCM opcode for least common multiple. */ case OP_LCM: { - Value vb = pop_value(vm); - Value va = pop_value(vm); - if (!((va.type == VAL_INT) || (va.type == VAL_FLOAT)) || - !((vb.type == VAL_INT) || (vb.type == VAL_FLOAT))) { - fprintf(stderr, "Runtime type error: LCM expects numbers, got %s and %s\n", - value_type_name(va.type), value_type_name(vb.type)); - exit(1); - } - int64_t a = (va.type == VAL_INT) ? va.i : (int64_t)va.d; - int64_t b = (vb.type == VAL_INT) ? vb.i : (int64_t)vb.d; - if (a == INT64_MIN) a = (int64_t)INT64_MAX; else if (a < 0) a = -a; - if (b == INT64_MIN) b = (int64_t)INT64_MAX; else if (b < 0) b = -b; - if (a == 0 || b == 0) { - free_value(va); free_value(vb); - push_value(vm, make_int(0)); - break; - } - /* gcd(a,b) */ - int64_t x = a, y = b; - while (y != 0) { - int64_t t = x % y; x = y; y = t; - } - int64_t g = x; - /* lcm = (a/g)*b (attempt to reduce overflow) */ - int64_t l = (a / g) * b; - Value res = make_int(l); + Value vb = pop_value(vm); + Value va = pop_value(vm); + if (!((va.type == VAL_INT) || (va.type == VAL_FLOAT)) || + !((vb.type == VAL_INT) || (vb.type == VAL_FLOAT))) { + fprintf(stderr, "Runtime type error: LCM expects numbers, got %s and %s\n", + value_type_name(va.type), value_type_name(vb.type)); + exit(1); + } + int64_t a = (va.type == VAL_INT) ? va.i : (int64_t)va.d; + int64_t b = (vb.type == VAL_INT) ? vb.i : (int64_t)vb.d; + if (a == INT64_MIN) + a = (int64_t)INT64_MAX; + else if (a < 0) + a = -a; + if (b == INT64_MIN) + b = (int64_t)INT64_MAX; + else if (b < 0) + b = -b; + if (a == 0 || b == 0) { free_value(va); free_value(vb); - push_value(vm, res); + push_value(vm, make_int(0)); break; + } + /* gcd(a,b) */ + int64_t x = a, y = b; + while (y != 0) { + int64_t t = x % y; + x = y; + y = t; + } + int64_t g = x; + /* lcm = (a/g)*b (attempt to reduce overflow) */ + int64_t l = (a / g) * b; + Value res = make_int(l); + free_value(va); + free_value(vb); + push_value(vm, res); + break; } diff --git a/src/vm/math/log.c b/src/vm/math/log.c index e8bd665..b8afcc6 100644 --- a/src/vm/math/log.c +++ b/src/vm/math/log.c @@ -10,27 +10,27 @@ */ /** -* @file log.c + * @file log.c * @brief Implements the OP_LOG opcode using C99 math.h log() (natural logarithm). */ #include case OP_LOG: { - Value v = pop_value(vm); - if (v.type == VAL_INT || v.type == VAL_FLOAT) { - double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; - if (x <= 0.0) { - /* Domain error: return NaN to signal invalid input */ - push_value(vm, make_float(NAN)); - } else { - double r = log(x); - push_value(vm, make_float(r)); - } - free_value(v); + Value v = pop_value(vm); + if (v.type == VAL_INT || v.type == VAL_FLOAT) { + double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; + if (x <= 0.0) { + /* Domain error: return NaN to signal invalid input */ + push_value(vm, make_float(NAN)); } else { - fprintf(stderr, "Runtime type error: LOG expects number, got %s\n", value_type_name(v.type)); - exit(1); + double r = log(x); + push_value(vm, make_float(r)); } - break; + free_value(v); + } else { + fprintf(stderr, "Runtime type error: LOG expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; } diff --git a/src/vm/math/log10.c b/src/vm/math/log10.c index 0da07e1..c40f7f0 100644 --- a/src/vm/math/log10.c +++ b/src/vm/math/log10.c @@ -10,26 +10,26 @@ */ /** -* @file log10.c + * @file log10.c * @brief Implements the OP_LOG10 opcode using C99 math.h log10(). */ #include case OP_LOG10: { - Value v = pop_value(vm); - if (v.type == VAL_INT || v.type == VAL_FLOAT) { - double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; - if (x <= 0.0) { - push_value(vm, make_float(NAN)); - } else { - double r = log10(x); - push_value(vm, make_float(r)); - } - free_value(v); + Value v = pop_value(vm); + if (v.type == VAL_INT || v.type == VAL_FLOAT) { + double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; + if (x <= 0.0) { + push_value(vm, make_float(NAN)); } else { - fprintf(stderr, "Runtime type error: LOG10 expects number, got %s\n", value_type_name(v.type)); - exit(1); + double r = log10(x); + push_value(vm, make_float(r)); } - break; + free_value(v); + } else { + fprintf(stderr, "Runtime type error: LOG10 expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; } diff --git a/src/vm/math/max.c b/src/vm/math/max.c index 2426875..ec2e903 100644 --- a/src/vm/math/max.c +++ b/src/vm/math/max.c @@ -8,7 +8,7 @@ */ /** -* @file max.c + * @file max.c * @brief Implements the OP_MAX opcode for finding the maximum of two values in the VM. * * This file handles the OP_MAX instruction, which finds the maximum of two integer values. @@ -32,10 +32,14 @@ */ case OP_MAX: { - Value b = pop_value(vm); - Value a = pop_value(vm); - if (a.type != VAL_INT || b.type != VAL_INT) { fprintf(stderr, "MAX expects ints\n"); exit(1); } - push_value(vm, make_int(a.i > b.i ? a.i : b.i)); - free_value(a); free_value(b); - break; + Value b = pop_value(vm); + Value a = pop_value(vm); + if (a.type != VAL_INT || b.type != VAL_INT) { + fprintf(stderr, "MAX expects ints\n"); + exit(1); + } + push_value(vm, make_int(a.i > b.i ? a.i : b.i)); + free_value(a); + free_value(b); + break; } diff --git a/src/vm/math/min.c b/src/vm/math/min.c index b4e724a..7931382 100644 --- a/src/vm/math/min.c +++ b/src/vm/math/min.c @@ -8,7 +8,7 @@ */ /** -* @file min.c + * @file min.c * @brief Implements the OP_MIN opcode for finding the minimum of two values in the VM. * * This file handles the OP_MIN instruction, which finds the minimum of two integer values. @@ -32,10 +32,14 @@ */ case OP_MIN: { - Value b = pop_value(vm); - Value a = pop_value(vm); - if (a.type != VAL_INT || b.type != VAL_INT) { fprintf(stderr, "MIN expects ints\n"); exit(1); } - push_value(vm, make_int(a.i < b.i ? a.i : b.i)); - free_value(a); free_value(b); - break; + Value b = pop_value(vm); + Value a = pop_value(vm); + if (a.type != VAL_INT || b.type != VAL_INT) { + fprintf(stderr, "MIN expects ints\n"); + exit(1); + } + push_value(vm, make_int(a.i < b.i ? a.i : b.i)); + free_value(a); + free_value(b); + break; } diff --git a/src/vm/math/mod.c b/src/vm/math/mod.c index 0373e07..b0319e7 100644 --- a/src/vm/math/mod.c +++ b/src/vm/math/mod.c @@ -8,7 +8,7 @@ */ /** -* @file mod.c + * @file mod.c * @brief Implements the OP_MOD opcode for modulo operation in the VM. * * This file handles the OP_MOD instruction, which computes the modulo of two integer values. @@ -33,20 +33,20 @@ */ case OP_MOD: { - Value b = pop_value(vm); - Value a = pop_value(vm); - if (a.type != VAL_INT || b.type != VAL_INT) { - fprintf(stderr, "Runtime type error: MOD expects ints, 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: modulo by zero\n"); - exit(1); - } - Value res = make_int(a.i % b.i); - free_value(a); - free_value(b); - push_value(vm, res); - break; + Value b = pop_value(vm); + Value a = pop_value(vm); + if (a.type != VAL_INT || b.type != VAL_INT) { + fprintf(stderr, "Runtime type error: MOD expects ints, 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: modulo 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/math/pow.c b/src/vm/math/pow.c index 649cca1..0db1434 100644 --- a/src/vm/math/pow.c +++ b/src/vm/math/pow.c @@ -8,7 +8,7 @@ */ /** -* @file pow.c + * @file pow.c * @brief Implements the OP_POW opcode for exponentiation in the VM. * * This file handles the OP_POW instruction, which computes the power of two integer values. @@ -32,20 +32,26 @@ */ case OP_POW: { - Value b = pop_value(vm); - Value a = pop_value(vm); - if (a.type != VAL_INT || b.type != VAL_INT) { fprintf(stderr, "POW expects ints\n"); exit(1); } - int64_t base = a.i; - int64_t exp = b.i; - int64_t res = 1; - if (exp < 0) { res = 0; } else { - while (exp > 0) { - if (exp & 1) res *= base; - base *= base; - exp >>= 1; - } + Value b = pop_value(vm); + Value a = pop_value(vm); + if (a.type != VAL_INT || b.type != VAL_INT) { + fprintf(stderr, "POW expects ints\n"); + exit(1); + } + int64_t base = a.i; + int64_t exp = b.i; + int64_t res = 1; + if (exp < 0) { + res = 0; + } else { + while (exp > 0) { + if (exp & 1) res *= base; + base *= base; + exp >>= 1; } - push_value(vm, make_int(res)); - free_value(a); free_value(b); - break; + } + push_value(vm, make_int(res)); + free_value(a); + free_value(b); + break; } diff --git a/src/vm/math/random_int.c b/src/vm/math/random_int.c index f04660d..7f871a3 100644 --- a/src/vm/math/random_int.c +++ b/src/vm/math/random_int.c @@ -8,7 +8,7 @@ */ /** -* @file random_int.c + * @file random_int.c * @brief Implements the OP_RANDOM_INT opcode for generating random integers in the VM. * * This file handles the OP_RANDOM_INT instruction, which generates a random integer @@ -34,14 +34,23 @@ */ case OP_RANDOM_INT: { - Value hi = pop_value(vm); - Value lo = pop_value(vm); - if (lo.type != VAL_INT || hi.type != VAL_INT) { fprintf(stderr, "RANDOM_INT expects (int, int)\n"); exit(1); } - int64_t a = lo.i, b = hi.i; - if (b <= a) { push_value(vm, make_int((int64_t)a)); free_value(lo); free_value(hi); break; } - int64_t span = b - a; - int64_t r = (int64_t)(rand() % (span)); - push_value(vm, make_int(a + r)); - free_value(lo); free_value(hi); + Value hi = pop_value(vm); + Value lo = pop_value(vm); + if (lo.type != VAL_INT || hi.type != VAL_INT) { + fprintf(stderr, "RANDOM_INT expects (int, int)\n"); + exit(1); + } + int64_t a = lo.i, b = hi.i; + if (b <= a) { + push_value(vm, make_int((int64_t)a)); + free_value(lo); + free_value(hi); break; + } + int64_t span = b - a; + int64_t r = (int64_t)(rand() % (span)); + push_value(vm, make_int(a + r)); + free_value(lo); + free_value(hi); + break; } diff --git a/src/vm/math/random_seed.c b/src/vm/math/random_seed.c index d79a6c9..fe0bc34 100644 --- a/src/vm/math/random_seed.c +++ b/src/vm/math/random_seed.c @@ -8,7 +8,7 @@ */ /** -* @file random_seed.c + * @file random_seed.c * @brief Implements the OP_RANDOM_SEED opcode for seeding the random number generator in the VM. * * This file handles the OP_RANDOM_SEED instruction, which seeds the random number generator @@ -31,10 +31,13 @@ */ case OP_RANDOM_SEED: { - Value seed = pop_value(vm); - if (seed.type != VAL_INT) { fprintf(stderr, "RANDOM_SEED expects int\n"); exit(1); } - srand((unsigned int)seed.i); - free_value(seed); - push_value(vm, make_int(0)); - break; + Value seed = pop_value(vm); + if (seed.type != VAL_INT) { + fprintf(stderr, "RANDOM_SEED expects int\n"); + exit(1); + } + srand((unsigned int)seed.i); + free_value(seed); + push_value(vm, make_int(0)); + break; } diff --git a/src/vm/math/round.c b/src/vm/math/round.c index 3f830ff..42bbc76 100644 --- a/src/vm/math/round.c +++ b/src/vm/math/round.c @@ -10,7 +10,7 @@ */ /** -* @file round.c + * @file round.c * @brief Implements the OP_ROUND opcode using C99 math.h round(). * C99 round() rounds half away from zero. */ @@ -18,26 +18,26 @@ #include case OP_ROUND: { - Value v = pop_value(vm); - if (v.type == VAL_INT) { - push_value(vm, make_int(v.i)); - free_value(v); - } else if (v.type == VAL_FLOAT) { - double r = round(v.d); - if (r >= (double)INT64_MIN && r <= (double)INT64_MAX) { - int64_t ii = (int64_t)r; - if ((double)ii == r) { - push_value(vm, make_int(ii)); - } else { - push_value(vm, make_float(r)); - } - } else { - push_value(vm, make_float(r)); - } - free_value(v); + Value v = pop_value(vm); + if (v.type == VAL_INT) { + push_value(vm, make_int(v.i)); + free_value(v); + } else if (v.type == VAL_FLOAT) { + double r = round(v.d); + if (r >= (double)INT64_MIN && r <= (double)INT64_MAX) { + int64_t ii = (int64_t)r; + if ((double)ii == r) { + push_value(vm, make_int(ii)); + } else { + push_value(vm, make_float(r)); + } } else { - fprintf(stderr, "Runtime type error: ROUND expects number, got %s\n", value_type_name(v.type)); - exit(1); + push_value(vm, make_float(r)); } - break; + free_value(v); + } else { + fprintf(stderr, "Runtime type error: ROUND expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; } diff --git a/src/vm/math/sign.c b/src/vm/math/sign.c index e54a787..799abfc 100644 --- a/src/vm/math/sign.c +++ b/src/vm/math/sign.c @@ -10,24 +10,27 @@ */ /** -* @file sign.c + * @file sign.c * @brief Implements the OP_SIGN opcode returning -1, 0, or 1. */ case OP_SIGN: { - Value v = pop_value(vm); - int out = 0; - if (v.type == VAL_INT) { - out = (v.i > 0) - (v.i < 0); - } else if (v.type == VAL_FLOAT) { - if (v.d > 0.0) out = 1; - else if (v.d < 0.0) out = -1; - else out = 0; - } else { - fprintf(stderr, "Runtime type error: SIGN expects number, got %s\n", value_type_name(v.type)); - exit(1); - } - free_value(v); - push_value(vm, make_int(out)); - break; + Value v = pop_value(vm); + int out = 0; + if (v.type == VAL_INT) { + out = (v.i > 0) - (v.i < 0); + } else if (v.type == VAL_FLOAT) { + if (v.d > 0.0) + out = 1; + else if (v.d < 0.0) + out = -1; + else + out = 0; + } else { + fprintf(stderr, "Runtime type error: SIGN expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + free_value(v); + push_value(vm, make_int(out)); + break; } diff --git a/src/vm/math/sin.c b/src/vm/math/sin.c index aa989b2..9dee63f 100644 --- a/src/vm/math/sin.c +++ b/src/vm/math/sin.c @@ -10,22 +10,22 @@ */ /** -* @file sin.c + * @file sin.c * @brief Implements the OP_SIN opcode using C99 math.h sin(). */ #include case OP_SIN: { - Value v = pop_value(vm); - if (v.type == VAL_INT || v.type == VAL_FLOAT) { - double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; - double r = sin(x); - push_value(vm, make_float(r)); - free_value(v); - } else { - fprintf(stderr, "Runtime type error: SIN expects number, got %s\n", value_type_name(v.type)); - exit(1); - } - break; + Value v = pop_value(vm); + if (v.type == VAL_INT || v.type == VAL_FLOAT) { + double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; + double r = sin(x); + push_value(vm, make_float(r)); + free_value(v); + } else { + fprintf(stderr, "Runtime type error: SIN expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; } diff --git a/src/vm/math/sqrt.c b/src/vm/math/sqrt.c index aaffee2..ff773c7 100644 --- a/src/vm/math/sqrt.c +++ b/src/vm/math/sqrt.c @@ -10,36 +10,36 @@ */ /** -* @file sqrt.c + * @file sqrt.c * @brief Implements the OP_SQRT opcode using C99 math.h sqrt(). */ #include case OP_SQRT: { - Value v = pop_value(vm); - if (v.type == VAL_INT || v.type == VAL_FLOAT) { - double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; - if (x < 0.0) { - push_value(vm, make_float(NAN)); - } else { - double r = sqrt(x); - /* preserve int if exactly integral */ - if (r >= (double)INT64_MIN && r <= (double)INT64_MAX) { - int64_t ii = (int64_t)r; - if ((double)ii == r) { - push_value(vm, make_int(ii)); - } else { - push_value(vm, make_float(r)); - } - } else { - push_value(vm, make_float(r)); - } - } - free_value(v); + Value v = pop_value(vm); + if (v.type == VAL_INT || v.type == VAL_FLOAT) { + double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; + if (x < 0.0) { + push_value(vm, make_float(NAN)); } else { - fprintf(stderr, "Runtime type error: SQRT expects number, got %s\n", value_type_name(v.type)); - exit(1); + double r = sqrt(x); + /* preserve int if exactly integral */ + if (r >= (double)INT64_MIN && r <= (double)INT64_MAX) { + int64_t ii = (int64_t)r; + if ((double)ii == r) { + push_value(vm, make_int(ii)); + } else { + push_value(vm, make_float(r)); + } + } else { + push_value(vm, make_float(r)); + } } - break; + free_value(v); + } else { + fprintf(stderr, "Runtime type error: SQRT expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; } diff --git a/src/vm/math/tan.c b/src/vm/math/tan.c index dd7ede3..b64b5f4 100644 --- a/src/vm/math/tan.c +++ b/src/vm/math/tan.c @@ -10,22 +10,22 @@ */ /** -* @file tan.c + * @file tan.c * @brief Implements the OP_TAN opcode using C99 math.h tan(). */ #include case OP_TAN: { - Value v = pop_value(vm); - if (v.type == VAL_INT || v.type == VAL_FLOAT) { - double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; - double r = tan(x); - push_value(vm, make_float(r)); - free_value(v); - } else { - fprintf(stderr, "Runtime type error: TAN expects number, got %s\n", value_type_name(v.type)); - exit(1); - } - break; + Value v = pop_value(vm); + if (v.type == VAL_INT || v.type == VAL_FLOAT) { + double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; + double r = tan(x); + push_value(vm, make_float(r)); + free_value(v); + } else { + fprintf(stderr, "Runtime type error: TAN expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; } diff --git a/src/vm/math/trunc.c b/src/vm/math/trunc.c index 7442074..d353885 100644 --- a/src/vm/math/trunc.c +++ b/src/vm/math/trunc.c @@ -10,33 +10,33 @@ */ /** -* @file trunc.c + * @file trunc.c * @brief Implements the OP_TRUNC opcode using C99 math.h trunc(). */ #include case OP_TRUNC: { - Value v = pop_value(vm); - if (v.type == VAL_INT) { - push_value(vm, make_int(v.i)); - free_value(v); - } else if (v.type == VAL_FLOAT) { - double r = trunc(v.d); - if (r >= (double)INT64_MIN && r <= (double)INT64_MAX) { - int64_t ii = (int64_t)r; - if ((double)ii == r) { - push_value(vm, make_int(ii)); - } else { - push_value(vm, make_float(r)); - } - } else { - push_value(vm, make_float(r)); - } - free_value(v); + Value v = pop_value(vm); + if (v.type == VAL_INT) { + push_value(vm, make_int(v.i)); + free_value(v); + } else if (v.type == VAL_FLOAT) { + double r = trunc(v.d); + if (r >= (double)INT64_MIN && r <= (double)INT64_MAX) { + int64_t ii = (int64_t)r; + if ((double)ii == r) { + push_value(vm, make_int(ii)); + } else { + push_value(vm, make_float(r)); + } } else { - fprintf(stderr, "Runtime type error: TRUNC expects number, got %s\n", value_type_name(v.type)); - exit(1); + push_value(vm, make_float(r)); } - break; + free_value(v); + } else { + fprintf(stderr, "Runtime type error: TRUNC expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; } diff --git a/src/vm/notcurses/clear.c b/src/vm/notcurses/clear.c index 91bd390..eb67bd0 100644 --- a/src/vm/notcurses/clear.c +++ b/src/vm/notcurses/clear.c @@ -1,5 +1,5 @@ /** -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -12,16 +12,17 @@ /* NC_CLEAR */ case OP_NC_CLEAR: { #ifdef FUN_WITH_NOTCURSES - if (_fun_nc && _fun_nc_std) { - ncplane_erase(_fun_nc_std); - notcurses_render(_fun_nc); - push_value(vm, make_int(0)); - } else { - push_value(vm, make_int(-1)); - } -#else - (void)_fun_nc; (void)_fun_nc_std; + if (_fun_nc && _fun_nc_std) { + ncplane_erase(_fun_nc_std); + notcurses_render(_fun_nc); + push_value(vm, make_int(0)); + } else { push_value(vm, make_int(-1)); + } +#else + (void)_fun_nc; + (void)_fun_nc_std; + push_value(vm, make_int(-1)); #endif - break; + break; } diff --git a/src/vm/notcurses/common.h b/src/vm/notcurses/common.h index de6d035..b548bfa 100644 --- a/src/vm/notcurses/common.h +++ b/src/vm/notcurses/common.h @@ -19,5 +19,5 @@ struct notcurses; struct ncplane; /* Shared static state within vm.c translation unit (header is included into vm.c at file scope) */ -static struct notcurses* _fun_nc = NULL; -static struct ncplane* _fun_nc_std = NULL; +static struct notcurses *_fun_nc = NULL; +static struct ncplane *_fun_nc_std = NULL; diff --git a/src/vm/notcurses/draw_text.c b/src/vm/notcurses/draw_text.c index 8f7ff95..d4cce7d 100644 --- a/src/vm/notcurses/draw_text.c +++ b/src/vm/notcurses/draw_text.c @@ -11,29 +11,30 @@ /* NC_DRAW_TEXT */ case OP_NC_DRAW_TEXT: { - Value textv = pop_value(vm); - Value xv = pop_value(vm); - Value yv = pop_value(vm); - int x = (xv.type == VAL_INT ? (int)xv.i : (xv.type == VAL_FLOAT ? (int)xv.d : 0)); - int y = (yv.type == VAL_INT ? (int)yv.i : (yv.type == VAL_FLOAT ? (int)yv.d : 0)); - char *text = value_to_string_alloc(&textv); - free_value(textv); - free_value(xv); - free_value(yv); + Value textv = pop_value(vm); + Value xv = pop_value(vm); + Value yv = pop_value(vm); + int x = (xv.type == VAL_INT ? (int)xv.i : (xv.type == VAL_FLOAT ? (int)xv.d : 0)); + int y = (yv.type == VAL_INT ? (int)yv.i : (yv.type == VAL_FLOAT ? (int)yv.d : 0)); + char *text = value_to_string_alloc(&textv); + free_value(textv); + free_value(xv); + free_value(yv); #ifdef FUN_WITH_NOTCURSES - if (_fun_nc && _fun_nc_std && text) { - ncplane_putstr_yx(_fun_nc_std, y, x, text); - notcurses_render(_fun_nc); - free(text); - push_value(vm, make_int(0)); - } else { - if (text) free(text); - push_value(vm, make_int(-1)); - } -#else - (void)_fun_nc; (void)_fun_nc_std; + if (_fun_nc && _fun_nc_std && text) { + ncplane_putstr_yx(_fun_nc_std, y, x, text); + notcurses_render(_fun_nc); + free(text); + push_value(vm, make_int(0)); + } else { if (text) free(text); push_value(vm, make_int(-1)); + } +#else + (void)_fun_nc; + (void)_fun_nc_std; + if (text) free(text); + push_value(vm, make_int(-1)); #endif - break; + break; } diff --git a/src/vm/notcurses/getch.c b/src/vm/notcurses/getch.c index 29e5933..076409f 100644 --- a/src/vm/notcurses/getch.c +++ b/src/vm/notcurses/getch.c @@ -11,33 +11,35 @@ /* NC_GETCH */ case OP_NC_GETCH: { - Value to_ms_v = pop_value(vm); - int timeout_ms = (to_ms_v.type == VAL_INT ? (int)to_ms_v.i : (to_ms_v.type == VAL_FLOAT ? (int)to_ms_v.d : 0)); - free_value(to_ms_v); + Value to_ms_v = pop_value(vm); + int timeout_ms = (to_ms_v.type == VAL_INT ? (int)to_ms_v.i : (to_ms_v.type == VAL_FLOAT ? (int)to_ms_v.d : 0)); + free_value(to_ms_v); #ifdef FUN_WITH_NOTCURSES - if (_fun_nc) { - /* For simplicity, use blocking get for now when timeout<=0 */ - if (timeout_ms <= 0) { - ncinput ni; - uint32_t id = notcurses_get_blocking(_fun_nc, &ni); - push_value(vm, make_int((int)id)); - } else { - /* Polling approximation: render then nonblocking get once */ - ncinput ni; - uint32_t id = notcurses_get_nblock(_fun_nc, &ni); - if (id == 0) { - /* no input available now: return -1 to indicate timeout */ - push_value(vm, make_int(-1)); - } else { - push_value(vm, make_int((int)id)); - } - } + if (_fun_nc) { + /* For simplicity, use blocking get for now when timeout<=0 */ + if (timeout_ms <= 0) { + ncinput ni; + uint32_t id = notcurses_get_blocking(_fun_nc, &ni); + push_value(vm, make_int((int)id)); } else { + /* Polling approximation: render then nonblocking get once */ + ncinput ni; + uint32_t id = notcurses_get_nblock(_fun_nc, &ni); + if (id == 0) { + /* no input available now: return -1 to indicate timeout */ push_value(vm, make_int(-1)); + } else { + push_value(vm, make_int((int)id)); + } } -#else - (void)_fun_nc; (void)_fun_nc_std; (void)timeout_ms; + } else { push_value(vm, make_int(-1)); + } +#else + (void)_fun_nc; + (void)_fun_nc_std; + (void)timeout_ms; + push_value(vm, make_int(-1)); #endif - break; + break; } diff --git a/src/vm/notcurses/init.c b/src/vm/notcurses/init.c index af37dfe..4a31c40 100644 --- a/src/vm/notcurses/init.c +++ b/src/vm/notcurses/init.c @@ -12,16 +12,23 @@ /* NC_INIT */ case OP_NC_INIT: { #ifdef FUN_WITH_NOTCURSES - if (_fun_nc) { push_value(vm, make_int(1)); break; } - struct notcurses_options opts = {0}; - _fun_nc = notcurses_core_init(&opts, NULL); - if (!_fun_nc) { push_value(vm, make_int(0)); break; } - _fun_nc_std = notcurses_stdplane(_fun_nc); + if (_fun_nc) { push_value(vm, make_int(1)); -#else - (void)_fun_nc; (void)_fun_nc_std; - fprintf(stderr, "Notcurses support disabled at build time. Reconfigure with -DFUN_WITH_NOTCURSES=ON.\n"); - push_value(vm, make_int(0)); -#endif break; + } + struct notcurses_options opts = {0}; + _fun_nc = notcurses_core_init(&opts, NULL); + if (!_fun_nc) { + push_value(vm, make_int(0)); + break; + } + _fun_nc_std = notcurses_stdplane(_fun_nc); + push_value(vm, make_int(1)); +#else + (void)_fun_nc; + (void)_fun_nc_std; + fprintf(stderr, "Notcurses support disabled at build time. Reconfigure with -DFUN_WITH_NOTCURSES=ON.\n"); + push_value(vm, make_int(0)); +#endif + break; } diff --git a/src/vm/notcurses/shutdown.c b/src/vm/notcurses/shutdown.c index 6f1b06b..e7858ab 100644 --- a/src/vm/notcurses/shutdown.c +++ b/src/vm/notcurses/shutdown.c @@ -12,14 +12,15 @@ /* NC_SHUTDOWN */ case OP_NC_SHUTDOWN: { #ifdef FUN_WITH_NOTCURSES - if (_fun_nc) { - notcurses_stop(_fun_nc); - _fun_nc = NULL; - _fun_nc_std = NULL; - } + if (_fun_nc) { + notcurses_stop(_fun_nc); + _fun_nc = NULL; + _fun_nc_std = NULL; + } #else - (void)_fun_nc; (void)_fun_nc_std; + (void)_fun_nc; + (void)_fun_nc_std; #endif - push_value(vm, make_int(0)); - break; + push_value(vm, make_int(0)); + break; } diff --git a/src/vm/openssl/md5.c b/src/vm/openssl/md5.c index abc549f..ede69cd 100644 --- a/src/vm/openssl/md5.c +++ b/src/vm/openssl/md5.c @@ -9,19 +9,25 @@ * Added: 2026-02-19 */ - /** +/** * OpenSSL MD5 builtin */ case OP_OPENSSL_MD5: { - Value vdata = pop_value(vm); - char *s = value_to_string_alloc(&vdata); - free_value(vdata); - if (!s) { push_value(vm, make_string("")); break; } - char *hex = fun_openssl_md5_hex((const unsigned char*)s, strlen(s)); - free(s); - if (!hex) { push_value(vm, make_string("")); break; } - Value out = make_string(hex); - free(hex); - push_value(vm, out); + Value vdata = pop_value(vm); + char *s = value_to_string_alloc(&vdata); + free_value(vdata); + if (!s) { + push_value(vm, make_string("")); break; + } + char *hex = fun_openssl_md5_hex((const unsigned char *)s, strlen(s)); + free(s); + if (!hex) { + push_value(vm, make_string("")); + break; + } + Value out = make_string(hex); + free(hex); + push_value(vm, out); + break; } diff --git a/src/vm/openssl/ripemd160.c b/src/vm/openssl/ripemd160.c index 3831048..f974107 100644 --- a/src/vm/openssl/ripemd160.c +++ b/src/vm/openssl/ripemd160.c @@ -9,19 +9,25 @@ * Added: 2026-02-19 */ - /* +/* * OpenSSL RIPEMD-160 builtin */ case OP_OPENSSL_RIPEMD160: { - Value vdata = pop_value(vm); - char *s = value_to_string_alloc(&vdata); - free_value(vdata); - if (!s) { push_value(vm, make_string("")); break; } - char *hex = fun_openssl_ripemd160_hex((const unsigned char*)s, strlen(s)); - free(s); - if (!hex) { push_value(vm, make_string("")); break; } - Value out = make_string(hex); - free(hex); - push_value(vm, out); + Value vdata = pop_value(vm); + char *s = value_to_string_alloc(&vdata); + free_value(vdata); + if (!s) { + push_value(vm, make_string("")); break; + } + char *hex = fun_openssl_ripemd160_hex((const unsigned char *)s, strlen(s)); + free(s); + if (!hex) { + push_value(vm, make_string("")); + break; + } + Value out = make_string(hex); + free(hex); + push_value(vm, out); + break; } diff --git a/src/vm/openssl/sha256.c b/src/vm/openssl/sha256.c index 90ed330..964f9ad 100644 --- a/src/vm/openssl/sha256.c +++ b/src/vm/openssl/sha256.c @@ -13,15 +13,21 @@ * OpenSSL SHA-256 builtin */ case OP_OPENSSL_SHA256: { - Value vdata = pop_value(vm); - char *s = value_to_string_alloc(&vdata); - free_value(vdata); - if (!s) { push_value(vm, make_string("")); break; } - char *hex = fun_openssl_sha256_hex((const unsigned char*)s, strlen(s)); - free(s); - if (!hex) { push_value(vm, make_string("")); break; } - Value out = make_string(hex); - free(hex); - push_value(vm, out); + Value vdata = pop_value(vm); + char *s = value_to_string_alloc(&vdata); + free_value(vdata); + if (!s) { + push_value(vm, make_string("")); break; + } + char *hex = fun_openssl_sha256_hex((const unsigned char *)s, strlen(s)); + free(s); + if (!hex) { + push_value(vm, make_string("")); + break; + } + Value out = make_string(hex); + free(hex); + push_value(vm, out); + break; } diff --git a/src/vm/openssl/sha512.c b/src/vm/openssl/sha512.c index c004820..805561d 100644 --- a/src/vm/openssl/sha512.c +++ b/src/vm/openssl/sha512.c @@ -13,15 +13,21 @@ * OpenSSL SHA-512 builtin */ case OP_OPENSSL_SHA512: { - Value vdata = pop_value(vm); - char *s = value_to_string_alloc(&vdata); - free_value(vdata); - if (!s) { push_value(vm, make_string("")); break; } - char *hex = fun_openssl_sha512_hex((const unsigned char*)s, strlen(s)); - free(s); - if (!hex) { push_value(vm, make_string("")); break; } - Value out = make_string(hex); - free(hex); - push_value(vm, out); + Value vdata = pop_value(vm); + char *s = value_to_string_alloc(&vdata); + free_value(vdata); + if (!s) { + push_value(vm, make_string("")); break; + } + char *hex = fun_openssl_sha512_hex((const unsigned char *)s, strlen(s)); + free(s); + if (!hex) { + push_value(vm, make_string("")); + break; + } + Value out = make_string(hex); + free(hex); + push_value(vm, out); + break; } diff --git a/src/vm/os/clock_mono_ms.c b/src/vm/os/clock_mono_ms.c index 9abbaab..09d3c26 100644 --- a/src/vm/os/clock_mono_ms.c +++ b/src/vm/os/clock_mono_ms.c @@ -17,23 +17,23 @@ * Stack after: [int ms] */ -#include #include +#include case OP_CLOCK_MONO_MS: { - int64_t ms; + int64_t ms; #if defined(CLOCK_MONOTONIC) && !defined(_WIN32) - struct timespec ts; - if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { - ms = (int64_t)ts.tv_sec * 1000 + (int64_t)(ts.tv_nsec / 1000000); - } else { - time_t s = time(NULL); - ms = (int64_t)s * 1000; - } -#else + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { + ms = (int64_t)ts.tv_sec * 1000 + (int64_t)(ts.tv_nsec / 1000000); + } else { time_t s = time(NULL); ms = (int64_t)s * 1000; + } +#else + time_t s = time(NULL); + ms = (int64_t)s * 1000; #endif - push_value(vm, make_int(ms)); - break; + push_value(vm, make_int(ms)); + break; } diff --git a/src/vm/os/date_format.c b/src/vm/os/date_format.c index 1e05714..e8b7b33 100644 --- a/src/vm/os/date_format.c +++ b/src/vm/os/date_format.c @@ -1,5 +1,5 @@ /** -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -21,52 +21,53 @@ * - If types are wrong, prints error and pushes empty string to keep stack safe. */ -#include -#include #include +#include +#include case OP_DATE_FORMAT: { - Value fmt = pop_value(vm); - Value ms = pop_value(vm); - if (ms.type != VAL_INT || fmt.type != VAL_STRING) { - fprintf(stderr, "DATE_FORMAT expects (fmt:string, ms:int)\n"); - if (ms.type != VAL_NIL) free_value(ms); - if (fmt.type != VAL_NIL) free_value(fmt); - push_value(vm, make_string("")); - break; - } - time_t secs = (time_t)(ms.i / 1000); - struct tm tmv; + Value fmt = pop_value(vm); + Value ms = pop_value(vm); + if (ms.type != VAL_INT || fmt.type != VAL_STRING) { + fprintf(stderr, "DATE_FORMAT expects (fmt:string, ms:int)\n"); + if (ms.type != VAL_NIL) free_value(ms); + if (fmt.type != VAL_NIL) free_value(fmt); + push_value(vm, make_string("")); + break; + } + time_t secs = (time_t)(ms.i / 1000); + struct tm tmv; #if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 1 - localtime_r(&secs, &tmv); + localtime_r(&secs, &tmv); #else - struct tm *ptm = localtime(&secs); - if (!ptm) { - free_value(ms); free_value(fmt); - push_value(vm, make_string("")); - break; - } - tmv = *ptm; -#endif - /* Determine buffer size by attempting once with a reasonable size and retrying if needed */ - size_t cap = 128; - char *buf = (char*)malloc(cap); - size_t n = strftime(buf, cap, fmt.s, &tmv); - if (n == 0) { - /* try larger */ - cap = 512; - buf = (char*)realloc(buf, cap); - n = strftime(buf, cap, fmt.s, &tmv); - } - Value out; - if (n == 0) { - out = make_string(""); - } else { - out = make_string(buf); - } - free(buf); + struct tm *ptm = localtime(&secs); + if (!ptm) { free_value(ms); free_value(fmt); - push_value(vm, out); + push_value(vm, make_string("")); break; + } + tmv = *ptm; +#endif + /* Determine buffer size by attempting once with a reasonable size and retrying if needed */ + size_t cap = 128; + char *buf = (char *)malloc(cap); + size_t n = strftime(buf, cap, fmt.s, &tmv); + if (n == 0) { + /* try larger */ + cap = 512; + buf = (char *)realloc(buf, cap); + n = strftime(buf, cap, fmt.s, &tmv); + } + Value out; + if (n == 0) { + out = make_string(""); + } else { + out = make_string(buf); + } + free(buf); + free_value(ms); + free_value(fmt); + push_value(vm, out); + break; } diff --git a/src/vm/os/env.c b/src/vm/os/env.c index 25daf08..f48c759 100644 --- a/src/vm/os/env.c +++ b/src/vm/os/env.c @@ -1,5 +1,5 @@ /** -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -10,16 +10,16 @@ // Get environment variables of the operation system. case OP_ENV: { - Value key = pop_value(vm); - if (key.type != VAL_STRING) { - fprintf(stderr, "Runtime type error: ENV expects string name\n"); - free_value(key); - exit(1); - } - const char *name = key.s ? key.s : ""; - const char *val = getenv(name); - /* Return empty string if not set (consistent with read_file fallback style) */ - push_value(vm, make_string(val ? val : "")); + Value key = pop_value(vm); + if (key.type != VAL_STRING) { + fprintf(stderr, "Runtime type error: ENV expects string name\n"); free_value(key); - break; + exit(1); + } + const char *name = key.s ? key.s : ""; + const char *val = getenv(name); + /* Return empty string if not set (consistent with read_file fallback style) */ + push_value(vm, make_string(val ? val : "")); + free_value(key); + break; } diff --git a/src/vm/os/env_all.c b/src/vm/os/env_all.c index 61b76cc..f02ff0e 100644 --- a/src/vm/os/env_all.c +++ b/src/vm/os/env_all.c @@ -5,31 +5,31 @@ * Copyright 2025 Johannes Findeisen * Licensed under the terms of the Apache-2.0 license. * https://opensource.org/license/apache-2-0 - * + * * Added: 2025-12-28 */ // Get all environment variables of the operation system and push them as a map. case OP_ENV_ALL: { - extern char **environ; - Value m = make_map_empty(); - if (environ) { - for (char **env = environ; *env; ++env) { - char *entry = *env; - char *equals = strchr(entry, '='); - if (equals) { - size_t key_len = equals - entry; - char *key = malloc(key_len + 1); - if (key) { - memcpy(key, entry, key_len); - key[key_len] = '\0'; - map_set(&m, key, make_string(equals + 1)); - free(key); - } - } + extern char **environ; + Value m = make_map_empty(); + if (environ) { + for (char **env = environ; *env; ++env) { + char *entry = *env; + char *equals = strchr(entry, '='); + if (equals) { + size_t key_len = equals - entry; + char *key = malloc(key_len + 1); + if (key) { + memcpy(key, entry, key_len); + key[key_len] = '\0'; + map_set(&m, key, make_string(equals + 1)); + free(key); } + } } - push_value(vm, m); - break; + } + push_value(vm, m); + break; } diff --git a/src/vm/os/fun_version.c b/src/vm/os/fun_version.c index f2ca4b6..2f2cf5b 100644 --- a/src/vm/os/fun_version.c +++ b/src/vm/os/fun_version.c @@ -5,7 +5,7 @@ * Copyright 2025 Johannes Findeisen * Licensed under the terms of the Apache-2.0 license. * https://opensource.org/license/apache-2-0 - * + * * Added: 2025-12-28 */ @@ -15,6 +15,6 @@ case OP_FUN_VERSION: { #ifndef FUN_VERSION #define FUN_VERSION "0.0.0-dev" #endif - push_value(vm, make_string(FUN_VERSION)); - break; + push_value(vm, make_string(FUN_VERSION)); + break; } diff --git a/src/vm/os/list_dir.c b/src/vm/os/list_dir.c index 5976c60..b8c6727 100644 --- a/src/vm/os/list_dir.c +++ b/src/vm/os/list_dir.c @@ -10,40 +10,40 @@ */ case OP_OS_LIST_DIR: { - /* pops path string; pushes array of strings */ - Value pathv = pop_value(vm); - char *path = value_to_string_alloc(&pathv); - free_value(pathv); + /* pops path string; pushes array of strings */ + Value pathv = pop_value(vm); + char *path = value_to_string_alloc(&pathv); + free_value(pathv); - Value arr = make_array_from_values(NULL, 0); - if (path) { - /* - * Using 'ls -1' as a fallback to avoid dirent.h conflicts on some systems. - * We escape the path minimally for the shell. - */ - size_t slen = strlen(path) + 16; - char *cmd = (char*)malloc(slen); - if (cmd) { - snprintf(cmd, slen, "ls -1 \"%s\"", path); - FILE *fp = popen(cmd, "r"); - if (fp) { - char line[1024]; - while (fgets(line, sizeof(line), fp)) { - /* Strip trailing newline */ - size_t l = strlen(line); - if (l > 0 && line[l-1] == '\n') line[l-1] = '\0'; - if (l > 1 && line[l-2] == '\r') line[l-2] = '\0'; - - if (line[0] != '\0') { - array_push(&arr, make_string(line)); - } - } - pclose(fp); - } - free(cmd); + Value arr = make_array_from_values(NULL, 0); + if (path) { + /* + * Using 'ls -1' as a fallback to avoid dirent.h conflicts on some systems. + * We escape the path minimally for the shell. + */ + size_t slen = strlen(path) + 16; + char *cmd = (char *)malloc(slen); + if (cmd) { + snprintf(cmd, slen, "ls -1 \"%s\"", path); + FILE *fp = popen(cmd, "r"); + if (fp) { + char line[1024]; + while (fgets(line, sizeof(line), fp)) { + /* Strip trailing newline */ + size_t l = strlen(line); + if (l > 0 && line[l - 1] == '\n') line[l - 1] = '\0'; + if (l > 1 && line[l - 2] == '\r') line[l - 2] = '\0'; + + if (line[0] != '\0') { + array_push(&arr, make_string(line)); + } } - free(path); + pclose(fp); + } + free(cmd); } - push_value(vm, arr); - break; + free(path); + } + push_value(vm, arr); + break; } diff --git a/src/vm/os/proc_run.c b/src/vm/os/proc_run.c index f8c8d77..4e193ce 100644 --- a/src/vm/os/proc_run.c +++ b/src/vm/os/proc_run.c @@ -13,79 +13,81 @@ #include #include #ifdef _WIN32 - #define popen _popen - #define pclose _pclose +#define popen _popen +#define pclose _pclose #endif case OP_PROC_RUN: { - /* Pops command string; pushes map {"out": string, "code": int} */ - Value cmdv = pop_value(vm); - char *cmd = value_to_string_alloc(&cmdv); - free_value(cmdv); - if (!cmd) { - Value m = make_map_empty(); - map_set(&m, "out", make_string("")); - map_set(&m, "code", make_int(-1)); - push_value(vm, m); - break; - } - - /* Use popen to run via shell and capture stdout */ - FILE *fp = popen(cmd, "r"); - int exit_code = -1; - char *out = NULL; - size_t cap = 0, len = 0; - if (fp) { - cap = 4096; - out = (char*)malloc(cap); - if (!out) { - pclose(fp); - free(cmd); - Value m = make_map_empty(); - map_set(&m, "out", make_string("")); - map_set(&m, "code", make_int(-1)); - push_value(vm, m); - break; - } - int c; - while ((c = fgetc(fp)) != EOF) { - if (len + 1 >= cap) { - cap *= 2; - char *nb = (char*)realloc(out, cap); - if (!nb) { - free(out); - pclose(fp); - free(cmd); - Value m = make_map_empty(); - map_set(&m, "out", make_string("")); - map_set(&m, "code", make_int(-1)); - push_value(vm, m); - goto done_push; - } - out = nb; - } - out[len++] = (char)c; - } - out[len] = '\0'; - int status = pclose(fp); -#ifdef __unix__ - if (WIFEXITED(status)) exit_code = WEXITSTATUS(status); - else exit_code = -1; -#else - exit_code = status; -#endif - } else { - out = strdup(""); - exit_code = -1; - } - + /* Pops command string; pushes map {"out": string, "code": int} */ + Value cmdv = pop_value(vm); + char *cmd = value_to_string_alloc(&cmdv); + free_value(cmdv); + if (!cmd) { Value m = make_map_empty(); - map_set(&m, "out", make_string(out ? out : "")); - map_set(&m, "code", make_int(exit_code)); + map_set(&m, "out", make_string("")); + map_set(&m, "code", make_int(-1)); push_value(vm, m); + break; + } + + /* Use popen to run via shell and capture stdout */ + FILE *fp = popen(cmd, "r"); + int exit_code = -1; + char *out = NULL; + size_t cap = 0, len = 0; + if (fp) { + cap = 4096; + out = (char *)malloc(cap); + if (!out) { + pclose(fp); + free(cmd); + Value m = make_map_empty(); + map_set(&m, "out", make_string("")); + map_set(&m, "code", make_int(-1)); + push_value(vm, m); + break; + } + int c; + while ((c = fgetc(fp)) != EOF) { + if (len + 1 >= cap) { + cap *= 2; + char *nb = (char *)realloc(out, cap); + if (!nb) { + free(out); + pclose(fp); + free(cmd); + Value m = make_map_empty(); + map_set(&m, "out", make_string("")); + map_set(&m, "code", make_int(-1)); + push_value(vm, m); + goto done_push; + } + out = nb; + } + out[len++] = (char)c; + } + out[len] = '\0'; + int status = pclose(fp); +#ifdef __unix__ + if (WIFEXITED(status)) + exit_code = WEXITSTATUS(status); + else + exit_code = -1; +#else + exit_code = status; +#endif + } else { + out = strdup(""); + exit_code = -1; + } + + Value m = make_map_empty(); + map_set(&m, "out", make_string(out ? out : "")); + map_set(&m, "code", make_int(exit_code)); + push_value(vm, m); done_push: - if (out) free(out); - free(cmd); - break; + if (out) free(out); + free(cmd); + break; } diff --git a/src/vm/os/proc_system.c b/src/vm/os/proc_system.c index d3c0e90..a57ff4f 100644 --- a/src/vm/os/proc_system.c +++ b/src/vm/os/proc_system.c @@ -10,24 +10,27 @@ */ case OP_PROC_SYSTEM: { - /* Pops command string; pushes exit code number */ - Value cmdv = pop_value(vm); - char *cmd = value_to_string_alloc(&cmdv); - free_value(cmdv); - if (!cmd) { - push_value(vm, make_int(-1)); - break; - } - int status = system(cmd); - int code = -1; -#ifdef __unix__ - if (status == -1) code = -1; - else if (WIFEXITED(status)) code = WEXITSTATUS(status); - else code = -1; -#else - code = status; -#endif - push_value(vm, make_int(code)); - free(cmd); + /* Pops command string; pushes exit code number */ + Value cmdv = pop_value(vm); + char *cmd = value_to_string_alloc(&cmdv); + free_value(cmdv); + if (!cmd) { + push_value(vm, make_int(-1)); break; + } + int status = system(cmd); + int code = -1; +#ifdef __unix__ + if (status == -1) + code = -1; + else if (WIFEXITED(status)) + code = WEXITSTATUS(status); + else + code = -1; +#else + code = status; +#endif + push_value(vm, make_int(code)); + free(cmd); + break; } diff --git a/src/vm/os/random_number.c b/src/vm/os/random_number.c index adc81f1..88e0612 100644 --- a/src/vm/os/random_number.c +++ b/src/vm/os/random_number.c @@ -5,7 +5,7 @@ * Copyright 2025 Johannes Findeisen * Licensed under the terms of the Apache-2.0 license. * https://opensource.org/license/apache-2-0 - * + * * Added: 2025-12-12 */ @@ -22,121 +22,123 @@ /* Platform-specific headers guarded per OS to avoid leaking problematic macros */ #if defined(_WIN32) || defined(_WIN64) - #include - #include +#include +#include #elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) - #include - #include - /* On some BSDs, arc4random_buf might be hidden by _POSIX_C_SOURCE. */ - void arc4random_buf(void *, size_t); +#include +#include +/* On some BSDs, arc4random_buf might be hidden by _POSIX_C_SOURCE. */ +void arc4random_buf(void *, size_t); #elif defined(__unix__) - #if __has_include() - #include - #endif - #include - #include +#if __has_include() +#include +#endif +#include +#include #endif case OP_RANDOM_NUMBER: { - /* pop requested raw byte length */ - Value lv = pop_value(vm); - if (lv.type != VAL_INT) { - fprintf(stderr, "Runtime type error: random_number(len) expects integer length\n"); - free_value(lv); - /* For safety, push empty string so callers expecting a value won't underflow */ - push_value(vm, make_string("")); - break; - } - int64_t len = lv.i; - if (len < 0) { - fprintf(stderr, "random_number error: negative length (%" PRId64 ")\n", len); - free_value(lv); - push_value(vm, make_string("")); - break; - } - if (len == 0) { - free_value(lv); - push_value(vm, make_string("")); - break; - } + /* pop requested raw byte length */ + Value lv = pop_value(vm); + if (lv.type != VAL_INT) { + fprintf(stderr, "Runtime type error: random_number(len) expects integer length\n"); + free_value(lv); + /* For safety, push empty string so callers expecting a value won't underflow */ + push_value(vm, make_string("")); + break; + } + int64_t len = lv.i; + if (len < 0) { + fprintf(stderr, "random_number error: negative length (%" PRId64 ")\n", len); + free_value(lv); + push_value(vm, make_string("")); + break; + } + if (len == 0) { + free_value(lv); + push_value(vm, make_string("")); + break; + } - /* Cap to prevent excessive allocations (max 1 MiB raw -> 2 MiB hex) */ - const int64_t MAX_RAW = (1LL << 20); - if (len > MAX_RAW) { - fprintf(stderr, "random_number error: requested length too large (%" PRId64 ", max %" PRId64 ")\n", len, MAX_RAW); - free_value(lv); - push_value(vm, make_string("")); - break; - } + /* Cap to prevent excessive allocations (max 1 MiB raw -> 2 MiB hex) */ + const int64_t MAX_RAW = (1LL << 20); + if (len > MAX_RAW) { + fprintf(stderr, "random_number error: requested length too large (%" PRId64 ", max %" PRId64 ")\n", len, MAX_RAW); + free_value(lv); + push_value(vm, make_string("")); + break; + } - unsigned char *raw = (unsigned char*)malloc((size_t)len); - char *hex = (char*)malloc((size_t)len * 2 + 1); - if (!raw || !hex) { - if (raw) free(raw); - if (hex) free(hex); - free_value(lv); - fprintf(stderr, "Out of memory in random_number\n"); - exit(1); - } + unsigned char *raw = (unsigned char *)malloc((size_t)len); + char *hex = (char *)malloc((size_t)len * 2 + 1); + if (!raw || !hex) { + if (raw) free(raw); + if (hex) free(hex); + free_value(lv); + fprintf(stderr, "Out of memory in random_number\n"); + exit(1); + } - int ok = 0; + int ok = 0; - /* --- Fill raw with cryptographically secure random bytes from the OS --- */ + /* --- Fill raw with cryptographically secure random bytes from the OS --- */ #if defined(_WIN32) || defined(_WIN64) - { - /* Windows: use BCryptGenRandom */ - NTSTATUS st = BCryptGenRandom(NULL, raw, (ULONG)len, BCRYPT_USE_SYSTEM_PREFERRED_RNG); - ok = (st == 0); - } + { + /* Windows: use BCryptGenRandom */ + NTSTATUS st = BCryptGenRandom(NULL, raw, (ULONG)len, BCRYPT_USE_SYSTEM_PREFERRED_RNG); + ok = (st == 0); + } #elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) - { - arc4random_buf(raw, (size_t)len); - ok = 1; - } + { + arc4random_buf(raw, (size_t)len); + ok = 1; + } #elif defined(__unix__) - { - /* Prefer getrandom if available, otherwise /dev/urandom */ - #if __has_include() - ssize_t n = getrandom(raw, (size_t)len, 0); - ok = (n == (ssize_t)len); - #else - ok = 0; - #endif - if (!ok) { - int fd = open("/dev/urandom", O_RDONLY); - if (fd >= 0) { - size_t off = 0; ssize_t n; - while (off < (size_t)len && (n = read(fd, raw + off, (size_t)len - off)) > 0) off += (size_t)n; - close(fd); - ok = (off == (size_t)len); - } - } - } + { +/* Prefer getrandom if available, otherwise /dev/urandom */ +#if __has_include() + ssize_t n = getrandom(raw, (size_t)len, 0); + ok = (n == (ssize_t)len); #else ok = 0; #endif - if (!ok) { - free(raw); - free(hex); - free_value(lv); - fprintf(stderr, "random_number error: OS RNG unavailable or failed\n"); - exit(1); + int fd = open("/dev/urandom", O_RDONLY); + if (fd >= 0) { + size_t off = 0; + ssize_t n; + while (off < (size_t)len && (n = read(fd, raw + off, (size_t)len - off)) > 0) + off += (size_t)n; + close(fd); + ok = (off == (size_t)len); + } } + } +#else + ok = 0; +#endif - /* hex encode */ - static const char hexdig[] = "0123456789abcdef"; - for (int64_t i = 0; i < len; ++i) { - unsigned char b = raw[i]; - hex[2*i] = hexdig[(b >> 4) & 0xF]; - hex[2*i+1] = hexdig[b & 0xF]; - } - hex[len * 2] = '\0'; - - Value s = make_string(hex); + if (!ok) { free(raw); free(hex); free_value(lv); - push_value(vm, s); - break; + fprintf(stderr, "random_number error: OS RNG unavailable or failed\n"); + exit(1); + } + + /* hex encode */ + static const char hexdig[] = "0123456789abcdef"; + for (int64_t i = 0; i < len; ++i) { + unsigned char b = raw[i]; + hex[2 * i] = hexdig[(b >> 4) & 0xF]; + hex[2 * i + 1] = hexdig[b & 0xF]; + } + hex[len * 2] = '\0'; + + Value s = make_string(hex); + free(raw); + free(hex); + free_value(lv); + push_value(vm, s); + break; } diff --git a/src/vm/os/serial_close.c b/src/vm/os/serial_close.c index b03290d..19dfa5b 100644 --- a/src/vm/os/serial_close.c +++ b/src/vm/os/serial_close.c @@ -14,18 +14,18 @@ #endif case OP_SERIAL_CLOSE: { - /* Pops fd (int); returns 1/0 */ - Value fdv = pop_value(vm); - int ok = 0; + /* Pops fd (int); returns 1/0 */ + Value fdv = pop_value(vm); + int ok = 0; #ifdef __unix__ - if (fdv.type != VAL_INT) { - fprintf(stderr, "Runtime type error: serial_close expects (int fd)\n"); - } else { - int fd = (int)fdv.i; - if (close(fd) == 0) ok = 1; - } + if (fdv.type != VAL_INT) { + fprintf(stderr, "Runtime type error: serial_close expects (int fd)\n"); + } else { + int fd = (int)fdv.i; + if (close(fd) == 0) ok = 1; + } #endif - free_value(fdv); - push_value(vm, make_int(ok)); - break; + free_value(fdv); + push_value(vm, make_int(ok)); + break; } diff --git a/src/vm/os/serial_config.c b/src/vm/os/serial_config.c index 04994b9..811ad31 100644 --- a/src/vm/os/serial_config.c +++ b/src/vm/os/serial_config.c @@ -14,77 +14,86 @@ #endif case OP_SERIAL_CONFIG: { - /* Pops flow_control (int), stop_bits (int), parity (int), data_bits (int), fd (int); returns 1/0 */ - Value flowv = pop_value(vm); - Value stopv = pop_value(vm); - Value parityv = pop_value(vm); - Value datav = pop_value(vm); - Value fdv = pop_value(vm); - int ok = 0; + /* Pops flow_control (int), stop_bits (int), parity (int), data_bits (int), fd (int); returns 1/0 */ + Value flowv = pop_value(vm); + Value stopv = pop_value(vm); + Value parityv = pop_value(vm); + Value datav = pop_value(vm); + Value fdv = pop_value(vm); + int ok = 0; #ifdef __unix__ - if (flowv.type != VAL_INT || stopv.type != VAL_INT || parityv.type != VAL_INT || - datav.type != VAL_INT || fdv.type != VAL_INT) { - fprintf(stderr, "Runtime type error: serial_config expects (int fd, int data_bits, int parity, int stop_bits, int flow_control)\n"); - } else { - int fd = (int)fdv.i; - int data_bits = (int)datav.i; - int parity = (int)parityv.i; - int stop_bits = (int)stopv.i; - int flow = (int)flowv.i; + if (flowv.type != VAL_INT || stopv.type != VAL_INT || parityv.type != VAL_INT || + datav.type != VAL_INT || fdv.type != VAL_INT) { + fprintf(stderr, "Runtime type error: serial_config expects (int fd, int data_bits, int parity, int stop_bits, int flow_control)\n"); + } else { + int fd = (int)fdv.i; + int data_bits = (int)datav.i; + int parity = (int)parityv.i; + int stop_bits = (int)stopv.i; + int flow = (int)flowv.i; - struct termios options; - if (tcgetattr(fd, &options) == 0) { - // Data bits - options.c_cflag &= ~CSIZE; - switch (data_bits) { - case 5: options.c_cflag |= CS5; break; - case 6: options.c_cflag |= CS6; break; - case 7: options.c_cflag |= CS7; break; - case 8: default: options.c_cflag |= CS8; break; - } + struct termios options; + if (tcgetattr(fd, &options) == 0) { + // Data bits + options.c_cflag &= ~CSIZE; + switch (data_bits) { + case 5: + options.c_cflag |= CS5; + break; + case 6: + options.c_cflag |= CS6; + break; + case 7: + options.c_cflag |= CS7; + break; + case 8: + default: + options.c_cflag |= CS8; + break; + } - // Parity - switch (parity) { - case 0: // None - options.c_cflag &= ~PARENB; - break; - case 1: // Odd - options.c_cflag |= PARENB; - options.c_cflag |= PARODD; - break; - case 2: // Even - options.c_cflag |= PARENB; - options.c_cflag &= ~PARODD; - break; - } + // Parity + switch (parity) { + case 0: // None + options.c_cflag &= ~PARENB; + break; + case 1: // Odd + options.c_cflag |= PARENB; + options.c_cflag |= PARODD; + break; + case 2: // Even + options.c_cflag |= PARENB; + options.c_cflag &= ~PARODD; + break; + } - // Stop bits - if (stop_bits == 2) { - options.c_cflag |= CSTOPB; - } else { - options.c_cflag &= ~CSTOPB; - } + // Stop bits + if (stop_bits == 2) { + options.c_cflag |= CSTOPB; + } else { + options.c_cflag &= ~CSTOPB; + } - // Flow control + // Flow control #ifdef CRTSCTS - if (flow == 1) { // Hardware (RTS/CTS) - options.c_cflag |= CRTSCTS; - } else { - options.c_cflag &= ~CRTSCTS; - } + if (flow == 1) { // Hardware (RTS/CTS) + options.c_cflag |= CRTSCTS; + } else { + options.c_cflag &= ~CRTSCTS; + } #endif - if (tcsetattr(fd, TCSANOW, &options) == 0) { - ok = 1; - } - } + if (tcsetattr(fd, TCSANOW, &options) == 0) { + ok = 1; + } } + } #endif - free_value(flowv); - free_value(stopv); - free_value(parityv); - free_value(datav); - free_value(fdv); - push_value(vm, make_int(ok)); - break; + free_value(flowv); + free_value(stopv); + free_value(parityv); + free_value(datav); + free_value(fdv); + push_value(vm, make_int(ok)); + break; } diff --git a/src/vm/os/serial_open.c b/src/vm/os/serial_open.c index 1694df3..e07401c 100644 --- a/src/vm/os/serial_open.c +++ b/src/vm/os/serial_open.c @@ -76,67 +76,105 @@ #endif case OP_SERIAL_OPEN: { - /* Pops baud_rate (int), path (string); returns fd (int) or 0 */ - Value baudv = pop_value(vm); - Value pathv = pop_value(vm); - int fd = 0; + /* Pops baud_rate (int), path (string); returns fd (int) or 0 */ + Value baudv = pop_value(vm); + Value pathv = pop_value(vm); + int fd = 0; #ifdef __unix__ - if (baudv.type != VAL_INT || pathv.type != VAL_STRING) { - fprintf(stderr, "Runtime type error: serial_open expects (string path, int baud_rate)\n"); - free_value(baudv); - free_value(pathv); - push_value(vm, make_int(0)); - break; - } - - const char *path = pathv.s ? pathv.s : ""; - int baud = (int)baudv.i; - speed_t speed; - - switch (baud) { - case 50: speed = B50; break; - case 75: speed = B75; break; - case 110: speed = B110; break; - case 134: speed = B134; break; - case 150: speed = B150; break; - case 200: speed = B200; break; - case 300: speed = B300; break; - case 600: speed = B600; break; - case 1200: speed = B1200; break; - case 1800: speed = B1800; break; - case 2400: speed = B2400; break; - case 4800: speed = B4800; break; - case 9600: speed = B9600; break; - case 19200: speed = B19200; break; - case 38400: speed = B38400; break; - case 57600: speed = B57600; break; - case 115200: speed = B115200; break; - case 230400: speed = B230400; break; - default: speed = B9600; break; - } - - fd = open(path, O_RDWR | O_NOCTTY | O_NDELAY); - if (fd != -1) { - struct termios options; - tcgetattr(fd, &options); - cfsetispeed(&options, speed); - cfsetospeed(&options, speed); - options.c_cflag |= (CLOCAL | CREAD); - options.c_cflag &= ~PARENB; - options.c_cflag &= ~CSTOPB; - options.c_cflag &= ~CSIZE; - options.c_cflag |= CS8; - options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); - options.c_iflag &= ~(IXON | IXOFF | IXANY); - options.c_oflag &= ~OPOST; - tcsetattr(fd, TCSANOW, &options); - fcntl(fd, F_SETFL, 0); // block on read - } else { - fd = 0; - } -#endif + if (baudv.type != VAL_INT || pathv.type != VAL_STRING) { + fprintf(stderr, "Runtime type error: serial_open expects (string path, int baud_rate)\n"); free_value(baudv); free_value(pathv); - push_value(vm, make_int(fd > 0 ? fd : 0)); + push_value(vm, make_int(0)); break; + } + + const char *path = pathv.s ? pathv.s : ""; + int baud = (int)baudv.i; + speed_t speed; + + switch (baud) { + case 50: + speed = B50; + break; + case 75: + speed = B75; + break; + case 110: + speed = B110; + break; + case 134: + speed = B134; + break; + case 150: + speed = B150; + break; + case 200: + speed = B200; + break; + case 300: + speed = B300; + break; + case 600: + speed = B600; + break; + case 1200: + speed = B1200; + break; + case 1800: + speed = B1800; + break; + case 2400: + speed = B2400; + break; + case 4800: + speed = B4800; + break; + case 9600: + speed = B9600; + break; + case 19200: + speed = B19200; + break; + case 38400: + speed = B38400; + break; + case 57600: + speed = B57600; + break; + case 115200: + speed = B115200; + break; + case 230400: + speed = B230400; + break; + default: + speed = B9600; + break; + } + + fd = open(path, O_RDWR | O_NOCTTY | O_NDELAY); + if (fd != -1) { + struct termios options; + tcgetattr(fd, &options); + cfsetispeed(&options, speed); + cfsetospeed(&options, speed); + options.c_cflag |= (CLOCAL | CREAD); + options.c_cflag &= ~PARENB; + options.c_cflag &= ~CSTOPB; + options.c_cflag &= ~CSIZE; + options.c_cflag |= CS8; + options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + options.c_iflag &= ~(IXON | IXOFF | IXANY); + options.c_oflag &= ~OPOST; + tcsetattr(fd, TCSANOW, &options); + fcntl(fd, F_SETFL, 0); // block on read + } else { + fd = 0; + } +#endif + free_value(baudv); + free_value(pathv); + push_value(vm, make_int(fd > 0 ? fd : 0)); + break; } diff --git a/src/vm/os/serial_recv.c b/src/vm/os/serial_recv.c index 520bed3..71877b5 100644 --- a/src/vm/os/serial_recv.c +++ b/src/vm/os/serial_recv.c @@ -14,34 +14,34 @@ #endif case OP_SERIAL_RECV: { - /* Pops maxlen (int), fd (int); returns data (string) */ - Value maxv = pop_value(vm); - Value fdv = pop_value(vm); - char *out = NULL; + /* Pops maxlen (int), fd (int); returns data (string) */ + Value maxv = pop_value(vm); + Value fdv = pop_value(vm); + char *out = NULL; #ifdef __unix__ - if (fdv.type != VAL_INT || maxv.type != VAL_INT) { - fprintf(stderr, "Runtime type error: serial_recv expects (int fd, int maxlen)\n"); - } else { - int fd = (int)fdv.i; - int maxlen = (int)maxv.i; - if (maxlen <= 0) maxlen = 4096; - if (maxlen > 1<<20) maxlen = 1<<20; /* cap at 1MB */ - out = (char*)malloc((size_t)maxlen + 1); - if (out) { - ssize_t n = read(fd, out, (size_t)maxlen); - if (n <= 0) { - free(out); - out = NULL; - } else { - out[n] = '\0'; - } - } + if (fdv.type != VAL_INT || maxv.type != VAL_INT) { + fprintf(stderr, "Runtime type error: serial_recv expects (int fd, int maxlen)\n"); + } else { + int fd = (int)fdv.i; + int maxlen = (int)maxv.i; + if (maxlen <= 0) maxlen = 4096; + if (maxlen > 1 << 20) maxlen = 1 << 20; /* cap at 1MB */ + out = (char *)malloc((size_t)maxlen + 1); + if (out) { + ssize_t n = read(fd, out, (size_t)maxlen); + if (n <= 0) { + free(out); + out = NULL; + } else { + out[n] = '\0'; + } } + } #endif - free_value(maxv); - free_value(fdv); - Value s = make_string(out ? out : ""); - if (out) free(out); - push_value(vm, s); - break; + free_value(maxv); + free_value(fdv); + Value s = make_string(out ? out : ""); + if (out) free(out); + push_value(vm, s); + break; } diff --git a/src/vm/os/serial_send.c b/src/vm/os/serial_send.c index c679fcc..4e014be 100644 --- a/src/vm/os/serial_send.c +++ b/src/vm/os/serial_send.c @@ -14,23 +14,23 @@ #endif case OP_SERIAL_SEND: { - /* Pops data (string), fd (int); returns bytes sent (int) */ - Value datav = pop_value(vm); - Value fdv = pop_value(vm); - int sent = -1; + /* Pops data (string), fd (int); returns bytes sent (int) */ + Value datav = pop_value(vm); + Value fdv = pop_value(vm); + int sent = -1; #ifdef __unix__ - if (fdv.type != VAL_INT || datav.type != VAL_STRING) { - fprintf(stderr, "Runtime type error: serial_send expects (int fd, string data)\n"); - } else { - int fd = (int)fdv.i; - const char *buf = datav.s ? datav.s : ""; - size_t len = strlen(buf); - ssize_t n = write(fd, buf, len); - if (n >= 0) sent = (int)n; - } + if (fdv.type != VAL_INT || datav.type != VAL_STRING) { + fprintf(stderr, "Runtime type error: serial_send expects (int fd, string data)\n"); + } else { + int fd = (int)fdv.i; + const char *buf = datav.s ? datav.s : ""; + size_t len = strlen(buf); + ssize_t n = write(fd, buf, len); + if (n >= 0) sent = (int)n; + } #endif - free_value(datav); - free_value(fdv); - push_value(vm, make_int(sent)); - break; + free_value(datav); + free_value(fdv); + push_value(vm, make_int(sent)); + break; } diff --git a/src/vm/os/sleep_ms.c b/src/vm/os/sleep_ms.c index 3728efe..3d599b3 100644 --- a/src/vm/os/sleep_ms.c +++ b/src/vm/os/sleep_ms.c @@ -10,18 +10,18 @@ */ case OP_SLEEP_MS: { - Value ms = pop_value(vm); - if (ms.type != VAL_INT) { - fprintf(stderr, "Runtime type error: sleep(ms) expects Number (milliseconds)\n"); - free_value(ms); - /* push Nil so caller-side POP is safe */ - push_value(vm, make_nil()); - break; - } - long t = (long)ms.i; - if (t > 0) fun_sleep_ms(t); + Value ms = pop_value(vm); + if (ms.type != VAL_INT) { + fprintf(stderr, "Runtime type error: sleep(ms) expects Number (milliseconds)\n"); free_value(ms); - /* push Nil so statement POP does not underflow */ + /* push Nil so caller-side POP is safe */ push_value(vm, make_nil()); break; + } + long t = (long)ms.i; + if (t > 0) fun_sleep_ms(t); + free_value(ms); + /* push Nil so statement POP does not underflow */ + push_value(vm, make_nil()); + break; } diff --git a/src/vm/os/socket_close.c b/src/vm/os/socket_close.c index 409c43a..d1d6ca5 100644 --- a/src/vm/os/socket_close.c +++ b/src/vm/os/socket_close.c @@ -10,20 +10,20 @@ */ case OP_SOCK_CLOSE: { - /* Pops fd; returns 1/0 */ - Value fdv = pop_value(vm); - int ok = 0; + /* Pops fd; returns 1/0 */ + Value fdv = pop_value(vm); + int ok = 0; #ifdef __unix__ - if (fdv.type != VAL_INT) { - fprintf(stderr, "Runtime type error: sock_close expects (int fd)\n"); - free_value(fdv); - push_value(vm, make_int(0)); - break; - } - int fd = (int)fdv.i; - ok = (close(fd) == 0) ? 1 : 0; -#endif + if (fdv.type != VAL_INT) { + fprintf(stderr, "Runtime type error: sock_close expects (int fd)\n"); free_value(fdv); - push_value(vm, make_int(ok)); + push_value(vm, make_int(0)); break; + } + int fd = (int)fdv.i; + ok = (close(fd) == 0) ? 1 : 0; +#endif + free_value(fdv); + push_value(vm, make_int(ok)); + break; } diff --git a/src/vm/os/socket_recv.c b/src/vm/os/socket_recv.c index 35b12a5..b524123 100644 --- a/src/vm/os/socket_recv.c +++ b/src/vm/os/socket_recv.c @@ -10,36 +10,37 @@ */ case OP_SOCK_RECV: { - /* Pops maxlen, fd; pushes data string ("" on EOF/error) */ - Value maxv = pop_value(vm); - Value fdv = pop_value(vm); - char *out = NULL; + /* Pops maxlen, fd; pushes data string ("" on EOF/error) */ + Value maxv = pop_value(vm); + Value fdv = pop_value(vm); + char *out = NULL; #ifdef __unix__ - if (fdv.type != VAL_INT || maxv.type != VAL_INT) { - fprintf(stderr, "Runtime type error: sock_recv expects (int fd, int maxlen)\n"); - free_value(maxv); - free_value(fdv); - push_value(vm, make_string("")); - break; - } - int fd = (int)fdv.i; - int maxlen = (int)maxv.i; - if (maxlen <= 0) maxlen = 4096; - if (maxlen > 1<<20) maxlen = 1<<20; /* cap at 1MB */ - out = (char*)malloc((size_t)maxlen + 1); - if (out) { - ssize_t n = recv(fd, out, (size_t)maxlen, 0); - if (n <= 0) { - free(out); out = NULL; - } else { - out[n] = '\0'; - } - } -#endif + if (fdv.type != VAL_INT || maxv.type != VAL_INT) { + fprintf(stderr, "Runtime type error: sock_recv expects (int fd, int maxlen)\n"); free_value(maxv); free_value(fdv); - Value s = make_string(out ? out : ""); - if (out) free(out); - push_value(vm, s); + push_value(vm, make_string("")); break; + } + int fd = (int)fdv.i; + int maxlen = (int)maxv.i; + if (maxlen <= 0) maxlen = 4096; + if (maxlen > 1 << 20) maxlen = 1 << 20; /* cap at 1MB */ + out = (char *)malloc((size_t)maxlen + 1); + if (out) { + ssize_t n = recv(fd, out, (size_t)maxlen, 0); + if (n <= 0) { + free(out); + out = NULL; + } else { + out[n] = '\0'; + } + } +#endif + free_value(maxv); + free_value(fdv); + Value s = make_string(out ? out : ""); + if (out) free(out); + push_value(vm, s); + break; } diff --git a/src/vm/os/socket_send.c b/src/vm/os/socket_send.c index 817d6a9..6a8084d 100644 --- a/src/vm/os/socket_send.c +++ b/src/vm/os/socket_send.c @@ -10,26 +10,29 @@ */ case OP_SOCK_SEND: { - /* Pops data string, fd; pushes bytes sent (>=0) or -1 */ - Value datav = pop_value(vm); - Value fdv = pop_value(vm); - int sent = -1; + /* Pops data string, fd; pushes bytes sent (>=0) or -1 */ + Value datav = pop_value(vm); + Value fdv = pop_value(vm); + int sent = -1; #ifdef __unix__ - if (fdv.type != VAL_INT || datav.type != VAL_STRING) { - fprintf(stderr, "Runtime type error: sock_send expects (int fd, string data)\n"); - free_value(datav); - free_value(fdv); - push_value(vm, make_int(-1)); - break; - } - int fd = (int)fdv.i; - const char *buf = datav.s ? datav.s : ""; - size_t len = strlen(buf); - ssize_t n = send(fd, buf, len, 0); - if (n >= 0) sent = (int)n; else sent = -1; -#endif + if (fdv.type != VAL_INT || datav.type != VAL_STRING) { + fprintf(stderr, "Runtime type error: sock_send expects (int fd, string data)\n"); free_value(datav); free_value(fdv); - push_value(vm, make_int(sent)); + push_value(vm, make_int(-1)); break; + } + int fd = (int)fdv.i; + const char *buf = datav.s ? datav.s : ""; + size_t len = strlen(buf); + ssize_t n = send(fd, buf, len, 0); + if (n >= 0) + sent = (int)n; + else + sent = -1; +#endif + free_value(datav); + free_value(fdv); + push_value(vm, make_int(sent)); + break; } diff --git a/src/vm/os/socket_tcp_accept.c b/src/vm/os/socket_tcp_accept.c index b002f6c..a93a3da 100644 --- a/src/vm/os/socket_tcp_accept.c +++ b/src/vm/os/socket_tcp_accept.c @@ -10,21 +10,21 @@ */ case OP_SOCK_TCP_ACCEPT: { - /* Pops listen fd; pushes client fd (>0) or 0 */ - Value fdv = pop_value(vm); - int client = 0; + /* Pops listen fd; pushes client fd (>0) or 0 */ + Value fdv = pop_value(vm); + int client = 0; #ifdef __unix__ - if (fdv.type != VAL_INT) { - fprintf(stderr, "Runtime type error: tcp_accept expects (int listen_fd)\n"); - free_value(fdv); - push_value(vm, make_int(0)); - break; - } - int s = (int)fdv.i; - int c = accept(s, NULL, NULL); - if (c >= 0) client = c; -#endif + if (fdv.type != VAL_INT) { + fprintf(stderr, "Runtime type error: tcp_accept expects (int listen_fd)\n"); free_value(fdv); - push_value(vm, make_int(client > 0 ? client : 0)); + push_value(vm, make_int(0)); break; + } + int s = (int)fdv.i; + int c = accept(s, NULL, NULL); + if (c >= 0) client = c; +#endif + free_value(fdv); + push_value(vm, make_int(client > 0 ? client : 0)); + break; } diff --git a/src/vm/os/socket_tcp_connect.c b/src/vm/os/socket_tcp_connect.c index 8db7c2f..1a229b2 100644 --- a/src/vm/os/socket_tcp_connect.c +++ b/src/vm/os/socket_tcp_connect.c @@ -10,41 +10,44 @@ */ case OP_SOCK_TCP_CONNECT: { - /* Pops port, host; pushes fd (>0) or 0 */ - Value portv = pop_value(vm); - Value hostv = pop_value(vm); - int fd = 0; + /* Pops port, host; pushes fd (>0) or 0 */ + Value portv = pop_value(vm); + Value hostv = pop_value(vm); + int fd = 0; #ifdef __unix__ - if (hostv.type != VAL_STRING || portv.type != VAL_INT) { - fprintf(stderr, "Runtime type error: tcp_connect expects (string host, int port)\n"); - free_value(portv); - free_value(hostv); - push_value(vm, make_int(0)); - break; - } - char *host = value_to_string_alloc(&hostv); - int port = (int)portv.i; - if (host) { - char portstr[16]; - snprintf(portstr, sizeof(portstr), "%d", port); - struct addrinfo hints, *res = NULL, *rp; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - if (getaddrinfo(host, portstr, &hints, &res) == 0) { - for (rp = res; rp != NULL; rp = rp->ai_next) { - int s = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); - if (s < 0) continue; - if (connect(s, rp->ai_addr, rp->ai_addrlen) == 0) { fd = s; break; } - close(s); - } - if (res) freeaddrinfo(res); - } - free(host); - } -#endif + if (hostv.type != VAL_STRING || portv.type != VAL_INT) { + fprintf(stderr, "Runtime type error: tcp_connect expects (string host, int port)\n"); free_value(portv); free_value(hostv); - push_value(vm, make_int(fd > 0 ? fd : 0)); + push_value(vm, make_int(0)); break; + } + char *host = value_to_string_alloc(&hostv); + int port = (int)portv.i; + if (host) { + char portstr[16]; + snprintf(portstr, sizeof(portstr), "%d", port); + struct addrinfo hints, *res = NULL, *rp; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + if (getaddrinfo(host, portstr, &hints, &res) == 0) { + for (rp = res; rp != NULL; rp = rp->ai_next) { + int s = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (s < 0) continue; + if (connect(s, rp->ai_addr, rp->ai_addrlen) == 0) { + fd = s; + break; + } + close(s); + } + if (res) freeaddrinfo(res); + } + free(host); + } +#endif + free_value(portv); + free_value(hostv); + push_value(vm, make_int(fd > 0 ? fd : 0)); + break; } diff --git a/src/vm/os/socket_tcp_listen.c b/src/vm/os/socket_tcp_listen.c index 7d0c72e..09477d6 100644 --- a/src/vm/os/socket_tcp_listen.c +++ b/src/vm/os/socket_tcp_listen.c @@ -10,44 +10,44 @@ */ case OP_SOCK_TCP_LISTEN: { - /* Pops backlog, port; pushes listen fd (>0) or 0 */ - Value backlogv = pop_value(vm); - Value portv = pop_value(vm); - int fd = 0; + /* Pops backlog, port; pushes listen fd (>0) or 0 */ + Value backlogv = pop_value(vm); + Value portv = pop_value(vm); + int fd = 0; #ifdef __unix__ - if (portv.type != VAL_INT || backlogv.type != VAL_INT) { - fprintf(stderr, "Runtime type error: tcp_listen expects (int port, int backlog)\n"); - free_value(backlogv); - free_value(portv); - push_value(vm, make_int(0)); - break; - } - int port = (int)portv.i; - int backlog = (int)backlogv.i; - int s = socket(AF_INET, SOCK_STREAM, 0); - if (s >= 0) { - int yes = 1; - setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); - struct sockaddr_in addr; - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = htonl(INADDR_ANY); - addr.sin_port = htons((uint16_t)port); - if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) == 0) { - if (listen(s, backlog > 0 ? backlog : 1) == 0) { - fd = s; - } else { - close(s); - } - } else { - close(s); - } - } -#else - (void)fd; -#endif + if (portv.type != VAL_INT || backlogv.type != VAL_INT) { + fprintf(stderr, "Runtime type error: tcp_listen expects (int port, int backlog)\n"); free_value(backlogv); free_value(portv); - push_value(vm, make_int(fd > 0 ? fd : 0)); + push_value(vm, make_int(0)); break; + } + int port = (int)portv.i; + int backlog = (int)backlogv.i; + int s = socket(AF_INET, SOCK_STREAM, 0); + if (s >= 0) { + int yes = 1; + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons((uint16_t)port); + if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) == 0) { + if (listen(s, backlog > 0 ? backlog : 1) == 0) { + fd = s; + } else { + close(s); + } + } else { + close(s); + } + } +#else + (void)fd; +#endif + free_value(backlogv); + free_value(portv); + push_value(vm, make_int(fd > 0 ? fd : 0)); + break; } diff --git a/src/vm/os/socket_unix_connect.c b/src/vm/os/socket_unix_connect.c index 10cf8a9..d80b31b 100644 --- a/src/vm/os/socket_unix_connect.c +++ b/src/vm/os/socket_unix_connect.c @@ -1,5 +1,5 @@ /** -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -11,43 +11,43 @@ #include #ifdef __unix__ -#include #include +#include #include #include #endif case OP_SOCK_UNIX_CONNECT: { - /* Pops path; returns fd (>0) or 0 */ - Value pathv = pop_value(vm); - int fd = 0; + /* Pops path; returns fd (>0) or 0 */ + Value pathv = pop_value(vm); + int fd = 0; #ifdef __unix__ - if (pathv.type != VAL_STRING) { - fprintf(stderr, "Runtime type error: unix_connect expects (string path)\n"); - free_value(pathv); - push_value(vm, make_int(0)); - break; - } - char *path = value_to_string_alloc(&pathv); - if (path) { - int s = socket(AF_UNIX, SOCK_STREAM, 0); - if (s >= 0) { - struct sockaddr_un addr; - memset(&addr, 0, sizeof(addr)); - addr.sun_family = AF_UNIX; - size_t maxlen = sizeof(addr.sun_path) - 1; - strncpy(addr.sun_path, path, maxlen); - addr.sun_path[maxlen] = '\0'; - if (connect(s, (struct sockaddr*)&addr, sizeof(addr)) == 0) { - fd = s; - } else { - close(s); - } - } - free(path); - } -#endif + if (pathv.type != VAL_STRING) { + fprintf(stderr, "Runtime type error: unix_connect expects (string path)\n"); free_value(pathv); - push_value(vm, make_int(fd > 0 ? fd : 0)); + push_value(vm, make_int(0)); break; + } + char *path = value_to_string_alloc(&pathv); + if (path) { + int s = socket(AF_UNIX, SOCK_STREAM, 0); + if (s >= 0) { + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + size_t maxlen = sizeof(addr.sun_path) - 1; + strncpy(addr.sun_path, path, maxlen); + addr.sun_path[maxlen] = '\0'; + if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) == 0) { + fd = s; + } else { + close(s); + } + } + free(path); + } +#endif + free_value(pathv); + push_value(vm, make_int(fd > 0 ? fd : 0)); + break; } diff --git a/src/vm/os/socket_unix_listen.c b/src/vm/os/socket_unix_listen.c index e4d96ae..49739d1 100644 --- a/src/vm/os/socket_unix_listen.c +++ b/src/vm/os/socket_unix_listen.c @@ -10,45 +10,45 @@ */ case OP_SOCK_UNIX_LISTEN: { - /* Pops backlog, path; returns listen fd (>0) or 0 */ - Value backlogv = pop_value(vm); - Value pathv = pop_value(vm); - int fd = 0; + /* Pops backlog, path; returns listen fd (>0) or 0 */ + Value backlogv = pop_value(vm); + Value pathv = pop_value(vm); + int fd = 0; #ifdef __unix__ - if (pathv.type != VAL_STRING || backlogv.type != VAL_INT) { - fprintf(stderr, "Runtime type error: unix_listen expects (string path, int backlog)\n"); - free_value(backlogv); - free_value(pathv); - push_value(vm, make_int(0)); - break; - } - char *path = value_to_string_alloc(&pathv); - int backlog = (int)backlogv.i; - if (path) { - int s = socket(AF_UNIX, SOCK_STREAM, 0); - if (s >= 0) { - struct sockaddr_un addr; - memset(&addr, 0, sizeof(addr)); - addr.sun_family = AF_UNIX; - size_t maxlen = sizeof(addr.sun_path) - 1; - strncpy(addr.sun_path, path, maxlen); - addr.sun_path[maxlen] = '\0'; - unlink(addr.sun_path); /* best effort */ - if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) == 0) { - if (listen(s, backlog > 0 ? backlog : 1) == 0) { - fd = s; - } else { - close(s); - } - } else { - close(s); - } - } - free(path); - } -#endif + if (pathv.type != VAL_STRING || backlogv.type != VAL_INT) { + fprintf(stderr, "Runtime type error: unix_listen expects (string path, int backlog)\n"); free_value(backlogv); free_value(pathv); - push_value(vm, make_int(fd > 0 ? fd : 0)); + push_value(vm, make_int(0)); break; + } + char *path = value_to_string_alloc(&pathv); + int backlog = (int)backlogv.i; + if (path) { + int s = socket(AF_UNIX, SOCK_STREAM, 0); + if (s >= 0) { + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + size_t maxlen = sizeof(addr.sun_path) - 1; + strncpy(addr.sun_path, path, maxlen); + addr.sun_path[maxlen] = '\0'; + unlink(addr.sun_path); /* best effort */ + if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) == 0) { + if (listen(s, backlog > 0 ? backlog : 1) == 0) { + fd = s; + } else { + close(s); + } + } else { + close(s); + } + } + free(path); + } +#endif + free_value(backlogv); + free_value(pathv); + push_value(vm, make_int(fd > 0 ? fd : 0)); + break; } diff --git a/src/vm/os/thread_common.c b/src/vm/os/thread_common.c index 0f6caeb..87019bd 100644 --- a/src/vm/os/thread_common.c +++ b/src/vm/os/thread_common.c @@ -15,24 +15,24 @@ #include #ifdef _WIN32 - #include - typedef HANDLE fun_thread_handle_t; - typedef DWORD fun_thread_ret_t; - #define FUN_THREAD_CALL WINAPI - #define fun_sleep_ms(ms) Sleep((DWORD)(ms)) +#include +typedef HANDLE fun_thread_handle_t; +typedef DWORD fun_thread_ret_t; +#define FUN_THREAD_CALL WINAPI +#define fun_sleep_ms(ms) Sleep((DWORD)(ms)) #else - #include - #include - typedef pthread_t fun_thread_handle_t; - typedef void* fun_thread_ret_t; - #define FUN_THREAD_CALL - static inline void fun_sleep_ms(long ms) { - if (ms <= 0) return; - struct timespec ts; - ts.tv_sec = ms / 1000; - ts.tv_nsec = (ms % 1000) * 1000000L; - nanosleep(&ts, NULL); - } +#include +#include +typedef pthread_t fun_thread_handle_t; +typedef void *fun_thread_ret_t; +#define FUN_THREAD_CALL +static inline void fun_sleep_ms(long ms) { + if (ms <= 0) return; + struct timespec ts; + ts.tv_sec = ms / 1000; + ts.tv_nsec = (ms % 1000) * 1000000L; + nanosleep(&ts, NULL); +} #endif #ifndef FUN_MAX_THREADS @@ -40,12 +40,12 @@ #endif typedef struct { - fun_thread_handle_t handle; - int used; - int done; - Value result; /* owned */ + fun_thread_handle_t handle; + int used; + int done; + Value result; /* owned */ #ifdef _WIN32 - DWORD threadId; + DWORD threadId; #endif } FunThreadEntry; @@ -55,24 +55,33 @@ static FunThreadEntry g_threads[FUN_MAX_THREADS]; static CRITICAL_SECTION g_thr_lock; static int g_thr_lock_inited = 0; static void fun_thr_lock_init(void) { - if (!g_thr_lock_inited) { - InitializeCriticalSection(&g_thr_lock); - g_thr_lock_inited = 1; - } + if (!g_thr_lock_inited) { + InitializeCriticalSection(&g_thr_lock); + g_thr_lock_inited = 1; + } +} +static void fun_lock(void) { + if (!g_thr_lock_inited) fun_thr_lock_init(); + EnterCriticalSection(&g_thr_lock); +} +static void fun_unlock(void) { + LeaveCriticalSection(&g_thr_lock); } -static void fun_lock(void) { if (!g_thr_lock_inited) fun_thr_lock_init(); EnterCriticalSection(&g_thr_lock); } -static void fun_unlock(void) { LeaveCriticalSection(&g_thr_lock); } #else static pthread_mutex_t g_thr_lock = PTHREAD_MUTEX_INITIALIZER; -static void fun_lock(void) { pthread_mutex_lock(&g_thr_lock); } -static void fun_unlock(void) { pthread_mutex_unlock(&g_thr_lock); } +static void fun_lock(void) { + pthread_mutex_lock(&g_thr_lock); +} +static void fun_unlock(void) { + pthread_mutex_unlock(&g_thr_lock); +} #endif typedef struct { - Bytecode *fn; /* function to call */ - int argc; - Value *args; /* array of argc Values (owned by task, will be freed) */ - int slot; /* registry slot */ + Bytecode *fn; /* function to call */ + int argc; + Value *args; /* array of argc Values (owned by task, will be freed) */ + int slot; /* registry slot */ } FunTask; #ifdef _WIN32 @@ -81,181 +90,201 @@ static fun_thread_ret_t FUN_THREAD_CALL fun_thread_main(LPVOID param) static fun_thread_ret_t fun_thread_main(void *param) #endif { - FunTask *task = (FunTask*)param; - VM tvm; - vm_init(&tvm); + FunTask *task = (FunTask *)param; + VM tvm; + vm_init(&tvm); - /* Build wrapper: LOAD_CONST ; LOAD_CONST ...; CALL argc; HALT */ - Bytecode *wrap = bytecode_new(); - int cFn = bytecode_add_constant(wrap, copy_value(&(Value){ .type = VAL_FUNCTION, .fn = task->fn })); - bytecode_add_instruction(wrap, OP_LOAD_CONST, cFn); - for (int i = 0; i < task->argc; ++i) { - int cArg = bytecode_add_constant(wrap, deep_copy_value(&task->args[i])); - bytecode_add_instruction(wrap, OP_LOAD_CONST, cArg); - } - bytecode_add_instruction(wrap, OP_CALL, task->argc); - bytecode_add_instruction(wrap, OP_HALT, 0); + /* Build wrapper: LOAD_CONST ; LOAD_CONST ...; CALL argc; HALT */ + Bytecode *wrap = bytecode_new(); + int cFn = bytecode_add_constant(wrap, copy_value(&(Value){.type = VAL_FUNCTION, .fn = task->fn})); + bytecode_add_instruction(wrap, OP_LOAD_CONST, cFn); + for (int i = 0; i < task->argc; ++i) { + int cArg = bytecode_add_constant(wrap, deep_copy_value(&task->args[i])); + bytecode_add_instruction(wrap, OP_LOAD_CONST, cArg); + } + bytecode_add_instruction(wrap, OP_CALL, task->argc); + bytecode_add_instruction(wrap, OP_HALT, 0); - vm_run(&tvm, wrap); + vm_run(&tvm, wrap); - /* Take the top of stack as result if present, else Nil */ - Value res = make_nil(); - if (tvm.sp >= 0) { - res = deep_copy_value(&tvm.stack[tvm.sp]); - } + /* Take the top of stack as result if present, else Nil */ + Value res = make_nil(); + if (tvm.sp >= 0) { + res = deep_copy_value(&tvm.stack[tvm.sp]); + } - /* cleanup */ - for (int i = 0; i < task->argc; ++i) { - free_value(task->args[i]); - } - free(task->args); - bytecode_free(wrap); + /* cleanup */ + for (int i = 0; i < task->argc; ++i) { + free_value(task->args[i]); + } + free(task->args); + bytecode_free(wrap); - /* store result */ - fun_lock(); - g_threads[task->slot].result = res; - g_threads[task->slot].done = 1; - fun_unlock(); + /* store result */ + fun_lock(); + g_threads[task->slot].result = res; + g_threads[task->slot].done = 1; + fun_unlock(); - free(task); + free(task); #ifdef _WIN32 - return 0; + return 0; #else - return NULL; + return NULL; #endif } static int fun_alloc_thread_slot(void) { - fun_lock(); - int idx = -1; - for (int i = 0; i < FUN_MAX_THREADS; ++i) { - if (!g_threads[i].used) { g_threads[i].used = 1; g_threads[i].done = 0; g_threads[i].result = make_nil(); idx = i; break; } + fun_lock(); + int idx = -1; + for (int i = 0; i < FUN_MAX_THREADS; ++i) { + if (!g_threads[i].used) { + g_threads[i].used = 1; + g_threads[i].done = 0; + g_threads[i].result = make_nil(); + idx = i; + break; } - fun_unlock(); - return idx; + } + fun_unlock(); + return idx; } static int fun_thread_spawn(Value fnVal, Value argsMaybe, int hasArgs) { - if (fnVal.type != VAL_FUNCTION || !fnVal.fn) { - fprintf(stderr, "Runtime error: thread_spawn expects Function as first argument\n"); - return 0; - } + if (fnVal.type != VAL_FUNCTION || !fnVal.fn) { + fprintf(stderr, "Runtime error: thread_spawn expects Function as first argument\n"); + return 0; + } - /* Collect args */ - int argc = 0; - Value *args = NULL; + /* Collect args */ + int argc = 0; + Value *args = NULL; - if (hasArgs) { - if (argsMaybe.type == VAL_ARRAY && argsMaybe.arr) { - int n = array_length(&argsMaybe); - if (n > 0) { - args = (Value*)calloc((size_t)n, sizeof(Value)); - if (!args) n = 0; - for (int i = 0; i < n; ++i) { - Value vi; - if (array_get_copy(&argsMaybe, i, &vi)) { - args[i] = deep_copy_value(&vi); - free_value(vi); - } else { - args[i] = make_nil(); - } - } - argc = n; - } - } else if (argsMaybe.type != VAL_NIL) { - args = (Value*)calloc(1, sizeof(Value)); - if (args) { - args[0] = deep_copy_value(&argsMaybe); - argc = 1; - } + if (hasArgs) { + if (argsMaybe.type == VAL_ARRAY && argsMaybe.arr) { + int n = array_length(&argsMaybe); + if (n > 0) { + args = (Value *)calloc((size_t)n, sizeof(Value)); + if (!args) n = 0; + for (int i = 0; i < n; ++i) { + Value vi; + if (array_get_copy(&argsMaybe, i, &vi)) { + args[i] = deep_copy_value(&vi); + free_value(vi); + } else { + args[i] = make_nil(); + } } + argc = n; + } + } else if (argsMaybe.type != VAL_NIL) { + args = (Value *)calloc(1, sizeof(Value)); + if (args) { + args[0] = deep_copy_value(&argsMaybe); + argc = 1; + } } + } - int slot = fun_alloc_thread_slot(); - if (slot < 0) { - fprintf(stderr, "Runtime error: too many threads\n"); - /* free args */ - for (int i = 0; i < argc; ++i) free_value(args[i]); - free(args); - return 0; - } + int slot = fun_alloc_thread_slot(); + if (slot < 0) { + fprintf(stderr, "Runtime error: too many threads\n"); + /* free args */ + for (int i = 0; i < argc; ++i) + free_value(args[i]); + free(args); + return 0; + } - FunTask *task = (FunTask*)calloc(1, sizeof(FunTask)); - if (!task) { - for (int i = 0; i < argc; ++i) free_value(args[i]); - free(args); - fun_lock(); g_threads[slot].used = 0; fun_unlock(); - return 0; - } - task->fn = fnVal.fn; - task->argc = argc; - task->args = args; - task->slot = slot; + FunTask *task = (FunTask *)calloc(1, sizeof(FunTask)); + if (!task) { + for (int i = 0; i < argc; ++i) + free_value(args[i]); + free(args); + fun_lock(); + g_threads[slot].used = 0; + fun_unlock(); + return 0; + } + task->fn = fnVal.fn; + task->argc = argc; + task->args = args; + task->slot = slot; #ifdef _WIN32 - HANDLE h = CreateThread(NULL, 0, fun_thread_main, (LPVOID)task, 0, &g_threads[slot].threadId); - if (!h) { - fprintf(stderr, "Runtime error: CreateThread failed\n"); - for (int i = 0; i < argc; ++i) free_value(args[i]); - free(args); - free(task); - fun_lock(); g_threads[slot].used = 0; fun_unlock(); - return 0; - } - fun_lock(); g_threads[slot].handle = h; fun_unlock(); + HANDLE h = CreateThread(NULL, 0, fun_thread_main, (LPVOID)task, 0, &g_threads[slot].threadId); + if (!h) { + fprintf(stderr, "Runtime error: CreateThread failed\n"); + for (int i = 0; i < argc; ++i) + free_value(args[i]); + free(args); + free(task); + fun_lock(); + g_threads[slot].used = 0; + fun_unlock(); + return 0; + } + fun_lock(); + g_threads[slot].handle = h; + fun_unlock(); #else - pthread_t tid; - int rc = pthread_create(&tid, NULL, fun_thread_main, (void*)task); - if (rc != 0) { - fprintf(stderr, "Runtime error: pthread_create failed\n"); - for (int i = 0; i < argc; ++i) free_value(args[i]); - free(args); - free(task); - fun_lock(); g_threads[slot].used = 0; fun_unlock(); - return 0; - } - fun_lock(); g_threads[slot].handle = tid; fun_unlock(); + pthread_t tid; + int rc = pthread_create(&tid, NULL, fun_thread_main, (void *)task); + if (rc != 0) { + fprintf(stderr, "Runtime error: pthread_create failed\n"); + for (int i = 0; i < argc; ++i) + free_value(args[i]); + free(args); + free(task); + fun_lock(); + g_threads[slot].used = 0; + fun_unlock(); + return 0; + } + fun_lock(); + g_threads[slot].handle = tid; + fun_unlock(); #endif - return slot + 1; /* external thread id: 1..N */ + return slot + 1; /* external thread id: 1..N */ } static Value fun_thread_join(int tid) { - if (tid <= 0 || tid > FUN_MAX_THREADS) { - fprintf(stderr, "Runtime error: thread_join invalid id %d\n", tid); - return make_nil(); - } - int idx = tid - 1; + if (tid <= 0 || tid > FUN_MAX_THREADS) { + fprintf(stderr, "Runtime error: thread_join invalid id %d\n", tid); + return make_nil(); + } + int idx = tid - 1; #ifdef _WIN32 - fun_lock(); - HANDLE h = g_threads[idx].handle; - int used = g_threads[idx].used; - fun_unlock(); - if (!used || !h) return make_nil(); - WaitForSingleObject(h, INFINITE); - CloseHandle(h); + fun_lock(); + HANDLE h = g_threads[idx].handle; + int used = g_threads[idx].used; + fun_unlock(); + if (!used || !h) return make_nil(); + WaitForSingleObject(h, INFINITE); + CloseHandle(h); #else - fun_lock(); - pthread_t h = g_threads[idx].handle; - int used = g_threads[idx].used; - fun_unlock(); - if (!used) return make_nil(); - pthread_join(h, NULL); + fun_lock(); + pthread_t h = g_threads[idx].handle; + int used = g_threads[idx].used; + fun_unlock(); + if (!used) return make_nil(); + pthread_join(h, NULL); #endif - /* fetch result and free slot */ - fun_lock(); - Value res = deep_copy_value(&g_threads[idx].result); - free_value(g_threads[idx].result); - g_threads[idx].result = make_nil(); - g_threads[idx].used = 0; - g_threads[idx].done = 0; + /* fetch result and free slot */ + fun_lock(); + Value res = deep_copy_value(&g_threads[idx].result); + free_value(g_threads[idx].result); + g_threads[idx].result = make_nil(); + g_threads[idx].used = 0; + g_threads[idx].done = 0; #ifdef _WIN32 - g_threads[idx].threadId = 0; + g_threads[idx].threadId = 0; #endif - fun_unlock(); + fun_unlock(); - return res; + return res; } diff --git a/src/vm/os/thread_join.c b/src/vm/os/thread_join.c index 354bea7..a1c0d0c 100644 --- a/src/vm/os/thread_join.c +++ b/src/vm/os/thread_join.c @@ -10,15 +10,15 @@ */ case OP_THREAD_JOIN: { - Value vtid = pop_value(vm); - if (vtid.type != VAL_INT) { - fprintf(stderr, "Runtime type error: thread_join expects thread id (int)\n"); - push_value(vm, make_nil()); - free_value(vtid); - break; - } - Value res = fun_thread_join((int)vtid.i); + Value vtid = pop_value(vm); + if (vtid.type != VAL_INT) { + fprintf(stderr, "Runtime type error: thread_join expects thread id (int)\n"); + push_value(vm, make_nil()); free_value(vtid); - push_value(vm, res); /* takes ownership */ break; + } + Value res = fun_thread_join((int)vtid.i); + free_value(vtid); + push_value(vm, res); /* takes ownership */ + break; } diff --git a/src/vm/os/thread_spawn.c b/src/vm/os/thread_spawn.c index 1902ee1..a115ed5 100644 --- a/src/vm/os/thread_spawn.c +++ b/src/vm/os/thread_spawn.c @@ -10,15 +10,15 @@ */ case OP_THREAD_SPAWN: { - /* operand: 0 -> no args; 1 -> has args array or single arg */ - Value argsMaybe = make_nil(); - if (inst.operand == 1) { - argsMaybe = pop_value(vm); /* maybe array or scalar */ - } - Value fnv = pop_value(vm); - int tid = fun_thread_spawn(fnv, argsMaybe, inst.operand == 1); - free_value(fnv); - if (inst.operand == 1) free_value(argsMaybe); - push_value(vm, make_int(tid)); - break; + /* operand: 0 -> no args; 1 -> has args array or single arg */ + Value argsMaybe = make_nil(); + if (inst.operand == 1) { + argsMaybe = pop_value(vm); /* maybe array or scalar */ + } + Value fnv = pop_value(vm); + int tid = fun_thread_spawn(fnv, argsMaybe, inst.operand == 1); + free_value(fnv); + if (inst.operand == 1) free_value(argsMaybe); + push_value(vm, make_int(tid)); + break; } diff --git a/src/vm/os/time_now_ms.c b/src/vm/os/time_now_ms.c index 43002b0..d415f34 100644 --- a/src/vm/os/time_now_ms.c +++ b/src/vm/os/time_now_ms.c @@ -1,5 +1,5 @@ /** -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -17,23 +17,23 @@ * Stack after: [int ms] */ -#include #include +#include case OP_TIME_NOW_MS: { - int64_t ms; + int64_t ms; #if defined(CLOCK_REALTIME) && !defined(_WIN32) - struct timespec ts; - if (clock_gettime(CLOCK_REALTIME, &ts) == 0) { - ms = (int64_t)ts.tv_sec * 1000 + (int64_t)(ts.tv_nsec / 1000000); - } else { - time_t s = time(NULL); - ms = (int64_t)s * 1000; - } -#else + struct timespec ts; + if (clock_gettime(CLOCK_REALTIME, &ts) == 0) { + ms = (int64_t)ts.tv_sec * 1000 + (int64_t)(ts.tv_nsec / 1000000); + } else { time_t s = time(NULL); ms = (int64_t)s * 1000; + } +#else + time_t s = time(NULL); + ms = (int64_t)s * 1000; #endif - push_value(vm, make_int(ms)); - break; + push_value(vm, make_int(ms)); + break; } diff --git a/src/vm/pcre2/findall.c b/src/vm/pcre2/findall.c index 09857a8..7b1a65b 100644 --- a/src/vm/pcre2/findall.c +++ b/src/vm/pcre2/findall.c @@ -12,89 +12,95 @@ /* PCRE2_FINDALL */ case OP_PCRE2_FINDALL: { #ifdef FUN_WITH_PCRE2 - Value vflags = pop_value(vm); - Value vtext = pop_value(vm); - Value vpat = pop_value(vm); - int flags = 0; - if (vflags.type == VAL_INT || vflags.type == VAL_BOOL) flags = (int)vflags.i; - char *pattern = value_to_string_alloc(&vpat); - char *subject = value_to_string_alloc(&vtext); - free_value(vflags); - free_value(vtext); - free_value(vpat); - if (!pattern || !subject) { - if (pattern) free(pattern); - if (subject) free(subject); - push_value(vm, make_array_from_values(NULL, 0)); - break; - } - #ifndef PCRE2_CODE_UNIT_WIDTH - #define PCRE2_CODE_UNIT_WIDTH 8 - #endif - #include - int errorcode; PCRE2_SIZE erroff; - uint32_t opt = 0; - if (flags & 1) opt |= PCRE2_CASELESS; /* I */ - if (flags & 2) opt |= PCRE2_MULTILINE; /* M */ - if (flags & 4) opt |= PCRE2_DOTALL; /* S */ - if (flags & 8) opt |= PCRE2_UTF; /* U */ - if (flags & 16) opt |= PCRE2_EXTENDED; /* X */ - pcre2_code *re = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, opt, &errorcode, &erroff, NULL); - if (!re) { - free(pattern); free(subject); - push_value(vm, make_array_from_values(NULL, 0)); - break; - } - pcre2_match_data *mdata = pcre2_match_data_create_from_pattern(re, NULL); - Value out = make_array_from_values(NULL, 0); - size_t subj_len = strlen(subject); - size_t start_off = 0; - int gcount = 0; - while (1) { - int rc = pcre2_match(re, (PCRE2_SPTR)subject, (PCRE2_SIZE)subj_len, start_off, 0, mdata, NULL); - if (rc <= 0) break; - PCRE2_SIZE *ov = pcre2_get_ovector_pointer(mdata); - int s0 = (int)ov[0]; - int e0 = (int)ov[1]; - /* result map for this match */ - Value res = make_map_empty(); - char *full = string_substr(subject, s0, e0 - s0); - (void)map_set(&res, "full", make_string(full ? full : "")); - if (full) free(full); - (void)map_set(&res, "start", make_int(s0)); - (void)map_set(&res, "end", make_int(e0)); - Value groups = make_array_from_values(NULL, 0); - for (int i = 1; i < rc; ++i) { - int s = (int)ov[2*i]; - int e = (int)ov[2*i+1]; - char *gstr = (s >= 0 && e >= s) ? string_substr(subject, s, e - s) : NULL; - Value gv = make_string(gstr ? gstr : ""); - if (gstr) free(gstr); - (void)array_push(&groups, gv); - } - (void)map_set(&res, "groups", groups); - (void)array_push(&out, res); - /* advance start offset; guard against empty match */ - if (e0 == s0) { - if ((size_t)e0 < subj_len) { - start_off = e0 + 1; - } else { - break; - } - } else { - start_off = e0; - } - gcount = rc; - } - pcre2_match_data_free(mdata); - pcre2_code_free(re); - free(pattern); free(subject); - push_value(vm, out); -#else - Value a = pop_value(vm); free_value(a); - Value b = pop_value(vm); free_value(b); - Value c = pop_value(vm); free_value(c); + Value vflags = pop_value(vm); + Value vtext = pop_value(vm); + Value vpat = pop_value(vm); + int flags = 0; + if (vflags.type == VAL_INT || vflags.type == VAL_BOOL) flags = (int)vflags.i; + char *pattern = value_to_string_alloc(&vpat); + char *subject = value_to_string_alloc(&vtext); + free_value(vflags); + free_value(vtext); + free_value(vpat); + if (!pattern || !subject) { + if (pattern) free(pattern); + if (subject) free(subject); push_value(vm, make_array_from_values(NULL, 0)); -#endif break; + } +#ifndef PCRE2_CODE_UNIT_WIDTH +#define PCRE2_CODE_UNIT_WIDTH 8 +#endif +#include + int errorcode; + PCRE2_SIZE erroff; + uint32_t opt = 0; + if (flags & 1) opt |= PCRE2_CASELESS; /* I */ + if (flags & 2) opt |= PCRE2_MULTILINE; /* M */ + if (flags & 4) opt |= PCRE2_DOTALL; /* S */ + if (flags & 8) opt |= PCRE2_UTF; /* U */ + if (flags & 16) opt |= PCRE2_EXTENDED; /* X */ + pcre2_code *re = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, opt, &errorcode, &erroff, NULL); + if (!re) { + free(pattern); + free(subject); + push_value(vm, make_array_from_values(NULL, 0)); + break; + } + pcre2_match_data *mdata = pcre2_match_data_create_from_pattern(re, NULL); + Value out = make_array_from_values(NULL, 0); + size_t subj_len = strlen(subject); + size_t start_off = 0; + int gcount = 0; + while (1) { + int rc = pcre2_match(re, (PCRE2_SPTR)subject, (PCRE2_SIZE)subj_len, start_off, 0, mdata, NULL); + if (rc <= 0) break; + PCRE2_SIZE *ov = pcre2_get_ovector_pointer(mdata); + int s0 = (int)ov[0]; + int e0 = (int)ov[1]; + /* result map for this match */ + Value res = make_map_empty(); + char *full = string_substr(subject, s0, e0 - s0); + (void)map_set(&res, "full", make_string(full ? full : "")); + if (full) free(full); + (void)map_set(&res, "start", make_int(s0)); + (void)map_set(&res, "end", make_int(e0)); + Value groups = make_array_from_values(NULL, 0); + for (int i = 1; i < rc; ++i) { + int s = (int)ov[2 * i]; + int e = (int)ov[2 * i + 1]; + char *gstr = (s >= 0 && e >= s) ? string_substr(subject, s, e - s) : NULL; + Value gv = make_string(gstr ? gstr : ""); + if (gstr) free(gstr); + (void)array_push(&groups, gv); + } + (void)map_set(&res, "groups", groups); + (void)array_push(&out, res); + /* advance start offset; guard against empty match */ + if (e0 == s0) { + if ((size_t)e0 < subj_len) { + start_off = e0 + 1; + } else { + break; + } + } else { + start_off = e0; + } + gcount = rc; + } + pcre2_match_data_free(mdata); + pcre2_code_free(re); + free(pattern); + free(subject); + push_value(vm, out); +#else + Value a = pop_value(vm); + free_value(a); + Value b = pop_value(vm); + free_value(b); + Value c = pop_value(vm); + free_value(c); + push_value(vm, make_array_from_values(NULL, 0)); +#endif + break; } diff --git a/src/vm/pcre2/match.c b/src/vm/pcre2/match.c index 0707449..3fafa0f 100644 --- a/src/vm/pcre2/match.c +++ b/src/vm/pcre2/match.c @@ -12,78 +12,85 @@ /* PCRE2_MATCH */ case OP_PCRE2_MATCH: { #ifdef FUN_WITH_PCRE2 - Value vflags = pop_value(vm); - Value vtext = pop_value(vm); - Value vpat = pop_value(vm); - int flags = 0; - if (vflags.type == VAL_INT || vflags.type == VAL_BOOL) flags = (int)vflags.i; - char *pattern = value_to_string_alloc(&vpat); - char *subject = value_to_string_alloc(&vtext); - free_value(vflags); - free_value(vtext); - free_value(vpat); - if (!pattern || !subject) { - if (pattern) free(pattern); - if (subject) free(subject); - push_value(vm, make_nil()); - break; - } - #ifndef PCRE2_CODE_UNIT_WIDTH - #define PCRE2_CODE_UNIT_WIDTH 8 - #endif - #include - int errorcode; PCRE2_SIZE erroff; - uint32_t opt = 0; - if (flags & 1) opt |= PCRE2_CASELESS; /* I */ - if (flags & 2) opt |= PCRE2_MULTILINE; /* M */ - if (flags & 4) opt |= PCRE2_DOTALL; /* S */ - if (flags & 8) opt |= PCRE2_UTF; /* U */ - if (flags & 16) opt |= PCRE2_EXTENDED; /* X */ - pcre2_code *re = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, opt, &errorcode, &erroff, NULL); - if (!re) { - free(pattern); free(subject); - push_value(vm, make_nil()); - break; - } - pcre2_match_data *mdata = pcre2_match_data_create_from_pattern(re, NULL); - int rc = pcre2_match(re, (PCRE2_SPTR)subject, (PCRE2_SIZE)strlen(subject), 0, 0, mdata, NULL); - if (rc <= 0) { - pcre2_match_data_free(mdata); - pcre2_code_free(re); - free(pattern); free(subject); - push_value(vm, make_nil()); - break; - } - PCRE2_SIZE *ov = pcre2_get_ovector_pointer(mdata); - /* Build result map */ - Value res = make_map_empty(); - int start0 = (int)ov[0]; - int end0 = (int)ov[1]; - char *full = string_substr(subject, start0, end0 - start0); - (void)map_set(&res, "full", make_string(full ? full : "")); - if (full) free(full); - (void)map_set(&res, "start", make_int(start0)); - (void)map_set(&res, "end", make_int(end0)); - /* groups array (excluding group 0) */ - Value groups = make_array_from_values(NULL, 0); - for (int i = 1; i < rc; ++i) { - int s = (int)ov[2*i]; - int e = (int)ov[2*i+1]; - char *gstr = (s >= 0 && e >= s) ? string_substr(subject, s, e - s) : NULL; - Value gv = make_string(gstr ? gstr : ""); - if (gstr) free(gstr); - (void)array_push(&groups, gv); - } - (void)map_set(&res, "groups", groups); + Value vflags = pop_value(vm); + Value vtext = pop_value(vm); + Value vpat = pop_value(vm); + int flags = 0; + if (vflags.type == VAL_INT || vflags.type == VAL_BOOL) flags = (int)vflags.i; + char *pattern = value_to_string_alloc(&vpat); + char *subject = value_to_string_alloc(&vtext); + free_value(vflags); + free_value(vtext); + free_value(vpat); + if (!pattern || !subject) { + if (pattern) free(pattern); + if (subject) free(subject); + push_value(vm, make_nil()); + break; + } +#ifndef PCRE2_CODE_UNIT_WIDTH +#define PCRE2_CODE_UNIT_WIDTH 8 +#endif +#include + int errorcode; + PCRE2_SIZE erroff; + uint32_t opt = 0; + if (flags & 1) opt |= PCRE2_CASELESS; /* I */ + if (flags & 2) opt |= PCRE2_MULTILINE; /* M */ + if (flags & 4) opt |= PCRE2_DOTALL; /* S */ + if (flags & 8) opt |= PCRE2_UTF; /* U */ + if (flags & 16) opt |= PCRE2_EXTENDED; /* X */ + pcre2_code *re = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, opt, &errorcode, &erroff, NULL); + if (!re) { + free(pattern); + free(subject); + push_value(vm, make_nil()); + break; + } + pcre2_match_data *mdata = pcre2_match_data_create_from_pattern(re, NULL); + int rc = pcre2_match(re, (PCRE2_SPTR)subject, (PCRE2_SIZE)strlen(subject), 0, 0, mdata, NULL); + if (rc <= 0) { pcre2_match_data_free(mdata); pcre2_code_free(re); - free(pattern); free(subject); - push_value(vm, res); -#else - Value a = pop_value(vm); free_value(a); - Value b = pop_value(vm); free_value(b); - Value c = pop_value(vm); free_value(c); + free(pattern); + free(subject); push_value(vm, make_nil()); -#endif break; + } + PCRE2_SIZE *ov = pcre2_get_ovector_pointer(mdata); + /* Build result map */ + Value res = make_map_empty(); + int start0 = (int)ov[0]; + int end0 = (int)ov[1]; + char *full = string_substr(subject, start0, end0 - start0); + (void)map_set(&res, "full", make_string(full ? full : "")); + if (full) free(full); + (void)map_set(&res, "start", make_int(start0)); + (void)map_set(&res, "end", make_int(end0)); + /* groups array (excluding group 0) */ + Value groups = make_array_from_values(NULL, 0); + for (int i = 1; i < rc; ++i) { + int s = (int)ov[2 * i]; + int e = (int)ov[2 * i + 1]; + char *gstr = (s >= 0 && e >= s) ? string_substr(subject, s, e - s) : NULL; + Value gv = make_string(gstr ? gstr : ""); + if (gstr) free(gstr); + (void)array_push(&groups, gv); + } + (void)map_set(&res, "groups", groups); + pcre2_match_data_free(mdata); + pcre2_code_free(re); + free(pattern); + free(subject); + push_value(vm, res); +#else + Value a = pop_value(vm); + free_value(a); + Value b = pop_value(vm); + free_value(b); + Value c = pop_value(vm); + free_value(c); + push_value(vm, make_nil()); +#endif + break; } diff --git a/src/vm/pcre2/test.c b/src/vm/pcre2/test.c index 013df5b..e0500ca 100644 --- a/src/vm/pcre2/test.c +++ b/src/vm/pcre2/test.c @@ -12,51 +12,57 @@ /* PCRE2_TEST */ case OP_PCRE2_TEST: { #ifdef FUN_WITH_PCRE2 - Value vflags = pop_value(vm); - Value vtext = pop_value(vm); - Value vpat = pop_value(vm); - int flags = 0; - if (vflags.type == VAL_INT || vflags.type == VAL_BOOL) flags = (int)vflags.i; - char *pattern = value_to_string_alloc(&vpat); - char *subject = value_to_string_alloc(&vtext); - free_value(vflags); - free_value(vtext); - free_value(vpat); - if (!pattern || !subject) { - if (pattern) free(pattern); - if (subject) free(subject); - push_value(vm, make_int(0)); - break; - } - #ifndef PCRE2_CODE_UNIT_WIDTH - #define PCRE2_CODE_UNIT_WIDTH 8 - #endif - #include - int errorcode; PCRE2_SIZE erroff; - uint32_t opt = 0; - if (flags & 1) opt |= PCRE2_CASELESS; /* I */ - if (flags & 2) opt |= PCRE2_MULTILINE; /* M */ - if (flags & 4) opt |= PCRE2_DOTALL; /* S */ - if (flags & 8) opt |= PCRE2_UTF; /* U */ - if (flags & 16) opt |= PCRE2_EXTENDED; /* X */ - pcre2_code *re = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, opt, &errorcode, &erroff, NULL); - if (!re) { - free(pattern); free(subject); - push_value(vm, make_int(0)); - break; - } - pcre2_match_data *mdata = pcre2_match_data_create_from_pattern(re, NULL); - int rc = pcre2_match(re, (PCRE2_SPTR)subject, (PCRE2_SIZE)strlen(subject), 0, 0, mdata, NULL); - pcre2_match_data_free(mdata); - pcre2_code_free(re); - free(pattern); free(subject); - push_value(vm, make_int(rc >= 0 ? 1 : 0)); -#else - /* pop args and return 0 when PCRE2 disabled */ - Value a = pop_value(vm); free_value(a); - Value b = pop_value(vm); free_value(b); - Value c = pop_value(vm); free_value(c); + Value vflags = pop_value(vm); + Value vtext = pop_value(vm); + Value vpat = pop_value(vm); + int flags = 0; + if (vflags.type == VAL_INT || vflags.type == VAL_BOOL) flags = (int)vflags.i; + char *pattern = value_to_string_alloc(&vpat); + char *subject = value_to_string_alloc(&vtext); + free_value(vflags); + free_value(vtext); + free_value(vpat); + if (!pattern || !subject) { + if (pattern) free(pattern); + if (subject) free(subject); push_value(vm, make_int(0)); -#endif break; + } +#ifndef PCRE2_CODE_UNIT_WIDTH +#define PCRE2_CODE_UNIT_WIDTH 8 +#endif +#include + int errorcode; + PCRE2_SIZE erroff; + uint32_t opt = 0; + if (flags & 1) opt |= PCRE2_CASELESS; /* I */ + if (flags & 2) opt |= PCRE2_MULTILINE; /* M */ + if (flags & 4) opt |= PCRE2_DOTALL; /* S */ + if (flags & 8) opt |= PCRE2_UTF; /* U */ + if (flags & 16) opt |= PCRE2_EXTENDED; /* X */ + pcre2_code *re = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, opt, &errorcode, &erroff, NULL); + if (!re) { + free(pattern); + free(subject); + push_value(vm, make_int(0)); + break; + } + pcre2_match_data *mdata = pcre2_match_data_create_from_pattern(re, NULL); + int rc = pcre2_match(re, (PCRE2_SPTR)subject, (PCRE2_SIZE)strlen(subject), 0, 0, mdata, NULL); + pcre2_match_data_free(mdata); + pcre2_code_free(re); + free(pattern); + free(subject); + push_value(vm, make_int(rc >= 0 ? 1 : 0)); +#else + /* pop args and return 0 when PCRE2 disabled */ + Value a = pop_value(vm); + free_value(a); + Value b = pop_value(vm); + free_value(b); + Value c = pop_value(vm); + free_value(c); + push_value(vm, make_int(0)); +#endif + break; } diff --git a/src/vm/pcsc/connect.c b/src/vm/pcsc/connect.c index e9f9a43..ce8b215 100644 --- a/src/vm/pcsc/connect.c +++ b/src/vm/pcsc/connect.c @@ -12,37 +12,47 @@ /* PCSC connect */ case OP_PCSC_CONNECT: { #ifdef FUN_WITH_PCSC - /* Stack: [..., ctx_id, reader_name] -> pops reader_name first, then ctx_id */ - Value vreader = pop_value(vm); - Value vctx = pop_value(vm); + /* Stack: [..., ctx_id, reader_name] -> pops reader_name first, then ctx_id */ + Value vreader = pop_value(vm); + Value vctx = pop_value(vm); - int ctx_id = (int)vctx.i; - free_value(vctx); - char *rname = value_to_string_alloc(&vreader); - free_value(vreader); + int ctx_id = (int)vctx.i; + free_value(vctx); + char *rname = value_to_string_alloc(&vreader); + free_value(vreader); - pcsc_ctx_entry *e = pcsc_get_ctx(ctx_id); - if (!e || !rname) { if (rname) free(rname); push_value(vm, make_int(0)); break; } - - int hslot = pcsc_alloc_card_slot(); - if (!hslot) { free(rname); push_value(vm, make_int(0)); break; } - pcsc_card_entry *ce = pcsc_get_card(hslot); - DWORD dwActive = 0; - LONG rv = SCardConnect(e->ctx, rname, SCARD_SHARE_SHARED, - SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, - &ce->h, &dwActive); - free(rname); - if (rv != SCARD_S_SUCCESS) { - ce->in_use = 0; - push_value(vm, make_int(0)); - break; - } - ce->proto = dwActive; - push_value(vm, make_int(hslot)); -#else - Value vreader = pop_value(vm); free_value(vreader); - Value vctx = pop_value(vm); free_value(vctx); + pcsc_ctx_entry *e = pcsc_get_ctx(ctx_id); + if (!e || !rname) { + if (rname) free(rname); push_value(vm, make_int(0)); -#endif break; + } + + int hslot = pcsc_alloc_card_slot(); + if (!hslot) { + free(rname); + push_value(vm, make_int(0)); + break; + } + pcsc_card_entry *ce = pcsc_get_card(hslot); + DWORD dwActive = 0; + LONG rv = SCardConnect(e->ctx, rname, SCARD_SHARE_SHARED, + SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, + &ce->h, &dwActive); + free(rname); + if (rv != SCARD_S_SUCCESS) { + ce->in_use = 0; + push_value(vm, make_int(0)); + break; + } + ce->proto = dwActive; + push_value(vm, make_int(hslot)); +#else + Value vreader = pop_value(vm); + free_value(vreader); + Value vctx = pop_value(vm); + free_value(vctx); + push_value(vm, make_int(0)); +#endif + break; } diff --git a/src/vm/pcsc/disconnect.c b/src/vm/pcsc/disconnect.c index 40796f9..aa7c160 100644 --- a/src/vm/pcsc/disconnect.c +++ b/src/vm/pcsc/disconnect.c @@ -1,5 +1,5 @@ /** -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -12,19 +12,23 @@ /* PCSC disconnect */ case OP_PCSC_DISCONNECT: { #ifdef FUN_WITH_PCSC - Value vh = pop_value(vm); - int hid = (int)vh.i; - free_value(vh); - pcsc_card_entry *ce = pcsc_get_card(hid); - if (!ce) { push_value(vm, make_int(0)); break; } - SCardDisconnect(ce->h, SCARD_LEAVE_CARD); - ce->in_use = 0; - ce->h = 0; - ce->proto = 0; - push_value(vm, make_int(1)); -#else - Value vh = pop_value(vm); free_value(vh); + Value vh = pop_value(vm); + int hid = (int)vh.i; + free_value(vh); + pcsc_card_entry *ce = pcsc_get_card(hid); + if (!ce) { push_value(vm, make_int(0)); -#endif break; + } + SCardDisconnect(ce->h, SCARD_LEAVE_CARD); + ce->in_use = 0; + ce->h = 0; + ce->proto = 0; + push_value(vm, make_int(1)); +#else + Value vh = pop_value(vm); + free_value(vh); + push_value(vm, make_int(0)); +#endif + break; } diff --git a/src/vm/pcsc/establish.c b/src/vm/pcsc/establish.c index 4b12875..88067e3 100644 --- a/src/vm/pcsc/establish.c +++ b/src/vm/pcsc/establish.c @@ -12,15 +12,25 @@ /* PCSC establish */ case OP_PCSC_ESTABLISH: { #ifdef FUN_WITH_PCSC - int slot = pcsc_alloc_ctx_slot(); - if (!slot) { push_value(vm, make_int(0)); break; } - pcsc_ctx_entry *e = pcsc_get_ctx(slot); - if (!e) { push_value(vm, make_int(0)); break; } - LONG rv = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &e->ctx); - if (rv != SCARD_S_SUCCESS) { e->in_use = 0; push_value(vm, make_int(0)); break; } - push_value(vm, make_int(slot)); -#else + int slot = pcsc_alloc_ctx_slot(); + if (!slot) { push_value(vm, make_int(0)); -#endif break; + } + pcsc_ctx_entry *e = pcsc_get_ctx(slot); + if (!e) { + push_value(vm, make_int(0)); + break; + } + LONG rv = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &e->ctx); + if (rv != SCARD_S_SUCCESS) { + e->in_use = 0; + push_value(vm, make_int(0)); + break; + } + push_value(vm, make_int(slot)); +#else + push_value(vm, make_int(0)); +#endif + break; } diff --git a/src/vm/pcsc/list_readers.c b/src/vm/pcsc/list_readers.c index 3cf0cd7..4cdd995 100644 --- a/src/vm/pcsc/list_readers.c +++ b/src/vm/pcsc/list_readers.c @@ -12,53 +12,60 @@ /* PCSC list_readers */ case OP_PCSC_LIST_READERS: { #ifdef FUN_WITH_PCSC - /* Pop context id; return [] if anything goes wrong */ - Value vid = pop_value(vm); - int id = (int)vid.i; - free_value(vid); + /* Pop context id; return [] if anything goes wrong */ + Value vid = pop_value(vm); + int id = (int)vid.i; + free_value(vid); - pcsc_ctx_entry *e = pcsc_get_ctx(id); - if (!e) { push_value(vm, make_array_from_values(NULL, 0)); break; } - - DWORD sz = 0; - LONG rv = SCardListReaders(e->ctx, NULL, NULL, &sz); - if (rv != SCARD_S_SUCCESS || sz == 0) { - push_value(vm, make_array_from_values(NULL, 0)); - break; - } - - char *msz = (char*)malloc(sz); - if (!msz) { push_value(vm, make_array_from_values(NULL, 0)); break; } - - rv = SCardListReaders(e->ctx, NULL, msz, &sz); - if (rv != SCARD_S_SUCCESS) { - free(msz); - push_value(vm, make_array_from_values(NULL, 0)); - break; - } - - Value vals[64]; - int count = 0; - char *p = msz; - while (*p && count < (int)(sizeof(vals)/sizeof(vals[0]))) { - size_t n = strlen(p); - vals[count++] = make_string(p); - p += n + 1; - } - Value arr = make_array_from_values(vals, count); - for (int i = 0; i < count; ++i) free_value(vals[i]); - free(msz); - - if (arr.type != VAL_ARRAY) { - push_value(vm, make_array_from_values(NULL, 0)); - } else { - push_value(vm, arr); - } -#else - /* Fallback (no PCSC): consume ctx id and return [] to keep the stack consistent */ - Value vid = pop_value(vm); - free_value(vid); + pcsc_ctx_entry *e = pcsc_get_ctx(id); + if (!e) { push_value(vm, make_array_from_values(NULL, 0)); -#endif break; + } + + DWORD sz = 0; + LONG rv = SCardListReaders(e->ctx, NULL, NULL, &sz); + if (rv != SCARD_S_SUCCESS || sz == 0) { + push_value(vm, make_array_from_values(NULL, 0)); + break; + } + + char *msz = (char *)malloc(sz); + if (!msz) { + push_value(vm, make_array_from_values(NULL, 0)); + break; + } + + rv = SCardListReaders(e->ctx, NULL, msz, &sz); + if (rv != SCARD_S_SUCCESS) { + free(msz); + push_value(vm, make_array_from_values(NULL, 0)); + break; + } + + Value vals[64]; + int count = 0; + char *p = msz; + while (*p && count < (int)(sizeof(vals) / sizeof(vals[0]))) { + size_t n = strlen(p); + vals[count++] = make_string(p); + p += n + 1; + } + Value arr = make_array_from_values(vals, count); + for (int i = 0; i < count; ++i) + free_value(vals[i]); + free(msz); + + if (arr.type != VAL_ARRAY) { + push_value(vm, make_array_from_values(NULL, 0)); + } else { + push_value(vm, arr); + } +#else + /* Fallback (no PCSC): consume ctx id and return [] to keep the stack consistent */ + Value vid = pop_value(vm); + free_value(vid); + push_value(vm, make_array_from_values(NULL, 0)); +#endif + break; } diff --git a/src/vm/pcsc/release.c b/src/vm/pcsc/release.c index 917db21..070c4d7 100644 --- a/src/vm/pcsc/release.c +++ b/src/vm/pcsc/release.c @@ -12,18 +12,22 @@ /* PCSC release */ case OP_PCSC_RELEASE: { #ifdef FUN_WITH_PCSC - Value vid = pop_value(vm); - int id = (int)vid.i; - free_value(vid); - pcsc_ctx_entry *e = pcsc_get_ctx(id); - if (!e) { push_value(vm, make_int(0)); break; } - SCardReleaseContext(e->ctx); - e->in_use = 0; - e->ctx = 0; - push_value(vm, make_int(1)); -#else - Value vid = pop_value(vm); free_value(vid); + Value vid = pop_value(vm); + int id = (int)vid.i; + free_value(vid); + pcsc_ctx_entry *e = pcsc_get_ctx(id); + if (!e) { push_value(vm, make_int(0)); -#endif break; + } + SCardReleaseContext(e->ctx); + e->in_use = 0; + e->ctx = 0; + push_value(vm, make_int(1)); +#else + Value vid = pop_value(vm); + free_value(vid); + push_value(vm, make_int(0)); +#endif + break; } diff --git a/src/vm/pcsc/transmit.c b/src/vm/pcsc/transmit.c index 4a348dc..f4b18ba 100644 --- a/src/vm/pcsc/transmit.c +++ b/src/vm/pcsc/transmit.c @@ -12,84 +12,92 @@ /* PCSC transmit */ case OP_PCSC_TRANSMIT: { #ifdef FUN_WITH_PCSC - /* pops apdu array, handle_id */ - Value vapdu = pop_value(vm); - Value vh = pop_value(vm); - int hid = (int)vh.i; - free_value(vh); - pcsc_card_entry *ce = pcsc_get_card(hid); + /* pops apdu array, handle_id */ + Value vapdu = pop_value(vm); + Value vh = pop_value(vm); + int hid = (int)vh.i; + free_value(vh); + pcsc_card_entry *ce = pcsc_get_card(hid); - Value m = make_map_empty(); - map_set(&m, "data", make_array_from_values(NULL, 0)); - map_set(&m, "sw1", make_int(-1)); - map_set(&m, "sw2", make_int(-1)); - map_set(&m, "code", make_int(-1)); + Value m = make_map_empty(); + map_set(&m, "data", make_array_from_values(NULL, 0)); + map_set(&m, "sw1", make_int(-1)); + map_set(&m, "sw2", make_int(-1)); + map_set(&m, "code", make_int(-1)); - if (!ce || vapdu.type != VAL_ARRAY) { - free_value(vapdu); - push_value(vm, m); - break; - } - int n = array_length(&vapdu); - if (n < 0) n = 0; - unsigned char sbuf[4096]; - if (n > (int)sizeof(sbuf)) n = (int)sizeof(sbuf); - for (int i = 0; i < n; ++i) { - Value tmp; - if (array_get_copy(&vapdu, i, &tmp)) { - int64_t v = tmp.type == VAL_INT ? tmp.i : 0; - sbuf[i] = (unsigned char)(v & 0xFF); - free_value(tmp); - } else { - sbuf[i] = 0; - } - } + if (!ce || vapdu.type != VAL_ARRAY) { free_value(vapdu); - - SCARD_IO_REQUEST pio; - if (ce->proto == SCARD_PROTOCOL_T0) pio = *SCARD_PCI_T0; - else if (ce->proto == SCARD_PROTOCOL_T1) pio = *SCARD_PCI_T1; - else { pio = *SCARD_PCI_T1; } - - unsigned char rbuf[4096]; - DWORD rlen = sizeof(rbuf); - LONG rv = SCardTransmit(ce->h, &pio, sbuf, (DWORD)n, NULL, rbuf, &rlen); - - int sw1 = -1, sw2 = -1; - int datalen = 0; - if (rv == SCARD_S_SUCCESS && rlen >= 2) { - sw1 = rbuf[rlen - 2]; - sw2 = rbuf[rlen - 1]; - datalen = (int)rlen - 2; - } - - map_set(&m, "sw1", make_int(sw1)); - map_set(&m, "sw2", make_int(sw2)); - map_set(&m, "code", make_int((int)rv)); - - if (datalen > 0) { - Value *vals = (Value*)malloc(sizeof(Value) * (size_t)datalen); - if (!vals) { - map_set(&m, "data", make_array_from_values(NULL, 0)); - } else { - for (int i = 0; i < datalen; ++i) vals[i] = make_int((int64_t)rbuf[i]); - Value arr = make_array_from_values(vals, datalen); - for (int i = 0; i < datalen; ++i) free_value(vals[i]); - free(vals); - map_set(&m, "data", arr); - } - } - push_value(vm, m); -#else - Value vapdu = pop_value(vm); free_value(vapdu); - Value vh = pop_value(vm); free_value(vh); - Value m = make_map_empty(); - map_set(&m, "data", make_array_from_values(NULL, 0)); - map_set(&m, "sw1", make_int(-1)); - map_set(&m, "sw2", make_int(-1)); - map_set(&m, "code", make_int(-2)); - push_value(vm, m); -#endif break; + } + int n = array_length(&vapdu); + if (n < 0) n = 0; + unsigned char sbuf[4096]; + if (n > (int)sizeof(sbuf)) n = (int)sizeof(sbuf); + for (int i = 0; i < n; ++i) { + Value tmp; + if (array_get_copy(&vapdu, i, &tmp)) { + int64_t v = tmp.type == VAL_INT ? tmp.i : 0; + sbuf[i] = (unsigned char)(v & 0xFF); + free_value(tmp); + } else { + sbuf[i] = 0; + } + } + free_value(vapdu); + + SCARD_IO_REQUEST pio; + if (ce->proto == SCARD_PROTOCOL_T0) + pio = *SCARD_PCI_T0; + else if (ce->proto == SCARD_PROTOCOL_T1) + pio = *SCARD_PCI_T1; + else { + pio = *SCARD_PCI_T1; + } + + unsigned char rbuf[4096]; + DWORD rlen = sizeof(rbuf); + LONG rv = SCardTransmit(ce->h, &pio, sbuf, (DWORD)n, NULL, rbuf, &rlen); + + int sw1 = -1, sw2 = -1; + int datalen = 0; + if (rv == SCARD_S_SUCCESS && rlen >= 2) { + sw1 = rbuf[rlen - 2]; + sw2 = rbuf[rlen - 1]; + datalen = (int)rlen - 2; + } + + map_set(&m, "sw1", make_int(sw1)); + map_set(&m, "sw2", make_int(sw2)); + map_set(&m, "code", make_int((int)rv)); + + if (datalen > 0) { + Value *vals = (Value *)malloc(sizeof(Value) * (size_t)datalen); + if (!vals) { + map_set(&m, "data", make_array_from_values(NULL, 0)); + } else { + for (int i = 0; i < datalen; ++i) + vals[i] = make_int((int64_t)rbuf[i]); + Value arr = make_array_from_values(vals, datalen); + for (int i = 0; i < datalen; ++i) + free_value(vals[i]); + free(vals); + map_set(&m, "data", arr); + } + } + + push_value(vm, m); +#else + Value vapdu = pop_value(vm); + free_value(vapdu); + Value vh = pop_value(vm); + free_value(vh); + Value m = make_map_empty(); + map_set(&m, "data", make_array_from_values(NULL, 0)); + map_set(&m, "sw1", make_int(-1)); + map_set(&m, "sw2", make_int(-1)); + map_set(&m, "code", make_int(-2)); + push_value(vm, m); +#endif + break; } diff --git a/src/vm/print.c b/src/vm/print.c index a50de0f..afbc506 100644 --- a/src/vm/print.c +++ b/src/vm/print.c @@ -8,7 +8,7 @@ */ /** -* @file print.c + * @file print.c * @brief Implements the OP_PRINT opcode for printing values in the VM. * * This file handles the OP_PRINT instruction, which prints the top value on the stack @@ -28,18 +28,18 @@ */ case OP_PRINT: { - Value v = pop_value(vm); - Value snap = deep_copy_value(&v); - free_value(v); - if (vm->output_count < OUTPUT_SIZE) { - int idx = vm->output_count; - vm->output[idx] = snap; - vm->output_is_partial[idx] = 0; // PRINT terminates the line - vm->output_count++; - } else { - free_value(snap); - fprintf(stderr, "Runtime error: output buffer overflow\n"); - exit(1); - } - break; + Value v = pop_value(vm); + Value snap = deep_copy_value(&v); + free_value(v); + if (vm->output_count < OUTPUT_SIZE) { + int idx = vm->output_count; + vm->output[idx] = snap; + vm->output_is_partial[idx] = 0; // PRINT terminates the line + vm->output_count++; + } else { + free_value(snap); + fprintf(stderr, "Runtime error: output buffer overflow\n"); + exit(1); + } + break; } diff --git a/src/vm/rust/get_sp.c b/src/vm/rust/get_sp.c index 1a08b30..1b7c895 100644 --- a/src/vm/rust/get_sp.c +++ b/src/vm/rust/get_sp.c @@ -15,12 +15,12 @@ */ case OP_RUST_GET_SP: { #ifdef FUN_WITH_RUST - extern int fun_op_rget_sp(VM *vm); - int rc = fun_op_rget_sp(vm); - (void)rc; /* rc currently unused; 0 means OK */ + extern int fun_op_rget_sp(VM * vm); + int rc = fun_op_rget_sp(vm); + (void)rc; /* rc currently unused; 0 means OK */ #else - vm_raise_error(vm, "RUST_GET_SP requires FUN_WITH_RUST=ON at build time"); - push_value(vm, make_int(-1)); + vm_raise_error(vm, "RUST_GET_SP requires FUN_WITH_RUST=ON at build time"); + push_value(vm, make_int(-1)); #endif - break; + break; } diff --git a/src/vm/rust/hello.c b/src/vm/rust/hello.c index 32772ca..86a279e 100644 --- a/src/vm/rust/hello.c +++ b/src/vm/rust/hello.c @@ -8,20 +8,20 @@ * * Added: 2026-01-27 */ - - /** + +/** * Rust FFI demo opcode: OP_RUST_HELLO * When executed, it pushes a hello string returned by Rust onto the VM stack. */ case OP_RUST_HELLO: { #ifdef FUN_WITH_RUST - const char *s = fun_rust_get_string(); - if (!s) s = ""; - push_value(vm, make_string(s)); + const char *s = fun_rust_get_string(); + if (!s) s = ""; + push_value(vm, make_string(s)); #else - vm_raise_error(vm, "RUST_HELLO requires FUN_WITH_RUST=ON at build time"); - push_value(vm, make_nil()); + vm_raise_error(vm, "RUST_HELLO requires FUN_WITH_RUST=ON at build time"); + push_value(vm, make_nil()); #endif - break; + break; } diff --git a/src/vm/rust/hello_args.c b/src/vm/rust/hello_args.c index d361907..ce5de0a 100644 --- a/src/vm/rust/hello_args.c +++ b/src/vm/rust/hello_args.c @@ -15,22 +15,22 @@ */ case OP_RUST_HELLO_ARGS: { #ifdef FUN_WITH_RUST - Value vmsg = pop_value(vm); - char *msg = value_to_string_alloc(&vmsg); - free_value(vmsg); - if (msg) { - fun_rust_print_string(msg); - free(msg); - } else { - fun_rust_print_string(""); - } - push_value(vm, make_nil()); + Value vmsg = pop_value(vm); + char *msg = value_to_string_alloc(&vmsg); + free_value(vmsg); + if (msg) { + fun_rust_print_string(msg); + free(msg); + } else { + fun_rust_print_string(""); + } + push_value(vm, make_nil()); #else - /* Still pop and free the arg to keep stack sane */ - Value vmsg = pop_value(vm); - free_value(vmsg); - vm_raise_error(vm, "RUST_HELLO_ARGS requires FUN_WITH_RUST=ON at build time"); - push_value(vm, make_nil()); + /* Still pop and free the arg to keep stack sane */ + Value vmsg = pop_value(vm); + free_value(vmsg); + vm_raise_error(vm, "RUST_HELLO_ARGS requires FUN_WITH_RUST=ON at build time"); + push_value(vm, make_nil()); #endif - break; + break; } diff --git a/src/vm/rust/hello_args_return.c b/src/vm/rust/hello_args_return.c index 6adf44d..3fed3c3 100644 --- a/src/vm/rust/hello_args_return.c +++ b/src/vm/rust/hello_args_return.c @@ -16,33 +16,33 @@ */ case OP_RUST_HELLO_ARGS_RETURN: { #ifdef FUN_WITH_RUST - Value vmsg = pop_value(vm); - char *msg = value_to_string_alloc(&vmsg); - free_value(vmsg); - if (msg) { - char *ret = fun_rust_echo_string(msg); - free(msg); - if (ret) { - push_value(vm, make_string(ret)); - fun_rust_string_free(ret); - } else { - push_value(vm, make_nil()); - } + Value vmsg = pop_value(vm); + char *msg = value_to_string_alloc(&vmsg); + free_value(vmsg); + if (msg) { + char *ret = fun_rust_echo_string(msg); + free(msg); + if (ret) { + push_value(vm, make_string(ret)); + fun_rust_string_free(ret); } else { - char *ret = fun_rust_echo_string(""); - if (ret) { - push_value(vm, make_string(ret)); - fun_rust_string_free(ret); - } else { - push_value(vm, make_nil()); - } + push_value(vm, make_nil()); } + } else { + char *ret = fun_rust_echo_string(""); + if (ret) { + push_value(vm, make_string(ret)); + fun_rust_string_free(ret); + } else { + push_value(vm, make_nil()); + } + } #else - /* Still pop and free the arg to keep stack sane */ - Value vmsg = pop_value(vm); - free_value(vmsg); - vm_raise_error(vm, "RUST_HELLO_ARGS_RETURN requires FUN_WITH_RUST=ON at build time"); - push_value(vm, make_nil()); + /* Still pop and free the arg to keep stack sane */ + Value vmsg = pop_value(vm); + free_value(vmsg); + vm_raise_error(vm, "RUST_HELLO_ARGS_RETURN requires FUN_WITH_RUST=ON at build time"); + push_value(vm, make_nil()); #endif - break; + break; } diff --git a/src/vm/rust/set_exit.c b/src/vm/rust/set_exit.c index 70f4314..ad3163c 100644 --- a/src/vm/rust/set_exit.c +++ b/src/vm/rust/set_exit.c @@ -15,19 +15,19 @@ */ case OP_RUST_SET_EXIT: { #ifdef FUN_WITH_RUST - extern int fun_op_rset_exit(VM *vm); - /* Expect an integer on the stack already (produced by prior ops) */ - /* fun_op_rset_exit will pop it and store into vm.exit_code */ - int rc = fun_op_rset_exit(vm); - (void)rc; - /* push Nil as a conventional result */ - push_value(vm, make_nil()); + extern int fun_op_rset_exit(VM * vm); + /* Expect an integer on the stack already (produced by prior ops) */ + /* fun_op_rset_exit will pop it and store into vm.exit_code */ + int rc = fun_op_rset_exit(vm); + (void)rc; + /* push Nil as a conventional result */ + push_value(vm, make_nil()); #else - /* pop and ignore to keep stack sane */ - Value v = pop_value(vm); - free_value(v); - vm_raise_error(vm, "RUST_SET_EXIT requires FUN_WITH_RUST=ON at build time"); - push_value(vm, make_nil()); + /* pop and ignore to keep stack sane */ + Value v = pop_value(vm); + free_value(v); + vm_raise_error(vm, "RUST_SET_EXIT requires FUN_WITH_RUST=ON at build time"); + push_value(vm, make_nil()); #endif - break; + break; } diff --git a/src/vm/sclamp.c b/src/vm/sclamp.c index 94fe304..52066d6 100644 --- a/src/vm/sclamp.c +++ b/src/vm/sclamp.c @@ -1,5 +1,5 @@ /** -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -8,35 +8,35 @@ */ case OP_SCLAMP: { - /* 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; + /* 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 out = 0; - if (bits <= 0) { - out = 0; + int64_t out = 0; + if (bits <= 0) { + out = 0; + } else { + 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 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; - } - } + uint64_t sign_bit = 1ULL << (bits - 1); + if (wrapped & sign_bit) { + /* sign-extend */ + out = (int64_t)(wrapped | (~mask)); + } else { + out = (int64_t)wrapped; + } } + } - push_value(vm, make_int(out)); - free_value(v); - break; + push_value(vm, make_int(out)); + free_value(v); + break; } diff --git a/src/vm/sqlite/close.c b/src/vm/sqlite/close.c index acc6a4e..d8aa477 100644 --- a/src/vm/sqlite/close.c +++ b/src/vm/sqlite/close.c @@ -14,19 +14,20 @@ */ case OP_SQLITE_CLOSE: { #ifdef FUN_WITH_SQLITE - Value vh = pop_value(vm); - int hid = (int)vh.i; - free_value(vh); - SqlHandle *h = sql_reg_get(hid); - if (h && h->db) { - sqlite3_close(h->db); - h->db = NULL; - sql_reg_del(hid); - } - push_value(vm, make_nil()); + Value vh = pop_value(vm); + int hid = (int)vh.i; + free_value(vh); + SqlHandle *h = sql_reg_get(hid); + if (h && h->db) { + sqlite3_close(h->db); + h->db = NULL; + sql_reg_del(hid); + } + push_value(vm, make_nil()); #else - Value v = pop_value(vm); free_value(v); - push_value(vm, make_nil()); + Value v = pop_value(vm); + free_value(v); + push_value(vm, make_nil()); #endif - break; + break; } diff --git a/src/vm/sqlite/exec.c b/src/vm/sqlite/exec.c index 2c7e207..8bb99d1 100644 --- a/src/vm/sqlite/exec.c +++ b/src/vm/sqlite/exec.c @@ -14,23 +14,29 @@ */ case OP_SQLITE_EXEC: { #ifdef FUN_WITH_SQLITE - Value vsql = pop_value(vm); - Value vh = pop_value(vm); - int hid = (int)vh.i; - char *sql = value_to_string_alloc(&vsql); - free_value(vh); - free_value(vsql); - SqlHandle *h = sql_reg_get(hid); - if (!h || !h->db || !sql) { if (sql) free(sql); push_value(vm, make_int(SQLITE_MISUSE)); break; } - char *errmsg = NULL; - int rc = sqlite3_exec(h->db, sql, NULL, NULL, &errmsg); - if (errmsg) sqlite3_free(errmsg); - free(sql); - push_value(vm, make_int(rc)); -#else - Value v1 = pop_value(vm); free_value(v1); - Value v2 = pop_value(vm); free_value(v2); - push_value(vm, make_int(-1)); -#endif + Value vsql = pop_value(vm); + Value vh = pop_value(vm); + int hid = (int)vh.i; + char *sql = value_to_string_alloc(&vsql); + free_value(vh); + free_value(vsql); + SqlHandle *h = sql_reg_get(hid); + if (!h || !h->db || !sql) { + if (sql) free(sql); + push_value(vm, make_int(SQLITE_MISUSE)); break; + } + char *errmsg = NULL; + int rc = sqlite3_exec(h->db, sql, NULL, NULL, &errmsg); + if (errmsg) sqlite3_free(errmsg); + free(sql); + push_value(vm, make_int(rc)); +#else + Value v1 = pop_value(vm); + free_value(v1); + Value v2 = pop_value(vm); + free_value(v2); + push_value(vm, make_int(-1)); +#endif + break; } diff --git a/src/vm/sqlite/open.c b/src/vm/sqlite/open.c index a7c3ac4..814937e 100644 --- a/src/vm/sqlite/open.c +++ b/src/vm/sqlite/open.c @@ -14,24 +14,32 @@ */ case OP_SQLITE_OPEN: { #ifdef FUN_WITH_SQLITE - Value vpath = pop_value(vm); - char *path = value_to_string_alloc(&vpath); - free_value(vpath); - if (!path) { push_value(vm, make_int(0)); break; } - sqlite3 *db = NULL; - int rc = sqlite3_open(path, &db); - free(path); - if (rc != SQLITE_OK || !db) { - if (db) sqlite3_close(db); - push_value(vm, make_int(0)); - break; - } - SqlHandle *h = sql_reg_add(db); - if (!h) { sqlite3_close(db); push_value(vm, make_int(0)); break; } - push_value(vm, make_int(h->id)); -#else - Value v = pop_value(vm); free_value(v); + Value vpath = pop_value(vm); + char *path = value_to_string_alloc(&vpath); + free_value(vpath); + if (!path) { push_value(vm, make_int(0)); -#endif break; + } + sqlite3 *db = NULL; + int rc = sqlite3_open(path, &db); + free(path); + if (rc != SQLITE_OK || !db) { + if (db) sqlite3_close(db); + push_value(vm, make_int(0)); + break; + } + SqlHandle *h = sql_reg_add(db); + if (!h) { + sqlite3_close(db); + push_value(vm, make_int(0)); + break; + } + push_value(vm, make_int(h->id)); +#else + Value v = pop_value(vm); + free_value(v); + push_value(vm, make_int(0)); +#endif + break; } diff --git a/src/vm/sqlite/query.c b/src/vm/sqlite/query.c index fb3c6d8..e08424b 100644 --- a/src/vm/sqlite/query.c +++ b/src/vm/sqlite/query.c @@ -14,49 +14,65 @@ */ case OP_SQLITE_QUERY: { #ifdef FUN_WITH_SQLITE - Value vsql = pop_value(vm); - Value vh = pop_value(vm); - int hid = (int)vh.i; - char *sql = value_to_string_alloc(&vsql); - free_value(vh); - free_value(vsql); - SqlHandle *h = sql_reg_get(hid); - if (!h || !h->db || !sql) { if (sql) free(sql); push_value(vm, make_array_from_values(NULL, 0)); break; } - sqlite3_stmt *stmt = NULL; - if (sqlite3_prepare_v2(h->db, sql, -1, &stmt, NULL) != SQLITE_OK) { - free(sql); - push_value(vm, make_array_from_values(NULL, 0)); - break; - } - free(sql); - Value rows = make_array_from_values(NULL, 0); - int ncols = sqlite3_column_count(stmt); - while (sqlite3_step(stmt) == SQLITE_ROW) { - Value row = make_map_empty(); - for (int i = 0; i < ncols; i++) { - const char *name = sqlite3_column_name(stmt, i); - int type = sqlite3_column_type(stmt, i); - Value kv; - switch (type) { - case SQLITE_INTEGER: kv = make_int((int64_t)sqlite3_column_int64(stmt, i)); break; - case SQLITE_FLOAT: kv = make_float(sqlite3_column_double(stmt, i)); break; - case SQLITE_TEXT: kv = make_string((const char*)sqlite3_column_text(stmt, i)); break; - case SQLITE_NULL: kv = make_nil(); break; - default: kv = make_nil(); break; /* ignore blobs for now */ - } - (void)map_set(&row, name ? name : "", kv); - } - (void)array_push(&rows, row); - /* Do NOT free 'row' here: rows array now owns it. Freeing would - destroy the map and leave a dangling pointer causing segfaults - when accessing fields like row["done"]. */ - } - sqlite3_finalize(stmt); - push_value(vm, rows); -#else - Value v1 = pop_value(vm); free_value(v1); - Value v2 = pop_value(vm); free_value(v2); + Value vsql = pop_value(vm); + Value vh = pop_value(vm); + int hid = (int)vh.i; + char *sql = value_to_string_alloc(&vsql); + free_value(vh); + free_value(vsql); + SqlHandle *h = sql_reg_get(hid); + if (!h || !h->db || !sql) { + if (sql) free(sql); push_value(vm, make_array_from_values(NULL, 0)); -#endif break; + } + sqlite3_stmt *stmt = NULL; + if (sqlite3_prepare_v2(h->db, sql, -1, &stmt, NULL) != SQLITE_OK) { + free(sql); + push_value(vm, make_array_from_values(NULL, 0)); + break; + } + free(sql); + Value rows = make_array_from_values(NULL, 0); + int ncols = sqlite3_column_count(stmt); + while (sqlite3_step(stmt) == SQLITE_ROW) { + Value row = make_map_empty(); + for (int i = 0; i < ncols; i++) { + const char *name = sqlite3_column_name(stmt, i); + int type = sqlite3_column_type(stmt, i); + Value kv; + switch (type) { + case SQLITE_INTEGER: + kv = make_int((int64_t)sqlite3_column_int64(stmt, i)); + break; + case SQLITE_FLOAT: + kv = make_float(sqlite3_column_double(stmt, i)); + break; + case SQLITE_TEXT: + kv = make_string((const char *)sqlite3_column_text(stmt, i)); + break; + case SQLITE_NULL: + kv = make_nil(); + break; + default: + kv = make_nil(); + break; /* ignore blobs for now */ + } + (void)map_set(&row, name ? name : "", kv); + } + (void)array_push(&rows, row); + /* Do NOT free 'row' here: rows array now owns it. Freeing would + destroy the map and leave a dangling pointer causing segfaults + when accessing fields like row["done"]. */ + } + sqlite3_finalize(stmt); + push_value(vm, rows); +#else + Value v1 = pop_value(vm); + free_value(v1); + Value v2 = pop_value(vm); + free_value(v2); + push_value(vm, make_array_from_values(NULL, 0)); +#endif + break; } diff --git a/src/vm/strings/find.c b/src/vm/strings/find.c index 48dfccc..24d9415 100644 --- a/src/vm/strings/find.c +++ b/src/vm/strings/find.c @@ -8,7 +8,7 @@ */ /** -* @file find.c + * @file find.c * @brief Implements the OP_FIND opcode for finding substrings in the VM. * * This file handles the OP_FIND instruction, which finds the index of a substring @@ -33,15 +33,15 @@ */ case OP_FIND: { - Value needle = pop_value(vm); - Value hay = pop_value(vm); - if (hay.type != VAL_STRING || needle.type != VAL_STRING) { - fprintf(stderr, "Runtime type error: FIND expects (string, string)\n"); - exit(1); - } - int idx = bi_find(&hay, &needle); - free_value(hay); - free_value(needle); - push_value(vm, make_int(idx)); - break; + Value needle = pop_value(vm); + Value hay = pop_value(vm); + if (hay.type != VAL_STRING || needle.type != VAL_STRING) { + fprintf(stderr, "Runtime type error: FIND expects (string, string)\n"); + exit(1); + } + int idx = bi_find(&hay, &needle); + free_value(hay); + free_value(needle); + push_value(vm, make_int(idx)); + break; } diff --git a/src/vm/strings/regex_match.c b/src/vm/strings/regex_match.c index 53390ab..857e1e2 100644 --- a/src/vm/strings/regex_match.c +++ b/src/vm/strings/regex_match.c @@ -15,43 +15,43 @@ #endif case OP_REGEX_MATCH: { - Value pattern = pop_value(vm); - Value str = pop_value(vm); - if (str.type != VAL_STRING || pattern.type != VAL_STRING) { - fprintf(stderr, "Runtime type error: REGEX_MATCH expects (string, string)\n"); - exit(1); - } + Value pattern = pop_value(vm); + Value str = pop_value(vm); + if (str.type != VAL_STRING || pattern.type != VAL_STRING) { + fprintf(stderr, "Runtime type error: REGEX_MATCH expects (string, string)\n"); + exit(1); + } #ifndef __unix__ - /* Not supported on non-UNIX: return 0 gracefully */ - free_value(pattern); - int truth = 0; - free_value(str); - push_value(vm, make_int(truth)); - break; + /* Not supported on non-UNIX: return 0 gracefully */ + free_value(pattern); + int truth = 0; + free_value(str); + push_value(vm, make_int(truth)); + break; #else - regex_t rx; - int rc = regcomp(&rx, pattern.s ? pattern.s : "", REG_EXTENDED); - if (rc != 0) { - /* invalid regex -> false */ - free_value(pattern); - free_value(str); - push_value(vm, make_int(0)); - break; - } - regmatch_t m; - int ok = regexec(&rx, str.s ? str.s : "", 1, &m, 0) == 0; - int truth = 0; - if (ok) { - /* full match means the match spans whole string */ - if (m.rm_so == 0 && str.s) { - size_t slen = strlen(str.s); - truth = (m.rm_eo == (regoff_t)slen) ? 1 : 0; - } - } - regfree(&rx); + regex_t rx; + int rc = regcomp(&rx, pattern.s ? pattern.s : "", REG_EXTENDED); + if (rc != 0) { + /* invalid regex -> false */ free_value(pattern); free_value(str); - push_value(vm, make_int(truth)); + push_value(vm, make_int(0)); break; + } + regmatch_t m; + int ok = regexec(&rx, str.s ? str.s : "", 1, &m, 0) == 0; + int truth = 0; + if (ok) { + /* full match means the match spans whole string */ + if (m.rm_so == 0 && str.s) { + size_t slen = strlen(str.s); + truth = (m.rm_eo == (regoff_t)slen) ? 1 : 0; + } + } + regfree(&rx); + free_value(pattern); + free_value(str); + push_value(vm, make_int(truth)); + break; #endif } diff --git a/src/vm/strings/regex_replace.c b/src/vm/strings/regex_replace.c index 014e0b4..37242b0 100644 --- a/src/vm/strings/regex_replace.c +++ b/src/vm/strings/regex_replace.c @@ -12,105 +12,108 @@ /* Regex global replace opcode using POSIX regex */ #ifdef __unix__ #include -#include #include +#include #endif case OP_REGEX_REPLACE: { - Value repl = pop_value(vm); - Value pattern = pop_value(vm); - Value str = pop_value(vm); - if (str.type != VAL_STRING || pattern.type != VAL_STRING || repl.type != VAL_STRING) { - fprintf(stderr, "Runtime type error: REGEX_REPLACE expects (string, string, string)\n"); - exit(1); - } + Value repl = pop_value(vm); + Value pattern = pop_value(vm); + Value str = pop_value(vm); + if (str.type != VAL_STRING || pattern.type != VAL_STRING || repl.type != VAL_STRING) { + fprintf(stderr, "Runtime type error: REGEX_REPLACE expects (string, string, string)\n"); + exit(1); + } #ifndef __unix__ - /* Not supported: return original string */ + /* Not supported: return original string */ + Value out = make_string(str.s ? str.s : ""); + free_value(repl); + free_value(pattern); + free_value(str); + push_value(vm, out); + break; +#else + regex_t rx; + int rc = regcomp(&rx, pattern.s ? pattern.s : "", REG_EXTENDED); + if (rc != 0) { + /* invalid regex -> return original */ Value out = make_string(str.s ? str.s : ""); free_value(repl); free_value(pattern); free_value(str); push_value(vm, out); break; -#else - regex_t rx; - int rc = regcomp(&rx, pattern.s ? pattern.s : "", REG_EXTENDED); - if (rc != 0) { - /* invalid regex -> return original */ - Value out = make_string(str.s ? str.s : ""); - free_value(repl); - free_value(pattern); - free_value(str); - push_value(vm, out); + } + + const char *s = str.s ? str.s : ""; + const char *r = repl.s ? repl.s : ""; + + size_t out_cap = strlen(s) + 1; + char *outbuf = (char *)malloc(out_cap); + size_t out_len = 0; + size_t pos = 0; + + enum { MAX_CAP = 16 }; + regmatch_t caps[MAX_CAP]; + + while (1) { + if (regexec(&rx, s + pos, MAX_CAP, caps, 0) != 0) { + /* no more matches: append the rest */ + size_t rest = strlen(s + pos); + if (out_len + rest + 1 > out_cap) { + out_cap = out_len + rest + 1; + outbuf = (char *)realloc(outbuf, out_cap); + } + memcpy(outbuf + out_len, s + pos, rest + 1); + out_len += rest; + break; + } + int mstart = (int)caps[0].rm_so; + int mend = (int)caps[0].rm_eo; + if (mstart < 0 || mend < mstart) { + /* Shouldn't happen, avoid infinite loop */ + break; + } + /* append prefix */ + size_t pre_len = (size_t)mstart; + if (out_len + pre_len + 1 > out_cap) { + out_cap = (out_len + pre_len + 1) * 2; + outbuf = (char *)realloc(outbuf, out_cap); + } + memcpy(outbuf + out_len, s + pos, pre_len); + out_len += pre_len; + + /* append replacement (no backref expansion for simplicity) */ + size_t rlen = strlen(r); + if (out_len + rlen + 1 > out_cap) { + out_cap = (out_len + rlen + 1) * 2; + outbuf = (char *)realloc(outbuf, out_cap); + } + memcpy(outbuf + out_len, r, rlen); + out_len += rlen; + + /* advance */ + pos += (size_t)mend; + if (mend == 0) { /* prevent zero-length match infinite loop */ + if (pos < strlen(s)) { + if (out_len + 1 > out_cap) { + out_cap = out_len + 2; + outbuf = (char *)realloc(outbuf, out_cap); + } + outbuf[out_len++] = s[pos++]; + } else { break; + } } + } - const char *s = str.s ? str.s : ""; - const char *r = repl.s ? repl.s : ""; - - size_t out_cap = strlen(s) + 1; - char *outbuf = (char*)malloc(out_cap); - size_t out_len = 0; - size_t pos = 0; - - enum { MAX_CAP = 16 }; - regmatch_t caps[MAX_CAP]; - - while (1) { - if (regexec(&rx, s + pos, MAX_CAP, caps, 0) != 0) { - /* no more matches: append the rest */ - size_t rest = strlen(s + pos); - if (out_len + rest + 1 > out_cap) { - out_cap = out_len + rest + 1; - outbuf = (char*)realloc(outbuf, out_cap); - } - memcpy(outbuf + out_len, s + pos, rest + 1); - out_len += rest; - break; - } - int mstart = (int)caps[0].rm_so; - int mend = (int)caps[0].rm_eo; - if (mstart < 0 || mend < mstart) { - /* Shouldn't happen, avoid infinite loop */ - break; - } - /* append prefix */ - size_t pre_len = (size_t)mstart; - if (out_len + pre_len + 1 > out_cap) { - out_cap = (out_len + pre_len + 1) * 2; - outbuf = (char*)realloc(outbuf, out_cap); - } - memcpy(outbuf + out_len, s + pos, pre_len); - out_len += pre_len; - - /* append replacement (no backref expansion for simplicity) */ - size_t rlen = strlen(r); - if (out_len + rlen + 1 > out_cap) { - out_cap = (out_len + rlen + 1) * 2; - outbuf = (char*)realloc(outbuf, out_cap); - } - memcpy(outbuf + out_len, r, rlen); - out_len += rlen; - - /* advance */ - pos += (size_t)mend; - if (mend == 0) { /* prevent zero-length match infinite loop */ - if (pos < strlen(s)) { - if (out_len + 1 > out_cap) { out_cap = out_len + 2; outbuf = (char*)realloc(outbuf, out_cap);} - outbuf[out_len++] = s[pos++]; - } else { - break; - } - } - } - - Value out = make_string(outbuf ? outbuf : ""); - if (outbuf) free(outbuf); - regfree(&rx); - free_value(repl); - free_value(pattern); - free_value(str); - push_value(vm, out); - break; + Value out = make_string(outbuf ? outbuf : ""); + if (outbuf) free(outbuf); + regfree(&rx); + free_value(repl); + free_value(pattern); + free_value(str); + push_value(vm, out); + break; #endif } diff --git a/src/vm/strings/regex_search.c b/src/vm/strings/regex_search.c index 8b1cf49..1d4fb82 100644 --- a/src/vm/strings/regex_search.c +++ b/src/vm/strings/regex_search.c @@ -12,19 +12,34 @@ /* Regex search (first match) opcode using POSIX regex */ #ifdef __unix__ #include -#include #include +#include #endif case OP_REGEX_SEARCH: { - Value pattern = pop_value(vm); - Value str = pop_value(vm); - if (str.type != VAL_STRING || pattern.type != VAL_STRING) { - fprintf(stderr, "Runtime type error: REGEX_SEARCH expects (string, string)\n"); - exit(1); - } + Value pattern = pop_value(vm); + Value str = pop_value(vm); + if (str.type != VAL_STRING || pattern.type != VAL_STRING) { + fprintf(stderr, "Runtime type error: REGEX_SEARCH expects (string, string)\n"); + exit(1); + } #ifndef __unix__ - /* Return default empty result on unsupported platforms */ + /* Return default empty result on unsupported platforms */ + Value m = make_map_empty(); + (void)map_set(&m, "match", make_string("")); + (void)map_set(&m, "start", make_int(-1)); + (void)map_set(&m, "end", make_int(-1)); + Value emptyArr = make_array_from_values(NULL, 0); + (void)map_set(&m, "groups", emptyArr); + free_value(pattern); + free_value(str); + push_value(vm, m); + break; +#else + regex_t rx; + int rc = regcomp(&rx, pattern.s ? pattern.s : "", REG_EXTENDED); + if (rc != 0) { + /* invalid regex -> empty result */ Value m = make_map_empty(); (void)map_set(&m, "match", make_string("")); (void)map_set(&m, "start", make_int(-1)); @@ -35,79 +50,71 @@ case OP_REGEX_SEARCH: { free_value(str); push_value(vm, m); break; -#else - regex_t rx; - int rc = regcomp(&rx, pattern.s ? pattern.s : "", REG_EXTENDED); - if (rc != 0) { - /* invalid regex -> empty result */ - Value m = make_map_empty(); - (void)map_set(&m, "match", make_string("")); - (void)map_set(&m, "start", make_int(-1)); - (void)map_set(&m, "end", make_int(-1)); - Value emptyArr = make_array_from_values(NULL, 0); - (void)map_set(&m, "groups", emptyArr); - free_value(pattern); - free_value(str); - push_value(vm, m); - break; + } + /* capture up to, say, 10 groups (including whole match) */ + enum { MAX_CAP = 16 }; + regmatch_t caps[MAX_CAP]; + int ok = regexec(&rx, str.s ? str.s : "", MAX_CAP, caps, 0) == 0; + Value outMap = make_map_empty(); + if (!ok) { + (void)map_set(&outMap, "match", make_string("")); + (void)map_set(&outMap, "start", make_int(-1)); + (void)map_set(&outMap, "end", make_int(-1)); + Value emptyArr = make_array_from_values(NULL, 0); + (void)map_set(&outMap, "groups", emptyArr); + } else { + int s = (int)caps[0].rm_so; + int e = (int)caps[0].rm_eo; + char *matchStr = NULL; + if (str.s && s >= 0 && e >= s) { + int len = e - s; + matchStr = (char *)malloc((size_t)len + 1); + if (matchStr) { + memcpy(matchStr, str.s + s, (size_t)len); + matchStr[len] = '\0'; + } } - /* capture up to, say, 10 groups (including whole match) */ - enum { MAX_CAP = 16 }; - regmatch_t caps[MAX_CAP]; - int ok = regexec(&rx, str.s ? str.s : "", MAX_CAP, caps, 0) == 0; - Value outMap = make_map_empty(); - if (!ok) { - (void)map_set(&outMap, "match", make_string("")); - (void)map_set(&outMap, "start", make_int(-1)); - (void)map_set(&outMap, "end", make_int(-1)); - Value emptyArr = make_array_from_values(NULL, 0); - (void)map_set(&outMap, "groups", emptyArr); - } else { - int s = (int)caps[0].rm_so; - int e = (int)caps[0].rm_eo; - char *matchStr = NULL; - if (str.s && s >= 0 && e >= s) { - int len = e - s; - matchStr = (char*)malloc((size_t)len + 1); - if (matchStr) { memcpy(matchStr, str.s + s, (size_t)len); matchStr[len] = '\0'; } - } - (void)map_set(&outMap, "match", make_string(matchStr ? matchStr : "")); - if (matchStr) free(matchStr); - (void)map_set(&outMap, "start", make_int(s)); - (void)map_set(&outMap, "end", make_int(e)); - /* groups 1..n */ - Value groupsArr = make_array_from_values(NULL, 0); - /* Count groups available */ - int groupCount = 0; - for (int i = 1; i < MAX_CAP; ++i) { - if (caps[i].rm_so == -1 || caps[i].rm_eo == -1) break; - groupCount++; - } - if (groupCount > 0) { - Value *vals = (Value*)calloc((size_t)groupCount, sizeof(Value)); - int vi = 0; - for (int i = 1; i <= groupCount; ++i) { - int gs = (int)caps[i].rm_so; - int ge = (int)caps[i].rm_eo; - char *gstr = NULL; - if (str.s && gs >= 0 && ge >= gs) { - int gl = ge - gs; - gstr = (char*)malloc((size_t)gl + 1); - if (gstr) { memcpy(gstr, str.s + gs, (size_t)gl); gstr[gl] = '\0'; } - } - vals[vi++] = make_string(gstr ? gstr : ""); - if (gstr) free(gstr); - } - groupsArr = make_array_from_values(vals, groupCount); - for (int i = 0; i < groupCount; ++i) free_value(vals[i]); - free(vals); - } - (void)map_set(&outMap, "groups", groupsArr); + (void)map_set(&outMap, "match", make_string(matchStr ? matchStr : "")); + if (matchStr) free(matchStr); + (void)map_set(&outMap, "start", make_int(s)); + (void)map_set(&outMap, "end", make_int(e)); + /* groups 1..n */ + Value groupsArr = make_array_from_values(NULL, 0); + /* Count groups available */ + int groupCount = 0; + for (int i = 1; i < MAX_CAP; ++i) { + if (caps[i].rm_so == -1 || caps[i].rm_eo == -1) break; + groupCount++; } - regfree(&rx); - free_value(pattern); - free_value(str); - push_value(vm, outMap); - break; + if (groupCount > 0) { + Value *vals = (Value *)calloc((size_t)groupCount, sizeof(Value)); + int vi = 0; + for (int i = 1; i <= groupCount; ++i) { + int gs = (int)caps[i].rm_so; + int ge = (int)caps[i].rm_eo; + char *gstr = NULL; + if (str.s && gs >= 0 && ge >= gs) { + int gl = ge - gs; + gstr = (char *)malloc((size_t)gl + 1); + if (gstr) { + memcpy(gstr, str.s + gs, (size_t)gl); + gstr[gl] = '\0'; + } + } + vals[vi++] = make_string(gstr ? gstr : ""); + if (gstr) free(gstr); + } + groupsArr = make_array_from_values(vals, groupCount); + for (int i = 0; i < groupCount; ++i) + free_value(vals[i]); + free(vals); + } + (void)map_set(&outMap, "groups", groupsArr); + } + regfree(&rx); + free_value(pattern); + free_value(str); + push_value(vm, outMap); + break; #endif } diff --git a/src/vm/strings/split.c b/src/vm/strings/split.c index c93e08f..626f876 100644 --- a/src/vm/strings/split.c +++ b/src/vm/strings/split.c @@ -8,7 +8,7 @@ */ /** -* @file split.c + * @file split.c * @brief Implements the OP_SPLIT opcode for splitting strings in the VM. * * This file handles the OP_SPLIT instruction, which splits a string into an array @@ -33,15 +33,15 @@ */ case OP_SPLIT: { - Value sep = pop_value(vm); - Value str = pop_value(vm); - if (str.type != VAL_STRING || sep.type != VAL_STRING) { - fprintf(stderr, "Runtime type error: SPLIT expects (string, string)\n"); - exit(1); - } - Value out = bi_split(&str, &sep); - free_value(str); - free_value(sep); - push_value(vm, out); - break; + Value sep = pop_value(vm); + Value str = pop_value(vm); + if (str.type != VAL_STRING || sep.type != VAL_STRING) { + fprintf(stderr, "Runtime type error: SPLIT expects (string, string)\n"); + exit(1); + } + Value out = bi_split(&str, &sep); + free_value(str); + free_value(sep); + push_value(vm, out); + break; } diff --git a/src/vm/strings/substr.c b/src/vm/strings/substr.c index 6b62375..763036c 100644 --- a/src/vm/strings/substr.c +++ b/src/vm/strings/substr.c @@ -8,7 +8,7 @@ */ /** -* @file substr.c + * @file substr.c * @brief Implements the OP_SUBSTR opcode for extracting substrings in the VM. * * This file handles the OP_SUBSTR instruction, which extracts a substring from a string @@ -34,17 +34,17 @@ */ case OP_SUBSTR: { - Value lenv = pop_value(vm); - Value startv = pop_value(vm); - Value str = pop_value(vm); - if (str.type != VAL_STRING || startv.type != VAL_INT || lenv.type != VAL_INT) { - fprintf(stderr, "Runtime type error: SUBSTR expects (string, int, int)\n"); - exit(1); - } - Value out = bi_substr(&str, (int)startv.i, (int)lenv.i); - free_value(str); - free_value(startv); - free_value(lenv); - push_value(vm, out); - break; + Value lenv = pop_value(vm); + Value startv = pop_value(vm); + Value str = pop_value(vm); + if (str.type != VAL_STRING || startv.type != VAL_INT || lenv.type != VAL_INT) { + fprintf(stderr, "Runtime type error: SUBSTR expects (string, int, int)\n"); + exit(1); + } + Value out = bi_substr(&str, (int)startv.i, (int)lenv.i); + free_value(str); + free_value(startv); + free_value(lenv); + push_value(vm, out); + break; } diff --git a/src/vm/tk/bind.c b/src/vm/tk/bind.c index 78fb732..1a097e4 100644 --- a/src/vm/tk/bind.c +++ b/src/vm/tk/bind.c @@ -11,44 +11,46 @@ /* TK_BIND */ case OP_TK_BIND: { - /* stack: ..., id, event, command -> rc */ - Value cmdv = pop_value(vm); - Value eventv = pop_value(vm); - Value idv = pop_value(vm); - char *cmd = value_to_string_alloc(&cmdv); - char *event = value_to_string_alloc(&eventv); - char *id = value_to_string_alloc(&idv); - free_value(cmdv); - free_value(eventv); - free_value(idv); + /* stack: ..., id, event, command -> rc */ + Value cmdv = pop_value(vm); + Value eventv = pop_value(vm); + Value idv = pop_value(vm); + char *cmd = value_to_string_alloc(&cmdv); + char *event = value_to_string_alloc(&eventv); + char *id = value_to_string_alloc(&idv); + free_value(cmdv); + free_value(eventv); + free_value(idv); - if (!id || !event || !cmd) { - if (id) free(id); - if (event) free(event); - if (cmd) free(cmd); - push_value(vm, make_int(-1)); - break; - } + if (!id || !event || !cmd) { + if (id) free(id); + if (event) free(event); + if (cmd) free(cmd); + push_value(vm, make_int(-1)); + break; + } - /* - * Construct: bind .id {command} - * Note: for now, command is just raw Tcl as well, - * but could be extended to call Fun functions if we had a callback mechanism. - */ - size_t slen = strlen(id) + strlen(event) + strlen(cmd) + 32; - char *script = (char*)malloc(slen); - if (!script) { - free(id); free(event); free(cmd); - push_value(vm, make_int(-1)); - break; - } - - snprintf(script, slen, "bind .%s %s {%s}", id, event, cmd); - int rc = fun_tk_eval_script(script); - free(script); + /* + * Construct: bind .id {command} + * Note: for now, command is just raw Tcl as well, + * but could be extended to call Fun functions if we had a callback mechanism. + */ + size_t slen = strlen(id) + strlen(event) + strlen(cmd) + 32; + char *script = (char *)malloc(slen); + if (!script) { free(id); free(event); free(cmd); - push_value(vm, make_int(rc)); + push_value(vm, make_int(-1)); break; + } + + snprintf(script, slen, "bind .%s %s {%s}", id, event, cmd); + int rc = fun_tk_eval_script(script); + free(script); + free(id); + free(event); + free(cmd); + push_value(vm, make_int(rc)); + break; } diff --git a/src/vm/tk/button.c b/src/vm/tk/button.c index 8b62792..723a6be 100644 --- a/src/vm/tk/button.c +++ b/src/vm/tk/button.c @@ -1,34 +1,58 @@ /* TK_BUTTON */ case OP_TK_BUTTON: { - /* stack: ..., id, text -> rc */ - Value textv = pop_value(vm); - Value idv = pop_value(vm); - char *text = value_to_string_alloc(&textv); - char *id = value_to_string_alloc(&idv); - free_value(textv); - free_value(idv); - if (!id) { if (text) free(text); push_value(vm, make_int(-1)); break; } - if (!text) { text = strdup(""); } - size_t n = 0; for (const char *p = text; *p; ++p) { n += (*p == '\\' || *p == '"') ? 2 : 1; } - char *et = (char*)malloc(n + 1); - if (!et) { free(id); free(text); push_value(vm, make_int(-1)); break; } - char *q = et; for (const char *p = text; *p; ++p) { if (*p == '\\' || *p == '"') *q++ = '\\'; *q++ = *p; } *q = '\0'; + /* stack: ..., id, text -> rc */ + Value textv = pop_value(vm); + Value idv = pop_value(vm); + char *text = value_to_string_alloc(&textv); + char *id = value_to_string_alloc(&idv); + free_value(textv); + free_value(idv); + if (!id) { + if (text) free(text); + push_value(vm, make_int(-1)); + break; + } + if (!text) { + text = strdup(""); + } + size_t n = 0; + for (const char *p = text; *p; ++p) { + n += (*p == '\\' || *p == '"') ? 2 : 1; + } + char *et = (char *)malloc(n + 1); + if (!et) { + free(id); free(text); - size_t slen = strlen(id) + strlen(et) + 196; - char *script = (char*)malloc(slen); - if (!script) { free(id); free(et); push_value(vm, make_int(-1)); break; } - /* - * Default behavior: clicking the button should terminate the app. We set - * -command {catch {destroy .}; exit 0} to both destroy the window and exit - * the process. Using 'catch' makes it safe if the window is already gone. - */ - snprintf(script, slen, - "if {[winfo exists .%s]} { .%s configure -text \"%s\" -command {catch {destroy .}; exit 0} } else { button .%s -text \"%s\" -command {catch {destroy .}; exit 0} }", - id, id, et, id, et); - int rc = fun_tk_eval_script(script); - free(script); + push_value(vm, make_int(-1)); + break; + } + char *q = et; + for (const char *p = text; *p; ++p) { + if (*p == '\\' || *p == '"') *q++ = '\\'; + *q++ = *p; + } + *q = '\0'; + free(text); + size_t slen = strlen(id) + strlen(et) + 196; + char *script = (char *)malloc(slen); + if (!script) { free(id); free(et); - push_value(vm, make_int(rc)); + push_value(vm, make_int(-1)); break; + } + /* + * Default behavior: clicking the button should terminate the app. We set + * -command {catch {destroy .}; exit 0} to both destroy the window and exit + * the process. Using 'catch' makes it safe if the window is already gone. + */ + snprintf(script, slen, + "if {[winfo exists .%s]} { .%s configure -text \"%s\" -command {catch {destroy .}; exit 0} } else { button .%s -text \"%s\" -command {catch {destroy .}; exit 0} }", + id, id, et, id, et); + int rc = fun_tk_eval_script(script); + free(script); + free(id); + free(et); + push_value(vm, make_int(rc)); + break; } diff --git a/src/vm/tk/eval.c b/src/vm/tk/eval.c index c2fd543..bac3cb1 100644 --- a/src/vm/tk/eval.c +++ b/src/vm/tk/eval.c @@ -1,5 +1,5 @@ /* -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -8,14 +8,14 @@ * * Added: 2025-12-09 */ - - /* TK_EVAL */ + +/* TK_EVAL */ case OP_TK_EVAL: { - Value text = pop_value(vm); - char *s = value_to_string_alloc(&text); - free_value(text); - int rc = fun_tk_eval_script(s ? s : ""); - if (s) free(s); - push_value(vm, make_int(rc)); - break; + Value text = pop_value(vm); + char *s = value_to_string_alloc(&text); + free_value(text); + int rc = fun_tk_eval_script(s ? s : ""); + if (s) free(s); + push_value(vm, make_int(rc)); + break; } diff --git a/src/vm/tk/label.c b/src/vm/tk/label.c index cb4dcfd..48ba676 100644 --- a/src/vm/tk/label.c +++ b/src/vm/tk/label.c @@ -1,31 +1,55 @@ /* TK_LABEL */ case OP_TK_LABEL: { - /* stack: ..., id, text -> rc */ - Value textv = pop_value(vm); - Value idv = pop_value(vm); - char *text = value_to_string_alloc(&textv); - char *id = value_to_string_alloc(&idv); - free_value(textv); - free_value(idv); - if (!id) { if (text) free(text); push_value(vm, make_int(-1)); break; } - if (!text) { text = strdup(""); } - /* escape id minimally (dots and word chars are fine) -> just use as-is */ - /* escape text for Tcl double quotes */ - size_t n = 0; for (const char *p = text; *p; ++p) { n += (*p == '\\' || *p == '"') ? 2 : 1; } - char *et = (char*)malloc(n + 1); - if (!et) { free(id); free(text); push_value(vm, make_int(-1)); break; } - char *q = et; for (const char *p = text; *p; ++p) { if (*p == '\\' || *p == '"') *q++ = '\\'; *q++ = *p; } *q = '\0'; + /* stack: ..., id, text -> rc */ + Value textv = pop_value(vm); + Value idv = pop_value(vm); + char *text = value_to_string_alloc(&textv); + char *id = value_to_string_alloc(&idv); + free_value(textv); + free_value(idv); + if (!id) { + if (text) free(text); + push_value(vm, make_int(-1)); + break; + } + if (!text) { + text = strdup(""); + } + /* escape id minimally (dots and word chars are fine) -> just use as-is */ + /* escape text for Tcl double quotes */ + size_t n = 0; + for (const char *p = text; *p; ++p) { + n += (*p == '\\' || *p == '"') ? 2 : 1; + } + char *et = (char *)malloc(n + 1); + if (!et) { + free(id); free(text); - size_t slen = strlen(id) + strlen(et) + 128; - char *script = (char*)malloc(slen); - if (!script) { free(id); free(et); push_value(vm, make_int(-1)); break; } - snprintf(script, slen, - "if {[winfo exists .%s]} { .%s configure -text \"%s\" } else { label .%s -text \"%s\" }", - id, id, et, id, et); - int rc = fun_tk_eval_script(script); - free(script); + push_value(vm, make_int(-1)); + break; + } + char *q = et; + for (const char *p = text; *p; ++p) { + if (*p == '\\' || *p == '"') *q++ = '\\'; + *q++ = *p; + } + *q = '\0'; + free(text); + size_t slen = strlen(id) + strlen(et) + 128; + char *script = (char *)malloc(slen); + if (!script) { free(id); free(et); - push_value(vm, make_int(rc)); + push_value(vm, make_int(-1)); break; + } + snprintf(script, slen, + "if {[winfo exists .%s]} { .%s configure -text \"%s\" } else { label .%s -text \"%s\" }", + id, id, et, id, et); + int rc = fun_tk_eval_script(script); + free(script); + free(id); + free(et); + push_value(vm, make_int(rc)); + break; } diff --git a/src/vm/tk/loop.c b/src/vm/tk/loop.c index a327b40..4150ae3 100644 --- a/src/vm/tk/loop.c +++ b/src/vm/tk/loop.c @@ -11,13 +11,13 @@ /* TK_LOOP */ case OP_TK_LOOP: { - fun_tk_loop(); + fun_tk_loop(); #ifdef FUN_WITH_TCLTK - /* Ensure the process terminates once the GUI window(s) are closed. */ - exit(0); + /* Ensure the process terminates once the GUI window(s) are closed. */ + exit(0); #else - /* When Tk support is not compiled in, behave as a no-op returning Nil. */ - push_value(vm, make_nil()); + /* When Tk support is not compiled in, behave as a no-op returning Nil. */ + push_value(vm, make_nil()); #endif - break; /* not reached when FUN_WITH_TCLTK */ + break; /* not reached when FUN_WITH_TCLTK */ } diff --git a/src/vm/tk/pack.c b/src/vm/tk/pack.c index 71e4345..ff87d5d 100644 --- a/src/vm/tk/pack.c +++ b/src/vm/tk/pack.c @@ -1,16 +1,23 @@ /* TK_PACK */ case OP_TK_PACK: { - Value idv = pop_value(vm); - char *id = value_to_string_alloc(&idv); - free_value(idv); - if (!id) { push_value(vm, make_int(-1)); break; } - size_t slen = strlen(id) + 16; - char *script = (char*)malloc(slen); - if (!script) { free(id); push_value(vm, make_int(-1)); break; } - snprintf(script, slen, "pack .%s", id); - int rc = fun_tk_eval_script(script); - free(script); - free(id); - push_value(vm, make_int(rc)); + Value idv = pop_value(vm); + char *id = value_to_string_alloc(&idv); + free_value(idv); + if (!id) { + push_value(vm, make_int(-1)); break; + } + size_t slen = strlen(id) + 16; + char *script = (char *)malloc(slen); + if (!script) { + free(id); + push_value(vm, make_int(-1)); + break; + } + snprintf(script, slen, "pack .%s", id); + int rc = fun_tk_eval_script(script); + free(script); + free(id); + push_value(vm, make_int(rc)); + break; } diff --git a/src/vm/tk/result.c b/src/vm/tk/result.c index 36f9a54..b680884 100644 --- a/src/vm/tk/result.c +++ b/src/vm/tk/result.c @@ -11,7 +11,7 @@ /* TK_RESULT */ case OP_TK_RESULT: { - const char *r = fun_tk_get_result(); - push_value(vm, make_string(r ? r : "")); - break; + const char *r = fun_tk_get_result(); + push_value(vm, make_string(r ? r : "")); + break; } diff --git a/src/vm/tk/wm_title.c b/src/vm/tk/wm_title.c index 5346b6e..5ca852f 100644 --- a/src/vm/tk/wm_title.c +++ b/src/vm/tk/wm_title.c @@ -11,23 +11,42 @@ /* TK_WM_TITLE */ case OP_TK_WM_TITLE: { - Value titlev = pop_value(vm); - char *title = value_to_string_alloc(&titlev); - free_value(titlev); - if (!title) { push_value(vm, make_int(-1)); break; } - /* Escape backslashes and double quotes for Tcl double-quoted strings */ - size_t n = 0; for (const char *p = title; *p; ++p) { n += (*p == '\\' || *p == '"') ? 2 : 1; } - char *esc = (char*)malloc(n + 1); - if (!esc) { free(title); push_value(vm, make_int(-1)); break; } - char *q = esc; for (const char *p = title; *p; ++p) { if (*p == '\\' || *p == '"') *q++ = '\\'; *q++ = *p; } *q = '\0'; - free(title); - size_t slen = strlen(esc) + 32; - char *script = (char*)malloc(slen); - if (!script) { free(esc); push_value(vm, make_int(-1)); break; } - snprintf(script, slen, "wm title . \"%s\"", esc); - int rc = fun_tk_eval_script(script); - free(script); - free(esc); - push_value(vm, make_int(rc)); + Value titlev = pop_value(vm); + char *title = value_to_string_alloc(&titlev); + free_value(titlev); + if (!title) { + push_value(vm, make_int(-1)); break; + } + /* Escape backslashes and double quotes for Tcl double-quoted strings */ + size_t n = 0; + for (const char *p = title; *p; ++p) { + n += (*p == '\\' || *p == '"') ? 2 : 1; + } + char *esc = (char *)malloc(n + 1); + if (!esc) { + free(title); + push_value(vm, make_int(-1)); + break; + } + char *q = esc; + for (const char *p = title; *p; ++p) { + if (*p == '\\' || *p == '"') *q++ = '\\'; + *q++ = *p; + } + *q = '\0'; + free(title); + size_t slen = strlen(esc) + 32; + char *script = (char *)malloc(slen); + if (!script) { + free(esc); + push_value(vm, make_int(-1)); + break; + } + snprintf(script, slen, "wm title . \"%s\"", esc); + int rc = fun_tk_eval_script(script); + free(script); + free(esc); + push_value(vm, make_int(rc)); + break; } diff --git a/src/vm/to_number.c b/src/vm/to_number.c index e26a4b3..9cb43f4 100644 --- a/src/vm/to_number.c +++ b/src/vm/to_number.c @@ -8,7 +8,7 @@ */ /** -* @file to_number.c + * @file to_number.c * @brief Implements the OP_TO_NUMBER opcode for converting values to integers in the VM. * * This file handles the OP_TO_NUMBER instruction, which converts a value to an integer. @@ -32,61 +32,64 @@ */ case OP_TO_NUMBER: { - Value v = pop_value(vm); - if (v.type == VAL_INT) { - 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); - } 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; - /* 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') { - /* 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 { - /* 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); + Value v = pop_value(vm); + if (v.type == VAL_INT) { + 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 { - free_value(v); - push_value(vm, make_int(0)); + push_value(vm, make_float(d)); } - break; + free_value(v); + } 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; + /* 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') { + /* 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 { + /* 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)); + } + break; } diff --git a/src/vm/to_string.c b/src/vm/to_string.c index 31c0932..227f1a7 100644 --- a/src/vm/to_string.c +++ b/src/vm/to_string.c @@ -8,7 +8,7 @@ */ /** -* @file to_string.c + * @file to_string.c * @brief Implements the OP_TO_STRING opcode for converting values to strings in the VM. * * This file handles the OP_TO_STRING instruction, which converts a value of any type @@ -38,11 +38,11 @@ */ 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; + 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; } diff --git a/src/vm/typeof.c b/src/vm/typeof.c index 13c3cdb..bb1484d 100644 --- a/src/vm/typeof.c +++ b/src/vm/typeof.c @@ -1,5 +1,5 @@ /** -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -8,21 +8,39 @@ */ case OP_TYPEOF: { - Value v = pop_value(vm); - 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; - case VAL_ARRAY: tname = "Array"; break; - case VAL_MAP: tname = "Map"; break; - case VAL_NIL: tname = "Nil"; break; - default: tname = "Unknown"; break; - } - /* push a new string value; make_string duplicates the C string */ - push_value(vm, make_string(tname)); - free_value(v); + Value v = pop_value(vm); + 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; + case VAL_ARRAY: + tname = "Array"; + break; + case VAL_MAP: + tname = "Map"; + break; + case VAL_NIL: + tname = "Nil"; + break; + default: + tname = "Unknown"; + break; + } + /* push a new string value; make_string duplicates the C string */ + push_value(vm, make_string(tname)); + free_value(v); + break; } diff --git a/src/vm/uclamp.c b/src/vm/uclamp.c index 46e1c28..a40a602 100644 --- a/src/vm/uclamp.c +++ b/src/vm/uclamp.c @@ -1,5 +1,5 @@ /** -* This file is part of the Fun programming language. + * This file is part of the Fun programming language. * https://fun-lang.xyz/ * * Copyright 2025 Johannes Findeisen @@ -8,22 +8,22 @@ */ case OP_UCLAMP: { - /* 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; + /* 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 mask; - if (bits <= 0) { - mask = 0ULL; - } else if (bits >= 64) { - mask = UINT64_MAX; - } else { - mask = (1ULL << bits) - 1ULL; - } + uint64_t mask; + if (bits <= 0) { + mask = 0ULL; + } else if (bits >= 64) { + mask = UINT64_MAX; + } else { + mask = (1ULL << bits) - 1ULL; + } - uint64_t wrapped = ((uint64_t)vi) & mask; - push_value(vm, make_int((int64_t)wrapped)); - free_value(v); - break; + uint64_t wrapped = ((uint64_t)vi) & mask; + push_value(vm, make_int((int64_t)wrapped)); + free_value(v); + break; } diff --git a/src/vm/xml/name.c b/src/vm/xml/name.c index e1eb0a3..2120610 100644 --- a/src/vm/xml/name.c +++ b/src/vm/xml/name.c @@ -12,15 +12,16 @@ /* OP_XML_NAME: pops node handle; pushes string */ case OP_XML_NAME: { #ifdef FUN_WITH_XML2 - Value vh = pop_value(vm); - int h = (vh.type == VAL_INT) ? (int)vh.i : 0; - xmlNodePtr n = xml_node_get(h); - free_value(vh); - const char *name = (n && n->name) ? (const char*)n->name : ""; - push_value(vm, make_string(name)); + Value vh = pop_value(vm); + int h = (vh.type == VAL_INT) ? (int)vh.i : 0; + xmlNodePtr n = xml_node_get(h); + free_value(vh); + const char *name = (n && n->name) ? (const char *)n->name : ""; + push_value(vm, make_string(name)); #else - Value drop = pop_value(vm); free_value(drop); - push_value(vm, make_string("")); + Value drop = pop_value(vm); + free_value(drop); + push_value(vm, make_string("")); #endif - break; + break; } diff --git a/src/vm/xml/parse.c b/src/vm/xml/parse.c index 4fb728e..226ef1e 100644 --- a/src/vm/xml/parse.c +++ b/src/vm/xml/parse.c @@ -12,23 +12,32 @@ /* OP_XML_PARSE: pops text string; pushes doc handle (>0) or 0 */ case OP_XML_PARSE: { #ifdef FUN_WITH_XML2 - static int xml_inited = 0; - if (!xml_inited) { xmlInitParser(); xml_inited = 1; } - Value vtext = pop_value(vm); - char *text = value_to_string_alloc(&vtext); - free_value(vtext); - if (!text) { push_value(vm, make_int(0)); break; } - xmlDocPtr doc = xmlReadMemory(text, (int)strlen(text), NULL, NULL, XML_PARSE_NONET); - free(text); - int h = 0; - if (doc) { - h = xml_doc_alloc(doc); - if (!h) { xmlFreeDoc(doc); } - } - push_value(vm, make_int(h)); -#else - Value drop = pop_value(vm); free_value(drop); + static int xml_inited = 0; + if (!xml_inited) { + xmlInitParser(); + xml_inited = 1; + } + Value vtext = pop_value(vm); + char *text = value_to_string_alloc(&vtext); + free_value(vtext); + if (!text) { push_value(vm, make_int(0)); -#endif break; + } + xmlDocPtr doc = xmlReadMemory(text, (int)strlen(text), NULL, NULL, XML_PARSE_NONET); + free(text); + int h = 0; + if (doc) { + h = xml_doc_alloc(doc); + if (!h) { + xmlFreeDoc(doc); + } + } + push_value(vm, make_int(h)); +#else + Value drop = pop_value(vm); + free_value(drop); + push_value(vm, make_int(0)); +#endif + break; } diff --git a/src/vm/xml/root.c b/src/vm/xml/root.c index d671887..49b82cf 100644 --- a/src/vm/xml/root.c +++ b/src/vm/xml/root.c @@ -12,19 +12,20 @@ /* OP_XML_ROOT: pops doc handle; pushes node handle (>0) or 0 */ case OP_XML_ROOT: { #ifdef FUN_WITH_XML2 - Value vh = pop_value(vm); - int h = (vh.type == VAL_INT) ? (int)vh.i : 0; - xmlDocPtr doc = xml_doc_get(h); - free_value(vh); - int nh = 0; - if (doc) { - xmlNodePtr root = xmlDocGetRootElement(doc); - if (root) nh = xml_node_alloc(root); - } - push_value(vm, make_int(nh)); + Value vh = pop_value(vm); + int h = (vh.type == VAL_INT) ? (int)vh.i : 0; + xmlDocPtr doc = xml_doc_get(h); + free_value(vh); + int nh = 0; + if (doc) { + xmlNodePtr root = xmlDocGetRootElement(doc); + if (root) nh = xml_node_alloc(root); + } + push_value(vm, make_int(nh)); #else - Value drop = pop_value(vm); free_value(drop); - push_value(vm, make_int(0)); + Value drop = pop_value(vm); + free_value(drop); + push_value(vm, make_int(0)); #endif - break; + break; } diff --git a/src/vm/xml/text.c b/src/vm/xml/text.c index 4eb3ebf..922d369 100644 --- a/src/vm/xml/text.c +++ b/src/vm/xml/text.c @@ -12,18 +12,25 @@ /* OP_XML_TEXT: pops node handle; pushes string (concatenate text node children) */ case OP_XML_TEXT: { #ifdef FUN_WITH_XML2 - Value vh = pop_value(vm); - int h = (vh.type == VAL_INT) ? (int)vh.i : 0; - xmlNodePtr n = xml_node_get(h); - free_value(vh); - if (!n) { push_value(vm, make_string("")); break; } - xmlChar *content = xmlNodeGetContent(n); - if (!content) { push_value(vm, make_string("")); break; } - push_value(vm, make_string((const char*)content)); - xmlFree(content); -#else - Value drop = pop_value(vm); free_value(drop); + Value vh = pop_value(vm); + int h = (vh.type == VAL_INT) ? (int)vh.i : 0; + xmlNodePtr n = xml_node_get(h); + free_value(vh); + if (!n) { push_value(vm, make_string("")); -#endif break; + } + xmlChar *content = xmlNodeGetContent(n); + if (!content) { + push_value(vm, make_string("")); + break; + } + push_value(vm, make_string((const char *)content)); + xmlFree(content); +#else + Value drop = pop_value(vm); + free_value(drop); + push_value(vm, make_string("")); +#endif + break; }