<commit_before>use std::path::PathBuf;

use rustc_data_structures::fx::FxHashMap;

use crate::externalfiles::ExternalHtml;
use crate::html::escape::Escape;
use crate::html::format::{Buffer, Print};
use crate::html::render::{ensure_trailing_slash, StylePath};

#[derive(Clone)]
crate struct Layout {
    crate logo: String,
    crate favicon: String,
    crate external_html: ExternalHtml,
    crate default_settings: FxHashMap<String, String>,
    crate krate: String,
    /// The given user css file which allow to customize the generated
    /// documentation theme.
    crate css_file_extension: Option<PathBuf>,
    /// If false, the `select` element to have search filtering by crates on rendered docs
    /// won't be generated.
    crate generate_search_filter: bool,
}

crate struct Page<'a> {
    crate title: &'a str,
    crate css_class: &'a str,
    crate root_path: &'a str,
    crate static_root_path: Option<&'a str>,
    crate description: &'a str,
    crate keywords: &'a str,
    crate resource_suffix: &'a str,
    crate extra_scripts: &'a [&'a str],
    crate static_extra_scripts: &'a [&'a str],
}

crate fn render<T: Print, S: Print>(
    layout: &Layout,
    page: &Page<'_>,
    sidebar: S,
    t: T,
    style_files: &[StylePath],
) -> String {
    let static_root_path = page.static_root_path.unwrap_or(page.root_path);
    format!(
        "<!DOCTYPE html>\
\
\
    <meta charset=\"utf-8\">\
    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
    <meta name=\"generator\" content=\"rustdoc\">\
    <meta name=\"description\" content=\"{description}\">\
    <meta name=\"keywords\" content=\"{keywords}\">\
    <title>{title}</title>\
    <link rel=\"stylesheet\" type=\"text/css\" href=\"{static_root_path}normalize{suffix}.css\">\
    <link rel=\"stylesheet\" type=\"text/css\" href=\"{static_root_path}rustdoc{suffix}.css\" \
          id=\"mainThemeStyle\">\
    {style_files}\
    \
    \
    \
    <link rel=\"stylesheet\" href=\"{static_root_path}noscript{suffix}.css\">\
    {css_extension}\
    {favicon}\
    {in_header}\
    \
\
\
    \
    {before_content}\
    \

☰
\
        {logo}\
        {sidebar}\
    \

\
        <button id=\"theme-picker\" aria-label=\"Pick another theme!\" aria-haspopup=\"menu\">\
            \
        </button>\

\

\
    \
    \
        <form class=\"search-form\">\

\

{filter_crates}\
                    <input class=\"search-input\" name=\"search\" \
                           disabled \
                           autocomplete=\"off\" \
                           spellcheck=\"false\" \
                           placeholder=\"Click or press ‘S’ to search, ‘?’ for more options…\" \
                           type=\"search\">\

\
                <button type=\"button\" class=\"help-button\">?</button>
                \
                    \
                \

\
        </form>\
    \

{content}
\

\

\
    {after_content}\

    \
    {extra_scripts}\
\
",
        css_extension = if layout.css_file_extension.is_some() {
            format!(
                "<link rel=\"stylesheet\" \
                       type=\"text/css\" \
                       href=\"{static_root_path}theme{suffix}.css\">",
                static_root_path = static_root_path,
                suffix = page.resource_suffix
            )
        } else {
            String::new()
        },
        content = Buffer::html().to_display(t),
        static_root_path = static_root_path,
        root_path = page.root_path,
        css_class = page.css_class,
        logo = {
            if layout.logo.is_empty() {
                format!(
                    "\

\

",
                    root = page.root_path,
                    path = ensure_trailing_slash(&layout.krate),
                    static_root_path = static_root_path,
                    suffix = page.resource_suffix
                )
            } else {
                format!(
                    "\

",
                    root = page.root_path,
                    path = ensure_trailing_slash(&layout.krate),
                    logo = layout.logo
                )
            }
        },
        title = page.title,
        description = page.description,
        keywords = page.keywords,
        favicon = if layout.favicon.is_empty() {
            format!(
                r##"<link rel="icon" type="image/svg+xml" href="{static_root_path}favicon{suffix}.svg">
<link rel="alternate icon" type="image/png" href="{static_root_path}favicon-16x16{suffix}.png">
<link rel="alternate icon" type="image/png" href="{static_root_path}favicon-32x32{suffix}.png">"##,
                static_root_path = static_root_path,
                suffix = page.resource_suffix
            )
        } else {
            format!(r#"<link rel="shortcut icon" href="{}">"#, layout.favicon)
        },
        in_header = layout.external_html.in_header,
        before_content = layout.external_html.before_content,
        after_content = layout.external_html.after_content,
        sidebar = Buffer::html().to_display(sidebar),
        krate = layout.krate,
        default_settings = layout
            .default_settings
            .iter()
            .map(|(k, v)| format!(r#" data-{}="{}""#, k.replace('-', "_"), Escape(v)))
            .collect::<String>(),
        style_files = style_files
            .iter()
            .filter_map(|t| {
                if let Some(stem) = t.path.file_stem() { Some((stem, t.disabled)) } else { None }
            })
            .filter_map(|t| {
                if let Some(path) = t.0.to_str() { Some((path, t.1)) } else { None }
            })
            .map(|t| format!(
                r#"<link rel="stylesheet" type="text/css" href="{}.css" {} {}>"#,
                Escape(&format!("{}{}{}", static_root_path, t.0, page.resource_suffix)),
                if t.1 { "disabled" } else { "" },
                if t.0 == "light" { "id=\"themeStyle\"" } else { "" }
            ))
            .collect::<String>(),
        suffix = page.resource_suffix,
        extra_scripts = page
            .static_extra_scripts
            .iter()
            .map(|e| {
                format!(
                    "",
                    static_root_path = static_root_path,
                    extra_script = e
                )
            })
            .chain(page.extra_scripts.iter().map(|e| {
                format!(
                    "",
                    root_path = page.root_path,
                    extra_script = e
                )
            }))
            .collect::<String>(),
        filter_crates = if layout.generate_search_filter {
            "<select id=\"crate-search\">\
                 <option value=\"All crates\">All crates</option>\
             </select>"
        } else {
            ""
        },
    )
}

crate fn redirect(url: &str) -> String {
    //

"##,
        url = url,
    )
}
<commit_msg>crates.js should use root_path and not static_root_path<commit_after>use std::path::PathBuf;

use rustc_data_structures::fx::FxHashMap;

use crate::externalfiles::ExternalHtml;
use crate::html::escape::Escape;
use crate::html::format::{Buffer, Print};
use crate::html::render::{ensure_trailing_slash, StylePath};

#[derive(Clone)]
crate struct Layout {
    crate logo: String,
    crate favicon: String,
    crate external_html: ExternalHtml,
    crate default_settings: FxHashMap<String, String>,
    crate krate: String,
    /// The given user css file which allow to customize the generated
    /// documentation theme.
    crate css_file_extension: Option<PathBuf>,
    /// If false, the `select` element to have search filtering by crates on rendered docs
    /// won't be generated.
    crate generate_search_filter: bool,
}

crate struct Page<'a> {
    crate title: &'a str,
    crate css_class: &'a str,
    crate root_path: &'a str,
    crate static_root_path: Option<&'a str>,
    crate description: &'a str,
    crate keywords: &'a str,
    crate resource_suffix: &'a str,
    crate extra_scripts: &'a [&'a str],
    crate static_extra_scripts: &'a [&'a str],
}

