tamer: xir::parse::ele: Support nested Sum NTs

This allows for a construction like this:

```
ele_parse! {
  [...]

  StmtX := QN_X {
    [...]
  };

  StmtY := QN_Y {
    [...]
  };

  ExprA := QN_A {
    [...]
  };

  ExprB := QN_B {
    [...]
  };

  Expr := (A | B);
  Stmt := (StmtX | StmtY);

  // This previously was not allowed:
  StmtOrExpr := (Stmt | Expr);
}
```

There were initially two barriers to doing so:

  1. Efficiently matching; and
  2. Outputting diagnostic information about the union of all expected
     elements.

The first was previously resolved with the introduction of `NT::matches`,
which is macro-expanded in a way that Rust will be able to optimize a
bit.  Worst case, it's effectively a linear search, but our Sum NTs are not
that deep in practice, so I don't expect that to be a concern.

The concern that I was trying to avoid was heap-allocated `NodeMatcher`s to
avoid recursive data structures, since that would have put heap access in a
very hot code path, which is not an option.

That left problem #2, which ended up being the harder problem.  The solution
was detailed in the previous commit, so you should look there, but it
amounts to being able to format individual entries as if they were a part
of a list by making them a function of not just the matcher itself, but also
the number of items in (recursively) the sum type and the position of the
matcher relative to that list.  The list length is easily
computed (recursively) at compile-time using `const`
functions (`NT::matches_n`).

And with that, NIR can be abstracted in sane ways using Sum NTs without a
bunch of duplication that would have been a maintenance burden and an
inevitable source of bugs (from having to duplicate NT references).

DEV-7145
main
Mike Gerwitz 2022-08-17 10:44:53 -04:00
parent fd3184c795
commit 4c86c5b63c
1 changed files with 103 additions and 40 deletions

View File

