use std::error::Error;
use std::ffi::{CStr, c_char, c_void};
use std::fmt::Display;
use std::io::{self, Read};
use std::mem::MaybeUninit;
use std::ptr;
use unsafe_libyaml::{
yaml_encoding_t::YAML_UTF8_ENCODING, yaml_event_delete, yaml_event_t, yaml_event_type_t,
yaml_mark_t, yaml_parser_delete, yaml_parser_initialize, yaml_parser_parse,
yaml_parser_set_encoding, yaml_parser_set_input, yaml_parser_t,
};
pub(super) use unsafe_libyaml::yaml_event_type_t::*;
pub(super) struct Parser<R>
where
R: Read,
{
parser: Box<yaml_parser_t>,
read_state: *mut ReadState<R>, }
struct ReadState<R>
where
R: Read,
{
reader: R,
bouncer: Vec<u8>,
error: Option<io::Error>,
}
impl<R> Parser<R>
where
R: Read,
{
pub(super) fn new(reader: R) -> Parser<R> {
let mut parser = unsafe {
let mut uninit = Box::new_uninit();
if yaml_parser_initialize(uninit.as_mut_ptr()).fail {
panic!("out of memory for yaml_parser_initialize");
}
uninit.assume_init()
};
let read_state = Box::into_raw(Box::new(ReadState {
reader,
bouncer: vec![],
error: None,
}));
unsafe {
yaml_parser_set_encoding(&mut *parser, YAML_UTF8_ENCODING);
yaml_parser_set_input(
&mut *parser,
Self::read_handler,
read_state.cast::<c_void>(),
);
};
Parser { parser, read_state }
}
fn read_state_mut(&mut self) -> &mut ReadState<R> {
unsafe { &mut *self.read_state }
}
pub(super) fn reader_mut(&mut self) -> &mut R {
&mut self.read_state_mut().reader
}
pub(super) fn next_event(&mut self) -> Result<Event, io::Error> {
Event::parse_next(&mut self.parser).map_err(|err| {
self.read_state_mut()
.error
.take()
.unwrap_or_else(|| io::Error::new(io::ErrorKind::InvalidData, err))
})
}
unsafe fn read_handler(
read_state: *mut c_void,
buffer: *mut u8,
buffer_size: u64,
size_read: *mut u64,
) -> i32 {
const READ_SUCCESS: i32 = 1;
const READ_FAILURE: i32 = 0;
if read_state.is_null() || buffer.is_null() || size_read.is_null() {
return READ_FAILURE;
}
let Ok(buffer_size) = usize::try_from(buffer_size) else {
return READ_FAILURE;
};
let read_state = unsafe { &mut *read_state.cast::<ReadState<R>>() };
read_state.bouncer.resize(buffer_size, 0);
match read_state.reader.read(&mut read_state.bouncer[..]) {
Ok(read_len) if read_len <= buffer_size => {
unsafe {
ptr::copy_nonoverlapping(read_state.bouncer.as_ptr(), buffer, read_len);
*size_read = read_len as u64;
}
read_state.error = None;
READ_SUCCESS
}
Ok(_) => {
read_state.error = Some(io::Error::other("misbehaving reader"));
READ_FAILURE
}
Err(err) => {
read_state.error = Some(err);
READ_FAILURE
}
}
}
}
impl<R> Drop for Parser<R>
where
R: Read,
{
fn drop(&mut self) {
unsafe {
yaml_parser_delete(&mut *self.parser);
drop(Box::from_raw(self.read_state));
}
}
}
pub(super) struct Event(yaml_event_t);
impl Event {
fn parse_next(parser: &mut yaml_parser_t) -> Result<Event, ParserError> {
let mut event = MaybeUninit::uninit();
unsafe {
if yaml_parser_parse(parser, event.as_mut_ptr()).ok {
Ok(Event(event.assume_init()))
} else {
Err(ParserError::new(parser))
}
}
}
pub(super) fn event_type(&self) -> yaml_event_type_t {
self.0.type_
}
pub(super) fn start_offset(&self) -> u64 {
self.0.start_mark.index
}
pub(super) fn end_offset(&self) -> u64 {
self.0.end_mark.index
}
}
impl Drop for Event {
fn drop(&mut self) {
unsafe {
yaml_event_delete(&mut self.0);
};
}
}
#[derive(Debug)]
struct ParserError {
problem: Option<LocatedError>,
context: Option<LocatedError>,
}
impl ParserError {
fn new(parser: &mut yaml_parser_t) -> Self {
let (problem, context);
unsafe {
problem = Self::try_cstr_into_string(parser.problem.cast::<c_char>());
context = Self::try_cstr_into_string(parser.context.cast::<c_char>());
}
Self {
problem: problem.map(|description| {
LocatedError::from_parts(
description,
parser.problem_mark,
Some(parser.problem_offset),
)
}),
context: context.map(|description| {
LocatedError::from_parts(description, parser.context_mark, None)
}),
}
}
unsafe fn try_cstr_into_string(ptr: *const c_char) -> Option<String> {
(!ptr.is_null()).then(|| unsafe { CStr::from_ptr(ptr).to_string_lossy().into_owned() })
}
}
impl Error for ParserError {}
impl Display for ParserError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.problem {
None => f.write_str("unknown libyaml error"),
Some(problem) => match &self.context {
None => write!(f, "{problem}"),
Some(context) => write!(f, "{problem}, {context}"),
},
}
}
}
#[derive(Debug)]
struct LocatedError {
description: String,
offset: u64,
line: u64,
column: u64,
}
impl LocatedError {
fn from_parts(description: String, mark: yaml_mark_t, override_offset: Option<u64>) -> Self {
Self {
description,
line: mark.line + 1,
column: mark.column + 1,
offset: match mark.index > 0 {
true => mark.index,
false => override_offset.unwrap_or(0),
},
}
}
}
impl Display for LocatedError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.line == 1 && self.column == 1 {
write!(
f,
"{issue} at position {byte}",
issue = self.description,
byte = self.offset
)
} else {
write!(
f,
"{issue} at line {line} column {column}",
issue = self.description,
line = self.line,
column = self.column,
)
}
}
}