<commit_before>extern crate argparse;
extern crate env_logger;
extern crate libc;
extern crate lithos;
extern crate quire;
extern crate scan_dir;
#[macro_use] extern crate log;

use std::env;
use std::fs::{metadata};
use std::path::{Path, PathBuf};
use std::process::exit;
use std::collections::BTreeMap;
use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT};

use argparse::{ArgumentParser, Parse, ParseOption, StoreTrue, Print, Collect};
use quire::{parse_config, Options};

use lithos::utils::{in_mapping, check_mapping, relative};
use lithos::range::in_range;
use lithos::master_config::MasterConfig;
use lithos::sandbox_config::SandboxConfig;
use lithos::container_config::{ContainerConfig, Variables};
use lithos::child_config::ChildConfig;
use lithos::network::{get_host_name, get_host_ip};
use lithos::id_map::{IdMapExt};

static EXIT_STATUS: AtomicUsize = ATOMIC_USIZE_INIT;

macro_rules! err {
    ( $( $x:expr ),* ) => {
        {
            error!($($x),*);
            EXIT_STATUS.store(1,  Ordering::SeqCst);
        }
    }
}

fn check_master_config(master: &MasterConfig, verbose: bool) {
    // TODO(tailhook) maybe check host only if we need it for hosts file
    match get_host_name() {
        Ok(hostname) => {
            if verbose {
                println!("Hostname is {}", hostname);
            }
        }
        Err(e) => {
            err!("Can't get hostname: {}", e);
        }
    }
    match get_host_ip() {
        Ok(ipaddr) => {
            if verbose {
                println!("IPAddr is {}", ipaddr);
            }
        }
        Err(e) => {
            err!("Can't get IPAddress: {}", e);
        }
    }

    if metadata(&master.devfs_dir).is_err() {
        err!("Devfs dir ({:?}) must exist and contain device nodes",
            master.devfs_dir);
    }
}

fn check_sandbox_config(sandbox: &SandboxConfig) {
    if sandbox.allow_users.len() == 0 {
        err!("No allowed users range. Please add `allow-users: [1-1000]`");
    }
    if sandbox.allow_groups.len() == 0 {
        err!("No allowed groups range. Please add `allow-groups: [1-1000]`");
    }
    // TODO(tailhook) check allow_users/allow_groups against uid_map/gid_map
}

fn check_container(config_file: &Path) -> Result<ContainerConfig, ()>
{
    // Only checks things that can be checked without other configs
    let config: ContainerConfig = match parse_config(config_file,
        &ContainerConfig::validator(), &Options::default())
    {
        Ok(cfg) => cfg,
        Err(e) => {
            err!("Can't read container config {:?}: {}", config_file, e);
            return Err(());
        }
    };
    if config.uid_map.len() > 0 {
        if !in_mapping(&config.uid_map, config.user_id) {
            err!("User is not in mapped range (uid: {})",
                config.user_id);
        }
    }
    if config.gid_map.len() > 0 {
        if !in_mapping(&config.gid_map, config.group_id) {
            err!("Group is not in mapped range (gid: {})",
                config.user_id);
        }
    }
    Ok(config)
}

fn check(config_file: &Path, verbose: bool,
    altered_sandbox: Option<String>, alter_config: Option<PathBuf>)
{
    let mut alter_config = alter_config;
    let master: MasterConfig = match parse_config(&config_file,
        &MasterConfig::validator(), &Options::default()) {
        Ok(cfg) => cfg,
        Err(e) => {
            err!("Can't parse config: {}", e);
            return;
        }
    };

    check_master_config(&master, verbose);

    let config_dir = config_file.parent().unwrap().join(&master.sandboxes_dir);
    scan_dir::ScanDir::files().read(&config_dir, |iter| {
        let yamls = iter.filter(|&(_, ref name)| name.ends_with(".yaml"));
        for (entry, current_fn) in yamls {
            // strip yaml suffix
            let current_name = &current_fn[..current_fn.len()-5];
            let sandbox: SandboxConfig = match parse_config(&entry.path(),
                &SandboxConfig::validator(), &Options::default()) {
                Ok(cfg) => cfg,
                Err(e) => {
                    err!("Can't parse config: {}", e);
                    continue;
                }
            };
            check_sandbox_config(&sandbox);

            let default_config = config_file.parent().unwrap()
                .join(&master.processes_dir)
                .join(sandbox.config_file.as_ref().unwrap_or(
                    &PathBuf::from(&current_fn)));
            let config_file = match (current_name, &altered_sandbox)
            {
                (name, &Some(ref t)) if name == t
                => alter_config.take().unwrap_or(default_config),
                _ => default_config,
            };

            debug!("Checking {:?}", config_file);
            let all_children: BTreeMap<String, ChildConfig>;
            all_children = match parse_config(&config_file,
                &ChildConfig::mapping_validator(), &Options::default()) {
                Ok(cfg) => cfg,
                Err(e) => {
                    warn!("Can't read child config {:?}: {}", config_file, e);
                    continue;
                }
            };
            for (ref child_name, ref child_cfg) in all_children.iter() {
                let cfg_path = Path::new(&child_cfg.config);
                if !cfg_path.is_absolute() {
                    err!("Config path must be absolute");
                    continue;
                }
                if !sandbox.check_path(&child_cfg.image) {
                    err!("Image {} in sandbox {}, proccess {} is invalid",
                        child_cfg.image, current_name, child_name);
                    continue;
                }
                debug!("Opening config for {:?}", child_name);
                let config = match check_container(&sandbox.image_dir
                    .join(&child_cfg.image)
                    .join(&relative(cfg_path, &Path::new("/"))))
                {
                    Ok(config) => config,
                    Err(()) => continue,
                };
                // Uidmaps aren't substituted
                if config.uid_map.len() > 0 {
                    if sandbox.uid_map.len() > 0 {
                        err!("Can't have uid_maps in both the sandbox and a \
                              container itself");
                    }
                } else {
                    if sandbox.uid_map.len() > 0 {
                        if sandbox.uid_map.map_id(config.user_id).is_none() {
                            err!("User is not in mapped range \
                                (uid: {})",
                                config.user_id);
                        }
                    }
                    if !in_range(&sandbox.allow_users, config.user_id) {
                        err!("User is not in allowed range (uid: {})",
                            config.user_id);
                    }
                }
                if config.gid_map.len() > 0 {
                    if sandbox.gid_map.len() > 0 {
                        err!("Can't have uid_maps in both the sandbox and a \
                              container itself");
                    }
                } else {
                    if sandbox.gid_map.len() > 0 {
                        if sandbox.gid_map.map_id(config.group_id).is_none() {
                            err!("Group is not in mapped range \
                                (gid: {})",
                                config.group_id);
                        }
                    }
                    if !in_range(&sandbox.allow_groups, config.group_id) {
                        err!("Group is not in allowed range (gid: {})",
                            config.group_id);
                    }
                }
                if !check_mapping(&sandbox.allow_users, &config.uid_map) {
                    err!("Bad uid mapping (probably doesn't match allow_users)");
                }
                if !check_mapping(&sandbox.allow_groups, &config.gid_map) {
                    err!("Bad gid mapping (probably doesn't match allow_groups)");
                }
                // Per-instance validation
                for (key, typ) in &config.variables {
                    if let Some(value) = child_cfg.variables.get(key) {
                        if let Err(e) = typ.validate(value, &sandbox) {
                            err!("Variable {:?} is invalid: {}", key, e);
                        }
                    } else {
                        err!("Variable {:?} is undefined", key);
                    }
                }
                for i in 0..child_cfg.instances {
                    let name = format!("{}/{}.{}",
                        current_name, child_name, i);
                    let icfg = match config.instantiate(&Variables {
                            user_vars: &child_cfg.variables,
                            lithos_name: &name,
                            lithos_config_filename: &child_cfg.config,
                        }) {
                        Ok(x) => x,
                        Err(e) => {
                            err!("Variable substitution error {:?} \
                                of sandbox {:?} of image {:?}: {}",
                                &child_cfg.config, current_name,
                                child_cfg.image,
                                e.join("; "));
                            continue;
                        }
                    };
                    // TODO(tailhook) check tcp ports
                    for (port, _) in icfg.tcp_ports {
                        if !in_range(&sandbox.allow_tcp_ports, port as u32) {
                            err!("Port {} is not allowed for {:?} \
                                of sandbox {:?} of image {:?}",
                                port, &child_cfg.config, current_name,
                                child_cfg.image);
                        }
                    }
                }
            }
        }
    }).map_err(|e| {
        err!("Can't read config directory {:?}: {}", config_dir, e);
    }).ok();
    if alter_config.is_some() {
        err!("Tree {:?} is not used", altered_sandbox);
    }
}

fn check_binaries() {
    let dir = match env::current_exe().ok()
        .and_then(|x| x.parent().map(|x| x.to_path_buf()))
    {
        Some(dir) => dir,
        None => {
            err!("Can't find out exe path");
            return;
        }
    };
    if metadata(&dir.join("lithos_tree")).is_err() {
        err!("Can't find lithos_tree binary");
    }
    if metadata(&dir.join("lithos_knot")).is_err() {
        err!("Can't find lithos_knot binary");
    }
}

fn main() {

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

    let mut config_file = PathBuf::from("/etc/lithos/master.yaml");
    let mut verbose = false;
    let mut alter_config = None;
    let mut sandbox_name = None;
    let mut check_containers = Vec::<String>::new();
    {
        let mut ap = ArgumentParser::new();
        ap.set_description("Checks if lithos configuration is ok");
        ap.refer(&mut config_file)
          .add_option(&["-C", "--config"], Parse,
            "Name of the global configuration file \
             (default /etc/lithos/master.yaml)")
          .metavar("FILE");
        ap.refer(&mut verbose)
          .add_option(&["-v", "--verbose"], StoreTrue,
            "Verbose output");
        ap.refer(&mut alter_config)
          .add_option(&["--alternate-config"], ParseOption,
            "Name of the alterate file name with configs.
             Useful to test configuration file before
             switching it to be primary one.
             You must also specify --sandbox.")
          .metavar("FILE");
        ap.refer(&mut sandbox_name)
          .add_option(&["--sandbox", "--sandbox-name",
            // Compatibility names
            "-T", "--tree", "--subtree-name",
            ], ParseOption,
            "Name of the sandbox for which --config-dir takes effect")
          .metavar("NAME");
        ap.refer(&mut check_containers)
          .add_option(&["--check-container"], Collect, "
            Instead of checking full lithos configuration check the
            container's configuration in the FILE. This is useful to check
            container locally, where you don't have lithos configured,
            before actually uploading the container. Multiple files may be
            specified in multiple arguments.
            ")
          .metavar("FILE");
        ap.add_option(&["--version"],
            Print(env!("CARGO_PKG_VERSION").to_string()),
            "Show version");
        match ap.parse_args() {
            Ok(()) => {}
            Err(x) => {
                exit(x);
            }
        }
    }
    if alter_config.is_some() && sandbox_name.is_none() {
        err!("Please specify --sandbox if you use --dir");
    }
    if check_containers.len() > 0 {
        for file in &check_containers {
            check_container(Path::new(file)).ok();
        }
    } else {
        check_binaries();
        check(&config_file, verbose, sandbox_name, alter_config);
    }
    let exit_status = EXIT_STATUS.load(Ordering::SeqCst) as i32;
    if exit_status != 0 {
        warn!("Lithos version v{}", cfg!(CARGO_PKG_VERSION));
    }
    exit(exit_status);
}
<commit_msg>Correct output of version in lithos_check<commit_after>extern crate argparse;
extern crate env_logger;
extern crate libc;
extern crate lithos;
extern crate quire;
extern crate scan_dir;
#[macro_use] extern crate log;

use std::env;
use std::fs::{metadata};
use std::path::{Path, PathBuf};
use std::process::exit;
use std::collections::BTreeMap;
use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT};

use argparse::{ArgumentParser, Parse, ParseOption, StoreTrue, Print, Collect};
use quire::{parse_config, Options};

use lithos::utils::{in_mapping, check_mapping, relative};
use lithos::range::in_range;
use lithos::master_config::MasterConfig;
use lithos::sandbox_config::SandboxConfig;
use lithos::container_config::{ContainerConfig, Variables};
use lithos::child_config::ChildConfig;
use lithos::network::{get_host_name, get_host_ip};
use lithos::id_map::{IdMapExt};

static EXIT_STATUS: AtomicUsize = ATOMIC_USIZE_INIT;

macro_rules! err {
    ( $( $x:expr ),* ) => {
        {
            error!($($x),*);
            EXIT_STATUS.store(1,  Ordering::SeqCst);
        }
    }
}

fn check_master_config(master: &MasterConfig, verbose: bool) {
    // TODO(tailhook) maybe check host only if we need it for hosts file
    match get_host_name() {
        Ok(hostname) => {
            if verbose {
                println!("Hostname is {}", hostname);
            }
        }
        Err(e) => {
            err!("Can't get hostname: {}", e);
        }
    }
    match get_host_ip() {
        Ok(ipaddr) => {
            if verbose {
                println!("IPAddr is {}", ipaddr);
            }
        }
        Err(e) => {
            err!("Can't get IPAddress: {}", e);
        }
    }

    if metadata(&master.devfs_dir).is_err() {
        err!("Devfs dir ({:?}) must exist and contain device nodes",
            master.devfs_dir);
    }
}

fn check_sandbox_config(sandbox: &SandboxConfig) {
    if sandbox.allow_users.len() == 0 {
        err!("No allowed users range. Please add `allow-users: [1-1000]`");
    }
    if sandbox.allow_groups.len() == 0 {
        err!("No allowed groups range. Please add `allow-groups: [1-1000]`");
    }
    // TODO(tailhook) check allow_users/allow_groups against uid_map/gid_map
}

fn check_container(config_file: &Path) -> Result<ContainerConfig, ()>
{
    // Only checks things that can be checked without other configs
    let config: ContainerConfig = match parse_config(config_file,
        &ContainerConfig::validator(), &Options::default())
    {
        Ok(cfg) => cfg,
        Err(e) => {
            err!("Can't read container config {:?}: {}", config_file, e);
            return Err(());
        }
    };
    if config.uid_map.len() > 0 {
        if !in_mapping(&config.uid_map, config.user_id) {
            err!("User is not in mapped range (uid: {})",
                config.user_id);
        }
    }
    if config.gid_map.len() > 0 {
        if !in_mapping(&config.gid_map, config.group_id) {
            err!("Group is not in mapped range (gid: {})",
                config.user_id);
        }
    }
    Ok(config)
}

fn check(config_file: &Path, verbose: bool,
    altered_sandbox: Option<String>, alter_config: Option<PathBuf>)
{
    let mut alter_config = alter_config;
    let master: MasterConfig = match parse_config(&config_file,
        &MasterConfig::validator(), &Options::default()) {
        Ok(cfg) => cfg,
        Err(e) => {
            err!("Can't parse config: {}", e);
            return;
        }
    };

    check_master_config(&master, verbose);

    let config_dir = config_file.parent().unwrap().join(&master.sandboxes_dir);
    scan_dir::ScanDir::files().read(&config_dir, |iter| {
        let yamls = iter.filter(|&(_, ref name)| name.ends_with(".yaml"));
        for (entry, current_fn) in yamls {
            // strip yaml suffix
            let current_name = &current_fn[..current_fn.len()-5];
            let sandbox: SandboxConfig = match parse_config(&entry.path(),
                &SandboxConfig::validator(), &Options::default()) {
                Ok(cfg) => cfg,
                Err(e) => {
                    err!("Can't parse config: {}", e);
                    continue;
                }
            };
            check_sandbox_config(&sandbox);

            let default_config = config_file.parent().unwrap()
                .join(&master.processes_dir)
                .join(sandbox.config_file.as_ref().unwrap_or(
                    &PathBuf::from(&current_fn)));
            let config_file = match (current_name, &altered_sandbox)
            {
                (name, &Some(ref t)) if name == t
                => alter_config.take().unwrap_or(default_config),
                _ => default_config,
            };

            debug!("Checking {:?}", config_file);
            let all_children: BTreeMap<String, ChildConfig>;
            all_children = match parse_config(&config_file,
                &ChildConfig::mapping_validator(), &Options::default()) {
                Ok(cfg) => cfg,
                Err(e) => {
                    warn!("Can't read child config {:?}: {}", config_file, e);
                    continue;
                }
            };
            for (ref child_name, ref child_cfg) in all_children.iter() {
                let cfg_path = Path::new(&child_cfg.config);
                if !cfg_path.is_absolute() {
                    err!("Config path must be absolute");
                    continue;
                }
                if !sandbox.check_path(&child_cfg.image) {
                    err!("Image {} in sandbox {}, proccess {} is invalid",
                        child_cfg.image, current_name, child_name);
                    continue;
                }
                debug!("Opening config for {:?}", child_name);
                let config = match check_container(&sandbox.image_dir
                    .join(&child_cfg.image)
                    .join(&relative(cfg_path, &Path::new("/"))))
                {
                    Ok(config) => config,
                    Err(()) => continue,
                };
                // Uidmaps aren't substituted
                if config.uid_map.len() > 0 {
                    if sandbox.uid_map.len() > 0 {
                        err!("Can't have uid_maps in both the sandbox and a \
                              container itself");
                    }
                } else {
                    if sandbox.uid_map.len() > 0 {
                        if sandbox.uid_map.map_id(config.user_id).is_none() {
                            err!("User is not in mapped range \
                                (uid: {})",
                                config.user_id);
                        }
                    }
                    if !in_range(&sandbox.allow_users, config.user_id) {
                        err!("User is not in allowed range (uid: {})",
                            config.user_id);
                    }
                }
                if config.gid_map.len() > 0 {
                    if sandbox.gid_map.len() > 0 {
                        err!("Can't have uid_maps in both the sandbox and a \
                              container itself");
                    }
                } else {
                    if sandbox.gid_map.len() > 0 {
                        if sandbox.gid_map.map_id(config.group_id).is_none() {
                            err!("Group is not in mapped range \
                                (gid: {})",
                                config.group_id);
                        }
                    }
                    if !in_range(&sandbox.allow_groups, config.group_id) {
                        err!("Group is not in allowed range (gid: {})",
                            config.group_id);
                    }
                }
                if !check_mapping(&sandbox.allow_users, &config.uid_map) {
                    err!("Bad uid mapping (probably doesn't match allow_users)");
                }
                if !check_mapping(&sandbox.allow_groups, &config.gid_map) {
                    err!("Bad gid mapping (probably doesn't match allow_groups)");
                }
                // Per-instance validation
                for (key, typ) in &config.variables {
                    if let Some(value) = child_cfg.variables.get(key) {
                        if let Err(e) = typ.validate(value, &sandbox) {
                            err!("Variable {:?} is invalid: {}", key, e);
                        }
                    } else {
                        err!("Variable {:?} is undefined", key);
                    }
                }
                for i in 0..child_cfg.instances {
                    let name = format!("{}/{}.{}",
                        current_name, child_name, i);
                    let icfg = match config.instantiate(&Variables {
                            user_vars: &child_cfg.variables,
                            lithos_name: &name,
                            lithos_config_filename: &child_cfg.config,
                        }) {
                        Ok(x) => x,
                        Err(e) => {
                            err!("Variable substitution error {:?} \
                                of sandbox {:?} of image {:?}: {}",
                                &child_cfg.config, current_name,
                                child_cfg.image,
                                e.join("; "));
                            continue;
                        }
                    };
                    // TODO(tailhook) check tcp ports
                    for (port, _) in icfg.tcp_ports {
                        if !in_range(&sandbox.allow_tcp_ports, port as u32) {
                            err!("Port {} is not allowed for {:?} \
                                of sandbox {:?} of image {:?}",
                                port, &child_cfg.config, current_name,
                                child_cfg.image);
                        }
                    }
                }
            }
        }
    }).map_err(|e| {
        err!("Can't read config directory {:?}: {}", config_dir, e);
    }).ok();
    if alter_config.is_some() {
        err!("Tree {:?} is not used", altered_sandbox);
    }
}

fn check_binaries() {
    let dir = match env::current_exe().ok()
        .and_then(|x| x.parent().map(|x| x.to_path_buf()))
    {
        Some(dir) => dir,
        None => {
            err!("Can't find out exe path");
            return;
        }
    };
    if metadata(&dir.join("lithos_tree")).is_err() {
        err!("Can't find lithos_tree binary");
    }
    if metadata(&dir.join("lithos_knot")).is_err() {
        err!("Can't find lithos_knot binary");
    }
}

fn main() {

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

    let mut config_file = PathBuf::from("/etc/lithos/master.yaml");
    let mut verbose = false;
    let mut alter_config = None;
    let mut sandbox_name = None;
    let mut check_containers = Vec::<String>::new();
    {
        let mut ap = ArgumentParser::new();
        ap.set_description("Checks if lithos configuration is ok");
        ap.refer(&mut config_file)
          .add_option(&["-C", "--config"], Parse,
            "Name of the global configuration file \
             (default /etc/lithos/master.yaml)")
          .metavar("FILE");
        ap.refer(&mut verbose)
          .add_option(&["-v", "--verbose"], StoreTrue,
            "Verbose output");
        ap.refer(&mut alter_config)
          .add_option(&["--alternate-config"], ParseOption,
            "Name of the alterate file name with configs.
             Useful to test configuration file before
             switching it to be primary one.
             You must also specify --sandbox.")
          .metavar("FILE");
        ap.refer(&mut sandbox_name)
          .add_option(&["--sandbox", "--sandbox-name",
            // Compatibility names
            "-T", "--tree", "--subtree-name",
            ], ParseOption,
            "Name of the sandbox for which --config-dir takes effect")
          .metavar("NAME");
        ap.refer(&mut check_containers)
          .add_option(&["--check-container"], Collect, "
            Instead of checking full lithos configuration check the
            container's configuration in the FILE. This is useful to check
            container locally, where you don't have lithos configured,
            before actually uploading the container. Multiple files may be
            specified in multiple arguments.
            ")
          .metavar("FILE");
        ap.add_option(&["--version"],
            Print(env!("CARGO_PKG_VERSION").to_string()),
            "Show version");
        match ap.parse_args() {
            Ok(()) => {}
            Err(x) => {
                exit(x);
            }
        }
    }
    if alter_config.is_some() && sandbox_name.is_none() {
        err!("Please specify --sandbox if you use --dir");
    }
    if check_containers.len() > 0 {
        for file in &check_containers {
            check_container(Path::new(file)).ok();
        }
    } else {
        check_binaries();
        check(&config_file, verbose, sandbox_name, alter_config);
    }
    let exit_status = EXIT_STATUS.load(Ordering::SeqCst) as i32;
    if exit_status != 0 {
        warn!("Lithos version v{}", env!("CARGO_PKG_VERSION"));
    }
    exit(exit_status);
}
<|endoftext|>


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

pub mod pointer;
use pointer::OpPointer;

#[derive(Debug, PartialEq, Clone)]
pub enum Primitive {
    // Primitive Types
    Int(i64),
    Float(f64),
    Str(String),
    Bool(bool),
    Empty,
}

use Primitive::{Bool, Empty, Float, Int, Str};

#[derive(Debug, PartialEq, Clone)]
pub enum Composite {
    List(Vec<Value>),
    Tuple(Vec<(String, Value)>),
}

use Composite::{List, Tuple};

#[derive(Debug, PartialEq, Clone)]
pub enum Value {
    // Binding names.
    S(String),
    // Primitive Types
    P(Primitive),
    // Composite Types.
    C(Composite),
    // Program Pointer
    T(usize),
}
use Value::{C, P, S, T};

#[derive(Debug, PartialEq, Clone)]
pub enum Op {
    // Stack and Name manipulation.
    Bind, // Bind a Val to a name in the heap
    Pop,  // Pop a Value off the value stack and discard it.
    // Math ops
    Add,
    Sub,
    Div,
    Mul,
    // Comparison Ops
    Equal,
    Gt,
    Lt,
    GtEq,
    LtEq,
    // Primitive Types ops
    Val(Primitive),
    // A bareword for use in bindings or lookups
    Sym(String),
    // Complex Type ops
    InitTuple,
    Field,
    InitList,
    Element,
    // Operations
    Cp,
    // Control Flow
    Bang,
    Jump(usize),
    JumpIfTrue(usize),
    Noop,
    // Pending Computation
    InitThunk(usize),
    Return,
    // - Call
    // Runtime hooks
    // - Map
    // - Filter
    // - Reduce
    // - Import
    // - Out
    // - Assert
    // - Print
}

#[derive(Debug)]
pub struct Error {}

pub struct VM {
    stack: Vec<Value>,
    // TODO(jwall): We may want to preserve order on these.
    symbols: BTreeMap<String, Value>,
    ops: OpPointer,
}

impl VM {
    pub fn new(ops: Vec<Op>) -> Self {
        Self {
            stack: Vec::new(),
            symbols: BTreeMap::new(),
            ops: OpPointer::new(ops),
        }
    }

    pub fn run(&mut self) -> Result<(), Error> {
        while self.ops.next().is_some() {
            let idx = self.ops.ptr.unwrap();
            match self.ops.op().unwrap() {
                Op::Val(p) => self.primitive_push(p.clone())?,
                Op::Sym(s) => self.push(Value::S(s.clone()))?,
                Op::Add => self.op_add()?,
                Op::Sub => self.op_sub()?,
                Op::Mul => self.op_mul()?,
                Op::Div => self.op_div()?,
                Op::Bind => self.op_bind()?,
                Op::Equal => self.op_equal()?,
                Op::Gt => self.op_gt()?,
                Op::Lt => self.op_lt()?,
                Op::GtEq => self.op_gteq()?,
                Op::LtEq => self.op_lteq()?,
                // Add a Composite list value to the stack
                Op::InitList => self.composite_push(List(Vec::new()))?,
                // Add a composite tuple value to the stack
                Op::InitTuple => self.composite_push(Tuple(Vec::new()))?,
                Op::Field => self.op_field()?,
                Op::Element => self.op_element()?,
                Op::Cp => self.op_copy()?,
                Op::Bang => return Err(Error {}),
                Op::InitThunk(jp) => self.op_thunk(idx, *jp)?,
                Op::Noop => {
                    self.stack.last();
                    // Do nothing
                }
                Op::Return => {
                    // TODO(jwall): This means we return back to the start of the frame.
                }
                Op::Jump(jp) => self.op_jump(*jp)?,
                Op::JumpIfTrue(jp) => self.op_jump_if_true(*jp)?,
                Op::Pop => {
                    self.pop()?;
                }
            };
        }
        Ok(())
    }
    fn op_jump(&mut self, jp: usize) -> Result<(), Error> {
        self.ops.jump(self.ops.ptr.map(|v| v + jp).unwrap_or(jp))?;
        Ok(())
    }

    fn op_jump_if_true(&mut self, jp: usize) -> Result<(), Error> {
        if let P(Bool(cond)) = self.pop()? {
            if cond {
                self.op_jump(jp)?;
            }
        }
        Ok(())
    }

    fn op_thunk(&mut self, idx: usize, jp: usize) -> Result<(), Error> {
        self.push(Value::T(idx))?;
        self.op_jump(jp)
    }

    fn op_equal(&mut self) -> Result<(), Error> {
        let left = self.pop()?;
        let right = self.pop()?;
        self.push(Value::P(Bool(left == right)))?;
        Ok(())
    }

    fn op_gt(&mut self) -> Result<(), Error> {
        let left = self.pop()?;
        let right = self.pop()?;
        match (left, right) {
            (P(Int(i)), P(Int(ii))) => {
                self.push(Value::P(Bool(i > ii)))?;
            }
            (P(Float(f)), P(Float(ff))) => {
                self.push(Value::P(Bool(f > ff)))?;
            }
            _ => return Err(Error {}),
        }
        Ok(())
    }

    fn op_lt(&mut self) -> Result<(), Error> {
        let left = self.pop()?;
        let right = self.pop()?;
        match (left, right) {
            (P(Int(i)), P(Int(ii))) => {
                self.push(Value::P(Bool(i < ii)))?;
            }
            (P(Float(f)), P(Float(ff))) => {
                self.push(Value::P(Bool(f < ff)))?;
            }
            _ => return Err(Error {}),
        }
        Ok(())
    }

    fn op_lteq(&mut self) -> Result<(), Error> {
        let left = self.pop()?;
        let right = self.pop()?;
        match (left, right) {
            (P(Int(i)), P(Int(ii))) => {
                self.push(Value::P(Bool(i <= ii)))?;
            }
            (P(Float(f)), P(Float(ff))) => {
                self.push(Value::P(Bool(f <= ff)))?;
            }
            _ => return Err(Error {}),
        }
        Ok(())
    }

    fn op_gteq(&mut self) -> Result<(), Error> {
        let left = self.pop()?;
        let right = self.pop()?;
        match (left, right) {
            (P(Int(i)), P(Int(ii))) => {
                self.push(Value::P(Bool(i >= ii)))?;
            }
            (P(Float(f)), P(Float(ff))) => {
                self.push(Value::P(Bool(f >= ff)))?;
            }
            _ => return Err(Error {}),
        }
        Ok(())
    }

    fn op_add(&mut self) -> Result<(), Error> {
        // Adds the previous two items in the stack.
        let left = self.pop()?;
        let right = self.pop()?;
        // Then pushes the result onto the stack.
        self.primitive_push(self.add(left, right)?)?;
        Ok(())
    }

    fn op_sub(&mut self) -> Result<(), Error> {
        // Subtracts the previous two items in the stack.
        let left = self.pop()?;
        let right = self.pop()?;
        // Then pushes the result onto the stack.
        self.primitive_push(self.sub(left, right)?)?;
        Ok(())
    }

    fn op_mul(&mut self) -> Result<(), Error> {
        // Multiplies the previous two items in the stack.
        let left = self.pop()?;
        let right = self.pop()?;
        // Then pushes the result onto the stack.
        self.primitive_push(self.mul(left, right)?)?;
        Ok(())
    }

    fn op_div(&mut self) -> Result<(), Error> {
        // Divides the previous two items in the stack.
        let left = self.pop()?;
        let right = self.pop()?;
        // Then pushes the result onto the stack.
        self.primitive_push(self.div(left, right)?)?;
        Ok(())
    }

    fn op_bind(&mut self) -> Result<(), Error> {
        // TODO(jwall): Okay this should actually
        // point to a location in the library op code
        // instead of storing in the heap.
        // pop val off stack.
        let val = self.pop()?;
        // pop name off stack.
        let name = self.pop()?;
        if let Value::S(name) = name {
            self.binding_push(name, val)?;
        } else {
            return Err(Error {});
        }
        Ok(())
    }

    fn op_field(&mut self) -> Result<(), Error> {
        // Add a Composite field value to a tuple on the stack
        // get value from stack
        let val = self.pop()?;
        // get name from stack.
        let name = if let Value::S(s) | Value::P(Primitive::Str(s)) = self.pop()? {
            s
        } else {
            return Err(Error {});
        };
        // get composite tuple from stack
        let tpl = self.pop()?;
        if let Value::C(Tuple(mut flds)) = tpl {
            // add name and value to tuple
            self.merge_field_into_tuple(&mut flds, name, val)?;
            // place composite tuple back on stack
            self.composite_push(Tuple(flds))?;
        } else {
            return Err(Error {});
        };
        Ok(())
    }

    fn op_element(&mut self) -> Result<(), Error> {
        // get element from stack.
        let val = self.pop()?;
        // get next value. It should be a Composite list.
        let tpl = self.pop()?;
        if let Value::C(List(mut elems)) = tpl {
            // add value to list
            elems.push(val);
            // Add that value to the list and put list back on stack.
            self.composite_push(List(elems))?;
        } else {
            return Err(Error {});
        };
        Ok(())
    }

    fn op_copy(&mut self) -> Result<(), Error> {
        // TODO Use Cow pointers for this?
        // get next value. It should be a Composite Tuple.
        if let Value::C(Tuple(flds)) = self.pop()? {
            // Make a copy of the original
            let original = Tuple(flds.clone());
            let copy = Tuple(flds);
            // Put the original on the Stack as well as the copy
            self.composite_push(original)?;
            self.composite_push(copy)?;
        } else {
            return Err(Error {});
        };
        Ok(())
    }

    fn merge_field_into_tuple(
        &self,
        src_fields: &mut Vec<(String, Value)>,
        name: String,
        value: Value,
    ) -> Result<(), Error> {
        for fld in src_fields.iter_mut() {
            if fld.0 == name {
                fld.1 = value;
                return Ok(());
            }
        }
        src_fields.push((name, value));
        Ok(())
    }

    fn push(&mut self, p: Value) -> Result<(), Error> {
        self.stack.push(p);
        Ok(())
    }

    fn primitive_push(&mut self, p: Primitive) -> Result<(), Error> {
        self.stack.push(Value::P(p));
        Ok(())
    }

    fn composite_push(&mut self, c: Composite) -> Result<(), Error> {
        self.stack.push(Value::C(c));
        Ok(())
    }

    fn binding_push(&mut self, name: String, val: Value) -> Result<(), Error> {
        // FIXME(jwall): Error if the symbol already exists.
        self.symbols.insert(name, val);
        Ok(())
    }

    pub fn get_binding(&mut self, name: &str) -> Result<&Value, Error> {
        match self.symbols.get(name) {
            Some(v) => Ok(v),
            None => Err(Error {}),
        }
    }

    fn pop(&mut self) -> Result<Value, Error> {
        match self.stack.pop() {
            Some(v) => Ok(v),
            None => Err(Error {}),
        }
    }

    fn mul(&self, left: Value, right: Value) -> Result<Primitive, Error> {
        Ok(match (left, right) {
            (Value::P(Primitive::Int(i)), Value::P(Primitive::Int(ii))) => Primitive::Int(i * ii),
            (Value::P(Primitive::Float(f)), Value::P(Primitive::Float(ff))) => {
                Primitive::Float(f * ff)
            }
            _ => return Err(Error {}),
        })
    }

    fn div(&self, left: Value, right: Value) -> Result<Primitive, Error> {
        Ok(match (left, right) {
            (Value::P(Primitive::Int(i)), Value::P(Primitive::Int(ii))) => Primitive::Int(i / ii),
            (Value::P(Primitive::Float(f)), Value::P(Primitive::Float(ff))) => {
                Primitive::Float(f / ff)
            }
            _ => return Err(Error {}),
        })
    }

    fn sub(&self, left: Value, right: Value) -> Result<Primitive, Error> {
        Ok(match (left, right) {
            (Value::P(Primitive::Int(i)), Value::P(Primitive::Int(ii))) => Primitive::Int(i - ii),
            (Value::P(Primitive::Float(f)), Value::P(Primitive::Float(ff))) => {
                Primitive::Float(f - ff)
            }
            _ => return Err(Error {}),
        })
    }

    fn add(&self, left: Value, right: Value) -> Result<Primitive, Error> {
        Ok(match (left, right) {
            (Value::P(Primitive::Int(i)), Value::P(Primitive::Int(ii))) => Primitive::Int(i + ii),
            (Value::P(Primitive::Float(f)), Value::P(Primitive::Float(ff))) => {
                Primitive::Float(f + ff)
            }
            (Value::P(Primitive::Str(s)), Value::P(Primitive::Str(ss))) => {
                let mut ns = String::new();
                ns.push_str(&s);
                ns.push_str(&ss);
                Primitive::Str(ns)
            }
            _ => return Err(Error {}),
        })
    }
}

#[cfg(test)]
mod test;
<commit_msg>MAINT: Cleanup warnings and unnecessary qualifiers.<commit_after>// Copyright 2019 Jeremy Wall
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::collections::BTreeMap;

pub mod pointer;
use pointer::OpPointer;

#[derive(Debug, PartialEq, Clone)]
pub enum Primitive {
    // Primitive Types
    Int(i64),
    Float(f64),
    Str(String),
    Bool(bool),
    Empty,
}

use Primitive::{Bool, Float, Int, Str};

#[derive(Debug, PartialEq, Clone)]
pub enum Composite {
    List(Vec<Value>),
    Tuple(Vec<(String, Value)>),
}

use Composite::{List, Tuple};

#[derive(Debug, PartialEq, Clone)]
pub enum Value {
    // Binding names.
    S(String),
    // Primitive Types
    P(Primitive),
    // Composite Types.
    C(Composite),
    // Program Pointer
    T(usize),
}
use Value::{C, P, S, T};

#[derive(Debug, PartialEq, Clone)]
pub enum Op {
    // Stack and Name manipulation.
    Bind, // Bind a Val to a name in the heap
    Pop,  // Pop a Value off the value stack and discard it.
    // Math ops
    Add,
    Sub,
    Div,
    Mul,
    // Comparison Ops
    Equal,
    Gt,
    Lt,
    GtEq,
    LtEq,
    // Primitive Types ops
    Val(Primitive),
    // A bareword for use in bindings or lookups
    Sym(String),
    // Complex Type ops
    InitTuple,
    Field,
    InitList,
    Element,
    // Operations
    Cp,
    // Control Flow
    Bang,
    Jump(usize),
    JumpIfTrue(usize),
    Noop,
    // Pending Computation
    InitThunk(usize),
    Return,
    // - Call
    // Runtime hooks
    // - Map
    // - Filter
    // - Reduce
    // - Import
    // - Out
    // - Assert
    // - Print
}

#[derive(Debug)]
pub struct Error {}

pub struct VM {
    stack: Vec<Value>,
    // TODO(jwall): We may want to preserve order on these.
    symbols: BTreeMap<String, Value>,
    ops: OpPointer,
}

impl VM {
    pub fn new(ops: Vec<Op>) -> Self {
        Self {
            stack: Vec::new(),
            symbols: BTreeMap::new(),
            ops: OpPointer::new(ops),
        }
    }

    pub fn run(&mut self) -> Result<(), Error> {
        while self.ops.next().is_some() {
            let idx = self.ops.ptr.unwrap();
            match self.ops.op().unwrap() {
                Op::Val(p) => self.push(P(p.clone()))?,
                Op::Sym(s) => self.push(S(s.clone()))?,
                Op::Add => self.op_add()?,
                Op::Sub => self.op_sub()?,
                Op::Mul => self.op_mul()?,
                Op::Div => self.op_div()?,
                Op::Bind => self.op_bind()?,
                Op::Equal => self.op_equal()?,
                Op::Gt => self.op_gt()?,
                Op::Lt => self.op_lt()?,
                Op::GtEq => self.op_gteq()?,
                Op::LtEq => self.op_lteq()?,
                // Add a Composite list value to the stack
                Op::InitList => self.push(C(List(Vec::new())))?,
                // Add a composite tuple value to the stack
                Op::InitTuple => self.push(C(Tuple(Vec::new())))?,
                Op::Field => self.op_field()?,
                Op::Element => self.op_element()?,
                Op::Cp => self.op_copy()?,
                Op::Bang => return Err(Error {}),
                Op::InitThunk(jp) => self.op_thunk(idx, *jp)?,
                Op::Noop => {
                    // Do nothing
                }
                Op::Return => {
                    // TODO(jwall): This means we return back to the start of the frame.
                }
                Op::Jump(jp) => self.op_jump(*jp)?,
                Op::JumpIfTrue(jp) => self.op_jump_if_true(*jp)?,
                Op::Pop => {
                    self.pop()?;
                }
            };
        }
        Ok(())
    }

    fn op_jump(&mut self, jp: usize) -> Result<(), Error> {
        self.ops.jump(self.ops.ptr.map(|v| v + jp).unwrap_or(jp))?;
        Ok(())
    }

    fn op_jump_if_true(&mut self, jp: usize) -> Result<(), Error> {
        if let P(Bool(cond)) = self.pop()? {
            if cond {
                self.op_jump(jp)?;
            }
        }
        Ok(())
    }

    fn op_thunk(&mut self, idx: usize, jp: usize) -> Result<(), Error> {
        self.push(T(idx))?;
        self.op_jump(jp)
    }

    fn op_equal(&mut self) -> Result<(), Error> {
        let left = self.pop()?;
        let right = self.pop()?;
        self.push(P(Bool(left == right)))?;
        Ok(())
    }

    fn op_gt(&mut self) -> Result<(), Error> {
        let left = self.pop()?;
        let right = self.pop()?;
        match (left, right) {
            (P(Int(i)), P(Int(ii))) => {
                self.push(P(Bool(i > ii)))?;
            }
            (P(Float(f)), P(Float(ff))) => {
                self.push(P(Bool(f > ff)))?;
            }
            _ => return Err(Error {}),
        }
        Ok(())
    }

    fn op_lt(&mut self) -> Result<(), Error> {
        let left = self.pop()?;
        let right = self.pop()?;
        match (left, right) {
            (P(Int(i)), P(Int(ii))) => {
                self.push(P(Bool(i < ii)))?;
            }
            (P(Float(f)), P(Float(ff))) => {
                self.push(P(Bool(f < ff)))?;
            }
            _ => return Err(Error {}),
        }
        Ok(())
    }

    fn op_lteq(&mut self) -> Result<(), Error> {
        let left = self.pop()?;
        let right = self.pop()?;
        match (left, right) {
            (P(Int(i)), P(Int(ii))) => {
                self.push(P(Bool(i <= ii)))?;
            }
            (P(Float(f)), P(Float(ff))) => {
                self.push(P(Bool(f <= ff)))?;
            }
            _ => return Err(Error {}),
        }
        Ok(())
    }

    fn op_gteq(&mut self) -> Result<(), Error> {
        let left = self.pop()?;
        let right = self.pop()?;
        match (left, right) {
            (P(Int(i)), P(Int(ii))) => {
                self.push(P(Bool(i >= ii)))?;
            }
            (P(Float(f)), P(Float(ff))) => {
                self.push(P(Bool(f >= ff)))?;
            }
            _ => return Err(Error {}),
        }
        Ok(())
    }

    fn op_add(&mut self) -> Result<(), Error> {
        // Adds the previous two items in the stack.
        let left = self.pop()?;
        let right = self.pop()?;
        // Then pushes the result onto the stack.
        self.push(P(self.add(left, right)?))?;
        Ok(())
    }

    fn op_sub(&mut self) -> Result<(), Error> {
        // Subtracts the previous two items in the stack.
        let left = self.pop()?;
        let right = self.pop()?;
        // Then pushes the result onto the stack.
        self.push(P(self.sub(left, right)?))?;
        Ok(())
    }

    fn op_mul(&mut self) -> Result<(), Error> {
        // Multiplies the previous two items in the stack.
        let left = self.pop()?;
        let right = self.pop()?;
        // Then pushes the result onto the stack.
        self.push(P(self.mul(left, right)?))?;
        Ok(())
    }

    fn op_div(&mut self) -> Result<(), Error> {
        // Divides the previous two items in the stack.
        let left = self.pop()?;
        let right = self.pop()?;
        // Then pushes the result onto the stack.
        self.push(P(self.div(left, right)?))?;
        Ok(())
    }

    fn op_bind(&mut self) -> Result<(), Error> {
        // pop val off stack.
        let val = self.pop()?;
        // pop name off stack.
        let name = self.pop()?;
        if let S(name) = name {
            self.binding_push(name, val)?;
        } else {
            return Err(Error {});
        }
        Ok(())
    }

    fn op_field(&mut self) -> Result<(), Error> {
        // Add a Composite field value to a tuple on the stack
        // get value from stack
        let val = self.pop()?;
        // get name from stack.
        let name = if let S(s) | P(Str(s)) = self.pop()? {
            s
        } else {
            return Err(Error {});
        };
        // get composite tuple from stack
        let tpl = self.pop()?;
        if let C(Tuple(mut flds)) = tpl {
            // add name and value to tuple
            self.merge_field_into_tuple(&mut flds, name, val)?;
            // place composite tuple back on stack
            self.push(C(Tuple(flds)))?;
        } else {
            return Err(Error {});
        };
        Ok(())
    }

    fn op_element(&mut self) -> Result<(), Error> {
        // get element from stack.
        let val = self.pop()?;
        // get next value. It should be a Composite list.
        let tpl = self.pop()?;
        if let C(List(mut elems)) = tpl {
            // add value to list
            elems.push(val);
            // Add that value to the list and put list back on stack.
            self.push(C(List(elems)))?;
        } else {
            return Err(Error {});
        };
        Ok(())
    }

    fn op_copy(&mut self) -> Result<(), Error> {
        // TODO Use Cow pointers for this?
        // get next value. It should be a Composite Tuple.
        if let C(Tuple(flds)) = self.pop()? {
            // Make a copy of the original
            let original = Tuple(flds.clone());
            let copy = Tuple(flds);
            // Put the original on the Stack as well as the copy
            self.push(C(original))?;
            self.push(C(copy))?;
        } else {
            return Err(Error {});
        };
        Ok(())
    }

    fn merge_field_into_tuple(
        &self,
        src_fields: &mut Vec<(String, Value)>,
        name: String,
        value: Value,
    ) -> Result<(), Error> {
        for fld in src_fields.iter_mut() {
            if fld.0 == name {
                fld.1 = value;
                return Ok(());
            }
        }
        src_fields.push((name, value));
        Ok(())
    }

    fn push(&mut self, p: Value) -> Result<(), Error> {
        self.stack.push(p);
        Ok(())
    }

    fn binding_push(&mut self, name: String, val: Value) -> Result<(), Error> {
        // FIXME(jwall): Error if the symbol already exists.
        self.symbols.insert(name, val);
        Ok(())
    }

    pub fn get_binding(&mut self, name: &str) -> Result<&Value, Error> {
        match self.symbols.get(name) {
            Some(v) => Ok(v),
            None => Err(Error {}),
        }
    }

    fn pop(&mut self) -> Result<Value, Error> {
        match self.stack.pop() {
            Some(v) => Ok(v),
            None => Err(Error {}),
        }
    }

    fn mul(&self, left: Value, right: Value) -> Result<Primitive, Error> {
        Ok(match (left, right) {
            (P(Int(i)), P(Int(ii))) => Int(i * ii),
            (P(Float(f)), P(Float(ff))) => Float(f * ff),
            _ => return Err(Error {}),
        })
    }

    fn div(&self, left: Value, right: Value) -> Result<Primitive, Error> {
        Ok(match (left, right) {
            (P(Int(i)), P(Int(ii))) => Int(i / ii),
            (P(Float(f)), P(Float(ff))) => Float(f / ff),
            _ => return Err(Error {}),
        })
    }

    fn sub(&self, left: Value, right: Value) -> Result<Primitive, Error> {
        Ok(match (left, right) {
            (P(Int(i)), Value::P(Int(ii))) => Int(i - ii),
            (P(Float(f)), Value::P(Float(ff))) => Float(f - ff),
            _ => return Err(Error {}),
        })
    }

    fn add(&self, left: Value, right: Value) -> Result<Primitive, Error> {
        Ok(match (left, right) {
            (P(Int(i)), Value::P(Int(ii))) => Int(i + ii),
            (P(Float(f)), Value::P(Float(ff))) => Float(f + ff),
            (P(Str(s)), Value::P(Str(ss))) => {
                let mut ns = String::new();
                ns.push_str(&s);
                ns.push_str(&ss);
                Str(ns)
            }
            _ => return Err(Error {}),
        })
    }
}

#[cfg(test)]
mod test;
<|endoftext|>


<commit_before>// Implements http://rosettacode.org/wiki/24_game
// Uses RPN expression
// Runs with rustc 0.12.0-pre-nightly (2e92c67dc 2014-08-28 23:56:20 +0000)

use std::io;
use std::rand::{task_rng, Rng};

#[cfg(not(test))]
fn main() {
    // generating 4 numbers
    let mut rng = task_rng();
    let choices: Vec<uint> = range(1u, 5).map(|_| rng.gen_range(1u, 10)).collect();
    println!("Make 24 with the following numbers");

    // start the game loop
    loop {
        print!("Your numbers: {}, {}, {}, {}\n", choices[0], choices[1], choices[2], choices[3]);
        let expr = io::stdin().read_line().ok().expect("Failed to read line!");
        if check_input(expr, &choices) { break; }
        print!("Try again? (y/n): ");
        let choice = io::stdin().read_line().ok().expect("Failed to read line!");
        if choice.as_slice().trim() != "y" { break; }
    }
}

fn check_input(expr: String, choices: &Vec<uint>) -> bool {
    let mut stack: Vec<uint> = Vec::new();
    for token in expr.as_slice().words() {
        if is_operator(token) {
            let (a, b) = (stack.pop(), stack.pop());
            match (a, b) {
                (Some(x), Some(y)) => stack.push(evaluate(y, x, token)),
                (_, _) => {
                    println!("Not a valid RPN expression!");
                    return false ;
                }
            }
        } else {
            let v: Option<uint> = from_str(token);
            match v {
                Some(n) => {
                    if !is_valid_number(n, choices) {
                        println!("Cannot use {}", n);
                        return false;
                    }
                    stack.push(n)
                },
                None => {
                    println!("Invalid input: {}", token);
                    return false;
                }
            }
        }
    }

    let ans = stack.pop();
    if stack.len() > 0 {
        println!("Not a valid RPN expression!");
        return false;
    }
    match ans {
        Some(x) => {
            if x == 24 {
                println!("Good job!");
                return true;
            }
            println!("Wrong answer. Result: {}", x);
        }
        None => println!("Error encountered!"),
    }
    false
}

// since evaluate is wrapped in is_operator the last
// pattern has to be "/"
fn evaluate(a: uint, b: uint, op: &str) -> uint {
    match op {
        "+" => a + b,
        "-" => a - b,
        "*" => a * b,
         _  => a / b, // has to be /
    }
}

fn is_operator(op: &str) -> bool {
    match op {
        "*" | "-" | "+" | "/" => true,
        _                     => false
    }
}

fn is_valid_number(n: uint, choices: &Vec<uint>) -> bool {
    for i in choices.iter() {
        if n == *i { return true }
    }
    false
}

#[test]
fn test_check_input() {
    let v1: Vec<uint> = vec![4, 3, 6, 2];

    // correct result
    let result1 = check_input("4 3 * 6 2 * +".to_string(), &v1);
    assert!(result1);

    // incorrect result
    let result2 = check_input("4 3 + 2 6 + -".to_string(), &v1);
    assert!(!result2);

    // wrong numbers in input
    let result3 = check_input("4 5 + 6 2 * -".to_string(), &v1);
    assert!(!result3);

    // invalid chars in input
    let result4 = check_input("4 ) + _ 2 * -".to_string(), &v1);
    assert!(!result4);

    // invalid RPN expression
    let result5 = check_input("4 3 + 6 2 *".to_string(), &v1);
    assert!(!result5);
}
<commit_msg>Fixed build error with unused import<commit_after>// Implements http://rosettacode.org/wiki/24_game
// Uses RPN expression
// Runs with rustc 0.12.0-pre-nightly (2e92c67dc 2014-08-28 23:56:20 +0000)

#[cfg(not(test))]
fn main() {
    use std::rand::{task_rng, Rng};
    use std::io;

    // generating 4 numbers
    let mut rng = task_rng();
    let choices: Vec<uint> = range(1u, 5).map(|_| rng.gen_range(1u, 10)).collect();
    println!("Make 24 with the following numbers");

    // start the game loop
    loop {
        print!("Your numbers: {}, {}, {}, {}\n", choices[0], choices[1], choices[2], choices[3]);
        let expr = io::stdin().read_line().ok().expect("Failed to read line!");
        if check_input(expr, &choices) { break; }
        print!("Try again? (y/n): ");
        let choice = io::stdin().read_line().ok().expect("Failed to read line!");
        if choice.as_slice().trim() != "y" { break; }
    }
}

fn check_input(expr: String, choices: &Vec<uint>) -> bool {
    let mut stack: Vec<uint> = Vec::new();
    for token in expr.as_slice().words() {
        if is_operator(token) {
            let (a, b) = (stack.pop(), stack.pop());
            match (a, b) {
                (Some(x), Some(y)) => stack.push(evaluate(y, x, token)),
                (_, _) => {
                    println!("Not a valid RPN expression!");
                    return false ;
                }
            }
        } else {
            let v: Option<uint> = from_str(token);
            match v {
                Some(n) => {
                    if !is_valid_number(n, choices) {
                        println!("Cannot use {}", n);
                        return false;
                    }
                    stack.push(n)
                },
                None => {
                    println!("Invalid input: {}", token);
                    return false;
                }
            }
        }
    }

    let ans = stack.pop();
    if stack.len() > 0 {
        println!("Not a valid RPN expression!");
        return false;
    }
    match ans {
        Some(x) => {
            if x == 24 {
                println!("Good job!");
                return true;
            }
            println!("Wrong answer. Result: {}", x);
        }
        None => println!("Error encountered!"),
    }
    false
}

// since evaluate is wrapped in is_operator the last
// pattern has to be "/"
fn evaluate(a: uint, b: uint, op: &str) -> uint {
    match op {
        "+" => a + b,
        "-" => a - b,
        "*" => a * b,
         _  => a / b, // has to be /
    }
}

fn is_operator(op: &str) -> bool {
    match op {
        "*" | "-" | "+" | "/" => true,
        _                     => false
    }
}

fn is_valid_number(n: uint, choices: &Vec<uint>) -> bool {
    for i in choices.iter() {
        if n == *i { return true }
    }
    false
}

#[test]
fn test_check_input() {
    let v1: Vec<uint> = vec![4, 3, 6, 2];

    // correct result
    let result1 = check_input("4 3 * 6 2 * +".to_string(), &v1);
    assert!(result1);

    // incorrect result
    let result2 = check_input("4 3 + 2 6 + -".to_string(), &v1);
    assert!(!result2);

    // wrong numbers in input
    let result3 = check_input("4 5 + 6 2 * -".to_string(), &v1);
    assert!(!result3);

    // invalid chars in input
    let result4 = check_input("4 ) + _ 2 * -".to_string(), &v1);
    assert!(!result4);

    // invalid RPN expression
    let result5 = check_input("4 3 + 6 2 *".to_string(), &v1);
    assert!(!result5);
}
<|endoftext|>


<commit_before>// https://leetcode.com/problems/maximum-rows-covered-by-columns/
pub fn maximum_rows(mat: Vec<Vec<i32>>, cols: i32) -> i32 {
    todo!()
}

fn main() {
    println!(
        "{}",
        maximum_rows(
            vec![vec![0, 0, 0], vec![1, 0, 1], vec![0, 1, 1], vec![0, 0, 1]],
            2
        )
    ); // 3
    println!(
        "{}",
        maximum_rows(
            vec![
                vec![0, 0, 0],
                vec![1, 0, 1],
                vec![0, 1, 1],
                vec![0, 0, 1],
                vec![1, 1, 1]
            ],
            2
        )
    ); // 3
    println!(
        "{}",
        maximum_rows(
            vec![
                vec![0, 0, 0],
                vec![1, 0, 1],
                vec![0, 1, 1],
                vec![0, 0, 1],
                vec![1, 1, 1]
            ],
            3
        )
    ); // 5
    println!("{}", maximum_rows(vec![vec![1], vec![0]], 1)); // 2
}
<commit_msg>Solve problem 2397<commit_after>use std::collections::VecDeque;

// https://leetcode.com/problems/maximum-rows-covered-by-columns/
pub fn maximum_rows(matrix: Vec<Vec<i32>>, num_select: i32) -> i32 {
    fn f(
        mat: &Vec<Vec<i32>>,
        num_cols: usize,
        index: usize,
        prefix_sums: &Vec<i32>,
        cols: &mut VecDeque<usize>,
    ) -> i32 {
        if cols.len() == num_cols {
            let mut num_rows = 0;
            for row in 0..mat.len() {
                let mut sum = prefix_sums[row];
                for col in cols.iter() {
                    sum -= mat[row as usize][*col as usize];
                }
                if sum == 0 {
                    num_rows += 1;
                }
            }
            return num_rows;
        }
        let mut max = 0;
        let max_cols = if mat.len() > 0 { mat[0].len() } else { 0 };
        for i in index..max_cols {
            cols.push_back(i);
            max = max.max(f(mat, num_cols, i + 1, prefix_sums, cols));
            cols.pop_back();
        }
        max
    }

    let mut prefix_sums = vec![0; matrix.len()];
    for i in 0..matrix.len() {
        let mut sum = 0;
        for j in 0..matrix[i].len() {
            sum += matrix[i][j];
        }
        prefix_sums[i] = sum;
    }
    f(
        &matrix,
        num_select as usize,
        0,
        &prefix_sums,
        &mut VecDeque::new(),
    )
}

fn main() {
    println!(
        "{}",
        maximum_rows(
            vec![vec![0, 0, 0], vec![1, 0, 1], vec![0, 1, 1], vec![0, 0, 1]],
            2
        )
    ); // 3
    println!(
        "{}",
        maximum_rows(
            vec![
                vec![0, 0, 0],
                vec![1, 0, 1],
                vec![0, 1, 1],
                vec![0, 0, 1],
                vec![1, 1, 1]
            ],
            2
        )
    ); // 3
    println!(
        "{}",
        maximum_rows(
            vec![
                vec![0, 0, 0],
                vec![1, 0, 1],
                vec![0, 1, 1],
                vec![0, 0, 1],
                vec![1, 1, 1]
            ],
            3
        )
    ); // 5
    println!("{}", maximum_rows(vec![vec![1], vec![0]], 1)); // 2
}
<|endoftext|>


<commit_before>use std::fmt;
use std::io::prelude::*;

use atty;
use termcolor::Color::{Cyan, Green, Red, Yellow};
use termcolor::{self, Color, ColorSpec, StandardStream, WriteColor};

use crate::util::errors::CargoResult;

/// The requested verbosity of output.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Verbosity {
    Verbose,
    Normal,
    Quiet,
}

/// An abstraction around a `Write`able object that remembers preferences for output verbosity and
/// color.
pub struct Shell {
    /// the `Write`able object, either with or without color support (represented by different enum
    /// variants)
    err: ShellOut,
    /// How verbose messages should be
    verbosity: Verbosity,
    /// Flag that indicates the current line needs to be cleared before
    /// printing. Used when a progress bar is currently displayed.
    needs_clear: bool,
}

impl fmt::Debug for Shell {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self.err {
            ShellOut::Write(_) => f
                .debug_struct("Shell")
                .field("verbosity", &self.verbosity)
                .finish(),
            ShellOut::Stream { color_choice, .. } => f
                .debug_struct("Shell")
                .field("verbosity", &self.verbosity)
                .field("color_choice", &color_choice)
                .finish(),
        }
    }
}

/// A `Write`able object, either with or without color support
enum ShellOut {
    /// A plain write object without color support
    Write(Box<dyn Write>),
    /// Color-enabled stdio, with information on whether color should be used
    Stream {
        stream: StandardStream,
        tty: bool,
        color_choice: ColorChoice,
    },
}

/// Whether messages should use color output
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum ColorChoice {
    /// Force color output
    Always,
    /// Force disable color output
    Never,
    /// Intelligently guess whether to use color output
    CargoAuto,
}

impl Shell {
    /// Creates a new shell (color choice and verbosity), defaulting to 'auto' color and verbose
    /// output.
    pub fn new() -> Shell {
        Shell {
            err: ShellOut::Stream {
                stream: StandardStream::stderr(ColorChoice::CargoAuto.to_termcolor_color_choice()),
                color_choice: ColorChoice::CargoAuto,
                tty: atty::is(atty::Stream::Stderr),
            },
            verbosity: Verbosity::Verbose,
            needs_clear: false,
        }
    }

    /// Creates a shell from a plain writable object, with no color, and max verbosity.
    pub fn from_write(out: Box<dyn Write>) -> Shell {
        Shell {
            err: ShellOut::Write(out),
            verbosity: Verbosity::Verbose,
            needs_clear: false,
        }
    }

    /// Prints a message, where the status will have `color` color, and can be justified. The
    /// messages follows without color.
    fn print(
        &mut self,
        status: &dyn fmt::Display,
        message: Option<&dyn fmt::Display>,
        color: Color,
        justified: bool,
    ) -> CargoResult<()> {
        match self.verbosity {
            Verbosity::Quiet => Ok(()),
            _ => {
                if self.needs_clear {
                    self.err_erase_line();
                }
                self.err.print(status, message, color, justified)
            }
        }
    }

    /// Sets whether the next print should clear the current line.
    pub fn set_needs_clear(&mut self, needs_clear: bool) {
        self.needs_clear = needs_clear;
    }

    /// Returns `true` if the `needs_clear` flag is unset.
    pub fn is_cleared(&self) -> bool {
        !self.needs_clear
    }

    /// Returns the width of the terminal in spaces, if any.
    pub fn err_width(&self) -> Option<usize> {
        match self.err {
            ShellOut::Stream { tty: true, .. } => imp::stderr_width(),
            _ => None,
        }
    }

    /// Returns `true` if stderr is a tty.
    pub fn is_err_tty(&self) -> bool {
        match self.err {
            ShellOut::Stream { tty, .. } => tty,
            _ => false,
        }
    }

    /// Gets a reference to the underlying writer.
    pub fn err(&mut self) -> &mut dyn Write {
        if self.needs_clear {
            self.err_erase_line();
        }
        self.err.as_write()
    }

    /// Erase from cursor to end of line.
    pub fn err_erase_line(&mut self) {
        if let ShellOut::Stream { tty: true, .. } = self.err {
            imp::err_erase_line(self);
            self.needs_clear = false;
        }
    }

    /// Shortcut to right-align and color green a status message.
    pub fn status<T, U>(&mut self, status: T, message: U) -> CargoResult<()>
    where
        T: fmt::Display,
        U: fmt::Display,
    {
        self.print(&status, Some(&message), Green, true)
    }

    pub fn status_header<T>(&mut self, status: T) -> CargoResult<()>
    where
        T: fmt::Display,
    {
        self.print(&status, None, Cyan, true)
    }

    /// Shortcut to right-align a status message.
    pub fn status_with_color<T, U>(
        &mut self,
        status: T,
        message: U,
        color: Color,
    ) -> CargoResult<()>
    where
        T: fmt::Display,
        U: fmt::Display,
    {
        self.print(&status, Some(&message), color, true)
    }

    /// Runs the callback only if we are in verbose mode.
    pub fn verbose<F>(&mut self, mut callback: F) -> CargoResult<()>
    where
        F: FnMut(&mut Shell) -> CargoResult<()>,
    {
        match self.verbosity {
            Verbosity::Verbose => callback(self),
            _ => Ok(()),
        }
    }

    /// Runs the callback if we are not in verbose mode.
    pub fn concise<F>(&mut self, mut callback: F) -> CargoResult<()>
    where
        F: FnMut(&mut Shell) -> CargoResult<()>,
    {
        match self.verbosity {
            Verbosity::Verbose => Ok(()),
            _ => callback(self),
        }
    }

    /// Prints a red 'error' message.
    pub fn error<T: fmt::Display>(&mut self, message: T) -> CargoResult<()> {
        if self.needs_clear {
            self.err_erase_line();
        }
        self.err.print(&"error:", Some(&message), Red, false)
    }

    /// Prints an amber 'warning' message.
    pub fn warn<T: fmt::Display>(&mut self, message: T) -> CargoResult<()> {
        match self.verbosity {
            Verbosity::Quiet => Ok(()),
            _ => self.print(&"warning:", Some(&message), Yellow, false),
        }
    }

    /// Updates the verbosity of the shell.
    pub fn set_verbosity(&mut self, verbosity: Verbosity) {
        self.verbosity = verbosity;
    }

    /// Gets the verbosity of the shell.
    pub fn verbosity(&self) -> Verbosity {
        self.verbosity
    }

    /// Updates the color choice (always, never, or auto) from a string..
    pub fn set_color_choice(&mut self, color: Option<&str>) -> CargoResult<()> {
        if let ShellOut::Stream {
            ref mut stream,
            ref mut color_choice,
            ..
        } = self.err
        {
            let cfg = match color {
                Some("always") => ColorChoice::Always,
                Some("never") => ColorChoice::Never,

                Some("auto") | None => ColorChoice::CargoAuto,

                Some(arg) => failure::bail!(
                    "argument for --color must be auto, always, or \
                     never, but found `{}`",
                    arg
                ),
            };
            *color_choice = cfg;
            *stream = StandardStream::stderr(cfg.to_termcolor_color_choice());
        }
        Ok(())
    }

    /// Gets the current color choice.
    ///
    /// If we are not using a color stream, this will always return `Never`, even if the color
    /// choice has been set to something else.
    pub fn color_choice(&self) -> ColorChoice {
        match self.err {
            ShellOut::Stream { color_choice, .. } => color_choice,
            ShellOut::Write(_) => ColorChoice::Never,
        }
    }

    /// Whether the shell supports color.
    pub fn supports_color(&self) -> bool {
        match &self.err {
            ShellOut::Write(_) => false,
            ShellOut::Stream { stream, .. } => stream.supports_color(),
        }
    }

    /// Prints a message and translates ANSI escape code into console colors.
    pub fn print_ansi(&mut self, message: &[u8]) -> CargoResult<()> {
        if self.needs_clear {
            self.err_erase_line();
        }
        #[cfg(windows)]
        {
            if let ShellOut::Stream { stream, .. } = &mut self.err {
                ::fwdansi::write_ansi(stream, message)?;
                return Ok(());
            }
        }
        self.err().write_all(message)?;
        Ok(())
    }
}

impl Default for Shell {
    fn default() -> Self {
        Self::new()
    }
}

impl ShellOut {
    /// Prints out a message with a status. The status comes first, and is bold plus the given
    /// color. The status can be justified, in which case the max width that will right align is
    /// 12 chars.
    fn print(
        &mut self,
        status: &dyn fmt::Display,
        message: Option<&dyn fmt::Display>,
        color: Color,
        justified: bool,
    ) -> CargoResult<()> {
        match *self {
            ShellOut::Stream { ref mut stream, .. } => {
                stream.reset()?;
                stream.set_color(ColorSpec::new().set_bold(true).set_fg(Some(color)))?;
                if justified {
                    write!(stream, "{:>12}", status)?;
                } else {
                    write!(stream, "{}", status)?;
                }
                stream.reset()?;
                match message {
                    Some(message) => writeln!(stream, " {}", message)?,
                    None => write!(stream, " ")?,
                }
            }
            ShellOut::Write(ref mut w) => {
                if justified {
                    write!(w, "{:>12}", status)?;
                } else {
                    write!(w, "{}", status)?;
                }
                match message {
                    Some(message) => writeln!(w, " {}", message)?,
                    None => write!(w, " ")?,
                }
            }
        }
        Ok(())
    }

    /// Gets this object as a `io::Write`.
    fn as_write(&mut self) -> &mut dyn Write {
        match *self {
            ShellOut::Stream { ref mut stream, .. } => stream,
            ShellOut::Write(ref mut w) => w,
        }
    }
}

impl ColorChoice {
    /// Converts our color choice to termcolor's version.
    fn to_termcolor_color_choice(self) -> termcolor::ColorChoice {
        match self {
            ColorChoice::Always => termcolor::ColorChoice::Always,
            ColorChoice::Never => termcolor::ColorChoice::Never,
            ColorChoice::CargoAuto => {
                if atty::is(atty::Stream::Stderr) {
                    termcolor::ColorChoice::Auto
                } else {
                    termcolor::ColorChoice::Never
                }
            }
        }
    }
}

#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
mod imp {
    use std::mem;

    use libc;

    use super::Shell;

    pub fn stderr_width() -> Option<usize> {
        unsafe {
            let mut winsize: libc::winsize = mem::zeroed();
            if libc::ioctl(libc::STDERR_FILENO, libc::TIOCGWINSZ.into(), &mut winsize) < 0 {
                return None;
            }
            if winsize.ws_col > 0 {
                Some(winsize.ws_col as usize)
            } else {
                None
            }
        }
    }

    pub fn err_erase_line(shell: &mut Shell) {
        // This is the "EL - Erase in Line" sequence. It clears from the cursor
        // to the end of line.
        // https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences
        let _ = shell.err.as_write().write_all(b"\x1B[K");
    }
}

#[cfg(all(
    unix,
    not(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))
))]
mod imp {
    pub(super) use super::default_err_erase_line as err_erase_line;

