<commit_before>// extern crate bindgen;
#[cfg(target_env = "msvc")]
extern crate cc;
extern crate pkg_config;
extern crate num_cpus;

use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};

// Automatically write bindings to libsass
//#[allow(dead_code)]
//fn write_bindings() {
//    let bindings = bindgen::Builder::default()
//        .header("libsass/include/sass.h")
//        .clang_arg("-Ilibsass/include")
//        // To avoid a test failing
//        .blacklist_type("max_align_t")
//        // we do static linking so it should be fine
//        // https://github.com/rust-lang/rust/issues/36927
//        .rustified_enum(".*")
//        .generate()
//        .expect("Unable to generate bindings");
//
//    // Write the bindings to the $OUT_DIR/bindings.rs file.
//    let out_path = PathBuf::from("src");
//    bindings
//        .write_to_file(out_path.join("bindings.rs"))
//        .expect("Couldn't write bindings!");
//}

macro_rules! t {
    ($e:expr) => (match $e {
        Ok(n) => n,
        Err(e) => panic!("\n{} failed with {}\n", stringify!($e), e),
    })
}

fn cp_r(dir: &Path, dest: &Path) {
    for entry in t!(fs::read_dir(dir)) {
        let entry = t!(entry);
        let path = entry.path();
        let dst = dest.join(path.file_name().unwrap());
        if t!(fs::metadata(&path)).is_file() {
            t!(fs::copy(path, dst));
        } else {
            t!(fs::create_dir_all(&dst));
            cp_r(&path, &dst);
        }
    }
}

// CIs usually do it automatically but for normal users libsass would be missing
fn init_submodule() {
    Command::new("git")
        .arg("submodule")
        .arg("init")
        .stderr(Stdio::null())
        .status()
        .expect("")
        .success();
    Command::new("git")
        .arg("submodule")
        .arg("update")
        .stderr(Stdio::null())
        .status()
        .expect("")
        .success();
}

fn get_libsass_folder() -> PathBuf {
    env::current_dir().unwrap().join("libsass")
}

#[cfg(not(target_os = "macos"))]
fn _compile(libprobe: fn(&str) -> bool) {
    if libprobe("stdc++") {
        println!("cargo:rustc-link-lib=dylib=stdc++");
    } else if libprobe("c++_shared") {
        println!("cargo:rustc-link-lib=dylib=c++_shared");
    } else if libprobe("c++") {
        println!("cargo:rustc-link-lib=dylib=c++");
    } else {
        panic!("no c++ library found");
    }
}

#[cfg(target_os = "macos")]
fn _compile(libprobe: fn(&str) -> bool) {

    if libprobe("c++_shared") {
        println!("cargo:rustc-link-lib=dylib=c++_shared");
    } else if libprobe("c++") {
        println!("cargo:rustc-link-lib=dylib=c++");
    } else if libprobe("stdc++") {
        println!("cargo:rustc-link-lib=dylib=stdc++");
    } else {
        panic!("no c++ library found");
    }
}

// linux/unix
#[cfg(not(target_env = "msvc"))]
fn compile() {
    let target = env::var("TARGET").expect("TARGET not found");
    let src = get_libsass_folder();
    let dest = PathBuf::from(env::var_os("OUT_DIR").unwrap());
    let build = dest.join("build");
    t!(fs::create_dir_all(&build));
    cp_r(&src, &build);
    let is_bsd = target.contains("dragonfly")
        || target.contains("freebsd")
        || target.contains("netbsd")
        || target.contains("openbsd");
    let jobs = env::var("MAKE_LIBSASS_JOBS").unwrap_or(num_cpus::get().to_string());
    let r = Command::new(if is_bsd { "gmake" } else { "make" })
        .current_dir(&build)
        .args(&["--jobs", &jobs])
        .output()
        .expect("error running make");

    if !r.status.success() {
        let err = String::from_utf8_lossy(&r.stderr);
        let out = String::from_utf8_lossy(&r.stdout);
        panic!("Build error:\nSTDERR:{}\nSTDOUT:{}", err, out);
    }

    println!(
        "cargo:rustc-link-search=native={}",
        build.join("lib").display()
    );
    println!("cargo:rustc-link-lib=static=sass");

    let libprobe = | lib: &str | -> bool {
      Command::new("cc")
        .arg("-xc++")
        .arg("-o/dev/null")
        .arg(format!("-l{}",lib))
        .arg("-shared")
        .stderr(Stdio::null())
        .status()
        .expect("")
        .success()
    };
    _compile(libprobe);
}

// windows
#[cfg(target_env = "msvc")]
fn compile() {
    let src = get_libsass_folder();
    let target = env::var("TARGET").expect("TARGET not found in environment");
    let msvc_platform = if target.contains("x86_64") {
        "Win64"
    } else {
        "Win32"
    };
    let dest = PathBuf::from(env::var_os("OUT_DIR").unwrap());
    let build = dest.join("build");
    t!(fs::create_dir_all(&build));
    cp_r(&src, &build);

    // Find an instance of devenv.exe from Visual Studio IDE in order to upgrade
    // libsass.sln to the current available IDE. Do nothing if no devenv.exe found
    let d = cc::windows_registry::find(target.as_str(), "devenv.exe");
    if let Some(mut d) = d {
        let d = d
            .args(&["/upgrade", "win\\libsass.sln"])
            .current_dir(&build)
            .output()
            .expect("error running devenv");
        if !d.status.success() {
            let err = String::from_utf8_lossy(&d.stderr);
            let out = String::from_utf8_lossy(&d.stdout);
            println!("Upgrade error:\nSTDERR:{}\nSTDOUT:{}", err, out);
        }
    }

    let search = Command::new("where")
        .args(&["msbuild.exe"])
        .output()
        .expect("Could not search for msbuild.exe on path");
    let mut msbuild = if search.status.success() {
        Command::new("msbuild.exe")
    } else {
        cc::windows_registry::find(target.as_str(), "msbuild.exe")
            .expect("Could not find msbuild.exe on the registry")
    };

    let jobs = env::var("MAKE_LIBSASS_JOBS").unwrap_or(num_cpus::get().to_string());

    let r = msbuild
        .args(&[
            "win\\libsass.sln",
            "/p:LIBSASS_STATIC_LIB=1",
            "/p:Configuration=Release",
            "/p:WholeProgramOptimization=false",
            format!("/m:{}", jobs).as_str(),
            format!("/p:Platform={}", msvc_platform).as_str(),
        ])
        .current_dir(&build)
        .output()
        .expect("error running msbuild");

    if !r.status.success() {
        let err = String::from_utf8_lossy(&r.stderr);
        let out = String::from_utf8_lossy(&r.stdout);
        panic!("Build error:\nSTDERR:{}\nSTDOUT:{}", err, out);
    }

    println!(
        "cargo:rustc-link-search=native={}",
        build.join("win").join("bin").display()
    );
    println!("cargo:rustc-link-lib=static=libsass");
}

fn main() {
    // Uncomment the line below to generate bindings. Doesn't work on CI as it
    // requires additional tooling
    // write_bindings();

    // Is it already built?
    if let Ok(_) = pkg_config::find_library("libsass") {
        return;
    }

    init_submodule();
    compile();
}
<commit_msg>Fix detection of C++ library on FreeBSD. Due to the way libprobe is written, this always detected libstdc++ on accident.<commit_after>// extern crate bindgen;
#[cfg(target_env = "msvc")]
extern crate cc;
extern crate pkg_config;
extern crate num_cpus;

use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};

// Automatically write bindings to libsass
//#[allow(dead_code)]
//fn write_bindings() {
//    let bindings = bindgen::Builder::default()
//        .header("libsass/include/sass.h")
//        .clang_arg("-Ilibsass/include")
//        // To avoid a test failing
//        .blacklist_type("max_align_t")
//        // we do static linking so it should be fine
//        // https://github.com/rust-lang/rust/issues/36927
//        .rustified_enum(".*")
//        .generate()
//        .expect("Unable to generate bindings");
//
//    // Write the bindings to the $OUT_DIR/bindings.rs file.
//    let out_path = PathBuf::from("src");
//    bindings
//        .write_to_file(out_path.join("bindings.rs"))
//        .expect("Couldn't write bindings!");
//}

macro_rules! t {
    ($e:expr) => (match $e {
        Ok(n) => n,
        Err(e) => panic!("\n{} failed with {}\n", stringify!($e), e),
    })
}

fn cp_r(dir: &Path, dest: &Path) {
    for entry in t!(fs::read_dir(dir)) {
        let entry = t!(entry);
        let path = entry.path();
        let dst = dest.join(path.file_name().unwrap());
        if t!(fs::metadata(&path)).is_file() {
            t!(fs::copy(path, dst));
        } else {
            t!(fs::create_dir_all(&dst));
            cp_r(&path, &dst);
        }
    }
}

// CIs usually do it automatically but for normal users libsass would be missing
fn init_submodule() {
    Command::new("git")
        .arg("submodule")
        .arg("init")
        .stderr(Stdio::null())
        .status()
        .expect("")
        .success();
    Command::new("git")
        .arg("submodule")
        .arg("update")
        .stderr(Stdio::null())
        .status()
        .expect("")
        .success();
}

fn get_libsass_folder() -> PathBuf {
    env::current_dir().unwrap().join("libsass")
}

#[cfg(not(target_os = "macos"))]
fn _compile(libprobe: fn(&str) -> bool) {
    if cfg!(target_os = "freebsd") && libprobe("c++") {
        println!("cargo:rustc-link-lib=dylib=c++");
    } else if libprobe("stdc++") {
        println!("cargo:rustc-link-lib=dylib=stdc++");
    } else if libprobe("c++_shared") {
        println!("cargo:rustc-link-lib=dylib=c++_shared");
    } else if libprobe("c++") {
        println!("cargo:rustc-link-lib=dylib=c++");
    } else {
        panic!("no c++ library found");
    }
}

#[cfg(target_os = "macos")]
fn _compile(libprobe: fn(&str) -> bool) {

    if libprobe("c++_shared") {
        println!("cargo:rustc-link-lib=dylib=c++_shared");
    } else if libprobe("c++") {
        println!("cargo:rustc-link-lib=dylib=c++");
    } else if libprobe("stdc++") {
        println!("cargo:rustc-link-lib=dylib=stdc++");
    } else {
        panic!("no c++ library found");
    }
}

// linux/unix
#[cfg(not(target_env = "msvc"))]
fn compile() {
    let target = env::var("TARGET").expect("TARGET not found");
    let src = get_libsass_folder();
    let dest = PathBuf::from(env::var_os("OUT_DIR").unwrap());
    let build = dest.join("build");
    t!(fs::create_dir_all(&build));
    cp_r(&src, &build);
    let is_bsd = target.contains("dragonfly")
        || target.contains("freebsd")
        || target.contains("netbsd")
        || target.contains("openbsd");
    let jobs = env::var("MAKE_LIBSASS_JOBS").unwrap_or(num_cpus::get().to_string());
    let r = Command::new(if is_bsd { "gmake" } else { "make" })
        .current_dir(&build)
        .args(&["--jobs", &jobs])
        .output()
        .expect("error running make");

    if !r.status.success() {
        let err = String::from_utf8_lossy(&r.stderr);
        let out = String::from_utf8_lossy(&r.stdout);
        panic!("Build error:\nSTDERR:{}\nSTDOUT:{}", err, out);
    }

    println!(
        "cargo:rustc-link-search=native={}",
        build.join("lib").display()
    );
    println!("cargo:rustc-link-lib=static=sass");

    let libprobe = | lib: &str | -> bool {
      Command::new("cc")
        .arg("-xc++")
        .arg("-o/dev/null")
        .arg(format!("-l{}",lib))
        .arg("-shared")
        .stderr(Stdio::null())
        .status()
        .expect("")
        .success()
    };
    _compile(libprobe);
}

