<commit_before>//! Script to check the validity of `href` links in our HTML documentation.
//!
//! In the past we've been quite error prone to writing in broken links as most
//! of them are manually rather than automatically added. As files move over
//! time or apis change old links become stale or broken. The purpose of this
//! script is to check all relative links in our documentation to make sure they
//! actually point to a valid place.
//!
//! Currently this doesn't actually do any HTML parsing or anything fancy like
//! that, it just has a simple "regex" to search for `href` and `id` tags.
//! These values are then translated to file URLs if possible and then the
//! destination is asserted to exist.
//!
//! A few whitelisted exceptions are allowed as there's known bugs in rustdoc,
//! but this should catch the majority of "broken link" cases.

#![deny(rust_2018_idioms)]

use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
use std::env;
use std::fs;
use std::path::{Path, PathBuf, Component};
use std::rc::Rc;

use crate::Redirect::*;

macro_rules! t {
    ($e:expr) => (match $e {
        Ok(e) => e,
        Err(e) => panic!("{} failed with {:?}", stringify!($e), e),
    })
}

fn main() {
    let docs = env::args_os().nth(1).unwrap();
    let docs = env::current_dir().unwrap().join(docs);
    let mut errors = false;
    walk(&mut HashMap::new(), &docs, &docs, &mut errors);
    if errors {
        panic!("found some broken links");
    }
}

#[derive(Debug)]
pub enum LoadError {
    IOError(std::io::Error),
    BrokenRedirect(PathBuf, std::io::Error),
    IsRedirect,
}

enum Redirect {
    SkipRedirect,
    FromRedirect(bool),
}

struct FileEntry {
    source: Rc<String>,
    ids: HashSet<String>,
}

type Cache = HashMap<PathBuf, FileEntry>;

fn small_url_encode(s: &str) -> String {
    s.replace("<", "%3C")
     .replace(">", "%3E")
     .replace(" ", "%20")
     .replace("?", "%3F")
     .replace("'", "%27")
     .replace("&", "%26")
     .replace(",", "%2C")
     .replace(":", "%3A")
     .replace(";", "%3B")
     .replace("[", "%5B")
     .replace("]", "%5D")
     .replace("\"", "%22")
}

impl FileEntry {
    fn parse_ids(&mut self, file: &Path, contents: &str, errors: &mut bool) {
        if self.ids.is_empty() {
            with_attrs_in_source(contents, " id", |fragment, i, _| {
                let frag = fragment.trim_start_matches("#").to_owned();
                let encoded = small_url_encode(&frag);
                if !self.ids.insert(frag) {
                    *errors = true;
                    println!("{}:{}: id is not unique: `{}`", file.display(), i, fragment);
                }
                // Just in case, we also add the encoded id.
                self.ids.insert(encoded);
            });
        }
    }
}

fn walk(cache: &mut Cache, root: &Path, dir: &Path, errors: &mut bool) {
    for entry in t!(dir.read_dir()).map(|e| t!(e)) {
        let path = entry.path();
        let kind = t!(entry.file_type());
        if kind.is_dir() {
            walk(cache, root, &path, errors);
        } else {
            let pretty_path = check(cache, root, &path, errors);
            if let Some(pretty_path) = pretty_path {
                let entry = cache.get_mut(&pretty_path).unwrap();
                // we don't need the source anymore,
                // so drop to reduce memory-usage
                entry.source = Rc::new(String::new());
            }
        }
    }
}

fn check(cache: &mut Cache,
         root: &Path,
         file: &Path,
         errors: &mut bool)
         -> Option<PathBuf> {
    // Ignore none HTML files.
    if file.extension().and_then(|s| s.to_str()) != Some("html") {
        return None;
    }

    // Unfortunately we're not 100% full of valid links today to we need a few
    // whitelists to get this past `make check` today.
    // FIXME(#32129)
    if file.ends_with("std/string/struct.String.html") ||
       file.ends_with("interpret/struct.ImmTy.html") ||
       file.ends_with("symbol/struct.InternedString.html") ||
       file.ends_with("ast/struct.ThinVec.html") ||
       file.ends_with("util/struct.ThinVec.html") ||
       file.ends_with("layout/struct.TyLayout.html") ||
       file.ends_with("humantime/struct.Timestamp.html") ||
       file.ends_with("log/index.html") ||
       file.ends_with("ty/struct.Slice.html") ||
       file.ends_with("ty/enum.Attributes.html") ||
       file.ends_with("ty/struct.SymbolName.html") {
        return None;
    }
    // FIXME(#32553)
    if file.ends_with("string/struct.String.html") {
        return None;
    }
    // FIXME(#32130)
    if file.ends_with("btree_set/struct.BTreeSet.html") ||
       file.ends_with("struct.BTreeSet.html") ||
       file.ends_with("btree_map/struct.BTreeMap.html") ||
       file.ends_with("hash_map/struct.HashMap.html") ||
       file.ends_with("hash_set/struct.HashSet.html") ||
       file.ends_with("sync/struct.Lrc.html") ||
       file.ends_with("sync/struct.RwLock.html") {
        return None;
    }

    let res = load_file(cache, root, file, SkipRedirect);
    let (pretty_file, contents) = match res {
        Ok(res) => res,
        Err(_) => return None,
    };
    {
        cache.get_mut(&pretty_file)
             .unwrap()
             .parse_ids(&pretty_file, &contents, errors);
    }

    // Search for anything that's the regex 'href[ ]*=[ ]*".*?"'
    with_attrs_in_source(&contents, " href", |url, i, base| {
        // Ignore external URLs
        if url.starts_with("http:") || url.starts_with("https:") ||
           url.starts_with("javascript:") || url.starts_with("ftp:") ||
           url.starts_with("irc:") || url.starts_with("data:") {
            return;
        }
        let mut parts = url.splitn(2, "#");
        let url = parts.next().unwrap();
        let fragment = parts.next();
        let mut parts = url.splitn(2, "?");
        let url = parts.next().unwrap();

        // Once we've plucked out the URL, parse it using our base url and
        // then try to extract a file path.
        let mut path = file.to_path_buf();
        if !base.is_empty() || !url.is_empty() {
            path.pop();
            for part in Path::new(base).join(url).components() {
                match part {
                    Component::Prefix(_) |
                    Component::RootDir => {
                        // Avoid absolute paths as they make the docs not
                        // relocatable by making assumptions on where the docs
                        // are hosted relative to the site root.
                        *errors = true;
                        println!("{}:{}: absolute path - {}",
                                 pretty_file.display(),
                                 i + 1,
                                 Path::new(base).join(url).display());
                        return;
                    }
                    Component::CurDir => {}
                    Component::ParentDir => { path.pop(); }
                    Component::Normal(s) => { path.push(s); }
                }
            }
        }

        // Alright, if we've found a file name then this file had better
        // exist! If it doesn't then we register and print an error.
        if path.exists() {
            if path.is_dir() {
                // Links to directories show as directory listings when viewing
                // the docs offline so it's best to avoid them.
                *errors = true;
                let pretty_path = path.strip_prefix(root).unwrap_or(&path);
                println!("{}:{}: directory link - {}",
                         pretty_file.display(),
                         i + 1,
                         pretty_path.display());
                return;
            }
            if let Some(extension) = path.extension() {
                // Ignore none HTML files.
                if extension != "html" {
                    return;
                }
            }
            let res = load_file(cache, root, &path, FromRedirect(false));
            let (pretty_path, contents) = match res {
                Ok(res) => res,
                Err(LoadError::IOError(err)) => {
                    panic!("error loading {}: {}", path.display(), err);
                }
                Err(LoadError::BrokenRedirect(target, _)) => {
                    *errors = true;
                    println!("{}:{}: broken redirect to {}",
                             pretty_file.display(),
                             i + 1,
                             target.display());
                    return;
                }
                Err(LoadError::IsRedirect) => unreachable!(),
            };

            if let Some(ref fragment) = fragment {
                // Fragments like `#1-6` are most likely line numbers to be
                // interpreted by javascript, so we're ignoring these
                if fragment.splitn(2, '-')
                           .all(|f| f.chars().all(|c| c.is_numeric())) {
                    return;
                }

                // These appear to be broken in mdbook right now?
                if fragment.starts_with("-") {
                    return;
                }

                let entry = &mut cache.get_mut(&pretty_path).unwrap();
                entry.parse_ids(&pretty_path, &contents, errors);

                if !entry.ids.contains(*fragment) {
                    *errors = true;
                    print!("{}:{}: broken link fragment ",
                           pretty_file.display(),
                           i + 1);
                    println!("`#{}` pointing to `{}`", fragment, pretty_path.display());
                };
            }
        } else {
            *errors = true;
            print!("{}:{}: broken link - ", pretty_file.display(), i + 1);
            let pretty_path = path.strip_prefix(root).unwrap_or(&path);
            println!("{}", pretty_path.display());
        }
    });
    Some(pretty_file)
}

fn load_file(cache: &mut Cache,
             root: &Path,
             file: &Path,
             redirect: Redirect)
             -> Result<(PathBuf, Rc<String>), LoadError> {
    let pretty_file = PathBuf::from(file.strip_prefix(root).unwrap_or(&file));

    let (maybe_redirect, contents) = match cache.entry(pretty_file.clone()) {
        Entry::Occupied(entry) => {
            (None, entry.get().source.clone())
        }
        Entry::Vacant(entry) => {
            let contents = match fs::read_to_string(file) {
                Ok(s) => Rc::new(s),
                Err(err) => {
                    return Err(if let FromRedirect(true) = redirect {
                        LoadError::BrokenRedirect(file.to_path_buf(), err)
                    } else {
                        LoadError::IOError(err)
                    })
                }
            };

            let maybe = maybe_redirect(&contents);
            if maybe.is_some() {
                if let SkipRedirect = redirect {
                    return Err(LoadError::IsRedirect);
                }
            } else {
                entry.insert(FileEntry {
                    source: contents.clone(),
                    ids: HashSet::new(),
                });
            }
            (maybe, contents)
        }
    };
    match maybe_redirect.map(|url| file.parent().unwrap().join(url)) {
        Some(redirect_file) => {
            load_file(cache, root, &redirect_file, FromRedirect(true))
        }
        None => Ok((pretty_file, contents)),
    }
}

