<commit_before>use std::collections::{BTreeMap, HashMap};
use std::fs;
use std::io::{self, Write};
use std::thread::ThreadId;
use std::time::Instant;

use crate::session::config::{Options, OptLevel};

#[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)]
pub enum ProfileCategory {
    Parsing,
    Expansion,
    TypeChecking,
    BorrowChecking,
    Codegen,
    Linking,
    Other,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ProfilerEvent {
    QueryStart { query_name: &'static str, category: ProfileCategory, time: Instant },
    QueryEnd { query_name: &'static str, category: ProfileCategory, time: Instant },
    GenericActivityStart { category: ProfileCategory, time: Instant },
    GenericActivityEnd { category: ProfileCategory, time: Instant },
    QueryCacheHit { query_name: &'static str, category: ProfileCategory },
    QueryCount { query_name: &'static str, category: ProfileCategory, count: usize },
    IncrementalLoadResultStart { query_name: &'static str, time: Instant },
    IncrementalLoadResultEnd { query_name: &'static str, time: Instant },
    QueryBlockedStart { query_name: &'static str, category: ProfileCategory, time: Instant },
    QueryBlockedEnd { query_name: &'static str, category: ProfileCategory, time: Instant },
}

impl ProfilerEvent {
    fn is_start_event(&self) -> bool {
        use self::ProfilerEvent::*;

        match self {
            QueryStart { .. } |
            GenericActivityStart { .. } |
            IncrementalLoadResultStart { .. } |
            QueryBlockedStart { .. } => true,

            QueryEnd { .. } |
            GenericActivityEnd { .. } |
            QueryCacheHit { .. } |
            QueryCount { .. } |
            IncrementalLoadResultEnd { .. } |
            QueryBlockedEnd { .. } => false,
        }
    }
}

pub struct SelfProfiler {
    events: HashMap<ThreadId, Vec<ProfilerEvent>>,
}

struct CategoryResultData {
    query_times: BTreeMap<&'static str, u64>,
    query_cache_stats: BTreeMap<&'static str, (u64, u64)>, //(hits, total)
}

impl CategoryResultData {
    fn new() -> CategoryResultData {
        CategoryResultData {
            query_times: BTreeMap::new(),
            query_cache_stats: BTreeMap::new(),
        }
    }

    fn total_time(&self) -> u64 {
        let mut total = 0;
        for (_, time) in &self.query_times {
            total += time;
        }

        total
    }

    fn total_cache_data(&self) -> (u64, u64) {
        let (mut hits, mut total) = (0, 0);

        for (_, (h, t)) in &self.query_cache_stats {
            hits += h;
            total += t;
        }

        (hits, total)
    }
}

impl Default for CategoryResultData {
    fn default() -> CategoryResultData {
        CategoryResultData::new()
    }
}

struct CalculatedResults {
    categories: BTreeMap<ProfileCategory, CategoryResultData>,
    crate_name: Option<String>,
    optimization_level: OptLevel,
    incremental: bool,
    verbose: bool,
}

impl CalculatedResults {
    fn new() -> CalculatedResults {
        CalculatedResults {
            categories: BTreeMap::new(),
            crate_name: None,
            optimization_level: OptLevel::No,
            incremental: false,
            verbose: false,
        }
    }

    fn consolidate(mut cr1: CalculatedResults, cr2: CalculatedResults) -> CalculatedResults {
        for (category, data) in cr2.categories {
            let cr1_data = cr1.categories.entry(category).or_default();

            for (query, time) in data.query_times {
                *cr1_data.query_times.entry(query).or_default() += time;
            }

            for (query, (hits, total)) in data.query_cache_stats {
                let (h, t) = cr1_data.query_cache_stats.entry(query).or_insert((0, 0));
                *h += hits;
                *t += total;
            }
        }

        cr1
    }

    fn total_time(&self) -> u64 {
        let mut total = 0;

        for (_, data) in &self.categories {
            total += data.total_time();
        }

        total
    }

    fn with_options(mut self, opts: &Options) -> CalculatedResults {
        self.crate_name = opts.crate_name.clone();
        self.optimization_level = opts.optimize;
        self.incremental = opts.incremental.is_some();
        self.verbose = opts.debugging_opts.verbose;

        self
    }
}

fn time_between_ns(start: Instant, end: Instant) -> u64 {
    if start < end {
        let time = end - start;
        (time.as_secs() * 1_000_000_000) + (time.subsec_nanos() as u64)
    } else {
        debug!("time_between_ns: ignorning instance of end < start");
        0
    }
}

fn calculate_percent(numerator: u64, denominator: u64) -> f32 {
    if denominator > 0 {
        ((numerator as f32) / (denominator as f32)) * 100.0
    } else {
        0.0
    }
}

impl SelfProfiler {
    pub fn new() -> SelfProfiler {
        let mut profiler = SelfProfiler {
            events: HashMap::new(),
        };

        profiler.start_activity(ProfileCategory::Other);

        profiler
    }

    #[inline]
    pub fn start_activity(&mut self, category: ProfileCategory) {
        self.record(ProfilerEvent::GenericActivityStart {
            category,
            time: Instant::now(),
        })
    }

    #[inline]
    pub fn end_activity(&mut self, category: ProfileCategory) {
        self.record(ProfilerEvent::GenericActivityEnd {
            category,
            time: Instant::now(),
        })
    }

    #[inline]
    pub fn record_computed_queries(
        &mut self,
        query_name: &'static str,
        category: ProfileCategory,
        count: usize)
        {
        self.record(ProfilerEvent::QueryCount {
            query_name,
            category,
            count,
        })
    }

    #[inline]
    pub fn record_query_hit(&mut self, query_name: &'static str, category: ProfileCategory) {
        self.record(ProfilerEvent::QueryCacheHit {
            query_name,
            category,
        })
    }

    #[inline]
    pub fn start_query(&mut self, query_name: &'static str, category: ProfileCategory) {
        self.record(ProfilerEvent::QueryStart {
            query_name,
            category,
            time: Instant::now(),
        });
    }

    #[inline]
    pub fn end_query(&mut self, query_name: &'static str, category: ProfileCategory) {
        self.record(ProfilerEvent::QueryEnd {
            query_name,
            category,
            time: Instant::now(),
        })
    }

    #[inline]
    pub fn incremental_load_result_start(&mut self, query_name: &'static str) {
        self.record(ProfilerEvent::IncrementalLoadResultStart {
            query_name,
            time: Instant::now(),
        })
    }

    #[inline]
    pub fn incremental_load_result_end(&mut self, query_name: &'static str) {
        self.record(ProfilerEvent::IncrementalLoadResultEnd {
            query_name,
            time: Instant::now(),
        })
    }

    #[inline]
    pub fn query_blocked_start(&mut self, query_name: &'static str, category: ProfileCategory) {
        self.record(ProfilerEvent::QueryBlockedStart {
            query_name,
            category,
            time: Instant::now(),
        })
    }

    #[inline]
    pub fn query_blocked_end(&mut self, query_name: &'static str, category: ProfileCategory) {
        self.record(ProfilerEvent::QueryBlockedEnd {
            query_name,
            category,
            time: Instant::now(),
        })
    }

    #[inline]
    fn record(&mut self, event: ProfilerEvent) {
        let thread_id = std::thread::current().id();
        let events = self.events.entry(thread_id).or_default();

        events.push(event);
    }

    fn calculate_thread_results(events: &Vec<ProfilerEvent>) -> CalculatedResults {
        use self::ProfilerEvent::*;

        assert!(
            events.last().map(|e| !e.is_start_event()).unwrap_or(true),
            "there was an event running when calculate_reslts() was called"
        );

        let mut results = CalculatedResults::new();

        //(event, child time to subtract)
        let mut query_stack = Vec::new();

        for event in events {
            match event {
                QueryStart { .. } | GenericActivityStart { .. } => {
                    query_stack.push((event, 0));
                },
                QueryEnd { query_name, category, time: end_time } => {
                    let previous_query = query_stack.pop();
                    if let Some((QueryStart {
                                    query_name: p_query_name,
                                    time: start_time,
                                    category: _ }, child_time_to_subtract)) = previous_query {
                        assert_eq!(
                            p_query_name,
                            query_name,
                            "Saw a query end but the previous query wasn't the corresponding start"
                        );

                        let time_ns = time_between_ns(*start_time, *end_time);
                        let self_time_ns = time_ns - child_time_to_subtract;
                        let result_data = results.categories.entry(*category).or_default();

                        *result_data.query_times.entry(query_name).or_default() += self_time_ns;

                        if let Some((_, child_time_to_subtract)) = query_stack.last_mut() {
                            *child_time_to_subtract += time_ns;
                        }
                    } else {
                        bug!("Saw a query end but the previous event wasn't a query start");
                    }
                }
                GenericActivityEnd { category, time: end_time } => {
                    let previous_event = query_stack.pop();
                    if let Some((GenericActivityStart {
                                    category: previous_category,
                                    time: start_time }, child_time_to_subtract)) = previous_event {
                        assert_eq!(
                            previous_category,
                            category,
                            "Saw an end but the previous event wasn't the corresponding start"
                        );

                        let time_ns = time_between_ns(*start_time, *end_time);
                        let self_time_ns = time_ns - child_time_to_subtract;
                        let result_data = results.categories.entry(*category).or_default();

                        *result_data.query_times
                            .entry("{time spent not running queries}")
                            .or_default() += self_time_ns;

                        if let Some((_, child_time_to_subtract)) = query_stack.last_mut() {
                            *child_time_to_subtract += time_ns;
                        }
                    } else {
                        bug!("Saw an activity end but the previous event wasn't an activity start");
                    }
                },
                QueryCacheHit { category, query_name } => {
                    let result_data = results.categories.entry(*category).or_default();

                    let (hits, total) =
                        result_data.query_cache_stats.entry(query_name).or_insert((0, 0));
                    *hits += 1;
                    *total += 1;
                },
                QueryCount { category, query_name, count } => {
                    let result_data = results.categories.entry(*category).or_default();

                    let (_, totals) =
                        result_data.query_cache_stats.entry(query_name).or_insert((0, 0));
                    *totals += *count as u64;
                },
                //we don't summarize incremental load result events in the simple output mode
                IncrementalLoadResultStart { .. } | IncrementalLoadResultEnd { .. } => { },
                //we don't summarize parallel query blocking in the simple output mode
                QueryBlockedStart { .. } | QueryBlockedEnd { .. } => { },
            }
        }

        //normalize the times to ms
        for (_, data) in &mut results.categories {
            for (_, time) in &mut data.query_times {
                *time = *time / 1_000_000;
            }
        }

        results
    }

    fn get_results(&self, opts: &Options) -> CalculatedResults {
        self.events
            .iter()
            .map(|(_, r)| SelfProfiler::calculate_thread_results(r))
            .fold(CalculatedResults::new(), CalculatedResults::consolidate)
            .with_options(opts)
    }

    pub fn print_results(&mut self, opts: &Options) {
        self.end_activity(ProfileCategory::Other);

        let results = self.get_results(opts);

        let total_time = results.total_time() as f32;

        let out = io::stderr();
        let mut lock = out.lock();

        let crate_name = results.crate_name.map(|n| format!(" for {}", n)).unwrap_or_default();

        writeln!(lock, "Self profiling results{}:", crate_name).unwrap();
        writeln!(lock).unwrap();

        writeln!(lock, "| Phase                                     | Time (ms)      \
                        | Time (%) | Queries        | Hits (%)")
            .unwrap();
        writeln!(lock, "| ----------------------------------------- | -------------- \
                        | -------- | -------------- | --------")
            .unwrap();

        let mut categories: Vec<_> = results.categories.iter().collect();
        categories.sort_by(|(_, data1), (_, data2)| data2.total_time().cmp(&data1.total_time()));

        for (category, data) in categories {
            let (category_hits, category_total) = data.total_cache_data();
            let category_hit_percent = calculate_percent(category_hits, category_total);

            writeln!(
                lock,
                "| {0: <41} | {1: >14} | {2: >8.2} | {3: >14} | {4: >8}",
                format!("{:?}", category),
                data.total_time(),
                ((data.total_time() as f32) / total_time) * 100.0,
                category_total,
                format!("{:.2}", category_hit_percent),
            ).unwrap();

            //in verbose mode, show individual query data
            if results.verbose {
                //don't show queries that took less than 1ms
                let mut times: Vec<_> = data.query_times.iter().filter(|(_, t)| **t > 0).collect();
                times.sort_by(|(_, time1), (_, time2)| time2.cmp(time1));

                for (query, time) in times {
                    let (hits, total) = data.query_cache_stats.get(query).unwrap_or(&(0, 0));
                    let hit_percent = calculate_percent(*hits, *total);

                    writeln!(
                        lock,
                        "| - {0: <39} | {1: >14} | {2: >8.2} | {3: >14} | {4: >8}",
                        query,
                        time,
                        ((*time as f32) / total_time) * 100.0,
                        total,
                        format!("{:.2}", hit_percent),
                    ).unwrap();
                }
            }
        }

        writeln!(lock).unwrap();
        writeln!(lock, "Optimization level: {:?}", opts.optimize).unwrap();
        writeln!(lock, "Incremental: {}", if results.incremental { "on" } else { "off" }).unwrap();
    }

    pub fn save_results(&self, opts: &Options) {
        let results = self.get_results(opts);

        let compilation_options =
            format!("{{ \"optimization_level\": \"{:?}\", \"incremental\": {} }}",
                    results.optimization_level,
                    if results.incremental { "true" } else { "false" });

        let mut category_data = String::new();

        for (category, data) in &results.categories {
            let (hits, total) = data.total_cache_data();
            let hit_percent = calculate_percent(hits, total);

            category_data.push_str(&format!("{{ \"category\": \"{:?}\", \"time_ms\": {}, \
                                                \"query_count\": {}, \"query_hits\": {} }}",
                                            category,
                                            data.total_time(),
                                            total,
                                            format!("{:.2}", hit_percent)));
        }

        let json = format!("{{ \"category_data\": {}, \"compilation_options\": {} }}",
                        category_data,
                        compilation_options);

        fs::write("self_profiler_results.json", json).unwrap();
    }
}
<commit_msg>[self-profiler] Misc cleanups<commit_after>use std::collections::{BTreeMap, HashMap};
use std::fs;
use std::io::{self, Write};
use std::thread::ThreadId;
use std::time::Instant;

use crate::session::config::{Options, OptLevel};

#[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)]
pub enum ProfileCategory {
    Parsing,
    Expansion,
    TypeChecking,
    BorrowChecking,
    Codegen,
    Linking,
    Other,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ProfilerEvent {
    QueryStart { query_name: &'static str, category: ProfileCategory, time: Instant },
    QueryEnd { query_name: &'static str, category: ProfileCategory, time: Instant },
    GenericActivityStart { category: ProfileCategory, time: Instant },
    GenericActivityEnd { category: ProfileCategory, time: Instant },
    QueryCacheHit { query_name: &'static str, category: ProfileCategory },
    QueryCount { query_name: &'static str, category: ProfileCategory, count: usize },
    IncrementalLoadResultStart { query_name: &'static str, time: Instant },
    IncrementalLoadResultEnd { query_name: &'static str, time: Instant },
    QueryBlockedStart { query_name: &'static str, category: ProfileCategory, time: Instant },
    QueryBlockedEnd { query_name: &'static str, category: ProfileCategory, time: Instant },
}

impl ProfilerEvent {
    fn is_start_event(&self) -> bool {
        use self::ProfilerEvent::*;

        match self {
            QueryStart { .. } |
            GenericActivityStart { .. } |
            IncrementalLoadResultStart { .. } |
            QueryBlockedStart { .. } => true,

            QueryEnd { .. } |
            GenericActivityEnd { .. } |
            QueryCacheHit { .. } |
            QueryCount { .. } |
            IncrementalLoadResultEnd { .. } |
            QueryBlockedEnd { .. } => false,
        }
    }
}

pub struct SelfProfiler {
    events: HashMap<ThreadId, Vec<ProfilerEvent>>,
}

struct CategoryResultData {
    query_times: BTreeMap<&'static str, u64>,
    query_cache_stats: BTreeMap<&'static str, (u64, u64)>, //(hits, total)
}

impl CategoryResultData {
    fn new() -> CategoryResultData {
        CategoryResultData {
            query_times: BTreeMap::new(),
            query_cache_stats: BTreeMap::new(),
        }
    }

    fn total_time(&self) -> u64 {
        self.query_times.iter().map(|(_, time)| time).sum()
    }

    fn total_cache_data(&self) -> (u64, u64) {
        let (mut hits, mut total) = (0, 0);

        for (_, (h, t)) in &self.query_cache_stats {
            hits += h;
            total += t;
        }

        (hits, total)
    }
}

impl Default for CategoryResultData {
    fn default() -> CategoryResultData {
        CategoryResultData::new()
    }
}

struct CalculatedResults {
    categories: BTreeMap<ProfileCategory, CategoryResultData>,
    crate_name: Option<String>,
    optimization_level: OptLevel,
    incremental: bool,
    verbose: bool,
}

impl CalculatedResults {
    fn new() -> CalculatedResults {
        CalculatedResults {
            categories: BTreeMap::new(),
            crate_name: None,
            optimization_level: OptLevel::No,
            incremental: false,
            verbose: false,
        }
    }

    fn consolidate(mut cr1: CalculatedResults, cr2: CalculatedResults) -> CalculatedResults {
        for (category, data) in cr2.categories {
            let cr1_data = cr1.categories.entry(category).or_default();

            for (query, time) in data.query_times {
                *cr1_data.query_times.entry(query).or_default() += time;
            }

            for (query, (hits, total)) in data.query_cache_stats {
                let (h, t) = cr1_data.query_cache_stats.entry(query).or_insert((0, 0));
                *h += hits;
                *t += total;
            }
        }

        cr1
    }

    fn total_time(&self) -> u64 {
        self.categories.iter().map(|(_, data)| data.total_time()).sum()
    }

    fn with_options(mut self, opts: &Options) -> CalculatedResults {
        self.crate_name = opts.crate_name.clone();
        self.optimization_level = opts.optimize;
        self.incremental = opts.incremental.is_some();
        self.verbose = opts.debugging_opts.verbose;

        self
    }
}

fn time_between_ns(start: Instant, end: Instant) -> u64 {
    if start < end {
        let time = end - start;
        (time.as_secs() * 1_000_000_000) + (time.subsec_nanos() as u64)
    } else {
        debug!("time_between_ns: ignorning instance of end < start");
        0
    }
}

fn calculate_percent(numerator: u64, denominator: u64) -> f32 {
    if denominator > 0 {
        ((numerator as f32) / (denominator as f32)) * 100.0
    } else {
        0.0
    }
}

impl SelfProfiler {
    pub fn new() -> SelfProfiler {
        let mut profiler = SelfProfiler {
            events: HashMap::new(),
        };

        profiler.start_activity(ProfileCategory::Other);

        profiler
    }

    #[inline]
    pub fn start_activity(&mut self, category: ProfileCategory) {
        self.record(ProfilerEvent::GenericActivityStart {
            category,
            time: Instant::now(),
        })
    }

    #[inline]
    pub fn end_activity(&mut self, category: ProfileCategory) {
        self.record(ProfilerEvent::GenericActivityEnd {
            category,
            time: Instant::now(),
        })
    }

    #[inline]
    pub fn record_computed_queries(
        &mut self,
        query_name: &'static str,
        category: ProfileCategory,
        count: usize)
        {
        self.record(ProfilerEvent::QueryCount {
            query_name,
            category,
            count,
        })
    }

    #[inline]
    pub fn record_query_hit(&mut self, query_name: &'static str, category: ProfileCategory) {
        self.record(ProfilerEvent::QueryCacheHit {
            query_name,
            category,
        })
    }

    #[inline]
    pub fn start_query(&mut self, query_name: &'static str, category: ProfileCategory) {
        self.record(ProfilerEvent::QueryStart {
            query_name,
            category,
            time: Instant::now(),
        });
    }

    #[inline]
    pub fn end_query(&mut self, query_name: &'static str, category: ProfileCategory) {
        self.record(ProfilerEvent::QueryEnd {
            query_name,
            category,
            time: Instant::now(),
        })
    }

    #[inline]
    pub fn incremental_load_result_start(&mut self, query_name: &'static str) {
        self.record(ProfilerEvent::IncrementalLoadResultStart {
            query_name,
            time: Instant::now(),
        })
    }

    #[inline]
    pub fn incremental_load_result_end(&mut self, query_name: &'static str) {
        self.record(ProfilerEvent::IncrementalLoadResultEnd {
            query_name,
            time: Instant::now(),
        })
    }

    #[inline]
    pub fn query_blocked_start(&mut self, query_name: &'static str, category: ProfileCategory) {
        self.record(ProfilerEvent::QueryBlockedStart {
            query_name,
            category,
            time: Instant::now(),
        })
    }

    #[inline]
    pub fn query_blocked_end(&mut self, query_name: &'static str, category: ProfileCategory) {
        self.record(ProfilerEvent::QueryBlockedEnd {
            query_name,
            category,
            time: Instant::now(),
        })
    }

    #[inline]
    fn record(&mut self, event: ProfilerEvent) {
        let thread_id = std::thread::current().id();
        let events = self.events.entry(thread_id).or_default();

        events.push(event);
    }

    fn calculate_thread_results(events: &Vec<ProfilerEvent>) -> CalculatedResults {
        use self::ProfilerEvent::*;

        assert!(
            events.last().map(|e| !e.is_start_event()).unwrap_or(true),
            "there was an event running when calculate_reslts() was called"
        );

        let mut results = CalculatedResults::new();

        //(event, child time to subtract)
        let mut query_stack = Vec::new();

        for event in events {
            match event {
                QueryStart { .. } | GenericActivityStart { .. } => {
                    query_stack.push((event, 0));
                },
                QueryEnd { query_name, category, time: end_time } => {
                    let previous_query = query_stack.pop();
                    if let Some((QueryStart {
                                    query_name: p_query_name,
                                    time: start_time,
                                    category: _ }, child_time_to_subtract)) = previous_query {
                        assert_eq!(
                            p_query_name,
                            query_name,
                            "Saw a query end but the previous query wasn't the corresponding start"
                        );

                        let time_ns = time_between_ns(*start_time, *end_time);
                        let self_time_ns = time_ns - child_time_to_subtract;
                        let result_data = results.categories.entry(*category).or_default();

                        *result_data.query_times.entry(query_name).or_default() += self_time_ns;

                        if let Some((_, child_time_to_subtract)) = query_stack.last_mut() {
                            *child_time_to_subtract += time_ns;
                        }
                    } else {
                        bug!("Saw a query end but the previous event wasn't a query start");
                    }
                }
                GenericActivityEnd { category, time: end_time } => {
                    let previous_event = query_stack.pop();
                    if let Some((GenericActivityStart {
                                    category: previous_category,
                                    time: start_time }, child_time_to_subtract)) = previous_event {
                        assert_eq!(
                            previous_category,
                            category,
                            "Saw an end but the previous event wasn't the corresponding start"
                        );

                        let time_ns = time_between_ns(*start_time, *end_time);
                        let self_time_ns = time_ns - child_time_to_subtract;
                        let result_data = results.categories.entry(*category).or_default();

                        *result_data.query_times
                            .entry("{time spent not running queries}")
                            .or_default() += self_time_ns;

                        if let Some((_, child_time_to_subtract)) = query_stack.last_mut() {
                            *child_time_to_subtract += time_ns;
                        }
                    } else {
                        bug!("Saw an activity end but the previous event wasn't an activity start");
                    }
                },
                QueryCacheHit { category, query_name } => {
                    let result_data = results.categories.entry(*category).or_default();

                    let (hits, total) =
                        result_data.query_cache_stats.entry(query_name).or_insert((0, 0));
                    *hits += 1;
                    *total += 1;
                },
                QueryCount { category, query_name, count } => {
                    let result_data = results.categories.entry(*category).or_default();

                    let (_, totals) =
                        result_data.query_cache_stats.entry(query_name).or_insert((0, 0));
                    *totals += *count as u64;
                },
                //we don't summarize incremental load result events in the simple output mode
                IncrementalLoadResultStart { .. } | IncrementalLoadResultEnd { .. } => { },
                //we don't summarize parallel query blocking in the simple output mode
                QueryBlockedStart { .. } | QueryBlockedEnd { .. } => { },
            }
        }

        //normalize the times to ms
        for (_, data) in &mut results.categories {
            for (_, time) in &mut data.query_times {
                *time = *time / 1_000_000;
            }
        }

        results
    }

    fn get_results(&self, opts: &Options) -> CalculatedResults {
        self.events
            .iter()
            .map(|(_, r)| SelfProfiler::calculate_thread_results(r))
            .fold(CalculatedResults::new(), CalculatedResults::consolidate)
            .with_options(opts)
    }

    pub fn print_results(&mut self, opts: &Options) {
        self.end_activity(ProfileCategory::Other);

        let results = self.get_results(opts);

        let total_time = results.total_time() as f32;

        let out = io::stderr();
        let mut lock = out.lock();

        let crate_name = results.crate_name.map(|n| format!(" for {}", n)).unwrap_or_default();

        writeln!(lock, "Self profiling results{}:", crate_name).unwrap();
        writeln!(lock).unwrap();

        writeln!(lock, "| Phase                                     | Time (ms)      \
                        | Time (%) | Queries        | Hits (%)")
            .unwrap();
        writeln!(lock, "| ----------------------------------------- | -------------- \
                        | -------- | -------------- | --------")
            .unwrap();

        let mut categories: Vec<_> = results.categories.iter().collect();
        categories.sort_by_cached_key(|(_, d)| d.total_time());

        for (category, data) in categories.iter().rev() {
            let (category_hits, category_total) = data.total_cache_data();
            let category_hit_percent = calculate_percent(category_hits, category_total);

            writeln!(
                lock,
                "| {0: <41} | {1: >14} | {2: >8.2} | {3: >14} | {4: >8}",
                format!("{:?}", category),
                data.total_time(),
                ((data.total_time() as f32) / total_time) * 100.0,
                category_total,
                format!("{:.2}", category_hit_percent),
            ).unwrap();

            //in verbose mode, show individual query data
            if results.verbose {
                //don't show queries that took less than 1ms
                let mut times: Vec<_> = data.query_times.iter().filter(|(_, t)| **t > 0).collect();
                times.sort_by(|(_, time1), (_, time2)| time2.cmp(time1));

                for (query, time) in times {
                    let (hits, total) = data.query_cache_stats.get(query).unwrap_or(&(0, 0));
                    let hit_percent = calculate_percent(*hits, *total);

                    writeln!(
                        lock,
                        "| - {0: <39} | {1: >14} | {2: >8.2} | {3: >14} | {4: >8}",
                        query,
                        time,
                        ((*time as f32) / total_time) * 100.0,
                        total,
                        format!("{:.2}", hit_percent),
                    ).unwrap();
                }
            }
        }

        writeln!(lock).unwrap();
        writeln!(lock, "Optimization level: {:?}", opts.optimize).unwrap();
        writeln!(lock, "Incremental: {}", if results.incremental { "on" } else { "off" }).unwrap();
    }

    pub fn save_results(&self, opts: &Options) {
        let results = self.get_results(opts);

        let compilation_options =
            format!("{{ \"optimization_level\": \"{:?}\", \"incremental\": {} }}",
                    results.optimization_level,
                    if results.incremental { "true" } else { "false" });

        let mut category_data = String::new();

        for (category, data) in &results.categories {
            let (hits, total) = data.total_cache_data();
            let hit_percent = calculate_percent(hits, total);

            category_data.push_str(&format!("{{ \"category\": \"{:?}\", \"time_ms\": {}, \
                                                \"query_count\": {}, \"query_hits\": {} }}",
                                            category,
                                            data.total_time(),
                                            total,
                                            format!("{:.2}", hit_percent)));
        }

        let json = format!("{{ \"category_data\": {}, \"compilation_options\": {} }}",
                        category_data,
                        compilation_options);

        fs::write("self_profiler_results.json", json).unwrap();
    }
}
<|endoftext|>


<commit_before>extern crate specs;
extern crate futures;
extern crate serde;
extern crate serde_json;
extern crate websocket_server;

use self::specs::{Join, ReadStorage, WriteStorage, System, Entities};
use self::futures::{Future, Sink};
use self::websocket_server::Message;
use self::serde::ser::Serialize;

use model::comp::{Pos, Vel, Connect, Disconnect, Player as PlayerComp, Actor};
use model::client::{Message as ClientMessage, OpCode};

use std::collections::HashMap;
use std::fmt::Debug;

pub struct Sending;
impl<'a> System<'a> for Sending {
    #[allow(type_complexity)]
    type SystemData = (ReadStorage<'a, Pos>,
     ReadStorage<'a, Vel>,
     ReadStorage<'a, PlayerComp>,
     ReadStorage<'a, Actor>,
     WriteStorage<'a, Connect>,
     ReadStorage<'a, Disconnect>,
     Entities<'a>);

    fn run(
        &mut self,
        (pos, vel, player, actor, mut connect, disconnect, entities): Self::SystemData,
    ) {

        handle_new_connections(&player, &entities, &actor, &mut connect);
        handle_disconnects(&player, &actor, &disconnect);

        send_world_updates(&player, &actor, &pos, &vel);
    }
}

fn send<T>(player: &PlayerComp, msg: &ClientMessage<T>)
where
    T: Serialize,
{
    let msg = serde_json::to_string(&msg).unwrap();
    let send_channel = player.send_channel.clone();
    send_channel.send(Message::Text(msg)).wait().expect(
        "Failed to send message",
    );
}

fn serialize<T>(t: &T) -> String
where
    T: Serialize + Debug,
{
    serde_json::to_string(t).expect(&format!("Failed to serialize object {:?}", t))
}

fn handle_new_connections(player: &ReadStorage<PlayerComp>, entities: &Entities, actor: &ReadStorage<Actor>, connect: &mut WriteStorage<Connect>) {
    let mut new_connections = Vec::new();
    for (new_player, entity, new_actor, _) in
        (player, &**entities, actor, &mut *connect).join()
        {
            new_connections.push((entity.clone(), new_actor.clone()));
            let mut payload = Vec::new();
            payload.push(serialize(&new_actor.id));
            let mut id_actor_kind = HashMap::new();
            for actor in (&actor).join() {
                id_actor_kind.insert(actor.id, actor.kind.clone());
            }
            payload.push(serialize(&id_actor_kind));
            let greeting = ClientMessage {
                opcode: OpCode::Greeting,
                payload: payload,
            };
            send(new_player, &greeting);
        }

    for new_connection in new_connections {
        let (new_entity, new_actor) = new_connection;
        connect.remove(new_entity);
        let connection = ClientMessage {
            opcode: OpCode::Connect,
            payload: new_actor.clone(),
        };
        for (player, entity) in (player, &**entities).join() {
            if entity != new_entity {
                send(player, &connection);
            }
        }
    }
}

fn handle_disconnects(player: &ReadStorage<PlayerComp>, actor: &ReadStorage<Actor>, disconnect: &ReadStorage<Disconnect>) {
    for (actor, _) in (actor, disconnect).join() {
        let disconnect = ClientMessage {
            opcode: OpCode::Disconnect,
            payload: actor.id.clone(),
        };
        for player in (player).join() {
            send(player, &disconnect);
        }
    }

}

fn send_world_updates(player: &ReadStorage<PlayerComp>, actor: &ReadStorage<Actor>, pos: &ReadStorage<Pos>, vel: &ReadStorage<Vel>) {
    let mut serialized_actors = HashMap::new();
    for actor in (actor).join() {
        serialized_actors.insert(actor.id, HashMap::new());
    }

    for (pos, vel, actor) in (pos, vel, actor).join() {
        let mut actor = serialized_actors.get_mut(&actor.id).unwrap();
        actor.insert("pos", serialize(&pos));
        actor.insert("vel", serialize(&vel));
    }

    for (player, actor) in (player, actor).join() {
        let mut actor = serialized_actors.get_mut(&actor.id).unwrap();
        actor.insert("delay", serialize(&player.delay));
    }
    let world_state = ClientMessage {
        opcode: OpCode::WorldUpdate,
        payload: serialized_actors,
    };
    for player in (player).join() {
        send(player, &world_state);
    }
}<commit_msg>Core: Remove dereferencing bullshit<commit_after>extern crate specs;
extern crate futures;
extern crate serde;
extern crate serde_json;
extern crate websocket_server;

use self::specs::{Join, ReadStorage, WriteStorage, System, Entities, EntitiesRes};
use self::futures::{Future, Sink};
use self::websocket_server::Message;
use self::serde::ser::Serialize;

use model::comp::{Pos, Vel, Connect, Disconnect, Player as PlayerComp, Actor};
use model::client::{Message as ClientMessage, OpCode};

use std::collections::HashMap;
use std::fmt::Debug;

pub struct Sending;
impl<'a> System<'a> for Sending {
    #[allow(type_complexity)]
    type SystemData = (ReadStorage<'a, Pos>,
     ReadStorage<'a, Vel>,
     ReadStorage<'a, PlayerComp>,
     ReadStorage<'a, Actor>,
     WriteStorage<'a, Connect>,
     ReadStorage<'a, Disconnect>,
     Entities<'a>);

    fn run(
        &mut self,
        (pos, vel, player, actor, mut connect, disconnect, entities): Self::SystemData,
    ) {

        handle_new_connections(&player, &*entities, &actor, &mut connect);
        handle_disconnects(&player, &actor, &disconnect);

        send_world_updates(&player, &actor, &pos, &vel);
    }
}

fn send<T>(player: &PlayerComp, msg: &ClientMessage<T>)
where
    T: Serialize,
{
    let msg = serde_json::to_string(&msg).unwrap();
    let send_channel = player.send_channel.clone();
    send_channel.send(Message::Text(msg)).wait().expect(
        "Failed to send message",
    );
}

fn serialize<T>(t: &T) -> String
where
    T: Serialize + Debug,
{
    serde_json::to_string(t).expect(&format!("Failed to serialize object {:?}", t))
}

fn handle_new_connections(player: &ReadStorage<PlayerComp>, entities: &EntitiesRes, actor: &ReadStorage<Actor>, connect: &mut WriteStorage<Connect>) {
    let mut new_connections = Vec::new();
    for (new_player, entity, new_actor, _) in
        (player, entities, actor, &mut *connect).join()
        {
            new_connections.push((entity.clone(), new_actor.clone()));
            let mut payload = Vec::new();
            payload.push(serialize(&new_actor.id));
            let mut id_actor_kind = HashMap::new();
            for actor in (&actor).join() {
                id_actor_kind.insert(actor.id, actor.kind.clone());
            }
            payload.push(serialize(&id_actor_kind));
            let greeting = ClientMessage {
                opcode: OpCode::Greeting,
                payload: payload,
            };
            send(new_player, &greeting);
        }

    for new_connection in new_connections {
        let (new_entity, new_actor) = new_connection;
        connect.remove(new_entity);
        let connection = ClientMessage {
            opcode: OpCode::Connect,
            payload: new_actor.clone(),
        };
        for (player, entity) in (player, entities).join() {
            if entity != new_entity {
                send(player, &connection);
            }
        }
    }
}

fn handle_disconnects(player: &ReadStorage<PlayerComp>, actor: &ReadStorage<Actor>, disconnect: &ReadStorage<Disconnect>) {
    for (actor, _) in (actor, disconnect).join() {
        let disconnect = ClientMessage {
            opcode: OpCode::Disconnect,
            payload: actor.id.clone(),
        };
        for player in (player).join() {
            send(player, &disconnect);
        }
    }

}

fn send_world_updates(player: &ReadStorage<PlayerComp>, actor: &ReadStorage<Actor>, pos: &ReadStorage<Pos>, vel: &ReadStorage<Vel>) {
    let mut serialized_actors = HashMap::new();
    for actor in (actor).join() {
        serialized_actors.insert(actor.id, HashMap::new());
    }

    for (pos, vel, actor) in (pos, vel, actor).join() {
        let mut actor = serialized_actors.get_mut(&actor.id).unwrap();
        actor.insert("pos", serialize(&pos));
        actor.insert("vel", serialize(&vel));
    }

    for (player, actor) in (player, actor).join() {
        let mut actor = serialized_actors.get_mut(&actor.id).unwrap();
        actor.insert("delay", serialize(&player.delay));
    }
    let world_state = ClientMessage {
        opcode: OpCode::WorldUpdate,
        payload: serialized_actors,
    };
    for player in (player).join() {
        send(player, &world_state);
    }
}<|endoftext|>


<commit_before>extern crate herder;

use herder::Mastodon;
use herder::client::Client;

#[test]
fn mastodon_node_created() {
    let Mastodon(mastodon_url) = Mastodon::new("http://localhost");
    assert_eq!(mastodon_url, String::from("http://localhost"));
}

#[test]
fn create_client_with_mastodon_domain() {
    let mastodon = Mastodon::new("http://localhost");
    let Client { url_base: mastodon_domain, .. } = mastodon.client(&mastodon.0, "MYTOKEN");
    assert_eq!(mastodon_domain, String::from("http://localhost"));
}

#[test]
fn create_client_with_bearer_token() {
    let mastodon = Mastodon::new("http://localhost");
    let Client { bearer_token: token, .. } = mastodon.client(&mastodon.0, "MYTOKEN");
    assert_eq!(token, String::from("MYTOKEN"));
}
<commit_msg>new tests for basic client functionality<commit_after>extern crate herder;

use herder::Mastodon;
use herder::client::Client;

#[test]
fn mastodon_node_created() {
    let Mastodon(domain) = Mastodon::new("http://localhost:3000/");
    assert_eq!(domain, String::from("http://localhost:3000/"));
}

#[test]
fn create_client_with_mastodon_domain() {
    let mastodon = Mastodon::new("http://localhost:3000/");
    let Client { url_base: domain, .. } = mastodon.client("MYTOKEN");
    assert_eq!(domain, String::from("http://localhost:3000/"));
}

#[test]
fn create_client_with_bearer_token() {
    let mastodon = Mastodon::new("http://localhost:3000/");
    let Client { bearer_token: token, .. } = mastodon.client("MYTOKEN");
    assert_eq!(token, String::from("MYTOKEN"));
}

#[test]
fn client_builds_api_endpoints() {
    let mastodon = Mastodon::new("http://localhost:3000/");
    let api_endpoint = mastodon.endpoint_url("/api/v1/");
    assert_eq!(api_endpoint, String::from("http://localhost:3000/api/v1/"));
}

#[test]
fn client_publish_a_toot() {
    unimplemented!()
}
<|endoftext|>


<commit_before>use super::{Parser, PathStyle};
use rustc_ast as ast;
use rustc_ast::attr;
use rustc_ast::token::{self, Nonterminal};
use rustc_ast_pretty::pprust;
use rustc_errors::{error_code, PResult};
use rustc_span::{sym, Span};

use tracing::debug;

#[derive(Debug)]
pub(super) enum InnerAttrPolicy<'a> {
    Permitted,
    Forbidden { reason: &'a str, saw_doc_comment: bool, prev_attr_sp: Option<Span> },
}

const DEFAULT_UNEXPECTED_INNER_ATTR_ERR_MSG: &str = "an inner attribute is not \
                                                     permitted in this context";

pub(super) const DEFAULT_INNER_ATTR_FORBIDDEN: InnerAttrPolicy<'_> = InnerAttrPolicy::Forbidden {
    reason: DEFAULT_UNEXPECTED_INNER_ATTR_ERR_MSG,
    saw_doc_comment: false,
    prev_attr_sp: None,
};

impl<'a> Parser<'a> {
    /// Parses attributes that appear before an item.
    pub(super) fn parse_outer_attributes(&mut self) -> PResult<'a, Vec<ast::Attribute>> {
        let mut attrs: Vec<ast::Attribute> = Vec::new();
        let mut just_parsed_doc_comment = false;
        loop {
            debug!("parse_outer_attributes: self.token={:?}", self.token);
            let attr = if self.check(&token::Pound) {
                let inner_error_reason = if just_parsed_doc_comment {
                    "an inner attribute is not permitted following an outer doc comment"
                } else if !attrs.is_empty() {
                    "an inner attribute is not permitted following an outer attribute"
                } else {
                    DEFAULT_UNEXPECTED_INNER_ATTR_ERR_MSG
                };
                let inner_parse_policy = InnerAttrPolicy::Forbidden {
                    reason: inner_error_reason,
                    saw_doc_comment: just_parsed_doc_comment,
                    prev_attr_sp: attrs.last().map(|a| a.span),
                };
                just_parsed_doc_comment = false;
                Some(self.parse_attribute(inner_parse_policy)?)
            } else if let token::DocComment(comment_kind, attr_style, data) = self.token.kind {
                if attr_style != ast::AttrStyle::Outer {
                    self.sess
                        .span_diagnostic
                        .struct_span_err_with_code(
                            self.token.span,
                            "expected outer doc comment",
                            error_code!(E0753),
                        )
                        .note(
                            "inner doc comments like this (starting with \
                         `//!` or `/*!`) can only appear before items",
                        )
                        .emit();
                }
                self.bump();
                just_parsed_doc_comment = true;
                Some(attr::mk_doc_comment(comment_kind, attr_style, data, self.prev_token.span))
            } else {
                None
            };

            if let Some(attr) = attr {
                attrs.push(attr);
            } else {
                break;
            }
        }
        Ok(attrs)
    }

    /// Matches `attribute = # ! [ meta_item ]`.
    /// `inner_parse_policy` prescribes how to handle inner attributes.
    fn parse_attribute(
        &mut self,
        inner_parse_policy: InnerAttrPolicy<'_>,
    ) -> PResult<'a, ast::Attribute> {
        debug!(
            "parse_attribute: inner_parse_policy={:?} self.token={:?}",
            inner_parse_policy, self.token
        );
        let lo = self.token.span;
        let ((item, style, span), tokens) = self.collect_tokens(|this| {
            if this.eat(&token::Pound) {
                let style = if this.eat(&token::Not) {
                    ast::AttrStyle::Inner
                } else {
                    ast::AttrStyle::Outer
                };

                this.expect(&token::OpenDelim(token::Bracket))?;
                let item = this.parse_attr_item(false)?;
                this.expect(&token::CloseDelim(token::Bracket))?;
                let attr_sp = lo.to(this.prev_token.span);

                // Emit error if inner attribute is encountered and forbidden.
                if style == ast::AttrStyle::Inner {
                    this.error_on_forbidden_inner_attr(attr_sp, inner_parse_policy);
                }

                Ok((item, style, attr_sp))
            } else {
                let token_str = pprust::token_to_string(&this.token);
                let msg = &format!("expected `#`, found `{}`", token_str);
                Err(this.struct_span_err(this.token.span, msg))
            }
        })?;

        Ok(attr::mk_attr_from_item(item, tokens, style, span))
    }

    pub(super) fn error_on_forbidden_inner_attr(&self, attr_sp: Span, policy: InnerAttrPolicy<'_>) {
        if let InnerAttrPolicy::Forbidden { reason, saw_doc_comment, prev_attr_sp } = policy {
            let prev_attr_note =
                if saw_doc_comment { "previous doc comment" } else { "previous outer attribute" };

            let mut diag = self.struct_span_err(attr_sp, reason);

            if let Some(prev_attr_sp) = prev_attr_sp {
                diag.span_label(attr_sp, "not permitted following an outer attribute")
                    .span_label(prev_attr_sp, prev_attr_note);
            }

            diag.note(
                "inner attributes, like `#![no_std]`, annotate the item enclosing them, \
                and are usually found at the beginning of source files. \
                Outer attributes, like `#[test]`, annotate the item following them.",
            )
            .emit();
        }
    }

    /// Parses an inner part of an attribute (the path and following tokens).
    /// The tokens must be either a delimited token stream, or empty token stream,
    /// or the "legacy" key-value form.
    ///     PATH `(` TOKEN_STREAM `)`
    ///     PATH `[` TOKEN_STREAM `]`
    ///     PATH `{` TOKEN_STREAM `}`
    ///     PATH
    ///     PATH `=` UNSUFFIXED_LIT
    /// The delimiters or `=` are still put into the resulting token stream.
    pub fn parse_attr_item(&mut self, capture_tokens: bool) -> PResult<'a, ast::AttrItem> {
        let item = match self.token.kind {
            token::Interpolated(ref nt) => match **nt {
                Nonterminal::NtMeta(ref item) => Some(item.clone().into_inner()),
                _ => None,
            },
            _ => None,
        };
        Ok(if let Some(item) = item {
            self.bump();
            item
        } else {
            let do_parse = |this: &mut Self| {
                let path = this.parse_path(PathStyle::Mod)?;
                let args = this.parse_attr_args()?;
                Ok(ast::AttrItem { path, args, tokens: None })
            };
            if capture_tokens {
                let (mut item, tokens) = self.collect_tokens(do_parse)?;
                item.tokens = tokens;
                item
            } else {
                do_parse(self)?
            }
        })
    }

    /// Parses attributes that appear after the opening of an item. These should
    /// be preceded by an exclamation mark, but we accept and warn about one
    /// terminated by a semicolon.
    ///
    /// Matches `inner_attrs*`.
    crate fn parse_inner_attributes(&mut self) -> PResult<'a, Vec<ast::Attribute>> {
        let mut attrs: Vec<ast::Attribute> = vec![];
        loop {
            // Only try to parse if it is an inner attribute (has `!`).
            let attr = if self.check(&token::Pound) && self.look_ahead(1, |t| t == &token::Not) {
                Some(self.parse_attribute(InnerAttrPolicy::Permitted)?)
            } else if let token::DocComment(comment_kind, attr_style, data) = self.token.kind {
                if attr_style == ast::AttrStyle::Inner {
                    self.bump();
                    Some(attr::mk_doc_comment(comment_kind, attr_style, data, self.prev_token.span))
                } else {
                    None
                }
            } else {
                None
            };
            if let Some(attr) = attr {
                attrs.push(attr);
            } else {
                break;
            }
        }
        Ok(attrs)
    }

    crate fn parse_unsuffixed_lit(&mut self) -> PResult<'a, ast::Lit> {
        let lit = self.parse_lit()?;
        debug!("checking if {:?} is unusuffixed", lit);

        if !lit.kind.is_unsuffixed() {
            self.struct_span_err(lit.span, "suffixed literals are not allowed in attributes")
                .help(
                    "instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), \
                    use an unsuffixed version (`1`, `1.0`, etc.)",
                )
                .emit();
        }

        Ok(lit)
    }

    /// Parses `cfg_attr(pred, attr_item_list)` where `attr_item_list` is comma-delimited.
    pub fn parse_cfg_attr(&mut self) -> PResult<'a, (ast::MetaItem, Vec<(ast::AttrItem, Span)>)> {
        let cfg_predicate = self.parse_meta_item()?;
        self.expect(&token::Comma)?;

        // Presumably, the majority of the time there will only be one attr.
        let mut expanded_attrs = Vec::with_capacity(1);
        while self.token.kind != token::Eof {
            let lo = self.token.span;
            let item = self.parse_attr_item(true)?;
            expanded_attrs.push((item, lo.to(self.prev_token.span)));
            if !self.eat(&token::Comma) {
                break;
            }
        }

        Ok((cfg_predicate, expanded_attrs))
    }

    /// Matches `COMMASEP(meta_item_inner)`.
    crate fn parse_meta_seq_top(&mut self) -> PResult<'a, Vec<ast::NestedMetaItem>> {
        // Presumably, the majority of the time there will only be one attr.
        let mut nmis = Vec::with_capacity(1);
        while self.token.kind != token::Eof {
            nmis.push(self.parse_meta_item_inner()?);
            if !self.eat(&token::Comma) {
                break;
            }
        }
        Ok(nmis)
    }

    /// Matches the following grammar (per RFC 1559).
    ///
    ///     meta_item : PATH ( '=' UNSUFFIXED_LIT | '(' meta_item_inner? ')' )? ;
    ///     meta_item_inner : (meta_item | UNSUFFIXED_LIT) (',' meta_item_inner)? ;
    pub fn parse_meta_item(&mut self) -> PResult<'a, ast::MetaItem> {
        let nt_meta = match self.token.kind {
            token::Interpolated(ref nt) => match **nt {
                token::NtMeta(ref e) => Some(e.clone()),
                _ => None,
            },
            _ => None,
        };

        if let Some(item) = nt_meta {
            return match item.meta(item.path.span) {
                Some(meta) => {
                    self.bump();
                    Ok(meta)
                }
                None => self.unexpected(),
            };
        }

        let lo = self.token.span;
        let path = self.parse_path(PathStyle::Mod)?;
        let kind = self.parse_meta_item_kind()?;
        let span = lo.to(self.prev_token.span);
        Ok(ast::MetaItem { path, kind, span })
    }

    crate fn parse_meta_item_kind(&mut self) -> PResult<'a, ast::MetaItemKind> {
        Ok(if self.eat(&token::Eq) {
            ast::MetaItemKind::NameValue(self.parse_unsuffixed_lit()?)
        } else if self.check(&token::OpenDelim(token::Paren)) {
            // Matches `meta_seq = ( COMMASEP(meta_item_inner) )`.
            let (list, _) = self.parse_paren_comma_seq(|p| p.parse_meta_item_inner())?;
            ast::MetaItemKind::List(list)
        } else {
            ast::MetaItemKind::Word
        })
    }

    /// Matches `meta_item_inner : (meta_item | UNSUFFIXED_LIT) ;`.
    fn parse_meta_item_inner(&mut self) -> PResult<'a, ast::NestedMetaItem> {
        match self.parse_unsuffixed_lit() {
            Ok(lit) => return Ok(ast::NestedMetaItem::Literal(lit)),
            Err(ref mut err) => err.cancel(),
        }

        match self.parse_meta_item() {
            Ok(mi) => return Ok(ast::NestedMetaItem::MetaItem(mi)),
            Err(ref mut err) => err.cancel(),
        }

        let found = pprust::token_to_string(&self.token);
        let msg = format!("expected unsuffixed literal or identifier, found `{}`", found);
        Err(self.struct_span_err(self.token.span, &msg))
    }
}

pub fn maybe_needs_tokens(attrs: &[ast::Attribute]) -> bool {
    // One of the attributes may either itself be a macro, or apply derive macros (`derive`),
    // or expand to macro attributes (`cfg_attr`).
    attrs.iter().any(|attr| {
        attr.ident().map_or(true, |ident| {
            ident.name == sym::derive
                || ident.name == sym::cfg_attr
                || !rustc_feature::is_builtin_attr_name(ident.name)
        })
    })
}
<commit_msg>Auto merge of #79433 - calebcartwright:parse-attr-vis, r=petrochenkov<commit_after>use super::{Parser, PathStyle};
use rustc_ast as ast;
use rustc_ast::attr;
use rustc_ast::token::{self, Nonterminal};
use rustc_ast_pretty::pprust;
use rustc_errors::{error_code, PResult};
use rustc_span::{sym, Span};

use tracing::debug;

// Public for rustfmt usage
#[derive(Debug)]
pub enum InnerAttrPolicy<'a> {
    Permitted,
    Forbidden { reason: &'a str, saw_doc_comment: bool, prev_attr_sp: Option<Span> },
}

const DEFAULT_UNEXPECTED_INNER_ATTR_ERR_MSG: &str = "an inner attribute is not \
                                                     permitted in this context";

pub(super) const DEFAULT_INNER_ATTR_FORBIDDEN: InnerAttrPolicy<'_> = InnerAttrPolicy::Forbidden {
    reason: DEFAULT_UNEXPECTED_INNER_ATTR_ERR_MSG,
    saw_doc_comment: false,
    prev_attr_sp: None,
};

impl<'a> Parser<'a> {
    /// Parses attributes that appear before an item.
    pub(super) fn parse_outer_attributes(&mut self) -> PResult<'a, Vec<ast::Attribute>> {
        let mut attrs: Vec<ast::Attribute> = Vec::new();
        let mut just_parsed_doc_comment = false;
        loop {
            debug!("parse_outer_attributes: self.token={:?}", self.token);
            let attr = if self.check(&token::Pound) {
                let inner_error_reason = if just_parsed_doc_comment {
                    "an inner attribute is not permitted following an outer doc comment"
                } else if !attrs.is_empty() {
                    "an inner attribute is not permitted following an outer attribute"
                } else {
                    DEFAULT_UNEXPECTED_INNER_ATTR_ERR_MSG
                };
                let inner_parse_policy = InnerAttrPolicy::Forbidden {
                    reason: inner_error_reason,
                    saw_doc_comment: just_parsed_doc_comment,
                    prev_attr_sp: attrs.last().map(|a| a.span),
                };
                just_parsed_doc_comment = false;
                Some(self.parse_attribute(inner_parse_policy)?)
            } else if let token::DocComment(comment_kind, attr_style, data) = self.token.kind {
                if attr_style != ast::AttrStyle::Outer {
                    self.sess
                        .span_diagnostic
                        .struct_span_err_with_code(
                            self.token.span,
                            "expected outer doc comment",
                            error_code!(E0753),
                        )
                        .note(
                            "inner doc comments like this (starting with \
                         `//!` or `/*!`) can only appear before items",
                        )
                        .emit();
                }
                self.bump();
                just_parsed_doc_comment = true;
                Some(attr::mk_doc_comment(comment_kind, attr_style, data, self.prev_token.span))
            } else {
                None
            };

            if let Some(attr) = attr {
                attrs.push(attr);
            } else {
                break;
            }
        }
        Ok(attrs)
    }

    /// Matches `attribute = # ! [ meta_item ]`.
    /// `inner_parse_policy` prescribes how to handle inner attributes.
    // Public for rustfmt usage.
    pub fn parse_attribute(
        &mut self,
        inner_parse_policy: InnerAttrPolicy<'_>,
    ) -> PResult<'a, ast::Attribute> {
        debug!(
            "parse_attribute: inner_parse_policy={:?} self.token={:?}",
            inner_parse_policy, self.token
        );
        let lo = self.token.span;
        let ((item, style, span), tokens) = self.collect_tokens(|this| {
            if this.eat(&token::Pound) {
                let style = if this.eat(&token::Not) {
                    ast::AttrStyle::Inner
                } else {
                    ast::AttrStyle::Outer
                };

                this.expect(&token::OpenDelim(token::Bracket))?;
                let item = this.parse_attr_item(false)?;
                this.expect(&token::CloseDelim(token::Bracket))?;
                let attr_sp = lo.to(this.prev_token.span);

                // Emit error if inner attribute is encountered and forbidden.
                if style == ast::AttrStyle::Inner {
                    this.error_on_forbidden_inner_attr(attr_sp, inner_parse_policy);
                }

                Ok((item, style, attr_sp))
            } else {
                let token_str = pprust::token_to_string(&this.token);
                let msg = &format!("expected `#`, found `{}`", token_str);
                Err(this.struct_span_err(this.token.span, msg))
            }
        })?;

        Ok(attr::mk_attr_from_item(item, tokens, style, span))
    }

    pub(super) fn error_on_forbidden_inner_attr(&self, attr_sp: Span, policy: InnerAttrPolicy<'_>) {
        if let InnerAttrPolicy::Forbidden { reason, saw_doc_comment, prev_attr_sp } = policy {
            let prev_attr_note =
                if saw_doc_comment { "previous doc comment" } else { "previous outer attribute" };

            let mut diag = self.struct_span_err(attr_sp, reason);

            if let Some(prev_attr_sp) = prev_attr_sp {
                diag.span_label(attr_sp, "not permitted following an outer attribute")
                    .span_label(prev_attr_sp, prev_attr_note);
            }

            diag.note(
                "inner attributes, like `#![no_std]`, annotate the item enclosing them, \
                and are usually found at the beginning of source files. \
                Outer attributes, like `#[test]`, annotate the item following them.",
            )
            .emit();
        }
    }

    /// Parses an inner part of an attribute (the path and following tokens).
    /// The tokens must be either a delimited token stream, or empty token stream,
    /// or the "legacy" key-value form.
    ///     PATH `(` TOKEN_STREAM `)`
    ///     PATH `[` TOKEN_STREAM `]`
    ///     PATH `{` TOKEN_STREAM `}`
    ///     PATH
    ///     PATH `=` UNSUFFIXED_LIT
    /// The delimiters or `=` are still put into the resulting token stream.
    pub fn parse_attr_item(&mut self, capture_tokens: bool) -> PResult<'a, ast::AttrItem> {
        let item = match self.token.kind {
            token::Interpolated(ref nt) => match **nt {
                Nonterminal::NtMeta(ref item) => Some(item.clone().into_inner()),
                _ => None,
            },
            _ => None,
        };
        Ok(if let Some(item) = item {
            self.bump();
            item
        } else {
            let do_parse = |this: &mut Self| {
                let path = this.parse_path(PathStyle::Mod)?;
                let args = this.parse_attr_args()?;
                Ok(ast::AttrItem { path, args, tokens: None })
            };
            if capture_tokens {
                let (mut item, tokens) = self.collect_tokens(do_parse)?;
                item.tokens = tokens;
                item
            } else {
                do_parse(self)?
            }
        })
    }

    /// Parses attributes that appear after the opening of an item. These should
    /// be preceded by an exclamation mark, but we accept and warn about one
    /// terminated by a semicolon.
    ///
    /// Matches `inner_attrs*`.
    crate fn parse_inner_attributes(&mut self) -> PResult<'a, Vec<ast::Attribute>> {
        let mut attrs: Vec<ast::Attribute> = vec![];
        loop {
            // Only try to parse if it is an inner attribute (has `!`).
            let attr = if self.check(&token::Pound) && self.look_ahead(1, |t| t == &token::Not) {
                Some(self.parse_attribute(InnerAttrPolicy::Permitted)?)
            } else if let token::DocComment(comment_kind, attr_style, data) = self.token.kind {
                if attr_style == ast::AttrStyle::Inner {
                    self.bump();
                    Some(attr::mk_doc_comment(comment_kind, attr_style, data, self.prev_token.span))
                } else {
                    None
                }
            } else {
                None
            };
            if let Some(attr) = attr {
                attrs.push(attr);
            } else {
                break;
            }
        }
        Ok(attrs)
    }

    crate fn parse_unsuffixed_lit(&mut self) -> PResult<'a, ast::Lit> {
        let lit = self.parse_lit()?;
        debug!("checking if {:?} is unusuffixed", lit);

        if !lit.kind.is_unsuffixed() {
            self.struct_span_err(lit.span, "suffixed literals are not allowed in attributes")
                .help(
                    "instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), \
                    use an unsuffixed version (`1`, `1.0`, etc.)",
                )
                .emit();
        }

        Ok(lit)
    }

    /// Parses `cfg_attr(pred, attr_item_list)` where `attr_item_list` is comma-delimited.
    pub fn parse_cfg_attr(&mut self) -> PResult<'a, (ast::MetaItem, Vec<(ast::AttrItem, Span)>)> {
        let cfg_predicate = self.parse_meta_item()?;
        self.expect(&token::Comma)?;

        // Presumably, the majority of the time there will only be one attr.
        let mut expanded_attrs = Vec::with_capacity(1);
        while self.token.kind != token::Eof {
            let lo = self.token.span;
            let item = self.parse_attr_item(true)?;
            expanded_attrs.push((item, lo.to(self.prev_token.span)));
            if !self.eat(&token::Comma) {
                break;
            }
        }

        Ok((cfg_predicate, expanded_attrs))
    }

    /// Matches `COMMASEP(meta_item_inner)`.
    crate fn parse_meta_seq_top(&mut self) -> PResult<'a, Vec<ast::NestedMetaItem>> {
        // Presumably, the majority of the time there will only be one attr.
        let mut nmis = Vec::with_capacity(1);
        while self.token.kind != token::Eof {
            nmis.push(self.parse_meta_item_inner()?);
            if !self.eat(&token::Comma) {
                break;
            }
        }
        Ok(nmis)
    }

    /// Matches the following grammar (per RFC 1559).
    ///
    ///     meta_item : PATH ( '=' UNSUFFIXED_LIT | '(' meta_item_inner? ')' )? ;
    ///     meta_item_inner : (meta_item | UNSUFFIXED_LIT) (',' meta_item_inner)? ;
    pub fn parse_meta_item(&mut self) -> PResult<'a, ast::MetaItem> {
        let nt_meta = match self.token.kind {
            token::Interpolated(ref nt) => match **nt {
                token::NtMeta(ref e) => Some(e.clone()),
                _ => None,
            },
            _ => None,
        };

        if let Some(item) = nt_meta {
            return match item.meta(item.path.span) {
                Some(meta) => {
                    self.bump();
                    Ok(meta)
                }
                None => self.unexpected(),
            };
        }

        let lo = self.token.span;
        let path = self.parse_path(PathStyle::Mod)?;
        let kind = self.parse_meta_item_kind()?;
        let span = lo.to(self.prev_token.span);
        Ok(ast::MetaItem { path, kind, span })
    }

