diff --git a/.github/workflows/cmake-ubuntu-latest.yml b/.github/workflows/cmake-ubuntu-latest.yml index 6f62ae4..fc55f0f 100644 --- a/.github/workflows/cmake-ubuntu-latest.yml +++ b/.github/workflows/cmake-ubuntu-latest.yml @@ -6,7 +6,6 @@ on: env: BUILD_TYPE: Release - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: build: @@ -14,6 +13,7 @@ jobs: defaults: run: working-directory: ${{github.workspace}} + steps: - uses: actions/checkout@v4 diff --git a/.gitignore b/.gitignore index 73e7471..cf923d8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,9 +7,9 @@ Doxygen.in !.gitignore !.github !.gitkeep +!api/.gitkeep .venv api/ -!api/.gitkeep build/* build_debug/* build_release/* @@ -23,8 +23,6 @@ downloaded.png json.xml lib/*.so out/ -playground/* -!playground/.gitkeep src/*.o src/rust/Cargo.lock src/rust/target diff --git a/CHANGELOG.md b/CHANGELOG.md index e8def14..d4b2424 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,30 +5,6 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) (during 0.x.y development phase). -## [0.42.1] - 2026-06-08 -### Fixed -- Small test script issue. -### Changed -- Documentation and website updates. No runtime code changes. -- README, `make`, and `Doxyfile` updates. No runtime code changes. - -## [0.42.0] - 2026-06-03 -### Added -- Redis/Valkey extension named `redis` (see `src/extensions/redis.c` and `src/vm/redis/*`). -- Redis example programs: `basic_ping.fun`, `hash_ops.fun`, `kv_set_get.fun`, `list_ops.fun`, and `redis_test.fun`. -- Initial Redis documentation page under `web/documentation/extensions/redis/`. -### Changed -- Build system: Redis extension is not compiled by default; enable it explicitly if needed. No other code changes. -- GitHub workflow tweaks. No code changes. - -## [0.41.16] - 2026-05-29 -### Added -- `playground/` directory for local-only experiments. -### Changed -- `examples/features.fun` and website features page updates. -- README and API documentation updates. No code changes. -- Documentation and web fixes. No code changes. - ## [0.41.15] - 2026-05-26 ### Changed - More internal optimizations. diff --git a/CMakeLists.txt b/CMakeLists.txt index dc0523d..1c754fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.42.2 LANGUAGES C) +project(fun VERSION 0.41.15 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) @@ -521,7 +521,7 @@ install(DIRECTORY lib/ # Optionally install example scripts option(FUN_INSTALL_EXAMPLES "Install example .fun scripts" ON) if(FUN_INSTALL_EXAMPLES) - install(DIRECTORY examples/ + install(DIRECTORY examples/ DESTINATION /usr/share/fun/examples FILES_MATCHING PATTERN "*.fun" ) diff --git a/Doxyfile b/Doxyfile index 95ef2de..39f0078 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = "Fun API Documentation" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 0.42.1 +PROJECT_NUMBER = 0.41.15 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewers a diff --git a/README.md b/README.md index 1511d85..3690522 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ ## What is Fun? -Fun is a small, strict, and simple programming language that runs on a compact stack-based virtual machine. The C core is intentionally minimal; most functionality and standard libraries are implemented in Fun itself. +Fun is a small, strict, and simple programming language that runs on a compact stack-based virtual machine. The C core is intentionally minimal; most functionality and standard libraries are implemented in Fun itself. The language emphasizes simplicity, consistency, and joy in coding. -Fun is dynamically typed with optional static type annotations, featuring first-class functions, classes with inheritance, pattern matching, and a rich standard library. It supports everything from basic scripting to TCP sockets, serial communication, threading, cryptography (MD5, SHA-1/256/384/512, CRC-32, AES-256), and a built-in debugger. +Fun is an experiment, just for fun, but Fun works! -Fun is an experiment — just for fun — but Fun works! +Fun is a highly strict programming language, but also highly simple. It looks like Python (My favorite language), but there are differences. -Influenced by **[Bash](https://www.gnu.org/software/bash/)**, **[C](https://en.wikipedia.org/wiki/The_C_Programming_Language)**, **[Lua](https://www.lua.org/)**, PHP, **[Python](https://www.python.org/)**, and **[Rust](https://www.rust-lang.org/)**. +Influenced by **[Bash](https://www.gnu.org/software/bash/)**, **[C](https://en.wikipedia.org/wiki/The_C_Programming_Language)**, **[Lua](https://www.lua.org/)**, PHP, **[Python](https://www.python.org/)**, and Rust (Most influences came from linked languages). Fun is and will ever be 100% free under the terms of the [Apache-2.0 License](https://opensource.org/license/apache-2-0). @@ -23,19 +23,11 @@ Fun is and will ever be 100% free under the terms of the [Apache-2.0 License](ht ## Characteristics -- **Dynamic typing** with optional **static type annotations** (`number`, `string`, `boolean`, `float`, `byte`, `uint8`–`uint64`, `int8`–`int64`) -- **Stack-based bytecode VM** written in C99 with ~220 opcodes -- **First-class functions**, anonymous functions (`fn`), and higher-order operations (`map`, `filter`, `reduce`) -- **Classes** with constructors, methods, and single inheritance -- **Exception handling** with `try`/`catch`/`finally` -- **Built-in data structures**: arrays (with slicing), maps/objects, strings -- **Concurrency**: threads (`thread_spawn`, `thread_join`) and cooperative async scheduler -- **Networking**: TCP and Unix domain sockets with non-blocking I/O polling -- **Serial communication**: full termios-based serial port control -- **Cryptography**: pure-Fun implementations of MD5, SHA-1/256/384/512, CRC-32/CRC-32C, AES-256 ECB -- **Built-in debugger** with breakpoints, step/next/finish, and stack inspection -- **Minimal C core** — most standard libraries are implemented in Fun itself -- **Internal style**: `snake_case` for functions and variables, `CamelCase` for class names +- Dynamic and optionally statically typed +- Type safety +- Written in C (C99) and Fun +- Internal libs are written with no_camel_case even when written in Fun, except class names +- Only a minimal function set is written in C, and most other core functions and libraries are implemented in Fun ## The Fun Manifesto @@ -53,13 +45,13 @@ Coding should be enjoyable, elegant, and consistent. - **One Way to Do It**
No clutter, no 15 ways of writing the same thing. Simplicity means clarity. - **Hackable by Nature**
- Fun should be small and embeddable, like Lua. Easy to understand, extend, and tinker with — true to the hacker spirit. + Fun should be small and embeddable, like Lua. Easy to understand, extend, and tinker with — true to the hacker spirit. - **Beautiful Defaults**
- A language that doesn't need linters, formatters, or style guides. Beauty is built in. + A language that doesn’t need linters, formatters, or style guides. Beauty is built in. ## The Community -Fun is not about being the fastest or the most feature-rich. It's about sharing joy in coding. The community should be: +Fun is not about being the fastest or the most feature-rich. It’s about sharing joy in coding. The community should be: - Respectful - Curious @@ -78,162 +70,63 @@ A language that feels like home for developers who: - Believe consistency is freedom - Want to write code that looks good and feels good -Fun may not change the world — but it will make programming a little more fun. +Fun may not change the world — but it will make programming a little more fun. -## Language Features +## Features -### Core Syntax & Types +### Core -- Indentation-based blocks (2-space), line and block comments -- Static type annotations with automatic runtime range clamping for fixed-width integers -- `typeof()`, `to_string()`, `to_number()`, `cast()` — type introspection and conversion -- `exit` statement with optional exit code, `#include` for source includes +- functions/classes/objects +- if/else if/else +- try/catch/finally -### Operators +And much more...! Look at the specs in [Spec](./spec/) for more detailed information. -- Arithmetic: `+`, `-`, `*`, `/`, `%` -- Comparison: `<`, `<=`, `>`, `>=`, `==`, `!=` -- Logical: `&&`, `||`, `!` (short-circuit) -- Bitwise: `band()`, `bor()`, `bxor()`, `bnot()`, `shl()`, `shr()`, `rol()`, `ror()` -- Ternary: `condition ? true_expr : false_expr` +### Lib (./lib/) -### Control Flow +See [./lib/](https://git.xw3.org/fun/fun/src/branch/main/lib) for what the standard library provides. -- `if` / `else if` / `else` -- `while` with `break` and `continue` -- `for var in array` — iteration -- `for var in range(start, end)` — numeric range -- `for (key, value) in map` — map destructuring -- `match` expression (stdlib) -- `try` / `catch` / `finally` +### Optional extensions (build-time selectable / only testing this on Linux actually): -### Functions +- [CGI](https://en.wikipedia.org/wiki/Common_Gateway_Interface) support builtin using [kcgi](https://kristaps.bsd.lv/kcgi/) (optional) — see [docs](./web/documentation/extensions/kcgi/kcgi.md) ☑ +- [cURL (libcurl)](./web/documentation/extensions/curl/curl.md) (optional) ☑ +- [INI (iniparser)](./web/documentation/extensions/ini/ini.md) (optional) ☑ +- [JSON (json-c)](./web/documentation/extensions/json/json.md) (optional) ☑ +- [PCRE2](./web/documentation/extensions/pcre2/pcre2.md) (optional) ☑ +- [PCSC (smart cards)](./web/documentation/extensions/pcsc/pcsc.md) (optional) ☑ +- [OpenSSL](./web/documentation/extensions/openssl/openssl.md) (optional) ☑ +- [SQLite](./web/documentation/extensions/sqlite/sqlite.md) (optional) ☑ +- [XML (libxml2)](./web/documentation/extensions/xml2/xml2.md) (optional) ☑ -- `fun name(params) body` — named functions -- `fn(params) body` — anonymous function literals -- First-class: pass as arguments, store in variables, recursion -- `return` with optional value (implicit nil) +☑ = Done (or basics implemented) / ☐ = Planned or in progress. -### Object-Oriented Programming +Note: Not all of the above features will be implemented. Those who are marked "Done" will probaly remain in Fun, but I don't know actually... ;) -- `class Name(typed params) body` with `_construct(this, ...)` auto-invoked constructor -- `this` keyword, `obj.method(args)` method call sugar, `obj.field` dot property access -- `class Child(...) extends Parent` with method overriding +There are some libs written in Fun available in the [./lib/](https://git.xw3.org/fun/fun/src/branch/main/lib) diretory. In the future most Fun enhancements should be written in Fun itself. -### Data Structures +### OpenSSL quickstart (MD5) -- **Arrays**: literal `[1, 2, 3]`, index `arr[0]`, slice `arr[1:3]`, negative indices; `len()`, `push()`, `pop()`, `insert()`, `remove()`, `contains()`, `indexOf()`, `clear()`, `enumerate()`, `zip()`, `join()`, `map()`, `filter()`, `reduce()` -- **Maps**: literal `{key: value}`, bracket `map["key"]`, dot `map.key` access; `has()`, `keys()`, `values()` -- **Strings**: concatenation with `+`, `len()`, `substr()`, `find()`, `split()`, `join()` - -### Mathematics - -- Built-in: `abs`, `min`, `max`, `fmin`, `fmax`, `clamp`, `pow`, `sqrt`, `floor`, `ceil`, `trunc`, `round`, `sin`, `cos`, `tan`, `exp`, `log`, `log10`, `gcd`, `lcm`, `isqrt`, `sign` -- Random: `random_seed()`, `random_int()`, `random_number()` (cryptographic) -- Integer clamping: `sclamp()`, `uclamp()` - -### I/O & Platform - -- `print()`, `echo()` — output -- `read_file()`, `write_file()` — file I/O -- `input_line()` — stdin with optional prompt -- `env()`, `env_all()` — environment variables -- `proc_run()`, `system()` — process execution -- `os_list_dir()` — directory listing -- `time_now_ms()`, `clock_mono_ms()`, `date_format()`, `sleep()` - -### Networking (Built-in, Unix) - -- TCP: listen, accept, connect, send, recv, close -- Unix domain sockets: listen, connect -- Non-blocking I/O: `fd_set_nonblock()`, `fd_poll_read()`, `fd_poll_write()` - -### Serial Communication (Unix) - -- `serial_open()`, `serial_config()`, `serial_send()`, `serial_recv()`, `serial_close()` - -### Concurrency - -- `thread_spawn(fn, args)` returns thread ID; `thread_join(id)` returns result -- Cooperative async scheduler in stdlib - -### Debugging & Tooling - -- Built-in debugger with 64 breakpoints, step/next/finish/continue -- `--trace` / `-t` for opcode-level execution tracing -- `--repl-on-error`: enter REPL on runtime error with stack preserved -- Full-featured REPL with history, tab completion, multi-line input, commands (`:help`, `:load`, `:edit`, `:save`, `:debug`, `:trace`, `:type`, and more) -- `funstx` — syntax checker with optional `--fix` mode - -## Standard Library (lib/) - -Written primarily in Fun itself: - -- **Strings**: trim, starts/ends-with, split, replace-all, case conversion, repeat -- **Arrays**: slice, reverse, concat, unique, flatten -- **Math**: `abs`, `clamp`, `gcd`, `lcm`, `powi`, min3, max3, array min/max -- **Encoding**: hex encode/decode, base64 encode/decode -- **Cryptography** (pure Fun): MD5, SHA-1, SHA-256, SHA-384, SHA-512, CRC-32, CRC-32C, AES-256 ECB -- **Functional**: Option (Some/None), Result (Ok/Err), pattern matching -- **Ranges**: `range(n)`, `range2(start, end)`, `range3(start, end, step)` -- **Date/Time**: `DateTime` class with formatting, timers, sleep -- **CLI**: `argv()`, `parse_args()` with flag support -- **Console**: `Console` class with prompt, ask, hidden input, progress bar -- **Thread**: `Thread` class, **Process**: `Process` class -- **Socket classes**: `TcpClient`, `TcpServer`, `UnixClient` -- **Serial**: `Serial` class -- **Async**: Cooperative scheduler with I/O polling -- **HTTP**: Static file server, CGI-capable server -- **IRC**: `IRCClient` with full protocol support -- **CGI**: `CGI` class for web applications - -## Optional Extensions (Build-time) - -Enabled via CMake flags, wrapping mature C libraries: - -| Extension | Backend | -|---------------------------------------------------------------|-------------| -| [JSON](./web/documentation/extensions/json/json.md) | json-c | -| [cURL](./web/documentation/extensions/curl/curl.md) | libcurl | -| [SQLite](./web/documentation/extensions/sqlite/sqlite.md) | libsqlite3 | -| [PCRE2](./web/documentation/extensions/pcre2/pcre2.md) | libpcre2 | -| [OpenSSL](./web/documentation/extensions/openssl/openssl.md) | libcrypto | -| [INI](./web/documentation/extensions/ini/ini.md) | iniparser | -| [XML](./web/documentation/extensions/xml2/xml2.md) | libxml2 | -| [PC/SC](./web/documentation/extensions/pcsc/pcsc.md) | libpcsclite | -| [KCGI](./web/documentation/extensions/kcgi/kcgi.md) | libkcgi | -| [Redis/Valkey](./web/documentation/extensions/redis/redis.md) | hiredis | - -Some extensions also have a corresponding stdlib wrapper class (e.g., `JSON`, `INI`, `XML`, `PCSC`, `PCRE2`, `KCGI`). - -## Quick start - -
$ git clone https://git.xw3.org/fun/fun.git
-$ cd fun
-$ cmake -B build -DCMAKE_BUILD_TYPE=Release
-$ cmake --build build --target fun
-$ ./build/fun
-fun> print("Hello, World!")
-Hello, World!
-fun> :quit
- -## Build Options - -- **`-DCMAKE_BUILD_TYPE=Debug`** — debug build with asserts -- **`-DCMAKE_BUILD_TYPE=Release`** — optimized build with LTO and stripping -- **`-DFUN_BUILD_MUSL=ON`** — static musl build -- Toggle each extension: `-DFUN_WITH_JSON=ON`, `-DFUN_WITH_CURL=ON`, etc. -- **`-DFUN_BUILD_DOXYGEN=ON`** — API reference +See the dedicated page: [./web/documentation/extensions/openssl/](./web/documentation/extensions/openssl/openssl.md) ## Documentation -- Handbook: [./web/documentation/handbook/handbook.md](./web/documentation/handbook/handbook.md) -- Types: [./web/documentation/types/types.md](./web/documentation/types/types.md) -- REPL: [./web/documentation/repl/repl.md](./web/documentation/repl/repl.md) -- Testing: [./web/documentation/testing/testing.md](./web/documentation/testing/testing.md) -- Spec: [./web/documentation/spec/v0.4.md](./web/documentation/spec/v0.4.md) +Looking for docs? Start here: + +- Local documentation index: [./web/documentation/documenatation.md](./web/documentation/documentation.md) + - Handbook: [./web/documentation/handbook/handbook.md](./web/documentation/handbook/handbook.md) + - Types overview: [./web/documentation/types/types.md](./web/documentation/types/types.md) + - REPL guide: [./web/documentation/repl/repl.md](./web/documentation/repl/repl.md) + - Testing: [./web/documentation/testing/testing.md](./web/documentation/testing/testing.md) + - Troubleshooting: [./web/documentation/troubleshooting/troubleshooting.md](./web/documentation/troubleshooting/troubleshooting.md) + +Additional references: + +- Specification: [./web/documentation/spec/v0.4.md](./web/documentation/spec/v0.4.md) (work in progress) - Changelog: [CHANGELOG.md](./CHANGELOG.md) -- Examples: [./examples/features.fun](./examples/features.fun) +- Examples demonstrating most features: [./examples/](./examples/) +- Internals and VM opcodes live in [./src/](./src/) (see [./src/vm/](./src/vm) for opcode implementations) + +Note: The project is evolving; some documents may lag behind. The docs index in [./web/documentation/documenatation.md](./web/documentation/documentation.md) is the most up‑to‑date entry point. ## Author diff --git a/cmake/Extensions/Extensions.cmake b/cmake/Extensions/Extensions.cmake index 5f79041..3e21523 100644 --- a/cmake/Extensions/Extensions.cmake +++ b/cmake/Extensions/Extensions.cmake @@ -21,7 +21,6 @@ include(${CMAKE_SOURCE_DIR}/cmake/Extensions/PCSC.cmake) include(${CMAKE_SOURCE_DIR}/cmake/Extensions/REPL.cmake) include(${CMAKE_SOURCE_DIR}/cmake/Extensions/SQLITE.cmake) include(${CMAKE_SOURCE_DIR}/cmake/Extensions/XML2.cmake) -include(${CMAKE_SOURCE_DIR}/cmake/Extensions/REDIS.cmake) # Summary of extension toggles message(STATUS "---- Fun extension summary ----") @@ -35,5 +34,4 @@ _fun_print_feature("PCRE2 (FUN_WITH_PCRE2)" FUN_WITH_PCRE2) _fun_print_feature("PCSC-Lite (FUN_WITH_PCSC)" FUN_WITH_PCSC) _fun_print_feature("REPL (FUN_WITH_REPL)" FUN_WITH_REPL) _fun_print_feature("SQLite (FUN_WITH_SQLITE)" FUN_WITH_SQLITE) -_fun_print_feature("Redis (FUN_WITH_REDIS)" FUN_WITH_REDIS) message(STATUS "--------------------------------") diff --git a/cmake/Extensions/REDIS.cmake b/cmake/Extensions/REDIS.cmake deleted file mode 100644 index eb48b9e..0000000 --- a/cmake/Extensions/REDIS.cmake +++ /dev/null @@ -1,22 +0,0 @@ -# Redis (hiredis) -option(FUN_WITH_REDIS "Enable Redis (hiredis) support" OFF) -set(HIREDIS_INCLUDE_DIRS "") -set(HIREDIS_LINK_LIBS "") -if(FUN_WITH_REDIS) - add_definitions(-DFUN_WITH_REDIS) - find_package(PkgConfig QUIET) - if(PKG_CONFIG_FOUND) - pkg_check_modules(HIREDIS QUIET hiredis) - endif() - if(HIREDIS_FOUND) - list(APPEND HIREDIS_INCLUDE_DIRS ${HIREDIS_INCLUDE_DIRS} ${HIREDIS_INCLUDE_DIRS}) - list(APPEND HIREDIS_LINK_LIBS ${HIREDIS_LINK_LIBS} ${HIREDIS_LIBRARIES}) - else() - find_library(HIREDIS_LIB hiredis) - if(HIREDIS_LIB) - list(APPEND HIREDIS_LINK_LIBS ${HIREDIS_LIB}) - else() - message(FATAL_ERROR "hiredis not found. Install hiredis (dev headers) or disable FUN_WITH_REDIS.") - endif() - endif() -endif() diff --git a/cmake/Targets.cmake b/cmake/Targets.cmake index b8939d8..534d279 100644 --- a/cmake/Targets.cmake +++ b/cmake/Targets.cmake @@ -48,8 +48,7 @@ foreach(var_pair LIBSQL LIBXML2 OPENSSL - KCGI - HIREDIS) + KCGI) if(${var_pair}_INCLUDE_DIRS) target_include_directories(fun_core PRIVATE ${${var_pair}_INCLUDE_DIRS}) endif() @@ -95,9 +94,6 @@ if(FUN_WITH_SQLITE) target_compile_definitions(fun_core PUBLIC FUN_WITH_SQLITE=1) endif() -if(FUN_WITH_REDIS) - target_compile_definitions(fun_core PUBLIC FUN_WITH_REDIS=1) -endif() if(FUN_WITH_OPENSSL) target_compile_definitions(fun_core PUBLIC FUN_WITH_OPENSSL=1) diff --git a/examples/extensions/redis/README.md b/examples/extensions/redis/README.md deleted file mode 100644 index 88bace7..0000000 --- a/examples/extensions/redis/README.md +++ /dev/null @@ -1,29 +0,0 @@ -Redis extension examples (hiredis) - -This folder contains small Fun scripts that demonstrate how to use the Redis extension. - -Prerequisites -- Build Fun with Redis support enabled (FUN_WITH_REDIS=ON). This is ON by default in cmake/Extensions/REDIS.cmake. -- A Redis-compatible server reachable at 127.0.0.1:6379. - -How to run -- Using the Fun CLI from the repository root: - - Debug profile path: build_debug/fun - - Release profile path: build_release/fun - -Examples -1. basic_ping.fun - - Connects, PINGs, then closes. - -2. kv_set_get.fun - - SET/GET, EXISTS and DEL for a demo key. - -3. list_ops.fun - - Demonstrates LPUSH and LRANGE on a list. - -4. hash_ops.fun - - Demonstrates HSET, HGET and HGETALL on a hash. - -Note -- All examples use direct inline command strings with redis_cmd(handle, "COMMAND args..."). -- Close the connection with redis_close(handle) when finished. diff --git a/examples/extensions/redis/basic_ping.fun b/examples/extensions/redis/basic_ping.fun deleted file mode 100755 index 773d8bb..0000000 --- a/examples/extensions/redis/basic_ping.fun +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env fun - -/* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - * - * Added: 2026-06-03 - */ - -/* - * Simple Redis test using Fun builtins backed by hiredis. - * - * Basic PING - */ - -h = redis_connect('127.0.0.1', 6379) - -print('handle type: ' + typeof(h)) - -print(redis_cmd(h, 'PING')) - -redis_close(h) diff --git a/examples/extensions/redis/hash_ops.fun b/examples/extensions/redis/hash_ops.fun deleted file mode 100755 index e5a8be9..0000000 --- a/examples/extensions/redis/hash_ops.fun +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env fun - -/* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - * - * Added: 2026-06-03 - */ - -/* - * Simple Redis test using Fun builtins backed by hiredis. - * - * Hash operations (HSET/HGET/HGETALL) - */ - -h = redis_connect('127.0.0.1', 6379) -key = 'fun:examples:redis:hash:user1' - -// Start fresh -_ = redis_cmd(h, 'DEL ' + key) - -// Set a couple of fields -print(redis_cmd(h, 'HSET ' + key + ' name Alice')) -print(redis_cmd(h, 'HSET ' + key + ' age 30')) - -// Fetch a single field -print('HGET name -> ' + redis_cmd(h, 'HGET ' + key + ' name')) - -// Fetch all fields (returns a flat array [field, value, field, value, ...]) -all = redis_cmd(h, 'HGETALL ' + key) -print('HGETALL ->') -print(all) - -redis_close(h) diff --git a/examples/extensions/redis/kv_set_get.fun b/examples/extensions/redis/kv_set_get.fun deleted file mode 100755 index e68b7a6..0000000 --- a/examples/extensions/redis/kv_set_get.fun +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env fun - -/* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - * - * Added: 2026-06-03 - */ - -/* - * Simple Redis test using Fun builtins backed by hiredis. - * - * Simple key/value set-get-delete - */ - -h = redis_connect('127.0.0.1', 6379) -key = 'fun:examples:redis:key' - -// Clean slate -_ = redis_cmd(h, 'DEL ' + key) - -// Set and get -print(redis_cmd(h, 'SET ' + key + ' 42')) -print('GET -> ' + redis_cmd(h, 'GET ' + key)) - -// Check existence -print('EXISTS -> ' + to_string(redis_cmd(h, 'EXISTS ' + key))) - -// Delete -print('DEL -> ' + to_string(redis_cmd(h, 'DEL ' + key))) -print('EXISTS(after DEL) -> ' + to_string(redis_cmd(h, 'EXISTS ' + key))) - -redis_close(h) diff --git a/examples/extensions/redis/list_ops.fun b/examples/extensions/redis/list_ops.fun deleted file mode 100755 index ba8d8ed..0000000 --- a/examples/extensions/redis/list_ops.fun +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env fun - -/* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - * - * Added: 2026-06-03 - */ - -/* - * Simple Redis test using Fun builtins backed by hiredis. - * - * List operations (LPUSH/LRANGE) - */ - -h = redis_connect('127.0.0.1', 6379) -key = 'fun:examples:redis:list' - -// Start fresh -_ = redis_cmd(h, 'DEL ' + key) - -// Push some values to the left -print(redis_cmd(h, 'LPUSH ' + key + ' a')) -print(redis_cmd(h, 'LPUSH ' + key + ' b')) -print(redis_cmd(h, 'LPUSH ' + key + ' c')) - -// Read entire list -vals = redis_cmd(h, 'LRANGE ' + key + ' 0 -1') -print('LRANGE 0 -1 -> ') -print(vals) - -redis_close(h) diff --git a/examples/extensions/redis/redis_test.fun b/examples/extensions/redis/redis_test.fun deleted file mode 100755 index b1b8989..0000000 --- a/examples/extensions/redis/redis_test.fun +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env fun - -/* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - * - * Added: 2026-06-03 - */ - -/* - * Simple Redis test using Fun builtins backed by hiredis. - */ - -h = redis_connect('127.0.0.1', 6379) - -print(typeof(h)) - -print(redis_cmd(h, 'PING')) - -_ = redis_cmd(h, 'SET fun_demo_key 42') - -print(redis_cmd(h, 'GET fun_demo_key')) - -redis_close(h) diff --git a/examples/features.fun b/examples/features.fun index 9ebce9d..483219e 100755 --- a/examples/features.fun +++ b/examples/features.fun @@ -10,120 +10,48 @@ print("") // ============================================ print("1. Strong Type System:") string name = "Fun Language" -float version = 0.41 +float version = 0.3 boolean is_awesome = true -number meaning = 42 items = [1, 2, 3, 4, 5] config = {"debug": true, "port": 8080} -nil_val = nil -print(" Language: " + name + " v" + fun_version()) +print(" Language: " + name + " v" + to_string(version)) print(" Awesome: " + to_string(is_awesome)) -print(" Nil: " + to_string(nil_val)) print("") // ============================================ -// 2. Type Introspection +// 2. Modern Array Operations // ============================================ -print("2. Type Introspection:") -number check_int = 42 -string check_str = "hello" -check_arr = [1, 2, 3] -check_map = {"key": "value"} -check_float = 3.14 - -print(" typeof(42) = " + typeof(check_int)) -print(" typeof(\"hello\") = " + typeof(check_str)) -print(" typeof([1,2,3]) = " + typeof(check_arr)) -print(" typeof(map) = " + typeof(check_map)) -print(" typeof(3.14) = " + typeof(check_float)) -print("") - -// ============================================ -// 3. Conversion & Casting -// ============================================ -print("3. Conversion & Casting:") -print(" to_string(42) = " + to_string(42)) -print(" to_number(\"99\") = " + to_string(to_number("99"))) -casted = cast(1, "boolean") -print(" cast(1, \"boolean\") = " + to_string(casted)) -casted2 = cast("42", "number") -print(" cast(\"42\", \"number\") = " + to_string(casted2)) -print("") - -// ============================================ -// 4. String Manipulation -// ============================================ -print("4. String Operations:") -string text = "Hello, Fun Language!" -print(" Original: " + text) -print(" Length: " + to_string(len(text))) -print(" Substr(0,5): " + substr(text, 0, 5)) -print(" Find(\"Fun\"): " + to_string(find(text, "Fun"))) -parts = split(text, " ") -print(" Split by space: " + to_string(parts)) -joined = join(parts, "-") -print(" Join with '-': " + joined) -print("") - -// ============================================ -// 5. Modern Array Operations -// ============================================ -print("5. Array Operations:") +print("2. Array Operations:") numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] print(" Original: " + to_string(numbers)) -print(" Length: " + to_string(len(numbers))) -print(" Index 0: " + to_string(numbers[0])) -print(" Index last: " + to_string(numbers[len(numbers) - 1])) -// Slice syntax arr[start:end] -sliced = numbers[2:7] -print(" Slice [2:7]: " + to_string(sliced)) +// Array helpers from spec: push, join, map, filter, reduce +joined = join([10, 20, 30], ", ") +print(" Joined: " + joined) -// Mutating array ops -arr = [10, 20, 30] -push(arr, 40) -print(" After push(40): " + to_string(arr)) -removed = pop(arr) -print(" Popped: " + to_string(removed) + ", arr: " + to_string(arr)) -insert(arr, 1, 15) -print(" After insert(1, 15): " + to_string(arr)) -removed = remove(arr, 0) -print(" Removed at 0: " + to_string(removed) + ", arr: " + to_string(arr)) - -// Search & utility -print(" Contains 20? " + to_string(contains(arr, 20))) -print(" Index of 20? " + to_string(indexOf(arr, 20))) -print(" Enumerate: " + to_string(enumerate(["a", "b", "c"]))) -print(" Zip: " + to_string(zip([1, 2, 3], ["x", "y", "z"]))) -clear(arr) -print(" After clear: " + to_string(arr)) +// Iterate arrays +print(" Iteration:") +for item in ["apple", "banana", "cherry"] + print(" " + item) print("") // ============================================ -// 6. Maps (Dictionaries) +// 3. Maps (Dictionaries) // ============================================ -print("6. Map Operations:") +print("3. Map Operations:") person = {"name": "Alice", "age": 30, "role": "Developer"} print(" Person: " + to_string(person)) print(" Has 'age' key: " + to_string(has(person, "age"))) print(" Keys: " + to_string(keys(person))) print(" Values: " + to_string(values(person))) - -// Bracket access and assignment -print(" person[\"name\"]: " + person["name"]) -person["age"] = 31 -print(" Updated age: " + to_string(person["age"])) - -// Object property access (dot notation) -print(" person.name: " + person.name) print("") // ============================================ -// 7. Object-Oriented Programming +// 4. Object-Oriented Programming // ============================================ -print("7. Classes & Objects:") +print("4. Classes & Objects:") class Counter(number initial, string label) count = 0 @@ -150,9 +78,9 @@ counter.display() print("") // ============================================ -// 8. Inheritance +// 5. Inheritance // ============================================ -print("8. Inheritance:") +print("5. Inheritance:") class Animal(string type) species = "" @@ -179,9 +107,9 @@ print(" Species: " + dog.species) print("") // ============================================ -// 9. Error Handling & Exceptions +// 6. Error Handling // ============================================ -print("9. Exception Handling:") +print("6. Exception Handling:") try print(" Attempting risky operation...") @@ -194,63 +122,51 @@ finally print("") // ============================================ -// 10. Comparison & Logical Operators +// 7. String Manipulation // ============================================ -print("10. Comparison & Logical Operators:") -number a = 10 -number b = 20 -print(" a=10, b=20") -print(" a < b: " + to_string(a < b)) -print(" a <= b: " + to_string(a <= b)) -print(" a > b: " + to_string(a > b)) -print(" a >= b: " + to_string(a >= b)) -print(" a == b: " + to_string(a == b)) -print(" a != b: " + to_string(a != b)) -print(" a < b && a > 0: " + to_string(a < b && a > 0)) -print(" a > b || a > 0: " + to_string(a > b || a > 0)) -print(" !true: " + to_string(!true)) -print(" Ternary (a < b ? \"yes\" : \"no\"): " + (a < b ? "yes" : "no")) +print("7. String Features:") +string text = "Hello, Fun Language!" +print(" Original: " + text) +// Note: Using stdlib functions (assumed to exist in utils modules) +// len, substr, find, split would come from stdlib print("") // ============================================ -// 11. Control Flow: if/else if/else +// 8. Mathematical Operations // ============================================ -print("11. If/Else If/Else:") -number score = 85 - -if score >= 90 - print(" Grade: A") -else if score >= 80 - print(" Grade: B") -else if score >= 70 - print(" Grade: C") -else - print(" Grade: F") +print("8. Math Functions:") +float x = 16.7 +print(" x = " + to_string(x)) +// Note: Math functions like sqrt, floor, ceil, abs, gcd, lcm +// would come from stdlib or similar print("") // ============================================ -// 12. Control Flow: Loops +// 9. Bitwise Operations // ============================================ -print("12. Loop Variants:") +print("9. Bitwise Operations:") +number bits1 = 12 +number bits2 = 10 +print(" 12 & 10 = " + to_string(band(bits1, bits2))) +print(" 12 | 10 = " + to_string(bor(bits1, bits2))) +print(" 12 ^ 10 = " + to_string(bxor(bits1, bits2))) +print(" 12 << 2 = " + to_string(shl(bits1, 2))) +print(" ~12 = " + to_string(bnot(bits1))) +print("") -// For-each with array -print(" For-each array:") -for item in ["apple", "banana", "cherry"] - print(" " + item) +// ============================================ +// 10. Control Flow +// ============================================ +print("10. Control Flow:") -// For-range loop -print(" For-range 0..4:") -for i in range(0, 5) - print(" " + to_string(i)) - -// For-map loop -print(" For-map key, value:") -config_map = {"a": 1, "b": 2, "c": 3} -for (k, v) in config_map - print(" " + k + " = " + to_string(v)) +// For loop with array +print(" Countdown:") +for i in [5, 4, 3, 2, 1] + print(" " + to_string(i) + "...") +print(" Liftoff!") // While with break/continue -print(" While with break/continue:") +print(" Skip evens:") number n = 0 while n < 10 n = n + 1 @@ -262,55 +178,24 @@ while n < 10 print("") // ============================================ -// 13. Mathematical Operations +// 11. Type Introspection // ============================================ -print("13. Math Functions:") -number mx = -16 -print(" abs(-16) = " + to_string(abs(mx))) -print(" min(10, 20) = " + to_string(min(10, 20))) -print(" max(10, 20) = " + to_string(max(10, 20))) -print(" clamp(50, 0, 10) = " + to_string(clamp(50, 0, 10))) -print(" pow(2, 10) = " + to_string(pow(2, 10))) -print(" sqrt(144) = " + to_string(sqrt(144))) -print(" floor(3.7) = " + to_string(floor(3.7))) -print(" ceil(3.2) = " + to_string(ceil(3.2))) -print(" round(3.5) = " + to_string(round(3.5))) -print(" trunc(3.9) = " + to_string(trunc(3.9))) -print(" sin(0) = " + to_string(sin(0))) -print(" cos(0) = " + to_string(cos(0))) -print(" gcd(12, 8) = " + to_string(gcd(12, 8))) -print(" lcm(12, 8) = " + to_string(lcm(12, 8))) -print(" isqrt(50) = " + to_string(isqrt(50))) -print(" sign(-42) = " + to_string(sign(-42))) -print(" fmin(3.1, 2.9) = " + to_string(fmin(3.1, 2.9))) -print(" fmax(3.1, 2.9) = " + to_string(fmax(3.1, 2.9))) +print("11. Type Introspection:") +number check_int = 42 +string check_str = "hello" +check_arr = [1, 2, 3] +check_map = {"key": "value"} -// Random (deterministic for demo) -random_seed(42) -print(" random_int(1, 100) = " + to_string(random_int(1, 100))) -print(" random_number(8) = " + random_number(8)) +print(" typeof(42) = " + typeof(check_int)) +print(" typeof(\"hello\") = " + typeof(check_str)) +print(" typeof([1,2,3]) = " + typeof(check_arr)) +print(" typeof(map) = " + typeof(check_map)) print("") // ============================================ -// 14. Bitwise Operations +// 12. Functional Programming // ============================================ -print("14. Bitwise Operations:") -number bits1 = 12 // 1100 -number bits2 = 10 // 1010 -print(" 12 & 10 = " + to_string(band(bits1, bits2))) -print(" 12 | 10 = " + to_string(bor(bits1, bits2))) -print(" 12 ^ 10 = " + to_string(bxor(bits1, bits2))) -print(" ~12 = " + to_string(bnot(bits1))) -print(" 12 << 2 = " + to_string(shl(bits1, 2))) -print(" 12 >> 2 = " + to_string(shr(bits1, 2))) -print(" rol(0x80000001, 1) = " + to_string(rol(0x80000001, 1))) -print(" ror(0x80000001, 1) = " + to_string(ror(0x80000001, 1))) -print("") - -// ============================================ -// 15. Functional & Higher-Order Programming -// ============================================ -print("15. Higher-Order Functions:") +print("12. Higher-Order Functions:") fun double(n) return n * 2 @@ -323,112 +208,28 @@ print(" apply_twice(5, double) = " + to_string(result)) print("") // ============================================ -// 16. Array Higher-Order Functions +// 13. Array Operations with Spec Functions // ============================================ -print("16. Array Higher-Order Functions:") +print("13. Array Higher-Order Functions:") nums = [1, 2, 3, 4, 5] fun square(x) return x * x squared = map(nums, square) -print(" map(nums, square): " + to_string(squared)) +print(" Squared: " + to_string(squared)) fun is_even(x) return x % 2 == 0 evens = filter(nums, is_even) -print(" filter(nums, is_even): " + to_string(evens)) +print(" Evens: " + to_string(evens)) fun sum(acc, x) return acc + x total = reduce(nums, 0, sum) -print(" reduce(nums, 0, sum): " + to_string(total)) -print("") - -// ============================================ -// 17. File I/O -// ============================================ -print("17. File I/O:") -write_file("/tmp/fun_demo.txt", "Hello from Fun!") -content = read_file("/tmp/fun_demo.txt") -print(" Written and read back: " + content) -print("") - -// ============================================ -// 18. Environment & OS -// ============================================ -print("18. Environment & OS:") -print(" HOME: " + env("HOME")) -print(" Version: " + fun_version()) - -// List directory (non-empty /tmp assumed) -listing = os_list_dir("/tmp") -print(" /tmp has " + to_string(len(listing)) + " entries") - -// Run a command and capture output -proc_result = proc_run("echo hello from fun") -print(" proc_run output: " + proc_result["out"]) - -// System call exit code -sys_code = system("true") -print(" system(\"true\") exit: " + to_string(sys_code)) -print("") - -// ============================================ -// 19. Date, Time & Sleep -// ============================================ -print("19. Date, Time & Sleep:") -now = time_now_ms() -print(" Now (epoch ms): " + to_string(now)) -print(" Date formatted: " + date_format(now, "%Y-%m-%d %H:%M:%S")) - -mono = clock_mono_ms() -print(" Monotonic ms: " + to_string(mono)) - -// Short sleep to demonstrate -sleep(10) -after = clock_mono_ms() -diff = after - mono -print(" Slept 10 ms, elapsed: " + to_string(diff) + " ms") -print("") - -// ============================================ -// 20. Echo (print without newline) -// ============================================ -print("20. Echo (no newline):") -echo(" Hello, ") -echo("world") -print("!") -print("") - -// ============================================ -// 21. Threading -// ============================================ -print("21. Threading:") - -fun worker(id) - print(" Thread " + to_string(id) + " says hi!") - return id * 2 - -t1 = thread_spawn(worker, [1]) -t2 = thread_spawn(worker, [2]) -r1 = thread_join(t1) -r2 = thread_join(t2) -print(" Joined thread results: " + to_string(r1) + ", " + to_string(r2)) -print("") - -// ============================================ -// 22. Type-Safe Integer Clamping -// ============================================ -print("22. Integer Clamping:") -byte val = 300 -print(" byte val = 300 -> clamped to " + to_string(val)) -int8 signed = 200 -print(" int8 val = 200 -> clamped to " + to_string(signed)) -uint16 big = 70000 -print(" uint16 val = 70000 -> clamped to " + to_string(big)) +print(" Sum: " + to_string(total)) print("") // ============================================ diff --git a/make b/make index 8dcffd3..c26a06a 100755 --- a/make +++ b/make @@ -31,7 +31,6 @@ if [ "$target" = "all" ]; then -DFUN_WITH_CPP=ON \ -DFUN_WITH_KCGI=ON \ -DFUN_WITH_OPENSSL=ON \ - -DFUN_WITH_REDIS=ON \ && cmake --build build --target fun elif [ "$target" = "all_debug" ]; then rm -rf build \ @@ -46,7 +45,6 @@ elif [ "$target" = "all_debug" ]; then -DFUN_WITH_INI=ON \ -DFUN_WITH_CPP=ON \ -DFUN_WITH_OPENSSL=ON \ - -DFUN_WITH_REDIS=ON \ -DFUN_DEBUG=ON \ && cmake --build build --target fun elif [ "$target" = "alpine" ]; then diff --git a/playground/.gitkeep b/playground/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/scripts/check_op_includes.py b/scripts/check_op_includes.py index be9b6e6..fbc58e9 100755 --- a/scripts/check_op_includes.py +++ b/scripts/check_op_includes.py @@ -134,9 +134,6 @@ def parse_includes_from_vm(text: str) -> set[str]: if d == "sqlite": if n in {"open", "close", "exec", "query"}: return f"SQLITE_{n.upper()}" - if d == "redis": - if n in {"connect", "cmd", "close"}: - return f"REDIS_{n.upper()}" if d == "pcsc": if n in {"establish", "release", "list_readers", "connect", "disconnect", "transmit"}: return f"PCSC_{n.upper()}" diff --git a/src/bytecode.h b/src/bytecode.h index 88765a7..42c4aea 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -186,11 +186,6 @@ typedef enum { OP_SQLITE_EXEC, // pops sql, handle; pushes sqlite rc (0=OK) OP_SQLITE_QUERY, // pops sql, handle; pushes array - // Redis (optional, hiredis) - OP_REDIS_CONNECT, // pops port:int, host:string; pushes handle (>0) or 0 - OP_REDIS_CMD, // pops cmd:string, handle:int; pushes reply (string/int/array/nil/map) - OP_REDIS_CLOSE, // pops handle:int; pushes Nil - // PCSC (smart card) opcodes OP_PCSC_ESTABLISH, // returns context id (>0) or 0 OP_PCSC_RELEASE, // pops ctx id; returns 1/0 diff --git a/src/extensions/redis.c b/src/extensions/redis.c deleted file mode 100644 index 7c6ff1b..0000000 --- a/src/extensions/redis.c +++ /dev/null @@ -1,221 +0,0 @@ -/* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2026 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - */ - -/** - * @file redis.c - * @brief Hiredis handle registry and reply mapping helpers for the Fun VM. - * - * This translation unit provides two small building blocks used by the Redis - * opcodes (implemented under src/vm/redis/*.c) and included from src/vm.c: - * - * 1) A process-local registry that assigns monotonically increasing positive - * integer identifiers to hiredis connection pointers (redisContext*). - * VM opcodes pass integer ids on the stack instead of raw pointers, keeping - * the bytecode portable and preventing accidental misuse of pointers. - * - * 2) Utilities to convert hiredis reply objects (redisReply) into Fun VM - * Value instances, recursively mapping arrays and supporting basic numeric - * and string types. - * - * Build-time feature flag - * ----------------------- - * The code is compiled only when the CMake option FUN_WITH_REDIS is enabled - * (i.e., the preprocessor symbol FUN_WITH_REDIS is defined). When disabled, - * this file contributes no symbols and the corresponding opcodes are compiled - * into stubs that return neutral values. - * - * Ownership and lifetime - * ---------------------- - * - The registry does NOT open or close Redis connections by itself; it merely - * stores pointers created elsewhere (e.g., via redisConnectWithTimeout()). - * - Adding an entry does not transfer ownership of the redisContext. Callers - * remain responsible for invoking redisFree() at the appropriate time. - * - Removing an entry from the registry does NOT free the connection; it only - * forgets the mapping between id and pointer. The connect/close opcodes take - * care of proper ownership transitions. - * - Integer identifiers are monotonically increasing per process. Once a - * handle id is deleted, it will not be reused within the same process - * lifetime. - * - * Error handling - * -------------- - * Functions here perform basic validation/allocations only. Allocation - * failures return NULL (for lookups/additions) or are silently ignored (for - * deletions of non-existent ids). No hiredis API calls are made here, so no - * hiredis error codes are produced by this module itself. - * - * Thread-safety - * ------------- - * The registry is a simple singly-linked list with no synchronization. It is - * NOT thread-safe. If the VM uses Redis from multiple threads, the caller must - * provide external synchronization around calls to these helpers. - * - * Example - * ------- - * @code{.c} - * // Open a hiredis connection elsewhere: - * struct timeval tv = { .tv_sec = 2, .tv_usec = 0 }; - * redisContext *ctx = redisConnectWithTimeout("127.0.0.1", 6379, tv); - * if (ctx && !ctx->err) { - * // Register and get an id: - * RedisHandle *h = redis_reg_add(ctx); - * int id = h ? h->id : -1; - * - * // Later look it up: - * RedisHandle *same = redis_reg_get(id); - * if (same) { - * // use same->ctx with hiredis APIs - * } - * - * // When finished, drop the registry entry and free manually: - * redis_reg_del(id); - * redisFree(ctx); - * } - * @endcode - */ - -#ifdef FUN_WITH_REDIS -#include -#include -#include - -/* Forward declarations from the VM (available in the same TU via includes) */ -static Value hiredis_reply_to_value(const redisReply *r); - -/** - * @brief Node in a singly-linked list of registered Redis handles. - * - * Associates a monotonically increasing positive integer identifier with a raw - * hiredis connection pointer. The list head is stored in a file-static global - * (g_redis_handles). - */ -typedef struct RedisHandle { - int id; /**< Positive identifier assigned by the registry. */ - redisContext *ctx; /**< Opaque pointer to a hiredis connection. */ - struct RedisHandle *next;/**< Next entry in the singly-linked list. */ -} RedisHandle; - -/** @brief Global head of the Redis handle list (NULL denotes empty list). */ -static RedisHandle *g_redis_handles = NULL; -/** @brief Next positive identifier to assign to a newly added handle. */ -static int g_redis_next_id = 1; - -/** - * @brief Add a hiredis connection handle to the registry. - * - * Allocates a new list node, assigns a fresh positive id, and prepends it to - * the internal registry list. Ownership of the redisContext remains with the - * caller; this registry does not free it during deletion. - * - * @param ctx Valid pointer to an opened hiredis connection. - * @return Pointer to the newly created RedisHandle on success; NULL on - * allocation failure. The returned pointer remains owned by the - * registry; do not free it directly. - */ -static RedisHandle *redis_reg_add(redisContext *ctx) { - RedisHandle *h = (RedisHandle *)calloc(1, sizeof(RedisHandle)); - if (!h) return NULL; - h->id = g_redis_next_id++; - h->ctx = ctx; - h->next = g_redis_handles; - g_redis_handles = h; - return h; -} - -/** - * @brief Look up a registered Redis handle by id. - * - * Performs a linear search over the internal list to find a matching id. - * - * @param id Positive identifier previously returned by redis_reg_add(). - * @return Pointer to the RedisHandle entry if found; NULL otherwise. - * - * @note The returned pointer is owned by the registry and must not be freed by - * the caller. - */ -static RedisHandle *redis_reg_get(int id) { - for (RedisHandle *p = g_redis_handles; p; p = p->next) - if (p->id == id) return p; - return NULL; -} - -/** - * @brief Remove a Redis handle entry from the registry. - * - * Deletes the list node associated with the given id. - * - * @param id Positive identifier of the entry to remove. - * - * @note This function does not free the underlying redisContext; the caller is - * responsible for calling redisFree() if appropriate. - * @note If the id does not exist, the function is a no-op. - */ -static void redis_reg_del(int id) { - RedisHandle **pp = &g_redis_handles; - while (*pp) { - if ((*pp)->id == id) { - RedisHandle *d = *pp; - *pp = d->next; - free(d); - return; - } - pp = &(*pp)->next; - } -} - -/** - * @brief Convert a hiredis reply to a Fun Value. - * - * Recursively maps hiredis reply types to the closest Fun representation: - * - REDIS_REPLY_STRING / STATUS -> string - * - REDIS_REPLY_INTEGER -> int - * - REDIS_REPLY_NIL -> nil - * - REDIS_REPLY_ARRAY -> array of recursively converted values - * - REDIS_REPLY_DOUBLE (if available) -> float - * - REDIS_REPLY_ERROR / default -> string (error text or "ERR") - * - * @param r Non-owning pointer to a redisReply. - * @return Value converted from the reply. For NULL replies, returns nil. - */ -static Value hiredis_reply_to_value(const redisReply *r) { - if (!r) return make_nil(); - switch (r->type) { - case REDIS_REPLY_STRING: - case REDIS_REPLY_STATUS: - return make_string(r->str ? r->str : ""); - case REDIS_REPLY_INTEGER: - return make_int((int64_t)r->integer); - case REDIS_REPLY_NIL: - return make_nil(); - case REDIS_REPLY_ARRAY: { - int n = (int)r->elements; - if (n <= 0) { - return make_array_from_values(NULL, 0); - } - Value *items = (Value *)calloc((size_t)n, sizeof(Value)); - if (!items) return make_array_from_values(NULL, 0); - for (int i = 0; i < n; i++) { - items[i] = hiredis_reply_to_value(r->element[i]); - } - Value arr = make_array_from_values(items, n); - for (int i = 0; i < n; i++) free_value(items[i]); - free(items); - return arr; - } -#ifdef REDIS_REPLY_DOUBLE - case REDIS_REPLY_DOUBLE: - return make_float(r->dval); -#endif - case REDIS_REPLY_ERROR: - default: - return make_string(r->str ? r->str : "ERR"); - } -} - -#endif /* FUN_WITH_REDIS */ diff --git a/src/fun.c b/src/fun.c index 12869de..127af48 100644 --- a/src/fun.c +++ b/src/fun.c @@ -142,26 +142,11 @@ int main(int argc, char **argv) { } char *joined = (char *)malloc(total); if (joined) { - size_t off = 0; + joined[0] = '\0'; for (int i = 0; i < sargc; ++i) { - int written = snprintf(joined + off, (off < total ? total - off : 0), - "%s%s", - argv[sargi + i], - (i + 1 < sargc) ? " " : ""); - if (written < 0) { /* encoding error */ - off = total; /* force stop */ - break; - } - size_t w = (size_t)written; - if (off + w >= total) { /* ensure we don't advance beyond buffer */ - off = total ? total - 1 : 0; - joined[off] = '\0'; - break; - } - off += w; + strcat(joined, argv[sargi + i]); + if (i + 1 < sargc) strcat(joined, " "); } - /* Ensure NUL termination even if loop didn't run */ - if (total > 0) joined[(off < total) ? off : (total - 1)] = '\0'; setenv("FUN_ARGS", joined, 1); free(joined); } diff --git a/src/parser.c b/src/parser.c index aaa020d..eee6572 100644 --- a/src/parser.c +++ b/src/parser.c @@ -2070,75 +2070,6 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) free(name); return 1; } - /* Redis builtins (hiredis) */ - if (strcmp(name, "redis_connect") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { - parser_fail(*pos, "redis_connect expects (host, port)"); - free(name); - return 0; - } - if (!consume_char(src, len, pos, ',')) { - parser_fail(*pos, "redis_connect expects (host, port)"); - free(name); - return 0; - } - if (!emit_expression(bc, src, len, pos)) { - parser_fail(*pos, "redis_connect expects (host, port)"); - free(name); - return 0; - } - if (!consume_char(src, len, pos, ')')) { - parser_fail(*pos, "Expected ')' after redis_connect args"); - free(name); - return 0; - } - bytecode_add_instruction(bc, OP_REDIS_CONNECT, 0); - free(name); - return 1; - } - if (strcmp(name, "redis_cmd") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { - parser_fail(*pos, "redis_cmd expects (handle, cmd)"); - free(name); - return 0; - } - if (!consume_char(src, len, pos, ',')) { - parser_fail(*pos, "redis_cmd expects (handle, cmd)"); - free(name); - return 0; - } - if (!emit_expression(bc, src, len, pos)) { - parser_fail(*pos, "redis_cmd expects (handle, cmd)"); - free(name); - return 0; - } - if (!consume_char(src, len, pos, ')')) { - parser_fail(*pos, "Expected ')' after redis_cmd args"); - free(name); - return 0; - } - bytecode_add_instruction(bc, OP_REDIS_CMD, 0); - free(name); - return 1; - } - if (strcmp(name, "redis_close") == 0) { - (*pos)++; /* '(' */ - if (!emit_expression(bc, src, len, pos)) { - parser_fail(*pos, "redis_close expects (handle)"); - free(name); - return 0; - } - if (!consume_char(src, len, pos, ')')) { - parser_fail(*pos, "Expected ')' after redis_close arg"); - free(name); - return 0; - } - bytecode_add_instruction(bc, OP_REDIS_CLOSE, 0); - free(name); - return 1; - } if (strcmp(name, "sqlite_close") == 0) { (*pos)++; /* '(' */ if (!emit_expression(bc, src, len, pos)) { diff --git a/src/repl.c b/src/repl.c index 4d2ccb7..458886f 100644 --- a/src/repl.c +++ b/src/repl.c @@ -259,16 +259,14 @@ static int complete_load_path(char *buf, size_t *len_io) { if (slash) { size_t dlen = (size_t)(slash - expanded); if (dlen == 0) { - /* use bounded copy to avoid potential overflow (even though "/" fits) */ - snprintf(dirpart, sizeof(dirpart), "%s", "/"); + strcpy(dirpart, "/"); } else { memcpy(dirpart, expanded, dlen); dirpart[dlen] = '\0'; } snprintf(base, sizeof(base), "%s", slash + 1); } else { - /* use bounded copy to avoid potential overflow (even though "." fits) */ - snprintf(dirpart, sizeof(dirpart), "%s", "."); + strcpy(dirpart, "."); snprintf(base, sizeof(base), "%s", expanded); } diff --git a/src/vm.c b/src/vm.c index 8841e36..f5a3803 100644 --- a/src/vm.c +++ b/src/vm.c @@ -73,7 +73,6 @@ #include "extensions/sqlite.c" #include "extensions/xml2.c" #include "extensions/kcgi.c" -#include "extensions/redis.c" /* forward declarations for include mapping used in error reporting */ extern char *preprocess_includes(const char *src); @@ -1231,13 +1230,6 @@ void vm_run(VM *vm, Bytecode *entry) { #include "vm/sqlite/query.c" #endif -/* Redis ops */ -#ifdef FUN_WITH_REDIS -#include "vm/redis/connect.c" -#include "vm/redis/cmd.c" -#include "vm/redis/close.c" -#endif - /* C++ demo opcodes (guarded) */ #ifdef FUN_WITH_CPP case OP_CPP_ADD: { diff --git a/src/vm.h b/src/vm.h index a3a864f..c76de93 100644 --- a/src/vm.h +++ b/src/vm.h @@ -66,8 +66,6 @@ static const char *opcode_names[] = { "JSON_PARSE", "JSON_STRINGIFY", "JSON_FROM_FILE", "JSON_TO_FILE", "CURL_GET", "CURL_POST", "CURL_DOWNLOAD", "SQLITE_OPEN", "SQLITE_CLOSE", "SQLITE_EXEC", "SQLITE_QUERY", - /* Redis (hiredis) */ - "REDIS_CONNECT", "REDIS_CMD", "REDIS_CLOSE", "LIBSQL_OPEN", "LIBSQL_CLOSE", "LIBSQL_EXEC", "LIBSQL_QUERY", "PCSC_ESTABLISH", "PCSC_RELEASE", "PCSC_LIST_READERS", "PCSC_CONNECT", "PCSC_DISCONNECT", "PCSC_TRANSMIT", "PCRE2_TEST", "PCRE2_MATCH", "PCRE2_FINDALL", diff --git a/src/vm/os/proc_system.c b/src/vm/os/proc_system.c index 90bc8db..238b18f 100644 --- a/src/vm/os/proc_system.c +++ b/src/vm/os/proc_system.c @@ -27,24 +27,6 @@ case OP_PROC_SYSTEM: { push_value(vm, make_int(-1)); break; } - /* Security hardening: reject commands containing shell metacharacters or control chars - to reduce risk of command injection when using system(3). This preserves simple - command execution like "ls -l" but blocks dangerous constructs like pipes, redirects, - command substitution, etc. */ - const char *bad = "&;|$<>`\\\"'()*?[]{}~"; - int unsafe = 0; - for (const unsigned char *p = (const unsigned char *)cmd; *p; ++p) { - if (*p < 0x20 || strchr(bad, (int)*p)) { /* control or meta */ - unsafe = 1; - break; - } - } - if (unsafe) { - /* refuse to execute potentially unsafe shell command */ - push_value(vm, make_int(-1)); - free(cmd); - break; - } int status = system(cmd); int code = -1; #ifdef __unix__ diff --git a/src/vm/redis/close.c b/src/vm/redis/close.c deleted file mode 100644 index 4315ce3..0000000 --- a/src/vm/redis/close.c +++ /dev/null @@ -1,45 +0,0 @@ -/* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - */ - -/** - * @file close.c - * @brief Implements the OP_REDIS_CLOSE opcode (conditional build). - * - * Closes a previously opened Redis connection and removes its registry entry. - * - * Stack effect - * ------------ - * OP_REDIS_CLOSE: (handle:int) -> Nil - * - * Behavior - * -------- - * - When FUN_WITH_REDIS is enabled, looks up the handle, calls redisFree() on - * the underlying connection if present, clears the pointer, and removes the - * registry entry. Always pushes Nil. - * - When FUN_WITH_REDIS is disabled, pops the argument and pushes Nil. - */ - -case OP_REDIS_CLOSE: { -#ifdef FUN_WITH_REDIS - Value vh = pop_value(vm); - int hid = (int)vh.i; - free_value(vh); - RedisHandle *h = redis_reg_get(hid); - if (h && h->ctx) { - redisFree(h->ctx); - h->ctx = NULL; - } - redis_reg_del(hid); - push_value(vm, make_nil()); -#else - Value v1 = pop_value(vm); free_value(v1); - push_value(vm, make_nil()); -#endif - break; -} diff --git a/src/vm/redis/cmd.c b/src/vm/redis/cmd.c deleted file mode 100644 index 8301a98..0000000 --- a/src/vm/redis/cmd.c +++ /dev/null @@ -1,58 +0,0 @@ -/* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - */ - -/** - * @file cmd.c - * @brief Implements the OP_REDIS_CMD opcode (conditional build). - * - * Executes a Redis command using the synchronous hiredis API on a previously - * opened handle and converts the reply to a Fun Value. - * - * Stack effect - * ------------ - * OP_REDIS_CMD: (handle:int, cmd:string) -> reply:Value - * - * Behavior - * -------- - * - The command string uses Redis inline protocol formatting (e.g., - * "PING", "SET key val", "LRANGE list 0 -1"). - * - When FUN_WITH_REDIS is enabled, the opcode looks up the connection by - * handle id, issues redisCommand(), and converts the resulting reply to a - * Fun Value: - * - status/string -> string - * - integer -> int - * - nil -> nil - * - array -> array of converted elements - * On errors or lookup failures, pushes Nil. - * - When FUN_WITH_REDIS is disabled, pops arguments and pushes Nil. - */ - -case OP_REDIS_CMD: { -#ifdef FUN_WITH_REDIS - Value vcmd = pop_value(vm); - Value vh = pop_value(vm); - int hid = (int)vh.i; - char *cmd = value_to_string_alloc(&vcmd); - free_value(vh); - free_value(vcmd); - RedisHandle *h = redis_reg_get(hid); - if (!h || !h->ctx || !cmd) { if (cmd) free(cmd); push_value(vm, make_nil()); break; } - redisReply *r = (redisReply *)redisCommand(h->ctx, cmd); - free(cmd); - if (!r) { push_value(vm, make_nil()); break; } - Value out = hiredis_reply_to_value(r); - freeReplyObject(r); - push_value(vm, out); -#else - Value v1 = pop_value(vm); free_value(v1); - Value v2 = pop_value(vm); free_value(v2); - push_value(vm, make_nil()); -#endif - break; -} diff --git a/src/vm/redis/connect.c b/src/vm/redis/connect.c deleted file mode 100644 index 1fd0fd3..0000000 --- a/src/vm/redis/connect.c +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - */ - -/** - * @file connect.c - * @brief Implements the OP_REDIS_CONNECT opcode (conditional build). - * - * Establishes a synchronous TCP connection to a Redis-compatible server using - * hiredis and registers the connection handle in the internal registry. - * - * Stack effect - * ------------ - * OP_REDIS_CONNECT: (host:string, port:int) -> handle:int (>0) or 0 on error - * - * Behavior - * -------- - * - When FUN_WITH_REDIS is enabled, attempts a blocking connect with a - * 2-second timeout via redisConnectWithTimeout(). On success, the created - * redisContext* is stored in the registry and the assigned positive handle - * id is pushed. On failure, 0 is pushed. - * - When FUN_WITH_REDIS is disabled, arguments are popped and 0 is pushed. - * - * Notes - * ----- - * - The returned handle must be closed with OP_REDIS_CLOSE to release - * resources and delete the registry entry. - */ - -case OP_REDIS_CONNECT: { -#ifdef FUN_WITH_REDIS - Value vport = pop_value(vm); - Value vhost = pop_value(vm); - int port = (int)vport.i; - char *host = value_to_string_alloc(&vhost); - free_value(vport); - free_value(vhost); - if (!host) { push_value(vm, make_int(0)); break; } - struct timeval tv; tv.tv_sec = 2; tv.tv_usec = 0; - redisContext *ctx = redisConnectWithTimeout(host, port, tv); - free(host); - if (!ctx || ctx->err) { - if (ctx) redisFree(ctx); - push_value(vm, make_int(0)); - break; - } - RedisHandle *h = redis_reg_add(ctx); - if (!h) { - redisFree(ctx); - push_value(vm, make_int(0)); - break; - } - push_value(vm, make_int(h->id)); -#else - Value v1 = pop_value(vm); free_value(v1); - Value v2 = pop_value(vm); free_value(v2); - push_value(vm, make_int(0)); -#endif - break; -} diff --git a/web/_includes/footer.html b/web/_includes/footer.html index ea1bfcf..e323926 100644 --- a/web/_includes/footer.html +++ b/web/_includes/footer.html @@ -4,7 +4,7 @@

Navigation