Compare commits
No commits in common. "d5f4a51d1239b46208449ad1efa18646a32be7cb" and "85b8d37b235160fc295d799e3fd45cae58bdf444" have entirely different histories.
d5f4a51d12
...
85b8d37b23
85 changed files with 800 additions and 1663 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -4,7 +4,7 @@ CMakeCache.txt
|
||||||
!.gitignore
|
!.gitignore
|
||||||
.venv
|
.venv
|
||||||
build*
|
build*
|
||||||
cmake_install.cmake
|
cmake*
|
||||||
database.sqlite
|
database.sqlite
|
||||||
demo_*
|
demo_*
|
||||||
dist/
|
dist/
|
||||||
|
|
|
||||||
577
CMakeLists.txt
577
CMakeLists.txt
|
|
@ -1,15 +1,543 @@
|
||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
project(fun VERSION 0.37.57 LANGUAGES C)
|
project(fun VERSION 0.37.48 LANGUAGES C)
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 99)
|
set(CMAKE_C_STANDARD 99)
|
||||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
include(${CMAKE_SOURCE_DIR}/cmake/Options.cmake)
|
# Defaults and options migrated from Makefile
|
||||||
include(${CMAKE_SOURCE_DIR}/cmake/Dependencies.cmake)
|
# Convenience: default path to bundled stdlib
|
||||||
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/Extensions.cmake)
|
set(FUN_LIB "${CMAKE_SOURCE_DIR}/lib" CACHE PATH "Path to bundled Fun stdlib")
|
||||||
include(${CMAKE_SOURCE_DIR}/cmake/Targets.cmake)
|
|
||||||
|
# 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()
|
||||||
|
|
||||||
# Convenience aggregate target (like 'build' in Makefile)
|
# Convenience aggregate target (like 'build' in Makefile)
|
||||||
add_custom_target(build
|
add_custom_target(build
|
||||||
|
|
@ -21,11 +549,11 @@ set(FUN_RUN_SCRIPT "" CACHE STRING "Script to run with the 'run' target, e.g. -D
|
||||||
|
|
||||||
if(FUN_WITH_REPL)
|
if(FUN_WITH_REPL)
|
||||||
add_custom_target(repl
|
add_custom_target(repl
|
||||||
COMMAND $<TARGET_FILE:fun>
|
COMMAND $<TARGET_FILE:fun>
|
||||||
DEPENDS fun
|
DEPENDS fun
|
||||||
USES_TERMINAL
|
USES_TERMINAL
|
||||||
COMMENT "Run Fun REPL"
|
COMMENT "Run Fun REPL"
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_custom_target(run
|
add_custom_target(run
|
||||||
|
|
@ -35,6 +563,21 @@ add_custom_target(run
|
||||||
COMMENT "Run Fun with script: ${FUN_RUN_SCRIPT}"
|
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
|
add_custom_target(ops
|
||||||
COMMAND ${_FUN_PY} ${CMAKE_SOURCE_DIR}/scripts/check_op_includes.py --verbose
|
COMMAND ${_FUN_PY} ${CMAKE_SOURCE_DIR}/scripts/check_op_includes.py --verbose
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
|
|
@ -49,6 +592,20 @@ add_custom_target(ops-quiet
|
||||||
COMMENT "Opcode include check"
|
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
|
# Clean and distclean
|
||||||
add_custom_target(fun_clean
|
add_custom_target(fun_clean
|
||||||
COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target clean
|
COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target clean
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
# 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()
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
# 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()
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
# 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 "--------------------------------")
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
# 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()
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
# 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()
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
# 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()
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
# 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()
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
# 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()
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
# 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()
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
# REPL toggle (affects sources of the fun executable)
|
|
||||||
option(FUN_WITH_REPL "Enable interactive REPL in the fun CLI" OFF)
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
# 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()
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
# 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()
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
# 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()
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
# 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)
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
# 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()
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
# 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)
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
# 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)
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
# 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)
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
#!/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 ===
|
|
||||||
*/
|
|
||||||
|
|
@ -1,314 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
|
|
@ -31,27 +31,29 @@ if obj != nil
|
||||||
if obj != nil
|
if obj != nil
|
||||||
print("Origin: " + to_string(obj["origin"]))
|
print("Origin: " + to_string(obj["origin"]))
|
||||||
|
|
||||||
// Possible output:
|
/* Possible output:
|
||||||
// Response: {
|
Response: {
|
||||||
// "args": {},
|
"args": {},
|
||||||
// "data": "",
|
"data": "",
|
||||||
// "files": {},
|
"files": {},
|
||||||
// "form": {
|
"form": {
|
||||||
// "lang": "fun",
|
"lang": "fun",
|
||||||
// "name": "Fun"
|
"name": "Fun"
|
||||||
// },
|
},
|
||||||
// "headers": {
|
"headers": {
|
||||||
// "Accept": "*/*",
|
"Accept": "*/*",
|
||||||
// "Content-Length": "17",
|
"Content-Length": "17",
|
||||||
// "Content-Type": "application/x-www-form-urlencoded",
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
// "Host": "httpbin.org",
|
"Host": "httpbin.org",
|
||||||
// "X-Amzn-Trace-Id": "Root=1-6944834b-74f499e251c322713e7dd9a8"
|
"X-Amzn-Trace-Id": "Root=1-6944834b-74f499e251c322713e7dd9a8"
|
||||||
// },
|
},
|
||||||
// "json": nil,
|
"json": null,
|
||||||
// "origin": "5.252.226.107",
|
"origin": "5.252.226.107",
|
||||||
// "url": "https://httpbin.org/post"
|
"url": "https://httpbin.org/post"
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// Content-Type: application/x-www-form-urlencoded
|
Content-Type: application/x-www-form-urlencoded
|
||||||
// Host: httpbin.org
|
Host: httpbin.org
|
||||||
// Origin: 5.252.226.107
|
Origin: 5.252.226.107
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
@ -1,37 +1,29 @@
|
||||||
|
|
||||||
[auth]
|
[auth]
|
||||||
user = "hanez"
|
user = "hanez"
|
||||||
token = "abcd1234"
|
token = "abcd1234"
|
||||||
|
|
||||||
|
|
||||||
[app]
|
[app]
|
||||||
name = "FunApp"
|
name = "FunApp"
|
||||||
version = "1.2.3"
|
version = "1.2.3"
|
||||||
debug = "1"
|
debug = "1"
|
||||||
|
|
||||||
|
|
||||||
[database]
|
[database]
|
||||||
host = "localhost"
|
host = "localhost"
|
||||||
port = "5432"
|
port = "5432"
|
||||||
user = "fun"
|
user = "fun"
|
||||||
pass = "secret"
|
pass = "secret"
|
||||||
pool_size = "8"
|
pool_size = "8"
|
||||||
timeout = "2.5"
|
timeout = "2.5"
|
||||||
|
|
||||||
|
|
||||||
[network]
|
[network]
|
||||||
ssl = "yes"
|
ssl = "yes"
|
||||||
retries = "3"
|
retries = "3"
|
||||||
base_url = "https://api.example.com"
|
base_url = "https://api.example.com"
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
feature_x = "on"
|
feature_x = "on"
|
||||||
feature_y = "off"
|
feature_y = "off"
|
||||||
|
|
||||||
|
|
||||||
[paths]
|
[paths]
|
||||||
data_dir = "./data"
|
data_dir = "./data"
|
||||||
log_file = "./logs/app.log"
|
log_file = "./logs/app.log"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,6 @@ if (res["code"] == 0)
|
||||||
else
|
else
|
||||||
print("Login failed!")
|
print("Login failed!")
|
||||||
|
|
||||||
// Propagate the child process exit status to the Fun program's exit code
|
|
||||||
exit res["code"]
|
|
||||||
|
|
||||||
/* Possible output:
|
/* Possible output:
|
||||||
Username: hanez
|
Username: hanez
|
||||||
Password:
|
Password:
|
||||||
70
make
70
make
|
|
@ -30,7 +30,9 @@ if [ "$target" = "all" ]; then
|
||||||
-DFUN_WITH_JSON=ON \
|
-DFUN_WITH_JSON=ON \
|
||||||
-DFUN_WITH_TCLTK=ON \
|
-DFUN_WITH_TCLTK=ON \
|
||||||
-DFUN_WITH_INI=ON \
|
-DFUN_WITH_INI=ON \
|
||||||
-DFUN_WITH_NOTCURSES=ON \
|
-DFUN_LINK_STATIC=OFF \
|
||||||
|
-DFUN_USE_MUSL=OFF \
|
||||||
|
-DFUN_DEBUG=OFF \
|
||||||
&& cmake --build build --target fun
|
&& cmake --build build --target fun
|
||||||
elif [ "$target" = "all_debug" ]; then
|
elif [ "$target" = "all_debug" ]; then
|
||||||
rm -rf build \
|
rm -rf build \
|
||||||
|
|
@ -45,12 +47,14 @@ elif [ "$target" = "all_debug" ]; then
|
||||||
-DFUN_WITH_JSON=ON \
|
-DFUN_WITH_JSON=ON \
|
||||||
-DFUN_WITH_TCLTK=ON \
|
-DFUN_WITH_TCLTK=ON \
|
||||||
-DFUN_WITH_INI=ON \
|
-DFUN_WITH_INI=ON \
|
||||||
-DFUN_WITH_NOTCURSES=ON \
|
-DFUN_LINK_STATIC=OFF \
|
||||||
|
-DFUN_USE_MUSL=OFF \
|
||||||
-DFUN_DEBUG=ON \
|
-DFUN_DEBUG=ON \
|
||||||
&& cmake --build build --target fun
|
&& cmake --build build --target fun
|
||||||
elif [ "$target" = "alpine" ]; then
|
elif [ "$target" = "alpine" ]; then
|
||||||
rm -rf build \
|
rm -rf build \
|
||||||
&& cmake -S . -B build \
|
&& cmake -S . -B build \
|
||||||
|
-DFUN_WITH_PCSC=OFF \
|
||||||
-DFUN_WITH_REPL=ON \
|
-DFUN_WITH_REPL=ON \
|
||||||
-DFUN_WITH_LIBSQL=ON \
|
-DFUN_WITH_LIBSQL=ON \
|
||||||
-DFUN_WITH_SQLITE=ON \
|
-DFUN_WITH_SQLITE=ON \
|
||||||
|
|
@ -58,35 +62,96 @@ elif [ "$target" = "alpine" ]; then
|
||||||
-DFUN_WITH_PCRE2=ON \
|
-DFUN_WITH_PCRE2=ON \
|
||||||
-DFUN_WITH_XML2=ON \
|
-DFUN_WITH_XML2=ON \
|
||||||
-DFUN_WITH_JSON=ON \
|
-DFUN_WITH_JSON=ON \
|
||||||
|
-DFUN_WITH_TCLTK=OFF \
|
||||||
-DFUN_WITH_INI=ON \
|
-DFUN_WITH_INI=ON \
|
||||||
|
-DFUN_LINK_STATIC=OFF \
|
||||||
|
-DFUN_DEBUG=OFF \
|
||||||
&& cmake --build build --target fun
|
&& cmake --build build --target fun
|
||||||
elif [ "$target" = "debug" ]; then
|
elif [ "$target" = "debug" ]; then
|
||||||
rm -rf build \
|
rm -rf build \
|
||||||
&& cmake -S . -B build \
|
&& cmake -S . -B build \
|
||||||
|
-DFUN_WITH_PCSC=OFF \
|
||||||
-DFUN_WITH_REPL=ON \
|
-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 \
|
-DFUN_DEBUG=ON \
|
||||||
&& cmake --build build --target fun
|
&& cmake --build build --target fun
|
||||||
elif [ "$target" = "freebsd" ]; then
|
elif [ "$target" = "freebsd" ]; then
|
||||||
rm -rf build \
|
rm -rf build \
|
||||||
&& cmake -S . -B build \
|
&& cmake -S . -B build \
|
||||||
|
-DFUN_WITH_PCSC=OFF \
|
||||||
-DFUN_WITH_REPL=ON \
|
-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
|
&& cmake --build build --target fun
|
||||||
elif [ "$target" = "install" ]; then
|
elif [ "$target" = "install" ]; then
|
||||||
sudo cmake --build build --target install
|
sudo cmake --build build --target install
|
||||||
elif [ "$target" = "minimal" ]; then
|
elif [ "$target" = "minimal" ]; then
|
||||||
rm -rf build \
|
rm -rf build \
|
||||||
&& cmake -S . -B 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
|
&& cmake --build build --target fun
|
||||||
elif [ "$target" = "musl" ]; then
|
elif [ "$target" = "musl" ]; then
|
||||||
rm -rf build \
|
rm -rf build \
|
||||||
&& cmake -S . -B build \
|
&& cmake -S . -B build \
|
||||||
|
-DFUN_WITH_PCSC=OFF \
|
||||||
-DFUN_WITH_REPL=ON \
|
-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_USE_MUSL=ON \
|
||||||
|
-DFUN_DEBUG=OFF \
|
||||||
&& cmake --build build --target fun
|
&& cmake --build build --target fun
|
||||||
elif [ "$target" = "repl" ]; then
|
elif [ "$target" = "repl" ]; then
|
||||||
rm -rf build \
|
rm -rf build \
|
||||||
&& cmake -S . -B build \
|
&& cmake -S . -B build \
|
||||||
|
-DFUN_WITH_PCSC=OFF \
|
||||||
-DFUN_WITH_REPL=ON \
|
-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
|
&& cmake --build build --target fun
|
||||||
else
|
else
|
||||||
echo "Build target $target not found... aborting!";
|
echo "Build target $target not found... aborting!";
|
||||||
|
|
@ -101,4 +166,3 @@ else
|
||||||
echo " - musl";
|
echo " - musl";
|
||||||
echo " - repl";
|
echo " - repl";
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,7 @@ BYTECODE = ROOT / "src" / "bytecode.h"
|
||||||
VM_C = ROOT / "src" / "vm.c"
|
VM_C = ROOT / "src" / "vm.c"
|
||||||
|
|
||||||
OP_RE = re.compile(r'\bOP_([A-Z0-9_]+)\b')
|
OP_RE = re.compile(r'\bOP_([A-Z0-9_]+)\b')
|
||||||
# Capture optional subdirectory and the basename separately
|
INCLUDE_RE = re.compile(r'#include\s+"vm/(?:[a-z0-9_]+/)?([a-z0-9_]+)\.c"')
|
||||||
INCLUDE_RE = re.compile(r'#include\s+"vm/(?:([a-z0-9_]+)/)?([a-z0-9_]+)\.c"')
|
|
||||||
|
|
||||||
def read_text(path: Path) -> str:
|
def read_text(path: Path) -> str:
|
||||||
try:
|
try:
|
||||||
|
|
@ -43,14 +42,14 @@ def parse_opcodes_from_bytecode(text: str) -> set[str]:
|
||||||
return ops
|
return ops
|
||||||
|
|
||||||
def parse_includes_from_vm(text: str) -> set[str]:
|
def parse_includes_from_vm(text: str) -> set[str]:
|
||||||
pairs = [(m.group(1) or "", m.group(2)) for m in INCLUDE_RE.finditer(text)]
|
incs = set(m.group(1) for m in INCLUDE_RE.finditer(text))
|
||||||
|
|
||||||
# Drop support includes that are not opcode handlers
|
# Drop support includes that are not opcode handlers
|
||||||
support_includes = {"thread_common", "stubs", "handles"}
|
support_includes = {"thread_common"}
|
||||||
pairs = [(d, n) for (d, n) in pairs if n not in support_includes]
|
incs = {name for name in incs if name not in support_includes}
|
||||||
|
|
||||||
# Base-name overrides (dir-agnostic) for a few special cases
|
# Map include base names to OP_* tokens.
|
||||||
base_overrides = {
|
overrides = {
|
||||||
# core
|
# core
|
||||||
"nop": "NOP", "halt": "HALT",
|
"nop": "NOP", "halt": "HALT",
|
||||||
"load_const": "LOAD_CONST", "load_local": "LOAD_LOCAL", "store_local": "STORE_LOCAL",
|
"load_const": "LOAD_CONST", "load_local": "LOAD_LOCAL", "store_local": "STORE_LOCAL",
|
||||||
|
|
@ -67,7 +66,7 @@ def parse_includes_from_vm(text: str) -> set[str]:
|
||||||
"make_array": "MAKE_ARRAY", "len": "LEN",
|
"make_array": "MAKE_ARRAY", "len": "LEN",
|
||||||
"index_get": "INDEX_GET", "index_set": "INDEX_SET",
|
"index_get": "INDEX_GET", "index_set": "INDEX_SET",
|
||||||
"push": "PUSH", "apop": "APOP", "set": "SET", "insert": "INSERT", "remove": "REMOVE",
|
"push": "PUSH", "apop": "APOP", "set": "SET", "insert": "INSERT", "remove": "REMOVE",
|
||||||
"slice": "SLICE", "clear": "CLEAR", "contains": "CONTAINS", "index_of": "INDEX_OF",
|
"slice": "SLICE",
|
||||||
# conversions and type/meta
|
# conversions and type/meta
|
||||||
"to_number": "TO_NUMBER", "to_string": "TO_STRING",
|
"to_number": "TO_NUMBER", "to_string": "TO_STRING",
|
||||||
"cast": "CAST", "typeof": "TYPEOF", "uclamp": "UCLAMP", "sclamp": "SCLAMP",
|
"cast": "CAST", "typeof": "TYPEOF", "uclamp": "UCLAMP", "sclamp": "SCLAMP",
|
||||||
|
|
@ -90,50 +89,9 @@ def parse_includes_from_vm(text: str) -> set[str]:
|
||||||
"thread_spawn": "THREAD_SPAWN", "thread_join": "THREAD_JOIN",
|
"thread_spawn": "THREAD_SPAWN", "thread_join": "THREAD_JOIN",
|
||||||
}
|
}
|
||||||
|
|
||||||
def map_token(d: str, n: str) -> str:
|
tokens = set()
|
||||||
# Directory-specific namespaces
|
for inc in incs:
|
||||||
if d == "curl":
|
tokens.add(overrides.get(inc, inc.upper()))
|
||||||
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
|
return tokens
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,8 @@ set -euo pipefail
|
||||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")"/.. && pwd)"
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")"/.. && pwd)"
|
||||||
EX_DIR="$ROOT/examples"
|
EX_DIR="$ROOT/examples"
|
||||||
|
|
||||||
# Optional overrides via FUN_BIN env. First positional arg may now be an examples subdirectory.
|
# Allow override via first arg or FUN_BIN env
|
||||||
BIN="${FUN_BIN:-}"
|
BIN="${1:-${FUN_BIN:-}}"
|
||||||
SUBDIR="${1:-}"
|
|
||||||
|
|
||||||
pick_bin() {
|
pick_bin() {
|
||||||
local cands=(
|
local cands=(
|
||||||
|
|
@ -37,17 +36,11 @@ pick_bin() {
|
||||||
return 1
|
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 [[ -z "${BIN}" ]]; then
|
||||||
if ! BIN="$(pick_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 "error: fun binary not found. Try building it (e.g., via CMake) or pass it explicitly:" >&2
|
||||||
echo " FUN_BIN=/path/to/fun scripts/run_examples.sh [examples-subdir]" >&2
|
echo " scripts/run_examples.sh /path/to/fun" >&2
|
||||||
echo "or: scripts/run_examples.sh /path/to/fun" >&2
|
echo "or set FUN_BIN=/path/to/fun" >&2
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
@ -62,26 +55,15 @@ if [[ -z "${FUN_LIB_DIR:-}" ]]; then
|
||||||
export FUN_LIB_DIR="$ROOT/lib"
|
export FUN_LIB_DIR="$ROOT/lib"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Do not auto-move failing examples; keep user's workspace unchanged
|
# Ensure error bucket exists
|
||||||
|
mkdir -p "$EX_DIR/error"
|
||||||
# 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
|
shopt -s nullglob
|
||||||
files=("$TARGET_DIR"/*.fun)
|
files=("$EX_DIR"/*.fun)
|
||||||
shopt -u nullglob
|
shopt -u nullglob
|
||||||
|
|
||||||
if (( ${#files[@]} == 0 )); then
|
if (( ${#files[@]} == 0 )); then
|
||||||
echo "No .fun example files found in $TARGET_DIR"
|
echo "No .fun example files found in $EX_DIR"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -98,7 +80,13 @@ for f in "${files[@]}"; do
|
||||||
echo "=== Running: ${f#$ROOT/} ==="
|
echo "=== Running: ${f#$ROOT/} ==="
|
||||||
if ! "$BIN" "$f"; then
|
if ! "$BIN" "$f"; then
|
||||||
echo "FAILED: ${f#$ROOT/}"
|
echo "FAILED: ${f#$ROOT/}"
|
||||||
# Intentionally do not move files on failure; leave control to the user
|
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
|
||||||
rc=1
|
rc=1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
|
||||||
484
spec/v0.3.md
484
spec/v0.3.md
|
|
@ -1,484 +0,0 @@
|
||||||
# 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.
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
# 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,28 +158,16 @@ static void rl_word_right(const char *out, size_t len, size_t *pos) {
|
||||||
while (*pos < len && out[*pos] == ' ') (*pos)++;
|
while (*pos < len && out[*pos] == ' ') (*pos)++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Expand file path for path-taking REPL commands (e.g., :load, :run) in-place; returns 1 if buffer changed (redraw) */
|
/* Expand file path for ":load " completion in-place; returns 1 if buffer changed (redraw) */
|
||||||
static int complete_load_path(char *buf, size_t *len_io) {
|
static int complete_load_path(char *buf, size_t *len_io) {
|
||||||
size_t len = *len_io;
|
size_t len = *len_io;
|
||||||
if (len < 3) return 0; /* minimally ":x" */
|
if (len < 5) return 0;
|
||||||
const char *p = buf;
|
const char *p = buf;
|
||||||
while (*p == ' ') p++;
|
while (*p == ' ') p++;
|
||||||
if (*p != ':') return 0;
|
if (*p != ':') return 0;
|
||||||
p++;
|
p++;
|
||||||
/* Parse command token (letters only) */
|
if (strncmp(p, "load", 4) != 0) return 0;
|
||||||
const char *cmd_start = p;
|
p += 4;
|
||||||
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++;
|
while (*p == ' ' || *p == '\t') p++;
|
||||||
size_t arg_off = (size_t)(p - buf);
|
size_t arg_off = (size_t)(p - buf);
|
||||||
if (arg_off > len) return 0;
|
if (arg_off > len) return 0;
|
||||||
|
|
@ -852,41 +840,40 @@ static int buffer_looks_incomplete(const char *buf) {
|
||||||
|
|
||||||
static void show_repl_help(void) {
|
static void show_repl_help(void) {
|
||||||
printf("Commands:\n");
|
printf("Commands:\n");
|
||||||
printf(" :help | :h Show this help\n");
|
printf(" :help Show this help\n");
|
||||||
printf(" :quit | :q | :exit Exit the REPL\n");
|
printf(" :quit | :q | :exit Exit the REPL\n");
|
||||||
printf(" :reset | :re Reset VM state (clears globals)\n");
|
printf(" :reset Reset VM state (clears globals)\n");
|
||||||
printf(" :dump | :du | :globals | :gl Dump current globals\n");
|
printf(" :dump | :globals Dump current globals\n");
|
||||||
printf(" :globals [pattern] Dump globals filtering by value substring\n");
|
printf(" :globals [pattern] Dump globals filtering by value substring\n");
|
||||||
printf(" :vars | :v [pattern] Alias for :globals\n");
|
printf(" :vars [pattern] Alias for :globals\n");
|
||||||
printf(" :clear | :cl Clear current input buffer\n");
|
printf(" :clear Clear current input buffer\n");
|
||||||
printf(" :print | :pr Show current buffer\n");
|
printf(" :print Show current buffer\n");
|
||||||
printf(" :run | :ru [file] Execute current buffer or the given file immediately\n");
|
printf(" :run Execute current buffer immediately\n");
|
||||||
printf(" :profile | :pf Execute buffer and show timing + instruction count\n");
|
printf(" :profile Execute buffer and show timing + instruction count\n");
|
||||||
printf(" :save | :sa <file> Save current buffer to file\n");
|
printf(" :save <file> Save current buffer to file\n");
|
||||||
printf(" :load | :lo <file> Load file into buffer (does not run)\n");
|
printf(" :load <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(" :paste [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(" :history [N] Show last N lines of history (default 50)\n");
|
||||||
printf(" :time | :ti on|off|toggle Toggle/enable/disable timing\n");
|
printf(" :time on|off|toggle Toggle/enable/disable timing\n");
|
||||||
printf(" :env | :en [NAME[=VALUE]] Get or set environment variable\n");
|
printf(" :env [NAME[=VALUE]] Get or set environment variable\n");
|
||||||
printf(" :backtrace | :bt | :ba Show backtrace of VM frames (most recent first)\n");
|
printf(" :backtrace | :bt Show backtrace of VM frames (most recent first)\n");
|
||||||
printf(" :frame | :fr N Select frame N for :locals/:list/:disasm (default: top)\n");
|
printf(" :frame N Select frame N for :locals/:list/:disas (default: top)\n");
|
||||||
printf(" :list | :li [±K] Show K lines of source around current frame line (default 5)\n");
|
printf(" :list [±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(" :disas [±N] Disassemble around current frame ip (default 5)\n");
|
||||||
printf(" :mdump | :md WHAT [offset [len]] [raw] [to <file>] Dump VM memory region\n");
|
printf(" :disasm WHAT [off [len]] [to <file>] Hexdump VM memory region to screen or file\n");
|
||||||
printf(" WHAT = code | stack | globals | consts\n");
|
printf(" WHAT = code | stack | globals | consts\n");
|
||||||
printf(" 'raw' writes binary bytes instead of a formatted hexdump\n");
|
printf(" :stack [N] Show top N (default all) stack values\n");
|
||||||
printf(" :stack | :st [N] Show top N (default all) stack values\n");
|
printf(" :top Show the top of the VM stack\n");
|
||||||
printf(" :top | :to Show the top of the VM stack\n");
|
printf(" :locals [FRAME] Show locals of frame (default: selected frame)\n");
|
||||||
printf(" :locals | :lc [FRAME] Show locals of frame (default: selected frame)\n");
|
printf(" :printv WHAT Print value: local[i] | stack[i] | global[i]\n");
|
||||||
printf(" :printv | :pv WHAT Print value: local[i] | stack[i] | global[i]\n");
|
printf(" :break [file:]line Set a breakpoint (default file = current frame file)\n");
|
||||||
printf(" :break | :br [file:]line Set a breakpoint (default file = current frame file)\n");
|
printf(" :info breaks List breakpoints\n");
|
||||||
printf(" :info | :in breaks List breakpoints\n");
|
printf(" :delete ID Delete breakpoint by ID\n");
|
||||||
printf(" :delete | :de ID Delete breakpoint by ID\n");
|
printf(" :clear breaks Remove all breakpoints\n");
|
||||||
printf(" :clear breaks | :cb Remove all breakpoints\n");
|
printf(" :cont Continue execution (exit REPL if in debug stop)\n");
|
||||||
printf(" :cont | :co Continue execution (exit REPL if in debug stop)\n");
|
printf(" :step Step one instruction\n");
|
||||||
printf(" :step | :sp Step one instruction\n");
|
printf(" :next Step over (current frame)\n");
|
||||||
printf(" :next | :ne Step over (current frame)\n");
|
printf(" :finish Run until the current frame returns\n");
|
||||||
printf(" :finish | :fi Run until the current frame returns\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static char *read_entire_file(const char *path, size_t *out_len) {
|
static char *read_entire_file(const char *path, size_t *out_len) {
|
||||||
|
|
@ -913,15 +900,6 @@ static int write_entire_file(const char *path, const char *data, size_t len) {
|
||||||
return n == 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 ---------- */
|
/* ---------- Hexdump helper ---------- */
|
||||||
static void hexdump_to(FILE *out, const unsigned char *data, size_t len, size_t base_off) {
|
static void hexdump_to(FILE *out, const unsigned char *data, size_t len, size_t base_off) {
|
||||||
if (!out || !data || len == 0) return;
|
if (!out || !data || len == 0) return;
|
||||||
|
|
@ -1093,19 +1071,19 @@ int fun_run_repl(VM *vm) {
|
||||||
char arg[2048] = {0};
|
char arg[2048] = {0};
|
||||||
sscanf(line, ":%63s %2047[^\n]", cmd, arg);
|
sscanf(line, ":%63s %2047[^\n]", cmd, arg);
|
||||||
|
|
||||||
if (cmd_is_one_of(cmd, (const char*[]){"quit","q","qu","exit", NULL})) {
|
if (strcmp(cmd, "quit") == 0 || strcmp(cmd, "q") == 0 || strcmp(cmd, "exit") == 0) {
|
||||||
break;
|
break;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"help","h", NULL})) {
|
} else if (strcmp(cmd, "help") == 0) {
|
||||||
show_repl_help();
|
show_repl_help();
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"reset","re", NULL})) {
|
} else if (strcmp(cmd, "reset") == 0) {
|
||||||
vm_reset(vm);
|
vm_reset(vm);
|
||||||
printf("VM state reset.\n");
|
printf("VM state reset.\n");
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"dump","du", NULL})) {
|
} else if (strcmp(cmd, "dump") == 0) {
|
||||||
vm_dump_globals(vm);
|
vm_dump_globals(vm);
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"globals","vars","gl","v","va", NULL})) {
|
} else if (strcmp(cmd, "globals") == 0 || strcmp(cmd, "vars") == 0) {
|
||||||
const char *pattern = lstrip(arg);
|
const char *pattern = lstrip(arg);
|
||||||
int filtered = (pattern && *pattern);
|
int filtered = (pattern && *pattern);
|
||||||
printf("=== globals%s%s ===\n", filtered ? " matching '" : "", filtered ? pattern : "");
|
printf("=== globals%s%s ===\n", filtered ? " matching '" : "", filtered ? pattern : "");
|
||||||
|
|
@ -1120,11 +1098,11 @@ int fun_run_repl(VM *vm) {
|
||||||
}
|
}
|
||||||
printf("===============\n");
|
printf("===============\n");
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"clear","cl", NULL})) {
|
} else if (strcmp(cmd, "clear") == 0) {
|
||||||
buflen = 0;
|
buflen = 0;
|
||||||
printf("(buffer cleared)\n");
|
printf("(buffer cleared)\n");
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"print","pr", NULL})) {
|
} else if (strcmp(cmd, "print") == 0) {
|
||||||
if (buflen == 0) printf("(buffer empty)\n");
|
if (buflen == 0) printf("(buffer empty)\n");
|
||||||
else {
|
else {
|
||||||
if (buflen >= bufcap) {
|
if (buflen >= bufcap) {
|
||||||
|
|
@ -1136,46 +1114,28 @@ int fun_run_repl(VM *vm) {
|
||||||
if (buflen > 0 && buffer[buflen-1] != '\n') printf("\n");
|
if (buflen > 0 && buffer[buflen-1] != '\n') printf("\n");
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"run","ru","profile","pf", NULL})) {
|
} else if (strcmp(cmd, "run") == 0 || strcmp(cmd, "profile") == 0) {
|
||||||
int is_profile = cmd_is_one_of(cmd, (const char*[]){"profile","pf", NULL});
|
if (buflen == 0) {
|
||||||
// 'arg' is a fixed-size local array, so its address is always non-null.
|
printf("(buffer empty)\n");
|
||||||
// Only check whether it contains a non-empty string.
|
continue;
|
||||||
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;
|
clock_t t_parse0 = 0, t_parse1 = 0, t_run0 = 0, t_run1 = 0;
|
||||||
if (is_profile) t_parse0 = clock();
|
if (strcmp(cmd, "profile") == 0) t_parse0 = clock();
|
||||||
Bytecode *bc = parse_string_to_bytecode(src);
|
Bytecode *bc = parse_string_to_bytecode(buffer);
|
||||||
if (is_profile) t_parse1 = clock();
|
if (strcmp(cmd, "profile") == 0) t_parse1 = clock();
|
||||||
|
|
||||||
if (bc) {
|
if (bc) {
|
||||||
if (repl_timing || is_profile) t_run0 = clock();
|
if (repl_timing || strcmp(cmd, "profile") == 0) t_run0 = clock();
|
||||||
vm_run(vm, bc);
|
vm_run(vm, bc);
|
||||||
if (repl_timing || is_profile) t_run1 = clock();
|
if (repl_timing || strcmp(cmd, "profile") == 0) t_run1 = clock();
|
||||||
|
|
||||||
if (is_profile) {
|
if (strcmp(cmd, "profile") == 0) {
|
||||||
double ms_parse = (double)(t_parse1 - t_parse0) * 1000.0 / (double)CLOCKS_PER_SEC;
|
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;
|
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",
|
printf("[profile] parse: %.2f ms, run: %.2f ms, total: %.2f ms, instr: %lld\n",
|
||||||
|
|
@ -1188,14 +1148,14 @@ int fun_run_repl(VM *vm) {
|
||||||
vm_print_output(vm);
|
vm_print_output(vm);
|
||||||
vm_clear_output(vm);
|
vm_clear_output(vm);
|
||||||
bytecode_free(bc);
|
bytecode_free(bc);
|
||||||
if (!from_file) append_history(hist, buffer);
|
append_history(hist, buffer);
|
||||||
} else {
|
} else {
|
||||||
int line_no = 0, col_no = 0;
|
int line_no = 0, col_no = 0;
|
||||||
char emsg[256];
|
char emsg[256];
|
||||||
if (parser_last_error(emsg, sizeof(emsg), &line_no, &col_no)) {
|
if (parser_last_error(emsg, sizeof(emsg), &line_no, &col_no)) {
|
||||||
printf("Parse error at %d:%d: %s\n", line_no, col_no, emsg);
|
printf("Parse error at %d:%d: %s\n", line_no, col_no, emsg);
|
||||||
int cur_line = 1;
|
int cur_line = 1;
|
||||||
const char *p = src ? src : buffer;
|
const char *p = buffer;
|
||||||
while (*p && cur_line < line_no) {
|
while (*p && cur_line < line_no) {
|
||||||
if (*p == '\n') cur_line++;
|
if (*p == '\n') cur_line++;
|
||||||
p++;
|
p++;
|
||||||
|
|
@ -1207,7 +1167,7 @@ int fun_run_repl(VM *vm) {
|
||||||
for (int i = 1; i < col_no; ++i) putchar(' ');
|
for (int i = 1; i < col_no; ++i) putchar(' ');
|
||||||
printf("^\n");
|
printf("^\n");
|
||||||
#ifdef FUN_DEBUG
|
#ifdef FUN_DEBUG
|
||||||
if (hist && !from_file) {
|
if (hist) {
|
||||||
fprintf(hist, "// ERROR %d:%d: %s\n", line_no, col_no, emsg);
|
fprintf(hist, "// ERROR %d:%d: %s\n", line_no, col_no, emsg);
|
||||||
fflush(hist);
|
fflush(hist);
|
||||||
}
|
}
|
||||||
|
|
@ -1215,20 +1175,16 @@ int fun_run_repl(VM *vm) {
|
||||||
} else {
|
} else {
|
||||||
printf("Parse error.\n");
|
printf("Parse error.\n");
|
||||||
#ifdef FUN_DEBUG
|
#ifdef FUN_DEBUG
|
||||||
if (hist && !from_file) {
|
if (hist) {
|
||||||
fprintf(hist, "// ERROR: parse error\n");
|
fprintf(hist, "// ERROR: parse error\n");
|
||||||
fflush(hist);
|
fflush(hist);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (from_file) {
|
buflen = 0;
|
||||||
free(filebuf);
|
|
||||||
} else {
|
|
||||||
buflen = 0; /* keep behavior: clear buffer only when running current buffer */
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"save","sa", NULL})) {
|
} else if (strcmp(cmd, "save") == 0) {
|
||||||
if (arg[0] == '\0') { printf("Usage: :save <file>\n"); continue; }
|
if (arg[0] == '\0') { printf("Usage: :save <file>\n"); continue; }
|
||||||
if (buflen == 0) { printf("(buffer empty)\n"); continue; }
|
if (buflen == 0) { printf("(buffer empty)\n"); continue; }
|
||||||
if (!write_entire_file(arg, buffer, buflen)) {
|
if (!write_entire_file(arg, buffer, buflen)) {
|
||||||
|
|
@ -1237,7 +1193,7 @@ int fun_run_repl(VM *vm) {
|
||||||
printf("Saved %zu bytes to '%s'\n", buflen, arg);
|
printf("Saved %zu bytes to '%s'\n", buflen, arg);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"load","lo", NULL})) {
|
} else if (strcmp(cmd, "load") == 0) {
|
||||||
if (arg[0] == '\0') { printf("Usage: :load <file>\n"); continue; }
|
if (arg[0] == '\0') { printf("Usage: :load <file>\n"); continue; }
|
||||||
size_t flen = 0;
|
size_t flen = 0;
|
||||||
char *filebuf = read_entire_file(arg, &flen);
|
char *filebuf = read_entire_file(arg, &flen);
|
||||||
|
|
@ -1256,7 +1212,7 @@ int fun_run_repl(VM *vm) {
|
||||||
free(filebuf);
|
free(filebuf);
|
||||||
printf("Loaded %zu bytes into buffer. Use :run or submit an empty line to execute.\n", buflen);
|
printf("Loaded %zu bytes into buffer. Use :run or submit an empty line to execute.\n", buflen);
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"paste","pa", NULL})) {
|
} else if (strcmp(cmd, "paste") == 0) {
|
||||||
int run_after = 0;
|
int run_after = 0;
|
||||||
const char *opt = lstrip(arg);
|
const char *opt = lstrip(arg);
|
||||||
if (opt && (strcmp(opt, "run") == 0 || strcmp(opt, "exec") == 0)) run_after = 1;
|
if (opt && (strcmp(opt, "run") == 0 || strcmp(opt, "exec") == 0)) run_after = 1;
|
||||||
|
|
@ -1319,13 +1275,13 @@ int fun_run_repl(VM *vm) {
|
||||||
printf("(pasted %zu bytes into buffer)\n", buflen);
|
printf("(pasted %zu bytes into buffer)\n", buflen);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"history","hi", NULL})) {
|
} else if (strcmp(cmd, "history") == 0) {
|
||||||
int n = 50;
|
int n = 50;
|
||||||
if (arg[0] != '\0') n = atoi(arg);
|
if (arg[0] != '\0') n = atoi(arg);
|
||||||
if (n <= 0) n = 50;
|
if (n <= 0) n = 50;
|
||||||
print_last_n_lines(hist_path, n);
|
print_last_n_lines(hist_path, n);
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"time","ti", NULL})) {
|
} else if (strcmp(cmd, "time") == 0) {
|
||||||
if (strcmp(lstrip(arg), "on") == 0) repl_timing = 1;
|
if (strcmp(lstrip(arg), "on") == 0) repl_timing = 1;
|
||||||
else if (strcmp(lstrip(arg), "off") == 0) repl_timing = 0;
|
else if (strcmp(lstrip(arg), "off") == 0) repl_timing = 0;
|
||||||
else if (strcmp(lstrip(arg), "toggle") == 0) repl_timing = !repl_timing;
|
else if (strcmp(lstrip(arg), "toggle") == 0) repl_timing = !repl_timing;
|
||||||
|
|
@ -1335,7 +1291,7 @@ int fun_run_repl(VM *vm) {
|
||||||
}
|
}
|
||||||
printf("Timing %s\n", repl_timing ? "enabled" : "disabled");
|
printf("Timing %s\n", repl_timing ? "enabled" : "disabled");
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"env","en", NULL})) {
|
} else if (strcmp(cmd, "env") == 0) {
|
||||||
const char *spec = lstrip(arg);
|
const char *spec = lstrip(arg);
|
||||||
if (!spec || *spec == '\0') {
|
if (!spec || *spec == '\0') {
|
||||||
env_show_usage();
|
env_show_usage();
|
||||||
|
|
@ -1354,7 +1310,7 @@ int fun_run_repl(VM *vm) {
|
||||||
env_set(name, val);
|
env_set(name, val);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"backtrace","bt","ba", NULL})) {
|
} else if (strcmp(cmd, "backtrace") == 0 || strcmp(cmd, "bt") == 0) {
|
||||||
if (vm->fp < 0) { printf("(no frames)\n"); continue; }
|
if (vm->fp < 0) { printf("(no frames)\n"); continue; }
|
||||||
printf("Backtrace (most recent call first):\n");
|
printf("Backtrace (most recent call first):\n");
|
||||||
for (int i = vm->fp; i >= 0; --i) {
|
for (int i = vm->fp; i >= 0; --i) {
|
||||||
|
|
@ -1365,7 +1321,7 @@ int fun_run_repl(VM *vm) {
|
||||||
printf(" #%d %s at %s ip=%d line=%d\n", i, fname, sfile, ip, vm->current_line);
|
printf(" #%d %s at %s ip=%d line=%d\n", i, fname, sfile, ip, vm->current_line);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"stack","st", NULL})) {
|
} else if (strcmp(cmd, "stack") == 0) {
|
||||||
int n = -1;
|
int n = -1;
|
||||||
const char *p = lstrip(arg);
|
const char *p = lstrip(arg);
|
||||||
if (p && *p) n = atoi(p);
|
if (p && *p) n = atoi(p);
|
||||||
|
|
@ -1380,7 +1336,7 @@ int fun_run_repl(VM *vm) {
|
||||||
free(sv);
|
free(sv);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"locals","lc", NULL})) {
|
} else if (strcmp(cmd, "locals") == 0) {
|
||||||
int idx = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp;
|
int idx = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp;
|
||||||
const char *p = lstrip(arg);
|
const char *p = lstrip(arg);
|
||||||
if (p && *p) {
|
if (p && *p) {
|
||||||
|
|
@ -1402,7 +1358,7 @@ int fun_run_repl(VM *vm) {
|
||||||
}
|
}
|
||||||
if (!any) printf(" (no non-nil locals)\n");
|
if (!any) printf(" (no non-nil locals)\n");
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"frame","fr", NULL})) {
|
} else if (strcmp(cmd, "frame") == 0) {
|
||||||
const char *p = lstrip(arg);
|
const char *p = lstrip(arg);
|
||||||
if (!p || !*p) { printf("Usage: :frame N\n"); continue; }
|
if (!p || !*p) { printf("Usage: :frame N\n"); continue; }
|
||||||
int v = atoi(p);
|
int v = atoi(p);
|
||||||
|
|
@ -1413,7 +1369,7 @@ int fun_run_repl(VM *vm) {
|
||||||
const char *sfile = (f->fn && f->fn->source_file) ? f->fn->source_file : "<unknown>";
|
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);
|
printf("Selected frame #%d: %s (%s)\n", selected_frame, fname, sfile);
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"list","li", NULL})) {
|
} else if (strcmp(cmd, "list") == 0) {
|
||||||
int k = 5;
|
int k = 5;
|
||||||
const char *p = lstrip(arg);
|
const char *p = lstrip(arg);
|
||||||
if (p && *p) k = atoi(p);
|
if (p && *p) k = atoi(p);
|
||||||
|
|
@ -1452,7 +1408,7 @@ int fun_run_repl(VM *vm) {
|
||||||
}
|
}
|
||||||
free(src);
|
free(src);
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"disasm","disassemble","di", NULL})) {
|
} else if (strcmp(cmd, "disas") == 0 || strcmp(cmd, "disassemble") == 0) {
|
||||||
int n = 5;
|
int n = 5;
|
||||||
const char *p = lstrip(arg);
|
const char *p = lstrip(arg);
|
||||||
if (p && *p) n = atoi(p);
|
if (p && *p) n = atoi(p);
|
||||||
|
|
@ -1483,29 +1439,26 @@ int fun_run_repl(VM *vm) {
|
||||||
printf("%c %6d: %-14s %d\n", (i == curip ? '>' : ' '), i, opname, ins.operand);
|
printf("%c %6d: %-14s %d\n", (i == curip ? '>' : ' '), i, opname, ins.operand);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"mdump","md", NULL})) {
|
} else if (strcmp(cmd, "disasm") == 0) {
|
||||||
/* Syntax: :mdump WHAT [offset [len]] [raw] [to <file>]
|
/* Syntax: :disasm WHAT [off [len]] [to <file>]
|
||||||
WHAT: code | stack | globals | consts
|
WHAT: code | stack | globals | consts */
|
||||||
'raw' writes binary bytes instead of a formatted hexdump */
|
|
||||||
const char *p = lstrip(arg);
|
const char *p = lstrip(arg);
|
||||||
if (!p || !*p) {
|
if (!p || !*p) {
|
||||||
printf("Usage: :mdump WHAT [offset [len]] [raw] [to <file>]\n");
|
printf("Usage: :disasm WHAT [off [len]] [to <file>]\n");
|
||||||
printf(" WHAT = code | stack | globals | consts\n");
|
printf(" WHAT = code | stack | globals | consts\n");
|
||||||
printf(" 'raw' writes binary bytes instead of a formatted hexdump\n");
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
char what[32];
|
char what[32];
|
||||||
int consumed = 0;
|
int consumed = 0;
|
||||||
if (sscanf(p, "%31s %n", what, &consumed) != 1) {
|
if (sscanf(p, "%31s %n", what, &consumed) != 1) {
|
||||||
printf("Usage: :mdump WHAT [offset [len]] [raw] [to <file>]\n");
|
printf("Usage: :disasm WHAT [off [len]] [to <file>]\n");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
p += consumed;
|
p += consumed;
|
||||||
|
|
||||||
size_t off = 0;
|
size_t off = 0;
|
||||||
size_t len = (size_t)-1; /* default later to clamp */
|
size_t len = (size_t)-1; /* default later to clamp */
|
||||||
int want_raw = 0; /* output raw bytes instead of hexdump */
|
|
||||||
|
|
||||||
/* parse optional off */
|
/* parse optional off */
|
||||||
while (*p == ' ' || *p == '\t') p++;
|
while (*p == ' ' || *p == '\t') p++;
|
||||||
|
|
@ -1528,13 +1481,6 @@ 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> */
|
/* optional: to <file> */
|
||||||
while (*p == ' ' || *p == '\t') p++;
|
while (*p == ' ' || *p == '\t') p++;
|
||||||
int to_file = 0;
|
int to_file = 0;
|
||||||
|
|
@ -1600,29 +1546,17 @@ int fun_run_repl(VM *vm) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!to_file) {
|
if (!to_file) {
|
||||||
if (want_raw) {
|
printf("Hexdump %s: total=%zu, offset=%zu, len=%zu\n", what, total, off, len);
|
||||||
/* Write raw bytes directly to stdout with no header */
|
hexdump_to(stdout, base + off, len, off);
|
||||||
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 {
|
} else {
|
||||||
FILE *fout = fopen(path, "wb");
|
FILE *fout = fopen(path, "wb");
|
||||||
if (!fout) { printf("Failed to open '%s' for writing\n", path); continue; }
|
if (!fout) { printf("Failed to open '%s' for writing\n", path); continue; }
|
||||||
if (want_raw) {
|
hexdump_to(fout, base + off, len, off);
|
||||||
fwrite(base + off, 1, len, fout);
|
fclose(fout);
|
||||||
fclose(fout);
|
printf("Wrote hexdump (%zu bytes from %s) to %s\n", len, what, path);
|
||||||
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;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"printv","pv", NULL})) {
|
} else if (strcmp(cmd, "printv") == 0) {
|
||||||
const char *spec = lstrip(arg);
|
const char *spec = lstrip(arg);
|
||||||
if (!spec || !*spec) { printf("Usage: :printv local[i] | stack[i] | global[i]\n"); continue; }
|
if (!spec || !*spec) { printf("Usage: :printv local[i] | stack[i] | global[i]\n"); continue; }
|
||||||
int idx = -1;
|
int idx = -1;
|
||||||
|
|
@ -1647,13 +1581,13 @@ int fun_run_repl(VM *vm) {
|
||||||
printf("Usage: :printv local[i] | stack[i] | global[i]\n");
|
printf("Usage: :printv local[i] | stack[i] | global[i]\n");
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"top","to", NULL})) {
|
} else if (strcmp(cmd, "top") == 0) {
|
||||||
if (vm->sp < 0) { printf("(stack empty)\n"); continue; }
|
if (vm->sp < 0) { printf("(stack empty)\n"); continue; }
|
||||||
char *sv = value_to_string_alloc(&vm->stack[vm->sp]);
|
char *sv = value_to_string_alloc(&vm->stack[vm->sp]);
|
||||||
printf("%s\n", sv ? sv : "nil");
|
printf("%s\n", sv ? sv : "nil");
|
||||||
free(sv);
|
free(sv);
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"break","br", NULL})) {
|
} else if (strcmp(cmd, "break") == 0) {
|
||||||
const char *p = lstrip(arg);
|
const char *p = lstrip(arg);
|
||||||
if (!p || !*p) { printf("Usage: :break [file:]line\n"); continue; }
|
if (!p || !*p) { printf("Usage: :break [file:]line\n"); continue; }
|
||||||
const char *colon = strchr(p, ':');
|
const char *colon = strchr(p, ':');
|
||||||
|
|
@ -1679,7 +1613,7 @@ int fun_run_repl(VM *vm) {
|
||||||
if (id >= 0) printf("Breakpoint %d set at %s:%d\n", id, filebuf, line);
|
if (id >= 0) printf("Breakpoint %d set at %s:%d\n", id, filebuf, line);
|
||||||
else printf("Failed to set breakpoint\n");
|
else printf("Failed to set breakpoint\n");
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"info","in", NULL})) {
|
} else if (strcmp(cmd, "info") == 0) {
|
||||||
const char *what = lstrip(arg);
|
const char *what = lstrip(arg);
|
||||||
if (what && strcmp(what, "breaks") == 0) {
|
if (what && strcmp(what, "breaks") == 0) {
|
||||||
vm_debug_list_breakpoints(vm);
|
vm_debug_list_breakpoints(vm);
|
||||||
|
|
@ -1687,15 +1621,11 @@ int fun_run_repl(VM *vm) {
|
||||||
printf("Usage: :info breaks\n");
|
printf("Usage: :info breaks\n");
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"delete","de", NULL})) {
|
} else if (strcmp(cmd, "delete") == 0) {
|
||||||
int id = atoi(lstrip(arg));
|
int id = atoi(lstrip(arg));
|
||||||
if (vm_debug_delete_breakpoint(vm, id)) printf("Deleted breakpoint %d\n", id);
|
if (vm_debug_delete_breakpoint(vm, id)) printf("Deleted breakpoint %d\n", id);
|
||||||
else printf("No such breakpoint %d\n", id);
|
else printf("No such breakpoint %d\n", id);
|
||||||
continue;
|
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) {
|
} else if (strcmp(cmd, "clear") == 0) {
|
||||||
const char *what = lstrip(arg);
|
const char *what = lstrip(arg);
|
||||||
if (what && strcmp(what, "breaks") == 0) {
|
if (what && strcmp(what, "breaks") == 0) {
|
||||||
|
|
@ -1705,22 +1635,22 @@ int fun_run_repl(VM *vm) {
|
||||||
printf("Usage: :clear breaks\n");
|
printf("Usage: :clear breaks\n");
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"cont","continue","co", NULL})) {
|
} else if (strcmp(cmd, "cont") == 0 || strcmp(cmd, "continue") == 0) {
|
||||||
vm_debug_request_continue(vm);
|
vm_debug_request_continue(vm);
|
||||||
printf("Continuing...\n");
|
printf("Continuing...\n");
|
||||||
if (vm->on_error_repl) return 0; /* exit REPL to continue execution */
|
if (vm->on_error_repl) return 0; /* exit REPL to continue execution */
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"step","sp", NULL})) {
|
} else if (strcmp(cmd, "step") == 0) {
|
||||||
vm_debug_request_step(vm);
|
vm_debug_request_step(vm);
|
||||||
printf("Stepping one instruction...\n");
|
printf("Stepping one instruction...\n");
|
||||||
if (vm->on_error_repl) return 0;
|
if (vm->on_error_repl) return 0;
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"next","ne", NULL})) {
|
} else if (strcmp(cmd, "next") == 0) {
|
||||||
vm_debug_request_next(vm);
|
vm_debug_request_next(vm);
|
||||||
printf("Stepping over...\n");
|
printf("Stepping over...\n");
|
||||||
if (vm->on_error_repl) return 0;
|
if (vm->on_error_repl) return 0;
|
||||||
continue;
|
continue;
|
||||||
} else if (cmd_is_one_of(cmd, (const char*[]){"finish","fi", NULL})) {
|
} else if (strcmp(cmd, "finish") == 0) {
|
||||||
vm_debug_request_finish(vm);
|
vm_debug_request_finish(vm);
|
||||||
printf("Running until current frame returns...\n");
|
printf("Running until current frame returns...\n");
|
||||||
if (vm->on_error_repl) return 0;
|
if (vm->on_error_repl) return 0;
|
||||||
|
|
|
||||||
4
src/vm.c
4
src/vm.c
|
|
@ -23,7 +23,6 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <math.h>
|
|
||||||
|
|
||||||
#ifdef __unix__
|
#ifdef __unix__
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
|
|
@ -63,8 +62,7 @@
|
||||||
/* Central INI handle registry and helpers */
|
/* Central INI handle registry and helpers */
|
||||||
#include "vm/ini/handles.c"
|
#include "vm/ini/handles.c"
|
||||||
#endif
|
#endif
|
||||||
/* Note: INI opcode handlers are included below; changes in vm/ini/ .c files
|
/* Note: INI opcode handlers are included below; changes in vm/ini/*.c require vm.c to rebuild. */
|
||||||
* require vm.c to rebuild. */
|
|
||||||
#include "external/json.c"
|
#include "external/json.c"
|
||||||
#include "external/libsql.c"
|
#include "external/libsql.c"
|
||||||
#include "external/pcsc.c"
|
#include "external/pcsc.c"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue