use core::fmt;
use core::hash::Hash;
use alloc::vec::Vec;
use vize_l0::{FxHashMap, String, cstr};
use crate::dump::Error as DumpError;
use crate::dump::value::DumpValue;
pub struct PagePrinter<'w, W: fmt::Write> {
w: &'w mut W,
page: &'static str,
}
impl<'w, W: fmt::Write> PagePrinter<'w, W> {
pub fn new(w: &'w mut W, page: &'static str) -> Self {
Self { w, page }
}
pub fn open(&mut self) -> fmt::Result {
writeln!(self.w, "[{}]", self.page)
}
pub fn scalar<T: DumpValue>(&mut self, name: &str, value: &T) -> fmt::Result {
write!(self.w, "{name}=")?;
value.print_value(self.w)?;
self.w.write_char('\n')
}
pub fn close_header(&mut self) -> fmt::Result {
self.w.write_char('\n')
}
pub fn list<T: DumpValue>(&mut self, name: &str, items: &[T]) -> fmt::Result {
if items.is_empty() {
return Ok(());
}
writeln!(self.w, "[{}.{name}]", self.page)?;
for item in items {
item.print_value(self.w)?;
self.w.write_char('\n')?;
}
self.w.write_char('\n')
}
pub fn map<K: DumpValue, V: DumpValue>(
&mut self,
name: &str,
map: &FxHashMap<K, V>,
) -> fmt::Result {
if map.is_empty() {
return Ok(());
}
let mut entries: Vec<(String, &V)> = Vec::with_capacity(map.len());
for (key, value) in map {
let mut printed = String::default();
key.print_value(&mut printed)?;
entries.push((printed, value));
}
entries.sort_unstable_by(|a, b| a.0.as_str().cmp(b.0.as_str()));
writeln!(self.w, "[{}.{name}]", self.page)?;
for (key, value) in entries {
self.w.write_str(key.as_str())?;
self.w.write_char('=')?;
value.print_value(self.w)?;
self.w.write_char('\n')?;
}
self.w.write_char('\n')
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LineEvent<'a> {
Skip,
Field,
Section(&'a str),
Entry(usize),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Cursor {
BeforeHeader,
Header,
Section(usize),
}
pub struct ParseState {
page: &'static str,
cursor: Cursor,
seen: u64,
}
impl ParseState {
#[must_use]
pub fn new(page: &'static str) -> Self {
Self {
page,
cursor: Cursor::BeforeHeader,
seen: 0,
}
}
pub fn classify<'a>(
&mut self,
line: &'a str,
line_no: usize,
) -> Result<LineEvent<'a>, DumpError> {
if let Some(name) = line
.strip_prefix('[')
.and_then(|rest| rest.strip_suffix(']'))
{
if self.cursor == Cursor::BeforeHeader {
if name == self.page {
self.cursor = Cursor::Header;
return Ok(LineEvent::Skip);
}
return Err(DumpError::new(
line_no,
cstr!("first section must be [{}]", self.page),
));
}
if name == self.page {
return Err(DumpError::new(
line_no,
cstr!("duplicate section [{}]", self.page),
));
}
if let Some(sub) = name
.strip_prefix(self.page)
.and_then(|rest| rest.strip_prefix('.'))
{
return Ok(LineEvent::Section(sub));
}
return Err(DumpError::new(line_no, cstr!("unknown section [{name}]")));
}
if line.is_empty() {
return Ok(LineEvent::Skip);
}
match self.cursor {
Cursor::BeforeHeader => Err(DumpError::new(
line_no,
cstr!("content before the [{}] header", self.page),
)),
Cursor::Header => Ok(LineEvent::Field),
Cursor::Section(index) => Ok(LineEvent::Entry(index)),
}
}
pub fn enter_section(
&mut self,
index: usize,
name: &str,
line_no: usize,
) -> Result<(), DumpError> {
let bit = 1u64 << index;
if self.seen & bit != 0 {
return Err(DumpError::new(
line_no,
cstr!("duplicate section [{}.{name}]", self.page),
));
}
self.seen |= bit;
self.cursor = Cursor::Section(index);
Ok(())
}
#[must_use]
pub fn unknown_section(&self, name: &str, line_no: usize) -> DumpError {
DumpError::new(line_no, cstr!("unknown section [{}.{name}]", self.page))
}
pub fn require_header(&self) -> Result<(), DumpError> {
if self.cursor == Cursor::BeforeHeader {
return Err(DumpError::new(0, cstr!("missing [{}] header", self.page)));
}
Ok(())
}
}
pub fn split_field(line: &str, line_no: usize) -> Result<(&str, &str), DumpError> {
line.split_once('=')
.ok_or_else(|| DumpError::new(line_no, cstr!("field line is missing `=`")))
}
pub fn set_scalar<T: DumpValue>(
slot: &mut Option<T>,
name: &str,
value: &str,
line_no: usize,
) -> Result<(), DumpError> {
if slot.is_some() {
return Err(DumpError::new(line_no, cstr!("duplicate field `{name}`")));
}
*slot = Some(T::parse_value(value, line_no)?);
Ok(())
}
#[must_use]
pub fn unknown_field(name: &str, line_no: usize) -> DumpError {
DumpError::new(line_no, cstr!("unknown field `{name}`"))
}
pub fn require_scalar<T>(slot: Option<T>, name: &str) -> Result<T, DumpError> {
slot.ok_or_else(|| DumpError::new(0, cstr!("missing field `{name}`")))
}
pub fn map_insert<K, V>(
map: &mut FxHashMap<K, V>,
line: &str,
line_no: usize,
) -> Result<(), DumpError>
where
K: DumpValue + Eq + Hash,
V: DumpValue,
{
let Some((key_text, value_text)) = line.split_once('=') else {
return Err(DumpError::new(
line_no,
cstr!("map entry line is missing `=`"),
));
};
let key = K::parse_value(key_text, line_no)?;
if map.contains_key(&key) {
return Err(DumpError::new(
line_no,
cstr!("duplicate map key `{key_text}`"),
));
}
map.insert(key, V::parse_value(value_text, line_no)?);
Ok(())
}