fn maybe_redirect(source: &str) -> Option<String> {
    const REDIRECT: &'static str = "
Redirecting to <a href=";

    let mut lines = source.lines();
    let redirect_line = lines.nth(6)?;

    redirect_line.find(REDIRECT).map(|i| {
        let rest = &redirect_line[(i + REDIRECT.len() + 1)..];
        let pos_quote = rest.find('"').unwrap();
        rest[..pos_quote].to_owned()
    })
}

fn with_attrs_in_source<F: FnMut(&str, usize, &str)>(contents: &str, attr: &str, mut f: F) {
    let mut base = "";
    for (i, mut line) in contents.lines().enumerate() {
        while let Some(j) = line.find(attr) {
            let rest = &line[j + attr.len()..];
            // The base tag should always be the first link in the document so
            // we can get away with using one pass.
            let is_base = line[..j].ends_with("<base");
            line = rest;
            let pos_equals = match rest.find("=") {
                Some(i) => i,
                None => continue,
            };
            if rest[..pos_equals].trim_start_matches(" ") != "" {
                continue;
            }

            let rest = &rest[pos_equals + 1..];

            let pos_quote = match rest.find(&['"', '\''][..]) {
                Some(i) => i,
                None => continue,
            };
            let quote_delim = rest.as_bytes()[pos_quote] as char;

            if rest[..pos_quote].trim_start_matches(" ") != "" {
                continue;
            }
            let rest = &rest[pos_quote + 1..];
            let url = match rest.find(quote_delim) {
                Some(i) => &rest[..i],
                None => continue,
            };
            if is_base {
                base = url;
                continue;
            }
            f(url, i, base)
        }
    }
}
<commit_msg>Whitelist iovec types in linkchecker<commit_after>//! Script to check the validity of `href` links in our HTML documentation.
//!
//! In the past we've been quite error prone to writing in broken links as most
//! of them are manually rather than automatically added. As files move over
//! time or apis change old links become stale or broken. The purpose of this
//! script is to check all relative links in our documentation to make sure they
//! actually point to a valid place.
//!
//! Currently this doesn't actually do any HTML parsing or anything fancy like
//! that, it just has a simple "regex" to search for `href` and `id` tags.
//! These values are then translated to file URLs if possible and then the
//! destination is asserted to exist.
//!
//! A few whitelisted exceptions are allowed as there's known bugs in rustdoc,
//! but this should catch the majority of "broken link" cases.

#![deny(rust_2018_idioms)]

use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
use std::env;
use std::fs;
use std::path::{Path, PathBuf, Component};
use std::rc::Rc;

use crate::Redirect::*;

macro_rules! t {
    ($e:expr) => (match $e {
        Ok(e) => e,
        Err(e) => panic!("{} failed with {:?}", stringify!($e), e),
    })
}

fn main() {
    let docs = env::args_os().nth(1).unwrap();
    let docs = env::current_dir().unwrap().join(docs);
    let mut errors = false;
    walk(&mut HashMap::new(), &docs, &docs, &mut errors);
    if errors {
        panic!("found some broken links");
    }
}

#[derive(Debug)]
pub enum LoadError {
    IOError(std::io::Error),
    BrokenRedirect(PathBuf, std::io::Error),
    IsRedirect,
}

enum Redirect {
    SkipRedirect,
    FromRedirect(bool),
}

struct FileEntry {
    source: Rc<String>,
    ids: HashSet<String>,
}

type Cache = HashMap<PathBuf, FileEntry>;

fn small_url_encode(s: &str) -> String {
    s.replace("<", "%3C")
     .replace(">", "%3E")
     .replace(" ", "%20")
     .replace("?", "%3F")
     .replace("'", "%27")
     .replace("&", "%26")
     .replace(",", "%2C")
     .replace(":", "%3A")
     .replace(";", "%3B")
     .replace("[", "%5B")
     .replace("]", "%5D")
     .replace("\"", "%22")
}

impl FileEntry {
    fn parse_ids(&mut self, file: &Path, contents: &str, errors: &mut bool) {
        if self.ids.is_empty() {
            with_attrs_in_source(contents, " id", |fragment, i, _| {
                let frag = fragment.trim_start_matches("#").to_owned();
                let encoded = small_url_encode(&frag);
                if !self.ids.insert(frag) {
                    *errors = true;
                    println!("{}:{}: id is not unique: `{}`", file.display(), i, fragment);
                }
                // Just in case, we also add the encoded id.
                self.ids.insert(encoded);
            });
        }
    }
}

fn walk(cache: &mut Cache, root: &Path, dir: &Path, errors: &mut bool) {
    for entry in t!(dir.read_dir()).map(|e| t!(e)) {
        let path = entry.path();
        let kind = t!(entry.file_type());
        if kind.is_dir() {
            walk(cache, root, &path, errors);
        } else {
            let pretty_path = check(cache, root, &path, errors);
            if let Some(pretty_path) = pretty_path {
                let entry = cache.get_mut(&pretty_path).unwrap();
                // we don't need the source anymore,
                // so drop to reduce memory-usage
                entry.source = Rc::new(String::new());
            }
        }
    }
}

fn check(cache: &mut Cache,
         root: &Path,
         file: &Path,
         errors: &mut bool)
         -> Option<PathBuf> {
    // Ignore none HTML files.
    if file.extension().and_then(|s| s.to_str()) != Some("html") {
        return None;
    }

    // Unfortunately we're not 100% full of valid links today to we need a few
    // whitelists to get this past `make check` today.
    // FIXME(#32129)
    if file.ends_with("std/string/struct.String.html") ||
       file.ends_with("interpret/struct.ImmTy.html") ||
       file.ends_with("symbol/struct.InternedString.html") ||
       file.ends_with("ast/struct.ThinVec.html") ||
       file.ends_with("util/struct.ThinVec.html") ||
       file.ends_with("layout/struct.TyLayout.html") ||
       file.ends_with("humantime/struct.Timestamp.html") ||
       file.ends_with("log/index.html") ||
       file.ends_with("ty/struct.Slice.html") ||
       file.ends_with("ty/enum.Attributes.html") ||
       file.ends_with("ty/struct.SymbolName.html") ||
       file.ends_with("io/struct.IoVec.html") ||
       file.ends_with("io/struct.IoVecMut.html") {
        return None;
    }
    // FIXME(#32553)
    if file.ends_with("string/struct.String.html") {
        return None;
    }
    // FIXME(#32130)
    if file.ends_with("btree_set/struct.BTreeSet.html") ||
       file.ends_with("struct.BTreeSet.html") ||
       file.ends_with("btree_map/struct.BTreeMap.html") ||
       file.ends_with("hash_map/struct.HashMap.html") ||
       file.ends_with("hash_set/struct.HashSet.html") ||
       file.ends_with("sync/struct.Lrc.html") ||
       file.ends_with("sync/struct.RwLock.html") {
        return None;
    }

    let res = load_file(cache, root, file, SkipRedirect);
    let (pretty_file, contents) = match res {
        Ok(res) => res,
        Err(_) => return None,
    };
    {
        cache.get_mut(&pretty_file)
             .unwrap()
             .parse_ids(&pretty_file, &contents, errors);
    }

    // Search for anything that's the regex 'href[ ]*=[ ]*".*?"'
    with_attrs_in_source(&contents, " href", |url, i, base| {
        // Ignore external URLs
        if url.starts_with("http:") || url.starts_with("https:") ||
           url.starts_with("javascript:") || url.starts_with("ftp:") ||
           url.starts_with("irc:") || url.starts_with("data:") {
            return;
        }
        let mut parts = url.splitn(2, "#");
        let url = parts.next().unwrap();
        let fragment = parts.next();
        let mut parts = url.splitn(2, "?");
        let url = parts.next().unwrap();

        // Once we've plucked out the URL, parse it using our base url and
        // then try to extract a file path.
        let mut path = file.to_path_buf();
        if !base.is_empty() || !url.is_empty() {
            path.pop();
            for part in Path::new(base).join(url).components() {
                match part {
                    Component::Prefix(_) |
                    Component::RootDir => {
                        // Avoid absolute paths as they make the docs not
                        // relocatable by making assumptions on where the docs
                        // are hosted relative to the site root.
                        *errors = true;
                        println!("{}:{}: absolute path - {}",
                                 pretty_file.display(),
                                 i + 1,
                                 Path::new(base).join(url).display());
                        return;
                    }
                    Component::CurDir => {}
                    Component::ParentDir => { path.pop(); }
                    Component::Normal(s) => { path.push(s); }
                }
            }
        }

        // Alright, if we've found a file name then this file had better
        // exist! If it doesn't then we register and print an error.
        if path.exists() {
            if path.is_dir() {
                // Links to directories show as directory listings when viewing
                // the docs offline so it's best to avoid them.
                *errors = true;
                let pretty_path = path.strip_prefix(root).unwrap_or(&path);
                println!("{}:{}: directory link - {}",
                         pretty_file.display(),
                         i + 1,
                         pretty_path.display());
                return;
            }
            if let Some(extension) = path.extension() {
                // Ignore none HTML files.
                if extension != "html" {
                    return;
                }
            }
            let res = load_file(cache, root, &path, FromRedirect(false));
            let (pretty_path, contents) = match res {
                Ok(res) => res,
                Err(LoadError::IOError(err)) => {
                    panic!("error loading {}: {}", path.display(), err);
                }
                Err(LoadError::BrokenRedirect(target, _)) => {
                    *errors = true;
                    println!("{}:{}: broken redirect to {}",
                             pretty_file.display(),
                             i + 1,
                             target.display());
                    return;
                }
                Err(LoadError::IsRedirect) => unreachable!(),
            };

            if let Some(ref fragment) = fragment {
                // Fragments like `#1-6` are most likely line numbers to be
                // interpreted by javascript, so we're ignoring these
                if fragment.splitn(2, '-')
                           .all(|f| f.chars().all(|c| c.is_numeric())) {
                    return;
                }

                // These appear to be broken in mdbook right now?
                if fragment.starts_with("-") {
                    return;
                }

                let entry = &mut cache.get_mut(&pretty_path).unwrap();
                entry.parse_ids(&pretty_path, &contents, errors);

                if !entry.ids.contains(*fragment) {
                    *errors = true;
                    print!("{}:{}: broken link fragment ",
                           pretty_file.display(),
                           i + 1);
                    println!("`#{}` pointing to `{}`", fragment, pretty_path.display());
                };
            }
        } else {
            *errors = true;
            print!("{}:{}: broken link - ", pretty_file.display(), i + 1);
            let pretty_path = path.strip_prefix(root).unwrap_or(&path);
            println!("{}", pretty_path.display());
        }
    });
    Some(pretty_file)
}

fn load_file(cache: &mut Cache,
             root: &Path,
             file: &Path,
             redirect: Redirect)
             -> Result<(PathBuf, Rc<String>), LoadError> {
    let pretty_file = PathBuf::from(file.strip_prefix(root).unwrap_or(&file));

    let (maybe_redirect, contents) = match cache.entry(pretty_file.clone()) {
        Entry::Occupied(entry) => {
            (None, entry.get().source.clone())
        }
        Entry::Vacant(entry) => {
            let contents = match fs::read_to_string(file) {
                Ok(s) => Rc::new(s),
                Err(err) => {
                    return Err(if let FromRedirect(true) = redirect {
                        LoadError::BrokenRedirect(file.to_path_buf(), err)
                    } else {
                        LoadError::IOError(err)
                    })
                }
            };

            let maybe = maybe_redirect(&contents);
            if maybe.is_some() {
                if let SkipRedirect = redirect {
                    return Err(LoadError::IsRedirect);
                }
            } else {
                entry.insert(FileEntry {
                    source: contents.clone(),
                    ids: HashSet::new(),
                });
            }
            (maybe, contents)
        }
    };
    match maybe_redirect.map(|url| file.parent().unwrap().join(url)) {
        Some(redirect_file) => {
            load_file(cache, root, &redirect_file, FromRedirect(true))
        }
        None => Ok((pretty_file, contents)),
    }
}

fn maybe_redirect(source: &str) -> Option<String> {
    const REDIRECT: &'static str = "
Redirecting to <a href=";

    let mut lines = source.lines();
    let redirect_line = lines.nth(6)?;

    redirect_line.find(REDIRECT).map(|i| {
        let rest = &redirect_line[(i + REDIRECT.len() + 1)..];
        let pos_quote = rest.find('"').unwrap();
        rest[..pos_quote].to_owned()
    })
}

fn with_attrs_in_source<F: FnMut(&str, usize, &str)>(contents: &str, attr: &str, mut f: F) {
    let mut base = "";
    for (i, mut line) in contents.lines().enumerate() {
        while let Some(j) = line.find(attr) {
            let rest = &line[j + attr.len()..];
            // The base tag should always be the first link in the document so
            // we can get away with using one pass.
            let is_base = line[..j].ends_with("<base");
            line = rest;
            let pos_equals = match rest.find("=") {
                Some(i) => i,
                None => continue,
            };
            if rest[..pos_equals].trim_start_matches(" ") != "" {
                continue;
            }

            let rest = &rest[pos_equals + 1..];

            let pos_quote = match rest.find(&['"', '\''][..]) {
                Some(i) => i,
                None => continue,
            };
            let quote_delim = rest.as_bytes()[pos_quote] as char;

            if rest[..pos_quote].trim_start_matches(" ") != "" {
                continue;
            }
            let rest = &rest[pos_quote + 1..];
            let url = match rest.find(quote_delim) {
                Some(i) => &rest[..i],
                None => continue,
            };
            if is_base {
                base = url;
                continue;
            }
            f(url, i, base)
        }
    }
}
<|endoftext|>


<commit_before>use std::io;
use std::path::{PathBuf, Path};
use std::fs::File;
use std::collections::HashMap;
use object::{Object, File as OFile};
use memmap::MmapOptions;
use gimli::*;
use rustc_demangle::demangle;
use cargo::core::Workspace;

use config::Config;
use source_analysis::*;
use traces::*;

/// Describes a function as `low_pc`, `high_pc` and bool representing `is_test`.
type FuncDesc = (u64, u64, FunctionType);

