use alloc::borrow::ToOwned;
use alloc::collections::BTreeMap;
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use crate::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TextSpan {
pub start: usize,
pub end: usize,
}
impl TextSpan {
pub fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SectionSpan {
pub body_start: usize,
pub body_end: usize,
}
#[derive(Debug, Clone, Default)]
pub struct SectionIndex {
sections: BTreeMap<ValuePath, SectionSpan>,
pub(crate) root_end: usize,
}
impl SectionIndex {
pub fn new() -> Self {
Self::default()
}
pub(crate) fn insert(&mut self, path: ValuePath, span: SectionSpan) {
self.sections.insert(path, span);
}
pub fn get(&self, path: &[PathSegment]) -> Option<SectionSpan> {
self.sections.get(path).copied()
}
pub fn root_end(&self) -> usize {
self.root_end
}
pub fn iter(&self) -> impl Iterator<Item = (&ValuePath, &SectionSpan)> {
self.sections.iter()
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PathSegment {
Key(String),
Index(usize),
}
pub type ValuePath = Vec<PathSegment>;
#[derive(Debug, Clone, Default)]
pub struct SpanIndex {
spans: BTreeMap<ValuePath, TextSpan>,
}
impl SpanIndex {
pub fn new() -> Self {
Self::default()
}
pub fn get(&self, path: &[PathSegment]) -> Option<TextSpan> {
self.spans.get(path).copied()
}
pub fn iter(&self) -> impl Iterator<Item = (&ValuePath, &TextSpan)> {
self.spans.iter()
}
pub(crate) fn insert(&mut self, path: ValuePath, span: TextSpan) {
self.spans.insert(path, span);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommentSpan {
pub span: TextSpan,
pub target: Option<ValuePath>,
}
#[derive(Debug, Clone, Default)]
pub struct CommentIndex {
comments: Vec<CommentSpan>,
trailing_comments: BTreeMap<ValuePath, TextSpan>,
}
impl CommentIndex {
pub fn new() -> Self {
Self::default()
}
pub fn iter(&self) -> impl Iterator<Item = &CommentSpan> {
self.comments.iter()
}
pub fn trailing_for(&self, path: &[PathSegment]) -> Option<TextSpan> {
self.trailing_comments.get(path).copied()
}
pub(crate) fn insert(&mut self, span: TextSpan, target: Option<ValuePath>) {
if let Some(path) = target.as_ref() {
self.trailing_comments.insert(path.clone(), span);
}
self.comments.push(CommentSpan { span, target });
}
}
pub fn parse_value_path(path: &str) -> Result<ValuePath, Error> {
if path.is_empty() {
return Err(Error::validate("value path is empty"));
}
let bytes = path.as_bytes();
let mut i = 0usize;
let mut segments = Vec::new();
while i < bytes.len() {
if bytes[i] == b'.' {
return Err(Error::validate("value path contains empty key"));
}
if bytes[i] == b'[' {
let (index, next_i) = parse_index(path, i)?;
segments.push(PathSegment::Index(index));
i = next_i;
} else {
let start = i;
while i < bytes.len() && bytes[i] != b'.' && bytes[i] != b'[' {
i += 1;
}
if start == i {
return Err(Error::validate("value path key is empty"));
}
segments.push(PathSegment::Key(path[start..i].to_owned()));
}
while i < bytes.len() && bytes[i] == b'[' {
let (index, next_i) = parse_index(path, i)?;
segments.push(PathSegment::Index(index));
i = next_i;
}
if i < bytes.len() {
if bytes[i] != b'.' {
return Err(Error::validate(format!(
"invalid value path syntax: '{}'",
path
)));
}
i += 1;
if i >= bytes.len() {
return Err(Error::validate("value path ends with '.'"));
}
}
}
Ok(segments)
}
fn parse_index(path: &str, start: usize) -> Result<(usize, usize), Error> {
let bytes = path.as_bytes();
if bytes.get(start) != Some(&b'[') {
return Err(Error::validate("invalid array index start"));
}
let mut i = start + 1;
let num_start = i;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
}
if num_start == i {
return Err(Error::validate("array index is empty"));
}
if bytes.get(i) != Some(&b']') {
return Err(Error::validate("array index missing closing ']'"));
}
let index = path[num_start..i]
.parse::<usize>()
.map_err(|e| Error::validate(format!("array index conversion error: {e}")))?;
Ok((index, i + 1))
}