<commit_before>extern crate hkg;
extern crate termion;
extern crate rustc_serialize;
extern crate kuchiki;
extern crate chrono;
extern crate hyper;
extern crate cancellation;
extern crate time;
extern crate url;

use std::path::Path;
use std::fs::File;
use std::fs;
use std::io::{stdout, stdin, Read, Write};
use std::thread;
use std::sync::mpsc::channel;
use std::sync::mpsc::Sender;
use cancellation::{CancellationToken, CancellationTokenSource, OperationCanceled};
use kuchiki::traits::*;
use rustc_serialize::json;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
use termion::event::Key;
use termion::terminal_size;
use termion::{color, style};
use hkg::utility::cache;
use hkg::model::IconItem;
use hkg::model::ListTopicItem;
use hkg::utility::client::*;

#[derive(PartialEq, Eq, Copy, Clone)]
enum Status {
    Startup,
    List,
    Show,
}

fn main() {

    // Initialize
    let stdout = stdout();
    let mut stdout = stdout.lock().into_raw_mode().unwrap();

    // Clear the screen.
    print!("{}", termion::clear::All);

    let mut builder = hkg::builder::Builder::new();
    let mut status = String::from("> ");

    let mut state = Status::Startup;
    let mut prev_state = state;
    let mut prev_width = terminal_size().unwrap().0;
    let mut is_web_requesting = false;

    let icon_manifest_string = cache::readfile(String::from("data/icon.manifest.json"));
    let icon_collection: Box<Vec<IconItem>> = Box::new(json::decode(&icon_manifest_string).unwrap());

    // initialize empty page
    let mut list_topic_items: Vec<ListTopicItem> = vec![];
    let mut show_item = builder.default_show_item();

    let mut index = hkg::screen::index::Index::new();
    let mut show = hkg::screen::show::Show::new(icon_collection);

    let (tx_req, rx_req) = channel::<ChannelItem>();
    let (tx_res, rx_res) = channel::<ChannelItem>();

    // web client
    thread::spawn(move || {
        let mut wr = WebResource::new();
        let ct = CancellationTokenSource::new();
        ct.cancel_after(std::time::Duration::new(10, 0));

        loop {
            match rx_req.recv() {
                Ok(item) => {

                    let th = thread::current();
                    ct.run(|| {
                               th.unpark();
                           },
                           || {
                               tx_res.send(page_request(&item, &mut wr, &ct)).unwrap();
                           });

                    if ct.is_canceled() {
                        thread::park_timeout(std::time::Duration::new(0, 250));
                        // Err(OperationCanceled)
                    } else {
                        // Ok(())
                    }
                }
                Err(_) => {}
            }
        }
    });

    // topics request
    let status_message = list_page(&mut is_web_requesting, &tx_req);
    status = format_status(status.clone(), &status_message);

    loop {

        // show UI
        if prev_state != state {
            print!("{}", termion::clear::All); // clear screen when switching state
            prev_state = state;
        }

        match rx_res.try_recv() {
            Ok(item) => {
                match item.extra {
                    ChannelItemType::Show(extra) => {
                        let document = kuchiki::parse_html().from_utf8().one(item.result.as_bytes());

                        let posturl = get_posturl(&extra.postid, extra.page);
                        show_item = builder.show_item(&document, &posturl);

                        status = format_status(status,
                                               &format!("[{}-{}:ROK][{}]",
                                                        show_item.url_query.message,
                                                        show_item.page,
                                                        is_web_requesting));

                        show.resetY();
                        print!("{}", termion::clear::All);
                        state = Status::Show;
                        is_web_requesting = false;
                    },
                    ChannelItemType::Index(_) => {
                        let document = kuchiki::parse_html().from_utf8().one(item.result.as_bytes());

                        list_topic_items = builder.list_topic_items(&document);

                        status = format_status(status,
                                               &format!("[TOPICS:ROK]"));

                        print!("{}", termion::clear::All);

                        state = Status::List;
                        is_web_requesting = false;
                    }
                }
            }
            Err(_) => {}
        }

        match state {
            Status::Startup => {

            },
            Status::List => {
                index.print(&mut stdout, &list_topic_items);
            }
            Status::Show => {
                show.print(&mut stdout, &show_item);
            }
        }

        print_status(&mut stdout, &status);

        stdout.flush().unwrap();

        if !is_web_requesting {

            let stdin = stdin();

            for c in stdin.keys() {

                if prev_width != terminal_size().unwrap().0 {
                    print!("{}", termion::clear::All);
                   prev_width = terminal_size().unwrap().0;
                }

                match c.unwrap() {
                    Key::Char('q') => {
                        print!("{}{}{}", termion::clear::All, style::Reset, termion::cursor::Show);
                        return
                    },
                    Key::Char('\n') => {

                        status = format_status(status, "ENTER");

                        match state {
                            Status::Startup => {},
                            Status::List => {
                                let i = index.get_selected_topic();
                                if i > 0 {
                                    let topic_item = &list_topic_items[i - 1];
                                    let postid = &topic_item.title.url_query.message;
                                    let page = 1;
                                    let status_message = show_page(&postid, page, &mut is_web_requesting, &tx_req);

                                    status = format_status(status.clone(),
                                                           &get_show_page_status_message(postid, page, &status_message));
                                }
                            }
                            Status::Show => {}
                        }
                        break
                    },
                    Key::Alt(c) => {
                        status = format_status(status, &format!("^{}", c));
                        break
                    },
                    Key::Ctrl(c) => {
                        status = format_status(status, &format!("*{}", c));
                        break
                    },
                    Key::Left => {
                        status = format_status(status, &format!("←"));

                        match state {
                            Status::Startup => {},
                            Status::List => {}
                            Status::Show => {
                                if show_item.page > 1 {
                                    let postid = &show_item.url_query.message;
                                    let page = &show_item.page - 1;
                                    let status_message = show_page(&postid, page, &mut is_web_requesting, &tx_req);

                                    status = format_status(status.clone(),
                                                           &get_show_page_status_message(postid, page, &status_message));
                                }
                            }
                        }
                        break
                    }
                    Key::Right => {
                        status = format_status(status, &format!("→"));
                        match state {
                            Status::Startup => {},
                            Status::List => {}
                            Status::Show => {
                                if show_item.max_page > show_item.page {
                                    let postid = &show_item.url_query.message;
                                    let page = &show_item.page + 1;
                                    let status_message = show_page(&postid, page, &mut is_web_requesting, &tx_req);

                                    status = format_status(status.clone(),
                                                           &get_show_page_status_message(postid, page, &status_message));
                                }
                            }
                        }
                        break
                    },
                    Key::PageUp => {
                        status = format_status(status, "↑");
                        match state {
                            Status::Startup => {},
                            Status::List => {
                                let tmp = index.get_selected_topic();
                                status = format_status(status, &format!("{}", tmp));

                                if tmp > 1 {
                                    index.select_topic(tmp - 1);
                                }
                            }
                            Status::Show => {
                                let bh = show.body_height();
                                if show.scrollUp(bh) {
                                    print!("{}", termion::clear::All);
                                }
                            }
                        }

                        break
                    },
                    Key::PageDown => {
                        status = format_status(status, "↓");
                        match state {
                            Status::Startup => {},
                            Status::List => {}
                            Status::Show => {
                                let bh = show.body_height();
                                if show.scrollDown(bh) {
                                    print!("{}", termion::clear::All);
                                }
                            }
                        }
                        break
                    },
                    Key::Up => {
                        status = format_status(status, "↑");
                        match state {
                            Status::Startup => {},
                            Status::List => {
                                let tmp = index.get_selected_topic();
                                status = format_status(status, &format!("{}", tmp));

                                if tmp > 1 {
                                    index.select_topic(tmp - 1);
                                }
                            }
                            Status::Show => {
                                if show.scrollUp(2) {
                                    print!("{}", termion::clear::All);
                                }
                            }
                        }

                        break
                    },
                    Key::Down => {
                        status = format_status(status, "↓");
                        match state {
                            Status::Startup => {},
                            Status::List => {
                                let tmp = index.get_selected_topic();
                                status = format_status(status, &format!("{}", tmp));

                                if tmp < index.body_height() {
                                    index.select_topic(tmp + 1);
                                }
                            }
                            Status::Show => {
                                if show.scrollDown(2) {
                                    print!("{}", termion::clear::All);
                                }
                            }
                        }
                        break
                    },
                    Key::Backspace => {
                        status = format_status(status, "B");

                        match state {
                            Status::Startup => {},
                            Status::List => {}
                            Status::Show => {
                                state = Status::List;
                                print!("{}", termion::clear::All);
                            }
                        }
                        break
                    },
                    Key::Char(c) => {
                        status = format_status(status, &format!(" {}", c));
                        break
                    },
                    // Key::Invalid => {
                    //     status = format_status(status, w as usize, &format!("???"));
                    //     break
                    // },
                    _ => {},
                }
            }
        }

    }
}

fn read_cache<P: AsRef<Path>, S: AsRef<Path>>(cache_path: P,
                                              file_name: S)
                                              -> Result<String, String> {
    let file_path = cache_path.as_ref().join(file_name);
    let mut file = try!(File::open(file_path).map_err(|e| e.to_string()));
    let mut contents = String::new();
    try!(file.read_to_string(&mut contents).map_err(|e| e.to_string()));
    Ok(contents)
}

fn write_cache<P: AsRef<Path>, S: AsRef<Path>>(cache_path: P,
                                               file_name: S,
                                               s: String)
                                               -> Result<(), String> {
    let file_path = cache_path.as_ref().join(file_name);
    try!(fs::create_dir_all(&cache_path).map_err(|e| e.to_string()));
    let mut file = try!(File::create(file_path).map_err(|e| e.to_string()));
    try!(file.write_all(&s.into_bytes()).map_err(|e| e.to_string()));
    Ok(())
}