    crate fn parse_meta_item_kind(&mut self) -> PResult<'a, ast::MetaItemKind> {
        Ok(if self.eat(&token::Eq) {
            ast::MetaItemKind::NameValue(self.parse_unsuffixed_lit()?)
        } else if self.check(&token::OpenDelim(token::Paren)) {
            // Matches `meta_seq = ( COMMASEP(meta_item_inner) )`.
            let (list, _) = self.parse_paren_comma_seq(|p| p.parse_meta_item_inner())?;
            ast::MetaItemKind::List(list)
        } else {
            ast::MetaItemKind::Word
        })
    }

    /// Matches `meta_item_inner : (meta_item | UNSUFFIXED_LIT) ;`.
    fn parse_meta_item_inner(&mut self) -> PResult<'a, ast::NestedMetaItem> {
        match self.parse_unsuffixed_lit() {
            Ok(lit) => return Ok(ast::NestedMetaItem::Literal(lit)),
            Err(ref mut err) => err.cancel(),
        }

        match self.parse_meta_item() {
            Ok(mi) => return Ok(ast::NestedMetaItem::MetaItem(mi)),
            Err(ref mut err) => err.cancel(),
        }

        let found = pprust::token_to_string(&self.token);
        let msg = format!("expected unsuffixed literal or identifier, found `{}`", found);
        Err(self.struct_span_err(self.token.span, &msg))
    }
}

pub fn maybe_needs_tokens(attrs: &[ast::Attribute]) -> bool {
    // One of the attributes may either itself be a macro, or apply derive macros (`derive`),
    // or expand to macro attributes (`cfg_attr`).
    attrs.iter().any(|attr| {
        attr.ident().map_or(true, |ident| {
            ident.name == sym::derive
                || ident.name == sym::cfg_attr
                || !rustc_feature::is_builtin_attr_name(ident.name)
        })
    })
}
<|endoftext|>


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

use dom::bindings::codegen::Bindings::HTMLCollectionBinding;
use dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods;
use dom::bindings::global::GlobalRef;
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{JS, Root, MutNullableHeap};
use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::bindings::trace::JSTraceable;
use dom::bindings::xmlname::namespace_from_domstring;
use dom::element::Element;
use dom::node::{Node, FollowingNodeIterator, PrecedingNodeIterator};
use dom::window::Window;
use std::ascii::AsciiExt;
use std::cell::Cell;
use string_cache::{Atom, Namespace, QualName};
use util::str::{DOMString, split_html_space_chars};

pub trait CollectionFilter : JSTraceable {
    fn filter<'a>(&self, elem: &'a Element, root: &'a Node) -> bool;
}

// An optional u32, using maxint to represent None.
// It would be nicer just to use Option<u32> for this, but that would produce word
// alignment issues since Option<u32> uses 33 bits.
#[derive(Clone, Copy, JSTraceable, HeapSizeOf)]
struct OptionU32 {
    bits: u32,
}

