use std::collections::{BTreeMap, BTreeSet};
use tla_syntax::{Def, Expr, Module, Unit, parse_module};
use crate::error::{Error, Result};
pub trait Modules {
fn source(&self, name: &str) -> Option<String>;
}
pub struct NoModules;
impl Modules for NoModules {
fn source(&self, _name: &str) -> Option<String> {
None
}
}
pub struct Directory(pub std::path::PathBuf);
impl Modules for Directory {
fn source(&self, name: &str) -> Option<String> {
std::fs::read_to_string(self.0.join(format!("{name}.tla"))).ok()
}
}
const BUILT_IN: &[&str] = &[
"Naturals",
"Integers",
"Reals",
"Sequences",
"FiniteSets",
"TLC",
"Bags",
"RealTime",
"Randomization",
"Toolbox",
];
#[derive(Debug)]
pub struct Spec {
modules: Vec<Module>,
facts: Vec<Facts>,
root: usize,
}
#[derive(Debug, Default)]
struct Facts {
variables: BTreeSet<String>,
constants: BTreeSet<String>,
extends: Vec<usize>,
instances: Vec<Instance>,
}
#[derive(Debug)]
pub(crate) struct Instance {
pub(crate) name: Option<String>,
pub(crate) target: usize,
pub(crate) subs: Vec<(String, Expr)>,
}
impl Spec {
pub fn parse(src: &str) -> Result<Self> {
Self::load(src, &NoModules)
}
pub fn from_file(path: impl AsRef<std::path::Path>) -> Result<Self> {
let path = path.as_ref();
let src = std::fs::read_to_string(path)
.map_err(|e| Error::Malformed(format!("reading {}: {e}", path.display())))?;
let directory = path.parent().unwrap_or(std::path::Path::new("."));
Self::load(&src, &Directory(directory.to_path_buf()))
}
pub fn load(src: &str, modules: &impl Modules) -> Result<Self> {
let root = parse_module(src)?;
let mut builder = Builder {
modules: Vec::new(),
index: BTreeMap::new(),
source: modules,
};
let root = builder.add(root)?;
let modules = builder.modules;
let facts = resolve(&modules, &builder.index)?;
Ok(Self {
modules,
facts,
root,
})
}
pub fn name(&self) -> &str {
&self.modules[self.root].name
}
pub fn variables(&self) -> impl Iterator<Item = &str> {
self.facts[self.root].variables.iter().map(String::as_str)
}
pub fn constants(&self) -> impl Iterator<Item = &str> {
self.facts[self.root].constants.iter().map(String::as_str)
}
pub fn defines(&self, name: &str) -> bool {
self.definition(self.root, name).is_some()
}
pub(crate) fn root(&self) -> usize {
self.root
}
pub(crate) fn declares_variable(&self, module: usize, name: &str) -> bool {
self.facts[module].variables.contains(name)
}
pub(crate) fn declares_constant(&self, module: usize, name: &str) -> bool {
self.facts[module].constants.contains(name)
}
pub(crate) fn definition(&self, module: usize, name: &str) -> Option<(usize, &Def)> {
let mut seen = BTreeSet::new();
self.search(module, name, &mut seen)
}
fn search(
&self,
module: usize,
name: &str,
seen: &mut BTreeSet<usize>,
) -> Option<(usize, &Def)> {
if !seen.insert(module) {
return None;
}
if let Some(def) = self.modules[module].definition(name) {
return Some((module, def));
}
self.facts[module]
.extends
.iter()
.find_map(|&parent| self.search(parent, name, seen))
}
pub(crate) fn instance(&self, module: usize, name: &str) -> Option<&Instance> {
let mut seen = BTreeSet::new();
self.find_instance(module, name, &mut seen)
}
fn find_instance(
&self,
module: usize,
name: &str,
seen: &mut BTreeSet<usize>,
) -> Option<&Instance> {
if !seen.insert(module) {
return None;
}
let here = self.facts[module]
.instances
.iter()
.find(|i| i.name.as_deref() == Some(name));
if here.is_some() {
return here;
}
self.facts[module]
.extends
.iter()
.find_map(|&parent| self.find_instance(parent, name, seen))
}
}
struct Builder<'a, M: Modules> {
modules: Vec<Module>,
index: BTreeMap<String, usize>,
source: &'a M,
}
impl<M: Modules> Builder<'_, M> {
fn add(&mut self, module: Module) -> Result<usize> {
let name = module.name.clone();
if let Some(&existing) = self.index.get(&name) {
return Ok(existing);
}
let position = self.modules.len();
self.index.insert(name, position);
self.modules.push(module);
let inner: Vec<Module> = self.modules[position]
.units
.iter()
.filter_map(|u| match u {
Unit::Inner(m) => Some((**m).clone()),
_ => None,
})
.collect();
for module in inner {
self.add(module)?;
}
let mut wanted: Vec<String> = self.modules[position].extends.clone();
wanted.extend(self.modules[position].units.iter().filter_map(|u| match u {
Unit::Instance { module, .. } => Some(module.clone()),
_ => None,
}));
for name in wanted {
if BUILT_IN.contains(&name.as_str()) || self.index.contains_key(&name) {
continue;
}
let Some(src) = self.source.source(&name) else {
return Err(Error::Undefined(format!(
"module `{name}` is neither a standard module nor one that could be found"
)));
};
let parsed = parse_module(&src)?;
self.add(parsed)?;
}
Ok(position)
}
}
fn resolve(modules: &[Module], index: &BTreeMap<String, usize>) -> Result<Vec<Facts>> {
let mut facts: Vec<Facts> = modules.iter().map(|_| Facts::default()).collect();
for (position, module) in modules.iter().enumerate() {
facts[position].extends = module
.extends
.iter()
.filter_map(|name| index.get(name).copied())
.collect();
facts[position].variables = module.variables().cloned().collect();
facts[position].constants = module.constants().map(|d| d.name.clone()).collect();
}
for position in 0..modules.len() {
let mut seen = BTreeSet::new();
let mut inherited = (BTreeSet::new(), BTreeSet::new());
collect(position, &facts, &mut seen, &mut inherited);
facts[position].variables.extend(inherited.0);
facts[position].constants.extend(inherited.1);
}
for (position, module) in modules.iter().enumerate() {
facts[position].instances = module
.units
.iter()
.filter_map(|u| match u {
Unit::Instance { name, module, subs } => Some((name, module, subs)),
_ => None,
})
.map(|(name, target, subs)| {
let target = *index.get(target).ok_or_else(|| {
Error::Undefined(format!("INSTANCE of unknown module `{target}`"))
})?;
Ok(Instance {
name: name.clone(),
target,
subs: complete(&facts[target], subs),
})
})
.collect::<Result<Vec<_>>>()?;
}
Ok(facts)
}
fn collect(
position: usize,
facts: &[Facts],
seen: &mut BTreeSet<usize>,
out: &mut (BTreeSet<String>, BTreeSet<String>),
) {
for &parent in &facts[position].extends {
if !seen.insert(parent) {
continue;
}
out.0.extend(facts[parent].variables.iter().cloned());
out.1.extend(facts[parent].constants.iter().cloned());
collect(parent, facts, seen, out);
}
}
fn complete(target: &Facts, given: &[(String, Expr)]) -> Vec<(String, Expr)> {
let mut subs = given.to_vec();
let declared = target.constants.iter().chain(target.variables.iter());
for name in declared {
if !subs.iter().any(|(given, _)| given == name) {
subs.push((name.clone(), Expr::Ident(name.clone())));
}
}
subs
}