Compare commits
37 commits
10144d2737
...
4365546327
| Author | SHA1 | Date | |
|---|---|---|---|
| 4365546327 | |||
| 74a003236d | |||
| 45dbcde527 | |||
| 502c648fc9 | |||
| 021860b55d | |||
| cf320199ef | |||
| be75ee47f8 | |||
| 59484f5a85 | |||
| 5d5b102252 | |||
| 94bb941297 | |||
| 1114c08036 | |||
| c64fab3ab1 | |||
| 22c785b333 | |||
| d93d3c7409 | |||
| 1a7d54893f | |||
| 42234fe0f4 | |||
| 3d708163ab | |||
| 12e64125b2 | |||
| bc75e7b681 | |||
| 463e6b691e | |||
| 9e64f42edc | |||
| 9e398f4ab4 | |||
| 5de0868140 | |||
| 624d4ff1e7 | |||
| b5ff0d5f5c | |||
| eb9055f385 | |||
| c0d8f423ea | |||
| e0e381406a | |||
| 4f1711e8cf | |||
| 0201a7f9fc | |||
| 9e6cb9fea4 | |||
| 86a9e74617 | |||
| 0d2264894a | |||
| b2b2fb2f2a | |||
| 47cf47a780 | |||
| a8968c1c14 | |||
| 84fa28f3cc |
73 changed files with 609 additions and 1142 deletions
|
|
@ -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]
|
||||
|
|
|
|||
106
.github/workflows/cmake-ubuntu-latest.yml
vendored
Normal file
106
.github/workflows/cmake-ubuntu-latest.yml
vendored
Normal 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
1
.gitignore
vendored
|
|
@ -2,6 +2,7 @@ CMakeCache.txt
|
|||
.*
|
||||
!.editorconfig
|
||||
!.gitignore
|
||||
!.github
|
||||
.venv
|
||||
build*
|
||||
cmake_install.cmake
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 "--------------------------------")
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
4
docs/external/README.md
vendored
4
docs/external/README.md
vendored
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
45
docs/external/libressl.md
vendored
45
docs/external/libressl.md
vendored
|
|
@ -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.
|
||||
17
docs/external/libsql.md
vendored
17
docs/external/libsql.md
vendored
|
|
@ -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.
|
||||
27
docs/external/notcurses.md
vendored
27
docs/external/notcurses.md
vendored
|
|
@ -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.
|
||||
21
docs/external/tcltk.md
vendored
21
docs/external/tcltk.md
vendored
|
|
@ -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.
|
||||
139
docs/handbook.md
139
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 <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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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... ;)
|
||||
*/
|
||||
|
|
@ -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... ;)
|
||||
*/
|
||||
|
|
@ -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
|
||||
*/
|
||||
177
examples/broken/complex_nested_no_capture.fun
Normal file
177
examples/broken/complex_nested_no_capture.fun
Normal 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
|
||||
*/
|
||||
|
|
@ -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
|
||||
*/
|
||||
|
|
@ -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
|
||||
*/
|
||||
|
|
@ -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
|
||||
*/
|
||||
|
|
@ -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
|
||||
*/
|
||||
|
|
@ -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
|
||||
*/
|
||||
|
|
@ -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%)
|
||||
*/
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
67
examples/functions/nested_functions.fun
Executable file
67
examples/functions/nested_functions.fun
Executable 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
|
||||
*/
|
||||
41
examples/functions/nested_no_capture.fun
Executable file
41
examples/functions/nested_no_capture.fun
Executable file
|
|
@ -0,0 +1,41 @@
|
|||
#!/usr/bin/env fun
|
||||
|
||||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 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
|
||||
*/
|
||||
|
|
@ -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
|
||||
|
|
|
|||
20
examples/snippets/nested.fun
Normal file
20
examples/snippets/nested.fun
Normal 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))
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
13
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 \
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
321
src/parser.c
321
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, ...) <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;
|
||||
}
|
||||
|
|
|
|||
54
src/vm.c
54
src/vm.c
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue