<commit_before>// Internal Dependencies ------------------------------------------------------
use ::command::{Command, CommandHandler};
use ::action::{ActionGroup, MessageActions};

// Statics --------------------------------------------------------------------
static HELP_TEXT_ONE: &'static str = "
**The following commands are currently available:**

- `!s <sound>` - Plays the requested sound immediately. See below for details on what `<sound>` can be.
- `!q <sound>` - Queues up the requested sound to be played once all other currently playing / queued sounds have finished.
- `!sounds` [<pattern>, ...] - Show a list of all available sound effects (matching the specified patterns) in a private channel.
- `!delete <effect>` - Deletes the specified sound effect.
- `!rename <old_effect_name> <new_effect_name>` - Renames the specified sound effect.
- `!silence` - Immediately stops all playing sounds and removes all other queued effects.
- `!greeting <add|remove> <user#ident> [<effect_name>]` - Adds or remove a custom greeting for a user.
- `!greetings` - Show a list of all existing custom user greetings in a private channel.
- `!alias <add|remove> <name> [<effect_name>, ...]` - Adds or remove a effect alias.
- `!aliases` - Show a list of all existing effect aliases in a private channel.
- `!bans` - Show a list of all banned users for the current server.
- `!ban <add|remove> <user#ident>` - Add or remove user bans.
- `!leave` - Makes the bot leave its current voice channel.
- `!pin` - Pins the bot to its current voice channel.
- `!ip` - Posts the bot's the public IP onto the current channel.
- `!reload` - Reloads the sound list from the on disk flac files.
- `!help` - Displays this help text.
";

static HELP_TEXT_TWO: &'static str = "**Sound Effects**

Sound effects can be played by requesting them via the `!s <sound>` command,
where `<sound>` can either be the *full name*, a *group prefix*, or a *wildcard*.

- `full name` - Simply check the `!sounds` listing.
- `group prefix` - This is the part of a sound name before the first `_`  character (e.g. `siw` is the *group prefix* for `siw_kaffee` etc.), requesting a *group prefix* will select a random sound from the group.
- `wildcards` - These are either `*` for any random sound or `*word*` for any sound effect which contains the specified word part.

**Effect File Uploads**

Sound effects can be directly uploaded by whitelisted users via the default discord file upload feature.

The filename must be at least 3 characters long, the file extension must be `flac` and the file itself must be a valid flac file encoded at 48khz and 16bit with at most 2 MiB.

Also, a effect with the same name may not yet exist.";

// Command Implementation -----------------------------------------------------
pub struct Handler;

impl CommandHandler for Handler {

    delete_command_message!();

    fn run(&self, command: Command) -> ActionGroup {
        vec![
            MessageActions::Send::single_private(
                &command.message, HELP_TEXT_ONE.to_string()
            ),
            MessageActions::Send::single_private(
                &command.message, HELP_TEXT_TWO.to_string()
            )
        ]
    }

}

<commit_msg>Update help text with uploaders commands.<commit_after>// Internal Dependencies ------------------------------------------------------
use ::command::{Command, CommandHandler};
use ::action::{ActionGroup, MessageActions};

// Statics --------------------------------------------------------------------
static HELP_TEXT_ONE: &'static str = "
**The following commands are currently available:**

- `!s <sound>` - Plays the requested sound immediately. See below for details on what `<sound>` can be.
- `!q <sound>` - Queues up the requested sound to be played once all other currently playing / queued sounds have finished.
- `!sounds` [<pattern>, ...] - Show a list of all available sound effects (matching the specified patterns) in a private channel.
- `!delete <effect>` - Deletes the specified sound effect.
- `!rename <old_effect_name> <new_effect_name>` - Renames the specified sound effect.
- `!silence` - Immediately stops all playing sounds and removes all other queued effects.
- `!greeting <add|remove> <user#ident> [<effect_name>]` - Adds or remove a custom greeting for a user.
- `!greetings` - Show a list of all existing custom user greetings in a private channel.
- `!uploader <add|remove> <user#ident> [<effect_name>]` - Adds or remove a user from the list of uploaders for the current server.
- `!uploaders` - Show a list of all users which are whitelisted to upload sound effects to the current server.
- `!alias <add|remove> <name> [<effect_name>, ...]` - Adds or remove a effect alias.
- `!aliases` - Show a list of all existing effect aliases in a private channel.
- `!bans` - Show a list of all banned users for the current server.
- `!ban <add|remove> <user#ident>` - Add or remove user bans.
- `!leave` - Makes the bot leave its current voice channel.
- `!pin` - Pins the bot to its current voice channel.
- `!ip` - Posts the bot's the public IP onto the current channel.
- `!reload` - Reloads the sound list from the on disk flac files.
- `!help` - Displays this help text.
";

static HELP_TEXT_TWO: &'static str = "**Sound Effects**

Sound effects can be played by requesting them via the `!s <sound>` command,
where `<sound>` can either be the *full name*, a *group prefix*, or a *wildcard*.

- `full name` - Simply check the `!sounds` listing.
- `group prefix` - This is the part of a sound name before the first `_`  character (e.g. `siw` is the *group prefix* for `siw_kaffee` etc.), requesting a *group prefix* will select a random sound from the group.
- `wildcards` - These are either `*` for any random sound or `*word*` for any sound effect which contains the specified word part.

**Effect File Uploads**

Sound effects can be directly uploaded by whitelisted users via the default discord file upload feature.

The filename must be at least 3 characters long, the file extension must be `flac` and the file itself must be a valid flac file encoded at 48khz and 16bit with at most 2 MiB.

Also, a effect with the same name may not yet exist.";

// Command Implementation -----------------------------------------------------
pub struct Handler;

impl CommandHandler for Handler {

    delete_command_message!();

    fn run(&self, command: Command) -> ActionGroup {
        vec![
            MessageActions::Send::single_private(
                &command.message, HELP_TEXT_ONE.to_string()
            ),
            MessageActions::Send::single_private(
                &command.message, HELP_TEXT_TWO.to_string()
            )
        ]
    }

}

<|endoftext|>


<commit_before>fn main() {
    println!("Hello, world!");
}
<commit_msg>Example of infinite loop<commit_after>fn main() {
    loop {
        println!("Hello, world!");
    }
}
<|endoftext|>


<commit_before>use super::super::cursive::Cursive;
use super::super::cursive::align;
use super::super::cursive::views::{Dialog, Layer, LinearLayout, ListView, Panel, TextView};
use super::super::cursive::traits::*; //{Identifiable,select};

use std::iter::Iterator;
use std::thread;
use std::sync::mpsc;
use std::time::Duration;

use ctrl::{Alive, ReceiveDialog, Status, SystemMsg, UiMsg};

pub struct Tui {
    pub ui_receiver: mpsc::Receiver<UiMsg>,
    pub ui_sender: mpsc::Sender<UiMsg>,
    pub system_sender: mpsc::Sender<SystemMsg>,

    handle: Cursive,
    alive: AliveDisplayData,
}

#[derive(Clone)]
struct AliveState {
    draw_char: usize,
    runs: bool,
}

struct AliveDisplayData {
    host: AliveState,
    pathes: Vec<AliveState>,
}

// following the advice for Rust clever enums
// to say what you mean, even if boolean
enum AliveSym {
    GoOn,
    Stop,
}

static RECT: usize = 40;
static SEPERATOR: &str = "..";
static STR_ALIVE: [char; 5] = ['*', '|', '/', '-', '\\']; // first char is start and not animated

static DEBUG_TEXT_ID: &str = "debug_info";
static VIEW_LIST_HOST: &str = "hostlist";

static PATHS_PREFIX_ID: &str = "pf";

static ID_HOST_INDEX: &str = "id_host";
static ID_HOST_NUMBER: &str = "id_max";
static ID_HOST_ALIVE: &str = "id_host_alive";

static TIMEOUT_SEARCH: u64 = 80;

impl Tui {
    pub fn new(
        title: String,
        system: mpsc::Sender<SystemMsg>,
        pathes: &Vec<String>,
        with_net: bool,
    ) -> Result<Tui, String> {
        let later_handle = Self::build_tui(title, pathes, with_net)?;

        // now build the actual TUI object
        let (tui_sender, tui_receiver) = mpsc::channel::<UiMsg>();
        let mut tui = Tui {
            handle: later_handle,
            ui_sender: tui_sender,
            ui_receiver: tui_receiver,
            system_sender: system,
            alive: AliveDisplayData {
                host: AliveState {
                    draw_char: 0,
                    runs: false,
                },
                pathes: vec![
                    AliveState {
                        draw_char: 0,
                        runs: false,
                    };
                    pathes.len()
                ],
            },
        };

        // test this, to update every with 20fps / this should be done when something changes ..... grrrr
        tui.handle.set_fps(20);
        // quit by 'q' key
        tui.handle.add_global_callback('q', |s| s.quit());
        Ok(tui)
    }

    pub fn step(&mut self) -> bool {
        if !self.handle.is_running() {
            return false;
        }

        while let Some(message) = self.ui_receiver.try_iter().next() {
            match message {
                UiMsg::Update(recv_dialog, text) => {
                    match recv_dialog {
                        ReceiveDialog::ShowNewPath { nr } => {
                            if let Some(mut _found) = self.handle
                                .find_id::<TextView>(&format!("{}{}", PATHS_PREFIX_ID, nr))
                            {
                                //found.append_content(&text.clone());
                            }
                        }
                        ReceiveDialog::ShowNewHost => {
                            if let Some(mut host_list) =
                                self.handle.find_id::<ListView>(VIEW_LIST_HOST)
                            {
                                error!("{:?} should be drawn!!", text);
                                host_list.add_child("", TextView::new(format!("{}", text)));
                            } else {
                                error!("{:?} could not be drawn!!", text);
                            }
                        }
                        ReceiveDialog::ShowStats { show } => {
                            let output = self.handle.find_id::<TextView>(ID_HOST_INDEX);
                            if let Some(mut found) = output {
                                found.set_content(show.line.to_string());
                            }
                        }
                        ReceiveDialog::Debug => {
                            if let Some(mut found) = self.handle.find_id::<TextView>(DEBUG_TEXT_ID)
                            {
                                found.set_content(text);
                            }
                        }
                    }
                }
                UiMsg::Animate(signal, on_off) => {
                    let sender_clone = self.ui_sender.clone();
                    match on_off {
                        Status::ON => {
                            self.toggle_alive(signal.clone(), AliveSym::GoOn);
                            let (already_running, toggle): (
                                bool,
                                &mut bool,
                            ) = match signal {
                                Alive::BusyPath(nr) => {
                                    (self.alive.pathes[nr].runs, &mut self.alive.pathes[nr].runs)
                                }
                                Alive::HostSearch => {
                                    (self.alive.host.runs, &mut self.alive.host.runs)
                                }
                            };
                            if !already_running {
                                *toggle = true;
                                // if timer not started, start
                                thread::spawn(move || {
                                    thread::sleep(Duration::from_millis(TIMEOUT_SEARCH));
                                    sender_clone.send(UiMsg::TimeOut(signal.clone())).unwrap();
                                });
                            }
                        }
                        Status::OFF => {
                            self.toggle_alive(signal.clone(), AliveSym::Stop);
                            let toggle: &mut bool = match signal {
                                Alive::BusyPath(nr) => &mut self.alive.pathes[nr].runs,
                                Alive::HostSearch => &mut self.alive.host.runs,
                            };
                            *toggle = false;
                        }
                    }
                }
                UiMsg::TimeOut(which) => {
                    let continue_timeout = match which {
                        Alive::BusyPath(nr) => self.alive.pathes[nr].runs,
                        Alive::HostSearch => self.alive.host.runs,
                    };
                    if continue_timeout {
                        self.toggle_alive(which.clone(), AliveSym::GoOn);
                        let sender_clone = self.ui_sender.clone();
                        thread::spawn(move || {
                            thread::sleep(Duration::from_millis(TIMEOUT_SEARCH));
                            sender_clone.send(UiMsg::TimeOut(which)).unwrap();
                        });
                    }
                }
            }
        }
        // step through the TUI
        self.handle.step();
        true
    }

    ///    # Example test
    ///  ```
    ///  use adfblib::ctrl::tui::Tui;
    ///
    ///  let boundary = 15;
    ///  let input_vec: Vec<String> =
    ///     vec!["The duck went swimming.".into(),
    ///         "A cool hat does not fit you.".into()];
    ///  let expected_output: Vec<String> =
    ///     vec!["The duc..mming.".into(), "A cool ..t you.".into()];
    ///  let output = adfblib::ctrl::tui::Tui::split_intelligently_ralign(&input_vec, boundary);
    ///  ```
    // todo: not public... only due to testing
    pub fn split_intelligently_ralign(vec: &Vec<String>, max_len: usize) -> Vec<String> {
        let mut out: Vec<String> = Vec::new();
        for el in vec {
            let real_len = el.chars().count();
            // simple case
            if real_len < max_len {
                out.push(format!(
                    "{}{}",
                    " ".repeat(max_len - real_len),
                    el.to_string()
                ));
            } else if real_len < max_len / 2 {
                out.push(el.chars().skip(real_len - max_len).collect::<String>());
            } else {
                let diff = max_len - SEPERATOR.chars().count();
                let real_middle = diff / 2;
                let offset = if real_middle * 2 < diff { 1 } else { 0 };

                let first_part = el.chars().take(real_middle + offset).collect::<String>();
                let last_part = el.chars().skip(real_len - real_middle).collect::<String>();

                out.push(format!("{}{}{}", first_part, SEPERATOR, last_part));
            }
        }
        out
    }

    fn toggle_alive(&mut self, signal: Alive, on: AliveSym) {
        let (view_name, counter) = match signal {
            Alive::HostSearch => (ID_HOST_ALIVE.to_string(), &mut self.alive.host.draw_char),
            Alive::BusyPath(nr) => (
                format!("{}{}", PATHS_PREFIX_ID, nr),
                &mut self.alive.pathes[nr].draw_char,
            ),
        };
        let mut output = self.handle.find_id::<TextView>(&view_name);
        if let Some(ref mut found) = output {
            let char_idx_to_put = match on {
                AliveSym::GoOn => {
                    *counter = (*counter + 1) % (STR_ALIVE.len() - 1);
                    *counter + 1
                }
                AliveSym::Stop => {
                    0 // the stop symbol's index
                }
            };
            let out = STR_ALIVE[char_idx_to_put];
            found.set_content(out.to_string());
        }
    }

    fn build_tui(title: String, pathes: &Vec<String>, with_net: bool) -> Result<Cursive, String> {
        let later_handle = Cursive::new();

        let screen_size = later_handle.screen_size();

        let max_cols = screen_size.x / RECT;

        if max_cols == 0 {
            return Err("Not able to build tui.".to_string());
        }
        let rows = pathes.len() / max_cols + 1;

        // 80% of rect size
        let max_table_width = RECT * 5 / 6;

        let mut vertical_layout = LinearLayout::vertical();

        // add host list on top
        if with_net {
            vertical_layout.add_child(
                Dialog::around(
                    LinearLayout::vertical()
                        .child(ListView::new().fixed_height(10).with_id(VIEW_LIST_HOST))
                        .child(
                            LinearLayout::horizontal()
                                .child(
                                    TextView::new(format!("{}", STR_ALIVE[0]))
                                        .with_id(ID_HOST_ALIVE),
                                )
                                .child(TextView::new(format!(" Looking at ")))
                                .child(
                                    TextView::new(format!("{}", 0))
                                        .h_align(align::HAlign::Right)
                                        .with_id(ID_HOST_INDEX),
                                )
                                .child(TextView::new(format!("/")))
                                .child(
                                    TextView::new(format!("{}", 0))
                                        .h_align(align::HAlign::Left)
                                        .with_id(ID_HOST_NUMBER),
                                ),
                        ),
                ).title("Host list"),
            );
        }

        // draw all pathes
        for j in 0..rows {
            let mut horizontal_layout = LinearLayout::horizontal();

            let cols = if j < rows - 1 {
                max_cols
            } else {
                pathes.len() % max_cols
            };

            for i in 0..cols {
                let my_number = j * max_cols + i; // j >= 1

                let differentiate_path = Self::split_intelligently_ralign(pathes, max_table_width);
                let path_name = format!("{}{}", PATHS_PREFIX_ID, my_number);

                horizontal_layout.add_child(Panel::new(
                    LinearLayout::horizontal()
                        .child(
                            // and link with an id
                            TextView::new(format!("{}", STR_ALIVE[0])).with_id(path_name),
                        )
                        .child(
                            TextView::new(format!("{}", differentiate_path[my_number]))
                                .fixed_height(3),
                        ),
                ));
            }
            vertical_layout.add_child(horizontal_layout);
        }

        // debug output here
        let debug_text = max_table_width;
        vertical_layout.add_child(Panel::new(
            TextView::new(format!("debug_text: {}", debug_text)).with_id(DEBUG_TEXT_ID),
        ));

        // return the dialog
        let layer =
            Dialog::around(Layer::new(vertical_layout)).title(format!("This uuid: {}", title));
        let mut later_handle = later_handle;
        later_handle.add_layer(layer);
        Ok(later_handle)
    }
} // impl Tui

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

    //run "cargo test -- --nocapture" to see debug println
    fn equal_with_boundary(input: &Vec<String>, expected: &Vec<String>, boundary: usize) -> bool {
        let output = Tui::split_intelligently_ralign(&input, boundary);
        println!("|{:?}|", output);
        println!("|{:?}|", expected);
        output.len() == expected.len() && output.iter().zip(expected.iter()).all(|(e, o)| e == o)
    }

    #[test]
    fn split_intelligently_ralign_exists() {
        let boundary = 20;
        let input_vec: Vec<String> = Vec::new();
        let _ = Tui::split_intelligently_ralign(&input_vec, boundary);
        assert!(true);
    }

    #[test]
    fn split_intelligently_ralign_alignment() {
        let boundary = 15;
        let input_vec: Vec<String> = vec!["duck".into(), "monkey".into()];
        let expected_output: Vec<String> = vec!["           duck".into(), "         monkey".into()];
        assert!(equal_with_boundary(&input_vec, &expected_output, boundary));
    }

    #[test]
    fn split_intelligently_ralign_fail() {
        let boundary = 20;
        let input_vec: Vec<String> = vec!["duck".into(), "monkey".into()];
        let expected_output: Vec<String> = vec!["duckkk".into(), "monkeyyyy".into()];
        assert!(!equal_with_boundary(&input_vec, &expected_output, boundary));
    }

    #[test]
    fn split_intelligently_ralign_shorten() {
        let boundary = 15;
        let input_vec: Vec<String> = vec![
            "The duck went swimming.".into(),
            "A cool hat does not fit you.".into(),
        ];
        let expected_output: Vec<String> = vec!["The duc..mming.".into(), "A cool ..t you.".into()];
        assert!(equal_with_boundary(&input_vec, &expected_output, boundary));
    }

    #[test]
    fn split_intelligently_ralign_utf8_russian() {
        let boundary = 15;
        let input_vec: Vec<String> = vec![
            "Герман Гессе родился в семье немецких".into(),
            "Его мать Мария Гундерт (1842—1902) была".into(),
        ];
        let expected_output: Vec<String> = vec![
            "Герман ..мецких".into(),
            "Его мат..) была".into(),
        ];
        assert!(equal_with_boundary(&input_vec, &expected_output, boundary));
    }

}
<commit_msg>Added fixed size thread-pool for free timers.<commit_after>use super::super::cursive::Cursive;
use super::super::cursive::align;
use super::super::cursive::views::{Dialog, Layer, LinearLayout, ListView, Panel, TextView};
use super::super::cursive::traits::*; //{Identifiable,select};

use std::iter::Iterator;
use std::thread;
use std::sync::mpsc;
use std::time::Duration;

use ctrl::{Alive, ReceiveDialog, Status, SystemMsg, UiMsg};
use common::ThreadPool;
use config;

pub struct Tui {
    pub ui_receiver: mpsc::Receiver<UiMsg>,
    pub ui_sender: mpsc::Sender<UiMsg>,
    pub system_sender: mpsc::Sender<SystemMsg>,

    handle: Cursive,
    alive: AliveDisplayData,
}

#[derive(Clone)]
struct AliveState {
    draw_char: usize,
    runs: bool,
}

struct AliveDisplayData {
    host: AliveState,
    pathes: Vec<AliveState>,
    timers: ThreadPool,
}

// following the advice for Rust clever enums
// to say what you mean, even if boolean
enum AliveSym {
    GoOn,
    Stop,
}