    pub fn stderr_width() -> Option<usize> {
        None
    }
}

#[cfg(windows)]
mod imp {
    use std::{cmp, mem, ptr};
    use winapi::um::fileapi::*;
    use winapi::um::handleapi::*;
    use winapi::um::processenv::*;
    use winapi::um::winbase::*;
    use winapi::um::wincon::*;
    use winapi::um::winnt::*;

    pub(super) use super::default_err_erase_line as err_erase_line;

    pub fn stderr_width() -> Option<usize> {
        unsafe {
            let stdout = GetStdHandle(STD_ERROR_HANDLE);
            let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = mem::zeroed();
            if GetConsoleScreenBufferInfo(stdout, &mut csbi) != 0 {
                return Some((csbi.srWindow.Right - csbi.srWindow.Left) as usize);
            }

            // On mintty/msys/cygwin based terminals, the above fails with
            // INVALID_HANDLE_VALUE. Use an alternate method which works
            // in that case as well.
            let h = CreateFileA(
                "CONOUT$\0".as_ptr() as *const CHAR,
                GENERIC_READ | GENERIC_WRITE,
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                ptr::null_mut(),
                OPEN_EXISTING,
                0,
                ptr::null_mut(),
            );
            if h == INVALID_HANDLE_VALUE {
                return None;
            }

            let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = mem::zeroed();
            let rc = GetConsoleScreenBufferInfo(h, &mut csbi);
            CloseHandle(h);
            if rc != 0 {
                let width = (csbi.srWindow.Right - csbi.srWindow.Left) as usize;
                // Unfortunately cygwin/mintty does not set the size of the
                // backing console to match the actual window size. This
                // always reports a size of 80 or 120 (not sure what
                // determines that). Use a conservative max of 60 which should
                // work in most circumstances. ConEmu does some magic to
                // resize the console correctly, but there's no reasonable way
                // to detect which kind of terminal we are running in, or if
                // GetConsoleScreenBufferInfo returns accurate information.
                return Some(cmp::min(60, width));
            }
            None
        }
    }
}

