Skip to content

Commit b8398d9

Browse files
committed
Auto merge of #47752 - mark-i-m:at-most-once-rep, r=nikomatsakis
Implement `?` macro repetition See rust-lang/rfcs#2298 (with disposition merge)
2 parents 0196b20 + b92e542 commit b8398d9

File tree

12 files changed

+374
-56
lines changed

12 files changed

+374
-56
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# `macro_at_most_once_rep`
2+
3+
The tracking issue for this feature is: TODO(mark-i-m)
4+
5+
With this feature gate enabled, one can use `?` as a Kleene operator meaning "0
6+
or 1 repetitions" in a macro definition. Previously only `+` and `*` were allowed.
7+
8+
For example:
9+
```rust
10+
macro_rules! foo {
11+
(something $(,)?) // `?` indicates `,` is "optional"...
12+
=> {}
13+
}
14+
```
15+
16+
------------------------
17+

src/libsyntax/ext/tt/macro_parser.rs

+10-5
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ struct MatcherPos {
181181
match_hi: usize,
182182

183183
// Specifically used if we are matching a repetition. If we aren't both should be `None`.
184+
/// The KleeneOp of this sequence if we are in a repetition.
185+
seq_op: Option<quoted::KleeneOp>,
184186
/// The separator if we are in a repetition
185187
sep: Option<Token>,
186188
/// The "parent" matcher position if we are in a repetition. That is, the matcher position just
@@ -263,6 +265,7 @@ fn initial_matcher_pos(ms: Vec<TokenTree>, lo: BytePos) -> Box<MatcherPos> {
263265
stack: vec![],
264266

265267
// Haven't descended into any sequences, so both of these are `None`.
268+
seq_op: None,
266269
sep: None,
267270
up: None,
268271
})
@@ -466,8 +469,8 @@ fn inner_parse_loop(
466469
}
467470
}
468471
// We don't need a separator. Move the "dot" back to the beginning of the matcher
469-
// and try to match again.
470-
else {
472+
// and try to match again UNLESS we are only allowed to have _one_ repetition.
473+
else if item.seq_op != Some(quoted::KleeneOp::ZeroOrOne) {
471474
item.match_cur = item.match_lo;
472475
item.idx = 0;
473476
cur_items.push(item);
@@ -486,8 +489,10 @@ fn inner_parse_loop(
486489
match item.top_elts.get_tt(idx) {
487490
// Need to descend into a sequence
488491
TokenTree::Sequence(sp, seq) => {
489-
if seq.op == quoted::KleeneOp::ZeroOrMore {
490-
// Examine the case where there are 0 matches of this sequence
492+
// Examine the case where there are 0 matches of this sequence
493+
if seq.op == quoted::KleeneOp::ZeroOrMore
494+
|| seq.op == quoted::KleeneOp::ZeroOrOne
495+
{
491496
let mut new_item = item.clone();
492497
new_item.match_cur += seq.num_captures;
493498
new_item.idx += 1;
@@ -497,11 +502,11 @@ fn inner_parse_loop(
497502
cur_items.push(new_item);
498503
}
499504

500-
// Examine the case where there is at least one match of this sequence
501505
let matches = create_matches(item.matches.len());
502506
cur_items.push(Box::new(MatcherPos {
503507
stack: vec![],
504508
sep: seq.separator.clone(),
509+
seq_op: Some(seq.op),
505510
idx: 0,
506511
matches,
507512
match_lo: item.match_cur,

src/libsyntax/ext/tt/macro_rules.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,8 @@ pub fn compile(sess: &ParseSess, features: &RefCell<Features>, def: &ast::Item)
237237
s.iter().map(|m| {
238238
if let MatchedNonterminal(ref nt) = *m {
239239
if let NtTT(ref tt) = **nt {
240-
let tt = quoted::parse(tt.clone().into(), true, sess).pop().unwrap();
240+
let tt = quoted::parse(tt.clone().into(), true, sess, features, &def.attrs)
241+
.pop().unwrap();
241242
valid &= check_lhs_nt_follows(sess, features, &def.attrs, &tt);
242243
return tt;
243244
}
@@ -253,7 +254,8 @@ pub fn compile(sess: &ParseSess, features: &RefCell<Features>, def: &ast::Item)
253254
s.iter().map(|m| {
254255
if let MatchedNonterminal(ref nt) = *m {
255256
if let NtTT(ref tt) = **nt {
256-
return quoted::parse(tt.clone().into(), false, sess).pop().unwrap();
257+
return quoted::parse(tt.clone().into(), false, sess, features, &def.attrs)
258+
.pop().unwrap();
257259
}
258260
}
259261
sess.span_diagnostic.span_bug(def.span, "wrong-structured lhs")

src/libsyntax/ext/tt/quoted.rs

+157-46
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
use ast;
11+
use {ast, attr};
1212
use ext::tt::macro_parser;
13+
use feature_gate::{self, emit_feature_err, Features, GateIssue};
1314
use parse::{token, ParseSess};
1415
use print::pprust;
1516
use symbol::keywords;
1617
use syntax_pos::{BytePos, Span, DUMMY_SP};
1718
use tokenstream;
1819

20+
use std::cell::RefCell;
21+
use std::iter::Peekable;
1922
use std::rc::Rc;
2023

2124
/// Contains the sub-token-trees of a "delimited" token tree, such as the contents of `(`. Note
@@ -78,6 +81,7 @@ pub enum KleeneOp {
7881
ZeroOrMore,
7982
/// Kleene plus (`+`) for one or more repetitions
8083
OneOrMore,
84+
ZeroOrOne,
8185
}
8286

8387
/// Similar to `tokenstream::TokenTree`, except that `$i`, `$i:ident`, and `$(...)`
@@ -169,6 +173,8 @@ impl TokenTree {
169173
/// `ident` are "matchers". They are not present in the body of a macro rule -- just in the
170174
/// pattern, so we pass a parameter to indicate whether to expect them or not.
171175
/// - `sess`: the parsing session. Any errors will be emitted to this session.
176+
/// - `features`, `attrs`: language feature flags and attributes so that we know whether to use
177+
/// unstable features or not.
172178
///
173179
/// # Returns
174180
///
@@ -177,18 +183,19 @@ pub fn parse(
177183
input: tokenstream::TokenStream,
178184
expect_matchers: bool,
179185
sess: &ParseSess,
186+
features: &RefCell<Features>,
187+
attrs: &[ast::Attribute],
180188
) -> Vec<TokenTree> {
181189
// Will contain the final collection of `self::TokenTree`
182190
let mut result = Vec::new();
183191

184192
// For each token tree in `input`, parse the token into a `self::TokenTree`, consuming
185193
// additional trees if need be.
186-
let mut trees = input.trees();
194+
let mut trees = input.trees().peekable();
187195
while let Some(tree) = trees.next() {
188-
let tree = parse_tree(tree, &mut trees, expect_matchers, sess);
189-
190196
// Given the parsed tree, if there is a metavar and we are expecting matchers, actually
191197
// parse out the matcher (i.e. in `$id:ident` this would parse the `:` and `ident`).
198+
let tree = parse_tree(tree, &mut trees, expect_matchers, sess, features, attrs);
192199
match tree {
193200
TokenTree::MetaVar(start_sp, ident) if expect_matchers => {
194201
let span = match trees.next() {
@@ -237,11 +244,15 @@ pub fn parse(
237244
/// converting `tree`
238245
/// - `expect_matchers`: same as for `parse` (see above).
239246
/// - `sess`: the parsing session. Any errors will be emitted to this session.
247+
/// - `features`, `attrs`: language feature flags and attributes so that we know whether to use
248+
/// unstable features or not.
240249
fn parse_tree<I>(
241250
tree: tokenstream::TokenTree,
242-
trees: &mut I,
251+
trees: &mut Peekable<I>,
243252
expect_matchers: bool,
244253
sess: &ParseSess,
254+
features: &RefCell<Features>,
255+
attrs: &[ast::Attribute],
245256
) -> TokenTree
246257
where
247258
I: Iterator<Item = tokenstream::TokenTree>,
@@ -260,9 +271,9 @@ where
260271
sess.span_diagnostic.span_err(span, &msg);
261272
}
262273
// Parse the contents of the sequence itself
263-
let sequence = parse(delimited.tts.into(), expect_matchers, sess);
274+
let sequence = parse(delimited.tts.into(), expect_matchers, sess, features, attrs);
264275
// Get the Kleene operator and optional separator
265-
let (separator, op) = parse_sep_and_kleene_op(trees, span, sess);
276+
let (separator, op) = parse_sep_and_kleene_op(trees, span, sess, features, attrs);
266277
// Count the number of captured "names" (i.e. named metavars)
267278
let name_captures = macro_parser::count_names(&sequence);
268279
TokenTree::Sequence(
@@ -315,12 +326,46 @@ where
315326
span,
316327
Rc::new(Delimited {
317328
delim: delimited.delim,
318-
tts: parse(delimited.tts.into(), expect_matchers, sess),
329+
tts: parse(delimited.tts.into(), expect_matchers, sess, features, attrs),
319330
}),
320331
),
321332
}
322333
}
323334

335+
/// Takes a token and returns `Some(KleeneOp)` if the token is `+` `*` or `?`. Otherwise, return
336+
/// `None`.
337+
fn kleene_op(token: &token::Token) -> Option<KleeneOp> {
338+
match *token {
339+
token::BinOp(token::Star) => Some(KleeneOp::ZeroOrMore),
340+
token::BinOp(token::Plus) => Some(KleeneOp::OneOrMore),
341+
token::Question => Some(KleeneOp::ZeroOrOne),
342+
_ => None,
343+
}
344+
}
345+
346+
/// Parse the next token tree of the input looking for a KleeneOp. Returns
347+
///
348+
/// - Ok(Ok(op)) if the next token tree is a KleeneOp
349+
/// - Ok(Err(tok, span)) if the next token tree is a token but not a KleeneOp
350+
/// - Err(span) if the next token tree is not a token
351+
fn parse_kleene_op<I>(
352+
input: &mut I,
353+
span: Span,
354+
) -> Result<Result<KleeneOp, (token::Token, Span)>, Span>
355+
where
356+
I: Iterator<Item = tokenstream::TokenTree>,
357+
{
358+
match input.next() {
359+
Some(tokenstream::TokenTree::Token(span, tok)) => match kleene_op(&tok) {
360+
Some(op) => Ok(Ok(op)),
361+
None => Ok(Err((tok, span))),
362+
},
363+
tree => Err(tree.as_ref()
364+
.map(tokenstream::TokenTree::span)
365+
.unwrap_or(span)),
366+
}
367+
}
368+
324369
/// Attempt to parse a single Kleene star, possibly with a separator.
325370
///
326371
/// For example, in a pattern such as `$(a),*`, `a` is the pattern to be repeated, `,` is the
@@ -334,55 +379,121 @@ where
334379
/// operator and separator, then a tuple with `(separator, KleeneOp)` is returned. Otherwise, an
335380
/// error with the appropriate span is emitted to `sess` and a dummy value is returned.
336381
fn parse_sep_and_kleene_op<I>(
337-
input: &mut I,
382+
input: &mut Peekable<I>,
338383
span: Span,
339384
sess: &ParseSess,
385+
features: &RefCell<Features>,
386+
attrs: &[ast::Attribute],
340387
) -> (Option<token::Token>, KleeneOp)
341388
where
342389
I: Iterator<Item = tokenstream::TokenTree>,
343390
{
344-
fn kleene_op(token: &token::Token) -> Option<KleeneOp> {
345-
match *token {
346-
token::BinOp(token::Star) => Some(KleeneOp::ZeroOrMore),
347-
token::BinOp(token::Plus) => Some(KleeneOp::OneOrMore),
348-
_ => None,
391+
// We basically look at two token trees here, denoted as #1 and #2 below
392+
let span = match parse_kleene_op(input, span) {
393+
// #1 is a `+` or `*` KleeneOp
394+
//
395+
// `?` is ambiguous: it could be a separator or a Kleene::ZeroOrOne, so we need to look
396+
// ahead one more token to be sure.
397+
Ok(Ok(op)) if op != KleeneOp::ZeroOrOne => return (None, op),
398+
399+
// #1 is `?` token, but it could be a Kleene::ZeroOrOne without a separator or it could
400+
// be a `?` separator followed by any Kleene operator. We need to look ahead 1 token to
401+
// find out which.
402+
Ok(Ok(op)) => {
403+
assert_eq!(op, KleeneOp::ZeroOrOne);
404+
405+
// Lookahead at #2. If it is a KleenOp, then #1 is a separator.
406+
let is_1_sep = if let Some(&tokenstream::TokenTree::Token(_, ref tok2)) = input.peek() {
407+
kleene_op(tok2).is_some()
408+
} else {
409+
false
410+
};
411+
412+
if is_1_sep {
413+
// #1 is a separator and #2 should be a KleepeOp::*
414+
// (N.B. We need to advance the input iterator.)
415+
match parse_kleene_op(input, span) {
416+
// #2 is a KleeneOp (this is the only valid option) :)
417+
Ok(Ok(op)) if op == KleeneOp::ZeroOrOne => {
418+
if !features.borrow().macro_at_most_once_rep
419+
&& !attr::contains_name(attrs, "allow_internal_unstable")
420+
{
421+
let explain = feature_gate::EXPLAIN_MACRO_AT_MOST_ONCE_REP;
422+
emit_feature_err(
423+
sess,
424+
"macro_at_most_once_rep",
425+
span,
426+
GateIssue::Language,
427+
explain,
428+
);
429+
}
430+
return (Some(token::Question), op);
431+
}
432+
Ok(Ok(op)) => return (Some(token::Question), op),
433+
434+
// #2 is a random token (this is an error) :(
435+
Ok(Err((_, span))) => span,
436+
437+
// #2 is not even a token at all :(
438+
Err(span) => span,
439+
}
440+
} else {
441+
if !features.borrow().macro_at_most_once_rep
442+
&& !attr::contains_name(attrs, "allow_internal_unstable")
443+
{
444+
let explain = feature_gate::EXPLAIN_MACRO_AT_MOST_ONCE_REP;
445+
emit_feature_err(
446+
sess,
447+
"macro_at_most_once_rep",
448+
span,
449+
GateIssue::Language,
450+
explain,
451+
);
452+
}
453+
454+
// #2 is a random tree and #1 is KleeneOp::ZeroOrOne
455+
return (None, op);
456+
}
349457
}
350-
}
351458

352-
// We attempt to look at the next two token trees in `input`. I will call the first #1 and the
353-
// second #2. If #1 and #2 don't match a valid KleeneOp with/without separator, that is an
354-
// error, and we should emit an error on the most specific span possible.
355-
let span = match input.next() {
356-
// #1 is a token
357-
Some(tokenstream::TokenTree::Token(span, tok)) => match kleene_op(&tok) {
358-
// #1 is a KleeneOp with no separator
359-
Some(op) => return (None, op),
360-
361-
// #1 is not a KleeneOp, but may be a separator... need to look at #2
362-
None => match input.next() {
363-
// #2 is a token
364-
Some(tokenstream::TokenTree::Token(span, tok2)) => match kleene_op(&tok2) {
365-
// #2 is a KleeneOp, so #1 must be a separator
366-
Some(op) => return (Some(tok), op),
367-
368-
// #2 is not a KleeneOp... error
369-
None => span,
370-
},
371-
372-
// #2 is not a token at all... error
373-
tree => tree.as_ref()
374-
.map(tokenstream::TokenTree::span)
375-
.unwrap_or(span),
376-
},
459+
// #1 is a separator followed by #2, a KleeneOp
460+
Ok(Err((tok, span))) => match parse_kleene_op(input, span) {
461+
// #2 is a KleeneOp :D
462+
Ok(Ok(op)) if op == KleeneOp::ZeroOrOne => {
463+
if !features.borrow().macro_at_most_once_rep
464+
&& !attr::contains_name(attrs, "allow_internal_unstable")
465+
{
466+
let explain = feature_gate::EXPLAIN_MACRO_AT_MOST_ONCE_REP;
467+
emit_feature_err(
468+
sess,
469+
"macro_at_most_once_rep",
470+
span,
471+
GateIssue::Language,
472+
explain,
473+
);
474+
}
475+
return (Some(tok), op);
476+
}
477+
Ok(Ok(op)) => return (Some(tok), op),
478+
479+
// #2 is a random token :(
480+
Ok(Err((_, span))) => span,
481+
482+
// #2 is not a token at all :(
483+
Err(span) => span,
377484
},
378485

379-
// #1 is not a token at all... error
380-
tree => tree.as_ref()
381-
.map(tokenstream::TokenTree::span)
382-
.unwrap_or(span),
486+
// #1 is not a token
487+
Err(span) => span,
383488
};
384489

385-
// Error...
386-
sess.span_diagnostic.span_err(span, "expected `*` or `+`");
490+
if !features.borrow().macro_at_most_once_rep
491+
&& !attr::contains_name(attrs, "allow_internal_unstable")
492+
{
493+
sess.span_diagnostic
494+
.span_err(span, "expected one of: `*`, `+`, or `?`");
495+
} else {
496+
sess.span_diagnostic.span_err(span, "expected `*` or `+`");
497+
}
387498
(None, KleeneOp::ZeroOrMore)
388499
}

0 commit comments

Comments
 (0)