<commit_before>// Clippy settings, full list here:
// <https://rust-lang.github.io/rust-clippy/master/index.html>
#![deny(clippy::clippy::implicit_return)]
#![allow(clippy::clippy::clippy::needless_return)]

#[macro_use]
extern crate lazy_static;

use backtrace::Backtrace;
use constants::*;
use git_version::git_version;
use isatty::{stdin_isatty, stdout_isatty};
use refiner::Refiner;
use regex::Regex;
use std::env;
use std::fs;
use std::io::{self, BufRead, BufReader, BufWriter, Write};
use std::panic;
use std::process::exit;
use std::process::{Command, Stdio};
use std::str;

mod constants;
mod refiner;
mod tokenizer;

const HELP_TEXT: &str = r#"
Usage: diff ... | riff

Colors diff and highlights what parts of changed lines have changed.

Git integration:
    git config --global pager.diff riff
    git config --global pager.show riff
    git config --global interactive.filter riff

Options:
    --help:    Print this text
    --version: Print version number
"#;

const HELP_TEXT_FOOTER: &str = r#"
Report issues at <https://github.com/walles/riff>.
"#;

const CRASH_FOOTER: &str = r#"
Please copy all of the above up to the --- RIFF CRASHED --- marker and report it at one of:
* <https://github.com/walles/riff/issues> (preferred)
* <johan.walles@gmail.com>
"#;

const HUNK_HEADER: &str = "\x1b[36m"; // Cyan
const PAGER_FORKBOMB_STOP: &str = "_RIFF_IGNORE_PAGER";

const GIT_VERSION: &str = git_version!();

lazy_static! {
    static ref STATIC_HEADERS: Vec<(Regex, &'static str)> = vec![
        (Regex::new("^diff ").unwrap(), BOLD),
        (Regex::new("^index ").unwrap(), BOLD),
        (Regex::new("^--- ").unwrap(), BOLD),
        (Regex::new("^\\+\\+\\+ ").unwrap(), BOLD),
        (Regex::new("^@@ ").unwrap(), HUNK_HEADER),
    ];
    static ref ANSI_COLOR_REGEX: Regex = Regex::new("\x1b[^m]*m").unwrap();
}

enum LastLineKind {
    Initial,
    Add,
    Remove,
}

#[must_use]
fn get_fixed_highlight(line: &str) -> &str {
    for static_header in STATIC_HEADERS.iter() {
        let re = &static_header.0;
        if re.is_match(line) {
            return static_header.1;
        }
    }

    return "";
}

fn print(stream: &mut BufWriter<&mut dyn Write>, text: &str) {
    stream.write(text.as_bytes()).unwrap();
}

fn println(stream: &mut BufWriter<&mut dyn Write>, text: &str) {
    print(stream, &text);
    print(stream, "\n");
}

fn highlight_diff(input: &mut dyn io::Read, output: &mut dyn io::Write) {
    let mut adds = String::new();
    let mut removes = String::new();
    let input = BufReader::new(input);
    let output = &mut BufWriter::new(output);
    let mut last_line_kind = LastLineKind::Initial;
    for line in input.lines() {
        let line = line.unwrap();

        // Strip out incoming ANSI formatting. This enables us to highlight
        // already-colored input.
        let line = ANSI_COLOR_REGEX.replace_all(&line, "");

        let fixed_highlight = get_fixed_highlight(&line);
        if !fixed_highlight.is_empty() {
            // Drain outstanding adds / removes
            for line in Refiner::create(&adds, &removes).format() {
                println(output, &line);
            }
            adds.clear();
            removes.clear();

            print(output, fixed_highlight);
            print(output, &line);
            println(output, NORMAL);
            continue;
        }

        // Collect adds / removes
        if !line.is_empty() && line.chars().next().unwrap() == '+' {
            adds.push_str(&line[1..]);
            adds.push('\n');
            last_line_kind = LastLineKind::Add;
            continue;
        } else if !line.is_empty() && line.chars().next().unwrap() == '-' {
            removes.push_str(&line[1..]);
            removes.push('\n');
            last_line_kind = LastLineKind::Remove;
            continue;
        }

        if line == NO_EOF_NEWLINE_MARKER {
            match last_line_kind {
                LastLineKind::Add => {
                    assert!(adds.pop().unwrap() == '\n');
                    continue;
                }
                LastLineKind::Remove => {
                    assert!(removes.pop().unwrap() == '\n');
                    continue;
                }
                LastLineKind::Initial => {
                    // This block intentionally left blank
                }
            }
        }

        last_line_kind = LastLineKind::Initial;

        // Drain outstanding adds / removes
        for line in Refiner::create(&adds, &removes).format() {
            println(output, &line);
        }
        adds.clear();
        removes.clear();

        // Print current line
        if line == NO_EOF_NEWLINE_MARKER {
            print(output, NO_EOF_NEWLINE_COLOR);
            print(output, &line);
            println(output, NORMAL);
        } else {
            println(output, &line);
        }
    }
    for line in Refiner::create(&adds, &removes).format() {
        println(output, &line);
    }
}

/// Try paging using the named pager (`$PATH` will be searched).
///
/// Returns `true` if the pager was found, `false` otherwise.
#[must_use]
fn try_pager(pager_name: &str) -> bool {
    let mut command = Command::new(pager_name);

    if env::var(PAGER_FORKBOMB_STOP).is_ok() {
        // Try preventing fork bombing if $PAGER is set to riff
        return false;
    }
    command.env(PAGER_FORKBOMB_STOP, "1");

    if !env::var("LESS").is_ok() {
        // Set by git when paging
        command.env("LESS", "FRX");
    }

    if !env::var("LV").is_ok() {
        // Set by git when paging
        command.env("LV", "-c");
    }

    command.stdin(Stdio::piped());

    match command.spawn() {
        Ok(mut pager) => {
            let pager_stdin = pager.stdin.as_mut().unwrap();
            highlight_diff(&mut io::stdin().lock(), pager_stdin);

            // FIXME: Report pager exit status if non-zero, together with
            // contents of pager stderr as well if possible.
            pager.wait().expect("Waiting for pager failed");

            return true;
        }
        Err(_) => {
            return false;
        }
    }
}

/// If `option` is found in `argv`, all instances of `option` will be removed
/// from `argv`.
///
/// Returns `true` if `option` was found and consumed, false otherwise.
#[must_use]
fn consume(option: &str, argv: &mut Vec<String>) -> bool {
    if !argv.contains(&option.to_string()) {
        // Not found
        return false;
    }

    argv.retain(|x| x != option);
    return true;
}

fn print_help(output: &mut dyn io::Write) {
    output.write(HELP_TEXT.trim().as_bytes()).unwrap();
    output.write(b"\n").unwrap();
    output.write(b"\n").unwrap();

    let self_path = env::args().into_iter().next().unwrap();
    let self_path = fs::canonicalize(self_path).unwrap();
    let self_path = self_path.as_path().to_str().unwrap();

    // FIXME: Do this only if we aren't already in the $PATH
    output
        .write("Installing riff in the $PATH:\n".as_bytes())
        .unwrap();
    output
        .write(&format!("  * sudo cp {} /usr/local/bin\n", self_path).as_bytes())
        .unwrap();
    output.write(b"\n").unwrap();

    output.write(HELP_TEXT_FOOTER.trim().as_bytes()).unwrap();
    output.write(b"\n").unwrap();
}

fn panic_handler(panic_info: &panic::PanicInfo) {
    let stderr: &mut dyn Write = &mut io::stderr();
    let stderr = &mut BufWriter::new(stderr);
    println(stderr, "\n\n------------ RIFF CRASHED -------------------");

    // Panic message
    if let Some(s) = panic_info.payload().downcast_ref::<&str>() {
        println(stderr, &format!("Panic message: <{:?}>", s));
        println(stderr, "");
    }

    // Backtrace
    // FIXME: Ditch the Backtrace-internal frames from this backtrace
    // FIXME: Ditch the panic internal frames at the end of the backtrace
    println(stderr, &format!("{:?}", Backtrace::new()));

    println(stderr, &format!("Riff version: {}", GIT_VERSION));

    println(stderr, CRASH_FOOTER);
}

fn main() {
    panic::set_hook(Box::new(|panic_info: &panic::PanicInfo| {
        panic_handler(panic_info);
    }));

    let mut args: Vec<String> = env::args().collect();
    if consume("--help", &mut args) || consume("-h", &mut args) {
        print_help(&mut io::stdout());
        return;
    }

    if consume("--version", &mut args) {
        println!("riff {}", GIT_VERSION);
        println!("");
        println!("Source code available at <https://github.com/walles/riff>.");
        return;
    }

    if stdin_isatty() {
        eprintln!("ERROR: Expected input from a pipe");
        eprintln!("");
        print_help(&mut io::stderr());
        exit(1);
    }

    if !stdout_isatty() {
        // We're being piped, just do stdin -> stdout
        highlight_diff(&mut io::stdin().lock(), &mut io::stdout());
        return;
    }

    let pager_env_var = env::var("PAGER");
    if pager_env_var.is_ok() {
        let pager_value = pager_env_var.unwrap();
        if try_pager(&pager_value) {
            return;
        }

        // FIXME: Print warning at the end if $PAGER was set to something that
        // doesn't exist.
    }

    if try_pager("moar") {
        return;
    }

    if try_pager("less") {
        return;
    }

    // No pager found, wth?
    highlight_diff(&mut io::stdin().lock(), &mut io::stdout());
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;

    #[cfg(test)]
    use pretty_assertions::assert_eq;

    fn remove(text: &str) -> String {
        return format!("{}{}{}", REMOVE, text, NORMAL);
    }

    fn add(text: &str) -> String {
        return format!("{}{}{}", ADD, text, NORMAL);
    }

    #[test]
    fn test_remove_trailing_newline() {
        let mut input = "-hej\n\
            +hej\n\
            \\ No newline at end of file\n\
            "
        .as_bytes();

        let expected = format!(
            "{}\n{}\n{}\n",
            remove(&format!("-hej{}⏎", INVERSE_VIDEO)),
            add("+hej"),
            format!(
                "{}\\ No newline at end of file{}",
                NO_EOF_NEWLINE_COLOR, NORMAL
            )
        );

        let mut output: Vec<u8> = Vec::new();
        highlight_diff(&mut input, &mut output);
        assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
    }

    #[test]
    fn test_trailing_newline_context() {
        let mut input = "+bepa\n apa\n\\ No newline at end of file\n".as_bytes();

        let expected = format!(
            "{}\n{}\n{}\n",
            add("+bepa"),
            " apa",
            format!(
                "{}\\ No newline at end of file{}",
                NO_EOF_NEWLINE_COLOR, NORMAL
            )
        );

        let mut output: Vec<u8> = Vec::new();
        highlight_diff(&mut input, &mut output);
        assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
    }

    #[test]
    fn test_testdata_examples() {
        // Example value: `/Users/johan/src/riff/target/debug/deps/riff-7a8916c06b0d3d6c`
        let exe_path = std::env::current_exe().unwrap();

        // Example value: `/Users/johan/src/riff`
        let project_path = exe_path
            .parent()
            .unwrap()
            .parent()
            .unwrap()
            .parent()
            .unwrap()
            .parent()
            .unwrap();

        // Example value: `/Users/johan/src/riff/testdata`
        let testdata_path = &project_path.join("testdata");
        assert!(testdata_path.is_dir());

        // Iterate all files in there
        for diff in fs::read_dir(testdata_path).unwrap() {
            let diff = diff.unwrap();
            let diff = diff.path();
            let diff = diff.as_path();
            if !diff.is_file() {
                continue;
            }

            if diff.extension().unwrap() != "diff" {
                continue;
            }

            println!("Evaluating example file <{}>...", diff.to_str().unwrap());

            // Run highlighting on the file into a memory buffer
            let mut actual_result: Vec<u8> = Vec::new();
            highlight_diff(&mut fs::File::open(diff).unwrap(), &mut actual_result);
            let actual_result = str::from_utf8(&actual_result).unwrap();

            // Load the corresponding .riff-output file into a string
            let basename = diff.file_stem().unwrap().to_str().unwrap();
            let expected_path = format!(
                "{}/{}.riff-output",
                testdata_path.to_str().unwrap(),
                basename
            );
            let expected_result = fs::read_to_string(expected_path).unwrap();

            // Assert that the highlighting output matches the contents of .riff-output
            assert_eq!(actual_result, expected_result);
        }
    }
}
<commit_msg>Fail CI build on Clippy warnings<commit_after>// Clippy settings, full list here:
// <https://rust-lang.github.io/rust-clippy/master/index.html>
#![allow(clippy::clippy::clippy::needless_return)]
//
// Fail build on Clippy warnings
#![deny(warnings)]