#[cfg(any(all(unix, not(any(target_os = "linux", target_os = "macos"))), windows,))]
fn default_err_erase_line(shell: &mut Shell) {
    if let Some(max_width) = imp::stderr_width() {
        let blank = " ".repeat(max_width);
        drop(write!(shell.err.as_write(), "{}\r", blank));
    }
}
<commit_msg>fixed unused code warning<commit_after>use std::fmt;
use std::io::prelude::*;

use atty;
use termcolor::Color::{Cyan, Green, Red, Yellow};
use termcolor::{self, Color, ColorSpec, StandardStream, WriteColor};

use crate::util::errors::CargoResult;

/// The requested verbosity of output.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Verbosity {
    Verbose,
    Normal,
    Quiet,
}

/// An abstraction around a `Write`able object that remembers preferences for output verbosity and
/// color.
pub struct Shell {
    /// the `Write`able object, either with or without color support (represented by different enum
    /// variants)
    err: ShellOut,
    /// How verbose messages should be
    verbosity: Verbosity,
    /// Flag that indicates the current line needs to be cleared before
    /// printing. Used when a progress bar is currently displayed.
    needs_clear: bool,
}

impl fmt::Debug for Shell {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self.err {
            ShellOut::Write(_) => f
                .debug_struct("Shell")
                .field("verbosity", &self.verbosity)
                .finish(),
            ShellOut::Stream { color_choice, .. } => f
                .debug_struct("Shell")
                .field("verbosity", &self.verbosity)
                .field("color_choice", &color_choice)
                .finish(),
        }
    }
}

