<commit_before>// TODO When we settle for a minimum version of Rust >= 1.42, remove this.
#![allow(clippy::match_like_matches_macro)]

use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{Data, Field, Fields, Ident, Lit, Meta, NestedMeta, Variant};

use super::RustlerAttr;

///
/// A parsing context struct.
///
/// `Context` holds information usable for different codegen modules.
///
pub(crate) struct Context<'a> {
    pub attrs: Vec<RustlerAttr>,
    pub ident: &'a proc_macro2::Ident,
    pub ident_with_lifetime: proc_macro2::TokenStream,
    pub variants: Option<Vec<&'a Variant>>,
    pub struct_fields: Option<Vec<&'a Field>>,
    pub is_tuple_struct: bool,
}

impl<'a> Context<'a> {
    pub fn from_ast(ast: &'a syn::DeriveInput) -> Self {
        let mut attrs: Vec<_> = ast
            .attrs
            .iter()
            .map(Context::get_rustler_attrs)
            .flatten()
            .collect();

        //
        // Default: generate encoder and decoder
        //
        if !Context::encode_decode_attr_set(&attrs) {
            attrs.push(RustlerAttr::Encode);
            attrs.push(RustlerAttr::Decode);
        }

        let has_lifetime = match ast.generics.lifetimes().count() {
            0 => false,
            1 => true,
            _ => panic!("Struct can only have one lifetime argument"),
        };

        let ident = &ast.ident;
        let ident_with_lifetime = if has_lifetime {
            quote! { #ident <'a> }
        } else {
            quote! { #ident }
        };

        let variants = match ast.data {
            Data::Enum(ref data_enum) => Some(data_enum.variants.iter().collect()),
            _ => None,
        };

        let struct_fields = match ast.data {
            Data::Struct(ref data_struct) => Some(data_struct.fields.iter().collect()),
            _ => None,
        };

        let is_tuple_struct = match ast.data {
            Data::Struct(ref data_struct) => match data_struct.fields {
                Fields::Unnamed(_) => true,
                _ => false,
            },
            _ => false,
        };

        Self {
            attrs,
            ident,
            ident_with_lifetime,
            variants,
            struct_fields,
            is_tuple_struct,
        }
    }

    pub fn atoms_module_name(&self, span: Span) -> Ident {
        Ident::new(&format!("RUSTLER_ATOMS_{}", self.ident), span)
    }

    pub fn encode(&self) -> bool {
        self.attrs.iter().any(|attr| match attr {
            RustlerAttr::Encode => true,
            _ => false,
        })
    }

    pub fn decode(&self) -> bool {
        self.attrs.iter().any(|attr| match attr {
            RustlerAttr::Decode => true,
            _ => false,
        })
    }

    pub fn field_atoms(&self) -> Option<Vec<TokenStream>> {
        self.struct_fields.as_ref().map(|struct_fields| {
            struct_fields
                .iter()
                .map(|field| {
                    let atom_fun = Self::field_to_atom_fun(field);

                    let ident = field.ident.as_ref().unwrap();
                    let ident_str = ident.to_string();
                    let ident_str = Self::remove_raw(&ident_str);

                    quote! {
                        #atom_fun = #ident_str,
                    }
                })
                .collect()
        })
    }

    pub fn field_to_atom_fun(field: &Field) -> Ident {
        let ident = field.ident.as_ref().unwrap();
        let ident_str = ident.to_string();
        let ident_str = Self::remove_raw(&ident_str);

        Ident::new(&format!("atom_{}", ident_str), Span::call_site())
    }

    pub fn escape_ident_with_index(ident_str: &str, index: usize, infix: &str) -> Ident {
        Ident::new(
            &format!(
                "RUSTLER_{}_field_{}_{}",
                infix,
                index,
                Self::remove_raw(ident_str)
            ),
            Span::call_site(),
        )
    }

    pub fn escape_ident(ident_str: &str, infix: &str) -> Ident {
        Ident::new(
            &format!("RUSTLER_{}_field_{}", infix, Self::remove_raw(ident_str)),
            Span::call_site(),
        )
    }

    fn remove_raw(ident_str: &str) -> &str {
        ident_str
            .split("r#")
            .last()
            .expect("split has always at least one element")
    }

    fn encode_decode_attr_set(attrs: &[RustlerAttr]) -> bool {
        attrs.iter().any(|attr| match attr {
            RustlerAttr::Encode => true,
            RustlerAttr::Decode => true,
            _ => false,
        })
    }

    fn get_rustler_attrs(attr: &syn::Attribute) -> Vec<RustlerAttr> {
        attr.path
            .segments
            .iter()
            .filter_map(|segment| {
                let meta = attr.parse_meta().expect("can parse meta");
                match segment.ident.to_string().as_ref() {
                    "rustler" => Some(Context::parse_rustler(&meta)),
                    "tag" => Context::try_parse_tag(&meta),
                    "module" => Context::try_parse_module(&meta),
                    _ => None,
                }
            })
            .flatten()
            .collect()
    }

    fn parse_rustler(meta: &Meta) -> Vec<RustlerAttr> {
        if let Meta::List(ref list) = meta {
            return list
                .nested
                .iter()
                .map(Context::parse_nested_rustler)
                .collect();
        }

        panic!("Expected encode and/or decode in rustler attribute");
    }

    fn parse_nested_rustler(nested: &NestedMeta) -> RustlerAttr {
        if let NestedMeta::Meta(Meta::Path(ref path)) = nested {
            match path.segments[0].ident.to_string().as_ref() {
                "encode" => return RustlerAttr::Encode,
                "decode" => return RustlerAttr::Decode,
                other => panic!("Unexpected literal {}", other),
            }
        }

        panic!("Expected encode and/or decode in rustler attribute");
    }

    fn try_parse_tag(meta: &Meta) -> Option<Vec<RustlerAttr>> {
        if let Meta::NameValue(ref name_value) = meta {
            if let Lit::Str(ref tag) = name_value.lit {
                return Some(vec![RustlerAttr::Tag(tag.value())]);
            }
        }
        panic!("Cannot parse module")
    }

    fn try_parse_module(meta: &Meta) -> Option<Vec<RustlerAttr>> {
        if let Meta::NameValue(name_value) = meta {
            if let Lit::Str(ref module) = name_value.lit {
                let ident = format!("Elixir.{}", module.value());
                return Some(vec![RustlerAttr::Module(ident)]);
            }
        }
        panic!("Cannot parse tag")
    }
}
<commit_msg>clippy: Prefer flat_map() over map().flatten()<commit_after>// TODO When we settle for a minimum version of Rust >= 1.42, remove this.
#![allow(clippy::match_like_matches_macro)]

use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{Data, Field, Fields, Ident, Lit, Meta, NestedMeta, Variant};

use super::RustlerAttr;

///
/// A parsing context struct.
///
/// `Context` holds information usable for different codegen modules.
///
pub(crate) struct Context<'a> {
    pub attrs: Vec<RustlerAttr>,
    pub ident: &'a proc_macro2::Ident,
    pub ident_with_lifetime: proc_macro2::TokenStream,
    pub variants: Option<Vec<&'a Variant>>,
    pub struct_fields: Option<Vec<&'a Field>>,
    pub is_tuple_struct: bool,
}

impl<'a> Context<'a> {
    pub fn from_ast(ast: &'a syn::DeriveInput) -> Self {
        let mut attrs: Vec<_> = ast
            .attrs
            .iter()
            .flat_map(Context::get_rustler_attrs)
            .collect();

        //
        // Default: generate encoder and decoder
        //
        if !Context::encode_decode_attr_set(&attrs) {
            attrs.push(RustlerAttr::Encode);
            attrs.push(RustlerAttr::Decode);
        }

        let has_lifetime = match ast.generics.lifetimes().count() {
            0 => false,
            1 => true,
            _ => panic!("Struct can only have one lifetime argument"),
        };

        let ident = &ast.ident;
        let ident_with_lifetime = if has_lifetime {
            quote! { #ident <'a> }
        } else {
            quote! { #ident }
        };

        let variants = match ast.data {
            Data::Enum(ref data_enum) => Some(data_enum.variants.iter().collect()),
            _ => None,
        };

        let struct_fields = match ast.data {
            Data::Struct(ref data_struct) => Some(data_struct.fields.iter().collect()),
            _ => None,
        };

        let is_tuple_struct = match ast.data {
            Data::Struct(ref data_struct) => match data_struct.fields {
                Fields::Unnamed(_) => true,
                _ => false,
            },
            _ => false,
        };

        Self {
            attrs,
            ident,
            ident_with_lifetime,
            variants,
            struct_fields,
            is_tuple_struct,
        }
    }

    pub fn atoms_module_name(&self, span: Span) -> Ident {
        Ident::new(&format!("RUSTLER_ATOMS_{}", self.ident), span)
    }

    pub fn encode(&self) -> bool {
        self.attrs.iter().any(|attr| match attr {
            RustlerAttr::Encode => true,
            _ => false,
        })
    }

    pub fn decode(&self) -> bool {
        self.attrs.iter().any(|attr| match attr {
            RustlerAttr::Decode => true,
            _ => false,
        })
    }

    pub fn field_atoms(&self) -> Option<Vec<TokenStream>> {
        self.struct_fields.as_ref().map(|struct_fields| {
            struct_fields
                .iter()
                .map(|field| {
                    let atom_fun = Self::field_to_atom_fun(field);

                    let ident = field.ident.as_ref().unwrap();
                    let ident_str = ident.to_string();
                    let ident_str = Self::remove_raw(&ident_str);

                    quote! {
                        #atom_fun = #ident_str,
                    }
                })
                .collect()
        })
    }

    pub fn field_to_atom_fun(field: &Field) -> Ident {
        let ident = field.ident.as_ref().unwrap();
        let ident_str = ident.to_string();
        let ident_str = Self::remove_raw(&ident_str);

        Ident::new(&format!("atom_{}", ident_str), Span::call_site())
    }

    pub fn escape_ident_with_index(ident_str: &str, index: usize, infix: &str) -> Ident {
        Ident::new(
            &format!(
                "RUSTLER_{}_field_{}_{}",
                infix,
                index,
                Self::remove_raw(ident_str)
            ),
            Span::call_site(),
        )
    }

    pub fn escape_ident(ident_str: &str, infix: &str) -> Ident {
        Ident::new(
            &format!("RUSTLER_{}_field_{}", infix, Self::remove_raw(ident_str)),
            Span::call_site(),
        )
    }

    fn remove_raw(ident_str: &str) -> &str {
        ident_str
            .split("r#")
            .last()
            .expect("split has always at least one element")
    }

    fn encode_decode_attr_set(attrs: &[RustlerAttr]) -> bool {
        attrs.iter().any(|attr| match attr {
            RustlerAttr::Encode => true,
            RustlerAttr::Decode => true,
            _ => false,
        })
    }

    fn get_rustler_attrs(attr: &syn::Attribute) -> Vec<RustlerAttr> {
        attr.path
            .segments
            .iter()
            .filter_map(|segment| {
                let meta = attr.parse_meta().expect("can parse meta");
                match segment.ident.to_string().as_ref() {
                    "rustler" => Some(Context::parse_rustler(&meta)),
                    "tag" => Context::try_parse_tag(&meta),
                    "module" => Context::try_parse_module(&meta),
                    _ => None,
                }
            })
            .flatten()
            .collect()
    }

    fn parse_rustler(meta: &Meta) -> Vec<RustlerAttr> {
        if let Meta::List(ref list) = meta {
            return list
                .nested
                .iter()
                .map(Context::parse_nested_rustler)
                .collect();
        }

        panic!("Expected encode and/or decode in rustler attribute");
    }

    fn parse_nested_rustler(nested: &NestedMeta) -> RustlerAttr {
        if let NestedMeta::Meta(Meta::Path(ref path)) = nested {
            match path.segments[0].ident.to_string().as_ref() {
                "encode" => return RustlerAttr::Encode,
                "decode" => return RustlerAttr::Decode,
                other => panic!("Unexpected literal {}", other),
            }
        }

        panic!("Expected encode and/or decode in rustler attribute");
    }

    fn try_parse_tag(meta: &Meta) -> Option<Vec<RustlerAttr>> {
        if let Meta::NameValue(ref name_value) = meta {
            if let Lit::Str(ref tag) = name_value.lit {
                return Some(vec![RustlerAttr::Tag(tag.value())]);
            }
        }
        panic!("Cannot parse module")
    }

    fn try_parse_module(meta: &Meta) -> Option<Vec<RustlerAttr>> {
        if let Meta::NameValue(name_value) = meta {
            if let Lit::Str(ref module) = name_value.lit {
                let ident = format!("Elixir.{}", module.value());
                return Some(vec![RustlerAttr::Module(ident)]);
            }
        }
        panic!("Cannot parse tag")
    }
}
<|endoftext|>


<commit_before>use inflector::Inflector;
use schemars::JsonSchema;
use serde::{Serialize, Deserialize};
use std::path::Path;
use strum::{EnumIter, IntoEnumIterator};

#[derive(Debug, Clone, PartialEq, EnumIter, Serialize, Deserialize, JsonSchema)]
pub enum Format {
    Bash,
    Calc,
    Date,
    Directory,
    Dockerfile,
    Docx,
    Flac,
    Gif,
    Html,
    Ipynb,
    JavaScript,
    Jpeg,
    Json,
    Json5,
    Julia,
    LaTeX,
    Makefile,
    Markdown,
    Mp3,
    Mp4,
    Odt,
    Ogg,
    Ogv,
    Pandoc,
    Pdf,
    Person,
    PlainText,
    Png,
    Python,
    R,
    RMarkdown,
    Rpng,
    Rust,
    Shell,
    ThreeGpp,
    Toml,
    TypeScript,
    Unknown,
    WebM,
    Xml,
    Yaml,
    Zsh,
}

impl Format {
    /// A list of known formats
    #[rustfmt::skip]
    pub fn spec(&self) -> FormatSpec {
        match self {

            // Data serialization formats. These may be used to store documents so `preview: true`.
            Format::Json => FormatSpec::new("JSON", "json", &[], false, true, FormatNodeType::Unknown),
            Format::Json5 => FormatSpec::new("JSON5", "json5", &[], false, true, FormatNodeType::Unknown),

            Format::Toml => FormatSpec::new("TOML", "toml", &[], false, true, FormatNodeType::Unknown),
            Format::Xml => FormatSpec::new("XML", "xml", &[], false, true, FormatNodeType::Unknown),
            Format::Yaml => FormatSpec::new("YAML", "yaml", &[], false, true, FormatNodeType::Unknown),

            // Code formats
            Format::Bash => FormatSpec::new("Bash", "bash", &[], false, false, FormatNodeType::SoftwareSourceCode),
            Format::Calc => FormatSpec::new("Calc", "calc", &[], false, false, FormatNodeType::SoftwareSourceCode),
            Format::Dockerfile => FormatSpec::new("Dockerfile", "dockerfile", &[], false, false, FormatNodeType::SoftwareSourceCode),
            Format::JavaScript => FormatSpec::new("JavaScript", "js", &[], false, false, FormatNodeType::SoftwareSourceCode),
            Format::Julia => FormatSpec::new("Julia", "jl", &[], false, false, FormatNodeType::SoftwareSourceCode),
            Format::Makefile => FormatSpec::new("Makefile", "makefile", &[], false, false, FormatNodeType::SoftwareSourceCode),
            Format::Python => FormatSpec::new("Python", "py", &["python3"], false, false, FormatNodeType::SoftwareSourceCode),
            Format::R => FormatSpec::new("R", "r", &[], false, false, FormatNodeType::SoftwareSourceCode),
            Format::Rust => FormatSpec::new("Rust", "rust", &[], false, false, FormatNodeType::SoftwareSourceCode),
            Format::Shell => FormatSpec::new("Shell", "sh", &[], false, false, FormatNodeType::SoftwareSourceCode),
            Format::TypeScript => FormatSpec::new("TypeScript", "ts", &[], false, false, FormatNodeType::SoftwareSourceCode),
            Format::Zsh => FormatSpec::new("ZSH", "zsh", &[], false, false, FormatNodeType::SoftwareSourceCode),

            // Article formats
            Format::Docx => FormatSpec::new("Microsoft Word", "docx", &[], true, true, FormatNodeType::Article),
            Format::Html => FormatSpec::new("HTML", "html", &[], false, true, FormatNodeType::Article),
            Format::Ipynb => FormatSpec::new("Jupyter Notebook", "ipynb", &[], false, true, FormatNodeType::Article),
            Format::Markdown => FormatSpec::new("Markdown", "md", &[], false, true, FormatNodeType::Article),
            Format::Odt => FormatSpec::new("Open Office Text", "odt", &[], true, true, FormatNodeType::Article),
            Format::Pandoc => FormatSpec::new("Pandoc JSON", "pandoc", &[], false, true, FormatNodeType::Article),
            Format::Pdf => FormatSpec::new("Portable Document Format", "pdf", &[], true, false, FormatNodeType::Article),
            Format::RMarkdown => FormatSpec::new("R Markdown", "rmd", &[], false, true, FormatNodeType::Article),
            Format::LaTeX => FormatSpec::new("LaTeX", "latex", &["tex"], false, true, FormatNodeType::Article),

            // Audio formats
            Format::Flac => FormatSpec::new("FLAC", "flac", &[], true, true, FormatNodeType::AudioObject),
            Format::Mp3 => FormatSpec::new("MP3", "mp3", &[], true, true, FormatNodeType::AudioObject),
            Format::Ogg => FormatSpec::new("Ogg", "ogg", &[], true, true, FormatNodeType::AudioObject),

            // Image formats
            Format::Gif => FormatSpec::new("GIF", "gif", &[], true, true, FormatNodeType::ImageObject),
            Format::Jpeg => FormatSpec::new("JPEG", "jpg", &["jpeg"], true, true, FormatNodeType::ImageObject),
            Format::Png => FormatSpec::new("PNG", "png", &[], true, true, FormatNodeType::ImageObject),
            Format::Rpng => FormatSpec::new("RPNG", "rpng", &[], true, true, FormatNodeType::ImageObject),

            // Video formats
            Format::ThreeGpp => FormatSpec::new("3GPP", "3gp", &[], true, true, FormatNodeType::VideoObject),
            Format::Mp4 => FormatSpec::new("MP4", "mp4", &[], true, true, FormatNodeType::VideoObject),
            Format::Ogv => FormatSpec::new("Ogg Video", "ogv", &[], true, true, FormatNodeType::VideoObject),
            Format::WebM => FormatSpec::new("WebM", "webm", &[], true, true, FormatNodeType::VideoObject),

            // Other
            Format::PlainText => FormatSpec::new("Plain text", "txt", &[], false, false, FormatNodeType::Unknown),
            Format::Person => FormatSpec::new("Person", "person", &[], false, false, FormatNodeType::Person),
            Format::Date => FormatSpec::new("Date", "date", &[], false, false, FormatNodeType::Date),

            // Specials
            Format::Directory => FormatSpec::directory(),
            Format::Unknown => FormatSpec::unknown("unknown")
        }
    }
}

/// The type of format as a schema `Node` type
#[derive(Clone, Debug, PartialEq, JsonSchema, Serialize)]
pub enum FormatNodeType {
    Article,
    AudioObject,
    ImageObject,
    VideoObject,
    SoftwareSourceCode,
    Date,
    Person,
    Unknown,
}

/// Specification of a format
///
/// Used to determine various application behaviors
/// e.g. not reading binary formats into memory unnecessarily
#[derive(Clone, Debug, PartialEq, JsonSchema, Serialize)]
#[schemars(deny_unknown_fields)]
pub struct FormatSpec {
    /// The title of the format e.g. "Markdown"
    pub title: String,

    /// The extension to use e.g. "md" when saving a file in this format
    ///
    /// Note: this is the "default" extension but other extensions can be
    /// listed in `aliases`.
    pub extension: String,

    /// Any additional names or extensions that this format
    /// should match against (should be lowercase).
    pub aliases: Vec<String>,

    /// Whether or not the format should be considered binary
    /// e.g. not to be displayed in a text / code editor
    pub binary: bool,

    /// Whether HTML previews are normally supported for documents of
    /// this format. See also `Document.previewable` which indicates whether
    /// a HTML preview is supported for a particular document.
    pub preview: bool,

    /// The kind of format
    pub node_type: FormatNodeType,

    /// Whether or not this is a known format specification (i.e. not automatically created)
    pub known: bool,
}

impl FormatSpec {
    /// Create a new format spec
    pub fn new(
        title: &str,
        extension: &str,
        aliases: &[&str],
        binary: bool,
        preview: bool,
        node_type: FormatNodeType,
    ) -> FormatSpec {
        FormatSpec {
            title: title.into(),
            extension: extension.into(),
            aliases: aliases.iter().map(|s| s.to_string()).collect(),
            binary,
            preview,
            node_type,
            known: true,
        }
    }

    /// Create the special `directory` format used on `File` objects
    /// that are directories
    pub fn directory() -> FormatSpec {
        FormatSpec {
            title: "Directory".to_string(),
            extension: "".into(),
            aliases: Vec::new(),
            binary: true,
            preview: false,
            node_type: FormatNodeType::Unknown,
            known: true,
        }
    }

    /// Create the special `unknown` file format where all we
    /// have is the name e.g. from a file extension.
    pub fn unknown(name: &str) -> FormatSpec {
        FormatSpec {
            title: name.to_string(),
            extension: name.to_lowercase(),
            aliases: Vec::new(),
            // Set binary to false so that any unregistered format
            // will be at least shown in editor...
            binary: false,
            // ..but not have a preview
            preview: false,
            node_type: FormatNodeType::Unknown,
            known: false,
        }
    }
}

/// Match a format name to a `Format`
///
/// Iterates over the `Format` variants and returns the first
/// that has a title, extension or aliases that match it.
pub fn match_name(name: &str) -> Format {
    let name = name.trim();

    // Early return for empty strings to avoid matching `Directory`
    // which has an empty extension.
    if name.is_empty() {
        return Format::Unknown;
    }

    let name = name.to_lowercase();
    for format in Format::iter() {
        let spec = format.spec();
        if name == spec.title.to_lowercase()
            || name == spec.extension
            || spec.aliases.contains(&name)
        {
            return format;
        }
    }

    Format::Unknown
}

/// Match a file path to a `Format`
///
/// Extracts a "name" (extension or file base name) from a
/// file path and then calls `match_name` on that.
pub fn match_path<P: AsRef<Path>>(path: &P) -> Format {
    let path = path.as_ref();

    // Get name from file extension, or filename if no extension
    let name = match path.extension() {
        Some(ext) => ext,
        None => match path.file_name() {
            Some(name) => name,
            // Fallback to the provided "path"
            None => path.as_os_str(),
        },
    };

    // Match that name
    match_name(&name.to_string_lossy().to_string())
}

/// Normalize a format name to its title
///
/// If the name can be matched against a known format then its
/// title will be returned. Otherwise, the name converted to title
/// case will be returned.
pub fn normalize_title(name: &str) -> String {
    let format = match_name(name);
    match format {
        Format::Unknown => name.to_title_case(),
        _ => format.spec().title,
    }
}
<commit_msg>fix(Formats): Use `lowercase` serialization consistent with other enums<commit_after>use inflector::Inflector;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::path::Path;
use strum::{EnumIter, EnumString, IntoEnumIterator};

#[derive(Debug, Clone, PartialEq, EnumIter, Serialize, Deserialize, JsonSchema, EnumString)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum Format {
    Bash,
    Calc,
    Date,
    Directory,
    Dockerfile,
    Docx,
    Flac,
    Gif,
    Html,
    Ipynb,
    JavaScript,
    Jpeg,
    Json,
    Json5,
    Julia,
    LaTeX,
    Makefile,
    Markdown,
    Mp3,
    Mp4,
    Odt,
    Ogg,
    Ogv,
    Pandoc,
    Pdf,
    Person,
    PlainText,
    Png,
    Python,
    R,
    RMarkdown,
    Rpng,
    Rust,
    Shell,
    ThreeGpp,
    Toml,
    TypeScript,
    Unknown,
    WebM,
    Xml,
    Yaml,
    Zsh,
}

impl Format {
    /// A list of known formats
    #[rustfmt::skip]
    pub fn spec(&self) -> FormatSpec {
        match self {

            // Data serialization formats. These may be used to store documents so `preview: true`.
            Format::Json => FormatSpec::new("JSON", "json", &[], false, true, FormatNodeType::Unknown),
            Format::Json5 => FormatSpec::new("JSON5", "json5", &[], false, true, FormatNodeType::Unknown),

            Format::Toml => FormatSpec::new("TOML", "toml", &[], false, true, FormatNodeType::Unknown),
            Format::Xml => FormatSpec::new("XML", "xml", &[], false, true, FormatNodeType::Unknown),
            Format::Yaml => FormatSpec::new("YAML", "yaml", &[], false, true, FormatNodeType::Unknown),

            // Code formats
            Format::Bash => FormatSpec::new("Bash", "bash", &[], false, false, FormatNodeType::SoftwareSourceCode),
            Format::Calc => FormatSpec::new("Calc", "calc", &[], false, false, FormatNodeType::SoftwareSourceCode),
            Format::Dockerfile => FormatSpec::new("Dockerfile", "dockerfile", &[], false, false, FormatNodeType::SoftwareSourceCode),
            Format::JavaScript => FormatSpec::new("JavaScript", "js", &[], false, false, FormatNodeType::SoftwareSourceCode),
            Format::Julia => FormatSpec::new("Julia", "jl", &[], false, false, FormatNodeType::SoftwareSourceCode),
            Format::Makefile => FormatSpec::new("Makefile", "makefile", &[], false, false, FormatNodeType::SoftwareSourceCode),
            Format::Python => FormatSpec::new("Python", "py", &["python3"], false, false, FormatNodeType::SoftwareSourceCode),
            Format::R => FormatSpec::new("R", "r", &[], false, false, FormatNodeType::SoftwareSourceCode),
            Format::Rust => FormatSpec::new("Rust", "rust", &[], false, false, FormatNodeType::SoftwareSourceCode),
            Format::Shell => FormatSpec::new("Shell", "sh", &[], false, false, FormatNodeType::SoftwareSourceCode),
            Format::TypeScript => FormatSpec::new("TypeScript", "ts", &[], false, false, FormatNodeType::SoftwareSourceCode),
            Format::Zsh => FormatSpec::new("ZSH", "zsh", &[], false, false, FormatNodeType::SoftwareSourceCode),

            // Article formats
            Format::Docx => FormatSpec::new("Microsoft Word", "docx", &[], true, true, FormatNodeType::Article),
            Format::Html => FormatSpec::new("HTML", "html", &[], false, true, FormatNodeType::Article),
            Format::Ipynb => FormatSpec::new("Jupyter Notebook", "ipynb", &[], false, true, FormatNodeType::Article),
            Format::Markdown => FormatSpec::new("Markdown", "md", &[], false, true, FormatNodeType::Article),
            Format::Odt => FormatSpec::new("Open Office Text", "odt", &[], true, true, FormatNodeType::Article),
            Format::Pandoc => FormatSpec::new("Pandoc JSON", "pandoc", &[], false, true, FormatNodeType::Article),
            Format::Pdf => FormatSpec::new("Portable Document Format", "pdf", &[], true, false, FormatNodeType::Article),
            Format::RMarkdown => FormatSpec::new("R Markdown", "rmd", &[], false, true, FormatNodeType::Article),
            Format::LaTeX => FormatSpec::new("LaTeX", "latex", &["tex"], false, true, FormatNodeType::Article),

            // Audio formats
            Format::Flac => FormatSpec::new("FLAC", "flac", &[], true, true, FormatNodeType::AudioObject),
            Format::Mp3 => FormatSpec::new("MP3", "mp3", &[], true, true, FormatNodeType::AudioObject),
            Format::Ogg => FormatSpec::new("Ogg", "ogg", &[], true, true, FormatNodeType::AudioObject),

            // Image formats
            Format::Gif => FormatSpec::new("GIF", "gif", &[], true, true, FormatNodeType::ImageObject),
            Format::Jpeg => FormatSpec::new("JPEG", "jpg", &["jpeg"], true, true, FormatNodeType::ImageObject),
            Format::Png => FormatSpec::new("PNG", "png", &[], true, true, FormatNodeType::ImageObject),
            Format::Rpng => FormatSpec::new("RPNG", "rpng", &[], true, true, FormatNodeType::ImageObject),

            // Video formats
            Format::ThreeGpp => FormatSpec::new("3GPP", "3gp", &[], true, true, FormatNodeType::VideoObject),
            Format::Mp4 => FormatSpec::new("MP4", "mp4", &[], true, true, FormatNodeType::VideoObject),
            Format::Ogv => FormatSpec::new("Ogg Video", "ogv", &[], true, true, FormatNodeType::VideoObject),
            Format::WebM => FormatSpec::new("WebM", "webm", &[], true, true, FormatNodeType::VideoObject),

            // Other
            Format::PlainText => FormatSpec::new("Plain text", "txt", &[], false, false, FormatNodeType::Unknown),
            Format::Person => FormatSpec::new("Person", "person", &[], false, false, FormatNodeType::Person),
            Format::Date => FormatSpec::new("Date", "date", &[], false, false, FormatNodeType::Date),

            // Specials
            Format::Directory => FormatSpec::directory(),
            Format::Unknown => FormatSpec::unknown("unknown")
        }
    }
}

/// The type of format as a schema `Node` type
#[derive(Clone, Debug, PartialEq, JsonSchema, Serialize)]
pub enum FormatNodeType {
    Article,
    AudioObject,
    ImageObject,
    VideoObject,
    SoftwareSourceCode,
    Date,
    Person,
    Unknown,
}

/// Specification of a format
///
/// Used to determine various application behaviors
/// e.g. not reading binary formats into memory unnecessarily
#[derive(Clone, Debug, PartialEq, JsonSchema, Serialize)]
#[schemars(deny_unknown_fields)]
pub struct FormatSpec {
    /// The title of the format e.g. "Markdown"
    pub title: String,

    /// The extension to use e.g. "md" when saving a file in this format
    ///
    /// Note: this is the "default" extension but other extensions can be
    /// listed in `aliases`.
    pub extension: String,

    /// Any additional names or extensions that this format
    /// should match against (should be lowercase).
    pub aliases: Vec<String>,

    /// Whether or not the format should be considered binary
    /// e.g. not to be displayed in a text / code editor
    pub binary: bool,

    /// Whether HTML previews are normally supported for documents of
    /// this format. See also `Document.previewable` which indicates whether
    /// a HTML preview is supported for a particular document.
    pub preview: bool,

    /// The kind of format
    pub node_type: FormatNodeType,

    /// Whether or not this is a known format specification (i.e. not automatically created)
    pub known: bool,
}

impl FormatSpec {
    /// Create a new format spec
    pub fn new(
        title: &str,
        extension: &str,
        aliases: &[&str],
        binary: bool,
        preview: bool,
        node_type: FormatNodeType,
    ) -> FormatSpec {
        FormatSpec {
            title: title.into(),
            extension: extension.into(),
            aliases: aliases.iter().map(|s| s.to_string()).collect(),
            binary,
            preview,
            node_type,
            known: true,
        }
    }

    /// Create the special `directory` format used on `File` objects
    /// that are directories
    pub fn directory() -> FormatSpec {
        FormatSpec {
            title: "Directory".to_string(),
            extension: "".into(),
            aliases: Vec::new(),
            binary: true,
            preview: false,
            node_type: FormatNodeType::Unknown,
            known: true,
        }
    }

