tamer: Remove wip-xmlo-xir-reader

This entirely removes the old XmloReader that has since been replaced with a
XIR-based reader.

I had been holding off on this because the new reader is slower, pending
performance optimizations (which I'll do a little later on), however the
performance loss is of no practical consideration and only affects the
linker, which is still fast.

Therefore, it's better to get this old code out of the way to simplify
refactoring going forward.  In particular, I'm working on the diagnostic
system.

This is a little sad, in a way---this is some of my first Rust code that I'm
deleting.

DEV-10935
main
Mike Gerwitz 2022-04-11 16:08:50 -04:00
parent 4c69efd175
commit cfc7f45bc4
12 changed files with 75 additions and 1686 deletions

View File

@ -51,7 +51,3 @@ paste = ">= 1.0.5"
# expecting it so that it can skip those compilation steps.
wip-frontends = []
# Use the XIR-based reader for parsing xmlo files in tameld. This flag will
# exist only while reimplementing the XmloReader.
wip-xmlo-xir-reader = []

View File

@ -31,12 +31,15 @@ use crate::{
Filesystem, FsCanonicalizer, PathFile, VisitOnceFile,
VisitOnceFilesystem,
},
iter::into_iter_while_ok,
ld::xmle::Sections,
obj::xmlo::{
AsgBuilder, AsgBuilderError, AsgBuilderState, XmloError, XmloReader,
},
parse::ParseError,
parse::{ParseState, Parsed},
sym::{GlobalSymbolIntern, GlobalSymbolResolve, SymbolId},
xir::reader::XmlXirReader,
xir::{
flat::{self, Object as XirfToken, StateError as XirfError},
writer::{Error as XirWriterError, XmlWriter},
@ -134,8 +137,6 @@ pub fn graphml(package_path: &str, output: &str) -> Result<(), TameldError> {
GraphMl::new(&g)
.pretty_print(true)
.export_node_weights(Box::new(|node| {
// eprintln!("{:?}", node);
let (name, kind, generated) = match node {
Some(n) => {
let generated = match n.src() {
@ -181,47 +182,25 @@ fn load_xmlo<'a, P: AsRef<Path>, S: Escaper>(
VisitOnceFile::Visited => return Ok(state),
};
let mut state = {
#[cfg(not(feature = "wip-xmlo-xir-reader"))]
{
let xmlo: XmloReader<_> = file.into();
let _ctx = ctx; // suppress warning
depgraph.import_xmlo(xmlo, state)?
}
#[cfg(feature = "wip-xmlo-xir-reader")]
{
use crate::iter::into_iter_while_ok;
use crate::parse::{ParseState, Parsed};
use crate::xir::reader::XmlXirReader;
// TODO: This entire block is a WIP and will be incrementally
// abstracted away.
into_iter_while_ok(
XmlXirReader::new(file, escaper, ctx),
|toks| {
flat::State::<64>::parse(toks)
.lower_while_ok::<XmloReader, _>(|xirf| {
into_iter_while_ok(xirf, |xmlo_out| {
// TODO: Transitionary---we do not want to filter.
depgraph.import_xmlo(
xmlo_out.filter_map(
|parsed| match parsed {
Parsed::Incomplete => None,
Parsed::Object(obj) => {
Some(Ok(obj))
}
},
),
state,
)
})
})
// TODO: This entire block is a WIP and will be incrementally
// abstracted away.
let mut state =
into_iter_while_ok(XmlXirReader::new(file, escaper, ctx), |toks| {
flat::State::<64>::parse(toks).lower_while_ok::<XmloReader, _>(
|xirf| {
into_iter_while_ok(xirf, |xmlo_out| {
// TODO: Transitionary---we do not want to filter.
depgraph.import_xmlo(
xmlo_out.filter_map(|parsed| match parsed {
Parsed::Incomplete => None,
Parsed::Object(obj) => Some(Ok(obj)),
}),
state,
)
})
},
)????
}
};
)
})????;
let mut dir: PathBuf = path.clone();
dir.pop();

View File

@ -88,7 +88,6 @@ pub mod obj;
pub mod parse;
pub mod span;
pub mod sym;
pub mod tpwrap;
#[cfg(test)]
pub mod test;

View File

@ -21,7 +21,6 @@
use crate::span::Span;
use crate::sym::SymbolId;
use crate::tpwrap::quick_xml::{Error as XmlError, InnerXmlError};
use std::fmt::Display;
/// Error during `xmlo` processing.
@ -35,8 +34,6 @@ use std::fmt::Display;
/// TODO: These errors provide no context (byte offset).
#[derive(Debug, PartialEq, Eq)]
pub enum XmloError {
/// XML parsing error (legacy, quick-xml).
XmlError(XmlError),
/// The root node was not an `lv:package`.
UnexpectedRoot,
/// A `preproc:sym` node was found, but is missing `@name`.
@ -64,16 +61,9 @@ pub enum XmloError {
UnexpectedEof,
}
impl From<InnerXmlError> for XmloError {
fn from(e: InnerXmlError) -> Self {
XmloError::XmlError(e.into())
}
}
impl Display for XmloError {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::XmlError(e) => e.fmt(fmt),
Self::UnexpectedRoot => {
write!(fmt, "unexpected package root (is this a package?)")
}
@ -132,9 +122,6 @@ impl Display for XmloError {
impl std::error::Error for XmloError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::XmlError(e) => Some(e),
_ => None,
}
None
}
}

View File

@ -29,17 +29,6 @@ use crate::{
xir::{attr::Attr, flat::Object as Xirf, QName},
};
// While the _use_ is gated, this isn't, to ensure that we still try to
// compile it while the flag is off (and so it's parsed by the language
// server).
mod quickxml;
#[cfg(not(feature = "wip-xmlo-xir-reader"))]
pub use quickxml::XmloReader;
#[cfg(feature = "wip-xmlo-xir-reader")]
pub use XmloReaderState as XmloReader;
/// `xmlo` reader events.
///
/// All data are parsed rather than being returned as [`u8`] slices,
@ -129,14 +118,14 @@ qname_const! {
QN_YIELDS: :L_YIELDS,
}
/// A parser capable of being composed with [`XmloReaderState`].
/// A parser capable of being composed with [`XmloReader`].
pub trait XmloState = ParseState<Token = Xirf, Context = EmptyContext>
where
<Self as ParseState>::Error: Into<XmloError>,
<Self as ParseState>::Object: Into<XmloEvent>;
#[derive(Debug, Default, PartialEq, Eq)]
pub enum XmloReaderState<
pub enum XmloReader<
SS: XmloState = SymtableState,
SD: XmloState = SymDepsState,
SF: XmloState = FragmentsState,
@ -163,7 +152,7 @@ pub enum XmloReaderState<
}
impl<SS: XmloState, SD: XmloState, SF: XmloState> ParseState
for XmloReaderState<SS, SD, SF>
for XmloReader<SS, SD, SF>
{
type Token = Xirf;
type Object = XmloEvent;
@ -174,7 +163,7 @@ impl<SS: XmloState, SD: XmloState, SF: XmloState> ParseState
tok: Self::Token,
ctx: NoContext,
) -> TransitionResult<Self> {
use XmloReaderState::*;
use XmloReader::*;
match (self, tok) {
(Ready, Xirf::Open(QN_LV_PACKAGE | QN_PACKAGE, ..)) => {

View File

@ -1,648 +0,0 @@
// xmlo object file reader
//
// Copyright (C) 2014-2021 Ryan Specialty Group, LLC.
//
// This file is part of TAME.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! `xmlo` object file reader.
//!
//! This defines a lower-level event-based [`XmloReader`] similar to that of
//! [`quick_xml`] (see [`XmloEvent`]),
//! where the events are a slightly higher-level abstraction over the
//! types of nodes present in the file.
//!
//! _Note that a "symbol" in the `xmlo` sense differs slightly from
//! [`SymbolId`];_
//! the former is more akin to an identifier.
//!
//! For more information on `xmlo` files,
//! see the [parent crate][super].A
//!
//! This reader will be used by both the compiler and linker,
//! and so its [`SymbolId`] type is generalized.
//!
//!
//! How To Use
//! ==========
//! The event-based API for [`XmloReader`] is similar to that of
//! [`quick_xml`].
//! There is minor overhead incurred from parsing if the emitted events are
//! not used,
//! but it is quite minimal.
//!
//! The next [`XmloEvent`] is retrieved using [`XmloReader::read_event`].
//! _You should stop reading at [`XmloEvent::Eoh`];_
//! reading the remainder of the object file has not yet been implemented.
use super::super::{SymAttrs, SymType};
use super::{XmloError, XmloEvent, XmloResult};
use crate::obj::xmlo::Dim;
use crate::span::UNKNOWN_SPAN;
use crate::sym::{GlobalSymbolInternUnchecked, GlobalSymbolResolve, SymbolId};
#[cfg(test)]
use crate::test::quick_xml::MockBytesStart as BytesStart;
#[cfg(test)]
use crate::test::quick_xml::MockXmlEvent as XmlEvent;
#[cfg(test)]
use crate::test::quick_xml::MockXmlReader as XmlReader;
#[cfg(not(test))]
use quick_xml::events::BytesStart;
#[cfg(not(test))]
use quick_xml::events::Event as XmlEvent;
#[cfg(not(test))]
use quick_xml::Reader as XmlReader;
use std::collections::VecDeque;
use std::convert::TryInto;
use std::io::BufRead;
use std::iter::Iterator;
use std::result::Result;
/// Wrapper around [`quick_xml::Reader`] for reading and parsing `xmlo`
/// object files.
///
/// This reader performs interning (see [crate::sym]) for data that is
/// expected to be duplicated or compared.
/// Other data are converted into more concise representations where
/// possible,
/// or are returned as owned [`String`] values otherwise,
/// with the understanding that values will be persisted within an IR
/// anyway.
/// This reader stores symbol attributes in the Legacy IR's [`SymAttrs`].
///
/// See [module-level documentation](self) for more information and
/// examples.
pub struct XmloReader<B>
where
B: BufRead,
{
/// Source `xmlo` reader.
reader: XmlReader<B>,
/// Internal buffer for [`XmlReader`].
buffer: Vec<u8>,
/// Another internal buffer for [`XmlReader`].
///
/// This buffer exists to work around ownership rules.
/// TODO: It this worth removing? If not, remove this TODO.
sub_buffer: Vec<u8>,
/// Whether the root has been validated.
///
/// This is used to ensure that we provide an error early on if we try
/// to process something that isn't a package.
seen_root: bool,
/// Name of the package currently being read.
///
/// This is known after processing the root `package` element,
/// provided that it's a proper root node.
pkg_name: Option<SymbolId>,
/// Queue of events already processed,
/// to be returned on [`XmloReader::read_event`].
///
/// This exists as an incremental transition toward producing a
/// streaming API and will eventually be eliminated.
/// It does incur a small performance cost.
event_queue: VecDeque<XmloEvent>,
}
impl<B> XmloReader<B>
where
B: BufRead,
{
/// Construct a new reader.
pub fn new(reader: B) -> Self {
let mut reader = XmlReader::from_reader(reader);
// xmlo files are compiler output and should be trusted
reader.check_end_names(false);
Self {
reader,
// TODO: option to accept buffer
buffer: Vec::new(),
sub_buffer: Vec::new(),
seen_root: false,
pkg_name: None,
event_queue: VecDeque::new(),
}
}
/// Continue reading and produce the next event.
///
/// An [`XmloEvent::Eoh`] event is emitted at the end of the header
/// (at the closing `preproc:fragment` node).
///
/// Stack Warning
/// =============
/// The source file will be read until an event can be produced.
/// This is recursive on the underlying [`XmlReader::read_event`],
/// and Rust dues not (at the time of writing) support tail call
/// optimization.
/// This shouldn't be a concern for proper `xmlo` files as long as you
/// acknowledge [`XmloEvent::Eoh`] and do not continue reading
/// further.
///
/// Errors
/// ======
/// - Any of [`XmloError`].
/// See private methods for more information.
///
/// TODO: Augment failures with context
pub fn read_event<'a>(&mut self) -> XmloResult<XmloEvent> {
// Just to cut down on peak memory usage, cleaning up after a
// previous run. This does not affect behavior.
self.buffer.clear();
self.sub_buffer.clear();
// Return queued events first before continuing processing.
// This allows us to begin to transition to a streaming API without
// many structural changes,
// but will eventually go away.
if let Some(event) = self.event_queue.pop_front() {
return Ok(event);
}
let event = self.reader.read_event(&mut self.buffer)?;
// Ensure that the first encountered node is something we expect
if !self.seen_root {
match &event {
// We don't process namespaces, so we have to guess what
// they may be (map xmlo files differ, for example)
XmlEvent::Start(ele) => {
if !(ele.name() == b"package"
|| ele.name() == b"lv:package")
{
return Err(XmloError::UnexpectedRoot);
}
self.seen_root = true;
}
_ => return self.read_event(),
}
}
match event {
XmlEvent::Empty(ele) if ele.name() == b"preproc:sym" => {
Self::process_sym(&self.pkg_name, &ele)
}
XmlEvent::Start(ele) => match ele.name() {
b"package" | b"lv:package" => Self::process_package(
&ele,
&mut self.pkg_name,
&mut self.event_queue,
),
b"preproc:sym-dep" => Self::process_dep(
&ele,
&mut self.reader,
&mut self.sub_buffer,
&mut self.event_queue,
),
b"preproc:fragment" => Self::process_fragment(
&ele,
&mut self.reader,
&mut self.sub_buffer,
),
// `func` symbols include additional data for param
// ordering, which we don't care about. But `map` includes
// source field information which we want to keep. (We
// don't care about `retmap` for our purposes.)
b"preproc:sym" => {
let mut event = Self::process_sym(&self.pkg_name, &ele)?;
match &mut event {
XmloEvent::SymDecl(name, attrs, _)
if attrs.ty == Some(SymType::Map) =>
{
attrs.from = Self::process_map_from(
&mut self.reader,
&mut self.sub_buffer,
*name,
)?;
Ok(event)
}
_ => {
self.reader.read_to_end(
ele.name(),
&mut self.sub_buffer,
)?;
Ok(event)
}
}
}
// Just like the outer match, recurse
_ => self.read_event(),
},
XmlEvent::End(ele) if ele.name() == b"preproc:fragments" => {
Ok(XmloEvent::Eoh(UNKNOWN_SPAN))
}
// Ignore and recurse, looking for something we can process
_ => self.read_event(),
}
}
/// Process `lv:package` element attributes.
fn process_package<'a>(
ele: &'a BytesStart<'a>,
pkg_name: &mut Option<SymbolId>,
event_queue: &mut VecDeque<XmloEvent>,
) -> XmloResult<XmloEvent> {
let mut program = false;
let mut elig = None;
let mut name = None;
let mut relroot = None;
for attr in ele.attributes().with_checks(false).filter_map(Result::ok) {
match attr.key {
b"name" => {
name =
Some(unsafe { (&attr.value).intern_utf8_unchecked() });
}
b"__rootpath" => {
relroot =
Some(unsafe { (&attr.value).intern_utf8_unchecked() });
}
b"program" => {
program = &*attr.value == b"true";
}
b"preproc:elig-class-yields" => {
elig =
Some(unsafe { (&attr.value).intern_utf8_unchecked() });
}
_ => (),
}
}
if let Some(given_name) = name {
event_queue.push_back(XmloEvent::PkgName(given_name));
}
if let Some(given_relroot) = relroot {
event_queue.push_back(XmloEvent::PkgRootPath(given_relroot));
}
if let Some(given_elig) = elig {
event_queue.push_back(XmloEvent::PkgEligClassYields(given_elig));
}
if program {
event_queue.push_back(XmloEvent::PkgProgramFlag);
}
*pkg_name = name;
Ok(event_queue.pop_front().unwrap())
}
/// Process `preproc:sym` element attributes.
///
/// The symbol name `preproc:sym/@name` is interned.
/// All other known attributes are parsed
/// and unknown attributes are ignored.
///
/// The result is a single [`XmloEvent::SymDecl`] with an interned
/// `preproc:sym/@name`.
///
/// Errors
/// ======
/// - [`XmloError::UnassociatedSym`] if missing `preproc:sym/@name`.
fn process_sym<'a>(
pkg_name: &Option<SymbolId>,
ele: &'a BytesStart<'a>,
) -> XmloResult<XmloEvent> {
let mut name: Option<SymbolId> = None;
let mut sym_attrs = SymAttrs::default();
for attr in ele.attributes().with_checks(false).filter_map(Result::ok) {
match attr.key {
b"name" => {
name = Some(unsafe { attr.value.intern_utf8_unchecked() });
}
b"src" => {
sym_attrs.src =
Some(unsafe { attr.value.intern_utf8_unchecked() });
}
b"type" => {
sym_attrs.ty =
Some((*attr.value).try_into().map_err(|_| {
XmloError::InvalidType(
unsafe { attr.value.intern_utf8_unchecked() },
UNKNOWN_SPAN,
)
})?);
}
b"dim" => {
sym_attrs.dim = Some(Self::char_to_dim(&attr.value)?);
}
b"dtype" => {
sym_attrs.dtype =
Some((*attr.value).try_into().map_err(|_| {
XmloError::InvalidDtype(
unsafe { attr.value.intern_utf8_unchecked() },
UNKNOWN_SPAN,
)
})?);
}
b"extern" => {
sym_attrs.extern_ = &*attr.value == b"true";
}
b"preproc:generated" => {
sym_attrs.generated = &*attr.value == b"true";
}
b"parent" => {
sym_attrs.parent =
Some(unsafe { attr.value.intern_utf8_unchecked() });
}
b"yields" => {
sym_attrs.yields =
Some(unsafe { attr.value.intern_utf8_unchecked() });
}
b"desc" => {
sym_attrs.desc =
Some(unsafe { attr.value.intern_utf8_unchecked() });
}
b"virtual" => {
sym_attrs.virtual_ = &*attr.value == b"true";
}
b"isoverride" => {
sym_attrs.override_ = &*attr.value == b"true";
}
// As this reader evolves, we may wish to provide an error
// for unknown attributes so that we can be sure that we've
// handled them all.
_ => (),
}
}
sym_attrs.pkg_name = *pkg_name;
name.map(|name_sym| {
XmloEvent::SymDecl(name_sym, sym_attrs, UNKNOWN_SPAN)
})
.ok_or(XmloError::UnassociatedSym(UNKNOWN_SPAN))
}
/// Process `preproc:from` for `preproc:sym[@type="map"]` elements.
///
/// Map symbols contain additional information describing source
/// inputs external to the system.
fn process_map_from<'a>(
reader: &mut XmlReader<B>,
buffer: &mut Vec<u8>,
name: SymbolId,
) -> XmloResult<Option<SymbolId>> {
let mut from = None;
loop {
match reader.read_event(buffer)? {
XmlEvent::Empty(ele) if ele.name() == b"preproc:from" => {
if from.is_some() {
// This feature isn't actually utilized for the
// input map.
return Err(XmloError::MapFromMultiple(
name,
UNKNOWN_SPAN,
));
}
from.replace(
ele.attributes()
.with_checks(false)
.filter_map(Result::ok)
.find(|attr| attr.key == b"name")
.map_or(
Err(XmloError::MapFromNameMissing(
name,
UNKNOWN_SPAN,
)),
|attr| {
Ok(unsafe {
attr.value.intern_utf8_unchecked()
})
},
)?,
);
}
XmlEvent::End(ele) if ele.name() == b"preproc:sym" => break,
// Note that whitespace counts as text
XmlEvent::Text(_) => (),
_ => todo!("unexpected preproc:sym[type=\"map\"] input"),
};
}
Ok(from)
}
/// Process `preproc:sym-dep` element.
///
/// This represents an adjacency list for a given identifier in the
/// dependency graph.
/// The structure of this element is looks like this:
///
/// ```xml
/// <preproc:sym-dep name=":class:some-sym">
/// <preproc:sym-ref name="someOtherSym" />
/// <!-- ... -->
/// </preproc:sym-dep>
/// ```
///
/// Errors
/// ======
/// - [`XmloError::UnassociatedSymDep`] if missing `preproc:sym-dep/@name`.
/// - [`XmloError::MalformedSymRef`] if missing `preproc:sym-ref/@name`
/// or if any `preproc:sym-dep/node()` is not a `prepreoc:sym-ref`.
/// - [`XmloError::XmlError`] on XML parsing failure.
fn process_dep<'a>(
ele: &'a BytesStart<'a>,
reader: &mut XmlReader<B>,
buffer: &mut Vec<u8>,
event_queue: &mut VecDeque<XmloEvent>,
) -> XmloResult<XmloEvent> {
let name = ele
.attributes()
.with_checks(false)
.filter_map(Result::ok)
.find(|attr| attr.key == b"name")
.map_or(
Err(XmloError::UnassociatedSymDep(UNKNOWN_SPAN)),
|attr| Ok(unsafe { attr.value.intern_utf8_unchecked() }),
)?;
event_queue.push_back(XmloEvent::SymDepStart(name, UNKNOWN_SPAN));
loop {
match reader.read_event(buffer)? {
XmlEvent::Empty(symref)
if symref.name() == b"preproc:sym-ref" =>
{
event_queue.push_back(XmloEvent::Symbol(
symref
.attributes()
.with_checks(false)
.filter_map(Result::ok)
.find(|attr| attr.key == b"name")
.map_or(
Err(XmloError::MalformedSymRef(
name, UNKNOWN_SPAN
)),
|attr| {
Ok(unsafe {
attr.value.intern_utf8_unchecked()
})
},
)?,
UNKNOWN_SPAN,
));
}
// We assume that elements are properly nested, so this must
// be the closing preproc:sym-dep tag.
XmlEvent::End(_) => break,
// Note that whitespace counts as text
XmlEvent::Text(_) => (),
// This is handled in a better way in the new parser.
_ => panic!(
"preproc:sym-dep must contain only preproc:sym-ref children for `{}`",
name.lookup_str(),
)
}
}
Ok(event_queue.pop_front().unwrap())
}
/// Process `preproc:fragment` element.
///
/// This element represents the compiled code for the given symbol.
/// The result is a single [`XmloEvent::Fragment`] with an interned
/// fragment and an interned `preproc:fragment/@id`.
/// The fragment is _left escaped_,
/// since it is assumed that it will be written back out verbatim
/// without further modification;
/// this save us from having to spend time re-escaping on output
/// down the line.
///
/// Errors
/// ======
/// - [`XmloError::UnassociatedFragment`] if missing `preproc:fragment/@id`.
/// - [`XmloError::MissingFragmentText`] if missing
/// `preproc:fragment/text()`.
/// - [`XmloError::XmlError`] for XML parsing errors.
fn process_fragment<'a>(
ele: &'a BytesStart<'a>,
reader: &mut XmlReader<B>,
buffer: &mut Vec<u8>,
) -> XmloResult<XmloEvent> {
let mut src_attrs = ele.attributes();
let mut filtered = src_attrs.with_checks(false).filter_map(Result::ok);
let id = filtered
.find(|attr| attr.key == b"id")
.filter(|attr| &*attr.value != b"")
.map_or(
Err(XmloError::UnassociatedFragment(UNKNOWN_SPAN)),
|attr| Ok(unsafe { attr.value.intern_utf8_unchecked() }),
)?;
let text = match reader.read_event(buffer)? {
XmlEvent::Text(ev) => {
// It is wasteful to unescape only to have to re-escape
// again on write, so keep the text raw (escaped), and also
// trust that it's valid UTF-8, having come from the
// compiler.
Ok(unsafe { ev.escaped().clone_uninterned_utf8_unchecked() })
}
_ => Err(XmloError::MissingFragmentText(id, UNKNOWN_SPAN)),
}?;
Ok(XmloEvent::Fragment(id, text, UNKNOWN_SPAN))
}
/// Convert single-character `@dim` to a [`Dim`].
fn char_to_dim(value: &[u8]) -> XmloResult<Dim> {
match value {
[b'0'] => Ok(Dim::Scalar),
[b'1'] => Ok(Dim::Vector),
[b'2'] => Ok(Dim::Matrix),
_ => Err(XmloError::InvalidDim(
unsafe { value.intern_utf8_unchecked() },
UNKNOWN_SPAN,
)),
}
}
}
impl<B> Iterator for XmloReader<B>
where
B: BufRead,
{
type Item = XmloResult<XmloEvent>;
/// Invoke [`XmloReader::read_event`] and yield the result via an
/// [`Iterator`] API.
///
/// *Warning*: This will always return [`Some`] for now.
/// Future changes may alter this behavior.
/// To terminate the iterator,
/// it's recommended that you use [`Iterator::take_while`] to filter
/// on the desired predicate,
/// such as [`XmloEvent::Eoh`].
fn next(&mut self) -> Option<Self::Item> {
Some(self.read_event())
}
}
impl<B> From<B> for XmloReader<B>
where
B: BufRead,
{
fn from(buf: B) -> Self {
Self::new(buf)
}
}
#[cfg(test)]
mod test;

View File

@ -1,822 +0,0 @@
// Tests for xmlo object file reader
//
// Copyright (C) 2014-2021 Ryan Specialty Group, LLC.
//
// This file is part of TAME.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use crate::obj::xmlo::{SymDtype, SymType};
use crate::sym::GlobalSymbolIntern;
use crate::test::quick_xml::*;
use crate::tpwrap::quick_xml::{Error as XmlError, InnerXmlError};
type Sut<B> = XmloReader<B>;
// Tests marked with "DONE" have been migrated to `super::test`.
macro_rules! xmlo_tests {
($(fn $fn:ident($sut:ident) $body:block)*) => {
$(
#[test]
fn $fn() -> XmloResult<()> {
let stub_data: &[u8] = &[];
#[allow(unused_mut)]
let mut $sut = Sut::new(stub_data);
// We don't want to have to output a proper root node
// for every one of our tests.
$sut.seen_root = true;
$sut.pkg_name = Some("pkg/name".intern());
$body;
Ok(())
}
)*
};
}
xmlo_tests! {
fn sets_parsing_options(sut) {
assert_eq!(Some(false), sut.reader.check_end);
}
// DONE
fn proxies_xml_failures(sut) {
sut.reader.next_event =
Some(Box::new(|_, _| Err(InnerXmlError::UnexpectedEof("test".into()))));
match sut.read_event() {
Err(XmloError::XmlError(XmlError(InnerXmlError::UnexpectedEof(_)))) => (),
bad => panic!("expected XmlError: {:?}", bad),
}
}
// DONE
fn sym_fails_without_name(sut) {
sut.reader.next_event = Some(Box::new(|_, _| {
Ok(XmlEvent::Start(MockBytesStart::new(
b"preproc:sym",
Some(MockAttributes::new(vec![])),
)))
}));
match sut.read_event() {
Err(XmloError::UnassociatedSym(_)) => (),
bad => panic!("expected XmloError::UnassociatedSym: {:?}", bad),
}
}
// DONE
fn fails_on_invalid_root(sut) {
// xmlo_tests macro sets this for us, so we need to clear it to
// be able to perform the check
sut.seen_root = false;
sut.reader.next_event = Some(Box::new(|_, _| {
Ok(XmlEvent::Start(MockBytesStart::new(
b"not-a-valid-package-node",
Some(MockAttributes::new(vec![])),
)))
}));
match sut.read_event() {
Err(XmloError::UnexpectedRoot) => (),
bad => panic!("expected XmloError: {:?}", bad),
}
}
// DONE
fn recognizes_valid_roots(sut) {
// xmlo_tests macro sets this for us, so we need to clear it to
// be able to perform the check
sut.seen_root = false;
// First valid root
sut.reader.next_event = Some(Box::new(|_, _| {
Ok(XmlEvent::Start(MockBytesStart::new(
b"package",
Some(MockAttributes::new(vec![
MockAttribute::new(b"program", b"true"),
])),
)))
}));
// Will fail if the above is not valid. See below for actually
// testing the package node.
sut.read_event()?;
// We don't process namespaces (to slow) so we have to handle
// the difference explicitly.
sut.seen_root = false;
sut.reader.next_event = Some(Box::new(|_, _| {
Ok(XmlEvent::Start(MockBytesStart::new(
b"lv:package",
Some(MockAttributes::new(vec![
MockAttribute::new(b"program", b"true"),
])),
)))
}));
sut.read_event()?;
}
// DONE
fn package_event_program(sut) {
sut.reader.next_event = Some(Box::new(|_, _| {
Ok(XmlEvent::Start(MockBytesStart::new(
b"package",
Some(MockAttributes::new(vec![
MockAttribute::new(b"program", b"true"),
MockAttribute::new(
b"preproc:elig-class-yields", b"eligClassYields",
),
])),
)))
}));
assert_eq!(
XmloEvent::PkgEligClassYields("eligClassYields".intern()),
sut.read_event()?
);
assert_eq!(
XmloEvent::PkgProgramFlag,
sut.read_event()?
);
}
// DONE
fn package_event_name(sut) {
sut.reader.next_event = Some(Box::new(|_, _| {
Ok(XmlEvent::Start(MockBytesStart::new(
b"package",
Some(MockAttributes::new(vec![
MockAttribute::new(b"name", b"pkg/name"),
MockAttribute::new(b"__rootpath", b"../../"),
])),
)))
}));
assert_eq!(
XmloEvent::PkgName("pkg/name".intern()),
sut.read_event()?
);
assert_eq!(
XmloEvent::PkgRootPath("../../".intern()),
sut.read_event()?
);
}
// DONE
fn sym_dep_event(sut) {
sut.reader.next_event = Some(Box::new(|_, event_i| match event_i {
0 => Ok(XmlEvent::Start(MockBytesStart::new(
b"preproc:sym-dep",
Some(MockAttributes::new(vec![MockAttribute::new(
b"name", b"depsym",
)])),
))),
1 => Ok(XmlEvent::Empty(MockBytesStart::new(
b"preproc:sym-ref",
Some(MockAttributes::new(vec![MockAttribute::new(
b"name", b"dep1",
)])),
))),
2 => Ok(XmlEvent::Empty(MockBytesStart::new(
b"preproc:sym-ref",
Some(MockAttributes::new(vec![MockAttribute::new(
b"name", b"dep2",
)])),
))),
3 => Ok(XmlEvent::End(MockBytesEnd::new(b"preproc:sym-dep"))),
_ => Err(InnerXmlError::UnexpectedEof(
format!("MockXmlReader out of events: {}", event_i).into(),
)),
}));
let result = sut.take(3).collect::<Result<Vec<_>, _>>()?;
assert_eq!(
vec![
XmloEvent::SymDepStart("depsym".intern(), UNKNOWN_SPAN),
XmloEvent::Symbol("dep1".intern(), UNKNOWN_SPAN),
XmloEvent::Symbol("dep2".intern(), UNKNOWN_SPAN),
],
result
);
}
// DONE
fn sym_dep_fails_with_missing_name(sut) {
sut.reader.next_event = Some(Box::new(|_, _| {
Ok(XmlEvent::Start(MockBytesStart::new(
b"preproc:sym-dep",
Some(MockAttributes::new(vec![])),
)))
}));
match sut.read_event() {
Err(XmloError::UnassociatedSymDep(_)) => (),
bad => panic!("expected XmloError: {:?}", bad),
}
}
// DONE
fn sym_dep_malformed_ref_missing_name(sut) {
sut.reader.next_event = Some(Box::new(|_, event_i| match event_i {
0 => Ok(XmlEvent::Start(MockBytesStart::new(
b"preproc:sym-dep",
Some(MockAttributes::new(vec![MockAttribute::new(
b"name", b"depsymbad",
)])),
))),
// no attributes
1 => Ok(XmlEvent::Empty(MockBytesStart::new(
b"preproc:sym-ref",
Some(MockAttributes::new(vec![])),
))),
_ => Err(InnerXmlError::UnexpectedEof(
format!("MockXmlReader out of events: {}", event_i).into(),
)),
}));
match sut.read_event() {
Err(XmloError::MalformedSymRef(name, _)) => {
assert_eq!(name, "depsymbad".into());
},
bad => panic!("expected XmloError: {:?}", bad),
}
}
// DONE (part of composite)
fn eoh_after_fragments(sut) {
sut.reader.next_event = Some(Box::new(|_, _| {
Ok(XmlEvent::End(MockBytesEnd::new(b"preproc:fragments")))
}));
let result = sut.read_event()?;
assert_eq!(XmloEvent::Eoh(UNKNOWN_SPAN), result);
}
// DONE
fn fragment_event(sut) {
let expected = "fragment text";
sut.reader.next_event = Some(Box::new(|_, event_i| match event_i {
0 => Ok(XmlEvent::Start(MockBytesStart::new(
b"preproc:fragment",
Some(MockAttributes::new(vec![MockAttribute::new(
b"id", b"fragsym",
)])),
))),
1 => Ok(XmlEvent::Text(MockBytesText::new(
b"fragment text"
))),
_ => Err(InnerXmlError::UnexpectedEof(
format!("MockXmlReader out of events: {}", event_i).into(),
)),
}));
let result = sut.read_event()?;
assert!(matches!(
result,
XmloEvent::Fragment(sym, given, _)
if sym == "fragsym".intern() && given.lookup_str() == expected
));
}
fn fragment_fails_with_missing_id(sut) {
sut.reader.next_event = Some(Box::new(|_, _| {
Ok(XmlEvent::Start(MockBytesStart::new(
b"preproc:fragment",
Some(MockAttributes::new(vec![])),
)))
}));
match sut.read_event() {
Err(XmloError::UnassociatedFragment(_)) => (),
bad => panic!("expected XmloError: {:?}", bad),
}
}
// DONE
// Yes, this happened.
fn fragment_fails_with_empty_id(sut) {
sut.reader.next_event = Some(Box::new(|_, _| {
Ok(XmlEvent::Start(MockBytesStart::new(
b"preproc:fragment",
Some(MockAttributes::new(vec![MockAttribute::new(
b"id", b"",
)])),
)))
}));
match sut.read_event() {
Err(XmloError::UnassociatedFragment(_)) => (),
bad => panic!("expected XmloError: {:?}", bad),
}
}
// DONE
fn fragment_fails_with_missing_text(sut) {
sut.reader.next_text = Some(Err(InnerXmlError::TextNotFound));
sut.reader.next_event = Some(Box::new(|_, _| {
Ok(XmlEvent::Start(MockBytesStart::new(
b"preproc:fragment",
Some(MockAttributes::new(vec![MockAttribute::new(
b"id", b"fragsym",
)])),
)))
}));
match sut.read_event() {
Err(XmloError::MissingFragmentText(symname, _)) => {
assert_eq!("fragsym".intern(), symname)
}
bad => panic!("expected XmloError: {:?}", bad),
}
}
fn skips_unneeded_nodes(sut) {
sut.reader.next_event = Some(Box::new(|_, event_i| match event_i {
// Skip over this
0 => Ok(XmlEvent::End(MockBytesEnd::new(
b"preproc:ignore-me",
))),
// And this
1 => Ok(XmlEvent::Start(MockBytesStart::new(
b"preproc:symtable",
Some(MockAttributes::new(vec![])),
))),
// But process this
2 => Ok(XmlEvent::Empty(MockBytesStart::new(
b"preproc:sym",
Some(MockAttributes::new(vec![MockAttribute::new(
b"name", b"sym-expected",
)])),
))),
_ => Err(InnerXmlError::UnexpectedEof(
format!("MockXmlReader out of events: {}", event_i).into(),
)),
}));
let result = sut.read_event()?;
assert_eq!(
XmloEvent::SymDecl(
"sym-expected".intern(),
SymAttrs {
pkg_name: Some("pkg/name".intern()),
..Default::default()
},
UNKNOWN_SPAN,
),
result
);
}
// Some preproc:sym nodes have children (`func` symbols,
// specifically) that we choose to ignore. See next test for
// data we do care about.
fn sym_nonempty_element(sut) {
sut.reader.next_event = Some(Box::new(|_, _| {
// Notice Start, not Empty
Ok(XmlEvent::Start(MockBytesStart::new(
b"preproc:sym",
Some(MockAttributes::new(vec![
MockAttribute::new(
b"name", b"sym-nonempty",
),
// Just to observe that processing works properly
MockAttribute::new(
b"dim", b"2",
),
])),
)))
}));
let result = sut.read_event()?;
assert_eq!(
XmloEvent::SymDecl(
"sym-nonempty".intern(),
SymAttrs {
dim: Some(Dim::Matrix),
pkg_name: Some("pkg/name".intern()),
..Default::default()
},
UNKNOWN_SPAN,
),
result
);
// Ensure that we have skipped the remainder of this element
// (all of its children) so that the next event will yield the
// next symbol.
assert_eq!(Some("preproc:sym".into()), sut.reader.read_to_end_name);
}
// DONE
// `map` symbols include information about their source
// fields.
fn sym_map_from(sut) {
sut.reader.next_event = Some(Box::new(|_, event_i| match event_i {
// Notice Start, not Empty
0 => Ok(XmlEvent::Start(MockBytesStart::new(
b"preproc:sym",
Some(MockAttributes::new(vec![
MockAttribute::new(
b"name", b"sym-map-from",
),
MockAttribute::new(
b"type", b"map",
),
])),
))),
// make sure that whitespace is permitted
1 => Ok(XmlEvent::Text(MockBytesText::new(
b" ",
))),
2 => Ok(XmlEvent::Empty(MockBytesStart::new(
b"preproc:from",
Some(MockAttributes::new(vec![
MockAttribute::new(
b"name", b"from-a",
),
])),
))),
3 => Ok(XmlEvent::End(MockBytesEnd::new(
b"preproc:sym",
))),
_ => Err(InnerXmlError::UnexpectedEof(
format!("MockXmlReader out of events: {}", event_i).into(),
)),
}));
let result = sut.read_event()?;
assert_eq!(
XmloEvent::SymDecl(
"sym-map-from".intern(),
SymAttrs {
ty: Some(SymType::Map),
from: Some(
"from-a".intern(),
),
pkg_name: Some("pkg/name".intern()),
..Default::default()
},
UNKNOWN_SPAN,
),
result
);
// Should _not_ have read to the end.
assert_eq!(None, sut.reader.read_to_end_name);
}
// DONE
fn sym_map_from_missing_name(sut) {
sut.reader.next_event = Some(Box::new(|_, event_i| match event_i {
// Notice Start, not Empty
0 => Ok(XmlEvent::Start(MockBytesStart::new(
b"preproc:sym",
Some(MockAttributes::new(vec![
MockAttribute::new(
b"name", b"sym-map-from-bad",
),
MockAttribute::new(
b"type", b"map",
),
])),
))),
// missing @name
1 => Ok(XmlEvent::Empty(MockBytesStart::new(
b"preproc:from",
Some(MockAttributes::new(vec![])),
))),
2 => Ok(XmlEvent::End(MockBytesEnd::new(
b"preproc:sym",
))),
_ => Err(InnerXmlError::UnexpectedEof(
format!("MockXmlReader out of events: {}", event_i).into(),
)),
}));
assert_eq!(
sut.read_event(),
Err(XmloError::MapFromNameMissing("sym-map-from-bad".into(), UNKNOWN_SPAN))
);
}
fn read_events_via_iterator(sut) {
sut.reader.next_event = Some(Box::new(|_, _| {
Ok(XmlEvent::Start(MockBytesStart::new(
b"package",
Some(MockAttributes::new(vec![
MockAttribute::new(b"name", b"pkg/name"),
])),
)))
}));
let result = sut.next().unwrap()?;
assert_eq!(
XmloEvent::PkgName("pkg/name".intern()),
result
);
}
}
macro_rules! sym_test_reader_event {
($sut:ident, $name:ident, $($key:ident=$val:literal),*) => {
// See xmlo_tests macro for explanation
$sut.seen_root = true;
$sut.reader.next_event = Some(Box::new(|_, event_i| {
match event_i {
0 => Ok(XmlEvent::Start(MockBytesStart::new(
b"package",
Some(MockAttributes::new(vec![
MockAttribute::new(b"name", b"pkg/name")
])),
))),
1 => Ok(XmlEvent::Empty(MockBytesStart::new(
b"preproc:sym",
Some(MockAttributes::new(
vec![
MockAttribute::new(
b"name",
stringify!($name).as_bytes(),
),
$(
MockAttribute::new(
stringify!($key).as_bytes(),
$val.as_bytes(),
),
)*
],
)),
))),
_ => Err(InnerXmlError::UnexpectedEof(
format!("MockXmlReader out of events: {}", event_i).into(),
)),
}
}));
// consume the package to set the name
let _ = $sut.read_event();
}
}
macro_rules! sym_tests {
($($name:ident: [$($key:ident=$val:literal),*] => $expect:expr)*) => {
$(
#[test]
fn $name() -> XmloResult<()> {
let stub_data: &[u8] = &[];
let mut sut = Sut::new(stub_data);
sym_test_reader_event!(sut, $name, $( $key=$val ),*);
let result = sut.read_event()?;
let mut expected_attrs = $expect;
expected_attrs.pkg_name = Some("pkg/name".intern());
assert_eq!(
XmloEvent::SymDecl(
stringify!($name).intern(),
expected_attrs,
UNKNOWN_SPAN,
),
result,
);
Ok(())
}
)*
}
}
// DONE
sym_tests! {
src: [src="foo/bar/baz"] => SymAttrs {
// see macro for src relpath
src: Some("foo/bar/baz".intern()),
..Default::default()
}
// note that this doesn't test every type; we're not going to
// duplicate the mapping for all of them here
tycgen: [type="cgen"] => SymAttrs {
ty: Some(SymType::Cgen),
..Default::default()
}
dim_0: [dim="0"] => SymAttrs {
dim: Some(Dim::Scalar),
..Default::default()
}
dim_1: [dim="1"] => SymAttrs {
dim: Some(Dim::Vector),
..Default::default()
}
dtyboolean: [dtype="boolean"] => SymAttrs {
dtype: Some(SymDtype::Boolean),
..Default::default()
}
dtyinteger: [dtype="integer"] => SymAttrs {
dtype: Some(SymDtype::Integer),
..Default::default()
}
dtyfloat: [dtype="float"] => SymAttrs {
dtype: Some(SymDtype::Float),
..Default::default()
}
dtyempty: [dtype="empty"] => SymAttrs {
dtype: Some(SymDtype::Empty),
..Default::default()
}
extern_true: [extern="true"] => SymAttrs {
extern_: true,
..Default::default()
}
// The compiler will never produce nonsense values, so we'll just
// provide a sane default rather than adding extra checks (and
// hopefully we don't regret this)
extern_crap: [extern="nonsense"] => SymAttrs {
extern_: false,
..Default::default()
}
parent: [parent="foo"] => SymAttrs {
parent: Some("foo".intern()),
..Default::default()
}
yields: [yields="yield"] => SymAttrs {
yields: Some("yield".intern()),
..Default::default()
}
desc: [desc="Description"] => SymAttrs {
desc: Some("Description".into()),
..Default::default()
}
r#virtual: [virtual="true"] => SymAttrs {
virtual_: true,
..Default::default()
}
r#override: [isoverride="true"] => SymAttrs {
override_: true,
..Default::default()
}
// Multiple attributes at once
multi: [src="foo", type="class", dim="1", dtype="float", extern="true"]
=> SymAttrs {
// see macro for src relpath
src: Some("foo".intern()),
ty: Some(SymType::Class),
dim: Some(Dim::Vector),
dtype: Some(SymDtype::Float),
extern_: true,
..Default::default()
}
}
// DONE
// can't be tested using the above
#[test]
fn generated_true() -> XmloResult<()> {
let stub_data: &[u8] = &[];
let mut sut = Sut::new(stub_data);
// See xmlo_tests macro for explanation
sut.seen_root = true;
sut.pkg_name = Some("pkg/name".intern());
sut.reader.next_event = Some(Box::new(|_, _| {
Ok(XmlEvent::Empty(MockBytesStart::new(
b"preproc:sym",
Some(MockAttributes::new(vec![
MockAttribute::new(b"name", b"generated_true"),
MockAttribute::new(b"preproc:generated", b"true"),
])),
)))
}));
let result = sut.read_event()?;
let expected_attrs = SymAttrs {
generated: true,
pkg_name: Some("pkg/name".intern()),
..Default::default()
};
assert_eq!(
XmloEvent::SymDecl(
"generated_true".intern(),
expected_attrs,
UNKNOWN_SPAN,
),
result
);
Ok(())
}
// DONE
#[test]
fn fails_on_non_ascii_dim() {
let stub_data: &[u8] = &[];
let mut sut = Sut::new(stub_data);
sym_test_reader_event!(sut, fail_sym, dim = "X1");
match sut.read_event() {
Err(XmloError::InvalidDim(dim, _)) => assert_eq!(dim, "X1".intern()),
bad => panic!("expected failure: {:?}", bad),
}
}
// DONE
#[test]
fn fails_on_multi_char_dim() {
let stub_data: &[u8] = &[];
let mut sut = Sut::new(stub_data);
sym_test_reader_event!(sut, fail_sym, dim = "11");
match sut.read_event() {
Err(XmloError::InvalidDim(dim, _)) => assert_eq!(dim, "11".intern()),
bad => panic!("expected failure: {:?}", bad),
}
}
// DONE
#[test]
fn fails_on_invalid_type() {
let stub_data: &[u8] = &[];
let mut sut = Sut::new(stub_data);
sym_test_reader_event!(sut, fail_sym, type = "foo");
match sut.read_event() {
Err(XmloError::InvalidType(ty, _)) => assert_eq!(ty, "foo".into()),
bad => panic!("expected failure: {:?}", bad),
}
}
// DONE
#[test]
fn fails_on_invalid_dtype() {
let stub_data: &[u8] = &[];
let mut sut = Sut::new(stub_data);
sym_test_reader_event!(sut, fail_sym, dtype = "foo");
match sut.read_event() {
Err(XmloError::InvalidDtype(dty, _)) => assert_eq!(dty, "foo".into()),
bad => panic!("expected failure: {:?}", bad),
}
}

View File

@ -39,7 +39,7 @@ const S3: Span = S2.offset_add(1).unwrap();
const S4: Span = S3.offset_add(1).unwrap();
const S5: Span = S4.offset_add(1).unwrap();
type Sut = XmloReaderState;
type Sut = XmloReader;
#[test]
fn fails_on_invalid_root() {

View File

@ -1,28 +0,0 @@
// Wrappers around third-party modules
//
// Copyright (C) 2014-2021 Ryan Specialty Group, LLC.
//
// This file is part of TAME.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! Wrappers around third-party modules to make them play nicely with TAMER.
//!
//! Some third-party libraries provide interfaces that present integration issues.
//! Those are often addressed in the context that they are used,
//! but sometimes those adapters need to be shared across different parts
//! of the system.
//! They live here.
pub mod quick_xml;

View File

@ -1,67 +0,0 @@
// `quick-xml` wrappers for TAME.
//
// Copyright (C) 2014-2021 Ryan Specialty Group, LLC.
//
// This file is part of TAME.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use std::fmt::Display;
/// Wrapped error type.
pub type InnerXmlError = quick_xml::Error;
/// Thin wrapper around [`quick_xml::Error`] to implement [`PartialEq`].
///
/// This will always yield `false`,
/// but allows us to derive the trait on types using [`Error`];
/// otherwise, this madness propagates indefinitely.
#[derive(Debug)]
pub struct Error(pub InnerXmlError);
impl PartialEq for Error {
/// [`quick_xml::Error`] does not implement [`PartialEq`] and so this
/// will always yield `false`.
fn eq(&self, _other: &Self) -> bool {
false
}
}
impl Eq for Error {}
impl From<InnerXmlError> for Error {
fn from(e: InnerXmlError) -> Self {
Self(e)
}
}
impl Into<InnerXmlError> for Error {
fn into(self) -> InnerXmlError {
self.0
}
}
impl Display for Error {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
// NB: If we eventually use `source` to display a hierarchy of
// errors, then we likely do not want the duplication here.
self.0.fmt(fmt)
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.0)
}
}

View File

@ -20,7 +20,7 @@
//! XIR error information.
use super::QName;
use crate::{span::Span, sym::SymbolId, tpwrap::quick_xml};
use crate::{span::Span, sym::SymbolId};
use std::{fmt::Display, str::Utf8Error};
/// Error attempting to produce a XIR object.
@ -63,7 +63,7 @@ pub enum Error {
AttrValueUnquoted(Option<QName>, Span),
// TODO: Better error translation.
QuickXmlError(quick_xml::Error, Span),
QuickXmlError(QuickXmlError, Span),
}
impl Error {
@ -164,7 +164,7 @@ pub enum SpanlessError {
NCColon(SymbolId),
InvalidQName(SymbolId),
InvalidUtf8(Utf8Error, Vec<u8>),
QuickXmlError(quick_xml::Error),
QuickXmlError(QuickXmlError),
}
impl SpanlessError {
@ -210,8 +210,52 @@ impl From<(Utf8Error, &[u8])> for SpanlessError {
}
}
impl<E: Into<quick_xml::Error>> From<E> for SpanlessError {
impl<E: Into<QuickXmlError>> From<E> for SpanlessError {
fn from(err: E) -> Self {
Self::QuickXmlError(err.into())
}
}
/// Thin wrapper around [`quick_xml::Error`] to implement [`PartialEq`].
///
/// This will always yield `false`,
/// but allows us to derive the trait on types using [`Error`];
/// otherwise, this madness propagates indefinitely.
#[derive(Debug)]
pub struct QuickXmlError(pub quick_xml::Error);
impl PartialEq for QuickXmlError {
/// [`quick_xml::Error`] does not implement [`PartialEq`] and so this
/// will always yield `false`.
fn eq(&self, _other: &Self) -> bool {
false
}
}
impl Eq for QuickXmlError {}
impl From<quick_xml::Error> for QuickXmlError {
fn from(e: quick_xml::Error) -> Self {
Self(e)
}
}
impl Into<quick_xml::Error> for QuickXmlError {
fn into(self) -> quick_xml::Error {
self.0
}
}
impl Display for QuickXmlError {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
// NB: If we eventually use `source` to display a hierarchy of
// errors, then we likely do not want the duplication here.
self.0.fmt(fmt)
}
}
impl std::error::Error for QuickXmlError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.0)
}
}

