Skip to main content

tatara_lisp_script/
script_ctx.rs

1//! `ScriptCtx` — the host context passed to every stdlib FFI call.
2//!
3//! Holds a shared HTTP agent (connection pooling across calls), the
4//! command-line argv, the current file being evaluated (for relative-
5//! path `require`), and the require cache (so repeated `(require …)`
6//! calls are no-ops). Embedders that want to extend the stdlib should
7//! either expose `ScriptCtx` directly or wrap it in their own type
8//! and re-register fn's against that.
9
10use std::collections::HashSet;
11use std::path::PathBuf;
12use std::time::Duration;
13
14#[derive(Default)]
15pub struct ScriptCtx {
16    /// Program arguments after the script path.
17    /// e.g. for `tatara-script imports.tlisp lilitu_io --all` → `["lilitu_io", "--all"]`.
18    pub argv: Vec<String>,
19
20    /// The file currently being evaluated (if any). Used by `(require)`
21    /// to resolve relative paths against the caller's directory.
22    pub current_file: Option<PathBuf>,
23
24    /// Set of canonical absolute paths already required. Prevents
25    /// re-evaluation on the second (or third, …) `(require …)` of the
26    /// same file — required forms define globals once.
27    pub required: HashSet<PathBuf>,
28
29    /// Lazily-initialized shared HTTP agent (connection pooling).
30    http_agent: Option<ureq::Agent>,
31
32    /// Recorded test cases when in `--test` mode. Each entry is a
33    /// (name, thunk-closure) pair — the closure captures the test body
34    /// for deferred execution.
35    pub tests: Vec<TestCase>,
36}
37
38/// A collected `(deftest name body)` form awaiting `--test` execution.
39pub struct TestCase {
40    pub name: String,
41    pub body: Vec<tatara_lisp::Spanned>,
42}
43
44impl ScriptCtx {
45    /// Construct a context with the given argv. Used by the binary
46    /// entry point; embedders may prefer to start from `Default` and
47    /// populate argv directly.
48    pub fn with_argv<I, S>(argv: I) -> Self
49    where
50        I: IntoIterator<Item = S>,
51        S: Into<String>,
52    {
53        Self {
54            argv: argv.into_iter().map(Into::into).collect(),
55            current_file: None,
56            required: HashSet::new(),
57            http_agent: None,
58            tests: Vec::new(),
59        }
60    }
61
62    /// Return a shared HTTP agent, initializing on first use.
63    pub fn http(&mut self) -> &ureq::Agent {
64        self.http_agent.get_or_insert_with(|| {
65            ureq::Agent::config_builder()
66                .timeout_global(Some(Duration::from_secs(30)))
67                .build()
68                .new_agent()
69        })
70    }
71}