<commit_before>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use crate::LSPExtraDataProvider;
use crate::{
    diagnostic_reporter::{get_diagnostics_data, DiagnosticReporter},
    js_language_server::JSLanguageServer,
    lsp_runtime_error::LSPRuntimeResult,
    node_resolution_info::{get_node_resolution_info, NodeResolutionInfo},
    utils::extract_project_name_from_url,
    utils::{extract_executable_definitions_from_text, extract_executable_document_from_text},
};
use common::{Diagnostic as CompilerDiagnostic, PerfLogger, SourceLocationKey, Span};
use crossbeam::channel::Sender;
use dashmap::{mapref::entry::Entry, DashMap};
use fnv::FnvBuildHasher;
use graphql_ir::{
    build_ir_with_extra_features, BuilderOptions, FragmentVariablesSemantic, Program,
};
use graphql_syntax::{
    parse_executable_with_error_recovery, ExecutableDefinition, ExecutableDocument, GraphQLSource,
};
use interner::{Intern, StringKey};
use log::debug;
use lsp_server::Message;
use lsp_types::{Diagnostic, DiagnosticTag, Range, TextDocumentPositionParams, Url};
use relay_compiler::{
    config::{Config, ProjectConfig},
    FileCategorizer,
};
use relay_transforms::deprecated_fields_for_executable_definition;
use schema::SDLSchema;
use schema_documentation::{
    CombinedSchemaDocumentation, SchemaDocumentation, SchemaDocumentationLoader,
};
use std::{collections::HashMap, path::PathBuf, sync::Arc};
use tokio::{sync::Notify, task};

use super::lsp_state_resources::LSPStateResources;

pub type Schemas = Arc<DashMap<StringKey, Arc<SDLSchema>, FnvBuildHasher>>;
pub type SourcePrograms = Arc<DashMap<StringKey, Program, FnvBuildHasher>>;
pub type ProjectStatusMap = Arc<DashMap<StringKey, ProjectStatus, FnvBuildHasher>>;

#[derive(Eq, PartialEq)]
pub enum ProjectStatus {
    Activated,
    Completed,
}

/// This structure contains all available resources that we may use in the Relay LSP message/notification
/// handlers. Such as schema, programs, extra_data_providers, etc...
pub struct LSPState<TPerfLogger: PerfLogger + 'static, TSchemaDocumentation: SchemaDocumentation> {
    config: Arc<Config>,
    root_dir: PathBuf,
    root_dir_str: String,
    pub extra_data_provider: Box<dyn LSPExtraDataProvider>,
    file_categorizer: FileCategorizer,
    schemas: Schemas,
    schema_documentation_loader: Option<Box<dyn SchemaDocumentationLoader<TSchemaDocumentation>>>,
    source_programs: SourcePrograms,
    synced_graphql_documents: HashMap<Url, Vec<GraphQLSource>>,
    perf_logger: Arc<TPerfLogger>,
    diagnostic_reporter: Arc<DiagnosticReporter>,
    notify_sender: Arc<Notify>,
    project_status: ProjectStatusMap,
    pub js_resource: Box<dyn JSLanguageServer<TPerfLogger, TSchemaDocumentation>>,
}

impl<TPerfLogger: PerfLogger + 'static, TSchemaDocumentation: SchemaDocumentation>
    LSPState<TPerfLogger, TSchemaDocumentation>
{
    /// Private constructor
    fn new(
        config: Arc<Config>,
        perf_logger: Arc<TPerfLogger>,
        extra_data_provider: Box<dyn LSPExtraDataProvider>,
        schema_documentation_loader: Option<
            Box<dyn SchemaDocumentationLoader<TSchemaDocumentation>>,
        >,
        js_resource: Box<dyn JSLanguageServer<TPerfLogger, TSchemaDocumentation>>,
        sender: Sender<Message>,
    ) -> Self {
        let file_categorizer = FileCategorizer::from_config(&config);
        let root_dir = &config.root_dir.clone();
        let diagnostic_reporter =
            Arc::new(DiagnosticReporter::new(config.root_dir.clone(), sender));

        Self {
            config,
            diagnostic_reporter,
            extra_data_provider,
            file_categorizer,
            notify_sender: Arc::new(Notify::new()),
            perf_logger,
            project_status: Arc::new(DashMap::with_hasher(FnvBuildHasher::default())),
            root_dir_str: root_dir.to_string_lossy().to_string(),
            root_dir: root_dir.clone(),
            schemas: Arc::new(DashMap::with_hasher(FnvBuildHasher::default())),
            schema_documentation_loader,
            source_programs: Arc::new(DashMap::with_hasher(FnvBuildHasher::default())),
            synced_graphql_documents: Default::default(),
            js_resource,
        }
    }

    /// This method is responsible for creating schema/source_programs for LSP internal state
    /// - so the LSP can provide these to Hover/Completion/GoToDefinition requests.
    /// It also creates a watchman subscription that is responsible for keeping these resources up-to-date.
    pub(crate) fn create_state(
        config: Arc<Config>,
        perf_logger: Arc<TPerfLogger>,
        extra_data_provider: Box<dyn LSPExtraDataProvider>,
        schema_documentation_loader: Option<
            Box<dyn SchemaDocumentationLoader<TSchemaDocumentation>>,
        >,
        js_resource: Box<dyn JSLanguageServer<TPerfLogger, TSchemaDocumentation>>,
        sender: Sender<Message>,
    ) -> Self {
        debug!("Creating lsp_state...");
        let lsp_state = Self::new(
            config,
            perf_logger,
            extra_data_provider,
            schema_documentation_loader,
            js_resource,
            sender.clone(),
        );

        // Preload schema documentation - this will warm-up schema documentation cache in the LSP Extra Data providers
        lsp_state.preload_documentation();

        let config_clone = Arc::clone(&lsp_state.config);
        let perf_logger_clone = Arc::clone(&lsp_state.perf_logger);
        let schemas = Arc::clone(&lsp_state.schemas);
        let source_programs = Arc::clone(&lsp_state.source_programs);
        let diagnostic_reporter = Arc::clone(&lsp_state.diagnostic_reporter);
        let notify_sender = Arc::clone(&lsp_state.notify_sender);
        let project_status = Arc::clone(&lsp_state.project_status);

        task::spawn(async move {
            let resources = LSPStateResources::new(
                config_clone,
                perf_logger_clone,
                schemas,
                source_programs,
                sender,
                diagnostic_reporter,
                notify_sender,
                project_status,
            );
            resources.watch().await.unwrap();
        });

        debug!("Creating lsp_state created!");
        lsp_state
    }

    pub fn get_schemas(&self) -> Schemas {
        self.schemas.clone()
    }

    pub(crate) fn get_source_programs_ref(&self) -> &SourcePrograms {
        &self.source_programs
    }

    pub(crate) fn resolve_node(
        &self,
        text_document_position: &TextDocumentPositionParams,
    ) -> LSPRuntimeResult<NodeResolutionInfo> {
        get_node_resolution_info(
            text_document_position,
            &self.synced_graphql_documents,
            &self.file_categorizer,
            &self.root_dir,
        )
    }

    pub(crate) fn resolve_executable_definitions(
        &self,
        params: &TextDocumentPositionParams,
    ) -> LSPRuntimeResult<Vec<ExecutableDefinition>> {
        extract_executable_definitions_from_text(params, &self.synced_graphql_documents)
    }

    pub(crate) fn root_dir(&self) -> &PathBuf {
        &self.root_dir
    }

    pub(crate) fn root_dir_str(&self) -> &str {
        &self.root_dir_str
    }

    pub(crate) fn remove_synced_sources(&mut self, url: &Url) {
        self.synced_graphql_documents.remove(url);
        self.diagnostic_reporter
            .clear_quick_diagnostics_for_url(url);
    }

    pub(crate) fn extract_executable_document_from_text(
        &mut self,
        position: &TextDocumentPositionParams,
        index_offset: usize,
    ) -> LSPRuntimeResult<(ExecutableDocument, Span, StringKey)> {
        extract_executable_document_from_text(
            &position,
            &self.synced_graphql_documents,
            &self.file_categorizer,
            &self.root_dir,
            index_offset,
        )
    }

    pub(crate) fn process_synced_sources(
        &mut self,
        url: Url,
        sources: Vec<GraphQLSource>,
    ) -> LSPRuntimeResult<()> {
        let project_name = self.extract_project_name_from_url(&url)?;

        if let Entry::Vacant(e) = self.project_status.entry(project_name) {
            e.insert(ProjectStatus::Activated);
            self.notify_sender.notify_one();
        }

        self.validate_synced_sources(url.clone(), project_name, &sources);
        self.insert_synced_sources(url, sources);
        Ok(())
    }

    pub fn extract_project_name_from_url(&self, url: &Url) -> LSPRuntimeResult<StringKey> {
        extract_project_name_from_url(&self.file_categorizer, url, &self.root_dir)
    }

    fn insert_synced_sources(&mut self, url: Url, sources: Vec<GraphQLSource>) {
        self.synced_graphql_documents.insert(url, sources);
    }

    fn validate_synced_sources(
        &mut self,
        url: Url,
        project_name: StringKey,
        graphql_sources: &[GraphQLSource],
    ) {
        let mut diagnostics = vec![];
        for graphql_source in graphql_sources {
            let result = parse_executable_with_error_recovery(
                &graphql_source.text,
                SourceLocationKey::standalone(&url.to_string()),
            );

            diagnostics.extend(
                result
                    .errors
                    .iter()
                    .map(|diagnostic| convert_diagnostic(graphql_source, diagnostic)),
            );
            if let Some(schema) = self.schemas.get(&project_name) {
                let compiler_diagnostics = match build_ir_with_extra_features(
                    &schema,
                    &result.item.definitions,
                    BuilderOptions {
                        allow_undefined_fragment_spreads: true,
                        fragment_variables_semantic: FragmentVariablesSemantic::PassedValue,
                        relay_mode: true,
                        default_anonymous_operation_name: None,
                    },
                )
                .and_then(|documents| {
                    let mut warnings = vec![];
                    for document in documents {
                        // Today the only warning we check for is deprecated
                        // fields, but in the future we could check for more
                        // things here by making this more generic.
                        warnings.extend(deprecated_fields_for_executable_definition(
                            &schema, &document,
                        )?)
                    }
                    Ok(warnings)
                }) {
                    Ok(warnings) => warnings,
                    Err(errors) => errors,
                };

                diagnostics.extend(
                    compiler_diagnostics
                        .iter()
                        .map(|diagnostic| convert_diagnostic(graphql_source, diagnostic)),
                );
            }
        }
        self.diagnostic_reporter
            .update_quick_diagnostics_for_url(url, diagnostics);
    }

    fn preload_documentation(&self) {
        for project_config in self.config.enabled_projects() {
            self.get_schema_documentation(&project_config.name.to_string());
        }
    }

    pub fn get_project_config_ref(&self, project_name: StringKey) -> Option<&ProjectConfig> {
        self.config
            .enabled_projects()
            .find(|project_config| project_config.name == project_name)
    }

    pub fn get_config(&self) -> Arc<Config> {
        self.config.clone()
    }

    pub fn get_logger(&self) -> Arc<TPerfLogger> {
        self.perf_logger.clone()
    }

    pub fn get_schema_documentation(&self, schema_name: &str) -> impl SchemaDocumentation {
        let primary = self
            .schemas
            .get(&schema_name.intern())
            .map(|x| Arc::clone(x.value()));
        let secondary = self
            .schema_documentation_loader
            .as_ref()
            .map(|loader| loader.get_schema_documentation(schema_name));

        CombinedSchemaDocumentation::new(primary, secondary)
    }

    /// Given a Range return first available diagnostic message for it
    pub(crate) fn get_diagnostic_for_range(&self, url: &Url, range: Range) -> Option<Diagnostic> {
        self.diagnostic_reporter
            .get_diagnostics_for_range(url, range)
    }
}

pub fn convert_diagnostic(
    graphql_source: &GraphQLSource,
    diagnostic: &CompilerDiagnostic,
) -> Diagnostic {
    let tags: Vec<DiagnosticTag> = diagnostic.tags();

    Diagnostic {
        code: None,
        data: get_diagnostics_data(&diagnostic),
        message: diagnostic.message().to_string(),
        range: diagnostic.location().span().to_range(
            &graphql_source.text,
            graphql_source.line_index,
            graphql_source.column_index,
        ),
        related_information: None,
        severity: Some(diagnostic.severity()),
        tags: if tags.len() == 0 { None } else { Some(tags) },
        source: None,
        ..Default::default()
    }
}
<commit_msg>Disable deprecated fields while we wait for quick diagnostics fix<commit_after>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use crate::LSPExtraDataProvider;
use crate::{
    diagnostic_reporter::{get_diagnostics_data, DiagnosticReporter},
    js_language_server::JSLanguageServer,
    lsp_runtime_error::LSPRuntimeResult,
    node_resolution_info::{get_node_resolution_info, NodeResolutionInfo},
    utils::extract_project_name_from_url,
    utils::{extract_executable_definitions_from_text, extract_executable_document_from_text},
};
use common::{Diagnostic as CompilerDiagnostic, PerfLogger, SourceLocationKey, Span};
use crossbeam::channel::Sender;
use dashmap::{mapref::entry::Entry, DashMap};
use fnv::FnvBuildHasher;
use graphql_ir::{
    build_ir_with_extra_features, BuilderOptions, FragmentVariablesSemantic, Program,
};
use graphql_syntax::{
    parse_executable_with_error_recovery, ExecutableDefinition, ExecutableDocument, GraphQLSource,
};
use interner::{Intern, StringKey};
use log::debug;
use lsp_server::Message;
use lsp_types::{Diagnostic, DiagnosticTag, Range, TextDocumentPositionParams, Url};
use relay_compiler::{
    config::{Config, ProjectConfig},
    FileCategorizer,
};
use relay_transforms::deprecated_fields_for_executable_definition;
use schema::SDLSchema;
use schema_documentation::{
    CombinedSchemaDocumentation, SchemaDocumentation, SchemaDocumentationLoader,
};
use std::{collections::HashMap, path::PathBuf, sync::Arc};
use tokio::{sync::Notify, task};

use super::lsp_state_resources::LSPStateResources;

pub type Schemas = Arc<DashMap<StringKey, Arc<SDLSchema>, FnvBuildHasher>>;
pub type SourcePrograms = Arc<DashMap<StringKey, Program, FnvBuildHasher>>;
pub type ProjectStatusMap = Arc<DashMap<StringKey, ProjectStatus, FnvBuildHasher>>;

#[derive(Eq, PartialEq)]
pub enum ProjectStatus {
    Activated,
    Completed,
}

/// This structure contains all available resources that we may use in the Relay LSP message/notification
/// handlers. Such as schema, programs, extra_data_providers, etc...
pub struct LSPState<TPerfLogger: PerfLogger + 'static, TSchemaDocumentation: SchemaDocumentation> {
    config: Arc<Config>,
    root_dir: PathBuf,
    root_dir_str: String,
    pub extra_data_provider: Box<dyn LSPExtraDataProvider>,
    file_categorizer: FileCategorizer,
    schemas: Schemas,
    schema_documentation_loader: Option<Box<dyn SchemaDocumentationLoader<TSchemaDocumentation>>>,
    source_programs: SourcePrograms,
    synced_graphql_documents: HashMap<Url, Vec<GraphQLSource>>,
    perf_logger: Arc<TPerfLogger>,
    diagnostic_reporter: Arc<DiagnosticReporter>,
    notify_sender: Arc<Notify>,
    project_status: ProjectStatusMap,
    pub js_resource: Box<dyn JSLanguageServer<TPerfLogger, TSchemaDocumentation>>,
}

impl<TPerfLogger: PerfLogger + 'static, TSchemaDocumentation: SchemaDocumentation>
    LSPState<TPerfLogger, TSchemaDocumentation>
{
    /// Private constructor
    fn new(
        config: Arc<Config>,
        perf_logger: Arc<TPerfLogger>,
        extra_data_provider: Box<dyn LSPExtraDataProvider>,
        schema_documentation_loader: Option<
            Box<dyn SchemaDocumentationLoader<TSchemaDocumentation>>,
        >,
        js_resource: Box<dyn JSLanguageServer<TPerfLogger, TSchemaDocumentation>>,
        sender: Sender<Message>,
    ) -> Self {
        let file_categorizer = FileCategorizer::from_config(&config);
        let root_dir = &config.root_dir.clone();
        let diagnostic_reporter =
            Arc::new(DiagnosticReporter::new(config.root_dir.clone(), sender));

        Self {
            config,
            diagnostic_reporter,
            extra_data_provider,
            file_categorizer,
            notify_sender: Arc::new(Notify::new()),
            perf_logger,
            project_status: Arc::new(DashMap::with_hasher(FnvBuildHasher::default())),
            root_dir_str: root_dir.to_string_lossy().to_string(),
            root_dir: root_dir.clone(),
            schemas: Arc::new(DashMap::with_hasher(FnvBuildHasher::default())),
            schema_documentation_loader,
            source_programs: Arc::new(DashMap::with_hasher(FnvBuildHasher::default())),
            synced_graphql_documents: Default::default(),
            js_resource,
        }
    }

    /// This method is responsible for creating schema/source_programs for LSP internal state
    /// - so the LSP can provide these to Hover/Completion/GoToDefinition requests.
    /// It also creates a watchman subscription that is responsible for keeping these resources up-to-date.
    pub(crate) fn create_state(
        config: Arc<Config>,
        perf_logger: Arc<TPerfLogger>,
        extra_data_provider: Box<dyn LSPExtraDataProvider>,
        schema_documentation_loader: Option<
            Box<dyn SchemaDocumentationLoader<TSchemaDocumentation>>,
        >,
        js_resource: Box<dyn JSLanguageServer<TPerfLogger, TSchemaDocumentation>>,
        sender: Sender<Message>,
    ) -> Self {
        debug!("Creating lsp_state...");
        let lsp_state = Self::new(
            config,
            perf_logger,
            extra_data_provider,
            schema_documentation_loader,
            js_resource,
            sender.clone(),
        );

        // Preload schema documentation - this will warm-up schema documentation cache in the LSP Extra Data providers
        lsp_state.preload_documentation();

        let config_clone = Arc::clone(&lsp_state.config);
        let perf_logger_clone = Arc::clone(&lsp_state.perf_logger);
        let schemas = Arc::clone(&lsp_state.schemas);
        let source_programs = Arc::clone(&lsp_state.source_programs);
        let diagnostic_reporter = Arc::clone(&lsp_state.diagnostic_reporter);
        let notify_sender = Arc::clone(&lsp_state.notify_sender);
        let project_status = Arc::clone(&lsp_state.project_status);

        task::spawn(async move {
            let resources = LSPStateResources::new(
                config_clone,
                perf_logger_clone,
                schemas,
                source_programs,
                sender,
                diagnostic_reporter,
                notify_sender,
                project_status,
            );
            resources.watch().await.unwrap();
        });

        debug!("Creating lsp_state created!");
        lsp_state
    }

    pub fn get_schemas(&self) -> Schemas {
        self.schemas.clone()
    }

    pub(crate) fn get_source_programs_ref(&self) -> &SourcePrograms {
        &self.source_programs
    }

    pub(crate) fn resolve_node(
        &self,
        text_document_position: &TextDocumentPositionParams,
    ) -> LSPRuntimeResult<NodeResolutionInfo> {
        get_node_resolution_info(
            text_document_position,
            &self.synced_graphql_documents,
            &self.file_categorizer,
            &self.root_dir,
        )
    }

    pub(crate) fn resolve_executable_definitions(
        &self,
        params: &TextDocumentPositionParams,
    ) -> LSPRuntimeResult<Vec<ExecutableDefinition>> {
        extract_executable_definitions_from_text(params, &self.synced_graphql_documents)
    }

    pub(crate) fn root_dir(&self) -> &PathBuf {
        &self.root_dir
    }

    pub(crate) fn root_dir_str(&self) -> &str {
        &self.root_dir_str
    }

    pub(crate) fn remove_synced_sources(&mut self, url: &Url) {
        self.synced_graphql_documents.remove(url);
        self.diagnostic_reporter
            .clear_quick_diagnostics_for_url(url);
    }

    pub(crate) fn extract_executable_document_from_text(
        &mut self,
        position: &TextDocumentPositionParams,
        index_offset: usize,
    ) -> LSPRuntimeResult<(ExecutableDocument, Span, StringKey)> {
        extract_executable_document_from_text(
            &position,
            &self.synced_graphql_documents,
            &self.file_categorizer,
            &self.root_dir,
            index_offset,
        )
    }

    pub(crate) fn process_synced_sources(
        &mut self,
        url: Url,
        sources: Vec<GraphQLSource>,
    ) -> LSPRuntimeResult<()> {
        let project_name = self.extract_project_name_from_url(&url)?;

        if let Entry::Vacant(e) = self.project_status.entry(project_name) {
            e.insert(ProjectStatus::Activated);
            self.notify_sender.notify_one();
        }

        self.validate_synced_sources(url.clone(), project_name, &sources);
        self.insert_synced_sources(url, sources);
        Ok(())
    }

    pub fn extract_project_name_from_url(&self, url: &Url) -> LSPRuntimeResult<StringKey> {
        extract_project_name_from_url(&self.file_categorizer, url, &self.root_dir)
    }

    fn insert_synced_sources(&mut self, url: Url, sources: Vec<GraphQLSource>) {
        self.synced_graphql_documents.insert(url, sources);
    }

    fn validate_synced_sources(
        &mut self,
        url: Url,
        project_name: StringKey,
        graphql_sources: &[GraphQLSource],
    ) {
        let mut diagnostics = vec![];
        for graphql_source in graphql_sources {
            let result = parse_executable_with_error_recovery(
                &graphql_source.text,
                SourceLocationKey::standalone(&url.to_string()),
            );

            diagnostics.extend(
                result
                    .errors
                    .iter()
                    .map(|diagnostic| convert_diagnostic(graphql_source, diagnostic)),
            );
            if let Some(schema) = self.schemas.get(&project_name) {
                let compiler_diagnostics = match build_ir_with_extra_features(
                    &schema,
                    &result.item.definitions,
                    BuilderOptions {
                        allow_undefined_fragment_spreads: true,
                        fragment_variables_semantic: FragmentVariablesSemantic::PassedValue,
                        relay_mode: true,
                        default_anonymous_operation_name: None,
                    },
                )
                .and_then(|documents| {
                    // Waiting until T101267297 is resolved to endable these.
                    if true {
                        return Ok(vec![]);
                    }
                    let mut warnings = vec![];
                    for document in documents {
                        // Today the only warning we check for is deprecated
                        // fields, but in the future we could check for more
                        // things here by making this more generic.
                        warnings.extend(deprecated_fields_for_executable_definition(
                            &schema, &document,
                        )?)
                    }
                    Ok(warnings)
                }) {
                    Ok(warnings) => warnings,
                    Err(errors) => errors,
                };

                diagnostics.extend(
                    compiler_diagnostics
                        .iter()
                        .map(|diagnostic| convert_diagnostic(graphql_source, diagnostic)),
                );
            }
        }
        self.diagnostic_reporter
            .update_quick_diagnostics_for_url(url, diagnostics);
    }

    fn preload_documentation(&self) {
        for project_config in self.config.enabled_projects() {
            self.get_schema_documentation(&project_config.name.to_string());
        }
    }

    pub fn get_project_config_ref(&self, project_name: StringKey) -> Option<&ProjectConfig> {
        self.config
            .enabled_projects()
            .find(|project_config| project_config.name == project_name)
    }

    pub fn get_config(&self) -> Arc<Config> {
        self.config.clone()
    }

    pub fn get_logger(&self) -> Arc<TPerfLogger> {
        self.perf_logger.clone()
    }

    pub fn get_schema_documentation(&self, schema_name: &str) -> impl SchemaDocumentation {
        let primary = self
            .schemas
            .get(&schema_name.intern())
            .map(|x| Arc::clone(x.value()));
        let secondary = self
            .schema_documentation_loader
            .as_ref()
            .map(|loader| loader.get_schema_documentation(schema_name));

        CombinedSchemaDocumentation::new(primary, secondary)
    }

    /// Given a Range return first available diagnostic message for it
    pub(crate) fn get_diagnostic_for_range(&self, url: &Url, range: Range) -> Option<Diagnostic> {
        self.diagnostic_reporter
            .get_diagnostics_for_range(url, range)
    }
}

