<commit_before>#![crate_name = "rboy"]

#![feature(old_io, std_misc, core, env)]

#[macro_use]
extern crate log;

extern crate getopts;

extern crate sdl2;

extern crate rboy;

use rboy::device::Device;
use std::time::Duration;
use std::sync::{Arc,RwLock};
use std::sync::mpsc::{Sender,Receiver};
use std::sync::mpsc::TryRecvError::{Disconnected,Empty};
use std::old_io::timer;

static SCALE: usize = 2;
static EXITCODE_INCORRECTOPTIONS: i32 = 1;
static EXITCODE_CPULOADFAILS: i32 = 2;

#[cfg(not(test))]
fn set_exit_status(exitcode: i32)
{
	std::env::set_exit_status(exitcode);
}

fn main() {
	let mut opts = getopts::Options::new();
	opts.optflag("s", "serial", "Output serial to stdout");
	opts.optflag("c", "classic", "Force Classic mode");

	let args: Vec<_> = std::env::args().collect();

	let matches = match opts.parse(&args[1..]) {
		Ok(m) => { m }
		Err(f) => { println!("{}", f); return }
	};

	let filename = if !matches.free.is_empty() {
		matches.free[0].clone()
	} else {
		let mut info_start = args[0].clone();
		info_start.push_str(" <filename>");
		println!("{}", opts.usage(info_start.as_slice()));
		set_exit_status(EXITCODE_INCORRECTOPTIONS);
		return;
	};

	sdl2::init(sdl2::INIT_VIDEO);
	let window = match sdl2::video::Window::new("RBoy - A gameboy in Rust",
												sdl2::video::WindowPos::PosUndefined,
												sdl2::video::WindowPos::PosUndefined,
												160*SCALE as i32,
												144*SCALE as i32,
												sdl2::video::WindowFlags::empty()) {
		Ok(window) => window,
		Err(err) => panic!("failed to open window: {}", err),
	};
	let renderer = match sdl2::render::Renderer::from_window(window, sdl2::render::RenderDriverIndex::Auto, sdl2::render::ACCELERATED) {
		Ok(screen) => screen,
		Err(err) => panic!("failed to open screen: {}", err),
	};

	let (sdl_tx, cpu_rx) = std::sync::mpsc::channel();
	let (cpu_tx, sdl_rx) = std::sync::mpsc::channel();
	let rawscreen = ::std::iter::repeat(0u8).take(160*144*3).collect();
	let arc = Arc::new(RwLock::new(rawscreen));
	let arc2 = arc.clone();

	let cpuloop_thread = std::thread::scoped(move|| cpuloop(&cpu_tx, &cpu_rx, arc2, filename.as_slice(), &matches));

	let mut timer = timer::Timer::new().unwrap();
	let periodic = timer.periodic(Duration::milliseconds(8));

	'main : loop {
		let _ = periodic.recv();
		match sdl_rx.try_recv() {
			Err(Disconnected) => { break 'main },
			Ok(_) => recalculate_screen(&renderer, &arc),
			Err(Empty) => {},
		}
		'event : loop {
			match sdl2::event::poll_event() {
				sdl2::event::Event::Quit { .. } => break 'main,
				sdl2::event::Event::None => break 'event,
				sdl2::event::Event::KeyDown { keycode: sdl2::keycode::KeyCode::Escape, .. }
					=> break 'main,
				sdl2::event::Event::KeyDown { keycode: sdl2::keycode::KeyCode::LShift, .. }
					=> { let _ = sdl_tx.send(GBEvent::SpeedUp); },
				sdl2::event::Event::KeyUp { keycode: sdl2::keycode::KeyCode::LShift, .. }
					=> { let _ = sdl_tx.send(GBEvent::SlowDown); },
				sdl2::event::Event::KeyDown { keycode: sdlkey, .. } => {
					match sdl_to_keypad(sdlkey) {
						Some(key) =>  { let _ = sdl_tx.send(GBEvent::KeyDown(key)); },
						None => {},
					}
				},
				sdl2::event::Event::KeyUp { keycode: sdlkey, .. } => {
					match sdl_to_keypad(sdlkey) {
						Some(key) => { let _ = sdl_tx.send(GBEvent::KeyUp(key)); },
						None => {},
					}
				},
				_ => {}
			}
		}
	}

	drop(sdl_tx); // Disconnect such that the cpuloop will exit
	let _ = cpuloop_thread.join();
}

fn sdl_to_keypad(key: sdl2::keycode::KeyCode) -> Option<rboy::KeypadKey> {
	match key {
		sdl2::keycode::KeyCode::Z => Some(rboy::KeypadKey::A),
		sdl2::keycode::KeyCode::X => Some(rboy::KeypadKey::B),
		sdl2::keycode::KeyCode::Up => Some(rboy::KeypadKey::Up),
		sdl2::keycode::KeyCode::Down => Some(rboy::KeypadKey::Down),
		sdl2::keycode::KeyCode::Left => Some(rboy::KeypadKey::Left),
		sdl2::keycode::KeyCode::Right => Some(rboy::KeypadKey::Right),
		sdl2::keycode::KeyCode::Space => Some(rboy::KeypadKey::Select),
		sdl2::keycode::KeyCode::Return => Some(rboy::KeypadKey::Start),
		_ => None,
	}
}

fn recalculate_screen(screen: &sdl2::render::Renderer, arc: &Arc<RwLock<Vec<u8>>>) {
	let mut drawer = screen.drawer();
	drawer.set_draw_color(sdl2::pixels::Color::RGB(0xFF, 0xFF, 0xFF));
	drawer.clear();

	let data =  arc.read().unwrap().clone();
	for y in range(0, 144) {
		for x in range(0, 160) {
			drawer.set_draw_color(sdl2::pixels::Color::RGB(data[y*160*3 + x*3 + 0],
															data[y*160*3 + x*3 + 1],
															data[y*160*3 + x*3 + 2]));
			drawer.draw_rect(sdl2::rect::Rect::new((x*SCALE) as i32, (y*SCALE) as i32, SCALE as i32, SCALE as i32));
		}
	}
	drawer.present();
}

enum GBEvent {
	KeyUp(rboy::KeypadKey),
	KeyDown(rboy::KeypadKey),
	SpeedUp,
	SlowDown,
}

fn cpuloop(cpu_tx: &Sender<u32>, cpu_rx: &Receiver<GBEvent>, arc: Arc<RwLock<Vec<u8>>>, filename: &str, matches: &getopts::Matches) {
	let opt_c = match matches.opt_present("classic") {
		true => Device::new(filename),
		false => Device::new_cgb(filename),
	};
	let mut c = match opt_c
	{
		Some(cpu) => { cpu },
		None => { error!("Could not get a valid gameboy"); set_exit_status(EXITCODE_CPULOADFAILS); return; },
	};
	c.set_stdout(matches.opt_present("serial"));

	let mut timer = timer::Timer::new().unwrap();
	let periodic = timer.periodic(Duration::milliseconds(8));
	let mut limit_speed = true;

	let waitticks = (4194.304f32 * 4.0) as u32;

	let mut ticks = 0;
	'cpuloop: loop {
		while ticks < waitticks {
			ticks += c.do_cycle();
			if c.check_and_reset_gpu_updated() {
				let mut data = arc.write().unwrap();
				let gpu_data = c.get_gpu_data();
				for i in range(0, data.len()) { data[i] = gpu_data[i]; }
				if cpu_tx.send(0).is_err() { break 'cpuloop };
			}
		}
		ticks -= waitticks;
		if limit_speed { let _ = periodic.recv(); }

		match cpu_rx.try_recv() {
			Ok(event) => match event {
				GBEvent::KeyUp(key) => c.keyup(key),
				GBEvent::KeyDown(key) => c.keydown(key),
				GBEvent::SpeedUp => limit_speed = false,
				GBEvent::SlowDown => limit_speed = true,
			},
			Err(Empty) => {},
			Err(Disconnected) => { break },
		};
	}
}
<commit_msg>Update to new SDL2 API<commit_after>#![crate_name = "rboy"]

#![feature(old_io, std_misc, core, env)]

#[macro_use]
extern crate log;

extern crate getopts;

extern crate sdl2;

extern crate rboy;

use rboy::device::Device;
use std::time::Duration;
use std::sync::{Arc,RwLock};
use std::sync::mpsc::{Sender,Receiver};
use std::sync::mpsc::TryRecvError::{Disconnected,Empty};
use std::old_io::timer;

static SCALE: usize = 2;
static EXITCODE_INCORRECTOPTIONS: i32 = 1;
static EXITCODE_CPULOADFAILS: i32 = 2;

#[cfg(not(test))]
fn set_exit_status(exitcode: i32)
{
	std::env::set_exit_status(exitcode);
}

fn main() {
	let mut opts = getopts::Options::new();
	opts.optflag("s", "serial", "Output serial to stdout");
	opts.optflag("c", "classic", "Force Classic mode");

	let args: Vec<_> = std::env::args().collect();

	let matches = match opts.parse(&args[1..]) {
		Ok(m) => { m }
		Err(f) => { println!("{}", f); return }
	};

	let filename = if !matches.free.is_empty() {
		matches.free[0].clone()
	} else {
		let mut info_start = args[0].clone();
		info_start.push_str(" <filename>");
		println!("{}", opts.usage(info_start.as_slice()));
		set_exit_status(EXITCODE_INCORRECTOPTIONS);
		return;
	};

	let sdl_context = sdl2::init(sdl2::INIT_VIDEO).unwrap();
	let window = match sdl2::video::Window::new("RBoy - A gameboy in Rust",
												sdl2::video::WindowPos::PosUndefined,
												sdl2::video::WindowPos::PosUndefined,
												160*SCALE as i32,
												144*SCALE as i32,
												sdl2::video::WindowFlags::empty()) {
		Ok(window) => window,
		Err(err) => panic!("failed to open window: {}", err),
	};
	let renderer = match sdl2::render::Renderer::from_window(window, sdl2::render::RenderDriverIndex::Auto, sdl2::render::ACCELERATED) {
		Ok(screen) => screen,
		Err(err) => panic!("failed to open screen: {}", err),
	};

	let (sdl_tx, cpu_rx) = std::sync::mpsc::channel();
	let (cpu_tx, sdl_rx) = std::sync::mpsc::channel();
	let rawscreen = ::std::iter::repeat(0u8).take(160*144*3).collect();
	let arc = Arc::new(RwLock::new(rawscreen));
	let arc2 = arc.clone();

	let cpuloop_thread = std::thread::scoped(move|| cpuloop(&cpu_tx, &cpu_rx, arc2, filename.as_slice(), &matches));

	let mut timer = timer::Timer::new().unwrap();
	let periodic = timer.periodic(Duration::milliseconds(8));

    let mut event_queue = sdl_context.event_pump();
	'main : loop {
		let _ = periodic.recv();
		match sdl_rx.try_recv() {
			Err(Disconnected) => { break 'main },
			Ok(_) => recalculate_screen(&renderer, &arc),
			Err(Empty) => {},
		}
		for event in event_queue.poll_iter() {
			match event {
				sdl2::event::Event::Quit { .. } => break 'main,
				sdl2::event::Event::KeyDown { keycode: sdl2::keycode::KeyCode::Escape, .. }
					=> break 'main,
				sdl2::event::Event::KeyDown { keycode: sdl2::keycode::KeyCode::LShift, .. }
					=> { let _ = sdl_tx.send(GBEvent::SpeedUp); },
				sdl2::event::Event::KeyUp { keycode: sdl2::keycode::KeyCode::LShift, .. }
					=> { let _ = sdl_tx.send(GBEvent::SlowDown); },
				sdl2::event::Event::KeyDown { keycode: sdlkey, .. } => {
					match sdl_to_keypad(sdlkey) {
						Some(key) =>  { let _ = sdl_tx.send(GBEvent::KeyDown(key)); },
						None => {},
					}
				},
				sdl2::event::Event::KeyUp { keycode: sdlkey, .. } => {
					match sdl_to_keypad(sdlkey) {
						Some(key) => { let _ = sdl_tx.send(GBEvent::KeyUp(key)); },
						None => {},
					}
				},
				_ => {}
			}
		}
	}

	drop(sdl_tx); // Disconnect such that the cpuloop will exit
	let _ = cpuloop_thread.join();
}

fn sdl_to_keypad(key: sdl2::keycode::KeyCode) -> Option<rboy::KeypadKey> {
	match key {
		sdl2::keycode::KeyCode::Z => Some(rboy::KeypadKey::A),
		sdl2::keycode::KeyCode::X => Some(rboy::KeypadKey::B),
		sdl2::keycode::KeyCode::Up => Some(rboy::KeypadKey::Up),
		sdl2::keycode::KeyCode::Down => Some(rboy::KeypadKey::Down),
		sdl2::keycode::KeyCode::Left => Some(rboy::KeypadKey::Left),
		sdl2::keycode::KeyCode::Right => Some(rboy::KeypadKey::Right),
		sdl2::keycode::KeyCode::Space => Some(rboy::KeypadKey::Select),
		sdl2::keycode::KeyCode::Return => Some(rboy::KeypadKey::Start),
		_ => None,
	}
}

fn recalculate_screen(screen: &sdl2::render::Renderer, arc: &Arc<RwLock<Vec<u8>>>) {
	let mut drawer = screen.drawer();
	drawer.set_draw_color(sdl2::pixels::Color::RGB(0xFF, 0xFF, 0xFF));
	drawer.clear();

	let data =  arc.read().unwrap().clone();
	for y in range(0, 144) {
		for x in range(0, 160) {
			drawer.set_draw_color(sdl2::pixels::Color::RGB(data[y*160*3 + x*3 + 0],
															data[y*160*3 + x*3 + 1],
															data[y*160*3 + x*3 + 2]));
			drawer.draw_rect(sdl2::rect::Rect::new((x*SCALE) as i32, (y*SCALE) as i32, SCALE as i32, SCALE as i32));
		}
	}
	drawer.present();
}

enum GBEvent {
	KeyUp(rboy::KeypadKey),
	KeyDown(rboy::KeypadKey),
	SpeedUp,
	SlowDown,
}

fn cpuloop(cpu_tx: &Sender<u32>, cpu_rx: &Receiver<GBEvent>, arc: Arc<RwLock<Vec<u8>>>, filename: &str, matches: &getopts::Matches) {
	let opt_c = match matches.opt_present("classic") {
		true => Device::new(filename),
		false => Device::new_cgb(filename),
	};
	let mut c = match opt_c
	{
		Some(cpu) => { cpu },
		None => { error!("Could not get a valid gameboy"); set_exit_status(EXITCODE_CPULOADFAILS); return; },
	};
	c.set_stdout(matches.opt_present("serial"));

	let mut timer = timer::Timer::new().unwrap();
	let periodic = timer.periodic(Duration::milliseconds(8));
	let mut limit_speed = true;

	let waitticks = (4194.304f32 * 4.0) as u32;

	let mut ticks = 0;
	'cpuloop: loop {
		while ticks < waitticks {
			ticks += c.do_cycle();
			if c.check_and_reset_gpu_updated() {
				let mut data = arc.write().unwrap();
				let gpu_data = c.get_gpu_data();
				for i in range(0, data.len()) { data[i] = gpu_data[i]; }
				if cpu_tx.send(0).is_err() { break 'cpuloop };
			}
		}
		ticks -= waitticks;
		if limit_speed { let _ = periodic.recv(); }

		match cpu_rx.try_recv() {
			Ok(event) => match event {
				GBEvent::KeyUp(key) => c.keyup(key),
				GBEvent::KeyDown(key) => c.keydown(key),
				GBEvent::SpeedUp => limit_speed = false,
				GBEvent::SlowDown => limit_speed = true,
			},
			Err(Empty) => {},
			Err(Disconnected) => { break },
		};
	}
}
<|endoftext|>


<commit_before>use type_sys::Value;

// The warning "unused_assignments" is allowed in many of these functions because there in the last
// argument of the "get_args!" macro, the assignment "count += 1" is unused.

macro_rules! get_args {
    ( $args:expr, $( $type:path ),* ) => {
        {
            let mut count = 0;
            (
                $(
                    if let &$type(val) = $args.get(count).expect("Wrong number of arguments") {
                        count += 1;
                        val
                    } else {
                        panic!("Wrong arguments");
                    },
                )*
            )
        }
    };
}

macro_rules! define_operator {
    ( $symbol:tt, $func_name:ident, $ret_type:path ) => {

        #[allow(unused_assignments)]
        pub fn $func_name(args: Vec<Value>) -> Value {
            use self::Value::*;
            let (lhs, rhs) = get_args!(args, Integer, Integer);
            $ret_type(lhs $symbol rhs)
        }

    }
}

pub fn resolve_func(name: String, args: Vec<Value>) -> Value {
    match name.as_ref() {
        "print" => print(args),
        _ => panic!("Unknown function: {}/{}", name, args.len()),
    }
}

pub fn print(args: Vec<Value>) -> Value {
    use self::Value::*;
    //let (val,) = get_args!(args, Integer);
    let val = args.get(0).expect("Wrong number of arguments");
    println!("=> {}", val);
    Void
}

//===========================
//== Arithmetic operations ==
//===========================

#[allow(unused_assignments)]
pub fn un_plus(args: Vec<Value>) -> Value {
    use self::Value::*;
    let (val,) = get_args!(args, Integer);
    Integer(val)
}

#[allow(unused_assignments)]
pub fn un_minus(args: Vec<Value>) -> Value {
    use self::Value::*;
    let (val,) = get_args!(args, Integer);
    Integer(-val)
}

define_operator!(+, plus,  Integer);
define_operator!(-, minus, Integer);
define_operator!(*, mul,   Integer);
define_operator!(/, div,   Integer);

//========================
//== Logical Operations ==
//========================

define_operator!(<,  lower,      Bool);
define_operator!(<=, lower_eq,   Bool);
define_operator!(>,  greater,    Bool);
define_operator!(>=, greater_eq, Bool);

#[allow(unused_assignments)]
pub fn equal(args: Vec<Value>) -> Value {
    use self::Value::*;
    let lhs = args.get(0).expect("Wrong number of arguments");
    let rhs = args.get(1).expect("Wrong number of arguments");
    Bool(lhs == rhs)
}

#[allow(unused_assignments)]
pub fn not_equal(args: Vec<Value>) -> Value {
    use self::Value::*;
    let lhs = args.get(0).expect("Wrong number of arguments");
    let rhs = args.get(1).expect("Wrong number of arguments");
    Bool(lhs != rhs)
}
<commit_msg>Rename type in builtins macro for compatibility<commit_after>use type_sys::Value;

// The warning "unused_assignments" is allowed in many of these functions because there in the last
// argument of the "get_args!" macro, the assignment "count += 1" is unused.

macro_rules! get_args {
    ( $args:expr, $( $arg_type:path ),* ) => {
        {
            let mut count = 0;
            (
                $(
                    if let &$arg_type(val) = $args.get(count).expect("Wrong number of arguments") {
                        count += 1;
                        val
                    } else {
                        panic!("Wrong arguments");
                    },
                )*
            )
        }
    };
}

macro_rules! define_operator {
    ( $symbol:tt, $func_name:ident, $ret_type:path ) => {

        #[allow(unused_assignments)]
        pub fn $func_name(args: Vec<Value>) -> Value {
            use self::Value::*;
            let (lhs, rhs) = get_args!(args, Integer, Integer);
            $ret_type(lhs $symbol rhs)
        }

    }
}

pub fn resolve_func(name: String, args: Vec<Value>) -> Value {
    match name.as_ref() {
        "print" => print(args),
        _ => panic!("Unknown function: {}/{}", name, args.len()),
    }
}

pub fn print(args: Vec<Value>) -> Value {
    use self::Value::*;
    //let (val,) = get_args!(args, Integer);
    let val = args.get(0).expect("Wrong number of arguments");
    println!("=> {}", val);
    Void
}

//===========================
//== Arithmetic operations ==
//===========================

#[allow(unused_assignments)]
pub fn un_plus(args: Vec<Value>) -> Value {
    use self::Value::*;
    let (val,) = get_args!(args, Integer);
    Integer(val)
}

#[allow(unused_assignments)]
pub fn un_minus(args: Vec<Value>) -> Value {
    use self::Value::*;
    let (val,) = get_args!(args, Integer);
    Integer(-val)
}

define_operator!(+, plus,  Integer);
define_operator!(-, minus, Integer);
define_operator!(*, mul,   Integer);
define_operator!(/, div,   Integer);

//========================
//== Logical Operations ==
//========================

define_operator!(<,  lower,      Bool);
define_operator!(<=, lower_eq,   Bool);
define_operator!(>,  greater,    Bool);
define_operator!(>=, greater_eq, Bool);

#[allow(unused_assignments)]
pub fn equal(args: Vec<Value>) -> Value {
    use self::Value::*;
    let lhs = args.get(0).expect("Wrong number of arguments");
    let rhs = args.get(1).expect("Wrong number of arguments");
    Bool(lhs == rhs)
}

#[allow(unused_assignments)]
pub fn not_equal(args: Vec<Value>) -> Value {
    use self::Value::*;
    let lhs = args.get(0).expect("Wrong number of arguments");
    let rhs = args.get(1).expect("Wrong number of arguments");
    Bool(lhs != rhs)
}
<|endoftext|>


<commit_before>// Implements the `zakopane checksum` subcommand.

use std::convert::TryInto;
use std::io::Read;
use std::io::Write;

use crate::structs::ChecksumCliOptions;
use crate::structs::ZakopaneError;

const READ_SIZE: usize = 1 << 20;

struct ChecksumWithPath {
    checksum: String,
    path: std::path::PathBuf,
}

impl ChecksumWithPath {
    pub fn new(checksum: String, path: std::path::PathBuf) -> Self {
        Self { checksum, path }
    }
}

type ChecksumResult = Result<ChecksumWithPath, ZakopaneError>;
type ChecksumTaskJoinHandle = tokio::task::JoinHandle<Vec<ChecksumWithPath>>;

struct ChecksumTaskDispatcherData {
    // Rate-limiter for spawned checksum tasks.
    semaphore: std::sync::Arc<tokio::sync::Semaphore>,

    cli_options: ChecksumCliOptions,

    // Counts total number of spawned checksum tasks.
    spawn_counter: std::sync::Arc<std::sync::atomic::AtomicUsize>,

    sender: tokio::sync::mpsc::Sender<ChecksumResult>,
}

impl ChecksumTaskDispatcherData {
    pub fn new(
        cli_options: ChecksumCliOptions,
        spawn_counter: std::sync::Arc<std::sync::atomic::AtomicUsize>,
        sender: tokio::sync::mpsc::Sender<ChecksumResult>,
    ) -> Self {
        Self {
            semaphore: std::sync::Arc::new(tokio::sync::Semaphore::new(cli_options.max_tasks)),
            cli_options,
            spawn_counter,
            sender,
        }
    }
}

struct FileDetails {
    is_file: bool,
    is_big: bool,
}

fn get_file_details(path: &std::path::PathBuf, big_file_bytes: Option<u64>) -> FileDetails {
    let metadata = match std::fs::symlink_metadata(path) {
        Ok(m) => m,
        Err(_) => {
            return FileDetails {
                is_file: false,
                is_big: false,
            }
        }
    };

    FileDetails {
        is_file: metadata.file_type().is_file(),
        is_big: if let Some(val) = big_file_bytes {
            metadata.len() > val
        } else {
            false
        },
    }
}

async fn get_semaphore_permit(
    path: &std::path::PathBuf,
    context: &ChecksumTaskDispatcherData,
) -> Option<tokio::sync::OwnedSemaphorePermit> {
    let file_details = get_file_details(&path, context.cli_options.big_file_bytes);
    if !file_details.is_file {
        return None;
    }

    // If the file is "big" (arbitrarily defined by user), attempt to
    // seize all permits to force us momentarily to work with a bit less
    // I/O contention.
    let permit = if file_details.is_big {
        context
            .semaphore
            .clone()
            .acquire_many_owned(context.cli_options.max_tasks.try_into().unwrap())
            .await
            .unwrap()
    } else {
        context.semaphore.clone().acquire_owned().await.unwrap()
    };
    Some(permit)
}

// Sends a checksum task result downstream to the collector task.
fn checksum_task_send_result(
    result: ChecksumResult,
    sender: tokio::sync::mpsc::Sender<ChecksumResult>,
) {
    if let Err(e) = sender.blocking_send(result) {
        eprintln!("BUG: Sender::send() failed: {}", e);
    }
}

fn checksum_task_impl(path: std::path::PathBuf) -> ChecksumResult {
    let mut hasher = crypto_hash::Hasher::new(crypto_hash::Algorithm::SHA256);
    let mut buffer: Vec<u8> = vec![0; READ_SIZE];
    let mut file = std::fs::File::open(&path).map_err(ZakopaneError::Io)?;
    loop {
        let read_bytes = file.read(&mut buffer).map_err(ZakopaneError::Io)?;
        if read_bytes == 0 {
            let checksum = hasher
                .finish()
                .into_iter()
                .map(|byte| format!("{:02x}", byte))
                .collect::<Vec<String>>()
                .join("");
            return Ok(ChecksumWithPath::new(checksum, path));
        }
        hasher
            .write_all(&buffer[..read_bytes])
            .map_err(ZakopaneError::Io)?;
    }
}

// Represents a spawned checksum task.
//
// `_permit` is moved into this checksum task simply to hold onto the
// semaphore-dispensed resource for the duration of this task.
fn checksum_task(
    path: std::path::PathBuf,
    sender: tokio::sync::mpsc::Sender<ChecksumResult>,
    _permit: tokio::sync::OwnedSemaphorePermit,
) {
    let result = checksum_task_impl(path);
    checksum_task_send_result(result, sender);
}

// Represents the spawned collection task.
async fn collector_task(
    mut receiver: tokio::sync::mpsc::Receiver<ChecksumResult>,
    spawn_counter: std::sync::Arc<std::sync::atomic::AtomicUsize>,
) -> Vec<ChecksumWithPath> {
    let mut sums: Vec<ChecksumWithPath> = Vec::new();
    let mut errors: usize = 0;
    loop {
        while let Some(result) = receiver.recv().await {
            match result {
                Ok(digest_with_path) => sums.push(digest_with_path),
                Err(_) => errors += 1,
            }
        }
        if sums.len() + errors == spawn_counter.load(std::sync::atomic::Ordering::SeqCst) {
            break;
        }
    }
    if errors != 0 {
        eprintln!("WARNING: {} errors collected", errors);
    }
    sums
}

// Spawns the collector task that listens for checksum results
// provided by the checksum tasks.
async fn spawn_collector(
    options: ChecksumCliOptions,
) -> (ChecksumTaskDispatcherData, ChecksumTaskJoinHandle) {
    let (sender, receiver) = tokio::sync::mpsc::channel(options.max_tasks);
    let spawn_counter = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
    let join_handle = tokio::task::spawn(collector_task(receiver, spawn_counter.clone()));
    (
        ChecksumTaskDispatcherData::new(options, spawn_counter, sender),
        join_handle,
    )
}

async fn spawn_checksum_tasks(context: ChecksumTaskDispatcherData) {
    let walk_iter = walkdir::WalkDir::new(&context.cli_options.path).into_iter();
    // The `filter_entry()` call is crafted s.t.
    // *    we skip and don't descend into hidden directories
    // *    unless the hidden directory is the target directory, because
    //      the target directory is always the first yielded value from
    //      the `WalkDir`.
    for entry in walk_iter.filter_entry(|e| {
        e.path() == &context.cli_options.path
            || !e
                .file_name()
                .to_str()
                .map(|path| path.starts_with("."))
                .unwrap_or(false)
    }) {
        let path = match entry {
            Ok(d) => d,
            Err(_) => continue,
        }
        .into_path();
        let permit = match get_semaphore_permit(&path, &context).await {
            Some(p) => p,
            None => continue,
        };

        context
            .spawn_counter
            .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
        let sender = context.sender.clone();
        tokio::task::spawn_blocking(move || checksum_task(path, sender, permit));
    }
}

// Pretty-prints the sorted `checksums` in a format much like what the
// `sha256sum` binary outputs.
//
// Note that the standard zakopane snapshot header is not added here.
fn pretty_format_checksums(path: std::path::PathBuf, checksums: Vec<ChecksumWithPath>) -> String {
    let mut buffer: Vec<String> = checksums
        .into_iter()
        .map(|e| {
            format!(
                "{}  ./{}",
                e.checksum,
                e.path.strip_prefix(&path).unwrap().to_str().unwrap()
            )
        })
        .collect();
    buffer.push(String::new());
    buffer.join("\n")
}

async fn checksum_impl(options: ChecksumCliOptions) -> String {
    let path = options.path.clone();
    let (dispatcher_data, join_handle) = spawn_collector(options).await;
    spawn_checksum_tasks(dispatcher_data).await;
    let mut checksums: Vec<ChecksumWithPath> = join_handle.await.unwrap();
    checksums.sort_by(|a, b| a.path.cmp(&b.path));
    pretty_format_checksums(path, checksums)
}

pub fn checksum(options: ChecksumCliOptions) -> String {
    let runtime = tokio::runtime::Runtime::new().unwrap();
    runtime.block_on(checksum_impl(options))
}
<commit_msg>checksum: Move `pretty_format_checksums()` call<commit_after>// Implements the `zakopane checksum` subcommand.

use std::convert::TryInto;
use std::io::Read;
use std::io::Write;

use crate::structs::ChecksumCliOptions;
use crate::structs::ZakopaneError;

const READ_SIZE: usize = 1 << 20;

struct ChecksumWithPath {
    checksum: String,
    path: std::path::PathBuf,
}

impl ChecksumWithPath {
    pub fn new(checksum: String, path: std::path::PathBuf) -> Self {
        Self { checksum, path }
    }
}

type ChecksumResult = Result<ChecksumWithPath, ZakopaneError>;
type ChecksumTaskJoinHandle = tokio::task::JoinHandle<Vec<ChecksumWithPath>>;

struct ChecksumTaskDispatcherData {
    // Rate-limiter for spawned checksum tasks.
    semaphore: std::sync::Arc<tokio::sync::Semaphore>,

    cli_options: ChecksumCliOptions,

    // Counts total number of spawned checksum tasks.
    spawn_counter: std::sync::Arc<std::sync::atomic::AtomicUsize>,

    sender: tokio::sync::mpsc::Sender<ChecksumResult>,
}

impl ChecksumTaskDispatcherData {
    pub fn new(
        cli_options: ChecksumCliOptions,
        spawn_counter: std::sync::Arc<std::sync::atomic::AtomicUsize>,
        sender: tokio::sync::mpsc::Sender<ChecksumResult>,
    ) -> Self {
        Self {
            semaphore: std::sync::Arc::new(tokio::sync::Semaphore::new(cli_options.max_tasks)),
            cli_options,
            spawn_counter,
            sender,
        }
    }
}

struct FileDetails {
    is_file: bool,
    is_big: bool,
}

fn get_file_details(path: &std::path::PathBuf, big_file_bytes: Option<u64>) -> FileDetails {
    let metadata = match std::fs::symlink_metadata(path) {
        Ok(m) => m,
        Err(_) => {
            return FileDetails {
                is_file: false,
                is_big: false,
            }
        }
    };

    FileDetails {
        is_file: metadata.file_type().is_file(),
        is_big: if let Some(val) = big_file_bytes {
            metadata.len() > val
        } else {
            false
        },
    }
}

async fn get_semaphore_permit(
    path: &std::path::PathBuf,
    context: &ChecksumTaskDispatcherData,
) -> Option<tokio::sync::OwnedSemaphorePermit> {
    let file_details = get_file_details(&path, context.cli_options.big_file_bytes);
    if !file_details.is_file {
        return None;
    }

    // If the file is "big" (arbitrarily defined by user), attempt to
    // seize all permits to force us momentarily to work with a bit less
    // I/O contention.
    let permit = if file_details.is_big {
        context
            .semaphore
            .clone()
            .acquire_many_owned(context.cli_options.max_tasks.try_into().unwrap())
            .await
            .unwrap()
    } else {
        context.semaphore.clone().acquire_owned().await.unwrap()
    };
    Some(permit)
}

// Sends a checksum task result downstream to the collector task.
fn checksum_task_send_result(
    result: ChecksumResult,
    sender: tokio::sync::mpsc::Sender<ChecksumResult>,
) {
    if let Err(e) = sender.blocking_send(result) {
        eprintln!("BUG: Sender::send() failed: {}", e);
    }
}

fn checksum_task_impl(path: std::path::PathBuf) -> ChecksumResult {
    let mut hasher = crypto_hash::Hasher::new(crypto_hash::Algorithm::SHA256);
    let mut buffer: Vec<u8> = vec![0; READ_SIZE];
    let mut file = std::fs::File::open(&path).map_err(ZakopaneError::Io)?;
    loop {
        let read_bytes = file.read(&mut buffer).map_err(ZakopaneError::Io)?;
        if read_bytes == 0 {
            let checksum = hasher
                .finish()
                .into_iter()
                .map(|byte| format!("{:02x}", byte))
                .collect::<Vec<String>>()
                .join("");
            return Ok(ChecksumWithPath::new(checksum, path));
        }
        hasher
            .write_all(&buffer[..read_bytes])
            .map_err(ZakopaneError::Io)?;
    }
}

// Represents a spawned checksum task.
//
// `_permit` is moved into this checksum task simply to hold onto the
// semaphore-dispensed resource for the duration of this task.
fn checksum_task(
    path: std::path::PathBuf,
    sender: tokio::sync::mpsc::Sender<ChecksumResult>,
    _permit: tokio::sync::OwnedSemaphorePermit,
) {
    let result = checksum_task_impl(path);
    checksum_task_send_result(result, sender);
}

// Represents the spawned collection task.
async fn collector_task(
    mut receiver: tokio::sync::mpsc::Receiver<ChecksumResult>,
    spawn_counter: std::sync::Arc<std::sync::atomic::AtomicUsize>,
) -> Vec<ChecksumWithPath> {
    let mut sums: Vec<ChecksumWithPath> = Vec::new();
    let mut errors: usize = 0;
    loop {
        while let Some(result) = receiver.recv().await {
            match result {
                Ok(digest_with_path) => sums.push(digest_with_path),
                Err(_) => errors += 1,
            }
        }
        if sums.len() + errors == spawn_counter.load(std::sync::atomic::Ordering::SeqCst) {
            break;
        }
    }
    if errors != 0 {
        eprintln!("WARNING: {} errors collected", errors);
    }
    sums
}

// Spawns the collector task that listens for checksum results
// provided by the checksum tasks.
async fn spawn_collector(
    options: ChecksumCliOptions,
) -> (ChecksumTaskDispatcherData, ChecksumTaskJoinHandle) {
    let (sender, receiver) = tokio::sync::mpsc::channel(options.max_tasks);
    let spawn_counter = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
    let join_handle = tokio::task::spawn(collector_task(receiver, spawn_counter.clone()));
    (
        ChecksumTaskDispatcherData::new(options, spawn_counter, sender),
        join_handle,
    )
}

async fn spawn_checksum_tasks(context: ChecksumTaskDispatcherData) {
    let walk_iter = walkdir::WalkDir::new(&context.cli_options.path).into_iter();
    // The `filter_entry()` call is crafted s.t.
    // *    we skip and don't descend into hidden directories
    // *    unless the hidden directory is the target directory, because
    //      the target directory is always the first yielded value from
    //      the `WalkDir`.
    for entry in walk_iter.filter_entry(|e| {
        e.path() == &context.cli_options.path
            || !e
                .file_name()
                .to_str()
                .map(|path| path.starts_with("."))
                .unwrap_or(false)
    }) {
        let path = match entry {
            Ok(d) => d,
            Err(_) => continue,
        }
        .into_path();
        let permit = match get_semaphore_permit(&path, &context).await {
            Some(p) => p,
            None => continue,
        };

        context
            .spawn_counter
            .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
        let sender = context.sender.clone();
        tokio::task::spawn_blocking(move || checksum_task(path, sender, permit));
    }
}

// Pretty-prints the sorted `checksums` in a format much like what the
// `sha256sum` binary outputs.
//
// Note that the standard zakopane snapshot header is not added here.
fn pretty_format_checksums(path: std::path::PathBuf, checksums: Vec<ChecksumWithPath>) -> String {
    let mut buffer: Vec<String> = checksums
        .into_iter()
        .map(|e| {
            format!(
                "{}  ./{}",
                e.checksum,
                e.path.strip_prefix(&path).unwrap().to_str().unwrap()
            )
        })
        .collect();
    buffer.push(String::new());
    buffer.join("\n")
}

async fn checksum_impl(options: ChecksumCliOptions) -> Vec<ChecksumWithPath> {
    let (dispatcher_data, join_handle) = spawn_collector(options).await;
    spawn_checksum_tasks(dispatcher_data).await;
    let mut checksums: Vec<ChecksumWithPath> = join_handle.await.unwrap();
    checksums.sort_by(|a, b| a.path.cmp(&b.path));
    checksums
}

pub fn checksum(options: ChecksumCliOptions) -> String {
    let path = options.path.clone();
    let runtime = tokio::runtime::Runtime::new().unwrap();
    let checksums = runtime.block_on(checksum_impl(options));
    pretty_format_checksums(path, checksums)
}
<|endoftext|>


<commit_before>pub static RUSTUP_HELP: &str = r"DISCUSSION:
    rustup installs The Rust Programming Language from the official
    release channels, enabling you to easily switch between stable,
    beta, and nightly compilers and keep them updated. It makes
    cross-compiling simpler with binary builds of the standard library
    for common platforms.

    If you are new to Rust consider running `rustup doc --book` to
    learn Rust.";

pub static SHOW_HELP: &str = r"DISCUSSION:
    Shows the name of the active toolchain and the version of `rustc`.

    If the active toolchain has installed support for additional
    compilation targets, then they are listed as well.

    If there are multiple toolchains installed then all installed
    toolchains are listed as well.";

pub static SHOW_ACTIVE_TOOLCHAIN_HELP: &str = r"DISCUSSION:
    Shows the name of the active toolchain.

    This is useful for figuring out the active tool chain from
    scripts.

    You should use `rustc --print sysroot` to get the sysroot, or
    `rustc --version` to get the toolchain version.";

pub static UPDATE_HELP: &str = r"DISCUSSION:
    With no toolchain specified, the `update` command updates each of
    the installed toolchains from the official release channels, then
    updates rustup itself.

    If given a toolchain argument then `update` updates that
    toolchain, the same as `rustup toolchain install`.";

pub static INSTALL_HELP: &str = r"DISCUSSION:
    Installs a specific rust toolchain.

    The 'install' command is an alias for 'rustup update <toolchain>'.";

pub static DEFAULT_HELP: &str = r"DISCUSSION:
    Sets the default toolchain to the one specified. If the toolchain
    is not already installed then it is installed first.";

pub static TOOLCHAIN_HELP: &str = r"DISCUSSION:
    Many `rustup` commands deal with *toolchains*, a single
    installation of the Rust compiler. `rustup` supports multiple
    types of toolchains. The most basic track the official release
    channels: 'stable', 'beta' and 'nightly'; but `rustup` can also
    install toolchains from the official archives, for alternate host
    platforms, and from local builds.

    Standard release channel toolchain names have the following form:

        <channel>[-<date>][-<host>]

        <channel>       = stable|beta|nightly|<version>
        <date>          = YYYY-MM-DD
        <host>          = <target-triple>

    'channel' is either a named release channel or an explicit version
    number, such as '1.8.0'. Channel names can be optionally appended
    with an archive date, as in 'nightly-2017-05-09', in which case
    the toolchain is downloaded from the archive for that date.

    Finally, the host may be specified as a target triple. This is
    most useful for installing a 32-bit compiler on a 64-bit platform,
    or for installing the [MSVC-based toolchain] on Windows. For
    example:

        $ rustup toolchain install stable-x86_64-pc-windows-msvc

    For convenience, elements of the target triple that are omitted
    will be inferred, so the above could be written:

        $ rustup default stable-msvc

    rustup can also manage symlinked local toolchain builds, which are
    often used to for developing Rust itself. For more information see
    `rustup toolchain help link`.";

pub static TOOLCHAIN_LINK_HELP: &str = r"DISCUSSION:
    'toolchain' is the custom name to be assigned to the new toolchain.
    Any name is permitted as long as it does not fully match an initial
    substring of a standard release channel. For example, you can use
    the names 'latest' or '2017-04-01' but you cannot use 'stable' or
    'beta-i686' or 'nightly-x86_64-unknown-linux-gnu'.

    'path' specifies the directory where the binaries and libraries for
    the custom toolchain can be found. For example, when used for
    development of Rust itself, toolchains can be linked directly out of
    the build directory. After building, you can test out different
    compiler versions as follows:

        $ rustup toolchain link latest-stage1 build/x86_64-unknown-linux-gnu/stage1
        $ rustup override set latest-stage1

    If you now compile a crate in the current directory, the custom
    toolchain 'latest-stage1' will be used.";

pub static OVERRIDE_HELP: &str = r"DISCUSSION:
    Overrides configure rustup to use a specific toolchain when
    running in a specific directory.

    Directories can be assigned their own Rust toolchain with `rustup
    override`. When a directory has an override then any time `rustc`
    or `cargo` is run inside that directory, or one of its child
    directories, the override toolchain will be invoked.

    To pin to a specific nightly:

        $ rustup override set nightly-2014-12-18

    Or a specific stable release:

        $ rustup override set 1.0.0

    To see the active toolchain use `rustup show`. To remove the
    override and use the default toolchain again, `rustup override
    unset`.";

pub static OVERRIDE_UNSET_HELP: &str = r"DISCUSSION:
    If `--path` argument is present, removes the override toolchain
    for the specified directory. If `--nonexistent` argument is
    present, removes the override toolchain for all nonexistent
    directories. Otherwise, removes the override toolchain for the
    current directory.";

pub static RUN_HELP: &str = r"DISCUSSION:
    Configures an environment to use the given toolchain and then runs
    the specified program. The command may be any program, not just
    rustc or cargo. This can be used for testing arbitrary toolchains
    without setting an override.

    Commands explicitly proxied by `rustup` (such as `rustc` and
    `cargo`) also have a shorthand for this available. The toolchain
    can be set by using `+toolchain` as the first argument. These are
    equivalent:

        $ cargo +nightly build

        $ rustup run nightly cargo build";

pub static DOC_HELP: &str = r"DISCUSSION:
    Opens the documentation for the currently active toolchain with
    the default browser.

    By default, it opens the documentation index. Use the various
    flags to open specific pieces of documentation.";

pub static COMPLETIONS_HELP: &str =
r"DISCUSSION:
    One can generate a completion script for `rustup` that is
    compatible with a given shell. The script is output on `stdout`
    allowing one to re-direct the output to the file of their
    choosing. Where you place the file will depend on which shell, and
    which operating system you are using. Your particular
    configuration may also determine where these scripts need to be
    placed.

    Here are some common set ups for the three supported shells under
    Unix and similar operating systems (such as GNU/Linux).

    BASH:

    Completion files are commonly stored in `/etc/bash_completion.d/` for
    system-wide commands, but can be stored in in
    `~/.local/share/bash_completion/completions` for user-specific commands.
    Run the command:

        $ rustup completions bash >> ~/.local/share/bash_completion/completions/rustup

    This installs the completion script. You may have to log out and
    log back in to your shell session for the changes to take affect.

    BASH (macOS/Homebrew):

    Homebrew stores bash completion files within the Homebrew directory.
    With the `bash-completion` brew formula installed, run the command:

        $ rustup completions bash > $(brew --prefix)/etc/bash_completion.d/rustup.bash-completion

    FISH:

    Fish completion files are commonly stored in
    `$HOME/.config/fish/completions`. Run the command:

        $ rustup completions fish > ~/.config/fish/completions/rustup.fish

    This installs the completion script. You may have to log out and
    log back in to your shell session for the changes to take affect.

    ZSH:

    ZSH completions are commonly stored in any directory listed in
    your `$fpath` variable. To use these completions, you must either
    add the generated script to one of those directories, or add your
    own to this list.

    Adding a custom directory is often the safest bet if you are
    unsure of which directory to use. First create the directory; for
    this example we'll create a hidden directory inside our `$HOME`
    directory:

        $ mkdir ~/.zfunc

    Then add the following lines to your `.zshrc` just before
    `compinit`:

        fpath+=~/.zfunc

    Now you can install the completions script using the following
    command:

        $ rustup completions zsh > ~/.zfunc/_rustup

    You must then either log out and log back in, or simply run

        $ exec zsh

    for the new completions to take affect.

    CUSTOM LOCATIONS:

    Alternatively, you could save these files to the place of your
    choosing, such as a custom directory inside your $HOME. Doing so
    will require you to add the proper directives, such as `source`ing
    inside your login script. Consult your shells documentation for
    how to add such directives.

    POWERSHELL:

    The powershell completion scripts require PowerShell v5.0+ (which
    comes Windows 10, but can be downloaded separately for windows 7
    or 8.1).

    First, check if a profile has already been set

        PS C:\> Test-Path $profile

    If the above command returns `False` run the following

        PS C:\> New-Item -path $profile -type file -force

    Now open the file provided by `$profile` (if you used the
    `New-Item` command it will be
    `${env:USERPROFILE}\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1`

    Next, we either save the completions file into our profile, or
    into a separate file and source it inside our profile. To save the
    completions into our profile simply use

        PS C:\> rustup completions powershell >> ${env:USERPROFILE}\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

    CARGO:

    Rustup can also generate a completion script for `cargo`. The script output
    by `rustup` will source the completion script distributed with your default
    toolchain. Not all shells are currently supported. Here are examples for
    the currently supported shells.

    BASH:

        $ rustup completions bash cargo >> ~/.local/share/bash_completion/completions/cargo

    ZSH:

        $ rustup completions zsh cargo > ~/.zfunc/_cargo";

pub static TOOLCHAIN_ARG_HELP: &str = "Toolchain name, such as 'stable', 'nightly', \
                                       or '1.8.0'. For more information see `rustup \
                                       help toolchain`";
<commit_msg>help: Update completions help to include mkdirs<commit_after>pub static RUSTUP_HELP: &str = r"DISCUSSION:
    rustup installs The Rust Programming Language from the official
    release channels, enabling you to easily switch between stable,
    beta, and nightly compilers and keep them updated. It makes
    cross-compiling simpler with binary builds of the standard library
    for common platforms.

    If you are new to Rust consider running `rustup doc --book` to
    learn Rust.";

pub static SHOW_HELP: &str = r"DISCUSSION:
    Shows the name of the active toolchain and the version of `rustc`.

    If the active toolchain has installed support for additional
    compilation targets, then they are listed as well.

    If there are multiple toolchains installed then all installed
    toolchains are listed as well.";

pub static SHOW_ACTIVE_TOOLCHAIN_HELP: &str = r"DISCUSSION:
    Shows the name of the active toolchain.

    This is useful for figuring out the active tool chain from
    scripts.

    You should use `rustc --print sysroot` to get the sysroot, or
    `rustc --version` to get the toolchain version.";

pub static UPDATE_HELP: &str = r"DISCUSSION:
    With no toolchain specified, the `update` command updates each of
    the installed toolchains from the official release channels, then
    updates rustup itself.

    If given a toolchain argument then `update` updates that
    toolchain, the same as `rustup toolchain install`.";

pub static INSTALL_HELP: &str = r"DISCUSSION:
    Installs a specific rust toolchain.

    The 'install' command is an alias for 'rustup update <toolchain>'.";

pub static DEFAULT_HELP: &str = r"DISCUSSION:
    Sets the default toolchain to the one specified. If the toolchain
    is not already installed then it is installed first.";

pub static TOOLCHAIN_HELP: &str = r"DISCUSSION:
    Many `rustup` commands deal with *toolchains*, a single
    installation of the Rust compiler. `rustup` supports multiple
    types of toolchains. The most basic track the official release
    channels: 'stable', 'beta' and 'nightly'; but `rustup` can also
    install toolchains from the official archives, for alternate host
    platforms, and from local builds.

    Standard release channel toolchain names have the following form:

        <channel>[-<date>][-<host>]

        <channel>       = stable|beta|nightly|<version>
        <date>          = YYYY-MM-DD
        <host>          = <target-triple>

    'channel' is either a named release channel or an explicit version
    number, such as '1.8.0'. Channel names can be optionally appended
    with an archive date, as in 'nightly-2017-05-09', in which case
    the toolchain is downloaded from the archive for that date.

    Finally, the host may be specified as a target triple. This is
    most useful for installing a 32-bit compiler on a 64-bit platform,
    or for installing the [MSVC-based toolchain] on Windows. For
    example:

        $ rustup toolchain install stable-x86_64-pc-windows-msvc

    For convenience, elements of the target triple that are omitted
    will be inferred, so the above could be written:

        $ rustup default stable-msvc

    rustup can also manage symlinked local toolchain builds, which are
    often used to for developing Rust itself. For more information see
    `rustup toolchain help link`.";

pub static TOOLCHAIN_LINK_HELP: &str = r"DISCUSSION:
    'toolchain' is the custom name to be assigned to the new toolchain.
    Any name is permitted as long as it does not fully match an initial
    substring of a standard release channel. For example, you can use
    the names 'latest' or '2017-04-01' but you cannot use 'stable' or
    'beta-i686' or 'nightly-x86_64-unknown-linux-gnu'.

    'path' specifies the directory where the binaries and libraries for
    the custom toolchain can be found. For example, when used for
    development of Rust itself, toolchains can be linked directly out of
    the build directory. After building, you can test out different
    compiler versions as follows:

        $ rustup toolchain link latest-stage1 build/x86_64-unknown-linux-gnu/stage1
        $ rustup override set latest-stage1

    If you now compile a crate in the current directory, the custom
    toolchain 'latest-stage1' will be used.";

pub static OVERRIDE_HELP: &str = r"DISCUSSION:
    Overrides configure rustup to use a specific toolchain when
    running in a specific directory.

    Directories can be assigned their own Rust toolchain with `rustup
    override`. When a directory has an override then any time `rustc`
    or `cargo` is run inside that directory, or one of its child
    directories, the override toolchain will be invoked.

    To pin to a specific nightly:

        $ rustup override set nightly-2014-12-18

    Or a specific stable release:

        $ rustup override set 1.0.0

    To see the active toolchain use `rustup show`. To remove the
    override and use the default toolchain again, `rustup override
    unset`.";

pub static OVERRIDE_UNSET_HELP: &str = r"DISCUSSION:
    If `--path` argument is present, removes the override toolchain
    for the specified directory. If `--nonexistent` argument is
    present, removes the override toolchain for all nonexistent
    directories. Otherwise, removes the override toolchain for the
    current directory.";

pub static RUN_HELP: &str = r"DISCUSSION:
    Configures an environment to use the given toolchain and then runs
    the specified program. The command may be any program, not just
    rustc or cargo. This can be used for testing arbitrary toolchains
    without setting an override.

    Commands explicitly proxied by `rustup` (such as `rustc` and
    `cargo`) also have a shorthand for this available. The toolchain
    can be set by using `+toolchain` as the first argument. These are
    equivalent:

        $ cargo +nightly build

        $ rustup run nightly cargo build";

pub static DOC_HELP: &str = r"DISCUSSION:
    Opens the documentation for the currently active toolchain with
    the default browser.

    By default, it opens the documentation index. Use the various
    flags to open specific pieces of documentation.";

pub static COMPLETIONS_HELP: &str =
r"DISCUSSION:
    One can generate a completion script for `rustup` that is
    compatible with a given shell. The script is output on `stdout`
    allowing one to re-direct the output to the file of their
    choosing. Where you place the file will depend on which shell, and
    which operating system you are using. Your particular
    configuration may also determine where these scripts need to be
    placed.

    Here are some common set ups for the three supported shells under
    Unix and similar operating systems (such as GNU/Linux).

    BASH:

    Completion files are commonly stored in `/etc/bash_completion.d/` for
    system-wide commands, but can be stored in in
    `~/.local/share/bash_completion/completions` for user-specific commands.
    Run the command:

        $ mkdir -p ~/.local/share/bash_completion/completions
        $ rustup completions bash >> ~/.local/share/bash_completion/completions/rustup

    This installs the completion script. You may have to log out and
    log back in to your shell session for the changes to take affect.

    BASH (macOS/Homebrew):

    Homebrew stores bash completion files within the Homebrew directory.
    With the `bash-completion` brew formula installed, run the command:

        $ mkdir -p $(brew --prefix)/etc/bash_completion.d
        $ rustup completions bash > $(brew --prefix)/etc/bash_completion.d/rustup.bash-completion

    FISH:

    Fish completion files are commonly stored in
    `$HOME/.config/fish/completions`. Run the command:

        $ mkdir -p ~/.config/fish/completions
        $ rustup completions fish > ~/.config/fish/completions/rustup.fish

    This installs the completion script. You may have to log out and
    log back in to your shell session for the changes to take affect.

    ZSH:

    ZSH completions are commonly stored in any directory listed in
    your `$fpath` variable. To use these completions, you must either
    add the generated script to one of those directories, or add your
    own to this list.

    Adding a custom directory is often the safest bet if you are
    unsure of which directory to use. First create the directory; for
    this example we'll create a hidden directory inside our `$HOME`
    directory:

        $ mkdir ~/.zfunc

    Then add the following lines to your `.zshrc` just before
    `compinit`:

        fpath+=~/.zfunc

    Now you can install the completions script using the following
    command:

        $ rustup completions zsh > ~/.zfunc/_rustup

    You must then either log out and log back in, or simply run

        $ exec zsh

    for the new completions to take affect.

    CUSTOM LOCATIONS:

    Alternatively, you could save these files to the place of your
    choosing, such as a custom directory inside your $HOME. Doing so
    will require you to add the proper directives, such as `source`ing
    inside your login script. Consult your shells documentation for
    how to add such directives.

    POWERSHELL:

    The powershell completion scripts require PowerShell v5.0+ (which
    comes Windows 10, but can be downloaded separately for windows 7
    or 8.1).

    First, check if a profile has already been set

        PS C:\> Test-Path $profile

    If the above command returns `False` run the following

        PS C:\> New-Item -path $profile -type file -force

    Now open the file provided by `$profile` (if you used the
    `New-Item` command it will be
    `${env:USERPROFILE}\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1`

    Next, we either save the completions file into our profile, or
    into a separate file and source it inside our profile. To save the
    completions into our profile simply use

        PS C:\> rustup completions powershell >> ${env:USERPROFILE}\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

    CARGO:

    Rustup can also generate a completion script for `cargo`. The script output
    by `rustup` will source the completion script distributed with your default
    toolchain. Not all shells are currently supported. Here are examples for
    the currently supported shells.

    BASH:

        $ rustup completions bash cargo >> ~/.local/share/bash_completion/completions/cargo

    ZSH:

        $ rustup completions zsh cargo > ~/.zfunc/_cargo";

pub static TOOLCHAIN_ARG_HELP: &str = "Toolchain name, such as 'stable', 'nightly', \
                                       or '1.8.0'. For more information see `rustup \
                                       help toolchain`";
<|endoftext|>


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

use data;
use parser::{ParseError, Parser};
use parser;

pub fn compile(source: &str) -> Result<HashMap<String, parser::Entry>, ParseError> {
  let p = Parser::new(source.chars());
  let entries = try!(p.parse());

  // TODO: parse all imports

  let mut map = HashMap::new();

  for mut entry in entries.move_iter() {
    let id = match entry {
      parser::Comment(..) | parser::Import(..) => continue,
      parser::Macro(ref id, _, _) => id.clone(),
      parser::Entity(ref id, ref mut value, ref indices, ref mut attrs)  => {
        // while we're here, fix up and Hash values with default indices
        match value  {
          &parser::Hash(..) => {
            if indices.len() > 0 {
              add_default_indices(value, indices.iter());
            }
          },
          _ => {}
        };
        for &parser::Attr(_, ref mut value, ref indices) in attrs.mut_iter() {
          match value  {
            &parser::Hash(..) => {
              if indices.len() > 0 {
                add_default_indices(value, indices.iter());
              }
            },
            _ => {}
          };
        }

        id.clone()
      }
    };
    map.insert(id, entry);
  }

  Ok(map)
}

fn add_default_indices<'r, I: Iterator<&'r parser::Expr> + Clone>(value: &mut parser::Value, mut indices: I) {
  match value {
    &parser::Hash(ref mut map, _, ref mut def_index) => {
      match indices.next() {
        Some(idx) => {
          for (_k, v) in map.mut_iter() {
            add_default_indices(v, indices.clone());
          }
          *def_index = Some(box idx.clone())
        },
        None => {}
      }
    },
    _ => {}
  }
}

pub type Env = HashMap<String, parser::Entry>;

pub struct ResolveContext<'a> {
  data: &'a data::Data,
  env: &'a Env,
  locals: Option<&'a data::Data>,
  index: Option<String>,
}

impl<'a> ResolveContext<'a> {
  pub fn new(env: &'a Env, data: &'a data::Data) -> ResolveContext<'a> {
    ResolveContext {
      env: env,
      data: data,
      locals: None,
      index: None,
    }
  }

  fn with_locals(&'a self, locals: &'a data::Data) -> ResolveContext<'a> {
    ResolveContext {
      env: self.env,
      data: self.data,
      locals: Some(locals),
      index: None,
    }
  }

  fn with_index(&'a self, index: Option<String>) -> ResolveContext<'a> {
    ResolveContext {
      env: self.env,
      data: self.data,
      locals: self.locals,
      index: index,
    }
  }
}

pub type ResolveResult = Result<ResolveTarget, ResolveError>;

pub enum ResolveTarget {
  Entry(parser::Entry),
  Value(parser::Value),
  Data(data::Data)
}

/// Errors that can occur when resolving a set of l20n resources into strings.
/// These errors are cause by problems in the l20n file, or incorrect Data
/// provided when localizing.
#[deriving(Show)]
pub enum ResolveError {
  /// A resource received a value of the wrong type.
  WrongType,
  /// A macro was called with the wrong number of arguments.
  WrongNumberOfArgs,
  /// Accessed an index of a Hash that does not exist.
  MissingIndex,
  /// Accessed an attribute of an entity that does not exist.
  MissingAttr,
  /// Tried to use a $var that did not exist in the provided Data.
  MissingVar(String),
  /// A string tried to use another string in the l20n resource that did not
  /// exist.
  MissingIdent(String),
}

/// Resolve an L20n resource into Data.
pub trait Resolve {

  /// Resolves this value a step. It could resolve to another Value, or
  /// resolve completely to a Data.
  fn resolve(&self, ctx: &ResolveContext) -> ResolveResult;

  /// Keeps resolving until a Data value is returned.
  fn resolve_data(&self, ctx: &ResolveContext) -> Result<data::Data, ResolveError> {
    match self.resolve(ctx) {
      Ok(Data(d)) => Ok(d),
      Ok(other) => other.resolve_data(ctx),
      Err(e) => Err(e)
    }
  }
}

impl Resolve for ResolveTarget {
  fn resolve(&self, ctx: &ResolveContext) -> ResolveResult {
    match *self {
      Data(ref d) => Ok(Data(d.clone())),
      Entry(ref e) => e.resolve(ctx),
      Value(ref v) => v.resolve(ctx)
    }
  }
}

impl Resolve for parser::Entry {
  fn resolve(&self, _ctx: &ResolveContext) -> ResolveResult {
    match *self {
      parser::Entity(_, ref value, _, _) => {
        Ok(Value(value.clone()))
      }
      _ => Ok(Data(data::Null))
    }
  }
}

impl Resolve for parser::Value {
  fn resolve(&self, ctx: &ResolveContext) -> ResolveResult {
    match *self {
      parser::Str(ref s) => Ok(Data(data::Str(s.clone()))),
      parser::ComplexStr(ref exprs) => {
        let mut vec = Vec::with_capacity(exprs.len());
        for expr in exprs.iter() {
          vec.push(match expr.resolve_data(ctx) {
            Ok(data::Str(s)) => s,
            Ok(data::Num(n)) => format!("{}", n),
            Ok(_) => return Err(WrongType),
            Err(e) => return Err(e)
          });
        }
        Ok(Data(data::Str(vec.concat())))
      }
      parser::Hash(ref map, ref def_key, ref def_index) => {
        match ctx.index {
          Some(ref s) => match map.find(s) {
              Some(v) => return Ok(Value(v.clone())),
              None => {}
          },
          None => {}
        };
        match *def_key {
          Some(ref s) => match map.find(s) {
              Some(v) => return Ok(Value(v.clone())),
              None => {}
          },
          None => {}
        };
        match *def_index {
          Some(ref e) => match e.resolve_data(ctx) {
              Ok(data::Str(ref s)) => match map.find(s) {
                Some(v) => return Ok(Value(v.clone())),
                None => {}
              },
              Ok(_) => return Err(WrongType),
              Err(e) => return Err(e)
          },
          None => {}
        };
        Err(MissingIndex)
      }
    }
  }
}

impl Resolve for parser::Expr {
  fn resolve(&self, ctx: &ResolveContext) -> ResolveResult {
    match *self {
      parser::ValExpr(ref val) => Ok(Value(val.clone())),
      parser::NumExpr(ref n) => Ok(Data(data::Num(*n))),
      parser::BinExpr(ref left, ref op, ref right) => {
        let left = try!(left.resolve_data(ctx));
        let right = try!(right.resolve_data(ctx));
        match (*op, left, right) {
          // math ops
          (parser::BiAdd, data::Num(l), data::Num(r)) => Ok(Data(data::Num(l + r))),
          (parser::BiSub, data::Num(l), data::Num(r)) => Ok(Data(data::Num(l - r))),
          (parser::BiMul, data::Num(l), data::Num(r)) => Ok(Data(data::Num(l * r))),
          (parser::BiDiv, data::Num(l), data::Num(r)) => Ok(Data(data::Num(l / r))),
          (parser::BiRem, data::Num(l), data::Num(r)) => Ok(Data(data::Num(l % r))),

          (parser::BiLt, data::Num(l), data::Num(r)) => Ok(Data(data::Bool(l < r))),
          (parser::BiLe, data::Num(l), data::Num(r)) => Ok(Data(data::Bool(l <= r))),
          (parser::BiGt, data::Num(l), data::Num(r)) => Ok(Data(data::Bool(l > r))),
          (parser::BiGe, data::Num(l), data::Num(r)) => Ok(Data(data::Bool(l >= r))),

          // logical ops
          (parser::BiAnd, data::Bool(l), data::Bool(r)) => Ok(Data(data::Bool(l && r))),
          (parser::BiOr, data::Bool(l), data::Bool(r)) => Ok(Data(data::Bool(l || r))),

          // equality ops. can be Num, Bool, or Str
          (parser::BiEq, data::Bool(l), data::Bool(r)) => Ok(Data(data::Bool(l == r))),
          (parser::BiEq, data::Str(l), data::Str(r)) => Ok(Data(data::Bool(l == r))),
          (parser::BiEq, data::Num(l), data::Num(r)) => Ok(Data(data::Bool(l == r))),
          (parser::BiNe, data::Bool(l), data::Bool(r)) => Ok(Data(data::Bool(l == r))),
          (parser::BiNe, data::Str(l), data::Str(r)) => Ok(Data(data::Bool(l == r))),
          (parser::BiNe, data::Num(l), data::Num(r)) => Ok(Data(data::Bool(l != r))),

          (_, _, _) => Err(WrongType)
        }
      }
      parser::UnExpr(ref op, ref expr) => {
        let expr = try!(expr.resolve_data(ctx));
        match (*op, expr) {
          (parser::UnAdd, data::Num(n)) => Ok(Data(data::Num(n))),
          (parser::UnSub, data::Num(n)) => Ok(Data(data::Num(-n))),
          (parser::UnNot, data::Bool(b)) => Ok(Data(data::Bool(!b))),
          _ => Err(WrongType)
        }
      }
      parser::VarExpr(ref name) => {
        match ctx.locals.and_then(|locals| locals.find_copy(name)) {
          Some(val) => return Ok(Data(val)),
          _ => {}
        };
        match ctx.data.find_copy(name) {
          Some(d) => Ok(Data(d)),
          None => Err(MissingVar(name.clone()))
        }
      }
      parser::IdentExpr(ref ident) => {
        match ctx.env.find(ident) {
          Some(e) => Ok(Entry(e.clone())),
          None => Err(MissingIdent(ident.clone()))
        }
      }
      parser::CondExpr(ref cond, ref consequent, ref alt) => {
        match try!(cond.resolve_data(ctx)) {
          data::Bool(b) => {
            if b {
              consequent.resolve(ctx)
            } else {
              alt.resolve(ctx)
            }
          },
          _ => Err(WrongType)
        }
      }
      parser::CallExpr(box parser::IdentExpr(ref ident), ref args) => {
        match ctx.env.find(ident) {
          Some(&parser::Macro(_, ref arg_names, ref body)) => {
            if args.len() == arg_names.len() {
              let mut map = HashMap::new();
              for (k, v) in arg_names.iter().zip(args.iter()) {
                let name = match k {
                  &parser::VarExpr(ref name) => name.clone(),
                  // not a VarExpr would be the parser going nuts
                  _ => unreachable!()
                };
                let arg = match v.resolve_data(ctx) {
                  Ok(val) => val,
                  Err(e) => return Err(e)
                };
                map.insert(name, arg);
              }
              let locals = data::Map(map);
              body.resolve(&ctx.with_locals(&locals))
            } else {
              Err(WrongNumberOfArgs)
            }
          }
          Some(_) => Err(WrongType),
          None => Err(MissingIdent(ident.clone()))
        }
      }
      parser::PropExpr(ref parent, ref prop, ref access) => {
        let prop = match *access {
          parser::Computed => match prop.resolve_data(ctx) {
            Ok(data::Str(s)) => s,
            Ok(_) => return Err(WrongType),
            Err(e) => return Err(e)
          },
          parser::Static => match *prop {
            box parser::IdentExpr(ref s) => s.clone(),
            _ => return Err(WrongType)
          }
        };

        match parent.resolve(ctx) {
          Ok(Data(data::Map(ref m))) => {
            match m.find_copy(&prop) {
              Some(d) => Ok(Data(d)),
              None => Err(MissingIndex)
            }
          },
          Ok(Entry(ref e)) => {
            match e.resolve(ctx) {
              Ok(Value(ref v)) => v.resolve(&ctx.with_index(Some(prop))),
              Ok(_) => Err(WrongType),
              Err(e) => Err(e)
            }
          },
          Ok(Value(ref v)) => {
            v.resolve(&ctx.with_index(Some(prop)))
          },
          Ok(_) => Err(WrongType),
          Err(e) => Err(e)
        }
      }
      parser::AttrExpr(ref parent, ref prop, ref access) => {
        let prop = match *access {
          parser::Computed => match prop.resolve_data(ctx) {
            Ok(data::Str(s)) => s,
            Ok(_) => return Err(WrongType),
            Err(e) => return Err(e)
          },
          parser::Static => match *prop {
            box parser::IdentExpr(ref s) => s.clone(),
            _ => return Err(WrongType)
          }
        };

        match parent.resolve(ctx) {
          Ok(Entry(parser::Entity(_, _, _, ref attrs))) => {
            for &parser::Attr(ref id, ref value, _) in attrs.iter() {
              if id.as_slice() == prop.as_slice() {
                return value.resolve(ctx)
              }
            }
            Err(MissingAttr)
          },
          Ok(_) => Err(WrongType),
          Err(e) => Err(e)
        }
      }
      ref e => fail!("{} not yet implemented", e)
    }
  }
}

#[cfg(test)]
mod tests {
  use super::{compile, Resolve, ResolveContext};
  use data::{Str, Null};

  #[test]
  fn test_compile() {
    let map = compile("<hi 'hello world'>").unwrap();
    let entity = map.get(&String::from_str("hi"));
    let data = Null;
    let ctx = ResolveContext::new(&map, &data);

    assert_eq!(entity.resolve_data(&ctx).unwrap(), Str(String::from_str("hello world")));

  }
}
<commit_msg>remove deprecated use of `std::collections::hashmap::HashMap::get`<commit_after>
use std::collections::HashMap;

use data;
use parser::{ParseError, Parser};
use parser;

pub fn compile(source: &str) -> Result<HashMap<String, parser::Entry>, ParseError> {
  let p = Parser::new(source.chars());
  let entries = try!(p.parse());

  // TODO: parse all imports

  let mut map = HashMap::new();

  for mut entry in entries.move_iter() {
    let id = match entry {
      parser::Comment(..) | parser::Import(..) => continue,
      parser::Macro(ref id, _, _) => id.clone(),
      parser::Entity(ref id, ref mut value, ref indices, ref mut attrs)  => {
        // while we're here, fix up and Hash values with default indices
        match value  {
          &parser::Hash(..) => {
            if indices.len() > 0 {
              add_default_indices(value, indices.iter());
            }
          },
          _ => {}
        };
        for &parser::Attr(_, ref mut value, ref indices) in attrs.mut_iter() {
          match value  {
            &parser::Hash(..) => {
              if indices.len() > 0 {
                add_default_indices(value, indices.iter());
              }
            },
            _ => {}
          };
        }

        id.clone()
      }
    };
    map.insert(id, entry);
  }

  Ok(map)
}

fn add_default_indices<'r, I: Iterator<&'r parser::Expr> + Clone>(value: &mut parser::Value, mut indices: I) {
  match value {
    &parser::Hash(ref mut map, _, ref mut def_index) => {
      match indices.next() {
        Some(idx) => {
          for (_k, v) in map.mut_iter() {
            add_default_indices(v, indices.clone());
          }
          *def_index = Some(box idx.clone())
        },
        None => {}
      }
    },
    _ => {}
  }
}

pub type Env = HashMap<String, parser::Entry>;

pub struct ResolveContext<'a> {
  data: &'a data::Data,
  env: &'a Env,
  locals: Option<&'a data::Data>,
  index: Option<String>,
}

impl<'a> ResolveContext<'a> {
  pub fn new(env: &'a Env, data: &'a data::Data) -> ResolveContext<'a> {
    ResolveContext {
      env: env,
      data: data,
      locals: None,
      index: None,
    }
  }

  fn with_locals(&'a self, locals: &'a data::Data) -> ResolveContext<'a> {
    ResolveContext {
      env: self.env,
      data: self.data,
      locals: Some(locals),
      index: None,
    }
  }

  fn with_index(&'a self, index: Option<String>) -> ResolveContext<'a> {
    ResolveContext {
      env: self.env,
      data: self.data,
      locals: self.locals,
      index: index,
    }
  }
}

pub type ResolveResult = Result<ResolveTarget, ResolveError>;

pub enum ResolveTarget {
  Entry(parser::Entry),
  Value(parser::Value),
  Data(data::Data)
}