#[derive(Clone, Copy, PartialEq)]
enum FunctionType {
    Generated,
    Test,
    Standard
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
pub enum LineType {
    /// Generated test main. Shouldn't be traced.
    TestMain,
    /// Entry of function known to be a test
    TestEntry(u64),
    /// Entry of function. May or may not be test
    FunctionEntry(u64),
    /// Standard statement
    Statement,
    /// Condition
    Condition,
    /// Unknown type
    Unknown,
    /// Unused meta-code
    UnusedGeneric,
}

#[derive(Clone, PartialEq, Eq, Hash)]
struct SourceLocation {
    pub path: PathBuf,
    pub line: u64,
}

#[derive(Debug, Clone)]
pub struct TracerData {
    /// Currently used to find generated __test::main and remove from coverage,
    /// may have uses in future for finding conditions etc
    pub trace_type: LineType,
    /// Start address of the line
    pub address: Option<u64>,
    /// Length of the instruction
    pub length: u64,
}

fn generate_func_desc<R, Offset>(die: &DebuggingInformationEntry<R, Offset>,
                                 debug_str: &DebugStr<R>) -> Result<FuncDesc>
    where R: Reader<Offset = Offset>,
          Offset: ReaderOffset
{
    let mut func_type = FunctionType::Standard;
    let low = die.attr_value(DW_AT_low_pc)?;
    let high = die.attr_value(DW_AT_high_pc)?;
    let linkage = die.attr_value(DW_AT_linkage_name)?;

    // Low is a program counter address so stored in an Addr
    let low = match low {
        Some(AttributeValue::Addr(x)) => x,
        _ => 0u64,
    };
    // High is an offset from the base pc, therefore is u64 data.
    let high = match high {
        Some(AttributeValue::Udata(x)) => x,
        _ => 0u64,
    };
    if let Some(AttributeValue::DebugStrRef(offset)) = linkage {
        let name = debug_str.get_str(offset)
            .and_then(|r| r.to_string().map(|s| s.to_string()))
            .unwrap_or("".into());
        let name = demangle(name.as_ref()).to_string();
        // Simplest test is whether it's in tests namespace.
        // Rust guidelines recommend all tests are in a tests module.
        func_type = if name.contains("tests::") {
            FunctionType::Test
        } else if name.contains("__test::main") {
            FunctionType::Generated
        } else {
            FunctionType::Standard
        };
    }
    Ok((low, high, func_type))
}

/// Finds all function entry points and returns a vector
/// This will identify definite tests, but may be prone to false negatives.
fn get_entry_points<R, Offset>(debug_info: &CompilationUnitHeader<R, Offset>,
                               debug_abbrev: &Abbreviations,
                               debug_str: &DebugStr<R>) -> Vec<FuncDesc>
    where R: Reader<Offset = Offset>,
          Offset: ReaderOffset
{
    let mut result:Vec<FuncDesc> = Vec::new();
    let mut cursor = debug_info.entries(debug_abbrev);
    // skip compilation unit root.
    let _ = cursor.next_entry();
    while let Ok(Some((_, node))) = cursor.next_dfs() {
        // Function DIE
        if node.tag() == DW_TAG_subprogram {

            if let Ok(fd) = generate_func_desc(node, debug_str) {
                result.push(fd);
            }
        }
    }
    result
}

fn get_addresses_from_program<R, Offset>(prog: IncompleteLineNumberProgram<R>,
                                         entries: &[(u64, LineType)],
                                         project: &Path,
                                         result: &mut HashMap<SourceLocation, Vec<TracerData>>) -> Result<()>
    where R: Reader<Offset = Offset>,
          Offset: ReaderOffset
{
    let ( cprog, seq) = prog.sequences()?;
    for s in seq {
        let mut temp_map: HashMap<SourceLocation, TracerData> = HashMap::new();
        let mut sm = cprog.resume_from(&s);
         while let Ok(Some((header, &ln_row))) = sm.next_row() {
             // If this row isn't useful move on
            if !ln_row.is_stmt() || ln_row.line().is_none() {
                continue;
            }
            if let Some(file) = ln_row.file(header) {
                let mut path = PathBuf::new();

                if let Some(dir) = file.directory(header) {
                    if let Ok(temp) = dir.to_string() {
                        path.push(temp.as_ref());
                    }
                }
                if let Ok(p) = path.canonicalize() {
                    path = p;
                }
                // Fix relative paths and determine if in target directory
                // Source in target directory shouldn't be covered as it's either
                // autogenerated or resulting from the projects Cargo.lock
                let is_target = if path.is_relative() {
                    path.starts_with("target")
                } else {
                    path.starts_with(project.join("target"))
                };
                // Source is part of project so we cover it.
                if !is_target && path.starts_with(project) {
                    if let Some(file) = ln_row.file(header) {
                        let line = ln_row.line().unwrap();
                        let file = file.path_name();

                        if let Ok(file) = file.to_string() {
                            path.push(file.as_ref());
                            if !path.is_file() {
                                // Not really a source file!
                                continue;
                            }
                            let address = ln_row.address();
                            let desc = entries.iter()
                                              .filter(|&&(addr, _)| addr == address )
                                              .map(|&(_, t)| t)
                                              .nth(0)
                                              .unwrap_or(LineType::Unknown);
                            let loc = SourceLocation {
                                path: path,
                                line: line,
                            };
                            if desc != LineType::TestMain && !temp_map.contains_key(&loc) {
                                temp_map.insert(loc, TracerData {
                                    address: Some(address),
                                    trace_type: desc,
                                    length: 1,
                                });
                            }
                        }
                    }
                }
            }
        }
        for (k, v) in &temp_map {
            if result.contains_key(k) {
                let mut x = result.get_mut(k).unwrap();
                x.push(v.clone());
            } else {
                result.insert(k.clone(), vec![v.clone()]);
            }
        }
    }
    Ok(())
}

fn get_line_addresses(endian: RunTimeEndian,
                      project: &Path,
                      obj: &OFile,
                      analysis: &HashMap<PathBuf, LineAnalysis>,
                      config: &Config) -> Result<TraceMap>  {

    let mut result = TraceMap::new();
    let debug_info = obj.section_data_by_name(".debug_info").unwrap_or(&[]);
    let debug_info = DebugInfo::new(debug_info, endian);
    let debug_abbrev = obj.section_data_by_name(".debug_abbrev").unwrap_or(&[]);
    let debug_abbrev = DebugAbbrev::new(debug_abbrev, endian);
    let debug_strings = obj.section_data_by_name(".debug_str").unwrap_or(&[]);
    let debug_strings = DebugStr::new(debug_strings, endian);
    let debug_line = obj.section_data_by_name(".debug_line").unwrap_or(&[]);
    let debug_line = DebugLine::new(debug_line, endian);

    let mut iter = debug_info.units();
    while let Ok(Some(cu)) = iter.next() {
        let addr_size = cu.address_size();
        let abbr = match cu.abbreviations(&debug_abbrev) {
            Ok(a) => a,
            _ => continue,
        };
        let entries = get_entry_points(&cu, &abbr, &debug_strings)
            .iter()
            .map(|&(a, b, c)| {
                match c {
                    FunctionType::Test => (a, LineType::TestEntry(b)),
                    FunctionType::Standard => (a, LineType::FunctionEntry(b)),
                    FunctionType::Generated => (a, LineType::TestMain),
                }
            }).collect::<Vec<_>>();
        if let Ok(Some((_, root))) = cu.entries(&abbr).next_dfs() {
            let offset = match root.attr_value(DW_AT_stmt_list) {
                Ok(Some(AttributeValue::DebugLineRef(o))) => o,
                _ => continue,
            };
            let prog = debug_line.program(offset, addr_size, None, None)?;
            let mut temp_map:HashMap<SourceLocation, Vec<TracerData>> = HashMap::new();
            if let Err(e) = get_addresses_from_program(prog, &entries, project, &mut temp_map) {
                if config.verbose {
                    println!("Potential issue reading test addresses {}", e);
                }
            }
            else {

                let temp_map = temp_map.into_iter()
                                       .filter(|&(ref k, _)| !(config.ignore_tests && k.path.starts_with(project.join("tests"))))
                                       .filter(|&(ref k, _)| !(config.exclude_path(&k.path)))
                                       .filter(|&(ref k, _)| !analysis.should_ignore(k.path.as_ref(), &(k.line as usize)))
                                       .collect::<HashMap<SourceLocation, Vec<TracerData>>>();
                let mut tracemap = TraceMap::new();
                for (k, val) in &temp_map {
                    for v in val.iter() {
                        tracemap.add_trace(&k.path, Trace {
                            line: k.line,
                            address: v.address,
                            length: 1,
                            stats: CoverageStat::Line(0)
                        });
                    }
                }
                result.merge(&tracemap);
            }
        }
    }

    for (file, ref line_analysis) in analysis.iter() {
        if config.exclude_path(file) {
            continue;
        }
        for line in &line_analysis.cover {
            let line = *line as u64;
            if !result.contains_location(file, line) && !line_analysis.should_ignore(&(line as usize)) {
                result.add_trace(file, Trace {
                    line: line,
                    address: None,
                    length: 0,
                    stats: CoverageStat::Line(0),
                });
            }
        }
    }
    Ok(result)
}

pub fn generate_tracemap(project: &Workspace, test: &Path, config: &Config) -> io::Result<TraceMap> {
    let manifest = project.root();
    let file = File::open(test)?;
    let file = unsafe {
        MmapOptions::new().map(&file)?
    };
    if let Ok(obj) = OFile::parse(&*file) {
        let analysis = get_line_analysis(project, config);
        let endian = if obj.is_little_endian() {
            RunTimeEndian::Little
        } else {
            RunTimeEndian::Big
        };
        if let Ok(result) = get_line_addresses(endian, manifest, &obj, &analysis, config) {
            Ok(result)
        } else {
            Err(io::Error::new(io::ErrorKind::InvalidData, "Error while parsing"))
        }
    } else {
        Err(io::Error::new(io::ErrorKind::InvalidData, "Unable to parse binary."))
    }
}

<commit_msg>Updated test_loader to filter out duplicate addresses.<commit_after>use std::io;
use std::path::{PathBuf, Path};
use std::fs::File;
use std::collections::HashMap;
use object::{Object, File as OFile};
use memmap::MmapOptions;
use gimli::*;
use rustc_demangle::demangle;
use cargo::core::Workspace;

use config::Config;
use source_analysis::*;
use traces::*;

/// Describes a function as `low_pc`, `high_pc` and bool representing `is_test`.
type FuncDesc = (u64, u64, FunctionType);

#[derive(Clone, Copy, PartialEq)]
enum FunctionType {
    Generated,
    Test,
    Standard
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
pub enum LineType {
    /// Generated test main. Shouldn't be traced.
    TestMain,
    /// Entry of function known to be a test
    TestEntry(u64),
    /// Entry of function. May or may not be test
    FunctionEntry(u64),
    /// Standard statement
    Statement,
    /// Condition
    Condition,
    /// Unknown type
    Unknown,
    /// Unused meta-code
    UnusedGeneric,
}

#[derive(Clone, PartialEq, Eq, Hash)]
struct SourceLocation {
    pub path: PathBuf,
    pub line: u64,
}

#[derive(Debug, Clone)]
pub struct TracerData {
    /// Currently used to find generated __test::main and remove from coverage,
    /// may have uses in future for finding conditions etc
    pub trace_type: LineType,
    /// Start address of the line
    pub address: Option<u64>,
    /// Length of the instruction
    pub length: u64,
}

fn generate_func_desc<R, Offset>(die: &DebuggingInformationEntry<R, Offset>,
                                 debug_str: &DebugStr<R>) -> Result<FuncDesc>
    where R: Reader<Offset = Offset>,
          Offset: ReaderOffset
{
    let mut func_type = FunctionType::Standard;
    let low = die.attr_value(DW_AT_low_pc)?;
    let high = die.attr_value(DW_AT_high_pc)?;
    let linkage = die.attr_value(DW_AT_linkage_name)?;

    // Low is a program counter address so stored in an Addr
    let low = match low {
        Some(AttributeValue::Addr(x)) => x,
        _ => 0u64,
    };
    // High is an offset from the base pc, therefore is u64 data.
    let high = match high {
        Some(AttributeValue::Udata(x)) => x,
        _ => 0u64,
    };
    if let Some(AttributeValue::DebugStrRef(offset)) = linkage {
        let name = debug_str.get_str(offset)
            .and_then(|r| r.to_string().map(|s| s.to_string()))
            .unwrap_or("".into());
        let name = demangle(name.as_ref()).to_string();
        // Simplest test is whether it's in tests namespace.
        // Rust guidelines recommend all tests are in a tests module.
        func_type = if name.contains("tests::") {
            FunctionType::Test
        } else if name.contains("__test::main") {
            FunctionType::Generated
        } else {
            FunctionType::Standard
        };
    }
    Ok((low, high, func_type))
}

/// Finds all function entry points and returns a vector
/// This will identify definite tests, but may be prone to false negatives.
fn get_entry_points<R, Offset>(debug_info: &CompilationUnitHeader<R, Offset>,
                               debug_abbrev: &Abbreviations,
                               debug_str: &DebugStr<R>) -> Vec<FuncDesc>
    where R: Reader<Offset = Offset>,
          Offset: ReaderOffset
{
    let mut result:Vec<FuncDesc> = Vec::new();
    let mut cursor = debug_info.entries(debug_abbrev);
    // skip compilation unit root.
    let _ = cursor.next_entry();
    while let Ok(Some((_, node))) = cursor.next_dfs() {
        // Function DIE
        if node.tag() == DW_TAG_subprogram {

            if let Ok(fd) = generate_func_desc(node, debug_str) {
                result.push(fd);
            }
        }
    }
    result
}

fn get_addresses_from_program<R, Offset>(prog: IncompleteLineNumberProgram<R>,
                                         entries: &[(u64, LineType)],
                                         project: &Path,
                                         result: &mut HashMap<SourceLocation, Vec<TracerData>>) -> Result<()>
    where R: Reader<Offset = Offset>,
          Offset: ReaderOffset
{
    let ( cprog, seq) = prog.sequences()?;
    for s in seq {
        let mut temp_map: HashMap<SourceLocation, TracerData> = HashMap::new();
        let mut sm = cprog.resume_from(&s);
         while let Ok(Some((header, &ln_row))) = sm.next_row() {
             // If this row isn't useful move on
            if !ln_row.is_stmt() || ln_row.line().is_none() {
                continue;
            }
            if let Some(file) = ln_row.file(header) {
                let mut path = PathBuf::new();

                if let Some(dir) = file.directory(header) {
                    if let Ok(temp) = dir.to_string() {
                        path.push(temp.as_ref());
                    }
                }
                if let Ok(p) = path.canonicalize() {
                    path = p;
                }
                // Fix relative paths and determine if in target directory
                // Source in target directory shouldn't be covered as it's either
                // autogenerated or resulting from the projects Cargo.lock
                let is_target = if path.is_relative() {
                    path.starts_with("target")
                } else {
                    path.starts_with(project.join("target"))
                };
                // Source is part of project so we cover it.
                if !is_target && path.starts_with(project) {
                    if let Some(file) = ln_row.file(header) {
                        let line = ln_row.line().unwrap();
                        let file = file.path_name();

                        if let Ok(file) = file.to_string() {
                            path.push(file.as_ref());
                            if !path.is_file() {
                                // Not really a source file!
                                continue;
                            }
                            let address = ln_row.address();
                            let desc = entries.iter()
                                              .filter(|&&(addr, _)| addr == address )
                                              .map(|&(_, t)| t)
                                              .nth(0)
                                              .unwrap_or(LineType::Unknown);
                            let loc = SourceLocation {
                                path: path,
                                line: line,
                            };
                            if desc != LineType::TestMain && !temp_map.contains_key(&loc) {
                                temp_map.insert(loc, TracerData {
                                    address: Some(address),
                                    trace_type: desc,
                                    length: 1,
                                });
                            }
                        }
                    }
                }
            }
        }
        for (k, v) in &temp_map {
            if result.contains_key(k) {
                let mut x = result.get_mut(k).unwrap();
                x.push(v.clone());
            } else {
                result.insert(k.clone(), vec![v.clone()]);
            }
        }
    }
    Ok(())
}

fn get_line_addresses(endian: RunTimeEndian,
                      project: &Path,
                      obj: &OFile,
                      analysis: &HashMap<PathBuf, LineAnalysis>,
                      config: &Config) -> Result<TraceMap>  {

    let mut result = TraceMap::new();
    let debug_info = obj.section_data_by_name(".debug_info").unwrap_or(&[]);
    let debug_info = DebugInfo::new(debug_info, endian);
    let debug_abbrev = obj.section_data_by_name(".debug_abbrev").unwrap_or(&[]);
    let debug_abbrev = DebugAbbrev::new(debug_abbrev, endian);
    let debug_strings = obj.section_data_by_name(".debug_str").unwrap_or(&[]);
    let debug_strings = DebugStr::new(debug_strings, endian);
    let debug_line = obj.section_data_by_name(".debug_line").unwrap_or(&[]);
    let debug_line = DebugLine::new(debug_line, endian);

    let mut iter = debug_info.units();
    while let Ok(Some(cu)) = iter.next() {
        let addr_size = cu.address_size();
        let abbr = match cu.abbreviations(&debug_abbrev) {
            Ok(a) => a,
            _ => continue,
        };
        let entries = get_entry_points(&cu, &abbr, &debug_strings)
            .iter()
            .map(|&(a, b, c)| {
                match c {
                    FunctionType::Test => (a, LineType::TestEntry(b)),
                    FunctionType::Standard => (a, LineType::FunctionEntry(b)),
                    FunctionType::Generated => (a, LineType::TestMain),
                }
            }).collect::<Vec<_>>();

        if let Ok(Some((_, root))) = cu.entries(&abbr).next_dfs() {
            let offset = match root.attr_value(DW_AT_stmt_list) {
                Ok(Some(AttributeValue::DebugLineRef(o))) => o,
                _ => continue,
            };
            let prog = debug_line.program(offset, addr_size, None, None)?;
            let mut temp_map : HashMap<SourceLocation, Vec<TracerData>> = HashMap::new();
            if let Err(e) = get_addresses_from_program(prog, &entries, project, &mut temp_map) {
                if config.verbose {
                    println!("Potential issue reading test addresses {}", e);
                }
            }
            else {
                // Deduplicate addresses
                for (_, v) in temp_map.iter_mut() {
                    v.dedup_by_key(|x| x.address);
                }
                let temp_map = temp_map.into_iter()
                                       .filter(|&(ref k, _)| !(config.ignore_tests && k.path.starts_with(project.join("tests"))))
                                       .filter(|&(ref k, _)| !(config.exclude_path(&k.path)))
                                       .filter(|&(ref k, _)| !analysis.should_ignore(k.path.as_ref(), &(k.line as usize)))
                                       .collect::<HashMap<SourceLocation, Vec<TracerData>>>();
                let mut tracemap = TraceMap::new();
                for (k, val) in &temp_map {
                    for v in val.iter() {
                        tracemap.add_trace(&k.path, Trace {
                            line: k.line,
                            address: v.address,
                            length: 1,
                            stats: CoverageStat::Line(0)
                        });
                    }
                }
                result.merge(&tracemap);
            }
        }
    }

    for (file, ref line_analysis) in analysis.iter() {
        if config.exclude_path(file) {
            continue;
        }
        for line in &line_analysis.cover {
            let line = *line as u64;
            if !result.contains_location(file, line) && !line_analysis.should_ignore(&(line as usize)) {
                result.add_trace(file, Trace {
                    line: line,
                    address: None,
                    length: 0,
                    stats: CoverageStat::Line(0),
                });
            }
        }
    }
    Ok(result)
}

pub fn generate_tracemap(project: &Workspace, test: &Path, config: &Config) -> io::Result<TraceMap> {
    let manifest = project.root();
    let file = File::open(test)?;
    let file = unsafe {
        MmapOptions::new().map(&file)?
    };
    if let Ok(obj) = OFile::parse(&*file) {
        let analysis = get_line_analysis(project, config);
        let endian = if obj.is_little_endian() {
            RunTimeEndian::Little
        } else {
            RunTimeEndian::Big
        };
        if let Ok(result) = get_line_addresses(endian, manifest, &obj, &analysis, config) {
            Ok(result)
        } else {
            Err(io::Error::new(io::ErrorKind::InvalidData, "Error while parsing"))
        }
    } else {
        Err(io::Error::new(io::ErrorKind::InvalidData, "Unable to parse binary."))
    }
}

<|endoftext|>


<commit_before>// This module defines a container that expresses zakocmp policy
// violations - i.e. human-readable descriptions of notable
// discrepancies between zakocmp snapshots.

#[path = "errors.rs"]
mod errors;

use errors::ZakocmpError;

// Represents possible policy violations as caller-passable ints.
pub const ADDED: i32 = 0;
pub const DELETED: i32 = 1 << 0;
pub const MODIFIED: i32 = 1 << 1;

// Represents human-readable counterparts to the above. These are meant
// for printing etc. and so are not relevant to callers.
const REPR_ADDED: &'static str = "+";
const REPR_DELETED: &'static str = "-";
const REPR_MODIFIED: &'static str = "!";

// A single violation consists of the offending path (arbitrary bytes)
// and the kind of violation (i32 - as above).
pub struct Violations {
    data: std::vec::Vec<(String, i32)>,
}

impl Violations {
    pub fn new() -> Violations {
        Violations { data: vec![] }
    }

