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 observer::OptionalCompilerObserver;
41use rustc_hash::FxHashMap;
42use std::path::PathBuf;
43use std::rc::Rc;
44use std::str::FromStr;
45use std::sync::Arc;
46
47use crate::observer::{CompilerObserver, RuntimeObserver};
48use crate::value::Lambda;
49use crate::vm::run_lambda;
50
51pub use crate::compiler::{CompilationOutput, GlobalsMap, compile, prepare_globals};
53pub use crate::errors::{AddContext, CatchableErrorKind, Error, ErrorKind, EvalResult};
54pub use crate::io::{DummyIO, EvalIO, FileType};
55pub use crate::pretty_ast::pretty_print_expr;
56pub use crate::source::SourceCode;
57pub use crate::value::{NixContext, NixContextElement};
58pub use crate::vm::{EvalMode, generators};
59pub use crate::warnings::{EvalWarning, WarningKind};
60pub use builtin_macros;
61use smol_str::SmolStr;
62
63pub use crate::value::{Builtin, CoercionKind, NixAttrs, NixList, NixString, Value};
64
65pub(crate) const REPL_LOCATION: &str = "[code]";
66
67#[cfg(feature = "impure")]
68pub use crate::io::StdIO;
69
70struct BuilderBuiltins {
71 builtins: Vec<(&'static str, Value)>,
72 src_builtins: Vec<(&'static str, &'static str)>,
73}
74
75enum BuilderGlobals {
76 Builtins(BuilderBuiltins),
77 Globals(Rc<GlobalsMap>),
78}
79
80pub struct EvaluationBuilder<'co, 'ro, 'env, IO> {
91 source_map: Option<SourceCode>,
92 globals: BuilderGlobals,
93 env: Option<&'env FxHashMap<SmolStr, Value>>,
94 io_handle: IO,
95 enable_import: bool,
96 mode: EvalMode,
97 nix_path: Option<String>,
98 compiler_observer: Option<&'co mut dyn CompilerObserver>,
99 runtime_observer: Option<&'ro mut dyn RuntimeObserver>,
100}
101
102impl<'co, 'ro, 'env, IO> EvaluationBuilder<'co, 'ro, 'env, IO>
103where
104 IO: AsRef<dyn EvalIO> + 'static,
105{
106 pub fn build(self) -> Evaluation<'co, 'ro, 'env, IO> {
114 let source_map = self.source_map.unwrap_or_default();
115
116 let globals = match self.globals {
117 BuilderGlobals::Globals(globals) => globals,
118 BuilderGlobals::Builtins(BuilderBuiltins {
119 mut builtins,
120 src_builtins,
121 }) => {
122 if let Some(store_dir) = self.io_handle.as_ref().store_dir() {
124 builtins.push(("storeDir", store_dir.into()));
125 }
126
127 crate::compiler::prepare_globals(
128 builtins,
129 src_builtins,
130 source_map.clone(),
131 self.enable_import,
132 )
133 }
134 };
135
136 Evaluation {
137 source_map,
138 globals,
139 env: self.env,
140 io_handle: self.io_handle,
141 mode: self.mode,
142 nix_path: self.nix_path,
143 compiler_observer: self.compiler_observer,
144 runtime_observer: self.runtime_observer,
145 }
146 }
147}
148
149impl<'co, 'ro, 'env, IO> EvaluationBuilder<'co, 'ro, 'env, IO> {
152 pub fn new(io_handle: IO) -> Self {
153 let mut builtins = builtins::pure_builtins();
154 builtins.extend(builtins::placeholders()); Self {
157 source_map: None,
158 enable_import: false,
159 io_handle,
160 globals: BuilderGlobals::Builtins(BuilderBuiltins {
161 builtins,
162 src_builtins: vec![],
163 }),
164 env: None,
165 mode: Default::default(),
166 nix_path: None,
167 compiler_observer: None,
168 runtime_observer: None,
169 }
170 }
171
172 pub fn io_handle<IO2>(self, io_handle: IO2) -> EvaluationBuilder<'co, 'ro, 'env, IO2> {
173 EvaluationBuilder {
174 io_handle,
175 source_map: self.source_map,
176 globals: self.globals,
177 env: self.env,
178 enable_import: self.enable_import,
179 mode: self.mode,
180 nix_path: self.nix_path,
181 compiler_observer: self.compiler_observer,
182 runtime_observer: self.runtime_observer,
183 }
184 }
185
186 pub fn with_enable_import(self, enable_import: bool) -> Self {
187 Self {
188 enable_import,
189 ..self
190 }
191 }
192
193 pub fn disable_import(self) -> Self {
194 self.with_enable_import(false)
195 }
196
197 pub fn enable_import(self) -> Self {
198 self.with_enable_import(true)
199 }
200
201 fn builtins_mut(&mut self) -> &mut BuilderBuiltins {
202 match &mut self.globals {
203 BuilderGlobals::Builtins(builtins) => builtins,
204 BuilderGlobals::Globals(_) => {
205 panic!("Cannot modify builtins on an EvaluationBuilder with globals configured")
206 }
207 }
208 }
209
210 pub fn add_builtins<I>(mut self, builtins: I) -> Self
217 where
218 I: IntoIterator<Item = (&'static str, Value)>,
219 {
220 self.builtins_mut().builtins.extend(builtins);
221 self
222 }
223
224 pub fn add_src_builtin(mut self, name: &'static str, src: &'static str) -> Self {
231 self.builtins_mut().src_builtins.push((name, src));
232 self
233 }
234
235 pub fn with_globals(self, globals: Rc<GlobalsMap>) -> Self {
242 Self {
243 globals: BuilderGlobals::Globals(globals),
244 ..self
245 }
246 }
247
248 pub fn with_source_map(self, source_map: SourceCode) -> Self {
249 debug_assert!(
250 self.source_map.is_none(),
251 "Cannot set the source_map on an EvaluationBuilder twice"
252 );
253 Self {
254 source_map: Some(source_map),
255 ..self
256 }
257 }
258
259 pub fn mode(self, mode: EvalMode) -> Self {
260 Self { mode, ..self }
261 }
262
263 pub fn nix_path(self, nix_path: Option<String>) -> Self {
264 Self { nix_path, ..self }
265 }
266
267 pub fn env(self, env: Option<&'env FxHashMap<SmolStr, Value>>) -> Self {
268 Self { env, ..self }
269 }
270
271 pub fn compiler_observer(
272 self,
273 compiler_observer: Option<&'co mut dyn CompilerObserver>,
274 ) -> Self {
275 Self {
276 compiler_observer,
277 ..self
278 }
279 }
280
281 pub fn set_compiler_observer(
282 &mut self,
283 compiler_observer: Option<&'co mut dyn CompilerObserver>,
284 ) {
285 self.compiler_observer = compiler_observer;
286 }
287
288 pub fn runtime_observer(self, runtime_observer: Option<&'ro mut dyn RuntimeObserver>) -> Self {
289 Self {
290 runtime_observer,
291 ..self
292 }
293 }
294
295 pub fn set_runtime_observer(&mut self, runtime_observer: Option<&'ro mut dyn RuntimeObserver>) {
296 self.runtime_observer = runtime_observer;
297 }
298}
299
300impl<IO> EvaluationBuilder<'_, '_, '_, IO> {
301 pub fn source_map(&mut self) -> &SourceCode {
302 self.source_map.get_or_insert_with(SourceCode::default)
303 }
304}
305
306impl EvaluationBuilder<'_, '_, '_, Box<dyn EvalIO>> {
307 pub fn new_pure() -> Self {
310 Self::new(Box::new(DummyIO) as Box<dyn EvalIO>).with_enable_import(false)
311 }
312
313 #[cfg(feature = "impure")]
314 pub fn enable_impure(mut self, io: Option<Box<dyn EvalIO>>) -> Self {
320 self.io_handle = io.unwrap_or_else(|| Box::new(StdIO) as Box<dyn EvalIO>);
321 self.enable_import = true;
322 self.builtins_mut()
323 .builtins
324 .extend(builtins::impure_builtins());
325
326 if self.nix_path.is_none() {
329 self.nix_path = std::env::var("NIX_PATH").ok();
330 }
331 self
332 }
333
334 #[cfg(feature = "impure")]
335 pub fn new_impure() -> Self {
337 Self::new_pure().enable_impure(None)
338 }
339}
340
341pub struct Evaluation<'co, 'ro, 'env, IO> {
348 source_map: SourceCode,
350
351 globals: Rc<GlobalsMap>,
353
354 env: Option<&'env FxHashMap<SmolStr, Value>>,
356
357 io_handle: IO,
362
363 mode: EvalMode,
367
368 nix_path: Option<String>,
371
372 compiler_observer: Option<&'co mut dyn CompilerObserver>,
375
376 runtime_observer: Option<&'ro mut dyn RuntimeObserver>,
379}
380
381#[derive(Debug, Default)]
385pub struct EvaluationResult {
386 pub value: Option<Value>,
388
389 pub errors: Vec<Error>,
391
392 pub warnings: Vec<EvalWarning>,
395
396 pub expr: Option<rnix::ast::Expr>,
398}
399
400impl<'co, 'ro, 'env, IO> Evaluation<'co, 'ro, 'env, IO> {
401 pub fn builder(io_handle: IO) -> EvaluationBuilder<'co, 'ro, 'env, IO> {
405 EvaluationBuilder::new(io_handle)
406 }
407
408 pub fn globals(&self) -> Rc<GlobalsMap> {
412 self.globals.clone()
413 }
414
415 pub fn source_map(&self) -> SourceCode {
419 self.source_map.clone()
420 }
421}
422
423impl<'co, 'ro, 'env> Evaluation<'co, 'ro, 'env, Box<dyn EvalIO>> {
424 #[cfg(feature = "impure")]
425 pub fn builder_impure() -> EvaluationBuilder<'co, 'ro, 'env, Box<dyn EvalIO>> {
426 EvaluationBuilder::new_impure()
427 }
428
429 pub fn builder_pure() -> EvaluationBuilder<'co, 'ro, 'env, Box<dyn EvalIO>> {
430 EvaluationBuilder::new_pure()
431 }
432}
433
434impl<IO> Evaluation<'_, '_, '_, IO>
435where
436 IO: AsRef<dyn EvalIO> + 'static,
437{
438 pub fn compile_only(
444 mut self,
445 code: impl AsRef<str>,
446 location: Option<PathBuf>,
447 ) -> EvaluationResult {
448 let mut result = EvaluationResult::default();
449 let source = self.source_map();
450
451 let location_str = location
452 .as_ref()
453 .map(|p| p.to_string_lossy().to_string())
454 .unwrap_or_else(|| REPL_LOCATION.into());
455
456 let file = source.add_file(location_str, code.as_ref().to_string());
457
458 let compiler_observer = self.compiler_observer.take();
459
460 parse_compile_internal(
461 &mut result,
462 code.as_ref(),
463 file,
464 location,
465 source,
466 self.globals,
467 self.env,
468 compiler_observer.into(),
469 );
470
471 result
472 }
473
474 pub fn evaluate(
478 mut self,
479 code: impl AsRef<str>,
480 location: Option<PathBuf>,
481 ) -> EvaluationResult {
482 let mut result = EvaluationResult::default();
483 let source = self.source_map();
484
485 let location_str = location
486 .as_ref()
487 .map(|p| p.to_string_lossy().to_string())
488 .unwrap_or_else(|| REPL_LOCATION.into());
489
490 let file = source.add_file(location_str, code.as_ref().to_string());
491
492 let compiler_observer = self.compiler_observer.take();
493
494 let lambda = match parse_compile_internal(
495 &mut result,
496 code.as_ref(),
497 file.clone(),
498 location,
499 source.clone(),
500 self.globals.clone(),
501 self.env,
502 compiler_observer.into(),
503 ) {
504 None => return result,
505 Some(cr) => cr,
506 };
507
508 let nix_path = self
512 .nix_path
513 .as_ref()
514 .and_then(|s| match nix_search_path::NixSearchPath::from_str(s) {
515 Ok(path) => Some(path),
516 Err(err) => {
517 result.warnings.push(EvalWarning {
518 kind: WarningKind::InvalidNixPath(err.to_string()),
519 span: file.span,
520 });
521 None
522 }
523 })
524 .unwrap_or_default();
525
526 let runtime_observer = self.runtime_observer.take();
527
528 let vm_result = run_lambda(
529 nix_path,
530 self.io_handle,
531 runtime_observer,
532 source.clone(),
533 self.globals,
534 lambda,
535 self.mode,
536 );
537
538 match vm_result {
539 Ok(mut runtime_result) => {
540 result.warnings.append(&mut runtime_result.warnings);
541 if let Value::Catchable(inner) = runtime_result.value {
542 result.errors.push(Error::new(
543 ErrorKind::CatchableError(*inner),
544 file.span,
545 source,
546 ));
547 return result;
548 }
549
550 result.value = Some(runtime_result.value);
551 }
552 Err(err) => {
553 result.errors.push(err);
554 }
555 }
556
557 result
558 }
559}
560
561#[allow(clippy::too_many_arguments)] fn parse_compile_internal(
565 result: &mut EvaluationResult,
566 code: &str,
567 file: Arc<codemap::File>,
568 location: Option<PathBuf>,
569 source: SourceCode,
570 globals: Rc<GlobalsMap>,
571 env: Option<&FxHashMap<SmolStr, Value>>,
572 compiler_observer: OptionalCompilerObserver<'_>,
573) -> Option<Rc<Lambda>> {
574 let parsed = rnix::ast::Root::parse(code);
575 let parse_errors = parsed.errors();
576
577 if !parse_errors.is_empty() {
578 result.errors.push(Error::new(
579 ErrorKind::ParseErrors(parse_errors.to_vec()),
580 file.span,
581 source,
582 ));
583 return None;
584 }
585
586 result.expr = parsed.tree().expr();
590
591 let compiler_result = match compiler::compile(
592 result.expr.as_ref().unwrap(),
593 location,
594 globals,
595 env,
596 &source,
597 &file,
598 compiler_observer,
599 ) {
600 Ok(result) => result,
601 Err(err) => {
602 result.errors.push(err);
603 return None;
604 }
605 };
606
607 result.warnings = compiler_result.warnings;
608 result.errors.extend(compiler_result.errors);
609
610 if !result.errors.is_empty() {
613 return None;
614 }
615
616 Some(compiler_result.lambda)
619}