/// A `Write`able object, either with or without color support
enum ShellOut {
    /// A plain write object without color support
    Write(Box<dyn Write>),
    /// Color-enabled stdio, with information on whether color should be used
    Stream {
        stream: StandardStream,
        tty: bool,
        color_choice: ColorChoice,
    },
}

/// Whether messages should use color output
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum ColorChoice {
    /// Force color output
    Always,
    /// Force disable color output
    Never,
    /// Intelligently guess whether to use color output
    CargoAuto,
}

impl Shell {
    /// Creates a new shell (color choice and verbosity), defaulting to 'auto' color and verbose
    /// output.
    pub fn new() -> Shell {
        Shell {
            err: ShellOut::Stream {
                stream: StandardStream::stderr(ColorChoice::CargoAuto.to_termcolor_color_choice()),
                color_choice: ColorChoice::CargoAuto,
                tty: atty::is(atty::Stream::Stderr),
            },
            verbosity: Verbosity::Verbose,
            needs_clear: false,
        }
    }

    /// Creates a shell from a plain writable object, with no color, and max verbosity.
    pub fn from_write(out: Box<dyn Write>) -> Shell {
        Shell {
            err: ShellOut::Write(out),
            verbosity: Verbosity::Verbose,
            needs_clear: false,
        }
    }

