Skip to main content

snix_cli_eval/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3use std::path::PathBuf;
4use std::rc::Rc;
5
6use rustc_hash::FxHashMap;
7use smol_str::SmolStr;
8use snix_build::buildservice;
9use snix_eval::{
10    ErrorKind, EvalIO, EvalMode, GlobalsMap, SourceCode, Value,
11    builtins::impure_builtins,
12    observer::{DisassemblingObserver, TracingObserver},
13};
14use snix_glue::{
15    builtins::{add_derivation_builtins, add_fetcher_builtins, add_import_builtins},
16    configure_nix_path,
17    snix_io::SnixIO,
18    snix_store_io::SnixStoreIO,
19};
20use std::fmt::Write;
21use tracing::{Span, info_span};
22use tracing_indicatif::span_ext::IndicatifSpanExt;
23
24pub mod args;
25pub mod assignment;
26pub mod repl;
27
28pub use args::Args;
29pub use repl::Repl;
30
31pub async fn init_io_handle(args: &Args) -> SnixStoreIO {
32    let (blob_service, directory_service, path_info_service, nar_calculation_service) =
33        snix_store::utils::construct_services(args.service_addrs.clone())
34            .await
35            .expect("unable to setup {blob|directory|pathinfo}service before interpreter setup");
36
37    let build_service = buildservice::from_addr(
38        &args.build_service_addr,
39        blob_service.clone(),
40        directory_service.clone(),
41    )
42    .await
43    .expect("unable to setup buildservice before interpreter setup");
44
45    SnixStoreIO::new(
46        blob_service,
47        directory_service,
48        path_info_service,
49        nar_calculation_service.into(),
50        build_service.into(),
51        tokio::runtime::Handle::current(),
52        args.hashed_mirrors.clone(),
53    )
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
57pub enum AllowIncomplete {
58    Allow,
59    #[default]
60    RequireComplete,
61}
62
63impl AllowIncomplete {
64    fn allow(&self) -> bool {
65        matches!(self, Self::Allow)
66    }
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub struct IncompleteInput;
71
72pub struct EvalResult {
73    value: Option<Value>,
74    globals: Rc<GlobalsMap>,
75}
76
77/// Interprets the given code snippet, printing out warnings and errors and returning the result
78#[allow(clippy::too_many_arguments)]
79pub fn evaluate<E: std::io::Write + Clone + Send>(
80    stderr: &mut E,
81    snix_store_io: Rc<SnixStoreIO>,
82    code: &str,
83    path: Option<PathBuf>,
84    args: &Args,
85    allow_incomplete: AllowIncomplete,
86    env: Option<&FxHashMap<SmolStr, Value>>,
87    globals: Option<Rc<GlobalsMap>>,
88    source_map: Option<SourceCode>,
89) -> Result<EvalResult, IncompleteInput> {
90    let mut eval_builder = snix_eval::Evaluation::builder(Box::new(SnixIO::new(
91        snix_store_io.clone() as Rc<dyn EvalIO>,
92    )) as Box<dyn EvalIO>)
93    .enable_import()
94    .env(env);
95
96    if args.strict {
97        eval_builder = eval_builder.mode(EvalMode::Strict);
98    }
99
100    match globals {
101        Some(globals) => {
102            eval_builder = eval_builder.with_globals(globals);
103        }
104        None => {
105            eval_builder = eval_builder.add_builtins(impure_builtins());
106            eval_builder = add_derivation_builtins(eval_builder, Rc::clone(&snix_store_io));
107            eval_builder = add_fetcher_builtins(eval_builder, Rc::clone(&snix_store_io));
108            eval_builder = add_import_builtins(eval_builder, Rc::clone(&snix_store_io));
109        }
110    };
111    eval_builder = configure_nix_path(eval_builder, &args.nix_path());
112
113    if let Some(source_map) = source_map {
114        eval_builder = eval_builder.with_source_map(source_map);
115    }
116
117    let source_map = eval_builder.source_map().clone();
118
119    let mut compiler_observer = DisassemblingObserver::new(source_map.clone(), stderr.clone());
120    if args.dump_bytecode {
121        eval_builder.set_compiler_observer(Some(&mut compiler_observer));
122    }
123
124    let mut runtime_observer = TracingObserver::new(stderr.clone());
125    if args.trace_runtime {
126        if args.trace_runtime_timing {
127            runtime_observer.enable_timing()
128        }
129        eval_builder.set_runtime_observer(Some(&mut runtime_observer));
130    }
131
132    let eval = eval_builder.build();
133
134    let span = if !args.trace_runtime && !args.dump_bytecode {
135        info_span!("evaluate", indicatif.pb_show = tracing::field::Empty)
136    } else {
137        info_span!("evaluate", indicatif.pb_hide = tracing::field::Empty)
138    };
139    let (result, globals) = span.in_scope(|| {
140        let span = Span::current();
141
142        span.pb_set_message("Evaluating…");
143        span.pb_start();
144
145        let globals = eval.globals();
146        let result = eval.evaluate(code, path);
147        (result, globals)
148    });
149    drop(span);
150
151    if allow_incomplete.allow()
152        && result.errors.iter().any(|err| {
153            matches!(
154                &err.kind,
155                ErrorKind::ParseErrors(pes)
156                    if pes.iter().any(|pe| matches!(pe, rnix::ParseError::UnexpectedEOF))
157            )
158        })
159    {
160        return Err(IncompleteInput);
161    }
162
163    if args.display_ast
164        && let Some(ref expr) = result.expr
165    {
166        writeln!(stderr, "AST: {}", snix_eval::pretty_print_expr(expr)).unwrap();
167    }
168
169    for error in &result.errors {
170        error.fancy_format_write(stderr);
171    }
172
173    if !args.no_warnings {
174        for warning in &result.warnings {
175            warning.fancy_format_write(stderr, &source_map);
176        }
177    }
178
179    if let Some(dumpdir) = &args.drv_dumpdir {
180        // Dump all known derivations files to `dumpdir`.
181        std::fs::create_dir_all(dumpdir).expect("failed to create drv dumpdir");
182        snix_store_io
183            .known_paths
184            .borrow()
185            .get_derivations()
186            // Skip already dumped derivations.
187            .filter(|(drv_path, _)| !dumpdir.join(drv_path.to_string()).exists())
188            .for_each(|(drv_path, drv)| {
189                std::fs::write(dumpdir.join(drv_path.to_string()), drv.to_aterm_bytes())
190                    .expect("failed to write drv to dumpdir");
191            })
192    }
193
194    Ok(EvalResult {
195        globals,
196        value: result.value,
197    })
198}
199
200pub struct InterpretResult {
201    output: String,
202    success: bool,
203    pub(crate) globals: Option<Rc<GlobalsMap>>,
204}
205
206impl InterpretResult {
207    pub fn empty_success(globals: Option<Rc<GlobalsMap>>) -> Self {
208        Self {
209            output: String::new(),
210            success: true,
211            globals,
212        }
213    }
214
215    pub fn finalize<E: std::io::Write>(self, stderr: &mut E) -> bool {
216        write!(stderr, "{}", self.output).unwrap();
217        self.success
218    }
219
220    pub fn output(&self) -> &str {
221        &self.output
222    }
223
224    pub fn success(&self) -> bool {
225        self.success
226    }
227}
228
229/// Interprets the given code snippet, printing out warnings, errors
230/// and the result itself. The return value indicates whether
231/// evaluation succeeded.
232#[allow(clippy::too_many_arguments)]
233pub fn interpret<E: std::io::Write + Clone + Send>(
234    stderr: &mut E,
235    snix_store_io: Rc<SnixStoreIO>,
236    code: &str,
237    path: Option<PathBuf>,
238    args: &Args,
239    explain: bool,
240    allow_incomplete: AllowIncomplete,
241    env: Option<&FxHashMap<SmolStr, Value>>,
242    globals: Option<Rc<GlobalsMap>>,
243    source_map: Option<SourceCode>,
244) -> Result<InterpretResult, IncompleteInput> {
245    let mut output = String::new();
246    let result = evaluate(
247        stderr,
248        snix_store_io,
249        code,
250        path,
251        args,
252        allow_incomplete,
253        env,
254        globals,
255        source_map,
256    )?;
257
258    if let Some(value) = result.value.as_ref() {
259        if explain {
260            writeln!(&mut output, "=> {}", value.explain()).unwrap();
261        } else if args.raw {
262            writeln!(&mut output, "{value}").unwrap();
263        } else {
264            writeln!(&mut output, "=> {} :: {}", value, value.type_of()).unwrap();
265        }
266    }
267
268    // inform the caller about any errors
269    Ok(InterpretResult {
270        output,
271        success: result.value.is_some(),
272        globals: Some(result.globals),
273    })
274}