From 6b54eafd707ab4f498b84dd2fae7988c679ed147 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Tue, 20 Jun 2023 15:14:38 -0400 Subject: [PATCH] tamer: asg::air: Hoist metavars in expressions This is intended to support NIR's lexical interpolation, which expands in place into metavariables. This commit does not yet contain the NIR portion (or xmli system test) because Meta needs to be able to handle concatenation first; that's next. DEV-13163 --- tamer/src/asg/air.rs | 4 +- tamer/src/asg/air/tpl/test.rs | 83 +++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/tamer/src/asg/air.rs b/tamer/src/asg/air.rs index 415904ec..9fe576e0 100644 --- a/tamer/src/asg/air.rs +++ b/tamer/src/asg/air.rs @@ -224,7 +224,7 @@ impl ParseState for AirAggregate { (PkgTpl(tplst), AirDoc(ttok)) => ctx.proxy(tplst, ttok), // Metasyntactic variables (metavariables) - (st @ PkgTpl(_), tok @ AirMeta(..)) => { + (st @ (PkgTpl(_) | PkgExpr(_)), tok @ AirMeta(..)) => { ctx.ret_or_transfer(st, tok, AirMetaAggregate::new()) } (PkgMeta(meta), AirMeta(mtok)) => ctx.proxy(meta, mtok), @@ -273,7 +273,7 @@ impl ParseState for AirAggregate { // TODO: We will need to be more intelligent about this, // since desugaring will produce metavariables in nested contexts, // e.g. within an expression within a template. - (st @ (Pkg(..) | PkgExpr(..) | PkgOpaque(..)), AirMeta(tok)) => { + (st @ (Pkg(..) | PkgOpaque(..)), AirMeta(tok)) => { Transition(st).err(AsgError::UnexpectedMeta(tok.span())) } diff --git a/tamer/src/asg/air/tpl/test.rs b/tamer/src/asg/air/tpl/test.rs index 5f457937..af08e9c9 100644 --- a/tamer/src/asg/air/tpl/test.rs +++ b/tamer/src/asg/air/tpl/test.rs @@ -702,3 +702,86 @@ fn tpl_doc_short_desc() { oi_docs.collect::>(), ); } + +// While NIR does not accept metavariables (params) within expressions that +// are the body of a template, +// metavariable interpolation does create them. +// Hoisting them out of the expression and into the template context is a +// fairly simple thing to do for AIR, +// but would be vastly more complicated for NIR, +// especially for a streaming parser, +// as it'd have to hold tokens until it was sure that no +// interpolation would occur. +// We therefore adopt the simple rule that metavariables are hoisted into +// the context of the parent template. +// This also gives much greater flexibility to any other (AIR) code +// generators. +#[test] +fn metavars_within_exprs_hoisted_to_parent_tpl() { + let id_tpl_outer = SPair("_tpl-outer_".into(), S2); + let id_tpl_inner = SPair("_tpl-inner_".into(), S9); + + let id_param_outer = SPair("@param_outer@".into(), S5); + let id_param_inner = SPair("@param_inner@".into(), S12); + + #[rustfmt::skip] + let toks = vec![ + Air::TplStart(S1), + Air::BindIdent(id_tpl_outer), + + // This expression begins the body of the template. + // NIR would not allow params past this point. + Air::ExprStart(ExprOp::Sum, S3), + // Expresions are not containers and so this metavariable should + // be hoisted to the parent container context. + // That container must be a valid meta context. + Air::MetaStart(S4), + Air::BindIdent(id_param_outer), + Air::MetaEnd(S6), + Air::ExprEnd(S7), + + // Nested template + Air::TplStart(S8), + Air::BindIdent(id_tpl_inner), + + Air::ExprStart(ExprOp::Sum, S10), + // Hoisting should be relative to the innermost template. + Air::MetaStart(S11), + Air::BindIdent(id_param_inner), + Air::MetaEnd(S13), + Air::ExprEnd(S14), + Air::TplEnd(S15), + Air::TplEnd(S16), + ]; + + let ctx = air_ctx_from_pkg_body_toks(toks); + let asg = ctx.asg_ref(); + + let oi_outer = pkg_expect_ident_oi::(&ctx, id_tpl_outer); + + let span_outer = ctx + .env_scope_lookup::(oi_outer, id_param_outer) + .expect("missing id_param_outer Ident") + .definition::(asg) + .expect("missing id_param_outer definition") + .resolve(asg) + .span(); + + assert_eq!(S4.merge(S6).unwrap(), span_outer); + + let oi_inner = ctx + .env_scope_lookup::(oi_outer, id_tpl_inner) + .expect("could not locate inner Tpl's Ident") + .definition::(asg) + .expect("missing inner Tpl"); + + let span_inner = ctx + .env_scope_lookup::(oi_inner, id_param_inner) + .expect("missing id_param_inner Ident") + .definition::(asg) + .expect("missing id_param_inner definition") + .resolve(asg) + .span(); + + assert_eq!(S11.merge(S13).unwrap(), span_inner); +}