<commit_before>use std::vec::Vec;
use std::string::String;
use std::collections::HashMap;

struct Compiler {
    pub lines: Vec<String>,
    pub instructions: Vec<String>,
    pub labels: HashMap<String, u32>,
    pub bytecodes: Vec<u32>,
}

impl Compiler {
    pub fn new() -> Self {
        Compiler {
            lines: Vec::new(),
            instructions: Vec::new(),
            labels: HashMap::new(),
            bytecodes: Vec::new(),
        }
    }

    pub fn parse(&mut self, assembly: &str) {
        self.split_lines(assembly);
        self.strip_comments();
        self.compile_labels();
        self.compile_bytecodes();
    }

    fn compile_bytecodes(&mut self) {
        let mut instructions = self.instructions.iter();

        while let Some(instruction) = instructions.next() {
            match instruction.find(':') {
                Some(_) => {
                    // Ignore labels
                }
                None => {
                    let mut bytecodes = instruction.split_whitespace();

                    // Opcode
                    match bytecodes.next() {
                        Some("brk") => {
                            self.bytecodes.push(0);
                        }
                        Some(opcode) => {
                            match u32::from_str_radix(opcode, 16) {
                                Ok(bytecode) => {
                                    self.bytecodes.push(bytecode);
                                }
                                _ => {}
                            }
                        }
                        None => {}
                    }

                    // Operand A
                    match bytecodes.next() {
                        Some(operand) => {
                            match self.labels.get(operand) {
                                None => {
                                    match u32::from_str_radix(operand, 16) {
                                        Ok(bytecode) => {
                                            self.bytecodes.push(bytecode);
                                        }
                                        _ => {}
                                    }
                                }
                                Some(address) => {
                                    self.bytecodes.push(*address);
                                }
                            }
                        }
                        None => {}
                    }

                    // Operand B
                    match bytecodes.next() {
                        Some(operand) => {
                            match self.labels.get(operand) {
                                None => {
                                    match u32::from_str_radix(operand, 16) {
                                        Ok(bytecode) => {
                                            self.bytecodes.push(bytecode);
                                        }
                                        _ => {}
                                    }
                                }
                                Some(address) => {
                                    self.bytecodes.push(*address);
                                }
                            }
                        }
                        None => {}
                    }

                    // Operand C
                    match bytecodes.next() {
                        Some(operand) => {
                            match self.labels.get(operand) {
                                None => {
                                    match u32::from_str_radix(operand, 16) {
                                        Ok(bytecode) => {
                                            self.bytecodes.push(bytecode);
                                        }
                                        _ => {}
                                    }
                                }
                                Some(address) => {
                                    self.bytecodes.push(*address);
                                }
                            }
                        }
                        None => {}
                    }
                }
            }
        }
    }

    fn compile_labels(&mut self) {
        let mut instructions = self.instructions.iter();
        let mut address = 0;

        while let Some(instruction) = instructions.next() {
            match instruction.find(':') {
                Some(index) => {
                    self.labels.insert(instruction.split_at(index).0.to_string(), address);
                }
                None => {
                    address += 4;
                }
            }
        }
    }

    fn strip_comments(&mut self) {
        let mut lines = self.lines.iter();
        while let Some(line) = lines.next() {
            let trimmed_line = line.trim();
            if !trimmed_line.is_empty() && !trimmed_line.starts_with(";") {
                let instruction = match trimmed_line.find(';') {
                    Some(index) => trimmed_line.split_at(index).0,
                    None => trimmed_line,
                };
                self.instructions.push(instruction.trim().to_string());
            }
        }
    }

    fn split_lines(&mut self, assembly: &str) {
        let mut line = String::new();
        let mut chars = assembly.chars();

        while let Some(c) = chars.next() {
            match c {
                // Line Feed | Vertical Tab | Form Feed | Next Line | Line/Paragraph Separator
                '\u{000A}' | '\u{000B}' | '\u{000C}' | '\u{0085}' | '\u{2028}' | '\u{2029}' => {
                    self.lines.push(line);
                    line = String::new();
                }

                // Carriage Return
                '\u{000D}' => {
                    self.lines.push(line);
                    line = String::new();

                    // Carriage Return + Line Feed
                    match chars.next() {
                        Some(n) => {
                            if n != '\u{000A}' {
                                line.push(n);
                            }
                        }
                        None => {}
                    }
                }

                _ => {
                    line.push(c);
                }
            }
        }

        if !line.is_empty() {
            self.lines.push(line);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::Compiler;
    use std::collections::HashMap;

    #[test]
    fn it_splits_lines_by_line_feed() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\n0 0 0 0");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_splits_lines_by_vertical_tab() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\x0B0 0 0 0");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_splits_lines_by_form_feed() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\x0C0 0 0 0");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_splits_lines_by_carriage_return() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\r0 0 0 0");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_splits_lines_by_carriage_return_line_feed() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\r\n0 0 0 0");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_splits_lines_by_next_line() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\u{0085}0 0 0 0");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_splits_lines_by_line_separator() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\u{2028}0 0 0 0");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_splits_lines_by_paragraph_separator() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\u{2029}0 0 0 0");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_ignors_trailing_separators() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\r\n0 0 0 0\r\n");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_strips_single_line_comments() {
        let mut compiler = Compiler::new();
        compiler.parse("; single line comment\n0 0 0 0\n");

        assert_eq!(compiler.lines.len(), 2);
        assert_eq!(compiler.instructions.len(), 1);
    }

    #[test]
    fn it_strips_single_line_comments_starting_with_spaces() {
        let mut compiler = Compiler::new();
        compiler.parse(" ; single line comment\n0 0 0 0\n");

        assert_eq!(compiler.lines.len(), 2);
        assert_eq!(compiler.instructions.len(), 1);
    }

    #[test]
    fn it_strips_single_line_comments_starting_with_tabs() {
        let mut compiler = Compiler::new();
        compiler.parse("\t; single line comment\n0 0 0 0\n");

        assert_eq!(compiler.lines.len(), 2);
        assert_eq!(compiler.instructions.len(), 1);
    }

    #[test]
    fn it_strips_inline_comments() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0; inline comment\n");

        assert_eq!(compiler.lines.len(), 1);
        assert_eq!(compiler.instructions, vec!["0 0 0 0"]);
    }

    #[test]
    fn it_strips_inline_comments_starting_with_spaces() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0 ; inline comment\n");

        assert_eq!(compiler.lines.len(), 1);
        assert_eq!(compiler.instructions, vec!["0 0 0 0"]);
    }

    #[test]
    fn it_strips_inline_comments_starting_with_tabs() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\t; inline comment\n");

        assert_eq!(compiler.lines.len(), 1);
        assert_eq!(compiler.instructions, vec!["0 0 0 0"]);
    }

    #[test]
    fn it_trims_spaces_from_instructions() {
        let mut compiler = Compiler::new();
        compiler.parse(" 0 0 0 0 ");

        assert_eq!(compiler.lines.len(), 1);
        assert_eq!(compiler.instructions, vec!["0 0 0 0"]);
    }

    #[test]
    fn it_trims_tabs_from_instructions() {
        let mut compiler = Compiler::new();
        compiler.parse("\t0 0 0 0\t");

        assert_eq!(compiler.lines.len(), 1);
        assert_eq!(compiler.instructions, vec!["0 0 0 0"]);
    }

    #[test]
    fn it_compiles_addresses_for_labels() {
        let mut compiler = Compiler::new();
        compiler.parse("first:\nsecond:\n0 0 0 0\nthird:");

        let mut labels = HashMap::new();
        labels.insert("first".to_string(), 0);
        labels.insert("second".to_string(), 0);
        labels.insert("third".to_string(), 4);

        assert_eq!(compiler.labels, labels);
    }

    #[test]
    fn it_lets_the_last_label_win() {
        let mut compiler = Compiler::new();
        compiler.parse("label:\n0 0 0 0\nlabel:");

        let mut labels = HashMap::new();
        labels.insert("label".to_string(), 4);

        assert_eq!(compiler.labels, labels);
    }

    #[test]
    fn it_ignores_trailing_label_characters() {
        let mut compiler = Compiler::new();
        compiler.parse("label:with bits\n0 0 0 0\nlabel:with bytes");

        let mut labels = HashMap::new();
        labels.insert("label".to_string(), 4);

        assert_eq!(compiler.labels, labels);
    }

    #[test]
    fn it_parses_hex_for_opcode_0() {
        let mut compiler = Compiler::new();
        compiler.parse("0");

        assert_eq!(compiler.bytecodes, vec![0x0]);
    }

    #[test]
    fn it_parses_brk_as_opcode_0() {
        let mut compiler = Compiler::new();
        compiler.parse("brk");

        assert_eq!(compiler.bytecodes, vec![0x0]);
    }

    #[test]
    fn it_parses_operand_A_as_hex() {
        let mut compiler = Compiler::new();
        compiler.parse("brk f");

        assert_eq!(compiler.bytecodes, vec![0x0, 0xf]);
    }

    #[test]
    fn it_parses_operand_A_as_a_label() {
        let mut compiler = Compiler::new();
        compiler.parse("brk end\nend:");

        assert_eq!(compiler.bytecodes, vec![0x0, 0x4]);
    }

    #[test]
    fn it_parses_operand_B_as_hex() {
        let mut compiler = Compiler::new();
        compiler.parse("brk f f");

        assert_eq!(compiler.bytecodes, vec![0x0, 0xf, 0xf]);
    }

    #[test]
    fn it_parses_operand_B_as_a_label() {
        let mut compiler = Compiler::new();
        compiler.parse("brk f end\nend:");

        assert_eq!(compiler.bytecodes, vec![0x0, 0xf, 0x4]);
    }

    #[test]
    fn it_parses_operand_C_as_hex() {
        let mut compiler = Compiler::new();
        compiler.parse("brk f f f");

        assert_eq!(compiler.bytecodes, vec![0x0, 0xf, 0xf, 0xf]);
    }

    #[test]
    fn it_parses_operand_C_as_a_label() {
        let mut compiler = Compiler::new();
        compiler.parse("brk f f end\nend:");

        assert_eq!(compiler.bytecodes, vec![0x0, 0xf, 0xf, 0x4]);
    }
}
<commit_msg>Ensure simple and complex examples compile.<commit_after>use std::vec::Vec;
use std::string::String;
use std::collections::HashMap;

struct Compiler {
    pub lines: Vec<String>,
    pub instructions: Vec<String>,
    pub labels: HashMap<String, u32>,
    pub bytecodes: Vec<u32>,
}

impl Compiler {
    pub fn new() -> Self {
        Compiler {
            lines: Vec::new(),
            instructions: Vec::new(),
            labels: HashMap::new(),
            bytecodes: Vec::new(),
        }
    }

    pub fn parse(&mut self, assembly: &str) {
        self.split_lines(assembly);
        self.strip_comments();
        self.compile_labels();
        self.compile_bytecodes();
    }