// windows
#[cfg(target_env = "msvc")]
fn compile() {
    let src = get_libsass_folder();
    let target = env::var("TARGET").expect("TARGET not found in environment");
    let msvc_platform = if target.contains("x86_64") {
        "Win64"
    } else {
        "Win32"
    };
    let dest = PathBuf::from(env::var_os("OUT_DIR").unwrap());
    let build = dest.join("build");
    t!(fs::create_dir_all(&build));
    cp_r(&src, &build);

    // Find an instance of devenv.exe from Visual Studio IDE in order to upgrade
    // libsass.sln to the current available IDE. Do nothing if no devenv.exe found
    let d = cc::windows_registry::find(target.as_str(), "devenv.exe");
    if let Some(mut d) = d {
        let d = d
            .args(&["/upgrade", "win\\libsass.sln"])
            .current_dir(&build)
            .output()
            .expect("error running devenv");
        if !d.status.success() {
            let err = String::from_utf8_lossy(&d.stderr);
            let out = String::from_utf8_lossy(&d.stdout);
            println!("Upgrade error:\nSTDERR:{}\nSTDOUT:{}", err, out);
        }
    }

    let search = Command::new("where")
        .args(&["msbuild.exe"])
        .output()
        .expect("Could not search for msbuild.exe on path");
    let mut msbuild = if search.status.success() {
        Command::new("msbuild.exe")
    } else {
        cc::windows_registry::find(target.as_str(), "msbuild.exe")
            .expect("Could not find msbuild.exe on the registry")
    };

    let jobs = env::var("MAKE_LIBSASS_JOBS").unwrap_or(num_cpus::get().to_string());

    let r = msbuild
        .args(&[
            "win\\libsass.sln",
            "/p:LIBSASS_STATIC_LIB=1",
            "/p:Configuration=Release",
            "/p:WholeProgramOptimization=false",
            format!("/m:{}", jobs).as_str(),
            format!("/p:Platform={}", msvc_platform).as_str(),
        ])
        .current_dir(&build)
        .output()
        .expect("error running msbuild");

    if !r.status.success() {
        let err = String::from_utf8_lossy(&r.stderr);
        let out = String::from_utf8_lossy(&r.stdout);
        panic!("Build error:\nSTDERR:{}\nSTDOUT:{}", err, out);
    }

    println!(
        "cargo:rustc-link-search=native={}",
        build.join("win").join("bin").display()
    );
    println!("cargo:rustc-link-lib=static=libsass");
}

fn main() {
    // Uncomment the line below to generate bindings. Doesn't work on CI as it
    // requires additional tooling
    // write_bindings();

    // Is it already built?
    if let Ok(_) = pkg_config::find_library("libsass") {
        return;
    }

    init_submodule();
    compile();
}
<|endoftext|>


<commit_before>// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![allow(non_upper_case_globals)]

use llvm::{Integer, Pointer, Float, Double, Struct, Array, Vector};
use llvm::{StructRetAttribute, ZExtAttribute};
use trans::cabi::{FnType, ArgType};
use trans::context::CrateContext;
use trans::type_::Type;

use std::cmp;

fn align_up_to(off: usize, a: usize) -> usize {
    return (off + a - 1) / a * a;
}

fn align(off: usize, ty: Type) -> usize {
    let a = ty_align(ty);
    return align_up_to(off, a);
}

fn ty_align(ty: Type) -> usize {
    match ty.kind() {
        Integer => ((ty.int_width() as usize) + 7) / 8,
        Pointer => 8,
        Float => 4,
        Double => 8,
        Struct => {
            if ty.is_packed() {
                1
            } else {
                let str_tys = ty.field_types();
                str_tys.iter().fold(1, |a, t| cmp::max(a, ty_align(*t)))
            }
        }
        Array => {
            let elt = ty.element_type();
            ty_align(elt)
        }
        Vector => {
            let len = ty.vector_length();
            let elt = ty.element_type();
            ty_align(elt) * len
        }
        _ => panic!("ty_align: unhandled type")
    }
}

fn ty_size(ty: Type) -> usize {
    match ty.kind() {
        Integer => ((ty.int_width() as usize) + 7) / 8,
        Pointer => 8,
        Float => 4,
        Double => 8,
        Struct => {
            if ty.is_packed() {
                let str_tys = ty.field_types();
                str_tys.iter().fold(0, |s, t| s + ty_size(*t))
            } else {
                let str_tys = ty.field_types();
                let size = str_tys.iter().fold(0, |s, t| align(s, *t) + ty_size(*t));
                align(size, ty)
            }
        }
        Array => {
            let len = ty.array_length();
            let elt = ty.element_type();
            let eltsz = ty_size(elt);
            len * eltsz
        }
        Vector => {
            let len = ty.vector_length();
            let elt = ty.element_type();
            let eltsz = ty_size(elt);
            len * eltsz
        }
        _ => panic!("ty_size: unhandled type")
    }
}

fn classify_ret_ty(ccx: &CrateContext, ty: Type) -> ArgType {
    if is_reg_ty(ty) {
        let attr = if ty == Type::i1(ccx) { Some(ZExtAttribute) } else { None };
        return ArgType::direct(ty, None, None, attr);
    }
    let size = ty_size(ty);
    if size <= 16 {
        let llty = if size <= 1 {
            Type::i8(ccx)
        } else if size <= 2 {
            Type::i16(ccx)
        } else if size <= 4 {
            Type::i32(ccx)
        } else if size <= 8 {
            Type::i64(ccx)
        } else {
            Type::array(&Type::i64(ccx), ((size + 7 ) / 8 ) as u64)
        };
        return ArgType::direct(ty, Some(llty), None, None);
    }
    ArgType::indirect(ty, Some(StructRetAttribute))
}

fn classify_arg_ty(ccx: &CrateContext, ty: Type) -> ArgType {
    if is_reg_ty(ty) {
        let attr = if ty == Type::i1(ccx) { Some(ZExtAttribute) } else { None };
        return ArgType::direct(ty, None, None, attr);
    }
    let size = ty_size(ty);
    if size <= 16 {
        let llty = if size == 0 {
            Type::array(&Type::i64(ccx), 0)
        } else if size == 1 {
            Type::i8(ccx)
        } else if size == 2 {
            Type::i16(ccx)
        } else if size <= 4 {
            Type::i32(ccx)
        } else if size <= 8 {
            Type::i64(ccx)
        } else {
            Type::array(&Type::i64(ccx), ((size + 7 ) / 8 ) as u64)
        };
        return ArgType::direct(ty, Some(llty), None, None);
    }
    ArgType::indirect(ty, None)
}

fn is_reg_ty(ty: Type) -> bool {
    match ty.kind() {
        Integer
        | Pointer
        | Float
        | Double
        | Vector => true,
        _ => false
    }
}

pub fn compute_abi_info(ccx: &CrateContext,
                        atys: &[Type],
                        rty: Type,
                        ret_def: bool) -> FnType {
    let mut arg_tys = Vec::new();
    for &aty in atys {
        let ty = classify_arg_ty(ccx, aty);
        arg_tys.push(ty);
    }

    let ret_ty = if ret_def {
        classify_ret_ty(ccx, rty)
    } else {
        ArgType::direct(Type::void(ccx), None, None, None)
    };

    return FnType {
        arg_tys: arg_tys,
        ret_ty: ret_ty,
    };
}
<commit_msg>Add Homogenous Aggregates for AArch64 codegen<commit_after>// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![allow(non_upper_case_globals)]

use llvm::{Integer, Pointer, Float, Double, Struct, Array, Vector};
use llvm::{StructRetAttribute, ZExtAttribute};
use trans::cabi::{FnType, ArgType};
use trans::context::CrateContext;
use trans::type_::Type;

use std::cmp;

fn align_up_to(off: usize, a: usize) -> usize {
    return (off + a - 1) / a * a;
}

fn align(off: usize, ty: Type) -> usize {
    let a = ty_align(ty);
    return align_up_to(off, a);
}

fn ty_align(ty: Type) -> usize {
    match ty.kind() {
        Integer => ((ty.int_width() as usize) + 7) / 8,
        Pointer => 8,
        Float => 4,
        Double => 8,
        Struct => {
            if ty.is_packed() {
                1
            } else {
                let str_tys = ty.field_types();
                str_tys.iter().fold(1, |a, t| cmp::max(a, ty_align(*t)))
            }
        }
        Array => {
            let elt = ty.element_type();
            ty_align(elt)
        }
        Vector => {
            let len = ty.vector_length();
            let elt = ty.element_type();
            ty_align(elt) * len
        }
        _ => panic!("ty_align: unhandled type")
    }
}

fn ty_size(ty: Type) -> usize {
    match ty.kind() {
        Integer => ((ty.int_width() as usize) + 7) / 8,
        Pointer => 8,
        Float => 4,
        Double => 8,
        Struct => {
            if ty.is_packed() {
                let str_tys = ty.field_types();
                str_tys.iter().fold(0, |s, t| s + ty_size(*t))
            } else {
                let str_tys = ty.field_types();
                let size = str_tys.iter().fold(0, |s, t| align(s, *t) + ty_size(*t));
                align(size, ty)
            }
        }
        Array => {
            let len = ty.array_length();
            let elt = ty.element_type();
            let eltsz = ty_size(elt);
            len * eltsz
        }
        Vector => {
            let len = ty.vector_length();
            let elt = ty.element_type();
            let eltsz = ty_size(elt);
            len * eltsz
        }
        _ => panic!("ty_size: unhandled type")
    }
}

fn is_homogenous_aggregate_ty(ty: Type) -> Option<(Type, u64)> {
    fn check_array(ty: Type) -> Option<(Type, u64)> {
        let len = ty.array_length() as u64;
        if len == 0 {
            return None
        }
        let elt = ty.element_type();

        // if our element is an HFA/HVA, so are we; multiply members by our len
        is_homogenous_aggregate_ty(elt).map(|(base_ty, members)| (base_ty, len * members))
    }

    fn check_struct(ty: Type) -> Option<(Type, u64)> {
        let str_tys = ty.field_types();
        if str_tys.len() == 0 {
            return None
        }

        let mut prev_base_ty = None;
        let mut members = 0;
        for opt_homog_agg in str_tys.iter().map(|t| is_homogenous_aggregate_ty(*t)) {
            match (prev_base_ty, opt_homog_agg) {
                // field isn't itself an HFA, so we aren't either
                (_, None) => return None,

                // first field - store its type and number of members
                (None, Some((field_ty, field_members))) => {
                    prev_base_ty = Some(field_ty);
                    members = field_members;
                },

                // 2nd or later field - give up if it's a different type; otherwise incr. members
                (Some(prev_ty), Some((field_ty, field_members))) => {
                    if prev_ty != field_ty {
                        return None;
                    }
                    members += field_members;
                }
            }
        }

        // Because of previous checks, we know prev_base_ty is Some(...) because
        //   1. str_tys has at least one element; and
        //   2. prev_base_ty was filled in (or we would've returned early)
        let (base_ty, members) = (prev_base_ty.unwrap(), members);

        // Ensure there is no padding.
        if ty_size(ty) == ty_size(base_ty) * (members as usize) {
            Some((base_ty, members))
        } else {
            None
        }
    }

    let homog_agg = match ty.kind() {
        Float  => Some((ty, 1)),
        Double => Some((ty, 1)),
        Array  => check_array(ty),
        Struct => check_struct(ty),
        Vector => match ty_size(ty) {
            4|8 => Some((ty, 1)),
            _   => None
        },
        _ => None
    };

    // Ensure we have at most four uniquely addressable members
    homog_agg.and_then(|(base_ty, members)| {
        if members > 0 && members <= 4 {
            Some((base_ty, members))
        } else {
            None
        }
    })
}

fn classify_ret_ty(ccx: &CrateContext, ty: Type) -> ArgType {
    if is_reg_ty(ty) {
        let attr = if ty == Type::i1(ccx) { Some(ZExtAttribute) } else { None };
        return ArgType::direct(ty, None, None, attr);
    }
    if let Some((base_ty, members)) = is_homogenous_aggregate_ty(ty) {
        let llty = Type::array(&base_ty, members);
        return ArgType::direct(ty, Some(llty), None, None);
    }
    let size = ty_size(ty);
    if size <= 16 {
        let llty = if size <= 1 {
            Type::i8(ccx)
        } else if size <= 2 {
            Type::i16(ccx)
        } else if size <= 4 {
            Type::i32(ccx)
        } else if size <= 8 {
            Type::i64(ccx)
        } else {
            Type::array(&Type::i64(ccx), ((size + 7 ) / 8 ) as u64)
        };
        return ArgType::direct(ty, Some(llty), None, None);
    }
    ArgType::indirect(ty, Some(StructRetAttribute))
}

