<commit_before>// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

mod cmd_finder;

use std::env;
use std::env::consts;
use std::fs;
use std::path::{Path, PathBuf};

static SHADERC_STATIC_LIB: &str = "shaderc_combined";
static SHADERC_SHARED_LIB: &str = "shaderc_shared";
static SHADERC_STATIC_LIB_FILE: &str = "libshaderc_combined.a";
static SHADERC_STATIC_LIB_FILE_MSVC: &str = "shaderc_combined.lib";

fn build_shaderc(shaderc_dir: &PathBuf, use_ninja: bool) -> PathBuf {
    let mut config = cmake::Config::new(shaderc_dir);
    config
        .profile("Release")
        .define("CMAKE_POSITION_INDEPENDENT_CODE", "ON")
        .define("SPIRV_SKIP_EXECUTABLES", "ON")
        .define("SPIRV_WERROR", "OFF")
        .define("SHADERC_SKIP_TESTS", "ON")
        .define("CMAKE_INSTALL_LIBDIR", "lib");
    if use_ninja {
        config.generator("Ninja");
    }
    config.build()
}

fn build_shaderc_msvc(shaderc_dir: &PathBuf) -> PathBuf {
    let linkage = env::var("CARGO_CFG_TARGET_FEATURE").unwrap_or_default();

    let mut config = cmake::Config::new(shaderc_dir);
    config
        .profile("Release")
        .define("CMAKE_POSITION_INDEPENDENT_CODE", "ON")
        .define("SPIRV_SKIP_EXECUTABLES", "ON")
        .define("SPIRV_WERROR", "OFF")
        .define("SHADERC_SKIP_TESTS", "ON")
        .define("CMAKE_INSTALL_LIBDIR", "lib")
        .generator("Ninja");

    // cmake-rs tries to be clever on Windows by injecting several
    // C/C++ flags, which causes problems. So I have to manually
    // define CMAKE_*_FLAGS_* here to suppress that.
    let config = if linkage.contains("crt-static") {
        // statically-linked CRT
        config
            .define("CMAKE_C_FLAGS", " /nologo /EHsc /MT")
            .define("CMAKE_CXX_FLAGS", " /nologo /EHsc /MT")
            .define("CMAKE_C_FLAGS_RELEASE", " /nologo /EHsc /MT")
            .define("CMAKE_CXX_FLAGS_RELEASE", " /nologo /EHsc /MT")
    } else {
        // dynamically-linked CRT
        config
            .define("CMAKE_C_FLAGS", " /nologo /EHsc /MD")
            .define("CMAKE_CXX_FLAGS", " /nologo /EHsc /MD")
            .define("CMAKE_C_FLAGS_RELEASE", " /nologo /EHsc /MD")
            .define("CMAKE_CXX_FLAGS_RELEASE", " /nologo /EHsc /MD")
            // prevent shaderc's cmake script messes with crt flags
            .define("SHADERC_ENABLE_SHARED_CRT", "ON")
    };

    config.build()
}

fn probe_linux_lib_kind(lib_name: &str, search_dir: &PathBuf) -> Option<&'static str> {
    if search_dir.join(&format!("lib{}.so", lib_name)).exists() {
        Some("dylib")
    } else if search_dir.join(&format!("lib{}.a", lib_name)).exists() {
        Some("static")
    } else {
        None
    }
}

fn main() {
    let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
    let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap();
    let config_build_from_source = env::var("CARGO_FEATURE_BUILD_FROM_SOURCE").is_ok();
    let explicit_lib_dir_set = env::var("SHADERC_LIB_DIR").is_ok();

    // Initialize explicit libshaderc search directory first
    let mut search_dir = if let Ok(lib_dir) = env::var("SHADERC_LIB_DIR") {
        println!(
            "cargo:warning=Specified {} to search for shaderc libraries",
            lib_dir
        );
        Some(lib_dir)
    } else {
        None
    };

    // If no explicit path is set and no explicit request is made to build from
    // source, check known locations before falling back to from-source-build
    if search_dir.is_none() && target_os == "linux" && !config_build_from_source {
        println!(
            "cargo:warning=Checking for system installed libraries.  \
             Use --features = build-from-source to disable this behavior"
        );
        search_dir = Some("/usr/lib/".to_owned());
    }

    // Try to build with the static lib if a path was received or chosen
    let search_dir = if let Some(search_dir) = search_dir {
        let path = Path::new(&search_dir);
        let cannonical = fs::canonicalize(&path);
        if path.is_relative() {
            println!(
                "cargo:warning=Provided path {:?} is relative.  Path must be \
                 relative to shaderc-sys crate, possibly not your current \
                 working directory",
                &path
            );
        } else if !path.is_dir() {
            println!("cargo:warning=Provided path {:?} is not a directory", &path);
        }
        if (cannonical.is_err()) && explicit_lib_dir_set {
            println!("cargo:warning={:?}", cannonical.err().unwrap());
            println!(
                "cargo:warning=Provided path {:?} could not be canonicallized",
                &path
            );
            None
        } else {
            cannonical.ok()
        }
    } else {
        None
    };

    if let Some(search_dir) = search_dir {
        let search_dir_str = search_dir.to_string_lossy();

        let combined_lib_path =
            search_dir.join(if target_os == "windows" && target_env == "msvc" {
                SHADERC_STATIC_LIB_FILE_MSVC
            } else {
                SHADERC_STATIC_LIB_FILE
            });

        let dylib_name = format!(
            "{}{}{}",
            consts::DLL_PREFIX,
            SHADERC_SHARED_LIB,
            consts::DLL_SUFFIX
        );
        let dylib_path = search_dir.join(dylib_name);

        if let Some((lib_name, kind)) = {
            if combined_lib_path.exists() {
                Some((SHADERC_STATIC_LIB, "static"))
            } else if dylib_path.exists() {
                Some((SHADERC_SHARED_LIB, "dylib"))
            } else {
                None
            }
        } {
            match (target_os.as_str(), target_env.as_str()) {
                ("linux", _) => {
                    println!("cargo:rustc-link-search=native={}", search_dir_str);

                    // Libraries from the SPIRV-Tools project
                    let spirv_tools_lib = probe_linux_lib_kind("SPIRV-Tools", &search_dir);
                    let spirv_tools_opt_lib = probe_linux_lib_kind("SPIRV-Tools-opt", &search_dir);
                    // Libraries from the Glslang project
                    let spirv_lib = probe_linux_lib_kind("SPIRV", &search_dir);
                    let glslang_lib = probe_linux_lib_kind("glslang", &search_dir);

                    match (spirv_tools_lib, spirv_tools_opt_lib, spirv_lib, glslang_lib) {
                        (Some(spirv_tools), Some(spirv_tools_opt), Some(spirv), Some(glslang)) => {
                            println!("cargo:warning=Found and linking system installed Glslang and SPIRV-Tools libraries.");
                            println!("cargo:rustc-link-lib={}=glslang", glslang);
                            println!("cargo:rustc-link-lib={}=SPIRV", spirv);
                            println!(
                                "cargo:rustc-link-lib={}=SPIRV-Tools",
                                spirv_tools
                            );
                            println!(
                                "cargo:rustc-link-lib={}=SPIRV-Tools-opt",
                                spirv_tools_opt
                            );
                        }
                        _ => {
                            println!(
                                "cargo:warning=Only shaderc library found.  \
                                 Assuming libraries it depends on are built into \
                                 the shaderc library."
                            );
                        }
                    }
                    println!("cargo:rustc-link-lib={}={}", kind, lib_name);
                    println!("cargo:rustc-link-lib=dylib=stdc++");
                    return;
                }
                ("windows", "msvc") => {
                    println!("cargo:warning=Windows MSVC static builds experimental");
                    println!("cargo:rustc-link-search=native={}", search_dir_str);
                    println!("cargo:rustc-link-lib={}={}", kind, lib_name);
                    return;
                }
                ("windows", "gnu") => {
                    println!("cargo:warning=Windows MinGW static builds experimental");
                    println!("cargo:rustc-link-search=native={}", search_dir_str);
                    println!("cargo:rustc-link-lib={}={}", kind, lib_name);
                    println!("cargo:rustc-link-lib=dylib=stdc++");
                    return;
                }
                ("macos", _) => {
                    println!("cargo:warning=MacOS static builds experimental");
                    println!("cargo:rustc-link-search=native={}", search_dir_str);
                    println!("cargo:rustc-link-lib={}={}", kind, lib_name);
                    println!("cargo:rustc-link-lib=dylib=c++");
                    return;
                }
                (_, _) => {
                    println!("cargo:warning=Platform unsupported for linking against system installed shaderc libraries");
                }
            }
        }
    }

    if config_build_from_source {
        println!("cargo:warning=Requested to build from source");
    } else {
        println!(
            "cargo:warning=System installed library not found.  Falling back \
             to build from source"
        );
    }

    let mut finder = cmd_finder::CommandFinder::new();

    finder.must_have("cmake");
    finder.must_have("git");
    finder.must_have("python");

    let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
    let shaderc_dir = Path::new(&manifest_dir).join("build");

    let mut lib_path = if target_env == "msvc" {
        finder.must_have("ninja");
        build_shaderc_msvc(&shaderc_dir)
    } else {
        let has_ninja = finder.maybe_have("ninja").is_some();
        build_shaderc(&shaderc_dir, has_ninja)
    };

    lib_path.push("lib");
    println!("cargo:rustc-link-search=native={}", lib_path.display());
    println!("cargo:rustc-link-lib=static=shaderc_combined");

    emit_std_cpp_link();
}

fn emit_std_cpp_link() {
    let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
    let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap();

    match (target_os.as_str(), target_env.as_str()) {
        ("linux", _) | ("windows", "gnu") => println!("cargo:rustc-link-lib=dylib=stdc++"),
        ("macos", _) => println!("cargo:rustc-link-lib=dylib=c++"),
        _ => {}
    }
}
<commit_msg>Search additional directories for already intalled libraries on Linux (#82)<commit_after>// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

mod cmd_finder;

use std::env;
use std::env::consts;
use std::fs;
use std::path::{Path, PathBuf};

static SHADERC_STATIC_LIB: &str = "shaderc_combined";
static SHADERC_SHARED_LIB: &str = "shaderc_shared";
static SHADERC_STATIC_LIB_FILE: &str = "libshaderc_combined.a";
static SHADERC_STATIC_LIB_FILE_MSVC: &str = "shaderc_combined.lib";

fn build_shaderc(shaderc_dir: &PathBuf, use_ninja: bool) -> PathBuf {
    let mut config = cmake::Config::new(shaderc_dir);
    config
        .profile("Release")
        .define("CMAKE_POSITION_INDEPENDENT_CODE", "ON")
        .define("SPIRV_SKIP_EXECUTABLES", "ON")
        .define("SPIRV_WERROR", "OFF")
        .define("SHADERC_SKIP_TESTS", "ON")
        .define("CMAKE_INSTALL_LIBDIR", "lib");
    if use_ninja {
        config.generator("Ninja");
    }
    config.build()
}

fn build_shaderc_msvc(shaderc_dir: &PathBuf) -> PathBuf {
    let linkage = env::var("CARGO_CFG_TARGET_FEATURE").unwrap_or_default();

    let mut config = cmake::Config::new(shaderc_dir);
    config
        .profile("Release")
        .define("CMAKE_POSITION_INDEPENDENT_CODE", "ON")
        .define("SPIRV_SKIP_EXECUTABLES", "ON")
        .define("SPIRV_WERROR", "OFF")
        .define("SHADERC_SKIP_TESTS", "ON")
        .define("CMAKE_INSTALL_LIBDIR", "lib")
        .generator("Ninja");

    // cmake-rs tries to be clever on Windows by injecting several
    // C/C++ flags, which causes problems. So I have to manually
    // define CMAKE_*_FLAGS_* here to suppress that.
    let config = if linkage.contains("crt-static") {
        // statically-linked CRT
        config
            .define("CMAKE_C_FLAGS", " /nologo /EHsc /MT")
            .define("CMAKE_CXX_FLAGS", " /nologo /EHsc /MT")
            .define("CMAKE_C_FLAGS_RELEASE", " /nologo /EHsc /MT")
            .define("CMAKE_CXX_FLAGS_RELEASE", " /nologo /EHsc /MT")
    } else {
        // dynamically-linked CRT
        config
            .define("CMAKE_C_FLAGS", " /nologo /EHsc /MD")
            .define("CMAKE_CXX_FLAGS", " /nologo /EHsc /MD")
            .define("CMAKE_C_FLAGS_RELEASE", " /nologo /EHsc /MD")
            .define("CMAKE_CXX_FLAGS_RELEASE", " /nologo /EHsc /MD")
            // prevent shaderc's cmake script messes with crt flags
            .define("SHADERC_ENABLE_SHARED_CRT", "ON")
    };

    config.build()
}

fn probe_linux_lib_kind(lib_name: &str, search_dir: &PathBuf) -> Option<&'static str> {
    if search_dir.join(&format!("lib{}.so", lib_name)).exists() {
        Some("dylib")
    } else if search_dir.join(&format!("lib{}.a", lib_name)).exists() {
        Some("static")
    } else {
        None
    }
}