/// Errors that can occur when resolving a set of l20n resources into strings.
/// These errors are cause by problems in the l20n file, or incorrect Data
/// provided when localizing.
#[deriving(Show)]
pub enum ResolveError {
  /// A resource received a value of the wrong type.
  WrongType,
  /// A macro was called with the wrong number of arguments.
  WrongNumberOfArgs,
  /// Accessed an index of a Hash that does not exist.
  MissingIndex,
  /// Accessed an attribute of an entity that does not exist.
  MissingAttr,
  /// Tried to use a $var that did not exist in the provided Data.
  MissingVar(String),
  /// A string tried to use another string in the l20n resource that did not
  /// exist.
  MissingIdent(String),
}

/// Resolve an L20n resource into Data.
pub trait Resolve {

  /// Resolves this value a step. It could resolve to another Value, or
  /// resolve completely to a Data.
  fn resolve(&self, ctx: &ResolveContext) -> ResolveResult;

  /// Keeps resolving until a Data value is returned.
  fn resolve_data(&self, ctx: &ResolveContext) -> Result<data::Data, ResolveError> {
    match self.resolve(ctx) {
      Ok(Data(d)) => Ok(d),
      Ok(other) => other.resolve_data(ctx),
      Err(e) => Err(e)
    }
  }
}

impl Resolve for ResolveTarget {
  fn resolve(&self, ctx: &ResolveContext) -> ResolveResult {
    match *self {
      Data(ref d) => Ok(Data(d.clone())),
      Entry(ref e) => e.resolve(ctx),
      Value(ref v) => v.resolve(ctx)
    }
  }
}

impl Resolve for parser::Entry {
  fn resolve(&self, _ctx: &ResolveContext) -> ResolveResult {
    match *self {
      parser::Entity(_, ref value, _, _) => {
        Ok(Value(value.clone()))
      }
      _ => Ok(Data(data::Null))
    }
  }
}

impl Resolve for parser::Value {
  fn resolve(&self, ctx: &ResolveContext) -> ResolveResult {
    match *self {
      parser::Str(ref s) => Ok(Data(data::Str(s.clone()))),
      parser::ComplexStr(ref exprs) => {
        let mut vec = Vec::with_capacity(exprs.len());
        for expr in exprs.iter() {
          vec.push(match expr.resolve_data(ctx) {
            Ok(data::Str(s)) => s,
            Ok(data::Num(n)) => format!("{}", n),
            Ok(_) => return Err(WrongType),
            Err(e) => return Err(e)
          });
        }
        Ok(Data(data::Str(vec.concat())))
      }
      parser::Hash(ref map, ref def_key, ref def_index) => {
        match ctx.index {
          Some(ref s) => match map.find(s) {
              Some(v) => return Ok(Value(v.clone())),
              None => {}
          },
          None => {}
        };
        match *def_key {
          Some(ref s) => match map.find(s) {
              Some(v) => return Ok(Value(v.clone())),
              None => {}
          },
          None => {}
        };
        match *def_index {
          Some(ref e) => match e.resolve_data(ctx) {
              Ok(data::Str(ref s)) => match map.find(s) {
                Some(v) => return Ok(Value(v.clone())),
                None => {}
              },
              Ok(_) => return Err(WrongType),
              Err(e) => return Err(e)
          },
          None => {}
        };
        Err(MissingIndex)
      }
    }
  }
}

impl Resolve for parser::Expr {
  fn resolve(&self, ctx: &ResolveContext) -> ResolveResult {
    match *self {
      parser::ValExpr(ref val) => Ok(Value(val.clone())),
      parser::NumExpr(ref n) => Ok(Data(data::Num(*n))),
      parser::BinExpr(ref left, ref op, ref right) => {
        let left = try!(left.resolve_data(ctx));
        let right = try!(right.resolve_data(ctx));
        match (*op, left, right) {
          // math ops
          (parser::BiAdd, data::Num(l), data::Num(r)) => Ok(Data(data::Num(l + r))),
          (parser::BiSub, data::Num(l), data::Num(r)) => Ok(Data(data::Num(l - r))),
          (parser::BiMul, data::Num(l), data::Num(r)) => Ok(Data(data::Num(l * r))),
          (parser::BiDiv, data::Num(l), data::Num(r)) => Ok(Data(data::Num(l / r))),
          (parser::BiRem, data::Num(l), data::Num(r)) => Ok(Data(data::Num(l % r))),

          (parser::BiLt, data::Num(l), data::Num(r)) => Ok(Data(data::Bool(l < r))),
          (parser::BiLe, data::Num(l), data::Num(r)) => Ok(Data(data::Bool(l <= r))),
          (parser::BiGt, data::Num(l), data::Num(r)) => Ok(Data(data::Bool(l > r))),
          (parser::BiGe, data::Num(l), data::Num(r)) => Ok(Data(data::Bool(l >= r))),

          // logical ops
          (parser::BiAnd, data::Bool(l), data::Bool(r)) => Ok(Data(data::Bool(l && r))),
          (parser::BiOr, data::Bool(l), data::Bool(r)) => Ok(Data(data::Bool(l || r))),

          // equality ops. can be Num, Bool, or Str
          (parser::BiEq, data::Bool(l), data::Bool(r)) => Ok(Data(data::Bool(l == r))),
          (parser::BiEq, data::Str(l), data::Str(r)) => Ok(Data(data::Bool(l == r))),
          (parser::BiEq, data::Num(l), data::Num(r)) => Ok(Data(data::Bool(l == r))),
          (parser::BiNe, data::Bool(l), data::Bool(r)) => Ok(Data(data::Bool(l == r))),
          (parser::BiNe, data::Str(l), data::Str(r)) => Ok(Data(data::Bool(l == r))),
          (parser::BiNe, data::Num(l), data::Num(r)) => Ok(Data(data::Bool(l != r))),

          (_, _, _) => Err(WrongType)
        }
      }
      parser::UnExpr(ref op, ref expr) => {
        let expr = try!(expr.resolve_data(ctx));
        match (*op, expr) {
          (parser::UnAdd, data::Num(n)) => Ok(Data(data::Num(n))),
          (parser::UnSub, data::Num(n)) => Ok(Data(data::Num(-n))),
          (parser::UnNot, data::Bool(b)) => Ok(Data(data::Bool(!b))),
          _ => Err(WrongType)
        }
      }
      parser::VarExpr(ref name) => {
        match ctx.locals.and_then(|locals| locals.find_copy(name)) {
          Some(val) => return Ok(Data(val)),
          _ => {}
        };
        match ctx.data.find_copy(name) {
          Some(d) => Ok(Data(d)),
          None => Err(MissingVar(name.clone()))
        }
      }
      parser::IdentExpr(ref ident) => {
        match ctx.env.find(ident) {
          Some(e) => Ok(Entry(e.clone())),
          None => Err(MissingIdent(ident.clone()))
        }
      }
      parser::CondExpr(ref cond, ref consequent, ref alt) => {
        match try!(cond.resolve_data(ctx)) {
          data::Bool(b) => {
            if b {
              consequent.resolve(ctx)
            } else {
              alt.resolve(ctx)
            }
          },
          _ => Err(WrongType)
        }
      }
      parser::CallExpr(box parser::IdentExpr(ref ident), ref args) => {
        match ctx.env.find(ident) {
          Some(&parser::Macro(_, ref arg_names, ref body)) => {
            if args.len() == arg_names.len() {
              let mut map = HashMap::new();
              for (k, v) in arg_names.iter().zip(args.iter()) {
                let name = match k {
                  &parser::VarExpr(ref name) => name.clone(),
                  // not a VarExpr would be the parser going nuts
                  _ => unreachable!()
                };
                let arg = match v.resolve_data(ctx) {
                  Ok(val) => val,
                  Err(e) => return Err(e)
                };
                map.insert(name, arg);
              }
              let locals = data::Map(map);
              body.resolve(&ctx.with_locals(&locals))
            } else {
              Err(WrongNumberOfArgs)
            }
          }
          Some(_) => Err(WrongType),
          None => Err(MissingIdent(ident.clone()))
        }
      }
      parser::PropExpr(ref parent, ref prop, ref access) => {
        let prop = match *access {
          parser::Computed => match prop.resolve_data(ctx) {
            Ok(data::Str(s)) => s,
            Ok(_) => return Err(WrongType),
            Err(e) => return Err(e)
          },
          parser::Static => match *prop {
            box parser::IdentExpr(ref s) => s.clone(),
            _ => return Err(WrongType)
          }
        };

        match parent.resolve(ctx) {
          Ok(Data(data::Map(ref m))) => {
            match m.find_copy(&prop) {
              Some(d) => Ok(Data(d)),
              None => Err(MissingIndex)
            }
          },
          Ok(Entry(ref e)) => {
            match e.resolve(ctx) {
              Ok(Value(ref v)) => v.resolve(&ctx.with_index(Some(prop))),
              Ok(_) => Err(WrongType),
              Err(e) => Err(e)
            }
          },
          Ok(Value(ref v)) => {
            v.resolve(&ctx.with_index(Some(prop)))
          },
          Ok(_) => Err(WrongType),
          Err(e) => Err(e)
        }
      }
      parser::AttrExpr(ref parent, ref prop, ref access) => {
        let prop = match *access {
          parser::Computed => match prop.resolve_data(ctx) {
            Ok(data::Str(s)) => s,
            Ok(_) => return Err(WrongType),
            Err(e) => return Err(e)
          },
          parser::Static => match *prop {
            box parser::IdentExpr(ref s) => s.clone(),
            _ => return Err(WrongType)
          }
        };

        match parent.resolve(ctx) {
          Ok(Entry(parser::Entity(_, _, _, ref attrs))) => {
            for &parser::Attr(ref id, ref value, _) in attrs.iter() {
              if id.as_slice() == prop.as_slice() {
                return value.resolve(ctx)
              }
            }
            Err(MissingAttr)
          },
          Ok(_) => Err(WrongType),
          Err(e) => Err(e)
        }
      }
      ref e => fail!("{} not yet implemented", e)
    }
  }
}

#[cfg(test)]
mod tests {
  use super::{compile, Resolve, ResolveContext};
  use data::{Str, Null};

  #[test]
  fn test_compile() {
    let map = compile("<hi 'hello world'>").unwrap();
    let entity = &map[String::from_str("hi")];
    let data = Null;
    let ctx = ResolveContext::new(&map, &data);

    assert_eq!(entity.resolve_data(&ctx).unwrap(), Str(String::from_str("hello world")));

  }
}
<|endoftext|>


<commit_before>use std::vec::Vec;
use std::string::String;

struct Compiler {
    pub lines: Vec<String>,
}

impl Compiler {
    pub fn new() -> Self {
        Compiler { lines: Vec::new() }
    }

    pub fn parse(&mut self, assembly: &str) {
        let mut line = String::new();
        let mut chars = assembly.chars();

        while let Some(c) = chars.next() {
            match c {
                // Line Feed
                // Vertical Tab
                // Form Feed
                '\u{000A}' | '\u{000B}' | '\u{000C}' => {
                    self.lines.push(line);
                    line = String::new();
                }

                _ => {
                    line.push(c);
                }
            }
        }

        if !line.is_empty() {
            self.lines.push(line);
        }
    }
}

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

    #[test]
    fn it_splits_lines_by_line_feed() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\n0 0 0 0");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_splits_lines_by_vertical_tab() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\x0B0 0 0 0");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_splits_lines_by_form_feed() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\x0C0 0 0 0");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_ignors_trailing_separators() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\n0 0 0 0\n");

        assert_eq!(compiler.lines.len(), 2);
    }
}
<commit_msg>Splits assembly on carriage returns.<commit_after>use std::vec::Vec;
use std::string::String;

struct Compiler {
    pub lines: Vec<String>,
}

impl Compiler {
    pub fn new() -> Self {
        Compiler { lines: Vec::new() }
    }