static RECT: usize = 40;
static SEPERATOR: &str = "..";
static STR_ALIVE: [char; 5] = ['*', '|', '/', '-', '\\']; // first char is start and not animated

static DEBUG_TEXT_ID: &str = "debug_info";
static VIEW_LIST_HOST: &str = "hostlist";

static PATHS_PREFIX_ID: &str = "pf";

static ID_HOST_INDEX: &str = "id_host";
static ID_HOST_NUMBER: &str = "id_max";
static ID_HOST_ALIVE: &str = "id_host_alive";

impl Tui {
    pub fn new(
        title: String,
        system: mpsc::Sender<SystemMsg>,
        pathes: &Vec<String>,
        with_net: bool,
    ) -> Result<Tui, String> {
        let later_handle = Self::build_tui(title, pathes, with_net)?;

        // now build the actual TUI object
        let (tui_sender, tui_receiver) = mpsc::channel::<UiMsg>();
        let mut tui = Tui {
            handle: later_handle,
            ui_sender: tui_sender,
            ui_receiver: tui_receiver,
            system_sender: system,
            alive: AliveDisplayData {
                host: AliveState {
                    draw_char: 0,
                    runs: false,
                },
                pathes: vec![
                    AliveState {
                        draw_char: 0,
                        runs: false,
                    };
                    pathes.len()
                ],
                timers: ThreadPool::new(pathes.len() + 1),
            },
        };

        // test this, to update every with 20fps / this should be done when something changes ..... grrrr
        tui.handle.set_fps(20);
        // quit by 'q' key
        tui.handle.add_global_callback('q', |s| s.quit());
        Ok(tui)
    }

    pub fn step(&mut self) -> bool {
        if !self.handle.is_running() {
            return false;
        }

        while let Some(message) = self.ui_receiver.try_iter().next() {
            match message {
                UiMsg::Update(recv_dialog, text) => {
                    match recv_dialog {
                        ReceiveDialog::ShowNewPath { nr } => {
                            if let Some(mut _found) = self.handle
                                .find_id::<TextView>(&format!("{}{}", PATHS_PREFIX_ID, nr))
                            {
                                //found.append_content(&text.clone());
                            }
                        }
                        ReceiveDialog::ShowNewHost => {
                            if let Some(mut host_list) =
                                self.handle.find_id::<ListView>(VIEW_LIST_HOST)
                            {
                                error!("{:?} should be drawn!!", text);
                                host_list.add_child("", TextView::new(format!("{}", text)));
                            } else {
                                error!("{:?} could not be drawn!!", text);
                            }
                        }
                        ReceiveDialog::ShowStats { show } => {
                            let output = self.handle.find_id::<TextView>(ID_HOST_INDEX);
                            if let Some(mut found) = output {
                                found.set_content(show.line.to_string());
                            }
                        }
                        ReceiveDialog::Debug => {
                            if let Some(mut found) = self.handle.find_id::<TextView>(DEBUG_TEXT_ID)
                            {
                                found.set_content(text);
                            }
                        }
                    }
                }
                UiMsg::Animate(signal, on_off) => {
                    let sender_clone = self.ui_sender.clone();
                    match on_off {
                        Status::ON => {
                            self.toggle_alive(signal.clone(), AliveSym::GoOn);
                            let (already_running, toggle,timeout_id): (
                                bool,
                                &mut bool,
                                usize
                            ) = match signal {
                                Alive::BusyPath(nr) => {
                                    (self.alive.pathes[nr].runs, &mut self.alive.pathes[nr].runs,nr+1)
                                }
                                Alive::HostSearch => {
                                    (self.alive.host.runs, &mut self.alive.host.runs,0)
                                }
                            };
                            if !already_running {
                                *toggle = true;
                                // if timer not started, start
                                self.alive.timers.renew(timeout_id, move || {
                                    thread::sleep(Duration::from_millis(
                                        config::tui::ALIVE_REFRESH,
                                    ));
                                    sender_clone.send(UiMsg::TimeOut(signal.clone())).unwrap();
                                });
                            }
                        }
                        Status::OFF => {
                            self.toggle_alive(signal.clone(), AliveSym::Stop);
                            let toggle: &mut bool = match signal {
                                Alive::BusyPath(nr) => &mut self.alive.pathes[nr].runs,
                                Alive::HostSearch => &mut self.alive.host.runs,
                            };
                            *toggle = false;
                        }
                    }
                }
                UiMsg::TimeOut(which) => {
                    let (continue_timeout, timeout_id) = match which {
                        Alive::BusyPath(nr) => (self.alive.pathes[nr].runs, nr + 1),
                        Alive::HostSearch => (self.alive.host.runs, 0),
                    };
                    if continue_timeout {
                        self.toggle_alive(which.clone(), AliveSym::GoOn);
                        let sender_clone = self.ui_sender.clone();
                        self.alive.timers.renew(timeout_id, move || {
                            thread::sleep(Duration::from_millis(config::tui::ALIVE_REFRESH));
                            sender_clone.send(UiMsg::TimeOut(which)).unwrap();
                        });
                    }
                }
            }
        }
        // step through the TUI
        self.handle.step();
        true
    }

    ///    # Example test
    ///  ```
    ///  use adfblib::ctrl::tui::Tui;
    ///
    ///  let boundary = 15;
    ///  let input_vec: Vec<String> =
    ///     vec!["The duck went swimming.".into(),
    ///         "A cool hat does not fit you.".into()];
    ///  let expected_output: Vec<String> =
    ///     vec!["The duc..mming.".into(), "A cool ..t you.".into()];
    ///  let output = adfblib::ctrl::tui::Tui::split_intelligently_ralign(&input_vec, boundary);
    ///  ```
    // todo: not public... only due to testing
    pub fn split_intelligently_ralign(vec: &Vec<String>, max_len: usize) -> Vec<String> {
        let mut out: Vec<String> = Vec::new();
        for el in vec {
            let real_len = el.chars().count();
            // simple case
            if real_len < max_len {
                out.push(format!(
                    "{}{}",
                    " ".repeat(max_len - real_len),
                    el.to_string()
                ));
            } else if real_len < max_len / 2 {
                out.push(el.chars().skip(real_len - max_len).collect::<String>());
            } else {
                let diff = max_len - SEPERATOR.chars().count();
                let real_middle = diff / 2;
                let offset = if real_middle * 2 < diff { 1 } else { 0 };

                let first_part = el.chars().take(real_middle + offset).collect::<String>();
                let last_part = el.chars().skip(real_len - real_middle).collect::<String>();

                out.push(format!("{}{}{}", first_part, SEPERATOR, last_part));
            }
        }
        out
    }

    fn toggle_alive(&mut self, signal: Alive, on: AliveSym) {
        let (view_name, counter) = match signal {
            Alive::HostSearch => (ID_HOST_ALIVE.to_string(), &mut self.alive.host.draw_char),
            Alive::BusyPath(nr) => (
                format!("{}{}", PATHS_PREFIX_ID, nr),
                &mut self.alive.pathes[nr].draw_char,
            ),
        };
        let mut output = self.handle.find_id::<TextView>(&view_name);
        if let Some(ref mut found) = output {
            let char_idx_to_put = match on {
                AliveSym::GoOn => {
                    *counter = (*counter + 1) % (STR_ALIVE.len() - 1);
                    *counter + 1
                }
                AliveSym::Stop => {
                    0 // the stop symbol's index
                }
            };
            let out = STR_ALIVE[char_idx_to_put];
            found.set_content(out.to_string());
        }
    }

    fn build_tui(title: String, pathes: &Vec<String>, with_net: bool) -> Result<Cursive, String> {
        let later_handle = Cursive::new();

        let screen_size = later_handle.screen_size();

        let max_cols = screen_size.x / RECT;

        if max_cols == 0 {
            return Err("Not able to build tui.".to_string());
        }
        let rows = pathes.len() / max_cols + 1;

        // 80% of rect size
        let max_table_width = RECT * 5 / 6;

        let mut vertical_layout = LinearLayout::vertical();

        // add host list on top
        if with_net {
            vertical_layout.add_child(
                Dialog::around(
                    LinearLayout::vertical()
                        .child(ListView::new().fixed_height(10).with_id(VIEW_LIST_HOST))
                        .child(
                            LinearLayout::horizontal()
                                .child(
                                    TextView::new(format!("{}", STR_ALIVE[0]))
                                        .with_id(ID_HOST_ALIVE),
                                )
                                .child(TextView::new(format!(" Looking at ")))
                                .child(
                                    TextView::new(format!("{}", 0))
                                        .h_align(align::HAlign::Right)
                                        .with_id(ID_HOST_INDEX),
                                )
                                .child(TextView::new(format!("/")))
                                .child(
                                    TextView::new(format!("{}", 0))
                                        .h_align(align::HAlign::Left)
                                        .with_id(ID_HOST_NUMBER),
                                ),
                        ),
                ).title("Host list"),
            );
        }

        // draw all pathes
        for j in 0..rows {
            let mut horizontal_layout = LinearLayout::horizontal();

            let cols = if j < rows - 1 {
                max_cols
            } else {
                pathes.len() % max_cols
            };

            for i in 0..cols {
                let my_number = j * max_cols + i; // j >= 1

                let differentiate_path = Self::split_intelligently_ralign(pathes, max_table_width);
                let path_name = format!("{}{}", PATHS_PREFIX_ID, my_number);

                horizontal_layout.add_child(Panel::new(
                    LinearLayout::horizontal()
                        .child(
                            // and link with an id
                            TextView::new(format!("{}", STR_ALIVE[0])).with_id(path_name),
                        )
                        .child(
                            TextView::new(format!("{}", differentiate_path[my_number]))
                                .fixed_height(3),
                        ),
                ));
            }
            vertical_layout.add_child(horizontal_layout);
        }

        // debug output here
        let debug_text = max_table_width;
        vertical_layout.add_child(Panel::new(
            TextView::new(format!("debug_text: {}", debug_text)).with_id(DEBUG_TEXT_ID),
        ));

        // return the dialog
        let layer =
            Dialog::around(Layer::new(vertical_layout)).title(format!("This uuid: {}", title));
        let mut later_handle = later_handle;
        later_handle.add_layer(layer);
        Ok(later_handle)
    }
} // impl Tui

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

    //run "cargo test -- --nocapture" to see debug println
    fn equal_with_boundary(input: &Vec<String>, expected: &Vec<String>, boundary: usize) -> bool {
        let output = Tui::split_intelligently_ralign(&input, boundary);
        println!("|{:?}|", output);
        println!("|{:?}|", expected);
        output.len() == expected.len() && output.iter().zip(expected.iter()).all(|(e, o)| e == o)
    }

    #[test]
    fn split_intelligently_ralign_exists() {
        let boundary = 20;
        let input_vec: Vec<String> = Vec::new();
        let _ = Tui::split_intelligently_ralign(&input_vec, boundary);
        assert!(true);
    }

    #[test]
    fn split_intelligently_ralign_alignment() {
        let boundary = 15;
        let input_vec: Vec<String> = vec!["duck".into(), "monkey".into()];
        let expected_output: Vec<String> = vec!["           duck".into(), "         monkey".into()];
        assert!(equal_with_boundary(&input_vec, &expected_output, boundary));
    }

    #[test]
    fn split_intelligently_ralign_fail() {
        let boundary = 20;
        let input_vec: Vec<String> = vec!["duck".into(), "monkey".into()];
        let expected_output: Vec<String> = vec!["duckkk".into(), "monkeyyyy".into()];
        assert!(!equal_with_boundary(&input_vec, &expected_output, boundary));
    }

    #[test]
    fn split_intelligently_ralign_shorten() {
        let boundary = 15;
        let input_vec: Vec<String> = vec![
            "The duck went swimming.".into(),
            "A cool hat does not fit you.".into(),
        ];
        let expected_output: Vec<String> = vec!["The duc..mming.".into(), "A cool ..t you.".into()];
        assert!(equal_with_boundary(&input_vec, &expected_output, boundary));
    }

    #[test]
    fn split_intelligently_ralign_utf8_russian() {
        let boundary = 15;
        let input_vec: Vec<String> = vec![
            "Герман Гессе родился в семье немецких".into(),
            "Его мать Мария Гундерт (1842—1902) была".into(),
        ];
        let expected_output: Vec<String> = vec![
            "Герман ..мецких".into(),
            "Его мат..) была".into(),
        ];
        assert!(equal_with_boundary(&input_vec, &expected_output, boundary));
    }

}
<|endoftext|>