    /// Create the special `unknown` file format where all we
    /// have is the name e.g. from a file extension.
    pub fn unknown(name: &str) -> FormatSpec {
        FormatSpec {
            title: name.to_string(),
            extension: name.to_lowercase(),
            aliases: Vec::new(),
            // Set binary to false so that any unregistered format
            // will be at least shown in editor...
            binary: false,
            // ..but not have a preview
            preview: false,
            node_type: FormatNodeType::Unknown,
            known: false,
        }
    }
}

/// Match a format name to a `Format`
///
/// Iterates over the `Format` variants and returns the first
/// that has a title, extension or aliases that match it.
pub fn match_name(name: &str) -> Format {
    let name = name.trim();

    // Early return for empty strings to avoid matching `Directory`
    // which has an empty extension.
    if name.is_empty() {
        return Format::Unknown;
    }

    let name = name.to_lowercase();
    for format in Format::iter() {
        let spec = format.spec();
        if name == spec.title.to_lowercase()
            || name == spec.extension
            || spec.aliases.contains(&name)
        {
            return format;
        }
    }

    Format::Unknown
}

/// Match a file path to a `Format`
///
/// Extracts a "name" (extension or file base name) from a
/// file path and then calls `match_name` on that.
pub fn match_path<P: AsRef<Path>>(path: &P) -> Format {
    let path = path.as_ref();

    // Get name from file extension, or filename if no extension
    let name = match path.extension() {
        Some(ext) => ext,
        None => match path.file_name() {
            Some(name) => name,
            // Fallback to the provided "path"
            None => path.as_os_str(),
        },
    };

    // Match that name
    match_name(&name.to_string_lossy().to_string())
}

/// Normalize a format name to its title
///
/// If the name can be matched against a known format then its
/// title will be returned. Otherwise, the name converted to title
/// case will be returned.
pub fn normalize_title(name: &str) -> String {
    let format = match_name(name);
    match format {
        Format::Unknown => name.to_title_case(),
        _ => format.spec().title,
    }
}
<|endoftext|>


<commit_before>// Copyright 2016 Google Inc. All rights reserved.
//
// 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.

//! An engine for handling edits (possibly from async sources) and undo. This
//! module actually implements a mini Conflict-free Replicated Data Type, but
//! is considerably simpler than the usual CRDT implementation techniques,
//! because all operations are serialized in this central engine.

use std::borrow::Cow;
use std::collections::BTreeSet;
use std;

use rope::{Rope, RopeInfo};
use subset::Subset;
use delta::Delta;

pub struct Engine {
    rev_id_counter: usize,
    union_str: Rope,
    revs: Vec<Revision>,
}

struct Revision {
    rev_id: usize,
    deletes_from_union: Subset,
    union_str_len: usize,
    edit: Contents,
}

use self::Contents::*;

enum Contents {
    Edit {
        priority: usize,
        undo_group: usize,
        inserts: Subset,
        deletes: Subset,
    },
    Undo {
        groups: BTreeSet<usize>,  // set of undo_group id's
    }
}

impl Engine {
    pub fn new(initial_contents: Rope) -> Engine {
        let rev = Revision {
            rev_id: 0,
            deletes_from_union: Subset::default(),
            union_str_len: initial_contents.len(),
            edit: Undo { groups: BTreeSet::default() },
        };
        Engine {
            rev_id_counter: 1,
            union_str: initial_contents,
            revs: vec![rev],
        }
    }

