From f2c5443176ceb4ebf39d32afc317e0d7e381fbf1 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Wed, 11 May 2022 16:38:59 -0400 Subject: [PATCH] tamer: asg: Remove generic Asg, rename {Base=>}Asg This is the beginning of an incremental refactoring to remove generics, to simplify the ASG. When I initially wrote the linker, I wasn't sure what direction I was going in, but I was also negatively influenced by more traditional approaches to both design and unit testing. If we're going to call the ASG an IR, then it needs to be one---if the core of the IR is generic, then it's more like an abstract data structure than anything. We can abstract around the IR to slice it up into components that are a little easier to reason about and understand how responsibilities are segregated. DEV-11864 --- tamer/benches/asg.rs | 2 +- tamer/benches/asg_lower_xmle.rs | 2 +- tamer/src/asg/base.rs | 738 ---------------------------- tamer/src/asg/error.rs | 62 +++ tamer/src/asg/graph.rs | 770 ++++++++++++++++++++++++++---- tamer/src/asg/mod.rs | 16 +- tamer/src/ld/poc.rs | 2 +- tamer/src/ld/xmle/lower.rs | 15 +- tamer/src/obj/xmlo/asg_builder.rs | 9 +- 9 files changed, 770 insertions(+), 846 deletions(-) delete mode 100644 tamer/src/asg/base.rs create mode 100644 tamer/src/asg/error.rs diff --git a/tamer/benches/asg.rs b/tamer/benches/asg.rs index 4aba94f7..b38c205c 100644 --- a/tamer/benches/asg.rs +++ b/tamer/benches/asg.rs @@ -30,7 +30,7 @@ use test::Bencher; mod base { use super::*; - use tamer::asg::{Asg, DefaultAsg, IdentKind, IdentObject, Source}; + use tamer::asg::{DefaultAsg, IdentKind, IdentObject, Source}; use tamer::sym::{GlobalSymbolIntern, SymbolId}; type Sut = DefaultAsg; diff --git a/tamer/benches/asg_lower_xmle.rs b/tamer/benches/asg_lower_xmle.rs index 2a153c63..1780992a 100644 --- a/tamer/benches/asg_lower_xmle.rs +++ b/tamer/benches/asg_lower_xmle.rs @@ -24,7 +24,7 @@ extern crate test; use test::Bencher; -use tamer::asg::{Asg, DataType, DefaultAsg, IdentKind, IdentObject, Source}; +use tamer::asg::{DataType, DefaultAsg, IdentKind, IdentObject, Source}; use tamer::ld::xmle::{lower::sort, Sections}; use tamer::sym::{GlobalSymbolIntern, SymbolId}; diff --git a/tamer/src/asg/base.rs b/tamer/src/asg/base.rs deleted file mode 100644 index 3fc3278d..00000000 --- a/tamer/src/asg/base.rs +++ /dev/null @@ -1,738 +0,0 @@ -// Concrete ASG -// -// Copyright (C) 2014-2022 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 . - -//! Base concrete [`Asg`] implementation. - -use super::graph::{Asg, AsgEdge, AsgResult, Node, ObjectRef}; -use super::ident::IdentKind; -use super::object::{ - FragmentText, IdentObjectData, IdentObjectState, Source, TransitionResult, -}; -use crate::global; -use crate::sym::SymbolId; -use petgraph::graph::{DiGraph, Graph, NodeIndex}; - -/// Index size for Graph nodes and edges. -type Ix = global::ProgSymSize; - -/// Concrete ASG. -/// -/// This implementation is currently based on [`petgraph`]. -/// -/// Identifiers are cached by name for `O(1)` lookup. -/// Since [`SymbolId`][crate::sym::SymbolId] is used for this purpose, -/// the index may contain more entries than nodes and may contain gaps. -/// -/// For more information, -/// see [`Asg`]. -#[derive(Debug, Default)] -pub struct BaseAsg { - // TODO: private; see `ld::xmle::lower`. - /// Directed graph on which objects are stored. - pub graph: DiGraph, AsgEdge, Ix>, - - /// Map of [`SymbolId`][crate::sym::SymbolId] to node indexes. - /// - /// This allows for `O(1)` lookup of identifiers in the graph. - /// Note that, - /// while we store [`NodeIndex`] internally, - /// the public API encapsulates it within an [`ObjectRef`]. - index: Vec>, - - /// Empty node indicating that no object exists for a given index. - empty_node: NodeIndex, -} - -impl BaseAsg -where - O: IdentObjectState + IdentObjectData, -{ - /// 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 - /// in the graph, - /// as well as the initial index capacity. - /// The value for `edges` may be more difficult to consider, - /// since edges are used to represent various relationships between - /// different types of objects, - /// but it's safe to say that each object will have at least one - /// edge to another object. - pub fn with_capacity(objects: usize, edges: usize) -> Self { - let mut graph = Graph::with_capacity(objects, edges); - let mut index = Vec::with_capacity(objects); - - // Exhaust the first index to be used as a placeholder. - let empty_node = graph.add_node(None); - index.push(empty_node); - - Self { - graph, - index, - empty_node, - } - } - - /// Get the underlying Graph - pub fn into_inner(self) -> DiGraph, AsgEdge, Ix> { - self.graph - } - - /// Index the provided symbol `name` as representing the identifier `node`. - /// - /// This index permits `O(1)` identifier lookups. - /// - /// After an identifier is indexed it is not expected to be reassigned - /// to another node. - /// Debug builds contain an assertion that will panic in this instance. - /// - /// Panics - /// ====== - /// Will panic if unable to allocate more space for the index. - fn index_identifier(&mut self, name: SymbolId, node: NodeIndex) { - let i = name.as_usize(); - - if i >= self.index.len() { - // If this is ever a problem we can fall back to usize max and - // re-compare before panicing - let new_size = (i + 1) - .checked_next_power_of_two() - .expect("internal error: cannot allocate space for ASG index"); - - self.index.resize(new_size, self.empty_node); - } - - // We should never overwrite indexes - debug_assert!(self.index[i] == self.empty_node); - - self.index[i] = node; - } - - /// Lookup `ident` or add a missing identifier to the graph and return a - /// reference to it. - /// - /// See [`IdentObjectState::declare`] for more information. - fn lookup_or_missing(&mut self, ident: SymbolId) -> ObjectRef { - self.lookup(ident).unwrap_or_else(|| { - let index = self.graph.add_node(Some(O::declare(ident))); - - self.index_identifier(ident, index); - ObjectRef::new(index) - }) - } - - /// Perform a state transition on an identifier by name. - /// - /// Look up `ident` or add a missing identifier if it does not yet exist - /// (see `lookup_or_missing`). - /// Then invoke `f` with the located identifier and replace the - /// identifier on the graph with the result. - /// - /// This will safely restore graph state to the original identifier - /// value on transition failure. - fn with_ident_lookup( - &mut self, - name: SymbolId, - f: F, - ) -> AsgResult - where - F: FnOnce(O) -> TransitionResult, - { - let identi = self.lookup_or_missing(name); - self.with_ident(identi, f) - } - - /// Perform a state transition on an identifier by [`ObjectRef`]. - /// - /// Invoke `f` with the located identifier and replace the identifier on - /// the graph with the result. - /// - /// This will safely restore graph state to the original identifier - /// value on transition failure. - fn with_ident(&mut self, identi: ObjectRef, f: F) -> AsgResult - where - F: FnOnce(O) -> TransitionResult, - { - let node = self.graph.node_weight_mut(identi.into()).unwrap(); - - let obj = node - .take() - .expect(&format!("internal error: missing object")); - - f(obj) - .and_then(|obj| { - node.replace(obj); - Ok(identi) - }) - .or_else(|(orig, err)| { - node.replace(orig); - Err(err.into()) - }) - } -} - -impl Asg for BaseAsg -where - O: IdentObjectState + IdentObjectData, -{ - fn declare( - &mut self, - name: SymbolId, - kind: IdentKind, - src: Source, - ) -> AsgResult { - self.with_ident_lookup(name, |obj| obj.resolve(kind, src)) - } - - fn declare_extern( - &mut self, - name: SymbolId, - kind: IdentKind, - src: Source, - ) -> AsgResult { - self.with_ident_lookup(name, |obj| obj.extern_(kind, src)) - } - - fn set_fragment( - &mut self, - identi: ObjectRef, - text: FragmentText, - ) -> AsgResult { - self.with_ident(identi, |obj| obj.set_fragment(text)) - } - - #[inline] - fn get>(&self, index: I) -> Option<&O> { - self.graph.node_weight(index.into().into()).map(|node| { - node.as_ref() - .expect("internal error: BaseAsg::get missing Node data") - }) - } - - #[inline] - fn lookup(&self, name: SymbolId) -> Option { - let i = name.as_usize(); - - self.index - .get(i) - .filter(|ni| ni.index() > 0) - .map(|ni| ObjectRef::new(*ni)) - } - - fn add_dep(&mut self, identi: ObjectRef, depi: ObjectRef) { - self.graph - .update_edge(identi.into(), depi.into(), Default::default()); - } - - #[inline] - fn has_dep(&self, ident: ObjectRef, dep: ObjectRef) -> bool { - self.graph.contains_edge(ident.into(), dep.into()) - } - - fn add_dep_lookup( - &mut self, - ident: SymbolId, - dep: SymbolId, - ) -> (ObjectRef, ObjectRef) { - let identi = self.lookup_or_missing(ident); - let depi = self.lookup_or_missing(dep); - - self.graph - .update_edge(identi.into(), depi.into(), Default::default()); - - (identi, depi) - } -} - -#[cfg(test)] -mod test { - use super::super::graph::AsgError; - use super::*; - use crate::asg::{ - Dim, IdentObject, TransitionError, TransitionResult, UnresolvedError, - }; - use crate::sym::{GlobalSymbolIntern, SymbolId}; - use std::cell::RefCell; - - #[derive(Debug, Default, PartialEq)] - struct StubIdentObject { - given_declare: Option, - given_extern: Option<(IdentKind, Source)>, - given_resolve: Option<(IdentKind, Source)>, - given_set_fragment: Option, - fail_redeclare: RefCell>, - fail_extern: RefCell>, - fail_set_fragment: RefCell>, - fail_resolved: RefCell>, - } - - impl<'i> IdentObjectData for StubIdentObject { - fn name(&self) -> Option { - self.given_declare - } - - fn kind(&self) -> Option<&IdentKind> { - self.given_resolve.as_ref().map(|args| &args.0) - } - - fn src(&self) -> Option<&Source> { - None - } - - fn fragment(&self) -> Option { - None - } - - fn as_ident(&self) -> Option<&IdentObject> { - None - } - } - - impl<'i> IdentObjectState for StubIdentObject { - fn declare(ident: SymbolId) -> Self { - Self { - given_declare: Some(ident), - ..Default::default() - } - } - - fn resolve( - mut self, - kind: IdentKind, - src: Source, - ) -> TransitionResult { - if self.fail_redeclare.borrow().is_some() { - let err = self.fail_redeclare.replace(None).unwrap(); - return Err((self, err)); - } - - self.given_resolve = Some((kind, src)); - Ok(self) - } - - fn resolved(&self) -> Result<&StubIdentObject, UnresolvedError> { - if self.fail_resolved.borrow().is_some() { - return Err(self.fail_resolved.replace(None).unwrap()); - } - - Ok(self) - } - - fn extern_( - mut self, - kind: IdentKind, - src: Source, - ) -> TransitionResult { - if self.fail_extern.borrow().is_some() { - let err = self.fail_extern.replace(None).unwrap(); - return Err((self, err)); - } - - self.given_extern = Some((kind, src)); - Ok(self) - } - - fn set_fragment( - mut self, - text: FragmentText, - ) -> TransitionResult { - if self.fail_set_fragment.borrow().is_some() { - let err = self.fail_set_fragment.replace(None).unwrap(); - return Err((self, err)); - } - - self.given_set_fragment.replace(text); - Ok(self) - } - } - - type Sut = BaseAsg; - - #[test] - fn create_with_capacity() { - let node_capacity = 100; - let edge_capacity = 300; - let sut = Sut::with_capacity(node_capacity, edge_capacity); - - // breaks encapsulation to introspect; the behavior is - // transparent to callers (aside from performance - // characteristics) - let (nc, ec) = sut.graph.capacity(); - assert!(nc >= node_capacity); - assert!(ec >= edge_capacity); - assert!(sut.index.capacity() >= node_capacity); - } - - #[test] - fn declare_new_unique_idents() -> AsgResult<()> { - 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 - // to ensure that it's not considered an already-defined - // identifier. - let syma = "syma".intern(); - let symb = "symab".intern(); - - let nodea = sut.declare( - syma, - IdentKind::Meta, - Source { - desc: Some("a".into()), - ..Default::default() - }, - )?; - - let nodeb = sut.declare( - symb, - IdentKind::Worksheet, - Source { - desc: Some("b".into()), - ..Default::default() - }, - )?; - - assert_ne!(nodea, nodeb); - - assert_eq!(Some(syma), sut.get(nodea).unwrap().given_declare); - assert_eq!( - Some(( - IdentKind::Meta, - Source { - desc: Some("a".into()), - ..Default::default() - }, - )), - sut.get(nodea).unwrap().given_resolve - ); - - assert_eq!(Some(symb), sut.get(nodeb).unwrap().given_declare); - assert_eq!( - Some(( - IdentKind::Worksheet, - Source { - desc: Some("b".into()), - ..Default::default() - }, - )), - sut.get(nodeb).unwrap().given_resolve - ); - - Ok(()) - } - - #[test] - fn lookup_by_symbol() -> AsgResult<()> { - let mut sut = Sut::new(); - - let sym = "lookup".into(); - let node = sut.declare( - sym, - IdentKind::Meta, - Source { - generated: true, - ..Default::default() - }, - )?; - - assert_eq!(Some(node), sut.lookup(sym)); - - Ok(()) - } - - #[test] - fn declare_returns_existing() -> AsgResult<()> { - let mut sut = Sut::new(); - - let sym = "symdup".into(); - let src = Source::default(); - let node = sut.declare(sym, IdentKind::Meta, src.clone())?; - - // Remember that our stub does not care about compatibility. - let rekind = IdentKind::Class(Dim::from_u8(3)); - let resrc = Source { - desc: Some("redeclare".into()), - ..Default::default() - }; - let redeclare = sut.declare(sym, rekind.clone(), resrc.clone())?; - - // We don't care what the objects are for this test, just that the - // same node is referenced. - assert_eq!(node, redeclare); - - assert_eq!(Some((rekind, resrc)), sut.get(node).unwrap().given_resolve,); - - Ok(()) - } - - // Builds upon declare_returns_existing. - #[test] - fn declare_fails_if_transition_fails() -> AsgResult<()> { - let mut sut = Sut::new(); - - let sym = "symdup".intern(); - let src = Source { - desc: Some("orig".into()), - ..Default::default() - }; - - // Set up an object to fail redeclaration. - let node = sut.declare(sym, IdentKind::Meta, src.clone())?; - let obj = sut.get(node).unwrap(); - let terr = TransitionError::ExternResolution { - name: "test fail".into(), - expected: IdentKind::Meta, - given: IdentKind::Meta, - }; - obj.fail_redeclare.replace(Some(terr.clone())); - - // Should invoke StubIdentObject::redeclare on the above `obj`. - let result = sut.declare(sym, IdentKind::Meta, Source::default()); - - if let Err(err) = result { - // The node should have been restored. - let obj = sut.get(node).unwrap(); - assert_eq!(src, obj.given_resolve.as_ref().unwrap().1); - - assert_eq!(AsgError::ObjectTransition(terr), err); - - Ok(()) - } else { - panic!("failure expected: {:?}", result); - } - } - - #[test] - fn declare_extern_returns_existing() -> AsgResult<()> { - let mut sut = Sut::new(); - - let sym = "symext".intern(); - let src = Source::default(); - let node = sut.declare_extern(sym, IdentKind::Meta, src.clone())?; - - // Remember that our stub does not care about compatibility. - let rekind = IdentKind::Class(Dim::from_u8(3)); - let resrc = Source { - desc: Some("redeclare".into()), - ..Default::default() - }; - let redeclare = - sut.declare_extern(sym, rekind.clone(), resrc.clone())?; - - // We don't care what the objects are for this test, just that the - // same node is referenced. - assert_eq!(node, redeclare); - assert_eq!(Some((rekind, resrc)), sut.get(node).unwrap().given_extern); - - Ok(()) - } - - // Builds upon declare_returns_existing. - #[test] - fn declare_extern_fails_if_transition_fails() -> AsgResult<()> { - let mut sut = Sut::new(); - - let sym = "symdup".intern(); - let src = Source { - desc: Some("orig".into()), - ..Default::default() - }; - - // Set up an object to fail redeclaration. - let node = sut.declare_extern(sym, IdentKind::Meta, src.clone())?; - let obj = sut.get(node).unwrap(); - - // It doesn't matter that this isn't the error that'll actually be - // returned, as long as it's some sort of TransitionError. - let terr = TransitionError::ExternResolution { - name: "test fail".into(), - expected: IdentKind::Meta, - given: IdentKind::Meta, - }; - obj.fail_extern.replace(Some(terr.clone())); - - // Should invoke StubIdentObject::extern_ on the above `obj`. - let result = - sut.declare_extern(sym, IdentKind::Meta, Source::default()); - - if let Err(err) = result { - // The node should have been restored. - let obj = sut.get(node).unwrap(); - - assert_eq!(src, obj.given_extern.as_ref().unwrap().1); - assert_eq!(AsgError::ObjectTransition(terr), err); - - Ok(()) - } else { - panic!("failure expected: {:?}", result); - } - } - - #[test] - fn add_fragment_to_ident() -> AsgResult<()> { - let mut sut = Sut::new(); - - let sym = "tofrag".intern(); - let src = Source { - generated: true, - ..Default::default() - }; - let node = sut.declare(sym, IdentKind::Meta, src.clone())?; - - let fragment = "a fragment".intern(); - let node_with_frag = sut.set_fragment(node, fragment)?; - - // Attaching a fragment should _replace_ the node, not create a - // new one - assert_eq!( - node, node_with_frag, - "fragment node does not match original node" - ); - - let obj = sut.get(node).unwrap(); - - assert_eq!(Some(sym), obj.given_declare); - assert_eq!(Some((IdentKind::Meta, src)), obj.given_resolve); - assert_eq!(Some(fragment), obj.given_set_fragment); - - Ok(()) - } - - #[test] - fn add_fragment_to_ident_fails_if_transition_fails() -> AsgResult<()> { - let mut sut = Sut::new(); - - let sym = "failfrag".intern(); - let src = Source { - generated: true, - ..Default::default() - }; - - // The failure will come from terr below, not this. - let node = sut.declare(sym, IdentKind::Meta, src.clone())?; - let obj = sut.get(node).unwrap(); - - // It doesn't matter that this isn't the error that'll actually be - // returned, as long as it's some sort of TransitionError. - let terr = TransitionError::BadFragmentDest { - name: String::from("test fail"), - }; - obj.fail_set_fragment.replace(Some(terr.clone())); - - let result = sut - .set_fragment(node, "".into()) - .expect_err("error expected"); - - // The node should have been restored. - let obj = sut.get(node).unwrap(); - - assert_eq!(sym, *obj.given_declare.as_ref().unwrap()); - assert_eq!(AsgError::ObjectTransition(terr), result); - - Ok(()) - } - - #[test] - fn add_ident_dep_to_ident() -> AsgResult<()> { - let mut sut = Sut::new(); - - let sym = "sym".intern(); - let dep = "dep".intern(); - - let symnode = sut.declare(sym, IdentKind::Meta, Source::default())?; - let depnode = sut.declare(dep, IdentKind::Meta, Source::default())?; - - sut.add_dep(symnode, depnode); - assert!(sut.has_dep(symnode, depnode)); - - // sanity check if we re-add a dep - sut.add_dep(symnode, depnode); - assert!(sut.has_dep(symnode, depnode)); - - Ok(()) - } - - // same as above test - #[test] - fn add_dep_lookup_existing() -> AsgResult<()> { - let mut sut = Sut::new(); - - let sym = "sym".intern(); - let dep = "dep".intern(); - - let _ = sut.declare(sym, IdentKind::Meta, Source::default())?; - let _ = sut.declare(dep, IdentKind::Meta, Source::default())?; - - let (symnode, depnode) = sut.add_dep_lookup(sym, dep); - assert!(sut.has_dep(symnode, depnode)); - - Ok(()) - } - - #[test] - fn add_dep_lookup_missing() -> AsgResult<()> { - let mut sut = Sut::new(); - - let sym = "sym".intern(); - let dep = "dep".intern(); - - // both of these are missing - let (symnode, depnode) = sut.add_dep_lookup(sym, dep); - assert!(sut.has_dep(symnode, depnode)); - - assert_eq!(Some(sym), sut.get(symnode).unwrap().given_declare); - assert_eq!(Some(dep), sut.get(depnode).unwrap().given_declare); - - Ok(()) - } - - #[test] - fn declare_return_missing_symbol() -> AsgResult<()> { - let mut sut = Sut::new(); - - let sym = "sym".intern(); - let dep = "dep".intern(); - - // both of these are missing, see add_dep_lookup_missing - let (symnode, _) = sut.add_dep_lookup(sym, dep); - - let src = Source { - desc: Some("redeclare missing".into()), - ..Default::default() - }; - - // Check with a declared value - let declared = sut.declare(sym, IdentKind::Meta, src.clone())?; - - assert_eq!(symnode, declared); - - let obj = sut.get(declared).unwrap(); - - assert_eq!(Some(sym), obj.given_declare); - assert_eq!(Some((IdentKind::Meta, src)), obj.given_resolve); - - Ok(()) - } -} diff --git a/tamer/src/asg/error.rs b/tamer/src/asg/error.rs new file mode 100644 index 00000000..ca771913 --- /dev/null +++ b/tamer/src/asg/error.rs @@ -0,0 +1,62 @@ +// Abstract semantic graph (ASG) errors +// +// Copyright (C) 2014-2022 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 . + +//! Errors resulting from operations on the ASG. + +use std::{ + error::Error, + fmt::{self, Display}, +}; + +use super::TransitionError; + +/// An error from an ASG operation. +#[derive(Debug, PartialEq)] +pub enum AsgError { + /// An object could not change state in the manner requested. + ObjectTransition(TransitionError), + /// The node was not expected in the current context + UnexpectedNode(String), +} + +impl Display for AsgError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::ObjectTransition(err) => Display::fmt(&err, fmt), + Self::UnexpectedNode(msg) => { + write!(fmt, "unexpected node: {}", msg) + } + } + } +} + +impl Error for AsgError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::ObjectTransition(err) => err.source(), + _ => None, + } + } +} + +impl From for AsgError { + fn from(err: TransitionError) -> Self { + Self::ObjectTransition(err) + } +} diff --git a/tamer/src/asg/graph.rs b/tamer/src/asg/graph.rs index f8869b5c..b745a15c 100644 --- a/tamer/src/asg/graph.rs +++ b/tamer/src/asg/graph.rs @@ -20,17 +20,44 @@ //! Abstract graph as the basis for concrete ASGs. use super::ident::IdentKind; -use super::object::{FragmentText, IdentObjectState, Source, TransitionError}; +use super::object::{ + FragmentText, IdentObjectData, IdentObjectState, Source, TransitionResult, +}; +use super::AsgError; +use crate::global; use crate::sym::SymbolId; -use petgraph::graph::NodeIndex; +use petgraph::graph::{DiGraph, Graph, NodeIndex}; use std::fmt::Debug; use std::result::Result; /// Datatype representing node and edge indexes. -pub trait IndexType: petgraph::graph::IndexType {} -impl IndexType for T {} +pub trait IndexType = petgraph::graph::IndexType; -/// An abstract semantic graph of [objects][super::object]. +/// A [`Result`] with a hard-coded [`AsgError`] error type. +/// +/// This is the result of every [`Asg`] operation that could potentially +/// fail in error. +pub type AsgResult = Result; + +/// There are currently no data stored on edges ("edge weights"). +pub type AsgEdge = (); + +/// Each node of the graph represents an object. +/// +/// Enclosed in an [`Option`] to permit moving owned values out of the +/// graph. +pub type Node = Option; + +/// Index size for Graph nodes and edges. +type Ix = global::ProgSymSize; + +/// An abstract semantic graph (ASG) of [objects][super::object]. +/// +/// This implementation is currently based on [`petgraph`]. +/// +/// Identifiers are cached by name for `O(1)` lookup. +/// Since [`SymbolId`][crate::sym::SymbolId] is used for this purpose, +/// the index may contain more entries than nodes and may contain gaps. /// /// This IR focuses on the definition and manipulation of objects and their /// dependencies. @@ -42,10 +69,157 @@ impl IndexType for T {} /// /// For more information, /// see the [module-level documentation][self]. -pub trait Asg +#[derive(Debug, Default)] +pub struct Asg { + // TODO: private; see `ld::xmle::lower`. + /// Directed graph on which objects are stored. + pub graph: DiGraph, AsgEdge, Ix>, + + /// Map of [`SymbolId`][crate::sym::SymbolId] to node indexes. + /// + /// This allows for `O(1)` lookup of identifiers in the graph. + /// Note that, + /// while we store [`NodeIndex`] internally, + /// the public API encapsulates it within an [`ObjectRef`]. + index: Vec>, + + /// Empty node indicating that no object exists for a given index. + empty_node: NodeIndex, +} + +impl Asg where - O: IdentObjectState, + O: IdentObjectState + IdentObjectData, { + /// Create a new ASG. + /// + /// See also [`with_capacity`](Asg::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 + /// in the graph, + /// as well as the initial index capacity. + /// The value for `edges` may be more difficult to consider, + /// since edges are used to represent various relationships between + /// different types of objects, + /// but it's safe to say that each object will have at least one + /// edge to another object. + pub fn with_capacity(objects: usize, edges: usize) -> Self { + let mut graph = Graph::with_capacity(objects, edges); + let mut index = Vec::with_capacity(objects); + + // Exhaust the first index to be used as a placeholder. + let empty_node = graph.add_node(None); + index.push(empty_node); + + Self { + graph, + index, + empty_node, + } + } + + /// Get the underlying Graph + pub fn into_inner(self) -> DiGraph, AsgEdge, Ix> { + self.graph + } + + /// Index the provided symbol `name` as representing the identifier `node`. + /// + /// This index permits `O(1)` identifier lookups. + /// + /// After an identifier is indexed it is not expected to be reassigned + /// to another node. + /// Debug builds contain an assertion that will panic in this instance. + /// + /// Panics + /// ====== + /// Will panic if unable to allocate more space for the index. + fn index_identifier(&mut self, name: SymbolId, node: NodeIndex) { + let i = name.as_usize(); + + if i >= self.index.len() { + // If this is ever a problem we can fall back to usize max and + // re-compare before panicing + let new_size = (i + 1) + .checked_next_power_of_two() + .expect("internal error: cannot allocate space for ASG index"); + + self.index.resize(new_size, self.empty_node); + } + + // We should never overwrite indexes + debug_assert!(self.index[i] == self.empty_node); + + self.index[i] = node; + } + + /// Lookup `ident` or add a missing identifier to the graph and return a + /// reference to it. + /// + /// See [`IdentObjectState::declare`] for more information. + fn lookup_or_missing(&mut self, ident: SymbolId) -> ObjectRef { + self.lookup(ident).unwrap_or_else(|| { + let index = self.graph.add_node(Some(O::declare(ident))); + + self.index_identifier(ident, index); + ObjectRef::new(index) + }) + } + + /// Perform a state transition on an identifier by name. + /// + /// Look up `ident` or add a missing identifier if it does not yet exist + /// (see `lookup_or_missing`). + /// Then invoke `f` with the located identifier and replace the + /// identifier on the graph with the result. + /// + /// This will safely restore graph state to the original identifier + /// value on transition failure. + fn with_ident_lookup( + &mut self, + name: SymbolId, + f: F, + ) -> AsgResult + where + F: FnOnce(O) -> TransitionResult, + { + let identi = self.lookup_or_missing(name); + self.with_ident(identi, f) + } + + /// Perform a state transition on an identifier by [`ObjectRef`]. + /// + /// Invoke `f` with the located identifier and replace the identifier on + /// the graph with the result. + /// + /// This will safely restore graph state to the original identifier + /// value on transition failure. + fn with_ident(&mut self, identi: ObjectRef, f: F) -> AsgResult + where + F: FnOnce(O) -> TransitionResult, + { + let node = self.graph.node_weight_mut(identi.into()).unwrap(); + + let obj = node + .take() + .expect(&format!("internal error: missing object")); + + f(obj) + .and_then(|obj| { + node.replace(obj); + Ok(identi) + }) + .or_else(|(orig, err)| { + node.replace(orig); + Err(err.into()) + }) + } + /// Declare a concrete identifier. /// /// An identifier declaration is similar to a declaration in a header @@ -77,12 +251,14 @@ where /// /// A successful declaration will add an identifier to the graph /// and return an [`ObjectRef`] reference. - fn declare( + pub fn declare( &mut self, name: SymbolId, kind: IdentKind, src: Source, - ) -> AsgResult; + ) -> AsgResult { + self.with_ident_lookup(name, |obj| obj.resolve(kind, src)) + } /// Declare an abstract identifier. /// @@ -104,23 +280,27 @@ where /// See [`IdentObjectState::extern_`] and /// [`IdentObjectState::resolve`] for more information on /// compatibility related to extern resolution. - fn declare_extern( + pub fn declare_extern( &mut self, name: SymbolId, kind: IdentKind, src: Source, - ) -> AsgResult; + ) -> AsgResult { + self.with_ident_lookup(name, |obj| obj.extern_(kind, src)) + } /// Set the fragment associated with a concrete identifier. /// /// Fragments are intended for use by the [linker][crate::ld]. /// For more information, /// see [`IdentObjectState::set_fragment`]. - fn set_fragment( + pub fn set_fragment( &mut self, identi: ObjectRef, text: FragmentText, - ) -> AsgResult; + ) -> AsgResult { + self.with_ident(identi, |obj| obj.set_fragment(text)) + } /// Retrieve an object from the graph by [`ObjectRef`]. /// @@ -129,7 +309,13 @@ where /// this should never fail so long as references are not shared /// between multiple graphs. /// It is nevertheless wrapped in an [`Option`] just in case. - fn get>(&self, index: I) -> Option<&O>; + #[inline] + pub fn get>(&self, index: I) -> Option<&O> { + self.graph.node_weight(index.into().into()).map(|node| { + node.as_ref() + .expect("internal error: Asg::get missing Node data") + }) + } /// Attempt to retrieve an identifier from the graph by name. /// @@ -137,7 +323,15 @@ where /// this method cannot be used to retrieve all possible objects on the /// graph---for /// that, see [`Asg::get`]. - fn lookup(&self, name: SymbolId) -> Option; + #[inline] + pub fn lookup(&self, name: SymbolId) -> Option { + let i = name.as_usize(); + + self.index + .get(i) + .filter(|ni| ni.index() > 0) + .map(|ni| ObjectRef::new(*ni)) + } /// Declare that `dep` is a dependency of `ident`. /// @@ -148,10 +342,16 @@ where /// See [`add_dep_lookup`][Asg::add_dep_lookup] if identifiers have to /// be looked up by [`SymbolId`] or if they may not yet have been /// declared. - fn add_dep(&mut self, ident: ObjectRef, dep: ObjectRef); + pub fn add_dep(&mut self, identi: ObjectRef, depi: ObjectRef) { + self.graph + .update_edge(identi.into(), depi.into(), Default::default()); + } /// Check whether `dep` is a dependency of `ident`. - fn has_dep(&self, ident: ObjectRef, dep: ObjectRef) -> bool; + #[inline] + pub fn has_dep(&self, ident: ObjectRef, dep: ObjectRef) -> bool { + self.graph.contains_edge(ident.into(), dep.into()) + } /// Declare that `dep` is a dependency of `ident`, /// regardless of whether they are known. @@ -166,18 +366,20 @@ where /// See [`IdentObjectState::declare`] for more information. /// /// References to both identifiers are returned in argument order. - fn add_dep_lookup( + pub fn add_dep_lookup( &mut self, ident: SymbolId, dep: SymbolId, - ) -> (ObjectRef, ObjectRef); -} + ) -> (ObjectRef, ObjectRef) { + let identi = self.lookup_or_missing(ident); + let depi = self.lookup_or_missing(dep); -/// A [`Result`] with a hard-coded [`AsgError`] error type. -/// -/// This is the result of every [`Asg`] operation that could potentially -/// fail in error. -pub type AsgResult = Result; + self.graph + .update_edge(identi.into(), depi.into(), Default::default()); + + (identi, depi) + } +} /// Reference to an [object][super::object] stored within the [`Asg`]. /// @@ -205,69 +407,473 @@ impl From for NodeIndex { } } -/// There are currently no data stored on edges ("edge weights"). -pub type AsgEdge = (); - -/// Each node of the graph represents an object. -/// -/// Enclosed in an [`Option`] to permit moving owned values out of the -/// graph. -pub type Node = Option; - -/// An error from an ASG operation. -#[derive(Debug, PartialEq)] -pub enum AsgError { - /// An object could not change state in the manner requested. - /// - /// See [`Asg::declare`] and [`Asg::set_fragment`] for more - /// information. - /// See also [`TransitionError`]. - ObjectTransition(TransitionError), - - /// The node was not expected in the current context - UnexpectedNode(String), -} - -impl std::fmt::Display for AsgError { - 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) - } - } - } -} - -impl std::error::Error for AsgError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::ObjectTransition(err) => err.source(), - _ => None, - } - } -} - -impl From for AsgError { - fn from(err: TransitionError) -> Self { - Self::ObjectTransition(err) - } -} - #[cfg(test)] mod test { + use super::super::error::AsgError; use super::*; + use crate::asg::{ + Dim, IdentObject, TransitionError, TransitionResult, UnresolvedError, + }; + use crate::sym::{GlobalSymbolIntern, SymbolId}; + use std::cell::RefCell; - mod objref { - use super::*; + #[derive(Debug, Default, PartialEq)] + struct StubIdentObject { + given_declare: Option, + given_extern: Option<(IdentKind, Source)>, + given_resolve: Option<(IdentKind, Source)>, + given_set_fragment: Option, + fail_redeclare: RefCell>, + fail_extern: RefCell>, + fail_set_fragment: RefCell>, + fail_resolved: RefCell>, + } - #[test] - fn to_from_nodeindex() { - let index = NodeIndex::::new(5); - let objref: ObjectRef = ObjectRef::from(index); + impl<'i> IdentObjectData for StubIdentObject { + fn name(&self) -> Option { + self.given_declare + } - assert_eq!(index, objref.0); - assert_eq!(index, objref.into()); + fn kind(&self) -> Option<&IdentKind> { + self.given_resolve.as_ref().map(|args| &args.0) + } + + fn src(&self) -> Option<&Source> { + None + } + + fn fragment(&self) -> Option { + None + } + + fn as_ident(&self) -> Option<&IdentObject> { + None } } + + impl<'i> IdentObjectState for StubIdentObject { + fn declare(ident: SymbolId) -> Self { + Self { + given_declare: Some(ident), + ..Default::default() + } + } + + fn resolve( + mut self, + kind: IdentKind, + src: Source, + ) -> TransitionResult { + if self.fail_redeclare.borrow().is_some() { + let err = self.fail_redeclare.replace(None).unwrap(); + return Err((self, err)); + } + + self.given_resolve = Some((kind, src)); + Ok(self) + } + + fn resolved(&self) -> Result<&StubIdentObject, UnresolvedError> { + if self.fail_resolved.borrow().is_some() { + return Err(self.fail_resolved.replace(None).unwrap()); + } + + Ok(self) + } + + fn extern_( + mut self, + kind: IdentKind, + src: Source, + ) -> TransitionResult { + if self.fail_extern.borrow().is_some() { + let err = self.fail_extern.replace(None).unwrap(); + return Err((self, err)); + } + + self.given_extern = Some((kind, src)); + Ok(self) + } + + fn set_fragment( + mut self, + text: FragmentText, + ) -> TransitionResult { + if self.fail_set_fragment.borrow().is_some() { + let err = self.fail_set_fragment.replace(None).unwrap(); + return Err((self, err)); + } + + self.given_set_fragment.replace(text); + Ok(self) + } + } + + type Sut = Asg; + + #[test] + fn create_with_capacity() { + let node_capacity = 100; + let edge_capacity = 300; + let sut = Sut::with_capacity(node_capacity, edge_capacity); + + // breaks encapsulation to introspect; the behavior is + // transparent to callers (aside from performance + // characteristics) + let (nc, ec) = sut.graph.capacity(); + assert!(nc >= node_capacity); + assert!(ec >= edge_capacity); + assert!(sut.index.capacity() >= node_capacity); + } + + #[test] + fn declare_new_unique_idents() -> AsgResult<()> { + 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 + // to ensure that it's not considered an already-defined + // identifier. + let syma = "syma".intern(); + let symb = "symab".intern(); + + let nodea = sut.declare( + syma, + IdentKind::Meta, + Source { + desc: Some("a".into()), + ..Default::default() + }, + )?; + + let nodeb = sut.declare( + symb, + IdentKind::Worksheet, + Source { + desc: Some("b".into()), + ..Default::default() + }, + )?; + + assert_ne!(nodea, nodeb); + + assert_eq!(Some(syma), sut.get(nodea).unwrap().given_declare); + assert_eq!( + Some(( + IdentKind::Meta, + Source { + desc: Some("a".into()), + ..Default::default() + }, + )), + sut.get(nodea).unwrap().given_resolve + ); + + assert_eq!(Some(symb), sut.get(nodeb).unwrap().given_declare); + assert_eq!( + Some(( + IdentKind::Worksheet, + Source { + desc: Some("b".into()), + ..Default::default() + }, + )), + sut.get(nodeb).unwrap().given_resolve + ); + + Ok(()) + } + + #[test] + fn lookup_by_symbol() -> AsgResult<()> { + let mut sut = Sut::new(); + + let sym = "lookup".into(); + let node = sut.declare( + sym, + IdentKind::Meta, + Source { + generated: true, + ..Default::default() + }, + )?; + + assert_eq!(Some(node), sut.lookup(sym)); + + Ok(()) + } + + #[test] + fn declare_returns_existing() -> AsgResult<()> { + let mut sut = Sut::new(); + + let sym = "symdup".into(); + let src = Source::default(); + let node = sut.declare(sym, IdentKind::Meta, src.clone())?; + + // Remember that our stub does not care about compatibility. + let rekind = IdentKind::Class(Dim::from_u8(3)); + let resrc = Source { + desc: Some("redeclare".into()), + ..Default::default() + }; + let redeclare = sut.declare(sym, rekind.clone(), resrc.clone())?; + + // We don't care what the objects are for this test, just that the + // same node is referenced. + assert_eq!(node, redeclare); + + assert_eq!(Some((rekind, resrc)), sut.get(node).unwrap().given_resolve,); + + Ok(()) + } + + // Builds upon declare_returns_existing. + #[test] + fn declare_fails_if_transition_fails() -> AsgResult<()> { + let mut sut = Sut::new(); + + let sym = "symdup".intern(); + let src = Source { + desc: Some("orig".into()), + ..Default::default() + }; + + // Set up an object to fail redeclaration. + let node = sut.declare(sym, IdentKind::Meta, src.clone())?; + let obj = sut.get(node).unwrap(); + let terr = TransitionError::ExternResolution { + name: "test fail".into(), + expected: IdentKind::Meta, + given: IdentKind::Meta, + }; + obj.fail_redeclare.replace(Some(terr.clone())); + + // Should invoke StubIdentObject::redeclare on the above `obj`. + let result = sut.declare(sym, IdentKind::Meta, Source::default()); + + if let Err(err) = result { + // The node should have been restored. + let obj = sut.get(node).unwrap(); + assert_eq!(src, obj.given_resolve.as_ref().unwrap().1); + + assert_eq!(AsgError::ObjectTransition(terr), err); + + Ok(()) + } else { + panic!("failure expected: {:?}", result); + } + } + + #[test] + fn declare_extern_returns_existing() -> AsgResult<()> { + let mut sut = Sut::new(); + + let sym = "symext".intern(); + let src = Source::default(); + let node = sut.declare_extern(sym, IdentKind::Meta, src.clone())?; + + // Remember that our stub does not care about compatibility. + let rekind = IdentKind::Class(Dim::from_u8(3)); + let resrc = Source { + desc: Some("redeclare".into()), + ..Default::default() + }; + let redeclare = + sut.declare_extern(sym, rekind.clone(), resrc.clone())?; + + // We don't care what the objects are for this test, just that the + // same node is referenced. + assert_eq!(node, redeclare); + assert_eq!(Some((rekind, resrc)), sut.get(node).unwrap().given_extern); + + Ok(()) + } + + // Builds upon declare_returns_existing. + #[test] + fn declare_extern_fails_if_transition_fails() -> AsgResult<()> { + let mut sut = Sut::new(); + + let sym = "symdup".intern(); + let src = Source { + desc: Some("orig".into()), + ..Default::default() + }; + + // Set up an object to fail redeclaration. + let node = sut.declare_extern(sym, IdentKind::Meta, src.clone())?; + let obj = sut.get(node).unwrap(); + + // It doesn't matter that this isn't the error that'll actually be + // returned, as long as it's some sort of TransitionError. + let terr = TransitionError::ExternResolution { + name: "test fail".into(), + expected: IdentKind::Meta, + given: IdentKind::Meta, + }; + obj.fail_extern.replace(Some(terr.clone())); + + // Should invoke StubIdentObject::extern_ on the above `obj`. + let result = + sut.declare_extern(sym, IdentKind::Meta, Source::default()); + + if let Err(err) = result { + // The node should have been restored. + let obj = sut.get(node).unwrap(); + + assert_eq!(src, obj.given_extern.as_ref().unwrap().1); + assert_eq!(AsgError::ObjectTransition(terr), err); + + Ok(()) + } else { + panic!("failure expected: {:?}", result); + } + } + + #[test] + fn add_fragment_to_ident() -> AsgResult<()> { + let mut sut = Sut::new(); + + let sym = "tofrag".intern(); + let src = Source { + generated: true, + ..Default::default() + }; + let node = sut.declare(sym, IdentKind::Meta, src.clone())?; + + let fragment = "a fragment".intern(); + let node_with_frag = sut.set_fragment(node, fragment)?; + + // Attaching a fragment should _replace_ the node, not create a + // new one + assert_eq!( + node, node_with_frag, + "fragment node does not match original node" + ); + + let obj = sut.get(node).unwrap(); + + assert_eq!(Some(sym), obj.given_declare); + assert_eq!(Some((IdentKind::Meta, src)), obj.given_resolve); + assert_eq!(Some(fragment), obj.given_set_fragment); + + Ok(()) + } + + #[test] + fn add_fragment_to_ident_fails_if_transition_fails() -> AsgResult<()> { + let mut sut = Sut::new(); + + let sym = "failfrag".intern(); + let src = Source { + generated: true, + ..Default::default() + }; + + // The failure will come from terr below, not this. + let node = sut.declare(sym, IdentKind::Meta, src.clone())?; + let obj = sut.get(node).unwrap(); + + // It doesn't matter that this isn't the error that'll actually be + // returned, as long as it's some sort of TransitionError. + let terr = TransitionError::BadFragmentDest { + name: String::from("test fail"), + }; + obj.fail_set_fragment.replace(Some(terr.clone())); + + let result = sut + .set_fragment(node, "".into()) + .expect_err("error expected"); + + // The node should have been restored. + let obj = sut.get(node).unwrap(); + + assert_eq!(sym, *obj.given_declare.as_ref().unwrap()); + assert_eq!(AsgError::ObjectTransition(terr), result); + + Ok(()) + } + + #[test] + fn add_ident_dep_to_ident() -> AsgResult<()> { + let mut sut = Sut::new(); + + let sym = "sym".intern(); + let dep = "dep".intern(); + + let symnode = sut.declare(sym, IdentKind::Meta, Source::default())?; + let depnode = sut.declare(dep, IdentKind::Meta, Source::default())?; + + sut.add_dep(symnode, depnode); + assert!(sut.has_dep(symnode, depnode)); + + // sanity check if we re-add a dep + sut.add_dep(symnode, depnode); + assert!(sut.has_dep(symnode, depnode)); + + Ok(()) + } + + // same as above test + #[test] + fn add_dep_lookup_existing() -> AsgResult<()> { + let mut sut = Sut::new(); + + let sym = "sym".intern(); + let dep = "dep".intern(); + + let _ = sut.declare(sym, IdentKind::Meta, Source::default())?; + let _ = sut.declare(dep, IdentKind::Meta, Source::default())?; + + let (symnode, depnode) = sut.add_dep_lookup(sym, dep); + assert!(sut.has_dep(symnode, depnode)); + + Ok(()) + } + + #[test] + fn add_dep_lookup_missing() -> AsgResult<()> { + let mut sut = Sut::new(); + + let sym = "sym".intern(); + let dep = "dep".intern(); + + // both of these are missing + let (symnode, depnode) = sut.add_dep_lookup(sym, dep); + assert!(sut.has_dep(symnode, depnode)); + + assert_eq!(Some(sym), sut.get(symnode).unwrap().given_declare); + assert_eq!(Some(dep), sut.get(depnode).unwrap().given_declare); + + Ok(()) + } + + #[test] + fn declare_return_missing_symbol() -> AsgResult<()> { + let mut sut = Sut::new(); + + let sym = "sym".intern(); + let dep = "dep".intern(); + + // both of these are missing, see add_dep_lookup_missing + let (symnode, _) = sut.add_dep_lookup(sym, dep); + + let src = Source { + desc: Some("redeclare missing".into()), + ..Default::default() + }; + + // Check with a declared value + let declared = sut.declare(sym, IdentKind::Meta, src.clone())?; + + assert_eq!(symnode, declared); + + let obj = sut.get(declared).unwrap(); + + assert_eq!(Some(sym), obj.given_declare); + assert_eq!(Some((IdentKind::Meta, src)), obj.given_resolve); + + Ok(()) + } } diff --git a/tamer/src/asg/mod.rs b/tamer/src/asg/mod.rs index c5e5b1f6..6b46e459 100644 --- a/tamer/src/asg/mod.rs +++ b/tamer/src/asg/mod.rs @@ -61,7 +61,7 @@ //! //! ``` //! use tamer::global; -//! use tamer::asg::{Asg, DefaultAsg, IdentKind, IdentObject, Source}; +//! use tamer::asg::{DefaultAsg, IdentKind, IdentObject, Source}; //! use tamer::sym::{Interner, DefaultProgInterner}; //! //! # fn main() -> Result<(), Box> { @@ -110,7 +110,7 @@ //! //! ``` //! # use tamer::global; -//! # use tamer::asg::{Asg, DefaultAsg, IdentKind, IdentObject, FragmentText, Source}; +//! # use tamer::asg::{DefaultAsg, IdentKind, IdentObject, FragmentText, Source}; //! # use tamer::sym::{Interner, DefaultProgInterner}; //! # //! # fn main() -> Result<(), Box> { @@ -156,7 +156,7 @@ //! //! ``` //! # use tamer::global; -//! # use tamer::asg::{Asg, DefaultAsg, IdentKind, IdentObject, FragmentText, Source}; +//! # use tamer::asg::{DefaultAsg, IdentKind, IdentObject, FragmentText, Source}; //! # use tamer::sym::{Interner, DefaultProgInterner}; //! # //! # fn main() -> Result<(), Box> { @@ -190,15 +190,13 @@ //! # } //! ``` -mod base; +mod error; mod graph; mod ident; mod object; -// TODO: Stop exposing this. See `ld::xmle::lower`. -pub use base::BaseAsg; - -pub use graph::{Asg, AsgError, AsgResult, IndexType, ObjectRef}; +pub use error::AsgError; +pub use graph::{Asg, AsgResult, IndexType, ObjectRef}; pub use ident::{DataType, Dim, IdentKind, IdentKindError}; pub use object::{ FragmentText, IdentObject, IdentObjectData, IdentObjectState, Source, @@ -206,4 +204,4 @@ pub use object::{ }; /// Default concrete ASG implementation. -pub type DefaultAsg = base::BaseAsg; +pub type DefaultAsg = graph::Asg; diff --git a/tamer/src/ld/poc.rs b/tamer/src/ld/poc.rs index 21ed21c5..5960c113 100644 --- a/tamer/src/ld/poc.rs +++ b/tamer/src/ld/poc.rs @@ -26,7 +26,7 @@ use super::xmle::{ XmleSections, }; use crate::{ - asg::{Asg, DefaultAsg, IdentObject}, + asg::{DefaultAsg, IdentObject}, diagnose::{AnnotatedSpan, Diagnostic}, fs::{ Filesystem, FsCanonicalizer, PathFile, VisitOnceFile, diff --git a/tamer/src/ld/xmle/lower.rs b/tamer/src/ld/xmle/lower.rs index 1207dc43..f211eb12 100644 --- a/tamer/src/ld/xmle/lower.rs +++ b/tamer/src/ld/xmle/lower.rs @@ -23,7 +23,7 @@ use super::section::{SectionsError, XmleSections}; use crate::{ - asg::{Asg, BaseAsg, IdentKind, IdentObject, ObjectRef}, + asg::{Asg, IdentKind, IdentObject, ObjectRef}, sym::{st, GlobalSymbolResolve, SymbolId}, }; use petgraph::visit::DfsPostOrder; @@ -37,7 +37,7 @@ pub type SortResult = Result; /// although function cycles are permitted. /// The actual operation performed is a post-order depth-first traversal. pub fn sort<'a, S: XmleSections<'a>>( - asg: &'a BaseAsg, + asg: &'a Asg, roots: &[ObjectRef], mut dest: S, ) -> SortResult @@ -71,10 +71,7 @@ where Ok(dest) } -fn get_ident<'a, S>( - depgraph: &'a BaseAsg, - name: S, -) -> &'a IdentObject +fn get_ident<'a, S>(depgraph: &'a Asg, name: S) -> &'a IdentObject where S: Into, { @@ -97,7 +94,7 @@ where /// /// We loop through all SCCs and check that they are not all functions. If /// they are, we ignore the cycle, otherwise we will return an error. -fn check_cycles(asg: &BaseAsg) -> SortResult<()> { +fn check_cycles(asg: &Asg) -> SortResult<()> { // While `tarjan_scc` does do a topological sort, it does not suit our // needs because we need to filter out some allowed cycles. It would // still be possible to use this, but we also need to only check nodes @@ -118,7 +115,7 @@ fn check_cycles(asg: &BaseAsg) -> SortResult<()> { } let is_all_funcs = scc.iter().all(|nx| { - let ident = Asg::get(asg, *nx).expect("missing node"); + let ident = asg.get(*nx).expect("missing node"); matches!(ident.kind(), Some(IdentKind::Func(..))) }); @@ -185,7 +182,7 @@ mod test { sym::GlobalSymbolIntern, }; - type TestAsg = BaseAsg; + type TestAsg = Asg; /// Create a graph with the expected {ret,}map head/tail identifiers. fn make_asg() -> TestAsg { diff --git a/tamer/src/obj/xmlo/asg_builder.rs b/tamer/src/obj/xmlo/asg_builder.rs index 1567df90..ddbafc23 100644 --- a/tamer/src/obj/xmlo/asg_builder.rs +++ b/tamer/src/obj/xmlo/asg_builder.rs @@ -42,8 +42,8 @@ use super::{ XmloError, }; use crate::asg::{ - Asg, AsgError, IdentKind, IdentKindError, IdentObjectState, ObjectRef, - Source, + Asg, AsgError, IdentKind, IdentKindError, IdentObjectData, + IdentObjectState, ObjectRef, Source, }; use crate::sym::SymbolId; use std::collections::HashSet; @@ -165,11 +165,10 @@ enum AsgBuilderInternalState { SymDep(SymbolId), } -impl AsgBuilder for G +impl AsgBuilder for Asg where - O: IdentObjectState, + O: IdentObjectState + IdentObjectData, S: BuildHasher + Default, - G: Asg, { fn import_xmlo( &mut self,