iodine 0.7.60 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/.clang-format +15 -0
  3. data/.rspec +1 -0
  4. data/.yardopts +1 -3
  5. data/CHANGELOG.md +132 -12
  6. data/Gemfile +5 -7
  7. data/LICENSE.txt +1 -1
  8. data/README.md +147 -671
  9. data/Rakefile +9 -16
  10. data/examples/chatroom.nru +9 -0
  11. data/examples/client +74 -0
  12. data/examples/config-hello.nru +9 -0
  13. data/examples/config-hello.ru +11 -0
  14. data/examples/config-hijack.ru +13 -0
  15. data/examples/config-shootout.ru +43 -0
  16. data/examples/config.nru +120 -0
  17. data/examples/config.rb +37 -0
  18. data/examples/config.ru +42 -53
  19. data/examples/fake +24 -0
  20. data/examples/hello_neorack +137 -0
  21. data/examples/routes.nru +29 -0
  22. data/examples/routes.ru +42 -0
  23. data/exe/iodine +218 -258
  24. data/ext/iodine/extconf.rb +27 -100
  25. data/ext/iodine/fio-stl.h +128336 -0
  26. data/ext/iodine/fio-stl.md +26396 -0
  27. data/ext/iodine/iodine.c +213 -1393
  28. data/ext/iodine/iodine.h +250 -55
  29. data/ext/iodine/iodine_arg_helper.h +329 -0
  30. data/ext/iodine/iodine_caller.h +234 -25
  31. data/ext/iodine/iodine_cli.h +434 -0
  32. data/ext/iodine/iodine_compression.h +312 -0
  33. data/ext/iodine/iodine_connection.h +3484 -34
  34. data/ext/iodine/iodine_core.h +363 -0
  35. data/ext/iodine/iodine_crypto.h +1441 -0
  36. data/ext/iodine/iodine_defer.h +225 -4
  37. data/ext/iodine/iodine_json.h +738 -4
  38. data/ext/iodine/iodine_listener.h +263 -0
  39. data/ext/iodine/iodine_log.h +297 -0
  40. data/ext/iodine/iodine_minimap.h +543 -0
  41. data/ext/iodine/iodine_mustache.h +461 -4
  42. data/ext/iodine/iodine_pubsub_eng.h +729 -0
  43. data/ext/iodine/iodine_pubsub_history.h +547 -0
  44. data/ext/iodine/iodine_pubsub_msg.h +228 -0
  45. data/ext/iodine/iodine_redis.h +322 -0
  46. data/ext/iodine/iodine_store.h +418 -16
  47. data/ext/iodine/iodine_threads.h +279 -0
  48. data/ext/iodine/iodine_tls.h +515 -6
  49. data/ext/iodine/iodine_utils.h +923 -0
  50. data/iodine.gemspec +37 -42
  51. data/lib/iodine/benchmark.rb +1690 -0
  52. data/lib/iodine/documentation.rb +3085 -0
  53. data/lib/iodine/version.rb +17 -1
  54. data/lib/iodine.rb +54 -255
  55. data/logo.svg +36 -0
  56. data/mustache.md +55 -0
  57. data/sig/iodine.rbs +4 -0
  58. metadata +58 -184
  59. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
  60. data/.github/workflows/ruby.yml +0 -42
  61. data/.gitignore +0 -21
  62. data/LIMITS.md +0 -41
  63. data/SPEC-PubSub-Draft.md +0 -159
  64. data/SPEC-WebSocket-Draft.md +0 -239
  65. data/bin/console +0 -22
  66. data/bin/info.md +0 -353
  67. data/bin/mustache_bench.rb +0 -100
  68. data/bin/poc/Gemfile.lock +0 -23
  69. data/bin/poc/README.md +0 -37
  70. data/bin/poc/config.ru +0 -66
  71. data/bin/poc/gemfile +0 -1
  72. data/bin/poc/www/index.html +0 -57
  73. data/examples/async_task.ru +0 -92
  74. data/examples/bates/README.md +0 -3
  75. data/examples/bates/config.ru +0 -342
  76. data/examples/bates/david+bold.pdf +0 -0
  77. data/examples/bates/public/drop-pdf.png +0 -0
  78. data/examples/bates/public/index.html +0 -600
  79. data/examples/echo.ru +0 -59
  80. data/examples/etag.ru +0 -16
  81. data/examples/hello.ru +0 -29
  82. data/examples/pubsub_engine.ru +0 -81
  83. data/examples/rack3.ru +0 -12
  84. data/examples/redis.ru +0 -70
  85. data/examples/shootout.ru +0 -73
  86. data/examples/sub-protocols.ru +0 -90
  87. data/examples/tcp_client.rb +0 -66
  88. data/examples/x-sendfile.ru +0 -14
  89. data/ext/iodine/fio.c +0 -11995
  90. data/ext/iodine/fio.h +0 -6371
  91. data/ext/iodine/fio_cli.c +0 -431
  92. data/ext/iodine/fio_cli.h +0 -189
  93. data/ext/iodine/fio_json_parser.h +0 -688
  94. data/ext/iodine/fio_siphash.c +0 -157
  95. data/ext/iodine/fio_siphash.h +0 -37
  96. data/ext/iodine/fio_tls.h +0 -129
  97. data/ext/iodine/fio_tls_missing.c +0 -649
  98. data/ext/iodine/fio_tls_openssl.c +0 -1106
  99. data/ext/iodine/fio_tmpfile.h +0 -50
  100. data/ext/iodine/fiobj.h +0 -44
  101. data/ext/iodine/fiobj4fio.h +0 -21
  102. data/ext/iodine/fiobj_ary.c +0 -333
  103. data/ext/iodine/fiobj_ary.h +0 -139
  104. data/ext/iodine/fiobj_data.c +0 -1187
  105. data/ext/iodine/fiobj_data.h +0 -167
  106. data/ext/iodine/fiobj_hash.c +0 -409
  107. data/ext/iodine/fiobj_hash.h +0 -176
  108. data/ext/iodine/fiobj_json.c +0 -622
  109. data/ext/iodine/fiobj_json.h +0 -68
  110. data/ext/iodine/fiobj_mem.h +0 -71
  111. data/ext/iodine/fiobj_mustache.c +0 -317
  112. data/ext/iodine/fiobj_mustache.h +0 -62
  113. data/ext/iodine/fiobj_numbers.c +0 -344
  114. data/ext/iodine/fiobj_numbers.h +0 -127
  115. data/ext/iodine/fiobj_str.c +0 -433
  116. data/ext/iodine/fiobj_str.h +0 -172
  117. data/ext/iodine/fiobject.c +0 -620
  118. data/ext/iodine/fiobject.h +0 -655
  119. data/ext/iodine/hpack.h +0 -1923
  120. data/ext/iodine/http.c +0 -2743
  121. data/ext/iodine/http.h +0 -1002
  122. data/ext/iodine/http1.c +0 -910
  123. data/ext/iodine/http1.h +0 -29
  124. data/ext/iodine/http1_parser.h +0 -878
  125. data/ext/iodine/http_internal.c +0 -1279
  126. data/ext/iodine/http_internal.h +0 -237
  127. data/ext/iodine/http_mime_parser.h +0 -350
  128. data/ext/iodine/iodine_caller.c +0 -230
  129. data/ext/iodine/iodine_connection.c +0 -941
  130. data/ext/iodine/iodine_defer.c +0 -420
  131. data/ext/iodine/iodine_fiobj2rb.h +0 -120
  132. data/ext/iodine/iodine_helpers.c +0 -282
  133. data/ext/iodine/iodine_helpers.h +0 -12
  134. data/ext/iodine/iodine_http.c +0 -1181
  135. data/ext/iodine/iodine_http.h +0 -23
  136. data/ext/iodine/iodine_json.c +0 -302
  137. data/ext/iodine/iodine_mustache.c +0 -567
  138. data/ext/iodine/iodine_pubsub.c +0 -580
  139. data/ext/iodine/iodine_pubsub.h +0 -26
  140. data/ext/iodine/iodine_rack_io.c +0 -273
  141. data/ext/iodine/iodine_rack_io.h +0 -20
  142. data/ext/iodine/iodine_store.c +0 -148
  143. data/ext/iodine/iodine_tcp.c +0 -346
  144. data/ext/iodine/iodine_tcp.h +0 -13
  145. data/ext/iodine/iodine_tls.c +0 -261
  146. data/ext/iodine/mustache_parser.h +0 -1546
  147. data/ext/iodine/redis_engine.c +0 -957
  148. data/ext/iodine/redis_engine.h +0 -79
  149. data/ext/iodine/resp_parser.h +0 -317
  150. data/ext/iodine/websocket_parser.h +0 -506
  151. data/ext/iodine/websockets.c +0 -743
  152. data/ext/iodine/websockets.h +0 -185
  153. data/lib/iodine/connection.rb +0 -61
  154. data/lib/iodine/json.rb +0 -42
  155. data/lib/iodine/mustache.rb +0 -113
  156. data/lib/iodine/pubsub.rb +0 -55
  157. data/lib/iodine/rack_utils.rb +0 -43
  158. data/lib/iodine/tls.rb +0 -16
  159. data/lib/rack/handler/iodine.rb +0 -33
  160. data/logo.png +0 -0