    fn get_current_undo(&self) -> Option<&BTreeSet<usize>> {
        for rev in self.revs.iter().rev() {
            if let Undo { ref groups } = rev.edit {
                return Some(groups);
            }
        }
        None
    }

    fn find_rev(&self, rev_id: usize) -> Option<usize> {
        for (i, rev) in self.revs.iter().enumerate().rev() {
            if rev.rev_id == rev_id {
                return Some(i)
            }
        }
        None
    }

    fn get_rev_from_index(&self, rev_index: usize) -> Rope {
        let mut deletes_from_union = Cow::Borrowed(&self.revs[rev_index].deletes_from_union);
        for rev in &self.revs[rev_index + 1..] {
            if let Edit { ref inserts, .. } = rev.edit {
                if !inserts.is_empty() {
                    deletes_from_union = Cow::Owned(deletes_from_union.transform_union(inserts));
                }
            }
        }
        deletes_from_union.delete_in(&self.union_str)
    }

    fn get_subset_from_index(&self, rev_index: usize) -> Cow<Subset> {
        let mut deletes_from_union = Cow::Borrowed(&self.revs[rev_index].deletes_from_union);
        for rev in &self.revs[rev_index + 1..] {
            if let Edit { ref inserts, .. } = rev.edit {
                if !inserts.is_empty() {
                    deletes_from_union = Cow::Owned(deletes_from_union.transform_union(inserts));
                }
            }
        }
        deletes_from_union
    }

    /// Get revision id of head revision.
    pub fn get_head_rev_id(&self) -> usize {
        self.revs.last().unwrap().rev_id
    }

    /// Get text of head revision.
    pub fn get_head(&self) -> Rope {
        self.get_rev_from_index(self.revs.len() - 1)
    }

    /// Get text of a given revision, if it can be found.
    pub fn get_rev(&self, rev: usize) -> Option<Rope> {
        self.find_rev(rev).map(|rev_index| self.get_rev_from_index(rev_index))
    }

    /// A delta that, when applied to previous head, results in the current head. Panics
    /// if there is not at least one edit.
    pub fn delta_rev_head(&self, base_rev: usize) -> Delta<RopeInfo> {
        let ix = self.find_rev(base_rev).expect("base revision not found");
        let rev = &self.revs[ix];
        let mut prev_from_union = Cow::Borrowed(&rev.deletes_from_union);
        for r in &self.revs[ix + 1..] {
            if let Edit { ref inserts, .. } = r.edit {
                if !inserts.is_empty() {
                    prev_from_union = Cow::Owned(prev_from_union.transform_union(inserts));
                }
            }
        }
        let head_rev = &self.revs.last().unwrap();
        Delta::synthesize(&self.union_str, &prev_from_union, &head_rev.deletes_from_union)
    }

    fn mk_new_rev(&self, new_priority: usize, undo_group: usize,
            base_rev: usize, delta: Delta<RopeInfo>) -> (Revision, Rope) {
        let ix = self.find_rev(base_rev).expect("base revision not found");
        let rev = &self.revs[ix];
        let (ins_delta, deletes) = delta.factor();
        let mut union_ins_delta = ins_delta.transform_expand(&rev.deletes_from_union, rev.union_str_len, true);
        let mut new_deletes = deletes.transform_expand(&rev.deletes_from_union);
        for r in &self.revs[ix + 1..] {
            if let Edit { priority, ref inserts, .. } = r.edit {
                if !inserts.is_empty() {
                    let after = new_priority >= priority;  // should never be ==
                    union_ins_delta = union_ins_delta.transform_expand(inserts, r.union_str_len, after);
                    new_deletes = new_deletes.transform_expand(inserts);
                }
            }
        }
        let new_inserts = union_ins_delta.inserted_subset();
        if !new_inserts.is_empty() {
            new_deletes = new_deletes.transform_expand(&new_inserts);
        }
        let new_union_str = union_ins_delta.apply(&self.union_str);
        let undone = self.get_current_undo().map_or(false, |undos| undos.contains(&undo_group));
        let mut new_from_union = Cow::Borrowed(&self.revs.last().unwrap().deletes_from_union);
        if undone {
            if !new_inserts.is_empty() {
                new_from_union = Cow::Owned(new_from_union.transform_union(&new_inserts));
            }
        } else {
            if !new_inserts.is_empty() {
                new_from_union = Cow::Owned(new_from_union.transform_expand(&new_inserts));
            }
            if !new_deletes.is_empty() {
                new_from_union = Cow::Owned(new_from_union.union(&new_deletes));
            }
        }
        (Revision {
            rev_id: self.rev_id_counter,
            deletes_from_union: new_from_union.into_owned(),
            union_str_len: new_union_str.len(),
            edit: Edit {
                priority: new_priority,
                undo_group: undo_group,
                inserts: new_inserts,
                deletes: new_deletes,
            }
        }, new_union_str)
    }

    pub fn edit_rev(&mut self, priority: usize, undo_group: usize,
            base_rev: usize, delta: Delta<RopeInfo>) {
        let (new_rev, new_union_str) = self.mk_new_rev(priority, undo_group, base_rev, delta);
        self.rev_id_counter += 1;
        self.revs.push(new_rev);
        self.union_str = new_union_str;
    }

    // This computes undo all the way from the beginning. An optimization would be to not
    // recompute the prefix up to where the history diverges, but it's not clear that's
    // even worth the code complexity.
    fn compute_undo(&self, groups: BTreeSet<usize>) -> Revision {
        let mut deletes_from_union = Subset::default();
        for rev in &self.revs {
            if let Edit { ref undo_group, ref inserts, ref deletes, .. } = rev.edit {
                if groups.contains(undo_group) {
                    if !inserts.is_empty() {
                        deletes_from_union = deletes_from_union.transform_union(inserts);
                    }
                } else {
                    if !inserts.is_empty() {
                        deletes_from_union = deletes_from_union.transform_expand(inserts);
                    }
                    if !deletes.is_empty() {
                        deletes_from_union = deletes_from_union.union(deletes);
                    }
                }
            }
        }
        Revision {
            rev_id: self.rev_id_counter,
            deletes_from_union: deletes_from_union,
            union_str_len: self.union_str.len(),
            edit: Undo {
                groups: groups
            }
        }
    }

    pub fn undo(&mut self, groups: BTreeSet<usize>) {
        let new_rev = self.compute_undo(groups);
        self.revs.push(new_rev);
        self.rev_id_counter += 1;
    }

    pub fn is_equivalent_revision(&self, base_rev: usize, other_rev: usize) -> bool {
        let base_subset = self.find_rev(base_rev).map(|rev_index| self.get_subset_from_index(rev_index));
        let other_subset = self.find_rev(other_rev).map(|rev_index| self.get_subset_from_index(rev_index));

        base_subset.is_some() && base_subset == other_subset
    }

    // Note: this function would need some work to handle retaining arbitrary revisions,
    // partly because the reachability calculation would become more complicated (a
    // revision might hold content from an undo group that would otherwise be gc'ed),
    // and partly because you need to retain more undo history, to supply input to the
    // reachability calculation.
    //
    // Thus, it's easiest to defer gc to when all plugins quiesce, but it's certainly
    // possible to fix it so that's not necessary.
    pub fn gc(&mut self, gc_groups: &BTreeSet<usize>) {
        let mut gc_dels = Subset::default();
        // TODO: want to let caller retain more rev_id's.
        let mut retain_revs = BTreeSet::new();
        if let Some(last) = self.revs.last() {
            retain_revs.insert(last.rev_id);
        }
        {
            let cur_undo = self.get_current_undo();
            for rev in &self.revs {
                if let Edit { ref undo_group, ref inserts, ref deletes, .. } = rev.edit {
                    if !retain_revs.contains(&rev.rev_id) && gc_groups.contains(undo_group) {
                        if cur_undo.map_or(false, |undos| undos.contains(undo_group)) {
                            if !inserts.is_empty() {
                                gc_dels = gc_dels.transform_union(inserts);
                            }
                        } else {
                            if !inserts.is_empty() {
                                gc_dels = gc_dels.transform_expand(inserts);
                            }
                            if !deletes.is_empty() {
                                gc_dels = gc_dels.union(deletes);
                            }
                        }
                    } else if !inserts.is_empty() {
                        gc_dels = gc_dels.transform_expand(inserts);
                    }
                }
            }
        }
        if !gc_dels.is_empty() {
            self.union_str = gc_dels.delete_in(&self.union_str);
        }
        let old_revs = std::mem::replace(&mut self.revs, Vec::new());
        for rev in old_revs.into_iter().rev() {
            match rev.edit {
                Edit { priority, undo_group, inserts, deletes } => {
                    let new_gc_dels = if inserts.is_empty() {
                        None
                    } else {
                        Some(inserts.transform_shrink(&gc_dels))
                    };
                    if retain_revs.contains(&rev.rev_id) || !gc_groups.contains(&undo_group) {
                        let (inserts, deletes, deletes_from_union, len) = if gc_dels.is_empty() {
                            (inserts, deletes, rev.deletes_from_union, rev.union_str_len)
                        } else {
                            (gc_dels.transform_shrink(&inserts),
                                gc_dels.transform_shrink(&deletes),
                                gc_dels.transform_shrink(&rev.deletes_from_union),
                                gc_dels.len_after_delete(rev.union_str_len))
                        };
                        self.revs.push(Revision {
                            rev_id: rev.rev_id,
                            deletes_from_union: deletes_from_union,
                            union_str_len: len,
                            edit: Edit {
                                priority: priority,
                                undo_group: undo_group,
                                inserts: inserts,
                                deletes: deletes,
                            }
                        });
                    }
                    if let Some(new_gc_dels) = new_gc_dels {
                        gc_dels = new_gc_dels;
                    }
                }
                Undo { groups } => {
                    // We're super-aggressive about dropping these; after gc, the history
                    // of which undos were used to compute deletes_from_union in edits may be lost.
                    if retain_revs.contains(&rev.rev_id) {
                        let (deletes_from_union, len) = if gc_dels.is_empty() {
                            (rev.deletes_from_union, rev.union_str_len)
                        } else {
                            (gc_dels.transform_shrink(&rev.deletes_from_union),
                                gc_dels.len_after_delete(rev.union_str_len))
                        };
                        self.revs.push(Revision {
                            rev_id: rev.rev_id,
                            deletes_from_union: deletes_from_union,
                            union_str_len: len,
                            edit: Undo {
                                groups: &groups - gc_groups,
                            }
                        })
                    }
                }
            }
        }
        self.revs.reverse();
    }
}
<commit_msg>Remove duplicatation in Engine method<commit_after>// Copyright 2016 Google Inc. All rights reserved.
//
// 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.

//! An engine for handling edits (possibly from async sources) and undo. This
//! module actually implements a mini Conflict-free Replicated Data Type, but
//! is considerably simpler than the usual CRDT implementation techniques,
//! because all operations are serialized in this central engine.

use std::borrow::Cow;
use std::collections::BTreeSet;
use std;

use rope::{Rope, RopeInfo};
use subset::Subset;
use delta::Delta;

pub struct Engine {
    rev_id_counter: usize,
    union_str: Rope,
    revs: Vec<Revision>,
}

struct Revision {
    rev_id: usize,
    deletes_from_union: Subset,
    union_str_len: usize,
    edit: Contents,
}

use self::Contents::*;

enum Contents {
    Edit {
        priority: usize,
        undo_group: usize,
        inserts: Subset,
        deletes: Subset,
    },
    Undo {
        groups: BTreeSet<usize>,  // set of undo_group id's
    }
}

impl Engine {
    pub fn new(initial_contents: Rope) -> Engine {
        let rev = Revision {
            rev_id: 0,
            deletes_from_union: Subset::default(),
            union_str_len: initial_contents.len(),
            edit: Undo { groups: BTreeSet::default() },
        };
        Engine {
            rev_id_counter: 1,
            union_str: initial_contents,
            revs: vec![rev],
        }
    }

