<commit_before>use super::*;

/// Represents a macro invocation. The Path indicates which macro
/// is being invoked, and the vector of token-trees contains the source
/// of the macro invocation.
///
/// NB: the additional ident for a `macro_rules`-style macro is actually
/// stored in the enclosing item. Oog.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Mac {
    pub path: Path,
    pub tts: Vec<TokenTree>,
}

/// When the main rust parser encounters a syntax-extension invocation, it
/// parses the arguments to the invocation as a token-tree. This is a very
/// loose structure, such that all sorts of different AST-fragments can
/// be passed to syntax extensions using a uniform type.
///
/// If the syntax extension is an MBE macro, it will attempt to match its
/// LHS token tree against the provided token tree, and if it finds a
/// match, will transcribe the RHS token tree, splicing in any captured
/// `macro_parser::matched_nonterminals` into the `SubstNt`s it finds.
///
/// The RHS of an MBE macro is the only place `SubstNt`s are substituted.
/// Nothing special happens to misnamed or misplaced `SubstNt`s.
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum TokenTree {
    /// A single token
    Token(Token),
    /// A delimited sequence of token trees
    Delimited(Delimited),
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Delimited {
    /// The type of delimiter
    pub delim: DelimToken,
    /// The delimited sequence of token trees
    pub tts: Vec<TokenTree>,
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Token {
    // Expression-operator symbols.
    Eq,
    Lt,
    Le,
    EqEq,
    Ne,
    Ge,
    Gt,
    AndAnd,
    OrOr,
    Not,
    Tilde,
    BinOp(BinOpToken),
    BinOpEq(BinOpToken),

    // Structural symbols
    At,
    Dot,
    DotDot,
    DotDotDot,
    Comma,
    Semi,
    Colon,
    ModSep,
    RArrow,
    LArrow,
    FatArrow,
    Pound,
    Dollar,
    Question,

    // Literals
    Literal(Lit),

    // Name components
    Ident(Ident),
    Underscore,
    Lifetime(Ident),

    DocComment(String),
}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum BinOpToken {
    Plus,
    Minus,
    Star,
    Slash,
    Percent,
    Caret,
    And,
    Or,
    Shl,
    Shr,
}

/// A delimiter token
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum DelimToken {
    /// A round parenthesis: `(` or `)`
    Paren,
    /// A square bracket: `[` or `]`
    Bracket,
    /// A curly brace: `{` or `}`
    Brace,
}

#[cfg(feature = "parsing")]
pub mod parsing {
    use super::*;
    use Lifetime;
    use generics::parsing::lifetime;
    use ident::parsing::ident;
    use lit::parsing::lit;
    use space::{block_comment, whitespace};

    named!(pub mac -> Mac, do_parse!(
        name: ident >>
        punct!("!") >>
        body: delimited >>
        (Mac {
            path: name.into(),
            tts: vec![TokenTree::Delimited(body)],
        })
    ));

    named!(pub delimited -> Delimited, alt!(
        delimited!(
            punct!("("),
            many0!(token_tree),
            punct!(")")
        ) => { |tts| Delimited { delim: DelimToken::Paren, tts: tts } }
        |
        delimited!(
            punct!("["),
            many0!(token_tree),
            punct!("]")
        ) => { |tts| Delimited { delim: DelimToken::Bracket, tts: tts } }
        |
        delimited!(
            punct!("{"),
            many0!(token_tree),
            punct!("}")
        ) => { |tts| Delimited { delim: DelimToken::Brace, tts: tts } }
    ));

    named!(token_tree -> TokenTree, alt!(
        map!(token, TokenTree::Token)
        |
        map!(delimited, TokenTree::Delimited)
    ));

    named!(token -> Token, alt!(
        punct!("_") => { |_| Token::Underscore }
        |
        punct!("&&") => { |_| Token::AndAnd } // must be before bin_op
        |
        punct!("||") => { |_| Token::OrOr } // must be before bin_op
        |
        map!(bin_op_eq, Token::BinOpEq)
        |
        map!(bin_op, Token::BinOp)
        |
        map!(lit, Token::Literal)
        |
        map!(ident, Token::Ident)
        |
        map!(lifetime, |lt: Lifetime| Token::Lifetime(lt.ident))
        |
        map!(doc_comment, Token::DocComment)
        |
        punct!("<=") => { |_| Token::Le }
        |
        punct!("==") => { |_| Token::EqEq }
        |
        punct!("!=") => { |_| Token::Ne }
        |
        punct!(">=") => { |_| Token::Ge }
        |
        punct!("::") => { |_| Token::ModSep }
        |
        punct!("->") => { |_| Token::RArrow }
        |
        punct!("<-") => { |_| Token::LArrow }
        |
        punct!("=>") => { |_| Token::FatArrow }
        |
        punct!("...") => { |_| Token::DotDotDot }
        |
        punct!("..") => { |_| Token::DotDot }
        |
        punct!(".") => { |_| Token::Dot }
        |
        punct!("=") => { |_| Token::Eq }
        |
        punct!("<") => { |_| Token::Lt }
        |
        punct!(">") => { |_| Token::Gt }
        |
        punct!("!") => { |_| Token::Not }
        |
        punct!("~") => { |_| Token::Tilde }
        |
        punct!("@") => { |_| Token::At }
        |
        punct!(",") => { |_| Token::Comma }
        |
        punct!(";") => { |_| Token::Semi }
        |
        punct!(":") => { |_| Token::Colon }
        |
        punct!("#") => { |_| Token::Pound }
        |
        punct!("$") => { |_| Token::Dollar }
        |
        punct!("?") => { |_| Token::Question }
    ));

    named!(bin_op -> BinOpToken, alt!(
        punct!("+") => { |_| BinOpToken::Plus }
        |
        punct!("-") => { |_| BinOpToken::Minus }
        |
        punct!("*") => { |_| BinOpToken::Star }
        |
        punct!("/") => { |_| BinOpToken::Slash }
        |
        punct!("%") => { |_| BinOpToken::Percent }
        |
        punct!("^") => { |_| BinOpToken::Caret }
        |
        punct!("&") => { |_| BinOpToken::And }
        |
        punct!("|") => { |_| BinOpToken::Or }
        |
        punct!("<<") => { |_| BinOpToken::Shl }
        |
        punct!(">>") => { |_| BinOpToken::Shr }
    ));

    named!(bin_op_eq -> BinOpToken, alt!(
        punct!("+=") => { |_| BinOpToken::Plus }
        |
        punct!("-=") => { |_| BinOpToken::Minus }
        |
        punct!("*=") => { |_| BinOpToken::Star }
        |
        punct!("/=") => { |_| BinOpToken::Slash }
        |
        punct!("%=") => { |_| BinOpToken::Percent }
        |
        punct!("^=") => { |_| BinOpToken::Caret }
        |
        punct!("&=") => { |_| BinOpToken::And }
        |
        punct!("|=") => { |_| BinOpToken::Or }
        |
        punct!("<<=") => { |_| BinOpToken::Shl }
        |
        punct!(">>=") => { |_| BinOpToken::Shr }
    ));

    named!(doc_comment -> String, alt!(
        do_parse!(
            punct!("//!") >>
            content: take_until!("\n") >>
            (format!("//!{}", content))
        )
        |
        do_parse!(
            option!(whitespace) >>
            peek!(tag!("/*!")) >>
            com: block_comment >>
            (com.to_owned())
        )
        |
        do_parse!(
            punct!("///") >>
            not!(peek!(tag!("/"))) >>
            content: take_until!("\n") >>
            (format!("///{}", content))
        )
        |
        do_parse!(
            option!(whitespace) >>
            peek!(tuple!(tag!("/**"), not!(tag!("*")))) >>
            com: block_comment >>
            (com.to_owned())
        )
    ));
}

#[cfg(feature = "printing")]
mod printing {
    use super::*;
    use quote::{Tokens, ToTokens};

    impl ToTokens for Mac {
        fn to_tokens(&self, tokens: &mut Tokens) {
            self.path.to_tokens(tokens);
            tokens.append("!");
            for tt in &self.tts {
                tt.to_tokens(tokens);
            }
        }
    }

    impl ToTokens for TokenTree {
        fn to_tokens(&self, tokens: &mut Tokens) {
            match *self {
                TokenTree::Token(ref token) => token.to_tokens(tokens),
                TokenTree::Delimited(ref delimited) => delimited.to_tokens(tokens),
            }
        }
    }

    impl DelimToken {
        fn open(&self) -> &'static str {
            match *self {
                DelimToken::Paren => "(",
                DelimToken::Bracket => "[",
                DelimToken::Brace => "{",
            }
        }

        fn close(&self) -> &'static str {
            match *self {
                DelimToken::Paren => ")",
                DelimToken::Bracket => "]",
                DelimToken::Brace => "}",
            }
        }
    }

    impl ToTokens for Delimited {
        fn to_tokens(&self, tokens: &mut Tokens) {
            tokens.append(self.delim.open());
            for tt in &self.tts {
                tt.to_tokens(tokens);
            }
            tokens.append(self.delim.close());
        }
    }

    impl ToTokens for Token {
        fn to_tokens(&self, tokens: &mut Tokens) {
            match *self {
                Token::Eq => tokens.append("="),
                Token::Lt => tokens.append("<"),
                Token::Le => tokens.append("<="),
                Token::EqEq => tokens.append("=="),
                Token::Ne => tokens.append("!="),
                Token::Ge => tokens.append(">="),
                Token::Gt => tokens.append(">"),
                Token::AndAnd => tokens.append("&&"),
                Token::OrOr => tokens.append("||"),
                Token::Not => tokens.append("!"),
                Token::Tilde => tokens.append("~"),
                Token::BinOp(binop) => tokens.append(binop.op()),
                Token::BinOpEq(binop) => tokens.append(binop.assign_op()),
                Token::At => tokens.append("@"),
                Token::Dot => tokens.append("."),
                Token::DotDot => tokens.append(".."),
                Token::DotDotDot => tokens.append("..."),
                Token::Comma => tokens.append(","),
                Token::Semi => tokens.append(";"),
                Token::Colon => tokens.append(":"),
                Token::ModSep => tokens.append("::"),
                Token::RArrow => tokens.append("->"),
                Token::LArrow => tokens.append("<-"),
                Token::FatArrow => tokens.append("=>"),
                Token::Pound => tokens.append("#"),
                Token::Dollar => tokens.append("$"),
                Token::Question => tokens.append("?"),
                Token::Literal(ref lit) => lit.to_tokens(tokens),
                Token::Ident(ref ident) => ident.to_tokens(tokens),
                Token::Underscore => tokens.append("_"),
                Token::Lifetime(ref ident) => ident.to_tokens(tokens),
                Token::DocComment(ref com) => {
                    tokens.append(&format!("{}\n", com));
                }
            }
        }
    }

    impl BinOpToken {
        fn op(&self) -> &'static str {
            match *self {
                BinOpToken::Plus => "+",
                BinOpToken::Minus => "-",
                BinOpToken::Star => "*",
                BinOpToken::Slash => "/",
                BinOpToken::Percent => "%",
                BinOpToken::Caret => "^",
                BinOpToken::And => "&",
                BinOpToken::Or => "|",
                BinOpToken::Shl => "<<",
                BinOpToken::Shr => ">>",
            }
        }

        fn assign_op(&self) -> &'static str {
            match *self {
                BinOpToken::Plus => "+=",
                BinOpToken::Minus => "-=",
                BinOpToken::Star => "*=",
                BinOpToken::Slash => "/=",
                BinOpToken::Percent => "%=",
                BinOpToken::Caret => "^=",
                BinOpToken::And => "&=",
                BinOpToken::Or => "|=",
                BinOpToken::Shl => "<<=",
                BinOpToken::Shr => ">>=",
            }
        }
    }

    impl ToTokens for BinOpToken {
        fn to_tokens(&self, tokens: &mut Tokens) {
            tokens.append(self.op());
        }
    }
}
<commit_msg>Require word break after underscore token in macro<commit_after>use super::*;

/// Represents a macro invocation. The Path indicates which macro
/// is being invoked, and the vector of token-trees contains the source
/// of the macro invocation.
///
/// NB: the additional ident for a `macro_rules`-style macro is actually
/// stored in the enclosing item. Oog.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Mac {
    pub path: Path,
    pub tts: Vec<TokenTree>,
}

/// When the main rust parser encounters a syntax-extension invocation, it
/// parses the arguments to the invocation as a token-tree. This is a very
/// loose structure, such that all sorts of different AST-fragments can
/// be passed to syntax extensions using a uniform type.
///
/// If the syntax extension is an MBE macro, it will attempt to match its
/// LHS token tree against the provided token tree, and if it finds a
/// match, will transcribe the RHS token tree, splicing in any captured
/// `macro_parser::matched_nonterminals` into the `SubstNt`s it finds.
///
/// The RHS of an MBE macro is the only place `SubstNt`s are substituted.
/// Nothing special happens to misnamed or misplaced `SubstNt`s.
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum TokenTree {
    /// A single token
    Token(Token),
    /// A delimited sequence of token trees
    Delimited(Delimited),
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Delimited {
    /// The type of delimiter
    pub delim: DelimToken,
    /// The delimited sequence of token trees
    pub tts: Vec<TokenTree>,
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Token {
    // Expression-operator symbols.
    Eq,
    Lt,
    Le,
    EqEq,
    Ne,
    Ge,
    Gt,
    AndAnd,
    OrOr,
    Not,
    Tilde,
    BinOp(BinOpToken),
    BinOpEq(BinOpToken),

    // Structural symbols
    At,
    Dot,
    DotDot,
    DotDotDot,
    Comma,
    Semi,
    Colon,
    ModSep,
    RArrow,
    LArrow,
    FatArrow,
    Pound,
    Dollar,
    Question,

    // Literals
    Literal(Lit),

    // Name components
    Ident(Ident),
    Underscore,
    Lifetime(Ident),

    DocComment(String),
}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum BinOpToken {
    Plus,
    Minus,
    Star,
    Slash,
    Percent,
    Caret,
    And,
    Or,
    Shl,
    Shr,
}

/// A delimiter token
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum DelimToken {
    /// A round parenthesis: `(` or `)`
    Paren,
    /// A square bracket: `[` or `]`
    Bracket,
    /// A curly brace: `{` or `}`
    Brace,
}

#[cfg(feature = "parsing")]
pub mod parsing {
    use super::*;
    use Lifetime;
    use generics::parsing::lifetime;
    use ident::parsing::ident;
    use lit::parsing::lit;
    use space::{block_comment, whitespace};

    named!(pub mac -> Mac, do_parse!(
        name: ident >>
        punct!("!") >>
        body: delimited >>
        (Mac {
            path: name.into(),
            tts: vec![TokenTree::Delimited(body)],
        })
    ));

    named!(pub delimited -> Delimited, alt!(
        delimited!(
            punct!("("),
            many0!(token_tree),
            punct!(")")
        ) => { |tts| Delimited { delim: DelimToken::Paren, tts: tts } }
        |
        delimited!(
            punct!("["),
            many0!(token_tree),
            punct!("]")
        ) => { |tts| Delimited { delim: DelimToken::Bracket, tts: tts } }
        |
        delimited!(
            punct!("{"),
            many0!(token_tree),
            punct!("}")
        ) => { |tts| Delimited { delim: DelimToken::Brace, tts: tts } }
    ));

    named!(token_tree -> TokenTree, alt!(
        map!(token, TokenTree::Token)
        |
        map!(delimited, TokenTree::Delimited)
    ));

    named!(token -> Token, alt!(
        keyword!("_") => { |_| Token::Underscore }
        |
        punct!("&&") => { |_| Token::AndAnd } // must be before bin_op
        |
        punct!("||") => { |_| Token::OrOr } // must be before bin_op
        |
        map!(bin_op_eq, Token::BinOpEq)
        |
        map!(bin_op, Token::BinOp)
        |
        map!(lit, Token::Literal)
        |
        map!(ident, Token::Ident)
        |
        map!(lifetime, |lt: Lifetime| Token::Lifetime(lt.ident))
        |
        map!(doc_comment, Token::DocComment)
        |
        punct!("<=") => { |_| Token::Le }
        |
        punct!("==") => { |_| Token::EqEq }
        |
        punct!("!=") => { |_| Token::Ne }
        |
        punct!(">=") => { |_| Token::Ge }
        |
        punct!("::") => { |_| Token::ModSep }
        |
        punct!("->") => { |_| Token::RArrow }
        |
        punct!("<-") => { |_| Token::LArrow }
        |
        punct!("=>") => { |_| Token::FatArrow }
        |
        punct!("...") => { |_| Token::DotDotDot }
        |
        punct!("..") => { |_| Token::DotDot }
        |
        punct!(".") => { |_| Token::Dot }
        |
        punct!("=") => { |_| Token::Eq }
        |
        punct!("<") => { |_| Token::Lt }
        |
        punct!(">") => { |_| Token::Gt }
        |
        punct!("!") => { |_| Token::Not }
        |
        punct!("~") => { |_| Token::Tilde }
        |
        punct!("@") => { |_| Token::At }
        |
        punct!(",") => { |_| Token::Comma }
        |
        punct!(";") => { |_| Token::Semi }
        |
        punct!(":") => { |_| Token::Colon }
        |
        punct!("#") => { |_| Token::Pound }
        |
        punct!("$") => { |_| Token::Dollar }
        |
        punct!("?") => { |_| Token::Question }
    ));

    named!(bin_op -> BinOpToken, alt!(
        punct!("+") => { |_| BinOpToken::Plus }
        |
        punct!("-") => { |_| BinOpToken::Minus }
        |
        punct!("*") => { |_| BinOpToken::Star }
        |
        punct!("/") => { |_| BinOpToken::Slash }
        |
        punct!("%") => { |_| BinOpToken::Percent }
        |
        punct!("^") => { |_| BinOpToken::Caret }
        |
        punct!("&") => { |_| BinOpToken::And }
        |
        punct!("|") => { |_| BinOpToken::Or }
        |
        punct!("<<") => { |_| BinOpToken::Shl }
        |
        punct!(">>") => { |_| BinOpToken::Shr }
    ));

    named!(bin_op_eq -> BinOpToken, alt!(
        punct!("+=") => { |_| BinOpToken::Plus }
        |
        punct!("-=") => { |_| BinOpToken::Minus }
        |
        punct!("*=") => { |_| BinOpToken::Star }
        |
        punct!("/=") => { |_| BinOpToken::Slash }
        |
        punct!("%=") => { |_| BinOpToken::Percent }
        |
        punct!("^=") => { |_| BinOpToken::Caret }
        |
        punct!("&=") => { |_| BinOpToken::And }
        |
        punct!("|=") => { |_| BinOpToken::Or }
        |
        punct!("<<=") => { |_| BinOpToken::Shl }
        |
        punct!(">>=") => { |_| BinOpToken::Shr }
    ));

    named!(doc_comment -> String, alt!(
        do_parse!(
            punct!("//!") >>
            content: take_until!("\n") >>
            (format!("//!{}", content))
        )
        |
        do_parse!(
            option!(whitespace) >>
            peek!(tag!("/*!")) >>
            com: block_comment >>
            (com.to_owned())
        )
        |
        do_parse!(
            punct!("///") >>
            not!(peek!(tag!("/"))) >>
            content: take_until!("\n") >>
            (format!("///{}", content))
        )
        |
        do_parse!(
            option!(whitespace) >>
            peek!(tuple!(tag!("/**"), not!(tag!("*")))) >>
            com: block_comment >>
            (com.to_owned())
        )
    ));
}

#[cfg(feature = "printing")]
mod printing {
    use super::*;
    use quote::{Tokens, ToTokens};

    impl ToTokens for Mac {
        fn to_tokens(&self, tokens: &mut Tokens) {
            self.path.to_tokens(tokens);
            tokens.append("!");
            for tt in &self.tts {
                tt.to_tokens(tokens);
            }
        }
    }

    impl ToTokens for TokenTree {
        fn to_tokens(&self, tokens: &mut Tokens) {
            match *self {
                TokenTree::Token(ref token) => token.to_tokens(tokens),
                TokenTree::Delimited(ref delimited) => delimited.to_tokens(tokens),
            }
        }
    }

    impl DelimToken {
        fn open(&self) -> &'static str {
            match *self {
                DelimToken::Paren => "(",
                DelimToken::Bracket => "[",
                DelimToken::Brace => "{",
            }
        }

        fn close(&self) -> &'static str {
            match *self {
                DelimToken::Paren => ")",
                DelimToken::Bracket => "]",
                DelimToken::Brace => "}",
            }
        }
    }

    impl ToTokens for Delimited {
        fn to_tokens(&self, tokens: &mut Tokens) {
            tokens.append(self.delim.open());
            for tt in &self.tts {
                tt.to_tokens(tokens);
            }
            tokens.append(self.delim.close());
        }
    }

    impl ToTokens for Token {
        fn to_tokens(&self, tokens: &mut Tokens) {
            match *self {
                Token::Eq => tokens.append("="),
                Token::Lt => tokens.append("<"),
                Token::Le => tokens.append("<="),
                Token::EqEq => tokens.append("=="),
                Token::Ne => tokens.append("!="),
                Token::Ge => tokens.append(">="),
                Token::Gt => tokens.append(">"),
                Token::AndAnd => tokens.append("&&"),
                Token::OrOr => tokens.append("||"),
                Token::Not => tokens.append("!"),
                Token::Tilde => tokens.append("~"),
                Token::BinOp(binop) => tokens.append(binop.op()),
                Token::BinOpEq(binop) => tokens.append(binop.assign_op()),
                Token::At => tokens.append("@"),
                Token::Dot => tokens.append("."),
                Token::DotDot => tokens.append(".."),
                Token::DotDotDot => tokens.append("..."),
                Token::Comma => tokens.append(","),
                Token::Semi => tokens.append(";"),
                Token::Colon => tokens.append(":"),
                Token::ModSep => tokens.append("::"),
                Token::RArrow => tokens.append("->"),
                Token::LArrow => tokens.append("<-"),
                Token::FatArrow => tokens.append("=>"),
                Token::Pound => tokens.append("#"),
                Token::Dollar => tokens.append("$"),
                Token::Question => tokens.append("?"),
                Token::Literal(ref lit) => lit.to_tokens(tokens),
                Token::Ident(ref ident) => ident.to_tokens(tokens),
                Token::Underscore => tokens.append("_"),
                Token::Lifetime(ref ident) => ident.to_tokens(tokens),
                Token::DocComment(ref com) => {
                    tokens.append(&format!("{}\n", com));
                }
            }
        }
    }

    impl BinOpToken {
        fn op(&self) -> &'static str {
            match *self {
                BinOpToken::Plus => "+",
                BinOpToken::Minus => "-",
                BinOpToken::Star => "*",
                BinOpToken::Slash => "/",
                BinOpToken::Percent => "%",
                BinOpToken::Caret => "^",
                BinOpToken::And => "&",
                BinOpToken::Or => "|",
                BinOpToken::Shl => "<<",
                BinOpToken::Shr => ">>",
            }
        }

        fn assign_op(&self) -> &'static str {
            match *self {
                BinOpToken::Plus => "+=",
                BinOpToken::Minus => "-=",
                BinOpToken::Star => "*=",
                BinOpToken::Slash => "/=",
                BinOpToken::Percent => "%=",
                BinOpToken::Caret => "^=",
                BinOpToken::And => "&=",
                BinOpToken::Or => "|=",
                BinOpToken::Shl => "<<=",
                BinOpToken::Shr => ">>=",
            }
        }
    }

    impl ToTokens for BinOpToken {
        fn to_tokens(&self, tokens: &mut Tokens) {
            tokens.append(self.op());
        }
    }
}
<|endoftext|>