#[macro_use]
extern crate lazy_static;

use backtrace::Backtrace;
use constants::*;
use git_version::git_version;
use isatty::{stdin_isatty, stdout_isatty};
use refiner::Refiner;
use regex::Regex;
use std::env;
use std::fs;
use std::io::{self, BufRead, BufReader, BufWriter, Write};
use std::panic;
use std::process::exit;
use std::process::{Command, Stdio};
use std::str;

mod constants;
mod refiner;
mod tokenizer;

const HELP_TEXT: &str = r#"
Usage: diff ... | riff

Colors diff and highlights what parts of changed lines have changed.

Git integration:
    git config --global pager.diff riff
    git config --global pager.show riff
    git config --global interactive.filter riff

Options:
    --help:    Print this text
    --version: Print version number
"#;

const HELP_TEXT_FOOTER: &str = r#"
Report issues at <https://github.com/walles/riff>.
"#;

const CRASH_FOOTER: &str = r#"
Please copy all of the above up to the --- RIFF CRASHED --- marker and report it at one of:
* <https://github.com/walles/riff/issues> (preferred)
* <johan.walles@gmail.com>
"#;

const HUNK_HEADER: &str = "\x1b[36m"; // Cyan
const PAGER_FORKBOMB_STOP: &str = "_RIFF_IGNORE_PAGER";

const GIT_VERSION: &str = git_version!();

lazy_static! {
    static ref STATIC_HEADERS: Vec<(Regex, &'static str)> = vec![
        (Regex::new("^diff ").unwrap(), BOLD),
        (Regex::new("^index ").unwrap(), BOLD),
        (Regex::new("^--- ").unwrap(), BOLD),
        (Regex::new("^\\+\\+\\+ ").unwrap(), BOLD),
        (Regex::new("^@@ ").unwrap(), HUNK_HEADER),
    ];
    static ref ANSI_COLOR_REGEX: Regex = Regex::new("\x1b[^m]*m").unwrap();
}

enum LastLineKind {
    Initial,
    Add,
    Remove,
}

#[must_use]
fn get_fixed_highlight(line: &str) -> &str {
    for static_header in STATIC_HEADERS.iter() {
        let re = &static_header.0;
        if re.is_match(line) {
            return static_header.1;
        }
    }

    return "";
}

fn print(stream: &mut BufWriter<&mut dyn Write>, text: &str) {
    stream.write(text.as_bytes()).unwrap();
}

fn println(stream: &mut BufWriter<&mut dyn Write>, text: &str) {
    print(stream, &text);
    print(stream, "\n");
}

fn highlight_diff(input: &mut dyn io::Read, output: &mut dyn io::Write) {
    let mut adds = String::new();
    let mut removes = String::new();
    let input = BufReader::new(input);
    let output = &mut BufWriter::new(output);
    let mut last_line_kind = LastLineKind::Initial;
    for line in input.lines() {
        let line = line.unwrap();

        // Strip out incoming ANSI formatting. This enables us to highlight
        // already-colored input.
        let line = ANSI_COLOR_REGEX.replace_all(&line, "");

        let fixed_highlight = get_fixed_highlight(&line);
        if !fixed_highlight.is_empty() {
            // Drain outstanding adds / removes
            for line in Refiner::create(&adds, &removes).format() {
                println(output, &line);
            }
            adds.clear();
            removes.clear();

            print(output, fixed_highlight);
            print(output, &line);
            println(output, NORMAL);
            continue;
        }

        // Collect adds / removes
        if !line.is_empty() && line.chars().next().unwrap() == '+' {
            adds.push_str(&line[1..]);
            adds.push('\n');
            last_line_kind = LastLineKind::Add;
            continue;
        } else if !line.is_empty() && line.chars().next().unwrap() == '-' {
            removes.push_str(&line[1..]);
            removes.push('\n');
            last_line_kind = LastLineKind::Remove;
            continue;
        }

        if line == NO_EOF_NEWLINE_MARKER {
            match last_line_kind {
                LastLineKind::Add => {
                    assert!(adds.pop().unwrap() == '\n');
                    continue;
                }
                LastLineKind::Remove => {
                    assert!(removes.pop().unwrap() == '\n');
                    continue;
                }
                LastLineKind::Initial => {
                    // This block intentionally left blank
                }
            }
        }

        last_line_kind = LastLineKind::Initial;

        // Drain outstanding adds / removes
        for line in Refiner::create(&adds, &removes).format() {
            println(output, &line);
        }
        adds.clear();
        removes.clear();

        // Print current line
        if line == NO_EOF_NEWLINE_MARKER {
            print(output, NO_EOF_NEWLINE_COLOR);
            print(output, &line);
            println(output, NORMAL);
        } else {
            println(output, &line);
        }
    }
    for line in Refiner::create(&adds, &removes).format() {
        println(output, &line);
    }
}

/// Try paging using the named pager (`$PATH` will be searched).
///
/// Returns `true` if the pager was found, `false` otherwise.
#[must_use]
fn try_pager(pager_name: &str) -> bool {
    let mut command = Command::new(pager_name);

    if env::var(PAGER_FORKBOMB_STOP).is_ok() {
        // Try preventing fork bombing if $PAGER is set to riff
        return false;
    }
    command.env(PAGER_FORKBOMB_STOP, "1");

    if !env::var("LESS").is_ok() {
        // Set by git when paging
        command.env("LESS", "FRX");
    }

    if !env::var("LV").is_ok() {
        // Set by git when paging
        command.env("LV", "-c");
    }

    command.stdin(Stdio::piped());

    match command.spawn() {
        Ok(mut pager) => {
            let pager_stdin = pager.stdin.as_mut().unwrap();
            highlight_diff(&mut io::stdin().lock(), pager_stdin);

            // FIXME: Report pager exit status if non-zero, together with
            // contents of pager stderr as well if possible.
            pager.wait().expect("Waiting for pager failed");

            return true;
        }
        Err(_) => {
            return false;
        }
    }
}

/// If `option` is found in `argv`, all instances of `option` will be removed
/// from `argv`.
///
/// Returns `true` if `option` was found and consumed, false otherwise.
#[must_use]
fn consume(option: &str, argv: &mut Vec<String>) -> bool {
    if !argv.contains(&option.to_string()) {
        // Not found
        return false;
    }

    argv.retain(|x| x != option);
    return true;
}

fn print_help(output: &mut dyn io::Write) {
    output.write(HELP_TEXT.trim().as_bytes()).unwrap();
    output.write(b"\n").unwrap();
    output.write(b"\n").unwrap();

    let self_path = env::args().into_iter().next().unwrap();
    let self_path = fs::canonicalize(self_path).unwrap();
    let self_path = self_path.as_path().to_str().unwrap();

    // FIXME: Do this only if we aren't already in the $PATH
    output
        .write("Installing riff in the $PATH:\n".as_bytes())
        .unwrap();
    output
        .write(&format!("  * sudo cp {} /usr/local/bin\n", self_path).as_bytes())
        .unwrap();
    output.write(b"\n").unwrap();

    output.write(HELP_TEXT_FOOTER.trim().as_bytes()).unwrap();
    output.write(b"\n").unwrap();
}

fn panic_handler(panic_info: &panic::PanicInfo) {
    let stderr: &mut dyn Write = &mut io::stderr();
    let stderr = &mut BufWriter::new(stderr);
    println(stderr, "\n\n------------ RIFF CRASHED -------------------");

    // Panic message
    if let Some(s) = panic_info.payload().downcast_ref::<&str>() {
        println(stderr, &format!("Panic message: <{:?}>", s));
        println(stderr, "");
    }

    // Backtrace
    // FIXME: Ditch the Backtrace-internal frames from this backtrace
    // FIXME: Ditch the panic internal frames at the end of the backtrace
    println(stderr, &format!("{:?}", Backtrace::new()));

    println(stderr, &format!("Riff version: {}", GIT_VERSION));

    println(stderr, CRASH_FOOTER);
}

fn main() {
    panic::set_hook(Box::new(|panic_info: &panic::PanicInfo| {
        panic_handler(panic_info);
    }));

    let mut args: Vec<String> = env::args().collect();
    if consume("--help", &mut args) || consume("-h", &mut args) {
        print_help(&mut io::stdout());
        return;
    }

    if consume("--version", &mut args) {
        println!("riff {}", GIT_VERSION);
        println!("");
        println!("Source code available at <https://github.com/walles/riff>.");
        return;
    }

    if stdin_isatty() {
        eprintln!("ERROR: Expected input from a pipe");
        eprintln!("");
        print_help(&mut io::stderr());
        exit(1);
    }

    if !stdout_isatty() {
        // We're being piped, just do stdin -> stdout
        highlight_diff(&mut io::stdin().lock(), &mut io::stdout());
        return;
    }

    let pager_env_var = env::var("PAGER");
    if pager_env_var.is_ok() {
        let pager_value = pager_env_var.unwrap();
        if try_pager(&pager_value) {
            return;
        }

        // FIXME: Print warning at the end if $PAGER was set to something that
        // doesn't exist.
    }

    if try_pager("moar") {
        return;
    }

    if try_pager("less") {
        return;
    }

    // No pager found, wth?
    highlight_diff(&mut io::stdin().lock(), &mut io::stdout());
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;

    #[cfg(test)]
    use pretty_assertions::assert_eq;

    fn remove(text: &str) -> String {
        return format!("{}{}{}", REMOVE, text, NORMAL);
    }

    fn add(text: &str) -> String {
        return format!("{}{}{}", ADD, text, NORMAL);
    }

    #[test]
    fn test_remove_trailing_newline() {
        let mut input = "-hej\n\
            +hej\n\
            \\ No newline at end of file\n\
            "
        .as_bytes();

        let expected = format!(
            "{}\n{}\n{}\n",
            remove(&format!("-hej{}⏎", INVERSE_VIDEO)),
            add("+hej"),
            format!(
                "{}\\ No newline at end of file{}",
                NO_EOF_NEWLINE_COLOR, NORMAL
            )
        );

        let mut output: Vec<u8> = Vec::new();
        highlight_diff(&mut input, &mut output);
        assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
    }

    #[test]
    fn test_trailing_newline_context() {
        let mut input = "+bepa\n apa\n\\ No newline at end of file\n".as_bytes();

        let expected = format!(
            "{}\n{}\n{}\n",
            add("+bepa"),
            " apa",
            format!(
                "{}\\ No newline at end of file{}",
                NO_EOF_NEWLINE_COLOR, NORMAL
            )
        );

        let mut output: Vec<u8> = Vec::new();
        highlight_diff(&mut input, &mut output);
        assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
    }

    #[test]
    fn test_testdata_examples() {
        // Example value: `/Users/johan/src/riff/target/debug/deps/riff-7a8916c06b0d3d6c`
        let exe_path = std::env::current_exe().unwrap();

        // Example value: `/Users/johan/src/riff`
        let project_path = exe_path
            .parent()
            .unwrap()
            .parent()
            .unwrap()
            .parent()
            .unwrap()
            .parent()
            .unwrap();

        // Example value: `/Users/johan/src/riff/testdata`
        let testdata_path = &project_path.join("testdata");
        assert!(testdata_path.is_dir());

        // Iterate all files in there
        for diff in fs::read_dir(testdata_path).unwrap() {
            let diff = diff.unwrap();
            let diff = diff.path();
            let diff = diff.as_path();
            if !diff.is_file() {
                continue;
            }

            if diff.extension().unwrap() != "diff" {
                continue;
            }

            println!("Evaluating example file <{}>...", diff.to_str().unwrap());

            // Run highlighting on the file into a memory buffer
            let mut actual_result: Vec<u8> = Vec::new();
            highlight_diff(&mut fs::File::open(diff).unwrap(), &mut actual_result);
            let actual_result = str::from_utf8(&actual_result).unwrap();

            // Load the corresponding .riff-output file into a string
            let basename = diff.file_stem().unwrap().to_str().unwrap();
            let expected_path = format!(
                "{}/{}.riff-output",
                testdata_path.to_str().unwrap(),
                basename
            );
            let expected_result = fs::read_to_string(expected_path).unwrap();

            // Assert that the highlighting output matches the contents of .riff-output
            assert_eq!(actual_result, expected_result);
        }
    }
}
<|endoftext|>


