tamer: ld::xmle: Narrow Sections types

This moves the logic that sorts identifiers into sections into Sections
itself, and introduces XmleSections to allow for mocking for testing.

This then allows us to narrow the types significantly, eliminating some
runtime checks.  The types can be narrowed further, but I'll be limiting the
work I'll be doing now; this'll be inevitably addressed as we use the ASG
for the compiler.

This also handles moving Sections tests, which was a TODO from the previous
commit.

DEV-10859
main
Mike Gerwitz 2021-10-14 12:37:32 -04:00
parent ea11cf1416
commit f055cb77c2
8 changed files with 626 additions and 599 deletions

View File

@ -27,7 +27,7 @@ use test::Bencher;
use tamer::ir::asg::{
Asg, DataType, DefaultAsg, IdentKind, IdentObject, Source,
};
use tamer::ld::xmle::lower::sort;
use tamer::ld::xmle::{lower::sort, Sections};
use tamer::sym::{GlobalSymbolIntern, SymbolId};
type TestAsg = DefaultAsg<IdentObject>;
@ -61,7 +61,7 @@ fn sort_1_with_1_000_existing_supernode(bench: &mut Bencher) {
});
bench.iter(|| {
drop(sort(&sut, &[root]));
drop(sort(&sut, &[root], Sections::new()));
});
}
@ -95,6 +95,6 @@ fn sort_1_with_1_000_existing_one_edge_per_node_one_path(bench: &mut Bencher) {
let root = orefs[0];
bench.iter(|| {
drop(sort(&sut, &[root]));
drop(sort(&sut, &[root], Sections::new()));
});
}

View File

