use crate::ast::Statement;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Pragma {
pub name: String,
pub args: Vec<String>,
pub line: usize,
}
#[derive(Debug, Clone, Default)]
pub struct PragmaSet {
pub entries: Vec<Pragma>,
}
impl PragmaSet {
pub fn host(strict: bool) -> PragmaSet {
let entries = if strict {
vec![Pragma {
name: "strict".to_string(),
args: Vec::new(),
line: 0,
}]
} else {
Vec::new()
};
PragmaSet { entries }
}
pub fn contains(&self, name: &str) -> bool {
self.entries.iter().any(|p| p.name == name)
}
pub fn strict_types(&self) -> bool {
self.contains("strict_types") || self.contains("strict")
}
pub fn strict_values(&self) -> bool {
self.contains("strict_values") || self.contains("strict")
}
pub fn strict_names(&self) -> bool {
self.contains("strict")
}
pub fn unknown(&self) -> impl Iterator<Item = &Pragma> {
self.entries.iter().filter(|p| !is_known(&p.name))
}
pub fn nested(&self, statements: &[Statement]) -> PragmaSet {
let mut entries = self.entries.clone();
entries.extend(declared_in(statements));
PragmaSet { entries }
}
}
pub fn is_known(name: &str) -> bool {
matches!(name, "strict_types" | "strict_values" | "strict")
}
pub fn declared_in(statements: &[Statement]) -> impl Iterator<Item = Pragma> + '_ {
statements.iter().filter_map(|stmt| match stmt {
Statement::Pragma { name, span } => Some(Pragma {
name: name.clone(),
args: Vec::new(),
line: span.line,
}),
_ => None,
})
}
pub fn collect_from_ast(file: &crate::ast::PolydatFile) -> PragmaSet {
PragmaSet::default().nested(&file.statements)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lexer::lex;
use crate::parser::parse;
fn pragmas_from(src: &str) -> PragmaSet {
let tokens = lex(src).expect("lex");
let ast = parse(tokens).expect("parse");
collect_from_ast(&ast)
}
fn statements(src: &str) -> Vec<Statement> {
parse(lex(src).expect("lex")).expect("parse").statements
}
#[test]
fn strict_implies_both_modes_and_strict_names() {
let set = pragmas_from("pragma strict\nid := cycle\n");
assert!(set.strict_types());
assert!(set.strict_values());
assert!(set.strict_names());
}
#[test]
fn parse_individual_modes() {
let set = pragmas_from("pragma strict_types\npragma strict_values\nid := cycle\n");
assert!(set.strict_types());
assert!(set.strict_values());
assert!(!set.strict_names());
}
#[test]
fn unknown_pragmas_are_collected() {
let set = pragmas_from("pragma warp_drive\npragma strict\nid := cycle\n");
assert!(set.strict_types());
let unknown: Vec<_> = set.unknown().collect();
assert_eq!(unknown.len(), 1);
assert_eq!(unknown[0].name, "warp_drive");
}
#[test]
fn host_strict_seeds_strict_under_the_program_pragmas() {
let set = PragmaSet::host(true).nested(&statements("pragma strict_values\nid := cycle\n"));
assert!(set.strict_names() && set.strict_types() && set.strict_values());
assert_eq!(set.unknown().count(), 0);
assert!(PragmaSet::host(false).entries.is_empty());
}
#[test]
fn nested_scope_inherits_the_enclosing_set() {
let outer = pragmas_from("pragma strict_values\nid := cycle\n");
let inner = outer.nested(&statements("x := cycle\n"));
assert!(inner.strict_values());
}
#[test]
fn nested_scope_adds_without_changing_the_enclosing_set() {
let outer = pragmas_from("pragma strict_types\nid := cycle\n");
let inner = outer.nested(&statements("pragma strict_values\nx := cycle\n"));
assert!(inner.strict_types() && inner.strict_values());
assert!(!outer.strict_values());
}
}