<commit_before>use super::error::{Error, Result};
use super::util;
use ansi_term::Colour;
use difference::{Changeset, Difference};
use lazy_static::lazy_static;
use regex::bytes::{Regex as ByteRegex, RegexBuilder as ByteRegexBuilder};
use regex::Regex;
use std::char;
use std::fs;
use std::io::{self, Write};
use std::path::Path;
use std::str;
use tree_sitter::{Language, LogType, Parser};

lazy_static! {
    static ref HEADER_REGEX: ByteRegex = ByteRegexBuilder::new(r"^===+\r?\n([^=]*)\r?\n===+\r?\n")
        .multi_line(true)
        .build()
        .unwrap();
    static ref DIVIDER_REGEX: ByteRegex = ByteRegexBuilder::new(r"\r?\n---+\r?\n")
        .multi_line(true)
        .build()
        .unwrap();
    static ref WHITESPACE_REGEX: Regex = Regex::new(r"\s+").unwrap();
    static ref SEXP_FIELD_REGEX: Regex = Regex::new(r" \w+: \(").unwrap();
}

#[derive(Debug, PartialEq, Eq)]
pub enum TestEntry {
    Group {
        name: String,
        children: Vec<TestEntry>,
    },
    Example {
        name: String,
        input: Vec<u8>,
        output: String,
        has_fields: bool,
    },
}

impl Default for TestEntry {
    fn default() -> Self {
        TestEntry::Group {
            name: String::new(),
            children: Vec::new(),
        }
    }
}

pub fn run_tests_at_path(
    language: Language,
    path: &Path,
    debug: bool,
    debug_graph: bool,
    filter: Option<&str>,
) -> Result<()> {
    let test_entry = parse_tests(path)?;
    let mut _log_session = None;
    let mut parser = Parser::new();
    parser.set_language(language).map_err(|e| e.to_string())?;

    if debug_graph {
        _log_session = Some(util::log_graphs(&mut parser, "log.html")?);
    } else if debug {
        parser.set_logger(Some(Box::new(|log_type, message| {
            if log_type == LogType::Lex {
                io::stderr().write(b"  ").unwrap();
            }
            write!(&mut io::stderr(), "{}\n", message).unwrap();
        })));
    }

    let mut failures = Vec::new();
    if let TestEntry::Group { children, .. } = test_entry {
        for child in children {
            run_tests(&mut parser, child, filter, 0, &mut failures)?;
        }
    }

    if failures.len() > 0 {
        println!("");

        if failures.len() == 1 {
            println!("1 failure:")
        } else {
            println!("{} failures:", failures.len())
        }

        print_diff_key();
        for (i, (name, actual, expected)) in failures.iter().enumerate() {
            println!("\n  {}. {}:", i + 1, name);
            print_diff(actual, expected);
        }
        Error::err(String::new())
    } else {
        Ok(())
    }
}

pub fn print_diff_key() {
    println!(
        "\n{} / {}",
        Colour::Green.paint("expected"),
        Colour::Red.paint("actual")
    );
}

pub fn print_diff(actual: &String, expected: &String) {
    let changeset = Changeset::new(actual, expected, " ");
    print!("    ");
    for diff in &changeset.diffs {
        match diff {
            Difference::Same(part) => {
                print!("{}{}", part, changeset.split);
            }
            Difference::Add(part) => {
                print!("{}{}", Colour::Green.paint(part), changeset.split);
            }
            Difference::Rem(part) => {
                print!("{}{}", Colour::Red.paint(part), changeset.split);
            }
        }
    }
    println!("");
}

fn run_tests(
    parser: &mut Parser,
    test_entry: TestEntry,
    filter: Option<&str>,
    mut indent_level: i32,
    failures: &mut Vec<(String, String, String)>,
) -> Result<()> {
    match test_entry {
        TestEntry::Example {
            name,
            input,
            output,
            has_fields,
        } => {
            if let Some(filter) = filter {
                if !name.contains(filter) {
                    return Ok(());
                }
            }
            let tree = parser.parse(&input, None).unwrap();
            let mut actual = tree.root_node().to_sexp();
            if !has_fields {
                actual = strip_sexp_fields(actual);
            }
            for _ in 0..indent_level {
                print!("  ");
            }
            if actual == output {
                println!("✓ {}", Colour::Green.paint(&name));
            } else {
                println!("✗ {}", Colour::Red.paint(&name));
                failures.push((name, actual, output));
            }
        }
        TestEntry::Group { name, children } => {
            for _ in 0..indent_level {
                print!("  ");
            }
            println!("{}:", name);
            indent_level += 1;
            for child in children {
                run_tests(parser, child, filter, indent_level, failures)?;
            }
        }
    }
    Ok(())
}

pub fn parse_tests(path: &Path) -> io::Result<TestEntry> {
    let name = path
        .file_stem()
        .and_then(|s| s.to_str())
        .unwrap_or("")
        .to_string();
    if path.is_dir() {
        let mut children = Vec::new();
        for entry in fs::read_dir(path)? {
            let entry = entry?;
            let hidden = entry
                .file_name()
                .to_str()
                .unwrap_or("")
                .starts_with(".");
            if !hidden {
                children.push(parse_tests(&entry.path())?);
            }
        }
        Ok(TestEntry::Group { name, children })
    } else {
        let content = fs::read_to_string(path)?;
        Ok(parse_test_content(name, content))
    }
}

pub fn strip_sexp_fields(sexp: String) -> String {
    SEXP_FIELD_REGEX.replace_all(&sexp, " (").to_string()
}

fn parse_test_content(name: String, content: String) -> TestEntry {
    let mut children = Vec::new();
    let bytes = content.as_bytes();
    let mut previous_name = String::new();
    let mut previous_header_end = 0;
    for header_match in HEADER_REGEX
        .find_iter(&bytes)
        .map(|m| (m.start(), m.end()))
        .chain(Some((bytes.len(), bytes.len())))
    {
        let (header_start, header_end) = header_match;
        if previous_header_end > 0 {
            if let Some(divider_match) =
                DIVIDER_REGEX.find(&bytes[previous_header_end..header_start])
            {
                let (divider_start, divider_end) = (
                    previous_header_end + divider_match.start(),
                    previous_header_end + divider_match.end(),
                );
                if let Ok(output) = str::from_utf8(&bytes[divider_end..header_start]) {
                    let input = bytes[previous_header_end..divider_start].to_vec();
                    let output = WHITESPACE_REGEX.replace_all(output.trim(), " ").to_string();
                    let output = output.replace(" )", ")");
                    let has_fields = SEXP_FIELD_REGEX.is_match(&output);
                    children.push(TestEntry::Example {
                        name: previous_name,
                        input,
                        output,
                        has_fields,
                    });
                }
            }
        }
        previous_name = String::from_utf8_lossy(&bytes[header_start..header_end])
            .trim_matches(|c| char::is_whitespace(c) || c == '=')
            .to_string();
        previous_header_end = header_end;
    }
    TestEntry::Group { name, children }
}

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

    #[test]
    fn test_parse_test_content() {
        let entry = parse_test_content(
            "the-filename".to_string(),
            r#"
===============
The first test
===============

a b c

---

(a
    (b c))

================
The second test
================
d
---
(d)
        "#
            .trim()
            .to_string(),
        );

        assert_eq!(
            entry,
            TestEntry::Group {
                name: "the-filename".to_string(),
                children: vec![
                    TestEntry::Example {
                        name: "The first test".to_string(),
                        input: "\na b c\n".as_bytes().to_vec(),
                        output: "(a (b c))".to_string(),
                        has_fields: false,
                    },
                    TestEntry::Example {
                        name: "The second test".to_string(),
                        input: "d".as_bytes().to_vec(),
                        output: "(d)".to_string(),
                        has_fields: false,
                    },
                ]
            }
        );
    }
}
<commit_msg>In test command, use the longest line of dashes as the test divider<commit_after>use super::error::{Error, Result};
use super::util;
use ansi_term::Colour;
use difference::{Changeset, Difference};
use lazy_static::lazy_static;
use regex::bytes::{Regex as ByteRegex, RegexBuilder as ByteRegexBuilder};
use regex::Regex;
use std::char;
use std::fs;
use std::io::{self, Write};
use std::path::Path;
use std::str;
use tree_sitter::{Language, LogType, Parser};

lazy_static! {
    static ref HEADER_REGEX: ByteRegex = ByteRegexBuilder::new(r"^===+\r?\n([^=]*)\r?\n===+\r?\n")
        .multi_line(true)
        .build()
        .unwrap();
    static ref DIVIDER_REGEX: ByteRegex = ByteRegexBuilder::new(r"^---+\r?\n")
        .multi_line(true)
        .build()
        .unwrap();
    static ref WHITESPACE_REGEX: Regex = Regex::new(r"\s+").unwrap();
    static ref SEXP_FIELD_REGEX: Regex = Regex::new(r" \w+: \(").unwrap();
}

#[derive(Debug, PartialEq, Eq)]
pub enum TestEntry {
    Group {
        name: String,
        children: Vec<TestEntry>,
    },
    Example {
        name: String,
        input: Vec<u8>,
        output: String,
        has_fields: bool,
    },
}

impl Default for TestEntry {
    fn default() -> Self {
        TestEntry::Group {
            name: String::new(),
            children: Vec::new(),
        }
    }
}

pub fn run_tests_at_path(
    language: Language,
    path: &Path,
    debug: bool,
    debug_graph: bool,
    filter: Option<&str>,
) -> Result<()> {
    let test_entry = parse_tests(path)?;
    let mut _log_session = None;
    let mut parser = Parser::new();
    parser.set_language(language).map_err(|e| e.to_string())?;

    if debug_graph {
        _log_session = Some(util::log_graphs(&mut parser, "log.html")?);
    } else if debug {
        parser.set_logger(Some(Box::new(|log_type, message| {
            if log_type == LogType::Lex {
                io::stderr().write(b"  ").unwrap();
            }
            write!(&mut io::stderr(), "{}\n", message).unwrap();
        })));
    }

    let mut failures = Vec::new();
    if let TestEntry::Group { children, .. } = test_entry {
        for child in children {
            run_tests(&mut parser, child, filter, 0, &mut failures)?;
        }
    }

    if failures.len() > 0 {
        println!("");

        if failures.len() == 1 {
            println!("1 failure:")
        } else {
            println!("{} failures:", failures.len())
        }

        print_diff_key();
        for (i, (name, actual, expected)) in failures.iter().enumerate() {
            println!("\n  {}. {}:", i + 1, name);
            print_diff(actual, expected);
        }
        Error::err(String::new())
    } else {
        Ok(())
    }
}

