Skip to main content

rexlang_engine/libraries/
library.rs

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