use crate::files::{Data, Files, Resolve};
use crate::{Diagnostic, Error, FailureMode, Options, Outcome};
use probl_sema::ir;
use probl_syntax::SourceFile;
use std::sync::Arc;
pub fn compile(name: &str, source: &str) -> Result<Program, Error> {
let file = Arc::new(SourceFile::new(name, source));
let (program, diagnostics) = probl_sema::compile(source);
match program {
Some(ir) => Ok(Program {
ir: Arc::new(ir),
warnings: diagnostics.into_iter().map(|d| Diagnostic::new(d, &file)).collect(),
file,
}),
None => Err(Error::compile(diagnostics, &file)),
}
}
#[derive(Clone)]
pub struct Program {
ir: Arc<ir::Program>,
file: Arc<SourceFile>,
warnings: Vec<Diagnostic>,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Mode {
Auto,
Enumerate,
#[non_exhaustive]
Sample { runs: u64, seed: u64 },
#[non_exhaustive]
Beam { worlds: u64 },
#[non_exhaustive]
Particles { runs: u64, seed: u64 },
}
impl Program {
pub fn warnings(&self) -> &[Diagnostic] {
&self.warnings
}
pub fn mode(&self) -> Mode {
match self.ir.settings.mode {
ir::Mode::Auto => Mode::Auto,
ir::Mode::Enumerate => Mode::Enumerate,
ir::Mode::Sample { runs, seed } => Mode::Sample { runs, seed },
ir::Mode::Beam { worlds } => Mode::Beam { worlds },
ir::Mode::Particles { runs, seed } => Mode::Particles { runs, seed },
}
}
pub fn failure_mode(&self) -> Option<FailureMode> {
self.ir.settings.on_error.map(FailureMode::new)
}
pub fn reads_data(&self) -> bool {
!self.ir.inputs.is_empty()
}
pub fn load(&self, files: &mut dyn Files, options: &Options) -> Result<Data, Error> {
let mut snapshots = probl_engine::data::Snapshots::default();
let cancel = options.cancel.as_ref().map(|c| c.flag());
probl_engine::data::load(
&self.ir,
&mut Resolve(files),
&mut snapshots,
&options.limits.input(),
cancel,
)
.map(Data::new)
.map_err(|e| Error::runtime(e, &self.file))
}
pub fn run(&self, options: &Options) -> Result<Outcome, Error> {
let mut printed = Vec::new();
let result = {
let mut print = |line: &str| printed.push(line.to_string());
self.run_with(options, &mut print)
};
match result {
Ok(mut outcome) => {
outcome.printed = printed;
Ok(outcome)
}
Err(mut e) => {
if let Some(outcome) = e.partial_mut() {
outcome.printed = printed;
}
Err(e)
}
}
}
pub fn run_with(&self, options: &Options, print: &mut (dyn FnMut(&str) + Send)) -> Result<Outcome, Error> {
let mut engine = options.engine(&self.ir);
engine.inputs = self.inputs(options)?;
#[cfg(target_arch = "wasm32")]
let result = probl_engine::run_on_this_thread(&self.ir, &engine, print);
#[cfg(not(target_arch = "wasm32"))]
let result = probl_engine::run(&self.ir, &engine, print);
let outcome = result.map_err(|e| Error::runtime(e, &self.file))?;
let outcome = Outcome::new(outcome, &self.ir, &self.file);
match outcome.failures().is_empty() {
true => Ok(outcome),
false => Err(Error::failed(outcome)),
}
}
fn inputs(&self, options: &Options) -> Result<Option<Arc<probl_engine::data::Inputs>>, Error> {
let span = self.ir.inputs.first().map(|i| i.span).unwrap_or_default();
match &options.data {
Some(data) if !data.inputs.fit(&self.ir) => Err(Error::usage(
span,
"the data was loaded for another program",
"load this program's data with its `Program::load`",
&self.file,
)),
Some(data) => Ok(Some(data.inputs.clone())),
None if self.reads_data() => Err(Error::usage(
span,
"the program reads data, which wasn't loaded",
"load it with `Program::load`, and give it to the run with `Options::data`",
&self.file,
)),
None => Ok(None),
}
}
pub(crate) fn report_count(&self) -> usize {
self.ir.reports.len()
}
}
impl std::fmt::Debug for Program {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Program")
.field("name", &self.file.name)
.field("mode", &self.mode())
.field("reports", &self.report_count())
.field("warnings", &self.warnings)
.finish()
}
}