crate fn render<T: Print, S: Print>(
    layout: &Layout,
    page: &Page<'_>,
    sidebar: S,
    t: T,
    style_files: &[StylePath],
) -> String {
    let static_root_path = page.static_root_path.unwrap_or(page.root_path);
    format!(
        "<!DOCTYPE html>\
\
\
    <meta charset=\"utf-8\">\
    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
    <meta name=\"generator\" content=\"rustdoc\">\
    <meta name=\"description\" content=\"{description}\">\
    <meta name=\"keywords\" content=\"{keywords}\">\
    <title>{title}</title>\
    <link rel=\"stylesheet\" type=\"text/css\" href=\"{static_root_path}normalize{suffix}.css\">\
    <link rel=\"stylesheet\" type=\"text/css\" href=\"{static_root_path}rustdoc{suffix}.css\" \
          id=\"mainThemeStyle\">\
    {style_files}\
    \
    \
    \
    <link rel=\"stylesheet\" href=\"{static_root_path}noscript{suffix}.css\">\
    {css_extension}\
    {favicon}\
    {in_header}\
    \
\
\
    \
    {before_content}\
    \

☰
\
        {logo}\
        {sidebar}\
    \

\
        <button id=\"theme-picker\" aria-label=\"Pick another theme!\" aria-haspopup=\"menu\">\
            \
        </button>\

\

\
    \
    \
        <form class=\"search-form\">\

\

{filter_crates}\
                    <input class=\"search-input\" name=\"search\" \
                           disabled \
                           autocomplete=\"off\" \
                           spellcheck=\"false\" \
                           placeholder=\"Click or press ‘S’ to search, ‘?’ for more options…\" \
                           type=\"search\">\

\
                <button type=\"button\" class=\"help-button\">?</button>
                \
                    \
                \

\
        </form>\
    \

{content}
\

\

\
    {after_content}\

    \
    {extra_scripts}\
\
",
        css_extension = if layout.css_file_extension.is_some() {
            format!(
                "<link rel=\"stylesheet\" \
                       type=\"text/css\" \
                       href=\"{static_root_path}theme{suffix}.css\">",
                static_root_path = static_root_path,
                suffix = page.resource_suffix
            )
        } else {
            String::new()
        },
        content = Buffer::html().to_display(t),
        static_root_path = static_root_path,
        root_path = page.root_path,
        css_class = page.css_class,
        logo = {
            if layout.logo.is_empty() {
                format!(
                    "\

\

",
                    root = page.root_path,
                    path = ensure_trailing_slash(&layout.krate),
                    static_root_path = static_root_path,
                    suffix = page.resource_suffix
                )
            } else {
                format!(
                    "\

",
                    root = page.root_path,
                    path = ensure_trailing_slash(&layout.krate),
                    logo = layout.logo
                )
            }
        },
        title = page.title,
        description = page.description,
        keywords = page.keywords,
        favicon = if layout.favicon.is_empty() {
            format!(
                r##"<link rel="icon" type="image/svg+xml" href="{static_root_path}favicon{suffix}.svg">
<link rel="alternate icon" type="image/png" href="{static_root_path}favicon-16x16{suffix}.png">
<link rel="alternate icon" type="image/png" href="{static_root_path}favicon-32x32{suffix}.png">"##,
                static_root_path = static_root_path,
                suffix = page.resource_suffix
            )
        } else {
            format!(r#"<link rel="shortcut icon" href="{}">"#, layout.favicon)
        },
        in_header = layout.external_html.in_header,
        before_content = layout.external_html.before_content,
        after_content = layout.external_html.after_content,
        sidebar = Buffer::html().to_display(sidebar),
        krate = layout.krate,
        default_settings = layout
            .default_settings
            .iter()
            .map(|(k, v)| format!(r#" data-{}="{}""#, k.replace('-', "_"), Escape(v)))
            .collect::<String>(),
        style_files = style_files
            .iter()
            .filter_map(|t| {
                if let Some(stem) = t.path.file_stem() { Some((stem, t.disabled)) } else { None }
            })
            .filter_map(|t| {
                if let Some(path) = t.0.to_str() { Some((path, t.1)) } else { None }
            })
            .map(|t| format!(
                r#"<link rel="stylesheet" type="text/css" href="{}.css" {} {}>"#,
                Escape(&format!("{}{}{}", static_root_path, t.0, page.resource_suffix)),
                if t.1 { "disabled" } else { "" },
                if t.0 == "light" { "id=\"themeStyle\"" } else { "" }
            ))
            .collect::<String>(),
        suffix = page.resource_suffix,
        extra_scripts = page
            .static_extra_scripts
            .iter()
            .map(|e| {
                format!(
                    "",
                    static_root_path = static_root_path,
                    extra_script = e
                )
            })
            .chain(page.extra_scripts.iter().map(|e| {
                format!(
                    "",
                    root_path = page.root_path,
                    extra_script = e
                )
            }))
            .collect::<String>(),
        filter_crates = if layout.generate_search_filter {
            "<select id=\"crate-search\">\
                 <option value=\"All crates\">All crates</option>\
             </select>"
        } else {
            ""
        },
    )
}

crate fn redirect(url: &str) -> String {
    //

"##,
        url = url,
    )
}
<|endoftext|>


<commit_before>use crate::ffi::CStr;
use crate::io;
use crate::mem;
use crate::sys::{unsupported, Void};
use crate::time::Duration;

use ::wasi::wasi_unstable as wasi;

pub struct Thread(Void);

pub const DEFAULT_MIN_STACK_SIZE: usize = 4096;

impl Thread {
    // unsafe: see thread::Builder::spawn_unchecked for safety requirements
    pub unsafe fn new(_stack: usize, _p: Box<dyn FnOnce()>)
        -> io::Result<Thread>
    {
        unsupported()
    }

    pub fn yield_now() {
        let ret = wasi::sched_yield();
        debug_assert_eq!(ret, Ok(()));
    }

    pub fn set_name(_name: &CStr) {
        // nope
    }

    pub fn sleep(dur: Duration) {
        let nanos = dur.as_nanos();
        assert!(nanos <= u64::max_value() as u128);

        const CLOCK_ID: wasi::Userdata = 0x0123_45678;

        let clock = wasi::raw::__wasi_subscription_u_clock_t {
            identifier: CLOCK_ID,
            clock_id: wasi::CLOCK_MONOTONIC,
            timeout: nanos as u64,
            precision: 0,
            flags: 0,
        };

        let in_ = [wasi::Subscription {
            userdata: 0,
            type_: wasi::EVENTTYPE_CLOCK,
            u: wasi::raw::__wasi_subscription_u { clock: clock },
        }];
        let mut out: [wasi::Event; 1] = [unsafe { mem::zeroed() }];
        let n = unsafe { wasi::poll_oneoff(&in_, &mut out).unwrap() };
        let wasi::Event { userdata, error, type_, .. } = out[0];
        match (n, userdata, error) {
            (1, CLOCK_ID, 0) if type_ == wasi::EVENTTYPE_CLOCK => {}
            _ => panic!("thread::sleep(): unexpected result of poll_oneoff"),
        }
    }

    pub fn join(self) {
        match self.0 {}
    }
}

pub mod guard {
    pub type Guard = !;
    pub unsafe fn current() -> Option<Guard> { None }
    pub unsafe fn init() -> Option<Guard> { None }
}
<commit_msg>simplify code<commit_after>use crate::ffi::CStr;
use crate::io;
use crate::mem;
use crate::sys::{unsupported, Void};
use crate::time::Duration;

use ::wasi::wasi_unstable as wasi;

pub struct Thread(Void);

pub const DEFAULT_MIN_STACK_SIZE: usize = 4096;

impl Thread {
    // unsafe: see thread::Builder::spawn_unchecked for safety requirements
    pub unsafe fn new(_stack: usize, _p: Box<dyn FnOnce()>)
        -> io::Result<Thread>
    {
        unsupported()
    }

    pub fn yield_now() {
        let ret = wasi::sched_yield();
        debug_assert_eq!(ret, Ok(()));
    }

    pub fn set_name(_name: &CStr) {
        // nope
    }

    pub fn sleep(dur: Duration) {
        let nanos = dur.as_nanos();
        assert!(nanos <= u64::max_value() as u128);

        const CLOCK_ID: wasi::Userdata = 0x0123_45678;

        let clock = wasi::raw::__wasi_subscription_u_clock_t {
            identifier: CLOCK_ID,
            clock_id: wasi::CLOCK_MONOTONIC,
            timeout: nanos as u64,
            precision: 0,
            flags: 0,
        };

        let in_ = [wasi::Subscription {
            userdata: 0,
            type_: wasi::EVENTTYPE_CLOCK,
            u: wasi::raw::__wasi_subscription_u { clock: clock },
        }];
        let (res, event) = unsafe {
            let mut out: [wasi::Event; 1] = mem::zeroed();
            let res = wasi::poll_oneoff(&in_, &mut out);
            (res, out[0])
        };
        match (res, event) {
            (Ok(1), wasi::Event {
                userdata: CLOCK_ID,
                error: 0,
                type_: wasi::EVENTTYPE_CLOCK,
                ..
            }) => {}
            _ => panic!("thread::sleep(): unexpected result of poll_oneoff"),
        }
    }

    pub fn join(self) {
        match self.0 {}
    }
}

pub mod guard {
    pub type Guard = !;
    pub unsafe fn current() -> Option<Guard> { None }
    pub unsafe fn init() -> Option<Guard> { None }
}
<|endoftext|>


<commit_before>//! A simple application that downloads the data dictionary and creates code or
//! data to reproduce it in the core library.
//!
//! ### How to use
//!
//! Simply run the application. It will automatically retrieve the dictionary
//! from the DCMTK data dictionary and store the result in "entries.rs".
//! Future versions will enable different kinds of outputs.
//!
//! Please use the `--help` flag for the full usage information.

use clap::{App, Arg};
use serde::Serialize;
use regex::Regex;

use heck::ShoutySnakeCase;
use std::{fs::{create_dir_all, File}, io::BufWriter};
use std::io::{BufRead, BufReader, Write};
use std::path::Path;

/// url to DCMTK dic file
const DEFAULT_LOCATION: &str = "https://raw.githubusercontent.com/DCMTK/dcmtk/master/dcmdata/data/dicom.dic";

fn main() {
    let matches = App::new("DICOM Dictionary Builder")
        .version("0.1.0")
        .arg(
            Arg::with_name("FROM")
                .default_value(DEFAULT_LOCATION)
                .help("Where to fetch the dictionary from"),
        )
        .arg(
            Arg::with_name("no-retired")
                .long("no-retired")
                .help("Whether to ignore retired tags")
                .takes_value(false),
        )
        .arg(
            Arg::with_name("OUTPUT")
                .short("o")
                .help("The path to the output file")
                .default_value("tags.rs")
                .takes_value(true),
        )
        .get_matches();

    let ignore_retired = matches.is_present("no-retired");

    let src = matches.value_of("FROM").unwrap();

    let dst = Path::new(matches.value_of("OUTPUT").unwrap());

    if src.starts_with("http:") || src.starts_with("https:") {
        // read from URL
        println!("Downloading DICOM dictionary ...");
        let mut resp = reqwest::blocking::get(src).unwrap();
        let mut data = vec![];
        resp.copy_to(&mut data).unwrap();

        let preamble = data.split(|&b| b == b'\n')
            .filter_map(|l| std::str::from_utf8(l).ok())
            .find(|l| l.contains("Copyright"))
            .unwrap_or("");
        let preamble = format!(
            "Adapted from the DCMTK project.\nURL: {}\nLicense: {}\n{}",
            src,
            "https://github.com/DCMTK/dcmtk/blob/master/COPYRIGHT",
            preamble,
        );

        let entries = parse_entries(&*data).unwrap();
        println!("Writing to file ...");
        to_code_file(dst, entries, !ignore_retired, &preamble).expect("Failed to write file");
    } else {
        // read from File
        let file = File::open(src).unwrap();
        let entries = parse_entries(BufReader::new(file)).unwrap();
        println!("Writing to file ...");
        to_code_file(dst, entries, !ignore_retired, "").expect("Failed to write file");
    }
}

type DynResult<T> = Result<T, Box<dyn std::error::Error>>;

fn parse_entries<R: BufRead>(source: R) -> DynResult<Vec<Entry>> {
    let mut result = vec![];

    for line in source.lines() {
        let line = line?;
        if line.starts_with('#') {
            continue;
        }

        // (0010,0010)	PN	PatientName	1	DICOM
        let parts: Vec<&str> = line.split('\t').collect();

        if parts[4] == "ILLEGAL" || parts[4] == "PRIVATE" || parts[4] == "GENERIC" {
            continue;
        }

        let mut vr = parts[1].to_string();

        // Some fields are dependent upon context.
        // We may want to support this at some point, but for now,
        // let's just choose some good defaults.
        if vr == "up" {
            vr = "UL".to_string();
        }
        if vr == "xs" {
            vr = "US".to_string();
        }
        if vr == "ox" {
            vr = "OB".to_string();
        }
        if vr == "px" {
            vr = "OB".to_string();
        }
        if vr == "lt" {
            vr = "OW".to_string();
        }
        if vr == "na" {
            // These are the "Item", "ItemDelimitationItem", "SequenceDelimitationItem", etc, values.
            // Question, should we generate const values for these, and use them internally?
            continue;
        }

        let mut alias = parts[2].to_string();
        if alias.starts_with("RETIRED_") {
            alias = alias.trim_start_matches("RETIRED_").to_string();
        }

        let tag = parts[0].to_string();

        let regex_tag = Regex::new(r"^\(([0-9A-F]{4}),([0-9A-F]{4})\)$")?;
        let regex_tag_group100 = Regex::new(r"^\(([0-9A-F]{2})00-[0-9A-F]{2}FF,([0-9A-F]{4})\)$")?;
        let regex_tag_element100 =
            Regex::new(r"^\(([0-9A-F]{4}),([0-9A-F]{2})00-[0-9A-F]{2}FF\)$")?;

        let cap = regex_tag.captures(tag.as_str());
        let tag_type;
        let tag_declaration = if let Some(cap) = cap {
            // single tag
            let group = cap.get(1).expect("capture group 1: group").as_str();
            let elem = cap.get(2).expect("capture group 2: element").as_str();
            tag_type = TagType::Single;
            format!("Tag(0x{}, 0x{})", group, elem)
        } else if let Some(cap) = regex_tag_group100.captures(tag.as_str()) {
            // tag range over groups: (ggxx, eeee)
            let group = cap.get(1).expect("capture group 1: group portion").as_str();
            let elem = cap.get(2).expect("capture group 2: element").as_str();
            tag_type = TagType::Group100;
            format!("Tag(0x{}00, 0x{})", group, elem)
        } else if let Some(cap) = regex_tag_element100.captures(tag.as_str()) {
            // tag range over elements: (gggg, eexx)
            let group = cap.get(1).expect("capture group 1: group").as_str();
            let elem = cap
                .get(2)
                .expect("capture group 2: element portion")
                .as_str();
            tag_type = TagType::Element100;
            format!("Tag(0x{}, 0x{}00)", group, elem)
        } else {
            panic!("invalid tag: {}", alias);
        };

        result.push(Entry {
            tag,
            vr,
            alias,
            vm: parts[3].to_string(),
            obs: parts[4].to_string(),
            is_retired: parts[4].contains("retired"),
            tag_declaration,
            tag_type,
        });
    }

    Ok(result)
}

#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Serialize)]
enum TagType {
    Single,
    Group100,
    Element100,
}

#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Serialize)]
struct Entry {
    /// (0010,0010)
    tag: String,
    /// PN
    vr: String,
    /// PatientName
    alias: String,
    /// 1
    vm: String,
    /// DICOM
    obs: String,
    /// Retired field?
    is_retired: bool,
    /// Tag declaration "Tag(0x6000, 0x1102)"
    tag_declaration: String,
    /// The type the tag represents
    tag_type: TagType,
}

/// Write the tag dictionary as Rust code.
fn to_code_file<P>(
    dest_path: P,
    entries: Vec<Entry>,
    include_retired: bool,
    preamble: &str,
) -> DynResult<()>
where
    P: AsRef<Path>,
{
    if let Some(p_dir) = dest_path.as_ref().parent() {
        create_dir_all(&p_dir)?;
    }
    let mut f = BufWriter::new(File::create(&dest_path)?);

    f.write_all(
        b"//! Automatically generated. Edit at your own risk.\n"
    )?;

    for line in preamble.split('\n') {
        write!(f, "//! {}\n", line)?;
    }

    f.write_all(b"\n\
    use dicom_core::dictionary::{DictionaryEntryRef, TagRange, TagRange::*};\n\
    use dicom_core::Tag;\n\
    use dicom_core::VR::*;\n\n",
    )?;

    for e in &entries {
        if !include_retired && e.is_retired {
            continue;
        }

        writeln!(f, "/// {} {} {} {} {}", e.alias, e.tag, e.vr, e.vm, e.obs)?;

        let tag_type = match e.tag_type {
            TagType::Single => "Tag",
            _ => "TagRange",
        };

        let tag_set = match e.tag_type {
            TagType::Single => e.tag_declaration.clone(),
            TagType::Group100 => format!("Group100({})", e.tag_declaration),
            TagType::Element100 => format!("Element100({})", e.tag_declaration),
        };

        writeln!(
            f,
            "#[rustfmt::skip]\npub const {}: {} = {};",
            e.alias.to_shouty_snake_case(),
            tag_type,
            tag_set,
        )?;
    }

    f.write_all(
        b"\n\n\
    type E = DictionaryEntryRef<'static>;\n\n\
    #[rustfmt::skip]\n\
    pub(crate) const ENTRIES: &[E] = &[\n",
    )?;
    for e in &entries {
        if !include_retired && e.is_retired {
            continue;
        }

        let (vr1, vr2) = e.vr.split_at(2);

        let second_vr = if vr2 != "" {
            format!(" /*{} */", vr2)
        } else {
            vr2.to_string()
        };

        let tag_set = match e.tag_type {
            TagType::Single => format!("Single({})", e.alias.to_shouty_snake_case()),
            _ => e.alias.to_shouty_snake_case(),
        };

        writeln!(
            f,
            "    E {{ tag: {}, alias: \"{}\", vr: {}{} }}, // {}",
            tag_set, e.alias, vr1, second_vr, e.obs
        )?;
    }
    f.write_all(b"];\n")?;

    Ok(())
}
<commit_msg>[dictionary-std] Update root docs<commit_after>//! A simple application that downloads the data dictionary and creates code or
//! data to reproduce it in the core library.
//!
//! ### How to use
//!
//! Simply run the application. It will automatically retrieve the dictionary
//! from the DCMTK data dictionary and store the result in "tags.rs".
//! Future versions will enable different kinds of outputs.
//!
//! Please use the `--help` flag for the full usage information.

use clap::{App, Arg};
use serde::Serialize;
use regex::Regex;

use heck::ShoutySnakeCase;
use std::{fs::{create_dir_all, File}, io::BufWriter};
use std::io::{BufRead, BufReader, Write};
use std::path::Path;

/// url to DCMTK dic file
const DEFAULT_LOCATION: &str = "https://raw.githubusercontent.com/DCMTK/dcmtk/master/dcmdata/data/dicom.dic";

fn main() {
    let matches = App::new("DICOM Dictionary Builder")
        .version("0.1.0")
        .arg(
            Arg::with_name("FROM")
                .default_value(DEFAULT_LOCATION)
                .help("Where to fetch the dictionary from"),
        )
        .arg(
            Arg::with_name("no-retired")
                .long("no-retired")
                .help("Whether to ignore retired tags")
                .takes_value(false),
        )
        .arg(
            Arg::with_name("OUTPUT")
                .short("o")
                .help("The path to the output file")
                .default_value("tags.rs")
                .takes_value(true),
        )
        .get_matches();

    let ignore_retired = matches.is_present("no-retired");

    let src = matches.value_of("FROM").unwrap();

    let dst = Path::new(matches.value_of("OUTPUT").unwrap());

    if src.starts_with("http:") || src.starts_with("https:") {
        // read from URL
        println!("Downloading DICOM dictionary ...");
        let mut resp = reqwest::blocking::get(src).unwrap();
        let mut data = vec![];
        resp.copy_to(&mut data).unwrap();

        let preamble = data.split(|&b| b == b'\n')
            .filter_map(|l| std::str::from_utf8(l).ok())
            .find(|l| l.contains("Copyright"))
            .unwrap_or("");
        let preamble = format!(
            "Adapted from the DCMTK project.\nURL: {}\nLicense: {}\n{}",
            src,
            "https://github.com/DCMTK/dcmtk/blob/master/COPYRIGHT",
            preamble,
        );

        let entries = parse_entries(&*data).unwrap();
        println!("Writing to file ...");
        to_code_file(dst, entries, !ignore_retired, &preamble).expect("Failed to write file");
    } else {
        // read from File
        let file = File::open(src).unwrap();
        let entries = parse_entries(BufReader::new(file)).unwrap();
        println!("Writing to file ...");
        to_code_file(dst, entries, !ignore_retired, "").expect("Failed to write file");
    }
}

type DynResult<T> = Result<T, Box<dyn std::error::Error>>;

fn parse_entries<R: BufRead>(source: R) -> DynResult<Vec<Entry>> {
    let mut result = vec![];

    for line in source.lines() {
        let line = line?;
        if line.starts_with('#') {
            continue;
        }

        // (0010,0010)	PN	PatientName	1	DICOM
        let parts: Vec<&str> = line.split('\t').collect();

        if parts[4] == "ILLEGAL" || parts[4] == "PRIVATE" || parts[4] == "GENERIC" {
            continue;
        }

        let mut vr = parts[1].to_string();

        // Some fields are dependent upon context.
        // We may want to support this at some point, but for now,
        // let's just choose some good defaults.
        if vr == "up" {
            vr = "UL".to_string();
        }
        if vr == "xs" {
            vr = "US".to_string();
        }
        if vr == "ox" {
            vr = "OB".to_string();
        }
        if vr == "px" {
            vr = "OB".to_string();
        }
        if vr == "lt" {
            vr = "OW".to_string();
        }
        if vr == "na" {
            // These are the "Item", "ItemDelimitationItem", "SequenceDelimitationItem", etc, values.
            // Question, should we generate const values for these, and use them internally?
            continue;
        }

        let mut alias = parts[2].to_string();
        if alias.starts_with("RETIRED_") {
            alias = alias.trim_start_matches("RETIRED_").to_string();
        }

        let tag = parts[0].to_string();

        let regex_tag = Regex::new(r"^\(([0-9A-F]{4}),([0-9A-F]{4})\)$")?;
        let regex_tag_group100 = Regex::new(r"^\(([0-9A-F]{2})00-[0-9A-F]{2}FF,([0-9A-F]{4})\)$")?;
        let regex_tag_element100 =
            Regex::new(r"^\(([0-9A-F]{4}),([0-9A-F]{2})00-[0-9A-F]{2}FF\)$")?;

        let cap = regex_tag.captures(tag.as_str());
        let tag_type;
        let tag_declaration = if let Some(cap) = cap {
            // single tag
            let group = cap.get(1).expect("capture group 1: group").as_str();
            let elem = cap.get(2).expect("capture group 2: element").as_str();
            tag_type = TagType::Single;
            format!("Tag(0x{}, 0x{})", group, elem)
        } else if let Some(cap) = regex_tag_group100.captures(tag.as_str()) {
            // tag range over groups: (ggxx, eeee)
            let group = cap.get(1).expect("capture group 1: group portion").as_str();
            let elem = cap.get(2).expect("capture group 2: element").as_str();
            tag_type = TagType::Group100;
            format!("Tag(0x{}00, 0x{})", group, elem)
        } else if let Some(cap) = regex_tag_element100.captures(tag.as_str()) {
            // tag range over elements: (gggg, eexx)
            let group = cap.get(1).expect("capture group 1: group").as_str();
            let elem = cap
                .get(2)
                .expect("capture group 2: element portion")
                .as_str();
            tag_type = TagType::Element100;
            format!("Tag(0x{}, 0x{}00)", group, elem)
        } else {
            panic!("invalid tag: {}", alias);
        };

        result.push(Entry {
            tag,
            vr,
            alias,
            vm: parts[3].to_string(),
            obs: parts[4].to_string(),
            is_retired: parts[4].contains("retired"),
            tag_declaration,
            tag_type,
        });
    }

    Ok(result)
}

#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Serialize)]
enum TagType {
    Single,
    Group100,
    Element100,
}

#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Serialize)]
struct Entry {
    /// (0010,0010)
    tag: String,
    /// PN
    vr: String,
    /// PatientName
    alias: String,
    /// 1
    vm: String,
    /// DICOM
    obs: String,
    /// Retired field?
    is_retired: bool,
    /// Tag declaration "Tag(0x6000, 0x1102)"
    tag_declaration: String,
    /// The type the tag represents
    tag_type: TagType,
}

/// Write the tag dictionary as Rust code.
fn to_code_file<P>(
    dest_path: P,
    entries: Vec<Entry>,
    include_retired: bool,
    preamble: &str,
) -> DynResult<()>
where
    P: AsRef<Path>,
{
    if let Some(p_dir) = dest_path.as_ref().parent() {
        create_dir_all(&p_dir)?;
    }
    let mut f = BufWriter::new(File::create(&dest_path)?);

    f.write_all(
        b"//! Automatically generated. Edit at your own risk.\n"
    )?;

    for line in preamble.split('\n') {
        write!(f, "//! {}\n", line)?;
    }

    f.write_all(b"\n\
    use dicom_core::dictionary::{DictionaryEntryRef, TagRange, TagRange::*};\n\
    use dicom_core::Tag;\n\
    use dicom_core::VR::*;\n\n",
    )?;

    for e in &entries {
        if !include_retired && e.is_retired {
            continue;
        }

        writeln!(f, "/// {} {} {} {} {}", e.alias, e.tag, e.vr, e.vm, e.obs)?;

        let tag_type = match e.tag_type {
            TagType::Single => "Tag",
            _ => "TagRange",
        };

        let tag_set = match e.tag_type {
            TagType::Single => e.tag_declaration.clone(),
            TagType::Group100 => format!("Group100({})", e.tag_declaration),
            TagType::Element100 => format!("Element100({})", e.tag_declaration),
        };

        writeln!(
            f,
            "#[rustfmt::skip]\npub const {}: {} = {};",
            e.alias.to_shouty_snake_case(),
            tag_type,
            tag_set,
        )?;
    }

    f.write_all(
        b"\n\n\
    type E = DictionaryEntryRef<'static>;\n\n\
    #[rustfmt::skip]\n\
    pub(crate) const ENTRIES: &[E] = &[\n",
    )?;
    for e in &entries {
        if !include_retired && e.is_retired {
            continue;
        }

        let (vr1, vr2) = e.vr.split_at(2);

        let second_vr = if vr2 != "" {
            format!(" /*{} */", vr2)
        } else {
            vr2.to_string()
        };

        let tag_set = match e.tag_type {
            TagType::Single => format!("Single({})", e.alias.to_shouty_snake_case()),
            _ => e.alias.to_shouty_snake_case(),
        };

        writeln!(
            f,
            "    E {{ tag: {}, alias: \"{}\", vr: {}{} }}, // {}",
            tag_set, e.alias, vr1, second_vr, e.obs
        )?;
    }
    f.write_all(b"];\n")?;

    Ok(())
}
<|endoftext|>


<commit_before>mod build_tables;
mod dedup;
mod grammars;
mod nfa;
mod node_types;
mod npm_files;
pub mod parse_grammar;
mod prepare_grammar;
pub mod properties;
mod render;
mod rules;
mod tables;

use self::build_tables::build_tables;
use self::grammars::{InlinedProductionMap, LexicalGrammar, SyntaxGrammar, VariableType};
use self::parse_grammar::parse_grammar;
use self::prepare_grammar::prepare_grammar;
use self::render::render_c_code;
use self::rules::AliasMap;
use crate::error::{Error, Result};
use lazy_static::lazy_static;
use regex::{Regex, RegexBuilder};
use std::collections::HashSet;
use std::fs::{self, File};
use std::io::{BufWriter, Write};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};

lazy_static! {
    static ref JSON_COMMENT_REGEX: Regex = RegexBuilder::new("^\\s*//.*")
        .multi_line(true)
        .build()
        .unwrap();
}

struct GeneratedParser {
    c_code: String,
    node_types_json: String,
}

pub fn generate_parser_in_directory(
    repo_path: &PathBuf,
    grammar_path: Option<&str>,
    properties_only: bool,
) -> Result<()> {
    let src_path = repo_path.join("src");
    let header_path = src_path.join("tree_sitter");
    let properties_dir_path = repo_path.join("properties");

    // Ensure that the output directories exist.
    fs::create_dir_all(&src_path)?;
    fs::create_dir_all(&header_path)?;

    // Read the grammar.json.
    let grammar_json;
    match grammar_path {
        Some(path) => {
            grammar_json = load_grammar_file(path.as_ref())?;
        }
        None => {
            let grammar_js_path = grammar_path.map_or(repo_path.join("grammar.js"), |s| s.into());
            grammar_json = load_grammar_file(&grammar_js_path)?;
            fs::write(&src_path.join("grammar.json"), &grammar_json)?;
        }
    }

    // Parse and preprocess the grammar.
    let input_grammar = parse_grammar(&grammar_json)?;
    let (syntax_grammar, lexical_grammar, inlines, simple_aliases) =
        prepare_grammar(&input_grammar)?;
    let language_name = input_grammar.name;

    // If run with no arguments, read all of the property sheets and compile them to JSON.
    if grammar_path.is_none() {
        let token_names = get_token_names(&syntax_grammar, &lexical_grammar);
        if let Ok(entries) = fs::read_dir(properties_dir_path) {
            for entry in entries {
                let css_path = entry?.path();
                let css = fs::read_to_string(&css_path)?;
                let sheet = properties::generate_property_sheet(&css_path, &css, &token_names)?;
                let property_sheet_json_path = src_path
                    .join(css_path.file_name().unwrap())
                    .with_extension("json");
                let property_sheet_json_file =
                    File::create(&property_sheet_json_path).map_err(Error::wrap(|| {
                        format!("Failed to create {:?}", property_sheet_json_path)
                    }))?;
                let mut writer = BufWriter::new(property_sheet_json_file);
                serde_json::to_writer_pretty(&mut writer, &sheet)?;
            }
        }
    }

    // Generate the parser and related files.
    if !properties_only {
        let GeneratedParser {
            c_code,
            node_types_json,
        } = generate_parser_for_grammar_with_opts(
            &language_name,
            syntax_grammar,
            lexical_grammar,
            inlines,
            simple_aliases,
        )?;

        write_file(&src_path.join("parser.c"), c_code)?;
        write_file(&src_path.join("node-types.json"), node_types_json)?;
        write_file(&header_path.join("parser.h"), tree_sitter::PARSER_HEADER)?;
        write_file(
            &repo_path.join("index.js"),
            npm_files::index_js(&language_name),
        )?;
        ensure_file(&src_path.join("binding.cc"), || {
            npm_files::binding_cc(&language_name)
        })?;
        ensure_file(&repo_path.join("binding.gyp"), || {
            npm_files::binding_gyp(&language_name)
        })?;
    }

    Ok(())
}

pub fn generate_parser_for_grammar(grammar_json: &str) -> Result<(String, String)> {
    let grammar_json = JSON_COMMENT_REGEX.replace_all(grammar_json, "\n");
    let input_grammar = parse_grammar(&grammar_json)?;
    let (syntax_grammar, lexical_grammar, inlines, simple_aliases) =
        prepare_grammar(&input_grammar)?;
    let parser = generate_parser_for_grammar_with_opts(
        &input_grammar.name,
        syntax_grammar,
        lexical_grammar,
        inlines,
        simple_aliases,
    )?;
    Ok((input_grammar.name, parser.c_code))
}

fn generate_parser_for_grammar_with_opts(
    name: &String,
    syntax_grammar: SyntaxGrammar,
    lexical_grammar: LexicalGrammar,
    inlines: InlinedProductionMap,
    simple_aliases: AliasMap,
) -> Result<GeneratedParser> {
    let variable_info = node_types::get_variable_info(&syntax_grammar, &lexical_grammar, &inlines)?;
    let node_types_json = node_types::generate_node_types_json(
        &syntax_grammar,
        &lexical_grammar,
        &simple_aliases,
        &variable_info,
    );
    let (parse_table, main_lex_table, keyword_lex_table, keyword_capture_token) = build_tables(
        &syntax_grammar,
        &lexical_grammar,
        &simple_aliases,
        &variable_info,
        &inlines,
    )?;
    let c_code = render_c_code(
        name,
        parse_table,
        main_lex_table,
        keyword_lex_table,
        keyword_capture_token,
        syntax_grammar,
        lexical_grammar,
        simple_aliases,
    );
    Ok(GeneratedParser {
        c_code,
        node_types_json: serde_json::to_string_pretty(&node_types_json).unwrap(),
    })
}

fn get_token_names(
    syntax_grammar: &SyntaxGrammar,
    lexical_grammar: &LexicalGrammar,
) -> HashSet<String> {
    let mut result = HashSet::new();
    for variable in &lexical_grammar.variables {
        if variable.kind == VariableType::Named {
            result.insert(variable.name.clone());
        }
    }
    for token in &syntax_grammar.external_tokens {
        if token.kind == VariableType::Named {
            result.insert(token.name.clone());
        }
    }
    for variable in &syntax_grammar.variables {
        for production in &variable.productions {
            for step in &production.steps {
                if let Some(alias) = &step.alias {
                    if !step.symbol.is_non_terminal() && alias.is_named {
                        result.insert(alias.value.clone());
                    }
                }
            }
        }
    }
    result
}

fn load_grammar_file(grammar_path: &Path) -> Result<String> {
    match grammar_path.extension().and_then(|e| e.to_str()) {
        Some("js") => Ok(load_js_grammar_file(grammar_path)?),
        Some("json") => Ok(fs::read_to_string(grammar_path)?),
        _ => Err(Error::new(format!(
            "Unknown grammar file extension: {:?}",
            grammar_path
        ))),
    }
}

fn load_js_grammar_file(grammar_path: &Path) -> Result<String> {
    let mut node_process = Command::new("node")
        .env("TREE_SITTER_GRAMMAR_PATH", grammar_path)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .expect("Failed to run `node`");

    let mut node_stdin = node_process
        .stdin
        .take()
        .expect("Failed to open stdin for node");
    let javascript_code = include_bytes!("./dsl.js");
    node_stdin
        .write(javascript_code)
        .expect("Failed to write to node's stdin");
    drop(node_stdin);
    let output = node_process
        .wait_with_output()
        .expect("Failed to read output from node");
    match output.status.code() {
        None => panic!("Node process was killed"),
        Some(0) => {}
        Some(code) => return Error::err(format!("Node process exited with status {}", code)),
    }

    let mut result = String::from_utf8(output.stdout).expect("Got invalid UTF8 from node");
    result.push('\n');
    Ok(result)
}

fn write_file(path: &Path, body: impl AsRef<[u8]>) -> Result<()> {
    fs::write(path, body).map_err(Error::wrap(|| {
        format!("Failed to write {:?}", path.file_name().unwrap())
    }))
}

fn ensure_file<T: AsRef<[u8]>>(path: &PathBuf, f: impl Fn() -> T) -> Result<()> {
    if path.exists() {
        Ok(())
    } else {
        write_file(path, f().as_ref())
    }
}
<commit_msg>cli: Don't regenerate index.js if it already exists<commit_after>mod build_tables;
mod dedup;
mod grammars;
mod nfa;
mod node_types;
mod npm_files;
pub mod parse_grammar;
mod prepare_grammar;
pub mod properties;
mod render;
mod rules;
mod tables;

use self::build_tables::build_tables;
use self::grammars::{InlinedProductionMap, LexicalGrammar, SyntaxGrammar, VariableType};
use self::parse_grammar::parse_grammar;
use self::prepare_grammar::prepare_grammar;
use self::render::render_c_code;
use self::rules::AliasMap;
use crate::error::{Error, Result};
use lazy_static::lazy_static;
use regex::{Regex, RegexBuilder};
use std::collections::HashSet;
use std::fs::{self, File};
use std::io::{BufWriter, Write};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};

lazy_static! {
    static ref JSON_COMMENT_REGEX: Regex = RegexBuilder::new("^\\s*//.*")
        .multi_line(true)
        .build()
        .unwrap();
}

struct GeneratedParser {
    c_code: String,
    node_types_json: String,
}

pub fn generate_parser_in_directory(
    repo_path: &PathBuf,
    grammar_path: Option<&str>,
    properties_only: bool,
) -> Result<()> {
    let src_path = repo_path.join("src");
    let header_path = src_path.join("tree_sitter");
    let properties_dir_path = repo_path.join("properties");

    // Ensure that the output directories exist.
    fs::create_dir_all(&src_path)?;
    fs::create_dir_all(&header_path)?;

    // Read the grammar.json.
    let grammar_json;
    match grammar_path {
        Some(path) => {
            grammar_json = load_grammar_file(path.as_ref())?;
        }
        None => {
            let grammar_js_path = grammar_path.map_or(repo_path.join("grammar.js"), |s| s.into());
            grammar_json = load_grammar_file(&grammar_js_path)?;
            fs::write(&src_path.join("grammar.json"), &grammar_json)?;
        }
    }

    // Parse and preprocess the grammar.
    let input_grammar = parse_grammar(&grammar_json)?;
    let (syntax_grammar, lexical_grammar, inlines, simple_aliases) =
        prepare_grammar(&input_grammar)?;
    let language_name = input_grammar.name;

    // If run with no arguments, read all of the property sheets and compile them to JSON.
    if grammar_path.is_none() {
        let token_names = get_token_names(&syntax_grammar, &lexical_grammar);
        if let Ok(entries) = fs::read_dir(properties_dir_path) {
            for entry in entries {
                let css_path = entry?.path();
                let css = fs::read_to_string(&css_path)?;
                let sheet = properties::generate_property_sheet(&css_path, &css, &token_names)?;
                let property_sheet_json_path = src_path
                    .join(css_path.file_name().unwrap())
                    .with_extension("json");
                let property_sheet_json_file =
                    File::create(&property_sheet_json_path).map_err(Error::wrap(|| {
                        format!("Failed to create {:?}", property_sheet_json_path)
                    }))?;
                let mut writer = BufWriter::new(property_sheet_json_file);
                serde_json::to_writer_pretty(&mut writer, &sheet)?;
            }
        }
    }

    // Generate the parser and related files.
    if !properties_only {
        let GeneratedParser {
            c_code,
            node_types_json,
        } = generate_parser_for_grammar_with_opts(
            &language_name,
            syntax_grammar,
            lexical_grammar,
            inlines,
            simple_aliases,
        )?;

        write_file(&src_path.join("parser.c"), c_code)?;
        write_file(&src_path.join("node-types.json"), node_types_json)?;
        write_file(&header_path.join("parser.h"), tree_sitter::PARSER_HEADER)?;
        ensure_file(&repo_path.join("index.js"), || {
            npm_files::index_js(&language_name)
        })?;
        ensure_file(&src_path.join("binding.cc"), || {
            npm_files::binding_cc(&language_name)
        })?;
        ensure_file(&repo_path.join("binding.gyp"), || {
            npm_files::binding_gyp(&language_name)
        })?;
    }

    Ok(())
}

