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