fn classify_arg_ty(ccx: &CrateContext, ty: Type) -> ArgType {
    if is_reg_ty(ty) {
        let attr = if ty == Type::i1(ccx) { Some(ZExtAttribute) } else { None };
        return ArgType::direct(ty, None, None, attr);
    }
    if let Some((base_ty, members)) = is_homogenous_aggregate_ty(ty) {
        let llty = Type::array(&base_ty, members);
        return ArgType::direct(ty, Some(llty), None, None);
    }
    let size = ty_size(ty);
    if size <= 16 {
        let llty = if size == 0 {
            Type::array(&Type::i64(ccx), 0)
        } else if size == 1 {
            Type::i8(ccx)
        } else if size == 2 {
            Type::i16(ccx)
        } else if size <= 4 {
            Type::i32(ccx)
        } else if size <= 8 {
            Type::i64(ccx)
        } else {
            Type::array(&Type::i64(ccx), ((size + 7 ) / 8 ) as u64)
        };
        return ArgType::direct(ty, Some(llty), None, None);
    }
    ArgType::indirect(ty, None)
}

fn is_reg_ty(ty: Type) -> bool {
    match ty.kind() {
        Integer
        | Pointer
        | Float
        | Double
        | Vector => true,
        _ => false
    }
}

pub fn compute_abi_info(ccx: &CrateContext,
                        atys: &[Type],
                        rty: Type,
                        ret_def: bool) -> FnType {
    let mut arg_tys = Vec::new();
    for &aty in atys {
        let ty = classify_arg_ty(ccx, aty);
        arg_tys.push(ty);
    }

    let ret_ty = if ret_def {
        classify_ret_ty(ccx, rty)
    } else {
        ArgType::direct(Type::void(ccx), None, None, None)
    };

    return FnType {
        arg_tys: arg_tys,
        ret_ty: ret_ty,
    };
}
<|endoftext|>


<commit_before>use getopts::{optflag,optopt,getopts,usage};
use std::os;
use std::collections::HashMap;
use std::io;
use std::io::fs::PathExtensions;

use item::parse_item;

macro_rules! main_try {
    ($result: expr, $errmsg: expr) => (
        match $result {
            Ok(m) => m,
            Err(e) => {
                println!("{}: {}", $errmsg, e);
                os::set_exit_status(1);
                return;
            }
        }
    )
}

fn get_env() -> HashMap<String, String> {
    let mut env = HashMap::new();
    env.extend(os::env().into_iter().filter(|&(ref key, ref value)| {
        key.as_slice().starts_with("MATES_") && value.len() > 0
    }));
    env
}

fn from_env<'a>(env: &'a HashMap<String, String>, key: &str) -> Option<&'a String> {
    env.find_equiv(&key)
}

fn expect_env<'a>(env: &'a HashMap<String, String>, key: &str) -> &'a String {
    from_env(env, key).expect(
        format!("The {} environment variable must be set.", key).as_slice()
    )
}

fn build_index(outfile: &Path, dir: &Path) -> io::IoResult<()> {
    if !dir.is_dir() {
        return Err(io::IoError {
            kind: io::MismatchedFileTypeForOperation,
            desc: "MATES_DIR must be a directory.",
            detail: None
        });
    };

    let mut outf = io::File::create(outfile);
    let entries = try!(io::fs::readdir(dir));
    for entry in entries.iter() {
        if !entry.is_file() {
            continue;
        }
        let itemstr = try!(io::File::open(entry).read_to_string());
        let item = parse_item(&itemstr);
        let name = item.single_value(&"FN".into_string());
        match item.all_values(&"EMAIL".into_string()) {
            Some(emails) => {
                for email in emails.iter() {
                    try!(outf.write_str(
                        format!("{}\t{}\n", email.get_raw_value(), name).as_slice()
                    ))
                };
            },
            None => ()
        };
    };
    return Ok(());
}

pub fn cli_main() {
    let args = os::args();

    let program = args[0].as_slice();
    let opts = [
        optflag("i", "index", "Create index."),
        optflag("h", "help", "Print help."),
        optopt("m", "mutt-search", "Search in index, for mutt search.", "")
    ];

    let matches = main_try!(getopts(args.tail(), opts), "Failed to parse arguments");

    let env = get_env();

    let print_usage = || {
        println!("{}", usage(program, opts));
        println!("Environment variables:");
        println!("- MATES_INDEX: Path to index file, which is basically a cache of all");
        println!("               contacts.");
        println!("- MATES_DIR:   The vdir to use.");
        println!("- MATES_GREP:  The grep executable to use.");
    };

    if matches.opt_present("h") {
        print_usage();

    } else if matches.opt_present("index") {
        let index_file = expect_env(&env, "MATES_INDEX");
        let mates_dir = expect_env(&env, "MATES_DIR");
        println!("Rebuilding index file \"{}\"...", index_file);
        main_try!(build_index(
            &Path::new(index_file.as_slice()),
            &Path::new(mates_dir.as_slice())
        ), "Failed to build index");

    } else if matches.opt_present("mutt-search") {
        let index_file = expect_env(&env, "MATES_INDEX");
        let default_grep = "grep".into_string();
        let grep_cmd = match from_env(&env, "MATES_GREP") {
            Some(x) => x,
            None => &default_grep
        };

        // FIXME: Better way to write this? We already checked for presence of mutt-search before
        let query = matches.opt_str("mutt-search").expect("This should never happen and yet it did.");
        let mut cmd = io::Command::new(grep_cmd.as_slice());
        cmd.arg(query.as_slice());
        cmd.arg(index_file.as_slice());
        cmd.stdout(io::process::InheritFd(1));
        cmd.stderr(io::process::InheritFd(2));

        let cmd_error = format!("Failed to execute `{}`", cmd);
        println!("");  // For some reason mutt requires an empty line
        let mut process = main_try!(cmd.spawn(), cmd_error);
        let code = main_try!(process.wait(), cmd_error);
        os::set_exit_status(match code {
            io::process::ExitStatus(x) => x,
            io::process::ExitSignal(x) => x,
        });

    } else {
        print_usage();
    };
}
<commit_msg>Some bugfixes<commit_after>use getopts::{optflag,optopt,getopts,usage};
use std::os;
use std::collections::HashMap;
use std::io;
use std::io::fs::PathExtensions;

use item::parse_item;

macro_rules! main_try {
    ($result: expr, $errmsg: expr) => (
        match $result {
            Ok(m) => m,
            Err(e) => {
                println!("{}: {}", $errmsg, e);
                os::set_exit_status(1);
                return;
            }
        }
    )
}

fn get_env() -> HashMap<String, String> {
    let mut env = HashMap::new();
    env.extend(os::env().into_iter().filter(|&(ref key, ref value)| {
        key.as_slice().starts_with("MATES_") && value.len() > 0
    }));
    env
}

fn from_env<'a>(env: &'a HashMap<String, String>, key: &str) -> Option<&'a String> {
    env.find_equiv(key)
}

fn expect_env<'a>(env: &'a HashMap<String, String>, key: &str) -> &'a String {
    from_env(env, key).expect(
        format!("The {} environment variable must be set.", key).as_slice()
    )
}

fn build_index(outfile: &Path, dir: &Path) -> io::IoResult<()> {
    if !dir.is_dir() {
        return Err(io::IoError {
            kind: io::MismatchedFileTypeForOperation,
            desc: "MATES_DIR must be a directory.",
            detail: None
        });
    };

    let mut outf = io::File::create(outfile);
    let entries = try!(io::fs::readdir(dir));
    for entry in entries.iter() {
        if !entry.is_file() {
            continue;
        }

        let itemstr = try!(io::File::open(entry).read_to_string());
        let item = parse_item(&itemstr);

        let name = match item.single_value(&"FN".into_string()) {
            Some(name) => name,
            None => {
                print!("Warning: No name in {}, skipping.\n", entry.display());
                continue;
            }
        };

        let emails = match item.all_values(&"EMAIL".into_string()) {
            Some(emails) => emails,
            None => {
                print!("Warning: No emails in {}, skipping.\n", entry.display());
                continue;
            }
        };

        for email in emails.iter() {
            try!(outf.write_str(
                format!("{}\t{}\n", email.get_raw_value(), name).as_slice()
            ))
        };
    };
    return Ok(());
}

pub fn cli_main() {
    let args = os::args();

    let program = args[0].as_slice();
    let opts = [
        optflag("i", "index", "Create index."),
        optflag("h", "help", "Print help."),
        optopt("m", "mutt-search", "Search in index, for mutt search.", "")
    ];

    let matches = main_try!(getopts(args.tail(), opts), "Failed to parse arguments");

    let env = get_env();

    let print_usage = || {
        println!("{}", usage(program, opts));
        println!("Environment variables:");
        println!("- MATES_INDEX: Path to index file, which is basically a cache of all");
        println!("               contacts.");
        println!("- MATES_DIR:   The vdir to use.");
        println!("- MATES_GREP:  The grep executable to use.");
    };

    if matches.opt_present("h") {
        print_usage();

    } else if matches.opt_present("index") {
        let index_file = expect_env(&env, "MATES_INDEX");
        let mates_dir = expect_env(&env, "MATES_DIR");
        println!("Rebuilding index file \"{}\"...", index_file);
        main_try!(build_index(
            &Path::new(index_file.as_slice()),
            &Path::new(mates_dir.as_slice())
        ), "Failed to build index");

    } else if matches.opt_present("mutt-search") {
        let index_file = expect_env(&env, "MATES_INDEX");
        let default_grep = "grep".into_string();
        let grep_cmd = match from_env(&env, "MATES_GREP") {
            Some(x) => x,
            None => &default_grep
        };

        // FIXME: Better way to write this? We already checked for presence of mutt-search before
        let query = matches.opt_str("mutt-search").expect("This should never happen and yet it did.");
        let mut cmd = io::Command::new(grep_cmd.as_slice());
        cmd.arg(query.as_slice());
        cmd.arg(index_file.as_slice());
        cmd.stdout(io::process::InheritFd(1));
        cmd.stderr(io::process::InheritFd(2));

        let cmd_error = format!("Failed to execute `{}`", cmd);
        println!("");  // For some reason mutt requires an empty line
        let mut process = main_try!(cmd.spawn(), cmd_error);
        let code = main_try!(process.wait(), cmd_error);
        os::set_exit_status(match code {
            io::process::ExitStatus(x) => x,
            io::process::ExitSignal(x) => x,
        });

    } else {
        print_usage();
    };
}
<|endoftext|>


<commit_before>//! Backend using the pure-rust crossplatform crossterm library.
//!
//! Requires the `crossterm-backend` feature.

#![cfg(feature = "crossterm")]

use crate::vec::Vec2;
use crate::{backend, theme};
use crossterm::{
    cursor, input, queue, terminal, AlternateScreen, AsyncReader, Attribute,
    Clear, ClearType, Color, Goto, InputEvent as CInputEvent,
    KeyEvent as CKeyEvent, MouseButton as CMouseButton,
    MouseEvent as CMouseEvent, SetAttr, SetBg, SetFg, Terminal,
};

use crate::event::{Event, Key, MouseButton, MouseEvent};
use std::cell::{Cell, RefCell};
use std::io::{self, BufWriter, Stdout, Write};

/// Backend using crossterm
pub struct Backend {
    current_style: Cell<theme::ColorPair>,
    last_button: Option<MouseButton>,
    // reader to read user input async.
    async_reader: AsyncReader,
    _alternate_screen: AlternateScreen,
    stdout: RefCell<BufWriter<Stdout>>,
    terminal: Terminal,
}

impl Backend {
    /// Creates a new crossterm backend.
    pub fn init() -> Result<Box<dyn backend::Backend>, crossterm::ErrorKind>
    where
        Self: Sized,
    {
        let _alternate_screen = AlternateScreen::to_alternate(true)?;

        let input = input();
        let async_reader = input.read_async();
        input.enable_mouse_mode().unwrap();

        cursor().hide()?;

        Ok(Box::new(Backend {
            current_style: Cell::new(theme::ColorPair::from_256colors(0, 0)),
            last_button: None,
            async_reader,
            _alternate_screen,
            stdout: RefCell::new(BufWriter::new(io::stdout())),
            terminal: terminal(),
        }))
    }

    fn apply_colors(&self, colors: theme::ColorPair) {
        with_color(colors.front, |c| {
            queue!(self.stdout.borrow_mut(), SetFg(*c))
        })
        .unwrap();
        with_color(colors.back, |c| {
            queue!(self.stdout.borrow_mut(), SetBg(*c))
        })
        .unwrap();
    }

