#![no_std]
use core::fmt;
use core::str;
#[inline]
fn from_utf8(v: &[u8]) -> &str {
#[cfg(not(debug_assertions))]
return unsafe { str::from_utf8_unchecked(v) };
#[cfg(debug_assertions)]
return str::from_utf8(v).expect("Impossible: Non-UTF8");
}
fn trim(s: &str) -> &str {
s.trim_matches(|chr: char| chr.is_ascii_whitespace())
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Item<'a> {
Error(&'a str),
Section {
name: &'a str,
raw: &'a str,
},
SectionEnd,
Property {
key: &'a str,
val: Option<&'a str>,
raw: &'a str,
},
Comment {
raw: &'a str,
},
Blank {
raw: &'a str,
},
}
impl fmt::Display for Item<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Item::Error(error) => writeln!(f, "{error}"),
Item::Section { name, raw: _ } => writeln!(f, "[{name}]"),
Item::SectionEnd => Ok(()),
Item::Property {
key,
val: Some(value),
raw: _,
} => writeln!(f, "{key}={value}"),
Item::Property {
key,
val: None,
raw: _,
} => writeln!(f, "{key}"),
Item::Comment { raw: comment } => writeln!(f, ";{comment}"),
Item::Blank { raw: _ } => f.write_str("\n"),
}
}
}
#[derive(Clone, Debug)]
pub struct Parser<'a> {
line: u32,
section_ended: bool,
state: &'a [u8],
}
impl<'a> Parser<'a> {
#[inline]
#[must_use]
pub const fn new(s: &'a str) -> Self {
let state = s.as_bytes();
Parser {
line: 0,
section_ended: false,
state,
}
}
#[inline]
#[must_use]
pub const fn line(&self) -> u32 {
self.line
}
#[inline]
#[must_use]
pub fn remainder(&self) -> &'a str {
from_utf8(self.state)
}
#[inline]
fn skip_ln(&mut self, mut s: &'a [u8]) {
if !s.is_empty() {
if s[0] == b'\r' {
s = &s[1..];
}
if !s.is_empty() && s[0] == b'\n' {
s = &s[1..];
}
self.line += 1;
}
self.state = s;
}
fn get_line_and_advance(&mut self, s: &'a [u8]) -> &'a str {
let i = parse::find_nl(s);
let line = from_utf8(&s[..i]);
self.skip_ln(&s[i..]);
line
}
}
impl<'a> Iterator for Parser<'a> {
type Item = Item<'a>;
fn next(&mut self) -> Option<Item<'a>> {
let s = self.state;
match s.first().copied() {
None => {
if self.section_ended {
None
} else {
self.section_ended = true;
Some(Item::SectionEnd)
}
}
Some(b'\r' | b'\n') => {
let line = self.get_line_and_advance(s);
Some(Item::Blank { raw: line })
}
Some(b';' | b'#') => {
let line = self.get_line_and_advance(s);
Some(Item::Comment { raw: line })
}
Some(b'[') => {
if self.section_ended {
self.section_ended = false;
let i = parse::find_nl(s);
if s[i - 1] != b']' {
let error = from_utf8(&s[..i]);
self.skip_ln(&s[i..]);
return Some(Item::Error(error));
}
let section = from_utf8(&s[1..i - 1]);
let section = trim(section);
self.skip_ln(&s[i..]);
Some(Item::Section {
name: section,
raw: from_utf8(&s[..i]),
})
} else {
self.section_ended = true;
Some(Item::SectionEnd)
}
}
_ => {
let eol_or_eq = parse::find_nl_chr(s, b'=');
let key = from_utf8(&s[..eol_or_eq]);
let key = trim(key);
if s.get(eol_or_eq) != Some(&b'=') {
self.skip_ln(&s[eol_or_eq..]);
if key.is_empty() {
return Some(Item::Blank {
raw: from_utf8(&s[..eol_or_eq]),
});
}
Some(Item::Property {
key,
val: None,
raw: from_utf8(&s[..eol_or_eq]),
})
} else {
let val_start = &s[eol_or_eq + 1..];
let i = parse::find_nl(val_start);
let value = from_utf8(&val_start[..i]);
let value = trim(value);
self.skip_ln(&val_start[i..]);
Some(Item::Property {
key,
val: Some(value),
raw: from_utf8(&s[..eol_or_eq + i + 1]),
})
}
}
}
}
}
impl core::iter::FusedIterator for Parser<'_> {}
mod parse;
#[cfg(test)]
mod tests;