    fn get_current_undo(&self) -> Option<&BTreeSet<usize>> {
        for rev in self.revs.iter().rev() {
            if let Undo { ref groups } = rev.edit {
                return Some(groups);
            }
        }
        None
    }

    fn find_rev(&self, rev_id: usize) -> Option<usize> {
        for (i, rev) in self.revs.iter().enumerate().rev() {
            if rev.rev_id == rev_id {
                return Some(i)
            }
        }
        None
    }

    /// Get the contents of the document at a given revision number
    fn get_rev_from_index(&self, rev_index: usize) -> Rope {
        self.get_subset_from_index(rev_index).delete_in(&self.union_str)
    }

    /// Get the Subset to delete from the current union string in order to obtain a revision's content
    fn get_subset_from_index(&self, rev_index: usize) -> Cow<Subset> {
        let mut deletes_from_union = Cow::Borrowed(&self.revs[rev_index].deletes_from_union);
        for rev in &self.revs[rev_index + 1..] {
            if let Edit { ref inserts, .. } = rev.edit {
                if !inserts.is_empty() {
                    deletes_from_union = Cow::Owned(deletes_from_union.transform_union(inserts));
                }
            }
        }
        deletes_from_union
    }

    /// Get revision id of head revision.
    pub fn get_head_rev_id(&self) -> usize {
        self.revs.last().unwrap().rev_id
    }

    /// Get text of head revision.
    pub fn get_head(&self) -> Rope {
        self.get_rev_from_index(self.revs.len() - 1)
    }

    /// Get text of a given revision, if it can be found.
    pub fn get_rev(&self, rev: usize) -> Option<Rope> {
        self.find_rev(rev).map(|rev_index| self.get_rev_from_index(rev_index))
    }

    /// A delta that, when applied to previous head, results in the current head. Panics
    /// if there is not at least one edit.
    pub fn delta_rev_head(&self, base_rev: usize) -> Delta<RopeInfo> {
        let ix = self.find_rev(base_rev).expect("base revision not found");
        let rev = &self.revs[ix];
        let mut prev_from_union = Cow::Borrowed(&rev.deletes_from_union);
        for r in &self.revs[ix + 1..] {
            if let Edit { ref inserts, .. } = r.edit {
                if !inserts.is_empty() {
                    prev_from_union = Cow::Owned(prev_from_union.transform_union(inserts));
                }
            }
        }
        let head_rev = &self.revs.last().unwrap();
        Delta::synthesize(&self.union_str, &prev_from_union, &head_rev.deletes_from_union)
    }

    fn mk_new_rev(&self, new_priority: usize, undo_group: usize,
            base_rev: usize, delta: Delta<RopeInfo>) -> (Revision, Rope) {
        let ix = self.find_rev(base_rev).expect("base revision not found");
        let rev = &self.revs[ix];
        let (ins_delta, deletes) = delta.factor();
        let mut union_ins_delta = ins_delta.transform_expand(&rev.deletes_from_union, rev.union_str_len, true);
        let mut new_deletes = deletes.transform_expand(&rev.deletes_from_union);
        for r in &self.revs[ix + 1..] {
            if let Edit { priority, ref inserts, .. } = r.edit {
                if !inserts.is_empty() {
                    let after = new_priority >= priority;  // should never be ==
                    union_ins_delta = union_ins_delta.transform_expand(inserts, r.union_str_len, after);
                    new_deletes = new_deletes.transform_expand(inserts);
                }
            }
        }
        let new_inserts = union_ins_delta.inserted_subset();
        if !new_inserts.is_empty() {
            new_deletes = new_deletes.transform_expand(&new_inserts);
        }
        let new_union_str = union_ins_delta.apply(&self.union_str);
        let undone = self.get_current_undo().map_or(false, |undos| undos.contains(&undo_group));
        let mut new_from_union = Cow::Borrowed(&self.revs.last().unwrap().deletes_from_union);
        if undone {
            if !new_inserts.is_empty() {
                new_from_union = Cow::Owned(new_from_union.transform_union(&new_inserts));
            }
        } else {
            if !new_inserts.is_empty() {
                new_from_union = Cow::Owned(new_from_union.transform_expand(&new_inserts));
            }
            if !new_deletes.is_empty() {
                new_from_union = Cow::Owned(new_from_union.union(&new_deletes));
            }
        }
        (Revision {
            rev_id: self.rev_id_counter,
            deletes_from_union: new_from_union.into_owned(),
            union_str_len: new_union_str.len(),
            edit: Edit {
                priority: new_priority,
                undo_group: undo_group,
                inserts: new_inserts,
                deletes: new_deletes,
            }
        }, new_union_str)
    }

    pub fn edit_rev(&mut self, priority: usize, undo_group: usize,
            base_rev: usize, delta: Delta<RopeInfo>) {
        let (new_rev, new_union_str) = self.mk_new_rev(priority, undo_group, base_rev, delta);
        self.rev_id_counter += 1;
        self.revs.push(new_rev);
        self.union_str = new_union_str;
    }

    // This computes undo all the way from the beginning. An optimization would be to not
    // recompute the prefix up to where the history diverges, but it's not clear that's
    // even worth the code complexity.
    fn compute_undo(&self, groups: BTreeSet<usize>) -> Revision {
        let mut deletes_from_union = Subset::default();
        for rev in &self.revs {
            if let Edit { ref undo_group, ref inserts, ref deletes, .. } = rev.edit {
                if groups.contains(undo_group) {
                    if !inserts.is_empty() {
                        deletes_from_union = deletes_from_union.transform_union(inserts);
                    }
                } else {
                    if !inserts.is_empty() {
                        deletes_from_union = deletes_from_union.transform_expand(inserts);
                    }
                    if !deletes.is_empty() {
                        deletes_from_union = deletes_from_union.union(deletes);
                    }
                }
            }
        }
        Revision {
            rev_id: self.rev_id_counter,
            deletes_from_union: deletes_from_union,
            union_str_len: self.union_str.len(),
            edit: Undo {
                groups: groups
            }
        }
    }

    pub fn undo(&mut self, groups: BTreeSet<usize>) {
        let new_rev = self.compute_undo(groups);
        self.revs.push(new_rev);
        self.rev_id_counter += 1;
    }

    pub fn is_equivalent_revision(&self, base_rev: usize, other_rev: usize) -> bool {
        let base_subset = self.find_rev(base_rev).map(|rev_index| self.get_subset_from_index(rev_index));
        let other_subset = self.find_rev(other_rev).map(|rev_index| self.get_subset_from_index(rev_index));

        base_subset.is_some() && base_subset == other_subset
    }

    // Note: this function would need some work to handle retaining arbitrary revisions,
    // partly because the reachability calculation would become more complicated (a
    // revision might hold content from an undo group that would otherwise be gc'ed),
    // and partly because you need to retain more undo history, to supply input to the
    // reachability calculation.
    //
    // Thus, it's easiest to defer gc to when all plugins quiesce, but it's certainly
    // possible to fix it so that's not necessary.
    pub fn gc(&mut self, gc_groups: &BTreeSet<usize>) {
        let mut gc_dels = Subset::default();
        // TODO: want to let caller retain more rev_id's.
        let mut retain_revs = BTreeSet::new();
        if let Some(last) = self.revs.last() {
            retain_revs.insert(last.rev_id);
        }
        {
            let cur_undo = self.get_current_undo();
            for rev in &self.revs {
                if let Edit { ref undo_group, ref inserts, ref deletes, .. } = rev.edit {
                    if !retain_revs.contains(&rev.rev_id) && gc_groups.contains(undo_group) {
                        if cur_undo.map_or(false, |undos| undos.contains(undo_group)) {
                            if !inserts.is_empty() {
                                gc_dels = gc_dels.transform_union(inserts);
                            }
                        } else {
                            if !inserts.is_empty() {
                                gc_dels = gc_dels.transform_expand(inserts);
                            }
                            if !deletes.is_empty() {
                                gc_dels = gc_dels.union(deletes);
                            }
                        }
                    } else if !inserts.is_empty() {
                        gc_dels = gc_dels.transform_expand(inserts);
                    }
                }
            }
        }
        if !gc_dels.is_empty() {
            self.union_str = gc_dels.delete_in(&self.union_str);
        }
        let old_revs = std::mem::replace(&mut self.revs, Vec::new());
        for rev in old_revs.into_iter().rev() {
            match rev.edit {
                Edit { priority, undo_group, inserts, deletes } => {
                    let new_gc_dels = if inserts.is_empty() {
                        None
                    } else {
                        Some(inserts.transform_shrink(&gc_dels))
                    };
                    if retain_revs.contains(&rev.rev_id) || !gc_groups.contains(&undo_group) {
                        let (inserts, deletes, deletes_from_union, len) = if gc_dels.is_empty() {
                            (inserts, deletes, rev.deletes_from_union, rev.union_str_len)
                        } else {
                            (gc_dels.transform_shrink(&inserts),
                                gc_dels.transform_shrink(&deletes),
                                gc_dels.transform_shrink(&rev.deletes_from_union),
                                gc_dels.len_after_delete(rev.union_str_len))
                        };
                        self.revs.push(Revision {
                            rev_id: rev.rev_id,
                            deletes_from_union: deletes_from_union,
                            union_str_len: len,
                            edit: Edit {
                                priority: priority,
                                undo_group: undo_group,
                                inserts: inserts,
                                deletes: deletes,
                            }
                        });
                    }
                    if let Some(new_gc_dels) = new_gc_dels {
                        gc_dels = new_gc_dels;
                    }
                }
                Undo { groups } => {
                    // We're super-aggressive about dropping these; after gc, the history
                    // of which undos were used to compute deletes_from_union in edits may be lost.
                    if retain_revs.contains(&rev.rev_id) {
                        let (deletes_from_union, len) = if gc_dels.is_empty() {
                            (rev.deletes_from_union, rev.union_str_len)
                        } else {
                            (gc_dels.transform_shrink(&rev.deletes_from_union),
                                gc_dels.len_after_delete(rev.union_str_len))
                        };
                        self.revs.push(Revision {
                            rev_id: rev.rev_id,
                            deletes_from_union: deletes_from_union,
                            union_str_len: len,
                            edit: Undo {
                                groups: &groups - gc_groups,
                            }
                        })
                    }
                }
            }
        }
        self.revs.reverse();
    }
}
<|endoftext|>


<commit_before>use std::str;
use std::str::Chars;
use std::iter::Enumerate;
use path_builder::*;

extern crate cairo;

pub struct PathParser<'external> {
    path_str: &'external str,
    chars_enumerator: Enumerate<Chars<'external>>,
    lookahead: Option <char>, /* None if we are in EOF */
    current_pos: usize,

