<commit_before>use archive_utils::extract_tar;
use octocrab::{models::User, params::repos::Commitish, Octocrab, OctocrabBuilder};
use provider::{
    async_trait::async_trait,
    codecs,
    eyre::Result,
    once_cell::sync::Lazy,
    regex::Regex,
    stencila_schema::{
        CreativeWorkContent, CreativeWorkPublisher, CreativeWorkVersion, Node, Organization,
        SoftwareSourceCode,
    },
    tracing, Provider, ProviderParsing, ProviderTrait,
};
use std::{
    fs::{create_dir_all, File},
    io::{self, Write},
    path::{Path, PathBuf},
};

pub struct GithubProvider;

impl GithubProvider {
    /// Create an API client
    fn client(token: Option<String>) -> Result<Octocrab> {
        let mut builder = Octocrab::builder();
        if let Some(token) = token {
            builder = builder.personal_token(token);
        }
        let client = builder.build()?;
        Ok(client)
    }

    /// Extract the GitHub repository owner and name from a [`SoftwareSourceCode`] node (if any)
    fn owner_name(ssc: &SoftwareSourceCode) -> Option<(&str, &str)> {
        if let Some(repo) = &ssc.code_repository {
            if let Some(repo) = repo.strip_prefix("https://github.com/") {
                let parts: Vec<&str> = repo.split('/').collect();
                if parts.len() >= 2 {
                    Some((parts[0], parts[1]))
                } else {
                    None
                }
            } else {
                None
            }
        } else {
            None
        }
    }

    /// Extract the sub-path from a [`SoftwareSourceCode`] node (if any)
    fn path(ssc: &SoftwareSourceCode) -> Option<&str> {
        ssc.content
            .as_ref()
            .and_then(|content| match content.as_ref() {
                CreativeWorkContent::String(path) => Some(path.as_str()),
                _ => None,
            })
    }

    /// Extract the version from a [`SoftwareSourceCode`] node (if any)
    fn version(ssc: &SoftwareSourceCode) -> Option<&str> {
        ssc.version
            .as_ref()
            .and_then(|version| match version.as_ref() {
                CreativeWorkVersion::String(version) => Some(version.as_str()),
                _ => None,
            })
    }
}

#[async_trait]
impl ProviderTrait for GithubProvider {
    fn spec() -> Provider {
        Provider::new("github")
    }

    fn parse(string: &str) -> Vec<ProviderParsing> {
        // Regex targeting short identifiers e.g. github:org/name
        static SIMPLE_REGEX: Lazy<Regex> = Lazy::new(|| {
            Regex::new(r"^github:(?://)?([a-z0-9\-]+)/([a-z0-9\-_]+)(?:/([^@]+))?(?:@(.+))?$")
                .expect("Unable to create regex")
        });

        // Regex targeting URL copied from the browser address bar
        static URL_REGEX: Lazy<Regex> = Lazy::new(|| {
            Regex::new(r"^(?:https?://)?github\.com/([a-z0-9\-]+)/([a-z0-9\-_]+)/?(?:(?:tree|blob))?/?([^/]+)?/?(.+)?$")
                .expect("Unable to create regex")
        });

        SIMPLE_REGEX
            .captures_iter(string)
            .into_iter()
            .map(|captures| {
                let capture = captures.get(0).unwrap();
                (
                    capture.start(),
                    capture.end(),
                    captures[1].to_string(),
                    captures[2].to_string(),
                    captures.get(4).map(|group| group.as_str().to_string()),
                    captures.get(3).map(|group| group.as_str().to_string()),
                )
            })
            .chain(URL_REGEX.captures_iter(string).into_iter().map(|captures| {
                let capture = captures.get(0).unwrap();
                (
                    capture.start(),
                    capture.end(),
                    captures[1].to_string(),
                    captures[2].to_string(),
                    captures.get(3).map(|group| group.as_str().to_string()),
                    captures.get(4).map(|group| group.as_str().to_string()),
                )
            }))
            .map(
                |(begin, end, org, name, version, content)| ProviderParsing {
                    begin,
                    end,
                    node: Node::SoftwareSourceCode(SoftwareSourceCode {
                        code_repository: Some(Box::new(format!(
                            "https://github.com/{}/{}",
                            org, name
                        ))),
                        publisher: Some(Box::new(CreativeWorkPublisher::Organization(
                            Organization {
                                name: Some(Box::new(org)),
                                ..Default::default()
                            },
                        ))),
                        name: Some(Box::new(name)),
                        version: version
                            .map(|version| Box::new(CreativeWorkVersion::String(version))),
                        content: content.map(|path| Box::new(CreativeWorkContent::String(path))),
                        ..Default::default()
                    }),
                },
            )
            .collect()
    }

    async fn import(node: &Node, destination: &Path, token: Option<String>) -> Result<bool> {
        let ssc = match node {
            Node::SoftwareSourceCode(ssc) => ssc,
            _ => return Ok(false),
        };
        let (owner, name) = match GithubProvider::owner_name(ssc) {
            Some(owner_name) => owner_name,
            None => return Ok(false),
        };
        let subpath = GithubProvider::path(ssc);
        let version = GithubProvider::version(ssc);

        let client = GithubProvider::client(token)?;
        let repo = client.repos(owner, name);

        // Fetch the contents at the path / version
        let mut builder = repo.get_content();
        if let Some(path) = subpath {
            builder = builder.path(path);
        }
        if let Some(version) = version {
            builder = builder.r#ref(version)
        }
        let content_items = builder.send().await?;
        let count = content_items.items.len();

        // No content (in directory)
        if count == 0 {
            tracing::warn!("No content at GitHub repository path/ref");
            return Ok(true);
        }

        // Content is a single file with content so write to destination
        if count == 1 {
            let content = &content_items.items[0];
            if content.r#type == "file" && content.content.is_some() {
                let name = PathBuf::from(&content.name);
                let string = content.decoded_content().unwrap_or_default();

                if let Some(dest_ext) = destination.extension() {
                    let dest_ext = dest_ext.to_string_lossy().to_string();
                    let source_ext = name.extension().map_or_else(
                        || content.name.to_string(),
                        |os_str| os_str.to_string_lossy().to_string(),
                    );
                    if source_ext == dest_ext {
                        // Destination format is same as content so just write it
                        let mut file = File::create(destination)?;
                        file.write_all(string.as_bytes())?;
                    } else {
                        // Destination has a different format so convert it first
                        codecs::str_to_path(&string, &source_ext, destination, None).await?;
                    }
                } else {
                    // Destination has no extension so treat it as a directory
                    // and write the file there
                    create_dir_all(destination)?;
                    let mut file = File::create(destination.join(name))?;
                    file.write_all(string.as_bytes())?;
                }
                return Ok(true);
            }
        }

        // Content is a directory so fetch the whole repo as a tarball and extract the directory
        // (getting the whole rpo as a tarball is more efficient than making lots of small requests
        // for each file's content; for most repos)
        let version = version.unwrap_or("HEAD").to_string();
        let commitish = Commitish::from(version);
        let response = repo.download_tarball(commitish).await?.error_for_status()?;

        let mut file = tempfile::NamedTempFile::new()?;
        let bytes = response.bytes().await?;
        io::copy(&mut bytes.as_ref(), &mut file)?;
        file.flush()?;
        let archive = file.path();

        create_dir_all(destination)?;
        extract_tar("gz", archive, destination, 1, subpath)?;

        Ok(true)
    }
}

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

    #[test]
    fn parse() {
        // No path or version
        for string in [
            "github:owner/name",
            "github://owner/name",
            "http://github.com/owner/name",
            "https://github.com/owner/name",
            "https://github.com/owner/name/",
            "https://github.com/owner/name/tree",
            "https://github.com/owner/name/blob/",
        ] {
            assert_json_is!(
                GithubProvider::parse(string)[0].node,
                {
                    "type": "SoftwareSourceCode",
                    "codeRepository": "https://github.com/owner/name",
                    "publisher": {
                        "type": "Organization",
                        "name": "owner"
                    },
                    "name": "name",
                }
            );
        }

        // Version, no path
        for string in [
            "github:owner/name@version",
            "github://owner/name@version",
            "https://github.com/owner/name/tree/version",
            "https://github.com/owner/name/tree/version/",
        ] {
            assert_json_is!(
                GithubProvider::parse(string)[0].node,
                {
                    "type": "SoftwareSourceCode",
                    "codeRepository": "https://github.com/owner/name",
                    "publisher": {
                        "type": "Organization",
                        "name": "owner"
                    },
                    "name": "name",
                    "version": "version"
                }
            );
        }

        // Folder path and version
        for string in [
            "github:owner/name/sub/folder@version",
            "github://owner/name/sub/folder@version",
            "https://github.com/owner/name/tree/version/sub/folder",
        ] {
            assert_json_is!(
                GithubProvider::parse(string)[0].node,
                {
                    "type": "SoftwareSourceCode",
                    "codeRepository": "https://github.com/owner/name",
                    "publisher": {
                        "type": "Organization",
                        "name": "owner"
                    },
                    "name": "name",
                    "version": "version",
                    "content": "sub/folder"
                }
            );
        }

        // File path and version
        for string in [
            "github:owner/name/sub/folder/file.ext@version",
            "github://owner/name/sub/folder/file.ext@version",
            "https://github.com/owner/name/blob/version/sub/folder/file.ext",
        ] {
            assert_json_is!(
                GithubProvider::parse(string)[0].node,
                {
                    "type": "SoftwareSourceCode",
                    "codeRepository": "https://github.com/owner/name",
                    "publisher": {
                        "type": "Organization",
                        "name": "owner"
                    },
                    "name": "name",
                    "version": "version",
                    "content": "sub/folder/file.ext"
                }
            );
        }

        // File path, no version
        for string in [
            "github:owner/name/sub/folder/file.ext",
            "github://owner/name/sub/folder/file.ext",
        ] {
            assert_json_is!(
                GithubProvider::parse(string)[0].node,
                {
                    "type": "SoftwareSourceCode",
                    "codeRepository": "https://github.com/owner/name",
                    "publisher": {
                        "type": "Organization",
                        "name": "owner"
                    },
                    "name": "name",
                    "content": "sub/folder/file.ext"
                }
            );
        }
    }

    #[ignore]
    #[tokio::test]
    async fn repos() -> Result<()> {
        let client = OctocrabBuilder::new().build()?;

        let repos = vec![("stencila", "stencila")];
        for (owner, name) in repos {
            let repo = client.repos(owner, name);

            let repo = repo.get().await?;

            let contributors: Vec<(String, String)> =
                if let Some(contributors_url) = repo.contributors_url {
                    let contributors: Vec<User> =
                        client.get(contributors_url.path(), None::<&()>).await?;
                    contributors
                        .into_iter()
                        .map(|user| (user.login, user.url.path().to_string()))
                        .collect()
                } else {
                    Vec::new()
                };

            // TODO: Have a cache of users and fetch user data from URL if necessary
            // TODO: Filter out users with [bot] in their login

            println!(
                "{},{},\"{}\",\"{}\",{},{},{},{},{},{},{},{},\"{}\"",
                owner,
                name,
                repo.description.unwrap_or_default(),
                repo.topics.unwrap_or_default().join(","),
                repo.license.map_or_else(|| "".to_string(), |l| l.name),
                repo.language
                    .map_or_else(|| "".to_string(), |v| v.as_str().unwrap().to_string()),
                repo.forks_count.unwrap_or(0),       // "Forks"
                repo.stargazers_count.unwrap_or(0),  // "Stars"
                repo.subscribers_count.unwrap_or(0), // "Watchers"
                repo.created_at.unwrap(),
                repo.pushed_at.unwrap(),
                repo.updated_at.unwrap(),
                contributors
                    .iter()
                    .map(|pair| pair.0.clone())
                    .collect::<Vec<String>>()
                    .join(",")
            );
        }

        Ok(())
    }
}
<commit_msg>refactor(GitHub Provider): Add `enrich` method<commit_after>use archive_utils::extract_tar;
use octocrab::{models::User, params::repos::Commitish, Octocrab, OctocrabBuilder};
use provider::{
    async_trait::async_trait,
    codecs,
    eyre::Result,
    once_cell::sync::Lazy,
    regex::Regex,
    stencila_schema::{
        CreativeWorkAuthors, CreativeWorkContent, CreativeWorkPublisher, CreativeWorkVersion, Date,
        Node, Organization, Person, SoftwareSourceCode, ThingDescription,
    },
    tracing, Provider, ProviderParsing, ProviderTrait,
};
use std::{
    fs::{create_dir_all, File},
    io::{self, Write},
    path::{Path, PathBuf},
};

pub struct GithubProvider;

impl GithubProvider {
    /// Create an API client
    fn client(token: Option<String>) -> Result<Octocrab> {
        let mut builder = Octocrab::builder();
        if let Some(token) = token {
            builder = builder.personal_token(token);
        }
        let client = builder.build()?;
        Ok(client)
    }

    /// Extract the GitHub repository owner and name from a [`SoftwareSourceCode`] node (if any)
    fn owner_name(ssc: &SoftwareSourceCode) -> Option<(&str, &str)> {
        if let Some(repo) = &ssc.code_repository {
            if let Some(repo) = repo.strip_prefix("https://github.com/") {
                let parts: Vec<&str> = repo.split('/').collect();
                if parts.len() >= 2 {
                    Some((parts[0], parts[1]))
                } else {
                    None
                }
            } else {
                None
            }
        } else {
            None
        }
    }

    /// Extract the sub-path from a [`SoftwareSourceCode`] node (if any)
    fn path(ssc: &SoftwareSourceCode) -> Option<&str> {
        ssc.content
            .as_ref()
            .and_then(|content| match content.as_ref() {
                CreativeWorkContent::String(path) => Some(path.as_str()),
                _ => None,
            })
    }

    /// Extract the version from a [`SoftwareSourceCode`] node (if any)
    fn version(ssc: &SoftwareSourceCode) -> Option<&str> {
        ssc.version
            .as_ref()
            .and_then(|version| match version.as_ref() {
                CreativeWorkVersion::String(version) => Some(version.as_str()),
                _ => None,
            })
    }
}

#[async_trait]
impl ProviderTrait for GithubProvider {
    fn spec() -> Provider {
        Provider::new("github")
    }

    fn parse(string: &str) -> Vec<ProviderParsing> {
        // Regex targeting short identifiers e.g. github:org/name
        static SIMPLE_REGEX: Lazy<Regex> = Lazy::new(|| {
            Regex::new(r"^github:(?://)?([a-z0-9\-]+)/([a-z0-9\-_]+)(?:/([^@]+))?(?:@(.+))?$")
                .expect("Unable to create regex")
        });

        // Regex targeting URL copied from the browser address bar
        static URL_REGEX: Lazy<Regex> = Lazy::new(|| {
            Regex::new(r"^(?:https?://)?github\.com/([a-z0-9\-]+)/([a-z0-9\-_]+)/?(?:(?:tree|blob))?/?([^/]+)?/?(.+)?$")
                .expect("Unable to create regex")
        });

        SIMPLE_REGEX
            .captures_iter(string)
            .into_iter()
            .map(|captures| {
                let capture = captures.get(0).unwrap();
                (
                    capture.start(),
                    capture.end(),
                    captures[1].to_string(),
                    captures[2].to_string(),
                    captures.get(4).map(|group| group.as_str().to_string()),
                    captures.get(3).map(|group| group.as_str().to_string()),
                )
            })
            .chain(URL_REGEX.captures_iter(string).into_iter().map(|captures| {
                let capture = captures.get(0).unwrap();
                (
                    capture.start(),
                    capture.end(),
                    captures[1].to_string(),
                    captures[2].to_string(),
                    captures.get(3).map(|group| group.as_str().to_string()),
                    captures.get(4).map(|group| group.as_str().to_string()),
                )
            }))
            .map(
                |(begin, end, org, name, version, content)| ProviderParsing {
                    begin,
                    end,
                    node: Node::SoftwareSourceCode(SoftwareSourceCode {
                        code_repository: Some(Box::new(format!(
                            "https://github.com/{}/{}",
                            org, name
                        ))),
                        publisher: Some(Box::new(CreativeWorkPublisher::Organization(
                            Organization {
                                name: Some(Box::new(org)),
                                ..Default::default()
                            },
                        ))),
                        name: Some(Box::new(name)),
                        version: version
                            .map(|version| Box::new(CreativeWorkVersion::String(version))),
                        content: content.map(|path| Box::new(CreativeWorkContent::String(path))),
                        ..Default::default()
                    }),
                },
            )
            .collect()
    }

    async fn enrich(node: Node) -> Result<Node> {
        let ssc = match &node {
            Node::SoftwareSourceCode(ssc) => ssc,
            _ => return Ok(node),
        };
        let (owner, name) = match GithubProvider::owner_name(ssc) {
            Some(owner_name) => owner_name,
            None => return Ok(node),
        };

        let client = GithubProvider::client(None)?;
        let repo = client.repos(owner, name);
        let repo = repo.get().await?;

        let description = repo
            .description
            .map(|desc| Box::new(ThingDescription::String(desc)));

        let keywords = repo.topics;

        let date_created = repo.created_at.map(|date| Box::new(Date::from(date)));
        let date_modified = repo.pushed_at.map(|date| Box::new(Date::from(date)));

        // TODO: Have a cache of users and fetch user data from URL if necessary
        let authors = match repo.contributors_url {
            Some(url) => {
                let contributors: Vec<User> =
                    client.get(url.path(), None::<&()>).await?;
                let authors = contributors
                    .into_iter()
                    .filter_map(|user| {
                        if user.login.contains("[bot]") {
                            None
                        } else {
                            Some(CreativeWorkAuthors::Person(Person {
                                name: Some(Box::new(user.login)),
                                ..Default::default()
                            }))
                        }
                    })
                    .collect::<Vec<CreativeWorkAuthors>>();
                Some(authors)
            }
            None => None,
        };

        // TODO: Implement transforming these to ssc fields
        /*
        repo.license.map_or_else(|| "".to_string(), |l| l.name);
        repo.language
            .map_or_else(|| "".to_string(), |v| v.as_str().unwrap().to_string());
        repo.forks_count.unwrap_or(0);       // "Forks"
        repo.stargazers_count.unwrap_or(0);  // "Stars"
        repo.subscribers_count.unwrap_or(0); // "Watchers"
        */

        let ssc = SoftwareSourceCode {
            description,
            keywords,
            date_created,
            date_modified,
            authors,
            ..ssc.clone()
        };

        Ok(Node::SoftwareSourceCode(ssc))
    }

    async fn import(node: &Node, destination: &Path, token: Option<String>) -> Result<bool> {
        let ssc = match node {
            Node::SoftwareSourceCode(ssc) => ssc,
            _ => return Ok(false),
        };
        let (owner, name) = match GithubProvider::owner_name(ssc) {
            Some(owner_name) => owner_name,
            None => return Ok(false),
        };
        let subpath = GithubProvider::path(ssc);
        let version = GithubProvider::version(ssc);

        let client = GithubProvider::client(token)?;
        let repo = client.repos(owner, name);

        // Fetch the contents at the path / version
        let mut builder = repo.get_content();
        if let Some(path) = subpath {
            builder = builder.path(path);
        }
        if let Some(version) = version {
            builder = builder.r#ref(version)
        }
        let content_items = builder.send().await?;
        let count = content_items.items.len();

        // No content (in directory)
        if count == 0 {
            tracing::warn!("No content at GitHub repository path/ref");
            return Ok(true);
        }

        // Content is a single file with content so write to destination
        if count == 1 {
            let content = &content_items.items[0];
            if content.r#type == "file" && content.content.is_some() {
                let name = PathBuf::from(&content.name);
                let string = content.decoded_content().unwrap_or_default();

                if let Some(dest_ext) = destination.extension() {
                    let dest_ext = dest_ext.to_string_lossy().to_string();
                    let source_ext = name.extension().map_or_else(
                        || content.name.to_string(),
                        |os_str| os_str.to_string_lossy().to_string(),
                    );
                    if source_ext == dest_ext {
                        // Destination format is same as content so just write it
                        let mut file = File::create(destination)?;
                        file.write_all(string.as_bytes())?;
                    } else {
                        // Destination has a different format so convert it first
                        codecs::str_to_path(&string, &source_ext, destination, None).await?;
                    }
                } else {
                    // Destination has no extension so treat it as a directory
                    // and write the file there
                    create_dir_all(destination)?;
                    let mut file = File::create(destination.join(name))?;
                    file.write_all(string.as_bytes())?;
                }
                return Ok(true);
            }
        }