@ -543,8 +543,6 @@ macro_rules! ele_parse {
}
}
impl crate::xir::parse::EleParseState for $nt {}
impl $nt {
/// Matcher describing the node recognized by this parser.
#[allow(dead_code)] // used by sum parser
@ -553,14 +551,50 @@ macro_rules! ele_parse {
crate::xir::parse::NodeMatcher::from($qname)
}
// Whether the given QName would be matched by any of the
// parsers associated with this type.
//
/// Whether the given QName would be matched by any of the
/// parsers associated with this type.
#[inline]
fn matches(qname: crate::xir::QName) -> bool {
Self::matcher().matches(qname)
}
/// Number of
/// [`NodeMatcher`](crate::xir::parse::NodeMatcher)s
/// considered by this parser.
///
/// This is always `1` for this parser.
#[allow(dead_code)] // used by Sum NTs
const fn matches_n() -> usize {
1
}
/// Format [`Self::matcher`] for display.
///
/// This value may be rendered singularly or as part of a
/// list of values joined together by Sum NTs.
/// This function receives the number of values to be
/// formatted as `n` and the current 0-indexed offset
/// within that list as `i`.
/// This allows for zero-copy rendering of composable NTs.
///
/// `i` must be incremented after the operation.
#[allow(dead_code)] // used by Sum NTs
fn fmt_matches(
n: usize,
i: &mut usize,
f: &mut std::fmt::Formatter
) -> std::fmt::Result {
use crate::{
fmt::ListDisplayWrapper,
xir::fmt::EleSumList,
};
EleSumList::fmt_nth(n, *i, &Self::matcher(), f)?;
*i += 1;
Ok(())
}
#[allow(dead_code)] // used by sum parser
fn cfg(&self) -> crate::xir::parse::EleParseCfg {
use $nt::*;
@ -1026,13 +1060,6 @@ macro_rules! ele_parse {
};
(@!ele_dfn_sum <$objty:ty> $vis:vis $super:ident $nt:ident [$($ntref:ident)*]) => {
$(
// Provide a (hopefully) helpful error that can be corrected
// rather than any obscure errors that may follow from trying
// to compose parsers that were not generated with this macro.
assert_impl_all!($ntref: crate::xir::parse::EleParseState);
)*
paste::paste! {
#[doc=concat!(
"Parser expecting one of ",
@ -1089,6 +1116,46 @@ macro_rules! ele_parse {
false $(|| $ntref::matches(qname))*
}
// Number of
// [`NodeMatcher`](crate::xir::parse::NodeMatcher)s
// considered by this parser.
//
// This is the sum of the number of matches of each
// constituent NT.
const fn matches_n() -> usize {
// Count the number of NTs by adding the number of
// matches in each.
0 $(+ $ntref::matches_n())*
}
/// Format constituent NTs for display.
///
/// This function receives the number of values to be
/// formatted as `n` and the current 0-indexed offset
/// within that list as `i`.
/// This allows for zero-copy rendering of composable NTs.
///
/// See also [`Self::fmt_matches_top`] to initialize the
/// formatting process with the correct values.
fn fmt_matches(
n: usize,
i: &mut usize,
f: &mut std::fmt::Formatter
) -> std::fmt::Result {
$(
$ntref::fmt_matches(n, i, f)?;
)*
Ok(())
}
/// Begin formatting using [`Self::fmt_matches`].
///
/// This provides the initial values for the function.
fn fmt_matches_top(f: &mut std::fmt::Formatter) -> std::fmt::Result {
Self::fmt_matches(Self::matches_n().saturating_sub(1), &mut 0, f)
}
fn cfg(&self) -> crate::xir::parse::EleParseCfg {
use $nt::*;
@ -1139,33 +1206,34 @@ macro_rules! ele_parse {
impl std::fmt::Display for $nt {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use crate::{
fmt::{DisplayWrapper, ListDisplayWrapper, TtQuote},
xir::fmt::EleSumList,
fmt::{DisplayWrapper, TtQuote},
};
let ntrefs = [
$(
$ntref::matcher(),
)*
];
let expected = EleSumList::wrap(&ntrefs);
match self {
Self::Expecting_(_)
| Self::NonPreemptableExpecting_(_) => {
write!(f, "expecting {expected}")
write!(f, "expecting ")?;
Self::fmt_matches_top(f)
},
Self::RecoverEleIgnore_(_, name, _, _)
| Self::RecoverEleIgnoreClosed_(_, name, _) => write!(
f,
"attempting to recover by ignoring element \
with unexpected name {given} \
(expected {expected})",
given = TtQuote::wrap(name),
),
| Self::RecoverEleIgnoreClosed_(_, name, _) => {
write!(
f,
"attempting to recover by ignoring element \
with unexpected name {given} \
(expected",
given = TtQuote::wrap(name),
)?;
Self::Done_ => write!(f, "done parsing {expected}"),
Self::fmt_matches_top(f)?;
f.write_str(")")
}
Self::Done_ => {
write!(f, "done parsing ")?;
Self::fmt_matches_top(f)
},
}
}
}
@ -1207,17 +1275,9 @@ macro_rules! ele_parse {
fn describe(&self) -> Vec<crate::diagnose::AnnotatedSpan> {
use crate::{
diagnose::Annotate,
fmt::{DisplayWrapper, ListDisplayWrapper, TtQuote},
xir::fmt::EleSumList,
fmt::{DisplayWrapper, TtQuote, DisplayFn},
};
let ntrefs = [
$(
$ntref::matcher(),
)*
];
let expected = EleSumList::wrap(&ntrefs);
match self {
Self::UnexpectedEle_(qname, span) => {
span
@ -1225,7 +1285,10 @@ macro_rules! ele_parse {
"element {name} cannot appear here",
name = TtQuote::wrap(qname),
))
.with_help(format!("expecting {expected}"))
.with_help(format!(
"expecting {}",
DisplayFn($nt::fmt_matches_top)
))
.into()
},
}