    builder: &'external mut RsvgPathBuilder,

    error_message: &'static str,
    has_error: bool,

    current_x: f64,
    current_y: f64
}

impl<'external> PathParser<'external> {
    pub fn new (builder: &'external mut RsvgPathBuilder, path_str: &'external str) -> PathParser<'external> {
        PathParser {
            path_str: path_str,
            chars_enumerator: path_str.chars ().enumerate (),
            lookahead: None,
            current_pos: 0,

            builder: builder,

            error_message: "",
            has_error: false,

            current_x: 0.0,
            current_y: 0.0
        }
    }

    pub fn parse (&mut self) -> bool {
        self.getchar ();

        return self.optional_whitespace () &&
            self.moveto_drawto_command_groups () &&
            self.optional_whitespace ();
    }

    fn getchar (&mut self) {
        if let Some ((pos, c)) = self.chars_enumerator.next () {
            self.lookahead = Some (c);
            self.current_pos = pos;
        } else {
            self.lookahead = None;
            self.current_pos += 1; /* this is EOF; point just past the end the string */
        }
    }

    fn error (&mut self, message: &'static str) -> bool {
        self.error_message = message;
        self.has_error = true;
        false
    }

    fn match_char (&mut self, c: char) -> bool {
        if let Some (x) = self.lookahead {
            if c == x {
                self.getchar ();
                return true;
            }
        }

        false
    }

    fn whitespace (&mut self) -> bool {
        if let Some (c) = self.lookahead {
            if c.is_whitespace () {
                assert! (self.match_char (c));

                while let Some (c) = self.lookahead {
                    if c.is_whitespace () {
                        assert! (self.match_char (c));
                        continue;
                    } else {
                        break;
                    }
                }

                return true;
            } else {
                return false;
            }
        }

        false
    }

    fn optional_whitespace (&mut self) -> bool {
        self.whitespace ();
        true
    }

    fn optional_comma_whitespace (&mut self) -> bool {
        assert! (self.optional_whitespace ());
        if self.match_char (',') {
            self.optional_whitespace ()
        } else {
            true
        }
    }

    fn lookahead_is (&self, c: char) -> bool {
        if let Some (x) = self.lookahead {
            if x == c {
                return true;
            }
        }

        false
    }

    fn lookahead_is_digit (&self, d: &mut char) -> bool {
        if let Some (c) = self.lookahead {
            if c.is_digit (10) {
                *d = c;
                return true;
            }
        }

        false
    }

    fn number (&mut self) -> Option <f64> {
        let mut has_sign: bool;
        let mut value: f64;
        let mut sign: f64;
        let mut exponent_sign: f64;
        let mut exponent: f64;

        has_sign = false;
        sign = 1.0;
        value = 0.0;
        exponent_sign = 1.0;
        exponent = 0.0;

        if self.match_char ('+') {
            sign = 1.0;
            has_sign = true;
        } else if self.match_char ('-') {
            sign = -1.0;
            has_sign = true;
        }

        let mut c: char = ' ';

        if self.lookahead_is_digit (&mut c) || self.lookahead_is ('.') {
            /* Integer part */

            while self.lookahead_is_digit (&mut c) {
                value = value * 10.0 + char_to_digit (c) as f64;

                assert! (self.match_char (c));
            }

            /* Fractional part */

            if self.match_char ('.') {
                let mut fraction: f64 = 1.0;

                let mut c: char = ' ';

                while self.lookahead_is_digit (&mut c) {
                    fraction = fraction / 10.0;
                    value += fraction * char_to_digit (c) as f64;

                    assert! (self.match_char (c));
                }
            }

            if self.match_char ('E') || self.match_char ('e') {
                /* exponent sign */

                if self.match_char ('+') {
                    exponent_sign = 1.0;
                } else if self.match_char ('-') {
                    exponent_sign = -1.0;
                }

                /* exponent */

                let mut c: char = ' ';

                if self.lookahead_is_digit (&mut c) {
                    while self.lookahead_is_digit (&mut c) {
                        exponent = exponent * 10.0 + char_to_digit (c) as f64;

                        assert! (self.match_char (c));
                    }
                } else {
                    self.error ("Expected digits for exponent");
                    return None;
                }
            }

            Some (value * sign * 10.0f64.powf (exponent * exponent_sign))
        } else {
            if has_sign {
                self.error ("Expected number after sign");
            }

            None
        }
    }

    fn coordinate_pair (&mut self) -> Option<(f64, f64)> {
        if let Some (num1) = self.number () {
            assert! (self.optional_comma_whitespace ());

            if let Some (num2) = self.number () {
                return Some ((num1, num2));
            } else {
                self.error ("Expected second coordinate of coordinate pair");
                return None
            }
        }

        None
    }

    fn emit_line_to (&mut self, absolute: bool, x: f64, y: f64) {
        if absolute {
            self.current_x = x;
            self.current_y = y;
        } else {
            self.current_x += x;
            self.current_y += y;
        }

        self.builder.line_to (self.current_x, self.current_y);
        println! ("emitting lineto {} {}", self.current_x, self.current_y);
    }

    fn lineto_argument_sequence (&mut self, absolute: bool) -> bool {
        if let Some ((x, y)) = self.coordinate_pair () {
            self.emit_line_to (absolute, x, y);

            self.whitespace ();

            if self.lookahead_is (',') {
                assert! (self.match_char (','));
                assert! (self.optional_whitespace ());

                if !self.lineto_argument_sequence (absolute) {
                    self.error ("Expected coordinate pair after comma");
                    return false;
                }
            }

            self.lineto_argument_sequence (absolute);
            true
        } else {
            false
        }
    }

    fn emit_move_to (&mut self, absolute: bool, x: f64, y: f64) {
        if absolute {
            self.current_x = x;
            self.current_y = y;
        } else {
            self.current_x += x;
            self.current_y += y;
        }

        self.builder.move_to (self.current_x, self.current_y);
        println! ("emitting moveto {} {}", self.current_x, self.current_y);
    }

    fn moveto_argument_sequence (&mut self, absolute: bool, is_initial_moveto: bool) -> bool {
        if let Some ((x, y)) = self.coordinate_pair () {
            if is_initial_moveto {
                self.emit_move_to (true, x, y);
            } else {
                self.emit_move_to (absolute, x, y);
            }

            self.whitespace ();

            if self.lookahead_is (',') {
                assert! (self.match_char (','));
                assert! (self.optional_whitespace ());

                if !self.lineto_argument_sequence (absolute) {
                    self.error ("Expected coordinate pair after comma");
                    return false;
                }
            }

            self.lineto_argument_sequence (absolute);
            true
        } else {
            self.error ("Expected coordinate pair after moveto")
        }
    }

    fn moveto (&mut self, is_initial_moveto: bool) -> bool {
        if self.lookahead_is ('M') || self.lookahead_is ('m') {
            let absolute: bool;

            if self.match_char ('M') {
                absolute = true;
            } else {
                assert! (self.match_char ('m'));
                absolute = false;
            }

            return self.optional_whitespace () &&
                self.moveto_argument_sequence (absolute, is_initial_moveto);
        }

        false
    }

    fn moveto_drawto_command_group (&mut self, is_initial_moveto: bool) -> bool {
        if self.moveto (is_initial_moveto) {
            return self.optional_whitespace () &&
                true; // FIXME self.optional_drawto_commands ();
        } else {
            false
        }
    }

    fn moveto_drawto_command_groups (&mut self) -> bool {
        if self.moveto_drawto_command_group (true) {
            loop {
                self.optional_whitespace ();
                if !self.moveto_drawto_command_group (false) {
                    break;
                }
            }

            true
        } else {
            self.error ("Expected moveto command")
        }
    }
}

fn char_to_digit (c: char) -> i32 {
    c as i32 - '0' as i32
}

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

    extern crate cairo;

    fn path_segment_vectors_are_equal (a: &Vec<cairo::PathSegment>,
                                       b: &Vec<cairo::PathSegment>) -> bool {

        if a.len() != b.len () {
            return false;
        }

        if a.len () == 0 && b.len () == 0 {
            return true;
        }

        let mut iter = a.iter().zip (b);

        loop {
            if let Some ((seg1, seg2)) = iter.next () {
                match *seg1 {
                    cairo::PathSegment::MoveTo ((x, y)) => {
                        if let cairo::PathSegment::MoveTo ((ox, oy)) = *seg2 {
                            println! ("{} {} {} {}", x, y, ox, oy);
                            if (x, y) != (ox, oy) {
                                return false;
                            }
                        } else {
                            return false;
                        }
                    },

                    cairo::PathSegment::LineTo ((x, y)) => {
                        if let cairo::PathSegment::LineTo ((ox, oy)) = *seg2 {
                            println! ("{} {} {} {}", x, y, ox, oy);
                            if (x, y) != (ox, oy) {
                                return false;
                            }
                        } else {
                            return false;
                        }
                    },

                    cairo::PathSegment::CurveTo ((x2, y2), (x3, y3), (x4, y4)) => {
                        if let cairo::PathSegment::CurveTo ((ox2, oy2), (ox3, oy3), (ox4, oy4)) = *seg2 {
                            if (ox2, oy2, ox3, oy3, ox4, oy4) != (x2, y2, x3, y3, x4, y4) {
                                return false;
                            }
                        } else {
                            return false;
                        }
                    },

                    cairo::PathSegment::ClosePath => {
                        if let cairo::PathSegment::ClosePath = *seg2 {
                            /* okay */
                        } else {
                            return false;
                        }
                    }
                }
            } else {
                break;
            }
        }

        true
    }

    fn print_error (parser: &PathParser) {
        let prefix = "Error in \"";

        println! ("");
        println! ("{}{}\"", prefix, &parser.path_str);

        for _ in 0 .. (prefix.len() + parser.current_pos) {
            print! (" ");
        }

        println! ("^ pos {}", parser.current_pos);
        println! ("{}", &parser.error_message);
    }

    fn parse_path (path_str: &str) -> RsvgPathBuilder {
        let mut builder = RsvgPathBuilder::new ();

        {
            let mut parser = PathParser::new (&mut builder, path_str);
            if !parser.parse () {
                print_error (&parser);
            }
        }

        builder
    }

    fn test_parser (path_str: &str,
                    expected_segments: &Vec<cairo::PathSegment>) {
        let builder = parse_path (path_str);
        let segments = builder.get_path_segments ();

        assert! (path_segment_vectors_are_equal (expected_segments, segments));
    }

    fn moveto (x: f64, y: f64) -> cairo::PathSegment {
        cairo::PathSegment::MoveTo ((x, y))
    }

    fn lineto (x: f64, y: f64) -> cairo::PathSegment {
        cairo::PathSegment::LineTo ((x, y))
    }

    fn curveto (x2: f64, y2: f64, x3: f64, y3: f64, x4: f64, y4: f64) -> cairo::PathSegment {
        cairo::PathSegment::CurveTo ((x2, y2), (x3, y3), (x4, y4))
    }

    fn closepath () -> cairo::PathSegment {
        cairo::PathSegment::ClosePath
    }

    #[test]
    fn path_parser_handles_empty_data () {
        test_parser ("",
                     &Vec::<cairo::PathSegment>::new ());
    }

    #[test]
    fn path_parser_handles_numbers () {
        test_parser ("M 10 20",
                     &vec![
                         moveto (10.0, 20.0)
                     ]);

        test_parser ("M .10 0.20",
                     &vec![
                         moveto (0.10, 0.20)
                     ]);
    }

    #[test]
    fn path_parser_handles_single_moveto () {
        test_parser ("M 10 20",
                     &vec![
                         moveto (10.0, 20.0)
                     ]);

        test_parser ("M10,20",
                     &vec![
                         moveto (10.0, 20.0)
                     ]);

        test_parser ("M10 20",
                     &vec![
                         moveto (10.0, 20.0)
                     ]);

        test_parser ("    M10,20     ",
                     &vec![
                         moveto (10.0, 20.0)
                     ]);
    }

    #[test]
    fn path_parser_handles_relative_moveto () {
        test_parser ("m10 20",
                     &vec![
                         moveto (10.0, 20.0)
                     ]);
    }

    #[test]
    fn path_parser_handles_absolute_moveto_with_implicit_linetos () {
        test_parser ("M10 20 30 40",
                     &vec![
                         moveto (10.0, 20.0),
                         lineto (30.0, 40.0)
                     ]);
    }

    #[test]
    fn path_parser_handles_relative_moveto_with_implicit_linetos () {
        test_parser ("m10 20 30 40",
                     &vec![
                         moveto (10.0, 20.0),
                         lineto (40.0, 60.0)
                     ]);
    }
}
<commit_msg>path_parser.rs: Tests for various representations of numbers<commit_after>use std::str;
use std::str::Chars;
use std::iter::Enumerate;
use path_builder::*;

