#[macro_use]
extern crate comemo;
#[macro_use]
extern crate tracing;
pub mod checks;
pub mod diagnostics;
pub mod engine;
pub mod eval;
pub mod id;
pub mod name_resolution;
pub mod node_state;
pub mod static_args;
mod stdlib;
pub mod types;
pub mod value;
use crate::engine::{Engine, FileError, FileId, Source};
use crate::static_args::NodeInstance;
use crate::stdlib::{get_iterator_kind, is_iterator, IteratorKind};
use crate::{
diagnostics::{Diagnostic, Level, Span},
types::check_node_equations,
};
use comemo::TrackedMut;
use ecow::EcoVec;
use rustre_parser::ast::{
AstToken, Ident, IncludeStatement, NodeProfileNode, ParamsNode, Root, TypedIdsNode,
};
use std::collections::{HashMap, HashSet};
use std::path::Path;
use std::rc::Rc;
#[derive(Clone, Debug)]
struct IncludeError {
statement: IncludeStatement,
error: FileError,
}
#[instrument(ret)]
#[memoize]
fn all_files_res(engine: TrackedMut<Engine>) -> (EcoVec<Source>, EcoVec<IncludeError>) {
let input_files = engine.input_files();
let mut files = EcoVec::with_capacity(input_files.len() + 1);
files.push(engine.stdlib());
files.extend(input_files);
let mut errors = EcoVec::new();
let mut visited = files.iter().map(|s| s.file()).collect::<HashSet<_>>();
let mut idx = 0;
while let Some(source) = files.get(idx).cloned() {
for include in source.root().all_include_statement() {
if let Some(include_target_str) = include.str() {
let include_target = include_target_str.unescaped();
match engine.imported_file(&source, Path::new(&include_target)) {
Ok(source) => {
if visited.insert(source.file()) {
files.push(source)
} else {
trace!(file = ?source.file(), "skipping already-included file");
}
}
Err(error) => errors.push(IncludeError {
statement: include,
error,
}),
}
}
}
idx += 1;
}
(files, errors)
}
#[memoize]
pub fn all_sources(mut engine: TrackedMut<Engine>) -> EcoVec<Source> {
let (files, _) = all_files_res(TrackedMut::reborrow_mut(&mut engine));
for source in &files {
for syntax_error in source.syntax_errors() {
let span = Span {
root: source.root(),
start: syntax_error.span.start,
end: syntax_error.span.end,
};
Diagnostic::build(Level::Error, "syntax error")
.with_attachment(span, syntax_error.msg)
.emit(&mut engine);
}
}
files
}
#[memoize]
pub fn all_roots(engine: TrackedMut<Engine>) -> EcoVec<Root> {
all_sources(engine).iter().map(Source::root).collect()
}
#[memoize]
pub fn enclosing_file_of_root(engine: TrackedMut<Engine>, root: Root) -> FileId {
let (sources, _) = all_files_res(engine);
for source in sources {
if source.root() == root {
return source.file();
}
}
panic!("cannot file enclosing file of {root:?}")
}
#[derive(Clone, Debug, Hash)]
pub struct Signature {
pub name: Option<Ident>,
pub params: Vec<TypedIdsNode>,
pub return_params: Vec<TypedIdsNode>,
}
impl Signature {
pub fn from_name(name: Option<Ident>) -> Self {
Self {
name,
params: Default::default(),
return_params: Default::default(),
}
}
#[inline]
pub fn with_params(
mut self,
params: Vec<TypedIdsNode>,
return_params: Vec<TypedIdsNode>,
) -> Self {
self.params = params;
self.return_params = return_params;
self
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct TypedSignature {
pub name: Option<Ident>,
pub params: Vec<(Ident, types::Type)>,
pub return_params: Vec<(Ident, types::Type)>,
}
impl TypedSignature {
pub fn from_name(name: Option<Ident>) -> Self {
Self {
name,
params: Default::default(),
return_params: Default::default(),
}
}
pub fn with_params(
mut self,
params: Vec<(Ident, types::Type)>,
return_params: Vec<(Ident, types::Type)>,
) -> Self {
self.params = params;
self.return_params = return_params;
self
}
pub fn map_all_params(self, mut f: impl FnMut(types::Type, usize) -> types::Type) -> Self {
let mut map = |params: Vec<_>| {
params
.into_iter()
.enumerate()
.map(|(idx, (id, ty))| (id, f(ty, idx)))
.collect::<Vec<_>>()
};
TypedSignature {
name: self.name,
params: map(self.params),
return_params: map(self.return_params),
}
}
}
#[memoize]
pub fn get_signature(mut engine: TrackedMut<Engine>, callee: NodeInstance) -> Signature {
let signature = Signature::from_name(callee.node.id_node().and_then(|id| id.ident()));
if callee.node.equal().is_some() {
if let Some(alias) = callee.node.alias() {
let alias_node = static_args::instance_of_effective_node(
TrackedMut::reborrow_mut(&mut engine),
Some(callee),
alias,
)
.unwrap(); return get_signature(engine, alias_node);
}
return signature;
}
let iterator_kind =
get_iterator_kind(TrackedMut::reborrow_mut(&mut engine), callee.node.clone());
if iterator_kind.is_some_and(|kind| kind != IteratorKind::BoolRed) {
return stdlib::get_iterator_signature(engine, callee);
}
let sig = callee.node.node_profile_node();
let get_params = |f: fn(&NodeProfileNode) -> Option<ParamsNode>| {
sig.clone()
.and_then(|sig| f(&sig))
.and_then(|p| p.all_var_decl_node().next())
.iter()
.flat_map(|v| v.all_typed_ids_node())
.collect::<Vec<_>>()
};
signature.with_params(
get_params(NodeProfileNode::params),
get_params(NodeProfileNode::return_params),
)
}
#[memoize]
pub fn get_typed_signature(mut engine: TrackedMut<Engine>, callee: NodeInstance) -> TypedSignature {
let signature = TypedSignature::from_name(callee.node.id_node().and_then(|id| id.ident()));
let is_iterator = is_iterator(TrackedMut::reborrow_mut(&mut engine), callee.node.clone());
if is_iterator {
return stdlib::get_iterator_typed_signature(engine, callee);
}
if callee.node.equal().is_some() {
if let Some(alias) = callee.node.alias() {
let alias_node = static_args::instance_of_effective_node(
TrackedMut::reborrow_mut(&mut engine),
Some(callee),
alias,
)
.unwrap(); return get_typed_signature(engine, alias_node);
}
return signature;
}
let sig = get_signature(TrackedMut::reborrow_mut(&mut engine), callee.clone());
let get_params = |mut engine: TrackedMut<Engine>, params: &[TypedIdsNode]| {
params
.iter()
.flat_map(|group| {
let ty = group
.type_node()
.map(|t| {
types::type_of_ast_type(
TrackedMut::reborrow_mut(&mut engine),
Some(callee.clone()),
t,
)
})
.unwrap_or_default();
group
.all_id_node()
.map(|id| id.ident().unwrap())
.zip(std::iter::repeat(ty))
})
.collect::<Vec<_>>()
};
let params = get_params(TrackedMut::reborrow_mut(&mut engine), &sig.params);
let return_params = get_params(TrackedMut::reborrow_mut(&mut engine), &sig.return_params);
signature.with_params(params, return_params)
}
#[memoize]
fn all_typed_signatures(
mut engine: TrackedMut<Engine>,
) -> Rc<HashMap<NodeInstance, TypedSignature>> {
Rc::new(
static_args::all_instances(TrackedMut::reborrow_mut(&mut engine))
.into_iter()
.map(|instance| {
let sig =
get_typed_signature(TrackedMut::reborrow_mut(&mut engine), instance.clone());
(instance, sig)
})
.collect(),
)
}
#[memoize]
pub fn check(mut engine: TrackedMut<Engine>) {
let (_, include_errors) = all_files_res(TrackedMut::reborrow_mut(&mut engine));
for error in include_errors {
if let Some(include_str) = error.statement.str() {
Diagnostic::build(Level::Error, "cannot include file")
.with_attachment(Span::of_lustre_str(&include_str), error.error.to_string())
.emit(&mut engine)
}
}
let _ = all_typed_signatures(TrackedMut::reborrow_mut(&mut engine));
for instance in static_args::all_instances(TrackedMut::reborrow_mut(&mut engine)) {
let node = instance.node.clone();
let _ = get_typed_signature(TrackedMut::reborrow_mut(&mut engine), instance.clone());
let name = node.id_node().unwrap().ident().unwrap();
let _name = name.text();
check_node_equations(TrackedMut::reborrow_mut(&mut engine), instance.clone());
node_state::check_node_function_state(TrackedMut::reborrow_mut(&mut engine), instance);
checks::check_arity(TrackedMut::reborrow_mut(&mut engine), node);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::test::SingleFile;
use comemo::Track;
#[test]
fn parse_query() {
let world = SingleFile::from_source(include_str!("../../tests/stable.lus"));
let mut engine = Engine::new(&world);
let engine = engine.track_mut();
let root = all_roots(engine).remove(1);
assert_eq!(root.all_include_statement().count(), 1);
}
}