1
0
Fork 0
forked from fun/fun

Compare commits

..

37 commits

Author SHA1 Message Date
4365546327 Removed all tcltk support. (0.40.5) 2026-04-03 19:51:08 +02:00
74a003236d Removed all libsql support. (0.40.4) 2026-04-03 19:10:15 +02:00
45dbcde527 Removed all notcurses support. (0.40.3) 2026-04-03 19:00:53 +02:00
502c648fc9 Removed all libressl support. (0.40.2) 2026-04-03 18:55:52 +02:00
021860b55d Added functions execution to CI workflow/action. No code changes. (0.40.1) 2026-04-02 03:27:04 +02:00
cf320199ef More nested function fun! (0.40.1) 2026-04-02 03:22:35 +02:00
be75ee47f8 Added nested functions to the Fun parser. (0.40.0) 2026-04-02 02:23:42 +02:00
59484f5a85 Refactored the directory structure in ./examples for Rust. No code changes. (0.39.16) 2026-04-01 23:11:37 +02:00
5d5b102252 Some fixes in scripts/run_examples.sh. (0.39.16) 2026-04-01 22:45:02 +02:00
94bb941297 Disabled apt update and upgrade in the CI workflow/action because it consumes to much CPU time. No code changes. (0.39.15) 2026-04-01 22:25:45 +02:00
1114c08036 Disabled apt update and upgrade in the CI workflow/action because it consumes to much CPU time. No code changes. (0.39.15) 2026-04-01 22:24:56 +02:00
c64fab3ab1 Housekeeping in the CI workflow/action. No code changes. (0.39.15) 2026-04-01 22:21:49 +02:00
22c785b333 Removed libxml2 in the CI workflow/action because it does not compile on Ubuntu. No code changes. (0.39.15) 2026-04-01 22:15:26 +02:00
d93d3c7409 Removed iniparser in the CI workflow/action because it does not compile on Ubuntu. No code changes. (0.39.15) 2026-04-01 22:12:27 +02:00
1a7d54893f Fixes in the CI workflow/action. No code changes. (0.39.15) 2026-04-01 22:06:49 +02:00
42234fe0f4 Enabled iniparser in the CI workflow/action. No code changes. (0.39.15) 2026-04-01 22:02:57 +02:00
3d708163ab CI workflow/action update. No code changes. (0.39.15) 2026-04-01 21:39:15 +02:00
12e64125b2 examples/extra to examples/external and adjusted the GitHub workflow file for actions. No code changes. (0.39.15) 2026-04-01 21:23:02 +02:00
bc75e7b681 Refactored examples directory structure. No code changes. (0.39.15) 2026-04-01 20:56:40 +02:00
463e6b691e CI action update. Removed the second workflow to test things. No code changes. (0.39.15) 2026-03-31 03:18:29 +02:00
9e64f42edc CI action update. Playing with a second workflow to test things. No code changes. (0.39.15) 2026-03-31 03:14:54 +02:00
9e398f4ab4 CI action update. Added a second workflow to test things. No code changes. (0.39.15) 2026-03-31 03:13:08 +02:00
5de0868140 CI action update. Added a second workflow to test things. No code changes. (0.39.15) 2026-03-31 03:06:06 +02:00
624d4ff1e7 CI action update. Back to ubuntu-latest again. No code changes. (0.39.15) 2026-03-31 03:00:34 +02:00
b5ff0d5f5c CI action update. I disabled all extra examples in the action. Seems not to work on Ubuntu in the current state. No code changes. (0.39.15) 2026-03-31 02:23:44 +02:00
eb9055f385 CI action update. Just trying things and I disabled the iniparser. No code changes. (0.39.15) 2026-03-31 02:18:02 +02:00
c0d8f423ea CI action update. Just trying things and INI still fails. No code changes. (0.39.15) 2026-03-31 02:14:45 +02:00
e0e381406a CI action update. I forgot to enable FUN_WITH_IN in the action before. No code changes. (0.39.15) 2026-03-31 02:11:28 +02:00
4f1711e8cf CI action update. I forgot libiniparser in the action before. No code changes. (0.39.15) 2026-03-31 02:08:13 +02:00
0201a7f9fc CI action update. I forgot libiniparser in the action before. No code changes. (0.39.15) 2026-03-31 02:01:57 +02:00
9e6cb9fea4 CI action update. Some example directory refactoring. No code changes. (0.39.15) 2026-03-31 01:57:16 +02:00
86a9e74617 CI action update. Switched to ubuntu-slim. No code changes. (0.39.15) 2026-03-31 01:49:58 +02:00
0d2264894a CI action update. No code changes. (0.39.15) 2026-03-31 01:39:39 +02:00
b2b2fb2f2a CI action update. No code changes. (0.39.15) 2026-03-31 01:32:58 +02:00
47cf47a780 CI action update. No code changes. (0.39.15) 2026-03-31 01:25:45 +02:00
a8968c1c14 CI action update. No code changes. (0.39.15) 2026-03-31 01:15:44 +02:00
84fa28f3cc Added first GitHub action for evaluation. No code changes. (0.39.15) 2026-03-31 00:58:11 +02:00
73 changed files with 609 additions and 1142 deletions