<commit_before>use std::io::Write;
use std::io;
use std::str;

use utils::{find_byte, log_stderr_bytes};

#[derive(Debug)]
pub struct Msg {
    pub pfx     : Option<Pfx>,
    pub command : Command,
    pub params  : Vec<Vec<u8>>,
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Pfx {
    Server(String),
    User {
        nick : String,
        /// user@host
        user : String,
    },
}

#[derive(Debug)]
pub enum Command {
    Str(String),
    Num(u16),
}

impl Msg {
    /// Parse a complete IRC message. NOTE: The string should NOT have CR-NL.
    pub fn parse(msg : &[u8]) -> Result<Msg, String> {
        if msg.len() == 0 {
            return Err("Empty msg".to_owned());
        }

        let mut slice = msg;

        let pfx : Option<Pfx> = {
            if msg[0] == b':' {
                // parse prefix
                let ws_idx = find_byte(slice, b' ').unwrap();
                let (pfx, slice_) = slice.split_at(ws_idx);
                slice = &slice_[ 1 .. ]; // drop the space
                Some(parse_pfx(pfx))
            } else {
                log_stderr_bytes("Can't parse msg prefix:", msg);
                None
            }
        };

        let command : Command = {
            let ws_idx = find_byte(slice, b' ').unwrap();
            let (command, slice_) = slice.split_at(ws_idx);
            slice = &slice_[ 1 .. ]; // drop the space
            match reply_num(command) {
                None => Command::Str(unsafe {
                    // Command strings are added by the server and they're
                    // always ASCII strings, so this is safe and O(1).
                    String::from_utf8_unchecked(command.to_owned())
                }),
                Some(num) => Command::Num(num)
            }
        };

        let params = try!(parse_params(slice));

        Ok(Msg {
            pfx: pfx,
            command: command,
            params: params,
        })
    }

    ////////////////////////////////////////////////////////////////////////////
    // Message generation

    pub fn user<W : Write>(hostname : &str, realname : &str, mut sink : W) -> io::Result<()> {
        write!(sink, "USER {} 0 * :{}\r\n", hostname, realname)
    }

    pub fn nick<W : Write>(arg : &str, mut sink : W) -> io::Result<()> {
        write!(sink, "NICK {}\r\n", arg)
    }

    pub fn pong<W : Write>(arg : &str, mut sink : W) -> io::Result<()> {
        write!(sink, "PONG {}\r\n", arg)
    }

    pub fn join<W : Write>(channel : &str, mut sink : W) -> io::Result<()> {
        write!(sink, "JOIN {}\r\n", channel)
    }

    pub fn privmsg<W : Write>(msgtarget : &str, msg : &str, mut sink : W) -> io::Result<()> {
        write!(sink, "PRIVMSG {} {}\r\n", msgtarget, msg)
    }

    pub fn quit<W : Write>(msg : Option<&str>, mut sink : W) -> io::Result<()> {
        match msg {
            None => write!(sink, "QUIT\r\n"),
            Some(msg) => write!(sink, "QUIT {}\r\n", msg)
        }
    }
}

fn reply_num(bs : &[u8]) -> Option<u16> {
    if bs.len() == 3 {
        let n3 = unsafe { *bs.get_unchecked(0) };
        let n2 = unsafe { *bs.get_unchecked(1) };
        let n1 = unsafe { *bs.get_unchecked(2) };
        if is_num_ascii(n3) && is_num_ascii(n2) && is_num_ascii(n1) {
            return Some(((n3 - b'0') as u16) * 100 +
                        ((n2 - b'0') as u16) * 10  +
                        ((n1 - b'0') as u16));
        }
    }
    None
}

#[inline]
fn is_num_ascii(b : u8) -> bool {
    b >= b'0' && b <= b'9'
}

fn parse_pfx(pfx : &[u8]) -> Pfx {
    match find_byte(pfx, b'!') {
        None => Pfx::Server(unsafe { str::from_utf8_unchecked(pfx).to_owned() }),
        Some(idx) => {
            Pfx::User {
                nick: unsafe { str::from_utf8_unchecked(&pfx[ 0 .. idx ]).to_owned() },
                user: unsafe { str::from_utf8_unchecked(&pfx[ idx + 1 .. ]).to_owned() }
            }
        }
    }
}

fn parse_params(mut chrs : &[u8]) -> Result<Vec<Vec<u8>>, String> {
    if chrs.len() == 0 {
        return Err("parse_params: Empty slice of chars".to_owned());
    }

    if chrs[0] == b':' {
        let start_idx = 1; // drop the colon
        Ok(vec![
           (&chrs[ start_idx .. ]).to_owned()
        ])
    } else {
        let mut ret : Vec<Vec<u8>> = Vec::new();

        loop {
            match find_byte(chrs, b' ') {
                None => {
                    ret.push(chrs.to_owned());
                    break;
                },
                Some(end_idx) => {
                    ret.push((&chrs[ 0 .. end_idx ]).to_owned());
                    chrs = &chrs[ end_idx + 1 .. ]; // +1 to drop the space
                }
            }
        }

        Ok(ret)
    }
}

////////////////////////////////////////////////////////////////////////////////

#[test]
fn parse_error_1() {
    let msg = b"ERROR :Closing Link: 127.0.0.1 (Client Quit)";
    assert!(Msg::parse(msg).is_ok());
}
<commit_msg>drop the ':' from prefix<commit_after>use std::io::Write;
use std::io;
use std::str;

use utils::{find_byte, log_stderr_bytes};

#[derive(Debug)]
pub struct Msg {
    pub pfx     : Option<Pfx>,
    pub command : Command,
    pub params  : Vec<Vec<u8>>,
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Pfx {
    Server(String),
    User {
        nick : String,
        /// user@host
        user : String,
    },
}

#[derive(Debug)]
pub enum Command {
    Str(String),
    Num(u16),
}

impl Msg {
    /// Parse a complete IRC message. NOTE: The string should NOT have CR-NL.
    pub fn parse(msg : &[u8]) -> Result<Msg, String> {
        if msg.len() == 0 {
            return Err("Empty msg".to_owned());
        }

        let mut slice = msg;

        let pfx : Option<Pfx> = {
            if msg[0] == b':' {
                // parse prefix
                let ws_idx = find_byte(slice, b' ').unwrap();
                let (mut pfx, slice_) = slice.split_at(ws_idx);
                // drop the : from pfx
                pfx = &pfx[ 1 .. ];
                slice = &slice_[ 1 .. ]; // drop the space
                Some(parse_pfx(&pfx))
            } else {
                log_stderr_bytes("Can't parse msg prefix:", msg);
                None
            }
        };

        let command : Command = {
            let ws_idx = find_byte(slice, b' ').unwrap();
            let (command, slice_) = slice.split_at(ws_idx);
            slice = &slice_[ 1 .. ]; // drop the space
            match reply_num(command) {
                None => Command::Str(unsafe {
                    // Command strings are added by the server and they're
                    // always ASCII strings, so this is safe and O(1).
                    String::from_utf8_unchecked(command.to_owned())
                }),
                Some(num) => Command::Num(num)
            }
        };

        let params = try!(parse_params(slice));

        Ok(Msg {
            pfx: pfx,
            command: command,
            params: params,
        })
    }

    ////////////////////////////////////////////////////////////////////////////
    // Message generation

    pub fn user<W : Write>(hostname : &str, realname : &str, mut sink : W) -> io::Result<()> {
        write!(sink, "USER {} 0 * :{}\r\n", hostname, realname)
    }

    pub fn nick<W : Write>(arg : &str, mut sink : W) -> io::Result<()> {
        write!(sink, "NICK {}\r\n", arg)
    }

    pub fn pong<W : Write>(arg : &str, mut sink : W) -> io::Result<()> {
        write!(sink, "PONG {}\r\n", arg)
    }

    pub fn join<W : Write>(channel : &str, mut sink : W) -> io::Result<()> {
        write!(sink, "JOIN {}\r\n", channel)
    }

    pub fn privmsg<W : Write>(msgtarget : &str, msg : &str, mut sink : W) -> io::Result<()> {
        write!(sink, "PRIVMSG {} {}\r\n", msgtarget, msg)
    }

    pub fn quit<W : Write>(msg : Option<&str>, mut sink : W) -> io::Result<()> {
        match msg {
            None => write!(sink, "QUIT\r\n"),
            Some(msg) => write!(sink, "QUIT {}\r\n", msg)
        }
    }
}

fn reply_num(bs : &[u8]) -> Option<u16> {
    if bs.len() == 3 {
        let n3 = unsafe { *bs.get_unchecked(0) };
        let n2 = unsafe { *bs.get_unchecked(1) };
        let n1 = unsafe { *bs.get_unchecked(2) };
        if is_num_ascii(n3) && is_num_ascii(n2) && is_num_ascii(n1) {
            return Some(((n3 - b'0') as u16) * 100 +
                        ((n2 - b'0') as u16) * 10  +
                        ((n1 - b'0') as u16));
        }
    }
    None
}

#[inline]
fn is_num_ascii(b : u8) -> bool {
    b >= b'0' && b <= b'9'
}

fn parse_pfx(pfx : &[u8]) -> Pfx {
    match find_byte(pfx, b'!') {
        None => Pfx::Server(unsafe { str::from_utf8_unchecked(pfx).to_owned() }),
        Some(idx) => {
            Pfx::User {
                nick: unsafe { str::from_utf8_unchecked(&pfx[ 0 .. idx ]).to_owned() },
                user: unsafe { str::from_utf8_unchecked(&pfx[ idx + 1 .. ]).to_owned() }
            }
        }
    }
}

fn parse_params(mut chrs : &[u8]) -> Result<Vec<Vec<u8>>, String> {
    if chrs.len() == 0 {
        return Err("parse_params: Empty slice of chars".to_owned());
    }

    if chrs[0] == b':' {
        let start_idx = 1; // drop the colon
        Ok(vec![
           (&chrs[ start_idx .. ]).to_owned()
        ])
    } else {
        let mut ret : Vec<Vec<u8>> = Vec::new();

        loop {
            match find_byte(chrs, b' ') {
                None => {
                    ret.push(chrs.to_owned());
                    break;
                },
                Some(end_idx) => {
                    ret.push((&chrs[ 0 .. end_idx ]).to_owned());
                    chrs = &chrs[ end_idx + 1 .. ]; // +1 to drop the space
                }
            }
        }

        Ok(ret)
    }
}

////////////////////////////////////////////////////////////////////////////////

#[test]
fn parse_error_1() {
    let msg = b"ERROR :Closing Link: 127.0.0.1 (Client Quit)";
    assert!(Msg::parse(msg).is_ok());
}
<|endoftext|>


<commit_before>use super::helpers::allocations;
use super::helpers::edits::{get_random_edit, invert_edit};
use super::helpers::fixtures::{fixtures_dir, get_language, get_test_language};
use super::helpers::random::Rand;
use super::helpers::scope_sequence::ScopeSequence;
use crate::generate;
use crate::parse::perform_edit;
use crate::test::{parse_tests, print_diff, print_diff_key, strip_sexp_fields, TestEntry};
use crate::util;
use lazy_static::lazy_static;
use std::{env, fs, time, usize};
use tree_sitter::{LogType, Node, Parser, Tree};

const EDIT_COUNT: usize = 3;
const TRIAL_COUNT: usize = 10;
const LANGUAGES: &'static [&'static str] = &[
    "bash",
    "c",
    "cpp",
    "embedded-template",
    "go",
    "html",
    "javascript",
    "json",
    "php",
    "python",
];

lazy_static! {
    static ref LOG_ENABLED: bool = env::var("TREE_SITTER_TEST_ENABLE_LOG").is_ok();
    static ref LOG_GRAPH_ENABLED: bool = env::var("TREE_SITTER_TEST_ENABLE_LOG_GRAPHS").is_ok();
    static ref LANGUAGE_FILTER: Option<String> = env::var("TREE_SITTER_TEST_LANGUAGE_FILTER").ok();
    static ref EXAMPLE_FILTER: Option<String> = env::var("TREE_SITTER_TEST_EXAMPLE_FILTER").ok();
    static ref TRIAL_FILTER: Option<usize> = env::var("TREE_SITTER_TEST_TRIAL_FILTER")
        .map(|s| usize::from_str_radix(&s, 10).unwrap())
        .ok();
    pub static ref SEED: usize = env::var("TREE_SITTER_TEST_SEED")
        .map(|s| usize::from_str_radix(&s, 10).unwrap())
        .unwrap_or(
            time::SystemTime::now()
                .duration_since(time::UNIX_EPOCH)
                .unwrap()
                .as_secs() as usize,
        );
}

#[test]
fn test_real_language_corpus_files() {
    eprintln!("\n\nRandom seed: {}\n", *SEED);
    let grammars_dir = fixtures_dir().join("grammars");
    let error_corpus_dir = fixtures_dir().join("error_corpus");

    let mut failure_count = 0;
    for language_name in LANGUAGES.iter().cloned() {
        if let Some(filter) = LANGUAGE_FILTER.as_ref() {
            if language_name != filter.as_str() {
                continue;
            }
        }

        let language = get_language(language_name);
        let mut corpus_dir = grammars_dir.join(language_name).join("corpus");
        if !corpus_dir.is_dir() {
            corpus_dir = grammars_dir.join(language_name).join("test").join("corpus");
        }

        let error_corpus_file = error_corpus_dir.join(&format!("{}_errors.txt", language_name));
        let main_tests = parse_tests(&corpus_dir).unwrap();
        let error_tests = parse_tests(&error_corpus_file).unwrap_or(TestEntry::default());
        let mut tests = flatten_tests(main_tests);
        tests.extend(flatten_tests(error_tests));

        if !tests.is_empty() {
            eprintln!("language: {:?}", language_name);
        }

        for (example_name, input, expected_output, has_fields) in tests {
            eprintln!("  example: {:?}", example_name);

            if TRIAL_FILTER.map_or(true, |t| t == 0) {
                allocations::start_recording();
                let mut log_session = None;
                let mut parser = get_parser(&mut log_session, "log.html");
                parser.set_language(language).unwrap();
                let tree = parser.parse(&input, None).unwrap();
                let mut actual_output = tree.root_node().to_sexp();
                if !has_fields {
                    actual_output = strip_sexp_fields(actual_output);
                }
                drop(tree);
                drop(parser);
                if actual_output != expected_output {
                    print_diff_key();
                    print_diff(&actual_output, &expected_output);
                    println!("");
                    failure_count += 1;
                    continue;
                }
                allocations::stop_recording();
            }

            let mut parser = Parser::new();
            parser.set_language(language).unwrap();
            let tree = parser.parse(&input, None).unwrap();
            drop(parser);

            for trial in 1..=TRIAL_COUNT {
                if TRIAL_FILTER.map_or(true, |filter| filter == trial) {
                    let mut rand = Rand::new(*SEED + trial);

                    allocations::start_recording();
                    let mut log_session = None;
                    let mut parser = get_parser(&mut log_session, "log.html");
                    parser.set_language(language).unwrap();
                    let mut tree = tree.clone();
                    let mut input = input.clone();

                    if *LOG_GRAPH_ENABLED {
                        eprintln!("{}\n", String::from_utf8_lossy(&input));
                    }

                    // Perform a random series of edits and reparse.
                    let mut undo_stack = Vec::new();
                    for _ in 0..EDIT_COUNT {
                        let edit = get_random_edit(&mut rand, &input);
                        undo_stack.push(invert_edit(&input, &edit));
                        perform_edit(&mut tree, &mut input, &edit);
                    }
                    if *LOG_GRAPH_ENABLED {
                        eprintln!("{}\n", String::from_utf8_lossy(&input));
                    }

                    let mut tree2 = parser.parse(&input, Some(&tree)).unwrap();

                    // Check that the new tree is consistent.
                    check_consistent_sizes(&tree2, &input);
                    if let Err(message) = check_changed_ranges(&tree, &tree2, &input) {
                        println!(
                            "\nUnexpected scope change in trial {}\n{}\n\n",
                            trial, message
                        );
                        failure_count += 1;
                        break;
                    }

                    // Undo all of the edits and re-parse again.
                    while let Some(edit) = undo_stack.pop() {
                        perform_edit(&mut tree2, &mut input, &edit);
                    }
                    if *LOG_GRAPH_ENABLED {
                        eprintln!("{}\n", String::from_utf8_lossy(&input));
                    }

                    let tree3 = parser.parse(&input, Some(&tree2)).unwrap();

                    // Verify that the final tree matches the expectation from the corpus.
                    let mut actual_output = tree3.root_node().to_sexp();
                    if !has_fields {
                        actual_output = strip_sexp_fields(actual_output);
                    }

                    if actual_output != expected_output {
                        println!(
                            "Incorrect parse for {} - {} - trial {}",
                            language_name, example_name, trial
                        );
                        print_diff_key();
                        print_diff(&actual_output, &expected_output);
                        println!("");
                        failure_count += 1;
                        break;
                    }

                    // Check that the edited tree is consistent.
                    check_consistent_sizes(&tree3, &input);
                    if let Err(message) = check_changed_ranges(&tree2, &tree3, &input) {
                        eprintln!(
                            "Unexpected scope change in trial {}\n{}\n\n",
                            trial, message
                        );
                        failure_count += 1;
                        break;
                    }

                    drop(tree);
                    drop(tree2);
                    drop(tree3);
                    drop(parser);
                    allocations::stop_recording();
                }
            }
        }
    }
    if failure_count > 0 {
        panic!("{} corpus tests failed", failure_count);
    }
}

#[test]
fn test_feature_corpus_files() {
    let test_grammars_dir = fixtures_dir().join("test_grammars");

    let mut failure_count = 0;
    for entry in fs::read_dir(&test_grammars_dir).unwrap() {
        let entry = entry.unwrap();
        if !entry.metadata().unwrap().is_dir() {
            continue;
        }
        let language_name = entry.file_name();
        let language_name = language_name.to_str().unwrap();

        if let Some(filter) = LANGUAGE_FILTER.as_ref() {
            if language_name != filter.as_str() {
                continue;
            }
        }

        let test_path = entry.path();
        let grammar_path = test_path.join("grammar.json");
        let error_message_path = test_path.join("expected_error.txt");
        let grammar_json = fs::read_to_string(grammar_path).unwrap();
        let generate_result = generate::generate_parser_for_grammar(&grammar_json);

        if error_message_path.exists() {
            if EXAMPLE_FILTER.is_some() {
                continue;
            }

            eprintln!("test language: {:?}", language_name);

            let expected_message = fs::read_to_string(&error_message_path).unwrap();
            if let Err(e) = generate_result {
                if e.message() != expected_message {
                    eprintln!(
                        "Unexpected error message.\n\nExpected:\n\n{}\nActual:\n\n{}\n",
                        expected_message,
                        e.message()
                    );
                    failure_count += 1;
                }
            } else {
                eprintln!(
                    "Expected error message but got none for test grammar '{}'",
                    language_name
                );
                failure_count += 1;
            }
        } else {
            let corpus_path = test_path.join("corpus.txt");
            let c_code = generate_result.unwrap().1;
            let language = get_test_language(language_name, &c_code, Some(&test_path));
            let test = parse_tests(&corpus_path).unwrap();
            let tests = flatten_tests(test);

            if !tests.is_empty() {
                eprintln!("test language: {:?}", language_name);
            }

            for (name, input, expected_output, has_fields) in tests {
                eprintln!("  example: {:?}", name);

                allocations::start_recording();
                let mut log_session = None;
                let mut parser = get_parser(&mut log_session, "log.html");
                parser.set_language(language).unwrap();
                let tree = parser.parse(&input, None).unwrap();
                let mut actual_output = tree.root_node().to_sexp();
                if !has_fields {
                    actual_output = strip_sexp_fields(actual_output);
                }

                drop(tree);
                drop(parser);
                if actual_output != expected_output {
                    print_diff_key();
                    print_diff(&actual_output, &expected_output);
                    println!("");
                    failure_count += 1;
                    continue;
                }
                allocations::stop_recording();
            }
        }
    }
    if failure_count > 0 {
        panic!("{} corpus tests failed", failure_count);
    }
}

fn check_consistent_sizes(tree: &Tree, input: &Vec<u8>) {
    fn check(node: Node, line_offsets: &Vec<usize>) {
        let start_byte = node.start_byte();
        let end_byte = node.end_byte();
        let start_point = node.start_position();
        let end_point = node.end_position();

        assert!(start_byte <= end_byte);
        assert!(start_point <= end_point);
        assert_eq!(
            start_byte,
            line_offsets[start_point.row] + start_point.column
        );
        assert_eq!(end_byte, line_offsets[end_point.row] + end_point.column);

        let mut last_child_end_byte = start_byte;
        let mut last_child_end_point = start_point;
        let mut some_child_has_changes = false;
        let mut actual_named_child_count = 0;
        for i in 0..node.child_count() {
            let child = node.child(i).unwrap();
            assert!(child.start_byte() >= last_child_end_byte);
            assert!(child.start_position() >= last_child_end_point);
            check(child, line_offsets);
            if child.has_changes() {
                some_child_has_changes = true;
            }
            if child.is_named() {
                actual_named_child_count += 1;
            }
            last_child_end_byte = child.end_byte();
            last_child_end_point = child.end_position();
        }

        assert_eq!(actual_named_child_count, node.named_child_count());

        if node.child_count() > 0 {
            assert!(end_byte >= last_child_end_byte);
            assert!(end_point >= last_child_end_point);
        }

        if some_child_has_changes {
            assert!(node.has_changes());
        }
    }

    let mut line_offsets = vec![0];
    for (i, c) in input.iter().enumerate() {
        if *c == '\n' as u8 {
            line_offsets.push(i + 1);
        }
    }

    check(tree.root_node(), &line_offsets);
}

fn check_changed_ranges(old_tree: &Tree, new_tree: &Tree, input: &Vec<u8>) -> Result<(), String> {
    let changed_ranges = old_tree.changed_ranges(new_tree).collect();
    let old_scope_sequence = ScopeSequence::new(old_tree);
    let new_scope_sequence = ScopeSequence::new(new_tree);
    old_scope_sequence.check_changes(&new_scope_sequence, &input, &changed_ranges)
}

fn get_parser(session: &mut Option<util::LogSession>, log_filename: &str) -> Parser {
    let mut parser = Parser::new();

    if *LOG_ENABLED {
        parser.set_logger(Some(Box::new(|log_type, msg| {
            if log_type == LogType::Lex {
                eprintln!("  {}", msg);
            } else {
                eprintln!("{}", msg);
            }
        })));
    } else if *LOG_GRAPH_ENABLED {
        *session = Some(util::log_graphs(&mut parser, log_filename).unwrap());
    }

    parser
}

fn flatten_tests(test: TestEntry) -> Vec<(String, Vec<u8>, String, bool)> {
    fn helper(test: TestEntry, prefix: &str, result: &mut Vec<(String, Vec<u8>, String, bool)>) {
        match test {
            TestEntry::Example {
                mut name,
                input,
                output,
                has_fields,
            } => {
                if !prefix.is_empty() {
                    name.insert_str(0, " - ");
                    name.insert_str(0, prefix);
                }
                if let Some(filter) = EXAMPLE_FILTER.as_ref() {
                    if !name.contains(filter.as_str()) {
                        return;
                    }
                }
                result.push((name, input, output, has_fields));
            }
            TestEntry::Group { mut name, children } => {
                if !prefix.is_empty() {
                    name.insert_str(0, " - ");
                    name.insert_str(0, prefix);
                }
                for child in children {
                    helper(child, &name, result);
                }
            }
        }
    }
    let mut result = Vec::new();
    helper(test, "", &mut result);
    result
}
<commit_msg>add Rust into languages list for corpus tests (#619)<commit_after>use super::helpers::allocations;
use super::helpers::edits::{get_random_edit, invert_edit};
use super::helpers::fixtures::{fixtures_dir, get_language, get_test_language};
use super::helpers::random::Rand;
use super::helpers::scope_sequence::ScopeSequence;
use crate::generate;
use crate::parse::perform_edit;
use crate::test::{parse_tests, print_diff, print_diff_key, strip_sexp_fields, TestEntry};
use crate::util;
use lazy_static::lazy_static;
use std::{env, fs, time, usize};
use tree_sitter::{LogType, Node, Parser, Tree};

const EDIT_COUNT: usize = 3;
const TRIAL_COUNT: usize = 10;
const LANGUAGES: &'static [&'static str] = &[
    "bash",
    "c",
    "cpp",
    "embedded-template",
    "go",
    "html",
    "javascript",
    "json",
    "php",
    "python",
    "rust",
];

lazy_static! {
    static ref LOG_ENABLED: bool = env::var("TREE_SITTER_TEST_ENABLE_LOG").is_ok();
    static ref LOG_GRAPH_ENABLED: bool = env::var("TREE_SITTER_TEST_ENABLE_LOG_GRAPHS").is_ok();
    static ref LANGUAGE_FILTER: Option<String> = env::var("TREE_SITTER_TEST_LANGUAGE_FILTER").ok();
    static ref EXAMPLE_FILTER: Option<String> = env::var("TREE_SITTER_TEST_EXAMPLE_FILTER").ok();
    static ref TRIAL_FILTER: Option<usize> = env::var("TREE_SITTER_TEST_TRIAL_FILTER")
        .map(|s| usize::from_str_radix(&s, 10).unwrap())
        .ok();
    pub static ref SEED: usize = env::var("TREE_SITTER_TEST_SEED")
        .map(|s| usize::from_str_radix(&s, 10).unwrap())
        .unwrap_or(
            time::SystemTime::now()
                .duration_since(time::UNIX_EPOCH)
                .unwrap()
                .as_secs() as usize,
        );
}

#[test]
fn test_real_language_corpus_files() {
    eprintln!("\n\nRandom seed: {}\n", *SEED);
    let grammars_dir = fixtures_dir().join("grammars");
    let error_corpus_dir = fixtures_dir().join("error_corpus");

    let mut failure_count = 0;
    for language_name in LANGUAGES.iter().cloned() {
        if let Some(filter) = LANGUAGE_FILTER.as_ref() {
            if language_name != filter.as_str() {
                continue;
            }
        }

        let language = get_language(language_name);
        let mut corpus_dir = grammars_dir.join(language_name).join("corpus");
        if !corpus_dir.is_dir() {
            corpus_dir = grammars_dir.join(language_name).join("test").join("corpus");
        }

        let error_corpus_file = error_corpus_dir.join(&format!("{}_errors.txt", language_name));
        let main_tests = parse_tests(&corpus_dir).unwrap();
        let error_tests = parse_tests(&error_corpus_file).unwrap_or(TestEntry::default());
        let mut tests = flatten_tests(main_tests);
        tests.extend(flatten_tests(error_tests));

        if !tests.is_empty() {
            eprintln!("language: {:?}", language_name);
        }

        for (example_name, input, expected_output, has_fields) in tests {
            eprintln!("  example: {:?}", example_name);

            if TRIAL_FILTER.map_or(true, |t| t == 0) {
                allocations::start_recording();
                let mut log_session = None;
                let mut parser = get_parser(&mut log_session, "log.html");
                parser.set_language(language).unwrap();
                let tree = parser.parse(&input, None).unwrap();
                let mut actual_output = tree.root_node().to_sexp();
                if !has_fields {
                    actual_output = strip_sexp_fields(actual_output);
                }
                drop(tree);
                drop(parser);
                if actual_output != expected_output {
                    print_diff_key();
                    print_diff(&actual_output, &expected_output);
                    println!("");
                    failure_count += 1;
                    continue;
                }
                allocations::stop_recording();
            }

            let mut parser = Parser::new();
            parser.set_language(language).unwrap();
            let tree = parser.parse(&input, None).unwrap();
            drop(parser);

            for trial in 1..=TRIAL_COUNT {
                if TRIAL_FILTER.map_or(true, |filter| filter == trial) {
                    let mut rand = Rand::new(*SEED + trial);

                    allocations::start_recording();
                    let mut log_session = None;
                    let mut parser = get_parser(&mut log_session, "log.html");
                    parser.set_language(language).unwrap();
                    let mut tree = tree.clone();
                    let mut input = input.clone();

                    if *LOG_GRAPH_ENABLED {
                        eprintln!("{}\n", String::from_utf8_lossy(&input));
                    }

                    // Perform a random series of edits and reparse.
                    let mut undo_stack = Vec::new();
                    for _ in 0..EDIT_COUNT {
                        let edit = get_random_edit(&mut rand, &input);
                        undo_stack.push(invert_edit(&input, &edit));
                        perform_edit(&mut tree, &mut input, &edit);
                    }
                    if *LOG_GRAPH_ENABLED {
                        eprintln!("{}\n", String::from_utf8_lossy(&input));
                    }

                    let mut tree2 = parser.parse(&input, Some(&tree)).unwrap();

                    // Check that the new tree is consistent.
                    check_consistent_sizes(&tree2, &input);
                    if let Err(message) = check_changed_ranges(&tree, &tree2, &input) {
                        println!(
                            "\nUnexpected scope change in trial {}\n{}\n\n",
                            trial, message
                        );
                        failure_count += 1;
                        break;
                    }

                    // Undo all of the edits and re-parse again.
                    while let Some(edit) = undo_stack.pop() {
                        perform_edit(&mut tree2, &mut input, &edit);
                    }
                    if *LOG_GRAPH_ENABLED {
                        eprintln!("{}\n", String::from_utf8_lossy(&input));
                    }

                    let tree3 = parser.parse(&input, Some(&tree2)).unwrap();

                    // Verify that the final tree matches the expectation from the corpus.
                    let mut actual_output = tree3.root_node().to_sexp();
                    if !has_fields {
                        actual_output = strip_sexp_fields(actual_output);
                    }

                    if actual_output != expected_output {
                        println!(
                            "Incorrect parse for {} - {} - trial {}",
                            language_name, example_name, trial
                        );
                        print_diff_key();
                        print_diff(&actual_output, &expected_output);
                        println!("");
                        failure_count += 1;
                        break;
                    }

                    // Check that the edited tree is consistent.
                    check_consistent_sizes(&tree3, &input);
                    if let Err(message) = check_changed_ranges(&tree2, &tree3, &input) {
                        eprintln!(
                            "Unexpected scope change in trial {}\n{}\n\n",
                            trial, message
                        );
                        failure_count += 1;
                        break;
                    }

                    drop(tree);
                    drop(tree2);
                    drop(tree3);
                    drop(parser);
                    allocations::stop_recording();
                }
            }
        }
    }
    if failure_count > 0 {
        panic!("{} corpus tests failed", failure_count);
    }
}

#[test]
fn test_feature_corpus_files() {
    let test_grammars_dir = fixtures_dir().join("test_grammars");

    let mut failure_count = 0;
    for entry in fs::read_dir(&test_grammars_dir).unwrap() {
        let entry = entry.unwrap();
        if !entry.metadata().unwrap().is_dir() {
            continue;
        }
        let language_name = entry.file_name();
        let language_name = language_name.to_str().unwrap();

        if let Some(filter) = LANGUAGE_FILTER.as_ref() {
            if language_name != filter.as_str() {
                continue;
            }
        }

        let test_path = entry.path();
        let grammar_path = test_path.join("grammar.json");
        let error_message_path = test_path.join("expected_error.txt");
        let grammar_json = fs::read_to_string(grammar_path).unwrap();
        let generate_result = generate::generate_parser_for_grammar(&grammar_json);

        if error_message_path.exists() {
            if EXAMPLE_FILTER.is_some() {
                continue;
            }

            eprintln!("test language: {:?}", language_name);

            let expected_message = fs::read_to_string(&error_message_path).unwrap();
            if let Err(e) = generate_result {
                if e.message() != expected_message {
                    eprintln!(
                        "Unexpected error message.\n\nExpected:\n\n{}\nActual:\n\n{}\n",
                        expected_message,
                        e.message()
                    );
                    failure_count += 1;
                }
            } else {
                eprintln!(
                    "Expected error message but got none for test grammar '{}'",
                    language_name
                );
                failure_count += 1;
            }
        } else {
            let corpus_path = test_path.join("corpus.txt");
            let c_code = generate_result.unwrap().1;
            let language = get_test_language(language_name, &c_code, Some(&test_path));
            let test = parse_tests(&corpus_path).unwrap();
            let tests = flatten_tests(test);

            if !tests.is_empty() {
                eprintln!("test language: {:?}", language_name);
            }

            for (name, input, expected_output, has_fields) in tests {
                eprintln!("  example: {:?}", name);

                allocations::start_recording();
                let mut log_session = None;
                let mut parser = get_parser(&mut log_session, "log.html");
                parser.set_language(language).unwrap();
                let tree = parser.parse(&input, None).unwrap();
                let mut actual_output = tree.root_node().to_sexp();
                if !has_fields {
                    actual_output = strip_sexp_fields(actual_output);
                }

                drop(tree);
                drop(parser);
                if actual_output != expected_output {
                    print_diff_key();
                    print_diff(&actual_output, &expected_output);
                    println!("");
                    failure_count += 1;
                    continue;
                }
                allocations::stop_recording();
            }
        }
    }
    if failure_count > 0 {
        panic!("{} corpus tests failed", failure_count);
    }
}

fn check_consistent_sizes(tree: &Tree, input: &Vec<u8>) {
    fn check(node: Node, line_offsets: &Vec<usize>) {
        let start_byte = node.start_byte();
        let end_byte = node.end_byte();
        let start_point = node.start_position();
        let end_point = node.end_position();

        assert!(start_byte <= end_byte);
        assert!(start_point <= end_point);
        assert_eq!(
            start_byte,
            line_offsets[start_point.row] + start_point.column
        );
        assert_eq!(end_byte, line_offsets[end_point.row] + end_point.column);

        let mut last_child_end_byte = start_byte;
        let mut last_child_end_point = start_point;
        let mut some_child_has_changes = false;
        let mut actual_named_child_count = 0;
        for i in 0..node.child_count() {
            let child = node.child(i).unwrap();
            assert!(child.start_byte() >= last_child_end_byte);
            assert!(child.start_position() >= last_child_end_point);
            check(child, line_offsets);
            if child.has_changes() {
                some_child_has_changes = true;
            }
            if child.is_named() {
                actual_named_child_count += 1;
            }
            last_child_end_byte = child.end_byte();
            last_child_end_point = child.end_position();
        }

        assert_eq!(actual_named_child_count, node.named_child_count());

        if node.child_count() > 0 {
            assert!(end_byte >= last_child_end_byte);
            assert!(end_point >= last_child_end_point);
        }

        if some_child_has_changes {
            assert!(node.has_changes());
        }
    }

    let mut line_offsets = vec![0];
    for (i, c) in input.iter().enumerate() {
        if *c == '\n' as u8 {
            line_offsets.push(i + 1);
        }
    }

    check(tree.root_node(), &line_offsets);
}

fn check_changed_ranges(old_tree: &Tree, new_tree: &Tree, input: &Vec<u8>) -> Result<(), String> {
    let changed_ranges = old_tree.changed_ranges(new_tree).collect();
    let old_scope_sequence = ScopeSequence::new(old_tree);
    let new_scope_sequence = ScopeSequence::new(new_tree);
    old_scope_sequence.check_changes(&new_scope_sequence, &input, &changed_ranges)
}

fn get_parser(session: &mut Option<util::LogSession>, log_filename: &str) -> Parser {
    let mut parser = Parser::new();

    if *LOG_ENABLED {
        parser.set_logger(Some(Box::new(|log_type, msg| {
            if log_type == LogType::Lex {
                eprintln!("  {}", msg);
            } else {
                eprintln!("{}", msg);
            }
        })));
    } else if *LOG_GRAPH_ENABLED {
        *session = Some(util::log_graphs(&mut parser, log_filename).unwrap());
    }

    parser
}

fn flatten_tests(test: TestEntry) -> Vec<(String, Vec<u8>, String, bool)> {
    fn helper(test: TestEntry, prefix: &str, result: &mut Vec<(String, Vec<u8>, String, bool)>) {
        match test {
            TestEntry::Example {
                mut name,
                input,
                output,
                has_fields,
            } => {
                if !prefix.is_empty() {
                    name.insert_str(0, " - ");
                    name.insert_str(0, prefix);
                }
                if let Some(filter) = EXAMPLE_FILTER.as_ref() {
                    if !name.contains(filter.as_str()) {
                        return;
                    }
                }
                result.push((name, input, output, has_fields));
            }
            TestEntry::Group { mut name, children } => {
                if !prefix.is_empty() {
                    name.insert_str(0, " - ");
                    name.insert_str(0, prefix);
                }
                for child in children {
                    helper(child, &name, result);
                }
            }
        }
    }
    let mut result = Vec::new();
    helper(test, "", &mut result);
    result
}
<|endoftext|>


<commit_before>use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
use rustc::hir::*;
use rustc::hir::intravisit::*;
use syntax::ast::{LitKind, NodeId, DUMMY_NODE_ID};
use syntax::codemap::{dummy_spanned, Span, DUMMY_SP};
use syntax::util::ThinVec;
use utils::{in_macro, snippet_opt, span_lint_and_then, SpanlessEq};

/// **What it does:** Checks for boolean expressions that can be written more
/// concisely.
///
/// **Why is this bad?** Readability of boolean expressions suffers from
/// unnecessary duplication.
///
/// **Known problems:** Ignores short circuiting behavior of `||` and
/// `&&`. Ignores `|`, `&` and `^`.
///
/// **Example:**
/// ```rust
/// if a && true  // should be: if a
/// if !(a == b)  // should be: if a != b
/// ```
declare_lint! {
    pub NONMINIMAL_BOOL,
    Allow,
    "boolean expressions that can be written more concisely"
}

/// **What it does:** Checks for boolean expressions that contain terminals that
/// can be eliminated.
///
/// **Why is this bad?** This is most likely a logic bug.
///
/// **Known problems:** Ignores short circuiting behavior.
///
/// **Example:**
/// ```rust
/// if a && b || a { ... }
/// ```
/// The `b` is unnecessary, the expression is equivalent to `if a`.
declare_lint! {
    pub LOGIC_BUG,
    Warn,
    "boolean expressions that contain terminals which can be eliminated"
}

// For each pairs, both orders are considered.
const METHODS_WITH_NEGATION: [(&str, &str); 2] = [
    ("is_some", "is_none"),
    ("is_err", "is_ok"),
];

#[derive(Copy, Clone)]
pub struct NonminimalBool;

impl LintPass for NonminimalBool {
    fn get_lints(&self) -> LintArray {
        lint_array!(NONMINIMAL_BOOL, LOGIC_BUG)
    }
}

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NonminimalBool {
    fn check_fn(
        &mut self,
        cx: &LateContext<'a, 'tcx>,
        _: intravisit::FnKind<'tcx>,
        _: &'tcx FnDecl,
        body: &'tcx Body,
        _: Span,
        _: NodeId,
    ) {
        NonminimalBoolVisitor { cx: cx }.visit_body(body)
    }
}

struct NonminimalBoolVisitor<'a, 'tcx: 'a> {
    cx: &'a LateContext<'a, 'tcx>,
}

