<commit_before>//! Implements the Git trait by shelling out to git.

use anyhow::{bail, Context, Result};
use std::{
    borrow::Cow,
    ffi,
    fmt::Write,
    io::{self, BufRead, Read},
    path, process, thread,
};

#[cfg(test)]
use std::collections::HashMap;

use super::{Branch, Git};

/// Shell provides access to the git CLI.
pub struct Shell<'a> {
    path: Option<&'a str>,

    /// envs is only available during tests and provides environment variable
    /// overrides.
    #[cfg(test)]
    envs: HashMap<ffi::OsString, ffi::OsString>,
}

impl<'a> Shell<'a> {
    /// Builds a new Shell, searching `$PATH` for a git executable.
    pub fn new() -> Self {
        Self {
            path: None,

            #[cfg(test)]
            envs: HashMap::new(),
        }
    }

    /// Adds an environment variable to be set for git invocations.
    #[cfg(test)]
    pub fn env<K, V>(&mut self, k: K, v: V) -> &mut Self
    where
        K: AsRef<ffi::OsStr>,
        V: AsRef<ffi::OsStr>,
    {
        self.envs
            .insert(k.as_ref().to_os_string(), v.as_ref().to_os_string());
        self
    }

    /// Builds a `process::Command` for internal use.
    #[allow(clippy::let_and_return, unused_mut)] // used in test
    fn cmd(&self) -> process::Command {
        let mut cmd = process::Command::new(self.path.unwrap_or("git"));
        #[cfg(test)]
        {
            cmd.envs(&self.envs);
        }

        cmd
    }
}

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

impl<'a> Git for Shell<'a> {
    fn set_global_config_str<K, V>(&self, k: K, v: V) -> Result<()>
    where
        K: AsRef<ffi::OsStr>,
        V: AsRef<ffi::OsStr>,
    {
        run_cmd(self.cmd().args(&["config", "--global"]).arg(k).arg(v))
    }

    /// git_dir reports the path to the .git directory for the provided directory.
    fn git_dir(&self, dir: &path::Path) -> Result<path::PathBuf> {
        let cmd_out = run_cmd_stdout(
            self.cmd()
                .args(&["rev-parse", "--git-dir"])
                .current_dir(dir),
        )?;

        let mut cmd_out =
            String::from_utf8(cmd_out).context("output of git rev-parse is not valid UTF-8")?;
        cmd_out.truncate(cmd_out.trim_end().len());

        let mut git_dir = path::PathBuf::from(cmd_out);
        if git_dir.is_relative() {
            git_dir = dir.join(git_dir);
        }

        Ok(git_dir)
    }

    fn list_branches(&self, dir: &path::Path) -> Result<Vec<Branch>> {
        let mut cmd = self.cmd();
        cmd.args(&["show-ref", "--heads", "--abbrev"])
            .current_dir(dir)
            .stderr(process::Stdio::piped())
            .stdout(process::Stdio::piped());
        let mut child = cmd
            .spawn()
            .with_context(|| format!("unable to run {}", cmd_desc(&cmd)))?;

        let stderr_thread = {
            let mut stderr_reader = child.stderr.take().unwrap();

            thread::spawn(move || -> Result<Vec<u8>> {
                let mut output = Vec::new();
                stderr_reader
                    .read_to_end(&mut output)
                    .context("error reading stderr")?;

                Ok(output)
            })
        };

        let mut branches: Vec<Branch> = Vec::new();
        {
            let stdout = child.stdout.take().unwrap();
            let rdr = io::BufReader::new(stdout);
            for line in rdr.lines() {
                let line = line.context("read 'git show-ref' output")?;
                let mut parts = line.split(' ');
                || -> Option<()> {
                    let hash = parts.next()?;
                    let refname = parts.next()?;
                    let name = refname.strip_prefix("refs/heads/")?;
                    branches.push(Branch {
                        name: name.to_string(),
                        shorthash: hash.to_string(),
                    });

                    Some(())
                }();
            }
        }

        let child_result = child.wait();
        let stderr = stderr_thread
            .join()
            .unwrap() // TODO: handle thread panic
            .map(|v| String::from_utf8(v).ok())
            .unwrap_or(None);

        // TODO: dedupe
        match child_result {
            Ok(status) => {
                if !status.success() {
                    let mut errmsg = format!("command {} failed: {}", cmd_desc(&cmd), status);
                    if let Some(stderr) = stderr {
                        if !stderr.is_empty() {
                            write!(&mut errmsg, "\nstderr: {}", &stderr)?;
                        }
                    }
                    bail!(errmsg);
                }
            }
            Err(err) => {
                let mut errmsg = format!("{} failed: {}", cmd_desc(&cmd), err);
                if let Some(stderr) = stderr {
                    if !stderr.is_empty() {
                        write!(&mut errmsg, "\nstderr: {}", &stderr)?;
                    }
                }
                bail!(errmsg);
            }
        };

        Ok(branches)
    }
}

/// Runs the given command without capturing its output,
/// and reports a meaningful error if it fails with a non-zero status code.
fn run_cmd(cmd: &mut process::Command) -> Result<()> {
    let status = cmd
        .status()
        .with_context(|| format!("run {}", cmd_desc(cmd)))?;
    if !status.success() {
        bail!("{} failed: {}", cmd_desc(cmd), status);
    }

    Ok(())
}

/// Runs the given command and captures its output.
/// Reports a meaningful error if the command fails with a non-zero status code,
/// or if reading its output failed.
fn run_cmd_stdout(cmd: &mut process::Command) -> Result<Vec<u8>> {
    let out = cmd
        .output()
        .with_context(|| format!("run {}", cmd_desc(cmd)))?;

    if !out.status.success() {
        let mut errmsg = format!("{} failed: {}", cmd_desc(cmd), out.status);
        if let Ok(stderr) = std::str::from_utf8(&out.stderr) {
            write!(&mut errmsg, "\nstderr: {}", stderr)?;
        }

        bail!(errmsg);
    }

    Ok(out.stdout)
}

/// Generates a meaningful description of a command.
fn cmd_desc(cmd: &process::Command) -> Cow<str> {
    let prog = cmd.get_program().to_string_lossy();
    let subcmd = cmd
        .get_args()
        .map(ffi::OsStr::to_string_lossy)
        .find(|s| !s.starts_with('-'));
    match subcmd {
        Some(subcmd) => Cow::Owned(format!("{} {}", prog, subcmd)),
        None => prog,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::fixscript;

    #[test]
    fn git_dir() -> Result<()> {
        let fixture = fixscript::open("empty_repo_single_commit.sh")?;

        let shell = Shell::new();
        let git_dir = shell.git_dir(fixture.dir())?;

        assert_eq!(git_dir, fixture.dir().join(".git"));
        Ok(())
    }

    #[test]
    fn git_dir_not_a_repository() -> Result<()> {
        let tempdir = tempfile::tempdir()?;
        let dir = tempdir.path();

        let shell = Shell::new();
        let err = shell.git_dir(dir).unwrap_err();

        assert!(
            format!("{}", err).contains("not a git repository"),
            "got error: {}",
            err
        );

        Ok(())
    }

    #[test]
    fn set_global_config_str() -> Result<()> {
        let workdir = tempfile::tempdir()?;
        let homedir = tempfile::tempdir()?;
        let home = homedir.path();

        let mut shell = Shell::new();
        shell.env("HOME", &home);

        shell.set_global_config_str("user.name", "Test User")?;

        let out = process::Command::new("git")
            .args(&["config", "user.name"])
            .env("HOME", &home)
            .current_dir(workdir.path())
            .output()?;
        assert!(
            out.status.success(),
            "failed to get git config: {}",
            out.status
        );

        assert_eq!(
            std::str::from_utf8(&out.stdout).unwrap().trim(),
            "Test User",
        );

        Ok(())
    }

    #[test]
    fn list_branches_empty_repo() -> Result<()> {
        let fixture = fixscript::open("empty_repo_no_commits.sh")?;

        let shell = Shell::new();
        let res = shell.list_branches(fixture.dir());

        assert!(res.is_err(), "expected error, got {:?}", res.unwrap());

        Ok(())
    }

    #[test]
    fn list_branches_single() -> Result<()> {
        let fixture = fixscript::open("empty_repo_single_commit.sh")?;

        let shell = Shell::new();
        let branches = shell.list_branches(fixture.dir())?;
        assert!(
            branches.len() == 1,
            "expected a single item, got {:?}",
            branches
        );

        let branch = &branches[0];
        assert_eq!(branch.name, "main");
        assert!(!branch.shorthash.is_empty(), "hash should not be empty");

        Ok(())
    }

    #[test]
    fn list_branches_not_a_repository() -> Result<()> {
        let tempdir = tempfile::tempdir()?;
        let dir = tempdir.path();

        let shell = Shell::new();
        let err = shell.list_branches(dir).unwrap_err();

        assert!(
            format!("{}", err).contains("not a git repository"),
            "got error: {}",
            err
        );

        Ok(())
    }
}
<commit_msg>git/shell: Drop unused path, use Default<commit_after>//! Implements the Git trait by shelling out to git.

use anyhow::{bail, Context, Result};
use std::{
    borrow::Cow,
    ffi,
    fmt::Write,
    io::{self, BufRead, Read},
    path, process, thread,
};

#[cfg(test)]
use std::collections::HashMap;

use super::{Branch, Git};

/// Shell provides access to the git CLI.
#[derive(Default)]
pub struct Shell {
    /// envs is only available during tests and provides environment variable
    /// overrides.
    #[cfg(test)]
    envs: HashMap<ffi::OsString, ffi::OsString>,
}

impl Shell {
    /// Builds a new Shell, searching `$PATH` for a git executable.
    pub fn new() -> Self {
        Default::default()
    }

    /// Adds an environment variable to be set for git invocations.
    #[cfg(test)]
    pub fn env<K, V>(&mut self, k: K, v: V) -> &mut Self
    where
        K: AsRef<ffi::OsStr>,
        V: AsRef<ffi::OsStr>,
    {
        self.envs
            .insert(k.as_ref().to_os_string(), v.as_ref().to_os_string());
        self
    }

    /// Builds a `process::Command` for internal use.
    #[allow(clippy::let_and_return, unused_mut)] // used in test
    fn cmd(&self) -> process::Command {
        let mut cmd = process::Command::new("git");
        #[cfg(test)]
        {
            cmd.envs(&self.envs);
        }

        cmd
    }
}

impl Git for Shell {
    fn set_global_config_str<K, V>(&self, k: K, v: V) -> Result<()>
    where
        K: AsRef<ffi::OsStr>,
        V: AsRef<ffi::OsStr>,
    {
        run_cmd(self.cmd().args(&["config", "--global"]).arg(k).arg(v))
    }

    /// git_dir reports the path to the .git directory for the provided directory.
    fn git_dir(&self, dir: &path::Path) -> Result<path::PathBuf> {
        let cmd_out = run_cmd_stdout(
            self.cmd()
                .args(&["rev-parse", "--git-dir"])
                .current_dir(dir),
        )?;

        let mut cmd_out =
            String::from_utf8(cmd_out).context("output of git rev-parse is not valid UTF-8")?;
        cmd_out.truncate(cmd_out.trim_end().len());

        let mut git_dir = path::PathBuf::from(cmd_out);
        if git_dir.is_relative() {
            git_dir = dir.join(git_dir);
        }

        Ok(git_dir)
    }

    fn list_branches(&self, dir: &path::Path) -> Result<Vec<Branch>> {
        let mut cmd = self.cmd();
        cmd.args(&["show-ref", "--heads", "--abbrev"])
            .current_dir(dir)
            .stderr(process::Stdio::piped())
            .stdout(process::Stdio::piped());
        let mut child = cmd
            .spawn()
            .with_context(|| format!("unable to run {}", cmd_desc(&cmd)))?;

        let stderr_thread = {
            let mut stderr_reader = child.stderr.take().unwrap();

            thread::spawn(move || -> Result<Vec<u8>> {
                let mut output = Vec::new();
                stderr_reader
                    .read_to_end(&mut output)
                    .context("error reading stderr")?;

                Ok(output)
            })
        };

        let mut branches: Vec<Branch> = Vec::new();
        {
            let stdout = child.stdout.take().unwrap();
            let rdr = io::BufReader::new(stdout);
            for line in rdr.lines() {
                let line = line.context("read 'git show-ref' output")?;
                let mut parts = line.split(' ');
                || -> Option<()> {
                    let hash = parts.next()?;
                    let refname = parts.next()?;
                    let name = refname.strip_prefix("refs/heads/")?;
                    branches.push(Branch {
                        name: name.to_string(),
                        shorthash: hash.to_string(),
                    });

                    Some(())
                }();
            }
        }

        let child_result = child.wait();
        let stderr = stderr_thread
            .join()
            .unwrap() // TODO: handle thread panic
            .map(|v| String::from_utf8(v).ok())
            .unwrap_or(None);

        // TODO: dedupe
        match child_result {
            Ok(status) => {
                if !status.success() {
                    let mut errmsg = format!("command {} failed: {}", cmd_desc(&cmd), status);
                    if let Some(stderr) = stderr {
                        if !stderr.is_empty() {
                            write!(&mut errmsg, "\nstderr: {}", &stderr)?;
                        }
                    }
                    bail!(errmsg);
                }
            }
            Err(err) => {
                let mut errmsg = format!("{} failed: {}", cmd_desc(&cmd), err);
                if let Some(stderr) = stderr {
                    if !stderr.is_empty() {
                        write!(&mut errmsg, "\nstderr: {}", &stderr)?;
                    }
                }
                bail!(errmsg);
            }
        };

        Ok(branches)
    }
}

/// Runs the given command without capturing its output,
/// and reports a meaningful error if it fails with a non-zero status code.
fn run_cmd(cmd: &mut process::Command) -> Result<()> {
    let status = cmd
        .status()
        .with_context(|| format!("run {}", cmd_desc(cmd)))?;
    if !status.success() {
        bail!("{} failed: {}", cmd_desc(cmd), status);
    }

    Ok(())
}

/// Runs the given command and captures its output.
/// Reports a meaningful error if the command fails with a non-zero status code,
/// or if reading its output failed.
fn run_cmd_stdout(cmd: &mut process::Command) -> Result<Vec<u8>> {
    let out = cmd
        .output()
        .with_context(|| format!("run {}", cmd_desc(cmd)))?;

    if !out.status.success() {
        let mut errmsg = format!("{} failed: {}", cmd_desc(cmd), out.status);
        if let Ok(stderr) = std::str::from_utf8(&out.stderr) {
            write!(&mut errmsg, "\nstderr: {}", stderr)?;
        }

        bail!(errmsg);
    }

    Ok(out.stdout)
}

/// Generates a meaningful description of a command.
fn cmd_desc(cmd: &process::Command) -> Cow<str> {
    let prog = cmd.get_program().to_string_lossy();
    let subcmd = cmd
        .get_args()
        .map(ffi::OsStr::to_string_lossy)
        .find(|s| !s.starts_with('-'));
    match subcmd {
        Some(subcmd) => Cow::Owned(format!("{} {}", prog, subcmd)),
        None => prog,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::fixscript;

    #[test]
    fn git_dir() -> Result<()> {
        let fixture = fixscript::open("empty_repo_single_commit.sh")?;

        let shell = Shell::new();
        let git_dir = shell.git_dir(fixture.dir())?;

        assert_eq!(git_dir, fixture.dir().join(".git"));
        Ok(())
    }

    #[test]
    fn git_dir_not_a_repository() -> Result<()> {
        let tempdir = tempfile::tempdir()?;
        let dir = tempdir.path();

        let shell = Shell::new();
        let err = shell.git_dir(dir).unwrap_err();

        assert!(
            format!("{}", err).contains("not a git repository"),
            "got error: {}",
            err
        );

        Ok(())
    }

    #[test]
    fn set_global_config_str() -> Result<()> {
        let workdir = tempfile::tempdir()?;
        let homedir = tempfile::tempdir()?;
        let home = homedir.path();

        let mut shell = Shell::new();
        shell.env("HOME", &home);

        shell.set_global_config_str("user.name", "Test User")?;

        let out = process::Command::new("git")
            .args(&["config", "user.name"])
            .env("HOME", &home)
            .current_dir(workdir.path())
            .output()?;
        assert!(
            out.status.success(),
            "failed to get git config: {}",
            out.status
        );

        assert_eq!(
            std::str::from_utf8(&out.stdout).unwrap().trim(),
            "Test User",
        );

        Ok(())
    }

    #[test]
    fn list_branches_empty_repo() -> Result<()> {
        let fixture = fixscript::open("empty_repo_no_commits.sh")?;

        let shell = Shell::new();
        let res = shell.list_branches(fixture.dir());

        assert!(res.is_err(), "expected error, got {:?}", res.unwrap());

        Ok(())
    }

    #[test]
    fn list_branches_single() -> Result<()> {
        let fixture = fixscript::open("empty_repo_single_commit.sh")?;

        let shell = Shell::new();
        let branches = shell.list_branches(fixture.dir())?;
        assert!(
            branches.len() == 1,
            "expected a single item, got {:?}",
            branches
        );

        let branch = &branches[0];
        assert_eq!(branch.name, "main");
        assert!(!branch.shorthash.is_empty(), "hash should not be empty");

        Ok(())
    }

    #[test]
    fn list_branches_not_a_repository() -> Result<()> {
        let tempdir = tempfile::tempdir()?;
        let dir = tempdir.path();

        let shell = Shell::new();
        let err = shell.list_branches(dir).unwrap_err();

        assert!(
            format!("{}", err).contains("not a git repository"),
            "got error: {}",
            err
        );

        Ok(())
    }
}
<|endoftext|>


<commit_before>use std::rc::Rc;

use gdk::{EventMask, EventMotion, EventScroll};
use gtk::prelude::*;
use gtk::DrawingArea;

use sounding_base::{DataRow, Sounding};

use app::{config, AppContext, AppContextPointer};
use coords::{convert_pressure_to_y, DeviceCoords, PPCoords, ScreenCoords, XYCoords};
use gui::{Drawable, LegendBox, SampleReadout, SlaveProfileDrawable};
use gui::plot_context::{GenericContext, HasGenericContext, PlotContext, PlotContextExt};
use gui::utility::{plot_curve_from_points, DrawingArgs};

pub struct CloudContext {
    generic: GenericContext,
}

impl CloudContext {
    pub fn new() -> Self {
        CloudContext {
            generic: GenericContext::new(),
        }
    }

    pub fn convert_pp_to_xy(coords: PPCoords) -> XYCoords {
        let y = convert_pressure_to_y(coords.press);

        // The + sign below looks weird, but is correct.
        let x = coords.pcnt;

        XYCoords { x, y }
    }

    pub fn convert_xy_to_pp(coords: XYCoords) -> PPCoords {
        use std::f64;

        let press = 10.0f64.powf(
            -coords.y * (f64::log10(config::MAXP) - f64::log10(config::MINP))
                + f64::log10(config::MAXP),
        );
        let pcnt = coords.x;

        PPCoords { pcnt, press }
    }

    pub fn convert_pp_to_screen(&self, coords: PPCoords) -> ScreenCoords {
        let xy = CloudContext::convert_pp_to_xy(coords);
        self.convert_xy_to_screen(xy)
    }

    pub fn convert_device_to_pp(&self, coords: DeviceCoords) -> PPCoords {
        let xy = self.convert_device_to_xy(coords);
        Self::convert_xy_to_pp(xy)
    }
}

impl HasGenericContext for CloudContext {
    fn get_generic_context(&self) -> &GenericContext {
        &self.generic
    }
}

impl PlotContextExt for CloudContext {
    fn zoom_to_envelope(&self) {}

    fn bound_view(&self) {
        let device_rect = self.get_device_rect();

        let bounds = DeviceCoords {
            col: device_rect.width,
            row: device_rect.height,
        };
        let lower_right = self.convert_device_to_xy(bounds);
        let upper_left = self.convert_device_to_xy(device_rect.upper_left);
        let height = upper_left.y - lower_right.y;

        let mut translate = self.get_translate();
        if height < 1.0 {
            if translate.y < 0.0 {
                translate.y = 0.0;
            }
            let max_y = 1.0 - height;
            if translate.y > max_y {
                translate.y = max_y;
            }
        } else {
            translate.y = -(height - 1.0) / 2.0;
        }
        self.set_translate(translate);
    }

    fn convert_xy_to_screen(&self, coords: XYCoords) -> ScreenCoords {
        // Apply translation first
        let x = coords.x;
        let y = coords.y - self.get_translate().y;

        // Apply scaling
        let x = x;
        let y = y * self.get_zoom_factor();

        ScreenCoords { x, y }
    }

    fn convert_screen_to_xy(&self, coords: ScreenCoords) -> XYCoords {
        // Unapply scaling first
        let x = coords.x;
        let y = coords.y / self.get_zoom_factor();

        // Unapply translation
        let x = x;
        let y = y + self.get_translate().y;

        XYCoords { x, y }
    }
}

impl Drawable for CloudContext {
    fn set_up_drawing_area(da: &DrawingArea, acp: &AppContextPointer) {
        da.set_hexpand(true);
        da.set_vexpand(true);

        let ac = Rc::clone(acp);
        da.connect_draw(move |_da, cr| ac.cloud.draw_callback(cr, &ac));

        let ac = Rc::clone(acp);
        da.connect_motion_notify_event(move |da, ev| ac.cloud.mouse_motion_event(da, ev, &ac));

        let ac = Rc::clone(acp);
        da.connect_leave_notify_event(move |_da, _ev| ac.cloud.leave_event(&ac));

        let ac = Rc::clone(acp);
        da.connect_key_press_event(move |_da, ev| CloudContext::key_press_event(ev, &ac));

        let ac = Rc::clone(acp);
        da.connect_configure_event(move |_da, ev| ac.cloud.configure_event(ev));

        let ac = Rc::clone(acp);
        da.connect_size_allocate(move |da, _ev| ac.cloud.size_allocate_event(da));

        da.set_can_focus(true);

        da.add_events((EventMask::SCROLL_MASK | EventMask::BUTTON_PRESS_MASK
            | EventMask::BUTTON_RELEASE_MASK
            | EventMask::POINTER_MOTION_HINT_MASK
            | EventMask::POINTER_MOTION_MASK | EventMask::LEAVE_NOTIFY_MASK
            | EventMask::KEY_PRESS_MASK)
            .bits() as i32);
    }

    fn scroll_event(&self, _event: &EventScroll, _ac: &AppContextPointer) -> Inhibit {
        Inhibit(false)
    }

    fn mouse_motion_event(
        &self,
        da: &DrawingArea,
        event: &EventMotion,
        ac: &AppContextPointer,
    ) -> Inhibit {
        da.grab_focus();

        if ac.plottable() && self.has_data() {
            let position: DeviceCoords = event.get_position().into();

            self.set_last_cursor_position(Some(position));
            let pp_position = self.convert_device_to_pp(position);
            let sample = ::sounding_analysis::linear_interpolate(
                &ac.get_sounding_for_display().unwrap(), // ac.plottable() call ensures this won't panic
                pp_position.press,
            );
            ac.set_sample(Some(sample));
            ac.mark_overlay_dirty();
            ac.update_all_gui();
        }
        Inhibit(false)
    }

    fn draw_background(&self, args: DrawingArgs) {
        self.draw_dendtritic_snow_growth_zone(args);
        draw_background_lines(args);
        self.prepare_to_make_text(args);
        self.draw_legend(args);
    }

    fn draw_data(&self, args: DrawingArgs) {
        draw_cloud_profile(args);
    }

    fn draw_overlays(&self, args: DrawingArgs) {
        if args.ac.config.borrow().show_active_readout {
            self.draw_active_sample(args);
        }
    }
}

impl SlaveProfileDrawable for CloudContext {
    fn get_master_zoom(&self, acp: &AppContextPointer) -> f64 {
        acp.skew_t.get_zoom_factor()
    }

    fn set_translate_y(&self, new_translate: XYCoords) {
        let mut translate = self.get_translate();
        translate.y = new_translate.y;
        self.set_translate(translate);
    }
}

impl SampleReadout for CloudContext {
    fn create_active_readout_text(vals: &DataRow, _snd: &Sounding) -> Vec<String> {
        let mut results = vec![];

        if let Some(cloud) = vals.cloud_fraction {
            let cld = (cloud).round();
            let line = format!("{:.0}%", cld);
            results.push(line);
        }

        results
    }
}

impl LegendBox for CloudContext {
    fn build_legend_strings(_ac: &AppContext) -> Vec<String> {
        vec!["Cloud Cover".to_owned()]
    }
}

fn draw_cloud_profile(args: DrawingArgs) {
    let (ac, cr) = (args.ac, args.cr);
    let config = ac.config.borrow();

    if let Some(sndg) = ac.get_sounding_for_display() {
        use sounding_base::Profile::{CloudFraction, Pressure};

        ac.cloud.set_has_data(true);

        let pres_data = sndg.get_profile(Pressure);
        let c_data = sndg.get_profile(CloudFraction);
        let mut profile = izip!(pres_data, c_data)
            .filter_map(|pair| {
                if let (Some(p), Some(c)) = (*pair.0, *pair.1) {
                    Some((p, c))
                } else {
                    None
                }
            })
            .filter_map(|pair| {
                let (press, pcnt) = pair;
                if press > config::MINP {
                    Some(ac.cloud.convert_pp_to_screen(PPCoords {
                        pcnt: pcnt / 100.0,
                        press,
                    }))
                } else {
                    None
                }
            });

        let line_width = config.omega_line_width; // FIXME: use another line width?
        let mut rgba = config.cloud_rgba;
        rgba.3 *= 0.75;

        cr.set_line_width(cr.device_to_user_distance(line_width, 0.0).0);
        cr.set_source_rgba(rgba.0, rgba.1, rgba.2, rgba.3);

        let mut previous: Option<ScreenCoords>;
        let mut curr: Option<ScreenCoords> = None;
        let mut next: Option<ScreenCoords> = None;
        loop {
            previous = curr;
            curr = next;
            next = profile.next();

            const XMIN: f64 = 0.0;
            let xmax: f64;
            let ymin: f64;
            let ymax: f64;
            if let (Some(p), Some(c), Some(n)) = (previous, curr, next) {
                // In the middle - most common
                xmax = c.x;
                let down = (c.y - p.y) / 2.0;
                let up = (n.y - c.y) / 2.0;
                ymin = c.y - down;
                ymax = c.y + up;
            } else if let (Some(p), Some(c), None) = (previous, curr, next) {
                // Last point
                xmax = c.x;
                let down = (c.y - p.y) / 2.0;
                let up = down;
                ymin = c.y - down;
                ymax = c.y + up;
            } else if let (None, Some(c), Some(n)) = (previous, curr, next) {
                // First point
                xmax = c.x;
                let up = (n.y - c.y) / 2.0;
                let down = up;
                ymin = c.y - down;
                ymax = c.y + up;
            } else if let (Some(_), None, None) = (previous, curr, next) {
                // Done - get out of here
                break;
            } else if let (None, None, Some(_)) = (previous, curr, next) {
                // Just getting into the loop - do nothing
                continue;
            } else if let (None, None, None) = (previous, curr, next) {
                // There is no data plot the no data and leave!
                ac.cloud.set_has_data(false);
                break;
            } else {
                // Impossible state
                unreachable!();
            }

            cr.rectangle(XMIN, ymin, xmax, ymax - ymin);
            cr.fill_preserve();
            cr.stroke();
        }
    } else {
        ac.cloud.set_has_data(false);
    }
}

fn draw_background_lines(args: DrawingArgs) {
    let (ac, cr) = (args.ac, args.cr);
    let config = ac.config.borrow();

    // Draw isobars
    if config.show_isobars {
        for pnts in config::ISOBAR_PNTS.iter() {
            let pnts = pnts.iter()
                .map(|xy_coords| ac.cloud.convert_xy_to_screen(*xy_coords));
            plot_curve_from_points(cr, config.background_line_width, config.isobar_rgba, pnts);
        }
    }
}
<commit_msg>Cloud profile background.<commit_after>use std::rc::Rc;

use gdk::{EventMask, EventMotion, EventScroll};
use gtk::prelude::*;
use gtk::DrawingArea;

use sounding_base::{DataRow, Sounding};

use app::{config, AppContext, AppContextPointer};
use coords::{convert_pressure_to_y, DeviceCoords, PPCoords, ScreenCoords, XYCoords};
use gui::{Drawable, LegendBox, SampleReadout, SlaveProfileDrawable};
use gui::plot_context::{GenericContext, HasGenericContext, PlotContext, PlotContextExt};
use gui::utility::{plot_curve_from_points, DrawingArgs};

pub struct CloudContext {
    generic: GenericContext,
}

impl CloudContext {
    pub fn new() -> Self {
        CloudContext {
            generic: GenericContext::new(),
        }
    }

    pub fn convert_pp_to_xy(coords: PPCoords) -> XYCoords {
        let y = convert_pressure_to_y(coords.press);

        // The + sign below looks weird, but is correct.
        let x = coords.pcnt;

        XYCoords { x, y }
    }

    pub fn convert_xy_to_pp(coords: XYCoords) -> PPCoords {
        use std::f64;

        let press = 10.0f64.powf(
            -coords.y * (f64::log10(config::MAXP) - f64::log10(config::MINP))
                + f64::log10(config::MAXP),
        );
        let pcnt = coords.x;

        PPCoords { pcnt, press }
    }

    pub fn convert_pp_to_screen(&self, coords: PPCoords) -> ScreenCoords {
        let xy = CloudContext::convert_pp_to_xy(coords);
        self.convert_xy_to_screen(xy)
    }

    pub fn convert_device_to_pp(&self, coords: DeviceCoords) -> PPCoords {
        let xy = self.convert_device_to_xy(coords);
        Self::convert_xy_to_pp(xy)
    }
}

impl HasGenericContext for CloudContext {
    fn get_generic_context(&self) -> &GenericContext {
        &self.generic
    }
}

impl PlotContextExt for CloudContext {
    fn zoom_to_envelope(&self) {}

    fn bound_view(&self) {
        let device_rect = self.get_device_rect();

        let bounds = DeviceCoords {
            col: device_rect.width,
            row: device_rect.height,
        };
        let lower_right = self.convert_device_to_xy(bounds);
        let upper_left = self.convert_device_to_xy(device_rect.upper_left);
        let height = upper_left.y - lower_right.y;

        let mut translate = self.get_translate();
        if height < 1.0 {
            if translate.y < 0.0 {
                translate.y = 0.0;
            }
            let max_y = 1.0 - height;
            if translate.y > max_y {
                translate.y = max_y;
            }
        } else {
            translate.y = -(height - 1.0) / 2.0;
        }
        self.set_translate(translate);
    }

    fn convert_xy_to_screen(&self, coords: XYCoords) -> ScreenCoords {
        // Apply translation first
        let x = coords.x;
        let y = coords.y - self.get_translate().y;

        // Apply scaling
        let x = x;
        let y = y * self.get_zoom_factor();

        ScreenCoords { x, y }
    }

    fn convert_screen_to_xy(&self, coords: ScreenCoords) -> XYCoords {
        // Unapply scaling first
        let x = coords.x;
        let y = coords.y / self.get_zoom_factor();

        // Unapply translation
        let x = x;
        let y = y + self.get_translate().y;

        XYCoords { x, y }
    }
}

impl Drawable for CloudContext {
    fn set_up_drawing_area(da: &DrawingArea, acp: &AppContextPointer) {
        da.set_hexpand(true);
        da.set_vexpand(true);

        let ac = Rc::clone(acp);
        da.connect_draw(move |_da, cr| ac.cloud.draw_callback(cr, &ac));

        let ac = Rc::clone(acp);
        da.connect_motion_notify_event(move |da, ev| ac.cloud.mouse_motion_event(da, ev, &ac));

        let ac = Rc::clone(acp);
        da.connect_leave_notify_event(move |_da, _ev| ac.cloud.leave_event(&ac));

        let ac = Rc::clone(acp);
        da.connect_key_press_event(move |_da, ev| CloudContext::key_press_event(ev, &ac));

        let ac = Rc::clone(acp);
        da.connect_configure_event(move |_da, ev| ac.cloud.configure_event(ev));

        let ac = Rc::clone(acp);
        da.connect_size_allocate(move |da, _ev| ac.cloud.size_allocate_event(da));

        da.set_can_focus(true);

        da.add_events((EventMask::SCROLL_MASK | EventMask::BUTTON_PRESS_MASK
            | EventMask::BUTTON_RELEASE_MASK
            | EventMask::POINTER_MOTION_HINT_MASK
            | EventMask::POINTER_MOTION_MASK | EventMask::LEAVE_NOTIFY_MASK
            | EventMask::KEY_PRESS_MASK)
            .bits() as i32);
    }

    fn scroll_event(&self, _event: &EventScroll, _ac: &AppContextPointer) -> Inhibit {
        Inhibit(false)
    }

    fn mouse_motion_event(
        &self,
        da: &DrawingArea,
        event: &EventMotion,
        ac: &AppContextPointer,
    ) -> Inhibit {
        da.grab_focus();

        if ac.plottable() && self.has_data() {
            let position: DeviceCoords = event.get_position().into();

            self.set_last_cursor_position(Some(position));
            let pp_position = self.convert_device_to_pp(position);
            let sample = ::sounding_analysis::linear_interpolate(
                &ac.get_sounding_for_display().unwrap(), // ac.plottable() call ensures this won't panic
                pp_position.press,
            );
            ac.set_sample(Some(sample));
            ac.mark_overlay_dirty();
            ac.update_all_gui();
        }
        Inhibit(false)
    }

    fn draw_background(&self, args: DrawingArgs) {
        draw_background_fill(args);
        draw_background_lines(args);
        self.prepare_to_make_text(args);
        self.draw_legend(args);
    }

    fn draw_data(&self, args: DrawingArgs) {
        draw_cloud_profile(args);
    }

    fn draw_overlays(&self, args: DrawingArgs) {
        if args.ac.config.borrow().show_active_readout {
            self.draw_active_sample(args);
        }
    }
}

impl SlaveProfileDrawable for CloudContext {
    fn get_master_zoom(&self, acp: &AppContextPointer) -> f64 {
        acp.skew_t.get_zoom_factor()
    }

    fn set_translate_y(&self, new_translate: XYCoords) {
        let mut translate = self.get_translate();
        translate.y = new_translate.y;
        self.set_translate(translate);
    }
}

impl SampleReadout for CloudContext {
    fn create_active_readout_text(vals: &DataRow, _snd: &Sounding) -> Vec<String> {
        let mut results = vec![];

        if let Some(cloud) = vals.cloud_fraction {
            let cld = (cloud).round();
            let line = format!("{:.0}%", cld);
            results.push(line);
        }

        results
    }
}

impl LegendBox for CloudContext {
    fn build_legend_strings(_ac: &AppContext) -> Vec<String> {
        vec!["Cloud Cover".to_owned()]
    }
}

fn draw_cloud_profile(args: DrawingArgs) {
    let (ac, cr) = (args.ac, args.cr);
    let config = ac.config.borrow();

    if let Some(sndg) = ac.get_sounding_for_display() {
        use sounding_base::Profile::{CloudFraction, Pressure};

        ac.cloud.set_has_data(true);

        let pres_data = sndg.get_profile(Pressure);
        let c_data = sndg.get_profile(CloudFraction);
        let mut profile = izip!(pres_data, c_data)
            .filter_map(|pair| {
                if let (Some(p), Some(c)) = (*pair.0, *pair.1) {
                    Some((p, c))
                } else {
                    None
                }
            })
            .filter_map(|pair| {
                let (press, pcnt) = pair;
                if press > config::MINP {
                    Some(ac.cloud.convert_pp_to_screen(PPCoords {
                        pcnt: pcnt / 100.0,
                        press,
                    }))
                } else {
                    None
                }
            });

        let line_width = config.omega_line_width; // FIXME: use another line width?
        let mut rgba = config.cloud_rgba;
        rgba.3 *= 0.75;

        cr.set_line_width(cr.device_to_user_distance(line_width, 0.0).0);
        cr.set_source_rgba(rgba.0, rgba.1, rgba.2, rgba.3);

        let mut previous: Option<ScreenCoords>;
        let mut curr: Option<ScreenCoords> = None;
        let mut next: Option<ScreenCoords> = None;
        loop {
            previous = curr;
            curr = next;
            next = profile.next();

            const XMIN: f64 = 0.0;
            let xmax: f64;
            let ymin: f64;
            let ymax: f64;
            if let (Some(p), Some(c), Some(n)) = (previous, curr, next) {
                // In the middle - most common
                xmax = c.x;
                let down = (c.y - p.y) / 2.0;
                let up = (n.y - c.y) / 2.0;
                ymin = c.y - down;
                ymax = c.y + up;
            } else if let (Some(p), Some(c), None) = (previous, curr, next) {
                // Last point
                xmax = c.x;
                let down = (c.y - p.y) / 2.0;
                let up = down;
                ymin = c.y - down;
                ymax = c.y + up;
            } else if let (None, Some(c), Some(n)) = (previous, curr, next) {
                // First point
                xmax = c.x;
                let up = (n.y - c.y) / 2.0;
                let down = up;
                ymin = c.y - down;
                ymax = c.y + up;
            } else if let (Some(_), None, None) = (previous, curr, next) {
                // Done - get out of here
                break;
            } else if let (None, None, Some(_)) = (previous, curr, next) {
                // Just getting into the loop - do nothing
                continue;
            } else if let (None, None, None) = (previous, curr, next) {
                // There is no data plot the no data and leave!
                ac.cloud.set_has_data(false);
                break;
            } else {
                // Impossible state
                unreachable!();
            }

            cr.rectangle(XMIN, ymin, xmax, ymax - ymin);
            cr.fill_preserve();
            cr.stroke();
        }
    } else {
        ac.cloud.set_has_data(false);
    }
}

fn draw_background_fill(args: DrawingArgs) {
    let (ac, cr, config) = (args.ac, args.cr, args.ac.config.borrow());

    let rgba = config.background_band_rgba;
    cr.set_source_rgba(rgba.0, rgba.1, rgba.2, rgba.3);

    let mut percents = [0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0,80.0,90.0,100.0].iter();
    let mut draw = true;
    let mut prev = percents.next();
    while let Some(prev_val) = prev {
        let curr = percents.next();
        if let Some(curr_val) = curr {
            if draw {
                let ll = PPCoords {
                    pcnt: *prev_val / 100.0,
                    press: config::MAXP,
                };
                let ur = PPCoords {
                    pcnt: *curr_val / 100.0,
                    press: config::MINP,
                };
                let ll = ac.cloud.convert_pp_to_screen(ll);
                let ur = ac.cloud.convert_pp_to_screen(ur);
                let ScreenCoords { x: xmin, y: ymin } = ll;
                let ScreenCoords { x: xmax, y: ymax } = ur;
                cr.rectangle(xmin, ymin, xmax - xmin, ymax - ymin);
                cr.fill();
                draw = false;
            } else {
                draw = true;
            }
        }
        prev = curr;
    }

    ac.cloud.draw_dendtritic_snow_growth_zone(args);

}

fn draw_background_lines(args: DrawingArgs) {
    let (ac, cr) = (args.ac, args.cr);
    let config = ac.config.borrow();

    // Draw isobars
    if config.show_isobars {
        for pnts in config::ISOBAR_PNTS.iter() {
            let pnts = pnts.iter()
                .map(|xy_coords| ac.cloud.convert_xy_to_screen(*xy_coords));
            plot_curve_from_points(cr, config.background_line_width, config.isobar_rgba, pnts);
        }
    }

    // Draw percent values
    let percents = [0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0,80.0,90.0,100.0];
    for percent in &percents{
        let pnts = [PPCoords{pcnt: *percent / 100.0, press: config::MINP},PPCoords{pcnt:*percent / 100.0, press:config::MAXP}];
        let pnts = pnts.iter().map(|pp_coord| ac.cloud.convert_pp_to_screen(*pp_coord));
        plot_curve_from_points(cr, config.background_line_width, config.isobar_rgba, pnts);
    }
}
<|endoftext|>


<commit_before>use std::hashmap::HashMap;
use typecheck::{Expr, Typed, Identifier, Apply, Number, Lambda, Let, identifier, apply, number, lambda, let_};
use parser::{Module, Class, Instance, Binding, DataDefinition, Constructor, TypeDeclaration,
    Pattern, ConstructorPattern, NumberPattern, IdentifierPattern, Alternative};
mod typecheck;

#[deriving(Eq)]
pub enum Instruction {
    Add,
    Sub,
    Push(int),
    PushGlobal(int),
    PushInt(int),
    Mkap,
    Eval,
    Unwind,
    Update(int),
    Pop(int),
    Slide(int),
}

enum Var {
    StackVariable(int),
    GlobalVariable(int)
}

pub struct SuperCombinator {
    arity : int,
    instructions : ~[Instruction]
}
impl SuperCombinator {
    fn new() -> SuperCombinator {
        SuperCombinator { arity : 0, instructions : ~[] }
    }
}

struct Scope<'a, T> {
    variables: HashMap<~str, T>,
    parent: Option<&'a Scope<'a, T>>
}

impl <'a, T> Scope<'a, T> {

    fn new() -> Scope<T> {
        Scope { variables : HashMap::new(), parent : None }
    }

    fn insert(&mut self, identifier : ~str, value : T) {
        self.variables.insert(identifier, value);
    }

    fn find(&'a self, identifier : &str) -> Option<&'a T> {
       match self.variables.find_equiv(&identifier) {
            Some(var) => Some(var),
            None => match self.parent {
                Some(parent) => parent.find(identifier),
                None => None
            }
       }
    }

    fn child(&'a self) -> Scope<'a, T> {
        Scope { variables : HashMap::new(), parent : Some(self) }
    }
}

struct Assembly {
    superCombinators: ~[(~str, SuperCombinator)]
}

impl Assembly {
    fn find(&self, name: &str) -> Option<Var> {
        let mut index = 0;
        for &(ref n, ref comb) in self.superCombinators.iter() {
            if name == *n {
                return Some(GlobalVariable(-1));
            }
            index += 1;
        }
        return None;
    }
}

pub struct Compiler {
    stackSize : int,
    globals : HashMap<~str, SuperCombinator>,
    variables: HashMap<~str, Var>,
    globalIndex : int
}

impl Compiler {
    pub fn new() -> Compiler {
        Compiler { stackSize : 0, globals : HashMap::new(), globalIndex : 0, variables: HashMap::new() }
    }

    pub fn compileModule(&mut self, module : &Module) {
        //TODO
        for bind in module.bindings.iter() {
            self.variables.insert(bind.name.clone(), GlobalVariable(self.globalIndex));
            self.globalIndex += 1;
            self.compileBinding(bind.arity, &bind.expression);
        }
    }
    fn compileBinding<'a>(&mut self, arity : int, expr : &Typed<Expr>) -> SuperCombinator {

        let mut comb = SuperCombinator::new();
        comb.arity = arity;
        let mut stack = CompilerNode { compiler: self, stack: Scope::new() };
        match &expr.expr {
            &Lambda(_, _) => {
                stack.compile(expr, &mut comb.instructions);
                comb.instructions.push(Update(0));
                comb.instructions.push(Pop(comb.arity));
                comb.instructions.push(Unwind);
            }
            _ => {
                stack.compile(expr, &mut comb.instructions);
                comb.instructions.push(Update(0));
                comb.instructions.push(Unwind);
            }
       }
       comb
    }

}

struct CompilerNode<'a> {
    stack: Scope<'a, Var>,
    compiler: &'a mut Compiler
}

//CompilerNode are only used locally and the destructor are just to reduce the stack size
//so this should be safe(?)
#[unsafe_destructor]
impl <'a> Drop for CompilerNode<'a> {
    fn drop(&mut self) {
        self.compiler.stackSize -= self.stack.variables.len() as int;
    }
}

impl <'a> CompilerNode<'a> {

    fn find(&'a self, identifier : &str) -> Option<&'a Var> {
       match self.stack.find(identifier) {
            Some(var) => Some(var),
            None => self.compiler.variables.find_equiv(&identifier)
        }
    }

    fn newStackVar(&mut self, identifier : ~str) {
        self.stack.insert(identifier, StackVariable(self.compiler.stackSize));
        self.compiler.stackSize += 1;
    }

    fn child(&'a self) -> CompilerNode<'a> {
        CompilerNode { compiler: self.compiler, stack : self.stack.child() }
    }

    fn compile<'a>(&mut self, expr : &Typed<Expr>, instructions : &mut ~[Instruction]) {
        match &expr.expr {
            &Identifier(ref name) => {
                match self.find(*name) {
                    None => fail!("Undefined variable " + *name),
                    Some(var) => {
                        match var {
                            &StackVariable(index) => instructions.push(Push(index)),
                            &GlobalVariable(index) => instructions.push(PushGlobal(index))
                        }
                    }
                }
            }
            &Number(num) => instructions.push(PushInt(num)),
            &Apply(ref func, ref arg) => {
                self.compile(*arg, instructions);
                self.compile(*func, instructions);
                instructions.push(Mkap);
                instructions.push(Eval);
            }
            &Lambda(ref varname, ref body) => {
                self.newStackVar(varname.clone());
                self.compile(*body, instructions);
            }
            &Let(ref bindings, ref body) => {
                for &(ref name, ref expr) in bindings.iter() {
                    self.newStackVar(name.clone());
                    self.compile(*expr, instructions);
                }
                self.compile(*body, instructions);
                instructions.push(Slide(bindings.len() as int));
            }
        }
    }
}

#[test]
fn test_add() {
    let e = apply(identifier(~"primIntAdd"), apply(number(1), number(2)));
    let mut comp = Compiler::new();
    let comb = comp.compileBinding(0, &e);

    assert!(comb.instructions == ~[PushInt(1), PushInt(2), Add]);
}
<commit_msg>The Compiler can now compile primitives<commit_after>use std::hashmap::HashMap;
use typecheck::{Expr, Typed, Identifier, Apply, Number, Lambda, Let, identifier, apply, number, lambda, let_};
use parser::{Module, Class, Instance, Binding, DataDefinition, Constructor, TypeDeclaration,
    Pattern, ConstructorPattern, NumberPattern, IdentifierPattern, Alternative};
mod typecheck;

#[deriving(Eq)]
pub enum Instruction {
    Add,
    Sub,
    Push(int),
    PushGlobal(int),
    PushInt(int),
    Mkap,
    Eval,
    Unwind,
    Update(int),
    Pop(int),
    Slide(int),
}

enum Var {
    StackVariable(int),
    GlobalVariable(int)
}

pub struct SuperCombinator {
    arity : int,
    instructions : ~[Instruction]
}
impl SuperCombinator {
    fn new() -> SuperCombinator {
        SuperCombinator { arity : 0, instructions : ~[] }
    }
}

struct Scope<'a, T> {
    variables: HashMap<~str, T>,
    parent: Option<&'a Scope<'a, T>>
}

impl <'a, T> Scope<'a, T> {

    fn new() -> Scope<T> {
        Scope { variables : HashMap::new(), parent : None }
    }

    fn insert(&mut self, identifier : ~str, value : T) {
        self.variables.insert(identifier, value);
    }

    fn find(&'a self, identifier : &str) -> Option<&'a T> {
       match self.variables.find_equiv(&identifier) {
            Some(var) => Some(var),
            None => match self.parent {
                Some(parent) => parent.find(identifier),
                None => None
            }
       }
    }

    fn child(&'a self) -> Scope<'a, T> {
        Scope { variables : HashMap::new(), parent : Some(self) }
    }
}

struct Assembly {
    superCombinators: ~[(~str, SuperCombinator)]
}

impl Assembly {
    fn find(&self, name: &str) -> Option<Var> {
        let mut index = 0;
        for &(ref n, ref comb) in self.superCombinators.iter() {
            if name == *n {
                return Some(GlobalVariable(-1));
            }
            index += 1;
        }
        return None;
    }
}

pub struct Compiler {
    stackSize : int,
    globals : HashMap<~str, SuperCombinator>,
    variables: HashMap<~str, Var>,
    globalIndex : int
}

impl Compiler {
    pub fn new() -> Compiler {
        Compiler { stackSize : 0, globals : HashMap::new(), globalIndex : 0, variables: HashMap::new() }
    }

    pub fn compileModule(&mut self, module : &Module) {
        //TODO
        for bind in module.bindings.iter() {
            self.variables.insert(bind.name.clone(), GlobalVariable(self.globalIndex));
            self.globalIndex += 1;
            self.compileBinding(bind.arity, &bind.expression);
        }
    }
    fn compileBinding<'a>(&mut self, arity : int, expr : &Typed<Expr>) -> SuperCombinator {

        let mut comb = SuperCombinator::new();
        comb.arity = arity;
        let mut stack = CompilerNode { compiler: self, stack: Scope::new() };
        match &expr.expr {
            &Lambda(_, _) => {
                stack.compile(expr, &mut comb.instructions);
                comb.instructions.push(Update(0));
                comb.instructions.push(Pop(comb.arity));
                comb.instructions.push(Unwind);
            }
            _ => {
                stack.compile(expr, &mut comb.instructions);
                comb.instructions.push(Update(0));
                comb.instructions.push(Unwind);
            }
       }
       comb
    }
    fn compileExpression(&mut self, expr: &Typed<Expr>) -> ~[Instruction] {
        let mut stack = CompilerNode { compiler: self, stack: Scope::new() };
        let mut instructions = ~[];
        stack.compile(expr, &mut instructions);
        instructions
    }

}

struct CompilerNode<'a> {
    stack: Scope<'a, Var>,
    compiler: &'a mut Compiler
}

//CompilerNode are only used locally and the destructor are just to reduce the stack size
//so this should be safe(?)
#[unsafe_destructor]
impl <'a> Drop for CompilerNode<'a> {
    fn drop(&mut self) {
        self.compiler.stackSize -= self.stack.variables.len() as int;
    }
}

impl <'a> CompilerNode<'a> {

    fn find(&'a self, identifier : &str) -> Option<&'a Var> {
       match self.stack.find(identifier) {
            Some(var) => Some(var),
            None => self.compiler.variables.find_equiv(&identifier)
        }
    }

    fn newStackVar(&mut self, identifier : ~str) {
        self.stack.insert(identifier, StackVariable(self.compiler.stackSize));
        self.compiler.stackSize += 1;
    }

    fn child(&'a self) -> CompilerNode<'a> {
        CompilerNode { compiler: self.compiler, stack : self.stack.child() }
    }

    fn compile<'a>(&mut self, expr : &Typed<Expr>, instructions : &mut ~[Instruction]) {
        match &expr.expr {
            &Identifier(ref name) => {
                match self.find(*name) {
                    None => fail!("Undefined variable " + *name),
                    Some(var) => {
                        match var {
                            &StackVariable(index) => instructions.push(Push(index)),
                            &GlobalVariable(index) => instructions.push(PushGlobal(index))
                        }
                    }
                }
            }
            &Number(num) => instructions.push(PushInt(num)),
            &Apply(ref func, ref arg) => {
                if !self.primitive(*func, *arg, instructions) {
                    self.compile(*arg, instructions);
                    self.compile(*func, instructions);
                    instructions.push(Mkap);
                    instructions.push(Eval);
                }
            }
            &Lambda(ref varname, ref body) => {
                self.newStackVar(varname.clone());
                self.compile(*body, instructions);
            }
            &Let(ref bindings, ref body) => {
                for &(ref name, ref expr) in bindings.iter() {
                    self.newStackVar(name.clone());
                    self.compile(*expr, instructions);
                }
                self.compile(*body, instructions);
                instructions.push(Slide(bindings.len() as int));
            }
        }
    }

    fn primitive(&mut self, func: &Typed<Expr>, arg: &Typed<Expr>, instructions: &mut ~[Instruction]) -> bool {
        match &func.expr {
            &Apply(ref prim_func, ref arg2) => {
                match &prim_func.expr {
                    &Identifier(ref name) => {
                        let maybeOP = match *name {
                            ~"primIntAdd" => Some(Add),
                            ~"primIntSubtract" => Some(Sub),
                            _ => None
                        };
                        match maybeOP {
                            Some(op) => {
                                self.compile(*arg2, instructions);
                                self.compile(arg, instructions);
                                instructions.push(op);
                                true
                            }
                            None => false
                        }
                    }
                    _ => false
                }
            }
            _ => false
        }
    }
}

#[test]
fn test_add() {
    let e = apply(apply(identifier(~"primIntAdd"), number(1)), number(2));
    let mut comp = Compiler::new();
    let instr = comp.compileExpression(&e);

    assert_eq!(instr, ~[PushInt(1), PushInt(2), Add]);
}
<|endoftext|>


<commit_before>//! Binary lambda calculus execution

use binary_encoding::{from_binary};
use lambda_encoding::{encode, decode};
use lambda_calculus::reduction::beta_full;
use self::Error::*;

/// An error that can occur during blc execution.
#[derive(Debug, PartialEq)]
pub enum Error {
    InvalidProgram
}

/// Executes a binary lambda calculus program, feeding it the given argument.
///
/// # Example
/// ```
/// use blc::execution::run;
///
/// let reverse = b"0001011001000110100000000001011100111110111100001011011110110000010";
///
/// assert_eq!(run(&*reverse, b"herp derp"), Ok("pred preh".into()));
/// ```
pub fn run(blc_program: &[u8], blc_argument: &[u8]) -> Result<String, Error> {
    let program = from_binary(blc_program);
    if program.is_err() { return Err(InvalidProgram) }
    let calculation = beta_full(program.unwrap().app(encode(blc_argument))); // safe

    Ok(decode(calculation))
}

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

    #[test]
    fn inflating() {
        // program code from http://www.ioccc.org/2012/tromp/inflate.Blc
        let inflate = b"01000100010001000110100000010110000000010111100100011010000000000001011001\
            11111111111011110010111100111111011111011001011111101111101101000011110011110011100111\
            00111100111100111100001011011000001000000101100000101100000010110000011011000000";
        let s_compressed = [0x1u8, 0x7a, 0x74];

        assert_eq!(run(&*inflate, &s_compressed[..]).unwrap(), "000000010111101001110100".to_owned());
    }

    #[test]
    fn deflating() {
        // program code from http://www.ioccc.org/2012/tromp/deflate.Blc
        let deflate = b"01000100011010000001011000000101011111100000000100010111000000001011111001\
            01010111111111111100000000110111000001100010111011001011000001101100001111100001111100\
            00101101110110000000010110011110000010010111111110111111101100010100100000111001110011\
            10100000011100111010000000";

        let s_blc = b"00000001011110100111010";

        assert_eq!(run(&*deflate, &s_blc[..]).unwrap().as_bytes(), [0x1u8, 0x7a, 0x74]);
    }
}
<commit_msg>rename superfluous type annotations<commit_after>//! Binary lambda calculus execution

use binary_encoding::{from_binary};
use lambda_encoding::{encode, decode};
use lambda_calculus::reduction::beta_full;
use self::Error::*;

/// An error that can occur during blc execution.
#[derive(Debug, PartialEq)]
pub enum Error {
    InvalidProgram
}

/// Executes a binary lambda calculus program, feeding it the given argument.
///
/// # Example
/// ```
/// use blc::execution::run;
///
/// let reverse = b"0001011001000110100000000001011100111110111100001011011110110000010";
///
/// assert_eq!(run(&*reverse, b"herp derp"), Ok("pred preh".into()));
/// ```
pub fn run(blc_program: &[u8], blc_argument: &[u8]) -> Result<String, Error> {
    let program = from_binary(blc_program);
    if program.is_err() { return Err(InvalidProgram) }
    let calculation = beta_full(program.unwrap().app(encode(blc_argument))); // safe

    Ok(decode(calculation))
}

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

    #[test]
    fn inflating() {
        // program code from http://www.ioccc.org/2012/tromp/inflate.Blc
        let inflate = b"01000100010001000110100000010110000000010111100100011010000000000001011001\
            11111111111011110010111100111111011111011001011111101111101101000011110011110011100111\
            00111100111100111100001011011000001000000101100000101100000010110000011011000000";
        let s_compressed = [0x1, 0x7a, 0x74];

        assert_eq!(run(&*inflate, &s_compressed[..]).unwrap(), "000000010111101001110100".to_owned());
    }

    #[test]
    fn deflating() {
        // program code from http://www.ioccc.org/2012/tromp/deflate.Blc
        let deflate = b"01000100011010000001011000000101011111100000000100010111000000001011111001\
            01010111111111111100000000110111000001100010111011001011000001101100001111100001111100\
            00101101110110000000010110011110000010010111111110111111101100010100100000111001110011\
            10100000011100111010000000";

        let s_blc = b"00000001011110100111010";

        assert_eq!(run(&*deflate, &s_blc[..]).unwrap().as_bytes(), [0x1, 0x7a, 0x74]);
    }
}
<|endoftext|>


<commit_before>//! This module contains definitions for the extension and related types.
//! Extensions are optional data frames sitting before the voxel data.
//! When present, an extender frame of 4 bytes is also present at the
//! end of the NIFTI-1 header, with the first byte set to something
//! other than 0.

use byteordered::{ByteOrdered, Endian};
use crate::error::{NiftiError, Result};
use std::io::{ErrorKind as IoErrorKind, Read};

/// Data type for the extender code.
#[derive(Debug, Default, PartialEq, Clone, Copy)]
pub struct Extender([u8; 4]);

impl Extender {
    /// Fetch the extender code from the given source, while expecting it to exist.
    #[deprecated(since = "0.8.0", note = "use `from_reader` instead")]
    pub fn from_stream<S: Read>(source: S) -> Result<Self> {
        Self::from_reader(source)
    }

    /// Fetch the extender code from the given source, while expecting it to exist.
    pub fn from_reader<S: Read>(mut source: S) -> Result<Self> {
        let mut extension = [0u8; 4];
        source.read_exact(&mut extension)?;
        Ok(extension.into())
    }

    /// Fetch the extender code from the given source, while
    /// being possible to not be available.
    /// Returns `None` if the source reaches EoF prematurely.
    /// Any other I/O error is delegated to a `NiftiError`.
    #[deprecated(since = "0.8.0", note = "use `from_reader_optional` instead")]
    pub fn from_stream_optional<S: Read>(source: S) -> Result<Option<Self>> {
        Self::from_reader_optional(source)
    }

    /// Fetch the extender code from the given source, while
    /// being possible to not be available.
    /// Returns `None` if the source reaches EoF prematurely.
    /// Any other I/O error is delegated to a `NiftiError`.
    pub fn from_reader_optional<S: Read>(mut source: S) -> Result<Option<Self>> {
        let mut extension = [0u8; 4];
        match source.read_exact(&mut extension) {
            Ok(()) => Ok(Some(extension.into())),
            Err(ref e) if e.kind() == IoErrorKind::UnexpectedEof => Ok(None),
            Err(e) => Err(NiftiError::from(e)),
        }
    }

    /// Whether extensions should exist upon this extender code.
    pub fn has_extensions(&self) -> bool {
        self.0[0] != 0
    }

    /// Get the extender's bytes
    pub fn as_bytes(&self) -> &[u8; 4] {
        &self.0
    }
}

impl From<[u8; 4]> for Extender {
    fn from(extender: [u8; 4]) -> Self {
        Extender(extender)
    }
}

/// Data type for the raw contents of an extension.
/// Users of this type have to reinterpret the data
/// to suit their needs.
#[derive(Debug, PartialEq, Clone)]
pub struct Extension {
    esize: i32,
    ecode: i32,
    edata: Vec<u8>,
}

impl Extension {
    /// Create an extension out of its main components.
    ///
    /// # Panics
    /// If `esize` does not correspond to the full size
    /// of the extension in bytes: `8 + edata.len()`
    pub fn new(esize: i32, ecode: i32, edata: Vec<u8>) -> Self {
        if esize as usize != 8 + edata.len() {
            panic!(
                "Illegal extension size: esize is {}, but full size is {}",
                esize,
                edata.len()
            );
        }

        Extension {
            esize,
            ecode,
            edata,
        }
    }

    /// Obtain the claimed extension raw size (`esize` field).
    pub fn size(&self) -> i32 {
        self.esize
    }

    /// Obtain the extension's code (`ecode` field).
    pub fn code(&self) -> i32 {
        self.ecode
    }

    /// Obtain the extension's data (`edata` field).
    pub fn data(&self) -> &Vec<u8> {
        &self.edata
    }

    /// Take the extension's raw data, discarding the rest.
    pub fn into_data(self) -> Vec<u8> {
        self.edata
    }
}

/// Data type for aggregating the extender code and
/// all extensions.
#[derive(Debug, PartialEq, Clone)]
pub struct ExtensionSequence {
    extender: Extender,
    extensions: Vec<Extension>,
}

impl IntoIterator for ExtensionSequence {
    type Item = Extension;
    type IntoIter = ::std::vec::IntoIter<Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        self.extensions.into_iter()
    }
}

impl<'a> IntoIterator for &'a ExtensionSequence {
    type Item = &'a Extension;
    type IntoIter = ::std::slice::Iter<'a, Extension>;

    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}

impl ExtensionSequence {
    /// Read a sequence of extensions from a source, up until `len` bytes.
    #[deprecated(since = "0.8.0", note = "use `from_reader` instead")]
    pub fn from_stream<S, E>(
        extender: Extender,
        source: ByteOrdered<S, E>,
        len: usize,
    ) -> Result<Self>
    where
        S: Read,
        E: Endian,
    {
        Self::from_reader(extender, source, len)
    }