View File

@ -204,8 +204,6 @@ pub struct CachingEscaper<S: Escaper> {
}
impl<S: Escaper> CachingEscaper<S> {
// TODO: remove allow along with wip-xmlo-xir-reader
#[allow(dead_code)]
pub fn new(inner: S) -> Self {
// We know we'll encounter more than the statically allocated
// symbols,
@ -226,8 +224,6 @@ impl<S: Escaper> CachingEscaper<S> {
}
}
// TODO: remove allow along with wip-xmlo-xir-reader
#[allow(dead_code)]
pub fn into_inner(self) -> S {
self.inner
}
@ -278,49 +274,13 @@ impl<S: Escaper> Escaper for CachingEscaper<S> {
}
}
/// Perform no escaping or unescaping.
///
/// _This should be removed after development of the XIR-based readers!_
#[cfg(not(feature = "wip-xmlo-xir-reader"))]
#[derive(Debug, Clone, Copy, Default)]
pub struct NullEscaper {}
#[cfg(not(feature = "wip-xmlo-xir-reader"))]
impl Escaper for NullEscaper {
#[inline]
fn escape_bytes(value: &[u8]) -> Cow<[u8]> {
Cow::Borrowed(value)
}
#[inline]
fn unescape_bytes(_value: &[u8]) -> Result<Cow<[u8]>, SpanlessError> {
panic!("NullEscaper should not be used for unescaping")
}
}
#[cfg(feature = "wip-xmlo-xir-reader")]
pub type DefaultEscaper = CachingEscaper<QuickXmlEscaper>;
#[cfg(not(feature = "wip-xmlo-xir-reader"))]
pub type DefaultEscaper = NullEscaper;
#[cfg(test)]
mod test {
use super::*;
use crate::sym::GlobalSymbolIntern;
// Simple sanity check to ensure that the default escaper actually does
// some sort of escaping.
#[cfg(feature = "wip-xmlo-xir-reader")]
#[test]
fn default_escaper_escapes() {
let sut = DefaultEscaper::default();
assert_eq!(
"foo&lt;bar".intern(),
sut.escape("foo<bar".intern()).into(),
);
}
mod cache {
use super::*;
use std::{collections::HashMap, result};