        // Content is a directory so fetch the whole repo as a tarball and extract the directory
        // (getting the whole rpo as a tarball is more efficient than making lots of small requests
        // for each file's content; for most repos)
        let version = version.unwrap_or("HEAD").to_string();
        let commitish = Commitish::from(version);
        let response = repo.download_tarball(commitish).await?.error_for_status()?;

        let mut file = tempfile::NamedTempFile::new()?;
        let bytes = response.bytes().await?;
        io::copy(&mut bytes.as_ref(), &mut file)?;
        file.flush()?;
        let archive = file.path();

        create_dir_all(destination)?;
        extract_tar("gz", archive, destination, 1, subpath)?;

        Ok(true)
    }
}

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

    #[test]
    fn parse() {
        // No path or version
        for string in [
            "github:owner/name",
            "github://owner/name",
            "http://github.com/owner/name",
            "https://github.com/owner/name",
            "https://github.com/owner/name/",
            "https://github.com/owner/name/tree",
            "https://github.com/owner/name/blob/",
        ] {
            assert_json_is!(
                GithubProvider::parse(string)[0].node,
                {
                    "type": "SoftwareSourceCode",
                    "codeRepository": "https://github.com/owner/name",
                    "publisher": {
                        "type": "Organization",
                        "name": "owner"
                    },
                    "name": "name",
                }
            );
        }

        // Version, no path
        for string in [
            "github:owner/name@version",
            "github://owner/name@version",
            "https://github.com/owner/name/tree/version",
            "https://github.com/owner/name/tree/version/",
        ] {
            assert_json_is!(
                GithubProvider::parse(string)[0].node,
                {
                    "type": "SoftwareSourceCode",
                    "codeRepository": "https://github.com/owner/name",
                    "publisher": {
                        "type": "Organization",
                        "name": "owner"
                    },
                    "name": "name",
                    "version": "version"
                }
            );
        }

        // Folder path and version
        for string in [
            "github:owner/name/sub/folder@version",
            "github://owner/name/sub/folder@version",
            "https://github.com/owner/name/tree/version/sub/folder",
        ] {
            assert_json_is!(
                GithubProvider::parse(string)[0].node,
                {
                    "type": "SoftwareSourceCode",
                    "codeRepository": "https://github.com/owner/name",
                    "publisher": {
                        "type": "Organization",
                        "name": "owner"
                    },
                    "name": "name",
                    "version": "version",
                    "content": "sub/folder"
                }
            );
        }

        // File path and version
        for string in [
            "github:owner/name/sub/folder/file.ext@version",
            "github://owner/name/sub/folder/file.ext@version",
            "https://github.com/owner/name/blob/version/sub/folder/file.ext",
        ] {
            assert_json_is!(
                GithubProvider::parse(string)[0].node,
                {
                    "type": "SoftwareSourceCode",
                    "codeRepository": "https://github.com/owner/name",
                    "publisher": {
                        "type": "Organization",
                        "name": "owner"
                    },
                    "name": "name",
                    "version": "version",
                    "content": "sub/folder/file.ext"
                }
            );
        }

        // File path, no version
        for string in [
            "github:owner/name/sub/folder/file.ext",
            "github://owner/name/sub/folder/file.ext",
        ] {
            assert_json_is!(
                GithubProvider::parse(string)[0].node,
                {
                    "type": "SoftwareSourceCode",
                    "codeRepository": "https://github.com/owner/name",
                    "publisher": {
                        "type": "Organization",
                        "name": "owner"
                    },
                    "name": "name",
                    "content": "sub/folder/file.ext"
                }
            );
        }
    }
}
<|endoftext|>


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

//! A syntax highlighting plugin based on syntect.

extern crate syntect;
extern crate xi_plugin_lib;
extern crate xi_core_lib as xi_core;
extern crate xi_rope;
extern crate xi_trace;

mod stackmap;

use std::sync::MutexGuard;
use std::borrow::Cow;
use std::collections::HashMap;
use std::path::Path;

use xi_core::{ViewId, ConfigTable};
use xi_core::plugin_rpc::ScopeSpan;
use xi_rope::rope::RopeDelta;
use xi_rope::interval::Interval;
use xi_rope::delta::Builder as EditBuilder;
use xi_trace::{trace, trace_block};
use xi_plugin_lib::{Cache, Plugin, StateCache, View, mainloop};

use syntect::parsing::{ParseState, ScopeStack, SyntaxSet, SCOPE_REPO,
                       SyntaxDefinition, ScopeRepository};
use stackmap::{StackMap, LookupResult};

const LINES_PER_RPC: usize = 10;
const INDENTATION_PRIORITY: u64 = 100;

/// The state for syntax highlighting of one file.
struct PluginState {
    stack_idents: StackMap,
    offset: usize,
    initial_state: LineState,
    spans_start: usize,
    // unflushed spans
    spans: Vec<ScopeSpan>,
    new_scopes: Vec<Vec<String>>,
}

type LockedRepo = MutexGuard<'static, ScopeRepository>;

/// The syntax highlighting state corresponding to the beginning of a line
/// (as stored in the state cache).
// Note: this needs to be option because the caching layer relies on Default.
// We can't implement that because the actual initial state depends on the
// syntax. There are other ways to handle this, but this will do for now.
type LineState = Option<(ParseState, ScopeStack)>;

/// The state of syntax highlighting for a collection of buffers.
struct Syntect<'a> {
    view_state: HashMap<ViewId, PluginState>,
    syntax_set: &'a SyntaxSet,
}

impl PluginState {
    fn new() -> Self {
        PluginState {
            stack_idents: StackMap::default(),
            offset: 0,
            initial_state: None,
            spans_start: 0,
            spans: Vec::new(),
            new_scopes: Vec::new(),
        }
    }

    // compute syntax for one line, also accumulating the style spans
    fn compute_syntax(&mut self, line: &str, state: LineState) -> LineState {
        let (mut parse_state, mut scope_state) = state.or_else(|| self.initial_state.clone()).unwrap();
        let ops = parse_state.parse_line(&line);

        let mut prev_cursor = 0;
        let repo = SCOPE_REPO.lock().unwrap();
        for (cursor, batch) in ops {
            if scope_state.len() > 0 {
                let scope_id = self.identifier_for_stack(&scope_state, &repo);
                let start = self.offset - self.spans_start + prev_cursor;
                let end = start + (cursor - prev_cursor);
                if start != end {
                    let span = ScopeSpan { start, end, scope_id };
                    self.spans.push(span);
                }
            }
            prev_cursor = cursor;
            scope_state.apply(&batch);
        }
        // add span for final state
        let start = self.offset - self.spans_start + prev_cursor;
        let end = start + (line.len() - prev_cursor);
        let scope_id = self.identifier_for_stack(&scope_state, &repo);
        let span = ScopeSpan { start, end, scope_id };
        self.spans.push(span);
        Some((parse_state, scope_state))
    }

    /// Returns the unique identifier for this `ScopeStack`. We use identifiers
    /// so we aren't constantly sending long stack names to the peer.
    fn identifier_for_stack(&mut self, stack: &ScopeStack, repo: &LockedRepo) -> u32 {
        let identifier = self.stack_idents.get_value(stack.as_slice());
        match identifier {
            LookupResult::Existing(id) => id,
            LookupResult::New(id) => {
                let stack_strings = stack.as_slice().iter()
                    .map(|slice| repo.to_string(*slice))
                    .collect::<Vec<_>>();
                self.new_scopes.push(stack_strings);
                id
            }
        }
    }

    #[allow(unused)]
    // Return true if there's any more work to be done.
    fn highlight_one_line(&mut self, ctx: &mut MyView) -> bool {
        if let Some(line_num) = ctx.get_frontier() {
            let (line_num, offset, state) = ctx.get_prev(line_num);
            if offset != self.offset {
                self.flush_spans(ctx);
                self.offset = offset;
                self.spans_start = offset;
            }
            let new_frontier = match ctx.get_line(line_num) {
                Ok("") => None,
                Ok(s) => {
                    let new_state = self.compute_syntax(s, state);
                    self.offset += s.len();
                    if s.as_bytes().last() == Some(&b'\n') {
                        Some((new_state, line_num + 1))
                    } else {
                        None
                    }
                }
                Err(_) => None,
            };
            let mut converged = false;
            if let Some((ref new_state, new_line_num)) = new_frontier {
                if let Some(old_state) = ctx.get(new_line_num) {
                    converged = old_state.as_ref().unwrap().0 == new_state.as_ref().unwrap().0;
                }
            }
            if !converged {
                if let Some((new_state, new_line_num)) = new_frontier {
                    ctx.set(new_line_num, new_state);
                    ctx.update_frontier(new_line_num);
                    return true;
                }
            }
            ctx.close_frontier();
        }
        false
    }

    fn flush_spans(&mut self, ctx: &mut MyView) {
        let _t = trace_block("PluginState::flush_spans", &["syntect"]);
        if !self.new_scopes.is_empty() {
            ctx.add_scopes(&self.new_scopes);
            self.new_scopes.clear();
        }
        if self.spans_start != self.offset {
            ctx.update_spans(self.spans_start, self.offset - self.spans_start,
                             &self.spans);
            self.spans.clear();
        }
        self.spans_start = self.offset;
    }
}

type MyView = View<StateCache<LineState>>;

impl<'a> Syntect<'a> {
    fn new(syntax_set: &'a SyntaxSet) -> Self {
        Syntect {
            view_state: HashMap::new(),
            syntax_set: syntax_set,
        }
    }

    /// Wipes any existing state and starts highlighting with `syntax`.
    fn do_highlighting(&mut self, view: &mut MyView) {
        let initial_state = {
            let syntax = self.guess_syntax(view.get_path());
            Some((ParseState::new(syntax), ScopeStack::new()))
        };

        let state = self.view_state.get_mut(&view.get_id()).unwrap();
        state.initial_state = initial_state;
        state.spans = Vec::new();
        state.new_scopes = Vec::new();
        state.offset = 0;
        state.spans_start = 0;
        view.get_cache().clear();
        view.schedule_idle();
    }

    fn guess_syntax(&'a self, path: Option<&Path>) -> &'a SyntaxDefinition {
        let _t = trace_block("Syntect::guess_syntax", &["syntect"]);
        match path {
            Some(path) => self.syntax_set.find_syntax_for_file(path)
                .ok()
                .unwrap_or(None)
                .unwrap_or_else(|| self.syntax_set.find_syntax_plain_text()),
            None => self.syntax_set.find_syntax_plain_text(),
        }
    }

    /// Checks if a newline has been inserted, and if so inserts whitespace
    /// as necessary.
    fn do_indentation(&mut self, view: &mut MyView, start: usize, end: usize, text: &str) {
        let _t = trace_block("PluginState::do_indentation", &["syntect"]);
        // don't touch indentation if this is not a simple edit
        if end != start { return }

        let line_ending = view.get_config().line_ending.clone();
        let is_newline = line_ending == text;

        if is_newline {
            let line_num = view.line_of_offset(start).unwrap();

            let use_spaces = view.get_config().translate_tabs_to_spaces;
            let tab_size = view.get_config().tab_size;
            let buf_size = view.get_buf_size();

            let result = if let Some(line) = view.get_line(line_num).ok() {
                // do not send update if last line is empty string (contains only line ending)
                if line == line_ending { return }

                let indent = self.indent_for_next_line(
                    line, use_spaces, tab_size);
                let ix = start + text.len();
                let interval = Interval::new_open_closed(ix, ix);
                //TODO: view should have a `get_edit_builder` fn?
                let mut builder = EditBuilder::new(buf_size);
                builder.replace(interval, indent.into());

                let delta = builder.build();
                Some(delta)
            } else {
                None
            };

            if let Some(delta) = result {
                view.edit(delta, INDENTATION_PRIORITY, false,
                          false, String::from("syntect"));
            }
        }
    }

    /// Returns the string which should be inserted after the newline
    /// to achieve the desired indentation level.
    fn indent_for_next_line<'b>(&self, prev_line: &'b str, use_spaces: bool,
                                tab_size: usize) -> Cow<'b, str> {
        let leading_ws = prev_line.char_indices()
            .find(|&(_, c)| !c.is_whitespace())
            .or(prev_line.char_indices().last())
            .map(|(idx, _)| unsafe { prev_line.slice_unchecked(0, idx) })
            .unwrap_or("");

        if self.increase_indentation(prev_line) {
            let indent_text = if use_spaces {
                &"                                    "[..tab_size]
            } else {
                "\t"
            };
            format!("{}{}", leading_ws, indent_text).into()
        } else {
            leading_ws.into()
        }
    }

    /// Checks if the indent level should be increased.
    fn increase_indentation(&self, prev_line: &str) -> bool {
        let trailing_char = prev_line.trim_right().chars()
            .rev().next().unwrap_or(' ');
        // very naive heuristic for modifying indentation level.
        match trailing_char {
            '{' | ':' => true,
            _ => false,
        }
    }
}

impl<'a> Plugin for Syntect<'a> {
    type Cache = StateCache<LineState>;

    fn new_view(&mut self, view: &mut View<Self::Cache>) {
        let _t = trace_block("Syntect::new_view", &["syntect"]);
        let view_id = view.get_id();
        let state = PluginState::new();
        self.view_state.insert(view_id, state);
        self.do_highlighting(view);
    }

    fn did_close(&mut self, view: &View<Self::Cache>) {
        self.view_state.remove(&view.get_id());
    }

    fn did_save(&mut self, view: &mut View<Self::Cache>, _old: Option<&Path>) {
        let _t = trace_block("Syntect::did_save", &["syntect"]);
        self.do_highlighting(view);
    }

    fn config_changed(&mut self, _view: &mut View<Self::Cache>, _changes: &ConfigTable) {}

    fn update(&mut self, view: &mut View<Self::Cache>, delta: Option<&RopeDelta>,
              _edit_type: String, _author: String) {
        let _t = trace_block("Syntect::update", &["syntect"]);
        view.schedule_idle();
        let should_auto_indent = view.get_config().auto_indent;
        if !should_auto_indent { return }
        if let Some(delta) = delta {
            let (iv, _) = delta.summary();
            if let Some(s) = delta.as_simple_insert() {
                let s: String = s.into();
                self.do_indentation(view, iv.start(), iv.end(), &s);
            }
        }
    }

    fn idle(&mut self, view: &mut View<Self::Cache>) {
        let state = self.view_state.get_mut(&view.get_id()).unwrap();
        for _ in 0..LINES_PER_RPC {
            if !state.highlight_one_line(view) {
                state.flush_spans(view);
                return;
            }
            if view.request_is_pending() {
                trace("yielding for request", &["syntect"]);
                break;
            }
        }
        state.flush_spans(view);
        view.schedule_idle();
    }
}

fn main() {
    let syntax_set = SyntaxSet::load_defaults_newlines();
    let mut state = Syntect::new(&syntax_set);
    mainloop(&mut state).unwrap();
}
<commit_msg>Fix deprecated use of str::slice_unchecked in syntect-plugin.<commit_after>// Copyright 2016 The xi-editor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! A syntax highlighting plugin based on syntect.

extern crate syntect;
extern crate xi_plugin_lib;
extern crate xi_core_lib as xi_core;
extern crate xi_rope;
extern crate xi_trace;

mod stackmap;

use std::sync::MutexGuard;
use std::borrow::Cow;
use std::collections::HashMap;
use std::path::Path;

use xi_core::{ViewId, ConfigTable};
use xi_core::plugin_rpc::ScopeSpan;
use xi_rope::rope::RopeDelta;
use xi_rope::interval::Interval;
use xi_rope::delta::Builder as EditBuilder;
use xi_trace::{trace, trace_block};
use xi_plugin_lib::{Cache, Plugin, StateCache, View, mainloop};

use syntect::parsing::{ParseState, ScopeStack, SyntaxSet, SCOPE_REPO,
                       SyntaxDefinition, ScopeRepository};
use stackmap::{StackMap, LookupResult};

const LINES_PER_RPC: usize = 10;
const INDENTATION_PRIORITY: u64 = 100;

/// The state for syntax highlighting of one file.
struct PluginState {
    stack_idents: StackMap,
    offset: usize,
    initial_state: LineState,
    spans_start: usize,
    // unflushed spans
    spans: Vec<ScopeSpan>,
    new_scopes: Vec<Vec<String>>,
}

type LockedRepo = MutexGuard<'static, ScopeRepository>;

/// The syntax highlighting state corresponding to the beginning of a line
/// (as stored in the state cache).
// Note: this needs to be option because the caching layer relies on Default.
// We can't implement that because the actual initial state depends on the
// syntax. There are other ways to handle this, but this will do for now.
type LineState = Option<(ParseState, ScopeStack)>;

/// The state of syntax highlighting for a collection of buffers.
struct Syntect<'a> {
    view_state: HashMap<ViewId, PluginState>,
    syntax_set: &'a SyntaxSet,
}

impl PluginState {
    fn new() -> Self {
        PluginState {
            stack_idents: StackMap::default(),
            offset: 0,
            initial_state: None,
            spans_start: 0,
            spans: Vec::new(),
            new_scopes: Vec::new(),
        }
    }

    // compute syntax for one line, also accumulating the style spans
    fn compute_syntax(&mut self, line: &str, state: LineState) -> LineState {
        let (mut parse_state, mut scope_state) = state.or_else(|| self.initial_state.clone()).unwrap();
        let ops = parse_state.parse_line(&line);

        let mut prev_cursor = 0;
        let repo = SCOPE_REPO.lock().unwrap();
        for (cursor, batch) in ops {
            if scope_state.len() > 0 {
                let scope_id = self.identifier_for_stack(&scope_state, &repo);
                let start = self.offset - self.spans_start + prev_cursor;
                let end = start + (cursor - prev_cursor);
                if start != end {
                    let span = ScopeSpan { start, end, scope_id };
                    self.spans.push(span);
                }
            }
            prev_cursor = cursor;
            scope_state.apply(&batch);
        }
        // add span for final state
        let start = self.offset - self.spans_start + prev_cursor;
        let end = start + (line.len() - prev_cursor);
        let scope_id = self.identifier_for_stack(&scope_state, &repo);
        let span = ScopeSpan { start, end, scope_id };
        self.spans.push(span);
        Some((parse_state, scope_state))
    }

    /// Returns the unique identifier for this `ScopeStack`. We use identifiers
    /// so we aren't constantly sending long stack names to the peer.
    fn identifier_for_stack(&mut self, stack: &ScopeStack, repo: &LockedRepo) -> u32 {
        let identifier = self.stack_idents.get_value(stack.as_slice());
        match identifier {
            LookupResult::Existing(id) => id,
            LookupResult::New(id) => {
                let stack_strings = stack.as_slice().iter()
                    .map(|slice| repo.to_string(*slice))
                    .collect::<Vec<_>>();
                self.new_scopes.push(stack_strings);
                id
            }
        }
    }

    #[allow(unused)]
    // Return true if there's any more work to be done.
    fn highlight_one_line(&mut self, ctx: &mut MyView) -> bool {
        if let Some(line_num) = ctx.get_frontier() {
            let (line_num, offset, state) = ctx.get_prev(line_num);
            if offset != self.offset {
                self.flush_spans(ctx);
                self.offset = offset;
                self.spans_start = offset;
            }
            let new_frontier = match ctx.get_line(line_num) {
                Ok("") => None,
                Ok(s) => {
                    let new_state = self.compute_syntax(s, state);
                    self.offset += s.len();
                    if s.as_bytes().last() == Some(&b'\n') {
                        Some((new_state, line_num + 1))
                    } else {
                        None
                    }
                }
                Err(_) => None,
            };
            let mut converged = false;
            if let Some((ref new_state, new_line_num)) = new_frontier {
                if let Some(old_state) = ctx.get(new_line_num) {
                    converged = old_state.as_ref().unwrap().0 == new_state.as_ref().unwrap().0;
                }
            }
            if !converged {
                if let Some((new_state, new_line_num)) = new_frontier {
                    ctx.set(new_line_num, new_state);
                    ctx.update_frontier(new_line_num);
                    return true;
                }
            }
            ctx.close_frontier();
        }
        false
    }

    fn flush_spans(&mut self, ctx: &mut MyView) {
        let _t = trace_block("PluginState::flush_spans", &["syntect"]);
        if !self.new_scopes.is_empty() {
            ctx.add_scopes(&self.new_scopes);
            self.new_scopes.clear();
        }
        if self.spans_start != self.offset {
            ctx.update_spans(self.spans_start, self.offset - self.spans_start,
                             &self.spans);
            self.spans.clear();
        }
        self.spans_start = self.offset;
    }
}

type MyView = View<StateCache<LineState>>;

impl<'a> Syntect<'a> {
    fn new(syntax_set: &'a SyntaxSet) -> Self {
        Syntect {
            view_state: HashMap::new(),
            syntax_set: syntax_set,
        }
    }