pub fn generate_parser_for_grammar(grammar_json: &str) -> Result<(String, String)> {
    let grammar_json = JSON_COMMENT_REGEX.replace_all(grammar_json, "\n");
    let input_grammar = parse_grammar(&grammar_json)?;
    let (syntax_grammar, lexical_grammar, inlines, simple_aliases) =
        prepare_grammar(&input_grammar)?;
    let parser = generate_parser_for_grammar_with_opts(
        &input_grammar.name,
        syntax_grammar,
        lexical_grammar,
        inlines,
        simple_aliases,
    )?;
    Ok((input_grammar.name, parser.c_code))
}

fn generate_parser_for_grammar_with_opts(
    name: &String,
    syntax_grammar: SyntaxGrammar,
    lexical_grammar: LexicalGrammar,
    inlines: InlinedProductionMap,
    simple_aliases: AliasMap,
) -> Result<GeneratedParser> {
    let variable_info = node_types::get_variable_info(&syntax_grammar, &lexical_grammar, &inlines)?;
    let node_types_json = node_types::generate_node_types_json(
        &syntax_grammar,
        &lexical_grammar,
        &simple_aliases,
        &variable_info,
    );
    let (parse_table, main_lex_table, keyword_lex_table, keyword_capture_token) = build_tables(
        &syntax_grammar,
        &lexical_grammar,
        &simple_aliases,
        &variable_info,
        &inlines,
    )?;
    let c_code = render_c_code(
        name,
        parse_table,
        main_lex_table,
        keyword_lex_table,
        keyword_capture_token,
        syntax_grammar,
        lexical_grammar,
        simple_aliases,
    );
    Ok(GeneratedParser {
        c_code,
        node_types_json: serde_json::to_string_pretty(&node_types_json).unwrap(),
    })
}

