Compare commits
14 commits
85b8d37b23
...
d5f4a51d12
| Author | SHA1 | Date | |
|---|---|---|---|
| d5f4a51d12 | |||
| b58b7a8222 | |||
| c64d87f877 | |||
| c05b18b914 | |||
| d58bed053e | |||
| 501be417e8 | |||
| a63aff3daa | |||
| b2ed368ffc | |||
| c9dcbfe96d | |||
| c6aef6f290 | |||
| db68b30d5b | |||
| 2fa8f8653e | |||
| ee8db4da9b | |||
| 75538e6142 |
85 changed files with 1663 additions and 800 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -4,7 +4,7 @@ CMakeCache.txt
|
|||
!.gitignore
|
||||
.venv
|
||||
build*
|
||||
cmake*
|
||||
cmake_install.cmake
|
||||
database.sqlite
|
||||
demo_*
|
||||
dist/
|
||||
|
|
|
|||
577
CMakeLists.txt
577
CMakeLists.txt
|
|
@ -1,543 +1,15 @@
|
|||
cmake_minimum_required(VERSION 3.10)
|
||||
project(fun VERSION 0.37.48 LANGUAGES C)
|
||||
project(fun VERSION 0.37.57 LANGUAGES C)
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
# Defaults and options migrated from Makefile
|
||||
# Convenience: default path to bundled stdlib
|
||||
set(FUN_LIB "${CMAKE_SOURCE_DIR}/lib" CACHE PATH "Path to bundled Fun stdlib")
|
||||
|
||||
# Optional: override default library directory for #include <...> lookups
|
||||
# -DDEFAULT_LIB_DIR=/custom/fun/lib (trailing slash added automatically)
|
||||
if(NOT DEFINED DEFAULT_LIB_DIR OR DEFAULT_LIB_DIR STREQUAL "")
|
||||
if(WIN32)
|
||||
set(_FUN_DEFAULT_LIB_DIR "C:/Users/Public/fun/lib")
|
||||
elseif(APPLE)
|
||||
set(_FUN_DEFAULT_LIB_DIR "/Library/Application Support/fun/lib")
|
||||
else()
|
||||
set(_FUN_DEFAULT_LIB_DIR "/usr/share/fun/lib")
|
||||
endif()
|
||||
set(DEFAULT_LIB_DIR "${_FUN_DEFAULT_LIB_DIR}" CACHE PATH "Default library directory for Fun stdlib (override with -DDEFAULT_LIB_DIR=...)" FORCE)
|
||||
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}/")
|
||||
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)
|
||||
option(FUN_WITH_PCSC "Enable PCSC (pcsclite) support" OFF)
|
||||
set(PCSC_LINK_LIBS "")
|
||||
set(PCSC_INCLUDE_DIRS "")
|
||||
if(FUN_WITH_PCSC)
|
||||
message(STATUS "Building with PCSC support")
|
||||
add_definitions(-DFUN_WITH_PCSC)
|
||||
if(APPLE)
|
||||
list(APPEND PCSC_LINK_LIBS "-framework PCSC")
|
||||
else()
|
||||
list(APPEND PCSC_INCLUDE_DIRS "/usr/include/PCSC")
|
||||
list(APPEND PCSC_LINK_LIBS pcsclite)
|
||||
endif()
|
||||
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)
|
||||
|
||||
# Threads (used by demos and optionally by the runtime)
|
||||
if(UNIX)
|
||||
find_package(Threads QUIET)
|
||||
endif()
|
||||
|
||||
# Core VM/library sources
|
||||
add_library(fun_core
|
||||
src/bytecode.c
|
||||
src/parser.c
|
||||
src/value.c
|
||||
src/vm.c
|
||||
)
|
||||
|
||||
target_include_directories(fun_core PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
)
|
||||
|
||||
# Apply options to core
|
||||
if(FUN_DEBUG)
|
||||
message(STATUS "FUN_DEBUG enabled: building with verbose debug logging")
|
||||
target_compile_definitions(fun_core PUBLIC FUN_VERSION="${PROJECT_VERSION}")
|
||||
target_compile_definitions(fun_core PUBLIC FUN_DEBUG=1)
|
||||
endif()
|
||||
|
||||
# Provide default stdlib directory to the runtime
|
||||
target_compile_definitions(fun_core PUBLIC FUN_VERSION="${PROJECT_VERSION}")
|
||||
target_compile_definitions(fun_core PUBLIC DEFAULT_LIB_DIR="${DEFAULT_LIB_DIR}")
|
||||
|
||||
# PCSC include and link (if enabled)
|
||||
if(PCSC_INCLUDE_DIRS)
|
||||
target_include_directories(fun_core PRIVATE ${PCSC_INCLUDE_DIRS})
|
||||
endif()
|
||||
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)
|
||||
endif()
|
||||
|
||||
# Link libm for C99 math functions if available
|
||||
find_library(M_LIB m)
|
||||
if(M_LIB)
|
||||
target_link_libraries(fun_core PUBLIC ${M_LIB})
|
||||
endif()
|
||||
|
||||
# Optional Notcurses TUI support (per-opcode files under src/vm/notcurses)
|
||||
option(FUN_WITH_NOTCURSES "Enable Notcurses TUI support" OFF)
|
||||
set(NOTCURSES_INCLUDE_DIRS "")
|
||||
set(NOTCURSES_LINK_LIBS "")
|
||||
if(FUN_WITH_NOTCURSES)
|
||||
message(STATUS "FUN_WITH_NOTCURSES=ON: enabling Notcurses TUI ops")
|
||||
add_definitions(-DFUN_WITH_NOTCURSES)
|
||||
find_package(PkgConfig QUIET)
|
||||
if(PkgConfig_FOUND)
|
||||
# Prefer full 'notcurses', fall back to 'notcurses-core'
|
||||
pkg_check_modules(NOTCURSES QUIET notcurses)
|
||||
if(NOT NOTCURSES_FOUND)
|
||||
pkg_check_modules(NOTCURSES QUIET notcurses-core)
|
||||
endif()
|
||||
endif()
|
||||
if(NOTCURSES_FOUND)
|
||||
list(APPEND NOTCURSES_INCLUDE_DIRS ${NOTCURSES_INCLUDE_DIRS} ${NOTCURSES_INCLUDE_DIRS})
|
||||
list(APPEND NOTCURSES_LINK_LIBS ${NOTCURSES_LINK_LIBS} ${NOTCURSES_LIBRARIES})
|
||||
include_directories(${NOTCURSES_INCLUDE_DIRS})
|
||||
else()
|
||||
message(FATAL_ERROR "FUN_WITH_NOTCURSES=ON but notcurses was not found via pkg-config (tried notcurses and notcurses-core). Install notcurses or configure with -DFUN_WITH_NOTCURSES=OFF")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# notcurses include and link (if enabled)
|
||||
if(NOTCURSES_INCLUDE_DIRS)
|
||||
target_include_directories(fun_core PRIVATE ${NOTCURSES_INCLUDE_DIRS})
|
||||
endif()
|
||||
if(NOTCURSES_LINK_LIBS)
|
||||
target_link_libraries(fun_core PUBLIC ${NOTCURSES_LINK_LIBS})
|
||||
endif()
|
||||
|
||||
# Interpreter /usr/bin/fun
|
||||
option(FUN_WITH_REPL "Enable interactive REPL in the fun CLI" OFF)
|
||||
add_executable(fun
|
||||
src/fun.c
|
||||
)
|
||||
# Provide version string to the CLI
|
||||
if(FUN_WITH_REPL)
|
||||
# Enable REPL compilation path and add the REPL source
|
||||
target_compile_definitions(fun PRIVATE FUN_WITH_REPL=1)
|
||||
target_sources(fun PRIVATE src/repl.c)
|
||||
endif()
|
||||
target_link_libraries(fun PRIVATE fun_core)
|
||||
|
||||
# Internal test programs
|
||||
add_executable(fun_test
|
||||
src/fun_test.c
|
||||
)
|
||||
target_link_libraries(fun_test PRIVATE fun_core)
|
||||
|
||||
add_executable(test_opcodes
|
||||
src/test_opcodes.c
|
||||
)
|
||||
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()
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/Options.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/Dependencies.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/Extensions.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/Targets.cmake)
|
||||
|
||||
# Convenience aggregate target (like 'build' in Makefile)
|
||||
add_custom_target(build
|
||||
|
|
@ -549,11 +21,11 @@ set(FUN_RUN_SCRIPT "" CACHE STRING "Script to run with the 'run' target, e.g. -D
|
|||
|
||||
if(FUN_WITH_REPL)
|
||||
add_custom_target(repl
|
||||
COMMAND $<TARGET_FILE:fun>
|
||||
DEPENDS fun
|
||||
USES_TERMINAL
|
||||
COMMENT "Run Fun REPL"
|
||||
)
|
||||
COMMAND $<TARGET_FILE:fun>
|
||||
DEPENDS fun
|
||||
USES_TERMINAL
|
||||
COMMENT "Run Fun REPL"
|
||||
)
|
||||
endif()
|
||||
|
||||
add_custom_target(run
|
||||
|
|
@ -563,21 +35,6 @@ add_custom_target(run
|
|||
COMMENT "Run Fun with script: ${FUN_RUN_SCRIPT}"
|
||||
)
|
||||
|
||||
add_custom_target(threads-demo
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "Running Thread class demo with FUN_LIB_DIR=${FUN_LIB}"
|
||||
COMMAND ${CMAKE_COMMAND} -E env FUN_LIB_DIR="${FUN_LIB}" $<TARGET_FILE:fun> ${CMAKE_SOURCE_DIR}/examples/threads_demo.fun
|
||||
DEPENDS fun
|
||||
USES_TERMINAL
|
||||
)
|
||||
|
||||
# Python for helper scripts
|
||||
find_package(Python3 QUIET COMPONENTS Interpreter)
|
||||
if(Python3_Interpreter_FOUND)
|
||||
set(_FUN_PY "${Python3_EXECUTABLE}")
|
||||
else()
|
||||
set(_FUN_PY "python3")
|
||||
endif()
|
||||
|
||||
add_custom_target(ops
|
||||
COMMAND ${_FUN_PY} ${CMAKE_SOURCE_DIR}/scripts/check_op_includes.py --verbose
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
|
|
@ -592,20 +49,6 @@ add_custom_target(ops-quiet
|
|||
COMMENT "Opcode include check"
|
||||
)
|
||||
|
||||
add_custom_target(examples
|
||||
COMMAND ${CMAKE_COMMAND} -E env FUN_LIB_DIR="${FUN_LIB}" FUN_WITH_INI=$<IF:$<BOOL:${FUN_WITH_INI}>,1,0> ${CMAKE_SOURCE_DIR}/scripts/run_examples.sh
|
||||
DEPENDS fun
|
||||
USES_TERMINAL
|
||||
COMMENT "Build and run all examples"
|
||||
)
|
||||
|
||||
add_custom_target(run-examples
|
||||
COMMAND ${CMAKE_COMMAND} -E env FUN_LIB_DIR="${FUN_LIB}" FUN_WITH_INI=$<IF:$<BOOL:${FUN_WITH_INI}>,1,0> ${CMAKE_SOURCE_DIR}/scripts/run_examples.sh
|
||||
DEPENDS fun
|
||||
USES_TERMINAL
|
||||
COMMENT "Build and run all examples"
|
||||
)
|
||||
|
||||
# Clean and distclean
|
||||
add_custom_target(fun_clean
|
||||
COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target clean
|
||||
|
|
|
|||
14
cmake/Dependencies.cmake
Normal file
14
cmake/Dependencies.cmake
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Common dependency discovery used across the project
|
||||
|
||||
# Threads (used by demos and optionally by the runtime)
|
||||
if(UNIX)
|
||||
find_package(Threads QUIET)
|
||||
endif()
|
||||
|
||||
# Python for helper scripts
|
||||
find_package(Python3 QUIET COMPONENTS Interpreter)
|
||||
if(Python3_Interpreter_FOUND)
|
||||
set(_FUN_PY "${Python3_EXECUTABLE}")
|
||||
else()
|
||||
set(_FUN_PY "python3")
|
||||
endif()
|
||||
17
cmake/Extensions/CURL.cmake
Normal file
17
cmake/Extensions/CURL.cmake
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# CURL
|
||||
option(FUN_WITH_CURL "Enable libcurl HTTP client support" OFF)
|
||||
set(CURL_INCLUDE_DIRS "")
|
||||
set(CURL_LINK_LIBS "")
|
||||
if(FUN_WITH_CURL)
|
||||
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})
|
||||
else()
|
||||
list(APPEND CURL_LINK_LIBS curl)
|
||||
endif()
|
||||
endif()
|
||||
39
cmake/Extensions/Extensions.cmake
Normal file
39
cmake/Extensions/Extensions.cmake
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# Toggleable optional features live in per-file modules. These populate *_INCLUDE_DIRS and *_LINK_LIBS
|
||||
list(PREPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/Modules")
|
||||
|
||||
# Helper to report feature status nicely
|
||||
function(_fun_print_feature name flag)
|
||||
if(${flag})
|
||||
message(STATUS " ${name}: ENABLED")
|
||||
else()
|
||||
message(STATUS " ${name}: DISABLED")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Include each extension module
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/TCLTK.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/SQLITE.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/PCSC.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/JSON.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/INI.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/XML2.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/LIBSQL.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/PCRE2.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/CURL.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/NOTCURSES.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/REPL.cmake)
|
||||
|
||||
# Summary of extension toggles
|
||||
message(STATUS "---- Fun extension summary ----")
|
||||
_fun_print_feature("Tcl/Tk (FUN_WITH_TCLTK)" FUN_WITH_TCLTK)
|
||||
_fun_print_feature("SQLite (FUN_WITH_SQLITE)" FUN_WITH_SQLITE)
|
||||
_fun_print_feature("PCSC (FUN_WITH_PCSC)" FUN_WITH_PCSC)
|
||||
_fun_print_feature("JSON-C (FUN_WITH_JSON)" FUN_WITH_JSON)
|
||||
_fun_print_feature("INI/iniparser (FUN_WITH_INI)" FUN_WITH_INI)
|
||||
_fun_print_feature("libxml2 (FUN_WITH_XML2)" FUN_WITH_XML2)
|
||||
_fun_print_feature("libsql (FUN_WITH_LIBSQL)" FUN_WITH_LIBSQL)
|
||||
_fun_print_feature("PCRE2 (FUN_WITH_PCRE2)" FUN_WITH_PCRE2)
|
||||
_fun_print_feature("libcurl (FUN_WITH_CURL)" FUN_WITH_CURL)
|
||||
_fun_print_feature("Notcurses (FUN_WITH_NOTCURSES)" FUN_WITH_NOTCURSES)
|
||||
_fun_print_feature("REPL (FUN_WITH_REPL)" FUN_WITH_REPL)
|
||||
message(STATUS "--------------------------------")
|
||||
27
cmake/Extensions/INI.cmake
Normal file
27
cmake/Extensions/INI.cmake
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# INI (iniparser)
|
||||
option(FUN_WITH_INI "Enable INI (iniparser) support" OFF)
|
||||
set(INIPARSER_INCLUDE_DIRS "")
|
||||
set(INIPARSER_LINK_LIBS "")
|
||||
if(FUN_WITH_INI)
|
||||
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()
|
||||
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()
|
||||
17
cmake/Extensions/JSON.cmake
Normal file
17
cmake/Extensions/JSON.cmake
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# JSON (json-c)
|
||||
option(FUN_WITH_JSON "Enable JSON (json-c) support" OFF)
|
||||
set(JSONC_INCLUDE_DIRS "")
|
||||
set(JSONC_LINK_LIBS "")
|
||||
if(FUN_WITH_JSON)
|
||||
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})
|
||||
else()
|
||||
list(APPEND JSONC_LINK_LIBS json-c)
|
||||
endif()
|
||||
endif()
|
||||
27
cmake/Extensions/LIBSQL.cmake
Normal file
27
cmake/Extensions/LIBSQL.cmake
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# libsql
|
||||
option(FUN_WITH_LIBSQL "Enable libsql (Turso) client support" OFF)
|
||||
set(LIBSQL_INCLUDE_DIRS "")
|
||||
set(LIBSQL_LINK_LIBS "")
|
||||
if(FUN_WITH_LIBSQL)
|
||||
add_definitions(-DFUN_WITH_LIBSQL)
|
||||
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(NOT LIBSQL_FOUND)
|
||||
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()
|
||||
20
cmake/Extensions/NOTCURSES.cmake
Normal file
20
cmake/Extensions/NOTCURSES.cmake
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Notcurses
|
||||
option(FUN_WITH_NOTCURSES "Enable Notcurses TUI support" OFF)
|
||||
set(NOTCURSES_INCLUDE_DIRS "")
|
||||
set(NOTCURSES_LINK_LIBS "")
|
||||
if(FUN_WITH_NOTCURSES)
|
||||
add_definitions(-DFUN_WITH_NOTCURSES)
|
||||
find_package(PkgConfig QUIET)
|
||||
if(PkgConfig_FOUND)
|
||||
pkg_check_modules(NOTCURSES QUIET notcurses)
|
||||
if(NOT NOTCURSES_FOUND)
|
||||
pkg_check_modules(NOTCURSES QUIET notcurses-core)
|
||||
endif()
|
||||
endif()
|
||||
if(NOT NOTCURSES_FOUND)
|
||||
message(FATAL_ERROR "FUN_WITH_NOTCURSES=ON but notcurses was not found via pkg-config (tried notcurses and notcurses-core). Install notcurses or configure with -DFUN_WITH_NOTCURSES=OFF")
|
||||
else()
|
||||
list(APPEND NOTCURSES_INCLUDE_DIRS ${NOTCURSES_INCLUDE_DIRS} ${NOTCURSES_INCLUDE_DIRS})
|
||||
list(APPEND NOTCURSES_LINK_LIBS ${NOTCURSES_LINK_LIBS} ${NOTCURSES_LIBRARIES})
|
||||
endif()
|
||||
endif()
|
||||
17
cmake/Extensions/PCRE2.cmake
Normal file
17
cmake/Extensions/PCRE2.cmake
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# PCRE2
|
||||
option(FUN_WITH_PCRE2 "Enable PCRE2 (Perl Compatible Regular Expressions) support" OFF)
|
||||
set(PCRE2_INCLUDE_DIRS "")
|
||||
set(PCRE2_LINK_LIBS "")
|
||||
if(FUN_WITH_PCRE2)
|
||||
add_definitions(-DFUN_WITH_PCRE2)
|
||||
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})
|
||||
else()
|
||||
list(APPEND PCRE2_LINK_LIBS pcre2-8)
|
||||
endif()
|
||||
endif()
|
||||
13
cmake/Extensions/PCSC.cmake
Normal file
13
cmake/Extensions/PCSC.cmake
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# PCSC
|
||||
option(FUN_WITH_PCSC "Enable PCSC (pcsclite) support" OFF)
|
||||
set(PCSC_LINK_LIBS "")
|
||||
set(PCSC_INCLUDE_DIRS "")
|
||||
if(FUN_WITH_PCSC)
|
||||
add_definitions(-DFUN_WITH_PCSC)
|
||||
if(APPLE)
|
||||
list(APPEND PCSC_LINK_LIBS "-framework PCSC")
|
||||
else()
|
||||
list(APPEND PCSC_INCLUDE_DIRS "/usr/include/PCSC")
|
||||
list(APPEND PCSC_LINK_LIBS pcsclite)
|
||||
endif()
|
||||
endif()
|
||||
2
cmake/Extensions/REPL.cmake
Normal file
2
cmake/Extensions/REPL.cmake
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# REPL toggle (affects sources of the fun executable)
|
||||
option(FUN_WITH_REPL "Enable interactive REPL in the fun CLI" OFF)
|
||||
22
cmake/Extensions/SQLITE.cmake
Normal file
22
cmake/Extensions/SQLITE.cmake
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# SQLite
|
||||
option(FUN_WITH_SQLITE "Enable SQLite (sqlite3) support" OFF)
|
||||
set(SQLITE3_INCLUDE_DIRS "")
|
||||
set(SQLITE3_LINK_LIBS "")
|
||||
if(FUN_WITH_SQLITE)
|
||||
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})
|
||||
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()
|
||||
46
cmake/Extensions/TCLTK.cmake
Normal file
46
cmake/Extensions/TCLTK.cmake
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Tcl/Tk GUI support
|
||||
option(FUN_WITH_TCLTK "Enable Tcl/Tk GUI support" OFF)
|
||||
set(TCL_INCLUDE_DIRS "")
|
||||
set(TCL_LINK_LIBS "")
|
||||
if(FUN_WITH_TCLTK)
|
||||
add_definitions(-DFUN_WITH_TCLTK)
|
||||
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()
|
||||
else()
|
||||
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})
|
||||
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)
|
||||
# macOS frameworks often unnecessary when using Homebrew tcl/tk
|
||||
elseif(WIN32)
|
||||
list(APPEND TCL_LINK_LIBS user32 gdi32 comctl32)
|
||||
else()
|
||||
find_package(X11 QUIET)
|
||||
if(X11_FOUND)
|
||||
list(APPEND TCL_INCLUDE_DIRS ${X11_INCLUDE_DIR})
|
||||
list(APPEND TCL_LINK_LIBS ${X11_LIBRARIES})
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
25
cmake/Extensions/XML2.cmake
Normal file
25
cmake/Extensions/XML2.cmake
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# XML (libxml2)
|
||||
option(FUN_WITH_XML2 "Enable XML (libxml2) support" OFF)
|
||||
set(LIBXML2_INCLUDE_DIRS "")
|
||||
set(LIBXML2_LINK_LIBS "")
|
||||
if(FUN_WITH_XML2)
|
||||
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})
|
||||
else()
|
||||
find_library(LIBXML2_LIB NAMES xml2 libxml2)
|
||||
if(LIBXML2_LIB)
|
||||
list(APPEND LIBXML2_LINK_LIBS ${LIBXML2_LIB})
|
||||
if(EXISTS "/usr/include/libxml2")
|
||||
list(APPEND LIBXML2_INCLUDE_DIRS "/usr/include/libxml2")
|
||||
endif()
|
||||
else()
|
||||
message(FATAL_ERROR "libxml2 not found. Install libxml2 (dev headers) or disable FUN_WITH_XML2.")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
35
cmake/Options.cmake
Normal file
35
cmake/Options.cmake
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# Global options and defaults
|
||||
|
||||
# Convenience: default path to bundled stdlib
|
||||
set(FUN_LIB "${CMAKE_SOURCE_DIR}/lib" CACHE PATH "Path to bundled Fun stdlib")
|
||||
|
||||
# Optional: build using musl libc instead of glibc on Linux (OFF by default)
|
||||
option(FUN_USE_MUSL "Use musl libc toolchain when available (Linux only)" OFF)
|
||||
|
||||
# Platform-specific configuration (DEFAULT_LIB_DIR defaults, libc defines/toolchain tweaks)
|
||||
if(WIN32)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/Platform/Windows.cmake)
|
||||
elseif(APPLE)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/Platform/macOS.cmake)
|
||||
elseif(UNIX)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/Platform/Linux.cmake)
|
||||
endif()
|
||||
|
||||
# If user provided DEFAULT_LIB_DIR, keep it; otherwise it should be set by platform include above
|
||||
set(DEFAULT_LIB_DIR "${DEFAULT_LIB_DIR}" CACHE PATH "Default library directory for Fun stdlib (override with -DDEFAULT_LIB_DIR=...)" FORCE)
|
||||
|
||||
# Ensure trailing slash for DEFAULT_LIB_DIR if it is defined
|
||||
if(DEFINED DEFAULT_LIB_DIR AND NOT DEFAULT_LIB_DIR STREQUAL "")
|
||||
if(NOT DEFAULT_LIB_DIR MATCHES "/$")
|
||||
set(DEFAULT_LIB_DIR "${DEFAULT_LIB_DIR}/")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Static linking is deprecated/unsupported: always build dynamically
|
||||
option(FUN_LINK_STATIC "(Deprecated) Attempt to link statically — ignored; dynamic linking is enforced" OFF)
|
||||
if(FUN_LINK_STATIC)
|
||||
message(WARNING "FUN_LINK_STATIC is deprecated and ignored. Building with dynamic linking.")
|
||||
endif()
|
||||
|
||||
# Debug option to enable verbose parser/VM logging
|
||||
option(FUN_DEBUG "Enable extra debug logging in Fun" OFF)
|
||||
24
cmake/Platform/Linux.cmake
Normal file
24
cmake/Platform/Linux.cmake
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Linux-specific configuration for Fun
|
||||
|
||||
# Default library directory (only set if not provided by user)
|
||||
if(NOT DEFINED DEFAULT_LIB_DIR OR DEFAULT_LIB_DIR STREQUAL "")
|
||||
set(DEFAULT_LIB_DIR "/usr/share/fun/lib" CACHE PATH "Default library directory for Fun stdlib (override with -DDEFAULT_LIB_DIR=...)" FORCE)
|
||||
endif()
|
||||
|
||||
# Handle musl toolchain selection when requested
|
||||
if(FUN_USE_MUSL)
|
||||
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}")
|
||||
set(CMAKE_C_COMPILER "${_FUN_MUSL_CC}" CACHE FILEPATH "C compiler" FORCE)
|
||||
set(FUN_LIBC "musl" CACHE STRING "Selected C library")
|
||||
add_compile_definitions(FUN_LIBC_MUSL)
|
||||
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")
|
||||
add_compile_definitions(FUN_LIBC_GLIBC)
|
||||
endif()
|
||||
else()
|
||||
set(FUN_LIBC "glibc" CACHE STRING "Selected C library")
|
||||
add_compile_definitions(FUN_LIBC_GLIBC)
|
||||
endif()
|
||||
10
cmake/Platform/Windows.cmake
Normal file
10
cmake/Platform/Windows.cmake
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Windows-specific configuration for Fun
|
||||
|
||||
# Default library directory (only set if not provided by user)
|
||||
if(NOT DEFINED DEFAULT_LIB_DIR OR DEFAULT_LIB_DIR STREQUAL "")
|
||||
set(DEFAULT_LIB_DIR "C:/Users/Public/fun/lib" CACHE PATH "Default library directory for Fun stdlib (override with -DDEFAULT_LIB_DIR=...)" FORCE)
|
||||
endif()
|
||||
|
||||
# libc macro (non-musl path)
|
||||
set(FUN_LIBC "glibc" CACHE STRING "Selected C library")
|
||||
add_compile_definitions(FUN_LIBC_GLIBC)
|
||||
10
cmake/Platform/macOS.cmake
Normal file
10
cmake/Platform/macOS.cmake
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# macOS-specific configuration for Fun
|
||||
|
||||
# Default library directory (only set if not provided by user)
|
||||
if(NOT DEFINED DEFAULT_LIB_DIR OR DEFAULT_LIB_DIR STREQUAL "")
|
||||
set(DEFAULT_LIB_DIR "/Library/Application Support/fun/lib" CACHE PATH "Default library directory for Fun stdlib (override with -DDEFAULT_LIB_DIR=...)" FORCE)
|
||||
endif()
|
||||
|
||||
# libc macro (non-musl path)
|
||||
set(FUN_LIBC "glibc" CACHE STRING "Selected C library")
|
||||
add_compile_definitions(FUN_LIBC_GLIBC)
|
||||
130
cmake/Targets.cmake
Normal file
130
cmake/Targets.cmake
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
# Targets for Fun project (moved from src/CMakeLists.txt)
|
||||
|
||||
# Core VM/library sources
|
||||
add_library(fun_core
|
||||
${CMAKE_SOURCE_DIR}/src/bytecode.c
|
||||
${CMAKE_SOURCE_DIR}/src/parser.c
|
||||
${CMAKE_SOURCE_DIR}/src/value.c
|
||||
${CMAKE_SOURCE_DIR}/src/vm.c
|
||||
)
|
||||
|
||||
# NOTE: Do not compile src/vm/*/*.c as independent units.
|
||||
# Those files are meant to be included by src/vm.c inside a big switch.
|
||||
|
||||
target_include_directories(fun_core PUBLIC
|
||||
${CMAKE_SOURCE_DIR}/src
|
||||
)
|
||||
|
||||
# Apply options to core
|
||||
if(FUN_DEBUG)
|
||||
message(STATUS "FUN_DEBUG enabled: building with verbose debug logging")
|
||||
target_compile_definitions(fun_core PUBLIC FUN_VERSION="${PROJECT_VERSION}")
|
||||
target_compile_definitions(fun_core PUBLIC FUN_DEBUG=1)
|
||||
endif()
|
||||
|
||||
# Provide default stdlib directory and version to the runtime
|
||||
target_compile_definitions(fun_core PUBLIC FUN_VERSION="${PROJECT_VERSION}")
|
||||
target_compile_definitions(fun_core PUBLIC DEFAULT_LIB_DIR="${DEFAULT_LIB_DIR}")
|
||||
|
||||
# Apply extension include/link variables discovered in cmake/Extensions
|
||||
foreach(var_pair
|
||||
PCSC
|
||||
JSONC
|
||||
PCRE2
|
||||
CURL
|
||||
SQLITE3
|
||||
INIPARSER
|
||||
LIBSQL
|
||||
LIBXML2
|
||||
TCL
|
||||
NOTCURSES)
|
||||
if(${var_pair}_INCLUDE_DIRS)
|
||||
target_include_directories(fun_core PRIVATE ${${var_pair}_INCLUDE_DIRS})
|
||||
endif()
|
||||
if(${var_pair}_LINK_LIBS)
|
||||
target_link_libraries(fun_core PUBLIC ${${var_pair}_LINK_LIBS})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Ensure feature compile definitions are applied to fun_core so
|
||||
# conditional code blocks (#ifdef FUN_WITH_*) are compiled as expected.
|
||||
|
||||
# Feature-specific sources and compile definitions
|
||||
if(FUN_WITH_PCSC)
|
||||
target_compile_definitions(fun_core PUBLIC FUN_WITH_PCSC=1)
|
||||
endif()
|
||||
|
||||
if(FUN_WITH_JSON)
|
||||
target_compile_definitions(fun_core PUBLIC FUN_WITH_JSON=1)
|
||||
endif()
|
||||
|
||||
if(FUN_WITH_INI)
|
||||
target_compile_definitions(fun_core PUBLIC FUN_WITH_INI=1)
|
||||
endif()
|
||||
|
||||
if(FUN_WITH_XML2)
|
||||
target_compile_definitions(fun_core PUBLIC FUN_WITH_XML2=1)
|
||||
endif()
|
||||
|
||||
if(FUN_WITH_LIBSQL)
|
||||
target_compile_definitions(fun_core PUBLIC FUN_WITH_LIBSQL=1)
|
||||
endif()
|
||||
|
||||
if(FUN_WITH_PCRE2)
|
||||
target_compile_definitions(fun_core PUBLIC FUN_WITH_PCRE2=1)
|
||||
endif()
|
||||
|
||||
if(FUN_WITH_CURL)
|
||||
target_compile_definitions(fun_core PUBLIC FUN_WITH_CURL=1)
|
||||
endif()
|
||||
|
||||
if(FUN_WITH_NOTCURSES)
|
||||
target_compile_definitions(fun_core PUBLIC FUN_WITH_NOTCURSES=1)
|
||||
endif()
|
||||
|
||||
if(FUN_WITH_SQLITE)
|
||||
target_compile_definitions(fun_core PUBLIC FUN_WITH_SQLITE=1)
|
||||
endif()
|
||||
|
||||
if(FUN_WITH_TCLTK)
|
||||
target_compile_definitions(fun_core PUBLIC FUN_WITH_TCLTK=1)
|
||||
endif()
|
||||
|
||||
# Special case for libxml2 system include path if enabled
|
||||
if(FUN_WITH_XML2)
|
||||
if(EXISTS "/usr/include/libxml2")
|
||||
target_include_directories(fun_core PRIVATE "/usr/include/libxml2")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Link threads if available on UNIX
|
||||
if(Threads_FOUND)
|
||||
target_link_libraries(fun_core PUBLIC Threads::Threads)
|
||||
endif()
|
||||
|
||||
# Link libm for C99 math functions if available
|
||||
find_library(M_LIB m)
|
||||
if(M_LIB)
|
||||
target_link_libraries(fun_core PUBLIC ${M_LIB})
|
||||
endif()
|
||||
|
||||
# Executable: fun (CLI)
|
||||
add_executable(fun
|
||||
${CMAKE_SOURCE_DIR}/src/fun.c
|
||||
)
|
||||
if(FUN_WITH_REPL)
|
||||
target_compile_definitions(fun PRIVATE FUN_WITH_REPL=1)
|
||||
target_sources(fun PRIVATE ${CMAKE_SOURCE_DIR}/src/repl.c)
|
||||
endif()
|
||||
target_link_libraries(fun PRIVATE fun_core)
|
||||
|
||||
# Internal test programs
|
||||
add_executable(fun_test
|
||||
${CMAKE_SOURCE_DIR}/src/fun_test.c)
|
||||
target_link_libraries(fun_test PRIVATE fun_core)
|
||||
|
||||
add_executable(test_opcodes
|
||||
${CMAKE_SOURCE_DIR}/src/test_opcodes.c)
|
||||
target_link_libraries(test_opcodes PRIVATE fun_core)
|
||||
|
||||
# Static linking flags are intentionally not applied (deprecated behavior)
|
||||
51
examples/base64_demo.fun
Executable file
51
examples/base64_demo.fun
Executable file
|
|
@ -0,0 +1,51 @@
|
|||
#!/usr/bin/env fun
|
||||
|
||||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2026-01-13
|
||||
*/
|
||||
|
||||
/*
|
||||
* Base64 usage demo (RFC 4648, standard alphabet)
|
||||
*
|
||||
* Run without installing:
|
||||
* FUN_LIB_DIR="$(pwd)/lib" ./build/fun examples/base64_demo.fun
|
||||
*/
|
||||
|
||||
#include <encoding/base64.fun>
|
||||
|
||||
print("=== Base64 demo ===")
|
||||
|
||||
// Bytes for the ASCII string "Hello"
|
||||
bytes = [0x48, 0x65, 0x6c, 0x6c, 0x6f]
|
||||
|
||||
// Encode bytes -> Base64 string
|
||||
b64 = b64_encode_bytes(bytes)
|
||||
print("b64(Hello) = " + b64) // Expected: SGVsbG8=
|
||||
|
||||
// Decode Base64 -> bytes array (uncomment to try)
|
||||
// decoded = b64_decode_to_bytes(b64)
|
||||
// // Build a comma-separated list of byte values as strings
|
||||
// parts = []
|
||||
// for i in range(0, len(decoded))
|
||||
// push(parts, to_string(decoded[i]))
|
||||
// print("bytes = " + join(parts, ",")) // Expected: 72,101,108,108,111
|
||||
|
||||
// Another example: padding with '='
|
||||
bytes2 = [0x46, 0x75, 0x6e] // "Fun"
|
||||
print("b64(Fun) = " + b64_encode_bytes(bytes2)) // Expected: RnVu
|
||||
|
||||
print("=== done ===")
|
||||
|
||||
/* Expected output:
|
||||
=== Base64 demo ===
|
||||
b64(Hello) = SGVsbG8=
|
||||
b64(Fun) = RnVu
|
||||
=== done ===
|
||||
*/
|
||||
314
examples/broken/ripemd160.fun
Executable file
314
examples/broken/ripemd160.fun
Executable file
|
|
@ -0,0 +1,314 @@
|
|||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2026 Johannes Findeisen
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2026-01-02
|
||||
*/
|
||||
|
||||
// lib/crypt/ripemd160.fun
|
||||
// Pure Fun implementation of RIPEMD-160 operating on hex-string or ASCII inputs.
|
||||
//
|
||||
// Public API (class):
|
||||
// rip = RIPEMD160()
|
||||
// rip.ripemd160_hex(hexStr) -> digest hex string (lowercase)
|
||||
// rip.ripemd160_str("abc") -> digest hex string of ASCII bytes
|
||||
|
||||
#include <strings.fun>
|
||||
|
||||
class RIPEMD160()
|
||||
// 32-bit helpers (match style from md5.fun)
|
||||
fun u32(this, x)
|
||||
m = 4294967296
|
||||
while x < 0
|
||||
x = x + m
|
||||
while x >= m
|
||||
x = x - m
|
||||
return x
|
||||
|
||||
fun add32(this, a, b)
|
||||
return this.u32(a + b)
|
||||
|
||||
fun add32_5(this, a, b, c, d, e)
|
||||
return this.u32(this.u32(this.u32(this.u32(a + b) + c) + d) + e)
|
||||
|
||||
fun rol32(this, x, s)
|
||||
return rol(this.u32(x), s)
|
||||
|
||||
fun and32(this, a, b)
|
||||
return band(this.u32(a), this.u32(b))
|
||||
|
||||
fun or32(this, a, b)
|
||||
return bor(this.u32(a), this.u32(b))
|
||||
|
||||
fun xor32(this, a, b)
|
||||
return bxor(this.u32(a), this.u32(b))
|
||||
|
||||
fun not32(this, x)
|
||||
return bnot(this.u32(x))
|
||||
|
||||
// hex helpers (same as md5/sha files)
|
||||
fun hex_val(this, ch)
|
||||
if (ch == "0")
|
||||
return 0
|
||||
else if (ch == "1")
|
||||
return 1
|
||||
else if (ch == "2")
|
||||
return 2
|
||||
else if (ch == "3")
|
||||
return 3
|
||||
else if (ch == "4")
|
||||
return 4
|
||||
else if (ch == "5")
|
||||
return 5
|
||||
else if (ch == "6")
|
||||
return 6
|
||||
else if (ch == "7")
|
||||
return 7
|
||||
else if (ch == "8")
|
||||
return 8
|
||||
else if (ch == "9")
|
||||
return 9
|
||||
else if (ch == "a" || ch == "A")
|
||||
return 10
|
||||
else if (ch == "b" || ch == "B")
|
||||
return 11
|
||||
else if (ch == "c" || ch == "C")
|
||||
return 12
|
||||
else if (ch == "d" || ch == "D")
|
||||
return 13
|
||||
else if (ch == "e" || ch == "E")
|
||||
return 14
|
||||
else if (ch == "f" || ch == "F")
|
||||
return 15
|
||||
else
|
||||
return 0
|
||||
|
||||
fun byte_from_hex_pair(this, hh)
|
||||
hi = this.hex_val(substr(hh, 0, 1))
|
||||
lo = this.hex_val(substr(hh, 1, 1))
|
||||
return hi * 16 + lo
|
||||
|
||||
fun from_hex(this, hex)
|
||||
arr = []
|
||||
i = 0
|
||||
n = len(hex)
|
||||
while i + 1 < n
|
||||
b = this.byte_from_hex_pair(substr(hex, i, 2))
|
||||
push(arr, b)
|
||||
i = i + 2
|
||||
return arr
|
||||
|
||||
fun two_hex(this, n)
|
||||
n = n % 256
|
||||
d = ["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"]
|
||||
hi = n / 16
|
||||
lo = n % 16
|
||||
parts = [d[hi], d[lo]]
|
||||
return join(parts, "")
|
||||
|
||||
fun bytes_to_hex(this, arr)
|
||||
i = 0
|
||||
out = []
|
||||
while i < len(arr)
|
||||
push(out, this.two_hex(arr[i]))
|
||||
i = i + 1
|
||||
return join(out, "")
|
||||
|
||||
// Padding (RIPEMD-160): like MD4/MD5 — append 0x80, zeros to 56 mod 64, then 64-bit length in little-endian
|
||||
fun pad_bytes(this, bytes)
|
||||
L = len(bytes)
|
||||
out = []
|
||||
i = 0
|
||||
while i < L
|
||||
push(out, bytes[i])
|
||||
i = i + 1
|
||||
push(out, 128)
|
||||
while (len(out) % 64) != 56
|
||||
push(out, 0)
|
||||
len_bits = L * 8
|
||||
j = 0
|
||||
while j < 8
|
||||
b = (len_bits / pow(2, 8 * j)) % 256
|
||||
push(out, b)
|
||||
j = j + 1
|
||||
return out
|
||||
|
||||
fun word32_le(this, b0, b1, b2, b3)
|
||||
return this.u32(b0 + b1 * 256 + b2 * 65536 + b3 * 16777216)
|
||||
|
||||
// RIPEMD-160 boolean functions
|
||||
fun f1(this, x, y, z)
|
||||
return this.xor32(this.xor32(x, y), z)
|
||||
|
||||
fun f2(this, x, y, z)
|
||||
return this.or32(this.and32(x, y), this.and32(this.not32(x), z))
|
||||
|
||||
fun f3(this, x, y, z)
|
||||
return this.xor32(this.or32(x, this.not32(y)), z)
|
||||
|
||||
fun f4(this, x, y, z)
|
||||
return this.or32(this.and32(x, z), this.and32(y, this.not32(z)))
|
||||
|
||||
fun f5(this, x, y, z)
|
||||
return this.xor32(x, this.or32(y, this.not32(z)))
|
||||
|
||||
// Process a 512-bit block
|
||||
fun process_block(this, H, block)
|
||||
// message words X[16] (little-endian)
|
||||
X = []
|
||||
i = 0
|
||||
while i < 16
|
||||
j = i * 4
|
||||
push(X, this.word32_le(block[j], block[j+1], block[j+2], block[j+3]))
|
||||
i = i + 1
|
||||
|
||||
// R and S (left line)
|
||||
R = [
|
||||
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
|
||||
7,4,13,1,10,6,15,3,12,0,9,5,2,14,11,8,
|
||||
3,10,14,4,9,15,8,1,2,7,0,6,13,11,5,12,
|
||||
1,9,11,10,0,8,12,4,13,3,7,15,14,5,6,2,
|
||||
4,0,5,9,7,12,2,10,14,1,3,8,11,6,15,13
|
||||
]
|
||||
S = [
|
||||
11,14,15,12,5,8,7,9,11,13,14,15,6,7,9,8,
|
||||
7,6,8,13,11,9,7,15,7,12,15,9,11,7,13,12,
|
||||
11,13,6,7,14,9,13,15,14,8,13,6,5,12,7,5,
|
||||
11,12,14,15,14,15,9,8,9,14,5,6,8,6,5,12,
|
||||
9,15,5,11,6,8,13,12,5,12,13,14,11,8,5,6
|
||||
]
|
||||
|
||||
// R' and S' (right line)
|
||||
Rp = [
|
||||
5,14,7,0,9,2,11,4,13,6,15,8,1,10,3,12,
|
||||
6,11,3,7,0,13,5,10,14,15,8,12,4,9,1,2,
|
||||
15,5,1,3,7,14,6,9,11,8,12,2,10,0,4,13,
|
||||
8,6,4,1,3,11,15,0,5,12,2,13,9,7,10,14,
|
||||
12,15,10,4,1,5,8,7,6,2,13,14,0,3,9,11
|
||||
]
|
||||
Sp = [
|
||||
8,9,9,11,13,15,15,5,7,7,8,11,14,14,12,6,
|
||||
9,13,15,7,12,8,9,11,7,7,12,7,6,15,13,11,
|
||||
9,7,15,11,8,6,6,14,12,13,5,14,13,13,7,5,
|
||||
15,5,8,11,14,14,6,14,6,9,12,9,12,5,15,8,
|
||||
8,5,12,9,12,5,14,6,8,13,6,5,15,13,11,11
|
||||
]
|
||||
|
||||
// Constants
|
||||
KL = [0, 1518500249, 1859775393, 2400959708, 2840853838] // 0x00, 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xA953FD4E
|
||||
KR = [1352829926, 1548603684, 1836072691, 2053994217, 0] // 0x50A28BE6, 0x5C4DD124, 0x6D703EF3, 0x7A6D76E9, 0x00000000
|
||||
|
||||
al = H[0]; bl = H[1]; cl = H[2]; dl = H[3]; el = H[4]
|
||||
ar = H[0]; br = H[1]; cr = H[2]; dr = H[3]; er = H[4]
|
||||
|
||||
j = 0
|
||||
while j < 80
|
||||
sL = S[j]
|
||||
sR = Sp[j]
|
||||
|
||||
// select function and constant per round for left lane
|
||||
if (j < 16)
|
||||
fL = this.f1(bl, cl, dl)
|
||||
kL = KL[0]
|
||||
else if (j < 32)
|
||||
fL = this.f2(bl, cl, dl)
|
||||
kL = KL[1]
|
||||
else if (j < 48)
|
||||
fL = this.f3(bl, cl, dl)
|
||||
kL = KL[2]
|
||||
else if (j < 64)
|
||||
fL = this.f4(bl, cl, dl)
|
||||
kL = KL[3]
|
||||
else
|
||||
fL = this.f5(bl, cl, dl)
|
||||
kL = KL[4]
|
||||
|
||||
// right lane
|
||||
if (j < 16)
|
||||
fR = this.f5(br, cr, dr)
|
||||
kR = KR[0]
|
||||
else if (j < 32)
|
||||
fR = this.f4(br, cr, dr)
|
||||
kR = KR[1]
|
||||
else if (j < 48)
|
||||
fR = this.f3(br, cr, dr)
|
||||
kR = KR[2]
|
||||
else if (j < 64)
|
||||
fR = this.f2(br, cr, dr)
|
||||
kR = KR[3]
|
||||
else
|
||||
fR = this.f1(br, cr, dr)
|
||||
kR = KR[4]
|
||||
|
||||
// left step
|
||||
tl = this.add32_5(al, fL, X[R[j]], kL, 0)
|
||||
tl = this.rol32(tl, sL)
|
||||
tl = this.add32(tl, el)
|
||||
al = el
|
||||
el = dl
|
||||
dl = this.rol32(cl, 10)
|
||||
cl = bl
|
||||
bl = tl
|
||||
|
||||
// right step
|
||||
tr = this.add32_5(ar, fR, X[Rp[j]], kR, 0)
|
||||
tr = this.rol32(tr, sR)
|
||||
tr = this.add32(tr, er)
|
||||
ar = er
|
||||
er = dr
|
||||
dr = this.rol32(cr, 10)
|
||||
cr = br
|
||||
br = tr
|
||||
|
||||
j = j + 1
|
||||
|
||||
// combine using originals
|
||||
h0 = H[0]; h1 = H[1]; h2 = H[2]; h3 = H[3]; h4 = H[4]
|
||||
tt = this.add32(h0, this.add32(bl, cr))
|
||||
H[0] = this.add32(h1, this.add32(cl, dr))
|
||||
H[1] = this.add32(h2, this.add32(dl, er))
|
||||
H[2] = this.add32(h3, this.add32(el, ar))
|
||||
H[3] = this.add32(h4, this.add32(al, br))
|
||||
H[4] = tt
|
||||
return H
|
||||
|
||||
fun ripemd160_bytes(this, bytes)
|
||||
// initialize h0..h4
|
||||
H = [1732584193, 4023233417, 2562383102, 271733878, 3285377520]
|
||||
data = this.pad_bytes(bytes)
|
||||
off = 0
|
||||
N = len(data)
|
||||
while off < N
|
||||
block = []
|
||||
i = 0
|
||||
while i < 64
|
||||
push(block, data[off + i])
|
||||
i = i + 1
|
||||
H = this.process_block(H, block)
|
||||
off = off + 64
|
||||
|
||||
// output as little-endian of h0..h4 (20 bytes)
|
||||
out = []
|
||||
j = 0
|
||||
while j < 5
|
||||
v = H[j]
|
||||
push(out, v % 256)
|
||||
push(out, (v / 256) % 256)
|
||||
push(out, (v / 65536) % 256)
|
||||
push(out, (v / 16777216) % 256)
|
||||
j = j + 1
|
||||
return out
|
||||
|
||||
fun ripemd160_hex(this, hexStr)
|
||||
bytes = this.from_hex(hexStr)
|
||||
digest = this.ripemd160_bytes(bytes)
|
||||
return this.bytes_to_hex(digest)
|
||||
|
||||
fun ripemd160_str(this, str)
|
||||
bytes = string_to_bytes_ascii(str)
|
||||
digest = this.ripemd160_bytes(bytes)
|
||||
return this.bytes_to_hex(digest)
|
||||
|
|
@ -1,29 +1,37 @@
|
|||
|
||||
[auth]
|
||||
user = "hanez"
|
||||
token = "abcd1234"
|
||||
user = "hanez"
|
||||
token = "abcd1234"
|
||||
|
||||
|
||||
[app]
|
||||
name = "FunApp"
|
||||
version = "1.2.3"
|
||||
debug = "1"
|
||||
name = "FunApp"
|
||||
version = "1.2.3"
|
||||
debug = "1"
|
||||
|
||||
|
||||
[database]
|
||||
host = "localhost"
|
||||
port = "5432"
|
||||
user = "fun"
|
||||
pass = "secret"
|
||||
pool_size = "8"
|
||||
timeout = "2.5"
|
||||
host = "localhost"
|
||||
port = "5432"
|
||||
user = "fun"
|
||||
pass = "secret"
|
||||
pool_size = "8"
|
||||
timeout = "2.5"
|
||||
|
||||
|
||||
[network]
|
||||
ssl = "yes"
|
||||
retries = "3"
|
||||
base_url = "https://api.example.com"
|
||||
ssl = "yes"
|
||||
retries = "3"
|
||||
base_url = "https://api.example.com"
|
||||
|
||||
|
||||
[features]
|
||||
feature_x = "on"
|
||||
feature_y = "off"
|
||||
feature_x = "on"
|
||||
feature_y = "off"
|
||||
|
||||
|
||||
[paths]
|
||||
data_dir = "./data"
|
||||
log_file = "./logs/app.log"
|
||||
data_dir = "./data"
|
||||
log_file = "./logs/app.log"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -31,29 +31,27 @@ if obj != nil
|
|||
if obj != nil
|
||||
print("Origin: " + to_string(obj["origin"]))
|
||||
|
||||
/* Possible output:
|
||||
Response: {
|
||||
"args": {},
|
||||
"data": "",
|
||||
"files": {},
|
||||
"form": {
|
||||
"lang": "fun",
|
||||
"name": "Fun"
|
||||
},
|
||||
"headers": {
|
||||
"Accept": "*/*",
|
||||
"Content-Length": "17",
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Host": "httpbin.org",
|
||||
"X-Amzn-Trace-Id": "Root=1-6944834b-74f499e251c322713e7dd9a8"
|
||||
},
|
||||
"json": null,
|
||||
"origin": "5.252.226.107",
|
||||
"url": "https://httpbin.org/post"
|
||||
}
|
||||
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Host: httpbin.org
|
||||
Origin: 5.252.226.107
|
||||
*/
|
||||
|
||||
// Possible output:
|
||||
// Response: {
|
||||
// "args": {},
|
||||
// "data": "",
|
||||
// "files": {},
|
||||
// "form": {
|
||||
// "lang": "fun",
|
||||
// "name": "Fun"
|
||||
// },
|
||||
// "headers": {
|
||||
// "Accept": "*/*",
|
||||
// "Content-Length": "17",
|
||||
// "Content-Type": "application/x-www-form-urlencoded",
|
||||
// "Host": "httpbin.org",
|
||||
// "X-Amzn-Trace-Id": "Root=1-6944834b-74f499e251c322713e7dd9a8"
|
||||
// },
|
||||
// "json": nil,
|
||||
// "origin": "5.252.226.107",
|
||||
// "url": "https://httpbin.org/post"
|
||||
// }
|
||||
//
|
||||
// Content-Type: application/x-www-form-urlencoded
|
||||
// Host: httpbin.org
|
||||
// Origin: 5.252.226.107
|
||||
|
|
@ -29,6 +29,9 @@ if (res["code"] == 0)
|
|||
else
|
||||
print("Login failed!")
|
||||
|
||||
// Propagate the child process exit status to the Fun program's exit code
|
||||
exit res["code"]
|
||||
|
||||
/* Possible output:
|
||||
Username: hanez
|
||||
Password:
|
||||
70
make
70
make
|
|
@ -30,9 +30,7 @@ if [ "$target" = "all" ]; then
|
|||
-DFUN_WITH_JSON=ON \
|
||||
-DFUN_WITH_TCLTK=ON \
|
||||
-DFUN_WITH_INI=ON \
|
||||
-DFUN_LINK_STATIC=OFF \
|
||||
-DFUN_USE_MUSL=OFF \
|
||||
-DFUN_DEBUG=OFF \
|
||||
-DFUN_WITH_NOTCURSES=ON \
|
||||
&& cmake --build build --target fun
|
||||
elif [ "$target" = "all_debug" ]; then
|
||||
rm -rf build \
|
||||
|
|
@ -47,14 +45,12 @@ elif [ "$target" = "all_debug" ]; then
|
|||
-DFUN_WITH_JSON=ON \
|
||||
-DFUN_WITH_TCLTK=ON \
|
||||
-DFUN_WITH_INI=ON \
|
||||
-DFUN_LINK_STATIC=OFF \
|
||||
-DFUN_USE_MUSL=OFF \
|
||||
-DFUN_WITH_NOTCURSES=ON \
|
||||
-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 \
|
||||
|
|
@ -62,96 +58,35 @@ elif [ "$target" = "alpine" ]; then
|
|||
-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" = "install" ]; then
|
||||
sudo cmake --build build --target install
|
||||
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!";
|
||||
|
|
@ -166,3 +101,4 @@ else
|
|||
echo " - musl";
|
||||
echo " - repl";
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ BYTECODE = ROOT / "src" / "bytecode.h"
|
|||
VM_C = ROOT / "src" / "vm.c"
|
||||
|
||||
OP_RE = re.compile(r'\bOP_([A-Z0-9_]+)\b')
|
||||
INCLUDE_RE = re.compile(r'#include\s+"vm/(?:[a-z0-9_]+/)?([a-z0-9_]+)\.c"')
|
||||
# Capture optional subdirectory and the basename separately
|
||||
INCLUDE_RE = re.compile(r'#include\s+"vm/(?:([a-z0-9_]+)/)?([a-z0-9_]+)\.c"')
|
||||
|
||||
def read_text(path: Path) -> str:
|
||||
try:
|
||||
|
|
@ -42,14 +43,14 @@ def parse_opcodes_from_bytecode(text: str) -> set[str]:
|
|||
return ops
|
||||
|
||||
def parse_includes_from_vm(text: str) -> set[str]:
|
||||
incs = set(m.group(1) for m in INCLUDE_RE.finditer(text))
|
||||
pairs = [(m.group(1) or "", m.group(2)) for m in INCLUDE_RE.finditer(text)]
|
||||
|
||||
# Drop support includes that are not opcode handlers
|
||||
support_includes = {"thread_common"}
|
||||
incs = {name for name in incs if name not in support_includes}
|
||||
support_includes = {"thread_common", "stubs", "handles"}
|
||||
pairs = [(d, n) for (d, n) in pairs if n not in support_includes]
|
||||
|
||||
# Map include base names to OP_* tokens.
|
||||
overrides = {
|
||||
# Base-name overrides (dir-agnostic) for a few special cases
|
||||
base_overrides = {
|
||||
# core
|
||||
"nop": "NOP", "halt": "HALT",
|
||||
"load_const": "LOAD_CONST", "load_local": "LOAD_LOCAL", "store_local": "STORE_LOCAL",
|
||||
|
|
@ -66,7 +67,7 @@ def parse_includes_from_vm(text: str) -> set[str]:
|
|||
"make_array": "MAKE_ARRAY", "len": "LEN",
|
||||
"index_get": "INDEX_GET", "index_set": "INDEX_SET",
|
||||
"push": "PUSH", "apop": "APOP", "set": "SET", "insert": "INSERT", "remove": "REMOVE",
|
||||
"slice": "SLICE",
|
||||
"slice": "SLICE", "clear": "CLEAR", "contains": "CONTAINS", "index_of": "INDEX_OF",
|
||||
# conversions and type/meta
|
||||
"to_number": "TO_NUMBER", "to_string": "TO_STRING",
|
||||
"cast": "CAST", "typeof": "TYPEOF", "uclamp": "UCLAMP", "sclamp": "SCLAMP",
|
||||
|
|
@ -89,9 +90,50 @@ def parse_includes_from_vm(text: str) -> set[str]:
|
|||
"thread_spawn": "THREAD_SPAWN", "thread_join": "THREAD_JOIN",
|
||||
}
|
||||
|
||||
tokens = set()
|
||||
for inc in incs:
|
||||
tokens.add(overrides.get(inc, inc.upper()))
|
||||
def map_token(d: str, n: str) -> str:
|
||||
# Directory-specific namespaces
|
||||
if d == "curl":
|
||||
return f"CURL_{n.upper()}"
|
||||
if d == "ini":
|
||||
if n in {"load", "free", "get_string", "get_int", "get_double", "get_bool", "set", "unset", "save"}:
|
||||
return f"INI_{n.upper()}"
|
||||
if d == "json":
|
||||
if n in {"parse", "stringify", "from_file", "to_file"}:
|
||||
return f"JSON_{n.upper()}"
|
||||
if d == "xml":
|
||||
if n in {"parse", "root", "name", "text"}:
|
||||
return f"XML_{n.upper()}"
|
||||
if d == "sqlite":
|
||||
if n in {"open", "close", "exec", "query"}:
|
||||
return f"SQLITE_{n.upper()}"
|
||||
if d == "libsql":
|
||||
if n in {"open", "close", "exec", "query"}:
|
||||
return f"LIBSQL_{n.upper()}"
|
||||
if d == "pcsc":
|
||||
if n in {"establish", "release", "list_readers", "connect", "disconnect", "transmit"}:
|
||||
return f"PCSC_{n.upper()}"
|
||||
if d == "pcre2":
|
||||
if n in {"test", "match", "findall"}:
|
||||
return f"PCRE2_{n.upper()}"
|
||||
if d == "tk":
|
||||
# wm_title already uses underscore
|
||||
return f"TK_{n.upper()}"
|
||||
if d == "notcurses":
|
||||
return f"NC_{n.upper()}"
|
||||
if d == "os":
|
||||
# special cases in OS
|
||||
if n == "list_dir":
|
||||
return "OS_LIST_DIR"
|
||||
if n.startswith("socket_"):
|
||||
rest = n.split("socket_", 1)[1].upper()
|
||||
return f"SOCK_{rest}"
|
||||
if n.startswith("serial_"):
|
||||
# serial_open/config/send/recv/close
|
||||
return f"SERIAL_{n.split('serial_',1)[1].upper()}"
|
||||
# Fallbacks: known base overrides, else uppercase name
|
||||
return base_overrides.get(n, n.upper())
|
||||
|
||||
tokens = set(map(lambda p: map_token(p[0], p[1]), pairs))
|
||||
return tokens
|
||||
|
||||
def main() -> int:
|
||||
|
|
|
|||
|
|
@ -11,8 +11,9 @@ set -euo pipefail
|
|||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")"/.. && pwd)"
|
||||
EX_DIR="$ROOT/examples"
|
||||
|
||||
# Allow override via first arg or FUN_BIN env
|
||||
BIN="${1:-${FUN_BIN:-}}"
|
||||
# Optional overrides via FUN_BIN env. First positional arg may now be an examples subdirectory.
|
||||
BIN="${FUN_BIN:-}"
|
||||
SUBDIR="${1:-}"
|
||||
|
||||
pick_bin() {
|
||||
local cands=(
|
||||
|
|
@ -36,11 +37,17 @@ pick_bin() {
|
|||
return 1
|
||||
}
|
||||
|
||||
# If user passed a subdir that actually looks like an executable path, treat it as BIN for backward compatibility
|
||||
if [[ -n "$SUBDIR" && -x "$SUBDIR" && ! -d "$EX_DIR/$SUBDIR" ]]; then
|
||||
BIN="$SUBDIR"
|
||||
SUBDIR=""
|
||||
fi
|
||||
|
||||
if [[ -z "${BIN}" ]]; then
|
||||
if ! BIN="$(pick_bin)"; then
|
||||
echo "error: fun binary not found. Try building it (e.g., via CMake) or pass it explicitly:" >&2
|
||||
echo " scripts/run_examples.sh /path/to/fun" >&2
|
||||
echo "or set FUN_BIN=/path/to/fun" >&2
|
||||
echo " FUN_BIN=/path/to/fun scripts/run_examples.sh [examples-subdir]" >&2
|
||||
echo "or: scripts/run_examples.sh /path/to/fun" >&2
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
|
|
@ -55,15 +62,26 @@ if [[ -z "${FUN_LIB_DIR:-}" ]]; then
|
|||
export FUN_LIB_DIR="$ROOT/lib"
|
||||
fi
|
||||
|
||||
# Ensure error bucket exists
|
||||
mkdir -p "$EX_DIR/error"
|
||||
# Do not auto-move failing examples; keep user's workspace unchanged
|
||||
|
||||
# Determine target examples directory (optionally restricted to a subdir)
|
||||
TARGET_DIR="$EX_DIR"
|
||||
if [[ -n "${SUBDIR}" ]]; then
|
||||
if [[ -d "$EX_DIR/$SUBDIR" ]]; then
|
||||
TARGET_DIR="$EX_DIR/$SUBDIR"
|
||||
else
|
||||
echo "error: examples subdirectory not found: $SUBDIR" >&2
|
||||
echo " expected at: $EX_DIR/$SUBDIR" >&2
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
|
||||
shopt -s nullglob
|
||||
files=("$EX_DIR"/*.fun)
|
||||
files=("$TARGET_DIR"/*.fun)
|
||||
shopt -u nullglob
|
||||
|
||||
if (( ${#files[@]} == 0 )); then
|
||||
echo "No .fun example files found in $EX_DIR"
|
||||
echo "No .fun example files found in $TARGET_DIR"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
|
@ -80,13 +98,7 @@ for f in "${files[@]}"; do
|
|||
echo "=== Running: ${f#$ROOT/} ==="
|
||||
if ! "$BIN" "$f"; then
|
||||
echo "FAILED: ${f#$ROOT/}"
|
||||
dest="$EX_DIR/error/$base_name"
|
||||
# Move the failing example to the error folder
|
||||
if mv -f "$f" "$dest"; then
|
||||
echo "Moved to: examples/error/$base_name"
|
||||
else
|
||||
echo "warning: failed to move $base_name to examples/error/" >&2
|
||||
fi
|
||||
# Intentionally do not move files on failure; leave control to the user
|
||||
rc=1
|
||||
fi
|
||||
done
|
||||
|
|
|
|||
484
spec/v0.3.md
Normal file
484
spec/v0.3.md
Normal file
|
|
@ -0,0 +1,484 @@
|
|||
# Fun Language Specification v0.3
|
||||
|
||||
This document describes Fun (Fun Uses Nothing) as of version 0.3. It supersedes v0.2 by formalizing classes/objects, inheritance, namespaced includes, richer collections and builtins, concurrency primitives, networking and OS integration, as reflected by the examples.
|
||||
|
||||
---
|
||||
|
||||
## 1) Overview and Goals
|
||||
|
||||
- Readable: strict, indentation-based syntax (2 spaces), no semicolons.
|
||||
- Safe: explicit types, no implicit numeric coercions, bounds-checked operations, controlled side effects.
|
||||
- Hackable: pragmatic stdlib, process I/O, sockets, threads.
|
||||
|
||||
What’s new in v0.3 (high level):
|
||||
- Classes and objects with methods, constructors, and inheritance (`extends`).
|
||||
- Dot-call sugar for method calls, and explicit `this` in method definitions.
|
||||
- Namespaced `#include ... as alias` for module imports.
|
||||
- Maps (dictionaries) with literals and helpers.
|
||||
- Control-flow additions: `break` and `continue`.
|
||||
- Threads: `thread_spawn`, `thread_join` and `sleep`.
|
||||
- Expanded system and network APIs (TCP/Unix sockets, serial, env, timers).
|
||||
- Bitwise helpers (`band`, `bor`, `bxor`, `bnot`, `shl`).
|
||||
- Exception syntax (`try/catch/finally`) is defined; runtime throwing/handling may be partial.
|
||||
|
||||
---
|
||||
|
||||
## 2) Lexical Structure
|
||||
|
||||
- Case-sensitive identifiers: letters, digits, `_`; must not start with a digit.
|
||||
- Comments:
|
||||
- Single-line: `// comment`
|
||||
- Multi-line: `/* ... */`
|
||||
- Whitespace and newlines:
|
||||
- Indentation is exactly 2 spaces; tabs are forbidden.
|
||||
- Newline terminates statements; no semicolons.
|
||||
|
||||
Reserved keywords (cannot be redefined):
|
||||
- `if`, `else`, `for`, `while`, `break`, `continue`
|
||||
- `fun`, `return`
|
||||
- `class`, `extends`
|
||||
- `global`, `private`
|
||||
- `true`, `false`
|
||||
- `try`, `catch`, `finally`
|
||||
|
||||
Notes:
|
||||
- `#include` is a directive, not an expression; `as` is part of the include alias syntax (see Modules & Includes).
|
||||
|
||||
---
|
||||
|
||||
## 3) Types
|
||||
|
||||
Scalar types:
|
||||
- `number`: 64-bit signed integer.
|
||||
- `float`: 64-bit IEEE-754 floating point.
|
||||
- `string`
|
||||
- `boolean`: `true` / `false` (in conditionals `0` and `1` are accepted where noted).
|
||||
- `byte`: 8-bit value (see conversions and overflow rules).
|
||||
|
||||
Fixed-width integers (signed/unsigned):
|
||||
- `int8`, `uint8`, `int16`, `uint16`, `int32`, `uint32`, `int64`, `uint64`
|
||||
|
||||
Aggregate types:
|
||||
- `array` and typed arrays: `array<T>`
|
||||
- `map<K, V>` (dictionary / associative array)
|
||||
- `object` (instances of `class`)
|
||||
|
||||
Examples:
|
||||
```fun
|
||||
number n = 42
|
||||
float pi = 3.14159
|
||||
string s = 'He said: "Fun!"'
|
||||
boolean ok = true
|
||||
byte b = 0x41
|
||||
|
||||
array<number> nums = [1, 2, 3]
|
||||
array mixed = [1, "two", true, [3, 4]]
|
||||
|
||||
m = { "a": 1, "b": 2 } // map<string, number>
|
||||
```
|
||||
|
||||
Dynamic typing escape hatch (discouraged; use when interacting with unknown data):
|
||||
```fun
|
||||
dynamic string x = 42 // allowed by spec; runtime performs dynamic checks
|
||||
x = "Now I'm a string"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4) Variables and Scope
|
||||
|
||||
- `global` variables are visible program-wide.
|
||||
- `private` variables are file-local (module private).
|
||||
- Rebinding a global or shadowing a name is a compile-time error.
|
||||
|
||||
```fun
|
||||
global string message = "Hello"
|
||||
private number count = 42
|
||||
number local = 23
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5) Operators and Builtins
|
||||
|
||||
Arithmetic: `+`, `-`, `*`, `/`, `%`
|
||||
|
||||
Comparison: `==`, `!=`, `>`, `<`, `>=`, `<=`
|
||||
|
||||
Boolean: `&&`, `||`, `!`
|
||||
|
||||
Assignment: `=`
|
||||
|
||||
Bitwise helpers (functions):
|
||||
- `band(a, b)`, `bor(a, b)`, `bxor(a, b)`, `bnot(a)`, `shl(a, n)`
|
||||
|
||||
```fun
|
||||
print(bxor(0x80000000, 0x00000001)) // 2147483649
|
||||
print(shl(0x80, 24)) // 2147483648
|
||||
```
|
||||
|
||||
Collection helpers (selected):
|
||||
- Arrays: `push(arr, v)`, `join(arr, sep)`, `map(arr, f)`, `filter(arr, pred)`, `reduce(arr, init, f)`
|
||||
- Maps: `has(m, key)`, `keys(m)`, `values(m)`
|
||||
|
||||
Type/convert helpers (selected):
|
||||
- `typeof(x) -> string`
|
||||
- `to_string(x)` and numeric casts (see examples for `cast_demo.fun` and `conversions_showcase.fun`)
|
||||
|
||||
---
|
||||
|
||||
## 6) Control Flow
|
||||
|
||||
If/Else:
|
||||
```fun
|
||||
if (x != y)
|
||||
print(x)
|
||||
else if (a == b || h != i)
|
||||
print(a + b)
|
||||
else
|
||||
if (k < 1 && l > 1)
|
||||
print("Buh!")
|
||||
```
|
||||
|
||||
While:
|
||||
```fun
|
||||
number i = 0
|
||||
while i < 10
|
||||
if i % 2 == 0
|
||||
i = i + 1
|
||||
continue
|
||||
if i > 5
|
||||
break
|
||||
i = i + 1
|
||||
```
|
||||
|
||||
For:
|
||||
- Range iteration: `for i in range(start, end)`
|
||||
- Array iteration: `for x in arr`
|
||||
- Map iteration: `for k in keys(m)` then `m[k]`
|
||||
|
||||
```fun
|
||||
for i in range(0, 5)
|
||||
print(i)
|
||||
|
||||
for x in [1, 2, 3]
|
||||
print(x)
|
||||
```
|
||||
|
||||
Loop control:
|
||||
- `break` exits the innermost loop.
|
||||
- `continue` skips to next iteration of the current loop.
|
||||
|
||||
Try/Catch/Finally (syntax defined; runtime throwing may be incomplete):
|
||||
```fun
|
||||
try
|
||||
// protected section
|
||||
risky()
|
||||
catch err
|
||||
print("caught error:")
|
||||
print(err)
|
||||
finally
|
||||
print("cleanup")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7) Functions
|
||||
|
||||
Built-in/runtime functions (selected):
|
||||
- Basic: `print(x)`, `range(a, b)`, `sleep(ms)`
|
||||
- Processes: `exec(cmd) -> string`, `system(cmd) -> number`
|
||||
- Async processes: `nexec(cmd) -> pid/object`, `nsystem(cmd) -> number`, `nspawn(cmd) -> pid`, `wait(pid)`, `read(pid)`, `kill(pid)`
|
||||
- Threads: `thread_spawn(fn, argOrArgs) -> thread_id`, `thread_join(thread_id) -> any`
|
||||
|
||||
User-defined functions:
|
||||
```fun
|
||||
fun add(a, b)
|
||||
return a + b
|
||||
|
||||
fun divide(a, b)
|
||||
if b == 0
|
||||
return 0, "division by zero"
|
||||
return a / b, ""
|
||||
```
|
||||
|
||||
Higher-order helper (example pattern):
|
||||
```fun
|
||||
fun call(f, arg)
|
||||
return f(arg)
|
||||
```
|
||||
|
||||
Multiple return values are supported syntactically; use tuple-like unpacking via arrays as needed in user code patterns.
|
||||
|
||||
---
|
||||
|
||||
## 8) Classes and Objects
|
||||
|
||||
Definition:
|
||||
```fun
|
||||
class Name(/* optional ctor params with types */)
|
||||
// field defaults
|
||||
field1 = 0
|
||||
field2 = ""
|
||||
|
||||
// method: first parameter must be `this`
|
||||
fun method(this, arg1, arg2)
|
||||
// ...
|
||||
return 0
|
||||
```
|
||||
|
||||
Constructors:
|
||||
- Default constructor maps the class header parameters to fields of the same names.
|
||||
- Optional explicit constructor hook: define `fun _construct(this, ...params...)` to customize initialization. It is invoked automatically on instantiation.
|
||||
|
||||
Fields and methods:
|
||||
- Fields are created/initialized with simple assignments in the class body.
|
||||
- Methods are functions declared inside the class; the first parameter must be `this`.
|
||||
- Private members: any field or method whose name starts with `_` is considered private to the class. Accessing them from outside should raise an access error.
|
||||
|
||||
Instantiation and method calls:
|
||||
```fun
|
||||
p = Point(10, -2)
|
||||
print(p.x) // field access via `.` or indexing
|
||||
print(p["x"]) // map-like field access is supported
|
||||
|
||||
// Dot-call sugar: p.method(a, b) is equivalent to method(p, a, b)
|
||||
print(p.toString())
|
||||
```
|
||||
|
||||
Method references:
|
||||
```fun
|
||||
move_fn = p["move"]
|
||||
move_fn(p, 3, 5) // call with explicit `this`
|
||||
```
|
||||
|
||||
Inheritance:
|
||||
```fun
|
||||
class Parent(number start)
|
||||
value = 0
|
||||
fun _construct(this, s)
|
||||
this.value = s
|
||||
|
||||
fun describe(this)
|
||||
return "Parent(value=" + to_string(this.value) + ")"
|
||||
|
||||
class Child(number start) extends Parent
|
||||
bonus = 5
|
||||
fun _construct(this, s)
|
||||
// runs after parent fields merged
|
||||
this.value = this.value + this.bonus
|
||||
fun describe(this)
|
||||
return "Child(value=" + to_string(this.value) + ", bonus=" + to_string(this.bonus) + ")"
|
||||
```
|
||||
|
||||
`typeof` on classes and instances:
|
||||
- `typeof(Point) == "Class"`
|
||||
- `typeof(p) == "Point(10, -2)"` (implementation-specific descriptive form)
|
||||
|
||||
---
|
||||
|
||||
## 9) Modules and Includes
|
||||
|
||||
Include sources:
|
||||
- System/stdlib: angle brackets search the Fun library path (e.g., `FUN_LIB_DIR`).
|
||||
```fun
|
||||
#include <utils/math.fun>
|
||||
```
|
||||
- Local file: quoted, relative to the current working directory or file.
|
||||
```fun
|
||||
#include "./utils/file.fun"
|
||||
```
|
||||
- Absolute path is supported.
|
||||
|
||||
Namespaced includes:
|
||||
- Use `as` to bind a module into a namespace alias.
|
||||
```fun
|
||||
#include <utils/math.fun> as m
|
||||
#include "examples/namespaced_mod.fun" as mod
|
||||
|
||||
print(m.add(2, 3))
|
||||
g = mod.Greeter("Hi")
|
||||
g.say("World")
|
||||
```
|
||||
|
||||
Aliased access uses `alias.symbol` or `alias.ClassName`.
|
||||
|
||||
Global/private at file scope control symbol exports from a module.
|
||||
|
||||
---
|
||||
|
||||
## 10) Collections
|
||||
|
||||
Arrays:
|
||||
- Literals with `[ ... ]`; may be heterogeneous unless `array<T>` is declared.
|
||||
- Helpers: `push`, `join`, `map`, `filter`, `reduce`, iteration via `for x in arr`.
|
||||
|
||||
Maps:
|
||||
- Literal: `{ key: value, ... }`
|
||||
- Indexing: `m["a"]`, assignment `m["c"] = 5`
|
||||
- Introspection: `has(m, key)`, `keys(m)`, `values(m)`
|
||||
- Iterate keys/values using arrays returned by `keys`/`values`.
|
||||
|
||||
---
|
||||
|
||||
## 11) Concurrency (Threads)
|
||||
|
||||
- `thread_spawn(fn, args)` starts `fn` in a new thread. `args` may be a single value or an array for multiple arguments.
|
||||
- `thread_join(id)` waits for the thread and returns its result.
|
||||
- `sleep(ms)` suspends current thread.
|
||||
|
||||
Example:
|
||||
```fun
|
||||
fun square(n)
|
||||
sleep(100)
|
||||
return n * n
|
||||
|
||||
ids = []
|
||||
for x in [1, 2, 3]
|
||||
push(ids, thread_spawn(square, x))
|
||||
|
||||
results = []
|
||||
for id in ids
|
||||
push(results, thread_join(id))
|
||||
|
||||
print(results)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12) System, Files, Environment, and Networking
|
||||
|
||||
Processes:
|
||||
- Blocking: `exec(cmd) -> string` (stdout), `system(cmd) -> number` (exit code)
|
||||
- Non-blocking: `nexec`, `nspawn`, `nsystem`, with `wait(pid)`, `read(pid)`, `kill(pid)` helpers
|
||||
|
||||
Environment and CLI:
|
||||
- `env(NAME) -> string` to read env variables (see `os_env.fun`)
|
||||
- `argv() -> array<string>` from `<cli.fun>`; also `FUN_ARGC`/`FUN_ARGS` environment interoperability in examples
|
||||
|
||||
Files:
|
||||
- Basic file I/O helpers exist in the stdlib; see `file_io.fun`, `file_print_for_file_line_by_line.fun`
|
||||
|
||||
Time:
|
||||
- Date/time and timers (see `datetime_basic.fun`, `datetime_extended.fun`, `datetime_timer.fun`)
|
||||
|
||||
Random:
|
||||
- `random` helpers (see `random_demo.fun`, `random_number_example.fun`)
|
||||
|
||||
Regex:
|
||||
- Regex operations via stdlib (see `regex_demo.fun`, `regex_procedural.fun`)
|
||||
|
||||
Networking:
|
||||
- TCP client helpers: `tcp_connect(host, port) -> fd`, `sock_send(fd, data)`, `sock_recv(fd, nbytes)`, `sock_close(fd)`
|
||||
- Unix domain sockets (see `unix_socket_echo.fun`)
|
||||
|
||||
Serial:
|
||||
- Serial port helpers (see `serial_demo.fun`)
|
||||
|
||||
Progress/UI:
|
||||
- Helper functions to render CLI progress (see `progress.fun`, `progress_inline.fun`)
|
||||
|
||||
Note: function names may live in stdlib modules; import accordingly (system-dependent availability).
|
||||
|
||||
---
|
||||
|
||||
## 13) Error Handling and Type Safety
|
||||
|
||||
- No implicit type coercion between numeric types; explicit casts or constructors are required.
|
||||
- Overflow/underflow on fixed-width types is an error.
|
||||
- Accessing undefined variables or re-defining globals is a compile-time error.
|
||||
- Shadowing internal/runtime functions is forbidden.
|
||||
- Exception syntax `try/catch/finally` is standardized in v0.3; throwing and catching at runtime may be partially implemented depending on the feature (see examples like `byte_overflow_try_catch.fun` and notes within).
|
||||
|
||||
---
|
||||
|
||||
## 14) Examples (from the repository)
|
||||
|
||||
Hello:
|
||||
```fun
|
||||
print("Hello, World!")
|
||||
```
|
||||
|
||||
Namespaced includes:
|
||||
```fun
|
||||
#include <utils/math.fun> as m
|
||||
print(m.add(2, 3))
|
||||
```
|
||||
|
||||
Classes:
|
||||
```fun
|
||||
class Counter
|
||||
value = 0
|
||||
fun inc(this)
|
||||
this.value = this.value + 1
|
||||
return this.value
|
||||
|
||||
c = Counter()
|
||||
print(c.inc())
|
||||
```
|
||||
|
||||
Inheritance:
|
||||
```fun
|
||||
class Parent(number start)
|
||||
value = 0
|
||||
fun _construct(this, s)
|
||||
this.value = s
|
||||
|
||||
class Child(number start) extends Parent
|
||||
bonus = 5
|
||||
fun _construct(this, s)
|
||||
this.value = this.value + this.bonus
|
||||
```
|
||||
|
||||
Threads:
|
||||
```fun
|
||||
tid = thread_spawn(add3, [10, 20, 30])
|
||||
print(thread_join(tid)) // 60
|
||||
```
|
||||
|
||||
TCP GET:
|
||||
```fun
|
||||
fd = tcp_connect("example.org", 80)
|
||||
req = "GET / HTTP/1.0\r\nHost: example.org\r\n\r\n"
|
||||
sent = sock_send(fd, req)
|
||||
print(sock_recv(fd, 8192))
|
||||
sock_close(fd)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 15) Versioning and Compatibility
|
||||
|
||||
- v0.3 keeps v0.2 syntax intact and adds new features. Where runtime support is evolving (exceptions, some stdlib facets), the syntax is stable and forward-compatible.
|
||||
- Examples are authoritative for idioms and available helpers; consult `examples/` and `lib/` modules.
|
||||
|
||||
---
|
||||
|
||||
## 16) Appendix: Notation and Conventions
|
||||
|
||||
- Use backticks for code identifiers in prose (`fun`, `class`, `extends`, etc.).
|
||||
- All code blocks are in `fun` pseudolanguage.
|
||||
- Indentation is always 2 spaces; tabs will cause errors.
|
||||
|
||||
---
|
||||
|
||||
## Changelog (from v0.2 to v0.3)
|
||||
|
||||
- Added: `class`, methods with explicit `this`, default and custom constructors via `_construct`.
|
||||
- Added: inheritance with `extends`.
|
||||
- Added: private members by leading underscore naming convention.
|
||||
- Added: dot-call sugar `obj.method(a, b)` ≡ `method(obj, a, b)`.
|
||||
- Added: namespaced includes: `#include <path> as alias`, `#include "path" as alias`.
|
||||
- Added: `map` type with literals `{ key: value }` and helpers.
|
||||
- Added: loop control `break`, `continue`.
|
||||
- Added: threads (`thread_spawn`, `thread_join`), sleep.
|
||||
- Added: socket helpers (TCP/Unix), serial devices, CLI argv, env helpers, timers.
|
||||
- Added: bitwise helper functions: `band`, `bor`, `bxor`, `bnot`, `shl`.
|
||||
- Added: exception syntax `try/catch/finally` (runtime handling is evolving).
|
||||
|
||||
---
|
||||
|
||||
## How to use this file
|
||||
|
||||
- Save this content as `./spec/v0.3.md` in the repository.
|
||||
- Keep `examples/` in sync with this spec. When adding a new feature, provide an example and update the spec accordingly.
|
||||
5
src/CMakeLists.txt
Normal file
5
src/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# This file is intentionally minimal. Targets have been moved to cmake/Targets.cmake
|
||||
# to keep the source tree clean. Keeping this include preserves compatibility if
|
||||
# someone adds this directory as a subdirectory in the future.
|
||||
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/Targets.cmake)
|
||||
260
src/repl.c
260
src/repl.c
|
|
@ -158,16 +158,28 @@ static void rl_word_right(const char *out, size_t len, size_t *pos) {
|
|||
while (*pos < len && out[*pos] == ' ') (*pos)++;
|
||||
}
|
||||
|
||||
/* Expand file path for ":load " completion in-place; returns 1 if buffer changed (redraw) */
|
||||
/* Expand file path for path-taking REPL commands (e.g., :load, :run) in-place; returns 1 if buffer changed (redraw) */
|
||||
static int complete_load_path(char *buf, size_t *len_io) {
|
||||
size_t len = *len_io;
|
||||
if (len < 5) return 0;
|
||||
if (len < 3) return 0; /* minimally ":x" */
|
||||
const char *p = buf;
|
||||
while (*p == ' ') p++;
|
||||
if (*p != ':') return 0;
|
||||
p++;
|
||||
if (strncmp(p, "load", 4) != 0) return 0;
|
||||
p += 4;
|
||||
/* Parse command token (letters only) */
|
||||
const char *cmd_start = p;
|
||||
while (*p && ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z'))) p++;
|
||||
size_t cmd_len = (size_t)(p - cmd_start);
|
||||
if (cmd_len == 0) return 0;
|
||||
/* Accept both full and short aliases for :load and :run */
|
||||
int is_supported = 0;
|
||||
if ((cmd_len == 4 && strncmp(cmd_start, "load", 4) == 0) ||
|
||||
(cmd_len == 2 && strncmp(cmd_start, "lo", 2) == 0) ||
|
||||
(cmd_len == 3 && strncmp(cmd_start, "run", 3) == 0) ||
|
||||
(cmd_len == 2 && strncmp(cmd_start, "ru", 2) == 0)) {
|
||||
is_supported = 1;
|
||||
}
|
||||
if (!is_supported) return 0;
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
size_t arg_off = (size_t)(p - buf);
|
||||
if (arg_off > len) return 0;
|
||||
|
|
@ -840,40 +852,41 @@ static int buffer_looks_incomplete(const char *buf) {
|
|||
|
||||
static void show_repl_help(void) {
|
||||
printf("Commands:\n");
|
||||
printf(" :help Show this help\n");
|
||||
printf(" :quit | :q | :exit Exit the REPL\n");
|
||||
printf(" :reset Reset VM state (clears globals)\n");
|
||||
printf(" :dump | :globals Dump current globals\n");
|
||||
printf(" :globals [pattern] Dump globals filtering by value substring\n");
|
||||
printf(" :vars [pattern] Alias for :globals\n");
|
||||
printf(" :clear Clear current input buffer\n");
|
||||
printf(" :print Show current buffer\n");
|
||||
printf(" :run Execute current buffer immediately\n");
|
||||
printf(" :profile Execute buffer and show timing + instruction count\n");
|
||||
printf(" :save <file> Save current buffer to file\n");
|
||||
printf(" :load <file> Load file into buffer (does not run)\n");
|
||||
printf(" :paste [run] Enter paste mode; end with a single '.' line (optional 'run')\n");
|
||||
printf(" :history [N] Show last N lines of history (default 50)\n");
|
||||
printf(" :time on|off|toggle Toggle/enable/disable timing\n");
|
||||
printf(" :env [NAME[=VALUE]] Get or set environment variable\n");
|
||||
printf(" :backtrace | :bt Show backtrace of VM frames (most recent first)\n");
|
||||
printf(" :frame N Select frame N for :locals/:list/:disas (default: top)\n");
|
||||
printf(" :list [±K] Show K lines of source around current frame line (default 5)\n");
|
||||
printf(" :disas [±N] Disassemble around current frame ip (default 5)\n");
|
||||
printf(" :disasm WHAT [off [len]] [to <file>] Hexdump VM memory region to screen or file\n");
|
||||
printf(" :help | :h Show this help\n");
|
||||
printf(" :quit | :q | :exit Exit the REPL\n");
|
||||
printf(" :reset | :re Reset VM state (clears globals)\n");
|
||||
printf(" :dump | :du | :globals | :gl Dump current globals\n");
|
||||
printf(" :globals [pattern] Dump globals filtering by value substring\n");
|
||||
printf(" :vars | :v [pattern] Alias for :globals\n");
|
||||
printf(" :clear | :cl Clear current input buffer\n");
|
||||
printf(" :print | :pr Show current buffer\n");
|
||||
printf(" :run | :ru [file] Execute current buffer or the given file immediately\n");
|
||||
printf(" :profile | :pf Execute buffer and show timing + instruction count\n");
|
||||
printf(" :save | :sa <file> Save current buffer to file\n");
|
||||
printf(" :load | :lo <file> Load file into buffer (does not run)\n");
|
||||
printf(" :paste | :pa [run] Enter paste mode; end with a single '.' line (optional 'run')\n");
|
||||
printf(" :history | :hi [N] Show last N lines of history (default 50)\n");
|
||||
printf(" :time | :ti on|off|toggle Toggle/enable/disable timing\n");
|
||||
printf(" :env | :en [NAME[=VALUE]] Get or set environment variable\n");
|
||||
printf(" :backtrace | :bt | :ba Show backtrace of VM frames (most recent first)\n");
|
||||
printf(" :frame | :fr N Select frame N for :locals/:list/:disasm (default: top)\n");
|
||||
printf(" :list | :li [±K] Show K lines of source around current frame line (default 5)\n");
|
||||
printf(" :disasm | :di [±N] Disassemble around current frame ip (default 5)\n");
|
||||
printf(" :mdump | :md WHAT [offset [len]] [raw] [to <file>] Dump VM memory region\n");
|
||||
printf(" WHAT = code | stack | globals | consts\n");
|
||||
printf(" :stack [N] Show top N (default all) stack values\n");
|
||||
printf(" :top Show the top of the VM stack\n");
|
||||
printf(" :locals [FRAME] Show locals of frame (default: selected frame)\n");
|
||||
printf(" :printv WHAT Print value: local[i] | stack[i] | global[i]\n");
|
||||
printf(" :break [file:]line Set a breakpoint (default file = current frame file)\n");
|
||||
printf(" :info breaks List breakpoints\n");
|
||||
printf(" :delete ID Delete breakpoint by ID\n");
|
||||
printf(" :clear breaks Remove all breakpoints\n");
|
||||
printf(" :cont Continue execution (exit REPL if in debug stop)\n");
|
||||
printf(" :step Step one instruction\n");
|
||||
printf(" :next Step over (current frame)\n");
|
||||
printf(" :finish Run until the current frame returns\n");
|
||||
printf(" 'raw' writes binary bytes instead of a formatted hexdump\n");
|
||||
printf(" :stack | :st [N] Show top N (default all) stack values\n");
|
||||
printf(" :top | :to Show the top of the VM stack\n");
|
||||
printf(" :locals | :lc [FRAME] Show locals of frame (default: selected frame)\n");
|
||||
printf(" :printv | :pv WHAT Print value: local[i] | stack[i] | global[i]\n");
|
||||
printf(" :break | :br [file:]line Set a breakpoint (default file = current frame file)\n");
|
||||
printf(" :info | :in breaks List breakpoints\n");
|
||||
printf(" :delete | :de ID Delete breakpoint by ID\n");
|
||||
printf(" :clear breaks | :cb Remove all breakpoints\n");
|
||||
printf(" :cont | :co Continue execution (exit REPL if in debug stop)\n");
|
||||
printf(" :step | :sp Step one instruction\n");
|
||||
printf(" :next | :ne Step over (current frame)\n");
|
||||
printf(" :finish | :fi Run until the current frame returns\n");
|
||||
}
|
||||
|
||||
static char *read_entire_file(const char *path, size_t *out_len) {
|
||||
|
|
@ -900,6 +913,15 @@ static int write_entire_file(const char *path, const char *data, size_t len) {
|
|||
return n == len;
|
||||
}
|
||||
|
||||
/* ---------- REPL command matching helper ---------- */
|
||||
static int cmd_is_one_of(const char *cmd, const char *const names[]) {
|
||||
if (!cmd || !*cmd) return 0;
|
||||
for (int i = 0; names[i] != NULL; ++i) {
|
||||
if (strcmp(cmd, names[i]) == 0) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------- Hexdump helper ---------- */
|
||||
static void hexdump_to(FILE *out, const unsigned char *data, size_t len, size_t base_off) {
|
||||
if (!out || !data || len == 0) return;
|
||||
|
|
@ -1071,19 +1093,19 @@ int fun_run_repl(VM *vm) {
|
|||
char arg[2048] = {0};
|
||||
sscanf(line, ":%63s %2047[^\n]", cmd, arg);
|
||||
|
||||
if (strcmp(cmd, "quit") == 0 || strcmp(cmd, "q") == 0 || strcmp(cmd, "exit") == 0) {
|
||||
if (cmd_is_one_of(cmd, (const char*[]){"quit","q","qu","exit", NULL})) {
|
||||
break;
|
||||
} else if (strcmp(cmd, "help") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"help","h", NULL})) {
|
||||
show_repl_help();
|
||||
continue;
|
||||
} else if (strcmp(cmd, "reset") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"reset","re", NULL})) {
|
||||
vm_reset(vm);
|
||||
printf("VM state reset.\n");
|
||||
continue;
|
||||
} else if (strcmp(cmd, "dump") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"dump","du", NULL})) {
|
||||
vm_dump_globals(vm);
|
||||
continue;
|
||||
} else if (strcmp(cmd, "globals") == 0 || strcmp(cmd, "vars") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"globals","vars","gl","v","va", NULL})) {
|
||||
const char *pattern = lstrip(arg);
|
||||
int filtered = (pattern && *pattern);
|
||||
printf("=== globals%s%s ===\n", filtered ? " matching '" : "", filtered ? pattern : "");
|
||||
|
|
@ -1098,11 +1120,11 @@ int fun_run_repl(VM *vm) {
|
|||
}
|
||||
printf("===============\n");
|
||||
continue;
|
||||
} else if (strcmp(cmd, "clear") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"clear","cl", NULL})) {
|
||||
buflen = 0;
|
||||
printf("(buffer cleared)\n");
|
||||
continue;
|
||||
} else if (strcmp(cmd, "print") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"print","pr", NULL})) {
|
||||
if (buflen == 0) printf("(buffer empty)\n");
|
||||
else {
|
||||
if (buflen >= bufcap) {
|
||||
|
|
@ -1114,28 +1136,46 @@ int fun_run_repl(VM *vm) {
|
|||
if (buflen > 0 && buffer[buflen-1] != '\n') printf("\n");
|
||||
}
|
||||
continue;
|
||||
} else if (strcmp(cmd, "run") == 0 || strcmp(cmd, "profile") == 0) {
|
||||
if (buflen == 0) {
|
||||
printf("(buffer empty)\n");
|
||||
continue;
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"run","ru","profile","pf", NULL})) {
|
||||
int is_profile = cmd_is_one_of(cmd, (const char*[]){"profile","pf", NULL});
|
||||
// 'arg' is a fixed-size local array, so its address is always non-null.
|
||||
// Only check whether it contains a non-empty string.
|
||||
int from_file = (arg[0] != '\0');
|
||||
|
||||
const char *src = NULL;
|
||||
char *filebuf = NULL;
|
||||
if (from_file) {
|
||||
size_t flen = 0;
|
||||
filebuf = read_entire_file(arg, &flen);
|
||||
if (!filebuf) {
|
||||
printf("Failed to load '%s'\n", arg);
|
||||
continue;
|
||||
}
|
||||
src = filebuf;
|
||||
} else {
|
||||
if (buflen == 0) {
|
||||
printf("(buffer empty)\n");
|
||||
continue;
|
||||
}
|
||||
if (buflen + 1 > bufcap) {
|
||||
buffer = (char*)realloc(buffer, buflen + 1);
|
||||
bufcap = buflen + 1;
|
||||
}
|
||||
buffer[buflen] = '\0';
|
||||
src = buffer;
|
||||
}
|
||||
if (buflen + 1 > bufcap) {
|
||||
buffer = (char*)realloc(buffer, buflen + 1);
|
||||
bufcap = buflen + 1;
|
||||
}
|
||||
buffer[buflen] = '\0';
|
||||
|
||||
clock_t t_parse0 = 0, t_parse1 = 0, t_run0 = 0, t_run1 = 0;
|
||||
if (strcmp(cmd, "profile") == 0) t_parse0 = clock();
|
||||
Bytecode *bc = parse_string_to_bytecode(buffer);
|
||||
if (strcmp(cmd, "profile") == 0) t_parse1 = clock();
|
||||
if (is_profile) t_parse0 = clock();
|
||||
Bytecode *bc = parse_string_to_bytecode(src);
|
||||
if (is_profile) t_parse1 = clock();
|
||||
|
||||
if (bc) {
|
||||
if (repl_timing || strcmp(cmd, "profile") == 0) t_run0 = clock();
|
||||
if (repl_timing || is_profile) t_run0 = clock();
|
||||
vm_run(vm, bc);
|
||||
if (repl_timing || strcmp(cmd, "profile") == 0) t_run1 = clock();
|
||||
if (repl_timing || is_profile) t_run1 = clock();
|
||||
|
||||
if (strcmp(cmd, "profile") == 0) {
|
||||
if (is_profile) {
|
||||
double ms_parse = (double)(t_parse1 - t_parse0) * 1000.0 / (double)CLOCKS_PER_SEC;
|
||||
double ms_run = (double)(t_run1 - t_run0) * 1000.0 / (double)CLOCKS_PER_SEC;
|
||||
printf("[profile] parse: %.2f ms, run: %.2f ms, total: %.2f ms, instr: %lld\n",
|
||||
|
|
@ -1148,14 +1188,14 @@ int fun_run_repl(VM *vm) {
|
|||
vm_print_output(vm);
|
||||
vm_clear_output(vm);
|
||||
bytecode_free(bc);
|
||||
append_history(hist, buffer);
|
||||
if (!from_file) append_history(hist, buffer);
|
||||
} else {
|
||||
int line_no = 0, col_no = 0;
|
||||
char emsg[256];
|
||||
if (parser_last_error(emsg, sizeof(emsg), &line_no, &col_no)) {
|
||||
printf("Parse error at %d:%d: %s\n", line_no, col_no, emsg);
|
||||
int cur_line = 1;
|
||||
const char *p = buffer;
|
||||
const char *p = src ? src : buffer;
|
||||
while (*p && cur_line < line_no) {
|
||||
if (*p == '\n') cur_line++;
|
||||
p++;
|
||||
|
|
@ -1167,7 +1207,7 @@ int fun_run_repl(VM *vm) {
|
|||
for (int i = 1; i < col_no; ++i) putchar(' ');
|
||||
printf("^\n");
|
||||
#ifdef FUN_DEBUG
|
||||
if (hist) {
|
||||
if (hist && !from_file) {
|
||||
fprintf(hist, "// ERROR %d:%d: %s\n", line_no, col_no, emsg);
|
||||
fflush(hist);
|
||||
}
|
||||
|
|
@ -1175,16 +1215,20 @@ int fun_run_repl(VM *vm) {
|
|||
} else {
|
||||
printf("Parse error.\n");
|
||||
#ifdef FUN_DEBUG
|
||||
if (hist) {
|
||||
if (hist && !from_file) {
|
||||
fprintf(hist, "// ERROR: parse error\n");
|
||||
fflush(hist);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
buflen = 0;
|
||||
if (from_file) {
|
||||
free(filebuf);
|
||||
} else {
|
||||
buflen = 0; /* keep behavior: clear buffer only when running current buffer */
|
||||
}
|
||||
continue;
|
||||
} else if (strcmp(cmd, "save") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"save","sa", NULL})) {
|
||||
if (arg[0] == '\0') { printf("Usage: :save <file>\n"); continue; }
|
||||
if (buflen == 0) { printf("(buffer empty)\n"); continue; }
|
||||
if (!write_entire_file(arg, buffer, buflen)) {
|
||||
|
|
@ -1193,7 +1237,7 @@ int fun_run_repl(VM *vm) {
|
|||
printf("Saved %zu bytes to '%s'\n", buflen, arg);
|
||||
}
|
||||
continue;
|
||||
} else if (strcmp(cmd, "load") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"load","lo", NULL})) {
|
||||
if (arg[0] == '\0') { printf("Usage: :load <file>\n"); continue; }
|
||||
size_t flen = 0;
|
||||
char *filebuf = read_entire_file(arg, &flen);
|
||||
|
|
@ -1212,7 +1256,7 @@ int fun_run_repl(VM *vm) {
|
|||
free(filebuf);
|
||||
printf("Loaded %zu bytes into buffer. Use :run or submit an empty line to execute.\n", buflen);
|
||||
continue;
|
||||
} else if (strcmp(cmd, "paste") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"paste","pa", NULL})) {
|
||||
int run_after = 0;
|
||||
const char *opt = lstrip(arg);
|
||||
if (opt && (strcmp(opt, "run") == 0 || strcmp(opt, "exec") == 0)) run_after = 1;
|
||||
|
|
@ -1275,13 +1319,13 @@ int fun_run_repl(VM *vm) {
|
|||
printf("(pasted %zu bytes into buffer)\n", buflen);
|
||||
}
|
||||
continue;
|
||||
} else if (strcmp(cmd, "history") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"history","hi", NULL})) {
|
||||
int n = 50;
|
||||
if (arg[0] != '\0') n = atoi(arg);
|
||||
if (n <= 0) n = 50;
|
||||
print_last_n_lines(hist_path, n);
|
||||
continue;
|
||||
} else if (strcmp(cmd, "time") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"time","ti", NULL})) {
|
||||
if (strcmp(lstrip(arg), "on") == 0) repl_timing = 1;
|
||||
else if (strcmp(lstrip(arg), "off") == 0) repl_timing = 0;
|
||||
else if (strcmp(lstrip(arg), "toggle") == 0) repl_timing = !repl_timing;
|
||||
|
|
@ -1291,7 +1335,7 @@ int fun_run_repl(VM *vm) {
|
|||
}
|
||||
printf("Timing %s\n", repl_timing ? "enabled" : "disabled");
|
||||
continue;
|
||||
} else if (strcmp(cmd, "env") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"env","en", NULL})) {
|
||||
const char *spec = lstrip(arg);
|
||||
if (!spec || *spec == '\0') {
|
||||
env_show_usage();
|
||||
|
|
@ -1310,7 +1354,7 @@ int fun_run_repl(VM *vm) {
|
|||
env_set(name, val);
|
||||
}
|
||||
continue;
|
||||
} else if (strcmp(cmd, "backtrace") == 0 || strcmp(cmd, "bt") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"backtrace","bt","ba", NULL})) {
|
||||
if (vm->fp < 0) { printf("(no frames)\n"); continue; }
|
||||
printf("Backtrace (most recent call first):\n");
|
||||
for (int i = vm->fp; i >= 0; --i) {
|
||||
|
|
@ -1321,7 +1365,7 @@ int fun_run_repl(VM *vm) {
|
|||
printf(" #%d %s at %s ip=%d line=%d\n", i, fname, sfile, ip, vm->current_line);
|
||||
}
|
||||
continue;
|
||||
} else if (strcmp(cmd, "stack") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"stack","st", NULL})) {
|
||||
int n = -1;
|
||||
const char *p = lstrip(arg);
|
||||
if (p && *p) n = atoi(p);
|
||||
|
|
@ -1336,7 +1380,7 @@ int fun_run_repl(VM *vm) {
|
|||
free(sv);
|
||||
}
|
||||
continue;
|
||||
} else if (strcmp(cmd, "locals") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"locals","lc", NULL})) {
|
||||
int idx = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp;
|
||||
const char *p = lstrip(arg);
|
||||
if (p && *p) {
|
||||
|
|
@ -1358,7 +1402,7 @@ int fun_run_repl(VM *vm) {
|
|||
}
|
||||
if (!any) printf(" (no non-nil locals)\n");
|
||||
continue;
|
||||
} else if (strcmp(cmd, "frame") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"frame","fr", NULL})) {
|
||||
const char *p = lstrip(arg);
|
||||
if (!p || !*p) { printf("Usage: :frame N\n"); continue; }
|
||||
int v = atoi(p);
|
||||
|
|
@ -1369,7 +1413,7 @@ int fun_run_repl(VM *vm) {
|
|||
const char *sfile = (f->fn && f->fn->source_file) ? f->fn->source_file : "<unknown>";
|
||||
printf("Selected frame #%d: %s (%s)\n", selected_frame, fname, sfile);
|
||||
continue;
|
||||
} else if (strcmp(cmd, "list") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"list","li", NULL})) {
|
||||
int k = 5;
|
||||
const char *p = lstrip(arg);
|
||||
if (p && *p) k = atoi(p);
|
||||
|
|
@ -1408,7 +1452,7 @@ int fun_run_repl(VM *vm) {
|
|||
}
|
||||
free(src);
|
||||
continue;
|
||||
} else if (strcmp(cmd, "disas") == 0 || strcmp(cmd, "disassemble") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"disasm","disassemble","di", NULL})) {
|
||||
int n = 5;
|
||||
const char *p = lstrip(arg);
|
||||
if (p && *p) n = atoi(p);
|
||||
|
|
@ -1439,26 +1483,29 @@ int fun_run_repl(VM *vm) {
|
|||
printf("%c %6d: %-14s %d\n", (i == curip ? '>' : ' '), i, opname, ins.operand);
|
||||
}
|
||||
continue;
|
||||
} else if (strcmp(cmd, "disasm") == 0) {
|
||||
/* Syntax: :disasm WHAT [off [len]] [to <file>]
|
||||
WHAT: code | stack | globals | consts */
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"mdump","md", NULL})) {
|
||||
/* Syntax: :mdump WHAT [offset [len]] [raw] [to <file>]
|
||||
WHAT: code | stack | globals | consts
|
||||
'raw' writes binary bytes instead of a formatted hexdump */
|
||||
const char *p = lstrip(arg);
|
||||
if (!p || !*p) {
|
||||
printf("Usage: :disasm WHAT [off [len]] [to <file>]\n");
|
||||
printf("Usage: :mdump WHAT [offset [len]] [raw] [to <file>]\n");
|
||||
printf(" WHAT = code | stack | globals | consts\n");
|
||||
printf(" 'raw' writes binary bytes instead of a formatted hexdump\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
char what[32];
|
||||
int consumed = 0;
|
||||
if (sscanf(p, "%31s %n", what, &consumed) != 1) {
|
||||
printf("Usage: :disasm WHAT [off [len]] [to <file>]\n");
|
||||
printf("Usage: :mdump WHAT [offset [len]] [raw] [to <file>]\n");
|
||||
continue;
|
||||
}
|
||||
p += consumed;
|
||||
|
||||
size_t off = 0;
|
||||
size_t len = (size_t)-1; /* default later to clamp */
|
||||
int want_raw = 0; /* output raw bytes instead of hexdump */
|
||||
|
||||
/* parse optional off */
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
|
|
@ -1481,6 +1528,13 @@ int fun_run_repl(VM *vm) {
|
|||
}
|
||||
}
|
||||
|
||||
/* optional: 'raw' keyword */
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
if (strncmp(p, "raw", 3) == 0 && (p[3] == '\0' || isspace((unsigned char)p[3]))) {
|
||||
want_raw = 1;
|
||||
p += 3;
|
||||
}
|
||||
|
||||
/* optional: to <file> */
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
int to_file = 0;
|
||||
|
|
@ -1546,17 +1600,29 @@ int fun_run_repl(VM *vm) {
|
|||
}
|
||||
|
||||
if (!to_file) {
|
||||
printf("Hexdump %s: total=%zu, offset=%zu, len=%zu\n", what, total, off, len);
|
||||
hexdump_to(stdout, base + off, len, off);
|
||||
if (want_raw) {
|
||||
/* Write raw bytes directly to stdout with no header */
|
||||
fwrite(base + off, 1, len, stdout);
|
||||
fflush(stdout);
|
||||
} else {
|
||||
printf("Hexdump %s: total=%zu, offset=%zu, len=%zu\n", what, total, off, len);
|
||||
hexdump_to(stdout, base + off, len, off);
|
||||
}
|
||||
} else {
|
||||
FILE *fout = fopen(path, "wb");
|
||||
if (!fout) { printf("Failed to open '%s' for writing\n", path); continue; }
|
||||
hexdump_to(fout, base + off, len, off);
|
||||
fclose(fout);
|
||||
printf("Wrote hexdump (%zu bytes from %s) to %s\n", len, what, path);
|
||||
if (want_raw) {
|
||||
fwrite(base + off, 1, len, fout);
|
||||
fclose(fout);
|
||||
printf("Wrote raw bytes (%zu from %s) to %s\n", len, what, path);
|
||||
} else {
|
||||
hexdump_to(fout, base + off, len, off);
|
||||
fclose(fout);
|
||||
printf("Wrote hexdump (%zu bytes from %s) to %s\n", len, what, path);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
} else if (strcmp(cmd, "printv") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"printv","pv", NULL})) {
|
||||
const char *spec = lstrip(arg);
|
||||
if (!spec || !*spec) { printf("Usage: :printv local[i] | stack[i] | global[i]\n"); continue; }
|
||||
int idx = -1;
|
||||
|
|
@ -1581,13 +1647,13 @@ int fun_run_repl(VM *vm) {
|
|||
printf("Usage: :printv local[i] | stack[i] | global[i]\n");
|
||||
}
|
||||
continue;
|
||||
} else if (strcmp(cmd, "top") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"top","to", NULL})) {
|
||||
if (vm->sp < 0) { printf("(stack empty)\n"); continue; }
|
||||
char *sv = value_to_string_alloc(&vm->stack[vm->sp]);
|
||||
printf("%s\n", sv ? sv : "nil");
|
||||
free(sv);
|
||||
continue;
|
||||
} else if (strcmp(cmd, "break") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"break","br", NULL})) {
|
||||
const char *p = lstrip(arg);
|
||||
if (!p || !*p) { printf("Usage: :break [file:]line\n"); continue; }
|
||||
const char *colon = strchr(p, ':');
|
||||
|
|
@ -1613,7 +1679,7 @@ int fun_run_repl(VM *vm) {
|
|||
if (id >= 0) printf("Breakpoint %d set at %s:%d\n", id, filebuf, line);
|
||||
else printf("Failed to set breakpoint\n");
|
||||
continue;
|
||||
} else if (strcmp(cmd, "info") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"info","in", NULL})) {
|
||||
const char *what = lstrip(arg);
|
||||
if (what && strcmp(what, "breaks") == 0) {
|
||||
vm_debug_list_breakpoints(vm);
|
||||
|
|
@ -1621,11 +1687,15 @@ int fun_run_repl(VM *vm) {
|
|||
printf("Usage: :info breaks\n");
|
||||
}
|
||||
continue;
|
||||
} else if (strcmp(cmd, "delete") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"delete","de", NULL})) {
|
||||
int id = atoi(lstrip(arg));
|
||||
if (vm_debug_delete_breakpoint(vm, id)) printf("Deleted breakpoint %d\n", id);
|
||||
else printf("No such breakpoint %d\n", id);
|
||||
continue;
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"cb", NULL})) {
|
||||
vm_debug_clear_breakpoints(vm);
|
||||
printf("Cleared all breakpoints\n");
|
||||
continue;
|
||||
} else if (strcmp(cmd, "clear") == 0) {
|
||||
const char *what = lstrip(arg);
|
||||
if (what && strcmp(what, "breaks") == 0) {
|
||||
|
|
@ -1635,22 +1705,22 @@ int fun_run_repl(VM *vm) {
|
|||
printf("Usage: :clear breaks\n");
|
||||
}
|
||||
continue;
|
||||
} else if (strcmp(cmd, "cont") == 0 || strcmp(cmd, "continue") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"cont","continue","co", NULL})) {
|
||||
vm_debug_request_continue(vm);
|
||||
printf("Continuing...\n");
|
||||
if (vm->on_error_repl) return 0; /* exit REPL to continue execution */
|
||||
continue;
|
||||
} else if (strcmp(cmd, "step") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"step","sp", NULL})) {
|
||||
vm_debug_request_step(vm);
|
||||
printf("Stepping one instruction...\n");
|
||||
if (vm->on_error_repl) return 0;
|
||||
continue;
|
||||
} else if (strcmp(cmd, "next") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"next","ne", NULL})) {
|
||||
vm_debug_request_next(vm);
|
||||
printf("Stepping over...\n");
|
||||
if (vm->on_error_repl) return 0;
|
||||
continue;
|
||||
} else if (strcmp(cmd, "finish") == 0) {
|
||||
} else if (cmd_is_one_of(cmd, (const char*[]){"finish","fi", NULL})) {
|
||||
vm_debug_request_finish(vm);
|
||||
printf("Running until current frame returns...\n");
|
||||
if (vm->on_error_repl) return 0;
|
||||
|
|
|
|||
4
src/vm.c
4
src/vm.c
|
|
@ -23,6 +23,7 @@
|
|||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <time.h>
|
||||
#include <math.h>
|
||||
|
||||
#ifdef __unix__
|
||||
#include <sys/wait.h>
|
||||
|
|
@ -62,7 +63,8 @@
|
|||
/* Central INI handle registry and helpers */
|
||||
#include "vm/ini/handles.c"
|
||||
#endif
|
||||
/* Note: INI opcode handlers are included below; changes in vm/ini/*.c require vm.c to rebuild. */
|
||||
/* Note: INI opcode handlers are included below; changes in vm/ini/ .c files
|
||||
* require vm.c to rebuild. */
|
||||
#include "external/json.c"
|
||||
#include "external/libsql.c"
|
||||
#include "external/pcsc.c"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue