use super::FlowLogRule;
use crate::common::compute_fp;
use crate::common::{FileId, Ignored, Span};
use crate::parser::error::{grammar_bug, ParseError};
use crate::parser::{span_of, Lexeme, Rule};
use pest::iterators::Pair;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) enum LoopConnective {
And,
Or,
}
pub(crate) type IterWindows = Vec<(u16, u16)>;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct StopRelation {
name: String,
fp: u64,
span: Ignored<Span>,
}
impl StopRelation {
#[must_use]
#[inline]
pub(crate) fn name(&self) -> &str {
&self.name
}
#[must_use]
#[inline]
pub(crate) fn fp(&self) -> u64 {
self.fp
}
#[must_use]
#[inline]
pub(crate) fn span(&self) -> Span {
self.span.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct IterativeDirective {
name: String,
fp: u64,
span: Ignored<Span>,
}
impl IterativeDirective {
#[must_use]
#[inline]
pub(crate) fn name(&self) -> &str {
&self.name
}
#[must_use]
#[inline]
pub(crate) fn fp(&self) -> u64 {
self.fp
}
#[must_use]
#[inline]
pub(crate) fn span(&self) -> Span {
self.span.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct StopGroup {
first: StopRelation,
rest: Vec<(LoopConnective, StopRelation)>,
}
impl StopGroup {
#[must_use]
pub(crate) fn new(first: StopRelation, rest: Vec<(LoopConnective, StopRelation)>) -> Self {
Self { first, rest }
}
#[must_use]
pub(crate) fn first(&self) -> &StopRelation {
&self.first
}
#[must_use]
pub(crate) fn rest(&self) -> &[(LoopConnective, StopRelation)] {
&self.rest
}
pub(crate) fn relations(&self) -> impl Iterator<Item = &StopRelation> {
std::iter::once(&self.first).chain(self.rest.iter().map(|(_, r)| r))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct LoopCondition {
while_part: Option<IterWindows>,
connective: Option<LoopConnective>,
until_part: Option<StopGroup>,
}
impl LoopCondition {
#[must_use]
pub(crate) fn new(
while_part: Option<IterWindows>,
connective: Option<LoopConnective>,
until_part: Option<StopGroup>,
) -> Self {
Self {
while_part,
connective,
until_part,
}
}
#[must_use]
pub(crate) fn while_part(&self) -> Option<&[(u16, u16)]> {
self.while_part.as_deref()
}
#[must_use]
pub(crate) fn connective(&self) -> Option<&LoopConnective> {
self.connective.as_ref()
}
#[must_use]
pub(crate) fn until_part(&self) -> Option<&StopGroup> {
self.until_part.as_ref()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct LoopBlock {
iterative_relations: Vec<IterativeDirective>,
condition: Option<LoopCondition>,
rules: Vec<FlowLogRule>,
span: Ignored<Span>,
}
impl LoopBlock {
#[must_use]
#[inline]
pub(crate) fn span(&self) -> Span {
self.span.0
}
#[must_use]
pub(crate) fn iterative_relations(&self) -> &[IterativeDirective] {
&self.iterative_relations
}
#[must_use]
pub(crate) fn condition(&self) -> Option<&LoopCondition> {
self.condition.as_ref()
}
#[must_use]
pub(crate) fn rules(&self) -> &[FlowLogRule] {
&self.rules
}
pub(crate) fn rules_mut(&mut self) -> &mut Vec<FlowLogRule> {
&mut self.rules
}
}
impl fmt::Display for LoopConnective {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::And => write!(f, "and"),
Self::Or => write!(f, "or"),
}
}
}
impl fmt::Display for StopRelation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name)
}
}
impl fmt::Display for StopGroup {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.first)?;
for (conn, rel) in &self.rest {
write!(f, " {conn} {rel}")?;
}
Ok(())
}
}
impl fmt::Display for LoopCondition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match (&self.while_part, &self.connective, &self.until_part) {
(Some(windows), conn, until) => {
if !windows.is_empty() {
write!(f, "while {{ {} }}", display_windows(windows))?;
}
if let Some(sg) = until {
if conn.as_ref() == Some(&LoopConnective::Or) {
write!(f, " or until {{ {sg} }}")?;
} else {
write!(f, " until {{ {sg} }}")?;
}
}
}
(None, conn, Some(sg)) => {
write!(f, "until {{ {sg} }}")?;
let _ = conn;
}
(None, _, None) => {}
}
Ok(())
}
}
fn display_windows(windows: &[(u16, u16)]) -> String {
let parts: Vec<String> = windows
.iter()
.map(|(lo, hi)| {
if *lo == 0 && *hi == u16::MAX {
"@it >= 0".to_string()
} else if *lo == 0 {
format!("@it <= {hi}")
} else if *hi == u16::MAX {
format!("@it >= {lo}")
} else if lo == hi {
format!("@it == {lo}")
} else {
format!("@it >= {lo} and @it <= {hi}")
}
})
.collect();
if parts.is_empty() {
String::new()
} else {
parts.join(" or ")
}
}
impl fmt::Display for LoopBlock {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.condition.is_some() {
write!(f, "loop")?;
} else {
write!(f, "fixpoint")?;
}
if let Some(cond) = &self.condition {
writeln!(f, " {} {{", cond)?;
} else {
writeln!(f, " {{")?;
}
for directive in &self.iterative_relations {
writeln!(f, " .iterative {}", directive.name())?;
}
for rule in &self.rules {
writeln!(f, " {rule}")?;
}
write!(f, "}}")
}
}
fn range_for_op(op: &str, n: u16) -> Result<Vec<(u16, u16)>, ParseError> {
Ok(match op {
"==" => vec![(n, n)],
"<" => {
if n == 0 {
vec![]
} else {
vec![(0, n - 1)]
}
}
"<=" => vec![(0, n)],
">" => {
if n == u16::MAX {
vec![]
} else {
vec![(n + 1, u16::MAX)]
}
}
">=" => vec![(n, u16::MAX)],
other => {
return Err(grammar_bug(format!(
"loop_iter_expr unknown comparison operator '{other}'"
)));
}
})
}
fn intersect_ranges(a: &[(u16, u16)], b: &[(u16, u16)]) -> Vec<(u16, u16)> {
let mut result = Vec::new();
for &(a_lo, a_hi) in a {
for &(b_lo, b_hi) in b {
let lo = a_lo.max(b_lo);
let hi = a_hi.min(b_hi);
if lo <= hi {
result.push((lo, hi));
}
}
}
result
}
fn union_ranges(a: &[(u16, u16)], b: &[(u16, u16)]) -> Vec<(u16, u16)> {
let mut result = a.to_vec();
result.extend_from_slice(b);
result
}
fn parse_connective(pair: Pair<Rule>) -> Result<LoopConnective, ParseError> {
let inner = pair
.into_inner()
.next()
.ok_or_else(|| grammar_bug("loop_connective missing child"))?;
Ok(match inner.as_rule() {
Rule::loop_and => LoopConnective::And,
Rule::loop_or => LoopConnective::Or,
r => {
return Err(grammar_bug(format!(
"loop_connective unexpected rule {r:?}"
)))
}
})
}
fn parse_iter_expr(pair: Pair<Rule>) -> Result<IterWindows, ParseError> {
let mut children = pair.into_inner();
let first_op = children
.next()
.ok_or_else(|| grammar_bug("loop_iter_expr missing first compare op"))?
.as_str()
.to_string();
let first_n: u16 = children
.next()
.ok_or_else(|| grammar_bug("loop_iter_expr missing first integer"))?
.as_str()
.trim_start_matches('+')
.parse()
.map_err(|e| {
grammar_bug(format!(
"loop_iter_expr iteration bound must fit in u16: {e}"
))
})?;
let mut ranges = range_for_op(&first_op, first_n)?;
while let Some(conn_pair) = children.next() {
let connective = parse_connective(conn_pair)?;
let op = children
.next()
.ok_or_else(|| grammar_bug("loop_iter_expr missing compare op in repeat"))?
.as_str()
.to_string();
let n: u16 = children
.next()
.ok_or_else(|| grammar_bug("loop_iter_expr missing integer in repeat"))?
.as_str()
.trim_start_matches('+')
.parse()
.map_err(|e| {
grammar_bug(format!(
"loop_iter_expr iteration bound must fit in u16: {e}"
))
})?;
let new_range = range_for_op(&op, n)?;
ranges = match connective {
LoopConnective::And => intersect_ranges(&ranges, &new_range),
LoopConnective::Or => union_ranges(&ranges, &new_range),
};
}
Ok(ranges)
}
fn parse_stop_group(pair: Pair<Rule>, file: FileId) -> Result<StopGroup, ParseError> {
let mut children = pair.into_inner();
let first_rel_pair = children
.next()
.ok_or_else(|| grammar_bug("loop_stop_group missing first bool relation"))?;
let first = parse_bool_relation(first_rel_pair, file)?;
let mut rest = Vec::new();
while let Some(conn_pair) = children.next() {
let connective = parse_connective(conn_pair)?;
let rel_pair = children
.next()
.ok_or_else(|| grammar_bug("loop_stop_group missing bool relation after connective"))?;
rest.push((connective, parse_bool_relation(rel_pair, file)?));
}
Ok(StopGroup::new(first, rest))
}
fn parse_bool_relation(pair: Pair<Rule>, file: FileId) -> Result<StopRelation, ParseError> {
let span = span_of(&pair, file);
let name = pair
.into_inner()
.next()
.ok_or_else(|| grammar_bug("loop_bool_relation missing relation_name"))?
.as_str()
.to_ascii_lowercase();
let fp = compute_fp(&name);
Ok(StopRelation {
name,
fp,
span: Ignored(span),
})
}
impl Lexeme for LoopCondition {
fn from_parsed_rule(pair: Pair<Rule>, file: FileId) -> Result<Self, ParseError> {
let mut children = pair.into_inner().peekable();
let first = children
.next()
.ok_or_else(|| grammar_bug("loop_condition missing first clause"))?;
Ok(match first.as_rule() {
Rule::loop_while => {
let iter_expr = first
.into_inner()
.next()
.ok_or_else(|| grammar_bug("loop_while missing loop_iter_expr"))?;
let windows = parse_iter_expr(iter_expr)?;
let mut connective = None;
let mut until_group = None;
for next in children.by_ref() {
match next.as_rule() {
Rule::loop_or => connective = Some(LoopConnective::Or),
Rule::loop_until => {
let stop_group_pair = next
.into_inner()
.next()
.ok_or_else(|| grammar_bug("loop_until missing loop_stop_group"))?;
until_group = Some(parse_stop_group(stop_group_pair, file)?);
}
r => {
return Err(grammar_bug(format!(
"loop_condition unexpected rule after loop_while: {r:?}"
)));
}
}
}
if until_group.is_some() && connective.is_none() {
connective = Some(LoopConnective::And);
}
Self::new(Some(windows), connective, until_group)
}
Rule::loop_until => {
let stop_group_pair = first
.into_inner()
.next()
.ok_or_else(|| grammar_bug("loop_until missing loop_stop_group"))?;
let stop_group = parse_stop_group(stop_group_pair, file)?;
let mut connective = None;
let mut while_windows = None;
for next in children {
match next.as_rule() {
Rule::loop_or => connective = Some(LoopConnective::Or),
Rule::loop_while => {
let iter_expr = next
.into_inner()
.next()
.ok_or_else(|| grammar_bug("loop_while missing loop_iter_expr"))?;
while_windows = Some(parse_iter_expr(iter_expr)?);
}
r => {
return Err(grammar_bug(format!(
"loop_condition unexpected rule after loop_until: {r:?}"
)));
}
}
}
if while_windows.is_some() && connective.is_none() {
connective = Some(LoopConnective::And);
}
Self::new(while_windows, connective, Some(stop_group))
}
r => {
return Err(grammar_bug(format!(
"loop_condition unexpected first child rule {r:?}"
)));
}
})
}
}
impl Lexeme for LoopBlock {
fn from_parsed_rule(pair: Pair<Rule>, file: FileId) -> Result<Self, ParseError> {
let span = span_of(&pair, file);
let mut inner = pair.into_inner().peekable();
let condition = if inner.peek().map(|p| p.as_rule()) == Some(Rule::loop_condition) {
let cond_pair = inner
.next()
.ok_or_else(|| grammar_bug("missing loop_condition"))?;
Some(LoopCondition::from_parsed_rule(cond_pair, file)?)
} else {
None
};
let mut iterative_relations = Vec::new();
let mut rules = Vec::new();
for item in inner {
match item.as_rule() {
Rule::iterative_directive => {
let directive_span = span_of(&item, file);
let name = item
.into_inner()
.next()
.ok_or_else(|| grammar_bug("iterative_directive missing relation_name"))?
.as_str()
.to_ascii_lowercase();
let fp = compute_fp(&name);
iterative_relations.push(IterativeDirective {
name,
fp,
span: Ignored(directive_span),
});
}
Rule::rule => {
rules.extend(FlowLogRule::expand_from_parsed_rule(item, file)?);
}
r => {
return Err(grammar_bug(format!(
"unexpected rule in loop/fixpoint block: {r:?}"
)));
}
}
}
Ok(Self {
iterative_relations,
condition,
rules,
span: Ignored(span),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::{FlowLogParser, Rule};
use pest::Parser;
fn parse_loop_block(input: &str) -> LoopBlock {
use crate::common::FileId;
if let Ok(mut pairs) = FlowLogParser::parse(Rule::loop_block, input) {
LoopBlock::from_parsed_rule(pairs.next().unwrap(), FileId(0)).unwrap()
} else {
let mut pairs = FlowLogParser::parse(Rule::fixpoint_block, input)
.unwrap_or_else(|e| panic!("parse error: {e}"));
LoopBlock::from_parsed_rule(pairs.next().unwrap(), FileId(0)).unwrap()
}
}
#[test]
fn fixpoint_no_condition() {
let block = parse_loop_block("fixpoint { }");
assert!(block.condition().is_none());
assert!(block.rules().is_empty());
}
#[test]
fn while_iter_less_equal() {
let block = parse_loop_block("loop while { @it <= 6 } { }");
let cond = block.condition().unwrap();
assert_eq!(cond.while_part().unwrap(), &[(0u16, 6u16)]);
assert!(cond.until_part().is_none());
}
#[test]
fn while_iter_greater_equal() {
let block = parse_loop_block("loop while { @it >= 10 } { }");
let cond = block.condition().unwrap();
assert_eq!(cond.while_part().unwrap(), &[(10u16, u16::MAX)]);
}
#[test]
fn while_iter_and_intersection() {
let block = parse_loop_block("loop while { @it >= 5 and @it <= 10 } { }");
let cond = block.condition().unwrap();
assert_eq!(cond.while_part().unwrap(), &[(5u16, 10u16)]);
}
#[test]
fn while_iter_or_union() {
let block = parse_loop_block("loop while { @it < 5 or @it > 10 } { }");
let cond = block.condition().unwrap();
assert_eq!(
cond.while_part().unwrap(),
&[(0u16, 4u16), (11u16, u16::MAX)]
);
assert!(cond.until_part().is_none());
}
#[test]
fn until_single_relation() {
let block = parse_loop_block("loop until { done } { }");
let cond = block.condition().unwrap();
assert!(cond.while_part().is_none());
let sg = cond.until_part().unwrap();
assert_eq!(sg.first().name(), "done");
assert_eq!(sg.first().fp(), compute_fp("done"));
assert!(sg.rest().is_empty());
}
#[test]
fn until_two_relations_or() {
let block = parse_loop_block("loop until { done1 or done2 } { }");
let cond = block.condition().unwrap();
let sg = cond.until_part().unwrap();
assert_eq!(sg.first().name(), "done1");
assert_eq!(sg.rest().len(), 1);
assert_eq!(sg.rest()[0].0, LoopConnective::Or);
assert_eq!(sg.rest()[0].1.name(), "done2");
}
#[test]
fn while_until_default_and() {
let block = parse_loop_block("loop while { @it <= 6 } until { done } { }");
let cond = block.condition().unwrap();
assert_eq!(cond.while_part().unwrap(), &[(0u16, 6u16)]);
assert_eq!(cond.connective(), Some(&LoopConnective::And));
let sg = cond.until_part().unwrap();
assert_eq!(sg.first().name(), "done");
}
#[test]
fn until_while_default_and() {
let block = parse_loop_block("loop until { done } while { @it <= 3 } { }");
let cond = block.condition().unwrap();
assert_eq!(cond.while_part().unwrap(), &[(0u16, 3u16)]);
assert_eq!(cond.connective(), Some(&LoopConnective::And));
assert_eq!(cond.until_part().unwrap().first().name(), "done");
}
#[test]
fn until_or_while() {
let block = parse_loop_block("loop until { done } or while { @it <= 1 } { }");
let cond = block.condition().unwrap();
assert_eq!(cond.while_part().unwrap(), &[(0u16, 1u16)]);
assert_eq!(cond.connective(), Some(&LoopConnective::Or));
assert_eq!(cond.until_part().unwrap().first().name(), "done");
}
#[test]
fn while_or_until() {
let block = parse_loop_block("loop while { @it <= 0 } or until { done } { }");
let cond = block.condition().unwrap();
assert_eq!(cond.while_part().unwrap(), &[(0u16, 0u16)]);
assert_eq!(cond.connective(), Some(&LoopConnective::Or));
assert_eq!(cond.until_part().unwrap().first().name(), "done");
}
#[test]
fn with_rule() {
let block = parse_loop_block("fixpoint { reach(X, Z) :- edge(X, Y), reach(Y, Z). }");
assert!(block.condition().is_none());
assert_eq!(block.rules().len(), 1);
assert_eq!(block.rules()[0].head().name(), "reach");
}
#[test]
fn display_fixpoint() {
let block = parse_loop_block("fixpoint { }");
let s = block.to_string();
assert!(s.starts_with("fixpoint {"));
}
#[test]
fn display_while_only() {
let block = parse_loop_block("loop while { @it <= 6 } { }");
let s = block.to_string();
assert!(s.contains("while") && s.contains("@it <= 6"));
}
#[test]
fn display_until_or_while() {
let block = parse_loop_block("loop until { done } or while { @it <= 6 } { }");
let s = block.to_string();
assert!(s.contains("done") && s.contains("or") && s.contains("@it <= 6"));
}
#[test]
fn relations_iterator() {
let block = parse_loop_block("loop until { done1 or done2 } { }");
let cond = block.condition().unwrap();
let sg = cond.until_part().unwrap();
let names: Vec<&str> = sg.relations().map(StopRelation::name).collect();
assert_eq!(names, vec!["done1", "done2"]);
}
#[test]
fn iterative_directive_single() {
let block = parse_loop_block("fixpoint { .iterative removed }");
let itr = block.iterative_relations();
assert_eq!(itr.len(), 1);
assert_eq!(itr[0].name(), "removed");
assert_eq!(itr[0].fp(), compute_fp("removed"));
assert!(block.rules().is_empty());
}
#[test]
fn iterative_directive_multiple() {
let block = parse_loop_block("fixpoint { .iterative active_edge .iterative degree }");
let itr = block.iterative_relations();
assert_eq!(itr.len(), 2);
assert_eq!(itr[0].name(), "active_edge");
assert_eq!(itr[1].name(), "degree");
}
#[test]
fn iterative_directive_with_rules() {
let block = parse_loop_block(
"fixpoint { .iterative reach reach(X, Z) :- edge(X, Y), reach(Y, Z). }",
);
assert_eq!(block.iterative_relations().len(), 1);
assert_eq!(block.iterative_relations()[0].name(), "reach");
assert_eq!(block.rules().len(), 1);
}
#[test]
fn iterative_directive_in_loop() {
let block = parse_loop_block(
"loop while { @it <= 5 } { .iterative active_edge active_edge(X,Y) :- edge(X,Y). }",
);
assert_eq!(block.iterative_relations().len(), 1);
assert_eq!(block.iterative_relations()[0].name(), "active_edge");
let cond = block.condition().unwrap();
assert_eq!(cond.while_part().unwrap(), &[(0u16, 5u16)]);
}
#[test]
fn no_iterative_is_empty() {
let block = parse_loop_block("loop while { @it <= 3 } { }");
assert!(block.iterative_relations().is_empty());
}
#[test]
fn display_iterative_directive() {
let block = parse_loop_block("fixpoint { .iterative active_edge .iterative degree }");
let s = block.to_string();
assert!(s.contains(".iterative active_edge"));
assert!(s.contains(".iterative degree"));
}
}