pub fn print_diff_key() {
    println!(
        "\n{} / {}",
        Colour::Green.paint("expected"),
        Colour::Red.paint("actual")
    );
}

pub fn print_diff(actual: &String, expected: &String) {
    let changeset = Changeset::new(actual, expected, " ");
    print!("    ");
    for diff in &changeset.diffs {
        match diff {
            Difference::Same(part) => {
                print!("{}{}", part, changeset.split);
            }
            Difference::Add(part) => {
                print!("{}{}", Colour::Green.paint(part), changeset.split);
            }
            Difference::Rem(part) => {
                print!("{}{}", Colour::Red.paint(part), changeset.split);
            }
        }
    }
    println!("");
}

fn run_tests(
    parser: &mut Parser,
    test_entry: TestEntry,
    filter: Option<&str>,
    mut indent_level: i32,
    failures: &mut Vec<(String, String, String)>,
) -> Result<()> {
    match test_entry {
        TestEntry::Example {
            name,
            input,
            output,
            has_fields,
        } => {
            if let Some(filter) = filter {
                if !name.contains(filter) {
                    return Ok(());
                }
            }
            let tree = parser.parse(&input, None).unwrap();
            let mut actual = tree.root_node().to_sexp();
            if !has_fields {
                actual = strip_sexp_fields(actual);
            }
            for _ in 0..indent_level {
                print!("  ");
            }
            if actual == output {
                println!("✓ {}", Colour::Green.paint(&name));
            } else {
                println!("✗ {}", Colour::Red.paint(&name));
                failures.push((name, actual, output));
            }
        }
        TestEntry::Group { name, children } => {
            for _ in 0..indent_level {
                print!("  ");
            }
            println!("{}:", name);
            indent_level += 1;
            for child in children {
                run_tests(parser, child, filter, indent_level, failures)?;
            }
        }
    }
    Ok(())
}

pub fn parse_tests(path: &Path) -> io::Result<TestEntry> {
    let name = path
        .file_stem()
        .and_then(|s| s.to_str())
        .unwrap_or("")
        .to_string();
    if path.is_dir() {
        let mut children = Vec::new();
        for entry in fs::read_dir(path)? {
            let entry = entry?;
            let hidden = entry.file_name().to_str().unwrap_or("").starts_with(".");
            if !hidden {
                children.push(parse_tests(&entry.path())?);
            }
        }
        Ok(TestEntry::Group { name, children })
    } else {
        let content = fs::read_to_string(path)?;
        Ok(parse_test_content(name, content))
    }
}

pub fn strip_sexp_fields(sexp: String) -> String {
    SEXP_FIELD_REGEX.replace_all(&sexp, " (").to_string()
}

fn parse_test_content(name: String, content: String) -> TestEntry {
    let mut children = Vec::new();
    let bytes = content.as_bytes();
    let mut prev_name = String::new();
    let mut prev_header_end = 0;

    // Identify all of the test descriptions using the `======` headers.
    for (header_start, header_end) in HEADER_REGEX
        .find_iter(&bytes)
        .map(|m| (m.start(), m.end()))
        .chain(Some((bytes.len(), bytes.len())))
    {
        // Find the longest line of dashes following each test description.
        // That is the divider between input and expected output.
        if prev_header_end > 0 {
            let divider_match = DIVIDER_REGEX
                .find_iter(&bytes[prev_header_end..header_start])
                .map(|m| (prev_header_end + m.start(), prev_header_end + m.end()))
                .max_by_key(|(start, end)| end - start);
            if let Some((divider_start, divider_end)) = divider_match {
                if let Ok(output) = str::from_utf8(&bytes[divider_end..header_start]) {
                    let mut input = bytes[prev_header_end..divider_start].to_vec();

                    // Remove trailing newline from the input.
                    input.pop();
                    if input.last() == Some(&b'\r') {
                        input.pop();
                    }

                    // Normalize the whitespace in the expected output.
                    let output = WHITESPACE_REGEX.replace_all(output.trim(), " ").to_string();
                    let output = output.replace(" )", ")");

                    // Identify if the expected output has fields indicated. If not, then
                    // fields will not be checked.
                    let has_fields = SEXP_FIELD_REGEX.is_match(&output);

                    children.push(TestEntry::Example {
                        name: prev_name,
                        input,
                        output,
                        has_fields,
                    });
                }
            }
        }
        prev_name = String::from_utf8_lossy(&bytes[header_start..header_end])
            .trim_matches(|c| char::is_whitespace(c) || c == '=')
            .to_string();
        prev_header_end = header_end;
    }
    TestEntry::Group { name, children }
}

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

    #[test]
    fn test_parse_test_content() {
        let entry = parse_test_content(
            "the-filename".to_string(),
            r#"
===============
The first test
===============

a b c

---

(a
    (b c))

================
The second test
================
d
---
(d)
        "#
            .trim()
            .to_string(),
        );

        assert_eq!(
            entry,
            TestEntry::Group {
                name: "the-filename".to_string(),
                children: vec![
                    TestEntry::Example {
                        name: "The first test".to_string(),
                        input: "\na b c\n".as_bytes().to_vec(),
                        output: "(a (b c))".to_string(),
                        has_fields: false,
                    },
                    TestEntry::Example {
                        name: "The second test".to_string(),
                        input: "d".as_bytes().to_vec(),
                        output: "(d)".to_string(),
                        has_fields: false,
                    },
                ]
            }
        );
    }

    #[test]
    fn test_parse_test_content_with_dashes_in_source_code() {
        let entry = parse_test_content(
            "the-filename".to_string(),
            r#"
==================
Code with dashes
==================
abc
---
defg
----
hijkl
-------

(a (b))

=========================
Code ending with dashes
=========================
abc
-----------
-------------------

(c (d))
        "#
            .trim()
            .to_string(),
        );

        assert_eq!(
            entry,
            TestEntry::Group {
                name: "the-filename".to_string(),
                children: vec![
                    TestEntry::Example {
                        name: "Code with dashes".to_string(),
                        input: "abc\n---\ndefg\n----\nhijkl".as_bytes().to_vec(),
                        output: "(a (b))".to_string(),
                        has_fields: false,
                    },
                    TestEntry::Example {
                        name: "Code ending with dashes".to_string(),
                        input: "abc\n-----------".as_bytes().to_vec(),
                        output: "(c (d))".to_string(),
                        has_fields: false,
                    },
                ]
            }
        );
    }
}
<|endoftext|>


<commit_before>use base64;
use serde_json;

use crate::algo::TryFrom;
use crate::error::{err_msg, ResultExt};
use crate::prelude::AlgoIo;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::error::Error;
use std::fs::OpenOptions;
use std::io::{self, BufRead, Write};
use std::process;

const ALGOOUT: &'static str = "/tmp/algoout";

#[derive(Deserialize)]
struct Request {
    data: Value,
    content_type: String,
}

#[derive(Serialize)]
struct AlgoSuccess {
    result: Value,
    metadata: RunnerMetadata,
}

#[derive(Serialize)]
struct AlgoFailure {
    error: RunnerError,
}

#[derive(Serialize)]
struct RunnerMetadata {
    content_type: String,
}

#[derive(Serialize)]
struct RunnerError {
    message: String,
    error_type: &'static str,
}

impl AlgoSuccess {
    fn new<S: Into<String>>(result: Value, content_type: S) -> AlgoSuccess {
        AlgoSuccess {
            result: result,
            metadata: RunnerMetadata {
                content_type: content_type.into(),
            },
        }
    }
}

impl AlgoFailure {
    fn new(err: &dyn Error) -> AlgoFailure {
        AlgoFailure {
            error: RunnerError {
                message: error_cause_chain(err),
                error_type: "AlgorithmError",
            },
        }
    }

    fn system(err: &dyn Error) -> AlgoFailure {
        AlgoFailure {
            error: RunnerError {
                message: error_cause_chain(err),
                error_type: "SystemError",
            },
        }
    }
}

pub fn setup_handler<F, IN, OUT, E, E2>(mut apply: F)
where
    F: FnMut(IN) -> Result<OUT, E>,
    IN: TryFrom<AlgoIo, Err = E2>,
    OUT: Into<AlgoIo>,
    E: Into<Box<Error>>,
    E2: Into<Box<Error>>,
{
    println!("PIPE_INIT_COMPLETE");
    flush_std_pipes();

    let stdin = io::stdin();
    for line in stdin.lock().lines() {
        let output_json = match line {
            Ok(json_line) => {
                let output = build_input(json_line).and_then(|input| match IN::try_from(input) {
                    Ok(algo_io) => match apply(algo_io) {
                        Ok(out) => Ok(out.into()),
                        Err(err) => Err(err.into()),
                    },
                    Err(err) => Err(err.into()),
                });
                flush_std_pipes();
                serialize_output(output)
            }
            Err(_) => {
                let err = line.context("failed to read stdin").unwrap_err();
                serde_json::to_string(&AlgoFailure::system(&err as &dyn Error))
                    .expect(&format!("Failed to read stdin and failed to encode the error: {}", err))
            }
        };
        algoout(&output_json);
    }
}

impl From<AlgoIo> for AlgoSuccess {
    fn from(output: AlgoIo) -> AlgoSuccess {
        match output {
            AlgoIo::Text(text) => AlgoSuccess::new(Value::String(text), "text"),
            AlgoIo::Json(json_obj) => AlgoSuccess::new(json_obj, "json"),
            AlgoIo::Binary(bytes) => {
                let result = base64::encode(&bytes);
                AlgoSuccess::new(Value::String(result), "binary")
            }
        }
    }
}

fn error_cause_chain(err: &dyn Error) -> String {
    let mut causes = vec![err.to_string()];
    let mut e = err;
    while let Some(cause) = e.source() {
        causes.push(cause.to_string());
        e = cause;
    }
    causes.join("\ncaused by: ")
}

fn serialize_output(output: Result<AlgoIo, Box<dyn Error>>) -> String {
    let json_result = match output {
        Ok(output) => serde_json::to_string(&AlgoSuccess::from(output)),
        Err(err) => serde_json::to_string(&AlgoFailure::new(&*err as &dyn Error)),
    };

    json_result.expect("Failed to encode JSON")
}

fn flush_std_pipes() {
    let _ = io::stdout().flush();
    let _ = io::stderr().flush();
}