    /// Wipes any existing state and starts highlighting with `syntax`.
    fn do_highlighting(&mut self, view: &mut MyView) {
        let initial_state = {
            let syntax = self.guess_syntax(view.get_path());
            Some((ParseState::new(syntax), ScopeStack::new()))
        };

        let state = self.view_state.get_mut(&view.get_id()).unwrap();
        state.initial_state = initial_state;
        state.spans = Vec::new();
        state.new_scopes = Vec::new();
        state.offset = 0;
        state.spans_start = 0;
        view.get_cache().clear();
        view.schedule_idle();
    }

    fn guess_syntax(&'a self, path: Option<&Path>) -> &'a SyntaxDefinition {
        let _t = trace_block("Syntect::guess_syntax", &["syntect"]);
        match path {
            Some(path) => self.syntax_set.find_syntax_for_file(path)
                .ok()
                .unwrap_or(None)
                .unwrap_or_else(|| self.syntax_set.find_syntax_plain_text()),
            None => self.syntax_set.find_syntax_plain_text(),
        }
    }

    /// Checks if a newline has been inserted, and if so inserts whitespace
    /// as necessary.
    fn do_indentation(&mut self, view: &mut MyView, start: usize, end: usize, text: &str) {
        let _t = trace_block("PluginState::do_indentation", &["syntect"]);
        // don't touch indentation if this is not a simple edit
        if end != start { return }

        let line_ending = view.get_config().line_ending.clone();
        let is_newline = line_ending == text;

        if is_newline {
            let line_num = view.line_of_offset(start).unwrap();

            let use_spaces = view.get_config().translate_tabs_to_spaces;
            let tab_size = view.get_config().tab_size;
            let buf_size = view.get_buf_size();

            let result = if let Some(line) = view.get_line(line_num).ok() {
                // do not send update if last line is empty string (contains only line ending)
                if line == line_ending { return }

                let indent = self.indent_for_next_line(
                    line, use_spaces, tab_size);
                let ix = start + text.len();
                let interval = Interval::new_open_closed(ix, ix);
                //TODO: view should have a `get_edit_builder` fn?
                let mut builder = EditBuilder::new(buf_size);
                builder.replace(interval, indent.into());

                let delta = builder.build();
                Some(delta)
            } else {
                None
            };

            if let Some(delta) = result {
                view.edit(delta, INDENTATION_PRIORITY, false,
                          false, String::from("syntect"));
            }
        }
    }

    /// Returns the string which should be inserted after the newline
    /// to achieve the desired indentation level.
    fn indent_for_next_line<'b>(&self, prev_line: &'b str, use_spaces: bool,
                                tab_size: usize) -> Cow<'b, str> {
        let leading_ws = prev_line.char_indices()
            .find(|&(_, c)| !c.is_whitespace())
            .or(prev_line.char_indices().last())
            .map(|(idx, _)| unsafe { prev_line.get_unchecked(0..idx) })
            .unwrap_or("");

        if self.increase_indentation(prev_line) {
            let indent_text = if use_spaces {
                &"                                    "[..tab_size]
            } else {
                "\t"
            };
            format!("{}{}", leading_ws, indent_text).into()
        } else {
            leading_ws.into()
        }
    }

    /// Checks if the indent level should be increased.
    fn increase_indentation(&self, prev_line: &str) -> bool {
        let trailing_char = prev_line.trim_right().chars()
            .rev().next().unwrap_or(' ');
        // very naive heuristic for modifying indentation level.
        match trailing_char {
            '{' | ':' => true,
            _ => false,
        }
    }
}

impl<'a> Plugin for Syntect<'a> {
    type Cache = StateCache<LineState>;

    fn new_view(&mut self, view: &mut View<Self::Cache>) {
        let _t = trace_block("Syntect::new_view", &["syntect"]);
        let view_id = view.get_id();
        let state = PluginState::new();
        self.view_state.insert(view_id, state);
        self.do_highlighting(view);
    }

    fn did_close(&mut self, view: &View<Self::Cache>) {
        self.view_state.remove(&view.get_id());
    }

    fn did_save(&mut self, view: &mut View<Self::Cache>, _old: Option<&Path>) {
        let _t = trace_block("Syntect::did_save", &["syntect"]);
        self.do_highlighting(view);
    }

    fn config_changed(&mut self, _view: &mut View<Self::Cache>, _changes: &ConfigTable) {}

    fn update(&mut self, view: &mut View<Self::Cache>, delta: Option<&RopeDelta>,
              _edit_type: String, _author: String) {
        let _t = trace_block("Syntect::update", &["syntect"]);
        view.schedule_idle();
        let should_auto_indent = view.get_config().auto_indent;
        if !should_auto_indent { return }
        if let Some(delta) = delta {
            let (iv, _) = delta.summary();
            if let Some(s) = delta.as_simple_insert() {
                let s: String = s.into();
                self.do_indentation(view, iv.start(), iv.end(), &s);
            }
        }
    }

    fn idle(&mut self, view: &mut View<Self::Cache>) {
        let state = self.view_state.get_mut(&view.get_id()).unwrap();
        for _ in 0..LINES_PER_RPC {
            if !state.highlight_one_line(view) {
                state.flush_spans(view);
                return;
            }
            if view.request_is_pending() {
                trace("yielding for request", &["syntect"]);
                break;
            }
        }
        state.flush_spans(view);
        view.schedule_idle();
    }
}

fn main() {
    let syntax_set = SyntaxSet::load_defaults_newlines();
    let mut state = Syntect::new(&syntax_set);
    mainloop(&mut state).unwrap();
}
<|endoftext|>


<commit_before>#![allow(dead_code)]
extern crate slab;
extern crate mio;

use self::mio::Token;

type Slab<T> = slab::Slab<T, Token>;

// Main struct to handle TCP networking
pub struct TcpNetwork {

}
<commit_msg>making basic networking structure<commit_after>#![allow(dead_code)]
extern crate slab;
extern crate mio;

use self::mio::Token;
use self::mio::tcp::TcpListener;

type Slab<T> = slab::Slab<T, Token>;

// Main struct to handle TCP networking
pub struct TcpNetwork {
    server_socket: TcpListener,
}
<|endoftext|>


<commit_before>// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![allow(non_snake_case)]

