<commit_before>use super::ring_key::RingKey;
use crate::{crypto::{hash,
                     keys::{Key,
                            NamedRevision,
                            OriginPublicEncryptionKey,
                            PublicOriginSigningKey,
                            SecretOriginSigningKey,
                            ServicePublicEncryptionKey,
                            ServiceSecretEncryptionKey,
                            UserPublicEncryptionKey,
                            UserSecretEncryptionKey}},
            error::{Error,
                    Result},
            fs::AtomicWriter};
use std::{convert::TryFrom,
          io::Write,
          path::{Path,
                 PathBuf}};

pub struct KeyCache(PathBuf);

impl AsRef<Path> for KeyCache {
    fn as_ref(&self) -> &Path { self.0.as_ref() }
}

impl KeyCache {
    pub fn new<P>(path: P) -> Self
        where P: Into<PathBuf>
    {
        KeyCache(path.into())
    }

    /// Ensure that the directory backing the cache exists on disk.
    pub fn setup(&self) -> Result<()> {
        if !self.0.is_dir() {
            std::fs::create_dir_all(&self.0)?;
        }
        Ok(())
    }

    /// Write a key into the cache. If the key already exists,
    /// and the content has the same hash value, nothing will be
    /// done. If the file exists and it has *different* content, an
    /// Error is returned.
    pub fn write_key<K>(&self, key: &K) -> Result<()>
        where K: AsRef<Path> + Key
    {
        self.maybe_write_key(key)
    }

    /// Note: name is just the name, not the name + revision
    pub fn latest_ring_key_revision(&self, name: &str) -> Result<RingKey> {
        self.fetch_latest_revision::<RingKey>(name)
    }

    pub fn latest_secret_origin_signing_key(&self, name: &str) -> Result<SecretOriginSigningKey> {
        self.fetch_latest_revision::<SecretOriginSigningKey>(name)
    }

    pub fn latest_public_origin_signing_key(&self, name: &str) -> Result<PublicOriginSigningKey> {
        self.fetch_latest_revision::<PublicOriginSigningKey>(name)
    }

    pub fn latest_user_secret_key(&self, user_name: &str) -> Result<UserSecretEncryptionKey> {
        self.fetch_latest_revision::<UserSecretEncryptionKey>(user_name)
    }

    pub fn latest_origin_public_encryption_key(&self,
                                               name: &str)
                                               -> Result<OriginPublicEncryptionKey> {
        self.fetch_latest_revision::<OriginPublicEncryptionKey>(name)
    }

    // TODO (CM): accept org + service_group instead? Create / accept
    // a proper type?
    /// Name should be in `"service.group@org"` format.
    pub fn latest_service_public_key(&self, name: &str) -> Result<ServicePublicEncryptionKey> {
        self.fetch_latest_revision::<ServicePublicEncryptionKey>(name)
    }

    /// Attemt to retrieve the specified signing key from the cache,
    /// if it exists and is valid.
    pub fn public_signing_key(&self,
                              named_revision: &NamedRevision)
                              -> Result<PublicOriginSigningKey> {
        self.fetch_specific_revision::<PublicOriginSigningKey>(named_revision)
    }

    pub fn secret_signing_key(&self,
                              named_revision: &NamedRevision)
                              -> Result<SecretOriginSigningKey> {
        self.fetch_specific_revision::<SecretOriginSigningKey>(named_revision)
    }

    pub fn user_public_encryption_key(&self,
                                      named_revision: &NamedRevision)
                                      -> Result<UserPublicEncryptionKey> {
        self.fetch_specific_revision::<UserPublicEncryptionKey>(named_revision)
    }

    pub fn service_secret_encryption_key(&self,
                                         named_revision: &NamedRevision)
                                         -> Result<ServiceSecretEncryptionKey> {
        self.fetch_specific_revision::<ServiceSecretEncryptionKey>(named_revision)
    }

    ////////////////////////////////////////////////////////////////////////

    // TODO (CM): Turn this into Option<Result<K>>; otherwise it
    // assumes that there must be at least one version of the key present
    fn fetch_latest_revision<K>(&self, name: &str) -> Result<K>
        where K: Key + TryFrom<PathBuf, Error = Error>
    {
        match self.get_latest_path_for(name, K::extension())? {
            Some(path) => <K as TryFrom<PathBuf>>::try_from(path),
            None => {
                let msg = format!("No revisions found for {}", name);
                Err(Error::CryptoError(msg))
            }
        }
    }

    /// Generic retrieval function to grab the key of the specified
    /// type `K` identified by `named_revision`
    fn fetch_specific_revision<K>(&self, named_revision: &NamedRevision) -> Result<K>
        where K: Key + TryFrom<PathBuf, Error = Error>
    {
        let path_in_cache = self.0.join(named_revision.filename::<K>());
        if path_in_cache.exists() {
            <K as TryFrom<PathBuf>>::try_from(path_in_cache)
        } else {
            Err(Error::CryptoError(format!("Key not found in cache: {}",
                                           path_in_cache.display()).to_string()))
        }
    }

    ////////////////////////////////////////////////////////////////////////

    /// Provides the path at which this file would be found in the
    /// cache, if it exists (or, alternatively, where it would be
    /// written to).
    // TODO (CM): Only making this public temporarily
    pub fn path_in_cache<K>(&self, key: K) -> PathBuf
        where K: AsRef<Path>
    {
        self.0.join(key.as_ref())
    }

    fn maybe_write_key<K>(&self, key: &K) -> Result<()>
        where K: AsRef<Path> + Key
    {
        let keyfile = self.path_in_cache(&key);
        let content = key.to_key_string();

        if keyfile.is_file() {
            let existing_hash = hash::hash_file(&keyfile)?;
            let new_hash = hash::hash_string(&content);
            if existing_hash != new_hash {
                let msg = format!("Existing key file {} found but new version hash is different, \
                                   failing to write new file over existing. (existing = {}, \
                                   incoming = {})",
                                  keyfile.display(),
                                  existing_hash,
                                  new_hash);
                return Err(Error::CryptoError(msg));
            }
        } else {
            // Technically speaking, this probably doesn't really need
            // to be an atomic write process, since we just tested
            // that the file doesn't currently exist. It does,
            // however, bundle up writing with platform-independent
            // permission setting, which is *super* convenient.
            let w = AtomicWriter::new_with_permissions(&keyfile, K::permissions())?;
            w.with_writer(|f| f.write_all(content.as_ref()))?;
        }
        Ok(())
    }

    /// Search the key cache for all files that are revisions of the
    /// given key. Returns the full paths to those files within the
    /// cache.
    fn get_all_paths_for(&self,
                         name: &str,
                         key_extension: &str)
                         -> Result<impl Iterator<Item = PathBuf>> {
        // Ideally, we'd want that `*` to be `\d{14}` to match the
        // structure of our revisions... perhaps that can be an
        // additional filter later on with an actual regex?
        let pattern = self.0.join(format!("{}-*.{}", name, key_extension));
        let pattern = pattern.to_string_lossy();

        // TODO (CM): this is a bogus error
        Ok(glob::glob(&pattern).map_err(|_e| Error::CryptoError("Couldn't glob!".to_string()))?
                               .filter_map(std::result::Result::ok)
                               .filter(|p| p.metadata().map(|m| m.is_file()).unwrap_or(false)))
    }

    /// Given a key name and extension, find the path that corresponds
    /// to the most recent revision of that key in the cache, if it
    /// exists.
    fn get_latest_path_for(&self, name: &str, key_extension: &str) -> Result<Option<PathBuf>> {
        Ok(self.get_all_paths_for(name, key_extension)?.max())
    }
}

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

    static VALID_KEY: &str = "ring-key-valid-20160504220722.sym.key";
    static VALID_NAME_WITH_REV: &str = "ring-key-valid-20160504220722";

    #[test]
    fn get_all_paths_for() {
        let (cache, _dir) = new_cache();

        let paths = cache.get_all_paths_for("beyonce", RingKey::extension())
                         .unwrap();
        assert_eq!(paths.count(), 0);

        cache.write_key(&RingKey::new("beyonce")).unwrap();
        let paths = cache.get_all_paths_for("beyonce", RingKey::extension())
                         .unwrap();
        assert_eq!(paths.count(), 1);

        wait_1_sec(); // ensure new revision
                      // will be different.
        cache.write_key(&RingKey::new("beyonce")).unwrap();

        let paths = cache.get_all_paths_for("beyonce", RingKey::extension())
                         .unwrap();
        assert_eq!(paths.count(), 2);

        // We should not include another named key in the count
        cache.write_key(&RingKey::new("jayz")).unwrap();
        let paths = cache.get_all_paths_for("beyonce", RingKey::extension())
                         .unwrap();
        assert_eq!(paths.count(), 2);
    }

    #[test]
    fn latest_cached_revision_single() {
        let (cache, _dir) = new_cache();

        let key = RingKey::new("beyonce");
        cache.write_key(&key).unwrap();

        let latest = cache.latest_ring_key_revision("beyonce").unwrap();
        assert_eq!(latest.name(), key.name());
        assert_eq!(latest.revision(), key.revision());
    }

    #[test]
    fn latest_cached_revision_multiple() {
        let (cache, _dir) = new_cache();

        let k1 = RingKey::new("beyonce");
        cache.write_key(&k1).unwrap();

        wait_1_sec();

        let k2 = RingKey::new("beyonce");
        cache.write_key(&k2).unwrap();

        assert_eq!(k1.name(), k2.name());
        assert_ne!(k1.revision(), k2.revision());

        let latest = cache.latest_ring_key_revision("beyonce").unwrap();
        assert_eq!(latest.name(), k2.name());
        assert_eq!(latest.revision(), k2.revision());
    }

    #[test]
    #[should_panic(expected = "No revisions found for")]
    fn latest_cached_revision_nonexistent() {
        let (cache, _dir) = new_cache();
        cache.latest_ring_key_revision("nope-nope").unwrap();
    }

    #[test]
    fn writing_ring_key() {
        let (cache, dir) = new_cache();

        let content = fixture_as_string(&format!("keys/{}", VALID_KEY));
        let new_key_file = dir.path().join(VALID_KEY);
        assert_eq!(new_key_file.is_file(), false);

        let key: RingKey = content.parse().unwrap();
        assert_eq!(key.name_with_rev(), VALID_NAME_WITH_REV);
        cache.write_key(&key).unwrap();
        assert!(new_key_file.is_file());

        let new_content = std::fs::read_to_string(new_key_file).unwrap();
        assert_eq!(new_content, content);
    }

    #[test]
    fn write_key_with_existing_identical() {
        let (cache, dir) = new_cache();
        let content = fixture_as_string(&format!("keys/{}", VALID_KEY));
        let new_key_file = dir.path().join(VALID_KEY);

        // install the key into the cache
        std::fs::copy(fixture(&format!("keys/{}", VALID_KEY)), &new_key_file).unwrap();

        let key: RingKey = content.parse().unwrap();
        cache.write_key(&key).unwrap();
        assert_eq!(key.name_with_rev(), VALID_NAME_WITH_REV);
        assert!(new_key_file.is_file());
    }

    #[test]
    #[should_panic(expected = "Existing key file")]
    fn write_key_exists_but_hashes_differ() {
        let (cache, dir) = new_cache();
        let old_content = fixture_as_string("keys/ring-key-valid-20160504220722.sym.key");

        std::fs::write(dir.path().join("ring-key-valid-20160504220722.sym.key"),
                       &old_content).unwrap();

        #[rustfmt::skip]
        let new_content = "SYM-SEC-1\nring-key-valid-20160504220722\n\nkA+c03Ly5qEoOZIjJ5zCD2vHI05pAW59PfCOb8thmZw=";

        assert_ne!(old_content, new_content);

        let new_key: RingKey = new_content.parse().unwrap();
        // this should fail
        cache.write_key(&new_key).unwrap();
    }

    // Old tests... not fully converting over to new implementation
    // yet because I think the function won't be sticking around very
    // long.

    // #[test]
    // fn cached_path() {
    //     let (cache, dir) = new_cache();
    //     fs::copy(fixture(&format!("keys/{}", VALID_KEY)),
    //              dir.path().join(VALID_KEY)).unwrap();

    //     let result = cache.ring_key_cached_path(VALID_NAME_WITH_REV).unwrap();
    //     assert_eq!(result, cache.path().join(VALID_KEY));
    // }

    // #[test]
    // #[should_panic(expected = "No secret key found at")]
    // fn get_secret_key_path_nonexistent() {
    //     let (cache, _dir) = new_cache();
    //     cache.ring_key_cached_path(VALID_NAME_WITH_REV).unwrap();
    // }
}
<commit_msg>[REFACTOR] Remove `maybe_write_key` function<commit_after>use super::ring_key::RingKey;
use crate::{crypto::{hash,
                     keys::{Key,
                            NamedRevision,
                            OriginPublicEncryptionKey,
                            PublicOriginSigningKey,
                            SecretOriginSigningKey,
                            ServicePublicEncryptionKey,
                            ServiceSecretEncryptionKey,
                            UserPublicEncryptionKey,
                            UserSecretEncryptionKey}},
            error::{Error,
                    Result},
            fs::AtomicWriter};
use std::{convert::TryFrom,
          io::Write,
          path::{Path,
                 PathBuf}};

pub struct KeyCache(PathBuf);

impl AsRef<Path> for KeyCache {
    fn as_ref(&self) -> &Path { self.0.as_ref() }
}

impl KeyCache {
    pub fn new<P>(path: P) -> Self
        where P: Into<PathBuf>
    {
        KeyCache(path.into())
    }

    /// Ensure that the directory backing the cache exists on disk.
    pub fn setup(&self) -> Result<()> {
        if !self.0.is_dir() {
            std::fs::create_dir_all(&self.0)?;
        }
        Ok(())
    }

    /// Write a key into the cache. If the key already exists,
    /// and the content has the same hash value, nothing will be
    /// done. If the file exists and it has *different* content, an
    /// Error is returned.
    pub fn write_key<K>(&self, key: &K) -> Result<()>
        where K: AsRef<Path> + Key
    {
        let keyfile = self.path_in_cache(&key);
        let content = key.to_key_string();

        if keyfile.is_file() {
            let existing_hash = hash::hash_file(&keyfile)?;
            let new_hash = hash::hash_string(&content);
            if existing_hash != new_hash {
                let msg = format!("Existing key file {} found but new version hash is different, \
                                   failing to write new file over existing. (existing = {}, \
                                   incoming = {})",
                                  keyfile.display(),
                                  existing_hash,
                                  new_hash);
                return Err(Error::CryptoError(msg));
            }
        } else {
            // Technically speaking, this probably doesn't really need
            // to be an atomic write process, since we just tested
            // that the file doesn't currently exist. It does,
            // however, bundle up writing with platform-independent
            // permission setting, which is *super* convenient.
            let w = AtomicWriter::new_with_permissions(&keyfile, K::permissions())?;
            w.with_writer(|f| f.write_all(content.as_ref()))?;
        }
        Ok(())
    }

    /// Note: name is just the name, not the name + revision
    pub fn latest_ring_key_revision(&self, name: &str) -> Result<RingKey> {
        self.fetch_latest_revision::<RingKey>(name)
    }

    pub fn latest_secret_origin_signing_key(&self, name: &str) -> Result<SecretOriginSigningKey> {
        self.fetch_latest_revision::<SecretOriginSigningKey>(name)
    }

    pub fn latest_public_origin_signing_key(&self, name: &str) -> Result<PublicOriginSigningKey> {
        self.fetch_latest_revision::<PublicOriginSigningKey>(name)
    }

    pub fn latest_user_secret_key(&self, user_name: &str) -> Result<UserSecretEncryptionKey> {
        self.fetch_latest_revision::<UserSecretEncryptionKey>(user_name)
    }

    pub fn latest_origin_public_encryption_key(&self,
                                               name: &str)
                                               -> Result<OriginPublicEncryptionKey> {
        self.fetch_latest_revision::<OriginPublicEncryptionKey>(name)
    }

    // TODO (CM): accept org + service_group instead? Create / accept
    // a proper type?
    /// Name should be in `"service.group@org"` format.
    pub fn latest_service_public_key(&self, name: &str) -> Result<ServicePublicEncryptionKey> {
        self.fetch_latest_revision::<ServicePublicEncryptionKey>(name)
    }

    /// Attemt to retrieve the specified signing key from the cache,
    /// if it exists and is valid.
    pub fn public_signing_key(&self,
                              named_revision: &NamedRevision)
                              -> Result<PublicOriginSigningKey> {
        self.fetch_specific_revision::<PublicOriginSigningKey>(named_revision)
    }

    pub fn secret_signing_key(&self,
                              named_revision: &NamedRevision)
                              -> Result<SecretOriginSigningKey> {
        self.fetch_specific_revision::<SecretOriginSigningKey>(named_revision)
    }

    pub fn user_public_encryption_key(&self,
                                      named_revision: &NamedRevision)
                                      -> Result<UserPublicEncryptionKey> {
        self.fetch_specific_revision::<UserPublicEncryptionKey>(named_revision)
    }

    pub fn service_secret_encryption_key(&self,
                                         named_revision: &NamedRevision)
                                         -> Result<ServiceSecretEncryptionKey> {
        self.fetch_specific_revision::<ServiceSecretEncryptionKey>(named_revision)
    }

    ////////////////////////////////////////////////////////////////////////

    // TODO (CM): Turn this into Option<Result<K>>; otherwise it
    // assumes that there must be at least one version of the key present
    fn fetch_latest_revision<K>(&self, name: &str) -> Result<K>
        where K: Key + TryFrom<PathBuf, Error = Error>
    {
        match self.get_latest_path_for(name, K::extension())? {
            Some(path) => <K as TryFrom<PathBuf>>::try_from(path),
            None => {
                let msg = format!("No revisions found for {}", name);
                Err(Error::CryptoError(msg))
            }
        }
    }

    /// Generic retrieval function to grab the key of the specified
    /// type `K` identified by `named_revision`
    fn fetch_specific_revision<K>(&self, named_revision: &NamedRevision) -> Result<K>
        where K: Key + TryFrom<PathBuf, Error = Error>
    {
        let path_in_cache = self.0.join(named_revision.filename::<K>());
        if path_in_cache.exists() {
            <K as TryFrom<PathBuf>>::try_from(path_in_cache)
        } else {
            Err(Error::CryptoError(format!("Key not found in cache: {}",
                                           path_in_cache.display()).to_string()))
        }
    }

    ////////////////////////////////////////////////////////////////////////

    /// Provides the path at which this file would be found in the
    /// cache, if it exists (or, alternatively, where it would be
    /// written to).
    // TODO (CM): Only making this public temporarily
    pub fn path_in_cache<K>(&self, key: K) -> PathBuf
        where K: AsRef<Path>
    {
        self.0.join(key.as_ref())
    }

    /// Search the key cache for all files that are revisions of the
    /// given key. Returns the full paths to those files within the
    /// cache.
    fn get_all_paths_for(&self,
                         name: &str,
                         key_extension: &str)
                         -> Result<impl Iterator<Item = PathBuf>> {
        // Ideally, we'd want that `*` to be `\d{14}` to match the
        // structure of our revisions... perhaps that can be an
        // additional filter later on with an actual regex?
        let pattern = self.0.join(format!("{}-*.{}", name, key_extension));
        let pattern = pattern.to_string_lossy();

        // TODO (CM): this is a bogus error
        Ok(glob::glob(&pattern).map_err(|_e| Error::CryptoError("Couldn't glob!".to_string()))?
                               .filter_map(std::result::Result::ok)
                               .filter(|p| p.metadata().map(|m| m.is_file()).unwrap_or(false)))
    }

    /// Given a key name and extension, find the path that corresponds
    /// to the most recent revision of that key in the cache, if it
    /// exists.
    fn get_latest_path_for(&self, name: &str, key_extension: &str) -> Result<Option<PathBuf>> {
        Ok(self.get_all_paths_for(name, key_extension)?.max())
    }
}

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

    static VALID_KEY: &str = "ring-key-valid-20160504220722.sym.key";
    static VALID_NAME_WITH_REV: &str = "ring-key-valid-20160504220722";

    #[test]
    fn get_all_paths_for() {
        let (cache, _dir) = new_cache();

        let paths = cache.get_all_paths_for("beyonce", RingKey::extension())
                         .unwrap();
        assert_eq!(paths.count(), 0);

        cache.write_key(&RingKey::new("beyonce")).unwrap();
        let paths = cache.get_all_paths_for("beyonce", RingKey::extension())
                         .unwrap();
        assert_eq!(paths.count(), 1);

        wait_1_sec(); // ensure new revision
                      // will be different.
        cache.write_key(&RingKey::new("beyonce")).unwrap();

        let paths = cache.get_all_paths_for("beyonce", RingKey::extension())
                         .unwrap();
        assert_eq!(paths.count(), 2);

        // We should not include another named key in the count
        cache.write_key(&RingKey::new("jayz")).unwrap();
        let paths = cache.get_all_paths_for("beyonce", RingKey::extension())
                         .unwrap();
        assert_eq!(paths.count(), 2);
    }

    #[test]
    fn latest_cached_revision_single() {
        let (cache, _dir) = new_cache();

        let key = RingKey::new("beyonce");
        cache.write_key(&key).unwrap();

        let latest = cache.latest_ring_key_revision("beyonce").unwrap();
        assert_eq!(latest.name(), key.name());
        assert_eq!(latest.revision(), key.revision());
    }

    #[test]
    fn latest_cached_revision_multiple() {
        let (cache, _dir) = new_cache();

        let k1 = RingKey::new("beyonce");
        cache.write_key(&k1).unwrap();

        wait_1_sec();

        let k2 = RingKey::new("beyonce");
        cache.write_key(&k2).unwrap();

        assert_eq!(k1.name(), k2.name());
        assert_ne!(k1.revision(), k2.revision());

        let latest = cache.latest_ring_key_revision("beyonce").unwrap();
        assert_eq!(latest.name(), k2.name());
        assert_eq!(latest.revision(), k2.revision());
    }

    #[test]
    #[should_panic(expected = "No revisions found for")]
    fn latest_cached_revision_nonexistent() {
        let (cache, _dir) = new_cache();
        cache.latest_ring_key_revision("nope-nope").unwrap();
    }

    #[test]
    fn writing_ring_key() {
        let (cache, dir) = new_cache();

        let content = fixture_as_string(&format!("keys/{}", VALID_KEY));
        let new_key_file = dir.path().join(VALID_KEY);
        assert_eq!(new_key_file.is_file(), false);

        let key: RingKey = content.parse().unwrap();
        assert_eq!(key.name_with_rev(), VALID_NAME_WITH_REV);
        cache.write_key(&key).unwrap();
        assert!(new_key_file.is_file());

        let new_content = std::fs::read_to_string(new_key_file).unwrap();
        assert_eq!(new_content, content);
    }

    #[test]
    fn write_key_with_existing_identical() {
        let (cache, dir) = new_cache();
        let content = fixture_as_string(&format!("keys/{}", VALID_KEY));
        let new_key_file = dir.path().join(VALID_KEY);

        // install the key into the cache
        std::fs::copy(fixture(&format!("keys/{}", VALID_KEY)), &new_key_file).unwrap();

        let key: RingKey = content.parse().unwrap();
        cache.write_key(&key).unwrap();
        assert_eq!(key.name_with_rev(), VALID_NAME_WITH_REV);
        assert!(new_key_file.is_file());
    }

    #[test]
    #[should_panic(expected = "Existing key file")]
    fn write_key_exists_but_hashes_differ() {
        let (cache, dir) = new_cache();
        let old_content = fixture_as_string("keys/ring-key-valid-20160504220722.sym.key");

        std::fs::write(dir.path().join("ring-key-valid-20160504220722.sym.key"),
                       &old_content).unwrap();

        #[rustfmt::skip]
        let new_content = "SYM-SEC-1\nring-key-valid-20160504220722\n\nkA+c03Ly5qEoOZIjJ5zCD2vHI05pAW59PfCOb8thmZw=";

        assert_ne!(old_content, new_content);

        let new_key: RingKey = new_content.parse().unwrap();
        // this should fail
        cache.write_key(&new_key).unwrap();
    }

    // Old tests... not fully converting over to new implementation
    // yet because I think the function won't be sticking around very
    // long.

    // #[test]
    // fn cached_path() {
    //     let (cache, dir) = new_cache();
    //     fs::copy(fixture(&format!("keys/{}", VALID_KEY)),
    //              dir.path().join(VALID_KEY)).unwrap();

    //     let result = cache.ring_key_cached_path(VALID_NAME_WITH_REV).unwrap();
    //     assert_eq!(result, cache.path().join(VALID_KEY));
    // }

    // #[test]
    // #[should_panic(expected = "No secret key found at")]
    // fn get_secret_key_path_nonexistent() {
    //     let (cache, _dir) = new_cache();
    //     cache.ring_key_cached_path(VALID_NAME_WITH_REV).unwrap();
    // }
}
<|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/. */

//! Abstractions for global scopes.
//!
//! This module contains smart pointers to global scopes, to simplify writing
//! code that works in workers as well as window scopes.

use devtools_traits::ScriptToDevtoolsControlMsg;
use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use dom::bindings::conversions::native_from_reflector_jsmanaged;
use dom::bindings::js::{JS, Root};
use dom::bindings::utils::{Reflectable, Reflector};
use dom::window::{self, ScriptHelpers};
use dom::workerglobalscope::WorkerGlobalScope;
use ipc_channel::ipc::IpcSender;
use js::jsapi::{GetGlobalForObjectCrossCompartment};
use js::jsapi::{JSContext, JSObject, JS_GetClass, MutableHandleValue};
use js::{JSCLASS_IS_DOMJSCLASS, JSCLASS_IS_GLOBAL};
use msg::constellation_msg::{ConstellationChan, PipelineId, WorkerId};
use net_traits::ResourceTask;
use profile_traits::mem;
use script_task::{CommonScriptMsg, ScriptChan, ScriptPort, ScriptTask};
use script_traits::TimerEventRequest;
use std::sync::mpsc::Sender;
use url::Url;
use util::mem::HeapSizeOf;

/// A freely-copyable reference to a rooted global object.
#[derive(Copy, Clone)]
pub enum GlobalRef<'a> {
    /// A reference to a `Window` object.
    Window(&'a window::Window),
    /// A reference to a `WorkerGlobalScope` object.
    Worker(&'a WorkerGlobalScope),
}

/// A stack-based rooted reference to a global object.
pub enum GlobalRoot {
    /// A root for a `Window` object.
    Window(Root<window::Window>),
    /// A root for a `WorkerGlobalScope` object.
    Worker(Root<WorkerGlobalScope>),
}

/// A traced reference to a global object, for use in fields of traced Rust
/// structures.
#[derive(JSTraceable, HeapSizeOf)]
#[must_root]
pub enum GlobalField {
    /// A field for a `Window` object.
    Window(JS<window::Window>),
    /// A field for a `WorkerGlobalScope` object.
    Worker(JS<WorkerGlobalScope>),
}

impl<'a> GlobalRef<'a> {
    /// Get the `JSContext` for the `JSRuntime` associated with the thread
    /// this global object is on.
    pub fn get_cx(&self) -> *mut JSContext {
        match *self {
            GlobalRef::Window(ref window) => window.get_cx(),
            GlobalRef::Worker(ref worker) => worker.get_cx(),
        }
    }

    /// Extract a `Window`, causing task failure if the global object is not
    /// a `Window`.
    pub fn as_window(&self) -> &window::Window {
        match *self {
            GlobalRef::Window(window) => window,
            GlobalRef::Worker(_) => panic!("expected a Window scope"),
        }
    }

    /// Get the `PipelineId` for this global scope.
    pub fn pipeline(&self) -> PipelineId {
        match *self {
            GlobalRef::Window(window) => window.pipeline(),
            GlobalRef::Worker(worker) => worker.pipeline(),
        }
    }

    /// Get a `mem::ProfilerChan` to send messages to the memory profiler task.
    pub fn mem_profiler_chan(&self) -> mem::ProfilerChan {
        match *self {
            GlobalRef::Window(window) => window.mem_profiler_chan(),
            GlobalRef::Worker(worker) => worker.mem_profiler_chan(),
        }
    }

    /// Get a `ConstellationChan` to send messages to the constellation channel when available.
    pub fn constellation_chan(&self) -> ConstellationChan {
        match *self {
            GlobalRef::Window(window) => window.constellation_chan(),
            GlobalRef::Worker(worker) => worker.constellation_chan(),
        }
    }

    /// Get the scheduler channel to request timer events.
    pub fn scheduler_chan(&self) -> Sender<TimerEventRequest> {
        match *self {
            GlobalRef::Window(window) => window.scheduler_chan(),
            GlobalRef::Worker(worker) => worker.scheduler_chan(),
        }
    }

    /// Get an `IpcSender<ScriptToDevtoolsControlMsg>` to send messages to Devtools
    /// task when available.
    pub fn devtools_chan(&self) -> Option<IpcSender<ScriptToDevtoolsControlMsg>> {
        match *self {
            GlobalRef::Window(window) => window.devtools_chan(),
            GlobalRef::Worker(worker) => worker.devtools_chan(),
        }
    }

    /// Get the `ResourceTask` for this global scope.
    pub fn resource_task(&self) -> ResourceTask {
        match *self {
            GlobalRef::Window(ref window) => {
                let doc = window.Document();
                let doc = doc.r();
                let loader = doc.loader();
                (*loader.resource_task).clone()
            }
            GlobalRef::Worker(ref worker) => worker.resource_task().clone(),
        }
    }

    /// Get the worker's id.
    pub fn get_worker_id(&self) -> Option<WorkerId> {
        match *self {
            GlobalRef::Window(_) => None,
            GlobalRef::Worker(ref worker) => Some(worker.get_worker_id()),
        }
    }

    /// Get next worker id.
    pub fn get_next_worker_id(&self) -> WorkerId {
        match *self {
            GlobalRef::Window(ref window) => window.get_next_worker_id(),
            GlobalRef::Worker(ref worker) => worker.get_next_worker_id()
        }
    }

    /// Get the URL for this global scope.
    pub fn get_url(&self) -> Url {
        match *self {
            GlobalRef::Window(ref window) => window.get_url(),
            GlobalRef::Worker(ref worker) => worker.get_url().clone(),
        }
    }

    /// `ScriptChan` used to send messages to the event loop of this global's
    /// thread.
    pub fn script_chan(&self) -> Box<ScriptChan + Send> {
        match *self {
            GlobalRef::Window(ref window) => window.script_chan(),
            GlobalRef::Worker(ref worker) => worker.script_chan(),
        }
    }

    /// Create a new sender/receiver pair that can be used to implement an on-demand
    /// event loop. Used for implementing web APIs that require blocking semantics
    /// without resorting to nested event loops.
    pub fn new_script_pair(&self) -> (Box<ScriptChan + Send>, Box<ScriptPort + Send>) {
        match *self {
            GlobalRef::Window(ref window) => window.new_script_pair(),
            GlobalRef::Worker(ref worker) => worker.new_script_pair(),
        }
    }

    /// Process a single event as if it were the next event in the task queue for
    /// this global.
    pub fn process_event(&self, msg: CommonScriptMsg) {
        match *self {
            GlobalRef::Window(_) => ScriptTask::process_event(msg),
            GlobalRef::Worker(ref worker) => worker.process_event(msg),
        }
    }

    /// Evaluate the JS messages on the `RootedValue` of this global
    pub fn evaluate_js_on_global_with_result(&self, code: &str, rval: MutableHandleValue) {
        match *self {
            GlobalRef::Window(window) => window.evaluate_js_on_global_with_result(code, rval),
            GlobalRef::Worker(worker) => worker.evaluate_js_on_global_with_result(code, rval),
        }
    }

    /// Set the `bool` value to indicate whether developer tools has requested
    /// updates from the global
    pub fn set_devtools_wants_updates(&self, send_updates: bool) {
        match *self {
            GlobalRef::Window(window) => window.set_devtools_wants_updates(send_updates),
            GlobalRef::Worker(worker) => worker.set_devtools_wants_updates(send_updates),
        }
    }
}

impl<'a> Reflectable for GlobalRef<'a> {
    fn reflector(&self) -> &Reflector {
        match *self {
            GlobalRef::Window(ref window) => window.reflector(),
            GlobalRef::Worker(ref worker) => worker.reflector(),
        }
    }
}

impl GlobalRoot {
    /// Obtain a safe reference to the global object that cannot outlive the
    /// lifetime of this root.
    pub fn r(&self) -> GlobalRef {
        match *self {
            GlobalRoot::Window(ref window) => GlobalRef::Window(window.r()),
            GlobalRoot::Worker(ref worker) => GlobalRef::Worker(worker.r()),
        }
    }
}

impl GlobalField {
    /// Create a new `GlobalField` from a rooted reference.
    #[allow(unrooted_must_root)]
    pub fn from_rooted(global: &GlobalRef) -> GlobalField {
        match *global {
            GlobalRef::Window(window) => GlobalField::Window(JS::from_ref(window)),
            GlobalRef::Worker(worker) => GlobalField::Worker(JS::from_ref(worker)),
        }
    }

    /// Create a stack-bounded root for this reference.
    pub fn root(&self) -> GlobalRoot {
        match *self {
            GlobalField::Window(ref window) => GlobalRoot::Window(window.root()),
            GlobalField::Worker(ref worker) => GlobalRoot::Worker(worker.root()),
        }
    }
}

/// Returns the global object of the realm that the given DOM object's reflector was created in.
pub fn global_object_for_reflector<T: Reflectable>(reflector: &T) -> GlobalRoot {
    global_object_for_js_object(*reflector.reflector().get_jsobject())
}

/// Returns the global object of the realm that the given JS object was created in.
#[allow(unrooted_must_root)]
pub fn global_object_for_js_object(obj: *mut JSObject) -> GlobalRoot {
    unsafe {
        let global = GetGlobalForObjectCrossCompartment(obj);
        let clasp = JS_GetClass(global);
        assert!(((*clasp).flags & (JSCLASS_IS_DOMJSCLASS | JSCLASS_IS_GLOBAL)) != 0);
        match native_from_reflector_jsmanaged(global) {
            Ok(window) => return GlobalRoot::Window(window),
            Err(_) => (),
        }

        match native_from_reflector_jsmanaged(global) {
            Ok(worker) => return GlobalRoot::Worker(worker),
            Err(_) => (),
        }

        panic!("found DOM global that doesn't unwrap to Window or WorkerGlobalScope")
    }
}
<commit_msg>Implement GlobalRef::reflector as an inherent method.<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/. */

//! Abstractions for global scopes.
//!
//! This module contains smart pointers to global scopes, to simplify writing
//! code that works in workers as well as window scopes.

use devtools_traits::ScriptToDevtoolsControlMsg;
use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use dom::bindings::conversions::native_from_reflector_jsmanaged;
use dom::bindings::js::{JS, Root};
use dom::bindings::utils::{Reflectable, Reflector};
use dom::window::{self, ScriptHelpers};
use dom::workerglobalscope::WorkerGlobalScope;
use ipc_channel::ipc::IpcSender;
use js::jsapi::{GetGlobalForObjectCrossCompartment};
use js::jsapi::{JSContext, JSObject, JS_GetClass, MutableHandleValue};
use js::{JSCLASS_IS_DOMJSCLASS, JSCLASS_IS_GLOBAL};
use msg::constellation_msg::{ConstellationChan, PipelineId, WorkerId};
use net_traits::ResourceTask;
use profile_traits::mem;
use script_task::{CommonScriptMsg, ScriptChan, ScriptPort, ScriptTask};
use script_traits::TimerEventRequest;
use std::sync::mpsc::Sender;
use url::Url;
use util::mem::HeapSizeOf;

/// A freely-copyable reference to a rooted global object.
#[derive(Copy, Clone)]
pub enum GlobalRef<'a> {
    /// A reference to a `Window` object.
    Window(&'a window::Window),
    /// A reference to a `WorkerGlobalScope` object.
    Worker(&'a WorkerGlobalScope),
}

/// A stack-based rooted reference to a global object.
pub enum GlobalRoot {
    /// A root for a `Window` object.
    Window(Root<window::Window>),
    /// A root for a `WorkerGlobalScope` object.
    Worker(Root<WorkerGlobalScope>),
}

/// A traced reference to a global object, for use in fields of traced Rust
/// structures.
#[derive(JSTraceable, HeapSizeOf)]
#[must_root]
pub enum GlobalField {
    /// A field for a `Window` object.
    Window(JS<window::Window>),
    /// A field for a `WorkerGlobalScope` object.
    Worker(JS<WorkerGlobalScope>),
}

impl<'a> GlobalRef<'a> {
    /// Get the `JSContext` for the `JSRuntime` associated with the thread
    /// this global object is on.
    pub fn get_cx(&self) -> *mut JSContext {
        match *self {
            GlobalRef::Window(ref window) => window.get_cx(),
            GlobalRef::Worker(ref worker) => worker.get_cx(),
        }
    }

    /// Extract a `Window`, causing task failure if the global object is not
    /// a `Window`.
    pub fn as_window(&self) -> &window::Window {
        match *self {
            GlobalRef::Window(window) => window,
            GlobalRef::Worker(_) => panic!("expected a Window scope"),
        }
    }

    /// Get the `PipelineId` for this global scope.
    pub fn pipeline(&self) -> PipelineId {
        match *self {
            GlobalRef::Window(window) => window.pipeline(),
            GlobalRef::Worker(worker) => worker.pipeline(),
        }
    }

    /// Get a `mem::ProfilerChan` to send messages to the memory profiler task.
    pub fn mem_profiler_chan(&self) -> mem::ProfilerChan {
        match *self {
            GlobalRef::Window(window) => window.mem_profiler_chan(),
            GlobalRef::Worker(worker) => worker.mem_profiler_chan(),
        }
    }

    /// Get a `ConstellationChan` to send messages to the constellation channel when available.
    pub fn constellation_chan(&self) -> ConstellationChan {
        match *self {
            GlobalRef::Window(window) => window.constellation_chan(),
            GlobalRef::Worker(worker) => worker.constellation_chan(),
        }
    }

    /// Get the scheduler channel to request timer events.
    pub fn scheduler_chan(&self) -> Sender<TimerEventRequest> {
        match *self {
            GlobalRef::Window(window) => window.scheduler_chan(),
            GlobalRef::Worker(worker) => worker.scheduler_chan(),
        }
    }

    /// Get an `IpcSender<ScriptToDevtoolsControlMsg>` to send messages to Devtools
    /// task when available.
    pub fn devtools_chan(&self) -> Option<IpcSender<ScriptToDevtoolsControlMsg>> {
        match *self {
            GlobalRef::Window(window) => window.devtools_chan(),
            GlobalRef::Worker(worker) => worker.devtools_chan(),
        }
    }

    /// Get the `ResourceTask` for this global scope.
    pub fn resource_task(&self) -> ResourceTask {
        match *self {
            GlobalRef::Window(ref window) => {
                let doc = window.Document();
                let doc = doc.r();
                let loader = doc.loader();
                (*loader.resource_task).clone()
            }
            GlobalRef::Worker(ref worker) => worker.resource_task().clone(),
        }
    }

    /// Get the worker's id.
    pub fn get_worker_id(&self) -> Option<WorkerId> {
        match *self {
            GlobalRef::Window(_) => None,
            GlobalRef::Worker(ref worker) => Some(worker.get_worker_id()),
        }
    }

    /// Get next worker id.
    pub fn get_next_worker_id(&self) -> WorkerId {
        match *self {
            GlobalRef::Window(ref window) => window.get_next_worker_id(),
            GlobalRef::Worker(ref worker) => worker.get_next_worker_id()
        }
    }

    /// Get the URL for this global scope.
    pub fn get_url(&self) -> Url {
        match *self {
            GlobalRef::Window(ref window) => window.get_url(),
            GlobalRef::Worker(ref worker) => worker.get_url().clone(),
        }
    }

    /// `ScriptChan` used to send messages to the event loop of this global's
    /// thread.
    pub fn script_chan(&self) -> Box<ScriptChan + Send> {
        match *self {
            GlobalRef::Window(ref window) => window.script_chan(),
            GlobalRef::Worker(ref worker) => worker.script_chan(),
        }
    }

    /// Create a new sender/receiver pair that can be used to implement an on-demand
    /// event loop. Used for implementing web APIs that require blocking semantics
    /// without resorting to nested event loops.
    pub fn new_script_pair(&self) -> (Box<ScriptChan + Send>, Box<ScriptPort + Send>) {
        match *self {
            GlobalRef::Window(ref window) => window.new_script_pair(),
            GlobalRef::Worker(ref worker) => worker.new_script_pair(),
        }
    }

    /// Process a single event as if it were the next event in the task queue for
    /// this global.
    pub fn process_event(&self, msg: CommonScriptMsg) {
        match *self {
            GlobalRef::Window(_) => ScriptTask::process_event(msg),
            GlobalRef::Worker(ref worker) => worker.process_event(msg),
        }
    }

    /// Evaluate the JS messages on the `RootedValue` of this global
    pub fn evaluate_js_on_global_with_result(&self, code: &str, rval: MutableHandleValue) {
        match *self {
            GlobalRef::Window(window) => window.evaluate_js_on_global_with_result(code, rval),
            GlobalRef::Worker(worker) => worker.evaluate_js_on_global_with_result(code, rval),
        }
    }

    /// Set the `bool` value to indicate whether developer tools has requested
    /// updates from the global
    pub fn set_devtools_wants_updates(&self, send_updates: bool) {
        match *self {
            GlobalRef::Window(window) => window.set_devtools_wants_updates(send_updates),
            GlobalRef::Worker(worker) => worker.set_devtools_wants_updates(send_updates),
        }
    }

    /// Returns the receiver's reflector.
    pub fn reflector(&self) -> &Reflector {
        match *self {
            GlobalRef::Window(ref window) => window.reflector(),
            GlobalRef::Worker(ref worker) => worker.reflector(),
        }
    }
}

impl GlobalRoot {
    /// Obtain a safe reference to the global object that cannot outlive the
    /// lifetime of this root.
    pub fn r(&self) -> GlobalRef {
        match *self {
            GlobalRoot::Window(ref window) => GlobalRef::Window(window.r()),
            GlobalRoot::Worker(ref worker) => GlobalRef::Worker(worker.r()),
        }
    }
}

impl GlobalField {
    /// Create a new `GlobalField` from a rooted reference.
    #[allow(unrooted_must_root)]
    pub fn from_rooted(global: &GlobalRef) -> GlobalField {
        match *global {
            GlobalRef::Window(window) => GlobalField::Window(JS::from_ref(window)),
            GlobalRef::Worker(worker) => GlobalField::Worker(JS::from_ref(worker)),
        }
    }

    /// Create a stack-bounded root for this reference.
    pub fn root(&self) -> GlobalRoot {
        match *self {
            GlobalField::Window(ref window) => GlobalRoot::Window(window.root()),
            GlobalField::Worker(ref worker) => GlobalRoot::Worker(worker.root()),
        }
    }
}

/// Returns the global object of the realm that the given DOM object's reflector was created in.
pub fn global_object_for_reflector<T: Reflectable>(reflector: &T) -> GlobalRoot {
    global_object_for_js_object(*reflector.reflector().get_jsobject())
}

/// Returns the global object of the realm that the given JS object was created in.
#[allow(unrooted_must_root)]
pub fn global_object_for_js_object(obj: *mut JSObject) -> GlobalRoot {
    unsafe {
        let global = GetGlobalForObjectCrossCompartment(obj);
        let clasp = JS_GetClass(global);
        assert!(((*clasp).flags & (JSCLASS_IS_DOMJSCLASS | JSCLASS_IS_GLOBAL)) != 0);
        match native_from_reflector_jsmanaged(global) {
            Ok(window) => return GlobalRoot::Window(window),
            Err(_) => (),
        }

        match native_from_reflector_jsmanaged(global) {
            Ok(worker) => return GlobalRoot::Worker(worker),
            Err(_) => (),
        }

        panic!("found DOM global that doesn't unwrap to Window or WorkerGlobalScope")
    }
}
<|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/. */

//! Generic types for font stuff.

use app_units::Au;
use byteorder::{ReadBytesExt, BigEndian};
use cssparser::Parser;
use num_traits::One;
use parser::{Parse, ParserContext};
use std::fmt::{self, Write};
use std::io::Cursor;
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
use values::distance::{ComputeSquaredDistance, SquaredDistance};

/// https://drafts.csswg.org/css-fonts-4/#feature-tag-value
#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue)]
pub struct FeatureTagValue<Integer> {
    /// A four-character tag, packed into a u32 (one byte per character).
    pub tag: FontTag,
    /// The actual value.
    pub value: Integer,
}

impl<Integer> ToCss for FeatureTagValue<Integer>
where
    Integer: One + ToCss + PartialEq,
{
    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    where
        W: Write,
    {
        self.tag.to_css(dest)?;
        // Don't serialize the default value.
        if self.value != Integer::one() {
            dest.write_char(' ')?;
            self.value.to_css(dest)?;
        }

        Ok(())
    }
}

/// Variation setting for a single feature, see:
///
/// https://drafts.csswg.org/css-fonts-4/#font-variation-settings-def
#[derive(Animate, Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToCss)]
pub struct VariationValue<Number> {
    /// A four-character tag, packed into a u32 (one byte per character).
    #[animation(constant)]
    pub tag: FontTag,
    /// The actual value.
    pub value: Number,
}

impl<Number> ComputeSquaredDistance for VariationValue<Number>
where
    Number: ComputeSquaredDistance,
{
    #[inline]
    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
        if self.tag != other.tag {
            return Err(());
        }
        self.value.compute_squared_distance(&other.value)
    }
}

/// A value both for font-variation-settings and font-feature-settings.
#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue)]
pub struct FontSettings<T>(pub Box<[T]>);

impl<T> FontSettings<T> {
    /// Default value of font settings as `normal`.
    #[inline]
    pub fn normal() -> Self {
        FontSettings(vec![].into_boxed_slice())
    }
}

impl<T: Parse> Parse for FontSettings<T> {
    /// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-feature-settings
    /// https://drafts.csswg.org/css-fonts-4/#font-variation-settings-def
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        if input.try(|i| i.expect_ident_matching("normal")).is_ok() {
            return Ok(Self::normal());
        }

        Ok(FontSettings(
            input.parse_comma_separated(|i| T::parse(context, i))?.into_boxed_slice()
        ))
    }
}

impl<T: ToCss> ToCss for FontSettings<T> {
    /// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-feature-settings
    /// https://drafts.csswg.org/css-fonts-4/#font-variation-settings-def
    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    where
        W: Write,
    {
        if self.0.is_empty() {
            return dest.write_str("normal");
        }

        let mut first = true;
        for item in self.0.iter() {
            if !first {
                dest.write_str(", ")?;
            }
            first = false;
            item.to_css(dest)?;
        }

        Ok(())
    }
}

/// A font four-character tag, represented as a u32 for convenience.
///
/// See:
///   https://drafts.csswg.org/css-fonts-4/#font-variation-settings-def
///   https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-feature-settings
///
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue)]
pub struct FontTag(pub u32);

impl ToCss for FontTag {
    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    where
        W: Write,
    {
        use byteorder::{BigEndian, ByteOrder};
        use std::str;

        let mut raw = [0u8; 4];
        BigEndian::write_u32(&mut raw, self.0);
        str::from_utf8(&raw).unwrap_or_default().to_css(dest)
    }
}

impl Parse for FontTag {
    fn parse<'i, 't>(
        _context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        let location = input.current_source_location();
        let tag = input.expect_string()?;

        // allowed strings of length 4 containing chars: <U+20, U+7E>
        if tag.len() != 4 || tag.as_bytes().iter().any(|c| *c < b' ' || *c > b'~') {
            return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
        }

        let mut raw = Cursor::new(tag.as_bytes());
        Ok(FontTag(raw.read_u32::<BigEndian>().unwrap()))
    }
}

#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf)]
#[derive(PartialEq, ToAnimatedValue, ToAnimatedZero)]
/// Additional information for keyword-derived font sizes.
pub struct KeywordInfo<Length> {
    /// The keyword used
    pub kw: KeywordSize,
    /// A factor to be multiplied by the computed size of the keyword
    pub factor: f32,
    /// An additional Au offset to add to the kw*factor in the case of calcs
    pub offset: Length,
}

impl<L> KeywordInfo<L>
where
    Au: Into<L>,
{
    /// KeywordInfo value for font-size: medium
    pub fn medium() -> Self {
        KeywordSize::Medium.into()
    }
}

impl<L> From<KeywordSize> for KeywordInfo<L>
where
    Au: Into<L>,
{
    fn from(x: KeywordSize) -> Self {
        KeywordInfo {
            kw: x,
            factor: 1.,
            offset: Au(0).into(),
        }
    }
}

/// CSS font keywords
#[derive(Animate, ComputeSquaredDistance, MallocSizeOf, ToAnimatedValue, ToAnimatedZero)]
#[derive(Clone, Copy, Debug, PartialEq)]
#[allow(missing_docs)]
pub enum KeywordSize {
    XXSmall = 1, // This is to enable the NonZero optimization
                 // which simplifies the representation of Option<KeywordSize>
                 // in bindgen
    XSmall,
    Small,
    Medium,
    Large,
    XLarge,
    XXLarge,
    // This is not a real font keyword and will not parse
    // HTML font-size 7 corresponds to this value
    XXXLarge,
}

impl KeywordSize {
    /// Convert to an HTML <font size> value
    pub fn html_size(&self) -> u8 {
        match *self {
            KeywordSize::XXSmall => 0,
            KeywordSize::XSmall => 1,
            KeywordSize::Small => 2,
            KeywordSize::Medium => 3,
            KeywordSize::Large => 4,
            KeywordSize::XLarge => 5,
            KeywordSize::XXLarge => 6,
            KeywordSize::XXXLarge => 7,
        }
    }
}

impl Default for KeywordSize {
    fn default() -> Self {
        KeywordSize::Medium
    }
}

impl ToCss for KeywordSize {
    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    where
        W: Write,
    {
        dest.write_str(match *self {
            KeywordSize::XXSmall => "xx-small",
            KeywordSize::XSmall => "x-small",
            KeywordSize::Small => "small",
            KeywordSize::Medium => "medium",
            KeywordSize::Large => "large",
            KeywordSize::XLarge => "x-large",
            KeywordSize::XXLarge => "xx-large",
            KeywordSize::XXXLarge => {
                debug_assert!(
                    false,
                    "We should never serialize specified values set via HTML presentation attributes"
                );
                "-servo-xxx-large"
            },
        })
    }
}
<commit_msg>Remove some useless manual layout optimisation for KeywordSize<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/. */

//! Generic types for font stuff.

use app_units::Au;
use byteorder::{ReadBytesExt, BigEndian};
use cssparser::Parser;
use num_traits::One;
use parser::{Parse, ParserContext};
use std::fmt::{self, Write};
use std::io::Cursor;
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
use values::distance::{ComputeSquaredDistance, SquaredDistance};

/// https://drafts.csswg.org/css-fonts-4/#feature-tag-value
#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue)]
pub struct FeatureTagValue<Integer> {
    /// A four-character tag, packed into a u32 (one byte per character).
    pub tag: FontTag,
    /// The actual value.
    pub value: Integer,
}

impl<Integer> ToCss for FeatureTagValue<Integer>
where
    Integer: One + ToCss + PartialEq,
{
    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    where
        W: Write,
    {
        self.tag.to_css(dest)?;
        // Don't serialize the default value.
        if self.value != Integer::one() {
            dest.write_char(' ')?;
            self.value.to_css(dest)?;
        }

        Ok(())
    }
}

/// Variation setting for a single feature, see:
///
/// https://drafts.csswg.org/css-fonts-4/#font-variation-settings-def
#[derive(Animate, Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToCss)]
pub struct VariationValue<Number> {
    /// A four-character tag, packed into a u32 (one byte per character).
    #[animation(constant)]
    pub tag: FontTag,
    /// The actual value.
    pub value: Number,
}

impl<Number> ComputeSquaredDistance for VariationValue<Number>
where
    Number: ComputeSquaredDistance,
{
    #[inline]
    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
        if self.tag != other.tag {
            return Err(());
        }
        self.value.compute_squared_distance(&other.value)
    }
}

/// A value both for font-variation-settings and font-feature-settings.
#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue)]
pub struct FontSettings<T>(pub Box<[T]>);

impl<T> FontSettings<T> {
    /// Default value of font settings as `normal`.
    #[inline]
    pub fn normal() -> Self {
        FontSettings(vec![].into_boxed_slice())
    }
}

impl<T: Parse> Parse for FontSettings<T> {
    /// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-feature-settings
    /// https://drafts.csswg.org/css-fonts-4/#font-variation-settings-def
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        if input.try(|i| i.expect_ident_matching("normal")).is_ok() {
            return Ok(Self::normal());
        }

        Ok(FontSettings(
            input.parse_comma_separated(|i| T::parse(context, i))?.into_boxed_slice()
        ))
    }
}

impl<T: ToCss> ToCss for FontSettings<T> {
    /// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-feature-settings
    /// https://drafts.csswg.org/css-fonts-4/#font-variation-settings-def
    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    where
        W: Write,
    {
        if self.0.is_empty() {
            return dest.write_str("normal");
        }

        let mut first = true;
        for item in self.0.iter() {
            if !first {
                dest.write_str(", ")?;
            }
            first = false;
            item.to_css(dest)?;
        }

        Ok(())
    }
}

/// A font four-character tag, represented as a u32 for convenience.
///
/// See:
///   https://drafts.csswg.org/css-fonts-4/#font-variation-settings-def
///   https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-feature-settings
///
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue)]
pub struct FontTag(pub u32);

impl ToCss for FontTag {
    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    where
        W: Write,
    {
        use byteorder::{BigEndian, ByteOrder};
        use std::str;

        let mut raw = [0u8; 4];
        BigEndian::write_u32(&mut raw, self.0);
        str::from_utf8(&raw).unwrap_or_default().to_css(dest)
    }
}

impl Parse for FontTag {
    fn parse<'i, 't>(
        _context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        let location = input.current_source_location();
        let tag = input.expect_string()?;

        // allowed strings of length 4 containing chars: <U+20, U+7E>
        if tag.len() != 4 || tag.as_bytes().iter().any(|c| *c < b' ' || *c > b'~') {
            return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
        }

        let mut raw = Cursor::new(tag.as_bytes());
        Ok(FontTag(raw.read_u32::<BigEndian>().unwrap()))
    }
}

#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf)]
#[derive(PartialEq, ToAnimatedValue, ToAnimatedZero)]
/// Additional information for keyword-derived font sizes.
pub struct KeywordInfo<Length> {
    /// The keyword used
    pub kw: KeywordSize,
    /// A factor to be multiplied by the computed size of the keyword
    pub factor: f32,
    /// An additional Au offset to add to the kw*factor in the case of calcs
    pub offset: Length,
}

impl<L> KeywordInfo<L>
where
    Au: Into<L>,
{
    /// KeywordInfo value for font-size: medium
    pub fn medium() -> Self {
        KeywordSize::Medium.into()
    }
}

impl<L> From<KeywordSize> for KeywordInfo<L>
where
    Au: Into<L>,
{
    fn from(x: KeywordSize) -> Self {
        KeywordInfo {
            kw: x,
            factor: 1.,
            offset: Au(0).into(),
        }
    }
}

/// CSS font keywords
#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf)]
#[derive(PartialEq, ToAnimatedValue, ToAnimatedZero)]
#[allow(missing_docs)]
pub enum KeywordSize {
    XXSmall,
    XSmall,
    Small,
    Medium,
    Large,
    XLarge,
    XXLarge,
    // This is not a real font keyword and will not parse
    // HTML font-size 7 corresponds to this value
    XXXLarge,
}

impl KeywordSize {
    /// Convert to an HTML <font size> value
    #[inline]
    pub fn html_size(self) -> u8 {
        self as u8
    }
}

impl Default for KeywordSize {
    fn default() -> Self {
        KeywordSize::Medium
    }
}

impl ToCss for KeywordSize {
    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    where
        W: Write,
    {
        dest.write_str(match *self {
            KeywordSize::XXSmall => "xx-small",
            KeywordSize::XSmall => "x-small",
            KeywordSize::Small => "small",
            KeywordSize::Medium => "medium",
            KeywordSize::Large => "large",
            KeywordSize::XLarge => "x-large",
            KeywordSize::XXLarge => "xx-large",
            KeywordSize::XXXLarge => {
                debug_assert!(
                    false,
                    "We should never serialize specified values set via HTML presentation attributes"
                );
                "-servo-xxx-large"
            },
        })
    }
}
<|endoftext|>


<commit_before>#![crate_id(name="seq", vers="1.0.0", author="Daniel MacDougall")]

#![feature(macro_rules)]

// TODO: Make -w flag work with decimals
// TODO: Support -f flag

extern crate getopts;
extern crate libc;

use std::cmp;
use std::os;

#[path = "../common/util.rs"]
mod util;

static NAME: &'static str = "seq";

#[deriving(Clone)]
struct SeqOptions {
    separator: String,
    terminator: Option<String>,
    widths: bool
}

fn parse_float(s: &str) -> Result<f64, String>{
    match from_str(s) {
        Some(n) => Ok(n),
        None => Err(format!("seq: invalid floating point argument: {:s}", s))
    }
}

fn escape_sequences(s: &str) -> String {
    s.replace("\\n", "\n").
        replace("\\t", "\t")
}

fn parse_options(args: Vec<String>, options: &mut SeqOptions) -> Result<Vec<String>, int> {
    let mut seq_args = vec!();
    let program = args.get(0).clone();
    let mut iter = args.move_iter().skip(1);
    loop {
        match iter.next() {
            Some(arg) => match arg.as_slice() {
                "--help" | "-h" => {
                    print_help(&program);
                    return Err(0);
                }
                "--version" | "-V" => {
                    print_version();
                    return Err(0);
                }
                "-s" | "--separator" => match iter.next() {
                    Some(sep) => options.separator = sep,
                    None => {
                        show_error!("expected a separator after {}", arg);
                        return Err(1);
                    }
                },
                "-t" | "--terminator" => match iter.next() {
                    Some(term) => options.terminator = Some(term),
                    None => {
                        show_error!("expected a terminator after '{}'", arg);
                        return Err(1);
                    }
                },
                "-w" | "--widths" => options.widths = true,
                "--" => {
                    seq_args.push_all_move(iter.collect());
                    break;
                },
                _ => {
                    if arg.len() > 1 && arg.as_slice().char_at(0) == '-' {
                        let argptr: *String = &arg;  // escape from the borrow checker
                        let mut chiter = unsafe { (*argptr).as_slice() }.chars().skip(1);
                        let mut ch = ' ';
                        while match chiter.next() { Some(m) => { ch = m; true } None => false } {
                            match ch {
                                'h' => {
                                    print_help(&program);
                                    return Err(0);
                                }
                                'V' => {
                                    print_version();
                                    return Err(0);
                                }
                                's' => match iter.next() {
                                    Some(sep) => {
                                        options.separator = sep;
                                        let next = chiter.next();
                                        if next.is_some() {
                                            show_error!("unexpected character ('{}')", next.unwrap());
                                            return Err(1);
                                        }
                                    }
                                    None => {
                                        show_error!("expected a separator after {}", arg);
                                        return Err(1);
                                    }
                                },
                                't' => match iter.next() {
                                    Some(term) => {
                                        options.terminator = Some(term);
                                        let next = chiter.next();
                                        if next.is_some() {
                                            show_error!("unexpected character ('{}')", next.unwrap());
                                            return Err(1);
                                        }
                                    }
                                    None => {
                                        show_error!("expected a terminator after {}", arg);
                                        return Err(1);
                                    }
                                },
                                'w' => options.widths = true,
                                _ => { seq_args.push(arg); break }
                            }
                        }
                    } else {
                        seq_args.push(arg);
                    }
                }
            },
            None => break
        }
    }
    Ok(seq_args)
}

fn print_help(program: &String) {
    let opts = [
        getopts::optopt("s", "separator", "Separator character (defaults to \\n)", ""),
        getopts::optopt("t", "terminator", "Terminator character (defaults to separator)", ""),
        getopts::optflag("w", "widths", "Equalize widths of all numbers by padding with zeros"),
        getopts::optflag("h", "help", "print this help text and exit"),
        getopts::optflag("V", "version", "print version and exit"),
    ];
    println!("seq 1.0.0\n");
    println!("Usage:\n  {} [-w] [-s string] [-t string] [first [step]] last\n", *program);
    println!("{:s}", getopts::usage("Print sequences of numbers", opts));
}

fn print_version() {
    println!("seq 1.0.0\n");
}

#[allow(dead_code)]
fn main() { os::set_exit_status(uumain(os::args())); }

pub fn uumain(args: Vec<String>) -> int {
    let program = args.get(0).clone();
    let mut options = SeqOptions {
        separator: "\n".to_string(),
        terminator: None,
        widths: false
    };
    let free = match parse_options(args, &mut options) {
        Ok(m) => m,
        Err(f) => return f
    };
    if free.len() < 1 || free.len() > 3 {
        crash!(1, "too {} operands.\nTry '{} --help' for more information.",
               if free.len() < 1 { "few" } else { "many" }, program);
    }
    let mut largest_dec = 0;
    let mut padding = 0;
    let first = if free.len() > 1 {
        let slice = free.get(0).as_slice();
        let len = slice.len();
        let dec = slice.find('.').unwrap_or(len);
        largest_dec = len - dec;
        padding = dec;
        match parse_float(slice) {
            Ok(n) => n,
            Err(s) => { show_error!("{:s}", s); return 1; }
        }
    } else {
        1.0
    };
    let step = if free.len() > 2 {
        let slice = free.get(1).as_slice();
        let len = slice.len();
        let dec = slice.find('.').unwrap_or(len);
        largest_dec = cmp::max(largest_dec, len - dec);
        padding = cmp::max(padding, dec);
        match parse_float(free.get(1).as_slice()) {
            Ok(n) => n,
            Err(s) => { show_error!("{:s}", s); return 1; }
        }
    } else {
        1.0
    };
    let last = {
        let slice = free.get(free.len() - 1).as_slice();
        padding = cmp::max(padding, slice.find('.').unwrap_or(slice.len()));
        match parse_float(slice) {
            Ok(n) => n,
            Err(s) => { show_error!("{:s}", s); return 1; }
        }
    };
    let separator = escape_sequences(options.separator.as_slice());
    let terminator = match options.terminator {
        Some(term) => escape_sequences(term.as_slice()),
        None => separator.clone()
    };
    print_seq(first, step, last, largest_dec, separator, terminator, options.widths, padding);

    0
}

#[inline(always)]
fn done_printing(next: f64, step: f64, last: f64) -> bool {
    if step >= 0f64 {
        next > last
    } else {
        next < last
    }
}

fn print_seq(first: f64, step: f64, last: f64, largest_dec: uint, separator: String, terminator: String, pad: bool, padding: uint) {
    let mut i = 0;
    let mut value = first + i as f64 * step;
    while !done_printing(value, step, last) {
        let istr = value.to_str();
        let ilen = istr.len();
        let before_dec = istr.as_slice().find('.').unwrap_or(ilen);
        if pad && before_dec < padding {
            for _ in range(0, padding - before_dec) {
                print!("0");
            }
        }
        print!("{}", istr);
        let mut idec = ilen - before_dec;
        if idec < largest_dec {
            if idec == 0 {
                print!(".");
                idec += 1;
            }
            for _ in range(idec, largest_dec) {
                print!("0")
            }
        }
        i += 1;
        value = first + i as f64 * step;
        if !done_printing(value, step, last) {
            print!("{:s}", separator);
        }
    }
    if (first >= last && step < 0f64) || (first <= last && step > 0f64) {
        print!("{:s}", terminator);
    }
}
<commit_msg>seq: fix broken pipe on Busybox test<commit_after>#![crate_id(name="seq", vers="1.0.0", author="Daniel MacDougall")]

#![feature(macro_rules)]

// TODO: Make -w flag work with decimals
// TODO: Support -f flag

extern crate getopts;
extern crate libc;

use std::cmp;
use std::io;
use std::os;

#[path = "../common/util.rs"]
mod util;

static NAME: &'static str = "seq";

macro_rules! pipe_write(
    ($($args:expr),+) => (
        match write!(&mut io::stdout() as &mut Writer, $($args),+) {
            Ok(_) => {}
            Err(f) =>
                if f.kind == io::BrokenPipe {
                    return
                } else {
                    fail!("{}", f.to_str())
                }
        }
    )
)

#[deriving(Clone)]
struct SeqOptions {
    separator: String,
    terminator: Option<String>,
    widths: bool
}

fn parse_float(s: &str) -> Result<f64, String>{
    match from_str(s) {
        Some(n) => Ok(n),
        None => Err(format!("seq: invalid floating point argument: {:s}", s))
    }
}

fn escape_sequences(s: &str) -> String {
    s.replace("\\n", "\n").
        replace("\\t", "\t")
}

fn parse_options(args: Vec<String>, options: &mut SeqOptions) -> Result<Vec<String>, int> {
    let mut seq_args = vec!();
    let program = args.get(0).clone();
    let mut iter = args.move_iter().skip(1);
    loop {
        match iter.next() {
            Some(arg) => match arg.as_slice() {
                "--help" | "-h" => {
                    print_help(&program);
                    return Err(0);
                }
                "--version" | "-V" => {
                    print_version();
                    return Err(0);
                }
                "-s" | "--separator" => match iter.next() {
                    Some(sep) => options.separator = sep,
                    None => {
                        show_error!("expected a separator after {}", arg);
                        return Err(1);
                    }
                },
                "-t" | "--terminator" => match iter.next() {
                    Some(term) => options.terminator = Some(term),
                    None => {
                        show_error!("expected a terminator after '{}'", arg);
                        return Err(1);
                    }
                },
                "-w" | "--widths" => options.widths = true,
                "--" => {
                    seq_args.push_all_move(iter.collect());
                    break;
                },
                _ => {
                    if arg.len() > 1 && arg.as_slice().char_at(0) == '-' {
                        let argptr: *String = &arg;  // escape from the borrow checker
                        let mut chiter = unsafe { (*argptr).as_slice() }.chars().skip(1);
                        let mut ch = ' ';
                        while match chiter.next() { Some(m) => { ch = m; true } None => false } {
                            match ch {
                                'h' => {
                                    print_help(&program);
                                    return Err(0);
                                }
                                'V' => {
                                    print_version();
                                    return Err(0);
                                }
                                's' => match iter.next() {
                                    Some(sep) => {
                                        options.separator = sep;
                                        let next = chiter.next();
                                        if next.is_some() {
                                            show_error!("unexpected character ('{}')", next.unwrap());
                                            return Err(1);
                                        }
                                    }
                                    None => {
                                        show_error!("expected a separator after {}", arg);
                                        return Err(1);
                                    }
                                },
                                't' => match iter.next() {
                                    Some(term) => {
                                        options.terminator = Some(term);
                                        let next = chiter.next();
                                        if next.is_some() {
                                            show_error!("unexpected character ('{}')", next.unwrap());
                                            return Err(1);
                                        }
                                    }
                                    None => {
                                        show_error!("expected a terminator after {}", arg);
                                        return Err(1);
                                    }
                                },
                                'w' => options.widths = true,
                                _ => { seq_args.push(arg); break }
                            }
                        }
                    } else {
                        seq_args.push(arg);
                    }
                }
            },
            None => break
        }
    }
    Ok(seq_args)
}

fn print_help(program: &String) {
    let opts = [
        getopts::optopt("s", "separator", "Separator character (defaults to \\n)", ""),
        getopts::optopt("t", "terminator", "Terminator character (defaults to separator)", ""),
        getopts::optflag("w", "widths", "Equalize widths of all numbers by padding with zeros"),
        getopts::optflag("h", "help", "print this help text and exit"),
        getopts::optflag("V", "version", "print version and exit"),
    ];
    println!("seq 1.0.0\n");
    println!("Usage:\n  {} [-w] [-s string] [-t string] [first [step]] last\n", *program);
    println!("{:s}", getopts::usage("Print sequences of numbers", opts));
}

fn print_version() {
    println!("seq 1.0.0\n");
}

#[allow(dead_code)]
fn main() { os::set_exit_status(uumain(os::args())); }

pub fn uumain(args: Vec<String>) -> int {
    let program = args.get(0).clone();
    let mut options = SeqOptions {
        separator: "\n".to_string(),
        terminator: None,
        widths: false
    };
    let free = match parse_options(args, &mut options) {
        Ok(m) => m,
        Err(f) => return f
    };
    if free.len() < 1 || free.len() > 3 {
        crash!(1, "too {} operands.\nTry '{} --help' for more information.",
               if free.len() < 1 { "few" } else { "many" }, program);
    }
    let mut largest_dec = 0;
    let mut padding = 0;
    let first = if free.len() > 1 {
        let slice = free.get(0).as_slice();
        let len = slice.len();
        let dec = slice.find('.').unwrap_or(len);
        largest_dec = len - dec;
        padding = dec;
        match parse_float(slice) {
            Ok(n) => n,
            Err(s) => { show_error!("{:s}", s); return 1; }
        }
    } else {
        1.0
    };
    let step = if free.len() > 2 {
        let slice = free.get(1).as_slice();
        let len = slice.len();
        let dec = slice.find('.').unwrap_or(len);
        largest_dec = cmp::max(largest_dec, len - dec);
        padding = cmp::max(padding, dec);
        match parse_float(free.get(1).as_slice()) {
            Ok(n) => n,
            Err(s) => { show_error!("{:s}", s); return 1; }
        }
    } else {
        1.0
    };
    let last = {
        let slice = free.get(free.len() - 1).as_slice();
        padding = cmp::max(padding, slice.find('.').unwrap_or(slice.len()));
        match parse_float(slice) {
            Ok(n) => n,
            Err(s) => { show_error!("{:s}", s); return 1; }
        }
    };
    let separator = escape_sequences(options.separator.as_slice());
    let terminator = match options.terminator {
        Some(term) => escape_sequences(term.as_slice()),
        None => separator.clone()
    };
    print_seq(first, step, last, largest_dec, separator, terminator, options.widths, padding);

    0
}

#[inline(always)]
fn done_printing(next: f64, step: f64, last: f64) -> bool {
    if step >= 0f64 {
        next > last
    } else {
        next < last
    }
}

fn print_seq(first: f64, step: f64, last: f64, largest_dec: uint, separator: String, terminator: String, pad: bool, padding: uint) {
    let mut i = 0;
    let mut value = first + i as f64 * step;
    while !done_printing(value, step, last) {
        let istr = value.to_str();
        let ilen = istr.len();
        let before_dec = istr.as_slice().find('.').unwrap_or(ilen);
        if pad && before_dec < padding {
            for _ in range(0, padding - before_dec) {
                pipe_write!("0");
            }
        }
        pipe_write!("{}", istr);
        let mut idec = ilen - before_dec;
        if idec < largest_dec {
            if idec == 0 {
                pipe_write!(".");
                idec += 1;
            }
            for _ in range(idec, largest_dec) {
                pipe_write!("0")
            }
        }
        i += 1;
        value = first + i as f64 * step;
        if !done_printing(value, step, last) {
            pipe_write!("{:s}", separator);
        }
    }
    if (first >= last && step < 0f64) || (first <= last && step > 0f64) {
        pipe_write!("{:s}", terminator);
    }
}
<|endoftext|>


<commit_before>use std::collections::{HashMap};
use grit;

pub fn optimize(prog: grit::ProgDef) -> grit::ProgDef {
  let mut prog = prog;
  let prog_info = collect_prog_info(&prog);
  prog.fun_defs = prog.fun_defs.map_in_place(|mut fun_def| {
    let fun_info = prog_info.get(&fun_def.name).unwrap();
    fun_def.blocks = fun_def.blocks.map_in_place(|mut block| {
      block.ops = block.ops.map_in_place(|op| update_op_callees(fun_info, op));
      block.jump = update_jump_callees(fun_info, block.jump);
      block
    });
    fun_def
  });
  prog
}

type ProgInfo = HashMap<grit::FunName, FunInfo>;

#[derive(Debug, PartialEq, Clone)]
struct FunInfo {
  capture_infos: Vec<Info>,
  var_infos: Vec<Info>,
  return_info: Info,
}

#[derive(Debug, PartialEq, Clone)]
enum Info {
  Any,
  Fun(grit::FunName, usize),
  No,
}

fn update_op_callees(fun_info: &FunInfo, op: grit::Op) -> grit::Op {
  match op {
    grit::Op::Call(target_var, callee, args) =>
      grit::Op::Call(target_var, update_callee(fun_info, callee), args),
    op => op,
  }
}

fn update_jump_callees(fun_info: &FunInfo, jump: grit::Jump) -> grit::Jump {
  match jump {
    grit::Jump::TailCall(callee, args) =>
      grit::Jump::TailCall(update_callee(fun_info, callee), args),
    jump => jump,
  }
}

fn update_callee(fun_info: &FunInfo, callee: grit::Callee) -> grit::Callee {
  match callee {
    grit::Callee::Unknown(val) =>
      match val_info(fun_info, &val) {
        Info::Fun(fun_name, fun_captc) =>
          if fun_captc == 0 {
            grit::Callee::Combinator(fun_name)
          } else {
            grit::Callee::KnownClosure(fun_name, val)
          },
        _ =>
          grit::Callee::Unknown(val)
      },
    callee => callee,
  }
}

fn collect_prog_info(prog: &grit::ProgDef) -> ProgInfo {
  let mut prog_info: ProgInfo = prog.fun_defs.iter().map(|fun_def| {
    (fun_def.name.clone(), FunInfo {
      capture_infos: (0..fun_def.capture_count).map(|_| Info::Any).collect(),
      var_infos: (0..fun_def.var_count).map(|_| Info::Any).collect(),
      return_info: Info::Any,
    })
  }).collect();

  loop {
    let prev_info = prog_info.clone();

    for fun_def in prog.fun_defs.iter() {
      for block in fun_def.blocks.iter() {
        for op in block.ops.iter() {
          op_info_update(&mut prog_info, &fun_def.name, op);
        }
        jump_info_update(&mut prog_info, &fun_def.name, &block.jump);
      }
    }

    if prev_info == prog_info {
      break
    }
  }

  prog_info
}

fn op_info_update(prog_info: &mut ProgInfo, fun_name: &grit::FunName, op: &grit::Op) {
  match *op {
    grit::Op::Call(ref result_var, ref callee, _) =>
      prog_info.get_mut(fun_name).unwrap().var_infos[result_var.0] = join_info(
        &prog_info.get(fun_name).unwrap().var_infos[result_var.0],
        return_info(prog_info, callee)),
    grit::Op::ExternCall(ref result_var, _, _) =>
      prog_info.get_mut(fun_name).unwrap().var_infos[result_var.0] = Info::No,
    grit::Op::AllocClos(ref closs) =>
      for &(ref clos_var, ref clos_name, ref captures) in closs.iter() {
        prog_info.get_mut(fun_name).unwrap().var_infos[clos_var.0] = join_info(
          &prog_info.get(fun_name).unwrap().var_infos[clos_var.0],
          Info::Fun(clos_name.clone(), captures.len()));
        for (idx, capture_val) in captures.iter().enumerate() {
          prog_info.get_mut(clos_name).unwrap().capture_infos[idx] = join_info(
            &prog_info.get(clos_name).unwrap().capture_infos[idx],
            val_info(prog_info.get(fun_name).unwrap(), capture_val));
        }
      },
    grit::Op::Assign(ref var_vals) =>
      for &(ref var, ref val) in var_vals.iter() {
        prog_info.get_mut(fun_name).unwrap().var_infos[var.0] = join_info(
          &prog_info.get(fun_name).unwrap().var_infos[var.0],
          val_info(prog_info.get(fun_name).unwrap(), val));
      },
  }
}

fn jump_info_update(prog_info: &mut ProgInfo,
  fun_name: &grit::FunName, jump: &grit::Jump)
{
  match *jump {
    grit::Jump::Goto(_) => { },
    grit::Jump::Return(ref val) =>
      prog_info.get_mut(fun_name).unwrap().return_info = join_info(
        &prog_info.get(fun_name).unwrap().return_info,
        val_info(prog_info.get(fun_name).unwrap(), val)),
    grit::Jump::TailCall(ref callee, _) =>
      prog_info.get_mut(fun_name).unwrap().return_info = join_info(
        &prog_info.get(fun_name).unwrap().return_info,
        return_info(prog_info, callee)),
    grit::Jump::Branch(_, _, _) => { },
  }
}

fn return_info(prog_info: &ProgInfo, callee: &grit::Callee) -> Info {
  match *callee {
    grit::Callee::Combinator(ref callee_name) |
    grit::Callee::KnownClosure(ref callee_name, _) =>
      prog_info.get(callee_name).unwrap().return_info.clone(),
    grit::Callee::Unknown(_) =>
      Info::No,
  }
}

fn val_info(fun_info: &FunInfo, val: &grit::Val) -> Info {
  match *val {
    grit::Val::Var(ref var) => fun_info.var_infos[var.0].clone(),
    grit::Val::Arg(_) => Info::No,
    grit::Val::Capture(idx) => fun_info.capture_infos[idx].clone(),
    grit::Val::Combinator(ref fun_name) => Info::Fun(fun_name.clone(), 0),
    grit::Val::Obj(_) | grit::Val::Int(_) => Info::No,
    grit::Val::True | grit::Val::False => Info::No,
  }
}

fn join_info(info_1: &Info, info_2: Info) -> Info {
  match *info_1 {
    Info::Any => info_2,
    Info::Fun(ref name_1, captures_1) => match info_2 {
      Info::Fun(name_2, captures_2) =>
        if *name_1 == name_2 {
          assert!(captures_1 == captures_2);
          Info::Fun(name_2, captures_2)
        } else {
          Info::No
        },
      Info::Any =>
        Info::Fun(name_1.clone(), captures_1),
      _ =>
        Info::No,
    },
    Info::No => Info::No,
  }
}
<commit_msg>Optimize only valid calls<commit_after>use std::collections::{HashMap};
use grit;

pub fn optimize(prog: grit::ProgDef) -> grit::ProgDef {
  let mut prog = prog;
  let prog_info = collect_prog_info(&prog);
  prog.fun_defs = prog.fun_defs.map_in_place(|mut fun_def| {
    let fun_info = prog_info.get(&fun_def.name).unwrap();
    fun_def.blocks = fun_def.blocks.map_in_place(|mut block| {
      block.ops = block.ops.map_in_place(|op|
        update_op_callees(&prog_info, fun_info, op));
      block.jump = update_jump_callees(&prog_info, fun_info, block.jump);
      block
    });
    fun_def
  });
  prog
}

type ProgInfo = HashMap<grit::FunName, FunInfo>;

#[derive(Debug, PartialEq, Clone)]
struct FunInfo {
  arg_count: usize,
  capture_infos: Vec<Info>,
  var_infos: Vec<Info>,
  return_info: Info,
}

#[derive(Debug, PartialEq, Clone)]
enum Info {
  Any,
  Fun(grit::FunName),
  No,
}

fn update_op_callees(prog_info: &ProgInfo, fun_info: &FunInfo,
  op: grit::Op) -> grit::Op
{
  match op {
    grit::Op::Call(target_var, callee, args) =>
      grit::Op::Call(target_var, update_callee(prog_info, fun_info,
        callee, args.len()), args),
    op => op,
  }
}

fn update_jump_callees(prog_info: &ProgInfo, fun_info: &FunInfo,
  jump: grit::Jump) -> grit::Jump
{
  match jump {
    grit::Jump::TailCall(callee, args) =>
      grit::Jump::TailCall(update_callee(prog_info, fun_info,
        callee, args.len()), args),
    jump => jump,
  }
}

fn update_callee(prog_info: &ProgInfo, fun_info: &FunInfo,
  callee: grit::Callee, arg_count: usize) -> grit::Callee
{
  match callee {
    grit::Callee::Unknown(val) =>
      match val_info(fun_info, &val) {
        Info::Fun(callee_name) => {
          let callee_info = prog_info.get(&callee_name).unwrap();
          if callee_info.arg_count == arg_count {
            if callee_info.capture_infos.len() == 0 {
              grit::Callee::Combinator(callee_name)
            } else {
              grit::Callee::KnownClosure(callee_name, val)
            }
          } else {
            grit::Callee::Unknown(val)
          }
        },
        _ =>
          grit::Callee::Unknown(val)
      },
    callee => callee,
  }
}

fn collect_prog_info(prog: &grit::ProgDef) -> ProgInfo {
  let mut prog_info: ProgInfo = prog.fun_defs.iter().map(|fun_def| {
    (fun_def.name.clone(), FunInfo {
      arg_count: fun_def.arg_count,
      capture_infos: (0..fun_def.capture_count).map(|_| Info::Any).collect(),
      var_infos: (0..fun_def.var_count).map(|_| Info::Any).collect(),
      return_info: Info::Any,
    })
  }).collect();

  loop {
    let prev_info = prog_info.clone();

    for fun_def in prog.fun_defs.iter() {
      for block in fun_def.blocks.iter() {
        for op in block.ops.iter() {
          op_info_update(&mut prog_info, &fun_def.name, op);
        }
        jump_info_update(&mut prog_info, &fun_def.name, &block.jump);
      }
    }

    if prev_info == prog_info {
      break
    }
  }

  prog_info
}

fn op_info_update(prog_info: &mut ProgInfo, fun_name: &grit::FunName, op: &grit::Op) {
  match *op {
    grit::Op::Call(ref result_var, ref callee, _) =>
      prog_info.get_mut(fun_name).unwrap().var_infos[result_var.0] = join_info(
        &prog_info.get(fun_name).unwrap().var_infos[result_var.0],
        return_info(prog_info, callee)),
    grit::Op::ExternCall(ref result_var, _, _) =>
      prog_info.get_mut(fun_name).unwrap().var_infos[result_var.0] = Info::No,
    grit::Op::AllocClos(ref closs) =>
      for &(ref clos_var, ref clos_name, ref captures) in closs.iter() {
        prog_info.get_mut(fun_name).unwrap().var_infos[clos_var.0] = join_info(
          &prog_info.get(fun_name).unwrap().var_infos[clos_var.0],
          Info::Fun(clos_name.clone()));
        for (idx, capture_val) in captures.iter().enumerate() {
          prog_info.get_mut(clos_name).unwrap().capture_infos[idx] = join_info(
            &prog_info.get(clos_name).unwrap().capture_infos[idx],
            val_info(prog_info.get(fun_name).unwrap(), capture_val));
        }
      },
    grit::Op::Assign(ref var_vals) =>
      for &(ref var, ref val) in var_vals.iter() {
        prog_info.get_mut(fun_name).unwrap().var_infos[var.0] = join_info(
          &prog_info.get(fun_name).unwrap().var_infos[var.0],
          val_info(prog_info.get(fun_name).unwrap(), val));
      },
  }
}

fn jump_info_update(prog_info: &mut ProgInfo,
  fun_name: &grit::FunName, jump: &grit::Jump)
{
  match *jump {
    grit::Jump::Goto(_) => { },
    grit::Jump::Return(ref val) =>
      prog_info.get_mut(fun_name).unwrap().return_info = join_info(
        &prog_info.get(fun_name).unwrap().return_info,
        val_info(prog_info.get(fun_name).unwrap(), val)),
    grit::Jump::TailCall(ref callee, _) =>
      prog_info.get_mut(fun_name).unwrap().return_info = join_info(
        &prog_info.get(fun_name).unwrap().return_info,
        return_info(prog_info, callee)),
    grit::Jump::Branch(_, _, _) => { },
  }
}

fn return_info(prog_info: &ProgInfo, callee: &grit::Callee) -> Info {
  match *callee {
    grit::Callee::Combinator(ref callee_name) |
    grit::Callee::KnownClosure(ref callee_name, _) =>
      prog_info.get(callee_name).unwrap().return_info.clone(),
    grit::Callee::Unknown(_) =>
      Info::No,
  }
}

fn val_info(fun_info: &FunInfo, val: &grit::Val) -> Info {
  match *val {
    grit::Val::Var(ref var) => fun_info.var_infos[var.0].clone(),
    grit::Val::Arg(_) => Info::No,
    grit::Val::Capture(idx) => fun_info.capture_infos[idx].clone(),
    grit::Val::Combinator(ref fun_name) => Info::Fun(fun_name.clone()),
    grit::Val::Obj(_) | grit::Val::Int(_) => Info::No,
    grit::Val::True | grit::Val::False => Info::No,
  }
}

fn join_info(info_1: &Info, info_2: Info) -> Info {
  match *info_1 {
    Info::Any => info_2,
    Info::Fun(ref name_1) => match info_2 {
      Info::Fun(name_2) =>
        if *name_1 == name_2 {
          Info::Fun(name_2)
        } else {
          Info::No
        },
      Info::Any =>
        Info::Fun(name_1.clone()),
      _ =>
        Info::No,
    },
    Info::No => Info::No,
  }
}
<|endoftext|>


<commit_before>use std::collections::BTreeMap;
use token::Token;

#[derive(Default)]
pub struct OptimizeState {
    pub tokens: Vec<Token>,
    // With HashMap, the order sometimes switches
    // in recursion, and the optimizer never exits.
    pub adds: BTreeMap<i32, i32>,
    pub sets: BTreeMap<i32, i32>,
    pub shift: i32,
}

impl OptimizeState {
    pub fn apply_shift(&mut self) {
        if self.shift != 0 {
            self.tokens.push(Token::Move(self.shift));
            self.shift = 0;
        }
    }

    pub fn apply_adds_sets(&mut self) {
        for (offset, value) in &self.sets {
            self.tokens.push(Token::Set(*offset, *value));
        }
        for (offset, value) in &self.adds {
            self.tokens.push(Token::Add(*offset, *value));
        }
        self.sets.clear();
        self.adds.clear();
    }

    pub fn add(&mut self, offset: i32, mut value: i32) {
        if self.sets.contains_key(&offset) {
            value = self.sets.get(&offset).unwrap() + value;
            self.sets.insert(offset, value);
        } else {
            value = self.adds.get(&offset).unwrap_or(&0) + value;
            if value != 0 {
                self.adds.insert(offset, value);
            } else {
                self.adds.remove(&offset);
            }
        }
    }

    pub fn set(&mut self, offset: i32, value: i32) {
        // Add before Set does nothing; remove it
        self.adds.remove(&offset);
        self.sets.insert(offset, value);
    }
}
<commit_msg>Simplification<commit_after>use std::collections::BTreeMap;
use token::Token;

#[derive(Default)]
pub struct OptimizeState {
    pub tokens: Vec<Token>,
    // With HashMap, the order sometimes switches
    // in recursion, and the optimizer never exits.
    pub adds: BTreeMap<i32, i32>,
    pub sets: BTreeMap<i32, i32>,
    pub shift: i32,
}

impl OptimizeState {
    pub fn apply_shift(&mut self) {
        if self.shift != 0 {
            self.tokens.push(Token::Move(self.shift));
            self.shift = 0;
        }
    }

    pub fn apply_adds_sets(&mut self) {
        for (offset, value) in &self.sets {
            self.tokens.push(Token::Set(*offset, *value));
        }
        for (offset, value) in &self.adds {
            self.tokens.push(Token::Add(*offset, *value));
        }
        self.sets.clear();
        self.adds.clear();
    }

    pub fn add(&mut self, offset: i32, mut value: i32) {
        if let Some(set) = self.sets.get_mut(&offset) {
            *set += value;
        } else {
            value = self.adds.get(&offset).unwrap_or(&0) + value;
            if value != 0 {
                self.adds.insert(offset, value);
            } else {
                self.adds.remove(&offset);
            }
        }
    }

    pub fn set(&mut self, offset: i32, value: i32) {
        // Add before Set does nothing; remove it
        self.adds.remove(&offset);
        self.sets.insert(offset, value);
    }
}
<|endoftext|>