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::{compile, prepare_globals, CompilationOutput, GlobalsMap};
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::{generators, EvalMode};
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}