    fn write<T>(&self, content: T)
    where
        T: std::fmt::Display,
    {
        write!(self.stdout.borrow_mut(), "{}", format!("{}", content))
            .unwrap();
    }

    fn set_attr(&self, attr: Attribute) {
        queue!(self.stdout.borrow_mut(), SetAttr(attr)).unwrap();
    }

    fn map_key(&mut self, event: CInputEvent) -> Event {
        match event {
            CInputEvent::Keyboard(key_event) => match key_event {
                CKeyEvent::Esc => Event::Key(Key::Esc),
                CKeyEvent::Backspace => Event::Key(Key::Backspace),
                CKeyEvent::Left => Event::Key(Key::Left),
                CKeyEvent::Right => Event::Key(Key::Right),
                CKeyEvent::Up => Event::Key(Key::Up),
                CKeyEvent::Down => Event::Key(Key::Down),
                CKeyEvent::Home => Event::Key(Key::Home),
                CKeyEvent::End => Event::Key(Key::End),
                CKeyEvent::PageUp => Event::Key(Key::PageUp),
                CKeyEvent::PageDown => Event::Key(Key::PageDown),
                CKeyEvent::Delete => Event::Key(Key::Del),
                CKeyEvent::Insert => Event::Key(Key::Ins),
                CKeyEvent::Enter => Event::Key(Key::Enter),
                CKeyEvent::Tab => Event::Key(Key::Tab),
                CKeyEvent::F(n) => Event::Key(Key::from_f(n)),
                CKeyEvent::Char(c) => Event::Char(c),
                CKeyEvent::Ctrl('c') => Event::Exit,
                CKeyEvent::Ctrl(c) => Event::CtrlChar(c),
                CKeyEvent::Alt(c) => Event::AltChar(c),
                _ => Event::Unknown(vec![]),
            },
            CInputEvent::Mouse(mouse_event) => match mouse_event {
                CMouseEvent::Press(btn, x, y) => {
                    let position = (x - 1, y - 1).into();

                    let event = match btn {
                        CMouseButton::Left => {
                            MouseEvent::Press(MouseButton::Left)
                        }
                        CMouseButton::Middle => {
                            MouseEvent::Press(MouseButton::Middle)
                        }
                        CMouseButton::Right => {
                            MouseEvent::Press(MouseButton::Right)
                        }
                        CMouseButton::WheelUp => MouseEvent::WheelUp,
                        CMouseButton::WheelDown => MouseEvent::WheelDown,
                    };

                    if let MouseEvent::Press(btn) = event {
                        self.last_button = Some(btn);
                    }

                    Event::Mouse {
                        event,
                        position,
                        offset: Vec2::zero(),
                    }
                }
                CMouseEvent::Release(x, y) if self.last_button.is_some() => {
                    let event = MouseEvent::Release(self.last_button.unwrap());
                    let position = (x - 1, y - 1).into();

                    Event::Mouse {
                        event,
                        position,
                        offset: Vec2::zero(),
                    }
                }
                CMouseEvent::Hold(x, y) if self.last_button.is_some() => {
                    let event = MouseEvent::Hold(self.last_button.unwrap());
                    let position = (x - 1, y - 1).into();

                    Event::Mouse {
                        event,
                        position,
                        offset: Vec2::zero(),
                    }
                }
                _ => {
                    log::warn!(
                        "Unknown mouse button event {:?}!",
                        mouse_event
                    );
                    Event::Unknown(vec![])
                }
            },
            _ => {
                log::warn!("Unknown mouse event {:?}!", event);
                Event::Unknown(vec![])
            }
        }
    }
}

impl backend::Backend for Backend {
    fn name(&self) -> &str {
        "crossterm"
    }

    fn poll_event(&mut self) -> Option<Event> {
        self.async_reader.next().map(|event| self.map_key(event))
    }

    fn finish(&mut self) {
        input().disable_mouse_mode().unwrap();
        cursor().show().unwrap();
    }

    fn refresh(&mut self) {
        self.stdout.borrow_mut().flush().unwrap();
    }

    fn has_colors(&self) -> bool {
        // TODO: color support detection?
        true
    }

    fn screen_size(&self) -> Vec2 {
        let size = self.terminal.size().unwrap_or((0, 0));
        Vec2::from(size)
    }

    fn print_at(&self, pos: Vec2, text: &str) {
        queue!(self.stdout.borrow_mut(), Goto(pos.x as u16, pos.y as u16))
            .unwrap();
        self.write(text);
    }

    fn print_at_rep(&self, pos: Vec2, repetitions: usize, text: &str) {
        if repetitions > 0 {
            let mut out = self.stdout.borrow_mut();

            queue!(out, Goto(pos.x as u16, pos.y as u16)).unwrap();

            // as I (Timon) wrote this I figured out that calling `write_str` for unix was flushing the stdout.
            // Current work aground is writing bytes instead of a string to the terminal.
            out.write_all(text.as_bytes()).unwrap();

            let mut dupes_left = repetitions - 1;
            while dupes_left > 0 {
                out.write_all(text.as_bytes()).unwrap();
                dupes_left -= 1;
            }
        }
    }

    fn clear(&self, color: theme::Color) {
        self.apply_colors(theme::ColorPair {
            front: color,
            back: color,
        });

        queue!(self.stdout.borrow_mut(), Clear(ClearType::All)).unwrap();
    }

    fn set_color(&self, color: theme::ColorPair) -> theme::ColorPair {
        let current_style = self.current_style.get();

        if current_style != color {
            self.apply_colors(color);
            self.current_style.set(color);
        }

        current_style
    }

    fn set_effect(&self, effect: theme::Effect) {
        match effect {
            theme::Effect::Simple => (),
            theme::Effect::Reverse => self.set_attr(Attribute::Reverse),
            theme::Effect::Bold => self.set_attr(Attribute::Bold),
            theme::Effect::Italic => self.set_attr(Attribute::Italic),
            theme::Effect::Strikethrough => {
                self.set_attr(Attribute::CrossedOut)
            }
            theme::Effect::Underline => self.set_attr(Attribute::Underlined),
        }
    }

    fn unset_effect(&self, effect: theme::Effect) {
        match effect {
            theme::Effect::Simple => (),
            theme::Effect::Reverse => self.set_attr(Attribute::NoInverse),
            theme::Effect::Bold => self.set_attr(Attribute::NormalIntensity),
            theme::Effect::Italic => self.set_attr(Attribute::NoItalic),
            theme::Effect::Strikethrough => {
                self.set_attr(Attribute::NotCrossedOut)
            }
            theme::Effect::Underline => self.set_attr(Attribute::NoUnderline),
        }
    }
}

