<commit_before>use std;
use std::iter::Peekable;

use ::opcodes::{AddressingMode, OpCode};
use assembler::token::{LexerToken, ParserToken};

#[derive(Debug, PartialEq)]
pub struct ParserError {
    pub message: String,
}

impl ParserError {
    fn expected_instruction(line: u32) -> ParserError {
        ParserError::from(format!("Instruction expected. Line {}", line))
    }

    fn invalid_opcode_addressing_mode_combination(line: u32) -> ParserError {
        ParserError::from(format!("Invalid addressing mode for opcode. Line {}", line))
    }

    fn unexpected_eol(line: u32) -> ParserError {
        ParserError::from(format!("Unexpected end of line. Line {}", line))
    }

    fn expected_eol(line: u32) -> ParserError {
        ParserError::from(format!("Expected end of line. Line {}", line))
    }

    fn cannot_parse_address(line: u32) -> ParserError {
        ParserError::from(format!("Unable to parse address. Line {}", line))
    }

    fn unexpected_token(line: u32) -> ParserError {
        ParserError::from(format!("Unexpected token. Line {}", line))
    }
}

impl From<String> for ParserError {
    fn from(error: String) -> ParserError {
        ParserError { message: error }
    }
}

impl<'a> From<&'a str> for ParserError {
    fn from(error: &str) -> ParserError {
        ParserError { message: error.into() }
    }
}

pub struct Parser {
    line: u32,
}

/// Parser processes a list of 6502 Assembly tokens
impl Parser {
    pub fn new() -> Parser {
        Parser { line: 0 }
    }

    pub fn parse(&mut self, tokens: Vec<Vec<LexerToken>>) -> Result<Vec<ParserToken>, ParserError> {
        let mut result = Vec::new();

        for line in &tokens {
            self.line += 1;

            let mut peeker = line.iter().peekable();

            // Skip blank lines
            if let None = peeker.peek() {
                continue;
            }

            let next = *peeker.peek().unwrap();

            if let &LexerToken::Ident(ref ident) = next {
                // Check if this is an opcode
                if Self::is_opcode(ident.clone()) {
                    // Yep its an opcode, lets figure out its addressing mode
                    let mut opcode = self.consume_opcode(&mut peeker, ident.clone())?;
                    result.append(&mut opcode);
                } else {
                    // Skip the ident and we'll check what is next
                    let original_ident = peeker.next().unwrap();
                    // if there is nothing else - lets mark this as a Label and move on
                    if let None = peeker.peek() {
                        result.push(ParserToken::Label(ident.clone()));
                        continue;
                    }

                    // A colon after the ident also indicates a label
                    let next = *peeker.peek().unwrap();
                    if let &LexerToken::Colon = next {
                        result.push(ParserToken::Label(ident.clone()));
                        continue;
                    }

                    // Is the next one a label as well? Thats an error:
                    if let &LexerToken::Ident(ref ident) = next {
                        // Lets add the original as a label
                        if let &LexerToken::Ident(ref original_ident) = original_ident {
                            result.push(ParserToken::Label(original_ident.clone()));
                        }

                        if !Self::is_opcode(ident.clone()) {
                            return Err(ParserError::expected_instruction(self.line));
                        } else {
                            // Oh it is an opcode after the label - consume it
                            let mut opcode = self.consume_opcode(&mut peeker, ident.clone())?;
                            result.append(&mut opcode);
                        }
                    }
                }
            }
        }

        Ok(result)
    }

    fn is_opcode<S>(mnemonic: S) -> bool
        where S: Into<String>
    {
        if let Some(opcode) = OpCode::from_mnemonic(mnemonic) {
            true
        } else {
            false
        }
    }