    /// Prints a message, where the status will have `color` color, and can be justified. The
    /// messages follows without color.
    fn print(
        &mut self,
        status: &dyn fmt::Display,
        message: Option<&dyn fmt::Display>,
        color: Color,
        justified: bool,
    ) -> CargoResult<()> {
        match self.verbosity {
            Verbosity::Quiet => Ok(()),
            _ => {
                if self.needs_clear {
                    self.err_erase_line();
                }
                self.err.print(status, message, color, justified)
            }
        }
    }

    /// Sets whether the next print should clear the current line.
    pub fn set_needs_clear(&mut self, needs_clear: bool) {
        self.needs_clear = needs_clear;
    }

    /// Returns `true` if the `needs_clear` flag is unset.
    pub fn is_cleared(&self) -> bool {
        !self.needs_clear
    }

    /// Returns the width of the terminal in spaces, if any.
    pub fn err_width(&self) -> Option<usize> {
        match self.err {
            ShellOut::Stream { tty: true, .. } => imp::stderr_width(),
            _ => None,
        }
    }

    /// Returns `true` if stderr is a tty.
    pub fn is_err_tty(&self) -> bool {
        match self.err {
            ShellOut::Stream { tty, .. } => tty,
            _ => false,
        }
    }

    /// Gets a reference to the underlying writer.
    pub fn err(&mut self) -> &mut dyn Write {
        if self.needs_clear {
            self.err_erase_line();
        }
        self.err.as_write()
    }