use quine_mc_cluskey::Bool;
struct Hir2Qmm<'a, 'tcx: 'a, 'v> {
    terminals: Vec<&'v Expr>,
    cx: &'a LateContext<'a, 'tcx>,
}

impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> {
    fn extract(&mut self, op: BinOp_, a: &[&'v Expr], mut v: Vec<Bool>) -> Result<Vec<Bool>, String> {
        for a in a {
            if let ExprBinary(binop, ref lhs, ref rhs) = a.node {
                if binop.node == op {
                    v = self.extract(op, &[lhs, rhs], v)?;
                    continue;
                }
            }
            v.push(self.run(a)?);
        }
        Ok(v)
    }

    fn run(&mut self, e: &'v Expr) -> Result<Bool, String> {
        // prevent folding of `cfg!` macros and the like
        if !in_macro(e.span) {
            match e.node {
                ExprUnary(UnNot, ref inner) => return Ok(Bool::Not(box self.run(inner)?)),
                ExprBinary(binop, ref lhs, ref rhs) => match binop.node {
                    BiOr => return Ok(Bool::Or(self.extract(BiOr, &[lhs, rhs], Vec::new())?)),
                    BiAnd => return Ok(Bool::And(self.extract(BiAnd, &[lhs, rhs], Vec::new())?)),
                    _ => (),
                },
                ExprLit(ref lit) => match lit.node {
                    LitKind::Bool(true) => return Ok(Bool::True),
                    LitKind::Bool(false) => return Ok(Bool::False),
                    _ => (),
                },
                _ => (),
            }
        }
        for (n, expr) in self.terminals.iter().enumerate() {
            if SpanlessEq::new(self.cx).ignore_fn().eq_expr(e, expr) {
                #[allow(cast_possible_truncation)]
                return Ok(Bool::Term(n as u8));
            }
            let negated = match e.node {
                ExprBinary(binop, ref lhs, ref rhs) => {
                    let mk_expr = |op| {
                        Expr {
                            id: DUMMY_NODE_ID,
                            hir_id: DUMMY_HIR_ID,
                            span: DUMMY_SP,
                            attrs: ThinVec::new(),
                            node: ExprBinary(dummy_spanned(op), lhs.clone(), rhs.clone()),
                        }
                    };
                    match binop.node {
                        BiEq => mk_expr(BiNe),
                        BiNe => mk_expr(BiEq),
                        BiGt => mk_expr(BiLe),
                        BiGe => mk_expr(BiLt),
                        BiLt => mk_expr(BiGe),
                        BiLe => mk_expr(BiGt),
                        _ => continue,
                    }
                },
                _ => continue,
            };
            if SpanlessEq::new(self.cx).ignore_fn().eq_expr(&negated, expr) {
                #[allow(cast_possible_truncation)]
                return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
            }
        }
        let n = self.terminals.len();
        self.terminals.push(e);
        if n < 32 {
            #[allow(cast_possible_truncation)]
            Ok(Bool::Term(n as u8))
        } else {
            Err("too many literals".to_owned())
        }
    }
}

struct SuggestContext<'a, 'tcx: 'a, 'v> {
    terminals: &'v [&'v Expr],
    cx: &'a LateContext<'a, 'tcx>,
    output: String,
    simplified: bool,
}

