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}