    // Accepts a path (arbitrary string of bytes) and a kind of policy
    // violation.
    pub fn add(&mut self, path: &str, kind: i32) -> std::result::Result<(), ZakocmpError> {
        match kind {
            ADDED | DELETED | MODIFIED => (),
            _ => return Err(ZakocmpError::Unknown(format!("bad kind: {}", kind))),
        };
        self.data.push((path.to_owned(), kind));
        Ok(())
    }
}

fn display_violation_type(kind: i32) -> &'static str {
    match kind {
        ADDED => REPR_ADDED,
        DELETED => REPR_DELETED,
        MODIFIED => REPR_MODIFIED,
        _ => panic!(format!("bad kind: {}", kind)),
    }
}

impl std::fmt::Display for Violations {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut sorted_violations = self.data.to_owned();
        sorted_violations.sort_unstable();
        for (path, kind) in sorted_violations.into_iter() {
            write!(f, "{} {}\n", display_violation_type(kind), path)?;
        }
        Ok(())
    }
}

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

    #[test]
    fn violations_add_fail() {
        let mut violations = Violations::new();
        let error = violations.add("some/path", 999).unwrap_err();
        match error {
            ZakocmpError::Unknown(message) => assert!(message.starts_with("bad")),
            _ => panic!("not a ZakocmpError::Unknown"),
        };
    }

    #[test]
    fn violations_add_okay() {
        let mut violations = Violations::new();
        assert!(violations
            .add(
                "arbitrary bytes are allowed so long as the type is valid",
                ADDED
            )
            .is_ok());
        // Empty strings are allowed, too.
        assert!(violations.add("", MODIFIED).is_ok());
        // Doubling up on strings is perfectly legitimate, too.
        assert!(violations.add("", MODIFIED).is_ok());
        assert!(violations.add("", ADDED).is_ok());
        assert!(violations.add("", DELETED).is_ok());
    }

    #[test]
    fn violations_display() {
        let mut violations = Violations::new();
        assert!(violations.add("jello there!", ADDED).is_ok());
        assert!(violations.add("iello there!", MODIFIED).is_ok());
        assert!(violations.add("hello there!", DELETED).is_ok());
        assert!(violations.add("a/path/of/some/sort", ADDED).is_ok());
        assert!(violations.add("b/path/of/some/sort", MODIFIED).is_ok());
        assert!(violations.add("z/path/of/some/sort", DELETED).is_ok());

        assert_eq!(
            format!("{}", violations),
            r#"+ a/path/of/some/sort
! b/path/of/some/sort
- hello there!
! iello there!
+ jello there!
- z/path/of/some/sort
"#
        );
    }
}
<commit_msg>violations: make panic clearer<commit_after>// This module defines a container that expresses zakocmp policy
// violations - i.e. human-readable descriptions of notable
// discrepancies between zakocmp snapshots.

#[path = "errors.rs"]
mod errors;

use errors::ZakocmpError;

// Represents possible policy violations as caller-passable ints.
pub const ADDED: i32 = 0;
pub const DELETED: i32 = 1 << 0;
pub const MODIFIED: i32 = 1 << 1;

// Represents human-readable counterparts to the above. These are meant
// for printing etc. and so are not relevant to callers.
const REPR_ADDED: &'static str = "+";
const REPR_DELETED: &'static str = "-";
const REPR_MODIFIED: &'static str = "!";

// A single violation consists of the offending path (arbitrary bytes)
// and the kind of violation (i32 - as above).
pub struct Violations {
    data: std::vec::Vec<(String, i32)>,
}

impl Violations {
    pub fn new() -> Violations {
        Violations { data: vec![] }
    }

    // Accepts a path (arbitrary string of bytes) and a kind of policy
    // violation.
    pub fn add(&mut self, path: &str, kind: i32) -> std::result::Result<(), ZakocmpError> {
        match kind {
            ADDED | DELETED | MODIFIED => (),
            _ => return Err(ZakocmpError::Unknown(format!("bad kind: {}", kind))),
        };
        self.data.push((path.to_owned(), kind));
        Ok(())
    }
}

fn display_violation_type(kind: i32) -> &'static str {
    match kind {
        ADDED => REPR_ADDED,
        DELETED => REPR_DELETED,
        MODIFIED => REPR_MODIFIED,
        // This case is serious: the burden is on us to have weeded out
        // invalid violation kinds in previous calls to add().
        _ => panic!(format!("BUG: bad kind: {}", kind)),
    }
}