fn with_color<F, R>(clr: theme::Color, f: F) -> R
where
    F: FnOnce(&Color) -> R,
{
    match clr {
        theme::Color::Dark(theme::BaseColor::Black) => f(&Color::Black),
        theme::Color::Dark(theme::BaseColor::Red) => f(&Color::DarkRed),
        theme::Color::Dark(theme::BaseColor::Green) => f(&Color::DarkGreen),
        theme::Color::Dark(theme::BaseColor::Yellow) => f(&Color::DarkYellow),
        theme::Color::Dark(theme::BaseColor::Blue) => f(&Color::DarkBlue),
        theme::Color::Dark(theme::BaseColor::Magenta) => {
            f(&Color::DarkMagenta)
        }
        theme::Color::Dark(theme::BaseColor::Cyan) => f(&Color::DarkCyan),
        theme::Color::Dark(theme::BaseColor::White) => f(&Color::Grey),

        theme::Color::Light(theme::BaseColor::Black) => f(&Color::Grey),
        theme::Color::Light(theme::BaseColor::Red) => f(&Color::Red),
        theme::Color::Light(theme::BaseColor::Green) => f(&Color::Green),
        theme::Color::Light(theme::BaseColor::Yellow) => f(&Color::Yellow),
        theme::Color::Light(theme::BaseColor::Blue) => f(&Color::Blue),
        theme::Color::Light(theme::BaseColor::Magenta) => f(&Color::Magenta),
        theme::Color::Light(theme::BaseColor::Cyan) => f(&Color::Cyan),
        theme::Color::Light(theme::BaseColor::White) => f(&Color::White),

        theme::Color::Rgb(r, g, b) => f(&Color::Rgb { r, g, b }),
        theme::Color::RgbLowRes(r, g, b) => {
            debug_assert!(r <= 5,
                          "Red color fragment (r = {}) is out of bound. Make sure r ≤ 5.",
                          r);
            debug_assert!(g <= 5,
                          "Green color fragment (g = {}) is out of bound. Make sure g ≤ 5.",
                          g);
            debug_assert!(b <= 5,
                          "Blue color fragment (b = {}) is out of bound. Make sure b ≤ 5.",
                          b);

            f(&Color::AnsiValue(16 + 36 * r + 6 * g + b))
        }

        theme::Color::TerminalDefault => {
            unimplemented!(
                "I have to take a look at how reset has to work out"
            );
        }
    }
}
<commit_msg>Fix crossterm mouse event position (#403)<commit_after>//! Backend using the pure-rust crossplatform crossterm library.
//!
//! Requires the `crossterm-backend` feature.

#![cfg(feature = "crossterm")]

use crate::vec::Vec2;
use crate::{backend, theme};
use crossterm::{
    cursor, input, queue, terminal, AlternateScreen, AsyncReader, Attribute,
    Clear, ClearType, Color, Goto, InputEvent as CInputEvent,
    KeyEvent as CKeyEvent, MouseButton as CMouseButton,
    MouseEvent as CMouseEvent, SetAttr, SetBg, SetFg, Terminal,
};

use crate::event::{Event, Key, MouseButton, MouseEvent};
use std::cell::{Cell, RefCell};
use std::io::{self, BufWriter, Stdout, Write};

/// Backend using crossterm
pub struct Backend {
    current_style: Cell<theme::ColorPair>,
    last_button: Option<MouseButton>,
    // reader to read user input async.
    async_reader: AsyncReader,
    _alternate_screen: AlternateScreen,
    stdout: RefCell<BufWriter<Stdout>>,
    terminal: Terminal,
}

impl Backend {
    /// Creates a new crossterm backend.
    pub fn init() -> Result<Box<dyn backend::Backend>, crossterm::ErrorKind>
    where
        Self: Sized,
    {
        let _alternate_screen = AlternateScreen::to_alternate(true)?;

        let input = input();
        let async_reader = input.read_async();
        input.enable_mouse_mode().unwrap();

        cursor().hide()?;

        Ok(Box::new(Backend {
            current_style: Cell::new(theme::ColorPair::from_256colors(0, 0)),
            last_button: None,
            async_reader,
            _alternate_screen,
            stdout: RefCell::new(BufWriter::new(io::stdout())),
            terminal: terminal(),
        }))
    }

    fn apply_colors(&self, colors: theme::ColorPair) {
        with_color(colors.front, |c| {
            queue!(self.stdout.borrow_mut(), SetFg(*c))
        })
        .unwrap();
        with_color(colors.back, |c| {
            queue!(self.stdout.borrow_mut(), SetBg(*c))
        })
        .unwrap();
    }

    fn write<T>(&self, content: T)
    where
        T: std::fmt::Display,
    {
        write!(self.stdout.borrow_mut(), "{}", format!("{}", content))
            .unwrap();
    }

    fn set_attr(&self, attr: Attribute) {
        queue!(self.stdout.borrow_mut(), SetAttr(attr)).unwrap();
    }

    fn map_key(&mut self, event: CInputEvent) -> Event {
        match event {
            CInputEvent::Keyboard(key_event) => match key_event {
                CKeyEvent::Esc => Event::Key(Key::Esc),
                CKeyEvent::Backspace => Event::Key(Key::Backspace),
                CKeyEvent::Left => Event::Key(Key::Left),
                CKeyEvent::Right => Event::Key(Key::Right),
                CKeyEvent::Up => Event::Key(Key::Up),
                CKeyEvent::Down => Event::Key(Key::Down),
                CKeyEvent::Home => Event::Key(Key::Home),
                CKeyEvent::End => Event::Key(Key::End),
                CKeyEvent::PageUp => Event::Key(Key::PageUp),
                CKeyEvent::PageDown => Event::Key(Key::PageDown),
                CKeyEvent::Delete => Event::Key(Key::Del),
                CKeyEvent::Insert => Event::Key(Key::Ins),
                CKeyEvent::Enter => Event::Key(Key::Enter),
                CKeyEvent::Tab => Event::Key(Key::Tab),
                CKeyEvent::F(n) => Event::Key(Key::from_f(n)),
                CKeyEvent::Char(c) => Event::Char(c),
                CKeyEvent::Ctrl('c') => Event::Exit,
                CKeyEvent::Ctrl(c) => Event::CtrlChar(c),
                CKeyEvent::Alt(c) => Event::AltChar(c),
                _ => Event::Unknown(vec![]),
            },
            CInputEvent::Mouse(mouse_event) => match mouse_event {
                CMouseEvent::Press(btn, x, y) => {
                    let position = (x, y).into();

                    let event = match btn {
                        CMouseButton::Left => {
                            MouseEvent::Press(MouseButton::Left)
                        }
                        CMouseButton::Middle => {
                            MouseEvent::Press(MouseButton::Middle)
                        }
                        CMouseButton::Right => {
                            MouseEvent::Press(MouseButton::Right)
                        }
                        CMouseButton::WheelUp => MouseEvent::WheelUp,
                        CMouseButton::WheelDown => MouseEvent::WheelDown,
                    };

                    if let MouseEvent::Press(btn) = event {
                        self.last_button = Some(btn);
                    }

                    Event::Mouse {
                        event,
                        position,
                        offset: Vec2::zero(),
                    }
                }
                CMouseEvent::Release(x, y) if self.last_button.is_some() => {
                    let event = MouseEvent::Release(self.last_button.unwrap());
                    let position = (x, y).into();

                    Event::Mouse {
                        event,
                        position,
                        offset: Vec2::zero(),
                    }
                }
                CMouseEvent::Hold(x, y) if self.last_button.is_some() => {
                    let event = MouseEvent::Hold(self.last_button.unwrap());
                    let position = (x, y).into();

                    Event::Mouse {
                        event,
                        position,
                        offset: Vec2::zero(),
                    }
                }
                _ => {
                    log::warn!(
                        "Unknown mouse button event {:?}!",
                        mouse_event
                    );
                    Event::Unknown(vec![])
                }
            },
            _ => {
                log::warn!("Unknown mouse event {:?}!", event);
                Event::Unknown(vec![])
            }
        }
    }
}

impl backend::Backend for Backend {
    fn name(&self) -> &str {
        "crossterm"
    }

    fn poll_event(&mut self) -> Option<Event> {
        self.async_reader.next().map(|event| self.map_key(event))
    }

    fn finish(&mut self) {
        input().disable_mouse_mode().unwrap();
        cursor().show().unwrap();
    }

    fn refresh(&mut self) {
        self.stdout.borrow_mut().flush().unwrap();
    }

    fn has_colors(&self) -> bool {
        // TODO: color support detection?
        true
    }

    fn screen_size(&self) -> Vec2 {
        let size = self.terminal.size().unwrap_or((0, 0));
        Vec2::from(size)
    }

    fn print_at(&self, pos: Vec2, text: &str) {
        queue!(self.stdout.borrow_mut(), Goto(pos.x as u16, pos.y as u16))
            .unwrap();
        self.write(text);
    }

    fn print_at_rep(&self, pos: Vec2, repetitions: usize, text: &str) {
        if repetitions > 0 {
            let mut out = self.stdout.borrow_mut();

            queue!(out, Goto(pos.x as u16, pos.y as u16)).unwrap();

            // as I (Timon) wrote this I figured out that calling `write_str` for unix was flushing the stdout.
            // Current work aground is writing bytes instead of a string to the terminal.
            out.write_all(text.as_bytes()).unwrap();

            let mut dupes_left = repetitions - 1;
            while dupes_left > 0 {
                out.write_all(text.as_bytes()).unwrap();
                dupes_left -= 1;
            }
        }
    }

    fn clear(&self, color: theme::Color) {
        self.apply_colors(theme::ColorPair {
            front: color,
            back: color,
        });

        queue!(self.stdout.borrow_mut(), Clear(ClearType::All)).unwrap();
    }

    fn set_color(&self, color: theme::ColorPair) -> theme::ColorPair {
        let current_style = self.current_style.get();

        if current_style != color {
            self.apply_colors(color);
            self.current_style.set(color);
        }

        current_style
    }

    fn set_effect(&self, effect: theme::Effect) {
        match effect {
            theme::Effect::Simple => (),
            theme::Effect::Reverse => self.set_attr(Attribute::Reverse),
            theme::Effect::Bold => self.set_attr(Attribute::Bold),
            theme::Effect::Italic => self.set_attr(Attribute::Italic),
            theme::Effect::Strikethrough => {
                self.set_attr(Attribute::CrossedOut)
            }
            theme::Effect::Underline => self.set_attr(Attribute::Underlined),
        }
    }

    fn unset_effect(&self, effect: theme::Effect) {
        match effect {
            theme::Effect::Simple => (),
            theme::Effect::Reverse => self.set_attr(Attribute::NoInverse),
            theme::Effect::Bold => self.set_attr(Attribute::NormalIntensity),
            theme::Effect::Italic => self.set_attr(Attribute::NoItalic),
            theme::Effect::Strikethrough => {
                self.set_attr(Attribute::NotCrossedOut)
            }
            theme::Effect::Underline => self.set_attr(Attribute::NoUnderline),
        }
    }
}

fn with_color<F, R>(clr: theme::Color, f: F) -> R
where
    F: FnOnce(&Color) -> R,
{
    match clr {
        theme::Color::Dark(theme::BaseColor::Black) => f(&Color::Black),
        theme::Color::Dark(theme::BaseColor::Red) => f(&Color::DarkRed),
        theme::Color::Dark(theme::BaseColor::Green) => f(&Color::DarkGreen),
        theme::Color::Dark(theme::BaseColor::Yellow) => f(&Color::DarkYellow),
        theme::Color::Dark(theme::BaseColor::Blue) => f(&Color::DarkBlue),
        theme::Color::Dark(theme::BaseColor::Magenta) => {
            f(&Color::DarkMagenta)
        }
        theme::Color::Dark(theme::BaseColor::Cyan) => f(&Color::DarkCyan),
        theme::Color::Dark(theme::BaseColor::White) => f(&Color::Grey),

        theme::Color::Light(theme::BaseColor::Black) => f(&Color::Grey),
        theme::Color::Light(theme::BaseColor::Red) => f(&Color::Red),
        theme::Color::Light(theme::BaseColor::Green) => f(&Color::Green),
        theme::Color::Light(theme::BaseColor::Yellow) => f(&Color::Yellow),
        theme::Color::Light(theme::BaseColor::Blue) => f(&Color::Blue),
        theme::Color::Light(theme::BaseColor::Magenta) => f(&Color::Magenta),
        theme::Color::Light(theme::BaseColor::Cyan) => f(&Color::Cyan),
        theme::Color::Light(theme::BaseColor::White) => f(&Color::White),

        theme::Color::Rgb(r, g, b) => f(&Color::Rgb { r, g, b }),
        theme::Color::RgbLowRes(r, g, b) => {
            debug_assert!(r <= 5,
                          "Red color fragment (r = {}) is out of bound. Make sure r ≤ 5.",
                          r);
            debug_assert!(g <= 5,
                          "Green color fragment (g = {}) is out of bound. Make sure g ≤ 5.",
                          g);
            debug_assert!(b <= 5,
                          "Blue color fragment (b = {}) is out of bound. Make sure b ≤ 5.",
                          b);

            f(&Color::AnsiValue(16 + 36 * r + 6 * g + b))
        }

        theme::Color::TerminalDefault => {
            unimplemented!(
                "I have to take a look at how reset has to work out"
            );
        }
    }
}
<|endoftext|>


<commit_before>pub mod alert;
pub mod segment;
pub mod agent;
pub mod swarm;
pub mod gen;
pub mod phen;

use std::rc::Rc;
use std::cell::RefCell;

use backend::obj;
use backend::obj::*;
use rand;
use chrono::Utc;
use chrono::DateTime;
use std::f32::consts;
use std::collections::HashMap;
use std::collections::HashSet;
use std::io;
use std::io::Write;
use std::fs;

use core::clock::*;
use core::geometry::*;
use core::geometry::Transform;
use core::resource::ResourceLoader;
use backend::world::agent::Agent;
use backend::world::agent::AgentType;
use backend::world::agent::TypedAgent;
use backend::world::swarm::*;
use serialize::base64::{self, ToBase64};

pub use self::alert::Alert;
pub use self::alert::AlertEvent;

pub trait WorldState {
	fn agent(&self, id: obj::Id) -> Option<&Agent>;
}

pub trait AlertReceiver {
	fn alert(&mut self, ev: Alert);
}

pub struct World {
	pub extent: Rect,
	swarms: HashMap<AgentType, Swarm>,
	emitters: Vec<Emitter>,
	registered: HashSet<Id>,
	extinctions: usize,
	minion_gene_pool: gen::GenePool,
	resource_gene_pool: gen::GenePool,
	clock: SharedTimer<SimulationTimer>,
	alerts: Vec<AlertEvent>,
}

impl WorldState for World {
	fn agent(&self, id: obj::Id) -> Option<&Agent> {
		self.swarms.get(&id.type_of()).and_then(|m| m.get(id))
	}
}

impl AlertReceiver for World {
	fn alert(&mut self, alert: Alert) {
		let timestamp = self.clock.borrow().seconds();
		self.alerts.push(AlertEvent::new(timestamp, alert));
	}
}

#[derive(Clone)]
pub enum Emission {
	CW(Angle),
	CCW(Angle),
	Random,
}

#[derive(Clone)]
pub struct Emitter {
	transform: Transform,
	rate: Seconds,
	emission: Emission,
}

impl Emitter {
	pub fn new(x: f32, y: f32, rate: Seconds, emission: Emission) -> Self {
		Emitter {
			transform: Transform::from_position(Position::new(x, y)),
			rate,
			emission,
		}
	}
	pub fn rate(&self) -> Seconds {
		self.rate
	}
	pub fn emission(&self) -> Emission {
		self.emission.clone()
	}
}

impl Transformable for Emitter {
	fn transform(&self) -> &Transform {
		&self.transform
	}
	fn transform_to(&mut self, t: &Transform) {
		self.transform.position = t.position;
		self.transform.angle = t.angle;
	}
}

impl World {
	pub fn new<R>(res: &R, minion_gene_pool: &str) -> Self
		where
			R: ResourceLoader<u8>, {
		let mut swarms = HashMap::new();
		let types = AgentType::all();
		let clock = Rc::new(RefCell::new(SimulationTimer::new()));
		for t in types {
			swarms.insert(*t, Swarm::new(*t, clock.clone()));
		}
		fn default_gene_pool(_: io::Error) -> gen::GenePool {
			gen::GenePool::parse_from_base64(
				&[
					"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
					"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
					"GzB2lQVwM00tTAm5gwajjf4wc0a5GzB2lQVwM00tTAm5gwajjf4wc0a5",
					"GzB2lQdwM10vQEu5zwaPgDhfq2v8GzB2lQdwM10vQEu5zwaPgDhfq2v8",
				],
			)
		}
		let emitter_rate = Seconds::new(0.4);
		World {
			extent: Rect::new(-80., -80., 80., 80.),
			swarms,
			emitters: vec![
				Emitter::new(-20., -20., emitter_rate, Emission::CW(consts::PI / 12.)),
				Emitter::new(-20., 20., emitter_rate, Emission::Random),
				Emitter::new(20., 20., emitter_rate, Emission::CCW(consts::PI / 12.)),
				Emitter::new(20., -20., emitter_rate, Emission::Random),
			],
			minion_gene_pool: res.load(minion_gene_pool)
				.map(|data| gen::GenePool::parse_from_resource(&data))
				.unwrap_or_else(default_gene_pool),
			resource_gene_pool: gen::GenePool::parse_from_base64(&["GyA21QoQ", "M00sWS0M"]),
			registered: HashSet::new(),
			extinctions: 0usize,
			clock,
			alerts: Vec::new(),
		}
	}

	pub fn tick(&mut self, dt: Seconds) {
		self.clock.borrow_mut().tick(dt);
	}

	pub fn clock(&self) -> SharedTimer<SimulationTimer> { self.clock.clone() }

	pub fn seconds(&self) -> Seconds { self.clock.borrow().seconds() }

	pub fn extinctions(&self) -> usize {
		self.extinctions
	}

	pub fn new_resource(&mut self, transform: &Transform, motion: Option<&Motion>) -> obj::Id {
		let mut gen = &mut self.resource_gene_pool.next();
		let id = self.swarm_mut(&AgentType::Resource)
			.spawn::<phen::Resource>(&mut gen, transform, motion, 0.8);
		self.register(id)
	}

	pub fn decay_to_resource(&mut self, transform: &Transform, dna: &gen::Dna) -> obj::Id {
		let id = self.swarm_mut(&AgentType::Resource)
			.spawn::<phen::Resource>(&mut gen::Genome::new(dna), transform, None, 0.8);
		self.register(id)
	}

	pub fn new_spore(&mut self, transform: &Transform, dna: &gen::Dna) -> obj::Id {
		let id = self.swarm_mut(&AgentType::Spore).spawn::<phen::Spore>(
			&mut gen::Genome::new(dna).mutate(&mut rand::thread_rng()),
			transform,
			None,
			0.8,
		);
		self.register(id)
	}

	pub fn hatch_spore(&mut self, transform: &Transform, dna: &gen::Dna) -> obj::Id {
		let id = self.swarm_mut(&AgentType::Minion).spawn::<phen::Minion>(
			&mut gen::Genome::new(dna),
			transform,
			None,
			0.3,
		);
		self.register(id)
	}

	pub fn randomize_minion(&mut self, pos: Position, motion: Option<&Motion>) -> obj::Id {
		self.minion_gene_pool.randomize();
		self.new_minion(pos, motion)
	}

	pub fn init_minions(&mut self) {
		self.extinctions += 1;
		let n = self.minion_gene_pool.len();
		let mut r = self.extent.top_right().x * 0.25;
		let mut angle = 0.0f32;
		let angle_delta = consts::PI * 2. / 16. as f32;
		for _ in 0..n {
			let pos = Position::new(r * angle.cos(), r * angle.sin());
			let mut gen = self.minion_gene_pool.next();
			let id = self.swarm_mut(&AgentType::Minion).spawn::<phen::Minion>(
				&mut gen,
				&Transform::new(pos, angle + consts::PI / 2.),
				None,
				0.3,
			);
			self.register(id);
			angle += angle_delta;
			r += 1.;
		}
	}

	pub fn new_minion(&mut self, pos: Position, motion: Option<&Motion>) -> obj::Id {
		let angle = consts::PI / 2. + f32::atan2(pos.y, pos.x);
		let mut gen = self.minion_gene_pool.next();
		let id = self.swarm_mut(&AgentType::Minion).spawn::<phen::Minion>(
			&mut gen,
			&Transform::new(
				pos,
				angle,
			),
			motion,
			0.3,
		);
		self.register(id)
	}

	pub fn register(&mut self, id: obj::Id) -> obj::Id {
		self.registered.insert(id);
		id
	}

	pub fn registered(&mut self) -> Box<[Id]> {
		let collection = self.registered
			.iter()
			.map(|r| *r)
			.collect::<Vec<_>>()
			.into_boxed_slice();
		self.registered.clear();
		collection
	}

	#[allow(dead_code)]
	pub fn agent(&self, id: obj::Id) -> Option<&Agent> {
		self.swarms.get(&id.type_of()).and_then(|m| m.get(id))
	}

	pub fn for_all_agents<F>(&mut self, callback: &mut F)
		where
			F: FnMut(&mut Agent), {
		for (_, swarm) in self.swarms.iter_mut() {
			for (_, mut agent) in swarm.agents_mut().iter_mut() {
				callback(&mut agent)
			}
		}
	}

	pub fn agent_mut(&mut self, id: obj::Id) -> Option<&mut Agent> {
		self.swarms.get_mut(&id.type_of()).and_then(
			|m| m.get_mut(id),
		)
	}

	pub fn agents(&self, agent_type: AgentType) -> &agent::AgentMap {
		self.swarms.get(&agent_type).unwrap().agents()
	}

	pub fn agents_mut(&mut self, agent_type: AgentType) -> &mut agent::AgentMap {
		self.swarms.get_mut(&agent_type).unwrap().agents_mut()
	}

	pub fn swarm_mut(&mut self, agent_type: &AgentType) -> &mut Swarm {
		self.swarms.get_mut(&agent_type).unwrap()
	}

	pub fn emitters(&self) -> &[Emitter] {
		self.emitters.as_slice()
	}

	pub fn swarms(&self) -> &SwarmMap {
		&self.swarms
	}

	pub fn sweep(&mut self) -> Box<[Agent]> {
		let mut v = Vec::new();
		for (_, agents) in self.swarms.iter_mut() {
			agents.free_resources(&mut v);
		}
		v.into_boxed_slice()
	}

	pub fn consume_alerts(&mut self) -> Box<[AlertEvent]> {
		self.alerts.drain(..).collect::<Vec<_>>().into_boxed_slice()
	}

	pub fn dump(&self) -> io::Result<String> {
		let now: DateTime<Utc> = Utc::now();
		let file_name = now.format("resources/%Y%m%d_%H%M%S.csv").to_string();
		let mut f = fs::File::create(&file_name)?;
		for (_, agent) in self.agents(agent::AgentType::Minion).iter() {
			info!("{}", agent.dna().to_base64(base64::STANDARD));
			f.write_fmt(format_args!(
				"{}\n",
				agent.dna().to_base64(base64::STANDARD)
			))?;
		}
		Ok(file_name)
	}
}
<commit_msg>Fixed extinction counter<commit_after>pub mod alert;
pub mod segment;
pub mod agent;
pub mod swarm;
pub mod gen;
pub mod phen;

use std::rc::Rc;
use std::cell::RefCell;

use backend::obj;
use backend::obj::*;
use rand;
use chrono::Utc;
use chrono::DateTime;
use std::f32::consts;
use std::collections::HashMap;
use std::collections::HashSet;
use std::io;
use std::io::Write;
use std::fs;

use core::clock::*;
use core::geometry::*;
use core::geometry::Transform;
use core::resource::ResourceLoader;
use backend::world::agent::Agent;
use backend::world::agent::AgentType;
use backend::world::agent::TypedAgent;
use backend::world::swarm::*;
use serialize::base64::{self, ToBase64};

pub use self::alert::Alert;
pub use self::alert::AlertEvent;

pub trait WorldState {
	fn agent(&self, id: obj::Id) -> Option<&Agent>;
}

pub trait AlertReceiver {
	fn alert(&mut self, ev: Alert);
}

pub struct World {
	pub extent: Rect,
	swarms: HashMap<AgentType, Swarm>,
	emitters: Vec<Emitter>,
	registered: HashSet<Id>,
	regenerations: usize,
	minion_gene_pool: gen::GenePool,
	resource_gene_pool: gen::GenePool,
	clock: SharedTimer<SimulationTimer>,
	alerts: Vec<AlertEvent>,
}

impl WorldState for World {
	fn agent(&self, id: obj::Id) -> Option<&Agent> {
		self.swarms.get(&id.type_of()).and_then(|m| m.get(id))
	}
}

impl AlertReceiver for World {
	fn alert(&mut self, alert: Alert) {
		let timestamp = self.clock.borrow().seconds();
		self.alerts.push(AlertEvent::new(timestamp, alert));
	}
}

#[derive(Clone)]
pub enum Emission {
	CW(Angle),
	CCW(Angle),
	Random,
}

#[derive(Clone)]
pub struct Emitter {
	transform: Transform,
	rate: Seconds,
	emission: Emission,
}

impl Emitter {
	pub fn new(x: f32, y: f32, rate: Seconds, emission: Emission) -> Self {
		Emitter {
			transform: Transform::from_position(Position::new(x, y)),
			rate,
			emission,
		}
	}
	pub fn rate(&self) -> Seconds {
		self.rate
	}
	pub fn emission(&self) -> Emission {
		self.emission.clone()
	}
}

impl Transformable for Emitter {
	fn transform(&self) -> &Transform {
		&self.transform
	}
	fn transform_to(&mut self, t: &Transform) {
		self.transform.position = t.position;
		self.transform.angle = t.angle;
	}
}

impl World {
	pub fn new<R>(res: &R, minion_gene_pool: &str) -> Self
		where
			R: ResourceLoader<u8>, {
		let mut swarms = HashMap::new();
		let types = AgentType::all();
		let clock = Rc::new(RefCell::new(SimulationTimer::new()));
		for t in types {
			swarms.insert(*t, Swarm::new(*t, clock.clone()));
		}
		fn default_gene_pool(_: io::Error) -> gen::GenePool {
			gen::GenePool::parse_from_base64(
				&[
					"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
					"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
					"GzB2lQVwM00tTAm5gwajjf4wc0a5GzB2lQVwM00tTAm5gwajjf4wc0a5",
					"GzB2lQdwM10vQEu5zwaPgDhfq2v8GzB2lQdwM10vQEu5zwaPgDhfq2v8",
				],
			)
		}
		let emitter_rate = Seconds::new(0.4);
		World {
			extent: Rect::new(-80., -80., 80., 80.),
			swarms,
			emitters: vec![
				Emitter::new(-20., -20., emitter_rate, Emission::CW(consts::PI / 12.)),
				Emitter::new(-20., 20., emitter_rate, Emission::Random),
				Emitter::new(20., 20., emitter_rate, Emission::CCW(consts::PI / 12.)),
				Emitter::new(20., -20., emitter_rate, Emission::Random),
			],
			minion_gene_pool: res.load(minion_gene_pool)
				.map(|data| gen::GenePool::parse_from_resource(&data))
				.unwrap_or_else(default_gene_pool),
			resource_gene_pool: gen::GenePool::parse_from_base64(&["GyA21QoQ", "M00sWS0M"]),
			registered: HashSet::new(),
			regenerations: 0usize,
			clock,
			alerts: Vec::new(),
		}
	}

	pub fn tick(&mut self, dt: Seconds) {
		self.clock.borrow_mut().tick(dt);
	}

	pub fn clock(&self) -> SharedTimer<SimulationTimer> { self.clock.clone() }

	pub fn seconds(&self) -> Seconds { self.clock.borrow().seconds() }

	pub fn extinctions(&self) -> usize {
		if self.regenerations > 1 { self.regenerations - 1 } else { 0usize }
	}

	pub fn new_resource(&mut self, transform: &Transform, motion: Option<&Motion>) -> obj::Id {
		let mut gen = &mut self.resource_gene_pool.next();
		let id = self.swarm_mut(&AgentType::Resource)
			.spawn::<phen::Resource>(&mut gen, transform, motion, 0.8);
		self.register(id)
	}

	pub fn decay_to_resource(&mut self, transform: &Transform, dna: &gen::Dna) -> obj::Id {
		let id = self.swarm_mut(&AgentType::Resource)
			.spawn::<phen::Resource>(&mut gen::Genome::new(dna), transform, None, 0.8);
		self.register(id)
	}

	pub fn new_spore(&mut self, transform: &Transform, dna: &gen::Dna) -> obj::Id {
		let id = self.swarm_mut(&AgentType::Spore).spawn::<phen::Spore>(
			&mut gen::Genome::new(dna).mutate(&mut rand::thread_rng()),
			transform,
			None,
			0.8,
		);
		self.register(id)
	}

	pub fn hatch_spore(&mut self, transform: &Transform, dna: &gen::Dna) -> obj::Id {
		let id = self.swarm_mut(&AgentType::Minion).spawn::<phen::Minion>(
			&mut gen::Genome::new(dna),
			transform,
			None,
			0.3,
		);
		self.register(id)
	}

	pub fn randomize_minion(&mut self, pos: Position, motion: Option<&Motion>) -> obj::Id {
		self.minion_gene_pool.randomize();
		self.new_minion(pos, motion)
	}

	pub fn init_minions(&mut self) {
		self.regenerations += 1;
		let n = self.minion_gene_pool.len();
		let mut r = self.extent.top_right().x * 0.25;
		let mut angle = 0.0f32;
		let angle_delta = consts::PI * 2. / 16. as f32;
		for _ in 0..n {
			let pos = Position::new(r * angle.cos(), r * angle.sin());
			let mut gen = self.minion_gene_pool.next();
			let id = self.swarm_mut(&AgentType::Minion).spawn::<phen::Minion>(
				&mut gen,
				&Transform::new(pos, angle + consts::PI / 2.),
				None,
				0.3,
			);
			self.register(id);
			angle += angle_delta;
			r += 1.;
		}
	}

	pub fn new_minion(&mut self, pos: Position, motion: Option<&Motion>) -> obj::Id {
		let angle = consts::PI / 2. + f32::atan2(pos.y, pos.x);
		let mut gen = self.minion_gene_pool.next();
		let id = self.swarm_mut(&AgentType::Minion).spawn::<phen::Minion>(
			&mut gen,
			&Transform::new(
				pos,
				angle,
			),
			motion,
			0.3,
		);
		self.register(id)
	}

	pub fn register(&mut self, id: obj::Id) -> obj::Id {
		self.registered.insert(id);
		id
	}

	pub fn registered(&mut self) -> Box<[Id]> {
		let collection = self.registered
			.iter()
			.map(|r| *r)
			.collect::<Vec<_>>()
			.into_boxed_slice();
		self.registered.clear();
		collection
	}

	#[allow(dead_code)]
	pub fn agent(&self, id: obj::Id) -> Option<&Agent> {
		self.swarms.get(&id.type_of()).and_then(|m| m.get(id))
	}

	pub fn for_all_agents<F>(&mut self, callback: &mut F)
		where
			F: FnMut(&mut Agent), {
		for (_, swarm) in self.swarms.iter_mut() {
			for (_, mut agent) in swarm.agents_mut().iter_mut() {
				callback(&mut agent)
			}
		}
	}

	pub fn agent_mut(&mut self, id: obj::Id) -> Option<&mut Agent> {
		self.swarms.get_mut(&id.type_of()).and_then(
			|m| m.get_mut(id),
		)
	}

	pub fn agents(&self, agent_type: AgentType) -> &agent::AgentMap {
		self.swarms.get(&agent_type).unwrap().agents()
	}

	pub fn agents_mut(&mut self, agent_type: AgentType) -> &mut agent::AgentMap {
		self.swarms.get_mut(&agent_type).unwrap().agents_mut()
	}

	pub fn swarm_mut(&mut self, agent_type: &AgentType) -> &mut Swarm {
		self.swarms.get_mut(&agent_type).unwrap()
	}

	pub fn emitters(&self) -> &[Emitter] {
		self.emitters.as_slice()
	}

	pub fn swarms(&self) -> &SwarmMap {
		&self.swarms
	}

	pub fn sweep(&mut self) -> Box<[Agent]> {
		let mut v = Vec::new();
		for (_, agents) in self.swarms.iter_mut() {
			agents.free_resources(&mut v);
		}
		v.into_boxed_slice()
	}

	pub fn consume_alerts(&mut self) -> Box<[AlertEvent]> {
		self.alerts.drain(..).collect::<Vec<_>>().into_boxed_slice()
	}

	pub fn dump(&self) -> io::Result<String> {
		let now: DateTime<Utc> = Utc::now();
		let file_name = now.format("resources/%Y%m%d_%H%M%S.csv").to_string();
		let mut f = fs::File::create(&file_name)?;
		for (_, agent) in self.agents(agent::AgentType::Minion).iter() {
			info!("{}", agent.dna().to_base64(base64::STANDARD));
			f.write_fmt(format_args!(
				"{}\n",
				agent.dna().to_base64(base64::STANDARD)
			))?;
		}
		Ok(file_name)
	}
}
<|endoftext|>


<commit_before>use anyhow::Result;
use clap::Parser;
use regex::Regex;
use std::collections::HashMap;
use std::fs::File;
use std::io::{self, BufRead};
use std::path::PathBuf;
use std::time::Instant;

use polars::frame::DataFrame;
use polars::prelude::*;

#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
    /// List of input files. Ideally with headers
    #[clap(short, long, multiple_values(true))]
    input: Vec<PathBuf>,

    /// An output file
    #[clap(short, long)]
    output: String,

    /// The column to select. If files have no headers, use "column_N"
    #[clap(short, long, multiple_values(true))]
    select: Vec<String>,

    /// The size of chunks to process at once
    #[clap(short, long, default_value_t = 100)]
    chunksize: usize,
}