View file

@ -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]

View file

@ -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

1
.gitignore vendored
View file

@ -2,6 +2,7 @@ CMakeCache.txt
.*
!.editorconfig
!.gitignore
!.github
.venv
build*
cmake_install.cmake

View file

@ -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)

View file

@ -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.

View file

@ -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 "--------------------------------")

View file

@ -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).

View file

@ -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`.

View file

@ -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.

View file

@ -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:

View file

@ -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 <libressl/openssl/...>` resolves correctly.

View file

@ -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<map>
## 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.

View file

@ -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.

View file

@ -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.

View file

@ -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 <ui/tk.fun>
tk = TK()
tk.title("Fun + Tk GUI")
tk.label("hello", "Hello, world!")
tk.pack("hello")
tk.button("ok", "OK")
tk.pack("ok")
tk.loop()
```
Notes:
- Ensure Tcl/Tk is installed (8.6+). On Linux install tcl/tk packages; on macOS install Homebrew tcl-tk; on Windows ensure the DLLs are available.
- The Fun process terminates when the main window is closed or when the example's OK button is clicked.
### Install Fun to the OS (optional)
Not recommended during early development, but supported:
@ -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 <ui/tk.fun>
tk = TK()
tk.title("Fun + Tk GUI")
tk.label("hello", "Hello, world!")
tk.pack("hello")
tk.button("ok", "OK")
tk.pack("ok")
tk.loop()
```
Notes:
- Ensure Tcl/Tk runtime libraries are available on your system.
### REPL (optional)

View file

@ -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<map<string,any>>.
## 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<map<string,any>>.
<!-- libSQL opcodes removed -->
## 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.
<!-- libSQL backend removed -->
## TK (Tcl/Tk UI)

View file

@ -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 <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2025-12-23
*/
#include <ui/tk.fun>
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... ;)
*/

View file

@ -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 <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2025-12-09
*/
// Demonstrates the Tk stdlib wrapper class using the new Tk opcodes.
include <ui/tk.fun>
tk = TK()
tk.title("Fun + Tk GUI")
tk.label("hello", "Hello, world!")
tk.pack("hello")
tk.button("ok", "OK")
tk.pack("ok")
// Enter GUI loop (no-op if built without FUN_WITH_TCLTK)
tk.loop()
/* Expected output:
A GUI... ;)
*/

View file

@ -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 <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2025-12-23
*/
#include <ui/tk.fun>
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
*/

View file

@ -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 <you@hanez.org>
* 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
*/

View file

@ -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 <you@hanez.org>
* 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
*/

View file

@ -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 <you@hanez.org>
* 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
*/

View file

@ -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 <you@hanez.org>
* 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
*/

View file

@ -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 <you@hanez.org>
* 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
*/

View file

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

View file

@ -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 <you@hanez.org>
* 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 <ui/notcurses.fun>
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%)
*/

View file

@ -1,33 +0,0 @@
#!/usr/bin/env fun
include <ui/notcurses.fun>
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()

View file

@ -1,38 +0,0 @@
#!/usr/bin/env fun
include <ui/notcurses.fun>
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()

View file

@ -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 <you@hanez.org>
* 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
*/

View file

@ -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 <you@hanez.org>
* 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
*/

View file

@ -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

View file

@ -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))

View file

@ -1,12 +0,0 @@
#!/usr/bin/env fun
#include <net/cgi.fun>
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

View file

@ -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 <net/cgi.fun>
cgi = CGI()

View file

@ -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 <you@hanez.org>
* 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 ---

13
make
View file

@ -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 \

View file

@ -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; }

View file

@ -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:

View file

@ -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<map>
// libsql (optional, independent)
OP_LIBSQL_OPEN, // pops url/path; pushes handle (>0) or 0
OP_LIBSQL_CLOSE, // pops handle; pushes Nil
OP_LIBSQL_EXEC, // pops sql, handle; pushes rc (0=OK)
OP_LIBSQL_QUERY, // pops sql, handle; pushes array<map>
// PCSC (smart card) opcodes
OP_PCSC_ESTABLISH, // returns context id (>0) or 0
@ -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

View file

@ -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, ...) <newline> 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("<fn>");
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 : "<function>";
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 : "<function>";
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 <fn> ; 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;
}

View file

@ -53,12 +53,7 @@
#include "value.h"
#include "vm.h"
/* Shared Notcurses state (optional) */
#ifdef FUN_WITH_NOTCURSES
#include <notcurses/notcurses.h>
#include <wchar.h> /* 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