diff --git a/CMakeLists.txt b/CMakeLists.txt index dd7dbf9..1f9fe34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.37.32 LANGUAGES C) +project(fun VERSION 0.37.44 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) @@ -20,7 +20,7 @@ if(NOT DEFINED DEFAULT_LIB_DIR OR DEFAULT_LIB_DIR STREQUAL "") else() set(_FUN_DEFAULT_LIB_DIR "/usr/share/fun/lib") endif() - set(DEFAULT_LIB_DIR "${_FUN_DEFAULT_LIB_DIR}" CACHE PATH "Default library directory for Fun stdlib (override with -DDEFAULT_LIB_DIR=...)" FORCE) + set(DEFAULT_LIB_DIR "${_FUN_DEFAULT_LIB_DIR}" CACHE PATH "Default library directory for Fun stdlib (override with -DDEFAULT_LIB_DIR=...)" FORCE) else() set(DEFAULT_LIB_DIR "${DEFAULT_LIB_DIR}" CACHE PATH "Default library directory for Fun stdlib (override with -DDEFAULT_LIB_DIR=...)" FORCE) endif() @@ -30,27 +30,27 @@ endif() option(FUN_USE_MUSL "Use musl libc toolchain when available (Linux only)" OFF) if(FUN_USE_MUSL AND UNIX AND NOT APPLE) - # Try to find a musl toolchain wrapper - find_program(_FUN_MUSL_CC NAMES musl-gcc musl-clang) - if(_FUN_MUSL_CC) - message(STATUS "FUN_USE_MUSL=ON: using musl toolchain: ${_FUN_MUSL_CC}") - # Force C compiler to musl wrapper before project() so the whole toolchain is configured accordingly - set(CMAKE_C_COMPILER "${_FUN_MUSL_CC}" CACHE FILEPATH "C compiler" FORCE) - set(FUN_LIBC "musl" CACHE STRING "Selected C library") - else() - message(WARNING "FUN_USE_MUSL=ON but no musl toolchain (musl-gcc or musl-clang) found. Falling back to default compiler (likely glibc).") - set(FUN_LIBC "glibc" CACHE STRING "Selected C library") - endif() -else() - # Default remains the system toolchain (typically glibc on Linux) + # Try to find a musl toolchain wrapper + find_program(_FUN_MUSL_CC NAMES musl-gcc musl-clang) + if(_FUN_MUSL_CC) + message(STATUS "FUN_USE_MUSL=ON: using musl toolchain: ${_FUN_MUSL_CC}") + # Force C compiler to musl wrapper before project() so the whole toolchain is configured accordingly + set(CMAKE_C_COMPILER "${_FUN_MUSL_CC}" CACHE FILEPATH "C compiler" FORCE) + set(FUN_LIBC "musl" CACHE STRING "Selected C library") + else() + message(WARNING "FUN_USE_MUSL=ON but no musl toolchain (musl-gcc or musl-clang) found. Falling back to default compiler (likely glibc).") set(FUN_LIBC "glibc" CACHE STRING "Selected C library") + endif() +else() + # Default remains the system toolchain (typically glibc on Linux) + set(FUN_LIBC "glibc" CACHE STRING "Selected C library") endif() # Expose a preprocessor macro indicating selected C library if(FUN_LIBC STREQUAL "musl") - add_definitions(-DFUN_LIBC_MUSL) + add_definitions(-DFUN_LIBC_MUSL) else() - add_definitions(-DFUN_LIBC_GLIBC) + add_definitions(-DFUN_LIBC_GLIBC) endif() # Optional: build statically linked executables @@ -82,8 +82,8 @@ if(FUN_LINK_STATIC) # On MSVC, prefer the static runtime if(MSVC) - foreach(flag_var CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE - CMAKE_C_FLAGS_RELWITHDEBINFO CMAKE_C_FLAGS_MINSIZEREL) + foreach(flag_var CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE + CMAKE_C_FLAGS_RELWITHDEBINFO CMAKE_C_FLAGS_MINSIZEREL) if(DEFINED ${flag_var}) string(REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") endif() @@ -151,7 +151,7 @@ if(FUN_WITH_TCLTK) endif() # Ensure trailing slash if(NOT DEFAULT_LIB_DIR MATCHES "/$") - set(DEFAULT_LIB_DIR "${DEFAULT_LIB_DIR}/") + set(DEFAULT_LIB_DIR "${DEFAULT_LIB_DIR}/") endif() # Optional SQLite support @@ -184,14 +184,14 @@ option(FUN_WITH_PCSC "Enable PCSC (pcsclite) support" OFF) set(PCSC_LINK_LIBS "") set(PCSC_INCLUDE_DIRS "") if(FUN_WITH_PCSC) - message(STATUS "Building with PCSC support") - add_definitions(-DFUN_WITH_PCSC) - if(APPLE) - list(APPEND PCSC_LINK_LIBS "-framework PCSC") - else() - list(APPEND PCSC_INCLUDE_DIRS "/usr/include/PCSC") - list(APPEND PCSC_LINK_LIBS pcsclite) - endif() + message(STATUS "Building with PCSC support") + add_definitions(-DFUN_WITH_PCSC) + if(APPLE) + list(APPEND PCSC_LINK_LIBS "-framework PCSC") + else() + list(APPEND PCSC_INCLUDE_DIRS "/usr/include/PCSC") + list(APPEND PCSC_LINK_LIBS pcsclite) + endif() endif() # Optional JSON (json-c) support @@ -269,9 +269,9 @@ if(FUN_WITH_XML2) list(APPEND LIBXML2_INCLUDE_DIRS "/usr/include/libxml2") include_directories(${LIBXML2_INCLUDE_DIRS}) endif() - else() - message(FATAL_ERROR "libxml2 not found. Install libxml2 (dev headers) or disable FUN_WITH_XML2.") - endif() + else() + message(FATAL_ERROR "libxml2 not found. Install libxml2 (dev headers) or disable FUN_WITH_XML2.") + endif() endif() # As a robust fallback, add standard system include path for libxml2 if present if(EXISTS "/usr/include/libxml2") @@ -365,21 +365,21 @@ endif() # Core VM/library sources add_library(fun_core - src/bytecode.c - src/parser.c - src/value.c - src/vm.c + src/bytecode.c + src/parser.c + src/value.c + src/vm.c ) target_include_directories(fun_core PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/src ) # Apply options to core if(FUN_DEBUG) - message(STATUS "FUN_DEBUG enabled: building with verbose debug logging") - target_compile_definitions(fun_core PUBLIC FUN_VERSION="${PROJECT_VERSION}") - target_compile_definitions(fun_core PUBLIC FUN_DEBUG=1) + message(STATUS "FUN_DEBUG enabled: building with verbose debug logging") + target_compile_definitions(fun_core PUBLIC FUN_VERSION="${PROJECT_VERSION}") + target_compile_definitions(fun_core PUBLIC FUN_DEBUG=1) endif() # Provide default stdlib directory to the runtime @@ -468,10 +468,16 @@ if(Threads_FOUND) target_link_libraries(fun_core PUBLIC Threads::Threads) endif() +# Link libm for C99 math functions if available +find_library(M_LIB m) +if(M_LIB) + target_link_libraries(fun_core PUBLIC ${M_LIB}) +endif() + # Interpreter /usr/bin/fun option(FUN_WITH_REPL "Enable interactive REPL in the fun CLI" OFF) add_executable(fun - src/fun.c + src/fun.c ) # Provide version string to the CLI if(FUN_WITH_REPL) @@ -483,12 +489,12 @@ target_link_libraries(fun PRIVATE fun_core) # Internal test programs add_executable(fun_test - src/fun_test.c + src/fun_test.c ) target_link_libraries(fun_test PRIVATE fun_core) add_executable(test_opcodes - src/test_opcodes.c + src/test_opcodes.c ) target_link_libraries(test_opcodes PRIVATE fun_core) @@ -511,11 +517,11 @@ set(FUN_RUN_SCRIPT "" CACHE STRING "Script to run with the 'run' target, e.g. -D if(FUN_WITH_REPL) add_custom_target(repl - COMMAND $ - DEPENDS fun - USES_TERMINAL - COMMENT "Run Fun REPL" - ) + COMMAND $ + DEPENDS fun + USES_TERMINAL + COMMENT "Run Fun REPL" +) endif() add_custom_target(run @@ -555,14 +561,14 @@ add_custom_target(ops-quiet ) add_custom_target(examples - COMMAND ${CMAKE_COMMAND} -E env FUN_LIB_DIR="${FUN_LIB}" ${CMAKE_SOURCE_DIR}/scripts/run_examples.sh + COMMAND ${CMAKE_COMMAND} -E env FUN_LIB_DIR="${FUN_LIB}" FUN_WITH_INI=$,1,0> ${CMAKE_SOURCE_DIR}/scripts/run_examples.sh DEPENDS fun USES_TERMINAL COMMENT "Build and run all examples" ) add_custom_target(run-examples - COMMAND ${CMAKE_COMMAND} -E env FUN_LIB_DIR="${FUN_LIB}" ${CMAKE_SOURCE_DIR}/scripts/run_examples.sh + COMMAND ${CMAKE_COMMAND} -E env FUN_LIB_DIR="${FUN_LIB}" FUN_WITH_INI=$,1,0> ${CMAKE_SOURCE_DIR}/scripts/run_examples.sh DEPENDS fun USES_TERMINAL COMMENT "Build and run all examples" @@ -570,20 +576,20 @@ add_custom_target(run-examples # Clean and distclean add_custom_target(fun_clean - COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target clean - COMMENT "Clean build outputs (objects, binaries) in the build directory" + COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target clean + COMMENT "Clean build outputs (objects, binaries) in the build directory" ) add_custom_target(distclean - COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target clean - COMMAND ${CMAKE_COMMAND} -E rm -rf - ${CMAKE_BINARY_DIR}/CMakeCache.txt - ${CMAKE_BINARY_DIR}/CMakeFiles - ${CMAKE_BINARY_DIR}/cmake_install.cmake - ${CMAKE_BINARY_DIR}/install_manifest.txt - ${CMAKE_BINARY_DIR}/Makefile - ${CMAKE_BINARY_DIR}/*.ninja - ${CMAKE_BINARY_DIR}/.ninja_* - COMMENT "Remove build outputs and CMake-generated files (reconfigure needed)" + COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target clean + COMMAND ${CMAKE_COMMAND} -E rm -rf + ${CMAKE_BINARY_DIR}/CMakeCache.txt + ${CMAKE_BINARY_DIR}/CMakeFiles + ${CMAKE_BINARY_DIR}/cmake_install.cmake + ${CMAKE_BINARY_DIR}/install_manifest.txt + ${CMAKE_BINARY_DIR}/Makefile + ${CMAKE_BINARY_DIR}/*.ninja + ${CMAKE_BINARY_DIR}/.ninja_* + COMMENT "Remove build outputs and CMake-generated files (reconfigure needed)" ) # Robust uninstall support as a function (uses install_manifest*.txt) @@ -615,14 +621,14 @@ list(APPEND _candidates set(_manifest_file \"\") foreach(_cand IN LISTS _candidates) if(EXISTS \"\${_cand}\") - set(_manifest_file \"\${_cand}\") - break() + set(_manifest_file \"\${_cand}\") + break() endif() endforeach() if(NOT _manifest_file) message(FATAL_ERROR - \"Cannot find install manifest. Tried: \${_candidates}. + \"Cannot find install manifest. Tried: \${_candidates}. If you installed from a different build directory, run uninstall from that build directory (where install_manifest*.txt resides).\") endif() @@ -632,21 +638,21 @@ set(_removed 0) set(_dirs \"\") foreach(_file IN LISTS _files) if(_file STREQUAL \"\") - continue() + continue() endif() if(EXISTS \"\${_file}\" OR IS_SYMLINK \"\${_file}\") - message(STATUS \"Uninstalling: \${_file}\") - file(REMOVE \"\${_file}\") - # If the file still exists after attempting to remove it, fail with an explicit error - if(EXISTS \"\${_file}\" OR IS_SYMLINK \"\${_file}\") - message(FATAL_ERROR \"Failed to remove: \${_file}. Try running the uninstall target with sudo if it is a system install.\") - endif() - # Track the containing directory for potential cleanup - get_filename_component(_dir \"\${_file}\" DIRECTORY) - list(APPEND _dirs \"\${_dir}\") - math(EXPR _removed \"\${_removed}+1\") + message(STATUS \"Uninstalling: \${_file}\") + file(REMOVE \"\${_file}\") + # If the file still exists after attempting to remove it, fail with an explicit error + if(EXISTS \"\${_file}\" OR IS_SYMLINK \"\${_file}\") + message(FATAL_ERROR \"Failed to remove: \${_file}. Try running the uninstall target with sudo if it is a system install.\") + endif() + # Track the containing directory for potential cleanup + get_filename_component(_dir \"\${_file}\" DIRECTORY) + list(APPEND _dirs \"\${_dir}\") + math(EXPR _removed \"\${_removed}+1\") else() - message(STATUS \"Skipping (not found): \${_file}\") + message(STATUS \"Skipping (not found): \${_file}\") endif() endforeach() @@ -659,14 +665,14 @@ set(_all_dirs \"\") foreach(_d IN LISTS _dirs) set(_p \"\${_d}\") while(NOT \"\${_p}\" STREQUAL \"\" AND NOT \"\${_p}\" STREQUAL \"/\") + list(APPEND _all_dirs \"\${_p}\") + get_filename_component(_p \"\${_p}\" DIRECTORY) + # Stop collecting parents once we reach a safe root or the filesystem root + list(FIND _safe_roots \"\${_p}\" _root_idx) + if(_root_idx GREATER -1) list(APPEND _all_dirs \"\${_p}\") - get_filename_component(_p \"\${_p}\" DIRECTORY) - # Stop collecting parents once we reach a safe root or the filesystem root - list(FIND _safe_roots \"\${_p}\" _root_idx) - if(_root_idx GREATER -1) - list(APPEND _all_dirs \"\${_p}\") - break() - endif() + break() + endif() endwhile() endforeach() @@ -679,38 +685,38 @@ set(_pass 0) while(TRUE) set(_pass_removed 0) foreach(_dir IN LISTS _all_dirs) - if(EXISTS \"\${_dir}\" AND IS_DIRECTORY \"\${_dir}\") - # Only act on directories within safe roots - set(_allowed FALSE) - foreach(_root IN LISTS _safe_roots) - if(\"\${_dir}\" MATCHES \"^\${_root}(/|\$)\") - set(_allowed TRUE) - break() - endif() - endforeach() - if(NOT _allowed) - # e.g. /usr/bin — do not remove - continue() - endif() - - # Remove only if empty (do not recurse unrelated content) - file(GLOB _dir_contents LIST_DIRECTORIES true \"\${_dir}/*\") - list(LENGTH _dir_contents _dir_len) - if(_dir_len EQUAL 0) - message(STATUS \"Removing empty directory: \${_dir}\") - file(REMOVE_RECURSE \"\${_dir}\") - if(IS_DIRECTORY \"\${_dir}\") - message(FATAL_ERROR \"Failed to remove directory: \${_dir}. Try running the uninstall target with sudo if it is a system install.\") - endif() - math(EXPR _removed_dirs \"\${_removed_dirs}+1\") - math(EXPR _pass_removed \"\${_pass_removed}+1\") - endif() + if(EXISTS \"\${_dir}\" AND IS_DIRECTORY \"\${_dir}\") + # Only act on directories within safe roots + set(_allowed FALSE) + foreach(_root IN LISTS _safe_roots) + if(\"\${_dir}\" MATCHES \"^\${_root}(/|\$)\") + set(_allowed TRUE) + break() endif() + endforeach() + if(NOT _allowed) + # e.g. /usr/bin — do not remove + continue() + endif() + + # Remove only if empty (do not recurse unrelated content) + file(GLOB _dir_contents LIST_DIRECTORIES true \"\${_dir}/*\") + list(LENGTH _dir_contents _dir_len) + if(_dir_len EQUAL 0) + message(STATUS \"Removing empty directory: \${_dir}\") + file(REMOVE_RECURSE \"\${_dir}\") + if(IS_DIRECTORY \"\${_dir}\") + message(FATAL_ERROR \"Failed to remove directory: \${_dir}. Try running the uninstall target with sudo if it is a system install.\") + endif() + math(EXPR _removed_dirs \"\${_removed_dirs}+1\") + math(EXPR _pass_removed \"\${_pass_removed}+1\") + endif() + endif() endforeach() math(EXPR _pass \"\${_pass}+1\") if(_pass_removed EQUAL 0 OR _pass GREATER 5) - break() + break() endif() endwhile() @@ -720,13 +726,13 @@ message(STATUS \"Uninstall finished. Removed \${_removed} files and \${_removed_ # Expose an 'uninstall' build target (run with sudo if system locations were used) add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} - -D MANIFEST="${CMAKE_BINARY_DIR}/install_manifest.txt" - -D CMAKE_BINARY_DIR="${CMAKE_BINARY_DIR}" - -D CMAKE_SOURCE_DIR="${CMAKE_SOURCE_DIR}" - -P "${_FUN_UNINSTALL_SCRIPT}" + -D MANIFEST="${CMAKE_BINARY_DIR}/install_manifest.txt" + -D CMAKE_BINARY_DIR="${CMAKE_BINARY_DIR}" + -D CMAKE_SOURCE_DIR="${CMAKE_SOURCE_DIR}" + -P "${_FUN_UNINSTALL_SCRIPT}" USES_TERMINAL COMMENT "Uninstall files installed by this project (use sudo if needed)" - ) +) endfunction() # Define the uninstall target @@ -735,21 +741,25 @@ fun_add_uninstall_target() # Install rules # Binary install(TARGETS fun - RUNTIME DESTINATION /usr/bin) + RUNTIME DESTINATION /usr/bin +) # Libs install(DIRECTORY lib/ - DESTINATION /usr/share/fun/lib - FILES_MATCHING PATTERN "*.fun") + DESTINATION /usr/share/fun/lib + FILES_MATCHING PATTERN "*.fun" +) # Optionally install example scripts option(FUN_INSTALL_EXAMPLES "Install example .fun scripts" ON) if(FUN_INSTALL_EXAMPLES) - install(DIRECTORY examples/ - DESTINATION /usr/share/fun/examples - FILES_MATCHING PATTERN "*.fun") + install(DIRECTORY examples/ + DESTINATION /usr/share/fun/examples + FILES_MATCHING PATTERN "*.fun" + ) endif() # - Docs install(FILES README.md LICENSE - DESTINATION /usr/share/doc/fun) + DESTINATION /usr/share/doc/fun +) diff --git a/examples/cli_argv_dump.fun b/examples/cli_argv_dump.fun new file mode 100755 index 0000000..0b2c2e9 --- /dev/null +++ b/examples/cli_argv_dump.fun @@ -0,0 +1,32 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-12-30 + */ + +// Demonstrates accessing raw script arguments via lib/cli.fun +// Usage: +// FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/cli_argv_dump.fun -f --force=42 foo bar + +#include + +args = argv() + +print(join(["FUN_ARGC=", env("FUN_ARGC")], "")) +print(join(["FUN_ARGS='", env("FUN_ARGS"), "'"], "")) +print("Raw argv array:") +print(args) + +/* Expected output: +FUN_ARGC=3 +FUN_ARGS='-f --force --force=42' +Raw argv array: +[-f, --force, --force=42 +*/ diff --git a/examples/cli_parse_example.fun b/examples/cli_parse_example.fun new file mode 100755 index 0000000..be40b40 --- /dev/null +++ b/examples/cli_parse_example.fun @@ -0,0 +1,48 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-12-30 + */ + +// Demonstrates parsing flags like -f, --force, --force=42 and positionals +// Usage examples: +// FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/cli_parse_example.fun -f foo +// FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/cli_parse_example.fun --force bar +// FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/cli_parse_example.fun --force=42 alpha beta +// FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/cli_parse_example.fun -abc x y + +#include + +args = argv() +parsed = parse_args(args) +flags = parsed["flags"] +pos = parsed["positionals"] + +if (flags["f"] == 1 || flags["force"] == 1) + print("Force enabled") + +// No hasKey() builtin available here; check via keys()+contains() +flag_keys = keys(flags) +if (contains(flag_keys, "force") && typeof(flags["force"]) == "string") + print(join(["Force value=", flags["force"]], "")) + +print("Flags map:") +print(flags) +print("Positionals:") +print(pos) + +/* Expected output (FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/cli_parse_example.fun --force bar xx x x --for=xxx --h=1): +1 +Force enabled +Flags map: +{"force": 1, "for": xxx, "h": 1} +Positionals: +[bar, xx, x, x] +*/ diff --git a/examples/data/complex.ini b/examples/data/complex.ini index 18084af..2d73052 100644 --- a/examples/data/complex.ini +++ b/examples/data/complex.ini @@ -1,32 +1,29 @@ +[auth] +user = "hanez" +token = "abcd1234" [app] -name = "FunApp" -version = "1.2.3" -debug = "1" - +name = "FunApp" +version = "1.2.3" +debug = "1" [database] -host = "localhost" -port = "5432" -user = "fun" -pass = "secret" -pool_size = "8" -timeout = "2.5" - +host = "localhost" +port = "5432" +user = "fun" +pass = "secret" +pool_size = "8" +timeout = "2.5" [network] -ssl = "yes" -retries = "3" -base_url = "https://api.example.com" - +ssl = "yes" +retries = "3" +base_url = "https://api.example.com" [features] -feature_x = "on" -feature_y = "off" - +feature_x = "on" +feature_y = "off" [paths] -data_dir = "./data" -log_file = "./logs/app.log" - - +data_dir = "./data" +log_file = "./logs/app.log" diff --git a/examples/data/htdocs/info.fun b/examples/data/htdocs/info.fun index 65e64cb..eca1076 100644 --- a/examples/data/htdocs/info.fun +++ b/examples/data/htdocs/info.fun @@ -16,7 +16,10 @@ print("