fn get_posturl(postid: &String, page: usize) -> String {
    let base_url = "http://forum1.hkgolden.com/view.aspx";
    let posturl = format!("{base_url}?type=BW&message={postid}&page={page}",
                          base_url = base_url,
                          postid = postid,
                          page = page);
    posturl
}

fn get_topic_bw_url() -> String {
    let base_url = "http://forum1.hkgolden.com";
    let url = format!("{base_url}/topics_bw.htm", base_url = base_url);
    url
}

fn page_request(item: &ChannelItem,
                wr: &mut WebResource,
                ct: &CancellationTokenSource)
                -> ChannelItem {

    match item.extra.clone() {
        ChannelItemType::Show(extra) => {
            let html_path = format!("data/html/{postid}/", postid = extra.postid);
            let show_file_name = format!("show_{page}.html", page = extra.page);

            let postid = extra.postid.clone();

            let (from_cache, result) = match read_cache(&html_path, &show_file_name) {
                Ok(result) => (true, result),
                Err(_) => {
                    let posturl = get_posturl(&extra.postid, extra.page);
                    let result = wr.get(&posturl);
                    (false, result)
                }
            };

            if !from_cache {
                let result2 = result.clone();
                write_cache(&html_path, &show_file_name, result2);
            }

            let result_item = ChannelItem {
                extra: ChannelItemType::Show(ChannelShowItem { postid: postid, page: extra.page }),
                result: result,
            };
            result_item
        },
        ChannelItemType::Index(_) => {

            let time_format = |t: time::Tm| {
                match t.strftime("%Y%m%d%H%M") {
                    Ok(s) => s.to_string(),
                    Err(e) => panic!(e)
                }
            };

            let time = time_format(time::now());

            let html_path = format!("data/html/topics/");
            let file_name = format!("{time}.html", time = time);

            let (from_cache, result) = match read_cache(&html_path, &file_name) {
                Ok(result) => (true, result),
                Err(_) => {
                    let url = get_topic_bw_url();
                    let result = wr.get(&url);
                    (false, result)
                }
            };

            if !from_cache {
                let result2 = result.clone();
                write_cache(&html_path, &file_name, result2);
            }

            let result_item = ChannelItem {
                extra: ChannelItemType::Index(ChannelIndexItem { }),
                result: result,
            };
            result_item
        }
    }

}

fn list_page(is_web_requesting: &mut bool, tx_req: &Sender<ChannelItem>) -> String {

    let ci = ChannelItem {
        extra: ChannelItemType::Index(ChannelIndexItem { }),
        result: String::from(""),
    };

    let status_message = match tx_req.send(ci) {
        Ok(()) => {
            *is_web_requesting = true;
            "SOK".to_string()
        }
        Err(e) => format!("{}:{}", "SFAIL", e).to_string(),
    };

    status_message
}

fn show_page(postid: &String, page: usize, is_web_requesting: &mut bool, tx_req: &Sender<ChannelItem>) -> String {

    let ci = ChannelItem {
        extra: ChannelItemType::Show(ChannelShowItem { postid: postid.clone(), page: page }),
        result: String::from(""),
    };

    let status_message = match tx_req.send(ci) {
        Ok(()) => {
            *is_web_requesting = true;
            "SOK".to_string()
        }
        Err(e) => format!("{}:{}", "SFAIL", e).to_string(),
    };

    status_message
}

fn get_show_page_status_message(postid: &String, page: usize, status_message: &String) -> String {
    format!("[{}-{}:{}]", postid, page, status_message)
}

fn print_status(stdout: &mut termion::raw::RawTerminal<std::io::StdoutLock>, status: &str) {
    // for status bar only
    let h = terminal_size().unwrap().1;

    write!(stdout, "{}{}{}{}{}{}",
            termion::cursor::Goto(1, h),
            color::Fg(color::White),
            style::Bold,
            format!("{status}", status = status),
            style::Reset,
            termion::cursor::Hide);
}

fn format_status(status: String, s: &str) -> String {
    let w = terminal_size().unwrap().0 as usize;;
    if status.len() >= w {
        String::from(format!("{}{}", &"> ", s))
    } else {
        String::from(format!("{}{}", &status, s))
    }
}
<commit_msg>reverse input match and status match<commit_after>extern crate hkg;
extern crate termion;
extern crate rustc_serialize;
extern crate kuchiki;
extern crate chrono;
extern crate hyper;
extern crate cancellation;
extern crate time;
extern crate url;

use std::path::Path;
use std::fs::File;
use std::fs;
use std::io::{stdout, stdin, Read, Write};
use std::thread;
use std::sync::mpsc::channel;
use std::sync::mpsc::Sender;
use cancellation::{CancellationToken, CancellationTokenSource, OperationCanceled};
use kuchiki::traits::*;
use rustc_serialize::json;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
use termion::event::Key;
use termion::terminal_size;
use termion::{color, style};
use hkg::utility::cache;
use hkg::model::IconItem;
use hkg::model::ListTopicItem;
use hkg::utility::client::*;

#[derive(PartialEq, Eq, Copy, Clone)]
enum Status {
    Startup,
    List,
    Show,
}

fn main() {

    // Initialize
    let stdout = stdout();
    let mut stdout = stdout.lock().into_raw_mode().unwrap();

    // Clear the screen.
    print!("{}", termion::clear::All);

    let mut builder = hkg::builder::Builder::new();
    let mut status = String::from("> ");

    let mut state = Status::Startup;
    let mut prev_state = state;
    let mut prev_width = terminal_size().unwrap().0;
    let mut is_web_requesting = false;

    let icon_manifest_string = cache::readfile(String::from("data/icon.manifest.json"));
    let icon_collection: Box<Vec<IconItem>> = Box::new(json::decode(&icon_manifest_string).unwrap());

    // initialize empty page
    let mut list_topic_items: Vec<ListTopicItem> = vec![];
    let mut show_item = builder.default_show_item();

    let mut index = hkg::screen::index::Index::new();
    let mut show = hkg::screen::show::Show::new(icon_collection);

    let (tx_req, rx_req) = channel::<ChannelItem>();
    let (tx_res, rx_res) = channel::<ChannelItem>();

    // web client
    thread::spawn(move || {
        let mut wr = WebResource::new();
        let ct = CancellationTokenSource::new();
        ct.cancel_after(std::time::Duration::new(10, 0));

        loop {
            match rx_req.recv() {
                Ok(item) => {

                    let th = thread::current();
                    ct.run(|| {
                               th.unpark();
                           },
                           || {
                               tx_res.send(page_request(&item, &mut wr, &ct)).unwrap();
                           });

                    if ct.is_canceled() {
                        thread::park_timeout(std::time::Duration::new(0, 250));
                        // Err(OperationCanceled)
                    } else {
                        // Ok(())
                    }
                }
                Err(_) => {}
            }
        }
    });

    // topics request
    let status_message = list_page(&mut is_web_requesting, &tx_req);
    status = format_status(status.clone(), &status_message);

    loop {

        // show UI
        if prev_state != state {
            print!("{}", termion::clear::All); // clear screen when switching state
            prev_state = state;
        }

        match rx_res.try_recv() {
            Ok(item) => {
                match item.extra {
                    ChannelItemType::Show(extra) => {
                        let document = kuchiki::parse_html().from_utf8().one(item.result.as_bytes());

                        let posturl = get_posturl(&extra.postid, extra.page);
                        show_item = builder.show_item(&document, &posturl);

                        status = format_status(status,
                                               &format!("[{}-{}:ROK][{}]",
                                                        show_item.url_query.message,
                                                        show_item.page,
                                                        is_web_requesting));

                        show.resetY();
                        print!("{}", termion::clear::All);
                        state = Status::Show;
                        is_web_requesting = false;
                    },
                    ChannelItemType::Index(_) => {
                        let document = kuchiki::parse_html().from_utf8().one(item.result.as_bytes());

                        list_topic_items = builder.list_topic_items(&document);

                        status = format_status(status,
                                               &format!("[TOPICS:ROK]"));

                        print!("{}", termion::clear::All);

                        state = Status::List;
                        is_web_requesting = false;
                    }
                }
            }
            Err(_) => {}
        }

        match state {
            Status::Startup => {

            },
            Status::List => {
                index.print(&mut stdout, &list_topic_items);
            }
            Status::Show => {
                show.print(&mut stdout, &show_item);
            }
        }

        print_status(&mut stdout, &status);

        stdout.flush().unwrap();

        if !is_web_requesting {

            let stdin = stdin();

            for c in stdin.keys() {

                if prev_width != terminal_size().unwrap().0 {
                    print!("{}", termion::clear::All);
                   prev_width = terminal_size().unwrap().0;
                }

                match state {
                    Status::Startup => {},
                    Status::List => {
                        match c.unwrap() {
                            Key::Char('q') => {
                                print!("{}{}{}", termion::clear::All, style::Reset, termion::cursor::Show);
                                return
                            },
                            Key::Char('\n') => {
                                status = format_status(status, "ENTER");
                                let i = index.get_selected_topic();
                                if i > 0 {
                                    let topic_item = &list_topic_items[i - 1];
                                    let postid = &topic_item.title.url_query.message;
                                    let page = 1;
                                    let status_message = show_page(&postid, page, &mut is_web_requesting, &tx_req);

                                    status = format_status(status.clone(),
                                                           &get_show_page_status_message(postid, page, &status_message));
                                }
                                break
                            },
                            Key::PageUp => {
                                status = format_status(status, "↑");
                                let tmp = index.get_selected_topic();
                                status = format_status(status, &format!("{}", tmp));

                                if tmp > 1 {
                                    index.select_topic(tmp - 1);
                                }
                                break
                            },
                            Key::Up => {
                                status = format_status(status, "↑");
                                let tmp = index.get_selected_topic();
                                status = format_status(status, &format!("{}", tmp));

                                if tmp > 1 {
                                    index.select_topic(tmp - 1);
                                }
                                break
                            },
                            Key::Down => {
                                status = format_status(status, "↓");
                                let tmp = index.get_selected_topic();
                                status = format_status(status, &format!("{}", tmp));

                                if tmp < index.body_height() {
                                    index.select_topic(tmp + 1);
                                }
                                break
                            },
                            _ => {},
                        }
                    },
                    Status::Show => {
                            match c.unwrap() {
                                Key::Char('q') => {
                                    print!("{}{}{}", termion::clear::All, style::Reset, termion::cursor::Show);
                                    return
                                },
                                Key::Left => {
                                    status = format_status(status, &format!("←"));
                                    if show_item.page > 1 {
                                        let postid = &show_item.url_query.message;
                                        let page = &show_item.page - 1;
                                        let status_message = show_page(&postid, page, &mut is_web_requesting, &tx_req);

                                        status = format_status(status.clone(),
                                                               &get_show_page_status_message(postid, page, &status_message));
                                    }
                                    break
                                }
                                Key::Right => {
                                    status = format_status(status, &format!("→"));
                                    if show_item.max_page > show_item.page {
                                        let postid = &show_item.url_query.message;
                                        let page = &show_item.page + 1;
                                        let status_message = show_page(&postid, page, &mut is_web_requesting, &tx_req);

                                        status = format_status(status.clone(),
                                                               &get_show_page_status_message(postid, page, &status_message));
                                    }
                                    break
                                },
                                Key::PageUp => {
                                    status = format_status(status, "↑");
                                    let bh = show.body_height();
                                    if show.scrollUp(bh) {
                                        print!("{}", termion::clear::All);
                                    }
                                    break
                                },
                                Key::PageDown => {
                                    status = format_status(status, "↓");
                                    let bh = show.body_height();
                                    if show.scrollDown(bh) {
                                        print!("{}", termion::clear::All);
                                    }
                                    break
                                },
                                Key::Up => {
                                    status = format_status(status, "↑");
                                    if show.scrollUp(2) {
                                        print!("{}", termion::clear::All);
                                    }
                                    break
                                },
                                Key::Down => {
                                    status = format_status(status, "↓");
                                    if show.scrollDown(2) {
                                        print!("{}", termion::clear::All);
                                    }
                                    break
                                },
                                Key::Backspace => {
                                    status = format_status(status, "B");
                                    state = Status::List;
                                    print!("{}", termion::clear::All);
                                    break
                                },
                                _ => {},
                            }
                        }
                }

            }
        }

    }
}

fn read_cache<P: AsRef<Path>, S: AsRef<Path>>(cache_path: P,
                                              file_name: S)
                                              -> Result<String, String> {
    let file_path = cache_path.as_ref().join(file_name);
    let mut file = try!(File::open(file_path).map_err(|e| e.to_string()));
    let mut contents = String::new();
    try!(file.read_to_string(&mut contents).map_err(|e| e.to_string()));
    Ok(contents)
}

fn write_cache<P: AsRef<Path>, S: AsRef<Path>>(cache_path: P,
                                               file_name: S,
                                               s: String)
                                               -> Result<(), String> {
    let file_path = cache_path.as_ref().join(file_name);
    try!(fs::create_dir_all(&cache_path).map_err(|e| e.to_string()));
    let mut file = try!(File::create(file_path).map_err(|e| e.to_string()));
    try!(file.write_all(&s.into_bytes()).map_err(|e| e.to_string()));
    Ok(())
}

fn get_posturl(postid: &String, page: usize) -> String {
    let base_url = "http://forum1.hkgolden.com/view.aspx";
    let posturl = format!("{base_url}?type=BW&message={postid}&page={page}",
                          base_url = base_url,
                          postid = postid,
                          page = page);
    posturl
}

fn get_topic_bw_url() -> String {
    let base_url = "http://forum1.hkgolden.com";
    let url = format!("{base_url}/topics_bw.htm", base_url = base_url);
    url
}

fn page_request(item: &ChannelItem,
                wr: &mut WebResource,
                ct: &CancellationTokenSource)
                -> ChannelItem {

    match item.extra.clone() {
        ChannelItemType::Show(extra) => {
            let html_path = format!("data/html/{postid}/", postid = extra.postid);
            let show_file_name = format!("show_{page}.html", page = extra.page);

            let postid = extra.postid.clone();

            let (from_cache, result) = match read_cache(&html_path, &show_file_name) {
                Ok(result) => (true, result),
                Err(_) => {
                    let posturl = get_posturl(&extra.postid, extra.page);
                    let result = wr.get(&posturl);
                    (false, result)
                }
            };

            if !from_cache {
                let result2 = result.clone();
                write_cache(&html_path, &show_file_name, result2);
            }

            let result_item = ChannelItem {
                extra: ChannelItemType::Show(ChannelShowItem { postid: postid, page: extra.page }),
                result: result,
            };
            result_item
        },
        ChannelItemType::Index(_) => {

            let time_format = |t: time::Tm| {
                match t.strftime("%Y%m%d%H%M") {
                    Ok(s) => s.to_string(),
                    Err(e) => panic!(e)
                }
            };

            let time = time_format(time::now());

            let html_path = format!("data/html/topics/");
            let file_name = format!("{time}.html", time = time);

            let (from_cache, result) = match read_cache(&html_path, &file_name) {
                Ok(result) => (true, result),
                Err(_) => {
                    let url = get_topic_bw_url();
                    let result = wr.get(&url);
                    (false, result)
                }
            };

            if !from_cache {
                let result2 = result.clone();
                write_cache(&html_path, &file_name, result2);
            }

            let result_item = ChannelItem {
                extra: ChannelItemType::Index(ChannelIndexItem { }),
                result: result,
            };
            result_item
        }
    }

}

fn list_page(is_web_requesting: &mut bool, tx_req: &Sender<ChannelItem>) -> String {

    let ci = ChannelItem {
        extra: ChannelItemType::Index(ChannelIndexItem { }),
        result: String::from(""),
    };

    let status_message = match tx_req.send(ci) {
        Ok(()) => {
            *is_web_requesting = true;
            "SOK".to_string()
        }
        Err(e) => format!("{}:{}", "SFAIL", e).to_string(),
    };

    status_message
}

fn show_page(postid: &String, page: usize, is_web_requesting: &mut bool, tx_req: &Sender<ChannelItem>) -> String {

    let ci = ChannelItem {
        extra: ChannelItemType::Show(ChannelShowItem { postid: postid.clone(), page: page }),
        result: String::from(""),
    };

    let status_message = match tx_req.send(ci) {
        Ok(()) => {
            *is_web_requesting = true;
            "SOK".to_string()
        }
        Err(e) => format!("{}:{}", "SFAIL", e).to_string(),
    };

    status_message
}

fn get_show_page_status_message(postid: &String, page: usize, status_message: &String) -> String {
    format!("[{}-{}:{}]", postid, page, status_message)
}

fn print_status(stdout: &mut termion::raw::RawTerminal<std::io::StdoutLock>, status: &str) {
    // for status bar only
    let h = terminal_size().unwrap().1;

    write!(stdout, "{}{}{}{}{}{}",
            termion::cursor::Goto(1, h),
            color::Fg(color::White),
            style::Bold,
            format!("{status}", status = status),
            style::Reset,
            termion::cursor::Hide);
}

fn format_status(status: String, s: &str) -> String {
    let w = terminal_size().unwrap().0 as usize;;
    if status.len() >= w {
        String::from(format!("{}{}", &"> ", s))
    } else {
        String::from(format!("{}{}", &status, s))
    }
}
<|endoftext|>


<commit_before>extern crate clap;
extern crate libc;
extern crate notify;

mod filter;

use std::ffi::CString;
use std::sync::mpsc::{channel, Receiver, RecvError};
use std::{env, thread, time};
use std::process::Command;

use clap::{App, Arg};
use libc::system;
use notify::{Event, RecommendedWatcher, Watcher};

use filter::Filter;

fn clear() {
    // TODO: determine better way to do this
    let clear_cmd;
    if cfg!(target_os = "windows") {
        clear_cmd = "cls";
    }
    else {
        clear_cmd = "clear";
    }

    let _ = Command::new(clear_cmd).status();
}

fn invoke(cmd: &str) {
    // TODO: determine a better way to get at system()
    let s = CString::new(cmd.clone()).unwrap();
    unsafe {
      system(s.as_ptr());
    }
}

fn wait(rx: &Receiver<Event>, filter: &Filter, verbose: bool) -> Result<Event, RecvError> {
    loop {
        // Block on initial notification
        let e = try!(rx.recv());
        if let Some(ref path) = e.path {
            if filter.is_excluded(&path) {
                if verbose {
                    println!("*** Ignoring {} due to filter", path.to_str().unwrap());
                }
                continue;
            }
        }

        // Accumulate subsequent events
        thread::sleep(time::Duration::from_millis(250));

        // Drain rx buffer and drop them
        loop {
            match rx.try_recv() {
                Ok(_) => continue,
                Err(_) => break,
            }
        }

        return Ok(e);
    }
}