    fn compile_bytecodes(&mut self) {
        let mut instructions = self.instructions.iter();

        while let Some(instruction) = instructions.next() {
            match instruction.find(':') {
                Some(_) => {
                    // Ignore labels
                }
                None => {
                    let mut bytecodes = instruction.split_whitespace();

                    // Opcode
                    match bytecodes.next() {
                        Some("brk") => {
                            self.bytecodes.push(0);
                        }
                        Some("lpc") => {
                            self.bytecodes.push(1);
                        }
                        Some("beq") => {
                            self.bytecodes.push(2);
                        }
                        Some("spc") => {
                            self.bytecodes.push(3);
                        }
                        Some("lea") => {
                            self.bytecodes.push(4);
                        }
                        Some("lra") => {
                            self.bytecodes.push(5);
                        }
                        Some("sra") => {
                            self.bytecodes.push(6);
                        }
                        Some("add") => {
                            self.bytecodes.push(7);
                        }
                        Some("sub") => {
                            self.bytecodes.push(8);
                        }
                        Some("mul") => {
                            self.bytecodes.push(9);
                        }
                        Some("div") => {
                            self.bytecodes.push(10);
                        }
                        Some("mod") => {
                            self.bytecodes.push(11);
                        }
                        Some("cmp") => {
                            self.bytecodes.push(12);
                        }
                        Some("nad") => {
                            self.bytecodes.push(13);
                        }
                        Some("drw") => {
                            self.bytecodes.push(14);
                        }
                        Some("key") => {
                            self.bytecodes.push(15);
                        }
                        Some("nop") => {
                            self.bytecodes.push(16);
                        }
                        Some(opcode) => {
                            match u32::from_str_radix(opcode, 16) {
                                Ok(bytecode) => {
                                    self.bytecodes.push(bytecode);
                                }
                                _ => {}
                            }
                        }
                        None => {}
                    }

                    // Operand A
                    match bytecodes.next() {
                        Some(operand) => {
                            match self.labels.get(operand) {
                                None => {
                                    match u32::from_str_radix(operand, 16) {
                                        Ok(bytecode) => {
                                            self.bytecodes.push(bytecode);
                                        }
                                        _ => {}
                                    }
                                }
                                Some(address) => {
                                    self.bytecodes.push(*address);
                                }
                            }
                        }
                        None => {}
                    }

                    // Operand B
                    match bytecodes.next() {
                        Some(operand) => {
                            match self.labels.get(operand) {
                                None => {
                                    match u32::from_str_radix(operand, 16) {
                                        Ok(bytecode) => {
                                            self.bytecodes.push(bytecode);
                                        }
                                        _ => {}
                                    }
                                }
                                Some(address) => {
                                    self.bytecodes.push(*address);
                                }
                            }
                        }
                        None => {}
                    }

                    // Operand C
                    match bytecodes.next() {
                        Some(operand) => {
                            match self.labels.get(operand) {
                                None => {
                                    match u32::from_str_radix(operand, 16) {
                                        Ok(bytecode) => {
                                            self.bytecodes.push(bytecode);
                                        }
                                        _ => {}
                                    }
                                }
                                Some(address) => {
                                    self.bytecodes.push(*address);
                                }
                            }
                        }
                        None => {}
                    }
                }
            }
        }
    }

    fn compile_labels(&mut self) {
        let mut instructions = self.instructions.iter();
        let mut address = 0;

        while let Some(instruction) = instructions.next() {
            match instruction.find(':') {
                Some(index) => {
                    self.labels.insert(instruction.split_at(index).0.to_string(), address);
                }
                None => {
                    address += 4;
                }
            }
        }
    }

    fn strip_comments(&mut self) {
        let mut lines = self.lines.iter();
        while let Some(line) = lines.next() {
            let trimmed_line = line.trim();
            if !trimmed_line.is_empty() && !trimmed_line.starts_with(";") {
                let instruction = match trimmed_line.find(';') {
                    Some(index) => trimmed_line.split_at(index).0,
                    None => trimmed_line,
                };
                self.instructions.push(instruction.trim().to_string());
            }
        }
    }

    fn split_lines(&mut self, assembly: &str) {
        let mut line = String::new();
        let mut chars = assembly.chars();

        while let Some(c) = chars.next() {
            match c {
                // Line Feed | Vertical Tab | Form Feed | Next Line | Line/Paragraph Separator
                '\u{000A}' | '\u{000B}' | '\u{000C}' | '\u{0085}' | '\u{2028}' | '\u{2029}' => {
                    self.lines.push(line);
                    line = String::new();
                }

                // Carriage Return
                '\u{000D}' => {
                    self.lines.push(line);
                    line = String::new();

                    // Carriage Return + Line Feed
                    match chars.next() {
                        Some(n) => {
                            if n != '\u{000A}' {
                                line.push(n);
                            }
                        }
                        None => {}
                    }
                }

                _ => {
                    line.push(c);
                }
            }
        }

        if !line.is_empty() {
            self.lines.push(line);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::Compiler;
    use std::collections::HashMap;

    #[test]
    fn it_splits_lines_by_line_feed() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\n0 0 0 0");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_splits_lines_by_vertical_tab() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\x0B0 0 0 0");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_splits_lines_by_form_feed() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\x0C0 0 0 0");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_splits_lines_by_carriage_return() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\r0 0 0 0");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_splits_lines_by_carriage_return_line_feed() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\r\n0 0 0 0");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_splits_lines_by_next_line() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\u{0085}0 0 0 0");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_splits_lines_by_line_separator() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\u{2028}0 0 0 0");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_splits_lines_by_paragraph_separator() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\u{2029}0 0 0 0");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_ignors_trailing_separators() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\r\n0 0 0 0\r\n");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_strips_single_line_comments() {
        let mut compiler = Compiler::new();
        compiler.parse("; single line comment\n0 0 0 0\n");

        assert_eq!(compiler.lines.len(), 2);
        assert_eq!(compiler.instructions.len(), 1);
    }

    #[test]
    fn it_strips_single_line_comments_starting_with_spaces() {
        let mut compiler = Compiler::new();
        compiler.parse(" ; single line comment\n0 0 0 0\n");

        assert_eq!(compiler.lines.len(), 2);
        assert_eq!(compiler.instructions.len(), 1);
    }

    #[test]
    fn it_strips_single_line_comments_starting_with_tabs() {
        let mut compiler = Compiler::new();
        compiler.parse("\t; single line comment\n0 0 0 0\n");

        assert_eq!(compiler.lines.len(), 2);
        assert_eq!(compiler.instructions.len(), 1);
    }

    #[test]
    fn it_strips_inline_comments() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0; inline comment\n");

        assert_eq!(compiler.lines.len(), 1);
        assert_eq!(compiler.instructions, vec!["0 0 0 0"]);
    }

    #[test]
    fn it_strips_inline_comments_starting_with_spaces() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0 ; inline comment\n");

        assert_eq!(compiler.lines.len(), 1);
        assert_eq!(compiler.instructions, vec!["0 0 0 0"]);
    }

    #[test]
    fn it_strips_inline_comments_starting_with_tabs() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\t; inline comment\n");

        assert_eq!(compiler.lines.len(), 1);
        assert_eq!(compiler.instructions, vec!["0 0 0 0"]);
    }

    #[test]
    fn it_trims_spaces_from_instructions() {
        let mut compiler = Compiler::new();
        compiler.parse(" 0 0 0 0 ");

        assert_eq!(compiler.lines.len(), 1);
        assert_eq!(compiler.instructions, vec!["0 0 0 0"]);
    }

    #[test]
    fn it_trims_tabs_from_instructions() {
        let mut compiler = Compiler::new();
        compiler.parse("\t0 0 0 0\t");

        assert_eq!(compiler.lines.len(), 1);
        assert_eq!(compiler.instructions, vec!["0 0 0 0"]);
    }

    #[test]
    fn it_compiles_addresses_for_labels() {
        let mut compiler = Compiler::new();
        compiler.parse("first:\nsecond:\n0 0 0 0\nthird:");

        let mut labels = HashMap::new();
        labels.insert("first".to_string(), 0);
        labels.insert("second".to_string(), 0);
        labels.insert("third".to_string(), 4);

        assert_eq!(compiler.labels, labels);
    }

    #[test]
    fn it_lets_the_last_label_win() {
        let mut compiler = Compiler::new();
        compiler.parse("label:\n0 0 0 0\nlabel:");

        let mut labels = HashMap::new();
        labels.insert("label".to_string(), 4);

        assert_eq!(compiler.labels, labels);
    }

    #[test]
    fn it_ignores_trailing_label_characters() {
        let mut compiler = Compiler::new();
        compiler.parse("label:with bits\n0 0 0 0\nlabel:with bytes");

        let mut labels = HashMap::new();
        labels.insert("label".to_string(), 4);

        assert_eq!(compiler.labels, labels);
    }

    #[test]
    fn it_parses_hex_for_opcode_0() {
        let mut compiler = Compiler::new();
        compiler.parse("0");

        assert_eq!(compiler.bytecodes, vec![0x0]);
    }

    #[test]
    fn it_parses_brk_as_opcode_0() {
        let mut compiler = Compiler::new();
        compiler.parse("brk");

        assert_eq!(compiler.bytecodes, vec![0x0]);
    }

    #[test]
    fn it_parses_operand_A_as_hex() {
        let mut compiler = Compiler::new();
        compiler.parse("brk f");

        assert_eq!(compiler.bytecodes, vec![0x0, 0xf]);
    }

    #[test]
    fn it_parses_operand_A_as_a_label() {
        let mut compiler = Compiler::new();
        compiler.parse("brk end\nend:");

        assert_eq!(compiler.bytecodes, vec![0x0, 0x4]);
    }

    #[test]
    fn it_parses_operand_B_as_hex() {
        let mut compiler = Compiler::new();
        compiler.parse("brk f f");

        assert_eq!(compiler.bytecodes, vec![0x0, 0xf, 0xf]);
    }

    #[test]
    fn it_parses_operand_B_as_a_label() {
        let mut compiler = Compiler::new();
        compiler.parse("brk f end\nend:");

        assert_eq!(compiler.bytecodes, vec![0x0, 0xf, 0x4]);
    }

    #[test]
    fn it_parses_operand_C_as_hex() {
        let mut compiler = Compiler::new();
        compiler.parse("brk f f f");

        assert_eq!(compiler.bytecodes, vec![0x0, 0xf, 0xf, 0xf]);
    }

    #[test]
    fn it_parses_operand_C_as_a_label() {
        let mut compiler = Compiler::new();
        compiler.parse("brk f f end\nend:");

        assert_eq!(compiler.bytecodes, vec![0x0, 0xf, 0xf, 0x4]);
    }

    #[test]
    fn it_compiles_the_simple_ctrl_c_example() {
        let mut compiler = Compiler::new();

        compiler.parse(" f 2 0 3  ; Read key press and store it in M[2]\n 8 2 2 3  ; Subtract M[3] \
                    from M[2] and store the result in M[2]\n 2 b 2 f  ; If M[2] equals 0, then \
                    set PC to M[b]\n 1 e 0 0  ; Else, set PC to M[e] ");

        assert_eq!(compiler.bytecodes,
                   vec![
            0xf, 0x2, 0x0, 0x3,
            0x8, 0x2, 0x2, 0x3,
            0x2, 0xb, 0x2, 0xf,
            0x1, 0xe, 0x0, 0x0,
        ]);
    }

    #[test]
    fn it_compiles_the_complex_ctrl_c_example() {
        let mut compiler = Compiler::new();

        compiler.parse(" \
        x:\n \
          nop 0 0 0\n \
        \n \
        ctrl-c:\n \
          add 4 9 a\n \
          nop 3 0 0\n \
        \n \
        check-ctrl-c:\n \
          key x 0 0              ; Read key press and store it in M[x]\n \
          sub x x ctrl-c         ; Subtract M[ctrl-c] from M[x] and store the result in M[x]\n \
          beq 17 x exit          ; If M[x] equals 0, then set PC to M[17]\n \
          lpc 1a check-ctrl-c 0  ; Else, set PC to M[1a]\n \
        \n \
        exit:\n \
          brk 0 0 0 \
        ");

        let mut labels = HashMap::new();
        labels.insert("x".to_string(), 0);
        labels.insert("ctrl-c".to_string(), 4);
        labels.insert("check-ctrl-c".to_string(), 12);
        labels.insert("exit".to_string(), 28);

        assert_eq!(compiler.labels, labels);

        assert_eq!(compiler.bytecodes,
                   vec![0x10, 0x0, 0x0, 0x0, 0x7, 0x4, 0x9, 0xa, 0x10, 0x3, 0x0, 0x0, 0xf, 0x0,
                        0x0, 0x0, 0x8, 0x0, 0x0, 0x4, 0x2, 0x17, 0x0, 0x1c, 0x1, 0x1a, 0xc, 0x0,
                        0x0, 0x0, 0x0, 0x0]);
    }
}
<|endoftext|>


<commit_before>use core::panic;
//
use std::fs;
use std::sync::mpsc::channel;
use std::sync::mpsc::Receiver;
use std::sync::mpsc::Sender;
use std::thread;

use parking_lot::RwLock;
use std::rc::Rc;
use std::sync::Arc;

#[macro_use]
pub(crate) mod macros;

pub mod codec;
pub mod codepointinfo;
pub mod config;
pub mod document;
pub mod editor;
pub mod event;
pub mod mapped_file;
pub mod modes;
pub mod screen;
pub mod view;

use crate::core::config::Config;
use crate::core::document::Document;
use crate::core::editor::Editor;
use crate::core::editor::EditorEnv;
use crate::core::event::Event;
use crate::core::event::EventMessage;
use crate::core::view::View;

pub const VERSION: &str = env!("CARGO_PKG_VERSION");

use std::sync::atomic::{AtomicUsize, Ordering};

//
pub static DBG_PRINTLN_FLAG: AtomicUsize = AtomicUsize::new(0);

pub fn enable_dbg_println() {
    DBG_PRINTLN_FLAG.store(1, Ordering::Relaxed);
}
pub fn disable_dbg_println() {
    DBG_PRINTLN_FLAG.store(0, Ordering::Relaxed);
}
pub fn toggle_dbg_println() {
    let v = DBG_PRINTLN_FLAG.load(Ordering::Relaxed);
    if v != 0 {
        disable_dbg_println();
    } else {
        enable_dbg_println();
    }
}

//
pub static USE_READ_CACHE: AtomicUsize = AtomicUsize::new(1);
pub fn disable_read_cache() {
    USE_READ_CACHE.store(0, Ordering::Relaxed);
}

pub fn use_read_cache() -> bool {
    USE_READ_CACHE.load(Ordering::Relaxed) != 0
}

//
pub static USE_BYTE_INDEX: AtomicUsize = AtomicUsize::new(1);
pub fn disable_byte_index() {
    USE_BYTE_INDEX.store(0, Ordering::Relaxed);
}

pub fn use_byte_index() -> bool {
    USE_BYTE_INDEX.load(Ordering::Relaxed) != 0
}

//
pub static BENCH_TO_EOF: AtomicUsize = AtomicUsize::new(0);
pub fn enable_bench_to_eof() {
    BENCH_TO_EOF.store(1, Ordering::Relaxed);
}
pub fn bench_to_eof() -> bool {
    BENCH_TO_EOF.load(Ordering::Relaxed) != 0
}

//
pub static RAW_FILTER_TO_SCREEN: AtomicUsize = AtomicUsize::new(0);
pub fn enable_raw_data_filter_to_screen() {
    RAW_FILTER_TO_SCREEN.store(1, Ordering::Relaxed);
}
pub fn raw_data_filter_to_screen() -> bool {
    RAW_FILTER_TO_SCREEN.load(Ordering::Relaxed) != 0
}

//
pub static NO_UI_RENDER: AtomicUsize = AtomicUsize::new(0);
pub fn set_no_ui_render(b: bool) {
    if b {
        NO_UI_RENDER.store(1, Ordering::Relaxed);
    } else {
        NO_UI_RENDER.store(0, Ordering::Relaxed);
    }
}
pub fn no_ui_render() -> bool {
    NO_UI_RENDER.load(Ordering::Relaxed) != 0
}

/*
 TODO(ceg):

    "core-mode" {
        scrollbar-mode
        v-split-mode
        h-split-mode
    }

text-mode =
    "raw-data"
    "utf8-codec"
    "mark-mode"
    "undo-mode"
    ----------
    "wrap-line"
    "wrap-word"

    --------------
    "replay-mode"

    "tab-mode"
    "selection/high-light"
    "fold-mode"
    "search-mode"
    "regex-mode"

    "exec-bin-mode"

    "dir-mode"

    "shell-mode"

    "ffi-mode"

    "hex-mode"

    "follow-mode"

    split layout.rs -> modes

  document.predefined_modes() Option<vec["internal:welcome-mode"]>
  document.predefined_modes() Option<vec["internal:debug-message"]>

  based on extension we will load predefine modes / keywords list etc ..

    replay-mode
      ctrl+x ctrl+x r :  {left,right} alt+{left,right}
*/

pub static WELCOME_MESSAGE: &str = r#"-*- Welcome to unlimitED! -*-

unlimitED! is an experimental text editor (running in the terminal).

SYNOPSIS
unlimited [options] [file ..]

It comes with:

  - basic UTF-8 support
  - very large file support
  - "infinite" undo/redo
  - multi-cursors
  - mouse selection (X11 terminal)

[Quit]
    Quit:           => ctrl+x ctrl+c

    Quit (no save)  => ctrl+x ctrl+q

    NB: quit will wait for large file(s) sync to storage.

[Moves]
    Left            =>
    Right           =>
    Up              =>
    Down            =>

[Edit]
    ctrl+o          => Open file (TODO)

    ctrl+u          => Undo
    ctrl+r          => Redo

[Selection/Copy/Paste]
    with the keyboard:

    with the mouse (X11 terminal):

[Save]
    ctrl+x ctrl+s   => Save
                    synchronization of large file(s) is done in the background and does not block the ui.

[Document Selection]

NB: unlimitED! comes with ABSOLUTELY NO WARRANTY
"#;

/// This function is the core of the editor.
/// It should be ran in an other thread than the main one (which is kept for ui)
pub fn run<'a>(
    config: Config,
    core_rx: &Receiver<EventMessage<'static>>,
    core_tx: &Sender<EventMessage<'static>>,
    ui_tx: &Sender<EventMessage<'static>>,
) {
    let (worker_tx, worker_rx) = channel();
    let (indexer_tx, indexer_rx) = channel();

    let mut editor = Editor::new(
        config,
        core_tx.clone(),
        ui_tx.clone(),
        worker_tx.clone(),
        indexer_tx.clone(),
    );
    let mut env = EditorEnv::new();

    // create worker thread
    let worker_th = {
        let core_tx = core_tx.clone();
        Some(thread::spawn(move || worker(&worker_rx, &core_tx)))
    };
    let indexer_th = {
        let core_tx = core_tx.clone();
        Some(thread::spawn(move || indexer(&indexer_rx, &core_tx)))
    };

    editor.worker_tx = worker_tx.clone();

    load_modes(&mut editor, &mut env);

    load_files(&mut editor, &mut env);

    create_views(&mut editor, &mut env);

    editor::main_loop(&mut editor, &mut env, &core_rx, &ui_tx);

    // wait for worker thread
    if let Some(worker_handle) = worker_th {
        worker_handle.join().unwrap()
    }
    if let Some(indexer_handle) = indexer_th {
        indexer_handle.join().unwrap()
    }
}

/////////////////////// worker.rs

pub fn worker(
    worker_rx: &Receiver<EventMessage<'static>>,
    _core_tx: &Sender<EventMessage<'static>>,
) {
    dbg_println!("[starting worker thread]");
    loop {
        if let Ok(evt) = worker_rx.recv() {
            match evt.event {
                Event::ApplicationQuitEvent => {
                    dbg_println!("[stopping worker thread]");
                    break;
                }

                Event::SyncTask { doc } => {
                    document::sync_to_storage(&doc);
                }

                //                Event::OutsourcedTask { task_uid,  editor, editor_env, doc_id, vid, action, params ? } => {
                //                    action();
                //                }
                _ => {
                    panic!("worker thread received an unexpected message");
                }
            }
        }
    }
}

pub fn indexer(
    worker_rx: &Receiver<EventMessage<'static>>,
    core_tx: &Sender<EventMessage<'static>>,
) {
    if !use_byte_index() {
        return;
    }

    dbg_println!("[starting worker thread (indexer)]");
    loop {
        if let Ok(evt) = worker_rx.recv() {
            match evt.event {
                Event::ApplicationQuitEvent => {
                    dbg_println!("[stopping worker thread]");
                    break;
                }

                // TODO(ceg): split in sub-threads/async task
                Event::IndexTask { document_map } => {
                    dbg_println!("[receive index task ]");

                    let mut refresh_ui = false;
                    let map = document_map.read();
                    let mut t0 = std::time::Instant::now();
                    for (_id, doc) in map.iter() {
                        document::build_index(doc);
                        refresh_ui = true;
                        let t1 = std::time::Instant::now();
                        if (t1 - t0).as_millis() > 1000 {
                            // send ui refresh event
                            let msg = EventMessage::new(0, Event::RefreshViewEvent);
                            core_tx.send(msg).unwrap_or(());

                            refresh_ui = false;
                            t0 = t1;
                        }
                    }

                    // last ui refresh
                    if refresh_ui {
                        let msg = EventMessage::new(0, Event::RefreshViewEvent);
                        core_tx.send(msg).unwrap_or(());
                    }
                }

                _ => {
                    panic!("worker thread received an unexpected message");
                }
            }
        }
    }
}

use crate::core::document::DocumentBuilder;

/// TODO(ceg): replace this by load/unload doc functions
/// the ui will open the documents on demand
pub fn load_files(editor: &mut Editor<'static>, env: &mut EditorEnv<'static>) {
    let mut id = editor.document_map.read().len();

    for f in &editor.config.files_list {
        // check file type
        if let Ok(metadata) = fs::metadata(f) {
            let file_type = metadata.file_type();

            // ignore directories for now
            if file_type.is_dir() {
                continue;
            }
        } else {
            // log error
            eprintln!("cannot check {} file type", f);
            continue;
        }

        let b = DocumentBuilder::new()
            .document_name(f)
            .file_name(f)
            .internal(false)
            .finalize();

        if let Some(b) = b {
            let doc_id = document::Id(id);
            b.as_ref().write().id = doc_id; // TODO(ceg): improve doc id generation
            editor.document_map.write().insert(doc_id, b);
            id += 1;
        }
    }

    // default buffer ?
    let map_is_empty = editor.document_map.read().is_empty();
    if map_is_empty {
        // edit.get_untitled_count() -> 1

        let b = DocumentBuilder::new()
            .document_name("untitled-1")
            .internal(false)
            .finalize();
        if let Some(b) = b {
            {
                let mut d = b.write();
                let s = WELCOME_MESSAGE.as_bytes();

                // move 1st tag to ctor/doc::new() ?
                d.tag(env.current_time, 0, vec![0]); // TODO(ceg): rm this only if the buffer log is cleared
                                                     //    create_views(&mut editor, &mut env);

                d.insert(0, s.len(), s);

                // do not allow to go back to empty buffer
                d.buffer_log_reset();
                d.changed = false;
            }
            let doc_id = document::Id(id);
            editor.document_map.write().insert(doc_id, b);
            id += 1;
        }
    }

    // configure document
    let modes = editor.modes.clone();
    for (mode_name, mode) in modes.borrow().iter() {
        // per mode document metadata
        dbg_println!("setup mode[{}] document metadata", mode_name);
        let mut mode = mode.borrow_mut();
        let map = editor.document_map.clone();
        let mut map = map.as_ref().write();
        for (_, doc) in map.iter_mut() {
            let mut doc = doc.write();
            mode.configure_document(editor, env, &mut doc);
        }
    }

    dbg_println!("id {}", id);
}

