1use std::path::PathBuf;
2use std::rc::Rc;
3
4use rustc_hash::FxHashMap;
5use smol_str::SmolStr;
6use snix_build::buildservice;
7use snix_eval::{
8 ErrorKind, EvalIO, EvalMode, GlobalsMap, SourceCode, Value,
9 builtins::impure_builtins,
10 observer::{DisassemblingObserver, TracingObserver},
11};
12use snix_glue::{
13 builtins::{add_derivation_builtins, add_fetcher_builtins, add_import_builtins},
14 configure_nix_path,
15 snix_io::SnixIO,
16 snix_store_io::SnixStoreIO,
17};
18use std::fmt::Write;
19use tracing::{Span, info_span};
20use tracing_indicatif::span_ext::IndicatifSpanExt;
21
22pub mod args;
23pub mod assignment;
24pub mod repl;
25
26pub use args::Args;
27pub use repl::Repl;
28
29pub fn init_io_handle(tokio_runtime: &tokio::runtime::Runtime, args: &Args) -> Rc<SnixStoreIO> {
30 let (blob_service, directory_service, path_info_service, nar_calculation_service) =
31 tokio_runtime
32 .block_on(snix_store::utils::construct_services(
33 args.service_addrs.clone(),
34 ))
35 .expect("unable to setup {blob|directory|pathinfo}service before interpreter setup");
36
37 let build_service = tokio_runtime
38 .block_on({
39 let blob_service = blob_service.clone();
40 let directory_service = directory_service.clone();
41 async move {
42 buildservice::from_addr(
43 &args.build_service_addr,
44 blob_service.clone(),
45 directory_service.clone(),
46 )
47 .await
48 }
49 })
50 .expect("unable to setup buildservice before interpreter setup");
51
52 Rc::new(SnixStoreIO::new(
53 blob_service.clone(),
54 directory_service.clone(),
55 path_info_service,
56 nar_calculation_service.into(),
57 build_service.into(),
58 tokio_runtime.handle().clone(),
59 args.hashed_mirrors.clone(),
60 ))
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
64pub enum AllowIncomplete {
65 Allow,
66 #[default]
67 RequireComplete,
68}
69
70impl AllowIncomplete {
71 fn allow(&self) -> bool {
72 matches!(self, Self::Allow)
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub struct IncompleteInput;
78
79pub struct EvalResult {
80 value: Option<Value>,
81 globals: Rc<GlobalsMap>,
82}
83
84#[allow(clippy::too_many_arguments)]
86pub fn evaluate<E: std::io::Write + Clone + Send>(
87 stderr: &mut E,
88 snix_store_io: Rc<SnixStoreIO>,
89 code: &str,
90 path: Option<PathBuf>,
91 args: &Args,
92 allow_incomplete: AllowIncomplete,
93 env: Option<&FxHashMap<SmolStr, Value>>,
94 globals: Option<Rc<GlobalsMap>>,
95 source_map: Option<SourceCode>,
96) -> Result<EvalResult, IncompleteInput> {
97 let mut eval_builder = snix_eval::Evaluation::builder(Box::new(SnixIO::new(
98 snix_store_io.clone() as Rc<dyn EvalIO>,
99 )) as Box<dyn EvalIO>)
100 .enable_import()
101 .env(env);
102
103 if args.strict {
104 eval_builder = eval_builder.mode(EvalMode::Strict);
105 }
106
107 match globals {
108 Some(globals) => {
109 eval_builder = eval_builder.with_globals(globals);
110 }
111 None => {
112 eval_builder = eval_builder.add_builtins(impure_builtins());
113 eval_builder = add_derivation_builtins(eval_builder, Rc::clone(&snix_store_io));
114 eval_builder = add_fetcher_builtins(eval_builder, Rc::clone(&snix_store_io));
115 eval_builder = add_import_builtins(eval_builder, Rc::clone(&snix_store_io));
116 }
117 };
118 eval_builder = configure_nix_path(eval_builder, &args.nix_path());
119
120 if let Some(source_map) = source_map {
121 eval_builder = eval_builder.with_source_map(source_map);
122 }
123
124 let source_map = eval_builder.source_map().clone();
125
126 let mut compiler_observer = DisassemblingObserver::new(source_map.clone(), stderr.clone());
127 if args.dump_bytecode {
128 eval_builder.set_compiler_observer(Some(&mut compiler_observer));
129 }
130
131 let mut runtime_observer = TracingObserver::new(stderr.clone());
132 if args.trace_runtime {
133 if args.trace_runtime_timing {
134 runtime_observer.enable_timing()
135 }
136 eval_builder.set_runtime_observer(Some(&mut runtime_observer));
137 }
138
139 let eval = eval_builder.build();
140
141 let span = if !args.trace_runtime && !args.dump_bytecode {
142 info_span!("evaluate", indicatif.pb_show = tracing::field::Empty)
143 } else {
144 info_span!("evaluate", indicatif.pb_hide = tracing::field::Empty)
145 };
146 let (result, globals) = span.in_scope(|| {
147 let span = Span::current();
148
149 span.pb_set_message("Evaluating…");
150 span.pb_start();
151
152 let globals = eval.globals();
153 let result = eval.evaluate(code, path);
154 (result, globals)
155 });
156 drop(span);
157
158 if allow_incomplete.allow()
159 && result.errors.iter().any(|err| {
160 matches!(
161 &err.kind,
162 ErrorKind::ParseErrors(pes)
163 if pes.iter().any(|pe| matches!(pe, rnix::parser::ParseError::UnexpectedEOF))
164 )
165 })
166 {
167 return Err(IncompleteInput);
168 }
169
170 if args.display_ast {
171 if let Some(ref expr) = result.expr {
172 writeln!(stderr, "AST: {}", snix_eval::pretty_print_expr(expr)).unwrap();
173 }
174 }
175
176 for error in &result.errors {
177 error.fancy_format_write(stderr);
178 }
179
180 if !args.no_warnings {
181 for warning in &result.warnings {
182 warning.fancy_format_write(stderr, &source_map);
183 }
184 }
185
186 if let Some(dumpdir) = &args.drv_dumpdir {
187 std::fs::create_dir_all(dumpdir).expect("failed to create drv dumpdir");
189 snix_store_io
190 .known_paths
191 .borrow()
192 .get_derivations()
193 .filter(|(drv_path, _)| !dumpdir.join(drv_path.to_string()).exists())
195 .for_each(|(drv_path, drv)| {
196 std::fs::write(dumpdir.join(drv_path.to_string()), drv.to_aterm_bytes())
197 .expect("failed to write drv to dumpdir");
198 })
199 }
200
201 Ok(EvalResult {
202 globals,
203 value: result.value,
204 })
205}
206
207pub struct InterpretResult {
208 output: String,
209 success: bool,
210 pub(crate) globals: Option<Rc<GlobalsMap>>,
211}
212
213impl InterpretResult {
214 pub fn empty_success(globals: Option<Rc<GlobalsMap>>) -> Self {
215 Self {
216 output: String::new(),
217 success: true,
218 globals,
219 }
220 }
221
222 pub fn finalize<E: std::io::Write>(self, stderr: &mut E) -> bool {
223 write!(stderr, "{}", self.output).unwrap();
224 self.success
225 }
226
227 pub fn output(&self) -> &str {
228 &self.output
229 }
230
231 pub fn success(&self) -> bool {
232 self.success
233 }
234}
235
236#[allow(clippy::too_many_arguments)]
240pub fn interpret<E: std::io::Write + Clone + Send>(
241 stderr: &mut E,
242 snix_store_io: Rc<SnixStoreIO>,
243 code: &str,
244 path: Option<PathBuf>,
245 args: &Args,
246 explain: bool,
247 allow_incomplete: AllowIncomplete,
248 env: Option<&FxHashMap<SmolStr, Value>>,
249 globals: Option<Rc<GlobalsMap>>,
250 source_map: Option<SourceCode>,
251) -> Result<InterpretResult, IncompleteInput> {
252 let mut output = String::new();
253 let result = evaluate(
254 stderr,
255 snix_store_io,
256 code,
257 path,
258 args,
259 allow_incomplete,
260 env,
261 globals,
262 source_map,
263 )?;
264
265 if let Some(value) = result.value.as_ref() {
266 if explain {
267 writeln!(&mut output, "=> {}", value.explain()).unwrap();
268 } else if args.raw {
269 writeln!(&mut output, "{value}").unwrap();
270 } else {
271 writeln!(&mut output, "=> {} :: {}", value, value.type_of()).unwrap();
272 }
273 }
274
275 Ok(InterpretResult {
277 output,
278 success: result.value.is_some(),
279 globals: Some(result.globals),
280 })
281}