tame/tamer/src/obj/xmlo/air/test.rs

590 lines
16 KiB
Rust

// Tests lowering `xmlo` object file into AIR
//
// 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::{FragmentText, IdentKind},
num::{Dim, Dtype},
obj::xmlo::{SymAttrs, SymType},
parse::Parsed,
span::dummy::*,
sym::GlobalSymbolIntern,
};
type Sut = XmloToAir;
use Parsed::{Incomplete, Object as O};
#[test]
fn data_from_package_event() {
let name = "name".into();
let relroot = "some/path".into();
#[rustfmt::skip]
let toks = vec![
PkgStart(S1),
PkgName(SPair(name, S2)),
PkgRootPath(SPair(relroot, S4)),
Eoh(S4),
]
.into_iter();
let mut sut = Sut::parse(toks);
assert_eq!(
#[rustfmt::skip]
Ok(vec![
Incomplete, // PkgStart
O(Air::PkgStart(S1, SPair(name, S2))),
Incomplete, // PkgRootPath
O(Air::PkgEnd(S4)),
]),
sut.by_ref().collect(),
);
let ctx = sut.finalize().unwrap().into_context();
assert_eq!(Some(name), ctx.prog_name);
assert_eq!(Some(relroot), ctx.relroot);
}
#[test]
fn adds_elig_as_root() {
let name = "name-root".into();
let elig_sym = "elig".into();
#[rustfmt::skip]
let toks = vec![
PkgStart(S1),
PkgName(SPair(name, S2)),
PkgEligClassYields(SPair(elig_sym, S3)),
Eoh(S4),
];
assert_eq!(
#[rustfmt::skip]
Ok(vec![
Incomplete, // PkgStart
O(Air::PkgStart(S1, SPair(name, S2))),
O(Air::IdentRoot(SPair(elig_sym, S3))),
O(Air::PkgEnd(S4)), // Eoh
]),
Sut::parse(toks.into_iter()).collect(),
);
}
#[test]
fn adds_sym_deps() {
let name = "name".into();
let sym_from = "from".into();
let sym_to1 = "to1".into();
let sym_to2 = "to2".into();
#[rustfmt::skip]
let toks = vec![
PkgStart(S1),
PkgName(SPair(name, S2)),
SymDepStart(SPair(sym_from, S3)),
Symbol(SPair(sym_to1, S4)),
Symbol(SPair(sym_to2, S5)),
Eoh(S6),
];
assert_eq!(
#[rustfmt::skip]
Ok(vec![
Incomplete, // PkgStart
O(Air::PkgStart(S1, SPair(name, S2))),
Incomplete, // SymDepStart
O(Air::IdentDep(SPair(sym_from, S3), SPair(sym_to1, S4))),
O(Air::IdentDep(SPair(sym_from, S3), SPair(sym_to2, S5))),
O(Air::PkgEnd(S6)),
]),
Sut::parse(toks.into_iter()).collect(),
);
}
#[test]
fn sym_decl_with_src_not_added_and_populates_found() {
let name = "name".into();
let sym = "sym".into();
let src_a = "src_a".into();
let src_b = "src_b".into();
#[rustfmt::skip]
let toks = vec![
PkgStart(S1),
PkgName(SPair(name, S2)),
SymDecl(
SPair(sym, S3),
SymAttrs {
src: Some(src_a),
..Default::default()
},
),
SymDecl(
SPair(sym, S4),
SymAttrs {
src: Some(src_b),
..Default::default()
},
),
Eoh(S5),
];
let mut sut = Sut::parse(toks.into_iter());
assert_eq!(
#[rustfmt::skip]
Ok(vec![
Incomplete, // PkgStart
O(Air::PkgStart(S1, SPair(name, S2))),
Incomplete, // SymDecl (@src)
Incomplete, // SymDecl (@src)
O(Air::PkgEnd(S5)),
]),
sut.by_ref().collect(),
);
let ctx = sut.finalize().unwrap().into_context();
let mut founds = ctx.found.unwrap().into_iter().collect::<Vec<_>>();
// Just to remove nondeterminism in case the iteration order happens
// to change.
founds.sort();
assert_eq!(vec![src_a, src_b], founds);
}
#[test]
fn sym_decl_added_to_graph() {
let name = "name".into();
let sym_extern = "sym_extern".into();
let sym_non_extern = "sym_non_extern".into();
let sym_map = "sym_map".into();
let sym_retmap = "sym_retmap".into();
let pkg_name = "pkg name".into();
#[rustfmt::skip]
let toks = vec![
PkgStart(S1),
PkgName(SPair(name, S2)),
SymDecl(
SPair(sym_extern, S3),
SymAttrs {
pkg_name: Some(pkg_name),
extern_: true,
ty: Some(SymType::Meta),
..Default::default()
},
),
SymDecl(
SPair(sym_non_extern, S4),
SymAttrs {
pkg_name: Some(pkg_name),
ty: Some(SymType::Meta),
..Default::default()
},
),
SymDecl(
SPair(sym_map, S5),
SymAttrs {
pkg_name: Some(pkg_name),
ty: Some(SymType::Map),
..Default::default()
},
),
SymDecl(
SPair(sym_retmap, S6),
SymAttrs {
pkg_name: Some(pkg_name),
ty: Some(SymType::RetMap),
..Default::default()
},
),
Eoh(S7),
];
let mut sut = Sut::parse(toks.into_iter());
// Note that each of these will have their package names cleared
// since this is considered to be the first package encountered.
assert_eq!(Some(Ok(Incomplete)), sut.next()); // PkgStart
assert_eq!(Some(Ok(O(Air::PkgStart(S1, SPair(name, S2))))), sut.next()); // PkgName
assert_eq!(
Some(Ok(O(Air::IdentExternDecl(
SPair(sym_extern, S3),
IdentKind::Meta,
Source {
pkg_name: None,
..Default::default()
}
)))),
sut.next(),
);
assert_eq!(
Some(Ok(O(Air::IdentDecl(
SPair(sym_non_extern, S4),
IdentKind::Meta,
Source {
pkg_name: None,
..Default::default()
}
)))),
sut.next(),
);
assert_eq!(
Some(Ok(O(Air::IdentDecl(
SPair(sym_map, S5),
IdentKind::Map,
Source {
pkg_name: None,
..Default::default()
}
)))),
sut.next(),
);
assert_eq!(
Some(Ok(O(Air::IdentDecl(
SPair(sym_retmap, S6),
IdentKind::RetMap,
Source {
pkg_name: None,
..Default::default()
}
)))),
sut.next(),
);
assert_eq!(Some(Ok(O(Air::PkgEnd(S7)))), sut.next());
let ctx = sut.finalize().unwrap().into_context();
// Both above symbols were local (no `src`),
// but note that we don't care if it's None or initialized with a
// length of 0.
assert!(ctx.found.unwrap_or_default().len() == 0);
}
// See above test, where pkg_name was cleared.
#[test]
fn sym_decl_pkg_name_retained_if_not_first() {
let sym = "sym".into();
let pkg_name = "pkg name".into();
// This is all that's needed to not consider this to be the first
// package,
// so that pkg_name is retained below.
let ctx = XmloAirContext {
first: false,
..Default::default()
};
#[rustfmt::skip]
let toks = vec![
PkgStart(S1),
PkgName(SPair(pkg_name, S2)),
SymDecl(
SPair(sym, S3),
SymAttrs {
pkg_name: Some(pkg_name),
ty: Some(SymType::Meta),
..Default::default()
},
),
Eoh(S4),
];
assert_eq!(
#[rustfmt::skip]
Ok(vec![
Incomplete, // PkgStart
O(Air::PkgStart(S1, SPair(pkg_name, S2))),
O(Air::IdentDecl(
SPair(sym, S3),
IdentKind::Meta,
Source {
pkg_name: Some(pkg_name),
..Default::default()
}
)),
O(Air::PkgEnd(S4)),
]),
Sut::parse_with_context(toks.into_iter(), ctx).collect(),
);
}
// This used to be set in SymAttrs by XmloReader,
// but that's no longer true with the new reader.
#[test]
fn sym_decl_pkg_name_set_if_empty_and_not_first() {
let sym = "sym".into();
let pkg_name = "pkg name".into();
let ctx = XmloAirContext {
first: false,
..Default::default()
};
#[rustfmt::skip]
let toks = vec![
PkgStart(S1),
PkgName(SPair(pkg_name, S2)),
SymDecl(
SPair(sym, S3),
SymAttrs {
// No name
ty: Some(SymType::Meta),
..Default::default()
},
),
Eoh(S4),
];
assert_eq!(
#[rustfmt::skip]
Ok(vec![
Incomplete, // PkgStart
O(Air::PkgStart(S1, SPair(pkg_name, S2))),
O(Air::IdentDecl(
SPair(sym, S3),
IdentKind::Meta,
Source {
pkg_name: Some(pkg_name), // Name inherited
..Default::default()
},
)),
O(Air::PkgEnd(S4)),
]),
Sut::parse_with_context(toks.into_iter(), ctx).collect(),
);
}
#[test]
fn ident_kind_conversion_error_propagates() {
let sym = "sym".into();
let bad_attrs = SymAttrs::default();
#[rustfmt::skip]
let toks = vec![
PkgStart(S1),
PkgName(SPair("name".into(), S2)),
SymDecl(SPair(sym, S3), bad_attrs),
Eoh(S1),
];
Sut::parse(toks.into_iter())
.collect::<Result<Vec<_>, _>>()
.expect_err("expected IdentKind conversion error");
}
#[test]
fn sets_fragment() {
let name = "name".into();
let sym = "sym".into();
let frag = FragmentText::from("foo");
#[rustfmt::skip]
let toks = vec![
PkgStart(S1),
PkgName(SPair(name, S2)),
Fragment(SPair(sym, S3), frag.clone()),
Eoh(S4),
];
assert_eq!(
#[rustfmt::skip]
Ok(vec![
Incomplete, // PkgStart
O(Air::PkgStart(S1, SPair(name, S2))),
O(Air::IdentFragment(SPair(sym, S3), frag)),
O(Air::PkgEnd(S4)),
]),
Sut::parse(toks.into_iter()).collect(),
);
}
macro_rules! test_kind {
($name:ident, $src:expr => $dest:expr) => {
#[test]
fn $name() {
assert_eq!(
Ok($dest),
SymAttrs {
ty: Some($src),
..Default::default()
}
.try_into()
);
}
};
($name:ident, $src:expr => $dest:expr, dim) => {
#[test]
fn $name() {
let dim = Dim::Vector;
assert_eq!(
Ok($dest(Dim::Vector)),
SymAttrs {
ty: Some($src),
dim: Some(dim),
..Default::default()
}
.try_into()
);
// no dim
let result = IdentKind::try_from(SymAttrs {
ty: Some($src),
..Default::default()
})
.expect_err("must fail when missing dim");
assert_eq!(XmloAirError::MissingDim, result);
}
};
($name:ident, $src:expr => $dest:expr, dtype) => {
#[test]
fn $name() {
let dtype = Dtype::Float;
assert_eq!(
Ok($dest(dtype)),
SymAttrs {
ty: Some($src),
dtype: Some(dtype),
..Default::default()
}
.try_into()
);
// no dtype
let result = IdentKind::try_from(SymAttrs {
ty: Some($src),
..Default::default()
})
.expect_err("must fail when missing dtype");
assert_eq!(XmloAirError::MissingDtype, result);
}
};
($name:ident, $src:expr => $dest:expr, dim, dtype) => {
#[test]
fn $name() {
let dim = Dim::Vector;
let dtype = Dtype::Float;
assert_eq!(
Ok($dest(Dim::Vector, dtype)),
SymAttrs {
ty: Some($src),
dim: Some(dim),
dtype: Some(dtype),
..Default::default()
}
.try_into()
);
// no dim
let dim_result = IdentKind::try_from(SymAttrs {
ty: Some($src),
dtype: Some(dtype),
..Default::default()
})
.expect_err("must fail when missing dim");
assert_eq!(XmloAirError::MissingDim, dim_result);
// no dtype
let dtype_result = IdentKind::try_from(SymAttrs {
ty: Some($src),
dim: Some(dim),
..Default::default()
})
.expect_err("must fail when missing dtype");
assert_eq!(XmloAirError::MissingDtype, dtype_result);
}
};
}
test_kind!(cgen, SymType::Cgen => IdentKind::Cgen, dim);
test_kind!(class, SymType::Class => IdentKind::Class, dim);
test_kind!(r#const, SymType::Const => IdentKind::Const, dim, dtype);
test_kind!(func, SymType::Func => IdentKind::Func, dim, dtype);
test_kind!(gen, SymType::Gen => IdentKind::Gen, dim, dtype);
test_kind!(lparam, SymType::Lparam => IdentKind::Lparam, dim, dtype);
test_kind!(param, SymType::Param => IdentKind::Param, dim, dtype);
test_kind!(rate, SymType::Rate => IdentKind::Rate, dtype);
test_kind!(tpl, SymType::Tpl => IdentKind::Tpl);
test_kind!(r#type, SymType::Type => IdentKind::Type, dtype);
test_kind!(maphead, SymType::MapHead => IdentKind::MapHead);
test_kind!(map, SymType::Map => IdentKind::Map);
test_kind!(maptail, SymType::MapTail => IdentKind::MapTail);
test_kind!(retmaphead, SymType::RetMapHead => IdentKind::RetMapHead);
test_kind!(retmap, SymType::RetMap => IdentKind::RetMap);
test_kind!(retmaptail, SymType::RetMapTail => IdentKind::RetMapTail);
test_kind!(meta, SymType::Meta => IdentKind::Meta);
test_kind!(worksheet, SymType::Worksheet => IdentKind::Worksheet);
#[test]
fn source_from_sym_attrs() {
let nsym: SymbolId = "name".intern();
let ssym: SymbolId = "src".intern();
let psym: SymbolId = "parent".intern();
let ysym: SymbolId = "yields".intern();
let fsym: SymbolId = "from".intern();
let attrs = SymAttrs {
pkg_name: Some(nsym),
src: Some(ssym),
generated: true,
parent: Some(psym),
yields: Some(ysym),
desc: Some("sym desc".into()),
from: Some(fsym),
virtual_: true,
override_: true,
..Default::default()
};
assert_eq!(
Source {
pkg_name: Some(nsym),
src: Some(ssym),
generated: attrs.generated,
parent: attrs.parent,
yields: attrs.yields,
desc: Some("sym desc".into()),
from: Some(fsym),
virtual_: true,
override_: true,
},
attrs.into(),
);
}