impl<'a, 'tcx, 'v> SuggestContext<'a, 'tcx, 'v> {
    fn snip(&self, e: &Expr) -> String {
        snippet_opt(self.cx, e.span).expect("don't try to improve booleans created by macros")
    }

    fn simplify_not(&self, expr: &Expr) -> Option<String> {
        match expr.node {
            ExprBinary(binop, ref lhs, ref rhs) => {
                match binop.node {
                    BiEq => Some(" != "),
                    BiNe => Some(" == "),
                    BiLt => Some(" >= "),
                    BiGt => Some(" <= "),
                    BiLe => Some(" > "),
                    BiGe => Some(" < "),
                    _ => None,
                }.map(|op| format!("{}{}{}", self.snip(lhs), op, self.snip(rhs)))
            },
            ExprMethodCall(ref path, _, ref args) if args.len() == 1 => {
                METHODS_WITH_NEGATION
                    .iter().cloned()
                    .flat_map(|(a, b)| vec![(a, b), (b, a)])
                    .find(|&(a, _)| a == path.name.as_str())
                    .map(|(_, neg_method)| format!("{}.{}()", self.snip(&args[0]), neg_method))
            },
            _ => None,
        }
    }

    fn recurse(&mut self, suggestion: &Bool) {
        use quine_mc_cluskey::Bool::*;
        match *suggestion {
            True => {
                self.output.push_str("true");
            },
            False => {
                self.output.push_str("false");
            },
            Not(ref inner) => match **inner {
                And(_) | Or(_) => {
                    self.output.push('!');
                    self.output.push('(');
                    self.recurse(inner);
                    self.output.push(')');
                },
                Term(n) => {
                    let terminal = self.terminals[n as usize];
                    if let Some(str) = self.simplify_not(terminal) {
                        self.simplified = true;
                        self.output.push_str(&str)
                    } else {
                        self.output.push('!');
                        let snip = self.snip(terminal);
                        self.output.push_str(&snip);
                    }
                },
                True | False | Not(_) => {
                    self.output.push('!');
                    self.recurse(inner)
                },
            },
            And(ref v) => {
                for (index, inner) in v.iter().enumerate() {
                    if index > 0 {
                        self.output.push_str(" && ");
                    }
                    if let Or(_) = *inner {
                        self.output.push('(');
                        self.recurse(inner);
                        self.output.push(')');
                    } else {
                        self.recurse(inner);
                    }
                }
            },
            Or(ref v) => {
                for (index, inner) in v.iter().enumerate() {
                    if index > 0 {
                        self.output.push_str(" || ");
                    }
                    self.recurse(inner);
                }
            },
            Term(n) => {
                let snip = self.snip(self.terminals[n as usize]);
                self.output.push_str(&snip);
            },
        }
    }
}

// The boolean part of the return indicates whether some simplifications have been applied.
fn suggest(cx: &LateContext, suggestion: &Bool, terminals: &[&Expr]) -> (String, bool) {
    let mut suggest_context = SuggestContext {
        terminals: terminals,
        cx: cx,
        output: String::new(),
        simplified: false,
    };
    suggest_context.recurse(suggestion);
    (suggest_context.output, suggest_context.simplified)
}

fn simple_negate(b: Bool) -> Bool {
    use quine_mc_cluskey::Bool::*;
    match b {
        True => False,
        False => True,
        t @ Term(_) => Not(Box::new(t)),
        And(mut v) => {
            for el in &mut v {
                *el = simple_negate(::std::mem::replace(el, True));
            }
            Or(v)
        },
        Or(mut v) => {
            for el in &mut v {
                *el = simple_negate(::std::mem::replace(el, True));
            }
            And(v)
        },
        Not(inner) => *inner,
    }
}

#[derive(Default)]
struct Stats {
    terminals: [usize; 32],
    negations: usize,
    ops: usize,
}

fn terminal_stats(b: &Bool) -> Stats {
    fn recurse(b: &Bool, stats: &mut Stats) {
        match *b {
            True | False => stats.ops += 1,
            Not(ref inner) => {
                match **inner {
                    And(_) | Or(_) => stats.ops += 1, // brackets are also operations
                    _ => stats.negations += 1,
                }
                recurse(inner, stats);
            },
            And(ref v) | Or(ref v) => {
                stats.ops += v.len() - 1;
                for inner in v {
                    recurse(inner, stats);
                }
            },
            Term(n) => stats.terminals[n as usize] += 1,
        }
    }
    use quine_mc_cluskey::Bool::*;
    let mut stats = Stats::default();
    recurse(b, &mut stats);
    stats
}

impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
    fn bool_expr(&self, e: &Expr) {
        let mut h2q = Hir2Qmm {
            terminals: Vec::new(),
            cx: self.cx,
        };
        if let Ok(expr) = h2q.run(e) {
            if h2q.terminals.len() > 8 {
                // QMC has exponentially slow behavior as the number of terminals increases
                // 8 is reasonable, it takes approximately 0.2 seconds.
                // See #825
                return;
            }

            let stats = terminal_stats(&expr);
            let mut simplified = expr.simplify();
            for simple in Bool::Not(Box::new(expr.clone())).simplify() {
                match simple {
                    Bool::Not(_) | Bool::True | Bool::False => {},
                    _ => simplified.push(Bool::Not(Box::new(simple.clone()))),
                }
                let simple_negated = simple_negate(simple);
                if simplified.iter().any(|s| *s == simple_negated) {
                    continue;
                }
                simplified.push(simple_negated);
            }
            let mut improvements = Vec::new();
            'simplified: for suggestion in &simplified {
                let simplified_stats = terminal_stats(suggestion);
                let mut improvement = false;
                for i in 0..32 {
                    // ignore any "simplifications" that end up requiring a terminal more often
                    // than in the original expression
                    if stats.terminals[i] < simplified_stats.terminals[i] {
                        continue 'simplified;
                    }
                    if stats.terminals[i] != 0 && simplified_stats.terminals[i] == 0 {
                        span_lint_and_then(
                            self.cx,
                            LOGIC_BUG,
                            e.span,
                            "this boolean expression contains a logic bug",
                            |db| {
                                db.span_help(
                                    h2q.terminals[i].span,
                                    "this expression can be optimized out by applying boolean operations to the \
                                     outer expression",
                                );
                                db.span_suggestion(
                                    e.span,
                                    "it would look like the following",
                                    suggest(self.cx, suggestion, &h2q.terminals).0,
                                );
                            },
                        );
                        // don't also lint `NONMINIMAL_BOOL`
                        return;
                    }
                    // if the number of occurrences of a terminal decreases or any of the stats
                    // decreases while none increases
                    improvement |= (stats.terminals[i] > simplified_stats.terminals[i])
                        || (stats.negations > simplified_stats.negations && stats.ops == simplified_stats.ops)
                        || (stats.ops > simplified_stats.ops && stats.negations == simplified_stats.negations);
                }
                if improvement {
                    improvements.push(suggestion);
                }
            }
            let nonminimal_bool_lint = |suggestions| {
                span_lint_and_then(
                    self.cx,
                    NONMINIMAL_BOOL,
                    e.span,
                    "this boolean expression can be simplified",
                    |db| { db.span_suggestions(e.span, "try", suggestions); },
                );
            };
            if improvements.is_empty() {
                let suggest = suggest(self.cx, &expr, &h2q.terminals);
                if suggest.1 {
                    nonminimal_bool_lint(vec![suggest.0])
                }
            } else {
                nonminimal_bool_lint(
                    improvements
                        .into_iter()
                        .map(|suggestion| suggest(self.cx, suggestion, &h2q.terminals).0)
                        .collect()
                );
            }
        }
    }
}

impl<'a, 'tcx> Visitor<'tcx> for NonminimalBoolVisitor<'a, 'tcx> {
    fn visit_expr(&mut self, e: &'tcx Expr) {
        if in_macro(e.span) {
            return;
        }
        match e.node {
            ExprBinary(binop, _, _) if binop.node == BiOr || binop.node == BiAnd => self.bool_expr(e),
            ExprUnary(UnNot, ref inner) => if self.cx.tables.node_types()[inner.hir_id].is_bool() {
                self.bool_expr(e);
            } else {
                walk_expr(self, e);
            },
            _ => walk_expr(self, e),
        }
    }
    fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
        NestedVisitorMap::None
    }
}
<commit_msg>Fix #2245<commit_after>use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
use rustc::hir::*;
use rustc::hir::intravisit::*;
use syntax::ast::{LitKind, NodeId, DUMMY_NODE_ID};
use syntax::codemap::{dummy_spanned, Span, DUMMY_SP};
use syntax::util::ThinVec;
use utils::{in_macro, snippet_opt, span_lint_and_then, SpanlessEq};

/// **What it does:** Checks for boolean expressions that can be written more
/// concisely.
///
/// **Why is this bad?** Readability of boolean expressions suffers from
/// unnecessary duplication.
///
/// **Known problems:** Ignores short circuiting behavior of `||` and
/// `&&`. Ignores `|`, `&` and `^`.
///
/// **Example:**
/// ```rust
/// if a && true  // should be: if a
/// if !(a == b)  // should be: if a != b
/// ```
declare_lint! {
    pub NONMINIMAL_BOOL,
    Allow,
    "boolean expressions that can be written more concisely"
}

/// **What it does:** Checks for boolean expressions that contain terminals that
/// can be eliminated.
///
/// **Why is this bad?** This is most likely a logic bug.
///
/// **Known problems:** Ignores short circuiting behavior.
///
/// **Example:**
/// ```rust
/// if a && b || a { ... }
/// ```
/// The `b` is unnecessary, the expression is equivalent to `if a`.
declare_lint! {
    pub LOGIC_BUG,
    Warn,
    "boolean expressions that contain terminals which can be eliminated"
}

// For each pairs, both orders are considered.
const METHODS_WITH_NEGATION: [(&str, &str); 2] = [
    ("is_some", "is_none"),
    ("is_err", "is_ok"),
];

#[derive(Copy, Clone)]
pub struct NonminimalBool;

impl LintPass for NonminimalBool {
    fn get_lints(&self) -> LintArray {
        lint_array!(NONMINIMAL_BOOL, LOGIC_BUG)
    }
}

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NonminimalBool {
    fn check_fn(
        &mut self,
        cx: &LateContext<'a, 'tcx>,
        _: intravisit::FnKind<'tcx>,
        _: &'tcx FnDecl,
        body: &'tcx Body,
        _: Span,
        _: NodeId,
    ) {
        NonminimalBoolVisitor { cx: cx }.visit_body(body)
    }
}

struct NonminimalBoolVisitor<'a, 'tcx: 'a> {
    cx: &'a LateContext<'a, 'tcx>,
}

use quine_mc_cluskey::Bool;
struct Hir2Qmm<'a, 'tcx: 'a, 'v> {
    terminals: Vec<&'v Expr>,
    cx: &'a LateContext<'a, 'tcx>,
}

impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> {
    fn extract(&mut self, op: BinOp_, a: &[&'v Expr], mut v: Vec<Bool>) -> Result<Vec<Bool>, String> {
        for a in a {
            if let ExprBinary(binop, ref lhs, ref rhs) = a.node {
                if binop.node == op {
                    v = self.extract(op, &[lhs, rhs], v)?;
                    continue;
                }
            }
            v.push(self.run(a)?);
        }
        Ok(v)
    }

    fn run(&mut self, e: &'v Expr) -> Result<Bool, String> {
        // prevent folding of `cfg!` macros and the like
        if !in_macro(e.span) {
            match e.node {
                ExprUnary(UnNot, ref inner) => return Ok(Bool::Not(box self.run(inner)?)),
                ExprBinary(binop, ref lhs, ref rhs) => match binop.node {
                    BiOr => return Ok(Bool::Or(self.extract(BiOr, &[lhs, rhs], Vec::new())?)),
                    BiAnd => return Ok(Bool::And(self.extract(BiAnd, &[lhs, rhs], Vec::new())?)),
                    _ => (),
                },
                ExprLit(ref lit) => match lit.node {
                    LitKind::Bool(true) => return Ok(Bool::True),
                    LitKind::Bool(false) => return Ok(Bool::False),
                    _ => (),
                },
                _ => (),
            }
        }
        for (n, expr) in self.terminals.iter().enumerate() {
            if SpanlessEq::new(self.cx).ignore_fn().eq_expr(e, expr) {
                #[allow(cast_possible_truncation)]
                return Ok(Bool::Term(n as u8));
            }
            let negated = match e.node {
                ExprBinary(binop, ref lhs, ref rhs) => {
                    let mk_expr = |op| {
                        Expr {
                            id: DUMMY_NODE_ID,
                            hir_id: DUMMY_HIR_ID,
                            span: DUMMY_SP,
                            attrs: ThinVec::new(),
                            node: ExprBinary(dummy_spanned(op), lhs.clone(), rhs.clone()),
                        }
                    };
                    match binop.node {
                        BiEq => mk_expr(BiNe),
                        BiNe => mk_expr(BiEq),
                        BiGt => mk_expr(BiLe),
                        BiGe => mk_expr(BiLt),
                        BiLt => mk_expr(BiGe),
                        BiLe => mk_expr(BiGt),
                        _ => continue,
                    }
                },
                _ => continue,
            };
            if SpanlessEq::new(self.cx).ignore_fn().eq_expr(&negated, expr) {
                #[allow(cast_possible_truncation)]
                return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
            }
        }
        let n = self.terminals.len();
        self.terminals.push(e);
        if n < 32 {
            #[allow(cast_possible_truncation)]
            Ok(Bool::Term(n as u8))
        } else {
            Err("too many literals".to_owned())
        }
    }
}

struct SuggestContext<'a, 'tcx: 'a, 'v> {
    terminals: &'v [&'v Expr],
    cx: &'a LateContext<'a, 'tcx>,
    output: String,
    simplified: bool,
}

