diff --git a/.editorconfig b/.editorconfig index e7b467c..1acfb67 100644 --- a/.editorconfig +++ b/.editorconfig @@ -45,7 +45,7 @@ ij_shell_use_unix_line_separator = true [*.c] indent_style = space -indent_size = 4 +indent_size = 2 tab_width = 1 [*.csv] diff --git a/.github/workflows/cmake-ubuntu-latest.yml b/.github/workflows/cmake-ubuntu-latest.yml new file mode 100644 index 0000000..041956d --- /dev/null +++ b/.github/workflows/cmake-ubuntu-latest.yml @@ -0,0 +1,106 @@ +name: Fun with CMake on Ubuntu-Slim + +on: + push: + branches: [ "main" ] + +env: + BUILD_TYPE: Release + +jobs: + build: + runs-on: ubuntu-slim + defaults: + run: + working-directory: ${{github.workspace}} + + steps: + - uses: actions/checkout@v4 + + - name: Update apt + run: sudo apt update + + #- name: Upgrade apt + # run: sudo apt upgrade -y + + - name: Install dependencies + run: sudo apt install cmake libjson-c-dev libssl-dev -y + + - name: Configure CMake + run: cmake -S . -B build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DFUN_DEBUG=OFF -DFUN_WITH_REPL=ON -DFUN_WITH_PCSC=OFF -DFUN_WITH_LIBSQL=OFF -DFUN_WITH_SQLITE=OFF -DFUN_WITH_CURL=OFF -DFUN_WITH_PCRE2=OFF -DFUN_WITH_XML2=OFF -DFUN_WITH_JSON=ON -DFUN_WITH_TCLTK=OFF -DFUN_WITH_INI=OFF -DFUN_WITH_NOTCURSES=OFF -DFUN_WITH_CPP=ON -DFUN_WITH_OPENSSL=ON -DFUN_WITH_LIBRESSL=OFF -DFUN_WITH_YAML=OFF -DFUN_USE_MUSL=OFF -DFUN_WITH_RUST=OFF + + - name: Build + run: cmake --build build --config ${{env.BUILD_TYPE}} --target all + + - name: Test + working-directory: ${{github.workspace}}/build + run: ctest -C ${{env.BUILD_TYPE}} + + - name: Run Examples + run: ./scripts/run_examples.sh + + - name: Run Examples (algos) + run: ./scripts/run_examples.sh algos + + - name: Run Examples (arrays) + run: ./scripts/run_examples.sh arrays + + - name: Run Examples (basics) + run: ./scripts/run_examples.sh basics + + - name: Run Examples (compose) + run: ./scripts/run_examples.sh compose + + - name: Run Examples (crypto) + run: ./scripts/run_examples.sh crypto + + #- name: Run Examples (external/curl) + # run: ./scripts/run_examples.sh external/curl + + #- name: Run Examples (external/ini) + # run: ./scripts/run_examples.sh external/ini + + - name: Run Examples (external/json) + run: ./scripts/run_examples.sh external/json + + #- name: Run Examples (external/libressl) + # run: ./scripts/run_examples.sh external/libressl + + #- name: Run Examples (external/libsql) + # run: ./scripts/run_examples.sh external/libsql + + #- name: Run Examples (external/notcurses) + # run: ./scripts/run_examples.sh external/notcurses + + - name: Run Examples (external/openssl) + run: ./scripts/run_examples.sh external/openssl + + #- name: Run Examples (external/pcre2) + # run: ./scripts/run_examples.sh external/pcre2 + + #- name: Run Examples (external/pcsc) + # run: ./scripts/run_examples.sh external/pcsc + + #- name: Run Examples (external/sqlite) + # run: ./scripts/run_examples.sh external/sqlite + + #- name: Run Examples (external/tcltk) + # run: ./scripts/run_examples.sh external/tcltk + + #- name: Run Examples (external/xml2) + # run: ./scripts/run_examples.sh external/xml2 + + - name: Run Examples (functions) + run: ./scripts/run_examples.sh functions + + - name: Run Examples (io) + run: ./scripts/run_examples.sh io + + - name: Run Examples (math) + run: ./scripts/run_examples.sh math + + - name: Run Examples (patterns) + run: ./scripts/run_examples.sh patterns + + - name: Run Examples (strings) + run: ./scripts/run_examples.sh strings diff --git a/.gitignore b/.gitignore index 48a69a6..063129a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ CMakeCache.txt .* !.editorconfig !.gitignore +!.github .venv build* cmake_install.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 34d9fb6..96d5229 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.39.15 LANGUAGES C) +project(fun VERSION 0.40.5 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/README.md b/README.md index dcfc6fd..fd335c9 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,6 @@ See [./lib/](https://git.xw3.org/fun/fun/src/branch/main/lib) for what the stand - [PCSC (smart cards)](./docs/external/pcsc.md) (optional) ☑ - [OpenSSL](./docs/external/openssl.md) (optional) ☑ - [SQLite](./docs/external/sqlite.md) (optional) ☑ -- [Tk (Tcl/Tk GUI)](./docs/external/tcltk.md) (optional) ☑ - [XML (libxml2)](./docs/external/xml2.md) (optional) ☑ ☑ = Done / ☐ = Planned or in progress. diff --git a/cmake/Extensions/Extensions.cmake b/cmake/Extensions/Extensions.cmake index 92843b6..a60fdca 100644 --- a/cmake/Extensions/Extensions.cmake +++ b/cmake/Extensions/Extensions.cmake @@ -10,34 +10,27 @@ function(_fun_print_feature name flag) endif() endfunction() -# Include each extension module -include(${CMAKE_SOURCE_DIR}/cmake/Extensions/TCLTK.cmake) +# Include each extension module (Tcl/Tk removed) 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) include(${CMAKE_SOURCE_DIR}/cmake/Extensions/OPENSSL.cmake) -include(${CMAKE_SOURCE_DIR}/cmake/Extensions/LIBRESSL.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) +## libsql extension removed _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) _fun_print_feature("OpenSSL (FUN_WITH_OPENSSL)" FUN_WITH_OPENSSL) -_fun_print_feature("LibreSSL (FUN_WITH_LIBRESSL)" FUN_WITH_LIBRESSL) message(STATUS "--------------------------------") diff --git a/cmake/Targets.cmake b/cmake/Targets.cmake index 2184b30..0e98eab 100644 --- a/cmake/Targets.cmake +++ b/cmake/Targets.cmake @@ -35,10 +35,7 @@ foreach(var_pair INIPARSER LIBSQL LIBXML2 - TCL - NOTCURSES - OPENSSL - LIBRESSL) + OPENSSL) if(${var_pair}_INCLUDE_DIRS) target_include_directories(fun_core PRIVATE ${${var_pair}_INCLUDE_DIRS}) endif() @@ -79,17 +76,11 @@ 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() if(FUN_WITH_OPENSSL) target_compile_definitions(fun_core PUBLIC FUN_WITH_OPENSSL=1) @@ -97,9 +88,6 @@ endif() # LibreSSL toggle: ensure compile definitions are applied to fun_core so # the LibreSSL code paths are compiled, and linking vars are handled above. -if(FUN_WITH_LIBRESSL) - target_compile_definitions(fun_core PUBLIC FUN_WITH_LIBRESSL=1) -endif() # Workaround: provide a dummy rust_eh_personality to satisfy linker when using # no_std Rust staticlib with panic abort (some toolchains still reference it). diff --git a/docs/README.md b/docs/README.md index d0cbc19..75c135a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -21,7 +21,7 @@ This file serves as an index of the documents in this directory. Links are relat ## New and supplemental guides -- [build.md](./build.md) - How to build Fun with CMake, available targets, and build options (FUN_DEBUG, FUN_USE_MUSL, FUN_WITH_CPP, FUN_WITH_RUST, FUN_WITH_OPENSSL, FUN_WITH_LIBRESSL). +- [build.md](./build.md) - How to build Fun with CMake, available targets, and build options (FUN_DEBUG, FUN_USE_MUSL, FUN_WITH_CPP, FUN_WITH_RUST, FUN_WITH_OPENSSL). - [cli.md](./cli.md) - Command-line usage of the `fun` executable: synopsis, options, exit codes, includes and library paths. - [fun.md](./fun.md) - Full usage guide for the `fun` executable: invocation patterns, REPL, env vars, include paths, examples, and install locations. - [asyncio.md](./asyncio.md) - Async I/O primitives and patterns: non-blocking sockets, fd polling, examples, and best practices. @@ -47,7 +47,7 @@ This file serves as an index of the documents in this directory. Links are relat Documentation for optional, build-time selectable integrations lives in [external/](./external/): - [Index of extensions](./external/README.md) - - Highlights: [cURL](./external/curl.md), [INI](./external/ini.md), [JSON](./external/json.md), [XML (libxml2)](./external/xml2.md), [SQLite](./external/sqlite.md), [libSQL](./external/libsql.md), [PCRE2](./external/pcre2.md), [PC/SC](./external/pcsc.md), [Notcurses](./external/notcurses.md), [Tcl/Tk](./external/tcltk.md), [OpenSSL](./external/openssl.md), [LibreSSL](./external/libressl.md) + - Highlights: [cURL](./external/curl.md), [INI](./external/ini.md), [JSON](./external/json.md), [XML (libxml2)](./external/xml2.md), [SQLite](./external/sqlite.md), [PCRE2](./external/pcre2.md), [PC/SC](./external/pcsc.md), [OpenSSL](./external/openssl.md) ## Tips @@ -55,4 +55,3 @@ Documentation for optional, build-time selectable integrations lives in [externa - For a broader project overview and quickstart, see the repository root [README.md](../README.md). - Crypto examples: - If built with `-DFUN_WITH_OPENSSL=ON`, try `examples/crypto/openssl_md5.fun`. - - If built with `-DFUN_WITH_LIBRESSL=ON`, try `examples/crypto/libressl_md5.fun`. diff --git a/docs/build.md b/docs/build.md index 04c45a0..9fb92d6 100644 --- a/docs/build.md +++ b/docs/build.md @@ -23,7 +23,6 @@ Fun exposes several options you can toggle at configure time: - `FUN_WITH_CPP` (ON/OFF) - Enable C++-based opcode/examples support - `FUN_WITH_RUST` (ON/OFF) - Build and link Rust staticlib from `src/rust/` - `FUN_WITH_OPENSSL` (ON/OFF) - Enable OpenSSL-backed helpers (MD5/SHA-256/SHA-512/RIPEMD-160) -- `FUN_WITH_LIBRESSL` (ON/OFF) - Enable LibreSSL-backed helpers (MD5/SHA-256/SHA-512/RIPEMD-160) When configuring, the build prints a summary like: @@ -56,7 +55,7 @@ cmake --build build_release --target build ### Enabling optional extensions ``` cmake -S . -B build_release -DCMAKE_BUILD_TYPE=Release \ - -DFUN_WITH_CPP=ON -DFUN_WITH_RUST=ON -DFUN_WITH_OPENSSL=ON -DFUN_WITH_LIBRESSL=ON + -DFUN_WITH_CPP=ON -DFUN_WITH_RUST=ON -DFUN_WITH_OPENSSL=ON cmake --build build_release --target build ``` @@ -64,7 +63,6 @@ If `FUN_WITH_RUST` is enabled, ensure `cargo` is available in PATH; the build wi If `FUN_WITH_OPENSSL` is enabled, CMake must detect your system OpenSSL (libcrypto). -If `FUN_WITH_LIBRESSL` is enabled, CMake detects LibreSSL directly (via pkg-config or standard include/lib locations) and links to LibreSSL's `libcrypto` — no OpenSSL installation is required. Both extensions use the EVP interface. Note: On OpenSSL 3.x, RIPEMD-160 may require the legacy provider; if unavailable, the helper returns an empty string. ## Running - CLI: run the `fun` executable from your build directory. diff --git a/docs/external/README.md b/docs/external/README.md index 5219cbd..c9986d0 100644 --- a/docs/external/README.md +++ b/docs/external/README.md @@ -13,13 +13,9 @@ This section documents Fun's optional, build-time selectable extensions. Each pa - [JSON (json-c)](./json.md) - [libxml2 (XML)](./xml2.md) - [SQLite](./sqlite.md) -- [libSQL (SQLite-compatible)](./libsql.md) - [PCRE2 (Perl-compatible regex)](./pcre2.md) - [PC/SC (Smart cards)](./pcsc.md) -- [Notcurses (TUI)](./notcurses.md) -- [Tcl/Tk (GUI)](./tcltk.md) - [OpenSSL](./openssl.md) -- [LibreSSL](./libressl.md) ## Notes: diff --git a/docs/external/libressl.md b/docs/external/libressl.md deleted file mode 100644 index 388c69a..0000000 --- a/docs/external/libressl.md +++ /dev/null @@ -1,45 +0,0 @@ -# LibreSSL extension (optional) - -- CMake option: FUN_WITH_LIBRESSL=ON -- Purpose: provide small crypto helpers backed by LibreSSL's libcrypto. Includes md5, sha256, sha512, ripemd160 helpers under libressl_* names. -- Homepage: https://www.libressl.org/ - -## Build notes: - -- Requires system LibreSSL development headers and libcrypto. The build detects LibreSSL directly (via pkg-config or standard include/lib locations) and links to libcrypto from LibreSSL — it does not require OpenSSL to be installed. -- When disabled, the builtins below evaluate to empty strings to mirror optionality behavior across extensions. - -## Provided helper functions/opcodes: - -- Function: libressl_md5(data:string) -> string (lowercase hex). -- Function: libressl_sha256(data:string) -> string (lowercase hex). -- Function: libressl_sha512(data:string) -> string (lowercase hex). -- Function: libressl_ripemd160(data:string) -> string (lowercase hex). -- Opcodes: OP_LIBRESSL_MD5, OP_LIBRESSL_SHA256, OP_LIBRESSL_SHA512, OP_LIBRESSL_RIPEMD160 (internal mappings for the functions above). - -## Quickstart: - -- Configure: `cmake -S . -B build -DFUN_WITH_LIBRESSL=ON` -- Build: `cmake --build build --target fun` -- Run examples: - - `./build/fun examples/crypto/libressl_md5.fun` - - `./build/fun examples/crypto/libressl_sha256.fun` - - `./build/fun examples/crypto/libressl_sha512.fun` - - `./build/fun examples/crypto/libressl_ripemd160.fun` - -## Example output: - -- md5(abc) = 900150983cd24fb0d6963f7d28e17f72 -- md5("") = d41d8cd98f00b204e9800998ecf8427e -- sha256(abc) = ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad -- sha256("") = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -- sha512(abc) = ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f -- sha512("") = cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e -- ripemd160(abc) = 8eb208f7e05d987a9b044a8e98c6b087f15a0bfc -- ripemd160("") = 9c1185a5c5e9fc54612808977ee8f548b2258d31 - -## Notes: - -- The functions accept any Fun value; non-strings are coerced via `to_string` semantics by the VM before hashing. -- On some platforms, RIPEMD-160 may not be available; in that case the helper returns an empty string. -- If your system installs LibreSSL headers under `/usr/include/libressl`, CMake will automatically add this directory to the include path so that `#include ` resolves correctly. diff --git a/docs/external/libsql.md b/docs/external/libsql.md deleted file mode 100644 index 43662a7..0000000 --- a/docs/external/libsql.md +++ /dev/null @@ -1,17 +0,0 @@ -# libSQL extension (optional) - -- CMake option: FUN_WITH_LIBSQL=ON -- Purpose: SQLite-compatible client using the libSQL (Turso) library. -- Homepage: https://libsql.org/ - -## Opcodes: - -- OP_LIBSQL_OPEN: pops url_or_path; pushes handle (>0) or 0 -- OP_LIBSQL_CLOSE: pops handle; pushes Nil -- OP_LIBSQL_EXEC: pops sql, handle; pushes rc:int (0=OK) -- OP_LIBSQL_QUERY: pops sql, handle; pushes array - -## Notes: - -- Uses a SQLite-compatible C API provided by libSQL; behavior is similar to the SQLite backend. -- This module is independent from the SQLite extension; you may enable either or both. diff --git a/docs/external/notcurses.md b/docs/external/notcurses.md deleted file mode 100644 index f845843..0000000 --- a/docs/external/notcurses.md +++ /dev/null @@ -1,27 +0,0 @@ -# Notcurses (TUI) extension (optional) - -- CMake option: FUN_WITH_NOTCURSES=ON -- Purpose: Terminal UI capabilities via the Notcurses library. -- Homepage: https://notcurses.com/ - -## Opcodes: - -- OP_NC_INIT: initialize Notcurses; returns 1 on success, 0 on failure -- OP_NC_SHUTDOWN: shutdown; returns 0 -- OP_NC_CLEAR: clear screen/plane; returns 0 -- OP_NC_DRAW_TEXT: pops text, x, y; draws; returns 0 -- OP_NC_GETCH: pops timeout_ms; returns codepoint or -1 on timeout/error -- OP_NC_GET_SIZE: push [rows, cols] of the stdplane, or -1 if unavailable -- OP_NC_SET_STYLE: pops style, bg_rgb, fg_rgb; sets colors/styles for stdplane; returns 0/-1 -- OP_NC_DRAW_CHAR: pops ch, x, y; draws a single Unicode codepoint; returns 0/-1 -- OP_NC_DRAW_HLINE: pops len, x, y, ch; draws a horizontal line; returns 0/-1 -- OP_NC_DRAW_VLINE: pops len, x, y, ch; draws a vertical line; returns 0/-1 -- OP_NC_BOX: pops x, y, w, h, style; draws a rectangle using ASCII/Unicode box glyphs; returns 0/-1 -- OP_NC_FILL: pops x, y, w, h, ch; fills an area with a codepoint; returns 0/-1 -- OP_NC_RENDER: forces a render; returns 0/-1 - -## Notes: - -- Requires Notcurses development headers/libs. -- Behavior may vary across terminals; see the implementation for details. -- When Fun is built without Notcurses (FUN_WITH_NOTCURSES=OFF), these opcodes still exist but perform no-ops and return -1/0 as documented, allowing scripts to degrade gracefully. diff --git a/docs/external/tcltk.md b/docs/external/tcltk.md deleted file mode 100644 index f39d66a..0000000 --- a/docs/external/tcltk.md +++ /dev/null @@ -1,21 +0,0 @@ -# Tcl/Tk (GUI) extension (optional) - -- CMake option: FUN_WITH_TCLTK=ON -- Purpose: Basic GUI functionality via Tcl/Tk. -- Homepage: https://www.tcl.tk/ - -## Opcodes: - -- OP_TK_EVAL: pops script string; pushes rc (0=OK) -- OP_TK_RESULT: pushes last Tcl result string -- OP_TK_LOOP: enters main event loop; pushes Nil when done -- OP_TK_WM_TITLE: pops title string; sets window title; pushes rc -- OP_TK_LABEL: pops text, id; creates/updates label .id; pushes rc -- OP_TK_BUTTON: pops text, id; creates/updates button .id; pushes rc -- OP_TK_PACK: pops id; packs .id; pushes rc -- OP_TK_BIND: pops command, event, id; binds; pushes rc - -## Notes: - -- Requires Tcl/Tk development headers/libs. -- GUI behavior depends on your desktop environment/window manager. diff --git a/docs/handbook.md b/docs/handbook.md index 006d60a..3e6508a 100644 --- a/docs/handbook.md +++ b/docs/handbook.md @@ -93,15 +93,12 @@ Pass all options as -DNAME=VALUE. The most relevant toggles are: - FUN_DEBUG=ON|OFF — verbose VM debug logging (default OFF) - FUN_WITH_CURL=ON|OFF — enable CURL (libcurl) support (default OFF) - FUN_WITH_JSON=ON|OFF — enable JSON (json-c) support (default OFF) -- FUN_WITH_LIBSQL=ON|OFF — enable libSQL (Turso) client support (default OFF) - FUN_WITH_XML2=ON|OFF — enable XML (libxml2) support (default OFF) - FUN_WITH_PCRE2=ON|OFF — enable PCRE2 (Perl-Compatible Regular Expressions) (default OFF) - FUN_WITH_PCSC=ON|OFF — enable PC/SC smart card (PCSC lite) support (default OFF) - FUN_WITH_REPL=ON|OFF — enable the interactive REPL (default OFF) - FUN_WITH_SQLITE=ON|OFF — enable SQLite (sqlite3) support (default OFF) -- FUN_WITH_TCLTK=ON|OFF — enable Tk (GUI via Tcl/Tk) support (default OFF) - FUN_WITH_INI=ON|OFF — enable INI (iniparser) support (default OFF) -- FUN_WITH_NOTCURSES=ON|OFF — enable Notcurses TUI support (default OFF) You can also set the default search path for the bundled stdlib with DEFAULT_LIB_DIR: @@ -131,27 +128,6 @@ sqlite3 ./database.sqlite < ./examples/data/database.sql FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/sqlite_example.fun ``` -#### libSQL example (optional feature) - -libSQL support is optional and disabled by default. It is implemented as an independent extension and can coexist with SQLite. To build with it and run the example: - -``` -cmake -S . -B build -DFUN_WITH_LIBSQL=ON -cmake --build build --target fun - -# Create the sample database using the sqlite3 CLI (libSQL implements the sqlite C API) -sqlite3 ./database.sqlite < ./examples/data/database.sql - -# Run the libSQL example -FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/libsql_example.fun -``` - -Available builtins when built with -DFUN_WITH_LIBSQL=ON: -- libsql_open(path_or_url) -> handle (>0) or 0 on error -- libsql_close(handle) -> Nil -- libsql_exec(handle, sql) -> rc (0 on success) -- libsql_query(handle, sql) -> array of map rows - #### XML example (optional feature) XML support (via libxml2) is optional and disabled by default. To build with it and run the example: @@ -196,52 +172,6 @@ Notes: - Handles are simple integers managed by the VM; nodes are owned by their document. - This initial integration focuses on parsing and basic navigation. Attributes, children iteration, and XPath may be added later. -#### Tk GUI example (optional feature) - -Tk GUI support is optional and disabled by default. It embeds a Tcl/Tk interpreter and exposes a small, Tk-only API to Fun code (no raw Tcl required). - -To build with Tk and run the example: - -``` -cmake -S . -B build -DFUN_WITH_TCLTK=ON -cmake --build build --target fun - -# Run the example -FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/tk_hello.fun -``` - -Available VM builtins when built with -DFUN_WITH_TCLTK=ON: -- tk_title(title: string) -> rc -- tk_label(id: string, text: string) -> rc -- tk_button(id: string, text: string) -> rc -- tk_pack(id: string) -> rc -- tk_loop() -> Nil (enters event loop until the window is closed) - -Standard library wrapper (lib/ui/tk.fun): -- class TK - - title(title: string): int - - label(id: string, text: string): int - - button(id: string, text: string): int - - pack(id: string): int - - loop(): Nil - -Example Fun code: -``` -include - -tk = TK() -tk.title("Fun + Tk GUI") -tk.label("hello", "Hello, world!") -tk.pack("hello") -tk.button("ok", "OK") -tk.pack("ok") -tk.loop() -``` - -Notes: -- Ensure Tcl/Tk is installed (8.6+). On Linux install tcl/tk packages; on macOS install Homebrew tcl-tk; on Windows ensure the DLLs are available. -- The Fun process terminates when the main window is closed or when the example's OK button is clicked. - ### Install Fun to the OS (optional) Not recommended during early development, but supported: @@ -534,7 +464,7 @@ Notes: - JSON types map to Fun types: object -> map, array -> array, string -> string, number -> number/float, true/false -> 1/0, null -> nil. - When writing, prettyFlag=1 enables pretty printing. -### CURL (optional) +### cURL (optional) Build flag: -DFUN_WITH_CURL=ON; requires libcurl. @@ -658,28 +588,6 @@ else ini_free(h) ``` -### Notcurses (optional) - -Build flag: -DFUN_WITH_NOTCURSES=ON; requires notcurses (pkg-config: notcurses or notcurses-core). - -VM API: -- nc_init() -> 1/0 (initialize TUI; call once) -- nc_draw_text(x, y, text) -> 0 on success, -1 on error -- nc_clear() -> 0 on success, -1 on error -- nc_getch(timeout_ms) -> key code (int), -1 if timeout/no input -- nc_shutdown() -> 1/0 (restore terminal) - -Example: -``` -if (nc_init()) - nc_draw_text(2, 1, "Fun + Notcurses") - print("Press any key...") - k = nc_getch(0) // 0 = blocking - nc_shutdown() -else - print("Notcurses init failed") -``` - ### XML (optional) Build flag: -DFUN_WITH_XML2=ON; requires libxml2. @@ -715,22 +623,6 @@ else Notes: - Handles are integers managed by the VM; nodes belong to their document. -### libSQL (optional) - -Build flag: -DFUN_WITH_LIBSQL=ON; requires libSQL client (compatible with sqlite3 C API). - -VM API: -- libsql_open(path_or_url) -> handle (>0) or 0 on error -- libsql_close(handle) -> nil -- libsql_exec(handle, sql) -> rc (0 on success) -- libsql_query(handle, sql) -> array of row maps - -Example flow: -1) h = libsql_open("./database.sqlite") -2) rows = libsql_query(h, "SELECT id, title FROM tasks;") -3) rc = libsql_exec(h, "INSERT INTO tasks (title) VALUES ('Try libSQL');") -4) libsql_close(h) - ### PCRE2 / Regex (optional) Build flag: -DFUN_WITH_PCRE2=ON; requires PCRE2 (8-bit API). @@ -748,35 +640,6 @@ Examples: regex_demo.fun, regex_procedural.fun Notes: - Patterns use PCRE2 syntax. -### Tcl/Tk GUI (optional) - -Build flag: -DFUN_WITH_TCLTK=ON; requires Tcl/Tk (8.6+). - -VM API: -- tk_title(title) -> rc -- tk_label(id, text) -> rc -- tk_button(id, text) -> rc -- tk_pack(id) -> rc -- tk_loop() -> nil (enters event loop) - -Stdlib wrapper: -- class TK (lib/ui/tk.fun) mirrors the VM API. - -Example: -``` -include - -tk = TK() -tk.title("Fun + Tk GUI") -tk.label("hello", "Hello, world!") -tk.pack("hello") -tk.button("ok", "OK") -tk.pack("ok") -tk.loop() -``` - -Notes: -- Ensure Tcl/Tk runtime libraries are available on your system. ### REPL (optional) diff --git a/docs/opcodes.md b/docs/opcodes.md index 3d512e9..5b04a27 100644 --- a/docs/opcodes.md +++ b/docs/opcodes.md @@ -157,12 +157,7 @@ This document provides an overview of the available VM opcodes implemented under - OP_SQLITE_EXEC: Execute statement; pops handle:int, sql:string; pushes rc:int (0=OK). - OP_SQLITE_QUERY: Run query; pops handle:int, sql:string; pushes array>. -## libSQL - -- OP_LIBSQL_OPEN: Open database (url or path); pops string; pushes handle (>0) or 0. -- OP_LIBSQL_CLOSE: Close database; pops handle; pushes Nil. -- OP_LIBSQL_EXEC: Execute statement; pops handle:int, sql:string; pushes rc:int (0=OK). -- OP_LIBSQL_QUERY: Run query; pops handle:int, sql:string; pushes array>. + ## OS, Time, Processes, Threads, Sockets, Serial @@ -245,9 +240,7 @@ This document provides an overview of the available VM opcodes implemented under - OP_NC_FILL: Fill rectangle with codepoint; pops x:int, y:int, w:int, h:int, ch:int; pushes 0/-1. - OP_NC_RENDER: Force a render; pushes 0/-1. -## SQLite-compatible lib (libSQL) - -- See “libSQL” section above; identical opcode set with different backend. + ## TK (Tcl/Tk UI) diff --git a/examples/blocking/tk_file_manager.fun b/examples/blocking/tk_file_manager.fun deleted file mode 100755 index 1614c45..0000000 --- a/examples/blocking/tk_file_manager.fun +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env fun - -/* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - * - * Added: 2025-12-23 - */ - -#include - -print("Initializing Fun File Manager...") -tk = TK() -tk.title("Fun File Manager") - -// Current directory -dir = env("PWD") -if (dir == "") - dir = "." -print("Current directory: " + dir) - -tk.label("path", "Current Dir: " + dir) -tk.pack("path") - -// Files listbox -tk.listbox("files") -tk.pack("files") - -// Populate listbox -fun refresh(tk, dir) - print("Refreshing file list for: " + dir) - tk.clear("files") - files = os_list_dir(dir) - print("Found " + to_string(len(files)) + " entries.") - for f in files - tk.insert("files", "end", f) - -refresh(tk, dir) - -// Refresh button -// Using tk.eval for button because tk.button currently exits the app. -tk.eval("button .refresh -text {Refresh} -command {puts {Refresh requested}}") -tk.pack("refresh") - -// Exit button -tk.button("exit", "Exit") -tk.pack("exit") - -print("Entering Tk loop...") -tk.loop() -print("Tk loop exited.") - -/* Expected output: -A GUI... ;) -*/ diff --git a/examples/blocking/tk_hello.fun b/examples/blocking/tk_hello.fun deleted file mode 100755 index 88d2129..0000000 --- a/examples/blocking/tk_hello.fun +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env fun - -/* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - * - * Added: 2025-12-09 - */ - -// Demonstrates the Tk stdlib wrapper class using the new Tk opcodes. - -include - -tk = TK() - -tk.title("Fun + Tk GUI") - -tk.label("hello", "Hello, world!") -tk.pack("hello") - -tk.button("ok", "OK") -tk.pack("ok") - -// Enter GUI loop (no-op if built without FUN_WITH_TCLTK) -tk.loop() - -/* Expected output: -A GUI... ;) -*/ diff --git a/examples/blocking/tk_testing.fun b/examples/blocking/tk_testing.fun deleted file mode 100755 index 9bff362..0000000 --- a/examples/blocking/tk_testing.fun +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env fun - -/* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - * - * Added: 2025-12-23 - */ - -#include - -print("Testing os_list_dir...") -files = os_list_dir(".") -print("Found " + to_string(len(files)) + " files.") -if len(files) > 0 - print("First file: " + files[0]) - -print("Testing tk_bind parsing...") -// We can't easily test Tk without an X server, but we can see if it crashes. -// If built with FUN_WITH_TCLTK, it should at least initialize. -// We use tk_eval to avoid full loop. -rc = tk_eval("set x 1") -print("tk_eval rc: " + to_string(rc)) -if rc == 0 - print("Tcl Result: " + tk_result()) - -/* Expected output: -Testing os_list_dir... -Found 23 files. -First file: build -Testing tk_bind parsing... -tk_eval rc: 0 -Tcl Result: 1 -*/ diff --git a/examples/broken/complex_nested_no_capture.fun b/examples/broken/complex_nested_no_capture.fun new file mode 100644 index 0000000..648bb1d --- /dev/null +++ b/examples/broken/complex_nested_no_capture.fun @@ -0,0 +1,177 @@ +#!/usr/bin/env fun + +/* + * 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-04-02 + */ + +/* + * Complex demonstration of nested functions that are local to their + * enclosing function, without any variable capture (no closures). + * All state is passed explicitly through parameters at each call. + * + * The example showcases: + * - Deeply nested helpers and multi-stage orchestration + * - Returning a nested function and using it later + * - Recursion implemented via a nested helper + * - Working with maps while threading state explicitly + */ + +print("=== Complex nested functions (no captures) ===") + +// 1) A small data pipeline on a user map, using local helpers +fun process_user(user) + // Ensure a key is present; if missing, fill with default + fun ensure_has(m, key, def) + if has(m, key) + return m + m[key] = def + return m + + // Normalize optional fields + fun normalize_city(m) + if has(m, "city") + return m + m["city"] = "Unknown" + return m + + // Add derived attributes explicitly via parameters (no function calls on RHS) + fun annotate(m) + a = m["age"] + // Inline age grouping without relying on an additional helper + g = "" + if a < 13 + g = "child" + else if a < 20 + g = "teen" + else if a < 65 + g = "adult" + else + g = "senior" + m["group"] = g + return m + + m1 = ensure_has(user, "name", "N/A") + m2 = ensure_has(m1, "age", 0) + m3 = normalize_city(m2) + m4 = annotate(m3) + return m4 + +u1 = {"name": "Ada", "age": 37} +u2 = process_user(u1) +print("process_user -> " + to_string(u2)) + +print("") +print("=== Returning a nested function (no captures) ===") + +// 2) Return an inner function and use it later. The returned function +// still requires all the data it needs as parameters (no implicit capture). +fun math_suite() + fun addk(x, k) + return x + k + + fun mulk(x, k) + return x * k + + // Integer power via nested recursion helper + fun powi(x, n) + fun loop(acc, base, exp) + if exp == 0 + return acc + return loop(acc * base, base, exp - 1) + return loop(1, x, n) + + // Compose a small arithmetic chain explicitly. Break into simple steps to + // match parser expectations and avoid nested calls on the right-hand side. + fun apply_chain(x, k_a, k_b, n) + t1 = x + k_a + t2 = t1 * k_b + t3 = powi(t2, n) + return t3 + + return apply_chain + +chain = math_suite() +print("chain(2, 3, 4, 2) -> expected ((2+3)*4)^2 = 400") +print(chain(2, 3, 4, 2)) + +print("") +print("=== Deep orchestration with 3-level nesting ===") + +// 3) Multi-stage pipeline with explicit parameter threading through each level +fun orchestrate(a, b, c) + fun stage1(x, a_, b_, c_) + fun stage2(y, b2, c2) + fun stage3(z, c3) + // No capture: every value needed arrives via parameters + tmp = z + c3 + return tmp * 2 + return stage3(y + b2, c2) + return stage2(x + a_, b_, c_) + + // Kick off with x = 0 and thread a, b, c explicitly + return stage1(0, a, b, c) + +print("orchestrate(1, 2, 3) -> stage3((0+1)+2, 3) * 2 = (3+3)*2 = 12") +print(orchestrate(1, 2, 3)) + +print("") +print("=== Nested recursion: factorial via inner loop ===") + +// 4) Factorial using an inner tail-recursive helper (no captures) +fun fact(n) + fun go(i, acc) + if i <= 1 + return acc + return go(i - 1, acc * i) + return go(n, 1) + +print("fact(6) -> expected 720") +print(fact(6)) + +print("") +print("=== Higher-order style without captures ===") + +// 5) Higher-order-like usage where the "strategy" function receives +// all needed parameters explicitly. +fun reducer_sum_with_limit(x, limit) + if x > limit + return 0 + return x + +fun fold3(a, b, c, f, p1, p2, p3) + // Apply f to each and sum; f must accept (value, param) + s1 = f(a, p1) + s2 = f(b, p2) + s3 = f(c, p3) + return s1 + s2 + s3 + +print("fold3 with limit: (5<=10 ? 5 : 0) + (12<=10 ? 0 : 0) + (7<=10 ? 7 : 0) = 12") +print(fold3(5, 12, 7, reducer_sum_with_limit, 10, 10, 10)) + +/* Expected output: +=== Complex nested functions (no captures) === +process_user -> {"age": 37, "city": "Unknown", "group": "adult", "name": "Ada"} + +=== Returning a nested function (no captures) === +chain(2, 3, 4, 2) -> expected ((2+3)*4)^2 = 400 +400 + +=== Deep orchestration with 3-level nesting === +orchestrate(1, 2, 3) -> stage3((0+1)+2, 3) * 2 = (3+3)*2 = 12 +12 + +=== Nested recursion: factorial via inner loop === +fact(6) -> expected 720 +720 + +=== Higher-order style without captures === +fold3 with limit: (5<=10 ? 5 : 0) + (12<=10 ? 0 : 0) + (7<=10 ? 7 : 0) = 12 +12 +*/ diff --git a/examples/crypto/libressl_md5.fun b/examples/crypto/libressl_md5.fun deleted file mode 100755 index 24b3740..0000000 --- a/examples/crypto/libressl_md5.fun +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env fun - -/* - * 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-02-19 - */ - -// LibreSSL MD5 example -// Enable with -DFUN_WITH_LIBRESSL=ON during build for real hashing. - -s = "abc" -d = libressl_md5(s) -print("md5(abc) = " + d) - -// Another quick check (empty string) -e = "" -print("md5(\"\") = " + libressl_md5(e)) - -/* Expected output: -md5(abc) = 900150983cd24fb0d6963f7d28e17f72 -md5("") = d41d8cd98f00b204e9800998ecf8427e -*/ diff --git a/examples/crypto/libressl_ripemd160.fun b/examples/crypto/libressl_ripemd160.fun deleted file mode 100755 index 437e6e1..0000000 --- a/examples/crypto/libressl_ripemd160.fun +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env fun - -/* - * 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-02-19 - */ - -// LibreSSL RIPEMD-160 example -// Enable with -DFUN_WITH_LIBRESSL=ON during build for real hashing. - -s = "abc" -d = libressl_ripemd160(s) -print("ripemd160(abc) = " + d) - -// Another quick check (empty string) -e = "" -print("ripemd160(\"\") = " + libressl_ripemd160(e)) - -/* Expected output: -ripemd160(abc) = 8eb208f7e05d987a9b049a9a5c0c2b74e07e6a5d -ripemd160("") = 9c1185a5c5e9fc54612808977ee8f548b2258d31 -*/ diff --git a/examples/crypto/libressl_sha256.fun b/examples/crypto/libressl_sha256.fun deleted file mode 100755 index a51baed..0000000 --- a/examples/crypto/libressl_sha256.fun +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env fun - -/* - * 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-02-19 - */ - -// LibreSSL SHA-256 example -// Enable with -DFUN_WITH_LIBRESSL=ON during build for real hashing. - -s = "abc" -d = libressl_sha256(s) -print("sha256(abc) = " + d) - -// Another quick check (empty string) -e = "" -print("sha256(\"\") = " + libressl_sha256(e)) - -/* Expected output: -sha256(abc) = ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad -sha256("") = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -*/ diff --git a/examples/crypto/libressl_sha512.fun b/examples/crypto/libressl_sha512.fun deleted file mode 100755 index 525dbcf..0000000 --- a/examples/crypto/libressl_sha512.fun +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env fun - -/* - * 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-02-19 - */ - -// LibreSSL SHA-512 example -// Enable with -DFUN_WITH_LIBRESSL=ON during build for real hashing. - -s = "abc" -d = libressl_sha512(s) -print("sha512(abc) = " + d) - -// Another quick check (empty string) -e = "" -print("sha512(\"\") = " + libressl_sha512(e)) - -/* Expected output: -sha512(abc) = ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f -sha512("") = cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e -*/ diff --git a/examples/extra/curl_download.fun b/examples/external/curl/curl_download.fun similarity index 100% rename from examples/extra/curl_download.fun rename to examples/external/curl/curl_download.fun diff --git a/examples/extra/curl_get_json.fun b/examples/external/curl/curl_get_json.fun similarity index 100% rename from examples/extra/curl_get_json.fun rename to examples/external/curl/curl_get_json.fun diff --git a/examples/extra/curl_post.fun b/examples/external/curl/curl_post.fun similarity index 100% rename from examples/extra/curl_post.fun rename to examples/external/curl/curl_post.fun diff --git a/examples/extra/ini_class_demo.fun b/examples/external/ini/ini_class_demo.fun similarity index 100% rename from examples/extra/ini_class_demo.fun rename to examples/external/ini/ini_class_demo.fun diff --git a/examples/extra/ini_complex.fun b/examples/external/ini/ini_complex.fun similarity index 100% rename from examples/extra/ini_complex.fun rename to examples/external/ini/ini_complex.fun diff --git a/examples/extra/ini_demo.fun b/examples/external/ini/ini_demo.fun similarity index 100% rename from examples/extra/ini_demo.fun rename to examples/external/ini/ini_demo.fun diff --git a/examples/extra/ini_diag.fun b/examples/external/ini/ini_diag.fun similarity index 100% rename from examples/extra/ini_diag.fun rename to examples/external/ini/ini_diag.fun diff --git a/examples/extra/ini_subsections.fun b/examples/external/ini/ini_subsections.fun similarity index 100% rename from examples/extra/ini_subsections.fun rename to examples/external/ini/ini_subsections.fun diff --git a/examples/extra/json_showcase.fun b/examples/external/json/json_showcase.fun similarity index 100% rename from examples/extra/json_showcase.fun rename to examples/external/json/json_showcase.fun diff --git a/examples/crypto/openssl_md5.fun b/examples/external/openssl/openssl_md5.fun similarity index 100% rename from examples/crypto/openssl_md5.fun rename to examples/external/openssl/openssl_md5.fun diff --git a/examples/crypto/openssl_ripemd160.fun b/examples/external/openssl/openssl_ripemd160.fun similarity index 100% rename from examples/crypto/openssl_ripemd160.fun rename to examples/external/openssl/openssl_ripemd160.fun diff --git a/examples/crypto/openssl_sha256.fun b/examples/external/openssl/openssl_sha256.fun similarity index 100% rename from examples/crypto/openssl_sha256.fun rename to examples/external/openssl/openssl_sha256.fun diff --git a/examples/crypto/openssl_sha512.fun b/examples/external/openssl/openssl_sha512.fun similarity index 100% rename from examples/crypto/openssl_sha512.fun rename to examples/external/openssl/openssl_sha512.fun diff --git a/examples/extra/pcre2_demo.fun b/examples/external/pcre2/pcre2_demo.fun similarity index 100% rename from examples/extra/pcre2_demo.fun rename to examples/external/pcre2/pcre2_demo.fun diff --git a/examples/extra/pcre2_opcodes.fun b/examples/external/pcre2/pcre2_opcodes.fun similarity index 100% rename from examples/extra/pcre2_opcodes.fun rename to examples/external/pcre2/pcre2_opcodes.fun diff --git a/examples/extra/pcre2_showcase.fun b/examples/external/pcre2/pcre2_showcase.fun similarity index 100% rename from examples/extra/pcre2_showcase.fun rename to examples/external/pcre2/pcre2_showcase.fun diff --git a/examples/extra/pcsc2_example.fun b/examples/external/pcre2/pcsc2_example.fun similarity index 100% rename from examples/extra/pcsc2_example.fun rename to examples/external/pcre2/pcsc2_example.fun diff --git a/examples/extra/pcsc_demo.fun b/examples/external/pcsc/pcsc_demo.fun similarity index 100% rename from examples/extra/pcsc_demo.fun rename to examples/external/pcsc/pcsc_demo.fun diff --git a/examples/extra/pcsc_example.fun b/examples/external/pcsc/pcsc_example.fun similarity index 100% rename from examples/extra/pcsc_example.fun rename to examples/external/pcsc/pcsc_example.fun diff --git a/examples/sqlited/README.md b/examples/external/sqlite/sqlited/README.md similarity index 100% rename from examples/sqlited/README.md rename to examples/external/sqlite/sqlited/README.md diff --git a/examples/sqlited/client.fun b/examples/external/sqlite/sqlited/client.fun similarity index 100% rename from examples/sqlited/client.fun rename to examples/external/sqlite/sqlited/client.fun diff --git a/examples/sqlited/protocol.md b/examples/external/sqlite/sqlited/protocol.md similarity index 100% rename from examples/sqlited/protocol.md rename to examples/external/sqlite/sqlited/protocol.md diff --git a/examples/sqlited/server.fun b/examples/external/sqlite/sqlited/server.fun similarity index 100% rename from examples/sqlited/server.fun rename to examples/external/sqlite/sqlited/server.fun diff --git a/examples/extra/xml_access_catalog.fun b/examples/external/xml2/xml_access_catalog.fun similarity index 100% rename from examples/extra/xml_access_catalog.fun rename to examples/external/xml2/xml_access_catalog.fun diff --git a/examples/extra/xml_access_employees.fun b/examples/external/xml2/xml_access_employees.fun similarity index 100% rename from examples/extra/xml_access_employees.fun rename to examples/external/xml2/xml_access_employees.fun diff --git a/examples/extra/xml_access_ns.fun b/examples/external/xml2/xml_access_ns.fun similarity index 100% rename from examples/extra/xml_access_ns.fun rename to examples/external/xml2/xml_access_ns.fun diff --git a/examples/extra/xml_class_example.fun b/examples/external/xml2/xml_class_example.fun similarity index 100% rename from examples/extra/xml_class_example.fun rename to examples/external/xml2/xml_class_example.fun diff --git a/examples/extra/xml_minimal.fun b/examples/external/xml2/xml_minimal.fun similarity index 100% rename from examples/extra/xml_minimal.fun rename to examples/external/xml2/xml_minimal.fun diff --git a/examples/extra/libsql_example.fun b/examples/extra/libsql_example.fun deleted file mode 100755 index 1bfc430..0000000 --- a/examples/extra/libsql_example.fun +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env fun - -/* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - * - * Added: 2025-11-26 - */ - -// Demonstrates the optional libSQL extension -// Build with: cmake -S . -B build -DFUN_WITH_LIBSQL=ON && cmake --build build - -// Prepare sample DB from SQL if needed (requires sqlite3 CLI installed) -// Create it once with: -// sqlite3 ./database.sqlite < ./examples/data/database.sql - -number h = libsql_open("./database.sqlite") -if h == 0 - print("Failed to open libSQL database") -else - libsql_exec(h, "CREATE TABLE IF NOT EXISTS todos(id INTEGER PRIMARY KEY, title TEXT, done INT)") - libsql_exec(h, "DELETE FROM todos") - libsql_exec(h, "INSERT INTO todos(title, done) VALUES('Buy milk', 0)") - libsql_exec(h, "INSERT INTO todos(title, done) VALUES('Write code', 1)") - - rows = libsql_query(h, "SELECT id, title, done FROM todos ORDER BY id") - for row in rows - print(to_string(row["id"]) + ": " + to_string(row["title"]) + " (done="+to_string(row["done"]) + ")") - - rows = libsql_query(h, "SELECT id, title, done, created_at FROM tasks ORDER BY id;") - print("Tasks (" + to_string(len(rows)) + "):") - for row in rows - string status = "✘" - if row["done"] == 1 - status = "✔" - print("- [" + status + "] (#" + to_string(row["id"]) + ") " + to_string(row["title"]) + " — " + to_string(row["created_at"])) - - number rc = libsql_exec(h, "INSERT INTO tasks (title, done) VALUES ('Try Fun + SQLite', 0);") - print("Insert rc=" + to_string(rc)) - - rows2 = libsql_query(h, "SELECT count(*) AS cnt FROM tasks;") - print("Total tasks now: " + to_string(rows2[0]["cnt"])) - - libsql_close(h) - -/* Example output: -1: Buy milk (done=0) -2: Write code (done=1) -Tasks (3): -- [✔] (#1) Write Fun + SQLite example — 2025-11-26 23:20:41 -- [✘] (#2) Ship optional feature flag — 2025-11-26 23:20:41 -- [✘] (#3) Celebrate with coffee — 2025-11-26 23:20:41 -Insert rc=0 -Total tasks now: 4 -*/ diff --git a/examples/extra/notcurses_hello.fun b/examples/extra/notcurses_hello.fun deleted file mode 100755 index ba0c493..0000000 --- a/examples/extra/notcurses_hello.fun +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env fun - -/* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - * - * Added: 2026-01-03 - */ - -/* - * Minimal Notcurses hello example. - */ - -include - -n = Notcurses() - -if n.init() == 0 - print("Notcurses not available. Rebuild with -DFUN_WITH_NOTCURSES=ON.") - exit(0) - -n.clear() -n.draw_text(2, 0, "Fun + Notcurses") -n.draw_text(4, 0, "Press any key to exit...") - -// blocking until key -_ = n.getch(0) -n.shutdown() - -/* Possible output: -A TUI. - -After exit: -3 renders, 991,14µs (223,57µs min, 330,38µs avg, 531,91µs max) -3 rasters, 320,76µs (106,45µs min, 106,92µs avg, 107,72µs max) -3 writes, 198,26µs (62,54µs min, 66,09µs avg, 69,94µs max) -59B (0B min, 19B avg, 30B max) 1 input Ghpa: 0 -0 failed renders, 0 failed rasters, 0 refreshes, 0 input errors -RGB emits:elides: def 1:38 fg 0:0 bg 0:0 -Cell emits:elides: 39:74805 (99,95%) 97,44% 0,00% 0,00% -Bmap emits:elides: 0:0 (0,00%) 0B (0,00%) SuM: 0 (0,00%) -*/ diff --git a/examples/extra/notcurses_menu.fun b/examples/extra/notcurses_menu.fun deleted file mode 100755 index 8c17449..0000000 --- a/examples/extra/notcurses_menu.fun +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env fun - -include - -n = Notcurses() -if n.init() == 0 - print("Notcurses not available. Build with -DFUN_WITH_NOTCURSES=ON.") - exit(1) - -items = ["First", "Second", "Third", "Fourth"] -sel = 0 -while true - n.clear() - // Title - n.set_style(0xFFFF00, 0x000000, 1) // bold yellow - n.draw_text(1, 2, "Demo Menu (j/k to move, Enter to select, ESC to quit)") - - // Render menu list - n.menu(3, 4, items, sel, 30, 0, 32) - - key = n.getch(0) - if key == 27 // ESC - break - if key == 106 // 'j' - sel = (sel + 1) % len(items) - if key == 107 // 'k' - sel = (sel - 1 + len(items)) % len(items) - if key == 10 // Enter - n.status_bar("Selected: " + items[sel], 32) - // brief wait - sleep_ms(300) - -n.shutdown() diff --git a/examples/extra/notcurses_progress.fun b/examples/extra/notcurses_progress.fun deleted file mode 100755 index 8176685..0000000 --- a/examples/extra/notcurses_progress.fun +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env fun - -include - -n = Notcurses() -if n.init() == 0 - print("Notcurses not available. Build with -DFUN_WITH_NOTCURSES=ON.") - exit(1) - -n.clear() -sz = n.size() -rows = 24 -cols = 80 -if typeof(sz) == "array" - if len(sz) >= 2 - rows = sz[0] - cols = sz[1] - -y = to_number(rows) / 2 -w = cols - 10 -if w < 20 - w = 20 - -i = 0 -while i <= 100 - // styles handled inside progress(); keep plane sane - rc = n.set_style(0x00FF00, 0x000000, 1) - frac = to_number(i) / 100 - rc = n.progress(y, 5, w, frac, 0) - k = n.getch(10) - i = i + 1 - -// final message -rc = n.set_style(0xFFFFFF, 0x000000, 1) -rc = n.draw_text(y + 2, 5, "Done!") -rc = n.render() - -rc = n.shutdown() diff --git a/examples/functions_test.fun b/examples/functions/functions_test.fun similarity index 100% rename from examples/functions_test.fun rename to examples/functions/functions_test.fun diff --git a/examples/functions/nested_functions.fun b/examples/functions/nested_functions.fun new file mode 100755 index 0000000..7d2da30 --- /dev/null +++ b/examples/functions/nested_functions.fun @@ -0,0 +1,67 @@ +#!/usr/bin/env fun + +/* + * 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-04-02 + */ + +/* + * Demonstrates nested functions that are only visible inside the + * outer function where they are defined. This example avoids + * capturing outer variables (closures) and instead passes values + * explicitly, which works with the current implementation. + */ + +print("=== Nested functions (local to outer function) ===") + +// Outer function defines two inner helpers that are only usable inside outer +fun outer(a, b) + // Defined inside outer; not visible as a global symbol + fun times3(x) + return x * 3 + + // Another local helper; also only visible within outer + fun sum_plus1(x, y) + return x + y + 1 + + // Use the local helpers + t1 = times3(a) + t2 = times3(b) + return sum_plus1(t1, t2) + +print("outer(2, 5) -> expected 2*3 + 5*3 + 1 = 22") +print(outer(2, 5)) + +print("") +print("=== Deeper nesting without variable capture ===") + +// Demonstrates function-inside-function-inside-function. +// Each level avoids referencing outer locals directly; values +// are threaded through parameters instead. +fun demo_deep(n) + fun one(x) + fun two(y) + fun three(z) + return z + 1 + return three(y) + 1 + return two(x) + 1 + return one(n) + +print("demo_deep(4) -> expected 7") +print(demo_deep(4)) + +/* Expected output: +=== Nested functions (local to outer function) === +outer(2, 5) -> expected 2*3 + 5*3 + 1 = 22 +22 + +=== Deeper nesting without variable capture === +demo_deep(4) -> expected 7 +7 +*/ diff --git a/examples/functions/nested_no_capture.fun b/examples/functions/nested_no_capture.fun new file mode 100755 index 0000000..5820340 --- /dev/null +++ b/examples/functions/nested_no_capture.fun @@ -0,0 +1,41 @@ +#!/usr/bin/env fun + +/* + * 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-04-02 + */ + +/* + * Demonstrates nested functions that are only visible inside the + * outer function where they are defined. This example avoids + * capturing outer variables (closures) and instead passes values + * explicitly, which works with the current implementation. + */ + +print("=== Nested (no captures), explicit parameters ===") + +fun outer(a, b) + fun mul3(x) + return x * 3 + + fun combine(x, y) + return x + y + 1 + + ax = mul3(a) + by = mul3(b) + return combine(ax, by) + +print("outer(2, 5) -> expected 2*3 + 5*3 + 1 = 22") +print(outer(2, 5)) + +/* Expected output: +=== Nested (no captures), explicit parameters === +outer(2, 5) -> expected 2*3 + 5*3 + 1 = 22 +22 +*/ diff --git a/examples/io/word_count.fun b/examples/io/word_count.fun index 96f9810..3445239 100755 --- a/examples/io/word_count.fun +++ b/examples/io/word_count.fun @@ -320,9 +320,6 @@ using: 1 cards)](./docs/external/pcsc.md): 1 [openssl](./docs/external/openssl.md): 1 [sqlite](./docs/external/sqlite.md): 1 -[tk: 1 -(tcl/tk: 1 -gui)](./docs/external/tcltk.md): 1 [xml: 1 (libxml2)](./docs/external/xml2.md): 1 =: 2 diff --git a/examples/rust_hello.fun b/examples/rust/rust_hello.fun similarity index 100% rename from examples/rust_hello.fun rename to examples/rust/rust_hello.fun diff --git a/examples/rust_hello_args.fun b/examples/rust/rust_hello_args.fun similarity index 100% rename from examples/rust_hello_args.fun rename to examples/rust/rust_hello_args.fun diff --git a/examples/rust_hello_args_return.fun b/examples/rust/rust_hello_args_return.fun similarity index 100% rename from examples/rust_hello_args_return.fun rename to examples/rust/rust_hello_args_return.fun diff --git a/examples/snippets/nested.fun b/examples/snippets/nested.fun new file mode 100644 index 0000000..c31aa47 --- /dev/null +++ b/examples/snippets/nested.fun @@ -0,0 +1,20 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Demonstrates nested functions that are only visible inside the + * outer function where they are defined. This example avoids + * capturing outer variables (closures) and instead passes values + * explicitly, which works with the current implementation. + * + * Added: 2026-04-02 + */ + +fun outer(x) + fun inner(y) + return y * 2 + return inner(x) + inner(3) + +print(outer(5)) diff --git a/examples/snippets/parse_test2.fun b/examples/snippets/parse_test2.fun deleted file mode 100755 index 248a940..0000000 --- a/examples/snippets/parse_test2.fun +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env fun - -#include - -cgi = CGI() -pairs = cgi._parse_urlencoded("a=1&b=2&c=3") -i = 0 -n = len(pairs) -while (i < n) - p = pairs[i] - print(to_string(p[0]) + "=" + to_string(p[1])) - i = i + 1 diff --git a/examples/snippets/test_parse.fun b/examples/snippets/test_parse.fun index 248a940..4772b4b 100755 --- a/examples/snippets/test_parse.fun +++ b/examples/snippets/test_parse.fun @@ -1,5 +1,17 @@ #!/usr/bin/env fun +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Demonstrates nested functions that are only visible inside the + * outer function where they are defined. This example avoids + * capturing outer variables (closures) and instead passes values + * explicitly, which works with the current implementation. + * + * Added: 2026-04-02 + */ + #include cgi = CGI() diff --git a/lib/ui/notcurses.fun b/lib/ui/notcurses.fun deleted file mode 100644 index e4b352c..0000000 --- a/lib/ui/notcurses.fun +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env fun - -/* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - * - * Added: 2026-01-03 - */ - -// Notcurses stdlib abstraction wrapping VM NC_* builtins. -// Provides a tiny OO-style helper around nc_init/nc_clear/nc_draw_text/nc_getch/nc_shutdown. -// If Fun was built without Notcurses support, init() returns 0 and methods are no-ops. - -class Notcurses() - // Initialize Notcurses core. Returns 1 on success, 0 on failure. - fun init(this) - return nc_init() - - // Clear the screen and render. Returns 0 on success, -1 on error. - fun clear(this) - return nc_clear() - - // Draw text at y, x and render. Returns 0 on success, -1 on error. - fun draw_text(this, y, x, text) - // allow flexible types - return nc_draw_text(to_number(y), to_number(x), to_string(text)) - - // Get a Unicode codepoint (int). timeout_ms<=0 blocks; >0 returns -1 if no key ready. - fun getch(this, timeout_ms) - if timeout_ms == nil - timeout_ms = 0 - return nc_getch(to_number(timeout_ms)) - - // Shut down Notcurses, restoring the terminal. - fun shutdown(this) - return nc_shutdown() - - // (banner helper removed temporarily due to parser limitations) - - // --- Extended primitives --- - fun size(this) - return nc_get_size() - - fun render(this) - return nc_render() - - // fg/bg are 0xRRGGBB, style bitmask: bold(1), dim(2), italic(4), underline(8), strike(16), reverse(32) - fun set_style(this, fg, bg, style) - // parser order for builtin is (style,bg,fg) - return nc_set_style(to_number(style), to_number(bg), to_number(fg)) - - fun draw_char(this, y, x, ch) - return nc_draw_char(to_number(y), to_number(x), to_number(ch)) - - fun hline(this, y, x, len, ch) - return nc_draw_hline(to_number(y), to_number(x), to_number(len), to_number(ch)) - - fun vline(this, y, x, len, ch) - return nc_draw_vline(to_number(y), to_number(x), to_number(len), to_number(ch)) - - fun box(this, y, x, h, w, style) - // builtin expects (x,y,w,h,style) - return nc_box(to_number(x), to_number(y), to_number(w), to_number(h), to_number(style)) - - fun fill(this, y, x, h, w, ch) - // builtin expects (x,y,w,h,ch) - return nc_fill(to_number(x), to_number(y), to_number(w), to_number(h), to_number(ch)) - - // --- Widgets as methods --- - // Status bar on the last row - fun status_bar(this, text, style) - sz = this.size() - if style == nil - style = 32 - rc = this.set_style(0x000000, 0xFFFFFF, style) - rc = this.fill(sz[0] - 1, 0, 1, sz[1], 32) - rc = this.draw_text(sz[0] - 1, 1, text) - rc = this.render() - return 0 - - // Draw styled label - fun label(this, y, x, text, style) - if style != nil - rc = this.set_style(0xFFFFFF, 0x000000, style) - rc = this.draw_text(y, x, text) - rc = this.render() - return 0 - - // Panel with optional title - fun box_panel(this, y, x, h, w, title, style) - if style == nil - style = 0 - rc = this.box(y, x, h, w, style) - if title != nil - rc = this.draw_text(y, x + 2, to_string(title)) - rc = this.render() - return 0 - - // Progress bar (frac in 0..1) - fun progress(this, y, x, w, frac, style) - if style == nil - style = 0 - if frac < 0 - frac = 0 - if frac > 1 - frac = 1 - // frame and title - rc = nc_box(to_number(x - 2), to_number(y - 1), to_number(w + 4), 3, to_number(style)) - // background in dim gray - rc = this.set_style(0x606060, 0x000000, 2) - rc = nc_fill(to_number(y), to_number(x), to_number(w), 1, 46) // '.' - // filled portion in bold green - rc = this.set_style(0x00FF00, 0x000000, 1) - fw = to_number(frac) * w - if fw < 0 - fw = 0 - if fw > w - fw = w - if fw > 0 - rc = nc_fill(to_number(y), to_number(x), to_number(fw), 1, 35) // '#' - rc = nc_draw_text(to_number(y - 1), to_number(x), "Progress") - rc = nc_render() - return 0 - - // Simple vertical menu, highlight selected index - fun menu(this, y, x, items, selected_idx, w, style, sel_style) - if style == nil - style = 0 - if sel_style == nil - sel_style = 32 - i = 0 - while i < len(items) - if i == selected_idx - rc = this.set_style(0x000000, 0xFFFFFF, sel_style) - else - rc = this.set_style(0xFFFFFF, 0x000000, style) - if w != nil - if len(to_string(items[i])) > w - rc = this.draw_text(y + i, x, substr(to_string(items[i]), 0, w)) - else - rc = this.draw_text(y + i, x, to_string(items[i])) - else - rc = this.draw_text(y + i, x, to_string(items[i])) - i = i + 1 - rc = this.render() - return 0 - - // Input line echo with cursor - fun input_line(this, y, x, prompt, buffer, cursor_idx, w, style) - if style == nil - style = 0 - if w == nil - w = len(buffer) + len(prompt) + 2 - rc = this.set_style(0xFFFFFF, 0x000000, style) - rc = this.draw_text(y, x, to_string(prompt)) - rc = this.fill(y, x + len(prompt), 1, w, 32) - rc = this.draw_text(y, x + len(prompt), to_string(buffer)) - this.set_style(0x000000, 0xFFFFFF, 32) - ci = cursor_idx - if ci < 0 - ci = 0 - if ci > len(buffer) - ci = len(buffer) - rc = this.draw_char(y, x + len(prompt) + ci, 32) - rc = this.render() - return 0 - -// --- End of class Notcurses --- diff --git a/make b/make index a11600c..3c49fac 100755 --- a/make +++ b/make @@ -22,33 +22,26 @@ if [ "$target" = "all" ]; then && cmake -S . -B build \ -DFUN_WITH_PCSC=ON \ -DFUN_WITH_REPL=ON \ - -DFUN_WITH_LIBSQL=ON \ -DFUN_WITH_SQLITE=ON \ -DFUN_WITH_CURL=ON \ -DFUN_WITH_PCRE2=ON \ -DFUN_WITH_XML2=ON \ -DFUN_WITH_JSON=ON \ - -DFUN_WITH_TCLTK=ON \ -DFUN_WITH_INI=ON \ - -DFUN_WITH_NOTCURSES=ON \ -DFUN_WITH_CPP=ON \ -DFUN_WITH_OPENSSL=ON \ - -DFUN_WITH_LIBRESSL=ON \ && cmake --build build --target fun elif [ "$target" = "all_debug" ]; then rm -rf build \ && cmake -S . -B build \ -DFUN_WITH_PCSC=ON \ -DFUN_WITH_REPL=ON \ - -DFUN_WITH_LIBSQL=ON \ -DFUN_WITH_SQLITE=ON \ -DFUN_WITH_CURL=ON \ -DFUN_WITH_PCRE2=ON \ -DFUN_WITH_XML2=ON \ -DFUN_WITH_JSON=ON \ - -DFUN_WITH_TCLTK=ON \ -DFUN_WITH_INI=ON \ - -DFUN_WITH_NOTCURSES=ON \ -DFUN_WITH_CPP=ON \ -DFUN_WITH_OPENSSL=ON \ -DFUN_DEBUG=ON \ @@ -57,7 +50,6 @@ elif [ "$target" = "alpine" ]; then rm -rf build \ && cmake -S . -B build \ -DFUN_WITH_REPL=ON \ - -DFUN_WITH_LIBSQL=ON \ -DFUN_WITH_SQLITE=ON \ -DFUN_WITH_CURL=ON \ -DFUN_WITH_PCRE2=ON \ @@ -72,15 +64,12 @@ elif [ "$target" = "cpp_all" ]; then && cmake -S . -B build \ -DFUN_WITH_PCSC=ON \ -DFUN_WITH_REPL=ON \ - -DFUN_WITH_LIBSQL=ON \ -DFUN_WITH_SQLITE=ON \ -DFUN_WITH_CURL=ON \ -DFUN_WITH_PCRE2=ON \ -DFUN_WITH_XML2=ON \ -DFUN_WITH_JSON=ON \ - -DFUN_WITH_TCLTK=ON \ -DFUN_WITH_INI=ON \ - -DFUN_WITH_NOTCURSES=ON \ -DFUN_WITH_CPP=ON \ -DFUN_WITH_OPENSSL=ON \ && cmake --build build --target fun @@ -128,13 +117,11 @@ elif [ "$target" = "rust_all" ]; then && cmake -S . -B build \ -DFUN_WITH_PCSC=ON \ -DFUN_WITH_REPL=ON \ - -DFUN_WITH_LIBSQL=ON \ -DFUN_WITH_SQLITE=ON \ -DFUN_WITH_CURL=ON \ -DFUN_WITH_PCRE2=ON \ -DFUN_WITH_XML2=ON \ -DFUN_WITH_JSON=ON \ - -DFUN_WITH_TCLTK=ON \ -DFUN_WITH_INI=ON \ -DFUN_WITH_RUST=ON \ -DFUN_WITH_CPP=ON \ diff --git a/scripts/run_examples.sh b/scripts/run_examples.sh index f6fced6..cc38060 100755 --- a/scripts/run_examples.sh +++ b/scripts/run_examples.sh @@ -19,9 +19,8 @@ pick_bin() { local cands=( "$ROOT/fun" "$ROOT/build/fun" - "$ROOT/build/bin/fun" - "$ROOT/cmake-build-debug/fun" - "$ROOT/cmake-build-release/fun" + "$ROOT/build_debug/fun" + "$ROOT/build_release/fun" ) for p in "${cands[@]}"; do [[ -x "$p" ]] && { echo "$p"; return 0; } diff --git a/src/bytecode.c b/src/bytecode.c index 7173f91..dfbd7b5 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -257,14 +257,6 @@ static const char *opcode_name(OpCode op) { return "SQLITE_EXEC"; case OP_SQLITE_QUERY: return "SQLITE_QUERY"; - case OP_LIBSQL_OPEN: - return "LIBSQL_OPEN"; - case OP_LIBSQL_CLOSE: - return "LIBSQL_CLOSE"; - case OP_LIBSQL_EXEC: - return "LIBSQL_EXEC"; - case OP_LIBSQL_QUERY: - return "LIBSQL_QUERY"; case OP_PCSC_ESTABLISH: return "PCSC_ESTABLISH"; case OP_PCSC_RELEASE: @@ -291,14 +283,6 @@ static const char *opcode_name(OpCode op) { return "OPENSSL_SHA512"; case OP_OPENSSL_RIPEMD160: return "OPENSSL_RIPEMD160"; - case OP_LIBRESSL_MD5: - return "LIBRESSL_MD5"; - case OP_LIBRESSL_SHA256: - return "LIBRESSL_SHA256"; - case OP_LIBRESSL_SHA512: - return "LIBRESSL_SHA512"; - case OP_LIBRESSL_RIPEMD160: - return "LIBRESSL_RIPEMD160"; case OP_INI_LOAD: return "INI_LOAD"; case OP_INI_FREE: diff --git a/src/bytecode.h b/src/bytecode.h index cacf59f..0171a22 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -162,11 +162,6 @@ typedef enum { OP_SQLITE_EXEC, // pops sql, handle; pushes sqlite rc (0=OK) OP_SQLITE_QUERY, // pops sql, handle; pushes array - // libsql (optional, independent) - OP_LIBSQL_OPEN, // pops url/path; pushes handle (>0) or 0 - OP_LIBSQL_CLOSE, // pops handle; pushes Nil - OP_LIBSQL_EXEC, // pops sql, handle; pushes rc (0=OK) - OP_LIBSQL_QUERY, // pops sql, handle; pushes array // PCSC (smart card) opcodes OP_PCSC_ESTABLISH, // returns context id (>0) or 0 @@ -187,11 +182,6 @@ typedef enum { OP_OPENSSL_SHA512, // pops data string; pushes sha512 hex string OP_OPENSSL_RIPEMD160, // pops data string; pushes ripemd160 hex string - // LibreSSL (optional; same API as OpenSSL but different toggle) - OP_LIBRESSL_MD5, // pops data string; pushes md5 hex string - OP_LIBRESSL_SHA256, // pops data string; pushes sha256 hex string - OP_LIBRESSL_SHA512, // pops data string; pushes sha512 hex string - OP_LIBRESSL_RIPEMD160, // pops data string; pushes ripemd160 hex string // INI (iniparser 4.2.6) optional OP_INI_LOAD, // pops path; pushes handle (>0) or 0 diff --git a/src/parser.c b/src/parser.c index 3b1cae2..af40c75 100644 --- a/src/parser.c +++ b/src/parser.c @@ -54,6 +54,11 @@ /* from parser_utils.c */ extern char *preprocess_includes_with_path(const char *src, const char *current_path); +/* Forward declarations for helpers used before their definitions */ +static void skip_to_eol(const char *src, size_t len, size_t *pos); +static int read_line_start(const char *src, size_t len, size_t *pos, int *out_indent); +static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, int current_indent); + /* ---- parser error state ---- */ static const char *g_current_source_path = NULL; /* for propagating filename into nested bytecodes */ static int g_has_error = 0; @@ -219,6 +224,22 @@ typedef struct { static LocalEnv *g_locals = NULL; +/* --- nested function environment tracking (for no-capture enforcement) --- */ +static LocalEnv *g_func_env_stack[64]; +static int g_func_env_depth = 0; /* number of valid outer env entries */ + +static int name_in_outer_envs(const char *name) { + if (g_func_env_depth <= 0) return 0; + for (int d = g_func_env_depth - 1; d >= 0; --d) { + LocalEnv *e = g_func_env_stack[d]; + if (!e) continue; + for (int i = 0; i < e->count; ++i) { + if (e->names[i] && strcmp(e->names[i], name) == 0) return 1; + } + } + return 0; +} + /* loop context for break/continue patching */ typedef struct LoopCtx { int break_jumps[64]; @@ -256,6 +277,94 @@ static int emit_expression(Bytecode *bc, const char *src, size_t len, size_t *po static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) { skip_spaces(src, len, pos); + /* anonymous function literal: fn(arg, ...) indented-body end */ + if (*pos + 2 < len && starts_with(src, len, *pos, "fn") && (src[*pos + 2] == '(' || src[*pos + 2] == ' ' || src[*pos + 2] == '\t')) { + *pos += 2; + skip_spaces(src, len, pos); + if (!consume_char(src, len, pos, '(')) { + parser_fail(*pos, "Expected '(' after 'fn'"); + return 0; + } + + /* build locals from parameters */ + LocalEnv env = {{0}, {0}, 0}; + LocalEnv *prev = g_locals; + int saved_depth = g_func_env_depth; + /* push outer env for no-capture checks */ + if (prev != NULL) { + if (g_func_env_depth >= (int)(sizeof(g_func_env_stack) / sizeof(g_func_env_stack[0]))) { + parser_fail(*pos, "Too many nested functions (env stack overflow)"); + return 0; + } + g_func_env_stack[g_func_env_depth++] = prev; + } + g_locals = &env; + + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] != ')') { + for (;;) { + char *pname = NULL; + if (!read_identifier_into(src, len, pos, &pname)) { + parser_fail(*pos, "Expected parameter name"); + g_locals = prev; + g_func_env_depth = saved_depth; + return 0; + } + if (local_find(pname) >= 0) { + parser_fail(*pos, "Duplicate parameter name '%s'", pname); + free(pname); + g_locals = prev; + g_func_env_depth = saved_depth; + return 0; + } + local_add(pname); + free(pname); + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] == ',') { + (*pos)++; + skip_spaces(src, len, pos); + continue; + } + break; + } + } + if (!consume_char(src, len, pos, ')')) { + parser_fail(*pos, "Expected ')' after parameter list"); + g_locals = prev; + g_func_env_depth = saved_depth; + return 0; + } + /* end header line */ + skip_to_eol(src, len, pos); + + /* compile body into separate Bytecode */ + Bytecode *fn_bc = bytecode_new(); + if (fn_bc) { + if (fn_bc->name) free((void *)fn_bc->name); + fn_bc->name = strdup(""); + if (fn_bc->source_file) free((void *)fn_bc->source_file); + if (g_current_source_path) fn_bc->source_file = strdup(g_current_source_path); + } + + /* parse body at increased indent if present */ + int body_indent = 0; + size_t look_body = *pos; + if (read_line_start(src, len, &look_body, &body_indent) && body_indent > 0) { + parse_block(fn_bc, src, len, pos, body_indent); + } else { + /* empty body ok */ + } + bytecode_add_instruction(fn_bc, OP_RETURN, 0); + + /* restore env stack */ + g_locals = prev; + g_func_env_depth = saved_depth; + + int fci = bytecode_add_constant(bc, make_function(fn_bc)); + bytecode_add_instruction(bc, OP_LOAD_CONST, fci); + return 1; + } + /* parenthesized */ if (*pos < len && src[*pos] == '(') { (*pos)++; /* '(' */ @@ -1984,91 +2093,6 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) free(name); return 1; } - /* libsql builtins (independent extension) */ - if (strcmp(name, "libsql_open") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { - parser_fail(*pos, "libsql_open expects (url_or_path)"); - free(name); - return 0; - } - if (!consume_char(src, len, pos, ')')) { - parser_fail(*pos, "Expected ')' after libsql_open arg"); - free(name); - return 0; - } - bytecode_add_instruction(bc, OP_LIBSQL_OPEN, 0); - free(name); - return 1; - } - if (strcmp(name, "libsql_close") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { - parser_fail(*pos, "libsql_close expects (handle)"); - free(name); - return 0; - } - if (!consume_char(src, len, pos, ')')) { - parser_fail(*pos, "Expected ')' after libsql_close arg"); - free(name); - return 0; - } - bytecode_add_instruction(bc, OP_LIBSQL_CLOSE, 0); - free(name); - return 1; - } - if (strcmp(name, "libsql_exec") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { - parser_fail(*pos, "libsql_exec expects (handle, sql)"); - free(name); - return 0; - } - if (!consume_char(src, len, pos, ',')) { - parser_fail(*pos, "libsql_exec expects (handle, sql)"); - free(name); - return 0; - } - if (!emit_expression(bc, src, len, pos)) { - parser_fail(*pos, "libsql_exec expects (handle, sql)"); - free(name); - return 0; - } - if (!consume_char(src, len, pos, ')')) { - parser_fail(*pos, "Expected ')' after libsql_exec args"); - free(name); - return 0; - } - bytecode_add_instruction(bc, OP_LIBSQL_EXEC, 0); - free(name); - return 1; - } - if (strcmp(name, "libsql_query") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { - parser_fail(*pos, "libsql_query expects (handle, sql)"); - free(name); - return 0; - } - if (!consume_char(src, len, pos, ',')) { - parser_fail(*pos, "libsql_query expects (handle, sql)"); - free(name); - return 0; - } - if (!emit_expression(bc, src, len, pos)) { - parser_fail(*pos, "libsql_query expects (handle, sql)"); - free(name); - return 0; - } - if (!consume_char(src, len, pos, ')')) { - parser_fail(*pos, "Expected ')' after libsql_query args"); - free(name); - return 0; - } - bytecode_add_instruction(bc, OP_LIBSQL_QUERY, 0); - free(name); - return 1; - } if (strcmp(name, "curl_post") == 0) { (*pos)++; /* '(' */ if (!emit_expression(bc, src, len, pos)) { @@ -2186,71 +2210,7 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) free(name); return 1; } - /* LibreSSL (md5/sha256/sha512/ripemd160) */ - if (strcmp(name, "libressl_md5") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { - parser_fail(*pos, "libressl_md5 expects (data)"); - free(name); - return 0; - } - if (!consume_char(src, len, pos, ')')) { - parser_fail(*pos, "Expected ')' after libressl_md5 arg"); - free(name); - return 0; - } - bytecode_add_instruction(bc, OP_LIBRESSL_MD5, 0); - free(name); - return 1; - } - if (strcmp(name, "libressl_sha256") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { - parser_fail(*pos, "libressl_sha256 expects (data)"); - free(name); - return 0; - } - if (!consume_char(src, len, pos, ')')) { - parser_fail(*pos, "Expected ')' after libressl_sha256 arg"); - free(name); - return 0; - } - bytecode_add_instruction(bc, OP_LIBRESSL_SHA256, 0); - free(name); - return 1; - } - if (strcmp(name, "libressl_sha512") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { - parser_fail(*pos, "libressl_sha512 expects (data)"); - free(name); - return 0; - } - if (!consume_char(src, len, pos, ')')) { - parser_fail(*pos, "Expected ')' after libressl_sha512 arg"); - free(name); - return 0; - } - bytecode_add_instruction(bc, OP_LIBRESSL_SHA512, 0); - free(name); - return 1; - } - if (strcmp(name, "libressl_ripemd160") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { - parser_fail(*pos, "libressl_ripemd160 expects (data)"); - free(name); - return 0; - } - if (!consume_char(src, len, pos, ')')) { - parser_fail(*pos, "Expected ')' after libressl_ripemd160 arg"); - free(name); - return 0; - } - bytecode_add_instruction(bc, OP_LIBRESSL_RIPEMD160, 0); - free(name); - return 1; - } + /* LibreSSL builtins removed */ /* PCSC builtins */ if (strcmp(name, "pcsc_establish") == 0) { (*pos)++; /* '(' */ @@ -4386,6 +4346,12 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) } /* push function value first */ + if (local_idx < 0 && name_in_outer_envs(name)) { + const char *fname = (bc && bc->name) ? bc->name : ""; + parser_fail(*pos, "Nested function '%s' cannot access outer local '%s'. Pass it as a parameter instead.", fname, name); + free(name); + return 0; + } if (local_idx >= 0) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, local_idx); } else { @@ -4541,6 +4507,12 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) free(name); return 1; } else { + if (local_idx < 0 && name_in_outer_envs(name)) { + const char *fname = (bc && bc->name) ? bc->name : ""; + parser_fail(*pos, "Nested function '%s' cannot access outer local '%s'. Pass it as a parameter instead.", fname, name); + free(name); + return 0; + } if (local_idx >= 0) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, local_idx); } else { @@ -6621,8 +6593,17 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, } /* build locals from parameters */ - LocalEnv env = {{0}, 0}; + LocalEnv env = {{0}, {0}, 0}; LocalEnv *prev = g_locals; + int saved_depth = g_func_env_depth; + if (prev != NULL) { + if (g_func_env_depth >= (int)(sizeof(g_func_env_stack) / sizeof(g_func_env_stack[0]))) { + parser_fail(*pos, "Too many nested functions (env stack overflow)"); + free(fname); + return; + } + g_func_env_stack[g_func_env_depth++] = prev; + } g_locals = &env; skip_spaces(src, len, pos); @@ -6692,12 +6673,46 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, } #endif - /* bind function to global: LOAD_CONST ; STORE_GLOBAL fgi */ + /* If we are inside another function (prev != NULL), bind this function as a local + * so that it is only visible within the current function scope. Otherwise, bind + * it as a global (top-level behavior unchanged). */ int fci = bytecode_add_constant(bc, make_function(fn_bc)); bytecode_add_instruction(bc, OP_LOAD_CONST, fci); - bytecode_add_instruction(bc, OP_STORE_GLOBAL, fgi); + if (prev != NULL) { + /* Bind into the OUTER function's local env (prev), not the inner temp env. */ + LocalEnv *save_env = g_locals; + g_locals = prev; + /* declare a local with the function name if not present */ + int lidx = local_find(fname); + if (lidx < 0) { + lidx = local_add(fname); + } + if (lidx < 0) { + /* failed to allocate local slot */ + g_locals = save_env; /* restore before returning */ + g_locals = prev; + free(fname); + return; + } + /* We want sibling inner functions to be able to call this nested function + * without closures. Strategy: also expose it as a global symbol so that + * inner functions (compiled into separate frames) can resolve the name + * via global lookup. Keep the local binding for the outer function body. */ + /* Duplicate the function value so we can store to both local and global. */ + bytecode_add_instruction(bc, OP_DUP, 0); + bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); + /* Bind a global under the same name for call resolution inside deeper nested functions. */ + int gsym = sym_index(fname); + bytecode_add_instruction(bc, OP_STORE_GLOBAL, gsym); + /* restore current env (will be reset to prev below) */ + g_locals = save_env; + } else { + /* top-level: global binding (backward compatible) */ + bytecode_add_instruction(bc, OP_STORE_GLOBAL, fgi); + } g_locals = prev; + g_func_env_depth = saved_depth; free(fname); continue; } diff --git a/src/vm.c b/src/vm.c index 51468ce..de841e0 100644 --- a/src/vm.c +++ b/src/vm.c @@ -53,12 +53,7 @@ #include "value.h" #include "vm.h" -/* Shared Notcurses state (optional) */ -#ifdef FUN_WITH_NOTCURSES -#include -#include /* ensure wcwidth/wcswidth prototypes present before notcurses.h on some systems */ -#endif -#include "vm/notcurses/common.h" +/* Notcurses support removed */ // Optional by extensions commonly used code. #ifdef's are in each single file. #include "external/curl.c" @@ -70,13 +65,10 @@ /* Note: INI opcode handlers are included below; changes in vm/ini/ .c files * require vm.c to rebuild. */ #include "external/json.c" -#include "external/libressl.c" -#include "external/libsql.c" #include "external/openssl.c" #include "external/pcre2.c" #include "external/pcsc.c" #include "external/sqlite.c" -#include "external/tcltk.c" #include "external/xml2.c" /* forward declarations for include mapping used in error reporting */ @@ -851,42 +843,10 @@ void vm_run(VM *vm, Bytecode *entry) { #include "vm/openssl/sha512.c" #endif -/* LibreSSL ops (md5/sha256/sha512/ripemd160) */ -#ifdef FUN_WITH_LIBRESSL -#include "vm/libressl/md5.c" -#include "vm/libressl/ripemd160.c" -#include "vm/libressl/sha256.c" -#include "vm/libressl/sha512.c" -#endif -/* Tk (Tcl/Tk) ops */ -#ifdef FUN_WITH_TCLTK -#include "vm/tk/bind.c" -#include "vm/tk/button.c" -#include "vm/tk/eval.c" -#include "vm/tk/label.c" -#include "vm/tk/loop.c" -#include "vm/tk/pack.c" -#include "vm/tk/result.c" -#include "vm/tk/wm_title.c" -#endif +/* Tcl/Tk support removed */ -/* Notcurses TUI ops (optional) */ -#ifdef FUN_WITH_NOTCURSES -#include "vm/notcurses/clear.c" -#include "vm/notcurses/draw_text.c" -#include "vm/notcurses/getch.c" -#include "vm/notcurses/init.c" -#include "vm/notcurses/shutdown.c" -#include "vm/notcurses/get_size.c" -#include "vm/notcurses/set_style.c" -#include "vm/notcurses/draw_char.c" -#include "vm/notcurses/hline.c" -#include "vm/notcurses/vline.c" -#include "vm/notcurses/box.c" -#include "vm/notcurses/fill.c" -#include "vm/notcurses/render.c" -#endif +/* Notcurses TUI ops removed */ /* SQLite ops */ #ifdef FUN_WITH_SQLITE @@ -912,13 +872,7 @@ void vm_run(VM *vm, Bytecode *entry) { } #endif -/* libsql ops (independent) */ -#ifdef FUN_WITH_LIBSQL -#include "vm/libsql/close.c" -#include "vm/libsql/exec.c" -#include "vm/libsql/open.c" -#include "vm/libsql/query.c" -#endif +/* libsql support removed */ /* PCRE2 ops */ #ifdef FUN_WITH_PCRE2