fn main() {
    let args = App::new("watchexec")
        .version("0.9")
        .about("Runs a command when any of the specified files/directories are modified")
        .arg(Arg::with_name("path")
            .help("Path to watch for changes")
            .short("w")
            .long("watch")
            .number_of_values(1)
            .multiple(true)
            .takes_value(true)
            .default_value("."))
        .arg(Arg::with_name("command")
            .help("Command to run")
            .multiple(true)
            .required(true))
        .arg(Arg::with_name("extensions")
             .help("Comma-separated list of file extensions to watch (js,css,html)")
             .short("e")
             .long("exts")
             .takes_value(true))
        .arg(Arg::with_name("clear")
            .help("Clear screen before running command")
            .short("c")
            .long("clear"))
        .arg(Arg::with_name("verbose")
             .help("Print diagnostic messages")
             .short("v")
             .long("verbose"))
        .arg(Arg::with_name("filter")
             .help("Ignore paths that do not match the pattern")
             .short("f")
             .long("filter")
             .number_of_values(1)
             .multiple(true)
             .takes_value(true)
             .value_name("pattern"))
        .arg(Arg::with_name("ignore")
             .help("Ignore events from paths matching a pattern")
             .short("i")
             .long("ignore")
             .number_of_values(1)
             .multiple(true)
             .takes_value(true)
             .value_name("pattern"))
        .get_matches();

    let verbose = args.is_present("verbose");

    let cwd = env::current_dir().unwrap();
    let mut filter = Filter::new(&cwd);

    // Ignore python bytecode and dotted directories by default
    let default_filters = vec!["*.pyc", ".*/*"];
    for p in default_filters {
        filter.add_ignore(p).expect("bad default filter");
    }

    if let Some(extensions) = args.values_of("extensions") {
        for ext in extensions {
            filter.add_extension(ext).expect("bad extension");
        }
    }

    if let Some(filters) = args.values_of("filter") {
        for p in filters {
            filter.add_filter(p).expect("bad filter");
        }
    }

    if let Some(ignores) = args.values_of("ignore") {
        for i in ignores {
            filter.add_ignore(i).expect("bad ignore pattern");
        }
    }

    let (tx, rx) = channel();
    let mut watcher: RecommendedWatcher = Watcher::new(tx).expect("unable to create watcher");

    let paths = args.values_of("path").unwrap();
    for path in paths {
        watcher.watch(path).expect("unable to watch path");
    }

    let cmd_parts: Vec<&str> = args.values_of("command").unwrap().collect();
    let cmd = cmd_parts.join(" ");

    loop {
        let e = wait(&rx, &filter, verbose).expect("error when waiting for filesystem changes");
        if args.is_present("clear") {
            clear();
        }

        if verbose {
            println!("*** {:?}: {:?}", e.op, e.path);
        }

        if verbose {
            println!("*** Running: {}", cmd);
        }

        invoke(&cmd);
    }
}
<commit_msg>Improved nomenclature<commit_after>extern crate clap;
extern crate libc;
extern crate notify;

mod filter;

use std::ffi::CString;
use std::sync::mpsc::{channel, Receiver, RecvError};
use std::{env, thread, time};
use std::process::Command;

use clap::{App, Arg};
use libc::system;
use notify::{Event, RecommendedWatcher, Watcher};

use filter::Filter;

fn clear() {
    // TODO: determine better way to do this
    let clear_cmd;
    if cfg!(target_os = "windows") {
        clear_cmd = "cls";
    }
    else {
        clear_cmd = "clear";
    }

    let _ = Command::new(clear_cmd).status();
}

fn invoke(cmd: &str) {
    // TODO: determine a better way to get at system()
    let s = CString::new(cmd.clone()).unwrap();
    unsafe {
      system(s.as_ptr());
    }
}

fn wait(rx: &Receiver<Event>, filter: &Filter, verbose: bool) -> Result<Event, RecvError> {
    loop {
        // Block on initial notification
        let e = try!(rx.recv());
        if let Some(ref path) = e.path {
            if filter.is_excluded(&path) {
                if verbose {
                    println!("*** Ignoring {} due to filter", path.to_str().unwrap());
                }
                continue;
            }
        }

        // Accumulate subsequent events
        thread::sleep(time::Duration::from_millis(250));

        // Drain rx buffer and drop them
        loop {
            match rx.try_recv() {
                Ok(_) => continue,
                Err(_) => break,
            }
        }

        return Ok(e);
    }
}

fn main() {
    let args = App::new("watchexec")
        .version("0.9.0")
        .about("Execute commands when watched files change")
        .arg(Arg::with_name("path")
            .help("Path to watch")
            .short("w")
            .long("watch")
            .number_of_values(1)
            .multiple(true)
            .takes_value(true)
            .default_value("."))
        .arg(Arg::with_name("command")
            .help("Command to execute")
            .multiple(true)
            .required(true))
        .arg(Arg::with_name("extensions")
             .help("Comma-separated list of file extensions to watch (js,css,html)")
             .short("e")
             .long("exts")
             .takes_value(true))
        .arg(Arg::with_name("clear")
            .help("Clear screen before executing command")
            .short("c")
            .long("clear"))
        .arg(Arg::with_name("verbose")
             .help("Prints diagnostic messages")
             .short("v")
             .long("verbose"))
        .arg(Arg::with_name("filter")
             .help("Ignore all modifications except those matching the pattern")
             .short("f")
             .long("filter")
             .number_of_values(1)
             .multiple(true)
             .takes_value(true)
             .value_name("pattern"))
        .arg(Arg::with_name("ignore")
             .help("Ignore modifications to paths matching the pattern")
             .short("i")
             .long("ignore")
             .number_of_values(1)
             .multiple(true)
             .takes_value(true)
             .value_name("pattern"))
        .get_matches();

    let verbose = args.is_present("verbose");

    let cwd = env::current_dir().unwrap();
    let mut filter = Filter::new(&cwd);

    // Ignore python bytecode and dotted directories by default
    let default_filters = vec!["*.pyc", ".*/*"];
    for p in default_filters {
        filter.add_ignore(p).expect("bad default filter");
    }

    if let Some(extensions) = args.values_of("extensions") {
        for ext in extensions {
            filter.add_extension(ext).expect("bad extension");
        }
    }

    if let Some(filters) = args.values_of("filter") {
        for p in filters {
            filter.add_filter(p).expect("bad filter");
        }
    }

    if let Some(ignores) = args.values_of("ignore") {
        for i in ignores {
            filter.add_ignore(i).expect("bad ignore pattern");
        }
    }

    let (tx, rx) = channel();
    let mut watcher: RecommendedWatcher = Watcher::new(tx).expect("unable to create watcher");

    let paths = args.values_of("path").unwrap();
    for path in paths {
        watcher.watch(path).expect("unable to watch path");
    }

    let cmd_parts: Vec<&str> = args.values_of("command").unwrap().collect();
    let cmd = cmd_parts.join(" ");

    loop {
        let e = wait(&rx, &filter, verbose).expect("error when waiting for filesystem changes");
        if args.is_present("clear") {
            clear();
        }

        if verbose {
            println!("*** {:?}: {:?}", e.op, e.path);
        }

        if verbose {
            println!("*** Executing: {}", cmd);
        }

        invoke(&cmd);
    }
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate serde_derive;

use anyhow::Result;
use chrono::prelude::*;
use docopt::Docopt;

static USAGE: &str = "
Washing State Ferry Schedules

Usage:
  wsf [options] <from> <to>
  wsf (-h | --help)

  <from> and <to> are a prefix of the departing terminal and arriving
  terminal, respectively. For example 'wsf sea ba' is equivalent to
  'wsf Seattle \"Bainbridge Island\"'.

Options:
  -a --all      Show all times for today, not just remaining ones
  -h --help     Show this screen.
";

#[derive(Debug, Deserialize)]
struct Args {
    arg_from: String,
    arg_to: String,
    flag_all: bool,
}

async fn run() -> Result<()> {
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.deserialize())
        .unwrap_or_else(|e| e.exit());

    let from_in: &str = &args.arg_from.to_ascii_lowercase();
    let to_in: &str = &args.arg_to.to_ascii_lowercase();

    let mut s = wsf::Session::new("afddf683-37c5-4d1a-8486-f7004a16d86d").await;

    let from = s.find_terminal(&from_in).await?.TerminalID;
    let to = s.find_terminal(&to_in).await?.TerminalID;

    let tc = s.schedule(from, to).await?;

    let now = Local::now();
    for time in tc.Times.iter() {
        if args.flag_all || time.depart_time()? > now {
            println!(
                "{}\t{}\t{}\t{}",
                time.depart_time()?.time(),
                tc.DepartingTerminalName,
                tc.ArrivingTerminalName,
                time.VesselName
            );
        };
    }
    s.save_cache()?;
    Ok(())
}

#[tokio::main]
async fn main() -> Result<()> {
    human_panic::setup_panic!();

    let env = env_logger::Env::default().filter_or("WSF_LOG", "info");
    env_logger::init_from_env(env);

    Ok(run().await?)
}
<commit_msg>Fix spelling of Washington<commit_after>#[macro_use]
extern crate serde_derive;

use anyhow::Result;
use chrono::prelude::*;
use docopt::Docopt;

static USAGE: &str = "
Washington State Ferry Schedules

Usage:
  wsf [options] <from> <to>
  wsf (-h | --help)

  <from> and <to> are a prefix of the departing terminal and arriving
  terminal, respectively. For example 'wsf sea ba' is equivalent to
  'wsf Seattle \"Bainbridge Island\"'.

Options:
  -a --all      Show all times for today, not just remaining ones
  -h --help     Show this screen.
";

#[derive(Debug, Deserialize)]
struct Args {
    arg_from: String,
    arg_to: String,
    flag_all: bool,
}