impl<'a, 'tcx, 'v> SuggestContext<'a, 'tcx, 'v> {
    fn snip(&self, e: &Expr) -> Option<String> {
        snippet_opt(self.cx, e.span)
    }

    fn simplify_not(&self, expr: &Expr) -> Option<String> {
        match expr.node {
            ExprBinary(binop, ref lhs, ref rhs) => {
                match binop.node {
                    BiEq => Some(" != "),
                    BiNe => Some(" == "),
                    BiLt => Some(" >= "),
                    BiGt => Some(" <= "),
                    BiLe => Some(" > "),
                    BiGe => Some(" < "),
                    _ => None,
                }.and_then(|op| Some(format!("{}{}{}", self.snip(lhs)?, op, self.snip(rhs)?)))
            },
            ExprMethodCall(ref path, _, ref args) if args.len() == 1 => {
                METHODS_WITH_NEGATION
                    .iter().cloned()
                    .flat_map(|(a, b)| vec![(a, b), (b, a)])
                    .find(|&(a, _)| a == path.name.as_str())
                    .and_then(|(_, neg_method)| Some(format!("{}.{}()", self.snip(&args[0])?, neg_method)))
            },
            _ => None,
        }
    }

    fn recurse(&mut self, suggestion: &Bool) -> Option<()> {
        use quine_mc_cluskey::Bool::*;
        match *suggestion {
            True => {
                self.output.push_str("true");
            },
            False => {
                self.output.push_str("false");
            },
            Not(ref inner) => match **inner {
                And(_) | Or(_) => {
                    self.output.push('!');
                    self.output.push('(');
                    self.recurse(inner);
                    self.output.push(')');
                },
                Term(n) => {
                    let terminal = self.terminals[n as usize];
                    if let Some(str) = self.simplify_not(terminal) {
                        self.simplified = true;
                        self.output.push_str(&str)
                    } else {
                        self.output.push('!');
                        let snip = self.snip(terminal)?;
                        self.output.push_str(&snip);
                    }
                },
                True | False | Not(_) => {
                    self.output.push('!');
                    self.recurse(inner)?;
                },
            },
            And(ref v) => {
                for (index, inner) in v.iter().enumerate() {
                    if index > 0 {
                        self.output.push_str(" && ");
                    }
                    if let Or(_) = *inner {
                        self.output.push('(');
                        self.recurse(inner);
                        self.output.push(')');
                    } else {
                        self.recurse(inner);
                    }
                }
            },
            Or(ref v) => {
                for (index, inner) in v.iter().enumerate() {
                    if index > 0 {
                        self.output.push_str(" || ");
                    }
                    self.recurse(inner);
                }
            },
            Term(n) => {
                let snip = self.snip(self.terminals[n as usize])?;
                self.output.push_str(&snip);
            },
        }
        Some(())
    }
}

// The boolean part of the return indicates whether some simplifications have been applied.
fn suggest(cx: &LateContext, suggestion: &Bool, terminals: &[&Expr]) -> (String, bool) {
    let mut suggest_context = SuggestContext {
        terminals: terminals,
        cx: cx,
        output: String::new(),
        simplified: false,
    };
    suggest_context.recurse(suggestion);
    (suggest_context.output, suggest_context.simplified)
}

fn simple_negate(b: Bool) -> Bool {
    use quine_mc_cluskey::Bool::*;
    match b {
        True => False,
        False => True,
        t @ Term(_) => Not(Box::new(t)),
        And(mut v) => {
            for el in &mut v {
                *el = simple_negate(::std::mem::replace(el, True));
            }
            Or(v)
        },
        Or(mut v) => {
            for el in &mut v {
                *el = simple_negate(::std::mem::replace(el, True));
            }
            And(v)
        },
        Not(inner) => *inner,
    }
}

#[derive(Default)]
struct Stats {
    terminals: [usize; 32],
    negations: usize,
    ops: usize,
}

fn terminal_stats(b: &Bool) -> Stats {
    fn recurse(b: &Bool, stats: &mut Stats) {
        match *b {
            True | False => stats.ops += 1,
            Not(ref inner) => {
                match **inner {
                    And(_) | Or(_) => stats.ops += 1, // brackets are also operations
                    _ => stats.negations += 1,
                }
                recurse(inner, stats);
            },
            And(ref v) | Or(ref v) => {
                stats.ops += v.len() - 1;
                for inner in v {
                    recurse(inner, stats);
                }
            },
            Term(n) => stats.terminals[n as usize] += 1,
        }
    }
    use quine_mc_cluskey::Bool::*;
    let mut stats = Stats::default();
    recurse(b, &mut stats);
    stats
}

impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
    fn bool_expr(&self, e: &Expr) {
        let mut h2q = Hir2Qmm {
            terminals: Vec::new(),
            cx: self.cx,
        };
        if let Ok(expr) = h2q.run(e) {
            if h2q.terminals.len() > 8 {
                // QMC has exponentially slow behavior as the number of terminals increases
                // 8 is reasonable, it takes approximately 0.2 seconds.
                // See #825
                return;
            }

            let stats = terminal_stats(&expr);
            let mut simplified = expr.simplify();
            for simple in Bool::Not(Box::new(expr.clone())).simplify() {
                match simple {
                    Bool::Not(_) | Bool::True | Bool::False => {},
                    _ => simplified.push(Bool::Not(Box::new(simple.clone()))),
                }
                let simple_negated = simple_negate(simple);
                if simplified.iter().any(|s| *s == simple_negated) {
                    continue;
                }
                simplified.push(simple_negated);
            }
            let mut improvements = Vec::new();
            'simplified: for suggestion in &simplified {
                let simplified_stats = terminal_stats(suggestion);
                let mut improvement = false;
                for i in 0..32 {
                    // ignore any "simplifications" that end up requiring a terminal more often
                    // than in the original expression
                    if stats.terminals[i] < simplified_stats.terminals[i] {
                        continue 'simplified;
                    }
                    if stats.terminals[i] != 0 && simplified_stats.terminals[i] == 0 {
                        span_lint_and_then(
                            self.cx,
                            LOGIC_BUG,
                            e.span,
                            "this boolean expression contains a logic bug",
                            |db| {
                                db.span_help(
                                    h2q.terminals[i].span,
                                    "this expression can be optimized out by applying boolean operations to the \
                                     outer expression",
                                );
                                db.span_suggestion(
                                    e.span,
                                    "it would look like the following",
                                    suggest(self.cx, suggestion, &h2q.terminals).0,
                                );
                            },
                        );
                        // don't also lint `NONMINIMAL_BOOL`
                        return;
                    }
                    // if the number of occurrences of a terminal decreases or any of the stats
                    // decreases while none increases
                    improvement |= (stats.terminals[i] > simplified_stats.terminals[i])
                        || (stats.negations > simplified_stats.negations && stats.ops == simplified_stats.ops)
                        || (stats.ops > simplified_stats.ops && stats.negations == simplified_stats.negations);
                }
                if improvement {
                    improvements.push(suggestion);
                }
            }
            let nonminimal_bool_lint = |suggestions| {
                span_lint_and_then(
                    self.cx,
                    NONMINIMAL_BOOL,
                    e.span,
                    "this boolean expression can be simplified",
                    |db| { db.span_suggestions(e.span, "try", suggestions); },
                );
            };
            if improvements.is_empty() {
                let suggest = suggest(self.cx, &expr, &h2q.terminals);
                if suggest.1 {
                    nonminimal_bool_lint(vec![suggest.0])
                }
            } else {
                nonminimal_bool_lint(
                    improvements
                        .into_iter()
                        .map(|suggestion| suggest(self.cx, suggestion, &h2q.terminals).0)
                        .collect()
                );
            }
        }
    }
}

impl<'a, 'tcx> Visitor<'tcx> for NonminimalBoolVisitor<'a, 'tcx> {
    fn visit_expr(&mut self, e: &'tcx Expr) {
        if in_macro(e.span) {
            return;
        }
        match e.node {
            ExprBinary(binop, _, _) if binop.node == BiOr || binop.node == BiAnd => self.bool_expr(e),
            ExprUnary(UnNot, ref inner) => if self.cx.tables.node_types()[inner.hir_id].is_bool() {
                self.bool_expr(e);
            } else {
                walk_expr(self, e);
            },
            _ => walk_expr(self, e),
        }
    }
    fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
        NestedVisitorMap::None
    }
}
<|endoftext|>


<commit_before>extern crate acronym;

#[test]
fn basic() {
  assert_eq!(acronym::abbreviate("Portable Network Graphics"), "PNG");
}

#[test]
#[ignore]
fn lowercase_words() {
  assert_eq!(acronym::abbreviate("Ruby on Rails"), "ROR");
}

#[test]
#[ignore]
fn camelcase() {
    assert_eq!(acronym::abbreviate("HyperText Markup Language"), "HTML");
}

#[test]
#[ignore]
fn punctuation() {
    assert_eq!(acronym::abbreviate("First In, First Out"), "FIFO");
}

#[test]
#[ignore]
fn all_caps_words() {
    assert_eq!(acronym::abbreviate("PHP: Hypertext Preprocessor"), "PHP");
}

#[test]
#[ignore]
fn non_acronym_all_caps_word() {
    assert_eq!(acronym::abbreviate("GNU Image Manipulation Program"), "GIMP");
}

#[test]
#[ignore]
fn hyphenated() {
    assert_eq!(acronym::abbreviate("Complementary metal-oxide semiconductor"),
               "CMOS");
}
<commit_msg>acronym: Add test for empty string<commit_after>extern crate acronym;

#[test]
fn empty() {
  assert_eq!(acronym::abbreviate(""), "");
}

#[test]
#[ignore]
fn basic() {
  assert_eq!(acronym::abbreviate("Portable Network Graphics"), "PNG");
}

#[test]
#[ignore]
fn lowercase_words() {
  assert_eq!(acronym::abbreviate("Ruby on Rails"), "ROR");
}

#[test]
#[ignore]
fn camelcase() {
    assert_eq!(acronym::abbreviate("HyperText Markup Language"), "HTML");
}

#[test]
#[ignore]
fn punctuation() {
    assert_eq!(acronym::abbreviate("First In, First Out"), "FIFO");
}

#[test]
#[ignore]
fn all_caps_words() {
    assert_eq!(acronym::abbreviate("PHP: Hypertext Preprocessor"), "PHP");
}

#[test]
#[ignore]
fn non_acronym_all_caps_word() {
    assert_eq!(acronym::abbreviate("GNU Image Manipulation Program"), "GIMP");
}

#[test]
#[ignore]
fn hyphenated() {
    assert_eq!(acronym::abbreviate("Complementary metal-oxide semiconductor"),
               "CMOS");
}
<|endoftext|>


<commit_before>
<commit_msg>binary-search: Added template to the stub file (#581)<commit_after>pub fn find(array: &[i32], key: i32) -> Option<usize> {
    unimplemented!(
        "Using the binary search algorithm, find the element '{}' in the array '{:?}' and return its index.",
        key,
        array
    );
}
<|endoftext|>