fn algoout(output_json: &str) {
    match OpenOptions::new().write(true).open(ALGOOUT) {
        Ok(mut f) => {
            let _ = f.write(output_json.as_bytes());
            let _ = f.write(b"\n");
        }
        Err(e) => {
            println!("Cannot write to algoout pipe: {}\n", e);
            process::exit(-1);
        }
    };
}

fn build_input(stdin: String) -> Result<AlgoIo, Box<dyn Error>> {
    let req = serde_json::from_str(&stdin).context("Error decoding JSON request")?;
    let Request { data, content_type } = req;
    let input = match (&*content_type, data) {
        ("text", Value::String(text)) => AlgoIo::Text(text),
        ("binary", Value::String(ref encoded)) => {
            let bytes =
                base64::decode(encoded).context("Error decoding request input as binary")?;
            AlgoIo::Binary(bytes)
        }
        ("json", json_obj) => AlgoIo::Json(json_obj),
        (_, _) => return Err(err_msg(format!("Content type '{}' is invalid", content_type)).into()),
    };
    Ok(input)
}
<commit_msg>Document setup_handler<commit_after>use base64;
use serde_json;

use crate::algo::TryFrom;
use crate::error::{err_msg, ResultExt};
use crate::prelude::AlgoIo;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::error::Error;
use std::fs::OpenOptions;
use std::io::{self, BufRead, Write};
use std::process;

const ALGOOUT: &'static str = "/tmp/algoout";

#[derive(Deserialize)]
struct Request {
    data: Value,
    content_type: String,
}

#[derive(Serialize)]
struct AlgoSuccess {
    result: Value,
    metadata: RunnerMetadata,
}

#[derive(Serialize)]
struct AlgoFailure {
    error: RunnerError,
}

#[derive(Serialize)]
struct RunnerMetadata {
    content_type: String,
}

#[derive(Serialize)]
struct RunnerError {
    message: String,
    error_type: &'static str,
}

impl AlgoSuccess {
    fn new<S: Into<String>>(result: Value, content_type: S) -> AlgoSuccess {
        AlgoSuccess {
            result: result,
            metadata: RunnerMetadata {
                content_type: content_type.into(),
            },
        }
    }
}

impl AlgoFailure {
    fn new(err: &dyn Error) -> AlgoFailure {
        AlgoFailure {
            error: RunnerError {
                message: error_cause_chain(err),
                error_type: "AlgorithmError",
            },
        }
    }

    fn system(err: &dyn Error) -> AlgoFailure {
        AlgoFailure {
            error: RunnerError {
                message: error_cause_chain(err),
                error_type: "SystemError",
            },
        }
    }
}

/// Configures the Algorithmia-compatible FaaS handler
///
/// This function is only used when authoring an algorithm to run on the Algorithmia platform.
/// This function receives a handler function or closure that is used to process each individual request.
/// It will block and only return once it reaches EOF of STDIN.
///
/// The handler function makes heavy use of generic conversion traits
/// to provide flexibility in what functions it can accept.
/// Essentially the handler is any function with an input argument that
/// `AlgoIo` can be converted into, and an output that is either
/// a type that can be convert into `AlgoIo` or any error type that
/// can be coerced into a boxed `Error`.
///
/// ## Getting started
///
/// The simplest usage of this function is to just use a simple function that works entirely with `String`s:
///
/// ```rust
/// use algorithmia::prelude::*;
///
/// fn apply(name: String) -> Result<String, String> {
///     unimplemented!()
/// }
///
/// fn main() {
///     setup_handler(apply)
/// }
/// ```
///
/// ## Automatic JSON serialization/deserialization
///
/// To use your own custom types as input and output, simply implement `Deserialize` and `Serialize` respectively.
///
/// ```rust
/// #[derive(Deserialize)]
/// struct Input { titles: Vec<String>, max: u32 }
///
/// #[derive(Serialize)]
/// struct Output { titles: Vec<String> }
///
/// fn apply(input: Input) -> Result<Output, Box<Error>> {
///     unimplemented!();
/// }
///
/// fn main() {
///     setup_handler(apply)
/// }
/// ```
///
/// ## Input/Output types:
/// **Valid input**
/// - Any type that implements `serde::Deserialize` (e.g. `#[derive(Deserialize)]`
/// - `algo::ByteVec` if working with binary input
///
/// **Valid output types (`Ok` variant of return value)**
/// - Any type that implements `serde::Serialize` (e.g. `#[derive(Serialize)]`
/// - `algo::ByteVec` if working with binary output
///
/// **Valid error types (`Err` variant of return value)**
/// Anything with an conversion to `Box<Error>`. This includes `String` and basically any type that implements the `Error` trait.
///
/// ## Preloading and Maintaining State (Advanced Usage)
///
/// If your algorithm has a preload step that doesn't vary with user input (e.g. loading a model),
/// you can perform that prior to calling `setup_handler` and then passing in a reference to that stay via a capturing closure:
///
/// ```rust
/// #[derive(Deserialize)]
/// struct Input { titles: Vec<String>, max: u32 }
///
/// #[derive(Serialize)]
/// struct Output { titles: Vec<String> }
///
/// struct App { model: Vec<u8> }
///
/// fn apply(input: Input, app: &App) -> Result<Output, String> {
///     unimplemented!();
/// }
///
/// fn main() {
///     let app = App { model: load_model() };
///     setup_handler(|input| apply(input, &app) )
/// }
/// ```
pub fn setup_handler<F, IN, OUT, E, E2>(mut apply: F)
where
    F: FnMut(IN) -> Result<OUT, E>,
    IN: TryFrom<AlgoIo, Err = E2>,
    OUT: Into<AlgoIo>,
    E: Into<Box<Error>>,
    E2: Into<Box<Error>>,
{
    println!("PIPE_INIT_COMPLETE");
    flush_std_pipes();

    let stdin = io::stdin();
    for line in stdin.lock().lines() {
        let output_json = match line {
            Ok(json_line) => {
                let output = build_input(json_line).and_then(|input| match IN::try_from(input) {
                    Ok(algo_io) => match apply(algo_io) {
                        Ok(out) => Ok(out.into()),
                        Err(err) => Err(err.into()),
                    },
                    Err(err) => Err(err.into()),
                });
                flush_std_pipes();
                serialize_output(output)
            }
            Err(_) => {
                let err = line.context("failed to read stdin").unwrap_err();
                serde_json::to_string(&AlgoFailure::system(&err as &dyn Error))
                    .expect(&format!("Failed to read stdin and failed to encode the error: {}", err))
            }
        };
        algoout(&output_json);
    }
}

impl From<AlgoIo> for AlgoSuccess {
    fn from(output: AlgoIo) -> AlgoSuccess {
        match output {
            AlgoIo::Text(text) => AlgoSuccess::new(Value::String(text), "text"),
            AlgoIo::Json(json_obj) => AlgoSuccess::new(json_obj, "json"),
            AlgoIo::Binary(bytes) => {
                let result = base64::encode(&bytes);
                AlgoSuccess::new(Value::String(result), "binary")
            }
        }
    }
}

fn error_cause_chain(err: &dyn Error) -> String {
    let mut causes = vec![err.to_string()];
    let mut e = err;
    while let Some(cause) = e.source() {
        causes.push(cause.to_string());
        e = cause;
    }
    causes.join("\ncaused by: ")
}

fn serialize_output(output: Result<AlgoIo, Box<dyn Error>>) -> String {
    let json_result = match output {
        Ok(output) => serde_json::to_string(&AlgoSuccess::from(output)),
        Err(err) => serde_json::to_string(&AlgoFailure::new(&*err as &dyn Error)),
    };

    json_result.expect("Failed to encode JSON")
}

fn flush_std_pipes() {
    let _ = io::stdout().flush();
    let _ = io::stderr().flush();
}

fn algoout(output_json: &str) {
    match OpenOptions::new().write(true).open(ALGOOUT) {
        Ok(mut f) => {
            let _ = f.write(output_json.as_bytes());
            let _ = f.write(b"\n");
        }
        Err(e) => {
            println!("Cannot write to algoout pipe: {}\n", e);
            process::exit(-1);
        }
    };
}

fn build_input(stdin: String) -> Result<AlgoIo, Box<dyn Error>> {
    let req = serde_json::from_str(&stdin).context("Error decoding JSON request")?;
    let Request { data, content_type } = req;
    let input = match (&*content_type, data) {
        ("text", Value::String(text)) => AlgoIo::Text(text),
        ("binary", Value::String(ref encoded)) => {
            let bytes =
                base64::decode(encoded).context("Error decoding request input as binary")?;
            AlgoIo::Binary(bytes)
        }
        ("json", json_obj) => AlgoIo::Json(json_obj),
        (_, _) => return Err(err_msg(format!("Content type '{}' is invalid", content_type)).into()),
    };
    Ok(input)
}
<|endoftext|>


<commit_before>use {Error, Parser, Result};

/// A file.
pub struct File {
    text: String,
}

macro_rules! io(
    ($result:expr) => (
        match $result {
            Ok(result) => result,
            Err(error) => return Err(Error {
                line: 0,
                column: 0,
                message: format!("{:?}", error),
            }),
        }
    );
);

impl File {
    /// Open a file.
    #[inline]
    pub fn open(path: &Path) -> Result<File> {
        use std::old_io::File as IoFile;

        Ok(File {
            text: io!(io!(IoFile::open(path)).read_to_string()),
        })
    }

    /// Return an iterator over the content of the file.
    pub fn parse<'s>(&'s self) -> Parser<'s> {
        Parser::new(&self.text)
    }
}

#[cfg(test)]
mod tests {
    use super::File;
    use tests::find_fixture;
    use {Event, Tag};

    #[test]
    fn parse() {
        let file = File::open(&find_fixture("benton")).unwrap();
        let mut parser = file.parse();

        for _ in range(0, 4) {
            match parser.next().unwrap() {
                Event::Tag(Tag::Unknown(..)) => {},
                _ => assert!(false),
            }
        }

        for _ in range(0, 4) {
            match parser.next().unwrap() {
                Event::Tag(Tag::Path(..)) => {},
                _ => assert!(false),
            }
        }

        match parser.next().unwrap() {
            Event::Tag(Tag::Unknown(..)) => {},
            _ => assert!(false),
        }

        assert!(parser.next().is_none());
    }
}
<commit_msg>Dry the tests<commit_after>use {Error, Parser, Result};

/// A file.
pub struct File {
    text: String,
}

