tame/tamer/src/asg/air/ir.rs

644 lines
23 KiB
Rust
Raw Normal View History

tamer: asg::air: AIR as a sum IR This introduces a new macro `sum_ir!` to help with a long-standing problem of not being able to easily narrow types in Rust without a whole lot of boilerplate. This patch includes a bit of documentation, so see that for more information. This was not a welcome change---I jumped down this rabbit hole trying to decompose `AirAggregate` so that I can share portions of parsing with the current parser and a template parser. I can now proceed with that. This is not the only implementation that I had tried. I previously inverted the approach, as I've been doing manually for some time: manually create types to hold the sets of variants, and then create a sum type to hold those types. That works, but it resulted in a mess for systems that have to use the IR, since now you have two enums to contend with. I didn't find that to be appropriate, because we shouldn't complicate the external API for implementation details. The enum for IRs is supposed to be like a bytecode---a list of operations that can be performed with the IR. They can be grouped if it makes sense for a public API, but in my case, I only wanted subsets for the sake of delegating responsibilities to smaller subsystems, while retaining the context that `match` provides via its exhaustiveness checking but does not expose as something concrete (which is deeply frustrating!). Anyway, here we are; this'll be refined over time, hopefully, and portions of it can be generalized for removing boilerplate from other IRs. Another thing to note is that this syntax is really a compromise---I had to move on, and I was spending too much time trying to get creative with `macro_rules!`. It isn't the best, and it doesn't seem very Rust-like in some places and is therefore not necessarily all that intuitive. This can be refined further in the future. But the end result, all things considered, isn't too bad. DEV-13708
2023-03-02 15:15:28 -05:00
// ASG IR tokens
//
// 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 <http://www.gnu.org/licenses/>.
//! AIR token definitions.
use super::super::{ExprOp, FragmentText, IdentKind, Source};
use crate::{
fmt::{DisplayWrapper, TtQuote},
parse::util::SPair,
span::Span,
};
#[cfg(doc)]
use super::{
super::graph::object::{Expr, Ident, Pkg, Tpl},
ExprStack,
tamer: asg::air: AIR as a sum IR This introduces a new macro `sum_ir!` to help with a long-standing problem of not being able to easily narrow types in Rust without a whole lot of boilerplate. This patch includes a bit of documentation, so see that for more information. This was not a welcome change---I jumped down this rabbit hole trying to decompose `AirAggregate` so that I can share portions of parsing with the current parser and a template parser. I can now proceed with that. This is not the only implementation that I had tried. I previously inverted the approach, as I've been doing manually for some time: manually create types to hold the sets of variants, and then create a sum type to hold those types. That works, but it resulted in a mess for systems that have to use the IR, since now you have two enums to contend with. I didn't find that to be appropriate, because we shouldn't complicate the external API for implementation details. The enum for IRs is supposed to be like a bytecode---a list of operations that can be performed with the IR. They can be grouped if it makes sense for a public API, but in my case, I only wanted subsets for the sake of delegating responsibilities to smaller subsystems, while retaining the context that `match` provides via its exhaustiveness checking but does not expose as something concrete (which is deeply frustrating!). Anyway, here we are; this'll be refined over time, hopefully, and portions of it can be generalized for removing boilerplate from other IRs. Another thing to note is that this syntax is really a compromise---I had to move on, and I was spending too much time trying to get creative with `macro_rules!`. It isn't the best, and it doesn't seem very Rust-like in some places and is therefore not necessarily all that intuitive. This can be refined further in the future. But the end result, all things considered, isn't too bad. DEV-13708
2023-03-02 15:15:28 -05:00
};
/// Create an IR able to be decomposed into types containing subsets of
/// tokens.
///
/// The purpose of this macro is to generate an IR that:
///
/// - Produces a familiar IR type that contains all tokens as variants;
/// and
/// - Is able to translate into narrower types containing subsets of those
/// variants.
///
/// This addresses the lack of type refinement---we
/// cannot create ad-hoc types that are a subset of another type,
/// e.g. an enum type that contains a subset of the variants of another
/// enum.
/// This results in awkward and verbose workarounds when we want the
/// context `match`'s exhaustiveness checking and pattern matching
/// to be delegated to a function application in that match arm;
/// we have to chose between creating separate types to hold that
/// information,
/// or use a single match.
/// The latter is unsuitable for systems created through the composition of
/// smaller subsystems,
/// and so this macro helps to alleviate the pain of the former.
///
/// This macro also removes the boilerplate of implementing
/// [`Display`](std::fmt::Display), [`Token`](crate::parse::Token), and
/// [`Object`](crate::parse::Object) by rolling the information necessary
/// for their definition into the definition of the tokens.
///
/// One of the best ways to understand all of the types involved is to look
/// at the generated documentation for an IR generated using this macro,
/// which walks the reader through the type translations.
///
/// This may be able to be generalized and useful to other IRs in the
/// future.
///
/// Arbitrary Sum Types
/// ===================
/// The above handles decomposing variants into subtypes,
/// but that mapping must be bijective.
/// But sometimes a token may need to be shared among multiple subsets for
/// use as input to parsers.
///
/// This macro also support defining explicit sum types using the `sum enum`
/// keywords,
/// at the tail of the IR definition,
/// to avoid the boilerplate of writing such a type yourself.
/// This is important for encouraging the creation of types that are
/// precisely define the input language of composable parsers.
tamer: asg::air: AIR as a sum IR This introduces a new macro `sum_ir!` to help with a long-standing problem of not being able to easily narrow types in Rust without a whole lot of boilerplate. This patch includes a bit of documentation, so see that for more information. This was not a welcome change---I jumped down this rabbit hole trying to decompose `AirAggregate` so that I can share portions of parsing with the current parser and a template parser. I can now proceed with that. This is not the only implementation that I had tried. I previously inverted the approach, as I've been doing manually for some time: manually create types to hold the sets of variants, and then create a sum type to hold those types. That works, but it resulted in a mess for systems that have to use the IR, since now you have two enums to contend with. I didn't find that to be appropriate, because we shouldn't complicate the external API for implementation details. The enum for IRs is supposed to be like a bytecode---a list of operations that can be performed with the IR. They can be grouped if it makes sense for a public API, but in my case, I only wanted subsets for the sake of delegating responsibilities to smaller subsystems, while retaining the context that `match` provides via its exhaustiveness checking but does not expose as something concrete (which is deeply frustrating!). Anyway, here we are; this'll be refined over time, hopefully, and portions of it can be generalized for removing boilerplate from other IRs. Another thing to note is that this syntax is really a compromise---I had to move on, and I was spending too much time trying to get creative with `macro_rules!`. It isn't the best, and it doesn't seem very Rust-like in some places and is therefore not necessarily all that intuitive. This can be refined further in the future. But the end result, all things considered, isn't too bad. DEV-13708
2023-03-02 15:15:28 -05:00
macro_rules! sum_ir {
(
$(#[$attr:meta])*
$vis:vis enum $ir:ident -> $svis:vis $sumty:ident $irname:literal {
$(
$(#[$iattr:meta])*
enum $subty:ident {
$(
$(#[$svattr:meta])*
$svar:ident ($($svident:ident : $svty:ty),*) => {
span: $svspan:expr,
display: |$svfmt:ident| $svfmtbody:expr,
},
)+
}
)+
}
$(
$(#[$sumattr:meta])*
$sumvis:vis sum enum $sumsub:ident = $($sumsubty:ident)|+;
)*
tamer: asg::air: AIR as a sum IR This introduces a new macro `sum_ir!` to help with a long-standing problem of not being able to easily narrow types in Rust without a whole lot of boilerplate. This patch includes a bit of documentation, so see that for more information. This was not a welcome change---I jumped down this rabbit hole trying to decompose `AirAggregate` so that I can share portions of parsing with the current parser and a template parser. I can now proceed with that. This is not the only implementation that I had tried. I previously inverted the approach, as I've been doing manually for some time: manually create types to hold the sets of variants, and then create a sum type to hold those types. That works, but it resulted in a mess for systems that have to use the IR, since now you have two enums to contend with. I didn't find that to be appropriate, because we shouldn't complicate the external API for implementation details. The enum for IRs is supposed to be like a bytecode---a list of operations that can be performed with the IR. They can be grouped if it makes sense for a public API, but in my case, I only wanted subsets for the sake of delegating responsibilities to smaller subsystems, while retaining the context that `match` provides via its exhaustiveness checking but does not expose as something concrete (which is deeply frustrating!). Anyway, here we are; this'll be refined over time, hopefully, and portions of it can be generalized for removing boilerplate from other IRs. Another thing to note is that this syntax is really a compromise---I had to move on, and I was spending too much time trying to get creative with `macro_rules!`. It isn't the best, and it doesn't seem very Rust-like in some places and is therefore not necessarily all that intuitive. This can be refined further in the future. But the end result, all things considered, isn't too bad. DEV-13708
2023-03-02 15:15:28 -05:00
) => {
// Enum consisting of all variants of all subtypes.
//
// This can be used as a convenient input token type,
// so that the caller does not have to worry about how we organize
// our tokens into subsets.
$(#[$attr])*
///
/// For more information,
/// see the following private subtypes:
$(
#[doc=concat!(" - [`", stringify!($subty), "`]")]
)+
#[derive(Debug, PartialEq)]
$vis enum $ir {
$(
$(
$(#[$svattr])*
///
/// _This variant will be translated into
#[doc=concat!(
"[`", stringify!($subty), "::", stringify!($svar), "`]"
)]
/// during translation into
#[doc=concat!("[`", stringify!($sumty), "`]._")]
$svar($($svty),*),
)+
)+
}
// Each individual inner type.
$(
$(#[$iattr])*
///
/// _This subtype is sourced from the respective variants of
#[doc=concat!("[`", stringify!($ir), "`]")]
/// during translation into
#[doc=concat!("[`", stringify!($sumty), "`]._")]
#[allow(clippy::enum_variant_names)] // intentional consistency
#[derive(Debug, PartialEq)]
$svis enum $subty {
$(
$(#[$svattr])*
///
/// _This type is sourced from
#[doc=concat!(
"[`", stringify!($ir), "::", stringify!($svar), "`]"
)]
/// during translation from
#[doc=concat!("[`", stringify!($ir), "`]")]
/// into
#[doc=concat!("[`", stringify!($sumty), "`]._")]
$svar($($svty),*),
)+
}
)+
// Sum type of each inner type.
//
// This is intended to be a private type;
// narrowing into subsets is an encapsulated implementation detail.
// ---
/// Sum type of the various subtypes sourced from
#[doc=concat!("[`", stringify!($ir), "`].")]
///
/// The public-facing type intended to be used outside of this
/// module is
#[doc=concat!("[`", stringify!($ir), "`].")]
///
/// By matching on this type,
/// you are able to work with a subset of the tokens of
#[doc=concat!("[`", stringify!($ir), "`],")]
/// enabling exhaustiveness checks and other type benefits in
/// composable subsystems.
#[allow(clippy::enum_variant_names)] // intentional consistency
#[derive(Debug, PartialEq)]
$svis enum $sumty {
$(
$(#[$iattr])*
$subty($subty),
)+
}
// Narrow from $ir to sum type $sumty.
impl From<$ir> for $sumty {
fn from(outer: $ir) -> Self {
match outer {
$( // inner
$( // inner variant (ivar)
#[allow(unused_variables)]
$ir::$svar($($svident),*) => Self::$subty(
$subty::$svar($($svident),*)
),
)+
)+
}
}
}
// Widen from sum type into $ir.
//
// This is important for `PartiallyStitchableParseState` to
// allow for lookahead tokens from stitched parsers to be widened.
impl From<$sumty> for $ir {
fn from(x: $sumty) -> Self {
match x {
$(
#[allow(unused_variables)]
$sumty::$subty(x) => x.into(),
)+
}
}
}
tamer: asg::air: AIR as a sum IR This introduces a new macro `sum_ir!` to help with a long-standing problem of not being able to easily narrow types in Rust without a whole lot of boilerplate. This patch includes a bit of documentation, so see that for more information. This was not a welcome change---I jumped down this rabbit hole trying to decompose `AirAggregate` so that I can share portions of parsing with the current parser and a template parser. I can now proceed with that. This is not the only implementation that I had tried. I previously inverted the approach, as I've been doing manually for some time: manually create types to hold the sets of variants, and then create a sum type to hold those types. That works, but it resulted in a mess for systems that have to use the IR, since now you have two enums to contend with. I didn't find that to be appropriate, because we shouldn't complicate the external API for implementation details. The enum for IRs is supposed to be like a bytecode---a list of operations that can be performed with the IR. They can be grouped if it makes sense for a public API, but in my case, I only wanted subsets for the sake of delegating responsibilities to smaller subsystems, while retaining the context that `match` provides via its exhaustiveness checking but does not expose as something concrete (which is deeply frustrating!). Anyway, here we are; this'll be refined over time, hopefully, and portions of it can be generalized for removing boilerplate from other IRs. Another thing to note is that this syntax is really a compromise---I had to move on, and I was spending too much time trying to get creative with `macro_rules!`. It isn't the best, and it doesn't seem very Rust-like in some places and is therefore not necessarily all that intuitive. This can be refined further in the future. But the end result, all things considered, isn't too bad. DEV-13708
2023-03-02 15:15:28 -05:00
// Widen from each inner type to outer.
//
// This is important for `PartiallyStitchableParseState` to
// allow for lookahead tokens from stitched parsers to be widened.
tamer: asg::air: AIR as a sum IR This introduces a new macro `sum_ir!` to help with a long-standing problem of not being able to easily narrow types in Rust without a whole lot of boilerplate. This patch includes a bit of documentation, so see that for more information. This was not a welcome change---I jumped down this rabbit hole trying to decompose `AirAggregate` so that I can share portions of parsing with the current parser and a template parser. I can now proceed with that. This is not the only implementation that I had tried. I previously inverted the approach, as I've been doing manually for some time: manually create types to hold the sets of variants, and then create a sum type to hold those types. That works, but it resulted in a mess for systems that have to use the IR, since now you have two enums to contend with. I didn't find that to be appropriate, because we shouldn't complicate the external API for implementation details. The enum for IRs is supposed to be like a bytecode---a list of operations that can be performed with the IR. They can be grouped if it makes sense for a public API, but in my case, I only wanted subsets for the sake of delegating responsibilities to smaller subsystems, while retaining the context that `match` provides via its exhaustiveness checking but does not expose as something concrete (which is deeply frustrating!). Anyway, here we are; this'll be refined over time, hopefully, and portions of it can be generalized for removing boilerplate from other IRs. Another thing to note is that this syntax is really a compromise---I had to move on, and I was spending too much time trying to get creative with `macro_rules!`. It isn't the best, and it doesn't seem very Rust-like in some places and is therefore not necessarily all that intuitive. This can be refined further in the future. But the end result, all things considered, isn't too bad. DEV-13708
2023-03-02 15:15:28 -05:00
$(
impl From<$subty> for $ir {
fn from(x: $subty) -> Self {
match x {
$(
#[allow(unused_variables)]
$subty::$svar($($svident),*) => Self::$svar($($svident),*),
)+
}
}
}
)*
impl std::fmt::Display for $ir {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
$( // inner
$( // inner variant (ivar)
#[allow(unused_variables)]
Self::$svar($($svident),*) => {
let $svfmt = f;
$svfmtbody
}
)+
)+
}
}
}
impl std::fmt::Display for $sumty {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
$(
#[allow(unused_variables)]
$sumty::$subty(x) => std::fmt::Display::fmt(x, f),
)+
}
}
}
tamer: asg::air: AIR as a sum IR This introduces a new macro `sum_ir!` to help with a long-standing problem of not being able to easily narrow types in Rust without a whole lot of boilerplate. This patch includes a bit of documentation, so see that for more information. This was not a welcome change---I jumped down this rabbit hole trying to decompose `AirAggregate` so that I can share portions of parsing with the current parser and a template parser. I can now proceed with that. This is not the only implementation that I had tried. I previously inverted the approach, as I've been doing manually for some time: manually create types to hold the sets of variants, and then create a sum type to hold those types. That works, but it resulted in a mess for systems that have to use the IR, since now you have two enums to contend with. I didn't find that to be appropriate, because we shouldn't complicate the external API for implementation details. The enum for IRs is supposed to be like a bytecode---a list of operations that can be performed with the IR. They can be grouped if it makes sense for a public API, but in my case, I only wanted subsets for the sake of delegating responsibilities to smaller subsystems, while retaining the context that `match` provides via its exhaustiveness checking but does not expose as something concrete (which is deeply frustrating!). Anyway, here we are; this'll be refined over time, hopefully, and portions of it can be generalized for removing boilerplate from other IRs. Another thing to note is that this syntax is really a compromise---I had to move on, and I was spending too much time trying to get creative with `macro_rules!`. It isn't the best, and it doesn't seem very Rust-like in some places and is therefore not necessarily all that intuitive. This can be refined further in the future. But the end result, all things considered, isn't too bad. DEV-13708
2023-03-02 15:15:28 -05:00
$(
impl std::fmt::Display for $subty {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
$(
#[allow(unused_variables)]
Self::$svar($($svident),*) => {
let $svfmt = f;
$svfmtbody
}
)+
}
}
}
)+
impl crate::parse::Token for $ir {
fn ir_name() -> &'static str {
$irname
}
fn span(&self) -> crate::span::Span {
match self {
$( // inner
$( // inner variant (ivar)
#[allow(unused_variables)]
Self::$svar($($svident),*) => (*$svspan).into(),
)+
)+
}
}
}
impl crate::parse::Token for $sumty {
fn ir_name() -> &'static str {
$irname
}
fn span(&self) -> crate::span::Span {
match self {
$(
$sumty::$subty(x) => x.span(),
)+
}
}
}
tamer: asg::air: AIR as a sum IR This introduces a new macro `sum_ir!` to help with a long-standing problem of not being able to easily narrow types in Rust without a whole lot of boilerplate. This patch includes a bit of documentation, so see that for more information. This was not a welcome change---I jumped down this rabbit hole trying to decompose `AirAggregate` so that I can share portions of parsing with the current parser and a template parser. I can now proceed with that. This is not the only implementation that I had tried. I previously inverted the approach, as I've been doing manually for some time: manually create types to hold the sets of variants, and then create a sum type to hold those types. That works, but it resulted in a mess for systems that have to use the IR, since now you have two enums to contend with. I didn't find that to be appropriate, because we shouldn't complicate the external API for implementation details. The enum for IRs is supposed to be like a bytecode---a list of operations that can be performed with the IR. They can be grouped if it makes sense for a public API, but in my case, I only wanted subsets for the sake of delegating responsibilities to smaller subsystems, while retaining the context that `match` provides via its exhaustiveness checking but does not expose as something concrete (which is deeply frustrating!). Anyway, here we are; this'll be refined over time, hopefully, and portions of it can be generalized for removing boilerplate from other IRs. Another thing to note is that this syntax is really a compromise---I had to move on, and I was spending too much time trying to get creative with `macro_rules!`. It isn't the best, and it doesn't seem very Rust-like in some places and is therefore not necessarily all that intuitive. This can be refined further in the future. But the end result, all things considered, isn't too bad. DEV-13708
2023-03-02 15:15:28 -05:00
$(
impl crate::parse::Token for $subty {
fn ir_name() -> &'static str {
concat!($irname, " narrowed into ", stringify!($subty))
}
fn span(&self) -> crate::span::Span {
match self {
$(
#[allow(unused_variables)]
Self::$svar($($svident),*) => (*$svspan).into(),
)+
}
}
}
)+
// Only implement for outer $ir,
// because the inner enums aren't things that we want yielded by
// parsers,
// at least at present,
// since they are nothing more than implementation details.
impl crate::parse::Object for $ir {}
// In addition to the complete sum type $sumty above,
// the user may also specify their own sum sum types with explicit
// subtypes.
$(
$(#[$sumattr])*
#[derive(Debug, PartialEq)]
$sumvis enum $sumsub {
$(
$sumsubty($sumsubty),
)+
}
impl crate::parse::Token for $sumsub {
fn ir_name() -> &'static str {
concat!($irname, " narrowed into ", stringify!($sumsub))
}
fn span(&self) -> crate::span::Span {
match self {
$(
Self::$sumsubty(x) => x.span(),
)+
}
}
}
impl std::fmt::Display for $sumsub {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
$(
Self::$sumsubty(x) => std::fmt::Display::fmt(x, f),
)+
}
}
}
$(
impl From<$sumsubty> for $sumsub {
fn from(x: $sumsubty) -> Self {
Self::$sumsubty(x)
}
}
)+
// Widen from each inner type to outer $ir.
//
// This is important for `PartiallyStitchableParseState` to
// allow for lookahead tokens from stitched parsers to be
// widened.
impl From<$sumsub> for $ir {
fn from(sum: $sumsub) -> Self {
match sum {
$(
$sumsub::$sumsubty(x) => x.into(),
)+
}
}
}
)*
tamer: asg::air: AIR as a sum IR This introduces a new macro `sum_ir!` to help with a long-standing problem of not being able to easily narrow types in Rust without a whole lot of boilerplate. This patch includes a bit of documentation, so see that for more information. This was not a welcome change---I jumped down this rabbit hole trying to decompose `AirAggregate` so that I can share portions of parsing with the current parser and a template parser. I can now proceed with that. This is not the only implementation that I had tried. I previously inverted the approach, as I've been doing manually for some time: manually create types to hold the sets of variants, and then create a sum type to hold those types. That works, but it resulted in a mess for systems that have to use the IR, since now you have two enums to contend with. I didn't find that to be appropriate, because we shouldn't complicate the external API for implementation details. The enum for IRs is supposed to be like a bytecode---a list of operations that can be performed with the IR. They can be grouped if it makes sense for a public API, but in my case, I only wanted subsets for the sake of delegating responsibilities to smaller subsystems, while retaining the context that `match` provides via its exhaustiveness checking but does not expose as something concrete (which is deeply frustrating!). Anyway, here we are; this'll be refined over time, hopefully, and portions of it can be generalized for removing boilerplate from other IRs. Another thing to note is that this syntax is really a compromise---I had to move on, and I was spending too much time trying to get creative with `macro_rules!`. It isn't the best, and it doesn't seem very Rust-like in some places and is therefore not necessarily all that intuitive. This can be refined further in the future. But the end result, all things considered, isn't too bad. DEV-13708
2023-03-02 15:15:28 -05:00
}
}
sum_ir! {
/// AIR token.
///
/// These tokens mimic a public API for the ASG,
/// and allow parsers to be completely decoupled from the ASG object that
/// they will eventually aggregate data into.
///
/// This IR is not intended to perform sophisticated manipulation of the
/// ASG---it
/// is intended to perform initial aggregation as part of a parsing
/// phase,
/// populating the ASG with the raw data that that will be
/// subsequently analyzed and rewritten.
///
/// Implementation Notes
/// ====================
/// [`Air`] is a public token type;
/// it serves as input into this subsystem and is expected to be
/// yielded by parsers in the lowering pipeline.
///
/// However,
/// the token is far too broad of a type for deconstructing the
/// subsystem into components.
/// TAMER makes extensive use of exhaustiveness checking and constrained
/// domains to aid in proving system correctness.
/// To facilitate that,
/// [`Air`] decomposes into [`AirSubsets`],
/// which is a sum type over the various subtypes listed below,
/// defined using [`sum_ir!`].
/// [`Air`] contains the union of each of those subtypes' variants for
/// convenience,
/// allowing the construction of subtypes to be encapsulated and
/// able to vary independently from the public type.
pub enum Air -> pub AirSubsets "AIR" {
tamer: asg::air: AIR as a sum IR This introduces a new macro `sum_ir!` to help with a long-standing problem of not being able to easily narrow types in Rust without a whole lot of boilerplate. This patch includes a bit of documentation, so see that for more information. This was not a welcome change---I jumped down this rabbit hole trying to decompose `AirAggregate` so that I can share portions of parsing with the current parser and a template parser. I can now proceed with that. This is not the only implementation that I had tried. I previously inverted the approach, as I've been doing manually for some time: manually create types to hold the sets of variants, and then create a sum type to hold those types. That works, but it resulted in a mess for systems that have to use the IR, since now you have two enums to contend with. I didn't find that to be appropriate, because we shouldn't complicate the external API for implementation details. The enum for IRs is supposed to be like a bytecode---a list of operations that can be performed with the IR. They can be grouped if it makes sense for a public API, but in my case, I only wanted subsets for the sake of delegating responsibilities to smaller subsystems, while retaining the context that `match` provides via its exhaustiveness checking but does not expose as something concrete (which is deeply frustrating!). Anyway, here we are; this'll be refined over time, hopefully, and portions of it can be generalized for removing boilerplate from other IRs. Another thing to note is that this syntax is really a compromise---I had to move on, and I was spending too much time trying to get creative with `macro_rules!`. It isn't the best, and it doesn't seem very Rust-like in some places and is therefore not necessarily all that intuitive. This can be refined further in the future. But the end result, all things considered, isn't too bad. DEV-13708
2023-03-02 15:15:28 -05:00
/// Tokens to be used by incomplete features that ought to type
/// check and be ignored rather than panic.
///
/// This allows for a complete parse of what is known.
enum AirTodo {
/// Placeholder token for objects that do not yet have a proper place on
/// the ASG.
Todo(span: Span) => {
span: span,
display: |f| write!(f, "TODO"),
},
}
/// Subset of [`Air`] tokens used to define [`Pkg`]s.
enum AirPkg {
/// Begin a new package of identifiers.
///
/// Packages are responsible for bundling together identifiers
/// representing subsystems that can be composed with other packages.
///
/// A source language may place limits on the objects that may appear
/// within a given package,
/// but we have no such restriction.
///
/// TODO: The package needs a name,
/// and we'll need to determine how to best represent that relative to
/// the project root and be considerate of symlinks.
PkgOpen(span: Span) => {
span: span,
display: |f| write!(f, "open package"),
},
/// Complete processing of the current package.
PkgClose(span: Span) => {
span: span,
display: |f| write!(f, "close package"),
},
}
/// Subset of [`Air`] tokens used to define [`Expr`]s.
enum AirExpr {
/// Create a new [`Expr`] on the graph and place it atop of the
/// expression stack.
///
/// If there was previously an expression ρ atop of the stack before
/// this operation,
/// a reference to this new expression will be automatically added
/// to ρ,
/// treating it as a child expression.
/// Otherwise,
/// the expression will be dangling unless bound to an identifier,
/// which will produce an error.
///
/// All expressions have an associated [`ExprOp`] that determines how
/// the expression will be evaluated.
/// An expression is associated with a source location,
/// but is anonymous unless assigned an identifier using
/// [`Air::BindIdent`].
///
/// Expressions are composed of references to other expressions.
ExprOpen(op: ExprOp, span: Span) => {
span: span,
display: |f| write!(f, "open {op} expression"),
},
/// Complete the expression atop of the expression stack and pop it from
/// the stack.
ExprClose(span: Span) => {
span: span,
display: |f| write!(f, "close expression"),
},
}
/// Subset of [`Air`] tokens dealing with the binding of identifiers
/// to objects.
enum AirBind {
/// Assign an identifier to the active object.
///
/// The "active" object depends on the current parsing state.
BindIdent(id: SPair) => {
span: id,
display: |f| write!(
f,
"identify active object as {}",
TtQuote::wrap(id),
),
},
/// Reference another object identified by the given [`SPair`].
///
/// Objects can be referenced before they are declared or defined,
/// so the provided identifier need not yet exist.
/// However,
/// the identifier must eventually be bound to an object of
/// the appropriate type depending on context.
RefIdent(id: SPair) => {
span: id,
display: |f| write!(
f,
"reference to identifier {}",
TtQuote::wrap(id),
),
},
tamer: asg::air: AIR as a sum IR This introduces a new macro `sum_ir!` to help with a long-standing problem of not being able to easily narrow types in Rust without a whole lot of boilerplate. This patch includes a bit of documentation, so see that for more information. This was not a welcome change---I jumped down this rabbit hole trying to decompose `AirAggregate` so that I can share portions of parsing with the current parser and a template parser. I can now proceed with that. This is not the only implementation that I had tried. I previously inverted the approach, as I've been doing manually for some time: manually create types to hold the sets of variants, and then create a sum type to hold those types. That works, but it resulted in a mess for systems that have to use the IR, since now you have two enums to contend with. I didn't find that to be appropriate, because we shouldn't complicate the external API for implementation details. The enum for IRs is supposed to be like a bytecode---a list of operations that can be performed with the IR. They can be grouped if it makes sense for a public API, but in my case, I only wanted subsets for the sake of delegating responsibilities to smaller subsystems, while retaining the context that `match` provides via its exhaustiveness checking but does not expose as something concrete (which is deeply frustrating!). Anyway, here we are; this'll be refined over time, hopefully, and portions of it can be generalized for removing boilerplate from other IRs. Another thing to note is that this syntax is really a compromise---I had to move on, and I was spending too much time trying to get creative with `macro_rules!`. It isn't the best, and it doesn't seem very Rust-like in some places and is therefore not necessarily all that intuitive. This can be refined further in the future. But the end result, all things considered, isn't too bad. DEV-13708
2023-03-02 15:15:28 -05:00
}
/// Subset of [`Air`] tokens for declaring and manipulating
/// [`Ident`]s.
///
/// These tokens are from the early days of `tameld` when the system
/// was still largely a proof-of-concept;
/// they do not marry well with TAMER's present design are
/// likely to change in the future.
enum AirIdent {
/// Declare a resolved identifier.
IdentDecl(name: SPair, kind: IdentKind, src: Source) => {
span: name,
display: |f| write!(
f,
"declaration of identifier {}",
TtQuote::wrap(name),
),
},
/// Declare an external identifier that must be resolved before linking.
IdentExternDecl(name: SPair, kind: IdentKind, src: Source) => {
span: name,
display: |f| write!(
f,
"declaration of external identifier {}",
TtQuote::wrap(name),
),
},
/// Declare that an identifier depends on another for its definition.
///
/// The first identifier will depend on the second
/// (`0 -> 1`).
/// The spans associated with each [`SPair`] will be used
/// if the respective identifier has not yet been defined.
IdentDep(name: SPair, dep: SPair) => {
span: name,
display: |f| write!(
f,
// TODO: Use list wrapper
"declaration of identifier dependency `{name} -> {dep}`",
),
},
/// Associate a code fragment with an identifier.
///
/// A fragment does not have an associated span because it is
/// conceptually associated with all the spans from which it is
/// derived;
/// the format of the object file will change in the future to
/// retain this information.
IdentFragment(name: SPair, text: FragmentText) => {
span: name,
display: |f| write!(
f,
"identifier {}` fragment text",
TtQuote::wrap(name),
),
},
/// Root an identifier at the request of some entity at the associated
/// span of the [`SPair`].
///
/// Rooting is caused by _something_,
/// and the span is intended to aid in tracking down why rooting
/// occurred.
IdentRoot(name: SPair) => {
span: name,
display: |f| write!(
f,
"rooting of identifier {}",
TtQuote::wrap(name),
),
},
}
/// Subset of [`Air`] tokens for defining [`Tpl`]s.
enum AirTpl {
/// Create a new [`Tpl`] on the graph and switch to template parsing.
///
/// Until [`Self::TplClose`] is found,
/// all parsed objects will be parented to the [`Tpl`] rather than the
/// parent [`Pkg`].
/// Template parsing also recognizes additional nodes that can appear
/// only in this mode.
///
/// The active [`ExprStack`] will be restored after template
/// parsing has concluded.
tamer: asg::air: AIR as a sum IR This introduces a new macro `sum_ir!` to help with a long-standing problem of not being able to easily narrow types in Rust without a whole lot of boilerplate. This patch includes a bit of documentation, so see that for more information. This was not a welcome change---I jumped down this rabbit hole trying to decompose `AirAggregate` so that I can share portions of parsing with the current parser and a template parser. I can now proceed with that. This is not the only implementation that I had tried. I previously inverted the approach, as I've been doing manually for some time: manually create types to hold the sets of variants, and then create a sum type to hold those types. That works, but it resulted in a mess for systems that have to use the IR, since now you have two enums to contend with. I didn't find that to be appropriate, because we shouldn't complicate the external API for implementation details. The enum for IRs is supposed to be like a bytecode---a list of operations that can be performed with the IR. They can be grouped if it makes sense for a public API, but in my case, I only wanted subsets for the sake of delegating responsibilities to smaller subsystems, while retaining the context that `match` provides via its exhaustiveness checking but does not expose as something concrete (which is deeply frustrating!). Anyway, here we are; this'll be refined over time, hopefully, and portions of it can be generalized for removing boilerplate from other IRs. Another thing to note is that this syntax is really a compromise---I had to move on, and I was spending too much time trying to get creative with `macro_rules!`. It isn't the best, and it doesn't seem very Rust-like in some places and is therefore not necessarily all that intuitive. This can be refined further in the future. But the end result, all things considered, isn't too bad. DEV-13708
2023-03-02 15:15:28 -05:00
TplOpen(span: Span) => {
span: span,
display: |f| write!(f, "open template"),
},
/// Close the active [`Tpl`] and exit template parsing.
///
/// The [`ExprStack`] will be restored to its prior state.
TplClose(span: Span) => {
span: span,
display: |f| write!(f, "close template"),
},
}
}
/// Expressions that are able to be bound to identifiers.
///
/// This is the primary token set when parsing packages,
/// since most everything in TAMER is an expression.
pub sum enum AirBindableExpr = AirExpr | AirBind;
tamer: asg::air: AIR as a sum IR This introduces a new macro `sum_ir!` to help with a long-standing problem of not being able to easily narrow types in Rust without a whole lot of boilerplate. This patch includes a bit of documentation, so see that for more information. This was not a welcome change---I jumped down this rabbit hole trying to decompose `AirAggregate` so that I can share portions of parsing with the current parser and a template parser. I can now proceed with that. This is not the only implementation that I had tried. I previously inverted the approach, as I've been doing manually for some time: manually create types to hold the sets of variants, and then create a sum type to hold those types. That works, but it resulted in a mess for systems that have to use the IR, since now you have two enums to contend with. I didn't find that to be appropriate, because we shouldn't complicate the external API for implementation details. The enum for IRs is supposed to be like a bytecode---a list of operations that can be performed with the IR. They can be grouped if it makes sense for a public API, but in my case, I only wanted subsets for the sake of delegating responsibilities to smaller subsystems, while retaining the context that `match` provides via its exhaustiveness checking but does not expose as something concrete (which is deeply frustrating!). Anyway, here we are; this'll be refined over time, hopefully, and portions of it can be generalized for removing boilerplate from other IRs. Another thing to note is that this syntax is really a compromise---I had to move on, and I was spending too much time trying to get creative with `macro_rules!`. It isn't the best, and it doesn't seem very Rust-like in some places and is therefore not necessarily all that intuitive. This can be refined further in the future. But the end result, all things considered, isn't too bad. DEV-13708
2023-03-02 15:15:28 -05:00
}