use rustc_hash::FxHashMap;
use shuck_ast::Span;
use smallvec::SmallVec;
use crate::{BindingId, ReferenceId, ScopeId, SpanKey};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BlockId(pub(crate) u32);
impl BlockId {
pub(crate) fn index(self) -> usize {
self.0 as usize
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BasicBlock {
pub id: BlockId,
pub commands: SmallVec<[Span; 1]>,
pub bindings: SmallVec<[BindingId; 2]>,
pub references: SmallVec<[ReferenceId; 4]>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EdgeKind {
Sequential,
ConditionalTrue,
ConditionalFalse,
LoopBack,
LoopExit,
CaseArm,
CaseFallthrough,
CaseContinue,
NestedRegion,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct FlowContext {
pub in_function: bool,
pub loop_depth: u32,
pub in_subshell: bool,
pub in_block: bool,
pub exit_status_checked: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CommandConditionRole {
If,
Elif,
While,
Until,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ControlFlowGraph {
pub(crate) blocks: Vec<BasicBlock>,
pub(crate) successors: Vec<SmallVec<[FlowEdge; 2]>>,
pub(crate) predecessors: Vec<SmallVec<[BlockId; 2]>>,
pub(crate) entry: BlockId,
pub(crate) exits: Vec<BlockId>,
pub(crate) natural_exits: Vec<BlockId>,
pub(crate) script_terminators: Vec<BlockId>,
pub(crate) script_always_terminates: bool,
pub(crate) unreachable: Vec<BlockId>,
pub(crate) scope_entries: FxHashMap<ScopeId, BlockId>,
pub(crate) scope_exits: FxHashMap<ScopeId, SmallVec<[BlockId; 2]>>,
pub(crate) command_blocks: FxHashMap<SpanKey, SmallVec<[BlockId; 1]>>,
pub(crate) unreachable_causes: FxHashMap<BlockId, UnreachableCause>,
}
pub(crate) type FlowEdge = (BlockId, EdgeKind);
impl ControlFlowGraph {
pub fn blocks(&self) -> &[BasicBlock] {
&self.blocks
}
pub fn block(&self, id: BlockId) -> &BasicBlock {
&self.blocks[id.index()]
}
pub fn successors(&self, id: BlockId) -> &[(BlockId, EdgeKind)] {
self.successors
.get(id.index())
.map(SmallVec::as_slice)
.unwrap_or(&[])
}
pub fn predecessors(&self, id: BlockId) -> &[BlockId] {
self.predecessors
.get(id.index())
.map(SmallVec::as_slice)
.unwrap_or(&[])
}
pub fn entry(&self) -> BlockId {
self.entry
}
pub fn exits(&self) -> &[BlockId] {
&self.exits
}
pub fn natural_exits(&self) -> &[BlockId] {
&self.natural_exits
}
pub fn script_terminators(&self) -> &[BlockId] {
&self.script_terminators
}
pub fn script_always_terminates(&self) -> bool {
self.script_always_terminates
}
pub fn scope_entry(&self, scope: ScopeId) -> Option<BlockId> {
self.scope_entries.get(&scope).copied()
}
pub(crate) fn scope_exits(&self, scope: ScopeId) -> Option<&[BlockId]> {
self.scope_exits.get(&scope).map(SmallVec::as_slice)
}
pub fn unreachable(&self) -> &[BlockId] {
&self.unreachable
}
pub(crate) fn block_ids_for_span(&self, span: Span) -> &[BlockId] {
self.command_blocks
.get(&SpanKey::new(span))
.map(SmallVec::as_slice)
.unwrap_or(&[])
}
pub(crate) fn unreachable_cause(&self, id: BlockId) -> Option<UnreachableCause> {
self.unreachable_causes.get(&id).copied()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum UnreachableCauseKind {
ShellTerminator,
ScriptTerminatingFunctionCall,
LoopControl,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct UnreachableCause {
pub(crate) span: Span,
pub(crate) kind: UnreachableCauseKind,
}
impl UnreachableCause {
pub(super) fn shell_terminator(span: Span) -> Self {
Self {
span,
kind: UnreachableCauseKind::ShellTerminator,
}
}
pub(super) fn script_terminating_function_call(span: Span) -> Self {
Self {
span,
kind: UnreachableCauseKind::ScriptTerminatingFunctionCall,
}
}
pub(super) fn loop_control(span: Span) -> Self {
Self {
span,
kind: UnreachableCauseKind::LoopControl,
}
}
}