fn load_data(path: &PathBuf, delim: u8) -> Result<DataFrame> {
    let mut df: DataFrame = CsvReader::from_path(path)?
        .has_header(true)
        .with_delimiter(delim)
        .with_null_values(Some(NullValues::AllColumns("NA".to_string())))
        .infer_schema(None)
        .finish()
        .unwrap();

    // hstack the experiment name (derived from the filename) into the DataFrame
    let iter_exp =
        vec![path.file_name().unwrap().to_str().unwrap().split('-').collect::<Vec<&str>>()[0..=2]
            .join("-")]
        .into_iter();
    let exp_col: Series = Series::new(
        "experiment",
        iter_exp.flat_map(|n| std::iter::repeat(n).take(df.height())).collect::<Vec<String>>(),
    );
    // exp_col.rename("experiment");
    df.hstack_mut(&[exp_col]).unwrap();

    Ok(df)
}

fn baseline_get_median_gt_zero(str_val: &Series) -> Series {
    let lists = str_val.utf8().unwrap().into_iter().map(|x| {
        x.unwrap().split(',').into_iter().map(|y| y.parse::<f64>().unwrap()).collect::<Vec<f64>>()
    });

    let medians: Vec<bool> =
        lists.into_iter().map(|x| Series::from_iter(x).median().unwrap() > 0.0).collect();
    Series::from_iter(medians)
}

