<commit_before>extern crate rustc_serialize;
extern crate libc;
#[macro_use] extern crate log;
extern crate regex;
extern crate shaman;
extern crate argparse;
extern crate quire;
#[macro_use] extern crate lithos;

use std::env;
use std::io::{stderr, Read, Write};
use std::io::Error as IoError;
use std::process::exit;
use std::path::{Path, PathBuf};
use std::path::Component::Normal;
use std::str::FromStr;
use std::ffi::{OsStr, OsString};
use std::fs::{File};
use std::fs::{copy, rename, metadata, read_link};
use std::os::unix::fs::symlink;
use std::os::unix::ffi::OsStrExt;
use std::default::Default;
use std::process::{Command, Stdio};

use shaman::digest::Digest;
use argparse::{ArgumentParser, Parse, ParseOption, StoreTrue};
use quire::parse_config;

use lithos::master_config::MasterConfig;
use lithos::tree_config::TreeConfig;
use lithos::signal;

fn hash_file(file: &Path) -> Result<String, IoError> {
    let mut hash = shaman::sha2::Sha256::new();
    // We assume that file is small enough so we don't care reading it
    // to memory
    let mut buf = Vec::with_capacity(1024);
    try!(try!(File::open(file)).read_to_end(&mut buf));
    hash.input(&buf);
    Ok(hash.result_str())
}

fn switch_config(master_cfg: &Path, tree_name: String, config_file: &Path,
    name_prefix: Option<String>)
    -> Result<(), String>
{
    match Command::new(env::current_exe().unwrap()
                       .parent().unwrap().join("lithos_check"))
        .stdin(Stdio::inherit())
        .stdout(Stdio::inherit())
        .stderr(Stdio::inherit())
        .arg("--config")
        .arg(&master_cfg)
        .arg("--tree")
        .arg(&tree_name)
        .arg("--alternate-config")
        .arg(&config_file)
        .output()
    {
        Ok(ref po) if po.status.code() == Some(0) => { }
        Ok(ref po) => {
            return Err(format!(
                "Configuration check failed with exit status: {}",
                po.status));
        }
        Err(e) => {
            return Err(format!("Can't check configuration: {}", e));
        }
    }
    info!("Checked. Proceeding");

    let master: MasterConfig = match parse_config(&master_cfg,
        &*MasterConfig::validator(), Default::default())
    {
        Ok(cfg) => cfg,
        Err(e) => {
            return Err(format!("Can't parse master config: {}", e));
        }
    };
    let tree_fn = master.config_dir.join(tree_name + ".yaml");
    let tree: TreeConfig = match parse_config(&tree_fn,
        &*TreeConfig::validator(), Default::default())
    {
        Ok(cfg) => cfg,
        Err(e) => {
            return Err(format!("Can't parse tree config: {}", e));
        }
    };

    let config_fn: OsString = if let Some(prefix) = name_prefix {
        let hash = try!(hash_file(&config_file)
            .map_err(|e| format!("Can't read file: {}", e)));
        OsString::from(prefix + &hash + ".yaml")
    } else {
        config_file.file_name().unwrap().to_owned()
    };
    let target_fn = tree.config_file.parent().unwrap()
                    .join(&config_fn);
    if target_fn == tree.config_file {
        return Err(format!("Target config file ({:?}) \
            is the current file ({:?}). You must have unique \
            name for each new configuration",
            target_fn, tree.config_file));
    }
    let lnk = try!(read_link(&tree.config_file)
       .map_err(|e| format!("Can't read link {:?}: {}", tree.config_file, e)));
    match lnk.components().rev().nth(1) {
        Some(Normal(_)) => {},
        _ => return Err(format!("The path {:?} must be a symlink \
            which points to the file at the same level of hierarchy.",
            tree.config_file)),
    };
    if lnk.file_name() == target_fn.file_name() {
        warn!("Target config dir is the active one. Nothing to do.");
        return Ok(());
    }
    if metadata(&target_fn).is_ok() {
        warn!("Target file {:?} exists. \
            Probably already copied. Skipping copying step.",
            target_fn);
    } else {
        info!("Seems everything nice. Copying");
        try!(copy(&config_file, &target_fn)
            .map_err(|e| format!("Error copying: {}", e)));
    }
    info!("Ok files are there. Making symlink");
    let mut tmpname = OsStr::from_bytes(b".tmp.").to_owned();
    tmpname.push(tree.config_file.file_name().unwrap());
    let symlink_fn = tree.config_file.with_file_name(tmpname);
    try!(symlink(&Path::new(&config_fn), &symlink_fn)
        .map_err(|e| format!("Error symlinking dir: {}", e)));
    try!(rename(&symlink_fn, &tree.config_file)
        .map_err(|e| format!("Error replacing symlink: {}", e)));

    info!("Done. Sending SIGQUIT to lithos_tree");
    let pid_file = master.runtime_dir.join("master.pid");
    let mut buf = String::with_capacity(50);
    let read = File::open(&pid_file)
            .and_then(|mut f| f.read_to_string(&mut buf))
            .ok();
    match read.and_then(|_| FromStr::from_str(&buf).ok()) {
        Some(pid) if signal::is_process_alive(pid) => {
            signal::send_signal(pid, signal::SIGQUIT);
        }
        Some(pid) => {
            warn!("Process with pid {} is not running...", pid);
        }
        None => {
            warn!("Can't read pid file {}. Probably daemon is not running.",
                pid_file.display());
        }
    };

    return Ok(());
}