fn main() {
    let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
    let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap();
    let config_build_from_source = env::var("CARGO_FEATURE_BUILD_FROM_SOURCE").is_ok();
    let explicit_lib_dir_set = env::var("SHADERC_LIB_DIR").is_ok();

    // Initialize explicit libshaderc search directory first
    let mut search_dir = if let Ok(lib_dir) = env::var("SHADERC_LIB_DIR") {
        println!(
            "cargo:warning=Specified {} to search for shaderc libraries",
            lib_dir
        );
        Some(lib_dir)
    } else {
        None
    };

    // If no explicit path is set and no explicit request is made to build from
    // source, check known locations before falling back to from-source-build
    if search_dir.is_none() && target_os == "linux" && !config_build_from_source {
        println!(
            "cargo:warning=Checking for system installed libraries.  \
             Use --features = build-from-source to disable this behavior"
        );

        // https://wiki.ubuntu.com/MultiarchSpec
        // https://wiki.debian.org/Multiarch/Implementation
        let debian_arch = match env::var("CARGO_CFG_TARGET_ARCH").unwrap() {
            arch if arch == "x86" => "i386".to_owned(),
            arch => arch,
        };
        let debian_triple_path = format!("/usr/lib/{}-linux-gnu/", debian_arch);

        search_dir = if Path::new(&debian_triple_path).exists() {
            // Debian, Ubuntu and their derivatives.
            Some(debian_triple_path)
        } else if env::var("CARGO_CFG_TARGET_ARCH").unwrap() == "x86_64"
                && Path::new("/usr/lib64/").exists() {
            // Other distributions running on x86_64 usually use this path.
            Some("/usr/lib64/".to_owned())
        } else {
            // Other distributions, not x86_64.
            Some("/usr/lib/".to_owned())
        };
    }

    // Try to build with the static lib if a path was received or chosen
    let search_dir = if let Some(search_dir) = search_dir {
        let path = Path::new(&search_dir);
        let cannonical = fs::canonicalize(&path);
        if path.is_relative() {
            println!(
                "cargo:warning=Provided path {:?} is relative.  Path must be \
                 relative to shaderc-sys crate, possibly not your current \
                 working directory",
                &path
            );
        } else if !path.is_dir() {
            println!("cargo:warning=Provided path {:?} is not a directory", &path);
        }
        if (cannonical.is_err()) && explicit_lib_dir_set {
            println!("cargo:warning={:?}", cannonical.err().unwrap());
            println!(
                "cargo:warning=Provided path {:?} could not be canonicallized",
                &path
            );
            None
        } else {
            cannonical.ok()
        }
    } else {
        None
    };

    if let Some(search_dir) = search_dir {
        let search_dir_str = search_dir.to_string_lossy();

        let combined_lib_path =
            search_dir.join(if target_os == "windows" && target_env == "msvc" {
                SHADERC_STATIC_LIB_FILE_MSVC
            } else {
                SHADERC_STATIC_LIB_FILE
            });

        let dylib_name = format!(
            "{}{}{}",
            consts::DLL_PREFIX,
            SHADERC_SHARED_LIB,
            consts::DLL_SUFFIX
        );
        let dylib_path = search_dir.join(dylib_name);

        if let Some((lib_name, kind)) = {
            if combined_lib_path.exists() {
                Some((SHADERC_STATIC_LIB, "static"))
            } else if dylib_path.exists() {
                Some((SHADERC_SHARED_LIB, "dylib"))
            } else {
                None
            }
        } {
            match (target_os.as_str(), target_env.as_str()) {
                ("linux", _) => {
                    println!("cargo:rustc-link-search=native={}", search_dir_str);

                    // Libraries from the SPIRV-Tools project
                    let spirv_tools_lib = probe_linux_lib_kind("SPIRV-Tools", &search_dir);
                    let spirv_tools_opt_lib = probe_linux_lib_kind("SPIRV-Tools-opt", &search_dir);
                    // Libraries from the Glslang project
                    let spirv_lib = probe_linux_lib_kind("SPIRV", &search_dir);
                    let glslang_lib = probe_linux_lib_kind("glslang", &search_dir);

                    match (spirv_tools_lib, spirv_tools_opt_lib, spirv_lib, glslang_lib) {
                        (Some(spirv_tools), Some(spirv_tools_opt), Some(spirv), Some(glslang)) => {
                            println!("cargo:warning=Found and linking system installed Glslang and SPIRV-Tools libraries.");
                            println!("cargo:rustc-link-lib={}=glslang", glslang);
                            println!("cargo:rustc-link-lib={}=SPIRV", spirv);
                            println!(
                                "cargo:rustc-link-lib={}=SPIRV-Tools",
                                spirv_tools
                            );
                            println!(
                                "cargo:rustc-link-lib={}=SPIRV-Tools-opt",
                                spirv_tools_opt
                            );
                        }
                        _ => {
                            println!(
                                "cargo:warning=Only shaderc library found.  \
                                 Assuming libraries it depends on are built into \
                                 the shaderc library."
                            );
                        }
                    }
                    println!("cargo:rustc-link-lib={}={}", kind, lib_name);
                    println!("cargo:rustc-link-lib=dylib=stdc++");
                    return;
                }
                ("windows", "msvc") => {
                    println!("cargo:warning=Windows MSVC static builds experimental");
                    println!("cargo:rustc-link-search=native={}", search_dir_str);
                    println!("cargo:rustc-link-lib={}={}", kind, lib_name);
                    return;
                }
                ("windows", "gnu") => {
                    println!("cargo:warning=Windows MinGW static builds experimental");
                    println!("cargo:rustc-link-search=native={}", search_dir_str);
                    println!("cargo:rustc-link-lib={}={}", kind, lib_name);
                    println!("cargo:rustc-link-lib=dylib=stdc++");
                    return;
                }
                ("macos", _) => {
                    println!("cargo:warning=MacOS static builds experimental");
                    println!("cargo:rustc-link-search=native={}", search_dir_str);
                    println!("cargo:rustc-link-lib={}={}", kind, lib_name);
                    println!("cargo:rustc-link-lib=dylib=c++");
                    return;
                }
                (_, _) => {
                    println!("cargo:warning=Platform unsupported for linking against system installed shaderc libraries");
                }
            }
        }
    }

    if config_build_from_source {
        println!("cargo:warning=Requested to build from source");
    } else {
        println!(
            "cargo:warning=System installed library not found.  Falling back \
             to build from source"
        );
    }

    let mut finder = cmd_finder::CommandFinder::new();

    finder.must_have("cmake");
    finder.must_have("git");
    finder.must_have("python");

    let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
    let shaderc_dir = Path::new(&manifest_dir).join("build");

    let mut lib_path = if target_env == "msvc" {
        finder.must_have("ninja");
        build_shaderc_msvc(&shaderc_dir)
    } else {
        let has_ninja = finder.maybe_have("ninja").is_some();
        build_shaderc(&shaderc_dir, has_ninja)
    };

    lib_path.push("lib");
    println!("cargo:rustc-link-search=native={}", lib_path.display());
    println!("cargo:rustc-link-lib=static=shaderc_combined");

    emit_std_cpp_link();
}

fn emit_std_cpp_link() {
    let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
    let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap();

    match (target_os.as_str(), target_env.as_str()) {
        ("linux", _) | ("windows", "gnu") => println!("cargo:rustc-link-lib=dylib=stdc++"),
        ("macos", _) => println!("cargo:rustc-link-lib=dylib=c++"),
        _ => {}
    }
}
<|endoftext|>


<commit_before>use direction::Orientation;
use std::iter;

/// A generic structure with a value for each axis.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct XY<T> {
    /// X-axis value
    pub x: T,
    /// Y-axis value
    pub y: T,
}

impl<T> XY<T> {
    /// Creates a new `XY` from the given values.
    pub fn new(x: T, y: T) -> Self {
        XY { x: x, y: y }
    }

    /// Creates a new `XY` by applying `f` to `x` and `y`.
    pub fn map<U, F: Fn(T) -> U>(self, f: F) -> XY<U> {
        XY::new(f(self.x), f(self.y))
    }

    /// Creates a new `XY` by applying `f` to `x`, and carrying `y` over.
    pub fn map_x<F: Fn(T) -> T>(self, f: F) -> Self {
        XY::new(f(self.x), self.y)
    }

    /// Creates a new `XY` by applying `f` to `y`, and carrying `x` over.
    pub fn map_y<F: Fn(T) -> T>(self, f: F) -> Self {
        XY::new(self.x, f(self.y))
    }

    /// Destructure self into a pair.
    pub fn pair(self) -> (T, T) {
        (self.x, self.y)
    }

    /// Return a `XY` with references to this one's values.
    pub fn as_ref(&self) -> XY<&T> {
        XY::new(&self.x, &self.y)
    }

    /// Creates an iterator that returns references to `x`, then `y`.
    pub fn iter(&self) -> iter::Chain<iter::Once<&T>, iter::Once<&T>> {
        iter::once(&self.x).chain(iter::once(&self.y))
    }

    /// Returns a reference to the value on the given axis.
    pub fn get(&self, o: Orientation) -> &T {
        match o {
            Orientation::Horizontal => &self.x,
            Orientation::Vertical => &self.y,
        }
    }

    /// Returns a mutable reference to the value on the given axis.
    pub fn get_mut(&mut self, o: Orientation) -> &mut T {
        match o {
            Orientation::Horizontal => &mut self.x,
            Orientation::Vertical => &mut self.y,
        }
    }

    /// Returns a new `XY` of tuples made by zipping `self` and `other`.
    pub fn zip<U>(self, other: XY<U>) -> XY<(T, U)> {
        XY::new((self.x, other.x), (self.y, other.y))
    }

    /// Returns a new `XY` by calling `f` on `self` and `other` for each axis.
    pub fn zip_map<U, V, F: Fn(T, U) -> V>(self, other: XY<U>, f: F) -> XY<V> {
        XY::new(f(self.x, other.x), f(self.y, other.y))
    }
}

impl<T: Clone> XY<T> {
    /// Returns a new `XY` with the axis `o` set to `value`.
    pub fn with_axis(&self, o: Orientation, value: T) -> Self {
        let mut new = self.clone();
        *o.get_ref(&mut new) = value;
        new
    }