fn get_token_names(
    syntax_grammar: &SyntaxGrammar,
    lexical_grammar: &LexicalGrammar,
) -> HashSet<String> {
    let mut result = HashSet::new();
    for variable in &lexical_grammar.variables {
        if variable.kind == VariableType::Named {
            result.insert(variable.name.clone());
        }
    }
    for token in &syntax_grammar.external_tokens {
        if token.kind == VariableType::Named {
            result.insert(token.name.clone());
        }
    }
    for variable in &syntax_grammar.variables {
        for production in &variable.productions {
            for step in &production.steps {
                if let Some(alias) = &step.alias {
                    if !step.symbol.is_non_terminal() && alias.is_named {
                        result.insert(alias.value.clone());
                    }
                }
            }
        }
    }
    result
}

fn load_grammar_file(grammar_path: &Path) -> Result<String> {
    match grammar_path.extension().and_then(|e| e.to_str()) {
        Some("js") => Ok(load_js_grammar_file(grammar_path)?),
        Some("json") => Ok(fs::read_to_string(grammar_path)?),
        _ => Err(Error::new(format!(
            "Unknown grammar file extension: {:?}",
            grammar_path
        ))),
    }
}

fn load_js_grammar_file(grammar_path: &Path) -> Result<String> {
    let mut node_process = Command::new("node")
        .env("TREE_SITTER_GRAMMAR_PATH", grammar_path)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .expect("Failed to run `node`");

    let mut node_stdin = node_process
        .stdin
        .take()
        .expect("Failed to open stdin for node");
    let javascript_code = include_bytes!("./dsl.js");
    node_stdin
        .write(javascript_code)
        .expect("Failed to write to node's stdin");
    drop(node_stdin);
    let output = node_process
        .wait_with_output()
        .expect("Failed to read output from node");
    match output.status.code() {
        None => panic!("Node process was killed"),
        Some(0) => {}
        Some(code) => return Error::err(format!("Node process exited with status {}", code)),
    }

    let mut result = String::from_utf8(output.stdout).expect("Got invalid UTF8 from node");
    result.push('\n');
    Ok(result)
}

fn write_file(path: &Path, body: impl AsRef<[u8]>) -> Result<()> {
    fs::write(path, body).map_err(Error::wrap(|| {
        format!("Failed to write {:?}", path.file_name().unwrap())
    }))
}

fn ensure_file<T: AsRef<[u8]>>(path: &PathBuf, f: impl Fn() -> T) -> Result<()> {
    if path.exists() {
        Ok(())
    } else {
        write_file(path, f().as_ref())
    }
}
<|endoftext|>


<commit_before>//! The main searching function.

pub mod eval;

use std::cmp::{min,max};
use std::sync::atomic::{Ordering,AtomicBool};

use pleco::{MoveList,Board,BitMove};
use pleco::core::*;
use pleco::tools::tt::*;
use pleco::core::score::*;
use pleco::tools::eval::Eval;
use pleco::tools::pleco_arc::Arc;

use MAX_PLY;
use TT_TABLE;

use time::time_management::TimeManager;
use time::uci_timer::*;
use threadpool::TIMER;
use root_moves::root_moves_list::RootMoveList;
use consts::*;

const THREAD_DIST: usize = 20;

//                                      1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20
static SKIP_SIZE: [u16; THREAD_DIST] = [1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4];
static START_PLY: [u16; THREAD_DIST] = [0, 1, 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 7];

pub struct Searcher {
    pub limit: Limits,
    pub board: Board,
    pub time_man: &'static TimeManager,
    pub tt: &'static TranspositionTable,
    pub id: usize,
    pub root_moves: RootMoveList,
    pub use_stdout: Arc<AtomicBool>,
}

impl Searcher {
    pub fn search_root(&mut self) {
        assert!(self.board.depth() == 0);
        self.root_moves.set_finished(false);
        if self.stop() {
            self.root_moves.set_finished(true);
            return;
        }

        if self.use_stdout() {
            println!("info id {} start", self.id);
        }

//        if self.main_thread() {
//            println!("info max_time: {}, ideal time: {}", self.time_man.maximum_time(), self.time_man.ideal_time());
//        }

        let max_depth = if let LimitsType::Depth(d) = self.limit.limits_type {
            d
        } else {
            MAX_PLY
        };

        let start_ply: u16 = START_PLY[self.id % THREAD_DIST];
        let skip_size: u16 = SKIP_SIZE[self.id % THREAD_DIST];
        let mut depth: u16 = start_ply;

        let mut delta: i32 = NEG_INFINITE as i32;
        #[allow(unused_assignments)]
        let mut best_value: i32 = NEG_INFINITE as i32;
        let mut alpha: i32 = NEG_INFINITE as i32;
        let mut beta: i32 = INFINITE as i32;

        let mut time_reduction: f64 = 1.0;
        let mut last_best_move: BitMove = BitMove::null();
        let mut best_move_stability: u32 = 0;

        self.root_moves.shuffle(self.id, &self.board);

        'iterative_deepening: while !self.stop() && depth < max_depth {
            self.root_moves.rollback();

            if depth >= 5 {
                delta = 18;
                alpha = max(self.root_moves.prev_best_score() - delta, NEG_INFINITE as i32);
                beta = min(self.root_moves.prev_best_score() + delta, INFINITE as i32);
            }

            'aspiration_window: loop {

                best_value = self.search::<PV>(alpha, beta, depth) as i32;
                self.root_moves.sort();

                if self.stop() {
                    break 'aspiration_window;
                }

                if best_value <= alpha {
                    alpha = max(best_value - delta, NEG_INFINITE as i32);
                } else if best_value >= beta {
                    beta = min(best_value + delta, INFINITE as i32);
                } else {
                    break 'aspiration_window;
                }
                delta += (delta / 4) + 5;

                assert!(alpha >= NEG_INFINITE as i32);
                assert!(beta <= INFINITE as i32);
            }

            self.root_moves.sort();
            if self.use_stdout() && self.main_thread() {
                println!("info depth {}", depth);
            }
            if !self.stop() {
                self.root_moves.set_depth_completed(depth);
            }
            depth += skip_size;

            if !self.main_thread() {
                continue;
            }

            let best_move = self.root_moves.first().bit_move;
            if best_move != last_best_move {
                time_reduction = 1.0;
                best_move_stability = 0;
            } else {
                time_reduction *= 0.91;
                best_move_stability += 1;
            }

            last_best_move = best_move;

            // check for time
            if let Some(_) = self.limit.use_time_management() {
                if !self.stop() {
                    let ideal = TIMER.ideal_time();
                    let elapsed = TIMER.elapsed();
                    let stability: f64 = f64::powi(0.92, best_move_stability as i32);
                    let new_ideal = (ideal as f64 * stability * time_reduction) as i64;
                    println!("ideal: {}, new_ideal: {}, elapsed: {}", ideal, new_ideal, elapsed);
                    if self.root_moves.len() == 1 || TIMER.elapsed() >= new_ideal {
                        break 'iterative_deepening;
                    }
                }
            }

        }
        self.root_moves.set_finished(true);
    }

    fn search<N: PVNode>(&mut self, mut alpha: i32, beta: i32, max_depth: u16) -> i32 {
        let is_pv: bool = N::is_pv();
        let at_root: bool = self.board.depth() == 0;
        let zob = self.board.zobrist();
        let (tt_hit, tt_entry): (bool, &mut Entry) = TT_TABLE.probe(zob);
        let tt_value = if tt_hit {tt_entry.score as i32} else {0};
        let in_check: bool = self.board.in_check();
        let ply = self.board.depth();

        let mut best_move = BitMove::null();

        let mut value = NEG_INFINITE as i32;
        let mut best_value = NEG_INFINITE as i32;
        let mut moves_played = 0;

        let mut pos_eval: i32 = 0;

        if self.main_thread() {
            self.check_time();
        }

        if ply >= max_depth || self.stop() {
            return Eval::eval_low(&self.board) as i32;
        }

        let plys_to_zero = max_depth - ply;

        if !at_root {
            if alpha >= beta {
                return alpha
            }
        }

        if !is_pv
            && tt_hit
            && tt_entry.depth as u16 >= plys_to_zero
            && tt_value != 0
            && correct_bound_eq(tt_value, beta, tt_entry.node_type()) {
            return tt_value;
        }

        if in_check {
            pos_eval = 0;
        } else {
            if tt_hit {
                if tt_entry.eval == 0 {
                    pos_eval = Eval::eval_low(&self.board) as i32;
                }
                if tt_value != 0 && correct_bound(tt_value, pos_eval, tt_entry.node_type()) {
                    pos_eval = tt_value;
                }
            } else {
                pos_eval = Eval::eval_low(&self.board) as i32;
                tt_entry.place(zob, BitMove::null(), 0, pos_eval as i16, 0, NodeBound::NoBound);
            }
        }

        if !in_check {

            if ply > 3
                && ply < 7
                && pos_eval - futility_margin(ply) >= beta && pos_eval < 10000 {
                return pos_eval;
            }
        }

        #[allow(unused_mut)]
        let mut moves: MoveList = if at_root {
            self.root_moves.to_list()
        } else {
            self.board.generate_pseudolegal_moves()
        };

        if moves.is_empty() {
            if self.board.in_check() {
                return MATE as i32 - (ply as i32);
            } else {
                return DRAW as i32;
            }
        }

        if !at_root {
            mvv_lva_sort(&mut moves, &self.board);
        }

        for (i, mov) in moves.iter().enumerate() {
            if at_root || self.board.legal_move(*mov) {
                moves_played += 1;
                let gives_check: bool = self.board.gives_check(*mov);
                self.board.apply_unknown_move(*mov, gives_check);
                self.tt.prefetch(self.board.zobrist());
                let do_full_depth: bool = if max_depth >= 3 && moves_played > 1 && ply >= 2 {
                    if in_check || gives_check {
                        value = -self.search::<NonPV>(-(alpha+1), -alpha, max_depth - 1);
                    } else {
                        value = -self.search::<NonPV>(-(alpha+1), -alpha, max_depth - 2);
                    }
                    value > alpha
                } else {
                    !is_pv || moves_played > 1
                };
                if do_full_depth {
                    value = -self.search::<NonPV>(-(alpha+1), -alpha, max_depth);
                }
                if is_pv && (moves_played == 1 || (value > alpha && (at_root || value < beta))) {
                    value = -self.search::<PV>(-beta, -alpha, max_depth);
                }
                self.board.undo_move();
                assert!(value > NEG_INFINITE as i32);
                assert!(value < INFINITE as i32);
                if self.stop() {
                    return 0;
                }
                if at_root {
                    if moves_played == 1 || value as i32 > alpha {
                        self.root_moves.insert_score_depth(i,value, max_depth);
                    } else {
                        self.root_moves.insert_score(i, NEG_INFINITE as i32);
                    }
                }

                if value > best_value {
                    best_value = value;

                    if value > alpha {
                        best_move = *mov;
                        if is_pv && value < beta {
                            alpha = value;
                        } else {
                            break;
                        }
                    }
                }
            }
        }

        if moves_played == 0 {
            if self.board.in_check() {
                return MATE as i32 - (ply as i32);
            } else {
                return DRAW as i32;
            }
        }

        let node_bound = if best_value as i32 >= beta {NodeBound::LowerBound}
            else if is_pv && !best_move.is_null() {NodeBound::Exact}
                else {NodeBound::UpperBound};

        tt_entry.place(zob, best_move, best_value as i16, pos_eval as i16, plys_to_zero as u8, node_bound);

        best_value
    }

    // TODO: Qscience search

    fn main_thread(&self) -> bool {
        self.id == 0
    }

    fn stop(&self) -> bool {
        self.root_moves.load_stop()
    }

    fn check_time(&mut self) {
        if self.limit.use_time_management().is_some()
            && TIMER.elapsed() >= TIMER.maximum_time() {
            self.root_moves.set_stop(true);
        } else if let Some(time) = self.limit.use_movetime() {
            if self.limit.elapsed() >= time as i64 {
                self.root_moves.set_stop(true);
            }
        }
    }

    pub fn print_startup(&self) {
        if self.use_stdout() {
            println!("info id {} start", self.id);
        }
    }

    pub fn use_stdout(&self) -> bool {
        self.use_stdout.load(Ordering::Relaxed)
    }

}