data/ext/iodine/iodine.h CHANGED
@@ -1,63 +1,258 @@
1
- #ifndef H_IODINE_H
2
- #define H_IODINE_H
3
-
4
- #include "ruby.h"
5
-
6
- #include "fio.h"
7
- #include "fio_tls.h"
8
- #include "fiobj.h"
9
- /* used for iodine_connect and iodine_listen routing */
10
- typedef struct {
11
- fio_str_info_s address;
12
- fio_str_info_s port;
13
- fio_str_info_s method;
14
- fio_str_info_s path;
15
- fio_str_info_s body;
16
- fio_str_info_s public;
17
- fio_str_info_s url;
18
- #ifndef __MINGW32__
19
- fio_tls_s *tls;
1
+ /**
2
+ * @file iodine.h
3
+ * @brief Main header file for the Iodine Ruby C extension.
4
+ *
5
+ * This file serves as the central include point for the Iodine HTTP/WebSocket
6
+ * server extension. It includes Ruby headers, defines global constants and
7
+ * symbols, configures the facil.io C STL library, and includes all Iodine
8
+ * sub-modules in the correct dependency order.
9
+ *
10
+ * Iodine is built on top of the facil.io C STL library (fio-stl.h) which
11
+ * provides the underlying IO reactor, HTTP server, WebSocket support, and
12
+ * various utilities.
13
+ */
14
+ #ifndef H___IODINE___H
15
+ #define H___IODINE___H
16
+ /* *****************************************************************************
17
+ facil.io
18
+ ***************************************************************************** */
19
+
20
+ /* Fix: __attribute__((weak)) doesn't work correctly on Windows PE/COFF
21
+ (MinGW DLLs) — weak symbols may not be properly allocated in the DLL's data
22
+ segment, causing segfaults when accessed (e.g., writing to FIO_LOG_LEVEL).
23
+ Since iodine compiles as a single translation unit, weak symbols are
24
+ unnecessary — override FIO_WEAK to empty before fio-stl.h is included. */
25
+ #if defined(_WIN32) || defined(__MINGW32__) || defined(__CYGWIN__)
26
+ #ifndef FIO_WEAK
27
+ #define FIO_WEAK /* empty — no weak attribute on Windows */
28
+ #endif
20
29
  #endif
21
- VALUE handler;
22
- FIOBJ headers;
23
- FIOBJ cookies;
24
- size_t max_headers;
25
- size_t max_body;
26
- intptr_t max_clients;
27
- size_t max_msg;
28
- uint8_t timeout;
29
- uint8_t ping;
30
- uint8_t log;
31
- enum {
32
- IODINE_SERVICE_RAW,
33
- IODINE_SERVICE_HTTP,
34
- IODINE_SERVICE_WS,
35
- } service;
36
- } iodine_connection_args_s;
37
30
 