impl OptionU32 {
    fn to_option(self) -> Option<u32> {
        if self.bits == u32::max_value() {
            None
        } else {
            Some(self.bits)
        }
    }

    fn some(bits: u32) -> OptionU32 {
        assert!(bits != u32::max_value());
        OptionU32 { bits: bits }
    }

    fn none() -> OptionU32 {
        OptionU32 { bits: u32::max_value() }
    }
}

#[dom_struct]
pub struct HTMLCollection {
    reflector_: Reflector,
    root: JS<Node>,
    #[ignore_heap_size_of = "Contains a trait object; can't measure due to #6870"]
    filter: Box<CollectionFilter + 'static>,
    // We cache the version of the root node and all its decendents,
    // the length of the collection, and a cursor into the collection.
    // FIXME: make the cached cursor element a weak pointer
    cached_version: Cell<u64>,
    cached_cursor_element: MutNullableHeap<JS<Element>>,
    cached_cursor_index: Cell<OptionU32>,
    cached_length: Cell<OptionU32>,
}

impl HTMLCollection {
    #[allow(unrooted_must_root)]
    pub fn new_inherited(root: &Node, filter: Box<CollectionFilter + 'static>) -> HTMLCollection {
        HTMLCollection {
            reflector_: Reflector::new(),
            root: JS::from_ref(root),
            filter: filter,
            // Default values for the cache
            cached_version: Cell::new(root.get_inclusive_descendants_version()),
            cached_cursor_element: MutNullableHeap::new(None),
            cached_cursor_index: Cell::new(OptionU32::none()),
            cached_length: Cell::new(OptionU32::none()),
        }
    }

