use alloc::borrow::ToOwned;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use brink_format::{DefinitionId, Value};
use crate::program::Program;
use crate::value_ops;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DebugPosition {
pub container_idx: u32,
pub offset: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DebugSourceLocation {
pub file: Option<String>,
pub range_start: u32,
pub range_len: u32,
}
pub struct DebugSnapshot {
pub status: &'static str,
pub current_location: Option<String>,
pub position: Option<DebugPosition>,
pub turn_index: u32,
pub globals: Vec<DebugGlobal>,
pub call_stack: Vec<DebugFrame>,
pub visit_counts: Vec<DebugVisit>,
pub visit_ids: Vec<DebugVisitId>,
pub pending_choices: Vec<DebugChoice>,
pub rng: DebugRng,
}
pub struct DebugGlobal {
pub name: String,
pub value: String,
}
pub struct DebugFrame {
pub kind: &'static str,
pub location: Option<String>,
pub position: Option<DebugPosition>,
pub temps: usize,
pub locals: Option<Vec<DebugLocal>>,
}
#[derive(Debug, Clone)]
pub struct DebugLocal {
pub slot: u16,
pub name: String,
pub value: DebugValue,
pub synthetic: bool,
}
#[derive(Debug, Clone)]
pub enum DebugValue {
Int(i32),
Float(f32),
Bool(bool),
Str(String),
Null,
List(Vec<String>),
DivertTarget(Option<String>),
Struct {
name: Option<String>,
fields: Vec<(String, DebugValue)>,
},
Handle {
kind: String,
id: u64,
},
Other(String),
}
pub struct DebugVisit {
pub path: String,
pub count: u32,
}
pub struct DebugVisitId {
pub def_id: String,
pub count: u32,
}
pub struct DebugChoice {
pub text: String,
pub sticky: bool,
pub source: Option<brink_format::SourceLocation>,
pub target: Option<String>,
pub def_id: String,
pub index: usize,
}
pub struct DebugRng {
pub seed: i32,
pub previous: i32,
}
pub(crate) struct NameResolver<'p> {
program: &'p Program,
}
impl<'p> NameResolver<'p> {
pub(crate) fn new(program: &'p Program) -> Self {
Self { program }
}
pub(crate) fn container_path(&self, idx: u32) -> Option<&str> {
self.program.container_path(idx)
}
pub(crate) fn def_path(&self, id: DefinitionId) -> Option<&str> {
let (idx, _) = self.program.resolve_target(id)?;
self.container_path(idx)
}
pub(crate) fn debug_value(&self, value: &Value) -> DebugValue {
match value {
Value::Int(i) => DebugValue::Int(*i),
Value::Float(f) => DebugValue::Float(*f),
Value::Bool(b) => DebugValue::Bool(*b),
Value::String(s) => DebugValue::Str(s.to_string()),
Value::Null => DebugValue::Null,
Value::List(list) => DebugValue::List(
list.items
.iter()
.filter_map(|id| self.program.list_item_name(*id))
.map(str::to_owned)
.collect(),
),
Value::DivertTarget(id) => {
DebugValue::DivertTarget(self.def_path(*id).map(str::to_owned))
}
Value::Record { shape, fields } => {
let shape_entry = self.program.struct_shape(*shape);
let name = shape_entry
.and_then(|s| self.program.name_checked(s.name))
.map(str::to_owned);
let field_names: Vec<&str> = shape_entry.map_or_else(Vec::new, |s| {
s.fields
.iter()
.map(|&n| self.program.name_checked(n).unwrap_or("?"))
.collect()
});
let fields = fields
.iter()
.enumerate()
.map(|(i, v)| {
let field_name = field_names
.get(i)
.map_or_else(|| format!("_{i}"), |n| (*n).to_owned());
(field_name, self.debug_value(v))
})
.collect();
DebugValue::Struct { name, fields }
}
Value::Handle { kind, id } => DebugValue::Handle {
kind: self.program.name_checked(*kind).unwrap_or("?").to_owned(),
id: *id,
},
other => DebugValue::Other(self.format_value(other)),
}
}
pub(crate) fn format_value(&self, value: &Value) -> String {
match value {
Value::Int(i) => i.to_string(),
Value::Float(f) => f.to_string(),
Value::Bool(b) => b.to_string(),
Value::String(s) => format!("\"{s}\""),
Value::Null => "null".to_owned(),
Value::List(list) => {
let members: Vec<&str> = list
.items
.iter()
.filter_map(|id| self.program.list_item_name(*id))
.collect();
format!("({})", members.join(", "))
}
Value::DivertTarget(id) => match self.def_path(*id) {
Some(p) => format!("-> {p}"),
None => "-> ?".to_owned(),
},
Value::VariablePointer(id) => match self.program.global_var_name(*id) {
Some(n) => format!("ref {n}"),
None => "ref ?".to_owned(),
},
Value::TempPointer { slot, frame_depth } => {
format!("temp[{slot}]@{frame_depth}")
}
Value::FragmentRef(idx) => format!("<fragment {idx}>"),
Value::Array(items) => {
let parts: Vec<String> = items.iter().map(|v| self.format_value(v)).collect();
format!("[{}]", parts.join(", "))
}
Value::Map(map) => {
let parts: Vec<String> = map
.iter()
.map(|(k, v)| format!("{}: {}", format_map_key(k), self.format_value(v)))
.collect();
format!("{{{}}}", parts.join(", "))
}
Value::Weighted(w) => {
let parts: Vec<String> = w
.entries
.iter()
.map(|(weight, v)| format!("{weight}: {}", self.format_value(v)))
.collect();
format!("Weighted {{ {} }}", parts.join(", "))
}
Value::Record { shape, fields } => {
let parts: Vec<String> = fields.iter().map(|v| self.format_value(v)).collect();
format!("Record#{}{{{}}}", shape.0, parts.join(", "))
}
Value::FnRef(target) => match self.def_path(*target) {
Some(p) => format!("fn {p}"),
None => "fn ?".to_owned(),
},
Value::Closure(c) => {
let name = self.def_path(c.target).unwrap_or("?");
let parts: Vec<String> = c
.env
.iter()
.map(|e| {
let mode = if e.is_ref { "ref" } else { "val" };
format!("{mode} {}", self.format_value(&e.payload))
})
.collect();
format!("fn {name}({})", parts.join(", "))
}
Value::Handle { kind, id } => {
let kind_name = self.program.name_checked(*kind).unwrap_or("?");
format!("handle {kind_name}#{id}")
}
Value::Projection(_)
| Value::OptionVal(_)
| Value::Range { .. }
| Value::Vec2(_)
| Value::Vec3(_)
| Value::Vec4(_)
| Value::Quat(_)
| Value::Mat2(_)
| Value::Mat3(_)
| Value::Mat4(_) => value_ops::stringify(value, self.program),
}
}
}
fn format_map_key(key: &brink_format::MapKey) -> String {
match key {
brink_format::MapKey::Int(n) => n.to_string(),
brink_format::MapKey::Str(s) => format!("\"{s}\""),
brink_format::MapKey::Bool(b) => b.to_string(),
}
}