Mike Gerwitz

Activist for User Freedom

aboutsummaryrefslogtreecommitdiffstats
path: root/tamer/src
diff options
context:
space:
mode:
Diffstat (limited to 'tamer/src')
-rw-r--r--tamer/src/fs.rs268
-rw-r--r--tamer/src/ir/asg/base.rs119
-rw-r--r--tamer/src/ir/asg/graph.rs36
-rw-r--r--tamer/src/ir/asg/ident.rs59
-rw-r--r--tamer/src/ir/asg/mod.rs4
-rw-r--r--tamer/src/ir/mod.rs11
-rw-r--r--tamer/src/ld/poc.rs201
-rw-r--r--tamer/src/lib.rs1
-rw-r--r--tamer/src/obj/xmlo/asg_builder.rs865
-rw-r--r--tamer/src/obj/xmlo/mod.rs17
-rw-r--r--tamer/src/obj/xmlo/reader.rs102
11 files changed, 1405 insertions, 278 deletions
diff --git a/tamer/src/fs.rs b/tamer/src/fs.rs
new file mode 100644
index 0000000..fa512c1
--- /dev/null
+++ b/tamer/src/fs.rs
@@ -0,0 +1,268 @@
+// Light filesystem abstractions
+//
+// Copyright (C) 2014-2020 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/>.
+
+//! Lightweight filesystem abstraction.
+//!
+//! This abstraction is intended to provide generics missing from Rust core,
+//! but makes no attempt to be comprehensive---it
+//! includes only what is needed for TAMER.
+//!
+//! - [`File`] provides a trait for operating on files; and
+//! - [`Filesystem`] provides a generic way to access files by path.
+//!
+//! This implements traits directly atop of Rust's core structs where
+//! possible.
+//!
+//!
+//! Visiting Files Once
+//! ===================
+//! [`VisitOnceFilesystem`] produces [`VisitOnceFile::FirstVisit`] the first
+//! time it encounters a given path,
+//! and [`VisitOnceFile::Visited`] every time thereafter.
+
+use std::collections::hash_map::RandomState;
+use std::collections::HashSet;
+use std::ffi::OsString;
+use std::fs;
+use std::hash::BuildHasher;
+use std::io::{BufReader, Read, Result};
+use std::marker::PhantomData;
+use std::path::{Path, PathBuf};
+
+/// A file.
+pub trait File
+where
+ Self: Sized,
+{
+ fn open<P: AsRef<Path>>(path: P) -> Result<Self>;
+}
+
+impl File for fs::File {
+ fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
+ Self::open(path)
+ }
+}
+
+impl<F: File + Read> File for BufReader<F> {
+ /// Open the file at `path` and construct a [`BufReader`] from it.
+ fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
+ Ok(BufReader::new(F::open(path)?))
+ }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct PathFile<F: File>(PathBuf, F);
+
+impl<F: File> Into<(PathBuf, F)> for PathFile<F> {
+ fn into(self) -> (PathBuf, F) {
+ (self.0, self.1)
+ }
+}
+
+impl<F: File> File for PathFile<F> {
+ fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
+ let buf = path.as_ref().to_path_buf();
+ let file = F::open(&buf)?;
+
+ Ok(Self(buf, file))
+ }
+}
+
+/// A filesystem.
+///
+/// Opening a file (using [`open`](Filesystem::open)) proxies to `F::open`.
+/// The type of files opened by this abstraction can therefore be controlled
+/// via generics.
+pub trait Filesystem<F: File>
+where
+ Self: Sized,
+{
+ fn open<P: AsRef<Path>>(&mut self, path: P) -> Result<F> {
+ F::open(path)
+ }
+}
+
+/// A potentially visited [`File`].
+///
+/// See [`VisitOnceFilesystem`] for more information.
+#[derive(Debug, PartialEq)]
+pub enum VisitOnceFile<F: File> {
+ /// First time visiting file at requested path.
+ FirstVisit(F),
+
+ /// Requested path has already been visited.
+ Visited,
+}
+
+impl<F: File> File for VisitOnceFile<F> {
+ fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
+ F::open(path).map(|file| Self::FirstVisit(file))
+ }
+}
+
+/// Opens each path only once.
+///
+/// When a [`File`] is first opened,
+/// it will be wrapped in [`VisitOnceFile::FirstVisit`]
+/// Subsequent calls to `open` will yield
+/// [`VisitOnceFile::Visited`] without attempting to open the file.
+///
+/// A file will not be marked as visited if it fails to be opened.
+pub struct VisitOnceFilesystem<C, S = RandomState>
+where
+ C: Canonicalizer,
+ S: BuildHasher,
+{
+ visited: HashSet<OsString, S>,
+ _c: PhantomData<C>,
+}
+
+impl<C, S> VisitOnceFilesystem<C, S>
+where
+ C: Canonicalizer,
+ S: BuildHasher + Default,
+{
+ /// New filesystem with no recorded paths.
+ pub fn new() -> Self {
+ Self {
+ visited: Default::default(),
+ _c: PhantomData,
+ }
+ }
+
+ /// Number of visited paths.
+ pub fn visit_len(&self) -> usize {
+ self.visited.len()
+ }
+}
+
+impl<C, S, F> Filesystem<VisitOnceFile<F>> for VisitOnceFilesystem<C, S>
+where
+ C: Canonicalizer,
+ S: BuildHasher,
+ F: File,
+{
+ /// Open a file, marking `path` as visited.
+ ///
+ /// The next time the same path is requested,
+ /// [`VisitOnceFile::Visited`] will be returned.
+ ///
+ /// `path` will not be marked as visited if opening fails.
+ fn open<P: AsRef<Path>>(&mut self, path: P) -> Result<VisitOnceFile<F>> {
+ let cpath = C::canonicalize(path)?;
+ let ostr = cpath.as_os_str();
+
+ if self.visited.contains(ostr) {
+ return Ok(VisitOnceFile::Visited);
+ }
+
+ VisitOnceFile::open(ostr).and_then(|file| {
+ self.visited.insert(ostr.to_os_string());
+ Ok(file)
+ })
+ }
+}
+
+pub trait Canonicalizer {
+ fn canonicalize<P: AsRef<Path>>(path: P) -> Result<PathBuf>;
+}
+
+pub struct FsCanonicalizer;
+
+impl Canonicalizer for FsCanonicalizer {
+ fn canonicalize<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
+ std::fs::canonicalize(path)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use std::path::PathBuf;
+
+ #[derive(Debug, PartialEq)]
+ struct DummyFile(PathBuf);
+
+ impl File for DummyFile {
+ fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
+ Ok(Self(path.as_ref().to_path_buf()))
+ }
+ }
+
+ impl Read for DummyFile {
+ fn read(&mut self, _buf: &mut [u8]) -> Result<usize> {
+ Ok(0)
+ }
+ }
+
+ #[test]
+ fn buf_reader_file() {
+ let path: PathBuf = "buf/path".into();
+ let result: BufReader<DummyFile> = File::open(path.clone()).unwrap();
+
+ assert_eq!(DummyFile(path), result.into_inner());
+ }
+
+ #[test]
+ fn path_file() {
+ let path: PathBuf = "buf/path".into();
+ let result: PathFile<DummyFile> = File::open(path.clone()).unwrap();
+
+ assert_eq!(PathFile(path.clone(), DummyFile(path.clone())), result);
+
+ // Tuple conversion.
+ assert_eq!((path.clone(), DummyFile(path.clone())), result.into());
+ }
+
+ mod canonicalizer {
+ use super::*;
+
+ struct StubCanonicalizer;
+
+ impl Canonicalizer for StubCanonicalizer {
+ fn canonicalize<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
+ let mut buf = path.as_ref().to_path_buf();
+ buf.push("CANONICALIZED");
+
+ Ok(buf)
+ }
+ }
+
+ #[test]
+ fn vist_once() {
+ let mut fs =
+ VisitOnceFilesystem::<StubCanonicalizer, RandomState>::new();
+ let path: PathBuf = "foo/bar".into();
+ let result = fs.open(path.clone()).unwrap();
+
+ let mut expected_path = path.clone().to_path_buf();
+ expected_path.push("CANONICALIZED");
+
+ // First time, return file.
+ assert_eq!(
+ VisitOnceFile::FirstVisit(DummyFile(expected_path)),
+ result
+ );
+
+ // Second time, already visited.
+ let result_2: VisitOnceFile<DummyFile> = fs.open(path).unwrap();
+ assert_eq!(VisitOnceFile::Visited, result_2);
+ }
+ }
+}
diff --git a/tamer/src/ir/asg/base.rs b/tamer/src/ir/asg/base.rs
index 637c892..6dc690c 100644
--- a/tamer/src/ir/asg/base.rs
+++ b/tamer/src/ir/asg/base.rs
@@ -20,7 +20,7 @@
//! Base concrete [`Asg`] implementation.
use super::graph::{
- Asg, AsgEdge, AsgError, AsgResult, Node, ObjectRef, SortableAsg,
+ Asg, AsgEdge, AsgError, AsgResult, IndexType, Node, ObjectRef, SortableAsg,
};
use super::ident::IdentKind;
use super::object::{
@@ -28,11 +28,8 @@ use super::object::{
};
use super::Sections;
use crate::sym::Symbol;
-use fixedbitset::FixedBitSet;
-use petgraph::graph::{
- DiGraph, EdgeIndex, Graph, IndexType, Neighbors, NodeIndex,
-};
-use petgraph::visit::{DfsPostOrder, GraphBase, IntoNeighbors, Visitable};
+use petgraph::graph::{DiGraph, Graph, NodeIndex};
+use petgraph::visit::DfsPostOrder;
/// Concrete ASG.
///
@@ -68,6 +65,13 @@ where
Ix: IndexType,
O: IdentObjectState<'i, O> + IdentObjectData<'i>,
{
+ /// Create a new ASG.
+ ///
+ /// See also [`with_capacity`](BaseAsg::with_capacity).
+ pub fn new() -> Self {
+ Self::with_capacity(0, 0)
+ }
+
/// Create an ASG with the provided initial capacity.
///
/// The value for `objects` will be used as the capacity for the nodes
@@ -78,10 +82,6 @@ where
/// different types of objects,
/// but it's safe to say that each object will have at least one
/// edge to another object.
- ///
- /// A basic `new` method is not provided to ensure that callers consider
- /// capacity during construction,
- /// since graphs can get quite large.
pub fn with_capacity(objects: usize, edges: usize) -> Self {
let mut graph = Graph::with_capacity(objects, edges);
let mut index = Vec::with_capacity(objects);
@@ -136,7 +136,7 @@ where
let index = self.graph.add_node(Some(O::declare(ident)));
self.index_identifier(ident, index);
- ObjectRef(index)
+ ObjectRef::new(index)
})
}
@@ -176,7 +176,7 @@ where
where
F: FnOnce(O) -> TransitionResult<O>,
{
- let node = self.graph.node_weight_mut(identi.0).unwrap();
+ let node = self.graph.node_weight_mut(identi.into()).unwrap();
let obj = node
.take()
@@ -277,7 +277,7 @@ where
#[inline]
fn get<I: Into<ObjectRef<Ix>>>(&self, index: I) -> Option<&O> {
- self.graph.node_weight(index.into().0).map(|node| {
+ self.graph.node_weight(index.into().into()).map(|node| {
node.as_ref()
.expect("internal error: BaseAsg::get missing Node data")
})
@@ -290,16 +290,17 @@ where
self.index
.get(i)
.filter(|ni| ni.index() > 0)
- .map(|ni| ObjectRef(*ni))
+ .map(|ni| ObjectRef::new(*ni))
}
fn add_dep(&mut self, identi: ObjectRef<Ix>, depi: ObjectRef<Ix>) {
- self.graph.update_edge(identi.0, depi.0, Default::default());
+ self.graph
+ .update_edge(identi.into(), depi.into(), Default::default());
}
#[inline]
fn has_dep(&self, ident: ObjectRef<Ix>, dep: ObjectRef<Ix>) -> bool {
- self.graph.contains_edge(ident.0, dep.0)
+ self.graph.contains_edge(ident.into(), dep.into())
}
fn add_dep_lookup(
@@ -310,7 +311,8 @@ where
let identi = self.lookup_or_missing(ident);
let depi = self.lookup_or_missing(dep);
- self.graph.update_edge(identi.0, depi.0, Default::default());
+ self.graph
+ .update_edge(identi.into(), depi.into(), Default::default());
(identi, depi)
}
@@ -367,41 +369,6 @@ where
}
}
-// TODO: encapsulate Petgraph API (N.B. this is untested!)
-impl<'i, O, Ix> Visitable for BaseAsg<O, Ix>
-where
- Ix: IndexType,
-{
- type Map = FixedBitSet;
-
- fn visit_map(&self) -> Self::Map {
- self.graph.visit_map()
- }
-
- fn reset_map(&self, map: &mut Self::Map) {
- self.graph.reset_map(map)
- }
-}
-
-impl<'i, O, Ix> GraphBase for BaseAsg<O, Ix>
-where
- Ix: IndexType,
-{
- type NodeId = NodeIndex<Ix>;
- type EdgeId = EdgeIndex<Ix>;
-}
-
-impl<'a, 'i, O, Ix> IntoNeighbors for &'a BaseAsg<O, Ix>
-where
- Ix: IndexType,
-{
- type Neighbors = Neighbors<'a, AsgEdge, Ix>;
-
- fn neighbors(self, n: Self::NodeId) -> Self::Neighbors {
- self.graph.neighbors(n)
- }
-}
-
#[cfg(test)]
mod test {
use super::super::graph::AsgError;
@@ -513,7 +480,7 @@ mod test {
#[test]
fn declare_new_unique_idents() -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
+ let mut sut = Sut::new();
// NB: The index ordering is important! We first use a larger
// index to create a gap, and then use an index within that gap
@@ -571,7 +538,7 @@ mod test {
#[test]
fn lookup_by_symbol() -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
+ let mut sut = Sut::new();
let sym = symbol_dummy!(1, "lookup");
let node = sut.declare(
@@ -590,7 +557,7 @@ mod test {
#[test]
fn declare_returns_existing() -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
+ let mut sut = Sut::new();
let sym = symbol_dummy!(1, "symdup");
let src = Source::default();
@@ -616,7 +583,7 @@ mod test {
// Builds upon declare_returns_existing.
#[test]
fn declare_fails_if_transition_fails() -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
+ let mut sut = Sut::new();
let sym = symbol_dummy!(1, "symdup");
let src = Source {
@@ -652,7 +619,7 @@ mod test {
#[test]
fn declare_extern_returns_existing() -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
+ let mut sut = Sut::new();
let sym = symbol_dummy!(1, "symext");
let src = Source::default();
@@ -678,7 +645,7 @@ mod test {
// Builds upon declare_returns_existing.
#[test]
fn declare_extern_fails_if_transition_fails() -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
+ let mut sut = Sut::new();
let sym = symbol_dummy!(1, "symdup");
let src = Source {
@@ -718,7 +685,7 @@ mod test {
#[test]
fn add_fragment_to_ident() -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
+ let mut sut = Sut::new();
let sym = symbol_dummy!(1, "tofrag");
let src = Source {
@@ -748,7 +715,7 @@ mod test {
#[test]
fn add_fragment_to_ident_fails_if_transition_fails() -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
+ let mut sut = Sut::new();
let sym = symbol_dummy!(1, "failfrag");
let src = Source {
@@ -782,7 +749,7 @@ mod test {
#[test]
fn add_ident_dep_to_ident() -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
+ let mut sut = Sut::new();
let sym = symbol_dummy!(1, "sym");
let dep = symbol_dummy!(2, "dep");
@@ -803,7 +770,7 @@ mod test {
// same as above test
#[test]
fn add_dep_lookup_existing() -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
+ let mut sut = Sut::new();
let sym = symbol_dummy!(1, "sym");
let dep = symbol_dummy!(2, "dep");
@@ -819,7 +786,7 @@ mod test {
#[test]
fn add_dep_lookup_missing() -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
+ let mut sut = Sut::new();
let sym = symbol_dummy!(1, "sym");
let dep = symbol_dummy!(2, "dep");
@@ -836,7 +803,7 @@ mod test {
#[test]
fn declare_return_missing_symbol() -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
+ let mut sut = Sut::new();
let sym = symbol_dummy!(1, "sym");
let dep = symbol_dummy!(2, "dep");
@@ -893,7 +860,7 @@ mod test {
#[test]
fn graph_sort() -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
+ let mut sut = Sut::new();
let mut meta = vec![];
let mut worksheet = vec![];
@@ -937,7 +904,7 @@ mod test {
#[test]
fn graph_sort_missing_node() -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
+ let mut sut = Sut::new();
let sym = symbol_dummy!(1, "sym");
let dep = symbol_dummy!(2, "dep");
@@ -968,7 +935,7 @@ mod test {
#[test]
fn graph_sort_no_roots() -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
+ let mut sut = Sut::new();
let sym = symbol_dummy!(1, "sym");
let dep = symbol_dummy!(2, "dep");
@@ -984,7 +951,7 @@ mod test {
#[test]
fn graph_sort_simple_cycle() -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
+ let mut sut = Sut::new();
let sym = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym");
let dep = Symbol::new_dummy(SymbolIndex::from_u32(3), "dep");
@@ -1028,7 +995,7 @@ mod test {
#[test]
fn graph_sort_two_simple_cycles() -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
+ let mut sut = Sut::new();
let sym = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym");
let sym2 = Symbol::new_dummy(SymbolIndex::from_u32(3), "sym2");
@@ -1098,7 +1065,7 @@ mod test {
#[test]
fn graph_sort_no_cycle_with_edge_to_same_node() -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
+ let mut sut = Sut::new();
let sym = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym");
let dep = Symbol::new_dummy(SymbolIndex::from_u32(3), "dep");
@@ -1139,7 +1106,7 @@ mod test {
#[test]
fn graph_sort_cycle_with_a_few_steps() -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
+ let mut sut = Sut::new();
let sym1 = Symbol::new_dummy(SymbolIndex::from_u32(1), "sym1");
let sym2 = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym2");
@@ -1196,7 +1163,7 @@ mod test {
#[test]
fn graph_sort_cyclic_function_with_non_function_with_a_few_steps(
) -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
+ let mut sut = Sut::new();
let sym1 = Symbol::new_dummy(SymbolIndex::from_u32(1), "sym1");
let sym2 = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym2");
@@ -1252,7 +1219,7 @@ mod test {
#[test]
fn graph_sort_cyclic_bookended_by_functions() -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
+ let mut sut = Sut::new();
let sym1 = Symbol::new_dummy(SymbolIndex::from_u32(1), "sym1");
let sym2 = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym2");
@@ -1308,7 +1275,7 @@ mod test {
#[test]
fn graph_sort_cyclic_function_ignored() -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
+ let mut sut = Sut::new();
let sym = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym");
let dep = Symbol::new_dummy(SymbolIndex::from_u32(3), "dep");
@@ -1349,7 +1316,7 @@ mod test {
#[test]
fn graph_sort_cyclic_function_is_bookended() -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
+ let mut sut = Sut::new();
let sym1 = Symbol::new_dummy(SymbolIndex::from_u32(1), "sym1");
let sym2 = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym2");
@@ -1405,7 +1372,7 @@ mod test {
#[test]
fn graph_sort_ignore_non_linked() -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
+ let mut sut = Sut::new();
let sym = Symbol::new_dummy(SymbolIndex::from_u32(2), "sym");
let dep = Symbol::new_dummy(SymbolIndex::from_u32(3), "dep");
diff --git a/tamer/src/ir/asg/graph.rs b/tamer/src/ir/asg/graph.rs
index 52078ed..96afc3b 100644
--- a/tamer/src/ir/asg/graph.rs
+++ b/tamer/src/ir/asg/graph.rs
@@ -25,10 +25,14 @@ use super::object::{
};
use super::Sections;
use crate::sym::Symbol;
-use petgraph::graph::{IndexType, NodeIndex};
+use petgraph::graph::NodeIndex;
use std::fmt::Debug;
use std::result::Result;
+/// Datatype representing node and edge indexes.
+pub trait IndexType: petgraph::graph::IndexType {}
+impl<T: petgraph::graph::IndexType> IndexType for T {}
+
/// An abstract semantic graph of [objects][super::object].
///
/// This IR focuses on the definition and manipulation of objects and their
@@ -200,21 +204,21 @@ pub type AsgResult<T, Ix> = Result<T, AsgError<Ix>>;
/// not pointers.
/// See the [module-level documentation][self] for more information.
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
-pub struct ObjectRef<Ix>(pub NodeIndex<Ix>);
+pub struct ObjectRef<Ix>(NodeIndex<Ix>);
-impl<Ix> From<NodeIndex<Ix>> for ObjectRef<Ix>
-where
- Ix: IndexType,
-{
+impl<Ix: IndexType> ObjectRef<Ix> {
+ pub fn new(index: NodeIndex<Ix>) -> Self {
+ Self(index)
+ }
+}
+
+impl<Ix: IndexType> From<NodeIndex<Ix>> for ObjectRef<Ix> {
fn from(index: NodeIndex<Ix>) -> Self {
Self(index)
}
}
-impl<Ix> From<ObjectRef<Ix>> for NodeIndex<Ix>
-where
- Ix: IndexType,
-{
+impl<Ix: IndexType> From<ObjectRef<Ix>> for NodeIndex<Ix> {
fn from(objref: ObjectRef<Ix>) -> Self {
objref.0
}
@@ -236,7 +240,7 @@ pub type Node<O> = Option<O>;
/// so this stores only owned values.
/// The caller will know the problem values.
#[derive(Debug, PartialEq)]
-pub enum AsgError<Ix: Debug> {
+pub enum AsgError<Ix: IndexType> {
/// An object could not change state in the manner requested.
///
/// See [`Asg::declare`] and [`Asg::set_fragment`] for more
@@ -251,21 +255,19 @@ pub enum AsgError<Ix: Debug> {
Cycles(Vec<Vec<ObjectRef<Ix>>>),
}
-impl<Ix: Debug> std::fmt::Display for AsgError<Ix> {
+impl<Ix: IndexType> std::fmt::Display for AsgError<Ix> {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::ObjectTransition(err) => std::fmt::Display::fmt(&err, fmt),
Self::UnexpectedNode(msg) => {
write!(fmt, "unexpected node: {}", msg)
}
- Self::Cycles(cycles) => {
- write!(fmt, "Cyclic dependencies detected: {:?}", cycles)
- }
+ Self::Cycles(_) => write!(fmt, "cyclic dependencies"),
}
}
}
-impl<Ix: Debug> std::error::Error for AsgError<Ix> {
+impl<Ix: IndexType> std::error::Error for AsgError<Ix> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::ObjectTransition(err) => err.source(),
@@ -274,7 +276,7 @@ impl<Ix: Debug> std::error::Error for AsgError<Ix> {
}
}
-impl<Ix: Debug> From<TransitionError> for AsgError<Ix> {
+impl<Ix: IndexType> From<TransitionError> for AsgError<Ix> {
fn from(err: TransitionError) -> Self {
Self::ObjectTransition(err)
}
diff --git a/tamer/src/ir/asg/ident.rs b/tamer/src/ir/asg/ident.rs
index b5ef44b..d0841d1 100644
--- a/tamer/src/ir/asg/ident.rs
+++ b/tamer/src/ir/asg/ident.rs
@@ -21,6 +21,7 @@
use crate::ir::legacyir::{SymAttrs, SymDtype, SymType};
use std::convert::TryFrom;
+use std::error::Error;
/// Types of identifiers.
///
@@ -181,7 +182,7 @@ impl std::fmt::Display for IdentKind {
}
impl<'i> TryFrom<SymAttrs<'i>> for IdentKind {
- type Error = &'static str;
+ type Error = IdentKindError;
/// Attempt to raise [`SymAttrs`] into an [`IdentKind`].
///
@@ -193,29 +194,29 @@ impl<'i> TryFrom<SymAttrs<'i>> for IdentKind {
}
impl<'i> TryFrom<&SymAttrs<'i>> for IdentKind {
- type Error = &'static str;
+ type Error = IdentKindError;
/// Attempt to raise [`SymAttrs`] into an [`IdentKind`].
///
/// Certain [`IdentKind`] require that certain attributes be present,
/// otherwise the conversion will fail.
fn try_from(attrs: &SymAttrs<'i>) -> Result<Self, Self::Error> {
- let ty = attrs.ty.as_ref().ok_or("missing symbol type")?;
+ let ty = attrs.ty.as_ref().ok_or(Self::Error::MissingType)?;
macro_rules! ident {
($to:expr) => {
Ok($to)
};
($to:expr, dim) => {
- Ok($to(Dim(attrs.dim.ok_or("missing dim")?)))
+ Ok($to(Dim(attrs.dim.ok_or(Self::Error::MissingDim)?)))
};
($to:expr, dtype) => {
- Ok($to(attrs.dtype.ok_or("missing dtype")?))
+ Ok($to(attrs.dtype.ok_or(Self::Error::MissingDtype)?))
};
($to:expr, dim, dtype) => {
Ok($to(
- Dim(attrs.dim.ok_or("missing dim")?),
- attrs.dtype.ok_or("missing dtype")?,
+ Dim(attrs.dim.ok_or(Self::Error::MissingDim)?),
+ attrs.dtype.ok_or(Self::Error::MissingDtype)?,
))
};
}
@@ -243,6 +244,34 @@ impl<'i> TryFrom<&SymAttrs<'i>> for IdentKind {
}
}
+#[derive(Debug, PartialEq, Eq)]
+pub enum IdentKindError {
+ /// Symbol type was not provided.
+ MissingType,
+
+ /// Number of symbol dimensions were not provided.
+ MissingDim,
+
+ /// Symbol dtype was not provided.
+ MissingDtype,
+}
+
+impl std::fmt::Display for IdentKindError {
+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
+ match self {
+ Self::MissingType => write!(fmt, "missing symbol type"),
+ Self::MissingDim => write!(fmt, "missing dim"),
+ Self::MissingDtype => write!(fmt, "missing dtype"),
+ }
+ }
+}
+
+impl Error for IdentKindError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ None
+ }
+}
+
/// Identifier dimensions.
///
/// This determines the number of subscripts needed to access a scalar
@@ -345,11 +374,13 @@ mod test {
);
// no dim
- IdentKind::try_from(SymAttrs {
+ let result = IdentKind::try_from(SymAttrs {
ty: Some($src),
..Default::default()
})
.expect_err("must fail when missing dim");
+
+ assert_eq!(IdentKindError::MissingDim, result);
}
};
@@ -369,11 +400,13 @@ mod test {
);
// no dtype
- IdentKind::try_from(SymAttrs {
+ let result = IdentKind::try_from(SymAttrs {
ty: Some($src),
..Default::default()
})
.expect_err("must fail when missing dtype");
+
+ assert_eq!(IdentKindError::MissingDtype, result);
}
};
@@ -395,20 +428,24 @@ mod test {
);
// no dim
- IdentKind::try_from(SymAttrs {
+ let dim_result = IdentKind::try_from(SymAttrs {
ty: Some($src),
dtype: Some(dtype),
..Default::default()
})
.expect_err("must fail when missing dim");
+ assert_eq!(IdentKindError::MissingDim, dim_result);
+
// no dtype
- IdentKind::try_from(SymAttrs {
+ let dtype_result = IdentKind::try_from(SymAttrs {
ty: Some($src),
dim: Some(dim),
..Default::default()
})
.expect_err("must fail when missing dtype");
+
+ assert_eq!(IdentKindError::MissingDtype, dtype_result);
}
};
}
diff --git a/tamer/src/ir/asg/mod.rs b/tamer/src/ir/asg/mod.rs
index d36d4c3..761716c 100644
--- a/tamer/src/ir/asg/mod.rs
+++ b/tamer/src/ir/asg/mod.rs
@@ -196,8 +196,8 @@ mod ident;
mod object;
mod section;
-pub use graph::{Asg, AsgError, AsgResult, ObjectRef, SortableAsg};
-pub use ident::{DataType, Dim, IdentKind};
+pub use graph::{Asg, AsgError, AsgResult, IndexType, ObjectRef, SortableAsg};
+pub use ident::{DataType, Dim, IdentKind, IdentKindError};
pub use object::{
FragmentText, IdentObject, IdentObjectData, IdentObjectState, Source,
TransitionError, TransitionResult,
diff --git a/tamer/src/ir/mod.rs b/tamer/src/ir/mod.rs
index faf2673..3b94703 100644
--- a/tamer/src/ir/mod.rs
+++ b/tamer/src/ir/mod.rs
@@ -59,6 +59,17 @@
//! a graph data structure,
//! and is capable of representing entire programs composed of many
//! different packages.
+//!
+//! Lowering
+//! ========
+//! IRs are progressively _lowered_ to other IRs that are closer to the
+//! final representation emitted by the compiler ("lower"-level).
+//!
+//! - [`xmlo::reader`](crate::obj::xmlo::reader) produces
+//! [`XmloEvent`](crate::obj::xmlo::XmloEvent)s containing
+//! [`legacyir`].
+//! - [`xmlo::asg_builder`](crate::obj::xmlo::asg_builder) immediately lowers
+//! those into [`asg`].
pub mod asg;
pub mod legacyir;
diff --git a/tamer/src/ld/poc.rs b/tamer/src/ld/poc.rs
index c75aacb..11f2f23 100644
--- a/tamer/src/ld/poc.rs
+++ b/tamer/src/ld/poc.rs
@@ -20,54 +20,47 @@
//! **This is a poorly-written proof of concept; do not use!** It has been
//! banished to its own file to try to make that more clear.
+use crate::fs::{
+ Filesystem, FsCanonicalizer, PathFile, VisitOnceFile, VisitOnceFilesystem,
+};
use crate::global;
use crate::ir::asg::{
- Asg, AsgError, DefaultAsg, IdentKind, IdentObject, IdentObjectData,
- ObjectRef, Sections, SortableAsg, Source,
+ Asg, AsgError, DefaultAsg, IdentObject, IdentObjectData, Sections,
+ SortableAsg,
};
use crate::obj::xmle::writer::XmleWriter;
-use crate::obj::xmlo::reader::{XmloError, XmloEvent, XmloReader};
+use crate::obj::xmlo::{AsgBuilder, AsgBuilderState, XmloReader};
use crate::sym::{DefaultInterner, Interner, Symbol};
-use fxhash::{FxHashMap, FxHashSet};
-use std::convert::TryInto;
+use fxhash::FxBuildHasher;
use std::error::Error;
use std::fs;
use std::io::BufReader;
+use std::path::{Path, PathBuf};
type LinkerAsg<'i> = DefaultAsg<'i, IdentObject<'i>, global::ProgIdentSize>;
-type LinkerObjectRef = ObjectRef<global::ProgIdentSize>;
-type LoadResult<'i> =
- Result<Option<(Option<&'i Symbol<'i>>, Option<String>)>, Box<dyn Error>>;
+type LinkerAsgBuilderState<'i> =
+ AsgBuilderState<'i, FxBuildHasher, global::ProgIdentSize>;
pub fn main(package_path: &str, output: &str) -> Result<(), Box<dyn Error>> {
- let mut pkgs_seen: FxHashSet<String> = Default::default();
- let mut fragments: FxHashMap<&str, String> = Default::default();
+ let mut fs = VisitOnceFilesystem::new();
let mut depgraph = LinkerAsg::with_capacity(65536, 65536);
- let mut roots = Vec::new();
let interner = DefaultInterner::new();
- let abs_path = fs::canonicalize(package_path)?;
-
- let (name, relroot) = load_xmlo(
- &abs_path.to_str().unwrap().to_string(),
- &mut pkgs_seen,
- &mut fragments,
+ let state = load_xmlo(
+ package_path,
+ &mut fs,
&mut depgraph,
&interner,
- &mut roots,
- )?
- .expect("missing root package information");
+ AsgBuilderState::new(),
+ )?;
- // println!(
- // "Graph {:?}",
- // depgraph
- // .graph
- // .raw_nodes()
- // .iter()
- // .map(|node| &node.weight)
- // .collect::<Vec<_>>()
- // );
+ let AsgBuilderState {
+ mut roots,
+ name,
+ relroot,
+ found: _,
+ } = state;
roots.extend(
vec!["___yield", "___worksheet"]
@@ -117,160 +110,48 @@ pub fn main(package_path: &str, output: &str) -> Result<(), Box<dyn Error>> {
Ok(())
}
-fn load_xmlo<'a, 'i, I: Interner<'i>>(
- path_str: &'a str,
- pkgs_seen: &mut FxHashSet<String>,
- fragments: &mut FxHashMap<&'i str, String>,
+fn load_xmlo<'a, 'i, I: Interner<'i>, P: AsRef<Path>>(
+ path_str: P,
+ fs: &mut VisitOnceFilesystem<FsCanonicalizer, FxBuildHasher>,
depgraph: &mut LinkerAsg<'i>,
interner: &'i I,
- roots: &mut Vec<LinkerObjectRef>,
-) -> LoadResult<'i> {
- let path = fs::canonicalize(path_str)?;
- let path_str = path.to_str().unwrap();
-
- let first = pkgs_seen.len() == 0;
-
- if !pkgs_seen.insert(path_str.to_string()) {
- return Ok(None);
- }
-
- //println!("processing {}", path_str);
-
- let mut found: FxHashSet<&str> = Default::default();
-
- let file = fs::File::open(&path)?;
- let reader = BufReader::new(file);
- let mut xmlo = XmloReader::new(reader, interner);
- let mut elig = None;
-
- let mut name: Option<&'i Symbol<'i>> = None;
- let mut relroot: Option<String> = None;
-
- loop {
- match xmlo.read_event() {
- Ok(XmloEvent::Package(attrs)) => {
- if first {
- name = attrs.name;
- relroot = attrs.relroot;
- }
- elig = attrs.elig;
- }
-
- Ok(XmloEvent::SymDeps(sym, deps)) => {
- // TODO: API needs to expose whether a symbol is already
- // known so that we can warn on them
-
- // Maps should not pull in symbols since we may end up
- // mapping to params that are never actually used
- if !sym.starts_with(":map:") {
- for dep_sym in deps {
- depgraph.add_dep_lookup(sym, dep_sym);
- }
- }
- }
-
- Ok(XmloEvent::SymDecl(sym, attrs)) => {
- if let Some(sym_src) = attrs.src {
- found.insert(sym_src);
- } else {
- let owned = attrs.src.is_none();
- let extern_ = attrs.extern_;
-
- let kind = (&attrs).try_into().map_err(|err| {
- format!("sym `{}` attrs error: {}", sym, err)
- });
-
- let mut src: Source = attrs.into();
-
- // Existing convention is to omit @src of local package
- // (in this case, the program being linked)
- if first {
- src.pkg_name = None;
- }
-
- match kind {
- Ok(kindval) => {
- // TODO: inefficient
- let link_root = owned
- && (kindval == IdentKind::Meta
- || kindval == IdentKind::Map
- || kindval == IdentKind::RetMap);
-
- if extern_ {
- depgraph.declare_extern(sym, kindval, src)?;
- } else {
- let node =
- depgraph.declare(sym, kindval, src)?;
-
- if link_root {
- roots.push(node);
- }
- }
- }
- Err(e) => return Err(e.into()),
- };
- }
- }
-
- Ok(XmloEvent::Fragment(sym, text)) => {
- match depgraph.lookup(sym) {
- Some(frag) => match depgraph.set_fragment(frag, text) {
- Ok(_) => (),
- Err(e) => return Err(e.into()),
- },
- None => {
- return Err(XmloError::MissingFragment(String::from(
- "missing fragment",
- ))
- .into());
- }
- };
- }
+ state: LinkerAsgBuilderState<'i>,
+) -> Result<LinkerAsgBuilderState<'i>, Box<dyn Error>> {
+ let cfile: PathFile<BufReader<fs::File>> = match fs.open(path_str)? {
+ VisitOnceFile::FirstVisit(file) => file,
+ VisitOnceFile::Visited => return Ok(state),
+ };
- // We don't need to read any further than the end of the
- // header (symtable, sym-deps, fragments)
- Ok(XmloEvent::Eoh) => break,
+ let (path, file) = cfile.into();
- Err(e) => return Err(e.into()),
- }
- }
+ let xmlo: XmloReader<'_, _, _> = (file, interner).into();
- if let Some(elig_sym) = elig {
- roots.push(depgraph.lookup(elig_sym).expect(
- "internal error: package elig references nonexistant symbol",
- ));
- }
+ let mut state = depgraph.import_xmlo(xmlo, state)?;
- let mut dir = path.clone();
+ let mut dir: PathBuf = path.clone();
dir.pop();
+ let found = state.found.take().unwrap_or_default();
+
for relpath in found.iter() {
let mut path_buf = dir.clone();
path_buf.push(relpath);
path_buf.set_extension("xmlo");
- // println!("Trying {:?}", path_buf);
- let path_abs = path_buf.canonicalize()?;
- let path = path_abs.to_str().unwrap();
-
- load_xmlo(path, pkgs_seen, fragments, depgraph, interner, roots)?;
+ state = load_xmlo(path_buf, fs, depgraph, interner, state)?;
}
- if first {
- Ok(Some((name, relroot)))
- } else {
- Ok(None)
- }
+ Ok(state)
}
fn get_ident<'a, 'i>(
depgraph: &'a LinkerAsg<'i>,
name: &'i Symbol<'i>,
-) -> Result<&'a IdentObject<'i>, XmloError> {
+) -> Result<&'a IdentObject<'i>, String> {
depgraph
.lookup(name)
.and_then(|id| depgraph.get(id))
- .ok_or(XmloError::MissingFragment(String::from(name as &str)))
+ .ok_or(format!("missing identifier: {}", name))
}
fn output_xmle<'a, 'i, I: Interner<'i>>(
diff --git a/tamer/src/lib.rs b/tamer/src/lib.rs
index 3c35893..ce77d84 100644
--- a/tamer/src/lib.rs
+++ b/tamer/src/lib.rs
@@ -24,6 +24,7 @@ pub mod global;
#[macro_use]
pub mod sym;
+pub mod fs;
pub mod ir;
pub mod ld;
pub mod obj;
diff --git a/tamer/src/obj/xmlo/asg_builder.rs b/tamer/src/obj/xmlo/asg_builder.rs
new file mode 100644
index 0000000..b957c95
--- /dev/null
+++ b/tamer/src/obj/xmlo/asg_builder.rs
@@ -0,0 +1,865 @@
+// Read from xmlo and immediately lower to ASG IR
+//
+// Copyright (C) 2014-2020 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/>.
+
+//! Lower [Legacy IR](crate::ir::legacyir) derived from [`XmloEvent`]
+//! into [`Asg`].
+//!
+//! [`AsgBuilder`] is exclusively responsible for this lowering operation
+//! within the context of [`xmlo` object files](super).
+//!
+//! Usage
+//! =====
+//! [`AsgBuilder`] accepts any [`Iterator`] of [`XmloEvent`]s,
+//! but is intended to be paired with [`XmloReader`](super::XmloReader).
+//!
+//! To fully load an `xmlo` file and its dependencies,
+//! begin with [`AsgBuilder::import_xmlo`] and an empty
+//! [`AsgBuilderState`].
+//! Each call accepts the previous state and returns a new state that may be
+//! used both for introspection and for the next call to `import_xmlo`.
+//! [`AsgBuilderState::found`] can be used to recursively load relative
+//! package paths;
+//! it is wrapped in an [`Option`] so that [`take`](Option::take) can be
+//! used to take ownership over the data.
+//!
+//! ```
+//! use tamer::global;
+//! use tamer::ir::asg::{DefaultAsg, IdentObject};
+//! use tamer::obj::xmlo::{AsgBuilder, AsgBuilderState, XmloReader};
+//! use tamer::sym::{DefaultInterner, Interner};
+//! use fxhash::FxBuildHasher;
+//! use std::io::BufReader;
+//!
+//! let src_xmlo: &[u8] = br#"<package>
+//! <preproc:symtable>
+//! <preproc:sym name="foo" type="cgen" src="dep/package" />
+//! </preproc:symtable>
+//! <preproc:fragments>
+//! </preproc:fragments>
+//! </package>"#;
+//!
+//! let interner = DefaultInterner::new();
+//! let xmlo = XmloReader::new(src_xmlo, &interner);
+//! let mut asg = DefaultAsg::<'_, IdentObject, global::ProgIdentSize>::new();
+//!
+//! let state = asg.import_xmlo(xmlo, AsgBuilderState::<'_, FxBuildHasher, _>::new());
+//!
+//! // Use `state.found` to recursively load dependencies.
+//! let AsgBuilderState { found, .. } = state.expect("unexpected failure");
+//! assert_eq!(
+//! vec![&"dep/package"],
+//! found.unwrap().iter().collect::<Vec<_>>(),
+//! );
+//! ```
+
+use super::reader::{XmloError, XmloEvent, XmloResult};
+use crate::ir::asg::{
+ Asg, AsgError, IdentKind, IdentKindError, IdentObjectState, IndexType,
+ ObjectRef, Source,
+};
+use crate::sym::Symbol;
+use std::collections::HashSet;
+use std::convert::TryInto;
+use std::error::Error;
+use std::fmt::Display;
+use std::hash::BuildHasher;
+
+pub type Result<'i, S, Ix> =
+ std::result::Result<AsgBuilderState<'i, S, Ix>, AsgBuilderError<Ix>>;
+
+/// Builder state between imports.
+///
+/// [`AsgBuilder::import_xmlo`] is designed to return its state after having
+/// been invoked,
+/// which contains in part a set of relative package import paths that
+/// were discovered during processing (`found`).
+/// This state can then be fed back to `import_xmlo` when recursively
+/// importing those packages.
+///
+/// The values [`name`](AsgBuilderState::name) and
+/// [`relroot`](AsgBuilderState::relroot) are set only once when the first
+/// package is processed.
+/// A package is considered to be the first if `name` is [`None`].
+///
+/// [`roots`](AsgBuilderState::roots) is added to as certain identifiers are
+/// discovered that should be used as starting points for a topological
+/// sort.
+/// This is used by the linker to only include dependencies that are
+/// actually used by a particular program.
+#[derive(Debug, Default)]
+pub struct AsgBuilderState<'i, S, Ix>
+where
+ S: BuildHasher,
+ Ix: IndexType,
+{
+ /// Discovered roots.
+ ///
+ /// Roots represent starting points for a topological sort of the
+ /// graph.
+ /// They are meaningful to the linker.
+ pub roots: Vec<ObjectRef<Ix>>,
+
+ /// Relative paths to imported packages that have been discovered.
+ ///
+ /// The caller will use these to perform recursive loads.
+ /// This is contained within an [`Option`] so that the caller can `take`
+ /// ownership over its contents.
+ ///
+ /// See [`AsgBuilder::import_xmlo`] for behavior when this value is
+ /// [`None`].
+ pub found: Option<HashSet<&'i str, S>>,
+
+ /// Program name once discovered.
+ ///
+ /// This will be set by the first package encountered.
+ pub name: Option<&'i Symbol<'i>>,
+
+ /// Relative path to project root once discovered.
+ ///
+ /// This will be set by the first package encountered.
+ pub relroot: Option<String>,
+}
+
+impl<'i, S, Ix> AsgBuilderState<'i, S, Ix>
+where
+ S: BuildHasher + Default,
+ Ix: IndexType,
+{
+ /// Create a new, empty state.
+ pub fn new() -> Self {
+ Default::default()
+ }
+
+ /// Whether this is the first discovered package.
+ ///
+ /// This is true if [`name`](AsgBuilderState::name) is [`None`].
+ fn is_first(&self) -> bool {
+ self.name.is_none()
+ }
+}
+
+/// Populate [`Asg`] with data derived from `xmlo` files.
+///
+/// This accepts anything capable of iterating over [`XmloResult`].
+///
+/// For more information on what data are processed,
+/// see [`AsgBuilderState`].
+/// See the [module-level documentation](self) for example usage.
+pub trait AsgBuilder<'i, O, S, Ix>
+where
+ O: IdentObjectState<'i, O>,
+ S: BuildHasher,
+ Ix: IndexType,
+{
+ /// Import [`XmloResult`]s into an [`Asg`].
+ ///
+ /// This is an IR lowering operation.
+ /// The [`XmloResult`] produces data gleaned from
+ /// [`legacyir`](crate::ir::legacyir),
+ /// and this process lowers it into the IR [`asg`](crate::ir::asg).
+ ///
+ /// Each call to this method augments the provided [`AsgBuilderState`];
+ /// see its documentation for more information.
+ /// Its initial value can be provided as [`Default::default`].
+ fn import_xmlo(
+ &mut self,
+ xmlo: impl Iterator<Item = XmloResult<XmloEvent<'i>>>,
+ state: AsgBuilderState<'i, S, Ix>,
+ ) -> Result<'i, S, Ix>;
+}
+
+impl<'i, O, S, Ix, G> AsgBuilder<'i, O, S, Ix> for G
+where
+ O: IdentObjectState<'i, O>,
+ S: BuildHasher + Default,
+ Ix: IndexType,
+ G: Asg<'i, O, Ix>,
+{
+ fn import_xmlo(
+ &mut self,
+ mut xmlo: impl Iterator<Item = XmloResult<XmloEvent<'i>>>,
+ mut state: AsgBuilderState<'i, S, Ix>,
+ ) -> Result<'i, S, Ix> {
+ let mut elig = None;
+ let first = state.is_first();
+ let found = state.found.get_or_insert(Default::default());
+
+ while let Some(ev) = xmlo.next() {
+ match ev? {
+ XmloEvent::Package(attrs) => {
+ if first {
+ state.name = attrs.name;
+ state.relroot = attrs.relroot;
+ }
+
+ elig = attrs.elig;
+ }
+
+ XmloEvent::SymDeps(sym, deps) => {
+ // Maps should not pull in symbols since we may end up
+ // mapping to params that are never actually used.
+ // TODO: Can these just be removed from the xmlo files
+ // rather than adding exceptions?
+ // TODO: No string comparison.
+ if !sym.starts_with(":map:") {
+ for dep_sym in deps {
+ self.add_dep_lookup(sym, dep_sym);
+ }
+ }
+ }
+
+ XmloEvent::SymDecl(sym, attrs) => {
+ if let Some(sym_src) = attrs.src {
+ found.insert(sym_src);
+ } else {
+ let extern_ = attrs.extern_;
+ let kindval = (&attrs).try_into()?;
+
+ let mut src: Source = attrs.into();
+
+ // Existing convention is to omit @src of local package
+ // (in this case, the program being linked)
+ if first {
+ src.pkg_name = None;
+ }
+
+ let link_root = matches!(
+ kindval,
+ IdentKind::Meta
+ | IdentKind::Map
+ | IdentKind::RetMap
+ );
+
+ if extern_ {
+ self.declare_extern(sym, kindval, src)?;
+ } else {
+ let node = self.declare(sym, kindval, src)?;
+
+ if link_root {
+ state.roots.push(node);
+ }
+ }
+ }
+ }
+
+ XmloEvent::Fragment(sym, text) => {
+ let frag = self.lookup(sym).ok_or(
+ AsgBuilderError::MissingFragmentIdent(sym.to_string()),
+ )?;
+
+ self.set_fragment(frag, text)?;
+ }
+
+ // We don't need to read any further than the end of the
+ // header (symtable, sym-deps, fragments). Note that this
+ // may change in the future, in which case this
+ // responsibility can be delegated to the linker (to produce
+ // an `Iterator` that stops at EOH).
+ XmloEvent::Eoh => break,
+ }
+ }
+
+ if let Some(elig_sym) = elig {
+ state
+ .roots
+ .push(self.lookup(elig_sym).ok_or(
+ AsgBuilderError::BadEligRef(elig_sym.to_string()),
+ )?);
+ }
+
+ Ok(state)
+ }
+}
+
+/// Error populating graph with [`XmloResult`]-derived data.
+#[derive(Debug, PartialEq)]
+pub enum AsgBuilderError<Ix: IndexType> {
+ /// Error with the source `xmlo` file.
+ XmloError(XmloError),
+
+ /// Error parsing into [`IdentKind`].
+ IdentKindError(IdentKindError),
+
+ /// [`Asg`] operation error.
+ AsgError(AsgError<Ix>),
+
+ /// Fragment encountered for an unknown identifier.
+ MissingFragmentIdent(String),
+
+ /// Eligibility classification references unknown identifier.
+ ///
+ /// This is generated by the compiler and so should never happen.
+ /// (That's not to say that it won't, but it shouldn't.)
+ BadEligRef(String),
+}
+
+impl<Ix: IndexType> Display for AsgBuilderError<Ix> {
+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
+ match self {
+ Self::XmloError(e) => e.fmt(fmt),
+ Self::IdentKindError(e) => e.fmt(fmt),
+ Self::AsgError(e) => e.fmt(fmt),
+
+ Self::MissingFragmentIdent(name) => write!(
+ fmt,
+ "encountered fragment for unknown identifier `{}`",
+ name,
+ ),
+
+ Self::BadEligRef(name) => write!(
+ fmt,
+ "internal error: package elig references nonexistant symbol `{}`",
+ name,
+ ),
+ }
+ }
+}
+
+impl<Ix: IndexType> From<XmloError> for AsgBuilderError<Ix> {
+ fn from(src: XmloError) -> Self {
+ Self::XmloError(src)
+ }
+}
+
+impl<Ix: IndexType> From<IdentKindError> for AsgBuilderError<Ix> {
+ fn from(src: IdentKindError) -> Self {
+ Self::IdentKindError(src)
+ }
+}
+
+impl<Ix: IndexType> From<AsgError<Ix>> for AsgBuilderError<Ix> {
+ fn from(src: AsgError<Ix>) -> Self {
+ Self::AsgError(src)
+ }
+}
+
+impl<Ix: IndexType> Error for AsgBuilderError<Ix> {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self {
+ Self::XmloError(e) => Some(e),
+ Self::IdentKindError(e) => Some(e),
+ Self::AsgError(e) => Some(e),
+ _ => None,
+ }
+ }
+}
+
+// These tests are coupled with BaseAsg, which is not ideal.
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::ir::asg::{DefaultAsg, FragmentText, IdentObject};
+ use crate::ir::legacyir::{PackageAttrs, SymAttrs, SymType};
+ use crate::sym::SymbolIndex;
+ use std::collections::hash_map::RandomState;
+
+ type SutIx = u8;
+ type Sut<'i> = DefaultAsg<'i, IdentObject<'i>, SutIx>;
+ type SutState<'i> = AsgBuilderState<'i, RandomState, SutIx>;
+
+ #[test]
+ fn gets_data_from_package_event() {
+ let mut sut = Sut::new();
+
+ let name = symbol_dummy!(1, "name");
+ let relroot = "some/path".to_string();
+
+ let evs = vec![Ok(XmloEvent::Package(PackageAttrs {
+ name: Some(&name),
+ relroot: Some(relroot.clone()),
+ ..Default::default()
+ }))];
+
+ let state = sut
+ .import_xmlo(evs.into_iter(), SutState::new())
+ .expect("parsing of proper PackageAttrs must succeed");
+
+ assert_eq!(Some(&name), state.name);
+ assert_eq!(Some(relroot), state.relroot);
+ }
+
+ #[test]
+ fn xmlo_error_returned() {
+ let mut sut = Sut::new();
+
+ let evs = vec![Err(XmloError::UnexpectedRoot)];
+ let result = sut.import_xmlo(evs.into_iter(), SutState::new());
+
+ assert_eq!(
+ AsgBuilderError::XmloError(XmloError::UnexpectedRoot),
+ result.expect_err("expected error to be proxied"),
+ );
+ }
+
+ #[test]
+ fn adds_elig_as_root() {
+ let mut sut = Sut::new();
+ let elig_sym = symbol_dummy!(1, "elig");
+
+ // The symbol must be on the graph, or it'll fail.
+ let elig_node = sut
+ .declare(&elig_sym, IdentKind::Meta, Default::default())
+ .unwrap();
+
+ let evs = vec![Ok(XmloEvent::Package(PackageAttrs {
+ elig: Some(&elig_sym),
+ ..Default::default()
+ }))];
+
+ let state = sut.import_xmlo(evs.into_iter(), SutState::new()).unwrap();
+
+ assert!(state.roots.contains(&elig_node));
+ }
+
+ #[test]
+ fn adds_sym_deps() {
+ let mut sut = Sut::new();
+
+ let sym_from = symbol_dummy!(1, "from");
+ let sym_to1 = symbol_dummy!(2, "to1");
+ let sym_to2 = symbol_dummy!(3, "to2");
+
+ let evs =
+ vec![Ok(XmloEvent::SymDeps(&sym_from, vec![&sym_to1, &sym_to2]))];
+
+ let _ = sut
+ .import_xmlo(evs.into_iter(), SutState::new())
+ .expect("unexpected failure");
+
+ let node_from = sut.lookup(&sym_from).expect("from node not added");
+ let node_to1 = sut.lookup(&sym_to1).expect("to1 node not added");
+ let node_to2 = sut.lookup(&sym_to2).expect("to2 node not added");
+
+ assert!(sut.has_dep(node_from, node_to1));
+ assert!(sut.has_dep(node_from, node_to2));
+ }
+
+ #[test]
+ fn ignores_map_sym_deps() {
+ let mut sut = Sut::new();
+
+ let sym_from = symbol_dummy!(1, ":map:sym");
+ let sym_to = symbol_dummy!(2, "to");
+
+ let evs = vec![Ok(XmloEvent::SymDeps(&sym_from, vec![&sym_to]))];
+
+ let _ = sut
+ .import_xmlo(evs.into_iter(), SutState::new())
+ .expect("unexpected failure");
+
+ assert!(sut.lookup(&sym_from).is_none());
+ }
+
+ #[test]
+ fn sym_decl_with_src_not_added_and_populates_found() {
+ let mut sut = Sut::new();
+
+ let sym = symbol_dummy!(1, "sym");
+ let src_a = symbol_dummy!(2, "src_a");
+ let src_b = symbol_dummy!(3, "src_b");
+
+ let evs = vec![
+ Ok(XmloEvent::SymDecl(
+ &sym,
+ SymAttrs {
+ src: Some(&src_a),
+ ..Default::default()
+ },
+ )),
+ Ok(XmloEvent::SymDecl(
+ &sym,
+ SymAttrs {
+ src: Some(&src_b),
+ ..Default::default()
+ },
+ )),
+ ];
+
+ let mut founds = sut
+ .import_xmlo(evs.into_iter(), SutState::new())
+ .expect("unexpected failure")
+ .found
+ .unwrap()
+ .iter()
+ .map(|s| *s)
+ .collect::<Vec<_>>();
+
+ // Just to remove nondeterminism in case the iteration order happens
+ // to change (we're using RandomState).
+ founds.sort();
+
+ assert_eq!(vec![&src_a as &str, &src_b as &str], founds);
+
+ // Symbols with `src` set are external and should not be added to
+ // the graph.
+ assert!(sut.lookup(&sym).is_none());
+ }
+
+ #[test]
+ fn sym_decl_added_to_graph() {
+ let mut sut = Sut::new();
+
+ let sym_extern = symbol_dummy!(1, "sym_extern");
+ let sym_non_extern = symbol_dummy!(2, "sym_non_extern");
+ let sym_map = symbol_dummy!(3, "sym_map");
+ let sym_retmap = symbol_dummy!(4, "sym_retmap");
+ let pkg_name = symbol_dummy!(5, "pkg name");
+
+ let evs = vec![
+ // Note that externs should not be recognized as roots even if
+ // their type would be.
+ Ok(XmloEvent::SymDecl(
+ &sym_extern,
+ SymAttrs {
+ pkg_name: Some(&pkg_name),
+ extern_: true,
+ ty: Some(SymType::Meta),
+ ..Default::default()
+ },
+ )),
+ // These three will be roots
+ Ok(XmloEvent::SymDecl(
+ &sym_non_extern,
+ SymAttrs {
+ pkg_name: Some(&pkg_name),
+ ty: Some(SymType::Meta),
+ ..Default::default()
+ },
+ )),
+ Ok(XmloEvent::SymDecl(
+ &sym_map,
+ SymAttrs {
+ pkg_name: Some(&pkg_name),
+ ty: Some(SymType::Map),
+ ..Default::default()
+ },
+ )),
+ Ok(XmloEvent::SymDecl(
+ &sym_retmap,
+ SymAttrs {
+ pkg_name: Some(&pkg_name),
+ ty: Some(SymType::RetMap),
+ ..Default::default()
+ },
+ )),
+ ];
+
+ let state = sut.import_xmlo(evs.into_iter(), SutState::new()).unwrap();
+
+ // Both above symbols were local (no `src`).
+ assert!(state.found.unwrap().len() == 0);
+
+ assert_eq!(
+ vec![
+ sut.lookup(&sym_non_extern).unwrap(),
+ sut.lookup(&sym_map).unwrap(),
+ sut.lookup(&sym_retmap).unwrap(),
+ ],
+ state.roots
+ );
+
+ // Note that each of these will have their package names cleared
+ // since this is considered to be the first package encountered.
+
+ assert_eq!(
+ &IdentObject::Extern(
+ &sym_extern,
+ IdentKind::Meta,
+ Source {
+ pkg_name: None,
+ ..Default::default()
+ },
+ ),
+ sut.get(sut.lookup(&sym_extern).unwrap()).unwrap(),
+ );
+
+ assert_eq!(
+ &IdentObject::Ident(
+ &sym_non_extern,
+ IdentKind::Meta,
+ Source {
+ pkg_name: None,
+ ..Default::default()
+ },
+ ),
+ sut.get(sut.lookup(&sym_non_extern).unwrap()).unwrap(),
+ );
+
+ assert_eq!(
+ &IdentObject::Ident(
+ &sym_map,
+ IdentKind::Map,
+ Source {
+ pkg_name: None,
+ ..Default::default()
+ },
+ ),
+ sut.get(sut.lookup(&sym_map).unwrap()).unwrap(),
+ );
+
+ assert_eq!(
+ &IdentObject::Ident(
+ &sym_retmap,
+ IdentKind::RetMap,
+ Source {
+ pkg_name: None,
+ ..Default::default()
+ },
+ ),
+ sut.get(sut.lookup(&sym_retmap).unwrap()).unwrap(),
+ );
+ }
+
+ // See above test, where pkg_name was cleared.
+ #[test]
+ fn sym_decl_pkg_name_retained_if_not_first() {
+ let mut sut = Sut::new();
+
+ let sym = symbol_dummy!(1, "sym");
+ let pkg_name = symbol_dummy!(2, "pkg name");
+
+ // This is all that's needed to not consider this to be the first
+ // package, so that pkg_name is retained below.
+ let state = AsgBuilderState::<'_, RandomState, SutIx> {
+ name: Some(&pkg_name),
+ ..Default::default()
+ };
+
+ let evs = vec![Ok(XmloEvent::SymDecl(
+ &sym,
+ SymAttrs {
+ pkg_name: Some(&pkg_name),
+ ty: Some(SymType::Meta),
+ ..Default::default()
+ },
+ ))];
+
+ let _ = sut.import_xmlo(evs.into_iter(), state).unwrap();
+
+ assert_eq!(
+ // `pkg_name` retained
+ &IdentObject::Ident(
+ &sym,
+ IdentKind::Meta,
+ Source {
+ pkg_name: Some(&pkg_name),
+ ..Default::default()
+ },
+ ),
+ sut.get(sut.lookup(&sym).unwrap()).unwrap(),
+ );
+ }
+
+ #[test]
+ fn ident_kind_conversion_error_propagates() {
+ let mut sut = Sut::new();
+
+ let sym = symbol_dummy!(1, "sym");
+ let bad_attrs = SymAttrs::default();
+
+ let evs = vec![Ok(XmloEvent::SymDecl(&sym, bad_attrs))];
+
+ let result = sut
+ .import_xmlo(evs.into_iter(), SutState::new())
+ .expect_err("expected IdentKind conversion error");
+
+ assert!(matches!(result, AsgBuilderError::IdentKindError(_)));
+ }
+
+ #[test]
+ fn declare_extern_error_propagates() {
+ let mut sut = Sut::new();
+
+ let sym = symbol_dummy!(1, "sym");
+
+ let evs = vec![
+ Ok(XmloEvent::SymDecl(
+ &sym,
+ SymAttrs {
+ extern_: true,
+ ty: Some(SymType::Meta),
+ ..Default::default()
+ },
+ )),
+ // Incompatible
+ Ok(XmloEvent::SymDecl(
+ &sym,
+ SymAttrs {
+ extern_: true,
+ ty: Some(SymType::Map),
+ ..Default::default()
+ },
+ )),
+ ];
+
+ let result = sut
+ .import_xmlo(evs.into_iter(), SutState::new())
+ .expect_err("expected extern error");
+
+ assert!(matches!(result, AsgBuilderError::AsgError(_)));
+ }
+
+ #[test]
+ fn declare_error_propagates() {
+ let mut sut = Sut::new();
+
+ let sym = symbol_dummy!(1, "sym");
+
+ let evs = vec![
+ Ok(XmloEvent::SymDecl(
+ &sym,
+ SymAttrs {
+ ty: Some(SymType::Meta),
+ ..Default::default()
+ },
+ )),
+ // Redeclare
+ Ok(XmloEvent::SymDecl(
+ &sym,
+ SymAttrs {
+ ty: Some(SymType::Meta),
+ ..Default::default()
+ },
+ )),
+ ];
+
+ let result = sut
+ .import_xmlo(evs.into_iter(), SutState::new())
+ .expect_err("expected declare error");
+
+ assert!(matches!(result, AsgBuilderError::AsgError(_)));
+ }
+
+ #[test]
+ fn sets_fragment() {
+ let mut sut = Sut::new();
+
+ let sym = symbol_dummy!(1, "sym");
+ let frag = FragmentText::from("foo");
+
+ let evs = vec![
+ Ok(XmloEvent::SymDecl(
+ &sym,
+ SymAttrs {
+ ty: Some(SymType::Meta),
+ ..Default::default()
+ },
+ )),
+ Ok(XmloEvent::Fragment(&sym, frag.clone())),
+ ];
+
+ let _ = sut.import_xmlo(evs.into_iter(), SutState::new()).unwrap();
+
+ let node = sut
+ .lookup(&sym)
+ .expect("ident/fragment was not added to graph");
+
+ assert_eq!(
+ Some(&IdentObject::IdentFragment(
+ &sym,
+ IdentKind::Meta,
+ Default::default(),
+ frag
+ )),
+ sut.get(node),
+ );
+ }
+
+ #[test]
+ fn error_missing_ident_for_fragment() {
+ let mut sut = Sut::new();
+
+ let sym = symbol_dummy!(1, "sym");
+
+ // Note: missing `SymDecl`.
+ let evs = vec![Ok(XmloEvent::Fragment(&sym, "foo".into()))];
+
+ let result = sut
+ .import_xmlo(evs.into_iter(), SutState::new())
+ .expect_err("expected error for fragment without ident");
+
+ assert_eq!(
+ AsgBuilderError::MissingFragmentIdent(sym.to_string()),
+ result,
+ );
+ }
+
+ #[test]
+ fn fragment_error_propagates() {
+ let mut sut = Sut::new();
+
+ let sym = symbol_dummy!(1, "sym");
+ let frag = FragmentText::from("foo");
+
+ let evs = vec![
+ Ok(XmloEvent::SymDecl(
+ &sym,
+ SymAttrs {
+ // Invalid fragment destination
+ extern_: true,
+ ty: Some(SymType::Meta),
+ ..Default::default()
+ },
+ )),
+ Ok(XmloEvent::Fragment(&sym, frag.clone())),
+ ];
+
+ let result = sut
+ .import_xmlo(evs.into_iter(), SutState::new())
+ .expect_err("expected fragment set failure");
+
+ assert!(matches!(
+ result,
+ AsgBuilderError::AsgError(AsgError::ObjectTransition(_))
+ ));
+
+ let node = sut
+ .lookup(&sym)
+ .expect("ident/fragment was not added to graph");
+
+ // The identifier should not have been modified on failure.
+ assert!(matches!(
+ sut.get(node).unwrap(),
+ IdentObject::Extern(_, _, _)
+ ));
+ }
+
+ #[test]
+ fn stops_at_eoh() {
+ let mut sut = Sut::new();
+
+ let pkg_name = symbol_dummy!(1, "pkg name");
+
+ let evs = vec![
+ // Stop here.
+ Ok(XmloEvent::Eoh),
+ // Shouldn't make it to this one.
+ Ok(XmloEvent::Package(PackageAttrs {
+ name: Some(&pkg_name),
+ ..Default::default()
+ })),
+ ];
+
+ let state = sut.import_xmlo(evs.into_iter(), SutState::new()).unwrap();
+
+ // Should still be true because we didn't get to the `PackageAttrs`
+ // event.
+ assert!(state.is_first());
+ }
+}
diff --git a/tamer/src/obj/xmlo/mod.rs b/tamer/src/obj/xmlo/mod.rs
index 9600792..3e81d76 100644
--- a/tamer/src/obj/xmlo/mod.rs
+++ b/tamer/src/obj/xmlo/mod.rs
@@ -73,5 +73,20 @@
//! <!-- Expanded src -->
//! </package>
//! ```
+//!
+//! IR Lowering
+//! ===========
+//! `xmlo` files are represented by the [Legacy IR](crate::ir::legacyir),
+//! which is emitted during read by [`AsgBuilder`].
+//! For more information about how they are lowered into the
+//! [ASG](crate::ir::asg),
+//! see [`asg_builder`].
+//!
+//! For a summary of IRs and how they interact,
+//! see [`crate::ir`].
+
+mod asg_builder;
+mod reader;
-pub mod reader;
+pub use asg_builder::{AsgBuilder, AsgBuilderState};
+pub use reader::{XmloError, XmloEvent, XmloReader};
diff --git a/tamer/src/obj/xmlo/reader.rs b/tamer/src/obj/xmlo/reader.rs
index ded13c0..754fc91 100644
--- a/tamer/src/obj/xmlo/reader.rs
+++ b/tamer/src/obj/xmlo/reader.rs
@@ -50,7 +50,7 @@
//!
//! ```
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
-//! use tamer::obj::xmlo::reader::{XmloReader, XmloEvent};
+//! use tamer::obj::xmlo::{XmloEvent, XmloReader};
//! use tamer::ir::legacyir::SymType;
//! use tamer::sym::{DefaultInterner, Interner};
//!
@@ -149,6 +149,7 @@ use quick_xml::Reader as XmlReader;
use std::convert::TryInto;
use std::fmt::Display;
use std::io::BufRead;
+use std::iter::Iterator;
use std::result::Result;
/// A [`Result`] with a hard-coded [`XmloError`] error type.
@@ -658,7 +659,7 @@ impl<'i, B: BufRead, I: Interner<'i>> XmloReader<'i, B, I> {
XmlError::TextNotFound => {
XmloError::MissingFragmentText(id.to_string())
}
- err => XmloError::XmlError(err),
+ _ => err.into(),
})?;
Ok(XmloEvent::Fragment(id, text))
@@ -684,6 +685,37 @@ impl<'i, B: BufRead, I: Interner<'i>> XmloReader<'i, B, I> {
}
}
+impl<'i, B, I> Iterator for XmloReader<'i, B, I>
+where
+ B: BufRead,
+ I: Interner<'i>,
+{
+ type Item = XmloResult<XmloEvent<'i>>;
+
+ /// 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<'i, B, I> From<(B, &'i I)> for XmloReader<'i, B, I>
+where
+ B: BufRead,
+ I: Interner<'i>,
+{
+ fn from(args: (B, &'i I)) -> Self {
+ Self::new(args.0, args.1)
+ }
+}
+
/// `xmlo` reader events.
///
/// All data are parsed rather than being returned as [`u8`] slices,
@@ -739,10 +771,10 @@ pub enum XmloEvent<'i> {
/// This drastically simplifies the reader and [`Result`] chaining.
///
/// TODO: These errors provide no context (byte offset).
-#[derive(Debug)]
+#[derive(Debug, PartialEq)]
pub enum XmloError {
/// XML parsing error.
- XmlError(XmlError),
+ XmlError(XmlParseError),
/// The root node was not an `lv:package`.
UnexpectedRoot,
/// A `preproc:sym` node was found, but is missing `@name`.
@@ -764,13 +796,11 @@ pub enum XmloError {
UnassociatedFragment,
/// A `preproc:fragment` element was found, but is missing `text()`.
MissingFragmentText(String),
- /// A `preproc:fragment` element was not found
- MissingFragment(String),
}
impl From<XmlError> for XmloError {
fn from(e: XmlError) -> Self {
- XmloError::XmlError(e)
+ XmloError::XmlError(e.into())
}
}
@@ -813,9 +843,6 @@ impl Display for XmloError {
"fragment found, but missing text for symbol `{}`",
symname,
),
- XmloError::MissingFragment(symname) => {
- write!(fmt, "fragment not found `{}`", symname,)
- }
}
}
}
@@ -829,6 +856,40 @@ impl std::error::Error for XmloError {
}
}
+/// Thin wrapper around [`XmlError`] to implement [`PartialEq`].
+///
+/// This will always yield `false`,
+/// but allows us to derive the trait on types using [`XmloError`];
+/// otherwise, this madness propagates indefinitely.
+#[derive(Debug)]
+pub struct XmlParseError(XmlError);
+
+impl PartialEq for XmlParseError {
+ /// [`XmlError`] does not implement [`PartialEq`] and so this will
+ /// always yield `false`.
+ fn eq(&self, _other: &Self) -> bool {
+ false
+ }
+}
+
+impl From<XmlError> for XmlParseError {
+ fn from(e: XmlError) -> Self {
+ Self(e)
+ }
+}
+
+impl Display for XmlParseError {
+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
+ self.0.fmt(fmt)
+ }
+}
+
+impl std::error::Error for XmlParseError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ Some(&self.0)
+ }
+}
+
#[cfg(test)]
mod mock {
use super::*;
@@ -958,7 +1019,7 @@ mod test {
Some(Box::new(|_, _| Err(XmlError::UnexpectedEof("test".into()))));
match sut.read_event() {
- Err(XmloError::XmlError(XmlError::UnexpectedEof(_))) => (),
+ Err(XmloError::XmlError(XmlParseError(XmlError::UnexpectedEof(_)))) => (),
bad => panic!("expected XmlError: {:?}", bad),
}
}
@@ -1511,6 +1572,25 @@ mod test {
bad => panic!("expected XmloError: {:?}", bad),
}
}
+
+ fn read_events_via_iterator(sut, interner) {
+ sut.reader.next_event = Some(Box::new(|_, _| {
+ Ok(XmlEvent::Start(MockBytesStart::new(
+ b"package",
+ Some(MockAttributes::new(vec![])),
+ )))
+ }));
+
+ let result = sut.next().unwrap()?;
+
+ assert_eq!(
+ XmloEvent::Package(PackageAttrs {
+ program: false,
+ ..Default::default()
+ }),
+ result
+ );
+ }
}
macro_rules! sym_test_reader_event {