    #[allow(unrooted_must_root)]
    pub fn new(window: &Window, root: &Node, filter: Box<CollectionFilter + 'static>) -> Root<HTMLCollection> {
        reflect_dom_object(box HTMLCollection::new_inherited(root, filter),
                           GlobalRef::Window(window), HTMLCollectionBinding::Wrap)
    }

    pub fn create(window: &Window, root: &Node,
                  filter: Box<CollectionFilter + 'static>) -> Root<HTMLCollection> {
        HTMLCollection::new(window, root, filter)
    }

    fn validate_cache(&self) {
        // Clear the cache if the root version is different from our cached version
        let cached_version = self.cached_version.get();
        let curr_version = self.root.get_inclusive_descendants_version();
        if curr_version != cached_version {
            // Default values for the cache
            self.cached_version.set(curr_version);
            self.cached_cursor_element.set(None);
            self.cached_length.set(OptionU32::none());
            self.cached_cursor_index.set(OptionU32::none());
        }
    }

    fn set_cached_cursor(&self, index: u32, element: Option<Root<Element>>) -> Option<Root<Element>> {
        if let Some(element) = element {
            self.cached_cursor_index.set(OptionU32::some(index));
            self.cached_cursor_element.set(Some(element.r()));
            Some(element)
        } else {
            None
        }
    }

