use std::collections::BTreeMap;
use std::fmt::Write as _;
use crate::predicate_op::PredicateOp;
use plotnik_core::Colors;
use super::format::{LineBuilder, Symbol, format_effect, nav_symbol, width_for_count};
use super::ids::TypeId;
use super::instructions::StepId;
use super::module::{Instruction, Module};
use super::nav::Nav;
use super::node_type_ir::NodeTypeIR;
use super::type_meta::{TypeData, TypeKind};
use super::{Call, Match, Return, Trampoline};
pub fn dump(module: &Module, colors: Colors) -> String {
let mut out = String::new();
let ctx = DumpContext::new(module, colors);
dump_strings(&mut out, module, &ctx);
dump_regexes(&mut out, module, &ctx);
dump_types_defs(&mut out, module, &ctx);
dump_types_members(&mut out, module, &ctx);
dump_types_names(&mut out, module, &ctx);
dump_entrypoints(&mut out, module, &ctx);
dump_code(&mut out, module, &ctx);
out
}
struct DumpContext {
step_labels: BTreeMap<u16, String>,
node_type_names: BTreeMap<u16, String>,
node_field_names: BTreeMap<u16, String>,
all_strings: Vec<String>,
str_width: usize,
type_width: usize,
member_width: usize,
name_width: usize,
step_width: usize,
colors: Colors,
}
impl DumpContext {
fn new(module: &Module, colors: Colors) -> Self {
let header = module.header();
let strings = module.strings();
let entrypoints = module.entrypoints();
let node_types = module.node_types();
let node_fields = module.node_fields();
let mut step_labels = BTreeMap::new();
step_labels.insert(0, "_ObjWrap".to_string());
for i in 0..entrypoints.len() {
let ep = entrypoints.get(i);
let name = strings.get(ep.name()).to_string();
step_labels.insert(ep.target(), name);
}
let mut node_type_names = BTreeMap::new();
for i in 0..node_types.len() {
let t = node_types.get(i);
node_type_names.insert(t.id, strings.get(t.name).to_string());
}
let mut node_field_names = BTreeMap::new();
for i in 0..node_fields.len() {
let f = node_fields.get(i);
node_field_names.insert(f.id, strings.get(f.name).to_string());
}
let str_count = header.str_table_count as usize;
let all_strings: Vec<String> = (0..str_count)
.map(|i| strings.get_by_index(i).to_string())
.collect();
let types = module.types();
let type_count = 3 + types.defs_count(); let str_width = width_for_count(str_count);
let type_width = width_for_count(type_count);
let member_width = width_for_count(types.members_count());
let name_width = width_for_count(types.names_count());
let step_width = width_for_count(header.transitions_count as usize);
Self {
step_labels,
node_type_names,
node_field_names,
all_strings,
str_width,
type_width,
member_width,
name_width,
step_width,
colors,
}
}
fn label_for(&self, step: StepId) -> Option<&str> {
self.step_labels.get(&step.get()).map(|s| s.as_str())
}
fn node_type_name(&self, id: u16) -> Option<&str> {
self.node_type_names.get(&id).map(|s| s.as_str())
}
fn node_field_name(&self, id: u16) -> Option<&str> {
self.node_field_names.get(&id).map(|s| s.as_str())
}
}
fn dump_strings(out: &mut String, module: &Module, ctx: &DumpContext) {
let c = &ctx.colors;
let strings = module.strings();
let count = module.header().str_table_count as usize;
let w = ctx.str_width;
writeln!(out, "{}[strings]{}", c.blue, c.reset).unwrap();
for i in 0..count {
let s = strings.get_by_index(i);
writeln!(out, "S{i:0w$} {}{s:?}{}", c.green, c.reset).unwrap();
}
out.push('\n');
}
fn dump_regexes(out: &mut String, module: &Module, ctx: &DumpContext) {
let count = module.header().regex_table_count as usize;
if count <= 1 {
return;
}
let c = &ctx.colors;
let regexes = module.regexes();
let w = width_for_count(count);
writeln!(out, "{}[regex]{}", c.blue, c.reset).unwrap();
for i in 1..count {
let string_id = regexes.get_string_id(i);
let pattern = &ctx.all_strings[string_id.get() as usize];
writeln!(out, "R{i:0w$} {}/{pattern}/{}", c.green, c.reset).unwrap();
}
out.push('\n');
}
fn dump_types_defs(out: &mut String, module: &Module, ctx: &DumpContext) {
let c = &ctx.colors;
let types = module.types();
let strings = module.strings();
let tw = ctx.type_width;
let mw = ctx.member_width;
writeln!(out, "{}[type_defs]{}", c.blue, c.reset).unwrap();
for i in 0..types.defs_count() {
let def = types.get_def(i);
let (formatted, comment) = match def.classify() {
TypeData::Primitive(kind) => {
let name = match kind {
TypeKind::Void => "<Void>",
TypeKind::Node => "<Node>",
TypeKind::String => "<String>",
_ => unreachable!(),
};
(name.to_string(), String::new())
}
TypeData::Wrapper { kind, inner } => {
let formatted = match kind {
TypeKind::Optional => format!("Optional(T{:0tw$})", inner.0),
TypeKind::ArrayZeroOrMore => format!("ArrayStar(T{:0tw$})", inner.0),
TypeKind::ArrayOneOrMore => format!("ArrayPlus(T{:0tw$})", inner.0),
TypeKind::Alias => format!("Alias(T{:0tw$})", inner.0),
_ => unreachable!(),
};
let comment = match kind {
TypeKind::Optional => {
let inner_name = format_type_name(inner, module, ctx);
format!("{} ; {}?{}", c.dim, inner_name, c.reset)
}
TypeKind::ArrayZeroOrMore => {
let inner_name = format_type_name(inner, module, ctx);
format!("{} ; {}*{}", c.dim, inner_name, c.reset)
}
TypeKind::ArrayOneOrMore => {
let inner_name = format_type_name(inner, module, ctx);
format!("{} ; {}+{}", c.dim, inner_name, c.reset)
}
TypeKind::Alias => String::new(),
_ => unreachable!(),
};
(formatted, comment)
}
TypeData::Composite {
kind,
member_start,
member_count,
} => {
let formatted = match kind {
TypeKind::Struct => {
format!("Struct M{:0mw$}:{}", member_start, member_count)
}
TypeKind::Enum => format!("Enum M{:0mw$}:{}", member_start, member_count),
_ => unreachable!(),
};
let comment = match kind {
TypeKind::Struct => {
let fields: Vec<_> = types
.members_of(&def)
.map(|m| strings.get(m.name).to_string())
.collect();
format!("{} ; {{ {} }}{}", c.dim, fields.join(", "), c.reset)
}
TypeKind::Enum => {
let variants: Vec<_> = types
.members_of(&def)
.map(|m| strings.get(m.name).to_string())
.collect();
format!("{} ; {}{}", c.dim, variants.join(" | "), c.reset)
}
_ => unreachable!(),
};
(formatted, comment)
}
};
writeln!(out, "T{i:0tw$} = {formatted}{comment}").unwrap();
}
out.push('\n');
}
fn dump_types_members(out: &mut String, module: &Module, ctx: &DumpContext) {
let c = &ctx.colors;
let types = module.types();
let strings = module.strings();
let mw = ctx.member_width;
let sw = ctx.str_width;
let tw = ctx.type_width;
writeln!(out, "{}[type_members]{}", c.blue, c.reset).unwrap();
for i in 0..types.members_count() {
let member = types.get_member(i);
let name = strings.get(member.name);
let type_name = format_type_name(member.type_id, module, ctx);
writeln!(
out,
"M{i:0mw$}: S{:0sw$} → T{:0tw$} {}; {name}: {type_name}{}",
member.name.0, member.type_id.0, c.dim, c.reset
)
.unwrap();
}
out.push('\n');
}
fn dump_types_names(out: &mut String, module: &Module, ctx: &DumpContext) {
let c = &ctx.colors;
let types = module.types();
let strings = module.strings();
let nw = ctx.name_width;
let sw = ctx.str_width;
let tw = ctx.type_width;
writeln!(out, "{}[type_names]{}", c.blue, c.reset).unwrap();
for i in 0..types.names_count() {
let entry = types.get_name(i);
let name = strings.get(entry.name);
writeln!(
out,
"N{i:0nw$}: S{:0sw$} → T{:0tw$} {}; {}{name}{}",
entry.name.0, entry.type_id.0, c.dim, c.blue, c.reset
)
.unwrap();
}
out.push('\n');
}
fn format_type_name(type_id: TypeId, module: &Module, ctx: &DumpContext) -> String {
let types = module.types();
let strings = module.strings();
if let Some(def) = types.get(type_id)
&& let TypeData::Primitive(kind) = def.classify()
&& let Some(name) = kind.primitive_name()
{
return format!("<{}>", name);
}
for i in 0..types.names_count() {
let entry = types.get_name(i);
if entry.type_id == type_id {
return strings.get(entry.name).to_string();
}
}
let tw = ctx.type_width;
format!("T{:0tw$}", type_id.0)
}
fn dump_entrypoints(out: &mut String, module: &Module, ctx: &DumpContext) {
let c = &ctx.colors;
let strings = module.strings();
let entrypoints = module.entrypoints();
let stw = ctx.step_width;
let tw = ctx.type_width;
writeln!(out, "{}[entrypoints]{}", c.blue, c.reset).unwrap();
let mut entries: Vec<_> = (0..entrypoints.len())
.map(|i| {
let ep = entrypoints.get(i);
let name = strings.get(ep.name());
(name, ep.target(), ep.result_type().0)
})
.collect();
entries.sort_by_key(|(name, _, _)| *name);
let max_len = entries.iter().map(|(n, _, _)| n.len()).max().unwrap_or(0);
for (name, target, type_id) in entries {
writeln!(
out,
"{}{name:width$}{} = {:0stw$} :: T{type_id:0tw$}",
c.blue,
c.reset,
target,
width = max_len
)
.unwrap();
}
out.push('\n');
}
fn is_padding(instr: &Instruction) -> bool {
match instr {
Instruction::Match(m) => {
m.is_match8()
&& m.nav == Nav::Epsilon
&& matches!(m.node_type, NodeTypeIR::Any)
&& m.node_field.is_none()
&& m.is_terminal()
}
_ => false,
}
}
fn format_padding_step(step: u16, step_width: usize) -> String {
LineBuilder::new(step_width).instruction_prefix(step, Symbol::PADDING)
}
fn dump_code(out: &mut String, module: &Module, ctx: &DumpContext) {
let c = &ctx.colors;
let header = module.header();
let transitions_count = header.transitions_count as usize;
let step_width = ctx.step_width;
writeln!(out, "{}[transitions]{}", c.blue, c.reset).unwrap();
let mut step = 0u16;
let mut first_label = true;
while (step as usize) < transitions_count {
if let Some(label) = ctx.step_labels.get(&step) {
if first_label {
writeln!(out, "{}{label}{}:", c.blue, c.reset).unwrap();
first_label = false;
} else {
writeln!(out, "\n{}{label}{}:", c.blue, c.reset).unwrap();
}
}
let instr = module.decode_step(step);
if is_padding(&instr) {
writeln!(out, "{}", format_padding_step(step, step_width)).unwrap();
step += 1;
continue;
}
let line = format_instruction(step, &instr, module, ctx, step_width);
out.push_str(&line);
out.push('\n');
let size = instruction_step_count(&instr);
step += size;
}
}
fn instruction_step_count(instr: &Instruction) -> u16 {
match instr {
Instruction::Match(m) => {
let pre = m.pre_effects().count();
let neg = m.neg_fields().count();
let post = m.post_effects().count();
let succ = m.succ_count();
let pred = if m.has_predicate() { 2 } else { 0 };
let slots = pre + neg + post + pred + succ;
if pre == 0 && neg == 0 && post == 0 && pred == 0 && succ <= 1 {
1 } else if slots <= 4 {
2 } else if slots <= 8 {
3 } else if slots <= 12 {
4 } else if slots <= 20 {
6 } else {
8 }
}
Instruction::Call(_) | Instruction::Return(_) | Instruction::Trampoline(_) => 1,
}
}
fn format_instruction(
step: u16,
instr: &Instruction,
module: &Module,
ctx: &DumpContext,
step_width: usize,
) -> String {
match instr {
Instruction::Match(m) => format_match(step, m, module, ctx, step_width),
Instruction::Call(c) => format_call(step, c, module, ctx, step_width),
Instruction::Return(r) => format_return(step, r, module, ctx, step_width),
Instruction::Trampoline(t) => format_trampoline(step, t, ctx, step_width),
}
}
fn format_match(
step: u16,
m: &Match,
module: &Module,
ctx: &DumpContext,
step_width: usize,
) -> String {
let builder = LineBuilder::new(step_width);
let symbol = nav_symbol(m.nav);
let prefix = format!(" {:0sw$} {} ", step, symbol.format(), sw = step_width);
let content = format_match_content(m, module, ctx);
let successors = format_match_successors(m, ctx, step_width);
let base = format!("{prefix}{content}");
builder.pad_successors(base, &successors)
}
fn format_match_content(m: &Match, module: &Module, ctx: &DumpContext) -> String {
let mut parts = Vec::new();
let pre: Vec<_> = m.pre_effects().map(|e| format_effect(&e)).collect();
if !pre.is_empty() {
parts.push(format!("[{}]", pre.join(" ")));
}
if !m.is_epsilon() {
for field_id in m.neg_fields() {
let name = ctx
.node_field_name(field_id)
.map(String::from)
.unwrap_or_else(|| format!("field#{field_id}"));
parts.push(format!("-{name}"));
}
let node_part = format_node_pattern(m, ctx);
if !node_part.is_empty() {
parts.push(node_part);
}
if let Some((op, is_regex, value_ref)) = m.predicate() {
let op = PredicateOp::from_byte(op);
let value = if is_regex {
let string_id = module.regexes().get_string_id(value_ref as usize);
let pattern = &ctx.all_strings[string_id.get() as usize];
format!("/{}/", pattern)
} else {
let s = &ctx.all_strings[value_ref as usize];
format!("{:?}", s)
};
parts.push(format!("{} {}", op.as_str(), value));
}
}
let post: Vec<_> = m.post_effects().map(|e| format_effect(&e)).collect();
if !post.is_empty() {
parts.push(format!("[{}]", post.join(" ")));
}
parts.join(" ")
}
fn format_node_pattern(m: &Match, ctx: &DumpContext) -> String {
let mut result = String::new();
if let Some(field_id) = m.node_field {
let name = ctx
.node_field_name(field_id.get())
.map(String::from)
.unwrap_or_else(|| format!("field#{}", field_id.get()));
result.push_str(&name);
result.push_str(": ");
}
match m.node_type {
NodeTypeIR::Any => {
result.push('_');
}
NodeTypeIR::Named(None) => {
result.push_str("(_)");
}
NodeTypeIR::Named(Some(type_id)) => {
let name = ctx
.node_type_name(type_id.get())
.map(String::from)
.unwrap_or_else(|| format!("node#{}", type_id.get()));
result.push('(');
result.push_str(&name);
result.push(')');
}
NodeTypeIR::Anonymous(None) => {
result.push_str("\"_\"");
}
NodeTypeIR::Anonymous(Some(type_id)) => {
let name = ctx
.node_type_name(type_id.get())
.map(String::from)
.unwrap_or_else(|| format!("anon#{}", type_id.get()));
result.push('"');
result.push_str(&name);
result.push('"');
}
}
result
}
fn format_match_successors(m: &Match, ctx: &DumpContext, step_width: usize) -> String {
if m.is_terminal() {
"◼".to_string()
} else {
m.successors()
.map(|s| format_step(s, ctx, step_width))
.collect::<Vec<_>>()
.join(", ")
}
}
fn format_call(
step: u16,
call: &Call,
_module: &Module,
ctx: &DumpContext,
step_width: usize,
) -> String {
let c = &ctx.colors;
let builder = LineBuilder::new(step_width);
let symbol = nav_symbol(call.nav());
let prefix = format!(" {:0sw$} {} ", step, symbol.format(), sw = step_width);
let field_part = if let Some(field_id) = call.node_field {
let name = ctx
.node_field_name(field_id.get())
.map(String::from)
.unwrap_or_else(|| format!("field#{}", field_id.get()));
format!("{name}: ")
} else {
String::new()
};
let target_name = ctx
.label_for(call.target)
.map(String::from)
.unwrap_or_else(|| format!("@{:0w$}", call.target.0, w = step_width));
let content = format!("{field_part}({}{}{})", c.blue, target_name, c.reset);
let successors = format!(
"{:0w$} : {:0w$}",
call.target.get(),
call.next.get(),
w = step_width
);
let base = format!("{prefix}{content}");
builder.pad_successors(base, &successors)
}
fn format_return(
step: u16,
_r: &Return,
_module: &Module,
_ctx: &DumpContext,
step_width: usize,
) -> String {
let builder = LineBuilder::new(step_width);
let prefix = format!(
" {:0sw$} {} ",
step,
Symbol::EMPTY.format(),
sw = step_width
);
builder.pad_successors(prefix, "▶")
}
fn format_trampoline(step: u16, t: &Trampoline, _ctx: &DumpContext, step_width: usize) -> String {
let builder = LineBuilder::new(step_width);
let prefix = format!(
" {:0sw$} {} ",
step,
Symbol::EMPTY.format(),
sw = step_width
);
let content = "Trampoline";
let successors = format!("{:0w$}", t.next.get(), w = step_width);
let base = format!("{prefix}{content}");
builder.pad_successors(base, &successors)
}
fn format_step(step: StepId, ctx: &DumpContext, step_width: usize) -> String {
let c = &ctx.colors;
if let Some(label) = ctx.label_for(step) {
format!("▶({}{}{})", c.blue, label, c.reset)
} else {
format!("{:0w$}", step.get(), w = step_width)
}
}