pub fn create_views(mut editor: &mut Editor<'static>, mut env: &mut EditorEnv<'static>) {
    let document_map = editor.document_map.clone();
    let document_map = document_map.read();

    // create default views
    // sort by arg pos first
    let mut docs_id: Vec<document::Id> = document_map.iter().map(|(k, _v)| *k).collect();
    docs_id.sort();
    let mut docs: Vec<Arc<RwLock<Document>>> = vec![];
    for id in docs_id.iter() {
        if let Some(doc) = document_map.get(id) {
            docs.push(Arc::clone(doc));
        }
    }

    let modes = match std::env::var("SINGLE_VIEW") {
        Ok(_) => vec!["simple-view".to_owned()],
        _ => vec!["basic-editor".to_owned()],
    };

    // create views
    for doc in docs {
        let view = View::new(
            &mut editor,
            &mut env,
            None,
            0,
            0,
            1,
            1,
            Some(doc),
            &modes,
            0,
        );
        dbg_println!("create {:?}", view.id);

        // top level views
        editor.root_views.push(view.id);
        editor.view_map.insert(view.id, Rc::new(RwLock::new(view)));
    }

    // index documents
    // TODO(ceg): send one event per doc
    if true {
        let msg = EventMessage {
            seq: 0,
            event: Event::IndexTask {
                document_map: Arc::clone(&editor.document_map),
            },
        };
        editor.indexer_tx.send(msg).unwrap_or(());
    }
}

use crate::core::modes::BasicEditorMode;
use crate::core::modes::SimpleViewMode;

use crate::core::modes::CoreMode;
use crate::core::modes::FindMode;
use crate::core::modes::TextMode;

use crate::core::modes::StatusMode;

use crate::core::modes::HsplitMode;
use crate::core::modes::VsplitMode;

use crate::core::modes::VscrollbarMode;

use crate::core::modes::LineNumberMode;

pub fn load_modes(editor: &mut Editor, _env: &mut EditorEnv) {
    // set default mode(s)
    editor.register_mode(Box::new(CoreMode::new()));
    editor.register_mode(Box::new(BasicEditorMode::new()));
    editor.register_mode(Box::new(SimpleViewMode::new()));

    editor.register_mode(Box::new(VsplitMode::new()));
    editor.register_mode(Box::new(HsplitMode::new()));

    editor.register_mode(Box::new(VscrollbarMode::new()));

    editor.register_mode(Box::new(TextMode::new()));
    editor.register_mode(Box::new(StatusMode::new()));

    editor.register_mode(Box::new(FindMode::new()));

    editor.register_mode(Box::new(LineNumberMode::new()));
}
<commit_msg>refresh ui after synchronization to disk is finshed<commit_after>use core::panic;
//
use std::fs;
use std::sync::mpsc::channel;
use std::sync::mpsc::Receiver;
use std::sync::mpsc::Sender;
use std::thread;

use parking_lot::RwLock;
use std::rc::Rc;
use std::sync::Arc;

#[macro_use]
pub(crate) mod macros;

pub mod codec;
pub mod codepointinfo;
pub mod config;
pub mod document;
pub mod editor;
pub mod event;
pub mod mapped_file;
pub mod modes;
pub mod screen;
pub mod view;

use crate::core::config::Config;
use crate::core::document::Document;
use crate::core::editor::Editor;
use crate::core::editor::EditorEnv;
use crate::core::event::Event;
use crate::core::event::EventMessage;
use crate::core::view::View;

pub const VERSION: &str = env!("CARGO_PKG_VERSION");

use std::sync::atomic::{AtomicUsize, Ordering};

//
pub static DBG_PRINTLN_FLAG: AtomicUsize = AtomicUsize::new(0);

pub fn enable_dbg_println() {
    DBG_PRINTLN_FLAG.store(1, Ordering::Relaxed);
}
pub fn disable_dbg_println() {
    DBG_PRINTLN_FLAG.store(0, Ordering::Relaxed);
}
pub fn toggle_dbg_println() {
    let v = DBG_PRINTLN_FLAG.load(Ordering::Relaxed);
    if v != 0 {
        disable_dbg_println();
    } else {
        enable_dbg_println();
    }
}

//
pub static USE_READ_CACHE: AtomicUsize = AtomicUsize::new(1);
pub fn disable_read_cache() {
    USE_READ_CACHE.store(0, Ordering::Relaxed);
}

pub fn use_read_cache() -> bool {
    USE_READ_CACHE.load(Ordering::Relaxed) != 0
}

//
pub static USE_BYTE_INDEX: AtomicUsize = AtomicUsize::new(1);
pub fn disable_byte_index() {
    USE_BYTE_INDEX.store(0, Ordering::Relaxed);
}

pub fn use_byte_index() -> bool {
    USE_BYTE_INDEX.load(Ordering::Relaxed) != 0
}

//
pub static BENCH_TO_EOF: AtomicUsize = AtomicUsize::new(0);
pub fn enable_bench_to_eof() {
    BENCH_TO_EOF.store(1, Ordering::Relaxed);
}
pub fn bench_to_eof() -> bool {
    BENCH_TO_EOF.load(Ordering::Relaxed) != 0
}

//
pub static RAW_FILTER_TO_SCREEN: AtomicUsize = AtomicUsize::new(0);
pub fn enable_raw_data_filter_to_screen() {
    RAW_FILTER_TO_SCREEN.store(1, Ordering::Relaxed);
}
pub fn raw_data_filter_to_screen() -> bool {
    RAW_FILTER_TO_SCREEN.load(Ordering::Relaxed) != 0
}

//
pub static NO_UI_RENDER: AtomicUsize = AtomicUsize::new(0);
pub fn set_no_ui_render(b: bool) {
    if b {
        NO_UI_RENDER.store(1, Ordering::Relaxed);
    } else {
        NO_UI_RENDER.store(0, Ordering::Relaxed);
    }
}
pub fn no_ui_render() -> bool {
    NO_UI_RENDER.load(Ordering::Relaxed) != 0
}

/*
 TODO(ceg):

    "core-mode" {
        scrollbar-mode
        v-split-mode
        h-split-mode
    }

text-mode =
    "raw-data"
    "utf8-codec"
    "mark-mode"
    "undo-mode"
    ----------
    "wrap-line"
    "wrap-word"

    --------------
    "replay-mode"

    "tab-mode"
    "selection/high-light"
    "fold-mode"
    "search-mode"
    "regex-mode"

    "exec-bin-mode"

    "dir-mode"

    "shell-mode"

    "ffi-mode"

    "hex-mode"

    "follow-mode"

    split layout.rs -> modes

  document.predefined_modes() Option<vec["internal:welcome-mode"]>
  document.predefined_modes() Option<vec["internal:debug-message"]>

  based on extension we will load predefine modes / keywords list etc ..

    replay-mode
      ctrl+x ctrl+x r :  {left,right} alt+{left,right}
*/

pub static WELCOME_MESSAGE: &str = r#"-*- Welcome to unlimitED! -*-

unlimitED! is an experimental text editor (running in the terminal).

SYNOPSIS
unlimited [options] [file ..]

It comes with:

  - basic UTF-8 support
  - very large file support
  - "infinite" undo/redo
  - multi-cursors
  - mouse selection (X11 terminal)

[Quit]
    Quit:           => ctrl+x ctrl+c

    Quit (no save)  => ctrl+x ctrl+q

    NB: quit will wait for large file(s) sync to storage.

[Moves]
    Left            =>
    Right           =>
    Up              =>
    Down            =>

[Edit]
    ctrl+o          => Open file (TODO)

    ctrl+u          => Undo
    ctrl+r          => Redo

[Selection/Copy/Paste]
    with the keyboard:

    with the mouse (X11 terminal):

[Save]
    ctrl+x ctrl+s   => Save
                    synchronization of large file(s) is done in the background and does not block the ui.

[Document Selection]

NB: unlimitED! comes with ABSOLUTELY NO WARRANTY
"#;

/// This function is the core of the editor.
/// It should be ran in an other thread than the main one (which is kept for ui)
pub fn run<'a>(
    config: Config,
    core_rx: &Receiver<EventMessage<'static>>,
    core_tx: &Sender<EventMessage<'static>>,
    ui_tx: &Sender<EventMessage<'static>>,
) {
    let (worker_tx, worker_rx) = channel();
    let (indexer_tx, indexer_rx) = channel();

    let mut editor = Editor::new(
        config,
        core_tx.clone(),
        ui_tx.clone(),
        worker_tx.clone(),
        indexer_tx.clone(),
    );
    let mut env = EditorEnv::new();

    // create worker thread
    let worker_th = {
        let core_tx = core_tx.clone();
        Some(thread::spawn(move || worker(&worker_rx, &core_tx)))
    };
    let indexer_th = {
        let core_tx = core_tx.clone();
        Some(thread::spawn(move || indexer(&indexer_rx, &core_tx)))
    };

    editor.worker_tx = worker_tx.clone();

    load_modes(&mut editor, &mut env);

    load_files(&mut editor, &mut env);

    create_views(&mut editor, &mut env);

    editor::main_loop(&mut editor, &mut env, &core_rx, &ui_tx);

    // wait for worker thread
    if let Some(worker_handle) = worker_th {
        worker_handle.join().unwrap()
    }
    if let Some(indexer_handle) = indexer_th {
        indexer_handle.join().unwrap()
    }
}

/////////////////////// worker.rs

pub fn worker(
    worker_rx: &Receiver<EventMessage<'static>>,
    core_tx: &Sender<EventMessage<'static>>,
) {
    dbg_println!("[starting worker thread]");
    loop {
        if let Ok(evt) = worker_rx.recv() {
            match evt.event {
                Event::ApplicationQuitEvent => {
                    dbg_println!("[stopping worker thread]");
                    break;
                }

                Event::SyncTask { doc } => {
                    document::sync_to_storage(&doc);

                    let msg = EventMessage::new(0, Event::RefreshViewEvent);
                    core_tx.send(msg).unwrap_or(());
                }

                //                Event::OutsourcedTask { task_uid,  editor, editor_env, doc_id, vid, action, params ? } => {
                //                    action();
                //                }
                _ => {
                    panic!("worker thread received an unexpected message");
                }
            }
        }
    }
}

pub fn indexer(
    worker_rx: &Receiver<EventMessage<'static>>,
    core_tx: &Sender<EventMessage<'static>>,
) {
    if !use_byte_index() {
        return;
    }

    dbg_println!("[starting worker thread (indexer)]");
    loop {
        if let Ok(evt) = worker_rx.recv() {
            match evt.event {
                Event::ApplicationQuitEvent => {
                    dbg_println!("[stopping worker thread]");
                    break;
                }

                // TODO(ceg): split in sub-threads/async task
                Event::IndexTask { document_map } => {
                    dbg_println!("[receive index task ]");

                    let mut refresh_ui = false;
                    let map = document_map.read();
                    let mut t0 = std::time::Instant::now();
                    for (_id, doc) in map.iter() {
                        document::build_index(doc);
                        refresh_ui = true;
                        let t1 = std::time::Instant::now();
                        if (t1 - t0).as_millis() > 1000 {
                            // send ui refresh event
                            let msg = EventMessage::new(0, Event::RefreshViewEvent);
                            core_tx.send(msg).unwrap_or(());

                            refresh_ui = false;
                            t0 = t1;
                        }
                    }

                    // last ui refresh
                    if refresh_ui {
                        let msg = EventMessage::new(0, Event::RefreshViewEvent);
                        core_tx.send(msg).unwrap_or(());
                    }
                }

                _ => {
                    panic!("worker thread received an unexpected message");
                }
            }
        }
    }
}

use crate::core::document::DocumentBuilder;