    /// Returns a new `XY` with the axis `o` set to the value from `other`.
    pub fn with_axis_from(&self, o: Orientation, other: &Self) -> Self {
        let mut new = self.clone();
        new.set_axis_from(o, other);
        new
    }

    /// Sets the axis `o` on `self` to the value from `other`.
    pub fn set_axis_from(&mut self, o: Orientation, other: &Self) {
        *o.get_ref(self) = o.get(other);
    }
}

impl<T> XY<Option<T>> {
    /// Returns a new `XY` by calling `unwrap_or` on each axis.
    pub fn unwrap_or(self, other: XY<T>) -> XY<T> {
        self.zip_map(other, |s, o| s.unwrap_or(o))
    }
}

impl XY<bool> {
    /// Returns `true` if any of `x` or `y` is `true`.
    pub fn any(&self) -> bool {
        self.x || self.y
    }

    /// Returns `true` if both `x` and `y` are `true`.
    pub fn both(&self) -> bool {
        self.x && self.y
    }
}

impl<T: Copy> XY<T> {
    /// Creates a `XY` with both `x` and `y` set to `value`.
    pub fn both_from(value: T) -> Self {
        XY::new(value, value)
    }
}

impl<T> From<(T, T)> for XY<T> {
    fn from((x, y): (T, T)) -> Self {
        XY::new(x, y)
    }
}
<commit_msg>Add some more XY methods<commit_after>use direction::Orientation;
use std::iter;

/// A generic structure with a value for each axis.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct XY<T> {
    /// X-axis value
    pub x: T,
    /// Y-axis value
    pub y: T,
}

impl<T> XY<T> {
    /// Creates a new `XY` from the given values.
    pub fn new(x: T, y: T) -> Self {
        XY { x: x, y: y }
    }

    /// Returns `f(self.x, self.y)`
    pub fn fold<U, F>(self, f: F) -> U
    where
        F: FnOnce(T, T) -> U,
    {
        f(self.x, self.y)
    }

    /// Creates a new `XY` by applying `f` to `x` and `y`.
    pub fn map<U, F>(self, f: F) -> XY<U>
    where
        F: Fn(T) -> U,
    {
        XY::new(f(self.x), f(self.y))
    }

    /// Applies `f` on axis where `condition` is true.
    ///
    /// Carries over `self` otherwise.
    pub fn map_if<F>(self, condition: XY<bool>, f: F) -> Self
    where
        F: Fn(T) -> T,
    {
        self.zip_map(condition, |v, c| if c { f(v) } else { v })
    }

    /// Creates a new `XY` by applying `f` to `x`, and carrying `y` over.
    pub fn map_x<F>(self, f: F) -> Self
    where
        F: FnOnce(T) -> T,
    {
        XY::new(f(self.x), self.y)
    }

    /// Creates a new `XY` by applying `f` to `y`, and carrying `x` over.
    pub fn map_y<F>(self, f: F) -> Self
    where
        F: FnOnce(T) -> T,
    {
        XY::new(self.x, f(self.y))
    }

    /// Destructure self into a pair.
    pub fn pair(self) -> (T, T) {
        (self.x, self.y)
    }

    /// Return a `XY` with references to this one's values.
    pub fn as_ref(&self) -> XY<&T> {
        XY::new(&self.x, &self.y)
    }

    /// Creates an iterator that returns references to `x`, then `y`.
    pub fn iter(&self) -> iter::Chain<iter::Once<&T>, iter::Once<&T>> {
        iter::once(&self.x).chain(iter::once(&self.y))
    }

    /// Returns a reference to the value on the given axis.
    pub fn get(&self, o: Orientation) -> &T {
        match o {
            Orientation::Horizontal => &self.x,
            Orientation::Vertical => &self.y,
        }
    }

    /// Returns a mutable reference to the value on the given axis.
    pub fn get_mut(&mut self, o: Orientation) -> &mut T {
        match o {
            Orientation::Horizontal => &mut self.x,
            Orientation::Vertical => &mut self.y,
        }
    }

    /// Returns a new `XY` of tuples made by zipping `self` and `other`.
    pub fn zip<U>(self, other: XY<U>) -> XY<(T, U)> {
        XY::new((self.x, other.x), (self.y, other.y))
    }

    /// Returns a new `XY` by calling `f` on `self` and `other` for each axis.
    pub fn zip_map<U, V, F>(self, other: XY<U>, f: F) -> XY<V>
    where
        F: Fn(T, U) -> V,
    {
        XY::new(f(self.x, other.x), f(self.y, other.y))
    }
}

impl<T: Clone> XY<T> {
    /// Returns a new `XY` with the axis `o` set to `value`.
    pub fn with_axis(&self, o: Orientation, value: T) -> Self {
        let mut new = self.clone();
        *o.get_ref(&mut new) = value;
        new
    }

    /// Returns a new `XY` with the axis `o` set to the value from `other`.
    pub fn with_axis_from(&self, o: Orientation, other: &Self) -> Self {
        let mut new = self.clone();
        new.set_axis_from(o, other);
        new
    }

    /// Sets the axis `o` on `self` to the value from `other`.
    pub fn set_axis_from(&mut self, o: Orientation, other: &Self) {
        *o.get_ref(self) = o.get(other);
    }
}

impl<T> XY<Option<T>> {
    /// Returns a new `XY` by calling `unwrap_or` on each axis.
    pub fn unwrap_or(self, other: XY<T>) -> XY<T> {
        self.zip_map(other, Option::unwrap_or)
    }
}

impl XY<bool> {
    /// Returns `true` if any of `x` or `y` is `true`.
    pub fn any(&self) -> bool {
        use std::ops::BitOr;
        self.fold(BitOr::bitor)
    }

    /// Returns `true` if both `x` and `y` are `true`.
    pub fn both(&self) -> bool {
        use std::ops::BitAnd;
        self.fold(BitAnd::bitand)
    }

    /// For each axis, keeps elements from `other` if `self` is `true`.
    pub fn select<T>(&self, other: XY<T>) -> XY<Option<T>> {
        self.zip_map(
            other,
            |keep, o| if keep { Some(o) } else { None },
        )
    }

    /// For each axis, selects `if_true` if `self` is true, else `if_false`.
    pub fn select_or<T>(&self, if_true: XY<T>, if_false: XY<T>) -> XY<T> {
        self.select(if_true).unwrap_or(if_false)
    }
}

impl<T: Copy> XY<T> {
    /// Creates a `XY` with both `x` and `y` set to `value`.
    pub fn both_from(value: T) -> Self {
        XY::new(value, value)
    }
}

impl<T> From<(T, T)> for XY<T> {
    fn from((x, y): (T, T)) -> Self {
        XY::new(x, y)
    }
}

impl<T, U> From<(XY<T>, XY<U>)> for XY<(T, U)> {
    fn from((t, u): (XY<T>, XY<U>)) -> Self {
        t.zip(u)
    }
}
<|endoftext|>


<commit_before>use std::result::Result;

use serde_json::*;
use gossyp_base::environment::Environment;

use super::bound_script::*;
use super::evaluate_expression::*;
use super::binding_environment::*;
use super::bind_statement::*;
use super::script::*;
use super::script_interpreter::*;

///
/// Enumeration representing how a failed bound statement can be described
///
#[derive(Deserialize, Serialize, Clone)]
pub enum FailedBoundStatement {
    RunCommand(FailedBoundExpression),
    Sequence(Vec<FailedBoundStatement>),
    Let(ScriptToken),
    Var(ScriptToken),
    Assign(ScriptToken),
    Loop(Box<FailedBoundStatement>),
    While(FailedBoundExpression),
    Using(FailedBoundExpression),
    Def(ScriptToken)
}

///
/// Generates a JS value indicating the statement that failed
///
fn generate_failed_bound_statement(script: &BoundScript) -> FailedBoundStatement {
    use self::FailedBoundStatement::*;

    match script {
        &BoundScript::AllocateVariables(_, ref script)  => generate_failed_bound_statement(&**script),
        &BoundScript::RunCommand(ref expr)              => RunCommand(generate_failed_bound_expression(expr)),
        &BoundScript::Sequence(ref statements)          => Sequence(statements.iter().map(|statement| generate_failed_bound_statement(statement)).collect()),
        &BoundScript::Assign(_, _, ref token)           => Assign(token.clone()),
        &BoundScript::Let(_, _, ref token)              => Let(token.clone()),
        &BoundScript::Var(_, _, ref token)              => Var(token.clone()),
        &BoundScript::Loop(ref loop_box)                => Loop(Box::new(generate_failed_bound_statement(&**loop_box))),
        &BoundScript::While(ref expr, _)                => While(generate_failed_bound_expression(expr)),
        &BoundScript::Using(ref expr, _)                => Using(generate_failed_bound_expression(expr)),
        &BoundScript::Def(ref token, _, _)              => Def(token.clone()),
    }
}

///
/// Creates an execution error
///
fn generate_script_error(error: ScriptEvaluationError, script: &BoundScript) -> Value {
    json![{
        "error":                    error,
        "failed-bound-statement":   generate_failed_bound_statement(script)
    }]
}

///
/// Evaluates the result of executing a sequence of steps
///
pub fn evaluate_sequence(sequence: &Vec<BoundScript>, environment: &Environment, execution_environment: &mut ScriptExecutionEnvironment) -> Result<Value, Value> {
    // Execute the script
    let mut result = vec![];
    for statement in sequence.iter() {
        // Evaluate the next statement
        let next_result = evaluate_statement(statement, environment, execution_environment)?;

        // The script result is built up from the result of each statement
        // TODO: unless there's something like a return statement?
        result.push(next_result);
    }

    // Script is done
    Ok(Value::Array(result))
}

///
/// Allocates variables before continuing
///
fn evaluate_allocate_variables(num_variables: u32, continuation: &BoundScript, environment: &Environment, execution_environment: &mut ScriptExecutionEnvironment) -> Result<Value, Value> {
    execution_environment.allocate_variables(num_variables);
    evaluate_statement(continuation, environment, execution_environment)
}

///
/// Assigns a value to a particular variable
///
fn evaluate_assignment(variable_index: u32, expr: &BoundExpression, environment: &Environment, execution_environment: &mut ScriptExecutionEnvironment) -> Result<Value, Value> {
    let expression_value = evaluate_expression(expr, environment, execution_environment)?;
    execution_environment.set_variable(variable_index, Box::new(expression_value.clone()));

    Ok(expression_value)
}

///
/// Evaluates the result of executing a single statement
///
pub fn evaluate_statement(statement: &BoundScript, environment: &Environment, execution_environment: &mut ScriptExecutionEnvironment) -> Result<Value, Value> {
    match statement {
        &BoundScript::AllocateVariables(num, ref continuation)  => evaluate_allocate_variables(num, &**continuation, environment, execution_environment),
        &BoundScript::RunCommand(ref expr)                      => evaluate_expression(expr, environment, execution_environment),
        &BoundScript::Sequence(ref steps)                       => evaluate_sequence(steps, environment, execution_environment),
        &BoundScript::Var(index, ref expr, _)                   => evaluate_assignment(index, expr, environment, execution_environment),
        &BoundScript::Assign(index, ref expr, _)                => evaluate_assignment(index, expr, environment, execution_environment),

        _                                                       => Err(generate_script_error(ScriptEvaluationError::StatementNotImplemented, statement))
    }
}

///
/// Evaluates the result of executing a single statement
///
pub fn evaluate_unbound_statement(statement: &Script, environment: &Environment, execution_environment: &mut ScriptExecutionEnvironment) -> Result<Value, Value> {
    let mut binding_environment = BindingEnvironment::from_environment(environment);
    let bound                   = bind_statement(statement, &mut *binding_environment)?;

    evaluate_statement(&bound, environment, execution_environment)
}

