pub mod backlog;
pub mod corpus;
pub mod diff;
mod parse;
use alloc::vec::Vec;
use core::fmt::{self, Write as _};
use vize_l0::String;
use crate::dump::json::push_json_string;
use crate::dump::page::{LineEvent, ParseState};
use crate::dump::{Dump, Error as DumpError, Mode as DumpMode};
use crate::pass::observer::{RecordedArg, RecordedRemark, RemarkArgValue, RemarkKind};
pub const REMARKS_SCHEMA_VERSION: u32 = 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RemarksSchemaMismatch {
pub expected: u32,
pub found: u32,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct RemarkLog {
pub remarks: Vec<RecordedRemark>,
}
impl RemarkLog {
#[must_use]
pub const fn new(remarks: Vec<RecordedRemark>) -> Self {
Self { remarks }
}
pub fn negotiate_schema_version(schema_version: u32) -> Result<(), RemarksSchemaMismatch> {
if schema_version == REMARKS_SCHEMA_VERSION {
Ok(())
} else {
Err(RemarksSchemaMismatch {
expected: REMARKS_SCHEMA_VERSION,
found: schema_version,
})
}
}
#[must_use]
pub fn count(&self, kind: RemarkKind) -> usize {
self.remarks
.iter()
.filter(|remark| remark.kind == kind)
.count()
}
#[must_use]
pub fn to_json(&self, command: &str) -> String {
let mut out = String::default();
out.push_str("{\"schema_version\":");
let _ = write!(out, "{REMARKS_SCHEMA_VERSION}");
out.push_str(",\"command\":");
push_json_string(&mut out, command);
out.push_str(",\"remarks\":[");
for (index, remark) in self.remarks.iter().enumerate() {
if index > 0 {
out.push(',');
}
push_remark_json(&mut out, remark);
}
out.push_str("]}\n");
out
}
}
fn push_remark_json(out: &mut String, remark: &RecordedRemark) {
out.push('{');
push_remark_fields(out, remark);
out.push('}');
}
#[doc(hidden)]
pub fn push_remark_fields(out: &mut String, remark: &RecordedRemark) {
out.push_str("\"stage\":");
push_json_string(out, remark.stage.as_str());
out.push_str(",\"pass\":");
push_json_string(out, remark.pass.as_str());
out.push_str(",\"kind\":");
push_json_string(out, remark.kind.as_str());
out.push_str(",\"name\":");
push_json_string(out, remark.name.as_str());
let _ = write!(
out,
",\"span\":{{\"start\":{},\"end\":{}}},\"args\":[",
remark.span.start, remark.span.end
);
for (index, arg) in remark.args.iter().enumerate() {
if index > 0 {
out.push(',');
}
out.push_str("{\"key\":");
push_json_string(out, arg.key.as_str());
out.push_str(",\"value\":");
match &arg.value {
RemarkArgValue::Str(text) => push_json_string(out, text.as_str()),
RemarkArgValue::Int(number) => {
let _ = write!(out, "{number}");
}
RemarkArgValue::Bool(flag) => out.push_str(if *flag { "true" } else { "false" }),
}
out.push('}');
}
out.push(']');
}
#[must_use]
pub fn entry_line(remark: &RecordedRemark) -> String {
let mut out = String::default();
let _ = print_entry(&mut out, remark, DumpMode::Full);
out
}
pub fn parse_entry_line(line: &str, line_no: usize) -> Result<RecordedRemark, DumpError> {
parse::entry(line, line_no)
}
fn print_entry<W: fmt::Write>(w: &mut W, remark: &RecordedRemark, mode: DumpMode) -> fmt::Result {
write!(
w,
"{}.{} {} {}",
remark.stage,
remark.pass,
remark.kind.as_str(),
remark.name
)?;
if mode == DumpMode::Full {
write!(w, " @{}:{}", remark.span.start, remark.span.end)?;
}
print_args(w, &remark.args)
}
fn print_args<W: fmt::Write>(w: &mut W, args: &[RecordedArg]) -> fmt::Result {
for arg in args {
write!(w, " {}=", arg.key)?;
match &arg.value {
RemarkArgValue::Str(text) => {
let mut quoted = String::default();
push_json_string(&mut quoted, text.as_str());
w.write_str(quoted.as_str())?;
}
RemarkArgValue::Int(number) => write!(w, "{number}")?,
RemarkArgValue::Bool(flag) => w.write_str(if *flag { "true" } else { "false" })?,
}
}
Ok(())
}
#[must_use]
pub fn args_text(args: &[RecordedArg]) -> String {
let mut out = String::default();
let _ = print_args(&mut out, args);
match out.strip_prefix(' ') {
Some(trimmed) => String::from(trimmed),
None => out,
}
}
const SECTION_ENTRIES: usize = 0;
impl Dump for RemarkLog {
fn print<W: fmt::Write>(&self, w: &mut W, mode: DumpMode) -> fmt::Result {
w.write_str("[remarks]\n\n")?;
if self.remarks.is_empty() {
return Ok(());
}
w.write_str("[remarks.entries]\n")?;
for remark in &self.remarks {
print_entry(w, remark, mode)?;
w.write_char('\n')?;
}
w.write_char('\n')
}
fn parse(input: &str) -> Result<Self, DumpError> {
let mut state = ParseState::new("remarks");
let mut remarks = Vec::new();
for (index, line) in input.lines().enumerate() {
let line_no = index + 1;
match state.classify(line, line_no)? {
LineEvent::Skip => {}
LineEvent::Field => {
let name = line.split_once('=').map_or(line, |(name, _)| name);
return Err(crate::dump::page::unknown_field(name, line_no));
}
LineEvent::Section("entries") => {
state.enter_section(SECTION_ENTRIES, "entries", line_no)?;
}
LineEvent::Section(other) => return Err(state.unknown_section(other, line_no)),
LineEvent::Entry(_) => remarks.push(parse::entry(line, line_no)?),
}
}
state.require_header()?;
Ok(Self { remarks })
}
}