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-10935main
parent
4c69efd175
commit
cfc7f45bc4
|
@ -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 = []
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -88,7 +88,6 @@ pub mod obj;
|
|||
pub mod parse;
|
||||
pub mod span;
|
||||
pub mod sym;
|
||||
pub mod tpwrap;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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, ..)) => {
|
||||
|
|
|
@ -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;
|
|
@ -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 {
|
||||