macro_rules! io(
    ($result:expr) => (
        match $result {
            Ok(result) => result,
            Err(error) => return Err(Error {
                line: 0,
                column: 0,
                message: format!("{:?}", error),
            }),
        }
    );
);

impl File {
    /// Open a file.
    #[inline]
    pub fn open(path: &Path) -> Result<File> {
        use std::old_io::File as IoFile;

        Ok(File {
            text: io!(io!(IoFile::open(path)).read_to_string()),
        })
    }

    /// Return an iterator over the content of the file.
    pub fn parse<'s>(&'s self) -> Parser<'s> {
        Parser::new(&self.text)
    }
}

#[cfg(test)]
mod tests {
    use super::File;
    use tests::find_fixture;
    use {Event, Tag};

    #[test]
    fn parse() {
        let file = File::open(&find_fixture("benton")).unwrap();
        let mut parser = file.parse();

        macro_rules! test(
            ($parser:expr, $tag:ident) => (
                match $parser.next().unwrap() {
                    Event::Tag(Tag::$tag(..)) => {},
                    _ => assert!(false),
                }
            );
        );

        for _ in range(0, 4) {
            test!(parser, Unknown);
        }
        for _ in range(0, 4) {
            test!(parser, Path);
        }
        test!(parser, Unknown);
        assert!(parser.next().is_none());
    }
}
<|endoftext|>


<commit_before>use libc::{self, c_int};
use {Errno, Result};
use unistd::Pid;

use sys::signal::Signal;

mod ffi {
    use libc::{pid_t, c_int};

    extern {
        pub fn waitpid(pid: pid_t, status: *mut c_int, options: c_int) -> pid_t;
    }
}

#[cfg(not(any(target_os = "linux",
              target_os = "android")))]
libc_bitflags!(
    pub flags WaitPidFlag: c_int {
        WNOHANG,
        WUNTRACED,
    }
);

#[cfg(any(target_os = "linux",
          target_os = "android"))]
libc_bitflags!(
    pub flags WaitPidFlag: c_int {
        WNOHANG,
        WUNTRACED,
        WEXITED,
        WCONTINUED,
        WNOWAIT, // Don't reap, just poll status.
        __WNOTHREAD, // Don't wait on children of other threads in this group
        __WALL, // Wait on all children, regardless of type
        __WCLONE,
    }
);

#[cfg(any(target_os = "linux",
          target_os = "android"))]
const WSTOPPED: WaitPidFlag = WUNTRACED;

#[derive(Eq, PartialEq, Clone, Copy, Debug)]
pub enum WaitStatus {
    Exited(Pid, i8),
    Signaled(Pid, Signal, bool),
    Stopped(Pid, Signal),
    #[cfg(any(target_os = "linux", target_os = "android"))]
    PtraceEvent(Pid, Signal, c_int),
    #[cfg(any(target_os = "linux", target_os = "android"))]
    PtraceSyscall(Pid),
    Continued(Pid),
    StillAlive
}

#[cfg(any(target_os = "linux",
          target_os = "android"))]
mod status {
    use sys::signal::Signal;
    use libc::c_int;
    use libc::SIGTRAP;

    pub fn exited(status: i32) -> bool {
        (status & 0x7F) == 0
    }

    pub fn exit_status(status: i32) -> i8 {
        ((status & 0xFF00) >> 8) as i8
    }

    pub fn signaled(status: i32) -> bool {
        ((((status & 0x7f) + 1) as i8) >> 1) > 0
    }

    pub fn term_signal(status: i32) -> Signal {
        Signal::from_c_int(status & 0x7f).unwrap()
    }

    pub fn dumped_core(status: i32) -> bool {
        (status & 0x80) != 0
    }

    pub fn stopped(status: i32) -> bool {
        (status & 0xff) == 0x7f
    }

    pub fn stop_signal(status: i32) -> Signal {
        // Keep only 7 bits of the signal: the high bit
        // is used to indicate syscall stops, below.
        Signal::from_c_int((status & 0x7F00) >> 8).unwrap()
    }

    pub fn syscall_stop(status: i32) -> bool {
        // From ptrace(2), setting PTRACE_O_TRACESYSGOOD has the effect
        // of delivering SIGTRAP | 0x80 as the signal number for syscall
        // stops. This allows easily distinguishing syscall stops from
        // genuine SIGTRAP signals.
        ((status & 0xFF00) >> 8) == SIGTRAP | 0x80
    }

    pub fn stop_additional(status: i32) -> c_int {
        (status >> 16) as c_int
    }

    pub fn continued(status: i32) -> bool {
        status == 0xFFFF
    }
}

#[cfg(any(target_os = "macos",
          target_os = "ios"))]
mod status {
    use sys::signal::{Signal,SIGCONT};

    const WCOREFLAG: i32 = 0x80;
    const WSTOPPED: i32 = 0x7f;

    fn wstatus(status: i32) -> i32 {
        status & 0x7F
    }

    pub fn exit_status(status: i32) -> i8 {
        ((status >> 8) & 0xFF) as i8
    }

    pub fn stop_signal(status: i32) -> Signal {
        Signal::from_c_int(status >> 8).unwrap()
    }

    pub fn continued(status: i32) -> bool {
        wstatus(status) == WSTOPPED && stop_signal(status) == SIGCONT
    }

    pub fn stopped(status: i32) -> bool {
        wstatus(status) == WSTOPPED && stop_signal(status) != SIGCONT
    }

    pub fn exited(status: i32) -> bool {
        wstatus(status) == 0
    }

    pub fn signaled(status: i32) -> bool {
        wstatus(status) != WSTOPPED && wstatus(status) != 0
    }

    pub fn term_signal(status: i32) -> Signal {
        Signal::from_c_int(wstatus(status)).unwrap()
    }

    pub fn dumped_core(status: i32) -> bool {
        (status & WCOREFLAG) != 0
    }
}

#[cfg(any(target_os = "freebsd",
          target_os = "openbsd",
          target_os = "dragonfly",
          target_os = "netbsd"))]
mod status {
    use sys::signal::Signal;

    const WCOREFLAG: i32 = 0x80;
    const WSTOPPED: i32 = 0x7f;

    fn wstatus(status: i32) -> i32 {
        status & 0x7F
    }

    pub fn stopped(status: i32) -> bool {
        wstatus(status) == WSTOPPED
    }

    pub fn stop_signal(status: i32) -> Signal {
        Signal::from_c_int(status >> 8).unwrap()
    }

    pub fn signaled(status: i32) -> bool {
        wstatus(status) != WSTOPPED && wstatus(status) != 0 && status != 0x13
    }

    pub fn term_signal(status: i32) -> Signal {
        Signal::from_c_int(wstatus(status)).unwrap()
    }

    pub fn exited(status: i32) -> bool {
        wstatus(status) == 0
    }

    pub fn exit_status(status: i32) -> i8 {
        (status >> 8) as i8
    }

    pub fn continued(status: i32) -> bool {
        status == 0x13
    }

    pub fn dumped_core(status: i32) -> bool {
        (status & WCOREFLAG) != 0
    }
}

fn decode(pid : Pid, status: i32) -> WaitStatus {
    if status::exited(status) {
        WaitStatus::Exited(pid, status::exit_status(status))
    } else if status::signaled(status) {
        WaitStatus::Signaled(pid, status::term_signal(status), status::dumped_core(status))
    } else if status::stopped(status) {
        cfg_if! {
            if #[cfg(any(target_os = "linux", target_os = "android"))] {
                fn decode_stopped(pid: Pid, status: i32) -> WaitStatus {
                    let status_additional = status::stop_additional(status);
                    if status::syscall_stop(status) {
                        WaitStatus::PtraceSyscall(pid)
                    } else if status_additional == 0 {
                        WaitStatus::Stopped(pid, status::stop_signal(status))
                    } else {
                        WaitStatus::PtraceEvent(pid, status::stop_signal(status), status::stop_additional(status))
                    }
                }
            } else {
                fn decode_stopped(pid: Pid, status: i32) -> WaitStatus {
                    WaitStatus::Stopped(pid, status::stop_signal(status))
                }
            }
        }
        decode_stopped(pid, status)
    } else {
        assert!(status::continued(status));
        WaitStatus::Continued(pid)
    }
}

pub fn waitpid<P: Into<Option<Pid>>>(pid: P, options: Option<WaitPidFlag>) -> Result<WaitStatus> {
    use self::WaitStatus::*;

    let mut status: i32 = 0;

    let option_bits = match options {
        Some(bits) => bits.bits(),
        None => 0
    };

    let res = unsafe { ffi::waitpid(pid.into().unwrap_or(Pid::from_raw(-1)).into(), &mut status as *mut c_int, option_bits) };

    Ok(match try!(Errno::result(res)) {
        0 => StillAlive,
        res => decode(Pid::from_raw(res), status),
    })
}

pub fn wait() -> Result<WaitStatus> {
    waitpid(None, None)
}
<commit_msg>Document WaitStatus and its variants<commit_after>use libc::{self, c_int};
use {Errno, Result};
use unistd::Pid;

use sys::signal::Signal;

mod ffi {
    use libc::{pid_t, c_int};

    extern {
        pub fn waitpid(pid: pid_t, status: *mut c_int, options: c_int) -> pid_t;
    }
}

#[cfg(not(any(target_os = "linux",
              target_os = "android")))]
libc_bitflags!(
    pub flags WaitPidFlag: c_int {
        WNOHANG,
        WUNTRACED,
    }
);

#[cfg(any(target_os = "linux",
          target_os = "android"))]
libc_bitflags!(
    pub flags WaitPidFlag: c_int {
        WNOHANG,
        WUNTRACED,
        WEXITED,
        WCONTINUED,
        WNOWAIT, // Don't reap, just poll status.
        __WNOTHREAD, // Don't wait on children of other threads in this group
        __WALL, // Wait on all children, regardless of type
        __WCLONE,
    }
);

#[cfg(any(target_os = "linux",
          target_os = "android"))]
const WSTOPPED: WaitPidFlag = WUNTRACED;

