use std::fmt::Write as FmtWrite;
use bumpalo::Bump;
use saphyr_parser::Parser;
use super::Context;
use super::extract_anchor_names;
use super::formatter::StreamingFormatter;
use super::traits::{AnchorStoreOps, ContextStackOps, FormatterBackend};
use crate::emitter::EmitterConfig;
use crate::error::{EmitError, EmitResult};
pub(super) struct ArenaBackend<'bump> {
context_stack: bumpalo::collections::Vec<'bump, Context>,
anchor_names: bumpalo::collections::Vec<'bump, bumpalo::collections::String<'bump>>,
#[allow(dead_code)] arena: &'bump Bump,
}
impl<'bump> ArenaBackend<'bump> {
pub fn new(context_capacity: usize, arena: &'bump Bump) -> Self {
let mut context_stack =
bumpalo::collections::Vec::with_capacity_in(context_capacity, arena);
context_stack.push(Context::Root);
Self {
context_stack,
anchor_names: bumpalo::collections::Vec::new_in(arena),
arena,
}
}
}
#[allow(clippy::use_self)] impl ContextStackOps for bumpalo::collections::Vec<'_, Context> {
#[inline]
fn push(&mut self, ctx: Context) {
bumpalo::collections::Vec::push(self, ctx);
}
#[inline]
fn pop(&mut self) -> Option<Context> {
bumpalo::collections::Vec::pop(self)
}
#[inline]
fn last(&self) -> Option<&Context> {
<[Context]>::last(self)
}
#[inline]
fn last_mut(&mut self) -> Option<&mut Context> {
<[Context]>::last_mut(self)
}
#[inline]
fn len(&self) -> usize {
bumpalo::collections::Vec::len(self)
}
}
#[allow(clippy::use_self)] impl<'bump> AnchorStoreOps
for bumpalo::collections::Vec<'bump, bumpalo::collections::String<'bump>>
{
fn ensure_capacity(&mut self, anchor_id: usize) {
while self.len() <= anchor_id {
self.push(bumpalo::collections::String::new_in(self.bump()));
}
}
fn get(&self, anchor_id: usize) -> Option<&str> {
<[bumpalo::collections::String]>::get(self, anchor_id)
.filter(|s| !s.is_empty())
.map(bumpalo::collections::String::as_str)
}
fn set_if_empty(&mut self, anchor_id: usize) -> &str {
if self[anchor_id].is_empty() {
let _ = write!(self[anchor_id], "anchor{anchor_id}");
}
self[anchor_id].as_str()
}
}
impl<'bump> FormatterBackend for ArenaBackend<'bump> {
type ContextStack = bumpalo::collections::Vec<'bump, Context>;
type AnchorStore = bumpalo::collections::Vec<'bump, bumpalo::collections::String<'bump>>;
#[inline]
fn context_stack(&self) -> &Self::ContextStack {
&self.context_stack
}
#[inline]
fn context_stack_mut(&mut self) -> &mut Self::ContextStack {
&mut self.context_stack
}
#[inline]
fn anchor_store(&self) -> &Self::AnchorStore {
&self.anchor_names
}
#[inline]
fn anchor_store_mut(&mut self) -> &mut Self::AnchorStore {
&mut self.anchor_names
}
}
pub fn format_streaming_arena(input: &str, config: &EmitterConfig) -> EmitResult<String> {
let arena_size = (input.len() / 4).max(4096);
let arena = Bump::with_capacity(arena_size);
let parser = Parser::new_from_str(input);
let output_capacity = input.len() + (input.len() / 5);
let context_capacity = 16;
let anchor_names = extract_anchor_names(input);
let mut backend = ArenaBackend::new(context_capacity, &arena);
{
let store = backend.anchor_store_mut();
for (id, name) in anchor_names.into_iter().enumerate() {
store.ensure_capacity(id);
if !name.is_empty() && store[id].is_empty() {
let _ = write!(store[id], "{name}");
}
}
}
let mut formatter = StreamingFormatter::new(config, output_capacity, backend);
for result in parser {
let (event, span) = result.map_err(|e| EmitError::Emit(e.to_string()))?;
formatter.format_event(event, span);
}
Ok(formatter.finish())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_arena_backend_creation() {
let arena = Bump::new();
let backend = ArenaBackend::new(16, &arena);
assert_eq!(backend.context_stack.len(), 1);
assert_eq!(backend.context_stack[0], Context::Root);
assert_eq!(backend.anchor_names.len(), 0);
}
#[test]
fn test_arena_context_stack_ops() {
let arena = Bump::new();
let mut stack = bumpalo::collections::Vec::with_capacity_in(16, &arena);
stack.push(Context::Root);
stack.push(Context::Sequence);
assert_eq!(stack.len(), 2);
assert_eq!(stack.last(), Some(&Context::Sequence));
if let Some(last) = stack.last_mut() {
*last = Context::MappingKey;
}
assert_eq!(stack.last(), Some(&Context::MappingKey));
assert_eq!(stack.pop(), Some(Context::MappingKey));
assert_eq!(stack.len(), 1);
}
#[test]
fn test_arena_anchor_store_ops() {
let arena = Bump::new();
let mut store: bumpalo::collections::Vec<'_, bumpalo::collections::String<'_>> =
bumpalo::collections::Vec::new_in(&arena);
store.ensure_capacity(5);
assert!(store.len() >= 6);
let name = store.set_if_empty(3);
assert_eq!(name, "anchor3");
assert_eq!(store.get(3), Some("anchor3"));
assert_eq!(store.get(2), None); assert_eq!(store.get(100), None);
assert!(AnchorStoreOps::is_empty(&store, 2));
assert!(!AnchorStoreOps::is_empty(&store, 3));
assert!(AnchorStoreOps::is_empty(&store, 100));
}
#[test]
fn test_arena_set_if_empty_idempotent() {
let arena = Bump::new();
let mut store: bumpalo::collections::Vec<'_, bumpalo::collections::String<'_>> =
bumpalo::collections::Vec::new_in(&arena);
store.ensure_capacity(5);
let name1 = store.set_if_empty(2).to_string();
assert_eq!(name1, "anchor2");
let name2 = store.set_if_empty(2);
assert_eq!(name2, "anchor2");
assert_eq!(name1, name2);
}
#[test]
fn test_arena_formatter_backend_accessors() {
let arena = Bump::new();
let mut backend = ArenaBackend::new(16, &arena);
assert_eq!(backend.context_stack().len(), 1);
backend.context_stack_mut().push(Context::Sequence);
assert_eq!(backend.context_stack().len(), 2);
assert_eq!(backend.anchor_store().len(), 0);
backend.anchor_store_mut().ensure_capacity(3);
assert!(backend.anchor_store().len() >= 4);
}
#[test]
fn test_format_streaming_arena_simple() {
let yaml = "key: value";
let config = EmitterConfig::default();
let result = format_streaming_arena(yaml, &config).unwrap();
assert!(result.contains("key:"));
assert!(result.contains("value"));
}
}