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}