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::nix_search_path::NixSearchPath;
54pub use crate::pretty_ast::pretty_print_expr;
55pub use crate::source::SourceCode;
56pub use crate::value::{NixContext, NixContextElement};
57pub use crate::vm::{generators, EvalMode};
58pub use crate::warnings::{EvalWarning, WarningKind};
59pub use builtin_macros;
60use smol_str::SmolStr;
61
62pub use crate::value::{Builtin, CoercionKind, NixAttrs, NixList, NixString, Value};
63
64#[cfg(feature = "impure")]
65pub use crate::io::StdIO;
66
67struct BuilderBuiltins {
68 builtins: Vec<(&'static str, Value)>,
69 src_builtins: Vec<(&'static str, &'static str)>,
70}
71
72enum BuilderGlobals {
73 Builtins(BuilderBuiltins),
74 Globals(Rc<GlobalsMap>),
75}
76
77pub struct EvaluationBuilder<'co, 'ro, 'env> {
88 source_map: Option<SourceCode>,
89 globals: BuilderGlobals,
90 env: Option<&'env FxHashMap<SmolStr, Value>>,
91 io_handle: Rc<dyn EvalIO>,
92 enable_import: bool,
93 mode: EvalMode,
94 nix_path: Option<String>,
95 compiler_observer: Option<&'co mut dyn CompilerObserver>,
96 runtime_observer: Option<&'ro mut dyn RuntimeObserver>,
97}
98
99impl<'co, 'ro, 'env> EvaluationBuilder<'co, 'ro, 'env> {
100 pub fn build(self) -> Evaluation<'co, 'ro, 'env> {
108 let source_map = self.source_map.unwrap_or_default();
109
110 let globals = match self.globals {
111 BuilderGlobals::Globals(globals) => globals,
112 BuilderGlobals::Builtins(BuilderBuiltins {
113 mut builtins,
114 src_builtins,
115 }) => {
116 if let Some(store_dir) = self.io_handle.as_ref().store_dir() {
118 builtins.push(("storeDir", store_dir.into()));
119 }
120
121 crate::compiler::prepare_globals(
122 builtins,
123 src_builtins,
124 source_map.clone(),
125 self.enable_import,
126 )
127 }
128 };
129
130 Evaluation {
131 source_map,
132 globals,
133 env: self.env,
134 io_handle: self.io_handle,
135 mode: self.mode,
136 nix_path: self.nix_path,
137 compiler_observer: self.compiler_observer,
138 runtime_observer: self.runtime_observer,
139 }
140 }
141}
142
143impl<'co, 'ro, 'env> EvaluationBuilder<'co, 'ro, 'env> {
146 pub fn new(io_handle: Rc<dyn EvalIO>) -> Self {
147 let mut builtins = builtins::pure_builtins();
148 builtins.extend(builtins::placeholders()); Self {
151 source_map: None,
152 enable_import: false,
153 io_handle,
154 globals: BuilderGlobals::Builtins(BuilderBuiltins {
155 builtins,
156 src_builtins: vec![],
157 }),
158 env: None,
159 mode: Default::default(),
160 nix_path: None,
161 compiler_observer: None,
162 runtime_observer: None,
163 }
164 }
165
166 pub fn io_handle(self, io_handle: Rc<dyn EvalIO>) -> EvaluationBuilder<'co, 'ro, 'env> {
167 EvaluationBuilder {
168 io_handle,
169 source_map: self.source_map,
170 globals: self.globals,
171 env: self.env,
172 enable_import: self.enable_import,
173 mode: self.mode,
174 nix_path: self.nix_path,
175 compiler_observer: self.compiler_observer,
176 runtime_observer: self.runtime_observer,
177 }
178 }
179
180 pub fn with_enable_import(self, enable_import: bool) -> Self {
181 Self {
182 enable_import,
183 ..self
184 }
185 }
186
187 pub fn disable_import(self) -> Self {
188 self.with_enable_import(false)
189 }
190
191 pub fn enable_import(self) -> Self {
192 self.with_enable_import(true)
193 }
194
195 fn builtins_mut(&mut self) -> &mut BuilderBuiltins {
196 match &mut self.globals {
197 BuilderGlobals::Builtins(builtins) => builtins,
198 BuilderGlobals::Globals(_) => {
199 panic!("Cannot modify builtins on an EvaluationBuilder with globals configured")
200 }
201 }
202 }
203
204 pub fn add_builtins<I>(mut self, builtins: I) -> Self
211 where
212 I: IntoIterator<Item = (&'static str, Value)>,
213 {
214 self.builtins_mut().builtins.extend(builtins);
215 self
216 }
217
218 pub fn add_src_builtin(mut self, name: &'static str, src: &'static str) -> Self {
225 self.builtins_mut().src_builtins.push((name, src));
226 self
227 }
228
229 pub fn with_globals(self, globals: Rc<GlobalsMap>) -> Self {
236 Self {
237 globals: BuilderGlobals::Globals(globals),
238 ..self
239 }
240 }
241
242 pub fn with_source_map(self, source_map: SourceCode) -> Self {
243 debug_assert!(
244 self.source_map.is_none(),
245 "Cannot set the source_map on an EvaluationBuilder twice"
246 );
247 Self {
248 source_map: Some(source_map),
249 ..self
250 }
251 }
252
253 pub fn mode(self, mode: EvalMode) -> Self {
254 Self { mode, ..self }
255 }
256
257 pub fn nix_path(self, nix_path: Option<String>) -> Self {
258 Self { nix_path, ..self }
259 }
260
261 pub fn env(self, env: Option<&'env FxHashMap<SmolStr, Value>>) -> Self {
262 Self { env, ..self }
263 }
264
265 pub fn compiler_observer(
266 self,
267 compiler_observer: Option<&'co mut dyn CompilerObserver>,
268 ) -> Self {
269 Self {
270 compiler_observer,
271 ..self
272 }
273 }
274
275 pub fn set_compiler_observer(
276 &mut self,
277 compiler_observer: Option<&'co mut dyn CompilerObserver>,
278 ) {
279 self.compiler_observer = compiler_observer;
280 }
281
282 pub fn runtime_observer(self, runtime_observer: Option<&'ro mut dyn RuntimeObserver>) -> Self {
283 Self {
284 runtime_observer,
285 ..self
286 }
287 }
288
289 pub fn set_runtime_observer(&mut self, runtime_observer: Option<&'ro mut dyn RuntimeObserver>) {
290 self.runtime_observer = runtime_observer;
291 }
292}
293
294impl EvaluationBuilder<'_, '_, '_> {
295 pub fn source_map(&mut self) -> &SourceCode {
296 self.source_map.get_or_insert_with(SourceCode::default)
297 }
298}
299
300impl EvaluationBuilder<'_, '_, '_> {
301 pub fn new_pure() -> Self {
304 Self::new(Rc::new(DummyIO) as Rc<dyn EvalIO>).with_enable_import(false)
305 }
306
307 #[cfg(feature = "impure")]
308 pub fn enable_impure(mut self, io: Option<Rc<dyn EvalIO>>) -> Self {
314 self.io_handle = io.unwrap_or_else(|| Rc::new(StdIO) as Rc<dyn EvalIO>);
315 self.enable_import = true;
316 self.builtins_mut()
317 .builtins
318 .extend(builtins::impure_builtins());
319
320 if self.nix_path.is_none() {
323 self.nix_path = std::env::var("NIX_PATH").ok();
324 }
325 self
326 }
327
328 #[cfg(feature = "impure")]
329 pub fn new_impure() -> Self {
331 Self::new_pure().enable_impure(None)
332 }
333}
334
335pub struct Evaluation<'co, 'ro, 'env> {
342 source_map: SourceCode,
344
345 globals: Rc<GlobalsMap>,
347
348 env: Option<&'env FxHashMap<SmolStr, Value>>,
350
351 io_handle: Rc<dyn EvalIO>,
356
357 mode: EvalMode,
361
362 nix_path: Option<String>,
365
366 compiler_observer: Option<&'co mut dyn CompilerObserver>,
369
370 runtime_observer: Option<&'ro mut dyn RuntimeObserver>,
373}
374
375#[derive(Debug, Default)]
379pub struct EvaluationResult {
380 pub value: Option<Value>,
382
383 pub errors: Vec<Error>,
385
386 pub warnings: Vec<EvalWarning>,
389
390 pub expr: Option<rnix::ast::Expr>,
392}
393
394impl<'co, 'ro, 'env> Evaluation<'co, 'ro, 'env> {
395 pub fn builder(io_handle: Rc<dyn EvalIO>) -> EvaluationBuilder<'co, 'ro, 'env> {
399 EvaluationBuilder::new(io_handle)
400 }
401
402 pub fn globals(&self) -> Rc<GlobalsMap> {
406 self.globals.clone()
407 }
408
409 pub fn source_map(&self) -> SourceCode {
413 self.source_map.clone()
414 }
415}
416
417impl<'co, 'ro, 'env> Evaluation<'co, 'ro, 'env> {
418 #[cfg(feature = "impure")]
419 pub fn builder_impure() -> EvaluationBuilder<'co, 'ro, 'env> {
420 EvaluationBuilder::new_impure()
421 }
422
423 pub fn builder_pure() -> EvaluationBuilder<'co, 'ro, 'env> {
424 EvaluationBuilder::new_pure()
425 }
426}
427
428impl Evaluation<'_, '_, '_> {
429 pub fn compile_only(
435 mut self,
436 code: impl AsRef<str>,
437 location: Option<PathBuf>,
438 ) -> EvaluationResult {
439 let mut result = EvaluationResult::default();
440 let source = self.source_map();
441
442 let location_str = location
443 .as_ref()
444 .map(|p| p.to_string_lossy().to_string())
445 .unwrap_or_else(|| "[code]".into());
446
447 let file = source.add_file(location_str, code.as_ref().to_string());
448
449 let mut noop_observer = observer::NoOpObserver::default();
450 let compiler_observer = self.compiler_observer.take().unwrap_or(&mut noop_observer);
451
452 parse_compile_internal(
453 &mut result,
454 code.as_ref(),
455 file,
456 location,
457 source,
458 self.globals,
459 self.env,
460 compiler_observer,
461 );
462
463 result
464 }
465
466 pub fn evaluate(
470 mut self,
471 code: impl AsRef<str>,
472 location: Option<PathBuf>,
473 ) -> EvaluationResult {
474 let mut result = EvaluationResult::default();
475 let source = self.source_map();
476
477 let location_str = location
478 .as_ref()
479 .map(|p| p.to_string_lossy().to_string())
480 .unwrap_or_else(|| "[code]".into());
481
482 let file = source.add_file(location_str, code.as_ref().to_string());
483
484 let mut noop_observer = observer::NoOpObserver::default();
485 let compiler_observer = self.compiler_observer.take().unwrap_or(&mut noop_observer);
486
487 let lambda = match parse_compile_internal(
488 &mut result,
489 code.as_ref(),
490 file.clone(),
491 location,
492 source.clone(),
493 self.globals.clone(),
494 self.env,
495 compiler_observer,
496 ) {
497 None => return result,
498 Some(cr) => cr,
499 };
500
501 let nix_path = self
505 .nix_path
506 .as_ref()
507 .and_then(|s| match nix_search_path::NixSearchPath::from_str(s) {
508 Ok(path) => Some(path),
509 Err(err) => {
510 result.warnings.push(EvalWarning {
511 kind: WarningKind::InvalidNixPath(err.to_string()),
512 span: file.span,
513 });
514 None
515 }
516 })
517 .unwrap_or_default();
518
519 let runtime_observer = self.runtime_observer.take().unwrap_or(&mut noop_observer);
520
521 let vm_result = run_lambda(
522 nix_path,
523 self.io_handle,
524 runtime_observer,
525 source.clone(),
526 self.globals,
527 lambda,
528 self.mode,
529 );
530
531 match vm_result {
532 Ok(mut runtime_result) => {
533 result.warnings.append(&mut runtime_result.warnings);
534 if let Value::Catchable(inner) = runtime_result.value {
535 result.errors.push(Error::new(
536 ErrorKind::CatchableError(*inner),
537 file.span,
538 source,
539 ));
540 return result;
541 }
542
543 result.value = Some(runtime_result.value);
544 }
545 Err(err) => {
546 result.errors.push(err);
547 }
548 }
549
550 result
551 }
552}
553
554#[allow(clippy::too_many_arguments)] fn parse_compile_internal(
558 result: &mut EvaluationResult,
559 code: &str,
560 file: Arc<codemap::File>,
561 location: Option<PathBuf>,
562 source: SourceCode,
563 globals: Rc<GlobalsMap>,
564 env: Option<&FxHashMap<SmolStr, Value>>,
565 compiler_observer: &mut dyn CompilerObserver,
566) -> Option<Rc<Lambda>> {
567 let parsed = rnix::ast::Root::parse(code);
568 let parse_errors = parsed.errors();
569
570 if !parse_errors.is_empty() {
571 result.errors.push(Error::new(
572 ErrorKind::ParseErrors(parse_errors.to_vec()),
573 file.span,
574 source,
575 ));
576 return None;
577 }
578
579 result.expr = parsed.tree().expr();
583
584 let compiler_result = match compiler::compile(
585 result.expr.as_ref().unwrap(),
586 location,
587 globals,
588 env,
589 &source,
590 &file,
591 compiler_observer,
592 ) {
593 Ok(result) => result,
594 Err(err) => {
595 result.errors.push(err);
596 return None;
597 }
598 };
599
600 result.warnings = compiler_result.warnings;
601 result.errors.extend(compiler_result.errors);
602
603 if !result.errors.is_empty() {
606 return None;
607 }
608
609 Some(compiler_result.lambda)
612}