<commit_before>//
// zhtta.rs
//
// Running on Rust 0.8
//
// Starting code for PS3
//
// Note: it would be very unwise to run this server on a machine that is
// on the Internet and contains any sensitive files!
//
// University of Virginia - cs4414 Fall 2013
// Weilin Xu and David Evans
// Version 0.3

extern mod extra;

use std::rt::io::*;
use std::rt::io::net::ip::{SocketAddr, Ipv4Addr};
use std::io::println;
use std::cell::Cell;
use std::{os, str, io, run, vec, hashmap, task};
use extra::arc;
use extra::priority_queue::PriorityQueue;
use std::comm::*;
use std::cast;
use extra::comm;
use extra::comm::DuplexStream;
use std::hashmap::HashSet;
use std::path::Path;
use extra::ringbuf::RingBuf;
use extra::sort;

static PORT:    int = 4414;
static IP: &'static str = "127.0.0.1";
static mut visitor_count: uint = 0;

struct access_t {
    filepath: std::path::PosixPath,
    size: uint,
    num_access: uint
}

struct sched_msg {
    stream: Option<std::rt::io::net::tcp::TcpStream>,
    filepath: ~std::path::PosixPath,
    priority: uint
}

impl std::cmp::Ord for sched_msg {
    fn lt(&self, other: &sched_msg) -> bool {
        return self.priority > other.priority;
    }
}

fn main() {
    let req_heap: PriorityQueue<sched_msg> = PriorityQueue::new();
    let shared_req_heap = arc::RWArc::new(req_heap);
    let add_vec = shared_req_heap.clone();
    let take_vec = shared_req_heap.clone();

    let cache: hashmap::HashMap<~str, ~str> = hashmap::HashMap::new();
    let shared_cache = arc::RWArc::new(cache);
    let add_cache = shared_cache.clone();
    let check_cache = shared_cache.clone();

    let accesses: ~[access_t] = ~[];
    let shared_accesses = arc::RWArc::new(accesses);
    let exist_accesses = shared_accesses.clone();
    let new_accesses = shared_accesses.clone();

    let (port, chan) = stream();
    let chan = SharedChan::new(chan);

    // dequeue file requests, and send responses.
    // FIFO
    do spawn {
        let (sm_port, sm_chan) = stream();

        // a task for sending responses.
        do spawn {
            loop {
                let mut tf: sched_msg = sm_port.recv(); // wait for the dequeued request to handle
                println(fmt!("begin serving file [%?]", tf.filepath.to_str()));
                // A web server should always reply a HTTP header for any legal HTTP request.
                tf.stream.write("HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n".as_bytes());
                match do check_cache.read |cc| { (*cc).find_copy(&tf.filepath.to_str()) } {
                    None        =>  { let data = writeFile(tf);
                                      do new_accesses.write |na| {
                                            if (*na).len() > 20 {
                                                (*na).pop_opt();
                                            }

                                            let file_info = access_t{filepath: tf.filepath, size: tf.filepath.stat().st_size, num_access: 1};
                                            (*na).push(file_info);
                                            do add_cache.write |ac| {
                                                let sort_access = sort::merge_sort(na, |it1, it2| { (it1.size*it1.num_access) <= (it2.size*it2.num_access)});
                                                for item in sort_access.iter() {
                                                    if file_info.size > (item.size*item.num_access) {
                                                        match (*ac).pop(&item.filepath.to_str()) {
                                                            Some    =>  { (*ac).swap(file_info.filepath.to_str(), data); break;}
                                                            None    =>  { }
                                                        }
                                                    }
                                                }
                                            }
                                      }
                                  }

                    Some(ct)    =>  { tf.stream.write(ct.as_bytes());
                                      do exist_accesses.write |ea| {
                                            for i in range((*ea).len()) {
                                                let mut item = (*ea)[i];
                                                if item.filepath == tf.filepath {
                                                    item.num_access += 1;
                                                    item = item.clone();
                                                    (*ea).remove(i);
                                                    (*ea).push(item);
                                                    break;
                                                }
                                            }
                                        }
                                    }
                }
                println(fmt!("finish file [%?]", tf.filepath.to_str()));
            }
        }

        loop {
            port.recv(); // wait for arrving notification
            do take_vec.write |vec| {
                if ((*vec).len() > 0) {
                    // LIFO didn't make sense in service scheduling, so we modify it as FIFO by using shift_opt() rather than pop().
                    let tf: sched_msg = (*vec).pop();
                    println(fmt!("shift from queue, size: %ud", (*vec).len()));
                    sm_chan.send(tf); // send the request to send-response-task to serve.
                }
            }
        }
    }

    // IP addresses to give higher priority
    let mut ip_vals: HashSet<u32> = HashSet::with_capacity(9000);
    ip_vals.insert((192 as u32 << 24) + (168 as u32 << 16));
    ip_vals.insert((127 as u32 << 24) + (143 as u32 << 16));
    ip_vals.insert((137 as u32 << 24) + (54 as u32 << 16));
    ip_vals.insert(0);
    let shared_ip_map = arc::RWArc::new(ip_vals);

    let shared_count = arc::RWArc::new(0);

    let ip = match FromStr::from_str(IP) { Some(ip) => ip,
                                           None => { println(fmt!("Error: Invalid IP address <%s>", IP));
                                                     return;},
                                         };

    let socket = net::tcp::TcpListener::bind(SocketAddr {ip: ip, port: PORT as u16});

    println(fmt!("Listening on %s:%d ...", ip.to_str(), PORT));
    let mut acceptor = socket.listen().unwrap();

    for stream in acceptor.incoming() {
        let stream = Cell::new(stream);

        let incr_count = shared_count.clone();

        // Start a new task to handle the each connection
        let child_chan = chan.clone();
        let shared_ip_map = shared_ip_map.clone();
        let child_add_vec = add_vec.clone();

        do spawn {
            do incr_count.write |count| {
                *count = *count + 1;
            }

            let mut stream = stream.take();
            let mut buf = [0, ..500];
            stream.read(buf);
            let request_str = str::from_utf8(buf);

            let req_group : ~[&str]= request_str.splitn_iter(' ', 3).collect();
            if req_group.len() > 2 {
                let path = req_group[1];
                println(fmt!("Request for path: \n%?", path));
                // More better path security!
                let unclean_path = os::getcwd().push(Path(path).to_str()).to_str();
                let mut file_path = ~os::getcwd();
                // paths are always normalized so a/b/../c becomes a/c
                if unclean_path.starts_with(file_path.to_str()) {
                    file_path = ~file_path.push(path);
                }
                if !os::path_exists(file_path) || os::path_is_dir(file_path) {
                    println(fmt!("Request received:\n%s", request_str));
                    let response: ~str = fmt!(
                        "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n
                         <doctype !html><title>Hello, Rust!</title>

Greetings, Krusty!

Visitor count: %u

                         \r\n", incr_count.read(|c| { *c }));

                    stream.write(response.as_bytes());
                }
                else {
                    // Requests scheduling

                    let mut priority = file_path.stat().unwrap().st_size as uint;
                    unsafe {
                        match stream {
                            Some(ref s) => {
                                    let stream = cast::transmute_mut(s);
                                    let pn = stream.peer_name().unwrap();
                                    println(fmt!("Peer is: %?", pn));
                                    match pn.ip {
                                        Ipv4Addr(a, b, c, d) => {
                                                                // Since we are sharing the ip_map it must be read
                                                                do shared_ip_map.read |map| {
                                                                    if check_ip(a,b,c,d, map) {
                                                                        priority = 1;
                                                                        println("local request!");
                                                                    }
                                                                }
                                                            },
                                        _                    =>  fail!()
                                    }
                            },
                            _    => fail!()
                        };
                    }
                    let msg: sched_msg = sched_msg{stream: stream, filepath: file_path.clone(), priority: priority};
                    let (sm_port, sm_chan) = std::comm::stream();
                    sm_chan.send(msg);

                    do child_add_vec.write |vec| {
                        let msg = sm_port.recv();
                        (*vec).push(msg); // enqueue new request.
                        println("add to queue");
                    }
                    child_chan.send(""); //notify the new arriving request.
                    println(fmt!("get file request: %?", file_path));
                }
            }
            println!("connection terminates")
        }
    }
}

// Looks up an ip prefix in the hashset by trying each octet
fn check_ip(a: u8, b: u8, c: u8, d: u8, map: &HashSet<u32>) -> bool {
    let mut mut_ip = a as u32 << 24;
    if map.contains(&mut_ip) {
        return true;
    }
    mut_ip += (b as u32 << 16);
    if map.contains(&mut_ip) {
        return true;
    }
    mut_ip += (c as u32 << 8);
    if map.contains(&mut_ip) {
        return true;
    }
    mut_ip += (d as u32);
    if map.contains(&mut_ip) {
        return true;
    }
    false
}

pub fn execFile(file_data: &str) -> ~str {
    let index = 0;
    let closes: ~[(uint, uint)] = file_data.matches_index_iter("\" -->").collect();
    let (port, chan) = DuplexStream();
    do task::spawn_supervised {
        do_gash(&chan)
    }

    let mut i = 0;
    let mut prev = 0;
    let mut result = ~"";
    for (begin, end) in  file_data.matches_index_iter("").collect();
    let (port, chan) = DuplexStream();
    do task::spawn_supervised {
        do_gash(&chan)
    }

    let mut i = 0;
    let mut prev = 0;
    let mut result = ~"";
    for (begin, end) in  file_data.matches_index_iter("


<commit_before>//! Flycheck provides the functionality needed to run `cargo check` or
//! another compatible command (f.x. clippy) in a background thread and provide
//! LSP diagnostics based on the output of the command.

use std::{
    fmt, io,
    process::{ChildStderr, ChildStdout, Command, Stdio},
    time::Duration,
};

use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
use paths::AbsPathBuf;
use serde::Deserialize;
use stdx::{process::streaming_output, JodChild};

pub use cargo_metadata::diagnostic::{
    Applicability, Diagnostic, DiagnosticCode, DiagnosticLevel, DiagnosticSpan,
    DiagnosticSpanMacroExpansion,
};

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FlycheckConfig {
    CargoCommand {
        command: String,
        target_triple: Option<String>,
        all_targets: bool,
        no_default_features: bool,
        all_features: bool,
        features: Vec<String>,
        extra_args: Vec<String>,
    },
    CustomCommand {
        command: String,
        args: Vec<String>,
    },
}

impl fmt::Display for FlycheckConfig {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            FlycheckConfig::CargoCommand { command, .. } => write!(f, "cargo {}", command),
            FlycheckConfig::CustomCommand { command, args } => {
                write!(f, "{} {}", command, args.join(" "))
            }
        }
    }
}

/// Flycheck wraps the shared state and communication machinery used for
/// running `cargo check` (or other compatible command) and providing
/// diagnostics based on the output.
/// The spawned thread is shut down when this struct is dropped.
#[derive(Debug)]
pub struct FlycheckHandle {
    // XXX: drop order is significant
    sender: Sender<Restart>,
    _thread: jod_thread::JoinHandle,
}

impl FlycheckHandle {
    pub fn spawn(
        id: usize,
        sender: Box<dyn Fn(Message) + Send>,
        config: FlycheckConfig,
        workspace_root: AbsPathBuf,
    ) -> FlycheckHandle {
        let actor = FlycheckActor::new(id, sender, config, workspace_root);
        let (sender, receiver) = unbounded::<Restart>();
        let thread = jod_thread::Builder::new()
            .name("Flycheck".to_owned())
            .spawn(move || actor.run(receiver))
            .expect("failed to spawn thread");
        FlycheckHandle { sender, _thread: thread }
    }

    /// Schedule a re-start of the cargo check worker.
    pub fn update(&self) {
        self.sender.send(Restart).unwrap();
    }
}

pub enum Message {
    /// Request adding a diagnostic with fixes included to a file
    AddDiagnostic { workspace_root: AbsPathBuf, diagnostic: Diagnostic },

    /// Request check progress notification to client
    Progress {
        /// Flycheck instance ID
        id: usize,
        progress: Progress,
    },
}

impl fmt::Debug for Message {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Message::AddDiagnostic { workspace_root, diagnostic } => f
                .debug_struct("AddDiagnostic")
                .field("workspace_root", workspace_root)
                .field("diagnostic_code", &diagnostic.code.as_ref().map(|it| &it.code))
                .finish(),
            Message::Progress { id, progress } => {
                f.debug_struct("Progress").field("id", id).field("progress", progress).finish()
            }
        }
    }
}

#[derive(Debug)]
pub enum Progress {
    DidStart,
    DidCheckCrate(String),
    DidFinish(io::Result<()>),
    DidCancel,
}

struct Restart;

struct FlycheckActor {
    id: usize,
    sender: Box<dyn Fn(Message) + Send>,
    config: FlycheckConfig,
    workspace_root: AbsPathBuf,
    /// CargoHandle exists to wrap around the communication needed to be able to
    /// run `cargo check` without blocking. Currently the Rust standard library
    /// doesn't provide a way to read sub-process output without blocking, so we
    /// have to wrap sub-processes output handling in a thread and pass messages
    /// back over a channel.
    cargo_handle: Option<CargoHandle>,
}

enum Event {
    Restart(Restart),
    CheckEvent(Option<CargoMessage>),
}

impl FlycheckActor {
    fn new(
        id: usize,
        sender: Box<dyn Fn(Message) + Send>,
        config: FlycheckConfig,
        workspace_root: AbsPathBuf,
    ) -> FlycheckActor {
        FlycheckActor { id, sender, config, workspace_root, cargo_handle: None }
    }
    fn progress(&self, progress: Progress) {
        self.send(Message::Progress { id: self.id, progress });
    }
    fn next_event(&self, inbox: &Receiver<Restart>) -> Option<Event> {
        let check_chan = self.cargo_handle.as_ref().map(|cargo| &cargo.receiver);
        select! {
            recv(inbox) -> msg => msg.ok().map(Event::Restart),
            recv(check_chan.unwrap_or(&never())) -> msg => Some(Event::CheckEvent(msg.ok())),
        }
    }
    fn run(mut self, inbox: Receiver<Restart>) {
        while let Some(event) = self.next_event(&inbox) {
            match event {
                Event::Restart(Restart) => {
                    if let Some(cargo_handle) = self.cargo_handle.take() {
                        // Cancel the previously spawned process
                        cargo_handle.cancel();
                    }
                    while let Ok(Restart) = inbox.recv_timeout(Duration::from_millis(50)) {}
                    self.progress(Progress::DidCancel);

                    let command = self.check_command();
                    tracing::debug!(?command, "will restart flycheck");
                    match CargoHandle::spawn(command) {
                        Ok(cargo_handle) => {
                            tracing::debug!(
                                command = ?self.check_command(),
                                "did  restart flycheck"
                            );
                            self.cargo_handle = Some(cargo_handle);
                            self.progress(Progress::DidStart);
                        }
                        Err(error) => {
                            tracing::error!(
                                command = ?self.check_command(),
                                %error, "failed to restart flycheck"
                            );
                        }
                    }
                }
                Event::CheckEvent(None) => {
                    tracing::debug!("flycheck finished");

                    // Watcher finished
                    let cargo_handle = self.cargo_handle.take().unwrap();
                    let res = cargo_handle.join();
                    if res.is_err() {
                        tracing::error!(
                            "Flycheck failed to run the following command: {:?}",
                            self.check_command()
                        );
                    }
                    self.progress(Progress::DidFinish(res));
                }
                Event::CheckEvent(Some(message)) => match message {
                    CargoMessage::CompilerArtifact(msg) => {
                        self.progress(Progress::DidCheckCrate(msg.target.name));
                    }

                    CargoMessage::Diagnostic(msg) => {
                        self.send(Message::AddDiagnostic {
                            workspace_root: self.workspace_root.clone(),
                            diagnostic: msg,
                        });
                    }
                },
            }
        }
        // If we rerun the thread, we need to discard the previous check results first
        self.cancel_check_process();
    }

    fn cancel_check_process(&mut self) {
        if let Some(cargo_handle) = self.cargo_handle.take() {
            cargo_handle.cancel();
            self.progress(Progress::DidCancel);
        }
    }
    fn check_command(&self) -> Command {
        let mut cmd = match &self.config {
            FlycheckConfig::CargoCommand {
                command,
                target_triple,
                no_default_features,
                all_targets,
                all_features,
                extra_args,
                features,
            } => {
                let mut cmd = Command::new(toolchain::cargo());
                cmd.arg(command);
                cmd.current_dir(&self.workspace_root);
                cmd.args(&["--workspace", "--message-format=json", "--manifest-path"])
                    .arg(self.workspace_root.join("Cargo.toml").as_os_str());

                if let Some(target) = target_triple {
                    cmd.args(&["--target", target.as_str()]);
                }
                if *all_targets {
                    cmd.arg("--all-targets");
                }
                if *all_features {
                    cmd.arg("--all-features");
                } else {
                    if *no_default_features {
                        cmd.arg("--no-default-features");
                    }
                    if !features.is_empty() {
                        cmd.arg("--features");
                        cmd.arg(features.join(" "));
                    }
                }
                cmd.args(extra_args);
                cmd
            }
            FlycheckConfig::CustomCommand { command, args } => {
                let mut cmd = Command::new(command);
                cmd.args(args);
                cmd
            }
        };
        cmd.current_dir(&self.workspace_root);
        cmd
    }

    fn send(&self, check_task: Message) {
        (self.sender)(check_task);
    }
}

/// A handle to a cargo process used for fly-checking.
struct CargoHandle {
    /// The handle to the actual cargo process. As we cannot cancel directly from with
    /// a read syscall dropping and therefor terminating the process is our best option.
    child: JodChild,
    thread: jod_thread::JoinHandle<io::Result<(bool, String)>>,
    receiver: Receiver<CargoMessage>,
}

impl CargoHandle {
    fn spawn(mut command: Command) -> std::io::Result<CargoHandle> {
        command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());
        let mut child = JodChild::spawn(command)?;

        let stdout = child.stdout.take().unwrap();
        let stderr = child.stderr.take().unwrap();