async fn run() -> Result<()> {
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.deserialize())
        .unwrap_or_else(|e| e.exit());

    let from_in: &str = &args.arg_from.to_ascii_lowercase();
    let to_in: &str = &args.arg_to.to_ascii_lowercase();

    let mut s = wsf::Session::new("afddf683-37c5-4d1a-8486-f7004a16d86d").await;

    let from = s.find_terminal(&from_in).await?.TerminalID;
    let to = s.find_terminal(&to_in).await?.TerminalID;

    let tc = s.schedule(from, to).await?;

    let now = Local::now();
    for time in tc.Times.iter() {
        if args.flag_all || time.depart_time()? > now {
            println!(
                "{}\t{}\t{}\t{}",
                time.depart_time()?.time(),
                tc.DepartingTerminalName,
                tc.ArrivingTerminalName,
                time.VesselName
            );
        };
    }
    s.save_cache()?;
    Ok(())
}

#[tokio::main]
async fn main() -> Result<()> {
    human_panic::setup_panic!();

    let env = env_logger::Env::default().filter_or("WSF_LOG", "info");
    env_logger::init_from_env(env);

    Ok(run().await?)
}
<|endoftext|>


<commit_before>#![desc = "Constraint Satisfaction in Rust"]
#![license = "MIT"]
#![crate_id = "csar#0.1"]
// Segfaults as lib (???), so we stay as bin for now...
//#[crate_type = "lib"];
#![feature(managed_boxes)]

use std::cell::RefCell;

#[allow(dead_code)]
fn main() {
   return;
}

/// Representation of finite domains as a list of intervals, maintaining
/// min and max for easy/quick access
struct Domain {
   min: int,
   max: int,
   intervals: Vec<int>
}

pub trait Propagator : ToStr {
   fn propagate(&mut self) -> Vec<Event>;
   fn register(&self);
   fn unregister(&self);
}

pub struct FDVar {
   name: String,
   dom: Domain,
   waitingOnMin: Vec<Propagator>,
   waitingOnMax: Vec<Propagator>,
   waitingOnIns: Vec<Propagator>
}

pub enum Event {
   Min,
   Max,
   Ins
}

impl Domain {
   /// Domain created with initial bounds
   fn new(min: int, max: int) -> Domain {
      Domain {
         min: min,
         max: max,
         intervals: vec![(min, max)]
      }
   }

   fn set_min(&mut self, min: int) {
      if min < self.min { return; }
      if min > self.max { return; } // TODO failure via conditions
      loop {
         match self.intervals[0] {
            (x, _) if min < x => { self.min = x; break; },
            (_, y) if min > y => { self.intervals.shift(); },
            (_, y) => { self.min = min; self.intervals[0] = (min, y); break }
         }
      }
   }

   fn set_max(&mut self, max: int) {
      if max > self.max { return; }
      if max < self.min { return; } // TODO failure via conditions
      loop {
         match self.intervals[self.intervals.len() - 1] {
            (_, y) if max > y => { self.max = y; break; },
            (x, _) if max < x => { self.intervals.pop(); },
            (x, _) => {
               self.max = max;
               self.intervals[self.intervals.len() - 1] = (x, max);
               break
            }
         }
      }
   }

   fn remove(&mut self, val: int) {
      if val > self.max || val < self.min { return; }
      let mut down = 0;
      let mut up = self.intervals.len();
      let mut test;
      loop {
         test = down + (up - down) / 2;
         match self.intervals[test] {
            (x, _) if val < x => {
               if test > down {
                  up = test;
               } else {
                  break;
               }
            },
            (_, y) if val > y => {
               if test < up - 1 {
                  down = test + 1;
               } else {
                  break;
               }
            },
            (x, y) if val == x && val == y => {
               self.intervals.remove(test);
               break;
            },
            (x, y) if val == x => {
               self.intervals[test] = (x + 1, y);
               break;
            },
            (x, y) if val == y => {
               self.intervals[test] = (x, y - 1);
               break;
            },
            (x, y) => {
               self.intervals[test] = (x, val - 1);
               self.intervals.insert(test + 1, (val + 1, y));
               break;
            }
         }
      }
      if test == 0 {
         match self.intervals[test] {
            (x, _) => self.min = x
         }
      } else if test == self.intervals.len() - 1 {
         match self.intervals[test] {
            (_, y) => self.max = y
         }
      }
   }
}

impl ToStr for Domain {
   fn to_str(&self) -> String {
      let mut s = "(" + self.min.to_str() + ", " + self.max.to_str() + ") [";
      for &(min, max) in self.intervals.iter() {
         s = s + min.to_str() + ".." + max.to_str() + ", ";
      }
      return s + "]";
   }
}

impl FDVar {
   pub fn new(min: int, max: int, name: String) -> FDVar {
      assert!(min <= max);
      FDVar {
         name: name,
         dom: Domain::new(min, max),
         waitingOnMin: vec![],
         waitingOnMax: vec![],
         waitingOnIns: vec![]
      }
   }

   pub fn min(&self) -> int {
      self.dom.min
   }

   pub fn max(&self) -> int {
      self.dom.max
   }

   fn set_min(&mut self, v: int) -> Vec<Event> {
      if v > self.min() {
         self.dom.set_min(v);
         if self.is_instanciated() {
            [Min, Ins]
         } else {
            [Min]
         }
      } else {
         []
      }
   }

   fn set_max(&mut self, v: int) -> Vec<Event> {
      if v < self.max() {
         self.dom.set_max(v);
         if self.is_instanciated() {
            [Max, Ins]
         } else {
            [Max]
         }
      } else {
         []
      }
   }

   fn is_instanciated(&self) -> bool {
      self.min() == self.max()
   }

   fn add_waiting_min(&self, p: &Propagator) {}

   fn add_waiting_max(&self, p: &Propagator) {}

   fn add_waiting_ins(&self, p: &Propagator) {}

   fn del_waiting_min(&self, p: &Propagator) {}

   fn del_waiting_max(&self, p: &Propagator) {}

   fn del_waiting_ins(&self, p: &Propagator) {}
}

impl ToStr for FDVar {
   fn to_str(&self) -> String {
      self.name + " (" + self.dom.to_str() + ")"
   }
}

pub struct LtXYx {
   x: @RefCell<FDVar>,
   y: @RefCell<FDVar>
}

impl LtXYx {
   pub fn new(x: @RefCell<FDVar>, y: @RefCell<FDVar>) -> LtXYx {
      let mut this = LtXYx { x: x, y: y };
      this.register();
      this.propagate();
      this
   }
}

impl Propagator for LtXYx {
   fn register(&self) {
      let mut y = self.y.borrow_mut();
      y.get().add_waiting_max(self);
   }

   fn unregister(&self) {
      let mut y = self.y.borrow_mut();
      y.get().del_waiting_max(self);
   }

   fn propagate(&mut self) -> Vec<Event> {
      let mut xx = self.x.borrow_mut();
      let x = xx.get();
      let mut yy = self.y.borrow_mut();
      let y = yy.get();
      if x.max() < y.min() {
         // entailed
         self.unregister();
         []
      } else if x.max() > y.max() - 1 {
         //if y.is_instanciated() {
         //   self.unregister();
         //}
         x.set_max(y.max() - 1)
      } else {
         []
      }
   }
}
impl ToStr for LtXYx {
   fn to_str(&self) -> String {
      "x < y"
     // format!("{} < {}", self.x.to_str(), self.y.to_str())
   }
}

#[cfg(test)]
mod tests;
<commit_msg>indent and stuff<commit_after>#![desc = "Constraint Satisfaction in Rust"]
#![license = "MIT"]
#![crate_id = "csar#0.1"]
// Segfaults as lib (???), so we stay as bin for now...
//#[crate_type = "lib"];
#![feature(managed_boxes)]

use std::cell::RefCell;

#[allow(dead_code)]
fn main() {
    return;
}

/// Representation of finite domains as a list of intervals, maintaining
/// min and max for easy/quick access
struct Domain {
    min: int,
    max: int,
    intervals: Vec<int>
}

pub trait Propagator : ToStr {
    fn propagate(&mut self) -> Vec<Event>;
    fn register(&self);
    fn unregister(&self);
}

pub struct FDVar {
    name: String,
    dom: Domain,
    waitingOnMin: Vec<Propagator>,
    waitingOnMax: Vec<Propagator>,
    waitingOnIns: Vec<Propagator>
}

pub enum Event {
    Min,
    Max,
    Ins
}

impl Domain {
    /// Domain created with initial bounds
    fn new(min: int, max: int) -> Domain {
        Domain {
            min: min,
            max: max,
            intervals: vec![(min, max)]
        }
    }

    fn set_min(&mut self, min: int) {
        if min < self.min { return; }
        if min > self.max { return; } // TODO failure via conditions
        loop {
            match self.intervals[0] {
                // note that the breaks are for the loop, not the matching
                (x, _) if min < x => { self.min = x; break; },
                (_, y) if min > y => { self.intervals.shift(); },
                (_, y) => { self.min = min; self.intervals[0] = (min, y); break }
            }
        }
    }

    fn set_max(&mut self, max: int) {
        if max > self.max { return; }
        if max < self.min { return; } // TODO failure via conditions
        loop {
            match self.intervals[self.intervals.len() - 1] {
                (_, y) if max > y => { self.max = y; break; },
                (x, _) if max < x => { self.intervals.pop(); },
                (x, _) => {
                    self.max = max;
                    self.intervals[self.intervals.len() - 1] = (x, max);
                    break
                }
            }
        }
    }

