1
0
Fork 0
forked from fun/fun

Compare commits

..

15 commits

68 changed files with 2731 additions and 216 deletions

View file

@ -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 $<TARGET_FILE:fun>
DEPENDS fun
USES_TERMINAL
COMMENT "Run Fun REPL"
)
COMMAND $<TARGET_FILE:fun>
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=$<IF:$<BOOL:${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=$<IF:$<BOOL:${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
)

32
examples/cli_argv_dump.fun Executable file
View file

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

48
examples/cli_parse_example.fun Executable file
View file

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

View file

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

View file

@ -16,7 +16,10 @@ print("<h1>Fun Environment Information</h1>")
print("<p><strong>Current Path:</strong> " + env("PATH") + "</p>")
print("<p><strong>Current User:</strong> " + env("USER") + "</p>")
print("<p><strong>Time:</strong> " + date_format(time_now_ms(), "%Y-%m-%d %H:%M:%S") + "</p>")
print("<p><strong>Version:</strong> " + to_string(fun_version()) + "</p>")
print("<p><strong>Local Version:</strong> " + to_string(fun_version()) + "</p>")
res = proc_run("fun -V")
installed_version = res["out"]
print("<p><strong>Installed Version:</strong> " + to_string(substr(installed_version, 4, len(installed_version)-2)) + "</p>")
print("<h2>Full Environment:</h2>")
print("<ul>")
all_env = env_all()

0
examples/env_all.fun Normal file → Executable file
View file

View file

@ -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 <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2025-12-28
*/
#include <io/socket.fun>
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")

View file

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

View file

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

50
examples/ini_diag.fun Normal file
View file

@ -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 <you@hanez.org>
* 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", "<def>")
print("app:name => " + v1)
print("[try app:version]")
v2 = ini_get_string(h, "app", "version", "<def>")
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
*/

View file

@ -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 <io/console.fun>
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!
*/

View file

@ -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 <io/console.fun>
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!
*/

36
examples/math_ceil.fun Normal file
View file

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

30
examples/math_cos.fun Normal file
View file

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

34
examples/math_exp_log.fun Normal file
View file

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

36
examples/math_floor.fun Normal file
View file

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

View file

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

35
examples/math_gcd_lcm.fun Normal file
View file

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

38
examples/math_isqrt.fun Normal file
View file

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

36
examples/math_round.fun Normal file
View file

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

32
examples/math_sign.fun Normal file
View file

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

30
examples/math_sin.fun Normal file
View file

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

32
examples/math_sqrt.fun Normal file
View file

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

28
examples/math_tan.fun Normal file
View file

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

36
examples/math_trunc.fun Normal file
View file

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

View file

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

View file

@ -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 <crypt/ripemd160.fun>
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"))

66
lib/cli.fun Normal file
View file

@ -0,0 +1,66 @@
/*
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2025-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 }

View file

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

View file

@ -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 <strings.fun>
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)

View file

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

View file

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

View file

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

3
make
View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,13 @@
/**
/*
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* 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);

View file

@ -11,6 +11,7 @@
#include "value.h"
#include "vm.h"
#include <stdio.h>
#include <math.h>
#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);

View file

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

View file

@ -29,9 +29,15 @@
#include <netdb.h>
#include <sys/un.h>
#include <unistd.h>
/* For hidden input (password) handling in OP_INPUT_LINE */
#include <termios.h>
//#include <arpa/inet.h>
#endif
#ifdef _WIN32
#include <windows.h>
#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 */

View file

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

View file

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

View file

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

View file

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

63
src/vm/ini/handles.c Normal file
View file

@ -0,0 +1,63 @@
/*
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* 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(<iniparser/iniparser.h>)
# include <iniparser/iniparser.h>
# include <iniparser/dictionary.h>
# elif __has_include(<iniparser.h>)
# include <iniparser.h>
# include <dictionary.h>
# else
# error "iniparser headers not found"
# endif
#else
# include <iniparser/iniparser.h>
# include <iniparser/dictionary.h>
#endif
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#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 */

View file

@ -27,35 +27,18 @@
# include <iniparser/iniparser.h>
# include <iniparser/dictionary.h>
#endif
#include <stdio.h> /* snprintf for helper */
#include <stddef.h>
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 */

View file

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

104
src/vm/ini/stubs.c Normal file
View file

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

View file

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

View file

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

43
src/vm/math/ceil.c Normal file
View file

@ -0,0 +1,43 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* 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 <math.h>
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;
}

31
src/vm/math/cos.c Normal file
View file

@ -0,0 +1,31 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* 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 <math.h>
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;
}

31
src/vm/math/exp.c Normal file
View file

@ -0,0 +1,31 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* 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 <math.h>
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;
}

43
src/vm/math/floor.c Normal file
View file

@ -0,0 +1,43 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* 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 <math.h>
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;
}

41
src/vm/math/fmax.c Normal file
View file

@ -0,0 +1,41 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* 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 <math.h>
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;
}

41
src/vm/math/fmin.c Normal file
View file

@ -0,0 +1,41 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* 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 <math.h>
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;
}

40
src/vm/math/gcd.c Normal file
View file

@ -0,0 +1,40 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* 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;
}

47
src/vm/math/isqrt.c Normal file
View file

@ -0,0 +1,47 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* 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;
}

48
src/vm/math/lcm.c Normal file
View file

@ -0,0 +1,48 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* 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;
}

36
src/vm/math/log.c Normal file
View file

@ -0,0 +1,36 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* 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 <math.h>
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;
}

35
src/vm/math/log10.c Normal file
View file

@ -0,0 +1,35 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* 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 <math.h>
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;
}

43
src/vm/math/round.c Normal file
View file

@ -0,0 +1,43 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* 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 <math.h>
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;
}

33
src/vm/math/sign.c Normal file
View file

@ -0,0 +1,33 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* 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;
}

31
src/vm/math/sin.c Normal file
View file

@ -0,0 +1,31 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* 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 <math.h>
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;
}

45
src/vm/math/sqrt.c Normal file
View file

@ -0,0 +1,45 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* 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 <math.h>
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;
}

31
src/vm/math/tan.c Normal file
View file

@ -0,0 +1,31 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* 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 <math.h>
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;
}

42
src/vm/math/trunc.c Normal file
View file

@ -0,0 +1,42 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* 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 <math.h>
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;
}