    pub fn by_tag_name(window: &Window, root: &Node, mut tag: DOMString)
                       -> Root<HTMLCollection> {
        let tag_atom = Atom::from(&*tag);
        tag.make_ascii_lowercase();
        let ascii_lower_tag = Atom::from(tag); // FIXME(ajeffrey): don't clone atom if it was already lowercased.
        HTMLCollection::by_atomic_tag_name(window, root, tag_atom, ascii_lower_tag)
    }

    pub fn by_atomic_tag_name(window: &Window, root: &Node, tag_atom: Atom, ascii_lower_tag: Atom)
                       -> Root<HTMLCollection> {
        #[derive(JSTraceable, HeapSizeOf)]
        struct TagNameFilter {
            tag: Atom,
            ascii_lower_tag: Atom,
        }
        impl CollectionFilter for TagNameFilter {
            fn filter(&self, elem: &Element, _root: &Node) -> bool {
                if self.tag == atom!("*") {
                    true
                } else if elem.html_element_in_html_document() {
                    *elem.local_name() == self.ascii_lower_tag
                } else {
                    *elem.local_name() == self.tag
                }
            }
        }
        let filter = TagNameFilter {
            tag: tag_atom,
            ascii_lower_tag: ascii_lower_tag,
        };
        HTMLCollection::create(window, root, box filter)
    }