extern crate cairo;

pub struct PathParser<'external> {
    path_str: &'external str,
    chars_enumerator: Enumerate<Chars<'external>>,
    lookahead: Option <char>, /* None if we are in EOF */
    current_pos: usize,

    builder: &'external mut RsvgPathBuilder,

    error_message: &'static str,
    has_error: bool,

    current_x: f64,
    current_y: f64
}

impl<'external> PathParser<'external> {
    pub fn new (builder: &'external mut RsvgPathBuilder, path_str: &'external str) -> PathParser<'external> {
        PathParser {
            path_str: path_str,
            chars_enumerator: path_str.chars ().enumerate (),
            lookahead: None,
            current_pos: 0,

            builder: builder,

            error_message: "",
            has_error: false,

            current_x: 0.0,
            current_y: 0.0
        }
    }

    pub fn parse (&mut self) -> bool {
        self.getchar ();

        return self.optional_whitespace () &&
            self.moveto_drawto_command_groups () &&
            self.optional_whitespace ();
    }

    fn getchar (&mut self) {
        if let Some ((pos, c)) = self.chars_enumerator.next () {
            self.lookahead = Some (c);
            self.current_pos = pos;
        } else {
            self.lookahead = None;
            self.current_pos += 1; /* this is EOF; point just past the end the string */
        }
    }

    fn error (&mut self, message: &'static str) -> bool {
        self.error_message = message;
        self.has_error = true;
        false
    }

    fn match_char (&mut self, c: char) -> bool {
        if let Some (x) = self.lookahead {
            if c == x {
                self.getchar ();
                return true;
            }
        }

        false
    }

    fn whitespace (&mut self) -> bool {
        if let Some (c) = self.lookahead {
            if c.is_whitespace () {
                assert! (self.match_char (c));

                while let Some (c) = self.lookahead {
                    if c.is_whitespace () {
                        assert! (self.match_char (c));
                        continue;
                    } else {
                        break;
                    }
                }

                return true;
            } else {
                return false;
            }
        }

        false
    }

    fn optional_whitespace (&mut self) -> bool {
        self.whitespace ();
        true
    }

    fn optional_comma_whitespace (&mut self) -> bool {
        assert! (self.optional_whitespace ());
        if self.match_char (',') {
            self.optional_whitespace ()
        } else {
            true
        }
    }

    fn lookahead_is (&self, c: char) -> bool {
        if let Some (x) = self.lookahead {
            if x == c {
                return true;
            }
        }

        false
    }

    fn lookahead_is_digit (&self, d: &mut char) -> bool {
        if let Some (c) = self.lookahead {
            if c.is_digit (10) {
                *d = c;
                return true;
            }
        }

        false
    }

    fn number (&mut self) -> Option <f64> {
        let mut has_sign: bool;
        let mut value: f64;
        let mut sign: f64;
        let mut exponent_sign: f64;
        let mut exponent: f64;

        has_sign = false;
        sign = 1.0;
        value = 0.0;
        exponent_sign = 1.0;
        exponent = 0.0;

        if self.match_char ('+') {
            sign = 1.0;
            has_sign = true;
        } else if self.match_char ('-') {
            sign = -1.0;
            has_sign = true;
        }

        let mut c: char = ' ';

        if self.lookahead_is_digit (&mut c) || self.lookahead_is ('.') {
            /* Integer part */

            while self.lookahead_is_digit (&mut c) {
                value = value * 10.0 + char_to_digit (c) as f64;

                assert! (self.match_char (c));
            }

            /* Fractional part */

            if self.match_char ('.') {
                let mut fraction: f64 = 1.0;

                let mut c: char = ' ';

                while self.lookahead_is_digit (&mut c) {
                    fraction = fraction / 10.0;
                    value += fraction * char_to_digit (c) as f64;

                    assert! (self.match_char (c));
                }
            }

            if self.match_char ('E') || self.match_char ('e') {
                /* exponent sign */

                if self.match_char ('+') {
                    exponent_sign = 1.0;
                } else if self.match_char ('-') {
                    exponent_sign = -1.0;
                }

                /* exponent */

                let mut c: char = ' ';

                if self.lookahead_is_digit (&mut c) {
                    while self.lookahead_is_digit (&mut c) {
                        exponent = exponent * 10.0 + char_to_digit (c) as f64;

                        assert! (self.match_char (c));
                    }
                } else {
                    self.error ("Expected digits for exponent");
                    return None;
                }
            }

            Some (value * sign * 10.0f64.powf (exponent * exponent_sign))
        } else {
            if has_sign {
                self.error ("Expected number after sign");
            }

            None
        }
    }

    fn coordinate_pair (&mut self) -> Option<(f64, f64)> {
        if let Some (num1) = self.number () {
            assert! (self.optional_comma_whitespace ());

            if let Some (num2) = self.number () {
                return Some ((num1, num2));
            } else {
                self.error ("Expected second coordinate of coordinate pair");
                return None
            }
        }

        None
    }

    fn emit_line_to (&mut self, absolute: bool, x: f64, y: f64) {
        if absolute {
            self.current_x = x;
            self.current_y = y;
        } else {
            self.current_x += x;
            self.current_y += y;
        }

        self.builder.line_to (self.current_x, self.current_y);
        println! ("emitting lineto {} {}", self.current_x, self.current_y);
    }

    fn lineto_argument_sequence (&mut self, absolute: bool) -> bool {
        if let Some ((x, y)) = self.coordinate_pair () {
            self.emit_line_to (absolute, x, y);

            self.whitespace ();

            if self.lookahead_is (',') {
                assert! (self.match_char (','));
                assert! (self.optional_whitespace ());

                if !self.lineto_argument_sequence (absolute) {
                    self.error ("Expected coordinate pair after comma");
                    return false;
                }
            }

            self.lineto_argument_sequence (absolute);
            true
        } else {
            false
        }
    }

    fn emit_move_to (&mut self, absolute: bool, x: f64, y: f64) {
        if absolute {
            self.current_x = x;
            self.current_y = y;
        } else {
            self.current_x += x;
            self.current_y += y;
        }

        self.builder.move_to (self.current_x, self.current_y);
        println! ("emitting moveto {} {}", self.current_x, self.current_y);
    }

    fn moveto_argument_sequence (&mut self, absolute: bool, is_initial_moveto: bool) -> bool {
        if let Some ((x, y)) = self.coordinate_pair () {
            if is_initial_moveto {
                self.emit_move_to (true, x, y);
            } else {
                self.emit_move_to (absolute, x, y);
            }

            self.whitespace ();

            if self.lookahead_is (',') {
                assert! (self.match_char (','));
                assert! (self.optional_whitespace ());

                if !self.lineto_argument_sequence (absolute) {
                    self.error ("Expected coordinate pair after comma");
                    return false;
                }
            }

            self.lineto_argument_sequence (absolute);
            true
        } else {
            self.error ("Expected coordinate pair after moveto")
        }
    }

    fn moveto (&mut self, is_initial_moveto: bool) -> bool {
        if self.lookahead_is ('M') || self.lookahead_is ('m') {
            let absolute: bool;

            if self.match_char ('M') {
                absolute = true;
            } else {
                assert! (self.match_char ('m'));
                absolute = false;
            }

            return self.optional_whitespace () &&
                self.moveto_argument_sequence (absolute, is_initial_moveto);
        }

        false
    }

    fn moveto_drawto_command_group (&mut self, is_initial_moveto: bool) -> bool {
        if self.moveto (is_initial_moveto) {
            return self.optional_whitespace () &&
                true; // FIXME self.optional_drawto_commands ();
        } else {
            false
        }
    }

    fn moveto_drawto_command_groups (&mut self) -> bool {
        if self.moveto_drawto_command_group (true) {
            loop {
                self.optional_whitespace ();
                if !self.moveto_drawto_command_group (false) {
                    break;
                }
            }

            true
        } else {
            self.error ("Expected moveto command")
        }
    }
}

