1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3
4use rsjsonnet_lang::arena::Arena;
5use rsjsonnet_lang::interner::InternedStr;
6use rsjsonnet_lang::program::{ImportError, LoadError, NativeError, Program, Thunk, Value};
7use rsjsonnet_lang::span::{SourceId, SpanId};
8
9use crate::src_manager::SrcManager;
10
11type NativeFunc<'p> = Box<dyn FnMut(&mut Program<'p>, &[Value<'p>]) -> Result<Value<'p>, String>>;
12
13pub struct Session<'p> {
14 program: Program<'p>,
15 inner: SessionInner<'p>,
16}
17
18struct SessionInner<'p> {
19 src_mgr: SrcManager,
20 source_paths: HashMap<SourceId, PathBuf>,
21 source_cache: HashMap<PathBuf, Thunk<'p>>,
22 search_paths: Vec<PathBuf>,
23 native_funcs: HashMap<InternedStr<'p>, NativeFunc<'p>>,
24 custom_stack_trace: Vec<String>,
25 max_trace: usize,
26 #[cfg(feature = "crossterm")]
27 colored_output: bool,
28}
29
30impl<'p> Session<'p> {
31 pub fn new(arena: &'p Arena) -> Self {
32 let program = Program::new(arena);
33 let (stdlib_src_id, stdlib_data) = program.get_stdlib_source();
34
35 let mut src_mgr = SrcManager::new();
36 src_mgr.insert_file(stdlib_src_id, "<stdlib>".into(), stdlib_data.into());
37
38 Self {
39 program,
40 inner: SessionInner {
41 src_mgr,
42 source_paths: HashMap::new(),
43 source_cache: HashMap::new(),
44 search_paths: Vec::new(),
45 native_funcs: HashMap::new(),
46 custom_stack_trace: Vec::new(),
47 max_trace: usize::MAX,
48 #[cfg(feature = "crossterm")]
49 colored_output: false,
50 },
51 }
52 }
53
54 #[must_use]
56 #[inline]
57 pub fn program(&self) -> &Program<'p> {
58 &self.program
59 }
60
61 #[must_use]
63 #[inline]
64 pub fn program_mut(&mut self) -> &mut Program<'p> {
65 &mut self.program
66 }
67
68 pub fn set_max_trace(&mut self, max_trace: usize) {
70 self.inner.max_trace = max_trace;
71 }
72
73 #[cfg(feature = "crossterm")]
74 pub fn set_colored_output(&mut self, colored_output: bool) {
75 self.inner.colored_output = colored_output;
76 }
77
78 pub fn add_search_path(&mut self, path: PathBuf) {
79 self.inner.search_paths.push(path);
80 }
81
82 pub fn add_native_func<const N: usize, F>(
113 &mut self,
114 name: &str,
115 params: &[&str; N],
116 mut func: F,
117 ) where
118 F: FnMut(&mut Program<'p>, &[Value<'p>; N]) -> Result<Value<'p>, String> + 'static,
119 {
120 let name = self.program.intern_str(name);
121 let params: Vec<_> = params.iter().map(|p| self.program.intern_str(p)).collect();
122
123 self.program.register_native_func(name, ¶ms);
124 self.inner.native_funcs.insert(
125 name,
126 Box::new(move |program, args| func(program, args.try_into().unwrap())),
127 );
128 }
129
130 pub fn load_virt_file(&mut self, repr_path: &str, data: Vec<u8>) -> Option<Thunk<'p>> {
138 self.inner
139 .load_virt_file(&mut self.program, repr_path, data)
140 }
141
142 pub fn load_real_file(&mut self, path: &Path) -> Option<Thunk<'p>> {
147 self.inner.load_real_file(&mut self.program, path)
148 }
149
150 pub fn eval_value(&mut self, thunk: &Thunk<'p>) -> Option<Value<'p>> {
155 match self.program.eval_value(thunk, &mut self.inner) {
156 Ok(v) => Some(v),
157 Err(e) => {
158 self.inner.print_eval_error(&self.program, &e);
159 None
160 }
161 }
162 }
163
164 pub fn eval_call(
169 &mut self,
170 func: &Thunk<'p>,
171 pos_args: &[Thunk<'p>],
172 named_args: &[(InternedStr<'p>, Thunk<'p>)],
173 ) -> Option<Value<'p>> {
174 match self
175 .program
176 .eval_call(func, pos_args, named_args, &mut self.inner)
177 {
178 Ok(v) => Some(v),
179 Err(e) => {
180 self.inner.print_eval_error(&self.program, &e);
181 None
182 }
183 }
184 }
185
186 pub fn manifest_json(&mut self, value: &Value<'p>, multiline: bool) -> Option<String> {
191 match self.program.manifest_json(value, multiline) {
192 Ok(s) => Some(s),
193 Err(e) => {
194 self.inner.print_eval_error(&self.program, &e);
195 None
196 }
197 }
198 }
199
200 pub fn print_error(&self, msg: &str) {
201 self.inner.print_error(msg);
202 }
203
204 pub fn print_note(&self, msg: &str) {
205 self.inner.print_note(msg);
206 }
207
208 pub fn push_custom_stack_trace_item(&mut self, text: String) {
210 self.inner.custom_stack_trace.push(text);
211 }
212
213 pub fn pop_custom_stack_trace_item(&mut self) {
216 self.inner.custom_stack_trace.pop().unwrap();
217 }
218}
219
220impl<'p> SessionInner<'p> {
221 fn load_virt_file(
222 &mut self,
223 program: &mut Program<'p>,
224 repr_path: &str,
225 data: Vec<u8>,
226 ) -> Option<Thunk<'p>> {
227 let (span_ctx, source_id) = program.span_manager_mut().insert_source_context(data.len());
228
229 self.src_mgr
230 .insert_file(source_id, repr_path.into(), data.into_boxed_slice());
231 let data = self.src_mgr.get_file_data(source_id);
232
233 match program.load_source(span_ctx, data, true, repr_path) {
234 Ok(thunk) => Some(thunk),
235 Err(ref e) => {
236 self.print_load_error(program, e);
237 None
238 }
239 }
240 }
241
242 fn load_real_file(&mut self, program: &mut Program<'p>, path: &Path) -> Option<Thunk<'p>> {
243 let norm_path = match path.canonicalize() {
244 Ok(p) => p,
245 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
246 self.print_error(format_args!("file {path:?} does not exist"));
247 return None;
248 }
249 Err(e) => {
250 self.print_error(format_args!("failed to canonicalize path {path:?}: {e}"));
251 return None;
252 }
253 };
254 if let Some(thunk) = self.source_cache.get(&norm_path) {
255 return Some(thunk.clone());
256 }
257
258 let data = match std::fs::read(path) {
259 Ok(data) => data,
260 Err(e) => {
261 self.print_error(format_args!("failed to read {path:?}: {e}"));
262 return None;
263 }
264 };
265
266 let repr_path = path.display().to_string();
267
268 let (span_ctx, source_id) = program.span_manager_mut().insert_source_context(data.len());
269 self.src_mgr
270 .insert_file(source_id, repr_path.clone(), data.into_boxed_slice());
271 self.source_paths.insert(source_id, path.to_path_buf());
272 let data = self.src_mgr.get_file_data(source_id);
273
274 match program.load_source(span_ctx, data, true, &repr_path) {
275 Ok(thunk) => {
276 self.source_cache.insert(norm_path, thunk.clone());
277 Some(thunk)
278 }
279 Err(ref e) => {
280 self.print_load_error(program, e);
281 None
282 }
283 }
284 }
285
286 fn find_import(&self, program: &Program<'p>, from: SpanId, path: &str) -> Option<PathBuf> {
287 let path = Path::new(path);
288 if path.is_absolute() {
289 if path.exists() {
290 Some(path.to_path_buf())
291 } else {
292 None
293 }
294 } else {
295 let (from_ctx, _, _) = program.span_manager().get_span(from);
296 let from_dir_path = match *program.span_manager().get_context(from_ctx) {
297 rsjsonnet_lang::span::SpanContext::Source(from_src) => {
298 self.source_paths.get(&from_src).and_then(|p| p.parent())
299 }
300 };
301
302 for base_path in from_dir_path
303 .into_iter()
304 .chain(self.search_paths.iter().map(PathBuf::as_path))
305 {
306 let full_path = base_path.join(path);
307 if full_path.exists() {
308 return Some(full_path);
309 }
310 }
311 None
312 }
313 }
314
315 fn print_rich_message(&self, msg: &[(String, crate::print::TextPartKind)]) {
316 #[cfg(feature = "crossterm")]
317 if self.colored_output {
318 crate::print::output_stderr_colored(msg);
319 return;
320 }
321
322 crate::print::output_stderr_plain(msg);
323 }
324
325 fn print_load_error(&self, program: &Program<'p>, error: &LoadError) {
326 match error {
327 LoadError::Lex(e) => {
328 let msg =
329 crate::report::lexer::render_error(e, program.span_manager(), &self.src_mgr);
330 self.print_rich_message(&msg);
331 }
332 LoadError::Parse(e) => {
333 let msg =
334 crate::report::parser::render_error(e, program.span_manager(), &self.src_mgr);
335 self.print_rich_message(&msg);
336 }
337 LoadError::Analyze(e) => {
338 let msg =
339 crate::report::analyze::render_error(e, program.span_manager(), &self.src_mgr);
340 self.print_rich_message(&msg);
341 }
342 }
343 }
344
345 fn print_eval_error(&self, program: &Program<'p>, error: &rsjsonnet_lang::program::EvalError) {
346 self.print_rich_message(&crate::report::eval::render_error_kind(
347 &error.kind,
348 program.span_manager(),
349 &self.src_mgr,
350 ));
351 self.print_stack_trace(program, &error.stack_trace);
352 eprintln!();
353 }
354
355 fn print_error<T: std::fmt::Display>(&self, msg: T) {
356 let msg = crate::report::render_error_message(msg);
357 self.print_rich_message(&msg);
358 }
359
360 fn print_note<T: std::fmt::Display>(&self, msg: T) {
361 let msg = crate::report::render_note_message(msg);
362 self.print_rich_message(&msg);
363 }
364
365 fn print_stack_trace(
366 &self,
367 program: &Program<'p>,
368 stack: &[rsjsonnet_lang::program::EvalStackTraceItem],
369 ) {
370 if stack.len() <= self.max_trace {
371 self.print_rich_message(&crate::report::stack_trace::render(
372 stack,
373 program.span_manager(),
374 &self.src_mgr,
375 ));
376 } else {
377 let second_len = self.max_trace / 2;
378 let first_len = self.max_trace - second_len;
379
380 self.print_rich_message(&crate::report::stack_trace::render(
381 &stack[(stack.len() - first_len)..],
382 program.span_manager(),
383 &self.src_mgr,
384 ));
385 self.print_note(format_args!(
386 "... {} items hidden ...",
387 stack.len() - self.max_trace
388 ));
389 self.print_rich_message(&crate::report::stack_trace::render(
390 &stack[..second_len],
391 program.span_manager(),
392 &self.src_mgr,
393 ));
394 }
395
396 for custom_item in self.custom_stack_trace.iter().rev() {
397 self.print_note(custom_item);
398 }
399 }
400}
401
402impl<'p> rsjsonnet_lang::program::Callbacks<'p> for SessionInner<'p> {
403 fn import(
404 &mut self,
405 program: &mut Program<'p>,
406 from: SpanId,
407 path: &str,
408 ) -> Result<Thunk<'p>, ImportError> {
409 let Some(full_path) = self.find_import(program, from, path) else {
410 self.print_error(format_args!("import {path:?} not found in search path"));
411 return Err(ImportError);
412 };
413 match self.load_real_file(program, &full_path) {
414 None => Err(ImportError),
415 Some(thunk) => Ok(thunk),
416 }
417 }
418
419 fn import_str(
420 &mut self,
421 program: &mut Program<'p>,
422 from: SpanId,
423 path: &str,
424 ) -> Result<String, ImportError> {
425 let Some(full_path) = self.find_import(program, from, path) else {
426 self.print_error(format_args!("import {path:?} not found in search path"));
427 return Err(ImportError);
428 };
429 let data = match std::fs::read(&full_path) {
430 Ok(data) => data,
431 Err(e) => {
432 self.print_error(format_args!("failed to read {full_path:?}: {e}"));
433 return Err(ImportError);
434 }
435 };
436 Ok(String::from_utf8_lossy(&data).into_owned())
437 }
438
439 fn import_bin(
440 &mut self,
441 program: &mut Program<'p>,
442 from: SpanId,
443 path: &str,
444 ) -> Result<Vec<u8>, ImportError> {
445 let Some(full_path) = self.find_import(program, from, path) else {
446 self.print_error(format_args!("import {path:?} not found in search path"));
447 return Err(ImportError);
448 };
449 let data = match std::fs::read(&full_path) {
450 Ok(data) => data,
451 Err(e) => {
452 self.print_error(format_args!("failed to read {full_path:?}: {e}"));
453 return Err(ImportError);
454 }
455 };
456 Ok(data)
457 }
458
459 fn trace(
460 &mut self,
461 program: &mut Program<'p>,
462 message: &str,
463 stack: &[rsjsonnet_lang::program::EvalStackTraceItem],
464 ) {
465 self.print_rich_message(&[
466 ("TRACE".into(), crate::print::TextPartKind::NoteLabel),
467 (": ".into(), crate::print::TextPartKind::MainMessage),
468 (message.into(), crate::print::TextPartKind::MainMessage),
469 ('\n'.into(), crate::print::TextPartKind::Space),
470 ]);
471 self.print_stack_trace(program, stack);
472 eprintln!();
473 }
474
475 fn native_call(
476 &mut self,
477 program: &mut Program<'p>,
478 name: InternedStr<'p>,
479 args: &[Value<'p>],
480 ) -> Result<Value<'p>, NativeError> {
481 let native_func = &mut self.native_funcs.get_mut(&name).unwrap();
482 match native_func(program, args) {
483 Ok(v) => Ok(v),
484 Err(e) => {
485 self.print_error(format_args!("native function {name:?} failed: {e}"));
486 Err(NativeError)
487 }
488 }
489 }
490}