/// TODO(ceg): replace this by load/unload doc functions
/// the ui will open the documents on demand
pub fn load_files(editor: &mut Editor<'static>, env: &mut EditorEnv<'static>) {
    let mut id = editor.document_map.read().len();

    for f in &editor.config.files_list {
        // check file type
        if let Ok(metadata) = fs::metadata(f) {
            let file_type = metadata.file_type();

            // ignore directories for now
            if file_type.is_dir() {
                continue;
            }
        } else {
            // log error
            eprintln!("cannot check {} file type", f);
            continue;
        }

        let b = DocumentBuilder::new()
            .document_name(f)
            .file_name(f)
            .internal(false)
            .finalize();

        if let Some(b) = b {
            let doc_id = document::Id(id);
            b.as_ref().write().id = doc_id; // TODO(ceg): improve doc id generation
            editor.document_map.write().insert(doc_id, b);
            id += 1;
        }
    }

    // default buffer ?
    let map_is_empty = editor.document_map.read().is_empty();
    if map_is_empty {
        // edit.get_untitled_count() -> 1

        let b = DocumentBuilder::new()
            .document_name("untitled-1")
            .internal(false)
            .finalize();
        if let Some(b) = b {
            {
                let mut d = b.write();
                let s = WELCOME_MESSAGE.as_bytes();

                // move 1st tag to ctor/doc::new() ?
                d.tag(env.current_time, 0, vec![0]); // TODO(ceg): rm this only if the buffer log is cleared
                                                     //    create_views(&mut editor, &mut env);

                d.insert(0, s.len(), s);

                // do not allow to go back to empty buffer
                d.buffer_log_reset();
                d.changed = false;
            }
            let doc_id = document::Id(id);
            editor.document_map.write().insert(doc_id, b);
            id += 1;
        }
    }

    // configure document
    let modes = editor.modes.clone();
    for (mode_name, mode) in modes.borrow().iter() {
        // per mode document metadata
        dbg_println!("setup mode[{}] document metadata", mode_name);
        let mut mode = mode.borrow_mut();
        let map = editor.document_map.clone();
        let mut map = map.as_ref().write();
        for (_, doc) in map.iter_mut() {
            let mut doc = doc.write();
            mode.configure_document(editor, env, &mut doc);
        }
    }

    dbg_println!("id {}", id);
}

pub fn create_views(mut editor: &mut Editor<'static>, mut env: &mut EditorEnv<'static>) {
    let document_map = editor.document_map.clone();
    let document_map = document_map.read();

    // create default views
    // sort by arg pos first
    let mut docs_id: Vec<document::Id> = document_map.iter().map(|(k, _v)| *k).collect();
    docs_id.sort();
    let mut docs: Vec<Arc<RwLock<Document>>> = vec![];
    for id in docs_id.iter() {
        if let Some(doc) = document_map.get(id) {
            docs.push(Arc::clone(doc));
        }
    }

    let modes = match std::env::var("SINGLE_VIEW") {
        Ok(_) => vec!["simple-view".to_owned()],
        _ => vec!["basic-editor".to_owned()],
    };

    // create views
    for doc in docs {
        let view = View::new(
            &mut editor,
            &mut env,
            None,
            0,
            0,
            1,
            1,
            Some(doc),
            &modes,
            0,
        );
        dbg_println!("create {:?}", view.id);

        // top level views
        editor.root_views.push(view.id);
        editor.view_map.insert(view.id, Rc::new(RwLock::new(view)));
    }

    // index documents
    // TODO(ceg): send one event per doc
    if true {
        let msg = EventMessage {
            seq: 0,
            event: Event::IndexTask {
                document_map: Arc::clone(&editor.document_map),
            },
        };
        editor.indexer_tx.send(msg).unwrap_or(());
    }
}

use crate::core::modes::BasicEditorMode;
use crate::core::modes::SimpleViewMode;

use crate::core::modes::CoreMode;
use crate::core::modes::FindMode;
use crate::core::modes::TextMode;

use crate::core::modes::StatusMode;

use crate::core::modes::HsplitMode;
use crate::core::modes::VsplitMode;

use crate::core::modes::VscrollbarMode;

use crate::core::modes::LineNumberMode;

pub fn load_modes(editor: &mut Editor, _env: &mut EditorEnv) {
    // set default mode(s)
    editor.register_mode(Box::new(CoreMode::new()));
    editor.register_mode(Box::new(BasicEditorMode::new()));
    editor.register_mode(Box::new(SimpleViewMode::new()));

    editor.register_mode(Box::new(VsplitMode::new()));
    editor.register_mode(Box::new(HsplitMode::new()));

    editor.register_mode(Box::new(VscrollbarMode::new()));

    editor.register_mode(Box::new(TextMode::new()));
    editor.register_mode(Box::new(StatusMode::new()));

    editor.register_mode(Box::new(FindMode::new()));

    editor.register_mode(Box::new(LineNumberMode::new()));
}
<|endoftext|>


