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-13708main
parent
431df6cecb
commit
5c60c5fd15
|
@ -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]
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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<_>>(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue