1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
mod manifest;
mod stdlib;
mod tla;
mod trace;

use std::{env, marker::PhantomData, path::PathBuf};

use clap::Parser;
use jrsonnet_evaluator::{error::Result, stack::set_stack_depth_limit, FileImportResolver, State};
use jrsonnet_gcmodule::with_thread_object_space;
pub use manifest::*;
pub use stdlib::*;
pub use tla::*;
pub use trace::*;

pub trait ConfigureState {
	type Guards;

	fn configure(&self, s: &State) -> Result<Self::Guards>;
}

#[derive(Parser)]
#[clap(next_help_heading = "INPUT")]
pub struct InputOpts {
	/// Treat input as code, evaluate them instead of reading file
	#[clap(long, short = 'e')]
	pub exec: bool,

	/// Path to the file to be compiled if `--evaluate` is unset, otherwise code itself
	pub input: String,
}

#[derive(Parser)]
#[clap(next_help_heading = "OPTIONS")]
pub struct MiscOpts {
	/// Maximal allowed number of stack frames,
	/// stack overflow error will be raised if this number gets exceeded.
	#[clap(long, short = 's', default_value = "200")]
	max_stack: usize,

	/// Library search dirs. (right-most wins)
	/// Any not found `imported` file will be searched in these.
	/// This can also be specified via `JSONNET_PATH` variable,
	/// which should contain a colon-separated (semicolon-separated on Windows) list of directories.
	#[clap(long, short = 'J')]
	jpath: Vec<PathBuf>,
}
impl ConfigureState for MiscOpts {
	type Guards = ();
	fn configure(&self, s: &State) -> Result<Self::Guards> {
		let mut library_paths = self.jpath.clone();
		library_paths.reverse();
		if let Some(path) = env::var_os("JSONNET_PATH") {
			library_paths.extend(env::split_paths(path.as_os_str()));
		}

		s.set_import_resolver(FileImportResolver::new(library_paths));

		set_stack_depth_limit(self.max_stack);
		Ok(())
	}
}

/// General configuration of jsonnet
#[derive(Parser)]
#[clap(name = "jrsonnet", version, author)]
pub struct GeneralOpts {
	#[clap(flatten)]
	misc: MiscOpts,

	#[clap(flatten)]
	tla: TlaOpts,
	#[clap(flatten)]
	std: StdOpts,

	#[clap(flatten)]
	gc: GcOpts,
}

impl ConfigureState for GeneralOpts {
	type Guards = (
		<TlaOpts as ConfigureState>::Guards,
		<GcOpts as ConfigureState>::Guards,
	);
	fn configure(&self, s: &State) -> Result<Self::Guards> {
		// Configure trace first, because tla-code/ext-code can throw
		self.misc.configure(s)?;
		let tla_guards = self.tla.configure(s)?;
		self.std.configure(s)?;
		let gc_guards = self.gc.configure(s)?;
		Ok((tla_guards, gc_guards))
	}
}

#[derive(Parser)]
#[clap(next_help_heading = "GARBAGE COLLECTION")]
pub struct GcOpts {
	/// Do not skip gc on exit
	#[clap(long)]
	gc_collect_on_exit: bool,
	/// Print gc stats before exit
	#[clap(long)]
	gc_print_stats: bool,
	/// Force garbage collection before printing stats
	/// Useful for checking for memory leaks
	/// Does nothing useless --gc-print-stats is specified
	#[clap(long)]
	gc_collect_before_printing_stats: bool,
}
impl ConfigureState for GcOpts {
	type Guards = (Option<GcStatsPrinter>, Option<LeakSpace>);

	fn configure(&self, _s: &State) -> Result<Self::Guards> {
		// Constructed structs have side-effects in Drop impl
		#[allow(clippy::unnecessary_lazy_evaluations)]
		Ok((
			self.gc_print_stats.then(|| GcStatsPrinter {
				collect_before_printing_stats: self.gc_collect_before_printing_stats,
			}),
			(!self.gc_collect_on_exit).then(|| LeakSpace(PhantomData)),
		))
	}
}

pub struct LeakSpace(PhantomData<()>);

impl Drop for LeakSpace {
	fn drop(&mut self) {
		with_thread_object_space(|s| s.leak())
	}
}

pub struct GcStatsPrinter {
	collect_before_printing_stats: bool,
}
impl Drop for GcStatsPrinter {
	fn drop(&mut self) {
		eprintln!("=== GC STATS ===");
		if self.collect_before_printing_stats {
			let collected = jrsonnet_gcmodule::collect_thread_cycles();
			eprintln!("Collected: {}", collected);
		}
		eprintln!("Tracked: {}", jrsonnet_gcmodule::count_thread_tracked())
	}
}