fn char_to_digit (c: char) -> i32 {
    c as i32 - '0' as i32
}

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

    extern crate cairo;

    fn path_segment_vectors_are_equal (a: &Vec<cairo::PathSegment>,
                                       b: &Vec<cairo::PathSegment>) -> bool {

        if a.len() != b.len () {
            return false;
        }

        if a.len () == 0 && b.len () == 0 {
            return true;
        }

        let mut iter = a.iter().zip (b);

        loop {
            if let Some ((seg1, seg2)) = iter.next () {
                match *seg1 {
                    cairo::PathSegment::MoveTo ((x, y)) => {
                        if let cairo::PathSegment::MoveTo ((ox, oy)) = *seg2 {
                            println! ("{} {} {} {}", x, y, ox, oy);
                            if (x, y) != (ox, oy) {
                                return false;
                            }
                        } else {
                            return false;
                        }
                    },

                    cairo::PathSegment::LineTo ((x, y)) => {
                        if let cairo::PathSegment::LineTo ((ox, oy)) = *seg2 {
                            println! ("{} {} {} {}", x, y, ox, oy);
                            if (x, y) != (ox, oy) {
                                return false;
                            }
                        } else {
                            return false;
                        }
                    },

                    cairo::PathSegment::CurveTo ((x2, y2), (x3, y3), (x4, y4)) => {
                        if let cairo::PathSegment::CurveTo ((ox2, oy2), (ox3, oy3), (ox4, oy4)) = *seg2 {
                            if (ox2, oy2, ox3, oy3, ox4, oy4) != (x2, y2, x3, y3, x4, y4) {
                                return false;
                            }
                        } else {
                            return false;
                        }
                    },

                    cairo::PathSegment::ClosePath => {
                        if let cairo::PathSegment::ClosePath = *seg2 {
                            /* okay */
                        } else {
                            return false;
                        }
                    }
                }
            } else {
                break;
            }
        }

        true
    }

    fn print_error (parser: &PathParser) {
        let prefix = "Error in \"";

        println! ("");
        println! ("{}{}\"", prefix, &parser.path_str);

        for _ in 0 .. (prefix.len() + parser.current_pos) {
            print! (" ");
        }

        println! ("^ pos {}", parser.current_pos);
        println! ("{}", &parser.error_message);
    }

    fn parse_path (path_str: &str) -> RsvgPathBuilder {
        let mut builder = RsvgPathBuilder::new ();

        {
            let mut parser = PathParser::new (&mut builder, path_str);
            if !parser.parse () {
                print_error (&parser);
            }
        }

        builder
    }

    fn test_parser (path_str: &str,
                    expected_segments: &Vec<cairo::PathSegment>) {
        let builder = parse_path (path_str);
        let segments = builder.get_path_segments ();

        assert! (path_segment_vectors_are_equal (expected_segments, segments));
    }

    fn moveto (x: f64, y: f64) -> cairo::PathSegment {
        cairo::PathSegment::MoveTo ((x, y))
    }

    fn lineto (x: f64, y: f64) -> cairo::PathSegment {
        cairo::PathSegment::LineTo ((x, y))
    }

    fn curveto (x2: f64, y2: f64, x3: f64, y3: f64, x4: f64, y4: f64) -> cairo::PathSegment {
        cairo::PathSegment::CurveTo ((x2, y2), (x3, y3), (x4, y4))
    }

    fn closepath () -> cairo::PathSegment {
        cairo::PathSegment::ClosePath
    }

    #[test]
    fn path_parser_handles_empty_data () {
        test_parser ("",
                     &Vec::<cairo::PathSegment>::new ());
    }

    #[test]
    fn path_parser_handles_numbers () {
        test_parser ("M 10 20",
                     &vec![
                         moveto (10.0, 20.0)
                     ]);

        test_parser ("M -10 -20",
                     &vec![
                         moveto (-10.0, -20.0)
                     ]);

        test_parser ("M .10 0.20",
                     &vec![
                         moveto (0.10, 0.20)
                     ]);

        test_parser ("M -.10 -0.20",
                     &vec![
                         moveto (-0.10, -0.20)
                     ]);

        test_parser ("M-.10-0.20",
                     &vec![
                         moveto (-0.10, -0.20)
                     ]);

        test_parser ("M.10.20",
                     &vec![
                         moveto (0.10, 0.20)
                     ]);

        test_parser ("M .10E1 .20e-4",
                     &vec![
                         moveto (1.0, 0.000020)
                     ]);

        test_parser ("M-.10E1-.20",
                     &vec![
                         moveto (-1.0, -0.20)
                     ]);

        test_parser ("M10.10E2 -0.20e3",
                     &vec![
                         moveto (1010.0, -200.0)
                     ]);

        test_parser ("M-10.10E2-0.20e-3",
                     &vec![
                         moveto (-1010.0, -0.00020)
                     ]);
    }

    #[test]
    fn path_parser_handles_single_moveto () {
        test_parser ("M 10 20",
                     &vec![
                         moveto (10.0, 20.0)
                     ]);

        test_parser ("M10,20",
                     &vec![
                         moveto (10.0, 20.0)
                     ]);

        test_parser ("M10 20",
                     &vec![
                         moveto (10.0, 20.0)
                     ]);

        test_parser ("    M10,20     ",
                     &vec![
                         moveto (10.0, 20.0)
                     ]);
    }

    #[test]
    fn path_parser_handles_relative_moveto () {
        test_parser ("m10 20",
                     &vec![
                         moveto (10.0, 20.0)
                     ]);
    }

    #[test]
    fn path_parser_handles_absolute_moveto_with_implicit_linetos () {
        test_parser ("M10 20 30 40",
                     &vec![
                         moveto (10.0, 20.0),
                         lineto (30.0, 40.0)
                     ]);
    }

    #[test]
    fn path_parser_handles_relative_moveto_with_implicit_linetos () {
        test_parser ("m10 20 30 40",
                     &vec![
                         moveto (10.0, 20.0),
                         lineto (40.0, 60.0)
                     ]);
    }
}
<|endoftext|>


<commit_before>/**
 * Return the minimal value for an uint.
 *
 * This is always 0
 */
pure fn min_value() -> uint { ret 0u; }

/**
 * Return the maximal value for an uint.
 *
 * This is 2^wordsize - 1
 */
pure fn max_value() -> uint {
     ret 0u - 1u;
}

fn add(x: uint, y: uint) -> uint { ret x + y; }

fn sub(x: uint, y: uint) -> uint { ret x - y; }

fn mul(x: uint, y: uint) -> uint { ret x * y; }

fn div(x: uint, y: uint) -> uint { ret x / y; }

fn rem(x: uint, y: uint) -> uint { ret x % y; }

pure fn lt(x: uint, y: uint) -> bool { ret x < y; }

pure fn le(x: uint, y: uint) -> bool { ret x <= y; }

pure fn eq(x: uint, y: uint) -> bool { ret x == y; }

pure fn ne(x: uint, y: uint) -> bool { ret x != y; }

pure fn ge(x: uint, y: uint) -> bool { ret x >= y; }

pure fn gt(x: uint, y: uint) -> bool { ret x > y; }

fn max(x: uint, y: uint) -> uint { if x > y { ret x; } ret y; }

fn min(x: uint, y: uint) -> uint { if x > y { ret y; } ret x; }

fn range(lo: uint, hi: uint, it: block(uint)) {
    while lo < hi { it(lo); lo += 1u; }
}

fn next_power_of_two(n: uint) -> uint {
    let halfbits: uint = sys::size_of::<uint>() * 4u;
    let tmp: uint = n - 1u;
    let shift: uint = 1u;
    while shift <= halfbits { tmp |= tmp >> shift; shift <<= 1u; }
    ret tmp + 1u;
}

fn parse_buf(buf: [u8], radix: uint) -> uint {
    if vec::len::<u8>(buf) == 0u {
        log_err "parse_buf(): buf is empty";
        fail;
    }
    let i = vec::len::<u8>(buf) - 1u;
    let power = 1u;
    let n = 0u;
    while true {
        n += (buf[i] - ('0' as u8) as uint) * power;
        power *= radix;
        if i == 0u { ret n; }
        i -= 1u;
    }
    fail;
}

fn from_str(s: str) -> uint { parse_buf(str::bytes(s), 10u) }

fn to_str(num: uint, radix: uint) -> str {
    let n = num;
    assert (0u < radix && radix <= 16u);
    fn digit(n: uint) -> char {
        ret alt n {
              0u { '0' }
              1u { '1' }
              2u { '2' }
              3u { '3' }
              4u { '4' }
              5u { '5' }
              6u { '6' }
              7u { '7' }
              8u { '8' }
              9u { '9' }
              10u { 'a' }
              11u { 'b' }
              12u { 'c' }
              13u { 'd' }
              14u { 'e' }
              15u { 'f' }
              _ { fail }
            };
    }
    if n == 0u { ret "0"; }
    let s: str = "";
    while n != 0u {
        s += str::unsafe_from_byte(digit(n % radix) as u8);
        n /= radix;
    }
    let s1: str = "";
    let len: uint = str::byte_len(s);
    while len != 0u { len -= 1u; s1 += str::unsafe_from_byte(s[len]); }
    ret s1;
}
fn str(i: uint) -> str { ret to_str(i, 10u); }

// Local Variables:
// mode: rust;
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// compile-command: "make -k -C $RBUILD 2>&1 | sed -e 's/\\/x\\//x:\\//g'";
// End:
<commit_msg>Mark uint add/sub/mul/div/rem functions as pure<commit_after>/**
 * Return the minimal value for an uint.
 *
 * This is always 0
 */
pure fn min_value() -> uint { ret 0u; }

/**
 * Return the maximal value for an uint.
 *
 * This is 2^wordsize - 1
 */
pure fn max_value() -> uint {
     ret 0u - 1u;
}

pure fn add(x: uint, y: uint) -> uint { ret x + y; }

pure fn sub(x: uint, y: uint) -> uint { ret x - y; }

pure fn mul(x: uint, y: uint) -> uint { ret x * y; }

pure fn div(x: uint, y: uint) -> uint { ret x / y; }

pure fn rem(x: uint, y: uint) -> uint { ret x % y; }

pure fn lt(x: uint, y: uint) -> bool { ret x < y; }

pure fn le(x: uint, y: uint) -> bool { ret x <= y; }

pure fn eq(x: uint, y: uint) -> bool { ret x == y; }

pure fn ne(x: uint, y: uint) -> bool { ret x != y; }

pure fn ge(x: uint, y: uint) -> bool { ret x >= y; }

pure fn gt(x: uint, y: uint) -> bool { ret x > y; }

fn max(x: uint, y: uint) -> uint { if x > y { ret x; } ret y; }

fn min(x: uint, y: uint) -> uint { if x > y { ret y; } ret x; }

fn range(lo: uint, hi: uint, it: block(uint)) {
    while lo < hi { it(lo); lo += 1u; }
}

fn next_power_of_two(n: uint) -> uint {
    let halfbits: uint = sys::size_of::<uint>() * 4u;
    let tmp: uint = n - 1u;
    let shift: uint = 1u;
    while shift <= halfbits { tmp |= tmp >> shift; shift <<= 1u; }
    ret tmp + 1u;
}

fn parse_buf(buf: [u8], radix: uint) -> uint {
    if vec::len::<u8>(buf) == 0u {
        log_err "parse_buf(): buf is empty";
        fail;
    }
    let i = vec::len::<u8>(buf) - 1u;
    let power = 1u;
    let n = 0u;
    while true {
        n += (buf[i] - ('0' as u8) as uint) * power;
        power *= radix;
        if i == 0u { ret n; }
        i -= 1u;
    }
    fail;
}

fn from_str(s: str) -> uint { parse_buf(str::bytes(s), 10u) }

fn to_str(num: uint, radix: uint) -> str {
    let n = num;
    assert (0u < radix && radix <= 16u);
    fn digit(n: uint) -> char {
        ret alt n {
              0u { '0' }
              1u { '1' }
              2u { '2' }
              3u { '3' }
              4u { '4' }
              5u { '5' }
              6u { '6' }
              7u { '7' }
              8u { '8' }
              9u { '9' }
              10u { 'a' }
              11u { 'b' }
              12u { 'c' }
              13u { 'd' }
              14u { 'e' }
              15u { 'f' }
              _ { fail }
            };
    }
    if n == 0u { ret "0"; }
    let s: str = "";
    while n != 0u {
        s += str::unsafe_from_byte(digit(n % radix) as u8);
        n /= radix;
    }
    let s1: str = "";
    let len: uint = str::byte_len(s);
    while len != 0u { len -= 1u; s1 += str::unsafe_from_byte(s[len]); }
    ret s1;
}
fn str(i: uint) -> str { ret to_str(i, 10u); }

// Local Variables:
// mode: rust;
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// compile-command: "make -k -C $RBUILD 2>&1 | sed -e 's/\\/x\\//x:\\//g'";
// End:
<|endoftext|>