Added a Redis/Valkey extension named redis. (0.42.0)
This commit is contained in:
parent
06072576fc
commit
cbb81c0b93
28 changed files with 798 additions and 15 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
project(fun VERSION 0.41.16 LANGUAGES C)
|
project(fun VERSION 0.42.0 LANGUAGES C)
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 99)
|
set(CMAKE_C_STANDARD 99)
|
||||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ include(${CMAKE_SOURCE_DIR}/cmake/Extensions/PCSC.cmake)
|
||||||
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/REPL.cmake)
|
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/REPL.cmake)
|
||||||
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/SQLITE.cmake)
|
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/SQLITE.cmake)
|
||||||
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/XML2.cmake)
|
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/XML2.cmake)
|
||||||
|
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/REDIS.cmake)
|
||||||
|
|
||||||
# Summary of extension toggles
|
# Summary of extension toggles
|
||||||
message(STATUS "---- Fun extension summary ----")
|
message(STATUS "---- Fun extension summary ----")
|
||||||
|
|
@ -34,4 +35,5 @@ _fun_print_feature("PCRE2 (FUN_WITH_PCRE2)" FUN_WITH_PCRE2)
|
||||||
_fun_print_feature("PCSC-Lite (FUN_WITH_PCSC)" FUN_WITH_PCSC)
|
_fun_print_feature("PCSC-Lite (FUN_WITH_PCSC)" FUN_WITH_PCSC)
|
||||||
_fun_print_feature("REPL (FUN_WITH_REPL)" FUN_WITH_REPL)
|
_fun_print_feature("REPL (FUN_WITH_REPL)" FUN_WITH_REPL)
|
||||||
_fun_print_feature("SQLite (FUN_WITH_SQLITE)" FUN_WITH_SQLITE)
|
_fun_print_feature("SQLite (FUN_WITH_SQLITE)" FUN_WITH_SQLITE)
|
||||||
|
_fun_print_feature("Redis (FUN_WITH_REDIS)" FUN_WITH_REDIS)
|
||||||
message(STATUS "--------------------------------")
|
message(STATUS "--------------------------------")
|
||||||
|
|
|
||||||
22
cmake/Extensions/REDIS.cmake
Normal file
22
cmake/Extensions/REDIS.cmake
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Redis (hiredis)
|
||||||
|
option(FUN_WITH_REDIS "Enable Redis (hiredis) support" ON)
|
||||||
|
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()
|
||||||
|
|
@ -48,7 +48,8 @@ foreach(var_pair
|
||||||
LIBSQL
|
LIBSQL
|
||||||
LIBXML2
|
LIBXML2
|
||||||
OPENSSL
|
OPENSSL
|
||||||
KCGI)
|
KCGI
|
||||||
|
HIREDIS)
|
||||||
if(${var_pair}_INCLUDE_DIRS)
|
if(${var_pair}_INCLUDE_DIRS)
|
||||||
target_include_directories(fun_core PRIVATE ${${var_pair}_INCLUDE_DIRS})
|
target_include_directories(fun_core PRIVATE ${${var_pair}_INCLUDE_DIRS})
|
||||||
endif()
|
endif()
|
||||||
|
|
@ -94,6 +95,9 @@ if(FUN_WITH_SQLITE)
|
||||||
target_compile_definitions(fun_core PUBLIC FUN_WITH_SQLITE=1)
|
target_compile_definitions(fun_core PUBLIC FUN_WITH_SQLITE=1)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(FUN_WITH_REDIS)
|
||||||
|
target_compile_definitions(fun_core PUBLIC FUN_WITH_REDIS=1)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(FUN_WITH_OPENSSL)
|
if(FUN_WITH_OPENSSL)
|
||||||
target_compile_definitions(fun_core PUBLIC FUN_WITH_OPENSSL=1)
|
target_compile_definitions(fun_core PUBLIC FUN_WITH_OPENSSL=1)
|
||||||
|
|
|
||||||
29
examples/extensions/redis/README.md
Normal file
29
examples/extensions/redis/README.md
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
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.
|
||||||
26
examples/extensions/redis/basic_ping.fun
Executable file
26
examples/extensions/redis/basic_ping.fun
Executable file
|
|
@ -0,0 +1,26 @@
|
||||||
|
#!/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-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)
|
||||||
38
examples/extensions/redis/hash_ops.fun
Executable file
38
examples/extensions/redis/hash_ops.fun
Executable file
|
|
@ -0,0 +1,38 @@
|
||||||
|
#!/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-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)
|
||||||
37
examples/extensions/redis/kv_set_get.fun
Executable file
37
examples/extensions/redis/kv_set_get.fun
Executable file
|
|
@ -0,0 +1,37 @@
|
||||||
|
#!/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-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)
|
||||||
36
examples/extensions/redis/list_ops.fun
Executable file
36
examples/extensions/redis/list_ops.fun
Executable file
|
|
@ -0,0 +1,36 @@
|
||||||
|
#!/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-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)
|
||||||
28
examples/extensions/redis/redis_test.fun
Executable file
28
examples/extensions/redis/redis_test.fun
Executable file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/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-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)
|
||||||
|
|
@ -186,6 +186,11 @@ typedef enum {
|
||||||
OP_SQLITE_EXEC, // pops sql, handle; pushes sqlite rc (0=OK)
|
OP_SQLITE_EXEC, // pops sql, handle; pushes sqlite rc (0=OK)
|
||||||
OP_SQLITE_QUERY, // pops sql, handle; pushes array<map>
|
OP_SQLITE_QUERY, // pops sql, handle; pushes array<map>
|
||||||
|
|
||||||
|
// 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
|
// PCSC (smart card) opcodes
|
||||||
OP_PCSC_ESTABLISH, // returns context id (>0) or 0
|
OP_PCSC_ESTABLISH, // returns context id (>0) or 0
|
||||||
OP_PCSC_RELEASE, // pops ctx id; returns 1/0
|
OP_PCSC_RELEASE, // pops ctx id; returns 1/0
|
||||||
|
|
|
||||||
221
src/extensions/redis.c
Normal file
221
src/extensions/redis.c
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 <hiredis/hiredis.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* 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 */
|
||||||
69
src/parser.c
69
src/parser.c
|
|
@ -2070,6 +2070,75 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
||||||
free(name);
|
free(name);
|
||||||
return 1;
|
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) {
|
if (strcmp(name, "sqlite_close") == 0) {
|
||||||
(*pos)++; /* '(' */
|
(*pos)++; /* '(' */
|
||||||
if (!emit_expression(bc, src, len, pos)) {
|
if (!emit_expression(bc, src, len, pos)) {
|
||||||
|
|
|
||||||
8
src/vm.c
8
src/vm.c
|
|
@ -73,6 +73,7 @@
|
||||||
#include "extensions/sqlite.c"
|
#include "extensions/sqlite.c"
|
||||||
#include "extensions/xml2.c"
|
#include "extensions/xml2.c"
|
||||||
#include "extensions/kcgi.c"
|
#include "extensions/kcgi.c"
|
||||||
|
#include "extensions/redis.c"
|
||||||
|
|
||||||
/* forward declarations for include mapping used in error reporting */
|
/* forward declarations for include mapping used in error reporting */
|
||||||
extern char *preprocess_includes(const char *src);
|
extern char *preprocess_includes(const char *src);
|
||||||
|
|
@ -1230,6 +1231,13 @@ void vm_run(VM *vm, Bytecode *entry) {
|
||||||
#include "vm/sqlite/query.c"
|
#include "vm/sqlite/query.c"
|
||||||
#endif
|
#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) */
|
/* C++ demo opcodes (guarded) */
|
||||||
#ifdef FUN_WITH_CPP
|
#ifdef FUN_WITH_CPP
|
||||||
case OP_CPP_ADD: {
|
case OP_CPP_ADD: {
|
||||||
|
|
|
||||||
2
src/vm.h
2
src/vm.h
|
|
@ -66,6 +66,8 @@ static const char *opcode_names[] = {
|
||||||
"JSON_PARSE", "JSON_STRINGIFY", "JSON_FROM_FILE", "JSON_TO_FILE",
|
"JSON_PARSE", "JSON_STRINGIFY", "JSON_FROM_FILE", "JSON_TO_FILE",
|
||||||
"CURL_GET", "CURL_POST", "CURL_DOWNLOAD",
|
"CURL_GET", "CURL_POST", "CURL_DOWNLOAD",
|
||||||
"SQLITE_OPEN", "SQLITE_CLOSE", "SQLITE_EXEC", "SQLITE_QUERY",
|
"SQLITE_OPEN", "SQLITE_CLOSE", "SQLITE_EXEC", "SQLITE_QUERY",
|
||||||
|
/* Redis (hiredis) */
|
||||||
|
"REDIS_CONNECT", "REDIS_CMD", "REDIS_CLOSE",
|
||||||
"LIBSQL_OPEN", "LIBSQL_CLOSE", "LIBSQL_EXEC", "LIBSQL_QUERY",
|
"LIBSQL_OPEN", "LIBSQL_CLOSE", "LIBSQL_EXEC", "LIBSQL_QUERY",
|
||||||
"PCSC_ESTABLISH", "PCSC_RELEASE", "PCSC_LIST_READERS", "PCSC_CONNECT", "PCSC_DISCONNECT", "PCSC_TRANSMIT",
|
"PCSC_ESTABLISH", "PCSC_RELEASE", "PCSC_LIST_READERS", "PCSC_CONNECT", "PCSC_DISCONNECT", "PCSC_TRANSMIT",
|
||||||
"PCRE2_TEST", "PCRE2_MATCH", "PCRE2_FINDALL",
|
"PCRE2_TEST", "PCRE2_MATCH", "PCRE2_FINDALL",
|
||||||
|
|
|
||||||
45
src/vm/redis/close.c
Normal file
45
src/vm/redis/close.c
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
58
src/vm/redis/cmd.c
Normal file
58
src/vm/redis/cmd.c
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
65
src/vm/redis/connect.c
Normal file
65
src/vm/redis/connect.c
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<!--<p style="text-align:center;">{% if site.git %}Source: <a href="{{ site.git }}{{ site.branch }}{{ page.path }}" class="git" target="_blank">{{ site.git }}{{ site.branch }}{{ page.path }}</a>{% endif %}</p>-->
|
<!--<p style="text-align:center;">{% if site.git %}Source: <a href="{{ site.git }}{{ site.branch }}{{ page.path }}" class="git" target="_blank">{{ site.git }}{{ site.branch }}{{ page.path }}</a>{% endif %}</p>-->
|
||||||
<h2 style="display:none;">Navigation</h2>
|
<h2 style="display:none;">Navigation</h2>
|
||||||
<ul style="list-style:none;margin-left:0px;text-align:center;">
|
<ul style="list-style:none;margin-left:0px;text-align:center;">
|
||||||
<li style="display:inline;"><a href="/" style="text-decoration:none;" title="Homepage" class="home">Homepage</a> -</li>
|
<li style="display:inline;"><a href="/" style="text-decoration:none;" title="Home" class="home">Home</a> -</li>
|
||||||
<!--<li style="display:inline;"> <a href="/about/" style="text-decoration:none;" title="About" class="about">About</a> -</li>-->
|
<!--<li style="display:inline;"> <a href="/about/" style="text-decoration:none;" title="About" class="about">About</a> -</li>-->
|
||||||
<li style="display:inline;"> <a href="/contact/" style="text-decoration:none;" title="Contact" class="mail">Contact</a> -</li>
|
<li style="display:inline;"> <a href="/contact/" style="text-decoration:none;" title="Contact" class="mail">Contact</a> -</li>
|
||||||
<li style="display:inline;"> <a href="/community/code-of-conduct/" style="text-decoration:none;" title="Code of Conduct" class="love">Code of Conduct</a> -</li>
|
<li style="display:inline;"> <a href="/community/code-of-conduct/" style="text-decoration:none;" title="Code of Conduct" class="love">Code of Conduct</a> -</li>
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,13 @@
|
||||||
<div class="trigger">
|
<div class="trigger">
|
||||||
<h2 style="display:none;">Navigation</h2>
|
<h2 style="display:none;">Navigation</h2>
|
||||||
<ul style="list-style: none;">
|
<ul style="list-style: none;">
|
||||||
<li><a class="page-link" href="/" style="text-decoration:none;" title="Homepage">/</a></li>
|
<li><a class="page-link" href="/" style="text-decoration:none;" title="Home">Home</a></li>
|
||||||
<li><a class="page-link" href="/about/" style="text-decoration:none;" title="About">About</a></li>
|
<li><a class="page-link" href="/about/" style="text-decoration:none;" title="About">About</a></li>
|
||||||
<li><a class="page-link" href="/features/" style="text-decoration:none;" title="Features">Features</a></li>
|
<li><a class="page-link" href="/features/" style="text-decoration:none;" title="Features">Features</a></li>
|
||||||
<!--<li><a class="page-link" href="/community/" style="text-decoration:none;" title="Community">Community</a></li>-->
|
|
||||||
<!--<li><a class="page-link" href="/contact/" style="text-decoration:none;" title="Contact">Contact</a></li>-->
|
|
||||||
<li><a class="page-link" href="/documentation/" style="text-decoration:none;" title="Documentation">Documentation</a></li>
|
<li><a class="page-link" href="/documentation/" style="text-decoration:none;" title="Documentation">Documentation</a></li>
|
||||||
<li><a class="page-link" href="/download/" style="text-decoration:none;" title="Download">Download</a></li>
|
<li><a class="page-link" href="/download/" style="text-decoration:none;" title="Download">Download</a></li>
|
||||||
|
<li><a class="page-link" href="/community/" style="text-decoration:none;" title="Community">Community</a></li>
|
||||||
|
<li><a class="page-link" href="/contact/" style="text-decoration:none;" title="Contact">Contact</a></li>
|
||||||
<li><a class="page-link" href="/faq/" style="text-decoration:none;" title="FAQ">FAQ</a></li>
|
<li><a class="page-link" href="/faq/" style="text-decoration:none;" title="FAQ">FAQ</a></li>
|
||||||
<!--
|
<!--
|
||||||
https://www.w3schools.com/howto/howto_js_toggle_dark_mode.asp
|
https://www.w3schools.com/howto/howto_js_toggle_dark_mode.asp
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
layout: page
|
layout: page
|
||||||
published: false
|
published: true
|
||||||
noToc: false
|
noToc: false
|
||||||
noDate: false
|
noDate: false
|
||||||
title: Community
|
title: Community
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
layout: page
|
layout: page
|
||||||
published: false
|
published: true
|
||||||
noToc: true
|
noToc: true
|
||||||
noDate: false
|
noDate: false
|
||||||
noPermalink: true
|
noPermalink: true
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ noToc: false
|
||||||
noComments: false
|
noComments: false
|
||||||
noDate: false
|
noDate: false
|
||||||
title: Building Fun
|
title: Building Fun
|
||||||
subtitle: 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).
|
subtitle: 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_REDIS).
|
||||||
description: 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).
|
description: 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_REDIS).
|
||||||
permalink: /documentation/build/
|
permalink: /documentation/build/
|
||||||
lang: en
|
lang: en
|
||||||
tags:
|
tags:
|
||||||
|
|
@ -50,6 +50,7 @@ Fun exposes several options you can toggle at configure time:
|
||||||
- `FUN_WITH_CPP` (ON/OFF) - Enable C++-based opcode/examples support
|
- `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_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_OPENSSL` (ON/OFF) - Enable OpenSSL-backed helpers (MD5/SHA-256/SHA-512/RIPEMD-160)
|
||||||
|
- `FUN_WITH_REDIS` (ON/OFF) - Enable Redis extension powered by hiredis (sync API: connect/cmd/close)
|
||||||
|
|
||||||
### VM configuration constants
|
### VM configuration constants
|
||||||
|
|
||||||
|
|
@ -72,6 +73,8 @@ See [VM](../vm/) for more information.
|
||||||
FUN_USE_MUSL: ENABLED|DISABLED
|
FUN_USE_MUSL: ENABLED|DISABLED
|
||||||
FUN_WITH_CPP: ENABLED|DISABLED
|
FUN_WITH_CPP: ENABLED|DISABLED
|
||||||
FUN_WITH_RUST: ENABLED|DISABLED
|
FUN_WITH_RUST: ENABLED|DISABLED
|
||||||
|
FUN_WITH_OPENSSL: ENABLED|DISABLED
|
||||||
|
FUN_WITH_REDIS: ENABLED|DISABLED
|
||||||
===========================</pre>
|
===========================</pre>
|
||||||
|
|
||||||
## Example commands
|
## Example commands
|
||||||
|
|
@ -92,7 +95,7 @@ cmake --build build_release --target build</pre>
|
||||||
### Enabling optional extensions
|
### Enabling optional extensions
|
||||||
|
|
||||||
<pre>cmake -S . -B build_release -DCMAKE_BUILD_TYPE=Release \
|
<pre>cmake -S . -B build_release -DCMAKE_BUILD_TYPE=Release \
|
||||||
-DFUN_WITH_CPP=ON -DFUN_WITH_RUST=ON -DFUN_WITH_OPENSSL=ON
|
-DFUN_WITH_CPP=ON -DFUN_WITH_RUST=ON -DFUN_WITH_OPENSSL=ON -DFUN_WITH_REDIS=ON
|
||||||
cmake --build build_release --target build</pre>
|
cmake --build build_release --target build</pre>
|
||||||
|
|
||||||
### Customizing VM limits
|
### Customizing VM limits
|
||||||
|
|
@ -104,6 +107,8 @@ 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_OPENSSL` is enabled, CMake must detect your system OpenSSL (libcrypto).
|
||||||
|
|
||||||
|
If `FUN_WITH_REDIS` is enabled, ensure `hiredis` headers and library are installed and discoverable (typically via pkg-config).
|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
- CLI: run the `fun` executable from your build directory.
|
- CLI: run the `fun` executable from your build directory.
|
||||||
- REPL: `fun -i` or just run `fun` without a script, depending on your CLI version (see [CLI](../cli/)).
|
- REPL: `fun -i` or just run `fun` without a script, depending on your CLI version (see [CLI](../cli/)).
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ The examples directory contains demonstrations of most Fun features, from basic
|
||||||
Documentation for optional, build-time selectable integrations lives in [./extensions/](./extensions/):
|
Documentation for optional, build-time selectable integrations lives in [./extensions/](./extensions/):
|
||||||
|
|
||||||
- [Index of extensions](./extensions/)
|
- [Index of extensions](./extensions/)
|
||||||
- Highlights: [cURL](./extensions/curl/), [INI](./extensions/ini/), [JSON](./extensions/json/), [XML (libxml2)](./extensions/xml2/), [SQLite](./extensions/sqlite/), [PCRE2](./extensions/pcre2/), [PC/SC](./extensions/pcsc/), [OpenSSL](./extensions/openssl/)
|
- Highlights: [cURL](./extensions/curl/), [INI](./extensions/ini/), [JSON](./extensions/json/), [XML (libxml2)](./extensions/xml2/), [SQLite](./extensions/sqlite/), [PCRE2](./extensions/pcre2/), [PC/SC](./extensions/pcsc/), [OpenSSL](./extensions/openssl/), [Redis](./extensions/redis/)
|
||||||
|
|
||||||
## Tips
|
## Tips
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ This section documents Fun's optional, build-time selectable extensions. Each pa
|
||||||
- [PC/SC (Smart cards)](./pcsc/)
|
- [PC/SC (Smart cards)](./pcsc/)
|
||||||
- [OpenSSL](./openssl/)
|
- [OpenSSL](./openssl/)
|
||||||
- [kcgi](./kcgi/)
|
- [kcgi](./kcgi/)
|
||||||
|
- [Redis (hiredis)](./redis/)
|
||||||
|
|
||||||
## Notes:
|
## Notes:
|
||||||
|
|
||||||
|
|
|
||||||
81
web/documentation/extensions/redis/redis.md
Normal file
81
web/documentation/extensions/redis/redis.md
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
---
|
||||||
|
layout: page
|
||||||
|
published: true
|
||||||
|
noToc: false
|
||||||
|
noComments: false
|
||||||
|
noDate: false
|
||||||
|
title: Redis (hiredis) Extension
|
||||||
|
subtitle: Redis client integration for Fun using the hiredis C library.
|
||||||
|
description: Documentation for the optional Redis extension in Fun. Provides redis_connect, redis_cmd, and redis_close builtins backed by hiredis.
|
||||||
|
permalink: /documentation/extensions/redis/
|
||||||
|
lang: en
|
||||||
|
tags:
|
||||||
|
- redis
|
||||||
|
- hiredis
|
||||||
|
- database
|
||||||
|
- cache
|
||||||
|
- extension
|
||||||
|
- networking
|
||||||
|
- async
|
||||||
|
---
|
||||||
|
|
||||||
|
The Redis extension adds a minimal client for Redis-compatible servers using the [hiredis](https://github.com/redis/hiredis){:class="ext"} C library. It currently exposes a simple synchronous API; Async I/O through Fun's event loop will be added later.
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
------------
|
||||||
|
- Build-time option: `-DFUN_WITH_REDIS=ON`
|
||||||
|
- System libraries: `hiredis` headers and library available (via pkg-config or default linker search paths)
|
||||||
|
- Runtime: a Redis-compatible server (e.g., on `127.0.0.1:6379`)
|
||||||
|
|
||||||
|
Enabling the extension
|
||||||
|
----------------------
|
||||||
|
Example CMake configure line enabling Redis along with other options:
|
||||||
|
|
||||||
|
<pre>cmake -S . -B build_release -DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DFUN_WITH_REDIS=ON -DFUN_WITH_OPENSSL=ON</pre>
|
||||||
|
|
||||||
|
When configured, the build summary will include a line:
|
||||||
|
|
||||||
|
<pre>Redis (FUN_WITH_REDIS): ENABLED</pre>
|
||||||
|
|
||||||
|
Provided builtins/opcodes
|
||||||
|
-------------------------
|
||||||
|
- `redis_connect(host: string, port: int) -> int`
|
||||||
|
- Establishes a TCP connection and returns a positive handle id on success, or `0` on error.
|
||||||
|
- Example: `h = redis_connect('127.0.0.1', 6379)`
|
||||||
|
|
||||||
|
- `redis_cmd(handle: int, cmd: string) -> Value`
|
||||||
|
- Executes a Redis inline command string and returns the reply as a Fun value.
|
||||||
|
- Reply mapping:
|
||||||
|
- Status/String -> string
|
||||||
|
- Integer -> number
|
||||||
|
- Nil -> `nil`
|
||||||
|
- Array -> array of recursively converted values
|
||||||
|
- Example: `print(redis_cmd(h, 'PING'))` ⇒ `PONG`
|
||||||
|
|
||||||
|
- `redis_close(handle: int) -> Nil`
|
||||||
|
- Closes the connection associated with the handle and frees resources.
|
||||||
|
|
||||||
|
Notes and limitations
|
||||||
|
---------------------
|
||||||
|
- Error handling: invalid handles or failed commands yield `nil` or an empty/neutral value depending on context.
|
||||||
|
- Security: this initial version does not include TLS; TLS support may be added in a future iteration once hiredis SSL is wired in.
|
||||||
|
- Async: the current API is synchronous. Integration with Fun's asyncio is planned.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
Runnable examples are included in the source tree:
|
||||||
|
|
||||||
|
- `examples/extensions/redis/basic_ping.fun`
|
||||||
|
- `examples/extensions/redis/kv_set_get.fun`
|
||||||
|
- `examples/extensions/redis/list_ops.fun`
|
||||||
|
- `examples/extensions/redis/hash_ops.fun`
|
||||||
|
|
||||||
|
Quick start (from repo root, using a built Fun executable):
|
||||||
|
|
||||||
|
<pre>build_debug/fun examples/extensions/redis/basic_ping.fun</pre>
|
||||||
|
|
||||||
|
Troubleshooting
|
||||||
|
---------------
|
||||||
|
- Ensure a Redis server is reachable at the host/port you pass to `redis_connect`.
|
||||||
|
- If `redis_connect` returns `0`, verify the hiredis library and headers are installed and that Fun was configured with `-DFUN_WITH_REDIS=ON`.
|
||||||
|
|
@ -111,7 +111,7 @@ Opcode handlers organization:
|
||||||
|
|
||||||
- To keep vm.c readable, most opcode implementations are factored into small .c files included directly into vm.c (e.g., vm/core/load_const.c, vm/logic/and.c, vm/arrays/push.c, vm/math/abs.c, vm/os/thread_spawn.c, etc.).
|
- To keep vm.c readable, most opcode implementations are factored into small .c files included directly into vm.c (e.g., vm/core/load_const.c, vm/logic/and.c, vm/arrays/push.c, vm/math/abs.c, vm/os/thread_spawn.c, etc.).
|
||||||
- This is a deliberate “amalgamation” style: small single-purpose C units compiled as part of vm.c.
|
- This is a deliberate “amalgamation” style: small single-purpose C units compiled as part of vm.c.
|
||||||
- Optional subsystems (JSON, PCRE2, CURL, SQLite, libSQL, PC/SC, XML2, Tcl/Tk, Notcurses, INI, OpenSSL/LibreSSL crypto helpers, sockets, serial, OS helpers) are grouped under src/extensions and src/vm/<domain>/.
|
- Optional subsystems (JSON, PCRE2, CURL, SQLite, libSQL, PC/SC, XML2, Redis, Tcl/Tk, Notcurses, INI, OpenSSL/LibreSSL crypto helpers, sockets, serial, OS helpers) are grouped under src/extensions and src/vm/<domain>/.
|
||||||
|
|
||||||
Dispatch naming and visibility:
|
Dispatch naming and visibility:
|
||||||
|
|
||||||
|
|
@ -152,7 +152,7 @@ The VM is dynamically typed. Values carry a tag; operations check types at runti
|
||||||
- Maps: OP_MAKE_MAP/KEYS/VALUES/HAS_KEY.
|
- Maps: OP_MAKE_MAP/KEYS/VALUES/HAS_KEY.
|
||||||
- Conversions/reflection: OP_TO_NUMBER/TO_STRING/CAST/TYPEOF, OP_UCLAMP/SCLAMP.
|
- Conversions/reflection: OP_TO_NUMBER/TO_STRING/CAST/TYPEOF, OP_UCLAMP/SCLAMP.
|
||||||
- I/O and OS: OP_READ_FILE/WRITE_FILE/INPUT_LINE/ENV/PROC_RUN/PROC_SYSTEM/TIME_NOW_MS/CLOCK_MONO_MS/DATE_FORMAT/OS_LIST_DIR/RANDOM_NUMBER, sockets, serial.
|
- I/O and OS: OP_READ_FILE/WRITE_FILE/INPUT_LINE/ENV/PROC_RUN/PROC_SYSTEM/TIME_NOW_MS/CLOCK_MONO_MS/DATE_FORMAT/OS_LIST_DIR/RANDOM_NUMBER, sockets, serial.
|
||||||
- Extensions (optional): JSON, CURL, SQLite, libSQL, PC/SC, XML2, Tcl/Tk, Notcurses, INI, OpenSSL/LibreSSL.
|
- Extensions (optional): JSON, CURL, SQLite, libSQL, PC/SC, XML2, Redis, Tcl/Tk, Notcurses, INI, OpenSSL/LibreSSL.
|
||||||
|
|
||||||
Each handler enforces argument types and returns clear error messages via vm_raise_error on misuse.
|
Each handler enforces argument types and returns clear error messages via vm_raise_error on misuse.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -339,8 +339,9 @@ Enabled via CMake flags; each wraps a mature C library:
|
||||||
| **XML** | `FUN_WITH_XML2` | libxml2 | `xml_parse()`, `xml_root()`, `xml_name()`, `xml_text()` |
|
| **XML** | `FUN_WITH_XML2` | libxml2 | `xml_parse()`, `xml_root()`, `xml_name()`, `xml_text()` |
|
||||||
| **PC/SC** | `FUN_WITH_PCSC` | libpcsclite | `pcsc_establish()`, `pcsc_list_readers()`, `pcsc_connect()`, `pcsc_transmit()`, etc. |
|
| **PC/SC** | `FUN_WITH_PCSC` | libpcsclite | `pcsc_establish()`, `pcsc_list_readers()`, `pcsc_connect()`, `pcsc_transmit()`, etc. |
|
||||||
| **KCGI** | `FUN_WITH_KCGI` | libkcgi | `kcgi_parse()`, `kcgi_reply_start()`, `kcgi_write()`, `kcgi_end()` |
|
| **KCGI** | `FUN_WITH_KCGI` | libkcgi | `kcgi_parse()`, `kcgi_reply_start()`, `kcgi_write()`, `kcgi_end()` |
|
||||||
|
| **Redis** | `FUN_WITH_REDIS` | hiredis | `redis_connect()`, `redis_cmd()`, `redis_close()` |
|
||||||
|
|
||||||
Each extension also has a corresponding **stdlib wrapper class** in `lib/io/` or `lib/net/`:
|
Some extensions also have a corresponding **stdlib wrapper class** in `lib/io/` or `lib/net/`:
|
||||||
|
|
||||||
- `JSON` class (`lib/io/json.fun`)
|
- `JSON` class (`lib/io/json.fun`)
|
||||||
- `INI` class (`lib/io/ini.fun`)
|
- `INI` class (`lib/io/ini.fun`)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue