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-7145main
parent
fd3184c795
commit
4c86c5b63c
|
@ -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()
|
||||
},
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue