Skip to main content

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}