    /// Read a sequence of extensions from a source, up until `len` bytes.
    pub fn from_reader<S, E>(
        extender: Extender,
        mut source: ByteOrdered<S, E>,
        len: usize,
    ) -> Result<Self>
    where
        S: Read,
        E: Endian,
    {
        let mut extensions = Vec::new();
        if extender.has_extensions() {
            let mut offset = 0;
            while offset < len {
                let esize = source.read_i32()?;
                let ecode = source.read_i32()?;

                let data_size = (esize as usize).saturating_sub(8);
                // rather than pre-allocating for the data size, this will
                // pre-allocate up to a more reliable amount and feed the
                // vector sequentially, to prevent some trivial OOM attacks
                let mut edata = Vec::with_capacity(usize::min(data_size, 1 << 25 /* 32M */));
                let nb_bytes_written = std::io::copy(&mut (&mut source).take(data_size as u64), &mut edata)?;

                if nb_bytes_written as usize != data_size {
                    return Err(NiftiError::IncompatibleLength(nb_bytes_written as usize, data_size));
                }
                if nb_bytes_written as usize != data_size {
                    return Err(NiftiError::IncompatibleLength(nb_bytes_written as usize, data_size));
                }

                extensions.push(Extension::new(i32::max(esize, 8), ecode, edata));
                offset += esize as usize;
            }
        }

        Ok(ExtensionSequence {
            extender,
            extensions,
        })
    }

    /// Obtain an iterator to the extensions.
    pub fn iter(&self) -> ::std::slice::Iter<Extension> {
        self.extensions.iter()
    }

    /// Whether the sequence of extensions is empty.
    pub fn is_empty(&self) -> bool {
        self.extensions.is_empty()
    }

    /// Obtain the number of extensions available.
    pub fn len(&self) -> usize {
        self.extensions.len()
    }

    /// Get the extender code from this extension sequence.
    pub fn extender(&self) -> Extender {
        self.extender
    }
}
<commit_msg>Remove dupe size check in ExtensionSequence<commit_after>//! This module contains definitions for the extension and related types.
//! Extensions are optional data frames sitting before the voxel data.
//! When present, an extender frame of 4 bytes is also present at the
//! end of the NIFTI-1 header, with the first byte set to something
//! other than 0.

use byteordered::{ByteOrdered, Endian};
use crate::error::{NiftiError, Result};
use std::io::{ErrorKind as IoErrorKind, Read};

/// Data type for the extender code.
#[derive(Debug, Default, PartialEq, Clone, Copy)]
pub struct Extender([u8; 4]);

impl Extender {
    /// Fetch the extender code from the given source, while expecting it to exist.
    #[deprecated(since = "0.8.0", note = "use `from_reader` instead")]
    pub fn from_stream<S: Read>(source: S) -> Result<Self> {
        Self::from_reader(source)
    }

    /// Fetch the extender code from the given source, while expecting it to exist.
    pub fn from_reader<S: Read>(mut source: S) -> Result<Self> {
        let mut extension = [0u8; 4];
        source.read_exact(&mut extension)?;
        Ok(extension.into())
    }

    /// Fetch the extender code from the given source, while
    /// being possible to not be available.
    /// Returns `None` if the source reaches EoF prematurely.
    /// Any other I/O error is delegated to a `NiftiError`.
    #[deprecated(since = "0.8.0", note = "use `from_reader_optional` instead")]
    pub fn from_stream_optional<S: Read>(source: S) -> Result<Option<Self>> {
        Self::from_reader_optional(source)
    }

