1#![cfg_attr(coverage_nightly, coverage(off))]
3
4use super::*;
5
6pub struct PQLRunner {}
7
8fn run_trials(
11 mut vm: Vm,
12 n_trails: usize,
13 where_program: Option<&VmProgram>,
14 programs: &[VmProgram],
15 selectors: &[ast::Selector],
16) -> PQLResult<RunnerOutput> {
17 let mut rng = rand::rng();
18 let mut output = RunnerOutput::new(vm.static_data.game, selectors);
19
20 while output.n_succ < n_trails {
21 if output.n_fail == n_trails {
22 return Err(((0, 1), VmError::SamplingFailed).into());
24 }
25
26 match vm.sample(&mut rng) {
27 Some(()) => {
28 if let Some(wp) = where_program {
29 let keep =
30 matches!(wp.execute(&mut vm.as_context())?, VmStackValue::Bool(true));
31
32 if !keep {
33 output.n_fail += 1;
35 continue;
36 }
37 }
38
39 for (idx, program) in programs.iter().enumerate() {
40 output.push_value(idx, program.execute(&mut vm.as_context())?);
41 }
42 output.n_succ += 1;
43 }
44 None => output.n_fail += 1,
45 }
46 }
47
48 Ok(output)
49}
50
51impl PQLRunner {
52 #[allow(clippy::missing_panics_doc)]
56 pub fn try_run_stmt(
57 stmt: &ast::Stmt<'_>,
58 max_trials: Option<usize>,
59 n_threads: Option<usize>,
60 ) -> PQLResult<RunnerOutput> {
61 let mut vm = Vm::from_stmt(stmt)?;
62
63 if let Some(n) = max_trials {
64 vm.static_data.n_trails = n;
65 }
66
67 let n_trails = vm.static_data.n_trails;
68
69 let where_program = match &stmt.where_clause {
70 Some(expr) => Some(vm::compile_where(&mut vm, expr)?),
71 None => None,
72 };
73
74 let programs = stmt
75 .selectors
76 .iter()
77 .map(|s| vm::compile_selector(&mut vm, s))
78 .collect::<PQLResult<Vec<_>>>()?;
79
80 let n_threads = n_threads
81 .or_else(|| thread::available_parallelism().map(usize::from).ok())
82 .unwrap_or(1)
83 .clamp(1, n_trails.max(1));
84
85 let outputs = thread::scope(|scope| {
86 let vm = &vm;
87 let programs = &programs;
88 let where_program = where_program.as_ref();
89 let selectors = &stmt.selectors;
90
91 (0..n_threads)
92 .map(|i| {
93 let quota = n_trails / n_threads + usize::from(i < n_trails % n_threads);
95
96 scope.spawn(move || {
97 run_trials(vm.clone(), quota, where_program, programs, selectors)
98 })
99 })
100 .collect::<Vec<_>>()
101 .into_iter()
102 .map(|handle| handle.join().unwrap())
103 .collect::<PQLResult<Vec<_>>>()
104 })?;
105
106 Ok(outputs
107 .into_iter()
108 .reduce(|mut acc, output| {
109 acc.merge(output);
110 acc
111 })
112 .unwrap())
113 }
114
115 pub fn run<S: io::Write, T: io::Write>(
117 src: &str,
118 max_trials: Option<usize>,
119 n_threads: Option<usize>,
120 stream_out: &mut S,
121 stream_err: &mut T,
122 ) -> io::Result<()> {
123 match parse_pql(src) {
124 Ok(stmts) => {
125 for (i, stmt) in stmts.iter().enumerate() {
126 if i > 0 {
127 writeln!(stream_out, "{:-<80}", "")?;
128 }
129
130 match Self::try_run_stmt(stmt, max_trials, n_threads) {
131 Ok(output) => {
132 output.report_to_stream(stmt, stream_out)?;
133 writeln!(stream_out, "{} trials", output.n_succ)?;
134 }
135 Err(err) => {
136 writeln!(stream_err, "{err:?} {}", &src[err.loc.0..err.loc.1])?;
137 }
138 }
139 }
140 }
141 Err(err) => writeln!(stream_err, "{err:?}")?,
142 }
143 Ok(())
144 }
145}