code_native/lib.rs
1//! `code-native` — Helper crate for writing Code language native modules in Rust.
2//!
3//! This crate eliminates the boilerplate required to create native `.so`
4//! modules for the Code language. It re-exports all C-ABI types, provides
5//! safe value constructors and field-access helpers, and ships the
6//! [`code_module!`] macro that generates the required `#[no_mangle]`
7//! entry-points (`code_module_abi_version` + `code_module_init`).
8//!
9//! # Quick Start
10//!
11//! ```rust,ignore
12//! use code_native::*;
13//!
14//! unsafe extern "C" fn handle_add(particle: CodeValue) -> CodeValue {
15//! let a = read_field_number(&particle, "a");
16//! let b = read_field_number(&particle, "b");
17//! code_object(vec![
18//! code_field("_class", code_string("AddResult")),
19//! code_field("result", code_number(a + b)),
20//! ])
21//! }
22//!
23//! code_module! {
24//! vars: [
25//! "PI" => code_number(3.14159),
26//! ],
27//! types: [
28//! "Add" [("a", "Number"), ("b", "Number")],
29//! ],
30//! handlers: [
31//! "Add" => handle_add,
32//! ],
33//! emissions: [],
34//! }
35//! ```
36//!
37//! Compile with:
38//! ```bash
39//! cargo build -p code-native
40//! rustc --edition 2021 --crate-type cdylib \
41//! --extern code_native=target/debug/libcode_native.rlib \
42//! -o mymodule.so mymodule.rs
43//! ```
44
45use std::ffi::{c_char, c_void, CStr, CString};
46use std::ptr;
47use std::sync::atomic::{AtomicPtr, Ordering};
48
49// ===========================================================================
50// C-ABI contract — a synced, drift-guarded vendored copy of `code-abi` (kept
51// unpublished on purpose; see `abi` module doc comment for why this crate
52// can't depend on it directly and still be published to crates.io).
53// ===========================================================================
54
55mod abi;
56pub use abi::*;
57
58// ===========================================================================
59// Value builders
60// ===========================================================================
61
62/// Create a Number value.
63pub fn code_number(n: f64) -> CodeValue {
64 CodeValue {
65 tag: CODE_TAG_NUMBER,
66 number: n,
67 string: ptr::null(),
68 boolean: 0,
69 fields: ptr::null(),
70 field_count: 0,
71 elements: ptr::null(),
72 element_count: 0,
73 }
74}
75
76/// Create a String value. The string is leaked into a C-compatible pointer
77/// that lives for the remainder of the process — fine for return values and
78/// module descriptors.
79pub fn code_string(s: &str) -> CodeValue {
80 CodeValue {
81 tag: CODE_TAG_STRING,
82 number: 0.0,
83 string: leak_str(s),
84 boolean: 0,
85 fields: ptr::null(),
86 field_count: 0,
87 elements: ptr::null(),
88 element_count: 0,
89 }
90}
91
92/// Create a String value from a raw `*const c_char` pointer.
93///
94/// Use this when you already hold a C string pointer (e.g. a `c"..."` literal
95/// or a pointer received from the runtime).
96pub fn code_string_raw(ptr: *const c_char) -> CodeValue {
97 CodeValue {
98 tag: CODE_TAG_STRING,
99 number: 0.0,
100 string: ptr,
101 boolean: 0,
102 fields: ptr::null(),
103 field_count: 0,
104 elements: ptr::null(),
105 element_count: 0,
106 }
107}
108
109/// Create a Boolean value.
110pub fn code_boolean(b: bool) -> CodeValue {
111 CodeValue {
112 tag: CODE_TAG_BOOLEAN,
113 number: 0.0,
114 string: ptr::null(),
115 boolean: if b { 1 } else { 0 },
116 fields: ptr::null(),
117 field_count: 0,
118 elements: ptr::null(),
119 element_count: 0,
120 }
121}
122
123/// Create a Null value.
124pub fn code_null() -> CodeValue {
125 CodeValue {
126 tag: CODE_TAG_NULL,
127 number: 0.0,
128 string: ptr::null(),
129 boolean: 0,
130 fields: ptr::null(),
131 field_count: 0,
132 elements: ptr::null(),
133 element_count: 0,
134 }
135}
136
137/// Create an Object value from a `Vec` of fields.
138///
139/// The vector is leaked so the field pointer remains valid.
140pub fn code_object(fields: Vec<CodeField>) -> CodeValue {
141 let boxed = fields.into_boxed_slice();
142 let count = boxed.len() as u32;
143 let ptr = Box::leak(boxed).as_ptr();
144 CodeValue {
145 tag: CODE_TAG_OBJECT,
146 number: 0.0,
147 string: ptr::null(),
148 boolean: 0,
149 fields: ptr,
150 field_count: count,
151 elements: ptr::null(),
152 element_count: 0,
153 }
154}
155
156/// Create an Array value from a `Vec` of elements.
157///
158/// The vector is leaked so the element pointer remains valid.
159pub fn code_array(elements: Vec<CodeValue>) -> CodeValue {
160 let boxed = elements.into_boxed_slice();
161 let count = boxed.len() as u32;
162 let ptr = Box::leak(boxed).as_ptr();
163 CodeValue {
164 tag: CODE_TAG_ARRAY,
165 number: 0.0,
166 string: ptr::null(),
167 boolean: 0,
168 fields: ptr::null(),
169 field_count: 0,
170 elements: ptr,
171 element_count: count,
172 }
173}
174
175/// Create a single object field. The name is leaked.
176pub fn code_field(name: &str, value: CodeValue) -> CodeField {
177 CodeField {
178 name: leak_str(name),
179 value,
180 }
181}
182
183// ===========================================================================
184// Reading helpers (for use in native function implementations)
185// ===========================================================================
186
187/// Read a string from an `CodeValue`, returning `""` if the tag is wrong or
188/// the pointer is null.
189///
190/// # Safety
191/// The `string` pointer inside `v` must be valid if `v.tag == CODE_TAG_STRING`.
192pub unsafe fn read_str<'a>(v: &CodeValue) -> &'a str {
193 if v.tag != CODE_TAG_STRING || v.string.is_null() {
194 return "";
195 }
196 CStr::from_ptr(v.string).to_str().unwrap_or("")
197}
198
199/// Read a number from an `CodeValue`, returning `0.0` if the tag is wrong.
200pub fn read_number(v: &CodeValue) -> f64 {
201 if v.tag != CODE_TAG_NUMBER {
202 return 0.0;
203 }
204 v.number
205}
206
207/// Read a boolean from an `CodeValue`, returning `false` if the tag is wrong.
208pub fn read_boolean(v: &CodeValue) -> bool {
209 if v.tag != CODE_TAG_BOOLEAN {
210 return false;
211 }
212 v.boolean != 0
213}
214
215/// Look up a field by name inside an Object `CodeValue`.
216///
217/// Returns `None` if the value is not an object or the field is not found.
218///
219/// # Safety
220/// The `fields` pointer and field name pointers must be valid.
221pub unsafe fn read_field<'a>(v: &CodeValue, name: &str) -> Option<&'a CodeValue> {
222 if v.tag != CODE_TAG_OBJECT || v.fields.is_null() {
223 return None;
224 }
225 for i in 0..v.field_count as usize {
226 let field = &*v.fields.add(i);
227 if !field.name.is_null() {
228 let field_name = CStr::from_ptr(field.name).to_str().unwrap_or("");
229 if field_name == name {
230 return Some(&field.value);
231 }
232 }
233 }
234 None
235}
236
237/// Convenience: read a string field from an object by name.
238/// Returns `""` if the field doesn't exist or isn't a string.
239///
240/// # Safety
241/// All field pointers must be valid.
242pub unsafe fn read_field_str<'a>(v: &CodeValue, name: &str) -> &'a str {
243 match read_field(v, name) {
244 Some(fv) => read_str(fv),
245 None => "",
246 }
247}
248
249/// Convenience: read a number field from an object by name.
250/// Returns `0.0` if the field doesn't exist or isn't a number.
251///
252/// # Safety
253/// All field pointers must be valid.
254pub unsafe fn read_field_number(v: &CodeValue, name: &str) -> f64 {
255 match read_field(v, name) {
256 Some(fv) => read_number(fv),
257 None => 0.0,
258 }
259}
260
261/// Convenience: read a boolean field from an object by name.
262/// Returns `false` if the field doesn't exist or isn't a boolean.
263///
264/// # Safety
265/// All field pointers must be valid.
266pub unsafe fn read_field_bool(v: &CodeValue, name: &str) -> bool {
267 match read_field(v, name) {
268 Some(fv) => read_boolean(fv),
269 None => false,
270 }
271}
272
273// ===========================================================================
274// String helpers
275// ===========================================================================
276
277/// Leak a Rust `&str` into a `*const c_char` that lives forever.
278///
279/// This is the standard way to produce C strings for the ABI. The memory is
280/// never freed — acceptable for module descriptors and return values.
281pub fn leak_str(s: &str) -> *const c_char {
282 CString::new(s)
283 .unwrap_or_else(|_| CString::new("").unwrap())
284 .into_raw() as *const c_char
285}
286
287// ===========================================================================
288// Emit callback (set by host, used by native modules)
289// ===========================================================================
290
291/// Global emit function pointer, set by the host via `code_module_set_emit`.
292#[doc(hidden)]
293pub static EMIT_FN_PTR: AtomicPtr<()> = AtomicPtr::new(ptr::null_mut());
294/// Global emit context pointer, set by the host via `code_module_set_emit`.
295#[doc(hidden)]
296pub static EMIT_CTX_PTR: AtomicPtr<c_void> = AtomicPtr::new(ptr::null_mut());
297
298/// Emit a particle to the host runtime.
299///
300/// The particle must be an object with a `_class` string field.
301/// If `code_module_set_emit` has not been called, this is a no-op.
302///
303/// Thread-safe: may be called from any thread.
304pub fn code_emit(particle: CodeValue) {
305 let fn_ptr = EMIT_FN_PTR.load(Ordering::Acquire);
306 if fn_ptr.is_null() {
307 return;
308 }
309 let ctx = EMIT_CTX_PTR.load(Ordering::Acquire);
310 let func: CodeEmitFn = unsafe { std::mem::transmute(fn_ptr) };
311 unsafe { func(ctx, particle) };
312}
313
314/// Build and emit a `Log` particle.
315///
316/// Convenience helper for native modules:
317/// ```rust,ignore
318/// code_emit_log("my-module", "Info", "Server started on port 3000");
319/// ```
320pub fn code_emit_log(source: &str, level: &str, message: &str) {
321 code_emit(code_object(vec![
322 code_field("_class", code_string("Log")),
323 code_field("source", code_string(source)),
324 code_field("level", code_string(level)),
325 code_field("message", code_string(message)),
326 ]));
327}
328
329/// Build and emit an `Exception` particle.
330///
331/// Convenience helper for native modules:
332/// ```rust,ignore
333/// code_emit_exception("my-module", "Something went wrong");
334/// ```
335pub fn code_emit_exception(source: &str, message: &str) {
336 code_emit(code_object(vec![
337 code_field("_class", code_string("Exception")),
338 code_field("source", code_string(source)),
339 code_field("message", code_string(message)),
340 ]));
341}
342
343// ===========================================================================
344// code_module! macro
345// ===========================================================================
346
347/// Declare an Code native module.
348///
349/// Generates the required `#[no_mangle]` C symbols:
350/// - `code_module_abi_version() -> u32`
351/// - `code_module_init() -> *const CodeModuleDesc`
352/// - `code_module_set_emit(fn, ctx)` — called by the host to provide the emit callback.
353///
354/// # Syntax
355///
356/// ```rust,ignore
357/// code_module! {
358/// vars: [
359/// "NAME" => value_expr,
360/// ],
361/// types: [
362/// "TypeName" [
363/// ("field", "FieldType"),
364/// ],
365/// ],
366/// handlers: [
367/// "ClassName" => handler_fn_ident,
368/// ],
369/// emissions: [
370/// "Log" => "base",
371/// "Exception" => "base",
372/// ],
373/// }
374/// ```
375///
376/// All four sections are required but may be empty (`[]`).
377#[macro_export]
378macro_rules! code_module {
379 (
380 vars: [ $( $var_name:literal => $var_value:expr ),* $(,)? ],
381 types: [ $( $type_name:literal [ $( ( $field_name:literal , $field_type:literal $( , $field_optional:literal )? ) ),* $(,)? ] ),* $(,)? ],
382 handlers: [ $( $handler_class:literal => $handler_fn:expr ),* $(,)? ],
383 emissions: [ $( $emit_class:literal => $emit_target:literal ),* $(,)? ]
384 $(,)?
385 ) => {
386 #[no_mangle]
387 pub extern "C" fn code_module_abi_version() -> u32 {
388 $crate::CODE_ABI_VERSION
389 }
390
391 #[no_mangle]
392 pub unsafe extern "C" fn code_module_set_emit(
393 emit_fn: $crate::CodeEmitFn,
394 context: *mut std::ffi::c_void,
395 ) {
396 $crate::EMIT_FN_PTR.store(emit_fn as *mut (), std::sync::atomic::Ordering::Release);
397 $crate::EMIT_CTX_PTR.store(context, std::sync::atomic::Ordering::Release);
398 }
399
400 #[no_mangle]
401 pub extern "C" fn code_module_init() -> *const $crate::CodeModuleDesc {
402 // Build exported variables
403 let vars: Vec<$crate::CodeExportVar> = vec![
404 $( $crate::CodeExportVar {
405 name: $crate::leak_str($var_name),
406 value: $var_value,
407 }, )*
408 ];
409
410 // Build exported types (each type has its own field array)
411 let types: Vec<$crate::CodeExportType> = vec![
412 $( {
413 let fields: Vec<$crate::CodeTypeField> = vec![
414 $( $crate::CodeTypeField {
415 name: $crate::leak_str($field_name),
416 type_name: $crate::leak_str($field_type),
417 is_optional: { 0u8 $( + ($field_optional as u8) )? },
418 }, )*
419 ];
420 let fields_leaked = Box::leak(fields.into_boxed_slice());
421 $crate::CodeExportType {
422 name: $crate::leak_str($type_name),
423 fields: fields_leaked.as_ptr(),
424 field_count: fields_leaked.len() as u32,
425 }
426 }, )*
427 ];
428
429 // Build exported handlers
430 let handlers: Vec<$crate::CodeExportHandler> = vec![
431 $( $crate::CodeExportHandler {
432 class_name: $crate::leak_str($handler_class),
433 handler: $handler_fn,
434 }, )*
435 ];
436
437 // Build emission declarations
438 let emissions: Vec<$crate::CodeEmission> = vec![
439 $( $crate::CodeEmission {
440 class_name: $crate::leak_str($emit_class),
441 target: {
442 // Map target string to constant
443 match $emit_target {
444 "base" => $crate::CODE_EMIT_TARGET_BASE,
445 _ => $crate::CODE_EMIT_TARGET_BASE, // default
446 }
447 },
448 }, )*
449 ];
450
451 // Leak all slices so pointers survive the return
452 let vars_leaked = Box::leak(vars.into_boxed_slice());
453 let types_leaked = Box::leak(types.into_boxed_slice());
454 let handlers_leaked = Box::leak(handlers.into_boxed_slice());
455 let emissions_leaked = Box::leak(emissions.into_boxed_slice());
456
457 let desc = Box::new($crate::CodeModuleDesc {
458 abi_version: $crate::CODE_ABI_VERSION,
459 vars: vars_leaked.as_ptr(),
460 var_count: vars_leaked.len() as u32,
461 handlers: handlers_leaked.as_ptr(),
462 handler_count: handlers_leaked.len() as u32,
463 types: types_leaked.as_ptr(),
464 type_count: types_leaked.len() as u32,
465 emissions: emissions_leaked.as_ptr(),
466 emission_count: emissions_leaked.len() as u32,
467 });
468
469 Box::leak(desc)
470 }
471 };
472}