        let (sender, receiver) = unbounded();
        let actor = CargoActor::new(sender, stdout, stderr);
        let thread = jod_thread::Builder::new()
            .name("CargoHandle".to_owned())
            .spawn(move || actor.run())
            .expect("failed to spawn thread");
        Ok(CargoHandle { child, thread, receiver })
    }

    fn cancel(mut self) {
        let _ = self.child.kill();
        let _ = self.child.wait();
    }

    fn join(mut self) -> io::Result<()> {
        let _ = self.child.kill();
        let exit_status = self.child.wait()?;
        let (read_at_least_one_message, error) = self.thread.join()?;
        if read_at_least_one_message || exit_status.success() {
            Ok(())
        } else {
            Err(io::Error::new(io::ErrorKind::Other, format!(
                "Cargo watcher failed, the command produced no valid metadata (exit code: {:?}):\n{}",
                exit_status, error
            )))
        }
    }
}

struct CargoActor {
    sender: Sender<CargoMessage>,
    stdout: ChildStdout,
    stderr: ChildStderr,
}

impl CargoActor {
    fn new(sender: Sender<CargoMessage>, stdout: ChildStdout, stderr: ChildStderr) -> CargoActor {
        CargoActor { sender, stdout, stderr }
    }

    fn run(self) -> io::Result<(bool, String)> {
        // We manually read a line at a time, instead of using serde's
        // stream deserializers, because the deserializer cannot recover
        // from an error, resulting in it getting stuck, because we try to
        // be resilient against failures.
        //
        // Because cargo only outputs one JSON object per line, we can
        // simply skip a line if it doesn't parse, which just ignores any
        // erroneus output.

        let mut error = String::new();
        let mut read_at_least_one_message = false;
        let output = streaming_output(
            self.stdout,
            self.stderr,
            &mut |line| {
                read_at_least_one_message = true;

                // Try to deserialize a message from Cargo or Rustc.
                let mut deserializer = serde_json::Deserializer::from_str(line);
                deserializer.disable_recursion_limit();
                if let Ok(message) = JsonMessage::deserialize(&mut deserializer) {
                    match message {
                        // Skip certain kinds of messages to only spend time on what's useful
                        JsonMessage::Cargo(message) => match message {
                            cargo_metadata::Message::CompilerArtifact(artifact)
                                if !artifact.fresh =>
                            {
                                self.sender.send(CargoMessage::CompilerArtifact(artifact)).unwrap();
                            }
                            cargo_metadata::Message::CompilerMessage(msg) => {
                                self.sender.send(CargoMessage::Diagnostic(msg.message)).unwrap();
                            }
                            _ => (),
                        },
                        JsonMessage::Rustc(message) => {
                            self.sender.send(CargoMessage::Diagnostic(message)).unwrap();
                        }
                    }
                }
            },
            &mut |line| {
                error.push_str(line);
                error.push('\n');
            },
        );
        match output {
            Ok(_) => Ok((read_at_least_one_message, error)),
            Err(e) => Err(io::Error::new(e.kind(), format!("{:?}: {}", e, error))),
        }
    }
}

enum CargoMessage {
    CompilerArtifact(cargo_metadata::Artifact),
    Diagnostic(Diagnostic),
}

#[derive(Deserialize)]
#[serde(untagged)]
enum JsonMessage {
    Cargo(cargo_metadata::Message),
    Rustc(Diagnostic),
}
<commit_msg>fix: Fix flycheck sending cancel progress with no running process<commit_after>//! Flycheck provides the functionality needed to run `cargo check` or
//! another compatible command (f.x. clippy) in a background thread and provide
//! LSP diagnostics based on the output of the command.

use std::{
    fmt, io,
    process::{ChildStderr, ChildStdout, Command, Stdio},
    time::Duration,
};

use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
use paths::AbsPathBuf;
use serde::Deserialize;
use stdx::{process::streaming_output, JodChild};

pub use cargo_metadata::diagnostic::{
    Applicability, Diagnostic, DiagnosticCode, DiagnosticLevel, DiagnosticSpan,
    DiagnosticSpanMacroExpansion,
};

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FlycheckConfig {
    CargoCommand {
        command: String,
        target_triple: Option<String>,
        all_targets: bool,
        no_default_features: bool,
        all_features: bool,
        features: Vec<String>,
        extra_args: Vec<String>,
    },
    CustomCommand {
        command: String,
        args: Vec<String>,
    },
}

impl fmt::Display for FlycheckConfig {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            FlycheckConfig::CargoCommand { command, .. } => write!(f, "cargo {}", command),
            FlycheckConfig::CustomCommand { command, args } => {
                write!(f, "{} {}", command, args.join(" "))
            }
        }
    }
}

/// Flycheck wraps the shared state and communication machinery used for
/// running `cargo check` (or other compatible command) and providing
/// diagnostics based on the output.
/// The spawned thread is shut down when this struct is dropped.
#[derive(Debug)]
pub struct FlycheckHandle {
    // XXX: drop order is significant
    sender: Sender<Restart>,
    _thread: jod_thread::JoinHandle,
}

impl FlycheckHandle {
    pub fn spawn(
        id: usize,
        sender: Box<dyn Fn(Message) + Send>,
        config: FlycheckConfig,
        workspace_root: AbsPathBuf,
    ) -> FlycheckHandle {
        let actor = FlycheckActor::new(id, sender, config, workspace_root);
        let (sender, receiver) = unbounded::<Restart>();
        let thread = jod_thread::Builder::new()
            .name("Flycheck".to_owned())
            .spawn(move || actor.run(receiver))
            .expect("failed to spawn thread");
        FlycheckHandle { sender, _thread: thread }
    }

    /// Schedule a re-start of the cargo check worker.
    pub fn update(&self) {
        self.sender.send(Restart).unwrap();
    }
}

pub enum Message {
    /// Request adding a diagnostic with fixes included to a file
    AddDiagnostic { workspace_root: AbsPathBuf, diagnostic: Diagnostic },

    /// Request check progress notification to client
    Progress {
        /// Flycheck instance ID
        id: usize,
        progress: Progress,
    },
}

impl fmt::Debug for Message {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Message::AddDiagnostic { workspace_root, diagnostic } => f
                .debug_struct("AddDiagnostic")
                .field("workspace_root", workspace_root)
                .field("diagnostic_code", &diagnostic.code.as_ref().map(|it| &it.code))
                .finish(),
            Message::Progress { id, progress } => {
                f.debug_struct("Progress").field("id", id).field("progress", progress).finish()
            }
        }
    }
}

#[derive(Debug)]
pub enum Progress {
    DidStart,
    DidCheckCrate(String),
    DidFinish(io::Result<()>),
    DidCancel,
}

struct Restart;

struct FlycheckActor {
    id: usize,
    sender: Box<dyn Fn(Message) + Send>,
    config: FlycheckConfig,
    workspace_root: AbsPathBuf,
    /// CargoHandle exists to wrap around the communication needed to be able to
    /// run `cargo check` without blocking. Currently the Rust standard library
    /// doesn't provide a way to read sub-process output without blocking, so we
    /// have to wrap sub-processes output handling in a thread and pass messages
    /// back over a channel.
    cargo_handle: Option<CargoHandle>,
}

enum Event {
    Restart(Restart),
    CheckEvent(Option<CargoMessage>),
}

impl FlycheckActor {
    fn new(
        id: usize,
        sender: Box<dyn Fn(Message) + Send>,
        config: FlycheckConfig,
        workspace_root: AbsPathBuf,
    ) -> FlycheckActor {
        FlycheckActor { id, sender, config, workspace_root, cargo_handle: None }
    }
    fn progress(&self, progress: Progress) {
        self.send(Message::Progress { id: self.id, progress });
    }
    fn next_event(&self, inbox: &Receiver<Restart>) -> Option<Event> {
        let check_chan = self.cargo_handle.as_ref().map(|cargo| &cargo.receiver);
        select! {
            recv(inbox) -> msg => msg.ok().map(Event::Restart),
            recv(check_chan.unwrap_or(&never())) -> msg => Some(Event::CheckEvent(msg.ok())),
        }
    }
    fn run(mut self, inbox: Receiver<Restart>) {
        while let Some(event) = self.next_event(&inbox) {
            match event {
                Event::Restart(Restart) => {
                    // Cancel the previously spawned process
                    self.cancel_check_process();
                    while let Ok(Restart) = inbox.recv_timeout(Duration::from_millis(50)) {}

                    let command = self.check_command();
                    tracing::debug!(?command, "will restart flycheck");
                    match CargoHandle::spawn(command) {
                        Ok(cargo_handle) => {
                            tracing::debug!(
                                command = ?self.check_command(),
                                "did  restart flycheck"
                            );
                            self.cargo_handle = Some(cargo_handle);
                            self.progress(Progress::DidStart);
                        }
                        Err(error) => {
                            tracing::error!(
                                command = ?self.check_command(),
                                %error, "failed to restart flycheck"
                            );
                        }
                    }
                }
                Event::CheckEvent(None) => {
                    tracing::debug!("flycheck finished");

                    // Watcher finished
                    let cargo_handle = self.cargo_handle.take().unwrap();
                    let res = cargo_handle.join();
                    if res.is_err() {
                        tracing::error!(
                            "Flycheck failed to run the following command: {:?}",
                            self.check_command()
                        );
                    }
                    self.progress(Progress::DidFinish(res));
                }
                Event::CheckEvent(Some(message)) => match message {
                    CargoMessage::CompilerArtifact(msg) => {
                        self.progress(Progress::DidCheckCrate(msg.target.name));
                    }

                    CargoMessage::Diagnostic(msg) => {
                        self.send(Message::AddDiagnostic {
                            workspace_root: self.workspace_root.clone(),
                            diagnostic: msg,
                        });
                    }
                },
            }
        }
        // If we rerun the thread, we need to discard the previous check results first
        self.cancel_check_process();
    }

    fn cancel_check_process(&mut self) {
        if let Some(cargo_handle) = self.cargo_handle.take() {
            cargo_handle.cancel();
            self.progress(Progress::DidCancel);
        }
    }

