Skip to main content

rexlang_engine/libraries/
library.rs

1use rexlang_typesystem::{AdtDecl, Type, collect_adts_in_types};
2
3use crate::EvaluatorRef;
4use crate::engine::{AsyncHandler, Export, Handler, NativeFuture};
5use crate::{Engine, EngineError, Pointer, RexAdt};
6
7/// A staged host library that you build up in Rust and later inject into an [`Engine`].
8///
9/// `Library` is the host-side representation of a Rex module. It lets embedders collect:
10///
11/// - Rex declarations such as `pub type ...`
12/// - typed Rust handlers via [`Library::export`] / [`Library::export_async`]
13/// - pointer-level native handlers via [`Library::export_native`] /
14///   [`Library::export_native_async`]
15///
16/// Once the library is assembled, pass it to [`Engine::inject_library`] to make it importable
17/// from Rex code.
18///
19/// This type is intentionally mutable and staged: you can build it incrementally, inspect its
20/// [`Library::declarations`] and [`Library::exports`], transform them, and only inject it once you
21/// are satisfied with the final module shape.
22///
23/// # Examples
24///
25/// ```rust,ignore
26/// use rexlang_engine::{Engine, Library};
27///
28/// let mut engine = Engine::with_prelude(()).unwrap();
29/// engine.add_default_resolvers();
30///
31/// let mut math = Library::new("acme.math");
32/// math.export("inc", |_state: &(), x: i32| Ok(x + 1)).unwrap();
33/// math.add_declaration("pub type Sign = Positive | Negative").unwrap();
34///
35/// engine.inject_library(math).unwrap();
36/// ```
37pub struct Library<State: Clone + Send + Sync + 'static> {
38    /// The module name Rex code will import.
39    ///
40    /// This should be the fully-qualified library path you want users to write in `import`
41    /// declarations, such as `acme.math` or `sample`.
42    ///
43    /// [`Engine::inject_library`] validates and reserves this name when the library is injected.
44    ///
45    /// # Examples
46    ///
47    /// ```rust,ignore
48    /// use rexlang_engine::Library;
49    ///
50    /// let library = Library::<()>::new("acme.math");
51    /// assert_eq!(library.name, "acme.math");
52    /// ```
53    pub name: String,
54
55    /// Raw Rex declarations that will be concatenated into the injected module source.
56    ///
57    /// This is most commonly used for `pub type ...` declarations, but it can hold any raw Rex
58    /// declaration text you want included in the virtual module source.
59    ///
60    /// The usual way to append to this field is [`Library::add_declaration`], which validates that
61    /// the added text is non-empty. The field itself is public so callers can inspect or construct
62    /// a library in multiple passes.
63    ///
64    /// # Examples
65    ///
66    /// ```rust,ignore
67    /// use rexlang_engine::Library;
68    ///
69    /// let mut library = Library::<()>::new("acme.status");
70    /// library
71    ///     .add_declaration("pub type Status = Ready | Failed string")
72    ///     .unwrap();
73    ///
74    /// assert_eq!(library.declarations.len(), 1);
75    /// ```
76    pub declarations: Vec<String>,
77
78    /// Staged host exports that will become callable Rex values when the library is injected.
79    ///
80    /// Each [`Export`] bundles a public Rex name, a declaration that is inserted into the virtual
81    /// module source, and the runtime injector that registers the implementation with the engine.
82    ///
83    /// Most callers populate this with [`Library::export`], [`Library::export_async`],
84    /// [`Library::export_native`], [`Library::export_native_async`], or [`Library::add_export`].
85    /// The field is public so advanced embedders can construct exports separately and assemble the
86    /// final library programmatically.
87    ///
88    /// # Examples
89    ///
90    /// ```rust,ignore
91    /// use rexlang_engine::{Export, Library};
92    ///
93    /// let mut library = Library::<()>::new("acme.math");
94    /// let export = Export::from_handler("inc", |_state: &(), x: i32| Ok(x + 1)).unwrap();
95    /// library.exports.push(export);
96    ///
97    /// assert_eq!(library.exports.len(), 1);
98    /// ```
99    pub exports: Vec<Export<State>>,
100}
101
102impl<State> Library<State>
103where
104    State: Clone + Send + Sync + 'static,
105{
106    /// Create an empty staged library with the given import name.
107    ///
108    /// The returned library contains no declarations and no exports yet. Add those with the
109    /// helper methods on `Library`, then pass it to [`Engine::inject_library`].
110    ///
111    /// # Examples
112    ///
113    /// ```rust,ignore
114    /// use rexlang_engine::Library;
115    ///
116    /// let library = Library::<()>::new("acme.math");
117    /// assert_eq!(library.name, "acme.math");
118    /// assert!(library.declarations.is_empty());
119    /// assert!(library.exports.is_empty());
120    /// ```
121    pub fn new(name: impl Into<String>) -> Self {
122        Self {
123            name: name.into(),
124            declarations: Vec::new(),
125            exports: Vec::new(),
126        }
127    }
128
129    /// Append a raw Rex declaration to this staged library.
130    ///
131    /// Use this when you already have declaration text in Rex syntax, for example `pub type ...`.
132    /// The text is stored exactly as provided and later concatenated into the virtual module source
133    /// that [`Engine::inject_library`] exposes to Rex imports.
134    ///
135    /// This rejects empty or whitespace-only strings.
136    ///
137    /// # Examples
138    ///
139    /// ```rust,ignore
140    /// use rexlang_engine::Library;
141    ///
142    /// let mut library = Library::<()>::new("acme.status");
143    /// library
144    ///     .add_declaration("pub type Status = Ready | Failed string")
145    ///     .unwrap();
146    /// ```
147    pub fn add_declaration(&mut self, declaration: impl Into<String>) -> Result<(), EngineError> {
148        let declaration = declaration.into();
149        if declaration.trim().is_empty() {
150            return Err(EngineError::Internal(
151                "library declaration cannot be empty".into(),
152            ));
153        }
154        self.declarations.push(declaration);
155        Ok(())
156    }
157
158    /// Convert an [`AdtDecl`] into Rex source and append it to [`Library::declarations`].
159    ///
160    /// This is a structured alternative to [`Library::add_declaration`] when you already have an
161    /// ADT declaration in typed form.
162    ///
163    /// # Examples
164    ///
165    /// ```rust,ignore
166    /// use rexlang_engine::{Engine, Library};
167    ///
168    /// let mut engine = Engine::with_prelude(()).unwrap();
169    /// let mut library = Library::new("acme.types");
170    /// let adt = engine.adt_decl_from_type(&rexlang_typesystem::Type::user_con("Thing", 0)).unwrap();
171    ///
172    /// library.add_adt_decl(adt).unwrap();
173    /// ```
174    pub fn add_adt_decl(&mut self, adt: AdtDecl) -> Result<(), EngineError> {
175        self.add_declaration(adt_declaration_line(&adt))
176    }
177
178    /// Discover user ADTs referenced by the supplied types and append their declarations.
179    ///
180    /// This is useful when you have Rust-side type information and want to emit the corresponding
181    /// Rex `pub type ...` declarations for every user-defined ADT it mentions.
182    ///
183    /// The discovery process:
184    ///
185    /// - walks the provided types recursively
186    /// - deduplicates repeated ADTs
187    /// - asks the engine to materialize each discovered ADT declaration
188    /// - appends the resulting declarations to this library
189    ///
190    /// If conflicting ADT definitions are found for the same type constructor name, this returns
191    /// an [`EngineError`] that describes the conflict instead of silently picking one.
192    ///
193    /// # Examples
194    ///
195    /// ```rust,ignore
196    /// use rex_engine::{Engine, Library};
197    /// use rexlang_typesystem::{BuiltinTypeId, Type};
198    ///
199    /// let mut engine = Engine::with_prelude(()).unwrap();
200    /// let mut library = Library::new("acme.types");
201    /// let types = vec![
202    ///     Type::app(Type::user_con("Foo", 1), Type::builtin(BuiltinTypeId::I32)),
203    ///     Type::user_con("Bar", 0),
204    /// ];
205    ///
206    /// library.add_adt_decls_from_types(&mut engine, types).unwrap();
207    /// ```
208    pub fn add_adt_decls_from_types(
209        &mut self,
210        engine: &mut Engine<State>,
211        types: Vec<Type>,
212    ) -> Result<(), EngineError> {
213        let adts = collect_adts_in_types(types).map_err(crate::collect_adts_error_to_engine)?;
214        for typ in adts {
215            let adt = engine.adt_decl_from_type(&typ)?;
216            self.add_adt_decl(adt)?;
217        }
218        Ok(())
219    }
220
221    /// Derive a Rex ADT declaration from a Rust type and append it to this library.
222    ///
223    /// This is the most ergonomic way to expose a Rust enum or struct that implements [`RexAdt`]
224    /// as a library-local Rex type declaration.
225    ///
226    /// Unlike [`RexAdt::inject_rex`], this stages the declaration inside the library instead of
227    /// injecting it straight into the engine root environment.
228    ///
229    /// # Examples
230    ///
231    /// ```rust,ignore
232    /// use rexlang_engine::{Engine, Library, RexAdt};
233    ///
234    /// #[derive(rexlang::Rex)]
235    /// struct Label {
236    ///     text: String,
237    /// }
238    ///
239    /// let mut engine = Engine::with_prelude(()).unwrap();
240    /// let mut library = Library::new("sample");
241    /// library.inject_rex_adt::<Label>(&mut engine).unwrap();
242    /// ```
243    pub fn inject_rex_adt<T>(&mut self, engine: &mut Engine<State>) -> Result<(), EngineError>
244    where
245        T: RexAdt,
246    {
247        let adt = T::rex_adt_decl(engine)?;
248        self.add_adt_decl(adt)
249    }
250
251    /// Append a preconstructed [`Export`] to this library.
252    ///
253    /// This is useful when exports are assembled elsewhere, such as from plugin metadata or a
254    /// higher-level registration layer.
255    ///
256    /// # Examples
257    ///
258    /// ```rust,ignore
259    /// use rexlang_engine::{Export, Library};
260    ///
261    /// let mut library = Library::<()>::new("acme.math");
262    /// let export = Export::from_handler("inc", |_state: &(), x: i32| Ok(x + 1)).unwrap();
263    /// library.add_export(export);
264    /// ```
265    pub fn add_export(&mut self, export: Export<State>) {
266        self.exports.push(export);
267    }
268
269    /// Stage a typed synchronous Rust handler as a library export.
270    ///
271    /// This is the most convenient API for exporting ordinary Rust functions or closures into a
272    /// library. The handler's argument and return types drive the Rex signature automatically.
273    ///
274    /// The staged export becomes available to Rex code after [`Engine::inject_library`] is called.
275    ///
276    /// # Examples
277    ///
278    /// ```rust,ignore
279    /// use rexlang_engine::Library;
280    ///
281    /// let mut library = Library::<()>::new("acme.math");
282    /// library.export("inc", |_state: &(), x: i32| Ok(x + 1)).unwrap();
283    /// ```
284    pub fn export<Sig, H>(&mut self, name: impl Into<String>, handler: H) -> Result<(), EngineError>
285    where
286        H: Handler<State, Sig>,
287    {
288        self.exports.push(Export::from_handler(name, handler)?);
289        Ok(())
290    }
291
292    /// Stage a typed asynchronous Rust handler as a library export.
293    ///
294    /// Use this when the host implementation is naturally async, for example when it awaits I/O or
295    /// other long-running work.
296    ///
297    /// # Examples
298    ///
299    /// ```rust,ignore
300    /// use rexlang_engine::Library;
301    ///
302    /// let mut library = Library::<()>::new("acme.math");
303    /// library
304    ///     .export_async("double_async", |_state: &(), x: i32| async move { Ok(x * 2) })
305    ///     .unwrap();
306    /// ```
307    pub fn export_async<Sig, H>(
308        &mut self,
309        name: impl Into<String>,
310        handler: H,
311    ) -> Result<(), EngineError>
312    where
313        H: AsyncHandler<State, Sig>,
314    {
315        self.exports
316            .push(Export::from_async_handler(name, handler)?);
317        Ok(())
318    }
319
320    /// Stage a pointer-level synchronous native export with an explicit Rex type scheme.
321    ///
322    /// This lower-level API is intended for dynamic or runtime-defined integrations where the
323    /// handler needs access to the engine heap or where the Rex type cannot be inferred from an
324    /// ordinary Rust function signature alone.
325    ///
326    /// `scheme` describes the Rex-visible type, and `arity` must match the number of arguments the
327    /// handler expects.
328    ///
329    /// # Examples
330    ///
331    /// ```rust,ignore
332    /// use rexlang_engine::{EvaluatorRef, Library, Pointer};
333    /// use rexlang_typesystem::{BuiltinTypeId, Scheme, Type};
334    ///
335    /// let mut library = Library::<()>::new("acme.dynamic");
336    /// let scheme = Scheme::new(
337    ///     vec![],
338    ///     vec![],
339    ///     Type::fun(Type::builtin(BuiltinTypeId::I32), Type::builtin(BuiltinTypeId::I32)),
340    /// );
341    ///
342    /// library
343    ///     .export_native("id_ptr", scheme, 1, |_engine: EvaluatorRef<'_, ()>, _typ: &Type, args: &[Pointer]| {
344    ///         Ok(args[0].clone())
345    ///     })
346    ///     .unwrap();
347    /// ```
348    pub fn export_native<F>(
349        &mut self,
350        name: impl Into<String>,
351        scheme: rexlang_typesystem::Scheme,
352        arity: usize,
353        handler: F,
354    ) -> Result<(), EngineError>
355    where
356        F: for<'a> Fn(
357                EvaluatorRef<'a, State>,
358                &'a Type,
359                &'a [Pointer],
360            ) -> Result<Pointer, EngineError>
361            + Send
362            + Sync
363            + 'static,
364    {
365        self.exports
366            .push(Export::from_native(name, scheme, arity, handler)?);
367        Ok(())
368    }
369
370    /// Stage a pointer-level asynchronous native export with an explicit Rex type scheme.
371    ///
372    /// This is the async counterpart to [`Library::export_native`]. Use it when the export needs
373    /// both direct engine access and asynchronous execution.
374    ///
375    /// # Examples
376    ///
377    /// ```rust,ignore
378    /// use futures::FutureExt;
379    /// use rexlang_engine::{EvaluatorRef, Library, Pointer};
380    /// use rexlang_typesystem::{BuiltinTypeId, Scheme, Type};
381    ///
382    /// let mut library = Library::<()>::new("acme.dynamic");
383    /// let scheme = Scheme::new(vec![], vec![], Type::builtin(BuiltinTypeId::I32));
384    ///
385    /// library
386    ///     .export_native_async(
387    ///         "answer_async",
388    ///         scheme,
389    ///         0,
390    ///         |engine: EvaluatorRef<'_, ()>, _typ: Type, _args: Vec<Pointer>| {
391    ///             async move { engine.heap.alloc_i32(42) }.boxed()
392    ///         },
393    ///     )
394    ///     .unwrap();
395    /// ```
396    pub fn export_native_async<F>(
397        &mut self,
398        name: impl Into<String>,
399        scheme: rexlang_typesystem::Scheme,
400        arity: usize,
401        handler: F,
402    ) -> Result<(), EngineError>
403    where
404        F: for<'a> Fn(EvaluatorRef<'a, State>, Type, Vec<Pointer>) -> NativeFuture<'a>
405            + Send
406            + Sync
407            + 'static,
408    {
409        self.exports
410            .push(Export::from_native_async(name, scheme, arity, handler)?);
411        Ok(())
412    }
413}
414
415fn adt_declaration_line(adt: &AdtDecl) -> String {
416    let head = if adt.params.is_empty() {
417        adt.name.to_string()
418    } else {
419        let params = adt
420            .params
421            .iter()
422            .map(|p| p.name.to_string())
423            .collect::<Vec<_>>()
424            .join(" ");
425        format!("{} {}", adt.name, params)
426    };
427    let variants = adt
428        .variants
429        .iter()
430        .map(|variant| {
431            if variant.args.is_empty() {
432                variant.name.to_string()
433            } else {
434                let args = variant
435                    .args
436                    .iter()
437                    .map(adt_variant_arg_string)
438                    .collect::<Vec<_>>()
439                    .join(" ");
440                format!("{} {}", variant.name, args)
441            }
442        })
443        .collect::<Vec<_>>()
444        .join(" | ");
445    format!("pub type {head} = {variants}")
446}
447
448fn adt_variant_arg_string(typ: &Type) -> String {
449    let s = typ.to_string();
450    if s.contains(" -> ") {
451        format!("({s})")
452    } else {
453        s
454    }
455}