#[cfg(test)]
mod test {
    use gossyp_base::basic::*;
    use super::*;
    use super::super::evaluate::*;

    #[test]
    fn can_execute_run_command() {
        let tool_expr           = Script::RunCommand(Expression::identifier("test"));
        let tool_environment    = DynamicEnvironment::new();

        tool_environment.define("test", Box::new(make_pure_tool(|_: ()| "Success")));

        let mut env             = ScriptExecutionEnvironment::new();
        let result              = evaluate_unbound_statement(&tool_expr, &tool_environment, &mut env);

        assert!(result == Ok(Value::String(String::from("Success"))));
    }

    #[test]
    fn can_execute_sequence() {
        let tool_expr           = Script::Sequence(
            vec![
                Script::RunCommand(Expression::string("\"test 1\"")),
                Script::RunCommand(Expression::string("\"test 2\""))
            ]);
        let tool_environment    = DynamicEnvironment::new();

        let mut env             = ScriptExecutionEnvironment::new();
        let result              = evaluate_unbound_statement(&tool_expr, &tool_environment, &mut env);

        assert!(result == Ok(Value::Array(vec![
            Value::String(String::from("test 1")),
            Value::String(String::from("test 2"))
        ])));
    }

    #[test]
    fn commands_have_own_environment() {
        let environment = DynamicEnvironment::new();

        // Tool that defines a new tool in its environment
        assert!(define_dynamic_tool(&environment, "make_tool", |_: (), env| define_pure_tool(env, "subtool", |_: ()| ())).is_ok());

        // Tools should get their own sub-environment so the new tool should not 'escape'. Need to do make_tool().subtool() to call the subtool
        assert!(gossyp_eval("subtool", &environment).is_err());
        assert!(gossyp_eval("make_tool", &environment).is_ok());
        assert!(gossyp_eval("subtool", &environment).is_err());
    }

    /*
    #[test]
    fn can_call_subtools() {
        let environment = DynamicEnvironment::new();

        // Tool that defines a new tool in its environment
        assert!(define_dynamic_tool(&environment, "make_tool", |_: (), env| define_pure_tool(env, "subtool", |_: ()| ())).is_ok());

        let subtool_result = gossyp_eval("make_tool().subtool()", &environment);
        if !subtool_result.is_ok() { println!("{:?}", subtool_result); }
        assert!(subtool_result.is_ok());
    }
    */
}
<commit_msg>Test that it’s possible to look things up from environments on the outside<commit_after>use std::result::Result;

use serde_json::*;
use gossyp_base::environment::Environment;

use super::bound_script::*;
use super::evaluate_expression::*;
use super::binding_environment::*;
use super::bind_statement::*;
use super::script::*;
use super::script_interpreter::*;

///
/// Enumeration representing how a failed bound statement can be described
///
#[derive(Deserialize, Serialize, Clone)]
pub enum FailedBoundStatement {
    RunCommand(FailedBoundExpression),
    Sequence(Vec<FailedBoundStatement>),
    Let(ScriptToken),
    Var(ScriptToken),
    Assign(ScriptToken),
    Loop(Box<FailedBoundStatement>),
    While(FailedBoundExpression),
    Using(FailedBoundExpression),
    Def(ScriptToken)
}

///
/// Generates a JS value indicating the statement that failed
///
fn generate_failed_bound_statement(script: &BoundScript) -> FailedBoundStatement {
    use self::FailedBoundStatement::*;

    match script {
        &BoundScript::AllocateVariables(_, ref script)  => generate_failed_bound_statement(&**script),
        &BoundScript::RunCommand(ref expr)              => RunCommand(generate_failed_bound_expression(expr)),
        &BoundScript::Sequence(ref statements)          => Sequence(statements.iter().map(|statement| generate_failed_bound_statement(statement)).collect()),
        &BoundScript::Assign(_, _, ref token)           => Assign(token.clone()),
        &BoundScript::Let(_, _, ref token)              => Let(token.clone()),
        &BoundScript::Var(_, _, ref token)              => Var(token.clone()),
        &BoundScript::Loop(ref loop_box)                => Loop(Box::new(generate_failed_bound_statement(&**loop_box))),
        &BoundScript::While(ref expr, _)                => While(generate_failed_bound_expression(expr)),
        &BoundScript::Using(ref expr, _)                => Using(generate_failed_bound_expression(expr)),
        &BoundScript::Def(ref token, _, _)              => Def(token.clone()),
    }
}

///
/// Creates an execution error
///
fn generate_script_error(error: ScriptEvaluationError, script: &BoundScript) -> Value {
    json![{
        "error":                    error,
        "failed-bound-statement":   generate_failed_bound_statement(script)
    }]
}

///
/// Evaluates the result of executing a sequence of steps
///
pub fn evaluate_sequence(sequence: &Vec<BoundScript>, environment: &Environment, execution_environment: &mut ScriptExecutionEnvironment) -> Result<Value, Value> {
    // Execute the script
    let mut result = vec![];
    for statement in sequence.iter() {
        // Evaluate the next statement
        let next_result = evaluate_statement(statement, environment, execution_environment)?;

        // The script result is built up from the result of each statement
        // TODO: unless there's something like a return statement?
        result.push(next_result);
    }

    // Script is done
    Ok(Value::Array(result))
}

///
/// Allocates variables before continuing
///
fn evaluate_allocate_variables(num_variables: u32, continuation: &BoundScript, environment: &Environment, execution_environment: &mut ScriptExecutionEnvironment) -> Result<Value, Value> {
    execution_environment.allocate_variables(num_variables);
    evaluate_statement(continuation, environment, execution_environment)
}

///
/// Assigns a value to a particular variable
///
fn evaluate_assignment(variable_index: u32, expr: &BoundExpression, environment: &Environment, execution_environment: &mut ScriptExecutionEnvironment) -> Result<Value, Value> {
    let expression_value = evaluate_expression(expr, environment, execution_environment)?;
    execution_environment.set_variable(variable_index, Box::new(expression_value.clone()));

    Ok(expression_value)
}

///
/// Evaluates the result of executing a single statement
///
pub fn evaluate_statement(statement: &BoundScript, environment: &Environment, execution_environment: &mut ScriptExecutionEnvironment) -> Result<Value, Value> {
    match statement {
        &BoundScript::AllocateVariables(num, ref continuation)  => evaluate_allocate_variables(num, &**continuation, environment, execution_environment),
        &BoundScript::RunCommand(ref expr)                      => evaluate_expression(expr, environment, execution_environment),
        &BoundScript::Sequence(ref steps)                       => evaluate_sequence(steps, environment, execution_environment),
        &BoundScript::Var(index, ref expr, _)                   => evaluate_assignment(index, expr, environment, execution_environment),
        &BoundScript::Assign(index, ref expr, _)                => evaluate_assignment(index, expr, environment, execution_environment),

        _                                                       => Err(generate_script_error(ScriptEvaluationError::StatementNotImplemented, statement))
    }
}

///
/// Evaluates the result of executing a single statement
///
pub fn evaluate_unbound_statement(statement: &Script, environment: &Environment, execution_environment: &mut ScriptExecutionEnvironment) -> Result<Value, Value> {
    let mut binding_environment = BindingEnvironment::from_environment(environment);
    let bound                   = bind_statement(statement, &mut *binding_environment)?;

    evaluate_statement(&bound, environment, execution_environment)
}

#[cfg(test)]
mod test {
    use gossyp_base::basic::*;
    use super::*;
    use super::super::evaluate::*;

    #[test]
    fn can_execute_run_command() {
        let tool_expr           = Script::RunCommand(Expression::identifier("test"));
        let tool_environment    = DynamicEnvironment::new();

        tool_environment.define("test", Box::new(make_pure_tool(|_: ()| "Success")));

        let mut env             = ScriptExecutionEnvironment::new();
        let result              = evaluate_unbound_statement(&tool_expr, &tool_environment, &mut env);

        assert!(result == Ok(Value::String(String::from("Success"))));
    }

    #[test]
    fn can_execute_sequence() {
        let tool_expr           = Script::Sequence(
            vec![
                Script::RunCommand(Expression::string("\"test 1\"")),
                Script::RunCommand(Expression::string("\"test 2\""))
            ]);
        let tool_environment    = DynamicEnvironment::new();

        let mut env             = ScriptExecutionEnvironment::new();
        let result              = evaluate_unbound_statement(&tool_expr, &tool_environment, &mut env);

        assert!(result == Ok(Value::Array(vec![
            Value::String(String::from("test 1")),
            Value::String(String::from("test 2"))
        ])));
    }

    #[test]
    fn commands_have_own_environment() {
        let environment = DynamicEnvironment::new();

        // Tool that defines a new tool in its environment
        assert!(define_dynamic_tool(&environment, "make_tool", |_: (), env| define_pure_tool(env, "subtool", |_: ()| ())).is_ok());

        // Tools should get their own sub-environment so the new tool should not 'escape'. Need to do make_tool().subtool() to call the subtool
        assert!(gossyp_eval("subtool", &environment).is_err());
        assert!(gossyp_eval("make_tool", &environment).is_ok());
        assert!(gossyp_eval("subtool", &environment).is_err());
    }

    #[test]
    fn commands_can_access_parent_environment() {
        let environment = DynamicEnvironment::new();

        // Tool that defines a new tool in its environment
        assert!(define_pure_tool(&environment, "one", |_: ()| 1).is_ok());
        assert!(define_dynamic_tool(&environment, "call_one", |_: (), env| -> Result<Value, Value> {
            Ok(env.get_json_tool("one")
                  .map_err(|_| json![ "Couldn't find tool" ])?
                  .invoke_json(Value::Null, env)?)
            }).is_ok());

        // Tools should get their own sub-environment so the new tool should not 'escape'. Need to do make_tool().subtool() to call the subtool
        assert!(gossyp_eval("one", &environment).is_ok());
        assert!(gossyp_eval("call_one", &environment).map_err(|x| { println!("{:?}", x); x }).is_ok());
    }

    /*
    #[test]
    fn can_call_subtools() {
        let environment = DynamicEnvironment::new();

        // Tool that defines a new tool in its environment
        assert!(define_dynamic_tool(&environment, "make_tool", |_: (), env| define_pure_tool(env, "subtool", |_: ()| ())).is_ok());

        let subtool_result = gossyp_eval("make_tool().subtool()", &environment);
        if !subtool_result.is_ok() { println!("{:?}", subtool_result); }
        assert!(subtool_result.is_ok());
    }
    */
}
<|endoftext|>


<commit_before>#[doc = "Prunes branches of the tree that are not exported"];

import rustc::syntax::ast;
import rustc::syntax::ast_util;
import rustc::middle::ast_map;

export mk_pass;

fn mk_pass() -> pass {
    run
}

fn run(srv: astsrv::srv, doc: doc::cratedoc) -> doc::cratedoc {
    let fold = fold::fold({
        fold_mod: fold_mod
        with *fold::default_seq_fold(srv)
    });
    fold.fold_crate(fold, doc)
}

fn fold_mod(fold: fold::fold<astsrv::srv>, doc: doc::moddoc) -> doc::moddoc {
    let doc = fold::default_seq_fold_mod(fold, doc);
    ~{
        mods: doc::modlist(exported_mods(fold.ctxt, doc)),
        fns: doc::fnlist(exported_fns(fold.ctxt, doc)),
        consts: doc::constlist(exported_consts(fold.ctxt, doc))
        with *doc
    }
}