register_long_diagnostics! {

E0373: r##"
This error occurs when an attempt is made to use data captured by a closure,
when that data may no longer exist. It's most commonly seen when attempting to
return a closure:

```
fn foo() -> Box<Fn(u32) -> u32> {
    let x = 0u32;
    Box::new(|y| x + y)
}
```

Notice that `x` is stack-allocated by `foo()`. By default, Rust captures
closed-over data by reference. This means that once `foo()` returns, `x` no
longer exists. An attempt to access `x` within the closure would thus be unsafe.

Another situation where this might be encountered is when spawning threads:

```
fn foo() {
    let x = 0u32;
    let y = 1u32;

    let thr = std::thread::spawn(|| {
        x + y
    });
}
```

Since our new thread runs in parallel, the stack frame containing `x` and `y`
may well have disappeared by the time we try to use them. Even if we call
`thr.join()` within foo (which blocks until `thr` has completed, ensuring the
stack frame won't disappear), we will not succeed: the compiler cannot prove
that this behaviour is safe, and so won't let us do it.

The solution to this problem is usually to switch to using a `move` closure.
This approach moves (or copies, where possible) data into the closure, rather
than taking references to it. For example:

```
fn foo() -> Box<Fn(u32) -> u32> {
    let x = 0u32;
    Box::new(move |y| x + y)
}
```

Now that the closure has its own copy of the data, there's no need to worry
about safety.
"##,

E0381: r##"
It is not allowed to use or capture an uninitialized variable. For example:

```
fn main() {
    let x: i32;
    let y = x; // error, use of possibly uninitialized variable
```

To fix this, ensure that any declared variables are initialized before being
used.
"##,

E0384: r##"
This error occurs when an attempt is made to reassign an immutable variable.
For example:

```
fn main(){
    let x = 3;
    x = 5; // error, reassignment of immutable variable
}
```

By default, variables in Rust are immutable. To fix this error, add the keyword
`mut` after the keyword `let` when declaring the variable. For example:

```
fn main(){
    let mut x = 3;
    x = 5;
}
```
"##

}

register_diagnostics! {
    E0382, // use of partially/collaterally moved value
    E0383, // partial reinitialization of uninitialized structure
    E0385, // {} in an aliasable location
    E0386, // {} in an immutable container
    E0387, // {} in a captured outer variable in an `Fn` closure
    E0388, // {} in a static location
    E0389  // {} in a `&` reference
}
<commit_msg>add long diagnostics for E0382<commit_after>// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![allow(non_snake_case)]

register_long_diagnostics! {

E0373: r##"
This error occurs when an attempt is made to use data captured by a closure,
when that data may no longer exist. It's most commonly seen when attempting to
return a closure:

```
fn foo() -> Box<Fn(u32) -> u32> {
    let x = 0u32;
    Box::new(|y| x + y)
}
```

Notice that `x` is stack-allocated by `foo()`. By default, Rust captures
closed-over data by reference. This means that once `foo()` returns, `x` no
longer exists. An attempt to access `x` within the closure would thus be unsafe.

Another situation where this might be encountered is when spawning threads:

```
fn foo() {
    let x = 0u32;
    let y = 1u32;

    let thr = std::thread::spawn(|| {
        x + y
    });
}
```

Since our new thread runs in parallel, the stack frame containing `x` and `y`
may well have disappeared by the time we try to use them. Even if we call
`thr.join()` within foo (which blocks until `thr` has completed, ensuring the
stack frame won't disappear), we will not succeed: the compiler cannot prove
that this behaviour is safe, and so won't let us do it.

The solution to this problem is usually to switch to using a `move` closure.
This approach moves (or copies, where possible) data into the closure, rather
than taking references to it. For example:

```
fn foo() -> Box<Fn(u32) -> u32> {
    let x = 0u32;
    Box::new(move |y| x + y)
}
```

Now that the closure has its own copy of the data, there's no need to worry
about safety.
"##,

E0381: r##"
It is not allowed to use or capture an uninitialized variable. For example:

```
fn main() {
    let x: i32;
    let y = x; // error, use of possibly uninitialized variable
```

To fix this, ensure that any declared variables are initialized before being
used.
"##,

E0382: r##"
This error occurs when an attempt is made to use a variable after its contents
have been moved elsewhere. For example:

```
struct MyStruct { s: u32 }

fn main() {
    let mut x = MyStruct{ s: 5u32 };
    let y = x;
    x.s = 6;
    println!("{}", x.s);
}
```

Since `MyStruct` is a type that is not marked `Copy`, the data gets moved out
of `x` when we set `y`. This is fundamental to Rust's ownership system: outside
of workarounds like `Rc`, a value cannot be owned by more than one variable.

If we own the type, the easiest way to address this problem is to implement
`Copy` and `Clone` on it, as shown below. This allows `y` to copy the
information in `x`, while leaving the original version owned by `x`. Subsequent
changes to `x` will not be reflected when accessing `y`.

```
#[derive(Copy, Clone)]
struct MyStruct { s: u32 }

fn main() {
    let mut x = MyStruct{ s: 5u32 };
    let y = x;
    x.s = 6;
    println!("{}", x.s);
}
```

Alternatively, if we don't control the struct's definition, or mutable shared
ownership is truly required, we can use `Rc` and `RefCell`:

```
use std::cell::RefCell;
use std::rc::Rc;

struct MyStruct { s: u32 }

fn main() {
    let mut x = Rc::new(RefCell::new(MyStruct{ s: 5u32 }));
    let y = x.clone();
    x.borrow_mut().s = 6;
    println!("{}", x.borrow.s);
}
```

With this approach, x and y share ownership of the data via the `Rc` (reference
count type). `RefCell` essentially performs runtime borrow checking: ensuring
that at most one writer or multiple readers can access the data at any one time.

If you wish to learn more about ownership in Rust, start with the chapter in the
Book:

https://doc.rust-lang.org/book/ownership.html
"##,

E0384: r##"
This error occurs when an attempt is made to reassign an immutable variable.
For example:

```
fn main(){
    let x = 3;
    x = 5; // error, reassignment of immutable variable
}
```

By default, variables in Rust are immutable. To fix this error, add the keyword
`mut` after the keyword `let` when declaring the variable. For example:

```
fn main(){
    let mut x = 3;
    x = 5;
}
```
"##

}

register_diagnostics! {
    E0383, // partial reinitialization of uninitialized structure
    E0385, // {} in an aliasable location
    E0386, // {} in an immutable container
    E0387, // {} in a captured outer variable in an `Fn` closure
    E0388, // {} in a static location
    E0389  // {} in a `&` reference
}
<|endoftext|>


<commit_before>use std::cell::RefCell;
use std::collections::hash_map::{Entry, HashMap};
use std::collections::hash_set::HashSet;
use std::rc::Rc;

use uuid::Uuid;

use alloc::AllocBox;
use js_types::js_type::{JsPtrEnum, JsType, JsVar};

pub struct Scope {
    pub parent: Option<Rc<Scope>>,
    alloc_box: Rc<RefCell<AllocBox>>,
    // TODO how should we save stack variables that are live-out from this scope
    // frame? Push them up to our parent? There's also the problem of this scope
    // needing access to its parent's variables that are on the stack, but right
    // now the way that'd work is by traversing the scope tree upwards until you
    // find what you're looking for, which is not the best solution. Also, how
    // granular is a scope? Is an `if` block a new scope, or are new scopes only
    // introduced by function calls (things that would actually introduce a new
    // stack frame)?
    //
    // Perhaps a better way to think about it is like this: every scope owns some
    // "stack allocator" object which contains *everything* allocated on the stack
    // up until that point, including things allocated by the parent scope. When
    // a new scope gets pushed, ownership of this object gets transferred to the
    // new scope, which then returns ownership when it exits, deleting the set of
    // things it allocated. This could even just be a list of arenas (where `list`
    // is an ambiguous term; it might be an actual stack or a hash map or something),
    // such that a scope can just drop its arena when it exits. Is that okay? What
    // problems could that cause? Lookup is a bit harder, I suppose, since you'd
    // have to search all arenas in the worst case. Maybe look into simonsapin's
    // ArenaTree implementation? There's also still the problem of live-out references,
    // as well as deleting things that get GC'd by making them `undefined`. Arenas
    // might be too coarse-grained to solve either of those problems.
    //
    // How can we handle live-out references? They're taken care of under the
    // hood in alloc_box, i.e. they don't get deleted. From the frontend, though,
    // all stack allocations will get deleted from the current scope unless they
    // get saved somewhere due to being live-out. POD values can be deleted safely,
    // since they contain no references. Non-POD must be moved into ownership by
    // the parent scope until they are GC'd, at which point the GC should tell
    // this scope that those references are now `undef`. How do you reconsile
    // the fact that a dead-out reference might not be considered as such until
    // after the scope exits? Obviously, you can consider everything live-out
    // unless the GC tells you it's not, but these pointers could in theory be
    // used by a different scope, even if they die. I guess that's not really a
    // problem, though, since usage makes you not dead anymore.
    //
    // So there we go. Delete all POD values when the scope exits, and pass all
    // references to the parent to await GC (unless this scope invokes the GC,
    // in which case they get marked as `undef` and die due to being POD.
    stack: HashMap<Uuid, JsVar>,
    pub get_roots: Box<Fn() -> HashSet<Uuid>>,
}

impl Scope {
    pub fn new<F>(alloc_box: &Rc<RefCell<AllocBox>>, get_roots: F) -> Scope
        where F: Fn() -> HashSet<Uuid> + 'static {
        Scope {
            parent: None,
            alloc_box: alloc_box.clone(),
            stack: HashMap::new(),
            get_roots: Box::new(get_roots),
        }
    }

    pub fn as_child<F>(parent: &Rc<Scope>, alloc_box: &Rc<RefCell<AllocBox>>, get_roots: F) -> Scope
        where F: Fn() -> HashSet<Uuid> + 'static {
        Scope {
            parent: Some(parent.clone()),
            alloc_box: alloc_box.clone(),
            stack: HashMap::new(),
            get_roots: Box::new(get_roots),
        }
    }

    pub fn set_parent(&mut self, parent: &Rc<Scope>) {
        self.parent = Some(parent.clone());
    }

    fn alloc(&mut self, uuid: Uuid, ptr: JsPtrEnum) -> Uuid {
        self.alloc_box.borrow_mut().alloc(uuid, ptr)
    }

    fn dealloc(&mut self, uuid: &Uuid) -> bool {
        self.alloc_box.borrow_mut().dealloc(uuid)
    }

    pub fn push(&mut self, var: JsVar, ptr: Option<JsPtrEnum>) -> Uuid {
        let uuid = match &var.t {
            &JsType::JsPtr => self.alloc(var.uuid, ptr.unwrap()),
                //.expect("ERR: Attempted allocation of heap pointer, but pointer contents were invalid!"));
            _ => var.uuid,
        };
        self.stack.insert(var.uuid, var);
        uuid
    }

    pub fn get_var_copy(&self, uuid: &Uuid) -> (Option<JsVar>, Option<JsPtrEnum>) {
        if let Some(var) = self.stack.get(uuid) {
            match var.t {
                JsType::JsPtr => {
                    if let Some(alloc) = self.alloc_box.borrow().find_id(uuid) {
                        (Some(var.clone()), Some(alloc.borrow().clone()))
                    } else {
                        // This case should be impossible unless you have an
                        // invalid ptr, which should also be impossible.
                        (None, None)
                    }
                },
                _ => (Some(var.clone()), None),
            }
        } else { (None, None) }
    }

    // TODO is there a better way to encode ptr than as an option that is only
    // ever used when it is `Some`? Default argument?
    pub fn update_var(&mut self, var: JsVar, ptr: Option<JsPtrEnum>) -> bool {
        match var.t {
            JsType::JsPtr => self.alloc_box.borrow_mut().update_ptr(&var.uuid, ptr.unwrap()),
            _ => {
                // TODO might be simpler to do this with `insert`, but I can't remember what it returns
                if let Entry::Occupied(mut view) = self.stack.entry(var.uuid) {
                    *view.get_mut() = var;
                    true
                } else { false }
            },
        }
    }
}

impl Drop for Scope {
    // TODO get the sig for this
    fn drop(&mut self) {
        for (uuid, var) in self.stack.drain() {
            // push to parent
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::cell::RefCell;
    use std::collections::hash_set::HashSet;
    use std::rc::{Rc, Weak};
    use uuid::Uuid;

    use alloc::{Alloc, AllocBox};
    use js_types::js_type::{JsVar, JsType, JsPtrEnum, JsKey, JsKeyEnum};
    use js_types::js_obj::JsObjStruct;
    use js_types::js_str::JsStrStruct;

    fn dummy_get_roots() -> HashSet<Uuid> {
        HashSet::new()
    }

    fn make_alloc_box() -> Rc<RefCell<AllocBox>> {
        Rc::new(RefCell::new(AllocBox::new()))
    }

    fn make_num(i: f64) -> JsVar {
        JsVar::new(JsType::JsNum(i))
    }

    fn make_obj(alloc_box: Rc<RefCell<AllocBox>>, kvs: Vec<(JsKey, JsVar)>) -> JsVar {
        let var = JsVar::new(JsType::JsPtr);
        alloc_box.borrow_mut()
            .alloc(var.uuid, JsPtrEnum::JsObj(JsObjStruct::new(None, "test", kvs)));
        var
    }

    #[test]
    fn test_new_scope() {
        let alloc_box = make_alloc_box();
        let mut test_scope = Scope::new(&alloc_box, dummy_get_roots);
        assert!(test_scope.parent.is_none());
    }

    #[test]
    fn test_as_child_scope() {
        let alloc_box = make_alloc_box();
        let parent_scope = Rc::new(Scope::new(&alloc_box, dummy_get_roots));
        let mut test_scope = Scope::as_child(&parent_scope, &alloc_box, dummy_get_roots);
        assert!(test_scope.parent.is_some());
    }

    #[test]
    fn test_set_parent() {
        let alloc_box = make_alloc_box();
        let parent_scope = Rc::new(Scope::new(&alloc_box, dummy_get_roots));
        let mut test_scope = Scope::new(&alloc_box, dummy_get_roots);
        assert!(test_scope.parent.is_none());
        test_scope.set_parent(&parent_scope);
        assert!(test_scope.parent.is_some());
    }

    #[test]
    fn test_alloc() {
        let alloc_box = make_alloc_box();
        let mut test_scope = Scope::new(&alloc_box, dummy_get_roots);
        let test_var = JsVar::new(JsType::JsPtr);
        let test_id = test_scope.alloc(test_var.uuid, JsPtrEnum::JsSym(String::from("test")));
        assert_eq!(test_id, test_var.uuid);
    }

    #[test]
    fn test_dealloc() {
        let alloc_box = make_alloc_box();
        let mut test_scope = Scope::new(&alloc_box, dummy_get_roots);
        let test_var = JsVar::new(JsType::JsPtr);
        let test_id = test_scope.alloc(test_var.uuid, JsPtrEnum::JsSym(String::from("test")));
        let bad_uuid = Uuid::new_v4();
        assert!(test_scope.dealloc(&test_id));
        assert!(!test_scope.dealloc(&bad_uuid));
    }

    #[test]
    fn test_get_var_copy() {
        let alloc_box = make_alloc_box();
        let mut test_scope = Scope::new(&alloc_box, dummy_get_roots);
        let test_var = JsVar::new(JsType::JsPtr);
        let test_id = test_scope.push(test_var, Some(JsPtrEnum::JsSym(String::from("test"))));
        let bad_uuid = Uuid::new_v4();

        let (var_copy, ptr_copy) = test_scope.get_var_copy(&test_id);
        assert!(var_copy.is_some());
        assert!(ptr_copy.is_some());

        let (bad_copy, ptr_copy) = test_scope.get_var_copy(&bad_uuid);
        assert!(bad_copy.is_none());
        assert!(ptr_copy.is_none());
    }

    #[test]
    fn test_update_var() {
        let alloc_box = make_alloc_box();
        let mut test_scope = Scope::new(&alloc_box, dummy_get_roots);
        let test_var = JsVar::new(JsType::JsPtr);
        let test_id = test_scope.push(test_var, Some(JsPtrEnum::JsSym(String::from("test"))));
        let (update, mut update_ptr) = test_scope.get_var_copy(&test_id);
        update_ptr = Some(JsPtrEnum::JsStr(JsStrStruct::new("test")));
        assert!(test_scope.update_var(update.unwrap(), update_ptr));

        let (update, update_ptr) = test_scope.get_var_copy(&test_id);
        match update_ptr.clone().unwrap() {
            JsPtrEnum::JsStr(JsStrStruct{text: ref s}) => assert_eq!(s, "test"),
            _ => ()
        }
        test_scope.dealloc(&test_id);
        assert!(update.is_some());
        assert!(!test_scope.update_var(update.unwrap(), update_ptr));
    }
}
<commit_msg>Allow scopes to GC and push heap-allocated (non-POD) vars to their parents on drop<commit_after>use std::cell::RefCell;
use std::collections::hash_map::{Entry, HashMap};
use std::collections::hash_set::HashSet;
use std::rc::Rc;

use uuid::Uuid;

use alloc::AllocBox;
use js_types::js_type::{JsPtrEnum, JsType, JsVar};

// Tunable GC parameter. Probably should not be a constant, but good enough for now.
const GC_THRESHOLD: usize = 64;

pub struct Scope {
    pub parent: Option<Rc<Scope>>,
    alloc_box: Rc<RefCell<AllocBox>>,
    // TODO how should we save stack variables that are live-out from this scope
    // frame? Push them up to our parent? There's also the problem of this scope
    // needing access to its parent's variables that are on the stack, but right
    // now the way that'd work is by traversing the scope tree upwards until you
    // find what you're looking for, which is not the best solution. Also, how
    // granular is a scope? Is an `if` block a new scope, or are new scopes only
    // introduced by function calls (things that would actually introduce a new
    // stack frame)?
    //
    // Perhaps a better way to think about it is like this: every scope owns some
    // "stack allocator" object which contains *everything* allocated on the stack
    // up until that point, including things allocated by the parent scope. When
    // a new scope gets pushed, ownership of this object gets transferred to the
    // new scope, which then returns ownership when it exits, deleting the set of
    // things it allocated. This could even just be a list of arenas (where `list`
    // is an ambiguous term; it might be an actual stack or a hash map or something),
    // such that a scope can just drop its arena when it exits. Is that okay? What
    // problems could that cause? Lookup is a bit harder, I suppose, since you'd
    // have to search all arenas in the worst case. Maybe look into simonsapin's
    // ArenaTree implementation? There's also still the problem of live-out references,
    // as well as deleting things that get GC'd by making them `undefined`. Arenas
    // might be too coarse-grained to solve either of those problems.
    //
    // How can we handle live-out references? They're taken care of under the
    // hood in alloc_box, i.e. they don't get deleted. From the frontend, though,
    // all stack allocations will get deleted from the current scope unless they
    // get saved somewhere due to being live-out. POD values can be deleted safely,
    // since they contain no references. Non-POD must be moved into ownership by
    // the parent scope until they are GC'd, at which point the GC should tell
    // this scope that those references are now `undef`. How do you reconsile
    // the fact that a dead-out reference might not be considered as such until
    // after the scope exits? Obviously, you can consider everything live-out
    // unless the GC tells you it's not, but these pointers could in theory be
    // used by a different scope, even if they die. I guess that's not really a
    // problem, though, since usage makes you not dead anymore.
    //
    // So there we go. Delete all POD values when the scope exits, and pass all
    // references to the parent to await GC (unless this scope invokes the GC,
    // in which case they get marked as `undef` and die due to being POD.
    stack: HashMap<Uuid, JsVar>,
    pub get_roots: Box<Fn() -> HashSet<Uuid>>,
}

impl Scope {
    pub fn new<F>(alloc_box: &Rc<RefCell<AllocBox>>, get_roots: F) -> Scope
        where F: Fn() -> HashSet<Uuid> + 'static {
        Scope {
            parent: None,
            alloc_box: alloc_box.clone(),
            stack: HashMap::new(),
            get_roots: Box::new(get_roots),
        }
    }

    pub fn as_child<F>(parent: &Rc<Scope>, alloc_box: &Rc<RefCell<AllocBox>>, get_roots: F) -> Scope
        where F: Fn() -> HashSet<Uuid> + 'static {
        Scope {
            parent: Some(parent.clone()),
            alloc_box: alloc_box.clone(),
            stack: HashMap::new(),
            get_roots: Box::new(get_roots),
        }
    }

    pub fn set_parent(&mut self, parent: &Rc<Scope>) {
        self.parent = Some(parent.clone());
    }

    fn alloc(&mut self, uuid: Uuid, ptr: JsPtrEnum) -> Uuid {
        self.alloc_box.borrow_mut().alloc(uuid, ptr)
    }

    fn dealloc(&mut self, uuid: &Uuid) -> bool {
        self.alloc_box.borrow_mut().dealloc(uuid)
    }

    pub fn push(&mut self, var: JsVar, ptr: Option<JsPtrEnum>) -> Uuid {
        let uuid = match &var.t {
            &JsType::JsPtr => self.alloc(var.uuid, ptr.unwrap()),
                //.expect("ERR: Attempted allocation of heap pointer, but pointer contents were invalid!"));
            _ => var.uuid,
        };
        self.stack.insert(var.uuid, var);
        uuid
    }

    pub fn own(&mut self, var: JsVar) {
        self.stack.insert(var.uuid, var);
    }

    pub fn get_var_copy(&self, uuid: &Uuid) -> (Option<JsVar>, Option<JsPtrEnum>) {
        if let Some(var) = self.stack.get(uuid) {
            match var.t {
                JsType::JsPtr => {
                    if let Some(alloc) = self.alloc_box.borrow().find_id(uuid) {
                        (Some(var.clone()), Some(alloc.borrow().clone()))
                    } else {
                        // This case should be impossible unless you have an
                        // invalid ptr, which should also be impossible.
                        (None, None)
                    }
                },
                _ => (Some(var.clone()), None),
            }
        } else { (None, None) }
    }

    // TODO is there a better way to encode ptr than as an option that is only
    // ever used when it is `Some`? Default argument?
    pub fn update_var(&mut self, var: JsVar, ptr: Option<JsPtrEnum>) -> bool {
        match var.t {
            JsType::JsPtr => self.alloc_box.borrow_mut().update_ptr(&var.uuid, ptr.unwrap()),
            _ => {
                // TODO might be simpler to do this with `insert`, but I can't remember what it returns
                if let Entry::Occupied(mut view) = self.stack.entry(var.uuid) {
                    *view.get_mut() = var;
                    true
                } else { false }
            },
        }
    }
}