    /// Fetch the extender code from the given source, while
    /// being possible to not be available.
    /// Returns `None` if the source reaches EoF prematurely.
    /// Any other I/O error is delegated to a `NiftiError`.
    pub fn from_reader_optional<S: Read>(mut source: S) -> Result<Option<Self>> {
        let mut extension = [0u8; 4];
        match source.read_exact(&mut extension) {
            Ok(()) => Ok(Some(extension.into())),
            Err(ref e) if e.kind() == IoErrorKind::UnexpectedEof => Ok(None),
            Err(e) => Err(NiftiError::from(e)),
        }
    }

    /// Whether extensions should exist upon this extender code.
    pub fn has_extensions(&self) -> bool {
        self.0[0] != 0
    }

    /// Get the extender's bytes
    pub fn as_bytes(&self) -> &[u8; 4] {
        &self.0
    }
}

impl From<[u8; 4]> for Extender {
    fn from(extender: [u8; 4]) -> Self {
        Extender(extender)
    }
}

/// Data type for the raw contents of an extension.
/// Users of this type have to reinterpret the data
/// to suit their needs.
#[derive(Debug, PartialEq, Clone)]
pub struct Extension {
    esize: i32,
    ecode: i32,
    edata: Vec<u8>,
}

impl Extension {
    /// Create an extension out of its main components.
    ///
    /// # Panics
    /// If `esize` does not correspond to the full size
    /// of the extension in bytes: `8 + edata.len()`
    pub fn new(esize: i32, ecode: i32, edata: Vec<u8>) -> Self {
        if esize as usize != 8 + edata.len() {
            panic!(
                "Illegal extension size: esize is {}, but full size is {}",
                esize,
                edata.len()
            );
        }

        Extension {
            esize,
            ecode,
            edata,
        }
    }

    /// Obtain the claimed extension raw size (`esize` field).
    pub fn size(&self) -> i32 {
        self.esize
    }

    /// Obtain the extension's code (`ecode` field).
    pub fn code(&self) -> i32 {
        self.ecode
    }

    /// Obtain the extension's data (`edata` field).
    pub fn data(&self) -> &Vec<u8> {
        &self.edata
    }

    /// Take the extension's raw data, discarding the rest.
    pub fn into_data(self) -> Vec<u8> {
        self.edata
    }
}

/// Data type for aggregating the extender code and
/// all extensions.
#[derive(Debug, PartialEq, Clone)]
pub struct ExtensionSequence {
    extender: Extender,
    extensions: Vec<Extension>,
}

impl IntoIterator for ExtensionSequence {
    type Item = Extension;
    type IntoIter = ::std::vec::IntoIter<Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        self.extensions.into_iter()
    }
}

impl<'a> IntoIterator for &'a ExtensionSequence {
    type Item = &'a Extension;
    type IntoIter = ::std::slice::Iter<'a, Extension>;

    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}

impl ExtensionSequence {
    /// Read a sequence of extensions from a source, up until `len` bytes.
    #[deprecated(since = "0.8.0", note = "use `from_reader` instead")]
    pub fn from_stream<S, E>(
        extender: Extender,
        source: ByteOrdered<S, E>,
        len: usize,
    ) -> Result<Self>
    where
        S: Read,
        E: Endian,
    {
        Self::from_reader(extender, source, len)
    }

    /// Read a sequence of extensions from a source, up until `len` bytes.
    pub fn from_reader<S, E>(
        extender: Extender,
        mut source: ByteOrdered<S, E>,
        len: usize,
    ) -> Result<Self>
    where
        S: Read,
        E: Endian,
    {
        let mut extensions = Vec::new();
        if extender.has_extensions() {
            let mut offset = 0;
            while offset < len {
                let esize = source.read_i32()?;
                let ecode = source.read_i32()?;

                let data_size = (esize as usize).saturating_sub(8);
                // rather than pre-allocating for the data size, this will
                // pre-allocate up to a more reliable amount and feed the
                // vector sequentially, to prevent some trivial OOM attacks
                let mut edata = Vec::with_capacity(usize::min(data_size, 1 << 25 /* 32M */));
                let nb_bytes_written = std::io::copy(&mut (&mut source).take(data_size as u64), &mut edata)?;

                if nb_bytes_written as usize != data_size {
                    return Err(NiftiError::IncompatibleLength(nb_bytes_written as usize, data_size));
                }

                extensions.push(Extension::new(i32::max(esize, 8), ecode, edata));
                offset += esize as usize;
            }
        }

        Ok(ExtensionSequence {
            extender,
            extensions,
        })
    }

    /// Obtain an iterator to the extensions.
    pub fn iter(&self) -> ::std::slice::Iter<Extension> {
        self.extensions.iter()
    }

    /// Whether the sequence of extensions is empty.
    pub fn is_empty(&self) -> bool {
        self.extensions.is_empty()
    }

    /// Obtain the number of extensions available.
    pub fn len(&self) -> usize {
        self.extensions.len()
    }

    /// Get the extender code from this extension sequence.
    pub fn extender(&self) -> Extender {
        self.extender
    }
}
<|endoftext|>


<commit_before>//! The `force` module
//!
//! This module will focus on internal game logic (outside of combat)
//! and building the necessary components for a smooth game experience.

pub mod startup;
pub mod shutdown;

//=========================================================================
// Base Definitions
//=========================================================================
use std::collections::HashMap;

//-------------------------------------------------------------------------
// Enums
//-------------------------------------------------------------------------
// To Do: To be expanded, fill in Class enum
#[derive(Serialize, Deserialize, Debug)]
enum Class {
	Consular(u8), //cleric
	Guardian(u8), //paladin
	Juggernaut(u8), //berserker
	Smuggler(u8), //rogue
	Operative(u8), //slayer
	BountyHunter(u8), //ranger
	Commoner, //Default
}

// To Do: To be expanded, fill in Species enum
#[derive(Serialize, Deserialize, Debug)]
enum Species {
	Human,
	Twilek,
	Zaabrak,
	Chiss,
}

// To Do: To be expanded, fill in and detail the Effect enum
// To Do: Think about how saves are incorporated into partial effects
#[derive(Serialize, Deserialize, Debug)]
pub enum Effect {
	Damage(Dice),
	Buff,
	Debuff,
}

// To Do: Think about Enums to specify status effects like Buffs/Debuffs
#[derive(Serialize, Deserialize, Debug)]
enum Saves {Fort(u8), Will(u8), Reflex(u8)}
#[derive(Serialize, Deserialize, Debug)]
enum Slot{Head, Shoulder, Body, Hand, Shoes, Enhancement, MH, OH}
#[derive(Serialize, Deserialize, Debug)]
enum Rtype{Melee, Ranged}
#[derive(Serialize, Deserialize, Debug)]
enum Dtype{Energy, Ion, Concussive, Physical}
//To Do: Implement Debuf/Serialization for Interact enum.
enum Interact {
    Personal(Box<FnMut(&Entity) -> Option<Vec<Effect>>>),
    Group(Box<FnMut(&Entity, Vec<&Entity>) -> Option<Vec<Effect>>>),
    Area(Box<FnMut(&Entity, Option<Vec<&Entity>>) -> Option<Vec<Effect>>>),
    Target(Box<FnMut(&Entity, &Entity) -> Option<Vec<Effect>>>),
}
//-------------------------------------------------------------------------
// Types
//-------------------------------------------------------------------------
//type Interactive = Box<FnMut(&Entity, Option<Vec<&Entity>>) -> Option<Effect>>;

//-------------------------------------------------------------------------
// Traits
//-------------------------------------------------------------------------

// To Do: Perhaps some `temporary` trait to indicate buffs and debuffs ticks.
// To Do: Fill out these traits to be more appropriate and constructive
// To Do: Implement Debug/Serialization effects for traits.
trait Item {
    fn appraise(&self) -> u16;
}

trait Equipment {
    fn equip(&self, person: Entity);
}

//-------------------------------------------------------------------------
// Structs
//-------------------------------------------------------------------------
// To Do: Flesh out the item types and enums with appropriate structs.
#[derive(Serialize, Deserialize, Debug)]
pub struct Dice { num_dice: u8, dice_size: u8}

#[derive(Serialize, Deserialize, Debug)]
struct Armor {
    name: String,
    value: u32,
    slot: Slot,
    armor: Option<u8>,
    effect: Option<Interact>,
}

#[derive(Serialize, Deserialize, Debug)]
struct Weapon {
    name: String,
    value: u32,
    slot: Slot,
    damage: Dice,
    threat_range: u8,
    threat_mult: u8,
    range_type: Rtype,
    damage_type: Dtype,
	effect: Option<Interact>,
}

#[derive(Serialize, Deserialize, Debug)]
struct Accessory {
	name: String,
	value: u32,
	effect: Option<Interact>,
}

#[derive(Serialize, Deserialize, Debug)]
enum Tool {
    Grenade,
    Holocron,
    Stim,
    Adrenal,
}

#[derive(Serialize, Deserialize, Debug)]
struct Loot;

#[derive(Serialize, Deserialize, Debug)]
struct Outfit {
	head: Option<Armor>,
	shoulders: Option<Armor>,
	body: Option<Armor>,
	hand: Option<Armor>,
	shoes: Option<Armor>,
	enhance: Vec<Option<Accessory>>,
	main_hand: Option<Weapon>,
	off_hand: Option<Weapon>,
}

#[derive(Serialize, Deserialize, Debug)]
struct CoreBlock {
	strength: u8,
	dexterity: u8,
	constitution: u8,
	wisdom: u8,
	intelligence: u8,
	charisma: u8,
}

#[derive(Serialize, Deserialize, Debug)]
struct DefenseBlock {
	armor: u8,
	cmd: u8,
	ref_save: u8,
	fort_save: u8,
	will_save: u8,
}

#[derive(Serialize, Deserialize, Debug, Default)]
struct AttackBlock {
	bab: u8,
	melee: u8,
	ranged: u8,
}

#[derive(Serialize, Deserialize, Debug, Default)]
struct StatBlock {
	core: CoreBlock,
	attack: AttackBlock,
	defense: DefenseBlock,
}

#[derive(Serialize, Deserialize, Debug)]
struct Trait
{
	name: String,
	description: String,
	effect: Interact
}

#[derive(Serialize, Deserialize, Debug)]
struct Ability
{
	name: String,
	description: String,
	effect: Interact
}

//To Do: Figure out how to update HP with class hit dice
//To Do: Figure out how to dynamically keep interconnected values updated
#[derive(Serialize, Deserialize, Debug)]
pub struct Entity {
	hp: u32,
	level: u8,
	class: Vec<Class>,
	species: Species,
	skills: HashMap<String, u8>,
	stats: StatBlock,
	traits: HashMap<String, Trait>,
	abilities: HashMap<String, Ability>,
	statuses: Option<Vec<Effect>>,
	inventory: Vec<Box<Item>>,
	equipped: Outfit,
}

//=========================================================================
// Impl
//=========================================================================

//-------------------------------------------------------------------------
// Trait Impls
//-------------------------------------------------------------------------
// Default
impl Default for CoreBlock {
	fn default() -> CoreBlock{
		CoreBlock { strength:10, dexterity:10, constitution:10,
			wisdom:10, intelligence:10, charisma:10}
	}
}

