use alloc::borrow::Cow;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use mf2_model::Span;
use crate::error::{Error, Role};
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Id<'a> {
parts: Vec<Cow<'a, str>>,
}
impl<'a> Id<'a> {
pub fn new(parts: Vec<Cow<'a, str>>) -> Self {
Id { parts }
}
pub fn parts(&self) -> &[Cow<'a, str>] {
&self.parts
}
pub fn push(&mut self, part: impl Into<Cow<'a, str>>) {
self.parts.push(part.into());
}
pub fn is_empty(&self) -> bool {
self.parts.is_empty()
}
pub fn len(&self) -> usize {
self.parts.len()
}
#[must_use]
pub fn join(&self, other: &Id<'a>) -> Id<'a> {
let mut parts = Vec::with_capacity(self.parts.len() + other.parts.len());
parts.extend(self.parts.iter().cloned());
parts.extend(other.parts.iter().cloned());
Id { parts }
}
pub fn canonical(&self) -> Result<String, Error> {
let mut out = String::new();
self.write_canonical(&mut out)?;
Ok(out)
}
pub fn write_canonical(&self, out: &mut String) -> Result<(), Error> {
if self.parts.is_empty() {
return Err(Error::Empty(Role::Id));
}
let start = out.len();
for (i, part) in self.parts.iter().enumerate() {
if i > 0 {
out.push('.');
}
if part.is_empty() {
return Err(Error::Empty(Role::IdPart));
}
for c in part.chars() {
if is_unwritable(c) {
return Err(Error::Unrepresentable(Role::Id, c));
}
if !is_id_char(c) {
out.push('\\');
}
out.push(c);
}
}
if out[start..] == *"---" {
out.insert(start, '\\');
}
Ok(())
}
pub fn read(text: &'a str) -> Result<Id<'a>, Error> {
if text.is_empty() {
return Err(Error::Empty(Role::Id));
}
let mut parts = Vec::new();
let mut part = Cow::Borrowed("");
let mut run = 0usize; let mut chars = text.char_indices();
while let Some((i, c)) = chars.next() {
match c {
'\\' => {
let owned = part.to_mut();
owned.push_str(&text[run..i]);
if let Some((j, escaped)) = chars.next() {
owned.push(escaped);
run = j + escaped.len_utf8();
} else {
run = text.len();
}
}
'.' => {
finish_part(&mut part, text, run, i, &mut parts);
part = Cow::Borrowed("");
run = i + 1;
}
_ => {}
}
}
finish_part(&mut part, text, run, text.len(), &mut parts);
if parts.is_empty() {
return Err(Error::Empty(Role::Id));
}
if parts.iter().any(|p| p.is_empty()) {
return Err(Error::Empty(Role::IdPart));
}
Ok(Id { parts })
}
}
fn finish_part<'a>(
part: &mut Cow<'a, str>,
text: &'a str,
run: usize,
end: usize,
parts: &mut Vec<Cow<'a, str>>,
) {
let run = &text[run.min(end)..end];
let done = match core::mem::replace(part, Cow::Borrowed("")) {
Cow::Borrowed(_) => Cow::Borrowed(run),
Cow::Owned(mut owned) => {
owned.push_str(run);
Cow::Owned(owned)
}
};
parts.push(done);
}
pub fn is_id_char(c: char) -> bool {
if c.is_ascii() {
return c.is_ascii_alphanumeric() || c == '_' || c == '-';
}
mf2_model::is_name_char(c)
}
fn is_unwritable(c: char) -> bool {
c.is_control() || c == '\u{2028}' || c == '\u{2029}'
}
impl fmt::Display for Id<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.parts.len() == 1 && self.parts[0] == "---" {
f.write_str("\\")?;
}
for (i, part) in self.parts.iter().enumerate() {
if i > 0 {
f.write_str(".")?;
}
for c in part.chars() {
if !is_id_char(c) && !is_unwritable(c) {
f.write_str("\\")?;
}
write!(f, "{c}")?;
}
}
Ok(())
}
}
#[derive(Clone, Debug, Default)]
pub enum ValueMap {
Linear {
base: u32,
},
Segments(Vec<Segment>),
#[default]
Empty,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Segment {
pub cooked: u32,
pub src: u32,
pub len: u32,
}
impl ValueMap {
pub fn source_offset(&self, at: u32) -> Option<u32> {
match self {
ValueMap::Linear { base } => Some(base.saturating_add(at)),
ValueMap::Empty => None,
ValueMap::Segments(segs) => {
let i = match segs.binary_search_by_key(&at, |s| s.cooked) {
Ok(i) => i,
Err(0) => return segs.first().map(|s| s.src),
Err(i) => i - 1,
};
let seg = segs.get(i)?;
let delta = at - seg.cooked;
Some(if delta < seg.len {
seg.src + delta
} else {
seg.src
})
}
}
}
pub fn source_span(&self, start: u32, end: u32) -> Option<Span> {
let s = self.source_offset(start)?;
let e = self.source_offset(end.max(start))?;
Some(Span {
start: s,
end: e.max(s),
})
}
}
#[derive(Clone, Debug)]
pub struct Comment<'a> {
pub text: Cow<'a, str>,
pub span: Span,
}
impl PartialEq for Comment<'_> {
fn eq(&self, other: &Self) -> bool {
self.text == other.text
}
}
impl Eq for Comment<'_> {}
#[derive(Clone, Debug)]
pub struct Meta<'a> {
pub name: Cow<'a, str>,
pub value: Option<Cow<'a, str>>,
pub span: Span,
pub value_span: Option<Span>,
}
impl PartialEq for Meta<'_> {
fn eq(&self, other: &Self) -> bool {
self.name == other.name && self.value == other.value
}
}
impl Eq for Meta<'_> {}
#[derive(Clone, Debug)]
pub struct Entry<'a, V> {
pub id: Id<'a>,
pub value: V,
pub comment: Option<Comment<'a>>,
pub meta: Vec<Meta<'a>>,
pub span: Span,
pub id_span: Span,
pub value_span: Span,
pub map: ValueMap,
}
impl<V: PartialEq> PartialEq for Entry<'_, V> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
&& self.value == other.value
&& self.comment == other.comment
&& self.meta == other.meta
}
}
impl<V: Eq> Eq for Entry<'_, V> {}
#[derive(Clone, Debug)]
pub struct Head<'a> {
pub id: Id<'a>,
pub comment: Option<Comment<'a>>,
pub meta: Vec<Meta<'a>>,
pub span: Span,
}
impl PartialEq for Head<'_> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id && self.comment == other.comment && self.meta == other.meta
}
}
impl Eq for Head<'_> {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Detached<'a> {
pub before: usize,
pub comment: Comment<'a>,
}
#[derive(Clone, Debug)]
pub struct Section<'a, V> {
pub head: Option<Head<'a>>,
pub entries: Vec<Entry<'a, V>>,
pub detached: Vec<Detached<'a>>,
}
impl<V: PartialEq> PartialEq for Section<'_, V> {
fn eq(&self, other: &Self) -> bool {
self.head == other.head && self.entries == other.entries && self.detached == other.detached
}
}
impl<V: Eq> Eq for Section<'_, V> {}
impl<V> Default for Section<'_, V> {
fn default() -> Self {
Section {
head: None,
entries: Vec::new(),
detached: Vec::new(),
}
}
}
#[derive(Clone, Debug)]
pub struct Resource<'a, V> {
pub comment: Option<Comment<'a>>,
pub meta: Vec<Meta<'a>>,
pub sections: Vec<Section<'a, V>>,
}
impl<V: PartialEq> PartialEq for Resource<'_, V> {
fn eq(&self, other: &Self) -> bool {
self.comment == other.comment && self.meta == other.meta && self.sections == other.sections
}
}
impl<V: Eq> Eq for Resource<'_, V> {}
impl<V> Default for Resource<'_, V> {
fn default() -> Self {
Resource {
comment: None,
meta: Vec::new(),
sections: Vec::new(),
}
}
}
impl<'a, V> Resource<'a, V> {
pub fn property(&self, name: &str) -> Option<&str> {
self.meta
.iter()
.find(|m| m.name == name)
.and_then(|m| m.value.as_deref())
}
pub fn locale(&self) -> Option<&str> {
self.property("locale")
}
pub fn iter(&self) -> impl Iterator<Item = EntryRef<'_, 'a, V>> {
self.sections.iter().flat_map(|s| {
s.entries.iter().map(move |entry| EntryRef {
section: s.head.as_ref(),
entry,
})
})
}
pub fn entry_count(&self) -> usize {
self.sections.iter().map(|s| s.entries.len()).sum()
}
pub fn map_values<W>(self, mut f: impl FnMut(&EntryInfo<'_, 'a>, V) -> W) -> Resource<'a, W> {
Resource {
comment: self.comment,
meta: self.meta,
sections: self
.sections
.into_iter()
.map(|s| {
let head = s.head;
let entries = s
.entries
.into_iter()
.map(|e| {
let Entry {
id,
value,
comment,
meta,
span,
id_span,
value_span,
map,
} = e;
let value = {
let info = EntryInfo {
section: head.as_ref(),
id: &id,
meta: &meta,
span,
id_span,
value_span,
map: &map,
};
f(&info, value)
};
Entry {
id,
value,
comment,
meta,
span,
id_span,
value_span,
map,
}
})
.collect();
Section {
head,
entries,
detached: s.detached,
}
})
.collect(),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct EntryRef<'r, 'a, V> {
pub section: Option<&'r Head<'a>>,
pub entry: &'r Entry<'a, V>,
}
impl<'a, V> EntryRef<'_, 'a, V> {
pub fn id(&self) -> Id<'a> {
match self.section {
Some(head) => head.id.join(&self.entry.id),
None => self.entry.id.clone(),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct EntryInfo<'r, 'a> {
pub section: Option<&'r Head<'a>>,
pub id: &'r Id<'a>,
pub meta: &'r [Meta<'a>],
pub span: Span,
pub id_span: Span,
pub value_span: Span,
pub map: &'r ValueMap,
}
impl<'a> EntryInfo<'_, 'a> {
pub fn full_id(&self) -> Id<'a> {
match self.section {
Some(head) => head.id.join(self.id),
None => self.id.clone(),
}
}
}