TAMER: Finalize dependency graph construction
There is more refactoring that can be done, but this extracts it out of POC.master
commit
1ddd9cceea
|
@ -48,7 +48,7 @@ mod base {
|
|||
|
||||
#[bench]
|
||||
fn declare_1_000(bench: &mut Bencher) {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
let mut sut = Sut::new();
|
||||
let interner = DefaultInterner::new();
|
||||
let xs = interned_n(&interner, 1_000);
|
||||
|
||||
|
@ -75,7 +75,7 @@ mod base {
|
|||
// The Ix size affects memory, but how about performance?
|
||||
#[bench]
|
||||
fn declare_1_000_prog_ident_size(bench: &mut Bencher) {
|
||||
let mut sut = SutProg::with_capacity(0, 0);
|
||||
let mut sut = SutProg::new();
|
||||
let interner = DefaultInterner::new();
|
||||
let xs = interned_n(&interner, 1_000);
|
||||
|
||||
|
@ -88,7 +88,7 @@ mod base {
|
|||
|
||||
#[bench]
|
||||
fn declare_extern_1_000(bench: &mut Bencher) {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
let mut sut = Sut::new();
|
||||
let interner = DefaultInterner::new();
|
||||
let xs = interned_n(&interner, 1_000);
|
||||
|
||||
|
@ -103,7 +103,7 @@ mod base {
|
|||
|
||||
#[bench]
|
||||
fn resolve_extern_1_000(bench: &mut Bencher) {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
let mut sut = Sut::new();
|
||||
let interner = DefaultInterner::new();
|
||||
let xs = interned_n(&interner, 1_000);
|
||||
|
||||
|
@ -124,7 +124,7 @@ mod base {
|
|||
// strings for each call.
|
||||
#[bench]
|
||||
fn set_fragment_1_000_with_new_str(bench: &mut Bencher) {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
let mut sut = Sut::new();
|
||||
let interner = DefaultInterner::new();
|
||||
let xs = interned_n(&interner, 1_000);
|
||||
|
||||
|
@ -147,7 +147,7 @@ mod base {
|
|||
|
||||
#[bench]
|
||||
fn lookup_1_000(bench: &mut Bencher) {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
let mut sut = Sut::new();
|
||||
let interner = DefaultInterner::new();
|
||||
let xs = interned_n(&interner, 1_000);
|
||||
|
||||
|
@ -162,7 +162,7 @@ mod base {
|
|||
|
||||
#[bench]
|
||||
fn get_1_000(bench: &mut Bencher) {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
let mut sut = Sut::new();
|
||||
let interner = DefaultInterner::new();
|
||||
let xs = interned_n(&interner, 1_000);
|
||||
|
||||
|
@ -186,7 +186,7 @@ mod base {
|
|||
// supernodes at the time of writing, relatively speaking.
|
||||
#[bench]
|
||||
fn add_dep_1_000_to_single_node(bench: &mut Bencher) {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
let mut sut = Sut::new();
|
||||
let interner = DefaultInterner::new();
|
||||
let xs = interned_n(&interner, 1_000);
|
||||
|
||||
|
@ -212,7 +212,7 @@ mod base {
|
|||
// Same as above but only one edge per node.
|
||||
#[bench]
|
||||
fn add_dep_1_000_one_edge_per_node(bench: &mut Bencher) {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
let mut sut = Sut::new();
|
||||
let interner = DefaultInterner::new();
|
||||
let xs = interned_n(&interner, 1_000);
|
||||
|
||||
|
@ -235,7 +235,7 @@ mod base {
|
|||
|
||||
#[bench]
|
||||
fn has_dep_1_000_single_node(bench: &mut Bencher) {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
let mut sut = Sut::new();
|
||||
let interner = DefaultInterner::new();
|
||||
let xs = interned_n(&interner, 1_000);
|
||||
|
||||
|
@ -264,7 +264,7 @@ mod base {
|
|||
// Same as above but only one edge per node.
|
||||
#[bench]
|
||||
fn has_dep_1_000_one_edge_per_node(bench: &mut Bencher) {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
let mut sut = Sut::new();
|
||||
let interner = DefaultInterner::new();
|
||||
let xs = interned_n(&interner, 1_000);
|
||||
|
||||
|
@ -293,7 +293,7 @@ mod base {
|
|||
|
||||
#[bench]
|
||||
fn add_dep_lookup_1_000_missing_one_edge_per_node(bench: &mut Bencher) {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
let mut sut = Sut::new();
|
||||
let interner = DefaultInterner::new();
|
||||
let xs = interned_n(&interner, 1_000);
|
||||
|
||||
|
@ -307,7 +307,7 @@ mod base {
|
|||
|
||||
#[bench]
|
||||
fn add_dep_lookup_1_000_existing_one_edge_per_node(bench: &mut Bencher) {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
let mut sut = Sut::new();
|
||||
let interner = DefaultInterner::new();
|
||||
let xs = interned_n(&interner, 1_000);
|
||||
|
||||
|
@ -325,7 +325,7 @@ mod base {
|
|||
|
||||
#[bench]
|
||||
fn sort_1_with_1_000_existing_supernode(bench: &mut Bencher) {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
let mut sut = Sut::new();
|
||||
let interner = DefaultInterner::new();
|
||||
let xs = interned_n(&interner, 1_000);
|
||||
|
||||
|
@ -357,7 +357,7 @@ mod base {
|
|||
fn sort_1_with_1_000_existing_one_edge_per_node_one_path(
|
||||
bench: &mut Bencher,
|
||||
) {
|
||||
let mut sut = Sut::with_capacity(0, 0);
|
||||
let mut sut = Sut::new();
|
||||
let interner = DefaultInterner::new();
|
||||
let xs = interned_n(&interner, 1_000);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
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),
|
||||
};
|
||||
|
||||
let first = pkgs_seen.len() == 0;
|
||||
let (path, file) = cfile.into();
|
||||
|
||||
if !pkgs_seen.insert(path_str.to_string()) {
|
||||
return Ok(None);
|
||||
}
|
||||
let xmlo: XmloReader<'_, _, _> = (file, interner).into();
|
||||
|
||||
//println!("processing {}", path_str);
|
||||
let mut state = depgraph.import_xmlo(xmlo, state)?;
|
||||
|
||||
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());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// We don't need to read any further than the end of the
|
||||
// header (symtable, sym-deps, fragments)
|
||||
Ok(XmloEvent::Eoh) => break,
|
||||
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(elig_sym) = elig {
|
||||
roots.push(depgraph.lookup(elig_sym).expect(
|
||||
"internal error: package elig references nonexistant symbol",
|
||||
));
|
||||
}
|
||||
|
||||
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>>(
|
||||
|
|
|
@ -24,6 +24,7 @@ pub mod global;
|
|||
#[macro_use]
|
||||
pub mod sym;
|
||||
|
||||
pub mod fs;
|
||||
pub mod ir;
|
||||
pub mod ld;
|
||||
pub mod obj;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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`].
|
||||
|
||||
pub mod reader;
|
||||
mod asg_builder;
|
||||
mod reader;
|
||||
|
||||
pub use asg_builder::{AsgBuilder, AsgBuilderState};
|
||||
pub use reader::{XmloError, XmloEvent, XmloReader};
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue