<commit_before>use anyhow::{Context, Result};
use log::trace;
use std::path::Path;
use std::slice::Iter;
use tokio::fs::File;
use tokio::io::AsyncBufReadExt;
use tokio::io::BufReader;

use crate::{IntoName, Name};

static DEFAULT_WORD_LIST: &str = r#"admin
alpha
api
app
assets
auth
beta
blog
chat
cms
confluence
ct
dashboard
dev
development
dialin
download
epaper
exchange
external
foto
ftp
gatekeeper
gateway
git
gitlab
gw
hq
id
in
intern
iphone
jobs
ldap
listserv
login
m
mail
mailhost
meet
mobi
mx
newsletter
ns
presse
public
radio
redirector
relay
reporting
rss
sales
sftp
shop
smtp
smtp01
smtp02
smtp1
smtp2
special
sso
staging
static
svn
test
ticket
tp
tr
update
upload
web
www
www-server
www-test
wwwtest"#;

#[derive(Debug)]
pub struct Wordlist {
    words: Vec<Name>,
}

impl Wordlist {
    pub async fn from_file<P: AsRef<Path>>(path: P) -> Result<Wordlist> {
        let file = File::open(path).await?;
        let mut buf_reader = BufReader::new(file);

        let mut words = Vec::new();
        loop {
            let mut buffer = String::new();
            let len = buf_reader.read_line(&mut buffer).await?;
            if len == 0 {
                break;
            }
            if Wordlist::is_comment(&buffer) {
                continue;
            }
            trace!("Parsing wordlist item '{}'.", buffer);
            let name: Name = buffer
                .trim_end() // BufReader::read_line returns trainling line break
                .into_name()
                .context("failed to read word list because of invalid domain name")?;
            words.push(name);
        }

        Ok(Wordlist { words })
    }

    fn is_comment(line: &str) -> bool {
        line.starts_with("//") || line.starts_with("#")
    }

    pub fn from_str(data: &str) -> Result<Wordlist> {
        let mut words = Vec::new();

        for line in data.lines() {
            if Wordlist::is_comment(&line) {
                continue;
            }
            trace!("Parsing wordlist item '{}'.", line);
            let name: Name = line
                .into_name()
                .context("failed to read word list because of invalid domain name")?;
            words.push(name);
        }

        Ok(Wordlist { words })
    }

    pub fn default() -> Result<Wordlist> {
        Wordlist::from_str(DEFAULT_WORD_LIST)
    }

    #[allow(dead_code)]
    pub fn iter(&self) -> Iter<Name> {
        self.words.iter()
    }

    #[allow(dead_code)]
    pub fn len(&self) -> usize {
        self.words.len()
    }
}

impl IntoIterator for Wordlist {
    type Item = Name;
    type IntoIter = std::vec::IntoIter<Self::Item>;

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

#[cfg(test)]
mod tests {

    use super::*;

    use spectral::prelude::*;

    #[tokio::test]
    async fn read_from_file_5000() {
        let path = "contrib/subdomains-top1mil-5000.txt";

        let wordlist = Wordlist::from_file(path).await;

        asserting("Wordlist with 5000 elements loaded from file")
            .that(&wordlist)
            .is_ok()
            .map(|x| &x.words)
            .has_length(5000)
    }

    #[tokio::test]
    async fn read_from_file_20000() {
        let path = "contrib/subdomains-top1mil-20000.txt";

        let wordlist = Wordlist::from_file(path).await;

        asserting("Wordlist with 20000 elements loaded from file")
            .that(&wordlist)
            .is_ok()
            .map(|x| &x.words)
            .has_length(19998)
    }

    #[test]
    fn read_from_string() {
        let wordlist = Wordlist::from_str(&DEFAULT_WORD_LIST);

        asserting("Wordlist with 5000 elements loaded from string")
            .that(&wordlist)
            .is_ok()
            .map(|x| &x.words)
            .has_length(5000)
    }
}
<commit_msg>Fix test after reducing internal wordlist<commit_after>use anyhow::{Context, Result};
use log::trace;
use std::path::Path;
use std::slice::Iter;
use tokio::fs::File;
use tokio::io::AsyncBufReadExt;
use tokio::io::BufReader;

use crate::{IntoName, Name};

static DEFAULT_WORD_LIST: &str = r#"admin
alpha
api
app
assets
auth
beta
blog
chat
cms
confluence
ct
dashboard
dev
development
dialin
download
epaper
exchange
external
foto
ftp
gatekeeper
gateway
git
gitlab
gw
hq
id
in
intern
iphone
jobs
ldap
listserv
login
m
mail
mailhost
meet
mobi
mx
newsletter
ns
presse
public
radio
redirector
relay
reporting
rss
sales
sftp
shop
smtp
smtp01
smtp02
smtp1
smtp2
special
sso
staging
static
svn
test
ticket
tp
tr
update
upload
web
www
www-server
www-test
wwwtest"#;

#[derive(Debug)]
pub struct Wordlist {
    words: Vec<Name>,
}

impl Wordlist {
    pub async fn from_file<P: AsRef<Path>>(path: P) -> Result<Wordlist> {
        let file = File::open(path).await?;
        let mut buf_reader = BufReader::new(file);

        let mut words = Vec::new();
        loop {
            let mut buffer = String::new();
            let len = buf_reader.read_line(&mut buffer).await?;
            if len == 0 {
                break;
            }
            if Wordlist::is_comment(&buffer) {
                continue;
            }
            trace!("Parsing wordlist item '{}'.", buffer);
            let name: Name = buffer
                .trim_end() // BufReader::read_line returns trainling line break
                .into_name()
                .context("failed to read word list because of invalid domain name")?;
            words.push(name);
        }

        Ok(Wordlist { words })
    }

    fn is_comment(line: &str) -> bool {
        line.starts_with("//") || line.starts_with("#")
    }

    pub fn from_str(data: &str) -> Result<Wordlist> {
        let mut words = Vec::new();

        for line in data.lines() {
            if Wordlist::is_comment(&line) {
                continue;
            }
            trace!("Parsing wordlist item '{}'.", line);
            let name: Name = line
                .into_name()
                .context("failed to read word list because of invalid domain name")?;
            words.push(name);
        }

        Ok(Wordlist { words })
    }

    pub fn default() -> Result<Wordlist> {
        Wordlist::from_str(DEFAULT_WORD_LIST)
    }

    #[allow(dead_code)]
    pub fn iter(&self) -> Iter<Name> {
        self.words.iter()
    }

    #[allow(dead_code)]
    pub fn len(&self) -> usize {
        self.words.len()
    }
}

impl IntoIterator for Wordlist {
    type Item = Name;
    type IntoIter = std::vec::IntoIter<Self::Item>;

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

#[cfg(test)]
mod tests {

    use super::*;

    use spectral::prelude::*;

    #[tokio::test]
    async fn read_from_file_5000() {
        let path = "contrib/subdomains-top1mil-5000.txt";

        let wordlist = Wordlist::from_file(path).await;

        asserting("Wordlist with 5000 elements loaded from file")
            .that(&wordlist)
            .is_ok()
            .map(|x| &x.words)
            .has_length(5000)
    }

    #[tokio::test]
    async fn read_from_file_20000() {
        let path = "contrib/subdomains-top1mil-20000.txt";

        let wordlist = Wordlist::from_file(path).await;

        asserting("Wordlist with 20000 elements loaded from file")
            .that(&wordlist)
            .is_ok()
            .map(|x| &x.words)
            .has_length(19998)
    }

    #[test]
    fn read_from_string() {
        let wordlist = Wordlist::from_str(&DEFAULT_WORD_LIST);

        asserting("Wordlist with 5000 elements loaded from string")
            .that(&wordlist)
            .is_ok()
            .map(|x| &x.words)
            .has_length(75)
    }
}
<|endoftext|>


<commit_before>// Implements http://rosettacode.org/wiki/99_Bottles_of_Beer
use std::string::String;

#[cfg(not(test))]
fn main() {
	for num_bottles in std::iter::range_step_inclusive(99, 1, -1) {
        let num_bottles=num_bottles as uint;

		println!("{}",bottles_line(num_bottles, true));
		println!("{}",bottles_line(num_bottles, false));
		println!("Take one down, pass it around...");
		println!("{}",bottles_line(num_bottles - 1, true));
		println!("-----------------------------------");
	}
}

fn bottles_line(num_bottles: uint, on_the_wall: bool) -> String {
	// the format! macro uses a built in internationalization formatting language
	// check out the docs for std::fmt
	let mut ln=format!("{0, plural, =0{No bottles} =1{One bottle} other{# bottles}} of beer",
                                                                                num_bottles);

	if on_the_wall {
		ln.push_str(" on the wall!");
	}

	ln.push_str("\n");
    ln
}

#[test]
fn gen_bottle_line() {
    let ln=bottles_line(42, false);
    let ln2=bottles_line(42, true);

    assert_eq!(ln.as_slice(),"42 bottles of beer\n");
    assert_eq!(ln2.as_slice(),"42 bottles of beer on the wall!\n");
}
<commit_msg>i18n plural string formatting has been removed from Rust.<commit_after>// Implements http://rosettacode.org/wiki/99_Bottles_of_Beer
use std::string::String;

#[cfg(not(test))]
fn main() {
	for num_bottles in std::iter::range_step_inclusive(99, 1, -1) {
        let num_bottles=num_bottles as uint;

		println!("{}",bottles_line(num_bottles, true));
		println!("{}",bottles_line(num_bottles, false));
		println!("Take one down, pass it around...");
		println!("{}",bottles_line(num_bottles - 1, true));
		println!("-----------------------------------");
	}
}

fn bottles_line(num_bottles: uint, on_the_wall: bool) -> String {
    let mut ln = match num_bottles {
        0 => "No bottles".to_string(),
        1 => "One bottle".to_string(),
        n => format!("{} bottles", n)
    };

    ln.push_str(" of beer");

	if on_the_wall {
		ln.push_str(" on the wall!");
	}

	ln.push_str("\n");
    ln
}

#[test]
fn gen_bottle_line() {
    let ln=bottles_line(42, false);
    let ln2=bottles_line(42, true);

    assert_eq!(ln.as_slice(),"42 bottles of beer\n");
    assert_eq!(ln2.as_slice(),"42 bottles of beer on the wall!\n");
}
<|endoftext|>


<commit_before>use std::cell::RefCell;
use std::cmp::Ordering;
use std::collections::{BTreeSet, HashMap, VecDeque};
use std::io::{self, Write};
use std::rc::Rc;

use anyhow::anyhow;
use cargo::core::compiler::{CompileKind, RustcTargetData};
use cargo::core::resolver::features::{ForceAllTargets, HasDevUnits};
use cargo::core::resolver::CliFeatures;
use cargo::core::FeatureValue;
use cargo::core::{dependency::DepKind, Dependency, Package, PackageId, Workspace};
use cargo::ops::{self, Packages};
use cargo::util::interning::InternedString;
use cargo::util::{CargoResult, Config};
use serde::{Deserialize, Serialize};
use tabwriter::TabWriter;

use crate::error::OutdatedError;

use super::pkg_status::*;
use super::Options;

/// An elaborate workspace containing resolved dependencies and
/// the update status of packages
pub struct ElaborateWorkspace<'ela> {
    pub workspace: &'ela Workspace<'ela>,
    pub pkgs: HashMap<PackageId, Package>,
    pub pkg_deps: HashMap<PackageId, HashMap<PackageId, Dependency>>,
    /// Map of package status
    pub pkg_status: RefCell<HashMap<Vec<PackageId>, PkgStatus>>,
    /// Whether using workspace mode
    pub workspace_mode: bool,
}

/// A struct to serialize to json with serde
#[derive(Serialize, Deserialize)]
pub struct CrateMetadata {
    pub crate_name: String,
    pub dependencies: BTreeSet<Metadata>,
}

#[derive(Serialize, Deserialize, Eq, PartialEq)]
pub struct Metadata {
    pub name: String,
    pub project: String,
    pub compat: String,
    pub latest: String,
    pub kind: Option<String>,
    pub platform: Option<String>,
}

impl Ord for Metadata {
    fn cmp(&self, other: &Self) -> Ordering { self.name.cmp(&other.name) }
}

impl PartialOrd for Metadata {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}

impl<'ela> ElaborateWorkspace<'ela> {
    /// Elaborate a `Workspace`
    pub fn from_workspace(
        workspace: &'ela Workspace<'_>,
        options: &Options,
    ) -> CargoResult<ElaborateWorkspace<'ela>> {
        // new in cargo 0.54.0
        let flag_features: BTreeSet<FeatureValue> = options
            .features
            .iter()
            .map(|feature| FeatureValue::new(InternedString::from(feature)))
            .collect();
        let specs = Packages::All.to_package_id_specs(workspace)?;

        let cli_features = CliFeatures {
            features: Rc::new(flag_features),
            all_features: options.all_features(),
            uses_default_features: options.no_default_features(),
        };

        //The CompileKind, this has no target since it's the temp workspace
        //targets are blank since we don't need to fully build for the targets to get the dependencies
        let compile_kind = CompileKind::from_requested_targets(workspace.config(), &[])?;
        let target_data = RustcTargetData::new(workspace, &compile_kind)?;
        let ws_resolve = ops::resolve_ws_with_opts(
            workspace,
            &target_data,
            &compile_kind,
            &cli_features,
            &specs,
            HasDevUnits::Yes,
            ForceAllTargets::Yes,
        )?;
        let packages = ws_resolve.pkg_set;
        let resolve = ws_resolve
            .workspace_resolve
            .expect("Error getting workspace resolved");
        let mut pkgs = HashMap::new();
        let mut pkg_deps = HashMap::new();
        for pkg in packages.get_many(packages.package_ids())? {
            let pkg_id = pkg.package_id();
            pkgs.insert(pkg_id, pkg.clone());
            let deps = pkg.dependencies();
            let mut dep_map = HashMap::new();
            for dep_id in resolve.deps(pkg_id) {
                for d in deps {
                    if d.matches_id(dep_id.0) {
                        dep_map.insert(dep_id.0, d.clone());
                        break;
                    }
                }
            }
            pkg_deps.insert(pkg_id, dep_map);
        }

        Ok(ElaborateWorkspace {
            workspace,
            pkgs,
            pkg_deps,
            pkg_status: RefCell::new(HashMap::new()),
            workspace_mode: options.workspace || workspace.current().is_err(),
        })
    }

    /// Determine root package based on current workspace and CLI options
    pub fn determine_root(&self, options: &Options) -> CargoResult<PackageId> {
        if let Some(ref root_name) = options.root {
            if let Ok(workspace_root) = self.workspace.current() {
                if root_name == workspace_root.name().as_str() {
                    Ok(workspace_root.package_id())
                } else {
                    for direct_dep in self.pkg_deps[&workspace_root.package_id()].keys() {
                        if self.pkgs[direct_dep].name().as_str() == root_name {
                            return Ok(*direct_dep);
                        }
                    }
                    Err(anyhow!(
                        "Root is neither the workspace root nor a direct dependency",
                    ))
                }
            } else {
                Err(anyhow!(
                    "--root is not allowed when running against a virtual manifest",
                ))
            }
        } else {
            Ok(self.workspace.current()?.package_id())
        }
    }

    /// Find a member based on member name
    fn find_member(&self, member: PackageId) -> CargoResult<PackageId> {
        for m in self.workspace.members() {
            // members with the same name in a workspace is not allowed
            // even with different paths
            if member.name() == m.name() {
                return Ok(m.package_id());
            }
        }
        Err(anyhow!("Workspace member {} not found", member.name()))
    }

    /// Find a contained package, which is a member or dependency inside the workspace
    fn find_contained_package(&self, name: &str) -> CargoResult<PackageId> {
        let root_path = self.workspace.root();
        for (pkg_id, pkg) in &self.pkgs {
            if pkg.manifest_path().starts_with(root_path) && pkg.name().as_str() == name {
                return Ok(*pkg_id);
            }
        }
        Err(anyhow!("Cannot find package {} in workspace", name))
    }

    /// Find a direct dependency of a contained package
    pub fn find_direct_dependency(
        &self,
        dependency_name: &str,
        dependent_package_name: &str,
    ) -> CargoResult<PackageId> {
        let dependent_package = self.find_contained_package(dependent_package_name)?;

        for direct_dep in self.pkg_deps[&dependent_package].keys() {
            if direct_dep.name().as_str() == dependency_name {
                return Ok(*direct_dep);
            }
        }

        for (pkg_id, pkg) in &self.pkgs {
            if pkg.name().as_str() == dependency_name {
                return Ok(*pkg_id);
            }
        }

        Err(anyhow!(
            "Direct dependency {} not found for package {}",
            dependency_name,
            dependent_package_name
        ))
    }

    /// Resolve compatible and latest status from the corresponding `ElaborateWorkspace`s
    pub fn resolve_status(
        &'ela self,
        compat: &ElaborateWorkspace<'_>,
        latest: &ElaborateWorkspace<'_>,
        options: &Options,
        _config: &Config,
        root: PackageId,
    ) -> CargoResult<()> {
        self.pkg_status.borrow_mut().clear();
        let (compat_root, latest_root) = if self.workspace_mode {
            (compat.find_member(root)?, latest.find_member(root)?)
        } else {
            (
                compat.determine_root(options)?,
                latest.determine_root(options)?,
            )
        };

        let mut queue = VecDeque::new();
        queue.push_back((vec![root], Some(compat_root), Some(latest_root)));
        while let Some((path, compat_pkg, latest_pkg)) = queue.pop_front() {
            let pkg = path.last().ok_or(OutdatedError::EmptyPath)?;
            let depth = path.len() as i32 - 1;
            // generate pkg_status
            let status = PkgStatus {
                compat: Status::from_versions(pkg.version(), compat_pkg.map(PackageId::version)),
                latest: Status::from_versions(pkg.version(), latest_pkg.map(PackageId::version)),
            };
            debug!(
                _config,
                "STATUS => PKG: {}; PATH: {:?}; COMPAT: {:?}; LATEST: {:?}; STATUS: {:?}",
                pkg,
                path,
                compat_pkg,
                latest_pkg,
                status
            );
            self.pkg_status.borrow_mut().insert(path.clone(), status);
            // next layer
            // this unwrap is safe since we first check if it is None :)
            if options.depth.is_none() || depth < options.depth.unwrap() {
                self.pkg_deps[pkg]
                    .keys()
                    .filter(|dep| !path.contains(dep))
                    .for_each(|&dep| {
                        let name = dep.name();
                        let compat_pkg = compat_pkg
                            .and_then(|id| compat.pkg_deps.get(&id))
                            .map(HashMap::keys)
                            .and_then(|mut deps| deps.find(|dep| dep.name() == name))
                            .cloned();
                        let latest_pkg = latest_pkg
                            .and_then(|id| latest.pkg_deps.get(&id))
                            .map(HashMap::keys)
                            .and_then(|mut deps| deps.find(|dep| dep.name() == name))
                            .cloned();
                        let mut path = path.clone();
                        path.push(dep);
                        queue.push_back((path, compat_pkg, latest_pkg));
                    });
            }
        }

        Ok(())
    }

    /// Print package status to `TabWriter`
    pub fn print_list(
        &'ela self,
        options: &Options,
        root: PackageId,
        preceding_line: bool,
    ) -> CargoResult<i32> {
        let mut lines = BTreeSet::new();
        let mut queue = VecDeque::new();
        queue.push_back(vec![root]);
        while let Some(path) = queue.pop_front() {
            let pkg = path.last().ok_or(OutdatedError::EmptyPath)?;
            let name = pkg.name().to_string();

            if options.ignore.contains(&name) {
                continue;
            }

            let depth = path.len() as i32 - 1;
            // generate lines
            let status = &self.pkg_status.borrow_mut()[&path];
            if (status.compat.is_changed() || status.latest.is_changed())
                && (options.packages.is_empty() || options.packages.contains(&name))
            {
                // name version compatible latest kind platform
                let parent = path.get(path.len() - 2);
                if let Some(parent) = parent {
                    let dependency = &self.pkg_deps[parent][pkg];
                    let label = if self.workspace_mode
                        || parent == &self.workspace.current()?.package_id()
                    {
                        name
                    } else {
                        format!("{}->{}", self.pkgs[parent].name(), name)
                    };
                    let line = format!(
                        "{}\t{}\t{}\t{}\t{:?}\t{}\n",
                        label,
                        pkg.version(),
                        status.compat.to_string(),
                        status.latest.to_string(),
                        dependency.kind(),
                        dependency
                            .platform()
                            .map(ToString::to_string)
                            .unwrap_or_else(|| "---".to_owned())
                    );
                    lines.insert(line);
                } else {
                    let line = format!(
                        "{}\t{}\t{}\t{}\t---\t---\n",
                        name,
                        pkg.version(),
                        status.compat.to_string(),
                        status.latest.to_string()
                    );
                    lines.insert(line);
                }
            }
            // next layer
            // this unwrap is safe since we first check if it is None :)
            if options.depth.is_none() || depth < options.depth.unwrap() {
                self.pkg_deps[pkg]
                    .keys()
                    .filter(|dep| !path.contains(dep))
                    .filter(|&dep| {
                        !self.workspace_mode
                            || !self.workspace.members().any(|mem| &mem.package_id() == dep)
                    })
                    .for_each(|&dep| {
                        let mut path = path.clone();
                        path.push(dep);
                        queue.push_back(path);
                    });
            }
        }

        if lines.is_empty() {
            if !self.workspace_mode {
                println!("All dependencies are up to date, yay!");
            }
        } else {
            if preceding_line {
                println!();
            }
            if self.workspace_mode {
                println!("{}\n================", root.name());
            }
            let mut tw = TabWriter::new(vec![]);
            writeln!(&mut tw, "Name\tProject\tCompat\tLatest\tKind\tPlatform")?;
            writeln!(&mut tw, "----\t-------\t------\t------\t----\t--------")?;
            for line in &lines {
                write!(&mut tw, "{}", line)?;
            }
            tw.flush()?;
            write!(io::stdout(), "{}", String::from_utf8(tw.into_inner()?)?)?;
            io::stdout().flush()?;
        }

        Ok(lines.len() as i32)
    }

    pub fn print_json(&'ela self, options: &Options, root: PackageId) -> CargoResult<i32> {
        let mut crate_graph = CrateMetadata {
            crate_name: root.name().to_string(),
            dependencies: BTreeSet::new(),
        };
        let mut queue = VecDeque::new();
        queue.push_back(vec![root]);

        while let Some(path) = queue.pop_front() {
            let pkg = path.last().ok_or(OutdatedError::EmptyPath)?;
            let name = pkg.name().to_string();

            if options.ignore.contains(&name) {
                continue;
            }

            let depth = path.len() as i32 - 1;
            // generate lines
            let status = &self.pkg_status.borrow_mut()[&path];
            if (status.compat.is_changed() || status.latest.is_changed())
                && (options.packages.is_empty() || options.packages.contains(&name))
            {
                // name version compatible latest kind platform
                // safely get the parent index
                let parent = if path.len() > 1 {
                    path.get(path.len() - 2)
                } else {
                    None
                };

                let line;

                if let Some(parent) = parent {
                    let dependency = &self.pkg_deps[parent][pkg];
                    let label = if self.workspace_mode
                        || parent == &self.workspace.current()?.package_id()
                    {
                        name
                    } else {
                        format!("{}->{}", self.pkgs[parent].name(), name)
                    };

                    let dependency_type = match dependency.kind() {
                        DepKind::Normal => "Normal",
                        DepKind::Development => "Development",
                        DepKind::Build => "Build",
                    };

                    line = Metadata {
                        name: label,
                        project: pkg.version().to_string(),
                        compat: status.compat.to_string(),
                        latest: status.latest.to_string(),
                        kind: Some(dependency_type.to_string()),
                        platform: dependency.platform().map(|p| p.to_string()),
                    };
                } else {
                    line = Metadata {
                        name,
                        project: pkg.version().to_string(),
                        compat: status.compat.to_string(),
                        latest: status.latest.to_string(),
                        kind: None,
                        platform: None,
                    };
                }

                crate_graph.dependencies.insert(line);
            }
            // next layer
            // this unwrap is safe since we first check if it is None :)
            if options.depth.is_none() || depth < options.depth.unwrap() {
                self.pkg_deps[pkg]
                    .keys()
                    .filter(|dep| !path.contains(dep))
                    .filter(|dep| {
                        !self.workspace_mode
                            || !self
                                .workspace
                                .members()
                                .any(|mem| mem.package_id() == **dep)
                    })
                    .for_each(|dep| {
                        let mut path = path.clone();
                        path.push(*dep);
                        queue.push_back(path);
                    });
            }
        }

        println!("{}", serde_json::to_string(&crate_graph)?);

        Ok(crate_graph.dependencies.len() as i32)
    }
}
<commit_msg>chore: clippy<commit_after>use std::cell::RefCell;
use std::cmp::Ordering;
use std::collections::{BTreeSet, HashMap, VecDeque};
use std::io::{self, Write};
use std::rc::Rc;

use anyhow::anyhow;
use cargo::core::compiler::{CompileKind, RustcTargetData};
use cargo::core::resolver::features::{ForceAllTargets, HasDevUnits};
use cargo::core::resolver::CliFeatures;
use cargo::core::FeatureValue;
use cargo::core::{dependency::DepKind, Dependency, Package, PackageId, Workspace};
use cargo::ops::{self, Packages};
use cargo::util::interning::InternedString;
use cargo::util::{CargoResult, Config};
use serde::{Deserialize, Serialize};
use tabwriter::TabWriter;

use crate::error::OutdatedError;

use super::pkg_status::*;
use super::Options;

/// An elaborate workspace containing resolved dependencies and
/// the update status of packages
pub struct ElaborateWorkspace<'ela> {
    pub workspace: &'ela Workspace<'ela>,
    pub pkgs: HashMap<PackageId, Package>,
    pub pkg_deps: HashMap<PackageId, HashMap<PackageId, Dependency>>,
    /// Map of package status
    pub pkg_status: RefCell<HashMap<Vec<PackageId>, PkgStatus>>,
    /// Whether using workspace mode
    pub workspace_mode: bool,
}

/// A struct to serialize to json with serde
#[derive(Serialize, Deserialize)]
pub struct CrateMetadata {
    pub crate_name: String,
    pub dependencies: BTreeSet<Metadata>,
}

#[derive(Serialize, Deserialize, Eq, PartialEq)]
pub struct Metadata {
    pub name: String,
    pub project: String,
    pub compat: String,
    pub latest: String,
    pub kind: Option<String>,
    pub platform: Option<String>,
}

impl Ord for Metadata {
    fn cmp(&self, other: &Self) -> Ordering { self.name.cmp(&other.name) }
}

impl PartialOrd for Metadata {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}

impl<'ela> ElaborateWorkspace<'ela> {
    /// Elaborate a `Workspace`
    pub fn from_workspace(
        workspace: &'ela Workspace<'_>,
        options: &Options,
    ) -> CargoResult<ElaborateWorkspace<'ela>> {
        // new in cargo 0.54.0
        let flag_features: BTreeSet<FeatureValue> = options
            .features
            .iter()
            .map(|feature| FeatureValue::new(InternedString::from(feature)))
            .collect();
        let specs = Packages::All.to_package_id_specs(workspace)?;

        let cli_features = CliFeatures {
            features: Rc::new(flag_features),
            all_features: options.all_features(),
            uses_default_features: options.no_default_features(),
        };

        //The CompileKind, this has no target since it's the temp workspace
        //targets are blank since we don't need to fully build for the targets to get the dependencies
        let compile_kind = CompileKind::from_requested_targets(workspace.config(), &[])?;
        let target_data = RustcTargetData::new(workspace, &compile_kind)?;
        let ws_resolve = ops::resolve_ws_with_opts(
            workspace,
            &target_data,
            &compile_kind,
            &cli_features,
            &specs,
            HasDevUnits::Yes,
            ForceAllTargets::Yes,
        )?;
        let packages = ws_resolve.pkg_set;
        let resolve = ws_resolve
            .workspace_resolve
            .expect("Error getting workspace resolved");
        let mut pkgs = HashMap::new();
        let mut pkg_deps = HashMap::new();
        for pkg in packages.get_many(packages.package_ids())? {
            let pkg_id = pkg.package_id();
            pkgs.insert(pkg_id, pkg.clone());
            let deps = pkg.dependencies();
            let mut dep_map = HashMap::new();
            for dep_id in resolve.deps(pkg_id) {
                for d in deps {
                    if d.matches_id(dep_id.0) {
                        dep_map.insert(dep_id.0, d.clone());
                        break;
                    }
                }
            }
            pkg_deps.insert(pkg_id, dep_map);
        }

        Ok(ElaborateWorkspace {
            workspace,
            pkgs,
            pkg_deps,
            pkg_status: RefCell::new(HashMap::new()),
            workspace_mode: options.workspace || workspace.current().is_err(),
        })
    }

    /// Determine root package based on current workspace and CLI options
    pub fn determine_root(&self, options: &Options) -> CargoResult<PackageId> {
        if let Some(ref root_name) = options.root {
            if let Ok(workspace_root) = self.workspace.current() {
                if root_name == workspace_root.name().as_str() {
                    Ok(workspace_root.package_id())
                } else {
                    for direct_dep in self.pkg_deps[&workspace_root.package_id()].keys() {
                        if self.pkgs[direct_dep].name().as_str() == root_name {
                            return Ok(*direct_dep);
                        }
                    }
                    Err(anyhow!(
                        "Root is neither the workspace root nor a direct dependency",
                    ))
                }
            } else {
                Err(anyhow!(
                    "--root is not allowed when running against a virtual manifest",
                ))
            }
        } else {
            Ok(self.workspace.current()?.package_id())
        }
    }

    /// Find a member based on member name
    fn find_member(&self, member: PackageId) -> CargoResult<PackageId> {
        for m in self.workspace.members() {
            // members with the same name in a workspace is not allowed
            // even with different paths
            if member.name() == m.name() {
                return Ok(m.package_id());
            }
        }
        Err(anyhow!("Workspace member {} not found", member.name()))
    }

    /// Find a contained package, which is a member or dependency inside the workspace
    fn find_contained_package(&self, name: &str) -> CargoResult<PackageId> {
        let root_path = self.workspace.root();
        for (pkg_id, pkg) in &self.pkgs {
            if pkg.manifest_path().starts_with(root_path) && pkg.name().as_str() == name {
                return Ok(*pkg_id);
            }
        }
        Err(anyhow!("Cannot find package {} in workspace", name))
    }

    /// Find a direct dependency of a contained package
    pub fn find_direct_dependency(
        &self,
        dependency_name: &str,
        dependent_package_name: &str,
    ) -> CargoResult<PackageId> {
        let dependent_package = self.find_contained_package(dependent_package_name)?;

        for direct_dep in self.pkg_deps[&dependent_package].keys() {
            if direct_dep.name().as_str() == dependency_name {
                return Ok(*direct_dep);
            }
        }

        for (pkg_id, pkg) in &self.pkgs {
            if pkg.name().as_str() == dependency_name {
                return Ok(*pkg_id);
            }
        }

        Err(anyhow!(
            "Direct dependency {} not found for package {}",
            dependency_name,
            dependent_package_name
        ))
    }

    /// Resolve compatible and latest status from the corresponding `ElaborateWorkspace`s
    pub fn resolve_status(
        &'ela self,
        compat: &ElaborateWorkspace<'_>,
        latest: &ElaborateWorkspace<'_>,
        options: &Options,
        _config: &Config,
        root: PackageId,
    ) -> CargoResult<()> {
        self.pkg_status.borrow_mut().clear();
        let (compat_root, latest_root) = if self.workspace_mode {
            (compat.find_member(root)?, latest.find_member(root)?)
        } else {
            (
                compat.determine_root(options)?,
                latest.determine_root(options)?,
            )
        };

        let mut queue = VecDeque::new();
        queue.push_back((vec![root], Some(compat_root), Some(latest_root)));
        while let Some((path, compat_pkg, latest_pkg)) = queue.pop_front() {
            let pkg = path.last().ok_or(OutdatedError::EmptyPath)?;
            let depth = path.len() as i32 - 1;
            // generate pkg_status
            let status = PkgStatus {
                compat: Status::from_versions(pkg.version(), compat_pkg.map(PackageId::version)),
                latest: Status::from_versions(pkg.version(), latest_pkg.map(PackageId::version)),
            };
            debug!(
                _config,
                "STATUS => PKG: {}; PATH: {:?}; COMPAT: {:?}; LATEST: {:?}; STATUS: {:?}",
                pkg,
                path,
                compat_pkg,
                latest_pkg,
                status
            );
            self.pkg_status.borrow_mut().insert(path.clone(), status);
            // next layer
            // this unwrap is safe since we first check if it is None :)
            if options.depth.is_none() || depth < options.depth.unwrap() {
                self.pkg_deps[pkg]
                    .keys()
                    .filter(|dep| !path.contains(dep))
                    .for_each(|&dep| {
                        let name = dep.name();
                        let compat_pkg = compat_pkg
                            .and_then(|id| compat.pkg_deps.get(&id))
                            .map(HashMap::keys)
                            .and_then(|mut deps| deps.find(|dep| dep.name() == name))
                            .cloned();
                        let latest_pkg = latest_pkg
                            .and_then(|id| latest.pkg_deps.get(&id))
                            .map(HashMap::keys)
                            .and_then(|mut deps| deps.find(|dep| dep.name() == name))
                            .cloned();
                        let mut path = path.clone();
                        path.push(dep);
                        queue.push_back((path, compat_pkg, latest_pkg));
                    });
            }
        }

        Ok(())
    }

    /// Print package status to `TabWriter`
    pub fn print_list(
        &'ela self,
        options: &Options,
        root: PackageId,
        preceding_line: bool,
    ) -> CargoResult<i32> {
        let mut lines = BTreeSet::new();
        let mut queue = VecDeque::new();
        queue.push_back(vec![root]);
        while let Some(path) = queue.pop_front() {
            let pkg = path.last().ok_or(OutdatedError::EmptyPath)?;
            let name = pkg.name().to_string();

            if options.ignore.contains(&name) {
                continue;
            }

            let depth = path.len() as i32 - 1;
            // generate lines
            let status = &self.pkg_status.borrow_mut()[&path];
            if (status.compat.is_changed() || status.latest.is_changed())
                && (options.packages.is_empty() || options.packages.contains(&name))
            {
                // name version compatible latest kind platform
                let parent = path.get(path.len() - 2);
                if let Some(parent) = parent {
                    let dependency = &self.pkg_deps[parent][pkg];
                    let label = if self.workspace_mode
                        || parent == &self.workspace.current()?.package_id()
                    {
                        name
                    } else {
                        format!("{}->{}", self.pkgs[parent].name(), name)
                    };
                    let line = format!(
                        "{}\t{}\t{}\t{}\t{:?}\t{}\n",
                        label,
                        pkg.version(),
                        status.compat.to_string(),
                        status.latest.to_string(),
                        dependency.kind(),
                        dependency
                            .platform()
                            .map(ToString::to_string)
                            .unwrap_or_else(|| "---".to_owned())
                    );
                    lines.insert(line);
                } else {
                    let line = format!(
                        "{}\t{}\t{}\t{}\t---\t---\n",
                        name,
                        pkg.version(),
                        status.compat.to_string(),
                        status.latest.to_string()
                    );
                    lines.insert(line);
                }
            }
            // next layer
            // this unwrap is safe since we first check if it is None :)
            if options.depth.is_none() || depth < options.depth.unwrap() {
                self.pkg_deps[pkg]
                    .keys()
                    .filter(|dep| !path.contains(dep))
                    .filter(|&dep| {
                        !self.workspace_mode
                            || !self.workspace.members().any(|mem| &mem.package_id() == dep)
                    })
                    .for_each(|&dep| {
                        let mut path = path.clone();
                        path.push(dep);
                        queue.push_back(path);
                    });
            }
        }

        if lines.is_empty() {
            if !self.workspace_mode {
                println!("All dependencies are up to date, yay!");
            }
        } else {
            if preceding_line {
                println!();
            }
            if self.workspace_mode {
                println!("{}\n================", root.name());
            }
            let mut tw = TabWriter::new(vec![]);
            writeln!(&mut tw, "Name\tProject\tCompat\tLatest\tKind\tPlatform")?;
            writeln!(&mut tw, "----\t-------\t------\t------\t----\t--------")?;
            for line in &lines {
                write!(&mut tw, "{}", line)?;
            }
            tw.flush()?;
            write!(io::stdout(), "{}", String::from_utf8(tw.into_inner()?)?)?;
            io::stdout().flush()?;
        }

        Ok(lines.len() as i32)
    }

    pub fn print_json(&'ela self, options: &Options, root: PackageId) -> CargoResult<i32> {
        let mut crate_graph = CrateMetadata {
            crate_name: root.name().to_string(),
            dependencies: BTreeSet::new(),
        };
        let mut queue = VecDeque::new();
        queue.push_back(vec![root]);

        while let Some(path) = queue.pop_front() {
            let pkg = path.last().ok_or(OutdatedError::EmptyPath)?;
            let name = pkg.name().to_string();

            if options.ignore.contains(&name) {
                continue;
            }

            let depth = path.len() as i32 - 1;
            // generate lines
            let status = &self.pkg_status.borrow_mut()[&path];
            if (status.compat.is_changed() || status.latest.is_changed())
                && (options.packages.is_empty() || options.packages.contains(&name))
            {
                // name version compatible latest kind platform
                // safely get the parent index
                let parent = if path.len() > 1 {
                    path.get(path.len() - 2)
                } else {
                    None
                };

                let line = if let Some(parent) = parent {
                    let dependency = &self.pkg_deps[parent][pkg];
                    let label = if self.workspace_mode
                        || parent == &self.workspace.current()?.package_id()
                    {
                        name
                    } else {
                        format!("{}->{}", self.pkgs[parent].name(), name)
                    };

                    let dependency_type = match dependency.kind() {
                        DepKind::Normal => "Normal",
                        DepKind::Development => "Development",
                        DepKind::Build => "Build",
                    };

                    Metadata {
                        name: label,
                        project: pkg.version().to_string(),
                        compat: status.compat.to_string(),
                        latest: status.latest.to_string(),
                        kind: Some(dependency_type.to_string()),
                        platform: dependency.platform().map(|p| p.to_string()),
                    }
                } else {
                    Metadata {
                        name,
                        project: pkg.version().to_string(),
                        compat: status.compat.to_string(),
                        latest: status.latest.to_string(),
                        kind: None,
                        platform: None,
                    }
                };

                crate_graph.dependencies.insert(line);
            }
            // next layer
            // this unwrap is safe since we first check if it is None :)
            if options.depth.is_none() || depth < options.depth.unwrap() {
                self.pkg_deps[pkg]
                    .keys()
                    .filter(|dep| !path.contains(dep))
                    .filter(|dep| {
                        !self.workspace_mode
                            || !self
                                .workspace
                                .members()
                                .any(|mem| mem.package_id() == **dep)
                    })
                    .for_each(|dep| {
                        let mut path = path.clone();
                        path.push(*dep);
                        queue.push_back(path);
                    });
            }
        }

        println!("{}", serde_json::to_string(&crate_graph)?);

        Ok(crate_graph.dependencies.len() as i32)
    }
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use font_context::FontContext;
use style::computed_values::border_style;
use opts::Opts;

use azure::azure_hl::{B8G8R8A8, Color, ColorPattern, DrawOptions};
use azure::azure_hl::{DrawSurfaceOptions, DrawTarget, Linear, StrokeOptions};
use azure::{AZ_CAP_BUTT, AZ_CAP_ROUND};
use azure::AZ_JOIN_BEVEL;
use azure::AzFloat;
use extra::arc::Arc;
use geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
use geom::side_offsets::SideOffsets2D;
use servo_net::image::base::Image;
use png::{RGBA8, K8, KA8};
use servo_util::geometry::Au;
use std::vec;
use std::libc::types::common::c99::uint16_t;
use std::libc::size_t;

pub struct RenderContext<'self> {
    draw_target: &'self DrawTarget,
    font_ctx: @mut FontContext,
    opts: &'self Opts,
    /// The rectangle that this context encompasses in page coordinates.
    page_rect: Rect<f32>,
    /// The rectangle that this context encompasses in screen coordinates (pixels).
    screen_rect: Rect<uint>,
}

impl<'self> RenderContext<'self>  {
    pub fn get_draw_target(&self) -> &'self DrawTarget {
        self.draw_target
    }

    pub fn draw_solid_color(&self, bounds: &Rect<Au>, color: Color) {
        self.draw_target.make_current();
        self.draw_target.fill_rect(&bounds.to_azure_rect(), &ColorPattern(color));
    }

    pub fn draw_border(&self,
                       bounds: &Rect<Au>,
                       border: SideOffsets2D<Au>,
                       color: SideOffsets2D<Color>,
                       style: SideOffsets2D<border_style::T>) {
        let draw_opts = DrawOptions(1 as AzFloat, 0 as uint16_t);
        let rect = bounds.to_azure_rect();
        let border = border.to_float_px();

        self.draw_target.make_current();
        let mut dash: [AzFloat, ..2] = [0 as AzFloat, 0 as AzFloat];
        let mut stroke_opts = StrokeOptions(0 as AzFloat, 10 as AzFloat);

        // draw top border
        RenderContext::apply_border_style(style.top, border.top, dash, &mut stroke_opts);
        let y = rect.origin.y + border.top * 0.5;
        let start = Point2D(rect.origin.x, y);
        let end = Point2D(rect.origin.x + rect.size.width, y);
        self.draw_target.stroke_line(start,
                                     end,
                                     &ColorPattern(color.top),
                                     &stroke_opts,
                                     &draw_opts);

        // draw right border
        RenderContext::apply_border_style(style.right, border.right, dash,  &mut stroke_opts);
        let x = rect.origin.x + rect.size.width - border.right * 0.5;
        let start = Point2D(x, rect.origin.y);
        let end = Point2D(x, rect.origin.y + rect.size.height);
        self.draw_target.stroke_line(start,
                                     end,
                                     &ColorPattern(color.right),
                                     &stroke_opts,
                                     &draw_opts);

        // draw bottom border
        RenderContext::apply_border_style(style.bottom, border.bottom, dash, &mut stroke_opts);
        let y = rect.origin.y + rect.size.height - border.bottom * 0.5;
        let start = Point2D(rect.origin.x, y);
        let end = Point2D(rect.origin.x + rect.size.width, y);
        self.draw_target.stroke_line(start,
                                     end,
                                     &ColorPattern(color.bottom),
                                     &stroke_opts,
                                     &draw_opts);

        // draw left border
        RenderContext::apply_border_style(style.left, border.left, dash,  &mut stroke_opts);
        let x = rect.origin.x + border.left * 0.5;
        let start = Point2D(x, rect.origin.y);
        let end = Point2D(x, rect.origin.y + rect.size.height);
        self.draw_target.stroke_line(start,
                                     end,
                                     &ColorPattern(color.left),
                                     &stroke_opts,
                                     &draw_opts);
    }

    pub fn draw_push_clip(&self, bounds: &Rect<Au>) {
        let rect = bounds.to_azure_rect();
        let path_builder = self.draw_target.create_path_builder();

        let left_top = Point2D(rect.origin.x, rect.origin.y);
        let right_top = Point2D(rect.origin.x + rect.size.width, rect.origin.y);
        let left_bottom = Point2D(rect.origin.x, rect.origin.y + rect.size.height);
        let right_bottom = Point2D(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height);

        path_builder.move_to(left_top);
        path_builder.line_to(right_top);
        path_builder.line_to(right_bottom);
        path_builder.line_to(left_bottom);

        let path = path_builder.finish();
        self.draw_target.push_clip(&path);
    }

    pub fn draw_pop_clip(&self) {
        self.draw_target.pop_clip();
    }

    pub fn draw_image(&self, bounds: Rect<Au>, image: Arc<~Image>) {
        let image = image.get();
        let size = Size2D(image.width as i32, image.height as i32);
        let pixel_width = match image.color_type {
            RGBA8 => 4,
            K8    => 1,
            KA8   => 2,
            _     => fail!(~"color type not supported"),
        };
        let stride = image.width * pixel_width;

        self.draw_target.make_current();
        let draw_target_ref = &self.draw_target;
        let azure_surface = draw_target_ref.create_source_surface_from_data(image.pixels, size,
                                                                            stride as i32, B8G8R8A8);
        let source_rect = Rect(Point2D(0 as AzFloat, 0 as AzFloat),
                               Size2D(image.width as AzFloat, image.height as AzFloat));
        let dest_rect = bounds.to_azure_rect();
        let draw_surface_options = DrawSurfaceOptions(Linear, true);
        let draw_options = DrawOptions(1.0f64 as AzFloat, 0);
        draw_target_ref.draw_surface(azure_surface,
                                     dest_rect,
                                     source_rect,
                                     draw_surface_options,
                                     draw_options);
    }

    pub fn clear(&self) {
        let pattern = ColorPattern(Color(1.0, 1.0, 1.0, 1.0));
        let rect = Rect(Point2D(self.page_rect.origin.x as AzFloat,
                                self.page_rect.origin.y as AzFloat),
                        Size2D(self.screen_rect.size.width as AzFloat,
                               self.screen_rect.size.height as AzFloat));
        self.draw_target.make_current();
        self.draw_target.fill_rect(&rect, &pattern);
    }

    fn apply_border_style(style: border_style::T, border_width: AzFloat, dash: &mut [AzFloat], stroke_opts: &mut StrokeOptions){
        match style{
            border_style::none => {
            }
            border_style::hidden => {
            }
            //FIXME(sammykim): This doesn't work with dash_pattern and cap_style well. I referred firefox code.
            border_style::dotted => {
                stroke_opts.line_width = border_width;

                if border_width > 2.0 {
                    dash[0] = 0 as AzFloat;
                    dash[1] = border_width * 2.0;

                    stroke_opts.set_cap_style(AZ_CAP_ROUND as u8);
                } else {
                    dash[0] = border_width;
                    dash[1] = border_width;
                }
                stroke_opts.mDashPattern = vec::raw::to_ptr(dash);
                stroke_opts.mDashLength = dash.len() as size_t;
            }
            border_style::dashed => {
                stroke_opts.set_cap_style(AZ_CAP_BUTT as u8);
                stroke_opts.line_width = border_width;
                dash[0] = border_width*3 as AzFloat;
                dash[1] = border_width*3 as AzFloat;
                stroke_opts.mDashPattern = vec::raw::to_ptr(dash);
                stroke_opts.mDashLength = dash.len() as size_t;
            }
            //FIXME(sammykim): BorderStyleSolid doesn't show proper join-style with comparing firefox.
            border_style::solid => {
                stroke_opts.set_cap_style(AZ_CAP_BUTT as u8);
                stroke_opts.set_join_style(AZ_JOIN_BEVEL as u8);
                stroke_opts.line_width = border_width;
                stroke_opts.mDashLength = 0 as size_t;
            }
            //FIXME(sammykim): Five more styles should be implemented.
            //double, groove, ridge, inset, outset
        }
    }
}

trait to_float {
    fn to_float(&self) -> f64;
}

impl to_float for u8 {
    fn to_float(&self) -> f64 {
        (*self as f64) / 255f64
    }
}

trait ToAzureRect {
    fn to_azure_rect(&self) -> Rect<AzFloat>;
}

impl ToAzureRect for Rect<Au> {
    fn to_azure_rect(&self) -> Rect<AzFloat> {
        Rect(Point2D(self.origin.x.to_nearest_px() as AzFloat,
                     self.origin.y.to_nearest_px() as AzFloat),
             Size2D(self.size.width.to_nearest_px() as AzFloat,
                    self.size.height.to_nearest_px() as AzFloat))
    }
}

trait ToSideOffsetsPx {
    fn to_float_px(&self) -> SideOffsets2D<AzFloat>;
}

impl ToSideOffsetsPx for SideOffsets2D<Au> {
    fn to_float_px(&self) -> SideOffsets2D<AzFloat> {
        SideOffsets2D::new(self.top.to_nearest_px() as AzFloat,
                           self.right.to_nearest_px() as AzFloat,
                           self.bottom.to_nearest_px() as AzFloat,
                           self.left.to_nearest_px() as AzFloat)
    }
}
<commit_msg>Render border solid-style properly<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use font_context::FontContext;
use style::computed_values::border_style;
use opts::Opts;

use azure::azure_hl::{B8G8R8A8, Color, ColorPattern, DrawOptions};
use azure::azure_hl::{DrawSurfaceOptions, DrawTarget, Linear, StrokeOptions};
use azure::{AZ_CAP_BUTT, AZ_CAP_ROUND};
use azure::AZ_JOIN_BEVEL;
use azure::AzFloat;
use extra::arc::Arc;
use geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
use geom::side_offsets::SideOffsets2D;
use servo_net::image::base::Image;
use png::{RGBA8, K8, KA8};
use servo_util::geometry::Au;
use std::vec;
use std::libc::types::common::c99::uint16_t;
use std::libc::size_t;

pub struct RenderContext<'self> {
    draw_target: &'self DrawTarget,
    font_ctx: @mut FontContext,
    opts: &'self Opts,
    /// The rectangle that this context encompasses in page coordinates.
    page_rect: Rect<f32>,
    /// The rectangle that this context encompasses in screen coordinates (pixels).
    screen_rect: Rect<uint>,
}

enum Direction {
    Top,
    Left,
    Right,
    Bottom
}

impl<'self> RenderContext<'self>  {
    pub fn get_draw_target(&self) -> &'self DrawTarget {
        self.draw_target
    }

    pub fn draw_solid_color(&self, bounds: &Rect<Au>, color: Color) {
        self.draw_target.make_current();
        self.draw_target.fill_rect(&bounds.to_azure_rect(), &ColorPattern(color));
    }

    pub fn draw_border(&self,
                       bounds: &Rect<Au>,
                       border: SideOffsets2D<Au>,
                       color: SideOffsets2D<Color>,
                       style: SideOffsets2D<border_style::T>) {
        let border = border.to_float_px();
        self.draw_target.make_current();

        self.draw_border_segment(Top, bounds, border, color, style);
        self.draw_border_segment(Right, bounds, border, color, style);
        self.draw_border_segment(Bottom, bounds, border, color, style);
        self.draw_border_segment(Left, bounds, border, color, style);
    }

    pub fn draw_push_clip(&self, bounds: &Rect<Au>) {
        let rect = bounds.to_azure_rect();
        let path_builder = self.draw_target.create_path_builder();

        let left_top = Point2D(rect.origin.x, rect.origin.y);
        let right_top = Point2D(rect.origin.x + rect.size.width, rect.origin.y);
        let left_bottom = Point2D(rect.origin.x, rect.origin.y + rect.size.height);
        let right_bottom = Point2D(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height);

        path_builder.move_to(left_top);
        path_builder.line_to(right_top);
        path_builder.line_to(right_bottom);
        path_builder.line_to(left_bottom);

        let path = path_builder.finish();
        self.draw_target.push_clip(&path);
    }

    pub fn draw_pop_clip(&self) {
        self.draw_target.pop_clip();
    }

    pub fn draw_image(&self, bounds: Rect<Au>, image: Arc<~Image>) {
        let image = image.get();
        let size = Size2D(image.width as i32, image.height as i32);
        let pixel_width = match image.color_type {
            RGBA8 => 4,
            K8    => 1,
            KA8   => 2,
            _     => fail!(~"color type not supported"),
        };
        let stride = image.width * pixel_width;

        self.draw_target.make_current();
        let draw_target_ref = &self.draw_target;
        let azure_surface = draw_target_ref.create_source_surface_from_data(image.pixels, size,
                                                                            stride as i32, B8G8R8A8);
        let source_rect = Rect(Point2D(0 as AzFloat, 0 as AzFloat),
                               Size2D(image.width as AzFloat, image.height as AzFloat));
        let dest_rect = bounds.to_azure_rect();
        let draw_surface_options = DrawSurfaceOptions(Linear, true);
        let draw_options = DrawOptions(1.0f64 as AzFloat, 0);
        draw_target_ref.draw_surface(azure_surface,
                                     dest_rect,
                                     source_rect,
                                     draw_surface_options,
                                     draw_options);
    }

    pub fn clear(&self) {
        let pattern = ColorPattern(Color(1.0, 1.0, 1.0, 1.0));
        let rect = Rect(Point2D(self.page_rect.origin.x as AzFloat,
                                self.page_rect.origin.y as AzFloat),
                        Size2D(self.screen_rect.size.width as AzFloat,
                               self.screen_rect.size.height as AzFloat));
        self.draw_target.make_current();
        self.draw_target.fill_rect(&rect, &pattern);
    }

    fn draw_border_segment(&self, direction: Direction, bounds: &Rect<Au>, border: SideOffsets2D<f32>, color: SideOffsets2D<Color>, style: SideOffsets2D<border_style::T>) {
        let (style_select, color_select) = match direction {
            Top => (style.top, color.top),
            Left => (style.left, color.left),
            Right => (style.right, color.right),
            Bottom => (style.bottom, color.bottom)
        };

        match style_select{
            border_style::none => {
            }
            border_style::hidden => {
            }
            //FIXME(sammykim): This doesn't work with dash_pattern and cap_style well. I referred firefox code.
            border_style::dotted => {
            }
            border_style::dashed => {
                self.draw_dashed_border_segment(direction,bounds,border,color_select);
            }
            border_style::solid => {
                self.draw_solid_border_segment(direction,bounds,border,color_select);
            }
            //FIXME(sammykim): Five more styles should be implemented.
            //double, groove, ridge, inset, outset
        }
    }

    fn draw_dashed_border_segment(&self, direction: Direction, bounds: &Rect<Au>, border: SideOffsets2D<f32>, color: Color) {
        let rect = bounds.to_azure_rect();
        let draw_opts = DrawOptions(1 as AzFloat, 0 as uint16_t);
        let mut stroke_opts = StrokeOptions(0 as AzFloat, 10 as AzFloat);
        let mut dash: [AzFloat, ..2] = [0 as AzFloat, 0 as AzFloat];

        stroke_opts.set_cap_style(AZ_CAP_BUTT as u8);

        let border_width = match direction {
            Top => border.top,
            Left => border.left,
            Right => border.right,
            Bottom => border.bottom
        };

        stroke_opts.line_width = border_width;
        dash[0] = border_width * 3 as AzFloat;
        dash[1] = border_width * 3 as AzFloat;
        stroke_opts.mDashPattern = vec::raw::to_ptr(dash);
        stroke_opts.mDashLength = dash.len() as size_t;

        let (start, end)  = match direction {
            Top => {
                let y = rect.origin.y + border.top * 0.5;
                let start = Point2D(rect.origin.x, y);
                let end = Point2D(rect.origin.x + rect.size.width, y);
                (start, end)
            }
            Left => {
                let x = rect.origin.x + border.left * 0.5;
                let start = Point2D(x, rect.origin.y + rect.size.height);
                let end = Point2D(x, rect.origin.y + border.top);
                (start, end)
            }
            Right => {
                let x = rect.origin.x + rect.size.width - border.right * 0.5;
                let start = Point2D(x, rect.origin.y);
                let end = Point2D(x, rect.origin.y + rect.size.height);
                (start, end)
            }
            Bottom => {
                let y = rect.origin.y + rect.size.height - border.bottom * 0.5;
                let start = Point2D(rect.origin.x + rect.size.width, y);
                let end = Point2D(rect.origin.x + border.left, y);
                (start, end)
            }
        };

        self.draw_target.stroke_line(start,
                                     end,
                                     &ColorPattern(color),
                                     &stroke_opts,
                                     &draw_opts);
    }

    fn draw_solid_border_segment(&self, direction: Direction, bounds: &Rect<Au>, border: SideOffsets2D<f32>, color: Color) {
        let rect = bounds.to_azure_rect();
        let draw_opts = DrawOptions(1.0 , 0);
        let path_builder = self.draw_target.create_path_builder();

        let left_top = Point2D(rect.origin.x, rect.origin.y);
        let right_top = Point2D(rect.origin.x + rect.size.width, rect.origin.y);
        let left_bottom = Point2D(rect.origin.x, rect.origin.y + rect.size.height);
        let right_bottom = Point2D(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height);

        match direction {
            Top => {
                path_builder.move_to(left_top);
                path_builder.line_to(right_top);
                path_builder.line_to(right_top + Point2D(-border.right, border.top));
                path_builder.line_to(left_top + Point2D(border.left, border.top));
            }
            Left => {
                path_builder.move_to(left_top);
                path_builder.line_to(left_top + Point2D(border.left, border.top));
                path_builder.line_to(left_bottom + Point2D(border.left, -border.bottom));
                path_builder.line_to(left_bottom);
            }
            Right => {
                path_builder.move_to(right_top);
                path_builder.line_to(right_bottom);
                path_builder.line_to(right_bottom + Point2D(-border.right, -border.bottom));
                path_builder.line_to(right_top + Point2D(-border.right, border.top));
            }
            Bottom => {
                path_builder.move_to(left_bottom);
                path_builder.line_to(left_bottom + Point2D(border.left, -border.bottom));
                path_builder.line_to(right_bottom + Point2D(-border.right, -border.bottom));
                path_builder.line_to(right_bottom);
            }
        }

        let path = path_builder.finish();
        self.draw_target.fill(&path, &ColorPattern(color), &draw_opts);
    }
}

trait to_float {
    fn to_float(&self) -> f64;
}

impl to_float for u8 {
    fn to_float(&self) -> f64 {
        (*self as f64) / 255f64
    }
}

trait ToAzureRect {
    fn to_azure_rect(&self) -> Rect<AzFloat>;
}

impl ToAzureRect for Rect<Au> {
    fn to_azure_rect(&self) -> Rect<AzFloat> {
        Rect(Point2D(self.origin.x.to_nearest_px() as AzFloat,
                     self.origin.y.to_nearest_px() as AzFloat),
             Size2D(self.size.width.to_nearest_px() as AzFloat,
                    self.size.height.to_nearest_px() as AzFloat))
    }
}

trait ToSideOffsetsPx {
    fn to_float_px(&self) -> SideOffsets2D<AzFloat>;
}

impl ToSideOffsetsPx for SideOffsets2D<Au> {
    fn to_float_px(&self) -> SideOffsets2D<AzFloat> {
        SideOffsets2D::new(self.top.to_nearest_px() as AzFloat,
                           self.right.to_nearest_px() as AzFloat,
                           self.bottom.to_nearest_px() as AzFloat,
                           self.left.to_nearest_px() as AzFloat)
    }
}
<|endoftext|>


<commit_before>use ast::{Expr, ExprItem, Term};
use math::rich_number::RichNumber;
use eval::fixity::Fixity;
use eval::context_item::ContextItem;
use eval::eval_context::{EvalContext, EvalError};

#[derive(Debug, PartialEq, Clone)]
pub struct EvalTreeNode {
    item: ContextItem,
    children: Vec<EvalTreeNode>,
}

impl EvalTreeNode {
    pub fn new(item: ContextItem) -> EvalTreeNode {
        EvalTreeNode {
            item: item,
            children: Vec::new(),
        }
    }

    pub fn with_children(item: ContextItem, children: Vec<EvalTreeNode>) -> EvalTreeNode {
        let mut node = EvalTreeNode::new(item);
        node.children = children;

        node
    }

    pub fn from_expr(context: &EvalContext, expr: Expr) -> Result<EvalTreeNode, EvalError> {
        let mut nodes = try!(EvalTreeNode::convert_to_nodes(context, expr));

        while nodes.len() != 1 {
            let node_index = EvalTreeNode::left_highest_precedence(&nodes);
            if node_index.is_none() {
                return Err(EvalError::UnexpectedSymbols);
            }
            let node_index = node_index.unwrap();

            let (node_index, params) = try!(EvalTreeNode::drain_params(&mut nodes, node_index));

            let node = &mut nodes[node_index];
            node.children = params;
        }

        debug_assert!(nodes.len() == 1);
        Ok(nodes.pop().unwrap())
    }

    fn convert_to_nodes(context: &EvalContext, expr: Expr) -> Result<Vec<EvalTreeNode>, EvalError> {
        let mut nodes: Vec<EvalTreeNode> = Vec::new();

        for item in expr.into_iter() {
            nodes.push(match item {
                ExprItem::AnonymousFunction(function) => {
                    EvalTreeNode::new(ContextItem::function_defaults(function))
                },
                ExprItem::SingleTerm(term) => match term {
                    Term::Symbol(sym) => {
                        let item = context.get(&sym);
                        if item.is_none() {
                            return Err(EvalError::UndefinedSymbol(sym));
                        }
                        EvalTreeNode::new(item.unwrap())
                    },
                    Term::Number(num) => {
                        EvalTreeNode::new(
                            ContextItem::Number(
                                RichNumber::from(num)
                            )
                        )
                    },
                    Term::StringLiteral(value) => {
                        EvalTreeNode::new(
                            ContextItem::Constant(value)
                        )
                    },
                },
                ExprItem::Group(expr) => try!(EvalTreeNode::from_expr(context, expr)),
            });
        }

        Ok(nodes)
    }

    /// Returns the index of the leftmost node with the highest precedence
    /// Only returns ContextItems with precedence
    fn left_highest_precedence(nodes: &Vec<EvalTreeNode>) -> Option<usize> {
        // (index, precedence)
        let mut highest: Option<(usize, u8)> = None;

        for (i, node) in nodes.iter().enumerate() {
            // This node has already been taken care of
            if !node.children.is_empty() {
                continue;
            }

            let precedence = node.item.resolve_precedence();
            if precedence.is_none() {
                continue;
            }
            let precedence = *precedence.unwrap();

            // This MUST be less than, NOT less than or equal to to get the
            // leftmost item
            if highest.is_none() || highest.unwrap().1 < precedence {
                highest = Some((i, precedence));
            }
        }

        highest.map(|x| x.0)
    }

    // Removes the nodes adjacent to the node_index (but not including the
    // node_index) that correspond to the number of parameters expected
    // by that node in the direction specified by that node's fixity
    // Returns the (node_index, parameter nodes)
    // Needs to return node_index because that index may change
    // due to this operation
    fn drain_params(nodes: &mut Vec<EvalTreeNode>, node_index: usize) -> Result<(usize, Vec<EvalTreeNode>), EvalError> {
        let fixity;
        let params;
        {
            let node = &nodes[node_index];

            // unwrap() is safe here because node item should be a definition or built in
            fixity = node.item.resolve_fixity().unwrap();
            params = node.item.resolve_params().unwrap();
            debug_assert!(params != 0);
        }

        Ok(match fixity {
            Fixity::Infix => {
                // Infix functions MUST have 2 parameters
                if params != 2 {
                    return Err(EvalError::InvalidSymbolDefinition {
                        expected_params: 2,
                        actual_params: params,
                    });
                }

                // if either of these conditions is true, the following calculation
                // would result in either overflow or an index error
                if node_index == 0 || node_index >= nodes.len() {
                    return Err(EvalError::ExpectedParams(params));
                }

                // It is vital that the second remove happen before the
                // first because this operation shifts all indexes to the left
                let second = nodes.remove(node_index + 1);
                let first = nodes.remove(node_index - 1);

                // node_index shifts to where the first argument was
                (node_index - 1, vec![first, second])
            },
            Fixity::Postfix => {
                let start;
                let end = node_index;

                // params > node_index would result in a negative sum
                if params > node_index || end > nodes.len() {
                    return Err(EvalError::ExpectedParams(params));
                }
                // need to declare this here to avoid overflow
                start = node_index - params;

                // node_index shifts to where the start used to be after
                // all the params are drained
                (start, nodes.drain(start..end).collect())
            },
            Fixity::Prefix => {
                // No need to worry about node_index being less than zero because we
                // are adding 1 here
                let start = node_index + 1;
                // The +1 in the end of the range is because ranges stop
                // at the index 1 before the end
                let end = node_index + params + 1;
                if end > nodes.len() {
                    return Err(EvalError::ExpectedParams(params));
                }

                // node_index remains the same here because the params
                // are after the node
                (node_index, nodes.drain(start..end).collect())
            },
        })
    }
}

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

    use eval::fixity::Fixity;
    use eval::context_item::ContextItem;
    use eval::eval_context::EvalContext;
    use math::rich_number::RichNumber;

    #[test]
    fn creates_tree_with_correct_precedence() {
        // f 3 + (4 * 8 + 99 - x) - ((67)) / (x - g 4) + 32
        let expr: Expr = vec![
            ExprItem::SingleTerm(Term::Symbol("f".to_owned())),
            ExprItem::SingleTerm(Term::Number(3f64)),
            ExprItem::SingleTerm(Term::Symbol("+".to_owned())),
            ExprItem::Group(vec![
                ExprItem::SingleTerm(Term::Number(4f64)),
                ExprItem::SingleTerm(Term::Symbol("*".to_owned())),
                ExprItem::SingleTerm(Term::Number(8f64)),
                ExprItem::SingleTerm(Term::Symbol("+".to_owned())),
                ExprItem::SingleTerm(Term::Number(99f64)),
                ExprItem::SingleTerm(Term::Symbol("-".to_owned())),
                ExprItem::SingleTerm(Term::Symbol("x".to_owned())),
            ]),
            ExprItem::SingleTerm(Term::Symbol("-".to_owned())),
            ExprItem::Group(vec![
                ExprItem::Group(vec![
                    ExprItem::SingleTerm(Term::Number(67f64)),
                ])
            ]),
            ExprItem::SingleTerm(Term::Symbol("/".to_owned())),
            ExprItem::Group(vec![
                ExprItem::SingleTerm(Term::Symbol("x".to_owned())),
                ExprItem::SingleTerm(Term::Symbol("-".to_owned())),
                ExprItem::SingleTerm(Term::Symbol("g".to_owned())),
                ExprItem::SingleTerm(Term::Number(4f64)),
            ]),
            ExprItem::SingleTerm(Term::Symbol("+".to_owned())),
            ExprItem::SingleTerm(Term::Number(32f64)),
        ];

        let mut context = basic_context();

        let expected_tree = {
            let nleaf = |n| EvalTreeNode::new(ContextItem::Number(RichNumber::from(n)));
            let lookup = |name| context.get(name).unwrap();

            EvalTreeNode::with_children(lookup("-"), vec![
                EvalTreeNode::with_children(lookup("+"), vec![
                    EvalTreeNode::with_children(lookup("f"), vec![
                        nleaf(3),
                    ]),
                    EvalTreeNode::with_children(lookup("+"), vec![
                        EvalTreeNode::with_children(lookup("*"), vec![
                            nleaf(4),
                            nleaf(8),
                        ]),
                        EvalTreeNode::with_children(lookup("-"), vec![
                            nleaf(99),
                            EvalTreeNode::new(lookup("x")),
                        ]),
                    ]),
                ]),
                EvalTreeNode::with_children(lookup("+"), vec![
                    EvalTreeNode::with_children(lookup("/"), vec![
                        nleaf(67),
                        EvalTreeNode::with_children(lookup("-"), vec![
                            EvalTreeNode::new(lookup("x")),
                            EvalTreeNode::with_children(lookup("g"), vec![
                                nleaf(4),
                            ]),
                        ]),
                    ]),
                    nleaf(32),
                ]),
            ])
        };

        let tree = EvalTreeNode::from_expr(&mut context, expr).unwrap();

        println!("expected:");
        print_tree(&expected_tree);
        println!("result:");
        print_tree(&tree);

        assert_eq!(tree, expected_tree);
    }

    /// Defines a basic context with the operators: + - / * %
    /// with their normal, mathematical precedence
    /// as well as two functions f and g as well as a number x
    /// None of these definitions have a real, evaluatable function
    fn basic_context() -> EvalContext {
        let mut context = EvalContext::new();

        let fake_unary: Function = Function {
            params: vec!["a".to_owned()],
            body: Expr::new(),
        };

        let fake_binary: Function = Function {
            params: vec!["a".to_owned(), "b".to_owned()],
            body: Expr::new(),
        };

        context.set_number("x", RichNumber::from(3875f64));

        context.define_defaults("f", fake_unary.clone());
        context.define_defaults("g", fake_unary.clone());

        context.define("+", Fixity::Infix, 6, fake_binary.clone());
        context.define("-", Fixity::Infix, 6, fake_binary.clone());

        context.define("*", Fixity::Infix, 7, fake_binary.clone());
        context.define("/", Fixity::Infix, 7, fake_binary.clone());
        context.define("%", Fixity::Infix, 7, fake_binary.clone());

        context
    }

    fn print_tree(tree: &EvalTreeNode) {
        _print_tree(tree, 0);
    }

    fn _print_tree(tree: &EvalTreeNode, depth: usize) {
        _print_item(&tree.item, depth);

        for child in &tree.children {
            _print_tree(child, depth + 1);
        }
    }

    fn _print_item(item: &ContextItem, depth: usize) {
        use std::iter;

        let tab: String = iter::repeat("    ").take(depth).collect();
        let formatted = match *item {
            ContextItem::Definition {
                ref precedence,
                function: Function {
                    ref params,
                    ..
                },
                ..
            } => format!("def: params: {}, precedence: {}", params.len(), precedence),
            ContextItem::Number(num) => format!("{}", num.value),
            _ => format!("{:?}", *item),
        };
        for line in formatted.lines() {
            println!("{}{}", tab, formatted);
        }
    }
}

<commit_msg>Corrected test case because it turns out the algorithm is smarter than I am<commit_after>use ast::{Expr, ExprItem, Term};
use math::rich_number::RichNumber;
use eval::fixity::Fixity;
use eval::context_item::ContextItem;
use eval::eval_context::{EvalContext, EvalError};

#[derive(Debug, PartialEq, Clone)]
pub struct EvalTreeNode {
    item: ContextItem,
    children: Vec<EvalTreeNode>,
}

impl EvalTreeNode {
    pub fn new(item: ContextItem) -> EvalTreeNode {
        EvalTreeNode {
            item: item,
            children: Vec::new(),
        }
    }

    pub fn with_children(item: ContextItem, children: Vec<EvalTreeNode>) -> EvalTreeNode {
        let mut node = EvalTreeNode::new(item);
        node.children = children;

        node
    }

    pub fn from_expr(context: &EvalContext, expr: Expr) -> Result<EvalTreeNode, EvalError> {
        let mut nodes = try!(EvalTreeNode::convert_to_nodes(context, expr));

        while nodes.len() != 1 {
            let node_index = EvalTreeNode::left_highest_precedence(&nodes);
            if node_index.is_none() {
                return Err(EvalError::UnexpectedSymbols);
            }
            let node_index = node_index.unwrap();

            let (node_index, params) = try!(EvalTreeNode::drain_params(&mut nodes, node_index));

            let node = &mut nodes[node_index];
            node.children = params;
        }

        debug_assert!(nodes.len() == 1);
        Ok(nodes.pop().unwrap())
    }

    fn convert_to_nodes(context: &EvalContext, expr: Expr) -> Result<Vec<EvalTreeNode>, EvalError> {
        let mut nodes: Vec<EvalTreeNode> = Vec::new();

        for item in expr.into_iter() {
            nodes.push(match item {
                ExprItem::AnonymousFunction(function) => {
                    EvalTreeNode::new(ContextItem::function_defaults(function))
                },
                ExprItem::SingleTerm(term) => match term {
                    Term::Symbol(sym) => {
                        let item = context.get(&sym);
                        if item.is_none() {
                            return Err(EvalError::UndefinedSymbol(sym));
                        }
                        EvalTreeNode::new(item.unwrap())
                    },
                    Term::Number(num) => {
                        EvalTreeNode::new(
                            ContextItem::Number(
                                RichNumber::from(num)
                            )
                        )
                    },
                    Term::StringLiteral(value) => {
                        EvalTreeNode::new(
                            ContextItem::Constant(value)
                        )
                    },
                },
                ExprItem::Group(expr) => try!(EvalTreeNode::from_expr(context, expr)),
            });
        }

        Ok(nodes)
    }

    /// Returns the index of the leftmost node with the highest precedence
    /// Only returns ContextItems with precedence
    fn left_highest_precedence(nodes: &Vec<EvalTreeNode>) -> Option<usize> {
        // (index, precedence)
        let mut highest: Option<(usize, u8)> = None;

        for (i, node) in nodes.iter().enumerate() {
            // This node has already been taken care of
            if !node.children.is_empty() {
                continue;
            }

            let precedence = node.item.resolve_precedence();
            if precedence.is_none() {
                continue;
            }
            let precedence = *precedence.unwrap();

            // This MUST be less than, NOT less than or equal to to get the
            // leftmost item
            if highest.is_none() || highest.unwrap().1 < precedence {
                highest = Some((i, precedence));
            }
        }

        highest.map(|x| x.0)
    }

    // Removes the nodes adjacent to the node_index (but not including the
    // node_index) that correspond to the number of parameters expected
    // by that node in the direction specified by that node's fixity
    // Returns the (node_index, parameter nodes)
    // Needs to return node_index because that index may change
    // due to this operation
    fn drain_params(nodes: &mut Vec<EvalTreeNode>, node_index: usize) -> Result<(usize, Vec<EvalTreeNode>), EvalError> {
        let fixity;
        let params;
        {
            let node = &nodes[node_index];

            // unwrap() is safe here because node item should be a definition or built in
            fixity = node.item.resolve_fixity().unwrap();
            params = node.item.resolve_params().unwrap();
            debug_assert!(params != 0);
        }

        Ok(match fixity {
            Fixity::Infix => {
                // Infix functions MUST have 2 parameters
                if params != 2 {
                    return Err(EvalError::InvalidSymbolDefinition {
                        expected_params: 2,
                        actual_params: params,
                    });
                }

                // if either of these conditions is true, the following calculation
                // would result in either overflow or an index error
                if node_index == 0 || node_index >= nodes.len() {
                    return Err(EvalError::ExpectedParams(params));
                }

                // It is vital that the second remove happen before the
                // first because this operation shifts all indexes to the left
                let second = nodes.remove(node_index + 1);
                let first = nodes.remove(node_index - 1);

                // node_index shifts to where the first argument was
                (node_index - 1, vec![first, second])
            },
            Fixity::Postfix => {
                let start;
                let end = node_index;

                // params > node_index would result in a negative sum
                if params > node_index || end > nodes.len() {
                    return Err(EvalError::ExpectedParams(params));
                }
                // need to declare this here to avoid overflow
                start = node_index - params;

                // node_index shifts to where the start used to be after
                // all the params are drained
                (start, nodes.drain(start..end).collect())
            },
            Fixity::Prefix => {
                // No need to worry about node_index being less than zero because we
                // are adding 1 here
                let start = node_index + 1;
                // The +1 in the end of the range is because ranges stop
                // at the index 1 before the end
                let end = node_index + params + 1;
                if end > nodes.len() {
                    return Err(EvalError::ExpectedParams(params));
                }

                // node_index remains the same here because the params
                // are after the node
                (node_index, nodes.drain(start..end).collect())
            },
        })
    }
}

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

    use eval::fixity::Fixity;
    use eval::context_item::ContextItem;
    use eval::eval_context::EvalContext;
    use math::rich_number::RichNumber;

    #[test]
    fn creates_tree_with_correct_precedence() {
        // f 3 + (4 * 8 + 99 - x) - ((67)) / (x - g 4) + 32
        let expr: Expr = vec![
            ExprItem::SingleTerm(Term::Symbol("f".to_owned())),
            ExprItem::SingleTerm(Term::Number(3f64)),
            ExprItem::SingleTerm(Term::Symbol("+".to_owned())),
            ExprItem::Group(vec![
                ExprItem::SingleTerm(Term::Number(4f64)),
                ExprItem::SingleTerm(Term::Symbol("*".to_owned())),
                ExprItem::SingleTerm(Term::Number(8f64)),
                ExprItem::SingleTerm(Term::Symbol("+".to_owned())),
                ExprItem::SingleTerm(Term::Number(99f64)),
                ExprItem::SingleTerm(Term::Symbol("-".to_owned())),
                ExprItem::SingleTerm(Term::Symbol("x".to_owned())),
            ]),
            ExprItem::SingleTerm(Term::Symbol("-".to_owned())),
            ExprItem::Group(vec![
                ExprItem::Group(vec![
                    ExprItem::SingleTerm(Term::Number(67f64)),
                ])
            ]),
            ExprItem::SingleTerm(Term::Symbol("/".to_owned())),
            ExprItem::Group(vec![
                ExprItem::SingleTerm(Term::Symbol("x".to_owned())),
                ExprItem::SingleTerm(Term::Symbol("-".to_owned())),
                ExprItem::SingleTerm(Term::Symbol("g".to_owned())),
                ExprItem::SingleTerm(Term::Number(4f64)),
            ]),
            ExprItem::SingleTerm(Term::Symbol("+".to_owned())),
            ExprItem::SingleTerm(Term::Number(32f64)),
        ];

        let mut context = basic_context();

        let expected_tree = {
            let nleaf = |n| EvalTreeNode::new(ContextItem::Number(RichNumber::from(n)));
            let lookup = |name| context.get(name).unwrap();

            EvalTreeNode::with_children(lookup("+"), vec![
                EvalTreeNode::with_children(lookup("-"), vec![
                    EvalTreeNode::with_children(lookup("+"), vec![
                        EvalTreeNode::with_children(lookup("f"), vec![
                            nleaf(3),
                        ]),
                        EvalTreeNode::with_children(lookup("-"), vec![
                            EvalTreeNode::with_children(lookup("+"), vec![
                                EvalTreeNode::with_children(lookup("*"), vec![
                                    nleaf(4),
                                    nleaf(8),
                                ]),
                                nleaf(99),
                            ]),
                            EvalTreeNode::new(lookup("x")),
                        ]),
                    ]),
                    EvalTreeNode::with_children(lookup("/"), vec![
                        nleaf(67),
                        EvalTreeNode::with_children(lookup("-"), vec![
                            EvalTreeNode::new(lookup("x")),
                            EvalTreeNode::with_children(lookup("g"), vec![
                                nleaf(4),
                            ]),
                        ]),
                    ]),
                ]),
                nleaf(32),
            ])
        };

        let tree = EvalTreeNode::from_expr(&mut context, expr).unwrap();

        assert_eq!(tree, expected_tree);
    }

    /// Defines a basic context with the operators: + - / * %
    /// with their normal, mathematical precedence
    /// as well as two functions f and g as well as a number x
    /// None of these definitions have a real, evaluatable function
    fn basic_context() -> EvalContext {
        let mut context = EvalContext::new();

        let fake_unary: Function = Function {
            params: vec!["a".to_owned()],
            body: Expr::new(),
        };

        let fake_binary: Function = Function {
            params: vec!["a".to_owned(), "b".to_owned()],
            body: Expr::new(),
        };

        context.set_number("x", RichNumber::from(3875f64));

        context.define_defaults("f", fake_unary.clone());
        context.define_defaults("g", fake_unary.clone());

        context.define("+", Fixity::Infix, 6, fake_binary.clone());
        context.define("-", Fixity::Infix, 6, fake_binary.clone());

        context.define("*", Fixity::Infix, 7, fake_binary.clone());
        context.define("/", Fixity::Infix, 7, fake_binary.clone());
        context.define("%", Fixity::Infix, 7, fake_binary.clone());

        context
    }

    /// Useful method for debugging trees
    fn _debug_print_tree(tree: &EvalTreeNode) {
        _print_tree(tree, 0);
    }

    fn _print_tree(tree: &EvalTreeNode, depth: usize) {
        _print_item(&tree.item, depth);

        for child in &tree.children {
            _print_tree(child, depth + 1);
        }
    }

    fn _print_item(item: &ContextItem, depth: usize) {
        use std::iter;

        let tab: String = iter::repeat("    ").take(depth).collect();
        let formatted = match *item {
            ContextItem::Definition {
                ref precedence,
                function: Function {
                    ref params,
                    ..
                },
                ..
            } => format!("def: params: {}, precedence: {}", params.len(), precedence),
            ContextItem::Number(num) => format!("{}", num.value),
            _ => format!("{:?}", *item),
        };
        for line in formatted.lines() {
            println!("{}{}", tab, formatted);
        }
    }
}

<|endoftext|>