1pub mod builtins;
16mod chunk;
17mod compiler;
18mod errors;
19mod io;
20pub mod observer;
21mod opcode;
22mod pretty_ast;
23mod source;
24mod spans;
25mod systems;
26mod upvalues;
27mod value;
28mod vm;
29mod warnings;
30
31mod nix_search_path;
32#[cfg(all(test, feature = "arbitrary"))]
33mod properties;
34#[cfg(test)]
35mod test_utils;
36#[cfg(test)]
37mod tests;
38
39use rustc_hash::FxHashMap;
40use std::path::PathBuf;
41use std::rc::Rc;
42use std::str::FromStr;
43use std::sync::Arc;
44
45use crate::observer::{CompilerObserver, RuntimeObserver};
46use crate::value::Lambda;
47use crate::vm::run_lambda;
48
49pub use crate::compiler::{CompilationOutput, GlobalsMap, compile, prepare_globals};
51pub use crate::errors::{AddContext, CatchableErrorKind, Error, ErrorKind, EvalResult};
52pub use crate::io::{DummyIO, EvalIO, FileType};
53pub use crate::pretty_ast::pretty_print_expr;
54pub use crate::source::SourceCode;
55pub use crate::value::{NixContext, NixContextElement};
56pub use crate::vm::{EvalMode, generators};
57pub use crate::warnings::{EvalWarning, WarningKind};
58pub use builtin_macros;
59use smol_str::SmolStr;
60
61pub use crate::value::{Builtin, CoercionKind, NixAttrs, NixList, NixString, Value};
62
63#[cfg(feature = "impure")]
64pub use crate::io::StdIO;
65
66struct BuilderBuiltins {
67    builtins: Vec<(&'static str, Value)>,
68    src_builtins: Vec<(&'static str, &'static str)>,
69}
70
71enum BuilderGlobals {
72    Builtins(BuilderBuiltins),
73    Globals(Rc<GlobalsMap>),
74}
75
76pub struct EvaluationBuilder<'co, 'ro, 'env, IO> {
87    source_map: Option<SourceCode>,
88    globals: BuilderGlobals,
89    env: Option<&'env FxHashMap<SmolStr, Value>>,
90    io_handle: IO,
91    enable_import: bool,
92    mode: EvalMode,
93    nix_path: Option<String>,
94    compiler_observer: Option<&'co mut dyn CompilerObserver>,
95    runtime_observer: Option<&'ro mut dyn RuntimeObserver>,
96}
97
98impl<'co, 'ro, 'env, IO> EvaluationBuilder<'co, 'ro, 'env, IO>
99where
100    IO: AsRef<dyn EvalIO> + 'static,
101{
102    pub fn build(self) -> Evaluation<'co, 'ro, 'env, IO> {
110        let source_map = self.source_map.unwrap_or_default();
111
112        let globals = match self.globals {
113            BuilderGlobals::Globals(globals) => globals,
114            BuilderGlobals::Builtins(BuilderBuiltins {
115                mut builtins,
116                src_builtins,
117            }) => {
118                if let Some(store_dir) = self.io_handle.as_ref().store_dir() {
120                    builtins.push(("storeDir", store_dir.into()));
121                }
122
123                crate::compiler::prepare_globals(
124                    builtins,
125                    src_builtins,
126                    source_map.clone(),
127                    self.enable_import,
128                )
129            }
130        };
131
132        Evaluation {
133            source_map,
134            globals,
135            env: self.env,
136            io_handle: self.io_handle,
137            mode: self.mode,
138            nix_path: self.nix_path,
139            compiler_observer: self.compiler_observer,
140            runtime_observer: self.runtime_observer,
141        }
142    }
143}
144
145impl<'co, 'ro, 'env, IO> EvaluationBuilder<'co, 'ro, 'env, IO> {
148    pub fn new(io_handle: IO) -> Self {
149        let mut builtins = builtins::pure_builtins();
150        builtins.extend(builtins::placeholders()); Self {
153            source_map: None,
154            enable_import: false,
155            io_handle,
156            globals: BuilderGlobals::Builtins(BuilderBuiltins {
157                builtins,
158                src_builtins: vec![],
159            }),
160            env: None,
161            mode: Default::default(),
162            nix_path: None,
163            compiler_observer: None,
164            runtime_observer: None,
165        }
166    }
167
168    pub fn io_handle<IO2>(self, io_handle: IO2) -> EvaluationBuilder<'co, 'ro, 'env, IO2> {
169        EvaluationBuilder {
170            io_handle,
171            source_map: self.source_map,
172            globals: self.globals,
173            env: self.env,
174            enable_import: self.enable_import,
175            mode: self.mode,
176            nix_path: self.nix_path,
177            compiler_observer: self.compiler_observer,
178            runtime_observer: self.runtime_observer,
179        }
180    }
181
182    pub fn with_enable_import(self, enable_import: bool) -> Self {
183        Self {
184            enable_import,
185            ..self
186        }
187    }
188
189    pub fn disable_import(self) -> Self {
190        self.with_enable_import(false)
191    }
192
193    pub fn enable_import(self) -> Self {
194        self.with_enable_import(true)
195    }
196
197    fn builtins_mut(&mut self) -> &mut BuilderBuiltins {
198        match &mut self.globals {
199            BuilderGlobals::Builtins(builtins) => builtins,
200            BuilderGlobals::Globals(_) => {
201                panic!("Cannot modify builtins on an EvaluationBuilder with globals configured")
202            }
203        }
204    }
205
206    pub fn add_builtins<I>(mut self, builtins: I) -> Self
213    where
214        I: IntoIterator<Item = (&'static str, Value)>,
215    {
216        self.builtins_mut().builtins.extend(builtins);
217        self
218    }
219
220    pub fn add_src_builtin(mut self, name: &'static str, src: &'static str) -> Self {
227        self.builtins_mut().src_builtins.push((name, src));
228        self
229    }
230
231    pub fn with_globals(self, globals: Rc<GlobalsMap>) -> Self {
238        Self {
239            globals: BuilderGlobals::Globals(globals),
240            ..self
241        }
242    }
243
244    pub fn with_source_map(self, source_map: SourceCode) -> Self {
245        debug_assert!(
246            self.source_map.is_none(),
247            "Cannot set the source_map on an EvaluationBuilder twice"
248        );
249        Self {
250            source_map: Some(source_map),
251            ..self
252        }
253    }
254
255    pub fn mode(self, mode: EvalMode) -> Self {
256        Self { mode, ..self }
257    }
258
259    pub fn nix_path(self, nix_path: Option<String>) -> Self {
260        Self { nix_path, ..self }
261    }
262
263    pub fn env(self, env: Option<&'env FxHashMap<SmolStr, Value>>) -> Self {
264        Self { env, ..self }
265    }
266
267    pub fn compiler_observer(
268        self,
269        compiler_observer: Option<&'co mut dyn CompilerObserver>,
270    ) -> Self {
271        Self {
272            compiler_observer,
273            ..self
274        }
275    }
276
277    pub fn set_compiler_observer(
278        &mut self,
279        compiler_observer: Option<&'co mut dyn CompilerObserver>,
280    ) {
281        self.compiler_observer = compiler_observer;
282    }
283
284    pub fn runtime_observer(self, runtime_observer: Option<&'ro mut dyn RuntimeObserver>) -> Self {
285        Self {
286            runtime_observer,
287            ..self
288        }
289    }
290
291    pub fn set_runtime_observer(&mut self, runtime_observer: Option<&'ro mut dyn RuntimeObserver>) {
292        self.runtime_observer = runtime_observer;
293    }
294}
295
296impl<IO> EvaluationBuilder<'_, '_, '_, IO> {
297    pub fn source_map(&mut self) -> &SourceCode {
298        self.source_map.get_or_insert_with(SourceCode::default)
299    }
300}
301
302impl EvaluationBuilder<'_, '_, '_, Box<dyn EvalIO>> {
303    pub fn new_pure() -> Self {
306        Self::new(Box::new(DummyIO) as Box<dyn EvalIO>).with_enable_import(false)
307    }
308
309    #[cfg(feature = "impure")]
310    pub fn enable_impure(mut self, io: Option<Box<dyn EvalIO>>) -> Self {
316        self.io_handle = io.unwrap_or_else(|| Box::new(StdIO) as Box<dyn EvalIO>);
317        self.enable_import = true;
318        self.builtins_mut()
319            .builtins
320            .extend(builtins::impure_builtins());
321
322        if self.nix_path.is_none() {
325            self.nix_path = std::env::var("NIX_PATH").ok();
326        }
327        self
328    }
329
330    #[cfg(feature = "impure")]
331    pub fn new_impure() -> Self {
333        Self::new_pure().enable_impure(None)
334    }
335}
336
337pub struct Evaluation<'co, 'ro, 'env, IO> {
344    source_map: SourceCode,
346
347    globals: Rc<GlobalsMap>,
349
350    env: Option<&'env FxHashMap<SmolStr, Value>>,
352
353    io_handle: IO,
358
359    mode: EvalMode,
363
364    nix_path: Option<String>,
367
368    compiler_observer: Option<&'co mut dyn CompilerObserver>,
371
372    runtime_observer: Option<&'ro mut dyn RuntimeObserver>,
375}
376
377#[derive(Debug, Default)]
381pub struct EvaluationResult {
382    pub value: Option<Value>,
384
385    pub errors: Vec<Error>,
387
388    pub warnings: Vec<EvalWarning>,
391
392    pub expr: Option<rnix::ast::Expr>,
394}
395
396impl<'co, 'ro, 'env, IO> Evaluation<'co, 'ro, 'env, IO> {
397    pub fn builder(io_handle: IO) -> EvaluationBuilder<'co, 'ro, 'env, IO> {
401        EvaluationBuilder::new(io_handle)
402    }
403
404    pub fn globals(&self) -> Rc<GlobalsMap> {
408        self.globals.clone()
409    }
410
411    pub fn source_map(&self) -> SourceCode {
415        self.source_map.clone()
416    }
417}
418
419impl<'co, 'ro, 'env> Evaluation<'co, 'ro, 'env, Box<dyn EvalIO>> {
420    #[cfg(feature = "impure")]
421    pub fn builder_impure() -> EvaluationBuilder<'co, 'ro, 'env, Box<dyn EvalIO>> {
422        EvaluationBuilder::new_impure()
423    }
424
425    pub fn builder_pure() -> EvaluationBuilder<'co, 'ro, 'env, Box<dyn EvalIO>> {
426        EvaluationBuilder::new_pure()
427    }
428}
429
430impl<IO> Evaluation<'_, '_, '_, IO>
431where
432    IO: AsRef<dyn EvalIO> + 'static,
433{
434    pub fn compile_only(
440        mut self,
441        code: impl AsRef<str>,
442        location: Option<PathBuf>,
443    ) -> EvaluationResult {
444        let mut result = EvaluationResult::default();
445        let source = self.source_map();
446
447        let location_str = location
448            .as_ref()
449            .map(|p| p.to_string_lossy().to_string())
450            .unwrap_or_else(|| "[code]".into());
451
452        let file = source.add_file(location_str, code.as_ref().to_string());
453
454        let mut noop_observer = observer::NoOpObserver::default();
455        let compiler_observer = self.compiler_observer.take().unwrap_or(&mut noop_observer);
456
457        parse_compile_internal(
458            &mut result,
459            code.as_ref(),
460            file,
461            location,
462            source,
463            self.globals,
464            self.env,
465            compiler_observer,
466        );
467
468        result
469    }
470
471    pub fn evaluate(
475        mut self,
476        code: impl AsRef<str>,
477        location: Option<PathBuf>,
478    ) -> EvaluationResult {
479        let mut result = EvaluationResult::default();
480        let source = self.source_map();
481
482        let location_str = location
483            .as_ref()
484            .map(|p| p.to_string_lossy().to_string())
485            .unwrap_or_else(|| "[code]".into());
486
487        let file = source.add_file(location_str, code.as_ref().to_string());
488
489        let mut noop_observer = observer::NoOpObserver::default();
490        let compiler_observer = self.compiler_observer.take().unwrap_or(&mut noop_observer);
491
492        let lambda = match parse_compile_internal(
493            &mut result,
494            code.as_ref(),
495            file.clone(),
496            location,
497            source.clone(),
498            self.globals.clone(),
499            self.env,
500            compiler_observer,
501        ) {
502            None => return result,
503            Some(cr) => cr,
504        };
505
506        let nix_path = self
510            .nix_path
511            .as_ref()
512            .and_then(|s| match nix_search_path::NixSearchPath::from_str(s) {
513                Ok(path) => Some(path),
514                Err(err) => {
515                    result.warnings.push(EvalWarning {
516                        kind: WarningKind::InvalidNixPath(err.to_string()),
517                        span: file.span,
518                    });
519                    None
520                }
521            })
522            .unwrap_or_default();
523
524        let runtime_observer = self.runtime_observer.take().unwrap_or(&mut noop_observer);
525
526        let vm_result = run_lambda(
527            nix_path,
528            self.io_handle,
529            runtime_observer,
530            source.clone(),
531            self.globals,
532            lambda,
533            self.mode,
534        );
535
536        match vm_result {
537            Ok(mut runtime_result) => {
538                result.warnings.append(&mut runtime_result.warnings);
539                if let Value::Catchable(inner) = runtime_result.value {
540                    result.errors.push(Error::new(
541                        ErrorKind::CatchableError(*inner),
542                        file.span,
543                        source,
544                    ));
545                    return result;
546                }
547
548                result.value = Some(runtime_result.value);
549            }
550            Err(err) => {
551                result.errors.push(err);
552            }
553        }
554
555        result
556    }
557}
558
559#[allow(clippy::too_many_arguments)] fn parse_compile_internal(
563    result: &mut EvaluationResult,
564    code: &str,
565    file: Arc<codemap::File>,
566    location: Option<PathBuf>,
567    source: SourceCode,
568    globals: Rc<GlobalsMap>,
569    env: Option<&FxHashMap<SmolStr, Value>>,
570    compiler_observer: &mut dyn CompilerObserver,
571) -> Option<Rc<Lambda>> {
572    let parsed = rnix::ast::Root::parse(code);
573    let parse_errors = parsed.errors();
574
575    if !parse_errors.is_empty() {
576        result.errors.push(Error::new(
577            ErrorKind::ParseErrors(parse_errors.to_vec()),
578            file.span,
579            source,
580        ));
581        return None;
582    }
583
584    result.expr = parsed.tree().expr();
588
589    let compiler_result = match compiler::compile(
590        result.expr.as_ref().unwrap(),
591        location,
592        globals,
593        env,
594        &source,
595        &file,
596        compiler_observer,
597    ) {
598        Ok(result) => result,
599        Err(err) => {
600            result.errors.push(err);
601            return None;
602        }
603    };
604
605    result.warnings = compiler_result.warnings;
606    result.errors.extend(compiler_result.errors);
607
608    if !result.errors.is_empty() {
611        return None;
612    }
613
614    Some(compiler_result.lambda)
617}