Fun Environment Information

") print("

Current Path: " + env("PATH") + "

") print("

Current User: " + env("USER") + "

") print("

Time: " + date_format(time_now_ms(), "%Y-%m-%d %H:%M:%S") + "

") -print("

Version: " + to_string(fun_version()) + "

") +print("

Local Version: " + to_string(fun_version()) + "

") +res = proc_run("fun -V") +installed_version = res["out"] +print("

Installed Version: " + to_string(substr(installed_version, 4, len(installed_version)-2)) + "

") print("

Full Environment:

") print("
    ") all_env = env_all() diff --git a/examples/env_all.fun b/examples/env_all.fun old mode 100644 new mode 100755 diff --git a/examples/extra/http_server_test.fun b/examples/extra/http_server_test.fun index ad3ab64..4a35ae9 100755 --- a/examples/extra/http_server_test.fun +++ b/examples/extra/http_server_test.fun @@ -1,3 +1,16 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-12-28 + */ + #include c = TcpClient() @@ -9,7 +22,6 @@ if (c.connect("127.0.0.1", 8080)) resp = c.recv_all(4096) print("GET Response contains foo=bar: " + to_string(find(resp, "foo = bar") >= 0)) print("GET Response contains baz=qux: " + to_string(find(resp, "baz = qux") >= 0)) - c.close() else print("Failed to connect to server") diff --git a/examples/ini_complex.fun b/examples/ini_complex.fun index 5c7d2b1..2707983 100755 --- a/examples/ini_complex.fun +++ b/examples/ini_complex.fun @@ -91,10 +91,9 @@ else retries=3 base_url=https://api.example.com [features] - feature_x=0 + feature_x=1 feature_y=0 [paths] data_dir=./data log_file=./logs/app.log */ - diff --git a/examples/ini_demo.fun b/examples/ini_demo.fun index 40f9cc4..7895584 100755 --- a/examples/ini_demo.fun +++ b/examples/ini_demo.fun @@ -19,7 +19,7 @@ if h == 0 print("Failed to load " + path) else u = ini_get_string(h, "auth", "user", "guest") - r = ini_get_int(h, "network", "retries", 3) + r = ini_get_int(h, "network", "retries", 5) s = ini_get_bool(h, "network", "ssl", 0) print("user=" + u) print("retries=" + to_string(r)) @@ -33,4 +33,11 @@ else user=����U retries=3 ssl=1 + +I wonder about the user= value when the default value is used! + +It should look like: +user=guest +retries=3 +ssl=1 */ diff --git a/examples/ini_diag.fun b/examples/ini_diag.fun new file mode 100644 index 0000000..f360cd9 --- /dev/null +++ b/examples/ini_diag.fun @@ -0,0 +1,50 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-02 + */ + +// Minimal diagnostic for INI lookups + +path = "./examples/data/complex.ini" +h = ini_load(path) +print("h=" + to_string(h)) +if h == 0 + print("Failed to load: " + path) +else + print("[try app:name]") + v1 = ini_get_string(h, "app", "name", "") + print("app:name => " + v1) + + print("[try app:version]") + v2 = ini_get_string(h, "app", "version", "") + print("app:version => " + v2) + + print("[try database:port]") + v3 = ini_get_int(h, "database", "port", -1) + print("database:port => " + to_string(v3)) + + print("[try network:ssl]") + v4 = ini_get_bool(h, "network", "ssl", -9) + print("network:ssl => " + to_string(v4)) + + ini_free(h) + +/* Expected output: +h=1 +[try app:name] +app:name => FunApp +[try app:version] +app:version => 1.2.3 +[try database:port] +database:port => 5432 +[try network:ssl] +network:ssl => 1 +*/ diff --git a/examples/input_hidden_example.fun b/examples/input_hidden_example.fun new file mode 100755 index 0000000..770f69f --- /dev/null +++ b/examples/input_hidden_example.fun @@ -0,0 +1,29 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Example: Demonstrate hidden input (no echo) for passwords. + * + * Added: 2026-01-02 + */ + +#include + +print("=== input_hidden() example ===") + +c = Console() + +username = c.ask("Username") +password = c.ask_hidden("Password") + +// Do not print the password; just show the username +print(join(["Hello, ", username, "!"], "")) + +/* Expected output: +Username: hanez +Password: +=== input_hidden() example === +Hello, hanez! +*/ diff --git a/examples/input_hidden_pam_auth.fun b/examples/input_hidden_pam_auth.fun new file mode 100755 index 0000000..56d5fb7 --- /dev/null +++ b/examples/input_hidden_pam_auth.fun @@ -0,0 +1,45 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Example: Demonstrate hidden input (no echo) for passwords using + * /usr/bin/chkpwd from https://git.xw3.org/hanez/chkusr for PAM authetication. + * + * Added: 2026-01-02 + */ + +#include + +print("=== input_hidden() example with PAM authentication ===") + +c = Console() + +username = c.ask("Username") +password = c.ask_hidden("Password") + +// Do not print the password; just show the username +print(join(["Hello, ", username, "!"], "")) + +res = proc_run("chkpwd -u " + username + " -p " + password) +content = res["out"] +if (res["code"] == 0) + print("Login success!") +else + print("Login failed!") + +/* Possible output: +Username: hanez +Password: +=== input_hidden() example with PAM authentication === +Hello, hanez! +Login success! + +Or: +Username: hanez +Password: +=== input_hidden() example with PAM authentication === +Hello, hanez! +Login failed! +*/ diff --git a/examples/math_ceil.fun b/examples/math_ceil.fun new file mode 100644 index 0000000..5c2ef16 --- /dev/null +++ b/examples/math_ceil.fun @@ -0,0 +1,36 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +// Demo of C99-like ceil(x) + +print("=== math ceil demo start ===") + +print("ceil(3.2) -> " + to_string(ceil(3.2))) // 4 +print("ceil(3.0) -> " + to_string(ceil(3.0))) // 3 +print("ceil(-3.2) -> " + to_string(ceil(-3.2))) // -3 +print("ceil(-3.0) -> " + to_string(ceil(-3.0))) // -3 +print("ceil(0.5) -> " + to_string(ceil(0.5))) // 1 +print("ceil(-0.5) -> " + to_string(ceil(-0.5))) // 0 + +print("=== math ceil demo end ===") + +/* Expected output (values): +=== math ceil demo start === +ceil(3.2) -> 4 +ceil(3.0) -> 3 +ceil(-3.2) -> -3 +ceil(-3.0) -> -3 +ceil(0.5) -> 1 +ceil(-0.5) -> 0 +=== math ceil demo end === +*/ diff --git a/examples/math_cos.fun b/examples/math_cos.fun new file mode 100644 index 0000000..f498126 --- /dev/null +++ b/examples/math_cos.fun @@ -0,0 +1,30 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +// Demo of cos(x) + +print("=== math cos demo start ===") + +print("cos(0) -> " + to_string(cos(0))) +print("cos(3.14159265359) ~ pi -> " + to_string(cos(3.14159265359))) +print("cos(1.57079632679) ~ pi/2 -> " + to_string(cos(1.57079632679))) + +print("=== math cos demo end ===") + +/* Expected output (approximate): +=== math cos demo start === +cos(0) -> 1 +cos(3.14159265359) ~ pi -> -1 +cos(1.57079632679) ~ pi/2 -> 0 +=== math cos demo end === +*/ diff --git a/examples/math_exp_log.fun b/examples/math_exp_log.fun new file mode 100644 index 0000000..5be06d2 --- /dev/null +++ b/examples/math_exp_log.fun @@ -0,0 +1,34 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +// Demo of exp(x), log(x), log10(x) + +print("=== math exp/log demo start ===") + +print("exp(0) -> " + to_string(exp(0))) +print("log(1) -> " + to_string(log(1))) +print("log10(1) -> " + to_string(log10(1))) +print("exp(1) -> " + to_string(exp(1))) +print("log(2.71828182846) ~ ln(e) -> " + to_string(log(2.71828182846))) + +print("=== math exp/log demo end ===") + +/* Expected output (approximate): +=== math exp/log demo start === +exp(0) -> 1 +log(1) -> 0 +log10(1) -> 0 +exp(1) -> 2.718281828... +log(2.71828182846) ~ ln(e) -> 1 +=== math exp/log demo end === +*/ diff --git a/examples/math_floor.fun b/examples/math_floor.fun new file mode 100644 index 0000000..94783c7 --- /dev/null +++ b/examples/math_floor.fun @@ -0,0 +1,36 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +// Demo of C99-like floor(x) + +print("=== math floor demo start ===") + +print("floor(3.7) -> " + to_string(floor(3.7))) // 3 +print("floor(3.0) -> " + to_string(floor(3.0))) // 3 +print("floor(-3.2) -> " + to_string(floor(-3.2))) // -4 +print("floor(-3.0) -> " + to_string(floor(-3.0))) // -3 +print("floor(0.5) -> " + to_string(floor(0.5))) // 0 +print("floor(-0.5) -> " + to_string(floor(-0.5))) // -1 + +print("=== math floor demo end ===") + +/* Expected output (values): +=== math floor demo start === +floor(3.7) -> 3 +floor(3.0) -> 3 +floor(-3.2) -> -4 +floor(-3.0) -> -3 +floor(0.5) -> 0 +floor(-0.5) -> -1 +=== math floor demo end === +*/ diff --git a/examples/math_fmin_fmax.fun b/examples/math_fmin_fmax.fun new file mode 100644 index 0000000..2b1a49d --- /dev/null +++ b/examples/math_fmin_fmax.fun @@ -0,0 +1,32 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +// Demo of fmin(x,y) / fmax(x,y) — float-aware min/max with C99 NaN handling + +print("=== math fmin/fmax demo start ===") + +print("fmin(3.2, 4) -> " + to_string(fmin(3.2, 4))) // 3.2 +print("fmax(3.2, 4) -> " + to_string(fmax(3.2, 4))) // 4 +print("fmin(-5, -2.5) -> " + to_string(fmin(-5, -2.5))) // -5 +print("fmax(-5, -2.5) -> " + to_string(fmax(-5, -2.5))) // -2.5 + +print("=== math fmin/fmax demo end ===") + +/* Expected output: +=== math fmin/fmax demo start === +fmin(3.2, 4) -> 3.2000000000000002 +fmax(3.2, 4) -> 4 +fmin(-5, -2.5) -> -5 +fmax(-5, -2.5) -> -2.5 +=== math fmin/fmax demo end === +*/ diff --git a/examples/math_gcd_lcm.fun b/examples/math_gcd_lcm.fun new file mode 100644 index 0000000..4f0fbab --- /dev/null +++ b/examples/math_gcd_lcm.fun @@ -0,0 +1,35 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +print("=== math gcd/lcm demo start ===") + +print("gcd(48,18) -> " + to_string(gcd(48,18))) // 6 +print("gcd(0,5) -> " + to_string(gcd(0,5))) // 5 +print("gcd(-21,6) -> " + to_string(gcd(-21,6))) // 3 + +print("lcm(21,6) -> " + to_string(lcm(21,6))) // 42 +print("lcm(0,5) -> " + to_string(lcm(0,5))) // 0 +print("lcm(-4,6) -> " + to_string(lcm(-4,6))) // 12 + +print("=== math gcd/lcm demo end ===") + +/* Expected output (values): +=== math gcd/lcm demo start === +gcd(48,18) -> 6 +gcd(0,5) -> 5 +gcd(-21,6) -> 3 +lcm(21,6) -> 42 +lcm(0,5) -> 0 +lcm(-4,6) -> 12 +=== math gcd/lcm demo end === +*/ diff --git a/examples/math_isqrt.fun b/examples/math_isqrt.fun new file mode 100644 index 0000000..e232444 --- /dev/null +++ b/examples/math_isqrt.fun @@ -0,0 +1,38 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +print("=== math isqrt demo start ===") + +print("isqrt(-4) -> " + to_string(isqrt(-4))) // 0 (non-negative only) +print("isqrt(0) -> " + to_string(isqrt(0))) // 0 +print("isqrt(1) -> " + to_string(isqrt(1))) // 1 +print("isqrt(2) -> " + to_string(isqrt(2))) // 1 +print("isqrt(3) -> " + to_string(isqrt(3))) // 1 +print("isqrt(4) -> " + to_string(isqrt(4))) // 2 +print("isqrt(15) -> " + to_string(isqrt(15))) // 3 +print("isqrt(16) -> " + to_string(isqrt(16))) // 4 + +print("=== math isqrt demo end ===") + +/* Expected output (values): +=== math isqrt demo start === +isqrt(-4) -> 0 +isqrt(0) -> 0 +isqrt(1) -> 1 +isqrt(2) -> 1 +isqrt(3) -> 1 +isqrt(4) -> 2 +isqrt(15) -> 3 +isqrt(16) -> 4 +=== math isqrt demo end === +*/ diff --git a/examples/math_round.fun b/examples/math_round.fun new file mode 100644 index 0000000..0c2117c --- /dev/null +++ b/examples/math_round.fun @@ -0,0 +1,36 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +// Demo of C99-like round(x) — half away from zero + +print("=== math round demo start ===") + +print("round(3.2) -> " + to_string(round(3.2))) // 3 +print("round(3.5) -> " + to_string(round(3.5))) // 4 +print("round(3.8) -> " + to_string(round(3.8))) // 4 +print("round(-3.2) -> " + to_string(round(-3.2))) // -3 +print("round(-3.5) -> " + to_string(round(-3.5))) // -4 +print("round(-3.8) -> " + to_string(round(-3.8))) // -4 + +print("=== math round demo end ===") + +/* Expected output (values): +=== math round demo start === +round(3.2) -> 3 +round(3.5) -> 4 +round(3.8) -> 4 +round(-3.2) -> -3 +round(-3.5) -> -4 +round(-3.8) -> -4 +=== math round demo end === +*/ diff --git a/examples/math_sign.fun b/examples/math_sign.fun new file mode 100644 index 0000000..9fdfd6c --- /dev/null +++ b/examples/math_sign.fun @@ -0,0 +1,32 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +print("=== math sign demo start ===") + +print("sign(-5) -> " + to_string(sign(-5))) // -1 +print("sign(0) -> " + to_string(sign(0))) // 0 +print("sign(7) -> " + to_string(sign(7))) // 1 +print("sign(-0.0) -> " + to_string(sign(-0.0))) // 0 +print("sign(3.14) -> " + to_string(sign(3.14))) // 1 + +print("=== math sign demo end ===") + +/* Expected output (values): +=== math sign demo start === +sign(-5) -> -1 +sign(0) -> 0 +sign(7) -> 1 +sign(-0.0) -> 0 +sign(3.14) -> 1 +=== math sign demo end === +*/ diff --git a/examples/math_sin.fun b/examples/math_sin.fun new file mode 100644 index 0000000..193881b --- /dev/null +++ b/examples/math_sin.fun @@ -0,0 +1,30 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +// Demo of sin(x) + +print("=== math sin demo start ===") + +print("sin(0) -> " + to_string(sin(0))) +print("sin(1.57079632679) ~ pi/2 -> " + to_string(sin(1.57079632679))) +print("sin(-1.57079632679) -> " + to_string(sin(-1.57079632679))) + +print("=== math sin demo end ===") + +/* Expected output (approximate): +=== math sin demo start === +sin(0) -> 0 +sin(1.57079632679) ~ pi/2 -> 1 +sin(-1.57079632679) -> -1 +=== math sin demo end === +*/ diff --git a/examples/math_sqrt.fun b/examples/math_sqrt.fun new file mode 100644 index 0000000..6052981 --- /dev/null +++ b/examples/math_sqrt.fun @@ -0,0 +1,32 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +// Demo of sqrt(x) + +print("=== math sqrt demo start ===") + +print("sqrt(0) -> " + to_string(sqrt(0))) +print("sqrt(9) -> " + to_string(sqrt(9))) +print("sqrt(2) -> " + to_string(sqrt(2))) +print("sqrt(-1) -> " + to_string(sqrt(-1))) // NaN expected + +print("=== math sqrt demo end ===") + +/* Expected output (approximate): +=== math sqrt demo start === +sqrt(0) -> 0 +sqrt(9) -> 3 +sqrt(2) -> 1.41421356... +sqrt(-1) -> nan +=== math sqrt demo end === +*/ diff --git a/examples/math_tan.fun b/examples/math_tan.fun new file mode 100644 index 0000000..fd63efa --- /dev/null +++ b/examples/math_tan.fun @@ -0,0 +1,28 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +// Demo of tan(x) + +print("=== math tan demo start ===") + +print("tan(0) -> " + to_string(tan(0))) +print("tan(0.78539816339) ~ pi/4 -> " + to_string(tan(0.78539816339))) + +print("=== math tan demo end ===") + +/* Expected output (approximate): +=== math tan demo start === +tan(0) -> 0 +tan(0.78539816339) ~ pi/4 -> 1 +=== math tan demo end === +*/ diff --git a/examples/math_trunc.fun b/examples/math_trunc.fun new file mode 100644 index 0000000..e8f395b --- /dev/null +++ b/examples/math_trunc.fun @@ -0,0 +1,36 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +// Demo of C99-like trunc(x) — rounds toward zero + +print("=== math trunc demo start ===") + +print("trunc(3.7) -> " + to_string(trunc(3.7))) // 3 +print("trunc(3.0) -> " + to_string(trunc(3.0))) // 3 +print("trunc(-3.7) -> " + to_string(trunc(-3.7))) // -3 +print("trunc(-3.0) -> " + to_string(trunc(-3.0))) // -3 +print("trunc(0.5) -> " + to_string(trunc(0.5))) // 0 +print("trunc(-0.5) -> " + to_string(trunc(-0.5))) // 0 + +print("=== math trunc demo end ===") + +/* Expected output (values): +=== math trunc demo start === +trunc(3.7) -> 3 +trunc(3.0) -> 3 +trunc(-3.7) -> -3 +trunc(-3.0) -> -3 +trunc(0.5) -> 0 +trunc(-0.5) -> 0 +=== math trunc demo end === +*/ diff --git a/examples/ripemd160_demo.fun b/examples/ripemd160_demo.fun index 9f7d06c..0d90f97 100755 --- a/examples/ripemd160_demo.fun +++ b/examples/ripemd160_demo.fun @@ -51,8 +51,8 @@ RIPEMD-160(hex: 616263) = 8eb208f7e05d987a9b044a8e98c6b087f15a0bfc */ /* Actual output: -RIPEMD-160("") = d90cc72ef4ad5a2c51f89dc938de0b989c4c4b46 -RIPEMD-160("abc") = 7f339b642aaa074a849c6c1cd860cb049cc792b3 -RIPEMD-160("message digest") = a4f9cd0270a341d05a713df4bf49a3ce8aabde64 -RIPEMD-160(hex: 616263) = 7f339b642aaa074a849c6c1cd860cb049cc792b3 +RIPEMD-160("") = 560e66de427fc2c3399e86c3e1d38c3c6017def2 +RIPEMD-160("abc") = 74266d6b4447493abb790c49e4bfa8f07263bcff +RIPEMD-160("message digest") = 80ee01c69badb632393a9e05d1e7eebc2f05ac48 +RIPEMD-160(hex: 616263) = 74266d6b4447493abb790c49e4bfa8f07263bcff */ diff --git a/examples/ripemd160_example.fun b/examples/ripemd160_example.fun new file mode 100644 index 0000000..36610cd --- /dev/null +++ b/examples/ripemd160_example.fun @@ -0,0 +1,36 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-02 + */ + +// THIS IS BROKEN ACTUALLY! IT DOES NOT CALCULATE THE EXPECTED HASHES! + +/* + * Example: Using RIPEMD160 from lib/crypt/ripemd160.fun + * + * Known vectors: + * - "" (empty) => 9c1185a5c5e9fc54612808977ee8f548b2258d31 + * - "abc" => 8eb208f7e05d987a9b04... (full known); we verify via hex of "abc" + * - "The quick brown fox jumps over the lazy dog" => 37f332f68db77bd9d7edd4969571ad671cf9dd3b + */ + +#include + +r = RIPEMD160() + +// empty string +print(r.ripemd160_str("")) + +// "abc" +print(r.ripemd160_hex("616263")) + +// pangram +print(r.ripemd160_str("The quick brown fox jumps over the lazy dog")) diff --git a/lib/cli.fun b/lib/cli.fun new file mode 100644 index 0000000..bf9ea6c --- /dev/null +++ b/lib/cli.fun @@ -0,0 +1,66 @@ +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-12-30 + */ + +// Simple CLI helpers for Fun scripts. +// +// argv(): returns an array of the raw arguments passed to the script +// (not including the script path). Requires the interpreter to +// export FUN_ARGC and FUN_ARGV_i environment variables. +// parse_args(args): parses flags and positionals from argv() like: +// -f -> flags["f"] = 1 +// --force -> flags["force"] = 1 +// --force=42 -> flags["force"] = "42" +// -abc -> flags["a"]=1, flags["b"]=1, flags["c"]=1 + +fun argv() + n = to_number(env("FUN_ARGC")) + a = [] + i = 0 + while (i < n) + // Build key without join() to avoid any potential call resolution issues + key = "FUN_ARGV_" + to_string(i) + push(a, env(key)) + i = i + 1 + return a + +// Returns: { "flags": map, "positionals": array } +fun parse_args(args) + flags = {} + pos = [] + i = 0 + n = len(args) + while (i < n) + s = args[i] + slen = len(s) + if (slen >= 2 && substr(s, 0, 2) == "--") + eq = find(s, "=") + if (eq >= 0) + key = substr(s, 2, eq - 2) // between -- and = + val = substr(s, eq + 1, slen - (eq + 1)) + flags[key] = val + else + key = substr(s, 2, slen - 2) + flags[key] = 1 + else if (slen == 2 && substr(s, 0, 1) == "-") + // short -f boolean + key = substr(s, 1, 1) + flags[key] = 1 + else if (slen > 2 && substr(s, 0, 1) == "-") + // compact short flags like -abc -> a=1, b=1, c=1 + j = 1 + while (j < slen) + k = substr(s, j, 1) + flags[k] = 1 + j = j + 1 + else + push(pos, s) + i = i + 1 + return { "flags": flags, "positionals": pos } diff --git a/lib/crypt/ripemd160.fun b/lib/crypt/ripemd160.fun index f866ab1..bfb4d8b 100644 --- a/lib/crypt/ripemd160.fun +++ b/lib/crypt/ripemd160.fun @@ -15,7 +15,10 @@ class RIPEMD160() KL = [0, 1518500249, 1859775393, 2400959708, 2840853838] - KR = [1352829926, 1548603684, 1836062451, 2054012649, 0] + //KR = [1352829926, 1548603684, 1836062451, 2054012649, 0] + // Correct KR constants according to RIPEMD-160 specification: + // 0x50A28BE6, 0x5C4DD124, 0x6D703EF3, 0x7A6D76E9, 0x00000000 + KR = [1352829926, 1554976324, 1836072691, 2053994217, 0] RL = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, @@ -58,22 +61,23 @@ class RIPEMD160() return x fun rol32(this, x, s) - return rol(this.u32(x), s) + x = this.u32(x) + return this.u32(bor(shl(x, s), shr(x, 32 - s))) fun add32(this, a, b) return this.u32(a + b) fun F(this, stage, x, y, z) if stage == 0 - return bxor(bxor(x, y), z) + return this.u32(bxor(bxor(x, y), z)) else if stage == 1 - return bor(band(x, y), band(bnot(x), z)) + return this.u32(bor(band(x, y), band(bnot(x), z))) else if stage == 2 - return bxor(bor(x, bnot(y)), z) + return this.u32(bxor(bor(x, bnot(y)), z)) else if stage == 3 - return bor(band(x, z), band(y, bnot(z))) + return this.u32(bor(band(x, z), band(y, bnot(z)))) else - return bxor(x, bor(y, bnot(z))) + return this.u32(bxor(x, bor(y, bnot(z)))) fun hex_val(this, ch) if ch == "0" @@ -204,7 +208,9 @@ class RIPEMD160() B = T // Right side step - Tr = this.add32(this.rol32(this.add32(this.add32(Ar, this.F(stage_r, Br, Cr, Dr)), this.add32(M[this.RR[j]], this.KR[stage_r])), this.SR[j]), Er) + // Note: Right branch uses F in reverse stage order (stage_r), + // but the constants KR are indexed by the forward round index (stage). + Tr = this.add32(this.rol32(this.add32(this.add32(Ar, this.F(stage_r, Br, Cr, Dr)), this.add32(M[this.RR[j]], this.KR[stage])), this.SR[j]), Er) Ar = Er Er = Dr Dr = this.rol32(Cr, 10) @@ -230,11 +236,19 @@ class RIPEMD160() h3 = state[3] h4 = state[4] - state[0] = this.add32(h1, this.add32(B, Cr)) - state[1] = this.add32(h2, this.add32(C, Dr)) - state[2] = this.add32(h3, this.add32(D, Er)) - state[3] = this.add32(h4, this.add32(E, Ar)) - state[4] = this.add32(h0, this.add32(A, Br)) + // Final state combination (canonical RIPEMD-160): + // T = h1 + Cl + Dr + // h1 = h2 + Dl + Er + // h2 = h3 + El + Ar + // h3 = h4 + Al + Br + // h4 = h0 + Bl + Cr + // h0 = T + T = this.add32(h1, this.add32(C, Dr)) + state[1] = this.add32(h2, this.add32(D, Er)) + state[2] = this.add32(h3, this.add32(E, Ar)) + state[3] = this.add32(h4, this.add32(A, Br)) + state[4] = this.add32(h0, this.add32(B, Cr)) + state[0] = T return state fun ripemd160_bytes(this, bytes) diff --git a/lib/crypt/ripemd160_new_try.fun b/lib/crypt/ripemd160_new_try.fun new file mode 100644 index 0000000..41ba167 --- /dev/null +++ b/lib/crypt/ripemd160_new_try.fun @@ -0,0 +1,316 @@ +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-02 + */ + +// THIS IS BROKEN ACTUALLY! IT DOES NOT CALCULATE THE EXPECTED HASHES! + +// lib/crypt/ripemd160.fun +// Pure Fun implementation of RIPEMD-160 operating on hex-string or ASCII inputs. +// +// Public API (class): +// rip = RIPEMD160() +// rip.ripemd160_hex(hexStr) -> digest hex string (lowercase) +// rip.ripemd160_str("abc") -> digest hex string of ASCII bytes + +#include + +class RIPEMD160() + // 32-bit helpers (match style from md5.fun) + fun u32(this, x) + m = 4294967296 + while x < 0 + x = x + m + while x >= m + x = x - m + return x + + fun add32(this, a, b) + return this.u32(a + b) + + fun add32_5(this, a, b, c, d, e) + return this.u32(this.u32(this.u32(this.u32(a + b) + c) + d) + e) + + fun rol32(this, x, s) + return rol(this.u32(x), s) + + fun and32(this, a, b) + return band(this.u32(a), this.u32(b)) + + fun or32(this, a, b) + return bor(this.u32(a), this.u32(b)) + + fun xor32(this, a, b) + return bxor(this.u32(a), this.u32(b)) + + fun not32(this, x) + return bnot(this.u32(x)) + + // hex helpers (same as md5/sha files) + fun hex_val(this, ch) + if (ch == "0") + return 0 + else if (ch == "1") + return 1 + else if (ch == "2") + return 2 + else if (ch == "3") + return 3 + else if (ch == "4") + return 4 + else if (ch == "5") + return 5 + else if (ch == "6") + return 6 + else if (ch == "7") + return 7 + else if (ch == "8") + return 8 + else if (ch == "9") + return 9 + else if (ch == "a" || ch == "A") + return 10 + else if (ch == "b" || ch == "B") + return 11 + else if (ch == "c" || ch == "C") + return 12 + else if (ch == "d" || ch == "D") + return 13 + else if (ch == "e" || ch == "E") + return 14 + else if (ch == "f" || ch == "F") + return 15 + else + return 0 + + fun byte_from_hex_pair(this, hh) + hi = this.hex_val(substr(hh, 0, 1)) + lo = this.hex_val(substr(hh, 1, 1)) + return hi * 16 + lo + + fun from_hex(this, hex) + arr = [] + i = 0 + n = len(hex) + while i + 1 < n + b = this.byte_from_hex_pair(substr(hex, i, 2)) + push(arr, b) + i = i + 2 + return arr + + fun two_hex(this, n) + n = n % 256 + d = ["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"] + hi = n / 16 + lo = n % 16 + parts = [d[hi], d[lo]] + return join(parts, "") + + fun bytes_to_hex(this, arr) + i = 0 + out = [] + while i < len(arr) + push(out, this.two_hex(arr[i])) + i = i + 1 + return join(out, "") + + // Padding (RIPEMD-160): like MD4/MD5 — append 0x80, zeros to 56 mod 64, then 64-bit length in little-endian + fun pad_bytes(this, bytes) + L = len(bytes) + out = [] + i = 0 + while i < L + push(out, bytes[i]) + i = i + 1 + push(out, 128) + while (len(out) % 64) != 56 + push(out, 0) + len_bits = L * 8 + j = 0 + while j < 8 + b = (len_bits / pow(2, 8 * j)) % 256 + push(out, b) + j = j + 1 + return out + + fun word32_le(this, b0, b1, b2, b3) + return this.u32(b0 + b1 * 256 + b2 * 65536 + b3 * 16777216) + + // RIPEMD-160 boolean functions + fun f1(this, x, y, z) + return this.xor32(this.xor32(x, y), z) + + fun f2(this, x, y, z) + return this.or32(this.and32(x, y), this.and32(this.not32(x), z)) + + fun f3(this, x, y, z) + return this.xor32(this.or32(x, this.not32(y)), z) + + fun f4(this, x, y, z) + return this.or32(this.and32(x, z), this.and32(y, this.not32(z))) + + fun f5(this, x, y, z) + return this.xor32(x, this.or32(y, this.not32(z))) + + // Process a 512-bit block + fun process_block(this, H, block) + // message words X[16] (little-endian) + X = [] + i = 0 + while i < 16 + j = i * 4 + push(X, this.word32_le(block[j], block[j+1], block[j+2], block[j+3])) + i = i + 1 + + // R and S (left line) + R = [ + 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, + 7,4,13,1,10,6,15,3,12,0,9,5,2,14,11,8, + 3,10,14,4,9,15,8,1,2,7,0,6,13,11,5,12, + 1,9,11,10,0,8,12,4,13,3,7,15,14,5,6,2, + 4,0,5,9,7,12,2,10,14,1,3,8,11,6,15,13 + ] + S = [ + 11,14,15,12,5,8,7,9,11,13,14,15,6,7,9,8, + 7,6,8,13,11,9,7,15,7,12,15,9,11,7,13,12, + 11,13,6,7,14,9,13,15,14,8,13,6,5,12,7,5, + 11,12,14,15,14,15,9,8,9,14,5,6,8,6,5,12, + 9,15,5,11,6,8,13,12,5,12,13,14,11,8,5,6 + ] + + // R' and S' (right line) + Rp = [ + 5,14,7,0,9,2,11,4,13,6,15,8,1,10,3,12, + 6,11,3,7,0,13,5,10,14,15,8,12,4,9,1,2, + 15,5,1,3,7,14,6,9,11,8,12,2,10,0,4,13, + 8,6,4,1,3,11,15,0,5,12,2,13,9,7,10,14, + 12,15,10,4,1,5,8,7,6,2,13,14,0,3,9,11 + ] + Sp = [ + 8,9,9,11,13,15,15,5,7,7,8,11,14,14,12,6, + 9,13,15,7,12,8,9,11,7,7,12,7,6,15,13,11, + 9,7,15,11,8,6,6,14,12,13,5,14,13,13,7,5, + 15,5,8,11,14,14,6,14,6,9,12,9,12,5,15,8, + 8,5,12,9,12,5,14,6,8,13,6,5,15,13,11,11 + ] + + // Constants + KL = [0, 1518500249, 1859775393, 2400959708, 2840853838] // 0x00, 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xA953FD4E + KR = [1352829926, 1548603684, 1836072691, 2053994217, 0] // 0x50A28BE6, 0x5C4DD124, 0x6D703EF3, 0x7A6D76E9, 0x00000000 + + al = H[0]; bl = H[1]; cl = H[2]; dl = H[3]; el = H[4] + ar = H[0]; br = H[1]; cr = H[2]; dr = H[3]; er = H[4] + + j = 0 + while j < 80 + sL = S[j] + sR = Sp[j] + + // select function and constant per round for left lane + if (j < 16) + fL = this.f1(bl, cl, dl) + kL = KL[0] + else if (j < 32) + fL = this.f2(bl, cl, dl) + kL = KL[1] + else if (j < 48) + fL = this.f3(bl, cl, dl) + kL = KL[2] + else if (j < 64) + fL = this.f4(bl, cl, dl) + kL = KL[3] + else + fL = this.f5(bl, cl, dl) + kL = KL[4] + + // right lane + if (j < 16) + fR = this.f5(br, cr, dr) + kR = KR[0] + else if (j < 32) + fR = this.f4(br, cr, dr) + kR = KR[1] + else if (j < 48) + fR = this.f3(br, cr, dr) + kR = KR[2] + else if (j < 64) + fR = this.f2(br, cr, dr) + kR = KR[3] + else + fR = this.f1(br, cr, dr) + kR = KR[4] + + // left step + tl = this.add32_5(al, fL, X[R[j]], kL, 0) + tl = this.rol32(tl, sL) + tl = this.add32(tl, el) + al = el + el = dl + dl = this.rol32(cl, 10) + cl = bl + bl = tl + + // right step + tr = this.add32_5(ar, fR, X[Rp[j]], kR, 0) + tr = this.rol32(tr, sR) + tr = this.add32(tr, er) + ar = er + er = dr + dr = this.rol32(cr, 10) + cr = br + br = tr + + j = j + 1 + + // combine using originals + h0 = H[0]; h1 = H[1]; h2 = H[2]; h3 = H[3]; h4 = H[4] + tt = this.add32(h0, this.add32(bl, cr)) + H[0] = this.add32(h1, this.add32(cl, dr)) + H[1] = this.add32(h2, this.add32(dl, er)) + H[2] = this.add32(h3, this.add32(el, ar)) + H[3] = this.add32(h4, this.add32(al, br)) + H[4] = tt + return H + + fun ripemd160_bytes(this, bytes) + // initialize h0..h4 + H = [1732584193, 4023233417, 2562383102, 271733878, 3285377520] + data = this.pad_bytes(bytes) + off = 0 + N = len(data) + while off < N + block = [] + i = 0 + while i < 64 + push(block, data[off + i]) + i = i + 1 + H = this.process_block(H, block) + off = off + 64 + + // output as little-endian of h0..h4 (20 bytes) + out = [] + j = 0 + while j < 5 + v = H[j] + push(out, v % 256) + push(out, (v / 256) % 256) + push(out, (v / 65536) % 256) + push(out, (v / 16777216) % 256) + j = j + 1 + return out + + fun ripemd160_hex(this, hexStr) + bytes = this.from_hex(hexStr) + digest = this.ripemd160_bytes(bytes) + return this.bytes_to_hex(digest) + + fun ripemd160_str(this, str) + bytes = string_to_bytes_ascii(str) + digest = this.ripemd160_bytes(bytes) + return this.bytes_to_hex(digest) diff --git a/lib/io/console.fun b/lib/io/console.fun index 52c89ad..e2a0cd0 100644 --- a/lib/io/console.fun +++ b/lib/io/console.fun @@ -17,6 +17,15 @@ class Console() else return input(join([q, ": "], "")) + // Ask for a secret string (e.g., password) without echoing input + // Returns the entered string. A trailing newline is not echoed back. + fun ask_hidden(this, question) + q = to_string(question) + if (len(q) == 0) + return input_hidden("") + else + return input_hidden(join([q, ": "], "")) + // Ask a yes/no question; returns 1 for yes, 0 for no // Accepts: y, yes, n, no (case-insensitive). Keeps asking until valid. fun ask_yes_no(this, question) diff --git a/lib/io/ini.fun b/lib/io/ini.fun index b6145e5..6261dec 100644 --- a/lib/io/ini.fun +++ b/lib/io/ini.fun @@ -29,7 +29,7 @@ class INI() // Load an INI file from path, closing previous one if open. // Returns handle (>0) or 0 on error. fun load(this, path) - if (this.h > 0) + if (this.is_open()) ini_free(this.h) this.h = 0 p = to_string(path) @@ -39,11 +39,13 @@ class INI() // True if a dictionary is open. fun is_open(this) - return this.h > 0 + // Be robust across runtimes: coerce to number and test non-zero + // Avoid using '>' to prevent integer-only GT errors in VM + return to_number(this.h) != 0 // Close and free resources. Safe to call multiple times. fun close(this) - if (this.h > 0) + if (this.is_open()) ini_free(this.h) this.h = 0 return 1 diff --git a/lib/net/http_server.fun b/lib/net/http_server.fun index dc78dac..06a8402 100644 --- a/lib/net/http_server.fun +++ b/lib/net/http_server.fun @@ -84,7 +84,7 @@ class HTTPServer(number port) env_vars = "QUERY_STRING='" + query_string + "' " if (len(post_data) > 0) env_vars = env_vars + "POST_DATA='" + post_data + "' " - + res = proc_run(env_vars + " " + "fun" + " " + file_path) content = res["out"] else diff --git a/make b/make index 914b699..a6a0205 100755 --- a/make +++ b/make @@ -100,6 +100,8 @@ elif [ "$target" = "freebsd" ]; then -DFUN_LINK_STATIC=OFF \ -DFUN_DEBUG=OFF \ && cmake --build build --target fun +elif [ "$target" = "install" ]; then + sudo cmake --build build --target install elif [ "$target" = "minimal" ]; then rm -rf build \ && cmake -S . -B build \ @@ -159,6 +161,7 @@ else echo " - alpine"; echo " - debug"; echo " - freebsd"; + echo " - install"; echo " - minimal"; echo " - musl"; echo " - repl"; diff --git a/scripts/run_examples.sh b/scripts/run_examples.sh index 1f7f5cd..1690cd7 100755 --- a/scripts/run_examples.sh +++ b/scripts/run_examples.sh @@ -69,16 +69,23 @@ fi rc=0 for f in "${files[@]}"; do + base_name="$(basename "$f")" + # If INI support is disabled, skip INI examples to avoid expected failures + if [[ "${FUN_WITH_INI:-}" =~ ^(0|OFF|off|false|False)$ ]]; then + if [[ "$base_name" == ini_*.fun ]]; then + echo "=== Skipping (INI disabled): examples/$base_name ===" + continue + fi + fi echo "=== Running: ${f#$ROOT/} ===" if ! "$BIN" "$f"; then echo "FAILED: ${f#$ROOT/}" - base="$(basename "$f")" - dest="$EX_DIR/error/$base" + dest="$EX_DIR/error/$base_name" # Move the failing example to the error folder if mv -f "$f" "$dest"; then - echo "Moved to: examples/error/$base" + echo "Moved to: examples/error/$base_name" else - echo "warning: failed to move $base to examples/error/" >&2 + echo "warning: failed to move $base_name to examples/error/" >&2 fi rc=1 fi diff --git a/src/bytecode.c b/src/bytecode.c index f1af0e2..80a4ce7 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -189,6 +189,23 @@ static const char *opcode_name(OpCode op) { 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"; default: return "???"; } } diff --git a/src/bytecode.h b/src/bytecode.h index 0c0e971..7944573 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -236,7 +236,32 @@ typedef enum { // exceptions (minimal) OP_TRY_PUSH, // operand = handler ip; push handler onto try-stack OP_TRY_POP, // pop current handler - OP_THROW // pops error value; if handler -> jump to it (push err), else print and terminate + OP_THROW, // pops error value; if handler -> jump to it (push err), else print and terminate + + // C99 math.h rounding family (float-aware) + OP_FLOOR, // pops x (int/float); pushes floor(x) (int if integral else float) + OP_CEIL, // pops x (int/float); pushes ceil(x) (int if integral else float) + OP_TRUNC, // pops x (int/float); pushes trunc(x) (int if integral else float) + OP_ROUND, // pops x (int/float); pushes round(x) (half away from zero) + + // C99 math.h transcendentals (float-aware) + OP_SIN, // pops x (int/float); pushes sin(x) + OP_COS, // pops x (int/float); pushes cos(x) + OP_TAN, // pops x (int/float); pushes tan(x) + OP_EXP, // pops x (int/float); pushes exp(x) + OP_LOG, // pops x (int/float); pushes natural log ln(x) + OP_LOG10, // pops x (int/float); pushes log10(x) + OP_SQRT, // pops x (int/float); pushes sqrt(x) + + // Integer math helpers + OP_GCD, // pops b, a; pushes gcd(|a|,|b|) + OP_LCM, // pops b, a; pushes lcm(|a|,|b|) (0 if either is 0) + OP_ISQRT, // pops x; pushes floor(sqrt(max(0,x))) for integers + OP_SIGN, // pops x; pushes -1, 0, or 1 depending on the sign + + // Min/Max variants (float-aware, C99 semantics) + OP_FMIN, // pops b, a (int/float); pushes fmin(a,b) (NaN handling per C99) + OP_FMAX // pops b, a (int/float); pushes fmax(a,b) (NaN handling per C99) } OpCode; typedef struct { diff --git a/src/fun.c b/src/fun.c index 041e306..08ae886 100644 --- a/src/fun.c +++ b/src/fun.c @@ -1,4 +1,13 @@ -/** +/* +* This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + */ + + /* * Main entry point for the Fun language interpreter. * Builds a CLI that runs a script file if provided; otherwise starts the REPL * when compiled with FUN_WITH_REPL enabled. @@ -85,6 +94,46 @@ int main(int argc, char **argv) { if (argi < argc) { const char *path = argv[argi]; + + /* Collect script arguments (everything after script path) and expose via env vars */ + int sargi = argi + 1; /* first script arg following the script path */ + int sargc = (sargi < argc) ? (argc - sargi) : 0; + + /* Export FUN_ARGC */ + { + char buf[32]; + snprintf(buf, sizeof(buf), "%d", sargc); + setenv("FUN_ARGC", buf, 1); + } + + /* Export FUN_ARGV_i */ + for (int i = 0; i < sargc; ++i) { + char key[32]; + snprintf(key, sizeof(key), "FUN_ARGV_%d", i); + setenv(key, argv[sargi + i], 1); + } + + /* Optional: space-joined convenience string FUN_ARGS */ + if (sargc > 0) { + size_t total = 0; + for (int i = 0; i < sargc; ++i) { + total += strlen(argv[sargi + i]) + 1; /* +1 for space or NUL */ + } + char *joined = (char*)malloc(total); + if (joined) { + joined[0] = '\0'; + for (int i = 0; i < sargc; ++i) { + strcat(joined, argv[sargi + i]); + if (i + 1 < sargc) strcat(joined, " "); + } + setenv("FUN_ARGS", joined, 1); + free(joined); + } + } else { + /* Ensure FUN_ARGS is at least cleared for consistency */ + setenv("FUN_ARGS", "", 1); + } + Bytecode *bc = parse_file_to_bytecode(path); if (!bc) { fprintf(stderr, "Failed to compile script: %s\n", path); diff --git a/src/fun_test.c b/src/fun_test.c index 64928f8..fa28b2c 100644 --- a/src/fun_test.c +++ b/src/fun_test.c @@ -11,6 +11,7 @@ #include "value.h" #include "vm.h" #include +#include #define ASSERT_EQ(val, expected) \ if ((val).type != VAL_INT || (val).i != (expected)) { \ @@ -33,6 +34,25 @@ int main(void) { int c3 = bytecode_add_constant(bc, make_int(3)); int c10 = bytecode_add_constant(bc, make_int(10)); int c42 = bytecode_add_constant(bc, make_int(42)); + int cf3_2 = bytecode_add_constant(bc, make_float(3.2)); + int cf3_5 = bytecode_add_constant(bc, make_float(3.5)); + int cf3_8 = bytecode_add_constant(bc, make_float(3.8)); + int cfn3_2 = bytecode_add_constant(bc, make_float(-3.2)); + int cfn3_5 = bytecode_add_constant(bc, make_float(-3.5)); + int cfn3_8 = bytecode_add_constant(bc, make_float(-3.8)); + int cf0 = bytecode_add_constant(bc, make_float(0.0)); + int cf1 = bytecode_add_constant(bc, make_float(1.0)); + int cf5 = bytecode_add_constant(bc, make_float(5.0)); + int c4 = bytecode_add_constant(bc, make_int(4)); + int c9 = bytecode_add_constant(bc, make_int(9)); + int c48 = bytecode_add_constant(bc, make_int(48)); + int c18 = bytecode_add_constant(bc, make_int(18)); + int c21 = bytecode_add_constant(bc, make_int(21)); + int c6 = bytecode_add_constant(bc, make_int(6)); + int c15 = bytecode_add_constant(bc, make_int(15)); + int c16 = bytecode_add_constant(bc, make_int(16)); + int cneg5 = bytecode_add_constant(bc, make_int(-5)); + int c7 = bytecode_add_constant(bc, make_int(7)); // ---------- Arithmetic ---------- bytecode_add_instruction(bc, OP_LOAD_CONST, c42); @@ -120,6 +140,156 @@ int main(void) { bytecode_add_instruction(bc, OP_LOAD_CONST, c1); bytecode_add_instruction(bc, OP_POP, 0); // dApache-2.0ard 1 (stack now empty) + // ---------- Rounding (math.h) demo ---------- + // floor/ceil/trunc/round on representative values + int start_round_demo = bc->instr_count; + (void)start_round_demo; + + // +3.2 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); + bytecode_add_instruction(bc, OP_FLOOR, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); + bytecode_add_instruction(bc, OP_CEIL, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); + bytecode_add_instruction(bc, OP_TRUNC, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); + bytecode_add_instruction(bc, OP_ROUND, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + + // +3.5 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_5); + bytecode_add_instruction(bc, OP_ROUND, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + + // -3.5 + bytecode_add_instruction(bc, OP_LOAD_CONST, cfn3_5); + bytecode_add_instruction(bc, OP_ROUND, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + + // -3.2 + bytecode_add_instruction(bc, OP_LOAD_CONST, cfn3_2); + bytecode_add_instruction(bc, OP_FLOOR, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, cfn3_2); + bytecode_add_instruction(bc, OP_CEIL, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + + // integers should be unchanged + bytecode_add_instruction(bc, OP_LOAD_CONST, c10); + bytecode_add_instruction(bc, OP_FLOOR, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + + // ---------- Transcendentals demo ---------- + // sin(0)=0 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf0); + bytecode_add_instruction(bc, OP_SIN, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // cos(0)=1 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf0); + bytecode_add_instruction(bc, OP_COS, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // tan(0)=0 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf0); + bytecode_add_instruction(bc, OP_TAN, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // exp(0)=1 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf0); + bytecode_add_instruction(bc, OP_EXP, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // log(1)=0 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf1); + bytecode_add_instruction(bc, OP_LOG, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // log10(1)=0 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf1); + bytecode_add_instruction(bc, OP_LOG10, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // sqrt(9)=3 + bytecode_add_instruction(bc, OP_LOAD_CONST, c9); + bytecode_add_instruction(bc, OP_SQRT, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + + // ---------- Integer math (gcd/lcm/isqrt/sign) demo ---------- + // gcd(48,18)=6 + bytecode_add_instruction(bc, OP_LOAD_CONST, c48); + bytecode_add_instruction(bc, OP_LOAD_CONST, c18); + bytecode_add_instruction(bc, OP_GCD, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + + // lcm(21,6)=42 + bytecode_add_instruction(bc, OP_LOAD_CONST, c21); + bytecode_add_instruction(bc, OP_LOAD_CONST, c6); + bytecode_add_instruction(bc, OP_LCM, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + + // isqrt cases: 0, 1, 15->3, 16->4 + bytecode_add_instruction(bc, OP_LOAD_CONST, c0); + bytecode_add_instruction(bc, OP_ISQRT, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, c1); + bytecode_add_instruction(bc, OP_ISQRT, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, c15); + bytecode_add_instruction(bc, OP_ISQRT, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, c16); + bytecode_add_instruction(bc, OP_ISQRT, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + + // sign(-5)=-1, sign(0)=0, sign(7)=1 + bytecode_add_instruction(bc, OP_LOAD_CONST, cneg5); + bytecode_add_instruction(bc, OP_SIGN, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, c0); + bytecode_add_instruction(bc, OP_SIGN, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, c7); + bytecode_add_instruction(bc, OP_SIGN, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + + // ---------- fmin/fmax demo ---------- + // fmin(3.2, 4) -> 3.2 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); + bytecode_add_instruction(bc, OP_LOAD_CONST, c4); + bytecode_add_instruction(bc, OP_FMIN, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // fmax(3.2, 4) -> 4 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); + bytecode_add_instruction(bc, OP_LOAD_CONST, c4); + bytecode_add_instruction(bc, OP_FMAX, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // NaN cases + double nanv = NAN; + int cNaN = bytecode_add_constant(bc, make_float(nanv)); + // fmin(NaN, 5.0) -> 5.0 + bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN); + bytecode_add_instruction(bc, OP_LOAD_CONST, cf5); + bytecode_add_instruction(bc, OP_FMIN, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // fmax(NaN, 5.0) -> 5.0 + bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN); + bytecode_add_instruction(bc, OP_LOAD_CONST, cf5); + bytecode_add_instruction(bc, OP_FMAX, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // fmin(5.0, NaN) -> 5.0 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf5); + bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN); + bytecode_add_instruction(bc, OP_FMIN, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // fmax(5.0, NaN) -> 5.0 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf5); + bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN); + bytecode_add_instruction(bc, OP_FMAX, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // fmin(NaN, NaN) -> NaN (prints as nan) + bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN); + bytecode_add_instruction(bc, OP_LOAD_CONST, cNaN); + bytecode_add_instruction(bc, OP_FMIN, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // ---------- HALT ---------- bytecode_add_instruction(bc, OP_HALT, 0); diff --git a/src/parser.c b/src/parser.c index f5b69d9..d014a73 100644 --- a/src/parser.c +++ b/src/parser.c @@ -709,6 +709,20 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) free(name); return 1; } + if (strcmp(name, "input_hidden") == 0) { + (*pos)++; /* '(' */ + int hasPrompt = 0; + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] != ')') { + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "input_hidden expects 0 or 1 argument"); free(name); return 0; } + hasPrompt = 1; + } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after input_hidden arg(s)"); free(name); return 0; } + /* operand bit0 = hasPrompt, bit1 = hidden */ + bytecode_add_instruction(bc, OP_INPUT_LINE, (hasPrompt ? 1 : 0) | 2); + free(name); + return 1; + } if (strcmp(name, "proc_run") == 0) { (*pos)++; /* '(' */ if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "proc_run expects 1 argument (command string)"); free(name); return 0; } @@ -1728,6 +1742,22 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) free(name); return 1; } + if (strcmp(name, "fmin") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "fmin expects 2 args"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "fmin expects 2 args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_FMIN, 0); + free(name); + return 1; + } + if (strcmp(name, "fmax") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "fmax expects 2 args"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "fmax expects 2 args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_FMAX, 0); + free(name); + return 1; + } if (strcmp(name, "clamp") == 0) { (*pos)++; /* '(' */ if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "clamp expects 3 args"); free(name); return 0; } @@ -1744,6 +1774,113 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) free(name); return 1; } + if (strcmp(name, "floor") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "floor expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_FLOOR, 0); + free(name); + return 1; + } + if (strcmp(name, "ceil") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "ceil expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_CEIL, 0); + free(name); + return 1; + } + if (strcmp(name, "trunc") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "trunc expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_TRUNC, 0); + free(name); + return 1; + } + if (strcmp(name, "round") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "round expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_ROUND, 0); + free(name); + return 1; + } + if (strcmp(name, "sin") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "sin expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_SIN, 0); + free(name); + return 1; + } + if (strcmp(name, "cos") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "cos expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_COS, 0); + free(name); + return 1; + } + if (strcmp(name, "tan") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "tan expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_TAN, 0); + free(name); + return 1; + } + if (strcmp(name, "exp") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "exp expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_EXP, 0); + free(name); + return 1; + } + if (strcmp(name, "log") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "log expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_LOG, 0); + free(name); + return 1; + } + if (strcmp(name, "log10") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "log10 expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_LOG10, 0); + free(name); + return 1; + } + if (strcmp(name, "sqrt") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "sqrt expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_SQRT, 0); + free(name); + return 1; + } + if (strcmp(name, "gcd") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "gcd expects 2 args"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "gcd expects 2 args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_GCD, 0); + free(name); + return 1; + } + if (strcmp(name, "lcm") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "lcm expects 2 args"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "lcm expects 2 args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_LCM, 0); + free(name); + return 1; + } + if (strcmp(name, "isqrt") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "isqrt expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_ISQRT, 0); + free(name); + return 1; + } + if (strcmp(name, "sign") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "sign expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_SIGN, 0); + free(name); + return 1; + } if (strcmp(name, "pow") == 0) { (*pos)++; /* '(' */ if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "pow expects 2 args"); free(name); return 0; } diff --git a/src/vm.c b/src/vm.c index f2a4470..22a45c4 100644 --- a/src/vm.c +++ b/src/vm.c @@ -29,9 +29,15 @@ #include #include #include +/* For hidden input (password) handling in OP_INPUT_LINE */ +#include //#include #endif +#ifdef _WIN32 +#include +#endif + /* Bring in split-out built-ins without changing the build system yet */ #include "iter.c" #include "map.c" @@ -42,6 +48,11 @@ // Optional by extensions commonly used code. #ifdef's are in each single file. #include "external/curl.c" #include "external/ini.c" +#ifdef FUN_WITH_INI +/* Central INI handle registry and helpers */ +#include "vm/ini/handles.c" +#endif +/* Note: INI opcode handlers are included below; changes in vm/ini/*.c require vm.c to rebuild. */ #include "external/json.c" #include "external/libsql.c" #include "external/pcsc.c" @@ -661,10 +672,27 @@ void vm_run(VM *vm, Bytecode *entry) { #include "vm/math/clamp.c" #include "vm/math/max.c" #include "vm/math/min.c" + #include "vm/math/fmax.c" + #include "vm/math/fmin.c" #include "vm/math/mod.c" #include "vm/math/pow.c" + #include "vm/math/floor.c" + #include "vm/math/ceil.c" + #include "vm/math/trunc.c" + #include "vm/math/round.c" + #include "vm/math/sin.c" + #include "vm/math/cos.c" + #include "vm/math/tan.c" + #include "vm/math/exp.c" + #include "vm/math/log.c" + #include "vm/math/log10.c" + #include "vm/math/sqrt.c" #include "vm/math/random_int.c" #include "vm/math/random_seed.c" + #include "vm/math/gcd.c" + #include "vm/math/lcm.c" + #include "vm/math/isqrt.c" + #include "vm/math/sign.c" #include "vm/os/env.c" #include "vm/os/env_all.c" @@ -730,6 +758,8 @@ void vm_run(VM *vm, Bytecode *entry) { #include "vm/ini/set.c" #include "vm/ini/unset.c" #include "vm/ini/save.c" + #else + #include "vm/ini/stubs.c" #endif /* CURL ops */ diff --git a/src/vm/ini/get_bool.c b/src/vm/ini/get_bool.c index fa886fb..302e287 100644 --- a/src/vm/ini/get_bool.c +++ b/src/vm/ini/get_bool.c @@ -23,8 +23,37 @@ case OP_INI_GET_BOOL: { dictionary *d = ini_get(h); int outb = def; if (d && sec && key) { - char full[1024]; ini_make_full_key(full, sizeof(full), sec, key); - outb = iniparser_getboolean(d, full, def); + char full[1024]; char alt[1024]; + ini_make_full_key(full, sizeof(full), sec, key); + memcpy(alt, full, sizeof(alt)); + for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) { if (alt[i] == ':') { alt[i] = '.'; break; } } + const char *s = iniparser_getstring(d, full, NULL); + if (!s) s = iniparser_getstring(d, alt, NULL); + if (s) { + /* normalize and parse boolean */ + char buf[256]; + size_t n = strlen(s); + if (n >= 2 && ((s[0]=='"' && s[n-1]=='"') || (s[0]=='\'' && s[n-1]=='\''))) { + size_t copy = (n-2) < sizeof(buf)-1 ? (n-2) : sizeof(buf)-1; + memcpy(buf, s+1, copy); buf[copy] = '\0'; + s = buf; + } + /* trim spaces */ + while (*s && (unsigned char)*s <= ' ') s++; + /* lower copy for textual booleans */ + char lb[256]; size_t li=0; for (; s[li] && li < sizeof(lb)-1; ++li) lb[li] = (char)tolower((unsigned char)s[li]); lb[li]='\0'; + if (strcmp(lb, "true")==0 || strcmp(lb, "yes")==0 || strcmp(lb, "on")==0) { + outb = 1; + } else if (strcmp(lb, "false")==0 || strcmp(lb, "no")==0 || strcmp(lb, "off")==0) { + outb = 0; + } else { + /* numeric */ + char *endp=NULL; long v = strtol(lb, &endp, 10); + outb = (endp && endp!=lb) ? (v!=0) : def; + } + } else { + outb = def; + } } free_value(vdef); free_value(vkey); free_value(vsec); free_value(vh); push_value(vm, make_int(outb ? 1 : 0)); diff --git a/src/vm/ini/get_double.c b/src/vm/ini/get_double.c index 9cf676f..7896621 100644 --- a/src/vm/ini/get_double.c +++ b/src/vm/ini/get_double.c @@ -23,8 +23,27 @@ case OP_INI_GET_DOUBLE: { dictionary *d = ini_get(h); double outd = def; if (d && sec && key) { - char full[1024]; ini_make_full_key(full, sizeof(full), sec, key); - outd = iniparser_getdouble(d, full, def); + char full[1024]; char alt[1024]; + ini_make_full_key(full, sizeof(full), sec, key); + memcpy(alt, full, sizeof(alt)); + for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) { if (alt[i] == ':') { alt[i] = '.'; break; } } + const char *s = iniparser_getstring(d, full, NULL); + if (!s) s = iniparser_getstring(d, alt, NULL); + if (s) { + char buf[256]; + size_t n = strlen(s); + if (n >= 2 && ((s[0]=='"' && s[n-1]=='"') || (s[0]=='\'' && s[n-1]=='\''))) { + size_t copy = (n-2) < sizeof(buf)-1 ? (n-2) : sizeof(buf)-1; + memcpy(buf, s+1, copy); buf[copy] = '\0'; + s = buf; + } + while (*s && (unsigned char)*s <= ' ') s++; + char *endp = NULL; + double v = strtod(s, &endp); + if (endp && endp != s) outd = v; else outd = def; + } else { + outd = def; + } } free_value(vdef); free_value(vkey); free_value(vsec); free_value(vh); push_value(vm, make_float(outd)); diff --git a/src/vm/ini/get_int.c b/src/vm/ini/get_int.c index 930bea9..4577ca3 100644 --- a/src/vm/ini/get_int.c +++ b/src/vm/ini/get_int.c @@ -23,8 +23,29 @@ case OP_INI_GET_INT: { dictionary *d = ini_get(h); int outi = def; if (d && sec && key) { - char full[1024]; ini_make_full_key(full, sizeof(full), sec, key); - outi = iniparser_getint(d, full, def); + char full[1024]; char alt[1024]; + ini_make_full_key(full, sizeof(full), sec, key); + memcpy(alt, full, sizeof(alt)); + for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) { if (alt[i] == ':') { alt[i] = '.'; break; } } + const char *s = iniparser_getstring(d, full, NULL); + if (!s) s = iniparser_getstring(d, alt, NULL); + if (s) { + /* strip optional quotes and parse */ + char buf[256]; + size_t n = strlen(s); + if (n >= 2 && ((s[0]=='"' && s[n-1]=='"') || (s[0]=='\'' && s[n-1]=='\''))) { + size_t copy = (n-2) < sizeof(buf)-1 ? (n-2) : sizeof(buf)-1; + memcpy(buf, s+1, copy); buf[copy] = '\0'; + s = buf; + } + /* skip leading spaces */ + while (*s && (unsigned char)*s <= ' ') s++; + char *endp = NULL; + long v = strtol(s, &endp, 10); + if (endp && endp != s) outi = (int)v; else outi = def; + } else { + outi = def; + } } free_value(vdef); free_value(vkey); free_value(vsec); free_value(vh); push_value(vm, make_int(outi)); diff --git a/src/vm/ini/get_string.c b/src/vm/ini/get_string.c index fcfa36f..ef44106 100644 --- a/src/vm/ini/get_string.c +++ b/src/vm/ini/get_string.c @@ -23,8 +23,19 @@ case OP_INI_GET_STRING: { dictionary *d = ini_get(h); const char *res = def; if (d && sec && key) { - char full[1024]; ini_make_full_key(full, sizeof(full), sec, key); + char full[1024]; char alt[1024]; + ini_make_full_key(full, sizeof(full), sec, key); + /* Build alternate with dot separator for robustness */ + ini_make_full_key(alt, sizeof(alt), sec, key); + size_t flen = strlen(full); + if (flen < sizeof(alt) && flen > 0) { /* create dot version in alt */ + memcpy(alt, full, flen + 1); + for (size_t i = 0; i < flen; ++i) if (alt[i] == ':') { alt[i] = '.'; break; } + } const char *s = iniparser_getstring(d, full, def); + if (s == def) { /* not found, try alternate dot form */ + s = iniparser_getstring(d, alt, def); + } res = s ? s : ""; } free_value(vdef); free_value(vkey); free_value(vsec); free_value(vh); diff --git a/src/vm/ini/handles.c b/src/vm/ini/handles.c new file mode 100644 index 0000000..2515410 --- /dev/null +++ b/src/vm/ini/handles.c @@ -0,0 +1,63 @@ +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + */ + +#ifdef FUN_WITH_INI +#if defined(__has_include) +# if __has_include() +# include +# include +# elif __has_include() +# include +# include +# else +# error "iniparser headers not found" +# endif +#else +# include +# include +#endif + +#include +#include +#include + +#include "handles.h" + +IniSlot g_ini[64]; + +int ini_alloc_handle(dictionary *d) { + if (!d) return 0; + for (int i = 1; i < (int)(sizeof(g_ini)/sizeof(g_ini[0])); ++i) { + if (!g_ini[i].in_use) { g_ini[i].in_use = 1; g_ini[i].dict = d; return i; } + } + return 0; +} + +dictionary* ini_get(int h) { + if (h > 0 && h < (int)(sizeof(g_ini)/sizeof(g_ini[0])) && g_ini[h].in_use) return g_ini[h].dict; + return NULL; +} + +int ini_free_handle(int h) { + if (h <= 0 || h >= (int)(sizeof(g_ini)/sizeof(g_ini[0])) || !g_ini[h].in_use) return 0; + if (g_ini[h].dict) iniparser_freedict(g_ini[h].dict); + g_ini[h].dict = NULL; + g_ini[h].in_use = 0; + return 1; +} + +void ini_make_full_key(char *buf, size_t cap, const char *sec, const char *key) { + if (!buf || cap == 0) return; + if (!sec) sec = ""; + if (!key) key = ""; + /* iniparser expects section:key; lookup is case-insensitive internally */ + snprintf(buf, cap, "%s:%s", sec, key); +} + +#endif /* FUN_WITH_INI */ diff --git a/src/vm/ini/handles.h b/src/vm/ini/handles.h index 3c2022f..831936c 100644 --- a/src/vm/ini/handles.h +++ b/src/vm/ini/handles.h @@ -27,35 +27,18 @@ # include # include #endif -#include /* snprintf for helper */ +#include typedef struct { dictionary *dict; int in_use; } IniSlot; -static IniSlot g_ini[64]; -static int ini_alloc_handle(dictionary *d) { - for (int i = 1; i < (int)(sizeof(g_ini)/sizeof(g_ini[0])); ++i) { - if (!g_ini[i].in_use) { g_ini[i].in_use = 1; g_ini[i].dict = d; return i; } - } - return 0; -} -static dictionary* ini_get(int h) { - if (h > 0 && h < (int)(sizeof(g_ini)/sizeof(g_ini[0])) && g_ini[h].in_use) return g_ini[h].dict; - return NULL; -} -static int ini_free_handle(int h) { - if (h <= 0 || h >= (int)(sizeof(g_ini)/sizeof(g_ini[0])) || !g_ini[h].in_use) return 0; - if (g_ini[h].dict) iniparser_freedict(g_ini[h].dict); - g_ini[h].dict = NULL; - g_ini[h].in_use = 0; - return 1; -} +/* Single global registry (defined in handles.c) */ +extern IniSlot g_ini[64]; -/* Helper to build section:key string safely into provided buffer */ -static inline void ini_make_full_key(char *buf, size_t cap, const char *sec, const char *key) { - if (!buf || cap == 0) return; - if (!sec) sec = ""; - if (!key) key = ""; - /* iniparser expects "section:key" */ - snprintf(buf, cap, "%s:%s", sec, key); -} +/* Registry API (implemented in handles.c) */ +int ini_alloc_handle(dictionary *d); +dictionary* ini_get(int h); +int ini_free_handle(int h); + +/* Helper to build section:key string safely into provided buffer (implemented in handles.c) */ +void ini_make_full_key(char *buf, size_t cap, const char *sec, const char *key); #endif /* FUN_WITH_INI */ diff --git a/src/vm/ini/set.c b/src/vm/ini/set.c index a0affd9..eee9660 100644 --- a/src/vm/ini/set.c +++ b/src/vm/ini/set.c @@ -23,9 +23,16 @@ case OP_INI_SET: { if (d && sec && key) { char *valstr = value_to_string_alloc(&vval); if (valstr) { - char full[1024]; snprintf(full, sizeof(full), "%s:%s", sec, key); + char full[1024]; char alt[1024]; + ini_make_full_key(full, sizeof(full), sec, key); + memcpy(alt, full, sizeof(alt)); + for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) { if (alt[i] == ':') { alt[i] = '.'; break; } } /* iniparser 4.x does not expose iniparser_set; use dictionary_set */ - if (dictionary_set(d, full, valstr) == 0) ok = 1; /* 0 means success */ + if (dictionary_set(d, full, valstr) == 0) { + ok = 1; /* 0 means success */ + } else if (dictionary_set(d, alt, valstr) == 0) { + ok = 1; + } free(valstr); } } diff --git a/src/vm/ini/stubs.c b/src/vm/ini/stubs.c new file mode 100644 index 0000000..1d13fb7 --- /dev/null +++ b/src/vm/ini/stubs.c @@ -0,0 +1,104 @@ +/* + * This file provides stub handlers for INI opcodes when FUN_WITH_INI is disabled. + * Each opcode reports a clear runtime error and returns a safe default. + */ + +/* OP_INI_LOAD: pops path string; pushes 0 (invalid handle) */ +case OP_INI_LOAD: { + Value vpath = pop_value(vm); + (void)vpath; /* unused */ + free_value(vpath); + fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); + push_value(vm, make_int(0)); + break; +} + +/* OP_INI_FREE: pops handle; pushes 0 */ +case OP_INI_FREE: { + Value vh = pop_value(vm); + free_value(vh); + fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); + push_value(vm, make_int(0)); + break; +} + +/* Getters: pop args; push defaults (string:"", int:0, double:0.0, bool:0) */ +case OP_INI_GET_STRING: { + Value vdef = pop_value(vm); + Value vkey = pop_value(vm); + Value vsec = pop_value(vm); + Value vh = pop_value(vm); + free_value(vh); free_value(vsec); free_value(vkey); + /* cannot convert here; return empty string */ + push_value(vm, make_string("")); + free_value(vdef); + fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); + break; +} + +case OP_INI_GET_INT: { + Value vdef = pop_value(vm); + Value vkey = pop_value(vm); + Value vsec = pop_value(vm); + Value vh = pop_value(vm); + free_value(vh); free_value(vsec); free_value(vkey); + (void)vdef; /* unused */ + push_value(vm, make_int(0)); + fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); + break; +} + +case OP_INI_GET_DOUBLE: { + Value vdef = pop_value(vm); + Value vkey = pop_value(vm); + Value vsec = pop_value(vm); + Value vh = pop_value(vm); + free_value(vh); free_value(vsec); free_value(vkey); + (void)vdef; /* unused */ + push_value(vm, make_float(0.0)); + fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); + break; +} + +case OP_INI_GET_BOOL: { + Value vdef = pop_value(vm); + Value vkey = pop_value(vm); + Value vsec = pop_value(vm); + Value vh = pop_value(vm); + free_value(vh); free_value(vsec); free_value(vkey); + (void)vdef; /* unused */ + push_value(vm, make_int(0)); + fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); + break; +} + +/* Mutators: return 0 */ +case OP_INI_SET: { + Value vval = pop_value(vm); + Value vkey = pop_value(vm); + Value vsec = pop_value(vm); + Value vh = pop_value(vm); + free_value(vh); free_value(vsec); free_value(vkey); free_value(vval); + fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); + push_value(vm, make_int(0)); + break; +} + +case OP_INI_UNSET: { + Value vkey = pop_value(vm); + Value vsec = pop_value(vm); + Value vh = pop_value(vm); + free_value(vh); free_value(vsec); free_value(vkey); + fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); + push_value(vm, make_int(0)); + break; +} + +case OP_INI_SAVE: { + Value vpath = pop_value(vm); + Value vh = pop_value(vm); + free_value(vh); free_value(vpath); + fun_vm_fprintf(stderr, "Runtime error: INI support disabled (rebuild with -DFUN_WITH_INI=ON)\n"); + push_value(vm, make_int(0)); + break; +} diff --git a/src/vm/ini/unset.c b/src/vm/ini/unset.c index d264f72..8c339c7 100644 --- a/src/vm/ini/unset.c +++ b/src/vm/ini/unset.c @@ -20,9 +20,13 @@ case OP_INI_UNSET: { const char *sec = (vsec.type==VAL_STRING)?vsec.s:NULL; int ok = 0; if (d && sec && key) { - char full[1024]; snprintf(full, sizeof(full), "%s:%s", sec, key); - /* iniparser 4.2.6 dictionary_unset returns void; assume success if inputs are valid */ + char full[1024]; char alt[1024]; + ini_make_full_key(full, sizeof(full), sec, key); + memcpy(alt, full, sizeof(alt)); + for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) { if (alt[i] == ':') { alt[i] = '.'; break; } } + /* iniparser 4.2.6 dictionary_unset returns void; remove both forms */ dictionary_unset(d, full); + dictionary_unset(d, alt); ok = 1; } free_value(vkey); free_value(vsec); free_value(vh); diff --git a/src/vm/io/input_line.c b/src/vm/io/input_line.c index 8c624d2..938dda9 100644 --- a/src/vm/io/input_line.c +++ b/src/vm/io/input_line.c @@ -1,6 +1,10 @@ case OP_INPUT_LINE: { - /* operand: 0 = no prompt; 1 = has prompt (string or any value convertible to string) */ - int has_prompt = inst.operand ? 1 : 0; + /* operand bit flags: + * bit0 (1): has prompt (string or any value convertible to string) — top of stack holds prompt when set + * bit1 (2): hidden input (do not echo typed characters) + */ + int has_prompt = (inst.operand & 1) ? 1 : 0; + int hidden = (inst.operand & 2) ? 1 : 0; if (has_prompt) { /* pop prompt value and print without newline */ Value pv = pop_value(vm); @@ -13,6 +17,35 @@ case OP_INPUT_LINE: { free_value(pv); } + /* For hidden input, temporarily disable terminal echo if possible */ + int echo_disabled = 0; +#ifdef _WIN32 + if (hidden) { + HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); + if (hStdin != INVALID_HANDLE_VALUE) { + DWORD mode; + if (GetConsoleMode(hStdin, &mode)) { + DWORD newMode = mode & ~(ENABLE_ECHO_INPUT); + if (SetConsoleMode(hStdin, newMode)) { + echo_disabled = 1; + } + } + } + } +#else + if (hidden) { + /* POSIX termios */ + struct termios oldt; + if (tcgetattr(STDIN_FILENO, &oldt) == 0) { + struct termios newt = oldt; + newt.c_lflag &= ~(ECHO); + if (tcsetattr(STDIN_FILENO, TCSANOW, &newt) == 0) { + echo_disabled = 1; + } + } + } +#endif + /* read a line from stdin, dynamically grow buffer */ size_t cap = 128; size_t len = 0; @@ -20,7 +53,8 @@ case OP_INPUT_LINE: { if (!buf) { fprintf(stderr, "Runtime error: out of memory reading input"); push_value(vm, make_string("")); - break; + /* On early exit, try to restore echo if we turned it off */ + goto restore_echo_and_break; } int ch; @@ -68,5 +102,36 @@ case OP_INPUT_LINE: { free(buf); push_done: + /* If we disabled echo, restore terminal settings and print a newline for UX */ +#ifdef _WIN32 + if (echo_disabled) { + HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); + if (hStdin != INVALID_HANDLE_VALUE) { + DWORD mode; + if (GetConsoleMode(hStdin, &mode)) { + /* Re-enable ECHO flag */ + mode |= ENABLE_ECHO_INPUT; + SetConsoleMode(hStdin, mode); + } + } + if (has_prompt) { + fputc('\n', stdout); + fflush(stdout); + } + } +#else + if (echo_disabled) { + struct termios t; + if (tcgetattr(STDIN_FILENO, &t) == 0) { + t.c_lflag |= ECHO; + tcsetattr(STDIN_FILENO, TCSANOW, &t); + } + if (has_prompt) { + fputc('\n', stdout); + fflush(stdout); + } + } +#endif +restore_echo_and_break: break; } diff --git a/src/vm/math/ceil.c b/src/vm/math/ceil.c new file mode 100644 index 0000000..bda8d29 --- /dev/null +++ b/src/vm/math/ceil.c @@ -0,0 +1,43 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +/** +* @file ceil.c + * @brief Implements the OP_CEIL opcode using C99 math.h ceil(). + */ + +#include + +case OP_CEIL: { + Value v = pop_value(vm); + if (v.type == VAL_INT) { + /* ceil(n) == n for integers */ + push_value(vm, make_int(v.i)); + free_value(v); + } else if (v.type == VAL_FLOAT) { + double r = ceil(v.d); + if (r >= (double)INT64_MIN && r <= (double)INT64_MAX) { + int64_t ii = (int64_t)r; + if ((double)ii == r) { + push_value(vm, make_int(ii)); + } else { + push_value(vm, make_float(r)); + } + } else { + push_value(vm, make_float(r)); + } + free_value(v); + } else { + fprintf(stderr, "Runtime type error: CEIL expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; +} diff --git a/src/vm/math/cos.c b/src/vm/math/cos.c new file mode 100644 index 0000000..b5651f0 --- /dev/null +++ b/src/vm/math/cos.c @@ -0,0 +1,31 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +/** +* @file cos.c + * @brief Implements the OP_COS opcode using C99 math.h cos(). + */ + +#include + +case OP_COS: { + Value v = pop_value(vm); + if (v.type == VAL_INT || v.type == VAL_FLOAT) { + double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; + double r = cos(x); + push_value(vm, make_float(r)); + free_value(v); + } else { + fprintf(stderr, "Runtime type error: COS expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; +} diff --git a/src/vm/math/exp.c b/src/vm/math/exp.c new file mode 100644 index 0000000..8ad99c2 --- /dev/null +++ b/src/vm/math/exp.c @@ -0,0 +1,31 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +/** +* @file exp.c + * @brief Implements the OP_EXP opcode using C99 math.h exp(). + */ + +#include + +case OP_EXP: { + Value v = pop_value(vm); + if (v.type == VAL_INT || v.type == VAL_FLOAT) { + double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; + double r = exp(x); + push_value(vm, make_float(r)); + free_value(v); + } else { + fprintf(stderr, "Runtime type error: EXP expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; +} diff --git a/src/vm/math/floor.c b/src/vm/math/floor.c new file mode 100644 index 0000000..72e2440 --- /dev/null +++ b/src/vm/math/floor.c @@ -0,0 +1,43 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +/** +* @file floor.c + * @brief Implements the OP_FLOOR opcode using C99 math.h floor(). + */ + +#include + +case OP_FLOOR: { + Value v = pop_value(vm); + if (v.type == VAL_INT) { + /* floor(n) == n for integers */ + push_value(vm, make_int(v.i)); + free_value(v); + } else if (v.type == VAL_FLOAT) { + double r = floor(v.d); + if (r >= (double)INT64_MIN && r <= (double)INT64_MAX) { + int64_t ii = (int64_t)r; + if ((double)ii == r) { + push_value(vm, make_int(ii)); + } else { + push_value(vm, make_float(r)); + } + } else { + push_value(vm, make_float(r)); + } + free_value(v); + } else { + fprintf(stderr, "Runtime type error: FLOOR expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; +} diff --git a/src/vm/math/fmax.c b/src/vm/math/fmax.c new file mode 100644 index 0000000..b8504ba --- /dev/null +++ b/src/vm/math/fmax.c @@ -0,0 +1,41 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +/** +* @file fmax.c + * @brief Implements the OP_FMAX opcode using C99 math.h fmax(). + * Accepts int or float; follows IEEE-754 NaN handling per fmax. + */ + +#include + +case OP_FMAX: { + Value b = pop_value(vm); + Value a = pop_value(vm); + if (!((a.type == VAL_INT || a.type == VAL_FLOAT) && (b.type == VAL_INT || b.type == VAL_FLOAT))) { + fprintf(stderr, "Runtime type error: FMAX expects numbers, got %s and %s\n", + value_type_name(a.type), value_type_name(b.type)); + exit(1); + } + double da = (a.type == VAL_FLOAT) ? a.d : (double)a.i; + double db = (b.type == VAL_FLOAT) ? b.d : (double)b.i; + double r = fmax(da, db); + Value out; + if (!isnan(r) && !isinf(r) && r >= (double)INT64_MIN && r <= (double)INT64_MAX) { + int64_t ii = (int64_t)r; + if ((double)ii == r) out = make_int(ii); else out = make_float(r); + } else { + out = make_float(r); + } + free_value(a); free_value(b); + push_value(vm, out); + break; +} diff --git a/src/vm/math/fmin.c b/src/vm/math/fmin.c new file mode 100644 index 0000000..ded0719 --- /dev/null +++ b/src/vm/math/fmin.c @@ -0,0 +1,41 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +/** +* @file fmin.c + * @brief Implements the OP_FMIN opcode using C99 math.h fmin(). + * Accepts int or float; follows IEEE-754 NaN handling per fmin. + */ + +#include + +case OP_FMIN: { + Value b = pop_value(vm); + Value a = pop_value(vm); + if (!((a.type == VAL_INT || a.type == VAL_FLOAT) && (b.type == VAL_INT || b.type == VAL_FLOAT))) { + fprintf(stderr, "Runtime type error: FMIN expects numbers, got %s and %s\n", + value_type_name(a.type), value_type_name(b.type)); + exit(1); + } + double da = (a.type == VAL_FLOAT) ? a.d : (double)a.i; + double db = (b.type == VAL_FLOAT) ? b.d : (double)b.i; + double r = fmin(da, db); + Value out; + if (!isnan(r) && !isinf(r) && r >= (double)INT64_MIN && r <= (double)INT64_MAX) { + int64_t ii = (int64_t)r; + if ((double)ii == r) out = make_int(ii); else out = make_float(r); + } else { + out = make_float(r); + } + free_value(a); free_value(b); + push_value(vm, out); + break; +} diff --git a/src/vm/math/gcd.c b/src/vm/math/gcd.c new file mode 100644 index 0000000..106a5e4 --- /dev/null +++ b/src/vm/math/gcd.c @@ -0,0 +1,40 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +/** +* @file gcd.c + * @brief Implements the OP_GCD opcode for greatest common divisor. + */ + +case OP_GCD: { + Value vb = pop_value(vm); + Value va = pop_value(vm); + if (!((va.type == VAL_INT) || (va.type == VAL_FLOAT)) || + !((vb.type == VAL_INT) || (vb.type == VAL_FLOAT))) { + fprintf(stderr, "Runtime type error: GCD expects numbers, got %s and %s\n", + value_type_name(va.type), value_type_name(vb.type)); + exit(1); + } + int64_t a = (va.type == VAL_INT) ? va.i : (int64_t)va.d; + int64_t b = (vb.type == VAL_INT) ? vb.i : (int64_t)vb.d; + if (a == INT64_MIN) a = (int64_t)INT64_MAX; else if (a < 0) a = -a; + if (b == INT64_MIN) b = (int64_t)INT64_MAX; else if (b < 0) b = -b; + while (b != 0) { + int64_t t = a % b; + a = b; + b = t; + } + Value res = make_int(a); + free_value(va); + free_value(vb); + push_value(vm, res); + break; +} diff --git a/src/vm/math/isqrt.c b/src/vm/math/isqrt.c new file mode 100644 index 0000000..6c60f29 --- /dev/null +++ b/src/vm/math/isqrt.c @@ -0,0 +1,47 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +/** +* @file isqrt.c + * @brief Implements the OP_ISQRT opcode for integer square root (floor). + */ + +case OP_ISQRT: { + Value v = pop_value(vm); + if (!((v.type == VAL_INT) || (v.type == VAL_FLOAT))) { + fprintf(stderr, "Runtime type error: ISQRT expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + int64_t a = (v.type == VAL_INT) ? v.i : (int64_t)v.d; + if (a <= 0) { + free_value(v); + push_value(vm, make_int(0)); + break; + } + /* Binary isqrt inline */ + uint64_t n = (uint64_t)a; + uint64_t x = 0; + uint64_t bit = (uint64_t)1 << 62; /* highest even bit set */ + while (bit > n) bit >>= 2; + while (bit != 0) { + if (n >= x + bit) { + n -= x + bit; + x = (x >> 1) + bit; + } else { + x >>= 1; + } + bit >>= 2; + } + int64_t r = (int64_t)x; + free_value(v); + push_value(vm, make_int(r)); + break; +} diff --git a/src/vm/math/lcm.c b/src/vm/math/lcm.c new file mode 100644 index 0000000..b477c3e --- /dev/null +++ b/src/vm/math/lcm.c @@ -0,0 +1,48 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +/** +* @file lcm.c + * @brief Implements the OP_LCM opcode for least common multiple. + */ + +case OP_LCM: { + Value vb = pop_value(vm); + Value va = pop_value(vm); + if (!((va.type == VAL_INT) || (va.type == VAL_FLOAT)) || + !((vb.type == VAL_INT) || (vb.type == VAL_FLOAT))) { + fprintf(stderr, "Runtime type error: LCM expects numbers, got %s and %s\n", + value_type_name(va.type), value_type_name(vb.type)); + exit(1); + } + int64_t a = (va.type == VAL_INT) ? va.i : (int64_t)va.d; + int64_t b = (vb.type == VAL_INT) ? vb.i : (int64_t)vb.d; + if (a == INT64_MIN) a = (int64_t)INT64_MAX; else if (a < 0) a = -a; + if (b == INT64_MIN) b = (int64_t)INT64_MAX; else if (b < 0) b = -b; + if (a == 0 || b == 0) { + free_value(va); free_value(vb); + push_value(vm, make_int(0)); + break; + } + /* gcd(a,b) */ + int64_t x = a, y = b; + while (y != 0) { + int64_t t = x % y; x = y; y = t; + } + int64_t g = x; + /* lcm = (a/g)*b (attempt to reduce overflow) */ + int64_t l = (a / g) * b; + Value res = make_int(l); + free_value(va); + free_value(vb); + push_value(vm, res); + break; +} diff --git a/src/vm/math/log.c b/src/vm/math/log.c new file mode 100644 index 0000000..e8bd665 --- /dev/null +++ b/src/vm/math/log.c @@ -0,0 +1,36 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +/** +* @file log.c + * @brief Implements the OP_LOG opcode using C99 math.h log() (natural logarithm). + */ + +#include + +case OP_LOG: { + Value v = pop_value(vm); + if (v.type == VAL_INT || v.type == VAL_FLOAT) { + double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; + if (x <= 0.0) { + /* Domain error: return NaN to signal invalid input */ + push_value(vm, make_float(NAN)); + } else { + double r = log(x); + push_value(vm, make_float(r)); + } + free_value(v); + } else { + fprintf(stderr, "Runtime type error: LOG expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; +} diff --git a/src/vm/math/log10.c b/src/vm/math/log10.c new file mode 100644 index 0000000..0da07e1 --- /dev/null +++ b/src/vm/math/log10.c @@ -0,0 +1,35 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +/** +* @file log10.c + * @brief Implements the OP_LOG10 opcode using C99 math.h log10(). + */ + +#include + +case OP_LOG10: { + Value v = pop_value(vm); + if (v.type == VAL_INT || v.type == VAL_FLOAT) { + double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; + if (x <= 0.0) { + push_value(vm, make_float(NAN)); + } else { + double r = log10(x); + push_value(vm, make_float(r)); + } + free_value(v); + } else { + fprintf(stderr, "Runtime type error: LOG10 expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; +} diff --git a/src/vm/math/round.c b/src/vm/math/round.c new file mode 100644 index 0000000..3f830ff --- /dev/null +++ b/src/vm/math/round.c @@ -0,0 +1,43 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +/** +* @file round.c + * @brief Implements the OP_ROUND opcode using C99 math.h round(). + * C99 round() rounds half away from zero. + */ + +#include + +case OP_ROUND: { + Value v = pop_value(vm); + if (v.type == VAL_INT) { + push_value(vm, make_int(v.i)); + free_value(v); + } else if (v.type == VAL_FLOAT) { + double r = round(v.d); + if (r >= (double)INT64_MIN && r <= (double)INT64_MAX) { + int64_t ii = (int64_t)r; + if ((double)ii == r) { + push_value(vm, make_int(ii)); + } else { + push_value(vm, make_float(r)); + } + } else { + push_value(vm, make_float(r)); + } + free_value(v); + } else { + fprintf(stderr, "Runtime type error: ROUND expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; +} diff --git a/src/vm/math/sign.c b/src/vm/math/sign.c new file mode 100644 index 0000000..e54a787 --- /dev/null +++ b/src/vm/math/sign.c @@ -0,0 +1,33 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +/** +* @file sign.c + * @brief Implements the OP_SIGN opcode returning -1, 0, or 1. + */ + +case OP_SIGN: { + Value v = pop_value(vm); + int out = 0; + if (v.type == VAL_INT) { + out = (v.i > 0) - (v.i < 0); + } else if (v.type == VAL_FLOAT) { + if (v.d > 0.0) out = 1; + else if (v.d < 0.0) out = -1; + else out = 0; + } else { + fprintf(stderr, "Runtime type error: SIGN expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + free_value(v); + push_value(vm, make_int(out)); + break; +} diff --git a/src/vm/math/sin.c b/src/vm/math/sin.c new file mode 100644 index 0000000..aa989b2 --- /dev/null +++ b/src/vm/math/sin.c @@ -0,0 +1,31 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +/** +* @file sin.c + * @brief Implements the OP_SIN opcode using C99 math.h sin(). + */ + +#include + +case OP_SIN: { + Value v = pop_value(vm); + if (v.type == VAL_INT || v.type == VAL_FLOAT) { + double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; + double r = sin(x); + push_value(vm, make_float(r)); + free_value(v); + } else { + fprintf(stderr, "Runtime type error: SIN expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; +} diff --git a/src/vm/math/sqrt.c b/src/vm/math/sqrt.c new file mode 100644 index 0000000..aaffee2 --- /dev/null +++ b/src/vm/math/sqrt.c @@ -0,0 +1,45 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +/** +* @file sqrt.c + * @brief Implements the OP_SQRT opcode using C99 math.h sqrt(). + */ + +#include + +case OP_SQRT: { + Value v = pop_value(vm); + if (v.type == VAL_INT || v.type == VAL_FLOAT) { + double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; + if (x < 0.0) { + push_value(vm, make_float(NAN)); + } else { + double r = sqrt(x); + /* preserve int if exactly integral */ + if (r >= (double)INT64_MIN && r <= (double)INT64_MAX) { + int64_t ii = (int64_t)r; + if ((double)ii == r) { + push_value(vm, make_int(ii)); + } else { + push_value(vm, make_float(r)); + } + } else { + push_value(vm, make_float(r)); + } + } + free_value(v); + } else { + fprintf(stderr, "Runtime type error: SQRT expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; +} diff --git a/src/vm/math/tan.c b/src/vm/math/tan.c new file mode 100644 index 0000000..dd7ede3 --- /dev/null +++ b/src/vm/math/tan.c @@ -0,0 +1,31 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +/** +* @file tan.c + * @brief Implements the OP_TAN opcode using C99 math.h tan(). + */ + +#include + +case OP_TAN: { + Value v = pop_value(vm); + if (v.type == VAL_INT || v.type == VAL_FLOAT) { + double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; + double r = tan(x); + push_value(vm, make_float(r)); + free_value(v); + } else { + fprintf(stderr, "Runtime type error: TAN expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; +} diff --git a/src/vm/math/trunc.c b/src/vm/math/trunc.c new file mode 100644 index 0000000..7442074 --- /dev/null +++ b/src/vm/math/trunc.c @@ -0,0 +1,42 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +/** +* @file trunc.c + * @brief Implements the OP_TRUNC opcode using C99 math.h trunc(). + */ + +#include + +case OP_TRUNC: { + Value v = pop_value(vm); + if (v.type == VAL_INT) { + push_value(vm, make_int(v.i)); + free_value(v); + } else if (v.type == VAL_FLOAT) { + double r = trunc(v.d); + if (r >= (double)INT64_MIN && r <= (double)INT64_MAX) { + int64_t ii = (int64_t)r; + if ((double)ii == r) { + push_value(vm, make_int(ii)); + } else { + push_value(vm, make_float(r)); + } + } else { + push_value(vm, make_float(r)); + } + free_value(v); + } else { + fprintf(stderr, "Runtime type error: TRUNC expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; +}