use relux_core::diagnostics::DefinitionRef;
use relux_core::diagnostics::EffectId;
use relux_core::diagnostics::FnId;
use relux_ir::IrEffect;
use relux_ir::IrEffectItem;
use relux_ir::IrEffectStart;
use relux_ir::IrExpr;
use relux_ir::IrFn;
use relux_ir::IrPureExpr;
use relux_ir::IrPureFn;
use relux_ir::IrPureStmt;
use relux_ir::IrShellStmt;
use relux_ir::IrTest;
use relux_ir::IrTestItem;
use relux_ir::Tables;
use relux_ir::marker::MarkerRecording;
use std::collections::HashSet;
pub fn collect_test_marker_recordings(
test: &IrTest,
test_meta: &relux_ir::TestMeta,
tables: &Tables,
) -> Vec<MarkerRecording> {
let mut visitor = Visitor::new(tables);
if let Some(recs) = tables.marker_recordings.get(test_meta.definition()) {
visitor.recordings.extend(recs.iter().cloned());
}
for start in test.starts() {
visitor.visit_effect_start(start);
}
for item in test.body() {
visitor.visit_test_item(item);
}
visitor.recordings
}
struct Visitor<'a> {
tables: &'a Tables,
seen_effects: HashSet<EffectId>,
seen_fns: HashSet<FnId>,
seen_pure_fns: HashSet<FnId>,
recordings: Vec<MarkerRecording>,
}
impl<'a> Visitor<'a> {
fn new(tables: &'a Tables) -> Self {
Self {
tables,
seen_effects: HashSet::new(),
seen_fns: HashSet::new(),
seen_pure_fns: HashSet::new(),
recordings: Vec::new(),
}
}
fn visit_effect_start(&mut self, start: &IrEffectStart) {
for entry in start.overlay() {
self.visit_pure_expr(entry.value());
}
let effect_id = start.effect().clone();
if !self.seen_effects.insert(effect_id.clone()) {
return;
}
let Some(result) = self.tables.effects.get(&effect_id) else {
return;
};
let Ok(effect) = result.as_ref() else {
return;
};
let effect = effect.clone();
if let Some(recs) = self
.tables
.marker_recordings
.get(&DefinitionRef::Effect(effect_id.clone()))
{
self.recordings.extend(recs.iter().cloned());
}
self.visit_effect(&effect);
}
fn visit_effect(&mut self, effect: &IrEffect) {
for start in effect.starts() {
self.visit_effect_start(start);
}
for item in effect.body() {
self.visit_effect_item(item);
}
}
fn visit_test_item(&mut self, item: &IrTestItem) {
match item {
IrTestItem::Comment { .. } | IrTestItem::DocString { .. } => {}
IrTestItem::Start { start, .. } => self.visit_effect_start(start),
IrTestItem::Let { stmt, .. } => {
if let Some(expr) = stmt.value() {
self.visit_pure_expr(expr);
}
}
IrTestItem::Shell { block, .. } => {
for stmt in block.body() {
self.visit_shell_stmt(stmt);
}
}
IrTestItem::Cleanup { block, .. } => {
for stmt in block.body() {
self.visit_shell_stmt(stmt);
}
}
}
}
fn visit_effect_item(&mut self, item: &IrEffectItem) {
match item {
IrEffectItem::Comment { .. }
| IrEffectItem::Expect { .. }
| IrEffectItem::Expose { .. } => {}
IrEffectItem::Start { start, .. } => self.visit_effect_start(start),
IrEffectItem::Let { stmt, .. } => {
if let Some(expr) = stmt.value() {
self.visit_pure_expr(expr);
}
}
IrEffectItem::Shell { block, .. } => {
for stmt in block.body() {
self.visit_shell_stmt(stmt);
}
}
IrEffectItem::Cleanup { block, .. } => {
for stmt in block.body() {
self.visit_shell_stmt(stmt);
}
}
}
}
fn visit_shell_stmt(&mut self, stmt: &IrShellStmt) {
match stmt {
IrShellStmt::Comment { .. }
| IrShellStmt::Send { .. }
| IrShellStmt::SendRaw { .. }
| IrShellStmt::MatchRegex { .. }
| IrShellStmt::MatchLiteral { .. }
| IrShellStmt::TimedMatchRegex { .. }
| IrShellStmt::TimedMatchLiteral { .. }
| IrShellStmt::Timeout { .. }
| IrShellStmt::FailRegex { .. }
| IrShellStmt::FailLiteral { .. }
| IrShellStmt::ClearFailPattern { .. }
| IrShellStmt::BufferReset { .. } => {}
IrShellStmt::Let { stmt, .. } => {
if let Some(expr) = stmt.value() {
self.visit_expr(expr);
}
}
IrShellStmt::Assign { stmt, .. } => self.visit_expr(stmt.value()),
IrShellStmt::Expr { expr, .. } => self.visit_expr(expr),
}
}
fn visit_pure_stmt(&mut self, stmt: &IrPureStmt) {
match stmt {
IrPureStmt::Comment { .. } => {}
IrPureStmt::Let { stmt, .. } => {
if let Some(expr) = stmt.value() {
self.visit_pure_expr(expr);
}
}
IrPureStmt::Assign { stmt, .. } => self.visit_pure_expr(stmt.value()),
IrPureStmt::Expr { expr, .. } => self.visit_pure_expr(expr),
}
}
fn visit_expr(&mut self, expr: &IrExpr) {
match expr {
IrExpr::String { .. }
| IrExpr::Var { .. }
| IrExpr::QualifiedVar { .. }
| IrExpr::CaptureRef { .. } => {}
IrExpr::Call { call, .. } => {
for arg in call.args() {
self.visit_expr(arg);
}
self.visit_fn(call.resolved());
}
}
}
fn visit_pure_expr(&mut self, expr: &IrPureExpr) {
match expr {
IrPureExpr::String { .. } | IrPureExpr::Var { .. } => {}
IrPureExpr::Call { call, .. } => {
for arg in call.args() {
self.visit_pure_expr(arg);
}
self.visit_pure_fn(call.resolved());
}
}
}
fn visit_fn(&mut self, fn_id: &FnId) {
if !self.seen_fns.insert(fn_id.clone()) {
return;
}
let Some(result) = self.tables.fns.get(fn_id) else {
return;
};
let Ok(ir_fn) = result.as_ref() else {
return;
};
if let IrFn::UserDefined { body, .. } = ir_fn.clone() {
if let Some(recs) = self
.tables
.marker_recordings
.get(&DefinitionRef::Fn(fn_id.clone()))
{
self.recordings.extend(recs.iter().cloned());
}
for stmt in body {
self.visit_shell_stmt(&stmt);
}
}
}
fn visit_pure_fn(&mut self, fn_id: &FnId) {
if !self.seen_pure_fns.insert(fn_id.clone()) {
return;
}
let Some(result) = self.tables.pure_fns.get(fn_id) else {
return;
};
let Ok(ir_fn) = result.as_ref() else {
return;
};
if let IrPureFn::UserDefined { body, .. } = ir_fn.clone() {
if let Some(recs) = self
.tables
.marker_recordings
.get(&DefinitionRef::Fn(fn_id.clone()))
{
self.recordings.extend(recs.iter().cloned());
}
for stmt in body {
self.visit_pure_stmt(&stmt);
}
}
}
}