use std::cell::RefCell;
use std::collections::HashSet;
use std::path::Path;
use std::rc::Rc;
use crate::error::WavepeekError;
use crate::expr::sema::{
BoundEventKind, BoundInsideItem, BoundLogicalKind, BoundLogicalNode, BoundSelection,
};
use crate::expr::{
BoundEventExpr, BoundLogicalExpr, EventEvalFrame, ExprDiagnostic, ExprValuePayload,
ExpressionHost, SampledValue, SignalHandle, Span, bind_event_expr_ast, bind_logical_expr_ast,
eval_logical_expr_at, event_matches_at, parse_event_expr_ast, parse_logical_expr_ast,
};
use crate::waveform::{ExprResolvedSignal, Waveform, expr_host::WaveformExprHost};
pub(crate) type SharedWaveform = Rc<RefCell<Waveform>>;
pub(crate) fn open_shared_waveform(path: &Path) -> Result<SharedWaveform, WavepeekError> {
Ok(Rc::new(RefCell::new(Waveform::open(path)?)))
}
pub(crate) struct ScopedExprHost<'a> {
inner: &'a dyn ExpressionHost,
scope: Option<&'a str>,
}
impl<'a> ScopedExprHost<'a> {
pub(crate) fn new(inner: &'a dyn ExpressionHost, scope: Option<&'a str>) -> Self {
Self { inner, scope }
}
}
impl ExpressionHost for ScopedExprHost<'_> {
fn resolve_signal(&self, name: &str) -> Result<SignalHandle, ExprDiagnostic> {
let resolved_name = match self.scope {
Some(_) if name.contains('.') => return Err(unknown_signal_diagnostic(name)),
Some(scope) => format!("{scope}.{name}"),
None => name.to_string(),
};
self.inner.resolve_signal(resolved_name.as_str())
}
fn signal_type(&self, handle: SignalHandle) -> Result<crate::expr::ExprType, ExprDiagnostic> {
self.inner.signal_type(handle)
}
fn sample_value(
&self,
handle: SignalHandle,
timestamp: u64,
) -> Result<SampledValue, ExprDiagnostic> {
self.inner.sample_value(handle, timestamp)
}
fn event_occurred(&self, handle: SignalHandle, timestamp: u64) -> Result<bool, ExprDiagnostic> {
self.inner.event_occurred(handle, timestamp)
}
}
pub(crate) fn bind_waveform_event_expr(
waveform: SharedWaveform,
scope: Option<&str>,
source: &str,
) -> Result<(WaveformExprHost, BoundEventExpr), WavepeekError> {
let host = WaveformExprHost::from_shared(waveform);
let scoped = ScopedExprHost::new(&host, scope);
let ast =
parse_event_expr_ast(source).map_err(|diagnostic| expr_diagnostic(source, diagnostic))?;
let bound = bind_event_expr_ast(&ast, &scoped)
.map_err(|diagnostic| expr_diagnostic(source, diagnostic))?;
Ok((host, bound))
}
pub(crate) fn bind_waveform_logical_expr(
host: &WaveformExprHost,
scope: Option<&str>,
source: &str,
) -> Result<BoundLogicalExpr, WavepeekError> {
let scoped = ScopedExprHost::new(host, scope);
let ast =
parse_logical_expr_ast(source).map_err(|diagnostic| expr_diagnostic(source, diagnostic))?;
bind_logical_expr_ast(&ast, &scoped).map_err(|diagnostic| expr_diagnostic(source, diagnostic))
}
pub(crate) fn eval_bound_logical_truth(
source: &str,
expr: &BoundLogicalExpr,
host: &dyn ExpressionHost,
timestamp: u64,
) -> Result<bool, WavepeekError> {
let value = eval_logical_expr_at(expr, host, timestamp)
.map_err(|diagnostic| expr_diagnostic(source, diagnostic))?;
match value.payload {
ExprValuePayload::Integral { bits, .. } => Ok(bits.chars().any(|bit| bit == '1')),
ExprValuePayload::Real { value } => Ok(value != 0.0),
ExprValuePayload::String { .. } => Ok(false),
}
}
pub(crate) fn event_expr_matches(
source: &str,
expr: &BoundEventExpr,
host: &dyn ExpressionHost,
frame: &EventEvalFrame<'_>,
) -> Result<bool, WavepeekError> {
event_matches_at(expr, host, frame).map_err(|diagnostic| expr_diagnostic(source, diagnostic))
}
pub(crate) fn expr_diagnostic(source: &str, diagnostic: ExprDiagnostic) -> WavepeekError {
WavepeekError::Expr(diagnostic.render(source))
}
pub(crate) fn referenced_signal_handles(expr: &BoundLogicalExpr) -> Vec<SignalHandle> {
let mut handles = Vec::new();
let mut seen = HashSet::new();
collect_logical_handles(&expr.root, &mut seen, &mut handles);
handles
}
pub(crate) fn event_candidate_handles(expr: &BoundEventExpr) -> Vec<SignalHandle> {
let mut handles = Vec::new();
let mut seen = HashSet::new();
for term in &expr.terms {
let handle = match term.event {
BoundEventKind::AnyTracked => None,
BoundEventKind::Named(handle)
| BoundEventKind::Posedge(handle)
| BoundEventKind::Negedge(handle)
| BoundEventKind::Edge(handle) => Some(handle),
};
if let Some(handle) = handle
&& seen.insert(handle)
{
handles.push(handle);
}
}
handles
}
pub(crate) fn event_expr_contains_wildcard(expr: &BoundEventExpr) -> bool {
expr.terms
.iter()
.any(|term| matches!(term.event, BoundEventKind::AnyTracked))
}
pub(crate) fn event_expr_is_any_tracked_only(expr: &BoundEventExpr) -> bool {
!expr.terms.is_empty()
&& expr
.terms
.iter()
.all(|term| matches!(term.event, BoundEventKind::AnyTracked))
}
pub(crate) fn event_expr_is_edge_only(expr: &BoundEventExpr) -> bool {
!expr.terms.is_empty()
&& expr.terms.iter().all(|term| {
matches!(
term.event,
BoundEventKind::Posedge(_) | BoundEventKind::Negedge(_) | BoundEventKind::Edge(_)
)
})
}
pub(crate) fn candidate_sources_for_handles(
host: &WaveformExprHost,
handles: &[SignalHandle],
) -> Result<Vec<ExprResolvedSignal>, WavepeekError> {
let mut sources = Vec::with_capacity(handles.len());
let mut seen = HashSet::new();
for handle in handles {
let resolved = host.resolved_signal_for_handle(*handle)?;
if seen.insert(resolved.signal_ref) {
sources.push(resolved);
}
}
Ok(sources)
}
fn collect_logical_handles(
node: &BoundLogicalNode,
seen: &mut HashSet<SignalHandle>,
handles: &mut Vec<SignalHandle>,
) {
match &node.kind {
BoundLogicalKind::SignalRef { handle } | BoundLogicalKind::Triggered { handle } => {
if seen.insert(*handle) {
handles.push(*handle);
}
}
BoundLogicalKind::Parenthesized { expr }
| BoundLogicalKind::Cast { expr, .. }
| BoundLogicalKind::Unary { expr, .. }
| BoundLogicalKind::Replication { expr, .. } => {
collect_logical_handles(expr, seen, handles)
}
BoundLogicalKind::Selection { base, selection } => {
collect_logical_handles(base, seen, handles);
collect_selection_handles(selection, seen, handles);
}
BoundLogicalKind::Binary { left, right, .. } => {
collect_logical_handles(left, seen, handles);
collect_logical_handles(right, seen, handles);
}
BoundLogicalKind::Conditional {
condition,
when_true,
when_false,
} => {
collect_logical_handles(condition, seen, handles);
collect_logical_handles(when_true, seen, handles);
collect_logical_handles(when_false, seen, handles);
}
BoundLogicalKind::Inside { expr, set } => {
collect_logical_handles(expr, seen, handles);
for item in set {
match item {
BoundInsideItem::Expr(expr) => collect_logical_handles(expr, seen, handles),
BoundInsideItem::Range { low, high } => {
collect_logical_handles(low, seen, handles);
collect_logical_handles(high, seen, handles);
}
}
}
}
BoundLogicalKind::Concatenation { items } => {
for item in items {
collect_logical_handles(item, seen, handles);
}
}
BoundLogicalKind::IntegralLiteral { .. }
| BoundLogicalKind::RealLiteral { .. }
| BoundLogicalKind::StringLiteral { .. }
| BoundLogicalKind::EnumLabel { .. } => {}
}
}
fn collect_selection_handles(
selection: &BoundSelection,
seen: &mut HashSet<SignalHandle>,
handles: &mut Vec<SignalHandle>,
) {
match selection {
BoundSelection::Bit { index } => collect_logical_handles(index, seen, handles),
BoundSelection::IndexedUp { base, .. } | BoundSelection::IndexedDown { base, .. } => {
collect_logical_handles(base, seen, handles);
}
BoundSelection::Part { .. } => {}
}
}
fn unknown_signal_diagnostic(name: &str) -> ExprDiagnostic {
ExprDiagnostic {
layer: crate::expr::DiagnosticLayer::Semantic,
code: "HOST-UNKNOWN-SIGNAL",
message: format!("unknown signal '{name}'"),
primary_span: Span::new(0, 0),
notes: vec![],
}
}
#[cfg(test)]
mod tests {
use crate::expr::{
ExprDiagnostic, ExprStorage, ExprType, ExprTypeKind, ExpressionHost, IntegerLikeKind,
SampledValue, SignalHandle, Span,
};
use super::{
ScopedExprHost, event_candidate_handles, event_expr_contains_wildcard,
referenced_signal_handles,
};
#[derive(Default)]
struct StubHost;
impl ExpressionHost for StubHost {
fn resolve_signal(&self, name: &str) -> Result<SignalHandle, ExprDiagnostic> {
match name {
"top.clk" => Ok(SignalHandle(1)),
"top.sig" => Ok(SignalHandle(2)),
"top.ev" => Ok(SignalHandle(3)),
other => Err(ExprDiagnostic {
layer: crate::expr::DiagnosticLayer::Semantic,
code: "HOST-UNKNOWN-SIGNAL",
message: format!("unknown signal '{other}'"),
primary_span: Span::new(0, 0),
notes: vec![],
}),
}
}
fn signal_type(&self, handle: SignalHandle) -> Result<ExprType, ExprDiagnostic> {
let kind = match handle {
SignalHandle(3) => ExprTypeKind::Event,
_ => ExprTypeKind::IntegerLike(IntegerLikeKind::Int),
};
Ok(ExprType {
kind,
storage: ExprStorage::Scalar,
width: 1,
is_four_state: true,
is_signed: false,
enum_type_id: None,
enum_labels: None,
})
}
fn sample_value(
&self,
_handle: SignalHandle,
_timestamp: u64,
) -> Result<SampledValue, ExprDiagnostic> {
Ok(SampledValue::Integral {
bits: Some("1".to_string()),
label: None,
})
}
fn event_occurred(
&self,
_handle: SignalHandle,
_timestamp: u64,
) -> Result<bool, ExprDiagnostic> {
Ok(true)
}
}
#[test]
fn scoped_host_rejects_dotted_names_when_scope_is_active() {
let host = StubHost;
let scoped = ScopedExprHost::new(&host, Some("top"));
let error = scoped
.resolve_signal("top.clk")
.expect_err("dotted scoped token should fail");
assert_eq!(error.code, "HOST-UNKNOWN-SIGNAL");
assert_eq!(error.message, "unknown signal 'top.clk'");
}
#[test]
fn bound_handle_helpers_preserve_unique_signal_order() {
let host = StubHost;
let scoped = ScopedExprHost::new(&host, Some("top"));
let logical_ast = crate::expr::parse_logical_expr_ast("sig && ev.triggered() && sig")
.expect("logical expr should parse");
let bound_logical = crate::expr::bind_logical_expr_ast(&logical_ast, &scoped)
.expect("logical expr should bind");
assert_eq!(
referenced_signal_handles(&bound_logical),
vec![SignalHandle(2), SignalHandle(3)]
);
let event_ast =
crate::expr::parse_event_expr_ast("* or posedge clk").expect("event expr should parse");
let bound_event =
crate::expr::bind_event_expr_ast(&event_ast, &scoped).expect("event expr should bind");
assert!(event_expr_contains_wildcard(&bound_event));
assert_eq!(event_candidate_handles(&bound_event), vec![SignalHandle(1)]);
}
}