#[cfg(feature = "std")]
use std::borrow::Cow;
#[cfg(not(feature = "std"))]
use alloc::borrow::Cow;
#[cfg(not(feature = "std"))]
use alloc::string::String;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use crate::Node;
#[derive(Debug, Clone)]
pub struct LazyTag {
pub raw_value: String,
pub tag: String,
cached: Option<Node>,
}
impl LazyTag {
pub fn new(raw_value: String, tag: String) -> Self {
Self {
raw_value,
tag,
cached: None,
}
}
pub fn get_or_coerce(&mut self) -> &Node {
if self.cached.is_none() {
self.cached = Some(self.coerce());
}
self.cached.as_ref().unwrap()
}
pub fn is_coerced(&self) -> bool {
self.cached.is_some()
}
fn coerce(&self) -> Node {
match self.tag.as_str() {
"!!int" | "tag:yaml.org,2002:int" => {
if let Ok(i) = self.raw_value.parse::<i64>() {
Node::Number(crate::Numeric::Integer(i))
} else {
Node::Str(
self.raw_value.clone(),
crate::QuoteType::Unquoted,
crate::BlockStyle::None,
)
}
}
"!!float" | "tag:yaml.org,2002:float" => {
if let Ok(f) = self.raw_value.parse::<f64>() {
Node::Number(crate::Numeric::Float(f))
} else {
Node::Str(
self.raw_value.clone(),
crate::QuoteType::Unquoted,
crate::BlockStyle::None,
)
}
}
"!!bool" | "tag:yaml.org,2002:bool" => match self.raw_value.to_lowercase().as_str() {
"true" | "yes" | "on" => Node::Boolean(true),
"false" | "no" | "off" => Node::Boolean(false),
_ => Node::Str(
self.raw_value.clone(),
crate::QuoteType::Unquoted,
crate::BlockStyle::None,
),
},
"!!null" | "tag:yaml.org,2002:null" => Node::None,
"!!str" | "tag:yaml.org,2002:str" => Node::Str(
self.raw_value.clone(),
crate::QuoteType::Unquoted,
crate::BlockStyle::None,
),
_ => {
Node::Str(
self.raw_value.clone(),
crate::QuoteType::Unquoted,
crate::BlockStyle::None,
)
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct CapacityHints {
pub mapping_pairs: usize,
pub sequence_items: usize,
pub string_capacity: usize,
pub nesting_depth: usize,
}
impl CapacityHints {
pub fn new() -> Self {
Self {
mapping_pairs: 8,
sequence_items: 8,
string_capacity: 32,
nesting_depth: 4,
}
}
pub fn small() -> Self {
Self {
mapping_pairs: 4,
sequence_items: 4,
string_capacity: 16,
nesting_depth: 2,
}
}
pub fn large() -> Self {
Self {
mapping_pairs: 32,
sequence_items: 32,
string_capacity: 64,
nesting_depth: 8,
}
}
pub fn from_stats(node_count: usize, max_depth: usize) -> Self {
let avg_size = (node_count / max_depth.max(1)).max(4);
Self {
mapping_pairs: avg_size,
sequence_items: avg_size,
string_capacity: 32,
nesting_depth: max_depth,
}
}
pub fn update(&mut self, mapping_size: usize, sequence_size: usize) {
self.mapping_pairs = (self.mapping_pairs + mapping_size) / 2;
self.sequence_items = (self.sequence_items + sequence_size) / 2;
}
}
impl Default for CapacityHints {
fn default() -> Self {
Self::new()
}
}
pub type ZeroCopyStr<'a> = Cow<'a, str>;
#[cfg(feature = "std")]
use crate::utils::string_interner::StringInterner;
#[derive(Debug)]
pub struct PerformanceOptimizer {
pub hints: CapacityHints,
#[cfg(feature = "std")]
pub string_interner: Option<StringInterner>,
pub lazy_tags: bool,
pub zero_copy: bool,
}
impl PerformanceOptimizer {
pub fn new() -> Self {
Self {
hints: CapacityHints::new(),
#[cfg(feature = "std")]
string_interner: None,
lazy_tags: false,
zero_copy: false,
}
}
pub fn aggressive() -> Self {
Self {
hints: CapacityHints::large(),
#[cfg(feature = "std")]
string_interner: Some(StringInterner::with_capacity(256)),
lazy_tags: true,
zero_copy: true,
}
}
#[cfg(feature = "std")]
pub fn enable_string_interning(&mut self, capacity: usize) {
self.string_interner = Some(StringInterner::with_capacity(capacity));
}
pub fn enable_lazy_tags(&mut self) {
self.lazy_tags = true;
}
pub fn enable_zero_copy(&mut self) {
self.zero_copy = true;
}
pub fn alloc_vec<T>(&self) -> Vec<T> {
Vec::with_capacity(self.hints.sequence_items)
}
pub fn alloc_string(&self) -> String {
String::with_capacity(self.hints.string_capacity)
}
}
impl Default for PerformanceOptimizer {
fn default() -> Self {
Self::new()
}
}
pub struct FastPathDetector;
impl FastPathDetector {
pub fn is_simple_scalar(s: &str) -> bool {
!s.is_empty()
&& s.chars()
.all(|c| c.is_alphanumeric() || c == '_' || c == '-')
}
pub fn is_simple_int(s: &str) -> bool {
!s.is_empty()
&& s.chars()
.all(|c| c.is_ascii_digit() || (c == '-' && s.starts_with('-')))
}
pub fn is_simple_mapping_line(s: &str) -> bool {
s.contains(crate::constants::CHAR_COLON)
&& !s.contains(crate::constants::CHAR_HASH)
&& !s.contains(crate::constants::CHAR_LBRACKET)
&& !s.contains(crate::constants::CHAR_LBRACE)
}
pub fn can_use_fast_path(content: &str) -> bool {
!content.contains(crate::constants::STR_DOC_START) && !content.contains(crate::constants::CHAR_AMPERSAND) && !content.contains(crate::constants::CHAR_ASTERISK) && !content.contains("!!") && !content.contains(crate::constants::STR_LITERAL_BLOCK) && !content.contains(crate::constants::STR_FOLDED_BLOCK) }
}
#[cfg(feature = "alloc")]
pub struct NodeBuilder {
hints: CapacityHints,
string_buffer: String,
#[allow(dead_code)]
vec_buffer: Vec<Node>,
}
#[cfg(feature = "alloc")]
impl NodeBuilder {
pub fn new() -> Self {
let hints = CapacityHints::new();
Self {
string_buffer: String::with_capacity(hints.string_capacity),
vec_buffer: Vec::with_capacity(hints.sequence_items),
hints,
}
}
pub fn with_hints(hints: CapacityHints) -> Self {
Self {
string_buffer: String::with_capacity(hints.string_capacity),
vec_buffer: Vec::with_capacity(hints.sequence_items),
hints,
}
}
pub fn build_string(&mut self, value: &str) -> Node {
self.string_buffer.clear();
self.string_buffer.push_str(value);
Node::Str(
self.string_buffer.clone(),
crate::QuoteType::Unquoted,
crate::BlockStyle::None,
)
}
pub fn build_array_with_capacity(&self, capacity: usize) -> Node {
Node::Array(Vec::with_capacity(capacity))
}
pub fn build_mapping_with_capacity(&self, capacity: usize) -> Node {
Node::Mapping(Vec::with_capacity(capacity))
}
pub fn hints(&self) -> &CapacityHints {
&self.hints
}
pub fn update_hints(&mut self, mapping_size: usize, sequence_size: usize) {
self.hints.update(mapping_size, sequence_size);
}
}
#[cfg(feature = "alloc")]
impl Default for NodeBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lazy_tag_unknown_tag() {
let mut lazy = LazyTag::new("data".to_string(), "!!unknown".to_string());
let node = lazy.get_or_coerce();
if let Node::Str(val, _, _) = node {
assert_eq!(val, "data");
} else {
panic!("Expected Node::Str for unknown tag");
}
}
#[test]
fn test_capacity_hints_from_stats() {
let hints = CapacityHints::from_stats(20, 5);
assert!(hints.mapping_pairs >= 4);
assert_eq!(hints.nesting_depth, 5);
}
#[test]
fn test_capacity_hints_default_trait() {
let hints = CapacityHints::default();
assert_eq!(hints.mapping_pairs, 8);
}
#[test]
fn test_performance_optimizer_alloc_methods() {
let opt = PerformanceOptimizer::new();
let v: Vec<u8> = opt.alloc_vec();
assert_eq!(v.capacity(), opt.hints.sequence_items);
let s = opt.alloc_string();
assert_eq!(s.capacity(), opt.hints.string_capacity);
}
#[test]
fn test_performance_optimizer_enable_flags() {
let mut opt = PerformanceOptimizer::new();
opt.enable_lazy_tags();
opt.enable_zero_copy();
assert!(opt.lazy_tags);
assert!(opt.zero_copy);
}
#[test]
#[cfg(feature = "alloc")]
fn test_node_builder_update_hints() {
let mut builder = NodeBuilder::new();
let old_mapping = builder.hints().mapping_pairs;
builder.update_hints(20, 30);
assert!(builder.hints().mapping_pairs != old_mapping);
}
#[test]
fn test_fast_path_detector_edge_cases() {
assert!(!FastPathDetector::is_simple_scalar(""));
assert!(!FastPathDetector::is_simple_int("abc"));
assert!(!FastPathDetector::is_simple_mapping_line("key [1,2,3]"));
}
#[test]
fn test_lazy_tag_int() {
let mut lazy = LazyTag::new("42".to_string(), "!!int".to_string());
assert!(!lazy.is_coerced());
{
let node = lazy.get_or_coerce();
assert!(matches!(node, Node::Number(crate::Numeric::Integer(42))));
}
assert!(lazy.is_coerced());
}
#[test]
fn test_lazy_tag_bool() {
let mut lazy = LazyTag::new("true".to_string(), "!!bool".to_string());
let node = lazy.get_or_coerce();
assert!(matches!(node, Node::Boolean(true)));
}
#[test]
fn test_capacity_hints() {
let hints = CapacityHints::new();
assert_eq!(hints.mapping_pairs, 8);
assert_eq!(hints.sequence_items, 8);
let small = CapacityHints::small();
assert_eq!(small.mapping_pairs, 4);
let large = CapacityHints::large();
assert_eq!(large.mapping_pairs, 32);
}
#[test]
fn test_capacity_hints_update() {
let mut hints = CapacityHints::new();
hints.update(16, 20);
assert!(hints.mapping_pairs > 8);
assert!(hints.sequence_items > 8);
}
#[test]
#[cfg(feature = "std")]
fn test_string_interning() {
let interner = StringInterner::new();
let s1 = interner.intern("test");
let s2 = interner.intern("test");
assert_eq!(s1.as_str(), s2.as_str());
assert_eq!(interner.len(), 1);
}
#[test]
fn test_fast_path_detector() {
assert!(FastPathDetector::is_simple_scalar("hello"));
assert!(FastPathDetector::is_simple_scalar("hello_world"));
assert!(!FastPathDetector::is_simple_scalar("hello world"));
assert!(FastPathDetector::is_simple_int("123"));
assert!(FastPathDetector::is_simple_int("-456"));
assert!(!FastPathDetector::is_simple_int("12.34"));
assert!(FastPathDetector::is_simple_mapping_line("key: value"));
assert!(!FastPathDetector::is_simple_mapping_line("key: [1, 2, 3]"));
}
#[test]
fn test_performance_optimizer() {
let optimizer = PerformanceOptimizer::new();
assert!(!optimizer.lazy_tags);
assert!(!optimizer.zero_copy);
let aggressive = PerformanceOptimizer::aggressive();
assert!(aggressive.lazy_tags);
assert!(aggressive.zero_copy);
}
#[test]
#[cfg(feature = "alloc")]
fn test_node_builder() {
let mut builder = NodeBuilder::new();
let _node = builder.build_string("test");
let _array = builder.build_array_with_capacity(10);
let _mapping = builder.build_mapping_with_capacity(5);
}
#[test]
fn test_fast_path_detection() {
let simple = "name: John\nage: 30";
assert!(FastPathDetector::can_use_fast_path(simple));
let complex = "name: John\n---\nage: 30";
assert!(!FastPathDetector::can_use_fast_path(complex));
}
}