tamer: NIR->xmli: Represent package imports

This doesn't do the actual hard work yet of resolving and loading a package,
but it does place it on the graph and re-derive it into the xmli output.

DEV-13708
main
Mike Gerwitz 2023-04-06 22:49:13 -04:00
parent 82e228009d
commit f4653790da
8 changed files with 126 additions and 65 deletions

View File

@ -165,8 +165,11 @@ impl ParseState for AirAggregate {
(st @ Toplevel(..), AirBind(BindIdent(id))) => {
Transition(st).err(AsgError::InvalidBindContext(id))
}
(st @ Toplevel(..), AirBind(RefIdent(id))) => {
Transition(st).err(AsgError::InvalidRefContext(id))
// Package import
(Toplevel(oi_pkg), AirBind(RefIdent(pathspec))) => {
oi_pkg.import(ctx.asg_mut(), pathspec);
Transition(Toplevel(oi_pkg)).incomplete()
}
// Note: We unfortunately can't match on `AirExpr | AirBind`

View File

@ -820,60 +820,6 @@ fn expr_ref_to_ident() {
assert!(oi_ident_bar.is_bound_to(&asg, oi_expr_bar));
}
#[test]
fn expr_ref_outside_of_expr_context() {
let id_pre = SPair("pre".into(), S2);
let id_foo = SPair("foo".into(), S4);
#[rustfmt::skip]
let toks = vec![
// We need to first bring ourselves out of the context of the
// package header.
Air::ExprStart(ExprOp::Sum, S1),
Air::BindIdent(id_pre),
Air::ExprEnd(S3),
// This will fail since we're not in an expression context.
Air::RefIdent(id_foo),
// RECOVERY: Simply ignore the above.
Air::ExprStart(ExprOp::Sum, S1),
Air::BindIdent(id_foo),
Air::ExprEnd(S3),
];
let mut sut = parse_as_pkg_body(toks);
assert_eq!(
#[rustfmt::skip]
vec![
Ok(Parsed::Incomplete), // PkgStart
Ok(Parsed::Incomplete), // ExprStart
Ok(Parsed::Incomplete), // BindIdent
Ok(Parsed::Incomplete), // ExprEnd
// Now we're past the header and in expression parsing mode.
Err(ParseError::StateError(AsgError::InvalidRefContext(
id_foo
))),
// RECOVERY: Proceed as normal
Ok(Parsed::Incomplete), // ExprStart
Ok(Parsed::Incomplete), // BindIdent
Ok(Parsed::Incomplete), // ExprEnd
Ok(Parsed::Incomplete), // PkgEnd
],
sut.by_ref().collect::<Vec<_>>(),
);
let asg = sut.finalize().unwrap().into_context();
// Verify that the identifier was bound just to have some confidence in
// the recovery.
let expr = asg.expect_ident_obj::<Expr>(id_foo);
assert_eq!(expr.span(), S1.merge(S3).unwrap());
}
#[test]
fn idents_share_defining_pkg() {
let id_foo = SPair("foo".into(), S3);

View File

@ -311,6 +311,35 @@ fn nested_open_pkg() {
);
}
#[test]
fn pkg_import() {
let pathspec = SPair("foo/bar".into(), S2);
#[rustfmt::skip]
let toks = vec![
Air::PkgStart(S1),
Air::RefIdent(pathspec),
Air::PkgEnd(S3),
];
let mut sut = Sut::parse(toks.into_iter());
assert!(sut.all(|x| x.is_ok()));
let asg = sut.finalize().unwrap().into_context();
let import = asg
.root(S1)
.edges_filtered::<Pkg>(&asg)
.next()
.expect("cannot find package from root")
.edges_filtered::<Pkg>(&asg)
.next()
.expect("cannot find imported package")
.resolve(&asg);
assert_eq!(pathspec, import.pathspec());
}
/// Parse using [`Sut`] when the test does not care about the outer package.
pub fn parse_as_pkg_body<I: IntoIterator<Item = Air>>(
toks: I,

View File

@ -20,26 +20,52 @@
//! Package object on the ASG.
use super::{
Ident, Object, ObjectIndex, ObjectRel, ObjectRelFrom, ObjectRelTy,
ObjectRelatable, ObjectTreeRelTo, Tpl,
Ident, Object, ObjectIndex, ObjectIndexRelTo, ObjectRel, ObjectRelFrom,
ObjectRelTy, ObjectRelatable, ObjectTreeRelTo, Tpl,
};
use crate::{
asg::Asg,
f::Functor,
parse::{util::SPair, Token},
span::Span,
sym::st::raw::WS_EMPTY,
};
use crate::{asg::Asg, f::Functor, span::Span};
use std::fmt::Display;
#[cfg(doc)]
use super::ObjectKind;
#[derive(Debug, PartialEq, Eq)]
pub struct Pkg(Span);
pub struct Pkg(Span, PathSpec);
/// Package path specification used to import this package.
///
/// TODO: This is simply punting on handling of imports for now.
type PathSpec = SPair;
impl Pkg {
/// Create a new package intended to serve as the compilation unit,
/// with an empty pathspec.
pub fn new<S: Into<Span>>(span: S) -> Self {
Self(span.into())
let s = span.into();
Self(s, SPair(WS_EMPTY, s))
}
/// Represent a package imported according to the provided
/// [`PathSpec`].
pub fn new_imported(pathspec: PathSpec) -> Self {
Self(pathspec.span(), pathspec)
}
pub fn span(&self) -> Span {
match self {
Self(span) => *span,
Self(span, _) => *span,
}
}
pub fn pathspec(&self) -> PathSpec {
match self {
Self(_, pathspec) => *pathspec,
}
}
}
@ -53,7 +79,7 @@ impl Display for Pkg {
impl Functor<Span> for Pkg {
fn map(self, f: impl FnOnce(Span) -> Span) -> Self::Target {
match self {
Self(span) => Self(f(span)),
Self(span, path) => Self(f(span), path),
}
}
}
@ -64,6 +90,10 @@ object_rel! {
///
/// Imported [`Ident`]s do not have edges from this package.
Pkg -> {
// Package import
cross Pkg,
// Identified objects owned by this package.
tree Ident,
// Anonymous templates are used for expansion.
@ -76,4 +106,14 @@ impl ObjectIndex<Pkg> {
pub fn close(self, asg: &mut Asg, span: Span) -> Self {
self.map_obj(asg, Pkg::fmap(|open| open.merge(span).unwrap_or(open)))
}
/// Indicate that a package should be imported at the provided
/// pathspec.
///
/// This simply adds the import to the graph;
/// package loading must be performed by another subsystem.
pub fn import(self, asg: &mut Asg, pathspec: SPair) -> Self {
let oi_import = asg.create(Pkg::new_imported(pathspec));
self.add_edge_to(asg, oi_import, Some(pathspec.span()))
}
}

View File

@ -176,7 +176,14 @@ impl<'a> TreeContext<'a> {
let paired_rel = dyn_rel.resolve_oi_pairs(self.asg);
match paired_rel.target() {
Object::Pkg((pkg, _)) => self.emit_package(pkg, depth),
Object::Pkg((pkg, oi_pkg)) => match paired_rel.source() {
Object::Root(_) => self.emit_package(pkg, depth),
Object::Pkg(_) => self.emit_import(pkg, depth),
_ => diagnostic_panic!(
vec![oi_pkg.error("package was not expected here")],
"invalid context for package object during xmli derivation",
),
},
// Identifiers will be considered in context;
// pass over it for now.
@ -223,6 +230,18 @@ impl<'a> TreeContext<'a> {
Some(package(pkg, depth))
}
/// Emit a package import statement.
fn emit_import(&mut self, pkg: &Pkg, depth: Depth) -> Option<Xirf> {
let ps = pkg.pathspec();
self.push(Xirf::attr(QN_PACKAGE, ps.symbol(), (ps.span(), ps.span())));
Some(Xirf::open(
QN_IMPORT,
OpenSpan::without_name_span(pkg.span()),
depth,
))
}
/// Emit an expression as a legacy TAME statement or expression.
///
/// Identified expressions must be represented using statements in

View File

@ -260,7 +260,7 @@ ele_parse! {
/// for composing larger systems out of smaller components.
ImportStmt := QN_IMPORT {
@ {
QN_PACKAGE => TodoAttr,
QN_PACKAGE => Ref,
QN_EXPORT => TodoAttr,
} => Todo,
};

View File

@ -0,0 +1,12 @@
<package xmlns="http://www.lovullo.com/rater"
xmlns:c="http://www.lovullo.com/calc"
xmlns:t="http://www.lovullo.com/rater/apply-template">
<import package="first" />
<import package="second" />
<import package="third" />
</package>

View File

@ -0,0 +1,12 @@
<?xml version="1.0"?>
<package xmlns="http://www.lovullo.com/rater"
xmlns:c="http://www.lovullo.com/calc"
xmlns:t="http://www.lovullo.com/rater/apply-template">
Packages aren't actually imported yet,
but they do need to be represented on the graph for `xmli` derivation.
<import package="first" />
<import package="second" />
<import package="third" />
</package>