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