    fn remove(&mut self, val: int) {
        if val > self.max || val < self.min { return; }
        let mut down = 0;
        let mut up = self.intervals.len();
        let mut test;
        loop {
            test = down + (up - down) / 2;
            match self.intervals[test] {
                (x, _) if val < x => {
                    if test > down {
                        up = test;
                    } else {
                        break;
                    }
                },
                (_, y) if val > y => {
                    if test < up - 1 {
                        down = test + 1;
                    } else {
                        break;
                    }
                },
                (x, y) if val == x && val == y => {
                    self.intervals.remove(test);
                    break;
                },
                (x, y) if val == x => {
                    self.intervals[test] = (x + 1, y);
                    break;
                },
                (x, y) if val == y => {
                    self.intervals[test] = (x, y - 1);
                    break;
                },
                (x, y) => {
                    self.intervals[test] = (x, val - 1);
                    self.intervals.insert(test + 1, (val + 1, y));
                    break;
                }
            }
        }
        if test == 0 {
            match self.intervals[test] {
                (x, _) => self.min = x
            }
        } else if test == self.intervals.len() - 1 {
            match self.intervals[test] {
                (_, y) => self.max = y
            }
        }
    }
}

impl ToStr for Domain {
    fn to_str(&self) -> String {
        let mut s = "(" + self.min.to_str() + ", " + self.max.to_str() + ") [";
        for &(min, max) in self.intervals.iter() {
            s = s + min.to_str() + ".." + max.to_str() + ", ";
        }
        return s + "]";
    }
}

impl FDVar {
    pub fn new(min: int, max: int, name: String) -> FDVar {
        assert!(min <= max);
        FDVar {
            name: name,
            dom: Domain::new(min, max),
            waitingOnMin: vec![],
            waitingOnMax: vec![],
            waitingOnIns: vec![]
        }
    }

    pub fn min(&self) -> int {
        self.dom.min
    }

    pub fn max(&self) -> int {
        self.dom.max
    }

    fn set_min(&mut self, v: int) -> Vec<Event> {
        if v > self.min() {
            self.dom.set_min(v);
            if self.is_instanciated() {
                [Min, Ins]
            } else {
                [Min]
            }
        } else {
            []
        }
    }

    fn set_max(&mut self, v: int) -> Vec<Event> {
        if v < self.max() {
            self.dom.set_max(v);
            if self.is_instanciated() {
                [Max, Ins]
            } else {
                [Max]
            }
        } else {
            []
        }
    }

    fn is_instanciated(&self) -> bool {
        self.min() == self.max()
    }

    fn add_waiting_min(&self, p: &Propagator) {}

    fn add_waiting_max(&self, p: &Propagator) {}

    fn add_waiting_ins(&self, p: &Propagator) {}

    fn del_waiting_min(&self, p: &Propagator) {}

    fn del_waiting_max(&self, p: &Propagator) {}

    fn del_waiting_ins(&self, p: &Propagator) {}
}

impl ToStr for FDVar {
    fn to_str(&self) -> String {
        self.name + " (" + self.dom.to_str() + ")"
    }
}

pub struct LtXYx {
    x: @RefCell<FDVar>,
    y: @RefCell<FDVar>
}

impl LtXYx {
    pub fn new(x: @RefCell<FDVar>, y: @RefCell<FDVar>) -> LtXYx {
        let mut this = LtXYx { x: x, y: y };
        this.register();
        this.propagate();
        this
    }
}

impl Propagator for LtXYx {
    fn register(&self) {
        let mut y = self.y.borrow_mut();
        y.get().add_waiting_max(self);
    }

    fn unregister(&self) {
        let mut y = self.y.borrow_mut();
        y.get().del_waiting_max(self);
    }

    fn propagate(&mut self) -> Vec<Event> {
        let mut xx = self.x.borrow_mut();
        let x = xx.get();
        let mut yy = self.y.borrow_mut();
        let y = yy.get();
        if x.max() < y.min() {
            // entailed
            self.unregister();
            []
        } else if x.max() > y.max() - 1 {
            //if y.is_instanciated() {
            //   self.unregister();
            //}
            x.set_max(y.max() - 1)
        } else {
            []
        }
    }
}
impl ToStr for LtXYx {
    fn to_str(&self) -> String {
        "x < y"
            // format!("{} < {}", self.x.to_str(), self.y.to_str())
    }
}

#[cfg(test)]
mod tests;
<|endoftext|>


<commit_before>#![feature(slice_patterns)]
#[macro_use]
extern crate nom;

use nom::{IResult, eof};
use std::collections::BTreeMap;
use std::fs;
use std::io;
use std::io::{Read, Write};
use std::path::Path;
use std::thread;

#[derive(Debug)]
#[derive(PartialEq)]
#[derive(Eq)]
enum Op {
    Add(u8),
    Mov(isize),
    In,
    Out,
    Loop(OpStream),

    // extra optimized ops
    Transfer(u8, Vec<(isize, u8)>),
}

use Op::*;

#[derive(Debug)]
#[derive(PartialEq)]
#[derive(Eq)]
struct OpStream {
    ops: Vec<Op>,
}

impl OpStream {
    fn optimize(&mut self) {
        let mut i = 0;
        while i < self.ops.len() {
            match &self.ops[i..] {
                [Add(a), Add(b), ..] => {
                    self.ops[i] = Add(a.wrapping_add(b));
                    self.ops.remove(i + 1);
                }
                [Mov(a), Mov(b), ..] => {
                    self.ops[i] = Mov(a + b);
                    self.ops.remove(i + 1);
                }
                [Add(0), ..] | [Mov(0), ..] => {
                    self.ops.remove(i);
                    if i > 0 {
                        i -= 1;
                    }
                }
                [Loop(_), ..] => {
                    let mut maybe_new_op = None;

                    if let &mut Loop(ref mut stream) = &mut self.ops[i] {
                        stream.optimize();
                        maybe_new_op = stream.find_alternative();
                    }

                    if let Some(new_op) = maybe_new_op {
                        self.ops[i] = new_op;
                    }

                    i += 1
                }
                _ => i += 1,
            }
        }
    }

    fn find_alternative(&self) -> Option<Op> {
        let mut map: BTreeMap<isize, u8> = BTreeMap::new();
        let mut rel_index = 0;

        for op in &self.ops[..] {
            match op {
                &Add(x) => {
                    let new_val = map.get(&rel_index).unwrap_or(&0).wrapping_add(x);
                    map.insert(rel_index, new_val);
                }
                &Mov(x) => {
                    rel_index += x;
                }
                _ => {
                    return None;
                }
            }
        }

        if rel_index != 0 {
            return None;
        }

        Some(Transfer(map.remove(&0).unwrap_or(0), map.into_iter().collect()))
    }

    fn get(&self) -> &[Op] {
        &self.ops[..]
    }
}

static ZERO: u8 = 0;

struct State {
    index: usize,
    memory: Vec<u8>,
}

impl State {
    fn new() -> State {
        State {
            index: 0,
            memory: vec![],
        }
    }

    fn rel_index(&self, relative: isize) -> usize {
        (self.index as isize + relative) as usize
    }

    fn step(&mut self, op: &Op) -> bool {
        match *op {
            Add(i) => {
                self[0] = self[0].wrapping_add(i);
            }
            Mov(n) => {
                self.index = self.rel_index(n);
            }
            In => {
                let mut c = [0u8];
                if io::stdin().read(&mut c).unwrap() == 0 {
                    return false;
                }
                self[0] = c[0];
            }
            Out => {
                io::stdout().write(&[self[0]]).unwrap();
            }
            Loop(ref ops) => {
                while self[0] != 0 {
                    if !self.run(ops.get()) {
                        return false;
                    }
                }
            }
            Transfer(d, ref map) => {
                if self[0] == 0 {
                    return true
                }

                let mut _v = self[0];
                let mut _n = 0;

                while _v != 0 {
                    _v = _v.wrapping_add(d);
                    if _v == self[0] {
                        // stalled: the current transfer will never complete
                        loop {
                            thread::park()
                        }
                    }
                    _n += 1
                }

                self[0] = 0;
                for &(k, v) in &map[..] {
                    self[k] = self[k].wrapping_add(v.wrapping_mul(_n));
                }
            }
        }
        return true;
    }

    fn run(&mut self, ops: &[Op]) -> bool {
        for op in ops {
            if !self.step(op) {
                return false;
            }
        }
        return true;
    }
}

impl std::ops::Index<isize> for State {
    type Output = u8;
    fn index(&self, _index: isize) -> &u8 {
        let idx = self.rel_index(_index);
        if idx >= self.memory.len() {
            &ZERO
        } else {
            &self.memory[idx]
        }
    }
}

impl std::ops::IndexMut<isize> for State {
    fn index_mut(&mut self, _index: isize) -> &mut u8 {
        let idx = self.rel_index(_index);
        if idx >= self.memory.len() {
            self.memory.resize(idx * 2 + 1, 0);
        }
        &mut self.memory[idx]
    }
}

fn main() {
    let filenames: Vec<String> = std::iter::FromIterator::from_iter(std::env::args());

    for filename in &filenames[1..] {
        match reader(&Path::new(filename))
                  .and_then(|mut reader| {
                      let mut buffer = Vec::new();
                      reader.read_to_end(&mut buffer).map(|_| buffer)
                  })
                  .map_err(|e| format!("{}", e))
                  .and_then(|buffer| {
                      match bf_parse_file(&buffer[..]) {
                          IResult::Done(_, o) => Ok(o),
                          IResult::Error(e) => Err(format!("Parsing error: {:?}", e)),
                          IResult::Incomplete(m) => Err(format!("Incomplete file: {:?}", m)),
                      }
                  }) {
            Ok(ops) => {
                let mut opstream = OpStream { ops: ops };
                opstream.optimize();
                State::new().run(opstream.get());
            }
            Err(e) => {
                writeln!(&mut io::stderr(),
                         "Error while processing {}: {}",
                         filename,
                         e)
                    .unwrap();
            }
        };
    }
}

