Skip to content

Commit

Permalink
Auto merge of #47752 - mark-i-m:at-most-once-rep, r=nikomatsakis
Browse files Browse the repository at this point in the history
Implement `?` macro repetition

See rust-lang/rfcs#2298 (with disposition merge)
  • Loading branch information
bors committed Feb 11, 2018
2 parents 0196b20 + b92e542 commit b8398d9
Show file tree
Hide file tree
Showing 12 changed files with 374 additions and 56 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# `macro_at_most_once_rep`

The tracking issue for this feature is: TODO(mark-i-m)

With this feature gate enabled, one can use `?` as a Kleene operator meaning "0
or 1 repetitions" in a macro definition. Previously only `+` and `*` were allowed.

For example:
```rust
macro_rules! foo {
(something $(,)?) // `?` indicates `,` is "optional"...
=> {}
}
```

------------------------

15 changes: 10 additions & 5 deletions src/libsyntax/ext/tt/macro_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ struct MatcherPos {
match_hi: usize,

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

// Haven't descended into any sequences, so both of these are `None`.
seq_op: None,
sep: None,
up: None,
})
Expand Down Expand Up @@ -466,8 +469,8 @@ fn inner_parse_loop(
}
}
// We don't need a separator. Move the "dot" back to the beginning of the matcher
// and try to match again.
else {
// and try to match again UNLESS we are only allowed to have _one_ repetition.
else if item.seq_op != Some(quoted::KleeneOp::ZeroOrOne) {
item.match_cur = item.match_lo;
item.idx = 0;
cur_items.push(item);
Expand All @@ -486,8 +489,10 @@ fn inner_parse_loop(
match item.top_elts.get_tt(idx) {
// Need to descend into a sequence
TokenTree::Sequence(sp, seq) => {
if seq.op == quoted::KleeneOp::ZeroOrMore {
// Examine the case where there are 0 matches of this sequence
// Examine the case where there are 0 matches of this sequence
if seq.op == quoted::KleeneOp::ZeroOrMore
|| seq.op == quoted::KleeneOp::ZeroOrOne
{
let mut new_item = item.clone();
new_item.match_cur += seq.num_captures;
new_item.idx += 1;
Expand All @@ -497,11 +502,11 @@ fn inner_parse_loop(
cur_items.push(new_item);
}

// Examine the case where there is at least one match of this sequence
let matches = create_matches(item.matches.len());
cur_items.push(Box::new(MatcherPos {
stack: vec![],
sep: seq.separator.clone(),
seq_op: Some(seq.op),
idx: 0,
matches,
match_lo: item.match_cur,
Expand Down
6 changes: 4 additions & 2 deletions src/libsyntax/ext/tt/macro_rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,8 @@ pub fn compile(sess: &ParseSess, features: &RefCell<Features>, def: &ast::Item)
s.iter().map(|m| {
if let MatchedNonterminal(ref nt) = *m {
if let NtTT(ref tt) = **nt {
let tt = quoted::parse(tt.clone().into(), true, sess).pop().unwrap();
let tt = quoted::parse(tt.clone().into(), true, sess, features, &def.attrs)
.pop().unwrap();
valid &= check_lhs_nt_follows(sess, features, &def.attrs, &tt);
return tt;
}
Expand All @@ -253,7 +254,8 @@ pub fn compile(sess: &ParseSess, features: &RefCell<Features>, def: &ast::Item)
s.iter().map(|m| {
if let MatchedNonterminal(ref nt) = *m {
if let NtTT(ref tt) = **nt {
return quoted::parse(tt.clone().into(), false, sess).pop().unwrap();
return quoted::parse(tt.clone().into(), false, sess, features, &def.attrs)
.pop().unwrap();
}
}
sess.span_diagnostic.span_bug(def.span, "wrong-structured lhs")
Expand Down
203 changes: 157 additions & 46 deletions src/libsyntax/ext/tt/quoted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use ast;
use {ast, attr};
use ext::tt::macro_parser;
use feature_gate::{self, emit_feature_err, Features, GateIssue};
use parse::{token, ParseSess};
use print::pprust;
use symbol::keywords;
use syntax_pos::{BytePos, Span, DUMMY_SP};
use tokenstream;

use std::cell::RefCell;
use std::iter::Peekable;
use std::rc::Rc;

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

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

// For each token tree in `input`, parse the token into a `self::TokenTree`, consuming
// additional trees if need be.
let mut trees = input.trees();
let mut trees = input.trees().peekable();
while let Some(tree) = trees.next() {
let tree = parse_tree(tree, &mut trees, expect_matchers, sess);

// Given the parsed tree, if there is a metavar and we are expecting matchers, actually
// parse out the matcher (i.e. in `$id:ident` this would parse the `:` and `ident`).
let tree = parse_tree(tree, &mut trees, expect_matchers, sess, features, attrs);
match tree {
TokenTree::MetaVar(start_sp, ident) if expect_matchers => {
let span = match trees.next() {
Expand Down Expand Up @@ -237,11 +244,15 @@ pub fn parse(
/// converting `tree`
/// - `expect_matchers`: same as for `parse` (see above).
/// - `sess`: the parsing session. Any errors will be emitted to this session.
/// - `features`, `attrs`: language feature flags and attributes so that we know whether to use
/// unstable features or not.
fn parse_tree<I>(
tree: tokenstream::TokenTree,
trees: &mut I,
trees: &mut Peekable<I>,
expect_matchers: bool,
sess: &ParseSess,
features: &RefCell<Features>,
attrs: &[ast::Attribute],
) -> TokenTree
where
I: Iterator<Item = tokenstream::TokenTree>,
Expand All @@ -260,9 +271,9 @@ where
sess.span_diagnostic.span_err(span, &msg);
}
// Parse the contents of the sequence itself
let sequence = parse(delimited.tts.into(), expect_matchers, sess);
let sequence = parse(delimited.tts.into(), expect_matchers, sess, features, attrs);
// Get the Kleene operator and optional separator
let (separator, op) = parse_sep_and_kleene_op(trees, span, sess);
let (separator, op) = parse_sep_and_kleene_op(trees, span, sess, features, attrs);
// Count the number of captured "names" (i.e. named metavars)
let name_captures = macro_parser::count_names(&sequence);
TokenTree::Sequence(
Expand Down Expand Up @@ -315,12 +326,46 @@ where
span,
Rc::new(Delimited {
delim: delimited.delim,
tts: parse(delimited.tts.into(), expect_matchers, sess),
tts: parse(delimited.tts.into(), expect_matchers, sess, features, attrs),
}),
),
}
}

/// Takes a token and returns `Some(KleeneOp)` if the token is `+` `*` or `?`. Otherwise, return
/// `None`.
fn kleene_op(token: &token::Token) -> Option<KleeneOp> {
match *token {
token::BinOp(token::Star) => Some(KleeneOp::ZeroOrMore),
token::BinOp(token::Plus) => Some(KleeneOp::OneOrMore),
token::Question => Some(KleeneOp::ZeroOrOne),
_ => None,
}
}

/// Parse the next token tree of the input looking for a KleeneOp. Returns
///
/// - Ok(Ok(op)) if the next token tree is a KleeneOp
/// - Ok(Err(tok, span)) if the next token tree is a token but not a KleeneOp
/// - Err(span) if the next token tree is not a token
fn parse_kleene_op<I>(
input: &mut I,
span: Span,
) -> Result<Result<KleeneOp, (token::Token, Span)>, Span>
where
I: Iterator<Item = tokenstream::TokenTree>,
{
match input.next() {
Some(tokenstream::TokenTree::Token(span, tok)) => match kleene_op(&tok) {
Some(op) => Ok(Ok(op)),
None => Ok(Err((tok, span))),
},
tree => Err(tree.as_ref()
.map(tokenstream::TokenTree::span)
.unwrap_or(span)),
}
}

/// Attempt to parse a single Kleene star, possibly with a separator.
///
/// For example, in a pattern such as `$(a),*`, `a` is the pattern to be repeated, `,` is the
Expand All @@ -334,55 +379,121 @@ where
/// operator and separator, then a tuple with `(separator, KleeneOp)` is returned. Otherwise, an
/// error with the appropriate span is emitted to `sess` and a dummy value is returned.
fn parse_sep_and_kleene_op<I>(
input: &mut I,
input: &mut Peekable<I>,
span: Span,
sess: &ParseSess,
features: &RefCell<Features>,
attrs: &[ast::Attribute],
) -> (Option<token::Token>, KleeneOp)
where
I: Iterator<Item = tokenstream::TokenTree>,
{
fn kleene_op(token: &token::Token) -> Option<KleeneOp> {
match *token {
token::BinOp(token::Star) => Some(KleeneOp::ZeroOrMore),
token::BinOp(token::Plus) => Some(KleeneOp::OneOrMore),
_ => None,
// We basically look at two token trees here, denoted as #1 and #2 below
let span = match parse_kleene_op(input, span) {
// #1 is a `+` or `*` KleeneOp
//
// `?` is ambiguous: it could be a separator or a Kleene::ZeroOrOne, so we need to look
// ahead one more token to be sure.
Ok(Ok(op)) if op != KleeneOp::ZeroOrOne => return (None, op),

// #1 is `?` token, but it could be a Kleene::ZeroOrOne without a separator or it could
// be a `?` separator followed by any Kleene operator. We need to look ahead 1 token to
// find out which.
Ok(Ok(op)) => {
assert_eq!(op, KleeneOp::ZeroOrOne);

// Lookahead at #2. If it is a KleenOp, then #1 is a separator.
let is_1_sep = if let Some(&tokenstream::TokenTree::Token(_, ref tok2)) = input.peek() {
kleene_op(tok2).is_some()
} else {
false
};

if is_1_sep {
// #1 is a separator and #2 should be a KleepeOp::*
// (N.B. We need to advance the input iterator.)
match parse_kleene_op(input, span) {
// #2 is a KleeneOp (this is the only valid option) :)
Ok(Ok(op)) if op == KleeneOp::ZeroOrOne => {
if !features.borrow().macro_at_most_once_rep
&& !attr::contains_name(attrs, "allow_internal_unstable")
{
let explain = feature_gate::EXPLAIN_MACRO_AT_MOST_ONCE_REP;
emit_feature_err(
sess,
"macro_at_most_once_rep",
span,
GateIssue::Language,
explain,
);
}
return (Some(token::Question), op);
}
Ok(Ok(op)) => return (Some(token::Question), op),

// #2 is a random token (this is an error) :(
Ok(Err((_, span))) => span,

// #2 is not even a token at all :(
Err(span) => span,
}
} else {
if !features.borrow().macro_at_most_once_rep
&& !attr::contains_name(attrs, "allow_internal_unstable")
{
let explain = feature_gate::EXPLAIN_MACRO_AT_MOST_ONCE_REP;
emit_feature_err(
sess,
"macro_at_most_once_rep",
span,
GateIssue::Language,
explain,
);
}

// #2 is a random tree and #1 is KleeneOp::ZeroOrOne
return (None, op);
}
}
}

// We attempt to look at the next two token trees in `input`. I will call the first #1 and the
// second #2. If #1 and #2 don't match a valid KleeneOp with/without separator, that is an
// error, and we should emit an error on the most specific span possible.
let span = match input.next() {
// #1 is a token
Some(tokenstream::TokenTree::Token(span, tok)) => match kleene_op(&tok) {
// #1 is a KleeneOp with no separator
Some(op) => return (None, op),

// #1 is not a KleeneOp, but may be a separator... need to look at #2
None => match input.next() {
// #2 is a token
Some(tokenstream::TokenTree::Token(span, tok2)) => match kleene_op(&tok2) {
// #2 is a KleeneOp, so #1 must be a separator
Some(op) => return (Some(tok), op),

// #2 is not a KleeneOp... error
None => span,
},

// #2 is not a token at all... error
tree => tree.as_ref()
.map(tokenstream::TokenTree::span)
.unwrap_or(span),
},
// #1 is a separator followed by #2, a KleeneOp
Ok(Err((tok, span))) => match parse_kleene_op(input, span) {
// #2 is a KleeneOp :D
Ok(Ok(op)) if op == KleeneOp::ZeroOrOne => {
if !features.borrow().macro_at_most_once_rep
&& !attr::contains_name(attrs, "allow_internal_unstable")
{
let explain = feature_gate::EXPLAIN_MACRO_AT_MOST_ONCE_REP;
emit_feature_err(
sess,
"macro_at_most_once_rep",
span,
GateIssue::Language,
explain,
);
}
return (Some(tok), op);
}
Ok(Ok(op)) => return (Some(tok), op),

// #2 is a random token :(
Ok(Err((_, span))) => span,

// #2 is not a token at all :(
Err(span) => span,
},

// #1 is not a token at all... error
tree => tree.as_ref()
.map(tokenstream::TokenTree::span)
.unwrap_or(span),
// #1 is not a token
Err(span) => span,
};

// Error...
sess.span_diagnostic.span_err(span, "expected `*` or `+`");
if !features.borrow().macro_at_most_once_rep
&& !attr::contains_name(attrs, "allow_internal_unstable")
{
sess.span_diagnostic
.span_err(span, "expected one of: `*`, `+`, or `?`");
} else {
sess.span_diagnostic.span_err(span, "expected `*` or `+`");
}
(None, KleeneOp::ZeroOrMore)
}
Loading

0 comments on commit b8398d9

Please sign in to comment.