    fn consume_opcode<'a, I, S>(&mut self,
                                mut peeker: &mut Peekable<I>,
                                ident: S)
                                -> Result<Vec<ParserToken>, ParserError>
        where I: Iterator<Item = &'a LexerToken>,
              S: Into<String> + std::fmt::Display
    {
        // Jump over the opcode
        peeker.next();

        // If there is nothing else after this opcode.. lets check if there is
        // a matching opcode with an implied addressing mode
        if let None = peeker.peek() {
            if let Some(opcode) =
                   OpCode::from_mnemonic_and_addressing_mode(ident, AddressingMode::Implied) {
                return Ok(vec![ParserToken::OpCode(opcode)]);
            } else {
                return Err(ParserError::invalid_opcode_addressing_mode_combination(self.line));
            }
        } else {
            // Check the next token, is it an address?
            let next = *peeker.peek().unwrap();
            if let &LexerToken::Address(ref address) = next {
                // Its an address. What sort of address?
                if address.len() == 2 || address.len() == 4 {
                    // Its zero-page or absolute.. lets try and convert it to a raw byte
                    let (bytes, addressing_mode) = if address.len() == 2 {
                        // Its a 1 byte address
                        if let Ok(raw_byte) = u8::from_str_radix(&address[..], 16) {
                            (vec![raw_byte], AddressingMode::ZeroPage)
                        } else {
                            return Err(ParserError::cannot_parse_address(self.line));
                        }
                    } else {
                        // Its a 2 byte address
                        if let Ok(low_byte) = u8::from_str_radix(&address[2..], 16) {
                            if let Ok(high_byte) = u8::from_str_radix(&address[0..2], 16) {
                                (vec![low_byte, high_byte], AddressingMode::Absolute)
                            } else {
                                return Err(ParserError::cannot_parse_address(self.line));
                            }
                        } else {
                            return Err(ParserError::cannot_parse_address(self.line));
                        }
                    };
                    // consume the address and peek what is next:
                    peeker.next();
                    if let None = peeker.peek() {
                        // Nothing else.. find an opcode with this ident and addressing mode
                        if let Some(opcode) =
                               OpCode::from_mnemonic_and_addressing_mode(ident, addressing_mode) {
                            // We found one..
                            let mut final_vec = vec![ParserToken::OpCode(opcode)];
                            // Push the address bytes into the result
                            for b in bytes {
                                final_vec.push(ParserToken::RawByte(b));
                            }
                            return Ok(final_vec);
                        } else {
                            return Err(ParserError::invalid_opcode_addressing_mode_combination(self.line));
                        }
                    }

                    // There is something after this address - if its
                    // a comma, then we're peachy. If its something else.. Thats
                    // an error.
                    let next = *peeker.peek().unwrap();
                    if let &LexerToken::Comma = next {
                        // Yes, its a comma. Consume it and check what is next
                        peeker.next();
                        // If theres nothing after the comma thats an error
                        if let None = peeker.peek() {
                            return Err(ParserError::unexpected_eol(self.line));
                        }

                        let next = *peeker.peek().unwrap();
                        if let &LexerToken::Ident(ref register) = next {
                            let register = register.to_uppercase();
                            if register != "X" && register != "Y" {
                                return Err(ParserError::unexpected_token(self.line));
                            }
                            let addressing_mode = if register == "X" {
                                if addressing_mode == AddressingMode::ZeroPage {
                                    AddressingMode::ZeroPageX
                                } else {
                                    AddressingMode::AbsoluteX
                                }
                            } else {
                                if addressing_mode == AddressingMode::ZeroPage {
                                    AddressingMode::ZeroPageY
                                } else {
                                    AddressingMode::AbsoluteY
                                }
                            };
                            if let Some(opcode) =
                                   OpCode::from_mnemonic_and_addressing_mode(ident, addressing_mode) {
                                // We found one..
                                let mut final_vec = vec![ParserToken::OpCode(opcode)];
                                // Push the address bytes into the result
                                for b in bytes {
                                    final_vec.push(ParserToken::RawByte(b));
                                }
                                return Ok(final_vec);
                            } else {
                                return Err(ParserError::invalid_opcode_addressing_mode_combination(self.line));
                            }
                        } else {
                            return Err(ParserError::unexpected_token(self.line));
                        }
                    } else {
                        return Err(ParserError::unexpected_token(self.line));
                    }
                    let next = *peeker.peek().unwrap();

                } else {
                    return Err(ParserError::cannot_parse_address(self.line));
                }
            }
            Ok(vec![ParserToken::Label("BLAH".into())])
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use ::assembler::token::{ImmediateBase, LexerToken, ParserToken};
    use ::opcodes::{AddressingMode, OpCode};

    #[test]
    fn can_parse_labels_via_lonely_label() {
        let tokens = vec![vec![LexerToken::Ident("MAIN".into())],
                          vec![LexerToken::Ident("START".into())]];

        let mut parser = Parser::new();
        let result = parser.parse(tokens).unwrap();

        assert_eq!(&[ParserToken::Label("MAIN".into()), ParserToken::Label("START".into())],
                   &result[..]);
    }

    #[test]
    fn can_parse_labels_via_colon_terminator() {
        let tokens = vec![vec![LexerToken::Ident("MAIN".into())], vec![LexerToken::Colon]];

        let mut parser = Parser::new();
        let result = parser.parse(tokens).unwrap();

        assert_eq!(&[ParserToken::Label("MAIN".into())], &result[..]);
    }

    #[test]
    fn can_parse_opcodes_after_labels_on_one_line() {
        let tokens = vec![vec![LexerToken::Ident("MAIN".into()),
                               LexerToken::Ident("LDA".into()),
                               LexerToken::Address("4400".into())]];

        let mut parser = Parser::new();
        let result = parser.parse(tokens).unwrap();

        assert_eq!(&[ParserToken::Label("MAIN".into()),
                     ParserToken::OpCode(OpCode::from_mnemonic_and_addressing_mode("LDA", AddressingMode::Absolute).unwrap()),
                     ParserToken::RawByte(0),
                     ParserToken::RawByte(68)],
                   &result[..]);
    }

    #[test]
    fn can_parse_double_labels_on_one_line() {
        let tokens = vec![vec![LexerToken::Ident("MAIN".into()),
                               LexerToken::Ident("START".into())]];

        let mut parser = Parser::new();
        let result = parser.parse(tokens);

        assert_eq!(Err(ParserError::expected_instruction(1)), result);
    }

    #[test]
    fn can_parse_opcode_with_implied_addressing_mode() {
        let tokens = vec![vec![LexerToken::Ident("CLC".into())]];

        let mut parser = Parser::new();
        let result = parser.parse(tokens).unwrap();

        assert_eq!(&[ParserToken::OpCode(OpCode::from_mnemonic_and_addressing_mode("CLC", AddressingMode::Implied).unwrap())], &result[..]);
    }

    #[test]
    fn can_parse_opcode_with_correct_absolute_x_addressing_mode() {
        let tokens = vec![vec![LexerToken::Ident("MAIN".into()),
                               LexerToken::Ident("LDA".into()),
                               LexerToken::Address("4400".into()),
                               LexerToken::Comma,
                               LexerToken::Ident("X".into())]];

        let mut parser = Parser::new();
        let result = parser.parse(tokens).unwrap();

        assert_eq!(&[ParserToken::Label("MAIN".into()),
                     ParserToken::OpCode(OpCode::from_mnemonic_and_addressing_mode("LDA", AddressingMode::AbsoluteX).unwrap()),
                     ParserToken::RawByte(0),
                     ParserToken::RawByte(68)],
                   &result[..]);
    }

    #[test]
    fn errors_on_incorrect_zero_page_y_usage() {
        // LDA does not support the ZeroPageY addressing mode
        let tokens = vec![vec![LexerToken::Ident("LDA".into()),
                               LexerToken::Address("44".into()),
                               LexerToken::Comma,
                               LexerToken::Ident("Y".into())]];

        let mut parser = Parser::new();
        let result = parser.parse(tokens);

        assert_eq!(Err(ParserError::invalid_opcode_addressing_mode_combination(1)),
                   result);
    }

    #[test]
    fn can_handle_correct_zero_page_y_usage() {
        // LDX does support the ZeroPageY addressing mode
        let tokens = vec![vec![LexerToken::Ident("LDX".into()),
                               LexerToken::Address("44".into()),
                               LexerToken::Comma,
                               LexerToken::Ident("Y".into())]];

        let mut parser = Parser::new();
        let result = parser.parse(tokens).unwrap();

        assert_eq!(&[
                     ParserToken::OpCode(OpCode::from_mnemonic_and_addressing_mode("LDX", AddressingMode::ZeroPageY).unwrap()),
                     ParserToken::RawByte(68)],
                   &result[..]);
    }
}<commit_msg>Add IndirectX and IndirectY support and tests<commit_after>use std;
use std::iter::Peekable;

use ::opcodes::{AddressingMode, OpCode};
use assembler::token::{LexerToken, ParserToken};

#[derive(Debug, PartialEq)]
pub struct ParserError {
    pub message: String,
}

impl ParserError {
    fn expected_instruction(line: u32) -> ParserError {
        ParserError::from(format!("Instruction expected. Line {}", line))
    }

    fn invalid_opcode_addressing_mode_combination(line: u32) -> ParserError {
        ParserError::from(format!("Invalid addressing mode for opcode. Line {}", line))
    }

    fn unexpected_eol(line: u32) -> ParserError {
        ParserError::from(format!("Unexpected end of line. Line {}", line))
    }

    fn expected_eol(line: u32) -> ParserError {
        ParserError::from(format!("Expected end of line. Line {}", line))
    }

    fn cannot_parse_address(line: u32) -> ParserError {
        ParserError::from(format!("Unable to parse address. Line {}", line))
    }

    fn unexpected_token(line: u32) -> ParserError {
        ParserError::from(format!("Unexpected token. Line {}", line))
    }

    fn address_out_of_bounds(line: u32) -> ParserError {
        ParserError::from(format!("Address too large. Line {}", line))
    }

    fn expected_address(line: u32) -> ParserError {
        ParserError::from(format!("Unexpected token, expected address. Line {}", line))
    }
}

impl From<String> for ParserError {
    fn from(error: String) -> ParserError {
        ParserError { message: error }
    }
}

impl<'a> From<&'a str> for ParserError {
    fn from(error: &str) -> ParserError {
        ParserError { message: error.into() }
    }
}

pub struct Parser {
    line: u32,
}

/// Parser processes a list of 6502 Assembly tokens
impl Parser {
    pub fn new() -> Parser {
        Parser { line: 0 }
    }

    pub fn parse(&mut self, tokens: Vec<Vec<LexerToken>>) -> Result<Vec<ParserToken>, ParserError> {
        let mut result = Vec::new();

        for line in &tokens {
            self.line += 1;

            let mut peeker = line.iter().peekable();

            // Skip blank lines
            if let None = peeker.peek() {
                continue;
            }

            let next = *peeker.peek().unwrap();

            if let &LexerToken::Ident(ref ident) = next {
                // Check if this is an opcode
                if Self::is_opcode(ident.clone()) {
                    // Yep its an opcode, lets figure out its addressing mode
                    let mut opcode = self.consume_opcode(&mut peeker, ident.clone())?;
                    result.append(&mut opcode);
                } else {
                    // Skip the ident and we'll check what is next
                    let original_ident = peeker.next().unwrap();
                    // if there is nothing else - lets mark this as a Label and move on
                    if let None = peeker.peek() {
                        result.push(ParserToken::Label(ident.clone()));
                        continue;
                    }

                    // A colon after the ident also indicates a label
                    let next = *peeker.peek().unwrap();
                    if let &LexerToken::Colon = next {
                        result.push(ParserToken::Label(ident.clone()));
                        continue;
                    }

                    // Is the next one a label as well? Thats an error:
                    if let &LexerToken::Ident(ref ident) = next {
                        // Lets add the original as a label
                        if let &LexerToken::Ident(ref original_ident) = original_ident {
                            result.push(ParserToken::Label(original_ident.clone()));
                        }

                        if !Self::is_opcode(ident.clone()) {
                            return Err(ParserError::expected_instruction(self.line));
                        } else {
                            // Oh it is an opcode after the label - consume it
                            let mut opcode = self.consume_opcode(&mut peeker, ident.clone())?;
                            result.append(&mut opcode);
                        }
                    }
                }
            }
        }

        Ok(result)
    }

    fn is_opcode<S>(mnemonic: S) -> bool
        where S: Into<String>
    {
        if let Some(opcode) = OpCode::from_mnemonic(mnemonic) {
            true
        } else {
            false
        }
    }

    fn consume_opcode<'a, I, S>(&mut self,
                                mut peeker: &mut Peekable<I>,
                                ident: S)
                                -> Result<Vec<ParserToken>, ParserError>
        where I: Iterator<Item = &'a LexerToken>,
              S: Into<String> + std::fmt::Display
    {
        // Jump over the opcode
        peeker.next();

        // If there is nothing else after this opcode.. lets check if there is
        // a matching opcode with an implied addressing mode
        if let None = peeker.peek() {
            if let Some(opcode) =
                   OpCode::from_mnemonic_and_addressing_mode(ident, AddressingMode::Implied) {
                return Ok(vec![ParserToken::OpCode(opcode)]);
            } else {
                return Err(ParserError::invalid_opcode_addressing_mode_combination(self.line));
            }
        } else {
            // Check the next token, is it an address?
            let next = *peeker.peek().unwrap();
            if let &LexerToken::Address(ref address) = next {
                // Its an address. What sort of address?
                if address.len() == 2 || address.len() == 4 {
                    // Its zero-page or absolute.. lets try and convert it to a raw byte
                    let (bytes, addressing_mode) = if address.len() == 2 {
                        // Its a 1 byte address
                        if let Ok(raw_byte) = u8::from_str_radix(&address[..], 16) {
                            (vec![raw_byte], AddressingMode::ZeroPage)
                        } else {
                            return Err(ParserError::cannot_parse_address(self.line));
                        }
                    } else {
                        // Its a 2 byte address
                        if let Ok(low_byte) = u8::from_str_radix(&address[2..], 16) {
                            if let Ok(high_byte) = u8::from_str_radix(&address[0..2], 16) {
                                (vec![low_byte, high_byte], AddressingMode::Absolute)
                            } else {
                                return Err(ParserError::cannot_parse_address(self.line));
                            }
                        } else {
                            return Err(ParserError::cannot_parse_address(self.line));
                        }
                    };
                    // consume the address and peek what is next:
                    peeker.next();
                    if let None = peeker.peek() {
                        // Nothing else.. find an opcode with this ident and addressing mode
                        if let Some(opcode) =
                               OpCode::from_mnemonic_and_addressing_mode(ident, addressing_mode) {
                            // We found one..
                            let mut final_vec = vec![ParserToken::OpCode(opcode)];
                            // Push the address bytes into the result
                            for b in bytes {
                                final_vec.push(ParserToken::RawByte(b));
                            }
                            return Ok(final_vec);
                        } else {
                            return Err(ParserError::invalid_opcode_addressing_mode_combination(self.line));
                        }
                    }

                    // There is something after this address - if its
                    // a comma, then we're peachy. If its something else.. Thats
                    // an error.
                    let next = *peeker.peek().unwrap();
                    if let &LexerToken::Comma = next {
                        // Yes, its a comma. Consume it and check what is next
                        peeker.next();
                        // If theres nothing after the comma thats an error
                        if let None = peeker.peek() {
                            return Err(ParserError::unexpected_eol(self.line));
                        }

                        let next = *peeker.peek().unwrap();
                        if let &LexerToken::Ident(ref register) = next {
                            let register = register.to_uppercase();
                            if register != "X" && register != "Y" {
                                return Err(ParserError::unexpected_token(self.line));
                            }
                            let addressing_mode = if register == "X" {
                                if addressing_mode == AddressingMode::ZeroPage {
                                    AddressingMode::ZeroPageX
                                } else {
                                    AddressingMode::AbsoluteX
                                }
                            } else {
                                if addressing_mode == AddressingMode::ZeroPage {
                                    AddressingMode::ZeroPageY
                                } else {
                                    AddressingMode::AbsoluteY
                                }
                            };
                            if let Some(opcode) =
                                   OpCode::from_mnemonic_and_addressing_mode(ident, addressing_mode) {
                                // We found one..
                                let mut final_vec = vec![ParserToken::OpCode(opcode)];
                                // Push the address bytes into the result
                                for b in bytes {
                                    final_vec.push(ParserToken::RawByte(b));
                                }
                                return Ok(final_vec);
                            } else {
                                return Err(ParserError::invalid_opcode_addressing_mode_combination(self.line));
                            }
                        } else {
                            return Err(ParserError::unexpected_token(self.line));
                        }
                    } else {
                        return Err(ParserError::unexpected_token(self.line));
                    }
                    let next = *peeker.peek().unwrap();

                } else {
                    return Err(ParserError::cannot_parse_address(self.line));
                }
            } else if let &LexerToken::OpenParenthesis = next {
                // We're moving into Indirect memory addressing
                let addressing_mode = AddressingMode::Indirect;
                peeker.next(); // skip the opening paren

                // If we have nothing else, thats an error
                if let None = peeker.peek() {
                    return Err(ParserError::unexpected_eol(self.line));
                }

                // Is the next thing an address?
                let next = *peeker.peek().unwrap();
                if let &LexerToken::Address(ref address) = next {
                    if address.len() != 2 && address.len() != 4 {
                        return Err(ParserError::address_out_of_bounds(self.line));
                    }

                    let bytes = if address.len() == 2 {
                        // Its a 1 byte address
                        if let Ok(raw_byte) = u8::from_str_radix(&address[..], 16) {
                            vec![raw_byte]
                        } else {
                            return Err(ParserError::cannot_parse_address(self.line));
                        }
                    } else {
                        // Its a 2 byte address
                        if let Ok(low_byte) = u8::from_str_radix(&address[2..], 16) {
                            if let Ok(high_byte) = u8::from_str_radix(&address[0..2], 16) {
                                vec![low_byte, high_byte]
                            } else {
                                return Err(ParserError::cannot_parse_address(self.line));
                            }
                        } else {
                            return Err(ParserError::cannot_parse_address(self.line));
                        }
                    };

                    // The address is the right length - lets jump over that and peek next
                    peeker.next();
                    if let None = peeker.peek() {
                        return Err(ParserError::unexpected_eol(self.line));
                    }
                    let next = *peeker.peek().unwrap();
                    if let &LexerToken::Comma = next {
                        // If its a comma - lets target IndirectX
                        peeker.next(); // skip the comma
                        if let None = peeker.peek() {
                            return Err(ParserError::unexpected_eol(self.line));
                        }

                        let next = *peeker.peek().unwrap();
                        if let &LexerToken::Ident(ref register) = next {
                            let register = register.to_uppercase();
                            if register != "X" {
                                return Err(ParserError::unexpected_token(self.line));
                            }

                            peeker.next(); // Jump over the X

                            if let None = peeker.peek() {
                                return Err(ParserError::unexpected_eol(self.line));
                            }

                            let next = *peeker.peek().unwrap();
                            if let &LexerToken::CloseParenthesis = next {
                                peeker.next();
                                // Lets make sure we can find an appropriate opcode
                                if let Some(opcode) = OpCode::from_mnemonic_and_addressing_mode(ident, AddressingMode::IndirectX) {
                                    // We have everything we need now.. lets return an IndirectX opcode
                                    // accompanied by the address
                                    return Ok(vec![ParserToken::OpCode(opcode), ParserToken::RawByte(bytes[0])]);
                                } else {
                                    return Err(ParserError::invalid_opcode_addressing_mode_combination(self.line));
                                }
                            } else {
                                return Err(ParserError::unexpected_token(self.line));
                            }
                        } else {
                            return Err(ParserError::unexpected_token(self.line));
                        }
                    } else if let &LexerToken::CloseParenthesis = next {
                        // We're headed for Indirect or IndirectY ..
                        let addressing_mode = AddressingMode::IndirectY;
                        peeker.next(); // Skip the closing paren

                        if let None = peeker.peek() {
                            // If this is the end.. then lets check if this
                            // is the indirect jump: JMP ($0000)
                            if let Some(opcode) = OpCode::from_mnemonic_and_addressing_mode(ident, AddressingMode::Indirect) {
                                // Yep, we've found the only Indirect opcode
                                let mut final_vec = vec![ParserToken::OpCode(opcode)];
                                for b in bytes {
                                    final_vec.push(ParserToken::RawByte(b));
                                }
                                return Ok(final_vec);
                            } else {
                                return Err(ParserError::invalid_opcode_addressing_mode_combination(self.line));
                            }
                        }

                        // Lets check for a comma
                        let next = *peeker.peek().unwrap();
                        if let &LexerToken::Comma = next {
                            // Great, lets continue
                            peeker.next();  // Skip the comma
                            if let None = peeker.peek() {
                                return Err(ParserError::unexpected_eol(self.line));
                            }

                            let next = *peeker.peek().unwrap();
                            if let &LexerToken::Ident(ref register) = next {
                                let register = register.to_uppercase();
                                // If its not IndirectY .. thats a problem
                                if register != "Y" {
                                    return Err(ParserError::unexpected_token(self.line));
                                }
                                if let Some(opcode) = OpCode::from_mnemonic_and_addressing_mode(ident, AddressingMode::Indirect) {
                                    // Yep, we've found the only Indirect opcode
                                    let mut final_vec = vec![ParserToken::OpCode(opcode)];
                                    for b in bytes {
                                        final_vec.push(ParserToken::RawByte(b));
                                    }

                                    return Ok(final_vec);
                                } else {
                                    return Err(ParserError::invalid_opcode_addressing_mode_combination(self.line));
                                }
                            } else {
                                return Err(ParserError::unexpected_token(self.line));
                            }
                        } else {
                            return Err(ParserError::unexpected_token(self.line));
                        }
                    } else {
                        return Err(ParserError::unexpected_token(self.line));
                    }
                } else {
                    return Err(ParserError::cannot_parse_address(self.line));
                }
            } else {
                return Err(ParserError::expected_address(self.line));
            }
        }
        Ok(vec![ParserToken::Label("BLAH".into())])
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use ::assembler::token::{ImmediateBase, LexerToken, ParserToken};
    use ::opcodes::{AddressingMode, OpCode};

    #[test]
    fn can_parse_labels_via_lonely_label() {
        let tokens = vec![vec![LexerToken::Ident("MAIN".into())],
                          vec![LexerToken::Ident("START".into())]];

        let mut parser = Parser::new();
        let result = parser.parse(tokens).unwrap();

        assert_eq!(&[ParserToken::Label("MAIN".into()), ParserToken::Label("START".into())],
                   &result[..]);
    }

    #[test]
    fn can_parse_labels_via_colon_terminator() {
        let tokens = vec![vec![LexerToken::Ident("MAIN".into())], vec![LexerToken::Colon]];

        let mut parser = Parser::new();
        let result = parser.parse(tokens).unwrap();

        assert_eq!(&[ParserToken::Label("MAIN".into())], &result[..]);
    }

    #[test]
    fn can_parse_opcodes_after_labels_on_one_line() {
        let tokens = vec![vec![LexerToken::Ident("MAIN".into()),
                               LexerToken::Ident("LDA".into()),
                               LexerToken::Address("4400".into())]];

        let mut parser = Parser::new();
        let result = parser.parse(tokens).unwrap();

        assert_eq!(&[ParserToken::Label("MAIN".into()),
                     ParserToken::OpCode(OpCode::from_mnemonic_and_addressing_mode("LDA", AddressingMode::Absolute).unwrap()),
                     ParserToken::RawByte(0),
                     ParserToken::RawByte(68)],
                   &result[..]);
    }

    #[test]
    fn can_parse_double_labels_on_one_line() {
        let tokens = vec![vec![LexerToken::Ident("MAIN".into()),
                               LexerToken::Ident("START".into())]];

        let mut parser = Parser::new();
        let result = parser.parse(tokens);

        assert_eq!(Err(ParserError::expected_instruction(1)), result);
    }

    #[test]
    fn can_parse_opcode_with_implied_addressing_mode() {
        let tokens = vec![vec![LexerToken::Ident("CLC".into())]];

        let mut parser = Parser::new();
        let result = parser.parse(tokens).unwrap();

        assert_eq!(&[ParserToken::OpCode(OpCode::from_mnemonic_and_addressing_mode("CLC", AddressingMode::Implied).unwrap())], &result[..]);
    }

    #[test]
    fn can_parse_opcode_with_correct_absolute_x_addressing_mode() {
        let tokens = vec![vec![LexerToken::Ident("MAIN".into()),
                               LexerToken::Ident("LDA".into()),
                               LexerToken::Address("4400".into()),
                               LexerToken::Comma,
                               LexerToken::Ident("X".into())]];

        let mut parser = Parser::new();
        let result = parser.parse(tokens).unwrap();

        assert_eq!(&[ParserToken::Label("MAIN".into()),
                     ParserToken::OpCode(OpCode::from_mnemonic_and_addressing_mode("LDA", AddressingMode::AbsoluteX).unwrap()),
                     ParserToken::RawByte(0),
                     ParserToken::RawByte(68)],
                   &result[..]);
    }

    #[test]
    fn errors_on_incorrect_zero_page_y_usage() {
        // LDA does not support the ZeroPageY addressing mode
        let tokens = vec![vec![LexerToken::Ident("LDA".into()),
                               LexerToken::Address("44".into()),
                               LexerToken::Comma,
                               LexerToken::Ident("Y".into())]];

        let mut parser = Parser::new();
        let result = parser.parse(tokens);

        assert_eq!(Err(ParserError::invalid_opcode_addressing_mode_combination(1)),
                   result);
    }

    #[test]
    fn can_handle_correct_zero_page_y_usage() {
        // LDX does support the ZeroPageY addressing mode
        let tokens = vec![vec![LexerToken::Ident("LDX".into()),
                               LexerToken::Address("44".into()),
                               LexerToken::Comma,
                               LexerToken::Ident("Y".into())]];

        let mut parser = Parser::new();
        let result = parser.parse(tokens).unwrap();

        assert_eq!(&[
                     ParserToken::OpCode(OpCode::from_mnemonic_and_addressing_mode("LDX", AddressingMode::ZeroPageY).unwrap()),
                     ParserToken::RawByte(68)],
                   &result[..]);
    }

    #[test]
    fn can_parse_indirect_x_addressing() {
        let tokens = vec![vec![LexerToken::Ident("LDA".into()),
                               LexerToken::OpenParenthesis,
                               LexerToken::Address("44".into()),
                               LexerToken::Comma,
                               LexerToken::Ident("X".into()),
                               LexerToken::CloseParenthesis]];

        let mut parser = Parser::new();
        let result = parser.parse(tokens).unwrap();

        assert_eq!(&[
                     ParserToken::OpCode(OpCode::from_mnemonic_and_addressing_mode("LDA", AddressingMode::IndirectX).unwrap()),
                     ParserToken::RawByte(68)],
                   &result[..]);
    }
}<|endoftext|>


<commit_before>use std;
use std::str;
use std::sync::{Arc, Mutex};
use std::time::Duration;

use curl::easy::Easy;
use futures::Future;
use tokio_curl::Session;
use quick_xml::events::BytesStart;
use quick_xml::events::Event as XmlEvent;
use quick_xml::events::attributes::Attributes;
use quick_xml::reader::Reader as XmlReader;
use regex::Regex;

use errors::*;

lazy_static! {
    static ref HOST: Regex = Regex::new(r"^((?:https?://)?[^/]+)").unwrap();
}

pub trait FromXml: Sized {
    fn from_xml<B: std::io::BufRead>(reader: &mut XmlReader<B>, start: &BytesStart) -> Result<Self>;
}

fn parse_atom_link<B: std::io::BufRead>(reader: &mut XmlReader<B>, attributes: Attributes) -> Option<String> {
    let mut link_tmp = None;
    let mut is_alternate = true;
    for attribute in attributes {
        match attribute {
            Ok(attribute) => {
                match reader.decode(attribute.key).as_ref() {
                    "href" => {
                        match attribute.unescape_and_decode_value(reader) {
                            Ok(link) => link_tmp = Some(link),
                            Err(_) => continue,
                        }
                    }
                    "rel" => {
                        is_alternate = reader.decode(attribute.value).as_ref() == "alternate";
                    }
                    _ => (),
                }
            }
            Err(_) => continue,
        }
    }
    if is_alternate { link_tmp } else { None }
}

fn skip_element<B: std::io::BufRead>(reader: &mut XmlReader<B>) -> Result<()> {
    let mut buf = Vec::new();
    loop {
        match reader.read_event(&mut buf) {
            Ok(XmlEvent::Start(_)) => {
                skip_element(reader)?;
            }
            Ok(XmlEvent::End(_)) => break,
            Ok(XmlEvent::Eof) => break,
            Err(err) => return Err(err.into()),
            _ => (),
        }
        buf.clear();
    }
    Ok(())
}

impl FromXml for Option<String> {
    fn from_xml<B: std::io::BufRead>(reader: &mut XmlReader<B>, _start: &BytesStart) -> Result<Self> {
        let mut buf = Vec::new();
        let mut content: Option<String> = None;
        loop {
            match reader.read_event(&mut buf) {
                Ok(XmlEvent::Start(_)) => {
                    skip_element(reader)?;
                }
                Ok(XmlEvent::Text(ref e)) => {
                    let text = e.unescape_and_decode(&reader)?;
                    content = Some(text);
                }
                Ok(XmlEvent::CData(ref e)) => {
                    let text = reader.decode(&e).as_ref().to_owned();
                    content = Some(text);
                }
                Ok(XmlEvent::End(_)) => break,
                Ok(XmlEvent::Eof) => break,
                Err(err) => return Err(err.into()),
                _ => (),
            }
            buf.clear();
        }
        Ok(content)
    }
}

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct RSS {
    pub title: String,
    pub link: String,
    pub items: Vec<Item>,
}

impl FromXml for RSS {
    fn from_xml<B: std::io::BufRead>(reader: &mut XmlReader<B>, _start: &BytesStart) -> Result<Self> {
        let mut buf = Vec::new();
        let mut rss = RSS::default();
        loop {
            match reader.read_event(&mut buf) {
                Ok(XmlEvent::Empty(ref e)) => {
                    match reader.decode(e.name()).as_ref() {
                        "link" => {
                            if let Some(link) = parse_atom_link(reader, e.attributes()) {
                                rss.link = link;
                            }
                        }
                        _ => (),
                    }
                }
                Ok(XmlEvent::Start(ref e)) => {
                    match reader.decode(e.name()).as_ref() {
                        "title" => {
                            if let Some(title) = Option::from_xml(reader, e)? {
                                rss.title = title;
                            }
                        }
                        "link" => {
                            if let Some(link) = Option::from_xml(reader, e)? {
                                // RSS
                                rss.link = link;
                            } else {
                                // ATOM
                                if let Some(link) = parse_atom_link(reader, e.attributes()) {
                                    rss.link = link;
                                }
                            }
                        }
                        "item" | "entry" => {
                            rss.items.push(Item::from_xml(reader, e)?);
                        }
                        _ => skip_element(reader)?,
                    }
                }
                Ok(XmlEvent::End(_)) => break,
                Ok(XmlEvent::Eof) => break,
                Err(err) => return Err(err.into()),
                _ => (),
            }
            buf.clear();
        }
        Ok(rss)
    }
}

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Item {
    pub title: Option<String>,
    pub link: Option<String>,
    pub id: Option<String>,
}

impl FromXml for Item {
    fn from_xml<B: std::io::BufRead>(reader: &mut XmlReader<B>, _start: &BytesStart) -> Result<Self> {
        let mut buf = Vec::new();
        let mut item = Item::default();
        loop {
            match reader.read_event(&mut buf) {
                Ok(XmlEvent::Empty(ref e)) => {
                    match reader.decode(e.name()).as_ref() {
                        "link" => {
                            if let Some(link) = parse_atom_link(reader, e.attributes()) {
                                item.link = Some(link);
                            }
                        }
                        _ => (),
                    }
                }
                Ok(XmlEvent::Start(ref e)) => {
                    match reader.decode(e.name()).as_ref() {
                        "title" => {
                            item.title = Option::from_xml(reader, e)?;
                        }
                        "link" => {
                            if let Some(link) = Option::from_xml(reader, e)? {
                                // RSS
                                item.link = Some(link);
                            } else {
                                // ATOM
                                if let Some(link) = parse_atom_link(reader, e.attributes()) {
                                    item.link = Some(link);
                                }
                            }
                        }
                        "id" | "guid" => {
                            item.id = Option::from_xml(reader, e)?;
                        }
                        _ => skip_element(reader)?,
                    }
                }
                Ok(XmlEvent::End(_)) => break,
                Ok(XmlEvent::Eof) => break,
                Err(err) => return Err(err.into()),
                _ => (),
            }
            buf.clear();
        }
        Ok(item)
    }
}

pub fn parse<B: std::io::BufRead>(reader: B) -> Result<RSS> {
    let mut reader = XmlReader::from_reader(reader);
    reader.trim_text(true);
    let mut buf = Vec::new();
    loop {
        match reader.read_event(&mut buf) {
            Ok(XmlEvent::Start(ref e)) => {
                match reader.decode(e.name()).as_ref() {
                    "rss" => continue,
                    "channel" | "feed" => {
                        return RSS::from_xml(&mut reader, e);
                    }
                    _ => skip_element(&mut reader)?,
                }
            }
            Ok(XmlEvent::Eof) => return Err(ErrorKind::EOF.into()),
            Err(err) => return Err(err.into()),
            _ => (),
        }
        buf.clear();
    }
}

fn fix_relative_url(mut rss: RSS, rss_link: &str) -> RSS {
    let rss_host = HOST.captures(rss_link).map_or(rss_link, |r| r.get(0).unwrap().as_str());
    match rss.link.as_str() {
        "" => rss.link = rss_host.to_owned(),
        "/" => rss.link = rss_host.to_owned(),
        _ => (),
    }
    for item in rss.items.iter_mut() {
        match item.link.as_mut() {
            Some(ref mut link) if link.starts_with("//") => {
                let mut s = String::from("http:");
                s.push_str(*link);
                **link = s;
            }
            Some(ref mut link) if link.starts_with("/") => {
                let mut s = String::from(rss_host);
                s.push_str(*link);
                **link = s;
            }
            _ => (),
        }
    }

    rss
}

pub fn fetch_feed<'a>(session: Session, link: String) -> impl Future<Item = RSS, Error = Error> + 'a {
    let mut req = Easy::new();
    let buf = Arc::new(Mutex::new(Vec::new()));
    {
        let buf = buf.clone();
        req.get(true).unwrap();
        req.url(&link).unwrap();
        req.accept_encoding("").unwrap(); // accept all encoding
        req.useragent("RSSBot/1.0 (https://github.com/iovxw/rssbot)").unwrap();
        req.follow_location(true).unwrap();
        req.timeout(Duration::from_secs(10)).unwrap();
        req.write_function(move |data| {
                buf.lock().unwrap().extend_from_slice(data);
                Ok(data.len())
            })
            .unwrap();
    }
    session.perform(req).map_err(|e| e.into()).and_then(move |mut resp| {
        let response_code = resp.response_code().unwrap();
        if response_code != 200 {
            return Err(ErrorKind::Http(response_code).into());
        }
        let buf = buf.lock().unwrap();
        let rss = parse(buf.as_slice())?;
        Ok(fix_relative_url(rss, &link))
    })
}

#[test]
fn test_host_regex() {
    assert!(HOST.captures("").is_none());
    assert!(HOST.captures("/path").is_none());
    assert_eq!(&HOST.captures("example.com/path").unwrap()[0],
               "example.com");
    assert_eq!(&HOST.captures("http://example.com/path").unwrap()[0],
               "http://example.com");
    assert_eq!(&HOST.captures("https://example.com/path").unwrap()[0],
               "https://example.com");
}
<commit_msg>add support for relative RSS.link<commit_after>use std;
use std::str;
use std::sync::{Arc, Mutex};
use std::time::Duration;

use curl::easy::Easy;
use futures::Future;
use tokio_curl::Session;
use quick_xml::events::BytesStart;
use quick_xml::events::Event as XmlEvent;
use quick_xml::events::attributes::Attributes;
use quick_xml::reader::Reader as XmlReader;
use regex::Regex;

use errors::*;

lazy_static! {
    static ref HOST: Regex = Regex::new(r"^((?:https?://)?[^/]+)").unwrap();
}

pub trait FromXml: Sized {
    fn from_xml<B: std::io::BufRead>(reader: &mut XmlReader<B>, start: &BytesStart) -> Result<Self>;
}

fn parse_atom_link<B: std::io::BufRead>(reader: &mut XmlReader<B>, attributes: Attributes) -> Option<String> {
    let mut link_tmp = None;
    let mut is_alternate = true;
    for attribute in attributes {
        match attribute {
            Ok(attribute) => {
                match reader.decode(attribute.key).as_ref() {
                    "href" => {
                        match attribute.unescape_and_decode_value(reader) {
                            Ok(link) => link_tmp = Some(link),
                            Err(_) => continue,
                        }
                    }
                    "rel" => {
                        is_alternate = reader.decode(attribute.value).as_ref() == "alternate";
                    }
                    _ => (),
                }
            }
            Err(_) => continue,
        }
    }
    if is_alternate { link_tmp } else { None }
}

fn skip_element<B: std::io::BufRead>(reader: &mut XmlReader<B>) -> Result<()> {
    let mut buf = Vec::new();
    loop {
        match reader.read_event(&mut buf) {
            Ok(XmlEvent::Start(_)) => {
                skip_element(reader)?;
            }
            Ok(XmlEvent::End(_)) => break,
            Ok(XmlEvent::Eof) => break,
            Err(err) => return Err(err.into()),
            _ => (),
        }
        buf.clear();
    }
    Ok(())
}

impl FromXml for Option<String> {
    fn from_xml<B: std::io::BufRead>(reader: &mut XmlReader<B>, _start: &BytesStart) -> Result<Self> {
        let mut buf = Vec::new();
        let mut content: Option<String> = None;
        loop {
            match reader.read_event(&mut buf) {
                Ok(XmlEvent::Start(_)) => {
                    skip_element(reader)?;
                }
                Ok(XmlEvent::Text(ref e)) => {
                    let text = e.unescape_and_decode(&reader)?;
                    content = Some(text);
                }
                Ok(XmlEvent::CData(ref e)) => {
                    let text = reader.decode(&e).as_ref().to_owned();
                    content = Some(text);
                }
                Ok(XmlEvent::End(_)) => break,
                Ok(XmlEvent::Eof) => break,
                Err(err) => return Err(err.into()),
                _ => (),
            }
            buf.clear();
        }
        Ok(content)
    }
}

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct RSS {
    pub title: String,
    pub link: String,
    pub items: Vec<Item>,
}

impl FromXml for RSS {
    fn from_xml<B: std::io::BufRead>(reader: &mut XmlReader<B>, _start: &BytesStart) -> Result<Self> {
        let mut buf = Vec::new();
        let mut rss = RSS::default();
        loop {
            match reader.read_event(&mut buf) {
                Ok(XmlEvent::Empty(ref e)) => {
                    match reader.decode(e.name()).as_ref() {
                        "link" => {
                            if let Some(link) = parse_atom_link(reader, e.attributes()) {
                                rss.link = link;
                            }
                        }
                        _ => (),
                    }
                }
                Ok(XmlEvent::Start(ref e)) => {
                    match reader.decode(e.name()).as_ref() {
                        "title" => {
                            if let Some(title) = Option::from_xml(reader, e)? {
                                rss.title = title;
                            }
                        }
                        "link" => {
                            if let Some(link) = Option::from_xml(reader, e)? {
                                // RSS
                                rss.link = link;
                            } else {
                                // ATOM
                                if let Some(link) = parse_atom_link(reader, e.attributes()) {
                                    rss.link = link;
                                }
                            }
                        }
                        "item" | "entry" => {
                            rss.items.push(Item::from_xml(reader, e)?);
                        }
                        _ => skip_element(reader)?,
                    }
                }
                Ok(XmlEvent::End(_)) => break,
                Ok(XmlEvent::Eof) => break,
                Err(err) => return Err(err.into()),
                _ => (),
            }
            buf.clear();
        }
        Ok(rss)
    }
}

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Item {
    pub title: Option<String>,
    pub link: Option<String>,
    pub id: Option<String>,
}

impl FromXml for Item {
    fn from_xml<B: std::io::BufRead>(reader: &mut XmlReader<B>, _start: &BytesStart) -> Result<Self> {
        let mut buf = Vec::new();
        let mut item = Item::default();
        loop {
            match reader.read_event(&mut buf) {
                Ok(XmlEvent::Empty(ref e)) => {
                    match reader.decode(e.name()).as_ref() {
                        "link" => {
                            if let Some(link) = parse_atom_link(reader, e.attributes()) {
                                item.link = Some(link);
                            }
                        }
                        _ => (),
                    }
                }
                Ok(XmlEvent::Start(ref e)) => {
                    match reader.decode(e.name()).as_ref() {
                        "title" => {
                            item.title = Option::from_xml(reader, e)?;
                        }
                        "link" => {
                            if let Some(link) = Option::from_xml(reader, e)? {
                                // RSS
                                item.link = Some(link);
                            } else {
                                // ATOM
                                if let Some(link) = parse_atom_link(reader, e.attributes()) {
                                    item.link = Some(link);
                                }
                            }
                        }
                        "id" | "guid" => {
                            item.id = Option::from_xml(reader, e)?;
                        }
                        _ => skip_element(reader)?,
                    }
                }
                Ok(XmlEvent::End(_)) => break,
                Ok(XmlEvent::Eof) => break,
                Err(err) => return Err(err.into()),
                _ => (),
            }
            buf.clear();
        }
        Ok(item)
    }
}

pub fn parse<B: std::io::BufRead>(reader: B) -> Result<RSS> {
    let mut reader = XmlReader::from_reader(reader);
    reader.trim_text(true);
    let mut buf = Vec::new();
    loop {
        match reader.read_event(&mut buf) {
            Ok(XmlEvent::Start(ref e)) => {
                match reader.decode(e.name()).as_ref() {
                    "rss" => continue,
                    "channel" | "feed" => {
                        return RSS::from_xml(&mut reader, e);
                    }
                    _ => skip_element(&mut reader)?,
                }
            }
            Ok(XmlEvent::Eof) => return Err(ErrorKind::EOF.into()),
            Err(err) => return Err(err.into()),
            _ => (),
        }
        buf.clear();
    }
}

fn set_url_relative_to_absolute(link: &mut String, host: &str) {
    match link.as_str() {
        _ if link.starts_with("//") => {
            let mut s = String::from("http:");
            s.push_str(link);
            *link = s;
        }
        _ if link.starts_with("/") => {
            let mut s = String::from(host);
            s.push_str(link);
            *link = s;
        }
        _ => (),
    }
}

fn fix_relative_url(mut rss: RSS, rss_link: &str) -> RSS {
    let rss_host = HOST.captures(rss_link).map_or(rss_link, |r| r.get(0).unwrap().as_str());
    match rss.link.as_str() {
        "" => rss.link = rss_host.to_owned(),
        "/" => rss.link = rss_host.to_owned(),
        _ => set_url_relative_to_absolute(&mut rss.link, rss_host),
    }
    for item in rss.items.iter_mut() {
        match item.link.as_mut() {
            Some(link) => {
                set_url_relative_to_absolute(link, rss_host);
            }
            _ => (),
        }
    }

    rss
}

pub fn fetch_feed<'a>(session: Session, link: String) -> impl Future<Item = RSS, Error = Error> + 'a {
    let mut req = Easy::new();
    let buf = Arc::new(Mutex::new(Vec::new()));
    {
        let buf = buf.clone();
        req.get(true).unwrap();
        req.url(&link).unwrap();
        req.accept_encoding("").unwrap(); // accept all encoding
        req.useragent("RSSBot/1.0 (https://github.com/iovxw/rssbot)").unwrap();
        req.follow_location(true).unwrap();
        req.timeout(Duration::from_secs(10)).unwrap();
        req.write_function(move |data| {
                buf.lock().unwrap().extend_from_slice(data);
                Ok(data.len())
            })
            .unwrap();
    }
    session.perform(req).map_err(|e| e.into()).and_then(move |mut resp| {
        let response_code = resp.response_code().unwrap();
        if response_code != 200 {
            return Err(ErrorKind::Http(response_code).into());
        }
        let buf = buf.lock().unwrap();
        let rss = parse(buf.as_slice())?;
        Ok(fix_relative_url(rss, &link))
    })
}

#[test]
fn test_host_regex() {
    assert!(HOST.captures("").is_none());
    assert!(HOST.captures("/path").is_none());
    assert_eq!(&HOST.captures("example.com/path").unwrap()[0],
               "example.com");
    assert_eq!(&HOST.captures("http://example.com/path").unwrap()[0],
               "http://example.com");
    assert_eq!(&HOST.captures("https://example.com/path").unwrap()[0],
               "https://example.com");
}
<|endoftext|>


<commit_before>use {lexer, parser, ast};
use std::io::prelude::*;
use std::io::BufReader;
use std::fs::File;
use combine;
use combine::State;
use error::*;
use eval::Env;
use token::Token;

pub fn run(path: &str, mut env: &mut Env) -> Result<()> {
    let mut token_buf = Vec::<Token>::with_capacity(1024);

    let file = File::open(path)?;
    let mut buf_reader = BufReader::new(file);
    let mut source = String::with_capacity(128);
    buf_reader.read_to_string(&mut source)?;
    exec(State::new(source.as_str()), &mut token_buf, &mut env)
}

fn exec<S>(input: S, mut token_buf: &mut Vec<Token>, mut env: &mut Env) -> Result<()>
where
    S: combine::Stream<Item = char>
{
    let exprs = read(input, &mut token_buf)?;
    let result = eval(&exprs, &mut env)?;
    print(result);
    Ok(())
}

fn read<S>(input: S, mut token_buf: &mut Vec<Token>) -> Result<Vec<ast::Expr>>
where
    S: combine::Stream<Item = char>
{
    let tokens = lexer::lex(State::new(input))
        .map(|x| x.0)
        .unwrap_or(Vec::new());
    let all_tokens = token_buf.drain(..).chain(tokens).collect::<Vec<_>>();
    let token_iter = combine::from_iter(all_tokens.into_iter());
    let (exprs, unparsed) = parser::parse(combine::State::new(token_iter))?;
    token_buf.extend(unparsed.input);
    Ok(exprs)
}

fn eval(exprs: &[ast::Expr], mut env: &mut Env) -> Result<ast::Expr> {
    match exprs.split_last() {
        Some((last, rest)) => {
            for expr in rest {
                expr.eval(&mut env)?;
            }
            last.eval(&mut env)
        }
        None => Ok(ast::Expr::Nil),
    }
}

fn print(result: ast::Expr) {
    if result != ast::Expr::Nil {
        println!("{}", result);
    }
}
<commit_msg>Trim file to make lexing work<commit_after>use {lexer, parser, ast};
use std::io::prelude::*;
use std::io::BufReader;
use std::fs::File;
use combine;
use combine::State;
use error::*;
use eval::Env;
use token::Token;

pub fn run(path: &str, mut env: &mut Env) -> Result<()> {
    let mut token_buf = Vec::<Token>::with_capacity(1024);

    let file = File::open(path)?;
    let mut buf_reader = BufReader::new(file);
    let mut source = String::with_capacity(128);
    buf_reader.read_to_string(&mut source)?;
    // TODO: why is trimming necessary?
    exec(State::new(source.trim()), &mut token_buf, &mut env)
}

fn exec<S>(input: S, mut token_buf: &mut Vec<Token>, mut env: &mut Env) -> Result<()>
where
    S: combine::Stream<Item = char>
{
    let exprs = read(input, &mut token_buf)?;
    let result = eval(&exprs, &mut env)?;
    print(result);
    Ok(())
}

fn read<S>(input: S, mut token_buf: &mut Vec<Token>) -> Result<Vec<ast::Expr>>
where
    S: combine::Stream<Item = char>
{
    let tokens = lexer::lex(input)
        .map(|x| x.0)
        .unwrap_or(Vec::new());
    let all_tokens = token_buf.drain(..).chain(tokens).collect::<Vec<_>>();
    let token_iter = combine::from_iter(all_tokens.into_iter());
    let (exprs, unparsed) = parser::parse(combine::State::new(token_iter))?;
    token_buf.extend(unparsed.input);
    Ok(exprs)
}

fn eval(exprs: &[ast::Expr], mut env: &mut Env) -> Result<ast::Expr> {
    match exprs.split_last() {
        Some((last, rest)) => {
            for expr in rest {
                expr.eval(&mut env)?;
            }
            last.eval(&mut env)
        }
        None => Ok(ast::Expr::Nil),
    }
}

fn print(result: ast::Expr) {
    if result != ast::Expr::Nil {
        println!("{}", result);
    }
}
<|endoftext|>


<commit_before>use std::char;
use std::collections::BTreeMap;
use std::fs::File;
use std::io;
use std::io::{BufRead, BufReader, BufWriter, Write};
use std::num::ParseIntError;
use std::path::Path;

#[cfg(test)]
use std::collections::BTreeSet;
#[cfg(test)]
use std::iter::FromIterator;

use fst;
use fst::MapBuilder;
use fst_generator;
use unicode_names2;

quick_error! {
    #[derive(Debug)]
    pub enum Error {
        Format(err: ParseIntError) {
            from()
            cause(err)
            description("Hex number format error")
        }
        Block(ch: u32) {
            display("Inconsistent block at {}", ch)
        }
        IO(err: io::Error) {
            from()
                cause(err)
                description("Couldn't read from file")
        }
        FST(err: fst::Error) {
            from()
                cause(err)
                description("Something went wrong inserting into the FST")
        }
    }
}

#[derive(PartialEq, Debug)]
enum LineType {
    None,
    Simple,
    BlockStart(u32),
    BlockEnd(u32),
}

fn process_line(names: &mut fst_generator::Names, line: &str) -> Result<LineType, Error> {
    if line.starts_with('#') || line.trim_left() == "" {
        return Ok(LineType::None);
    }
    let fields: Vec<&str> = line.splitn(15, ';').collect();
    let cp = try!(u32::from_str_radix(fields[0], 16));
    if let Some(ch) = char::from_u32(cp) {
        let query = fields[1].to_owned();
        if query.ends_with(", First>") {
            return Ok(LineType::BlockStart(ch as u32));
        } else if query.ends_with(", Last>") {
            return Ok(LineType::BlockEnd(ch as u32));
        }
        names.insert(vec![query], ch);
        // TODO: Add the unicode_names2 name here
        match fields.get(10) {
            Some(&"") | None => {}
            Some(name) => names.insert(vec![name.to_string()], ch),
        }
        Ok(LineType::Simple)
    } else {
        Ok(LineType::None)
    }
}

#[test]
fn test_processing() {
    {
        let mut sorted_names = fst_generator::Names::new();

        // Non-data gets skipped:
        assert_eq!(
            LineType::None,
            process_line(&mut sorted_names, "# this is a comment").unwrap()
        );
        assert_eq!(LineType::None, process_line(&mut sorted_names, "").unwrap());
        assert_eq!(
            LineType::None,
            process_line(&mut sorted_names, "    ").unwrap()
        );
    }
    {
        let mut sorted_names = fst_generator::Names::new();
        assert_eq!(
            LineType::Simple,
            process_line(
                &mut sorted_names,
                "03BB;GREEK SMALL LETTER LAMDA;Ll;0;L;;;;;N;GREEK SMALL LETTER LAMBDA;;039B;;039B"
            ).unwrap()
        );
        let have = BTreeSet::from_iter(sorted_names.iter().map(|(name, chs)| {
            let v: Vec<char> = chs.into_iter().map(|ch| ch.to_owned()).collect();
            (name.as_str(), v)
        }));
        let want = BTreeSet::from_iter(vec![
            // Current unicode spelling:
            ("greek", vec!['\u{03bb}']),
            ("greek small letter lamda", vec!['\u{03bb}']),
            ("lamda", vec!['\u{03bb}']),
            // Unicode 1.0 spelling:
            ("greek small letter lambda", vec!['\u{03bb}']),
            ("lambda", vec!['\u{03bb}']),
        ]);
        assert_eq!(have, want);
    }

    {
        let mut sorted_names = fst_generator::Names::new();
        // Some from NameAliases.txt:
        assert_eq!(
            LineType::Simple,
            process_line(&mut sorted_names, "0091;PRIVATE USE ONE;control").unwrap()
        );
        assert_eq!(
            LineType::Simple,
            process_line(&mut sorted_names, "0092;PRIVATE USE TWO;control").unwrap()
        );

        assert_eq!(
            LineType::Simple,
            process_line(&mut sorted_names, "0005;ENQUIRY;control").unwrap()
        );
        assert_eq!(
            LineType::Simple,
            process_line(&mut sorted_names, "200D;ZWJ;abbreviation").unwrap()
        );

        // And some from UnicodeData.txt:
        assert_eq!(
            LineType::Simple,
            process_line(
                &mut sorted_names,
                "00AE;REGISTERED SIGN;So;0;ON;;;;;N;REGISTERED TRADE MARK SIGN;;;;"
            ).unwrap()
        );
        assert_eq!(
            LineType::Simple,
            process_line(
                &mut sorted_names,
                "0214;LATIN CAPITAL LETTER U WITH DOUBLE GRAVE;Lu;0;L;0055 \
                 030F;;;;N;;;;0215;e"
            ).unwrap()
        );

        // CJK blocks:
        assert_eq!(
            LineType::BlockStart(0x3400),
            process_line(
                &mut sorted_names,
                "3400;<CJK Ideograph Extension A, First>;Lo;0;L;;;;;N;;;;;"
            ).unwrap()
        );
        assert_eq!(
            LineType::BlockEnd(0x4DB5),
            process_line(
                &mut sorted_names,
                "4DB5;<CJK Ideograph Extension A, Last>;Lo;0;L;;;;;N;;;;;"
            ).unwrap()
        );

        let have = BTreeSet::from_iter(sorted_names.iter().map(|(name, chs)| {
            let v: Vec<char> = chs.into_iter().map(|ch| ch.to_owned()).collect();
            (name.as_str(), v)
        }));
        let want = BTreeSet::from_iter(vec![
            ("capital", vec!['\u{0214}']),
            ("double", vec!['\u{0214}']),
            ("enquiry", vec!['\u{0005}']),
            ("grave", vec!['\u{0214}']),
            ("latin", vec!['\u{0214}']),
            ("latin capital letter u with double grave", vec!['\u{0214}']),
            ("one", vec!['\u{91}']),
            ("two", vec!['\u{92}']),
            ("private", vec!['\u{91}', '\u{92}']),
            ("use", vec!['\u{91}', '\u{92}']),
            ("private use one", vec!['\u{91}']),
            ("private use two", vec!['\u{92}']),
            ("registered", vec!['\u{AE}']),
            ("trade", vec!['\u{AE}']),
            ("mark", vec!['\u{AE}']),
            ("registered sign", vec!['\u{AE}']),
            ("registered trade mark sign", vec!['\u{AE}']),
            // Skip the "U": it's too short to be meaningful
            ("zwj", vec!['\u{200D}']),
        ]);
        assert_eq!(have, want);
    }
}

#[test]
fn test_old_names() {}

pub fn read_names(names: &mut fst_generator::Names, file: &Path) -> Result<(), Error> {
    let reader = BufReader::new(try!(File::open(file)));
    let mut lines = reader.lines();
    while let Some(line) = lines.next() {
        match try!(process_line(names, try!(line).as_str())) {
            LineType::Simple | LineType::None => {}
            LineType::BlockStart(start) => {
                let line = lines.next().ok_or(Error::Block(start))??;
                match try!(process_line(names, &line)) {
                    LineType::Simple | LineType::None | LineType::BlockStart(_) => {
                        return Err(Error::Block(start))
                    }
                    LineType::BlockEnd(end) => for i in start..=end {
                        if let Some(ch) = char::from_u32(i as u32) {
                            if let Some(name) = unicode_names2::name(ch) {
                                names.insert(vec![name.to_string()], ch);
                            }
                        }
                    },
                }
            }
            LineType::BlockEnd(end) => {
                return Err(Error::Block(end));
            }
        }
    }
    Ok(())
}

pub fn write_name_data(names: &fst_generator::Names, output: &Path) -> Result<(), Error> {
    let fst_byte_filename = output.join("name_fst.bin");
    let out = BufWriter::new(try!(File::create(fst_byte_filename)));
    let mut map_builder = try!(MapBuilder::new(out));

    let mut counter: u64 = 0;
    let mut results: BTreeMap<String, u64> = BTreeMap::new();

    for (name, chs) in names.iter() {
        if chs.len() > 1 {
            let mut key = String::new();
            for c in chs {
                key.push(*c)
            }

            let num: u64 = (0xff << 32) | *results.entry(key).or_insert_with(|| {
                counter += 1;
                counter - 1
            });
            try!(map_builder.insert(name, num));
        } else {
            try!(map_builder.insert(name, *chs.iter().next().unwrap() as u64));
        }
    }
    try!(map_builder.finish());

    // Now generate the multi-results file:
    let multi_result_filename = output.join("names.rs");
    let mut rust_out = BufWriter::new(try!(File::create(multi_result_filename)));
    let mut ambiguous_chars: Vec<String> = vec![String::new(); counter as usize];
    for (chars, i) in results {
        ambiguous_chars[i as usize] = chars;
    }
    try!(writeln!(&mut rust_out, "/// Generated with `make names`"));
    try!(writeln!(
        &mut rust_out,
        "#[cfg_attr(rustfmt, rustfmt_skip)]"
    ));
    try!(writeln!(
        &mut rust_out,
        "pub static AMBIGUOUS_CHARS: &'static [&'static str; {}] = &[",
        ambiguous_chars.len()
    ));
    for chars in ambiguous_chars {
        try!(writeln!(&mut rust_out, "    {:?},", chars));
    }
    try!(writeln!(&mut rust_out, "];"));

    Ok(())
}
<commit_msg>Update to range syntax, for current stable rust<commit_after>use std::char;
use std::collections::BTreeMap;
use std::fs::File;
use std::io;
use std::io::{BufRead, BufReader, BufWriter, Write};
use std::num::ParseIntError;
use std::path::Path;

#[cfg(test)]
use std::collections::BTreeSet;
#[cfg(test)]
use std::iter::FromIterator;

use fst;
use fst::MapBuilder;
use fst_generator;
use unicode_names2;

quick_error! {
    #[derive(Debug)]
    pub enum Error {
        Format(err: ParseIntError) {
            from()
            cause(err)
            description("Hex number format error")
        }
        Block(ch: u32) {
            display("Inconsistent block at {}", ch)
        }
        IO(err: io::Error) {
            from()
                cause(err)
                description("Couldn't read from file")
        }
        FST(err: fst::Error) {
            from()
                cause(err)
                description("Something went wrong inserting into the FST")
        }
    }
}

#[derive(PartialEq, Debug)]
enum LineType {
    None,
    Simple,
    BlockStart(u32),
    BlockEnd(u32),
}

fn process_line(names: &mut fst_generator::Names, line: &str) -> Result<LineType, Error> {
    if line.starts_with('#') || line.trim_left() == "" {
        return Ok(LineType::None);
    }
    let fields: Vec<&str> = line.splitn(15, ';').collect();
    let cp = try!(u32::from_str_radix(fields[0], 16));
    if let Some(ch) = char::from_u32(cp) {
        let query = fields[1].to_owned();
        if query.ends_with(", First>") {
            return Ok(LineType::BlockStart(ch as u32));
        } else if query.ends_with(", Last>") {
            return Ok(LineType::BlockEnd(ch as u32));
        }
        names.insert(vec![query], ch);
        // TODO: Add the unicode_names2 name here
        match fields.get(10) {
            Some(&"") | None => {}
            Some(name) => names.insert(vec![name.to_string()], ch),
        }
        Ok(LineType::Simple)
    } else {
        Ok(LineType::None)
    }
}

#[test]
fn test_processing() {
    {
        let mut sorted_names = fst_generator::Names::new();

        // Non-data gets skipped:
        assert_eq!(
            LineType::None,
            process_line(&mut sorted_names, "# this is a comment").unwrap()
        );
        assert_eq!(LineType::None, process_line(&mut sorted_names, "").unwrap());
        assert_eq!(
            LineType::None,
            process_line(&mut sorted_names, "    ").unwrap()
        );
    }
    {
        let mut sorted_names = fst_generator::Names::new();
        assert_eq!(
            LineType::Simple,
            process_line(
                &mut sorted_names,
                "03BB;GREEK SMALL LETTER LAMDA;Ll;0;L;;;;;N;GREEK SMALL LETTER LAMBDA;;039B;;039B"
            ).unwrap()
        );
        let have = BTreeSet::from_iter(sorted_names.iter().map(|(name, chs)| {
            let v: Vec<char> = chs.into_iter().map(|ch| ch.to_owned()).collect();
            (name.as_str(), v)
        }));
        let want = BTreeSet::from_iter(vec![
            // Current unicode spelling:
            ("greek", vec!['\u{03bb}']),
            ("greek small letter lamda", vec!['\u{03bb}']),
            ("lamda", vec!['\u{03bb}']),
            // Unicode 1.0 spelling:
            ("greek small letter lambda", vec!['\u{03bb}']),
            ("lambda", vec!['\u{03bb}']),
        ]);
        assert_eq!(have, want);
    }

    {
        let mut sorted_names = fst_generator::Names::new();
        // Some from NameAliases.txt:
        assert_eq!(
            LineType::Simple,
            process_line(&mut sorted_names, "0091;PRIVATE USE ONE;control").unwrap()
        );
        assert_eq!(
            LineType::Simple,
            process_line(&mut sorted_names, "0092;PRIVATE USE TWO;control").unwrap()
        );

        assert_eq!(
            LineType::Simple,
            process_line(&mut sorted_names, "0005;ENQUIRY;control").unwrap()
        );
        assert_eq!(
            LineType::Simple,
            process_line(&mut sorted_names, "200D;ZWJ;abbreviation").unwrap()
        );

        // And some from UnicodeData.txt:
        assert_eq!(
            LineType::Simple,
            process_line(
                &mut sorted_names,
                "00AE;REGISTERED SIGN;So;0;ON;;;;;N;REGISTERED TRADE MARK SIGN;;;;"
            ).unwrap()
        );
        assert_eq!(
            LineType::Simple,
            process_line(
                &mut sorted_names,
                "0214;LATIN CAPITAL LETTER U WITH DOUBLE GRAVE;Lu;0;L;0055 \
                 030F;;;;N;;;;0215;e"
            ).unwrap()
        );

        // CJK blocks:
        assert_eq!(
            LineType::BlockStart(0x3400),
            process_line(
                &mut sorted_names,
                "3400;<CJK Ideograph Extension A, First>;Lo;0;L;;;;;N;;;;;"
            ).unwrap()
        );
        assert_eq!(
            LineType::BlockEnd(0x4DB5),
            process_line(
                &mut sorted_names,
                "4DB5;<CJK Ideograph Extension A, Last>;Lo;0;L;;;;;N;;;;;"
            ).unwrap()
        );

        let have = BTreeSet::from_iter(sorted_names.iter().map(|(name, chs)| {
            let v: Vec<char> = chs.into_iter().map(|ch| ch.to_owned()).collect();
            (name.as_str(), v)
        }));
        let want = BTreeSet::from_iter(vec![
            ("capital", vec!['\u{0214}']),
            ("double", vec!['\u{0214}']),
            ("enquiry", vec!['\u{0005}']),
            ("grave", vec!['\u{0214}']),
            ("latin", vec!['\u{0214}']),
            ("latin capital letter u with double grave", vec!['\u{0214}']),
            ("one", vec!['\u{91}']),
            ("two", vec!['\u{92}']),
            ("private", vec!['\u{91}', '\u{92}']),
            ("use", vec!['\u{91}', '\u{92}']),
            ("private use one", vec!['\u{91}']),
            ("private use two", vec!['\u{92}']),
            ("registered", vec!['\u{AE}']),
            ("trade", vec!['\u{AE}']),
            ("mark", vec!['\u{AE}']),
            ("registered sign", vec!['\u{AE}']),
            ("registered trade mark sign", vec!['\u{AE}']),
            // Skip the "U": it's too short to be meaningful
            ("zwj", vec!['\u{200D}']),
        ]);
        assert_eq!(have, want);
    }
}

#[test]
fn test_old_names() {}

pub fn read_names(names: &mut fst_generator::Names, file: &Path) -> Result<(), Error> {
    let reader = BufReader::new(try!(File::open(file)));
    let mut lines = reader.lines();
    while let Some(line) = lines.next() {
        match try!(process_line(names, try!(line).as_str())) {
            LineType::Simple | LineType::None => {}
            LineType::BlockStart(start) => {
                let line = lines.next().ok_or(Error::Block(start))??;
                match try!(process_line(names, &line)) {
                    LineType::Simple | LineType::None | LineType::BlockStart(_) => {
                        return Err(Error::Block(start))
                    }
                    // TODO: update to inclusive range syntax when it's stable:
                    LineType::BlockEnd(end) => for i in start..end + 1 {
                        if let Some(ch) = char::from_u32(i as u32) {
                            if let Some(name) = unicode_names2::name(ch) {
                                names.insert(vec![name.to_string()], ch);
                            }
                        }
                    },
                }
            }
            LineType::BlockEnd(end) => {
                return Err(Error::Block(end));
            }
        }
    }
    Ok(())
}

pub fn write_name_data(names: &fst_generator::Names, output: &Path) -> Result<(), Error> {
    let fst_byte_filename = output.join("name_fst.bin");
    let out = BufWriter::new(try!(File::create(fst_byte_filename)));
    let mut map_builder = try!(MapBuilder::new(out));

    let mut counter: u64 = 0;
    let mut results: BTreeMap<String, u64> = BTreeMap::new();

    for (name, chs) in names.iter() {
        if chs.len() > 1 {
            let mut key = String::new();
            for c in chs {
                key.push(*c)
            }

            let num: u64 = (0xff << 32) | *results.entry(key).or_insert_with(|| {
                counter += 1;
                counter - 1
            });
            try!(map_builder.insert(name, num));
        } else {
            try!(map_builder.insert(name, *chs.iter().next().unwrap() as u64));
        }
    }
    try!(map_builder.finish());

    // Now generate the multi-results file:
    let multi_result_filename = output.join("names.rs");
    let mut rust_out = BufWriter::new(try!(File::create(multi_result_filename)));
    let mut ambiguous_chars: Vec<String> = vec![String::new(); counter as usize];
    for (chars, i) in results {
        ambiguous_chars[i as usize] = chars;
    }
    try!(writeln!(&mut rust_out, "/// Generated with `make names`"));
    try!(writeln!(
        &mut rust_out,
        "#[cfg_attr(rustfmt, rustfmt_skip)]"
    ));
    try!(writeln!(
        &mut rust_out,
        "pub static AMBIGUOUS_CHARS: &'static [&'static str; {}] = &[",
        ambiguous_chars.len()
    ));
    for chars in ambiguous_chars {
        try!(writeln!(&mut rust_out, "    {:?},", chars));
    }
    try!(writeln!(&mut rust_out, "];"));

    Ok(())
}
<|endoftext|>


<commit_before>use yaml_rust::{YamlLoader, Yaml};

use std::collections::btree_map::BTreeMap;
use std::fs::{OpenOptions,remove_file};
use std::io;
use std::io::{Write,Read};
use std::path::{Path,PathBuf};

// TODO: add comments to the yaml
//        * document that empty branch means track current
// TODO: load this file from disk
static DEFAULT_CONF: &'static str = "---
symbols:
    new: '✚'
    changed: '■'
    staged: '●'
    conflicts: '✖'
    difference_ahead: '↑'
    difference_behind: '↓'
monitor_remotes:
    upstream: master
colors:
    branch: 'blue'
    remote_difference: 'white'
    new: 'red'
    changed: 'red'
    staged: 'green'
    conflicts: 'yellow'
";

pub struct Conf {
    c: Yaml,
}

impl Conf {
    pub fn new(yaml: Yaml) -> Conf {
        Conf { c: yaml }
    }

    pub fn get_new_symbol(&self) -> String {
        String::from(self.c["symbols"]["new"].as_str().unwrap())
    }
    pub fn get_changed_symbol(&self) -> String {
        String::from(self.c["symbols"]["changed"].as_str().unwrap())
    }
    pub fn get_staged_symbol(&self) -> String {
        String::from(self.c["symbols"]["staged"].as_str().unwrap())
    }
    pub fn get_conflicts_symbol(&self) -> String {
        String::from(self.c["symbols"]["conflicts"].as_str().unwrap())
    }
    pub fn get_difference_ahead_symbol(&self) -> String {
        String::from(self.c["symbols"]["difference_ahead"].as_str().unwrap())
    }
    pub fn get_difference_behind_symbol(&self) -> String {
        String::from(self.c["symbols"]["difference_behind"].as_str().unwrap())
    }

    pub fn get_remotes_monitoring(&self) -> BTreeMap<String, String> {
        let remotes = self.c["monitor_remotes"].as_hash().unwrap();
        let mut response: BTreeMap<String, String> = BTreeMap::new();
        for (k, v) in remotes {
            response.insert(String::from(k.as_str().unwrap()), String::from(v.as_str().unwrap()));
        }
        response
    }

    pub fn get_branch_color(&self) -> String {
        String::from(self.c["colors"]["branch"].as_str().unwrap())
    }
    pub fn get_remote_difference_color(&self) -> String {
        String::from(self.c["colors"]["remote_difference"].as_str().unwrap())
    }
    pub fn get_new_color(&self) -> String {
        String::from(self.c["colors"]["new"].as_str().unwrap())
    }
    pub fn get_changed_color(&self) -> String {
        String::from(self.c["colors"]["changed"].as_str().unwrap())
    }
    pub fn get_staged_color(&self) -> String {
        String::from(self.c["colors"]["staged"].as_str().unwrap())
    }
    pub fn get_conflicts_color(&self) -> String {
        String::from(self.c["colors"]["conflicts"].as_str().unwrap())
    }
}

pub fn get_default_configuration() -> Conf {
    let docs = YamlLoader::load_from_str(DEFAULT_CONF).unwrap();
    Conf::new(docs[0].clone())
}

pub fn create_default_config(path: PathBuf) -> Result<String, io::Error> {
    match OpenOptions::new()
                .write(true)
                .create_new(true)
                .open(path.clone()) {
        Ok(mut file) => {
            match file.write_all(&String::from(DEFAULT_CONF).into_bytes()) {
                Ok(_) => (),
                Err(e) => return Err(e)
            };
            match file.flush() {
                Ok(_) => (),
                Err(e) => return Err(e)
            };
            Ok(String::from(path.to_str().unwrap()))
        },
        Err(e) => Err(e)
    }
}

#[test]
fn test_default_new_symbol() {
    let c = get_default_configuration();
    assert_eq!(c.get_new_symbol(), "✚");
}
#[test]
fn test_default_changed_symbol() {
    let c = get_default_configuration();
    assert_eq!(c.get_changed_symbol(), "■");
}
#[test]
fn test_default_staged_symbol() {
    let c = get_default_configuration();
    assert_eq!(c.get_staged_symbol(), "●");
}
#[test]
fn test_default_conflicts_symbol() {
    let c = get_default_configuration();
    assert_eq!(c.get_conflicts_symbol(), "✖");
}
#[test]
fn test_difference_ahead_symbol() {
    let c = get_default_configuration();
    assert_eq!(c.get_difference_ahead_symbol(), "↑");
}
#[test]
fn test_difference_behind_symbol() {
    let c = get_default_configuration();
    assert_eq!(c.get_difference_behind_symbol(), "↓");
}
#[test]
fn test_default_monitored_remotes() {
    let c = get_default_configuration();
    let remotes = c.get_remotes_monitoring();
    assert_eq!(remotes["upstream"], String::from("master"));
}

#[test]
fn test_monitored_remotes_ordering() {
    let config = "
    monitor_remotes:
        a: b
        c: d
        e: f
    ";
    let docs = YamlLoader::load_from_str(config).unwrap();
    let c = Conf::new(docs[0].clone());
    let remotes = c.get_remotes_monitoring();
    let mut iter = remotes.iter();
    assert_eq!(iter.next().unwrap(), (&String::from("a"), &String::from("b")));
    assert_eq!(iter.next().unwrap(), (&String::from("c"), &String::from("d")));
    assert_eq!(iter.next().unwrap(), (&String::from("e"), &String::from("f")));
}
#[test]
fn test_default_branch_color() {
    let c = get_default_configuration();
    assert_eq!(c.get_branch_color(), "blue");
}
#[test]
fn test_default_remote_difference_color() {
    let c = get_default_configuration();
    assert_eq!(c.get_remote_difference_color(), "white");
}
#[test]
fn test_default_new_color() {
    let c = get_default_configuration();
    assert_eq!(c.get_new_color(), "red");
}
#[test]
fn test_default_changed_color() {
    let c = get_default_configuration();
    assert_eq!(c.get_changed_color(), "red");
}
#[test]
fn test_default_staged_color() {
    let c = get_default_configuration();
    assert_eq!(c.get_staged_color(), "green");
}
#[test]
fn test_default_conflicts_color() {
    let c = get_default_configuration();
    assert_eq!(c.get_conflicts_color(), "yellow");
}
#[allow(unused_must_use)]
#[test]
fn test_create_default_config() {
    let p = PathBuf::from("/tmp/test_pretty_git_prompt_config1");
    if Path::new(&p).exists() {
        remove_file(p.clone());
    }

    let result = create_default_config(p.clone());
    assert!(result.is_ok());

    let mut file = OpenOptions::new()
                .read(true)
                .open(p.clone()).unwrap();
    let mut contents = String::new();
    file.read_to_string(&mut contents);
    assert_eq!(contents, DEFAULT_CONF);

    remove_file(p.clone());
}
#[test]
fn test_create_default_config_when_exists() {
    let p = PathBuf::from("/tmp/test_pretty_git_prompt_config2");
    OpenOptions::new()
                .write(true)
                .create(true)
                .truncate(true)
                .open(p.clone());
    assert!(Path::new(&p).exists());

    let result = create_default_config(p.clone());
    assert!(result.is_err());

    remove_file(p.clone());
}
<commit_msg>polish: better symbols, color<commit_after>use yaml_rust::{YamlLoader, Yaml};

use std::collections::btree_map::BTreeMap;
use std::fs::{OpenOptions,remove_file};
use std::io;
use std::io::{Write,Read};
use std::path::{Path,PathBuf};

// TODO: add comments to the yaml
//        * document that empty branch means track current
// TODO: load this file from disk
// TODO: prepare for future: make dict of symbols, colors, remotes
//        * enable specifying bold
static DEFAULT_CONF: &'static str = "---
symbols:
    new: '✚'
    changed: 'Δ'
    staged: '▶'
    conflicts: '✖'
    difference_ahead: '↑'
    difference_behind: '↓'
monitor_remotes:
    upstream: master
colors:
    branch: 'blue'
    remote_difference: 'white'
    new: '014'
    changed: 'red'
    staged: 'green'
    conflicts: 'yellow'
";

pub struct Conf {
    c: Yaml,
}

impl Conf {
    pub fn new(yaml: Yaml) -> Conf {
        Conf { c: yaml }
    }

    pub fn get_new_symbol(&self) -> String {
        String::from(self.c["symbols"]["new"].as_str().unwrap())
    }
    pub fn get_changed_symbol(&self) -> String {
        String::from(self.c["symbols"]["changed"].as_str().unwrap())
    }
    pub fn get_staged_symbol(&self) -> String {
        String::from(self.c["symbols"]["staged"].as_str().unwrap())
    }
    pub fn get_conflicts_symbol(&self) -> String {
        String::from(self.c["symbols"]["conflicts"].as_str().unwrap())
    }
    pub fn get_difference_ahead_symbol(&self) -> String {
        String::from(self.c["symbols"]["difference_ahead"].as_str().unwrap())
    }
    pub fn get_difference_behind_symbol(&self) -> String {
        String::from(self.c["symbols"]["difference_behind"].as_str().unwrap())
    }

    pub fn get_remotes_monitoring(&self) -> BTreeMap<String, String> {
        let remotes = self.c["monitor_remotes"].as_hash().unwrap();
        let mut response: BTreeMap<String, String> = BTreeMap::new();
        for (k, v) in remotes {
            response.insert(String::from(k.as_str().unwrap()), String::from(v.as_str().unwrap()));
        }
        response
    }

    pub fn get_branch_color(&self) -> String {
        String::from(self.c["colors"]["branch"].as_str().unwrap())
    }
    pub fn get_remote_difference_color(&self) -> String {
        String::from(self.c["colors"]["remote_difference"].as_str().unwrap())
    }
    pub fn get_new_color(&self) -> String {
        String::from(self.c["colors"]["new"].as_str().unwrap())
    }
    pub fn get_changed_color(&self) -> String {
        String::from(self.c["colors"]["changed"].as_str().unwrap())
    }
    pub fn get_staged_color(&self) -> String {
        String::from(self.c["colors"]["staged"].as_str().unwrap())
    }
    pub fn get_conflicts_color(&self) -> String {
        String::from(self.c["colors"]["conflicts"].as_str().unwrap())
    }
}

pub fn get_default_configuration() -> Conf {
    let docs = YamlLoader::load_from_str(DEFAULT_CONF).unwrap();
    Conf::new(docs[0].clone())
}

pub fn create_default_config(path: PathBuf) -> Result<String, io::Error> {
    match OpenOptions::new()
                .write(true)
                .create_new(true)
                .open(path.clone()) {
        Ok(mut file) => {
            match file.write_all(&String::from(DEFAULT_CONF).into_bytes()) {
                Ok(_) => (),
                Err(e) => return Err(e)
            };
            match file.flush() {
                Ok(_) => (),
                Err(e) => return Err(e)
            };
            Ok(String::from(path.to_str().unwrap()))
        },
        Err(e) => Err(e)
    }
}

#[test]
fn test_default_new_symbol() {
    let c = get_default_configuration();
    assert_eq!(c.get_new_symbol(), "✚");
}
#[test]
fn test_default_changed_symbol() {
    let c = get_default_configuration();
    assert_eq!(c.get_changed_symbol(), "■");
}
#[test]
fn test_default_staged_symbol() {
    let c = get_default_configuration();
    assert_eq!(c.get_staged_symbol(), "●");
}
#[test]
fn test_default_conflicts_symbol() {
    let c = get_default_configuration();
    assert_eq!(c.get_conflicts_symbol(), "✖");
}
#[test]
fn test_difference_ahead_symbol() {
    let c = get_default_configuration();
    assert_eq!(c.get_difference_ahead_symbol(), "↑");
}
#[test]
fn test_difference_behind_symbol() {
    let c = get_default_configuration();
    assert_eq!(c.get_difference_behind_symbol(), "↓");
}
#[test]
fn test_default_monitored_remotes() {
    let c = get_default_configuration();
    let remotes = c.get_remotes_monitoring();
    assert_eq!(remotes["upstream"], String::from("master"));
}

#[test]
fn test_monitored_remotes_ordering() {
    let config = "
    monitor_remotes:
        a: b
        c: d
        e: f
    ";
    let docs = YamlLoader::load_from_str(config).unwrap();
    let c = Conf::new(docs[0].clone());
    let remotes = c.get_remotes_monitoring();
    let mut iter = remotes.iter();
    assert_eq!(iter.next().unwrap(), (&String::from("a"), &String::from("b")));
    assert_eq!(iter.next().unwrap(), (&String::from("c"), &String::from("d")));
    assert_eq!(iter.next().unwrap(), (&String::from("e"), &String::from("f")));
}
#[test]
fn test_default_branch_color() {
    let c = get_default_configuration();
    assert_eq!(c.get_branch_color(), "blue");
}
#[test]
fn test_default_remote_difference_color() {
    let c = get_default_configuration();
    assert_eq!(c.get_remote_difference_color(), "white");
}
#[test]
fn test_default_new_color() {
    let c = get_default_configuration();
    assert_eq!(c.get_new_color(), "red");
}
#[test]
fn test_default_changed_color() {
    let c = get_default_configuration();
    assert_eq!(c.get_changed_color(), "red");
}
#[test]
fn test_default_staged_color() {
    let c = get_default_configuration();
    assert_eq!(c.get_staged_color(), "green");
}
#[test]
fn test_default_conflicts_color() {
    let c = get_default_configuration();
    assert_eq!(c.get_conflicts_color(), "yellow");
}
#[allow(unused_must_use)]
#[test]
fn test_create_default_config() {
    let p = PathBuf::from("/tmp/test_pretty_git_prompt_config1");
    if Path::new(&p).exists() {
        remove_file(p.clone());
    }

    let result = create_default_config(p.clone());
    assert!(result.is_ok());

    let mut file = OpenOptions::new()
                .read(true)
                .open(p.clone()).unwrap();
    let mut contents = String::new();
    file.read_to_string(&mut contents);
    assert_eq!(contents, DEFAULT_CONF);

    remove_file(p.clone());
}
#[test]
fn test_create_default_config_when_exists() {
    let p = PathBuf::from("/tmp/test_pretty_git_prompt_config2");
    OpenOptions::new()
                .write(true)
                .create(true)
                .truncate(true)
                .open(p.clone());
    assert!(Path::new(&p).exists());

    let result = create_default_config(p.clone());
    assert!(result.is_err());

    remove_file(p.clone());
}
<|endoftext|>