pub fn convert_diagnostic(
    graphql_source: &GraphQLSource,
    diagnostic: &CompilerDiagnostic,
) -> Diagnostic {
    let tags: Vec<DiagnosticTag> = diagnostic.tags();

    Diagnostic {
        code: None,
        data: get_diagnostics_data(&diagnostic),
        message: diagnostic.message().to_string(),
        range: diagnostic.location().span().to_range(
            &graphql_source.text,
            graphql_source.line_index,
            graphql_source.column_index,
        ),
        related_information: None,
        severity: Some(diagnostic.severity()),
        tags: if tags.len() == 0 { None } else { Some(tags) },
        source: None,
        ..Default::default()
    }
}
<|endoftext|>


<commit_before>#![feature(raw)]
extern crate elf;
extern crate getopts;

use getopts::Options;
use std::env;
use std::path::Path;
use std::fs::File;
use std::io;
use std::io::Write;

#[repr(C,packed)]
#[derive(Debug)]
struct LoadInfo {
    rel_data_size: u32,
    entry_loc: u32,        /* Entry point for user application */
    init_data_loc: u32,    /* Data initialization information in flash */
    init_data_size: u32,   /* Size of initialization information */
    got_start_offset: u32, /* Offset in memory to start of GOT */
    got_end_offset: u32,   /* Offset in memory to end of GOT */
    bss_start_offset: u32, /* Offset in memory to start of BSS */
    bss_end_offset: u32    /* Offset in memory to end of BSS */
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();
    let mut opts = Options::new();
    opts.optopt("o", "", "set output file name", "OUTFILE");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => { m }
        Err(f) => { panic!(f.to_string()) }
    };
    let output = matches.opt_str("o");
    let input = if !matches.free.is_empty() {
        matches.free[0].clone()
    } else {
        print_usage(&program, opts);
        return;
    };

    let path = Path::new(&input);
    let file = match elf::File::open_path(&path) {
        Ok(f) => f,
        Err(e) => panic!("Error: {:?}", e),
    };

    match output {
        None => {
            let mut out = io::stdout();
            do_work(&file, &mut out)
        }
        Some(name) => match File::create(Path::new(&name)) {
            Ok(mut f) => do_work(&file, &mut f),
            Err(e) => panic!("Error: {:?}", e),
        }
    };

}

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [-o OUTFILE] FILE", program);
    print!("{}", opts.usage(&brief));
}

fn get_section<'a>(input: &'a elf::File, name: &str) -> &'a elf::Section {
    match input.get_section(name) {
        Some(s) => {
            s
        },
        None => panic!("Failed to look up {} section", name),
    }
}

fn do_work(input: &elf::File, output: &mut Write) {
    let (rel_data_size, rel_data) = match input.sections.iter()
            .find(|section| section.shdr.name == ".rel.data".as_ref()) {
        Some(section) => {
            (section.shdr.size, section.data.as_ref())
        },
        None => (0 as u64, &[] as &[u8])

    };
    let text = get_section(input, ".text");
    let got = get_section(input, ".got");
    let data = get_section(input, ".data");
    let bss = get_section(input, ".bss");

    let load_info = LoadInfo {
        rel_data_size: rel_data_size as u32,
        entry_loc: (input.ehdr.entry ^ 0x80000000) as u32,
        init_data_loc: text.shdr.size as u32,
        init_data_size: (data.shdr.size + got.shdr.size) as u32,
        got_start_offset: 0,
        got_end_offset: got.shdr.size as u32,
        bss_start_offset: bss.shdr.addr as u32,
        bss_end_offset: (bss.shdr.addr + bss.shdr.size) as u32
    };

    let load_info_bytes : &[u8] = unsafe {
        std::mem::transmute(std::raw::Slice {
            data: &load_info,
            len: std::mem::size_of::<LoadInfo>()
        })
    };

    let mut total_len : u32 =
        (std::mem::size_of::<u32>() +
        load_info_bytes.as_ref().len() +
        rel_data.len() +
        text.data.len() +
        got.data.len() +
        data.data.len()) as u32;
    let pad = (4 - (total_len % 4)) % 4;
    total_len = total_len + pad;

    let total_len_buf : &[u8; 4] = unsafe {
        std::mem::transmute(&total_len)
    };

    let _ = output.write(total_len_buf);

    let _ = output.write(load_info_bytes);

    let _ = output.write(rel_data.as_ref());

    let _ = output.write(text.data.as_ref());
    let _ = output.write(got.data.as_ref());
    let _ = output.write(data.data.as_ref());

    for _ in 0..pad {
        let _ = output.write(&[0]);
    }
}

<commit_msg>Pad app binaries to nearest power of 2<commit_after>#![feature(raw)]
extern crate elf;
extern crate getopts;

use getopts::Options;
use std::env;
use std::path::Path;
use std::fs::File;
use std::io;
use std::io::Write;

#[repr(C,packed)]
#[derive(Debug)]
struct LoadInfo {
    rel_data_size: u32,
    entry_loc: u32,        /* Entry point for user application */
    init_data_loc: u32,    /* Data initialization information in flash */
    init_data_size: u32,   /* Size of initialization information */
    got_start_offset: u32, /* Offset in memory to start of GOT */
    got_end_offset: u32,   /* Offset in memory to end of GOT */
    bss_start_offset: u32, /* Offset in memory to start of BSS */
    bss_end_offset: u32    /* Offset in memory to end of BSS */
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();
    let mut opts = Options::new();
    opts.optopt("o", "", "set output file name", "OUTFILE");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => { m }
        Err(f) => { panic!(f.to_string()) }
    };
    let output = matches.opt_str("o");
    let input = if !matches.free.is_empty() {
        matches.free[0].clone()
    } else {
        print_usage(&program, opts);
        return;
    };

    let path = Path::new(&input);
    let file = match elf::File::open_path(&path) {
        Ok(f) => f,
        Err(e) => panic!("Error: {:?}", e),
    };

    match output {
        None => {
            let mut out = io::stdout();
            do_work(&file, &mut out)
        }
        Some(name) => match File::create(Path::new(&name)) {
            Ok(mut f) => do_work(&file, &mut f),
            Err(e) => panic!("Error: {:?}", e),
        }
    };

}

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [-o OUTFILE] FILE", program);
    print!("{}", opts.usage(&brief));
}

fn get_section<'a>(input: &'a elf::File, name: &str) -> &'a elf::Section {
    match input.get_section(name) {
        Some(s) => {
            s
        },
        None => panic!("Failed to look up {} section", name),
    }
}

fn do_work(input: &elf::File, output: &mut Write) {
    let (rel_data_size, rel_data) = match input.sections.iter()
            .find(|section| section.shdr.name == ".rel.data".as_ref()) {
        Some(section) => {
            (section.shdr.size, section.data.as_ref())
        },
        None => (0 as u64, &[] as &[u8])

    };
    let text = get_section(input, ".text");
    let got = get_section(input, ".got");
    let data = get_section(input, ".data");
    let bss = get_section(input, ".bss");

    let load_info = LoadInfo {
        rel_data_size: rel_data_size as u32,
        entry_loc: (input.ehdr.entry ^ 0x80000000) as u32,
        init_data_loc: text.shdr.size as u32,
        init_data_size: (data.shdr.size + got.shdr.size) as u32,
        got_start_offset: 0,
        got_end_offset: got.shdr.size as u32,
        bss_start_offset: bss.shdr.addr as u32,
        bss_end_offset: (bss.shdr.addr + bss.shdr.size) as u32
    };

    let load_info_bytes : &[u8] = unsafe {
        std::mem::transmute(std::raw::Slice {
            data: &load_info,
            len: std::mem::size_of::<LoadInfo>()
        })
    };

    let mut total_len : u32 =
        (std::mem::size_of::<u32>() +
        load_info_bytes.as_ref().len() +
        rel_data.len() +
        text.data.len() +
        got.data.len() +
        data.data.len()) as u32;

    let pad = if total_len.count_ones() > 1 {
        let power2len = 1 << (32 - total_len.leading_zeros());
        power2len - total_len
    } else {
        0
    };
    total_len = total_len + pad;

    let total_len_buf : &[u8; 4] = unsafe {
        std::mem::transmute(&total_len)
    };

    let _ = output.write(total_len_buf);

    let _ = output.write(load_info_bytes);

    let _ = output.write(rel_data.as_ref());

    let _ = output.write(text.data.as_ref());
    let _ = output.write(got.data.as_ref());
    let _ = output.write(data.data.as_ref());

    for _ in 0..pad {
        let _ = output.write(&[0]);
    }
}

<|endoftext|>


