1
0
Fork 0
forked from fun/fun

Fixed the code style in *.c files to two spaces indentation and add a linter named funstx to Fun. (0.39.0)

This commit is contained in:
Johannes Findeisen 2026-03-18 20:52:00 +01:00
commit 03b2532474
237 changed files with 17550 additions and 13911 deletions

View file

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.10) 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 99)
set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_STANDARD_REQUIRED ON)
@ -46,7 +46,16 @@ message(STATUS "===========================")
# Convenience aggregate target (like 'build' in Makefile) # Convenience aggregate target (like 'build' in Makefile)
add_custom_target(build 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 --- # --- Rust (Cargo) integration: optionally build and link a staticlib with opcode examples ---
@ -400,6 +409,11 @@ install(TARGETS fun
RUNTIME DESTINATION /usr/bin RUNTIME DESTINATION /usr/bin
) )
# Also install the syntax-checker CLI by default
install(TARGETS funstx
RUNTIME DESTINATION /usr/bin
)
# Libs # Libs
install(DIRECTORY lib/ install(DIRECTORY lib/
DESTINATION /usr/share/fun/lib DESTINATION /usr/share/fun/lib

View file

@ -139,6 +139,12 @@ if(FUN_WITH_REPL)
endif() endif()
target_link_libraries(fun PRIVATE fun_core) 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 # Internal test programs
add_executable(fun_test add_executable(fun_test
${CMAKE_SOURCE_DIR}/src/fun_test.c) ${CMAKE_SOURCE_DIR}/src/fun_test.c)

View file

@ -1,24 +1,24 @@
#!/usr/bin/env fun #!/usr/bin/env fun
/* /*
* This file is part of the Fun programming language. * This file is part of the Fun programming language.
* https://fun-lang.xyz/ * https://fun-lang.xyz/
* *
* Copyright 2025 Johannes Findeisen <you@hanez.org> * Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license. * Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0 * https://opensource.org/license/apache-2-0
*/ */
/* /*
* Arrays Example * Arrays Example
* *
* Demonstrates basic array operations including: * Demonstrates basic array operations including:
* - Array declaration and initialization * - Array declaration and initialization
* - Accessing elements via indexing * - Accessing elements via indexing
* - Iterating through arrays with `for` loops * - Iterating through arrays with `for` loops
* - Creating subarrays (slices) * - Creating subarrays (slices)
*- Length calculation *- Length calculation
*/ */
// Arrays basics // Arrays basics
arr = [1, 2, 3] arr = [1, 2, 3]

View file

@ -1,24 +1,24 @@
#!/usr/bin/env fun #!/usr/bin/env fun
/* /*
* This file is part of the Fun programming language. * This file is part of the Fun programming language.
* https://fun-lang.xyz/ * https://fun-lang.xyz/
* *
* Copyright 2025 Johannes Findeisen <you@hanez.org> * Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license. * Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0 * https://opensource.org/license/apache-2-0
*/ */
/* /*
* Array Iteration Examples * Array Iteration Examples
* *
* Focuses specifically on: * Focuses specifically on:
* - Various iteration methods (`for`, `while`) * - Various iteration methods (`for`, `while`)
* - Using array mapping functions * - Using array mapping functions
* - Filtering arrays based on conditions * - Filtering arrays based on conditions
* - Reducing arrays to computed values * - Reducing arrays to computed values
*- Converting between different collection types during iteration *- Converting between different collection types during iteration
*/ */
// for-in over an array literal // for-in over an array literal
for x in [1, 2, 3] for x in [1, 2, 3]

View file

@ -1,26 +1,26 @@
#!/usr/bin/env fun #!/usr/bin/env fun
/* /*
* This file is part of the Fun programming language. * This file is part of the Fun programming language.
* https://fun-lang.xyz/ * https://fun-lang.xyz/
* *
* Copyright 2026 Johannes Findeisen <you@hanez.org> * Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license. * Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0 * https://opensource.org/license/apache-2-0
* *
* Added: 2026-01-13 * Added: 2026-01-13
*/ */
/* /*
* Base64 usage demo (RFC 4648, standard alphabet) * Base64 usage demo (RFC 4648, standard alphabet)
* *
* Run without installing: * Run without installing:
* FUN_LIB_DIR="$(pwd)/lib" ./build/fun examples/base64_demo.fun * FUN_LIB_DIR="$(pwd)/lib" ./build/fun examples/base64_demo.fun
*/ */
#include <encoding/base64.fun> #include <encoding/base64.fun>
print("=== Base64 demo ===") print("=== Base64 demo ===")
// Bytes for the ASCII string "Hello" // Bytes for the ASCII string "Hello"
bytes = [0x48, 0x65, 0x6c, 0x6c, 0x6f] bytes = [0x48, 0x65, 0x6c, 0x6c, 0x6f]

View file

@ -1,15 +1,15 @@
#!/usr/bin/env fun #!/usr/bin/env fun
/* /*
* This file is part of the Fun programming language. * This file is part of the Fun programming language.
* https://fun-lang.xyz/ * https://fun-lang.xyz/
* *
* Copyright 2026 Johannes Findeisen <you@hanez.org> * Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license. * Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0 * https://opensource.org/license/apache-2-0
* *
* Added: 2026-01-29 * Added: 2026-01-29
*/ */
/* /*
Build: Build:

View file

@ -1,15 +1,15 @@
#!/usr/bin/env fun #!/usr/bin/env fun
/* /*
* This file is part of the Fun programming language. * This file is part of the Fun programming language.
* https://fun-lang.xyz/ * https://fun-lang.xyz/
* *
* Copyright 2025 Johannes Findeisen <you@hanez.org> * Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license. * Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0 * https://opensource.org/license/apache-2-0
* *
* Added: 2025-12-09 * Added: 2025-12-09
*/ */
// Simple stopwatch using DateTime helpers // Simple stopwatch using DateTime helpers
#include <utils/datetime.fun> #include <utils/datetime.fun>

View file

@ -1,15 +1,15 @@
#!/usr/bin/env fun #!/usr/bin/env fun
/* /*
* This file is part of the Fun programming language. * This file is part of the Fun programming language.
* https://fun-lang.xyz/ * https://fun-lang.xyz/
* *
* Copyright 2025 Johannes Findeisen <you@hanez.org> * Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license. * Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0 * https://opensource.org/license/apache-2-0
* *
* Added: 2025-12-11 * Added: 2025-12-11
*/ */
// Demonstrates usage of RANDOM_SEED and RANDOM_INT opcodes via // Demonstrates usage of RANDOM_SEED and RANDOM_INT opcodes via
// the built-ins: random_seed(seed) and random_int(lo, hiExclusive). // the built-ins: random_seed(seed) and random_int(lo, hiExclusive).

View file

@ -1,33 +1,33 @@
#!/usr/bin/env fun #!/usr/bin/env fun
/* /*
* This file is part of the Fun programming language. * This file is part of the Fun programming language.
* https://fun-lang.xyz/ * https://fun-lang.xyz/
* *
* Copyright 2026 Johannes Findeisen <you@hanez.org> * Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license. * Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0 * https://opensource.org/license/apache-2-0
* *
* Added: 2026-01-27 * Added: 2026-01-27
*/ */
/* /*
* Rust-backed opcode demo: rust_hello() * Rust-backed opcode demo: rust_hello()
* *
* Build instructions: * Build instructions:
* - Default builds disable Rust integration. * - Default builds disable Rust integration.
* - Enable it via: cmake -S . -B build_debug -DFUN_WITH_RUST=ON * - Enable it via: cmake -S . -B build_debug -DFUN_WITH_RUST=ON
* - Then: cmake --build build_debug --target fun * - Then: cmake --build build_debug --target fun
* *
* Run: * Run:
* build_debug/fun examples/rust_hello.fun * build_debug/fun examples/rust_hello.fun
* *
* Expected output (with FUN_WITH_RUST=ON): * Expected output (with FUN_WITH_RUST=ON):
* Hello from Rust ops! * Hello from Rust ops!
* *
* If built without Rust, calling rust_hello() will raise a runtime error * If built without Rust, calling rust_hello() will raise a runtime error
* explaining that Rust integration is disabled. * explaining that Rust integration is disabled.
*/ */
print(rust_hello()) print(rust_hello())

View file

@ -1,13 +1,13 @@
#!/usr/bin/env fun #!/usr/bin/env fun
/* /*
* This file is part of the Fun programming language. * This file is part of the Fun programming language.
* https://fun-lang.xyz/ * https://fun-lang.xyz/
* *
* Copyright 2025 Johannes Findeisen <you@hanez.org> * Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license. * Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0 * https://opensource.org/license/apache-2-0
*/ */
// Short-circuit demo for || and && // Short-circuit demo for || and &&

View file

@ -1,15 +1,15 @@
#!/usr/bin/env fun #!/usr/bin/env fun
/* /*
* This file is part of the Fun programming language. * This file is part of the Fun programming language.
* https://fun-lang.xyz/ * https://fun-lang.xyz/
* *
* Copyright 2025 Johannes Findeisen <you@hanez.org> * Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license. * Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0 * https://opensource.org/license/apache-2-0
* *
* Added: 2025-12-27 * Added: 2025-12-27
*/ */
print(bxor(0x80000000, 0x00000001)) print(bxor(0x80000000, 0x00000001))
print(bor(0x80000000, 0x00000001)) print(bor(0x80000000, 0x00000001))

View file

@ -1,15 +1,15 @@
#!/usr/bin/env fun #!/usr/bin/env fun
/* /*
* This file is part of the Fun programming language. * This file is part of the Fun programming language.
* https://fun-lang.xyz/ * https://fun-lang.xyz/
* *
* Copyright 2025 Johannes Findeisen <you@hanez.org> * Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license. * Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0 * https://opensource.org/license/apache-2-0
* *
* Added: 2025-09-30 * Added: 2025-09-30
*/ */
// Threading demo for Fun // Threading demo for Fun
// Run without installing: // Run without installing:

View file

@ -12,46 +12,46 @@
/* array utilities */ /* array utilities */
int array_contains(const Value *v, const Value *needle) { int array_contains(const Value *v, const Value *needle) {
if (!v || v->type != VAL_ARRAY || !v->arr) return 0; if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
int n = array_length(v); int n = array_length(v);
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
Value item; Value item;
if (array_get_copy(v, i, &item)) { if (array_get_copy(v, i, &item)) {
int eq = value_equals(&item, needle); int eq = value_equals(&item, needle);
free_value(item); free_value(item);
if (eq) return 1; if (eq) return 1;
}
} }
return 0; }
return 0;
} }
int array_index_of(const Value *v, const Value *needle) { int array_index_of(const Value *v, const Value *needle) {
if (!v || v->type != VAL_ARRAY || !v->arr) return -1; if (!v || v->type != VAL_ARRAY || !v->arr) return -1;
int n = array_length(v); int n = array_length(v);
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
Value item; Value item;
if (array_get_copy(v, i, &item)) { if (array_get_copy(v, i, &item)) {
int eq = value_equals(&item, needle); int eq = value_equals(&item, needle);
free_value(item); free_value(item);
if (eq) return i; if (eq) return i;
}
} }
return -1; }
return -1;
} }
void array_clear(Value *v) { void array_clear(Value *v) {
if (!v || v->type != VAL_ARRAY || !v->arr) return; if (!v || v->type != VAL_ARRAY || !v->arr) return;
int n = array_length(v); int n = array_length(v);
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
Value item; Value item;
if (array_get_copy(v, i, &item)) { if (array_get_copy(v, i, &item)) {
free_value(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);
} }
}
/* 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);
}
} }

View file

@ -8,235 +8,400 @@
*/ */
#include "bytecode.h" #include "bytecode.h"
#include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
Bytecode *bytecode_new(void) { Bytecode *bytecode_new(void) {
Bytecode *bc = (Bytecode*)malloc(sizeof(Bytecode)); Bytecode *bc = (Bytecode *)malloc(sizeof(Bytecode));
bc->instructions = NULL; bc->instructions = NULL;
bc->instr_count = 0; bc->instr_count = 0;
bc->constants = NULL; bc->constants = NULL;
bc->const_count = 0; bc->const_count = 0;
bc->name = NULL; bc->name = NULL;
bc->source_file = NULL; bc->source_file = NULL;
return bc; return bc;
} }
int bytecode_add_constant(Bytecode *bc, Value v) { int bytecode_add_constant(Bytecode *bc, Value v) {
bc->constants = (Value*)realloc(bc->constants, sizeof(Value) * (bc->const_count + 1)); bc->constants = (Value *)realloc(bc->constants, sizeof(Value) * (bc->const_count + 1));
bc->constants[bc->const_count] = copy_value(&v); bc->constants[bc->const_count] = copy_value(&v);
return bc->const_count++; return bc->const_count++;
} }
int bytecode_add_instruction(Bytecode *bc, OpCode op, int32_t operand) { 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 = (Instruction *)realloc(bc->instructions, sizeof(Instruction) * (bc->instr_count + 1));
bc->instructions[bc->instr_count].op = op; bc->instructions[bc->instr_count].op = op;
bc->instructions[bc->instr_count].operand = operand; bc->instructions[bc->instr_count].operand = operand;
return bc->instr_count++; return bc->instr_count++;
} }
void bytecode_set_operand(Bytecode *bc, int idx, int32_t operand) { void bytecode_set_operand(Bytecode *bc, int idx, int32_t operand) {
if (idx >= 0 && idx < bc->instr_count) { if (idx >= 0 && idx < bc->instr_count) {
bc->instructions[idx].operand = operand; bc->instructions[idx].operand = operand;
} }
} }
void bytecode_free(Bytecode *bc) { void bytecode_free(Bytecode *bc) {
if (!bc) return; if (!bc) return;
for (int i = 0; i < bc->const_count; ++i) { for (int i = 0; i < bc->const_count; ++i) {
free_value(bc->constants[i]); free_value(bc->constants[i]);
} }
free(bc->constants); free(bc->constants);
free(bc->instructions); free(bc->instructions);
if (bc->name) free((void*)bc->name); if (bc->name) free((void *)bc->name);
if (bc->source_file) free((void*)bc->source_file); if (bc->source_file) free((void *)bc->source_file);
free(bc); free(bc);
} }
static const char *opcode_name(OpCode op) { static const char *opcode_name(OpCode op) {
switch (op) { switch (op) {
case OP_NOP: return "NOP"; case OP_NOP:
case OP_LOAD_CONST: return "LOAD_CONST"; return "NOP";
case OP_LOAD_LOCAL: return "LOAD_LOCAL"; case OP_LOAD_CONST:
case OP_STORE_LOCAL: return "STORE_LOCAL"; return "LOAD_CONST";
case OP_LOAD_GLOBAL: return "LOAD_GLOBAL"; case OP_LOAD_LOCAL:
case OP_STORE_GLOBAL: return "STORE_GLOBAL"; return "LOAD_LOCAL";
case OP_ADD: return "ADD"; case OP_STORE_LOCAL:
case OP_SUB: return "SUB"; return "STORE_LOCAL";
case OP_MUL: return "MUL"; case OP_LOAD_GLOBAL:
case OP_DIV: return "DIV"; return "LOAD_GLOBAL";
case OP_LT: return "LT"; case OP_STORE_GLOBAL:
case OP_LTE: return "LTE"; return "STORE_GLOBAL";
case OP_GT: return "GT"; case OP_ADD:
case OP_GTE: return "GTE"; return "ADD";
case OP_EQ: return "EQ"; case OP_SUB:
case OP_NEQ: return "NEQ"; return "SUB";
case OP_POP: return "POP"; case OP_MUL:
case OP_JUMP: return "JUMP"; return "MUL";
case OP_JUMP_IF_FALSE: return "JUMP_IF_FALSE"; case OP_DIV:
case OP_CALL: return "CALL"; return "DIV";
case OP_RETURN: return "RETURN"; case OP_LT:
case OP_PRINT: return "PRINT"; return "LT";
case OP_ECHO: return "ECHO"; case OP_LTE:
case OP_HALT: return "HALT"; return "LTE";
case OP_MOD: return "MOD"; case OP_GT:
case OP_AND: return "AND"; return "GT";
case OP_OR: return "OR"; case OP_GTE:
case OP_NOT: return "NOT"; return "GTE";
case OP_DUP: return "DUP"; case OP_EQ:
case OP_SWAP: return "SWAP"; return "EQ";
case OP_MAKE_ARRAY: return "MAKE_ARRAY"; case OP_NEQ:
case OP_INDEX_GET: return "INDEX_GET"; return "NEQ";
case OP_INDEX_SET: return "INDEX_SET"; case OP_POP:
case OP_LEN: return "LEN"; return "POP";
case OP_PUSH: return "ARR_PUSH"; case OP_JUMP:
case OP_APOP: return "ARR_POP"; return "JUMP";
case OP_SET: return "ARR_SET"; case OP_JUMP_IF_FALSE:
case OP_INSERT: return "ARR_INSERT"; return "JUMP_IF_FALSE";
case OP_REMOVE: return "ARR_REMOVE"; case OP_CALL:
case OP_SLICE: return "SLICE"; return "CALL";
case OP_TO_NUMBER: return "TO_NUMBER"; case OP_RETURN:
case OP_TO_STRING: return "TO_STRING"; return "RETURN";
case OP_TYPEOF: return "TYPEOF"; case OP_PRINT:
case OP_CAST: return "CAST"; return "PRINT";
case OP_SPLIT: return "SPLIT"; case OP_ECHO:
case OP_JOIN: return "JOIN"; return "ECHO";
case OP_SUBSTR: return "SUBSTR"; case OP_HALT:
case OP_FIND: return "FIND"; return "HALT";
case OP_REGEX_MATCH: return "REGEX_MATCH"; case OP_MOD:
case OP_REGEX_SEARCH: return "REGEX_SEARCH"; return "MOD";
case OP_REGEX_REPLACE: return "REGEX_REPLACE"; case OP_AND:
case OP_CONTAINS: return "CONTAINS"; return "AND";
case OP_INDEX_OF: return "INDEX_OF"; case OP_OR:
case OP_CLEAR: return "CLEAR"; return "OR";
case OP_ENUMERATE: return "ENUMERATE"; case OP_NOT:
case OP_ZIP: return "ZIP"; return "NOT";
case OP_MIN: return "MIN"; case OP_DUP:
case OP_MAX: return "MAX"; return "DUP";
case OP_CLAMP: return "CLAMP"; case OP_SWAP:
case OP_ABS: return "ABS"; return "SWAP";
case OP_POW: return "POW"; case OP_MAKE_ARRAY:
case OP_RANDOM_SEED: return "RANDOM_SEED"; return "MAKE_ARRAY";
case OP_RANDOM_INT: return "RANDOM_INT"; case OP_INDEX_GET:
case OP_MAKE_MAP: return "MAKE_MAP"; return "INDEX_GET";
case OP_KEYS: return "KEYS"; case OP_INDEX_SET:
case OP_VALUES: return "VALUES"; return "INDEX_SET";
case OP_HAS_KEY: return "HAS_KEY"; case OP_LEN:
case OP_READ_FILE: return "READ_FILE"; return "LEN";
case OP_WRITE_FILE: return "WRITE_FILE"; case OP_PUSH:
case OP_ENV: return "ENV"; return "ARR_PUSH";
case OP_INPUT_LINE: return "INPUT_LINE"; case OP_APOP:
case OP_PROC_RUN: return "PROC_RUN"; return "ARR_POP";
case OP_PROC_SYSTEM: return "PROC_SYSTEM"; case OP_SET:
case OP_TIME_NOW_MS: return "TIME_NOW_MS"; return "ARR_SET";
case OP_CLOCK_MONO_MS: return "CLOCK_MONO_MS"; case OP_INSERT:
case OP_DATE_FORMAT: return "DATE_FORMAT"; return "ARR_INSERT";
case OP_ENV_ALL: return "ENV_ALL"; case OP_REMOVE:
case OP_FUN_VERSION: return "FUN_VERSION"; return "ARR_REMOVE";
case OP_THREAD_SPAWN: return "THREAD_SPAWN"; case OP_SLICE:
case OP_THREAD_JOIN: return "THREAD_JOIN"; return "SLICE";
case OP_SLEEP_MS: return "SLEEP_MS"; case OP_TO_NUMBER:
case OP_RANDOM_NUMBER: return "RANDOM_NUMBER"; return "TO_NUMBER";
case OP_BAND: return "BAND"; case OP_TO_STRING:
case OP_BOR: return "BOR"; return "TO_STRING";
case OP_BXOR: return "BXOR"; case OP_TYPEOF:
case OP_BNOT: return "BNOT"; return "TYPEOF";
case OP_SHL: return "SHL"; case OP_CAST:
case OP_SHR: return "SHR"; return "CAST";
case OP_ROTL: return "ROTL"; case OP_SPLIT:
case OP_ROTR: return "ROTR"; return "SPLIT";
case OP_JSON_PARSE: return "JSON_PARSE"; case OP_JOIN:
case OP_JSON_STRINGIFY: return "JSON_STRINGIFY"; return "JOIN";
case OP_JSON_FROM_FILE: return "JSON_FROM_FILE"; case OP_SUBSTR:
case OP_JSON_TO_FILE: return "JSON_TO_FILE"; return "SUBSTR";
case OP_CURL_GET: return "CURL_GET"; case OP_FIND:
case OP_CURL_POST: return "CURL_POST"; return "FIND";
case OP_CURL_DOWNLOAD: return "CURL_DOWNLOAD"; case OP_REGEX_MATCH:
case OP_SQLITE_OPEN: return "SQLITE_OPEN"; return "REGEX_MATCH";
case OP_SQLITE_CLOSE: return "SQLITE_CLOSE"; case OP_REGEX_SEARCH:
case OP_SQLITE_EXEC: return "SQLITE_EXEC"; return "REGEX_SEARCH";
case OP_SQLITE_QUERY: return "SQLITE_QUERY"; case OP_REGEX_REPLACE:
case OP_LIBSQL_OPEN: return "LIBSQL_OPEN"; return "REGEX_REPLACE";
case OP_LIBSQL_CLOSE: return "LIBSQL_CLOSE"; case OP_CONTAINS:
case OP_LIBSQL_EXEC: return "LIBSQL_EXEC"; return "CONTAINS";
case OP_LIBSQL_QUERY: return "LIBSQL_QUERY"; case OP_INDEX_OF:
case OP_PCSC_ESTABLISH: return "PCSC_ESTABLISH"; return "INDEX_OF";
case OP_PCSC_RELEASE: return "PCSC_RELEASE"; case OP_CLEAR:
case OP_PCSC_LIST_READERS: return "PCSC_LIST_READERS"; return "CLEAR";
case OP_PCSC_CONNECT: return "PCSC_CONNECT"; case OP_ENUMERATE:
case OP_PCSC_DISCONNECT: return "PCSC_DISCONNECT"; return "ENUMERATE";
case OP_PCSC_TRANSMIT: return "PCSC_TRANSMIT"; case OP_ZIP:
case OP_PCRE2_TEST: return "PCRE2_TEST"; return "ZIP";
case OP_PCRE2_MATCH: return "PCRE2_MATCH"; case OP_MIN:
case OP_PCRE2_FINDALL: return "PCRE2_FINDALL"; return "MIN";
case OP_OPENSSL_MD5: return "OPENSSL_MD5"; case OP_MAX:
case OP_OPENSSL_SHA256: return "OPENSSL_SHA256"; return "MAX";
case OP_OPENSSL_SHA512: return "OPENSSL_SHA512"; case OP_CLAMP:
case OP_OPENSSL_RIPEMD160: return "OPENSSL_RIPEMD160"; return "CLAMP";
case OP_LIBRESSL_MD5: return "LIBRESSL_MD5"; case OP_ABS:
case OP_LIBRESSL_SHA256: return "LIBRESSL_SHA256"; return "ABS";
case OP_LIBRESSL_SHA512: return "LIBRESSL_SHA512"; case OP_POW:
case OP_LIBRESSL_RIPEMD160: return "LIBRESSL_RIPEMD160"; return "POW";
case OP_INI_LOAD: return "INI_LOAD"; case OP_RANDOM_SEED:
case OP_INI_FREE: return "INI_FREE"; return "RANDOM_SEED";
case OP_INI_GET_STRING: return "INI_GET_STRING"; case OP_RANDOM_INT:
case OP_INI_GET_INT: return "INI_GET_INT"; return "RANDOM_INT";
case OP_INI_GET_DOUBLE: return "INI_GET_DOUBLE"; case OP_MAKE_MAP:
case OP_INI_GET_BOOL: return "INI_GET_BOOL"; return "MAKE_MAP";
case OP_INI_SET: return "INI_SET"; case OP_KEYS:
case OP_INI_UNSET: return "INI_UNSET"; return "KEYS";
case OP_INI_SAVE: return "INI_SAVE"; case OP_VALUES:
case OP_XML_PARSE: return "XML_PARSE"; return "VALUES";
case OP_XML_ROOT: return "XML_ROOT"; case OP_HAS_KEY:
case OP_XML_NAME: return "XML_NAME"; return "HAS_KEY";
case OP_XML_TEXT: return "XML_TEXT"; case OP_READ_FILE:
case OP_TK_EVAL: return "TK_EVAL"; return "READ_FILE";
case OP_TK_RESULT: return "TK_RESULT"; case OP_WRITE_FILE:
case OP_TK_LOOP: return "TK_LOOP"; return "WRITE_FILE";
case OP_TK_WM_TITLE: return "TK_WM_TITLE"; case OP_ENV:
case OP_TK_LABEL: return "TK_LABEL"; return "ENV";
case OP_TK_BUTTON: return "TK_BUTTON"; case OP_INPUT_LINE:
case OP_TK_PACK: return "TK_PACK"; return "INPUT_LINE";
case OP_FLOOR: return "FLOOR"; case OP_PROC_RUN:
case OP_CEIL: return "CEIL"; return "PROC_RUN";
case OP_TRUNC: return "TRUNC"; case OP_PROC_SYSTEM:
case OP_ROUND: return "ROUND"; return "PROC_SYSTEM";
case OP_SIN: return "SIN"; case OP_TIME_NOW_MS:
case OP_COS: return "COS"; return "TIME_NOW_MS";
case OP_TAN: return "TAN"; case OP_CLOCK_MONO_MS:
case OP_EXP: return "EXP"; return "CLOCK_MONO_MS";
case OP_LOG: return "LOG"; case OP_DATE_FORMAT:
case OP_LOG10: return "LOG10"; return "DATE_FORMAT";
case OP_SQRT: return "SQRT"; case OP_ENV_ALL:
case OP_GCD: return "GCD"; return "ENV_ALL";
case OP_LCM: return "LCM"; case OP_FUN_VERSION:
case OP_ISQRT: return "ISQRT"; return "FUN_VERSION";
case OP_SIGN: return "SIGN"; case OP_THREAD_SPAWN:
case OP_FMIN: return "FMIN"; return "THREAD_SPAWN";
case OP_FMAX: return "FMAX"; case OP_THREAD_JOIN:
case OP_RUST_HELLO: return "RUST_HELLO"; return "THREAD_JOIN";
case OP_RUST_HELLO_ARGS: return "RUST_HELLO_ARGS"; case OP_SLEEP_MS:
case OP_RUST_HELLO_ARGS_RETURN: return "RUST_HELLO_ARGS_RETURN"; return "SLEEP_MS";
case OP_RUST_GET_SP: return "RUST_GET_SP"; case OP_RANDOM_NUMBER:
case OP_RUST_SET_EXIT: return "RUST_SET_EXIT"; return "RANDOM_NUMBER";
default: return "???"; 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) { void bytecode_dump(const Bytecode *bc) {
if (!bc) { if (!bc) {
printf("<null bytecode>\n"); printf("<null bytecode>\n");
return; return;
} }
printf("Constants (%d):\n", bc->const_count); printf("Constants (%d):\n", bc->const_count);
for (int i = 0; i < bc->const_count; ++i) { for (int i = 0; i < bc->const_count; ++i) {
printf(" [%d] ", i); printf(" [%d] ", i);
print_value(&bc->constants[i]); print_value(&bc->constants[i]);
printf("\n"); printf("\n");
} }
printf("Instructions (%d):\n", bc->instr_count); printf("Instructions (%d):\n", bc->instr_count);
for (int i = 0; i < bc->instr_count; ++i) { for (int i = 0; i < bc->instr_count; ++i) {
const Instruction *ins = &bc->instructions[i]; const Instruction *ins = &bc->instructions[i];
printf(" %3d: %-15s %d\n", i, opcode_name(ins->op), ins->operand); printf(" %3d: %-15s %d\n", i, opcode_name(ins->op), ins->operand);
} }
} }

View file

@ -10,304 +10,304 @@
#ifndef FUN_BYTECODE_H #ifndef FUN_BYTECODE_H
#define FUN_BYTECODE_H #define FUN_BYTECODE_H
#include <stdint.h>
#include "value.h" #include "value.h"
#include <stdint.h>
// VM opcodes // VM opcodes
typedef enum { typedef enum {
OP_NOP, OP_NOP,
OP_LOAD_CONST, // operand = constant index OP_LOAD_CONST, // operand = constant index
OP_LOAD_LOCAL, // operand = local slot index OP_LOAD_LOCAL, // operand = local slot index
OP_STORE_LOCAL, // operand = local slot index OP_STORE_LOCAL, // operand = local slot index
OP_LOAD_GLOBAL, // OP_LOAD_GLOBAL, //
OP_STORE_GLOBAL, // OP_STORE_GLOBAL, //
OP_ADD, // OP_ADD, //
OP_SUB, // OP_SUB, //
OP_MUL, // OP_MUL, //
OP_DIV, // OP_DIV, //
OP_LT, // a < b -> push 1/0 OP_LT, // a < b -> push 1/0
OP_LTE, // a <= b -> push 1/0 OP_LTE, // a <= b -> push 1/0
OP_GT, // a > b -> push 1/0 OP_GT, // a > b -> push 1/0
OP_GTE, // a >= b -> push 1/0 OP_GTE, // a >= b -> push 1/0
OP_EQ, // a == b -> push 1/0 OP_EQ, // a == b -> push 1/0
OP_NEQ, // a != b -> push 1/0 OP_NEQ, // a != b -> push 1/0
OP_POP, // discard top of stack OP_POP, // discard top of stack
OP_JUMP, // unconditional jump OP_JUMP, // unconditional jump
OP_JUMP_IF_FALSE, // jump if top of stack is false (0) OP_JUMP_IF_FALSE, // jump if top of stack is false (0)
OP_CALL, // operand = arg count; pops fn + args and enters fn OP_CALL, // operand = arg count; pops fn + args and enters fn
OP_RETURN, // pop optional return value and return to caller OP_RETURN, // pop optional return value and return to caller
OP_PRINT, OP_PRINT,
OP_ECHO, // like print but does not append a newline; prints immediately OP_ECHO, // like print but does not append a newline; prints immediately
OP_HALT, OP_HALT,
OP_LINE, // operand = source line number (debug marker) OP_LINE, // operand = source line number (debug marker)
// add after existing opcodes // add after existing opcodes
OP_MOD, // a % b OP_MOD, // a % b
OP_AND, // logical AND OP_AND, // logical AND
OP_OR, // logical OR OP_OR, // logical OR
OP_NOT, // logical NOT OP_NOT, // logical NOT
OP_DUP, // duplicate top of stack OP_DUP, // duplicate top of stack
OP_SWAP, // swap top two stack values OP_SWAP, // swap top two stack values
// arrays // arrays
OP_MAKE_ARRAY, // operand = element count; pops N values, pushes array OP_MAKE_ARRAY, // operand = element count; pops N values, pushes array
OP_INDEX_GET, // pops index, array; pushes element copy OP_INDEX_GET, // pops index, array; pushes element copy
OP_INDEX_SET, // pops value, index, array; sets in place OP_INDEX_SET, // pops value, index, array; sets in place
// array and builtin helpers // array and builtin helpers
OP_LEN, // pops array or string; pushes length OP_LEN, // pops array or string; pushes length
OP_PUSH, // pops value, array; pushes new length OP_PUSH, // pops value, array; pushes new length
OP_APOP, // pops array; pushes removed element OP_APOP, // pops array; pushes removed element
OP_SET, // pops value, index, array; pushes value OP_SET, // pops value, index, array; pushes value
OP_INSERT, // pops value, index, array; pushes new length OP_INSERT, // pops value, index, array; pushes new length
OP_REMOVE, // pops index, array; pushes removed element OP_REMOVE, // pops index, array; pushes removed element
OP_SLICE, // pops end, start, array; pushes new array OP_SLICE, // pops end, start, array; pushes new array
// conversions // conversions
OP_TO_NUMBER, // pops any; pushes int (parse strings) OP_TO_NUMBER, // pops any; pushes int (parse strings)
OP_TO_STRING, // pops any; pushes string OP_TO_STRING, // pops any; pushes string
OP_CAST, // pops typeName, value; pushes casted value (see vm/cast.c) OP_CAST, // pops typeName, value; pushes casted value (see vm/cast.c)
OP_TYPEOF, // pops any; pushes string name of type OP_TYPEOF, // pops any; pushes string name of type
OP_UCLAMP, // pops number; pushes number masked to N bits (operand = bits) 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) OP_SCLAMP, // pops number; pushes number clamped to signed N-bit range (operand = bits)
// string ops // string ops
OP_SPLIT, // pops sep, string; pushes array of strings OP_SPLIT, // pops sep, string; pushes array of strings
OP_JOIN, // pops sep, array; pushes string OP_JOIN, // pops sep, array; pushes string
OP_SUBSTR, // pops len, start, string; pushes string OP_SUBSTR, // pops len, start, string; pushes string
OP_FIND, // pops needle, haystack; pushes int index or -1 OP_FIND, // pops needle, haystack; pushes int index or -1
// regex ops (POSIX) // regex ops (POSIX)
OP_REGEX_MATCH, // pops pattern, string; pushes 1/0 for full match 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_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 OP_REGEX_REPLACE, // pops repl, pattern, string; pushes string with global replacements
// array utils // array utils
OP_CONTAINS, // pops value, array; pushes 1/0 OP_CONTAINS, // pops value, array; pushes 1/0
OP_INDEX_OF, // pops value, array; pushes index or -1 OP_INDEX_OF, // pops value, array; pushes index or -1
OP_CLEAR, // pops array; clears it; pushes nothing (we'll push 0) OP_CLEAR, // pops array; clears it; pushes nothing (we'll push 0)
// iteration helpers // iteration helpers
OP_ENUMERATE, // pops array; pushes array of [index, value] OP_ENUMERATE, // pops array; pushes array of [index, value]
OP_ZIP, // pops b, a; pushes array of [a[i], b[i]] OP_ZIP, // pops b, a; pushes array of [a[i], b[i]]
// math // math
OP_MIN, // pops b, a; pushes min(a,b) OP_MIN, // pops b, a; pushes min(a,b)
OP_MAX, // pops b, a; pushes max(a,b) OP_MAX, // pops b, a; pushes max(a,b)
OP_CLAMP, // pops hi, lo, x; pushes clamped OP_CLAMP, // pops hi, lo, x; pushes clamped
OP_ABS, // pops x; pushes |x| OP_ABS, // pops x; pushes |x|
OP_POW, // pops b, a; pushes a^b OP_POW, // pops b, a; pushes a^b
OP_RANDOM_SEED, // pops seed; sets RNG seed; pushes nothing (we'll push 0) 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) OP_RANDOM_INT, // pops hi, lo; pushes random int in [lo, hi)
// maps // maps
OP_MAKE_MAP, // operand = pair count; pops 2*n (key,value)..., pushes map OP_MAKE_MAP, // operand = pair count; pops 2*n (key,value)..., pushes map
OP_KEYS, // pops map; pushes array of keys OP_KEYS, // pops map; pushes array of keys
OP_VALUES, // pops map; pushes array of values OP_VALUES, // pops map; pushes array of values
OP_HAS_KEY, // pops key, map; pushes 1/0 OP_HAS_KEY, // pops key, map; pushes 1/0
// file I/O // file I/O
OP_READ_FILE, // pops path string; pushes content string (or "") OP_READ_FILE, // pops path string; pushes content string (or "")
OP_WRITE_FILE, // pops data string, path string; pushes 1/0 OP_WRITE_FILE, // pops data string, path string; pushes 1/0
// OS // OS
OP_ENV, // pops name string; pushes value string (or "") 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_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_RUN, // pops command string; pushes map {"out": string, "code": int}
OP_PROC_SYSTEM, // pops command string; pushes exit code number 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_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_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_DATE_FORMAT, // pops fmt string, ms epoch (int); pushes formatted date string using strftime
OP_ENV_ALL, // pushes map of all environment variables OP_ENV_ALL, // pushes map of all environment variables
OP_FUN_VERSION, // pushes version string OP_FUN_VERSION, // pushes version string
// Threads // Threads
OP_THREAD_SPAWN, // operand: 0=no args, 1=has args; pops [args?], fn; pushes thread id (int>0) 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_THREAD_JOIN, // pops thread id; waits; pushes result value (or Nil)
OP_SLEEP_MS, // pops milliseconds; sleeps; pushes Nil (for statement POP safety) 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) OP_RANDOM_NUMBER, // pops length; pushes hex string of that length from OS RNG (hex-encoded)
// Bitwise (32-bit) and shifts/rotates // Bitwise (32-bit) and shifts/rotates
OP_BAND, // pops b, a; pushes (uint32_t)(a & b) OP_BAND, // pops b, a; pushes (uint32_t)(a & b)
OP_BOR, // 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_BXOR, // pops b, a; pushes (uint32_t)(a ^ b)
OP_BNOT, // pops a; pushes (uint32_t)(~a) OP_BNOT, // pops a; pushes (uint32_t)(~a)
OP_SHL, // pops s, a; pushes (uint32_t)(a << (s&31)) OP_SHL, // pops s, a; pushes (uint32_t)(a << (s&31))
OP_SHR, // pops s, a; pushes (uint32_t)(a >> (s&31)) logical OP_SHR, // pops s, a; pushes (uint32_t)(a >> (s&31)) logical
OP_ROTL, // pops s, a; pushes rotl32(a, s) OP_ROTL, // pops s, a; pushes rotl32(a, s)
OP_ROTR, // pops s, a; pushes rotr32(a, s) OP_ROTR, // pops s, a; pushes rotr32(a, s)
// JSON (json-c) // JSON (json-c)
OP_JSON_PARSE, // pops text string; pushes value (or Nil on error) OP_JSON_PARSE, // pops text string; pushes value (or Nil on error)
OP_JSON_STRINGIFY, // pops pretty(bool), value; pushes string OP_JSON_STRINGIFY, // pops pretty(bool), value; pushes string
OP_JSON_FROM_FILE, // pops path string; pushes value (or Nil) OP_JSON_FROM_FILE, // pops path string; pushes value (or Nil)
OP_JSON_TO_FILE, // pops pretty(bool), value, path; pushes 1/0 OP_JSON_TO_FILE, // pops pretty(bool), value, path; pushes 1/0
// CURL (libcurl) // CURL (libcurl)
OP_CURL_GET, // pops [headers map?], url; pushes response string (or "") 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_POST, // pops [headers map?], body string, url; pushes response string (or "")
OP_CURL_DOWNLOAD, // pops [headers map?], path, url; pushes 1/0 OP_CURL_DOWNLOAD, // pops [headers map?], path, url; pushes 1/0
// SQLite (optional) // SQLite (optional)
OP_SQLITE_OPEN, // pops path; pushes handle (>0) or 0 OP_SQLITE_OPEN, // pops path; pushes handle (>0) or 0
OP_SQLITE_CLOSE, // pops handle; pushes Nil OP_SQLITE_CLOSE, // pops handle; pushes Nil
OP_SQLITE_EXEC, // pops sql, handle; pushes sqlite rc (0=OK) OP_SQLITE_EXEC, // pops sql, handle; pushes sqlite rc (0=OK)
OP_SQLITE_QUERY, // pops sql, handle; pushes array<map> OP_SQLITE_QUERY, // pops sql, handle; pushes array<map>
// libsql (optional, independent) // libsql (optional, independent)
OP_LIBSQL_OPEN, // pops url/path; pushes handle (>0) or 0 OP_LIBSQL_OPEN, // pops url/path; pushes handle (>0) or 0
OP_LIBSQL_CLOSE, // pops handle; pushes Nil OP_LIBSQL_CLOSE, // pops handle; pushes Nil
OP_LIBSQL_EXEC, // pops sql, handle; pushes rc (0=OK) OP_LIBSQL_EXEC, // pops sql, handle; pushes rc (0=OK)
OP_LIBSQL_QUERY, // pops sql, handle; pushes array<map> OP_LIBSQL_QUERY, // pops sql, handle; pushes array<map>
// PCSC (smart card) opcodes // PCSC (smart card) opcodes
OP_PCSC_ESTABLISH, // returns context id (>0) or 0 OP_PCSC_ESTABLISH, // returns context id (>0) or 0
OP_PCSC_RELEASE, // pops ctx id; returns 1/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_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_CONNECT, // pops reader, ctx id; returns handle id (>0) or 0
OP_PCSC_DISCONNECT, // pops handle id; returns 1/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} OP_PCSC_TRANSMIT, // pops apdu array, handle id; returns map {"data":[],"sw1":n,"sw2":n,"code":n}
// PCRE2 regex ops (optional) // PCRE2 regex ops (optional)
OP_PCRE2_TEST, // pops flags, text, pattern; pushes 1/0 OP_PCRE2_TEST, // pops flags, text, pattern; pushes 1/0
OP_PCRE2_MATCH, // pops flags, text, pattern; pushes match map or Nil OP_PCRE2_MATCH, // pops flags, text, pattern; pushes match map or Nil
OP_PCRE2_FINDALL, // pops flags, text, pattern; pushes array of match maps OP_PCRE2_FINDALL, // pops flags, text, pattern; pushes array of match maps
// OpenSSL (optional) // OpenSSL (optional)
OP_OPENSSL_MD5, // pops data string; pushes md5 hex string OP_OPENSSL_MD5, // pops data string; pushes md5 hex string
OP_OPENSSL_SHA256, // pops data string; pushes sha256 hex string OP_OPENSSL_SHA256, // pops data string; pushes sha256 hex string
OP_OPENSSL_SHA512, // pops data string; pushes sha512 hex string OP_OPENSSL_SHA512, // pops data string; pushes sha512 hex string
OP_OPENSSL_RIPEMD160, // pops data string; pushes ripemd160 hex string OP_OPENSSL_RIPEMD160, // pops data string; pushes ripemd160 hex string
// LibreSSL (optional; same API as OpenSSL but different toggle) // LibreSSL (optional; same API as OpenSSL but different toggle)
OP_LIBRESSL_MD5, // pops data string; pushes md5 hex string OP_LIBRESSL_MD5, // pops data string; pushes md5 hex string
OP_LIBRESSL_SHA256, // pops data string; pushes sha256 hex string OP_LIBRESSL_SHA256, // pops data string; pushes sha256 hex string
OP_LIBRESSL_SHA512, // pops data string; pushes sha512 hex string OP_LIBRESSL_SHA512, // pops data string; pushes sha512 hex string
OP_LIBRESSL_RIPEMD160, // pops data string; pushes ripemd160 hex string OP_LIBRESSL_RIPEMD160, // pops data string; pushes ripemd160 hex string
// INI (iniparser 4.2.6) optional // INI (iniparser 4.2.6) optional
OP_INI_LOAD, // pops path; pushes handle (>0) or 0 OP_INI_LOAD, // pops path; pushes handle (>0) or 0
OP_INI_FREE, // pops handle; pushes 1/0 OP_INI_FREE, // pops handle; pushes 1/0
OP_INI_GET_STRING, // pops def, key, section, handle; pushes string 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_INT, // pops def, key, section, handle; pushes int
OP_INI_GET_DOUBLE, // pops def, key, section, handle; pushes float 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_GET_BOOL, // pops def, key, section, handle; pushes int (0/1)
OP_INI_SET, // pops value, key, section, handle; pushes 1/0 OP_INI_SET, // pops value, key, section, handle; pushes 1/0
OP_INI_UNSET, // pops 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 OP_INI_SAVE, // pops path, handle; pushes 1/0
// XML (libxml2) optional minimal API // XML (libxml2) optional minimal API
OP_XML_PARSE, // pops text string; pushes doc handle (>0) or 0 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_ROOT, // pops doc handle; pushes node handle (>0) or 0
OP_XML_NAME, // pops node handle; pushes string (node name) OP_XML_NAME, // pops node handle; pushes string (node name)
OP_XML_TEXT, // pops node handle; pushes string (node text) OP_XML_TEXT, // pops node handle; pushes string (node text)
// Sockets (UNIX platforms) // Sockets (UNIX platforms)
OP_SOCK_TCP_LISTEN, // pops backlog, port; returns listen fd (>0) or 0 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_ACCEPT, // pops listen fd; returns client fd (>0) or 0
OP_SOCK_TCP_CONNECT, // pops port, host; returns 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_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_RECV, // pops maxlen, fd; returns data string ("" on EOF/error)
OP_SOCK_CLOSE, // pops fd; returns 1/0 OP_SOCK_CLOSE, // pops fd; returns 1/0
OP_SOCK_UNIX_LISTEN, // pops backlog, path; returns listen fd (>0) or 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 OP_SOCK_UNIX_CONNECT, // pops path; returns fd (>0) or 0
// process control // process control
OP_EXIT, // pops code (or uses operand) and terminates script with exit code OP_EXIT, // pops code (or uses operand) and terminates script with exit code
// OS additions // OS additions
OP_OS_LIST_DIR, // pops path string; pushes array of strings OP_OS_LIST_DIR, // pops path string; pushes array of strings
// Tk additions // Tk additions
OP_TK_BIND, // pops command, event, id; binds event to command OP_TK_BIND, // pops command, event, id; binds event to command
// Serial communication (termios) // Serial communication (termios)
OP_SERIAL_OPEN, // pops baud_rate (int), path (string); returns fd (int) or 0 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_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_SEND, // pops data (string), fd; returns bytes sent (int)
OP_SERIAL_RECV, // pops maxlen (int), fd; returns data (string) OP_SERIAL_RECV, // pops maxlen (int), fd; returns data (string)
OP_SERIAL_CLOSE, // pops fd; returns 1/0 OP_SERIAL_CLOSE, // pops fd; returns 1/0
// Tk (Tcl/Tk) optional minimal API // Tk (Tcl/Tk) optional minimal API
OP_TK_EVAL, // pops script string; pushes int rc (0 = OK) OP_TK_EVAL, // pops script string; pushes int rc (0 = OK)
OP_TK_RESULT, // pushes string: last Tcl result OP_TK_RESULT, // pushes string: last Tcl result
OP_TK_LOOP, // enters Tk event loop; pushes Nil when done 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_WM_TITLE, // pops title string; sets window title; pushes rc
OP_TK_LABEL, // pops text, id; creates/updates label .id; 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_BUTTON, // pops text, id; creates/updates button .id; pushes rc
OP_TK_PACK, // pops id; packs .id; pushes rc OP_TK_PACK, // pops id; packs .id; pushes rc
// exceptions (minimal) // exceptions (minimal)
OP_TRY_PUSH, // operand = handler ip; push handler onto try-stack OP_TRY_PUSH, // operand = handler ip; push handler onto try-stack
OP_TRY_POP, // pop current handler OP_TRY_POP, // pop current handler
OP_THROW, // pops error value; if handler -> jump to it (push err), else print and terminate OP_THROW, // pops error value; if handler -> jump to it (push err), else print and terminate
// C99 math.h rounding family (float-aware) // C99 math.h rounding family (float-aware)
OP_FLOOR, // pops x (int/float); pushes floor(x) (int if integral else float) 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_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_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) OP_ROUND, // pops x (int/float); pushes round(x) (half away from zero)
// C99 math.h transcendentals (float-aware) // C99 math.h transcendentals (float-aware)
OP_SIN, // pops x (int/float); pushes sin(x) OP_SIN, // pops x (int/float); pushes sin(x)
OP_COS, // pops x (int/float); pushes cos(x) OP_COS, // pops x (int/float); pushes cos(x)
OP_TAN, // pops x (int/float); pushes tan(x) OP_TAN, // pops x (int/float); pushes tan(x)
OP_EXP, // pops x (int/float); pushes exp(x) OP_EXP, // pops x (int/float); pushes exp(x)
OP_LOG, // pops x (int/float); pushes natural log ln(x) OP_LOG, // pops x (int/float); pushes natural log ln(x)
OP_LOG10, // pops x (int/float); pushes log10(x) OP_LOG10, // pops x (int/float); pushes log10(x)
OP_SQRT, // pops x (int/float); pushes sqrt(x) OP_SQRT, // pops x (int/float); pushes sqrt(x)
// Integer math helpers // Integer math helpers
OP_GCD, // pops b, a; pushes gcd(|a|,|b|) OP_GCD, // pops b, a; pushes gcd(|a|,|b|)
OP_LCM, // pops b, a; pushes lcm(|a|,|b|) (0 if either is 0) 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_ISQRT, // pops x; pushes floor(sqrt(max(0,x))) for integers
OP_SIGN, // pops x; pushes -1, 0, or 1 depending on the sign OP_SIGN, // pops x; pushes -1, 0, or 1 depending on the sign
// Min/Max variants (float-aware, C99 semantics) // Min/Max variants (float-aware, C99 semantics)
OP_FMIN, // pops b, a (int/float); pushes fmin(a,b) (NaN handling per C99) 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) OP_FMAX, // pops b, a (int/float); pushes fmax(a,b) (NaN handling per C99)
// Rust FFI demo opcode(s) // Rust FFI demo opcode(s)
OP_RUST_HELLO, // pushes string returned from Rust (hello world) 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, // 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_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_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) OP_RUST_SET_EXIT, // pops int and sets VM exit_code (via Rust writing VM memory)
// C++ demo opcode(s) // C++ demo opcode(s)
OP_CPP_ADD, // pops b, a; pushes (a + b) OP_CPP_ADD, // pops b, a; pushes (a + b)
/* Notcurses TUI (optional) */ /* Notcurses TUI (optional) */
OP_NC_INIT, // initializes Notcurses; returns 1 on success, 0 on failure OP_NC_INIT, // initializes Notcurses; returns 1 on success, 0 on failure
OP_NC_SHUTDOWN, // shuts down Notcurses; returns 0 OP_NC_SHUTDOWN, // shuts down Notcurses; returns 0
OP_NC_CLEAR, // clears screen/plane; returns 0 OP_NC_CLEAR, // clears screen/plane; returns 0
OP_NC_DRAW_TEXT, // pops text, x, y; draws; 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 OP_NC_GETCH // pops timeout_ms; returns codepoint or -1 on timeout/error
} OpCode; } OpCode;
typedef struct { typedef struct {
OpCode op; OpCode op;
int32_t operand; int32_t operand;
} Instruction; } Instruction;
typedef struct Bytecode { typedef struct Bytecode {
Instruction *instructions; Instruction *instructions;
int instr_count; int instr_count;
Value *constants; Value *constants;
int const_count; int const_count;
/* debug metadata */ /* debug metadata */
const char *name; /* function or module name (optional) */ const char *name; /* function or module name (optional) */
const char *source_file; /* originating source filename (optional) */ const char *source_file; /* originating source filename (optional) */
} Bytecode; } Bytecode;
// constructors / manipulation // constructors / manipulation

15
src/external/curl.c vendored
View file

@ -12,18 +12,23 @@
/* Ensure libcurl headers and helpers are defined at file scope (not inside vm_run) */ /* Ensure libcurl headers and helpers are defined at file scope (not inside vm_run) */
#ifdef FUN_WITH_CURL #ifdef FUN_WITH_CURL
#include <curl/curl.h> #include <curl/curl.h>
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) { static size_t fun_curl_write_cb(void *ptr, size_t sz, size_t nm, void *ud) {
size_t add = sz * nm; size_t add = sz * nm;
FunCurlBuf *b = (FunCurlBuf*)ud; FunCurlBuf *b = (FunCurlBuf *)ud;
char *p = (char*)realloc(b->d, b->n + add + 1); char *p = (char *)realloc(b->d, b->n + add + 1);
if (!p) return 0; if (!p) return 0;
memcpy(p + b->n, ptr, add); 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; return add;
} }
static size_t fun_curl_file_write_cb(void *ptr, size_t sz, size_t nm, void *ud) { 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); return fwrite(ptr, sz, nm, f);
} }
#endif #endif

