#[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>,
pub parent: Option<std::sync::Arc<PragmaSet>>,
}
impl PragmaSet {
pub fn contains(&self, name: &str) -> bool {
if self.entries.iter().any(|p| p.name == name) {
return true;
}
match &self.parent {
Some(p) => p.contains(name),
None => false,
}
}
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 unknown(&self) -> impl Iterator<Item = &Pragma> {
self.entries.iter().filter(|p| !is_known(&p.name))
}
pub fn attach_to(self, outer: std::sync::Arc<PragmaSet>) -> (PragmaSet, Vec<PragmaConflict>) {
let mut conflicts = Vec::new();
for entry in &self.entries {
let mut cursor: &PragmaSet = outer.as_ref();
loop {
if let Some(existing) = cursor.entries.iter().find(|p| p.name == entry.name)
&& existing.args != entry.args
{
conflicts.push(PragmaConflict {
name: entry.name.clone(),
outer_line: existing.line,
inner_line: entry.line,
});
break;
}
match &cursor.parent {
Some(p) => cursor = p.as_ref(),
None => break,
}
}
}
let attached = PragmaSet {
entries: self.entries,
parent: Some(outer),
};
(attached, conflicts)
}
}
fn is_known(name: &str) -> bool {
matches!(name, "strict_types" | "strict_values" | "strict")
}
pub fn collect_from_ast(file: &crate::dsl::ast::GkFile) -> PragmaSet {
use crate::dsl::ast::Statement;
let mut entries = Vec::new();
for stmt in &file.statements {
if let Statement::Pragma { name, span } = stmt {
entries.push(Pragma {
name: name.clone(),
args: Vec::new(),
line: span.line,
});
}
}
PragmaSet { entries, parent: None }
}
#[derive(Debug, Clone)]
pub struct PragmaConflict {
pub name: String,
pub outer_line: usize,
pub inner_line: usize,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dsl::lexer::lex;
use crate::dsl::parser::parse;
fn pragmas_from(src: &str) -> PragmaSet {
let tokens = lex(src).expect("lex");
let ast = parse(tokens).expect("parse");
collect_from_ast(&ast)
}
#[test]
fn parse_strict_alias() {
let set = pragmas_from("pragma strict\nid := cycle\n");
assert!(set.strict_types());
assert!(set.strict_values());
}
#[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());
}
#[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 attached_inherits_outer_pragmas() {
let outer = std::sync::Arc::new(PragmaSet {
entries: vec![Pragma {
name: "strict_values".into(),
args: vec![],
line: 1,
}],
parent: None,
});
let inner = PragmaSet::default();
let (attached, conflicts) = inner.attach_to(outer);
assert!(attached.strict_values(), "inner should see outer's strict_values via parent walk");
assert!(conflicts.is_empty());
}
#[test]
fn attached_local_pragma_wins_for_unrelated_names() {
let outer = std::sync::Arc::new(PragmaSet {
entries: vec![Pragma {
name: "strict_types".into(),
args: vec![],
line: 1,
}],
parent: None,
});
let inner = PragmaSet {
entries: vec![Pragma {
name: "strict_values".into(),
args: vec![],
line: 5,
}],
parent: None,
};
let (attached, conflicts) = inner.attach_to(outer);
assert!(attached.strict_types());
assert!(attached.strict_values());
assert!(conflicts.is_empty());
}
#[test]
fn attached_records_arg_conflict() {
let outer = std::sync::Arc::new(PragmaSet {
entries: vec![Pragma {
name: "assert_for".into(),
args: vec!["alpha".into()],
line: 1,
}],
parent: None,
});
let inner = PragmaSet {
entries: vec![Pragma {
name: "assert_for".into(),
args: vec!["beta".into()],
line: 5,
}],
parent: None,
};
let (_attached, conflicts) = inner.attach_to(outer);
assert_eq!(conflicts.len(), 1);
assert_eq!(conflicts[0].name, "assert_for");
assert_eq!(conflicts[0].outer_line, 1);
assert_eq!(conflicts[0].inner_line, 5);
}
}