// 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 . //! 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}; /// 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. 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)|+; )* ) => { // 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(), )+ } } } // Widen from each inner type to outer. // // This is important for `PartiallyStitchableParseState` to // allow for lookahead tokens from stitched parsers to be widened. $( 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), )+ } } } $( 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(), )+ } } } $( 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])* #[allow(clippy::enum_variant_names)] // intentional consistency #[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(), )+ } } } )* } } 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" { /// 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), ), }, } /// 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 expression stack will be restored after template /// parsing has concluded. TplOpen(span: Span) => { span: span, display: |f| write!(f, "open template"), }, /// Close the active [`Tpl`] and exit template parsing. /// /// The expression stack 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; /// Tokens that may be used to define or apply templates. pub sum enum AirTemplatable = AirExpr | AirBind | AirTpl; } impl From for AirTemplatable { fn from(expr: AirBindableExpr) -> Self { match expr { AirBindableExpr::AirExpr(x) => Self::AirExpr(x), AirBindableExpr::AirBind(x) => Self::AirBind(x), } } }