<commit_before>#![allow(unused_parens)]

extern crate ansi_term;
extern crate clap;
extern crate regex;

#[macro_use]
mod error;
mod motions;
mod accelerator;
mod driver;

use std::env;
use std::path::Path;
use ansi_term::Colour::*;
use clap::{App, Arg, SubCommand};
use clap::AppSettings::*;
use error::Error;
use accelerator::Accelerator;
use driver::Driver;

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

fn main() {
  run().unwrap();
}

fn run() -> Result<(), Error> {
  let directory_arg = (
    Arg::with_name("directory")
    .help("The directory in which accelerate will look for motions, can also be set with ACCELERATE_DIRECTORY")
    .short("d")
    .long("directory")
    .value_name("PATH")
  );

  let driver_args = [
    Arg::with_name("conn_str")
    .help("The connection string to use when connecting to your database, can also be set with ACCELERATE_CONNECTION")
    .short("c")
    .long("connection")
    .value_name("CONNECTION"),
    Arg::with_name("driver")
    .help("The driver accelerate will use to execute your motions, can also be set with ACCELERATE_DRIVER")
    .short("r")
    .long("driver")
    .value_name("DRIVER")
    .possible_values(&["postgres"])
  ];

  let matches = (
    App::new("Accelerate")
    .bin_name("accelerate")
    .version(VERSION)
    .author("Caleb Meredith <calebmeredith8@gmail.com>")
    .about("Accelerate your databases back and forth through easy to manage migration files")
    .settings(&[
      ArgRequiredElseHelp,
      GlobalVersion,
      VersionlessSubcommands,
      UnifiedHelpMessage,
      SubcommandRequiredElseHelp,
      AllowLeadingHyphen
    ])
    .subcommand(
      SubCommand::with_name("ls")
      .about("Lists all of your available motions")
      .arg(&directory_arg)
    )
    .subcommand(
      SubCommand::with_name("status")
      .about("Informs you about the status of all your motions in the database")
      .arg(&directory_arg)
      .args(&driver_args)
    )
    .subcommand(
      SubCommand::with_name("create")
      .about("Creates a new motion using your defined template")
      .arg(&directory_arg)
      .arg(
        Arg::with_name("name")
        .help("The name (with directory path) of the motion you want to create. A timestamp will automatically be added to the last segment")
        .required(true)
        .value_name("NAME")
      )
    )
    .subcommand(
      SubCommand::with_name("add")
      .about("Will add `n` motions to the driver")
      .arg(&directory_arg)
      .args(&driver_args)
      .arg(
        Arg::with_name("n")
        .help("The number of motions to add to the driver")
        .value_name("N")
        .default_value("1")
      )
    )
    .subcommand(
      SubCommand::with_name("sub")
      .about("Will sub `n` motions in the driver")
      .arg(&directory_arg)
      .args(&driver_args)
      .arg(
        Arg::with_name("n")
        .help("The number of motions to sub in the driver")
        .value_name("N")
        .default_value("1")
      )
    )
    .subcommand(
      SubCommand::with_name("up")
      .about("Will add all motions that have not yet been applied to the database")
      .arg(&directory_arg)
      .args(&driver_args)
    )
    .subcommand(
      SubCommand::with_name("down")
      .about("Will sub all motions that have been applied in the database")
      .arg(&directory_arg)
      .args(&driver_args)
    )
    .subcommand(
      SubCommand::with_name("redo")
      .about("Will sub and then add just the last motion")
      .arg(&directory_arg)
      .args(&driver_args)
    )
    .subcommand(
      SubCommand::with_name("reset")
      .about("Will sub all motions that have been applied in the database and then add all of the motions available")
      .arg(&directory_arg)
      .args(&driver_args)
    )
  ).get_matches();

  let subcommand_name = matches.subcommand_name().unwrap();
  let matches = matches.subcommand_matches(subcommand_name).unwrap();

  let directory_env = env::var("ACCELERATE_DIRECTORY").ok();
  let driver_env = env::var("ACCELERATE_DRIVER").ok();
  let connection_env = env::var("ACCELERATE_CONNECTION").ok();

  let motions = || {
    let directory = matches.value_of("directory").or(directory_env.as_ref().map(|s| s.as_str())).unwrap_or(".");
    motions::find(&Path::new(directory))
  };

  let driver = || {
    let driver_type = matches.value_of("driver").or(driver_env.as_ref().map(|s| s.as_str()));
    let conn_str = matches.value_of("conn_str").or(connection_env.as_ref().map(|s| s.as_str()));
    let conn_str = try!(conn_str.ok_or(error!("A connection string is required and none was provided to either the command line or the environment variable `ACCELERATE_CONNECTION`.")) as Result<&str, Error>);
    driver::get(driver_type, conn_str)
  };

  let accelerator = || Accelerator::new(try!(driver()), try!(motions()));

  match subcommand_name {
    "ls" => {
      for motion in try!(motions()) {
        println!("{}", motion);
      }
    },
    "status" => {
      let driver = try!(driver());
      let records = try!(driver.get_records());
      for motion in try!(motions()) {
        if records.contains(&motion.name) {
          println!("{} {}", Green.bold().paint("✔"), motion);
        } else {
          println!("{} {}", Red.bold().paint("𝙭"), motion);
        }
      }
    },
    "create" => {
      unimplemented!();
    },
    "add" => {
      let mut accelerator = try!(accelerator());
      let n = try!(matches.value_of("n").unwrap_or("1").parse::<usize>());
      try!(accelerator.add(n));
    },
    "sub" => {
      let mut accelerator = try!(accelerator());
      let n = try!(matches.value_of("n").unwrap_or("1").parse::<usize>());
      try!(accelerator.sub(n));
    },
    "up" => {
      let mut accelerator = try!(accelerator());
      try!(accelerator.add(usize::max_value()));
    },
    "down" => {
      let mut accelerator = try!(accelerator());
      try!(accelerator.sub(usize::max_value()));
    },
    "redo" => {
      let mut accelerator = try!(accelerator());
      try!(accelerator.sub(1));
      try!(accelerator.add(1));
    },
    "reset" => {
      let mut accelerator = try!(accelerator());
      let applied = accelerator.applied_count();
      try!(accelerator.sub(usize::max_value()));
      try!(accelerator.add(applied));
    },
    _ => unreachable!(),
  }

  Ok(())
}
<commit_msg>rename some things<commit_after>#![allow(unused_parens)]

extern crate ansi_term;
extern crate clap;
extern crate regex;

#[macro_use]
mod error;
mod motions;
mod accelerator;
mod driver;

use std::env;
use std::path::Path;
use ansi_term::Colour::*;
use clap::{App, Arg, SubCommand};
use clap::AppSettings::*;
use error::Error;
use accelerator::Accelerator;
use driver::Driver;

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

fn main() {
  run().unwrap();
}