38
- #include "iodine_caller.h"
39
- #include "iodine_connection.h"
40
- #include "iodine_defer.h"
41
- #include "iodine_helpers.h"
42
- #include "iodine_http.h"
43
- #include "iodine_json.h"
44
- #include "iodine_mustache.h"
45
- #include "iodine_pubsub.h"
46
- #include "iodine_rack_io.h"
47
- #include "iodine_store.h"
48
- #include "iodine_tcp.h"
49
- #include "iodine_tls.h"
31
+ #define FIO_LEAK_COUNTER 1
32
+ #include "fio-stl.h"
33
+
34
+ typedef unsigned long long fio_thread_t;
35
+ typedef int fio_thread_pid_t;
36
+
37
+ #if defined(_WIN32) || defined(__MINGW32__) || defined(__CYGWIN__)
38
+ #include <synchapi.h>
39
+ typedef CONDITION_VARIABLE fio_thread_cond_t;
40
+ #else
41
+ #include <pthread.h>
42
+ typedef pthread_cond_t fio_thread_cond_t;
43
+ #endif
44
+
45
+ #define FIO_MUSTACHE_LAMBDA_SUPPORT 1
46
+ #define FIO_THREADS_BYO 1
47
+ #define FIO_THREADS_FORK_BYO 1
48
+ #define FIO_MEMORY_ARENA_COUNT_MAX 4
49
+ #define FIO_EVERYTHING
50
+
51
+ #ifndef DEBUG
52
+ // #undef FIO_LEAK_COUNTER_SKIP_EXIT
53
+ // #define FIO_LEAK_COUNTER_SKIP_EXIT 1
54
+ /* Ruby doesn't always free everything pre-cleanup, so no point in counting */
55
+ // #undef FIO_LEAK_COUNTER
56
+ // #define FIO_LEAK_COUNTER 0
57
+ #endif
58
+
59
+ #include FIO_INCLUDE_FILE
60
+
61
+ #ifndef DEBUG
62
+ #endif
50
63
 
51
64
  /* *****************************************************************************
52
- Constants
65
+ Ruby
53
66
  ***************************************************************************** */
54
- extern VALUE IodineModule;
55
- extern VALUE IodineBaseModule;
56
- extern VALUE iodine_default_args;
57
- extern ID iodine_call_id;
58
- extern ID iodine_to_s_id;
59
67
 
60
- #define IODINE_RSTRINFO(rstr) \
61
- ((fio_str_info_s){.len = RSTRING_LEN(rstr), .data = RSTRING_PTR(rstr)})
68
+ #include <ruby.h>
69
+ #include <ruby/encoding.h>
70
+ #include <ruby/intern.h>
71
+ #include <ruby/io.h>
72
+ #include <ruby/thread.h>
62
73
 