22
src/external/ini.c vendored
View file

@ -11,18 +11,18 @@
#ifdef FUN_WITH_INI #ifdef FUN_WITH_INI
#if defined(__has_include) #if defined(__has_include)
# if __has_include(<iniparser/iniparser.h>) #if __has_include(<iniparser/iniparser.h>)
# include <iniparser/iniparser.h> #include <iniparser/dictionary.h>
# include <iniparser/dictionary.h> #include <iniparser/iniparser.h>
# elif __has_include(<iniparser.h>) #elif __has_include(<iniparser.h>)
# include <iniparser.h> #include <dictionary.h>
# include <dictionary.h> #include <iniparser.h>
# else
# error "iniparser headers not found"
# endif
#else #else
# include <iniparser/iniparser.h> #error "iniparser headers not found"
# include <iniparser/dictionary.h> #endif
#else
#include <iniparser/dictionary.h>
#include <iniparser/iniparser.h>
#endif #endif
#include "vm/ini/handles.h" #include "vm/ini/handles.h"
#endif #endif

171
src/external/json.c vendored
View file

@ -9,7 +9,7 @@
* Added: 2025-12-11 (2025-12-11 migrated from src/vm.c) * 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 #ifdef FUN_WITH_JSON
#include "value.h" #include "value.h"
@ -20,88 +20,99 @@
/* --- Conversion helpers between json-c and Fun Value --- */ /* --- Conversion helpers between json-c and Fun Value --- */
static Value json_to_fun(json_object *j) { static Value json_to_fun(json_object *j) {
if (!j) return make_nil(); if (!j) return make_nil();
enum json_type t = json_object_get_type(j); enum json_type t = json_object_get_type(j);
switch (t) { switch (t) {
case json_type_null: return make_nil(); case json_type_null:
case json_type_boolean: return make_bool(json_object_get_boolean(j)); return make_nil();
case json_type_double: return make_float(json_object_get_double(j)); case json_type_boolean:
case json_type_int: return make_int((int64_t)json_object_get_int64(j)); return make_bool(json_object_get_boolean(j));
case json_type_string: return make_string(json_object_get_string(j)); case json_type_double:
case json_type_array: { return make_float(json_object_get_double(j));
size_t n = json_object_array_length(j); case json_type_int:
if (n == 0) { return make_int((int64_t)json_object_get_int64(j));
return make_array_from_values(NULL, 0); case json_type_string:
} return make_string(json_object_get_string(j));
Value *vals = (Value*)malloc(sizeof(Value) * n); case json_type_array: {
if (!vals) return make_array_from_values(NULL, 0); size_t n = json_object_array_length(j);
for (size_t i = 0; i < n; ++i) { if (n == 0) {
json_object *item = json_object_array_get_idx(j, (int)i); return make_array_from_values(NULL, 0);
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();
} }
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) { static json_object *fun_to_json(const Value *v) {
switch (v->type) { switch (v->type) {
case VAL_NIL: return json_object_new_null(); case VAL_NIL:
case VAL_BOOL: return json_object_new_boolean(v->i ? 1 : 0); return json_object_new_null();
case VAL_INT: return json_object_new_int64(v->i); case VAL_BOOL:
case VAL_FLOAT: return json_object_new_double(v->d); return json_object_new_boolean(v->i ? 1 : 0);
case VAL_STRING: return json_object_new_string(v->s ? v->s : ""); case VAL_INT:
case VAL_ARRAY: { return json_object_new_int64(v->i);
json_object *arr = json_object_new_array(); case VAL_FLOAT:
int n = array_length(v); return json_object_new_double(v->d);
for (int i = 0; i < n; ++i) { case VAL_STRING:
Value item; return json_object_new_string(v->s ? v->s : "");
if (array_get_copy(v, i, &item)) { case VAL_ARRAY: {
json_object_array_add(arr, fun_to_json(&item)); json_object *arr = json_object_new_array();
free_value(item); int n = array_length(v);
} else { for (int i = 0; i < n; ++i) {
json_object_array_add(arr, json_object_new_null()); Value item;
} if (array_get_copy(v, i, &item)) {
} json_object_array_add(arr, fun_to_json(&item));
return arr; free_value(item);
} } else {
case VAL_MAP: { json_object_array_add(arr, json_object_new_null());
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("<unsupported>");
} }
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("<unsupported>");
}
} }
#endif #endif

View file

@ -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/ * https://fun-lang.xyz/
* *
* Copyright 2026 Johannes Findeisen <you@hanez.org> * Copyright 2026 Johannes Findeisen <you@hanez.org>
@ -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). */ /* 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 char *fun_libressl_md5_hex(const unsigned char *data, size_t len) {
static const char hexdig[] = "0123456789abcdef"; static const char hexdig[] = "0123456789abcdef";
if (!data && len != 0) return NULL; if (!data && len != 0) return NULL;
#ifdef FUN_WITH_LIBRESSL #ifdef FUN_WITH_LIBRESSL
const EVP_MD *md = EVP_md5(); const EVP_MD *md = EVP_md5();
if (!md) return NULL; if (!md) return NULL;
int dlen = EVP_MD_get_size(md); int dlen = EVP_MD_get_size(md);
if (dlen <= 0) return NULL; if (dlen <= 0) return NULL;
unsigned char *digest = (unsigned char*)malloc((size_t)dlen); unsigned char *digest = (unsigned char *)malloc((size_t)dlen);
if (!digest) return NULL; if (!digest) return NULL;
unsigned int out_len = 0; unsigned int out_len = 0;
int ok; int ok;
if (len == 0) { if (len == 0) {
ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL);
} else { } else {
ok = EVP_Digest(data, len, digest, &out_len, md, NULL); ok = EVP_Digest(data, len, digest, &out_len, md, NULL);
} }
if (ok != 1 || (int)out_len != dlen) { free(digest); return NULL; } if (ok != 1 || (int)out_len != dlen) {
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); 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 #else
char *hex = (char*)malloc(1); char *hex = (char *)malloc(1);
if (hex) hex[0] = '\0'; if (hex) hex[0] = '\0';
return hex; return hex;
#endif #endif
} }
/* Compute SHA-256 hex of input buffer; returns malloc'ed C string (lowercase hex). */ /* 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 char *fun_libressl_sha256_hex(const unsigned char *data, size_t len) {
static const char hexdig[] = "0123456789abcdef"; static const char hexdig[] = "0123456789abcdef";
if (!data && len != 0) return NULL; if (!data && len != 0) return NULL;
#ifdef FUN_WITH_LIBRESSL #ifdef FUN_WITH_LIBRESSL
const EVP_MD *md = EVP_sha256(); const EVP_MD *md = EVP_sha256();
if (!md) return NULL; if (!md) return NULL;
int dlen = EVP_MD_get_size(md); int dlen = EVP_MD_get_size(md);
if (dlen <= 0) return NULL; if (dlen <= 0) return NULL;
unsigned char *digest = (unsigned char*)malloc((size_t)dlen); unsigned char *digest = (unsigned char *)malloc((size_t)dlen);
if (!digest) return NULL; if (!digest) return NULL;
unsigned int out_len = 0; unsigned int out_len = 0;
int ok; int ok;
if (len == 0) { if (len == 0) {
ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL);
} else { } else {
ok = EVP_Digest(data, len, digest, &out_len, md, NULL); ok = EVP_Digest(data, len, digest, &out_len, md, NULL);
} }
if (ok != 1 || (int)out_len != dlen) { free(digest); return NULL; } if (ok != 1 || (int)out_len != dlen) {
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); 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 #else
char *hex = (char*)malloc(1); char *hex = (char *)malloc(1);
if (hex) hex[0] = '\0'; if (hex) hex[0] = '\0';
return hex; return hex;
#endif #endif
} }
/* Compute SHA-512 hex of input buffer; returns malloc'ed C string (lowercase hex). */ /* 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 char *fun_libressl_sha512_hex(const unsigned char *data, size_t len) {
static const char hexdig[] = "0123456789abcdef"; static const char hexdig[] = "0123456789abcdef";
if (!data && len != 0) return NULL; if (!data && len != 0) return NULL;
#ifdef FUN_WITH_LIBRESSL #ifdef FUN_WITH_LIBRESSL
const EVP_MD *md = EVP_sha512(); const EVP_MD *md = EVP_sha512();
if (!md) return NULL; if (!md) return NULL;
int dlen = EVP_MD_get_size(md); int dlen = EVP_MD_get_size(md);
if (dlen <= 0) return NULL; if (dlen <= 0) return NULL;
unsigned char *digest = (unsigned char*)malloc((size_t)dlen); unsigned char *digest = (unsigned char *)malloc((size_t)dlen);
if (!digest) return NULL; if (!digest) return NULL;
unsigned int out_len = 0; unsigned int out_len = 0;
int ok; int ok;
if (len == 0) { if (len == 0) {
ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL);
} else { } else {
ok = EVP_Digest(data, len, digest, &out_len, md, NULL); ok = EVP_Digest(data, len, digest, &out_len, md, NULL);
} }
if (ok != 1 || (int)out_len != dlen) { free(digest); return NULL; } if (ok != 1 || (int)out_len != dlen) {
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); 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 #else
char *hex = (char*)malloc(1); char *hex = (char *)malloc(1);
if (hex) hex[0] = '\0'; if (hex) hex[0] = '\0';
return hex; return hex;
#endif #endif
} }
/* Compute RIPEMD-160 hex of input buffer; returns malloc'ed C string (lowercase hex). */ /* 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 char *fun_libressl_ripemd160_hex(const unsigned char *data, size_t len) {
static const char hexdig[] = "0123456789abcdef"; static const char hexdig[] = "0123456789abcdef";
if (!data && len != 0) return NULL; if (!data && len != 0) return NULL;
#ifdef FUN_WITH_LIBRESSL #ifdef FUN_WITH_LIBRESSL
const EVP_MD *md = EVP_ripemd160(); const EVP_MD *md = EVP_ripemd160();
if (!md) return NULL; if (!md) return NULL;
int dlen = EVP_MD_get_size(md); int dlen = EVP_MD_get_size(md);
if (dlen <= 0) return NULL; if (dlen <= 0) return NULL;
unsigned char *digest = (unsigned char*)malloc((size_t)dlen); unsigned char *digest = (unsigned char *)malloc((size_t)dlen);
if (!digest) return NULL; if (!digest) return NULL;
unsigned int out_len = 0; unsigned int out_len = 0;
int ok; int ok;
if (len == 0) { if (len == 0) {
ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL);
} else { } else {
ok = EVP_Digest(data, len, digest, &out_len, md, NULL); ok = EVP_Digest(data, len, digest, &out_len, md, NULL);
} }
if (ok != 1 || (int)out_len != dlen) { free(digest); return NULL; } if (ok != 1 || (int)out_len != dlen) {
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); 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 #else
char *hex = (char*)malloc(1); char *hex = (char *)malloc(1);
if (hex) hex[0] = '\0'; if (hex) hex[0] = '\0';
return hex; return hex;
#endif #endif
} }

51
src/external/libsql.c vendored
View file

@ -10,46 +10,49 @@
*/ */
#ifdef FUN_WITH_LIBSQL #ifdef FUN_WITH_LIBSQL
#include <sqlite3.h> /* libsql provides a sqlite3-compatible C API */
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdio.h>
#include <sqlite3.h> /* libsql provides a sqlite3-compatible C API */
typedef struct LibSqlHandle { typedef struct LibSqlHandle {
int id; int id;
sqlite3 *db; sqlite3 *db;
struct LibSqlHandle *next; struct LibSqlHandle *next;
} LibSqlHandle; } LibSqlHandle;
static LibSqlHandle *g_libsql_handles = NULL; static LibSqlHandle *g_libsql_handles = NULL;
static int g_libsql_next_id = 1; static int g_libsql_next_id = 1;
static LibSqlHandle *libsql_reg_add(sqlite3 *db) { static LibSqlHandle *libsql_reg_add(sqlite3 *db) {
LibSqlHandle *h = (LibSqlHandle*)malloc(sizeof(LibSqlHandle)); LibSqlHandle *h = (LibSqlHandle *)malloc(sizeof(LibSqlHandle));
if (!h) return NULL; if (!h) return NULL;
h->id = g_libsql_next_id++; h->id = g_libsql_next_id++;
h->db = db; h->db = db;
h->next = g_libsql_handles; h->next = g_libsql_handles;
g_libsql_handles = h; g_libsql_handles = h;
return h; return h;
} }
static LibSqlHandle *libsql_reg_get(int id) { static LibSqlHandle *libsql_reg_get(int id) {
LibSqlHandle *p = g_libsql_handles; LibSqlHandle *p = g_libsql_handles;
while (p) { if (p->id == id) return p; p = p->next; } while (p) {
return NULL; if (p->id == id) return p;
p = p->next;
}
return NULL;
} }
static void libsql_reg_del(int id) { static void libsql_reg_del(int id) {
LibSqlHandle **pp = &g_libsql_handles; LibSqlHandle **pp = &g_libsql_handles;
while (*pp) { while (*pp) {
if ((*pp)->id == id) { if ((*pp)->id == id) {
LibSqlHandle *dead = *pp; LibSqlHandle *dead = *pp;
*pp = (*pp)->next; *pp = (*pp)->next;
free(dead); free(dead);
return; return;
}
pp = &((*pp)->next);
} }
pp = &((*pp)->next);
}
} }
#endif #endif

246
src/external/openssl.c vendored
View file

@ -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/ * https://fun-lang.xyz/
* *
* Copyright 2026 Johannes Findeisen <you@hanez.org> * Copyright 2026 Johannes Findeisen <you@hanez.org>
@ -9,7 +9,7 @@
* Added: 2026-02-19 * Added: 2026-02-19
*/ */
/* /*
* OpenSSL integration helpers (MD5, RIPEMD-160, SHA-256, SHA-512) * 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 * returns an allocated empty string ("") to keep behavior consistent with
* other optional extensions. */ * other optional extensions. */
static char *fun_openssl_md5_hex(const unsigned char *data, size_t len) { static char *fun_openssl_md5_hex(const unsigned char *data, size_t len) {
static const char hexdig[] = "0123456789abcdef"; static const char hexdig[] = "0123456789abcdef";
if (!data && len != 0) return NULL; if (!data && len != 0) return NULL;
#ifdef FUN_WITH_OPENSSL #ifdef FUN_WITH_OPENSSL
const EVP_MD *md = EVP_md5(); const EVP_MD *md = EVP_md5();
if (!md) return NULL; if (!md) return NULL;
int dlen = EVP_MD_get_size(md); int dlen = EVP_MD_get_size(md);
if (dlen <= 0) return NULL; if (dlen <= 0) return NULL;
unsigned char *digest = (unsigned char*)malloc((size_t)dlen); unsigned char *digest = (unsigned char *)malloc((size_t)dlen);
if (!digest) return NULL; if (!digest) return NULL;
unsigned int out_len = 0; unsigned int out_len = 0;
int ok; int ok;
if (len == 0) { if (len == 0) {
// EVP_Digest handles zero-length fine as well // EVP_Digest handles zero-length fine as well
ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL);
} else { } else {
ok = EVP_Digest(data, len, digest, &out_len, md, NULL); ok = EVP_Digest(data, len, digest, &out_len, md, NULL);
} }
if (ok != 1 || (int)out_len != dlen) { free(digest); return NULL; } if (ok != 1 || (int)out_len != dlen) {
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); 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 #else
char *hex = (char*)malloc(1); char *hex = (char *)malloc(1);
if (hex) hex[0] = '\0'; if (hex) hex[0] = '\0';
return hex; return hex;
#endif #endif
} }
/* Compute SHA-256 hex of input buffer; returns malloc'ed C string (lowercase hex). /* Compute SHA-256 hex of input buffer; returns malloc'ed C string (lowercase hex).
* Fallback when OpenSSL disabled: empty string. */ * Fallback when OpenSSL disabled: empty string. */
static char *fun_openssl_sha256_hex(const unsigned char *data, size_t len) { static char *fun_openssl_sha256_hex(const unsigned char *data, size_t len) {
static const char hexdig[] = "0123456789abcdef"; static const char hexdig[] = "0123456789abcdef";
if (!data && len != 0) return NULL; if (!data && len != 0) return NULL;
#ifdef FUN_WITH_OPENSSL #ifdef FUN_WITH_OPENSSL
const EVP_MD *md = EVP_sha256(); const EVP_MD *md = EVP_sha256();
if (!md) return NULL; if (!md) return NULL;
int dlen = EVP_MD_get_size(md); int dlen = EVP_MD_get_size(md);
if (dlen <= 0) return NULL; if (dlen <= 0) return NULL;
unsigned char *digest = (unsigned char*)malloc((size_t)dlen); unsigned char *digest = (unsigned char *)malloc((size_t)dlen);
if (!digest) return NULL; if (!digest) return NULL;
unsigned int out_len = 0; unsigned int out_len = 0;
int ok; int ok;
if (len == 0) { if (len == 0) {
ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL);
} else { } else {
ok = EVP_Digest(data, len, digest, &out_len, md, NULL); ok = EVP_Digest(data, len, digest, &out_len, md, NULL);
} }
if (ok != 1 || (int)out_len != dlen) { free(digest); return NULL; } if (ok != 1 || (int)out_len != dlen) {
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); 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 #else
char *hex = (char*)malloc(1); char *hex = (char *)malloc(1);
if (hex) hex[0] = '\0'; if (hex) hex[0] = '\0';
return hex; return hex;
#endif #endif
} }
/* Compute SHA-512 hex of input buffer; returns malloc'ed C string (lowercase hex). /* Compute SHA-512 hex of input buffer; returns malloc'ed C string (lowercase hex).
* Fallback when OpenSSL disabled: empty string. */ * Fallback when OpenSSL disabled: empty string. */
static char *fun_openssl_sha512_hex(const unsigned char *data, size_t len) { static char *fun_openssl_sha512_hex(const unsigned char *data, size_t len) {
static const char hexdig[] = "0123456789abcdef"; static const char hexdig[] = "0123456789abcdef";
if (!data && len != 0) return NULL; if (!data && len != 0) return NULL;
#ifdef FUN_WITH_OPENSSL #ifdef FUN_WITH_OPENSSL
const EVP_MD *md = EVP_sha512(); const EVP_MD *md = EVP_sha512();
if (!md) return NULL; if (!md) return NULL;
int dlen = EVP_MD_get_size(md); int dlen = EVP_MD_get_size(md);
if (dlen <= 0) return NULL; if (dlen <= 0) return NULL;
unsigned char *digest = (unsigned char*)malloc((size_t)dlen); unsigned char *digest = (unsigned char *)malloc((size_t)dlen);
if (!digest) return NULL; if (!digest) return NULL;
unsigned int out_len = 0; unsigned int out_len = 0;
int ok; int ok;
if (len == 0) { if (len == 0) {
ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL);
} else { } else {
ok = EVP_Digest(data, len, digest, &out_len, md, NULL); ok = EVP_Digest(data, len, digest, &out_len, md, NULL);
} }
if (ok != 1 || (int)out_len != dlen) { free(digest); return NULL; } if (ok != 1 || (int)out_len != dlen) {
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); 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 #else
char *hex = (char*)malloc(1); char *hex = (char *)malloc(1);
if (hex) hex[0] = '\0'; if (hex) hex[0] = '\0';
return hex; return hex;
#endif #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 * 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. */ * 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 char *fun_openssl_ripemd160_hex(const unsigned char *data, size_t len) {
static const char hexdig[] = "0123456789abcdef"; static const char hexdig[] = "0123456789abcdef";
if (!data && len != 0) return NULL; if (!data && len != 0) return NULL;
#ifdef FUN_WITH_OPENSSL #ifdef FUN_WITH_OPENSSL
const EVP_MD *md = EVP_ripemd160(); const EVP_MD *md = EVP_ripemd160();
if (!md) return NULL; if (!md) return NULL;
int dlen = EVP_MD_get_size(md); int dlen = EVP_MD_get_size(md);
if (dlen <= 0) return NULL; if (dlen <= 0) return NULL;
unsigned char *digest = (unsigned char*)malloc((size_t)dlen); unsigned char *digest = (unsigned char *)malloc((size_t)dlen);
if (!digest) return NULL; if (!digest) return NULL;
unsigned int out_len = 0; unsigned int out_len = 0;
int ok; int ok;
if (len == 0) { if (len == 0) {
ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL); ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL);
} else { } else {
ok = EVP_Digest(data, len, digest, &out_len, md, NULL); ok = EVP_Digest(data, len, digest, &out_len, md, NULL);
} }
if (ok != 1 || (int)out_len != dlen) { free(digest); return NULL; } if (ok != 1 || (int)out_len != dlen) {
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); 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 #else
char *hex = (char*)malloc(1); char *hex = (char *)malloc(1);
if (hex) hex[0] = '\0'; if (hex) hex[0] = '\0';
return hex; return hex;
#endif #endif
} }

View file

@ -9,7 +9,7 @@
* Added: 2025-12-11 (2025-12-11 migrated from src/vm/libsql/common.c) * 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 * 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 * 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 * PCRE2_CODE_UNIT_WIDTH macro must be defined before the first inclusion of

87
src/external/pcsc.c vendored
View file

@ -16,61 +16,70 @@ Included the file scope from vm.c.
#ifdef FUN_WITH_PCSC #ifdef FUN_WITH_PCSC
#if defined(__has_include) #if defined(__has_include)
#if __has_include(<PCSC/winscard.h>) #if __has_include(<PCSC/winscard.h>)
#include <PCSC/winscard.h> #include <PCSC/winscard.h>
#include <PCSC/wintypes.h> #include <PCSC/wintypes.h>
#elif __has_include(<winscard.h>) #elif __has_include(<winscard.h>)
#include <winscard.h> #include <winscard.h>
#else #else
#error "FUN_WITH_PCSC is enabled but PCSC headers were not found" #error "FUN_WITH_PCSC is enabled but PCSC headers were not found"
#endif #endif
#else #else
#include <PCSC/winscard.h> #include <PCSC/winscard.h>
#include <PCSC/wintypes.h> #include <PCSC/wintypes.h>
#endif #endif
#include <string.h> #include <string.h>
typedef struct {
SCARDCONTEXT ctx;
int in_use;
} pcsc_ctx_entry;
typedef struct { typedef struct {
SCARDHANDLE h; SCARDCONTEXT ctx;
DWORD proto; int in_use;
int in_use; } pcsc_ctx_entry;
typedef struct {
SCARDHANDLE h;
DWORD proto;
int in_use;
} pcsc_card_entry; } pcsc_card_entry;
static pcsc_ctx_entry g_pcsc_ctx[8]; static pcsc_ctx_entry g_pcsc_ctx[8];
static pcsc_card_entry g_pcsc_card[32]; static pcsc_card_entry g_pcsc_card[32];
static int pcsc_alloc_ctx_slot(void) { static int pcsc_alloc_ctx_slot(void) {
for (int i = 0; i < (int)(sizeof(g_pcsc_ctx)/sizeof(g_pcsc_ctx[0])); ++i) { 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; } 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) { static int pcsc_alloc_card_slot(void) {
for (int i = 0; i < (int)(sizeof(g_pcsc_card)/sizeof(g_pcsc_card[0])); ++i) { 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; } 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) { static pcsc_ctx_entry *pcsc_get_ctx(int id) {
if (id <= 0) return NULL; if (id <= 0) return NULL;
int idx = id - 1; int idx = id - 1;
if (idx < 0 || idx >= (int)(sizeof(g_pcsc_ctx)/sizeof(g_pcsc_ctx[0]))) return NULL; 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; if (!g_pcsc_ctx[idx].in_use) return NULL;
return &g_pcsc_ctx[idx]; return &g_pcsc_ctx[idx];
} }
static pcsc_card_entry* pcsc_get_card(int id) { static pcsc_card_entry *pcsc_get_card(int id) {
if (id <= 0) return NULL; if (id <= 0) return NULL;
int idx = id - 1; int idx = id - 1;
if (idx < 0 || idx >= (int)(sizeof(g_pcsc_card)/sizeof(g_pcsc_card[0]))) return NULL; 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; if (!g_pcsc_card[idx].in_use) return NULL;
return &g_pcsc_card[idx]; return &g_pcsc_card[idx];
} }
#endif #endif

42
src/external/sqlite.c vendored
View file

@ -16,34 +16,40 @@
#include <sqlite3.h> #include <sqlite3.h>
typedef struct SqlHandle { typedef struct SqlHandle {
int id; int id;
sqlite3 *db; sqlite3 *db;
struct SqlHandle *next; struct SqlHandle *next;
} SqlHandle; } SqlHandle;
static SqlHandle *g_sql_handles = NULL; static SqlHandle *g_sql_handles = NULL;
static int g_sql_next_id = 1; static int g_sql_next_id = 1;
static SqlHandle* sql_reg_add(sqlite3 *db) { static SqlHandle *sql_reg_add(sqlite3 *db) {
SqlHandle *h = (SqlHandle*)calloc(1, sizeof(SqlHandle)); SqlHandle *h = (SqlHandle *)calloc(1, sizeof(SqlHandle));
if (!h) return NULL; if (!h) return NULL;
h->id = g_sql_next_id++; h->id = g_sql_next_id++;
h->db = db; h->db = db;
h->next = g_sql_handles; h->next = g_sql_handles;
g_sql_handles = h; g_sql_handles = h;
return h; return h;
} }
static SqlHandle* sql_reg_get(int id) { static SqlHandle *sql_reg_get(int id) {
for (SqlHandle *p = g_sql_handles; p; p = p->next) if (p->id == id) return p; for (SqlHandle *p = g_sql_handles; p; p = p->next)
return NULL; if (p->id == id) return p;
return NULL;
} }
static void sql_reg_del(int id) { static void sql_reg_del(int id) {
SqlHandle **pp = &g_sql_handles; SqlHandle **pp = &g_sql_handles;
while (*pp) { while (*pp) {
if ((*pp)->id == id) { SqlHandle *d = *pp; *pp = d->next; free(d); return; } if ((*pp)->id == id) {
pp = &(*pp)->next; SqlHandle *d = *pp;
*pp = d->next;
free(d);
return;
} }
pp = &(*pp)->next;
}
} }
#endif #endif

82
src/external/tcltk.c vendored
View file

@ -12,58 +12,68 @@
#ifdef FUN_WITH_TCLTK #ifdef FUN_WITH_TCLTK
#include <tcl.h> #include <tcl.h>
#include <tk.h> #include <tk.h>
static Tcl_Interp* g_fun_tcl_interp = NULL; static Tcl_Interp *g_fun_tcl_interp = NULL;
static void fun_tk_init_once(void) { static void fun_tk_init_once(void) {
if (g_fun_tcl_interp) return; if (g_fun_tcl_interp) return;
Tcl_FindExecutable(NULL); Tcl_FindExecutable(NULL);
g_fun_tcl_interp = Tcl_CreateInterp(); g_fun_tcl_interp = Tcl_CreateInterp();
if (!g_fun_tcl_interp) return; if (!g_fun_tcl_interp) return;
if (Tcl_Init(g_fun_tcl_interp) != TCL_OK) { if (Tcl_Init(g_fun_tcl_interp) != TCL_OK) {
fprintf(stderr, "Tcl_Init failed: %s\n", Tcl_GetStringResult(g_fun_tcl_interp)); fprintf(stderr, "Tcl_Init failed: %s\n", Tcl_GetStringResult(g_fun_tcl_interp));
} }
if (Tk_Init(g_fun_tcl_interp) != TCL_OK) { if (Tk_Init(g_fun_tcl_interp) != TCL_OK) {
fprintf(stderr, "Tk_Init failed: %s\n", Tcl_GetStringResult(g_fun_tcl_interp)); 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 */ /* Ensure the app terminates if the main window is closed via window manager */
/* Best-effort: set WM_DELETE_WINDOW handler to exit the process. */ /* Best-effort: set WM_DELETE_WINDOW handler to exit the process. */
Tcl_Eval(g_fun_tcl_interp, "wm protocol . WM_DELETE_WINDOW {exit 0}"); Tcl_Eval(g_fun_tcl_interp, "wm protocol . WM_DELETE_WINDOW {exit 0}");
} }
static int fun_tk_eval_script(const char *script) { static int fun_tk_eval_script(const char *script) {
fun_tk_init_once(); fun_tk_init_once();
if (!g_fun_tcl_interp) return -1; if (!g_fun_tcl_interp) return -1;
int rc = Tcl_Eval(g_fun_tcl_interp, script ? script : ""); int rc = Tcl_Eval(g_fun_tcl_interp, script ? script : "");
return rc; /* TCL_OK = 0 */ return rc; /* TCL_OK = 0 */
} }
static const char* fun_tk_get_result(void) { static const char *fun_tk_get_result(void) {
fun_tk_init_once(); fun_tk_init_once();
if (!g_fun_tcl_interp) return ""; if (!g_fun_tcl_interp) return "";
return Tcl_GetStringResult(g_fun_tcl_interp); return Tcl_GetStringResult(g_fun_tcl_interp);
} }
static void fun_tk_loop(void) { static void fun_tk_loop(void) {
fun_tk_init_once(); fun_tk_init_once();
if (!g_fun_tcl_interp) return; if (!g_fun_tcl_interp) return;
/* Drive Tk event loop until all main windows are closed */ /* Drive Tk event loop until all main windows are closed */
while (Tk_GetNumMainWindows() > 0) { while (Tk_GetNumMainWindows() > 0) {
while (Tcl_DoOneEvent(0)) {} while (Tcl_DoOneEvent(0)) {
/* tiny sleep to avoid busy spin */ }
/* tiny sleep to avoid busy spin */
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
Sleep(1); Sleep(1);
#else #else
#include <time.h> #include <time.h>
struct timespec ts = {0, 1000000}; /* 1 ms */ struct timespec ts = {0, 1000000}; /* 1 ms */
nanosleep(&ts, NULL); nanosleep(&ts, NULL);
#endif #endif
} }
} }
#else #else
/* Stubs when Tcl/Tk is disabled */ /* Stubs when Tcl/Tk is disabled */
static void fun_tk_init_once(void) { (void)0; } static void fun_tk_init_once(void) {
static int fun_tk_eval_script(const char *script) { (void)script; return -1; } (void)0;
static const char* fun_tk_get_result(void) { return ""; } }
static void fun_tk_loop(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 #endif

34
src/external/xml2.c vendored
View file

@ -13,24 +13,34 @@
#include <libxml/parser.h> #include <libxml/parser.h>
#include <libxml/tree.h> #include <libxml/tree.h>
typedef struct { xmlDocPtr doc; int in_use; } XmlDocSlot; typedef struct {
typedef struct { xmlNodePtr node; int in_use; } XmlNodeSlot; xmlDocPtr doc;
int in_use;
} XmlDocSlot;
typedef struct {
xmlNodePtr node;
int in_use;
} XmlNodeSlot;
static XmlDocSlot g_xml_docs[64]; static XmlDocSlot g_xml_docs[64];
static XmlNodeSlot g_xml_nodes[256]; static XmlNodeSlot g_xml_nodes[256];
static int xml_doc_alloc(xmlDocPtr d) { static int xml_doc_alloc(xmlDocPtr d) {
for (int i = 1; i < (int)(sizeof(g_xml_docs)/sizeof(g_xml_docs[0])); ++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; } if (!g_xml_docs[i].in_use) {
g_xml_docs[i].in_use = 1;
g_xml_docs[i].doc = d;
return i;
}
} }
return 0; return 0;
} }
static xmlDocPtr xml_doc_get(int h) { 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; return NULL;
} }
static int xml_doc_free_handle(int h) { 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); if (g_xml_docs[h].doc) xmlFreeDoc(g_xml_docs[h].doc);
g_xml_docs[h].doc = NULL; g_xml_docs[h].doc = NULL;
g_xml_docs[h].in_use = 0; 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) { static int xml_node_alloc(xmlNodePtr n) {
for (int i = 1; i < (int)(sizeof(g_xml_nodes)/sizeof(g_xml_nodes[0])); ++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; } if (!g_xml_nodes[i].in_use) {
g_xml_nodes[i].in_use = 1;
g_xml_nodes[i].node = n;
return i;
}
} }
return 0; return 0;
} }
static xmlNodePtr xml_node_get(int h) { 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; return NULL;
} }
static int xml_node_free_handle(int h) { 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 */ /* nodes are owned by their document; do not free here */
g_xml_nodes[h].node = NULL; g_xml_nodes[h].node = NULL;
g_xml_nodes[h].in_use = 0; g_xml_nodes[h].in_use = 0;

212
src/fun.c
View file

@ -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/ * https://fun-lang.xyz/
* *
* Copyright 2025 Johannes Findeisen <you@hanez.org> * Copyright 2025 Johannes Findeisen <you@hanez.org>
@ -7,15 +7,15 @@
* https://opensource.org/license/apache-2-0 * https://opensource.org/license/apache-2-0
*/ */
/* /*
* Main entry point for the Fun language interpreter. * Main entry point for the Fun language interpreter.
* Builds a CLI that runs a script file if provided; otherwise starts the REPL * Builds a CLI that runs a script file if provided; otherwise starts the REPL
* when compiled with FUN_WITH_REPL enabled. * when compiled with FUN_WITH_REPL enabled.
*/ */
#include "bytecode.h" #include "bytecode.h"
#include "vm.h"
#include "parser.h" #include "parser.h"
#include "vm.h"
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -30,127 +30,127 @@
#endif #endif
static void print_usage(const char *prog) { static void print_usage(const char *prog) {
printf("Fun %s\n", FUN_VERSION); printf("Fun %s\n", FUN_VERSION);
printf("Usage:\n"); printf("Usage:\n");
#ifdef FUN_WITH_REPL #ifdef FUN_WITH_REPL
printf(" %s [--trace|-t] [--repl-on-error] [script.fun]\n", prog ? prog : "fun"); printf(" %s [--trace|-t] [--repl-on-error] [script.fun]\n", prog ? prog : "fun");
printf(" %s --help | -h\n", prog ? prog : "fun"); printf(" %s --help | -h\n", prog ? prog : "fun");
printf(" %s --version | -V\n", prog ? prog : "fun"); printf(" %s --version | -V\n", prog ? prog : "fun");
printf("\n"); printf("\n");
printf("Options:\n"); printf("Options:\n");
printf(" --trace, -t Print executed ops and stack tops during run\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(" --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("When no script is provided, a REPL starts. Submit an empty line to execute the buffer.\n");
#else #else
printf(" %s [--trace|-t] <script.fun>\n", prog ? prog : "fun"); printf(" %s [--trace|-t] <script.fun>\n", prog ? prog : "fun");
printf(" %s --help | -h\n", prog ? prog : "fun"); printf(" %s --help | -h\n", prog ? prog : "fun");
printf(" %s --version | -V\n", prog ? prog : "fun"); printf(" %s --version | -V\n", prog ? prog : "fun");
printf("\n"); printf("\n");
printf("Options:\n --trace, -t Print executed ops and stack tops during run\n\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("REPL is disabled in this build. Please provide a script file to run.\n");
#endif #endif
} }
int main(int argc, char **argv) { int main(int argc, char **argv) {
/* Set FUN_EXECUTABLE environment variable to the path of this binary */ /* Set FUN_EXECUTABLE environment variable to the path of this binary */
setenv("FUN_EXECUTABLE", argv[0], 1); setenv("FUN_EXECUTABLE", argv[0], 1);
VM vm; VM vm;
vm_init(&vm); vm_init(&vm);
int argi = 1; int argi = 1;
for (; argi < argc; ++argi) { for (; argi < argc; ++argi) {
const char *arg = argv[argi]; const char *arg = argv[argi];
if (strcmp(arg, "--help") == 0 || strcmp(arg, "-h") == 0) { if (strcmp(arg, "--help") == 0 || strcmp(arg, "-h") == 0) {
print_usage(argv[0]); print_usage(argv[0]);
return 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;
} }
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 #ifndef FUN_WITH_REPL
if (argi >= argc) { if (argi >= argc) {
fprintf(stderr, "Error: REPL is disabled. Please provide a script to run.\n"); fprintf(stderr, "Error: REPL is disabled. Please provide a script to run.\n");
print_usage(argv[0]); print_usage(argv[0]);
return 2; return 2;
} }
#endif #endif
if (argi < argc) { if (argi < argc) {
const char *path = argv[argi]; const char *path = argv[argi];
/* Collect script arguments (everything after script path) and expose via env vars */ /* Collect script arguments (everything after script path) and expose via env vars */
int sargi = argi + 1; /* first script arg following the script path */ int sargi = argi + 1; /* first script arg following the script path */
int sargc = (sargi < argc) ? (argc - sargi) : 0; int sargc = (sargi < argc) ? (argc - sargi) : 0;
/* Export FUN_ARGC */ /* Export FUN_ARGC */
{ {
char buf[32]; char buf[32];
snprintf(buf, sizeof(buf), "%d", sargc); snprintf(buf, sizeof(buf), "%d", sargc);
setenv("FUN_ARGC", buf, 1); 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_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 #ifdef FUN_WITH_REPL
return fun_run_repl(&vm); return fun_run_repl(&vm);
#else #else
fprintf(stderr, "Internal error: REPL not available in this build.\n"); fprintf(stderr, "Internal error: REPL not available in this build.\n");
return 2; return 2;
#endif #endif
} }

View file

@ -10,302 +10,302 @@
#include "bytecode.h" #include "bytecode.h"
#include "value.h" #include "value.h"
#include "vm.h" #include "vm.h"
#include <stdio.h>
#include <math.h> #include <math.h>
#include <stdio.h>
#define ASSERT_EQ(val, expected) \ #define ASSERT_EQ(val, expected) \
if ((val).type != VAL_INT || (val).i != (expected)) { \ if ((val).type != VAL_INT || (val).i != (expected)) { \
fprintf(stderr, "Assertion failed: expected %lld, got ", (long long)(expected)); \ fprintf(stderr, "Assertion failed: expected %lld, got ", (long long)(expected)); \
print_value(&(val)); \ print_value(&(val)); \
printf("\n"); \ printf("\n"); \
return 1; \ return 1; \
} }
int main(void) { int main(void) {
VM vm; VM vm;
vm_init(&vm); vm_init(&vm);
Bytecode *bc = bytecode_new(); Bytecode *bc = bytecode_new();
// constants // constants
int c0 = bytecode_add_constant(bc, make_int(0)); int c0 = bytecode_add_constant(bc, make_int(0));
int c1 = bytecode_add_constant(bc, make_int(1)); int c1 = bytecode_add_constant(bc, make_int(1));
int c2 = bytecode_add_constant(bc, make_int(2)); int c2 = bytecode_add_constant(bc, make_int(2));
int c3 = bytecode_add_constant(bc, make_int(3)); int c3 = bytecode_add_constant(bc, make_int(3));
int c10 = bytecode_add_constant(bc, make_int(10)); int c10 = bytecode_add_constant(bc, make_int(10));
int c42 = bytecode_add_constant(bc, make_int(42)); int c42 = bytecode_add_constant(bc, make_int(42));
int cf3_2 = bytecode_add_constant(bc, make_float(3.2)); int cf3_2 = bytecode_add_constant(bc, make_float(3.2));
int cf3_5 = bytecode_add_constant(bc, make_float(3.5)); int cf3_5 = bytecode_add_constant(bc, make_float(3.5));
int cf3_8 = bytecode_add_constant(bc, make_float(3.8)); int cf3_8 = bytecode_add_constant(bc, make_float(3.8));
int cfn3_2 = bytecode_add_constant(bc, make_float(-3.2)); int cfn3_2 = bytecode_add_constant(bc, make_float(-3.2));
int cfn3_5 = bytecode_add_constant(bc, make_float(-3.5)); int cfn3_5 = bytecode_add_constant(bc, make_float(-3.5));
int cfn3_8 = bytecode_add_constant(bc, make_float(-3.8)); int cfn3_8 = bytecode_add_constant(bc, make_float(-3.8));
int cf0 = bytecode_add_constant(bc, make_float(0.0)); int cf0 = bytecode_add_constant(bc, make_float(0.0));
int cf1 = bytecode_add_constant(bc, make_float(1.0)); int cf1 = bytecode_add_constant(bc, make_float(1.0));
int cf5 = bytecode_add_constant(bc, make_float(5.0)); int cf5 = bytecode_add_constant(bc, make_float(5.0));
int c4 = bytecode_add_constant(bc, make_int(4)); int c4 = bytecode_add_constant(bc, make_int(4));
int c9 = bytecode_add_constant(bc, make_int(9)); int c9 = bytecode_add_constant(bc, make_int(9));
int c48 = bytecode_add_constant(bc, make_int(48)); int c48 = bytecode_add_constant(bc, make_int(48));
int c18 = bytecode_add_constant(bc, make_int(18)); int c18 = bytecode_add_constant(bc, make_int(18));
int c21 = bytecode_add_constant(bc, make_int(21)); int c21 = bytecode_add_constant(bc, make_int(21));
int c6 = bytecode_add_constant(bc, make_int(6)); int c6 = bytecode_add_constant(bc, make_int(6));
int c15 = bytecode_add_constant(bc, make_int(15)); int c15 = bytecode_add_constant(bc, make_int(15));
int c16 = bytecode_add_constant(bc, make_int(16)); int c16 = bytecode_add_constant(bc, make_int(16));
int cneg5 = bytecode_add_constant(bc, make_int(-5)); int cneg5 = bytecode_add_constant(bc, make_int(-5));
int c7 = bytecode_add_constant(bc, make_int(7)); int c7 = bytecode_add_constant(bc, make_int(7));
// ---------- Arithmetic ---------- // ---------- Arithmetic ----------
bytecode_add_instruction(bc, OP_LOAD_CONST, c42); bytecode_add_instruction(bc, OP_LOAD_CONST, c42);
bytecode_add_instruction(bc, OP_LOAD_CONST, c1); bytecode_add_instruction(bc, OP_LOAD_CONST, c1);
bytecode_add_instruction(bc, OP_ADD, 0); // 42+1=43 bytecode_add_instruction(bc, OP_ADD, 0); // 42+1=43
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
bytecode_add_instruction(bc, OP_LOAD_CONST, c10); bytecode_add_instruction(bc, OP_LOAD_CONST, c10);
bytecode_add_instruction(bc, OP_LOAD_CONST, c3); bytecode_add_instruction(bc, OP_LOAD_CONST, c3);
bytecode_add_instruction(bc, OP_SUB, 0); // 10-3=7 bytecode_add_instruction(bc, OP_SUB, 0); // 10-3=7
bytecode_add_instruction(bc, OP_PRINT, 0); 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_LOAD_CONST, c3); bytecode_add_instruction(bc, OP_LOAD_CONST, c3);
bytecode_add_instruction(bc, OP_MUL, 0); // 2*3=6 bytecode_add_instruction(bc, OP_MUL, 0); // 2*3=6
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
bytecode_add_instruction(bc, OP_LOAD_CONST, c10); bytecode_add_instruction(bc, OP_LOAD_CONST, c10);
bytecode_add_instruction(bc, OP_LOAD_CONST, c2); bytecode_add_instruction(bc, OP_LOAD_CONST, c2);
bytecode_add_instruction(bc, OP_DIV, 0); // 10/2=5 bytecode_add_instruction(bc, OP_DIV, 0); // 10/2=5
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
bytecode_add_instruction(bc, OP_LOAD_CONST, c10); bytecode_add_instruction(bc, OP_LOAD_CONST, c10);
bytecode_add_instruction(bc, OP_LOAD_CONST, c3); bytecode_add_instruction(bc, OP_LOAD_CONST, c3);
bytecode_add_instruction(bc, OP_MOD, 0); // 10%3=1 bytecode_add_instruction(bc, OP_MOD, 0); // 10%3=1
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// ---------- Comparisons ---------- // ---------- Comparisons ----------
bytecode_add_instruction(bc, OP_LOAD_CONST, c1); bytecode_add_instruction(bc, OP_LOAD_CONST, c1);
bytecode_add_instruction(bc, OP_LOAD_CONST, c2); bytecode_add_instruction(bc, OP_LOAD_CONST, c2);
bytecode_add_instruction(bc, OP_LT, 0); // 1<2=1 bytecode_add_instruction(bc, OP_LT, 0); // 1<2=1
bytecode_add_instruction(bc, OP_PRINT, 0); 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_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_LTE, 0); // 2<=2=1
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
bytecode_add_instruction(bc, OP_LOAD_CONST, c3); bytecode_add_instruction(bc, OP_LOAD_CONST, c3);
bytecode_add_instruction(bc, OP_LOAD_CONST, c2); bytecode_add_instruction(bc, OP_LOAD_CONST, c2);
bytecode_add_instruction(bc, OP_GT, 0); // 3>2=1 bytecode_add_instruction(bc, OP_GT, 0); // 3>2=1
bytecode_add_instruction(bc, OP_PRINT, 0); 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_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_GTE, 0); // 2>=2=1
bytecode_add_instruction(bc, OP_PRINT, 0); 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_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_EQ, 0); // 2==2=1
bytecode_add_instruction(bc, OP_PRINT, 0); 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_LOAD_CONST, c3); bytecode_add_instruction(bc, OP_LOAD_CONST, c3);
bytecode_add_instruction(bc, OP_NEQ, 0); // 2!=3=1 bytecode_add_instruction(bc, OP_NEQ, 0); // 2!=3=1
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// ---------- Logical ---------- // ---------- Logical ----------
bytecode_add_instruction(bc, OP_LOAD_CONST, c1); bytecode_add_instruction(bc, OP_LOAD_CONST, c1);
bytecode_add_instruction(bc, OP_LOAD_CONST, c0); bytecode_add_instruction(bc, OP_LOAD_CONST, c0);
bytecode_add_instruction(bc, OP_AND, 0); // 1&&0=0 bytecode_add_instruction(bc, OP_AND, 0); // 1&&0=0
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
bytecode_add_instruction(bc, OP_LOAD_CONST, c1); bytecode_add_instruction(bc, OP_LOAD_CONST, c1);
bytecode_add_instruction(bc, OP_LOAD_CONST, c0); bytecode_add_instruction(bc, OP_LOAD_CONST, c0);
bytecode_add_instruction(bc, OP_OR, 0); // 1||0=1 bytecode_add_instruction(bc, OP_OR, 0); // 1||0=1
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
bytecode_add_instruction(bc, OP_LOAD_CONST, c0); bytecode_add_instruction(bc, OP_LOAD_CONST, c0);
bytecode_add_instruction(bc, OP_NOT, 0); // !0=1 bytecode_add_instruction(bc, OP_NOT, 0); // !0=1
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// ---------- Stack ---------- // ---------- Stack ----------
bytecode_add_instruction(bc, OP_LOAD_CONST, c1); bytecode_add_instruction(bc, OP_LOAD_CONST, c1);
bytecode_add_instruction(bc, OP_DUP, 0); // duplicate 1 bytecode_add_instruction(bc, OP_DUP, 0); // duplicate 1
bytecode_add_instruction(bc, OP_ADD, 0); // 1+1=2 bytecode_add_instruction(bc, OP_ADD, 0); // 1+1=2
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
bytecode_add_instruction(bc, OP_LOAD_CONST, c1); bytecode_add_instruction(bc, OP_LOAD_CONST, c1);
bytecode_add_instruction(bc, OP_LOAD_CONST, c2); bytecode_add_instruction(bc, OP_LOAD_CONST, c2);
bytecode_add_instruction(bc, OP_SWAP, 0); // swap top two bytecode_add_instruction(bc, OP_SWAP, 0); // swap top two
bytecode_add_instruction(bc, OP_PRINT, 0); // top=1 bytecode_add_instruction(bc, OP_PRINT, 0); // top=1
bytecode_add_instruction(bc, OP_LOAD_CONST, c1); 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_POP, 0); // dApache-2.0ard 1 (stack now empty)
// ---------- Rounding (math.h) demo ---------- // ---------- Rounding (math.h) demo ----------
// floor/ceil/trunc/round on representative values // floor/ceil/trunc/round on representative values
int start_round_demo = bc->instr_count; int start_round_demo = bc->instr_count;
(void)start_round_demo; (void)start_round_demo;
// +3.2 // +3.2
bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2);
bytecode_add_instruction(bc, OP_FLOOR, 0); bytecode_add_instruction(bc, OP_FLOOR, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2);
bytecode_add_instruction(bc, OP_CEIL, 0); bytecode_add_instruction(bc, OP_CEIL, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2);
bytecode_add_instruction(bc, OP_TRUNC, 0); bytecode_add_instruction(bc, OP_TRUNC, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2);
bytecode_add_instruction(bc, OP_ROUND, 0); bytecode_add_instruction(bc, OP_ROUND, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// +3.5 // +3.5
bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_5); bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_5);
bytecode_add_instruction(bc, OP_ROUND, 0); bytecode_add_instruction(bc, OP_ROUND, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// -3.5 // -3.5
bytecode_add_instruction(bc, OP_LOAD_CONST, cfn3_5); bytecode_add_instruction(bc, OP_LOAD_CONST, cfn3_5);
bytecode_add_instruction(bc, OP_ROUND, 0); bytecode_add_instruction(bc, OP_ROUND, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// -3.2 // -3.2
bytecode_add_instruction(bc, OP_LOAD_CONST, cfn3_2); bytecode_add_instruction(bc, OP_LOAD_CONST, cfn3_2);
bytecode_add_instruction(bc, OP_FLOOR, 0); bytecode_add_instruction(bc, OP_FLOOR, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
bytecode_add_instruction(bc, OP_LOAD_CONST, cfn3_2); bytecode_add_instruction(bc, OP_LOAD_CONST, cfn3_2);
bytecode_add_instruction(bc, OP_CEIL, 0); bytecode_add_instruction(bc, OP_CEIL, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// integers should be unchanged // integers should be unchanged
bytecode_add_instruction(bc, OP_LOAD_CONST, c10); bytecode_add_instruction(bc, OP_LOAD_CONST, c10);
bytecode_add_instruction(bc, OP_FLOOR, 0); bytecode_add_instruction(bc, OP_FLOOR, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// ---------- Transcendentals demo ---------- // ---------- Transcendentals demo ----------
// sin(0)=0 // sin(0)=0
bytecode_add_instruction(bc, OP_LOAD_CONST, cf0); bytecode_add_instruction(bc, OP_LOAD_CONST, cf0);
bytecode_add_instruction(bc, OP_SIN, 0); bytecode_add_instruction(bc, OP_SIN, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// cos(0)=1 // cos(0)=1
bytecode_add_instruction(bc, OP_LOAD_CONST, cf0); bytecode_add_instruction(bc, OP_LOAD_CONST, cf0);
bytecode_add_instruction(bc, OP_COS, 0); bytecode_add_instruction(bc, OP_COS, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// tan(0)=0 // tan(0)=0
bytecode_add_instruction(bc, OP_LOAD_CONST, cf0); bytecode_add_instruction(bc, OP_LOAD_CONST, cf0);
bytecode_add_instruction(bc, OP_TAN, 0); bytecode_add_instruction(bc, OP_TAN, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// exp(0)=1 // exp(0)=1
bytecode_add_instruction(bc, OP_LOAD_CONST, cf0); bytecode_add_instruction(bc, OP_LOAD_CONST, cf0);
bytecode_add_instruction(bc, OP_EXP, 0); bytecode_add_instruction(bc, OP_EXP, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// log(1)=0 // log(1)=0
bytecode_add_instruction(bc, OP_LOAD_CONST, cf1); bytecode_add_instruction(bc, OP_LOAD_CONST, cf1);
bytecode_add_instruction(bc, OP_LOG, 0); bytecode_add_instruction(bc, OP_LOG, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// log10(1)=0 // log10(1)=0
bytecode_add_instruction(bc, OP_LOAD_CONST, cf1); bytecode_add_instruction(bc, OP_LOAD_CONST, cf1);
bytecode_add_instruction(bc, OP_LOG10, 0); bytecode_add_instruction(bc, OP_LOG10, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// sqrt(9)=3 // sqrt(9)=3
bytecode_add_instruction(bc, OP_LOAD_CONST, c9); bytecode_add_instruction(bc, OP_LOAD_CONST, c9);
bytecode_add_instruction(bc, OP_SQRT, 0); bytecode_add_instruction(bc, OP_SQRT, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// ---------- Integer math (gcd/lcm/isqrt/sign) demo ---------- // ---------- Integer math (gcd/lcm/isqrt/sign) demo ----------
// gcd(48,18)=6 // gcd(48,18)=6
bytecode_add_instruction(bc, OP_LOAD_CONST, c48); bytecode_add_instruction(bc, OP_LOAD_CONST, c48);
bytecode_add_instruction(bc, OP_LOAD_CONST, c18); bytecode_add_instruction(bc, OP_LOAD_CONST, c18);
bytecode_add_instruction(bc, OP_GCD, 0); bytecode_add_instruction(bc, OP_GCD, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// lcm(21,6)=42 // lcm(21,6)=42
bytecode_add_instruction(bc, OP_LOAD_CONST, c21); bytecode_add_instruction(bc, OP_LOAD_CONST, c21);
bytecode_add_instruction(bc, OP_LOAD_CONST, c6); bytecode_add_instruction(bc, OP_LOAD_CONST, c6);
bytecode_add_instruction(bc, OP_LCM, 0); bytecode_add_instruction(bc, OP_LCM, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// isqrt cases: 0, 1, 15->3, 16->4 // isqrt cases: 0, 1, 15->3, 16->4
bytecode_add_instruction(bc, OP_LOAD_CONST, c0); bytecode_add_instruction(bc, OP_LOAD_CONST, c0);
bytecode_add_instruction(bc, OP_ISQRT, 0); bytecode_add_instruction(bc, OP_ISQRT, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
bytecode_add_instruction(bc, OP_LOAD_CONST, c1); bytecode_add_instruction(bc, OP_LOAD_CONST, c1);
bytecode_add_instruction(bc, OP_ISQRT, 0); bytecode_add_instruction(bc, OP_ISQRT, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
bytecode_add_instruction(bc, OP_LOAD_CONST, c15); bytecode_add_instruction(bc, OP_LOAD_CONST, c15);
bytecode_add_instruction(bc, OP_ISQRT, 0); bytecode_add_instruction(bc, OP_ISQRT, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
bytecode_add_instruction(bc, OP_LOAD_CONST, c16); bytecode_add_instruction(bc, OP_LOAD_CONST, c16);
bytecode_add_instruction(bc, OP_ISQRT, 0); bytecode_add_instruction(bc, OP_ISQRT, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// sign(-5)=-1, sign(0)=0, sign(7)=1 // sign(-5)=-1, sign(0)=0, sign(7)=1
bytecode_add_instruction(bc, OP_LOAD_CONST, cneg5); bytecode_add_instruction(bc, OP_LOAD_CONST, cneg5);
bytecode_add_instruction(bc, OP_SIGN, 0); bytecode_add_instruction(bc, OP_SIGN, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
bytecode_add_instruction(bc, OP_LOAD_CONST, c0); bytecode_add_instruction(bc, OP_LOAD_CONST, c0);
bytecode_add_instruction(bc, OP_SIGN, 0); bytecode_add_instruction(bc, OP_SIGN, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
bytecode_add_instruction(bc, OP_LOAD_CONST, c7); bytecode_add_instruction(bc, OP_LOAD_CONST, c7);
bytecode_add_instruction(bc, OP_SIGN, 0); bytecode_add_instruction(bc, OP_SIGN, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// ---------- fmin/fmax demo ---------- // ---------- fmin/fmax demo ----------
// fmin(3.2, 4) -> 3.2 // fmin(3.2, 4) -> 3.2
bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2);
bytecode_add_instruction(bc, OP_LOAD_CONST, c4); bytecode_add_instruction(bc, OP_LOAD_CONST, c4);
bytecode_add_instruction(bc, OP_FMIN, 0); bytecode_add_instruction(bc, OP_FMIN, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// fmax(3.2, 4) -> 4 // fmax(3.2, 4) -> 4
bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2);
bytecode_add_instruction(bc, OP_LOAD_CONST, c4); bytecode_add_instruction(bc, OP_LOAD_CONST, c4);
bytecode_add_instruction(bc, OP_FMAX, 0); bytecode_add_instruction(bc, OP_FMAX, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// NaN cases // NaN cases
double nanv = NAN; double nanv = NAN;
int cNaN = bytecode_add_constant(bc, make_float(nanv)); int cNaN = bytecode_add_constant(bc, make_float(nanv));
// fmin(NaN, 5.0) -> 5.0 // fmin(NaN, 5.0) -> 5.0
bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN); bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN);
bytecode_add_instruction(bc, OP_LOAD_CONST, cf5); bytecode_add_instruction(bc, OP_LOAD_CONST, cf5);
bytecode_add_instruction(bc, OP_FMIN, 0); bytecode_add_instruction(bc, OP_FMIN, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// fmax(NaN, 5.0) -> 5.0 // fmax(NaN, 5.0) -> 5.0
bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN); bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN);
bytecode_add_instruction(bc, OP_LOAD_CONST, cf5); bytecode_add_instruction(bc, OP_LOAD_CONST, cf5);
bytecode_add_instruction(bc, OP_FMAX, 0); bytecode_add_instruction(bc, OP_FMAX, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// fmin(5.0, NaN) -> 5.0 // fmin(5.0, NaN) -> 5.0
bytecode_add_instruction(bc, OP_LOAD_CONST, cf5); bytecode_add_instruction(bc, OP_LOAD_CONST, cf5);
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_FMIN, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// fmax(5.0, NaN) -> 5.0 // fmax(5.0, NaN) -> 5.0
bytecode_add_instruction(bc, OP_LOAD_CONST, cf5); bytecode_add_instruction(bc, OP_LOAD_CONST, cf5);
bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN); bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN);
bytecode_add_instruction(bc, OP_FMAX, 0); bytecode_add_instruction(bc, OP_FMAX, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// fmin(NaN, NaN) -> NaN (prints as nan) // 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_LOAD_CONST, cNaN); bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN);
bytecode_add_instruction(bc, OP_FMIN, 0); bytecode_add_instruction(bc, OP_FMIN, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
// ---------- HALT ---------- // ---------- HALT ----------
bytecode_add_instruction(bc, OP_HALT, 0); bytecode_add_instruction(bc, OP_HALT, 0);
printf("=== Bytecode dump ===\n"); printf("=== Bytecode dump ===\n");
for (int i = 0; i < bc->instr_count; ++i) { for (int i = 0; i < bc->instr_count; ++i) {
Instruction instr = bc->instructions[i]; Instruction instr = bc->instructions[i];
printf("instr %3d: opcode=%2d operand=%d\n", i, instr.op, instr.operand); printf("instr %3d: opcode=%2d operand=%d\n", i, instr.op, instr.operand);
} }
printf("=====================\n"); printf("=====================\n");
// run VM // run VM
vm_run(&vm, bc); 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); vm_clear_output(&vm);
bytecode_free(bc); bytecode_free(bc);
return 0; return 0;
} }

View file

@ -12,49 +12,51 @@
/* enumerate(arr) -> [[0, v0], [1, v1], ...] */ /* enumerate(arr) -> [[0, v0], [1, v1], ...] */
Value bi_enumerate(const Value *arr) { Value bi_enumerate(const Value *arr) {
int n = array_length(arr); int n = array_length(arr);
if (n <= 0) return make_array_from_values(NULL, 0); if (n <= 0) return make_array_from_values(NULL, 0);
Value *pairs = (Value*)malloc(sizeof(Value) * n); Value *pairs = (Value *)malloc(sizeof(Value) * n);
if (!pairs) return make_array_from_values(NULL, 0); if (!pairs) return make_array_from_values(NULL, 0);
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
Value elem; Value elem;
array_get_copy(arr, i, &elem); array_get_copy(arr, i, &elem);
Value kv_vals[2]; Value kv_vals[2];
kv_vals[0] = make_int(i); kv_vals[0] = make_int(i);
kv_vals[1] = elem; kv_vals[1] = elem;
Value kv = make_array_from_values(kv_vals, 2); Value kv = make_array_from_values(kv_vals, 2);
free_value(kv_vals[0]); free_value(kv_vals[0]);
free_value(kv_vals[1]); free_value(kv_vals[1]);
pairs[i] = kv; pairs[i] = kv;
} }
Value out = make_array_from_values(pairs, n); Value out = make_array_from_values(pairs, n);
for (int i = 0; i < n; ++i) free_value(pairs[i]); for (int i = 0; i < n; ++i)
free(pairs); free_value(pairs[i]);
return out; free(pairs);
return out;
} }
/* zip(a, b) -> [[a0,b0], [a1,b1], ...] up to min(len(a),len(b)) */ /* zip(a, b) -> [[a0,b0], [a1,b1], ...] up to min(len(a),len(b)) */
Value bi_zip(const Value *a, const Value *b) { Value bi_zip(const Value *a, const Value *b) {
int na = array_length(a); int na = array_length(a);
int nb = array_length(b); int nb = array_length(b);
int n = na < nb ? na : nb; int n = na < nb ? na : nb;
if (n <= 0) return make_array_from_values(NULL, 0); if (n <= 0) return make_array_from_values(NULL, 0);
Value *pairs = (Value*)malloc(sizeof(Value) * n); Value *pairs = (Value *)malloc(sizeof(Value) * n);
if (!pairs) return make_array_from_values(NULL, 0); if (!pairs) return make_array_from_values(NULL, 0);
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
Value av, bv; Value av, bv;
array_get_copy(a, i, &av); array_get_copy(a, i, &av);
array_get_copy(b, i, &bv); array_get_copy(b, i, &bv);
Value kv_vals[2]; Value kv_vals[2];
kv_vals[0] = av; kv_vals[0] = av;
kv_vals[1] = bv; kv_vals[1] = bv;
Value kv = make_array_from_values(kv_vals, 2); Value kv = make_array_from_values(kv_vals, 2);
free_value(kv_vals[0]); free_value(kv_vals[0]);
free_value(kv_vals[1]); free_value(kv_vals[1]);
pairs[i] = kv; pairs[i] = kv;
} }
Value out = make_array_from_values(pairs, n); Value out = make_array_from_values(pairs, n);
for (int i = 0; i < n; ++i) free_value(pairs[i]); for (int i = 0; i < n; ++i)
free(pairs); free_value(pairs[i]);
return out; free(pairs);
return out;
} }

163
src/map.c
View file

@ -13,104 +13,113 @@
/* Internal Map definition; Value holds struct Map* */ /* Internal Map definition; Value holds struct Map* */
typedef struct Map { typedef struct Map {
int refcount; int refcount;
int count; int count;
int cap; int cap;
char **keys; /* each key owned here */ char **keys; /* each key owned here */
Value *vals; /* each value owned here */ Value *vals; /* each value owned here */
} Map; } Map;
Value make_map_empty(void) { Value make_map_empty(void) {
Map *m = (Map*)malloc(sizeof(Map)); Map *m = (Map *)malloc(sizeof(Map));
if (!m) return make_nil(); if (!m) return make_nil();
m->refcount = 1; m->refcount = 1;
m->count = 0; m->count = 0;
m->cap = 0; m->cap = 0;
m->keys = NULL; m->keys = NULL;
m->vals = NULL; m->vals = NULL;
Value v; Value v;
v.type = VAL_MAP; v.type = VAL_MAP;
v.map = (struct Map*)m; v.map = (struct Map *)m;
return v; return v;
} }
static int map_ensure_cap(Map *m, int need) { static int map_ensure_cap(Map *m, int need) {
if (m->cap >= need) return 1; if (m->cap >= need) return 1;
int ncap = m->cap == 0 ? 4 : m->cap * 2; int ncap = m->cap == 0 ? 4 : m->cap * 2;
while (ncap < need) ncap *= 2; while (ncap < need)
char **nkeys = (char**)realloc(m->keys, sizeof(char*) * ncap); ncap *= 2;
Value *nvals = (Value*)realloc(m->vals, sizeof(Value) * ncap); char **nkeys = (char **)realloc(m->keys, sizeof(char *) * ncap);
if (!nkeys || !nvals) return 0; Value *nvals = (Value *)realloc(m->vals, sizeof(Value) * ncap);
m->keys = nkeys; if (!nkeys || !nvals) return 0;
m->vals = nvals; m->keys = nkeys;
m->cap = ncap; m->vals = nvals;
return 1; m->cap = ncap;
return 1;
} }
int map_set(Value *vm, const char *key, Value v) { int map_set(Value *vm, const char *key, Value v) {
if (!vm || vm->type != VAL_MAP || !vm->map || !key) { free_value(v); return 0; } if (!vm || vm->type != VAL_MAP || !vm->map || !key) {
Map *m = (Map*)vm->map; free_value(v);
for (int i = 0; i < m->count; ++i) { return 0;
if (strcmp(m->keys[i], key) == 0) { }
free_value(m->vals[i]); Map *m = (Map *)vm->map;
m->vals[i] = v; for (int i = 0; i < m->count; ++i) {
return 1; 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); if (!map_ensure_cap(m, m->count + 1)) {
m->vals[m->count] = v; free_value(v);
m->count++; return 0;
return 1; }
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) { int map_get_copy(const Value *vm, const char *key, Value *out) {
if (!vm || vm->type != VAL_MAP || !vm->map || !key) return 0; if (!vm || vm->type != VAL_MAP || !vm->map || !key) return 0;
Map *m = (Map*)vm->map; Map *m = (Map *)vm->map;
for (int i = 0; i < m->count; ++i) { for (int i = 0; i < m->count; ++i) {
if (strcmp(m->keys[i], key) == 0) { if (strcmp(m->keys[i], key) == 0) {
if (out) *out = copy_value(&m->vals[i]); if (out) *out = copy_value(&m->vals[i]);
return 1; return 1;
}
} }
return 0; }
return 0;
} }
int map_has(const Value *vm, const char *key) { int map_has(const Value *vm, const char *key) {
if (!vm || vm->type != VAL_MAP || !vm->map || !key) return 0; if (!vm || vm->type != VAL_MAP || !vm->map || !key) return 0;
Map *m = (Map*)vm->map; Map *m = (Map *)vm->map;
for (int i = 0; i < m->count; ++i) { for (int i = 0; i < m->count; ++i) {
if (strcmp(m->keys[i], key) == 0) return 1; if (strcmp(m->keys[i], key) == 0) return 1;
} }
return 0; return 0;
} }
Value map_keys_array(const Value *vm) { Value map_keys_array(const Value *vm) {
if (!vm || vm->type != VAL_MAP || !vm->map) return make_array_from_values(NULL, 0); if (!vm || vm->type != VAL_MAP || !vm->map) return make_array_from_values(NULL, 0);
Map *m = (Map*)vm->map; Map *m = (Map *)vm->map;
if (m->count <= 0) return make_array_from_values(NULL, 0); if (m->count <= 0) return make_array_from_values(NULL, 0);
Value *tmp = (Value*)malloc(sizeof(Value) * m->count); Value *tmp = (Value *)malloc(sizeof(Value) * m->count);
if (!tmp) return make_array_from_values(NULL, 0); if (!tmp) return make_array_from_values(NULL, 0);
for (int i = 0; i < m->count; ++i) { for (int i = 0; i < m->count; ++i) {
tmp[i] = make_string(m->keys[i]); tmp[i] = make_string(m->keys[i]);
} }
Value arr = make_array_from_values(tmp, m->count); Value arr = make_array_from_values(tmp, m->count);
for (int i = 0; i < m->count; ++i) free_value(tmp[i]); for (int i = 0; i < m->count; ++i)
free(tmp); free_value(tmp[i]);
return arr; free(tmp);
return arr;
} }
Value map_values_array(const Value *vm) { Value map_values_array(const Value *vm) {
if (!vm || vm->type != VAL_MAP || !vm->map) return make_array_from_values(NULL, 0); if (!vm || vm->type != VAL_MAP || !vm->map) return make_array_from_values(NULL, 0);
Map *m = (Map*)vm->map; Map *m = (Map *)vm->map;
if (m->count <= 0) return make_array_from_values(NULL, 0); if (m->count <= 0) return make_array_from_values(NULL, 0);
Value *tmp = (Value*)malloc(sizeof(Value) * m->count); Value *tmp = (Value *)malloc(sizeof(Value) * m->count);
if (!tmp) return make_array_from_values(NULL, 0); if (!tmp) return make_array_from_values(NULL, 0);
for (int i = 0; i < m->count; ++i) { for (int i = 0; i < m->count; ++i) {
tmp[i] = copy_value(&m->vals[i]); tmp[i] = copy_value(&m->vals[i]);
} }
Value arr = make_array_from_values(tmp, m->count); Value arr = make_array_from_values(tmp, m->count);
for (int i = 0; i < m->count; ++i) free_value(tmp[i]); for (int i = 0; i < m->count; ++i)
free(tmp); free_value(tmp[i]);
return arr; free(tmp);
return arr;
} }

11547
src/parser.c

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

3480
src/repl.c

File diff suppressed because it is too large Load diff

View file

@ -14,121 +14,126 @@
/* string helpers returning newly allocated C strings or arrays */ /* string helpers returning newly allocated C strings or arrays */
char *string_substr(const char *s, int start, int len) { char *string_substr(const char *s, int start, int len) {
if (!s) return strdup(""); if (!s) return strdup("");
int n = (int)strlen(s); int n = (int)strlen(s);
if (start < 0) start = 0; if (start < 0) start = 0;
if (start > n) start = n; if (start > n) start = n;
if (len < 0) len = 0; if (len < 0) len = 0;
if (start + len > n) len = n - start; if (start + len > n) len = n - start;
char *out = (char*)malloc((size_t)len + 1); char *out = (char *)malloc((size_t)len + 1);
if (!out) return strdup(""); if (!out) return strdup("");
memcpy(out, s + start, (size_t)len); memcpy(out, s + start, (size_t)len);
out[len] = '\0'; out[len] = '\0';
return out; return out;
} }
int string_find(const char *hay, const char *needle) { int string_find(const char *hay, const char *needle) {
if (!hay || !needle) return -1; if (!hay || !needle) return -1;
const char *p = strstr(hay, needle); const char *p = strstr(hay, needle);
if (!p) return -1; if (!p) return -1;
return (int)(p - hay); return (int)(p - hay);
} }
Value string_split_to_array(const char *s, const char *sep) { Value string_split_to_array(const char *s, const char *sep) {
if (!s) s = ""; if (!s) s = "";
if (!sep) sep = ""; if (!sep) sep = "";
int seplen = (int)strlen(sep); int seplen = (int)strlen(sep);
if (seplen == 0) { if (seplen == 0) {
/* split into characters */ /* split into characters */
int n = (int)strlen(s); int n = (int)strlen(s);
if (n <= 0) return make_array_from_values(NULL, 0); if (n <= 0) return make_array_from_values(NULL, 0);
Value *tmp = (Value*)malloc(sizeof(Value) * n); Value *tmp = (Value *)malloc(sizeof(Value) * n);
if (!tmp) return make_array_from_values(NULL, 0); if (!tmp) return make_array_from_values(NULL, 0);
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
char ch[2] = { s[i], 0 }; char ch[2] = {s[i], 0};
tmp[i] = make_string(ch); 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;
} }
/* split by separator */ Value arr = make_array_from_values(tmp, n);
Value *parts = NULL; for (int i = 0; i < n; ++i)
int count = 0; free_value(tmp[i]);
int cap = 0; free(tmp);
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; 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) { char *array_join_with_sep(const Value *v, const char *sep) {
if (!v || v->type != VAL_ARRAY || !v->arr) return strdup(""); if (!v || v->type != VAL_ARRAY || !v->arr) return strdup("");
if (!sep) sep = ""; if (!sep) sep = "";
/* Array is defined in value.c; we only need safe public access. */ /* Array is defined in value.c; we only need safe public access. */
const int n = array_length(v); const int n = array_length(v);
if (n <= 0) return strdup(""); if (n <= 0) return strdup("");
char **parts = (char**)malloc(sizeof(char*) * n); char **parts = (char **)malloc(sizeof(char *) * n);
if (!parts) return strdup(""); if (!parts) return strdup("");
size_t total = 0; size_t total = 0;
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
Value item; Value item;
if (!array_get_copy(v, i, &item)) { if (!array_get_copy(v, i, &item)) {
parts[i] = strdup(""); parts[i] = strdup("");
} else { } else {
parts[i] = value_to_string_alloc(&item); parts[i] = value_to_string_alloc(&item);
free_value(item); free_value(item);
}
total += strlen(parts[i]);
if (i + 1 < n) total += strlen(sep);
} }
total += strlen(parts[i]);
if (i + 1 < n) total += strlen(sep);
}
char *out = (char*)malloc(total + 1); char *out = (char *)malloc(total + 1);
if (!out) { if (!out) {
for (int i = 0; i < n; ++i) free(parts[i]); for (int i = 0; i < n; ++i)
free(parts); free(parts[i]);
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); free(parts);
out[off] = '\0'; return strdup("");
return out; }
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;
} }

View file

@ -13,29 +13,29 @@
/* String built-ins wrappers used by VM opcodes */ /* String built-ins wrappers used by VM opcodes */
Value bi_split(const Value *str, const Value *sep) { Value bi_split(const Value *str, const Value *sep) {
const char *s = (str && str->type == VAL_STRING && str->s) ? str->s : ""; const char *s = (str && str->type == VAL_STRING && str->s) ? str->s : "";
const char *p = (sep && sep->type == VAL_STRING && sep->s) ? sep->s : ""; const char *p = (sep && sep->type == VAL_STRING && sep->s) ? sep->s : "";
return string_split_to_array(s, p); return string_split_to_array(s, p);
} }
Value bi_join(const Value *arr, const Value *sep) { Value bi_join(const Value *arr, const Value *sep) {
const char *p = (sep && sep->type == VAL_STRING && sep->s) ? sep->s : ""; const char *p = (sep && sep->type == VAL_STRING && sep->s) ? sep->s : "";
char *s = array_join_with_sep(arr, p); char *s = array_join_with_sep(arr, p);
Value out = make_string(s ? s : ""); Value out = make_string(s ? s : "");
if (s) free(s); if (s) free(s);
return out; return out;
} }
Value bi_substr(const Value *str, int start, int len) { Value bi_substr(const Value *str, int start, int len) {
const char *s = (str && str->type == VAL_STRING && str->s) ? str->s : ""; const char *s = (str && str->type == VAL_STRING && str->s) ? str->s : "";
char *sub = string_substr(s, start, len); char *sub = string_substr(s, start, len);
Value out = make_string(sub ? sub : ""); Value out = make_string(sub ? sub : "");
if (sub) free(sub); if (sub) free(sub);
return out; return out;
} }
int bi_find(const Value *hay, const Value *needle) { int bi_find(const Value *hay, const Value *needle) {
const char *h = (hay && hay->type == VAL_STRING && hay->s) ? hay->s : ""; const char *h = (hay && hay->type == VAL_STRING && hay->s) ? hay->s : "";
const char *n = (needle && needle->type == VAL_STRING && needle->s) ? needle->s : ""; const char *n = (needle && needle->type == VAL_STRING && needle->s) ? needle->s : "";
return string_find(h, n); return string_find(h, n);
} }

View file

@ -7,69 +7,69 @@
* https://opensource.org/license/apache-2-0 * https://opensource.org/license/apache-2-0
*/ */
#include "vm.h"
#include "bytecode.h" #include "bytecode.h"
#include "value.h" #include "value.h"
#include "vm.h"
#include <stdio.h> #include <stdio.h>
int main() { int main() {
VM vm; VM vm;
vm_init(&vm); vm_init(&vm);
Bytecode *bc = bytecode_new(); Bytecode *bc = bytecode_new();
// Example: test OP_ADD // Example: test OP_ADD
int c1 = bytecode_add_constant(bc, make_int(5)); int c1 = bytecode_add_constant(bc, make_int(5));
int c2 = bytecode_add_constant(bc, make_int(3)); int c2 = bytecode_add_constant(bc, make_int(3));
bytecode_add_instruction(bc, OP_LOAD_CONST, c1); bytecode_add_instruction(bc, OP_LOAD_CONST, c1);
bytecode_add_instruction(bc, OP_LOAD_CONST, c2); bytecode_add_instruction(bc, OP_LOAD_CONST, c2);
bytecode_add_instruction(bc, OP_ADD, 0); bytecode_add_instruction(bc, OP_ADD, 0);
bytecode_add_instruction(bc, OP_PRINT, 0); bytecode_add_instruction(bc, OP_PRINT, 0);
printf("=== Bytecode dump ===\n"); printf("=== Bytecode dump ===\n");
for (int i = 0; i < bc->instr_count; ++i) { for (int i = 0; i < bc->instr_count; ++i) {
Instruction instr = bc->instructions[i]; Instruction instr = bc->instructions[i];
printf("instr %3d: opcode=%2d operand=%d\n", i, instr.op, instr.operand); printf("instr %3d: opcode=%2d operand=%d\n", i, instr.op, instr.operand);
} }
printf("=====================\n"); printf("=====================\n");
bytecode_dump(bc); bytecode_dump(bc);
printf("=====================\n"); printf("=====================\n");
vm_run(&vm, bc); vm_run(&vm, bc);
printf("Output count: %d\n", vm.output_count); printf("Output count: %d\n", vm.output_count);
for (int i = 0; i < vm.output_count; i++) { for (int i = 0; i < vm.output_count; i++) {
printf("Output[%d] = ", i); printf("Output[%d] = ", i);
print_value(&vm.output[i]); print_value(&vm.output[i]);
printf("\n"); printf("\n");
} }
vm_clear_output(&vm); vm_clear_output(&vm);
/* --- Rust FFI demo: call a Rust opcode and string function --- */ /* --- Rust FFI demo: call a Rust opcode and string function --- */
#ifdef FUN_WITH_RUST #ifdef FUN_WITH_RUST
extern int fun_op_radd(VM *vm); extern int fun_op_radd(VM * vm);
extern const char *fun_rust_get_string(void); extern const char *fun_rust_get_string(void);
printf("=== Rust FFI demo ===\n"); printf("=== Rust FFI demo ===\n");
const char *rs = fun_rust_get_string(); const char *rs = fun_rust_get_string();
if (rs) { if (rs) {
printf("Rust says: %s\n", rs); printf("Rust says: %s\n", rs);
} }
/* prepare stack: push 10 and 32, then call Rust add -> expect 42 */ /* prepare stack: push 10 and 32, then call Rust add -> expect 42 */
vm_push_i64(&vm, 10); vm_push_i64(&vm, 10);
vm_push_i64(&vm, 32); vm_push_i64(&vm, 32);
int rc = fun_op_radd(&vm); int rc = fun_op_radd(&vm);
printf("fun_op_radd rc=%d\n", rc); printf("fun_op_radd rc=%d\n", rc);
long long sum = (long long)vm_pop_i64(&vm); long long sum = (long long)vm_pop_i64(&vm);
printf("Rust op result: %lld\n", sum); printf("Rust op result: %lld\n", sum);
#else #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 #endif
vm_free(&vm); vm_free(&vm);
bytecode_free(bc); bytecode_free(bc);
return 0; return 0;
} }

View file

@ -8,470 +8,487 @@
*/ */
#include "value.h" #include "value.h"
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdio.h>
/* Compile helper implementations into this TU to avoid build system changes */ /* Compile helper implementations into this TU to avoid build system changes */
#include "str_utils.c"
#include "array_utils.c" #include "array_utils.c"
#include "str_utils.c"
typedef struct Array { typedef struct Array {
int refcount; int refcount;
int count; int count;
Value *items; /* owns items; each item owned by array */ Value *items; /* owns items; each item owned by array */
} Array; } Array;
typedef struct Map { typedef struct Map {
int refcount; int refcount;
int count; int count;
int cap; int cap;
char **keys; /* each key owned here */ char **keys; /* each key owned here */
Value *vals; /* each value owned here */ Value *vals; /* each value owned here */
} Map; } Map;
Value make_int(int64_t v) { Value make_int(int64_t v) {
Value val; Value val;
val.type = VAL_INT; val.type = VAL_INT;
val.i = v; val.i = v;
return val; return val;
} }
Value make_float(double v) { Value make_float(double v) {
Value val; Value val;
val.type = VAL_FLOAT; val.type = VAL_FLOAT;
val.d = v; val.d = v;
return val; return val;
} }
Value make_bool(int v) { Value make_bool(int v) {
Value val; Value val;
val.type = VAL_BOOL; val.type = VAL_BOOL;
val.i = v ? 1 : 0; val.i = v ? 1 : 0;
return val; return val;
} }
Value make_string(const char *s) { Value make_string(const char *s) {
Value val; Value val;
val.type = VAL_STRING; val.type = VAL_STRING;
if (s) val.s = strdup(s); if (s)
else val.s = strdup(""); val.s = strdup(s);
return val; else
val.s = strdup("");
return val;
} }
Value make_function(struct Bytecode *fn) { Value make_function(struct Bytecode *fn) {
Value val; Value val;
val.type = VAL_FUNCTION; val.type = VAL_FUNCTION;
val.fn = fn; val.fn = fn;
return val; return val;
} }
Value make_nil(void) { Value make_nil(void) {
Value v; Value v;
v.type = VAL_NIL; v.type = VAL_NIL;
return v; return v;
} }
Value make_array_from_values(const Value *vals, int count) { Value make_array_from_values(const Value *vals, int count) {
if (count < 0) count = 0; if (count < 0) count = 0;
Array *arr = (Array*)malloc(sizeof(Array)); Array *arr = (Array *)malloc(sizeof(Array));
if (!arr) { if (!arr) {
Value nil = make_nil(); Value nil = make_nil();
return 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; for (int i = 0; i < count; ++i) {
arr->count = count; arr->items[i] = copy_value(&vals[i]);
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;
} }
Value v; } else {
v.type = VAL_ARRAY; arr->items = NULL;
v.arr = (struct Array*)arr; }
return v; Value v;
v.type = VAL_ARRAY;
v.arr = (struct Array *)arr;
return v;
} }
int array_length(const Value *v) { int array_length(const Value *v) {
if (!v || v->type != VAL_ARRAY || !v->arr) return -1; if (!v || v->type != VAL_ARRAY || !v->arr) return -1;
const Array *a = (const Array*)v->arr; const Array *a = (const Array *)v->arr;
return a->count; return a->count;
} }
int array_get_copy(const Value *v, int index, Value *out) { int array_get_copy(const Value *v, int index, Value *out) {
if (!v || v->type != VAL_ARRAY || !v->arr) return 0; if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
const Array *a = (const Array*)v->arr; const Array *a = (const Array *)v->arr;
if (index < 0 || index >= a->count) return 0; if (index < 0 || index >= a->count) return 0;
if (out) *out = copy_value(&a->items[index]); if (out) *out = copy_value(&a->items[index]);
return 1; return 1;
} }
int array_set(Value *v, int index, Value newElem) { int array_set(Value *v, int index, Value newElem) {
if (!v || v->type != VAL_ARRAY || !v->arr) return 0; if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
Array *a = (Array*)v->arr; Array *a = (Array *)v->arr;
if (index < 0 || index >= a->count) return 0; if (index < 0 || index >= a->count) return 0;
free_value(a->items[index]); free_value(a->items[index]);
a->items[index] = newElem; /* take ownership */ a->items[index] = newElem; /* take ownership */
return 1; return 1;
} }
static int ensure_array_capacity(Array *a, int newCount) { static int ensure_array_capacity(Array *a, int newCount) {
if (newCount <= a->count) return 1; if (newCount <= a->count) return 1;
/* grow to at least newCount; double strategy */ /* grow to at least newCount; double strategy */
int curr = a->count; int curr = a->count;
int cap = curr; int cap = curr;
if (cap < 4) cap = 4; if (cap < 4) cap = 4;
while (cap < newCount) cap *= 2; while (cap < newCount)
Value *newItems = (Value*)realloc(a->items, sizeof(Value) * cap); cap *= 2;
if (!newItems) return 0; Value *newItems = (Value *)realloc(a->items, sizeof(Value) * cap);
/* if growing beyond current count, initialize new slots to nil */ if (!newItems) return 0;
if (cap > a->count) { /* if growing beyond current count, initialize new slots to nil */
for (int i = a->count; i < cap; ++i) { if (cap > a->count) {
newItems[i] = make_nil(); 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) { int array_push(Value *v, Value newElem) {
if (!v || v->type != VAL_ARRAY || !v->arr) return -1; if (!v || v->type != VAL_ARRAY || !v->arr) return -1;
Array *a = (Array*)v->arr; Array *a = (Array *)v->arr;
/* ensure capacity for count+1 by reallocating items array to at least count+1 elements */ /* 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)); Value *newItems = (Value *)realloc(a->items, sizeof(Value) * (a->count + 1));
if (!newItems) { free_value(newElem); return -1; } if (!newItems) {
a->items = newItems; free_value(newElem);
a->items[a->count] = newElem; /* take ownership */ return -1;
a->count += 1; }
return a->count; a->items = newItems;
a->items[a->count] = newElem; /* take ownership */
a->count += 1;
return a->count;
} }
int array_pop(Value *v, Value *out) { int array_pop(Value *v, Value *out) {
if (!v || v->type != VAL_ARRAY || !v->arr) return 0; if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
Array *a = (Array*)v->arr; Array *a = (Array *)v->arr;
if (a->count <= 0) return 0; if (a->count <= 0) return 0;
int idx = a->count - 1; int idx = a->count - 1;
if (out) *out = a->items[idx]; /* transfer ownership */ if (out)
else free_value(a->items[idx]); *out = a->items[idx]; /* transfer ownership */
a->count -= 1; else
return 1; free_value(a->items[idx]);
a->count -= 1;
return 1;
} }
int array_insert(Value *v, int index, Value newElem) { int array_insert(Value *v, int index, Value newElem) {
if (!v || v->type != VAL_ARRAY || !v->arr) return -1; if (!v || v->type != VAL_ARRAY || !v->arr) return -1;
Array *a = (Array*)v->arr; Array *a = (Array *)v->arr;
if (index < 0) index = 0; if (index < 0) index = 0;
if (index > a->count) index = a->count; if (index > a->count) index = a->count;
Value *newItems = (Value*)realloc(a->items, sizeof(Value) * (a->count + 1)); Value *newItems = (Value *)realloc(a->items, sizeof(Value) * (a->count + 1));
if (!newItems) { free_value(newElem); return -1; } if (!newItems) {
a->items = newItems; free_value(newElem);
/* shift right */ return -1;
for (int i = a->count; i > index; --i) { }
a->items[i] = a->items[i - 1]; a->items = newItems;
} /* shift right */
a->items[index] = newElem; /* take ownership */ for (int i = a->count; i > index; --i) {
a->count += 1; a->items[i] = a->items[i - 1];
return a->count; }
a->items[index] = newElem; /* take ownership */
a->count += 1;
return a->count;
} }
int array_remove(Value *v, int index, Value *out) { int array_remove(Value *v, int index, Value *out) {
if (!v || v->type != VAL_ARRAY || !v->arr) return 0; if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
Array *a = (Array*)v->arr; Array *a = (Array *)v->arr;
if (index < 0 || index >= a->count) return 0; if (index < 0 || index >= a->count) return 0;
if (out) *out = a->items[index]; /* transfer ownership */ if (out)
else free_value(a->items[index]); *out = a->items[index]; /* transfer ownership */
/* shift left */ else
for (int i = index; i < a->count - 1; ++i) { free_value(a->items[index]);
a->items[i] = a->items[i + 1]; /* shift left */
} for (int i = index; i < a->count - 1; ++i) {
a->count -= 1; a->items[i] = a->items[i + 1];
return 1; }
a->count -= 1;
return 1;
} }
Value array_slice(const Value *v, int start, int end) { Value array_slice(const Value *v, int start, int end) {
if (!v || v->type != VAL_ARRAY || !v->arr) return make_nil(); if (!v || v->type != VAL_ARRAY || !v->arr) return make_nil();
const Array *a = (const Array*)v->arr; const Array *a = (const Array *)v->arr;
int n = a->count; int n = a->count;
if (start < 0) start = 0; if (start < 0) start = 0;
if (end < 0 || end > n) end = n; if (end < 0 || end > n) end = n;
if (start > end) start = end; if (start > end) start = end;
int m = end - start; int m = end - start;
if (m <= 0) { if (m <= 0) {
return make_array_from_values(NULL, 0); return make_array_from_values(NULL, 0);
} }
return make_array_from_values(a->items + start, m); return make_array_from_values(a->items + start, m);
} }
Value array_concat(const Value *av, const Value *bv) { Value array_concat(const Value *av, const Value *bv) {
if (!av || !bv || av->type != VAL_ARRAY || bv->type != VAL_ARRAY) return make_nil(); if (!av || !bv || av->type != VAL_ARRAY || bv->type != VAL_ARRAY) return make_nil();
const Array *a = (const Array*)av->arr; const Array *a = (const Array *)av->arr;
const Array *b = (const Array*)bv->arr; const Array *b = (const Array *)bv->arr;
int na = a ? a->count : 0; int na = a ? a->count : 0;
int nb = b ? b->count : 0; int nb = b ? b->count : 0;
int total = na + nb; int total = na + nb;
if (total <= 0) return make_array_from_values(NULL, 0); if (total <= 0) return make_array_from_values(NULL, 0);
Value *tmp = (Value*)malloc(sizeof(Value) * total); Value *tmp = (Value *)malloc(sizeof(Value) * total);
if (!tmp) return make_nil(); if (!tmp) return make_nil();
for (int i = 0; i < na; ++i) tmp[i] = a->items[i]; for (int i = 0; i < na; ++i)
for (int j = 0; j < nb; ++j) tmp[na + j] = b->items[j]; tmp[i] = a->items[i];
Value out = make_array_from_values(tmp, total); for (int j = 0; j < nb; ++j)
/* free temporaries we copied from (deep copy in make_array_from_values) */ tmp[na + j] = b->items[j];
free(tmp); Value out = make_array_from_values(tmp, total);
return out; /* free temporaries we copied from (deep copy in make_array_from_values) */
free(tmp);
return out;
} }
Value copy_value(const Value *v) { Value copy_value(const Value *v) {
Value out; Value out;
out.type = v->type; out.type = v->type;
switch (v->type) { switch (v->type) {
case VAL_INT: case VAL_INT:
out.i = v->i; out.i = v->i;
break; break;
case VAL_FLOAT: case VAL_FLOAT:
out.d = v->d; out.d = v->d;
break; break;
case VAL_BOOL: case VAL_BOOL:
out.i = v->i ? 1 : 0; out.i = v->i ? 1 : 0;
break; break;
case VAL_STRING: case VAL_STRING:
out.s = v->s ? strdup(v->s) : strdup(""); out.s = v->s ? strdup(v->s) : strdup("");
break; break;
case VAL_FUNCTION: case VAL_FUNCTION:
out.fn = v->fn; /* shallow copy pointer */ out.fn = v->fn; /* shallow copy pointer */
break; break;
case VAL_ARRAY: { case VAL_ARRAY: {
Array *a = (Array*)v->arr; Array *a = (Array *)v->arr;
out.arr = (struct Array*)a; out.arr = (struct Array *)a;
if (a) a->refcount++; if (a) a->refcount++;
break; break;
} }
case VAL_MAP: { case VAL_MAP: {
Map *m = (Map*)v->map; Map *m = (Map *)v->map;
out.map = (struct Map*)m; out.map = (struct Map *)m;
if (m) m->refcount++; if (m) m->refcount++;
break; break;
} }
case VAL_NIL: case VAL_NIL:
default: default:
break; break;
} }
return out; return out;
} }
/* deep copy including arrays (recursively copies items) */ /* deep copy including arrays (recursively copies items) */
Value deep_copy_value(const Value *v) { Value deep_copy_value(const Value *v) {
switch (v->type) { switch (v->type) {
case VAL_INT: case VAL_INT:
return make_int(v->i); return make_int(v->i);
case VAL_FLOAT: case VAL_FLOAT:
return make_float(v->d); return make_float(v->d);
case VAL_BOOL: case VAL_BOOL:
return make_bool(v->i); return make_bool(v->i);
case VAL_STRING: case VAL_STRING:
return make_string(v->s ? v->s : ""); return make_string(v->s ? v->s : "");
case VAL_FUNCTION: case VAL_FUNCTION:
return make_function(v->fn); /* shallow pointer for function bytecode */ return make_function(v->fn); /* shallow pointer for function bytecode */
case VAL_ARRAY: { case VAL_ARRAY: {
const Array *a = (const Array*)v->arr; const Array *a = (const Array *)v->arr;
if (!a || a->count <= 0) { if (!a || a->count <= 0) {
return make_array_from_values(NULL, 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();
} }
/* 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) { void free_value(Value v) {
if (v.type == VAL_STRING && v.s) { if (v.type == VAL_STRING && v.s) {
free(v.s); free(v.s);
} else if (v.type == VAL_ARRAY && v.arr) { } else if (v.type == VAL_ARRAY && v.arr) {
Array *a = (Array*)v.arr; Array *a = (Array *)v.arr;
if (--a->refcount == 0) { if (--a->refcount == 0) {
for (int i = 0; i < a->count; ++i) { for (int i = 0; i < a->count; ++i) {
free_value(a->items[i]); free_value(a->items[i]);
} }
free(a->items); free(a->items);
free(a); 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);
}
} }
/* 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) { void print_value(const Value *v) {
switch (v->type) { switch (v->type) {
case VAL_INT: case VAL_INT:
printf("%" PRId64, v->i); printf("%" PRId64, v->i);
break; break;
case VAL_FLOAT: case VAL_FLOAT:
printf("%.17g", v->d); printf("%.17g", v->d);
break; break;
case VAL_STRING: case VAL_STRING:
printf("%s", v->s ? v->s : ""); printf("%s", v->s ? v->s : "");
break; break;
case VAL_BOOL: case VAL_BOOL:
printf("%s", v->i ? "true" : "false"); printf("%s", v->i ? "true" : "false");
break; break;
case VAL_FUNCTION: case VAL_FUNCTION:
printf("<function@%p>", (void*)v->fn); printf("<function@%p>", (void *)v->fn);
break; break;
case VAL_ARRAY: { case VAL_ARRAY: {
const Array *a = (const Array*)v->arr; const Array *a = (const Array *)v->arr;
printf("["); printf("[");
if (a) { if (a) {
for (int i = 0; i < a->count; ++i) { for (int i = 0; i < a->count; ++i) {
if (i > 0) printf(", "); if (i > 0) printf(", ");
print_value(&a->items[i]); 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;
} }
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) { int value_is_truthy(const Value *v) {
switch (v->type) { switch (v->type) {
case VAL_INT: case VAL_INT:
return v->i != 0; return v->i != 0;
case VAL_FLOAT: case VAL_FLOAT:
return v->d != 0.0; return v->d != 0.0;
case VAL_BOOL: case VAL_BOOL:
return v->i != 0; return v->i != 0;
case VAL_STRING: case VAL_STRING:
return v->s && v->s[0] != '\0'; return v->s && v->s[0] != '\0';
case VAL_FUNCTION: case VAL_FUNCTION:
return 1; return 1;
case VAL_ARRAY: { case VAL_ARRAY: {
const Array *a = (const Array*)v->arr; const Array *a = (const Array *)v->arr;
return a && a->count > 0; return a && a->count > 0;
} }
case VAL_NIL: case VAL_NIL:
default: default:
return 0; return 0;
} }
} }
/* allocate a printable C string for the value; caller must free */ /* allocate a printable C string for the value; caller must free */
char *value_to_string_alloc(const Value *v) { char *value_to_string_alloc(const Value *v) {
if (!v) return strdup("nil"); if (!v) return strdup("nil");
char buf[128]; char buf[128];
switch (v->type) { switch (v->type) {
case VAL_INT: { case VAL_INT: {
char tmp[64]; char tmp[64];
snprintf(tmp, sizeof(tmp), "%" PRId64, v->i); snprintf(tmp, sizeof(tmp), "%" PRId64, v->i);
return strdup(tmp); return strdup(tmp);
} }
case VAL_FLOAT: { case VAL_FLOAT: {
char tmp[64]; char tmp[64];
snprintf(tmp, sizeof(tmp), "%.17g", v->d); snprintf(tmp, sizeof(tmp), "%.17g", v->d);
return strdup(tmp); return strdup(tmp);
} }
case VAL_STRING: case VAL_STRING:
return strdup(v->s ? v->s : ""); return strdup(v->s ? v->s : "");
case VAL_BOOL: case VAL_BOOL:
return strdup(v->i ? "true" : "false"); return strdup(v->i ? "true" : "false");
case VAL_FUNCTION: { case VAL_FUNCTION: {
snprintf(buf, sizeof(buf), "<function@%p>", (void*)v->fn); snprintf(buf, sizeof(buf), "<function@%p>", (void *)v->fn);
return strdup(buf); return strdup(buf);
} }
case VAL_ARRAY: { case VAL_ARRAY: {
int n = array_length(v); int n = array_length(v);
if (n < 0) n = 0; if (n < 0) n = 0;
snprintf(buf, sizeof(buf), "[array n=%d]", n); snprintf(buf, sizeof(buf), "[array n=%d]", n);
return strdup(buf); return strdup(buf);
} }
case VAL_MAP: { case VAL_MAP: {
int n = 0; int n = 0;
if (v->type == VAL_MAP && v->map) { if (v->type == VAL_MAP && v->map) {
const Map *m = (const Map*)v->map; const Map *m = (const Map *)v->map;
n = m ? m->count : 0; n = m ? m->count : 0;
}
snprintf(buf, sizeof(buf), "{map n=%d}", n);
return strdup(buf);
}
case VAL_NIL:
default:
return strdup("nil");
} }
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) { int value_equals(const Value *a, const Value *b) {
// Numeric cross-type equality: int vs float compares numerically // 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)) { 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 da = (a->type == VAL_INT) ? (double)a->i : a->d;
double db = (b->type == VAL_INT) ? (double)b->i : b->d; double db = (b->type == VAL_INT) ? (double)b->i : b->d;
return da == db; return da == db;
} }
if (a->type != b->type) return 0; if (a->type != b->type) return 0;
switch (a->type) { switch (a->type) {
case VAL_INT: return a->i == b->i; case VAL_INT:
case VAL_BOOL: return (a->i != 0) == (b->i != 0); return a->i == b->i;
case VAL_STRING: { case VAL_BOOL:
const char *sa = a->s ? a->s : ""; return (a->i != 0) == (b->i != 0);
const char *sb = b->s ? b->s : ""; case VAL_STRING: {
return strcmp(sa, sb) == 0; const char *sa = a->s ? a->s : "";
} const char *sb = b->s ? b->s : "";
default: return 0; return strcmp(sa, sb) == 0;
} }
default:
return 0;
}
} }

View file

@ -8,7 +8,7 @@
*/ */
/** /**
* @file value.h * @file value.h
* @brief Defines the Value type and associated functions for the Fun VM. * @brief Defines the Value type and associated functions for the Fun VM.
* *
* This file defines the `Value` type, which represents all possible data types * This file defines the `Value` type, which represents all possible data types
@ -45,26 +45,26 @@ struct Array; /* forward */
struct Map; /* forward */ struct Map; /* forward */
typedef enum { typedef enum {
VAL_INT, VAL_INT,
VAL_BOOL, VAL_BOOL,
VAL_STRING, VAL_STRING,
VAL_FUNCTION, VAL_FUNCTION,
VAL_ARRAY, VAL_ARRAY,
VAL_MAP, VAL_MAP,
VAL_NIL, VAL_NIL,
VAL_FLOAT VAL_FLOAT
} ValueType; } ValueType;
typedef struct { typedef struct {
ValueType type; ValueType type;
union { union {
int64_t i; int64_t i;
double d; double d;
char *s; char *s;
struct Bytecode *fn; struct Bytecode *fn;
struct Array *arr; struct Array *arr;
struct Map *map; struct Map *map;
}; };
} Value; } Value;
/* constructors / helpers */ /* constructors / helpers */
@ -77,46 +77,46 @@ Value make_float(double v);
/* arrays */ /* arrays */
Value make_array_from_values(const Value *vals, int count); /* deep-copies vals */ 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_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_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_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_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_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_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 */ 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_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 */ Value array_concat(const Value *a, const Value *b); /* returns new array */
/* maps (string keys) */ /* maps (string keys) */
Value make_map_empty(void); /* new empty map */ 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_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_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 */ int map_has(const Value *m, const char *key); /* 1/0 */
Value map_keys_array(const Value *m); /* array of strings */ Value map_keys_array(const Value *m); /* array of strings */
Value map_values_array(const Value *m); /* array of values (copies) */ Value map_values_array(const Value *m); /* array of values (copies) */
/* copy/free */ /* 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 */ 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 */ /* utilities */
void print_value(const Value *v); void print_value(const Value *v);
int value_is_truthy(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 */ /* stringify into a newly-allocated C string; caller must free */
char *value_to_string_alloc(const Value *v); char *value_to_string_alloc(const Value *v);
/* array utils */ /* array utils */
int array_contains(const Value *arr, const Value *needle); /* 1/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 */ int array_index_of(const Value *arr, const Value *needle); /* idx or -1 */
void array_clear(Value *arr); /* free elements, count=0 */ void array_clear(Value *arr); /* free elements, count=0 */
/* string helpers returning newly allocated C strings or arrays */ /* string helpers returning newly allocated C strings or arrays */
char *string_substr(const char *s, int start, int len); /* clamps bounds */ char *string_substr(const char *s, int start, int len); /* clamps bounds */
int string_find(const char *hay, const char *needle); /* index or -1 */ 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 */ 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 */ char *array_join_with_sep(const Value *arr, const char *sep); /* join items as strings */
#endif #endif

1481
src/vm.c

File diff suppressed because it is too large Load diff

153
src/vm.h
View file

@ -20,94 +20,93 @@
#define STACK_SIZE 1024 #define STACK_SIZE 1024
static const char *opcode_names[] = { static const char *opcode_names[] = {
"NOP","LOAD_CONST","LOAD_LOCAL","STORE_LOCAL", "NOP", "LOAD_CONST", "LOAD_LOCAL", "STORE_LOCAL",
"LOAD_GLOBAL","STORE_GLOBAL","ADD","SUB","MUL","DIV", "LOAD_GLOBAL", "STORE_GLOBAL", "ADD", "SUB", "MUL", "DIV",
"LT","LTE","GT","GTE","EQ","NEQ","POP","JUMP", "LT", "LTE", "GT", "GTE", "EQ", "NEQ", "POP", "JUMP",
"JUMP_IF_FALSE","CALL","RETURN","PRINT","ECHO","HALT", "JUMP_IF_FALSE", "CALL", "RETURN", "PRINT", "ECHO", "HALT",
"LINE", "LINE",
"MOD","AND","OR","NOT","DUP","SWAP", "MOD", "AND", "OR", "NOT", "DUP", "SWAP",
"MAKE_ARRAY","INDEX_GET","INDEX_SET", "MAKE_ARRAY", "INDEX_GET", "INDEX_SET",
"LEN","PUSH","APOP","SET","INSERT","REMOVE","SLICE", "LEN", "PUSH", "APOP", "SET", "INSERT", "REMOVE", "SLICE",
"TO_NUMBER","TO_STRING","CAST","TYPEOF", "TO_NUMBER", "TO_STRING", "CAST", "TYPEOF",
"SPLIT","JOIN","SUBSTR","FIND", "SPLIT", "JOIN", "SUBSTR", "FIND",
"REGEX_MATCH","REGEX_SEARCH","REGEX_REPLACE", "REGEX_MATCH", "REGEX_SEARCH", "REGEX_REPLACE",
"CONTAINS","INDEX_OF","CLEAR", "CONTAINS", "INDEX_OF", "CLEAR",
"ENUMERATE","ZIP", "ENUMERATE", "ZIP",
"MIN","MAX","CLAMP","ABS","POW","RANDOM_SEED","RANDOM_INT", "MIN", "MAX", "CLAMP", "ABS", "POW", "RANDOM_SEED", "RANDOM_INT",
"MAKE_MAP","KEYS","VALUES","HAS_KEY", "MAKE_MAP", "KEYS", "VALUES", "HAS_KEY",
"READ_FILE","WRITE_FILE","ENV","INPUT_LINE","PROC_RUN","PROC_SYSTEM", "READ_FILE", "WRITE_FILE", "ENV", "INPUT_LINE", "PROC_RUN", "PROC_SYSTEM",
"TIME_NOW_MS","CLOCK_MONO_MS","DATE_FORMAT", "TIME_NOW_MS", "CLOCK_MONO_MS", "DATE_FORMAT",
"THREAD_SPAWN","THREAD_JOIN","SLEEP_MS", "THREAD_SPAWN", "THREAD_JOIN", "SLEEP_MS",
"RANDOM_NUMBER", "RANDOM_NUMBER",
"BAND","BOR","BXOR","BNOT","SHL","SHR","ROTL","ROTR", "BAND", "BOR", "BXOR", "BNOT", "SHL", "SHR", "ROTL", "ROTR",
"JSON_PARSE","JSON_STRINGIFY","JSON_FROM_FILE","JSON_TO_FILE", "JSON_PARSE", "JSON_STRINGIFY", "JSON_FROM_FILE", "JSON_TO_FILE",
"CURL_GET","CURL_POST","CURL_DOWNLOAD", "CURL_GET", "CURL_POST", "CURL_DOWNLOAD",
"SQLITE_OPEN","SQLITE_CLOSE","SQLITE_EXEC","SQLITE_QUERY", "SQLITE_OPEN", "SQLITE_CLOSE", "SQLITE_EXEC", "SQLITE_QUERY",
"LIBSQL_OPEN","LIBSQL_CLOSE","LIBSQL_EXEC","LIBSQL_QUERY", "LIBSQL_OPEN", "LIBSQL_CLOSE", "LIBSQL_EXEC", "LIBSQL_QUERY",
"PCSC_ESTABLISH","PCSC_RELEASE","PCSC_LIST_READERS","PCSC_CONNECT","PCSC_DISCONNECT","PCSC_TRANSMIT", "PCSC_ESTABLISH", "PCSC_RELEASE", "PCSC_LIST_READERS", "PCSC_CONNECT", "PCSC_DISCONNECT", "PCSC_TRANSMIT",
"PCRE2_TEST","PCRE2_MATCH","PCRE2_FINDALL", "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", "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", "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", "SOCK_TCP_LISTEN", "SOCK_TCP_ACCEPT", "SOCK_TCP_CONNECT", "SOCK_SEND", "SOCK_RECV", "SOCK_CLOSE", "SOCK_UNIX_LISTEN", "SOCK_UNIX_CONNECT",
"EXIT", "EXIT",
"OS_LIST_DIR", "OS_LIST_DIR",
"TK_BIND", "TK_BIND",
"SERIAL_OPEN","SERIAL_CONFIG","SERIAL_SEND","SERIAL_RECV","SERIAL_CLOSE", "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", "TK_EVAL", "TK_RESULT", "TK_LOOP", "TK_WM_TITLE", "TK_LABEL", "TK_BUTTON", "TK_PACK",
"TRY_PUSH","TRY_POP","THROW", "TRY_PUSH", "TRY_POP", "THROW",
"FMIN","FMAX", "FMIN", "FMAX",
/* Rust FFI demo */ /* Rust FFI demo */
"RUST_HELLO","RUST_HELLO_ARGS","RUST_HELLO_ARGS_RETURN","RUST_GET_SP","RUST_SET_EXIT", "RUST_HELLO", "RUST_HELLO_ARGS", "RUST_HELLO_ARGS_RETURN", "RUST_GET_SP", "RUST_SET_EXIT",
/* C++ demo */ /* C++ demo */
"CPP_ADD", "CPP_ADD",
/* Notcurses TUI (optional) */ /* Notcurses TUI (optional) */
"NC_INIT","NC_SHUTDOWN","NC_CLEAR","NC_DRAW_TEXT","NC_GETCH" "NC_INIT", "NC_SHUTDOWN", "NC_CLEAR", "NC_DRAW_TEXT", "NC_GETCH"};
};
typedef struct { typedef struct {
Bytecode *fn; Bytecode *fn;
int ip; int ip;
Value locals[MAX_FRAME_LOCALS]; Value locals[MAX_FRAME_LOCALS];
/* exception handling (per-frame) */ /* exception handling (per-frame) */
int try_stack[16]; int try_stack[16];
int try_sp; /* -1 when empty */ int try_sp; /* -1 when empty */
} Frame; } Frame;
struct VM { struct VM {
Value stack[STACK_SIZE]; Value stack[STACK_SIZE];
int sp; int sp;
Frame frames[MAX_FRAMES]; Frame frames[MAX_FRAMES];
int fp; // frame pointer, -1 when no frame int fp; // frame pointer, -1 when no frame
Value globals[MAX_GLOBALS]; Value globals[MAX_GLOBALS];
Value output[OUTPUT_SIZE]; // store printed values Value output[OUTPUT_SIZE]; // store printed values
int output_count; int output_count;
int output_is_partial[OUTPUT_SIZE]; // 1 when the corresponding output entry should not end with newline (echo) 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 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 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 (*on_error_repl)(struct VM *vm); // optional hook to run REPL on error
/* --- Debugger state --- */ /* --- Debugger state --- */
int debug_step_mode; // 0 none, 1 step, 2 next, 3 finish int debug_step_mode; // 0 none, 1 step, 2 next, 3 finish
int debug_step_target_fp; // target frame pointer for next/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 long long debug_step_start_ic; // instruction count snapshot when step/next requested
int debug_stop_requested; // force a pause at loop top int debug_stop_requested; // force a pause at loop top
struct { struct {
char *file; // strdup'ed file path char *file; // strdup'ed file path
int line; // 1-based line int line; // 1-based line
int active; // 1 if active int active; // 1 if active
} breakpoints[64]; } breakpoints[64];
int break_count; // number of active breakpoints int break_count; // number of active breakpoints
}; };
typedef struct VM VM; typedef struct VM VM;
@ -136,8 +135,8 @@ void vm_raise_error(VM *vm, const char *msg);
/* --- Debugger API --- */ /* --- Debugger API --- */
void vm_debug_reset(VM *vm); 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_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_delete_breakpoint(VM *vm, int id); // returns 1 on success
void vm_debug_clear_breakpoints(VM *vm); void vm_debug_clear_breakpoints(VM *vm);
void vm_debug_list_breakpoints(VM *vm); void vm_debug_list_breakpoints(VM *vm);
void vm_debug_request_step(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); void vm_debug_request_continue(VM *vm);
static inline int opcode_is_valid(int op) { 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) --- */ /* --- Minimal C ABI helpers for FFI (Rust opcode experiments) --- */

View file

@ -8,7 +8,7 @@
*/ */
/** /**
* @file add.c * @file add.c
* @brief Implements the OP_ADD opcode for arithmetic and string concatenation in the VM. * @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 * This file handles the OP_ADD instruction, which performs addition or concatenation
@ -36,50 +36,50 @@
*/ */
case OP_ADD: { case OP_ADD: {
Value b = pop_value(vm); Value b = pop_value(vm);
Value a = 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_INT || a.type == VAL_FLOAT) && (b.type == VAL_INT || b.type == VAL_FLOAT)) {
if (a.type == VAL_FLOAT || 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 da = (a.type == VAL_FLOAT) ? a.d : (double)a.i;
double db = (b.type == VAL_FLOAT) ? b.d : (double)b.i; double db = (b.type == VAL_FLOAT) ? b.d : (double)b.i;
Value res = make_float(da + db); Value res = make_float(da + db);
free_value(a); free_value(a);
free_value(b); free_value(b);
push_value(vm, res); 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);
} else { } else {
fprintf(stderr, "Runtime type error: ADD expects both numbers, both strings, or both arrays, got %s and %s\n", Value res = make_int(a.i + b.i);
value_type_name(a.type), value_type_name(b.type)); free_value(a);
exit(1); 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;
} }

View file

@ -8,7 +8,7 @@
*/ */
/** /**
* @file div.c * @file div.c
* @brief Implements the OP_DIV opcode for integer division in the VM. * @brief Implements the OP_DIV opcode for integer division in the VM.
* *
* This file handles the OP_DIV instruction, which performs integer division * This file handles the OP_DIV instruction, which performs integer division
@ -33,34 +33,34 @@
*/ */
case OP_DIV: { case OP_DIV: {
Value b = pop_value(vm); Value b = pop_value(vm);
Value a = 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_INT || a.type == VAL_FLOAT) && (b.type == VAL_INT || b.type == VAL_FLOAT)) {
if (a.type == VAL_FLOAT || 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 da = (a.type == VAL_FLOAT) ? a.d : (double)a.i;
double db = (b.type == VAL_FLOAT) ? b.d : (double)b.i; double db = (b.type == VAL_FLOAT) ? b.d : (double)b.i;
if (db == 0.0) { if (db == 0.0) {
vm_raise_error(vm, "division by zero"); vm_raise_error(vm, "division by zero");
break; break;
} }
Value res = make_float(da / db); Value res = make_float(da / db);
free_value(a); free_value(a);
free_value(b); free_value(b);
push_value(vm, res); 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);
}
} else { } else {
fprintf(stderr, "Runtime type error: DIV expects numbers, got %s and %s\n", if (b.i == 0) {
value_type_name(a.type), value_type_name(b.type)); vm_raise_error(vm, "division by zero");
exit(1); 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;
} }

View file

@ -8,7 +8,7 @@
*/ */
/** /**
* @file mul.c * @file mul.c
* @brief Implements the OP_MUL opcode for integer multiplication in the VM. * @brief Implements the OP_MUL opcode for integer multiplication in the VM.
* *
* This file handles the OP_MUL instruction, which performs integer multiplication * This file handles the OP_MUL instruction, which performs integer multiplication
@ -32,26 +32,26 @@
*/ */
case OP_MUL: { case OP_MUL: {
Value b = pop_value(vm); Value b = pop_value(vm);
Value a = 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_INT || a.type == VAL_FLOAT) && (b.type == VAL_INT || b.type == VAL_FLOAT)) {
if (a.type == VAL_FLOAT || 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 da = (a.type == VAL_FLOAT) ? a.d : (double)a.i;
double db = (b.type == VAL_FLOAT) ? b.d : (double)b.i; double db = (b.type == VAL_FLOAT) ? b.d : (double)b.i;
Value res = make_float(da * db); Value res = make_float(da * db);
free_value(a); free_value(a);
free_value(b); free_value(b);
push_value(vm, res); push_value(vm, res);
} else {
Value res = make_int(a.i * b.i);
free_value(a);
free_value(b);
push_value(vm, res);
}
} else { } else {
fprintf(stderr, "Runtime type error: MUL expects numbers, got %s and %s\n", Value res = make_int(a.i * b.i);
value_type_name(a.type), value_type_name(b.type)); free_value(a);
exit(1); 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;
} }

View file

@ -8,7 +8,7 @@
*/ */
/** /**
* @file sub.c * @file sub.c
* @brief Implements the OP_SUB opcode for integer subtraction in the VM. * @brief Implements the OP_SUB opcode for integer subtraction in the VM.
* *
* This file handles the OP_SUB instruction, which performs integer subtraction * This file handles the OP_SUB instruction, which performs integer subtraction
@ -32,26 +32,26 @@
*/ */
case OP_SUB: { case OP_SUB: {
Value b = pop_value(vm); Value b = pop_value(vm);
Value a = 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_INT || a.type == VAL_FLOAT) && (b.type == VAL_INT || b.type == VAL_FLOAT)) {
if (a.type == VAL_FLOAT || 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 da = (a.type == VAL_FLOAT) ? a.d : (double)a.i;
double db = (b.type == VAL_FLOAT) ? b.d : (double)b.i; double db = (b.type == VAL_FLOAT) ? b.d : (double)b.i;
Value res = make_float(da - db); Value res = make_float(da - db);
free_value(a); free_value(a);
free_value(b); free_value(b);
push_value(vm, res); push_value(vm, res);
} else {
Value res = make_int(a.i - b.i);
free_value(a);
free_value(b);
push_value(vm, res);
}
} else { } else {
fprintf(stderr, "Runtime type error: SUB expects numbers, got %s and %s\n", Value res = make_int(a.i - b.i);
value_type_name(a.type), value_type_name(b.type)); free_value(a);
exit(1); 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;
} }

View file

@ -8,7 +8,7 @@
*/ */
/** /**
* @file apop.c * @file apop.c
* @brief Implements the OP_APOP opcode for removing elements from arrays in the VM. * @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 * This file handles the OP_APOP instruction, which removes the last element from an array
@ -33,17 +33,17 @@
*/ */
case OP_APOP: { case OP_APOP: {
Value arr = pop_value(vm); Value arr = pop_value(vm);
if (arr.type != VAL_ARRAY) { if (arr.type != VAL_ARRAY) {
fprintf(stderr, "Runtime type error: ARR_APOP expects array\n"); fprintf(stderr, "Runtime type error: ARR_APOP expects array\n");
exit(1); exit(1);
} }
Value out; Value out;
if (!array_pop(&arr, &out)) { if (!array_pop(&arr, &out)) {
fprintf(stderr, "Runtime error: pop from empty array\n"); fprintf(stderr, "Runtime error: pop from empty array\n");
exit(1); exit(1);
} }
free_value(arr); free_value(arr);
push_value(vm, out); push_value(vm, out);
break; break;
} }

View file

@ -32,13 +32,13 @@
*/ */
case OP_CLEAR: { case OP_CLEAR: {
Value arr = pop_value(vm); Value arr = pop_value(vm);
if (arr.type != VAL_ARRAY) { if (arr.type != VAL_ARRAY) {
fprintf(stderr, "Runtime type error: CLEAR expects array\n"); fprintf(stderr, "Runtime type error: CLEAR expects array\n");
exit(1); exit(1);
} }
array_clear(&arr); array_clear(&arr);
free_value(arr); free_value(arr);
push_value(vm, make_int(0)); push_value(vm, make_int(0));
break; break;
} }

View file

@ -8,7 +8,7 @@
*/ */
/** /**
* @file contains.c * @file contains.c
* @brief Implements the OP_CONTAINS opcode for checking array membership in the VM. * @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. * This file handles the OP_CONTAINS instruction, which checks if a value is present in an array.
@ -32,15 +32,15 @@
*/ */
case OP_CONTAINS: { case OP_CONTAINS: {
Value needle = pop_value(vm); Value needle = pop_value(vm);
Value arr = pop_value(vm); Value arr = pop_value(vm);
if (arr.type != VAL_ARRAY) { if (arr.type != VAL_ARRAY) {
fprintf(stderr, "Runtime type error: CONTAINS expects (array, value)\n"); fprintf(stderr, "Runtime type error: CONTAINS expects (array, value)\n");
exit(1); exit(1);
} }
int ok = array_contains(&arr, &needle); int ok = array_contains(&arr, &needle);
free_value(arr); free_value(arr);
free_value(needle); free_value(needle);
push_value(vm, make_int(ok ? 1 : 0)); push_value(vm, make_int(ok ? 1 : 0));
break; break;
} }

View file

@ -8,7 +8,7 @@
*/ */
/** /**
* @file enumerate.c * @file enumerate.c
* @brief Implements the OP_ENUMERATE opcode for enumerating arrays in the VM. * @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 * This file handles the OP_ENUMERATE instruction, which creates an array of [index, value] pairs
@ -32,13 +32,13 @@
*/ */
case OP_ENUMERATE: { case OP_ENUMERATE: {
Value arr = pop_value(vm); Value arr = pop_value(vm);
if (arr.type != VAL_ARRAY) { if (arr.type != VAL_ARRAY) {
fprintf(stderr, "Runtime type error: ENUMERATE expects array\n"); fprintf(stderr, "Runtime type error: ENUMERATE expects array\n");
exit(1); exit(1);
} }
Value out = bi_enumerate(&arr); Value out = bi_enumerate(&arr);
free_value(arr); free_value(arr);
push_value(vm, out); push_value(vm, out);
break; break;
} }

View file

@ -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. * @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 * This file handles the OP_INDEX_GET instruction, which retrieves an element from
@ -34,34 +34,41 @@
*/ */
case OP_INDEX_GET: { case OP_INDEX_GET: {
Value idx = pop_value(vm); Value idx = pop_value(vm);
Value container = pop_value(vm); Value container = pop_value(vm);
#ifdef FUN_DEBUG #ifdef FUN_DEBUG
fprintf(stderr, "DEBUG INDEX_GET: container.type=%d idx.type=%d\n", fprintf(stderr, "DEBUG INDEX_GET: container.type=%d idx.type=%d\n",
container.type, idx.type); container.type, idx.type);
#endif #endif
if (container.type == VAL_ARRAY) { if (container.type == VAL_ARRAY) {
if (idx.type != VAL_INT) { fprintf(stderr, "INDEX_GET index must be int for array\n"); exit(1); } if (idx.type != VAL_INT) {
Value elem; fprintf(stderr, "INDEX_GET index must be int for array\n");
if (!array_get_copy(&container, (int)idx.i, &elem)) { exit(1);
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; 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;
} }

View file

@ -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. * @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. * 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: { case OP_INDEX_OF: {
Value needle = pop_value(vm); Value needle = pop_value(vm);
Value arr = pop_value(vm); Value arr = pop_value(vm);
if (arr.type != VAL_ARRAY) { if (arr.type != VAL_ARRAY) {
fprintf(stderr, "Runtime type error: INDEX_OF expects (array, value)\n"); fprintf(stderr, "Runtime type error: INDEX_OF expects (array, value)\n");
exit(1); exit(1);
} }
int idx = array_index_of(&arr, &needle); int idx = array_index_of(&arr, &needle);
free_value(arr); free_value(arr);
free_value(needle); free_value(needle);
push_value(vm, make_int(idx)); push_value(vm, make_int(idx));
break; break;
} }

View file

@ -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. * @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 * This file handles the OP_INDEX_SET instruction, which assigns a value to an
@ -32,32 +32,39 @@
* @date 2025-10-16 * @date 2025-10-16
*/ */
case OP_INDEX_SET: { case OP_INDEX_SET: {
Value v = pop_value(vm); Value v = pop_value(vm);
Value idx = pop_value(vm); Value idx = pop_value(vm);
Value container = pop_value(vm); Value container = pop_value(vm);
#ifdef FUN_DEBUG #ifdef FUN_DEBUG
fprintf(stderr, "DEBUG INDEX_SET: container.type=%d idx.type=%d value.type=%d\n", fprintf(stderr, "DEBUG INDEX_SET: container.type=%d idx.type=%d value.type=%d\n",
container.type, idx.type, v.type); container.type, idx.type, v.type);
#endif #endif
if (container.type == VAL_ARRAY) { if (container.type == VAL_ARRAY) {
if (idx.type != VAL_INT) { fprintf(stderr, "INDEX_SET index must be int for array\n"); exit(1); } if (idx.type != VAL_INT) {
if (!array_set(&container, (int)idx.i, v)) { fprintf(stderr, "INDEX_SET index must be int for array\n");
fprintf(stderr, "Runtime error: index out of range\n"); exit(1); 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; 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;
} }

View file

@ -8,7 +8,7 @@
*/ */
/** /**
* @file insert.c * @file insert.c
* @brief Implements the OP_INSERT opcode for inserting elements into arrays in the VM. * @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 * This file handles the OP_INSERT instruction, which inserts a value into an array
@ -34,20 +34,20 @@
*/ */
case OP_INSERT: { case OP_INSERT: {
Value v = pop_value(vm); Value v = pop_value(vm);
Value idx = pop_value(vm); Value idx = pop_value(vm);
Value arr = pop_value(vm); Value arr = pop_value(vm);
if (arr.type != VAL_ARRAY || idx.type != VAL_INT) { if (arr.type != VAL_ARRAY || idx.type != VAL_INT) {
fprintf(stderr, "Runtime type error: ARR_INSERT expects (array, int, value)\n"); fprintf(stderr, "Runtime type error: ARR_INSERT expects (array, int, value)\n");
exit(1); exit(1);
} }
int n = array_insert(&arr, (int)idx.i, v); int n = array_insert(&arr, (int)idx.i, v);
if (n < 0) { if (n < 0) {
fprintf(stderr, "Runtime error: insert failed (OOM?)\n"); fprintf(stderr, "Runtime error: insert failed (OOM?)\n");
exit(1); exit(1);
} }
free_value(arr); free_value(arr);
free_value(idx); free_value(idx);
push_value(vm, make_int(n)); push_value(vm, make_int(n));
break; break;
} }

View file

@ -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. * @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 * This file handles the OP_JOIN instruction, which joins the elements of an array into a string
@ -33,15 +33,15 @@
*/ */
case OP_JOIN: { case OP_JOIN: {
Value sep = pop_value(vm); Value sep = pop_value(vm);
Value arr = pop_value(vm); Value arr = pop_value(vm);
if (arr.type != VAL_ARRAY || sep.type != VAL_STRING) { if (arr.type != VAL_ARRAY || sep.type != VAL_STRING) {
fprintf(stderr, "Runtime type error: JOIN expects (array, string)\n"); fprintf(stderr, "Runtime type error: JOIN expects (array, string)\n");
exit(1); exit(1);
} }
Value out = bi_join(&arr, &sep); Value out = bi_join(&arr, &sep);
free_value(arr); free_value(arr);
free_value(sep); free_value(sep);
push_value(vm, out); push_value(vm, out);
break; break;
} }

View file

@ -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. * @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, * This file handles the OP_MAKE_ARRAY instruction, which pops `n` values from the stack,
@ -33,23 +33,26 @@
*/ */
case OP_MAKE_ARRAY: { case OP_MAKE_ARRAY: {
int n = inst.operand; int n = inst.operand;
if (n < 0 || vm->sp + 1 < n) { if (n < 0 || vm->sp + 1 < n) {
fprintf(stderr, "Runtime error: invalid element count for MAKE_ARRAY\n"); fprintf(stderr, "Runtime error: invalid element count for MAKE_ARRAY\n");
exit(1); exit(1);
} }
/* pop n values into temp array preserving original order */ /* pop n values into temp array preserving original order */
Value *vals = (Value*)malloc(sizeof(Value) * n); Value *vals = (Value *)malloc(sizeof(Value) * n);
if (!vals) { fprintf(stderr, "Runtime error: OOM in MAKE_ARRAY\n"); exit(1); } if (!vals) {
for (int i = n - 1; i >= 0; --i) { fprintf(stderr, "Runtime error: OOM in MAKE_ARRAY\n");
vals[i] = pop_value(vm); /* take ownership */ exit(1);
} }
/* build array by copying values, then free originals */ for (int i = n - 1; i >= 0; --i) {
Value arr = make_array_from_values(vals, n); vals[i] = pop_value(vm); /* take ownership */
for (int i = 0; i < n; ++i) { }
free_value(vals[i]); /* build array by copying values, then free originals */
} Value arr = make_array_from_values(vals, n);
free(vals); for (int i = 0; i < n; ++i) {
push_value(vm, arr); free_value(vals[i]);
break; }
free(vals);
push_value(vm, arr);
break;
} }

View file

@ -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. * @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. * 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: { case OP_PUSH: {
Value v = pop_value(vm); Value v = pop_value(vm);
Value arr = pop_value(vm); Value arr = pop_value(vm);
if (arr.type != VAL_ARRAY) { if (arr.type != VAL_ARRAY) {
fprintf(stderr, "Runtime type error: ARR_PUSH expects array\n"); fprintf(stderr, "Runtime type error: ARR_PUSH expects array\n");
exit(1); exit(1);
} }
int n = array_push(&arr, v); int n = array_push(&arr, v);
if (n < 0) { if (n < 0) {
fprintf(stderr, "Runtime error: push failed (OOM?)\n"); fprintf(stderr, "Runtime error: push failed (OOM?)\n");
exit(1); exit(1);
} }
free_value(arr); free_value(arr);
push_value(vm, make_int(n)); push_value(vm, make_int(n));
break; break;
} }

View file

@ -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. * @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 * This file handles the OP_ARR_REMOVE instruction, which removes an element from an array
@ -34,19 +34,19 @@
*/ */
case OP_REMOVE: { case OP_REMOVE: {
Value idx = pop_value(vm); Value idx = pop_value(vm);
Value arr = pop_value(vm); Value arr = pop_value(vm);
if (arr.type != VAL_ARRAY || idx.type != VAL_INT) { if (arr.type != VAL_ARRAY || idx.type != VAL_INT) {
fprintf(stderr, "Runtime type error: ARR_REMOVE expects (array, int)\n"); fprintf(stderr, "Runtime type error: ARR_REMOVE expects (array, int)\n");
exit(1); exit(1);
} }
Value out; Value out;
if (!array_remove(&arr, (int)idx.i, &out)) { if (!array_remove(&arr, (int)idx.i, &out)) {
fprintf(stderr, "Runtime error: remove index out of range\n"); fprintf(stderr, "Runtime error: remove index out of range\n");
exit(1); exit(1);
} }
free_value(arr); free_value(arr);
free_value(idx); free_value(idx);
push_value(vm, out); push_value(vm, out);
break; break;
} }

View file

@ -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. * @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. * 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: { case OP_SET: {
Value v = pop_value(vm); Value v = pop_value(vm);
Value idx = pop_value(vm); Value idx = pop_value(vm);
Value arr = pop_value(vm); Value arr = pop_value(vm);
if (arr.type != VAL_ARRAY || idx.type != VAL_INT) { if (arr.type != VAL_ARRAY || idx.type != VAL_INT) {
fprintf(stderr, "Runtime type error: ARR_SET expects (array, int, value)\n"); fprintf(stderr, "Runtime type error: ARR_SET expects (array, int, value)\n");
exit(1); exit(1);
} }
if (!array_set(&arr, (int)idx.i, v)) { if (!array_set(&arr, (int)idx.i, v)) {
fprintf(stderr, "Runtime error: set index out of range\n"); fprintf(stderr, "Runtime error: set index out of range\n");
exit(1); exit(1);
} }
free_value(arr); free_value(arr);
free_value(idx); free_value(idx);
/* v already owned by array; push copy for return value */ /* v already owned by array; push copy for return value */
push_value(vm, copy_value(&v)); push_value(vm, copy_value(&v));
free_value(v); free_value(v);
break; break;
} }

View file

@ -8,7 +8,7 @@
*/ */
/** /**
* @file slice.c * @file slice.c
* @brief Implements the OP_SLICE opcode for array slicing in the VM. * @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 * This file handles the OP_SLICE instruction, which creates a new array containing
@ -33,17 +33,17 @@
*/ */
case OP_SLICE: { case OP_SLICE: {
Value end = pop_value(vm); Value end = pop_value(vm);
Value start = pop_value(vm); Value start = pop_value(vm);
Value arr = pop_value(vm); Value arr = pop_value(vm);
if (arr.type != VAL_ARRAY || start.type != VAL_INT || end.type != VAL_INT) { if (arr.type != VAL_ARRAY || start.type != VAL_INT || end.type != VAL_INT) {
fprintf(stderr, "Runtime type error: SLICE expects (array, int, int)\n"); fprintf(stderr, "Runtime type error: SLICE expects (array, int, int)\n");
exit(1); exit(1);
} }
Value out = array_slice(&arr, (int)start.i, (int)end.i); Value out = array_slice(&arr, (int)start.i, (int)end.i);
free_value(arr); free_value(arr);
free_value(start); free_value(start);
free_value(end); free_value(end);
push_value(vm, out); push_value(vm, out);
break; break;
} }

View file

@ -8,7 +8,7 @@
*/ */
/** /**
* @file zip.c * @file zip.c
* @brief Implements the OP_ZIP opcode for array zipping in the VM. * @brief Implements the OP_ZIP opcode for array zipping in the VM.
* *
* This file handles the OP_ZIP instruction, which combines two arrays into * This file handles the OP_ZIP instruction, which combines two arrays into
@ -33,15 +33,15 @@
*/ */
case OP_ZIP: { case OP_ZIP: {
Value b = pop_value(vm); Value b = pop_value(vm);
Value a = pop_value(vm); Value a = pop_value(vm);
if (a.type != VAL_ARRAY || b.type != VAL_ARRAY) { if (a.type != VAL_ARRAY || b.type != VAL_ARRAY) {
fprintf(stderr, "Runtime type error: ZIP expects (array, array)\n"); fprintf(stderr, "Runtime type error: ZIP expects (array, array)\n");
exit(1); exit(1);
} }
Value out = bi_zip(&a, &b); Value out = bi_zip(&a, &b);
free_value(a); free_value(a);
free_value(b); free_value(b);
push_value(vm, out); push_value(vm, out);
break; break;
} }

View file

@ -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/ * https://fun-lang.xyz/
* *
* Copyright 2025 Johannes Findeisen <you@hanez.org> * Copyright 2025 Johannes Findeisen <you@hanez.org>
@ -15,13 +15,13 @@
* pushes: (uint32_t)(a & b) * pushes: (uint32_t)(a & b)
*/ */
case OP_BAND: { case OP_BAND: {
Value vb = pop_value(vm); Value vb = pop_value(vm);
Value va = pop_value(vm); Value va = pop_value(vm);
uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u; 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 b = (vb.type == VAL_INT) ? (uint32_t)vb.i : 0u;
uint32_t r = a & b; uint32_t r = a & b;
free_value(vb); free_value(vb);
free_value(va); free_value(va);
push_value(vm, make_int((int64_t)(uint64_t)r)); push_value(vm, make_int((int64_t)(uint64_t)r));
break; break;
} }

View file

@ -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/ * https://fun-lang.xyz/
* *
* Copyright 2025 Johannes Findeisen <you@hanez.org> * Copyright 2025 Johannes Findeisen <you@hanez.org>
@ -15,10 +15,10 @@
* pushes: (uint32_t)(~a) * pushes: (uint32_t)(~a)
*/ */
case OP_BNOT: { case OP_BNOT: {
Value va = pop_value(vm); Value va = pop_value(vm);
uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u; uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u;
uint32_t r = ~a; uint32_t r = ~a;
free_value(va); free_value(va);
push_value(vm, make_int((int64_t)(uint64_t)r)); push_value(vm, make_int((int64_t)(uint64_t)r));
break; break;
} }

View file

@ -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/ * https://fun-lang.xyz/
* *
* Copyright 2025 Johannes Findeisen <you@hanez.org> * Copyright 2025 Johannes Findeisen <you@hanez.org>
@ -15,13 +15,13 @@
* pushes: (uint32_t)(a | b) * pushes: (uint32_t)(a | b)
*/ */
case OP_BOR: { case OP_BOR: {
Value vb = pop_value(vm); Value vb = pop_value(vm);
Value va = pop_value(vm); Value va = pop_value(vm);
uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u; 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 b = (vb.type == VAL_INT) ? (uint32_t)vb.i : 0u;
uint32_t r = a | b; uint32_t r = a | b;
free_value(vb); free_value(vb);
free_value(va); free_value(va);
push_value(vm, make_int((int64_t)(uint64_t)r)); push_value(vm, make_int((int64_t)(uint64_t)r));
break; break;
} }

View file

@ -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/ * https://fun-lang.xyz/
* *
* Copyright 2025 Johannes Findeisen <you@hanez.org> * Copyright 2025 Johannes Findeisen <you@hanez.org>
@ -15,13 +15,13 @@
* pushes: (uint32_t)(a ^ b) * pushes: (uint32_t)(a ^ b)
*/ */
case OP_BXOR: { case OP_BXOR: {
Value vb = pop_value(vm); Value vb = pop_value(vm);
Value va = pop_value(vm); Value va = pop_value(vm);
uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u; 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 b = (vb.type == VAL_INT) ? (uint32_t)vb.i : 0u;
uint32_t r = a ^ b; uint32_t r = a ^ b;
free_value(vb); free_value(vb);
free_value(va); free_value(va);
push_value(vm, make_int((int64_t)(uint64_t)r)); push_value(vm, make_int((int64_t)(uint64_t)r));
break; break;
} }

View file

@ -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/ * https://fun-lang.xyz/
* *
* Copyright 2025 Johannes Findeisen <you@hanez.org> * Copyright 2025 Johannes Findeisen <you@hanez.org>
@ -15,14 +15,14 @@
* pushes: rotl32(a, s) * pushes: rotl32(a, s)
*/ */
case OP_ROTL: { case OP_ROTL: {
Value vs = pop_value(vm); Value vs = pop_value(vm);
Value va = pop_value(vm); Value va = pop_value(vm);
uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u; uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u;
uint32_t s = (vs.type == VAL_INT) ? (uint32_t)vs.i : 0u; uint32_t s = (vs.type == VAL_INT) ? (uint32_t)vs.i : 0u;
s &= 31u; s &= 31u;
uint32_t r = (s == 0u) ? a : ((a << s) | (a >> (32u - s))); uint32_t r = (s == 0u) ? a : ((a << s) | (a >> (32u - s)));
free_value(vs); free_value(vs);
free_value(va); free_value(va);
push_value(vm, make_int((int64_t)(uint64_t)r)); push_value(vm, make_int((int64_t)(uint64_t)r));
break; break;
} }

View file

@ -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/ * https://fun-lang.xyz/
* *
* Copyright 2025 Johannes Findeisen <you@hanez.org> * Copyright 2025 Johannes Findeisen <you@hanez.org>
@ -15,14 +15,14 @@
* pushes: rotr32(a, s) * pushes: rotr32(a, s)
*/ */
case OP_ROTR: { case OP_ROTR: {
Value vs = pop_value(vm); Value vs = pop_value(vm);
Value va = pop_value(vm); Value va = pop_value(vm);
uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u; uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u;
uint32_t s = (vs.type == VAL_INT) ? (uint32_t)vs.i : 0u; uint32_t s = (vs.type == VAL_INT) ? (uint32_t)vs.i : 0u;
s &= 31u; s &= 31u;
uint32_t r = (s == 0u) ? a : ((a >> s) | (a << (32u - s))); uint32_t r = (s == 0u) ? a : ((a >> s) | (a << (32u - s)));
free_value(vs); free_value(vs);
free_value(va); free_value(va);
push_value(vm, make_int((int64_t)(uint64_t)r)); push_value(vm, make_int((int64_t)(uint64_t)r));
break; break;
} }

View file

@ -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/ * https://fun-lang.xyz/
* *
* Copyright 2025 Johannes Findeisen <you@hanez.org> * Copyright 2025 Johannes Findeisen <you@hanez.org>
@ -15,14 +15,14 @@
* pushes: (uint32_t)(a << (s&31)) * pushes: (uint32_t)(a << (s&31))
*/ */
case OP_SHL: { case OP_SHL: {
Value vs = pop_value(vm); Value vs = pop_value(vm);
Value va = pop_value(vm); Value va = pop_value(vm);
uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u; uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u;
uint32_t s = (vs.type == VAL_INT) ? (uint32_t)vs.i : 0u; uint32_t s = (vs.type == VAL_INT) ? (uint32_t)vs.i : 0u;
s &= 31u; s &= 31u;
uint32_t r = (s == 0u) ? a : (a << s); uint32_t r = (s == 0u) ? a : (a << s);
free_value(vs); free_value(vs);
free_value(va); free_value(va);
push_value(vm, make_int((int64_t)(uint64_t)r)); push_value(vm, make_int((int64_t)(uint64_t)r));
break; break;
} }

View file

@ -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/ * https://fun-lang.xyz/
* *
* Copyright 2025 Johannes Findeisen <you@hanez.org> * Copyright 2025 Johannes Findeisen <you@hanez.org>
@ -15,14 +15,14 @@
* pushes: (uint32_t)(a >> (s&31)) using logical shift * pushes: (uint32_t)(a >> (s&31)) using logical shift
*/ */
case OP_SHR: { case OP_SHR: {
Value vs = pop_value(vm); Value vs = pop_value(vm);
Value va = pop_value(vm); Value va = pop_value(vm);
uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u; uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u;
uint32_t s = (vs.type == VAL_INT) ? (uint32_t)vs.i : 0u; uint32_t s = (vs.type == VAL_INT) ? (uint32_t)vs.i : 0u;
s &= 31u; s &= 31u;
uint32_t r = (s == 0u) ? a : (a >> s); uint32_t r = (s == 0u) ? a : (a >> s);
free_value(vs); free_value(vs);
free_value(va); free_value(va);
push_value(vm, make_int((int64_t)(uint64_t)r)); push_value(vm, make_int((int64_t)(uint64_t)r));
break; break;
} }

View file

@ -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/ * https://fun-lang.xyz/
* *
* Copyright 2025 Johannes Findeisen <you@hanez.org> * Copyright 2025 Johannes Findeisen <you@hanez.org>
@ -17,73 +17,77 @@
*/ */
case OP_CAST: { case OP_CAST: {
/* pop type then value (args pushed in this order: value, typeName) */ /* pop type then value (args pushed in this order: value, typeName) */
Value t = pop_value(vm); Value t = pop_value(vm);
Value v = pop_value(vm); Value v = pop_value(vm);
const char *tn = (t.type == VAL_STRING && t.s) ? t.s : NULL; const char *tn = (t.type == VAL_STRING && t.s) ? t.s : NULL;
Value out = make_nil(); Value out = make_nil();
/* Normalize target name to lowercase into a small buffer */ /* Normalize target name to lowercase into a small buffer */
char target[32]; char target[32];
int k = 0; int k = 0;
if (tn) { if (tn) {
const char *p = tn; const char *p = tn;
while (*p && k < (int)sizeof(target) - 1) { while (*p && k < (int)sizeof(target) - 1) {
char c = *p++; char c = *p++;
if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a'); if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a');
target[k++] = c; target[k++] = c;
}
} }
target[k] = '\0'; }
target[k] = '\0';
if (!tn) { if (!tn) {
out = make_nil(); out = make_nil();
} else if (strcmp(target, "number") == 0) { } else if (strcmp(target, "number") == 0) {
if (v.type == VAL_INT) { if (v.type == VAL_INT) {
out = make_int(v.i); out = make_int(v.i);
} else if (v.type == VAL_STRING) { } else if (v.type == VAL_STRING) {
const char *s = v.s ? v.s : ""; const char *s = v.s ? v.s : "";
const char *p = s; const char *p = s;
while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p++; while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')
char *endp = NULL; p++;
long long parsed = strtoll(p, &endp, 10); char *endp = NULL;
while (endp && (*endp == ' ' || *endp == '\t' || *endp == '\r' || *endp == '\n')) endp++; long long parsed = strtoll(p, &endp, 10);
if (endp && *endp != '\0') out = make_int(0); while (endp && (*endp == ' ' || *endp == '\t' || *endp == '\r' || *endp == '\n'))
else out = make_int((int64_t)parsed); endp++;
} else { if (endp && *endp != '\0')
out = make_int(0); out = make_int(0);
} else
} else if (strcmp(target, "string") == 0) { out = make_int((int64_t)parsed);
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 { } 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(t);
free_value(v); free_value(v);
push_value(vm, out); push_value(vm, out);
break; break;
} }

View file

@ -8,7 +8,7 @@
*/ */
/** /**
* @file call.c * @file call.c
* @brief Implements the OP_CALL opcode for function calls in the VM. * @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. * This file handles the OP_CALL instruction, which calls a function with arguments.
@ -28,27 +28,27 @@
*/ */
case OP_CALL: { case OP_CALL: {
int argc = inst.operand; int argc = inst.operand;
if (argc < 0) argc = 0; if (argc < 0) argc = 0;
/* collect args in reverse (preserve order) */ /* collect args in reverse (preserve order) */
Value *args = NULL; Value *args = NULL;
if (argc > 0) { if (argc > 0) {
args = (Value*)malloc(sizeof(Value) * argc); args = (Value *)malloc(sizeof(Value) * argc);
/* pop args into array in reverse */ /* pop args into array in reverse */
for (int i = argc - 1; i >= 0; --i) { for (int i = argc - 1; i >= 0; --i) {
args[i] = pop_value(vm); args[i] = pop_value(vm);
}
} }
/* pop function value */ }
Value fnv = pop_value(vm); /* pop function value */
if (fnv.type != VAL_FUNCTION) { Value fnv = pop_value(vm);
fprintf(stderr, "Runtime type error: CALL expects function\n"); if (fnv.type != VAL_FUNCTION) {
exit(1); 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); /* push new frame and transfer args */
/* free args array (locals moved), free fnv (no-op for function) */ vm_push_frame(vm, fnv.fn, argc, args);
free(args); /* free args array (locals moved), free fnv (no-op for function) */
/* note: fnv contains a pointer to the Bytecode, don't free here */ free(args);
break; /* note: fnv contains a pointer to the Bytecode, don't free here */
break;
} }

View file

@ -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. * @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. * This file handles the OP_DUP instruction, which duplicates the top value on the stack.
@ -25,17 +25,17 @@
* // Bytecode: OP_DUP * // Bytecode: OP_DUP
* // Stack before: [42] * // Stack before: [42]
* // Stack after: [42, 42] * // Stack after: [42, 42]
* *
* @author Johannes Findeisen * @author Johannes Findeisen
* @date 2025-10-16 * @date 2025-10-16
*/ */
case OP_DUP: { case OP_DUP: {
if (vm->sp < 0) { if (vm->sp < 0) {
fprintf(stderr, "Runtime error: stack underflow for DUP\n"); fprintf(stderr, "Runtime error: stack underflow for DUP\n");
exit(1); exit(1);
} }
Value top = vm->stack[vm->sp]; Value top = vm->stack[vm->sp];
push_value(vm, copy_value(&top)); push_value(vm, copy_value(&top));
break; break;
} }

View file

@ -10,7 +10,7 @@
*/ */
/** /**
* @file exit.c * @file exit.c
* @brief Implements the OP_EXIT opcode to terminate the script with an exit code. * @brief Implements the OP_EXIT opcode to terminate the script with an exit code.
* *
* Behavior: * Behavior:
@ -20,22 +20,22 @@
*/ */
case OP_EXIT: { case OP_EXIT: {
int code = 0; int code = 0;
if (vm->sp >= 0) { if (vm->sp >= 0) {
Value v = pop_value(vm); Value v = pop_value(vm);
if (v.type == VAL_INT) { if (v.type == VAL_INT) {
code = (int)v.i; code = (int)v.i;
} else if (v.type == VAL_STRING) { } else if (v.type == VAL_STRING) {
/* best-effort parse number from string */ /* best-effort parse number from string */
code = (int)strtoll(v.s, NULL, 10); code = (int)strtoll(v.s, NULL, 10);
} else if (v.type == VAL_NIL) { } else if (v.type == VAL_NIL) {
code = 0; code = 0;
} else { } else {
/* unsupported type for exit; default to 0 */ /* unsupported type for exit; default to 0 */
code = 0; code = 0;
}
free_value(v);
} }
vm->exit_code = code; free_value(v);
return; }
vm->exit_code = code;
return;
} }

View file

@ -8,7 +8,7 @@
*/ */
/** /**
* @file halt.c * @file halt.c
* @brief Implements the OP_HALT opcode for stopping VM execution. * @brief Implements the OP_HALT opcode for stopping VM execution.
* *
* This file handles the OP_HALT instruction, which stops the execution of the VM. * This file handles the OP_HALT instruction, which stops the execution of the VM.
@ -27,4 +27,4 @@
*/ */
case OP_HALT: case OP_HALT:
return; return;

View file

@ -8,7 +8,7 @@
*/ */
/** /**
* @file jump.c * @file jump.c
* @brief Implements the OP_JUMP opcode for unconditional jumps in the VM. * @brief Implements the OP_JUMP opcode for unconditional jumps in the VM.
* *
* This file handles the OP_JUMP instruction, which performs an unconditional * This file handles the OP_JUMP instruction, which performs an unconditional
@ -28,6 +28,6 @@
*/ */
case OP_JUMP: { case OP_JUMP: {
f->ip = inst.operand; f->ip = inst.operand;
break; break;
} }

View file

@ -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. * @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 * This file handles the OP_JUMP_IF_FALSE instruction, which jumps if the top
@ -29,11 +29,11 @@
*/ */
case OP_JUMP_IF_FALSE: { case OP_JUMP_IF_FALSE: {
Value cond = pop_value(vm); Value cond = pop_value(vm);
int truthy = value_is_truthy(&cond); int truthy = value_is_truthy(&cond);
free_value(cond); free_value(cond);
if (!truthy) { if (!truthy) {
f->ip = inst.operand; f->ip = inst.operand;
} }
break; break;
} }

View file

@ -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. * @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 * This file handles the OP_LOAD_CONST instruction, which loads a constant value
@ -26,12 +26,12 @@
*/ */
case OP_LOAD_CONST: { case OP_LOAD_CONST: {
int idx = inst.operand; int idx = inst.operand;
if (idx < 0 || idx >= f->fn->const_count) { if (idx < 0 || idx >= f->fn->const_count) {
fprintf(stderr, "Runtime error: constant index out of range\n"); fprintf(stderr, "Runtime error: constant index out of range\n");
exit(1); exit(1);
} }
Value c = copy_value(&f->fn->constants[idx]); Value c = copy_value(&f->fn->constants[idx]);
push_value(vm, c); push_value(vm, c);
break; break;
} }

View file

@ -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. * @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 * This file handles the OP_LOAD_GLOBAL instruction, which loads a global variable
@ -31,14 +31,14 @@
*/ */
case OP_LOAD_GLOBAL: { case OP_LOAD_GLOBAL: {
int idx = inst.operand; int idx = inst.operand;
if (idx < 0 || idx >= MAX_GLOBALS) { if (idx < 0 || idx >= MAX_GLOBALS) {
fprintf(stderr, "Runtime error: global index out of range\n"); fprintf(stderr, "Runtime error: global index out of range\n");
exit(1); exit(1);
} }
#ifdef FUN_DEBUG #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 #endif
push_value(vm, copy_value(&vm->globals[idx])); push_value(vm, copy_value(&vm->globals[idx]));
break; break;
} }

View file

@ -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. * @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 * This file handles the OP_LOAD_LOCAL instruction, which loads a local variable
@ -31,12 +31,12 @@
*/ */
case OP_LOAD_LOCAL: { case OP_LOAD_LOCAL: {
int slot = inst.operand; int slot = inst.operand;
if (slot < 0 || slot >= MAX_FRAME_LOCALS) { if (slot < 0 || slot >= MAX_FRAME_LOCALS) {
fprintf(stderr, "Runtime error: local slot out of range\n"); fprintf(stderr, "Runtime error: local slot out of range\n");
exit(1); exit(1);
} }
Value val = copy_value(&f->locals[slot]); Value val = copy_value(&f->locals[slot]);
push_value(vm, val); push_value(vm, val);
break; break;
} }

View file

@ -8,7 +8,7 @@
*/ */
/** /**
* @file nop.c * @file nop.c
* @brief Implements the OP_NOP opcode for no operation in the VM. * @brief Implements the OP_NOP opcode for no operation in the VM.
* *
* This file handles the OP_NOP instruction, which performs no operation. * This file handles the OP_NOP instruction, which performs no operation.
@ -27,4 +27,4 @@
*/ */
case OP_NOP: case OP_NOP:
break; break;

View file

@ -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. * @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. * This file handles the OP_POP instruction, which removes the top value from the stack.
@ -29,11 +29,11 @@
*/ */
case OP_POP: { case OP_POP: {
if (vm->sp < 0) { if (vm->sp < 0) {
fprintf(stderr, "Runtime error: stack underflow for POP\n"); fprintf(stderr, "Runtime error: stack underflow for POP\n");
exit(1); exit(1);
} }
Value v = pop_value(vm); Value v = pop_value(vm);
free_value(v); free_value(v);
break; break;
} }

View file

@ -8,7 +8,7 @@
*/ */
/** /**
* @file return.c * @file return.c
* @brief Implements the OP_RETURN opcode for returning from a function in the VM. * @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 * This file handles the OP_RETURN instruction, which returns from the current function
@ -32,10 +32,12 @@
*/ */
case OP_RETURN: { case OP_RETURN: {
Value retv; Value retv;
if (vm->sp >= 0) retv = pop_value(vm); if (vm->sp >= 0)
else retv = make_nil(); retv = pop_value(vm);
vm_pop_frame(vm); else
push_value(vm, retv); retv = make_nil();
break; vm_pop_frame(vm);
push_value(vm, retv);
break;
} }

View file

@ -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. * @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 * This file handles the OP_STORE_GLOBAL instruction, which stores a value into a global variable
@ -31,16 +31,16 @@
*/ */
case OP_STORE_GLOBAL: { case OP_STORE_GLOBAL: {
int idx = inst.operand; int idx = inst.operand;
if (idx < 0 || idx >= MAX_GLOBALS) { if (idx < 0 || idx >= MAX_GLOBALS) {
fprintf(stderr, "Runtime error: global index out of range\n"); fprintf(stderr, "Runtime error: global index out of range\n");
exit(1); exit(1);
} }
Value v = pop_value(vm); Value v = pop_value(vm);
#ifdef FUN_DEBUG #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 #endif
free_value(vm->globals[idx]); free_value(vm->globals[idx]);
vm->globals[idx] = v; vm->globals[idx] = v;
break; break;
} }

View file

@ -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. * @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 * This file handles the OP_STORE_LOCAL instruction, which stores a value into a local variable
@ -31,14 +31,14 @@
*/ */
case OP_STORE_LOCAL: { case OP_STORE_LOCAL: {
int slot = inst.operand; int slot = inst.operand;
if (slot < 0 || slot >= MAX_FRAME_LOCALS) { if (slot < 0 || slot >= MAX_FRAME_LOCALS) {
fprintf(stderr, "Runtime error: local slot out of range\n"); fprintf(stderr, "Runtime error: local slot out of range\n");
exit(1); exit(1);
} }
Value v = pop_value(vm); Value v = pop_value(vm);
/* free previous local then move v into it */ /* free previous local then move v into it */
free_value(f->locals[slot]); free_value(f->locals[slot]);
f->locals[slot] = v; f->locals[slot] = v;
break; break;
} }

View file

@ -8,7 +8,7 @@
*/ */
/** /**
* @file swap.c * @file swap.c
* @brief Implements the OP_SWAP opcode for stack manipulation in the VM. * @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 * This file handles the OP_SWAP instruction, which swaps the top two values
@ -26,13 +26,13 @@
*/ */
case OP_SWAP: { case OP_SWAP: {
if (vm->sp < 1) { if (vm->sp < 1) {
fprintf(stderr, "Runtime error: stack underflow for SWAP\n"); fprintf(stderr, "Runtime error: stack underflow for SWAP\n");
exit(1); exit(1);
} }
Value a = vm->stack[vm->sp]; Value a = vm->stack[vm->sp];
Value b = vm->stack[vm->sp - 1]; Value b = vm->stack[vm->sp - 1];
vm->stack[vm->sp] = b; vm->stack[vm->sp] = b;
vm->stack[vm->sp - 1] = a; vm->stack[vm->sp - 1] = a;
break; break;
} }

View file

@ -8,26 +8,26 @@
*/ */
case OP_THROW: { case OP_THROW: {
Value err = pop_value(vm); Value err = pop_value(vm);
/* if there is a handler in this frame, jump to it and push err for catch */ /* if there is a handler in this frame, jump to it and push err for catch */
if (f->try_sp >= 0) { if (f->try_sp >= 0) {
int try_idx = f->try_stack[f->try_sp--]; int try_idx = f->try_stack[f->try_sp--];
int target = f->fn->instructions[try_idx].operand; int target = f->fn->instructions[try_idx].operand;
/* push error for catch block */ /* push error for catch block */
push_value(vm, err); /* transfer ownership to stack */ push_value(vm, err); /* transfer ownership to stack */
f->ip = target; 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, "<error>\n");
}
free_value(err);
/* clear frames to stop execution */
vm->fp = -1;
break; 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, "<error>\n");
}
free_value(err);
/* clear frames to stop execution */
vm->fp = -1;
break;
} }

View file

@ -8,6 +8,6 @@
*/ */
case OP_TRY_POP: { case OP_TRY_POP: {
if (f->try_sp >= 0) f->try_sp--; if (f->try_sp >= 0) f->try_sp--;
break; break;
} }

View file

@ -8,11 +8,11 @@
*/ */
case OP_TRY_PUSH: { case OP_TRY_PUSH: {
/* push index of this TRY instruction; handler ip is in its operand (may be patched later) */ /* 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) { if (f->try_sp >= (int)(sizeof(f->try_stack) / sizeof(f->try_stack[0])) - 1) {
fprintf(stderr, "Runtime error: try depth exceeded\n"); fprintf(stderr, "Runtime error: try depth exceeded\n");
exit(1); exit(1);
} }
f->try_stack[++f->try_sp] = f->ip - 1; /* index of TRY_PUSH instruction */ f->try_stack[++f->try_sp] = f->ip - 1; /* index of TRY_PUSH instruction */
break; break;
} }

View file

@ -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/ * https://fun-lang.xyz/
* *
* Copyright 2026 Johannes Findeisen <you@hanez.org> * Copyright 2026 Johannes Findeisen <you@hanez.org>
@ -21,8 +21,8 @@ extern "C" {
} }
extern "C" int fun_op_cpp_add(VM *vm) { extern "C" int fun_op_cpp_add(VM *vm) {
int64_t a = vm_pop_i64(vm); int64_t a = vm_pop_i64(vm);
int64_t b = vm_pop_i64(vm); int64_t b = vm_pop_i64(vm);
vm_push_i64(vm, a + b); vm_push_i64(vm, a + b);
return 0; // success return 0; // success
} }

View file

@ -3,45 +3,53 @@
*/ */
case OP_CURL_DOWNLOAD: { case OP_CURL_DOWNLOAD: {
#ifdef FUN_WITH_CURL #ifdef FUN_WITH_CURL
Value vpath = pop_value(vm); Value vpath = pop_value(vm);
Value vurl = pop_value(vm); Value vurl = pop_value(vm);
char *url = value_to_string_alloc(&vurl); char *url = value_to_string_alloc(&vurl);
char *path = value_to_string_alloc(&vpath); char *path = value_to_string_alloc(&vpath);
free_value(vurl); free_value(vurl);
free_value(vpath); free_value(vpath);
if (!url || !path) { if (!url || !path) {
if (url) free(url); if (url) free(url);
if (path) free(path); 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);
push_value(vm, make_int(0)); push_value(vm, make_int(0));
#endif
break; 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;
} }

View file

@ -3,31 +3,39 @@
*/ */
case OP_CURL_GET: { case OP_CURL_GET: {
#ifdef FUN_WITH_CURL #ifdef FUN_WITH_CURL
Value vurl = pop_value(vm); Value vurl = pop_value(vm);
char *url = value_to_string_alloc(&vurl); char *url = value_to_string_alloc(&vurl);
free_value(vurl); free_value(vurl);
if (!url) { push_value(vm, make_string("")); break; } if (!url) {
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("")); push_value(vm, make_string(""));
#endif
break; 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;
} }

View file

@ -3,39 +3,50 @@
*/ */
case OP_CURL_POST: { case OP_CURL_POST: {
#ifdef FUN_WITH_CURL #ifdef FUN_WITH_CURL
Value vbody = pop_value(vm); Value vbody = pop_value(vm);
Value vurl = pop_value(vm); Value vurl = pop_value(vm);
char *url = value_to_string_alloc(&vurl); char *url = value_to_string_alloc(&vurl);
char *body = value_to_string_alloc(&vbody); char *body = value_to_string_alloc(&vbody);
free_value(vurl); free_value(vurl);
free_value(vbody); free_value(vbody);
if (!url) { if (body) free(body); push_value(vm, make_string("")); break; } if (!url) {
if (!body) body = strdup(""); if (body) free(body);
FunCurlBuf buf = { NULL, 0 }; push_value(vm, make_string(""));
CURL *h = curl_easy_init(); break;
if (!h) { free(url); free(body); push_value(vm, make_string("")); break; } }
curl_easy_setopt(h, CURLOPT_URL, url); if (!body) body = strdup("");
curl_easy_setopt(h, CURLOPT_FOLLOWLOCATION, 1L); FunCurlBuf buf = {NULL, 0};
curl_easy_setopt(h, CURLOPT_POST, 1L); CURL *h = curl_easy_init();
curl_easy_setopt(h, CURLOPT_POSTFIELDS, body); if (!h) {
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(url);
free(body); 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("")); push_value(vm, make_string(""));
#endif
break; 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;
} }

View file

@ -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/ * https://fun-lang.xyz/
* *
* Copyright 2025 Johannes Findeisen <you@hanez.org> * Copyright 2025 Johannes Findeisen <you@hanez.org>
@ -7,25 +7,25 @@
* https://opensource.org/license/apache-2-0 * https://opensource.org/license/apache-2-0
*/ */
/** /**
* Implements OP_ECHO: print top-of-stack value without trailing newline. * 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, * 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. * so the CLI can render echo output together with following print output.
*/ */
case OP_ECHO: { case OP_ECHO: {
Value v = pop_value(vm); Value v = pop_value(vm);
Value snap = deep_copy_value(&v); Value snap = deep_copy_value(&v);
free_value(v); free_value(v);
if (vm->output_count < OUTPUT_SIZE) { if (vm->output_count < OUTPUT_SIZE) {
int idx = vm->output_count; int idx = vm->output_count;
vm->output[idx] = snap; vm->output[idx] = snap;
vm->output_is_partial[idx] = 1; // ECHO does not end the line vm->output_is_partial[idx] = 1; // ECHO does not end the line
vm->output_count++; vm->output_count++;
} else { } else {
free_value(snap); free_value(snap);
fprintf(stderr, "Runtime error: output buffer overflow\n"); fprintf(stderr, "Runtime error: output buffer overflow\n");
exit(1); exit(1);
} }
break; break;
} }

View file

@ -12,11 +12,11 @@
/* OP_INI_FREE: pops handle; pushes 1/0 */ /* OP_INI_FREE: pops handle; pushes 1/0 */
#ifdef FUN_WITH_INI #ifdef FUN_WITH_INI
case OP_INI_FREE: { case OP_INI_FREE: {
Value vh = pop_value(vm); Value vh = pop_value(vm);
int h = (vh.type == VAL_INT) ? (int)vh.i : 0; int h = (vh.type == VAL_INT) ? (int)vh.i : 0;
free_value(vh); free_value(vh);
int ok = ini_free_handle(h); int ok = ini_free_handle(h);
push_value(vm, make_int(ok)); push_value(vm, make_int(ok));
break; break;
} }
#endif #endif

View file

@ -12,51 +12,67 @@
/* OP_INI_GET_BOOL */ /* OP_INI_GET_BOOL */
#ifdef FUN_WITH_INI #ifdef FUN_WITH_INI
case OP_INI_GET_BOOL: { case OP_INI_GET_BOOL: {
Value vdef = pop_value(vm); Value vdef = pop_value(vm);
Value vkey = pop_value(vm); Value vkey = pop_value(vm);
Value vsec = pop_value(vm); Value vsec = pop_value(vm);
Value vh = pop_value(vm); Value vh = pop_value(vm);
int def = (vdef.type==VAL_INT||vdef.type==VAL_BOOL) ? (int)vdef.i : 0; 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 *key = (vkey.type == VAL_STRING) ? vkey.s : NULL;
const char *sec = (vsec.type==VAL_STRING) ? vsec.s : NULL; const char *sec = (vsec.type == VAL_STRING) ? vsec.s : NULL;
int h = (vh.type==VAL_INT) ? (int)vh.i : 0; int h = (vh.type == VAL_INT) ? (int)vh.i : 0;
dictionary *d = ini_get(h); dictionary *d = ini_get(h);
int outb = def; int outb = def;
if (d && sec && key) { if (d && sec && key) {
char full[1024]; char alt[1024]; char full[1024];
ini_make_full_key(full, sizeof(full), sec, key); char alt[1024];
memcpy(alt, full, sizeof(alt)); ini_make_full_key(full, sizeof(full), sec, key);
for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) { if (alt[i] == ':') { alt[i] = '.'; break; } } memcpy(alt, full, sizeof(alt));
const char *s = iniparser_getstring(d, full, NULL); for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) {
if (!s) s = iniparser_getstring(d, alt, NULL); if (alt[i] == ':') {
if (s) { alt[i] = '.';
/* normalize and parse boolean */ break;
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); const char *s = iniparser_getstring(d, full, NULL);
push_value(vm, make_int(outb ? 1 : 0)); if (!s) s = iniparser_getstring(d, alt, NULL);
break; 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 #endif

View file

@ -12,41 +12,55 @@
/* OP_INI_GET_DOUBLE */ /* OP_INI_GET_DOUBLE */
#ifdef FUN_WITH_INI #ifdef FUN_WITH_INI
case OP_INI_GET_DOUBLE: { case OP_INI_GET_DOUBLE: {
Value vdef = pop_value(vm); Value vdef = pop_value(vm);
Value vkey = pop_value(vm); Value vkey = pop_value(vm);
Value vsec = pop_value(vm); Value vsec = pop_value(vm);
Value vh = 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); 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 *key = (vkey.type == VAL_STRING) ? vkey.s : NULL;
const char *sec = (vsec.type==VAL_STRING) ? vsec.s : NULL; const char *sec = (vsec.type == VAL_STRING) ? vsec.s : NULL;
int h = (vh.type==VAL_INT) ? (int)vh.i : 0; int h = (vh.type == VAL_INT) ? (int)vh.i : 0;
dictionary *d = ini_get(h); dictionary *d = ini_get(h);
double outd = def; double outd = def;
if (d && sec && key) { if (d && sec && key) {
char full[1024]; char alt[1024]; char full[1024];
ini_make_full_key(full, sizeof(full), sec, key); char alt[1024];
memcpy(alt, full, sizeof(alt)); ini_make_full_key(full, sizeof(full), sec, key);
for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) { if (alt[i] == ':') { alt[i] = '.'; break; } } memcpy(alt, full, sizeof(alt));
const char *s = iniparser_getstring(d, full, NULL); for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) {
if (!s) s = iniparser_getstring(d, alt, NULL); if (alt[i] == ':') {
if (s) { alt[i] = '.';
char buf[256]; break;
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); const char *s = iniparser_getstring(d, full, NULL);
push_value(vm, make_float(outd)); if (!s) s = iniparser_getstring(d, alt, NULL);
break; 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 #endif

View file

@ -12,43 +12,57 @@
/* OP_INI_GET_INT */ /* OP_INI_GET_INT */
#ifdef FUN_WITH_INI #ifdef FUN_WITH_INI
case OP_INI_GET_INT: { case OP_INI_GET_INT: {
Value vdef = pop_value(vm); Value vdef = pop_value(vm);
Value vkey = pop_value(vm); Value vkey = pop_value(vm);
Value vsec = pop_value(vm); Value vsec = pop_value(vm);
Value vh = pop_value(vm); Value vh = pop_value(vm);
int def = (vdef.type==VAL_INT) ? (int)vdef.i : 0; int def = (vdef.type == VAL_INT) ? (int)vdef.i : 0;
const char *key = (vkey.type==VAL_STRING) ? vkey.s : NULL; const char *key = (vkey.type == VAL_STRING) ? vkey.s : NULL;
const char *sec = (vsec.type==VAL_STRING) ? vsec.s : NULL; const char *sec = (vsec.type == VAL_STRING) ? vsec.s : NULL;
int h = (vh.type==VAL_INT) ? (int)vh.i : 0; int h = (vh.type == VAL_INT) ? (int)vh.i : 0;
dictionary *d = ini_get(h); dictionary *d = ini_get(h);
int outi = def; int outi = def;
if (d && sec && key) { if (d && sec && key) {
char full[1024]; char alt[1024]; char full[1024];
ini_make_full_key(full, sizeof(full), sec, key); char alt[1024];
memcpy(alt, full, sizeof(alt)); ini_make_full_key(full, sizeof(full), sec, key);
for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) { if (alt[i] == ':') { alt[i] = '.'; break; } } memcpy(alt, full, sizeof(alt));
const char *s = iniparser_getstring(d, full, NULL); for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) {
if (!s) s = iniparser_getstring(d, alt, NULL); if (alt[i] == ':') {
if (s) { alt[i] = '.';
/* strip optional quotes and parse */ break;
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); const char *s = iniparser_getstring(d, full, NULL);
push_value(vm, make_int(outi)); if (!s) s = iniparser_getstring(d, alt, NULL);
break; 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 #endif

View file

@ -12,34 +12,42 @@
/* OP_INI_GET_STRING */ /* OP_INI_GET_STRING */
#ifdef FUN_WITH_INI #ifdef FUN_WITH_INI
case OP_INI_GET_STRING: { case OP_INI_GET_STRING: {
Value vdef = pop_value(vm); Value vdef = pop_value(vm);
Value vkey = pop_value(vm); Value vkey = pop_value(vm);
Value vsec = pop_value(vm); Value vsec = pop_value(vm);
Value vh = pop_value(vm); Value vh = pop_value(vm);
const char *def = (vdef.type==VAL_STRING && vdef.s) ? vdef.s : ""; const char *def = (vdef.type == VAL_STRING && vdef.s) ? vdef.s : "";
const char *key = (vkey.type==VAL_STRING) ? vkey.s : NULL; const char *key = (vkey.type == VAL_STRING) ? vkey.s : NULL;
const char *sec = (vsec.type==VAL_STRING) ? vsec.s : NULL; const char *sec = (vsec.type == VAL_STRING) ? vsec.s : NULL;
int h = (vh.type==VAL_INT) ? (int)vh.i : 0; int h = (vh.type == VAL_INT) ? (int)vh.i : 0;
dictionary *d = ini_get(h); dictionary *d = ini_get(h);
const char *res = def; const char *res = def;
if (d && sec && key) { if (d && sec && key) {
char full[1024]; char alt[1024]; char full[1024];
ini_make_full_key(full, sizeof(full), sec, key); char alt[1024];
/* Build alternate with dot separator for robustness */ ini_make_full_key(full, sizeof(full), sec, key);
ini_make_full_key(alt, sizeof(alt), sec, key); /* Build alternate with dot separator for robustness */
size_t flen = strlen(full); ini_make_full_key(alt, sizeof(alt), sec, key);
if (flen < sizeof(alt) && flen > 0) { /* create dot version in alt */ size_t flen = strlen(full);
memcpy(alt, full, flen + 1); if (flen < sizeof(alt) && flen > 0) { /* create dot version in alt */
for (size_t i = 0; i < flen; ++i) if (alt[i] == ':') { alt[i] = '.'; break; } 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); const char *s = iniparser_getstring(d, full, def);
push_value(vm, make_string(res)); if (s == def) { /* not found, try alternate dot form */
break; 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 #endif

View file

@ -9,55 +9,59 @@
#ifdef FUN_WITH_INI #ifdef FUN_WITH_INI
#if defined(__has_include) #if defined(__has_include)
# if __has_include(<iniparser/iniparser.h>) #if __has_include(<iniparser/iniparser.h>)
# include <iniparser/iniparser.h> #include <iniparser/dictionary.h>
# include <iniparser/dictionary.h> #include <iniparser/iniparser.h>
# elif __has_include(<iniparser.h>) #elif __has_include(<iniparser.h>)
# include <iniparser.h> #include <dictionary.h>
# include <dictionary.h> #include <iniparser.h>
# else
# error "iniparser headers not found"
# endif
#else #else
# include <iniparser/iniparser.h> #error "iniparser headers not found"
# include <iniparser/dictionary.h> #endif
#else
#include <iniparser/dictionary.h>
#include <iniparser/iniparser.h>
#endif #endif
#include <ctype.h> #include <ctype.h>
#include <string.h>
#include <stdio.h> #include <stdio.h>
#include <string.h>
#include "handles.h" #include "handles.h"
IniSlot g_ini[64]; IniSlot g_ini[64];
int ini_alloc_handle(dictionary *d) { int ini_alloc_handle(dictionary *d) {
if (!d) return 0; if (!d) return 0;
for (int i = 1; i < (int)(sizeof(g_ini)/sizeof(g_ini[0])); ++i) { 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 (!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) { 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; if (h > 0 && h < (int)(sizeof(g_ini) / sizeof(g_ini[0])) && g_ini[h].in_use) return g_ini[h].dict;
return NULL; return NULL;
} }
int ini_free_handle(int h) { 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 (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); if (g_ini[h].dict) iniparser_freedict(g_ini[h].dict);
g_ini[h].dict = NULL; g_ini[h].dict = NULL;
g_ini[h].in_use = 0; g_ini[h].in_use = 0;
return 1; return 1;
} }
void ini_make_full_key(char *buf, size_t cap, const char *sec, const char *key) { void ini_make_full_key(char *buf, size_t cap, const char *sec, const char *key) {
if (!buf || cap == 0) return; if (!buf || cap == 0) return;
if (!sec) sec = ""; if (!sec) sec = "";
if (!key) key = ""; if (!key) key = "";
/* iniparser expects section:key; lookup is case-insensitive internally */ /* iniparser expects section:key; lookup is case-insensitive internally */
snprintf(buf, cap, "%s:%s", sec, key); snprintf(buf, cap, "%s:%s", sec, key);
} }
#endif /* FUN_WITH_INI */ #endif /* FUN_WITH_INI */

View file

@ -14,29 +14,32 @@
#ifdef FUN_WITH_INI #ifdef FUN_WITH_INI
#if defined(__has_include) #if defined(__has_include)
# if __has_include(<iniparser/iniparser.h>) #if __has_include(<iniparser/iniparser.h>)
# include <iniparser/iniparser.h> #include <iniparser/dictionary.h>
# include <iniparser/dictionary.h> #include <iniparser/iniparser.h>
# elif __has_include(<iniparser.h>) #elif __has_include(<iniparser.h>)
# include <iniparser.h> #include <dictionary.h>
# include <dictionary.h> #include <iniparser.h>
# else
# error "iniparser headers not found"
# endif
#else #else
# include <iniparser/iniparser.h> #error "iniparser headers not found"
# include <iniparser/dictionary.h> #endif
#else
#include <iniparser/dictionary.h>
#include <iniparser/iniparser.h>
#endif #endif
#include <stddef.h> #include <stddef.h>
typedef struct { dictionary *dict; int in_use; } IniSlot; typedef struct {
dictionary *dict;
int in_use;
} IniSlot;
/* Single global registry (defined in handles.c) */ /* Single global registry (defined in handles.c) */
extern IniSlot g_ini[64]; extern IniSlot g_ini[64];
/* Registry API (implemented in handles.c) */ /* Registry API (implemented in handles.c) */
int ini_alloc_handle(dictionary *d); int ini_alloc_handle(dictionary *d);
dictionary* ini_get(int h); dictionary *ini_get(int h);
int ini_free_handle(int h); int ini_free_handle(int h);
/* Helper to build section:key string safely into provided buffer (implemented in handles.c) */ /* Helper to build section:key string safely into provided buffer (implemented in handles.c) */

View file

@ -12,18 +12,20 @@
/* OP_INI_LOAD: pops path string; pushes handle (>0) or 0 */ /* OP_INI_LOAD: pops path string; pushes handle (>0) or 0 */
#ifdef FUN_WITH_INI #ifdef FUN_WITH_INI
case OP_INI_LOAD: { case OP_INI_LOAD: {
Value vpath = pop_value(vm); Value vpath = pop_value(vm);
const char *path = (vpath.type == VAL_STRING && vpath.s) ? vpath.s : NULL; const char *path = (vpath.type == VAL_STRING && vpath.s) ? vpath.s : NULL;
int h = 0; int h = 0;
if (path) { if (path) {
dictionary *d = iniparser_load(path); dictionary *d = iniparser_load(path);
if (d) { if (d) {
h = ini_alloc_handle(d); h = ini_alloc_handle(d);
if (!h) { iniparser_freedict(d); } if (!h) {
} iniparser_freedict(d);
}
} }
free_value(vpath); }
push_value(vm, make_int(h)); free_value(vpath);
break; push_value(vm, make_int(h));
break;
} }
#endif #endif

View file

@ -12,17 +12,22 @@
/* OP_INI_SAVE */ /* OP_INI_SAVE */
#ifdef FUN_WITH_INI #ifdef FUN_WITH_INI
case OP_INI_SAVE: { case OP_INI_SAVE: {
Value vpath = pop_value(vm); Value vpath = pop_value(vm);
Value vh = pop_value(vm); Value vh = pop_value(vm);
const char *path = (vpath.type==VAL_STRING)?vpath.s:NULL; const char *path = (vpath.type == VAL_STRING) ? vpath.s : NULL;
dictionary *d = ini_get((vh.type==VAL_INT)?(int)vh.i:0); dictionary *d = ini_get((vh.type == VAL_INT) ? (int)vh.i : 0);
int ok = 0; int ok = 0;
if (d && path) { if (d && path) {
FILE *f = fopen(path, "w"); FILE *f = fopen(path, "w");
if (f) { iniparser_dump_ini(d, f); fclose(f); ok = 1; } if (f) {
iniparser_dump_ini(d, f);
fclose(f);
ok = 1;
} }
free_value(vpath); free_value(vh); }
push_value(vm, make_int(ok)); free_value(vpath);
break; free_value(vh);
push_value(vm, make_int(ok));
break;
} }
#endif #endif

Some files were not shown because too many files have changed in this diff Show more