/// Possible return values from `wait()` or `waitpid()`.
///
/// Each status (other than `StillAlive`) describes a state transition
/// in a child process `Pid`, such as the process exiting or stopping,
/// plus additional data about the transition if any.
///
/// Note that there are two Linux-specific enum variants, `PtraceEvent`
/// and `PtraceSyscall`. Portable code should avoid exhaustively
/// matching on `WaitStatus`.
#[derive(Eq, PartialEq, Clone, Copy, Debug)]
pub enum WaitStatus {
    /// The process exited normally (as with `exit()` or returning from
    /// `main`) with the given exit code. This case matches the C macro
    /// `WIFEXITED(status)`; the second field is `WEXITSTATUS(status)`.
    Exited(Pid, i8),
    /// The process was killed by the given signal. The third field
    /// indicates whether the signal generated a core dump. This case
    /// matches the C macro `WIFSIGNALED(status)`; the last two fields
    /// correspond to `WTERMSIG(status)` and `WCOREDUMP(status)`.
    Signaled(Pid, Signal, bool),
    /// The process is alive, but was stopped by the given signal. This
    /// is only reported if `WaitPidFlag::WUNTRACED` was passed. This
    /// case matches the C macro `WIFSTOPPED(status)`; the second field
    /// is `WSTOPSIG(status)`.
    Stopped(Pid, Signal),
    /// The traced process was stopped by a `PTRACE_EVENT_*` event. See
    /// [`nix::sys::ptrace`] and [`ptrace`(2)] for more information. All
    /// currently-defined events use `SIGTRAP` as the signal; the third
    /// field is the `PTRACE_EVENT_*` value of the event.
    ///
    /// [`nix::sys::ptrace`]: ../ptrace/index.html
    /// [`ptrace`(2)]: http://man7.org/linux/man-pages/man2/ptrace.2.html
    #[cfg(any(target_os = "linux", target_os = "android"))]
    PtraceEvent(Pid, Signal, c_int),
    /// The traced process was stopped by execution of a system call,
    /// and `PTRACE_O_TRACESYSGOOD` is in effect. See [`ptrace`(2)] for
    /// more information.
    ///
    /// [`ptrace`(2)]: http://man7.org/linux/man-pages/man2/ptrace.2.html
    #[cfg(any(target_os = "linux", target_os = "android"))]
    PtraceSyscall(Pid),
    /// The process was previously stopped but has resumed execution
    /// after receiving a `SIGCONT` signal. This is only reported if
    /// `WaitPidFlag::WCONTINUED` was passed. This case matches the C
    /// macro `WIFCONTINUED(status)`.
    Continued(Pid),
    /// There are currently no state changes to report in any awaited
    /// child process. This is only returned if `WaitPidFlag::WNOHANG`
    /// was used (otherwise `wait()` or `waitpid()` would block until
    /// there was something to report).
    StillAlive
}

#[cfg(any(target_os = "linux",
          target_os = "android"))]
mod status {
    use sys::signal::Signal;
    use libc::c_int;
    use libc::SIGTRAP;

    pub fn exited(status: i32) -> bool {
        (status & 0x7F) == 0
    }

    pub fn exit_status(status: i32) -> i8 {
        ((status & 0xFF00) >> 8) as i8
    }

    pub fn signaled(status: i32) -> bool {
        ((((status & 0x7f) + 1) as i8) >> 1) > 0
    }

    pub fn term_signal(status: i32) -> Signal {
        Signal::from_c_int(status & 0x7f).unwrap()
    }

    pub fn dumped_core(status: i32) -> bool {
        (status & 0x80) != 0
    }

    pub fn stopped(status: i32) -> bool {
        (status & 0xff) == 0x7f
    }

    pub fn stop_signal(status: i32) -> Signal {
        // Keep only 7 bits of the signal: the high bit
        // is used to indicate syscall stops, below.
        Signal::from_c_int((status & 0x7F00) >> 8).unwrap()
    }

    pub fn syscall_stop(status: i32) -> bool {
        // From ptrace(2), setting PTRACE_O_TRACESYSGOOD has the effect
        // of delivering SIGTRAP | 0x80 as the signal number for syscall
        // stops. This allows easily distinguishing syscall stops from
        // genuine SIGTRAP signals.
        ((status & 0xFF00) >> 8) == SIGTRAP | 0x80
    }

    pub fn stop_additional(status: i32) -> c_int {
        (status >> 16) as c_int
    }

    pub fn continued(status: i32) -> bool {
        status == 0xFFFF
    }
}

#[cfg(any(target_os = "macos",
          target_os = "ios"))]
mod status {
    use sys::signal::{Signal,SIGCONT};

    const WCOREFLAG: i32 = 0x80;
    const WSTOPPED: i32 = 0x7f;

    fn wstatus(status: i32) -> i32 {
        status & 0x7F
    }

    pub fn exit_status(status: i32) -> i8 {
        ((status >> 8) & 0xFF) as i8
    }

    pub fn stop_signal(status: i32) -> Signal {
        Signal::from_c_int(status >> 8).unwrap()
    }

    pub fn continued(status: i32) -> bool {
        wstatus(status) == WSTOPPED && stop_signal(status) == SIGCONT
    }

    pub fn stopped(status: i32) -> bool {
        wstatus(status) == WSTOPPED && stop_signal(status) != SIGCONT
    }

    pub fn exited(status: i32) -> bool {
        wstatus(status) == 0
    }

    pub fn signaled(status: i32) -> bool {
        wstatus(status) != WSTOPPED && wstatus(status) != 0
    }

    pub fn term_signal(status: i32) -> Signal {
        Signal::from_c_int(wstatus(status)).unwrap()
    }

    pub fn dumped_core(status: i32) -> bool {
        (status & WCOREFLAG) != 0
    }
}

#[cfg(any(target_os = "freebsd",
          target_os = "openbsd",
          target_os = "dragonfly",
          target_os = "netbsd"))]
mod status {
    use sys::signal::Signal;

    const WCOREFLAG: i32 = 0x80;
    const WSTOPPED: i32 = 0x7f;

    fn wstatus(status: i32) -> i32 {
        status & 0x7F
    }

    pub fn stopped(status: i32) -> bool {
        wstatus(status) == WSTOPPED
    }

    pub fn stop_signal(status: i32) -> Signal {
        Signal::from_c_int(status >> 8).unwrap()
    }

    pub fn signaled(status: i32) -> bool {
        wstatus(status) != WSTOPPED && wstatus(status) != 0 && status != 0x13
    }

    pub fn term_signal(status: i32) -> Signal {
        Signal::from_c_int(wstatus(status)).unwrap()
    }

    pub fn exited(status: i32) -> bool {
        wstatus(status) == 0
    }

    pub fn exit_status(status: i32) -> i8 {
        (status >> 8) as i8
    }

    pub fn continued(status: i32) -> bool {
        status == 0x13
    }

    pub fn dumped_core(status: i32) -> bool {
        (status & WCOREFLAG) != 0
    }
}

fn decode(pid : Pid, status: i32) -> WaitStatus {
    if status::exited(status) {
        WaitStatus::Exited(pid, status::exit_status(status))
    } else if status::signaled(status) {
        WaitStatus::Signaled(pid, status::term_signal(status), status::dumped_core(status))
    } else if status::stopped(status) {
        cfg_if! {
            if #[cfg(any(target_os = "linux", target_os = "android"))] {
                fn decode_stopped(pid: Pid, status: i32) -> WaitStatus {
                    let status_additional = status::stop_additional(status);
                    if status::syscall_stop(status) {
                        WaitStatus::PtraceSyscall(pid)
                    } else if status_additional == 0 {
                        WaitStatus::Stopped(pid, status::stop_signal(status))
                    } else {
                        WaitStatus::PtraceEvent(pid, status::stop_signal(status), status::stop_additional(status))
                    }
                }
            } else {
                fn decode_stopped(pid: Pid, status: i32) -> WaitStatus {
                    WaitStatus::Stopped(pid, status::stop_signal(status))
                }
            }
        }
        decode_stopped(pid, status)
    } else {
        assert!(status::continued(status));
        WaitStatus::Continued(pid)
    }
}

pub fn waitpid<P: Into<Option<Pid>>>(pid: P, options: Option<WaitPidFlag>) -> Result<WaitStatus> {
    use self::WaitStatus::*;

    let mut status: i32 = 0;

    let option_bits = match options {
        Some(bits) => bits.bits(),
        None => 0
    };

    let res = unsafe { ffi::waitpid(pid.into().unwrap_or(Pid::from_raw(-1)).into(), &mut status as *mut c_int, option_bits) };

    Ok(match try!(Errno::result(res)) {
        0 => StillAlive,
        res => decode(Pid::from_raw(res), status),
    })
}

pub fn wait() -> Result<WaitStatus> {
    waitpid(None, None)
}
<|endoftext|>


<commit_before>#![cfg(test)]

use std::collections::HashMap;
use rustc_serialize::json;
use rustc_serialize::json::{decode, Json, Object};
use rustc_serialize::{Decoder, Decodable};
use std::io::prelude::*;
use std::fs::File;
use token::{Token, ReservedWord};

pub struct ExpectPass {
    source: String,
    expected: Json,
    options: Option<Json>
}

pub struct ExpectFail {
    source: String,
    error: String,
    options: Option<Json>
}

pub enum TestCase {
    Pass(ExpectPass),
    Fail(ExpectFail)
}

trait JsonExt {
    fn into_array(self) -> json::Array;
    fn into_string(self) -> String;
    fn into_string_opt(self) -> Option<String>;
}

impl JsonExt for Json {
    fn into_array(self) -> json::Array {
        match self {
            Json::Array(array) => array,
            _ => panic!("expected array")
        }
    }
    fn into_string(self) -> String {
        match self {
            Json::String(string) => string,
            _ => panic!("expected string")
        }
    }
    fn into_string_opt(self) -> Option<String> {
        match self {
            Json::Null => None,
            Json::String(string) => Some(string),
            _ => panic!("expected string or null")
        }
    }
}

fn parse_expect_fail(test: &mut Object) -> ExpectFail {
    ExpectFail {
        source: test.remove("source").unwrap().into_string(),
        error: test.remove("error").unwrap().into_string(),
        options: None
    }
}

fn parse_expect_pass(test: &mut Object) -> ExpectPass {
    ExpectPass {
        source: test.remove("source").unwrap().into_string(),
        expected: test.remove("expected").unwrap(),
        options: None
    }
}

fn deserialize_token(mut data: Json) -> Token {
    let mut obj = data.as_object_mut().unwrap();
    let ty = obj.remove("type").unwrap().into_string();

    let mut matchers = HashMap::new();
    matchers.insert("LBrace", &|_:&mut Object| -> Option<Token> { Some(Token::LBrace) });
    matchers.insert("DecimalInt", &|data:&mut Object| -> Option<Token> {
        match data.remove("value") {
            None => None,
            Some(str) => Some(Token::DecimalInt(str.into_string()))
        }
    });
    let f = matchers.get("DecimalInt").unwrap();
    let obj = Json::from_str("{\"type\":\"DecimalInt\",\"value\":\"11.3\"}").unwrap().as_object_mut().unwrap();
    f(obj).unwrap()
}

