Skip to content

Commit

Permalink
Implement ignore and index metavar expression
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonas Schievink committed Jul 11, 2022
1 parent f7bb932 commit df66eb7
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 5 deletions.
18 changes: 18 additions & 0 deletions crates/hir-def/src/macro_expansion_tests/mbe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1612,3 +1612,21 @@ impl Foo {
"#]],
)
}

#[test]
fn test_metavar_exprs() {
check(
r#"
macro_rules! m {
( $( $t:tt )* ) => ( $( ${ignore(t)} -${index()} )-* );
}
const _: i32 = m!(a b c);
"#,
expect![[r#"
macro_rules! m {
( $( $t:tt )* ) => ( $( ${ignore(t)} -${index()} )-* );
}
const _: i32 = -0--1--2;
"#]],
);
}
8 changes: 8 additions & 0 deletions crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ macro_rules! m {
($($i:ident)*) => ($_);
($($true:ident)*) => ($true);
($($false:ident)*) => ($false);
(double_dollar) => ($$);
($) => (m!($););
($($t:tt)*) => ($( ${ignore(t)} ${index()} )-*);
}
m!($);
"#,
Expand All @@ -29,7 +31,9 @@ macro_rules! m {
($($i:ident)*) => ($_);
($($true:ident)*) => ($true);
($($false:ident)*) => ($false);
(double_dollar) => ($$);
($) => (m!($););
($($t:tt)*) => ($( ${ignore(t)} ${index()} )-*);
}
m!($);
"#]],
Expand Down Expand Up @@ -59,6 +63,8 @@ f3!();
macro_rules! m1 { ($$i) => () }
m1!();
macro_rules! m2 { () => ( ${invalid()} ) }
m2!();
"#,
expect![[r#"
macro_rules! i1 { invalid }
Expand All @@ -80,6 +86,8 @@ macro_rules! f3 { ($i:_) => () }
macro_rules! m1 { ($$i) => () }
/* error: invalid macro definition: `$$` is not allowed on the pattern side */
macro_rules! m2 { () => ( ${invalid()} ) }
/* error: invalid macro definition: invalid metavariable expression */
"#]],
)
}
Expand Down
1 change: 1 addition & 0 deletions crates/mbe/src/benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ fn invocation_fixtures(rules: &FxHashMap<String, DeclarativeMacro>) -> Vec<(Stri
});
parent.token_trees.push(subtree.into());
}
Op::Ignore { .. } | Op::Index { .. } => {}
};

// Simple linear congruential generator for determistic result
Expand Down
2 changes: 2 additions & 0 deletions crates/mbe/src/expander/matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,7 @@ fn match_loop_inner<'t>(
}
try_push!(next_items, item);
}
OpDelimited::Op(Op::Ignore { .. } | Op::Index { .. }) => {}
OpDelimited::Open => {
if matches!(src.clone().next(), Some(tt::TokenTree::Subtree(..))) {
item.dot.next();
Expand Down Expand Up @@ -747,6 +748,7 @@ fn collect_vars(collector_fun: &mut impl FnMut(SmolStr), pattern: &MetaTemplate)
Op::Leaf(_) => (),
Op::Subtree { tokens, .. } => collect_vars(collector_fun, tokens),
Op::Repeat { tokens, .. } => collect_vars(collector_fun, tokens),
Op::Ignore { .. } | Op::Index { .. } => {}
}
}
}
Expand Down
17 changes: 17 additions & 0 deletions crates/mbe/src/expander/transcriber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,23 @@ fn expand_subtree(
err = err.or(e);
push_fragment(arena, fragment)
}
Op::Ignore { name, id } => {
// Expand the variable, but ignore the result. This registers the repetition count.
expand_var(ctx, name, *id);
}
Op::Index { depth } => {
let index = ctx
.nesting
.get(ctx.nesting.len() - 1 - (*depth as usize))
.map_or(0, |nest| nest.idx);
arena.push(
tt::Leaf::Literal(tt::Literal {
text: index.to_string().into(),
id: tt::TokenId::unspecified(),
})
.into(),
);
}
}
}
// drain the elements added in this instance of expand_subtree
Expand Down
60 changes: 55 additions & 5 deletions crates/mbe/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ impl MetaTemplate {
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum Op {
Var { name: SmolStr, kind: Option<SmolStr>, id: tt::TokenId },
Ignore { name: SmolStr, id: tt::TokenId },
Index { depth: u32 },
Repeat { tokens: MetaTemplate, kind: RepeatKind, separator: Option<Separator> },
Leaf(tt::Leaf),
Subtree { tokens: MetaTemplate, delimiter: Option<tt::Delimiter> },
Expand Down Expand Up @@ -113,11 +115,30 @@ fn next_op<'a>(first: &tt::TokenTree, src: &mut TtIter<'a>, mode: Mode) -> Resul
Some(it) => it,
};
match second {
tt::TokenTree::Subtree(subtree) => {
let (separator, kind) = parse_repeat(src)?;
let tokens = MetaTemplate::parse(subtree, mode)?;
Op::Repeat { tokens, separator, kind }
}
tt::TokenTree::Subtree(subtree) => match subtree.delimiter_kind() {
Some(tt::DelimiterKind::Parenthesis) => {
let (separator, kind) = parse_repeat(src)?;
let tokens = MetaTemplate::parse(subtree, mode)?;
Op::Repeat { tokens, separator, kind }
}
Some(tt::DelimiterKind::Brace) => match mode {
Mode::Template => {
parse_metavar_expr(&mut TtIter::new(subtree)).map_err(|()| {
ParseError::unexpected("invalid metavariable expression")
})?
}
Mode::Pattern => {
return Err(ParseError::unexpected(
"`${}` metavariable expressions are not allowed in matchers",
))
}
},
_ => {
return Err(ParseError::expected(
"expected `$()` repetition or `${}` expression",
))
}
},
tt::TokenTree::Leaf(leaf) => match leaf {
tt::Leaf::Ident(ident) if ident.text == "crate" => {
// We simply produce identifier `$crate` here. And it will be resolved when lowering ast to Path.
Expand Down Expand Up @@ -209,3 +230,32 @@ fn parse_repeat(src: &mut TtIter) -> Result<(Option<Separator>, RepeatKind), Par
}
Err(ParseError::InvalidRepeat)
}

fn parse_metavar_expr(src: &mut TtIter) -> Result<Op, ()> {
let func = src.expect_ident()?;
let args = src.expect_subtree()?;

if args.delimiter_kind() != Some(tt::DelimiterKind::Parenthesis) {
return Err(());
}

let mut args = TtIter::new(args);

let op = match &*func.text {
"ignore" => {
let ident = args.expect_ident()?;
Op::Ignore { name: ident.text.clone(), id: ident.id }
}
"index" => {
let depth = if args.len() == 0 { 0 } else { args.expect_u32_literal()? };
Op::Index { depth }
}
_ => return Err(()),
};

if args.next().is_some() {
return Err(());
}

Ok(op)
}
7 changes: 7 additions & 0 deletions crates/mbe/src/tt_iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ impl<'a> TtIter<'a> {
}
}

pub(crate) fn expect_u32_literal(&mut self) -> Result<u32, ()> {
match self.expect_literal()? {
tt::Leaf::Literal(lit) => lit.text.parse().map_err(drop),
_ => Err(()),
}
}

pub(crate) fn expect_punct(&mut self) -> Result<&'a tt::Punct, ()> {
match self.expect_leaf()? {
tt::Leaf::Punct(it) => Ok(it),
Expand Down

0 comments on commit df66eb7

Please sign in to comment.