impl Drop for Scope {
    fn drop(&mut self) {
        if self.alloc_box.borrow().len() > GC_THRESHOLD {
            self.alloc_box.borrow_mut().mark_roots((self.get_roots)());
            self.alloc_box.borrow_mut().mark_ptrs();
            self.alloc_box.borrow_mut().sweep_ptrs();
        }
        if let Some(ref mut parent) = self.parent {
            for (_, var) in self.stack.drain() {
                match var.t {
                    JsType::JsPtr => Rc::get_mut(parent).unwrap().own(var),
                    _ => (),
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::cell::RefCell;
    use std::collections::hash_set::HashSet;
    use std::rc::{Rc, Weak};
    use uuid::Uuid;

    use alloc::{Alloc, AllocBox};
    use js_types::js_type::{JsVar, JsType, JsPtrEnum, JsKey, JsKeyEnum};
    use js_types::js_obj::JsObjStruct;
    use js_types::js_str::JsStrStruct;

    fn dummy_get_roots() -> HashSet<Uuid> {
        HashSet::new()
    }

    fn make_alloc_box() -> Rc<RefCell<AllocBox>> {
        Rc::new(RefCell::new(AllocBox::new()))
    }

    fn make_num(i: f64) -> JsVar {
        JsVar::new(JsType::JsNum(i))
    }

    fn make_obj(alloc_box: Rc<RefCell<AllocBox>>, kvs: Vec<(JsKey, JsVar)>) -> JsVar {
        let var = JsVar::new(JsType::JsPtr);
        alloc_box.borrow_mut()
            .alloc(var.uuid, JsPtrEnum::JsObj(JsObjStruct::new(None, "test", kvs)));
        var
    }

    #[test]
    fn test_new_scope() {
        let alloc_box = make_alloc_box();
        let mut test_scope = Scope::new(&alloc_box, dummy_get_roots);
        assert!(test_scope.parent.is_none());
    }

    #[test]
    fn test_as_child_scope() {
        let alloc_box = make_alloc_box();
        let parent_scope = Rc::new(Scope::new(&alloc_box, dummy_get_roots));
        let mut test_scope = Scope::as_child(&parent_scope, &alloc_box, dummy_get_roots);
        assert!(test_scope.parent.is_some());
    }

    #[test]
    fn test_set_parent() {
        let alloc_box = make_alloc_box();
        let parent_scope = Rc::new(Scope::new(&alloc_box, dummy_get_roots));
        let mut test_scope = Scope::new(&alloc_box, dummy_get_roots);
        assert!(test_scope.parent.is_none());
        test_scope.set_parent(&parent_scope);
        assert!(test_scope.parent.is_some());
    }

    #[test]
    fn test_alloc() {
        let alloc_box = make_alloc_box();
        let mut test_scope = Scope::new(&alloc_box, dummy_get_roots);
        let test_var = JsVar::new(JsType::JsPtr);
        let test_id = test_scope.alloc(test_var.uuid, JsPtrEnum::JsSym(String::from("test")));
        assert_eq!(test_id, test_var.uuid);
    }

    #[test]
    fn test_dealloc() {
        let alloc_box = make_alloc_box();
        let mut test_scope = Scope::new(&alloc_box, dummy_get_roots);
        let test_var = JsVar::new(JsType::JsPtr);
        let test_id = test_scope.alloc(test_var.uuid, JsPtrEnum::JsSym(String::from("test")));
        let bad_uuid = Uuid::new_v4();
        assert!(test_scope.dealloc(&test_id));
        assert!(!test_scope.dealloc(&bad_uuid));
    }

    #[test]
    fn test_get_var_copy() {
        let alloc_box = make_alloc_box();
        let mut test_scope = Scope::new(&alloc_box, dummy_get_roots);
        let test_var = JsVar::new(JsType::JsPtr);
        let test_id = test_scope.push(test_var, Some(JsPtrEnum::JsSym(String::from("test"))));
        let bad_uuid = Uuid::new_v4();

        let (var_copy, ptr_copy) = test_scope.get_var_copy(&test_id);
        assert!(var_copy.is_some());
        assert!(ptr_copy.is_some());

        let (bad_copy, ptr_copy) = test_scope.get_var_copy(&bad_uuid);
        assert!(bad_copy.is_none());
        assert!(ptr_copy.is_none());
    }

    #[test]
    fn test_update_var() {
        let alloc_box = make_alloc_box();
        let mut test_scope = Scope::new(&alloc_box, dummy_get_roots);
        let test_var = JsVar::new(JsType::JsPtr);
        let test_id = test_scope.push(test_var, Some(JsPtrEnum::JsSym(String::from("test"))));
        let (update, mut update_ptr) = test_scope.get_var_copy(&test_id);
        update_ptr = Some(JsPtrEnum::JsStr(JsStrStruct::new("test")));
        assert!(test_scope.update_var(update.unwrap(), update_ptr));

        let (update, update_ptr) = test_scope.get_var_copy(&test_id);
        match update_ptr.clone().unwrap() {
            JsPtrEnum::JsStr(JsStrStruct{text: ref s}) => assert_eq!(s, "test"),
            _ => ()
        }
        test_scope.dealloc(&test_id);
        assert!(update.is_some());
        assert!(!test_scope.update_var(update.unwrap(), update_ptr));
    }
}
<|endoftext|>


<commit_before>use std::cell::Cell;

use cairo::{self, ImageSurface};

use attributes::Attribute;
use error::NodeError;
use handle::RsvgHandle;
use node::{NodeResult, NodeTrait, RsvgCNodeImpl, RsvgNode};
use parsers;
use property_bag::PropertyBag;
use util::clamp;

use super::context::{FilterContext, FilterOutput, FilterResult, IRect};
use super::iterators::{ImageSurfaceDataExt, ImageSurfaceDataShared, Pixels};
use super::{make_result, Filter, FilterError, PrimitiveWithInput};

/// The `feOffset` filter primitive.
pub struct Offset {
    base: PrimitiveWithInput,
    dx: Cell<f64>,
    dy: Cell<f64>,
}

impl Offset {
    /// Constructs a new `Offset` with empty properties.
    #[inline]
    pub fn new() -> Offset {
        Offset {
            base: PrimitiveWithInput::new::<Self>(),
            dx: Cell::new(0f64),
            dy: Cell::new(0f64),
        }
    }
}

impl NodeTrait for Offset {
    fn set_atts(
        &self,
        node: &RsvgNode,
        handle: *const RsvgHandle,
        pbag: &PropertyBag,
    ) -> NodeResult {
        self.base.set_atts(node, handle, pbag)?;

        for (_key, attr, value) in pbag.iter() {
            match attr {
                Attribute::Dx => self
                    .dx
                    .set(parsers::number(value).map_err(|err| NodeError::parse_error(attr, err))?),
                Attribute::Dy => self
                    .dy
                    .set(parsers::number(value).map_err(|err| NodeError::parse_error(attr, err))?),
                _ => (),
            }
        }

        Ok(())
    }

    #[inline]
    fn get_c_impl(&self) -> *const RsvgCNodeImpl {
        self.base.get_c_impl()
    }
}

impl Filter for Offset {
    fn render(&self, _node: &RsvgNode, ctx: &FilterContext) -> Result<FilterResult, FilterError> {
        let input = make_result(self.base.get_input(ctx))?;
        let bounds = self.base.get_bounds(ctx).add_input(&input).into_irect();

        let dx = self.dx.get();
        let dy = self.dy.get();
        let paffine = ctx.paffine();
        let ox = (paffine.xx * dx + paffine.xy * dy) as i32;
        let oy = (paffine.yx * dx + paffine.yy * dy) as i32;

        let input_data = unsafe {
            ImageSurfaceDataShared::new_unchecked(input.surface())
                .map_err(FilterError::BadInputSurfaceStatus)?
        };

        // input_bounds contains all pixels within bounds,
        // for which (x + ox) and (y + oy) also lie within bounds.
        let input_bounds = IRect {
            x0: clamp(bounds.x0 - ox, bounds.x0, bounds.x1),
            y0: clamp(bounds.y0 - oy, bounds.y0, bounds.y1),
            x1: clamp(bounds.x1 - ox, bounds.x0, bounds.x1),
            y1: clamp(bounds.y1 - oy, bounds.y0, bounds.y1),
        };

        let mut output_surface = ImageSurface::create(
            cairo::Format::ARgb32,
            input_data.width as i32,
            input_data.height as i32,
        ).map_err(FilterError::OutputSurfaceCreation)?;

        let output_stride = output_surface.get_stride() as usize;
        {
            let mut output_data = output_surface.get_data().unwrap();

            for (x, y, pixel) in Pixels::new(input_data, input_bounds) {
                let output_x = (x as i32 + ox) as usize;
                let output_y = (y as i32 + oy) as usize;
                output_data.set_pixel(output_stride, pixel, output_x, output_y);
            }
        }

        Ok(FilterResult {
            name: self.base.result.borrow().clone(),
            output: FilterOutput {
                surface: output_surface,
                bounds,
            },
        })
    }
}
<commit_msg>Remake feOffset in terms of cairo<commit_after>use std::cell::Cell;

use cairo::{self, ImageSurface};

use attributes::Attribute;
use error::NodeError;
use handle::RsvgHandle;
use node::{NodeResult, NodeTrait, RsvgCNodeImpl, RsvgNode};
use parsers;
use property_bag::PropertyBag;
use util::clamp;

use super::context::{FilterContext, FilterOutput, FilterResult, IRect};
use super::{make_result, Filter, FilterError, PrimitiveWithInput};

/// The `feOffset` filter primitive.
pub struct Offset {
    base: PrimitiveWithInput,
    dx: Cell<f64>,
    dy: Cell<f64>,
}

impl Offset {
    /// Constructs a new `Offset` with empty properties.
    #[inline]
    pub fn new() -> Offset {
        Offset {
            base: PrimitiveWithInput::new::<Self>(),
            dx: Cell::new(0f64),
            dy: Cell::new(0f64),
        }
    }
}

impl NodeTrait for Offset {
    fn set_atts(
        &self,
        node: &RsvgNode,
        handle: *const RsvgHandle,
        pbag: &PropertyBag,
    ) -> NodeResult {
        self.base.set_atts(node, handle, pbag)?;

        for (_key, attr, value) in pbag.iter() {
            match attr {
                Attribute::Dx => self
                    .dx
                    .set(parsers::number(value).map_err(|err| NodeError::parse_error(attr, err))?),
                Attribute::Dy => self
                    .dy
                    .set(parsers::number(value).map_err(|err| NodeError::parse_error(attr, err))?),
                _ => (),
            }
        }

        Ok(())
    }

    #[inline]
    fn get_c_impl(&self) -> *const RsvgCNodeImpl {
        self.base.get_c_impl()
    }
}

impl Filter for Offset {
    fn render(&self, _node: &RsvgNode, ctx: &FilterContext) -> Result<FilterResult, FilterError> {
        let input = make_result(self.base.get_input(ctx))?;
        let bounds = self.base.get_bounds(ctx).add_input(&input).into_irect();

        let dx = self.dx.get();
        let dy = self.dy.get();
        let paffine = ctx.paffine();
        let ox = (paffine.xx * dx + paffine.xy * dy) as i32;
        let oy = (paffine.yx * dx + paffine.yy * dy) as i32;

        // output_bounds contains all pixels within bounds,
        // for which (x - ox) and (y - oy) also lie within bounds.
        let output_bounds = IRect {
            x0: clamp(bounds.x0 + ox, bounds.x0, bounds.x1),
            y0: clamp(bounds.y0 + oy, bounds.y0, bounds.y1),
            x1: clamp(bounds.x1 + ox, bounds.x0, bounds.x1),
            y1: clamp(bounds.y1 + oy, bounds.y0, bounds.y1),
        };

        let output_surface = ImageSurface::create(
            cairo::Format::ARgb32,
            ctx.source_graphic().get_width(),
            ctx.source_graphic().get_height(),
        ).map_err(FilterError::OutputSurfaceCreation)?;

        let cr = cairo::Context::new(&output_surface);
        cr.rectangle(
            output_bounds.x0 as f64,
            output_bounds.y0 as f64,
            (output_bounds.x1 - output_bounds.x0) as f64,
            (output_bounds.y1 - output_bounds.y0) as f64,
        );
        cr.clip();

        cr.set_source_surface(&input.surface(), f64::from(ox), f64::from(oy));
        cr.paint();

        Ok(FilterResult {
            name: self.base.result.borrow().clone(),
            output: FilterOutput {
                surface: output_surface,
                bounds,
            },
        })
    }
}
<|endoftext|>