From 73145626712406ebd3a78c3724a902009486c053 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Tue, 27 Jun 2023 16:20:41 -0400 Subject: [PATCH] tamer: nir::abstract_bind: New lowering operation This builds upon the concepts of the previous commit to translate identifier binding into an abstract binding if it utilizes a symbol that follows a metavariable naming convention. See the provided documentation for more information. This commit _does not_ integrate this into the lowering pipeline yet, since the abstract identifiers are still rejected (as TODOs) by AIR. DEV-13163 --- tamer/src/nir.rs | 2 + tamer/src/nir/abstract_bind.rs | 233 ++++++++++++++++++++++++++++ tamer/src/nir/abstract_bind/test.rs | 111 +++++++++++++ 3 files changed, 346 insertions(+) create mode 100644 tamer/src/nir/abstract_bind.rs create mode 100644 tamer/src/nir/abstract_bind/test.rs diff --git a/tamer/src/nir.rs b/tamer/src/nir.rs index 3d7136d2..74e34fe5 100644 --- a/tamer/src/nir.rs +++ b/tamer/src/nir.rs @@ -49,6 +49,7 @@ //! The entry point for NIR in the lowering pipeline is exported as //! [`XirfToNir`]. +mod abstract_bind; mod air; mod interp; mod parse; @@ -72,6 +73,7 @@ use std::{ fmt::{Debug, Display}, }; +pub use abstract_bind::{AbstractBindTranslate, AbstractBindTranslateError}; pub use air::{NirToAir, NirToAirError, NirToAirParseType}; pub use interp::{InterpError, InterpState as InterpolateNir}; pub use parse::{ diff --git a/tamer/src/nir/abstract_bind.rs b/tamer/src/nir/abstract_bind.rs new file mode 100644 index 00000000..ae7c3821 --- /dev/null +++ b/tamer/src/nir/abstract_bind.rs @@ -0,0 +1,233 @@ +// Abstract binding translation for NIR +// +// Copyright (C) 2014-2023 Ryan Specialty, LLC. +// +// This file is part of TAME. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Translation of non-interpolated abstract bindings for NIR. +//! +//! Metavariables can be used in the context of an identifier binding to +//! produce identifiers dynamically via template expansion. +//! For example: +//! +//! ```xml +//! +//! +//! +//! +//! +//! +//! ``` +//! +//! In the above example, +//! both `@as@` and `@yields@` represent identifier bindings, +//! but the concrete names are not known until the expansion of the +//! respective metavariables. +//! This is equivalently expressed using interpolation: +//! +//! ```xml +//! +//! +//! ``` +//! +//! The above interpolation would cause the generation of an abstract +//! identifier via [`super::interp`]. +//! However, +//! because metavariables historically have an exclusive naming convention +//! that requires a `@` prefix and suffix, +//! the curly braces can be optionally omitted. +//! This works just fine for the `@match@` _reference_ above, +//! because that reference is unambiguously referring to a metavariable of +//! that name. +//! +//! But binding contexts are different, +//! because they assert that the provided lexical symbol should serve as +//! the name of an identifier. +//! We need an additional and explicit level of indirection, +//! otherwise we run into the following ambiguity: +//! +//! ```xml +//! +//! ``` +//! +//! In this case, +//! if we interpret `@as@` in both contexts to be bindings, +//! then there is a redefinition, +//! which is an error. +//! We instead want the equivalent of this: +//! +//! ```xml +//! +//! ``` +//! +//! This creates an awkward ambiguity, +//! because what if we instead want this? +//! +//! ```xml +//! +//! ``` +//! +//! This was not possible in the XSLT-based TAME. +//! TAMER instead adopts this awkward convention, +//! implemented in this module: +//! +//! 1. Template parameters treat all symbols in binding position +//! (`@name`) +//! as concrete identifiers. +//! This behavior can be overridden using curly braces to trigger +//! interpolation. +//! +//! 2. All other bindings treat symbols matching the `@`-prefix-suffix +//! metavariable naming convention as abstract bindings. +//! This is equivalent to the interpolation behavior. +//! +//! This module is therefore an _optional_ syntactic feature of TAME. +//! If desired, +//! this module could be omitted from the lowering pipeline in favor of +//! explicitly utilizing interpolation for all abstract identifiers. +//! When utilized, +//! this lowering operation may be intergrated into the lowering pipeline +//! either before or after interpolation. +//! +//! TODO: Since this module acts upon a naming convention, +//! shouldn't it also enforce it on definition so that we know that +//! metavariables _will_ always follow that convention? +//! At the time of writing, +//! no part of TAMER yet enforces metavariable naming conventions. + +use super::{Nir, NirEntity}; +use crate::{parse::prelude::*, sym::GlobalSymbolResolve}; +use memchr::memchr; + +use Nir::*; +use NirEntity::*; + +#[derive(Debug, PartialEq, Default)] +pub enum AbstractBindTranslate { + #[default] + Ready, + + /// The next bind is in the context of a metavariable definition + /// (e.g. template parameter). + /// + /// This should always be accurate since the grammar of NIR, + /// at least in XML form, + /// both requires binding as part of the attribute list and so before any + /// other opening element and may only contain one such attribute. + /// If this assumption does not hold in the future, + /// then the metavariable identifier will not be defined and will + /// result in a reference instead, + /// leading to errors. + /// Until then, + /// this is a simple and straightforward implementation. + BindingMeta, +} + +impl Display for AbstractBindTranslate { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "scanning for abstract binding via metavariable naming convention" + ) + } +} + +impl ParseState for AbstractBindTranslate { + type Token = Nir; + type Object = Nir; + type Error = AbstractBindTranslateError; + + fn parse_token( + self, + tok: Self::Token, + _: &mut Self::Context, + ) -> TransitionResult { + use AbstractBindTranslate::*; + + match (self, tok) { + (Ready, BindIdent(name)) if needs_translation(name) => { + Transition(Ready).ok(BindIdentAbstract(name)) + } + + (Ready, tok @ Open(TplParam, _)) => Transition(BindingMeta).ok(tok), + + (BindingMeta, tok @ BindIdent(_)) => Transition(Ready).ok(tok), + + (st @ (Ready | BindingMeta), tok) => Transition(st).ok(tok), + } + } + + fn is_accepting(&self, _: &Self::Context) -> bool { + true + } +} + +/// Determine whether the given name requires translation into an abstract +/// identifier. +/// +/// It's important to understand how the naming convention is utilized; +/// this assumes that: +/// +/// 1. Metavariables are `@`-prefixed. +/// The convention is actually to have a suffix too, +/// but since `@` is not permitted at the time of writing for any +/// other types of identifiers, +/// it should be the case that a prefix also implies a suffix, +/// otherwise some other portion of the system will fail. +/// 2. This should not be consulted for metavariable definitions, +/// like template parameters. +fn needs_translation(name: SPair) -> bool { + // Unlike the interpolation module which must check many symbols, + // we assume here that it's not necessary + // (and may be even be determental) + // for a "quick" check version given that this is invoked for + // bindings, + // and bindings will very likely introduce something new. + // It'd be worth verifying this assumption at some point in the future, + // but is unlikely to make a significant different either way. + #[rustfmt::skip] + matches!( + memchr(b'@', name.symbol().lookup_str().as_bytes()), + Some(0), + ) +} + +diagnostic_infallible! { + pub enum AbstractBindTranslateError {} +} + +#[cfg(test)] +mod test; diff --git a/tamer/src/nir/abstract_bind/test.rs b/tamer/src/nir/abstract_bind/test.rs new file mode 100644 index 00000000..0699d50f --- /dev/null +++ b/tamer/src/nir/abstract_bind/test.rs @@ -0,0 +1,111 @@ +// Test abstract binding translation for NIR +// +// Copyright (C) 2014-2023 Ryan Specialty, LLC. +// +// This file is part of TAME. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::*; +use crate::span::dummy::*; + +type Sut = AbstractBindTranslate; +use Parsed::Object as O; + +#[test] +fn already_abstract_bind_ignored_despite_metavar_naming() { + // This is named as a metavariable. + let name = "@foo@".into(); + + assert_tok_translate( + // Identifier is already abstract... + BindIdentAbstract(SPair(name, S1)), + // ...so this acts as an identity operation. + BindIdentAbstract(SPair(name, S1)), + ); +} + +#[test] +fn concrete_bind_without_metavar_naming_ignored_non_meta() { + // This is _not_ named as a metavariable. + let name = "concrete".into(); + + assert_tok_translate( + // Identifier is concrete and the name does not follow a + // metavariable naming convention... + BindIdent(SPair(name, S1)), + // ...and so this acts as an identity operation. + BindIdent(SPair(name, S1)), + ); +} + +#[test] +fn non_meta_concrete_bind_with_metavar_naming_translated_to_abstract_bind() { + // This is named as a metavariable. + let name = "@foo@".into(); + + assert_tok_translate( + // Identifier is concrete... + BindIdent(SPair(name, S1)), + // ...and so gets translated into an abstract binding. + // Its data are otherwise the same. + BindIdentAbstract(SPair(name, S1)), + ); +} + +// Metavariable definitions must be left concrete since they produce the +// identifiers that are utilized by other abstract identifiers. +#[test] +fn meta_concrete_bind_with_metavar_naming_ignored() { + // This is named as a metavariable. + let name = "@param@".into(); + let name_outside = "@foo@".into(); + + #[rustfmt::skip] + let toks = [ + Open(TplParam, S1), + // This identifier utilizes a metavariable naming convention, + // but we're in a template parameter context, + // which defines a metavariable. + BindIdent(SPair(name, S2)), + Close(TplParam, S3), + + // Back outside of a param context. + BindIdent(SPair(name_outside, S4)), + ]; + + #[rustfmt::skip] + assert_eq!( + Ok(vec![ + O(Open(TplParam, S1)), + // Since we are in a metavariable definition context, + // the bind _stays concrete_. + O(BindIdent(SPair(name, S2))), + O(Close(TplParam, S3)), + + // This one is now outside the metavariable context, + // and so we should go back to translating to abstract. + O(BindIdentAbstract(SPair(name_outside, S4))), + ]), + Sut::parse(toks.into_iter()).collect() + ); +} + +fn assert_tok_translate(tok: Nir, expect: Nir) { + #[rustfmt::skip] + assert_eq!( + Ok(vec![O(expect)]), + Sut::parse([tok].into_iter()).collect() + ); +}