fn exported_mods(srv: astsrv::srv, doc: doc::moddoc) -> [doc::moddoc] {
    exported_things(
        srv, doc,
        exported_mods_from_crate,
        exported_mods_from_mod
    )
}

fn exported_fns(srv: astsrv::srv, doc: doc::moddoc) -> [doc::fndoc] {
    exported_things(
        srv, doc,
        exported_fns_from_crate,
        exported_fns_from_mod
    )
}

fn exported_consts(srv: astsrv::srv, doc: doc::moddoc) -> [doc::constdoc] {
    exported_things(
        srv, doc,
        exported_consts_from_crate,
        exported_consts_from_mod
    )
}

fn exported_things<T>(
    srv: astsrv::srv,
    doc: doc::moddoc,
    from_crate: fn(astsrv::srv, doc::moddoc) -> [T],
    from_mod: fn(astsrv::srv, doc::moddoc) -> [T]
) -> [T] {
    if doc.id == ast::crate_node_id {
        from_crate(srv, doc)
    } else {
        from_mod(srv, doc)
    }
}

fn exported_mods_from_crate(
    srv: astsrv::srv,
    doc: doc::moddoc
) -> [doc::moddoc] {
    exported_mods_from(srv, doc, is_exported_from_crate)
}

fn exported_mods_from_mod(
    srv: astsrv::srv,
    doc: doc::moddoc
) -> [doc::moddoc] {
    exported_mods_from(srv, doc, bind is_exported_from_mod(_, doc.id, _))
}

fn exported_fns_from_crate(
    srv: astsrv::srv,
    doc: doc::moddoc
) -> [doc::fndoc] {
    exported_fns_from(srv, doc, is_exported_from_crate)
}

fn exported_fns_from_mod(
    srv: astsrv::srv,
    doc: doc::moddoc
) -> [doc::fndoc] {
    exported_fns_from(srv, doc, bind is_exported_from_mod(_, doc.id, _))
}

fn exported_consts_from_crate(
    srv: astsrv::srv,
    doc: doc::moddoc
) -> [doc::constdoc] {
    exported_consts_from(srv, doc, is_exported_from_crate)
}

fn exported_consts_from_mod(
    srv: astsrv::srv,
    doc: doc::moddoc
) -> [doc::constdoc] {
    exported_consts_from(srv, doc, bind is_exported_from_mod(_, doc.id, _))
}

fn exported_fns_from(
    srv: astsrv::srv,
    doc: doc::moddoc,
    is_exported: fn(astsrv::srv, str) -> bool
) -> [doc::fndoc] {
    vec::filter_map(*doc.fns) { |doc|
        if is_exported(srv, doc.name) {
            some(doc)
        } else {
            none
        }
    }
}

fn exported_mods_from(
    srv: astsrv::srv,
    doc: doc::moddoc,
    is_exported: fn(astsrv::srv, str) -> bool
) -> [doc::moddoc] {
    vec::filter_map(*doc.mods) { |doc|
        if is_exported(srv, doc.name) {
            some(doc)
        } else {
            none
        }
    }
}

fn exported_consts_from(
    srv: astsrv::srv,
    doc: doc::moddoc,
    is_exported: fn(astsrv::srv, str) -> bool
) -> [doc::constdoc] {
    vec::filter_map(*doc.consts) { |doc|
        if is_exported(srv, doc.name) {
            some(doc)
        } else {
            none
        }
    }
}

fn is_exported_from_mod(
    srv: astsrv::srv,
    mod_id: doc::ast_id,
    item_name: str
) -> bool {
    astsrv::exec(srv) {|ctxt|
        alt ctxt.ast_map.get(mod_id) {
          ast_map::node_item(item) {
            alt item.node {
              ast::item_mod(m) {
                ast_util::is_exported(item_name, m)
              }
            }
          }
        }
    }
}

fn is_exported_from_crate(
    srv: astsrv::srv,
    item_name: str
) -> bool {
    astsrv::exec(srv) {|ctxt|
        ast_util::is_exported(item_name, ctxt.ast.node.module)
    }
}

#[test]
fn should_prune_unexported_fns() {
    let source = "mod b { export a; fn a() { } fn b() { } }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let doc = run(srv, doc);
    assert vec::len(*doc.topmod.mods[0].fns) == 1u;
}

#[test]
fn should_prune_unexported_fns_from_top_mod() {
    let source = "export a; fn a() { } fn b() { }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let doc = run(srv, doc);
    assert vec::len(*doc.topmod.fns) == 1u;
}

#[test]
fn should_prune_unexported_modules() {
    let source = "mod a { export a; mod a { } mod b { } }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let doc = run(srv, doc);
    assert vec::len(*doc.topmod.mods[0].mods) == 1u;
}

#[test]
fn should_prune_unexported_modules_from_top_mod() {
    let source = "export a; mod a { } mod b { }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let doc = run(srv, doc);
    assert vec::len(*doc.topmod.mods) == 1u;
}

#[test]
fn should_prune_unexported_consts() {
    let source = "mod a { export a; \
                  const a: bool = true; \
                  const b: bool = true; }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let doc = run(srv, doc);
    assert vec::len(*doc.topmod.mods[0].consts) == 1u;
}

#[test]
fn should_prune_unexported_consts_from_top_mod() {
    let source = "export a; const a: bool = true; const b: bool = true;";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let doc = run(srv, doc);
    assert vec::len(*doc.topmod.consts) == 1u;
}
<commit_msg>rustdoc: Prune unexported enums and variants<commit_after>#[doc = "Prunes branches of the tree that are not exported"];

import rustc::syntax::ast;
import rustc::syntax::ast_util;
import rustc::middle::ast_map;

export mk_pass;

fn mk_pass() -> pass {
    run
}

fn run(srv: astsrv::srv, doc: doc::cratedoc) -> doc::cratedoc {
    let fold = fold::fold({
        fold_mod: fold_mod
        with *fold::default_seq_fold(srv)
    });
    fold.fold_crate(fold, doc)
}

fn fold_mod(fold: fold::fold<astsrv::srv>, doc: doc::moddoc) -> doc::moddoc {
    let doc = fold::default_seq_fold_mod(fold, doc);
    ~{
        mods: doc::modlist(exported_mods(fold.ctxt, doc)),
        fns: doc::fnlist(exported_fns(fold.ctxt, doc)),
        consts: doc::constlist(exported_consts(fold.ctxt, doc)),
        enums: doc::enumlist(exported_enums(fold.ctxt, doc))
        with *doc
    }
}

fn exported_mods(srv: astsrv::srv, doc: doc::moddoc) -> [doc::moddoc] {
    exported_things(
        srv, doc,
        exported_mods_from_crate,
        exported_mods_from_mod
    )
}

fn exported_fns(srv: astsrv::srv, doc: doc::moddoc) -> [doc::fndoc] {
    exported_things(
        srv, doc,
        exported_fns_from_crate,
        exported_fns_from_mod
    )
}

fn exported_consts(srv: astsrv::srv, doc: doc::moddoc) -> [doc::constdoc] {
    exported_things(
        srv, doc,
        exported_consts_from_crate,
        exported_consts_from_mod
    )
}

fn exported_enums(srv: astsrv::srv, doc: doc::moddoc) -> [doc::enumdoc] {
    exported_things(
        srv, doc,
        exported_enums_from_crate,
        exported_enums_from_mod
    )
}

fn exported_things<T>(
    srv: astsrv::srv,
    doc: doc::moddoc,
    from_crate: fn(astsrv::srv, doc::moddoc) -> [T],
    from_mod: fn(astsrv::srv, doc::moddoc) -> [T]
) -> [T] {
    if doc.id == ast::crate_node_id {
        from_crate(srv, doc)
    } else {
        from_mod(srv, doc)
    }
}

fn exported_mods_from_crate(
    srv: astsrv::srv,
    doc: doc::moddoc
) -> [doc::moddoc] {
    exported_mods_from(srv, doc, is_exported_from_crate)
}

fn exported_mods_from_mod(
    srv: astsrv::srv,
    doc: doc::moddoc
) -> [doc::moddoc] {
    exported_mods_from(srv, doc, bind is_exported_from_mod(_, doc.id, _))
}

fn exported_fns_from_crate(
    srv: astsrv::srv,
    doc: doc::moddoc
) -> [doc::fndoc] {
    exported_fns_from(srv, doc, is_exported_from_crate)
}

fn exported_fns_from_mod(
    srv: astsrv::srv,
    doc: doc::moddoc
) -> [doc::fndoc] {
    exported_fns_from(srv, doc, bind is_exported_from_mod(_, doc.id, _))
}

fn exported_consts_from_crate(
    srv: astsrv::srv,
    doc: doc::moddoc
) -> [doc::constdoc] {
    exported_consts_from(srv, doc, is_exported_from_crate)
}

fn exported_consts_from_mod(
    srv: astsrv::srv,
    doc: doc::moddoc
) -> [doc::constdoc] {
    exported_consts_from(srv, doc, bind is_exported_from_mod(_, doc.id, _))
}

fn exported_enums_from_crate(
    srv: astsrv::srv,
    doc: doc::moddoc
) -> [doc::enumdoc] {
    exported_enums_from(srv, doc, is_exported_from_crate)
}

fn exported_enums_from_mod(
    srv: astsrv::srv,
    doc: doc::moddoc
) -> [doc::enumdoc] {
    exported_enums_from(srv, doc, bind is_exported_from_mod(_, doc.id, _))
}

fn exported_fns_from(
    srv: astsrv::srv,
    doc: doc::moddoc,
    is_exported: fn(astsrv::srv, str) -> bool
) -> [doc::fndoc] {
    vec::filter_map(*doc.fns) { |doc|
        if is_exported(srv, doc.name) {
            some(doc)
        } else {
            none
        }
    }
}

fn exported_mods_from(
    srv: astsrv::srv,
    doc: doc::moddoc,
    is_exported: fn(astsrv::srv, str) -> bool
) -> [doc::moddoc] {
    vec::filter_map(*doc.mods) { |doc|
        if is_exported(srv, doc.name) {
            some(doc)
        } else {
            none
        }
    }
}

fn exported_consts_from(
    srv: astsrv::srv,
    doc: doc::moddoc,
    is_exported: fn(astsrv::srv, str) -> bool
) -> [doc::constdoc] {
    vec::filter_map(*doc.consts) { |doc|
        if is_exported(srv, doc.name) {
            some(doc)
        } else {
            none
        }
    }
}

fn exported_enums_from(
    srv: astsrv::srv,
    doc: doc::moddoc,
    is_exported: fn(astsrv::srv, str) -> bool
) -> [doc::enumdoc] {
    vec::filter_map(*doc.enums) { |doc|
        if is_exported(srv, doc.name) {
            some(~{
                variants: exported_variants_from(
                    srv, doc, is_exported)
                with *doc
            })
        } else {
            none
        }
    }
}

fn exported_variants_from(
    srv: astsrv::srv,
    doc: doc::enumdoc,
    is_exported: fn(astsrv::srv, str) -> bool
) -> [doc::variantdoc] {
    vec::filter_map(doc.variants) { |doc|
        if is_exported(srv, doc.name) {
            some(doc)
        } else {
            none
        }
    }
}

fn is_exported_from_mod(
    srv: astsrv::srv,
    mod_id: doc::ast_id,
    item_name: str
) -> bool {
    astsrv::exec(srv) {|ctxt|
        alt ctxt.ast_map.get(mod_id) {
          ast_map::node_item(item) {
            alt item.node {
              ast::item_mod(m) {
                ast_util::is_exported(item_name, m)
              }
            }
          }
        }
    }
}

fn is_exported_from_crate(
    srv: astsrv::srv,
    item_name: str
) -> bool {
    astsrv::exec(srv) {|ctxt|
        ast_util::is_exported(item_name, ctxt.ast.node.module)
    }
}