<commit_before>#![crate_id = "jit_macro"]
#![comment = "LibJIT Macro"]
#![crate_type = "dylib"]
#![crate_type = "rlib"]
#![feature(quote, globs, macro_registrar, managed_boxes)]
#![deny(non_uppercase_statics, missing_doc, unnecessary_parens, unrecognized_lint, unreachable_code, unnecessary_allocation, unnecessary_typecast, unnecessary_allocation, uppercase_variables, non_camel_case_types, unused_must_use)]
//! This crate provides a macro `jit_type` which can compile a Rust type
//! into its LibJIT counterpart. It even supports functions *and* structs!
//!
//! For example:
//!
//! ```rust
//! #![feature(phase)]
//! extern crate jit;
//! #[phase(syntax)]
//! extern crate jit_macro;
//! fn main() {
//! 	let ty = jit_type!(i64);
//! 	assert_eq(ty.get_size(), 8);
//! 	let floor_sig = jit_type!((f64) -> i32);
//! 	let double_array_ty = jit_type!({
//! 		len: int,
//! 		ptr: **f64
//! 	});
//! }
//! ```
extern crate syntax;
extern crate collections;
use collections::TreeMap;
use syntax::ext::quote::rt::ToSource;
use syntax::ast::*;
use syntax::codemap::Span;
use syntax::ext::base::*;
use syntax::ext::build::AstBuilder;
use syntax::parse::token;
use syntax::parse::token::*;
use std::iter::Peekable;
use std::slice::Items;
#[macro_registrar]
#[doc(hidden)]
pub fn macro_registrar(register: |Name, SyntaxExtension|) {
	let expander = box BasicMacroExpander { expander: jit_type, span: None };
	register(token::intern("jit_type"), NormalTT(expander, None))
}
fn error(e:Option<TokenTree>, tok:Token) -> Result<(), String> {
	match e {
		Some(TTTok(_, ref got)) => Err(format!("Bad syntax - got {} but expected {}", got, tok)),
		Some(TTNonterminal(_, ref got)) => Err(format!("Bad syntax - got {} but expected {}", got, tok)),
		Some(TTDelim(ref trees)) => error(Some(trees.deref().get(0).clone()), tok),
		Some(_) => Err("Bad syntax".into_string()),
		None => Err("Abrupt end".into_string())
	}
}
fn expect<'a>(_: &mut ExtCtxt, tts:&mut Peekable<&'a TokenTree, Items<'a, TokenTree>>, tok:Token) -> Result<(), String> {
	match tts.next().map(|v| v.clone()) {
		Some(TTTok(_, ref got)) if *got == tok => {
			Ok(())
		},
		Some(TTDelim(ref trees)) => {
			let curr = trees.deref().get(0).clone();
			match curr {
				TTTok(_, ref got) if *got == tok =>
					Ok(()),
				TTTok(_, ref tok) =>
					Err(format!("Bad result {}", tok)),
				_ =>
					Err("Bad result".into_string())
			}
		},
		v => error(v, tok)
	}
}
fn jit_parse_type<'a>(cx: &mut ExtCtxt, tts:&mut Peekable<&'a TokenTree, Items<'a, TokenTree>>) -> Result<P<Expr>, String> {
	let val:Option<&TokenTree> = tts.peek().map(|v|*v);
	match val {
		Some(ref val) => {
			match **val {
				TTDelim(ref toks) => {
					match *toks.get(0) {
						TTTok(span, LPAREN) => {
							let toks = toks.slice(1, toks.len() - 1);
							if toks.len() == 0 {
								Ok(quote_expr!(cx, ::jit::Types::get_void()))
							} else {
								let mut arg_types : Vec<P<Expr>> = Vec::new();
								{
									let mut tts = toks.iter().peekable();
									loop {
										let parsed = try!(jit_parse_type(cx, &mut tts));
										arg_types.push(cx.expr_addr_of(parsed.span, parsed));
										match tts.next() {
											Some(v) =>
												match *v {
													TTTok(_, COMMA) => {
														continue;
													},
													_ => return Err("Unexpected token in function decl".into_string())
												},
											None => break
										}
									}
								}
								tts.next();
								try!(expect(cx, tts, RARROW));
								let parsed_ret = try!(jit_parse_type(cx, tts));
								let ret = cx.expr_addr_of(parsed_ret.span, parsed_ret);
								let arg_types_ex = cx.expr(span, ExprVec(arg_types));
								let func = quote_expr!(&mut*cx, ::jit::Type::create_signature);
								let cdecl = quote_expr!(&mut*cx, ::jit::CDECL);
								let arg_types_ex = cx.expr_mut_addr_of(arg_types_ex.span, arg_types_ex);
								let arg_types_ex = cx.expr_method_call(span, arg_types_ex, cx.ident_of("as_mut_slice"), vec!());
								let call = cx.expr_call(span, func, vec!(cdecl, ret, arg_types_ex));
								Ok(call)

							}
						},
						TTTok(span, LBRACE) => {
							let toks = toks.slice(1, toks.len() - 1);
							let mut tts = toks.iter().peekable();
							let mut structure:TreeMap<String, P<Expr>> = TreeMap::new();
							loop {
								match tts.next() {
									Some(ref v) => {
										match **v {
											TTTok(_, COMMA) =>
												continue,
											TTTok(_, IDENT(ident, _)) => {
												let field_name = ident.to_source();
												try!(expect(cx, &mut tts, COLON));
												let field_type = try!(jit_parse_type(cx, &mut tts));
												structure.insert(field_name, field_type);
											},
											_ => return Err("Expected ident".into_string())
										}
									},
									None => break
								}
							}
							let func = quote_expr!(&mut*cx, ::jit::Type::create_struct);
							let targs:Vec<P<Expr>> = structure.iter().map(|(_, t)| cx.expr_addr_of(t.span, *t)).collect();
							let targs_vec = cx.expr(span, ExprVec(targs));
							let targs_vec = cx.expr_method_call(span, targs_vec, cx.ident_of("as_mut_slice"), vec!());
							Ok(cx.expr_call(span, func, vec!(targs_vec)))
						},
						_ => Err("Expected bracket".into_string())
					}
				},
				TTTok(_, IDENT(ident, _)) => {
					let ident = ident.to_source();
					let res = match ident.as_slice() {
						"void" => Ok(quote_expr!(cx, ::jit::Types::get_void())),
						"int" | "i32" => Ok(quote_expr!(cx, ::jit::Types::get_int())),
						"uint" | "u32" => Ok(quote_expr!(cx, ::jit::Types::get_uint())),
						"i64" => Ok(quote_expr!(cx, ::jit::Types::get_long())),
						"u64" => Ok(quote_expr!(cx, ::jit::Types::get_ulong())),
						"char" => Ok(quote_expr!(cx, ::jit::Types::get_char())),
						"bool" => Ok(quote_expr!(cx, ::jit::Types::get_bool())),
						"f64" => Ok(quote_expr!(cx, ::jit::Types::get_float64())),
						"f32" => Ok(quote_expr!(cx, ::jit::Types::get_float32())),
						"String" => Ok(quote_expr!(cx, ::jit::Types::get_cstring())),
						"Vec" => Ok(quote_expr!(cx, ::jit::Types::get_vec())),
						id => Err(format!("Unexpected identifier {}", id))
					};
					if res.is_ok() {
						tts.next();
					}
					res
				},
				TTTok(span, BINOP(STAR)) => {
					tts.next();
					let func = quote_expr!(&mut*cx, ::jit::Type::create_pointer);
					let parsed_type = try!(jit_parse_type(&mut*cx, tts));
					let wv = cx.expr_addr_of(span, parsed_type);
					Ok(cx.expr_call(span, func, vec!(wv)))
				},
				TTTok(_, DOLLAR) => {
					tts.next();
					match tts.next() {
						Some(ref tks) => {
							match **tks {
								TTTok(span, IDENT(ident, _)) => {
									Ok(cx.expr_ident(span, ident))
								}
								_ => Err("Expected next".into_string())
							}
						},
						None => Err("Abrupt end".into_string())
					}
				},
				ref token => {
					try!(error(Some(token.clone()), DOLLAR));
					Err("...".into_string())
				}
			}
		},
		None =>
			Err("Not a type".into_string())
	}
}
fn jit_type(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box<MacResult> {
	let mut iter = tts.iter().peekable();
	MacExpr::new(match jit_parse_type(cx, &mut iter) {
		Ok(v) => v,
		Err(e) => {
			cx.span_err(sp, e.as_slice());
			quote_expr!(cx, fail!("Invalid"))
		}
	})
}<commit_msg>Support struct field names<commit_after>#![crate_id = "jit_macro"]
#![comment = "LibJIT Macro"]
#![crate_type = "dylib"]
#![crate_type = "rlib"]
#![feature(quote, globs, macro_registrar, managed_boxes)]
#![deny(non_uppercase_statics, missing_doc, unnecessary_parens, unrecognized_lint, unreachable_code, unnecessary_allocation, unnecessary_typecast, unnecessary_allocation, uppercase_variables, non_camel_case_types, unused_must_use)]
//! This crate provides a macro `jit_type` which can compile a Rust type
//! into its LibJIT counterpart. It even supports functions *and* structs!
//!
//! For example:
//!
//! ```rust
//! #![feature(phase)]
//! extern crate jit;
//! #[phase(syntax)]
//! extern crate jit_macro;
//! fn main() {
//! 	let ty = jit_type!(i64);
//! 	assert_eq(ty.get_size(), 8);
//! 	let floor_sig = jit_type!((f64) -> i32);
//! 	let double_array_ty = jit_type!({
//! 		len: int,
//! 		ptr: **f64
//! 	});
//! }
//! ```
extern crate syntax;
use syntax::ext::quote::rt::ToSource;
use syntax::ast::*;
use syntax::codemap::Span;
use syntax::ext::base::*;
use syntax::ext::build::AstBuilder;
use syntax::parse::token;
use syntax::parse::token::*;
use std::iter::Peekable;
use std::slice::Items;
#[macro_registrar]
#[doc(hidden)]
pub fn macro_registrar(register: |Name, SyntaxExtension|) {
	let expander = box BasicMacroExpander { expander: jit_type, span: None };
	register(token::intern("jit_type"), NormalTT(expander, None))
}
fn error(e:Option<TokenTree>, tok:Token) -> Result<(), String> {
	match e {
		Some(TTTok(_, ref got)) => Err(format!("Bad syntax - got {} but expected {}", got, tok)),
		Some(TTNonterminal(_, ref got)) => Err(format!("Bad syntax - got {} but expected {}", got, tok)),
		Some(TTDelim(ref trees)) => error(Some(trees.deref().get(0).clone()), tok),
		Some(_) => Err("Bad syntax".into_string()),
		None => Err("Abrupt end".into_string())
	}
}
fn expect<'a>(_: &mut ExtCtxt, tts:&mut Peekable<&'a TokenTree, Items<'a, TokenTree>>, tok:Token) -> Result<(), String> {
	match tts.next().map(|v| v.clone()) {
		Some(TTTok(_, ref got)) if *got == tok => {
			Ok(())
		},
		Some(TTDelim(ref trees)) => {
			let curr = trees.deref().get(0).clone();
			match curr {
				TTTok(_, ref got) if *got == tok =>
					Ok(()),
				TTTok(_, ref tok) =>
					Err(format!("Bad result {}", tok)),
				_ =>
					Err("Bad result".into_string())
			}
		},
		v => error(v, tok)
	}
}
fn jit_parse_type<'a>(cx: &mut ExtCtxt, tts:&mut Peekable<&'a TokenTree, Items<'a, TokenTree>>) -> Result<P<Expr>, String> {
	let val:Option<&TokenTree> = tts.peek().map(|v|*v);
	match val {
		Some(ref val) => {
			match **val {
				TTDelim(ref toks) => {
					match *toks.get(0) {
						TTTok(span, LPAREN) => {
							let toks = toks.slice(1, toks.len() - 1);
							if toks.len() == 0 {
								Ok(quote_expr!(cx, ::jit::Types::get_void()))
							} else {
								let mut arg_types : Vec<P<Expr>> = Vec::new();
								{
									let mut tts = toks.iter().peekable();
									loop {
										let parsed = try!(jit_parse_type(cx, &mut tts));
										arg_types.push(cx.expr_addr_of(parsed.span, parsed));
										match tts.next() {
											Some(v) =>
												match *v {
													TTTok(_, COMMA) => {
														continue;
													},
													_ => return Err("Unexpected token in function decl".into_string())
												},
											None => break
										}
									}
								}
								tts.next();
								try!(expect(cx, tts, RARROW));
								let parsed_ret = try!(jit_parse_type(cx, tts));
								let ret = cx.expr_addr_of(parsed_ret.span, parsed_ret);
								let arg_types_ex = cx.expr(span, ExprVec(arg_types));
								let func = quote_expr!(&mut*cx, ::jit::Type::create_signature);
								let cdecl = quote_expr!(&mut*cx, ::jit::CDECL);
								let arg_types_ex = cx.expr_mut_addr_of(arg_types_ex.span, arg_types_ex);
								let arg_types_ex = cx.expr_method_call(span, arg_types_ex, cx.ident_of("as_mut_slice"), vec!());
								let call = cx.expr_call(span, func, vec!(cdecl, ret, arg_types_ex));
								Ok(call)

							}
						},
						TTTok(span, LBRACE) => {
							let toks = toks.slice(1, toks.len() - 1);
							let mut tts = toks.iter().peekable();
							let mut names:Vec<P<Expr>> = Vec::new();
							let mut types:Vec<P<Expr>> = Vec::new();
							loop {
								match tts.next() {
									Some(ref v) => {
										match **v {
											TTTok(_, COMMA) =>
												continue,
											TTTok(span, IDENT(field_name, _)) => {
												try!(expect(cx, &mut tts, COLON));
												let field_type = try!(jit_parse_type(cx, &mut tts));
												names.push(cx.expr_str_uniq(span, get_ident(field_name)));
												types.push(cx.expr_addr_of(field_type.span, field_type));
											},
											_ => return Err("Expected ident".into_string())
										}
									},
									None => break
								}
							}
							let func = quote_expr!(&mut*cx, ::jit::Type::create_struct);
							let types_vec = cx.expr(span, ExprVec(types));
							let types_vec_slices = cx.expr_method_call(span, types_vec, cx.ident_of("as_mut_slice"), vec!());
							let names_vec = cx.expr(span, ExprVec(names));
							let ident_type = cx.ident_of("ty");
							Ok(cx.expr_block(cx.block(span, vec!(
								cx.stmt_let(span, false, ident_type, cx.expr_call(span, func, vec!(types_vec_slices))),
								cx.stmt_expr(cx.expr_method_call(span, cx.expr_ident(span, ident_type), cx.ident_of("set_names"), vec!(names_vec))),
								cx.stmt_expr(cx.expr_ident(span, ident_type))
							), None)))
						},
						_ => Err("Expected bracket".into_string())
					}
				},
				TTTok(_, IDENT(ident, _)) => {
					let ident = ident.to_source();
					let res = match ident.as_slice() {
						"void" => Ok(quote_expr!(cx, ::jit::Types::get_void())),
						"int" | "i32" => Ok(quote_expr!(cx, ::jit::Types::get_int())),
						"uint" | "u32" => Ok(quote_expr!(cx, ::jit::Types::get_uint())),
						"i64" => Ok(quote_expr!(cx, ::jit::Types::get_long())),
						"u64" => Ok(quote_expr!(cx, ::jit::Types::get_ulong())),
						"char" => Ok(quote_expr!(cx, ::jit::Types::get_char())),
						"bool" => Ok(quote_expr!(cx, ::jit::Types::get_bool())),
						"f64" => Ok(quote_expr!(cx, ::jit::Types::get_float64())),
						"f32" => Ok(quote_expr!(cx, ::jit::Types::get_float32())),
						"String" => Ok(quote_expr!(cx, ::jit::Types::get_cstring())),
						"Vec" => Ok(quote_expr!(cx, ::jit::Types::get_vec())),
						id => Err(format!("Unexpected identifier {}", id))
					};
					if res.is_ok() {
						tts.next();
					}
					res
				},
				TTTok(span, BINOP(STAR)) => {
					tts.next();
					let func = quote_expr!(&mut*cx, ::jit::Type::create_pointer);
					let parsed_type = try!(jit_parse_type(&mut*cx, tts));
					let wv = cx.expr_addr_of(span, parsed_type);
					Ok(cx.expr_call(span, func, vec!(wv)))
				},
				TTTok(_, DOLLAR) => {
					tts.next();
					match tts.next() {
						Some(ref tks) => {
							match **tks {
								TTTok(span, IDENT(ident, _)) => {
									Ok(cx.expr_ident(span, ident))
								}
								_ => Err("Expected next".into_string())
							}
						},
						None => Err("Abrupt end".into_string())
					}
				},
				ref token => {
					try!(error(Some(token.clone()), DOLLAR));
					Err("...".into_string())
				}
			}
		},
		None =>
			Err("Not a type".into_string())
	}
}
fn jit_type(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box<MacResult> {
	let mut iter = tts.iter().peekable();
	MacExpr::new(match jit_parse_type(cx, &mut iter) {
		Ok(v) => v,
		Err(e) => {
			cx.span_err(sp, e.as_slice());
			quote_expr!(cx, fail!("Invalid"))
		}
	})
}<|endoftext|>


<commit_before>use core::ops::Deref;
use diesel;
use diesel::prelude::*;
use diesel::sqlite::SqliteConnection;
use regex::Regex;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time;

use db::DB;
use db::schema::{directories, songs};
use errors::*;
use metadata;
use vfs::Vfs;

const INDEX_BUILDING_INSERT_BUFFER_SIZE: usize = 1000; // Insertions in each transaction

pub struct IndexConfig {
	pub album_art_pattern: Option<Regex>,
	pub sleep_duration: u64, // in seconds
	pub path: PathBuf,
}

impl IndexConfig {
	pub fn new() -> IndexConfig {
		IndexConfig {
			sleep_duration: 60 * 30, // 30 minutes
			album_art_pattern: None,
			path: Path::new(":memory:").to_path_buf(),
		}
	}
}

#[derive(Debug, Insertable)]
#[table_name="songs"]
struct NewSong {
	path: String,
	parent: String,
	track_number: Option<i32>,
	disc_number: Option<i32>,
	title: Option<String>,
	artist: Option<String>,
	album_artist: Option<String>,
	year: Option<i32>,
	album: Option<String>,
	artwork: Option<String>,
}

#[derive(Debug, Insertable)]
#[table_name="directories"]
struct NewDirectory {
	path: String,
	parent: Option<String>,
	artist: Option<String>,
	year: Option<i32>,
	album: Option<String>,
	artwork: Option<String>,
	date_added: i32,
}

struct IndexBuilder<'db> {
	new_songs: Vec<NewSong>,
	new_directories: Vec<NewDirectory>,
	connection: &'db Mutex<SqliteConnection>,
}

impl<'db> IndexBuilder<'db> {
	fn new(connection: &Mutex<SqliteConnection>) -> Result<IndexBuilder> {
		let mut new_songs = Vec::new();
		let mut new_directories = Vec::new();
		new_songs.reserve_exact(INDEX_BUILDING_INSERT_BUFFER_SIZE);
		new_directories.reserve_exact(INDEX_BUILDING_INSERT_BUFFER_SIZE);
		Ok(IndexBuilder {
		       new_songs: new_songs,
		       new_directories: new_directories,
		       connection: connection,
		   })
	}