impl Drop for Searcher {
    fn drop(&mut self) {
        self.root_moves.set_finished(true);
    }
}

fn mvv_lva_sort(moves: &mut MoveList, board: &Board) {
    moves.sort_by_key(|a| {
        let piece = board.piece_at_sq((*a).get_src()).unwrap();

        if a.is_capture() {
            piece.value() - board.captured_piece(*a).unwrap().value()
        } else if a.is_castle() {
            1
        } else if piece == PieceType::P {
            if a.is_double_push().0 {
                2
            } else {
                3
            }
        } else {
            4
        }
    })
}

fn correct_bound_eq(tt_value: i32, beta: i32, bound: NodeBound) -> bool {
    if tt_value as i32 >= beta {
        bound as u8 & NodeBound::LowerBound as u8 != 0
    } else {
        bound as u8 & NodeBound::UpperBound as u8 != 0
    }
}

fn correct_bound(tt_value: i32, val: i32, bound: NodeBound) -> bool {
    if tt_value as i32 > val {
        bound as u8 & NodeBound::LowerBound as u8 != 0
    } else {
        bound as u8 & NodeBound::UpperBound as u8 != 0
    }
}

fn futility_margin(depth: u16) -> i32 {
    depth as i32 * 150
}<commit_msg>forgot a file :3<commit_after>//! The main searching function.

pub mod eval;

use std::cmp::{min,max};
use std::sync::atomic::{Ordering,AtomicBool};

use pleco::{MoveList,Board,BitMove};
use pleco::core::*;
use pleco::tools::tt::*;
use pleco::core::score::*;
use pleco::tools::pleco_arc::Arc;

use MAX_PLY;
use TT_TABLE;

use time::time_management::TimeManager;
use time::uci_timer::*;
use threadpool::TIMER;
use root_moves::root_moves_list::RootMoveList;
use tables::material::Material;
use tables::pawn_table::PawnTable;
use consts::*;

const THREAD_DIST: usize = 20;

//                                      1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20
static SKIP_SIZE: [u16; THREAD_DIST] = [1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4];
static START_PLY: [u16; THREAD_DIST] = [0, 1, 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 7];

pub struct Searcher {
    pub limit: Limits,
    pub board: Board,
    pub time_man: &'static TimeManager,
    pub tt: &'static TranspositionTable,
    pub pawns: PawnTable,
    pub material: Material,
    pub id: usize,
    pub root_moves: RootMoveList,
    pub use_stdout: Arc<AtomicBool>,
}

impl Searcher {
    pub fn search_root(&mut self) {
        assert!(self.board.depth() == 0);
        self.root_moves.set_finished(false);
        if self.stop() {
            self.root_moves.set_finished(true);
            return;
        }

        if self.use_stdout() {
            println!("info id {} start", self.id);
        }

//        if self.main_thread() {
//            println!("info max_time: {}, ideal time: {}", self.time_man.maximum_time(), self.time_man.ideal_time());
//        }

        let max_depth = if let LimitsType::Depth(d) = self.limit.limits_type {
            d
        } else {
            MAX_PLY
        };

        let start_ply: u16 = START_PLY[self.id % THREAD_DIST];
        let skip_size: u16 = SKIP_SIZE[self.id % THREAD_DIST];
        let mut depth: u16 = start_ply;

        let mut delta: i32 = NEG_INFINITE as i32;
        #[allow(unused_assignments)]
        let mut best_value: i32 = NEG_INFINITE as i32;
        let mut alpha: i32 = NEG_INFINITE as i32;
        let mut beta: i32 = INFINITE as i32;

        let mut time_reduction: f64 = 1.0;
        let mut last_best_move: BitMove = BitMove::null();
        let mut best_move_stability: u32 = 0;

        self.root_moves.shuffle(self.id, &self.board);

        'iterative_deepening: while !self.stop() && depth < max_depth {
            self.root_moves.rollback();

            if depth >= 5 {
                delta = 18;
                alpha = max(self.root_moves.prev_best_score() - delta, NEG_INFINITE as i32);
                beta = min(self.root_moves.prev_best_score() + delta, INFINITE as i32);
            }

            'aspiration_window: loop {

                best_value = self.search::<PV>(alpha, beta, depth) as i32;
                self.root_moves.sort();

                if self.stop() {
                    break 'aspiration_window;
                }

                if best_value <= alpha {
                    alpha = max(best_value - delta, NEG_INFINITE as i32);
                } else if best_value >= beta {
                    beta = min(best_value + delta, INFINITE as i32);
                } else {
                    break 'aspiration_window;
                }
                delta += (delta / 4) + 5;

                assert!(alpha >= NEG_INFINITE as i32);
                assert!(beta <= INFINITE as i32);
            }

            self.root_moves.sort();
            if self.use_stdout() && self.main_thread() {
                println!("info depth {}", depth);
            }
            if !self.stop() {
                self.root_moves.set_depth_completed(depth);
            }
            depth += skip_size;

            if !self.main_thread() {
                continue;
            }

            let best_move = self.root_moves.first().bit_move;
            if best_move != last_best_move {
                time_reduction = 1.0;
                best_move_stability = 0;
            } else {
                time_reduction *= 0.91;
                best_move_stability += 1;
            }

            last_best_move = best_move;

            // check for time
            if let Some(_) = self.limit.use_time_management() {
                if !self.stop() {
                    let ideal = TIMER.ideal_time();
                    let elapsed = TIMER.elapsed();
                    let stability: f64 = f64::powi(0.92, best_move_stability as i32);
                    let new_ideal = (ideal as f64 * stability * time_reduction) as i64;
                    println!("ideal: {}, new_ideal: {}, elapsed: {}", ideal, new_ideal, elapsed);
                    if self.root_moves.len() == 1 || TIMER.elapsed() >= new_ideal {
                        break 'iterative_deepening;
                    }
                }
            }

        }
        self.root_moves.set_finished(true);
    }

    fn search<N: PVNode>(&mut self, mut alpha: i32, beta: i32, max_depth: u16) -> i32 {
        let is_pv: bool = N::is_pv();
        let at_root: bool = self.board.depth() == 0;
        let zob = self.board.zobrist();
        let (tt_hit, tt_entry): (bool, &mut Entry) = TT_TABLE.probe(zob);
        let tt_value = if tt_hit {tt_entry.score as i32} else {0};
        let in_check: bool = self.board.in_check();
        let ply = self.board.depth();

        let mut best_move = BitMove::null();

        let mut value = NEG_INFINITE as i32;
        let mut best_value = NEG_INFINITE as i32;
        let mut moves_played = 0;

        let mut pos_eval: i32 = 0;

        if self.main_thread() {
            self.check_time();
        }

        if ply >= max_depth || self.stop() {
            return self.eval();
        }

        let plys_to_zero = max_depth - ply;

        if !at_root {
            if alpha >= beta {
                return alpha
            }
        }

        if !is_pv
            && tt_hit
            && tt_entry.depth as u16 >= plys_to_zero
            && tt_value != 0
            && correct_bound_eq(tt_value, beta, tt_entry.node_type()) {
            return tt_value;
        }

        if in_check {
            pos_eval = 0;
        } else {
            if tt_hit {
                if tt_entry.eval == 0 {
                    pos_eval = self.eval();
                }
                if tt_value != 0 && correct_bound(tt_value, pos_eval, tt_entry.node_type()) {
                    pos_eval = tt_value;
                }
            } else {
                pos_eval = self.eval();
                tt_entry.place(zob, BitMove::null(), 0, pos_eval as i16, 0, NodeBound::NoBound);
            }
        }

        if !in_check {

            if ply > 3
                && ply < 7
                && pos_eval - futility_margin(ply) >= beta && pos_eval < 10000 {
                return pos_eval;
            }
        }

        #[allow(unused_mut)]
        let mut moves: MoveList = if at_root {
            self.root_moves.to_list()
        } else {
            self.board.generate_pseudolegal_moves()
        };

        if moves.is_empty() {
            if self.board.in_check() {
                return MATE as i32 - (ply as i32);
            } else {
                return DRAW as i32;
            }
        }

        if !at_root {
            mvv_lva_sort(&mut moves, &self.board);
        }

        for (i, mov) in moves.iter().enumerate() {
            if at_root || self.board.legal_move(*mov) {
                moves_played += 1;
                let gives_check: bool = self.board.gives_check(*mov);
                self.board.apply_unknown_move(*mov, gives_check);
                self.tt.prefetch(self.board.zobrist());
                let do_full_depth: bool = if max_depth >= 3 && moves_played > 1 && ply >= 2 {
                    if in_check || gives_check {
                        value = -self.search::<NonPV>(-(alpha+1), -alpha, max_depth - 1);
                    } else {
                        value = -self.search::<NonPV>(-(alpha+1), -alpha, max_depth - 2);
                    }
                    value > alpha
                } else {
                    !is_pv || moves_played > 1
                };
                if do_full_depth {
                    value = -self.search::<NonPV>(-(alpha+1), -alpha, max_depth);
                }
                if is_pv && (moves_played == 1 || (value > alpha && (at_root || value < beta))) {
                    value = -self.search::<PV>(-beta, -alpha, max_depth);
                }
                self.board.undo_move();
                assert!(value > NEG_INFINITE as i32);
                assert!(value < INFINITE as i32);
                if self.stop() {
                    return 0;
                }
                if at_root {
                    if moves_played == 1 || value as i32 > alpha {
                        self.root_moves.insert_score_depth(i,value, max_depth);
                    } else {
                        self.root_moves.insert_score(i, NEG_INFINITE as i32);
                    }
                }

                if value > best_value {
                    best_value = value;

                    if value > alpha {
                        best_move = *mov;
                        if is_pv && value < beta {
                            alpha = value;
                        } else {
                            break;
                        }
                    }
                }
            }
        }

        if moves_played == 0 {
            if self.board.in_check() {
                return MATE as i32 - (ply as i32);
            } else {
                return DRAW as i32;
            }
        }

        let node_bound = if best_value as i32 >= beta {NodeBound::LowerBound}
            else if is_pv && !best_move.is_null() {NodeBound::Exact}
                else {NodeBound::UpperBound};

        tt_entry.place(zob, best_move, best_value as i16, pos_eval as i16, plys_to_zero as u8, node_bound);

        best_value
    }

    // TODO: Qscience search

    pub fn eval(&mut self) -> Value {
        let pawns = &mut self.pawns;
        let material = &mut self.material;
        eval::Evaluation::evaluate(&self.board, pawns, material)
    }

    fn main_thread(&self) -> bool {
        self.id == 0
    }

    fn stop(&self) -> bool {
        self.root_moves.load_stop()
    }

    fn check_time(&mut self) {
        if self.limit.use_time_management().is_some()
            && TIMER.elapsed() >= TIMER.maximum_time() {
            self.root_moves.set_stop(true);
        } else if let Some(time) = self.limit.use_movetime() {
            if self.limit.elapsed() >= time as i64 {
                self.root_moves.set_stop(true);
            }
        }
    }

    pub fn print_startup(&self) {
        if self.use_stdout() {
            println!("info id {} start", self.id);
        }
    }

    pub fn use_stdout(&self) -> bool {
        self.use_stdout.load(Ordering::Relaxed)
    }

}

impl Drop for Searcher {
    fn drop(&mut self) {
        self.root_moves.set_finished(true);
    }
}

fn mvv_lva_sort(moves: &mut MoveList, board: &Board) {
    moves.sort_by_key(|a| {
        let piece = board.piece_at_sq((*a).get_src()).unwrap();

        if a.is_capture() {
            piece.value() - board.captured_piece(*a).unwrap().value()
        } else if a.is_castle() {
            1
        } else if piece == PieceType::P {
            if a.is_double_push().0 {
                2
            } else {
                3
            }
        } else {
            4
        }
    })
}

