use crate::{
Edge, Node,
compile::{ModuleError, SourceMap},
node,
};
use petgraph::visit::{Data, IntoEdgesDirected, IntoNodeReferences, NodeIndexable, Visitable};
use steel::{
SteelErr, SteelVal,
parser::{ast::ExprKind, span::Span},
steel_vm::engine::Engine,
};
#[derive(Clone, Debug)]
pub struct Compiled {
pub exprs: Vec<ExprKind>,
pub src: String,
pub map: SourceMap,
}
#[derive(Debug, thiserror::Error)]
pub enum CompileError {
#[error("module generation failed")]
Module(#[from] ModuleError),
#[error("module evaluation failed")]
Eval {
#[source]
err: SteelErr,
module: Box<Compiled>,
},
}
impl CompileError {
pub fn into_module(self) -> Option<Compiled> {
match self {
Self::Module(_) => None,
Self::Eval { module, .. } => Some(*module),
}
}
}
pub fn init<'a, G>(
get_node: node::GetNode<'a>,
graph: G,
entrypoints: &[crate::compile::Entrypoint],
config: &crate::compile::Config,
) -> Result<(Engine, Compiled), CompileError>
where
G: Data<EdgeWeight = Edge>
+ IntoEdgesDirected
+ IntoNodeReferences
+ NodeIndexable
+ Visitable
+ Copy,
G::NodeWeight: Node,
{
let mut vm = Engine::new_base();
vm.register_value(crate::ROOT_STATE, SteelVal::empty_hashmap());
vm.register_value(crate::ARGS, crate::args::default());
crate::graph::register(get_node, graph, &[], &mut vm);
let compiled = compile(get_node, graph, &mut vm, entrypoints, config)?;
Ok((vm, compiled))
}
pub fn compile<'a, G>(
get_node: node::GetNode<'a>,
graph: G,
vm: &mut Engine,
entrypoints: &[crate::compile::Entrypoint],
config: &crate::compile::Config,
) -> Result<Compiled, CompileError>
where
G: Data<EdgeWeight = Edge>
+ IntoEdgesDirected
+ IntoNodeReferences
+ NodeIndexable
+ Visitable
+ Copy,
G::NodeWeight: Node,
{
let module_start = web_time::Instant::now();
let exprs = crate::compile::module(get_node, graph, entrypoints, config)?;
log::debug!("Generated steel module ({:?})", module_start.elapsed());
let src = fmt_module(&exprs);
let map = SourceMap::parse(&src);
let compiled = Compiled { exprs, src, map };
let run_start = web_time::Instant::now();
let result = vm.run(compiled.src.clone());
log::debug!("Compiled steel ({:?})", run_start.elapsed());
match result {
Ok(_) => Ok(compiled),
Err(err) => Err(CompileError::Eval {
err,
module: Box::new(compiled),
}),
}
}
pub fn fmt_module(module: &[ExprKind]) -> String {
module
.iter()
.map(|expr| expr.to_pretty(80))
.collect::<Vec<String>>()
.join("\n\n")
}
pub fn steel_err_span(
err: &SteelErr,
vm: &Engine,
compiled: &Compiled,
) -> Option<std::ops::Range<usize>> {
let in_module = |span: &Span| {
span.source_id()
.and_then(|id| vm.get_source(&id))
.is_some_and(|text| text.as_ref().as_ref() == compiled.src)
};
steel_err_spans(err)
.find(in_module)
.map(|span| span.usize_range())
}
pub fn steel_err_raw_span(err: &SteelErr) -> Option<std::ops::Range<usize>> {
steel_err_spans(err).next().map(|span| span.usize_range())
}
pub fn steel_err_node(err: &SteelErr, vm: &Engine, compiled: &Compiled) -> Option<Vec<node::Id>> {
compiled.map.node_at(steel_err_span(err, vm, compiled)?)
}
pub fn error_chain(err: &dyn std::error::Error) -> String {
use std::fmt::Write;
let mut s = err.to_string();
let mut source = err.source();
while let Some(e) = source {
write!(s, "\ncaused by: {e}").expect("writing to a String never fails");
source = e.source();
}
s
}
fn steel_err_spans(err: &SteelErr) -> impl Iterator<Item = Span> + '_ {
err.span().into_iter().chain(
err.stack_trace()
.iter()
.flat_map(|trace| trace.trace().iter().rev().filter_map(|frame| *frame.span())),
)
}