    fn check_command(&self) -> Command {
        let mut cmd = match &self.config {
            FlycheckConfig::CargoCommand {
                command,
                target_triple,
                no_default_features,
                all_targets,
                all_features,
                extra_args,
                features,
            } => {
                let mut cmd = Command::new(toolchain::cargo());
                cmd.arg(command);
                cmd.current_dir(&self.workspace_root);
                cmd.args(&["--workspace", "--message-format=json", "--manifest-path"])
                    .arg(self.workspace_root.join("Cargo.toml").as_os_str());

                if let Some(target) = target_triple {
                    cmd.args(&["--target", target.as_str()]);
                }
                if *all_targets {
                    cmd.arg("--all-targets");
                }
                if *all_features {
                    cmd.arg("--all-features");
                } else {
                    if *no_default_features {
                        cmd.arg("--no-default-features");
                    }
                    if !features.is_empty() {
                        cmd.arg("--features");
                        cmd.arg(features.join(" "));
                    }
                }
                cmd.args(extra_args);
                cmd
            }
            FlycheckConfig::CustomCommand { command, args } => {
                let mut cmd = Command::new(command);
                cmd.args(args);
                cmd
            }
        };
        cmd.current_dir(&self.workspace_root);
        cmd
    }

    fn send(&self, check_task: Message) {
        (self.sender)(check_task);
    }
}

/// A handle to a cargo process used for fly-checking.
struct CargoHandle {
    /// The handle to the actual cargo process. As we cannot cancel directly from with
    /// a read syscall dropping and therefor terminating the process is our best option.
    child: JodChild,
    thread: jod_thread::JoinHandle<io::Result<(bool, String)>>,
    receiver: Receiver<CargoMessage>,
}

impl CargoHandle {
    fn spawn(mut command: Command) -> std::io::Result<CargoHandle> {
        command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());
        let mut child = JodChild::spawn(command)?;

        let stdout = child.stdout.take().unwrap();
        let stderr = child.stderr.take().unwrap();

        let (sender, receiver) = unbounded();
        let actor = CargoActor::new(sender, stdout, stderr);
        let thread = jod_thread::Builder::new()
            .name("CargoHandle".to_owned())
            .spawn(move || actor.run())
            .expect("failed to spawn thread");
        Ok(CargoHandle { child, thread, receiver })
    }

    fn cancel(mut self) {
        let _ = self.child.kill();
        let _ = self.child.wait();
    }

    fn join(mut self) -> io::Result<()> {
        let _ = self.child.kill();
        let exit_status = self.child.wait()?;
        let (read_at_least_one_message, error) = self.thread.join()?;
        if read_at_least_one_message || exit_status.success() {
            Ok(())
        } else {
            Err(io::Error::new(io::ErrorKind::Other, format!(
                "Cargo watcher failed, the command produced no valid metadata (exit code: {:?}):\n{}",
                exit_status, error
            )))
        }
    }
}

struct CargoActor {
    sender: Sender<CargoMessage>,
    stdout: ChildStdout,
    stderr: ChildStderr,
}

impl CargoActor {
    fn new(sender: Sender<CargoMessage>, stdout: ChildStdout, stderr: ChildStderr) -> CargoActor {
        CargoActor { sender, stdout, stderr }
    }

    fn run(self) -> io::Result<(bool, String)> {
        // We manually read a line at a time, instead of using serde's
        // stream deserializers, because the deserializer cannot recover
        // from an error, resulting in it getting stuck, because we try to
        // be resilient against failures.
        //
        // Because cargo only outputs one JSON object per line, we can
        // simply skip a line if it doesn't parse, which just ignores any
        // erroneus output.

        let mut error = String::new();
        let mut read_at_least_one_message = false;
        let output = streaming_output(
            self.stdout,
            self.stderr,
            &mut |line| {
                read_at_least_one_message = true;

                // Try to deserialize a message from Cargo or Rustc.
                let mut deserializer = serde_json::Deserializer::from_str(line);
                deserializer.disable_recursion_limit();
                if let Ok(message) = JsonMessage::deserialize(&mut deserializer) {
                    match message {
                        // Skip certain kinds of messages to only spend time on what's useful
                        JsonMessage::Cargo(message) => match message {
                            cargo_metadata::Message::CompilerArtifact(artifact)
                                if !artifact.fresh =>
                            {
                                self.sender.send(CargoMessage::CompilerArtifact(artifact)).unwrap();
                            }
                            cargo_metadata::Message::CompilerMessage(msg) => {
                                self.sender.send(CargoMessage::Diagnostic(msg.message)).unwrap();
                            }
                            _ => (),
                        },
                        JsonMessage::Rustc(message) => {
                            self.sender.send(CargoMessage::Diagnostic(message)).unwrap();
                        }
                    }
                }
            },
            &mut |line| {
                error.push_str(line);
                error.push('\n');
            },
        );
        match output {
            Ok(_) => Ok((read_at_least_one_message, error)),
            Err(e) => Err(io::Error::new(e.kind(), format!("{:?}: {}", e, error))),
        }
    }
}

enum CargoMessage {
    CompilerArtifact(cargo_metadata::Artifact),
    Diagnostic(Diagnostic),
}

#[derive(Deserialize)]
#[serde(untagged)]
enum JsonMessage {
    Cargo(cargo_metadata::Message),
    Rustc(Diagnostic),
}
<|endoftext|>


<commit_before>
#[derive(Debug)]
#[repr(packed)] // repr(C) would add unwanted padding before first_section
pub struct ElfSectionsTag {
    typ: u32,
    size: u32,
    pub number_of_sections: u32,
    entry_size: u32,
    shndx: u32, // string table
    first_section: ElfSection,
}

impl ElfSectionsTag {
    pub fn sections(&'static self) -> ElfSectionIter {
        ElfSectionIter {
            current_section: &self.first_section,
            remaining_sections: self.number_of_sections - 1,
            entry_size: self.entry_size,
        }
    }
}

#[derive(Clone)]
pub struct ElfSectionIter {
    current_section: &'static ElfSection,
    remaining_sections: u32,
    entry_size: u32,
}

impl Iterator for ElfSectionIter {
    type Item = &'static ElfSection;
    fn next(&mut self) -> Option<&'static ElfSection> {
        if self.remaining_sections == 0 {
            None
        } else {
            let section = self.current_section;
            let next_section_addr = (self.current_section as *const _ as u32) + self.entry_size;
            self.current_section = unsafe{ &*(next_section_addr as *const ElfSection) };
            self.remaining_sections -= 1;
			if section.typ == ElfSectionType::Unused as u32 {
				self.next()
			} else {
	            Some(section)
			}
        }
    }
}

#[derive(Debug)]
#[repr(C)]
pub struct ElfSection {
    name: u32,
    typ: u32,
    pub flags: u64,
    pub addr: u64,
    offset: u64,
    pub size: u64,
    link: u32,
    info: u32,
    addralign: u64,
    entry_size: u64,
}

#[repr(u32)]
pub enum ElfSectionType {
    Unused = 0,
    ProgramSection = 1,
    LinkerSymbolTable = 2,
    StringTable = 3,
    RelaRelocation = 4,
    SymbolHashTable = 5,
    DynamicLinkingTable = 6,
    Note = 7,
    Uninitialized = 8,
    RelRelocation = 9,
    Reserved = 10,
    DynamicLoaderSymbolTable = 11,
    // plus environment-specific use from 0x60000000 to 0x6FFFFFFF
    // plus processor-specific use from 0x70000000 to 0x7FFFFFFF
}

bitflags! {
    flags ElfSectionFlags: u64 {
        const WRITABLE = 0x1,
        const ALLOCATED = 0x2,
        const EXECUTABLE = 0x4,
        // plus environment-specific use at 0x0F000000
        // plus processor-specific use at 0xF0000000
    }
}
<commit_msg>Add ElfSection::flags() method<commit_after>
#[derive(Debug)]
#[repr(packed)] // repr(C) would add unwanted padding before first_section
pub struct ElfSectionsTag {
    typ: u32,
    size: u32,
    pub number_of_sections: u32,
    entry_size: u32,
    shndx: u32, // string table
    first_section: ElfSection,
}

impl ElfSectionsTag {
    pub fn sections(&'static self) -> ElfSectionIter {
        ElfSectionIter {
            current_section: &self.first_section,
            remaining_sections: self.number_of_sections - 1,
            entry_size: self.entry_size,
        }
    }
}

#[derive(Clone)]
pub struct ElfSectionIter {
    current_section: &'static ElfSection,
    remaining_sections: u32,
    entry_size: u32,
}

impl Iterator for ElfSectionIter {
    type Item = &'static ElfSection;
    fn next(&mut self) -> Option<&'static ElfSection> {
        if self.remaining_sections == 0 {
            None
        } else {
            let section = self.current_section;
            let next_section_addr = (self.current_section as *const _ as u32) + self.entry_size;
            self.current_section = unsafe{ &*(next_section_addr as *const ElfSection) };
            self.remaining_sections -= 1;
			if section.typ == ElfSectionType::Unused as u32 {
				self.next()
			} else {
	            Some(section)
			}
        }
    }
}

#[derive(Debug)]
#[repr(C)]
pub struct ElfSection {
    name: u32,
    typ: u32,
    pub flags: u64,
    pub addr: u64,
    offset: u64,
    pub size: u64,
    link: u32,
    info: u32,
    addralign: u64,
    entry_size: u64,
}

impl ElfSection {
    pub fn flags(&self) -> ElfSectionFlags {
        ElfSectionFlags::from_bits_truncate(self.flags)
    }
}

#[repr(u32)]
pub enum ElfSectionType {
    Unused = 0,
    ProgramSection = 1,
    LinkerSymbolTable = 2,
    StringTable = 3,
    RelaRelocation = 4,
    SymbolHashTable = 5,
    DynamicLinkingTable = 6,
    Note = 7,
    Uninitialized = 8,
    RelRelocation = 9,
    Reserved = 10,
    DynamicLoaderSymbolTable = 11,
    // plus environment-specific use from 0x60000000 to 0x6FFFFFFF
    // plus processor-specific use from 0x70000000 to 0x7FFFFFFF
}

bitflags! {
    flags ElfSectionFlags: u64 {
        const WRITABLE = 0x1,
        const ALLOCATED = 0x2,
        const EXECUTABLE = 0x4,
        // plus environment-specific use at 0x0F000000
        // plus processor-specific use at 0xF0000000
    }
}
<|endoftext|>


<commit_before>use dynasmrt::x64::Assembler;
use dynasmrt::{DynasmApi, DynasmLabelApi};

use super::*;
use peephole::{self, Count};

dynasm!(asm
    ; .alias pointer, r12
    ; .alias mem_start, r13
    ; .alias mem_limit, r14
    ; .alias rts, r15
);