impl Default for DefenseBlock {
	fn default() -> DefenseBlock{
		DefenseBlock { armor: 10, cmd: 10, fort_save: 0,
            ref_save: 0, will_save: 0}
	}
}

impl Default for Outfit {
	fn default() -> Outfit {
		Outfit { head: None, shoulders: None, body: None,
			hand: None, shoes: None, enhance: vec![None],
			main_hand: None, off_hand: None,
}
	}
}
impl Default for Entity {
	fn default() -> Entity{
        let stat_block: StatBlock = Default::default();
		Entity{ hp: 0, level: 0, class: vec![Class::Commoner], species: Species::Human,
			skills: HashMap::new(), stats: stat_block, traits: HashMap::new(),
			abilities: HashMap::new(), statuses: None, inventory: Vec::new(),
			equipped: Default::default()}
	}
}

// To Do: Think about implementing a `textNode` trait of some sort for easy conversion from structs to Cursive

//-------------------------------------------------------------------------
// Method Impls
//-------------------------------------------------------------------------

//=========================================================================
// Functions
//=========================================================================
// To Do: Fill in the `drinks_served` function (aka roll macros)
pub fn drinks_served(drink: &str) {

}

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

    #[test]
    fn test_smallentity() {
        let emixan: Entity = Default::default();
    }

    #[test]
    fn test_entity() {
        let emixan = Entity {
            hp: 0, level: 1,
            class: vec![Class::Operative(1)],
            species: Species::Chiss,
            skills: HashMap::new(),
            stats: StatBlock::default(),
            traits: HashMap::new(),
            abilities: HashMap::new(),
            statuses: None,
            inventory: Vec::<Box<Item>>::new(),
            equipped: Outfit::default(),
        };
    }

    #[test]
    fn test_datasave() {
        let emixan: Entity = Default::default();
        let serial = serde_json::to_string(&emixan).unwrap();
        println!("serialized = {}", serial);

        let deserial: Entity = serde_json::from_str(&serial).unwrap();
        println!("deserialized = {:?}", deserial);
    }

    #[test]
    fn test_spellbook() {
        use super::super::jedi_code::abilities::saber_throw;
        let saber_throw_ability = Ability {
            name: String::from("Saber Throw"),
            description: String::from("Throw a saber"),
            effect: Interact::Target(Box::new(saber_throw)),
        };
        let mut spellbook = HashMap::new();
        spellbook.insert(String::from("Saber Throw"), saber_throw_ability);
    }

}
<commit_msg>Saving<commit_after>//! The `force` module
//!
//! This module will focus on internal game logic (outside of combat)
//! and building the necessary components for a smooth game experience.

pub mod startup;
pub mod shutdown;

//=========================================================================
// Base Definitions
//=========================================================================
use std::collections::HashMap;

//-------------------------------------------------------------------------
// Enums
//-------------------------------------------------------------------------
// To Do: To be expanded, fill in Class enum
#[derive(Serialize, Deserialize, Debug)]
enum Class {
	Consular(u8), //cleric
	Guardian(u8), //paladin
	Juggernaut(u8), //berserker
	Smuggler(u8), //rogue
	Operative(u8), //slayer
	BountyHunter(u8), //ranger
	Commoner,
}

// To Do: To be expanded, fill in Species enum
#[derive(Serialize, Deserialize, Debug)]
enum Species {
	Human,
	Twilek,
	Zaabrak,
	Chiss,
}

// To Do: To be expanded, fill in and detail the Effect enum
// To Do: Think about how saves are incorporated into partial effects
#[derive(Serialize, Deserialize, Debug)]
pub enum Effect {
	Damage(Dice),
	Buff,
	Debuff,
}

// To Do: Think about Enums to specify status effects like Buffs/Debuffs
#[derive(Serialize, Deserialize, Debug)]
enum Saves {Fort(u8), Will(u8), Reflex(u8)}
#[derive(Serialize, Deserialize, Debug)]
enum Slot{Head, Shoulder, Body, Hand, Shoes, Enhancement, MH, OH}
#[derive(Serialize, Deserialize, Debug)]
enum Rtype{Melee, Ranged}
#[derive(Serialize, Deserialize, Debug)]
enum Dtype{Energy, Ion, Concussive, Physical}
//To Do: Implement Debuf/Serialization for Interact enum.
enum Interact {
    Personal(Box<FnMut(&Entity) -> Option<Vec<Effect>>>),
    Group(Box<FnMut(&Entity, Vec<&Entity>) -> Option<Vec<Effect>>>),
    Area(Box<FnMut(&Entity, Option<Vec<&Entity>>) -> Option<Vec<Effect>>>),
    Target(Box<FnMut(&Entity, &Entity) -> Option<Vec<Effect>>>),
}
//-------------------------------------------------------------------------
// Types
//-------------------------------------------------------------------------
//type Interactive = Box<FnMut(&Entity, Option<Vec<&Entity>>) -> Option<Effect>>;

//-------------------------------------------------------------------------
// Traits
//-------------------------------------------------------------------------

// To Do: Perhaps some `temporary` trait to indicate buffs and debuffs ticks.
// To Do: Fill out these traits to be more appropriate and constructive
// To Do: Implement Debug/Serialization effects for traits.
trait Item {
    fn appraise(&self) -> u16;
}

trait Equipment {
    fn equip(&self, person: Entity);
}

//-------------------------------------------------------------------------
// Structs
//-------------------------------------------------------------------------
// To Do: Flesh out the item types and enums with appropriate structs.
#[derive(Serialize, Deserialize, Debug)]
pub struct Dice { num_dice: u8, dice_size: u8}

#[derive(Serialize, Deserialize, Debug)]
struct Armor {
    name: String,
    value: u32,
    slot: Slot,
    armor: Option<u8>,
    effect: Option<Interact>,
}

#[derive(Serialize, Deserialize, Debug)]
struct Weapon {
    name: String,
    value: u32,
    slot: Slot,
    damage: Dice,
    threat_range: u8,
    threat_mult: u8,
    range_type: Rtype,
    damage_type: Dtype,
	effect: Option<Interact>,
}

#[derive(Serialize, Deserialize, Debug)]
struct Accessory {
	name: String,
	value: u32,
	effect: Option<Interact>,
}

#[derive(Serialize, Deserialize, Debug)]
enum Tool {
    Grenade,
    Holocron,
    Stim,
    Adrenal,
}

#[derive(Serialize, Deserialize, Debug)]
struct Loot;

#[derive(Serialize, Deserialize, Debug)]
struct Outfit {
	head: Option<Armor>,
	shoulders: Option<Armor>,
	body: Option<Armor>,
	hand: Option<Armor>,
	shoes: Option<Armor>,
	enhance: Vec<Option<Accessory>>,
	main_hand: Option<Weapon>,
	off_hand: Option<Weapon>,
}

#[derive(Serialize, Deserialize, Debug)]
struct CoreBlock {
	strength: u8,
	dexterity: u8,
	constitution: u8,
	wisdom: u8,
	intelligence: u8,
	charisma: u8,
}

#[derive(Serialize, Deserialize, Debug)]
struct DefenseBlock {
	armor: u8,
	cmd: u8,
	ref_save: u8,
	fort_save: u8,
	will_save: u8,
}

#[derive(Serialize, Deserialize, Debug, Default)]
struct AttackBlock {
	bab: u8,
	melee: u8,
	ranged: u8,
}

#[derive(Serialize, Deserialize, Debug, Default)]
struct StatBlock {
	core: CoreBlock,
	attack: AttackBlock,
	defense: DefenseBlock,
}

#[derive(Serialize, Deserialize, Debug)]
struct Trait
{
	name: String,
	description: String,
	effect: Interact
}

#[derive(Serialize, Deserialize, Debug)]
struct Ability
{
	name: String,
	description: String,
	effect: Interact
}

//To Do: Figure out how to update HP with class hit dice
//To Do: Figure out how to dynamically keep interconnected values updated
#[derive(Serialize, Deserialize, Debug)]
pub struct Entity {
	hp: u32,
	level: u8,
	class: Vec<Class>,
	species: Species,
	skills: HashMap<String, u8>,
	stats: StatBlock,
	traits: HashMap<String, Trait>,
	abilities: HashMap<String, Ability>,
	statuses: Option<Vec<Effect>>,
	inventory: Vec<Box<Item>>,
	equipped: Outfit,
}

//=========================================================================
// Impl
//=========================================================================

//-------------------------------------------------------------------------
// Trait Impls
//-------------------------------------------------------------------------
// Default
impl Default for CoreBlock {
	fn default() -> CoreBlock{
		CoreBlock { strength:10, dexterity:10, constitution:10,
			wisdom:10, intelligence:10, charisma:10}
	}
}

impl Default for DefenseBlock {
	fn default() -> DefenseBlock{
		DefenseBlock { armor: 10, cmd: 10, fort_save: 0,
            ref_save: 0, will_save: 0}
	}
}

impl Default for Outfit {
	fn default() -> Outfit {
		Outfit { head: None, shoulders: None, body: None,
			hand: None, shoes: None, enhance: vec![None],
			main_hand: None, off_hand: None,
}
	}
}
impl Default for Entity {
	fn default() -> Entity{
        let stat_block: StatBlock = Default::default();
		Entity{ hp: 0, level: 0, class: vec![Class::Commoner], species: Species::Human,
			skills: HashMap::new(), stats: stat_block, traits: HashMap::new(),
			abilities: HashMap::new(), statuses: None, inventory: Vec::new(),
			equipped: Default::default()}
	}
}

// To Do: Think about implementing a `textNode` trait of some sort for easy conversion from structs to Cursive

//-------------------------------------------------------------------------
// Method Impls
//-------------------------------------------------------------------------

//=========================================================================
// Functions
//=========================================================================
// To Do: Fill in the `drinks_served` function (aka roll macros)
pub fn drinks_served(drink: &str) {

}

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

    #[test]
    fn test_smallentity() {
        let emixan: Entity = Default::default();
    }

    #[test]
    fn test_entity() {
        let emixan = Entity {
            hp: 0, level: 1,
            class: vec![Class::Operative(1)],
            species: Species::Chiss,
            skills: HashMap::new(),
            stats: StatBlock::default(),
            traits: HashMap::new(),
            abilities: HashMap::new(),
            statuses: None,
            inventory: Vec::<Box<Item>>::new(),
            equipped: Outfit::default(),
        };
    }

    #[test]
    fn test_datasave() {
        let emixan: Entity = Default::default();
        let serial = serde_json::to_string(&emixan).unwrap();
        println!("serialized = {}", serial);

        let deserial: Entity = serde_json::from_str(&serial).unwrap();
        println!("deserialized = {:?}", deserial);
    }

    #[test]
    fn test_spellbook() {
        use super::super::jedi_code::abilities::saber_throw;
        let saber_throw_ability = Ability {
            name: String::from("Saber Throw"),
            description: String::from("Throw a saber"),
            effect: Interact::Target(Box::new(saber_throw)),
        };
        let mut spellbook = HashMap::new();
        spellbook.insert(String::from("Saber Throw"), saber_throw_ability);
    }

}
<|endoftext|>