tame/tamer/src/ld/xmle/xir/test.rs

491 lines
13 KiB
Rust

// Test lowering operations into XIR.
//
// Copyright (C) 2014-2023 Ryan Specialty, LLC.
//
// This file is part of TAME.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use crate::{
asg::{IdentKind, Source},
ld::xmle::{section::PushResult, Sections},
num::{Dim, Dtype},
parse::util::SPair,
span::dummy::*,
sym::{GlobalSymbolIntern, GlobalSymbolResolve},
xir::{
pred::{not, open},
tree::{merge_attr_fragments, parser_from},
},
};
use std::collections::HashSet;
type TestResult = Result<(), Box<dyn std::error::Error>>;
macro_rules! assert_attr{
($attrs:ident, $name:ident, $expected:expr, $($args:expr),*) => {
assert_eq!(
$attrs.find($name).map(|a| a.value()),
$expected,
$($args),*
)
}
}
#[test]
fn test_produces_header() -> TestResult {
let empty = Sections::new();
let name = "test-pkg".intern();
let relroot = "rel/root/".intern();
let result = lower_iter(empty, name, relroot)
.take_while(|tok| match tok {
// TODO
Token::Close(_, _) => false,
_ => true,
})
.collect::<Vec<Token>>();
assert_eq!(Token::Open(QN_PACKAGE, LSPAN.into()), result[0]);
Ok(())
}
#[test]
fn test_closes_package() -> TestResult {
let empty = Sections::new();
let result = lower_iter(empty, "foo".intern(), "relroot".intern()).last();
assert_eq!(Some(Token::Close(Some(QN_PACKAGE), LSPAN.into())), result);
Ok(())
}
#[test]
fn test_writes_deps() -> TestResult {
let relroot = "relroot-deps".intern();
let objs = [
Ident::Opaque(
SPair("cgentest".into(), S1),
IdentKind::Cgen(Dim::Vector),
Source {
yields: Some("yieldsValue".intern()),
parent: Some("cparent".intern()),
generated: true,
pkg_name: Some("pkg/name".intern()),
..Default::default()
},
),
Ident::Opaque(
SPair("classtest".into(), S2),
IdentKind::Class(Dim::Matrix),
Default::default(),
),
Ident::Opaque(
SPair("consttest".into(), S3),
IdentKind::Const(Dim::Scalar, Dtype::Boolean),
Default::default(),
),
Ident::Opaque(
SPair("functest".into(), S4),
IdentKind::Func(Dim::Matrix, Dtype::Integer),
Default::default(),
),
Ident::Opaque(
SPair("gentest".into(), S5),
IdentKind::Gen(Dim::Matrix, Dtype::Boolean),
Default::default(),
),
Ident::Opaque(
SPair("lparamtest".into(), S6),
IdentKind::Gen(Dim::Matrix, Dtype::Float),
Default::default(),
),
Ident::Opaque(
SPair("paramtest".into(), S7),
IdentKind::Gen(Dim::Scalar, Dtype::Integer),
Default::default(),
),
Ident::Opaque(
SPair("ratetest".into(), S8),
IdentKind::Rate(Dtype::Integer),
Default::default(),
),
Ident::Opaque(
SPair("tpltest".into(), S9),
IdentKind::Tpl,
Default::default(),
),
Ident::Opaque(
SPair("typetest".into(), S1),
IdentKind::Type(Dtype::Integer),
Default::default(),
),
Ident::Opaque(
SPair("mapheadtest".into(), S2),
IdentKind::MapHead,
Default::default(),
),
Ident::Opaque(
SPair("maptest".into(), S3),
IdentKind::Map,
Default::default(),
),
Ident::Opaque(
SPair("maptailtest".into(), S4),
IdentKind::MapTail,
Default::default(),
),
Ident::Opaque(
SPair("retmapheadtest".into(), S5),
IdentKind::RetMapHead,
Default::default(),
),
Ident::Opaque(
SPair("retmaptest".into(), S6),
IdentKind::RetMap,
Default::default(),
),
Ident::Opaque(
SPair("retmaptailtest".into(), S7),
IdentKind::RetMapTail,
Default::default(),
),
Ident::Opaque(
SPair("metatest".into(), S8),
IdentKind::Meta,
Source {
desc: Some("test desc".intern()),
..Default::default()
},
),
Ident::Opaque(
SPair("worksheettest".into(), S9),
IdentKind::Worksheet,
Default::default(),
),
];
// Creating a stub to return our deps prevents us from being obstructed
// by changes to Sections' requirements.
struct StubSections<'a> {
deps: Vec<&'a Ident>,
}
impl<'a> XmleSections<'a> for StubSections<'a> {
fn push(&mut self, _ident: &'a Ident) -> PushResult {
unimplemented!()
}
fn take_deps(&mut self) -> Vec<&'a Ident> {
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 lower_iter = lower_iter(sections, "pkg".intern(), relroot)
.skip_while(not(open(QN_L_DEP)));
let mut iter = parser_from(merge_attr_fragments(&mut lower_iter));
let given = iter
.next()
.expect("tree object expected")
.unwrap() // Tree
.into_element()
.expect("element expected");
// Sanity check to ensure we have the element we're expecting.
assert_eq!(QN_L_DEP, given.name());
let children = given.children();
assert_eq!(
children.len(),
objs.len(),
"expected child node for each dependency"
);
let p_syms = children.into_iter().map(|child| {
let ele = child.as_element().expect("child expected to be an element");
assert_eq!(QN_P_SYM, ele.name());
ele
});
p_syms.enumerate().for_each(|(i, ele)| {
let ident = &objs[i];
let attrs = ele.attrs();
assert_eq!(
attrs.find(QN_NAME).map(|a| a.value()),
ident.name().map(|name| name.symbol()),
);
assert_eq!(
attrs.find(QN_TYPE).map(|a| a.value()),
Some(ident.kind().unwrap().as_sym())
);
let generated = attrs.find(QN_P_GENERATED).map(|a| a.value());
if let Some(Source {
generated: true, ..
}) = ident.src()
{
assert_eq!(generated, Some("true".intern()));
} else {
assert_eq!(generated, None);
}
if let Some(Source { parent, .. }) = ident.src() {
assert_attr!(attrs, QN_PARENT, *parent,);
}
if let Some(Source { yields, .. }) = ident.src() {
assert_attr!(attrs, QN_YIELDS, *yields,);
}
if let Some(Source {
desc: Some(desc), ..
}) = ident.src()
{
assert_attr!(attrs, QN_DESC, Some(*desc),);
}
if let Some(Source {
pkg_name: Some(pkg_name),
..
}) = ident.src()
{
let expected = [relroot, *pkg_name]
.iter()
.map(GlobalSymbolResolve::lookup_str)
.collect::<String>()
.intern();
assert_attr!(attrs, QN_SRC, Some(expected),);
}
// Object-specific attributes
match ident.kind().unwrap() {
IdentKind::Cgen(dim) | IdentKind::Class(dim) => {
assert_attr!(
attrs,
QN_DIM,
Some(Into::<SymbolId>::into(*dim)),
"invalid {:?} @dim",
ident.kind()
);
}
IdentKind::Const(dim, dtype)
| IdentKind::Func(dim, dtype)
| IdentKind::Gen(dim, dtype)
| IdentKind::Lparam(dim, dtype)
| IdentKind::Param(dim, dtype) => {
assert_attr!(
attrs,
QN_DIM,
Some((*dim).into()),
"invalid {:?} @dim",
ident.kind()
);
assert_attr!(
attrs,
QN_DTYPE,
Some((*dtype).into()),
"invalid {:?} @dtype",
ident.kind()
);
}
IdentKind::Rate(dtype) | IdentKind::Type(dtype) => {
assert_attr!(
attrs,
QN_DTYPE,
Some((*dtype).into()),
"invalid {:?} @dim",
ident.kind()
);
}
// The others have no additional attributes
_ => {}
}
});
Ok(())
}
#[test]
fn test_writes_map_froms() -> TestResult {
let mut sections = Sections::new();
let relroot = "relroot-deps".intern();
let a = Ident::IdentFragment(
SPair("a".into(), S1),
IdentKind::Map,
Source {
from: Some("froma".intern()),
..Default::default()
},
"fraga".intern(),
);
let b = Ident::IdentFragment(
SPair("a".into(), S2),
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.push(&a)?;
sections.push(&a)?;
sections.push(&b)?;
let mut iter = parser_from(
lower_iter(sections, "pkg".intern(), relroot)
.skip_while(not(open(QN_L_MAP_FROM))),
);
let given = iter
.next()
.expect("tree object expected")
.unwrap() // Tree
.into_element()
.expect("element expected");
// Sanity check to ensure we have the element we're expecting.
assert_eq!(QN_L_MAP_FROM, given.name());
let froms = given.children();
let mut found = HashSet::new();
froms.iter().for_each(|from| {
assert_eq!(QN_L_FROM, from.as_element().unwrap().name());
found.insert(
from.as_element()
.unwrap()
.attrs()
.find(QN_NAME)
.expect("expecting @name")
.value(),
);
});
assert!(found.contains(&"froma".intern()));
assert!(found.contains(&"fromb".intern()));
Ok(())
}
macro_rules! test_exec_sec {
($name:ident, $qn:ident, $type:expr) => {
#[test]
fn $name() -> TestResult {
let mut sections = Sections::new();
let relroot = "relroot-exec".intern();
let frag_a = "a fragment".intern();
let frag_b = "b fragment".intern();
let a = Ident::IdentFragment(
SPair("a".into(), S1),
$type,
Default::default(),
frag_a,
);
let b = Ident::IdentFragment(
SPair("b".into(), S2),
$type,
Default::default(),
frag_b,
);
sections.push(&a)?;
sections.push(&b)?;
let mut iter = parser_from(
lower_iter(sections, "pkg".intern(), relroot)
.skip_while(not(open($qn))),
);
let given = iter
.next()
.expect("tree object expected")
.unwrap() // Tree
.into_element()
.expect("element expected");
// Sanity check to ensure we have the element we're expecting.
assert_eq!($qn, given.name());
let nodes = given.children();
// The text is considered escaped since the fragment was already escaped
// within the xmlo file it was read from.
// Order _absolutely_ matters,
// since the purpose of the linker is to put things into the correct
// order for execution.
assert_eq!(Some(frag_a), nodes[0].as_sym());
assert_eq!(Some(frag_b), nodes[1].as_sym());
assert_eq!(nodes.len(), 2);
Ok(())
}
};
}
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::Vector));