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
main
Mike Gerwitz 2023-03-02 15:15:28 -05:00
parent d42a46d2b8
commit 34b64fd619
4 changed files with 590 additions and 277 deletions

View File

@ -17,257 +17,45 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! Intermediate representation for construction of the
//! [abstract semantic graph (ASG)](super) (AIR).
//!
//! AIR serves as an abstraction layer between higher-level parsers and the
//! aggregate ASG.
//! It allows parsers to operate as a raw stream of data without having to
//! worry about ownership of or references to the ASG,
//! and allows for multiple such parsers to be joined.
//!
//! AIR is _not_ intended to replace the API of the ASG---it
//! is intended as a termination point for the parsing pipeline,
//! and as such implements a subset of the ASG's API that is suitable
//! for aggregating raw data from source and object files.
//! Given that it does so little and is so close to the [`Asg`] API,
//! one might say that the abstraction is as light as air,
//! but that would surely result in face-palming and so we're not going
//! air such cringeworthy dad jokes here.
use super::{
graph::object::{Expr, Pkg},
Asg, AsgError, ExprOp, FragmentText, IdentKind, ObjectIndex, Source,
Asg, AsgError, ObjectIndex,
};
use crate::{
asg::graph::object::Tpl,
f::Functor,
fmt::{DisplayWrapper, TtQuote},
parse::{self, util::SPair, ParseState, Token, Transition, Transitionable},
span::{Span, UNKNOWN_SPAN},
parse::{util::SPair, ParseState, Transition, Transitionable},
span::Span,
sym::SymbolId,
};
use std::fmt::{Debug, Display};
///! Intermediate representation for construction of the
///! [abstract semantic graph (ASG)](super) (AIR).
///!
///! AIR serves as an abstraction layer between higher-level parsers and the
///! aggregate ASG.
///! It allows parsers to operate as a raw stream of data without having to
///! worry about ownership of or references to the ASG,
///! and allows for multiple such parsers to be joined.
///!
///! AIR is _not_ intended to replace the API of the ASG---it
///! is intended as a termination point for the parsing pipeline,
///! and as such implements a subset of the ASG's API that is suitable
///! for aggregating raw data from source and object files.
///! Given that it does so little and is so close to the [`Asg`] API,
///! one might say that the abstraction is as light as air,
///! but that would surely result in face-palming and so we're not going
///! air such cringeworthy dad jokes here.
#[macro_use]
mod ir;
pub use ir::Air;
pub type IdentSym = SymbolId;
pub type DepSym = SymbolId;
/// 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.
#[derive(Debug, PartialEq)]
pub enum Air {
/// Placeholder token for objects that do not yet have a proper place on
/// the ASG.
Todo,
/// 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),
/// Complete processing of the current package.
PkgClose(Span),
/// 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(ExprOp, Span),
/// Complete the expression atop of the expression stack and pop it from
/// the stack.
ExprClose(Span),
/// Reference another expression identified by the given [`SPair`].
///
/// Values 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 [`Expr`].
///
/// Since all values in TAME are referentially tansparent,
/// the system has flexibility in determining what it should do with a
/// reference.
ExprRef(SPair),
/// Assign an identifier to the active object.
///
/// The "active" object depends on the current parsing state.
BindIdent(SPair),
/// Declare a resolved identifier.
IdentDecl(SPair, IdentKind, Source),
/// Declare an external identifier that must be resolved before linking.
IdentExternDecl(SPair, IdentKind, Source),
/// 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(SPair, SPair),
/// 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(SPair, FragmentText),
/// 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(SPair),
/// 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 [`ExprStack`] will be [`Held`],
/// to be restored after template parsing has concluded.
TplOpen(Span),
/// Close the active [`Tpl`] and exit template parsing.
///
/// The [`ExprStack`] will be restored to its prior state.
TplClose(Span),
}
impl Token for Air {
fn ir_name() -> &'static str {
"AIR"
}
fn span(&self) -> crate::span::Span {
use Air::*;
match self {
Todo => UNKNOWN_SPAN,
PkgOpen(span)
| PkgClose(span)
| ExprOpen(_, span)
| ExprClose(span)
| TplOpen(span)
| TplClose(span) => *span,
BindIdent(spair)
| ExprRef(spair)
| IdentDecl(spair, _, _)
| IdentExternDecl(spair, _, _)
| IdentDep(spair, _)
| IdentFragment(spair, _)
| IdentRoot(spair) => spair.span(),
}
}
}
impl parse::Object for Air {}
impl Display for Air {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use Air::*;
match self {
Todo => write!(f, "TODO"),
PkgOpen(_) => write!(f, "open package"),
PkgClose(_) => write!(f, "close package"),
ExprOpen(op, _) => write!(f, "open {op} expression"),
ExprClose(_) => write!(f, "close expression"),
BindIdent(id) => {
write!(f, "identify active object as {}", TtQuote::wrap(id))
}
ExprRef(id) => {
write!(
f,
"reference to the expression identified by {}",
TtQuote::wrap(id)
)
}
IdentDecl(spair, _, _) => {
write!(f, "declaration of identifier {}", TtQuote::wrap(spair))
}
IdentExternDecl(spair, _, _) => {
write!(
f,
"declaration of external identifier {}",
TtQuote::wrap(spair)
)
}
IdentDep(isym, dsym) => write!(
f,
// TODO: Use list wrapper
"declaration of identifier dependency `{isym} -> {dsym}`"
),
IdentFragment(depsym, _text) => {
write!(f, "identifier {}` fragment text", TtQuote::wrap(depsym))
}
IdentRoot(sym) => {
write!(f, "rooting of identifier {}", TtQuote::wrap(sym))
}
TplOpen(name) => {
write!(f, "open template {}", TtQuote::wrap(name))
}
TplClose(_) => {
write!(f, "close template")
}
}
}
}
/// Stack of held expressions,
/// with the root expression at the bottom of the stack.
///
@ -568,7 +356,7 @@ impl ParseState for AirAggregate {
/// Destination [`Asg`] that this parser lowers into.
///
/// This ASG will be yielded by [`parse::Parser::finalize`].
/// This ASG will be yielded by [`crate::parse::Parser::finalize`].
type Context = Asg;
fn parse_token(
@ -576,55 +364,59 @@ impl ParseState for AirAggregate {
tok: Self::Token,
asg: &mut Self::Context,
) -> crate::parse::TransitionResult<Self> {
use Air::*;
use ir::{
AirBind::*, AirExpr::*, AirIdent::*, AirPkg::*, AirSubsets::*,
AirTodo::*, AirTpl::*,
};
use AirAggregate::*;
// TODO: Seems to be about time for refactoring this...
match (self, tok) {
(st, Todo) => Transition(st).incomplete(),
match (self, tok.into()) {
(st, AirTodo(Todo(_))) => Transition(st).incomplete(),
(Empty(es), PkgOpen(span)) => {
(Empty(es), AirPkg(PkgOpen(span))) => {
let oi_pkg = asg.create(Pkg::new(span)).root(asg);
Transition(PkgDfn(oi_pkg, es)).incomplete()
}
(PkgDfn(oi_pkg, es), PkgOpen(span)) => {
(PkgDfn(oi_pkg, es), AirPkg(PkgOpen(span))) => {
Transition(PkgDfn(oi_pkg, es))
.err(AsgError::NestedPkgOpen(span, oi_pkg.span()))
}
(BuildingExpr(oi_pkg, es, oi), PkgOpen(span)) => {
(BuildingExpr(oi_pkg, es, oi), AirPkg(PkgOpen(span))) => {
Transition(BuildingExpr(oi_pkg, es, oi))
.err(AsgError::NestedPkgOpen(span, oi_pkg.span()))
}
(PkgDfn(oi_pkg, es), PkgClose(span)) => {
(PkgDfn(oi_pkg, es), AirPkg(PkgClose(span))) => {
oi_pkg.close(asg, span);
Transition(Empty(es)).incomplete()
}
(st @ (Empty(..) | BuildingExpr(..)), PkgClose(span)) => {
(st @ (Empty(..) | BuildingExpr(..)), AirPkg(PkgClose(span))) => {
Transition(st).err(AsgError::InvalidPkgCloseContext(span))
}
(PkgDfn(oi_pkg, es), ExprOpen(op, span)) => {
(PkgDfn(oi_pkg, es), AirExpr(ExprOpen(op, span))) => {
let oi = asg.create(Expr::new(op, span));
Transition(BuildingExpr(oi_pkg, es.activate(), oi)).incomplete()
}
(BuildingExpr(oi_pkg, es, poi), ExprOpen(op, span)) => {
(BuildingExpr(oi_pkg, es, poi), AirExpr(ExprOpen(op, span))) => {
let oi = poi.create_subexpr(asg, Expr::new(op, span));
Transition(BuildingExpr(oi_pkg, es.push(poi), oi)).incomplete()
}
(st @ Empty(..), ExprOpen(_, span) | TplOpen(span)) => {
Transition(st).err(AsgError::PkgExpected(span))
}
(
st @ Empty(..),
AirExpr(ExprOpen(_, span)) | AirTpl(TplOpen(span)),
) => Transition(st).err(AsgError::PkgExpected(span)),
(st @ (Empty(..) | PkgDfn(..)), ExprClose(span)) => {
(st @ (Empty(..) | PkgDfn(..)), AirExpr(ExprClose(span))) => {
Transition(st).err(AsgError::UnbalancedExpr(span))
}
(BuildingExpr(oi_pkg, es, oi), ExprClose(end)) => {
(BuildingExpr(oi_pkg, es, oi), AirExpr(ExprClose(end))) => {
let start: Span = oi.into();
let _ = oi.map_obj(asg, |expr| {
@ -650,7 +442,7 @@ impl ParseState for AirAggregate {
}
}
(BuildingExpr(oi_pkg, es, oi), BindIdent(id)) => {
(BuildingExpr(oi_pkg, es, oi), AirBind(BindIdent(id))) => {
let oi_ident = asg.lookup_or_missing(id);
oi_pkg.defines(asg, oi_ident);
@ -667,61 +459,66 @@ impl ParseState for AirAggregate {
}
}
(BuildingExpr(oi_pkg, es, oi), ExprRef(ident)) => {
(BuildingExpr(oi_pkg, es, oi), AirExpr(ExprRef(ident))) => {
Transition(BuildingExpr(oi_pkg, es, oi.ref_expr(asg, ident)))
.incomplete()
}
(st @ (Empty(_) | PkgDfn(_, _)), BindIdent(ident)) => {
(st @ (Empty(_) | PkgDfn(_, _)), AirBind(BindIdent(ident))) => {
Transition(st).err(AsgError::InvalidExprBindContext(ident))
}
(st @ (Empty(_) | PkgDfn(_, _)), ExprRef(ident)) => {
(st @ (Empty(_) | PkgDfn(_, _)), AirExpr(ExprRef(ident))) => {
Transition(st).err(AsgError::InvalidExprRefContext(ident))
}
(st @ Empty(_), IdentDecl(name, kind, src)) => {
(st @ Empty(_), AirIdent(IdentDecl(name, kind, src))) => {
asg.declare(name, kind, src).map(|_| ()).transition(st)
}
(st @ Empty(_), IdentExternDecl(name, kind, src)) => asg
(st @ Empty(_), AirIdent(IdentExternDecl(name, kind, src))) => asg
.declare_extern(name, kind, src)
.map(|_| ())
.transition(st),
(st @ Empty(_), IdentDep(sym, dep)) => {
(st @ Empty(_), AirIdent(IdentDep(sym, dep))) => {
asg.add_dep_lookup(sym, dep);
Transition(st).incomplete()
}
(st @ Empty(_), IdentFragment(sym, text)) => {
(st @ Empty(_), AirIdent(IdentFragment(sym, text))) => {
asg.set_fragment(sym, text).map(|_| ()).transition(st)
}
(st @ Empty(_), IdentRoot(sym)) => {
(st @ Empty(_), AirIdent(IdentRoot(sym))) => {
let obj = asg.lookup_or_missing(sym);
asg.add_root(obj);
Transition(st).incomplete()
}
(PkgDfn(oi_pkg, es), TplOpen(span)) => {
(PkgDfn(oi_pkg, es), AirTpl(TplOpen(span))) => {
let oi_tpl = asg.create(Tpl::new(span));
Transition(BuildingTpl((oi_pkg, es.hold()), oi_tpl, None))
.incomplete()
}
(BuildingExpr(..), TplOpen(_span)) => todo!("BuildingExpr TplOpen"),
(BuildingExpr(..), AirTpl(TplOpen(_span))) => {
todo!("BuildingExpr TplOpen")
}
(BuildingTpl((oi_pkg, es), oi_tpl, None), BindIdent(name)) => asg
(
BuildingTpl((oi_pkg, es), oi_tpl, None),
AirBind(BindIdent(name)),
) => asg
.lookup_or_missing(name)
.bind_definition(asg, name, oi_tpl)
.map(|oi_ident| oi_pkg.defines(asg, oi_ident))
.map(|_| ())
.transition(BuildingTpl((oi_pkg, es), oi_tpl, Some(name))),
(BuildingTpl((oi_pkg, es), oi_tpl, _), TplClose(span)) => {
(BuildingTpl((oi_pkg, es), oi_tpl, _), AirTpl(TplClose(span))) => {
oi_tpl.close(asg, span);
Transition(es.release_st(oi_pkg)).incomplete()
}
@ -730,14 +527,10 @@ impl ParseState for AirAggregate {
(
st @ (Empty(..) | PkgDfn(..) | BuildingExpr(..)),
TplClose(span),
AirTpl(TplClose(span)),
) => Transition(st).err(AsgError::UnbalancedTpl(span)),
(
st,
tok @ (IdentDecl(..) | IdentExternDecl(..) | IdentDep(..)
| IdentFragment(..) | IdentRoot(..)),
) => todo!("{st:?}, {tok:?}"),
(st, tok @ AirIdent(_)) => todo!("{st:?}, {tok:?}"),
}
}

View File

@ -0,0 +1,518 @@
// 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, Held,
};
/// 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.
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,
},
)+
}
)+
}
) => {
// 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 each inner type to outer.
$(
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 $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 $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 {}
}
}
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(super) 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"),
},
/// Reference another expression identified by the given [`SPair`].
///
/// Values 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 [`Expr`].
///
/// Since all values in TAME are referentially tansparent,
/// the system has flexibility in determining what it should do with a
/// reference.
ExprRef(id: SPair) => {
span: id,
display: |f| write!(
f,
"reference to identifier {}",
TtQuote::wrap(id),
),
},
}
/// 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),
),
},
}
/// 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 [`ExprStack`] will be [`Held`],
/// to 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 [`ExprStack`] will be restored to its prior state.
TplClose(span: Span) => {
span: span,
display: |f| write!(f, "close template"),
},
}
}
}

View File

@ -22,11 +22,12 @@
use super::super::Ident;
use super::*;
use crate::asg::graph::object::expr::ExprRel;
use crate::asg::graph::object::ObjectRel;
use crate::parse::Parser;
use crate::{
parse::{ParseError, Parsed},
asg::{
graph::object::{expr::ExprRel, ObjectRel},
ExprOp, IdentKind, Source,
},
parse::{ParseError, Parsed, Parser, Token},
span::dummy::*,
};
use std::assert_matches::assert_matches;

View File

@ -26,6 +26,7 @@ use crate::{
diagnose::Diagnostic,
nir::NirEntity,
parse::prelude::*,
span::UNKNOWN_SPAN,
};
use super::Nir;
@ -61,7 +62,7 @@ impl ParseState for NirToAir {
#[cfg(not(feature = "wip-nir-to-air"))]
{
let _ = tok; // prevent `unused_variables` warning
return Transition(Ready).ok(Air::Todo);
return Transition(Ready).ok(Air::Todo(UNKNOWN_SPAN));
}
#[allow(unreachable_code)] // due to wip-nir-to-air
@ -121,7 +122,7 @@ impl ParseState for NirToAir {
| Nir::Text(_)
| Nir::Open(NirEntity::TplParam, _)
| Nir::Close(NirEntity::TplParam, _),
) => Transition(Ready).ok(Air::Todo),
) => Transition(Ready).ok(Air::Todo(UNKNOWN_SPAN)),
}
}