<commit_before>// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use crate::pci::{
    PciBarConfiguration, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType,
    PciInterruptPin, PciProgrammingInterface, PciSerialBusSubClass,
};
use crate::register_space::{Register, RegisterSpace};
use crate::usb::host_backend::host_backend_device_provider::HostBackendDeviceProvider;
use crate::usb::xhci::xhci::Xhci;
use crate::usb::xhci::xhci_backend_device_provider::XhciBackendDeviceProvider;
use crate::usb::xhci::xhci_regs::{init_xhci_mmio_space_and_regs, XhciRegs};
use crate::utils::FailHandle;
use resources::{Alloc, MmioType, SystemAllocator};
use std::mem;
use std::os::unix::io::RawFd;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use sys_util::{error, EventFd, GuestMemory};

const XHCI_BAR0_SIZE: u64 = 0x10000;

#[derive(Clone, Copy)]
enum UsbControllerProgrammingInterface {
    Usb3HostController = 0x30,
}

impl PciProgrammingInterface for UsbControllerProgrammingInterface {
    fn get_register_value(&self) -> u8 {
        *self as u8
    }
}

/// Use this handle to fail xhci controller.
pub struct XhciFailHandle {
    usbcmd: Register<u32>,
    usbsts: Register<u32>,
    xhci_failed: AtomicBool,
}

impl XhciFailHandle {
    pub fn new(regs: &XhciRegs) -> XhciFailHandle {
        XhciFailHandle {
            usbcmd: regs.usbcmd.clone(),
            usbsts: regs.usbsts.clone(),
            xhci_failed: AtomicBool::new(false),
        }
    }
}

impl FailHandle for XhciFailHandle {
    /// Fail this controller. Will set related registers and flip failed bool.
    fn fail(&self) {
        // set run/stop to stop.
        const USBCMD_STOPPED: u32 = 0;
        // Set host system error bit.
        const USBSTS_HSE: u32 = 1 << 2;
        self.usbcmd.set_value(USBCMD_STOPPED);
        self.usbsts.set_value(USBSTS_HSE);

        self.xhci_failed.store(true, Ordering::SeqCst);
        error!("xhci controller stopped working");
    }

    /// Returns true if xhci is already failed.
    fn failed(&self) -> bool {
        self.xhci_failed.load(Ordering::SeqCst)
    }
}

// Xhci controller should be created with backend device provider. Then irq should be assigned
// before initialized. We are not making `failed` as a state here to optimize performance. Cause we
// need to set failed in other threads.
enum XhciControllerState {
    Unknown,
    Created {
        device_provider: HostBackendDeviceProvider,
    },
    IrqAssigned {
        device_provider: HostBackendDeviceProvider,
        irq_evt: EventFd,
        irq_resample_evt: EventFd,
    },
    Initialized {
        mmio: RegisterSpace,
        // Xhci init could fail.
        #[allow(dead_code)]
        xhci: Option<Arc<Xhci>>,
        fail_handle: Arc<dyn FailHandle>,
    },
}

/// xHCI PCI interface implementation.
pub struct XhciController {
    config_regs: PciConfiguration,
    pci_bus_dev: Option<(u8, u8)>,
    mem: GuestMemory,
    bar0: u64, // bar0 in config_regs will be changed by guest. Not sure why.
    state: XhciControllerState,
}

impl XhciController {
    /// Create new xhci controller.
    pub fn new(mem: GuestMemory, usb_provider: HostBackendDeviceProvider) -> Self {
        let config_regs = PciConfiguration::new(
            0x01b73, // fresco logic, (google = 0x1ae0)
            0x1000,  // fresco logic pdk. This chip has broken msi. See kernel xhci-pci.c
            PciClassCode::SerialBusController,
            &PciSerialBusSubClass::USB,
            Some(&UsbControllerProgrammingInterface::Usb3HostController),
            PciHeaderType::Device,
            0,
            0,
        );
        XhciController {
            config_regs,
            pci_bus_dev: None,
            mem,
            bar0: 0,
            state: XhciControllerState::Created {
                device_provider: usb_provider,
            },
        }
    }

    /// Init xhci controller when it's forked.
    pub fn init_when_forked(&mut self) {
        match mem::replace(&mut self.state, XhciControllerState::Unknown) {
            XhciControllerState::IrqAssigned {
                device_provider,
                irq_evt,
                irq_resample_evt,
            } => {
                let (mmio, regs) = init_xhci_mmio_space_and_regs();
                let fail_handle: Arc<dyn FailHandle> = Arc::new(XhciFailHandle::new(&regs));
                let xhci = match Xhci::new(
                    fail_handle.clone(),
                    self.mem.clone(),
                    device_provider,
                    irq_evt,
                    irq_resample_evt,
                    regs,
                ) {
                    Ok(xhci) => Some(xhci),
                    Err(_) => {
                        error!("fail to init xhci");
                        fail_handle.fail();
                        return;
                    }
                };

                self.state = XhciControllerState::Initialized {
                    mmio,
                    xhci,
                    fail_handle,
                }
            }
            _ => {
                error!("xhci controller is in a wrong state");
            }
        }
    }
}

impl PciDevice for XhciController {
    fn debug_label(&self) -> String {
        "xhci controller".to_owned()
    }

    fn assign_bus_dev(&mut self, bus: u8, device: u8) {
        self.pci_bus_dev = Some((bus, device));
    }

    fn keep_fds(&self) -> Vec<RawFd> {
        match &self.state {
            XhciControllerState::Created { device_provider } => device_provider.keep_fds(),
            _ => {
                error!("xhci controller is in a wrong state");
                vec![]
            }
        }
    }

    fn assign_irq(
        &mut self,
        irq_evt: EventFd,
        irq_resample_evt: EventFd,
        irq_num: u32,
        irq_pin: PciInterruptPin,
    ) {
        match mem::replace(&mut self.state, XhciControllerState::Unknown) {
            XhciControllerState::Created { device_provider } => {
                self.config_regs.set_irq(irq_num as u8, irq_pin);
                self.state = XhciControllerState::IrqAssigned {
                    device_provider,
                    irq_evt,
                    irq_resample_evt,
                }
            }
            _ => {
                error!("xhci controller is in a wrong state");
            }
        }
    }

    fn allocate_io_bars(
        &mut self,
        resources: &mut SystemAllocator,
    ) -> std::result::Result<Vec<(u64, u64)>, PciDeviceError> {
        let (bus, dev) = self
            .pci_bus_dev
            .expect("assign_bus_dev must be called prior to allocate_io_bars");
        // xHCI spec 5.2.1.
        let bar0_addr = resources
            .mmio_allocator(MmioType::Low)
            .allocate_with_align(
                XHCI_BAR0_SIZE,
                Alloc::PciBar { bus, dev, bar: 0 },
                "xhci_bar0".to_string(),
                XHCI_BAR0_SIZE,
            )
            .map_err(|e| PciDeviceError::IoAllocationFailed(XHCI_BAR0_SIZE, e))?;
        let bar0_config = PciBarConfiguration::default()
            .set_register_index(0)
            .set_address(bar0_addr)
            .set_size(XHCI_BAR0_SIZE);
        self.config_regs
            .add_pci_bar(&bar0_config)
            .map_err(|e| PciDeviceError::IoRegistrationFailed(bar0_addr, e))?;
        self.bar0 = bar0_addr;
        Ok(vec![(bar0_addr, XHCI_BAR0_SIZE)])
    }

    fn read_config_register(&self, reg_idx: usize) -> u32 {
        self.config_regs.read_reg(reg_idx)
    }

    fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]) {
        (&mut self.config_regs).write_reg(reg_idx, offset, data)
    }

    fn read_bar(&mut self, addr: u64, data: &mut [u8]) {
        let bar0 = self.bar0;
        if addr < bar0 || addr > bar0 + XHCI_BAR0_SIZE {
            return;
        }
        match &self.state {
            XhciControllerState::Initialized { mmio, .. } => {
                // Read bar would still work even if it's already failed.
                mmio.read(addr - bar0, data);
            }
            _ => {
                error!("xhci controller is in a wrong state");
            }
        }
    }

    fn write_bar(&mut self, addr: u64, data: &[u8]) {
        let bar0 = self.bar0;
        if addr < bar0 || addr > bar0 + XHCI_BAR0_SIZE {
            return;
        }
        match &self.state {
            XhciControllerState::Initialized {
                mmio, fail_handle, ..
            } => {
                if !fail_handle.failed() {
                    mmio.write(addr - bar0, data);
                }
            }
            _ => {
                error!("xhci controller is in a wrong state");
            }
        }
    }

    fn on_device_sandboxed(&mut self) {
        self.init_when_forked();
    }
}
<commit_msg>devices: xhci: use get_bar_addr() to find BAR 0<commit_after>// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use crate::pci::{
    PciBarConfiguration, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType,
    PciInterruptPin, PciProgrammingInterface, PciSerialBusSubClass,
};
use crate::register_space::{Register, RegisterSpace};
use crate::usb::host_backend::host_backend_device_provider::HostBackendDeviceProvider;
use crate::usb::xhci::xhci::Xhci;
use crate::usb::xhci::xhci_backend_device_provider::XhciBackendDeviceProvider;
use crate::usb::xhci::xhci_regs::{init_xhci_mmio_space_and_regs, XhciRegs};
use crate::utils::FailHandle;
use resources::{Alloc, MmioType, SystemAllocator};
use std::mem;
use std::os::unix::io::RawFd;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use sys_util::{error, EventFd, GuestMemory};

const XHCI_BAR0_SIZE: u64 = 0x10000;

#[derive(Clone, Copy)]
enum UsbControllerProgrammingInterface {
    Usb3HostController = 0x30,
}

impl PciProgrammingInterface for UsbControllerProgrammingInterface {
    fn get_register_value(&self) -> u8 {
        *self as u8
    }
}

/// Use this handle to fail xhci controller.
pub struct XhciFailHandle {
    usbcmd: Register<u32>,
    usbsts: Register<u32>,
    xhci_failed: AtomicBool,
}

impl XhciFailHandle {
    pub fn new(regs: &XhciRegs) -> XhciFailHandle {
        XhciFailHandle {
            usbcmd: regs.usbcmd.clone(),
            usbsts: regs.usbsts.clone(),
            xhci_failed: AtomicBool::new(false),
        }
    }
}

impl FailHandle for XhciFailHandle {
    /// Fail this controller. Will set related registers and flip failed bool.
    fn fail(&self) {
        // set run/stop to stop.
        const USBCMD_STOPPED: u32 = 0;
        // Set host system error bit.
        const USBSTS_HSE: u32 = 1 << 2;
        self.usbcmd.set_value(USBCMD_STOPPED);
        self.usbsts.set_value(USBSTS_HSE);

        self.xhci_failed.store(true, Ordering::SeqCst);
        error!("xhci controller stopped working");
    }

    /// Returns true if xhci is already failed.
    fn failed(&self) -> bool {
        self.xhci_failed.load(Ordering::SeqCst)
    }
}

// Xhci controller should be created with backend device provider. Then irq should be assigned
// before initialized. We are not making `failed` as a state here to optimize performance. Cause we
// need to set failed in other threads.
enum XhciControllerState {
    Unknown,
    Created {
        device_provider: HostBackendDeviceProvider,
    },
    IrqAssigned {
        device_provider: HostBackendDeviceProvider,
        irq_evt: EventFd,
        irq_resample_evt: EventFd,
    },
    Initialized {
        mmio: RegisterSpace,
        // Xhci init could fail.
        #[allow(dead_code)]
        xhci: Option<Arc<Xhci>>,
        fail_handle: Arc<dyn FailHandle>,
    },
}