fn correct_bound_eq(tt_value: i32, beta: i32, bound: NodeBound) -> bool {
    if tt_value as i32 >= beta {
        bound as u8 & NodeBound::LowerBound as u8 != 0
    } else {
        bound as u8 & NodeBound::UpperBound as u8 != 0
    }
}

fn correct_bound(tt_value: i32, val: i32, bound: NodeBound) -> bool {
    if tt_value as i32 > val {
        bound as u8 & NodeBound::LowerBound as u8 != 0
    } else {
        bound as u8 & NodeBound::UpperBound as u8 != 0
    }
}

fn futility_margin(depth: u16) -> i32 {
    depth as i32 * 150
}<|endoftext|>


<commit_before>use doubly_linked_list::*;

#[test]
fn is_generic() {
    struct Foo;
    LinkedList::<Foo>::new();
}

// ———————————————————————————————————————————————————————————
// Tests for Step 1: push / pop at front and back
// ———————————————————————————————————————————————————————————

#[test]
#[ignore]
fn basics_empty_list() {
    let list: LinkedList<i32> = LinkedList::new();
    assert_eq!(list.len(), 0);
}

// push / pop at back ————————————————————————————————————————
#[test]
#[ignore]
fn basics_single_element_back() {
    let mut list: LinkedList<i32> = LinkedList::new();
    list.push_back(5);

    assert_eq!(list.len(), 1);
    assert_eq!(list.pop_back(), Some(5));
}

#[test]
#[ignore]
fn basics_push_pop_at_back() {
    let mut list: LinkedList<i32> = LinkedList::new();
    for i in 0..10 {
        list.push_back(i);
    }
    assert_eq!(list.len(), 10);

    for i in (0..10).rev() {
        assert_eq!(i, list.pop_back().unwrap());
    }
    assert_eq!(list.len(), 0);
}

// push / pop at front ———————————————————————————————————————
#[test]
#[ignore]
fn basics_single_element_front() {
    let mut list: LinkedList<i32> = LinkedList::new();
    list.push_front(5);

    assert_eq!(list.len(), 1);
    assert_eq!(list.pop_front(), Some(5));
}

#[test]
#[ignore]
fn basics_push_pop_at_front() {
    let mut list: LinkedList<i32> = LinkedList::new();
    for i in 0..10 {
        list.push_front(i);
    }
    assert_eq!(list.len(), 10);

    for i in (0..10).rev() {
        assert_eq!(i, list.pop_front().unwrap());
    }
    assert_eq!(list.len(), 0);
}

// push / pop at mixed sides —————————————————————————————————
#[test]
#[ignore]
fn basics_push_front_pop_back() {
    let mut list: LinkedList<i32> = LinkedList::new();
    for i in 0..10 {
        list.push_front(i);
    }
    for i in 0..10 {
        assert_eq!(i, list.pop_back().unwrap());
    }
}

#[test]
#[ignore]
fn basics_push_back_pop_front() {
    let mut list: LinkedList<i32> = LinkedList::new();
    for i in 0..10 {
        list.push_back(i);
    }
    for i in 0..10 {
        assert_eq!(i, list.pop_front().unwrap());
    }
}

// ———————————————————————————————————————————————————————————
// Tests for Step 2: iteration
// ———————————————————————————————————————————————————————————

#[test]
#[ignore]
fn iter() {
    let mut list: LinkedList<i32> = LinkedList::new();
    for num in 0..10 {
        list.push_back(num);
    }

    for (num, &entered_num) in (0..10).zip(list.iter()) {
        assert_eq!(num, entered_num);
    }
}

// ———————————————————————————————————————————————————————————
// Tests for Step 3: full cursor functionality
// ———————————————————————————————————————————————————————————

#[test]
#[ignore]
fn cursor_insert_before_on_empty_list() {
    // insert_after on empty list is already tested via push_back()
    let mut list = LinkedList::new();
    list.cursor_front().insert_before(0);
    assert_eq!(Some(0), list.pop_front());
}

#[test]
#[ignore]
fn cursor_insert_after_in_middle() {
    let mut list = (0..10).collect::<LinkedList<_>>();

    {
        let mut cursor = list.cursor_front();
        let didnt_run_into_end = cursor.seek_forward(4);
        assert!(didnt_run_into_end);

        for n in (0..10).rev() {
            cursor.insert_after(n);
        }
    }

    assert_eq!(list.len(), 20);

    let expected = (0..5).chain(0..10).chain(5..10);

    assert!(expected.eq(list.iter().cloned()));
}

#[test]
#[ignore]
fn cursor_insert_before_in_middle() {
    let mut list = (0..10).collect::<LinkedList<_>>();

    {
        let mut cursor = list.cursor_back();
        let didnt_run_into_end = cursor.seek_backward(4);
        assert!(didnt_run_into_end);

        for n in 0..10 {
            cursor.insert_before(n);
        }
    }

    assert_eq!(list.len(), 20);

    let expected = (0..5).chain(0..10).chain(5..10);

    assert!(expected.eq(list.iter().cloned()));
}

// "iterates" via next() and checks that it visits the right elements
#[test]
#[ignore]
fn cursor_next_and_peek() {
    let mut list = (0..10).collect::<LinkedList<_>>();
    let mut cursor = list.cursor_front();

    assert_eq!(cursor.peek_mut(), Some(&mut 0));

    for n in 1..10 {
        let next = cursor.next().cloned();
        assert_eq!(next, Some(n));
        assert_eq!(next, cursor.peek_mut().cloned());
    }
}

// "iterates" via prev() and checks that it visits the right elements
#[test]
#[ignore]
fn cursor_prev_and_peek() {
    let mut list = (0..10).collect::<LinkedList<_>>();
    let mut cursor = list.cursor_back();

    assert_eq!(cursor.peek_mut(), Some(&mut 9));

    for n in (0..9).rev() {
        let prev = cursor.prev().cloned();
        assert_eq!(prev, Some(n));
        assert_eq!(prev, cursor.peek_mut().cloned());
    }
}

// removes all elements starting from the middle
#[test]
#[ignore]
fn cursor_take() {
    let mut list = (0..10).collect::<LinkedList<_>>();
    let mut cursor = list.cursor_front();
    cursor.seek_forward(5);

    for expected in (5..10).chain((0..5).rev()) {
        assert_eq!(cursor.take(), Some(expected));
    }
}

// ———————————————————————————————————————————————————————————
// Tests for Step 4: clean-up via `Drop`
// ———————————————————————————————————————————————————————————

// The leak tests that are also for this step are separated into
// their own files so that nothing else interferes with the allocator
// whilst they run

// checks number of drops
// may pass for incorrect programs if double frees happen
// exactly as often as destructor leaks
#[test]
#[ignore]
fn drop_no_double_frees() {
    use std::cell::Cell;
    struct DropCounter<'a>(&'a Cell<usize>);

    impl<'a> Drop for DropCounter<'a> {
        fn drop(&mut self) {
            let num = self.0.get();
            self.0.set(num + 1);
        }
    }

    const N: usize = 15;

    let counter = Cell::new(0);
    let list = std::iter::repeat_with(|| DropCounter(&counter))
        .take(N)
        .collect::<LinkedList<_>>();

    assert_eq!(list.len(), N);
    drop(list);
    assert_eq!(counter.get(), N);
}

// ———————————————————————————————————————————————————————————
// Tests for Step 5 (advanced): covariance and Send/Sync
// ———————————————————————————————————————————————————————————

// These are compile time tests. They won't compile unless your
// code passes.
// Additional tests for code that must *not* compile are in
// pre_implemented.rs for technical reasons.

#[cfg(feature = "advanced")]
#[test]
#[ignore]
fn advanced_linked_list_is_send_sync() {
    trait AssertSend: Send {}
    trait AssertSync: Sync {}

    impl<T: Send> AssertSend for LinkedList<T> {}
    impl<T: Sync> AssertSync for LinkedList<T> {}
}

#[cfg(feature = "advanced")]
#[allow(dead_code)]
#[test]
#[ignore]
fn advanced_is_covariant() {
    fn a<'a>(x: LinkedList<&'static str>) -> LinkedList<&'a str> {
        x
    }

    fn a_iter<'a>(i: Iter<'static, &'static str>) -> Iter<'a, &'a str> {
        i
    }
}
<commit_msg>doubly-linked-list: test drops of large lists<commit_after>use doubly_linked_list::*;

#[test]
fn is_generic() {
    struct Foo;
    LinkedList::<Foo>::new();
}

// ———————————————————————————————————————————————————————————
// Tests for Step 1: push / pop at front and back
// ———————————————————————————————————————————————————————————

#[test]
#[ignore]
fn basics_empty_list() {
    let list: LinkedList<i32> = LinkedList::new();
    assert_eq!(list.len(), 0);
}

// push / pop at back ————————————————————————————————————————
#[test]
#[ignore]
fn basics_single_element_back() {
    let mut list: LinkedList<i32> = LinkedList::new();
    list.push_back(5);

    assert_eq!(list.len(), 1);
    assert_eq!(list.pop_back(), Some(5));
}

#[test]
#[ignore]
fn basics_push_pop_at_back() {
    let mut list: LinkedList<i32> = LinkedList::new();
    for i in 0..10 {
        list.push_back(i);
    }
    assert_eq!(list.len(), 10);

    for i in (0..10).rev() {
        assert_eq!(i, list.pop_back().unwrap());
    }
    assert_eq!(list.len(), 0);
}

// push / pop at front ———————————————————————————————————————
#[test]
#[ignore]
fn basics_single_element_front() {
    let mut list: LinkedList<i32> = LinkedList::new();
    list.push_front(5);

    assert_eq!(list.len(), 1);
    assert_eq!(list.pop_front(), Some(5));
}

#[test]
#[ignore]
fn basics_push_pop_at_front() {
    let mut list: LinkedList<i32> = LinkedList::new();
    for i in 0..10 {
        list.push_front(i);
    }
    assert_eq!(list.len(), 10);

    for i in (0..10).rev() {
        assert_eq!(i, list.pop_front().unwrap());
    }
    assert_eq!(list.len(), 0);
}

// push / pop at mixed sides —————————————————————————————————
#[test]
#[ignore]
fn basics_push_front_pop_back() {
    let mut list: LinkedList<i32> = LinkedList::new();
    for i in 0..10 {
        list.push_front(i);
    }
    for i in 0..10 {
        assert_eq!(i, list.pop_back().unwrap());
    }
}

#[test]
#[ignore]
fn basics_push_back_pop_front() {
    let mut list: LinkedList<i32> = LinkedList::new();
    for i in 0..10 {
        list.push_back(i);
    }
    for i in 0..10 {
        assert_eq!(i, list.pop_front().unwrap());
    }
}

// ———————————————————————————————————————————————————————————
// Tests for Step 2: iteration
// ———————————————————————————————————————————————————————————

#[test]
#[ignore]
fn iter() {
    let mut list: LinkedList<i32> = LinkedList::new();
    for num in 0..10 {
        list.push_back(num);
    }

    for (num, &entered_num) in (0..10).zip(list.iter()) {
        assert_eq!(num, entered_num);
    }
}

// ———————————————————————————————————————————————————————————
// Tests for Step 3: full cursor functionality
// ———————————————————————————————————————————————————————————

#[test]
#[ignore]
fn cursor_insert_before_on_empty_list() {
    // insert_after on empty list is already tested via push_back()
    let mut list = LinkedList::new();
    list.cursor_front().insert_before(0);
    assert_eq!(Some(0), list.pop_front());
}

#[test]
#[ignore]
fn cursor_insert_after_in_middle() {
    let mut list = (0..10).collect::<LinkedList<_>>();

    {
        let mut cursor = list.cursor_front();
        let didnt_run_into_end = cursor.seek_forward(4);
        assert!(didnt_run_into_end);

        for n in (0..10).rev() {
            cursor.insert_after(n);
        }
    }

    assert_eq!(list.len(), 20);

    let expected = (0..5).chain(0..10).chain(5..10);

    assert!(expected.eq(list.iter().cloned()));
}

