relon_eval_api/module.rs
1//! Module resolution protocol.
2//!
3//! `@import("path")` does not look up files itself. Instead, the evaluator
4//! asks each registered [`ModuleResolver`] in order until one returns
5//! `Some(ModuleSource)`. The evaluator then parses and evaluates that
6//! source once, caching the result by its [`ModuleSource::canonical_id`].
7//!
8//! Concrete resolver implementations (`StdModuleResolver`,
9//! `FilesystemModuleResolver`, host-supplied resolvers) live in the
10//! backend crate (`relon-evaluator`); the trait + payload type live here
11//! so any backend implementing [`crate::Evaluator`] can share them.
12
13use crate::error::RuntimeError;
14use crate::scope::Scope;
15use relon_parser::TokenRange;
16use std::sync::Arc;
17
18/// The source text plus identity of a module produced by a [`ModuleResolver`].
19#[derive(Debug, Clone)]
20pub struct ModuleSource {
21 /// Stable identity for this module — used as the key for the module cache
22 /// and the cycle-detection stack. For filesystem modules this is the
23 /// canonical absolute path; for `std/...` modules it is the virtual path
24 /// itself; for host-provided modules it can be any unique string.
25 pub canonical_id: String,
26 /// The Relon source text to parse and evaluate.
27 pub source: String,
28 /// Working directory used when nested `@import("./relative.relon")` calls
29 /// fire from inside this module. Filesystem resolvers normally set this to
30 /// the parent of `canonical_id`; in-memory modules can leave it empty.
31 pub current_dir: String,
32}
33
34/// A pluggable resolver that answers `@import("path")` requests.
35///
36/// Resolvers are polled in order; the first non-`None` return value is used.
37/// Returning `Ok(None)` defers to the next resolver. Returning `Err(_)` aborts
38/// the import without consulting later resolvers.
39pub trait ModuleResolver: Send + Sync {
40 fn resolve(
41 &self,
42 path: &str,
43 scope: &Arc<Scope>,
44 range: TokenRange,
45 ) -> Result<Option<ModuleSource>, RuntimeError>;
46}