Skip to main content

probl/
program.rs

1//! Compiling a program, loading its data and running it.
2
3use crate::files::{Data, Files, Resolve};
4use crate::{Diagnostic, Error, FailureMode, Options, Outcome};
5use probl_sema::ir;
6use probl_syntax::SourceFile;
7use std::sync::Arc;
8
9/// Compile a program. `name` is what its diagnostics call it, such as its
10/// file's name. Warnings come with the program; errors come instead of it, as
11/// an error of kind [`Compile`](crate::ErrorKind::Compile).
12pub fn compile(name: &str, source: &str) -> Result<Program, Error> {
13    let file = Arc::new(SourceFile::new(name, source));
14    let (program, diagnostics) = probl_sema::compile(source);
15    match program {
16        Some(ir) => Ok(Program {
17            ir: Arc::new(ir),
18            warnings: diagnostics.into_iter().map(|d| Diagnostic::new(d, &file)).collect(),
19            file,
20        }),
21        None => Err(Error::compile(diagnostics, &file)),
22    }
23}
24
25/// A compiled program. Cloning it is cheap, and it can run any number of
26/// times, from any thread.
27#[derive(Clone)]
28pub struct Program {
29    ir: Arc<ir::Program>,
30    file: Arc<SourceFile>,
31    warnings: Vec<Diagnostic>,
32}
33
34/// How a program runs: its `@mode`, or what [`Options`] choose instead.
35#[non_exhaustive]
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub enum Mode {
38    /// The default: enumerate, for now. Later, it may sample a model that
39    /// can't be enumerated, and say so.
40    Auto,
41    /// Follow every random choice, exactly.
42    Enumerate,
43    /// Sample this many runs, from this seed.
44    #[non_exhaustive]
45    Sample { runs: u64, seed: u64 },
46    /// Beam search: designed, not built yet. Running it is an error of kind
47    /// [`Unsupported`](crate::ErrorKind::Unsupported).
48    #[non_exhaustive]
49    Beam { worlds: u64 },
50    /// Particles: designed, not built yet. Running it is an error of kind
51    /// [`Unsupported`](crate::ErrorKind::Unsupported).
52    #[non_exhaustive]
53    Particles { runs: u64, seed: u64 },
54}
55
56impl Program {
57    /// What the compiler warned about.
58    pub fn warnings(&self) -> &[Diagnostic] {
59        &self.warnings
60    }
61
62    /// Its `@mode`, which [`Options`] can override.
63    pub fn mode(&self) -> Mode {
64        match self.ir.settings.mode {
65            ir::Mode::Auto => Mode::Auto,
66            ir::Mode::Enumerate => Mode::Enumerate,
67            ir::Mode::Sample { runs, seed } => Mode::Sample { runs, seed },
68            ir::Mode::Beam { worlds } => Mode::Beam { worlds },
69            ir::Mode::Particles { runs, seed } => Mode::Particles { runs, seed },
70        }
71    }
72
73    /// Its `@on_error`, if it has one. [`Options::on_error`] overrides it.
74    pub fn failure_mode(&self) -> Option<FailureMode> {
75        self.ir.settings.on_error.map(FailureMode::new)
76    }
77
78    /// Whether it uses `read`, and so needs [`load`](Program::load) before
79    /// it runs.
80    pub fn reads_data(&self) -> bool {
81        !self.ir.inputs.is_empty()
82    }
83
84    /// Read its data from `files`, with the limits and the cancellation in
85    /// `options`. Errors are about a `read(…)` call, and say where in the
86    /// data they are.
87    pub fn load(&self, files: &mut dyn Files, options: &Options) -> Result<Data, Error> {
88        let mut snapshots = probl_engine::data::Snapshots::default();
89        let cancel = options.cancel.as_ref().map(|c| c.flag());
90        probl_engine::data::load(
91            &self.ir,
92            &mut Resolve(files),
93            &mut snapshots,
94            &options.limits.input(),
95            cancel,
96        )
97        .map(Data::new)
98        .map_err(|e| Error::runtime(e, &self.file))
99    }
100
101    /// Run it. What it `print`s is collected in the outcome.
102    ///
103    /// In partial mode, when some worlds fail, the run is an error whose
104    /// [`partial`](Error::partial) has what the other worlds gave.
105    pub fn run(&self, options: &Options) -> Result<Outcome, Error> {
106        let mut printed = Vec::new();
107        let result = {
108            let mut print = |line: &str| printed.push(line.to_string());
109            self.run_with(options, &mut print)
110        };
111        match result {
112            Ok(mut outcome) => {
113                outcome.printed = printed;
114                Ok(outcome)
115            }
116            Err(mut e) => {
117                if let Some(outcome) = e.partial_mut() {
118                    outcome.printed = printed;
119                }
120                Err(e)
121            }
122        }
123    }
124
125    /// Run it, giving each line it `print`s to `print` as it's printed.
126    pub fn run_with(&self, options: &Options, print: &mut (dyn FnMut(&str) + Send)) -> Result<Outcome, Error> {
127        let mut engine = options.engine(&self.ir);
128        engine.inputs = self.inputs(options)?;
129        #[cfg(target_arch = "wasm32")]
130        let result = probl_engine::run_on_this_thread(&self.ir, &engine, print);
131        #[cfg(not(target_arch = "wasm32"))]
132        let result = probl_engine::run(&self.ir, &engine, print);
133        let outcome = result.map_err(|e| Error::runtime(e, &self.file))?;
134        let outcome = Outcome::new(outcome, &self.ir, &self.file);
135        match outcome.failures().is_empty() {
136            true => Ok(outcome),
137            false => Err(Error::failed(outcome)),
138        }
139    }
140
141    /// The data `options` give the run, which must be this program's.
142    fn inputs(&self, options: &Options) -> Result<Option<Arc<probl_engine::data::Inputs>>, Error> {
143        let span = self.ir.inputs.first().map(|i| i.span).unwrap_or_default();
144        match &options.data {
145            Some(data) if !data.inputs.fit(&self.ir) => Err(Error::usage(
146                span,
147                "the data was loaded for another program",
148                "load this program's data with its `Program::load`",
149                &self.file,
150            )),
151            Some(data) => Ok(Some(data.inputs.clone())),
152            None if self.reads_data() => Err(Error::usage(
153                span,
154                "the program reads data, which wasn't loaded",
155                "load it with `Program::load`, and give it to the run with `Options::data`",
156                &self.file,
157            )),
158            None => Ok(None),
159        }
160    }
161
162    /// How many reports it has, in [`Outcome::reports`].
163    pub(crate) fn report_count(&self) -> usize {
164        self.ir.reports.len()
165    }
166}
167
168impl std::fmt::Debug for Program {
169    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170        f.debug_struct("Program")
171            .field("name", &self.file.name)
172            .field("mode", &self.mode())
173            .field("reports", &self.report_count())
174            .field("warnings", &self.warnings)
175            .finish()
176    }
177}