fn run() -> Result<(), Error> {
  let directory_arg = (
    Arg::with_name("directory")
    .help("The directory in which accelerate will look for motions, can also be set with ACCELERATE_DIRECTORY")
    .short("d")
    .long("directory")
    .value_name("PATH")
  );

  let driver_args = [
    Arg::with_name("database")
    .help("The connection string to connecting to your database, can also be set with ACCELERATE_DATABASE")
    .short("b")
    .long("database")
    .value_name("STRING"),
    Arg::with_name("driver_name")
    .help("The driver Accelerate will use to execute your motions, can also be set with ACCELERATE_DRIVER")
    .short("r")
    .long("driver")
    .value_name("NAME")
    .possible_values(&["postgres"])
  ];

  let matches = (
    App::new("Accelerate")
    .bin_name("accelerate")
    .version(VERSION)
    .author("Caleb Meredith <calebmeredith8@gmail.com>")
    .about("Accelerate your databases back and forth through easy to manage migration files")
    .settings(&[
      ArgRequiredElseHelp,
      GlobalVersion,
      VersionlessSubcommands,
      UnifiedHelpMessage,
      SubcommandRequiredElseHelp,
      AllowLeadingHyphen
    ])
    .subcommand(
      SubCommand::with_name("ls")
      .about("Lists all of your available motions")
      .arg(&directory_arg)
    )
    .subcommand(
      SubCommand::with_name("status")
      .about("Informs you about the status of all your motions in the database")
      .arg(&directory_arg)
      .args(&driver_args)
    )
    .subcommand(
      SubCommand::with_name("create")
      .about("Creates a new motion using your defined template")
      .arg(&directory_arg)
      .arg(
        Arg::with_name("name")
        .help("The name (with directory path) of the motion you want to create. A timestamp will automatically be added to the last segment")
        .required(true)
        .value_name("NAME")
      )
    )
    .subcommand(
      SubCommand::with_name("add")
      .about("Will add `n` motions to the driver")
      .arg(&directory_arg)
      .args(&driver_args)
      .arg(
        Arg::with_name("n")
        .help("The number of motions to add to the driver")
        .value_name("N")
        .default_value("1")
      )
    )
    .subcommand(
      SubCommand::with_name("sub")
      .about("Will sub `n` motions in the driver")
      .arg(&directory_arg)
      .args(&driver_args)
      .arg(
        Arg::with_name("n")
        .help("The number of motions to sub in the driver")
        .value_name("N")
        .default_value("1")
      )
    )
    .subcommand(
      SubCommand::with_name("up")
      .about("Will add all motions that have not yet been applied to the database")
      .arg(&directory_arg)
      .args(&driver_args)
    )
    .subcommand(
      SubCommand::with_name("down")
      .about("Will sub all motions that have been applied in the database")
      .arg(&directory_arg)
      .args(&driver_args)
    )
    .subcommand(
      SubCommand::with_name("redo")
      .about("Will sub and then add just the last motion")
      .arg(&directory_arg)
      .args(&driver_args)
    )
    .subcommand(
      SubCommand::with_name("reset")
      .about("Will sub all motions that have been applied in the database and then add all of the motions available")
      .arg(&directory_arg)
      .args(&driver_args)
    )
  ).get_matches();

  let subcommand_name = matches.subcommand_name().unwrap();
  let matches = matches.subcommand_matches(subcommand_name).unwrap();

  let directory_env = env::var("ACCELERATE_DIRECTORY").ok();
  let driver_name_env = env::var("ACCELERATE_DRIVER").ok();
  let database_env = env::var("ACCELERATE_DATABASE").ok();

  let motions = || {
    let directory = matches.value_of("directory").or(directory_env.as_ref().map(|s| s.as_str())).unwrap_or(".");
    motions::find(&Path::new(directory))
  };

  let driver = || {
    let driver_name = matches.value_of("driver_name").or(driver_name_env.as_ref().map(|s| s.as_str()));
    let database = matches.value_of("database").or(database_env.as_ref().map(|s| s.as_str()));
    let database = try!(database.ok_or(error!("A database connection string is required and none was found in either the command line arguments or the environment variable `ACCELERATE_DATABASE`.")));
    driver::get(driver_name, database)
  };

  let accelerator = || Accelerator::new(try!(driver()), try!(motions()));

  match subcommand_name {
    "ls" => {
      for motion in try!(motions()) {
        println!("{}", motion);
      }
    },
    "status" => {
      let driver = try!(driver());
      let records = try!(driver.get_records());
      for motion in try!(motions()) {
        if records.contains(&motion.name) {
          println!("{} {}", Green.bold().paint("✔"), motion);
        } else {
          println!("{} {}", Red.bold().paint("𝙭"), motion);
        }
      }
    },
    "create" => {
      unimplemented!();
    },
    "add" => {
      let mut accelerator = try!(accelerator());
      let n = try!(matches.value_of("n").unwrap_or("1").parse::<usize>());
      try!(accelerator.add(n));
    },
    "sub" => {
      let mut accelerator = try!(accelerator());
      let n = try!(matches.value_of("n").unwrap_or("1").parse::<usize>());
      try!(accelerator.sub(n));
    },
    "up" => {
      let mut accelerator = try!(accelerator());
      try!(accelerator.add(usize::max_value()));
    },
    "down" => {
      let mut accelerator = try!(accelerator());
      try!(accelerator.sub(usize::max_value()));
    },
    "redo" => {
      let mut accelerator = try!(accelerator());
      try!(accelerator.sub(1));
      try!(accelerator.add(1));
    },
    "reset" => {
      let mut accelerator = try!(accelerator());
      let applied = accelerator.applied_count();
      try!(accelerator.sub(usize::max_value()));
      try!(accelerator.add(applied));
    },
    _ => unreachable!(),
  }

  Ok(())
}
<|endoftext|>


<commit_before>#![allow(match_of_unit_variant_via_paren_dotdot)]

#![feature(plugin)]
#![plugin(plex)]

use std::io::Read;

mod lexer {
    use std::fmt::Display;
    use std::fmt::Formatter;
    use std::fmt;

    #[derive(PartialEq, Clone, Debug)]
    pub enum Token {
        Def,
        Extern,
        Delimiter, // ';' character
        OpeningParenthesis,
        ClosingParenthesis,
        Comma,
        Identifier(String),
        Number(f64),
        Operator(String),

        Whitespace,
        Comment,
    }

    // ► The Specials - Too Much Too Young

    lexer! {
        fn take_token(text: 'a) -> (Token, &'a str); // Token and the rest

        r#"[ \t\r\n]+"# => (Token::Whitespace, text),
        // "C-style" comments (/* .. */) - can't contain "*/"
        r#"/[*](~(.*[*]/.*))[*]/"# => (Token::Comment, text),
        // "C++-style" comments (// ...)
        r#"//[^\n]*"# => (Token::Comment, text),
        r#"def"# => (Token::Def, text),
        r#"extern"# => (Token::Extern, text),
        r#"[a-zA-Z_][a-zA-Z0-9_]*"# => (Token::Identifier(text.to_owned()), text),
        r#"[0-9.]+"# => {
            (if let Ok(i) = text.parse() {
                Token::Number(i)
            } else {
                panic!("float {} is out of range", text)
            }, text)
        },
        r#"\("# => (Token::OpeningParenthesis, text),
        r#"\)"# => (Token::ClosingParenthesis, text),
        r#";"# => (Token::Delimiter, text),
        r#","# => (Token::Comma, text),
        r#"[\+\-\*\/]+"# => (Token::Operator(text.to_owned()), text),
    }

    impl Token {
        pub fn contents(&self) -> Option<&str> {
            let s = match *self {
                Token::Identifier(ref s) => s,
                _ => return None,
            };
            Some(s)
        }
    }

    /// The Lexer
    #[derive(PartialEq, Clone, Debug, Copy)]
    pub struct Lexer<'a> {
        source: &'a str,
        remaining: &'a str,
    }

    impl<'a> Lexer<'a> {
        /// Create a new Lexer
        pub fn new(src: &'a str) -> Self {
            Lexer {
                source: src,
                remaining: src,
            }
        }
    }

    impl<'a> Iterator for Lexer<'a> {
        type Item = (Token, TextSpan);

        fn next(&mut self) -> Option<Self::Item> {
            loop {
                if let Some(token) = take_token(&mut self.remaining) {
                    if let (Token::Whitespace, _) = token {
                        continue;
                    } else {
                        let (token, span) = token;
                        let text_span = TextSpan::from(self.source, self.remaining, span);
                        return Some((token, text_span));
                    }
                } else {
                    return None;
                }
            }
        }
    }

    /// A structure for grouping byte offset of text spans.
    #[derive(Debug)]
    #[derive(Copy, Clone)]
    #[derive(Eq, PartialEq)]
    pub struct TextSpan {
        pub low: usize,
        pub high: usize,
    }

    impl TextSpan {
        /// Create a text span from a string and a slice of it.
        pub fn from(source: &str, remaining: &str, token: &str) -> TextSpan {
            let high = source.len() - remaining.len();
            let low = high - token.len();
            TextSpan {
                low: low,
                high: high,
            }
        }

        pub fn merge(a: TextSpan, b: TextSpan) -> TextSpan {
            let low = if a.low < b.low {
                a.low
            } else {
                b.low
            };
            let high = if a.high > b.high {
                a.high
            } else {
                b.high
            };
            TextSpan {
                low: low,
                high: high,
            }
        }
    }

    impl Display for TextSpan {
        fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
            write!(f, "[{}, {})", self.low, self.high)
        }
    }
}

mod ast {
    use std::collections::HashMap;

    use lexer::*;

    pub use self::ASTNode::{ExternNode, FunctionNode};
    pub use self::Expression::{LiteralExpr, VariableExpr, BinaryExpr, CallExpr};

    pub struct Program {
        pub astnodes: Vec<ASTNode>,
    }

    #[derive(PartialEq, Clone, Debug)]
    pub enum ASTNode {
        ExternNode(Prototype),
        FunctionNode(Function),
    }

    #[derive(PartialEq, Clone, Debug)]
    pub struct Function {
        pub prototype: Prototype,
        pub body: Expression,
    }

    #[derive(PartialEq, Clone, Debug)]
    pub struct Prototype {
        pub name: String,
        pub args: Vec<String>,
    }

    #[derive(PartialEq, Clone, Debug)]
    pub enum Expression {
        LiteralExpr(f64),
        VariableExpr(String),
        BinaryExpr(String, Box<Expression>, Box<Expression>),
        CallExpr(String, Vec<Expression>),
    }
}

mod parser {
    // Grammar:
    //
    // program : [[statement | expression] Delimiter ?]*;
    // statement : [declaration | definition];
    // declaration : Extern prototype;
    // definition : Def prototype expression;
    // prototype : Identifier OpeningParenthesis [Identifier Comma ?]* ClosingParenthesis;
    // expression : [primary_expr (Operator primary_expr)*];
    // primary_expr : [Identifier | Number | call_expr | parenthesis_expr];
    // call_expr : Identifier OpeningParenthesis [expression Comma ?]* ClosingParenthesis;
    // parenthesis_expr: OpeningParenthesis expression ClosingParenthesis;
    //

    use ast::*;
    use lexer::*;

    use lexer::Token::*;

    parser! {
        fn parse_(Token, TextSpan);

        // Ignore spans
        (a, b) {
            TextSpan::merge(a, b)
        }

        program: Vec<ASTNode> {
            => vec![],
            stex[e] rest[mut p] => {
                p.push(e);
                p
            },
        }

        stex: ASTNode {
            statement[e] => e,
            expression[e] => e
        }

        rest: Vec<ASTNode> {
            program[p] => {
                p
            },
            Delimiter program[p] => {
                p
            },
        }

        statement: ASTNode {
            declaration[e] => {
                e
            },
            definition[e] => {
                e
            }
        }

        declaration: ASTNode {
            Extern prototype[p] => {
                ExternNode(p)
            },
        }

        definition: ASTNode {
            Def prototype[p] expr[e] => {
                FunctionNode(Function {
                    prototype: p,
                    body: e,
                })
            }
        }

        prototype: Prototype {
            Identifier(id) OpeningParenthesis arguments[a] ClosingParenthesis => Prototype {
                name: id,
                args: a,
            }
        }

        arguments: Vec<String> {
            => vec![],
            Identifier(id) extra_args[mut a] => {
                a.push(id);
                a
            }
        }

        extra_args: Vec<String> {
            Comma arguments[a] => {
                a
            },
            arguments[a] => {
                a
            }
        }

        primary_expression: Expression {
           Number(i) => LiteralExpr(i)
        }

        expression: ASTNode {
            primary_expression[e] => {
                let prototype = Prototype {name: "".to_string(), args: vec![]};
                FunctionNode (Function {
                    prototype: prototype,
                    body: e
                })
            }
        }

        expr: Expression {
            => LiteralExpr(0.0)
        }
    }

    pub fn parse<I: Iterator<Item = (Token, TextSpan)>>
        (i: I)
         -> Result<Vec<ASTNode>, (Option<(Token, TextSpan)>, &'static str)> {
        parse_(i)
    }
}

fn main() {
    let mut s = String::new();
    std::io::stdin().read_to_string(&mut s).unwrap();
    let lexer = lexer::Lexer::new(&s);
    for i in lexer {
        println!("{:?}", i);
    }
    println!("{:?}", parser::parse(lexer));
}

#[test]
fn test_parser() {
    use parser::*;
    use ast::*;
    use lexer::*;
    let tests = vec![
        ("extern sin();", vec![ExternNode(Prototype{name:"sin".to_string(), args:vec![]})]),
        ("", vec![]),
        ("def foo(x y x);", vec![FunctionNode(Function { prototype: Prototype { name: "foo".to_string(), args: vec!["x".to_string(), "y".to_string(), "x".to_string()] }, body: LiteralExpr(0.0) })]),
    ];

    for tc in tests {
        let parsed = lexer::Lexer::new(tc.0);
        let r = parser::parse(parsed).unwrap();
        assert_eq!(tc.1, r);
    }
}
<commit_msg>parser: add new rules<commit_after>#![allow(match_of_unit_variant_via_paren_dotdot)]

#![feature(plugin)]
#![plugin(plex)]

use std::io::Read;

