use std::collections::BTreeSet;
use indexmap::IndexMap;
use plotnik_bytecode::Nav;
use plotnik_core::Symbol;
use crate::analyze::type_check::DefId;
use crate::bytecode::{InstructionIR, Label, MatchIR, MemberRef, NodeTypeIR, PredicateValueIR};
use crate::emit::StringTableBuilder;
use super::compiler::CompileCtx;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum SemanticOp {
Nav(Nav),
MatchNamed(Option<String>),
MatchAnon(Option<String>),
MatchAny,
Field(String),
NegField(String),
Predicate(u8, String),
Effect(u8, Option<String>),
Call(String),
Return,
CycleMarker(usize),
}
pub type Path = Vec<SemanticOp>;
pub type Fingerprint = BTreeSet<Path>;
#[cfg(debug_assertions)]
fn collect_ops_from_match(instr: &MatchIR, ctx: &CompileCtx) -> Vec<SemanticOp> {
let mut ops = Vec::new();
for e in &instr.pre_effects {
let name = resolve_member_name(&e.member_ref, ctx.interner);
ops.push(SemanticOp::Effect(e.opcode as u8, name));
}
if instr.nav != Nav::Epsilon {
ops.push(SemanticOp::Nav(instr.nav));
match &instr.node_type {
NodeTypeIR::Any => ops.push(SemanticOp::MatchAny),
NodeTypeIR::Named(id) => {
let name = id.and_then(|i| resolve_node_type_name(i, ctx.node_types, ctx.interner));
ops.push(SemanticOp::MatchNamed(name));
}
NodeTypeIR::Anonymous(id) => {
let name = id.and_then(|i| resolve_node_type_name(i, ctx.node_types, ctx.interner));
ops.push(SemanticOp::MatchAnon(name));
}
}
}
if let Some(f) = instr.node_field {
let name = resolve_field_name(Some(f), ctx.node_fields, ctx.interner);
ops.push(SemanticOp::Field(
name.unwrap_or_else(|| format!("field#{}", f)),
));
}
for &f in &instr.neg_fields {
let name = resolve_field_name(std::num::NonZeroU16::new(f), ctx.node_fields, ctx.interner);
ops.push(SemanticOp::NegField(
name.unwrap_or_else(|| format!("field#{}", f)),
));
}
if let Some(p) = &instr.predicate {
let value = resolve_predicate_value(&p.value, &ctx.strings.borrow());
ops.push(SemanticOp::Predicate(p.op.to_byte(), value));
}
for e in &instr.post_effects {
let name = resolve_member_name(&e.member_ref, ctx.interner);
ops.push(SemanticOp::Effect(e.opcode as u8, name));
}
ops
}
#[cfg(debug_assertions)]
fn resolve_member_name(
member_ref: &Option<MemberRef>,
interner: &plotnik_core::Interner,
) -> Option<String> {
let Some(MemberRef::Deferred { field_name, .. }) = member_ref else {
return None;
};
interner.try_resolve(*field_name).map(|s| s.to_string())
}
#[cfg(debug_assertions)]
fn resolve_node_type_name(
id: std::num::NonZeroU16,
node_types: Option<&indexmap::IndexMap<Symbol, plotnik_core::NodeTypeId>>,
interner: &plotnik_core::Interner,
) -> Option<String> {
let types = node_types?;
for (sym, type_id) in types {
if type_id.get() == id.get() {
return interner.try_resolve(*sym).map(|s| s.to_string());
}
}
None
}
#[cfg(debug_assertions)]
fn resolve_field_name(
id: Option<std::num::NonZeroU16>,
node_fields: Option<&indexmap::IndexMap<Symbol, plotnik_core::NodeFieldId>>,
interner: &plotnik_core::Interner,
) -> Option<String> {
let id = id?;
let fields = node_fields?;
for (sym, field_id) in fields {
if field_id.get() == id.get() {
return interner.try_resolve(*sym).map(|s| s.to_string());
}
}
None
}
#[cfg(debug_assertions)]
fn resolve_predicate_value(value: &PredicateValueIR, strings: &StringTableBuilder) -> String {
match value {
PredicateValueIR::String(id) => strings.get_str(*id).to_string(),
PredicateValueIR::Regex(id) => format!("/{}/", strings.get_str(*id)),
}
}
#[cfg(debug_assertions)]
fn build_instruction_map(
instructions: &[InstructionIR],
) -> std::collections::HashMap<Label, &InstructionIR> {
instructions.iter().map(|i| (i.label(), i)).collect()
}
#[cfg(debug_assertions)]
pub fn fingerprint_from_ir(
instructions: &[InstructionIR],
entry: Label,
def_entries: &IndexMap<DefId, Label>,
ctx: &CompileCtx,
) -> Fingerprint {
let instr_map = build_instruction_map(instructions);
let label_to_def: std::collections::HashMap<Label, DefId> = def_entries
.iter()
.map(|(&def_id, &label)| (label, def_id))
.collect();
let mut fingerprint = Fingerprint::new();
let mut stack: Vec<(Label, Path, std::collections::HashMap<Label, usize>)> =
vec![(entry, Vec::new(), std::collections::HashMap::new())];
while let Some((current, mut path, mut label_positions)) = stack.pop() {
if let Some(&pos) = label_positions.get(¤t) {
path.push(SemanticOp::CycleMarker(pos));
fingerprint.insert(path);
continue;
}
let Some(instr) = instr_map.get(¤t) else {
fingerprint.insert(path);
continue;
};
if let InstructionIR::Match(m) = instr
&& m.is_epsilon()
&& m.pre_effects.is_empty()
&& m.post_effects.is_empty()
{
for &succ in &m.successors {
stack.push((succ, path.clone(), label_positions.clone()));
}
continue;
}
let current_pos = path.len();
label_positions.insert(current, current_pos);
match instr {
InstructionIR::Match(m) => {
let ops = collect_ops_from_match(m, ctx);
path.extend(ops);
if m.successors.is_empty() {
fingerprint.insert(path);
} else {
for &succ in &m.successors {
stack.push((succ, path.clone(), label_positions.clone()));
}
}
}
InstructionIR::Call(c) => {
let call_name = label_to_def
.get(&c.target)
.map(|def_id| format!("def#{}", def_id.as_u32()))
.unwrap_or_else(|| format!("label#{}", c.target.0));
path.push(SemanticOp::Call(call_name));
stack.push((c.next, path, label_positions));
}
InstructionIR::Return(_) => {
path.push(SemanticOp::Return);
fingerprint.insert(path);
}
InstructionIR::Trampoline(t) => {
stack.push((t.next, path, label_positions));
}
}
}
fingerprint
}
#[cfg(debug_assertions)]
pub fn debug_verify_ir_fingerprint(
instructions: &[InstructionIR],
entry: Label,
def_entries: &IndexMap<DefId, Label>,
def_name: &str,
ctx: &CompileCtx,
) {
let fingerprint = fingerprint_from_ir(instructions, entry, def_entries, ctx);
if std::env::var("PLOTNIK_DEBUG_FINGERPRINT").is_ok() {
eprintln!("=== Fingerprint for {} ===", def_name);
for (i, path) in fingerprint.iter().enumerate() {
eprintln!("Path {}: {:?}", i, path);
}
eprintln!("=== End fingerprint ===\n");
}
}
#[cfg(not(debug_assertions))]
#[inline(always)]
pub fn debug_verify_ir_fingerprint(
_instructions: &[InstructionIR],
_entry: Label,
_def_entries: &IndexMap<DefId, Label>,
_def_name: &str,
_ctx: &CompileCtx,
) {
}