1use std::path::PathBuf;
2use std::rc::Rc;
3use std::str::FromStr;
4
5use rustc_hash::FxHashMap;
6use smol_str::SmolStr;
7use std::fmt::Write;
8use tracing::instrument;
9use tvix_eval::{
10 builtins::impure_builtins,
11 observer::{DisassemblingObserver, TracingObserver},
12 ErrorKind, EvalIO, EvalMode, GlobalsMap, SourceCode, Value,
13};
14use tvix_glue::{
15 builtins::{add_derivation_builtins, add_import_builtins},
16 configure_nix_path,
17 tvix_io::TvixIO,
18 tvix_store_io::TvixStoreIO,
19};
20use tvix_simstore::simulated_store_builtins;
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(args: &Args) -> Rc<TvixStoreIO> {
30 let mut simstore = tvix_simstore::SimulatedStoreIO::default();
33 if let (Some(nix_path), Some(store_dir)) = (args.nix_path(), simstore.store_dir()) {
34 let search_path =
35 tvix_eval::NixSearchPath::from_str(&nix_path).expect("NIX_PATH was invalid");
36 for entry in search_path.get_entries() {
37 let path = entry.get_path();
38 if !path.starts_with(&store_dir) {
39 continue;
40 }
41
42 simstore
43 .add_passthru(&path.to_string_lossy(), path.to_path_buf())
44 .expect("setting passthru failed");
45 }
46 }
47
48 Rc::new(TvixStoreIO::new(simstore))
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
52pub enum AllowIncomplete {
53 Allow,
54 #[default]
55 RequireComplete,
56}
57
58impl AllowIncomplete {
59 fn allow(&self) -> bool {
60 matches!(self, Self::Allow)
61 }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub struct IncompleteInput;
66
67pub struct EvalResult {
68 value: Option<Value>,
69 globals: Rc<GlobalsMap>,
70}
71
72#[allow(clippy::too_many_arguments)]
74pub fn evaluate(
75 tvix_store_io: Rc<TvixStoreIO>,
76 code: &str,
77 path: Option<PathBuf>,
78 args: &Args,
79 allow_incomplete: AllowIncomplete,
80 env: Option<&FxHashMap<SmolStr, Value>>,
81 globals: Option<Rc<GlobalsMap>>,
82 source_map: Option<SourceCode>,
83) -> Result<EvalResult, IncompleteInput> {
84 let mut eval_builder = tvix_eval::Evaluation::builder(Rc::new(TvixIO::new(
85 tvix_store_io.clone() as Rc<dyn EvalIO>,
86 )) as Rc<dyn EvalIO>)
87 .enable_import()
88 .env(env);
89
90 if args.strict {
91 eval_builder = eval_builder.mode(EvalMode::Strict);
92 }
93
94 match globals {
95 Some(globals) => {
96 eval_builder = eval_builder.with_globals(globals);
97 }
98 None => {
99 eval_builder = eval_builder.add_builtins(impure_builtins());
100 eval_builder = eval_builder.add_builtins(simulated_store_builtins());
101 eval_builder = add_derivation_builtins(eval_builder, Rc::clone(&tvix_store_io));
102 eval_builder = add_import_builtins(eval_builder, Rc::clone(&tvix_store_io));
104 }
105 };
106 eval_builder = configure_nix_path(eval_builder, &args.nix_path());
107
108 if let Some(source_map) = source_map {
109 eval_builder = eval_builder.with_source_map(source_map);
110 }
111
112 let source_map = eval_builder.source_map().clone();
113 let (result, globals) = {
114 let mut compiler_observer =
115 DisassemblingObserver::new(source_map.clone(), std::io::stderr());
116 if args.dump_bytecode {
117 eval_builder.set_compiler_observer(Some(&mut compiler_observer));
118 }
119
120 let mut runtime_observer = TracingObserver::new(std::io::stderr());
121 if args.trace_runtime {
122 if args.trace_runtime_timing {
123 runtime_observer.enable_timing()
124 }
125 eval_builder.set_runtime_observer(Some(&mut runtime_observer));
126 }
127
128 let eval = eval_builder.build();
129 let globals = eval.globals();
130 let result = eval.evaluate(code, path);
131 (result, globals)
132 };
133
134 if allow_incomplete.allow()
135 && result.errors.iter().any(|err| {
136 matches!(
137 &err.kind,
138 ErrorKind::ParseErrors(pes)
139 if pes.iter().any(|pe| matches!(pe, rnix::parser::ParseError::UnexpectedEOF))
140 )
141 })
142 {
143 return Err(IncompleteInput);
144 }
145
146 if args.display_ast {
147 if let Some(ref expr) = result.expr {
148 eprintln!("AST: {}", tvix_eval::pretty_print_expr(expr));
149 }
150 }
151
152 for error in &result.errors {
153 error.fancy_format_stderr();
154 }
155
156 if !args.no_warnings {
157 for warning in &result.warnings {
158 warning.fancy_format_stderr(&source_map);
159 }
160 }
161
162 if let Some(dumpdir) = &args.drv_dumpdir {
163 std::fs::create_dir_all(dumpdir).expect("failed to create drv dumpdir");
165 tvix_store_io
166 .known_paths
167 .borrow()
168 .get_derivations()
169 .filter(|(drv_path, _)| !dumpdir.join(drv_path.to_string()).exists())
171 .for_each(|(drv_path, drv)| {
172 std::fs::write(dumpdir.join(drv_path.to_string()), drv.to_aterm_bytes())
173 .expect("failed to write drv to dumpdir");
174 })
175 }
176
177 Ok(EvalResult {
178 globals,
179 value: result.value,
180 })
181}
182
183pub struct InterpretResult {
184 output: String,
185 success: bool,
186 pub(crate) globals: Option<Rc<GlobalsMap>>,
187}
188
189impl InterpretResult {
190 pub fn empty_success(globals: Option<Rc<GlobalsMap>>) -> Self {
191 Self {
192 output: String::new(),
193 success: true,
194 globals,
195 }
196 }
197
198 pub fn finalize(self) -> bool {
199 print!("{}", self.output);
200 self.success
201 }
202
203 pub fn output(&self) -> &str {
204 &self.output
205 }
206
207 pub fn success(&self) -> bool {
208 self.success
209 }
210}
211
212#[instrument(skip_all, fields(indicatif.pb_show=tracing::field::Empty))]
216#[allow(clippy::too_many_arguments)]
217pub fn interpret(
218 tvix_store_io: Rc<TvixStoreIO>,
219 code: &str,
220 path: Option<PathBuf>,
221 args: &Args,
222 explain: bool,
223 allow_incomplete: AllowIncomplete,
224 env: Option<&FxHashMap<SmolStr, Value>>,
225 globals: Option<Rc<GlobalsMap>>,
226 source_map: Option<SourceCode>,
227) -> Result<InterpretResult, IncompleteInput> {
228 let mut output = String::new();
229 let result = evaluate(
230 tvix_store_io,
231 code,
232 path,
233 args,
234 allow_incomplete,
235 env,
236 globals,
237 source_map,
238 )?;
239
240 if let Some(value) = result.value.as_ref() {
241 if explain {
242 writeln!(&mut output, "=> {}", value.explain()).unwrap();
243 } else if args.raw {
244 writeln!(&mut output, "{}", value.to_contextful_str().unwrap()).unwrap();
245 } else {
246 writeln!(&mut output, "=> {} :: {}", value, value.type_of()).unwrap();
247 }
248 }
249
250 Ok(InterpretResult {
252 output,
253 success: result.value.is_some(),
254 globals: Some(result.globals),
255 })
256}