impl std::fmt::Display for Violations {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut sorted_violations = self.data.to_owned();
        sorted_violations.sort_unstable();
        for (path, kind) in sorted_violations.into_iter() {
            write!(f, "{} {}\n", display_violation_type(kind), path)?;
        }
        Ok(())
    }
}

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

    #[test]
    fn violations_add_fail() {
        let mut violations = Violations::new();
        let error = violations.add("some/path", 999).unwrap_err();
        match error {
            ZakocmpError::Unknown(message) => assert!(message.starts_with("bad")),
            _ => panic!("not a ZakocmpError::Unknown"),
        };
    }

    #[test]
    fn violations_add_okay() {
        let mut violations = Violations::new();
        assert!(violations
            .add(
                "arbitrary bytes are allowed so long as the type is valid",
                ADDED
            )
            .is_ok());
        // Empty strings are allowed, too.
        assert!(violations.add("", MODIFIED).is_ok());
        // Doubling up on strings is perfectly legitimate, too.
        assert!(violations.add("", MODIFIED).is_ok());
        assert!(violations.add("", ADDED).is_ok());
        assert!(violations.add("", DELETED).is_ok());
    }

    #[test]
    fn violations_display() {
        let mut violations = Violations::new();
        assert!(violations.add("jello there!", ADDED).is_ok());
        assert!(violations.add("iello there!", MODIFIED).is_ok());
        assert!(violations.add("hello there!", DELETED).is_ok());
        assert!(violations.add("a/path/of/some/sort", ADDED).is_ok());
        assert!(violations.add("b/path/of/some/sort", MODIFIED).is_ok());
        assert!(violations.add("z/path/of/some/sort", DELETED).is_ok());

        assert_eq!(
            format!("{}", violations),
            r#"+ a/path/of/some/sort
! b/path/of/some/sort
- hello there!
! iello there!
+ jello there!
- z/path/of/some/sort
"#
        );
    }
}
<|endoftext|>


<commit_before>use region::Region;
use strategy::Strategy;
use superregion::SuperRegion;
use turn::Turn;

pub struct FastSpread;

impl FastSpread
{
	pub fn new() -> Self
	{
		FastSpread
	}
}

impl Strategy for FastSpread
{
	fn set_regions<T>(&mut self,_regs: T)
		where T: IntoIterator<Item=Region>
	{
		unimplemented!();
	}

	fn region_mut<'a>(&'a mut self,_id: usize) -> Option<&'a mut Region>
	{
		unimplemented!();
	}

	fn set_superregions<T>(&mut self,_sregs: T)
		where T: IntoIterator<Item=SuperRegion>
	{
		unimplemented!();
	}

	fn turn_other<T>(&mut self,_turns: T)
		where T: IntoIterator<Item=Turn>
	{
		unimplemented!();
	}

	fn get_starting_regions<T>(&self,_avail: T) -> Option<(usize,usize,usize,usize,usize,usize)>
		where T: IntoIterator<Item=usize>
	{
		unimplemented!();
	}

	fn placement(&self,_count: usize) -> Vec<Turn>
	{
		unimplemented!();
	}

	fn turn(&self) -> Vec<Turn>
	{
		unimplemented!();
	}
}

<commit_msg>dummy implementation (still unimplemented for place and turn)<commit_after>use std::collections::BTreeMap;
use std::iter::FromIterator;

use region::Region as Location;
use request::Request;
use strategy::Strategy;
use superregion::SuperRegion;
use turn::Turn;

#[derive(Debug,Clone,PartialEq)]
struct Region
{
	loc: Location,
	distances: BTreeMap<usize,(usize,usize)>,
}

impl Region
{
	fn new(loc: Location) -> Region
	{
		Region
		{
			loc: loc,
			distances: BTreeMap::new(),
		}
	}

	/// BTreeMap<target,(len,via)>
	fn update(&mut self,other: usize,table: &BTreeMap<usize,(usize,usize)>) -> bool
	{
		let table2 = BTreeMap::from_iter(table.clone().into_iter().chain(self.loc.neighbours.iter().map(|&n|(n,(1,n)))));
		let mut updated = table2.ne(&table);
		let table = table2;

		for (&id,&(distance,_)) in table.iter()
		{
			let distance = distance+1;
			if let Some(&(current,_)) = self.distances.get(&id)
			{
				if distance < current
				{
					self.distances.insert(id,(distance,other));
					updated = true;
				}
			}
			else
			{
				self.distances.insert(id,(distance,other));
				updated = true;
			}
		}

		updated
	}
}

pub struct FastSpread
{
	regions: BTreeMap<usize,Region>,
	super_regions: BTreeMap<usize,SuperRegion>,
}

impl FastSpread
{
	pub fn new() -> Self
	{
		FastSpread
		{
			regions: BTreeMap::new(),
			super_regions: BTreeMap::new(),
		}
	}

	fn init_distances(&mut self)
	{
		let mut updated = true;
		while updated
		{
			updated = false;
			let regions_immutable = self.regions.clone();
			for reg in regions_immutable.values()
			{
				for &other in reg.loc.neighbours.iter()
				{
					let table = self.regions[&other].distances.clone();
					if self.regions.get_mut(&reg.loc.id).unwrap().update(other,&table)
					{
						updated = true;
					}
				}
			}
		}
	}
}

impl Strategy for FastSpread
{
	fn set_regions<T>(&mut self,regs: T)
		where T: IntoIterator<Item=Location>
	{
		self.regions.extend
		(
			regs.into_iter()
				.map(|l|(l.id,Region::new(l)))
		);
	}

	fn region_mut<'a>(&'a mut self,id: usize) -> Option<&'a mut Location>
	{
		self.regions.get_mut(&id).map(|r|&mut r.loc)
	}

	fn set_superregions<T>(&mut self,sregs: T)
		where T: IntoIterator<Item=SuperRegion>
	{
		self.super_regions.extend
		(
			sregs.into_iter()
				.map(|sr|(sr.id,sr))
		);
	}

	fn turn_other<T>(&mut self,_turns: T)
		where T: IntoIterator<Item=Turn>
	{
		//TODO: to something maybe?
	}

	fn get_starting_regions<T>(&self,_avail: T) -> Option<(usize,usize,usize,usize,usize,usize)>
		where T: IntoIterator<Item=usize>
	{
		//TODO: choose starting regions
		None
	}

	fn placement(&self,_count: usize) -> Vec<Turn>
	{
		unimplemented!();
	}

	fn turn(&self) -> Vec<Turn>
	{
		unimplemented!();
	}

	fn raw_event_after(&mut self,req: &Request) -> Option<String>
	{
		if let &Request::ListNeighbours(_) = req
		{
			self.init_distances();
		}
		None
	}
}

<|endoftext|>


<commit_before>use std;
use std::cell::RefCell;
use std::io;
use std::io::{BufWriter, Write};

use termion;
use termion::color;
use termion::color::Color;
use termion::input::MouseTerminal;
use termion::raw::{IntoRawMode, RawTerminal};
use termion::screen::AlternateScreen;
use termion::style;
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;

pub struct Screen {
    out: RefCell<MouseTerminal<AlternateScreen<RawTerminal<BufWriter<io::Stdout>>>>>,
    buf: RefCell<Vec<Option<(String, String)>>>,
    w: usize,
    h: usize,
    cursor_pos: (usize, usize),
}

impl Screen {
    pub fn new() -> Self {
        let (w, h) = termion::terminal_size().unwrap();
        let buf = std::iter::repeat(Some(("".into(), " ".into())))
            .take(w as usize * h as usize)
            .collect();
        Screen {
            out: RefCell::new(MouseTerminal::from(AlternateScreen::from(
                BufWriter::with_capacity(1 << 14, io::stdout())
                    .into_raw_mode()
                    .unwrap(),
            ))),
            buf: RefCell::new(buf),
            w: w as usize,
            h: h as usize,
            cursor_pos: (0, 0),
        }
    }

    pub fn clear<C>(&self, col: &C)
    where
        C: Color + Clone,
    {
        for cell in self.buf.borrow_mut().iter_mut() {
            match *cell {
                Some((ref mut style, ref mut text)) => {
                    *style = format!("{}{}", color::Fg(col.clone()), color::Bg(col.clone()));
                    text.clear();
                    text.push_str(" ");
                }
                _ => {
                    *cell = Some((
                        format!("{}{}", color::Fg(col.clone()), color::Bg(col.clone())),
                        " ".into(),
                    ));
                }
            }
        }
    }

    #[allow(dead_code)]
    pub fn resize(&mut self, w: usize, h: usize) {
        self.w = w;
        self.h = h;
        self.buf
            .borrow_mut()
            .resize(w * h, Some(("".into(), " ".into())));
    }

    pub fn present(&self) {
        let mut out = self.out.borrow_mut();
        let buf = self.buf.borrow();

        let mut last_style = "";
        write!(out, "{}", last_style).unwrap();

        // Write everything to the tmp_string first.
        for y in 0..self.h {
            let mut x = 0;
            write!(out, "{}", termion::cursor::Goto(1, y as u16 + 1)).unwrap();
            while x < self.w {
                if let Some((ref style, ref text)) = buf[y * self.w + x] {
                    if style != last_style {
                        write!(out, "{}{}", style::Reset, style).unwrap();
                        last_style = style;
                    }
                    write!(out, "{}", text).unwrap();
                    x += 1;
                } else {
                    x += 1;
                }
            }
        }

        let (cx, cy) = self.cursor_pos;
        write!(
            out,
            "{}",
            termion::cursor::Goto(1 + cx as u16, 1 + cy as u16)
        ).unwrap();

        // Make sure everything is written out
        out.flush().unwrap();
    }
    pub fn draw(&self, x: usize, y: usize, text: &str) {
        self.draw_with_style(x, y, text, "");
    }

    pub fn draw_with_style(&self, x: usize, y: usize, text: &str, style: &str) {
        if y < self.h {
            let mut buf = self.buf.borrow_mut();
            let mut x = x;
            for g in UnicodeSegmentation::graphemes(text, true) {
                let width = UnicodeWidthStr::width(g);
                if width > 0 {
                    if x < self.w {
                        buf[y * self.w + x] = Some((style.to_string(), g.into()));
                    }
                    x += 1;
                    for _ in 0..(width - 1) {
                        if x < self.w {
                            buf[y * self.w + x] = None;
                        }
                        x += 1;
                    }
                }
            }
        }
    }