/// Compiles peephole-optimized AST to x64 machine code.
///
/// Uses the `dynasmrt` assembler
pub fn compile(program: &peephole::Program, checked: bool) -> Program {
    let mut asm = dynasmrt::x64::Assembler::new();
    let start = asm.offset();

    dynasm!(asm
        ; push r12
        ; push r13
        ; push r14
        ; push r15
        ; mov pointer, rcx
        ; mov mem_start, rcx
        ; mov mem_limit, rcx
        ; add mem_limit, rdx
        ; mov rts, r8
    );

    compile_sequence(&mut asm, program, checked);

    dynasm!(asm
        ; mov rax, rts::OKAY as i32
        ; jmp ->finish

        ; ->underflow:
        ; mov rax, rts::UNDERFLOW as i32
        ; jmp ->finish

        ; ->overflow:
        ; mov rax, rts::OVERFLOW as i32

        ; ->finish:
        ; pop r15
        ; pop r14
        ; pop r13
        ; pop r12
        ; ret
    );

    let code = asm.finalize().unwrap();

    Program {
        code: code,
        start: start,
    }
}

fn compile_sequence(asm: &mut Assembler, program: &[peephole::Instruction], checked: bool) {
    for instruction in program {
        compile_instruction(asm, instruction, checked);
    }
}

fn compile_instruction(asm: &mut Assembler, instruction: &peephole::Instruction, checked: bool) {
    use peephole::Instruction::*;

    match *instruction {
        Right(count) => {
            dynasm!(asm
                ;; load_pos_offset(asm, count, checked)
                ; add pointer, rax
            );
        }

        Left(count) => {
            dynasm!(asm
                ;; load_neg_offset(asm, count, checked)
                ; sub pointer, rax
            );
        }

        Change(count) => {
            dynasm!(asm
                ; add [pointer], BYTE count as i8
            );
        }

        In => {
            dynasm!(asm
                ; mov rax, QWORD rts::RtsState::read as _
                ; mov rcx, rts
                ; sub rsp, BYTE 0x28
                ; call rax
                ; add rsp, BYTE 0x28
                ; mov [pointer], al
            );
        }

        Out => {
            dynasm!(asm
                ; mov rax, QWORD rts::RtsState::write as _
                ; mov rcx, rts
                ; xor rdx, rdx
                ; mov dl, [pointer]
                ; sub rsp, BYTE 0x28
                ; call rax
                ; add rsp, BYTE 0x28
            );
        }

        SetZero => {
            dynasm!(asm
                ; mov BYTE [pointer], 0
            )
        }

        FindZeroRight(skip) => {
            dynasm!(asm
                ; jmp >end_loop
                ; begin_loop:
                ;; load_pos_offset(asm, skip, checked)
                ; add pointer, rax
                ; end_loop:
                ; cmp BYTE [pointer], 0
                ; jnz <begin_loop
            )
        }

        FindZeroLeft(skip) => {
            dynasm!(asm
                ; jmp >end_loop
                ; begin_loop:
                ;; load_neg_offset(asm, skip, checked)
                ; sub pointer, rax
                ; end_loop:
                ; cmp BYTE [pointer], 0
                ; jnz <begin_loop
            )
        }

        OffsetAddRight(offset) => {
            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jz >skip
                ;; load_pos_offset(asm, offset, checked)
                ; mov cl, BYTE [pointer]
                ; mov BYTE [pointer], 0
                ; add BYTE [pointer + rax], cl
                ; skip:
            );
        }

        OffsetAddLeft(offset) => {
            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jz >skip
                ;; load_neg_offset(asm, offset, checked)
                ; mov cl, BYTE [pointer]
                ; mov BYTE [pointer], 0
                ; neg rax
                ; add BYTE [pointer + rax], cl
                ; skip:
            );
        }

        Loop(ref body) => {
            let begin_label = asm.new_dynamic_label();
            let end_label   = asm.new_dynamic_label();

            dynasm!(asm
                ; jmp =>end_label
                ; =>begin_label
            );

            compile_sequence(asm, body, checked);

            dynasm!(asm
                ; =>end_label
                ; cmp BYTE [pointer], 0
                ; jnz =>begin_label
            );
        }
    }
}

#[inline]
fn load_offset(asm: &mut Assembler, offset: Count) {
    if offset as i32 as Count == offset {
        dynasm!(asm
            ; mov rax, DWORD offset as i32
        );
    } else {
        dynasm!(asm
            ; mov rax, QWORD offset as i64
        );
    }
}

fn load_pos_offset(asm: &mut Assembler, offset: Count, checked: bool) {
    load_offset(asm, offset);

    if checked {
        dynasm!(asm
            ; mov rcx, mem_limit
            ; sub rcx, pointer
            ; cmp rcx, rax
            ; jle ->overflow
        );
    }
}

fn load_neg_offset(asm: &mut Assembler, offset: Count, checked: bool) {
    load_offset(asm, offset);

    if checked {
        dynasm!(asm
            ; mov rcx, pointer
            ; sub rcx, mem_start
            ; cmp rcx, rax
            ; jl ->underflow
        );
    }
}

<commit_msg>Some inlines.<commit_after>use dynasmrt::x64::Assembler;
use dynasmrt::{DynasmApi, DynasmLabelApi};

use super::*;
use peephole::{self, Count};

dynasm!(asm
    ; .alias pointer, r12
    ; .alias mem_start, r13
    ; .alias mem_limit, r14
    ; .alias rts, r15
);

/// Compiles peephole-optimized AST to x64 machine code.
///
/// Uses the `dynasmrt` assembler
pub fn compile(program: &peephole::Program, checked: bool) -> Program {
    let mut asm = dynasmrt::x64::Assembler::new();
    let start = asm.offset();

    dynasm!(asm
        ; push r12
        ; push r13
        ; push r14
        ; push r15
        ; mov pointer, rcx
        ; mov mem_start, rcx
        ; mov mem_limit, rcx
        ; add mem_limit, rdx
        ; mov rts, r8
    );

    compile_sequence(&mut asm, program, checked);

    dynasm!(asm
        ; mov rax, rts::OKAY as i32
        ; jmp ->finish

        ; ->underflow:
        ; mov rax, rts::UNDERFLOW as i32
        ; jmp ->finish

        ; ->overflow:
        ; mov rax, rts::OVERFLOW as i32

        ; ->finish:
        ; pop r15
        ; pop r14
        ; pop r13
        ; pop r12
        ; ret
    );

    let code = asm.finalize().unwrap();

    Program {
        code: code,
        start: start,
    }
}

fn compile_sequence(asm: &mut Assembler, program: &[peephole::Instruction], checked: bool) {
    for instruction in program {
        compile_instruction(asm, instruction, checked);
    }
}

fn compile_instruction(asm: &mut Assembler, instruction: &peephole::Instruction, checked: bool) {
    use peephole::Instruction::*;

    match *instruction {
        Right(count) => {
            dynasm!(asm
                ;; load_pos_offset(asm, count, checked)
                ; add pointer, rax
            );
        }

        Left(count) => {
            dynasm!(asm
                ;; load_neg_offset(asm, count, checked)
                ; sub pointer, rax
            );
        }

        Change(count) => {
            dynasm!(asm
                ; add [pointer], BYTE count as i8
            );
        }

        In => {
            dynasm!(asm
                ; mov rax, QWORD rts::RtsState::read as _
                ; mov rcx, rts
                ; sub rsp, BYTE 0x28
                ; call rax
                ; add rsp, BYTE 0x28
                ; mov [pointer], al
            );
        }

        Out => {
            dynasm!(asm
                ; mov rax, QWORD rts::RtsState::write as _
                ; mov rcx, rts
                ; xor rdx, rdx
                ; mov dl, [pointer]
                ; sub rsp, BYTE 0x28
                ; call rax
                ; add rsp, BYTE 0x28
            );
        }

        SetZero => {
            dynasm!(asm
                ; mov BYTE [pointer], 0
            )
        }

        FindZeroRight(skip) => {
            dynasm!(asm
                ; jmp >end_loop
                ; begin_loop:
                ;; load_pos_offset(asm, skip, checked)
                ; add pointer, rax
                ; end_loop:
                ; cmp BYTE [pointer], 0
                ; jnz <begin_loop
            )
        }

        FindZeroLeft(skip) => {
            dynasm!(asm
                ; jmp >end_loop
                ; begin_loop:
                ;; load_neg_offset(asm, skip, checked)
                ; sub pointer, rax
                ; end_loop:
                ; cmp BYTE [pointer], 0
                ; jnz <begin_loop
            )
        }

        OffsetAddRight(offset) => {
            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jz >skip
                ;; load_pos_offset(asm, offset, checked)
                ; mov cl, BYTE [pointer]
                ; mov BYTE [pointer], 0
                ; add BYTE [pointer + rax], cl
                ; skip:
            );
        }

        OffsetAddLeft(offset) => {
            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jz >skip
                ;; load_neg_offset(asm, offset, checked)
                ; mov cl, BYTE [pointer]
                ; mov BYTE [pointer], 0
                ; neg rax
                ; add BYTE [pointer + rax], cl
                ; skip:
            );
        }

        Loop(ref body) => {
            let begin_label = asm.new_dynamic_label();
            let end_label   = asm.new_dynamic_label();

            dynasm!(asm
                ; jmp =>end_label
                ; =>begin_label
            );

            compile_sequence(asm, body, checked);

            dynasm!(asm
                ; =>end_label
                ; cmp BYTE [pointer], 0
                ; jnz =>begin_label
            );
        }
    }
}

#[inline]
fn load_offset(asm: &mut Assembler, offset: Count) {
    if offset as i32 as Count == offset {
        dynasm!(asm
            ; mov rax, DWORD offset as i32
        );
    } else {
        dynasm!(asm
            ; mov rax, QWORD offset as i64
        );
    }
}

#[inline]
fn load_pos_offset(asm: &mut Assembler, offset: Count, checked: bool) {
    load_offset(asm, offset);

    if checked {
        dynasm!(asm
            ; mov rcx, mem_limit
            ; sub rcx, pointer
            ; cmp rcx, rax
            ; jle ->overflow
        );
    }
}

#[inline]
fn load_neg_offset(asm: &mut Assembler, offset: Count, checked: bool) {
    load_offset(asm, offset);

    if checked {
        dynasm!(asm
            ; mov rcx, pointer
            ; sub rcx, mem_start
            ; cmp rcx, rax
            ; jl ->underflow
        );
    }
}

<|endoftext|>