/// xHCI PCI interface implementation.
pub struct XhciController {
    config_regs: PciConfiguration,
    pci_bus_dev: Option<(u8, u8)>,
    mem: GuestMemory,
    state: XhciControllerState,
}

impl XhciController {
    /// Create new xhci controller.
    pub fn new(mem: GuestMemory, usb_provider: HostBackendDeviceProvider) -> Self {
        let config_regs = PciConfiguration::new(
            0x01b73, // fresco logic, (google = 0x1ae0)
            0x1000,  // fresco logic pdk. This chip has broken msi. See kernel xhci-pci.c
            PciClassCode::SerialBusController,
            &PciSerialBusSubClass::USB,
            Some(&UsbControllerProgrammingInterface::Usb3HostController),
            PciHeaderType::Device,
            0,
            0,
        );
        XhciController {
            config_regs,
            pci_bus_dev: None,
            mem,
            state: XhciControllerState::Created {
                device_provider: usb_provider,
            },
        }
    }

    /// Init xhci controller when it's forked.
    pub fn init_when_forked(&mut self) {
        match mem::replace(&mut self.state, XhciControllerState::Unknown) {
            XhciControllerState::IrqAssigned {
                device_provider,
                irq_evt,
                irq_resample_evt,
            } => {
                let (mmio, regs) = init_xhci_mmio_space_and_regs();
                let fail_handle: Arc<dyn FailHandle> = Arc::new(XhciFailHandle::new(&regs));
                let xhci = match Xhci::new(
                    fail_handle.clone(),
                    self.mem.clone(),
                    device_provider,
                    irq_evt,
                    irq_resample_evt,
                    regs,
                ) {
                    Ok(xhci) => Some(xhci),
                    Err(_) => {
                        error!("fail to init xhci");
                        fail_handle.fail();
                        return;
                    }
                };

                self.state = XhciControllerState::Initialized {
                    mmio,
                    xhci,
                    fail_handle,
                }
            }
            _ => {
                error!("xhci controller is in a wrong state");
            }
        }
    }
}

impl PciDevice for XhciController {
    fn debug_label(&self) -> String {
        "xhci controller".to_owned()
    }

    fn assign_bus_dev(&mut self, bus: u8, device: u8) {
        self.pci_bus_dev = Some((bus, device));
    }

    fn keep_fds(&self) -> Vec<RawFd> {
        match &self.state {
            XhciControllerState::Created { device_provider } => device_provider.keep_fds(),
            _ => {
                error!("xhci controller is in a wrong state");
                vec![]
            }
        }
    }

    fn assign_irq(
        &mut self,
        irq_evt: EventFd,
        irq_resample_evt: EventFd,
        irq_num: u32,
        irq_pin: PciInterruptPin,
    ) {
        match mem::replace(&mut self.state, XhciControllerState::Unknown) {
            XhciControllerState::Created { device_provider } => {
                self.config_regs.set_irq(irq_num as u8, irq_pin);
                self.state = XhciControllerState::IrqAssigned {
                    device_provider,
                    irq_evt,
                    irq_resample_evt,
                }
            }
            _ => {
                error!("xhci controller is in a wrong state");
            }
        }
    }

    fn allocate_io_bars(
        &mut self,
        resources: &mut SystemAllocator,
    ) -> std::result::Result<Vec<(u64, u64)>, PciDeviceError> {
        let (bus, dev) = self
            .pci_bus_dev
            .expect("assign_bus_dev must be called prior to allocate_io_bars");
        // xHCI spec 5.2.1.
        let bar0_addr = resources
            .mmio_allocator(MmioType::Low)
            .allocate_with_align(
                XHCI_BAR0_SIZE,
                Alloc::PciBar { bus, dev, bar: 0 },
                "xhci_bar0".to_string(),
                XHCI_BAR0_SIZE,
            )
            .map_err(|e| PciDeviceError::IoAllocationFailed(XHCI_BAR0_SIZE, e))?;
        let bar0_config = PciBarConfiguration::default()
            .set_register_index(0)
            .set_address(bar0_addr)
            .set_size(XHCI_BAR0_SIZE);
        self.config_regs
            .add_pci_bar(&bar0_config)
            .map_err(|e| PciDeviceError::IoRegistrationFailed(bar0_addr, e))?;
        Ok(vec![(bar0_addr, XHCI_BAR0_SIZE)])
    }

    fn read_config_register(&self, reg_idx: usize) -> u32 {
        self.config_regs.read_reg(reg_idx)
    }

    fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]) {
        (&mut self.config_regs).write_reg(reg_idx, offset, data)
    }

    fn read_bar(&mut self, addr: u64, data: &mut [u8]) {
        let bar0 = self.config_regs.get_bar_addr(0);
        if addr < bar0 || addr > bar0 + XHCI_BAR0_SIZE {
            return;
        }
        match &self.state {
            XhciControllerState::Initialized { mmio, .. } => {
                // Read bar would still work even if it's already failed.
                mmio.read(addr - bar0, data);
            }
            _ => {
                error!("xhci controller is in a wrong state");
            }
        }
    }

    fn write_bar(&mut self, addr: u64, data: &[u8]) {
        let bar0 = self.config_regs.get_bar_addr(0);
        if addr < bar0 || addr > bar0 + XHCI_BAR0_SIZE {
            return;
        }
        match &self.state {
            XhciControllerState::Initialized {
                mmio, fail_handle, ..
            } => {
                if !fail_handle.failed() {
                    mmio.write(addr - bar0, data);
                }
            }
            _ => {
                error!("xhci controller is in a wrong state");
            }
        }
    }

    fn on_device_sandboxed(&mut self) {
        self.init_when_forked();
    }
}
<|endoftext|>


<commit_before>use std::collections::HashSet;
use std::collections::VecDeque;
use std::io;

mod digits;
mod errors;
mod intcode;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct Position {
    x: isize,
    y: isize,
}

impl Position {
    const ZERO: Self = Self { x: 0, y: 0 };
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum Direction {
    North,
    South,
    West,
    East,
}

impl Direction {
    fn all() -> Vec<Self> {
        vec![Self::North, Self::East, Self::South, Self::West]
    }

    fn except(excluded: &Self) -> Vec<Self> {
        match excluded {
            Self::North => vec![Self::East, Self::South, Self::West],
            Self::East => vec![Self::North, Self::South, Self::West],
            Self::South => vec![Self::North, Self::East, Self::West],
            Self::West => vec![Self::North, Self::East, Self::South],
        }
    }

    fn opposite(&self) -> Self {
        match self {
            Self::North => Self::South,
            Self::East => Self::West,
            Self::South => Self::North,
            Self::West => Self::East,
        }
    }

    fn to_code(&self) -> intcode::Code {
        match self {
            Self::North => 1,
            Self::South => 2,
            Self::West => 3,
            Self::East => 4,
        }
    }

    fn advance(&self, position: &Position) -> Position {
        match self {
            Self::North => Position {
                x: position.x,
                y: position.y - 1,
            },
            Self::South => Position {
                x: position.x,
                y: position.y + 1,
            },
            Self::West => Position {
                x: position.x - 1,
                y: position.y,
            },
            Self::East => Position {
                x: position.x + 1,
                y: position.y,
            },
        }
    }
}

type Path = VecDeque<Direction>;

#[derive(Debug, Clone, Copy, PartialEq)]
enum RunResult {
    ValidPath,
    HitWall,
    Found,
}

impl RunResult {
    fn successful(&self) -> bool {
        match self {
            Self::ValidPath => true,
            Self::HitWall => false,
            Self::Found => true,
        }
    }
}

#[derive(Debug, Clone)]
struct State {
    path: Path,
    result: Option<RunResult>,
}

fn main() -> io::Result<()> {
    let mut code = String::new();
    io::stdin().read_line(&mut code)?;
    let program = intcode::parse(&code)?;

    let mut successes: HashSet<Path> = HashSet::new();
    let mut found: Option<Path> = None;
    Direction::all()
        .into_iter()
        .map(|direction| {
            let mut path = VecDeque::new();
            path.push_back(direction);
            path
        })
        .for_each(|path| {
            let result = run(program.clone(), path.clone());
            if result == RunResult::Found {
                found = Some(path);
            } else if result.successful() {
                successes.insert(path);
            }
        });
    while found == None {
        let last_successes: Vec<Path> = successes.drain().collect();
        last_successes.into_iter().for_each(|success| {
            Direction::except(&success[success.len() - 1].opposite())
                .into_iter()
                .map(|direction| {
                    let mut path = success.clone();
                    path.push_back(direction);
                    path
                })
                .filter(|path| !loops(path))
                .for_each(|path| {
                    let result = run(program.clone(), path.clone());
                    if result == RunResult::Found {
                        found = Some(path);
                    } else if result.successful() {
                        successes.insert(path);
                    }
                });
        })
    }

    println!("{}", found.unwrap().len());

    Ok(())
}

fn loops(path: &Path) -> bool {
    let mut positions: HashSet<Position> = HashSet::new();
    let mut position = Position::ZERO;
    positions.insert(position);
    for direction in path {
        position = direction.advance(&position);
        if positions.contains(&position) {
            return true;
        }
        positions.insert(position);
    }
    false
}

fn run(program: intcode::Program, mut path: Path) -> RunResult {
    let first = path.pop_front().unwrap();
    let device = intcode::CooperativeDevice::new(
        State { path, result: None },
        vec![first.to_code()],
        |mut state, output| match output {
            0 => {
                state.result = Some(RunResult::HitWall);
                (state, vec![])
            }
            1 => match state.path.pop_front() {
                None => {
                    state.result = Some(RunResult::ValidPath);
                    (state, vec![])
                }
                Some(first) => (state, vec![first.to_code()]),
            },
            2 => {
                if !state.path.is_empty() {
                    panic!("Found it, but we weren't done.\n{:?}", state);
                }
                state.result = Some(RunResult::Found);
                (state, vec![])
            }
            _ => panic!("Invalid output: {}", output),
        },
    );
    let (_, state) = intcode::evaluate(program.clone(), device);
    state.result.unwrap()
}
<commit_msg>2019: Day 15, part 1: performance improvements.<commit_after>use std::collections::HashSet;
use std::collections::VecDeque;
use std::io;

mod digits;
mod errors;
mod intcode;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct Position {
    x: isize,
    y: isize,
}

impl Position {
    const ZERO: Self = Self { x: 0, y: 0 };
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum Direction {
    North,
    South,
    West,
    East,
}

impl Direction {
    fn all() -> Vec<Self> {
        vec![Self::North, Self::East, Self::South, Self::West]
    }

    fn except(excluded: &Self) -> Vec<Self> {
        match excluded {
            Self::North => vec![Self::East, Self::South, Self::West],
            Self::East => vec![Self::North, Self::South, Self::West],
            Self::South => vec![Self::North, Self::East, Self::West],
            Self::West => vec![Self::North, Self::East, Self::South],
        }
    }

    fn opposite(&self) -> Self {
        match self {
            Self::North => Self::South,
            Self::East => Self::West,
            Self::South => Self::North,
            Self::West => Self::East,
        }
    }