#[test]
#[ignore]
fn cursor_insert_before_in_middle() {
    let mut list = (0..10).collect::<LinkedList<_>>();

    {
        let mut cursor = list.cursor_back();
        let didnt_run_into_end = cursor.seek_backward(4);
        assert!(didnt_run_into_end);

        for n in 0..10 {
            cursor.insert_before(n);
        }
    }

    assert_eq!(list.len(), 20);

    let expected = (0..5).chain(0..10).chain(5..10);

    assert!(expected.eq(list.iter().cloned()));
}

// "iterates" via next() and checks that it visits the right elements
#[test]
#[ignore]
fn cursor_next_and_peek() {
    let mut list = (0..10).collect::<LinkedList<_>>();
    let mut cursor = list.cursor_front();

    assert_eq!(cursor.peek_mut(), Some(&mut 0));

    for n in 1..10 {
        let next = cursor.next().cloned();
        assert_eq!(next, Some(n));
        assert_eq!(next, cursor.peek_mut().cloned());
    }
}

// "iterates" via prev() and checks that it visits the right elements
#[test]
#[ignore]
fn cursor_prev_and_peek() {
    let mut list = (0..10).collect::<LinkedList<_>>();
    let mut cursor = list.cursor_back();

    assert_eq!(cursor.peek_mut(), Some(&mut 9));

    for n in (0..9).rev() {
        let prev = cursor.prev().cloned();
        assert_eq!(prev, Some(n));
        assert_eq!(prev, cursor.peek_mut().cloned());
    }
}

// removes all elements starting from the middle
#[test]
#[ignore]
fn cursor_take() {
    let mut list = (0..10).collect::<LinkedList<_>>();
    let mut cursor = list.cursor_front();
    cursor.seek_forward(5);

    for expected in (5..10).chain((0..5).rev()) {
        assert_eq!(cursor.take(), Some(expected));
    }
}

// ———————————————————————————————————————————————————————————
// Tests for Step 4: clean-up via `Drop`
// ———————————————————————————————————————————————————————————

// The leak tests that are also for this step are separated into
// their own files so that nothing else interferes with the allocator
// whilst they run

// checks number of drops
// may pass for incorrect programs if double frees happen
// exactly as often as destructor leaks
#[test]
#[ignore]
fn drop_no_double_frees() {
    use std::cell::Cell;
    struct DropCounter<'a>(&'a Cell<usize>);

    impl<'a> Drop for DropCounter<'a> {
        fn drop(&mut self) {
            let num = self.0.get();
            self.0.set(num + 1);
        }
    }

    const N: usize = 15;

    let counter = Cell::new(0);
    let list = std::iter::repeat_with(|| DropCounter(&counter))
        .take(N)
        .collect::<LinkedList<_>>();

    assert_eq!(list.len(), N);
    drop(list);
    assert_eq!(counter.get(), N);
}

#[test]
#[ignore]
fn drop_large_list() {
    drop((0..2_000_000).collect::<LinkedList<i32>>());
}

// ———————————————————————————————————————————————————————————
// Tests for Step 5 (advanced): covariance and Send/Sync
// ———————————————————————————————————————————————————————————

// These are compile time tests. They won't compile unless your
// code passes.
// Additional tests for code that must *not* compile are in
// pre_implemented.rs for technical reasons.

#[cfg(feature = "advanced")]
#[test]
#[ignore]
fn advanced_linked_list_is_send_sync() {
    trait AssertSend: Send {}
    trait AssertSync: Sync {}

    impl<T: Send> AssertSend for LinkedList<T> {}
    impl<T: Sync> AssertSync for LinkedList<T> {}
}

#[cfg(feature = "advanced")]
#[allow(dead_code)]
#[test]
#[ignore]
fn advanced_is_covariant() {
    fn a<'a>(x: LinkedList<&'static str>) -> LinkedList<&'a str> {
        x
    }

    fn a_iter<'a>(i: Iter<'static, &'static str>) -> Iter<'a, &'a str> {
        i
    }
}
<|endoftext|>


<commit_before>// This integration test suite relies heavily on the string
// representation of the Violations struct, as that's the final
// user-visible output that the zakocmp binary presents.
extern crate indoc;

use indoc::indoc;
use libzakocmp::config::Config;
use libzakocmp::snapshot::snapshot_string_for_testing;
use libzakocmp::snapshot::Snapshot;

#[test]
fn test_basic_default_immutability() {
    let config: Config = Config::new(r#"default-policy: immutable"#).unwrap();

    // Verifies that empty snapshots never turn up violations.
    let empty_older = Snapshot::new(&snapshot_string_for_testing("")).unwrap();
    let empty_newer = Snapshot::new(&snapshot_string_for_testing("")).unwrap();
    let empty_violations = libzakocmp::enter(&config, &empty_older, &empty_newer);
    assert_eq!(empty_violations.to_string(), "");

    // Verifies that disjoint snapshots also violate this policy.
    let disjoint_older = Snapshot::new(&snapshot_string_for_testing(
        "0000000000000000000000000000000000000000000000000000000000000000  ./a/b/c",
    ))
    .unwrap();
    let disjoint_newer = Snapshot::new(&snapshot_string_for_testing(
        "0000000000000000000000000000000000000000000000000000000000000000  ./x/y/z",
    ))
    .unwrap();
    let disjoint_violations = libzakocmp::enter(&config, &disjoint_older, &disjoint_newer);
    // From zakocmp's point of view, ``./a/b/c'' was deleted and
    // ``./x/y/z'' was added.
    assert_eq!(
        disjoint_violations.to_string(),
        indoc!(
            r#"
            - ./a/b/c
            + ./x/y/z
            "#
        )
    );

    // Verifies that zakocmp catches the two changed files interspersed
    // with others that did not.
    let shifty_older = Snapshot::new(&snapshot_string_for_testing(indoc!(
        r#"
        0000000000000000000000000000000000000000000000000000000000000000  ./a/b/changed
        0000000000000000000000000000000000000000000000000000000000000000  ./c/d/unchanged
        0000000000000000000000000000000000000000000000000000000000000000  ./e/f/unchanged
        0000000000000000000000000000000000000000000000000000000000000000  ./g/h/unchanged
        0000000000000000000000000000000000000000000000000000000000000000  ./i/j/changed
        0000000000000000000000000000000000000000000000000000000000000000  ./k/l/unchanged
        "#
    )))
    .unwrap();
    let shifty_newer = Snapshot::new(&snapshot_string_for_testing(indoc!(
        r#"
        ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff  ./a/b/changed
        0000000000000000000000000000000000000000000000000000000000000000  ./c/d/unchanged
        0000000000000000000000000000000000000000000000000000000000000000  ./e/f/unchanged
        0000000000000000000000000000000000000000000000000000000000000000  ./g/h/unchanged
        ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff  ./i/j/changed
        0000000000000000000000000000000000000000000000000000000000000000  ./k/l/unchanged
        "#
    )))
    .unwrap();
    let shifty_violations = libzakocmp::enter(&config, &shifty_older, &shifty_newer);
    assert_eq!(
        shifty_violations.to_string(),
        indoc!(
            r#"
            ! ./a/b/changed
            ! ./i/j/changed
            "#
        )
    );
}
<commit_msg>zakocmp: augment integration tests<commit_after>// This integration test suite relies heavily on the string
// representation of the Violations struct, as that's the final
// user-visible output that the zakocmp binary presents.
extern crate indoc;

use indoc::indoc;
use libzakocmp::config::Config;
use libzakocmp::snapshot::snapshot_string_for_testing;
use libzakocmp::snapshot::Snapshot;

#[test]
fn test_basic_default_immutability() {
    let config: Config = Config::new(r#"default-policy: immutable"#).unwrap();

    // Verifies that empty snapshots never turn up violations.
    let empty_older = Snapshot::new(&snapshot_string_for_testing("")).unwrap();
    let empty_newer = Snapshot::new(&snapshot_string_for_testing("")).unwrap();
    let empty_violations = libzakocmp::enter(&config, &empty_older, &empty_newer);
    assert_eq!(empty_violations.to_string(), "");

    // Verifies that disjoint snapshots also violate this policy.
    let disjoint_older = Snapshot::new(&snapshot_string_for_testing(
        "0000000000000000000000000000000000000000000000000000000000000000  ./a/b/c",
    ))
    .unwrap();
    let disjoint_newer = Snapshot::new(&snapshot_string_for_testing(
        "0000000000000000000000000000000000000000000000000000000000000000  ./x/y/z",
    ))
    .unwrap();
    let disjoint_violations = libzakocmp::enter(&config, &disjoint_older, &disjoint_newer);
    // From zakocmp's point of view, ``./a/b/c'' was deleted and
    // ``./x/y/z'' was added.
    assert_eq!(
        disjoint_violations.to_string(),
        indoc!(
            r#"
            - ./a/b/c
            + ./x/y/z
            "#
        )
    );

    // Verifies that zakocmp catches the two changed files interspersed
    // with others that did not.
    let shifty_older = Snapshot::new(&snapshot_string_for_testing(indoc!(
        r#"
        0000000000000000000000000000000000000000000000000000000000000000  ./a/b/changed
        0000000000000000000000000000000000000000000000000000000000000000  ./c/d/unchanged
        0000000000000000000000000000000000000000000000000000000000000000  ./e/f/unchanged
        0000000000000000000000000000000000000000000000000000000000000000  ./g/h/unchanged
        0000000000000000000000000000000000000000000000000000000000000000  ./i/j/changed
        0000000000000000000000000000000000000000000000000000000000000000  ./k/l/unchanged
        "#
    )))
    .unwrap();
    let shifty_newer = Snapshot::new(&snapshot_string_for_testing(indoc!(
        r#"
        ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff  ./a/b/changed
        0000000000000000000000000000000000000000000000000000000000000000  ./c/d/unchanged
        0000000000000000000000000000000000000000000000000000000000000000  ./e/f/unchanged
        0000000000000000000000000000000000000000000000000000000000000000  ./g/h/unchanged
        ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff  ./i/j/changed
        0000000000000000000000000000000000000000000000000000000000000000  ./k/l/unchanged
        "#
    )))
    .unwrap();
    let shifty_violations = libzakocmp::enter(&config, &shifty_older, &shifty_newer);
    assert_eq!(
        shifty_violations.to_string(),
        indoc!(
            r#"
            ! ./a/b/changed
            ! ./i/j/changed
            "#
        )
    );

    // As an additional sanity check, verifies that zakocmp snapshots
    // are not sensitive to the ordering of input files.
    let shifty_newer_shuffled = Snapshot::new(&snapshot_string_for_testing(indoc!(
        r#"
        0000000000000000000000000000000000000000000000000000000000000000  ./e/f/unchanged
        ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff  ./i/j/changed
        0000000000000000000000000000000000000000000000000000000000000000  ./k/l/unchanged
        ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff  ./a/b/changed
        0000000000000000000000000000000000000000000000000000000000000000  ./g/h/unchanged
        0000000000000000000000000000000000000000000000000000000000000000  ./c/d/unchanged
        "#
    )))
    .unwrap();
    let the_same_shifty_violations =
        libzakocmp::enter(&config, &shifty_older, &shifty_newer_shuffled);
    assert_eq!(
        shifty_violations.to_string(),
        the_same_shifty_violations.to_string()
    );
}
<|endoftext|>


<commit_before>#![warn(rust_2018_idioms)]
#![cfg(feature = "full")]

use tokio::fs;

#[tokio::test]
async fn copy() {
    fs::write("foo.txt", b"Hello File!").await.unwrap();
    fs::copy("foo.txt", "bar.txt").await.unwrap();

    let from = fs::read("foo.txt").await.unwrap();
    let to = fs::read("bar.txt").await.unwrap();

    assert_eq!(from, to);
}
<commit_msg>Add foo.txt and bar.txt to .gitignore (#2294)<commit_after>#![warn(rust_2018_idioms)]
#![cfg(feature = "full")]

use tempfile::tempdir;
use tokio::fs;

#[tokio::test]
async fn copy() {
    let dir = tempdir().unwrap();

    let source_path = dir.path().join("foo.txt");
    let dest_path = dir.path().join("bar.txt");

    fs::write(&source_path, b"Hello File!").await.unwrap();
    fs::copy(&source_path, &dest_path).await.unwrap();

    let from = fs::read(&source_path).await.unwrap();
    let to = fs::read(&dest_path).await.unwrap();

    assert_eq!(from, to);
}
<|endoftext|>