Fun API Documentation 0.42.1
The programming language that makes you have fun!
Loading...
Searching...
No Matches
redis.c
Go to the documentation of this file.
1/*
2 * This file is part of the Fun programming language.
3 * https://fun-lang.xyz/
4 *
5 * Copyright 2026 Johannes Findeisen <you@hanez.org>
6 * Licensed under the terms of the Apache-2.0 license.
7 * https://opensource.org/license/apache-2-0
8 */
9
10/**
11 * @file redis.c
12 * @brief Hiredis handle registry and reply mapping helpers for the Fun VM.
13 *
14 * This translation unit provides two small building blocks used by the Redis
15 * opcodes (implemented under src/vm/redis/*.c) and included from src/vm.c:
16 *
17 * 1) A process-local registry that assigns monotonically increasing positive
18 * integer identifiers to hiredis connection pointers (redisContext*).
19 * VM opcodes pass integer ids on the stack instead of raw pointers, keeping
20 * the bytecode portable and preventing accidental misuse of pointers.
21 *
22 * 2) Utilities to convert hiredis reply objects (redisReply) into Fun VM
23 * Value instances, recursively mapping arrays and supporting basic numeric
24 * and string types.
25 *
26 * Build-time feature flag
27 * -----------------------
28 * The code is compiled only when the CMake option FUN_WITH_REDIS is enabled
29 * (i.e., the preprocessor symbol FUN_WITH_REDIS is defined). When disabled,
30 * this file contributes no symbols and the corresponding opcodes are compiled
31 * into stubs that return neutral values.
32 *
33 * Ownership and lifetime
34 * ----------------------
35 * - The registry does NOT open or close Redis connections by itself; it merely
36 * stores pointers created elsewhere (e.g., via redisConnectWithTimeout()).
37 * - Adding an entry does not transfer ownership of the redisContext. Callers
38 * remain responsible for invoking redisFree() at the appropriate time.
39 * - Removing an entry from the registry does NOT free the connection; it only
40 * forgets the mapping between id and pointer. The connect/close opcodes take
41 * care of proper ownership transitions.
42 * - Integer identifiers are monotonically increasing per process. Once a
43 * handle id is deleted, it will not be reused within the same process
44 * lifetime.
45 *
46 * Error handling
47 * --------------
48 * Functions here perform basic validation/allocations only. Allocation
49 * failures return NULL (for lookups/additions) or are silently ignored (for
50 * deletions of non-existent ids). No hiredis API calls are made here, so no
51 * hiredis error codes are produced by this module itself.
52 *
53 * Thread-safety
54 * -------------
55 * The registry is a simple singly-linked list with no synchronization. It is
56 * NOT thread-safe. If the VM uses Redis from multiple threads, the caller must
57 * provide external synchronization around calls to these helpers.
58 *
59 * Example
60 * -------
61 * @code{.c}
62 * // Open a hiredis connection elsewhere:
63 * struct timeval tv = { .tv_sec = 2, .tv_usec = 0 };
64 * redisContext *ctx = redisConnectWithTimeout("127.0.0.1", 6379, tv);
65 * if (ctx && !ctx->err) {
66 * // Register and get an id:
67 * RedisHandle *h = redis_reg_add(ctx);
68 * int id = h ? h->id : -1;
69 *
70 * // Later look it up:
71 * RedisHandle *same = redis_reg_get(id);
72 * if (same) {
73 * // use same->ctx with hiredis APIs
74 * }
75 *
76 * // When finished, drop the registry entry and free manually:
77 * redis_reg_del(id);
78 * redisFree(ctx);
79 * }
80 * @endcode
81 */
82
83#ifdef FUN_WITH_REDIS
84#include <hiredis/hiredis.h>
85#include <stdlib.h>
86#include <string.h>
87
88/* Forward declarations from the VM (available in the same TU via includes) */
89static Value hiredis_reply_to_value(const redisReply *r);
90
91/**
92 * @brief Node in a singly-linked list of registered Redis handles.
93 *
94 * Associates a monotonically increasing positive integer identifier with a raw
95 * hiredis connection pointer. The list head is stored in a file-static global
96 * (g_redis_handles).
97 */
98typedef struct RedisHandle {
99 int id; /**< Positive identifier assigned by the registry. */
100 redisContext *ctx; /**< Opaque pointer to a hiredis connection. */
101 struct RedisHandle *next;/**< Next entry in the singly-linked list. */
102} RedisHandle;
103
104/** @brief Global head of the Redis handle list (NULL denotes empty list). */
105static RedisHandle *g_redis_handles = NULL;
106/** @brief Next positive identifier to assign to a newly added handle. */
107static int g_redis_next_id = 1;
108
109/**
110 * @brief Add a hiredis connection handle to the registry.
111 *
112 * Allocates a new list node, assigns a fresh positive id, and prepends it to
113 * the internal registry list. Ownership of the redisContext remains with the
114 * caller; this registry does not free it during deletion.
115 *
116 * @param ctx Valid pointer to an opened hiredis connection.
117 * @return Pointer to the newly created RedisHandle on success; NULL on
118 * allocation failure. The returned pointer remains owned by the
119 * registry; do not free it directly.
120 */
121static RedisHandle *redis_reg_add(redisContext *ctx) {
122 RedisHandle *h = (RedisHandle *)calloc(1, sizeof(RedisHandle));
123 if (!h) return NULL;
124 h->id = g_redis_next_id++;
125 h->ctx = ctx;
126 h->next = g_redis_handles;
127 g_redis_handles = h;
128 return h;
129}
130
131/**
132 * @brief Look up a registered Redis handle by id.
133 *
134 * Performs a linear search over the internal list to find a matching id.
135 *
136 * @param id Positive identifier previously returned by redis_reg_add().
137 * @return Pointer to the RedisHandle entry if found; NULL otherwise.
138 *
139 * @note The returned pointer is owned by the registry and must not be freed by
140 * the caller.
141 */
142static RedisHandle *redis_reg_get(int id) {
143 for (RedisHandle *p = g_redis_handles; p; p = p->next)
144 if (p->id == id) return p;
145 return NULL;
146}
147
148/**
149 * @brief Remove a Redis handle entry from the registry.
150 *
151 * Deletes the list node associated with the given id.
152 *
153 * @param id Positive identifier of the entry to remove.
154 *
155 * @note This function does not free the underlying redisContext; the caller is
156 * responsible for calling redisFree() if appropriate.
157 * @note If the id does not exist, the function is a no-op.
158 */
159static void redis_reg_del(int id) {
160 RedisHandle **pp = &g_redis_handles;
161 while (*pp) {
162 if ((*pp)->id == id) {
163 RedisHandle *d = *pp;
164 *pp = d->next;
165 free(d);
166 return;
167 }
168 pp = &(*pp)->next;
169 }
170}
171
172/**
173 * @brief Convert a hiredis reply to a Fun Value.
174 *
175 * Recursively maps hiredis reply types to the closest Fun representation:
176 * - REDIS_REPLY_STRING / STATUS -> string
177 * - REDIS_REPLY_INTEGER -> int
178 * - REDIS_REPLY_NIL -> nil
179 * - REDIS_REPLY_ARRAY -> array of recursively converted values
180 * - REDIS_REPLY_DOUBLE (if available) -> float
181 * - REDIS_REPLY_ERROR / default -> string (error text or "ERR")
182 *
183 * @param r Non-owning pointer to a redisReply.
184 * @return Value converted from the reply. For NULL replies, returns nil.
185 */
186static Value hiredis_reply_to_value(const redisReply *r) {
187 if (!r) return make_nil();
188 switch (r->type) {
189 case REDIS_REPLY_STRING:
190 case REDIS_REPLY_STATUS:
191 return make_string(r->str ? r->str : "");
192 case REDIS_REPLY_INTEGER:
193 return make_int((int64_t)r->integer);
194 case REDIS_REPLY_NIL:
195 return make_nil();
196 case REDIS_REPLY_ARRAY: {
197 int n = (int)r->elements;
198 if (n <= 0) {
199 return make_array_from_values(NULL, 0);
200 }
201 Value *items = (Value *)calloc((size_t)n, sizeof(Value));
202 if (!items) return make_array_from_values(NULL, 0);
203 for (int i = 0; i < n; i++) {
204 items[i] = hiredis_reply_to_value(r->element[i]);
205 }
206 Value arr = make_array_from_values(items, n);
207 for (int i = 0; i < n; i++) free_value(items[i]);
208 free(items);
209 return arr;
210 }
211#ifdef REDIS_REPLY_DOUBLE
212 case REDIS_REPLY_DOUBLE:
213 return make_float(r->dval);
214#endif
215 case REDIS_REPLY_ERROR:
216 default:
217 return make_string(r->str ? r->str : "ERR");
218 }
219}
220
221#endif /* FUN_WITH_REDIS */
Tagged union representing a Fun value.
Definition value.h:68
Value make_nil(void)
Construct a nil Value.
Definition value.c:126
Value make_string(const char *s)
Construct a string Value by duplicating the given C string.
Definition value.c:95
void free_value(Value v)
Free dynamic storage owned by a Value.
Definition value.c:517
Value make_float(double v)
Construct a Value representing a double-precision float.
Definition value.c:64
Value make_int(int64_t v)
Construct a Value representing a 64-bit integer.
Definition value.c:51
Value make_array_from_values(const Value *vals, int count)
Create an array Value by copying items from an input span.
Definition value.c:142