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}