    /// Erase from cursor to end of line.
    pub fn err_erase_line(&mut self) {
        if let ShellOut::Stream { tty: true, .. } = self.err {
            imp::err_erase_line(self);
            self.needs_clear = false;
        }
    }

    /// Shortcut to right-align and color green a status message.
    pub fn status<T, U>(&mut self, status: T, message: U) -> CargoResult<()>
    where
        T: fmt::Display,
        U: fmt::Display,
    {
        self.print(&status, Some(&message), Green, true)
    }

    pub fn status_header<T>(&mut self, status: T) -> CargoResult<()>
    where
        T: fmt::Display,
    {
        self.print(&status, None, Cyan, true)
    }

    /// Shortcut to right-align a status message.
    pub fn status_with_color<T, U>(
        &mut self,
        status: T,
        message: U,
        color: Color,
    ) -> CargoResult<()>
    where
        T: fmt::Display,
        U: fmt::Display,
    {
        self.print(&status, Some(&message), color, true)
    }

    /// Runs the callback only if we are in verbose mode.
    pub fn verbose<F>(&mut self, mut callback: F) -> CargoResult<()>
    where
        F: FnMut(&mut Shell) -> CargoResult<()>,
    {
        match self.verbosity {
            Verbosity::Verbose => callback(self),
            _ => Ok(()),
        }
    }

