#![deny(rustdoc::broken_intra_doc_links)]
use std::{
collections::HashMap,
fmt::{self, Display, Formatter},
};
use biome_rowan::{Language, SyntaxElement, SyntaxNode};
pub mod builder;
use crate::builder::BlockId;
#[derive(Debug, Clone)]
pub struct ControlFlowGraph<L: Language> {
pub blocks: Vec<BasicBlock<L>>,
pub node: SyntaxNode<L>,
}
impl<L: Language> ControlFlowGraph<L> {
fn new(node: SyntaxNode<L>) -> Self {
ControlFlowGraph {
blocks: vec![BasicBlock::new(None, None)],
node,
}
}
pub fn get(&self, id: BlockId) -> &BasicBlock<L> {
let block_index = id.index() as usize;
&self.blocks[block_index]
}
pub fn block_id_iter(&self) -> impl Iterator<Item = (BlockId, &BasicBlock<L>)> {
self.blocks.iter().enumerate().map(|(index, block)| {
(
BlockId {
index: index as u32,
},
block,
)
})
}
}
#[derive(Debug, Clone)]
pub struct BasicBlock<L: Language> {
pub instructions: Vec<Instruction<L>>,
pub exception_handlers: Vec<ExceptionHandler>,
pub cleanup_handlers: Vec<ExceptionHandler>,
}
impl<L: Language> BasicBlock<L> {
fn new(
exception_handlers: impl IntoIterator<Item = ExceptionHandler>,
cleanup_handlers: impl IntoIterator<Item = ExceptionHandler>,
) -> Self {
Self {
instructions: Vec::new(),
exception_handlers: exception_handlers.into_iter().collect(),
cleanup_handlers: cleanup_handlers.into_iter().collect(),
}
}
}
#[derive(Debug, Clone)]
pub struct Instruction<L: Language> {
pub kind: InstructionKind,
pub node: Option<SyntaxElement<L>>,
}
#[derive(Copy, Clone, Debug)]
pub enum InstructionKind {
Statement,
Jump {
conditional: bool,
block: BlockId,
finally_fallthrough: bool,
},
Return,
}
#[derive(Debug, Clone, Copy)]
pub struct ExceptionHandler {
pub kind: ExceptionHandlerKind,
pub target: BlockId,
}
#[derive(Debug, Clone, Copy)]
pub enum ExceptionHandlerKind {
Catch,
Finally,
}
impl<L: Language> Display for ControlFlowGraph<L> {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
writeln!(fmt, "flowchart TB")?;
let mut links = HashMap::new();
for (id, block) in self.blocks.iter().enumerate() {
if fmt.alternate() {
writeln!(fmt, " subgraph block_{id}")?;
writeln!(fmt, " direction TB")?;
} else {
write!(fmt, " block_{id}[\"<b>block_{id}</b><br/>")?;
}
for (index, inst) in block.instructions.iter().enumerate() {
if fmt.alternate() {
write!(fmt, " ")?;
if let Some(index) = index.checked_sub(1) {
write!(fmt, "block_{id}_inst_{index} --> ")?;
}
writeln!(fmt, "block_{id}_inst_{index}[\"{inst}\"]")?;
} else {
if index > 0 {
write!(fmt, "<br/>")?;
}
write!(fmt, "{inst}")?;
}
if let InstructionKind::Jump {
conditional, block, ..
} = inst.kind
{
let condition = inst
.node
.as_ref()
.filter(|_| conditional)
.map(|node| (node.kind(), node.text_trimmed_range()));
links.insert((id, index, block.index()), condition);
}
}
if fmt.alternate() {
writeln!(fmt, " end")?;
} else {
writeln!(fmt, "\"]")?;
}
}
writeln!(fmt)?;
for ((id, index, to), condition) in links {
write!(fmt, " block_{id}")?;
if fmt.alternate() {
write!(fmt, "_inst_{index}")?;
}
if let Some((cond, range)) = condition {
write!(fmt, " -- \"{cond:?} {range:?}\"")?;
}
writeln!(fmt, " --> block_{to}")?;
}
Ok(())
}
}
impl<L: Language> Display for Instruction<L> {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
match self.kind {
InstructionKind::Statement => {
if let Some(node) = &self.node {
write!(
fmt,
"Statement({:?} {:?})",
node.kind(),
node.text_trimmed_range()
)
} else {
write!(fmt, "Statement")
}
}
InstructionKind::Jump {
conditional: true,
block,
..
} if self.node.is_some() => {
let node = self.node.as_ref().unwrap();
write!(
fmt,
"Jump {{ condition: {:?} {:?}, block: {} }}",
node.kind(),
node.text_trimmed_range(),
block.index()
)
}
InstructionKind::Jump { block, .. } => {
write!(fmt, "Jump {{ block: {} }}", block.index())
}
InstructionKind::Return => {
if let Some(node) = &self.node {
write!(
fmt,
"Return({:?} {:?})",
node.kind(),
node.text_trimmed_range()
)
} else {
write!(fmt, "Return")
}
}
}
}
}