#[test]
fn should_prune_unexported_fns() {
    let source = "mod b { export a; fn a() { } fn b() { } }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let doc = run(srv, doc);
    assert vec::len(*doc.topmod.mods[0].fns) == 1u;
}

#[test]
fn should_prune_unexported_fns_from_top_mod() {
    let source = "export a; fn a() { } fn b() { }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let doc = run(srv, doc);
    assert vec::len(*doc.topmod.fns) == 1u;
}

#[test]
fn should_prune_unexported_modules() {
    let source = "mod a { export a; mod a { } mod b { } }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let doc = run(srv, doc);
    assert vec::len(*doc.topmod.mods[0].mods) == 1u;
}

#[test]
fn should_prune_unexported_modules_from_top_mod() {
    let source = "export a; mod a { } mod b { }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let doc = run(srv, doc);
    assert vec::len(*doc.topmod.mods) == 1u;
}

#[test]
fn should_prune_unexported_consts() {
    let source = "mod a { export a; \
                  const a: bool = true; \
                  const b: bool = true; }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let doc = run(srv, doc);
    assert vec::len(*doc.topmod.mods[0].consts) == 1u;
}

#[test]
fn should_prune_unexported_consts_from_top_mod() {
    let source = "export a; const a: bool = true; const b: bool = true;";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let doc = run(srv, doc);
    assert vec::len(*doc.topmod.consts) == 1u;
}

#[test]
fn should_prune_unexported_enums_from_top_mod() {
    let source = "export a; mod a { } enum b { c }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let doc = run(srv, doc);
    assert vec::len(*doc.topmod.enums) == 0u;
}

#[test]
fn should_prune_unexported_enums() {
    let source = "mod a { export a; mod a { } enum b { c } }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let doc = run(srv, doc);
    assert vec::len(*doc.topmod.mods[0].enums) == 0u;
}

#[test]
fn should_prune_unexported_variants_from_top_mod() {
    let source = "export b::{}; enum b { c }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let doc = run(srv, doc);
    assert vec::len(doc.topmod.enums[0].variants) == 0u;
}

#[test]
fn should_prune_unexported_variants() {
    let source = "mod a { export b::{}; enum b { c } }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let doc = run(srv, doc);
    assert vec::len(doc.topmod.mods[0].enums[0].variants) == 0u;
}
<|endoftext|>


<commit_before>use futures_core::task::{Context, Poll};
use futures_io::AsyncRead;
use std::io;
use std::pin::Pin;

/// Future for the [`take`](super::AsyncReadExt::take) method.
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct Take<R: Unpin> {
    inner: R,
    limit: u64,
}

impl<R: Unpin> Unpin for Take<R> { }

impl<R: AsyncRead + Unpin> Take<R> {
    pub(super) fn new(inner: R, limit: u64) -> Self {
        Take { inner, limit }
    }

    pub fn limit(&self) -> u64 {
        self.limit
    }

    pub fn set_limit(&mut self, limit: u64) {
        self.limit = limit
    }
}

impl<R: AsyncRead + Unpin> AsyncRead for Take<R> {
    fn poll_read(
        mut self: Pin<&mut Self>,
        cx: &mut Context<'_>,
        buf: &mut [u8],
    ) -> Poll<Result<usize, io::Error>> {
        if self.limit == 0 {
            return Poll::Ready(Ok(0));
        }

        let max = std::cmp::min(buf.len() as u64, self.limit) as usize;
        let n = ready!(Pin::new(&mut self.inner).poll_read(cx, &mut buf[..max]))?;
        self.limit -= n as u64;
        Poll::Ready(Ok(n))
    }
}
<commit_msg>Add tests for Take::limit and Take::set_limit.<commit_after>use futures_core::task::{Context, Poll};
use futures_io::AsyncRead;
use std::io;
use std::pin::Pin;

/// Future for the [`take`](super::AsyncReadExt::take) method.
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct Take<R: Unpin> {
    inner: R,
    limit: u64,
}

impl<R: Unpin> Unpin for Take<R> { }

impl<R: AsyncRead + Unpin> Take<R> {
    pub(super) fn new(inner: R, limit: u64) -> Self {
        Take { inner, limit }
    }

    /// Returns the remaining number of bytes that can be
    /// read before this instance will return EOF.
    ///
    /// # Note
    ///
    /// This instance may reach `EOF` after reading fewer bytes than indicated by
    /// this method if the underlying [`AsyncRead`] instance reaches EOF.
    ///
    /// # Examples
    ///
    /// ```
    /// #![feature(async_await)]
    /// # futures::executor::block_on(async {
    /// use futures::io::AsyncReadExt;
    /// use std::io::Cursor;
    ///
    /// let reader = Cursor::new(&b"12345678"[..]);
    /// let mut buffer = [0; 2];
    ///
    /// let mut take = reader.take(4);
    /// let n = take.read(&mut buffer).await?;
    ///
    /// assert_eq!(take.limit(), 2);
    /// # Ok::<(), Box<dyn std::error::Error>>(()) }).unwrap();
    /// ```
    pub fn limit(&self) -> u64 {
        self.limit
    }

    /// Sets the number of bytes that can be read before this instance will
    /// return EOF. This is the same as constructing a new `Take` instance, so
    /// the amount of bytes read and the previous limit value don't matter when
    /// calling this method.
    ///
    /// # Examples
    ///
    /// ```
    /// #![feature(async_await)]
    /// # futures::executor::block_on(async {
    /// use futures::io::AsyncReadExt;
    /// use std::io::Cursor;
    ///
    /// let reader = Cursor::new(&b"12345678"[..]);
    /// let mut buffer = [0; 4];
    ///
    /// let mut take = reader.take(4);
    /// let n = take.read(&mut buffer).await?;
    ///
    /// assert_eq!(n, 4);
    /// assert_eq!(take.limit(), 0);
    ///
    /// take.set_limit(10);
    /// let n = take.read(&mut buffer).await?;
    /// assert_eq!(n, 4);
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(()) }).unwrap();
    /// ```
    pub fn set_limit(&mut self, limit: u64) {
        self.limit = limit
    }
}

impl<R: AsyncRead + Unpin> AsyncRead for Take<R> {
    fn poll_read(
        mut self: Pin<&mut Self>,
        cx: &mut Context<'_>,
        buf: &mut [u8],
    ) -> Poll<Result<usize, io::Error>> {
        if self.limit == 0 {
            return Poll::Ready(Ok(0));
        }

        let max = std::cmp::min(buf.len() as u64, self.limit) as usize;
        let n = ready!(Pin::new(&mut self.inner).poll_read(cx, &mut buf[..max]))?;
        self.limit -= n as u64;
        Poll::Ready(Ok(n))
    }
}
<|endoftext|>


<commit_before>use bifrost::vector_clock::{StandardVectorClock, Relation};
use bifrost::utils::time::get_time;
use std::collections::{BTreeSet, BTreeMap};
use chashmap::{CHashMap, WriteGuard};
use ram::types::{Id};
use ram::cell::{Cell, ReadError, WriteError};
use server::NebServer;
use super::*;

pub type CellMetaGuard <'a> = WriteGuard<'a, Id, CellMeta>;
pub type CommitHistory = BTreeMap<Id, CellHistory>;

pub struct CellMeta {
    read: TransactionId,
    write: TransactionId,
    owner: Option<TransactionId>, // transaction that owns the cell in Committing state
    waiting: BTreeSet<TransactionId>, // transactions that waiting for owner to finish
}

#[derive(Debug, Serialize, Deserialize)]
pub enum CommitOp {
    Write(Cell),
    Update(Cell),
    Remove(Id)
}

#[derive(Debug, Serialize, Deserialize)]
pub enum PrepareResult {
    Wait,
    Success,
    TransactionNotExisted,
    NotRealizable,
}

#[derive(Debug, Serialize, Deserialize)]
pub enum CommitResult {
    Success,
    WriteError(Id, WriteError, Vec<RollbackFailure>),
    CellChanged(Id, Vec<RollbackFailure>),
    CheckFailed(CommitCheckError),
}

#[derive(Debug, Serialize, Deserialize)]
pub enum CommitCheckError {
    CellNumberDoesNotMatch(usize, usize),
    TransactionNotExisted,
    TransactionNotCommitted,
    TransactionAlreadyCommitted,
    TransactionAlreadyAborted,
}

#[derive(Debug, Eq, PartialEq)]
enum TransactionState {
    Started,
    Aborted,
    Committing,
    Committed,
}

struct Transaction {
    server: u64,
    id: TransactionId,
    state: TransactionState,
    affect_cells: usize,
    last_activity: i64,
    history: Option<CommitHistory>
}

struct CellHistory {
    cell: Option<Cell>,
    current_version: u64
}

impl CellHistory {
    pub fn new (cell: Option<Cell>, current_ver: u64) -> CellHistory {
        CellHistory {
            cell: cell,
            current_version: current_ver,
        }
    }
}
#[derive(Debug, Serialize, Deserialize)]
pub struct RollbackFailure {
    id: Id,
    error: WriteError
}

pub struct DataManager {
    cells: CHashMap<Id, CellMeta>,
    tnxs: CHashMap<TransactionId, Transaction>,
    server: Arc<NebServer>
}

service! {
    rpc read(server_id: u64, clock: StandardVectorClock, tid: TransactionId, id: Id) -> DataSiteResponse<TransactionExecResult<Cell, ReadError>>;

    // two phase commit
    rpc prepare(clock :StandardVectorClock, tid: TransactionId, cell_ids: Vec<Id>) -> DataSiteResponse<PrepareResult>;
    rpc commit(clock :StandardVectorClock, tid: TransactionId, cells: Vec<CommitOp>) -> DataSiteResponse<CommitResult>;

    // because there may be some exception on commit, abort have to handle 'committed' and 'committing' transactions
    // for committed transaction, abort need to recover the data according to it's cells history
    rpc abort(clock :StandardVectorClock, tid: TransactionId) -> DataSiteResponse<()>;

    // there also should be a 'end' from transaction manager to inform data manager to clean up and release cell locks
    rpc end(clock :StandardVectorClock, tid: TransactionId) -> DataSiteResponse<()>;
}

dispatch_rpc_service_functions!(DataManager);

impl DataManager {
    fn local_clock(&self) -> StandardVectorClock {
        self.server.tnx_peer.clock.to_clock()
    }
    fn update_clock(&self, clock: &StandardVectorClock) {
        self.server.tnx_peer.clock.merge_with(clock);
    }
    fn get_transaction(&self, tid: &TransactionId, server: &u64) -> WriteGuard<TransactionId, Transaction> {
        if !self.tnxs.contains_key(tid) {
            self.tnxs.upsert(tid.clone(), ||{
                Transaction {
                    id: tid.clone(),
                    server: *server,
                    state: TransactionState::Started,
                    affect_cells: 0,
                    last_activity: get_time(),
                    history: None
                }
            }, |t|{});
        }
        match self.tnxs.get_mut(tid) {
            Some(tnx) => tnx,
            _ => self.get_transaction(tid, server) // it should be rare
        }
    }
    fn get_cell_meta(&self, id: &Id) -> CellMetaGuard {
        if !self.cells.contains_key(id) {
            self.cells.upsert(id.clone(), || {
                CellMeta {
                    read: TransactionId::new(),
                    write: TransactionId::new(),
                    owner: None,
                    waiting: BTreeSet::new()
                }
            }, |c|{});
        }
        match self.cells.get_mut(id) {
            Some(cell) => cell,
            _ => self.get_cell_meta(id)
        }
    }
    fn response_with<T>(&self, data: T)
        -> Result<DataSiteResponse<T>, ()>{
        Ok(DataSiteResponse::new(&self.server.tnx_peer, data))
    }
    fn rollback(&self, history: &CommitHistory) -> Vec<RollbackFailure> {
        let mut failures: Vec<RollbackFailure> = Vec::new();
        for (id, history) in history.iter() {
            let cell = &history.cell;
            let current_ver = history.current_version;
            let error = if cell.is_none() { // the cell was created, need to remove
                self.server.chunks.remove_cell_by(&id, |cell| {
                    cell.header.version == current_ver
                }).err()
            } else if current_ver > 0 { // the cell was updated, need to update back
                self.server.chunks.update_cell_by(id, |cell_to_update|{
                    if cell_to_update.header.version == current_ver {
                        cell.clone()
                    } else { None }
                }).err()
            } else { // the cell was removed, need to put back
                let mut cell = cell.clone().unwrap();
                self.server.chunks.write_cell(&mut cell).err()
            };
            if let Some(error) = error {
                failures.push(RollbackFailure{id: *id, error: error});
            }
        }
        failures
    }
}

impl Service for DataManager {
    fn read(&self, server_id: &u64, clock: &StandardVectorClock, tid: &TransactionId, id: &Id)
        -> Result<DataSiteResponse<TransactionExecResult<Cell, ReadError>>, ()> {
        self.update_clock(clock);
        let mut meta = self.get_cell_meta(id);
        let committing = meta.owner.is_some();
        let read_too_late = &meta.write > tid;
        let mut tnx = self.get_transaction(tid, server_id);
        if read_too_late { // not realizable
            return self.response_with(TransactionExecResult::Rejected);
        }
        if committing { // ts >= wt but committing, need to wait until it committed
            meta.waiting.insert(tid.clone());
            return self.response_with(TransactionExecResult::Wait);
        }
        let cell = match self.server.chunks.read_cell(id) {
            Ok(cell) => {cell}
            Err(read_error) => {
                // cannot read
                return self.response_with(TransactionExecResult::Error(read_error));
            }
        };
        let update_read = &meta.read < tid;
        if update_read {
            meta.read = tid.clone()
        }
        tnx.last_activity = get_time();
        self.response_with(TransactionExecResult::Accepted(cell))
    }
    fn prepare(&self, clock :&StandardVectorClock, tid: &TransactionId, cell_ids: &Vec<Id>)
        -> Result<DataSiteResponse<PrepareResult>, ()> {
        // In this stage, data manager will not do any write operation but mark cell owner in their meta as a lock
        // It will also check if write are realizable. If not, transaction manager should retry with new id
        // cell_ids must be sorted to avoid deadlock. It can be done from data manager by using BTreeMap keys
        self.update_clock(clock);
        let mut tnx = match self.tnxs.get_mut(tid) {
            Some(tnx) => tnx,
            _ => { return self.response_with(PrepareResult::TransactionNotExisted); }
        };
        let mut cell_metas: Vec<CellMetaGuard> = Vec::new();
        for cell_id in cell_ids {
            let meta = self.get_cell_meta(cell_id);
            if tid < &meta.read { // write too late
                break;
            }
            if tid < &meta.write {
                if meta.owner.is_some() { // not committed, should wait and try prepare again
                    return self.response_with(PrepareResult::Wait)
                }
            }
            cell_metas.push(meta);
        }
        if cell_metas.len() < cell_ids.len() {
            return self.response_with(PrepareResult::NotRealizable); // need retry
        } else {
            for mut meta in cell_metas {
                meta.owner = Some(tid.clone()) // set owner to lock this cell
            }
            tnx.state = TransactionState::Committing;
            tnx.affect_cells = cell_ids.len(); // for cell number check
            tnx.last_activity = get_time();    // check if transaction timeout
            return self.response_with(PrepareResult::Success);
        }
    }
    fn commit(&self, clock :&StandardVectorClock, tid: &TransactionId, cells: &Vec<CommitOp>)
        -> Result<DataSiteResponse<CommitResult>, ()>  {
        self.update_clock(clock);
        let mut tnx = match self.tnxs.get_mut(tid) {
            Some(tnx) => tnx,
            _ => { return self.response_with(CommitResult::CheckFailed(CommitCheckError::TransactionNotExisted)); }
        };
        // check state
        match tnx.state {
            TransactionState::Started => {return self.response_with(CommitResult::CheckFailed(CommitCheckError::TransactionNotCommitted))},
            TransactionState::Aborted => {return self.response_with(CommitResult::CheckFailed(CommitCheckError::TransactionAlreadyAborted))},
            TransactionState::Committed => {return self.response_with(CommitResult::CheckFailed(CommitCheckError::TransactionAlreadyCommitted))},
            TransactionState::Committing => {}
        }
        // check cell list integrity
        let prepared_cells_num = tnx.affect_cells;
        let arrived_cells_num = self.cells.len();
        if prepared_cells_num != arrived_cells_num {return self.response_with(CommitResult::CheckFailed(CommitCheckError::CellNumberDoesNotMatch(prepared_cells_num, arrived_cells_num)))}
        let mut commit_history: CommitHistory = BTreeMap::new(); // for rollback in case of write error
        let mut write_error: Option<(Id, WriteError)> = None;
        for cell_op in cells {
            match cell_op {
                &CommitOp::Write(ref cell) => {
                    let mut cell = cell.clone();
                    let write_result = self.server.chunks.write_cell(&mut cell);
                    match write_result {
                        Ok(header) => {
                            commit_history.insert(cell.id(), CellHistory::new(None, header.version));
                        },
                        Err(error) => {
                            write_error = Some((cell.id(), error));
                            break;
                        }
                    };
                },
                &CommitOp::Remove(ref cell_id) => {
                    let original_cell = {
                        match self.server.chunks.read_cell(cell_id) {
                            Ok(cell) => cell,
                            Err(re) => {
                                write_error = Some((*cell_id, WriteError::ReadError(re)));
                                break;
                            }
                        }
                    };
                    let write_result = self.server.chunks.remove_cell_by(cell_id, |cell|{
                        let version = cell.header.version;
                        version == original_cell.header.version
                    });
                    match write_result {
                        Ok(()) => {
                            commit_history.insert(*cell_id, CellHistory::new(Some(original_cell), 0));
                        },
                        Err(error) => {
                            write_error = Some((*cell_id, error));
                            break;
                        }
                    }
                },
                &CommitOp::Update(ref cell) => {
                    let cell_id = cell.id();
                    let original_cell = {
                        match self.server.chunks.read_cell(&cell_id) {
                            Ok(cell) => cell,
                            Err(re) => {
                                write_error = Some((cell_id, WriteError::ReadError(re)));
                                break;
                            }
                        }
                    };
                    let write_result = self.server.chunks.update_cell_by(&cell_id, |cell_to_update| {
                        if cell_to_update.header.version == original_cell.header.version {
                            Some(cell.clone())
                        } else {
                            None
                        }
                    });
                    match write_result {
                        Ok(cell) => {
                            commit_history.insert(cell_id, CellHistory::new(Some(original_cell), cell.header.version));
                        },
                        Err(error) => {
                            write_error = Some((cell_id, error));
                            break;
                        }
                    }
                }
            }
        }
        // check if any of those operations failed, if yes, rollback and fail this commit
        if let Some((id, error)) = write_error {
            let rollback_result = self.rollback(&commit_history);
            tnx.state = TransactionState::Aborted; // set to abort so it can't be abort again
            match error {
                WriteError::DeletionPredictionFailed | WriteError::UserCanceledUpdate => {
                    // in this case, we can inform transaction manager to try again
                    return self.response_with(CommitResult::CellChanged(
                        id, rollback_result
                    ));
                }
                _ => {
                    // other failure due to unfixable error should abort without retry
                    return self.response_with(CommitResult::WriteError(
                        id, error, rollback_result
                    ));
                }
            }
        } else {
            // all set, able to commit
            tnx.state = TransactionState::Committed;
            tnx.history = Some(commit_history);
            return self.response_with(CommitResult::Success)
        }
    }
    fn abort(&self, clock :&StandardVectorClock, tid: &TransactionId)
        -> Result<DataSiteResponse<()>, ()>  {
        self.update_clock(clock);
        unimplemented!()
    }
    fn end(&self, clock :&StandardVectorClock, tid: &TransactionId)
             -> Result<DataSiteResponse<()>, ()>  {
        self.update_clock(clock);
        unimplemented!()
    }
}<commit_msg>impelement 'abort' and partial 'end'<commit_after>use bifrost::vector_clock::{StandardVectorClock, Relation};
use bifrost::utils::time::get_time;
use std::collections::{BTreeSet, BTreeMap};
use chashmap::{CHashMap, WriteGuard};
use ram::types::{Id};
use ram::cell::{Cell, ReadError, WriteError};
use server::NebServer;
use super::*;

pub type CellMetaGuard <'a> = WriteGuard<'a, Id, CellMeta>;
pub type CommitHistory = BTreeMap<Id, CellHistory>;

pub struct CellMeta {
    read: TransactionId,
    write: TransactionId,
    owner: Option<TransactionId>, // transaction that owns the cell in Committing state
    waiting: BTreeSet<TransactionId>, // transactions that waiting for owner to finish
}

#[derive(Debug, Serialize, Deserialize)]
pub enum CommitOp {
    Write(Cell),
    Update(Cell),
    Remove(Id)
}

#[derive(Debug, Serialize, Deserialize)]
pub enum PrepareResult {
    Wait,
    Success,
    TransactionNotExisted,
    NotRealizable,
}

#[derive(Debug, Serialize, Deserialize)]
pub enum CommitResult {
    Success,
    WriteError(Id, WriteError, Vec<RollbackFailure>),
    CellChanged(Id, Vec<RollbackFailure>),
    CheckFailed(CheckError),
}

#[derive(Debug, Serialize, Deserialize)]
pub enum CheckError {
    CellNumberDoesNotMatch(usize, usize),
    TransactionNotExisted,
    TransactionNotCommitted,
    TransactionAlreadyCommitted,
    TransactionAlreadyAborted,
    TransactionCannotEnd,
}

#[derive(Debug, Eq, PartialEq)]
enum TransactionState {
    Started,
    Aborted,
    Committing,
    Committed,
}

#[derive(Debug, Serialize, Deserialize)]
pub enum AbortResult {
    CheckFailed(CheckError),
    Success(Option<Vec<RollbackFailure>>),
}

#[derive(Debug, Serialize, Deserialize)]
pub enum EndResult {
    CheckFailed(CheckError),
    Success,
}

struct Transaction {
    server: u64,
    id: TransactionId,
    state: TransactionState,
    affect_cells: usize,
    last_activity: i64,
    history: Option<CommitHistory>
}

struct CellHistory {
    cell: Option<Cell>,
    current_version: u64
}

impl CellHistory {
    pub fn new (cell: Option<Cell>, current_ver: u64) -> CellHistory {
        CellHistory {
            cell: cell,
            current_version: current_ver,
        }
    }
}
#[derive(Debug, Serialize, Deserialize)]
pub struct RollbackFailure {
    id: Id,
    error: WriteError
}

pub struct DataManager {
    cells: CHashMap<Id, CellMeta>,
    tnxs: CHashMap<TransactionId, Transaction>,
    server: Arc<NebServer>
}