    pub fn by_tag_name_ns(window: &Window, root: &Node, tag: DOMString,
                          maybe_ns: Option<DOMString>) -> Root<HTMLCollection> {
        let local = Atom::from(tag);
        let ns = namespace_from_domstring(maybe_ns);
        let qname = QualName::new(ns, local);
        HTMLCollection::by_qual_tag_name(window, root, qname)
    }

    pub fn by_qual_tag_name(window: &Window, root: &Node, qname: QualName) -> Root<HTMLCollection> {
        #[derive(JSTraceable, HeapSizeOf)]
        struct TagNameNSFilter {
            qname: QualName
        }
        impl CollectionFilter for TagNameNSFilter {
            fn filter(&self, elem: &Element, _root: &Node) -> bool {
                    ((self.qname.ns == Namespace(atom!("*"))) || (self.qname.ns == *elem.namespace()))
                &&  ((self.qname.local == atom!("*")) || (self.qname.local == *elem.local_name()))
            }
        }
        let filter = TagNameNSFilter {
            qname: qname
        };
        HTMLCollection::create(window, root, box filter)
    }

    pub fn by_class_name(window: &Window, root: &Node, classes: DOMString)
                         -> Root<HTMLCollection> {
        let class_atoms = split_html_space_chars(&classes).map(Atom::from).collect();
        HTMLCollection::by_atomic_class_name(window, root, class_atoms)
    }

    pub fn by_atomic_class_name(window: &Window, root: &Node, classes: Vec<Atom>)
                         -> Root<HTMLCollection> {
        #[derive(JSTraceable, HeapSizeOf)]
        struct ClassNameFilter {
            classes: Vec<Atom>
        }
        impl CollectionFilter for ClassNameFilter {
            fn filter(&self, elem: &Element, _root: &Node) -> bool {
                self.classes.iter().all(|class| elem.has_class(class))
            }
        }
        let filter = ClassNameFilter {
            classes: classes
        };
        HTMLCollection::create(window, root, box filter)
    }

    pub fn children(window: &Window, root: &Node) -> Root<HTMLCollection> {
        #[derive(JSTraceable, HeapSizeOf)]
        struct ElementChildFilter;
        impl CollectionFilter for ElementChildFilter {
            fn filter(&self, elem: &Element, root: &Node) -> bool {
                root.is_parent_of(elem.upcast())
            }
        }
        HTMLCollection::create(window, root, box ElementChildFilter)
    }

    pub fn elements_iter_after(&self, after: &Node) -> HTMLCollectionElementsIter {
        // Iterate forwards from a node.
        HTMLCollectionElementsIter {
            node_iter: after.following_nodes(&self.root),
            root: Root::from_ref(&self.root),
            filter: &self.filter,
        }
    }

    pub fn elements_iter(&self) -> HTMLCollectionElementsIter {
        // Iterate forwards from the root.
        self.elements_iter_after(&*self.root)
    }

    pub fn elements_iter_before(&self, before: &Node) -> HTMLCollectionElementsRevIter {
        // Iterate backwards from a node.
        HTMLCollectionElementsRevIter {
            node_iter: before.preceding_nodes(&self.root),
            root: Root::from_ref(&self.root),
            filter: &self.filter,
        }
    }

}

// TODO: Make this generic, and avoid code duplication
pub struct HTMLCollectionElementsIter<'a> {
    node_iter: FollowingNodeIterator,
    root: Root<Node>,
    filter: &'a Box<CollectionFilter>,
}

impl<'a> Iterator for HTMLCollectionElementsIter<'a> {
    type Item = Root<Element>;

    fn next(&mut self) -> Option<Self::Item> {
        let filter = &self.filter;
        let root = &self.root;
        self.node_iter.by_ref()
                      .filter_map(Root::downcast)
                      .filter(|element| filter.filter(&element, root))
                      .next()
   }
}

pub struct HTMLCollectionElementsRevIter<'a> {
    node_iter: PrecedingNodeIterator,
    root: Root<Node>,
    filter: &'a Box<CollectionFilter>,
}

impl<'a> Iterator for HTMLCollectionElementsRevIter<'a> {
    type Item = Root<Element>;

    fn next(&mut self) -> Option<Self::Item> {
        let filter = &self.filter;
        let root = &self.root;
        self.node_iter.by_ref()
            .filter_map(Root::downcast)
            .filter(|element| filter.filter(&element, root))
            .next()
    }
}

impl HTMLCollectionMethods for HTMLCollection {
    // https://dom.spec.whatwg.org/#dom-htmlcollection-length
    fn Length(&self) -> u32 {
        self.validate_cache();

        if let Some(cached_length) = self.cached_length.get().to_option() {
            // Cache hit
            cached_length
        } else {
            // Cache miss, calculate the length
            let length = self.elements_iter().count() as u32;
            self.cached_length.set(OptionU32::some(length));
            length
        }
    }

    // https://dom.spec.whatwg.org/#dom-htmlcollection-item
    fn Item(&self, index: u32) -> Option<Root<Element>> {
        self.validate_cache();

        if let Some(element) = self.cached_cursor_element.get() {
            // Cache hit, the cursor element is set
            if let Some(cached_index) = self.cached_cursor_index.get().to_option() {
                if cached_index == index {
                    // The cursor is the element we're looking for
                    Some(element)
                } else if cached_index < index {
                    // The cursor is before the element we're looking for
                    // Iterate forwards, starting at the cursor.
                    let offset = index - (cached_index + 1);
                    let node: Root<Node> = Root::upcast(element);
                    self.set_cached_cursor(index, self.elements_iter_after(node.r()).nth(offset as usize))
                } else {
                    // The cursor is after the element we're looking for
                    // Iterate backwards, starting at the cursor.
                    let offset = cached_index - (index + 1);
                    let node: Root<Node> = Root::upcast(element);
                    self.set_cached_cursor(index, self.elements_iter_before(node.r()).nth(offset as usize))
                }
            } else {
                // Cache miss
                // Iterate forwards through all the nodes
                self.set_cached_cursor(index, self.elements_iter().nth(index as usize))
            }
        } else {
            // Cache miss
            // Iterate forwards through all the nodes
            self.set_cached_cursor(index, self.elements_iter().nth(index as usize))
        }
    }

    // https://dom.spec.whatwg.org/#dom-htmlcollection-nameditem
    fn NamedItem(&self, key: DOMString) -> Option<Root<Element>> {
        // Step 1.
        if key.is_empty() {
            return None;
        }

        // Step 2.
        self.elements_iter().find(|elem| {
            elem.get_string_attribute(&atom!("id")) == key ||
            (elem.namespace() == &ns!(html) && elem.get_string_attribute(&atom!("name")) == key)
        })
    }

    // https://dom.spec.whatwg.org/#dom-htmlcollection-item
    fn IndexedGetter(&self, index: u32, found: &mut bool) -> Option<Root<Element>> {
        let maybe_elem = self.Item(index);
        *found = maybe_elem.is_some();
        maybe_elem
    }

    // check-tidy: no specs after this line
    fn NamedGetter(&self, name: DOMString, found: &mut bool) -> Option<Root<Element>> {
        let maybe_elem = self.NamedItem(name);
        *found = maybe_elem.is_some();
        maybe_elem
    }

    // https://dom.spec.whatwg.org/#interface-htmlcollection
    fn SupportedPropertyNames(&self) -> Vec<DOMString> {
        // Step 1
        let mut result = vec![];

        // Step 2
        for elem in self.elements_iter() {
            // Step 2.1
            let id_attr = elem.get_string_attribute(&atom!("id"));
            if !id_attr.is_empty() && !result.contains(&id_attr) {
                result.push(id_attr)
            }
            // Step 2.2
            let name_attr = elem.get_string_attribute(&atom!("name"));
            if !name_attr.is_empty() && !result.contains(&name_attr) && *elem.namespace() == ns!(html) {
                result.push(name_attr)
            }
        }

        // Step 3
        result
    }
}
<commit_msg>Auto merge of #10094 - KiChjang:remove-extra-iter, r=asajeffrey<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use dom::bindings::codegen::Bindings::HTMLCollectionBinding;
use dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods;
use dom::bindings::global::GlobalRef;
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{JS, Root, MutNullableHeap};
use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::bindings::trace::JSTraceable;
use dom::bindings::xmlname::namespace_from_domstring;
use dom::element::Element;
use dom::node::Node;
use dom::window::Window;
use std::ascii::AsciiExt;
use std::cell::Cell;
use string_cache::{Atom, Namespace, QualName};
use util::str::{DOMString, split_html_space_chars};

pub trait CollectionFilter : JSTraceable {
    fn filter<'a>(&self, elem: &'a Element, root: &'a Node) -> bool;
}

// An optional u32, using maxint to represent None.
// It would be nicer just to use Option<u32> for this, but that would produce word
// alignment issues since Option<u32> uses 33 bits.
#[derive(Clone, Copy, JSTraceable, HeapSizeOf)]
struct OptionU32 {
    bits: u32,
}

impl OptionU32 {
    fn to_option(self) -> Option<u32> {
        if self.bits == u32::max_value() {
            None
        } else {
            Some(self.bits)
        }
    }

    fn some(bits: u32) -> OptionU32 {
        assert!(bits != u32::max_value());
        OptionU32 { bits: bits }
    }

    fn none() -> OptionU32 {
        OptionU32 { bits: u32::max_value() }
    }
}

#[dom_struct]
pub struct HTMLCollection {
    reflector_: Reflector,
    root: JS<Node>,
    #[ignore_heap_size_of = "Contains a trait object; can't measure due to #6870"]
    filter: Box<CollectionFilter + 'static>,
    // We cache the version of the root node and all its decendents,
    // the length of the collection, and a cursor into the collection.
    // FIXME: make the cached cursor element a weak pointer
    cached_version: Cell<u64>,
    cached_cursor_element: MutNullableHeap<JS<Element>>,
    cached_cursor_index: Cell<OptionU32>,
    cached_length: Cell<OptionU32>,
}

impl HTMLCollection {
    #[allow(unrooted_must_root)]
    pub fn new_inherited(root: &Node, filter: Box<CollectionFilter + 'static>) -> HTMLCollection {
        HTMLCollection {
            reflector_: Reflector::new(),
            root: JS::from_ref(root),
            filter: filter,
            // Default values for the cache
            cached_version: Cell::new(root.get_inclusive_descendants_version()),
            cached_cursor_element: MutNullableHeap::new(None),
            cached_cursor_index: Cell::new(OptionU32::none()),
            cached_length: Cell::new(OptionU32::none()),
        }
    }

    #[allow(unrooted_must_root)]
    pub fn new(window: &Window, root: &Node, filter: Box<CollectionFilter + 'static>) -> Root<HTMLCollection> {
        reflect_dom_object(box HTMLCollection::new_inherited(root, filter),
                           GlobalRef::Window(window), HTMLCollectionBinding::Wrap)
    }

    pub fn create(window: &Window, root: &Node,
                  filter: Box<CollectionFilter + 'static>) -> Root<HTMLCollection> {
        HTMLCollection::new(window, root, filter)
    }

    fn validate_cache(&self) {
        // Clear the cache if the root version is different from our cached version
        let cached_version = self.cached_version.get();
        let curr_version = self.root.get_inclusive_descendants_version();
        if curr_version != cached_version {
            // Default values for the cache
            self.cached_version.set(curr_version);
            self.cached_cursor_element.set(None);
            self.cached_length.set(OptionU32::none());
            self.cached_cursor_index.set(OptionU32::none());
        }
    }

    fn set_cached_cursor(&self, index: u32, element: Option<Root<Element>>) -> Option<Root<Element>> {
        if let Some(element) = element {
            self.cached_cursor_index.set(OptionU32::some(index));
            self.cached_cursor_element.set(Some(element.r()));
            Some(element)
        } else {
            None
        }
    }

    pub fn by_tag_name(window: &Window, root: &Node, mut tag: DOMString)
                       -> Root<HTMLCollection> {
        let tag_atom = Atom::from(&*tag);
        tag.make_ascii_lowercase();
        let ascii_lower_tag = Atom::from(tag); // FIXME(ajeffrey): don't clone atom if it was already lowercased.
        HTMLCollection::by_atomic_tag_name(window, root, tag_atom, ascii_lower_tag)
    }

    pub fn by_atomic_tag_name(window: &Window, root: &Node, tag_atom: Atom, ascii_lower_tag: Atom)
                       -> Root<HTMLCollection> {
        #[derive(JSTraceable, HeapSizeOf)]
        struct TagNameFilter {
            tag: Atom,
            ascii_lower_tag: Atom,
        }
        impl CollectionFilter for TagNameFilter {
            fn filter(&self, elem: &Element, _root: &Node) -> bool {
                if self.tag == atom!("*") {
                    true
                } else if elem.html_element_in_html_document() {
                    *elem.local_name() == self.ascii_lower_tag
                } else {
                    *elem.local_name() == self.tag
                }
            }
        }
        let filter = TagNameFilter {
            tag: tag_atom,
            ascii_lower_tag: ascii_lower_tag,
        };
        HTMLCollection::create(window, root, box filter)
    }

    pub fn by_tag_name_ns(window: &Window, root: &Node, tag: DOMString,
                          maybe_ns: Option<DOMString>) -> Root<HTMLCollection> {
        let local = Atom::from(tag);
        let ns = namespace_from_domstring(maybe_ns);
        let qname = QualName::new(ns, local);
        HTMLCollection::by_qual_tag_name(window, root, qname)
    }

    pub fn by_qual_tag_name(window: &Window, root: &Node, qname: QualName) -> Root<HTMLCollection> {
        #[derive(JSTraceable, HeapSizeOf)]
        struct TagNameNSFilter {
            qname: QualName
        }
        impl CollectionFilter for TagNameNSFilter {
            fn filter(&self, elem: &Element, _root: &Node) -> bool {
                    ((self.qname.ns == Namespace(atom!("*"))) || (self.qname.ns == *elem.namespace()))
                &&  ((self.qname.local == atom!("*")) || (self.qname.local == *elem.local_name()))
            }
        }
        let filter = TagNameNSFilter {
            qname: qname
        };
        HTMLCollection::create(window, root, box filter)
    }

    pub fn by_class_name(window: &Window, root: &Node, classes: DOMString)
                         -> Root<HTMLCollection> {
        let class_atoms = split_html_space_chars(&classes).map(Atom::from).collect();
        HTMLCollection::by_atomic_class_name(window, root, class_atoms)
    }

    pub fn by_atomic_class_name(window: &Window, root: &Node, classes: Vec<Atom>)
                         -> Root<HTMLCollection> {
        #[derive(JSTraceable, HeapSizeOf)]
        struct ClassNameFilter {
            classes: Vec<Atom>
        }
        impl CollectionFilter for ClassNameFilter {
            fn filter(&self, elem: &Element, _root: &Node) -> bool {
                self.classes.iter().all(|class| elem.has_class(class))
            }
        }
        let filter = ClassNameFilter {
            classes: classes
        };
        HTMLCollection::create(window, root, box filter)
    }

    pub fn children(window: &Window, root: &Node) -> Root<HTMLCollection> {
        #[derive(JSTraceable, HeapSizeOf)]
        struct ElementChildFilter;
        impl CollectionFilter for ElementChildFilter {
            fn filter(&self, elem: &Element, root: &Node) -> bool {
                root.is_parent_of(elem.upcast())
            }
        }
        HTMLCollection::create(window, root, box ElementChildFilter)
    }

    pub fn elements_iter_after(&self, after: &Node) -> HTMLCollectionElementsIter {
        // Iterate forwards from a node.
        HTMLCollectionElementsIter {
            node_iter: box after.following_nodes(&self.root),
            root: Root::from_ref(&self.root),
            filter: &self.filter,
        }
    }

    pub fn elements_iter(&self) -> HTMLCollectionElementsIter {
        // Iterate forwards from the root.
        self.elements_iter_after(&*self.root)
    }

    pub fn elements_iter_before(&self, before: &Node) -> HTMLCollectionElementsIter {
        // Iterate backwards from a node.
        HTMLCollectionElementsIter {
            node_iter: box before.preceding_nodes(&self.root),
            root: Root::from_ref(&self.root),
            filter: &self.filter,
        }
    }

}

// TODO: Make this generic, and avoid code duplication
pub struct HTMLCollectionElementsIter<'a> {
    node_iter: Box<Iterator<Item = Root<Node>>>,
    root: Root<Node>,
    filter: &'a Box<CollectionFilter>,
}

impl<'a> Iterator for HTMLCollectionElementsIter<'a> {
    type Item = Root<Element>;

    fn next(&mut self) -> Option<Self::Item> {
        let filter = &self.filter;
        let root = &self.root;
        self.node_iter.by_ref()
                      .filter_map(Root::downcast)
                      .filter(|element| filter.filter(&element, root))
                      .next()
   }
}

impl HTMLCollectionMethods for HTMLCollection {
    // https://dom.spec.whatwg.org/#dom-htmlcollection-length
    fn Length(&self) -> u32 {
        self.validate_cache();

        if let Some(cached_length) = self.cached_length.get().to_option() {
            // Cache hit
            cached_length
        } else {
            // Cache miss, calculate the length
            let length = self.elements_iter().count() as u32;
            self.cached_length.set(OptionU32::some(length));
            length
        }
    }

    // https://dom.spec.whatwg.org/#dom-htmlcollection-item
    fn Item(&self, index: u32) -> Option<Root<Element>> {
        self.validate_cache();

        if let Some(element) = self.cached_cursor_element.get() {
            // Cache hit, the cursor element is set
            if let Some(cached_index) = self.cached_cursor_index.get().to_option() {
                if cached_index == index {
                    // The cursor is the element we're looking for
                    Some(element)
                } else if cached_index < index {
                    // The cursor is before the element we're looking for
                    // Iterate forwards, starting at the cursor.
                    let offset = index - (cached_index + 1);
                    let node: Root<Node> = Root::upcast(element);
                    self.set_cached_cursor(index, self.elements_iter_after(node.r()).nth(offset as usize))
                } else {
                    // The cursor is after the element we're looking for
                    // Iterate backwards, starting at the cursor.
                    let offset = cached_index - (index + 1);
                    let node: Root<Node> = Root::upcast(element);
                    self.set_cached_cursor(index, self.elements_iter_before(node.r()).nth(offset as usize))
                }
            } else {
                // Cache miss
                // Iterate forwards through all the nodes
                self.set_cached_cursor(index, self.elements_iter().nth(index as usize))
            }
        } else {
            // Cache miss
            // Iterate forwards through all the nodes
            self.set_cached_cursor(index, self.elements_iter().nth(index as usize))
        }
    }

    // https://dom.spec.whatwg.org/#dom-htmlcollection-nameditem
    fn NamedItem(&self, key: DOMString) -> Option<Root<Element>> {
        // Step 1.
        if key.is_empty() {
            return None;
        }

        // Step 2.
        self.elements_iter().find(|elem| {
            elem.get_string_attribute(&atom!("id")) == key ||
            (elem.namespace() == &ns!(html) && elem.get_string_attribute(&atom!("name")) == key)
        })
    }

    // https://dom.spec.whatwg.org/#dom-htmlcollection-item
    fn IndexedGetter(&self, index: u32, found: &mut bool) -> Option<Root<Element>> {
        let maybe_elem = self.Item(index);
        *found = maybe_elem.is_some();
        maybe_elem
    }

    // check-tidy: no specs after this line
    fn NamedGetter(&self, name: DOMString, found: &mut bool) -> Option<Root<Element>> {
        let maybe_elem = self.NamedItem(name);
        *found = maybe_elem.is_some();
        maybe_elem
    }

    // https://dom.spec.whatwg.org/#interface-htmlcollection
    fn SupportedPropertyNames(&self) -> Vec<DOMString> {
        // Step 1
        let mut result = vec![];

        // Step 2
        for elem in self.elements_iter() {
            // Step 2.1
            let id_attr = elem.get_string_attribute(&atom!("id"));
            if !id_attr.is_empty() && !result.contains(&id_attr) {
                result.push(id_attr)
            }
            // Step 2.2
            let name_attr = elem.get_string_attribute(&atom!("name"));
            if !name_attr.is_empty() && !result.contains(&name_attr) && *elem.namespace() == ns!(html) {
                result.push(name_attr)
            }
        }

        // Step 3
        result
    }
}
<|endoftext|>