vj-sim 0.1.0

Netlist, scheduler and process model of the vj SystemVerilog simulator.
Documentation
//! Pattern matching (IEEE 1800-2023 12.6): what the analysis side needs.
//!
//! A pattern variable (`.n` in `tagged Valid .n`) is bound when its pattern
//! matches and read by the later conditions and the matched branch. slang
//! gives it no declaration statement and, for a `?:` pattern, no scope
//! either, so the declared-locals scans (`process::liveness`, a subroutine's
//! locals) cannot find it as a block member: [`pattern_vars`] finds them in
//! the patterns themselves.

use std::collections::HashSet;

use sv_lang::{SemNode, Symbol};
use sv_lang_kinds::{ExpressionKind, PatternKind, StatementKind};

use crate::symid::{SymbolId, SymbolUid};

/// Every pattern variable bound anywhere in the subtree of `root`
/// (`if (e matches p)`, `case (e) matches`, `e matches p ? a : b`), once
/// each, in source order. Variables are told apart by [`SymbolUid::uid`]; a
/// `?:` pattern's variable lives in no scope, so two of them with the same
/// name share a key (and a slot) -- codegen refuses the pair when their types
/// differ in width.
pub fn pattern_vars<'d>(root: SemNode<'d>) -> Vec<Symbol<'d>> {
    let mut out = Vec::new();
    let mut seen: HashSet<SymbolId> = HashSet::new();
    collect(root, &mut out, &mut seen);
    out
}

fn collect<'d>(root: SemNode<'d>, out: &mut Vec<Symbol<'d>>, seen: &mut HashSet<SymbolId>) {
    let mut stack = vec![root];
    while let Some(node) = stack.pop() {
        let mut kids = node.children();
        if let Some(p) = node.as_pattern()
            && p.kind() == PatternKind::Variable
            && let Some(v) = p.variable()
            && !v.name().is_empty()
            && seen.insert(v.uid())
        {
            out.push(v);
        }
        if let Some(s) = node.as_statement() {
            // Only these two statements hold patterns; every other kind's
            // children are already in `node.children()`.
            if s.kind() == StatementKind::Conditional {
                for c in s.conditions() {
                    kids.extend(c.pattern().map(|p| p.as_sem_node()));
                }
            } else if s.kind() == StatementKind::PatternCase {
                for item in s.pattern_case_items() {
                    kids.push(item.pattern().as_sem_node());
                    kids.push(item.stmt().as_sem_node());
                }
            }
        }
        if let Some(e) = node.as_expression()
            && e.kind() == ExpressionKind::ConditionalOp
        {
            for c in e.conditions() {
                kids.extend(c.pattern().map(|p| p.as_sem_node()));
            }
        }
        kids.reverse();
        stack.extend(kids);
    }
}

/// The case expression of a `case ... matches` statement. The bindings'
/// `Statement::expr` has no `PatternCase` arm, so it is the first expression
/// among the statement's semantic children (slang visits the case expression
/// before the items); the other expression children are exactly the items'
/// `&&&` filters, which is checked. `None` when `stmt` is not a `PatternCase`
/// or the children do not have that shape.
pub fn pattern_case_expr<'d>(stmt: sv_lang::Statement<'d>) -> Option<sv_lang::Expression<'d>> {
    if stmt.kind() != StatementKind::PatternCase {
        return None;
    }
    let exprs: Vec<_> = stmt
        .as_sem_node()
        .children()
        .into_iter()
        .filter_map(|c| c.as_expression())
        .collect();
    let filters = stmt
        .pattern_case_items()
        .iter()
        .filter(|i| i.filter().is_some())
        .count();
    (exprs.len() == 1 + filters).then(|| exprs[0])
}