    pub fn hide_cursor(&self) {
        write!(self.out.borrow_mut(), "{}", termion::cursor::Hide).unwrap();
    }

    pub fn show_cursor(&self) {
        write!(self.out.borrow_mut(), "{}", termion::cursor::Show).unwrap();
    }

    pub fn move_cursor(&mut self, x: usize, y: usize) {
        self.cursor_pos = (x, y);
    }
}

impl Drop for Screen {
    fn drop(&mut self) {
        write!(
            self.out.borrow_mut(),
            "{}{}{}",
            color::Fg(color::Reset),
            color::Bg(color::Reset),
            termion::clear::All,
        ).unwrap();
        self.show_cursor();
    }
}
<commit_msg>Hide cursor movements<commit_after>use std;
use std::cell::RefCell;
use std::io;
use std::io::{BufWriter, Write};

use termion;
use termion::color;
use termion::color::Color;
use termion::input::MouseTerminal;
use termion::raw::{IntoRawMode, RawTerminal};
use termion::screen::AlternateScreen;
use termion::style;
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;

pub struct Screen {
    out: RefCell<MouseTerminal<AlternateScreen<RawTerminal<BufWriter<io::Stdout>>>>>,
    buf: RefCell<Vec<Option<(String, String)>>>,
    w: usize,
    h: usize,
    cursor_pos: (usize, usize),
    cursor_visible: bool,
}

impl Screen {
    pub fn new() -> Self {
        let (w, h) = termion::terminal_size().unwrap();
        let buf = std::iter::repeat(Some(("".into(), " ".into())))
            .take(w as usize * h as usize)
            .collect();
        Screen {
            out: RefCell::new(MouseTerminal::from(AlternateScreen::from(
                BufWriter::with_capacity(1 << 14, io::stdout())
                    .into_raw_mode()
                    .unwrap(),
            ))),
            buf: RefCell::new(buf),
            w: w as usize,
            h: h as usize,
            cursor_pos: (0, 0),
            cursor_visible: true,
        }
    }

    pub fn clear<C>(&self, col: &C)
    where
        C: Color + Clone,
    {
        for cell in self.buf.borrow_mut().iter_mut() {
            match *cell {
                Some((ref mut style, ref mut text)) => {
                    *style = format!("{}{}", color::Fg(col.clone()), color::Bg(col.clone()));
                    text.clear();
                    text.push_str(" ");
                }
                _ => {
                    *cell = Some((
                        format!("{}{}", color::Fg(col.clone()), color::Bg(col.clone())),
                        " ".into(),
                    ));
                }
            }
        }
    }

    #[allow(dead_code)]
    pub fn resize(&mut self, w: usize, h: usize) {
        self.w = w;
        self.h = h;
        self.buf
            .borrow_mut()
            .resize(w * h, Some(("".into(), " ".into())));
    }

    pub fn present(&self) {
        let mut out = self.out.borrow_mut();
        let buf = self.buf.borrow();

        write!(out, "{}", termion::cursor::Hide).unwrap();
        let mut last_style = "";
        write!(out, "{}", last_style).unwrap();

        // Write everything to the tmp_string first.
        for y in 0..self.h {
            let mut x = 0;
            write!(out, "{}", termion::cursor::Goto(1, y as u16 + 1)).unwrap();
            while x < self.w {
                if let Some((ref style, ref text)) = buf[y * self.w + x] {
                    if style != last_style {
                        write!(out, "{}{}", style::Reset, style).unwrap();
                        last_style = style;
                    }
                    write!(out, "{}", text).unwrap();
                    x += 1;
                } else {
                    x += 1;
                }
            }
        }

        if self.cursor_visible {
            let (cx, cy) = self.cursor_pos;
            write!(
                out,
                "{}{}",
                termion::cursor::Goto(1 + cx as u16, 1 + cy as u16),
                termion::cursor::Show,
            )
            .unwrap();
        }

        // Make sure everything is written out
        out.flush().unwrap();
    }
    pub fn draw(&self, x: usize, y: usize, text: &str) {
        self.draw_with_style(x, y, text, "");
    }

    pub fn draw_with_style(&self, x: usize, y: usize, text: &str, style: &str) {
        if y < self.h {
            let mut buf = self.buf.borrow_mut();
            let mut x = x;
            for g in UnicodeSegmentation::graphemes(text, true) {
                let width = UnicodeWidthStr::width(g);
                if width > 0 {
                    if x < self.w {
                        buf[y * self.w + x] = Some((style.to_string(), g.into()));
                    }
                    x += 1;
                    for _ in 0..(width - 1) {
                        if x < self.w {
                            buf[y * self.w + x] = None;
                        }
                        x += 1;
                    }
                }
            }
        }
    }

    pub fn hide_cursor(&mut self) {
        self.cursor_visible = false;
    }

    pub fn show_cursor(&mut self) {
        self.cursor_visible = true;
    }

    pub fn move_cursor(&mut self, x: usize, y: usize) {
        self.cursor_pos = (x, y);
    }
}

impl Drop for Screen {
    fn drop(&mut self) {
        write!(
            self.out.borrow_mut(),
            "{}{}{}",
            color::Fg(color::Reset),
            color::Bg(color::Reset),
            termion::clear::All,
        )
        .unwrap();
        self.show_cursor();
    }
}
<|endoftext|>


<commit_before>use std::collections::{HashMap};
use std::path::{Path};
use std::sync::{Arc};

use crossbeam::sync::{MsQueue};
use glium::backend::{Facade};

use debug::{gnomon, indicator};
use inverse_kinematics::{Chain};
use model::{Model};
use unlit_model::{UnlitModel};
use render::render_frame::{RenderFrame};

pub const DEPTH_DIMENSION: u32 = 2048;

#[derive(Eq, PartialEq, Hash, Copy, Clone)]
pub enum ModelId {
	Player,
	Scene,
	IKModel, // TODO: we are going to need more of these / a dynamic way to generate ids and load at a later time
	Tree,

	// DEBUG
	Gnomon,
	Indicator,
}

pub struct RenderContext {
	pub q: Arc<MsQueue<RenderFrame>>, // TODO: make private and provide minimal decent api
	window_size: (u32, u32), // TODO: maybe this should be a per RenderFrame parameter
	pub models: HashMap<ModelId, Arc<Model>>,

	// DEBUG
	pub unlit_models: HashMap<ModelId, Arc<UnlitModel>>,
}

impl RenderContext {
	pub fn new<F: Facade>(facade: &F, q: Arc<MsQueue<RenderFrame>>, window_size: (u32, u32), ik_chains: &Vec<Chain>) -> RenderContext {
		let model_map = load_initial_models(facade, ik_chains);

		// DEBUG
		let mut unlit_models = HashMap::new();
		unlit_models.insert(ModelId::Gnomon, Arc::new(gnomon::model(facade)));
		unlit_models.insert(ModelId::Indicator, Arc::new(indicator::model(facade)));

		RenderContext {
			q: q,
			window_size: window_size,
			models: model_map,

			// DEBUG
			unlit_models: unlit_models,
		}
	}

	pub fn aspect_ratio(&self) -> f32 {
		(self.window_size.0 as f32) / (self.window_size.1 as f32)
	}
}