    /// Runs the callback if we are not in verbose mode.
    pub fn concise<F>(&mut self, mut callback: F) -> CargoResult<()>
    where
        F: FnMut(&mut Shell) -> CargoResult<()>,
    {
        match self.verbosity {
            Verbosity::Verbose => Ok(()),
            _ => callback(self),
        }
    }

    /// Prints a red 'error' message.
    pub fn error<T: fmt::Display>(&mut self, message: T) -> CargoResult<()> {
        if self.needs_clear {
            self.err_erase_line();
        }
        self.err.print(&"error:", Some(&message), Red, false)
    }

    /// Prints an amber 'warning' message.
    pub fn warn<T: fmt::Display>(&mut self, message: T) -> CargoResult<()> {
        match self.verbosity {
            Verbosity::Quiet => Ok(()),
            _ => self.print(&"warning:", Some(&message), Yellow, false),
        }
    }

    /// Updates the verbosity of the shell.
    pub fn set_verbosity(&mut self, verbosity: Verbosity) {
        self.verbosity = verbosity;
    }

    /// Gets the verbosity of the shell.
    pub fn verbosity(&self) -> Verbosity {
        self.verbosity
    }

    /// Updates the color choice (always, never, or auto) from a string..
    pub fn set_color_choice(&mut self, color: Option<&str>) -> CargoResult<()> {
        if let ShellOut::Stream {
            ref mut stream,
            ref mut color_choice,
            ..
        } = self.err
        {
            let cfg = match color {
                Some("always") => ColorChoice::Always,
                Some("never") => ColorChoice::Never,

                Some("auto") | None => ColorChoice::CargoAuto,

                Some(arg) => failure::bail!(
                    "argument for --color must be auto, always, or \
                     never, but found `{}`",
                    arg
                ),
            };
            *color_choice = cfg;
            *stream = StandardStream::stderr(cfg.to_termcolor_color_choice());
        }
        Ok(())
    }

    /// Gets the current color choice.
    ///
    /// If we are not using a color stream, this will always return `Never`, even if the color
    /// choice has been set to something else.
    pub fn color_choice(&self) -> ColorChoice {
        match self.err {
            ShellOut::Stream { color_choice, .. } => color_choice,
            ShellOut::Write(_) => ColorChoice::Never,
        }
    }

    /// Whether the shell supports color.
    pub fn supports_color(&self) -> bool {
        match &self.err {
            ShellOut::Write(_) => false,
            ShellOut::Stream { stream, .. } => stream.supports_color(),
        }
    }

    /// Prints a message and translates ANSI escape code into console colors.
    pub fn print_ansi(&mut self, message: &[u8]) -> CargoResult<()> {
        if self.needs_clear {
            self.err_erase_line();
        }
        #[cfg(windows)]
        {
            if let ShellOut::Stream { stream, .. } = &mut self.err {
                ::fwdansi::write_ansi(stream, message)?;
                return Ok(());
            }
        }
        self.err().write_all(message)?;
        Ok(())
    }
}

impl Default for Shell {
    fn default() -> Self {
        Self::new()
    }
}

impl ShellOut {
    /// Prints out a message with a status. The status comes first, and is bold plus the given
    /// color. The status can be justified, in which case the max width that will right align is
    /// 12 chars.
    fn print(
        &mut self,
        status: &dyn fmt::Display,
        message: Option<&dyn fmt::Display>,
        color: Color,
        justified: bool,
    ) -> CargoResult<()> {
        match *self {
            ShellOut::Stream { ref mut stream, .. } => {
                stream.reset()?;
                stream.set_color(ColorSpec::new().set_bold(true).set_fg(Some(color)))?;
                if justified {
                    write!(stream, "{:>12}", status)?;
                } else {
                    write!(stream, "{}", status)?;
                }
                stream.reset()?;
                match message {
                    Some(message) => writeln!(stream, " {}", message)?,
                    None => write!(stream, " ")?,
                }
            }
            ShellOut::Write(ref mut w) => {
                if justified {
                    write!(w, "{:>12}", status)?;
                } else {
                    write!(w, "{}", status)?;
                }
                match message {
                    Some(message) => writeln!(w, " {}", message)?,
                    None => write!(w, " ")?,
                }
            }
        }
        Ok(())
    }

    /// Gets this object as a `io::Write`.
    fn as_write(&mut self) -> &mut dyn Write {
        match *self {
            ShellOut::Stream { ref mut stream, .. } => stream,
            ShellOut::Write(ref mut w) => w,
        }
    }
}

impl ColorChoice {
    /// Converts our color choice to termcolor's version.
    fn to_termcolor_color_choice(self) -> termcolor::ColorChoice {
        match self {
            ColorChoice::Always => termcolor::ColorChoice::Always,
            ColorChoice::Never => termcolor::ColorChoice::Never,
            ColorChoice::CargoAuto => {
                if atty::is(atty::Stream::Stderr) {
                    termcolor::ColorChoice::Auto
                } else {
                    termcolor::ColorChoice::Never
                }
            }
        }
    }
}

#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
mod imp {
    use std::mem;

    use libc;

    use super::Shell;

    pub fn stderr_width() -> Option<usize> {
        unsafe {
            let mut winsize: libc::winsize = mem::zeroed();
            if libc::ioctl(libc::STDERR_FILENO, libc::TIOCGWINSZ.into(), &mut winsize) < 0 {
                return None;
            }
            if winsize.ws_col > 0 {
                Some(winsize.ws_col as usize)
            } else {
                None
            }
        }
    }

    pub fn err_erase_line(shell: &mut Shell) {
        // This is the "EL - Erase in Line" sequence. It clears from the cursor
        // to the end of line.
        // https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences
        let _ = shell.err.as_write().write_all(b"\x1B[K");
    }
}

#[cfg(all(
    unix,
    not(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))
))]
mod imp {
    pub(super) use super::default_err_erase_line as err_erase_line;

    pub fn stderr_width() -> Option<usize> {
        None
    }
}

#[cfg(windows)]
mod imp {
    use std::{cmp, mem, ptr};
    use winapi::um::fileapi::*;
    use winapi::um::handleapi::*;
    use winapi::um::processenv::*;
    use winapi::um::winbase::*;
    use winapi::um::wincon::*;
    use winapi::um::winnt::*;

    pub(super) use super::default_err_erase_line as err_erase_line;

    pub fn stderr_width() -> Option<usize> {
        unsafe {
            let stdout = GetStdHandle(STD_ERROR_HANDLE);
            let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = mem::zeroed();
            if GetConsoleScreenBufferInfo(stdout, &mut csbi) != 0 {
                return Some((csbi.srWindow.Right - csbi.srWindow.Left) as usize);
            }

            // On mintty/msys/cygwin based terminals, the above fails with
            // INVALID_HANDLE_VALUE. Use an alternate method which works
            // in that case as well.
            let h = CreateFileA(
                "CONOUT$\0".as_ptr() as *const CHAR,
                GENERIC_READ | GENERIC_WRITE,
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                ptr::null_mut(),
                OPEN_EXISTING,
                0,
                ptr::null_mut(),
            );
            if h == INVALID_HANDLE_VALUE {
                return None;
            }

            let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = mem::zeroed();
            let rc = GetConsoleScreenBufferInfo(h, &mut csbi);
            CloseHandle(h);
            if rc != 0 {
                let width = (csbi.srWindow.Right - csbi.srWindow.Left) as usize;
                // Unfortunately cygwin/mintty does not set the size of the
                // backing console to match the actual window size. This
                // always reports a size of 80 or 120 (not sure what
                // determines that). Use a conservative max of 60 which should
                // work in most circumstances. ConEmu does some magic to
                // resize the console correctly, but there's no reasonable way
                // to detect which kind of terminal we are running in, or if
                // GetConsoleScreenBufferInfo returns accurate information.
                return Some(cmp::min(60, width));
            }
            None
        }
    }
}

#[cfg(any(
    all(
        unix,
        not(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))
    ),
    windows,
))]
fn default_err_erase_line(shell: &mut Shell) {
    if let Some(max_width) = imp::stderr_width() {
        let blank = " ".repeat(max_width);
        drop(write!(shell.err.as_write(), "{}\r", blank));
    }
}
<|endoftext|>