fn parse_test(mut test: Json) -> TestCase {
    let obj = test.as_object_mut().unwrap();
    if obj.contains_key("error") {
        TestCase::Fail(parse_expect_fail(obj))
    } else {
        TestCase::Pass(parse_expect_pass(obj))
    }
}

pub fn read_tests(path: &str) -> Vec<TestCase> {
    let mut f: File = File::open(path).unwrap();
    let mut s = String::new();
    f.read_to_string(&mut s);
    parse_tests(&s)
}

pub fn parse_tests(src: &str) -> Vec<TestCase> {
    let data: Json = src.parse().unwrap();
    data.into_array()
        .into_iter()
        .map(parse_test)
        .collect()
}
<commit_msg>Niko said I need an explicit annotation on the local and aturon said I need a boxed trait object (an existential) for the closure; this compiles<commit_after>#![cfg(test)]

use std::collections::HashMap;
use rustc_serialize::json;
use rustc_serialize::json::{decode, Json, Object};
use rustc_serialize::{Decoder, Decodable};
use std::io::prelude::*;
use std::fs::File;
use token::{Token, ReservedWord};

pub struct ExpectPass {
    source: String,
    expected: Json,
    options: Option<Json>
}

pub struct ExpectFail {
    source: String,
    error: String,
    options: Option<Json>
}

pub enum TestCase {
    Pass(ExpectPass),
    Fail(ExpectFail)
}

trait JsonExt {
    fn into_array(self) -> json::Array;
    fn into_string(self) -> String;
    fn into_string_opt(self) -> Option<String>;
}

impl JsonExt for Json {
    fn into_array(self) -> json::Array {
        match self {
            Json::Array(array) => array,
            _ => panic!("expected array")
        }
    }
    fn into_string(self) -> String {
        match self {
            Json::String(string) => string,
            _ => panic!("expected string")
        }
    }
    fn into_string_opt(self) -> Option<String> {
        match self {
            Json::Null => None,
            Json::String(string) => Some(string),
            _ => panic!("expected string or null")
        }
    }
}

fn parse_expect_fail(test: &mut Object) -> ExpectFail {
    ExpectFail {
        source: test.remove("source").unwrap().into_string(),
        error: test.remove("error").unwrap().into_string(),
        options: None
    }
}

fn parse_expect_pass(test: &mut Object) -> ExpectPass {
    ExpectPass {
        source: test.remove("source").unwrap().into_string(),
        expected: test.remove("expected").unwrap(),
        options: None
    }
}

fn deserialize_token(mut data: Json) -> Token {
    let mut obj = data.as_object_mut().unwrap();
    let ty = obj.remove("type").unwrap().into_string();

    let mut matchers: HashMap<&str, Box<Fn(&mut Object) -> Option<Token>>> = HashMap::new();
    matchers.insert("LBrace", Box::new(|_:&mut Object| -> Option<Token> { Some(Token::LBrace) }));
    matchers.insert("DecimalInt", Box::new(|data:&mut Object| -> Option<Token> {
        match data.remove("value") {
            None => None,
            Some(str) => Some(Token::DecimalInt(str.into_string()))
        }
    }));
    let f = matchers.get("DecimalInt").unwrap();
    let mut data = Json::from_str("{\"type\":\"DecimalInt\",\"value\":\"11.3\"}").unwrap();
    let obj = data.as_object_mut().unwrap();
    f(obj).unwrap()
}

fn parse_test(mut test: Json) -> TestCase {
    let obj = test.as_object_mut().unwrap();
    if obj.contains_key("error") {
        TestCase::Fail(parse_expect_fail(obj))
    } else {
        TestCase::Pass(parse_expect_pass(obj))
    }
}

pub fn read_tests(path: &str) -> Vec<TestCase> {
    let mut f: File = File::open(path).unwrap();
    let mut s = String::new();
    f.read_to_string(&mut s);
    parse_tests(&s)
}

pub fn parse_tests(src: &str) -> Vec<TestCase> {
    let data: Json = src.parse().unwrap();
    data.into_array()
        .into_iter()
        .map(parse_test)
        .collect()
}
<|endoftext|>


<commit_before>use piston_window::*;
use piston_window::types::Color;

use board::Board;
use randomizer::Randomizer;
use tetromino::*;
use settings::*;
use stats::*;

enum GameState {
    Playing,
    Paused
}

pub trait Game {
    fn on_input(&mut self, input: Input);
    fn on_update(&mut self, update_args: UpdateArgs);
    fn render(&mut self, context: Context, graphics: &mut G2d);
}

pub trait Renderer {
    fn render(&self, context: Context, graphics: &mut G2d);
}

pub trait GridRenderer {
    fn render(&self, x: i32, y: i32, context: Context, graphics: &mut G2d);
}

pub struct Rustris {
    board: Board,
    randomizer: Randomizer,
    current_piece: Piece,
    next_piece: Piece,
    stats: GameStats,
    time_since_moved: f64,
    time_per_tick: f64
    // state: GameState
}
impl Rustris {
    pub fn new() -> Rustris {
        let mut randomizer = Randomizer::new();
        let current_piece = randomizer.create_piece();
        let next_piece = randomizer.create_piece();
        Rustris {
            board: Board::new(),
            randomizer: randomizer,
            current_piece: current_piece,
            next_piece: next_piece,
            stats: GameStats::new(),
            time_since_moved: 0.0,
            time_per_tick: 1.0  //For testing
        }
    }

    pub fn reset(&mut self) {

    }

    pub fn get_game_stats(&self) -> &GameStats {
        &self.stats
    }

    pub fn set_current_piece(&mut self, piece: Piece) {
        self.current_piece = piece;
    }

    fn is_valid_board_position(&self, piece: &Piece) -> bool {
        // TODO: Add tests for this
        piece.blocks_iter().all(|block| {
            (block.x >= 0 && block.x < WIDTH_IN_BLOCKS &&
             block.y >= 0 && block.y < HEIGHT_IN_BLOCKS &&
             !self.board.is_space_occupied(block))
        })
    }

    fn should_lock(&self, piece: &Piece) -> bool {
        // TODO: Add tests for this
        piece.blocks_iter().any(|block| {
            block.y >= HEIGHT_IN_BLOCKS ||
            self.board.is_space_occupied(block)
        })
    }

    fn lock_current_piece(&mut self) {
        self.board.set_piece(&self.current_piece);
    }

    fn remove_completed_lines(&mut self) {
        let number_removed = self.board.remove_completed_rows();
        self.stats.score_completed_lines(number_removed);
    }

    fn get_new_piece(&mut self) {
        let next = self.next_piece;
        self.set_current_piece(next);
        self.next_piece = self.randomizer.create_piece();
    }

    fn update(&mut self) {
        let moved = self.current_piece.moved(Direction::Down);
        if self.should_lock(&moved) {
            self.lock_current_piece();
            self.remove_completed_lines();
            self.get_new_piece();
        } else {
            self.set_current_piece(moved);
        }
    }
}
impl Game for Rustris {
    fn on_input(&mut self, input: Input) {
        let mut moved: Option<Piece> = None;
        match input {
            Input::Press(key) => {
                match key {
                    Button::Keyboard(Key::Up) => {
                        moved = Some(self.current_piece.rotated());
                    }
                    Button::Keyboard(Key::Down) => {
                        let move_down = self.current_piece.moved(Direction::Down);
                        if self.is_valid_board_position(&move_down) {
                            self.time_since_moved = 0.0;
                            self.stats.score_soft_drop();
                            self.set_current_piece(move_down);
                        }
                    }
                    Button::Keyboard(Key::Left) => {
                        moved = Some(self.current_piece.moved(Direction::Left));
                    }
                    Button::Keyboard(Key::Right) => {
                        moved = Some(self.current_piece.moved(Direction::Right));
                    }
                    Button::Keyboard(Key::Space) => {
                        // TODO: Implement hard drop code
                    }
                    _ => {}
                }
            }
            _ => {}
        }
        if let Some(piece) = moved {
            if self.is_valid_board_position(&piece) {
                self.set_current_piece(piece);
            }
        }
    }

    fn on_update(&mut self, update_args: UpdateArgs) {
        self.time_since_moved += update_args.dt;
        // TODO: The threshold needs to be able to be updated
        // as the game progresses
        if self.time_since_moved >= self.time_per_tick {
            self.time_since_moved -= self.time_per_tick;
            self.update();
        }
    }

    fn render(&mut self, context: Context, graphics: &mut G2d) {
        self.board.render(context, graphics);
        self.current_piece.render(context, graphics);
    }
}

// TODO: Move this into its own module
pub fn render_square_in_grid(x: i32, y: i32, color: Color, context: Context, graphics: &mut G2d) {
    let square = rectangle::square(
        0.0, 0.0, BLOCK_SIZE - (2.0 * GRID_LINE_WIDTH)
    );
    let transform = context.transform
        // Translate the origin of the transform based on where the grid starts
        .trans(GRID_X_OFFSET, GRID_Y_OFFSET)
        // Translate the square into the first grid cell (off of the bounding box)
        .trans(GRID_LINE_WIDTH, GRID_LINE_WIDTH)
        // Translate the square into the proper grid cell based on the grid x and y position
        .trans((x as f64)*BLOCK_SIZE, (y as f64)*BLOCK_SIZE);
    rectangle(color, square, transform, graphics);
}
<commit_msg>take a first stab at implementing the hard drop<commit_after>use piston_window::*;
use piston_window::types::Color;

use board::Board;
use randomizer::Randomizer;
use tetromino::*;
use settings::*;
use stats::*;

enum GameState {
    Playing,
    Paused
}

pub trait Game {
    fn on_input(&mut self, input: Input);
    fn on_update(&mut self, update_args: UpdateArgs);
    fn render(&mut self, context: Context, graphics: &mut G2d);
}

pub trait Renderer {
    fn render(&self, context: Context, graphics: &mut G2d);
}

pub trait GridRenderer {
    fn render(&self, x: i32, y: i32, context: Context, graphics: &mut G2d);
}

