Compare commits
55 commits
0fa6d56c14
...
d2fca3275a
| Author | SHA1 | Date | |
|---|---|---|---|
| d2fca3275a | |||
| b16a42a4a7 | |||
| 87f540797b | |||
| 1eacf9411c | |||
| ea6553fb19 | |||
| 5b90ff497c | |||
| 3f586a90e1 | |||
| 4039abd10d | |||
| 40584f27f5 | |||
| 8b9b9249fd | |||
| 2927f88340 | |||
| ebae80cf44 | |||
| b2986075cb | |||
| 26ddaf161e | |||
| ccd03d3bec | |||
| 3722df654b | |||
| 9fabf86e61 | |||
| b1e1160bff | |||
| 9dc8afc387 | |||
| 40d114cb9c | |||
| 6da1861e12 | |||
| 682bce05af | |||
| e482ec198a | |||
| 66bed28023 | |||
| db450a871c | |||
| e65756226d | |||
| ecf64d6ac2 | |||
| 8fa142e763 | |||
| fb5670a699 | |||
| 49261b96c2 | |||
| f05a3d3cde | |||
| ff01ebc8dc | |||
| c57b1cac30 | |||
| c22c35d07e | |||
| 424d2fd9df | |||
| 7ea3c9108a | |||
| 11bfb8ee05 | |||
| 56712206d1 | |||
| 9416ec3457 | |||
| 3411a4d846 | |||
| e86062aaad | |||
| 7659957c1f | |||
| 4d82d73515 | |||
| bb848b6679 | |||
| d9804d9bf8 | |||
| 5c3d4f1247 | |||
| 485477960b | |||
| d5c7b91eb3 | |||
| 786f1ddc06 | |||
| 2312b9d0c3 | |||
| 685c84c077 | |||
| 56e3e22f06 | |||
| 9c098eb92b | |||
| d028434406 | |||
| afb0e8cd3a |
125 changed files with 7198 additions and 514 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -5,8 +5,11 @@ CMakeCache.txt
|
|||
.venv
|
||||
build*
|
||||
cmake*
|
||||
database.sqlite
|
||||
demo_*
|
||||
dist/
|
||||
downloaded.png
|
||||
json.xml
|
||||
lib/*.so
|
||||
out/
|
||||
src/*.o
|
||||
|
|
|
|||
393
CMakeLists.txt
393
CMakeLists.txt
|
|
@ -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
129
README.md
|
|
@ -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
373
SEMANTIC_VERSIONING.md
Normal 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.
|
||||
|
||||
Backus–Naur 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
200
demo.fun
|
|
@ -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! ===")
|
||||
592
docs/handbook.md
592
docs/handbook.md
|
|
@ -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 isn’t 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
41
examples/byte_overflow_try_catch.fun
Executable file
41
examples/byte_overflow_try_catch.fun
Executable 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
|
||||
*/
|
||||
163
examples/conversions_showcase.fun
Normal file
163
examples/conversions_showcase.fun
Normal 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
54
examples/crc32_example.fun
Executable 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
54
examples/crc32c_example.fun
Executable 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
24
examples/curl_download.fun
Executable 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
25
examples/curl_get_json.fun
Executable 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
32
examples/curl_post.fun
Executable 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
32
examples/data/catalog.xml
Normal 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
32
examples/data/complex.ini
Normal 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"
|
||||
|
||||
|
||||
53
examples/data/complex.json
Normal file
53
examples/data/complex.json
Normal 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
|
||||
}
|
||||
12
examples/data/database.sql
Normal file
12
examples/data/database.sql
Normal 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);
|
||||
22
examples/data/employees.xml
Normal file
22
examples/data/employees.xml
Normal 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
24
examples/data/example.xml
Normal 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>
|
||||
11
examples/data/ns_example.xml
Normal file
11
examples/data/ns_example.xml
Normal 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>
|
||||
25
examples/data/subsections.ini
Normal file
25
examples/data/subsections.ini
Normal 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
66
examples/datetime_extended.fun
Executable 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
31
examples/datetime_timer.fun
Executable 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
23
examples/echo_example.fun
Executable 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!
|
||||
|
|
@ -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
33
examples/extra/pcre2_demo.fun
Executable 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
0
examples/include_local_util.fun
Normal file → Executable file
46
examples/ini_class_demo.fun
Executable file
46
examples/ini_class_demo.fun
Executable 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
75
examples/ini_complex.fun
Executable 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
30
examples/ini_demo.fun
Executable 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
70
examples/ini_subsections.fun
Executable 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
65
examples/json_showcase.fun
Executable 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
59
examples/libsql_example.fun
Executable 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
|
||||
*/
|
||||
|
|
@ -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
140
examples/pcre2_opcodes.fun
Executable 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; we’ll 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
32
examples/pcre2_showcase.fun
Executable 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
81
examples/random_demo.fun
Executable 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
|
||||
*/
|
||||
50
examples/random_number_example.fun
Executable file
50
examples/random_number_example.fun
Executable 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
48
examples/sqlite_example.fun
Executable 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
29
examples/tk_hello.fun
Executable 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
97
examples/xml_access_catalog.fun
Executable 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
|
||||
*/
|
||||
81
examples/xml_access_employees.fun
Executable file
81
examples/xml_access_employees.fun
Executable 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
60
examples/xml_access_ns.fun
Executable 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
110
examples/xml_class_example.fun
Executable 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
24
examples/xml_minimal.fun
Executable 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
156
lib/crypt/crc32.fun
Normal 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
156
lib/crypt/crc32c.fun
Normal 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
96
lib/io/ini.fun
Normal 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
32
lib/io/json.fun
Normal 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)
|
||||
|
|
@ -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
57
lib/io/xml.fun
Normal 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
42
lib/regex/pcre2.fun
Normal 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
36
lib/ui/tk.fun
Normal 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()
|
||||
|
|
@ -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
165
make
Executable 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
199
play.fun
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 "???";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
29
src/ext/curl.c
Normal 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
28
src/ext/ini.c
Normal 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
107
src/ext/json.c
Normal 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
55
src/ext/libsql.c
Normal 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
22
src/ext/pcre2.c
Normal 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
76
src/ext/pcsc.c
Normal 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
49
src/ext/sqlite.c
Normal 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
69
src/ext/tcltk.c
Normal 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
57
src/ext/xml2.c
Normal 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
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
565
src/parser.c
565
src/parser.c
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
74
src/pcsc.c
74
src/pcsc.c
|
|
@ -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 */
|
||||
|
|
@ -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
135
src/vm.c
|
|
@ -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"
|
||||
|
|
|
|||
19
src/vm.h
19
src/vm.h
|
|
@ -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
33
src/vm/core/throw.c
Normal 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
13
src/vm/core/try_pop.c
Normal 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
18
src/vm/core/try_push.c
Normal 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
47
src/vm/curl/download.c
Normal 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
33
src/vm/curl/get.c
Normal 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
41
src/vm/curl/post.c
Normal 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
22
src/vm/echo.c
Normal 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
22
src/vm/ini/free.c
Normal 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
33
src/vm/ini/get_bool.c
Normal 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
33
src/vm/ini/get_double.c
Normal 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
33
src/vm/ini/get_int.c
Normal 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
34
src/vm/ini/get_string.c
Normal 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
61
src/vm/ini/handles.h
Normal 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
29
src/vm/ini/load.c
Normal 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
28
src/vm/ini/save.c
Normal 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
36
src/vm/ini/set.c
Normal 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
32
src/vm/ini/unset.c
Normal 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
30
src/vm/json/from_file.c
Normal 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
38
src/vm/json/parse.c
Normal 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
32
src/vm/json/stringify.c
Normal 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
Loading…
Add table
Add a link
Reference in a new issue