named!(bf_parse_file<&[u8], Vec<Op> >, chain!(ops: bf_parse ~ eof, || ops));
named!(bf_parse<&[u8], Vec<Op> >,
    many0!(
        chain!(
            opt!(is_not!("+-<>.,[]")) ~
            op: alt!(
                chain!(tag!("+"), || Add(0x01)) |
                chain!(tag!("-"), || Add(0xff)) |
                chain!(tag!(">"), || Mov(1)) |
                chain!(tag!("<"), || Mov(-1)) |
                chain!(tag!("."), || Out) |
                chain!(tag!(","), || In) |
                chain!(subops: delimited!(tag!("["), bf_parse, tag!("]")), || Loop(OpStream { ops: subops }))
            ) ~
            opt!(is_not!("+-<>.,[]")), || op)
        )
    );

fn reader(path: &Path) -> Result<io::BufReader<fs::File>, io::Error> {
    fs::File::open(path).map(|f| io::BufReader::new(f))
}

#[cfg(test)]
mod tests {
    use nom::IResult::Done;
    use super::{bf_parse_file, State, OpStream};
    use super::Op::*;

    #[test]
    fn test_state_index() {
        let mut state = State {
            index: 0,
            memory: vec![23, 0, 0, 0, 0, 42],
        };
        assert_eq!(23, state[0]);
        state.index = 5;
        assert_eq!(42, state[0]);
    }

    #[test]
    fn test_state_index_mut() {
        let mut state = State::new();
        state[0] = 23;
        assert_eq!(23, state.memory[state.index]);
        state.index = 5;
        state[0] = 42;
        assert_eq!(42, state.memory[state.index]);
    }

    #[test]
    fn test_state_step_add() {
        let mut state = State::new();
        state.step(&Add(23));
        assert_eq!(23, state[0]);
        state.step(&Add(42));
        assert_eq!(65, state[0]);
        state.step(&Add(190));
        assert_eq!(255, state[0]);
    }

    #[test]
    fn test_state_step_mov() {
        let mut state = State::new();
        state.step(&Mov(1));
        assert_eq!(1, state.index);
        state.step(&Mov(42));
        assert_eq!(43, state.index);
        state.step(&Mov(-1));
        assert_eq!(42, state.index);
    }

    #[test]
    fn test_state_step_loop() {
        let mut state = State::new();
        state[0] = 23;
        state.step(&Loop(OpStream { ops: vec![Add(1)] }));
        assert_eq!(0, state[0]);
    }

    #[test]
    fn test_state_step_transfer() {
        let mut state = State::new();
        state[0] = 15;
        state[1] = 7;
        state.step(&Transfer(5, vec![(1, 2)]));
        assert_eq!(0, state[0]);
        assert_eq!(1, state[1]);
    }

    #[test]
    fn test_opstream_optimize() {
        let mut opstream = OpStream {
            ops: vec![Mov(1),
                      Mov(1),
                      Add(0x01),
                      Add(0x0ff),
                      Add(0x0ff),
                      Mov(1),
                      Mov(-1),
                      Loop(OpStream { ops: vec![Mov(2), Mov(3)] })],
        };
        opstream.optimize();

        assert_eq!(opstream,
                   OpStream { ops: vec![Mov(2), Add(0xff), Loop(OpStream { ops: vec![Mov(5)] })] });
    }

    #[test]
    fn test_opstream_optimize_transfer() {
        let mut opstream = OpStream {
            ops: vec![Loop(OpStream { ops: vec![Add(0x01), Mov(3), Add(0xff), Mov(-3)] })],
        };
        opstream.optimize();

        assert_eq!(opstream,
                   OpStream { ops: vec![Transfer(1, vec![(3, 255)])] });
    }

    #[test]
    fn test_parse() {
        let input = b"+-[+.,]+";
        assert_eq!(bf_parse_file(&input[..]),
                   Done(&b""[..],
                        vec![Add(0x01),
                             Add(0xff),
                             Loop(OpStream { ops: vec![Add(1), Out, In] }),
                             Add(0x01)]));
    }
}
<commit_msg>refactor: remove reader function<commit_after>#![feature(slice_patterns)]
#[macro_use]
extern crate nom;

use nom::{IResult, eof};
use std::collections::BTreeMap;
use std::fs;
use std::io;
use std::io::{Read, Write};
use std::thread;

#[derive(Debug)]
#[derive(PartialEq)]
#[derive(Eq)]
enum Op {
    Add(u8),
    Mov(isize),
    In,
    Out,
    Loop(OpStream),

    // extra optimized ops
    Transfer(u8, Vec<(isize, u8)>),
}

use Op::*;

#[derive(Debug)]
#[derive(PartialEq)]
#[derive(Eq)]
struct OpStream {
    ops: Vec<Op>,
}

impl OpStream {
    fn optimize(&mut self) {
        let mut i = 0;
        while i < self.ops.len() {
            match &self.ops[i..] {
                [Add(a), Add(b), ..] => {
                    self.ops[i] = Add(a.wrapping_add(b));
                    self.ops.remove(i + 1);
                }
                [Mov(a), Mov(b), ..] => {
                    self.ops[i] = Mov(a + b);
                    self.ops.remove(i + 1);
                }
                [Add(0), ..] | [Mov(0), ..] => {
                    self.ops.remove(i);
                    if i > 0 {
                        i -= 1;
                    }
                }
                [Loop(_), ..] => {
                    let mut maybe_new_op = None;

                    if let &mut Loop(ref mut stream) = &mut self.ops[i] {
                        stream.optimize();
                        maybe_new_op = stream.find_alternative();
                    }

                    if let Some(new_op) = maybe_new_op {
                        self.ops[i] = new_op;
                    }

                    i += 1
                }
                _ => i += 1,
            }
        }
    }

    fn find_alternative(&self) -> Option<Op> {
        let mut map: BTreeMap<isize, u8> = BTreeMap::new();
        let mut rel_index = 0;

        for op in &self.ops[..] {
            match op {
                &Add(x) => {
                    let new_val = map.get(&rel_index).unwrap_or(&0).wrapping_add(x);
                    map.insert(rel_index, new_val);
                }
                &Mov(x) => {
                    rel_index += x;
                }
                _ => {
                    return None;
                }
            }
        }

        if rel_index != 0 {
            return None;
        }

        Some(Transfer(map.remove(&0).unwrap_or(0), map.into_iter().collect()))
    }

    fn get(&self) -> &[Op] {
        &self.ops[..]
    }
}

static ZERO: u8 = 0;

struct State {
    index: usize,
    memory: Vec<u8>,
}

impl State {
    fn new() -> State {
        State {
            index: 0,
            memory: vec![],
        }
    }

    fn rel_index(&self, relative: isize) -> usize {
        (self.index as isize + relative) as usize
    }

    fn step(&mut self, op: &Op) -> bool {
        match *op {
            Add(i) => {
                self[0] = self[0].wrapping_add(i);
            }
            Mov(n) => {
                self.index = self.rel_index(n);
            }
            In => {
                let mut c = [0u8];
                if io::stdin().read(&mut c).unwrap() == 0 {
                    return false;
                }
                self[0] = c[0];
            }
            Out => {
                io::stdout().write(&[self[0]]).unwrap();
            }
            Loop(ref ops) => {
                while self[0] != 0 {
                    if !self.run(ops.get()) {
                        return false;
                    }
                }
            }
            Transfer(d, ref map) => {
                if self[0] == 0 {
                    return true
                }

                let mut _v = self[0];
                let mut _n = 0;

                while _v != 0 {
                    _v = _v.wrapping_add(d);
                    if _v == self[0] {
                        // stalled: the current transfer will never complete
                        loop {
                            thread::park()
                        }
                    }
                    _n += 1
                }

                self[0] = 0;
                for &(k, v) in &map[..] {
                    self[k] = self[k].wrapping_add(v.wrapping_mul(_n));
                }
            }
        }
        return true;
    }

    fn run(&mut self, ops: &[Op]) -> bool {
        for op in ops {
            if !self.step(op) {
                return false;
            }
        }
        return true;
    }
}

impl std::ops::Index<isize> for State {
    type Output = u8;
    fn index(&self, _index: isize) -> &u8 {
        let idx = self.rel_index(_index);
        if idx >= self.memory.len() {
            &ZERO
        } else {
            &self.memory[idx]
        }
    }
}

impl std::ops::IndexMut<isize> for State {
    fn index_mut(&mut self, _index: isize) -> &mut u8 {
        let idx = self.rel_index(_index);
        if idx >= self.memory.len() {
            self.memory.resize(idx * 2 + 1, 0);
        }
        &mut self.memory[idx]
    }
}

fn main() {
    let filenames: Vec<String> = std::iter::FromIterator::from_iter(std::env::args());

    for filename in &filenames[1..] {
        match fs::File::open(filename)
                  .map(|f| io::BufReader::new(f))
                  .and_then(|mut reader| {
                      let mut buffer = Vec::new();
                      reader.read_to_end(&mut buffer).map(|_| buffer)
                  })
                  .map_err(|e| format!("{}", e))
                  .and_then(|buffer| {
                      match bf_parse_file(&buffer[..]) {
                          IResult::Done(_, o) => Ok(o),
                          IResult::Error(e) => Err(format!("Parsing error: {:?}", e)),
                          IResult::Incomplete(m) => Err(format!("Incomplete file: {:?}", m)),
                      }
                  }) {
            Ok(ops) => {
                let mut opstream = OpStream { ops: ops };
                opstream.optimize();
                State::new().run(opstream.get());
            }
            Err(e) => {
                writeln!(&mut io::stderr(),
                         "Error while processing {}: {}",
                         filename,
                         e)
                    .unwrap();
            }
        };
    }
}