    pub fn parse(&mut self, assembly: &str) {
        let mut line = String::new();
        let mut chars = assembly.chars();

        while let Some(c) = chars.next() {
            match c {
                // Line Feed | Vertical Tab | Form Feed
                '\u{000A}' | '\u{000B}' | '\u{000C}' => {
                    self.lines.push(line);
                    line = String::new();
                }

                // Carriage Return
                '\u{000D}' => {
                    self.lines.push(line);
                    line = String::new();

                    // Carriage Return + Line Feed
                    match chars.next() {
                        Some(n) => {
                            if n != '\u{000A}' {
                                line.push(n);
                            }
                        }
                        None => {}
                    }
                }

                _ => {
                    line.push(c);
                }
            }
        }

        if !line.is_empty() {
            self.lines.push(line);
        }
    }
}

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

    #[test]
    fn it_splits_lines_by_line_feed() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\n0 0 0 0");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_splits_lines_by_vertical_tab() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\x0B0 0 0 0");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_splits_lines_by_form_feed() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\x0C0 0 0 0");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_splits_lines_by_carriage_return() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\r0 0 0 0");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_splits_lines_by_carriage_return_line_feed() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\r\n0 0 0 0");

        assert_eq!(compiler.lines.len(), 2);
    }

    #[test]
    fn it_ignors_trailing_separators() {
        let mut compiler = Compiler::new();
        compiler.parse("0 0 0 0\r\n0 0 0 0\r\n");

        assert_eq!(compiler.lines.len(), 2);
    }
}
<|endoftext|>


<commit_before>#[derive(Debug)]
enum Command {
    FunctionCode(FunctionCode),
    ExtendedCode(ExtendedCode),
}

#[derive(Debug)]
enum FunctionCode {
    DCode(DCode),
    GCode(GCode),
    MCode(MCode),
}

#[derive(Debug)]
enum ExtendedCode {
    FS,
    MO,
    AD,
    AM,
    SR,
    LP,
    TF,
    TA,
    TD,
}

#[derive(Debug)]
enum DCode {
    Operation,
    SelectAperture,
}

#[derive(Debug)]
enum GCode {
    InterpolationMode,
    RegionMode,
    QuadrantMode,
    Comment,
}

#[derive(Debug)]
enum MCode {
    EndOfFile,
}

#[cfg(test)]
mod test {
    use super::{Command, FunctionCode, DCode, GCode};

    #[test]
    fn test_gcode_debug() {
        let c = Command::FunctionCode(FunctionCode::GCode(GCode::Comment));
        let debug = format!("{:?}", c);
        assert_eq!(debug, "FunctionCode(GCode(Comment))");
    }

}
<commit_msg>Add GerberCode trait<commit_after>trait GerberCode {
    fn to_code(&self) -> String;
}

#[derive(Debug)]
enum Command {
    FunctionCode(FunctionCode),
    ExtendedCode(ExtendedCode),
}

#[derive(Debug)]
enum FunctionCode {
    DCode(DCode),
    GCode(GCode),
    MCode(MCode),
}

#[derive(Debug)]
enum ExtendedCode {
    FS,
    MO,
    AD,
    AM,
    SR,
    LP,
    TF,
    TA,
    TD,
}

#[derive(Debug)]
enum DCode {
    Operation,
    SelectAperture,
}

#[derive(Debug)]
enum GCode {
    InterpolationMode,
    RegionMode,
    QuadrantMode,
    Comment(String),
}

#[derive(Debug)]
enum MCode {
    EndOfFile,
}

impl GerberCode for GCode {
    fn to_code(&self) -> String {
        match self {
            &GCode::InterpolationMode => format!("TODO InterpolationMode"),
            &GCode::RegionMode => format!("TODO RegionMode"),
            &GCode::QuadrantMode => format!("TODO QuadrantMode"),
            &GCode::Comment(ref comment) => format!("G04 {} *\n", comment),
        }
    }
}

#[cfg(test)]
mod test {
    use super::{Command, FunctionCode, DCode, GCode};
    use super::GerberCode;

    #[test]
    fn test_gcode_debug() {
        let c = Command::FunctionCode(FunctionCode::GCode(GCode::Comment("test".to_string())));
        let debug = format!("{:?}", c);
        assert_eq!(debug, "FunctionCode(GCode(Comment(\"test\")))");
    }

    #[test]
    fn test_to_code() {
        let comment = GCode::Comment("testcomment".to_string());
        assert_eq!(comment.to_code(), "G04 testcomment *\n".to_string());
    }

}
<|endoftext|>


<commit_before>use asm_ops::*;
use coki_jitter::jit::{OUTPUT_OFFSET, PRINT_FUNCTION};

use std::error::Error;
use std::io::prelude::*;
use std::fs::File;
use std::path::Path;
use std::process::Command;

use std::fmt;
impl fmt::Display for Register {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

pub fn compile(ops: &Vec<AsmOp>) -> Vec<u8> {
    let mut string = format!(
r"use64
lea r8, [rip]
sub r8, 7
add r8, {}
include 'PROC64.INC'
print =  {:?}
", OUTPUT_OFFSET, PRINT_FUNCTION);
    let mut block_counter = 0;
    for op in ops {
        for _ in 0..block_counter {
            string = string + &"  ";
        }
        let temp_str = match op {
            &AsmOp::Add(ref dest, ref operand) => {
                match (dest, operand) {
                    (&AsmOperand::Memory(_), &AsmOperand::Value(_)) => {
                        format!("add {}, dword {}\n", dest, operand)
                    }
                    _ => format!("add {}, {}\n", dest, operand),
                }
            }
            &AsmOp::Sub(ref dest, ref operand) => format!("sub {}, {}\n", dest, operand),

            &AsmOp::Mul(ref dest, ref operand) => format!("imul {}, {}\n", dest, operand),
            &AsmOp::Div(_, _) => panic!(),
            &AsmOp::Mod(_, _) => panic!(),

            &AsmOp::Pop(ref dest) => format!("popq {}\n", dest),
            &AsmOp::Push(ref dest) => format!("pushq {}\n", dest),
            &AsmOp::Mov(ref dest, ref operand) => {
                match (dest, operand) {
                    (&AsmOperand::Memory(_), &AsmOperand::Value(_)) => {
                        format!("mov {}, dword {}\n", dest, operand)
                    }
                    _ => format!("mov {}, {}\n", dest, operand),
                }
            },
            &AsmOp::Label(ref name) => {
                block_counter += 1;
                format!("{}:\n", name)
            }
            &AsmOp::Loop(ref name) => {
                block_counter -= 1;
                format!("\rloopq {}\n", name)
            }
            _ => format!("{}", op),
        };
        string = string + &temp_str;
    }
    println!("\nOutput assembly is:\n{}\n", string);
    write_asm(&string);
    assemble();
    read_bytes()
}

fn write_asm(str: &String) {
    let path = Path::new("target/temp.asm");
    let display = path.display();

    let mut file = match File::create(&path) {
        Err(why) => panic!("couldn't create {}: {}", display, Error::description(&why)),
        Ok(file) => file,
    };

    match file.write_all(str.as_bytes()) {
        Err(why) => {
            panic!("couldn't write to {}: {}",
                   display,
                   Error::description(&why))
        }
        Ok(_) => println!("Successfully wrote to {}", display),
    }
}

#[cfg(windows)]
const FASM: &'static str = "./fasm.exe";

#[cfg(unix)]
const FASM: &'static str = "./fasm";

fn assemble() {
    let output = Command::new(FASM)
                     .arg("target/temp.asm")
                     .output()
                     .unwrap_or_else(|e| panic!("failed to execute process: {}", e));

    if !output.status.success() {
        println!("status: {}", output.status);
        println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
        println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
        panic!("Can't assemble!");
    } else {
        println!("Successfully assembled to temp.bin");
    }
}

pub fn read_bytes() -> Vec<u8> {
    let path = Path::new("target/temp.bin");
    // let path = Path::new("test.bin");
    let display = path.display();

    let mut file = match File::open(&path) {
        Err(why) => panic!("couldn't open {}: {}", display, Error::description(&why)),
        Ok(file) => file,
    };

    let mut contents: Vec<u8> = Vec::new();
    // Returns amount of bytes read and append the result to the buffer
    let result = file.read_to_end(&mut contents).unwrap();
    println!("Program consists of {} bytes", result);
    contents
}
<commit_msg>Removed tabs for asm code<commit_after>use asm_ops::*;
use coki_jitter::jit::{OUTPUT_OFFSET, PRINT_FUNCTION};

use std::error::Error;
use std::io::prelude::*;
use std::fs::File;
use std::path::Path;
use std::process::Command;

use std::fmt;
impl fmt::Display for Register {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

pub fn compile(ops: &Vec<AsmOp>) -> Vec<u8> {
    let mut string = format!(
r"use64
lea r8, [rip]
sub r8, 7
add r8, {}
include 'PROC64.INC'
print =  {:?}
", OUTPUT_OFFSET, PRINT_FUNCTION);
    for op in ops {
        let temp_str = match op {
            &AsmOp::Add(ref dest, ref operand) => {
                match (dest, operand) {
                    (&AsmOperand::Memory(_), &AsmOperand::Value(_)) => {
                        format!("add {}, dword {}\n", dest, operand)
                    }
                    _ => format!("add {}, {}\n", dest, operand),
                }
            }
            &AsmOp::Sub(ref dest, ref operand) => format!("sub {}, {}\n", dest, operand),

            &AsmOp::Mul(ref dest, ref operand) => format!("imul {}, {}\n", dest, operand),
            &AsmOp::Div(_, _) => panic!(),
            &AsmOp::Mod(_, _) => panic!(),

            &AsmOp::Pop(ref dest) => format!("popq {}\n", dest),
            &AsmOp::Push(ref dest) => format!("pushq {}\n", dest),
            &AsmOp::Mov(ref dest, ref operand) => {
                match (dest, operand) {
                    (&AsmOperand::Memory(_), &AsmOperand::Value(_)) => {
                        format!("mov {}, dword {}\n", dest, operand)
                    }
                    _ => format!("mov {}, {}\n", dest, operand),
                }
            },
            &AsmOp::Label(ref name) => format!("{}:\n", name),
            &AsmOp::Loop(ref name) => format!("\rloopq {}\n", name),
            _ => format!("{}", op),
        };
        string = string + &temp_str;
    }
    println!("\nOutput assembly is:\n{}\n", string);
    write_asm(&string);
    assemble();
    read_bytes()
}

fn write_asm(str: &String) {
    let path = Path::new("target/temp.asm");
    let display = path.display();

    let mut file = match File::create(&path) {
        Err(why) => panic!("couldn't create {}: {}", display, Error::description(&why)),
        Ok(file) => file,
    };

    match file.write_all(str.as_bytes()) {
        Err(why) => {
            panic!("couldn't write to {}: {}",
                   display,
                   Error::description(&why))
        }
        Ok(_) => println!("Successfully wrote to {}", display),
    }
}

#[cfg(windows)]
const FASM: &'static str = "./fasm.exe";

#[cfg(unix)]
const FASM: &'static str = "./fasm";

fn assemble() {
    let output = Command::new(FASM)
                     .arg("target/temp.asm")
                     .output()
                     .unwrap_or_else(|e| panic!("failed to execute process: {}", e));

    if !output.status.success() {
        println!("status: {}", output.status);
        println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
        println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
        panic!("Can't assemble!");
    } else {
        println!("Successfully assembled to temp.bin");
    }
}

pub fn read_bytes() -> Vec<u8> {
    let path = Path::new("target/temp.bin");
    // let path = Path::new("test.bin");
    let display = path.display();

    let mut file = match File::open(&path) {
        Err(why) => panic!("couldn't open {}: {}", display, Error::description(&why)),
        Ok(file) => file,
    };

    let mut contents: Vec<u8> = Vec::new();
    // Returns amount of bytes read and append the result to the buffer
    let result = file.read_to_end(&mut contents).unwrap();
    println!("Program consists of {} bytes", result);
    contents
}
<|endoftext|>