use crate::libyml::{
error::{Error, Mark, Result},
safe_cstr::{self, CStr},
tag::Tag,
util::Owned,
};
#[allow(clippy::unsafe_removed_from_name)]
use libyml as sys;
use std::{
borrow::Cow,
fmt::{self, Debug},
mem::MaybeUninit,
ptr::{addr_of_mut, NonNull},
slice,
};
#[derive(Debug)]
pub struct Parser<'input> {
pub pin: Owned<ParserPinned<'input>>,
}
#[derive(Debug, Clone)]
pub struct ParserPinned<'input> {
pub sys: sys::YamlParserT,
pub input: Cow<'input, [u8]>,
}
#[derive(Debug)]
pub enum Event<'input> {
StreamStart,
StreamEnd,
DocumentStart,
DocumentEnd,
Alias(Anchor),
Scalar(Scalar<'input>),
SequenceStart(SequenceStart),
SequenceEnd,
MappingStart(MappingStart),
MappingEnd,
}
pub struct Scalar<'input> {
pub anchor: Option<Anchor>,
pub tag: Option<Tag>,
pub value: Box<[u8]>,
pub style: ScalarStyle,
pub repr: Option<&'input [u8]>,
}
#[derive(Debug)]
pub struct SequenceStart {
pub anchor: Option<Anchor>,
pub tag: Option<Tag>,
}
#[derive(Debug)]
pub struct MappingStart {
pub anchor: Option<Anchor>,
pub tag: Option<Tag>,
}
#[derive(Ord, PartialOrd, Eq, PartialEq)]
pub struct Anchor(Box<[u8]>);
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum ScalarStyle {
Plain,
SingleQuoted,
DoubleQuoted,
Literal,
Folded,
}
impl<'input> Parser<'input> {
pub fn new(input: Cow<'input, [u8]>) -> Parser<'input> {
let owned = Owned::<ParserPinned<'input>>::new_uninit();
let pin = unsafe {
let parser = addr_of_mut!((*owned.ptr).sys);
if sys::yaml_parser_initialize(parser).fail {
panic!(
"Failed to initialize YAML parser: {}",
Error::parse_error(parser)
);
}
sys::yaml_parser_set_encoding(
parser,
sys::YamlUtf8Encoding,
);
sys::yaml_parser_set_input_string(
parser,
input.as_ptr(),
input.len() as u64,
);
addr_of_mut!((*owned.ptr).input).write(input);
Owned::assume_init(owned)
};
Parser { pin }
}
pub fn parse_next_event(
&mut self,
) -> Result<(Event<'input>, Mark)> {
let mut event = MaybeUninit::<sys::YamlEventT>::uninit();
unsafe {
let parser = addr_of_mut!((*self.pin.ptr).sys);
if (*parser).error != sys::YamlNoError {
return Err(Error::parse_error(parser));
}
let event = event.as_mut_ptr();
if sys::yaml_parser_parse(parser, event).fail {
return Err(Error::parse_error(parser));
}
let event_type = (*event).type_;
if event_type == sys::YamlNoEvent
|| event_type == sys::YamlStreamEndEvent
{
let mark = Mark {
sys: (*event).start_mark,
};
sys::yaml_event_delete(event);
return Ok((Event::StreamEnd, mark));
}
if event_type == sys::YamlScalarEvent
&& (*event).data.scalar.value.is_null()
{
let mark = Mark {
sys: (*event).start_mark,
};
sys::yaml_event_delete(event);
return Ok((Event::StreamEnd, mark));
}
let ret = convert_event(&*event, &(*self.pin.ptr).input);
let mark = Mark {
sys: (*event).start_mark,
};
sys::yaml_event_delete(event);
Ok((ret, mark))
}
}
pub fn is_ok(&self) -> bool {
unsafe {
let parser = addr_of_mut!((*self.pin.ptr).sys);
if sys::yaml_parser_initialize(parser).fail {
return false;
}
sys::yaml_parser_set_encoding(
parser,
sys::YamlUtf8Encoding,
);
let input_ptr = (*self.pin.ptr).input.as_ptr();
let input_len = (*self.pin.ptr).input.len() as u64;
sys::yaml_parser_set_input_string(
parser, input_ptr, input_len,
);
true
}
}
}
unsafe fn convert_event<'input>(
sys: &sys::YamlEventT,
input: &'input Cow<'input, [u8]>,
) -> Event<'input> {
match sys.type_ {
sys::YamlStreamStartEvent => Event::StreamStart,
sys::YamlStreamEndEvent => Event::StreamEnd,
sys::YamlDocumentStartEvent => Event::DocumentStart,
sys::YamlDocumentEndEvent => Event::DocumentEnd,
sys::YamlAliasEvent => Event::Alias(
optional_anchor(sys.data.alias.anchor).unwrap(),
),
sys::YamlScalarEvent => {
let value_slice = slice::from_raw_parts(
sys.data.scalar.value,
sys.data.scalar.length as usize,
);
let repr = optional_repr(sys, input);
Event::Scalar(Scalar {
anchor: optional_anchor(sys.data.scalar.anchor),
tag: optional_tag(sys.data.scalar.tag),
value: Box::from(value_slice),
style: match sys.data.scalar.style {
sys::YamlScalarStyleT::YamlPlainScalarStyle => ScalarStyle::Plain,
sys::YamlScalarStyleT::YamlSingleQuotedScalarStyle => ScalarStyle::SingleQuoted,
sys::YamlScalarStyleT::YamlDoubleQuotedScalarStyle => ScalarStyle::DoubleQuoted,
sys::YamlScalarStyleT::YamlLiteralScalarStyle => ScalarStyle::Literal,
sys::YamlScalarStyleT::YamlFoldedScalarStyle => ScalarStyle::Folded,
_ => unreachable!(),
},
repr,
})
}
sys::YamlSequenceStartEvent => {
Event::SequenceStart(SequenceStart {
anchor: optional_anchor(sys.data.sequence_start.anchor),
tag: optional_tag(sys.data.sequence_start.tag),
})
}
sys::YamlSequenceEndEvent => Event::SequenceEnd,
sys::YamlMappingStartEvent => {
Event::MappingStart(MappingStart {
anchor: optional_anchor(sys.data.mapping_start.anchor),
tag: optional_tag(sys.data.mapping_start.tag),
})
}
sys::YamlMappingEndEvent => Event::MappingEnd,
sys::YamlNoEvent => unreachable!(),
_ => unreachable!(),
}
}
unsafe fn optional_anchor(anchor: *const u8) -> Option<Anchor> {
if anchor.is_null() {
return None;
}
let ptr = NonNull::new(anchor as *mut i8)?;
let cstr = CStr::from_ptr(ptr);
Some(Anchor(Box::from(cstr.to_bytes())))
}
unsafe fn optional_tag(tag: *const u8) -> Option<Tag> {
if tag.is_null() {
return None;
}
let ptr = NonNull::new(tag as *mut i8)?;
let cstr = CStr::from_ptr(ptr);
Some(Tag(Box::from(cstr.to_bytes())))
}
unsafe fn optional_repr<'input>(
sys: &sys::YamlEventT,
input: &'input Cow<'input, [u8]>,
) -> Option<&'input [u8]> {
let start = sys.start_mark.index as usize;
let end = sys.end_mark.index as usize;
Some(&input[start..end])
}
impl Debug for Scalar<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let Scalar {
anchor,
tag,
value,
style,
repr: _,
} = self;
struct LossySlice<'a>(&'a [u8]);
impl Debug for LossySlice<'_> {
fn fmt(
&self,
formatter: &mut fmt::Formatter<'_>,
) -> fmt::Result {
safe_cstr::debug_lossy(self.0, formatter)
}
}
formatter
.debug_struct("Scalar")
.field("anchor", anchor)
.field("tag", tag)
.field("value", &LossySlice(value))
.field("style", style)
.finish()
}
}
impl Debug for Anchor {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
safe_cstr::debug_lossy(&self.0, formatter)
}
}
impl Drop for ParserPinned<'_> {
fn drop(&mut self) {
unsafe { sys::yaml_parser_delete(&mut self.sys) }
}
}