fn main() {
    let mut master_config = PathBuf::from("/etc/lithos.yaml");
    let mut verbose = false;
    let mut name_prefix = None;
    let mut config_file = PathBuf::from("");
    let mut tree_name = "".to_string();
    {
        let mut ap = ArgumentParser::new();
        ap.set_description("Checks if lithos configuration is ok");
        ap.refer(&mut master_config)
          .add_option(&["--master"], Parse,
            "Name of the master configuration file (default /etc/lithos.yaml)")
          .metavar("FILE");
        ap.refer(&mut verbose)
          .add_option(&["-v", "--verbose"], StoreTrue,
            "Verbose configuration");
        ap.refer(&mut name_prefix)
          .add_option(&["--hashed-name"], ParseOption, "
            Do not use last component of FILE as a name, but create an unique
            name based on the PREFIX and hash of the contents.
            ")
          .metavar("PREFIX");
        ap.refer(&mut tree_name)
          .add_argument("tree", Parse,
            "Name of the tree which configuration will be switched for")
          .required()
          .metavar("NAME");
        ap.refer(&mut config_file)
          .add_argument("new_config", Parse, "
            Name of the configuration directory to switch to. It doesn't
            have to be a directory inside `config-dir`, and it will be copied
            there. However, if directory with the same name exists in the
            `config-dir` it's assumed that it's already copied and will not
            be updated. Be sure to use unique directory for each deployment.
            ")
          .metavar("FILE")
          .required();
        match ap.parse_args() {
            Ok(()) => {}
            Err(x) => {
                exit(x);
            }
        }
    }
    match switch_config(&master_config, tree_name, &config_file, name_prefix)
    {
        Ok(()) => {
            exit(0);
        }
        Err(e) => {
            write!(&mut stderr(), "Fatal error: {}\n", e).unwrap();
            exit(1);
        }
    }
}
<commit_msg>Fix switch script<commit_after>extern crate rustc_serialize;
extern crate libc;
#[macro_use] extern crate log;
extern crate env_logger;
extern crate regex;
extern crate shaman;
extern crate argparse;
extern crate quire;
#[macro_use] extern crate lithos;

use std::env;
use std::io::{stderr, Read, Write};
use std::io::Error as IoError;
use std::process::exit;
use std::path::{Path, PathBuf};
use std::path::Component;
use std::str::FromStr;
use std::ffi::{OsStr, OsString};
use std::fs::{File};
use std::fs::{copy, rename, metadata, read_link};
use std::os::unix::fs::symlink;
use std::os::unix::ffi::OsStrExt;
use std::default::Default;
use std::process::{Command, Stdio};

use shaman::digest::Digest;
use argparse::{ArgumentParser, Parse, ParseOption, StoreTrue};
use quire::parse_config;

use lithos::master_config::MasterConfig;
use lithos::tree_config::TreeConfig;
use lithos::signal;

fn hash_file(file: &Path) -> Result<String, IoError> {
    let mut hash = shaman::sha2::Sha256::new();
    // We assume that file is small enough so we don't care reading it
    // to memory
    let mut buf = Vec::with_capacity(1024);
    try!(try!(File::open(file)).read_to_end(&mut buf));
    hash.input(&buf);
    Ok(hash.result_str())
}

fn switch_config(master_cfg: &Path, tree_name: String, config_file: &Path,
    name_prefix: Option<String>)
    -> Result<(), String>
{
    match Command::new(env::current_exe().unwrap()
                       .parent().unwrap().join("lithos_check"))
        .stdin(Stdio::inherit())
        .stdout(Stdio::inherit())
        .stderr(Stdio::inherit())
        .arg("--config")
        .arg(&master_cfg)
        .arg("--tree")
        .arg(&tree_name)
        .arg("--alternate-config")
        .arg(&config_file)
        .output()
    {
        Ok(ref po) if po.status.code() == Some(0) => { }
        Ok(ref po) => {
            return Err(format!(
                "Configuration check failed with exit status: {}",
                po.status));
        }
        Err(e) => {
            return Err(format!("Can't check configuration: {}", e));
        }
    }
    info!("Checked. Proceeding");

    let master: MasterConfig = match parse_config(&master_cfg,
        &*MasterConfig::validator(), Default::default())
    {
        Ok(cfg) => cfg,
        Err(e) => {
            return Err(format!("Can't parse master config: {}", e));
        }
    };
    let tree_fn = master.config_dir.join(tree_name + ".yaml");
    let tree: TreeConfig = match parse_config(&tree_fn,
        &*TreeConfig::validator(), Default::default())
    {
        Ok(cfg) => cfg,
        Err(e) => {
            return Err(format!("Can't parse tree config: {}", e));
        }
    };

    let config_fn: OsString = if let Some(prefix) = name_prefix {
        let hash = try!(hash_file(&config_file)
            .map_err(|e| format!("Can't read file: {}", e)));
        OsString::from(prefix + &hash + ".yaml")
    } else {
        config_file.file_name().unwrap().to_owned()
    };
    let target_fn = tree.config_file.parent().unwrap()
                    .join(&config_fn);
    if target_fn == tree.config_file {
        return Err(format!("Target config file ({:?}) \
            is the current file ({:?}). You must have unique \
            name for each new configuration",
            target_fn, tree.config_file));
    }
    let lnk = try!(read_link(&tree.config_file)
       .map_err(|e| format!("Can't read link {:?}: {}", tree.config_file, e)));
    let mut cmp_iter = lnk.components();
    match (cmp_iter.next(), cmp_iter.next()) {
        (Some(Component::Normal(_)), None) => {},
        _ => return Err(format!("The path {:?} must be a symlink \
            which points to the file at the same level of hierarchy.",
            tree.config_file)),
    };
    if lnk.file_name() == target_fn.file_name() {
        warn!("Target config dir is the active one. Nothing to do.");
        return Ok(());
    }
    if metadata(&target_fn).is_ok() {
        warn!("Target file {:?} exists. \
            Probably already copied. Skipping copying step.",
            target_fn);
    } else {
        info!("Seems everything nice. Copying");
        try!(copy(&config_file, &target_fn)
            .map_err(|e| format!("Error copying: {}", e)));
    }
    info!("Ok files are there. Making symlink");
    let mut tmpname = OsStr::from_bytes(b".tmp.").to_owned();
    tmpname.push(tree.config_file.file_name().unwrap());
    let symlink_fn = tree.config_file.with_file_name(tmpname);
    try!(symlink(&Path::new(&config_fn), &symlink_fn)
        .map_err(|e| format!("Error symlinking dir: {}", e)));
    try!(rename(&symlink_fn, &tree.config_file)
        .map_err(|e| format!("Error replacing symlink: {}", e)));

    info!("Done. Sending SIGQUIT to lithos_tree");
    let pid_file = master.runtime_dir.join("master.pid");
    let mut buf = String::with_capacity(50);
    let read = File::open(&pid_file)
            .and_then(|mut f| f.read_to_string(&mut buf))
            .ok();
    match read.and_then(|_| FromStr::from_str(&buf).ok()) {
        Some(pid) if signal::is_process_alive(pid) => {
            signal::send_signal(pid, signal::SIGQUIT);
        }
        Some(pid) => {
            warn!("Process with pid {} is not running...", pid);
        }
        None => {
            warn!("Can't read pid file {}. Probably daemon is not running.",
                pid_file.display());
        }
    };

    return Ok(());
}

fn main() {
    if env::var("RUST_LOG").is_err() {
        env::set_var("RUST_LOG", "warn");
    }
    env_logger::init().unwrap();

    let mut master_config = PathBuf::from("/etc/lithos.yaml");
    let mut verbose = false;
    let mut name_prefix = None;
    let mut config_file = PathBuf::from("");
    let mut tree_name = "".to_string();
    {
        let mut ap = ArgumentParser::new();
        ap.set_description("Checks if lithos configuration is ok");
        ap.refer(&mut master_config)
          .add_option(&["--master"], Parse,
            "Name of the master configuration file (default /etc/lithos.yaml)")
          .metavar("FILE");
        ap.refer(&mut verbose)
          .add_option(&["-v", "--verbose"], StoreTrue,
            "Verbose configuration");
        ap.refer(&mut name_prefix)
          .add_option(&["--hashed-name"], ParseOption, "
            Do not use last component of FILE as a name, but create an unique
            name based on the PREFIX and hash of the contents.
            ")
          .metavar("PREFIX");
        ap.refer(&mut tree_name)
          .add_argument("tree", Parse,
            "Name of the tree which configuration will be switched for")
          .required()
          .metavar("NAME");
        ap.refer(&mut config_file)
          .add_argument("new_config", Parse, "
            Name of the configuration directory to switch to. It doesn't
            have to be a directory inside `config-dir`, and it will be copied
            there. However, if directory with the same name exists in the
            `config-dir` it's assumed that it's already copied and will not
            be updated. Be sure to use unique directory for each deployment.
            ")
          .metavar("FILE")
          .required();
        match ap.parse_args() {
            Ok(()) => {}
            Err(x) => {
                exit(x);
            }
        }
    }
    match switch_config(&master_config, tree_name, &config_file, name_prefix)
    {
        Ok(()) => {
            exit(0);
        }
        Err(e) => {
            write!(&mut stderr(), "Fatal error: {}\n", e).unwrap();
            exit(1);
        }
    }
}
<|endoftext|>


<commit_before>extern crate etcd;

use std::thread::{sleep, spawn};
use std::time::Duration;

use etcd::{Client, Error};

/// Wrapper around Client that automatically cleans up etcd after each test.
struct TestClient {
    c: Client,
}

impl TestClient {
    /// Creates a new client for a test.
    fn new() -> TestClient {
        TestClient {
            c: Client::new("http://etcd:2379").unwrap(),
        }
    }
}

impl Drop for TestClient {
    #[allow(unused_must_use)]
    fn drop(&mut self) {
        self.c.delete("/test", true);
    }
}

#[test]
fn create() {
    let client = TestClient::new();

    let response = match client.c.create("/test/foo", "bar", Some(60)) {
        Ok(response) => response,
        Err(error) => panic!("{:?}", error),
    };

    assert_eq!(response.action, "create".to_string());
    assert_eq!(response.node.value.unwrap(), "bar".to_string());
    assert_eq!(response.node.ttl.unwrap(), 60);
}

#[test]
fn create_does_not_replace_existing_key() {
    let client = TestClient::new();

    client.c.create("/test/foo", "bar", Some(60)).ok().unwrap();

    match client.c.create("/test/foo", "bar", None).err().unwrap() {
        Error::Etcd(error) => assert_eq!(error.message, "Key already exists".to_string()),
        _ => panic!("expected EtcdError due to pre-existing key"),
    };
}

#[test]
fn create_in_order() {
    let client = TestClient::new();

    let keys: Vec<String> = (1..4).map(|ref _key| {
        client.c.create_in_order(
            "/test/foo",
            "bar",
            None,
        ).ok().unwrap().node.key.unwrap()
    }).collect();

    assert!(keys[0] < keys[1]);
    assert!(keys[1] < keys[2]);
}

#[test]
fn create_in_order_must_operate_on_a_directory() {
    let client = TestClient::new();

    client.c.create("/test/foo", "bar", None).ok().unwrap();

    assert!(client.c.create_in_order("/test/foo", "baz", None).is_err());
}

#[test]
fn compare_and_delete() {
    let client = TestClient::new();

    let modified_index = client.c.create(
        "/test/foo",
        "bar",
        None
    ).ok().unwrap().node.modified_index.unwrap();

    let response = client.c.compare_and_delete(
        "/test/foo",
        Some("bar"),
        Some(modified_index)
    ).ok().unwrap();

    assert_eq!(response.action, "compareAndDelete".to_string());
}

#[test]
fn compare_and_delete_only_index() {
    let client = TestClient::new();

    let modified_index = client.c.create(
        "/test/foo",
        "bar",
        None
    ).ok().unwrap().node.modified_index.unwrap();

    let response = client.c.compare_and_delete(
        "/test/foo",
        None,
        Some(modified_index)
    ).ok().unwrap();

    assert_eq!(response.action, "compareAndDelete".to_string());
}

#[test]
fn compare_and_delete_only_value() {
    let client = TestClient::new();
    client.c.create("/test/foo", "bar", None).ok().unwrap();

    let response = client.c.compare_and_delete(
        "/test/foo",
        Some("bar"),
        None,
    ).ok().unwrap();

    assert_eq!(response.action, "compareAndDelete".to_string());
}

#[test]
fn compare_and_delete_requires_conditions() {
    let client = TestClient::new();
    client.c.create("/test/foo", "bar", None).ok().unwrap();

    match client.c.compare_and_delete("/test/foo", None, None).err().unwrap() {
        Error::InvalidConditions(message) => assert_eq!(
            message,
            "Current value or modified index is required."
        ),
        _ => panic!("expected Error::InvalidConditions"),
    }
}

#[test]
fn compare_and_swap() {
    let client = TestClient::new();

    let modified_index = client.c.create(
        "/test/foo",
        "bar",
        None
    ).ok().unwrap().node.modified_index.unwrap();

    let response = client.c.compare_and_swap(
        "/test/foo",
        "baz",
        Some(100),
        Some("bar"),
        Some(modified_index)
    ).ok().unwrap();

    assert_eq!(response.action, "compareAndSwap".to_string());
}

#[test]
fn compare_and_swap_only_index() {
    let client = TestClient::new();

    let modified_index = client.c.create(
        "/test/foo",
        "bar",
        None
    ).ok().unwrap().node.modified_index.unwrap();

    let response = client.c.compare_and_swap(
        "/test/foo",
        "baz",
        None,
        None,
        Some(modified_index)
    ).ok().unwrap();

    assert_eq!(response.action, "compareAndSwap".to_string());
}

#[test]
fn compare_and_swap_only_value() {
    let client = TestClient::new();
    client.c.create("/test/foo", "bar", None).ok().unwrap();

    let response = client.c.compare_and_swap(
        "/test/foo",
        "bar",
        None,
        Some("bar"),
        None,
    ).ok().unwrap();

    assert_eq!(response.action, "compareAndSwap".to_string());
}

#[test]
fn compare_and_swap_requires_conditions() {
    let client = TestClient::new();
    client.c.create("/test/foo", "bar", None).ok().unwrap();

    match client.c.compare_and_swap("/test/foo", "bar", None, None, None).err().unwrap() {
        Error::InvalidConditions(message) => assert_eq!(
            message,
            "Current value or modified index is required."
        ),
        _ => panic!("expected Error::InvalidConditions"),
    }
}

#[test]
fn get() {
    let client = TestClient::new();
    client.c.create("/test/foo", "bar", Some(60)).ok().unwrap();

    let response = client.c.get("/test/foo", false, false, false).ok().unwrap();

    assert_eq!(response.action, "get".to_string());
    assert_eq!(response.node.value.unwrap(), "bar".to_string());
    assert_eq!(response.node.ttl.unwrap(), 60);

}

#[test]
fn get_non_recursive() {
    let client = TestClient::new();

    client.c.set("/test/dir/baz", "blah", None).ok();
    client.c.set("/test/foo", "bar", None).ok();

    let response = client.c.get("/test", true, false, false).ok().unwrap();

    assert_eq!(response.node.dir.unwrap(), true);

    let nodes = response.node.nodes.unwrap();

    assert_eq!(nodes[0].clone().key.unwrap(), "/test/dir".to_string());
    assert_eq!(nodes[0].clone().dir.unwrap(), true);
    assert_eq!(nodes[1].clone().key.unwrap(), "/test/foo".to_string());
    assert_eq!(nodes[1].clone().value.unwrap(), "bar".to_string());
}

#[test]
fn get_recursive() {
    let client = TestClient::new();

    client.c.set("/test/dir/baz", "blah", None).ok();

    let response = client.c.get("/test", true, true, false).ok().unwrap();
    let nodes = response.node.nodes.unwrap();

    assert_eq!(
        nodes[0].clone().nodes.unwrap()[0].clone().value.unwrap(),
        "blah".to_string()
    );
}

#[test]
fn leader_stats() {
    let client = TestClient::new();

    client.c.leader_stats().unwrap();
}

#[test]
fn set() {
    let client = TestClient::new();

    let response = client.c.set("/test/foo", "baz", None).ok().unwrap();

    assert_eq!(response.action, "set".to_string());
    assert_eq!(response.node.value.unwrap(), "baz".to_string());
    assert!(response.node.ttl.is_none());
}

#[test]
fn set_dir() {
    let client = TestClient::new();

    assert!(client.c.set_dir("/test", None).is_ok());
    assert!(client.c.set_dir("/test", None).is_err());

    client.c.set("/test/foo", "bar", None).ok().unwrap();

    assert!(client.c.set_dir("/test/foo", None).is_ok());
}

#[test]
fn update() {
    let client = TestClient::new();
    client.c.create("/test/foo", "bar", None).ok().unwrap();

    let response = client.c.update("/test/foo", "blah", Some(30)).ok().unwrap();

    assert_eq!(response.action, "update".to_string());
    assert_eq!(response.node.value.unwrap(), "blah".to_string());
    assert_eq!(response.node.ttl.unwrap(), 30);
}

#[test]
fn update_requires_existing_key() {
    let client = TestClient::new();

    match client.c.update("/test/foo", "bar", None).err().unwrap() {
        Error::Etcd(error) => assert_eq!(error.message, "Key not found".to_string()),
        _ => panic!("expected EtcdError due to missing key"),
    };
}

#[test]
fn update_dir() {
    let client = TestClient::new();

    client.c.create_dir("/test", None).ok().unwrap();

    let response = client.c.update_dir("/test", Some(60)).ok().unwrap();

    assert_eq!(response.node.ttl.unwrap(), 60);
}

#[test]
fn update_dir_replaces_key() {
    let client = TestClient::new();

    client.c.set("/test/foo", "bar", None).ok().unwrap();

    let response = client.c.update_dir("/test/foo", Some(60)).ok().unwrap();

    assert_eq!(response.node.value.unwrap(), "");
    assert_eq!(response.node.ttl.unwrap(), 60);
}

#[test]
fn update_dir_requires_existing_dir() {
    let client = TestClient::new();

    assert!(client.c.update_dir("/test", None).is_err());
}

#[test]
fn delete() {
    let client = TestClient::new();
    client.c.create("/test/foo", "bar", None).ok().unwrap();

    let response = client.c.delete("/test/foo", false).ok().unwrap();

    assert_eq!(response.action, "delete");
}

#[test]
fn create_dir() {
    let client = TestClient::new();

    let response = client.c.create_dir("/test/dir", None).ok().unwrap();

    assert_eq!(response.action, "create".to_string());
    assert!(response.node.dir.unwrap());
    assert!(response.node.value.is_none());
}

#[test]
fn delete_dir() {
    let client = TestClient::new();
    client.c.create_dir("/test/dir", None).ok().unwrap();

    let response = client.c.delete_dir("/test/dir").ok().unwrap();

    assert_eq!(response.action, "delete");
}

#[test]
fn watch() {
    let child = spawn(|| {
        let client = Client::new("http://etcd:2379").unwrap();

        sleep(Duration::from_millis(50));

        client.set("/test/foo", "baz", None).ok().unwrap();
    });

    let client = TestClient::new();

    client.c.create("/test/foo", "bar", None).ok().unwrap();

    let response = client.c.watch("/test/foo", None, false).ok().unwrap();

    assert_eq!(response.node.value.unwrap(), "baz".to_string());

    child.join().ok().unwrap();
}

#[test]
fn watch_index() {
    let client = TestClient::new();

    let index = client.c.set("/test/foo", "bar", None).ok().unwrap().node.modified_index.unwrap();

    let response = client.c.watch("/test/foo", Some(index), false).ok().unwrap();

    assert_eq!(response.node.modified_index.unwrap(), index);
    assert_eq!(response.node.value.unwrap(), "bar".to_string());
}

#[test]
fn watch_recursive() {
    let child = spawn(|| {
        let client = Client::new("http://etcd:2379").unwrap();

        sleep(Duration::from_millis(50));

        client.set("/test/foo/bar", "baz", None).ok().unwrap();
    });

    let client = TestClient::new();

    let response = client.c.watch("/test", None, true).ok().unwrap();

    assert_eq!(response.node.key.unwrap(), "/test/foo/bar".to_string());
    assert_eq!(response.node.value.unwrap(), "baz".to_string());

    child.join().ok().unwrap();
}

#[test]
fn version() {
    let client = TestClient::new();

    let version = client.c.version().ok().unwrap();

    assert_eq!(version.etcdcluster.unwrap(), "2.3.0".to_string());
    assert_eq!(version.etcdserver.unwrap(), "2.3.0".to_string());
}
<commit_msg>Impl Deref for TestClient.<commit_after>extern crate etcd;

use std::ops::Deref;
use std::thread::{sleep, spawn};
use std::time::Duration;

use etcd::{Client, Error};

/// Wrapper around Client that automatically cleans up etcd after each test.
struct TestClient {
    c: Client,
}

impl TestClient {
    /// Creates a new client for a test.
    fn new() -> TestClient {
        TestClient {
            c: Client::new("http://etcd:2379").unwrap(),
        }
    }
}

impl Drop for TestClient {
    #[allow(unused_must_use)]
    fn drop(&mut self) {
        self.delete("/test", true);
    }
}

impl Deref for TestClient {
    type Target = Client;

    fn deref(&self) -> &Self::Target {
        &self.c
    }
}

#[test]
fn create() {
    let client = TestClient::new();

    let response = match client.create("/test/foo", "bar", Some(60)) {
        Ok(response) => response,
        Err(error) => panic!("{:?}", error),
    };

    assert_eq!(response.action, "create".to_string());
    assert_eq!(response.node.value.unwrap(), "bar".to_string());
    assert_eq!(response.node.ttl.unwrap(), 60);
}

#[test]
fn create_does_not_replace_existing_key() {
    let client = TestClient::new();

    client.create("/test/foo", "bar", Some(60)).ok().unwrap();

    match client.create("/test/foo", "bar", None).err().unwrap() {
        Error::Etcd(error) => assert_eq!(error.message, "Key already exists".to_string()),
        _ => panic!("expected EtcdError due to pre-existing key"),
    };
}

#[test]
fn create_in_order() {
    let client = TestClient::new();

    let keys: Vec<String> = (1..4).map(|ref _key| {
        client.create_in_order(
            "/test/foo",
            "bar",
            None,
        ).ok().unwrap().node.key.unwrap()
    }).collect();

    assert!(keys[0] < keys[1]);
    assert!(keys[1] < keys[2]);
}

#[test]
fn create_in_order_must_operate_on_a_directory() {
    let client = TestClient::new();

    client.create("/test/foo", "bar", None).ok().unwrap();

    assert!(client.create_in_order("/test/foo", "baz", None).is_err());
}

#[test]
fn compare_and_delete() {
    let client = TestClient::new();

    let modified_index = client.create(
        "/test/foo",
        "bar",
        None
    ).ok().unwrap().node.modified_index.unwrap();

    let response = client.compare_and_delete(
        "/test/foo",
        Some("bar"),
        Some(modified_index)
    ).ok().unwrap();

    assert_eq!(response.action, "compareAndDelete".to_string());
}

#[test]
fn compare_and_delete_only_index() {
    let client = TestClient::new();

    let modified_index = client.create(
        "/test/foo",
        "bar",
        None
    ).ok().unwrap().node.modified_index.unwrap();

    let response = client.compare_and_delete(
        "/test/foo",
        None,
        Some(modified_index)
    ).ok().unwrap();

    assert_eq!(response.action, "compareAndDelete".to_string());
}

#[test]
fn compare_and_delete_only_value() {
    let client = TestClient::new();
    client.create("/test/foo", "bar", None).ok().unwrap();

    let response = client.compare_and_delete(
        "/test/foo",
        Some("bar"),
        None,
    ).ok().unwrap();

    assert_eq!(response.action, "compareAndDelete".to_string());
}

#[test]
fn compare_and_delete_requires_conditions() {
    let client = TestClient::new();
    client.create("/test/foo", "bar", None).ok().unwrap();

    match client.compare_and_delete("/test/foo", None, None).err().unwrap() {
        Error::InvalidConditions(message) => assert_eq!(
            message,
            "Current value or modified index is required."
        ),
        _ => panic!("expected Error::InvalidConditions"),
    }
}

#[test]
fn compare_and_swap() {
    let client = TestClient::new();

    let modified_index = client.create(
        "/test/foo",
        "bar",
        None
    ).ok().unwrap().node.modified_index.unwrap();

    let response = client.compare_and_swap(
        "/test/foo",
        "baz",
        Some(100),
        Some("bar"),
        Some(modified_index)
    ).ok().unwrap();

    assert_eq!(response.action, "compareAndSwap".to_string());
}

#[test]
fn compare_and_swap_only_index() {
    let client = TestClient::new();

    let modified_index = client.create(
        "/test/foo",
        "bar",
        None
    ).ok().unwrap().node.modified_index.unwrap();

    let response = client.compare_and_swap(
        "/test/foo",
        "baz",
        None,
        None,
        Some(modified_index)
    ).ok().unwrap();

    assert_eq!(response.action, "compareAndSwap".to_string());
}

#[test]
fn compare_and_swap_only_value() {
    let client = TestClient::new();
    client.create("/test/foo", "bar", None).ok().unwrap();

    let response = client.compare_and_swap(
        "/test/foo",
        "bar",
        None,
        Some("bar"),
        None,
    ).ok().unwrap();

    assert_eq!(response.action, "compareAndSwap".to_string());
}

#[test]
fn compare_and_swap_requires_conditions() {
    let client = TestClient::new();
    client.create("/test/foo", "bar", None).ok().unwrap();

    match client.compare_and_swap("/test/foo", "bar", None, None, None).err().unwrap() {
        Error::InvalidConditions(message) => assert_eq!(
            message,
            "Current value or modified index is required."
        ),
        _ => panic!("expected Error::InvalidConditions"),
    }
}

#[test]
fn get() {
    let client = TestClient::new();
    client.create("/test/foo", "bar", Some(60)).ok().unwrap();

    let response = client.get("/test/foo", false, false, false).ok().unwrap();

    assert_eq!(response.action, "get".to_string());
    assert_eq!(response.node.value.unwrap(), "bar".to_string());
    assert_eq!(response.node.ttl.unwrap(), 60);

}

#[test]
fn get_non_recursive() {
    let client = TestClient::new();

    client.set("/test/dir/baz", "blah", None).ok();
    client.set("/test/foo", "bar", None).ok();

    let response = client.get("/test", true, false, false).ok().unwrap();

    assert_eq!(response.node.dir.unwrap(), true);

    let nodes = response.node.nodes.unwrap();

    assert_eq!(nodes[0].clone().key.unwrap(), "/test/dir".to_string());
    assert_eq!(nodes[0].clone().dir.unwrap(), true);
    assert_eq!(nodes[1].clone().key.unwrap(), "/test/foo".to_string());
    assert_eq!(nodes[1].clone().value.unwrap(), "bar".to_string());
}

#[test]
fn get_recursive() {
    let client = TestClient::new();

    client.set("/test/dir/baz", "blah", None).ok();

    let response = client.get("/test", true, true, false).ok().unwrap();
    let nodes = response.node.nodes.unwrap();

    assert_eq!(
        nodes[0].clone().nodes.unwrap()[0].clone().value.unwrap(),
        "blah".to_string()
    );
}

#[test]
fn leader_stats() {
    let client = TestClient::new();

    client.leader_stats().unwrap();
}

#[test]
fn set() {
    let client = TestClient::new();

    let response = client.set("/test/foo", "baz", None).ok().unwrap();

    assert_eq!(response.action, "set".to_string());
    assert_eq!(response.node.value.unwrap(), "baz".to_string());
    assert!(response.node.ttl.is_none());
}

#[test]
fn set_dir() {
    let client = TestClient::new();

    assert!(client.set_dir("/test", None).is_ok());
    assert!(client.set_dir("/test", None).is_err());

    client.set("/test/foo", "bar", None).ok().unwrap();

    assert!(client.set_dir("/test/foo", None).is_ok());
}

#[test]
fn update() {
    let client = TestClient::new();
    client.create("/test/foo", "bar", None).ok().unwrap();

    let response = client.update("/test/foo", "blah", Some(30)).ok().unwrap();

    assert_eq!(response.action, "update".to_string());
    assert_eq!(response.node.value.unwrap(), "blah".to_string());
    assert_eq!(response.node.ttl.unwrap(), 30);
}

#[test]
fn update_requires_existing_key() {
    let client = TestClient::new();

    match client.update("/test/foo", "bar", None).err().unwrap() {
        Error::Etcd(error) => assert_eq!(error.message, "Key not found".to_string()),
        _ => panic!("expected EtcdError due to missing key"),
    };
}

#[test]
fn update_dir() {
    let client = TestClient::new();

    client.create_dir("/test", None).ok().unwrap();

    let response = client.update_dir("/test", Some(60)).ok().unwrap();

    assert_eq!(response.node.ttl.unwrap(), 60);
}

#[test]
fn update_dir_replaces_key() {
    let client = TestClient::new();

    client.set("/test/foo", "bar", None).ok().unwrap();

    let response = client.update_dir("/test/foo", Some(60)).ok().unwrap();

    assert_eq!(response.node.value.unwrap(), "");
    assert_eq!(response.node.ttl.unwrap(), 60);
}

#[test]
fn update_dir_requires_existing_dir() {
    let client = TestClient::new();

    assert!(client.update_dir("/test", None).is_err());
}

#[test]
fn delete() {
    let client = TestClient::new();
    client.create("/test/foo", "bar", None).ok().unwrap();

    let response = client.delete("/test/foo", false).ok().unwrap();

    assert_eq!(response.action, "delete");
}

#[test]
fn create_dir() {
    let client = TestClient::new();

    let response = client.create_dir("/test/dir", None).ok().unwrap();

    assert_eq!(response.action, "create".to_string());
    assert!(response.node.dir.unwrap());
    assert!(response.node.value.is_none());
}

#[test]
fn delete_dir() {
    let client = TestClient::new();
    client.create_dir("/test/dir", None).ok().unwrap();

    let response = client.delete_dir("/test/dir").ok().unwrap();

    assert_eq!(response.action, "delete");
}

#[test]
fn watch() {
    let child = spawn(|| {
        let client = Client::new("http://etcd:2379").unwrap();

        sleep(Duration::from_millis(50));

        client.set("/test/foo", "baz", None).ok().unwrap();
    });

    let client = TestClient::new();

    client.create("/test/foo", "bar", None).ok().unwrap();

    let response = client.watch("/test/foo", None, false).ok().unwrap();

    assert_eq!(response.node.value.unwrap(), "baz".to_string());

    child.join().ok().unwrap();
}

#[test]
fn watch_index() {
    let client = TestClient::new();

    let index = client.set("/test/foo", "bar", None).ok().unwrap().node.modified_index.unwrap();

    let response = client.watch("/test/foo", Some(index), false).ok().unwrap();

    assert_eq!(response.node.modified_index.unwrap(), index);
    assert_eq!(response.node.value.unwrap(), "bar".to_string());
}

#[test]
fn watch_recursive() {
    let child = spawn(|| {
        let client = Client::new("http://etcd:2379").unwrap();

        sleep(Duration::from_millis(50));

        client.set("/test/foo/bar", "baz", None).ok().unwrap();
    });

    let client = TestClient::new();

    let response = client.watch("/test", None, true).ok().unwrap();

    assert_eq!(response.node.key.unwrap(), "/test/foo/bar".to_string());
    assert_eq!(response.node.value.unwrap(), "baz".to_string());

    child.join().ok().unwrap();
}

#[test]
fn version() {
    let client = TestClient::new();

    let version = client.version().ok().unwrap();

    assert_eq!(version.etcdcluster.unwrap(), "2.3.0".to_string());
    assert_eq!(version.etcdserver.unwrap(), "2.3.0".to_string());
}
<|endoftext|>


<commit_before>//! [β-reduction](https://en.wikipedia.org/wiki/Beta_normal_form) for lambda `Term`s

use term::*;
use term::Term::*;
use self::Order::*;

/// Set to `true` to see all the steps of β-reductions. The default is `false`.
pub const SHOW_REDUCTIONS: bool = false;

/// The [evaluation order](https://en.wikipedia.org/wiki/Lambda_calculus#Reduction_strategies) of
/// β-reductions. `Applicative` order will fail to fully reduce expressions containing functions
/// without a normal form, e.g. the Y combinator (they will expand forever). `CallByName`,
/// `HeadSpine` and `CallByValue` orders don't always normalize fully. The default is `Normal`.
#[derive(Debug, PartialEq)]
pub enum Order {
    /// leftmost outermost
    Normal,
    /// leftmost outermost, but not inside abstractions
    CallByName,
    /// leftmost outermost, but abstractions reduced only in head position
    HeadSpine,
    /// `HeadSpine` + `Normal`
    HybridNormal,
    /// leftmost innermost
    Applicative,
    /// leftmost innermost, but not inside abstractions
    CallByValue,
    /// `CallByValue` + `Applicative`
    HybridApplicative
}

/// Applies two `Term`s with substitution and variable update, consuming the first one in the
/// process.
///
/// # Example
/// ```
/// use lambda_calculus::reduction::apply;
/// use lambda_calculus::parser::parse;
///
/// let lhs    = parse(&"λλ42(λ13)").unwrap();
/// let rhs    = parse(&"λ51").unwrap();
/// let result = parse(&"λ3(λ61)(λ1(λ71))").unwrap();
///
/// assert_eq!(apply(lhs, &rhs), Ok(result));
/// ```
pub fn apply(mut lhs: Term, rhs: &Term) -> Result<Term, Error> {
    _apply(&mut lhs, rhs, 0);

    lhs.unabs()
}

fn _apply(lhs: &mut Term, rhs: &Term, depth: usize) {
    match *lhs {
        Var(i) => if i == depth {
            *lhs = rhs.clone(); // substitute a top-level variable from lhs with rhs
            update_free_variables(lhs, depth - 1, 0); // update indices of free variables from rhs
        } else if i > depth {
            *lhs = Var(i - 1) // decrement a free variable's index
        },
        Abs(_) => {
            _apply(lhs.unabs_ref_mut().unwrap(), rhs, depth + 1)
        },
        App(_, _) => {
            _apply(lhs.lhs_ref_mut().unwrap(), rhs, depth);
            _apply(lhs.rhs_ref_mut().unwrap(), rhs, depth)
        }
    }
}

fn update_free_variables(term: &mut Term, added_depth: usize, own_depth: usize) {
    match *term {
        Var(i) => if i > own_depth {
            *term = Var(i + added_depth)
        },
        Abs(_) => {
            update_free_variables(term.unabs_ref_mut().unwrap(), added_depth, own_depth + 1)
        },
        App(_, _) => {
            update_free_variables(term.lhs_ref_mut().unwrap(), added_depth, own_depth);
            update_free_variables(term.rhs_ref_mut().unwrap(), added_depth, own_depth)
        }
    }
}

impl Term {
    /// Applies `self` to another `Term` and performs substitution, consuming `self` in the process.
    ///
    /// # Example
    /// ```
    /// use lambda_calculus::parser::parse;
    ///
    /// let lhs    = parse(&"λλ42(λ13)").unwrap();
    /// let rhs    = parse(&"λ51").unwrap();
    /// let result = parse(&"λ3(λ61)(λ1(λ71))").unwrap();
    ///
    /// assert_eq!(lhs.apply(&rhs), Ok(result));
    /// ```
    pub fn apply(self, rhs: &Term) -> Result<Term, Error> {
        apply(self, rhs)
    }

    /// Reduces an `App`lication by substitution and variable update.
    ///
    /// # Example
    /// ```
    /// use lambda_calculus::term::{app, abs};
    /// use lambda_calculus::term::Term::Var;
    /// use lambda_calculus::arithmetic::zero;
    /// use lambda_calculus::combinators::i;
    ///
    /// assert_eq!(app(i(), zero()).eval(), Ok(zero()));
    /// ```
    pub fn eval(self) -> Result<Term, Error> {
        let (lhs, rhs) = try!(self.unapp());

        apply(lhs, &rhs)
    }

    /// Performs a single β-reduction on `self` with the specified evaluation `Order`.
    ///
    /// # Example
    ///
    /// ```
    /// use lambda_calculus::arithmetic::succ;
    /// use lambda_calculus::reduction::Order;
    ///
    /// let mut succ_one = succ().app(1.into());
    /// succ_one.beta_once(&Order::Normal);
    ///
    /// assert_eq!(succ_one, succ().apply(&1.into()).unwrap());
    /// ```
    pub fn beta_once(&mut self, order: &Order) {
        self.beta_once_indicative(order);
    }

    fn beta_once_indicative(&mut self, order: &Order) -> bool {
        match *order {
            Normal       => self._beta_once_normal(0),
            CallByName   => self._beta_once_call_by_name(),
            HeadSpine    => self._beta_once_head_spine(0, true),
            Applicative  => self._beta_once_applicative(0),
            CallByValue  => self._beta_once_call_by_value(),
            HybridNormal => {
                self._beta_once_head_spine(0, true);
                self._beta_once_normal(0)
            },
            HybridApplicative => {
                self._beta_once_call_by_value();
                self._beta_once_applicative(0)
            }
        }
    }

    fn eval_with_info(&mut self, depth: u32) {
        if SHOW_REDUCTIONS { print!("    {} reduces to ", show_precedence(self, 0, depth)) };
        let copy = self.clone();
        *self = copy.eval().unwrap();
        if SHOW_REDUCTIONS { println!("{}", show_precedence(self, 0, depth)) }
    }

    // the return value indicates if reduction was performed
    fn _beta_once_normal(&mut self, depth: u32) -> bool {
        match *self {
            Var(_) => false,
            Abs(_) => self.unabs_ref_mut().unwrap()._beta_once_normal(depth + 1),
            App(_, _) => {
                if self.lhs_ref().unwrap().unabs_ref().is_ok() {
                    self.eval_with_info(depth);
                    true
                } else {
                    self.lhs_ref_mut().unwrap()._beta_once_normal(depth) ||
                    self.rhs_ref_mut().unwrap()._beta_once_normal(depth)
                }
            }
        }
    }

    // the return value indicates if reduction was performed
    fn _beta_once_call_by_name(&mut self) -> bool {
        match *self {
            Var(_) => false,
            Abs(_) => false,
            App(_, _) => {
                if self.lhs_ref().unwrap().unabs_ref().is_ok() {
                    self.eval_with_info(0);
                    true
                } else {
                    self.lhs_ref_mut().unwrap()._beta_once_call_by_name() ||
                    self.rhs_ref_mut().unwrap()._beta_once_call_by_name()
                }
            }
        }
    }

    // the return value indicates if reduction was performed
    fn _beta_once_head_spine(&mut self, depth: u32, is_head: bool) -> bool {
        match *self {
            Var(_) => false,
            Abs(_) => if is_head {
                self.unabs_ref_mut().unwrap()._beta_once_head_spine(depth + 1, is_head)
            } else {
                false
            },
            App(_, _) => {
                if self.lhs_ref().unwrap().unabs_ref().is_ok() {
                    self.eval_with_info(0);
                    true
                } else {
                    self.lhs_ref_mut().unwrap()._beta_once_head_spine(depth, is_head) ||
                    self.rhs_ref_mut().unwrap()._beta_once_head_spine(depth, false)
                }
            }
        }
    }

    // the return value indicates if reduction was performed
    fn _beta_once_applicative(&mut self, depth: u32) -> bool {
        match *self {
            Var(_) => false,
            Abs(_) => self.unabs_ref_mut().unwrap()._beta_once_applicative(depth + 1),
            App(_, _) => {
                if  self.lhs_ref().unwrap().unabs_ref().is_ok() &&
                   !self.rhs_ref().unwrap().is_beta_reducible(&Applicative)
                {
                    self.eval_with_info(depth);
                    true
                } else {
                    self.lhs_ref_mut().unwrap()._beta_once_applicative(depth) ||
                    self.rhs_ref_mut().unwrap()._beta_once_applicative(depth)
                }
            }
        }
    }

    // the return value indicates if reduction was performed
    fn _beta_once_call_by_value(&mut self) -> bool {
        match *self {
            Var(_) => false,
            Abs(_) => false,
            App(_, _) => {
                if  self.lhs_ref().unwrap().unabs_ref().is_ok() &&
                   !self.rhs_ref().unwrap().is_beta_reducible(&CallByValue)
                {
                    self.eval_with_info(0);
                    true
                } else {
                    self.lhs_ref_mut().unwrap()._beta_once_call_by_value() ||
                    self.rhs_ref_mut().unwrap()._beta_once_call_by_value()
                }
            }
        }
    }

    /// Checks whether `self` is β-reducible under the specified evaluation `Order`.
    ///
    /// # Example
    ///
    /// ```
    /// use lambda_calculus::arithmetic::{succ, zero};
    /// use lambda_calculus::reduction::Order;
    ///
    /// let reducible     = succ().app(1.into());
    /// let not_reducible = zero();
    ///
    /// assert!(reducible.is_beta_reducible(&Order::Normal));
    /// assert!(!not_reducible.is_beta_reducible(&Order::Normal));
    /// ```
    pub fn is_beta_reducible(&self, order: &Order) -> bool {
        match *self {
            Var(_) => false,
            Abs(_) => match *order {
                Normal | Applicative => self.unabs_ref().unwrap().is_beta_reducible(order),
                _ => false
            },
            App(_, _) => {
                if self.lhs_ref().unwrap().unabs_ref().is_ok() {
                    true
                } else {
                    self.lhs_ref().unwrap().is_beta_reducible(order) ||
                    self.rhs_ref().unwrap().is_beta_reducible(order)
                }
            }
        }
    }

    /// Performs full β-reduction on `self` using the specified evaluation `Order`.
    ///
    /// # Example
    ///
    /// ```
    /// use lambda_calculus::arithmetic::pred;
    /// use lambda_calculus::reduction::Order;
    ///
    /// let mut pred_one = pred().app(1.into());
    /// pred_one.beta_full(&Order::Normal);
    ///
    /// assert_eq!(pred_one, 0.into());
    /// ```
    pub fn beta_full(&mut self, order: &Order) {
        loop {
            if SHOW_REDUCTIONS { println!("reducing {}", self) }
            if !self.beta_once_indicative(order) { break }
        }
        if SHOW_REDUCTIONS { println!("    doesn't reduce") }
    }
}

/// Performs full β-reduction on a `Term` using the specified evaluation `Order`, consuming its
/// argument in the process.
///
/// # Example
///
/// ```
/// use lambda_calculus::arithmetic::pred;
/// use lambda_calculus::reduction::{beta_full, Order};
///
/// let pred_one = pred().app(1.into());
///
/// assert_eq!(beta_full(pred_one, &Order::Normal), 0.into());
/// ```
pub fn beta_full(mut term: Term, order: &Order) -> Term {
    term.beta_full(order);
    term
}

/// Performs a single β-reduction on a `Term` using the specified evaluation `Order`, consuming its
/// argument in the process.
///
/// # Example
///
/// ```
/// use lambda_calculus::arithmetic::succ;
/// use lambda_calculus::reduction::{beta_once, Order};
///
/// let succ_one = succ().app(1.into());
///
/// assert_eq!(beta_once(succ_one, &Order::Normal), succ().apply(&1.into()).unwrap());
/// ```
pub fn beta_once(mut term: Term, order: &Order) -> Term {
    term.beta_once(order);
    term
}

#[cfg(test)]
mod test {
    use super::*;
    use parser::parse;
    use combinators::{i, y};

    #[test]
    fn normal_order() {
        let reduces_instantly = parse(&"(λλ1)((λλλ((32)1))(λλ2))").unwrap();
        assert_eq!(beta_full(reduces_instantly.clone(), &Normal),
                   beta_once(reduces_instantly, &Normal));

        let should_reduce = parse(&"(λ2)((λ111)(λ111))").unwrap();
        assert_eq!(beta_full(should_reduce, &Normal), Var(1))
    }

    #[test]
    fn call_by_name_order() {
        let mut expr = app(abs(app(i(), Var(1))), app(i(), i()));
        expr.beta_once(&CallByName);
        assert_eq!(expr, app(i(), app(i(), i())));
        expr.beta_once(&CallByName);
        assert_eq!(expr, app(i(), i()));
        expr.beta_once(&CallByName);
        assert_eq!(expr, i());
    }

    #[test]
    fn applicative_order() {
        let expr = parse(&"λ1(((λλλ1)1)((λλ21)1))").unwrap();
        assert_eq!(&format!("{}", beta_once(expr, &Applicative)), "λ1((λλ1)((λλ21)1))");

        let expands = parse(&"(λ2)((λ111)(λ111))").unwrap();
        assert_eq!(&format!("{}", beta_once(expands, &Applicative)), "(λ2)((λ111)(λ111)(λ111))")
    }

    #[test]
    fn call_by_value_order() {
        let mut expr = app(abs(app(i(), Var(1))), app(i(), i()));
        expr.beta_once(&CallByValue);
        assert_eq!(expr, app(abs(app(i(), Var(1))), i()));
        expr.beta_once(&CallByValue);
        assert_eq!(expr, app(i(), i()));
        expr.beta_once(&CallByValue);
        assert_eq!(expr, i());
    }

    #[test]
    fn reducibility() {
        assert!(y().is_beta_reducible(&Normal));
        assert!(y().is_beta_reducible(&Applicative));
        assert!(!y().is_beta_reducible(&CallByName));
        assert!(!y().is_beta_reducible(&CallByValue));
    }
}
<commit_msg>adjust CBN<commit_after>//! [β-reduction](https://en.wikipedia.org/wiki/Beta_normal_form) for lambda `Term`s

use term::*;
use term::Term::*;
use self::Order::*;

/// Set to `true` to see all the steps of β-reductions. The default is `false`.
pub const SHOW_REDUCTIONS: bool = false;

/// The [evaluation order](https://en.wikipedia.org/wiki/Lambda_calculus#Reduction_strategies) of
/// β-reductions. `Applicative` order will fail to fully reduce expressions containing functions
/// without a normal form, e.g. the Y combinator (they will expand forever). `CallByName`,
/// `HeadSpine` and `CallByValue` orders don't always normalize fully. The default is `Normal`.
#[derive(Debug, PartialEq)]
pub enum Order {
    /// leftmost outermost
    Normal,
    /// leftmost outermost, but not inside abstractions
    CallByName,
    /// leftmost outermost, but abstractions reduced only in head position
    HeadSpine,
    /// `HeadSpine` + `Normal`
    HybridNormal,
    /// leftmost innermost
    Applicative,
    /// leftmost innermost, but not inside abstractions
    CallByValue,
    /// `CallByValue` + `Applicative`
    HybridApplicative
}

/// Applies two `Term`s with substitution and variable update, consuming the first one in the
/// process.
///
/// # Example
/// ```
/// use lambda_calculus::reduction::apply;
/// use lambda_calculus::parser::parse;
///
/// let lhs    = parse(&"λλ42(λ13)").unwrap();
/// let rhs    = parse(&"λ51").unwrap();
/// let result = parse(&"λ3(λ61)(λ1(λ71))").unwrap();
///
/// assert_eq!(apply(lhs, &rhs), Ok(result));
/// ```
pub fn apply(mut lhs: Term, rhs: &Term) -> Result<Term, Error> {
    _apply(&mut lhs, rhs, 0);

    lhs.unabs()
}

fn _apply(lhs: &mut Term, rhs: &Term, depth: usize) {
    match *lhs {
        Var(i) => if i == depth {
            *lhs = rhs.clone(); // substitute a top-level variable from lhs with rhs
            update_free_variables(lhs, depth - 1, 0); // update indices of free variables from rhs
        } else if i > depth {
            *lhs = Var(i - 1) // decrement a free variable's index
        },
        Abs(_) => {
            _apply(lhs.unabs_ref_mut().unwrap(), rhs, depth + 1)
        },
        App(_, _) => {
            _apply(lhs.lhs_ref_mut().unwrap(), rhs, depth);
            _apply(lhs.rhs_ref_mut().unwrap(), rhs, depth)
        }
    }
}

fn update_free_variables(term: &mut Term, added_depth: usize, own_depth: usize) {
    match *term {
        Var(i) => if i > own_depth {
            *term = Var(i + added_depth)
        },
        Abs(_) => {
            update_free_variables(term.unabs_ref_mut().unwrap(), added_depth, own_depth + 1)
        },
        App(_, _) => {
            update_free_variables(term.lhs_ref_mut().unwrap(), added_depth, own_depth);
            update_free_variables(term.rhs_ref_mut().unwrap(), added_depth, own_depth)
        }
    }
}

impl Term {
    /// Applies `self` to another `Term` and performs substitution, consuming `self` in the process.
    ///
    /// # Example
    /// ```
    /// use lambda_calculus::parser::parse;
    ///
    /// let lhs    = parse(&"λλ42(λ13)").unwrap();
    /// let rhs    = parse(&"λ51").unwrap();
    /// let result = parse(&"λ3(λ61)(λ1(λ71))").unwrap();
    ///
    /// assert_eq!(lhs.apply(&rhs), Ok(result));
    /// ```
    pub fn apply(self, rhs: &Term) -> Result<Term, Error> {
        apply(self, rhs)
    }

    /// Reduces an `App`lication by substitution and variable update.
    ///
    /// # Example
    /// ```
    /// use lambda_calculus::term::{app, abs};
    /// use lambda_calculus::term::Term::Var;
    /// use lambda_calculus::arithmetic::zero;
    /// use lambda_calculus::combinators::i;
    ///
    /// assert_eq!(app(i(), zero()).eval(), Ok(zero()));
    /// ```
    pub fn eval(self) -> Result<Term, Error> {
        let (lhs, rhs) = try!(self.unapp());

        apply(lhs, &rhs)
    }

    /// Performs a single β-reduction on `self` with the specified evaluation `Order`.
    ///
    /// # Example
    ///
    /// ```
    /// use lambda_calculus::arithmetic::succ;
    /// use lambda_calculus::reduction::Order;
    ///
    /// let mut succ_one = succ().app(1.into());
    /// succ_one.beta_once(&Order::Normal);
    ///
    /// assert_eq!(succ_one, succ().apply(&1.into()).unwrap());
    /// ```
    pub fn beta_once(&mut self, order: &Order) {
        self.beta_once_indicative(order);
    }

    fn beta_once_indicative(&mut self, order: &Order) -> bool {
        match *order {
            Normal       => self._beta_once_normal(0),
            CallByName   => self._beta_once_call_by_name(),
            HeadSpine    => self._beta_once_head_spine(0, true),
            Applicative  => self._beta_once_applicative(0),
            CallByValue  => self._beta_once_call_by_value(),
            HybridNormal => {
                self._beta_once_head_spine(0, true);
                self._beta_once_normal(0)
            },
            HybridApplicative => {
                self._beta_once_call_by_value();
                self._beta_once_applicative(0)
            }
        }
    }

    fn eval_with_info(&mut self, depth: u32) {
        if SHOW_REDUCTIONS { print!("    {} reduces to ", show_precedence(self, 0, depth)) };
        let copy = self.clone();
        *self = copy.eval().unwrap();
        if SHOW_REDUCTIONS { println!("{}", show_precedence(self, 0, depth)) }
    }

    // the return value indicates if reduction was performed
    fn _beta_once_normal(&mut self, depth: u32) -> bool {
        match *self {
            Var(_) => false,
            Abs(_) => self.unabs_ref_mut().unwrap()._beta_once_normal(depth + 1),
            App(_, _) => {
                if self.lhs_ref().unwrap().unabs_ref().is_ok() {
                    self.eval_with_info(depth);
                    true
                } else {
                    self.lhs_ref_mut().unwrap()._beta_once_normal(depth) ||
                    self.rhs_ref_mut().unwrap()._beta_once_normal(depth)
                }
            }
        }
    }

    // the return value indicates if reduction was performed
    fn _beta_once_call_by_name(&mut self) -> bool {
        match *self {
            Var(_) => false,
            Abs(_) => false,
            App(_, _) => {
                if self.lhs_ref().unwrap().unabs_ref().is_ok() {
                    self.eval_with_info(0);
                    true
                } else {
                    self.lhs_ref_mut().unwrap()._beta_once_call_by_name()
                }
            }
        }
    }

    // the return value indicates if reduction was performed
    fn _beta_once_head_spine(&mut self, depth: u32, is_head: bool) -> bool {
        match *self {
            Var(_) => false,
            Abs(_) => if is_head {
                self.unabs_ref_mut().unwrap()._beta_once_head_spine(depth + 1, is_head)
            } else {
                false
            },
            App(_, _) => {
                if self.lhs_ref().unwrap().unabs_ref().is_ok() {
                    self.eval_with_info(0);
                    true
                } else {
                    self.lhs_ref_mut().unwrap()._beta_once_head_spine(depth, is_head) ||
                    self.rhs_ref_mut().unwrap()._beta_once_head_spine(depth, false)
                }
            }
        }
    }

    // the return value indicates if reduction was performed
    fn _beta_once_applicative(&mut self, depth: u32) -> bool {
        match *self {
            Var(_) => false,
            Abs(_) => self.unabs_ref_mut().unwrap()._beta_once_applicative(depth + 1),
            App(_, _) => {
                if  self.lhs_ref().unwrap().unabs_ref().is_ok() &&
                   !self.rhs_ref().unwrap().is_beta_reducible(&Applicative)
                {
                    self.eval_with_info(depth);
                    true
                } else {
                    self.lhs_ref_mut().unwrap()._beta_once_applicative(depth) ||
                    self.rhs_ref_mut().unwrap()._beta_once_applicative(depth)
                }
            }
        }
    }

    // the return value indicates if reduction was performed
    fn _beta_once_call_by_value(&mut self) -> bool {
        match *self {
            Var(_) => false,
            Abs(_) => false,
            App(_, _) => {
                if  self.lhs_ref().unwrap().unabs_ref().is_ok() &&
                   !self.rhs_ref().unwrap().is_beta_reducible(&CallByValue)
                {
                    self.eval_with_info(0);
                    true
                } else {
                    self.lhs_ref_mut().unwrap()._beta_once_call_by_value() ||
                    self.rhs_ref_mut().unwrap()._beta_once_call_by_value()
                }
            }
        }
    }

    /// Checks whether `self` is β-reducible under the specified evaluation `Order`.
    ///
    /// # Example
    ///
    /// ```
    /// use lambda_calculus::arithmetic::{succ, zero};
    /// use lambda_calculus::reduction::Order;
    ///
    /// let reducible     = succ().app(1.into());
    /// let not_reducible = zero();
    ///
    /// assert!(reducible.is_beta_reducible(&Order::Normal));
    /// assert!(!not_reducible.is_beta_reducible(&Order::Normal));
    /// ```
    pub fn is_beta_reducible(&self, order: &Order) -> bool {
        match *self {
            Var(_) => false,
            Abs(_) => match *order {
                Normal | Applicative => self.unabs_ref().unwrap().is_beta_reducible(order),
                _ => false
            },
            App(_, _) => {
                if self.lhs_ref().unwrap().unabs_ref().is_ok() {
                    true
                } else {
                    self.lhs_ref().unwrap().is_beta_reducible(order) ||
                    self.rhs_ref().unwrap().is_beta_reducible(order)
                }
            }
        }
    }

    /// Performs full β-reduction on `self` using the specified evaluation `Order`.
    ///
    /// # Example
    ///
    /// ```
    /// use lambda_calculus::arithmetic::pred;
    /// use lambda_calculus::reduction::Order;
    ///
    /// let mut pred_one = pred().app(1.into());
    /// pred_one.beta_full(&Order::Normal);
    ///
    /// assert_eq!(pred_one, 0.into());
    /// ```
    pub fn beta_full(&mut self, order: &Order) {
        loop {
            if SHOW_REDUCTIONS { println!("reducing {}", self) }
            if !self.beta_once_indicative(order) { break }
        }
        if SHOW_REDUCTIONS { println!("    doesn't reduce") }
    }
}

/// Performs full β-reduction on a `Term` using the specified evaluation `Order`, consuming its
/// argument in the process.
///
/// # Example
///
/// ```
/// use lambda_calculus::arithmetic::pred;
/// use lambda_calculus::reduction::{beta_full, Order};
///
/// let pred_one = pred().app(1.into());
///
/// assert_eq!(beta_full(pred_one, &Order::Normal), 0.into());
/// ```
pub fn beta_full(mut term: Term, order: &Order) -> Term {
    term.beta_full(order);
    term
}

/// Performs a single β-reduction on a `Term` using the specified evaluation `Order`, consuming its
/// argument in the process.
///
/// # Example
///
/// ```
/// use lambda_calculus::arithmetic::succ;
/// use lambda_calculus::reduction::{beta_once, Order};
///
/// let succ_one = succ().app(1.into());
///
/// assert_eq!(beta_once(succ_one, &Order::Normal), succ().apply(&1.into()).unwrap());
/// ```
pub fn beta_once(mut term: Term, order: &Order) -> Term {
    term.beta_once(order);
    term
}

#[cfg(test)]
mod test {
    use super::*;
    use parser::parse;
    use combinators::{i, y};

    #[test]
    fn normal_order() {
        let reduces_instantly = parse(&"(λλ1)((λλλ((32)1))(λλ2))").unwrap();
        assert_eq!(beta_full(reduces_instantly.clone(), &Normal),
                   beta_once(reduces_instantly, &Normal));

        let should_reduce = parse(&"(λ2)((λ111)(λ111))").unwrap();
        assert_eq!(beta_full(should_reduce, &Normal), Var(1))
    }

    #[test]
    fn call_by_name_order() {
        let mut expr = app(abs(app(i(), Var(1))), app(i(), i()));
        expr.beta_once(&CallByName);
        assert_eq!(expr, app(i(), app(i(), i())));
        expr.beta_once(&CallByName);
        assert_eq!(expr, app(i(), i()));
        expr.beta_once(&CallByName);
        assert_eq!(expr, i());
    }

    #[test]
    fn applicative_order() {
        let expr = parse(&"λ1(((λλλ1)1)((λλ21)1))").unwrap();
        assert_eq!(&format!("{}", beta_once(expr, &Applicative)), "λ1((λλ1)((λλ21)1))");

        let expands = parse(&"(λ2)((λ111)(λ111))").unwrap();
        assert_eq!(&format!("{}", beta_once(expands, &Applicative)), "(λ2)((λ111)(λ111)(λ111))")
    }

    #[test]
    fn call_by_value_order() {
        let mut expr = app(abs(app(i(), Var(1))), app(i(), i()));
        expr.beta_once(&CallByValue);
        assert_eq!(expr, app(abs(app(i(), Var(1))), i()));
        expr.beta_once(&CallByValue);
        assert_eq!(expr, app(i(), i()));
        expr.beta_once(&CallByValue);
        assert_eq!(expr, i());
    }

    #[test]
    fn reducibility() {
        assert!(y().is_beta_reducible(&Normal));
        assert!(y().is_beta_reducible(&Applicative));
        assert!(!y().is_beta_reducible(&CallByName));
        assert!(!y().is_beta_reducible(&CallByValue));
    }
}
<|endoftext|>


<commit_before>//! [β-reduction](https://en.wikipedia.org/wiki/Beta_normal_form) for lambda `Term`s

use term::{Term, TermError};
use term::Term::*;
use std::fmt;
use std::mem;
pub use self::Order::*;

/// The [evaluation order](http://www.cs.cornell.edu/courses/cs6110/2014sp/Handouts/Sestoft.pdf) of
/// β-reductions.
///
/// - The `NOR`, `HNO`, `APP` and `HAP` orders reduce expressions to their normal forms
/// - The `APP` order will fail to fully reduce expressions containing functions without a normal
/// form, e.g. the `Y` combinator (they will expand forever)
/// - The `CBN` order reduces to weak head normal form
/// - The `CBV` order reduces to weak normal form
/// - The `HSP` order reduces to head normal form
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Order {
    /// Normal - leftmost outermost; the most popular reduction strategy
    NOR,
    /// Call-by-name - leftmost outermost; no reductions inside abstractions
    CBN,
    /// Head spine - leftmost outermost; abstractions reduced only in head position
    HSP,
    /// Hybrid normal - a mix between `HSP` (head spine) and `NOR` (normal) strategies
    HNO,
    /// Applicative - leftmost innermost; the most eager strategy; unfit for recursion combinators
    APP,
    /// Call-by-value - leftmost innermost, no reductions inside abstractions
    CBV,
    /// Hybrid applicative - a mix between `CBV` (call-by-value) and `APP` (applicative) strategies;
    /// usually the fastest-reducing normalizing strategy
    HAP
}

/// Applies two `Term`s via substitution and variable update, consuming the first one in the process.
///
/// # Example
/// ```
/// use lambda_calculus::reduction::apply;
/// use lambda_calculus::*;
///
/// // these are valid terms, but be careful with unwraps in your code
/// let lhs    = parse(&"λλ42(λ13)", DeBruijn).unwrap();
/// let rhs    = parse(&"λ51", DeBruijn).unwrap();
/// let result = parse(&"λ3(λ61)(λ1(λ71))", DeBruijn).unwrap();
///
/// assert_eq!(apply(lhs, &rhs), Ok(result));
/// ```
/// # Errors
///
/// Returns a `TermError` if `lhs` is not an `Abs`traction.
pub fn apply(mut lhs: Term, rhs: &Term) -> Result<Term, TermError> {
    lhs.unabs_ref()?;

    _apply(&mut lhs, rhs, 0);

    lhs.unabs()
}

fn _apply(lhs: &mut Term, rhs: &Term, depth: usize) {
    match *lhs {
        Var(i) => if i == depth {
            *lhs = rhs.to_owned(); // substitute a top-level variable from lhs with rhs
            update_free_variables(lhs, depth - 1, 0); // update indices of free variables from rhs
        } else if i > depth {
            *lhs = Var(i - 1) // decrement a free variable's index
        },
        Abs(ref mut abstracted) => {
            _apply(abstracted, rhs, depth + 1)
        },
        App(ref mut lhs_lhs, ref mut lhs_rhs) => {
            _apply(lhs_lhs, rhs, depth);
            _apply(lhs_rhs, rhs, depth)
        }
    }
}

fn update_free_variables(term: &mut Term, added_depth: usize, own_depth: usize) {
    match *term {
        Var(ref mut i) => if *i > own_depth {
            *i += added_depth
        },
        Abs(ref mut abstracted) => {
            update_free_variables(abstracted, added_depth, own_depth + 1)
        },
        App(ref mut lhs, ref mut rhs) => {
            update_free_variables(lhs, added_depth, own_depth);
            update_free_variables(rhs, added_depth, own_depth)
        }
    }
}

/// Performs β-reduction on a `Term` with the specified evaluation `Order` and an optional limit on
/// the number of reductions (`0` means no limit) and returns the reduced `Term`.
///
/// # Example
///
/// ```
/// use lambda_calculus::*;
///
/// let expr    = parse(&"(λa.λb.λc.a (λd.λe.e (d b)) (λd.c) (λd.d)) (λa.λb.a b)", Classic).unwrap();
/// let reduced = parse(&"λa.λb.b", Classic).unwrap();
///
/// assert_eq!(beta(expr, NOR, 0), reduced);
/// ```
pub fn beta(mut term: Term, order: Order, limit: usize) -> Term {
    term.reduce(order, limit);
    term
}

impl Term {
    fn eval(&mut self, count: &mut usize) {
        let to_apply = mem::replace(self, Var(0)); // replace self with a dummy
        let (lhs, rhs) = to_apply.unapp().unwrap(); // safe; only called in reduction sites
        let applied = apply(lhs, &rhs).unwrap(); // ditto
        mem::replace(self, applied); // move self back to its place

        *count += 1;
    }

    fn is_reducible(&self, limit: usize, count: &usize) -> bool {
        self.lhs_ref().and_then(|t| t.unabs_ref()).is_ok() && (limit == 0 || *count < limit )
    }

    /// Performs β-reduction on a `Term` with the specified evaluation `Order` and an optional limit
    /// on the number of reductions (`0` means no limit) and returns the number of performed
    /// reductions.
    ///
    /// # Example
    ///
    /// ```
    /// use lambda_calculus::*;
    ///
    /// let mut expression = parse(&"(λa.λb.λc.b (a b c)) (λa.λb.b)", Classic).unwrap();
    /// let reduced        = parse(&"λa.λb.a b", Classic).unwrap();
    ///
    /// expression.reduce(NOR, 0);
    ///
    /// assert_eq!(expression, reduced);
    /// ```
    pub fn reduce(&mut self, order: Order, limit: usize) -> usize {
        let mut count = 0;

        match order {
            CBN => self.beta_cbn(limit, &mut count),
            NOR => self.beta_nor(limit, &mut count),
            CBV => self.beta_cbv(limit, &mut count),
            APP => self.beta_app(limit, &mut count),
            HSP => self.beta_hsp(limit, &mut count),
            HNO => self.beta_hno(limit, &mut count),
            HAP => self.beta_hap(limit, &mut count)
        }

        count
    }

    fn beta_cbn(&mut self, limit: usize, count: &mut usize) {
        if limit != 0 && *count == limit { return }

        if let App(_, _) = *self {
            self.lhs_mut().unwrap().beta_cbn(limit, count);

            if self.is_reducible(limit, count) {
                self.eval(count);
                self.beta_cbn(limit, count);
            }
        }
    }

    fn beta_nor(&mut self, limit: usize, count: &mut usize) {
        if limit != 0 && *count == limit { return }

        match *self {
            Abs(ref mut abstracted) => abstracted.beta_nor(limit, count),
            App(_, _) => {
                self.lhs_mut().unwrap().beta_cbn(limit, count);

                if self.is_reducible(limit, count) {
                    self.eval(count);
                    self.beta_nor(limit, count);
                } else {
                    self.lhs_mut().unwrap().beta_nor(limit, count);
                    self.rhs_mut().unwrap().beta_nor(limit, count);
                }
            },
            _ => ()
        }
    }

    fn beta_cbv(&mut self, limit: usize, count: &mut usize) {
        if limit != 0 && *count == limit { return }

        if let App(_, _) = *self {
            self.lhs_mut().unwrap().beta_cbv(limit, count);
            self.rhs_mut().unwrap().beta_cbv(limit, count);

            if self.is_reducible(limit, count) {
                self.eval(count);
                self.beta_cbv(limit, count);
            }
        }
    }

    fn beta_app(&mut self, limit: usize, count: &mut usize) {
        if limit != 0 && *count == limit { return }

        match *self {
            Abs(ref mut abstracted) => abstracted.beta_app(limit, count),
            App(_, _) => {
                self.lhs_mut().unwrap().beta_app(limit, count);
                self.rhs_mut().unwrap().beta_app(limit, count);

                if self.is_reducible(limit, count) {
                    self.eval(count);
                    self.beta_app(limit, count);
                }
            },
            _ => ()
        }
    }

    fn beta_hap(&mut self, limit: usize, count: &mut usize) {
        if limit != 0 && *count == limit { return }

        match *self {
            Abs(ref mut abstracted) => abstracted.beta_hap(limit, count),
            App(_, _) => {
                self.lhs_mut().unwrap().beta_cbv(limit, count);
                self.rhs_mut().unwrap().beta_hap(limit, count);

                if self.is_reducible(limit, count) {
                    self.eval(count);
                    self.beta_hap(limit, count);
                } else {
                    self.lhs_mut().unwrap().beta_hap(limit, count);
                }
            },
            _ => ()
        }
    }

    fn beta_hsp(&mut self, limit: usize, count: &mut usize) {
        if limit != 0 && *count == limit { return }

        match *self {
            Abs(ref mut abstracted) => abstracted.beta_hsp(limit, count),
            App(_, _) => {
                self.lhs_mut().unwrap().beta_hsp(limit, count);

                if self.is_reducible(limit, count) {
                    self.eval(count);
                    self.beta_hsp(limit, count)
                }
            },
            _ => ()
        }
    }

    fn beta_hno(&mut self, limit: usize, count: &mut usize) {
        if limit != 0 && *count == limit { return }

        match *self {
            Abs(ref mut abstracted) => abstracted.beta_hno(limit, count),
            App(_, _) => {
                self.lhs_mut().unwrap().beta_hsp(limit, count);

                if self.is_reducible(limit, count) {
                    self.eval(count);
                    self.beta_hno(limit, count)
                } else {
                    self.lhs_mut().unwrap().beta_hno(limit, count);
                    self.rhs_mut().unwrap().beta_hno(limit, count);
                }
            },
            _ => ()
        }
    }
}

impl fmt::Display for Order {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", match *self {
            NOR => "normal",
            CBN => "call-by-name",
            HSP => "head spine",
            HNO => "hybrid normal",
            APP => "applicative",
            CBV => "call-by-value",
            HAP => "hybrid applicative"
        })
    }
}
<commit_msg>reduction::apply > Term::apply<commit_after>//! [β-reduction](https://en.wikipedia.org/wiki/Beta_normal_form) for lambda `Term`s

use term::{Term, TermError};
use term::Term::*;
use std::fmt;
use std::mem;
pub use self::Order::*;

/// The [evaluation order](http://www.cs.cornell.edu/courses/cs6110/2014sp/Handouts/Sestoft.pdf) of
/// β-reductions.
///
/// - The `NOR`, `HNO`, `APP` and `HAP` orders reduce expressions to their normal forms
/// - The `APP` order will fail to fully reduce expressions containing functions without a normal
/// form, e.g. the `Y` combinator (they will expand forever)
/// - The `CBN` order reduces to weak head normal form
/// - The `CBV` order reduces to weak normal form
/// - The `HSP` order reduces to head normal form
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Order {
    /// Normal - leftmost outermost; the most popular reduction strategy
    NOR,
    /// Call-by-name - leftmost outermost; no reductions inside abstractions
    CBN,
    /// Head spine - leftmost outermost; abstractions reduced only in head position
    HSP,
    /// Hybrid normal - a mix between `HSP` (head spine) and `NOR` (normal) strategies
    HNO,
    /// Applicative - leftmost innermost; the most eager strategy; unfit for recursion combinators
    APP,
    /// Call-by-value - leftmost innermost, no reductions inside abstractions
    CBV,
    /// Hybrid applicative - a mix between `CBV` (call-by-value) and `APP` (applicative) strategies;
    /// usually the fastest-reducing normalizing strategy
    HAP
}

/// Performs β-reduction on a `Term` with the specified evaluation `Order` and an optional limit on
/// the number of reductions (`0` means no limit) and returns the reduced `Term`.
///
/// # Example
///
/// ```
/// use lambda_calculus::*;
///
/// let expr    = parse(&"(λa.λb.λc.a (λd.λe.e (d b)) (λd.c) (λd.d)) (λa.λb.a b)", Classic).unwrap();
/// let reduced = parse(&"λa.λb.b", Classic).unwrap();
///
/// assert_eq!(beta(expr, NOR, 0), reduced);
/// ```
pub fn beta(mut term: Term, order: Order, limit: usize) -> Term {
    term.reduce(order, limit);
    term
}

impl Term {
    /// Applies a `Term` to `self` via substitution and variable update.
    ///
    /// # Example
    /// ```
    /// use lambda_calculus::*;
    ///
    /// // these are valid terms, but be careful with unwraps in your code
    /// let mut term1  = parse(&"λλ42(λ13)", DeBruijn).unwrap();
    /// let term2      = parse(&"λ51", DeBruijn).unwrap();
    /// let result     = parse(&"λ3(λ61)(λ1(λ71))", DeBruijn).unwrap();
    ///
    /// term1.apply(&term2);
    ///
    /// assert_eq!(term1, result);
    /// ```
    /// # Errors
    ///
    /// Returns a `TermError` if `self` is not an `Abs`traction.
    pub fn apply(&mut self, rhs: &Term) -> Result<(), TermError> {
        self.unabs_ref()?;

        self._apply(rhs, 0);

        let ret = mem::replace(self, Var(0)); // replace self with a dummy
        mem::replace(self, ret.unabs().unwrap()); // move unabstracted self back to its place

        Ok(())
    }

    fn _apply(&mut self, rhs: &Term, depth: usize) {
        match *self {
            Var(i) => if i == depth {
                *self = rhs.to_owned(); // substitute a top-level variable from lhs with rhs
                self.update_free_variables(depth - 1, 0); // update indices of free variables from rhs
            } else if i > depth {
                *self = Var(i - 1) // decrement a free variable's index
            },
            Abs(ref mut abstracted) => {
                abstracted._apply(rhs, depth + 1)
            },
            App(ref mut lhs_lhs, ref mut lhs_rhs) => {
                lhs_lhs._apply(rhs, depth);
                lhs_rhs._apply(rhs, depth)
            }
        }
    }

    fn update_free_variables(&mut self, added_depth: usize, own_depth: usize) {
        match *self {
            Var(ref mut i) => if *i > own_depth {
                *i += added_depth
            },
            Abs(ref mut abstracted) => {
                abstracted.update_free_variables(added_depth, own_depth + 1)
            },
            App(ref mut lhs, ref mut rhs) => {
                lhs.update_free_variables(added_depth, own_depth);
                rhs.update_free_variables(added_depth, own_depth)
            }
        }
    }

    fn eval(&mut self, count: &mut usize) {
        let to_apply = mem::replace(self, Var(0)); // replace self with a dummy
        let (mut lhs, rhs) = to_apply.unapp().unwrap(); // safe; only called in reduction sites
        lhs.apply(&rhs).unwrap(); // ditto
        mem::replace(self, lhs); // move self back to its place

        *count += 1;
    }

    fn is_reducible(&self, limit: usize, count: &usize) -> bool {
        self.lhs_ref().and_then(|t| t.unabs_ref()).is_ok() && (limit == 0 || *count < limit )
    }

    /// Performs β-reduction on a `Term` with the specified evaluation `Order` and an optional limit
    /// on the number of reductions (`0` means no limit) and returns the number of performed
    /// reductions.
    ///
    /// # Example
    ///
    /// ```
    /// use lambda_calculus::*;
    ///
    /// let mut expression = parse(&"(λa.λb.λc.b (a b c)) (λa.λb.b)", Classic).unwrap();
    /// let reduced        = parse(&"λa.λb.a b", Classic).unwrap();
    ///
    /// expression.reduce(NOR, 0);
    ///
    /// assert_eq!(expression, reduced);
    /// ```
    pub fn reduce(&mut self, order: Order, limit: usize) -> usize {
        let mut count = 0;

        match order {
            CBN => self.beta_cbn(limit, &mut count),
            NOR => self.beta_nor(limit, &mut count),
            CBV => self.beta_cbv(limit, &mut count),
            APP => self.beta_app(limit, &mut count),
            HSP => self.beta_hsp(limit, &mut count),
            HNO => self.beta_hno(limit, &mut count),
            HAP => self.beta_hap(limit, &mut count)
        }

        count
    }

    fn beta_cbn(&mut self, limit: usize, count: &mut usize) {
        if limit != 0 && *count == limit { return }

        if let App(_, _) = *self {
            self.lhs_mut().unwrap().beta_cbn(limit, count);

            if self.is_reducible(limit, count) {
                self.eval(count);
                self.beta_cbn(limit, count);
            }
        }
    }

    fn beta_nor(&mut self, limit: usize, count: &mut usize) {
        if limit != 0 && *count == limit { return }

        match *self {
            Abs(ref mut abstracted) => abstracted.beta_nor(limit, count),
            App(_, _) => {
                self.lhs_mut().unwrap().beta_cbn(limit, count);

                if self.is_reducible(limit, count) {
                    self.eval(count);
                    self.beta_nor(limit, count);
                } else {
                    self.lhs_mut().unwrap().beta_nor(limit, count);
                    self.rhs_mut().unwrap().beta_nor(limit, count);
                }
            },
            _ => ()
        }
    }

    fn beta_cbv(&mut self, limit: usize, count: &mut usize) {
        if limit != 0 && *count == limit { return }

        if let App(_, _) = *self {
            self.lhs_mut().unwrap().beta_cbv(limit, count);
            self.rhs_mut().unwrap().beta_cbv(limit, count);

            if self.is_reducible(limit, count) {
                self.eval(count);
                self.beta_cbv(limit, count);
            }
        }
    }

    fn beta_app(&mut self, limit: usize, count: &mut usize) {
        if limit != 0 && *count == limit { return }

        match *self {
            Abs(ref mut abstracted) => abstracted.beta_app(limit, count),
            App(_, _) => {
                self.lhs_mut().unwrap().beta_app(limit, count);
                self.rhs_mut().unwrap().beta_app(limit, count);

                if self.is_reducible(limit, count) {
                    self.eval(count);
                    self.beta_app(limit, count);
                }
            },
            _ => ()
        }
    }

    fn beta_hap(&mut self, limit: usize, count: &mut usize) {
        if limit != 0 && *count == limit { return }

        match *self {
            Abs(ref mut abstracted) => abstracted.beta_hap(limit, count),
            App(_, _) => {
                self.lhs_mut().unwrap().beta_cbv(limit, count);
                self.rhs_mut().unwrap().beta_hap(limit, count);

                if self.is_reducible(limit, count) {
                    self.eval(count);
                    self.beta_hap(limit, count);
                } else {
                    self.lhs_mut().unwrap().beta_hap(limit, count);
                }
            },
            _ => ()
        }
    }

    fn beta_hsp(&mut self, limit: usize, count: &mut usize) {
        if limit != 0 && *count == limit { return }

        match *self {
            Abs(ref mut abstracted) => abstracted.beta_hsp(limit, count),
            App(_, _) => {
                self.lhs_mut().unwrap().beta_hsp(limit, count);

                if self.is_reducible(limit, count) {
                    self.eval(count);
                    self.beta_hsp(limit, count)
                }
            },
            _ => ()
        }
    }

    fn beta_hno(&mut self, limit: usize, count: &mut usize) {
        if limit != 0 && *count == limit { return }

        match *self {
            Abs(ref mut abstracted) => abstracted.beta_hno(limit, count),
            App(_, _) => {
                self.lhs_mut().unwrap().beta_hsp(limit, count);

                if self.is_reducible(limit, count) {
                    self.eval(count);
                    self.beta_hno(limit, count)
                } else {
                    self.lhs_mut().unwrap().beta_hno(limit, count);
                    self.rhs_mut().unwrap().beta_hno(limit, count);
                }
            },
            _ => ()
        }
    }
}

impl fmt::Display for Order {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", match *self {
            NOR => "normal",
            CBN => "call-by-name",
            HSP => "head spine",
            HNO => "hybrid normal",
            APP => "applicative",
            CBV => "call-by-value",
            HAP => "hybrid applicative"
        })
    }
}
<|endoftext|>


<commit_before>#![crate_name = "irc"]
#![crate_type = "dylib"]
#![license = "MIT/ASL2"]
#![feature(if_let, slicing_syntax, globs, phase)]

#![allow(dead_code)]
#![deny(unused_must_use)]
#![deny(warnings)]

#[phase(plugin, link)] extern crate log;
extern crate time;
extern crate serialize;

pub use self::message::IrcMessage;

pub use self::connection::IrcConnection;
pub use self::connection::RawWrite;

pub use self::event::{
	IrcEvent,
	IrcEventMessage,
	IrcEventJoinBundle,
	IrcEventWhoBundle,
};

pub use self::watchers::{
    RegisterError,
    RegisterErrorType,

    JoinResult,
    JoinSuccess,
    JoinError,

    WhoResult,
    WhoRecord,
    WhoSuccess,
    WhoError,

    BundlerManager,
    JoinBundlerTrigger,
    WhoBundlerTrigger,
};

pub use self::parse::{
    IrcMsgPrefix,
};

mod numerics;
mod connection;
mod message;
mod watchers;
mod core_plugins;

#[experimental = "Subject to being moved"]
/// Experimental utility code
pub mod util;

#[experimental = "Subject to being moved"]
/// Experimental parsing code
pub mod parse;

/// Event types
mod event;
<commit_msg>Stronger lint: deny +unused_variables +unused_mut<commit_after>#![crate_name = "irc"]
#![crate_type = "dylib"]
#![license = "MIT/ASL2"]
#![feature(if_let, slicing_syntax, globs, phase)]

#![allow(dead_code)]
#![deny(unused_must_use, warnings, unused_variables, unused_mut)]

#[phase(plugin, link)] extern crate log;
extern crate time;
extern crate serialize;

pub use self::message::IrcMessage;

pub use self::connection::IrcConnection;
pub use self::connection::RawWrite;

pub use self::event::{
	IrcEvent,
	IrcEventMessage,
	IrcEventJoinBundle,
	IrcEventWhoBundle,
};

pub use self::watchers::{
    RegisterError,
    RegisterErrorType,

    JoinResult,
    JoinSuccess,
    JoinError,

    WhoResult,
    WhoRecord,
    WhoSuccess,
    WhoError,

    BundlerManager,
    JoinBundlerTrigger,
    WhoBundlerTrigger,
};

pub use self::parse::{
    IrcMsgPrefix,
};

mod numerics;
mod connection;
mod message;
mod watchers;
mod core_plugins;

#[experimental = "Subject to being moved"]
/// Experimental utility code
pub mod util;

#[experimental = "Subject to being moved"]
/// Experimental parsing code
pub mod parse;

/// Event types
mod event;
<|endoftext|>


<commit_before>//! Draw ellipse

pub use rectangle::centered;
pub use rectangle::centered_square as circle;

use internal;
use triangulation;
use Graphics;
use Color;
use math::Matrix2d;
use DrawState;

/// Ellipse border
#[derive(Copy, Clone)]
pub struct Border {
    /// The border color
    pub color: internal::Color,
    /// The border radius
    pub radius: internal::Radius,
}

/// An ellipse with filled color
#[derive(Copy, Clone)]
pub struct Ellipse {
    /// The ellipse color
    pub color: internal::Color,
    /// The ellipse border
    pub border: Option<Border>,
}

impl Ellipse {
    /// Creates a new ellipse
    pub fn new(color: internal::Color) -> Ellipse {
        Ellipse {
            color: color,
            border: None
        }
    }

    /// Creates a new ellipse border
    pub fn new_border(
        color: internal::Color,
        radius: internal::Radius
    ) -> Ellipse {
        Ellipse {
            color: [0.0; 4],
            border: Some(Border {
                    color: color,
                    radius: radius,
                })
        }
    }

    /// Draws the ellipse.
    pub fn draw<G>(
        &self,
        rectangle: internal::Rectangle,
        draw_state: &DrawState,
        transform: Matrix2d,
        g: &mut G
    )
        where G: Graphics
    {
        g.tri_list(
            draw_state,
            &self.color,
            |f|
        triangulation::with_ellipse_tri_list(
            128,
            transform,
            rectangle,
            |vertices| f(vertices)
        ));

        if let Some(Border { color, radius: border_radius }) = self.border {
            g.tri_list(
                &draw_state,
                &color,
                |f|
            triangulation::with_ellipse_border_tri_list(
                128,
                transform,
                rectangle,
                border_radius,
                |vertices| f(vertices)
            ));
        }
    }
}

/*
quack! {
    e: Ellipse[]
    get:
        fn () -> Color [] { Color(e.color) }
        fn () -> MaybeBorder [] { MaybeBorder(e.border) }
    set:
        fn (val: Color) [] { e.color = val.0 }
        fn (val: Border) [] { e.border = Some(val) }
        fn (val: MaybeBorder) [] { e.border = val.0 }
    action:
}
*/

#[cfg(test)]
mod test {
    use super::Ellipse;
    use super::Border;
    use Color;
    use quack::Set;

    #[test]
    fn test_ellipse() {
        let _ellipse = Ellipse::new([1.0; 4])
            .set(Color([0.0; 4]))
            .set(Border { color: [1.0; 4], radius: 3.0 });
    }
}
<commit_msg>Added builder methods to `Ellipse`<commit_after>//! Draw ellipse

pub use rectangle::centered;
pub use rectangle::centered_square as circle;

use internal;
use triangulation;
use Graphics;
use Color;
use math::Matrix2d;
use DrawState;

/// Ellipse border
#[derive(Copy, Clone)]
pub struct Border {
    /// The border color
    pub color: internal::Color,
    /// The border radius
    pub radius: internal::Radius,
}

/// An ellipse with filled color
#[derive(Copy, Clone)]
pub struct Ellipse {
    /// The ellipse color
    pub color: internal::Color,
    /// The ellipse border
    pub border: Option<Border>,
}

impl Ellipse {
    /// Creates a new ellipse
    pub fn new(color: internal::Color) -> Ellipse {
        Ellipse {
            color: color,
            border: None
        }
    }

    /// Creates a new ellipse border
    pub fn new_border(
        color: internal::Color,
        radius: internal::Radius
    ) -> Ellipse {
        Ellipse {
            color: [0.0; 4],
            border: Some(Border {
                    color: color,
                    radius: radius,
                })
        }
    }

    /// Sets ellipse color.
    pub fn color(mut self, value: internal::Color) -> Self {
        self.color = value;
        self
    }

    /// Sets ellipse border.
    pub fn border(mut self, value: Border) -> Self {
        self.border = Some(value);
        self
    }

    /// Sets optional ellipse border.
    pub fn maybe_border(mut self, value: Option<Border>) -> Self {
        self.border = value;
        self
    }

    /// Draws the ellipse.
    pub fn draw<G>(
        &self,
        rectangle: internal::Rectangle,
        draw_state: &DrawState,
        transform: Matrix2d,
        g: &mut G
    )
        where G: Graphics
    {
        g.tri_list(
            draw_state,
            &self.color,
            |f|
        triangulation::with_ellipse_tri_list(
            128,
            transform,
            rectangle,
            |vertices| f(vertices)
        ));

        if let Some(Border { color, radius: border_radius }) = self.border {
            g.tri_list(
                &draw_state,
                &color,
                |f|
            triangulation::with_ellipse_border_tri_list(
                128,
                transform,
                rectangle,
                border_radius,
                |vertices| f(vertices)
            ));
        }
    }
}

#[cfg(test)]
mod test {
    use super::Ellipse;
    use super::Border;
    use Color;
    use quack::Set;

    #[test]
    fn test_ellipse() {
        let _ellipse = Ellipse::new([1.0; 4])
            .set(Color([0.0; 4]))
            .set(Border { color: [1.0; 4], radius: 3.0 });
    }
}
<|endoftext|>