74
+ /* ruby_thread_has_gvl_p() exists since Ruby 2.x but was only publicly declared
75
+ in ruby/thread.h starting with Ruby 4.0. On older Rubies, provide our own
76
+ declaration to avoid implicit-function-declaration errors on Windows/macOS.
77
+ On Ruby 4.0+, rb_thread_call_with_gvl() is GVL-lenient (safe to call whether
78
+ or not the GVL is held), so the check is unnecessary — but we still declare
79
+ it to keep the code unconditionally compilable. */
80
+ #if RUBY_API_VERSION_MAJOR < 4
81
+ int ruby_thread_has_gvl_p(void);
63
82
  #endif
83
+
84
+ /* *****************************************************************************
85
+ Ruby Method ID Constants
86
+
87
+ These IDs are cached for efficient Ruby method calls from C code.
88
+ They are initialized in Init_iodine() using IODINE_CONST_ID_STORE().
89
+ ***************************************************************************** */
90
+
91
+ /** @brief Cached Ruby method ID for `call` */
92
+ static ID IODINE_CALL_ID;
93
+ static ID IODINE_CLOSE_ID;
94
+ static ID IODINE_EACH_ID;
95
+ static ID IODINE_FILENO_ID;
96
+ static ID IODINE_NEW_ID;
97
+ static ID IODINE_CURRENT_ID;
98
+ static ID IODINE_JOIN_ID;
99
+ static ID IODINE_TO_PATH_ID;
100
+ static ID IODINE_TO_S_ID;
101
+ static ID IODINE_TO_JSON_ID;
102
+ static ID IODINE_RACK_HIJACK_ID;
103
+ static ID IODINE_ON_AUTHENTICATE_ID;
104
+ static ID IODINE_ON_AUTHENTICATE_SSE_ID;
105
+ static ID IODINE_ON_AUTHENTICATE_WEBSOCKET_ID;
106
+ static ID IODINE_ON_CLOSE_ID;
107
+ static ID IODINE_ON_DATA_ID;
108
+ static ID IODINE_ON_DRAINED_ID;
109
+ static ID IODINE_ON_EVENTSOURCE_ID;
110
+ static ID IODINE_ON_EVENTSOURCE_RECONNECT_ID;
111
+ static ID IODINE_ON_FINISH_ID;
112
+ static ID IODINE_ON_HTTP_ID;
113
+ static ID IODINE_ON_MESSAGE_ID;
114
+ static ID IODINE_ON_OPEN_ID;
115
+ static ID IODINE_ON_SHUTDOWN_ID;
116
+ static ID IODINE_ON_TIMEOUT_ID;
117
+ static ID IODINE_QUERY_ID;
118
+
119
+ static ID IODINE_INDEX_ID;
120
+ static ID IODINE_SHOW_ID;
121
+ // static ID IODINE_NEW_ID;
122
+ static ID IODINE_EDIT_ID;
123
+ static ID IODINE_CREATE_ID;
124
+ static ID IODINE_UPDATE_ID;
125
+ static ID IODINE_DELETE_ID;
126
+
127
+ /**
128
+ * Stores a Ruby method ID constant and protects its Symbol from GC.
129
+ *
130
+ * @param name The C variable name to store the ID in.
131
+ * @param value The Ruby method name as a C string.
132
+ */
133
+ #define IODINE_CONST_ID_STORE(name, value) \
134
+ name = rb_intern(value); \
135
+ STORE.hold(RB_ID2SYM(name));
136
+
137
+ static VALUE iodine_rb_IODINE;
138
+ static VALUE iodine_rb_IODINE_BASE;
139
+ static VALUE iodine_rb_IODINE_BASE_APP404;
140
+ static VALUE iodine_rb_IODINE_CONNECTION;
141
+ static VALUE iodine_rb_IODINE_PUBSUB;
142
+ static VALUE iodine_rb_IODINE_PUBSUB_ENG;
143
+ static VALUE iodine_rb_IODINE_PUBSUB_MSG;
144
+ static VALUE iodine_rb_IODINE_PUBSUB_SUB;
145
+ static VALUE iodine_rb_IODINE_TLS;
146
+ static rb_encoding *IodineUTF8Encoding;
147
+ static rb_encoding *IodineBinaryEncoding;
148
+
149
+ static VALUE IODINE_CONNECTION_ENV_TEMPLATE = Qnil;
150
+ static VALUE IODINE_CONNECTION_ENV_TEMPLATE_HTTPS = Qnil;
151
+ static VALUE IODINE_RACK_PROTOCOL_STR;
152
+ static VALUE IODINE_RACK_HIJACK_ID_SYM;
153
+ static VALUE IODINE_RACK_HIJACK_STR;
154
+ static VALUE IODINE_RACK_HIJACK_SYM;
155
+ static VALUE IODINE_RACK_UPGRADE_STR;
156
+ static VALUE IODINE_RACK_UPGRADE_Q_STR;
157
+ static VALUE IODINE_RACK_UPGRADE_WS_SYM;
158
+ static VALUE IODINE_RACK_UPGRADE_SSE_SYM;
159
+ static VALUE IODINE_RACK_AFTER_RPLY_STR;
160
+
161
+ /** Buffer size for raw TCP/IP connection data reads (16KB default). */
162
+ #ifndef IODINE_RAW_ON_DATA_READ_BUFFER
163
+ #define IODINE_RAW_ON_DATA_READ_BUFFER (1ULL << 14)
164
+ #endif
165
+
166
+ /**
167
+ * Returns true if the Ruby VALUE should be skipped for GC store operations.
168
+ * Immediate values (nil, true, false, fixnums) don't need GC protection.
169
+ */
170
+ #define IODINE_STORE_IS_SKIP(o) \
171
+ (!o || o == Qnil || o == Qtrue || o == Qfalse || TYPE(o) == RUBY_T_FIXNUM)
172
+
173
+ /**
174
+ * Converts a Ruby String to a fio_str_info_s struct.
175
+ * @note The Ruby String must remain valid while the struct is in use.
176
+ */
177
+ #define IODINE_RSTR_INFO(o) \
178
+ { .buf = RSTRING_PTR(o), .len = (size_t)RSTRING_LEN(o) }
179
+
180
+ /* *****************************************************************************
181
+ Deferring Ruby Blocks
182
+
183
+ Provides macros for scheduling Ruby blocks to run asynchronously in the
184
+ IO reactor's thread pool.
185
+ ***************************************************************************** */
186
+
187
+ /** Forward declaration for async block execution callback. */
188
+ static void iodine_defer_performe_once(void *block, void *ignr);
189
+
190
+ /**
191
+ * Schedules a Ruby block (Proc) to run asynchronously in the thread pool.
192
+ *
193
+ * The block is protected from GC until execution completes.
194
+ *
195
+ * @param blk A Ruby VALUE representing a Proc object.
196
+ */
197
+ #define IODINE_DEFER_BLOCK(blk) \
198
+ do { \
199
+ STORE.hold((blk)); \
200
+ fio_io_async(&IODINE_THREAD_POOL, \
201
+ iodine_defer_performe_once, \
202
+ (void *)(blk)); \
203
+ } while (0)
204
+
205
+ /* *****************************************************************************
206
+ Leak Counters
207
+ ***************************************************************************** */
208
+ FIO_LEAK_COUNTER_DEF(iodine_connection)
209
+ FIO_LEAK_COUNTER_DEF(iodine_minimap)
210
+ FIO_LEAK_COUNTER_DEF(iodine_mustache)
211
+ FIO_LEAK_COUNTER_DEF(iodine_pubsub_msg)
212
+ FIO_LEAK_COUNTER_DEF(iodine_pubsub_sub)
213
+ // FIO_LEAK_COUNTER_DEF(iodine_pubsub_eng) /* Ruby doesn't free */
214
+ FIO_LEAK_COUNTER_DEF(iodine_hmap)
215
+
216
+ /* *****************************************************************************
217
+ Common Iodine Helpers
218
+
219
+ Core infrastructure shared across all Iodine modules.
220
+ ***************************************************************************** */
221
+
222
+ /** The global IO reactor thread pool for async task execution. */
223
+ static fio_io_async_s IODINE_THREAD_POOL;
224
+
225
+ static VALUE iodine_handler_method_injection__inner(VALUE self,
226
+ VALUE handler,
227
+ bool is_middleware);
228
+ /* Core GC protection */
229
+ #include "iodine_store.h"
230
+ /* layer 1 helpers */
231
+ #include "iodine_arg_helper.h"
232
+ /* layer 2 helpers */
233
+ #include "iodine_caller.h"
234
+ #include "iodine_json.h"
235
+ #include "iodine_listener.h"
236
+ #include "iodine_pubsub_msg.h"
237
+ #include "iodine_utils.h"
238
+ /* layer 1 modules */
239
+ #include "iodine_cli.h"
240
+ #include "iodine_defer.h"
241
+ #include "iodine_log.h"
242
+ #include "iodine_minimap.h"
243
+ /* layer 2 modules */
244
+ #include "iodine_compression.h"
245
+ #include "iodine_crypto.h"
246
+ #include "iodine_mustache.h"
247
+ #include "iodine_pubsub_eng.h"
248
+ #include "iodine_pubsub_history.h"
249
+ #include "iodine_redis.h"
250
+ #include "iodine_threads.h"
251
+ #include "iodine_tls.h"
252
+ /* layer 3 modules */
253
+ #include "iodine_connection.h"
254
+
255
+ /* layer 4 modules */
256
+ #include "iodine_core.h"
257
+
258
+ #endif /* H___IODINE___H */
@@ -0,0 +1,329 @@
1
+ #ifndef H___IODINE_ARG_HELPER___H
2
+ #define H___IODINE_ARG_HELPER___H
3
+ #include "iodine.h"
4
+
5
+ /* *****************************************************************************
6
+ Ruby Multi-Argument Helper
7
+
8
+ Reads and validates method arguments (either "splat" or Hash Map).
9
+
10
+ iodine_rb2c_arg(argc, argv,
11
+ IODINE_ARG_RB(var_name, id_or_0, "name1", 1),
12
+ IODINE_ARG_STR(var_name, id_or_0, "name2", 0),
13
+ IODINE_ARG_NUM(var_name, id_or_0, "name3", 0),
14
+ IODINE_ARG_PROC(var_name, id_or_0, "name4", 0));
15
+
16
+ The `IODINE_ARG_XXX` macro arguments are as follows:
17
+
18
+ IODINE_ARG_XXX(target_varriable, id_or_0, named_argument, required)
19
+
20
+ Example:
21
+
22
+ fio_buf_info_s file_name = {0};
23
+ fio_buf_info_s file_content = {0};
24
+ VALUE on_yaml_block = Qnil;
25
+ iodine_rb2c_arg(argc, argv,
26
+ IODINE_ARG_STR(file_name, 0, "file", 0),
27
+ IODINE_ARG_STR(file_content, 0, "data", 0),
28
+ IODINE_ARG_PROC(on_yaml_block, 0, "on_yaml", 1));
29
+
30
+ For Ruby methods declared as:
31
+
32
+ static VALUE my_method(int argc, VALUE *argv, VALUE klass);
33
+ rb_define_singleton_method(klass, "method", my_method, -1);
34
+
35
+ ***************************************************************************** */
36
+
37
+ /* *****************************************************************************
38
+ Ruby arguments to C arguments
39
+ ***************************************************************************** */
40
+
41
+ typedef struct {
42
+ int expected_type;
43
+ union {
44
+ VALUE *rb;
45
+ fio_buf_info_s *buf;
46
+ fio_str_info_s *str;
47
+ int64_t *num;
48
+ size_t *zu;
49
+ int32_t *i32;
50
+ int16_t *i16;
51
+ int8_t *i8;
52
+ uint64_t *u64;
53
+ uint32_t *u32;
54
+ uint16_t *u16;
55
+ uint8_t *u8;
56
+ };
57
+ ID id;
58
+ fio_buf_info_s name;
59
+ int required;
60
+ } iodine_rb2c_arg_s;
61
+
62
+ #define IODINE_ARG_RB(t_var, id_, name_, required_) \
63
+ { \
64
+ .expected_type = 0, .rb = &(t_var), .id = (id_), \
65
+ .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \
66
+ }
67
+ #define IODINE_ARG_BUF(t_var, id_, name_, required_) \
68
+ { \
69
+ .expected_type = 1, .buf = &(t_var), .id = (id_), \
70
+ .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \
71
+ }
72
+ #define IODINE_ARG_STR(t_var, id_, name_, required_) \
73
+ { \
74
+ .expected_type = 2, .str = &(t_var), .id = (id_), \
75
+ .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \
76
+ }
77
+ #define IODINE_ARG_NUM(t_var, id_, name_, required_) \
78
+ { \
79
+ .expected_type = 3, .num = &(t_var), .id = (id_), \
80
+ .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \
81
+ }
82
+ #define IODINE_ARG_PROC(t_var, id_, name_, required_) \
83
+ { \
84
+ .expected_type = 4, .rb = &(t_var), .id = (id_), \
85
+ .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \
86
+ }
87
+ #define IODINE_ARG_SIZE_T(t_var, id_, name_, required_) \
88
+ { \
89
+ .expected_type = 5, .zu = &(t_var), .id = (id_), \
90
+ .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \
91
+ }
92
+ #define IODINE_ARG_I32(t_var, id_, name_, required_) \
93
+ { \
94
+ .expected_type = 6, .i32 = &(t_var), .id = (id_), \
95
+ .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \
96
+ }
97
+ #define IODINE_ARG_I16(t_var, id_, name_, required_) \
98
+ { \
99
+ .expected_type = 7, .i16 = &(t_var), .id = (id_), \
100
+ .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \
101
+ }
102
+ #define IODINE_ARG_I8(t_var, id_, name_, required_) \
103
+ { \
104
+ .expected_type = 8, .i8 = &(t_var), .id = (id_), \
105
+ .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \
106
+ }
107
+ #define IODINE_ARG_BOOL(t_var, id_, name_, required_) \
108
+ { \
109
+ .expected_type = 9, .u8 = &(t_var), .id = (id_), \
110
+ .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \
111
+ }
112
+ #define IODINE_ARG_U64(t_var, id_, name_, required_) \
113
+ { \
114
+ .expected_type = 10, .u64 = &(t_var), .id = (id_), \
115
+ .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \
116
+ }
117
+ #define IODINE_ARG_U32(t_var, id_, name_, required_) \
118
+ { \
119
+ .expected_type = 11, .u32 = &(t_var), .id = (id_), \
120
+ .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \
121
+ }
122
+ #define IODINE_ARG_U16(t_var, id_, name_, required_) \
123
+ { \
124
+ .expected_type = 12, .u16 = &(t_var), .id = (id_), \
125
+ .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \
126
+ }
127
+ #define IODINE_ARG_U8(t_var, id_, name_, required_) \
128
+ { \
129
+ .expected_type = 13, .u8 = &(t_var), .id = (id_), \
130
+ .name = FIO_BUF_INFO1(((char *)name_)), .required = (required_) \
131
+ }
132
+
133
+ /** Converts a Ruby Integer to a signed value in [min, max] or raises. */
134
+ static int64_t iodine___rb2c_arg2i(VALUE o,
135
+ int64_t min,
136
+ int64_t max,
137
+ const char *name) {
138
+ if (!RB_INTEGER_TYPE_P(o))
139
+ rb_raise(rb_eTypeError, "%s should be a Number", name);
140
+ long long v = NUM2LL(o); /* raises RangeError beyond int64 */
141
+ if (v < min || v > max)
142
+ rb_raise(rb_eRangeError, "%s out of range", name);
143
+ return (int64_t)v;
144
+ }
145
+
146
+ /** Converts a Ruby Integer to an unsigned value in [0, max] or raises. */
147
+ static uint64_t iodine___rb2c_arg2u(VALUE o, uint64_t max, const char *name) {
148
+ if (!RB_INTEGER_TYPE_P(o))
149
+ rb_raise(rb_eTypeError, "%s should be a Number", name);
150
+ uint64_t u = 0;
151
+ /* returns sign: -1/-2 when negative, +2 on uint64 overflow */
152
+ int s = rb_integer_pack(o,
153
+ &u,
154
+ 1,
155
+ sizeof(u),
156
+ 0,
157
+ INTEGER_PACK_LSWORD_FIRST |
158
+ INTEGER_PACK_NATIVE_BYTE_ORDER);
159
+ if (s < 0 || s > 1 || u > max)
160
+ rb_raise(rb_eRangeError, "%s out of range", name);
161
+ return u;
162
+ }
163
+
164
+ /** Reads and validates method arguments (either "splat" or Hash Map). */
165
+ static int iodine_rb2c_arg(int argc, const VALUE *argv, iodine_rb2c_arg_s *a) {
166
+ int i = 0;
167
+ VALUE tmp = Qnil;
168
+
169
+ #define IODINE_RB2C_STORE_ARG() \
170
+ if (!a[i].rb) \
171
+ goto too_many_arguments; \
172
+ if (tmp == Qnil && a[i].expected_type != 4) { \
173
+ if (a[i].required) \
174
+ goto missing_required; \
175
+ continue; \
176
+ } \
177
+ switch (a[i].expected_type) { \
178
+ case 0: \
179
+ a[i].rb[0] = tmp; \
180
+ STORE.cache(tmp); \
181
+ continue; \
182
+ case 1: \
183
+ if (RB_TYPE_P(tmp, RUBY_T_SYMBOL)) \
184
+ tmp = rb_sym2str(tmp); \
185
+ if (!RB_TYPE_P(tmp, RUBY_T_STRING)) \
186
+ rb_raise(rb_eTypeError, \
187
+ "%s should be a String (or Symbol)", \
188
+ a[i].name.buf); \
189
+ STORE.cache(tmp); \
190
+ a[i].buf[0] = FIO_BUF_INFO2(RSTRING_PTR(tmp), (size_t)RSTRING_LEN(tmp)); \
191
+ continue; \
192
+ case 2: \
193
+ if (RB_TYPE_P(tmp, RUBY_T_SYMBOL)) \
194
+ tmp = rb_sym2str(tmp); \
195
+ if (!RB_TYPE_P(tmp, RUBY_T_STRING)) \
196
+ rb_raise(rb_eTypeError, \
197
+ "%s should be a String (or Symbol)", \
198
+ a[i].name.buf); \
199
+ STORE.cache(tmp); \
200
+ a[i].str[0] = FIO_STR_INFO2(RSTRING_PTR(tmp), (size_t)RSTRING_LEN(tmp)); \
201
+ continue; \
202
+ case 3: \
203
+ a[i].num[0] = (int64_t) \
204
+ iodine___rb2c_arg2i(tmp, INT64_MIN, INT64_MAX, a[i].name.buf); \
205
+ continue; \
206
+ case 4: \
207
+ if (tmp == Qnil) { \
208
+ if (rb_block_given_p()) { \
209
+ tmp = rb_block_proc(); \
210
+ STORE.cache(tmp); \
211
+ } else if (a[i].required) \
212
+ goto missing_required; \
213
+ else \
214
+ continue; \
215
+ } else if (tmp != Qnil && !rb_respond_to(tmp, rb_intern2("call", 4))) \
216
+ rb_raise(rb_eArgError, "a callback object MUST respond to `call`"); \
217
+ a[i].rb[0] = tmp; \
218
+ STORE.cache(tmp); \
219
+ continue; \
220
+ case 5: \
221
+ a[i].zu[0] = (size_t) \
222
+ iodine___rb2c_arg2u(tmp, SIZE_MAX, a[i].name.buf); \
223
+ continue; \
224
+ case 6: \
225
+ a[i].i32[0] = (int32_t) \
226
+ iodine___rb2c_arg2i(tmp, INT32_MIN, INT32_MAX, a[i].name.buf); \
227
+ continue; \
228
+ case 7: \
229
+ a[i].i16[0] = (int16_t) \
230
+ iodine___rb2c_arg2i(tmp, INT16_MIN, INT16_MAX, a[i].name.buf); \
231
+ continue; \
232
+ case 8: \
233
+ a[i].i8[0] = (int8_t) \
234
+ iodine___rb2c_arg2i(tmp, INT8_MIN, INT8_MAX, a[i].name.buf); \
235
+ continue; \
236
+ case 9: /* nil keeps the default (handled above) */ \
237
+ if (tmp != Qtrue && tmp != Qfalse) \
238
+ rb_raise(rb_eTypeError, "%s should be a Boolean", a[i].name.buf); \
239
+ a[i].u8[0] = (tmp == Qtrue); \
240
+ continue; \
241
+ case 10: \
242
+ a[i].u64[0] = (uint64_t) \
243
+ iodine___rb2c_arg2u(tmp, UINT64_MAX, a[i].name.buf); \
244
+ continue; \
245
+ case 11: \
246
+ a[i].u32[0] = (uint32_t) \
247
+ iodine___rb2c_arg2u(tmp, UINT32_MAX, a[i].name.buf); \
248
+ continue; \
249
+ case 12: \
250
+ a[i].u16[0] = (uint16_t) \
251
+ iodine___rb2c_arg2u(tmp, UINT16_MAX, a[i].name.buf); \
252
+ continue; \
253
+ case 13: \
254
+ a[i].u8[0] = (uint8_t) \
255
+ iodine___rb2c_arg2u(tmp, UINT8_MAX, a[i].name.buf); \
256
+ continue; \
257
+ default: \
258
+ rb_raise(rb_eException, \
259
+ "C code failure - missing valid expected_type @ %s", \
260
+ a[i].name.buf); \
261
+ }
262
+ /* FIXME: allow partially named arguments. */
263
+ if (argc) {
264
+ --argc;
265
+ for (; i < argc; ++i) {
266
+ /* unnamed parameters (list of parameters), excluding last parameter */
267
+ tmp = argv[i];
268
+ IODINE_RB2C_STORE_ARG();
269
+ }
270
+ /* last parameter is special, as it may be a named parameter Hash */
271
+ tmp = argv[argc];
272
+ i = argc; /* does `for` add even after condition breaks loop? no, so why? */
273
+
274
+ if (RB_TYPE_P(tmp, RUBY_T_HASH)) { /* named parameters (hash table) */
275
+ VALUE tbl = tmp;
276
+ if (a[i].expected_type == 0)
277
+ a[i].rb[0] = tmp;
278
+ for (; a[i].rb; ++i) {
279
+ tmp = rb_hash_aref(
280
+ tbl,
281
+ rb_id2sym(a[i].id ? a[i].id
282
+ : rb_intern2(a[i].name.buf, a[i].name.len)));
283
+ IODINE_RB2C_STORE_ARG();
284
+ }
285
+ return 0;
286
+ }
287
+ if (a[i].rb) {
288
+ do {
289
+ IODINE_RB2C_STORE_ARG();
290
+ } while (0);
291
+ ++i;
292
+ }
293
+ tmp = Qnil;
294
+ }
295
+ for (; a[i].rb; ++i) {
296
+ /* possible leftover last parameter */
297
+ if (a[i].expected_type == 4) {
298
+ IODINE_RB2C_STORE_ARG();
299
+ continue;
300
+ }
301
+ if (a[i].required)
302
+ goto missing_required;
303
+ }
304
+ return 0;
305
+
306
+ #undef IODINE_RB2C_STORE_ARG
307
+ too_many_arguments:
308
+ rb_raise(rb_eArgError, "too many arguments in method call.");
309
+ return -1;
310
+ missing_required:
311
+ rb_raise(rb_eArgError, "missing required argument %s.", a[i].name.buf);
312
+ return -1;
313
+ }
314
+
315
+ /**
316
+ * Reads and validates method arguments (either "splat" or Hash Map).
317
+ *
318
+ * Use:
319
+ *
320
+ * iodine_rb2c_arg(argc, argv,
321
+ * IODINE_ARG_RB(var_name, id_or_0, "name1", 1),
322
+ * IODINE_ARG_STR(var_name, id_or_0, "name2", 0),
323
+ * IODINE_ARG_NUM(var_name, id_or_0, "name3", 0),
324
+ * IODINE_ARG_PROC(var_name, id_or_0, "name4", 0));
325
+ */
326
+ #define iodine_rb2c_arg(argc, argv, ...) \
327
+ iodine_rb2c_arg(argc, argv, (iodine_rb2c_arg_s[]){__VA_ARGS__, {0}})
328
+
329
+ #endif /* H___IODINE_ARG_HELPER___H */