Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ? macro repetition #47752

Merged
merged 18 commits into from
Feb 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: can we add assert_eq!(op, KleeneOp::ZeroOrOne); here? I find it helps catch mistakes when later somebody messes with the arms accidentally....

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