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 = if cfg!(target_family = "wasm") {
83 1
84 } else {
85 n_threads
86 .or_else(|| thread::available_parallelism().map(usize::from).ok())
87 .unwrap_or(1)
88 .clamp(1, n_trails.max(1))
89 };
90
91 if n_threads == 1 {
92 return run_trials(
93 vm,
94 n_trails,
95 where_program.as_ref(),
96 &programs,
97 &stmt.selectors,
98 );
99 }
100
101 let outputs = thread::scope(|scope| {
102 let vm = &vm;
103 let programs = &programs;
104 let where_program = where_program.as_ref();
105 let selectors = &stmt.selectors;
106
107 (0..n_threads)
108 .map(|i| {
109 let quota = n_trails / n_threads + usize::from(i < n_trails % n_threads);
111
112 scope.spawn(move || {
113 run_trials(vm.clone(), quota, where_program, programs, selectors)
114 })
115 })
116 .collect::<Vec<_>>()
117 .into_iter()
118 .map(|handle| handle.join().unwrap())
119 .collect::<PQLResult<Vec<_>>>()
120 })?;
121
122 Ok(outputs
123 .into_iter()
124 .reduce(|mut acc, output| {
125 acc.merge(output);
126 acc
127 })
128 .unwrap())
129 }
130
131 pub fn run<S: io::Write, T: io::Write>(
133 src: &str,
134 max_trials: Option<usize>,
135 n_threads: Option<usize>,
136 stream_out: &mut S,
137 stream_err: &mut T,
138 ) -> io::Result<()> {
139 match parse_pql(src) {
140 Ok(stmts) => {
141 for (i, stmt) in stmts.iter().enumerate() {
142 if i > 0 {
143 writeln!(stream_out, "{:-<80}", "")?;
144 }
145
146 match Self::try_run_stmt(stmt, max_trials, n_threads) {
147 Ok(output) => {
148 output.report_to_stream(stmt, stream_out)?;
149 writeln!(stream_out, "{} trials", output.n_succ)?;
150 }
151 Err(err) => {
152 writeln!(stream_err, "{err:?} {}", &src[err.loc.0..err.loc.1])?;
153 }
154 }
155 }
156 }
157 Err(err) => writeln!(stream_err, "{err:?}")?,
158 }
159 Ok(())
160 }
161}