fn filter_input(input: &mut DataFrame, baseline: bool) -> DataFrame {
    if baseline {
        filter_baseline(input)
    } else {
        filter_differential(input)
    }
}

fn filter_baseline(input: &mut DataFrame) -> DataFrame {
    // This is a baseline experiment.
    // Find columns starting with lower case g, then apply the function to convert to
    // medain and select greater than zero
    let mut meas = Vec::<Expr>::new();

    for column in input.get_column_names_owned() {
        if column.starts_with('g') {
            input.apply(&column, baseline_get_median_gt_zero).unwrap();
            meas.push(col(&column));
        }
    }
    // println!("{:?}", meas);
    // Selection should now have all the gN column names in it
    input
        .clone()
        .lazy()
        .filter(any_exprs(&meas[0..meas.len()/2]))
        .filter(any_exprs(&meas[meas.len()/2 .. meas.len()])) // If we try to do the whole thing at once, we get a stack overflow
        .collect()
        .unwrap()
}

fn filter_differential(input: &DataFrame) -> DataFrame {
    // find the p value and log fold columns
    let pv_regex = Regex::new(r".*p-value.*").unwrap();
    let log_fold_regex = Regex::new(r".*log2.*").unwrap();
    let mut exprs: Vec<Expr> = Vec::new();
    let mut pv: Expr;
    let mut lf: Expr;
    for column in input.get_column_names_owned() {
        if pv_regex.is_match(&column) {
            pv = col(&column).is_not_null();
            exprs.push(pv);
        } else if log_fold_regex.is_match(&column) {
            lf = col(&column).neq(lit(0));
            exprs.push(lf);
        }
    }

    input.clone().lazy().filter(all_exprs(exprs)).collect().unwrap()
}

fn load_chunk(
    paths: &mut Vec<PathBuf>,
    select: &mut Vec<String>,
) -> Result<DataFrame, anyhow::Error> {
    /*
    Work with the chunks of input to reduce them into a single dataframe of the genes we want from
    that particular set of experiments. Should return a single dataframe
    */
    let mut output: Option<DataFrame> = None;
    while let Some(infile) = paths.pop() {
        // Load the input Csv
        let mut input: DataFrame = if infile.extension().unwrap() == "tsv" {
            load_data(&infile, b'\t')
        } else {
            load_data(&infile, b',')
        }
        .unwrap_or_else(|error| match error.downcast_ref::<PolarsError>() {
            Some(PolarsError::Io(_string)) => panic!("Input file does not exist! {:?}", infile),
            _ => panic!("An error occurred! {:?}", error),
        });

        // Rename columns to remove . in the names. Now also remove spaces
        let mut new_cols = Vec::new();
        let mut c: u8 = 0;
        for nm in input.get_column_names().iter() {
            let mut new_name = nm.replace('.', "").replace(' ', "");
            if new_cols.contains(&new_name) {
                c += 1;
                new_name += &c.to_string(); // Hopefully avoid duplicate names
            }
            new_cols.push(new_name);
        }

        if new_cols != input.get_column_names() {
            input.set_column_names(&new_cols).unwrap_or_else(|error| {
                panic!("Failed to set column names for some reason {:?}", error);
            });
        }

        // Need to detect which type of experiment it is - differential or baseline
        let type_re = Regex::new(r"tpms").unwrap();
        let filtered = filter_input(&mut input, type_re.is_match(infile.to_str().unwrap()));
        // Drop everything from the input except the genes
        let genes: DataFrame = filtered.select(select.iter()).unwrap_or_else(|error| match error {
            PolarsError::NotFound(_string) => {
                panic!(
                    "{:?} was not found in the header, does the file have a header?\n{:?}",
                    select,
                    input.get_column_names()
                );
            },
            _ => panic!("Error selecting column from input: {:?}", error),
        });

        output = match output {
            None => {
                let output_df = genes.clone();
                Some(output_df)
            },
            Some(mut output_df) => {
                // genes =/= output, so we are handling a new file
                output_df.extend(&genes).unwrap_or_else(|error| {
                    panic!("Failed to extend the output dataframe. {:?}", error)
                });
                output_df =
                    output_df.unique(None, UniqueKeepStrategy::First).unwrap_or_else(|error| {
                        panic!("Failed to parse selected column for uniqueness. {:?}", error)
                    });
                Some(output_df)
            },
        }
    }
    Ok(output.unwrap())
}

fn get_taxid(paths: &mut Vec<PathBuf>, lookup_table: &mut HashMap<String, String>) {
    let tax_regex = Regex::new(r".*Taxon_([0-9]{4,})").unwrap();

    while let Some(path) = paths.pop() {
        let exp_name =
            path.file_name().unwrap().to_str().unwrap().split('-').collect::<Vec<&str>>()[0..=2]
                .join("-")
                .replace(".condensed", "");

        let file = File::open(path).unwrap();
        let lines = io::BufReader::new(file).lines();
        for line in lines {
            match line {
                Ok(line) => {
                    let captures = tax_regex.captures(line.as_str());
                    match captures {
                        Some(caps) => {
                            let taxid = caps.get(1).map_or("", |m| m.as_str());
                            lookup_table.insert(exp_name, taxid.to_string());
                            break;
                        },
                        None => {},
                    };
                },
                Err(_idc) => {},
            }
        }
    }
}