mod lexer {
    use std::fmt::Display;
    use std::fmt::Formatter;
    use std::fmt;

    #[derive(PartialEq, Clone, Debug)]
    pub enum Token {
        Def,
        Extern,
        Delimiter, // ';' character
        OpeningParenthesis,
        ClosingParenthesis,
        Comma,
        Identifier(String),
        Number(f64),
        Operator(String),

        Whitespace,
        Comment,
    }

    // ► The Specials - Too Much Too Young

    lexer! {
        fn take_token(text: 'a) -> (Token, &'a str); // Token and the rest

        r#"[ \t\r\n]+"# => (Token::Whitespace, text),
        // "C-style" comments (/* .. */) - can't contain "*/"
        r#"/[*](~(.*[*]/.*))[*]/"# => (Token::Comment, text),
        // "C++-style" comments (// ...)
        r#"//[^\n]*"# => (Token::Comment, text),
        r#"def"# => (Token::Def, text),
        r#"extern"# => (Token::Extern, text),
        r#"[a-zA-Z_][a-zA-Z0-9_]*"# => (Token::Identifier(text.to_owned()), text),
        r#"[0-9.]+"# => {
            (if let Ok(i) = text.parse() {
                Token::Number(i)
            } else {
                panic!("float {} is out of range", text)
            }, text)
        },
        r#"\("# => (Token::OpeningParenthesis, text),
        r#"\)"# => (Token::ClosingParenthesis, text),
        r#";"# => (Token::Delimiter, text),
        r#","# => (Token::Comma, text),
        r#"[\+\-\*\/]+"# => (Token::Operator(text.to_owned()), text),
    }

    impl Token {
        pub fn contents(&self) -> Option<&str> {
            let s = match *self {
                Token::Identifier(ref s) => s,
                _ => return None,
            };
            Some(s)
        }
    }

    /// The Lexer
    #[derive(PartialEq, Clone, Debug, Copy)]
    pub struct Lexer<'a> {
        source: &'a str,
        remaining: &'a str,
    }

    impl<'a> Lexer<'a> {
        /// Create a new Lexer
        pub fn new(src: &'a str) -> Self {
            Lexer {
                source: src,
                remaining: src,
            }
        }
    }

    impl<'a> Iterator for Lexer<'a> {
        type Item = (Token, TextSpan);

        fn next(&mut self) -> Option<Self::Item> {
            loop {
                if let Some(token) = take_token(&mut self.remaining) {
                    if let (Token::Whitespace, _) = token {
                        continue;
                    } else {
                        let (token, span) = token;
                        let text_span = TextSpan::from(self.source, self.remaining, span);
                        return Some((token, text_span));
                    }
                } else {
                    return None;
                }
            }
        }
    }

    /// A structure for grouping byte offset of text spans.
    #[derive(Debug)]
    #[derive(Copy, Clone)]
    #[derive(Eq, PartialEq)]
    pub struct TextSpan {
        pub low: usize,
        pub high: usize,
    }

    impl TextSpan {
        /// Create a text span from a string and a slice of it.
        pub fn from(source: &str, remaining: &str, token: &str) -> TextSpan {
            let high = source.len() - remaining.len();
            let low = high - token.len();
            TextSpan {
                low: low,
                high: high,
            }
        }

        pub fn merge(a: TextSpan, b: TextSpan) -> TextSpan {
            let low = if a.low < b.low {
                a.low
            } else {
                b.low
            };
            let high = if a.high > b.high {
                a.high
            } else {
                b.high
            };
            TextSpan {
                low: low,
                high: high,
            }
        }
    }

    impl Display for TextSpan {
        fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
            write!(f, "[{}, {})", self.low, self.high)
        }
    }
}

mod ast {
    use std::collections::HashMap;

    use lexer::*;

    pub use self::ASTNode::{ExternNode, FunctionNode};
    pub use self::Expression::{LiteralExpr, VariableExpr, BinaryExpr, CallExpr};

    pub struct Program {
        pub astnodes: Vec<ASTNode>,
    }

    #[derive(PartialEq, Clone, Debug)]
    pub enum ASTNode {
        ExternNode(Prototype),
        FunctionNode(Function),
    }

    #[derive(PartialEq, Clone, Debug)]
    pub struct Function {
        pub prototype: Prototype,
        pub body: Expression,
    }

    #[derive(PartialEq, Clone, Debug)]
    pub struct Prototype {
        pub name: String,
        pub args: Vec<String>,
    }

    #[derive(PartialEq, Clone, Debug)]
    pub enum Expression {
        LiteralExpr(f64),
        VariableExpr(String),
        BinaryExpr(String, Box<Expression>, Box<Expression>),
        CallExpr(String, Vec<Expression>),
    }
}

mod parser {
    // Grammar:
    //
    // program : [[statement | expression] Delimiter ?]*;
    // statement : [declaration | definition];
    // declaration : Extern prototype;
    // definition : Def prototype expression;
    // prototype : Identifier OpeningParenthesis [Identifier Comma ?]* ClosingParenthesis;
    // expression : [primary_expr (Operator primary_expr)*];
    // primary_expr : [Identifier | Number | call_expr | parenthesis_expr];
    // call_expr : Identifier OpeningParenthesis [expression Comma ?]* ClosingParenthesis;
    // parenthesis_expr: OpeningParenthesis expression ClosingParenthesis;
    //

    use ast::*;
    use lexer::*;

    use lexer::Token::*;

    parser! {
        fn parse_(Token, TextSpan);

        // Ignore spans
        (a, b) {
            TextSpan::merge(a, b)
        }

        program: Vec<ASTNode> {
            => vec![],
            stex[e] rest[mut p] => {
                p.push(e);
                p
            },
        }

        stex: ASTNode {
            statement[e] => e,
            expression[e] => e
        }

        rest: Vec<ASTNode> {
            program[p] => {
                p
            },
            Delimiter program[p] => {
                p
            },
        }

        statement: ASTNode {
            declaration[e] => {
                e
            },
            definition[e] => {
                e
            }
        }

        declaration: ASTNode {
            Extern prototype[p] => {
                ExternNode(p)
            },
        }

        definition: ASTNode {
            Def prototype[p] expr[e] => {
                FunctionNode(Function {
                    prototype: p,
                    body: e,
                })
            }
        }

        prototype: Prototype {
            Identifier(id) OpeningParenthesis arguments[a] ClosingParenthesis => Prototype {
                name: id,
                args: a,
            }
        }

        arguments: Vec<String> {
            => vec![],
            Identifier(id) extra_args[mut a] => {
                a.push(id);
                a
            }
        }

        extra_args: Vec<String> {
            Comma arguments[a] => {
                a
            },
            arguments[a] => {
                a
            }
        }

        expression: ASTNode {
            expr[e] => {
                let prototype = Prototype {name: "".to_string(), args: vec![]};
                FunctionNode (Function {
                    prototype: prototype,
                    body: e
                })
            }
        }

        primary_expression: Expression {
            #[no_reduce(OpeningParenthesis)]
            Identifier(id) => VariableExpr(id),
            Number(val) => LiteralExpr(val),
            Identifier(id) OpeningParenthesis many_exprs[args] ClosingParenthesis => {
                CallExpr(id, args)
            },
            parenthesis_expr[e] => e,
        }

        many_exprs: Vec<Expression> {
            => vec![],
            expr[e] extra_exprs[mut exprs] => {
                exprs.push(e);
                exprs
            }
        }
        extra_exprs: Vec<Expression> {
            Comma many_exprs[a] => {
                a
            },
            many_exprs[a] => {
                a
            }
        }

        parenthesis_expr: Expression {
            OpeningParenthesis expr[e] ClosingParenthesis => e
        }

        expr: Expression {
           primary_expression[e] binary_expr[st] => e
        }

        binary_expr: Expression {
            Operator(op) primary_expression[e] => e
        }
    }

    pub fn parse<I: Iterator<Item = (Token, TextSpan)>>
        (i: I)
         -> Result<Vec<ASTNode>, (Option<(Token, TextSpan)>, &'static str)> {
        parse_(i)
    }
}

fn main() {
    let mut s = String::new();
    std::io::stdin().read_to_string(&mut s).unwrap();
    let lexer = lexer::Lexer::new(&s);
    for i in lexer {
        println!("{:?}", i);
    }
    println!("{:?}", parser::parse(lexer));
}

#[test]
fn test_parser() {
    use parser::*;
    use ast::*;
    use lexer::*;
    let tests = vec![
        ("extern sin();", vec![ExternNode(Prototype{name:"sin".to_string(), args:vec![]})]),
        ("", vec![]),
        ("def foo(x y x);", vec![FunctionNode(Function { prototype: Prototype { name: "foo".to_string(), args: vec!["x".to_string(), "y".to_string(), "x".to_string()] }, body: LiteralExpr(0.0) })]),
    ];

    for tc in tests {
        let parsed = lexer::Lexer::new(tc.0);
        let r = parser::parse(parsed).unwrap();
        assert_eq!(tc.1, r);
    }
}
<|endoftext|>


<commit_before>extern crate docopt;
extern crate rustc_serialize;
extern crate gsdmm;

use gsdmm::GSDMM;
use docopt::Docopt;
use std::io::{BufRead,BufReader};
use std::fs::File;
use std::collections::HashSet;
use std::io::Write;

const USAGE: &'static str ="
Gibbs sampling algorithm for a Dirichlet Mixture Model of Yin and Wang 2014.

Usage:
  gsdmm <datafile> <vocabfile> <outprefix> [-k <max_clusters>] [-a <alpha>] [-b <beta>] [-m <maxit>]
  gsdmm (-h | --help)
  gsdmm --version

Options:
  -h --help             Show this screen.
  --version             Show version.
  -k=<K>                Upper bound on the number of possible clusters. [default: 8]
  -a --alpha=<alpha>    Alpha controls the probability that a student will join a table that is currently empty
                        When alpha is 0, no one will join an empty table. [default: 0.1]
  -b --beta=<beta>      Beta controls the student's affinity for other students with similar interests. A low beta means
                        that students desire to sit with students of similar interests. A high beta means they are less
                        concerned with affinity and are more influenced by the popularity of a table. [default: 0.1]
  -m --maxit=<m>        Maximum number of iterations. [default: 30]

";

#[derive(Debug, RustcDecodable)]
struct Args {
    //    flag_mode: isize,
    arg_datafile: String,
    arg_vocabfile: String,
    arg_outprefix: String,
    flag_k: usize,
    flag_alpha: f64,
    flag_beta: f64,
    flag_maxit: isize
}

