tamer: asg::air: Template application within expressions

This recognizes template application within expressions.  Since expressions
can occur within templates, this can occur arbitrarily deeply.

And with that, we have the core of the template system represented on the
graph.  Of course, there are some glaring scoping issues to be resolved, but
those aren't unique to template application.

DEV-13708
main
Mike Gerwitz 2023-04-05 14:21:56 -04:00
parent daa8c6967b
commit c0e5b1d750
8 changed files with 127 additions and 13 deletions

View File

@ -40,7 +40,6 @@ use super::{
Asg, AsgError, Expr, Ident, ObjectIndex,
};
use crate::{
diagnostic_todo,
parse::{prelude::*, StateStack},
span::{Span, UNKNOWN_SPAN},
sym::SymbolId,
@ -472,8 +471,8 @@ impl AirAggregateCtx {
stack.iter().rev().find_map(|st| match st {
AirAggregate::Empty => None,
AirAggregate::Toplevel(pkg_oi) => Some((*pkg_oi).into()),
AirAggregate::PkgExpr(_) => {
diagnostic_todo!(vec![], "PkgExpr expansion_oi")
AirAggregate::PkgExpr(exprst) => {
exprst.active_expr_oi().map(Into::into)
}
AirAggregate::PkgTpl(tplst) => {
tplst.active_tpl_oi().map(Into::into)

View File

@ -177,6 +177,19 @@ impl AirExprAggregate {
.add_edge_to(asg, oi_expr, None);
Ok(())
}
/// The [`ObjectIndex`] of the active expression being built,
/// if any.
///
/// This will return the deepest active subexpression.
pub(super) fn active_expr_oi(&self) -> Option<ObjectIndex<Expr>> {
use AirExprAggregate::*;
match self {
Ready(_) => None,
BuildingExpr(_, oi) => Some(*oi),
}
}
}
/// Stack of held expressions,

View File

@ -24,7 +24,7 @@ use crate::asg::{
test::{asg_from_toks, parse_as_pkg_body},
Air, AirAggregate,
},
graph::object::Meta,
graph::object::{Meta, ObjectRel},
Expr, ExprOp, Ident,
};
use crate::span::dummy::*;
@ -158,6 +158,56 @@ fn tpl_within_expr() {
);
}
// Like the above test,
// but now we're _applying_ a template.
#[test]
fn tpl_apply_within_expr() {
let id_expr = SPair("expr".into(), S3);
let id_tpl = SPair("_tpl_".into(), S5);
let ref_tpl = SPair("_tpl_".into(), S8);
#[rustfmt::skip]
let toks = vec![
Air::ExprStart(ExprOp::Sum, S2),
Air::BindIdent(id_expr),
// This will not be present in the final expression,
// as if it were hoisted out.
Air::TplStart(S4),
Air::BindIdent(id_tpl),
Air::TplEnd(S6),
// But the application will remain.
Air::TplStart(S7),
Air::RefIdent(ref_tpl),
Air::TplEndRef(S9),
Air::ExprEnd(S10),
];
let asg = asg_from_toks(toks);
// The inner template.
let tpl = asg.expect_ident_obj::<Tpl>(id_tpl);
assert_eq!(S4.merge(S6).unwrap(), tpl.span());
// The expression that was produced on the graph ought to be equivalent
// to the expression without the template being present at all,
// but retaining the _application_.
let oi_expr = asg.expect_ident_oi::<Expr>(id_expr);
let expr = oi_expr.resolve(&asg);
assert_eq!(S2.merge(S10).unwrap(), expr.span());
assert_eq!(
#[rustfmt::skip]
vec![
S7.merge(S9).unwrap(),
],
oi_expr
.edges(&asg)
.map(|rel| rel.widen().resolve(&asg).span())
.collect::<Vec<_>>()
);
}
#[test]
fn close_tpl_without_open() {
let id_tpl = SPair("_tpl_".into(), S3);

View File

@ -23,7 +23,7 @@ use std::fmt::Display;
use super::{
Asg, Ident, Object, ObjectIndex, ObjectIndexRelTo, ObjectRel,
ObjectRelFrom, ObjectRelTy, ObjectRelatable, ObjectTreeRelTo,
ObjectRelFrom, ObjectRelTy, ObjectRelatable, ObjectTreeRelTo, Tpl,
};
use crate::{
f::Functor,
@ -221,6 +221,9 @@ object_rel! {
Expr -> {
cross Ident,
tree Expr,
// Template application
tree Tpl,
}
}

View File

@ -132,6 +132,16 @@ macro_rules! object_rel {
object_rel!{ @impl_rel_to $from $ety $kind }
)*
impl From<[<$from Rel>]> for ObjectIndex<Object> {
fn from(value: [<$from Rel>]) -> Self {
match value {
$(
[<$from Rel>]::$kind(oi) => oi.widen(),
)*
}
}
}
}};
// Static edge types.
@ -474,7 +484,9 @@ impl<O: ObjectKind + ObjectRelatable> ObjectIndex<O> {
/// adhere to the prescribed ontology,
/// provided that invariants are properly upheld by the
/// [`asg`](crate::asg) module.
pub trait ObjectRel<OA: ObjectKind + ObjectRelatable>: Sized {
pub trait ObjectRel<OA: ObjectKind + ObjectRelatable>:
Sized + Into<ObjectIndex<Object>>
{
/// Attempt to narrow into the [`ObjectKind`] `OB`.
///
/// Unlike [`Object`] nodes,
@ -510,6 +522,12 @@ pub trait ObjectRel<OA: ObjectKind + ObjectRelatable>: Sized {
self.narrow::<OB>().map(Into::into)
}
/// Widen into an [`ObjectIndex`],
/// discarding static type information.
fn widen(self) -> ObjectIndex<Object> {
self.into()
}
/// Whether this relationship represents an ontological cross edge.
///
/// A _cross edge_ is an edge between two trees as described by the

View File

@ -309,7 +309,7 @@ impl<'a> TreeContext<'a> {
// Convert this into a long-hand template expansion so that we
// do not have to deal with converting underscore-padded
// template names back into short-hand form.
Object::Pkg(..) | Object::Tpl(..) => {
Object::Pkg(..) | Object::Tpl(..) | Object::Expr(..) => {
// [`Ident`]s are skipped during traversal,
// so we'll handle it ourselves here.
// This also gives us the opportunity to make sure that
@ -341,7 +341,12 @@ impl<'a> TreeContext<'a> {
))
}
_ => todo!("emit_template: {src:?}"),
_ => diagnostic_todo!(
vec![
oi_tpl.note("interpreting this as a template application"),
],
"emit_template: {src:?}"
),
}
}

View File

@ -152,5 +152,23 @@
<template name="_short-hand-unary-with-values-inner_" />
<apply-template name="_short-hand-unary-with-values-inner_" />
</template>
<template name="_short-hand-in-expr_" />
<rate yields="shortHandTplInExpr">
<apply-template name="_short-hand-in-expr_">
<with-param name="@in@" value="rate" />
</apply-template>
</rate>
<template name="_tpl-with-short-hand-inner_">
<template name="_tpl-with-short-hand-inner-inner_" />
<apply-template name="_tpl-with-short-hand-inner-inner_" />
<c:sum>
<apply-template name="_tpl-with-short-hand-inner-inner_">
<with-param name="@in@" value="sum" />
</apply-template>
</c:sum>
</template>
</package>

View File

@ -148,19 +148,27 @@
<template name="_short-hand-unary-with-values-inner_" />
<t:short-hand-unary-with-values-inner />
</t:short-hand-unary-with-values>
<!-- TODO
<template name="_short-hand-in-expr_" />
<rate yields="shortHandTplInExpr">
<t:short-hand in="rate" />
<t:short-hand-in-expr in="rate" />
</rate>
<template name="_tpl-with-short-hand-inner_">
<t:short-hand />
<template name="_tpl-with-short-hand-inner-inner_" />
<t:tpl-with-short-hand-inner-inner />
<c:sum>
<t:short-hand in="sum" />
<t:tpl-with-short-hand-inner-inner in="sum" />
</c:sum>
</template>
-->
</package>