	fn flush_songs(&mut self) -> Result<()> {
		let connection = self.connection.lock().unwrap();
		let connection = connection.deref();
		connection
			.transaction::<_, Error, _>(|| {
				                            diesel::insert(&self.new_songs)
				                                .into(songs::table)
				                                .execute(connection)?;
				                            Ok(())
				                           })?;
		self.new_songs.clear();
		Ok(())
	}

	fn flush_directories(&mut self) -> Result<()> {
		let connection = self.connection.lock().unwrap();
		let connection = connection.deref();
		connection
			.transaction::<_, Error, _>(|| {
				                            diesel::insert(&self.new_directories)
				                                .into(directories::table)
				                                .execute(connection)?;
				                            Ok(())
				                           })?;
		self.new_directories.clear();
		Ok(())
	}

	fn push_song(&mut self, song: NewSong) -> Result<()> {
		if self.new_songs.len() >= self.new_songs.capacity() {
			self.flush_songs()?;
		}
		self.new_songs.push(song);
		Ok(())
	}

	fn push_directory(&mut self, directory: NewDirectory) -> Result<()> {
		if self.new_directories.len() >= self.new_directories.capacity() {
			self.flush_directories()?;
		}
		self.new_directories.push(directory);
		Ok(())
	}
}

pub struct Index {
	vfs: Arc<Vfs>,
	connection: Arc<Mutex<SqliteConnection>>,
	album_art_pattern: Option<Regex>,
	sleep_duration: u64,
}

impl Index {
	pub fn new(vfs: Arc<Vfs>,
	           connection: Arc<Mutex<SqliteConnection>>,
	           config: &IndexConfig)
	           -> Index {
		let index = Index {
			vfs: vfs,
			connection: connection,
			album_art_pattern: config.album_art_pattern.clone(),
			sleep_duration: config.sleep_duration,
		};
		index
	}

	pub fn update_index(&self) -> Result<()> {
		let start = time::Instant::now();
		println!("Beginning library index update");
		self.clean()?;
		self.populate()?;
		println!("Library index update took {} seconds",
		         start.elapsed().as_secs());
		Ok(())
	}

	fn clean(&self) -> Result<()> {
		{
			let all_songs: Vec<String>;
			{
				let connection = self.connection.lock().unwrap();
				let connection = connection.deref();
				all_songs = songs::table
					.select(songs::columns::path)
					.load(connection)?;
			}

			let missing_songs = all_songs
				.into_iter()
				.filter(|ref song_path| {
					        let path = Path::new(&song_path);
					        !path.exists() || self.vfs.real_to_virtual(path).is_err()
					       })
				.collect::<Vec<_>>();

			let connection = self.connection.lock().unwrap();
			let connection = connection.deref();
			diesel::delete(songs::table.filter(songs::columns::path.eq_any(missing_songs)))
				.execute(connection)?;
		}

		{
			let all_directories: Vec<String>;
			{
				let connection = self.connection.lock().unwrap();
				let connection = connection.deref();
				all_directories = directories::table
					.select(directories::columns::path)
					.load(connection)?;
			}

			let missing_directories = all_directories
				.into_iter()
				.filter(|ref directory_path| {
					        let path = Path::new(&directory_path);
					        !path.exists() || self.vfs.real_to_virtual(path).is_err()
					       })
				.collect::<Vec<_>>();

			let connection = self.connection.lock().unwrap();
			let connection = connection.deref();
			diesel::delete(directories::table.filter(directories::columns::path
			                                             .eq_any(missing_directories)))
					.execute(connection)?;
		}

		Ok(())
	}

	fn populate(&self) -> Result<()> {
		let vfs = self.vfs.deref();
		let mount_points = vfs.get_mount_points();
		let mut builder = IndexBuilder::new(&self.connection)?;
		for (_, target) in mount_points {
			self.populate_directory(&mut builder, None, target.as_path())?;
		}
		builder.flush_songs()?;
		builder.flush_directories()?;
		Ok(())
	}

	fn get_artwork(&self, dir: &Path) -> Option<String> {
		let pattern = match self.album_art_pattern {
			Some(ref p) => p,
			_ => return None,
		};

		if let Ok(dir_content) = fs::read_dir(dir) {
			for file in dir_content {
				if let Ok(file) = file {
					if let Some(name_string) = file.file_name().to_str() {
						if pattern.is_match(name_string) {
							return file.path().to_str().map(|p| p.to_owned());
						}
					}
				}
			}
		}

		None
	}

	fn populate_directory(&self,
	                      builder: &mut IndexBuilder,
	                      parent: Option<&Path>,
	                      path: &Path)
	                      -> Result<()> {

		// Find artwork
		let artwork = self.get_artwork(path);

		// Extract path and parent path
		let parent_string = parent.and_then(|p| p.to_str()).map(|s| s.to_owned());
		let path_string = path.to_str().ok_or("Invalid directory path")?;

		// Find date added
		let metadata = fs::metadata(path_string)?;
		let created = metadata
			.created()
			.or(metadata.modified())?
			.duration_since(time::UNIX_EPOCH)?
			.as_secs() as i32;

		let mut directory_album = None;
		let mut directory_year = None;
		let mut directory_artist = None;
		let mut inconsistent_directory_album = false;
		let mut inconsistent_directory_year = false;
		let mut inconsistent_directory_artist = false;

		// Insert content
		if let Ok(dir_content) = fs::read_dir(path) {
			for file in dir_content {
				let file_path = match file {
					Ok(f) => f.path(),
					_ => continue,
				};

				if file_path.is_dir() {
					self.populate_directory(builder, Some(path), file_path.as_path())?;
				} else {
					if let Some(file_path_string) = file_path.to_str() {
						if let Ok(tags) = metadata::read(file_path.as_path()) {
							if tags.year.is_some() {
								inconsistent_directory_year |= directory_year.is_some() &&
								                               directory_year != tags.year;
								directory_year = tags.year;
							}

							if tags.album.is_some() {
								inconsistent_directory_album |= directory_album.is_some() &&
								                                directory_album != tags.album;
								directory_album = tags.album.as_ref().map(|a| a.clone());
							}

							if tags.album_artist.is_some() {
								inconsistent_directory_artist |= directory_artist.is_some() &&
								                                 directory_artist !=
								                                 tags.album_artist;
								directory_artist = tags.album_artist.as_ref().map(|a| a.clone());
							} else if tags.artist.is_some() {
								inconsistent_directory_artist |= directory_artist.is_some() &&
								                                 directory_artist != tags.artist;
								directory_artist = tags.artist.as_ref().map(|a| a.clone());
							}

							let song = NewSong {
								path: file_path_string.to_owned(),
								parent: path_string.to_owned(),
								disc_number: tags.disc_number.map(|n| n as i32),
								track_number: tags.track_number.map(|n| n as i32),
								title: tags.title,
								artist: tags.artist,
								album_artist: tags.album_artist,
								album: tags.album,
								year: tags.year,
								artwork: artwork.as_ref().map(|s| s.to_owned()),
							};

							builder.push_song(song)?;
						}
					}
				}
			}
		}

		// Insert directory
		if inconsistent_directory_year {
			directory_year = None;
		}
		if inconsistent_directory_album {
			directory_album = None;
		}
		if inconsistent_directory_artist {
			directory_artist = None;
		}

		let directory = NewDirectory {
			path: path_string.to_owned(),
			parent: parent_string,
			artwork: artwork,
			album: directory_album,
			artist: directory_artist,
			year: directory_year,
			date_added: created,
		};
		builder.push_directory(directory)?;

		Ok(())
	}

	pub fn update_loop(&self) {
		loop {
			if let Err(e) = self.update_index() {
				println!("Error while updating index: {}", e);
			}
			thread::sleep(time::Duration::from_secs(self.sleep_duration));
		}
	}
}