fn main() {

    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());

    // get the data and vocabulary
    let vocab:HashSet<String> = lines_from_file(&args.arg_vocabfile).into_iter().collect();
    let docs:Vec<Vec<String>> = lines_from_file(&args.arg_datafile).into_iter().map(|line| {
        let mut term_vector = line.to_owned()
            .split_whitespace()
            .map(|s| s.to_owned())
            .filter(|s| (&vocab).contains(s))
            .collect::<Vec<String>>();

        // sort and dedupe: this implementation requires binary term counts
        term_vector.sort();
        term_vector.dedup();
        term_vector
    }).collect::<Vec<Vec<String>>>();

    let mut model = GSDMM::new(args.flag_alpha, args.flag_beta, args.flag_k, args.flag_maxit, vocab, docs);
    model.fit();

    // write the labels
    {
        let fname = (&args.arg_outprefix).clone() + "labels.csv";
        let error_msg = format ! ("Could not write file {}!", fname);
        let mut f = File::create( fname ).expect( & error_msg);
        let mut scored = Vec::<(String,String,String)>::new();

        // zip with the input data so we get clustered, raw input documents in the output set
        for (doc,txt) in (&model.docs).iter().zip(lines_from_file(&args.arg_datafile).iter()) {
            let p = model.score( & doc);
            let mut row = p.iter().enumerate().collect::<Vec<_>>();
            row.sort_by(|a,b| a.1.partial_cmp(b.1).unwrap());
            let line = row.pop().unwrap();
            scored.push((line.0.to_string(), line.1.to_string(), txt.clone()));
        }
        scored.sort();
        for (label, score, txt) in scored {
            f.write((label + "," + &score + "," + &txt + "\n").as_bytes());
        }
    }

    // write the cluster descriptions
    {
        let fname = (&args.arg_outprefix).clone() + "cluster_descriptions.txt";
        let error_msg = format!("Could not write file {}!", fname);
        let mut f = File::create(fname).expect(&error_msg);
        for k in 0..args.flag_k {
            let ref word_dist = model.cluster_word_distributions[k];
            let mut line = k.to_string() + " ";
            let mut dist_counts:Vec<String> = word_dist.iter().map(|(a,b)| a.clone() + ":" + &b.clone().to_string() ).collect();
            dist_counts.sort();
            line += &dist_counts.join(" ");
            f.write((line+"\n").as_bytes());
        }
    }

    fn lines_from_file(filename: &str) -> Vec<String>
    {
        let error_msg = format!("Could not read file {}!", filename);
        let file = File::open(filename).expect(&error_msg);
        let buf = BufReader::new(file);
        buf.lines().map(|l| l.expect("Could not parse line!")).collect()
    }
}
<commit_msg>Error handling for NaNs in scoring<commit_after>extern crate docopt;
extern crate rustc_serialize;
extern crate gsdmm;

use gsdmm::GSDMM;
use docopt::Docopt;
use std::io::{BufRead,BufReader};
use std::fs::File;
use std::collections::HashSet;
use std::io::Write;
use std::cmp::Ordering;

const USAGE: &'static str ="
Gibbs sampling algorithm for a Dirichlet Mixture Model of Yin and Wang 2014.

Usage:
  gsdmm <datafile> <vocabfile> <outprefix> [-k <max_clusters>] [-a <alpha>] [-b <beta>] [-m <maxit>]
  gsdmm (-h | --help)
  gsdmm --version

Options:
  -h --help             Show this screen.
  --version             Show version.
  -k=<K>                Upper bound on the number of possible clusters. [default: 8]
  -a --alpha=<alpha>    Alpha controls the probability that a student will join a table that is currently empty
                        When alpha is 0, no one will join an empty table. [default: 0.1]
  -b --beta=<beta>      Beta controls the student's affinity for other students with similar interests. A low beta means
                        that students desire to sit with students of similar interests. A high beta means they are less
                        concerned with affinity and are more influenced by the popularity of a table. [default: 0.1]
  -m --maxit=<m>        Maximum number of iterations. [default: 30]

";

#[derive(Debug, RustcDecodable)]
struct Args {
    //    flag_mode: isize,
    arg_datafile: String,
    arg_vocabfile: String,
    arg_outprefix: String,
    flag_k: usize,
    flag_alpha: f64,
    flag_beta: f64,
    flag_maxit: isize
}

fn main() {

    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());

    // get the data and vocabulary
    let vocab:HashSet<String> = lines_from_file(&args.arg_vocabfile).into_iter().collect();
    let docs:Vec<Vec<String>> = lines_from_file(&args.arg_datafile).into_iter().map(|line| {
        let mut term_vector = line.to_owned()
            .split_whitespace()
            .map(|s| s.to_owned())
            .filter(|s| (&vocab).contains(s))
            .collect::<Vec<String>>();

        // sort and dedupe: this implementation requires binary term counts
        term_vector.sort();
        term_vector.dedup();
        term_vector
    }).collect::<Vec<Vec<String>>>();

    let mut model = GSDMM::new(args.flag_alpha, args.flag_beta, args.flag_k, args.flag_maxit, vocab, docs);
    model.fit();

    // write the labels
    {
        let fname = (&args.arg_outprefix).clone() + "labels.csv";
        let error_msg = format ! ("Could not write file {}!", fname);
        let mut f = File::create( fname ).expect( & error_msg);
        let mut scored = Vec::<(String,String,String)>::new();

        // zip with the input data so we get clustered, raw input documents in the output set
        for (doc,txt) in (&model.docs).iter().zip(lines_from_file(&args.arg_datafile).iter()) {
            let p = model.score( & doc);
            let mut row = p.iter().enumerate().collect::<Vec<_>>();
            let mut defect_count = 0;
            row.sort_by(|a,b| a.1.partial_cmp(b.1).unwrap_or({
                if defect_count == 0 {
                    println!("Could not compare {:?} ? {:?} for document:\n {:?}", a, b, doc);
                    defect_count+=1;
                }
                match a.1.is_nan() {
                    true => Ordering::Less,
                    false => Ordering::Greater
                }
            }));
            let line = row.pop().unwrap();
            scored.push((line.0.to_string(), line.1.to_string(), txt.clone()));
        }
        scored.sort();
        for (label, score, txt) in scored {
            f.write((label + "," + &score + "," + &txt + "\n").as_bytes());
        }
    }

    // write the cluster descriptions
    {
        let fname = (&args.arg_outprefix).clone() + "cluster_descriptions.txt";
        let error_msg = format!("Could not write file {}!", fname);
        let mut f = File::create(fname).expect(&error_msg);
        for k in 0..args.flag_k {
            let ref word_dist = model.cluster_word_distributions[k];
            let mut line = k.to_string() + " ";
            let mut dist_counts:Vec<String> = word_dist.iter().map(|(a,b)| a.clone() + ":" + &b.clone().to_string() ).collect();
            dist_counts.sort();
            line += &dist_counts.join(" ");
            f.write((line+"\n").as_bytes());
        }
    }

    fn lines_from_file(filename: &str) -> Vec<String>
    {
        let error_msg = format!("Could not read file {}!", filename);
        let file = File::open(filename).expect(&error_msg);
        let buf = BufReader::new(file);
        buf.lines().map(|l| l.expect("Could not parse line!")).collect()
    }
}
<|endoftext|>


<commit_before>mod buf;
mod checkers;
mod format;
mod lexer;
mod nodes;
mod parser;
#[cfg(test)]
mod rename;
mod translator;
use md5;
#[cfg(test)]
use pretty_assertions::assert_eq;
use std::env;
use std::fs;
use std::io::BufRead;
use std::path::Path;
use std::process::{exit, Command, Stdio};
use std::string::String;

#[test]
fn test_rename() {
    let mut m = parser::get_module(&"json".to_string()).unwrap();
    let exports = checkers::get_exports(&m);
    let mut names: Vec<String> = Vec::new();
    for e in exports.consts {
        names.push(e.id);
    }
    for f in exports.fns {
        names.push(f.form.name);
    }
    for t in exports.types {
        names.push(t.form.alias);
    }
    println!("{:?}", names);
    rename::rename_mod(&mut m, &"kek".to_string(), &names);
    let c = translator::translate(&m);
    fs::write("out.c", format::format_compat_module(&c)).unwrap();
}

fn main() {
    let args: Vec<String> = env::args().collect();
    if args[1] == "build" {
        match build(&args[2..]) {
            Ok(_) => exit(0),
            Err(m) => {
                eprintln!("{}", m);
                exit(1);
            }
        }
    }
    if args[1] == "deptree" {
        exit(deptree(&args[2..]));
    }
    if args[1] == "exports" {
        exit(exports(&args[2..]));
    }
    exit(1);
}

#[test]
fn snapshots() {
    for entry in fs::read_dir("test/snapshots").unwrap() {
        let tmp = entry.unwrap().path();
        let path = tmp.to_str().unwrap();
        if path.ends_with(".out.c") {
            continue;
        }
        println!("{}", path);

        // get the output
        let m = parser::get_module(&path.to_string()).unwrap();
        let mods = resolve_deps(&m);
        let c_mods: Vec<nodes::CompatModule> =
            mods.iter().map(|m| translator::translate(&m)).collect();
        let result = format::format_compat_module(&c_mods[0]);

        let snapshot_path = path.replace(".c", ".out.c");
        if env::var("UPDATE_SNAPSHOT").is_ok() {
            fs::write(snapshot_path, result).unwrap();
        } else {
            assert_eq!(result, fs::read_to_string(snapshot_path).unwrap());
        }
    }
}

fn build(argv: &[String]) -> Result<(), String> {
    if argv.len() < 1 || argv.len() > 2 {
        return Err(String::from("Usage: build <source-path> [<output-path>]"));
    }
    let path = &argv[0];
    let name = if argv.len() == 2 {
        argv[1].clone()
    } else {
        Path::new(&path)
            .with_extension("")
            .file_name()
            .unwrap()
            .to_str()
            .unwrap()
            .to_string()
    };

    // Get the module we're building.
    let m = parser::get_module(&path)?;

    // Get all modules that our module depends on. This includes our module as
    // well, so this is a complete list of modules required for the build.
    let mods = resolve_deps(&m);

    // Dome some checks.
    for m in &mods {
        for imp in module_imports(m) {
            let dep = parser::get_module(&imp.path)?;
            if !checkers::depused(&m, &dep) {
                return Err(format!("{}: imported {} is not used", m.id, dep.id));
            }
        }
    }

    // Convert all modules to C modules.
    let c_mods: Vec<nodes::CompatModule> = mods.iter().map(|m| translator::translate(&m)).collect();

    // Save all C modules somewhere.
    let tmppath = format!("{}/tmp", parser::homepath());
    if fs::metadata(&tmppath).is_err() {
        fs::create_dir(&tmppath).unwrap();
    }
    struct PathId {
        path: String,
        id: String,
    }
    let mut paths: Vec<PathId> = Vec::new();
    for module in &c_mods {
        let src = format::format_compat_module(&module);
        let path = format!("{}/{:x}.c", tmppath, md5::compute(&module.id));
        paths.push(PathId {
            path: String::from(&path),
            id: String::from(&module.id),
        });
        fs::write(&path, &src).unwrap();
    }

    let mut link: Vec<String> = vec!["m".to_string()];
    for module in &c_mods {
        for l in &module.link {
            if link.iter().position(|x| x == l).is_none() {
                link.push(l.clone());
            }
        }
    }

    let mut cmd = Command::new("c99");
    cmd.args(&[
        "-Wall",
        "-Wextra",
        "-Werror",
        "-pedantic",
        "-pedantic-errors",
        "-fmax-errors=3",
        "-Wno-parentheses",
        "-g",
    ]);
    for p in &paths {
        cmd.arg(p.path.clone());
    }
    cmd.args(&["-o", &name]);
    for l in link {
        cmd.args(&["-l", &l.trim()]);
    }
    cmd.stderr(Stdio::piped());
    let mut proc = cmd.spawn().unwrap();
    let out = proc.stderr.as_mut().unwrap();
    let reader = std::io::BufReader::new(out);
    for liner in reader.lines() {
        let mut line = liner.unwrap();
        for p in &paths {
            line = line.replace(&p.path, &format!("{} -> {}", &p.id, &p.path));
        }
        println!("{}", &line);
    }
    let r = proc.wait().unwrap();
    exit(r.code().unwrap());
}