pub struct Rustris {
    board: Board,
    randomizer: Randomizer,
    current_piece: Piece,
    next_piece: Piece,
    stats: GameStats,
    time_since_moved: f64,
    time_per_tick: f64
    // state: GameState
}
impl Rustris {
    pub fn new() -> Rustris {
        let mut randomizer = Randomizer::new();
        let current_piece = randomizer.create_piece();
        let next_piece = randomizer.create_piece();
        Rustris {
            board: Board::new(),
            randomizer: randomizer,
            current_piece: current_piece,
            next_piece: next_piece,
            stats: GameStats::new(),
            time_since_moved: 0.0,
            time_per_tick: 1.0  //For testing
        }
    }

    pub fn reset(&mut self) {

    }

    pub fn get_game_stats(&self) -> &GameStats {
        &self.stats
    }

    pub fn set_current_piece(&mut self, piece: Piece) {
        self.current_piece = piece;
    }

    fn is_valid_board_position(&self, piece: &Piece) -> bool {
        // TODO: Add tests for this
        piece.blocks_iter().all(|block| {
            (block.x >= 0 && block.x < WIDTH_IN_BLOCKS &&
             block.y >= 0 && block.y < HEIGHT_IN_BLOCKS &&
             !self.board.is_space_occupied(block))
        })
    }

    fn should_lock(&self, piece: &Piece) -> bool {
        // TODO: Add tests for this
        piece.blocks_iter().any(|block| {
            block.y >= HEIGHT_IN_BLOCKS ||
            self.board.is_space_occupied(block)
        })
    }

    fn lock_current_piece(&mut self) {
        self.board.set_piece(&self.current_piece);
    }

    fn remove_completed_lines(&mut self) {
        let number_removed = self.board.remove_completed_rows();
        self.stats.score_completed_lines(number_removed);
    }

    fn calculate_ghost_piece(&self) -> Piece {
        let mut ghost = self.current_piece;
        ghost.y = HEIGHT_IN_BLOCKS - 1;
        while !self.is_valid_board_position(&ghost) {
            ghost.y -= 1;
        }
        ghost
    }

    fn get_new_piece(&mut self) {
        let next = self.next_piece;
        self.set_current_piece(next);
        self.next_piece = self.randomizer.create_piece();
    }

    fn update(&mut self) {
        let moved = self.current_piece.moved(Direction::Down);
        if self.should_lock(&moved) {
            self.lock_current_piece();
            self.remove_completed_lines();
            self.get_new_piece();
        } else {
            self.set_current_piece(moved);
        }
    }
}
impl Game for Rustris {
    fn on_input(&mut self, input: Input) {
        let mut moved: Option<Piece> = None;
        match input {
            Input::Press(key) => {
                match key {
                    Button::Keyboard(Key::Up) => {
                        moved = Some(self.current_piece.rotated());
                    }
                    Button::Keyboard(Key::Down) => {
                        let move_down = self.current_piece.moved(Direction::Down);
                        if self.is_valid_board_position(&move_down) {
                            self.time_since_moved = 0.0;
                            self.stats.score_soft_drop();
                            self.set_current_piece(move_down);
                        }
                    }
                    Button::Keyboard(Key::Left) => {
                        moved = Some(self.current_piece.moved(Direction::Left));
                    }
                    Button::Keyboard(Key::Right) => {
                        moved = Some(self.current_piece.moved(Direction::Right));
                    }
                    Button::Keyboard(Key::Space) => {
                        let ghost = self.calculate_ghost_piece();
                        let rows_dropped = (ghost.y - self.current_piece.y) as u32;
                        self.stats.score_hard_drop(rows_dropped);
                        self.set_current_piece(ghost);
                        self.lock_current_piece();
                        self.remove_completed_lines();
                        self.get_new_piece();
                    }
                    _ => {}
                }
            }
            _ => {}
        }
        if let Some(piece) = moved {
            if self.is_valid_board_position(&piece) {
                self.set_current_piece(piece);
            }
        }
    }

    fn on_update(&mut self, update_args: UpdateArgs) {
        self.time_since_moved += update_args.dt;
        // TODO: The threshold needs to be able to be updated
        // as the game progresses
        if self.time_since_moved >= self.time_per_tick {
            self.time_since_moved -= self.time_per_tick;
            self.update();
        }
    }

    fn render(&mut self, context: Context, graphics: &mut G2d) {
        self.board.render(context, graphics);
        self.current_piece.render(context, graphics);
        self.calculate_ghost_piece().render(context, graphics);
    }
}

// TODO: Move this into its own module
pub fn render_square_in_grid(x: i32, y: i32, color: Color, context: Context, graphics: &mut G2d) {
    let square = rectangle::square(
        0.0, 0.0, BLOCK_SIZE - (2.0 * GRID_LINE_WIDTH)
    );
    let transform = context.transform
        // Translate the origin of the transform based on where the grid starts
        .trans(GRID_X_OFFSET, GRID_Y_OFFSET)
        // Translate the square into the first grid cell (off of the bounding box)
        .trans(GRID_LINE_WIDTH, GRID_LINE_WIDTH)
        // Translate the square into the proper grid cell based on the grid x and y position
        .trans((x as f64)*BLOCK_SIZE, (y as f64)*BLOCK_SIZE);
    rectangle(color, square, transform, graphics);
}
<|endoftext|>


<commit_before>use std::collections::HashMap;

use entity::{Entity, EntityDetails};
use id::{Id, get_id};
use player::Player;
use timestamp::get_timestamp;
use zone::{Zone, ZoneKind};

/// Represents the game's current phase
#[derive(Debug, Clone, PartialEq)]
pub enum GamePhase {
    Main,

    // TODO: Other game phases
}

#[derive(Debug, Clone, PartialEq)]
pub enum GameStatus {
    Processing,
    NeedsPlayerAction,

    // TODO: GameEnded? Need to represent win/draw and potentially error
}

/// Defines all of the actions that a player can take when they have
#[derive(Debug, Clone)]
pub enum PlayerAction {
    PassPriority,
    PlayLand {
        entity_id: Id,
    },

    // TODO: Other possible player actions

    // Some potentially interesting actions:
    // * Concede (doesn't need priority)
    // * Flip a morph card (doesn't need priority)
}

/// Will be used to define mutations to the game state. Mutations are defined as
/// objects so that effects can respond to and replace them.
#[derive(Debug, Clone)]
pub enum GameMutation {
}

/// Represents all of the important serializable information about a game.
///
/// `Game` cycles between two primary statuses:
/// * `GameStatus::Processing` -- the game is currently processing
/// * `GameStatus::NeedsPlayerAction` -- the game requires a response from the
///   player who has priority, defined by `priority_player`.
///
/// All mutation to the game will come from `PlayerAction` and `GameMutation`
/// objects, which will be filtered by effects created by the game's rules.
#[derive(Debug, Clone)]
pub struct Game {
    pub zones: HashMap<Id, Zone>,
    pub players: HashMap<Id, Player>,

    /// The base definition of each entity in the game, before being modified by
    /// the currently active effects.
    pub entities: HashMap<Id, Entity>,

    /// The order that players have their turns in.
    pub player_turn_order: Vec<Id>,

    /// The player whose turn it is right now.
    ///
    /// I'm not sure if there's a game state in which there is no active player,
    /// but it simplifies initialization of `Game`!
    pub active_player: Option<Id>,

    /// The player who currently has priority.
    ///
    /// In certain parts of the game, like resolution of state-based-actions and
    /// the untap step, no players have priority.
    pub priority_player: Option<Id>,

    /// The current phase of the game, as defined in the game rules.
    pub current_phase: GamePhase,

    /// The current game status, defining the core interaction loop.
    pub current_status: GameStatus,

    // TODO: A reference to an entity descriptor pool, like what cards are legal
    //       in this format.
    // TODO: The stack, a Vec<Entity>?
    // TODO: A log of player actions
    // TODO: A list of currently active effects and their durations
}

impl Game {
    /// Process the given player action.
    pub fn do_player_action(&mut self, acting_player_id: Id, action: &PlayerAction) {
        // We're busy, no players can act right now!
        if self.current_status == GameStatus::Processing {
            return;
        }

        // Players can only act (right now) when they have priority
        match self.priority_player {
            Some(priority_id) => {
                if acting_player_id != priority_id {
                    return;
                }
            },
            None => return,
        }

        // We'll break the actual action handling into a private routine
        // eventually
        match *action {
            PlayerAction::PassPriority => {
                let current_priority_index = self.player_turn_order
                    .iter()
                    .position(|&id| id == acting_player_id)
                    .expect("Player with priority is missing from player_turn_order!");

                let current_active_id = self.active_player.unwrap();
                let current_active_index = self.player_turn_order
                    .iter()
                    .position(|&id| id == current_active_id)
                    .expect("Player with priority is missing from player_turn_order!");

                let player_count = self.player_turn_order.len();

                let next_priority_index = (current_priority_index + 1) % player_count;
                let next_priority_id = self.player_turn_order[next_priority_index];

                // If priority would swing back around to the active player,
                // it's time to advance the turn cycle.
                if next_priority_id == current_active_id {
                    let next_active_index = (current_active_index + 1) % player_count;
                    let next_active_id = self.player_turn_order[next_active_index];

                    self.active_player = Some(next_active_id);
                    self.priority_player = Some(next_active_id);
                } else {
                    self.priority_player = Some(next_priority_id);
                }
            },
            PlayerAction::PlayLand { entity_id } => {
                // TODO: Players can only play lands when it's their turn
                // TODO: Players can only play lands during a main phase
                // TODO: Players can only play lands when the stack is empty

                let player_hand_id = self
                    .find_zone_id(|zone| {
                        match zone.kind {
                            ZoneKind::Hand { player_id } => player_id == acting_player_id,
                            _ => false,
                        }
                    })
                    .expect("Unable to locate player's hand!");

                let battlefield_id = self
                    .find_zone_id(|zone| {
                        match zone.kind {
                            ZoneKind::Battlefield => true,
                            _ => false,
                        }
                    })
                    .expect("Unable to locate battlefield!");

                // We need to make sure we have a land to play!
                match self.entities.get(&entity_id) {
                    Some(entity) => {
                        // Make sure it's in our player's hand
                        if entity.zone != player_hand_id {
                            return;
                        }

                        // Make sure it's a land
                        match entity.details {
                            EntityDetails::Forest => {},
                            // TODO: Other entity details, when they're introduced
                        }
                    }
                    None => return,
                }

                // We just checked to make sure this existed above!
                let entity = self.entities.remove(&entity_id).unwrap();

                // For now, let's just generate a new entity and put it on the
                // battlefield.
                let new_entity = Entity {
                    id: get_id(),
                    zone: battlefield_id,
                    timestamp: get_timestamp(),
                    details: entity.details,
                };

                // TODO: Use GameMutation instead?
                self.entities.insert(new_entity.id, new_entity);
            },
        }
    }