@ -23,10 +23,7 @@
use super::xmle::{
lower::{sort, SortError},
xir::lower_iter,
Sections,
};
use crate::fs::{
Filesystem, FsCanonicalizer, PathFile, VisitOnceFile, VisitOnceFilesystem,
XmleSections,
};
use crate::global;
use crate::ir::asg::{Asg, DefaultAsg, IdentObject, IdentObjectData};
@ -34,6 +31,13 @@ use crate::ir::xir::writer::XmlWriter;
use crate::obj::xmlo::{AsgBuilder, AsgBuilderState, XmloReader};
use crate::sym::SymbolId;
use crate::sym::{GlobalSymbolIntern, GlobalSymbolResolve};
use crate::{
fs::{
Filesystem, FsCanonicalizer, PathFile, VisitOnceFile,
VisitOnceFilesystem,
},
ld::xmle::Sections,
};
use fxhash::FxBuildHasher;
use petgraph_graphml::GraphMl;
use std::error::Error;
@ -72,7 +76,7 @@ pub fn xmle(package_path: &str, output: &str) -> Result<(), Box<dyn Error>> {
.filter_map(|sym| depgraph.lookup(sym)),
);
let mut sorted = match sort(&depgraph, &roots) {
let sorted = match sort(&depgraph, &roots, Sections::new()) {
Ok(sections) => sections,
Err(SortError::Cycles(cycles)) => {
let msg: Vec<String> = cycles
@ -105,8 +109,7 @@ pub fn xmle(package_path: &str, output: &str) -> Result<(), Box<dyn Error>> {
};
output_xmle(
&depgraph,
&mut sorted,
sorted,
name.expect("missing root package name"),
relroot.expect("missing root package relroot"),
output,
@ -200,46 +203,16 @@ fn load_xmlo<'a, P: AsRef<Path>>(
Ok(state)
}
fn get_ident<'a>(
depgraph: &'a LinkerAsg,
name: SymbolId<global::ProgSymSize>,
) -> Result<&'a IdentObject, String> {
depgraph
.lookup(name)
.and_then(|id| depgraph.get(id))
.ok_or(format!("missing identifier: {}", name.lookup_str()))
}
fn output_xmle<'a>(
depgraph: &'a LinkerAsg,
sorted: &mut Sections<'a>,
fn output_xmle<'a, S: XmleSections<'a>>(
sorted: S,
name: SymbolId,
relroot: SymbolId,
output: &str,
) -> Result<(), Box<dyn Error>> {
if !sorted.map.is_empty() {
sorted
.map
.set_head(get_ident(depgraph, ":map:___head".intern())?);
sorted
.map
.set_tail(get_ident(depgraph, ":map:___tail".intern())?);
}
if !sorted.retmap.is_empty() {
sorted
.retmap
.set_head(get_ident(depgraph, ":retmap:___head".intern())?);
sorted
.retmap
.set_tail(get_ident(depgraph, ":retmap:___tail".intern())?);
}
let file = fs::File::create(output)?;
let mut buf = BufWriter::new(file);
lower_iter(&sorted, name, relroot).write(&mut buf, Default::default())?;
lower_iter(sorted, name, relroot).write(&mut buf, Default::default())?;
buf.flush()?;
Ok(())

View File

@ -17,33 +17,37 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! Lowering of the [ASG](crate::ir::asg) into `xmle` [`Sections`].
//! Lowering of the [ASG](crate::ir::asg) into `xmle` [`XmleSections`].
//!
//! See the [parent module](super) for more information.
use super::{section::SectionsError, Sections};
use crate::ir::asg::{
Asg, BaseAsg, IdentKind, IdentObject, IdentObjectData, IndexType, ObjectRef,
use super::section::{SectionsError, XmleSections};
use crate::{
ir::asg::{
Asg, BaseAsg, IdentKind, IdentObject, IdentObjectData, IndexType,
ObjectRef,
},
sym::{st, GlobalSymbolResolve, SymbolId},
};
use petgraph::visit::DfsPostOrder;
// Result of [`sort`].
pub type SortResult<T, Ix> = Result<T, SortError<Ix>>;
/// Lower ASG into [`Sections`] by sorting relocatable text fragments.
/// Lower ASG into [`XmleSections`] by ordering relocatable text fragments.
///
/// This performs the equivalent of a topological sort,
/// although function cycles are permitted.
/// The actual operation performed is a post-order depth-first traversal.
pub fn sort<'a, Ix>(
pub fn sort<'a, Ix, S: XmleSections<'a>>(
asg: &'a BaseAsg<IdentObject, Ix>,
roots: &[ObjectRef<Ix>],
) -> SortResult<Sections<'a>, Ix>
mut dest: S,
) -> SortResult<S, Ix>
where
Ix: IndexType,
S: XmleSections<'a>,
{
let mut deps = Sections::new();
// TODO: we should check for cycles as we sort (as the POC did).
check_cycles(asg)?;
@ -54,12 +58,40 @@ where
dfs.stack.push((*index).into());
}
// These are always generated by the map compiler,
// but do not have edges that would allow them to be properly ordered
// (adding an edge to every map object would be wasteful).
dest.push(get_ident(asg, st::L_MAP_UUUHEAD))?;
dest.push(get_ident(asg, st::L_RETMAP_UUUHEAD))?;
while let Some(index) = dfs.next(&asg.graph) {
let ident = asg.get(index).expect("missing node");
deps.push(ident)?;
dest.push(ident)?;
}
Ok(deps)
dest.push(get_ident(asg, st::L_MAP_UUUTAIL))?;
dest.push(get_ident(asg, st::L_RETMAP_UUUTAIL))?;
Ok(dest)
}
fn get_ident<'a, Ix, S>(
depgraph: &'a BaseAsg<IdentObject, Ix>,
name: S,
) -> &'a IdentObject
where
Ix: IndexType,
S: Into<SymbolId>,
{
let sym = name.into();
depgraph
.lookup(sym)
.and_then(|id| depgraph.get(id))
.expect(&format!(
"missing internal identifier: {}",
sym.lookup_str()
))
}
/// Check graph for cycles
@ -123,7 +155,7 @@ where
/// sorting process.
#[derive(Debug, PartialEq)]
pub enum SortError<Ix: IndexType> {
/// Error while building [`Sections`].
/// Error while lowering into [`XmleSections`].
SectionsError(SectionsError),
/// The graph has a cyclic dependency.
@ -156,95 +188,148 @@ mod test {
use super::*;
use crate::{
ir::{
asg::{DataType, Dim, FragmentText, IdentObject, Source},
asg::{Dim, FragmentText, IdentObject, Source},
legacyir::SymDtype,
},
ld::xmle::{section::PushResult, Sections},
sym::GlobalSymbolIntern,
};
type Sut = BaseAsg<IdentObject, u16>;
type TestAsg = BaseAsg<IdentObject, u16>;
macro_rules! assert_section_sym {
( $iterable:expr, $s:ident ) => {{
let mut pos = 0;
/// Create a graph with the expected {ret,}map head/tail identifiers.
fn make_asg() -> TestAsg {
let mut asg = TestAsg::new();
for obj in $iterable.iter() {
let sym = obj.name().expect("missing object");
assert_eq!($s.get(pos).map(|s| *s), Some(sym));
let text = "dummy fragment".intern();
pos = pos + 1;
}
asg.declare(
st::L_MAP_UUUHEAD.into(),
IdentKind::MapHead,
Default::default(),
)
.and_then(|id| asg.set_fragment(id, text))
.unwrap();
assert_eq!(pos, $s.len());
};};
}
asg.declare(
st::L_MAP_UUUTAIL.into(),
IdentKind::MapTail,
Default::default(),
)
.and_then(|id| asg.set_fragment(id, text))
.unwrap();
macro_rules! add_syms {
($sut:ident, $base:expr, {$($dest:ident <- $name:ident: $kind:expr,)*}) => {
$(
let sym = stringify!($name).intern();
asg.declare(
st::L_RETMAP_UUUHEAD.into(),
IdentKind::RetMapHead,
Default::default(),
)
.and_then(|id| asg.set_fragment(id, text))
.unwrap();
$sut.declare(sym, $kind, Source::default()).unwrap();
let (_, _) = $sut.add_dep_lookup($base, sym);
asg.declare(
st::L_RETMAP_UUUTAIL.into(),
IdentKind::RetMapTail,
Default::default(),
)
.and_then(|id| asg.set_fragment(id, text))
.unwrap();
$dest.push(sym);
)*
};
asg
}
#[test]
fn graph_sort() -> SortResult<(), u16> {
let mut sut = Sut::new();
// We care only about the order of pushes, not the sections they end
// up in.
struct StubSections<'a> {
pushed: Vec<&'a IdentObject>,
}
let mut st = vec![];
let mut map = vec![];
let mut retmap = vec![];
impl<'a> XmleSections<'a> for StubSections<'a> {
fn push(&mut self, ident: &'a IdentObject) -> PushResult {
self.pushed.push(ident);
Ok(())
}
let base = "sym1".intern();
let base_node = sut
.declare(base, IdentKind::Map, Source::default())
fn take_deps(&mut self) -> Vec<&'a IdentObject> {
unimplemented!()
}
fn take_static(&mut self) -> Vec<SymbolId> {
unimplemented!()
}
fn take_map(&mut self) -> Vec<SymbolId> {
unimplemented!()
}
fn take_map_froms(&mut self) -> fxhash::FxHashSet<SymbolId> {
unimplemented!()
}
fn take_retmap(&mut self) -> Vec<SymbolId> {
unimplemented!()
}
fn take_exec(&mut self) -> Vec<SymbolId> {
unimplemented!()
}
}
let mut asg = make_asg();
// Add them in an unsorted order.
let adep = asg
.declare("adep".into(), IdentKind::Meta, Default::default())
.unwrap();
let a = asg
.declare("a".into(), IdentKind::Meta, Default::default())
.unwrap();
let adepdep = asg
.declare("adepdep".into(), IdentKind::Meta, Default::default())
.unwrap();
add_syms!(sut, base, {
st <- meta1: IdentKind::Meta,
st <- work1: IdentKind::Worksheet,
st <- const1: IdentKind::Const(Dim::from_u8(0), DataType::Float),
map <- map1: IdentKind::MapHead,
map <- map2: IdentKind::Map,
map <- map3: IdentKind::MapTail,
retmap <- retmap1: IdentKind::RetMapHead,
retmap <- retmap2: IdentKind::RetMap,
retmap <- retmap3: IdentKind::RetMapTail,
retmap <- retmap4: IdentKind::RetMapTail,
retmap <- retmap5: IdentKind::RetMap,
map <- map4: IdentKind::MapHead,
map <- map5: IdentKind::Map,
st <- meta2: IdentKind::Meta,
st <- work2: IdentKind::Worksheet,
map <- map6: IdentKind::MapTail,
retmap <- retmap6: IdentKind::RetMapHead,
st <- const2: IdentKind::Const(Dim::from_u8(2), DataType::Float),
});
asg.add_dep(a, adep);
asg.add_dep(adep, adepdep);
map.push(base);
let sections =
sort(&asg, &vec![a], StubSections { pushed: Vec::new() })?;
let sections = sort(&sut, &vec![base_node])?;
assert_section_sym!(sections.st, st);
assert_section_sym!(sections.map, map);
assert_section_sym!(sections.retmap, retmap);
assert_eq!(
sections.pushed,
vec![
// Static head
asg.lookup(st::L_MAP_UUUHEAD.into())
.and_then(|id| asg.get(id)),
asg.lookup(st::L_RETMAP_UUUHEAD.into())
.and_then(|id| asg.get(id)),
// Post-order
asg.get(adepdep),
asg.get(adep),
asg.get(a),
// Static tail
asg.lookup(st::L_MAP_UUUTAIL.into())
.and_then(|id| asg.get(id)),
asg.lookup(st::L_RETMAP_UUUTAIL.into())
.and_then(|id| asg.get(id)),
]
.into_iter()
.collect::<Option<Vec<_>>>()
.unwrap()
);
Ok(())
}
#[test]
fn graph_sort_missing_node() -> SortResult<(), u16> {
let mut sut = Sut::new();
let mut asg = make_asg();
let sym = "sym".intern();
let dep = "dep".intern();
let sym_node = sut
let sym_node = asg
.declare(
sym,
IdentKind::Tpl,
@ -255,12 +340,12 @@ mod test {
)
.unwrap();
sut.set_fragment(sym_node, FragmentText::from("foo"))
asg.set_fragment(sym_node, FragmentText::from("foo"))
.unwrap();
let (_, _) = sut.add_dep_lookup(sym, dep);
let (_, _) = asg.add_dep_lookup(sym, dep);
match sort(&sut, &vec![sym_node]) {
match sort(&asg, &vec![sym_node], Sections::new()) {
Ok(_) => panic!("Unexpected success - dependency is not in graph"),
Err(SortError::SectionsError(SectionsError::UnresolvedObject(
_,
@ -274,29 +359,33 @@ mod test {
}
#[test]
fn graph_sort_no_roots() -> SortResult<(), u16> {
let mut sut = Sut::new();
fn graph_sort_no_roots_same_as_empty_graph() -> SortResult<(), u16> {
let mut asg_nonempty_no_roots = make_asg();
// "empty" (it has the head/tail {ret,}map objects)
let asg_empty = make_asg();
let sym = "sym".intern();
let dep = "dep".intern();
let (_, _) = sut.add_dep_lookup(sym, dep);
asg_nonempty_no_roots.add_dep_lookup(sym, dep);
let sections = sort(&sut, &vec![])?;
assert_eq!(Sections::new(), sections);
assert_eq!(
sort(&asg_nonempty_no_roots, &vec![], Sections::new()),
sort(&asg_empty, &vec![], Sections::new())
);
Ok(())
}
#[test]
fn graph_sort_simple_cycle() -> SortResult<(), u16> {
let mut sut = Sut::new();
let mut asg = make_asg();
let sym = "sym".intern();
let dep = "dep".intern();
let sym_node = sut
let sym_node = asg
.declare(
sym,
IdentKind::Tpl,
@ -307,7 +396,7 @@ mod test {
)
.unwrap();
let dep_node = sut
let dep_node = asg
.declare(
dep,
IdentKind::Tpl,
@ -318,15 +407,15 @@ mod test {
)
.unwrap();
sut.set_fragment(sym_node, FragmentText::from("foo"))
asg.set_fragment(sym_node, FragmentText::from("foo"))
.unwrap();
sut.set_fragment(dep_node, FragmentText::from("bar"))
asg.set_fragment(dep_node, FragmentText::from("bar"))
.unwrap();
let (_, _) = sut.add_dep_lookup(sym, dep);
let (_, _) = sut.add_dep_lookup(dep, sym);
let (_, _) = asg.add_dep_lookup(sym, dep);
let (_, _) = asg.add_dep_lookup(dep, sym);
let result = sort(&sut, &vec![sym_node]);
let result = sort(&asg, &vec![sym_node], Sections::new());
let expected: Vec<Vec<ObjectRef<u16>>> =
vec![vec![dep_node.into(), sym_node.into()]];
@ -341,14 +430,14 @@ mod test {
#[test]
fn graph_sort_two_simple_cycles() -> SortResult<(), u16> {
let mut sut = Sut::new();
let mut asg = make_asg();
let sym = "sym".intern();
let sym2 = "sym2".intern();
let dep = "dep".intern();
let dep2 = "dep2".intern();
let sym_node = sut
let sym_node = asg
.declare(
sym,
IdentKind::Tpl,
@ -359,7 +448,7 @@ mod test {
)
.unwrap();
let sym2_node = sut
let sym2_node = asg
.declare(
sym2,
IdentKind::Tpl,
@ -370,7 +459,7 @@ mod test {
)
.unwrap();
let dep_node = sut
let dep_node = asg
.declare(
dep,
IdentKind::Tpl,
@ -381,7 +470,7 @@ mod test {
)
.unwrap();
let dep2_node = sut
let dep2_node = asg
.declare(
dep2,
IdentKind::Tpl,
@ -392,21 +481,21 @@ mod test {
)
.unwrap();
sut.set_fragment(sym_node, FragmentText::from("foo"))
asg.set_fragment(sym_node, FragmentText::from("foo"))
.unwrap();
sut.set_fragment(sym2_node, FragmentText::from("bar"))
asg.set_fragment(sym2_node, FragmentText::from("bar"))
.unwrap();
sut.set_fragment(dep_node, FragmentText::from("baz"))
asg.set_fragment(dep_node, FragmentText::from("baz"))
.unwrap();
sut.set_fragment(dep2_node, FragmentText::from("huh"))
asg.set_fragment(dep2_node, FragmentText::from("huh"))
.unwrap();
let (_, _) = sut.add_dep_lookup(sym, dep);
let (_, _) = sut.add_dep_lookup(dep, sym);
let (_, _) = sut.add_dep_lookup(sym2, dep2);
let (_, _) = sut.add_dep_lookup(dep2, sym2);
let (_, _) = asg.add_dep_lookup(sym, dep);
let (_, _) = asg.add_dep_lookup(dep, sym);
let (_, _) = asg.add_dep_lookup(sym2, dep2);
let (_, _) = asg.add_dep_lookup(dep2, sym2);
let result = sort(&sut, &vec![sym_node]);
let result = sort(&asg, &vec![sym_node], Sections::new());
let expected: Vec<Vec<ObjectRef<u16>>> = vec![
vec![dep_node.into(), sym_node.into()],
@ -423,12 +512,12 @@ mod test {
#[test]
fn graph_sort_no_cycle_with_edge_to_same_node() -> SortResult<(), u16> {
let mut sut = Sut::new();
let mut asg = make_asg();
let sym = "sym".intern();
let dep = "dep".intern();
let sym_node = sut
let sym_node = asg
.declare(
sym,
IdentKind::Tpl,
@ -439,7 +528,7 @@ mod test {
)
.unwrap();
let dep_node = sut
let dep_node = asg
.declare(
dep,
IdentKind::Tpl,
@ -450,15 +539,15 @@ mod test {
)
.unwrap();
sut.set_fragment(sym_node, FragmentText::from("foo"))
asg.set_fragment(sym_node, FragmentText::from("foo"))
.unwrap();
sut.set_fragment(dep_node, FragmentText::from("bar"))
asg.set_fragment(dep_node, FragmentText::from("bar"))
.unwrap();
let (_, _) = sut.add_dep_lookup(sym, dep);
let (_, _) = sut.add_dep_lookup(sym, dep);
let (_, _) = asg.add_dep_lookup(sym, dep);
let (_, _) = asg.add_dep_lookup(sym, dep);
let result = sort(&sut, &vec![sym_node]);
let result = sort(&asg, &vec![sym_node], Sections::new());
match result {
Ok(_) => (),
@ -470,13 +559,13 @@ mod test {
#[test]
fn graph_sort_cycle_with_a_few_steps() -> SortResult<(), u16> {
let mut sut = Sut::new();
let mut asg = make_asg();
let sym1 = "sym1".intern();
let sym2 = "sym2".intern();
let sym3 = "sym3".intern();
let sym1_node = sut
let sym1_node = asg
.declare(
sym1,
IdentKind::Tpl,
@ -487,7 +576,7 @@ mod test {
)
.unwrap();
let sym2_node = sut
let sym2_node = asg
.declare(
sym2,
IdentKind::Tpl,
@ -498,7 +587,7 @@ mod test {
)
.unwrap();
let sym3_node = sut
let sym3_node = asg
.declare(
sym3,
IdentKind::Tpl,
@ -509,18 +598,18 @@ mod test {
)
.unwrap();
sut.set_fragment(sym1_node, FragmentText::from("foo"))
asg.set_fragment(sym1_node, FragmentText::from("foo"))
.unwrap();
sut.set_fragment(sym2_node, FragmentText::from("bar"))
asg.set_fragment(sym2_node, FragmentText::from("bar"))
.unwrap();
sut.set_fragment(sym3_node, FragmentText::from("baz"))
asg.set_fragment(sym3_node, FragmentText::from("baz"))
.unwrap();
let (_, _) = sut.add_dep_lookup(sym1, sym2);
let (_, _) = sut.add_dep_lookup(sym2, sym3);
let (_, _) = sut.add_dep_lookup(sym3, sym1);
let (_, _) = asg.add_dep_lookup(sym1, sym2);
let (_, _) = asg.add_dep_lookup(sym2, sym3);
let (_, _) = asg.add_dep_lookup(sym3, sym1);
let result = sort(&sut, &vec![sym1_node]);
let result = sort(&asg, &vec![sym1_node], Sections::new());
let expected: Vec<Vec<ObjectRef<u16>>> =
vec![vec![sym3_node.into(), sym2_node.into(), sym1_node.into()]];
@ -536,13 +625,13 @@ mod test {
#[test]
fn graph_sort_cyclic_function_with_non_function_with_a_few_steps(
) -> SortResult<(), u16> {
let mut sut = Sut::new();
let mut asg = make_asg();
let sym1 = "sym1".intern();
let sym2 = "sym2".intern();
let sym3 = "sym3".intern();
let sym1_node = sut
let sym1_node = asg
.declare(
sym1,
IdentKind::Tpl,
@ -553,7 +642,7 @@ mod test {
)
.unwrap();
let sym2_node = sut
let sym2_node = asg
.declare(
sym2,
IdentKind::Tpl,
@ -564,7 +653,7 @@ mod test {
)
.unwrap();
let sym3_node = sut
let sym3_node = asg
.declare(
sym3,
IdentKind::Func(Dim::default(), SymDtype::Empty),
@ -575,18 +664,18 @@ mod test {
)
.unwrap();
sut.set_fragment(sym1_node, FragmentText::from("foo"))
asg.set_fragment(sym1_node, FragmentText::from("foo"))
.unwrap();
sut.set_fragment(sym2_node, FragmentText::from("bar"))
asg.set_fragment(sym2_node, FragmentText::from("bar"))
.unwrap();
sut.set_fragment(sym3_node, FragmentText::from("baz"))
asg.set_fragment(sym3_node, FragmentText::from("baz"))
.unwrap();
let (_, _) = sut.add_dep_lookup(sym1, sym2);
let (_, _) = sut.add_dep_lookup(sym2, sym3);
let (_, _) = sut.add_dep_lookup(sym3, sym1);
let (_, _) = asg.add_dep_lookup(sym1, sym2);
let (_, _) = asg.add_dep_lookup(sym2, sym3);
let (_, _) = asg.add_dep_lookup(sym3, sym1);
let result = sort(&sut, &vec![sym1_node]);
let result = sort(&asg, &vec![sym1_node], Sections::new());
let expected: Vec<Vec<ObjectRef<u16>>> =
vec![vec![sym3_node.into(), sym2_node.into(), sym1_node.into()]];
@ -601,13 +690,13 @@ mod test {
#[test]
fn graph_sort_cyclic_bookended_by_functions() -> SortResult<(), u16> {
let mut sut = Sut::new();
let mut asg = make_asg();
let sym1 = "sym1".intern();
let sym2 = "sym2".intern();
let sym3 = "sym3".intern();
let sym1_node = sut
let sym1_node = asg
.declare(
sym1,
IdentKind::Func(Dim::default(), SymDtype::Empty),
@ -618,7 +707,7 @@ mod test {
)
.unwrap();
let sym2_node = sut
let sym2_node = asg
.declare(
sym2,
IdentKind::Tpl,
@ -629,7 +718,7 @@ mod test {
)
.unwrap();
let sym3_node = sut
let sym3_node = asg
.declare(
sym3,
IdentKind::Func(Dim::default(), SymDtype::Empty),
@ -640,18 +729,18 @@ mod test {
)
.unwrap();
sut.set_fragment(sym1_node, FragmentText::from("foo"))
asg.set_fragment(sym1_node, FragmentText::from("foo"))
.unwrap();
sut.set_fragment(sym2_node, FragmentText::from("bar"))
asg.set_fragment(sym2_node, FragmentText::from("bar"))
.unwrap();
sut.set_fragment(sym3_node, FragmentText::from("baz"))
asg.set_fragment(sym3_node, FragmentText::from("baz"))
.unwrap();
let (_, _) = sut.add_dep_lookup(sym1, sym2);
let (_, _) = sut.add_dep_lookup(sym2, sym3);
let (_, _) = sut.add_dep_lookup(sym3, sym1);
let (_, _) = asg.add_dep_lookup(sym1, sym2);
let (_, _) = asg.add_dep_lookup(sym2, sym3);
let (_, _) = asg.add_dep_lookup(sym3, sym1);
let result = sort(&sut, &vec![sym1_node]);
let result = sort(&asg, &vec![sym1_node], Sections::new());
let expected: Vec<Vec<ObjectRef<u16>>> =
vec![vec![sym3_node.into(), sym2_node.into(), sym1_node.into()]];
@ -666,12 +755,12 @@ mod test {
#[test]
fn graph_sort_cyclic_function_ignored() -> SortResult<(), u16> {
let mut sut = Sut::new();
let mut asg = make_asg();
let sym = "sym".intern();
let dep = "dep".intern();
let sym_node = sut
let sym_node = asg
.declare(
sym,
IdentKind::Func(Dim::default(), SymDtype::Empty),
@ -682,7 +771,7 @@ mod test {
)
.unwrap();
let dep_node = sut
let dep_node = asg
.declare(
dep,
IdentKind::Func(Dim::default(), SymDtype::Empty),
@ -693,15 +782,15 @@ mod test {
)
.unwrap();
sut.set_fragment(sym_node, FragmentText::from("foo"))
asg.set_fragment(sym_node, FragmentText::from("foo"))
.unwrap();
sut.set_fragment(dep_node, FragmentText::from("bar"))
asg.set_fragment(dep_node, FragmentText::from("bar"))
.unwrap();
let (_, _) = sut.add_dep_lookup(sym, dep);
let (_, _) = sut.add_dep_lookup(dep, sym);
let (_, _) = asg.add_dep_lookup(sym, dep);
let (_, _) = asg.add_dep_lookup(dep, sym);
let result = sort(&sut, &vec![sym_node]);
let result = sort(&asg, &vec![sym_node], Sections::new());
match result {
Ok(_) => (),
@ -713,13 +802,13 @@ mod test {
#[test]
fn graph_sort_cyclic_function_is_bookended() -> SortResult<(), u16> {
let mut sut = Sut::new();
let mut asg = make_asg();
let sym1 = "sym1".intern();
let sym2 = "sym2".intern();
let sym3 = "sym3".intern();
let sym1_node = sut
let sym1_node = asg
.declare(
sym1,
IdentKind::Tpl,
@ -730,7 +819,7 @@ mod test {
)
.unwrap();
let sym2_node = sut
let sym2_node = asg
.declare(
sym2,
IdentKind::Func(Dim::default(), SymDtype::Empty),
@ -741,7 +830,7 @@ mod test {
)
.unwrap();
let sym3_node = sut
let sym3_node = asg
.declare(
sym3,
IdentKind::Tpl,
@ -752,18 +841,18 @@ mod test {
)
.unwrap();
sut.set_fragment(sym1_node, FragmentText::from("foo"))
asg.set_fragment(sym1_node, FragmentText::from("foo"))
.unwrap();
sut.set_fragment(sym2_node, FragmentText::from("bar"))
asg.set_fragment(sym2_node, FragmentText::from("bar"))
.unwrap();
sut.set_fragment(sym3_node, FragmentText::from("baz"))
asg.set_fragment(sym3_node, FragmentText::from("baz"))
.unwrap();
let (_, _) = sut.add_dep_lookup(sym1, sym2);
let (_, _) = sut.add_dep_lookup(sym2, sym3);
let (_, _) = sut.add_dep_lookup(sym3, sym1);
let (_, _) = asg.add_dep_lookup(sym1, sym2);
let (_, _) = asg.add_dep_lookup(sym2, sym3);
let (_, _) = asg.add_dep_lookup(sym3, sym1);
let result = sort(&sut, &vec![sym1_node]);
let result = sort(&asg, &vec![sym1_node], Sections::new());
let expected: Vec<Vec<ObjectRef<u16>>> =
vec![vec![sym3_node.into(), sym2_node.into(), sym1_node.into()]];
@ -778,13 +867,13 @@ mod test {
#[test]
fn graph_sort_ignore_non_linked() -> SortResult<(), u16> {
let mut sut = Sut::new();
let mut asg = make_asg();
let sym = "sym".intern();
let dep = "dep".intern();
let ignored = "ignored".intern();
let sym_node = sut
let sym_node = asg
.declare(
sym,
IdentKind::Tpl,
@ -795,7 +884,7 @@ mod test {
)
.unwrap();
let dep_node = sut
let dep_node = asg
.declare(
dep,
IdentKind::Tpl,
@ -806,7 +895,7 @@ mod test {
)
.unwrap();
let ignored_node = sut
let ignored_node = asg
.declare(
ignored,
IdentKind::Tpl,
@ -817,17 +906,17 @@ mod test {
)
.unwrap();
sut.set_fragment(sym_node, FragmentText::from("foo"))
asg.set_fragment(sym_node, FragmentText::from("foo"))
.unwrap();
sut.set_fragment(dep_node, FragmentText::from("bar"))
asg.set_fragment(dep_node, FragmentText::from("bar"))
.unwrap();
sut.set_fragment(ignored_node, FragmentText::from("baz"))
asg.set_fragment(ignored_node, FragmentText::from("baz"))
.unwrap();
let (_, _) = sut.add_dep_lookup(sym, dep);
let (_, _) = sut.add_dep_lookup(ignored, sym);
let (_, _) = asg.add_dep_lookup(sym, dep);
let (_, _) = asg.add_dep_lookup(ignored, sym);
let result = sort(&sut, &vec![sym_node]);
let result = sort(&asg, &vec![sym_node], Sections::new());
match result {
Ok(_) => (),

View File

@ -65,7 +65,7 @@
//! <l:static>
//! function func_min(args,min1,min2) {return min1;}
//! </l:static>
//! <l:exec>C['CMP_OP_EQ'] = 1;</l:exec>
//! <l:exec>A['foo']=E(A['bar']);</l:exec>
//! </package>
//! ```
@ -74,4 +74,4 @@ mod section;
pub mod lower;
pub mod xir;
pub use section::{Section, SectionIter, Sections, SectionsIter};
pub use section::{Sections, XmleSections};

View File

@ -19,7 +19,10 @@
//! Sections of a linked [`xmle`](super) object file.
//!
//! These sections are the result of [`sort`](super::lower::sort),
//! An [`XmleSections`] object is responsible for placing provided
//! identifiers into the appropriate section,
//! but _it must be provided properly ordered data_.
//! This ordering is the result of [`sort`](super::lower::sort),
//! which places the relocatable object code fragments in the order
//! necessary for execution.
@ -28,110 +31,40 @@ use crate::ir::asg::{
};
use crate::sym::{GlobalSymbolResolve, SymbolId};
use fxhash::FxHashSet;
use std::collections::hash_set;
use std::iter::Chain;
use std::option;
use std::mem::take;
use std::result::Result;
use std::slice::Iter;
/// A section of an [object file](crate::obj).
///
/// Most sections will only need a `body`, but some innlude `head` and `tail`
/// information. Rather than dealing with those differently, each `Section`
/// will have a `head` and `tail` that are empty by default.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Section<'a> {
head: Option<&'a IdentObject>,
body: Vec<&'a IdentObject>,
tail: Option<&'a IdentObject>,
pub type PushResult<T = ()> = Result<T, SectionsError>;
pub trait XmleSections<'a> {
fn push(&mut self, ident: &'a IdentObject) -> PushResult;
fn take_deps(&mut self) -> Vec<&'a IdentObject>;
fn take_static(&mut self) -> Vec<SymbolId>;
fn take_map(&mut self) -> Vec<SymbolId>;
fn take_map_froms(&mut self) -> FxHashSet<SymbolId>;
fn take_retmap(&mut self) -> Vec<SymbolId>;
fn take_exec(&mut self) -> Vec<SymbolId>;
}
impl<'a> Section<'a> {
/// New empty section.
pub fn new() -> Self {
Self {
head: None,
body: Vec::new(),
tail: None,
}
}
/// Check if the `Section` is empty
#[inline]
pub fn is_empty(&self) -> bool {
self.body.len() == 0
}
/// Push an `IdentObject` into a `Section`'s head
#[inline]
pub fn set_head(&mut self, obj: &'a IdentObject) {
self.head.replace(obj);
}
/// Push an `IdentObject` into a `Section`'s body
#[inline]
pub fn push_body(&mut self, obj: &'a IdentObject) {
self.body.push(obj)
}
/// Push an `IdentObject` into a `Section`'s tail
#[inline]
pub fn set_tail(&mut self, obj: &'a IdentObject) {
self.tail.replace(obj);
}
/// Construct a new iterator visiting each head, body, and tail object
/// in order.
#[inline]
pub fn iter(&self) -> SectionIter {
SectionIter(
self.head
.iter()
.chain(self.body.iter())
.chain(self.tail.iter()),
)
}
}
/// Iterator over the head, body, and tail of a [`Section`].
/// Sections of a linked `xmle` file.
///
/// This iterator should be created with [`Section::iter`].
///
/// This hides the complex iterator type from callers.
pub struct SectionIter<'a>(
Chain<
Chain<option::Iter<'a, &'a IdentObject>, Iter<'a, &'a IdentObject>>,
option::Iter<'a, &'a IdentObject>,
>,
);
impl<'a> Iterator for SectionIter<'a> {
type Item = &'a IdentObject;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(|x| *x)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let (low, high) = self.0.size_hint();
(low, high.map(|x| x + 2))
}
}
pub type PushResult = Result<(), SectionsError>;
/// ASG objects organized into logical sections.
///
/// These sections may not necessarily correspond directly to sections of an
/// [object file](crate::obj).
// TODO: Remove pub
/// For more information on these sections,
/// see the [parent module](super).
#[derive(Debug, Default, PartialEq)]
pub struct Sections<'a> {
pub map: Section<'a>,
pub retmap: Section<'a>,
pub st: Section<'a>,
pub rater: Section<'a>,
deps: Vec<&'a IdentObject>,
map_froms: FxHashSet<SymbolId>,
map: Vec<SymbolId>,
retmap: Vec<SymbolId>,
st: Vec<SymbolId>,
exec: Vec<SymbolId>,
}
impl<'a> Sections<'a> {
@ -139,34 +72,58 @@ impl<'a> Sections<'a> {
#[inline]
pub fn new() -> Self {
Self {
map: Section::new(),
retmap: Section::new(),
st: Section::new(),
rater: Section::new(),
..Default::default()
}
}
}
impl<'a> XmleSections<'a> for Sections<'a> {
/// Push an object into the appropriate section.
///
/// Objects are expected to be properly sorted relative to their order
/// of execution so that their text fragments are placed in the
/// correct order in the final program text.
pub fn push(&mut self, ident: &'a IdentObject) -> PushResult {
fn push(&mut self, ident: &'a IdentObject) -> PushResult {
self.deps.push(ident);
// TODO: This cannot happen, so use an API without Option.
let name = ident.name().expect("missing identifier name");
let frag = ident.fragment().map(|sym| *sym);
match ident.resolved()?.kind() {
Some(kind) => match kind {
IdentKind::Cgen(_)
| IdentKind::Gen(_, _)
| IdentKind::Lparam(_, _) => {
// These types do not have fragments.
}
IdentKind::Meta
| IdentKind::Worksheet
| IdentKind::Param(_, _)
| IdentKind::Type(_)
| IdentKind::Func(_, _)
| IdentKind::Const(_, _) => self.st.push_body(ident),
| IdentKind::Const(_, _) => {
self.st.push(expect_frag(name, frag)?)
}
IdentKind::MapHead | IdentKind::Map | IdentKind::MapTail => {
self.map.push_body(ident)
self.map.push(expect_frag(name, frag)?);
if let Some(from) =
ident.src().expect("missing map src").from
{
self.map_froms.insert(from);
}
}
IdentKind::RetMapHead
| IdentKind::RetMap
| IdentKind::RetMapTail => self.retmap.push_body(ident),
_ => self.rater.push_body(ident),
| IdentKind::RetMapTail => {
self.retmap.push(expect_frag(name, frag)?)
}
// TODO: Why do templates have fragments?
IdentKind::Class(_) | IdentKind::Rate(_) | IdentKind::Tpl => {
self.exec.push(expect_frag(name, frag)?)
}
},
None => {
// TODO: This should not be possible; ensure that with types
@ -187,75 +144,44 @@ impl<'a> Sections<'a> {
Ok(())
}
/// Construct an iterator over each of the individual sections in
/// arbitrary order.
///
/// Each individual section is ordered as stated in [`Section::iter`],
/// but you should not rely on the order that the sections themselves
/// appear in;
/// they may change or be combined in the future.
/// At the time of writing,
/// they are chained in the same order in which they are defined
/// on the [`Sections`] struct.
#[inline]
pub fn iter_all(&self) -> SectionsIter {
SectionsIter(SectionsIterType::All(
self.map
.iter()
.chain(self.retmap.iter())
.chain(self.st.iter())
.chain(self.rater.iter()),
))
fn take_deps(&mut self) -> Vec<&'a IdentObject> {
take(&mut self.deps)
}
/// Construct an iterator over the static sections in arbitrary order.
///
/// These sections contain fragments that do not depend on any external
/// inputs and can therefore be executed a single time when the
/// program is loaded into memory.
///
/// Each individual section is ordered as stated in [`Section::iter`],
/// but you should not rely on the order that the sections themselves
/// appear in;
/// they may change or be combined in the future.
#[inline]
pub fn iter_static(&self) -> SectionsIter {
SectionsIter(SectionsIterType::Single(self.st.iter()))
fn take_static(&mut self) -> Vec<SymbolId> {
take(&mut self.st)
}
/// Construct an iterator over the map section.
#[inline]
pub fn iter_map(&self) -> SectionsIter {
SectionsIter(SectionsIterType::Single(self.map.iter()))
fn take_map(&mut self) -> Vec<SymbolId> {
take(&mut self.map)
}
/// Iterate over each unique map `from` entry.
///
/// Multiple mappings may reference the same source field,
/// which would produce duplicate values if they are not filtered.
#[inline]
pub fn iter_map_froms_uniq(&self) -> hash_set::IntoIter<SymbolId> {
self.iter_map()
.filter_map(|ident| {
ident.src().expect("internal error: missing map src").from
})
.collect::<FxHashSet<SymbolId>>()
.into_iter()
fn take_map_froms(&mut self) -> FxHashSet<SymbolId> {
take(&mut self.map_froms)
}
/// Construct an iterator over the return map section.
#[inline]
pub fn iter_retmap(&self) -> SectionsIter {
SectionsIter(SectionsIterType::Single(self.retmap.iter()))
fn take_retmap(&mut self) -> Vec<SymbolId> {
take(&mut self.retmap)
}
/// Construct an iterator over the executable `rater` section.
#[inline]
pub fn iter_exec(&self) -> SectionsIter {
SectionsIter(SectionsIterType::Single(self.rater.iter()))
fn take_exec(&mut self) -> Vec<SymbolId> {
take(&mut self.exec)
}
}
fn expect_frag(
ident_name: SymbolId,
frag: Option<SymbolId>,
) -> PushResult<SymbolId> {
frag.ok_or(SectionsError::MissingFragment(ident_name))
}
/// Error during [`Sections`] building.
#[derive(Debug, PartialEq)]
pub enum SectionsError {
@ -269,6 +195,9 @@ pub enum SectionsError {
/// or a bug in the compiler itself.
UnresolvedObject(UnresolvedError),
/// Identifier is missing an expected text fragment.
MissingFragment(SymbolId),
/// The kind of an object encountered during sorting could not be
/// determined.
///
@ -298,6 +227,11 @@ impl std::fmt::Display for SectionsError {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::UnresolvedObject(err) => err.fmt(fmt),
Self::MissingFragment(name) => write!(
fmt,
"missing text fragment for object `{}` (this may be a compiler bug!)",
name,
),
Self::MissingObjectKind(name) => write!(
fmt,
"missing object kind for object `{}` (this may be a compiler bug!)",
@ -307,205 +241,199 @@ impl std::fmt::Display for SectionsError {
}
}
// Compose the chained iterator type for [`SectionsIter`].
// This could be further abstracted away,
// but it's likely that `Sections` will be simplified in the future.
type SIter<'a> = SectionIter<'a>;
type CSIter1<'a, L> = Chain<L, SIter<'a>>;
type CSIter2<'a, L> = CSIter1<'a, CSIter1<'a, L>>;
type SIter4<'a> = CSIter2<'a, CSIter1<'a, SIter<'a>>>;
/// Types of iterators encapsulated by [`SectionsIter`].
enum SectionsIterType<'a> {
All(SIter4<'a>),
Single(SIter<'a>),
}
/// Iterator over each of the sections.
///
/// This iterator should be created with [`Sections::iter_all`].
///
/// This hides the complex iterator type from callers.
pub struct SectionsIter<'a>(SectionsIterType<'a>);
impl<'a> Iterator for SectionsIter<'a> {
type Item = &'a IdentObject;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
match &mut self.0 {
SectionsIterType::All(inner) => inner.next(),
SectionsIterType::Single(inner) => inner.next(),
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
match &self.0 {
SectionsIterType::All(inner) => inner.size_hint(),
SectionsIterType::Single(inner) => inner.size_hint(),
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::ir::asg::{IdentKind, IdentObject, Source};
use crate::ir::asg::{Dim, IdentKind, IdentObject, Source};
use crate::ir::legacyir::SymDtype;
use crate::sym::GlobalSymbolIntern;
type Sut<'a> = Section<'a>;
type Sut<'a> = Sections<'a>;
#[test]
fn section_empty() {
let section = Sut::new();
fn sections_empty() {
let mut sut = Sut::new();
assert!(section.head.is_none());
assert!(section.body.is_empty());
assert!(section.tail.is_none());
assert!(sut.take_deps().is_empty());
assert!(sut.take_map_froms().is_empty());
assert!(sut.take_map().is_empty());
assert!(sut.take_retmap().is_empty());
assert!(sut.take_static().is_empty());
assert!(sut.take_exec().is_empty());
}
#[test]
fn section_head() {
let mut section = Sut::new();
let obj = IdentObject::Missing("sym".intern());
fn sections_push_adds_dep() -> PushResult {
let mut sut = Sut::new();
assert!(section.head.is_none());
section.set_head(&obj);
assert_eq!(Some(&obj), section.head);
}
#[test]
fn section_body() {
let mut section = Sut::new();
let obj = IdentObject::Missing("sym".intern());
assert!(section.body.is_empty());
section.push_body(&obj);
let body = section.body;
assert_eq!(Some(&&obj), body.get(0));
}
#[test]
fn section_tail() {
let mut section = Sut::new();
let obj = IdentObject::Missing("sym".intern());
assert!(section.tail.is_none());
section.set_tail(&obj);
assert_eq!(Some(&obj), section.tail);
}
#[test]
fn section_is_empty_head() {
let mut section = Sut::new();
let obj = IdentObject::Missing("sym".intern());
// head does not contribute
assert!(section.is_empty());
section.set_head(&obj);
assert!(section.is_empty());
}
#[test]
fn section_is_empty_body() {
let mut section = Sut::new();
let obj = IdentObject::Missing("sym".intern());
assert!(section.is_empty());
section.push_body(&obj);
assert!(!section.is_empty());
}
#[test]
fn section_is_empty_tail() {
let mut section = Sut::new();
let obj = IdentObject::Missing("sym".intern());
// tail does not contribute
assert!(section.is_empty());
section.set_tail(&obj);
assert!(section.is_empty());
}
#[test]
fn section_iterator() {
let mut section = Sut::new();
let obj = IdentObject::Missing("sym".intern());
let expect = vec![&obj, &obj, &obj];
section.set_head(&obj);
section.push_body(&obj);
section.set_tail(&obj);
let collection: Vec<_> = section.iter().collect();
assert_eq!(expect, collection);
}
#[test]
fn sections_iter_all() {
let mut sections = Sections::new();
let objs = (0..=5)
.map(|i| IdentObject::Missing(i.to_string().into()))
.collect::<Vec<_>>();
sections.map.set_head(&objs[0]);
sections.map.body.push(&objs[1]);
sections.map.set_tail(&objs[2]);
sections.retmap.body.push(&objs[3]);
sections.st.body.push(&objs[4]);
sections.rater.body.push(&objs[5]);
assert_eq!(
sections.iter_all().collect::<Vec<_>>(),
objs.iter().collect::<Vec<_>>()
let a = IdentObject::IdentFragment(
"a".intern(),
IdentKind::Const(Dim::from_u8(0), SymDtype::Integer),
Default::default(),
"fraga".intern(),
);
// Different section than a, to be sure that we still add it.
let b = IdentObject::IdentFragment(
"b".intern(),
IdentKind::MapHead,
Default::default(),
"fragb".intern(),
);
sut.push(&a)?;
sut.push(&b)?;
assert_eq!(sut.take_deps(), vec![&a, &b],);
Ok(())
}
// Certain identifiers have no fragments because the code is associated
// with their parents (for now, anyway).
#[test]
fn idents_not_needing_fragments() -> PushResult {
let mut sut = Sut::new();
let cgen = IdentObject::Ident(
"cgen".intern(),
IdentKind::Cgen(Dim::from_u8(1)),
Default::default(),
);
let gen = IdentObject::Ident(
"gen".intern(),
IdentKind::Gen(Dim::from_u8(1), SymDtype::Integer),
Default::default(),
);
let lparam = IdentObject::Ident(
"lparam".intern(),
IdentKind::Lparam(Dim::from_u8(1), SymDtype::Integer),
Default::default(),
);
sut.push(&cgen)?;
sut.push(&gen)?;
sut.push(&lparam)?;
// They should be added as deps...
assert_eq!(sut.take_deps(), vec![&cgen, &gen, &lparam]);
// ...but not added to any sections.
assert!(sut.take_map_froms().is_empty());
assert!(sut.take_map().is_empty());
assert!(sut.take_retmap().is_empty());
assert!(sut.take_static().is_empty());
assert!(sut.take_exec().is_empty());
Ok(())
}
#[test]
fn sections_iter_map_froms_uniq() {
fn sections_map_froms_is_uniq() -> PushResult {
let mut sut_a = Sections::new();
let mut sut_b = Sections::new();
let a = IdentObject::Ident(
let a = IdentObject::IdentFragment(
"a".intern(),
IdentKind::Map,
Source {
from: Some("froma".intern()),
..Default::default()
},
"mapa".intern(),
);
let b = IdentObject::Ident(
let b = IdentObject::IdentFragment(
"a".intern(),
IdentKind::Map,
Source {
from: Some("fromb".intern()),
..Default::default()
},
"mapb".intern(),
);
// A contains duplicates.
sut_a.map.body.push(&a);
sut_a.map.body.push(&a);
sut_a.map.body.push(&b);
sut_a.push(&a)?;
sut_a.push(&a)?;
sut_a.push(&b)?;
// B does not.
sut_b.map.body.push(&a);
sut_b.map.body.push(&b);
sut_b.push(&a)?;
sut_b.push(&b)?;
let a_froms = sut_a.take_map_froms();
// They should compare the same.
assert_eq!(a_froms, sut_b.take_map_froms());
// And should use the proper ids.
assert!(a_froms.contains(&"froma".intern()));
assert!(a_froms.contains(&"fromb".intern()));
Ok(())
}
macro_rules! add_syms {
($sut:ident, { $($name:ident: $kind:expr,)* }) => {
$(
let $name = IdentObject::IdentFragment(
stringify!($name).intern(),
$kind,
Default::default(),
stringify!($kind).intern(), // fragment
);
$sut.push(&$name)?;
)*
};
}
macro_rules! fragvec {
($($name:ident),*) => {
vec![
$(&$name),*
].into_iter().map(|x| *x.fragment().unwrap()).collect::<Vec<SymbolId>>()
}
}
#[test]
fn push_sorts_fragments_into_sections() -> PushResult {
let mut sut = Sections::new();
add_syms!(sut, {
cgen: IdentKind::Cgen(Dim::from_u8(1)),
class: IdentKind::Class(Dim::from_u8(2)),
const_: IdentKind::Const(Dim::from_u8(0), SymDtype::Boolean),
func: IdentKind::Func(Dim::from_u8(1), SymDtype::Integer),
gen: IdentKind::Gen(Dim::from_u8(1), SymDtype::Boolean),
lparam: IdentKind::Lparam(Dim::from_u8(2), SymDtype::Float),
param: IdentKind::Param(Dim::from_u8(0), SymDtype::Integer),
rate: IdentKind::Rate(SymDtype::Integer),
tpl: IdentKind::Tpl,
ty: IdentKind::Type(SymDtype::Integer),
maphead: IdentKind::MapHead,
map: IdentKind::Map,
maptail: IdentKind::MapTail,
retmaphead: IdentKind::RetMapHead,
retmap: IdentKind::RetMap,
retmaptail: IdentKind::RetMapTail,
meta: IdentKind::Meta,
worksheet: IdentKind::Worksheet,
});
assert_eq!(sut.take_map(), fragvec![maphead, map, maptail]);
assert_eq!(sut.take_retmap(), fragvec![retmaphead, retmap, retmaptail]);
assert_eq!(
sut_a.iter_map_froms_uniq().collect::<Vec<_>>(),
sut_b.iter_map_froms_uniq().collect::<Vec<_>>(),
sut.take_static(),
fragvec![const_, func, param, ty, meta, worksheet]
);
assert_eq!(sut.take_exec(), fragvec![class, rate, tpl]);
Ok(())
}
}

View File

@ -17,7 +17,7 @@
// 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 [`Sections`] into a XIR [`Token`] stream for `xmle` output.
//! Lower [`XmleSections`] into a XIR [`Token`] stream for `xmle` output.
//!
//! This is the final step in the linker,
//! producing the `xmle` file that can be used to produce the standalone
@ -29,7 +29,7 @@
//! which can then use [`XmlWriter`](crate::ir::xir::writer::XmlWriter)
//! for writing.
use super::{super::LSPAN, Sections, SectionsIter};
use super::{super::LSPAN, section::XmleSections};
use crate::{
ir::{
asg::{IdentKind, IdentObject},
@ -41,8 +41,7 @@ use crate::{
sym::{st::*, SymbolId},
};
use arrayvec::ArrayVec;
use std::iter::Chain;
use std::{array, collections::hash_set};
use std::{array, collections::hash_set, iter::Chain, vec};
qname_const! {
QN_DESC: :L_DESC,
@ -106,19 +105,19 @@ const DEP_CLOSE: usize = 1; // open is never stored; see `refill_toks`
/// Size of [`DepListIter`] [`Token`] buffer.
const DEP_TOK_SIZE: usize = DEP_MAX_ATTRS_KEY_VAL + DEP_CLOSE;
/// Iterator that lowers [`Sections`] into `l:dep` as a XIR [`Token`]
/// Iterator that lowers [`XmleSections`] into `l:dep` as a XIR [`Token`]
/// stream.
///
/// This iterator functions by allocating a constant-sized
/// [`ArrayVec`]-based buffer that is populated with token data each time
/// an object is requested from the underlying [`SectionsIter`].
/// an object is requested from the underlying iterator.
/// Once the buffer runs out,
/// another object is requested and the buffer populated with the
/// appropriate token stream.
/// This repeats until no more section object data is available.
struct DepListIter<'a> {
/// Source data to lower into `l:deps`.
iter: SectionsIter<'a>,
iter: vec::IntoIter<&'a IdentObject>,
/// Constant-size [`Token`] buffer used as a stack.
toks: ArrayVec<Token, DEP_TOK_SIZE>,
/// Relative path to project root.
@ -127,9 +126,9 @@ struct DepListIter<'a> {
impl<'a> DepListIter<'a> {
#[inline]
fn new(sections: &'a Sections, relroot: SymbolId) -> Self {
fn new(iter: vec::IntoIter<&'a IdentObject>, relroot: SymbolId) -> Self {
Self {
iter: sections.iter_all(),
iter,
toks: ArrayVec::new(),
// TODO: we cannot trust that an arbitrary symbol is escaped; this
// needs better typing, along with other things.
@ -138,7 +137,7 @@ impl<'a> DepListIter<'a> {
}
/// Re-fill buffer with a new list of [`Token]s representing the next
/// available object from the inner [`SectionsIter`].
/// available object from the inner iterator.
///
/// Each token is pushed onto the buffer _in reverse_,
/// since it is treated like a stack;
@ -262,9 +261,9 @@ struct MapFromsIter {
impl MapFromsIter {
#[inline]
fn new<'a>(sections: &'a Sections) -> Self {
fn new<'a>(iter: hash_set::IntoIter<SymbolId>) -> Self {
let iter = Self {
iter: sections.iter_map_froms_uniq(),
iter,
// Most of the time we have a single `from` (4 tokens).
toks: ArrayVec::new(),
};
@ -298,38 +297,24 @@ impl Iterator for MapFromsIter {
/// Produce text fragments associated with objects.
///
/// Here, "text" refers to the compiled program text.
struct FragmentIter<'a> {
iter: SectionsIter<'a>,
struct FragmentIter {
iter: vec::IntoIter<SymbolId>,
}
impl<'a> FragmentIter<'a> {
impl FragmentIter {
#[inline]
fn new(iter: SectionsIter<'a>) -> Self {
fn new(iter: vec::IntoIter<SymbolId>) -> Self {
Self { iter }
}
}
impl<'a> Iterator for FragmentIter<'a> {
impl Iterator for FragmentIter {
type Item = Token;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.iter
.by_ref()
.filter_map(|obj| {
match obj {
IdentObject::IdentFragment(_, _, _, frag) => Some(*frag),
// These will never have fragments.
IdentObject::Ident(_, IdentKind::Cgen(_), _)
| IdentObject::Ident(_, IdentKind::Gen(_, _), _)
| IdentObject::Ident(_, IdentKind::Lparam(_, _), _) => None,
// Error, but we really should catch that during Section
// lowering.
_ => todo! {},
}
})
.map(|frag| Token::Text(Text::Escaped(frag), LSPAN))
.next()
}
@ -351,13 +336,13 @@ pub struct LowerIter<'a>(
Chain<HeaderIter, ElemWrapIter<DepListIter<'a>>>,
ElemWrapIter<MapFromsIter>,
>,
ElemWrapIter<FragmentIter<'a>>,
ElemWrapIter<FragmentIter>,
>,
ElemWrapIter<FragmentIter<'a>>,
ElemWrapIter<FragmentIter>,
>,
ElemWrapIter<FragmentIter<'a>>,
ElemWrapIter<FragmentIter>,
>,
ElemWrapIter<FragmentIter<'a>>,
ElemWrapIter<FragmentIter>,
>,
>,
);
@ -366,7 +351,7 @@ impl<'a> Iterator for LowerIter<'a> {
type Item = Token;
/// Produce the next XIR [`Token`] representing the lowering of
/// [`Sections`] from the [ASG](crate::ir::asg).
/// [`XmleSections`] from the [ASG](crate::ir::asg).
///
/// This produces a single token at a time,
/// but [`DepListIter`] buffers tokens before emitting them,
@ -378,14 +363,14 @@ impl<'a> Iterator for LowerIter<'a> {
}
}
/// Lower [`Sections`] into a XIR [`Token`] stream for writing.
/// Lower [`XmleSections`] into a XIR [`Token`] stream for writing.
///
/// This produces the final representation for the `xmle` file,
/// which can be written using
/// [`XmlWriter`](crate::ir::xir::writer::XmlWriter).
#[inline]
pub fn lower_iter<'a>(
sections: &'a Sections,
pub fn lower_iter<'a, S: XmleSections<'a>>(
mut sections: S,
pkg_name: SymbolId,
relroot: SymbolId,
) -> LowerIter<'a> {
@ -396,28 +381,32 @@ pub fn lower_iter<'a>(
.chain(elem_wrap(
QN_L_DEP,
LSPAN,
DepListIter::new(sections, relroot),
DepListIter::new(sections.take_deps().into_iter(), relroot),
))
.chain(elem_wrap(
QN_L_MAP_FROM,
LSPAN,
MapFromsIter::new(sections.take_map_froms().into_iter()),
))
.chain(elem_wrap(QN_L_MAP_FROM, LSPAN, MapFromsIter::new(sections)))
.chain(elem_wrap(
QN_L_MAP_EXEC,
LSPAN,
FragmentIter::new(sections.iter_map()),
FragmentIter::new(sections.take_map().into_iter()),
))
.chain(elem_wrap(
QN_L_RETMAP_EXEC,
LSPAN,
FragmentIter::new(sections.iter_retmap()),
FragmentIter::new(sections.take_retmap().into_iter()),
))
.chain(elem_wrap(
QN_L_STATIC,
LSPAN,
FragmentIter::new(sections.iter_static()),
FragmentIter::new(sections.take_static().into_iter()),
))
.chain(elem_wrap(
QN_L_EXEC,
LSPAN,
FragmentIter::new(sections.iter_exec()),
FragmentIter::new(sections.take_exec().into_iter()),
)),
))
}

View File

@ -19,15 +19,16 @@
use super::*;
use crate::convert::ExpectInto;
use crate::ir::asg::IdentObjectData;
use crate::ir::legacyir::SymDtype;
use crate::ir::{
asg::{Dim, IdentKind, Source},
asg::{Dim, IdentKind, IdentObjectData, Source},
legacyir::SymDtype,
xir::{
pred::{not, open},
tree::{parser_from, Attr},
},
};
use crate::ld::xmle::section::PushResult;
use crate::ld::xmle::Sections;
use crate::sym::{GlobalSymbolIntern, GlobalSymbolResolve};
use std::collections::HashSet;
@ -49,7 +50,7 @@ fn test_produces_header() -> TestResult {
let name = "test-pkg".intern();
let relroot = "rel/root/".intern();
let result = lower_iter(&empty, name, relroot)
let result = lower_iter(empty, name, relroot)
.take_while(|tok| match tok {
// TODO
Token::Close(_, _) => false,
@ -66,7 +67,7 @@ fn test_produces_header() -> TestResult {
fn test_closes_package() -> TestResult {
let empty = Sections::new();
let result = lower_iter(&empty, "foo".intern(), "relroot".intern()).last();
let result = lower_iter(empty, "foo".intern(), "relroot".intern()).last();
assert_eq!(Some(Token::Close(Some(QN_PACKAGE), LSPAN)), result);
Ok(())
@ -74,7 +75,6 @@ fn test_closes_package() -> TestResult {
#[test]
fn test_writes_deps() -> TestResult {
let mut sections = Sections::new();
let relroot = "relroot-deps".intern();
let objs = [
@ -179,10 +179,48 @@ fn test_writes_deps() -> TestResult {
),
];
objs.iter().for_each(|x| sections.st.push_body(x));
// Creating a stub to return our deps prevents us from being obstructed
// by changes to Sections' requirements.
struct StubSections<'a> {
deps: Vec<&'a IdentObject>,
}
impl<'a> XmleSections<'a> for StubSections<'a> {
fn push(&mut self, _ident: &'a IdentObject) -> PushResult {
unimplemented!()
}
fn take_deps(&mut self) -> Vec<&'a IdentObject> {
self.deps.clone()
}
fn take_static(&mut self) -> Vec<SymbolId> {
vec![]
}
fn take_map(&mut self) -> Vec<SymbolId> {
vec![]
}
fn take_map_froms(&mut self) -> fxhash::FxHashSet<SymbolId> {
Default::default()
}
fn take_retmap(&mut self) -> Vec<SymbolId> {
vec![]
}
fn take_exec(&mut self) -> Vec<SymbolId> {
vec![]
}
}
let sections = StubSections {
deps: objs.iter().collect(),
};
let mut iter = parser_from(
lower_iter(&sections, "pkg".intern(), relroot)
lower_iter(sections, "pkg".intern(), relroot)
.skip_while(not(open(QN_L_DEP))),
);
@ -345,32 +383,34 @@ fn test_writes_map_froms() -> TestResult {
let mut sections = Sections::new();
let relroot = "relroot-deps".intern();
let a = IdentObject::Ident(
let a = IdentObject::IdentFragment(
"a".intern(),
IdentKind::Map,
Source {
from: Some("froma".intern()),
..Default::default()
},
"fraga".intern(),
);
let b = IdentObject::Ident(
let b = IdentObject::IdentFragment(
"a".intern(),
IdentKind::Map,
Source {
from: Some("fromb".intern()),
..Default::default()
},
"fragb".intern(),
);
// Add a duplicate just to ensure that we're using the right method on
// `Sections` for uniqueness.
sections.map.push_body(&a);
sections.map.push_body(&a);
sections.map.push_body(&b);
sections.push(&a)?;
sections.push(&a)?;
sections.push(&b)?;
let mut iter = parser_from(
lower_iter(&sections, "pkg".intern(), relroot)
lower_iter(sections, "pkg".intern(), relroot)
.skip_while(not(open(QN_L_MAP_FROM))),
);
@ -409,7 +449,7 @@ fn test_writes_map_froms() -> TestResult {
}
macro_rules! test_exec_sec {
($name:ident, $qn:ident, $sec:ident) => {
($name:ident, $qn:ident, $type:expr) => {
#[test]
fn $name() -> TestResult {
let mut sections = Sections::new();
@ -420,23 +460,23 @@ macro_rules! test_exec_sec {
let a = IdentObject::IdentFragment(
"a".intern(),
IdentKind::Map,
$type,
Default::default(),
frag_a,
);
let b = IdentObject::IdentFragment(
"b".intern(),
IdentKind::Map,
$type,
Default::default(),
frag_b,
);
sections.$sec.push_body(&a);
sections.$sec.push_body(&b);
sections.push(&a)?;
sections.push(&b)?;
let mut iter = parser_from(
lower_iter(&sections, "pkg".intern(), relroot)
lower_iter(sections, "pkg".intern(), relroot)
.skip_while(not(open($qn))),
);
@ -467,7 +507,7 @@ macro_rules! test_exec_sec {
};
}
test_exec_sec!(test_map_exec, QN_L_MAP_EXEC, map);
test_exec_sec!(test_retmap_exec, QN_L_RETMAP_EXEC, retmap);
test_exec_sec!(test_static, QN_L_STATIC, st);
test_exec_sec!(test_exec, QN_L_EXEC, rater);
test_exec_sec!(test_map_exec, QN_L_MAP_EXEC, IdentKind::Map);
test_exec_sec!(test_retmap_exec, QN_L_RETMAP_EXEC, IdentKind::RetMap);
test_exec_sec!(test_static, QN_L_STATIC, IdentKind::Worksheet);
test_exec_sec!(test_exec, QN_L_EXEC, IdentKind::Class(Dim::from_u8(1)));

View File

@ -309,6 +309,9 @@ static_symbol_newtypes! {
/// may need escaping in XML attributes.
uri: UriStaticSymbolId<global::ProgSymSize>,
/// Any other generic string that does not fit into any particular type.
str: GenericStaticSymbolId<global::ProgSymSize>,
/// Static 16-bit [`Span`](crate::span::Span) context.
///
/// These contexts are intended for use in generated code where a better
@ -434,6 +437,11 @@ pub mod st {
L_XMLNS: cid "xmlns",
L_YIELDS: cid "yields",
L_MAP_UUUHEAD: str ":map:___head",
L_MAP_UUUTAIL: str ":map:___tail",
L_RETMAP_UUUHEAD: str ":retmap:___head",
L_RETMAP_UUUTAIL: str ":retmap:___tail",
URI_LV_RATER: uri "http://www.lovullo.com/rater",
URI_LV_PREPROC: uri "http://www.lovullo.com/rater/preproc",
URI_LV_LINKER: uri "http://www.lovullo.com/rater/linker",