fn main() {
    let timer = Instant::now();

    let mut args = Args::parse();

    // Always select the experiment
    args.select.push("experiment".to_string());

    // separate expression data from taxa data
    let mut taxa_files: Vec<PathBuf> = Vec::new();
    let mut expr_files: Vec<PathBuf> = Vec::new();

    let re = Regex::new(r"sdrf.tsv").unwrap();

    for infile in args.input {
        if re.is_match(infile.to_str().unwrap()) {
            // File is experiment setup data, parse for taxa
            taxa_files.push(infile);
        } else {
            // Should only be expression data, parse for genes
            expr_files.push(infile);
        }
    }

    println!("{} taxa files to parse", taxa_files.len());
    println!("{} expression files to parse", expr_files.len());

    let n_files = expr_files.len();

    let mut dataframes: Vec<DataFrame> = Vec::new();

    let chunked_input: Vec<Vec<PathBuf>> =
        expr_files.chunks(args.chunksize).map(|x| x.to_vec()).collect();
    // Read everything into a big vector
    let mut n_chunks = 0;
    for mut files_chunk in chunked_input {
        dataframes.push(
            load_chunk(&mut files_chunk, &mut args.select)
                .unwrap_or_else(|_error| panic!("Something wrong in one of the reads, aborting")),
        );
        n_chunks += 1;
        println!("Done {} files", n_chunks * args.chunksize);
    }

    let mut output: DataFrame = dataframes[0].clone();

    for item in dataframes.iter() {
        output.extend(item).unwrap_or_else(|error| {
            panic!("Unable to extend the DataFrame! Out of memory? {:?}", error)
        });
        output.unique(None, UniqueKeepStrategy::First).unwrap();
    }

    // Now parse the experiment files to get taxa
    let chunked_taxa: Vec<Vec<PathBuf>> =
        taxa_files.chunks(args.chunksize).map(|x| x.to_vec()).collect();

    let mut lookup_table: HashMap<String, String> = HashMap::new();

    for mut tax_chunk in chunked_taxa {
        get_taxid(&mut tax_chunk, &mut lookup_table);
    }

    // Now we have the experiment - taxid lookup_table, we just need to add the column to the output df

    let expt_col = output.select(["experiment"]).unwrap();
    let mut tax_ids: Vec<String> = Vec::with_capacity(expt_col.height());
    for ex in expt_col.iter() {
        let mut warn_flag: bool = true;
        let uhy = ex.utf8().unwrap();
        for (idx, x) in uhy.into_iter().enumerate() {
            if lookup_table.contains_key(x.unwrap()) {
                tax_ids.insert(idx, lookup_table.get(x.unwrap()).unwrap().to_string());
            } else {
                tax_ids.insert(idx, String::new());
                if warn_flag {
                    println!("Experiment {} does not name a taxon", x.unwrap());
                    warn_flag = false;
                }
            }
        }
    }

    output.hstack_mut(&[Series::new("taxid", tax_ids.into_iter())]).unwrap();

    let out_stream: File = File::create(args.output).unwrap();
    CsvWriter::new(out_stream)
        .has_header(true)
        .finish(&mut output)
        .unwrap_or_else(|error| panic!("Something wrong writing file: {:?}", error));

    println!("Processed {} in {} seconds", n_files, timer.elapsed().as_secs());
}
<commit_msg>Previous split didn't quite work - combine the expression fold with an or<commit_after>use anyhow::Result;
use clap::Parser;
use regex::Regex;
use std::collections::HashMap;
use std::fs::File;
use std::io::{self, BufRead};
use std::path::PathBuf;
use std::time::Instant;

use polars::frame::DataFrame;
use polars::prelude::*;

#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
    /// List of input files. Ideally with headers
    #[clap(short, long, multiple_values(true))]
    input: Vec<PathBuf>,

    /// An output file
    #[clap(short, long)]
    output: String,

    /// The column to select. If files have no headers, use "column_N"
    #[clap(short, long, multiple_values(true))]
    select: Vec<String>,

    /// The size of chunks to process at once
    #[clap(short, long, default_value_t = 100)]
    chunksize: usize,
}

fn load_data(path: &PathBuf, delim: u8) -> Result<DataFrame> {
    let mut df: DataFrame = CsvReader::from_path(path)?
        .has_header(true)
        .with_delimiter(delim)
        .with_null_values(Some(NullValues::AllColumns("NA".to_string())))
        .infer_schema(None)
        .finish()
        .unwrap();

    // hstack the experiment name (derived from the filename) into the DataFrame
    let iter_exp =
        vec![path.file_name().unwrap().to_str().unwrap().split('-').collect::<Vec<&str>>()[0..=2]
            .join("-")]
        .into_iter();
    let exp_col: Series = Series::new(
        "experiment",
        iter_exp.flat_map(|n| std::iter::repeat(n).take(df.height())).collect::<Vec<String>>(),
    );
    // exp_col.rename("experiment");
    df.hstack_mut(&[exp_col]).unwrap();

    Ok(df)
}

fn baseline_get_median_gt_zero(str_val: &Series) -> Series {
    let lists = str_val.utf8().unwrap().into_iter().map(|x| {
        x.unwrap().split(',').into_iter().map(|y| y.parse::<f64>().unwrap()).collect::<Vec<f64>>()
    });

    let medians: Vec<bool> =
        lists.into_iter().map(|x| Series::from_iter(x).median().unwrap() > 0.0).collect();
    Series::from_iter(medians)
}

fn filter_input(input: &mut DataFrame, baseline: bool) -> DataFrame {
    if baseline {
        filter_baseline(input)
    } else {
        filter_differential(input)
    }
}

fn filter_baseline(input: &mut DataFrame) -> DataFrame {
    // This is a baseline experiment.
    // Find columns starting with lower case g, then apply the function to convert to
    // medain and select greater than zero
    let mut meas = Vec::<Expr>::new();

    for column in input.get_column_names_owned() {
        if column.starts_with('g') {
            input.apply(&column, baseline_get_median_gt_zero).unwrap();
            meas.push(col(&column));
        }
    }
    // Selection should now have all the gN column names in it
    input
        .clone()
        .lazy()
        .filter(any_exprs(&meas[0..meas.len()/2]).or(any_exprs(&meas[meas.len()/2 .. meas.len()])) ) // If we try to do the whole thing at once, we get a stack overflow
        .collect()
        .unwrap()
}

fn filter_differential(input: &DataFrame) -> DataFrame {
    // find the p value and log fold columns
    let pv_regex = Regex::new(r".*p-value.*").unwrap();
    let log_fold_regex = Regex::new(r".*log2.*").unwrap();
    let mut exprs: Vec<Expr> = Vec::new();
    let mut pv: Expr;
    let mut lf: Expr;
    for column in input.get_column_names_owned() {
        if pv_regex.is_match(&column) {
            pv = col(&column).is_not_null();
            exprs.push(pv);
        } else if log_fold_regex.is_match(&column) {
            lf = col(&column).neq(lit(0));
            exprs.push(lf);
        }
    }

    input.clone().lazy().filter(all_exprs(exprs)).collect().unwrap()
}

fn load_chunk(
    paths: &mut Vec<PathBuf>,
    select: &mut Vec<String>,
) -> Result<DataFrame, anyhow::Error> {
    /*
    Work with the chunks of input to reduce them into a single dataframe of the genes we want from
    that particular set of experiments. Should return a single dataframe
    */
    let mut output: Option<DataFrame> = None;
    while let Some(infile) = paths.pop() {
        // Load the input Csv
        let mut input: DataFrame = if infile.extension().unwrap() == "tsv" {
            load_data(&infile, b'\t')
        } else {
            load_data(&infile, b',')
        }
        .unwrap_or_else(|error| match error.downcast_ref::<PolarsError>() {
            Some(PolarsError::Io(_string)) => panic!("Input file does not exist! {:?}", infile),
            _ => panic!("An error occurred! {:?}", error),
        });

        // Rename columns to remove . in the names. Now also remove spaces
        let mut new_cols = Vec::new();
        let mut c: u8 = 0;
        for nm in input.get_column_names().iter() {
            let mut new_name = nm.replace('.', "").replace(' ', "");
            if new_cols.contains(&new_name) {
                c += 1;
                new_name += &c.to_string(); // Hopefully avoid duplicate names
            }
            new_cols.push(new_name);
        }

        if new_cols != input.get_column_names() {
            input.set_column_names(&new_cols).unwrap_or_else(|error| {
                panic!("Failed to set column names for some reason {:?}", error);
            });
        }

        // Need to detect which type of experiment it is - differential or baseline
        let type_re = Regex::new(r"tpms").unwrap();
        let filtered = filter_input(&mut input, type_re.is_match(infile.to_str().unwrap()));
        // Drop everything from the input except the genes
        let genes: DataFrame = filtered.select(select.iter()).unwrap_or_else(|error| match error {
            PolarsError::NotFound(_string) => {
                panic!(
                    "{:?} was not found in the header, does the file have a header?\n{:?}",
                    select,
                    input.get_column_names()
                );
            },
            _ => panic!("Error selecting column from input: {:?}", error),
        });

        output = match output {
            None => {
                let output_df = genes.clone();
                Some(output_df)
            },
            Some(mut output_df) => {
                // genes =/= output, so we are handling a new file
                output_df.extend(&genes).unwrap_or_else(|error| {
                    panic!("Failed to extend the output dataframe. {:?}", error)
                });
                output_df =
                    output_df.unique(None, UniqueKeepStrategy::First).unwrap_or_else(|error| {
                        panic!("Failed to parse selected column for uniqueness. {:?}", error)
                    });
                Some(output_df)
            },
        }
    }
    Ok(output.unwrap())
}

fn get_taxid(paths: &mut Vec<PathBuf>, lookup_table: &mut HashMap<String, String>) {
    let tax_regex = Regex::new(r".*Taxon_([0-9]{4,})").unwrap();

    while let Some(path) = paths.pop() {
        let exp_name =
            path.file_name().unwrap().to_str().unwrap().split('-').collect::<Vec<&str>>()[0..=2]
                .join("-")
                .replace(".condensed", "");

        let file = File::open(path).unwrap();
        let lines = io::BufReader::new(file).lines();
        for line in lines {
            match line {
                Ok(line) => {
                    let captures = tax_regex.captures(line.as_str());
                    match captures {
                        Some(caps) => {
                            let taxid = caps.get(1).map_or("", |m| m.as_str());
                            lookup_table.insert(exp_name, taxid.to_string());
                            break;
                        },
                        None => {},
                    };
                },
                Err(_idc) => {},
            }
        }
    }
}

fn main() {
    let timer = Instant::now();

    let mut args = Args::parse();

    // Always select the experiment
    args.select.push("experiment".to_string());

    // separate expression data from taxa data
    let mut taxa_files: Vec<PathBuf> = Vec::new();
    let mut expr_files: Vec<PathBuf> = Vec::new();

    let re = Regex::new(r"sdrf.tsv").unwrap();

    for infile in args.input {
        if re.is_match(infile.to_str().unwrap()) {
            // File is experiment setup data, parse for taxa
            taxa_files.push(infile);
        } else {
            // Should only be expression data, parse for genes
            expr_files.push(infile);
        }
    }

    println!("{} taxa files to parse", taxa_files.len());
    println!("{} expression files to parse", expr_files.len());

    let n_files = expr_files.len();

    let mut dataframes: Vec<DataFrame> = Vec::new();

    let chunked_input: Vec<Vec<PathBuf>> =
        expr_files.chunks(args.chunksize).map(|x| x.to_vec()).collect();
    // Read everything into a big vector
    let mut n_chunks = 0;
    for mut files_chunk in chunked_input {
        dataframes.push(
            load_chunk(&mut files_chunk, &mut args.select)
                .unwrap_or_else(|_error| panic!("Something wrong in one of the reads, aborting")),
        );
        n_chunks += 1;
        println!("Done {} files", n_chunks * args.chunksize);
    }

    let mut output: DataFrame = dataframes[0].clone();

    for item in dataframes.iter() {
        output.extend(item).unwrap_or_else(|error| {
            panic!("Unable to extend the DataFrame! Out of memory? {:?}", error)
        });
        output.unique(None, UniqueKeepStrategy::First).unwrap();
    }

    // Now parse the experiment files to get taxa
    let chunked_taxa: Vec<Vec<PathBuf>> =
        taxa_files.chunks(args.chunksize).map(|x| x.to_vec()).collect();

    let mut lookup_table: HashMap<String, String> = HashMap::new();

    for mut tax_chunk in chunked_taxa {
        get_taxid(&mut tax_chunk, &mut lookup_table);
    }

    // Now we have the experiment - taxid lookup_table, we just need to add the column to the output df

    let expt_col = output.select(["experiment"]).unwrap();
    let mut tax_ids: Vec<String> = Vec::with_capacity(expt_col.height());
    for ex in expt_col.iter() {
        let mut warn_flag: bool = true;
        let uhy = ex.utf8().unwrap();
        for (idx, x) in uhy.into_iter().enumerate() {
            if lookup_table.contains_key(x.unwrap()) {
                tax_ids.insert(idx, lookup_table.get(x.unwrap()).unwrap().to_string());
            } else {
                tax_ids.insert(idx, String::new());
                if warn_flag {
                    println!("Experiment {} does not name a taxon", x.unwrap());
                    warn_flag = false;
                }
            }
        }
    }

    output.hstack_mut(&[Series::new("taxid", tax_ids.into_iter())]).unwrap();

    let out_stream: File = File::create(args.output).unwrap();
    CsvWriter::new(out_stream)
        .has_header(true)
        .finish(&mut output)
        .unwrap_or_else(|error| panic!("Something wrong writing file: {:?}", error));

    println!("Processed {} in {} seconds", n_files, timer.elapsed().as_secs());
}
<|endoftext|>