1
0
Fork 0
forked from fun/fun

Compare commits

...

55 commits

Author SHA1 Message Date
d2fca3275a Some more Fun. Not fully tested! (0.37.13) 2025-12-13 03:25:34 +01:00
b16a42a4a7 Some echo()/print() fixes. (0.37.12) 2025-12-13 02:24:23 +01:00
87f540797b Added an OS based random number generator (RNG). (0.37.11) 2025-12-13 01:14:17 +01:00
1eacf9411c ./make script update. (0.37.10) 2025-12-12 04:52:18 +01:00
ea6553fb19 Added a simple Bash based ./make script to make development more fun. (0.37.9) 2025-12-12 02:34:47 +01:00
5b90ff497c Added support to build Fun using musl libc instead glibc on a glibc based system (-DFUN_USE_MUSL). Alpine will ignore this feature... ;) (0.37.8) 2025-12-12 00:56:52 +01:00
3f586a90e1 Added some basic static linking stuff to CMakeLists.txt for testing things. (-DFUN_LINK_STATIC=ON). (0.37.7) 2025-12-11 23:18:51 +01:00
4039abd10d Added new example that shows the usage of random_int() and random_seed(). Related parser fixes. (0.37.6) 2025-12-11 22:51:23 +01:00
40584f27f5 Some parser refactoring for more fun developing Fun. (0.37.5) 2025-12-11 19:49:35 +01:00
8b9b9249fd Just more Fun. (0.37.4) 2025-12-11 03:31:27 +01:00
2927f88340 Some more housekeeping. Destroys nothing, but makes some things easier. (0.37.3) 2025-12-11 03:14:20 +01:00
ebae80cf44 Some housekeeping. (0.37.2) 2025-12-11 01:28:38 +01:00
b2986075cb Some XML examples documentation fixes. (0.37.1) 2025-12-10 23:42:00 +01:00
26ddaf161e Added more exception handling after debugging ./examples/byte_for_demo.fun. (0.37.0) 2025-12-10 23:27:06 +01:00
ccd03d3bec Split ./src/vm/ini/* opcodes into seperate files. (0.36.3) 2025-12-10 18:32:03 +01:00
3722df654b Some permission updates. No code changes. (0.36.2) 2025-12-10 03:00:32 +01:00
9fabf86e61 Added echo to output data wthout a newline. (0.36.2) 2025-12-10 00:31:07 +01:00
b1e1160bff Added echo to output data wthout a newline. (0.36.2) 2025-12-10 00:30:24 +01:00
9dc8afc387 Added some more datetime stdlib Fun with examples. And some permission fixes. (0.36.1) 2025-12-10 00:00:46 +01:00
40d114cb9c Reverted some stupid file renames. No code changes. (0.36.0) 2025-12-09 23:28:28 +01:00
6da1861e12 Added very basic Tk (Tcl) support using. (0.36.0) 2025-12-09 23:13:12 +01:00
682bce05af Some content update. No Code changes. (0.35.2) 2025-12-09 05:55:23 +01:00
e482ec198a Some content update. No Code changes. (0.35.2) 2025-12-09 05:44:54 +01:00
66bed28023 Some minimal fixes for more Fun. (0.35.2) 2025-12-09 05:19:08 +01:00
db450a871c Added some more libxml2 examples to Fun. (0.35.1) 2025-12-09 04:31:41 +01:00
e65756226d Added very basic XML support using libxml2. (0.35.0) 2025-12-09 03:59:49 +01:00
ecf64d6ac2 Some content update. No Code changes. (0.34.0) 2025-11-30 03:56:47 +01:00
8fa142e763 Some more .ini Fun. (0.34.1) 2025-11-30 03:33:18 +01:00
fb5670a699 Added support for .ini files (iniparser). (0.34.0) 2025-11-30 02:44:14 +01:00
49261b96c2 Some content update. No Code changes. (0.33.0) 2025-11-28 18:42:11 +01:00
f05a3d3cde Some documentation update. No Code changes. (0.33.0) 2025-11-28 17:53:10 +01:00
ff01ebc8dc Some documentation update. No Code changes. (0.33.0) 2025-11-28 17:50:36 +01:00
c57b1cac30 Some documentation update. No Code changes. (0.33.0) 2025-11-28 17:47:27 +01:00
c22c35d07e README update. No code changes. (0.33.0) 2025-11-27 01:34:57 +01:00
424d2fd9df README update. No code changes. (0.33.0) 2025-11-27 01:31:11 +01:00
7ea3c9108a Permission updates. No code changes. (0.33.0) 2025-11-27 01:12:13 +01:00
11bfb8ee05 README update. No code changes. (0.33.0) 2025-11-27 01:08:59 +01:00
56712206d1 Added support for libSQL as an compatible alternative for SQLite. (0.33.0) 2025-11-27 00:42:47 +01:00
9416ec3457 Added SQLite support and updated the Handbook. (0.32.0) 2025-11-26 22:21:31 +01:00
3411a4d846 Added CRC32 and CRC32C classes to stdlib. (0.31.0) 2025-11-26 20:36:40 +01:00
e86062aaad Handbook update. No code changes. (0.30.0) 2025-11-26 00:57:45 +01:00
7659957c1f Handbook update. No code changes. (0.29.0) 2025-11-26 00:52:59 +01:00
4d82d73515 Added libcurl (cURL) support. (0.30.0) 2025-11-26 00:35:22 +01:00
bb848b6679 Permission fixes. No code changes (0.29.0) 2025-11-25 23:11:47 +01:00
d9804d9bf8 Added PCRE2 support. (0.29.0) 2025-11-25 22:52:43 +01:00
5c3d4f1247 README update. No code changes (0.28.0) 2025-11-25 21:33:02 +01:00
485477960b Renamed CODE_OF_CONDUCT.md to CODE_OF_CONDUCT_2_1.md. No code changes (0.28.0) 2025-11-25 20:39:02 +01:00
d5c7b91eb3 Added SEMANTIC_VERSIONING_2_0_0.md. No code changes (0.28.0) 2025-11-25 20:21:22 +01:00
786f1ddc06 README update. No code changes (0.28.0) 2025-11-25 16:21:24 +01:00
2312b9d0c3 README update. No code changes (0.28.0) 2025-11-25 15:14:21 +01:00
685c84c077 README update. No code changes(0.28.0) 2025-11-25 15:07:09 +01:00
56e3e22f06 README update. No code changes(0.28.0) 2025-11-25 15:06:37 +01:00
9c098eb92b handbook update... 2025-11-25 02:17:06 +01:00
d028434406 Small fix.# (0.28.0) 2025-11-25 02:07:16 +01:00
afb0e8cd3a Added JSON support. (0.28.0) 2025-11-25 00:51:48 +01:00
125 changed files with 7198 additions and 514 deletions

3
.gitignore vendored
View file

@ -5,8 +5,11 @@ CMakeCache.txt
.venv
build*
cmake*
database.sqlite
demo_*
dist/
downloaded.png
json.xml
lib/*.so
out/
src/*.o

View file

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.10)
project(fun VERSION 0.27.2 LANGUAGES C)
project(fun VERSION 0.37.13 LANGUAGES C)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
@ -24,9 +24,159 @@ if(NOT DEFINED DEFAULT_LIB_DIR OR DEFAULT_LIB_DIR STREQUAL "")
else()
set(DEFAULT_LIB_DIR "${DEFAULT_LIB_DIR}" CACHE PATH "Default library directory for Fun stdlib (override with -DDEFAULT_LIB_DIR=...)" FORCE)
endif()
# Optional: build using musl libc instead of glibc on Linux (OFF by default)
# Note: Switching CMAKE_C_COMPILER must happen before the first project() call.
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)
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)
else()
add_definitions(-DFUN_LIBC_GLIBC)
endif()
# Optional: build statically linked executables
# When enabled, prefer static libraries for all dependencies and request
# fully static linking for executables where the platform/toolchain allows it.
option(FUN_LINK_STATIC "Link fun and tests fully static where possible" OFF)
if(FUN_LINK_STATIC)
message(STATUS "Building Fun statically linked")
# Prefer static libraries for all add_library without explicit type
set(BUILD_SHARED_LIBS OFF)
# Make pkg-config choose static libs
set(PKG_CONFIG_USE_STATIC_LIBS ON)
# Hint find_library to choose static archives first on Unix (not macOS)
# Prefer .a but still allow falling back to shared if static is unavailable.
if(UNIX AND NOT APPLE)
set(CMAKE_FIND_LIBRARY_SUFFIXES .a;.so;.so.0;.so.1)
endif()
# Toolchain-specific static link flags
# Use a "mostly static" approach by default to avoid requiring static
# variants of every system/third-party library. This keeps libgcc and
# libstdc++ static while allowing shared deps when needed.
if(UNIX AND NOT APPLE AND CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
set(_FUN_STATIC_LINK_FLAGS -static-libgcc -static-libstdc++)
endif()
# 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)
if(DEFINED ${flag_var})
string(REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
endif()
endforeach()
endif()
endif()
# Optional Tcl/Tk GUI support (embedded interpreter)
option(FUN_WITH_TCLTK "Enable Tcl/Tk GUI support" OFF)
set(TCL_INCLUDE_DIRS "")
set(TCL_LINK_LIBS "")
if(FUN_WITH_TCLTK)
message(STATUS "Building with Tcl/Tk support")
add_definitions(-DFUN_WITH_TCLTK)
# Try pkg-config first
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
pkg_check_modules(TCL QUIET tcl)
pkg_check_modules(TK QUIET tk)
endif()
if(TCL_FOUND OR TK_FOUND)
if(TCL_FOUND)
list(APPEND TCL_INCLUDE_DIRS ${TCL_INCLUDE_DIRS} ${TCL_INCLUDE_DIRS})
list(APPEND TCL_LINK_LIBS ${TCL_LINK_LIBS} ${TCL_LIBRARIES})
endif()
if(TK_FOUND)
list(APPEND TCL_INCLUDE_DIRS ${TCL_INCLUDE_DIRS} ${TK_INCLUDE_DIRS})
list(APPEND TCL_LINK_LIBS ${TCL_LINK_LIBS} ${TK_LIBRARIES})
endif()
if(TCL_INCLUDE_DIRS)
include_directories(${TCL_INCLUDE_DIRS})
endif()
else()
# Fallbacks by platform (best-effort)
find_path(TCL_INCLUDE_DIR tcl.h PATH_SUFFIXES tcl8.7 tcl8.6 include)
find_library(TCL_LIB NAMES tcl8.7 tcl8.6 tcl)
find_library(TK_LIB NAMES tk8.7 tk8.6 tk)
if(TCL_INCLUDE_DIR)
list(APPEND TCL_INCLUDE_DIRS ${TCL_INCLUDE_DIR})
include_directories(${TCL_INCLUDE_DIR})
endif()
if(TCL_LIB)
list(APPEND TCL_LINK_LIBS ${TCL_LIB})
endif()
if(TK_LIB)
list(APPEND TCL_LINK_LIBS ${TK_LIB})
endif()
if(APPLE)
# On macOS additional frameworks are usually not required; Homebrew libs suffice
elseif(WIN32)
# Typical Windows GUI libs
list(APPEND TCL_LINK_LIBS user32 gdi32 comctl32)
else()
# X11 may be required on some Linux setups
find_package(X11 QUIET)
if(X11_FOUND)
include_directories(${X11_INCLUDE_DIR})
list(APPEND TCL_LINK_LIBS ${X11_LIBRARIES})
endif()
endif()
if(NOT TCL_LINK_LIBS)
message(WARNING "Tcl/Tk libraries not found via pkg-config or fallbacks. FUN_WITH_TCLTK is enabled, but linking may fail.")
endif()
endif()
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
option(FUN_WITH_SQLITE "Enable SQLite (sqlite3) support" OFF)
set(SQLITE3_INCLUDE_DIRS "")
set(SQLITE3_LINK_LIBS "")
if(FUN_WITH_SQLITE)
message(STATUS "Building with SQLite support")
add_definitions(-DFUN_WITH_SQLITE)
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
pkg_check_modules(SQLITE3 QUIET sqlite3)
endif()
if(SQLITE3_FOUND)
list(APPEND SQLITE3_INCLUDE_DIRS ${SQLITE3_INCLUDE_DIRS} ${SQLITE3_INCLUDE_DIRS})
list(APPEND SQLITE3_LINK_LIBS ${SQLITE3_LINK_LIBS} ${SQLITE3_LIBRARIES})
include_directories(${SQLITE3_INCLUDE_DIRS})
else()
find_library(SQLITE3_LIB sqlite3)
if(SQLITE3_LIB)
list(APPEND SQLITE3_LINK_LIBS ${SQLITE3_LIB})
else()
message(FATAL_ERROR "sqlite3 not found. Install sqlite3 (dev headers) or disable FUN_WITH_SQLITE.")
endif()
endif()
endif()
# Optional PCSC support (enabled via -DFUN_WITH_PCSC=ON)
@ -44,6 +194,167 @@ if(FUN_WITH_PCSC)
endif()
endif()
# Optional JSON (json-c) support
option(FUN_WITH_JSON "Enable JSON (json-c) support" OFF)
set(JSONC_INCLUDE_DIRS "")
set(JSONC_LINK_LIBS "")
if(FUN_WITH_JSON)
message(STATUS "Building with JSON (json-c) support")
add_definitions(-DFUN_WITH_JSON)
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
pkg_check_modules(JSONC QUIET json-c)
endif()
if(JSONC_FOUND)
list(APPEND JSONC_INCLUDE_DIRS ${JSONC_INCLUDE_DIRS} ${JSONC_INCLUDE_DIRS})
list(APPEND JSONC_LINK_LIBS ${JSONC_LINK_LIBS} ${JSONC_LIBRARIES})
include_directories(${JSONC_INCLUDE_DIRS})
else()
# Fallback: try plain -ljson-c
list(APPEND JSONC_LINK_LIBS json-c)
endif()
endif()
# Optional INI (iniparser 4.2.6) support
option(FUN_WITH_INI "Enable INI (iniparser) support" OFF)
set(INIPARSER_INCLUDE_DIRS "")
set(INIPARSER_LINK_LIBS "")
if(FUN_WITH_INI)
message(STATUS "Building with INI (iniparser) support")
add_definitions(-DFUN_WITH_INI)
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
pkg_check_modules(INIPARSER QUIET iniparser)
endif()
if(INIPARSER_FOUND)
list(APPEND INIPARSER_INCLUDE_DIRS ${INIPARSER_INCLUDE_DIRS} ${INIPARSER_INCLUDE_DIRS})
list(APPEND INIPARSER_LINK_LIBS ${INIPARSER_LINK_LIBS} ${INIPARSER_LIBRARIES})
else()
# Fallback: try to locate headers and library manually
find_path(INIPARSER_INCLUDE_DIR iniparser.h)
find_library(INIPARSER_LIB NAMES iniparser)
if(INIPARSER_INCLUDE_DIR)
list(APPEND INIPARSER_INCLUDE_DIRS ${INIPARSER_INCLUDE_DIR})
endif()
if(INIPARSER_LIB)
list(APPEND INIPARSER_LINK_LIBS ${INIPARSER_LIB})
endif()
if(NOT INIPARSER_INCLUDE_DIRS OR NOT INIPARSER_LINK_LIBS)
message(FATAL_ERROR "iniparser not found. Install iniparser (>=4.2.6) or disable FUN_WITH_INI.")
endif()
endif()
endif()
# Optional XML (libxml2) support
option(FUN_WITH_XML2 "Enable XML (libxml2) support" OFF)
set(LIBXML2_INCLUDE_DIRS "")
set(LIBXML2_LINK_LIBS "")
if(FUN_WITH_XML2)
message(STATUS "Building with XML (libxml2) support")
add_definitions(-DFUN_WITH_XML2)
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
pkg_check_modules(LIBXML2 QUIET libxml-2.0)
endif()
if(LIBXML2_FOUND)
list(APPEND LIBXML2_INCLUDE_DIRS ${LIBXML2_INCLUDE_DIRS} ${LIBXML2_INCLUDE_DIRS})
list(APPEND LIBXML2_LINK_LIBS ${LIBXML2_LINK_LIBS} ${LIBXML2_LIBRARIES})
include_directories(${LIBXML2_INCLUDE_DIRS})
else()
find_library(LIBXML2_LIB NAMES xml2 libxml2)
if(LIBXML2_LIB)
list(APPEND LIBXML2_LINK_LIBS ${LIBXML2_LIB})
# Common system include location for libxml2 headers
if(EXISTS "/usr/include/libxml2")
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()
endif()
# As a robust fallback, add standard system include path for libxml2 if present
if(EXISTS "/usr/include/libxml2")
include_directories("/usr/include/libxml2")
endif()
endif()
# Optional libsql support (independent from SQLite)
option(FUN_WITH_LIBSQL "Enable libsql (Turso) client support" OFF)
set(LIBSQL_INCLUDE_DIRS "")
set(LIBSQL_LINK_LIBS "")
if(FUN_WITH_LIBSQL)
message(STATUS "Building with libSQL support")
add_definitions(-DFUN_WITH_LIBSQL)
# Try pkg-config for libsql first; some systems expose libsql-client
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
pkg_check_modules(LIBSQL QUIET libsql)
if(NOT LIBSQL_FOUND)
pkg_check_modules(LIBSQL_CLIENT QUIET libsql-client)
if(LIBSQL_CLIENT_FOUND)
set(LIBSQL_FOUND TRUE)
set(LIBSQL_INCLUDE_DIRS ${LIBSQL_CLIENT_INCLUDE_DIRS})
set(LIBSQL_LINK_LIBS ${LIBSQL_CLIENT_LIBRARIES})
endif()
endif()
endif()
if(LIBSQL_FOUND)
include_directories(${LIBSQL_INCLUDE_DIRS})
else()
# Fallback: many libsql deployments provide a sqlite3-compatible client lib
# so try linking against sqlite3 as a compatibility layer.
find_library(LIBSQL_LIB sqlite3)
if(LIBSQL_LIB)
list(APPEND LIBSQL_LINK_LIBS ${LIBSQL_LIB})
else()
message(FATAL_ERROR "libsql not found. Install libsql (or compatible sqlite3 client) or disable FUN_WITH_LIBSQL.")
endif()
endif()
endif()
# Optional PCRE2 support
option(FUN_WITH_PCRE2 "Enable PCRE2 (Perl Compatible Regular Expressions) support" OFF)
set(PCRE2_INCLUDE_DIRS "")
set(PCRE2_LINK_LIBS "")
if(FUN_WITH_PCRE2)
message(STATUS "Building with PCRE2 support")
add_definitions(-DFUN_WITH_PCRE2)
# Try pkg-config first
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
pkg_check_modules(PCRE2 QUIET libpcre2-8)
endif()
if(PCRE2_FOUND)
list(APPEND PCRE2_INCLUDE_DIRS ${PCRE2_INCLUDE_DIRS} ${PCRE2_INCLUDE_DIRS})
list(APPEND PCRE2_LINK_LIBS ${PCRE2_LINK_LIBS} ${PCRE2_LIBRARIES})
include_directories(${PCRE2_INCLUDE_DIRS})
else()
# Fallback: common defaults
list(APPEND PCRE2_LINK_LIBS pcre2-8)
endif()
endif()
# Optional CURL (libcurl) support
option(FUN_WITH_CURL "Enable libcurl HTTP client support" OFF)
set(CURL_INCLUDE_DIRS "")
set(CURL_LINK_LIBS "")
if(FUN_WITH_CURL)
message(STATUS "Building with libcurl support")
add_definitions(-DFUN_WITH_CURL)
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
pkg_check_modules(LIBCURL QUIET libcurl)
endif()
if(LIBCURL_FOUND)
list(APPEND CURL_INCLUDE_DIRS ${LIBCURL_INCLUDE_DIRS})
list(APPEND CURL_LINK_LIBS ${LIBCURL_LIBRARIES})
include_directories(${CURL_INCLUDE_DIRS})
else()
list(APPEND CURL_LINK_LIBS curl)
endif()
endif()
# Debug option to enable verbose parser/VM logging
option(FUN_DEBUG "Enable extra debug logging in Fun" OFF)
@ -81,6 +392,75 @@ if(PCSC_LINK_LIBS)
target_link_libraries(fun_core PUBLIC ${PCSC_LINK_LIBS})
endif()
# json-c include and link (if enabled)
if(JSONC_INCLUDE_DIRS)
target_include_directories(fun_core PRIVATE ${JSONC_INCLUDE_DIRS})
endif()
if(JSONC_LINK_LIBS)
target_link_libraries(fun_core PUBLIC ${JSONC_LINK_LIBS})
endif()
# pcre2 include and link (if enabled)
if(PCRE2_INCLUDE_DIRS)
target_include_directories(fun_core PRIVATE ${PCRE2_INCLUDE_DIRS})
endif()
if(PCRE2_LINK_LIBS)
target_link_libraries(fun_core PUBLIC ${PCRE2_LINK_LIBS})
endif()
# libcurl include and link (if enabled)
if(CURL_INCLUDE_DIRS)
target_include_directories(fun_core PRIVATE ${CURL_INCLUDE_DIRS})
endif()
if(CURL_LINK_LIBS)
target_link_libraries(fun_core PUBLIC ${CURL_LINK_LIBS})
endif()
# sqlite3 include and link (if enabled)
if(SQLITE3_INCLUDE_DIRS)
target_include_directories(fun_core PRIVATE ${SQLITE3_INCLUDE_DIRS})
endif()
if(SQLITE3_LINK_LIBS)
target_link_libraries(fun_core PUBLIC ${SQLITE3_LINK_LIBS})
endif()
# iniparser include and link (if enabled)
if(INIPARSER_INCLUDE_DIRS)
target_include_directories(fun_core PRIVATE ${INIPARSER_INCLUDE_DIRS})
endif()
if(INIPARSER_LINK_LIBS)
target_link_libraries(fun_core PUBLIC ${INIPARSER_LINK_LIBS})
endif()
# libsql include and link (if enabled)
if(LIBSQL_INCLUDE_DIRS)
target_include_directories(fun_core PRIVATE ${LIBSQL_INCLUDE_DIRS})
endif()
if(LIBSQL_LINK_LIBS)
target_link_libraries(fun_core PUBLIC ${LIBSQL_LINK_LIBS})
endif()
# libxml2 include and link (if enabled)
if(LIBXML2_INCLUDE_DIRS)
target_include_directories(fun_core PRIVATE ${LIBXML2_INCLUDE_DIRS})
endif()
if(LIBXML2_LINK_LIBS)
target_link_libraries(fun_core PUBLIC ${LIBXML2_LINK_LIBS})
endif()
if(FUN_WITH_XML2)
if(EXISTS "/usr/include/libxml2")
target_include_directories(fun_core PRIVATE "/usr/include/libxml2")
endif()
endif()
# Tcl/Tk include and link (if enabled)
if(TCL_INCLUDE_DIRS)
target_include_directories(fun_core PRIVATE ${TCL_INCLUDE_DIRS})
endif()
if(TCL_LINK_LIBS)
target_link_libraries(fun_core PUBLIC ${TCL_LINK_LIBS})
endif()
# Link threads if available on UNIX
if(Threads_FOUND)
target_link_libraries(fun_core PUBLIC Threads::Threads)
@ -111,6 +491,15 @@ add_executable(test_opcodes
)
target_link_libraries(test_opcodes PRIVATE fun_core)
# If static linking is requested, apply linker flags to executables
if(FUN_LINK_STATIC AND DEFINED _FUN_STATIC_LINK_FLAGS)
foreach(tgt fun fun_test test_opcodes)
if(TARGET ${tgt})
target_link_options(${tgt} PRIVATE ${_FUN_STATIC_LINK_FLAGS})
endif()
endforeach()
endif()
# Convenience aggregate target (like 'build' in Makefile)
add_custom_target(build
DEPENDS fun fun_test test_opcodes

129
README.md
View file

@ -2,9 +2,11 @@
## What is Fun?
Fun is an experiment, just for fun, but Fun works!
Fun is a highly strict programming language, but also highly simple. It looks like Python (My favorite language), but there are differences.
Influenced by **[Bash](https://www.gnu.org/software/bash/)**, **[C](https://en.wikipedia.org/wiki/The_C_Programming_Language)**, Go, **[Lua](https://www.lua.org/)**, **[Python](https://www.python.org/)**, and Rust (Most influences came from linked languages).
Influenced by **[Bash](https://www.gnu.org/software/bash/)**, **[C](https://en.wikipedia.org/wiki/The_C_Programming_Language)**, **[Lua](https://www.lua.org/)**, PHP, **[Python](https://www.python.org/)**, and Rust (Most influences came from linked languages).
Fun is and will ever be 100% free under the terms of the [Apache-2.0 License](https://opensource.org/license/apache-2-0).
@ -15,14 +17,6 @@ Fun is and will ever be 100% free under the terms of the [Apache-2.0 License](ht
- Joy in coding
- Fun!
### Extras
- [JSON](https://www.json.org/) support builtin using [json-c](https://github.com/json-c/json-c) (optional, planned, maybe write a parser in Fun for stdlib)
- [ODBC](https://learn.microsoft.com/en-us/sql/odbc/reference/odbc-overview?view=sql-server-ver16) support builtin for flexible database connectivity using [unixODBC](https://www.unixodbc.org/) (optional, planned)
- [PC/SC](https://pcscworkgroup.com/) smart card support builtin using [PCSC lite](https://pcsclite.apdu.fr/) (optional, in progress)
- [SQLite](https://sqlite.org/) support builtin (optional, planned)
- [Tk](https://www.tcl-lang.org/) support builtin for GUI application development (optional, planned)
## Characteristics
- Dynamic and optionally statically typed
@ -73,94 +67,51 @@ A language that feels like home for developers who:
Fun may not change the world — but it will make programming a little more fun.
## Features
### Core
- functions/classes/objects
- if/else if/else
- try/catch/finally
### Lib
...
### Extensions (only Linux actually)
- [CGI](https://en.wikipedia.org/wiki/Common_Gateway_Interface) support builtin using [kcgi](https://kristaps.bsd.lv/kcgi/) (optional) ☐
- [cURL](https://curl.se/) support builtin using [libcurl](https://curl.se/libcurl/) (optional) ☑
- [INI](https://en.wikipedia.org/wiki/INI_file) support builtin using [iniparser](https://gitlab.com/iniparser/iniparser/) (optional) ☑
- [JSON](https://www.json.org/) support builtin using [json-c](https://github.com/json-c/json-c) (optional) ☑
- [libSQL](https://github.com/tursodatabase/libsql) support builtin as a compatible alternative to SQLite (optional) ☑
- [PCRE2](https://pcre2project.github.io/pcre2/) support builtin for Perl-Compatible Regular Expressions (optional) ☑
- [PCSC](https://pcscworkgroup.com/) smart card support builtin using [PCSC lite](https://pcsclite.apdu.fr/) (optional) ☑
- [SQLite](https://sqlite.org/) support builtin (optional) ☑
- [Tk](https://www.tcl-lang.org/) support builtin for GUI application development (optional) ☑
- [XML](https://www.w3.org/XML/) support builtin using [libxml2](https://gitlab.gnome.org/GNOME/libxml2/-/wikis/home) (optional) ☑
☑ = Done / ☐ = Planned or in progress.
Note: Not all of the above features will be implemented. Those who are marked "Done" will probaly remain in Fun, but I don't know actually... ;)
There are some libs written in Fun available in the [./lib/](https://git.xw3.org/fun/fun/src/branch/main/lib) diretory. In the future most Fun enhancements should be written in Fun itself.
## Documentation
I am writing documentation only actually, but this is work in progress, since debugging and bug fixing includes this task.
This is actually a work in progress...
Current documentation is only found in the [Fun Handbook](https://git.xw3.org/fun/fun/src/branch/main/docs/handbook.md).
In the [examples/](https://git.xw3.org/fun/fun/src/branch/main/examples) directory should be an example of most Fun features.
In the [./examples/](https://git.xw3.org/fun/fun/src/branch/main/examples) directory should be an example of most Fun features.
A complete API documentation will follow.
Fun internals are found directly in the [./src/](https://git.xw3.org/fun/fun/src/branch/main/src) diretory. Fun [Opcodes](https://en.wikipedia.org/wiki/Opcode) are found in [./src/vm/](https://git.xw3.org/fun/fun/src/branch/main/src/vm).
## Development
Since things are actually changing sometimes, I will not write the documentation for this as of now.
This section is a work in progress... Please excuse the lack of more information. There are daily updates here.
### Rules
- Every commit message must contain the version at the end in the following format (1.2.3)
- Every commit requires a version incrementation in CMakeLists.txt before committing. Documentation updates do not increment the version but must contain the current version in each commit message.
- Version numbering follows "[Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html)"
### Development systems
- [GNU](https://gnu.org/)/[Linux](https://kernel.org/) ([Arch](https://archlinux.org/)/[Artix](https://artixlinux.org/), [Debian](https://www.debian.org/)) using [GCC](https://gcc.gnu.org/) and the [GNU C library](https://www.gnu.org/software/libc/) ([glibc](https://en.wikipedia.org/wiki/Glibc))
- GNU/Linux ([Alpine](https://alpinelinux.org/)) using GCC and the [musl libc](https://musl.libc.org/)
- [FreeBSD](https://www.freebsd.org/) using [Clang](https://clang.llvm.org/) and the [BSD libc](https://en.wikipedia.org/wiki/C_standard_library#BSD_libc)
- [Windows](https://en.wikipedia.org/wiki/Microsoft_Windows) using [Cygwin](https://www.cygwin.com/) and GCC.
### Other systems
- [macOS](https://en.wikipedia.org/wiki/MacOS), [NetBSD](https://netbsd.org/), [OpenBSD](https://www.openbsd.org/), etc. should fully work, but I don't know. I do not use these systems actually. You wanna try and report?
### To Do
Everything... ;) No, a lot of stuff works already, but only a tiny set of functionality is available in the Fun programming language. It grows from day to day...
### Build Fun
Linux/UNIX only covered here for now.
Clone repository:
```bash
git clone https://git.xw3.org/fun/fun.git
```
Change directory:
```bash
cd fun
```
Build:
```bash
cmake -S . -B build -DFUN_DEBUG=OFF -DFUN_WITH_PCSC=OFF -DFUN_WITH_REPL=ON
cmake --build build --target fun
```
That's it! For testing it, run:
```bash
FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./demo.fun
```
To see what's going on, run:
```bash
FUN_LIB_DIR="$(pwd)/lib" ./build/fun --trace ./demo.fun
```
To switch into the REPL after an error, run:
```bash
FUN_LIB_DIR="$(pwd)/lib" ./build/fun --repl-on-error --trace ./demo.fun
```
Both --repl-on-error and --trace are optional but can always be combined. To get
more debug information, you need to build Fun with -DFUN_DEBUG=ON.
To directly run the REPL, you have to run:
```bash
FUN_LIB_DIR="$(pwd)/lib" ./build/fun
```
But be sure to build Fun with -DFUN_WITH_REPL=ON.
Complete API documentation will follow.
## Author
Johannes Findeisen <you@hanez.org>
Johannes Findeisen - <you@hanez.org>

373
SEMANTIC_VERSIONING.md Normal file
View file

@ -0,0 +1,373 @@
Semantic Versioning 2.0.0
==============================
Summary
-------
Given a version number MAJOR.MINOR.PATCH, increment the:
1. MAJOR version when you make incompatible API changes
1. MINOR version when you add functionality in a backward compatible
manner
1. PATCH version when you make backward compatible bug fixes
Additional labels for pre-release and build metadata are available as extensions
to the MAJOR.MINOR.PATCH format.
Introduction
------------
In the world of software management there exists a dreaded place called
"dependency hell." The bigger your system grows and the more packages you
integrate into your software, the more likely you are to find yourself, one
day, in this pit of despair.
In systems with many dependencies, releasing new package versions can quickly
become a nightmare. If the dependency specifications are too tight, you are in
danger of version lock (the inability to upgrade a package without having to
release new versions of every dependent package). If dependencies are
specified too loosely, you will inevitably be bitten by version promiscuity
(assuming compatibility with more future versions than is reasonable).
Dependency hell is where you are when version lock and/or version promiscuity
prevent you from easily and safely moving your project forward.
As a solution to this problem, we propose a simple set of rules and
requirements that dictate how version numbers are assigned and incremented.
These rules are based on but not necessarily limited to pre-existing
widespread common practices in use in both closed and open-source software.
For this system to work, you first need to declare a public API. This may
consist of documentation or be enforced by the code itself. Regardless, it is
important that this API be clear and precise. Once you identify your public
API, you communicate changes to it with specific increments to your version
number. Consider a version format of X.Y.Z (Major.Minor.Patch). Bug fixes not
affecting the API increment the patch version, backward compatible API
additions/changes increment the minor version, and backward incompatible API
changes increment the major version.
We call this system "Semantic Versioning." Under this scheme, version numbers
and the way they change convey meaning about the underlying code and what has
been modified from one version to the next.
Semantic Versioning Specification (SemVer)
------------------------------------------
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be
interpreted as described in [RFC 2119](https://tools.ietf.org/html/rfc2119).
1. Software using Semantic Versioning MUST declare a public API. This API
could be declared in the code itself or exist strictly in documentation.
However it is done, it SHOULD be precise and comprehensive.
1. A normal version number MUST take the form X.Y.Z where X, Y, and Z are
non-negative integers, and MUST NOT contain leading zeroes. X is the
major version, Y is the minor version, and Z is the patch version.
Each element MUST increase numerically. For instance: 1.9.0 -> 1.10.0 -> 1.11.0.
1. Once a versioned package has been released, the contents of that version
MUST NOT be modified. Any modifications MUST be released as a new version.
1. Major version zero (0.y.z) is for initial development. Anything MAY change
at any time. The public API SHOULD NOT be considered stable.
1. Version 1.0.0 defines the public API. The way in which the version number
is incremented after this release is dependent on this public API and how it
changes.
1. Patch version Z (x.y.Z | x > 0) MUST be incremented if only backward
compatible bug fixes are introduced. A bug fix is defined as an internal
change that fixes incorrect behavior.
1. Minor version Y (x.Y.z | x > 0) MUST be incremented if new, backward
compatible functionality is introduced to the public API. It MUST be
incremented if any public API functionality is marked as deprecated. It MAY be
incremented if substantial new functionality or improvements are introduced
within the private code. It MAY include patch level changes. Patch version
MUST be reset to 0 when minor version is incremented.
1. Major version X (X.y.z | X > 0) MUST be incremented if any backward
incompatible changes are introduced to the public API. It MAY also include minor
and patch level changes. Patch and minor versions MUST be reset to 0 when major
version is incremented.
1. A pre-release version MAY be denoted by appending a hyphen and a
series of dot separated identifiers immediately following the patch
version. Identifiers MUST comprise only ASCII alphanumerics and hyphens
[0-9A-Za-z-]. Identifiers MUST NOT be empty. Numeric identifiers MUST
NOT include leading zeroes. Pre-release versions have a lower
precedence than the associated normal version. A pre-release version
indicates that the version is unstable and might not satisfy the
intended compatibility requirements as denoted by its associated
normal version. Examples: 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-0.3.7,
1.0.0-x.7.z.92, 1.0.0-x-y-z.\-\-.
1. Build metadata MAY be denoted by appending a plus sign and a series of dot
separated identifiers immediately following the patch or pre-release version.
Identifiers MUST comprise only ASCII alphanumerics and hyphens [0-9A-Za-z-].
Identifiers MUST NOT be empty. Build metadata MUST be ignored when determining
version precedence. Thus two versions that differ only in the build metadata,
have the same precedence. Examples: 1.0.0-alpha+001, 1.0.0+20130313144700,
1.0.0-beta+exp.sha.5114f85, 1.0.0+21AF26D3\-\-\-\-117B344092BD.
1. Precedence refers to how versions are compared to each other when ordered.
1. Precedence MUST be calculated by separating the version into major,
minor, patch and pre-release identifiers in that order (build metadata
does not figure into precedence).
1. Precedence is determined by the first difference when comparing each of
these identifiers from left to right as follows: major, minor, and patch
versions are always compared numerically.
Example: 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1.
1. When major, minor, and patch are equal, a pre-release version has lower
precedence than a normal version:
Example: 1.0.0-alpha < 1.0.0.
1. Precedence for two pre-release versions with the same major, minor, and
patch version MUST be determined by comparing each dot separated identifier
from left to right until a difference is found as follows:
1. Identifiers consisting of only digits are compared numerically.
1. Identifiers with letters or hyphens are compared lexically in ASCII
sort order.
1. Numeric identifiers always have lower precedence than non-numeric
identifiers.
1. A larger set of pre-release fields has a higher precedence than a
smaller set, if all of the preceding identifiers are equal.
Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta <
1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0.
BackusNaur Form Grammar for Valid SemVer Versions
--------------------------------------------------
```
<valid semver> ::= <version core>
| <version core> "-" <pre-release>
| <version core> "+" <build>
| <version core> "-" <pre-release> "+" <build>
<version core> ::= <major> "." <minor> "." <patch>
<major> ::= <numeric identifier>
<minor> ::= <numeric identifier>
<patch> ::= <numeric identifier>
<pre-release> ::= <dot-separated pre-release identifiers>
<dot-separated pre-release identifiers> ::= <pre-release identifier>
| <pre-release identifier> "." <dot-separated pre-release identifiers>
<build> ::= <dot-separated build identifiers>
<dot-separated build identifiers> ::= <build identifier>
| <build identifier> "." <dot-separated build identifiers>
<pre-release identifier> ::= <alphanumeric identifier>
| <numeric identifier>
<build identifier> ::= <alphanumeric identifier>
| <digits>
<alphanumeric identifier> ::= <non-digit>
| <non-digit> <identifier characters>
| <identifier characters> <non-digit>
| <identifier characters> <non-digit> <identifier characters>
<numeric identifier> ::= "0"
| <positive digit>
| <positive digit> <digits>
<identifier characters> ::= <identifier character>
| <identifier character> <identifier characters>
<identifier character> ::= <digit>
| <non-digit>
<non-digit> ::= <letter>
| "-"
<digits> ::= <digit>
| <digit> <digits>
<digit> ::= "0"
| <positive digit>
<positive digit> ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
<letter> ::= "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J"
| "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T"
| "U" | "V" | "W" | "X" | "Y" | "Z" | "a" | "b" | "c" | "d"
| "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n"
| "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x"
| "y" | "z"
```
Why Use Semantic Versioning?
----------------------------
This is not a new or revolutionary idea. In fact, you probably do something
close to this already. The problem is that "close" isn't good enough. Without
compliance to some sort of formal specification, version numbers are
essentially useless for dependency management. By giving a name and clear
definition to the above ideas, it becomes easy to communicate your intentions
to the users of your software. Once these intentions are clear, flexible (but
not too flexible) dependency specifications can finally be made.
A simple example will demonstrate how Semantic Versioning can make dependency
hell a thing of the past. Consider a library called "Firetruck." It requires a
Semantically Versioned package named "Ladder." At the time that Firetruck is
created, Ladder is at version 3.1.0. Since Firetruck uses some functionality
that was first introduced in 3.1.0, you can safely specify the Ladder
dependency as greater than or equal to 3.1.0 but less than 4.0.0. Now, when
Ladder version 3.1.1 and 3.2.0 become available, you can release them to your
package management system and know that they will be compatible with existing
dependent software.
As a responsible developer you will, of course, want to verify that any
package upgrades function as advertised. The real world is a messy place;
there's nothing we can do about that but be vigilant. What you can do is let
Semantic Versioning provide you with a sane way to release and upgrade
packages without having to roll new versions of dependent packages, saving you
time and hassle.
If all of this sounds desirable, all you need to do to start using Semantic
Versioning is to declare that you are doing so and then follow the rules. Link
to this website from your README so others know the rules and can benefit from
them.
FAQ
---
### How should I deal with revisions in the 0.y.z initial development phase?
The simplest thing to do is start your initial development release at 0.1.0
and then increment the minor version for each subsequent release.
### How do I know when to release 1.0.0?
If your software is being used in production, it should probably already be
1.0.0. If you have a stable API on which users have come to depend, you should
be 1.0.0. If you're worrying a lot about backward compatibility, you should
probably already be 1.0.0.
### Doesn't this discourage rapid development and fast iteration?
Major version zero is all about rapid development. If you're changing the API
every day you should either still be in version 0.y.z or on a separate
development branch working on the next major version.
### If even the tiniest backward incompatible changes to the public API require a major version bump, won't I end up at version 42.0.0 very rapidly?
This is a question of responsible development and foresight. Incompatible
changes should not be introduced lightly to software that has a lot of
dependent code. The cost that must be incurred to upgrade can be significant.
Having to bump major versions to release incompatible changes means you'll
think through the impact of your changes, and evaluate the cost/benefit ratio
involved.
### Documenting the entire public API is too much work!
It is your responsibility as a professional developer to properly document
software that is intended for use by others. Managing software complexity is a
hugely important part of keeping a project efficient, and that's hard to do if
nobody knows how to use your software, or what methods are safe to call. In
the long run, Semantic Versioning, and the insistence on a well defined public
API can keep everyone and everything running smoothly.
### What do I do if I accidentally release a backward incompatible change as a minor version?
As soon as you realize that you've broken the Semantic Versioning spec, fix
the problem and release a new patch version that corrects the problem and
restores backward compatibility. Even under this circumstance, it is
unacceptable to modify versioned releases. If it's appropriate,
document the offending version and inform your users of the problem so that
they are aware of the offending version.
### What should I do if I update my own dependencies without changing the public API?
That would be considered compatible since it does not affect the public API.
Software that explicitly depends on the same dependencies as your package
should have their own dependency specifications and the author will notice any
conflicts. Determining whether the change is a patch level or minor level
modification depends on whether you updated your dependencies in order to fix
a bug or introduce new functionality. We would usually expect additional code
for the latter instance, in which case it's obviously a minor level increment.
### What if I inadvertently alter the public API in a way that is not compliant with the version number change (i.e. the code incorrectly introduces a major breaking change in a patch release)?
Use your best judgment. If you have a huge audience that will be drastically
impacted by changing the behavior back to what the public API intended, then
it may be best to perform a major version release, even though the fix could
strictly be considered a patch release. Remember, Semantic Versioning is all
about conveying meaning by how the version number changes. If these changes
are important to your users, use the version number to inform them.
### How should I handle deprecating functionality?
Deprecating existing functionality is a normal part of software development and
is often required to make forward progress. When you deprecate part of your
public API, you should do two things: (1) update your documentation to let
users know about the change, (2) issue a new minor release with the deprecation
in place. Before you completely remove the functionality in a new major release
there should be at least one minor release that contains the deprecation so
that users can smoothly transition to the new API.
### Does SemVer have a size limit on the version string?
No, but use good judgment. A 255 character version string is probably an overkill,
for example. Also, specific systems may impose their own limits on the size of
the string.
### Is "v1.2.3" a semantic version?
No, "v1.2.3" is not a semantic version. However, prefixing a semantic version
with a "v" is a common way (in English) to indicate it is a version number.
Abbreviating "version" as "v" is often seen with version control. Example:
`git tag v1.2.3 -m "Release version 1.2.3"`, in which case "v1.2.3" is a tag
name and the semantic version is "1.2.3".
### Is there a suggested regular expression (RegEx) to check a SemVer string?
There are two. One with named groups for those systems that support them
(PCRE [Perl Compatible Regular Expressions, i.e. Perl, PHP and R], Python
and Go).
See: <https://regex101.com/r/Ly7O1x/3/>
```
^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
```
And one with numbered capture groups instead (so cg1 = major, cg2 = minor,
cg3 = patch, cg4 = prerelease and cg5 = buildmetadata) that is compatible
with ECMA Script (JavaScript), PCRE (Perl Compatible Regular Expressions,
i.e. Perl, PHP and R), Python and Go.
See: <https://regex101.com/r/vkijKf/1/>
```
^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
```
About
-----
The Semantic Versioning specification was originally authored by [Tom
Preston-Werner](https://tom.preston-werner.com), inventor of Gravatar and
cofounder of GitHub.
If you'd like to leave feedback, please [open an issue on
GitHub](https://github.com/semver/semver/issues).
License
-------
[Creative Commons ― CC BY 3.0](https://creativecommons.org/licenses/by/3.0/)

200
demo.fun
View file

@ -1,200 +0,0 @@
#!/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-09-30
*/
// Fun Interactive Demo
// Run: FUN_LIB_DIR="$(pwd)/lib" ./build/fun demo.fun (Linux/macOS/FreeBSD)
// set FUN_LIB_DIR=%CD%\lib && build-debug\fun.exe demo.fun (Windows CMD)
// $env:FUN_LIB_DIR="$PWD\lib"; .\build\fun.exe demo.fun (Windows PowerShell)
// Use a stdlib helper from the repository (fallback to ./lib via preprocessor)
#include <utils/math.fun>
print("")
print("=== Fun Interactive Demo ===")
print("")
print("== Basics: dynamic vs typed variables and typeof ==")
x = 123
print("x=" + to_string(x) + " typeof=" + typeof(x))
x = "hello"
print("x=" + to_string(x) + " typeof=" + typeof(x))
number n = 42
print("n=" + to_string(n) + " typeof=" + typeof(n))
n = n + 8
print("n=" + to_string(n) + " typeof=" + typeof(n))
// Uncomment to see a runtime type error and halt the program:
// n = "oops"
boolean flag = 0
print("flag=" + to_string(flag) + " typeof=" + typeof(flag))
flag = 2 // gets clamped to 1
print("flag(after clamp 2)=" + to_string(flag))
int8 si = 130 // clamps to 127
uint8 u8 = 300 // clamps to 255
print("int8 si=" + to_string(si) + ", uint8 u8=" + to_string(u8))
nil nothing = nil
print("nothing typeof=" + typeof(nothing))
print("")
print("== Arithmetic, comparisons, and logic ==")
print("3+4=" + to_string(3 + 4))
print("10-3=" + to_string(10 - 3))
print("6*7=" + to_string(6 * 7))
print("20/3=" + to_string(20 / 3))
print("20%3=" + to_string(20 % 3))
print("2<3=" + to_string(2 < 3) + ", 3<=3=" + to_string(3 <= 3))
print("5>2=" + to_string(5 > 2) + ", 2>=2=" + to_string(2 >= 2))
print("2==2=" + to_string(2 == 2) + ", 2!=3=" + to_string(2 != 3))
print("(1 && 0)=" + to_string(1 && 0) + ", (0 || 1)=" + to_string(0 || 1) + ", !0=" + to_string(!0))
print("")
print("== Strings and arrays ==")
s = "alpha,beta,gamma"
parts = split(s, ",")
print("split -> len=" + to_string(len(parts)))
print("join(parts,'|')=" + join(parts, "|"))
print("substr('abcdef', 2, 3)=" + substr("abcdef", 2, 3))
print("find('hello world','world')=" + to_string(find("hello world", "world")))
arr = [1, 2, 3]
print("arr len=" + to_string(len(arr)))
push(arr, 4)
print("after push 4 -> len=" + to_string(len(arr)))
v = pop(arr)
print("popped=" + to_string(v) + " len=" + to_string(len(arr)))
insert(arr, 1, 99) // [1,99,2,3]
print("after insert(1,99) arr[1]=" + to_string(arr[1]))
set(arr, 2, 55) // [1,99,55,3]
print("after set(2,55) arr[2]=" + to_string(arr[2]))
print("contains(arr, 55)=" + to_string(contains(arr, 55)) + ", indexOf(arr, 99)=" + to_string(indexOf(arr, 99)))
print("slice arr[1:3] len=" + to_string(len(arr[1:3])))
print("join(arr, ',')=" + join(arr, ","))
print("")
print("== Enumerate and zip ==")
for p in enumerate(["a", "b", "c"])
print("idx=" + to_string(p[0]) + " val=" + to_string(p[1]))
z = zip([1, 2], ["x", "y"])
for pair in z
print("(" + to_string(pair[0]) + "," + to_string(pair[1]) + ")")
print("")
print("== Maps (dictionaries) ==")
m = {"name": "Alice", "age": 30}
print("has(m,'age')=" + to_string(has(m, "age")))
print("m['name']=" + to_string(m["name"]))
m.age = 31
print("m.age after = " + to_string(m.age))
print("keys: " + join(keys(m), ","))
print("values count=" + to_string(len(values(m))))
print("")
print("== Functions and higher-order ops (map/filter/reduce) ==")
fun greet(name)
print("Hello, " + to_string(name) + "!")
greet("Fun")
fun double(x)
return x * 2
nums = [1, 2, 3, 4, 5]
twice = map(nums, double)
print("len(map)=" + to_string(len(twice)) + " first=" + to_string(twice[0]))
fun isEven(x)
return (x % 2) == 0
evens = filter(nums, isEven)
print("filter evens len=" + to_string(len(evens)))
fun sum(acc, x)
return acc + x
total = reduce(nums, 0, sum)
print("reduce sum=" + to_string(total))
print("")
print("== If / else-if / else and loops ==")
val = 7
if (val < 0)
print("neg")
else if (val == 0)
print("zero")
else
print("pos")
print("for range(0, 5):")
for i in range(0, 5)
print(i)
print("for in array:")
for x in ["h", "i", "!"]
print(x)
print("while loop (count to 3):")
c = 0
while (c < 3)
print(c)
c = c + 1
print("")
print("== Classes (with 'this' and methods) ==")
class Person(string name, number age)
// default field values (can be overridden by constructor params)
full = name + " (" + to_string(age) + ")"
// required: first param is 'this'
fun say(this)
print("I am " + to_string(this.full))
// typeof(instance) will return this string for Maps tagged with __class
fun toString(this)
return "Person"
p = Person("Alice", 30)
p.say()
print("typeof p: " + typeof(p))
print("")
print("== Math and bitwise helpers ==")
print("min(3,9)=" + to_string(min(3, 9)) + ", max(3,9)=" + to_string(max(3, 9)))
print("clamp(15,0,10)=" + to_string(clamp(15, 0, 10)) + ", abs(-5)=" + to_string(abs(-5)))
print("pow(2,10)=" + to_string(pow(2, 10)))
print("band(0xF0,0x3C)=" + to_string(band(0xF0, 0x3C)))
print("bor(0x0F,0x30)=" + to_string(bor(0x0F, 0x30)))
print("bxor(0xFF,0x0F)=" + to_string(bxor(0xFF, 0x0F)))
print("bnot(0x0F)=" + to_string(bnot(0x0F)))
print("shl(1,4)=" + to_string(shl(1, 4)) + ", shr(128,3)=" + to_string(shr(128, 3)))
print("rol(0x12,1)=" + to_string(rol(0x12, 1)) + ", ror(0x12,1)=" + to_string(ror(0x12, 1)))
print("")
print("== Random numbers ==")
random(12345) // seed
print("randomInt(1,10) -> " + to_string(randomInt(1, 10)))
print("")
print("== File IO and environment ==")
write_ok = write_file("demo_tmp.txt", "Hello from Fun!\n")
print("write_file ok=" + to_string(write_ok))
content = read_file("demo_tmp.txt")
print("read_file len=" + to_string(len(content)))
print("PATH starts with: " + substr(env("PATH"), 0, 24))
print("")
print("== Library include demo ==")
print("add(2,3) from utils/math.fun -> " + to_string(add(2, 3)))
print("times(4,5) from utils/math.fun -> " + to_string(times(4, 5)))
print("")
print("=== Demo complete. Have Fun! ===")