    fn to_code(&self) -> intcode::Code {
        match self {
            Self::North => 1,
            Self::South => 2,
            Self::West => 3,
            Self::East => 4,
        }
    }

    fn advance(&self, position: &Position) -> Position {
        match self {
            Self::North => Position {
                x: position.x,
                y: position.y - 1,
            },
            Self::South => Position {
                x: position.x,
                y: position.y + 1,
            },
            Self::West => Position {
                x: position.x - 1,
                y: position.y,
            },
            Self::East => Position {
                x: position.x + 1,
                y: position.y,
            },
        }
    }
}

type Path = VecDeque<Direction>;

#[derive(Debug, Clone, Copy, PartialEq)]
enum RunResult {
    ValidPath,
    HitWall,
    Found,
}

impl RunResult {
    fn successful(&self) -> bool {
        match self {
            Self::ValidPath => true,
            Self::HitWall => false,
            Self::Found => true,
        }
    }
}

#[derive(Debug, Clone)]
struct State {
    path: Path,
    result: Option<RunResult>,
}

fn main() -> io::Result<()> {
    let mut code = String::new();
    io::stdin().read_line(&mut code)?;
    let program = intcode::parse(&code)?;

    let mut successes: HashSet<Path> = HashSet::new();
    successes.insert(VecDeque::new());
    let mut found: Option<Path> = None;
    while found.is_none() {
        let last_successes: Vec<Path> = successes.drain().collect();
        for (path, result) in step(&program, last_successes) {
            if result == RunResult::Found {
                found = Some(path);
            } else if result.successful() {
                successes.insert(path);
            }
        }
    }

    println!("{}", found.unwrap().len());

    Ok(())
}

fn step(program: &intcode::Program, successes: Vec<Path>) -> Vec<(Path, RunResult)> {
    successes
        .iter()
        .flat_map(|success| {
            let directions = match success.back() {
                None => Direction::all(),
                Some(last) => Direction::except(&last.opposite()),
            };
            directions
                .into_iter()
                .map(move |direction| {
                    let mut path = success.clone();
                    path.push_back(direction);
                    path
                })
                .filter(|path| !loops(path))
                .map(|path| {
                    let result = run(program.clone(), path.clone());
                    (path, result)
                })
        })
        .collect()
}

fn loops(path: &Path) -> bool {
    let mut positions: HashSet<Position> = HashSet::new();
    let mut position = Position::ZERO;
    positions.insert(position);
    for direction in path {
        position = direction.advance(&position);
        if positions.contains(&position) {
            return true;
        }
        positions.insert(position);
    }
    false
}

fn run(program: intcode::Program, mut path: Path) -> RunResult {
    let first = path.pop_front().unwrap();
    let device = intcode::CooperativeDevice::new(
        State { path, result: None },
        vec![first.to_code()],
        |mut state, output| match output {
            0 => {
                state.result = Some(RunResult::HitWall);
                (state, vec![])
            }
            1 => match state.path.pop_front() {
                None => {
                    state.result = Some(RunResult::ValidPath);
                    (state, vec![])
                }
                Some(first) => (state, vec![first.to_code()]),
            },
            2 => {
                if !state.path.is_empty() {
                    panic!("Found it, but we weren't done.\n{:?}", state);
                }
                state.result = Some(RunResult::Found);
                (state, vec![])
            }
            _ => panic!("Invalid output: {}", output),
        },
    );
    let (_, state) = intcode::evaluate(program.clone(), device);
    state.result.unwrap()
}
<|endoftext|>


<commit_before>///! File system utilities, particularly functionality that requires
///! alternative implementations for alternative operating systems.
use eyre::{eyre, Result};
use std::{fs, io, os, path::Path};

/// Set permissions on a file
#[allow(unused_variables)]
pub fn set_perms<File: AsRef<Path>>(path: File, mode: u32) -> Result<()> {
    #[cfg(any(target_os = "linux", target_os = "macos"))]
    {
        use os::unix::fs::PermissionsExt;
        fs::set_permissions(path, fs::Permissions::from_mode(mode))?;
    }

    Ok(())
}

/// Create a symbolic (soft) link to a file
pub fn symlink_file<Original: AsRef<Path>, Link: AsRef<Path>>(
    original: Original,
    link: Link,
) -> Result<()> {
    #[cfg(any(target_os = "linux", target_os = "macos"))]
    os::unix::fs::symlink(original, link)?;

    #[cfg(target_os = "windows")]
    os::windows::fs::symlink_file(original, link)?;

    Ok(())
}

/// Create a symbolic (soft) link to a directory
pub fn symlink_dir<Original: AsRef<Path>, Link: AsRef<Path>>(
    original: Original,
    link: Link,
) -> Result<()> {
    #[cfg(any(target_os = "linux", target_os = "macos"))]
    os::unix::fs::symlink(original, link)?;

    #[cfg(target_os = "windows")]
    os::windows::fs::symlink_dir(original, link)?;

    Ok(())
}

/// Copy a file or directory if it exists
pub fn copy_if_exists(src: impl AsRef<Path>, dest: impl AsRef<Path>) -> Result<()> {
    let src = src.as_ref();
    if src.exists() {
        if src.is_dir() {
            copy_dir_all(src, dest)?;
        } else {
            fs::copy(src, dest)?;
        }
    }
    Ok(())
}

/// Clear a directory
pub fn clear_dir_all(dir: impl AsRef<Path>) -> Result<()> {
    fs::remove_dir_all(&dir)?;
    fs::create_dir_all(&dir)?;
    Ok(())
}

/// Recursively copy a directory to another
pub fn copy_dir_all(src: impl AsRef<Path>, dest: impl AsRef<Path>) -> Result<()> {
    fs::create_dir_all(&dest)?;
    for entry in fs::read_dir(src)? {
        let entry = entry?;
        if entry.file_type()?.is_dir() {
            copy_dir_all(entry.path(), dest.as_ref().join(entry.file_name()))?;
        } else if let Err(error) = fs::copy(entry.path(), dest.as_ref().join(entry.file_name())) {
            // Ignore "the source path is neither a regular file nor a symlink to a regular file" errors
            if !matches!(error.kind(), io::ErrorKind::InvalidInput) {
                return Err(eyre!(error));
            }
        }
    }
    Ok(())
}

/// Move a directory
///
/// This is a lot less efficient than `std::fs::rename` but will work across mounts
pub fn move_dir_all(src: impl AsRef<Path>, dest: impl AsRef<Path>) -> Result<()> {
    copy_dir_all(&src, &dest)?;
    fs::remove_dir_all(&src)?;
    Ok(())
}
<commit_msg>chore(Filesystem utilities): Add `remove_if_exists` function<commit_after>///! File system utilities, particularly functionality that requires
///! alternative implementations for alternative operating systems.
use eyre::{eyre, Result};
use std::{fs, io, os, path::Path};

/// Set permissions on a file
#[allow(unused_variables)]
pub fn set_perms<File: AsRef<Path>>(path: File, mode: u32) -> Result<()> {
    #[cfg(any(target_os = "linux", target_os = "macos"))]
    {
        use os::unix::fs::PermissionsExt;
        fs::set_permissions(path, fs::Permissions::from_mode(mode))?;
    }

    Ok(())
}

/// Create a symbolic (soft) link to a file
pub fn symlink_file<Original: AsRef<Path>, Link: AsRef<Path>>(
    original: Original,
    link: Link,
) -> Result<()> {
    #[cfg(any(target_os = "linux", target_os = "macos"))]
    os::unix::fs::symlink(original, link)?;

    #[cfg(target_os = "windows")]
    os::windows::fs::symlink_file(original, link)?;

    Ok(())
}

/// Create a symbolic (soft) link to a directory
pub fn symlink_dir<Original: AsRef<Path>, Link: AsRef<Path>>(
    original: Original,
    link: Link,
) -> Result<()> {
    #[cfg(any(target_os = "linux", target_os = "macos"))]
    os::unix::fs::symlink(original, link)?;

    #[cfg(target_os = "windows")]
    os::windows::fs::symlink_dir(original, link)?;

    Ok(())
}

/// Remove a file or directory if it exists
pub fn remove_if_exists(path: impl AsRef<Path>) -> Result<()> {
    let path = path.as_ref();
    if path.exists() {
        if path.is_dir() {
            fs::remove_dir_all(path)?;
        } else {
            fs::remove_file(path)?;
        }
    }
    Ok(())
}

/// Copy a file or directory if it exists
pub fn copy_if_exists(src: impl AsRef<Path>, dest: impl AsRef<Path>) -> Result<()> {
    let src = src.as_ref();
    if src.exists() {
        if src.is_dir() {
            copy_dir_all(src, dest)?;
        } else {
            fs::copy(src, dest)?;
        }
    }
    Ok(())
}

/// Clear a directory
pub fn clear_dir_all(dir: impl AsRef<Path>) -> Result<()> {
    fs::remove_dir_all(&dir)?;
    fs::create_dir_all(&dir)?;
    Ok(())
}

/// Recursively copy a directory to another
pub fn copy_dir_all(src: impl AsRef<Path>, dest: impl AsRef<Path>) -> Result<()> {
    fs::create_dir_all(&dest)?;
    for entry in fs::read_dir(src)? {
        let entry = entry?;
        if entry.file_type()?.is_dir() {
            copy_dir_all(entry.path(), dest.as_ref().join(entry.file_name()))?;
        } else if let Err(error) = fs::copy(entry.path(), dest.as_ref().join(entry.file_name())) {
            // Ignore "the source path is neither a regular file nor a symlink to a regular file" errors
            if !matches!(error.kind(), io::ErrorKind::InvalidInput) {
                return Err(eyre!(error));
            }
        }
    }
    Ok(())
}

/// Move a directory
///
/// This is a lot less efficient than `std::fs::rename` but will work across mounts
pub fn move_dir_all(src: impl AsRef<Path>, dest: impl AsRef<Path>) -> Result<()> {
    copy_dir_all(&src, &dest)?;
    fs::remove_dir_all(&src)?;
    Ok(())
}
<|endoftext|>


<commit_before>//! Handling of `preserveAspectRatio` values
//!
//! This module handles `preserveAspectRatio` values [per the SVG specification][spec].
//! We have an [`AspectRatio`] struct which encapsulates such a value.
//!
//! ```
//! assert_eq! (AspectRatio::parse ("xMidYMid", ()),
//!             Ok (AspectRatio { defer: false,
//!                               align: Align::Aligned { align: AlignMode::XmidYmid,
//!                                                       fit: FitMode::Meet } }));
//! ```
//!
//! [`AspectRatio`]: struct.AspectRatio.html
//! [spec]: https://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute

use parsers::Parse;
use parsers::ParseError;
use error::*;

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum FitMode {
    Meet,
    Slice
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum AlignMode {
    XminYmin,
    XmidYmin,
    XmaxYmin,
    XminYmid,
    XmidYmid,
    XmaxYmid,
    XminYmax,
    XmidYmax,
    XmaxYmax
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Align {
    None,
    Aligned {
        align: AlignMode,
        fit: FitMode
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct AspectRatio {
    pub defer: bool,
    pub align: Align
}

enum Align1D {
    Min,
    Mid,
    Max
}

fn align_1d (a: Align1D, dest_pos: f64, dest_size: f64, obj_size: f64) -> f64 {
    match a {
        Align1D::Min => { dest_pos },
        Align1D::Mid => { dest_pos + (dest_size - obj_size) / 2.0 },
        Align1D::Max => { dest_pos + dest_size - obj_size }
    }
}

impl AspectRatio {
    pub fn compute (&self,
                    object_width: f64,
                    object_height: f64,
                    dest_x: f64,
                    dest_y: f64,
                    dest_width: f64,
                    dest_height: f64) -> (f64, f64, f64, f64) {
        match self.align {
            Align::None => { (dest_x, dest_y, dest_width, dest_height) }

            Align::Aligned { align, fit } => {
                let w_factor = dest_width / object_width;
                let h_factor = dest_height / object_height;
                let factor: f64;

                match fit {
                    FitMode::Meet  => { factor = w_factor.min (h_factor); }
                    FitMode::Slice => { factor = w_factor.max (h_factor); }
                }

                let w = object_width * factor;
                let h = object_height * factor;

                let xalign: Align1D;
                let yalign: Align1D;

                match align {
                    AlignMode::XminYmin => { xalign = Align1D::Min; yalign = Align1D::Min; },
                    AlignMode::XminYmid => { xalign = Align1D::Min; yalign = Align1D::Mid; },
                    AlignMode::XminYmax => { xalign = Align1D::Min; yalign = Align1D::Max; },
                    AlignMode::XmidYmin => { xalign = Align1D::Mid; yalign = Align1D::Min; },
                    AlignMode::XmidYmid => { xalign = Align1D::Mid; yalign = Align1D::Mid; },
                    AlignMode::XmidYmax => { xalign = Align1D::Mid; yalign = Align1D::Max; },
                    AlignMode::XmaxYmin => { xalign = Align1D::Max; yalign = Align1D::Min; },
                    AlignMode::XmaxYmid => { xalign = Align1D::Max; yalign = Align1D::Mid; },
                    AlignMode::XmaxYmax => { xalign = Align1D::Max; yalign = Align1D::Max; }
                }

                let xpos = align_1d (xalign, dest_x, dest_width, w);
                let ypos = align_1d (yalign, dest_y, dest_height, h);

                (xpos, ypos, w, h)
            }
        }
    }
}

impl Default for Align {
    fn default () -> Align {
        Align::Aligned {
            align: AlignMode::XmidYmid,
            fit: FitMode::Meet
        }
    }
}

impl Default for AspectRatio {
    fn default () -> AspectRatio {
        AspectRatio {
            defer: false,
            align: Default::default ()
        }
    }
}

fn parse_align_mode (s: &str) -> Option<Align> {
    match s {
        "none"     => { Some (Align::None) },
        "xMinYMin" => { Some (Align::Aligned { align: AlignMode::XminYmin, fit: FitMode::Meet } ) },
        "xMidYMin" => { Some (Align::Aligned { align: AlignMode::XmidYmin, fit: FitMode::Meet } ) },
        "xMaxYMin" => { Some (Align::Aligned { align: AlignMode::XmaxYmin, fit: FitMode::Meet } ) },
        "xMinYMid" => { Some (Align::Aligned { align: AlignMode::XminYmid, fit: FitMode::Meet } ) },
        "xMidYMid" => { Some (Align::Aligned { align: AlignMode::XmidYmid, fit: FitMode::Meet } ) },
        "xMaxYMid" => { Some (Align::Aligned { align: AlignMode::XmaxYmid, fit: FitMode::Meet } ) },
        "xMinYMax" => { Some (Align::Aligned { align: AlignMode::XminYmax, fit: FitMode::Meet } ) },
        "xMidYMax" => { Some (Align::Aligned { align: AlignMode::XmidYmax, fit: FitMode::Meet } ) },
        "xMaxYMax" => { Some (Align::Aligned { align: AlignMode::XmaxYmax, fit: FitMode::Meet } ) },
        _          => { None }
    }
}

fn parse_fit_mode (s: &str) -> Option<FitMode> {
    match s {
        "meet"  => { Some (FitMode::Meet) },
        "slice" => { Some (FitMode::Slice) },
        _       => { None }
    }
}

enum ParseState {
    Defer,
    Align,
    Fit,
    Finished
}

fn make_err () -> AttributeError {
    AttributeError::Parse (ParseError::new ("expected \"[defer] <align> [meet | slice]\""))
}

impl Parse for AspectRatio {
    type Data = ();
    type Err = AttributeError;

    fn parse(s: &str, _: ()) -> Result<AspectRatio, AttributeError> {
        let mut defer = false;
        let mut align: Align = Default::default ();
        let mut fit_mode = FitMode::Meet;

        let mut state = ParseState::Defer;

        for v in s.split_whitespace () {

            match state {
                ParseState::Defer => {
                    if v == "defer" {
                        defer = true;
                        state = ParseState::Align;
                    } else if let Some (parsed_align) = parse_align_mode (v) {
                        align = parsed_align;
                        state = ParseState::Fit;
                    } else {
                        return Err(make_err ());
                    }
                },

                ParseState::Align => {
                    if let Some (parsed_align) = parse_align_mode (v) {
                        align = parsed_align;
                        state = ParseState::Fit;
                    } else {
                        return Err(make_err ());
                    }
                },

                ParseState::Fit => {
                    if let Some (parsed_fit) = parse_fit_mode (v) {
                        fit_mode = parsed_fit;
                        state = ParseState::Finished;
                    } else {
                        return Err(make_err ());
                    }
                },

                _ => {
                    return Err(make_err ());
                }
            }
        }

        // The string must match "[defer] <align> [meet | slice]".
        // Since the meet|slice is optional, we can end up in either
        // of the following states:
        match state {
            ParseState::Fit | ParseState::Finished => {},
            _ => { return Err(make_err ()); }
        }

        Ok (AspectRatio {
            defer: defer,
            align: match align {
                Align::None => { Align::None },
                Align::Aligned { align, .. } => {
                    Align::Aligned {
                        align: align,
                        fit: fit_mode
                    }
                }
            }
        })
    }
}

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

    #[test]
    fn parsing_invalid_strings_yields_error () {
        assert! (AspectRatio::parse ("", ()).is_err ());

        assert! (AspectRatio::parse ("defer", ()).is_err ());

        assert! (AspectRatio::parse ("defer foo", ()).is_err ());

        assert! (AspectRatio::parse ("defer xmidymid", ()).is_err ());

        assert! (AspectRatio::parse ("defer xMidYMid foo", ()).is_err ());

        assert! (AspectRatio::parse ("xmidymid", ()).is_err ());

        assert! (AspectRatio::parse ("xMidYMid foo", ()).is_err ());

        assert! (AspectRatio::parse ("defer xMidYMid meet foo", ()).is_err ());
    }

    #[test]
    fn parses_valid_strings () {
        assert_eq! (AspectRatio::parse ("defer none", ()),
                    Ok (AspectRatio { defer: true,
                                      align: Align::None }));

        assert_eq! (AspectRatio::parse ("xMidYMid", ()),
                    Ok (AspectRatio { defer: false,
                                      align: Align::Aligned { align: AlignMode::XmidYmid,
                                                              fit: FitMode::Meet } }));

        assert_eq! (AspectRatio::parse ("defer xMidYMid", ()),
                    Ok (AspectRatio { defer: true,
                                      align: Align::Aligned { align: AlignMode::XmidYmid,
                                                              fit: FitMode::Meet } }));

        assert_eq! (AspectRatio::parse ("defer xMinYMax", ()),
                    Ok (AspectRatio { defer: true,
                                      align: Align::Aligned { align: AlignMode::XminYmax,
                                                              fit: FitMode::Meet } }));

        assert_eq! (AspectRatio::parse ("defer xMaxYMid meet", ()),
                    Ok (AspectRatio { defer: true,
                                      align: Align::Aligned { align: AlignMode::XmaxYmid,
                                                              fit: FitMode::Meet } }));

        assert_eq! (AspectRatio::parse ("defer xMinYMax slice", ()),
                    Ok (AspectRatio { defer: true,
                                      align: Align::Aligned { align: AlignMode::XminYmax,
                                                              fit: FitMode::Slice } }));
    }

    #[test]
    fn aligns () {
        assert_eq! (AspectRatio::parse ("xMinYMin meet", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (0.0, 0.0, 0.1, 1.0));
        assert_eq! (AspectRatio::parse ("xMinYMin slice", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (0.0, 0.0, 10.0, 100.0));

        assert_eq! (AspectRatio::parse ("xMinYMid meet", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (0.0, 0.0, 0.1, 1.0));
        assert_eq! (AspectRatio::parse ("xMinYMid slice", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (0.0, -49.5, 10.0, 100.0));

        assert_eq! (AspectRatio::parse ("xMinYMax meet", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (0.0, 0.0, 0.1, 1.0));
        assert_eq! (AspectRatio::parse ("xMinYMax slice", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (0.0, -99.0, 10.0, 100.0));

        assert_eq! (AspectRatio::parse ("xMidYMin meet", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (4.95, 0.0, 0.1, 1.0));
        assert_eq! (AspectRatio::parse ("xMidYMin slice", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (0.0, 0.0, 10.0, 100.0));

        assert_eq! (AspectRatio::parse ("xMidYMid meet", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (4.95, 0.0, 0.1, 1.0));
        assert_eq! (AspectRatio::parse ("xMidYMid slice", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (0.0, -49.5, 10.0, 100.0));

        assert_eq! (AspectRatio::parse ("xMidYMax meet", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (4.95, 0.0, 0.1, 1.0));
        assert_eq! (AspectRatio::parse ("xMidYMax slice", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (0.0, -99.0, 10.0, 100.0));

        assert_eq! (AspectRatio::parse ("xMaxYMin meet", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (9.9, 0.0, 0.1, 1.0));
        assert_eq! (AspectRatio::parse ("xMaxYMin slice", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (0.0, 0.0, 10.0, 100.0));

        assert_eq! (AspectRatio::parse ("xMaxYMid meet", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (9.9, 0.0, 0.1, 1.0));
        assert_eq! (AspectRatio::parse ("xMaxYMid slice", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (0.0, -49.5, 10.0, 100.0));

        assert_eq! (AspectRatio::parse ("xMaxYMax meet", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (9.9, 0.0, 0.1, 1.0));
        assert_eq! (AspectRatio::parse ("xMaxYMax slice", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (0.0, -99.0, 10.0, 100.0));
    }
}
<commit_msg>cargo-clippy: derive(Copy) for Align1D<commit_after>//! Handling of `preserveAspectRatio` values
//!
//! This module handles `preserveAspectRatio` values [per the SVG specification][spec].
//! We have an [`AspectRatio`] struct which encapsulates such a value.
//!
//! ```
//! assert_eq! (AspectRatio::parse ("xMidYMid", ()),
//!             Ok (AspectRatio { defer: false,
//!                               align: Align::Aligned { align: AlignMode::XmidYmid,
//!                                                       fit: FitMode::Meet } }));
//! ```
//!
//! [`AspectRatio`]: struct.AspectRatio.html
//! [spec]: https://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute

use parsers::Parse;
use parsers::ParseError;
use error::*;

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum FitMode {
    Meet,
    Slice
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum AlignMode {
    XminYmin,
    XmidYmin,
    XmaxYmin,
    XminYmid,
    XmidYmid,
    XmaxYmid,
    XminYmax,
    XmidYmax,
    XmaxYmax
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Align {
    None,
    Aligned {
        align: AlignMode,
        fit: FitMode
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct AspectRatio {
    pub defer: bool,
    pub align: Align
}

#[derive(Debug, Copy, Clone)]
enum Align1D {
    Min,
    Mid,
    Max
}

fn align_1d (a: Align1D, dest_pos: f64, dest_size: f64, obj_size: f64) -> f64 {
    match a {
        Align1D::Min => { dest_pos },
        Align1D::Mid => { dest_pos + (dest_size - obj_size) / 2.0 },
        Align1D::Max => { dest_pos + dest_size - obj_size }
    }
}

impl AspectRatio {
    pub fn compute (&self,
                    object_width: f64,
                    object_height: f64,
                    dest_x: f64,
                    dest_y: f64,
                    dest_width: f64,
                    dest_height: f64) -> (f64, f64, f64, f64) {
        match self.align {
            Align::None => { (dest_x, dest_y, dest_width, dest_height) }

            Align::Aligned { align, fit } => {
                let w_factor = dest_width / object_width;
                let h_factor = dest_height / object_height;
                let factor: f64;

                match fit {
                    FitMode::Meet  => { factor = w_factor.min (h_factor); }
                    FitMode::Slice => { factor = w_factor.max (h_factor); }
                }

                let w = object_width * factor;
                let h = object_height * factor;

                let xalign: Align1D;
                let yalign: Align1D;

                match align {
                    AlignMode::XminYmin => { xalign = Align1D::Min; yalign = Align1D::Min; },
                    AlignMode::XminYmid => { xalign = Align1D::Min; yalign = Align1D::Mid; },
                    AlignMode::XminYmax => { xalign = Align1D::Min; yalign = Align1D::Max; },
                    AlignMode::XmidYmin => { xalign = Align1D::Mid; yalign = Align1D::Min; },
                    AlignMode::XmidYmid => { xalign = Align1D::Mid; yalign = Align1D::Mid; },
                    AlignMode::XmidYmax => { xalign = Align1D::Mid; yalign = Align1D::Max; },
                    AlignMode::XmaxYmin => { xalign = Align1D::Max; yalign = Align1D::Min; },
                    AlignMode::XmaxYmid => { xalign = Align1D::Max; yalign = Align1D::Mid; },
                    AlignMode::XmaxYmax => { xalign = Align1D::Max; yalign = Align1D::Max; }
                }

                let xpos = align_1d (xalign, dest_x, dest_width, w);
                let ypos = align_1d (yalign, dest_y, dest_height, h);

                (xpos, ypos, w, h)
            }
        }
    }
}

impl Default for Align {
    fn default () -> Align {
        Align::Aligned {
            align: AlignMode::XmidYmid,
            fit: FitMode::Meet
        }
    }
}

impl Default for AspectRatio {
    fn default () -> AspectRatio {
        AspectRatio {
            defer: false,
            align: Default::default ()
        }
    }
}

fn parse_align_mode (s: &str) -> Option<Align> {
    match s {
        "none"     => { Some (Align::None) },
        "xMinYMin" => { Some (Align::Aligned { align: AlignMode::XminYmin, fit: FitMode::Meet } ) },
        "xMidYMin" => { Some (Align::Aligned { align: AlignMode::XmidYmin, fit: FitMode::Meet } ) },
        "xMaxYMin" => { Some (Align::Aligned { align: AlignMode::XmaxYmin, fit: FitMode::Meet } ) },
        "xMinYMid" => { Some (Align::Aligned { align: AlignMode::XminYmid, fit: FitMode::Meet } ) },
        "xMidYMid" => { Some (Align::Aligned { align: AlignMode::XmidYmid, fit: FitMode::Meet } ) },
        "xMaxYMid" => { Some (Align::Aligned { align: AlignMode::XmaxYmid, fit: FitMode::Meet } ) },
        "xMinYMax" => { Some (Align::Aligned { align: AlignMode::XminYmax, fit: FitMode::Meet } ) },
        "xMidYMax" => { Some (Align::Aligned { align: AlignMode::XmidYmax, fit: FitMode::Meet } ) },
        "xMaxYMax" => { Some (Align::Aligned { align: AlignMode::XmaxYmax, fit: FitMode::Meet } ) },
        _          => { None }
    }
}

fn parse_fit_mode (s: &str) -> Option<FitMode> {
    match s {
        "meet"  => { Some (FitMode::Meet) },
        "slice" => { Some (FitMode::Slice) },
        _       => { None }
    }
}

enum ParseState {
    Defer,
    Align,
    Fit,
    Finished
}

fn make_err () -> AttributeError {
    AttributeError::Parse (ParseError::new ("expected \"[defer] <align> [meet | slice]\""))
}

impl Parse for AspectRatio {
    type Data = ();
    type Err = AttributeError;

    fn parse(s: &str, _: ()) -> Result<AspectRatio, AttributeError> {
        let mut defer = false;
        let mut align: Align = Default::default ();
        let mut fit_mode = FitMode::Meet;

        let mut state = ParseState::Defer;

        for v in s.split_whitespace () {

            match state {
                ParseState::Defer => {
                    if v == "defer" {
                        defer = true;
                        state = ParseState::Align;
                    } else if let Some (parsed_align) = parse_align_mode (v) {
                        align = parsed_align;
                        state = ParseState::Fit;
                    } else {
                        return Err(make_err ());
                    }
                },

                ParseState::Align => {
                    if let Some (parsed_align) = parse_align_mode (v) {
                        align = parsed_align;
                        state = ParseState::Fit;
                    } else {
                        return Err(make_err ());
                    }
                },

                ParseState::Fit => {
                    if let Some (parsed_fit) = parse_fit_mode (v) {
                        fit_mode = parsed_fit;
                        state = ParseState::Finished;
                    } else {
                        return Err(make_err ());
                    }
                },

                _ => {
                    return Err(make_err ());
                }
            }
        }

        // The string must match "[defer] <align> [meet | slice]".
        // Since the meet|slice is optional, we can end up in either
        // of the following states:
        match state {
            ParseState::Fit | ParseState::Finished => {},
            _ => { return Err(make_err ()); }
        }

        Ok (AspectRatio {
            defer: defer,
            align: match align {
                Align::None => { Align::None },
                Align::Aligned { align, .. } => {
                    Align::Aligned {
                        align: align,
                        fit: fit_mode
                    }
                }
            }
        })
    }
}

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

    #[test]
    fn parsing_invalid_strings_yields_error () {
        assert! (AspectRatio::parse ("", ()).is_err ());

        assert! (AspectRatio::parse ("defer", ()).is_err ());

        assert! (AspectRatio::parse ("defer foo", ()).is_err ());

        assert! (AspectRatio::parse ("defer xmidymid", ()).is_err ());

        assert! (AspectRatio::parse ("defer xMidYMid foo", ()).is_err ());

        assert! (AspectRatio::parse ("xmidymid", ()).is_err ());

        assert! (AspectRatio::parse ("xMidYMid foo", ()).is_err ());

        assert! (AspectRatio::parse ("defer xMidYMid meet foo", ()).is_err ());
    }

    #[test]
    fn parses_valid_strings () {
        assert_eq! (AspectRatio::parse ("defer none", ()),
                    Ok (AspectRatio { defer: true,
                                      align: Align::None }));

        assert_eq! (AspectRatio::parse ("xMidYMid", ()),
                    Ok (AspectRatio { defer: false,
                                      align: Align::Aligned { align: AlignMode::XmidYmid,
                                                              fit: FitMode::Meet } }));

        assert_eq! (AspectRatio::parse ("defer xMidYMid", ()),
                    Ok (AspectRatio { defer: true,
                                      align: Align::Aligned { align: AlignMode::XmidYmid,
                                                              fit: FitMode::Meet } }));

        assert_eq! (AspectRatio::parse ("defer xMinYMax", ()),
                    Ok (AspectRatio { defer: true,
                                      align: Align::Aligned { align: AlignMode::XminYmax,
                                                              fit: FitMode::Meet } }));

        assert_eq! (AspectRatio::parse ("defer xMaxYMid meet", ()),
                    Ok (AspectRatio { defer: true,
                                      align: Align::Aligned { align: AlignMode::XmaxYmid,
                                                              fit: FitMode::Meet } }));

        assert_eq! (AspectRatio::parse ("defer xMinYMax slice", ()),
                    Ok (AspectRatio { defer: true,
                                      align: Align::Aligned { align: AlignMode::XminYmax,
                                                              fit: FitMode::Slice } }));
    }

    #[test]
    fn aligns () {
        assert_eq! (AspectRatio::parse ("xMinYMin meet", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (0.0, 0.0, 0.1, 1.0));
        assert_eq! (AspectRatio::parse ("xMinYMin slice", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (0.0, 0.0, 10.0, 100.0));

        assert_eq! (AspectRatio::parse ("xMinYMid meet", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (0.0, 0.0, 0.1, 1.0));
        assert_eq! (AspectRatio::parse ("xMinYMid slice", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (0.0, -49.5, 10.0, 100.0));

        assert_eq! (AspectRatio::parse ("xMinYMax meet", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (0.0, 0.0, 0.1, 1.0));
        assert_eq! (AspectRatio::parse ("xMinYMax slice", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (0.0, -99.0, 10.0, 100.0));

        assert_eq! (AspectRatio::parse ("xMidYMin meet", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (4.95, 0.0, 0.1, 1.0));
        assert_eq! (AspectRatio::parse ("xMidYMin slice", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (0.0, 0.0, 10.0, 100.0));

        assert_eq! (AspectRatio::parse ("xMidYMid meet", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (4.95, 0.0, 0.1, 1.0));
        assert_eq! (AspectRatio::parse ("xMidYMid slice", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (0.0, -49.5, 10.0, 100.0));

        assert_eq! (AspectRatio::parse ("xMidYMax meet", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (4.95, 0.0, 0.1, 1.0));
        assert_eq! (AspectRatio::parse ("xMidYMax slice", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (0.0, -99.0, 10.0, 100.0));

        assert_eq! (AspectRatio::parse ("xMaxYMin meet", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (9.9, 0.0, 0.1, 1.0));
        assert_eq! (AspectRatio::parse ("xMaxYMin slice", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (0.0, 0.0, 10.0, 100.0));

        assert_eq! (AspectRatio::parse ("xMaxYMid meet", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (9.9, 0.0, 0.1, 1.0));
        assert_eq! (AspectRatio::parse ("xMaxYMid slice", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (0.0, -49.5, 10.0, 100.0));

        assert_eq! (AspectRatio::parse ("xMaxYMax meet", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (9.9, 0.0, 0.1, 1.0));
        assert_eq! (AspectRatio::parse ("xMaxYMax slice", ()).unwrap().compute (1.0, 10.0, 0.0, 0.0, 10.0, 1.0), (0.0, -99.0, 10.0, 100.0));
    }
}
<|endoftext|>