// TODO: don't pass in chains but make something like IntoModel
//
fn load_initial_models<F: Facade>(facade: &F, ik_chains: &Vec<Chain>) -> HashMap<ModelId, Arc<Model>> {
	let mut map = HashMap::new();

	const MODEL_PATH_STRINGS: [(ModelId, &'static str); 3] = [
		(ModelId::Player, "./data/player.obj"),
		(ModelId::Scene,  "./data/level.obj"),
		(ModelId::Tree,   "./data/tree.obj")
	];
	for &(model_id, path) in &MODEL_PATH_STRINGS {
		let model = Arc::new(Model::new(facade, &Path::new(path)));
		map.insert(model_id, model);
	}
	for chain in ik_chains {
		map.insert(ModelId::IKModel, Arc::new(chain.model(facade)));
	}
	map
}

unsafe impl Send for RenderContext {}
unsafe impl Sync for RenderContext {}
<commit_msg>Use slice instead of ref Vec<commit_after>use std::collections::{HashMap};
use std::path::{Path};
use std::sync::{Arc};

use crossbeam::sync::{MsQueue};
use glium::backend::{Facade};

use debug::{gnomon, indicator};
use inverse_kinematics::{Chain};
use model::{Model};
use unlit_model::{UnlitModel};
use render::render_frame::{RenderFrame};

pub const DEPTH_DIMENSION: u32 = 2048;

#[derive(Eq, PartialEq, Hash, Copy, Clone)]
pub enum ModelId {
	Player,
	Scene,
	IKModel, // TODO: we are going to need more of these / a dynamic way to generate ids and load at a later time
	Tree,

	// DEBUG
	Gnomon,
	Indicator,
}

pub struct RenderContext {
	pub q: Arc<MsQueue<RenderFrame>>, // TODO: make private and provide minimal decent api
	window_size: (u32, u32), // TODO: maybe this should be a per RenderFrame parameter
	pub models: HashMap<ModelId, Arc<Model>>,

	// DEBUG
	pub unlit_models: HashMap<ModelId, Arc<UnlitModel>>,
}

impl RenderContext {
	pub fn new<F: Facade>(facade: &F, q: Arc<MsQueue<RenderFrame>>, window_size: (u32, u32), ik_chains: &[Chain]) -> RenderContext {
		let model_map = load_initial_models(facade, ik_chains);

		// DEBUG
		let mut unlit_models = HashMap::new();
		unlit_models.insert(ModelId::Gnomon, Arc::new(gnomon::model(facade)));
		unlit_models.insert(ModelId::Indicator, Arc::new(indicator::model(facade)));

		RenderContext {
			q: q,
			window_size: window_size,
			models: model_map,

			// DEBUG
			unlit_models: unlit_models,
		}
	}

	pub fn aspect_ratio(&self) -> f32 {
		(self.window_size.0 as f32) / (self.window_size.1 as f32)
	}
}

// TODO: don't pass in chains but make something like IntoModel
//
fn load_initial_models<F: Facade>(facade: &F, ik_chains: &[Chain]) -> HashMap<ModelId, Arc<Model>> {
	let mut map = HashMap::new();

	const MODEL_PATH_STRINGS: [(ModelId, &'static str); 3] = [
		(ModelId::Player, "./data/player.obj"),
		(ModelId::Scene,  "./data/level.obj"),
		(ModelId::Tree,   "./data/tree.obj")
	];
	for &(model_id, path) in &MODEL_PATH_STRINGS {
		let model = Arc::new(Model::new(facade, &Path::new(path)));
		map.insert(model_id, model);
	}
	for chain in ik_chains {
		map.insert(ModelId::IKModel, Arc::new(chain.model(facade)));
	}
	map
}

unsafe impl Send for RenderContext {}
unsafe impl Sync for RenderContext {}
<|endoftext|>


<commit_before>//! Serialization for TimeSteward data.
//!
//! TimeSteward has a few special requirements for serialization:
//! * DataTimelineHandle objects get special consideration, to support DAGs and cyclic data structures.
//! * The serialization needs to not block other operations for more than O(1) time at a time.
//! * The serialization must be lossless and platform-independent. For this reason, we always use bincode in low-endian mode.

struct SerializationContext <DataTimelineHandle: ???> {
  timeline_index: HashMap<DataTimelineHandle, TimelineInfo>,
}

impl SerializationContext {
  fn new ()->Self {

  }
  fn <T: TimeStewardSerialize> serialize (&mut self, data: & T) {

  }
}<commit_msg>Started writing the implementation of the real handle serialization/deserialization algorithms<commit_after>//! Serialization for TimeSteward data.
//!
//! TimeSteward has a few special requirements for serialization:
//! * DataTimelineHandle objects get special consideration, to support DAGs and cyclic data structures.
//! * The serialization needs to not block other operations for more than O(1) time at a time.
//! * The serialization must be lossless and platform-independent. For this reason, we always use bincode in low-endian mode.

use std::mem;
use std::ptr;

thread_local! {
  static DESERIALIZATION_CONTEXT: RefCell<Option <DeserializationContext>> = RefCell::new (None);
}

impl <T: DataTimeline> Serialize for DataTimelineHandle <T> {
  fn serialize <S: Serializer> (&self, serializer: S)->Result <S::Ok, S::Error> {
    (self.inner as usize).serialize (serializer)
  }
}

impl <'a, T: DataTimeline> Deserialize <'a> for DataTimelineHandle <T> {
  fn deserialize <D: Deserializer> (deserializer: D)->Result <Self, D::Error> {
    let old_pointer = usize::deserialize (deserializer)?;
    DESERIALIZATION_CONTEXT.with (| cell | {
      let mut guard = cell.borrow_mut();
      let mut map = &mut guard.unwrap().map;
      Ok(map.entry (old_pointer).or_insert_with (| | {
        DataTimelineHandle {
          inner: Arc::new (unsafe {mem::uninitialized()}),
        }
      }).clone())
    })
  }
}

impl DeserializationContext {
  fn deserialize_(deserializer: D)->Result <(), D::Error> {
    let old_pointer = usize::deserialize (deserializer)?;
    DESERIALIZATION_CONTEXT.with (| cell | {
      let mut guard = cell.borrow_mut();
      let mut map = &mut guard.unwrap().map;
      let new_pointer = map.entry (old_pointer).or_insert_with (| | {
        DataTimelineHandle {
          inner: Arc::new (unsafe {mem::uninitialized()}),
        }
      }).inner;
      unsafe {ptr::write (new_pointer as *mut DataTimelineHandle, DataTimeline::deserialize (D))?;}
    })?;
    Ok(())
  }
}
<|endoftext|>


<commit_before>use std::collections::HashMap;

pub trait StaticSymbolTable<'a>
{
	/// Return all symbols in this table with the specified name (in any namespace)
	fn lookup_by_name(name: &str) -> Vec<Symbol<'a>>;

	/// Return all symbols in this table with the specified namespace
	fn lookup_by_namespace(namespace: &str) -> Vec<Symbol<'a>>;

	/// Lookup a variable by its name and namespace. Duplicate symbols are not allowed, so the result will be unique
	/// @return the specified symbol or None if the specified symbol is not in this namespace
	fn lookup_variable(namespace: &str, name: &str) -> Option<Symbol<'a>>;

	/// Lookup a function by its name and namespace. Functions with the same signature are not allowed, so the result will be unique
	/// If no result is found in the direct namespace, the parent namespaces will be searched
	/// @return the specified symbol or None if the specified symbol is not in this namespace or a parent namespace
	fn lookup_function(namespace: &str, name: &str, argument_types: &Vec<&str>) -> Option<Symbol<'a>>;

	/// Lookup a structure (class, enum) by its name and namespace.
	/// Duplicate classes in the same namespace are not allowed, so the result will be unique
	/// If no result is found in the direct namespace, the parent namespaces will be searched
	/// @return the specified symbol AND it's specification (unwrapped, for convenience)
	/// or None if the specified symbol is not in this namespace or a parent namespace
	fn lookup_structure(namespace: &str, name: &str) -> Option<(Symbol<'a>, Structure<'a>)>;

	/// Adds a symbol to this table and allocates it's location
	/// Returns true if the symbol could be added; false otherwise
	/// Duplicate symbols are not allowed
	/// TODO: support overloaded methods
	fn add(class: SymbolClass<'a>, namespace: &'a str, name: &'a str) -> bool;
}

pub enum SymbolLocation<'a>
{
	/// Indicates that a register has been reserved for a specific use (e.g. by a variable)
	Register { name: &'a str },

	/// Indicates a location in memory
	Memory { address: MemoryAddress<'a> },

	/// Indicates that the symbol should not be accessed, as it represents a structured entity
	Structured
}

pub enum SymbolClass<'a>
{
	Variable { variable_type: &'a str },

	/// Function signature //TODO: support exceptions
	Function { return_type: &'a str, argument_types: &'a Vec<&'a str> },

	/// Includes class, enum, and interface
	Structure { subtype: &'a str, specification: Structure<'a> },
}

pub struct Structure<'a>
{
	/// Map from memberName -> (mmberType, offset)
	pub members: HashMap<&'a str, (&'a str, u16)>,
}

pub struct Symbol<'a>
{
	/// Namespace of this symbol, without the final "." or the name of this symbol
	pub namespace: &'a str,

	/// Identifier for the symbol (e.g. name of variable, function, class, etc. without it's namespace)
	pub name: &'a str,

	/// What this symbol represents (class, enum, variable, function, etc)
	pub symbol_class: SymbolClass<'a>,

	/// Memory location of this symbol. Methods will always map to a SymbolLocation::Memory label with a 0 offset
	pub location: SymbolLocation<'a>,
}

pub struct MemoryAddress<'a>
{
	/// Label marking the base address where the symbol is stored
	pub label_name: &'a str,

	/// Offset from the base address to access the symbol. Methods will always have an offset of 0
	pub offset: u16,
}
<commit_msg>Simplify Structure definition<commit_after>use std::collections::HashMap;

pub trait StaticSymbolTable<'a>
{
	/// Return all symbols in this table with the specified name (in any namespace)
	fn lookup_by_name(name: &str) -> Vec<Symbol<'a>>;

	/// Return all symbols in this table with the specified namespace
	fn lookup_by_namespace(namespace: &str) -> Vec<Symbol<'a>>;

	/// Lookup a variable by its name and namespace. Duplicate symbols are not allowed, so the result will be unique
	/// @return the specified symbol or None if the specified symbol is not in this namespace
	fn lookup_variable(namespace: &str, name: &str) -> Option<Symbol<'a>>;

	/// Lookup a function by its name and namespace. Functions with the same signature are not allowed, so the result will be unique
	/// If no result is found in the direct namespace, the parent namespaces will be searched
	/// @return the specified symbol or None if the specified symbol is not in this namespace or a parent namespace
	fn lookup_function(namespace: &str, name: &str, argument_types: &Vec<&str>) -> Option<Symbol<'a>>;

	/// Lookup a structure (class, enum) by its name and namespace.
	/// Duplicate classes in the same namespace are not allowed, so the result will be unique
	/// If no result is found in the direct namespace, the parent namespaces will be searched
	/// @return the specified symbol or None if the specified symbol is not in this namespace or a parent namespace
	fn lookup_structure(namespace: &str, name: &str) -> Option<(Symbol<'a>)>;

	/// Adds a symbol to this table and allocates it's location
	/// Returns true if the symbol could be added; false otherwise
	/// Duplicate symbols are not allowed
	/// TODO: support overloaded methods
	fn add(class: SymbolClass<'a>, namespace: &'a str, name: &'a str) -> bool;
}

pub enum SymbolLocation<'a>
{
	/// Indicates that a register has been reserved for a specific use (e.g. by a variable)
	Register { name: &'a str },

	/// Indicates a location in memory (e.g. for static variables)
	Memory { address: MemoryAddress<'a> },

	/// Indicates an offset location from a structured entity (e.g. a member variable of a class)
	InstancedMemory { offset: u16 },

	/// Indicates that the symbol should not be accessed, as it represents a structured entity
	Structured
}

pub enum SymbolClass<'a>
{
	Variable { variable_type: &'a str },

	/// Function signature //TODO: support exceptions
	Function { return_type: &'a str, argument_types: &'a Vec<&'a str> },

	/// Includes class, enum, and interface
	Structure { subtype: &'a str },
}

pub struct Symbol<'a>
{
	/// Namespace of this symbol, without the final "." or the name of this symbol
	pub namespace: &'a str,

	/// Identifier for the symbol (e.g. name of variable, function, class, etc. without it's namespace)
	pub name: &'a str,

	/// What this symbol represents (class, enum, variable, function, etc)
	pub symbol_class: SymbolClass<'a>,

	/// Memory location of this symbol. Methods will always map to a SymbolLocation::Memory label with a 0 offset
	pub location: SymbolLocation<'a>,
}

pub struct MemoryAddress<'a>
{
	/// Label marking the base address where the symbol is stored
	pub label_name: &'a str,

	/// Offset from the base address to access the symbol. Methods will always have an offset of 0
	pub offset: u16,
}
<|endoftext|>