fn resolve_deps(m: &nodes::Module) -> Vec<nodes::Module> {
    let mut deps: Vec<nodes::Module> = vec![m.clone()];
    let mut present = vec![m.id.clone()];
    for imp in module_imports(&m) {
        let sub = parser::get_module(&imp.path).unwrap();
        for dep in resolve_deps(&sub) {
            if present.contains(&&dep.id) {
                continue;
            }
            present.push(dep.id.clone());
            deps.push(dep);
        }
    }
    return deps;
}

struct Import {
    path: String,
}

fn module_imports(module: &nodes::Module) -> Vec<Import> {
    let mut list: Vec<Import> = vec![];
    for element in &module.elements {
        match element {
            nodes::ModuleObject::Import { path } => list.push(Import { path: path.clone() }),
            _ => {}
        }
    }
    return list;
}

fn deptree(argv: &[String]) -> i32 {
    if argv.len() != 1 {
        eprintln!("usage: deptree <source-path>");
        return 1;
    }
    let path = &argv[0];
    let m = parser::get_module(&path).unwrap();
    let tree = build_tree(&m);
    let r = render_tree(&tree);
    println!("{}", r.join("\n"));
    return 0;
}

struct DepNode {
    name: String,
    deps: Vec<DepNode>,
}

fn build_tree(module: &nodes::Module) -> DepNode {
    let imports = module_imports(module);
    let mut deps = vec![];
    for import in imports {
        let m = parser::get_module(&import.path).unwrap();
        deps.push(build_tree(&m));
    }
    return DepNode {
        name: module.id.clone(),
        deps,
    };
}

fn render_tree(tree: &DepNode) -> Vec<String> {
    let mut lines: Vec<String> = vec![tree.name.clone()];
    let n = tree.deps.len();
    for (i, dep) in tree.deps.iter().enumerate() {
        let r = render_tree(&dep);
        if i == n - 1 {
            lines.append(&mut indent_tree(r, " └", "  "));
        } else {
            lines.append(&mut indent_tree(r, " ├", " │"));
        }
    }
    return lines;
}

fn indent_tree(lines: Vec<String>, first: &str, cont: &str) -> Vec<String> {
    let mut result: Vec<String> = Vec::new();
    for (i, line) in lines.iter().enumerate() {
        if i == 0 {
            result.push(format!("{}{}", first, line))
        } else {
            result.push(format!("{}{}", cont, line));
        }
    }
    return result;
}

fn exports(argv: &[String]) -> i32 {
    for path in argv {
        let m = parser::get_module(&path.to_string()).unwrap();
        println!("mod {}", m.id);
        let exports = checkers::get_exports(&m);
        if !exports.consts.is_empty() {
            println!("constants");
            for c in exports.consts {
                println!("\t{}", c.id);
            }
        }
        if !exports.types.is_empty() {
            println!("types");
            for c in exports.types {
                println!("\t{}", c.form.alias);
            }
        }
        if !exports.fns.is_empty() {
            println!("functions");
            for c in exports.fns {
                let mut params = String::from("(");
                for (i, parameter) in c.parameters.list.iter().enumerate() {
                    if i > 0 {
                        params += ", ";
                    }
                    params += &format::format_type(&parameter.type_name);
                    params += " ";
                    for form in &parameter.forms {
                        params += &format::format_form(&form);
                    }
                }
                if c.parameters.variadic {
                    params += ", ...";
                }
                params += ")";

                let form = format::format_form(&c.form);
                let mut s = String::new();
                s += &format!("{}\t{} {}", format::format_type(&c.type_name), form, params);
                println!("\t{}", s);
            }
        }
    }
    return 0;
}
<commit_msg>1 error is enough<commit_after>mod buf;
mod checkers;
mod format;
mod lexer;
mod nodes;
mod parser;
#[cfg(test)]
mod rename;
mod translator;
use md5;
#[cfg(test)]
use pretty_assertions::assert_eq;
use std::env;
use std::fs;
use std::io::BufRead;
use std::path::Path;
use std::process::{exit, Command, Stdio};
use std::string::String;

#[test]
fn test_rename() {
    let mut m = parser::get_module(&"json".to_string()).unwrap();
    let exports = checkers::get_exports(&m);
    let mut names: Vec<String> = Vec::new();
    for e in exports.consts {
        names.push(e.id);
    }
    for f in exports.fns {
        names.push(f.form.name);
    }
    for t in exports.types {
        names.push(t.form.alias);
    }
    println!("{:?}", names);
    rename::rename_mod(&mut m, &"kek".to_string(), &names);
    let c = translator::translate(&m);
    fs::write("out.c", format::format_compat_module(&c)).unwrap();
}

fn main() {
    let args: Vec<String> = env::args().collect();
    if args[1] == "build" {
        match build(&args[2..]) {
            Ok(_) => exit(0),
            Err(m) => {
                eprintln!("{}", m);
                exit(1);
            }
        }
    }
    if args[1] == "deptree" {
        exit(deptree(&args[2..]));
    }
    if args[1] == "exports" {
        exit(exports(&args[2..]));
    }
    exit(1);
}

#[test]
fn snapshots() {
    for entry in fs::read_dir("test/snapshots").unwrap() {
        let tmp = entry.unwrap().path();
        let path = tmp.to_str().unwrap();
        if path.ends_with(".out.c") {
            continue;
        }
        println!("{}", path);

        // get the output
        let m = parser::get_module(&path.to_string()).unwrap();
        let mods = resolve_deps(&m);
        let c_mods: Vec<nodes::CompatModule> =
            mods.iter().map(|m| translator::translate(&m)).collect();
        let result = format::format_compat_module(&c_mods[0]);

        let snapshot_path = path.replace(".c", ".out.c");
        if env::var("UPDATE_SNAPSHOT").is_ok() {
            fs::write(snapshot_path, result).unwrap();
        } else {
            assert_eq!(result, fs::read_to_string(snapshot_path).unwrap());
        }
    }
}

fn build(argv: &[String]) -> Result<(), String> {
    if argv.len() < 1 || argv.len() > 2 {
        return Err(String::from("Usage: build <source-path> [<output-path>]"));
    }
    let path = &argv[0];
    let name = if argv.len() == 2 {
        argv[1].clone()
    } else {
        Path::new(&path)
            .with_extension("")
            .file_name()
            .unwrap()
            .to_str()
            .unwrap()
            .to_string()
    };

    // Get the module we're building.
    let m = parser::get_module(&path)?;

    // Get all modules that our module depends on. This includes our module as
    // well, so this is a complete list of modules required for the build.
    let mods = resolve_deps(&m);

    // Dome some checks.
    for m in &mods {
        for imp in module_imports(m) {
            let dep = parser::get_module(&imp.path)?;
            if !checkers::depused(&m, &dep) {
                return Err(format!("{}: imported {} is not used", m.id, dep.id));
            }
        }
    }

    // Convert all modules to C modules.
    let c_mods: Vec<nodes::CompatModule> = mods.iter().map(|m| translator::translate(&m)).collect();

    // Save all C modules somewhere.
    let tmppath = format!("{}/tmp", parser::homepath());
    if fs::metadata(&tmppath).is_err() {
        fs::create_dir(&tmppath).unwrap();
    }
    struct PathId {
        path: String,
        id: String,
    }
    let mut paths: Vec<PathId> = Vec::new();
    for module in &c_mods {
        let src = format::format_compat_module(&module);
        let path = format!("{}/{:x}.c", tmppath, md5::compute(&module.id));
        paths.push(PathId {
            path: String::from(&path),
            id: String::from(&module.id),
        });
        fs::write(&path, &src).unwrap();
    }

    let mut link: Vec<String> = vec!["m".to_string()];
    for module in &c_mods {
        for l in &module.link {
            if link.iter().position(|x| x == l).is_none() {
                link.push(l.clone());
            }
        }
    }

    let mut cmd = Command::new("c99");
    cmd.args(&[
        "-Wall",
        "-Wextra",
        "-Werror",
        "-pedantic",
        "-pedantic-errors",
        "-fmax-errors=1",
        "-Wno-parentheses",
        "-g",
    ]);
    for p in &paths {
        cmd.arg(p.path.clone());
    }
    cmd.args(&["-o", &name]);
    for l in link {
        cmd.args(&["-l", &l.trim()]);
    }
    cmd.stderr(Stdio::piped());
    let mut proc = cmd.spawn().unwrap();
    let out = proc.stderr.as_mut().unwrap();
    let reader = std::io::BufReader::new(out);
    for liner in reader.lines() {
        let mut line = liner.unwrap();
        for p in &paths {
            line = line.replace(&p.path, &format!("{} -> {}", &p.id, &p.path));
        }
        println!("{}", &line);
    }
    let r = proc.wait().unwrap();
    exit(r.code().unwrap());
}

fn resolve_deps(m: &nodes::Module) -> Vec<nodes::Module> {
    let mut deps: Vec<nodes::Module> = vec![m.clone()];
    let mut present = vec![m.id.clone()];
    for imp in module_imports(&m) {
        let sub = parser::get_module(&imp.path).unwrap();
        for dep in resolve_deps(&sub) {
            if present.contains(&&dep.id) {
                continue;
            }
            present.push(dep.id.clone());
            deps.push(dep);
        }
    }
    return deps;
}

struct Import {
    path: String,
}

fn module_imports(module: &nodes::Module) -> Vec<Import> {
    let mut list: Vec<Import> = vec![];
    for element in &module.elements {
        match element {
            nodes::ModuleObject::Import { path } => list.push(Import { path: path.clone() }),
            _ => {}
        }
    }
    return list;
}

fn deptree(argv: &[String]) -> i32 {
    if argv.len() != 1 {
        eprintln!("usage: deptree <source-path>");
        return 1;
    }
    let path = &argv[0];
    let m = parser::get_module(&path).unwrap();
    let tree = build_tree(&m);
    let r = render_tree(&tree);
    println!("{}", r.join("\n"));
    return 0;
}

struct DepNode {
    name: String,
    deps: Vec<DepNode>,
}

fn build_tree(module: &nodes::Module) -> DepNode {
    let imports = module_imports(module);
    let mut deps = vec![];
    for import in imports {
        let m = parser::get_module(&import.path).unwrap();
        deps.push(build_tree(&m));
    }
    return DepNode {
        name: module.id.clone(),
        deps,
    };
}

fn render_tree(tree: &DepNode) -> Vec<String> {
    let mut lines: Vec<String> = vec![tree.name.clone()];
    let n = tree.deps.len();
    for (i, dep) in tree.deps.iter().enumerate() {
        let r = render_tree(&dep);
        if i == n - 1 {
            lines.append(&mut indent_tree(r, " └", "  "));
        } else {
            lines.append(&mut indent_tree(r, " ├", " │"));
        }
    }
    return lines;
}

fn indent_tree(lines: Vec<String>, first: &str, cont: &str) -> Vec<String> {
    let mut result: Vec<String> = Vec::new();
    for (i, line) in lines.iter().enumerate() {
        if i == 0 {
            result.push(format!("{}{}", first, line))
        } else {
            result.push(format!("{}{}", cont, line));
        }
    }
    return result;
}

