tamer: asg::air::tpl: Parse template body expressions

And finally we have tokens aggregated onto the ASG in the context of a
template.  I expected to arrive here much more quickly, but there was a lot
of necessary refactoring.  There's a lot more that could be done, but I need
to continue; I had wanted this done a week ago.

It is worth noting, though, that this finally achieves something I had been
wondering about since the inception of this project---how I'd represent
templates on the graph.  I think this worked out rather nicely.  It wasn't
even until a few months ago that I decided to use AIR instead of NIR for
that purpose (NIR wouldn't have worked).

And note how I didn't have to touch the program derivation at all---the
system test just works with the AIR change, because of the consistent
construction of the graph.  Beautiful.

DEV-13708
main
Mike Gerwitz 2023-03-08 14:47:31 -05:00
parent 431df6cecb
commit 5c60c5fd15
6 changed files with 238 additions and 28 deletions

View File

@ -38,10 +38,7 @@
use self::expr::AirExprAggregateReachable;
use super::{graph::object::Pkg, Asg, AsgError, ObjectIndex};
use crate::{
parse::prelude::*,
sym::SymbolId,
};
use crate::{parse::prelude::*, sym::SymbolId};
use std::fmt::{Debug, Display};
#[macro_use]

View File

@ -59,8 +59,15 @@ pub enum AirTplAggregate {
/// which simplifies AIR generation.
Ready(ObjectIndex<Pkg>),
Toplevel(
ObjectIndex<Pkg>,
ObjectIndex<Tpl>,
AirExprAggregateStoreDangling<Tpl>,
Option<SPair>,
),
/// Aggregating tokens into a template.
BuildingTpl(
TplExpr(
ObjectIndex<Pkg>,
ObjectIndex<Tpl>,
AirExprAggregateStoreDangling<Tpl>,
@ -72,10 +79,14 @@ impl Display for AirTplAggregate {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Ready(_) => write!(f, "ready for template definition"),
Self::BuildingTpl(_, expr, _, None) => {
Self::Toplevel(_, expr, _, None)
| Self::TplExpr(_, expr, _, None) => {
write!(f, "building anonymous template with {expr}")
}
Self::BuildingTpl(_, expr, _, Some(name)) => {
Self::Toplevel(_, expr, _, Some(name))
| Self::TplExpr(_, expr, _, Some(name)) => {
write!(
f,
"building named template {} with {expr}",
@ -106,7 +117,7 @@ impl ParseState for AirTplAggregate {
(Ready(oi_pkg), AirTpl(TplOpen(span))) => {
let oi_tpl = asg.create(Tpl::new(span));
Transition(BuildingTpl(
Transition(Toplevel(
oi_pkg,
oi_tpl,
AirExprAggregate::new_in(oi_tpl),
@ -115,29 +126,62 @@ impl ParseState for AirTplAggregate {
.incomplete()
}
(
BuildingTpl(oi_pkg, oi_tpl, expr, _),
AirBind(BindIdent(name)),
) => asg
.lookup_or_missing(name)
.bind_definition(asg, name, oi_tpl)
.map(|oi_ident| oi_pkg.defines(asg, oi_ident))
.map(|_| ())
.transition(BuildingTpl(oi_pkg, oi_tpl, expr, Some(name))),
(Toplevel(..), AirTpl(TplOpen(_span))) => todo!("nested tpl open"),
(Toplevel(oi_pkg, oi_tpl, expr, _), AirBind(BindIdent(name))) => {
asg.lookup_or_missing(name)
.bind_definition(asg, name, oi_tpl)
.map(|oi_ident| oi_pkg.defines(asg, oi_ident))
.map(|_| ())
.transition(Toplevel(oi_pkg, oi_tpl, expr, Some(name)))
}
(Toplevel(..), AirBind(RefIdent(_))) => {
todo!("tpl Toplevel RefIdent")
}
(
BuildingTpl(oi_pkg, oi_tpl, _expr_done, _),
Toplevel(oi_pkg, oi_tpl, _expr_done, _),
AirTpl(TplClose(span)),
) => {
oi_tpl.close(asg, span);
Transition(Ready(oi_pkg)).incomplete()
}
(BuildingTpl(..), AirPkg(_)) => {
(TplExpr(oi_pkg, oi_tpl, expr, name), AirTpl(TplClose(span))) => {
// TODO: duplicated with AirAggregate
match expr.is_accepting(asg) {
true => {
// TODO: this is duplicated with the above
oi_tpl.close(asg, span);
Transition(Ready(oi_pkg)).incomplete()
}
false => Transition(TplExpr(oi_pkg, oi_tpl, expr, name))
.err(AsgError::InvalidTplCloseContext(span)),
}
}
(Toplevel(..) | TplExpr(..), AirPkg(_)) => {
todo!("template cannot define packages")
}
(BuildingTpl(..), tok) => todo!("BuildingTpl body: {tok:?}"),
(Toplevel(..) | TplExpr(..), AirIdent(_)) => {
todo!("linker token cannot be used in templates")
}
(
Toplevel(oi_pkg, oi_tpl, expr, name)
| TplExpr(oi_pkg, oi_tpl, expr, name),
AirExpr(etok),
) => Self::delegate_expr(asg, oi_pkg, oi_tpl, expr, name, etok),
(TplExpr(oi_pkg, oi_tpl, expr, name), AirBind(etok)) => {
Self::delegate_expr(asg, oi_pkg, oi_tpl, expr, name, etok)
}
(TplExpr(..), AirTpl(TplOpen(_))) => {
todo!("nested template (template-generated template)")
}
(st @ Ready(..), AirTpl(TplClose(span))) => {
Transition(st).err(AsgError::UnbalancedTpl(span))
@ -159,6 +203,32 @@ impl AirTplAggregate {
pub(super) fn new_in_pkg(oi_pkg: ObjectIndex<Pkg>) -> Self {
Self::Ready(oi_pkg)
}
/// Delegate to the expression parser [`AirExprAggregate`].
// TODO: Sir, this argument count is out of control.
fn delegate_expr(
asg: &mut <Self as ParseState>::Context,
oi_pkg: ObjectIndex<Pkg>,
oi_tpl: ObjectIndex<Tpl>,
expr: AirExprAggregateStoreDangling<Tpl>,
name: Option<SPair>,
etok: impl Into<<AirExprAggregateStoreDangling<Tpl> as ParseState>::Token>,
) -> TransitionResult<Self> {
let tok = etok.into();
expr.parse_token(tok, asg).branch_dead::<Self, _>(
|expr, ()| {
Transition(Self::Toplevel(oi_pkg, oi_tpl, expr, name))
.incomplete()
},
|expr, result, ()| {
result
.map(ParseStatus::reflexivity)
.transition(Self::TplExpr(oi_pkg, oi_tpl, expr, name))
},
(),
)
}
}
#[cfg(test)]

View File

@ -19,8 +19,12 @@
use super::*;
use crate::asg::{
air::{expr::test::collect_subexprs, Air, AirAggregate},
Expr, ExprOp,
air::{
expr::test::collect_subexprs,
test::{asg_from_toks, parse_as_pkg_body},
Air, AirAggregate,
},
Expr, ExprOp, Ident,
};
use crate::span::dummy::*;
@ -151,3 +155,111 @@ fn tpl_within_expr() {
.collect::<Vec<_>>(),
);
}
#[test]
fn close_tpl_without_open() {
let toks = vec![
Air::TplClose(S1),
// RECOVERY: Try again.
Air::TplOpen(S2),
Air::TplClose(S3),
];
assert_eq!(
vec![
Ok(Parsed::Incomplete), // PkgOpen
Err(ParseError::StateError(AsgError::UnbalancedTpl(S1))),
// RECOVERY
Ok(Parsed::Incomplete), // TplOpen
Ok(Parsed::Incomplete), // TplClose
Ok(Parsed::Incomplete), // PkgClose
],
parse_as_pkg_body(toks).collect::<Vec<_>>(),
);
}
#[test]
fn tpl_with_reachable_expression() {
let id_tpl = SPair("_tpl_".into(), S2);
let id_expr_a = SPair("expra".into(), S4);
let id_expr_b = SPair("exprb".into(), S7);
#[rustfmt::skip]
let toks = vec![
Air::TplOpen(S1),
Air::BindIdent(id_tpl),
Air::ExprOpen(ExprOp::Sum, S3),
Air::BindIdent(id_expr_a),
Air::ExprClose(S5),
Air::ExprOpen(ExprOp::Sum, S6),
Air::BindIdent(id_expr_b),
Air::ExprClose(S8),
Air::TplClose(S9),
];
let asg = asg_from_toks(toks);
let oi_tpl = asg.expect_ident_oi::<Tpl>(id_tpl);
let tpl = oi_tpl.resolve(&asg);
assert_eq!(S1.merge(S9).unwrap(), tpl.span());
// The inner expressions are reachable,
// but the intent is to expand them into the template's eventual
// application site.
// Given that,
// they should be defined by the template...
assert_eq!(
vec![
asg.lookup(id_expr_b).unwrap(),
asg.lookup(id_expr_a).unwrap(),
],
oi_tpl.edges_filtered::<Ident>(&asg).collect::<Vec<_>>()
);
// ...but not by the package containing the template.
let oi_pkg = asg.lookup(id_tpl).unwrap().src_pkg(&asg).unwrap();
assert_eq!(
vec![
// The only identifier on the package should be the template itself.
asg.lookup(id_tpl).unwrap(),
],
oi_pkg.edges_filtered::<Ident>(&asg).collect::<Vec<_>>()
);
}
#[test]
fn close_tpl_mid_open() {
let id_expr = SPair("expr".into(), S3);
#[rustfmt::skip]
let toks = vec![
Air::TplOpen(S1),
Air::ExprOpen(ExprOp::Sum, S2),
Air::BindIdent(id_expr),
// This is misplaced.
Air::TplClose(S4),
// RECOVERY: Close the expression and try again.
Air::ExprClose(S5),
Air::TplClose(S6),
];
assert_eq!(
#[rustfmt::skip]
vec![
Ok(Parsed::Incomplete), // PkgOpen
Ok(Parsed::Incomplete), // TplOpen
Ok(Parsed::Incomplete), // ExprOpen
Ok(Parsed::Incomplete), // BindIdent
Err(ParseError::StateError(
AsgError::InvalidTplCloseContext(S4))
),
// RECOVERY
Ok(Parsed::Incomplete), // ExprClose
Ok(Parsed::Incomplete), // TplClose
Ok(Parsed::Incomplete), // PkgClose
],
parse_as_pkg_body(toks).collect::<Vec<_>>(),
);
}

View File

@ -69,12 +69,7 @@ pub enum AsgError {
/// being defined.
NestedPkgOpen(Span, Span),
/// Attempted to close a package when not in a package context.
///
/// This could mean that a package close was attempted while parsing
/// some other object
/// (e.g. an expression),
/// or that there is no open package to close.
/// Attempted to close a package when not in a package toplevel context.
InvalidPkgCloseContext(Span),
/// Attempted to open an expression in an invalid context.
@ -113,6 +108,10 @@ pub enum AsgError {
///
/// Ideally this situation is syntactically invalid in a source IR.
InvalidExprRefContext(SPair),
/// Attempted to close a template when not in a template toplevel
/// context.
InvalidTplCloseContext(Span),
}
impl Display for AsgError {
@ -145,6 +144,9 @@ impl Display for AsgError {
TtQuote::wrap(ident)
)
}
InvalidTplCloseContext(_) => {
write!(f, "invalid context for template close",)
}
}
}
}
@ -241,6 +243,14 @@ impl Diagnostic for AsgError {
"cannot reference the value of an expression from outside \
of an expression context",
)],
InvalidTplCloseContext(span) => vec![
span.error("template close was not expected here"),
span.help(
"a template must be closed at the same level of nesting \
that it was opened",
),
],
}
}
}

View File

@ -35,4 +35,13 @@
<template name="_empty_" />
<rate yields="postTpl" />
<template name="_with-static-reachable_">
<rate yields="tplStaticA">
<c:sum />
</rate>
<classify as="tpl-static-b">
<any />
</classify>
</template>
</package>

View File

@ -39,4 +39,16 @@
<template name="_empty_" />
<rate yields="postTpl" />
<template name="_with-static-reachable_">
All expressions here are reachable
(having been derived from named statements).
<rate yields="tplStaticA">
<c:sum />
</rate>
<classify as="tpl-static-b">
<any />
</classify>
</template>
</package>