    /// Finds the zone that passes the given condition, if it exists.
    pub fn find_zone_id<F>(&self, predicate: F) -> Option<Id>
    where
        F: Fn(&Zone) -> bool
    {
        self.zones.values()
            .find(|zone| predicate(zone))
            .and_then(|zone| Some(zone.id))
    }

    /// Create a version of `Game` as viewed by the given player. This
    /// should collapse hidden zones and unknown information. Hopefully, it's
    /// also cheap, since the most naive way to implement client communication
    /// would be to send a full state on every change!
    pub fn view_as_player(&self, _player_id: Id) -> Game {
        // TODO: Hide entities that player has no knowledge of, like face-down
        //       permanents.

        // TODO: Hide zones that player has no knowledge of, like libraries, and
        //       face-down exile zones created by cards like Bomat Courier.

        self.clone()
    }

    /// Queries an entity in the game by ID.
    ///
    /// Entity representation may be unintuitive -- the `Entity` objects stored
    /// in the `Game` only contain their base state. When querying an
    /// entity's current state, we need to traverse a list of effects currently
    /// modifying that object in layer order.
    ///
    /// As an example, consider a Mountain enchanted by Spreading Seas:
    ///
    /// The definition of Mountain says that it has type `Basic Land - Mountain`
    /// and thus has `{T}: Add {R} to your mana pool`.
    ///
    /// Spreading Seas, when on the battlefield, has a replacement effect for
    /// that removes its target's abilities and changes its type line to `Basic
    /// Land - Island`.
    ///
    /// The mountain's current state (post-effect) thus says that it has type
    /// `Basic Land - Island` and taps for blue mana only!
    ///
    /// If we wanted to return the Mountain entity directly, we'd either have to
    /// modify the data whenever an effect enters/leaves the battlefield or just
    /// keep a list of active effects and calculate them on each observation.
    ///
    /// While the former technique is possible, I think that calculating effects
    /// on each individual observation event is simpler.
    pub fn view_entity(&self, entity_id: Id) -> Option<Entity> {
        let base_entity = self.entities.get(&entity_id)?;

        // TODO: Enumerate the set of effects that could affect this entity.

        Some(base_entity.clone())
    }
}
<commit_msg>Add 'process_state_based_actions' stub<commit_after>use std::collections::HashMap;

use entity::{Entity, EntityDetails};
use id::{Id, get_id};
use player::Player;
use timestamp::get_timestamp;
use zone::{Zone, ZoneKind};

/// Represents the game's current phase
#[derive(Debug, Clone, PartialEq)]
pub enum GamePhase {
    Main,

    // TODO: Other game phases
}

#[derive(Debug, Clone, PartialEq)]
pub enum GameStatus {
    Processing,
    NeedsPlayerAction,

    // TODO: GameEnded? Need to represent win/draw and potentially error
}

/// Defines all of the actions that a player can take when they have
#[derive(Debug, Clone)]
pub enum PlayerAction {
    PassPriority,
    PlayLand {
        entity_id: Id,
    },

    // TODO: Other possible player actions

    // Some potentially interesting actions:
    // * Concede (doesn't need priority)
    // * Flip a morph card (doesn't need priority)
}

/// Will be used to define mutations to the game state. Mutations are defined as
/// objects so that effects can respond to and replace them.
#[derive(Debug, Clone)]
pub enum GameMutation {
}

/// Represents all of the important serializable information about a game.
///
/// `Game` cycles between two primary statuses:
/// * `GameStatus::Processing` -- the game is currently processing
/// * `GameStatus::NeedsPlayerAction` -- the game requires a response from the
///   player who has priority, defined by `priority_player`.
///
/// All mutation to the game will come from `PlayerAction` and `GameMutation`
/// objects, which will be filtered by effects created by the game's rules.
#[derive(Debug, Clone)]
pub struct Game {
    pub zones: HashMap<Id, Zone>,
    pub players: HashMap<Id, Player>,

    /// The base definition of each entity in the game, before being modified by
    /// the currently active effects.
    pub entities: HashMap<Id, Entity>,

    /// The order that players have their turns in.
    pub player_turn_order: Vec<Id>,

    /// The player whose turn it is right now.
    ///
    /// I'm not sure if there's a game state in which there is no active player,
    /// but it simplifies initialization of `Game`!
    pub active_player: Option<Id>,

    /// The player who currently has priority.
    ///
    /// In certain parts of the game, like resolution of state-based-actions and
    /// the untap step, no players have priority.
    pub priority_player: Option<Id>,

    /// The current phase of the game, as defined in the game rules.
    pub current_phase: GamePhase,

    /// The current game status, defining the core interaction loop.
    pub current_status: GameStatus,

    // TODO: A reference to an entity descriptor pool, like what cards are legal
    //       in this format.
    // TODO: The stack, a Vec<Entity>?
    // TODO: A log of player actions
    // TODO: A list of currently active effects and their durations
}

impl Game {
    /// Process the given player action.
    pub fn do_player_action(&mut self, acting_player_id: Id, action: &PlayerAction) {
        // We're busy, no players can act right now!
        if self.current_status == GameStatus::Processing {
            return;
        }

        // Players can only act (right now) when they have priority
        match self.priority_player {
            Some(priority_id) => {
                if acting_player_id != priority_id {
                    return;
                }
            },
            None => return,
        }

        // We'll break the actual action handling into a private routine
        // eventually
        match *action {
            PlayerAction::PassPriority => {
                let current_priority_index = self.player_turn_order
                    .iter()
                    .position(|&id| id == acting_player_id)
                    .expect("Player with priority is missing from player_turn_order!");

                let current_active_id = self.active_player.unwrap();
                let current_active_index = self.player_turn_order
                    .iter()
                    .position(|&id| id == current_active_id)
                    .expect("Player with priority is missing from player_turn_order!");

                let player_count = self.player_turn_order.len();

                let next_priority_index = (current_priority_index + 1) % player_count;
                let next_priority_id = self.player_turn_order[next_priority_index];

                // If priority would swing back around to the active player,
                // it's time to advance the turn cycle.
                if next_priority_id == current_active_id {
                    let next_active_index = (current_active_index + 1) % player_count;
                    let next_active_id = self.player_turn_order[next_active_index];

                    self.active_player = Some(next_active_id);
                    self.priority_player = Some(next_active_id);
                } else {
                    self.priority_player = Some(next_priority_id);
                }
            },
            PlayerAction::PlayLand { entity_id } => {
                // TODO: Players can only play lands when it's their turn
                // TODO: Players can only play lands during a main phase
                // TODO: Players can only play lands when the stack is empty

                let player_hand_id = self
                    .find_zone_id(|zone| {
                        match zone.kind {
                            ZoneKind::Hand { player_id } => player_id == acting_player_id,
                            _ => false,
                        }
                    })
                    .expect("Unable to locate player's hand!");

                let battlefield_id = self
                    .find_zone_id(|zone| {
                        match zone.kind {
                            ZoneKind::Battlefield => true,
                            _ => false,
                        }
                    })
                    .expect("Unable to locate battlefield!");

                // We need to make sure we have a land to play!
                match self.entities.get(&entity_id) {
                    Some(entity) => {
                        // Make sure it's in our player's hand
                        if entity.zone != player_hand_id {
                            return;
                        }

                        // Make sure it's a land
                        match entity.details {
                            EntityDetails::Forest => {},
                            // TODO: Other entity details, when they're introduced
                        }
                    }
                    None => return,
                }

                // We just checked to make sure this existed above!
                let entity = self.entities.remove(&entity_id).unwrap();

                // For now, let's just generate a new entity and put it on the
                // battlefield.
                let new_entity = Entity {
                    id: get_id(),
                    zone: battlefield_id,
                    timestamp: get_timestamp(),
                    details: entity.details,
                };

                // TODO: Use GameMutation instead?
                self.entities.insert(new_entity.id, new_entity);
            },
        }
    }

    /// Apply all state-based actions, like creatures dying due to damage.
    pub fn process_state_based_actions(&mut self) {
        // TODO: State based actions!
    }

    /// Finds the zone that passes the given condition, if it exists.
    pub fn find_zone_id<F>(&self, predicate: F) -> Option<Id>
    where
        F: Fn(&Zone) -> bool
    {
        self.zones.values()
            .find(|zone| predicate(zone))
            .and_then(|zone| Some(zone.id))
    }

    /// Create a version of `Game` as viewed by the given player. This
    /// should collapse hidden zones and unknown information. Hopefully, it's
    /// also cheap, since the most naive way to implement client communication
    /// would be to send a full state on every change!
    pub fn view_as_player(&self, _player_id: Id) -> Game {
        // TODO: Hide entities that player has no knowledge of, like face-down
        //       permanents.

        // TODO: Hide zones that player has no knowledge of, like libraries, and
        //       face-down exile zones created by cards like Bomat Courier.

        self.clone()
    }

    /// Queries an entity in the game by ID.
    ///
    /// Entity representation may be unintuitive -- the `Entity` objects stored
    /// in the `Game` only contain their base state. When querying an
    /// entity's current state, we need to traverse a list of effects currently
    /// modifying that object in layer order.
    ///
    /// As an example, consider a Mountain enchanted by Spreading Seas:
    ///
    /// The definition of Mountain says that it has type `Basic Land - Mountain`
    /// and thus has `{T}: Add {R} to your mana pool`.
    ///
    /// Spreading Seas, when on the battlefield, has a replacement effect for
    /// that removes its target's abilities and changes its type line to `Basic
    /// Land - Island`.
    ///
    /// The mountain's current state (post-effect) thus says that it has type
    /// `Basic Land - Island` and taps for blue mana only!
    ///
    /// If we wanted to return the Mountain entity directly, we'd either have to
    /// modify the data whenever an effect enters/leaves the battlefield or just
    /// keep a list of active effects and calculate them on each observation.
    ///
    /// While the former technique is possible, I think that calculating effects
    /// on each individual observation event is simpler.
    pub fn view_entity(&self, entity_id: Id) -> Option<Entity> {
        let base_entity = self.entities.get(&entity_id)?;

        // TODO: Enumerate the set of effects that could affect this entity.

        Some(base_entity.clone())
    }
}
<|endoftext|>