fn exports(argv: &[String]) -> i32 {
    for path in argv {
        let m = parser::get_module(&path.to_string()).unwrap();
        println!("mod {}", m.id);
        let exports = checkers::get_exports(&m);
        if !exports.consts.is_empty() {
            println!("constants");
            for c in exports.consts {
                println!("\t{}", c.id);
            }
        }
        if !exports.types.is_empty() {
            println!("types");
            for c in exports.types {
                println!("\t{}", c.form.alias);
            }
        }
        if !exports.fns.is_empty() {
            println!("functions");
            for c in exports.fns {
                let mut params = String::from("(");
                for (i, parameter) in c.parameters.list.iter().enumerate() {
                    if i > 0 {
                        params += ", ";
                    }
                    params += &format::format_type(&parameter.type_name);
                    params += " ";
                    for form in &parameter.forms {
                        params += &format::format_form(&form);
                    }
                }
                if c.parameters.variadic {
                    params += ", ...";
                }
                params += ")";

                let form = format::format_form(&c.form);
                let mut s = String::new();
                s += &format!("{}\t{} {}", format::format_type(&c.type_name), form, params);
                println!("\t{}", s);
            }
        }
    }
    return 0;
}
<|endoftext|>


<commit_before>fn main() {
}
<commit_msg>Add obtaining of program arguments.<commit_after>///
/// Should program usage be printed?
///
/// # Arguments
///
/// * `args` - Program arguments, excluding program name.
///
pub fn should_print_usage(args: &Vec<String>) -> bool {
    args.len() != 1 || args[0] == "-h" || args[0] == "--help"
}

///
/// Prints program usage to the standard output.
///
pub fn print_usage() {
    println!("usage: yabir PROG");
}

fn main() {
    let args: Vec<_> = std::env::args().skip(1).collect();
    if should_print_usage(&args) {
        print_usage();
        std::process::exit(0);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_should_print_usage_returns_true_on_no_arguments() {
        assert!(should_print_usage(&vec![]));
    }

    #[test]
    fn test_should_print_usage_returns_true_on_help_short_form() {
        assert!(should_print_usage(&vec!["-h".to_string()]));
    }

    #[test]
    fn test_should_print_usage_returns_true_on_help_long_form() {
        assert!(should_print_usage(&vec!["--help".to_string()]));
    }

    #[test]
    fn test_should_print_usage_returns_false_when_prog_is_given() {
        assert!(!should_print_usage(&vec!["prog.bf".to_string()]));
    }
}
<|endoftext|>


<commit_before>
#![feature(macro_rules)]

use std::collections::HashMap;
use std::comm::{Receiver, Sender};
use std::io::{Acceptor, BufferedReader, BufWriter, Listener, MemWriter, TcpListener, TcpStream};
use std::io::stdin;
use std::rand::random;

type Id = u32;
type NullResult<T> = Result<T, ()>;
type Packet = Vec<u8>;

enum Message {
    RemovePlayer(Id),
    SendPacket(Id, Packet),
    AddPlayer(TcpStream),
}

struct Player {
    id: Id,
    tcp: TcpStream,
    name: String,
    send: Sender<Packet>,
}

struct Server {
    players: HashMap<Id, Player>,
    send: Sender<Message>,
    recv: Receiver<Message>,
}

macro_rules! try(
    ($e:expr) => (match $e { Ok(e) => e, Err(_) => return Err(()) })
)

fn main() {
    println!("NoLifePony Server");
    let mut server = Server::new();
    server.run();
    println!("Shutting down");
}

fn new_packet() -> NullResult<MemWriter> {
    let mut buf = MemWriter::new();
    try!(buf.write([0, 0, 0, 0]));
    Ok(buf)
}

fn packet_sender(recv: Receiver<Packet>, mut tcp: TcpStream) {
    for mut msg in recv.iter() {
        println!("Got packet to send!");
        let len = msg.len() as u32 - 4;
        {
            let mut buf = BufWriter::new(msg.mut_slice_to(4));
            buf.write_be_u32(len).unwrap();
        }
        let msg = msg;
        tcp.write(msg.as_slice()).unwrap();
    }
}

fn packet_receiver(send: Sender<Message>, tcp: TcpStream, id: Id) {
    struct Packets {
        buf: BufferedReader<TcpStream>,
    }
    impl Iterator<Packet> for Packets {
        fn next(&mut self) -> Option<Packet> {
            self.buf.read_be_u32().and_then(|len| self.buf.read_exact(len as uint)).ok()
        }
    }
    let mut packets = Packets {
        buf: BufferedReader::new(tcp),
    };
    for packet in packets {
        println!("Got packet!");
        send.send(SendPacket(id, packet));
    }
    send.send(RemovePlayer(id));
}

fn listener(send: Sender<Message>) {
    let listen = TcpListener::bind("0.0.0.0", 273);
    let mut accept = listen.listen();
    for stream in accept.incoming() {
        match stream {
            Ok(stream) => send.send(AddPlayer(stream)),
            Err(_) => return,
        }
    }
}

fn commands(_: Sender<Message>) -> NullResult<()> {
    let mut cin = stdin();
    for line in cin.lines() {
        let line = try!(line);
        let line: String = line.as_slice().chars().map(|c| c.to_lowercase())
            .filter(|c| *c != '\n' && *c != '\r').collect();
        match line.as_slice() {
            _ => println!("Unknown command"),
        }
    }
    Ok(())
}

impl Player {
    fn new(id: Id, tcp: TcpStream, send: Sender<Message>) -> Player {
        let tcp2 = tcp.clone();
        spawn(proc() packet_receiver(send, tcp2, id));
        let (send, recv) = channel::<Packet>();
        let tcp2 = tcp.clone();
        spawn(proc() packet_sender(recv, tcp2));
        Player {
            id: id,
            tcp: tcp,
            name: String::new(),
            send: send,
        }
    }
}

impl Server {
    fn new() -> Server {
        let (send, recv) = channel::<Message>();
        Server {
            players: HashMap::new(),
            send: send,
            recv: recv,
        }
    }
    fn gen_id(&self) -> Id {
        loop {
            let id = random();
            if self.players.find(&id).is_none() { return id }
        }
    }
    fn add_player(&mut self, mut tcp: TcpStream) {
        match tcp.peer_name() {
            Ok(name) => println!("Player connected from {}", name),
            Err(_) => println!("Failed to get player peer name"),
        }
        let player = Player::new(self.gen_id(), tcp, self.send.clone());
        self.players.insert(player.id, player);
    }
    fn run(&mut self) {
        let send = self.send.clone();
        spawn(proc() listener(send));
        let send = self.send.clone();
        spawn(proc() commands(send).unwrap());
        loop {
            let message = self.recv.recv();
            match message {
                AddPlayer(tcp) => self.add_player(tcp),
                _ => println!("Not handled yet"),
            }
        }
    }
}
<commit_msg>Now parses opcodes!<commit_after>
#![feature(macro_rules)]

use std::collections::HashMap;
use std::comm::{Receiver, Sender};
use std::io::{Acceptor, BufferedReader, BufWriter, Listener, MemReader, MemWriter, TcpListener, TcpStream};
use std::io::stdin;
use std::num::ToStrRadix;
use std::rand::random;

type Id = u32;
type NullResult<T> = Result<T, ()>;
type Packet = Vec<u8>;

enum Message {
    RemovePlayer(Id),
    HandlePacket(Id, Packet),
    AddPlayer(TcpStream),
}

struct Player {
    id: Id,
    tcp: TcpStream,
    name: String,
    send: Sender<Packet>,
}

struct Server {
    players: HashMap<Id, Player>,
    send: Sender<Message>,
    recv: Receiver<Message>,
}

fn main() {
    println!("NoLifePony Server");
    let mut server = Server::new();
    server.run();
    println!("Shutting down");
}

fn new_packet() -> MemWriter {
    let mut buf = MemWriter::new();
    buf.write([0, 0, 0, 0]).unwrap();
    buf
}

fn packet_sender(recv: Receiver<Packet>, mut tcp: TcpStream) {
    for mut msg in recv.iter() {
        println!("Got packet to send!");
        let len = msg.len() as u32 - 4;
        {
            let mut buf = BufWriter::new(msg.mut_slice_to(4));
            buf.write_be_u32(len).unwrap();
        }
        let msg = msg;
        tcp.write(msg.as_slice()).unwrap();
    }
}

fn packet_receiver(send: Sender<Message>, tcp: TcpStream, id: Id) {
    struct Packets {
        buf: BufferedReader<TcpStream>,
    }
    impl Iterator<Packet> for Packets {
        fn next(&mut self) -> Option<Packet> {
            self.buf.read_be_u32().and_then(|len| self.buf.read_exact(len as uint)).ok()
        }
    }
    let mut packets = Packets {
        buf: BufferedReader::new(tcp),
    };
    for packet in packets {
        println!("Got packet!");
        send.send(HandlePacket(id, packet));
    }
    send.send(RemovePlayer(id));
}

fn listener(send: Sender<Message>) {
    let listen = TcpListener::bind("0.0.0.0", 273);
    let mut accept = listen.listen();
    for stream in accept.incoming() {
        match stream {
            Ok(stream) => send.send(AddPlayer(stream)),
            Err(_) => return,
        }
    }
}

fn commands(_: Sender<Message>) {
    let mut cin = stdin();
    for line in cin.lines() {
        let line = line.unwrap();
        let line: String = line.as_slice().chars().map(|c| c.to_lowercase())
            .filter(|c| *c != '\n' && *c != '\r').collect();
        match line.as_slice() {
            _ => println!("Unknown command"),
        }
    }
}

impl Player {
    fn default_name(id: Id) -> String {
        "Pony".to_string().append(id.to_str_radix(10).as_slice())
    }
    fn new(id: Id, tcp: TcpStream, send: Sender<Message>) -> Player {
        let tcp2 = tcp.clone();
        spawn(proc() packet_receiver(send, tcp2, id));
        let (send, recv) = channel::<Packet>();
        let tcp2 = tcp.clone();
        spawn(proc() packet_sender(recv, tcp2));
        Player {
            id: id,
            tcp: tcp,
            name: Player::default_name(id),
            send: send,
        }
    }
}

impl Server {
    fn new() -> Server {
        let (send, recv) = channel::<Message>();
        let send2 = send.clone();
        spawn(proc() listener(send2));
        let send2 = send.clone();
        spawn(proc() commands(send2));
        Server {
            players: HashMap::new(),
            send: send,
            recv: recv,
        }
    }
    fn gen_id(&self) -> Id {
        loop {
            let id = random();
            if self.players.find(&id).is_none() { return id }
        }
    }
    fn add_player(&mut self, mut tcp: TcpStream) {
        match tcp.peer_name() {
            Ok(name) => println!("Player connected from {}", name),
            Err(_) => println!("Failed to get player peer name"),
        }
        let id = self.gen_id();
        let player = Player::new(id, tcp, self.send.clone());
        self.players.insert(player.id, player);
    }
    fn remove_player(&mut self, id: Id) {
        let mut player = match self.players.pop(&id) {
            Some(player) => player,
            None => return,
        };
        println!("Removing player {}", id);
        player.tcp.close_read().unwrap();
        player.tcp.close_write().unwrap();
    }
    fn handle_packet(&mut self, id: Id, packet: Packet) {
        let player = match self.players.find_mut(&id) {
            Some(player) => player,
            None => { println!("Packet from non-existant player: {}", id); return },
        };
        let mut packet = MemReader::new(packet);
        let opcode = match packet.read_be_u16() {
            Ok(opcode) => opcode,
            Err(_) => { println!("Failed to read opcode from player: {}", id); return },
        };
        match opcode {
            _ => println!("Unknown opcode {}", opcode),
        }
    }
    fn run(&mut self) {
        loop {
            let message = self.recv.recv();
            match message {
                AddPlayer(tcp) => self.add_player(tcp),
                RemovePlayer(id) => self.remove_player(id),
                HandlePacket(id, packet) => self.handle_packet(id, packet),
            }
        }
    }
}
<|endoftext|>