fn _get_test_db(name: &str) -> DB {
	use vfs::VfsConfig;
	use std::collections::HashMap;

	let mut collection_path = PathBuf::new();
	collection_path.push("test");
	collection_path.push("collection");
	let mut mount_points = HashMap::new();
	mount_points.insert("root".to_owned(), collection_path);

	let vfs = Arc::new(Vfs::new(VfsConfig { mount_points: mount_points }));

	let mut index_config = IndexConfig::new();
	index_config.album_art_pattern = Some(Regex::new(r#"^Folder\.(png|jpg|jpeg)$"#).unwrap());
	index_config.path = PathBuf::new();
	index_config.path.push("test");
	index_config.path.push(name);

	if index_config.path.exists() {
		fs::remove_file(&index_config.path).unwrap();
	}

	DB::new(vfs, &index_config).unwrap()
}

#[test]
fn test_populate() {
	use db::models::*;

	let db = _get_test_db("populate.sqlite");
	let index = db.get_index();
	index.update_index().unwrap();
	index.update_index().unwrap(); // Check that subsequent updates don't run into conflicts

	let connection = index.connection.lock().unwrap();
	let connection = connection.deref();
	let all_directories: Vec<Directory> = directories::table.load(connection).unwrap();
	let all_songs: Vec<Song> = songs::table.load(connection).unwrap();
	assert_eq!(all_directories.len(), 5);
	assert_eq!(all_songs.len(), 12);
}

#[test]
fn test_metadata() {
	use db::models::*;

	let mut target = PathBuf::new();
	target.push("test");
	target.push("collection");
	target.push("Tobokegao");
	target.push("Picnic");

	let mut song_path = target.clone();
	song_path.push("05 - シャーベット (Sherbet).mp3");

	let mut artwork_path = target.clone();
	artwork_path.push("Folder.png");

	let db = _get_test_db("metadata.sqlite");
	let index = db.get_index();
	index.update_index().unwrap();

	let connection = index.connection.lock().unwrap();
	let connection = connection.deref();
	let songs: Vec<Song> = songs::table
		.filter(songs::columns::title.eq("シャーベット (Sherbet)"))
		.load(connection)
		.unwrap();

	assert_eq!(songs.len(), 1);
	let song = &songs[0];
	assert_eq!(song.path, song_path.to_string_lossy().as_ref());
	assert_eq!(song.track_number, Some(5));
	assert_eq!(song.disc_number, None);
	assert_eq!(song.title, Some("シャーベット (Sherbet)".to_owned()));
	assert_eq!(song.artist, Some("Tobokegao".to_owned()));
	assert_eq!(song.album_artist, None);
	assert_eq!(song.album, Some("Picnic".to_owned()));
	assert_eq!(song.year, Some(2016));
	assert_eq!(song.artwork,
	           Some(artwork_path.to_string_lossy().into_owned()));
}
<commit_msg>Minor improvements to indexing: - Directories now appear before their content during indexing - No longer error when cleaning a large number of missing items<commit_after>use core::ops::Deref;
use diesel;
use diesel::prelude::*;
use diesel::sqlite::SqliteConnection;
use regex::Regex;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time;

use db::DB;
use db::schema::{directories, songs};
use errors::*;
use metadata;
use vfs::Vfs;

const INDEX_BUILDING_INSERT_BUFFER_SIZE: usize = 1000; // Insertions in each transaction
const INDEX_BUILDING_CLEAN_BUFFER_SIZE: usize = 500; // Insertions in each transaction

pub struct IndexConfig {
	pub album_art_pattern: Option<Regex>,
	pub sleep_duration: u64, // in seconds
	pub path: PathBuf,
}

impl IndexConfig {
	pub fn new() -> IndexConfig {
		IndexConfig {
			sleep_duration: 60 * 30, // 30 minutes
			album_art_pattern: None,
			path: Path::new(":memory:").to_path_buf(),
		}
	}
}

#[derive(Debug, Insertable)]
#[table_name="songs"]
struct NewSong {
	path: String,
	parent: String,
	track_number: Option<i32>,
	disc_number: Option<i32>,
	title: Option<String>,
	artist: Option<String>,
	album_artist: Option<String>,
	year: Option<i32>,
	album: Option<String>,
	artwork: Option<String>,
}

#[derive(Debug, Insertable)]
#[table_name="directories"]
struct NewDirectory {
	path: String,
	parent: Option<String>,
	artist: Option<String>,
	year: Option<i32>,
	album: Option<String>,
	artwork: Option<String>,
	date_added: i32,
}

struct IndexBuilder<'db> {
	new_songs: Vec<NewSong>,
	new_directories: Vec<NewDirectory>,
	connection: &'db Mutex<SqliteConnection>,
}

impl<'db> IndexBuilder<'db> {
	fn new(connection: &Mutex<SqliteConnection>) -> Result<IndexBuilder> {
		let mut new_songs = Vec::new();
		let mut new_directories = Vec::new();
		new_songs.reserve_exact(INDEX_BUILDING_INSERT_BUFFER_SIZE);
		new_directories.reserve_exact(INDEX_BUILDING_INSERT_BUFFER_SIZE);
		Ok(IndexBuilder {
		       new_songs: new_songs,
		       new_directories: new_directories,
		       connection: connection,
		   })
	}

	fn flush_songs(&mut self) -> Result<()> {
		let connection = self.connection.lock().unwrap();
		let connection = connection.deref();
		connection
			.transaction::<_, Error, _>(|| {
				                            diesel::insert(&self.new_songs)
				                                .into(songs::table)
				                                .execute(connection)?;
				                            Ok(())
				                           })?;
		self.new_songs.clear();
		Ok(())
	}

	fn flush_directories(&mut self) -> Result<()> {
		let connection = self.connection.lock().unwrap();
		let connection = connection.deref();
		connection
			.transaction::<_, Error, _>(|| {
				                            diesel::insert(&self.new_directories)
				                                .into(directories::table)
				                                .execute(connection)?;
				                            Ok(())
				                           })?;
		self.new_directories.clear();
		Ok(())
	}

	fn push_song(&mut self, song: NewSong) -> Result<()> {
		if self.new_songs.len() >= self.new_songs.capacity() {
			self.flush_songs()?;
		}
		self.new_songs.push(song);
		Ok(())
	}

	fn push_directory(&mut self, directory: NewDirectory) -> Result<()> {
		if self.new_directories.len() >= self.new_directories.capacity() {
			self.flush_directories()?;
		}
		self.new_directories.push(directory);
		Ok(())
	}
}

pub struct Index {
	vfs: Arc<Vfs>,
	connection: Arc<Mutex<SqliteConnection>>,
	album_art_pattern: Option<Regex>,
	sleep_duration: u64,
}

impl Index {
	pub fn new(vfs: Arc<Vfs>,
	           connection: Arc<Mutex<SqliteConnection>>,
	           config: &IndexConfig)
	           -> Index {
		let index = Index {
			vfs: vfs,
			connection: connection,
			album_art_pattern: config.album_art_pattern.clone(),
			sleep_duration: config.sleep_duration,
		};
		index
	}

	pub fn update_index(&self) -> Result<()> {
		let start = time::Instant::now();
		println!("Beginning library index update");
		self.clean()?;
		self.populate()?;
		println!("Library index update took {} seconds",
		         start.elapsed().as_secs());
		Ok(())
	}

	fn clean(&self) -> Result<()> {
		{
			let all_songs: Vec<String>;
			{
				let connection = self.connection.lock().unwrap();
				let connection = connection.deref();
				all_songs = songs::table
					.select(songs::columns::path)
					.load(connection)?;
			}

			let missing_songs = all_songs
				.into_iter()
				.filter(|ref song_path| {
					        let path = Path::new(&song_path);
					        !path.exists() || self.vfs.real_to_virtual(path).is_err()
					       })
				.collect::<Vec<_>>();

			{
				let connection = self.connection.lock().unwrap();
				let connection = connection.deref();
				for chunk in missing_songs[..].chunks(INDEX_BUILDING_CLEAN_BUFFER_SIZE) {
					diesel::delete(songs::table.filter(songs::columns::path.eq_any(chunk)))
						.execute(connection)?;
				}

			}
		}

		{
			let all_directories: Vec<String>;
			{
				let connection = self.connection.lock().unwrap();
				let connection = connection.deref();
				all_directories = directories::table
					.select(directories::columns::path)
					.load(connection)?;
			}

			let missing_directories = all_directories
				.into_iter()
				.filter(|ref directory_path| {
					        let path = Path::new(&directory_path);
					        !path.exists() || self.vfs.real_to_virtual(path).is_err()
					       })
				.collect::<Vec<_>>();

			{
				let connection = self.connection.lock().unwrap();
				let connection = connection.deref();
				for chunk in missing_directories[..].chunks(INDEX_BUILDING_CLEAN_BUFFER_SIZE) {
					diesel::delete(directories::table.filter(directories::columns::path
					                                             .eq_any(chunk)))
							.execute(connection)?;
				}
			}
		}

		Ok(())
	}

	fn populate(&self) -> Result<()> {
		let vfs = self.vfs.deref();
		let mount_points = vfs.get_mount_points();
		let mut builder = IndexBuilder::new(&self.connection)?;
		for (_, target) in mount_points {
			self.populate_directory(&mut builder, None, target.as_path())?;
		}
		builder.flush_songs()?;
		builder.flush_directories()?;
		Ok(())
	}

	fn get_artwork(&self, dir: &Path) -> Option<String> {
		let pattern = match self.album_art_pattern {
			Some(ref p) => p,
			_ => return None,
		};

		if let Ok(dir_content) = fs::read_dir(dir) {
			for file in dir_content {
				if let Ok(file) = file {
					if let Some(name_string) = file.file_name().to_str() {
						if pattern.is_match(name_string) {
							return file.path().to_str().map(|p| p.to_owned());
						}
					}
				}
			}
		}

		None
	}

	fn populate_directory(&self,
	                      builder: &mut IndexBuilder,
	                      parent: Option<&Path>,
	                      path: &Path)
	                      -> Result<()> {

		// Find artwork
		let artwork = self.get_artwork(path);

		// Extract path and parent path
		let parent_string = parent.and_then(|p| p.to_str()).map(|s| s.to_owned());
		let path_string = path.to_str().ok_or("Invalid directory path")?;

		// Find date added
		let metadata = fs::metadata(path_string)?;
		let created = metadata
			.created()
			.or(metadata.modified())?
			.duration_since(time::UNIX_EPOCH)?
			.as_secs() as i32;

		let mut directory_album = None;
		let mut directory_year = None;
		let mut directory_artist = None;
		let mut inconsistent_directory_album = false;
		let mut inconsistent_directory_year = false;
		let mut inconsistent_directory_artist = false;

		// Sub directories
		let mut sub_directories = Vec::new();

		// Insert content
		if let Ok(dir_content) = fs::read_dir(path) {
			for file in dir_content {
				let file_path = match file {
					Ok(f) => f.path(),
					_ => continue,
				};

				if file_path.is_dir() {
					sub_directories.push(file_path.to_path_buf());
					continue;
				}

				if let Some(file_path_string) = file_path.to_str() {
					if let Ok(tags) = metadata::read(file_path.as_path()) {
						if tags.year.is_some() {
							inconsistent_directory_year |= directory_year.is_some() &&
							                               directory_year != tags.year;
							directory_year = tags.year;
						}

						if tags.album.is_some() {
							inconsistent_directory_album |= directory_album.is_some() &&
							                                directory_album != tags.album;
							directory_album = tags.album.as_ref().map(|a| a.clone());
						}

						if tags.album_artist.is_some() {
							inconsistent_directory_artist |= directory_artist.is_some() &&
							                                 directory_artist != tags.album_artist;
							directory_artist = tags.album_artist.as_ref().map(|a| a.clone());
						} else if tags.artist.is_some() {
							inconsistent_directory_artist |= directory_artist.is_some() &&
							                                 directory_artist != tags.artist;
							directory_artist = tags.artist.as_ref().map(|a| a.clone());
						}

						let song = NewSong {
							path: file_path_string.to_owned(),
							parent: path_string.to_owned(),
							disc_number: tags.disc_number.map(|n| n as i32),
							track_number: tags.track_number.map(|n| n as i32),
							title: tags.title,
							artist: tags.artist,
							album_artist: tags.album_artist,
							album: tags.album,
							year: tags.year,
							artwork: artwork.as_ref().map(|s| s.to_owned()),
						};

						builder.push_song(song)?;
					}
				}
			}
		}

		// Insert directory
		if inconsistent_directory_year {
			directory_year = None;
		}
		if inconsistent_directory_album {
			directory_album = None;
		}
		if inconsistent_directory_artist {
			directory_artist = None;
		}

		let directory = NewDirectory {
			path: path_string.to_owned(),
			parent: parent_string,
			artwork: artwork,
			album: directory_album,
			artist: directory_artist,
			year: directory_year,
			date_added: created,
		};
		builder.push_directory(directory)?;

		// Populate subdirectories
		for sub_directory in sub_directories {
			self.populate_directory(builder, Some(path), &sub_directory)?;
		}

		Ok(())
	}

	pub fn update_loop(&self) {
		loop {
			if let Err(e) = self.update_index() {
				println!("Error while updating index: {}", e);
			}
			thread::sleep(time::Duration::from_secs(self.sleep_duration));
		}
	}
}

fn _get_test_db(name: &str) -> DB {
	use vfs::VfsConfig;
	use std::collections::HashMap;

	let mut collection_path = PathBuf::new();
	collection_path.push("test");
	collection_path.push("collection");
	let mut mount_points = HashMap::new();
	mount_points.insert("root".to_owned(), collection_path);

	let vfs = Arc::new(Vfs::new(VfsConfig { mount_points: mount_points }));

	let mut index_config = IndexConfig::new();
	index_config.album_art_pattern = Some(Regex::new(r#"^Folder\.(png|jpg|jpeg)$"#).unwrap());
	index_config.path = PathBuf::new();
	index_config.path.push("test");
	index_config.path.push(name);

	if index_config.path.exists() {
		fs::remove_file(&index_config.path).unwrap();
	}

	DB::new(vfs, &index_config).unwrap()
}

#[test]
fn test_populate() {
	use db::models::*;

	let db = _get_test_db("populate.sqlite");
	let index = db.get_index();
	index.update_index().unwrap();
	index.update_index().unwrap(); // Check that subsequent updates don't run into conflicts

	let connection = index.connection.lock().unwrap();
	let connection = connection.deref();
	let all_directories: Vec<Directory> = directories::table.load(connection).unwrap();
	let all_songs: Vec<Song> = songs::table.load(connection).unwrap();
	assert_eq!(all_directories.len(), 5);
	assert_eq!(all_songs.len(), 12);
}

#[test]
fn test_metadata() {
	use db::models::*;

	let mut target = PathBuf::new();
	target.push("test");
	target.push("collection");
	target.push("Tobokegao");
	target.push("Picnic");

	let mut song_path = target.clone();
	song_path.push("05 - シャーベット (Sherbet).mp3");

	let mut artwork_path = target.clone();
	artwork_path.push("Folder.png");

	let db = _get_test_db("metadata.sqlite");
	let index = db.get_index();
	index.update_index().unwrap();

	let connection = index.connection.lock().unwrap();
	let connection = connection.deref();
	let songs: Vec<Song> = songs::table
		.filter(songs::columns::title.eq("シャーベット (Sherbet)"))
		.load(connection)
		.unwrap();

	assert_eq!(songs.len(), 1);
	let song = &songs[0];
	assert_eq!(song.path, song_path.to_string_lossy().as_ref());
	assert_eq!(song.track_number, Some(5));
	assert_eq!(song.disc_number, None);
	assert_eq!(song.title, Some("シャーベット (Sherbet)".to_owned()));
	assert_eq!(song.artist, Some("Tobokegao".to_owned()));
	assert_eq!(song.album_artist, None);
	assert_eq!(song.album, Some("Picnic".to_owned()));
	assert_eq!(song.year, Some(2016));
	assert_eq!(song.artwork,
	           Some(artwork_path.to_string_lossy().into_owned()));
}
<|endoftext|>