named!(bf_parse_file<&[u8], Vec<Op> >, chain!(ops: bf_parse ~ eof, || ops));
named!(bf_parse<&[u8], Vec<Op> >,
    many0!(
        chain!(
            opt!(is_not!("+-<>.,[]")) ~
            op: alt!(
                chain!(tag!("+"), || Add(0x01)) |
                chain!(tag!("-"), || Add(0xff)) |
                chain!(tag!(">"), || Mov(1)) |
                chain!(tag!("<"), || Mov(-1)) |
                chain!(tag!("."), || Out) |
                chain!(tag!(","), || In) |
                chain!(subops: delimited!(tag!("["), bf_parse, tag!("]")), || Loop(OpStream { ops: subops }))
            ) ~
            opt!(is_not!("+-<>.,[]")), || op)
        )
    );

#[cfg(test)]
mod tests {
    use nom::IResult::Done;
    use super::{bf_parse_file, State, OpStream};
    use super::Op::*;

    #[test]
    fn test_state_index() {
        let mut state = State {
            index: 0,
            memory: vec![23, 0, 0, 0, 0, 42],
        };
        assert_eq!(23, state[0]);
        state.index = 5;
        assert_eq!(42, state[0]);
    }

    #[test]
    fn test_state_index_mut() {
        let mut state = State::new();
        state[0] = 23;
        assert_eq!(23, state.memory[state.index]);
        state.index = 5;
        state[0] = 42;
        assert_eq!(42, state.memory[state.index]);
    }

    #[test]
    fn test_state_step_add() {
        let mut state = State::new();
        state.step(&Add(23));
        assert_eq!(23, state[0]);
        state.step(&Add(42));
        assert_eq!(65, state[0]);
        state.step(&Add(190));
        assert_eq!(255, state[0]);
    }

    #[test]
    fn test_state_step_mov() {
        let mut state = State::new();
        state.step(&Mov(1));
        assert_eq!(1, state.index);
        state.step(&Mov(42));
        assert_eq!(43, state.index);
        state.step(&Mov(-1));
        assert_eq!(42, state.index);
    }

    #[test]
    fn test_state_step_loop() {
        let mut state = State::new();
        state[0] = 23;
        state.step(&Loop(OpStream { ops: vec![Add(1)] }));
        assert_eq!(0, state[0]);
    }

    #[test]
    fn test_state_step_transfer() {
        let mut state = State::new();
        state[0] = 15;
        state[1] = 7;
        state.step(&Transfer(5, vec![(1, 2)]));
        assert_eq!(0, state[0]);
        assert_eq!(1, state[1]);
    }

    #[test]
    fn test_opstream_optimize() {
        let mut opstream = OpStream {
            ops: vec![Mov(1),
                      Mov(1),
                      Add(0x01),
                      Add(0x0ff),
                      Add(0x0ff),
                      Mov(1),
                      Mov(-1),
                      Loop(OpStream { ops: vec![Mov(2), Mov(3)] })],
        };
        opstream.optimize();

        assert_eq!(opstream,
                   OpStream { ops: vec![Mov(2), Add(0xff), Loop(OpStream { ops: vec![Mov(5)] })] });
    }

    #[test]
    fn test_opstream_optimize_transfer() {
        let mut opstream = OpStream {
            ops: vec![Loop(OpStream { ops: vec![Add(0x01), Mov(3), Add(0xff), Mov(-3)] })],
        };
        opstream.optimize();

        assert_eq!(opstream,
                   OpStream { ops: vec![Transfer(1, vec![(3, 255)])] });
    }

    #[test]
    fn test_parse() {
        let input = b"+-[+.,]+";
        assert_eq!(bf_parse_file(&input[..]),
                   Done(&b""[..],
                        vec![Add(0x01),
                             Add(0xff),
                             Loop(OpStream { ops: vec![Add(1), Out, In] }),
                             Add(0x01)]));
    }
}
<|endoftext|>


<commit_before>use std::io::{stdin, BufReader, BufRead};
use std::collections::HashMap;

#[derive(PartialEq, Debug, Clone)]
enum Value {
    IntVal(i64),
    FloatVal(f64),
    StringVal(String),
    CharVar(char),
    BoolVal(bool),
}

#[derive(PartialEq, Debug, Clone)]
enum Command {
    Push(Value),
    Pop,
    Dup,
    Plus,
    Minus,
    Times,
    Divide,
    Mod,
    Print,
    MkType,
    New,
    GetRef,
    SetRef,
    SysGC,
    SysMemstats,
}

fn read_lines() -> Vec<String> {
    BufReader::new(stdin())
        .lines()
        .map(|l| l.unwrap().trim().to_owned())
        .collect()
}

fn parse_err<a>(msg: &str) -> Result<a, String> {
    Err(msg.to_owned())
}

fn parse_value(parts: &Vec<&str>) -> Result<Value, String> {
    parse_err("not implemented")
}

fn parse_command(line: String) -> Result<Command, String> {
    let parts: Vec<&str> = line.split_whitespace().collect();
    if parts.len() < 1 {
        return parse_err("empty command?");
    }
    match parts[0] {
        "push" => {
            if parts.len() > 2 {
                Ok(Command::Push(try!(parse_value(&parts))))
            } else {
                parse_err("invalid push statement")
            }
        }
        "pop" => Ok(Command::Pop),
        "dup" => Ok(Command::Dup),
        "plus" => Ok(Command::Plus),
        "minus" => Ok(Command::Minus),
        "times" => Ok(Command::Times),
        "divide" => Ok(Command::Divide),
        "mod" => Ok(Command::Mod),
        "print" => Ok(Command::Print),
        "mktype" => Ok(Command::MkType),
        "new" => Ok(Command::New),
        "getref" => Ok(Command::GetRef),
        "setref" => Ok(Command::SetRef),
        "sysgc" => Ok(Command::SysGC),
        "sysmemstats" => Ok(Command::SysMemstats),
        _ => Err("invalid command: ".to_owned() + line.as_str()),
    }
}

fn build(lines: Vec<String>) -> Result<(Vec<Command>, HashMap<String, usize>), String> {
    let mut commands = vec![];
    let mut names = HashMap::new();

    for line in lines {
        if line.len() == 0 || line.starts_with("#") {
            continue;
        } else if line.starts_with("!") {
            names.insert(line.trim_left_matches("!").to_owned(), commands.len());
        } else {
            commands.push(try!(parse_command(line)))
        }
    }

    Ok((commands, names))
}

fn main() {
    let lines = read_lines();
    println!("{:?}", lines);
    println!("{:?}", build(lines));
}
<commit_msg>Finish up parsing<commit_after>use std::io::{stdin, BufReader, BufRead};
use std::collections::HashMap;

#[derive(PartialEq, Debug, Clone)]
enum Value {
    I(i64),
    F(f64),
    S(String),
    C(char),
    B(bool),
    R(i32, i32),
}

#[derive(PartialEq, Debug, Clone)]
enum Command {
    Push(Value),
    Pop,
    Dup,
    Plus,
    Minus,
    Times,
    Divide,
    Mod,
    Print,
    MkType,
    New,
    GetRef,
    SetRef,
    SysGC,
    SysMemstats,
}

fn read_lines() -> Vec<String> {
    BufReader::new(stdin())
        .lines()
        .map(|l| l.unwrap().trim().to_owned())
        .collect()
}

fn parse_err<A>(msg: &str) -> Result<A, String> {
    Err(msg.to_owned())
}

fn parse_value(parts: &Vec<&str>) -> Result<Value, String> {
    if parts.len() <= 2 {
        return parse_err("push stmt too short");
    }
    match parts[1] {
        "int" => Ok(Value::I(parts[2].to_owned().parse().unwrap())),
        "float" => Ok(Value::F(parts[2].to_owned().parse().unwrap())),
        "string" => Ok(Value::S(parts[2].to_owned())),
        "char" => Ok(Value::C(parts[2].chars().next().unwrap())),
        "bool" => Ok(Value::B(parts[2].to_owned().parse().unwrap())),
        _ => parse_err("invalid value"),
    }
}

fn parse_command(line: String) -> Result<Command, String> {
    let parts: Vec<&str> = line.split_whitespace().collect();
    if parts.len() < 1 {
        return parse_err("empty command?");
    }
    match parts[0] {
        "push" => Ok(Command::Push(try!(parse_value(&parts)))),
        "pop" => Ok(Command::Pop),
        "dup" => Ok(Command::Dup),
        "plus" => Ok(Command::Plus),
        "minus" => Ok(Command::Minus),
        "times" => Ok(Command::Times),
        "divide" => Ok(Command::Divide),
        "mod" => Ok(Command::Mod),
        "print" => Ok(Command::Print),
        "mktype" => Ok(Command::MkType),
        "new" => Ok(Command::New),
        "getref" => Ok(Command::GetRef),
        "setref" => Ok(Command::SetRef),
        "sysgc" => Ok(Command::SysGC),
        "sysmemstats" => Ok(Command::SysMemstats),
        _ => Err("invalid command: ".to_owned() + line.as_str()),
    }
}

fn build(lines: Vec<String>) -> Result<(Vec<Command>, HashMap<String, usize>), String> {
    let mut commands = vec![];
    let mut names = HashMap::new();

    for line in lines {
        if line.len() == 0 || line.starts_with("#") {
            continue;
        } else if line.starts_with("!") {
            names.insert(line.trim_left_matches("!").to_owned(), commands.len());
        } else {
            commands.push(try!(parse_command(line)))
        }
    }

    Ok((commands, names))
}

// fn interpret(

fn main() {
    let lines = read_lines();
    println!("{:?}", build(lines));
}
<|endoftext|>