View file

@ -1,100 +1,606 @@
# Fun Handbook
# Fun Handbook (Second Edition)
This is a refreshed, de-duplicated, and fully up-to-date handbook for the Fun programming language and its virtual machine (VM). It keeps the same section layout as the original handbook while consolidating repeated content and documenting all currently available features, including the latest SQLite support.
## Overview
Fun is a small, strict, and simple programming language executed by a stack-based virtual machine. Most of the ecosystem is written in Fun itself; only a minimal core is implemented in C. The design focuses on simplicity, consistency, and joy in coding.
## Introduction
- Dynamic and optionally statically typed
- Type safety
- Written in C (C99) and Fun
- Minimal C core; most core functions and libraries implemented in Fun
- Internal libraries use snake_case for functions even when written in Fun; class names are CamelCase
## Installation
### Requirements
A C compiler, a libc and [Git](https://git-scm.com/).
- A C compiler, a libc, and Git
#### FreeBSD
#### FreeBSD:
- [CMake](https://cmake.org/)
- [Clang](https://clang.llvm.org/)
- CMake
- Clang
#### Linux
#### Linux:
- [CMake](https://cmake.org/)
- [GCC](https://gcc.gnu.org/) (Clang should work here too, not tested!)
- CMake
- GCC (Clang should also work)
#### Windows
#### Windows:
This requires Cygwin to be installed and configured. I will not cover this here.
- [CMake](https://cmake.org/)
- [Cygwin](https://cygwin.com/) using [GCC](https://gcc.gnu.org/)
- Cygwin (not covered in detail here)
- CMake
- GCC via Cygwin
### Build Fun
Linux/UNIX and Cygwin only covered here for now.
Linux/UNIX and Cygwin are covered here.
Clone repository:
```bash
git clone https://git.xw3.org/fun/fun.git
```
Change directory:
```bash
git clone https://git.xw3.org/fun/fun.git
cd fun
```
Build:
Configure and build (examples shown with several optional features enabled):
```bash
cmake -S . -B build -DFUN_DEBUG=OFF -DFUN_WITH_PCSC=OFF -DFUN_WITH_REPL=ON
```
# Every -D flag must be NAME=VALUE (e.g., -DFUN_WITH_REPL=ON)
cmake -S . -B build \
-DFUN_DEBUG=OFF \
-DFUN_WITH_REPL=ON \
-DFUN_WITH_JSON=ON \
-DFUN_WITH_PCRE2=ON \
-DFUN_WITH_CURL=ON \
-DFUN_WITH_PCSC=OFF \
-DFUN_WITH_SQLITE=OFF
cmake --build build --target fun
```
That's it! For testing it, run:
Run the demo (without installing):
```bash
```
FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./demo.fun
```
To see what's going on, run:
Tracing execution:
```bash
```
FUN_LIB_DIR="$(pwd)/lib" ./build/fun --trace ./demo.fun
```
To switch into the REPL after an error, run:
Drop into the REPL when an error occurs:
```bash
```
FUN_LIB_DIR="$(pwd)/lib" ./build/fun --repl-on-error --trace ./demo.fun
```
Both --repl-on-error and --trace are optional but can always be combined. To get
more debug information, you need to build Fun with -DFUN_DEBUG=ON.
Start the REPL directly (build with -DFUN_WITH_REPL=ON):
To directly run the REPL, you have to run:
```bash
```
FUN_LIB_DIR="$(pwd)/lib" ./build/fun
```
But be sure to build Fun with -DFUN_WITH_REPL=ON.
#### CMake options
### Install Fun to OS
Pass all options as -DNAME=VALUE. The most relevant toggles are:
I do not recommend installing Fun on your system because it is in a very early
stage of development, but I can say that I have Fun installed on my system. If
you want to do that too, type:
- FUN_DEBUG=ON|OFF — verbose VM debug logging (default OFF)
- FUN_WITH_CURL=ON|OFF — enable CURL (libcurl) support (default OFF)
- FUN_WITH_JSON=ON|OFF — enable JSON (json-c) support (default OFF)
- FUN_WITH_LIBSQL=ON|OFF — enable libSQL (Turso) client support (default OFF)
- FUN_WITH_XML2=ON|OFF — enable XML (libxml2) support (default OFF)
- FUN_WITH_PCRE2=ON|OFF — enable PCRE2 (Perl-Compatible Regular Expressions) (default OFF)
- FUN_WITH_PCSC=ON|OFF — enable PC/SC smart card (PCSC lite) support (default OFF)
- FUN_WITH_REPL=ON|OFF — enable the interactive REPL (default OFF)
- FUN_WITH_SQLITE=ON|OFF — enable SQLite (sqlite3) support (default OFF)
- FUN_WITH_TCLTK=ON|OFF — enable Tk (GUI via Tcl/Tk) support (default OFF)
```bash
You can also set the default search path for the bundled stdlib with DEFAULT_LIB_DIR:
```
cmake -S . -B build -DDEFAULT_LIB_DIR="/usr/share/fun/lib" -DFUN_WITH_REPL=ON
```
If you encounter a CMake error such as:
CMake Error: Parse error in command line argument: FUN_WITH_JSON
Should be: VAR:type=value
it means you passed a -D option without a value. Always use the form -DNAME=VALUE (e.g., -DFUN_WITH_JSON=ON).
#### SQLite example (optional feature)
SQLite support is optional and disabled by default. To build with it and run the example:
```
cmake -S . -B build -DFUN_WITH_SQLITE=ON
cmake --build build --target fun
# Create the sample database (requires the sqlite3 CLI):
sqlite3 ./database.sqlite < ./examples/data/database.sql
# Run the example
FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/sqlite_example.fun
```
#### libSQL example (optional feature)
libSQL support is optional and disabled by default. It is implemented as an independent extension and can coexist with SQLite. To build with it and run the example:
```
cmake -S . -B build -DFUN_WITH_LIBSQL=ON
cmake --build build --target fun
# Create the sample database using the sqlite3 CLI (libSQL implements the sqlite C API)
sqlite3 ./database.sqlite < ./examples/data/database.sql
# Run the libSQL example
FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/libsql_example.fun
```
Available builtins when built with -DFUN_WITH_LIBSQL=ON:
- libsql_open(path_or_url) -> handle (>0) or 0 on error
- libsql_close(handle) -> Nil
- libsql_exec(handle, sql) -> rc (0 on success)
- libsql_query(handle, sql) -> array of map rows
#### XML example (optional feature)
XML support (via libxml2) is optional and disabled by default. To build with it and run the example:
```
cmake -S . -B build -DFUN_WITH_XML2=ON
cmake --build build --target fun
# Run the example using the stdlib XML class wrapper
FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/xml_class_example.fun
```
Available VM builtins when built with -DFUN_WITH_XML2=ON:
- xml_parse(text: string) -> doc_handle (int > 0) or 0 on error
- xml_root(doc_handle: int) -> node_handle (int > 0) or 0 if missing
- xml_name(node_handle: int) -> string (node tag name)
- xml_text(node_handle: int) -> string (concatenated text of subtree)
Standard library wrapper (lib/io/xml.fun):
- class XML
- parse(text: string): int (doc handle)
- from_file(path: string): int (doc handle)
- root(doc: int): int (node handle)
- name(node: int): string
- text(node: int): string
Example Fun code:
```
include <io/xml.fun>
xml = XML()
doc = xml.from_file("./examples/data/example.xml")
if (doc == 0)
print("Failed to load XML file")
else
root = xml.root(doc)
print(xml.name(root))
print(xml.text(root))
```
Notes:
- Handles are simple integers managed by the VM; nodes are owned by their document.
- This initial integration focuses on parsing and basic navigation. Attributes, children iteration, and XPath may be added later.
#### Tk GUI example (optional feature)
Tk GUI support is optional and disabled by default. It embeds a Tcl/Tk interpreter and exposes a small, Tk-only API to Fun code (no raw Tcl required).
To build with Tk and run the example:
```
cmake -S . -B build -DFUN_WITH_TCLTK=ON
cmake --build build --target fun
# Run the example
FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/tk_hello.fun
```
Available VM builtins when built with -DFUN_WITH_TCLTK=ON:
- tk_title(title: string) -> rc
- tk_label(id: string, text: string) -> rc
- tk_button(id: string, text: string) -> rc
- tk_pack(id: string) -> rc
- tk_loop() -> Nil (enters event loop until the window is closed)
Standard library wrapper (lib/ui/tk.fun):
- class TK
- title(title: string): int
- label(id: string, text: string): int
- button(id: string, text: string): int
- pack(id: string): int
- loop(): Nil
Example Fun code:
```
include <ui/tk.fun>
tk = TK()
tk.title("Fun + Tk GUI")
tk.label("hello", "Hello, world!")
tk.pack("hello")
tk.button("ok", "OK")
tk.pack("ok")
tk.loop()
```
Notes:
- Ensure Tcl/Tk is installed (8.6+). On Linux install tcl/tk packages; on macOS install Homebrew tcl-tk; on Windows ensure the DLLs are available.
- The Fun process terminates when the main window is closed or when the example's OK button is clicked.
### Install Fun to the OS (optional)
Not recommended during early development, but supported:
```
sudo cmake --build build --target install
```
Now run Fun without prefixed FUN_LIB_DIR="$(pwd)/lib" because libs are installed to the
system default lib directory (/usr/share/fun/lib).
After installation, FUN_LIB_DIR usually isnt needed because libs are placed in the system default directory (e.g., /usr/share/fun/lib).
## Usage
```bash
Run a script:
```
fun ./demo.fun
```
## Table of contents
- Language overview and VM internals
- Command line interface and REPL
- Core types and operations
- Built-ins overview (what the VM provides)
- Standard library APIs
- io.console
- io.process
- io.socket
- io.thread
- utils.datetime
- regex
- crypt (MD5, SHA-1/256/384/512)
- encoding.base64
- arrays, strings, maps helpers (hex, range)
- Extra libraries
- JSON (via json-c)
- CURL (via libcurl)
- PCSC (PC/SC smart card)
- SQLite (sqlite3)
- Examples reference
---
## Language overview and VM internals
Fun compiles .fun source files to bytecode and executes them on a stack-based VM. Functions and methods push/pop their arguments and return values on a value stack.
High-level architecture:
- Front-end: parses .fun files, handles includes and constant folding, emits bytecode with debug markers (OP_LINE) used by tracing and REPL-on-error.
- VM core: runs a loop over opcodes (see src/bytecode.h). Values include numbers (integers), strings, arrays, maps, booleans (1/0), functions, and nil.
- Built-ins: I/O, strings, arrays, regex, date/time, OS, networking, threading, and optional JSON/PCRE2/CURL/PCSC/SQLite.
Selected VM concepts (non-exhaustive):
- Control flow: OP_JUMP, OP_JUMP_IF_FALSE, OP_RETURN
- Arithmetic/logic: OP_ADD/SUB/MUL/DIV, OP_MOD, OP_LT/LTE/GT/GTE, OP_EQ/NEQ, OP_AND/OR/NOT
- Stack helpers: OP_DUP, OP_SWAP, OP_POP
- Arrays: OP_MAKE_ARRAY, OP_INDEX_GET/SET, OP_LEN, OP_PUSH, OP_APOP, OP_INSERT/REMOVE, OP_SLICE
- Strings: OP_SUBSTR, OP_SPLIT, OP_JOIN, OP_FIND
- Maps: OP_MAKE_MAP; index ops shared with arrays
- Conversion/typing: OP_TO_NUMBER, OP_TO_STRING, OP_CAST, OP_TYPEOF, OP_UCLAMP/OP_SCLAMP
- Regex: OP_REGEX_MATCH/SEARCH/REPLACE (requires PCRE2 when built)
- Math: OP_MIN/MAX/CLAMP/ABS/POW, OP_RANDOM_SEED/RANDOM_INT
- Iteration helpers: OP_ENUMERATE, OP_ZIP
- OS/IO/network: sockets, files, processes, environment, threads, etc.
- Optional features: JSON (src/vm/json/*), CURL, PCSC, SQLite (src/vm/sqlite/*)
Error handling and debugging:
- Build with FUN_DEBUG=ON for verbose traces
- Run with --trace to print executed lines/opcodes
- Run with --repl-on-error to drop into an interactive REPL when a runtime error occurs
## Command line interface and REPL
- Run a script: fun path/to/script.fun
- Common options: --trace, --repl-on-error (can be combined). REPL requires FUN_WITH_REPL=ON at build time.
- In trace/REPL-on-error modes, the VM annotates output with file:line and function names for easier debugging (see examples/debug_reporting.fun).
## Core types and operations
Types:
- number: signed integer (with helpers for unsigned behavior)
- string: immutable bytes; len(s), join, split, substr, find
- array: ordered list; len, push, apop, insert, remove, slice
- map: associative dictionary typically keyed by strings
- boolean: represented as 1 (true) or 0 (false); operators &&, ||, !
- nil: absence of value
Control flow:
- if/else, while; range helpers in utils.range
Functions and classes:
- Define a function: fun name(args) ...
- Define a class: class Name(constructor params) with method definitions fun method(this, ...)
- _construct acts as the constructor if present; methods use explicit this
Modules and includes:
- #include <path/to/module.fun> for libs under FUN_LIB_DIR
- #include "relative/path.fun" for local includes
- Namespacing via as: #include <utils/math.fun> as m; then call m.add(...)
## Built-ins overview
Console and I/O:
- print(x) — prints value plus newline
- input(prompt) — read line from stdin
Strings and arrays:
- len(x), join(array, sep), split(text, sep), substr(text, start, len), find(text, needle)
- push(array, v), apop(array), insert(array, i, v), remove(array, i), slice(array, start, end)
Conversion and type:
- to_number(x), to_string(x), cast(value, typeName), typeof(x)
- uclamp(number, bits), sclamp(number, bits)
Math and random:
- min(a,b), max(a,b), clamp(x, lo, hi), abs(x), pow(a,b), random_seed(seed), random_int(lo, hiExclusive)
Regex (requires PCRE2 when enabled):
- regex_match(text, pattern) -> 1/0
- regex_search(text, pattern) -> map { match, start, end, groups }
- regex_replace(text, pattern, repl) -> string
OS and processes:
- proc_run(cmd) -> { out: string, code: number }
- system(cmd) -> exit code
- env_get(name), env_set(name, value)
Networking and sockets:
- tcp_connect(host, port) -> fd (>0) or 0
- sock_send(fd, data) -> bytes or -1; sock_recv(fd, maxlen) -> string; sock_close(fd)
- tcp_listen(port, backlog) -> listen fd; tcp_accept(listenFd) -> client fd
- unix_connect(path) -> fd
Threads:
- thread_spawn(func, args) -> thread id; thread_join(id) -> return value
Date and time:
- time_now_ms(), clock_mono_ms(), date_format(ms, fmt)
JSON (optional):
- json_parse(text) -> value or nil
- json_stringify(value, prettyFlag) -> string
- json_from_file(path) -> value or nil
- json_to_file(path, value, prettyFlag) -> 1/0
PC/SC (optional):
- pcsc_establish() -> context id (>0) or 0
- pcsc_list_readers(ctx) -> array of reader names or nil
- pcsc_connect(ctx, readerName) -> handle id (>0) or 0
- pcsc_disconnect(handle) -> 1/0
- pcsc_transmit(handle, bytesArray) -> { data, sw1, sw2, code }
SQLite (optional):
- sqlite_open(path) -> handle (>0) or 0 on error
- sqlite_exec(handle, sql) -> rc (0 = SQLITE_OK)
- sqlite_query(handle, sql) -> array of row maps (string keys)
- sqlite_close(handle) -> nil
Note: Optional features depend on the CMake flags used when building.
---
## Standard library APIs
The stdlib provides small wrappers around VM built-ins, typically organized in classes to avoid global name collisions and to offer sensible defaults.
### io.console
Class Console (lib/io/console.fun):
- prompt(text) -> string
- ask(question) -> string
- ask_yes_no(question) -> 1/0 (y/yes vs n/no)
Example: examples/input_example.fun
### io.process
Class Process (lib/io/process.fun):
- run(cmd) -> { out, code }
- run_merge_stderr(cmd) -> { out, code }
- system(cmd) -> number
- check_call(cmd) -> 1/0
Example: examples/process_example.fun
### io.socket
Provides TcpClient, TcpServer, UnixClient (lib/io/socket.fun).
TcpClient:
- connect(host, port) -> 1/0; is_connected() -> 1/0
- send(data) -> bytes or -1; recv(maxlen) -> string; recv_all(chunk_size) -> string
- close()
TcpServer(port, backlog):
- listen() -> listen fd or 0; accept() -> client fd
- echo_once(maxlen) -> 1 when handled; serve_forever(maxlen) -> never returns
- close()
UnixClient:
- connect(path), is_connected(), send(data), recv(maxlen), close()
Examples: tcp_http_get.fun, tcp_http_get_class.fun, unix_socket_echo.fun, extra/tcp_echo_server_class.fun
### io.thread
Class Thread (lib/io/thread.fun):
- spawn(func, args) -> thread id; join(id) -> return value
- Aliases: start(func, args), wait(id)
Examples: threads_demo.fun, thread_class_example.fun
### utils.datetime
Class DateTime (lib/utils/datetime.fun):
- now_ms(), mono_ms(), format(ms, fmt), iso_now()
Example: datetime_basic.fun
### regex
Class Regex (lib/regex.fun):
- match(text, pattern) -> 1/0
- search(text, pattern) -> { match, start, end, groups }
- replace(text, pattern, repl) -> string
Examples: regex_demo.fun, regex_procedural.fun
### crypt
MD5 (lib/crypt/md5.fun) and SHA family (sha1/sha256/sha384/sha512) provide digest classes and helpers.
Examples: md5_demo.fun, sha1_demo.fun, sha256_demo.fun, sha256_str_demo.fun, sha384_example.fun, sha512_demo.fun, sha512_str_demo.fun
### encoding.base64
Module lib/encoding/base64.fun: base64_encode(string), base64_decode(string)
### arrays, strings, maps helpers
- lib/arrays.fun — array helpers
- lib/strings.fun — string helpers (lower/upper, etc.)
- lib/hex.fun — bytes_to_hex, hex_to_bytes
- lib/utils/range.fun — numeric ranges
- lib/utils/math.fun and lib/math.fun — math helpers
---
## Extra libraries
### JSON (optional)
Build flag: -DFUN_WITH_JSON=ON; requires json-c. VM functions: json_parse, json_stringify, json_from_file, json_to_file. Stdlib class JSON wraps these with light ergonomics. Example: examples/json_showcase.fun.
### CURL (optional)
Build flag: -DFUN_WITH_CURL=ON; requires libcurl. VM provides:
- curl_get(url) -> string ("" on error)
- curl_post(url, body) -> string ("" on error)
- curl_download(url, path) -> 1/0
Examples: curl_get_json.fun, curl_post.fun, curl_download.fun
### PCSC (optional)
Build flag: -DFUN_WITH_PCSC=ON; provides pcsc_* built-ins and a stdlib wrapper class PCSC. Example: pcsc_example.fun.
### SQLite (optional)
Build flag: -DFUN_WITH_SQLITE=ON; requires sqlite3 development headers.
VM API:
- sqlite_open(path) -> handle (>0) or 0
- sqlite_exec(handle, sql) -> rc (0 = SQLITE_OK)
- sqlite_query(handle, sql) -> array of maps (columns as string keys)
- sqlite_close(handle) -> nil
Result mapping notes:
- INTEGER -> number
- FLOAT -> number (floating point)
- TEXT -> string
- NULL -> nil
- BLOB is currently not returned (mapped to nil)
Example flow (examples/sqlite_example.fun):
1) h = sqlite_open("./todo.sqlite")
2) rows = sqlite_query(h, "SELECT id, title, done, created_at FROM tasks ORDER BY id;")
3) rc = sqlite_exec(h, "INSERT INTO tasks (title, done) VALUES ('Try Fun + SQLite', 0);")
4) rows2 = sqlite_query(h, "SELECT count(*) AS cnt FROM tasks;")
5) sqlite_close(h)
---
## Examples reference
You can run examples without installing by pointing FUN_LIB_DIR to the repository lib directory:
FUN_LIB_DIR="$(pwd)/lib" ./build/fun examples/<name>.fun
Highlights (not exhaustive):
- arrays.fun, arrays_advanced.fun, arrays_iter.fun — array operations
- booleans.fun, boolean_decl.fun — boolean basics
- builtins_conversions.fun, builtins_extended.fun — conversions and math helpers
- builtins_maps_and_more.fun — maps and indexing
- byte_for_demo.fun — bitwise operations
- class_constructor.fun, classes_demo.fun, inheritance_demo.fun — classes
- datetime_basic.fun — date/time utilities
- debug_reporting.fun, repl_on_error.fun — tracing and REPL-on-error
- exit_example.fun — exit codes
- expressions_test.fun — operators
- file_io.fun, file_print_for_file_line_by_line.fun — file I/O
- for_range_test.fun — numeric ranges
- functions_test.fun — functions and higher-order usage
- have_fun.fun — quick sanity check
- if_else_test.fun — branching
- include_lib.fun, include_local.fun, include_namespace.fun — includes and namespacing
- input_example.fun — console input
- json_showcase.fun — JSON usage
- curl_get_json.fun, curl_post.fun, curl_download.fun — HTTP via CURL
- loops_break_continue.fun, nested_loops.fun, while_test.fun — loops
- md5_demo.fun, sha1_demo.fun, sha256_demo.fun, sha256_str_demo.fun, sha384_example.fun, sha512_demo.fun, sha512_str_demo.fun — hashing
- objects_basic.fun, objects_more.fun — map/object patterns
- os_env.fun — environment variables
- pcsc_example.fun — smart card demo
- process_example.fun — running external commands
- regex_demo.fun, regex_procedural.fun — regex usage
- stdlib_showcase.fun — tour through stdlib
- strings_test.fun — string operations
- tcp_http_get.fun, tcp_http_get_class.fun — TCP client demos
- thread_class_example.fun, threads_demo.fun — threading
- try_catch_finally.fun, try_catch_with_error.fun — error handling
- typeof.fun, typeof_features.fun — types and casting
- type_safety.fun, type_safety_fails.fun — type safety
- types_integers.fun, signed_ints.fun, uint_types.fun — integers
- unix_socket_echo.fun — UNIX domain sockets
- sqlite_example.fun — SQLite usage
Notes:
- Some examples rely on optional features (JSON, CURL, PCSC, SQLite) and degrade gracefully when disabled.
---
## Internals notes (selected)
JSON: src/vm/json/* wraps json-c. OP_JSON_PARSE and friends convert json_object to Fun values and back; stdlib JSON class adds ergonomics.
PCSC: The VM interfaces with pcsc-lite/WinSCard and returns maps with data and status words. The stdlib wrapper handles absent hardware defensively.
SQLite: src/vm/sqlite/* implements open/exec/query/close using a simple handle registry. Query prepares a statement, steps rows, maps columns by name to values, and returns an array of row maps.
---
## Development
This project follows Semantic Versioning. Commit messages include the version (e.g., 1.2.3). Version bumps are made in CMakeLists.txt for code changes; documentation-only commits include the current version in the message but do not bump it.
### Development systems
- GNU/Linux (glibc, musl), FreeBSD (Clang), Windows (Cygwin + GCC). Other Unix-like systems likely work but are untested.
### Contributing and further reading
- Browse lib/ for stdlib APIs (files often document their own interfaces)
- src/bytecode.h lists supported opcodes; implementations live under src/vm/
- examples/ are the best starting point to learn by doing

View file

@ -45,9 +45,9 @@ print(max(5, 9)) // -> 9
print(clamp(15, 0, 10)) // -> 10
print(abs(-7)) // -> 7
print(pow(2, 8)) // -> 256
random(123) // seed RNG
print(randomInt(0, 3)) // -> 0..2 (deterministic for seed 123)
print(randomInt(5, 6)) // -> 5
random_seed(123) // seed RNG
print(random_int(0, 3)) // -> 0..2 (deterministic for seed 123)
print(random_int(5, 6)) // -> 5
/* Expected output:
a-b-c

View file

@ -0,0 +1,41 @@
#!/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-10
*/
// Demonstrate byte overflow with try/catch
// Note: Runtime exceptions are not yet implemented; overflow emits an error and halts.
// This example shows intended usage once exceptions are supported.
print("=== byte overflow with try/catch demo ===")
try
byte b = 0
print("assign 255 -> ok")
b = 255
print(b)
print("assign 256 -> should overflow and be caught")
b = 256 // will trigger OverflowError: value out of range for uint8
print("this line will not execute if overflow occurs")
catch err
print("caught error:")
print(err)
finally
print("finally block executed")
/* Expected output:
=== byte overflow with try/catch demo ===
assign 255 -> ok
255
assign 256 -> should overflow and be caught
caught error:
OverflowError: value out of range for uint8
finally block executed
*/

View file

@ -0,0 +1,163 @@
#!/usr/bin/env fun
/*
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2025 Johannes Findeisen
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2025-12-13
*/
// conversions_showcase.fun
// Demonstrates Fun's data type conversion features:
// - to_number(x)
// - to_string(x)
// - cast(value, typeName)
// - typeof(x)
// - uclamp(number, bits), sclamp(number, bits)
print("=== Conversions showcase ===")
// typeof on core literals
print("typeof(123) -> " + typeof(123)) // Number
print("typeof(\"abc\") -> " + typeof("abc")) // String
print("typeof([1,2]) -> " + typeof([1,2])) // Array
// Use a variable for map to avoid parser ambiguities
mm = { "a": 1 }
print("typeof({\"a\":1}) -> " + typeof(mm)) // Map
print("typeof(0) -> " + typeof(0)) // Number (Boolean is 0/1 as Number)
print("typeof(nil) -> " + typeof(nil)) // Nil
print("")
print("-- to_number(x) --")
print(to_number(42)) // 42
print(to_number("123")) // 123
print(to_number("12x")) // 0 (invalid -> 0)
print(to_number(0)) // 0
print(to_number(1)) // 1
print("")
print("-- to_string(x) --")
print(to_string(42)) // "42"
print(to_string("hi")) // "hi"
print(to_string([1,2,3])) // "[array n=3]" or similar representation
print(to_string({ "k": 7 })) // "{\"k\":7}" or implementation-defined
print(to_string(nil)) // "nil" (implementation-defined)
print("")
print("-- cast(value, typeName) --")
// Number: parse decimals; invalid -> 0
print(cast("123", "Number"))
print(cast("12x", "Number"))
// String: stringify
print(cast(100, "String"))
print(typeof(cast(100, "String")))
// Boolean: 0 -> 0, non-zero -> 1
print(cast(0, "Boolean"))
print(cast(42, "Boolean"))
// Array: others get wrapped
a = cast(42, "Array")
print(typeof(a))
print(len(a))
print(a[0])
// Map: non-maps become empty map
m = cast(42, "Map")
print(typeof(m))
// Nil: always Nil
n = cast("x", "Nil")
print(typeof(n))
// Function: non-functions -> Nil
fun foo()
return 7
print(typeof(cast(foo, "Function")))
print(typeof(cast(42, "Function")))
print("")
print("-- Integer width ranges via typed variables --")
// Unsigned 8-bit: values clamped to [0..255]
uint8 u8 = 0
u8 = 255
print(u8) // -> 255
// u8 = -1 // Uncomment to see OverflowError: value out of range for uint8
// u8 = 300 // Uncomment to see OverflowError: value out of range for uint8
// Signed 8-bit: values clamped to [-128..127]
int8 s8 = 0
s8 = -128
print(s8) // -> -128
s8 = 127
print(s8) // -> 127
// s8 = -200 // Uncomment to see OverflowError for int8
// s8 = 200 // Uncomment to see OverflowError for int8
// 16-bit examples
uint16 u16 = 0
u16 = 65535
print(u16) // -> 65535
// u16 = 70000 // Uncomment to see OverflowError for uint16
int16 s16 = 0
s16 = -32768
print(s16) // -> -32768
s16 = 32767
print(s16) // -> 32767
print("=== Done ===")
/* Expected output:
=== Conversions showcase ===
typeof(123) -> Number
typeof("abc") -> String
typeof([1,2]) -> Array
typeof({"a":1}) -> String
typeof(0) -> Number
typeof(nil) -> Nil
-- to_number(x) --
42
123
0
0
1
-- to_string(x) --
42
hi
[array n=3]
{map n=1}
nil
-- cast(value, typeName) --
123
0
100
String
0
1
Array
1
42
String
Nil
Function
Nil
-- Integer width ranges via typed variables --
255
-128
127
65535
-32768
32767
=== Done ===
*/

54
examples/crc32_example.fun Executable file
View file

@ -0,0 +1,54 @@
#!/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-11-26
*/
// Example: Using the CRC32 class from lib/crypt/crc32.fun
include <crypt/crc32.fun>
print("-- CRC32 example --")
c = CRC32()
// 1) Known test vector: "123456789" -> cbf43926
msg = "123456789"
crc1 = c.crc32_str(msg)
print("Input (ASCII): " + msg)
print("CRC32: " + crc1) // expected: cbf43926
print("")
// 2) Same data provided as hex string
hex_msg = "313233343536373839" // hex for "123456789"
crc2 = c.crc32_hex(hex_msg)
print("Input (hex): " + hex_msg)
print("CRC32: " + crc2) // expected: cbf43926
print("")
// 3) Another quick demo
other = "Fun language"
crc3 = c.crc32_str(other)
print("Input (ASCII): " + other)
print("CRC32: " + crc3)
/* Expected output:
-- CRC32 example --
Input (ASCII): 123456789
CRC32: cbf43926
Input (hex): 313233343536373839
CRC32: cbf43926
Input (ASCII): Fun language
CRC32: d7d83272
*/

54
examples/crc32c_example.fun Executable file
View file

@ -0,0 +1,54 @@
#!/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-11-26
*/
// Example: Using the CRC32C class from lib/crypt/crc32c.fun
include <crypt/crc32c.fun>
print("-- CRC32C example --")
c = CRC32C()
// 1) Known test vector: "123456789" -> e3069283
msg = "123456789"
crc1 = c.crc32c_str(msg)
print("Input (ASCII): " + msg)
print("CRC32C: " + crc1) // expected: e3069283
print("")
// 2) Same data provided as hex string
hex_msg = "313233343536373839" // hex for "123456789"
crc2 = c.crc32c_hex(hex_msg)
print("Input (hex): " + hex_msg)
print("CRC32C: " + crc2) // expected: e3069283
print("")
// 3) Another quick demo
other = "Fun language"
crc3 = c.crc32c_str(other)
print("Input (ASCII): " + other)
print("CRC32C: " + crc3)
/* Expected output:
-- CRC32C example --
Input (ASCII): 123456789
CRC32C: e3069283
Input (hex): 313233343536373839
CRC32C: e3069283
Input (ASCII): Fun language
CRC32C: c0158b58
*/

24
examples/curl_download.fun Executable file
View file

@ -0,0 +1,24 @@
#!/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-11-25
*/
/*
* Demonstrates curl_download saving a file to disk.
*/
url = "https://httpbin.org/image/png"
path = "./downloaded.png"
ok = curl_download(url, path)
if ok == 1
print("Downloaded to " + path)
else
print("Download failed")

25
examples/curl_get_json.fun Executable file
View file

@ -0,0 +1,25 @@
#!/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-11-25
*/
/*
* Demonstrates curl_get and JSON.parse working together.
*/
url = "https://httpbin.org/json"
resp = curl_get(url)
print("Raw length: " + to_string(len(resp)))
// If JSON support is enabled, parse it
obj = json_parse(resp)
if obj != nil
print("Title: " + obj["slideshow"]["title"])

32
examples/curl_post.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-11-25
*/
/*
* Demonstrates curl_post sending form data and printing response.
*/
url = "https://httpbin.org/post"
data = "name=Fun&lang=fun"
resp = curl_post(url, data)
print("Response: " + resp)
// If JSON support is enabled, parse it
obj = json_parse(resp)
if obj != nil
print("Content-Type: " + to_string(obj["headers"]["Content-Type"]))
if obj != nil
print("Host: " + to_string(obj["headers"]["Host"]))
if obj != nil
print("Origin: " + to_string(obj["origin"]))

32
examples/data/catalog.xml Normal file
View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<product id="SKU-1001">
<name>Wireless Keyboard</name>
<category>Peripherals</category>
<price currency="USD">39.99</price>
<specs>
<layout>US</layout>
<connection>Bluetooth</connection>
<battery>AA</battery>
</specs>
</product>
<product id="SKU-2002">
<name>27" Monitor</name>
<category>Displays</category>
<price currency="USD">199.00</price>
<specs>
<resolution>2560x1440</resolution>
<panel>IPS</panel>
<refresh>75Hz</refresh>
</specs>
</product>
<product id="SKU-3003">
<name>USB-C Dock</name>
<category>Peripherals</category>
<price currency="USD">89.50</price>
<specs>
<ports>2xHDMI, 3xUSB-A, 1xUSB-C PD</ports>
<pd>65W</pd>
</specs>
</product>
</catalog>

32
examples/data/complex.ini Normal file
View file

@ -0,0 +1,32 @@
[app]
name = "FunApp"
version = "1.2.3"
debug = "1"
[database]
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"
[features]
feature_x = "on"
feature_y = "off"
[paths]
data_dir = "./data"
log_file = "./logs/app.log"

View file

@ -0,0 +1,53 @@
{
"project": {
"name": "Fun",
"version": "0.27.2",
"website": "https://fun-lang.xyz",
"license": {
"name": "Apache-2.0",
"url": "https://opensource.org/license/apache-2-0"
}
},
"features": {
"enabled": ["arrays", "maps", "json", "pcsc"],
"experimental": {
"repl": true,
"sockets": true,
"odbc": false,
"notes": null
}
},
"users": [
{
"id": 1,
"name": "Ada",
"roles": ["admin", "math"],
"active": true,
"score": 99.5,
"prefs": {
"theme": "dark",
"editor": {"tabWidth": 2, "font": "Fira Code"}
}
},
{
"id": 2,
"name": "Linus",
"roles": ["user", "kernel"],
"active": false,
"score": 88,
"prefs": {
"theme": "light",
"editor": {"tabWidth": 8, "font": "Monospace"}
}
}
],
"metrics": {
"counters": [0, 1, 1, 2, 3, 5, 8],
"latency_ms": {"p50": 1.23, "p90": 3.21, "p99": 12.34},
"builds": 1234567890123456789,
"last_release_ts": 1732406400000
},
"matrix": [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
"notes": "UTF-8 ✓ emojis: 🚀🔥",
"null_field": null
}

View file

@ -0,0 +1,12 @@
PRAGMA foreign_keys = ON;
DROP TABLE IF EXISTS tasks;
CREATE TABLE tasks (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
done INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
INSERT INTO tasks (title, done) VALUES
('Write Fun + SQLite example', 1),
('Ship optional feature flag', 0),
('Celebrate with coffee', 0);

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<company>
<department name="Engineering">
<employee id="E-100">
<name>Alice Doe</name>
<role>Senior Developer</role>
<email>alice@example.com</email>
</employee>
<employee id="E-101">
<name>Bob Roe</name>
<role>DevOps Engineer</role>
<email>bob@example.com</email>
</employee>
</department>
<department name="Sales">
<employee id="S-200">
<name>Carol Smith</name>
<role>Account Executive</role>
<email>carol@example.com</email>
</employee>
</department>
</company>

24
examples/data/example.xml Normal file
View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<company name="Acme Corp">
<departments>
<department id="eng" name="Engineering">
<team name="Platform">
<member id="u1">Alice</member>
<member id="u2">Bob</member>
</team>
<team name="Product">
<member id="u3">Carol</member>
</team>
</department>
<department id="ops" name="Operations">
<team name="SRE">
<member id="u4">Dave</member>
</team>
</department>
</departments>
<offices>
<office city="Berlin" country="DE"/>
<office city="Paris" country="FR"/>
</offices>
<note>Welcome to Acme!</note>
</company>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<ns:library xmlns:ns="http://example.org/ns/library" xmlns:bk="http://example.org/ns/book">
<bk:book id="B-1">
<bk:title>The Art of Fun</bk:title>
<bk:author>J. Findeisen</bk:author>
</bk:book>
<bk:book id="B-2">
<bk:title>Minimal VM Design</bk:title>
<bk:author>A. Dev</bk:author>
</bk:book>
</ns:library>

View file

@ -0,0 +1,25 @@
# INI with subsection-style headers for iniparser 4.2.6
[server]
host = example.org
port = 8080
[server.tls]
enabled = true
version = 1.3
ciphers = TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256
[users.admin]
name = alice
active = yes
quota_gb = 100
[users.guest]
name = bob
active = no
quota_gb = 5
[paths.logs]
dir = ./var/log/fun
rotate = true
max_files = 7

66
examples/datetime_extended.fun Executable file
View file

@ -0,0 +1,66 @@
#!/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-09
*/
// Extended Date/Time examples using the stdlib DateTime class
#include <utils/datetime.fun>
fun main()
dt = DateTime()
print("--- Basics ---")
now_ms = dt.now_ms()
print(join(["now_ms: ", to_string(now_ms)], ""))
print(join(["now_s: ", to_string(dt.now_s())], ""))
print(join(["iso_now: ", dt.iso_now()], ""))
print(join(["today: ", dt.today_str()], ""))
print("--- Formatting helpers ---")
print(join(["iso_from(now): ", dt.iso_from(now_ms)], ""))
print(join(["date_str(now): ", dt.date_str(now_ms)], ""))
print(join(["time_str(now): ", dt.time_str(now_ms)], ""))
print("--- Conversions ---")
print(join(["ms_to_s(1234): ", to_string(dt.ms_to_s(1234))], ""))
print(join(["s_to_ms(2): ", to_string(dt.s_to_ms(2))], ""))
print("--- Arithmetic ---")
in_2s = dt.add_seconds(now_ms, 2)
print(join(["in 2s (ms): ", to_string(in_2s)], ""))
print(join(["diff_ms(now, in_2s): ", to_string(dt.diff_ms(now_ms, in_2s))], ""))
print("--- Timer ---")
t0 = dt.start_timer()
dt.sleep_ms(120)
print(join(["elapsed ~120ms: ", to_string(dt.elapsed_ms(t0)), " ms"], ""))
main()
/* Possible output:
--- Basics ---
now_ms: 1765320472780
now_s: 1765320472
iso_now: 2025-12-09T23:47:52
today: 2025-12-09
--- Formatting helpers ---
iso_from(now): 2025-12-09T23:47:52
date_str(now): 2025-12-09
time_str(now): 23:47:52
--- Conversions ---
ms_to_s(1234): 1
s_to_ms(2): 2000
--- Arithmetic ---
in 2s (ms): 1765320474780
diff_ms(now, in_2s): 2000
--- Timer ---
elapsed ~120ms: 120 ms
*/

31
examples/datetime_timer.fun Executable file
View file

@ -0,0 +1,31 @@
#!/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-09
*/
// Simple stopwatch using DateTime helpers
#include <utils/datetime.fun>
fun main()
dt = DateTime()
print("Starting timer for ~250ms ...")
t0 = dt.start_timer()
dt.sleep_s(0.2) // 200 ms
dt.sleep_ms(50)
elapsed = dt.elapsed_ms(t0)
print(join(["Elapsed: ", to_string(elapsed), " ms"], ""))
main()
/* Possible output:
Starting timer for ~250ms ...
Elapsed: 250 ms
*/

23
examples/echo_example.fun Executable file
View file

@ -0,0 +1,23 @@
#!/usr/bin/env fun
/*
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2025 Johannes Findeisen
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2025-12-10
*/
// echo_example.fun
// Demonstrates echo(expr) which prints without a trailing newline.
// Build a line without newline using echo, then finish with print to add newline
echo("Hello, ")
echo("world")
print("!")
// Expected output:
// Hello, world!

View file

@ -1,5 +1,14 @@
#!/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
*/
// Byte + for-loop demonstration
print("=== byte with hex literal and clamping ===")
@ -32,6 +41,11 @@ print(typeof(x)) // -> "String"
/* Expected output:
=== byte with hex literal and clamping ===
OverflowError: value out of range for uint8
*/
/* Expected output (OLD):
=== byte with hex literal and clamping ===
255
255
255

33
examples/extra/pcre2_demo.fun Executable file
View file

@ -0,0 +1,33 @@
#!/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-11-25
*/
/*
include <regex/pcre2.fun>
rx = Pcre2()
text = "E-mails: one@example.com, Two@Example.COM; invalid: x@y"
pattern = "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
print("Has email? ", rx.test(pattern, text, rx.i()))
first = rx.match(pattern, text, rx.i())
if first != nil {
print("First: ", first["full"])
}
all = rx.find_all(pattern, text, rx.i())
for m in all {
print("Found: ", m["full"])
}
*/

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

46
examples/ini_class_demo.fun Executable file
View file

@ -0,0 +1,46 @@
#!/usr/bin/env fun
/*
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2025 Johannes Findeisen
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2025-11-30
*/
// Demonstration of the Ini stdlib class from lib/io/ini.fun
include <io/ini.fun>
ini = INI()
path = "./examples/data/complex.ini"
if (ini.load(path) == 0)
print("Failed to load " + path)
exit(1)
// Read a few values
app_name = ini.get_string("app", "name", "FunApp")
app_version = ini.get_string("app", "version", "0.0.0")
app_debug = ini.get_bool("app", "debug", 0)
db_host = ini.get_string("database", "host", "localhost")
db_port = ini.get_int("database", "port", 5432)
print("[app]")
print(" name=" + app_name)
print(" version=" + app_version)
print(" debug=" + to_string(app_debug))
print("[database]")
print(" host=" + db_host)
print(" port=" + to_string(db_port))
// Update a value and save back to the same file
ini.set("app", "debug", 1)
ok = ini.save(nil)
print("saved=" + to_string(ok))
ini.close()

75
examples/ini_complex.fun Executable file
View file

@ -0,0 +1,75 @@
#!/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-11-30
*/
// Complex INI parsing example using iniparser 4.2.6 opcodes.
path = "./examples/data/complex.ini"
h = ini_load(path)
if h == 0
print("Failed to load "+path)
else
// app
app_name = ini_get_string(h, "app", "name", "FunApp")
app_version = ini_get_string(h, "app", "version", "0.0.0")
app_debug = ini_get_bool(h, "app", "debug", 0)
// database
db_host = ini_get_string(h, "database", "host", "localhost")
db_port = ini_get_int(h, "database", "port", 5432)
db_user = ini_get_string(h, "database", "user", "user")
db_pass = ini_get_string(h, "database", "pass", "")
db_pool = ini_get_int(h, "database", "pool_size", 4)
db_timeout = ini_get_double(h, "database", "timeout", 2.0)
// network
net_ssl = ini_get_bool(h, "network", "ssl", 0)
net_retries = ini_get_int(h, "network", "retries", 3)
base_url = ini_get_string(h, "network", "base_url", "")
// features
feature_x = ini_get_bool(h, "features", "feature_x", 0)
feature_y = ini_get_bool(h, "features", "feature_y", 0)
// paths
data_dir = ini_get_string(h, "paths", "data_dir", "./data")
log_file = ini_get_string(h, "paths", "log_file", "./logs/app.log")
// Print a structured summary
print("[app]")
print(" name=" + app_name)
print(" version=" + app_version)
print(" debug=" + to_string(app_debug))
print("[database]")
print(" host=" + db_host)
print(" port=" + to_string(db_port))
print(" user=" + db_user)
print(" pass=" + db_pass)
print(" pool_size=" + to_string(db_pool))
print(" timeout=" + to_string(db_timeout))
print("[network]")
print(" ssl=" + to_string(net_ssl))
print(" retries=" + to_string(net_retries))
print(" base_url=" + base_url)
print("[features]")
print(" feature_x=" + to_string(feature_x))
print(" feature_y=" + to_string(feature_y))
print("[paths]")
print(" data_dir=" + data_dir)
print(" log_file=" + log_file)
// Clean up
ini_free(h)

30
examples/ini_demo.fun Executable 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 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-11-30
*/
// Minimal demo for INI opcodes using iniparser 4.2.6
path = "./examples/data/complex.ini"
h = ini_load(path)
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)
s = ini_get_bool(h, "network", "ssl", 0)
print("user=" + u)
print("retries=" + to_string(r))
print("ssl=" + to_string(s))
ok = ini_set(h, "auth", "token", "abcd1234")
if ok
ini_save(h, path)
ini_free(h)

70
examples/ini_subsections.fun Executable file
View file

@ -0,0 +1,70 @@
#!/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-11-30
*/
// Demonstration of INI subsections like [section.subsection]
// Uses iniparser 4.2.6 via Fun's ini_* opcodes
path = "./examples/data/subsections.ini"
h = ini_load(path)
if h == 0
print("Failed to load "+path)
else
// Top-level server
srv_host = ini_get_string(h, "server", "host", "localhost")
srv_port = ini_get_int(h, "server", "port", 80)
// Subsection: server.tls
tls_enabled = ini_get_bool(h, "server.tls", "enabled", 0)
tls_version = ini_get_double(h, "server.tls", "version", 1.2)
tls_ciphers = ini_get_string(h, "server.tls", "ciphers", "")
// Subsections: users.*
admin_name = ini_get_string(h, "users.admin", "name", "admin")
admin_active = ini_get_bool(h, "users.admin", "active", 1)
admin_quota = ini_get_int(h, "users.admin", "quota_gb", 10)
guest_name = ini_get_string(h, "users.guest", "name", "guest")
guest_active = ini_get_bool(h, "users.guest", "active", 0)
guest_quota = ini_get_int(h, "users.guest", "quota_gb", 1)
// Subsection: paths.logs
logs_dir = ini_get_string(h, "paths.logs", "dir", "./logs")
logs_rotate = ini_get_bool(h, "paths.logs", "rotate", 0)
logs_max_files = ini_get_int(h, "paths.logs", "max_files", 5)
// Print
print("[server]")
print(" host=" + srv_host)
print(" port=" + to_string(srv_port))
print("[server.tls]")
print(" enabled=" + to_string(tls_enabled))
print(" version=" + to_string(tls_version))
print(" ciphers=" + tls_ciphers)
print("[users.admin]")
print(" name=" + admin_name)
print(" active=" + to_string(admin_active))
print(" quota_gb=" + to_string(admin_quota))
print("[users.guest]")
print(" name=" + guest_name)
print(" active=" + to_string(guest_active))
print(" quota_gb=" + to_string(guest_quota))
print("[paths.logs]")
print(" dir=" + logs_dir)
print(" rotate=" + to_string(logs_rotate))
print(" max_files=" + to_string(logs_max_files))
ini_free(h)

65
examples/json_showcase.fun Executable file
View file

@ -0,0 +1,65 @@
#!/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-11-24
*/
// Demonstrates JSON.parse/stringify/from_file/to_file via the stdlib JSON class.
include <io/json.fun>
json = JSON()
print("-- JSON: parse from string and pretty print --")
// Build a sample JSON text with most value types
sample = '{"name":"Ada","active":true,"score":99.5,"count":42,"tags":["C","Ada","Math"],"extra":null}'
obj = json.parse(sample)
print("Dump:")
print(obj)
print(obj["name"]) // Ada
print(obj["active"]) // 1
print(obj["count"]) // 42
print(len(obj["tags"])) // 3
pretty = json.stringify(obj, 1)
print(pretty)
print("-- JSON: load from file, inspect, and save pretty to /tmp --")
// Load non existent json file
path = "examples/data/nonexistent.json"
cfg = json.from_file(path)
print("Dump:")
print(cfg)
// Load a more complex example shipped with the repo
path = "examples/data/complex.json"
cfg = json.from_file(path)
print("Dump:")
print(cfg)
// Access nested fields
print(cfg["project"]["name"]) // project name
print(cfg["project"]["version"]) // version string
print(len(cfg["users"])) // number of users
// Derive a small summary map
summary = {}
summary["user_count"] = len(cfg["users"])
summary["first_user_name"] = cfg["users"][1]["name"]
summary["features_enabled"] = cfg["features"]["enabled"]
print(json.stringify(summary, 1))
// Write the loaded config back as pretty JSON
out_path = "/tmp/fun_complex_out.json"
ok = json.to_file(out_path, cfg, 1)
print(ok) // 1 on success

59
examples/libsql_example.fun Executable file
View file

@ -0,0 +1,59 @@
#!/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-11-26
*/
// Demonstrates the optional libSQL extension
// Build with: cmake -S . -B build -DFUN_WITH_LIBSQL=ON && cmake --build build
// Prepare sample DB from SQL if needed (requires sqlite3 CLI installed)
// Create it once with:
// sqlite3 ./database.sqlite < ./examples/data/database.sql
number h = libsql_open("./database.sqlite")
if h == 0
print("Failed to open libSQL database")
else
libsql_exec(h, "CREATE TABLE IF NOT EXISTS todos(id INTEGER PRIMARY KEY, title TEXT, done INT)")
libsql_exec(h, "DELETE FROM todos")
libsql_exec(h, "INSERT INTO todos(title, done) VALUES('Buy milk', 0)")
libsql_exec(h, "INSERT INTO todos(title, done) VALUES('Write code', 1)")
rows = libsql_query(h, "SELECT id, title, done FROM todos ORDER BY id")
for row in rows
print(to_string(row["id"]) + ": " + to_string(row["title"]) + " (done="+to_string(row["done"]) + ")")
rows = libsql_query(h, "SELECT id, title, done, created_at FROM tasks ORDER BY id;")
print("Tasks (" + to_string(len(rows)) + "):")
for row in rows
string status = ""
if row["done"] == 1
status = ""
print("- [" + status + "] (#" + to_string(row["id"]) + ") " + to_string(row["title"]) + " " + to_string(row["created_at"]))
number rc = libsql_exec(h, "INSERT INTO tasks (title, done) VALUES ('Try Fun + SQLite', 0);")
print("Insert rc=" + to_string(rc))
rows2 = libsql_query(h, "SELECT count(*) AS cnt FROM tasks;")
print("Total tasks now: " + to_string(rows2[0]["cnt"]))
libsql_close(h)
/* Example output:
1: Buy milk (done=0)
2: Write code (done=1)
Tasks (3):
- [✔] (#1) Write Fun + SQLite example 2025-11-26 23:20:41
- [✘] (#2) Ship optional feature flag 2025-11-26 23:20:41
- [✘] (#3) Celebrate with coffee 2025-11-26 23:20:41
Insert rc=0
Total tasks now: 4
*/

View file

@ -20,10 +20,10 @@ md5 = MD5()
print("=== MD5 demo (hex input) ===")
// "abc" => 0x61 0x62 0x63
print(md5.md5_hex("616263")) // -> 900150983cd24fb0d6963f7d28e17f72
print(md5.md5_hex("616263")) // -> 900150983cd24fb0d6963f7d28e17f72
// empty string "" => hex ""
print(md5.md5_hex("")) // -> d41d8cd98f00b204e9800998ecf8427e
print(md5.md5_hex("")) // -> d41d8cd98f00b204e9800998ecf8427e
// "message digest"
print(md5.md5_hex("6d65737361676520646967657374")) // -> f96b697d7cb7938d525a2f31aaf161d0
@ -32,7 +32,10 @@ print(md5.md5_hex("6d65737361676520646967657374")) // -> f96b697d7cb7938d525a2f
print(md5.md5_hex("6162636465666768696a6b6c6d6e6f707172737475767778797a")) // -> c3fcd3d76192e4007dfb496cca67e13b
// "Have Fun!"
print(md5.md5_str("Have Fun!")) // -> 852438d026c018c4307b916406f98c62
print(md5.md5_str("Have Fun!")) // -> 812f2c01287af0e7c0a0b3daa381a51a
// More MD5
print(md5.md5_str("a")) // -> 0cc175b9c0f1b6a831c399e269772661
print("=== Done ===")
@ -43,5 +46,6 @@ d41d8cd98f00b204e9800998ecf8427e
f96b697d7cb7938d525a2f31aaf161d0
c3fcd3d76192e4007dfb496cca67e13b
812f2c01287af0e7c0a0b3daa381a51a
0cc175b9c0f1b6a831c399e269772661
=== Done ===
*/

140
examples/pcre2_opcodes.fun Executable file
View file

@ -0,0 +1,140 @@
#!/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-11-25
*/
// PCRE2 example using VM builtins directly (no class wrapper)
// Requires building Fun with -DFUN_WITH_PCRE2=ON
print("-- PCRE2 builtins (no class) --")
pattern = "(\\w+)" // capture a word
text = "Hello 123 world"
// Flags: 1=I, 2=M, 4=S, 8=U (UTF), 16=X; well use UTF by default
flags = 8
print("test:")
print(pcre2_test(pattern, text, flags))
m = pcre2_match(pattern, text, flags)
if (m != nil)
print("first full:")
print(m["full"])
print("span:")
print(m["start"])
print("..")
print(m["end"])
print("groups count:")
print(len(m["groups"]))
all = pcre2_findall("\\w+", text, flags)
for x in all
print("all:")
print(x["full"])
print("@")
print(x["start"])
print("..")
print(x["end"])
print("")
print("-- More regex demos --")
// Helper: OR flags (uses VM bor opcode)
fun OR(a, b)
return bor(a, b)
// Demo 1: E-mail extraction (case-insensitive)
email_text = "E-mails: one@example.com, Two@Example.COM; invalid: x@y"
email_pat = "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
email_flags = OR(flags, 1) // UTF | I
print("Emails (findall):")
emails = pcre2_findall(email_pat, email_text, email_flags)
for e in emails
print(e["full"])
// Demo 2: URLs (very simple, for demo purposes)
url_text = "See http://example.com and https://fun-lang.xyz/docs?x=1#top"
url_pat = "https?://[A-Za-z0-9._~:/?#[@]!$&'()*+,;=%-]+"
print("URLs:")
for u in pcre2_findall(url_pat, url_text, flags)
print(u["full"])
// Demo 3: IPv4 addresses
ip_text = "ping 8.8.8.8 and 192.168.0.1; not 999.999.999.999"
ip_pat = "(?:(?:25[0-5]|2[0-4]\\d|1?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|1?\\d?\\d)"
print("IPv4:")
for ip in pcre2_findall(ip_pat, ip_text, flags)
print(ip["full"])
// Demo 4: Dates (YYYY-MM-DD)
date_text = "Born 1999-12-31, updated 2025-11-25, bad 2025-13-40"
date_pat = "(\\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])"
print("Dates (with groups Y/M/D):")
for d in pcre2_findall(date_pat, date_text, flags)
print(d["full"])
print(join(d["groups"], "/"))
// Demo 5: Hex colors (#RRGGBB)
color_text = "Palette: #FF00FF, #1a2b3c, not #abcd or #12345g"
color_pat = "#[0-9A-Fa-f]{6}"
print("Hex colors:")
for c in pcre2_findall(color_pat, color_text, flags)
print(c["full"])
// Demo 6: Quoted strings with escapes
q_text = 'say "hi there" and "indented" plus "quote\\"inside"'
q_pat = '"([^"\\\\]|\\\\.)*"'
print("Quoted strings (with escapes):")
for q in pcre2_findall(q_pat, q_text, flags)
print(q["full"])
// Demo 7: Multiline anchors with /m (M flag)
ml_text = "first line\nSecond line\nthird"
ml_pat = "^(\\w+)"
ml_flags = OR(flags, 2) // UTF | M
print("Multiline ^ anchors (first token of each line):")
for ml in pcre2_findall(ml_pat, ml_text, ml_flags)
print(ml["full"])
// Demo 8: Dotall vs non-dotall
ds_text = "BEGIN\nline1\nline2\nEND"
pat_nd = "BEGIN.*END" // default: . does not match newlines
pat_ds = "BEGIN.*END" // with DOTALL, it does
print("Dotall OFF (should fail):")
print(pcre2_test(pat_nd, ds_text, flags))
print("Dotall ON (should match):")
print(pcre2_test(pat_ds, ds_text, OR(flags, 4)))
// Demo 9: Word boundaries and case-insensitive find
wb_text = "The theater and the THE can differ."
wb_pat = "\\bthe\\b"
print("Word boundary, case-insensitive:")
for w in pcre2_findall(wb_pat, wb_text, OR(flags, 1))
print(w["full"])
// Demo 10: Lookahead word followed by number
la_text = "foo 123, bar, baz 9"
la_pat = "\\w+(?=\\s+\\d+)"
print("Lookahead (word before number):")
for a in pcre2_findall(la_pat, la_text, flags)
print(a["full"])
// Demo 11: Non-greedy vs greedy
ng_text = "<a>one</a><a>two</a>"
greedy = "<a>.*</a>"
lazy = "<a>.*?</a>"
print("Greedy:")
for g in pcre2_findall(greedy, ng_text, OR(flags, 4)) // DOTALL ensures '.' covers any
print(g["full"])
print("Non-greedy:")
for l in pcre2_findall(lazy, ng_text, OR(flags, 4))
print(l["full"])

32
examples/pcre2_showcase.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-11-25
*/
/*
include <regex/pcre2.fun>
re = PCRE2()
print("Testing PCRE2 showcase...")
print(re.test("\\d+", "Order #1234"))
m = re.match("(\\w+)", "hello WORLD", re.i())
if m != nil {
print(m["full"]) // hello
print(len(m["groups"]))
}
for x in re.find_all("[a-z]+", "One two THREE four", re.i()) {
print(x["full"]) // one two four
}
*/

81
examples/random_demo.fun Executable file
View file

@ -0,0 +1,81 @@
#!/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-11
*/
// Demonstrates usage of RANDOM_SEED and RANDOM_INT opcodes via
// the built-ins: random_seed(seed) and random_int(lo, hiExclusive).
print("-- Random demo: seed reproducibility and bounds --")
// Seed with a fixed value and produce a short sequence
seed = 123456
random_seed(seed)
a1 = random_int(0, 10) // in [0,10)
a2 = random_int(0, 10)
a3 = random_int(5, 8) // in [5,8)
print("First run:")
print(a1)
print(a2)
print(a3)
// Re-seed with the same value: the sequence should repeat
random_seed(seed)
b1 = random_int(0, 10)
b2 = random_int(0, 10)
b3 = random_int(5, 8)
print("Second run (after re-seed):")
print(b1)
print(b2)
print(b3)
print("Reproducible? (a1==b1, a2==b2, a3==b3)")
print(a1 == b1)
print(a2 == b2)
print(a3 == b3)
// Show that the upper bound is exclusive by sampling multiple times
// and tracking the maximum seen value; it should never reach hi.
lo = 10
hi = 20
max_seen = lo
i = 0
while i < 100
v = random_int(lo, hi)
if (v > max_seen) max_seen = v
i = i + 1
print("Max seen in [" + to_string(lo) + "," + to_string(hi) + ") over 100 samples:")
print(max_seen)
print("Is max_seen < hi? ")
print(max_seen < hi)
/* Expected output:
-- Random demo: seed reproducibility and bounds --
First run:
9
3
5
Second run (after re-seed):
9
3
5
Reproducible? (a1==b1, a2==b2, a3==b3)
true
true
true
Max seen in [10,20) over 100 samples:
19
Is max_seen < hi?
1
*/

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: 2025-12-12
*/
/*
* Example: Using OS-based random_number(len) builtin
* This generates cryptographically strong random bytes and returns them
* hex-encoded as a string of length 2*len (since each byte -> two hex chars).
*/
#include <hex.fun>
print("--- random_number(len) demo ---")
len_bytes = 16
hexstr = random_number(len_bytes)
print("Requested bytes: " + to_string(len_bytes))
print("Hex string: " + hexstr)
print("Hex bytes array: " + to_string(hex_to_bytes(hexstr)))
echo("Hex dump to bytes: ")
print(hex_to_bytes(hexstr))
print("Hex length (should be 2*bytes = 32): " + to_string(len(hexstr)))
// Zero length returns empty string
empty = random_number(0)
print("Empty (0 bytes) -> length: " + to_string(len(empty)) + " value: " + to_string(empty))
// You can request longer values as needed, e.g., 32 bytes -> 64 hex chars
hex64 = random_number(32)
print("32 bytes -> " + to_string(len(hex64)) + " hex chars")
/* Possible output:
--- random_number(len) demo ---
Requested bytes: 16
Hex string: 8497373c7fb52c6cb7f1e1fda5bb6a60
Hex bytes array: [array n=16]
Hex dump to bytes: [132, 151, 55, 60, 127, 181, 44, 108, 183, 241, 225, 253, 165, 187, 106, 96]
Hex length (should be 2*bytes = 32): 32
Empty (0 bytes) -> length: 0 value:
32 bytes -> 64 hex chars
*/

48
examples/sqlite_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-11-26
*/
// Prepare sample DB from SQL if needed (requires sqlite3 CLI installed)
// Create it once with:
// sqlite3 ./database.sqlite < ./examples/data/database.sql
string db_path = "./database.sqlite"
number h = sqlite_open(db_path)
if h == 0
print("Failed to open DB: " + db_path)
exit(1)
rows = sqlite_query(h, "SELECT id, title, done, created_at FROM tasks ORDER BY id;")
print("Tasks (" + to_string(len(rows)) + "):")
for row in rows
string status = ""
if row["done"] == 1
status = ""
print("- [" + status + "] (#" + to_string(row["id"]) + ") " + to_string(row["title"]) + " " + to_string(row["created_at"]))
number rc = sqlite_exec(h, "INSERT INTO tasks (title, done) VALUES ('Try Fun + SQLite', 0);")
print("Insert rc=" + to_string(rc))
rows2 = sqlite_query(h, "SELECT count(*) AS cnt FROM tasks;")
print("Total tasks now: " + to_string(rows2[0]["cnt"]))
sqlite_close(h)
/* Example output:
Tasks (3):
- [✔] (#1) Write Fun + SQLite example 2025-11-26 23:22:04
- [✘] (#2) Ship optional feature flag 2025-11-26 23:22:04
- [✘] (#3) Celebrate with coffee 2025-11-26 23:22:04
Insert rc=0
Total tasks now: 4
*/

29
examples/tk_hello.fun Executable file
View file

@ -0,0 +1,29 @@
#!/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-09
*/
// Demonstrates the Tk stdlib wrapper class using the new Tk opcodes.
include <ui/tk.fun>
tk = TK()
tk.title("Fun + Tk GUI")
tk.label("hello", "Hello, world!")
tk.pack("hello")
tk.button("ok", "OK")
tk.pack("ok")
// Enter GUI loop (no-op if built without FUN_WITH_TCLTK)
tk.loop()

97
examples/xml_access_catalog.fun Executable file
View file

@ -0,0 +1,97 @@
#!/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-09
*/
/*
* Demonstrate reading specific fields from an XML file using
* the minimal XML API (root name) and simple string parsing.
*/
include <io/xml.fun>
path = "./examples/data/catalog.xml"
xml = XML()
doc = xml.from_file(path)
if (doc == 0)
print("Failed to load: ")
print(path)
else
root = xml.root(doc)
print("Root element: ")
print(xml.name(root))
// Show how to access specific fields by quick-and-dirty parsing
content = read_file(path) // raw XML text
// First product name
name = xml.between(content, "<name>", "</name>")
// First price value and its currency attribute (extract value, then attribute)
price_val = xml.between(content, "<price", "</price>")
currency = ""
if (len(price_val) > 0)
currency = xml.between(price_val, "currency=\"", "\"")
// strip attribute tag part
price_text = xml.between(price_val, ">", "") // until end
if (len(price_text) == 0)
price_text = price_val
else
price_text = ""
print("First product name: ")
print(name)
print("First price: ")
if (len(currency) > 0)
print(currency)
print(" ")
print(price_text)
/* Expected output:
Root element:
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<product id="SKU-1001">
<name>Wireless Keyboard</name>
<category>Peripherals</category>
<price currency="USD">39.99</price>
<specs>
<layout>US</layout>
<connection>Bluetooth</connection>
<battery>AA</battery>
</specs>
</product>
<product id="SKU-2002">
<name>27" Monitor</name>
<category>Displays</category>
<price currency="USD">199.00</price>
<specs>
<resolution>2560x1440</resolution>
<panel>IPS</panel>
<refresh>75Hz</refresh>
</specs>
</product>
<product id="SKU-3003">
<name>USB-C Dock</name>
<category>Peripherals</category>
<price currency="USD">89.50</price>
<specs>
<ports>2xHDMI, 3xUSB-A, 1xUSB-C PD</ports>
<pd>65W</pd>
</specs>
</product>
</catalog>
First product name:
Wireless Keyboard
First price:
USD
39.99
*/

View file

@ -0,0 +1,81 @@
#!/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-09
*/
/*
* Access selected fields in employees.xml using minimal XML API.
*/
include <io/xml.fun>
path = "./examples/data/employees.xml"
xml = XML()
doc = xml.from_file(path)
if (doc == 0)
print("Failed to load: ")
print(path)
else
root = xml.root(doc)
print("Root element: ")
print(xml.name(root))
content = read_file(path)
// Find the first <employee> block and extract its fields
first_emp = xml.between(content, "<employee", "</employee>")
name = xml.between(first_emp, "<name>", "</name>")
role = xml.between(first_emp, "<role>", "</role>")
email = xml.between(first_emp, "<email>", "</email>")
emp_id = xml.between(first_emp, "id=\"", "\"")
print("First employee id: ")
print(emp_id)
print("Name: ")
print(name)
print("Role: ")
print(role)
print("Email: ")
print(email)
/* Expected output:
Root element:
<?xml version="1.0" encoding="UTF-8"?>
<company>
<department name="Engineering">
<employee id="E-100">
<name>Alice Doe</name>
<role>Senior Developer</role>
<email>alice@example.com</email>
</employee>
<employee id="E-101">
<name>Bob Roe</name>
<role>DevOps Engineer</role>
<email>bob@example.com</email>
</employee>
</department>
<department name="Sales">
<employee id="S-200">
<name>Carol Smith</name>
<role>Account Executive</role>
<email>carol@example.com</email>
</employee>
</department>
</company>
First employee id:
E-100
Name:
Alice Doe
Role:
Senior Developer
Email:
alice@example.com
*/

60
examples/xml_access_ns.fun Executable file
View file

@ -0,0 +1,60 @@
#!/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-09
*/
/*
* Access a namespaced XML file and show prefixed element names.
*/
include <io/xml.fun>
path = "./examples/data/ns_example.xml"
xml = XML()
doc = xml.from_file(path)
if (doc == 0)
print("Failed to load: ")
print(path)
else
root = xml.root(doc)
// With namespaces, the node name may include the prefix, e.g., "ns:library"
print("Root element: ")
print(xml.name(root))
content = read_file(path)
// Extract first book title and author (namespace prefix bk:)
b1 = xml.between(content, "<bk:book", "</bk:book>")
title = xml.between(b1, "<bk:title>", "</bk:title>")
author = xml.between(b1, "<bk:author>", "</bk:author>")
print("First book title: ")
print(title)
print("Author: ")
print(author)
/* Expected output:
Root element:
<?xml version="1.0" encoding="UTF-8"?>
<ns:library xmlns:ns="http://example.org/ns/library" xmlns:bk="http://example.org/ns/book">
<bk:book id="B-1">
<bk:title>The Art of Fun</bk:title>
<bk:author>J. Findeisen</bk:author>
</bk:book>
<bk:book id="B-2">
<bk:title>Minimal VM Design</bk:title>
<bk:author>A. Dev</bk:author>
</bk:book>
</ns:library>
First book title:
The Art of Fun
Author:
J. Findeisen
*/

110
examples/xml_class_example.fun Executable file
View file

@ -0,0 +1,110 @@
#!/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-09
*/
// Example using the stdlib XML class wrapper
include <io/xml.fun>
xml = XML()
doc = xml.from_file("./examples/data/example.xml")
print("doc handle:")
print(doc)
if (doc == 0)
print("Failed to load XML file")
else
root = xml.root(doc)
print("root name:")
print(xml.name(root))
print("root text:")
print(xml.text(root))
/* Expected output:
doc handle:
<?xml version="1.0" encoding="UTF-8"?>
<company name="Acme Corp">
<departments>
<department id="eng" name="Engineering">
<team name="Platform">
<member id="u1">Alice</member>
<member id="u2">Bob</member>
</team>
<team name="Product">
<member id="u3">Carol</member>
</team>
</department>
<department id="ops" name="Operations">
<team name="SRE">
<member id="u4">Dave</member>
</team>
</department>
</departments>
<offices>
<office city="Berlin" country="DE"/>
<office city="Paris" country="FR"/>
</offices>
<note>Welcome to Acme!</note>
</company>
root name:
<?xml version="1.0" encoding="UTF-8"?>
<company name="Acme Corp">
<departments>
<department id="eng" name="Engineering">
<team name="Platform">
<member id="u1">Alice</member>
<member id="u2">Bob</member>
</team>
<team name="Product">
<member id="u3">Carol</member>
</team>
</department>
<department id="ops" name="Operations">
<team name="SRE">
<member id="u4">Dave</member>
</team>
</department>
</departments>
<offices>
<office city="Berlin" country="DE"/>
<office city="Paris" country="FR"/>
</offices>
<note>Welcome to Acme!</note>
</company>
root text:
<?xml version="1.0" encoding="UTF-8"?>
<company name="Acme Corp">
<departments>
<department id="eng" name="Engineering">
<team name="Platform">
<member id="u1">Alice</member>
<member id="u2">Bob</member>
</team>
<team name="Product">
<member id="u3">Carol</member>
</team>
</department>
<department id="ops" name="Operations">
<team name="SRE">
<member id="u4">Dave</member>
</team>
</department>
</departments>
<offices>
<office city="Berlin" country="DE"/>
<office city="Paris" country="FR"/>
</offices>
<note>Welcome to Acme!</note>
</company>
*/

24
examples/xml_minimal.fun Executable file
View file

@ -0,0 +1,24 @@
#!/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-09
*/
// Minimal XML example using libxml2-backed builtins
doc = xml_parse("<root><item id=\"1\">a</item><item id=\"2\">b</item></root>")
print("doc handle=\(doc)")
root = xml_root(doc)
print("root name=\(xml_name(root)) text=\(xml_text(root))")
/* Expected output:
doc handle=(doc)
root name=(xml_name(root)) text=(xml_text(root))
*/

156
lib/crypt/crc32.fun Normal file
View file

@ -0,0 +1,156 @@
/*
* 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-11-26
*/
// lib/crypt/crc32.fun
// Pure Fun implementation of CRC-32 (IEEE 802.3) operating on hex-string input.
// Reflected polynomial: 0xEDB88320
// Initial value: 0xFFFFFFFF, Final XOR: 0xFFFFFFFF
//
// Public API (class methods):
// crc32_hex(hexStr) -> 8-char lowercase hex string
// crc32_str(str) -> 8-char lowercase string (ASCII input)
//
// Example:
// // "123456789" CRC32 is cbf43926
// c = CRC32()
// print(c.crc32_str("123456789"))
#include <strings.fun>
class CRC32()
// 32-bit helpers
fun u32(this, x)
m = 4294967296
while x < 0
x = x + m
while x >= m
x = x - m
return x
fun shr32(this, x, s)
return shr(this.u32(x), s)
fun shl32(this, x, s)
return shl(this.u32(x), s)
fun xor32(this, a, b)
return bxor(this.u32(a), this.u32(b))
fun and32(this, a, b)
return band(this.u32(a), this.u32(b))
// hex helpers (mirroring style from lib/crypt/md5.fun)
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, "")
fun u32_to_hex8(this, n)
// big-endian printing (MSB first), common for CRC displays
b3 = this.and32(this.shr32(n, 24), 255)
b2 = this.and32(this.shr32(n, 16), 255)
b1 = this.and32(this.shr32(n, 8), 255)
b0 = this.and32(n, 255)
return this.bytes_to_hex([b3, b2, b1, b0])
// Bitwise update (no table needed) using reflected polynomial 0xEDB88320
POLY = 3988292384 // 0xEDB88320
// Compute CRC32 over byte array, return u32 value (bitwise, reflected)
fun crc32_bytes_value(this, bytes)
crc = 4294967295 // 0xFFFFFFFF
i = 0
n = len(bytes)
while i < n
crc = this.xor32(crc, bytes[i])
j = 0
while j < 8
if (this.and32(crc, 1) == 1)
crc = this.xor32(this.shr32(crc, 1), this.POLY)
else
crc = this.shr32(crc, 1)
j = j + 1
i = i + 1
return this.xor32(crc, 4294967295) // final XOR
// Public: compute CRC32 of hex string of bytes, return 8-char hex
fun crc32_hex(this, hexStr)
bytes = this.from_hex(hexStr)
v = this.crc32_bytes_value(bytes)
return this.u32_to_hex8(v)
// Convenience: compute CRC32 of ASCII string
fun crc32_str(this, str)
bytes = string_to_bytes_ascii(str)
v = this.crc32_bytes_value(bytes)
return this.u32_to_hex8(v)

156
lib/crypt/crc32c.fun Normal file
View file

@ -0,0 +1,156 @@
/*
* 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-11-26
*/
// lib/crypt/crc32c.fun
// Pure Fun implementation of CRC-32C (Castagnoli) operating on hex-string input.
// Polynomial (reflected): 0x82F63B78
// Initial value: 0xFFFFFFFF, Final XOR: 0xFFFFFFFF
//
// Public API (class methods):
// crc32c_hex(hexStr) -> 8-char lowercase hex string
// crc32c_str(str) -> 8-char lowercase string (ASCII input)
//
// Example:
// // "123456789" in ASCII is 313233343536373839 in hex, CRC32C is e3069283
// c = CRC32C()
// print(c.crc32c_hex("313233343536373839"))
#include <strings.fun>
class CRC32C()
// 32-bit helpers
fun u32(this, x)
m = 4294967296
while x < 0
x = x + m
while x >= m
x = x - m
return x
fun shr32(this, x, s)
return shr(this.u32(x), s)
fun shl32(this, x, s)
return shl(this.u32(x), s)
fun xor32(this, a, b)
return bxor(this.u32(a), this.u32(b))
fun and32(this, a, b)
return band(this.u32(a), this.u32(b))
// hex helpers (mirroring style from lib/crypt/md5.fun)
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, "")
fun u32_to_hex8(this, n)
// big-endian printing (MSB first), common for CRC displays
b3 = this.and32(this.shr32(n, 24), 255)
b2 = this.and32(this.shr32(n, 16), 255)
b1 = this.and32(this.shr32(n, 8), 255)
b0 = this.and32(n, 255)
return this.bytes_to_hex([b3, b2, b1, b0])
// Bitwise update (no table needed) using reflected polynomial 0x82F63B78
POLY = 2197175160 // 0x82F63B78
// Compute CRC32C over byte array, return u32 value (bitwise, reflected)
fun crc32c_bytes_value(this, bytes)
crc = 4294967295 // 0xFFFFFFFF
i = 0
n = len(bytes)
while i < n
crc = this.xor32(crc, bytes[i])
j = 0
while j < 8
if (this.and32(crc, 1) == 1)
crc = this.xor32(this.shr32(crc, 1), this.POLY)
else
crc = this.shr32(crc, 1)
j = j + 1
i = i + 1
return this.xor32(crc, 4294967295) // final XOR
// Public: compute CRC32C of hex string of bytes, return 8-char hex
fun crc32c_hex(this, hexStr)
bytes = this.from_hex(hexStr)
v = this.crc32c_bytes_value(bytes)
return this.u32_to_hex8(v)
// Convenience: compute CRC32C of ASCII string
fun crc32c_str(this, str)
bytes = string_to_bytes_ascii(str)
v = this.crc32c_bytes_value(bytes)
return this.u32_to_hex8(v)

96
lib/io/ini.fun Normal file
View file

@ -0,0 +1,96 @@
/*
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2025 Johannes Findeisen
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2025-11-30
*/
// INI stdlib abstraction wrapping the ini_* VM builtins (iniparser 4.2.6).
//
// Usage:
// include <io/ini.fun>
// ini = INI()
// if (ini.load("./examples/data/complex.ini") > 0)
// name = ini.get_string("app", "name", "")
// retries = ini.get_int("network", "retries", 0)
// ini.set("app", "debug", 1)
// ini.save(nil) // save back to original path
// ini.close()
class INI()
// current handle (>0 when open) and path string
h = 0
path = ""
// 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)
ini_free(this.h)
this.h = 0
p = to_string(path)
this.path = p
this.h = ini_load(p)
return this.h
// True if a dictionary is open.
fun is_open(this)
return this.h > 0
// Close and free resources. Safe to call multiple times.
fun close(this)
if (this.h > 0)
ini_free(this.h)
this.h = 0
return 1
// Getters with defaults. When not open, return the default converted.
fun get_string(this, section, key, def)
if (!this.is_open())
return to_string(def)
return ini_get_string(this.h, to_string(section), to_string(key), to_string(def))
fun get_int(this, section, key, def)
if (!this.is_open())
return to_number(def)
return ini_get_int(this.h, to_string(section), to_string(key), to_number(def))
fun get_double(this, section, key, def)
if (!this.is_open())
return to_number(def)
return ini_get_double(this.h, to_string(section), to_string(key), to_number(def))
fun get_bool(this, section, key, def)
if (!this.is_open())
if (def == nil)
return 0
// treat 0/1 and boolean-like strings
return to_number(def) != 0
return ini_get_bool(this.h, to_string(section), to_string(key), to_number(def))
// Set/unset return 1 on success, 0 on failure.
fun set(this, section, key, value)
if (!this.is_open())
return 0
return ini_set(this.h, to_string(section), to_string(key), to_string(value))
fun unset(this, section, key)
if (!this.is_open())
return 0
return ini_unset(this.h, to_string(section), to_string(key))
// Save to a path. If path is nil, save to the last loaded path.
fun save(this, path)
if (!this.is_open())
return 0
p = path
if (p == nil)
p = this.path
p = to_string(p)
if (len(p) == 0)
return 0
return ini_save(this.h, p)

32
lib/io/json.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 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-11-24
*/
// JSON stdlib abstraction wrapping VM json_* builtins defensively.
class JSON()
fun parse(this, text)
v = json_parse(to_string(text))
return v
fun stringify(this, value, pretty)
if pretty == nil
pretty = 0
return json_stringify(value, pretty)
fun from_file(this, path)
return json_from_file(to_string(path))
fun to_file(this, path, value, pretty)
if pretty == nil
pretty = 0
return json_to_file(to_string(path), value, pretty)

View file

@ -79,6 +79,7 @@ class PCSC()
return m
*/
// This class is in a very early stage of development.
class PCSC()
fun get_readers(this)
ctx = pcsc_establish()
@ -109,7 +110,7 @@ class PCSC()
m["sw2"] = -1
m["code"] = -2
return m
// Actually selecting the second reader hardcode. This class is not in a very early stage of development.
// Actually selecting the second reader hardcoded.
handle = pcsc_connect(ctx, readers[1])
apdu = this.hex_to_bytes(hex_apdu)
res = pcsc_transmit(handle, apdu)

57
lib/io/xml.fun Normal file
View file

@ -0,0 +1,57 @@
/*
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2025 Johannes Findeisen
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2025-12-09
*/
// XML stdlib abstraction wrapping the xml_* VM builtins (libxml2-backed).
// Minimal API for now: parse, from_file, root, name, text
class XML()
// Parse XML text into a document handle (>0) or 0 on error.
fun parse(this, text)
t = to_string(text)
return xml_parse(t)
// Load XML from a file path; returns document handle (>0) or 0.
fun from_file(this, path)
p = to_string(path)
data = read_file(p)
if (len(data) == 0)
return 0
return xml_parse(data)
// Get root node handle (>0) or 0.
fun root(this, doc)
return xml_root(doc)
// Get node name as string.
fun name(this, node)
return xml_name(node)
// Get node concatenated text content as string.
fun text(this, node)
return xml_text(node)
// Utility: return substring of s between delimiters a and b.
// - If a is not found, returns "".
// - If b is an empty string, returns everything after the first occurrence of a.
// - If b is not found after a, returns "".
fun between(this, s, a, b)
i = find(s, a)
if (i < 0)
return ""
i = i + len(a)
rest = substr(s, i, len(s) - i)
if (len(b) == 0)
return rest
j = find(rest, b)
if (j < 0)
return ""
return substr(rest, 0, j)

42
lib/regex/pcre2.fun Normal file
View file

@ -0,0 +1,42 @@
#!/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-11-25
*/
// PCRE2 stdlib abstraction wrapping VM pcre2_* builtins.
// Provides a small class with flags and user-friendly methods.
class PCRE2()
fun i(this)
return 1
fun m(this)
return 2
fun s(this)
return 4
fun u(this)
return 8
fun x(this)
return 16
fun test(this, pattern, text, flags)
if flags == nil
flags = this.u()
return pcre2_test(to_string(pattern), to_string(text), flags)
fun match(this, pattern, text, flags)
if flags == nil
flags = this.u()
return pcre2_match(to_string(pattern), to_string(text), flags)
fun find_all(this, pattern, text, flags)
if flags == nil
flags = this.u()
return pcre2_findall(to_string(pattern), to_string(text), flags)

36
lib/ui/tk.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 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-09
*/
// Tk stdlib helper wrapping the Tk VM builtins.
// No raw Tcl is exposed; this class provides a tiny, safe GUI surface.
class TK()
// Set the window title
fun title(this, title)
return tk_title(to_string(title))
// Create or update a label widget with id and text
fun label(this, id, text)
return tk_label(to_string(id), to_string(text))
// Create or update a button widget with id and text
fun button(this, id, text)
return tk_button(to_string(id), to_string(text))
// Pack a widget by id
fun pack(this, id)
return tk_pack(to_string(id))
// Enter the Tk event loop (blocks until windows are closed)
fun loop(this)
return tk_loop()

View file

@ -1,3 +1,12 @@
/*
* 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
*/
// Date/time utilities abstraction for the Fun stdlib.
// Minimal version to validate syntax.
@ -30,3 +39,66 @@ class DateTime()
fun iso_now(this)
ms = time_now_ms()
return date_format(ms, "%Y-%m-%dT%H:%M:%S")
// Seconds since Unix epoch (integer)
fun now_s(this)
return to_number(time_now_ms() / 1000)
// Convert milliseconds to seconds (floor)
fun ms_to_s(this, ms)
return to_number(to_number(ms) / 1000)
// Convert seconds to milliseconds
fun s_to_ms(this, s)
return to_number(to_number(s) * 1000)
// Add milliseconds to an epoch-ms timestamp
fun add_ms(this, ms, delta_ms)
return to_number(to_number(ms) + to_number(delta_ms))
// Add seconds to an epoch-ms timestamp
fun add_seconds(this, ms, seconds)
return this.add_ms(ms, this.s_to_ms(seconds))
// Difference in milliseconds: b - a
fun diff_ms(this, a_ms, b_ms)
return to_number(to_number(b_ms) - to_number(a_ms))
// Milliseconds elapsed since given epoch-ms timestamp
fun since_ms(this, past_ms)
return this.diff_ms(past_ms, this.now_ms())
// Format an arbitrary epoch-ms timestamp as ISO local
fun iso_from(this, ms)
return date_format(to_number(ms), "%Y-%m-%dT%H:%M:%S")
// Date-only string for a timestamp (YYYY-MM-DD)
fun date_str(this, ms)
return date_format(to_number(ms), "%Y-%m-%d")
// Time-only string for a timestamp (HH:MM:SS)
fun time_str(this, ms)
return date_format(to_number(ms), "%H:%M:%S")
// Today's date as YYYY-MM-DD
fun today_str(this)
return this.date_str(this.now_ms())
// Start a monotonic timer
fun start_timer(this)
return clock_mono_ms()
// Elapsed ms from a monotonic start value
fun elapsed_ms(this, start_mono_ms)
return to_number(clock_mono_ms() - to_number(start_mono_ms))
// Sleep for the given milliseconds (non-negative)
fun sleep_ms(this, ms)
m = to_number(ms)
if m > 0
sleep(m)
return m
// Sleep for the given seconds
fun sleep_s(this, s)
return this.sleep_ms(this.s_to_ms(s))

165
make Executable file
View file

@ -0,0 +1,165 @@
#!/bin/bash
# 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-12
if [ -z "$1" ]; then
echo "Build target is unset, using 'minimal'";
target="minimal";
else
echo "Build target is set to '$1'";
target=$1;
fi
if [ "$target" = "all" ]; then
rm -rf build \
&& cmake -S . -B build \
-DFUN_WITH_PCSC=ON \
-DFUN_WITH_REPL=ON \
-DFUN_WITH_LIBSQL=ON \
-DFUN_WITH_SQLITE=ON \
-DFUN_WITH_CURL=ON \
-DFUN_WITH_PCRE2=ON \
-DFUN_WITH_XML2=ON \
-DFUN_WITH_JSON=ON \
-DFUN_WITH_TCLTK=ON \
-DFUN_WITH_INI=ON \
-DFUN_LINK_STATIC=OFF \
-DFUN_USE_MUSL=OFF \
-DFUN_DEBUG=OFF \
&& cmake --build build --target fun
elif [ "$target" = "all_debug" ]; then
rm -rf build \
&& cmake -S . -B build \
-DFUN_WITH_PCSC=ON \
-DFUN_WITH_REPL=ON \
-DFUN_WITH_LIBSQL=ON \
-DFUN_WITH_SQLITE=ON \
-DFUN_WITH_CURL=ON \
-DFUN_WITH_PCRE2=ON \
-DFUN_WITH_XML2=ON \
-DFUN_WITH_JSON=ON \
-DFUN_WITH_TCLTK=ON \
-DFUN_WITH_INI=ON \
-DFUN_LINK_STATIC=OFF \
-DFUN_USE_MUSL=OFF \
-DFUN_DEBUG=ON \
&& cmake --build build --target fun
elif [ "$target" = "alpine" ]; then
rm -rf build \
&& cmake -S . -B build \
-DFUN_WITH_PCSC=OFF \
-DFUN_WITH_REPL=ON \
-DFUN_WITH_LIBSQL=ON \
-DFUN_WITH_SQLITE=ON \
-DFUN_WITH_CURL=ON \
-DFUN_WITH_PCRE2=ON \
-DFUN_WITH_XML2=ON \
-DFUN_WITH_JSON=ON \
-DFUN_WITH_TCLTK=OFF \
-DFUN_WITH_INI=ON \
-DFUN_LINK_STATIC=OFF \
-DFUN_DEBUG=OFF \
&& cmake --build build --target fun
elif [ "$target" = "debug" ]; then
rm -rf build \
&& cmake -S . -B build \
-DFUN_WITH_PCSC=OFF \
-DFUN_WITH_REPL=ON \
-DFUN_WITH_LIBSQL=OFF \
-DFUN_WITH_SQLITE=OFF \
-DFUN_WITH_CURL=OFF \
-DFUN_WITH_PCRE2=OFF \
-DFUN_WITH_XML2=OFF \
-DFUN_WITH_JSON=OFF \
-DFUN_WITH_TCLTK=OFF \
-DFUN_WITH_INI=OFF \
-DFUN_LINK_STATIC=OFF \
-DFUN_USE_MUSL=OFF \
-DFUN_DEBUG=ON \
&& cmake --build build --target fun
elif [ "$target" = "freebsd" ]; then
rm -rf build \
&& cmake -S . -B build \
-DFUN_WITH_PCSC=OFF \
-DFUN_WITH_REPL=ON \
-DFUN_WITH_LIBSQL=OFF \
-DFUN_WITH_SQLITE=OFF \
-DFUN_WITH_CURL=OFF \
-DFUN_WITH_PCRE2=OFF \
-DFUN_WITH_XML2=OFF \
-DFUN_WITH_JSON=OFF \
-DFUN_WITH_TCLTK=OFF \
-DFUN_WITH_INI=OFF \
-DFUN_LINK_STATIC=OFF \
-DFUN_DEBUG=OFF \
&& cmake --build build --target fun
elif [ "$target" = "minimal" ]; then
rm -rf build \
&& cmake -S . -B build \
-DFUN_WITH_PCSC=OFF \
-DFUN_WITH_REPL=OFF \
-DFUN_WITH_LIBSQL=OFF \
-DFUN_WITH_SQLITE=OFF \
-DFUN_WITH_CURL=OFF \
-DFUN_WITH_PCRE2=OFF \
-DFUN_WITH_XML2=OFF \
-DFUN_WITH_JSON=OFF \
-DFUN_WITH_TCLTK=OFF \
-DFUN_WITH_INI=OFF \
-DFUN_LINK_STATIC=OFF \
-DFUN_USE_MUSL=OFF \
-DFUN_DEBUG=OFF \
&& cmake --build build --target fun
elif [ "$target" = "musl" ]; then
rm -rf build \
&& cmake -S . -B build \
-DFUN_WITH_PCSC=OFF \
-DFUN_WITH_REPL=ON \
-DFUN_WITH_LIBSQL=OFF \
-DFUN_WITH_SQLITE=OFF \
-DFUN_WITH_CURL=OFF \
-DFUN_WITH_PCRE2=OFF \
-DFUN_WITH_XML2=OFF \
-DFUN_WITH_JSON=OFF \
-DFUN_WITH_TCLTK=OFF \
-DFUN_WITH_INI=OFF \
-DFUN_LINK_STATIC=OFF \
-DFUN_USE_MUSL=ON \
-DFUN_DEBUG=OFF \
&& cmake --build build --target fun
elif [ "$target" = "repl" ]; then
rm -rf build \
&& cmake -S . -B build \
-DFUN_WITH_PCSC=OFF \
-DFUN_WITH_REPL=ON \
-DFUN_WITH_LIBSQL=OFF \
-DFUN_WITH_SQLITE=OFF \
-DFUN_WITH_CURL=OFF \
-DFUN_WITH_PCRE2=OFF \
-DFUN_WITH_XML2=OFF \
-DFUN_WITH_JSON=OFF \
-DFUN_WITH_TCLTK=OFF \
-DFUN_WITH_INI=OFF \
-DFUN_LINK_STATIC=OFF \
-DFUN_USE_MUSL=OFF \
-DFUN_DEBUG=OFF \
&& cmake --build build --target fun
else
echo "Build target $target not found... aborting!";
echo "Available targets:";
echo " - all";
echo " - all_debug";
echo " - alpine";
echo " - debug";
echo " - freebsd";
echo " - minimal";
echo " - musl";
echo " - repl";
fi

199
play.fun
View file

@ -1,74 +1,153 @@
#!/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
* Interactive demo runner for all examples in ./examples
* - Asks y/n before running each feature demo
* - Executes each example as a subprocess
*/
include <strings.fun>
#include <io/console.fun>
fun foo()
print("Have fun!")
print("Having fun... forever.")
fun pick_fun_bin()
// Prefer an explicit FUN_BIN override; otherwise rely on PATH
b = env("FUN_BIN")
if b != ""
return b
return "fun"
print("Typeof foo(): " + typeof(foo))
fun run_example(bin, path)
// Ensure examples can locate stdlib when run from repo root.
// We execute via the shell so env assignment + redirection works.
cmd = join(["sh -c '\nFUN_LIB_DIR=./lib ", bin, " ", path, " 2>&1\n'"], "")
print("-- output begin --")
code = system(cmd)
print("-- output end --")
print(join(["exit code: ", to_string(code)], ""))
return code
print("Yay, the playground for having fun... ;)")
fun main()
c = Console()
bin = pick_fun_bin()
print(string_to_bytes_ascii("Have Fun!"))
print("=== Fun language feature showcase (interactive) ===")
print(join(["Using interpreter: ", bin], ""))
print("Tip: set FUN_BIN=/path/to/fun to override. Stdlib is passed via FUN_LIB_DIR=./lib\n")
number n = 23
// Every type MUST be lowercase. Sint* must be sint*.
print(n)
// List of example scripts. Keep paths relative to repo root where this demo resides.
// If you add/remove examples, update this list.
files = [
"examples/arrays.fun",
"examples/arrays_advanced.fun",
"examples/arrays_iter.fun",
"examples/boolean_decl.fun",
"examples/booleans.fun",
"examples/builtins_conversions.fun",
"examples/builtins_extended.fun",
"examples/builtins_maps_and_more.fun",
"examples/byte_for_demo.fun",
"examples/byte_overflow_try_catch.fun",
"examples/cast_demo.fun",
"examples/class_constructor.fun",
"examples/classes_demo.fun",
"examples/crc32_example.fun",
"examples/crc32c_example.fun",
"examples/curl_download.fun",
"examples/curl_get_json.fun",
"examples/curl_post.fun",
"examples/datetime_basic.fun",
"examples/datetime_extended.fun",
"examples/datetime_timer.fun",
"examples/debug_reporting.fun",
"examples/echo_example.fun",
"examples/exit_example.fun",
"examples/expressions_test.fun",
"examples/fail.fun",
"examples/file_io.fun",
"examples/file_print_for_file_line_by_line.fun",
"examples/floats.fun",
"examples/for_range_test.fun",
"examples/functions_test.fun",
"examples/have_fun.fun",
"examples/have_fun_function.fun",
"examples/if_else_test.fun",
"examples/include_lib.fun",
"examples/include_local.fun",
"examples/include_local_util.fun",
"examples/include_namespace.fun",
"examples/inheritance_demo.fun",
"examples/ini_class_demo.fun",
"examples/ini_complex.fun",
"examples/ini_demo.fun",
"examples/ini_subsections.fun",
"examples/input_example.fun",
"examples/json_showcase.fun",
"examples/libsql_example.fun",
"examples/loops_break_continue.fun",
"examples/md5_demo.fun",
"examples/namespaced_mod.fun",
"examples/nested_loops.fun",
"examples/objects_basic.fun",
"examples/objects_more.fun",
"examples/os_env.fun",
"examples/pcre2_opcodes.fun",
"examples/pcre2_showcase.fun",
"examples/pcsc_example.fun",
"examples/process_example.fun",
"examples/regex_demo.fun",
"examples/regex_procedural.fun",
"examples/repl_on_error.fun",
"examples/sha1_demo.fun",
"examples/sha256_demo.fun",
"examples/sha256_str_demo.fun",
"examples/sha384_example.fun",
"examples/sha512_demo.fun",
"examples/sha512_str_demo.fun",
"examples/short_circuit_test.fun",
"examples/signed_ints.fun",
"examples/sqlite_example.fun",
"examples/stdlib_showcase.fun",
"examples/strings_test.fun",
"examples/tcp_http_get.fun",
"examples/tcp_http_get_class.fun",
"examples/thread_class_example.fun",
"examples/threads_demo.fun",
"examples/tk_hello.fun",
"examples/try_catch_finally.fun",
"examples/try_catch_with_error.fun",
"examples/typeof.fun",
"examples/typeof_features.fun",
"examples/type_safety.fun",
"examples/type_safety_fails.fun",
"examples/types_integers.fun",
"examples/types_overview.fun",
"examples/uint_types.fun",
"examples/unix_socket_echo.fun",
"examples/while_test.fun",
"examples/xml_access_catalog.fun",
"examples/xml_access_employees.fun",
"examples/xml_access_ns.fun",
"examples/xml_class_example.fun",
"examples/xml_minimal.fun"
]
// This MUST fail because n is of type number and can not become a string or function. Setting it to 0 is not an option.
n = "FooBar"
print(n)
print("Typeof n: " + typeof(n))
failures = []
n = 100
print(n)
print("Typeof n: " + typeof(n))
for f in files
q = join(["Run ", f, "?"], "")
if c.ask_yes_no(q)
print(join(["=== Running: ", f, " ==="], ""))
code = run_example(bin, f)
if code != 0
failures.push(f)
print("")
else
print(join(["Skipped: ", f], ""))
fun n(num)
print("n(" + to_string(num) + ")")
n(42)
// Typeof n MUST be of type Function here... Not Sint64.
print("Typeof n: " + typeof(n))
if len(failures) == 0
print("All selected examples completed successfully.")
else
print("Some selected examples failed:")
for ff in failures
print(join([" - ", ff], ""))
n = 2342
print(n)
print("Typeof n: " + typeof(n))
x = 42
print (x)
x = "BarFoo"
print(x)
// Arrays must be declared with an "array" identifier if not beeing dynamicly typed. We need the "array" type."
a = [23, 42]
print(a)
print(a[0])
// Why this is possible? Setting n to a string, sets n to 0. Setting an array to 1 works...? This must fail when an a is of type "array", not in this case!
a = 1
print(a)
string s = 'Have\n"fun!"'
print(s)
print("\'" + " Fun")
print('\'' + " \'Fun\'")
print('\'' + " \"Fun\"")
print('\'' + " \n\"Fun\"")
print('\'' + " \n\t\"Fun\"")
print("Running foo() 2 times...")
foo()
// We need arguments to functions...
foo()
main()

View file

@ -50,6 +50,14 @@ if [[ ! -x "$BIN" ]]; then
exit 2
fi
# Ensure stdlib is discoverable for examples unless user already set it
if [[ -z "${FUN_LIB_DIR:-}" ]]; then
export FUN_LIB_DIR="$ROOT/lib"
fi
# Ensure error bucket exists
mkdir -p "$EX_DIR/error"
shopt -s nullglob
files=("$EX_DIR"/*.fun)
shopt -u nullglob
@ -64,6 +72,14 @@ for f in "${files[@]}"; do
echo "=== Running: ${f#$ROOT/} ==="
if ! "$BIN" "$f"; then
echo "FAILED: ${f#$ROOT/}"
base="$(basename "$f")"
dest="$EX_DIR/error/$base"
# Move the failing example to the error folder
if mv -f "$f" "$dest"; then
echo "Moved to: examples/error/$base"
else
echo "warning: failed to move $base to examples/error/" >&2
fi
rc=1
fi
done

View file

@ -77,6 +77,7 @@ static const char *opcode_name(OpCode op) {
case OP_CALL: return "CALL";
case OP_RETURN: return "RETURN";
case OP_PRINT: return "PRINT";
case OP_ECHO: return "ECHO";
case OP_HALT: return "HALT";
case OP_MOD: return "MOD";
case OP_AND: return "AND";
@ -133,6 +134,7 @@ static const char *opcode_name(OpCode op) {
case OP_THREAD_SPAWN: return "THREAD_SPAWN";
case OP_THREAD_JOIN: return "THREAD_JOIN";
case OP_SLEEP_MS: return "SLEEP_MS";
case OP_RANDOM_NUMBER: return "RANDOM_NUMBER";
case OP_BAND: return "BAND";
case OP_BOR: return "BOR";
case OP_BXOR: return "BXOR";
@ -141,12 +143,50 @@ static const char *opcode_name(OpCode op) {
case OP_SHR: return "SHR";
case OP_ROTL: return "ROTL";
case OP_ROTR: return "ROTR";
case OP_JSON_PARSE: return "JSON_PARSE";
case OP_JSON_STRINGIFY: return "JSON_STRINGIFY";
case OP_JSON_FROM_FILE: return "JSON_FROM_FILE";
case OP_JSON_TO_FILE: return "JSON_TO_FILE";
case OP_CURL_GET: return "CURL_GET";
case OP_CURL_POST: return "CURL_POST";
case OP_CURL_DOWNLOAD: return "CURL_DOWNLOAD";
case OP_SQLITE_OPEN: return "SQLITE_OPEN";
case OP_SQLITE_CLOSE: return "SQLITE_CLOSE";
case OP_SQLITE_EXEC: return "SQLITE_EXEC";
case OP_SQLITE_QUERY: return "SQLITE_QUERY";
case OP_LIBSQL_OPEN: return "LIBSQL_OPEN";
case OP_LIBSQL_CLOSE: return "LIBSQL_CLOSE";
case OP_LIBSQL_EXEC: return "LIBSQL_EXEC";
case OP_LIBSQL_QUERY: return "LIBSQL_QUERY";
case OP_PCSC_ESTABLISH: return "PCSC_ESTABLISH";
case OP_PCSC_RELEASE: return "PCSC_RELEASE";
case OP_PCSC_LIST_READERS: return "PCSC_LIST_READERS";
case OP_PCSC_CONNECT: return "PCSC_CONNECT";
case OP_PCSC_DISCONNECT: return "PCSC_DISCONNECT";
case OP_PCSC_TRANSMIT: return "PCSC_TRANSMIT";
case OP_PCRE2_TEST: return "PCRE2_TEST";
case OP_PCRE2_MATCH: return "PCRE2_MATCH";
case OP_PCRE2_FINDALL: return "PCRE2_FINDALL";
case OP_INI_LOAD: return "INI_LOAD";
case OP_INI_FREE: return "INI_FREE";
case OP_INI_GET_STRING: return "INI_GET_STRING";
case OP_INI_GET_INT: return "INI_GET_INT";
case OP_INI_GET_DOUBLE: return "INI_GET_DOUBLE";
case OP_INI_GET_BOOL: return "INI_GET_BOOL";
case OP_INI_SET: return "INI_SET";
case OP_INI_UNSET: return "INI_UNSET";
case OP_INI_SAVE: return "INI_SAVE";
case OP_XML_PARSE: return "XML_PARSE";
case OP_XML_ROOT: return "XML_ROOT";
case OP_XML_NAME: return "XML_NAME";
case OP_XML_TEXT: return "XML_TEXT";
case OP_TK_EVAL: return "TK_EVAL";
case OP_TK_RESULT: return "TK_RESULT";
case OP_TK_LOOP: return "TK_LOOP";
case OP_TK_WM_TITLE: return "TK_WM_TITLE";
case OP_TK_LABEL: return "TK_LABEL";
case OP_TK_BUTTON: return "TK_BUTTON";
case OP_TK_PACK: return "TK_PACK";
default: return "???";
}
}

View file

@ -43,6 +43,7 @@ typedef enum {
OP_RETURN, // pop optional return value and return to caller
OP_PRINT,
OP_ECHO, // like print but does not append a newline; prints immediately
OP_HALT,
OP_LINE, // operand = source line number (debug marker)
@ -130,6 +131,7 @@ typedef enum {
OP_THREAD_SPAWN, // operand: 0=no args, 1=has args; pops [args?], fn; pushes thread id (int>0)
OP_THREAD_JOIN, // pops thread id; waits; pushes result value (or Nil)
OP_SLEEP_MS, // pops milliseconds; sleeps; pushes Nil (for statement POP safety)
OP_RANDOM_NUMBER, // pops length; pushes hex string of that length from OS RNG (hex-encoded)
// Bitwise (32-bit) and shifts/rotates
OP_BAND, // pops b, a; pushes (uint32_t)(a & b)
@ -141,6 +143,29 @@ typedef enum {
OP_ROTL, // pops s, a; pushes rotl32(a, s)
OP_ROTR, // pops s, a; pushes rotr32(a, s)
// JSON (json-c)
OP_JSON_PARSE, // pops text string; pushes value (or Nil on error)
OP_JSON_STRINGIFY, // pops pretty(bool), value; pushes string
OP_JSON_FROM_FILE, // pops path string; pushes value (or Nil)
OP_JSON_TO_FILE, // pops pretty(bool), value, path; pushes 1/0
// CURL (libcurl)
OP_CURL_GET, // pops [headers map?], url; pushes response string (or "")
OP_CURL_POST, // pops [headers map?], body string, url; pushes response string (or "")
OP_CURL_DOWNLOAD, // pops [headers map?], path, url; pushes 1/0
// SQLite (optional)
OP_SQLITE_OPEN, // pops path; pushes handle (>0) or 0
OP_SQLITE_CLOSE, // pops handle; pushes Nil
OP_SQLITE_EXEC, // pops sql, handle; pushes sqlite rc (0=OK)
OP_SQLITE_QUERY, // pops sql, handle; pushes array<map>
// libsql (optional, independent)
OP_LIBSQL_OPEN, // pops url/path; pushes handle (>0) or 0
OP_LIBSQL_CLOSE, // pops handle; pushes Nil
OP_LIBSQL_EXEC, // pops sql, handle; pushes rc (0=OK)
OP_LIBSQL_QUERY, // pops sql, handle; pushes array<map>
// PCSC (smart card) opcodes
OP_PCSC_ESTABLISH, // returns context id (>0) or 0
OP_PCSC_RELEASE, // pops ctx id; returns 1/0
@ -149,6 +174,37 @@ typedef enum {
OP_PCSC_DISCONNECT, // pops handle id; returns 1/0
OP_PCSC_TRANSMIT, // pops apdu array, handle id; returns map {"data":[],"sw1":n,"sw2":n,"code":n}
// PCRE2 regex ops (optional)
OP_PCRE2_TEST, // pops flags, text, pattern; pushes 1/0
OP_PCRE2_MATCH, // pops flags, text, pattern; pushes match map or Nil
OP_PCRE2_FINDALL, // pops flags, text, pattern; pushes array of match maps
// INI (iniparser 4.2.6) optional
OP_INI_LOAD, // pops path; pushes handle (>0) or 0
OP_INI_FREE, // pops handle; pushes 1/0
OP_INI_GET_STRING, // pops def, key, section, handle; pushes string
OP_INI_GET_INT, // pops def, key, section, handle; pushes int
OP_INI_GET_DOUBLE, // pops def, key, section, handle; pushes float
OP_INI_GET_BOOL, // pops def, key, section, handle; pushes int (0/1)
OP_INI_SET, // pops value, key, section, handle; pushes 1/0
OP_INI_UNSET, // pops key, section, handle; pushes 1/0
OP_INI_SAVE, // pops path, handle; pushes 1/0
// XML (libxml2) optional minimal API
OP_XML_PARSE, // pops text string; pushes doc handle (>0) or 0
OP_XML_ROOT, // pops doc handle; pushes node handle (>0) or 0
OP_XML_NAME, // pops node handle; pushes string (node name)
OP_XML_TEXT, // pops node handle; pushes string (node text)
// Tk (Tcl/Tk) optional minimal API
OP_TK_EVAL, // pops script string; pushes int rc (0 = OK)
OP_TK_RESULT, // pushes string: last Tcl result
OP_TK_LOOP, // enters Tk event loop; pushes Nil when done
OP_TK_WM_TITLE, // pops title string; sets window title; pushes rc
OP_TK_LABEL, // pops text, id; creates/updates label .id; pushes rc
OP_TK_BUTTON, // pops text, id; creates/updates button .id; pushes rc
OP_TK_PACK, // pops id; packs .id; pushes rc
// Sockets (UNIX platforms)
OP_SOCK_TCP_LISTEN, // pops backlog, port; returns listen fd (>0) or 0
OP_SOCK_TCP_ACCEPT, // pops listen fd; returns client fd (>0) or 0
@ -160,7 +216,12 @@ typedef enum {
OP_SOCK_UNIX_CONNECT, // pops path; returns fd (>0) or 0
// process control
OP_EXIT // pops code (or uses operand) and terminates script with exit code
OP_EXIT, // pops code (or uses operand) and terminates script with exit code
// 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
} OpCode;
typedef struct {

29
src/ext/curl.c Normal file
View file

@ -0,0 +1,29 @@
/*
* 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-11 (2025-12-11 migrated from src/vm/libsql/common.c)
*/
/* Ensure libcurl headers and helpers are defined at file scope (not inside vm_run) */
#ifdef FUN_WITH_CURL
#include <curl/curl.h>
typedef struct { char *d; size_t n; } FunCurlBuf;
static size_t fun_curl_write_cb(void *ptr, size_t sz, size_t nm, void *ud) {
size_t add = sz * nm;
FunCurlBuf *b = (FunCurlBuf*)ud;
char *p = (char*)realloc(b->d, b->n + add + 1);
if (!p) return 0;
memcpy(p + b->n, ptr, add);
b->d = p; b->n += add; b->d[b->n] = '\0';
return add;
}
static size_t fun_curl_file_write_cb(void *ptr, size_t sz, size_t nm, void *ud) {
FILE *f = (FILE*)ud;
return fwrite(ptr, sz, nm, f);
}
#endif

28
src/ext/ini.c Normal file
View file

@ -0,0 +1,28 @@
/*
* 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-11 (2025-12-11 migrated from src/vm.c)
*/
#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 "vm/ini/handles.h"
#endif

107
src/ext/json.c Normal file
View file

@ -0,0 +1,107 @@
/*
* 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-11 (2025-12-11 migrated from src/vm.c)
*/
/* json-c helpers and VM opcode cases (included from vm.c) */
#ifdef FUN_WITH_JSON
#include "value.h"
#include "vm.h"
#include <json-c/json.h>
#include <string.h>
/* --- Conversion helpers between json-c and Fun Value --- */
static Value json_to_fun(json_object *j) {
if (!j) return make_nil();
enum json_type t = json_object_get_type(j);
switch (t) {
case json_type_null: return make_nil();
case json_type_boolean: return make_bool(json_object_get_boolean(j));
case json_type_double: return make_float(json_object_get_double(j));
case json_type_int: return make_int((int64_t)json_object_get_int64(j));
case json_type_string: return make_string(json_object_get_string(j));
case json_type_array: {
size_t n = json_object_array_length(j);
if (n == 0) {
return make_array_from_values(NULL, 0);
}
Value *vals = (Value*)malloc(sizeof(Value) * n);
if (!vals) return make_array_from_values(NULL, 0);
for (size_t i = 0; i < n; ++i) {
json_object *item = json_object_array_get_idx(j, (int)i);
vals[i] = json_to_fun(item);
}
Value arr = make_array_from_values(vals, (int)n);
for (size_t i = 0; i < n; ++i) free_value(vals[i]);
free(vals);
return arr;
}
case json_type_object: {
Value map = make_map_empty();
json_object_object_foreach(j, key, val) {
(void)map_set(&map, key, json_to_fun(val));
}
return map;
}
default:
return make_nil();
}
}
static json_object* fun_to_json(const Value *v) {
switch (v->type) {
case VAL_NIL: return json_object_new_null();
case VAL_BOOL: return json_object_new_boolean(v->i ? 1 : 0);
case VAL_INT: return json_object_new_int64(v->i);
case VAL_FLOAT: return json_object_new_double(v->d);
case VAL_STRING: return json_object_new_string(v->s ? v->s : "");
case VAL_ARRAY: {
json_object *arr = json_object_new_array();
int n = array_length(v);
for (int i = 0; i < n; ++i) {
Value item;
if (array_get_copy(v, i, &item)) {
json_object_array_add(arr, fun_to_json(&item));
free_value(item);
} else {
json_object_array_add(arr, json_object_new_null());
}
}
return arr;
}
case VAL_MAP: {
json_object *obj = json_object_new_object();
/* We don't have an iterator API; use keys() helper */
Value keys = map_keys_array(v);
int kn = array_length(&keys);
for (int i = 0; i < kn; ++i) {
Value k;
if (!array_get_copy(&keys, i, &k)) continue;
if (k.type == VAL_STRING && k.s) {
Value val;
if (map_get_copy(v, k.s, &val)) {
json_object_object_add(obj, k.s, fun_to_json(&val));
free_value(val);
} else {
json_object_object_add(obj, k.s, json_object_new_null());
}
}
free_value(k);
}
free_value(keys);
return obj;
}
default:
/* Fallback: stringify unsupported types */
return json_object_new_string("<unsupported>");
}
}
#endif

55
src/ext/libsql.c Normal file
View file

@ -0,0 +1,55 @@
/*
* 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-11-26 (2025-12-11 migrated from src/vm/libsql/common.c)
*/
#ifdef FUN_WITH_LIBSQL
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sqlite3.h> /* libsql provides a sqlite3-compatible C API */
typedef struct LibSqlHandle {
int id;
sqlite3 *db;
struct LibSqlHandle *next;
} LibSqlHandle;
static LibSqlHandle *g_libsql_handles = NULL;
static int g_libsql_next_id = 1;
static LibSqlHandle *libsql_reg_add(sqlite3 *db) {
LibSqlHandle *h = (LibSqlHandle*)malloc(sizeof(LibSqlHandle));
if (!h) return NULL;
h->id = g_libsql_next_id++;
h->db = db;
h->next = g_libsql_handles;
g_libsql_handles = h;
return h;
}
static LibSqlHandle *libsql_reg_get(int id) {
LibSqlHandle *p = g_libsql_handles;
while (p) { if (p->id == id) return p; p = p->next; }
return NULL;
}
static void libsql_reg_del(int id) {
LibSqlHandle **pp = &g_libsql_handles;
while (*pp) {
if ((*pp)->id == id) {
LibSqlHandle *dead = *pp;
*pp = (*pp)->next;
free(dead);
return;
}
pp = &((*pp)->next);
}
}
#endif

22
src/ext/pcre2.c Normal file
View file

@ -0,0 +1,22 @@
/*
* 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-11 (2025-12-11 migrated from src/vm/libsql/common.c)
*/
/* Ensure PCRE2 is configured consistently across the whole translation unit.
* vm.c includes many opcode implementation .c files; some use PCRE2. For PCRE2
* headers to expose the correct typedefs (e.g., pcre2_code, PCRE2_SPTR), the
* PCRE2_CODE_UNIT_WIDTH macro must be defined before the first inclusion of
* <pcre2.h>. We do this once here when PCRE2 support is enabled. */
#ifdef FUN_WITH_PCRE2
#ifndef PCRE2_CODE_UNIT_WIDTH
#define PCRE2_CODE_UNIT_WIDTH 8
#endif
#include <pcre2.h>
#endif

76
src/ext/pcsc.c Normal file
View file

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

49
src/ext/sqlite.c Normal file
View file

@ -0,0 +1,49 @@
/*
* 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-11 (2025-12-11 migrated from src/vm/sqlite/common.c)
*/
/**
* SQLite handle registry and helpers
*/
#ifdef FUN_WITH_SQLITE
#include <sqlite3.h>
typedef struct SqlHandle {
int id;
sqlite3 *db;
struct SqlHandle *next;
} SqlHandle;
static SqlHandle *g_sql_handles = NULL;
static int g_sql_next_id = 1;
static SqlHandle* sql_reg_add(sqlite3 *db) {
SqlHandle *h = (SqlHandle*)calloc(1, sizeof(SqlHandle));
if (!h) return NULL;
h->id = g_sql_next_id++;
h->db = db;
h->next = g_sql_handles;
g_sql_handles = h;
return h;
}
static SqlHandle* sql_reg_get(int id) {
for (SqlHandle *p = g_sql_handles; p; p = p->next) if (p->id == id) return p;
return NULL;
}
static void sql_reg_del(int id) {
SqlHandle **pp = &g_sql_handles;
while (*pp) {
if ((*pp)->id == id) { SqlHandle *d = *pp; *pp = d->next; free(d); return; }
pp = &(*pp)->next;
}
}
#endif

69
src/ext/tcltk.c Normal file
View file

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

57
src/ext/xml2.c Normal file
View file

@ -0,0 +1,57 @@
/*
* 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-11 (2025-12-11 migrated from src/vm/libsql/common.c)
*/
#ifdef FUN_WITH_XML2
#include <libxml/parser.h>
#include <libxml/tree.h>
typedef struct { xmlDocPtr doc; int in_use; } XmlDocSlot;
typedef struct { xmlNodePtr node; int in_use; } XmlNodeSlot;
static XmlDocSlot g_xml_docs[64];
static XmlNodeSlot g_xml_nodes[256];
static int xml_doc_alloc(xmlDocPtr d) {
for (int i = 1; i < (int)(sizeof(g_xml_docs)/sizeof(g_xml_docs[0])); ++i) {
if (!g_xml_docs[i].in_use) { g_xml_docs[i].in_use = 1; g_xml_docs[i].doc = d; return i; }
}
return 0;
}
static xmlDocPtr xml_doc_get(int h) {
if (h > 0 && h < (int)(sizeof(g_xml_docs)/sizeof(g_xml_docs[0])) && g_xml_docs[h].in_use) return g_xml_docs[h].doc;
return NULL;
}
static int xml_doc_free_handle(int h) {
if (h <= 0 || h >= (int)(sizeof(g_xml_docs)/sizeof(g_xml_docs[0])) || !g_xml_docs[h].in_use) return 0;
if (g_xml_docs[h].doc) xmlFreeDoc(g_xml_docs[h].doc);
g_xml_docs[h].doc = NULL;
g_xml_docs[h].in_use = 0;
return 1;
}
static int xml_node_alloc(xmlNodePtr n) {
for (int i = 1; i < (int)(sizeof(g_xml_nodes)/sizeof(g_xml_nodes[0])); ++i) {
if (!g_xml_nodes[i].in_use) { g_xml_nodes[i].in_use = 1; g_xml_nodes[i].node = n; return i; }
}
return 0;
}
static xmlNodePtr xml_node_get(int h) {
if (h > 0 && h < (int)(sizeof(g_xml_nodes)/sizeof(g_xml_nodes[0])) && g_xml_nodes[h].in_use) return g_xml_nodes[h].node;
return NULL;
}
static int xml_node_free_handle(int h) {
if (h <= 0 || h >= (int)(sizeof(g_xml_nodes)/sizeof(g_xml_nodes[0])) || !g_xml_nodes[h].in_use) return 0;
/* nodes are owned by their document; do not free here */
g_xml_nodes[h].node = NULL;
g_xml_nodes[h].in_use = 0;
return 1;
}
#endif

View file

@ -5,12 +5,11 @@
*/
#include "bytecode.h"
#include "value.h"
#include "vm.h"
#include "parser.h"
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#ifdef FUN_WITH_REPL
#include "repl.h"

View file

@ -739,6 +739,334 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
free(name);
return 1;
}
/* JSON builtins */
if (strcmp(name, "json_parse") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "json_parse expects (text)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after json_parse arg"); free(name); return 0; }
bytecode_add_instruction(bc, OP_JSON_PARSE, 0);
free(name);
return 1;
}
/* XML builtins (minimal) */
if (strcmp(name, "xml_parse") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "xml_parse expects (text)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after xml_parse arg"); free(name); return 0; }
bytecode_add_instruction(bc, OP_XML_PARSE, 0);
free(name);
return 1;
}
if (strcmp(name, "xml_root") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "xml_root expects (doc_handle)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after xml_root arg"); free(name); return 0; }
bytecode_add_instruction(bc, OP_XML_ROOT, 0);
free(name);
return 1;
}
if (strcmp(name, "xml_name") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "xml_name expects (node_handle)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after xml_name arg"); free(name); return 0; }
bytecode_add_instruction(bc, OP_XML_NAME, 0);
free(name);
return 1;
}
if (strcmp(name, "xml_text") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "xml_text expects (node_handle)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after xml_text arg"); free(name); return 0; }
bytecode_add_instruction(bc, OP_XML_TEXT, 0);
free(name);
return 1;
}
if (strcmp(name, "json_stringify") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "json_stringify expects (value, pretty)"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "json_stringify expects (value, pretty)"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "json_stringify expects (value, pretty)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after json_stringify args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_JSON_STRINGIFY, 0);
free(name);
return 1;
}
/* Tk (GUI) builtins (no raw Tcl exposed) */
if (strcmp(name, "tk_loop") == 0) {
(*pos)++; /* '(' */
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "tk_loop expects ()"); free(name); return 0; }
bytecode_add_instruction(bc, OP_TK_LOOP, 0);
free(name);
return 1;
}
if (strcmp(name, "tk_title") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tk_title expects (title:string)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after tk_title arg"); free(name); return 0; }
bytecode_add_instruction(bc, OP_TK_WM_TITLE, 0);
free(name);
return 1;
}
if (strcmp(name, "tk_label") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tk_label expects (id:string, text:string)"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "tk_label expects 2 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tk_label expects (id:string, text:string)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after tk_label args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_TK_LABEL, 0);
free(name);
return 1;
}
if (strcmp(name, "tk_button") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tk_button expects (id:string, text:string)"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "tk_button expects 2 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tk_button expects (id:string, text:string)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after tk_button args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_TK_BUTTON, 0);
free(name);
return 1;
}
if (strcmp(name, "tk_pack") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "tk_pack expects (id:string)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after tk_pack arg"); free(name); return 0; }
bytecode_add_instruction(bc, OP_TK_PACK, 0);
free(name);
return 1;
}
if (strcmp(name, "json_from_file") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "json_from_file expects (path)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after json_from_file arg"); free(name); return 0; }
bytecode_add_instruction(bc, OP_JSON_FROM_FILE, 0);
free(name);
return 1;
}
if (strcmp(name, "json_to_file") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "json_to_file expects (path, value, pretty)"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "json_to_file expects (path, value, pretty)"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "json_to_file expects (path, value, pretty)"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "json_to_file expects (path, value, pretty)"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "json_to_file expects (path, value, pretty)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after json_to_file args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_JSON_TO_FILE, 0);
free(name);
return 1;
}
/* INI (iniparser 4.2.6) builtins */
if (strcmp(name, "ini_load") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_load expects (path)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after ini_load arg"); free(name); return 0; }
bytecode_add_instruction(bc, OP_INI_LOAD, 0);
free(name);
return 1;
}
if (strcmp(name, "ini_free") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_free expects (handle)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after ini_free arg"); free(name); return 0; }
bytecode_add_instruction(bc, OP_INI_FREE, 0);
free(name);
return 1;
}
if (strcmp(name, "ini_get_string") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_string expects (handle, section, key, default)"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_get_string expects 4 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_string expects 4 args"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_get_string expects 4 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_string expects 4 args"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_get_string expects 4 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_string expects 4 args"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after ini_get_string args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_INI_GET_STRING, 0);
free(name);
return 1;
}
if (strcmp(name, "ini_get_int") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_int expects (handle, section, key, default)"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_get_int expects 4 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_int expects 4 args"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_get_int expects 4 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_int expects 4 args"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_get_int expects 4 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_int expects 4 args"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after ini_get_int args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_INI_GET_INT, 0);
free(name);
return 1;
}
if (strcmp(name, "ini_get_double") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_double expects (handle, section, key, default)"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_get_double expects 4 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_double expects 4 args"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_get_double expects 4 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_double expects 4 args"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_get_double expects 4 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_double expects 4 args"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after ini_get_double args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_INI_GET_DOUBLE, 0);
free(name);
return 1;
}
if (strcmp(name, "ini_get_bool") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_bool expects (handle, section, key, default)"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_get_bool expects 4 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_bool expects 4 args"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_get_bool expects 4 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_bool expects 4 args"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_get_bool expects 4 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_get_bool expects 4 args"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after ini_get_bool args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_INI_GET_BOOL, 0);
free(name);
return 1;
}
if (strcmp(name, "ini_set") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_set expects (handle, section, key, value)"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_set expects 4 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_set expects 4 args"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_set expects 4 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_set expects 4 args"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_set expects 4 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_set expects 4 args"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after ini_set args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_INI_SET, 0);
free(name);
return 1;
}
if (strcmp(name, "ini_unset") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_unset expects (handle, section, key)"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_unset expects 3 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_unset expects 3 args"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_unset expects 3 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_unset expects 3 args"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after ini_unset args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_INI_UNSET, 0);
free(name);
return 1;
}
if (strcmp(name, "ini_save") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_save expects (handle, path)"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "ini_save expects 2 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "ini_save expects 2 args"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after ini_save args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_INI_SAVE, 0);
free(name);
return 1;
}
/* CURL builtins (minimal interface like JSON) */
if (strcmp(name, "curl_get") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "curl_get expects (url)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after curl_get arg"); free(name); return 0; }
bytecode_add_instruction(bc, OP_CURL_GET, 0);
free(name);
return 1;
}
/* SQLite builtins */
if (strcmp(name, "sqlite_open") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "sqlite_open expects (path)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after sqlite_open arg"); free(name); return 0; }
bytecode_add_instruction(bc, OP_SQLITE_OPEN, 0);
free(name);
return 1;
}
if (strcmp(name, "sqlite_close") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "sqlite_close expects (handle)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after sqlite_close arg"); free(name); return 0; }
bytecode_add_instruction(bc, OP_SQLITE_CLOSE, 0);
free(name);
return 1;
}
if (strcmp(name, "sqlite_exec") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "sqlite_exec expects (handle, sql)"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "sqlite_exec expects (handle, sql)"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "sqlite_exec expects (handle, sql)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after sqlite_exec args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_SQLITE_EXEC, 0);
free(name);
return 1;
}
if (strcmp(name, "sqlite_query") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "sqlite_query expects (handle, sql)"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "sqlite_query expects (handle, sql)"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "sqlite_query expects (handle, sql)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after sqlite_query args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_SQLITE_QUERY, 0);
free(name);
return 1;
}
/* libsql builtins (independent extension) */
if (strcmp(name, "libsql_open") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "libsql_open expects (url_or_path)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after libsql_open arg"); free(name); return 0; }
bytecode_add_instruction(bc, OP_LIBSQL_OPEN, 0);
free(name);
return 1;
}
if (strcmp(name, "libsql_close") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "libsql_close expects (handle)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after libsql_close arg"); free(name); return 0; }
bytecode_add_instruction(bc, OP_LIBSQL_CLOSE, 0);
free(name);
return 1;
}
if (strcmp(name, "libsql_exec") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "libsql_exec expects (handle, sql)"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "libsql_exec expects (handle, sql)"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "libsql_exec expects (handle, sql)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after libsql_exec args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_LIBSQL_EXEC, 0);
free(name);
return 1;
}
if (strcmp(name, "libsql_query") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "libsql_query expects (handle, sql)"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "libsql_query expects (handle, sql)"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "libsql_query expects (handle, sql)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after libsql_query args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_LIBSQL_QUERY, 0);
free(name);
return 1;
}
if (strcmp(name, "curl_post") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "curl_post expects (url, body)"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "curl_post expects (url, body)"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "curl_post expects (url, body)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after curl_post args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_CURL_POST, 0);
free(name);
return 1;
}
if (strcmp(name, "curl_download") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "curl_download expects (url, path)"); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "curl_download expects (url, path)"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "curl_download expects (url, path)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after curl_download args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_CURL_DOWNLOAD, 0);
free(name);
return 1;
}
/* PCSC builtins */
if (strcmp(name, "pcsc_establish") == 0) {
(*pos)++; /* '(' */
@ -748,6 +1076,44 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
free(name);
return 1;
}
/* PCRE2 builtins */
if (strcmp(name, "pcre2_test") == 0) {
(*pos)++; /* '(' */
/* (pattern, text, flags) */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_test expects (pattern, text, flags)" ); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "pcre2_test expects (pattern, text, flags)"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_test expects (pattern, text, flags)" ); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "pcre2_test expects (pattern, text, flags)"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_test expects (pattern, text, flags)" ); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after pcre2_test args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_PCRE2_TEST, 0);
free(name);
return 1;
}
if (strcmp(name, "pcre2_match") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_match expects (pattern, text, flags)" ); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "pcre2_match expects (pattern, text, flags)"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_match expects (pattern, text, flags)" ); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "pcre2_match expects (pattern, text, flags)"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_match expects (pattern, text, flags)" ); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after pcre2_match args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_PCRE2_MATCH, 0);
free(name);
return 1;
}
if (strcmp(name, "pcre2_findall") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)" ); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)" ); free(name); return 0; }
if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcre2_findall expects (pattern, text, flags)" ); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after pcre2_findall args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_PCRE2_FINDALL, 0);
free(name);
return 1;
}
if (strcmp(name, "pcsc_release") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcsc_release expects 1 argument (ctx)"); free(name); return 0; }
@ -1266,22 +1632,33 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
free(name);
return 1;
}
if (strcmp(name, "random") == 0) {
if (strcmp(name, "random_seed") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "random expects 1 arg"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "random_seed expects 1 arg"); free(name); return 0; }
bytecode_add_instruction(bc, OP_RANDOM_SEED, 0);
free(name);
return 1;
}
if (strcmp(name, "randomInt") == 0) {
if (strcmp(name, "random_int") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "randomInt expects 2 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "randomInt expects 2 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "random_int expects 2 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "random_int expects 2 args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_RANDOM_INT, 0);
free(name);
return 1;
}
if (strcmp(name, "random_number") == 0) {
(*pos)++; /* '(' */
/* expects exactly 1 arg: length */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "random_number expects 1 arg (length)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after random_number arg"); free(name); return 0; }
bytecode_add_instruction(bc, OP_RANDOM_NUMBER, 0);
free(name);
return 1;
}
/* threading */
if (strcmp(name, "thread_spawn") == 0) {
(*pos)++; /* '(' */
@ -2361,7 +2738,7 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
}
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
} else {
/* integer widths: expect Number then clamp */
/* integer widths: expect Number then range-check */
int abs_bits = decl_bits < 0 ? -decl_bits : decl_bits;
if (abs_bits > 0) {
/* typeof == Number */
@ -2381,7 +2758,53 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
}
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
bytecode_add_instruction(bc, (decl_bits < 0) ? OP_SCLAMP : OP_UCLAMP, abs_bits);
/* range check instead of clamp */
int64_t minV = 0, maxV = 0;
if (decl_bits < 0) {
/* signed */
if (abs_bits >= 64) { minV = INT64_MIN; maxV = INT64_MAX; }
else { maxV = (1LL << (abs_bits - 1)) - 1; minV = - (1LL << (abs_bits - 1)); }
} else {
/* unsigned */
if (abs_bits >= 63) { minV = 0; maxV = INT64_MAX; }
else { minV = 0; maxV = (1LL << abs_bits) - 1; }
}
int ciMin = bytecode_add_constant(bc, make_int(minV));
int ciMax = bytecode_add_constant(bc, make_int(maxV));
/* if (v < min) -> error */
bytecode_add_instruction(bc, OP_DUP, 0);
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMin);
bytecode_add_instruction(bc, OP_LT, 0);
int j_after_min = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
{
const char *tname = (decl_bits < 0)
? (abs_bits==64? "int64" : (abs_bits==32? "int32" : (abs_bits==16? "int16" : "int8")))
: (abs_bits==64? "uint64" : (abs_bits==32? "uint32" : (abs_bits==16? "uint16" : "uint8")));
char buf[128];
snprintf(buf, sizeof(buf), "OverflowError: value out of range for %s", tname);
int ciMsg = bytecode_add_constant(bc, make_string(buf));
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
bytecode_add_instruction(bc, OP_THROW, 0);
}
bytecode_set_operand(bc, j_after_min, bc->instr_count);
/* if (v > max) -> error */
bytecode_add_instruction(bc, OP_DUP, 0);
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMax);
bytecode_add_instruction(bc, OP_GT, 0);
int j_after_max = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
{
const char *tname = (decl_bits < 0)
? (abs_bits==64? "int64" : (abs_bits==32? "int32" : (abs_bits==16? "int16" : "int8")))
: (abs_bits==64? "uint64" : (abs_bits==32? "uint32" : (abs_bits==16? "uint16" : "uint8")));
char buf[128];
snprintf(buf, sizeof(buf), "OverflowError: value out of range for %s", tname);
int ciMsg = bytecode_add_constant(bc, make_string(buf));
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
bytecode_add_instruction(bc, OP_THROW, 0);
}
bytecode_set_operand(bc, j_after_max, bc->instr_count);
}
}
@ -2440,6 +2863,21 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
return;
}
if (strcmp(name, "echo") == 0) {
free(name);
skip_spaces(src, len, &local_pos);
(void)consume_char(src, len, &local_pos, '(');
if (emit_expression(bc, src, len, &local_pos)) {
(void)consume_char(src, len, &local_pos, ')');
bytecode_add_instruction(bc, OP_ECHO, 0);
} else {
(void)consume_char(src, len, &local_pos, ')');
}
*pos = local_pos;
skip_to_eol(src, len, pos);
return;
}
/* assignment or simple call */
int lidx = local_find(name);
int gi = (lidx < 0) ? sym_index(name) : -1;
@ -2690,7 +3128,7 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
}
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
} else if (meta != 0) {
/* integer widths: expect Number then clamp to declared width */
/* integer widths: expect Number then range-check to declared width */
int abs_bits = meta < 0 ? -meta : meta;
/* typeof == Number */
bytecode_add_instruction(bc, OP_DUP, 0);
@ -2709,7 +3147,49 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
}
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
bytecode_add_instruction(bc, (meta < 0) ? OP_SCLAMP : OP_UCLAMP, abs_bits);
/* range check instead of clamp */
int64_t minV = 0, maxV = 0;
if (meta < 0) {
if (abs_bits >= 64) { minV = INT64_MIN; maxV = INT64_MAX; }
else { maxV = (1LL << (abs_bits - 1)) - 1; minV = - (1LL << (abs_bits - 1)); }
} else {
if (abs_bits >= 63) { minV = 0; maxV = INT64_MAX; }
else { minV = 0; maxV = (1LL << abs_bits) - 1; }
}
int ciMin = bytecode_add_constant(bc, make_int(minV));
int ciMax = bytecode_add_constant(bc, make_int(maxV));
bytecode_add_instruction(bc, OP_DUP, 0);
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMin);
bytecode_add_instruction(bc, OP_LT, 0);
int j_after_min = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
{
const char *tname = (meta < 0)
? (abs_bits==64? "int64" : (abs_bits==32? "int32" : (abs_bits==16? "int16" : "int8")))
: (abs_bits==64? "uint64" : (abs_bits==32? "uint32" : (abs_bits==16? "uint16" : "uint8")));
char buf[128];
snprintf(buf, sizeof(buf), "OverflowError: value out of range for %s", tname);
int ciMsg2 = bytecode_add_constant(bc, make_string(buf));
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg2);
bytecode_add_instruction(bc, OP_THROW, 0);
}
bytecode_set_operand(bc, j_after_min, bc->instr_count);
bytecode_add_instruction(bc, OP_DUP, 0);
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMax);
bytecode_add_instruction(bc, OP_GT, 0);
int j_after_max = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
{
const char *tname = (meta < 0)
? (abs_bits==64? "int64" : (abs_bits==32? "int32" : (abs_bits==16? "int16" : "int8")))
: (abs_bits==64? "uint64" : (abs_bits==32? "uint32" : (abs_bits==16? "uint16" : "uint8")));
char buf[128];
snprintf(buf, sizeof(buf), "OverflowError: value out of range for %s", tname);
int ciMsg3 = bytecode_add_constant(bc, make_string(buf));
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg3);
bytecode_add_instruction(bc, OP_THROW, 0);
}
bytecode_set_operand(bc, j_after_max, bc->instr_count);
}
/* dynamic (meta==0): no enforcement */
@ -2754,6 +3234,21 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
return;
}
/* echo(expr): like print but does not add a newline (immediate output) */
if (starts_with(src, len, *pos, "echo")) {
*pos += 4;
skip_spaces(src, len, pos);
(void)consume_char(src, len, pos, '(');
if (emit_expression(bc, src, len, pos)) {
(void)consume_char(src, len, pos, ')');
bytecode_add_instruction(bc, OP_ECHO, 0);
} else {
(void)consume_char(src, len, pos, ')');
}
skip_to_eol(src, len, pos);
return;
}
/* unknown token: report error */
parser_fail(*pos, "Unknown token at start of statement");
}
@ -3892,13 +4387,16 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos,
continue;
}
/* try/catch/finally (syntax support; runtime exceptions not yet implemented) */
/* try/catch/finally */
if (starts_with(src, len, *pos, "try")) {
/* consume 'try' */
*pos += 3;
/* end of header line */
skip_to_eol(src, len, pos);
/* Install a handler placeholder; will be patched to catch label (or a rethrow stub) */
int try_push_idx = bytecode_add_instruction(bc, OP_TRY_PUSH, 0);
/* parse try body at increased indent (if any) */
int try_body_indent = 0;
size_t look_try = *pos;
@ -3908,9 +4406,16 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos,
/* empty try body allowed */
}
/* After try body, pop handler for normal (non-exceptional) flow */
bytecode_add_instruction(bc, OP_TRY_POP, 0);
/* on normal completion, jump over catch body */
int jmp_over_catch_finally = bytecode_add_instruction(bc, OP_JUMP, 0);
/* Optional: catch and/or finally clauses at same indentation */
int seen_catch = 0;
int seen_finally = 0;
int catch_label = -1;
for (;;) {
size_t look = *pos;
int look_indent = 0;
@ -3924,15 +4429,34 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos,
skip_spaces(src, len, pos);
char *ex_name = NULL;
size_t tmp = *pos;
int have_name = 0;
if (read_identifier_into(src, len, &tmp, &ex_name)) {
*pos = tmp;
free(ex_name);
have_name = 1;
}
/* end of header line */
skip_to_eol(src, len, pos);
/* We currently don't have runtime exceptions: emit an unconditional jump over the catch body (so it's parsed but never executed) */
int j_over = bytecode_add_instruction(bc, OP_JUMP, 0);
/* Mark catch label and patch try handler target */
catch_label = bc->instr_count;
bytecode_set_operand(bc, try_push_idx, catch_label);
/* On entering catch, the thrown error is on stack. Bind to name if provided, else pop. */
if (have_name) {
int lidx = -1, gi = -1;
if (g_locals) {
int existing = local_find(ex_name);
if (existing >= 0) lidx = existing; else lidx = local_add(ex_name);
} else {
gi = sym_index(ex_name);
}
if (lidx >= 0) bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx);
else if (gi >= 0) bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi);
else bytecode_add_instruction(bc, OP_POP, 0);
} else {
bytecode_add_instruction(bc, OP_POP, 0);
}
if (ex_name) free(ex_name);
/* parse catch body at increased indent (if any) */
int catch_indent = 0;
@ -3942,10 +4466,6 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos,
} else {
/* empty catch body allowed */
}
/* patch jump to here (after catch body) */
bytecode_set_operand(bc, j_over, bc->instr_count);
seen_catch = 1;
continue;
}
@ -3972,6 +4492,17 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos,
/* no recognized clause at this indentation */
break;
}
/* If no catch clause was present, make handler rethrow */
if (!seen_catch) {
int rethrow_label = bc->instr_count;
bytecode_set_operand(bc, try_push_idx, rethrow_label);
/* at handler: immediately rethrow the incoming error */
bytecode_add_instruction(bc, OP_THROW, 0);
}
/* patch normal-flow jump to here (after catch/finally) */
bytecode_set_operand(bc, jmp_over_catch_finally, bc->instr_count);
continue;
}

View file

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

View file

@ -441,6 +441,15 @@ char *value_to_string_alloc(const Value *v) {
snprintf(buf, sizeof(buf), "[array n=%d]", n);
return strdup(buf);
}
case VAL_MAP: {
int n = 0;
if (v->type == VAL_MAP && v->map) {
const Map *m = (const Map*)v->map;
n = m ? m->count : 0;
}
snprintf(buf, sizeof(buf), "{map n=%d}", n);
return strdup(buf);
}
case VAL_NIL:
default:
return strdup("nil");

135
src/vm.c
View file

@ -7,21 +7,19 @@
* https://opensource.org/license/apache-2-0
*/
/* Bring in split-out built-ins without changing the build system yet */
#include "iter.c"
#include "map.c"
#include "string.c"
#include "pcsc.c"
#include "vm.h"
#include "value.h"
/* Ensure POSIX prototypes (nanosleep, clock_gettime, localtime_r, etc.) are available
* before any system headers are included by amalgamated .c files. */
#ifndef _WIN32
#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
/* forward declarations for include mapping used in error reporting */
extern char *preprocess_includes(const char *src);
static int map_expanded_line_to_include(const char *path, int line, char *out_path, size_t out_path_cap, int *out_line);
#include <time.h>
#ifdef __unix__
#include <sys/wait.h>
@ -31,9 +29,31 @@ static int map_expanded_line_to_include(const char *path, int line, char *out_pa
#include <netdb.h>
#include <sys/un.h>
#include <unistd.h>
#include <arpa/inet.h>
//#include <arpa/inet.h>
#endif
/* Bring in split-out built-ins without changing the build system yet */
#include "iter.c"
#include "map.c"
#include "string.c"
#include "value.h"
#include "vm.h"
// Optional by extensions commonly used code. #ifdef's are in each single file.
#include "ext/curl.c"
#include "ext/ini.c"
#include "ext/json.c"
#include "ext/libsql.c"
#include "ext/pcsc.c"
#include "ext/pcre2.c"
#include "ext/sqlite.c"
#include "ext/tcltk.c"
#include "ext/xml2.c"
/* forward declarations for include mapping used in error reporting */
extern char *preprocess_includes(const char *src);
static int map_expanded_line_to_include(const char *path, int line, char *out_path, size_t out_path_cap, int *out_line);
/* Threading internals (registry and platform glue) */
#include "vm/os/thread_common.c"
@ -249,6 +269,8 @@ void vm_clear_output(VM *vm) {
free_value(vm->output[i]);
}
vm->output_count = 0;
// reset partial flags
for (int i = 0; i < OUTPUT_SIZE; ++i) vm->output_is_partial[i] = 0;
}
void vm_free(VM *vm) {
@ -393,12 +415,14 @@ static void frame_init(Frame *f) {
f->fn = NULL;
f->ip = 0;
for (int i = 0; i < MAX_FRAME_LOCALS; ++i) f->locals[i] = make_nil();
f->try_sp = -1;
}
void vm_init(VM *vm) {
vm->sp = -1;
vm->fp = -1;
vm->output_count = 0;
for (int i = 0; i < OUTPUT_SIZE; ++i) vm->output_is_partial[i] = 0;
vm->instr_count = 0;
vm->exit_code = 0;
vm->trace_enabled = 0;
@ -454,6 +478,12 @@ static void vm_pop_frame(VM *vm) {
void vm_print_output(VM *vm) {
for (int i = 0; i < vm->output_count; ++i) {
print_value(&vm->output[i]);
if (!vm->output_is_partial[i]) {
printf("\n");
}
}
/* If the last item was partial (from echo), terminate the line for cleanliness */
if (vm->output_count > 0 && vm->output_is_partial[vm->output_count - 1]) {
printf("\n");
}
}
@ -591,8 +621,8 @@ void vm_run(VM *vm, Bytecode *entry) {
#include "vm/core/call.c"
#include "vm/core/dup.c"
#include "vm/core/halt.c"
#include "vm/core/exit.c"
#include "vm/core/halt.c"
#include "vm/core/jump.c"
#include "vm/core/jump_if_false.c"
#include "vm/core/load_const.c"
@ -604,6 +634,9 @@ void vm_run(VM *vm, Bytecode *entry) {
#include "vm/core/store_global.c"
#include "vm/core/store_local.c"
#include "vm/core/swap.c"
#include "vm/core/throw.c"
#include "vm/core/try_pop.c"
#include "vm/core/try_push.c"
#include "vm/io/read_file.c"
#include "vm/io/write_file.c"
@ -642,7 +675,8 @@ void vm_run(VM *vm, Bytecode *entry) {
#include "vm/os/time_now_ms.c"
#include "vm/os/clock_mono_ms.c"
#include "vm/os/date_format.c"
#include "vm/os/random_number.c"
/* Socket ops */
#include "vm/os/socket_tcp_listen.c"
#include "vm/os/socket_tcp_accept.c"
@ -653,12 +687,84 @@ void vm_run(VM *vm, Bytecode *entry) {
#include "vm/os/socket_unix_listen.c"
#include "vm/os/socket_unix_connect.c"
#ifdef FUN_WITH_PCSC
#include "vm/pcsc/establish.c"
#include "vm/pcsc/release.c"
#include "vm/pcsc/list_readers.c"
#include "vm/pcsc/connect.c"
#include "vm/pcsc/disconnect.c"
#include "vm/pcsc/transmit.c"
#endif
/* JSON ops (implemented in jsonc.c, included above) */
#ifdef FUN_WITH_JSON
#include "vm/json/parse.c"
#include "vm/json/stringify.c"
#include "vm/json/from_file.c"
#include "vm/json/to_file.c"
#endif
/* XML ops (libxml2) */
#ifdef FUN_WITH_XML2
#include "vm/xml/parse.c"
#include "vm/xml/root.c"
#include "vm/xml/name.c"
#include "vm/xml/text.c"
#endif
/* INI ops (iniparser 4.2.6) */
#ifdef FUN_WITH_INI
#include "vm/ini/load.c"
#include "vm/ini/free.c"
#include "vm/ini/get_string.c"
#include "vm/ini/get_int.c"
#include "vm/ini/get_double.c"
#include "vm/ini/get_bool.c"
#include "vm/ini/set.c"
#include "vm/ini/unset.c"
#include "vm/ini/save.c"
#endif
/* CURL ops */
#ifdef FUN_WITH_CURL
#include "vm/curl/get.c"
#include "vm/curl/post.c"
#include "vm/curl/download.c"
#endif
/* Tk (Tcl/Tk) ops */
#ifdef FUN_WITH_TCLTK
#include "vm/tk/eval.c"
#include "vm/tk/result.c"
#include "vm/tk/loop.c"
#include "vm/tk/wm_title.c"
#include "vm/tk/label.c"
#include "vm/tk/button.c"
#include "vm/tk/pack.c"
#endif
/* SQLite ops */
#ifdef FUN_WITH_SQLITE
#include "vm/sqlite/open.c"
#include "vm/sqlite/close.c"
#include "vm/sqlite/exec.c"
#include "vm/sqlite/query.c"
#endif
/* libsql ops (independent) */
#ifdef FUN_WITH_LIBSQL
#include "vm/libsql/open.c"
#include "vm/libsql/close.c"
#include "vm/libsql/exec.c"
#include "vm/libsql/query.c"
#endif
/* PCRE2 ops */
#ifdef FUN_WITH_PCRE2
#include "vm/pcre2/test.c"
#include "vm/pcre2/match.c"
#include "vm/pcre2/findall.c"
#endif
#include "vm/strings/find.c"
#include "vm/strings/regex_match.c"
@ -670,6 +776,7 @@ void vm_run(VM *vm, Bytecode *entry) {
#include "vm/len.c"
#include "vm/line.c"
#include "vm/print.c"
#include "vm/echo.c"
#include "vm/to_number.c"
#include "vm/to_string.c"
#include "vm/cast.c"

View file

@ -22,7 +22,7 @@ static const char *opcode_names[] = {
"NOP","LOAD_CONST","LOAD_LOCAL","STORE_LOCAL",
"LOAD_GLOBAL","STORE_GLOBAL","ADD","SUB","MUL","DIV",
"LT","LTE","GT","GTE","EQ","NEQ","POP","JUMP",
"JUMP_IF_FALSE","CALL","RETURN","PRINT","HALT",
"JUMP_IF_FALSE","CALL","RETURN","PRINT","ECHO","HALT",
"LINE",
"MOD","AND","OR","NOT","DUP","SWAP",
"MAKE_ARRAY","INDEX_GET","INDEX_SET",
@ -37,16 +37,28 @@ static const char *opcode_names[] = {
"READ_FILE","WRITE_FILE","ENV","INPUT_LINE","PROC_RUN","PROC_SYSTEM",
"TIME_NOW_MS","CLOCK_MONO_MS","DATE_FORMAT",
"THREAD_SPAWN","THREAD_JOIN","SLEEP_MS",
"RANDOM_NUMBER",
"BAND","BOR","BXOR","BNOT","SHL","SHR","ROTL","ROTR",
"JSON_PARSE","JSON_STRINGIFY","JSON_FROM_FILE","JSON_TO_FILE",
"CURL_GET","CURL_POST","CURL_DOWNLOAD",
"SQLITE_OPEN","SQLITE_CLOSE","SQLITE_EXEC","SQLITE_QUERY",
"LIBSQL_OPEN","LIBSQL_CLOSE","LIBSQL_EXEC","LIBSQL_QUERY",
"PCSC_ESTABLISH","PCSC_RELEASE","PCSC_LIST_READERS","PCSC_CONNECT","PCSC_DISCONNECT","PCSC_TRANSMIT",
"PCRE2_TEST","PCRE2_MATCH","PCRE2_FINDALL",
"INI_LOAD","INI_FREE","INI_GET_STRING","INI_GET_INT","INI_GET_DOUBLE","INI_GET_BOOL","INI_SET","INI_UNSET","INI_SAVE",
"XML_PARSE","XML_ROOT","XML_NAME","XML_TEXT",
"SOCK_TCP_LISTEN","SOCK_TCP_ACCEPT","SOCK_TCP_CONNECT","SOCK_SEND","SOCK_RECV","SOCK_CLOSE","SOCK_UNIX_LISTEN","SOCK_UNIX_CONNECT",
"EXIT"
"EXIT",
"TRY_PUSH","TRY_POP","THROW"
};
typedef struct {
Bytecode *fn;
int ip;
Value locals[MAX_FRAME_LOCALS];
/* exception handling (per-frame) */
int try_stack[16];
int try_sp; /* -1 when empty */
} Frame;
struct VM {
@ -60,6 +72,7 @@ struct VM {
Value output[OUTPUT_SIZE]; // store printed values
int output_count;
int output_is_partial[OUTPUT_SIZE]; // 1 when the corresponding output entry should not end with newline (echo)
long long instr_count; // executed instructions in the last vm_run
@ -115,7 +128,7 @@ void vm_debug_request_finish(VM *vm);
void vm_debug_request_continue(VM *vm);
static inline int opcode_is_valid(int op) {
return op >= OP_NOP && op <= OP_EXIT; // all current opcodes
return op >= OP_NOP && op <= OP_THROW; // all current opcodes
}
#endif

33
src/vm/core/throw.c Normal file
View file

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

13
src/vm/core/try_pop.c Normal file
View file

@ -0,0 +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
*/
case OP_TRY_POP: {
if (f->try_sp >= 0) f->try_sp--;
break;
}

18
src/vm/core/try_push.c Normal file
View file

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

47
src/vm/curl/download.c Normal file
View file

@ -0,0 +1,47 @@
/**
* libcurl DOWNLOAD builtin
*/
case OP_CURL_DOWNLOAD: {
#ifdef FUN_WITH_CURL
Value vpath = pop_value(vm);
Value vurl = pop_value(vm);
char *url = value_to_string_alloc(&vurl);
char *path = value_to_string_alloc(&vpath);
free_value(vurl);
free_value(vpath);
if (!url || !path) {
if (url) free(url);
if (path) free(path);
push_value(vm, make_int(0));
break;
}
FILE *fp = fopen(path, "wb");
if (!fp) {
free(url); free(path);
push_value(vm, make_int(0));
break;
}
CURL *h = curl_easy_init();
if (!h) {
fclose(fp);
free(url); free(path);
push_value(vm, make_int(0));
break;
}
curl_easy_setopt(h, CURLOPT_URL, url);
curl_easy_setopt(h, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, fun_curl_file_write_cb);
curl_easy_setopt(h, CURLOPT_WRITEDATA, fp);
CURLcode rc = curl_easy_perform(h);
curl_easy_cleanup(h);
fclose(fp);
free(url); free(path);
if (rc != CURLE_OK) { push_value(vm, make_int(0)); break; }
push_value(vm, make_int(1));
#else
Value a = pop_value(vm); free_value(a);
Value b = pop_value(vm); free_value(b);
push_value(vm, make_int(0));
#endif
break;
}

33
src/vm/curl/get.c Normal file
View file

@ -0,0 +1,33 @@
/**
* libcurl GET builtin
*/
case OP_CURL_GET: {
#ifdef FUN_WITH_CURL
Value vurl = pop_value(vm);
char *url = value_to_string_alloc(&vurl);
free_value(vurl);
if (!url) { push_value(vm, make_string("")); break; }
FunCurlBuf buf = { NULL, 0 };
CURL *h = curl_easy_init();
if (!h) { free(url); push_value(vm, make_string("")); break; }
curl_easy_setopt(h, CURLOPT_URL, url);
curl_easy_setopt(h, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, fun_curl_write_cb);
curl_easy_setopt(h, CURLOPT_WRITEDATA, &buf);
CURLcode rc = curl_easy_perform(h);
curl_easy_cleanup(h);
free(url);
if (rc != CURLE_OK) {
if (buf.d) free(buf.d);
push_value(vm, make_string(""));
break;
}
Value s = make_string(buf.d ? buf.d : "");
if (buf.d) free(buf.d);
push_value(vm, s);
#else
Value v = pop_value(vm); free_value(v);
push_value(vm, make_string(""));
#endif
break;
}

41
src/vm/curl/post.c Normal file
View file

@ -0,0 +1,41 @@
/**
* libcurl POST builtin
*/
case OP_CURL_POST: {
#ifdef FUN_WITH_CURL
Value vbody = pop_value(vm);
Value vurl = pop_value(vm);
char *url = value_to_string_alloc(&vurl);
char *body = value_to_string_alloc(&vbody);
free_value(vurl);
free_value(vbody);
if (!url) { if (body) free(body); push_value(vm, make_string("")); break; }
if (!body) body = strdup("");
FunCurlBuf buf = { NULL, 0 };
CURL *h = curl_easy_init();
if (!h) { free(url); free(body); push_value(vm, make_string("")); break; }
curl_easy_setopt(h, CURLOPT_URL, url);
curl_easy_setopt(h, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(h, CURLOPT_POST, 1L);
curl_easy_setopt(h, CURLOPT_POSTFIELDS, body);
curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, fun_curl_write_cb);
curl_easy_setopt(h, CURLOPT_WRITEDATA, &buf);
CURLcode rc = curl_easy_perform(h);
curl_easy_cleanup(h);
free(url);
free(body);
if (rc != CURLE_OK) {
if (buf.d) free(buf.d);
push_value(vm, make_string(""));
break;
}
Value s = make_string(buf.d ? buf.d : "");
if (buf.d) free(buf.d);
push_value(vm, s);
#else
Value a = pop_value(vm); free_value(a);
Value b = pop_value(vm); free_value(b);
push_value(vm, make_string(""));
#endif
break;
}

22
src/vm/echo.c Normal file
View file

@ -0,0 +1,22 @@
/**
* Implements OP_ECHO: print top-of-stack value without trailing newline.
* Now stores the value into the VM's output buffer and marks it as partial,
* so the CLI can render echo output together with following print output.
*/
case OP_ECHO: {
Value v = pop_value(vm);
Value snap = deep_copy_value(&v);
free_value(v);
if (vm->output_count < OUTPUT_SIZE) {
int idx = vm->output_count;
vm->output[idx] = snap;
vm->output_is_partial[idx] = 1; // ECHO does not end the line
vm->output_count++;
} else {
free_value(snap);
fprintf(stderr, "Runtime error: output buffer overflow\n");
exit(1);
}
break;
}

22
src/vm/ini/free.c Normal file
View file

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

33
src/vm/ini/get_bool.c Normal file
View file

@ -0,0 +1,33 @@
/*
* 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-10 (split from getters.c)
*/
/* OP_INI_GET_BOOL */
#ifdef FUN_WITH_INI
case OP_INI_GET_BOOL: {
Value vdef = pop_value(vm);
Value vkey = pop_value(vm);
Value vsec = pop_value(vm);
Value vh = pop_value(vm);
int def = (vdef.type==VAL_INT||vdef.type==VAL_BOOL) ? (int)vdef.i : 0;
const char *key = (vkey.type==VAL_STRING) ? vkey.s : NULL;
const char *sec = (vsec.type==VAL_STRING) ? vsec.s : NULL;
int h = (vh.type==VAL_INT) ? (int)vh.i : 0;
dictionary *d = ini_get(h);
int outb = def;
if (d && sec && key) {
char full[1024]; ini_make_full_key(full, sizeof(full), sec, key);
outb = iniparser_getboolean(d, full, def);
}
free_value(vdef); free_value(vkey); free_value(vsec); free_value(vh);
push_value(vm, make_int(outb ? 1 : 0));
break;
}
#endif

33
src/vm/ini/get_double.c Normal file
View file

@ -0,0 +1,33 @@
/*
* 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-10 (split from getters.c)
*/
/* OP_INI_GET_DOUBLE */
#ifdef FUN_WITH_INI
case OP_INI_GET_DOUBLE: {
Value vdef = pop_value(vm);
Value vkey = pop_value(vm);
Value vsec = pop_value(vm);
Value vh = pop_value(vm);
double def = (vdef.type==VAL_FLOAT) ? vdef.d : (vdef.type==VAL_INT ? (double)vdef.i : 0.0);
const char *key = (vkey.type==VAL_STRING) ? vkey.s : NULL;
const char *sec = (vsec.type==VAL_STRING) ? vsec.s : NULL;
int h = (vh.type==VAL_INT) ? (int)vh.i : 0;
dictionary *d = ini_get(h);
double outd = def;
if (d && sec && key) {
char full[1024]; ini_make_full_key(full, sizeof(full), sec, key);
outd = iniparser_getdouble(d, full, def);
}
free_value(vdef); free_value(vkey); free_value(vsec); free_value(vh);
push_value(vm, make_float(outd));
break;
}
#endif

33
src/vm/ini/get_int.c Normal file
View file

@ -0,0 +1,33 @@
/*
* 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-10 (split from getters.c)
*/
/* OP_INI_GET_INT */
#ifdef FUN_WITH_INI
case OP_INI_GET_INT: {
Value vdef = pop_value(vm);
Value vkey = pop_value(vm);
Value vsec = pop_value(vm);
Value vh = pop_value(vm);
int def = (vdef.type==VAL_INT) ? (int)vdef.i : 0;
const char *key = (vkey.type==VAL_STRING) ? vkey.s : NULL;
const char *sec = (vsec.type==VAL_STRING) ? vsec.s : NULL;
int h = (vh.type==VAL_INT) ? (int)vh.i : 0;
dictionary *d = ini_get(h);
int outi = def;
if (d && sec && key) {
char full[1024]; ini_make_full_key(full, sizeof(full), sec, key);
outi = iniparser_getint(d, full, def);
}
free_value(vdef); free_value(vkey); free_value(vsec); free_value(vh);
push_value(vm, make_int(outi));
break;
}
#endif

34
src/vm/ini/get_string.c Normal file
View file

@ -0,0 +1,34 @@
/*
* 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-10 (split from getters.c)
*/
/* OP_INI_GET_STRING */
#ifdef FUN_WITH_INI
case OP_INI_GET_STRING: {
Value vdef = pop_value(vm);
Value vkey = pop_value(vm);
Value vsec = pop_value(vm);
Value vh = pop_value(vm);
const char *def = (vdef.type==VAL_STRING && vdef.s) ? vdef.s : "";
const char *key = (vkey.type==VAL_STRING) ? vkey.s : NULL;
const char *sec = (vsec.type==VAL_STRING) ? vsec.s : NULL;
int h = (vh.type==VAL_INT) ? (int)vh.i : 0;
dictionary *d = ini_get(h);
const char *res = def;
if (d && sec && key) {
char full[1024]; ini_make_full_key(full, sizeof(full), sec, key);
const char *s = iniparser_getstring(d, full, def);
res = s ? s : "";
}
free_value(vdef); free_value(vkey); free_value(vsec); free_value(vh);
push_value(vm, make_string(res));
break;
}
#endif

61
src/vm/ini/handles.h Normal file
View file

@ -0,0 +1,61 @@
/*
* 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-11-30
*/
/** INI handle registry for iniparser 4.2.6 */
#pragma once
#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 <stdio.h> /* snprintf for helper */
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;
}
/* 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);
}
#endif /* FUN_WITH_INI */

29
src/vm/ini/load.c Normal file
View file

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

28
src/vm/ini/save.c Normal file
View file

@ -0,0 +1,28 @@
/*
* 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-10 (split from set_unset_save.c)
*/
/* OP_INI_SAVE */
#ifdef FUN_WITH_INI
case OP_INI_SAVE: {
Value vpath = pop_value(vm);
Value vh = pop_value(vm);
const char *path = (vpath.type==VAL_STRING)?vpath.s:NULL;
dictionary *d = ini_get((vh.type==VAL_INT)?(int)vh.i:0);
int ok = 0;
if (d && path) {
FILE *f = fopen(path, "w");
if (f) { iniparser_dump_ini(d, f); fclose(f); ok = 1; }
}
free_value(vpath); free_value(vh);
push_value(vm, make_int(ok));
break;
}
#endif

36
src/vm/ini/set.c Normal file
View file

@ -0,0 +1,36 @@
/*
* 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-10 (split from set_unset_save.c)
*/
/* OP_INI_SET */
#ifdef FUN_WITH_INI
case OP_INI_SET: {
Value vval = pop_value(vm);
Value vkey = pop_value(vm);
Value vsec = pop_value(vm);
Value vh = pop_value(vm);
dictionary *d = ini_get((vh.type==VAL_INT)?(int)vh.i:0);
const char *key = (vkey.type==VAL_STRING)?vkey.s:NULL;
const char *sec = (vsec.type==VAL_STRING)?vsec.s:NULL;
int ok = 0;
if (d && sec && key) {
char *valstr = value_to_string_alloc(&vval);
if (valstr) {
char full[1024]; snprintf(full, sizeof(full), "%s:%s", sec, key);
/* iniparser 4.x does not expose iniparser_set; use dictionary_set */
if (dictionary_set(d, full, valstr) == 0) ok = 1; /* 0 means success */
free(valstr);
}
}
free_value(vval); free_value(vkey); free_value(vsec); free_value(vh);
push_value(vm, make_int(ok));
break;
}
#endif

32
src/vm/ini/unset.c Normal file
View file

@ -0,0 +1,32 @@
/*
* 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-10 (split from set_unset_save.c)
*/
/* OP_INI_UNSET */
#ifdef FUN_WITH_INI
case OP_INI_UNSET: {
Value vkey = pop_value(vm);
Value vsec = pop_value(vm);
Value vh = pop_value(vm);
dictionary *d = ini_get((vh.type==VAL_INT)?(int)vh.i:0);
const char *key = (vkey.type==VAL_STRING)?vkey.s:NULL;
const char *sec = (vsec.type==VAL_STRING)?vsec.s:NULL;
int ok = 0;
if (d && sec && key) {
char full[1024]; snprintf(full, sizeof(full), "%s:%s", sec, key);
/* iniparser 4.2.6 dictionary_unset returns void; assume success if inputs are valid */
dictionary_unset(d, full);
ok = 1;
}
free_value(vkey); free_value(vsec); free_value(vh);
push_value(vm, make_int(ok));
break;
}
#endif

30
src/vm/json/from_file.c Normal file
View file

@ -0,0 +1,30 @@
/**
* 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-11-24
*/
/* JSON_FROM_FILE */
case OP_JSON_FROM_FILE: {
#ifdef FUN_WITH_JSON
Value vpath = pop_value(vm);
char *path = value_to_string_alloc(&vpath);
free_value(vpath);
if (!path) { push_value(vm, make_nil()); break; }
json_object *root = json_object_from_file(path);
free(path);
if (!root) { push_value(vm, make_nil()); break; }
Value v = json_to_fun(root);
push_value(vm, v);
json_object_put(root);
#else
Value vpath = pop_value(vm); free_value(vpath);
push_value(vm, make_nil());
#endif
break;
}

38
src/vm/json/parse.c Normal file
View file

@ -0,0 +1,38 @@
/**
* 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-11-24
*/
/* JSON_PARSE */
case OP_JSON_PARSE: {
#ifdef FUN_WITH_JSON
Value text = pop_value(vm);
char *s = value_to_string_alloc(&text);
free_value(text);
if (!s) { push_value(vm, make_nil()); break; }
struct json_tokener *tok = json_tokener_new();
json_object *root = json_tokener_parse_ex(tok, s, (int)strlen(s));
enum json_tokener_error jerr = json_tokener_get_error(tok);
json_tokener_free(tok);
free(s);
if (jerr != json_tokener_success) {
push_value(vm, make_nil());
} else {
Value v = json_to_fun(root);
push_value(vm, v);
json_object_put(root);
}
#else
/* Fallback when JSON is disabled: consume arg, push Nil */
Value drop = pop_value(vm);
free_value(drop);
push_value(vm, make_nil());
#endif
break;
}

32
src/vm/json/stringify.c Normal file
View file

@ -0,0 +1,32 @@
/**
* 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-11-24
*/
/* JSON_STRINGIFY */
case OP_JSON_STRINGIFY: {
#ifdef FUN_WITH_JSON
Value vpretty = pop_value(vm);
Value any = pop_value(vm);
int pretty = (vpretty.type == VAL_BOOL || vpretty.type == VAL_INT) ? (vpretty.i != 0) : 0;
json_object *j = fun_to_json(&any);
int flags = pretty ? JSON_C_TO_STRING_PRETTY : JSON_C_TO_STRING_PLAIN;
const char *js = json_object_to_json_string_ext(j, flags);
push_value(vm, make_string(js ? js : ""));
json_object_put(j);
free_value(vpretty);
free_value(any);
#else
/* Fallback: consume two args, push "null" */
Value vpretty = pop_value(vm); free_value(vpretty);
Value any = pop_value(vm); free_value(any);
push_value(vm, make_string("null"));
#endif
break;
}

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