service! {
    rpc read(server_id: u64, clock: StandardVectorClock, tid: TransactionId, id: Id) -> DataSiteResponse<TransactionExecResult<Cell, ReadError>>;

    // two phase commit
    rpc prepare(clock :StandardVectorClock, tid: TransactionId, cell_ids: Vec<Id>) -> DataSiteResponse<PrepareResult>;
    rpc commit(clock :StandardVectorClock, tid: TransactionId, cells: Vec<CommitOp>) -> DataSiteResponse<CommitResult>;

    // because there may be some exception on commit, abort have to handle 'committed' and 'committing' transactions
    // for committed transaction, abort need to recover the data according to it's cells history
    rpc abort(clock :StandardVectorClock, tid: TransactionId) -> DataSiteResponse<AbortResult>;

    // there also should be a 'end' from transaction manager to inform data manager to clean up and release cell locks
    rpc end(clock :StandardVectorClock, tid: TransactionId) -> DataSiteResponse<EndResult>;
}

dispatch_rpc_service_functions!(DataManager);

impl DataManager {
    fn local_clock(&self) -> StandardVectorClock {
        self.server.tnx_peer.clock.to_clock()
    }
    fn update_clock(&self, clock: &StandardVectorClock) {
        self.server.tnx_peer.clock.merge_with(clock);
    }
    fn get_transaction(&self, tid: &TransactionId, server: &u64) -> WriteGuard<TransactionId, Transaction> {
        if !self.tnxs.contains_key(tid) {
            self.tnxs.upsert(tid.clone(), ||{
                Transaction {
                    id: tid.clone(),
                    server: *server,
                    state: TransactionState::Started,
                    affect_cells: 0,
                    last_activity: get_time(),
                    history: None
                }
            }, |t|{});
        }
        match self.tnxs.get_mut(tid) {
            Some(tnx) => tnx,
            _ => self.get_transaction(tid, server) // it should be rare
        }
    }
    fn get_cell_meta(&self, id: &Id) -> CellMetaGuard {
        if !self.cells.contains_key(id) {
            self.cells.upsert(*id, || {
                CellMeta {
                    read: TransactionId::new(),
                    write: TransactionId::new(),
                    owner: None,
                    waiting: BTreeSet::new()
                }
            }, |c|{});
        }
        match self.cells.get_mut(id) {
            Some(cell) => cell,
            _ => self.get_cell_meta(id)
        }
    }
    fn response_with<T>(&self, data: T)
        -> Result<DataSiteResponse<T>, ()>{
        Ok(DataSiteResponse::new(&self.server.tnx_peer, data))
    }
    fn rollback(&self, history: &CommitHistory) -> Vec<RollbackFailure> {
        let mut failures: Vec<RollbackFailure> = Vec::new();
        for (id, history) in history.iter() {
            let cell = &history.cell;
            let current_ver = history.current_version;
            let error = if cell.is_none() { // the cell was created, need to remove
                self.server.chunks.remove_cell_by(&id, |cell| {
                    cell.header.version == current_ver
                }).err()
            } else if current_ver > 0 { // the cell was updated, need to update back
                self.server.chunks.update_cell_by(id, |cell_to_update|{
                    if cell_to_update.header.version == current_ver {
                        cell.clone()
                    } else { None }
                }).err()
            } else { // the cell was removed, need to put back
                let mut cell = cell.clone().unwrap();
                self.server.chunks.write_cell(&mut cell).err()
            };
            if let Some(error) = error {
                failures.push(RollbackFailure{id: *id, error: error});
            }
        }
        failures
    }
}

impl Service for DataManager {
    fn read(&self, server_id: &u64, clock: &StandardVectorClock, tid: &TransactionId, id: &Id)
        -> Result<DataSiteResponse<TransactionExecResult<Cell, ReadError>>, ()> {
        self.update_clock(clock);
        let mut meta = self.get_cell_meta(id);
        let committing = meta.owner.is_some();
        let read_too_late = &meta.write > tid;
        let mut tnx = self.get_transaction(tid, server_id);
        if read_too_late { // not realizable
            return self.response_with(TransactionExecResult::Rejected);
        }
        if committing { // ts >= wt but committing, need to wait until it committed
            meta.waiting.insert(tid.clone());
            return self.response_with(TransactionExecResult::Wait);
        }
        let cell = match self.server.chunks.read_cell(id) {
            Ok(cell) => {cell}
            Err(read_error) => {
                // cannot read
                return self.response_with(TransactionExecResult::Error(read_error));
            }
        };
        let update_read = &meta.read < tid;
        if update_read {
            meta.read = tid.clone()
        }
        tnx.last_activity = get_time();
        self.response_with(TransactionExecResult::Accepted(cell))
    }
    fn prepare(&self, clock :&StandardVectorClock, tid: &TransactionId, cell_ids: &Vec<Id>)
        -> Result<DataSiteResponse<PrepareResult>, ()> {
        // In this stage, data manager will not do any write operation but mark cell owner in their meta as a lock
        // It will also check if write are realizable. If not, transaction manager should retry with new id
        // cell_ids must be sorted to avoid deadlock. It can be done from data manager by using BTreeMap keys
        self.update_clock(clock);
        let mut tnx = match self.tnxs.get_mut(tid) {
            Some(tnx) => tnx,
            _ => { return self.response_with(PrepareResult::TransactionNotExisted); }
        };
        let mut cell_metas: Vec<CellMetaGuard> = Vec::new();
        for cell_id in cell_ids {
            let meta = self.get_cell_meta(cell_id);
            if tid < &meta.read { // write too late
                break;
            }
            if tid < &meta.write {
                if meta.owner.is_some() { // not committed, should wait and try prepare again
                    return self.response_with(PrepareResult::Wait)
                }
            }
            cell_metas.push(meta);
        }
        if cell_metas.len() < cell_ids.len() {
            return self.response_with(PrepareResult::NotRealizable); // need retry
        } else {
            for mut meta in cell_metas {
                meta.owner = Some(tid.clone()) // set owner to lock this cell
            }
            tnx.state = TransactionState::Committing;
            tnx.affect_cells = cell_ids.len(); // for cell number check
            tnx.last_activity = get_time();    // check if transaction timeout
            return self.response_with(PrepareResult::Success);
        }
    }
    fn commit(&self, clock :&StandardVectorClock, tid: &TransactionId, cells: &Vec<CommitOp>)
        -> Result<DataSiteResponse<CommitResult>, ()>  {
        self.update_clock(clock);
        let mut tnx = match self.tnxs.get_mut(tid) {
            Some(tnx) => tnx,
            _ => { return self.response_with(CommitResult::CheckFailed(CheckError::TransactionNotExisted)); }
        };
        // check state
        match tnx.state {
            TransactionState::Started => {return self.response_with(CommitResult::CheckFailed(CheckError::TransactionNotCommitted))},
            TransactionState::Aborted => {return self.response_with(CommitResult::CheckFailed(CheckError::TransactionAlreadyAborted))},
            TransactionState::Committed => {return self.response_with(CommitResult::CheckFailed(CheckError::TransactionAlreadyCommitted))},
            TransactionState::Committing => {}
        }
        // check cell list integrity
        let prepared_cells_num = tnx.affect_cells;
        let arrived_cells_num = self.cells.len();
        if prepared_cells_num != arrived_cells_num {return self.response_with(CommitResult::CheckFailed(CheckError::CellNumberDoesNotMatch(prepared_cells_num, arrived_cells_num)))}
        let mut commit_history: CommitHistory = BTreeMap::new(); // for rollback in case of write error
        let mut write_error: Option<(Id, WriteError)> = None;
        for cell_op in cells {
            match cell_op {
                &CommitOp::Write(ref cell) => {
                    let mut cell = cell.clone();
                    let write_result = self.server.chunks.write_cell(&mut cell);
                    match write_result {
                        Ok(header) => {
                            commit_history.insert(cell.id(), CellHistory::new(None, header.version));
                        },
                        Err(error) => {
                            write_error = Some((cell.id(), error));
                            break;
                        }
                    };
                },
                &CommitOp::Remove(ref cell_id) => {
                    let original_cell = {
                        match self.server.chunks.read_cell(cell_id) {
                            Ok(cell) => cell,
                            Err(re) => {
                                write_error = Some((*cell_id, WriteError::ReadError(re)));
                                break;
                            }
                        }
                    };
                    let write_result = self.server.chunks.remove_cell_by(cell_id, |cell|{
                        let version = cell.header.version;
                        version == original_cell.header.version
                    });
                    match write_result {
                        Ok(()) => {
                            commit_history.insert(*cell_id, CellHistory::new(Some(original_cell), 0));
                        },
                        Err(error) => {
                            write_error = Some((*cell_id, error));
                            break;
                        }
                    }
                },
                &CommitOp::Update(ref cell) => {
                    let cell_id = cell.id();
                    let original_cell = {
                        match self.server.chunks.read_cell(&cell_id) {
                            Ok(cell) => cell,
                            Err(re) => {
                                write_error = Some((cell_id, WriteError::ReadError(re)));
                                break;
                            }
                        }
                    };
                    let write_result = self.server.chunks.update_cell_by(&cell_id, |cell_to_update| {
                        if cell_to_update.header.version == original_cell.header.version {
                            Some(cell.clone())
                        } else {
                            None
                        }
                    });
                    match write_result {
                        Ok(cell) => {
                            commit_history.insert(cell_id, CellHistory::new(Some(original_cell), cell.header.version));
                        },
                        Err(error) => {
                            write_error = Some((cell_id, error));
                            break;
                        }
                    }
                }
            }
        }
        // check if any of those operations failed, if yes, rollback and fail this commit
        if let Some((id, error)) = write_error {
            let rollback_result = self.rollback(&commit_history);
            tnx.state = TransactionState::Aborted; // set to abort so it can't be abort again
            tnx.last_activity = get_time();
            match error {
                WriteError::DeletionPredictionFailed | WriteError::UserCanceledUpdate => {
                    // in this case, we can inform transaction manager to try again
                    return self.response_with(CommitResult::CellChanged(
                        id, rollback_result
                    ));
                }
                _ => {
                    // other failure due to unfixable error should abort without retry
                    return self.response_with(CommitResult::WriteError(
                        id, error, rollback_result
                    ));
                }
            }
        } else {
            // all set, able to commit
            tnx.state = TransactionState::Committed;
            tnx.history = Some(commit_history);
            tnx.last_activity = get_time();
            return self.response_with(CommitResult::Success)
        }
    }
    fn abort(&self, clock :&StandardVectorClock, tid: &TransactionId)
        -> Result<DataSiteResponse<AbortResult>, ()>  {
        self.update_clock(clock);
        let mut tnx = match self.tnxs.get_mut(tid) {
            Some(tnx) => tnx,
            _ => { return self.response_with(AbortResult::CheckFailed(CheckError::TransactionNotExisted)); }
        };
        if tnx.state == TransactionState::Aborted {
            return self.response_with(AbortResult::CheckFailed(CheckError::TransactionAlreadyAborted));
        }
        let rollback_failure = {
            if let Some(ref history) = tnx.history {
                let history = self.rollback(history);
                if history.len() == 0 { None } else { Some(history) }
            } else { None }
        };
        tnx.last_activity = get_time();
        tnx.state = TransactionState::Aborted;
        self.response_with(AbortResult::Success(rollback_failure))
    }
    fn end(&self, clock :&StandardVectorClock, tid: &TransactionId)
             -> Result<DataSiteResponse<EndResult>, ()>  {
        self.update_clock(clock);
        let mut tnx = match self.tnxs.get_mut(tid) {
            Some(tnx) => tnx,
            _ => { return self.response_with(EndResult::CheckFailed(CheckError::TransactionNotExisted)); }
        };
        if !(tnx.state == TransactionState::Aborted || tnx.state == TransactionState::Committed) {
            return self.response_with(EndResult::CheckFailed(CheckError::TransactionCannotEnd))
        }
        // TODO: clean up and inform waiting transaction managers
        self.response_with(EndResult::Success)
    }
}

// TODO: update 'read' and 'write' in CellMeta and deal with abort recover<|endoftext|>