<commit_before>use std::sync::Arc;
use std::sync::mpsc::Sender;

use iron::prelude::*;
use iron::status;
use iron::middleware::Handler;
use bodyparser;
use rustc_serialize::json;
use regex::Regex;

use config::Config;
use github;
use messenger::{self, Messenger};
use pr_merge::{self, PRMergeMessage};
use slack::SlackAttachmentBuilder;
use util;

pub struct GithubHandler {
    pub config: Arc<Config>,
    pub github_session: Arc<github::api::Session>,
    pr_merge_worker: pr_merge::Worker,
}

struct GithubEventHandler {
    pub messenger: Box<Messenger>,
    pub config: Arc<Config>,
    pub event: String,
    pub data: github::HookBody,
    pub action: String,
    pub github_session: Arc<github::api::Session>,
    pub pr_merge: Sender<PRMergeMessage>,
}

const MAX_CONCURRENT_MERGES: usize = 20;

impl GithubHandler {
    pub fn new(config: Arc<Config>, github_session: github::api::Session) -> GithubHandler {

        let session = Arc::new(github_session);
        GithubHandler {
            config: config.clone(),
            github_session: session.clone(),
            pr_merge_worker: pr_merge::Worker::new(MAX_CONCURRENT_MERGES,
                                                   config.clone(),
                                                   session.clone()),
        }
    }
}

impl Handler for GithubHandler {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {
        let event: String = match req.headers.get_raw("x-github-event") {
            Some(ref h) if h.len() == 1 => String::from_utf8_lossy(&h[0]).into_owned(),
            None | Some(..) => {
                error!("Expected to find exactly one event header");
                return Ok(Response::with((status::BadRequest,
                                          "Expected to find exactly one event header")));
            }
        };

        let body = match req.get::<bodyparser::Raw>() {
            Ok(Some(j)) => j,
            Err(_) | Ok(None) => {
                error!("Error reading json");
                return Ok(Response::with((status::BadRequest, format!("Error reading json"))));
            }
        };

        let data: github::HookBody = match json::decode(body.as_str()) {
            Ok(h) => h,
            Err(e) => {
                error!("Error parsing json: {}\n---\n{}\n---\n", e, body.as_str());
                return Ok(Response::with((status::BadRequest,
                                          format!("Error parsing JSON: {}", e))));
            }
        };

        let action = match data.action {
            Some(ref a) => a.clone(),
            None => String::new(),
        };

        let handler = GithubEventHandler {
            event: event.clone(),
            data: data,
            action: action,
            config: self.config.clone(),
            messenger: messenger::from_config(self.config.clone()),
            github_session: self.github_session.clone(),
            pr_merge: self.pr_merge_worker.new_sender(),
        };

        match handler.handle_event() {
            Some(r) => Ok(r),
            None => Ok(Response::with((status::Ok, format!("Unhandled event: {}", event)))),
        }
    }
}

impl GithubEventHandler {
    fn handle_event(&self) -> Option<Response> {
        info!("Received event: {}", self.event);
        if self.event == "ping" {
            Some(self.handle_ping())
        } else if self.event == "pull_request" {
            Some(self.handle_pr())
        } else if self.event == "pull_request_review_comment" {
            Some(self.handle_pr_review_comment())
        } else if self.event == "pull_request_review" {
            Some(self.handle_pr_review())
        } else if self.event == "commit_comment" {
            Some(self.handle_commit_comment())
        } else if self.event == "issue_comment" {
            Some(self.handle_issue_comment())
        } else if self.event == "push" {
            Some(self.handle_push())
        } else {
            None
        }
    }

    fn slack_user_name(&self, user: &github::User) -> String {
        self.config.users.slack_user_name(user.login(), &self.data.repository)
    }

    fn handle_ping(&self) -> Response {
        Response::with((status::Ok, "ping"))
    }

    fn handle_pr(&self) -> Response {
        if let Some(ref pull_request) = self.data.pull_request {
            let verb: Option<String>;
            if self.action == "opened" {
                verb = Some(format!("opened by {}", self.slack_user_name(&pull_request.user)));
            } else if self.action == "closed" {
                if pull_request.merged == Some(true) {
                    verb = Some("merged".to_string());
                } else {
                    verb = Some("closed".to_string());
                }
            } else if self.action == "reopened" {
                verb = Some("reopened".to_string());
            } else if self.action == "assigned" {
                let assignees_str = self.config
                    .users
                    .slack_user_names(&pull_request.assignees, &self.data.repository)
                    .join(", ");
                verb = Some(format!("assigned to {}", assignees_str));
            } else if self.action == "unassigned" {
                verb = Some("unassigned".to_string());
            } else {
                verb = None;
            }

            if let Some(ref verb) = verb {
                let msg = format!("Pull Request {}", verb);
                let attachments = vec![SlackAttachmentBuilder::new("")
                                           .title(format!("Pull Request #{}: \"{}\"",
                                                          pull_request.number,
                                                          pull_request.title.as_str()))
                                           .title_link(pull_request.html_url.as_str())
                                           .build()];

                self.messenger.send_to_all(&msg,
                                           &attachments,
                                           &pull_request.user,
                                           &self.data.sender,
                                           &self.data.repository,
                                           &pull_request.assignees);
            }

            if self.action == "labeled" {
                if let Some(ref label) = self.data.label {
                    self.merge_pull_request(pull_request, label);
                }
            } else if verb == Some("merged".to_string()) {
                self.merge_pull_request_all_labels(pull_request);
            }
        }

        Response::with((status::Ok, "pr"))
    }

    fn handle_pr_review_comment(&self) -> Response {
        if let Some(ref pull_request) = self.data.pull_request {
            if let Some(ref comment) = self.data.comment {
                if self.action == "created" {
                    self.do_pull_request_comment(pull_request,
                                                 &comment.user,
                                                 comment.body.as_str(),
                                                 comment.html_url.as_str());
                }

            }
        }

        Response::with((status::Ok, "pr_review_comment"))
    }

    fn handle_pr_review(&self) -> Response {
        if let Some(ref pull_request) = self.data.pull_request {
            if let Some(ref review) = self.data.review {
                if self.action == "submitted" {

                    // just a comment. should just be handled by regular comment handler.
                    if review.state == "commented" {
                        self.do_pull_request_comment(pull_request,
                                                     &review.user,
                                                     review.body.as_str(),
                                                     review.html_url.as_str());
                        return Response::with((status::Ok, "pr_review [comment]"));
                    }

                    let action_msg;
                    let state_msg;
                    let color;
                    if review.state == "changes_requested" {
                        action_msg = "requested changes to";
                        state_msg = "Changes Requested";
                        color = "danger";

                    } else if review.state == "approved" {
                        action_msg = "approved";
                        state_msg = "Approved";
                        color = "good";

                    } else {
                        return Response::with((status::Ok, "pr_review [ignored]"));
                    }

                    let msg = format!("{} {} PR \"{}\"",
                                      self.slack_user_name(&review.user),
                                      action_msg,
                                      util::make_link(pull_request.html_url.as_str(),
                                                      pull_request.title.as_str()));

                    let attachments = vec![SlackAttachmentBuilder::new(review.body.as_str())
                                               .title(format!("Review: {}", state_msg))
                                               .title_link(review.html_url.as_str())
                                               .color(color)
                                               .build()];

                    self.messenger.send_to_all(&msg,
                                               &attachments,
                                               &pull_request.user,
                                               &self.data.sender,
                                               &self.data.repository,
                                               &pull_request.assignees);

                }
            }
        }

        Response::with((status::Ok, "pr_review"))
    }

    fn do_pull_request_comment(&self, pull_request: &github::PullRequest,
                               commenter: &github::User, comment_body: &str, comment_url: &str) {
        if comment_body.trim().len() == 0 {
            return;
        }

        let msg = format!("Comment on \"{}\"",
                          util::make_link(pull_request.html_url.as_str(),
                                          pull_request.title.as_str()));

        let attachments = vec![SlackAttachmentBuilder::new(comment_body)
                                   .title(format!("{} said:", self.slack_user_name(&commenter)))
                                   .title_link(comment_url)
                                   .build()];

        self.messenger.send_to_all(&msg,
                                   &attachments,
                                   &pull_request.user,
                                   &self.data.sender,
                                   &self.data.repository,
                                   &pull_request.assignees);

    }

    fn handle_commit_comment(&self) -> Response {
        if let Some(ref comment) = self.data.comment {
            if self.action == "created" {
                if let Some(ref commit_id) = comment.commit_id {
                    let commit: &str = &commit_id[0..7];
                    let commit_url =
                        format!("{}/commit/{}", self.data.repository.html_url, commit_id);
                    let commit_path: String;
                    if let Some(ref path) = comment.path {
                        commit_path = path.to_string();
                    } else {
                        commit_path = commit.to_string();
                    }

                    let msg = format!("Comment on \"{}\" ({})",
                                      commit_path,
                                      util::make_link(commit_url.as_str(), commit));

                    let attachments = vec![SlackAttachmentBuilder::new(comment.body.as_str())
                                               .title(format!("{} said:",
                                                              self.slack_user_name(&comment.user)))
                                               .title_link(comment.html_url.as_str())
                                               .build()];

                    self.messenger.send_to_all(&msg,
                                               &attachments,
                                               &comment.user,
                                               &self.data.sender,
                                               &self.data.repository,
                                               &vec![]);
                }
            }
        }

        Response::with((status::Ok, "commit_comment"))
    }

    fn handle_issue_comment(&self) -> Response {
        if let Some(ref issue) = self.data.issue {
            if let Some(ref comment) = self.data.comment {
                if self.action == "created" {
                    let msg = format!("Comment on \"{}\"",
                                      util::make_link(issue.html_url.as_str(),
                                                      issue.title.as_str()));

                    let attachments = vec![SlackAttachmentBuilder::new(comment.body.as_str())
                                               .title(format!("{} said:",
                                                              self.slack_user_name(&comment.user)))
                                               .title_link(comment.html_url.as_str())
                                               .build()];

                    self.messenger.send_to_all(&msg,
                                               &attachments,
                                               &issue.user,
                                               &self.data.sender,
                                               &self.data.repository,
                                               &issue.assignees);
                }
            }
        }
        Response::with((status::Ok, "issue_comment"))
    }

    fn handle_push(&self) -> Response {
        Response::with((status::Ok, "push"))
    }

    fn merge_pull_request_all_labels(&self, pull_request: &github::PullRequest) {
        if !pull_request.is_merged() {
            return;
        }

        let labels = match self.github_session
            .get_pull_request_labels(&self.data.repository.owner.login(),
                                     &self.data.repository.name,
                                     pull_request.number) {
            Ok(l) => l,
            Err(e) => {
                self.messenger.send_to_owner("Error getting Pull Request labels",
                                             &vec![SlackAttachmentBuilder::new(&e)
                                                       .color("danger")
                                                       .build()],
                                             &pull_request.user,
                                             &self.data.repository);
                return;
            }
        };

        for label in &labels {
            self.merge_pull_request(pull_request, label);
        }
    }

    fn merge_pull_request(&self, pull_request: &github::PullRequest, label: &github::Label) {
        if !pull_request.is_merged() {
            return;
        }

        let re = Regex::new(r"(?i)backport-([\d\.]+)").unwrap();
        let backport = match re.captures(&label.name) {
            Some(c) => c[1].to_string(),
            None => return,
        };
        let target_branch = "release/".to_string() + &backport;
        let mut attachment = SlackAttachmentBuilder::new("");

        attachment.title(format!("Source PR: #{}: \"{}\"",
                           pull_request.number,
                           pull_request.title)
                .as_str())
            .title_link(pull_request.html_url.clone());

        if let Err(e) = self.pr_merge
            .send(PRMergeMessage::merge(&self.data.repository, pull_request, &target_branch)) {
            error!("Error sending merge request message: {}", e)
        }
    }
}
<commit_msg>Remove unused variable (#18)<commit_after>use std::sync::Arc;
use std::sync::mpsc::Sender;

use iron::prelude::*;
use iron::status;
use iron::middleware::Handler;
use bodyparser;
use rustc_serialize::json;
use regex::Regex;

use config::Config;
use github;
use messenger::{self, Messenger};
use pr_merge::{self, PRMergeMessage};
use slack::SlackAttachmentBuilder;
use util;

pub struct GithubHandler {
    pub config: Arc<Config>,
    pub github_session: Arc<github::api::Session>,
    pr_merge_worker: pr_merge::Worker,
}

struct GithubEventHandler {
    pub messenger: Box<Messenger>,
    pub config: Arc<Config>,
    pub event: String,
    pub data: github::HookBody,
    pub action: String,
    pub github_session: Arc<github::api::Session>,
    pub pr_merge: Sender<PRMergeMessage>,
}

const MAX_CONCURRENT_MERGES: usize = 20;

impl GithubHandler {
    pub fn new(config: Arc<Config>, github_session: github::api::Session) -> GithubHandler {

        let session = Arc::new(github_session);
        GithubHandler {
            config: config.clone(),
            github_session: session.clone(),
            pr_merge_worker: pr_merge::Worker::new(MAX_CONCURRENT_MERGES,
                                                   config.clone(),
                                                   session.clone()),
        }
    }
}

impl Handler for GithubHandler {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {
        let event: String = match req.headers.get_raw("x-github-event") {
            Some(ref h) if h.len() == 1 => String::from_utf8_lossy(&h[0]).into_owned(),
            None | Some(..) => {
                error!("Expected to find exactly one event header");
                return Ok(Response::with((status::BadRequest,
                                          "Expected to find exactly one event header")));
            }
        };

        let body = match req.get::<bodyparser::Raw>() {
            Ok(Some(j)) => j,
            Err(_) | Ok(None) => {
                error!("Error reading json");
                return Ok(Response::with((status::BadRequest, format!("Error reading json"))));
            }
        };

        let data: github::HookBody = match json::decode(body.as_str()) {
            Ok(h) => h,
            Err(e) => {
                error!("Error parsing json: {}\n---\n{}\n---\n", e, body.as_str());
                return Ok(Response::with((status::BadRequest,
                                          format!("Error parsing JSON: {}", e))));
            }
        };

        let action = match data.action {
            Some(ref a) => a.clone(),
            None => String::new(),
        };

        let handler = GithubEventHandler {
            event: event.clone(),
            data: data,
            action: action,
            config: self.config.clone(),
            messenger: messenger::from_config(self.config.clone()),
            github_session: self.github_session.clone(),
            pr_merge: self.pr_merge_worker.new_sender(),
        };

        match handler.handle_event() {
            Some(r) => Ok(r),
            None => Ok(Response::with((status::Ok, format!("Unhandled event: {}", event)))),
        }
    }
}

impl GithubEventHandler {
    fn handle_event(&self) -> Option<Response> {
        info!("Received event: {}", self.event);
        if self.event == "ping" {
            Some(self.handle_ping())
        } else if self.event == "pull_request" {
            Some(self.handle_pr())
        } else if self.event == "pull_request_review_comment" {
            Some(self.handle_pr_review_comment())
        } else if self.event == "pull_request_review" {
            Some(self.handle_pr_review())
        } else if self.event == "commit_comment" {
            Some(self.handle_commit_comment())
        } else if self.event == "issue_comment" {
            Some(self.handle_issue_comment())
        } else if self.event == "push" {
            Some(self.handle_push())
        } else {
            None
        }
    }

    fn slack_user_name(&self, user: &github::User) -> String {
        self.config.users.slack_user_name(user.login(), &self.data.repository)
    }

    fn handle_ping(&self) -> Response {
        Response::with((status::Ok, "ping"))
    }

    fn handle_pr(&self) -> Response {
        if let Some(ref pull_request) = self.data.pull_request {
            let verb: Option<String>;
            if self.action == "opened" {
                verb = Some(format!("opened by {}", self.slack_user_name(&pull_request.user)));
            } else if self.action == "closed" {
                if pull_request.merged == Some(true) {
                    verb = Some("merged".to_string());
                } else {
                    verb = Some("closed".to_string());
                }
            } else if self.action == "reopened" {
                verb = Some("reopened".to_string());
            } else if self.action == "assigned" {
                let assignees_str = self.config
                    .users
                    .slack_user_names(&pull_request.assignees, &self.data.repository)
                    .join(", ");
                verb = Some(format!("assigned to {}", assignees_str));
            } else if self.action == "unassigned" {
                verb = Some("unassigned".to_string());
            } else {
                verb = None;
            }

            if let Some(ref verb) = verb {
                let msg = format!("Pull Request {}", verb);
                let attachments = vec![SlackAttachmentBuilder::new("")
                                           .title(format!("Pull Request #{}: \"{}\"",
                                                          pull_request.number,
                                                          pull_request.title.as_str()))
                                           .title_link(pull_request.html_url.as_str())
                                           .build()];

                self.messenger.send_to_all(&msg,
                                           &attachments,
                                           &pull_request.user,
                                           &self.data.sender,
                                           &self.data.repository,
                                           &pull_request.assignees);
            }

            if self.action == "labeled" {
                if let Some(ref label) = self.data.label {
                    self.merge_pull_request(pull_request, label);
                }
            } else if verb == Some("merged".to_string()) {
                self.merge_pull_request_all_labels(pull_request);
            }
        }

        Response::with((status::Ok, "pr"))
    }

    fn handle_pr_review_comment(&self) -> Response {
        if let Some(ref pull_request) = self.data.pull_request {
            if let Some(ref comment) = self.data.comment {
                if self.action == "created" {
                    self.do_pull_request_comment(pull_request,
                                                 &comment.user,
                                                 comment.body.as_str(),
                                                 comment.html_url.as_str());
                }

            }
        }

        Response::with((status::Ok, "pr_review_comment"))
    }

    fn handle_pr_review(&self) -> Response {
        if let Some(ref pull_request) = self.data.pull_request {
            if let Some(ref review) = self.data.review {
                if self.action == "submitted" {

                    // just a comment. should just be handled by regular comment handler.
                    if review.state == "commented" {
                        self.do_pull_request_comment(pull_request,
                                                     &review.user,
                                                     review.body.as_str(),
                                                     review.html_url.as_str());
                        return Response::with((status::Ok, "pr_review [comment]"));
                    }

                    let action_msg;
                    let state_msg;
                    let color;
                    if review.state == "changes_requested" {
                        action_msg = "requested changes to";
                        state_msg = "Changes Requested";
                        color = "danger";

                    } else if review.state == "approved" {
                        action_msg = "approved";
                        state_msg = "Approved";
                        color = "good";

                    } else {
                        return Response::with((status::Ok, "pr_review [ignored]"));
                    }

                    let msg = format!("{} {} PR \"{}\"",
                                      self.slack_user_name(&review.user),
                                      action_msg,
                                      util::make_link(pull_request.html_url.as_str(),
                                                      pull_request.title.as_str()));

                    let attachments = vec![SlackAttachmentBuilder::new(review.body.as_str())
                                               .title(format!("Review: {}", state_msg))
                                               .title_link(review.html_url.as_str())
                                               .color(color)
                                               .build()];

                    self.messenger.send_to_all(&msg,
                                               &attachments,
                                               &pull_request.user,
                                               &self.data.sender,
                                               &self.data.repository,
                                               &pull_request.assignees);

                }
            }
        }

        Response::with((status::Ok, "pr_review"))
    }

    fn do_pull_request_comment(&self, pull_request: &github::PullRequest,
                               commenter: &github::User, comment_body: &str, comment_url: &str) {
        if comment_body.trim().len() == 0 {
            return;
        }

        let msg = format!("Comment on \"{}\"",
                          util::make_link(pull_request.html_url.as_str(),
                                          pull_request.title.as_str()));

        let attachments = vec![SlackAttachmentBuilder::new(comment_body)
                                   .title(format!("{} said:", self.slack_user_name(&commenter)))
                                   .title_link(comment_url)
                                   .build()];

        self.messenger.send_to_all(&msg,
                                   &attachments,
                                   &pull_request.user,
                                   &self.data.sender,
                                   &self.data.repository,
                                   &pull_request.assignees);

    }

    fn handle_commit_comment(&self) -> Response {
        if let Some(ref comment) = self.data.comment {
            if self.action == "created" {
                if let Some(ref commit_id) = comment.commit_id {
                    let commit: &str = &commit_id[0..7];
                    let commit_url =
                        format!("{}/commit/{}", self.data.repository.html_url, commit_id);
                    let commit_path: String;
                    if let Some(ref path) = comment.path {
                        commit_path = path.to_string();
                    } else {
                        commit_path = commit.to_string();
                    }

                    let msg = format!("Comment on \"{}\" ({})",
                                      commit_path,
                                      util::make_link(commit_url.as_str(), commit));

                    let attachments = vec![SlackAttachmentBuilder::new(comment.body.as_str())
                                               .title(format!("{} said:",
                                                              self.slack_user_name(&comment.user)))
                                               .title_link(comment.html_url.as_str())
                                               .build()];

                    self.messenger.send_to_all(&msg,
                                               &attachments,
                                               &comment.user,
                                               &self.data.sender,
                                               &self.data.repository,
                                               &vec![]);
                }
            }
        }

        Response::with((status::Ok, "commit_comment"))
    }

    fn handle_issue_comment(&self) -> Response {
        if let Some(ref issue) = self.data.issue {
            if let Some(ref comment) = self.data.comment {
                if self.action == "created" {
                    let msg = format!("Comment on \"{}\"",
                                      util::make_link(issue.html_url.as_str(),
                                                      issue.title.as_str()));

                    let attachments = vec![SlackAttachmentBuilder::new(comment.body.as_str())
                                               .title(format!("{} said:",
                                                              self.slack_user_name(&comment.user)))
                                               .title_link(comment.html_url.as_str())
                                               .build()];

                    self.messenger.send_to_all(&msg,
                                               &attachments,
                                               &issue.user,
                                               &self.data.sender,
                                               &self.data.repository,
                                               &issue.assignees);
                }
            }
        }
        Response::with((status::Ok, "issue_comment"))
    }

    fn handle_push(&self) -> Response {
        Response::with((status::Ok, "push"))
    }

    fn merge_pull_request_all_labels(&self, pull_request: &github::PullRequest) {
        if !pull_request.is_merged() {
            return;
        }

        let labels = match self.github_session
            .get_pull_request_labels(&self.data.repository.owner.login(),
                                     &self.data.repository.name,
                                     pull_request.number) {
            Ok(l) => l,
            Err(e) => {
                self.messenger.send_to_owner("Error getting Pull Request labels",
                                             &vec![SlackAttachmentBuilder::new(&e)
                                                       .color("danger")
                                                       .build()],
                                             &pull_request.user,
                                             &self.data.repository);
                return;
            }
        };

        for label in &labels {
            self.merge_pull_request(pull_request, label);
        }
    }

    fn merge_pull_request(&self, pull_request: &github::PullRequest, label: &github::Label) {
        if !pull_request.is_merged() {
            return;
        }

        let re = Regex::new(r"(?i)backport-([\d\.]+)").unwrap();
        let backport = match re.captures(&label.name) {
            Some(c) => c[1].to_string(),
            None => return,
        };
        let target_branch = "release/".to_string() + &backport;

        if let Err(e) = self.pr_merge
            .send(PRMergeMessage::merge(&self.data.repository, pull_request, &target_branch)) {
            error!("Error sending merge request message: {}", e)
        }
    }
}
<|endoftext|>


<commit_before>// High-level bindings to Azure.

import bindgen::{AzCreateColorPattern, AzCreateDrawTargetForCairoSurface};
import AzDrawSurfaceOptions;
import bindgen::AzDrawTargetClearRect;
import bindgen::AzDrawTargetCreateSourceSurfaceFromData;
import bindgen::{AzDrawTargetDrawSurface, AzDrawTargetFillRect, AzDrawTargetFlush};
import bindgen::{AzDrawTargetStrokeRect};
import AzFilter;
import bindgen::{AzReleaseColorPattern, AzReleaseDrawTarget};
import bindgen::AzReleaseSourceSurface;
import AzSurfaceFormat;
import cairo_hl::ImageSurface;
import geom::rect::Rect;
import geom::size::Size2D;
import ptr::{addr_of, null};

trait AsAzureRect {
    fn as_azure_rect() -> AzRect;
}

impl AzureExtensions of AsAzureRect for Rect<AzFloat> {
    fn as_azure_rect() -> AzRect {
        {
            x: self.origin.x,
            y: self.origin.y,
            width: self.size.width,
            height: self.size.height
        }
    }
}

trait AsAzureIntSize {
    fn as_azure_int_size() -> AzIntSize;
}

impl AzureExtensions of AsAzureIntSize for Size2D<i32> {
    fn as_azure_int_size() -> AzIntSize {
        {
            width: self.width,
            height: self.height
        }
    }
}

class Color {
    let r: AzFloat;
    let g: AzFloat;
    let b: AzFloat;
    let a: AzFloat;

    new(r: AzFloat, g: AzFloat, b: AzFloat, a: AzFloat) {
        self.r = r;
        self.g = g;
        self.b = b;
        self.a = a;
    }

    fn as_azure_color() -> AzColor {
        { r: self.r, g: self.g, b: self.b, a: self.a }
    }
}

// FIXME: Should have a class hierarchy here starting with Pattern.
class ColorPattern {
    let azure_color_pattern: AzColorPatternRef;

    new(+color: Color) {
        self.azure_color_pattern = AzCreateColorPattern(addr_of(color.as_azure_color()));
    }

    drop {
        AzReleaseColorPattern(self.azure_color_pattern);
    }
}

class StrokeOptions {
    let line_width: AzFloat;
    let miter_limit: AzFloat;
    let fields: uint16_t;

    new(line_width: AzFloat, miter_limit: AzFloat, fields: uint16_t) {
        self.line_width = line_width;
        self.miter_limit = miter_limit;
        self.fields = fields;
    }

    fn as_azure_stroke_options() -> AzStrokeOptions {
        {
            mLineWidth: self.line_width,
            mMiterLimit: self.miter_limit,
            mDashPattern: null(),
            mDashLength: 0,
            mDashOffset: 0.0f as AzFloat,
            fields: 0
        }
    }
}

class DrawOptions {
    let alpha: AzFloat;
    let fields: uint16_t;

    new(alpha: AzFloat, fields: uint16_t) {
        self.alpha = alpha;
        self.fields = fields;
    }

    fn as_azure_draw_options() -> AzDrawOptions {
        {
            mAlpha: self.alpha,
            fields: self.fields
        }
    }
}

enum SurfaceFormat {
    B8G8R8A8,
    R8G8R8X8,
    R5G6B5,
    A8
}

impl extensions for SurfaceFormat {
    fn as_azure_surface_format() -> AzSurfaceFormat {
        self as AzSurfaceFormat
    }
}

enum Filter {
    Linear,
    Point
}

impl extensions for Filter {
    fn as_azure_filter() -> AzFilter {
        self as AzFilter
    }
}

struct DrawSurfaceOptions {
    filter: Filter;
    sampling_bounds: bool;

    new(filter: Filter, sampling_bounds: bool) {
        self.filter = filter;
        self.sampling_bounds = sampling_bounds;
    }

    fn as_azure_draw_surface_options() -> AzDrawSurfaceOptions {
        { fields: ((self.filter as int) | (if self.sampling_bounds { 8 } else { 0 })) as u32 }
    }
}

class DrawTarget {
    let azure_draw_target: AzDrawTargetRef;

    new(&&cairo_surface: ImageSurface) {
        self.azure_draw_target = AzCreateDrawTargetForCairoSurface(cairo_surface.cairo_surface);
    }

    drop {
        AzReleaseDrawTarget(self.azure_draw_target);
    }

    fn flush() {
        AzDrawTargetFlush(self.azure_draw_target);
    }

    fn clear_rect(&&rect: Rect<AzFloat>) {
        AzDrawTargetClearRect(self.azure_draw_target, addr_of(rect.as_azure_rect()));
    }

    fn fill_rect(&&rect: Rect<AzFloat>, &&pattern: ColorPattern) {
        AzDrawTargetFillRect(self.azure_draw_target,
                             addr_of(rect.as_azure_rect()),
                             pattern.azure_color_pattern);
    }

    fn stroke_rect(&&rect: Rect<AzFloat>, &&pattern: ColorPattern, &&stroke_options: StrokeOptions,
                   &&draw_options: DrawOptions) {
        AzDrawTargetStrokeRect(self.azure_draw_target,
                               addr_of(rect.as_azure_rect()),
                               pattern.azure_color_pattern,
                               addr_of(stroke_options.as_azure_stroke_options()),
                               addr_of(draw_options.as_azure_draw_options()));
    }

    fn draw_surface(surface: SourceSurface, dest: Rect<AzFloat>, source: Rect<AzFloat>,
                    surf_options: DrawSurfaceOptions, options: DrawOptions) {

        AzDrawTargetDrawSurface(self.azure_draw_target,
                                surface.azure_source_surface,
                                addr_of(dest.as_azure_rect()),
                                addr_of(source.as_azure_rect()),
                                addr_of(surf_options.as_azure_draw_surface_options()),
                                addr_of(options.as_azure_draw_options()));
    }

    fn create_source_surface_from_data(data: &[u8], size: Size2D<i32>, stride: i32,
                                       format: SurfaceFormat) -> SourceSurface {

        assert data.len() as i32 == stride * size.height;
        let azure_surface =
            AzDrawTargetCreateSourceSurfaceFromData(self.azure_draw_target,
                                                    addr_of(data[0]),
                                                    addr_of(size.as_azure_int_size()),
                                                    stride,
                                                    format.as_azure_surface_format());
        SourceSurface(azure_surface)
    }
}

struct SourceSurface {
    azure_source_surface: AzSourceSurfaceRef;

    new(azure_source_surface: AzSourceSurfaceRef) {
        self.azure_source_surface = azure_source_surface;
    }

    drop {
        AzReleaseSourceSurface(self.azure_source_surface);
    }
}

<commit_msg>Update for impl language changes<commit_after>// High-level bindings to Azure.

import bindgen::{AzCreateColorPattern, AzCreateDrawTargetForCairoSurface};
import AzDrawSurfaceOptions;
import bindgen::AzDrawTargetClearRect;
import bindgen::AzDrawTargetCreateSourceSurfaceFromData;
import bindgen::{AzDrawTargetDrawSurface, AzDrawTargetFillRect, AzDrawTargetFlush};
import bindgen::{AzDrawTargetStrokeRect};
import AzFilter;
import bindgen::{AzReleaseColorPattern, AzReleaseDrawTarget};
import bindgen::AzReleaseSourceSurface;
import AzSurfaceFormat;
import cairo_hl::ImageSurface;
import geom::rect::Rect;
import geom::size::Size2D;
import ptr::{addr_of, null};

trait AsAzureRect {
    fn as_azure_rect() -> AzRect;
}

impl Rect<AzFloat> : AsAzureRect {
    fn as_azure_rect() -> AzRect {
        {
            x: self.origin.x,
            y: self.origin.y,
            width: self.size.width,
            height: self.size.height
        }
    }
}

trait AsAzureIntSize {
    fn as_azure_int_size() -> AzIntSize;
}

impl Size2D<i32> : AsAzureIntSize {
    fn as_azure_int_size() -> AzIntSize {
        {
            width: self.width,
            height: self.height
        }
    }
}

class Color {
    let r: AzFloat;
    let g: AzFloat;
    let b: AzFloat;
    let a: AzFloat;

    new(r: AzFloat, g: AzFloat, b: AzFloat, a: AzFloat) {
        self.r = r;
        self.g = g;
        self.b = b;
        self.a = a;
    }

    fn as_azure_color() -> AzColor {
        { r: self.r, g: self.g, b: self.b, a: self.a }
    }
}

// FIXME: Should have a class hierarchy here starting with Pattern.
class ColorPattern {
    let azure_color_pattern: AzColorPatternRef;

    new(+color: Color) {
        self.azure_color_pattern = AzCreateColorPattern(addr_of(color.as_azure_color()));
    }

    drop {
        AzReleaseColorPattern(self.azure_color_pattern);
    }
}

class StrokeOptions {
    let line_width: AzFloat;
    let miter_limit: AzFloat;
    let fields: uint16_t;

    new(line_width: AzFloat, miter_limit: AzFloat, fields: uint16_t) {
        self.line_width = line_width;
        self.miter_limit = miter_limit;
        self.fields = fields;
    }

    fn as_azure_stroke_options() -> AzStrokeOptions {
        {
            mLineWidth: self.line_width,
            mMiterLimit: self.miter_limit,
            mDashPattern: null(),
            mDashLength: 0,
            mDashOffset: 0.0f as AzFloat,
            fields: 0
        }
    }
}

class DrawOptions {
    let alpha: AzFloat;
    let fields: uint16_t;

    new(alpha: AzFloat, fields: uint16_t) {
        self.alpha = alpha;
        self.fields = fields;
    }

    fn as_azure_draw_options() -> AzDrawOptions {
        {
            mAlpha: self.alpha,
            fields: self.fields
        }
    }
}

enum SurfaceFormat {
    B8G8R8A8,
    R8G8R8X8,
    R5G6B5,
    A8
}

impl SurfaceFormat {
    fn as_azure_surface_format() -> AzSurfaceFormat {
        self as AzSurfaceFormat
    }
}

enum Filter {
    Linear,
    Point
}

impl Filter {
    fn as_azure_filter() -> AzFilter {
        self as AzFilter
    }
}

struct DrawSurfaceOptions {
    filter: Filter;
    sampling_bounds: bool;

    new(filter: Filter, sampling_bounds: bool) {
        self.filter = filter;
        self.sampling_bounds = sampling_bounds;
    }

    fn as_azure_draw_surface_options() -> AzDrawSurfaceOptions {
        { fields: ((self.filter as int) | (if self.sampling_bounds { 8 } else { 0 })) as u32 }
    }
}

class DrawTarget {
    let azure_draw_target: AzDrawTargetRef;

    new(&&cairo_surface: ImageSurface) {
        self.azure_draw_target = AzCreateDrawTargetForCairoSurface(cairo_surface.cairo_surface);
    }

    drop {
        AzReleaseDrawTarget(self.azure_draw_target);
    }

    fn flush() {
        AzDrawTargetFlush(self.azure_draw_target);
    }

    fn clear_rect(&&rect: Rect<AzFloat>) {
        AzDrawTargetClearRect(self.azure_draw_target, addr_of(rect.as_azure_rect()));
    }

    fn fill_rect(&&rect: Rect<AzFloat>, &&pattern: ColorPattern) {
        AzDrawTargetFillRect(self.azure_draw_target,
                             addr_of(rect.as_azure_rect()),
                             pattern.azure_color_pattern);
    }

    fn stroke_rect(&&rect: Rect<AzFloat>, &&pattern: ColorPattern, &&stroke_options: StrokeOptions,
                   &&draw_options: DrawOptions) {
        AzDrawTargetStrokeRect(self.azure_draw_target,
                               addr_of(rect.as_azure_rect()),
                               pattern.azure_color_pattern,
                               addr_of(stroke_options.as_azure_stroke_options()),
                               addr_of(draw_options.as_azure_draw_options()));
    }

    fn draw_surface(surface: SourceSurface, dest: Rect<AzFloat>, source: Rect<AzFloat>,
                    surf_options: DrawSurfaceOptions, options: DrawOptions) {

        AzDrawTargetDrawSurface(self.azure_draw_target,
                                surface.azure_source_surface,
                                addr_of(dest.as_azure_rect()),
                                addr_of(source.as_azure_rect()),
                                addr_of(surf_options.as_azure_draw_surface_options()),
                                addr_of(options.as_azure_draw_options()));
    }

    fn create_source_surface_from_data(data: &[u8], size: Size2D<i32>, stride: i32,
                                       format: SurfaceFormat) -> SourceSurface {

        assert data.len() as i32 == stride * size.height;
        let azure_surface =
            AzDrawTargetCreateSourceSurfaceFromData(self.azure_draw_target,
                                                    addr_of(data[0]),
                                                    addr_of(size.as_azure_int_size()),
                                                    stride,
                                                    format.as_azure_surface_format());
        SourceSurface(azure_surface)
    }
}

struct SourceSurface {
    azure_source_surface: AzSourceSurfaceRef;

    new(azure_source_surface: AzSourceSurfaceRef) {
        self.azure_source_surface = azure_source_surface;
    }

    drop {
        AzReleaseSourceSurface(self.azure_source_surface);
    }
}

<|endoftext|>


<commit_before>use crate::{
    prelude::{verify::TxErr, Properties, SignedBlock, TxVariant},
    serializer::*,
};
use std::io::{self, Cursor, Error};

#[repr(u8)]
pub enum MsgType {
    Error = 0,
    GetProperties = 1,
    GetBlock = 2,
    Broadcast = 3,
}

pub enum MsgRequest {
    GetProperties,
    GetBlock(u64), // height
    Broadcast(TxVariant),
}

impl MsgRequest {
    pub fn serialize(self) -> Vec<u8> {
        match self {
            MsgRequest::GetProperties => vec![MsgType::GetProperties as u8],
            MsgRequest::GetBlock(height) => {
                let mut buf = Vec::with_capacity(9);
                buf.push(MsgType::GetBlock as u8);
                buf.push_u64(height);
                buf
            }
            MsgRequest::Broadcast(tx) => {
                let mut buf = Vec::with_capacity(4096);
                buf.push(MsgType::Broadcast as u8);
                tx.serialize(&mut buf);
                buf
            }
        }
    }

    pub fn deserialize(cursor: &mut Cursor<&[u8]>) -> io::Result<Self> {
        let tag = cursor.take_u8()?;
        match tag {
            t if t == MsgType::GetProperties as u8 => Ok(MsgRequest::GetProperties),
            t if t == MsgType::GetBlock as u8 => {
                let height = cursor.take_u64()?;
                Ok(MsgRequest::GetBlock(height))
            }
            t if t == MsgType::Broadcast as u8 => {
                let tx = TxVariant::deserialize(cursor)
                    .ok_or_else(|| Error::new(io::ErrorKind::InvalidData, "failed to decode tx"))?;
                Ok(MsgRequest::Broadcast(tx))
            }
            _ => Err(Error::new(
                io::ErrorKind::InvalidData,
                "invalid msg request",
            )),
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ErrorKind {
    Io,
    InvalidHeight,
    TxValidation(TxErr),
}

impl ErrorKind {
    fn serialize(self, buf: &mut Vec<u8>) {
        match self {
            ErrorKind::Io => buf.push(0),
            ErrorKind::InvalidHeight => buf.push(1),
            ErrorKind::TxValidation(err) => {
                buf.push(2);
                err.serialize(buf);
            }
        }
    }

    fn deserialize(cursor: &mut Cursor<&[u8]>) -> io::Result<Self> {
        let tag = cursor.take_u8()?;
        Ok(match tag {
            0 => ErrorKind::Io,
            1 => ErrorKind::InvalidHeight,
            2 => ErrorKind::TxValidation(TxErr::deserialize(cursor)?),
            _ => {
                return Err(io::Error::new(
                    io::ErrorKind::InvalidData,
                    "failed to deserialize ErrorKind",
                ))
            }
        })
    }
}

#[derive(Clone, Debug)]
pub enum MsgResponse {
    Error(ErrorKind),
    GetProperties(Properties),
    GetBlock(SignedBlock),
    Broadcast(),
}

impl MsgResponse {
    pub fn is_err(&self) -> bool {
        match self {
            MsgResponse::Error(..) => true,
            _ => false,
        }
    }

    pub fn serialize(self) -> Vec<u8> {
        use std::mem;

        match self {
            MsgResponse::Error(err) => {
                let mut buf = Vec::with_capacity(2048);
                err.serialize(&mut buf);
                buf
            }
            MsgResponse::GetProperties(props) => {
                let mut buf = Vec::with_capacity(4096 + mem::size_of::<Properties>());
                buf.push(MsgType::GetProperties as u8);
                buf.push_u64(props.height);
                {
                    let mut tx_buf = Vec::with_capacity(4096);
                    TxVariant::OwnerTx(*props.owner).serialize(&mut tx_buf);
                    buf.extend_from_slice(&tx_buf);
                }
                buf.push_balance(&props.network_fee);
                buf.push_balance(&props.token_supply);
                buf
            }
            MsgResponse::GetBlock(block) => {
                let mut buf = Vec::with_capacity(1_048_576);
                buf.push(MsgType::GetBlock as u8);
                block.serialize_with_tx(&mut buf);
                buf
            }
            MsgResponse::Broadcast() => vec![MsgType::Broadcast as u8],
        }
    }

    pub fn deserialize(cursor: &mut Cursor<&[u8]>) -> io::Result<Self> {
        let tag = cursor.take_u8()?;
        match tag {
            t if t == MsgType::Error as u8 => {
                let err = ErrorKind::deserialize(cursor)?;
                Ok(MsgResponse::Error(err))
            }
            t if t == MsgType::GetProperties as u8 => {
                let height = cursor.take_u64()?;
                let owner = {
                    let var = TxVariant::deserialize(cursor).ok_or_else(|| {
                        Error::new(io::ErrorKind::InvalidData, "failed to deserialize owner tx")
                    })?;
                    match var {
                        TxVariant::OwnerTx(tx) => Box::new(tx),
                        _ => {
                            return Err(Error::new(io::ErrorKind::InvalidData, "expected owner tx"))
                        }
                    }
                };
                let network_fee = cursor.take_balance()?;
                let token_supply = cursor.take_balance()?;
                Ok(MsgResponse::GetProperties(Properties {
                    height,
                    owner,
                    network_fee,
                    token_supply,
                }))
            }
            t if t == MsgType::GetBlock as u8 => {
                let block = SignedBlock::deserialize_with_tx(cursor)
                    .ok_or_else(|| Error::from(io::ErrorKind::UnexpectedEof))?;
                Ok(MsgResponse::GetBlock(block))
            }
            _ => Err(Error::new(
                io::ErrorKind::InvalidData,
                "invalid msg response",
            )),
        }
    }
}
<commit_msg>Fix MsgResponse::Error serialization<commit_after>use crate::{
    prelude::{verify::TxErr, Properties, SignedBlock, TxVariant},
    serializer::*,
};
use std::io::{self, Cursor, Error};

#[repr(u8)]
pub enum MsgType {
    Error = 0,
    GetProperties = 1,
    GetBlock = 2,
    Broadcast = 3,
}

pub enum MsgRequest {
    GetProperties,
    GetBlock(u64), // height
    Broadcast(TxVariant),
}

impl MsgRequest {
    pub fn serialize(self) -> Vec<u8> {
        match self {
            MsgRequest::GetProperties => vec![MsgType::GetProperties as u8],
            MsgRequest::GetBlock(height) => {
                let mut buf = Vec::with_capacity(9);
                buf.push(MsgType::GetBlock as u8);
                buf.push_u64(height);
                buf
            }
            MsgRequest::Broadcast(tx) => {
                let mut buf = Vec::with_capacity(4096);
                buf.push(MsgType::Broadcast as u8);
                tx.serialize(&mut buf);
                buf
            }
        }
    }

    pub fn deserialize(cursor: &mut Cursor<&[u8]>) -> io::Result<Self> {
        let tag = cursor.take_u8()?;
        match tag {
            t if t == MsgType::GetProperties as u8 => Ok(MsgRequest::GetProperties),
            t if t == MsgType::GetBlock as u8 => {
                let height = cursor.take_u64()?;
                Ok(MsgRequest::GetBlock(height))
            }
            t if t == MsgType::Broadcast as u8 => {
                let tx = TxVariant::deserialize(cursor)
                    .ok_or_else(|| Error::new(io::ErrorKind::InvalidData, "failed to decode tx"))?;
                Ok(MsgRequest::Broadcast(tx))
            }
            _ => Err(Error::new(
                io::ErrorKind::InvalidData,
                "invalid msg request",
            )),
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ErrorKind {
    Io,
    InvalidHeight,
    TxValidation(TxErr),
}

impl ErrorKind {
    fn serialize(self, buf: &mut Vec<u8>) {
        match self {
            ErrorKind::Io => buf.push(0),
            ErrorKind::InvalidHeight => buf.push(1),
            ErrorKind::TxValidation(err) => {
                buf.push(2);
                err.serialize(buf);
            }
        }
    }

    fn deserialize(cursor: &mut Cursor<&[u8]>) -> io::Result<Self> {
        let tag = cursor.take_u8()?;
        Ok(match tag {
            0 => ErrorKind::Io,
            1 => ErrorKind::InvalidHeight,
            2 => ErrorKind::TxValidation(TxErr::deserialize(cursor)?),
            _ => {
                return Err(io::Error::new(
                    io::ErrorKind::InvalidData,
                    "failed to deserialize ErrorKind",
                ))
            }
        })
    }
}

#[derive(Clone, Debug)]
pub enum MsgResponse {
    Error(ErrorKind),
    GetProperties(Properties),
    GetBlock(SignedBlock),
    Broadcast(),
}

impl MsgResponse {
    pub fn is_err(&self) -> bool {
        match self {
            MsgResponse::Error(..) => true,
            _ => false,
        }
    }

    pub fn serialize(self) -> Vec<u8> {
        use std::mem;

        match self {
            MsgResponse::Error(err) => {
                let mut buf = Vec::with_capacity(2048);
                buf.push(MsgType::Error as u8);
                err.serialize(&mut buf);
                buf
            }
            MsgResponse::GetProperties(props) => {
                let mut buf = Vec::with_capacity(4096 + mem::size_of::<Properties>());
                buf.push(MsgType::GetProperties as u8);
                buf.push_u64(props.height);
                {
                    let mut tx_buf = Vec::with_capacity(4096);
                    TxVariant::OwnerTx(*props.owner).serialize(&mut tx_buf);
                    buf.extend_from_slice(&tx_buf);
                }
                buf.push_balance(&props.network_fee);
                buf.push_balance(&props.token_supply);
                buf
            }
            MsgResponse::GetBlock(block) => {
                let mut buf = Vec::with_capacity(1_048_576);
                buf.push(MsgType::GetBlock as u8);
                block.serialize_with_tx(&mut buf);
                buf
            }
            MsgResponse::Broadcast() => vec![MsgType::Broadcast as u8],
        }
    }

    pub fn deserialize(cursor: &mut Cursor<&[u8]>) -> io::Result<Self> {
        let tag = cursor.take_u8()?;
        match tag {
            t if t == MsgType::Error as u8 => {
                let err = ErrorKind::deserialize(cursor)?;
                Ok(MsgResponse::Error(err))
            }
            t if t == MsgType::GetProperties as u8 => {
                let height = cursor.take_u64()?;
                let owner = {
                    let var = TxVariant::deserialize(cursor).ok_or_else(|| {
                        Error::new(io::ErrorKind::InvalidData, "failed to deserialize owner tx")
                    })?;
                    match var {
                        TxVariant::OwnerTx(tx) => Box::new(tx),
                        _ => {
                            return Err(Error::new(io::ErrorKind::InvalidData, "expected owner tx"))
                        }
                    }
                };
                let network_fee = cursor.take_balance()?;
                let token_supply = cursor.take_balance()?;
                Ok(MsgResponse::GetProperties(Properties {
                    height,
                    owner,
                    network_fee,
                    token_supply,
                }))
            }
            t if t == MsgType::GetBlock as u8 => {
                let block = SignedBlock::deserialize_with_tx(cursor)
                    .ok_or_else(|| Error::from(io::ErrorKind::UnexpectedEof))?;
                Ok(MsgResponse::GetBlock(block))
            }
            _ => Err(Error::new(
                io::ErrorKind::InvalidData,
                "invalid msg response",
            )),
        }
    }
}
<|endoftext|>


<commit_before>use crate::fmt_list;
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::convert::TryFrom;
use std::fmt::{self, Write};
use std::ops::Range;

#[derive(Clone)]
pub struct RawEmitter {
    pub file: String,
    pub desc: String,
    pub bytes_used: usize,
}

impl RawEmitter {
    pub fn new() -> RawEmitter {
        RawEmitter { file: String::new(), bytes_used: 0, desc: String::new() }
    }

    fn blank_line(&mut self) {
        if self.file.is_empty() || self.file.ends_with("\n\n") {
            return;
        }
        writeln!(&mut self.file).unwrap();
    }

    fn emit_bitset(&mut self, ranges: &[Range<u32>]) {
        let last_code_point = ranges.last().unwrap().end;
        // bitset for every bit in the codepoint range
        //
        // + 2 to ensure an all zero word to use for padding
        let mut buckets = vec![0u64; (last_code_point as usize / 64) + 2];
        for range in ranges {
            for codepoint in range.clone() {
                let bucket = codepoint as usize / 64;
                let bit = codepoint as u64 % 64;
                buckets[bucket] |= 1 << bit;
            }
        }

        let mut words = buckets;
        // Ensure that there's a zero word in the dataset, used for padding and
        // such.
        words.push(0);
        let unique_words =
            words.iter().cloned().collect::<BTreeSet<_>>().into_iter().collect::<Vec<_>>();
        if unique_words.len() > u8::MAX as usize {
            panic!("cannot pack {} into 8 bits", unique_words.len());
        }
        // needed for the chunk mapping to work
        assert_eq!(unique_words[0], 0, "has a zero word");
        let canonicalized = Canonicalized::canonicalize(&unique_words);

        let word_indices = canonicalized.unique_mapping.clone();
        let compressed_words = words.iter().map(|w| word_indices[w]).collect::<Vec<u8>>();

        let mut best = None;
        for length in 1..=64 {
            let mut temp = self.clone();
            temp.emit_chunk_map(word_indices[&0], &compressed_words, length);
            if let Some((_, size)) = best {
                if temp.bytes_used < size {
                    best = Some((length, temp.bytes_used));
                }
            } else {
                best = Some((length, temp.bytes_used));
            }
        }
        self.emit_chunk_map(word_indices[&0], &compressed_words, best.unwrap().0);

        struct Bits(u64);
        impl fmt::Debug for Bits {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                write!(f, "0b{:064b}", self.0)
            }
        }

        writeln!(
            &mut self.file,
            "static BITSET_CANONICAL: [u64; {}] = [{}];",
            canonicalized.canonical_words.len(),
            fmt_list(canonicalized.canonical_words.iter().map(|v| Bits(*v))),
        )
        .unwrap();
        self.bytes_used += 8 * canonicalized.canonical_words.len();
        writeln!(
            &mut self.file,
            "static BITSET_MAPPING: [(u8, u8); {}] = [{}];",
            canonicalized.canonicalized_words.len(),
            fmt_list(&canonicalized.canonicalized_words),
        )
        .unwrap();
        // 8 bit index into shifted words, 7 bits for shift + optional flip
        // We only need it for the words that we removed by applying a shift and
        // flip to them.
        self.bytes_used += 2 * canonicalized.canonicalized_words.len();

        self.blank_line();

        writeln!(&mut self.file, "pub fn lookup(c: char) -> bool {{").unwrap();
        writeln!(&mut self.file, "    super::bitset_search(",).unwrap();
        writeln!(&mut self.file, "        c as u32,").unwrap();
        writeln!(&mut self.file, "        &BITSET_CHUNKS_MAP,").unwrap();
        writeln!(&mut self.file, "        &BITSET_INDEX_CHUNKS,").unwrap();
        writeln!(&mut self.file, "        &BITSET_CANONICAL,").unwrap();
        writeln!(&mut self.file, "        &BITSET_MAPPING,").unwrap();
        writeln!(&mut self.file, "    )").unwrap();
        writeln!(&mut self.file, "}}").unwrap();
    }

    fn emit_chunk_map(&mut self, zero_at: u8, compressed_words: &[u8], chunk_length: usize) {
        let mut compressed_words = compressed_words.to_vec();
        for _ in 0..(chunk_length - (compressed_words.len() % chunk_length)) {
            // pad out bitset index with zero words so we have all chunks of
            // chunkchunk_length
            compressed_words.push(zero_at);
        }

        let mut chunks = BTreeSet::new();
        for chunk in compressed_words.chunks(chunk_length) {
            chunks.insert(chunk);
        }
        let chunk_map = chunks
            .clone()
            .into_iter()
            .enumerate()
            .map(|(idx, chunk)| (chunk, idx))
            .collect::<HashMap<_, _>>();
        let mut chunk_indices = Vec::new();
        for chunk in compressed_words.chunks(chunk_length) {
            chunk_indices.push(chunk_map[chunk]);
        }

        writeln!(
            &mut self.file,
            "static BITSET_CHUNKS_MAP: [u8; {}] = [{}];",
            chunk_indices.len(),
            fmt_list(&chunk_indices),
        )
        .unwrap();
        self.bytes_used += chunk_indices.len();
        writeln!(
            &mut self.file,
            "static BITSET_INDEX_CHUNKS: [[u8; {}]; {}] = [{}];",
            chunk_length,
            chunks.len(),
            fmt_list(chunks.iter()),
        )
        .unwrap();
        self.bytes_used += chunk_length * chunks.len();
    }
}

pub fn emit_codepoints(emitter: &mut RawEmitter, ranges: &[Range<u32>]) {
    emitter.blank_line();

    let mut bitset = emitter.clone();
    bitset.emit_bitset(&ranges);

    let mut skiplist = emitter.clone();
    skiplist.emit_skiplist(&ranges);

    if bitset.bytes_used <= skiplist.bytes_used {
        *emitter = bitset;
        emitter.desc = String::from("bitset");
    } else {
        *emitter = skiplist;
        emitter.desc = String::from("skiplist");
    }
}

struct Canonicalized {
    canonical_words: Vec<u64>,
    canonicalized_words: Vec<(u8, u8)>,

    /// Maps an input unique word to the associated index (u8) which is into
    /// canonical_words or canonicalized_words (in order).
    unique_mapping: HashMap<u64, u8>,
}

impl Canonicalized {
    fn canonicalize(unique_words: &[u64]) -> Self {
        #[derive(Copy, Clone, Debug)]
        enum Mapping {
            Rotate(u32),
            Invert,
            RotateAndInvert(u32),
            ShiftRight(u32),
        }

        // key is the word being mapped to
        let mut mappings: BTreeMap<u64, Vec<(u64, Mapping)>> = BTreeMap::new();
        for &a in unique_words {
            'b: for &b in unique_words {
                // skip self
                if a == b {
                    continue;
                }

                // All possible distinct rotations
                for rotation in 1..64 {
                    if a.rotate_right(rotation) == b {
                        mappings.entry(b).or_default().push((a, Mapping::Rotate(rotation)));
                        // We're not interested in further mappings between a and b
                        continue 'b;
                    }
                }

                if (!a) == b {
                    mappings.entry(b).or_default().push((a, Mapping::Invert));
                    // We're not interested in further mappings between a and b
                    continue 'b;
                }

                // All possible distinct rotations, inverted
                for rotation in 1..64 {
                    if (!a.rotate_right(rotation)) == b {
                        mappings
                            .entry(b)
                            .or_default()
                            .push((a, Mapping::RotateAndInvert(rotation)));
                        // We're not interested in further mappings between a and b
                        continue 'b;
                    }
                }

                // All possible shifts
                for shift_by in 1..64 {
                    if a == (b >> shift_by) {
                        mappings
                            .entry(b)
                            .or_default()
                            .push((a, Mapping::ShiftRight(shift_by as u32)));
                        // We're not interested in further mappings between a and b
                        continue 'b;
                    }
                }
            }
        }
        // These are the bitset words which will be represented "raw" (as a u64)
        let mut canonical_words = Vec::new();
        // These are mapped words, which will be represented by an index into
        // the canonical_words and a Mapping; u16 when encoded.
        let mut canonicalized_words = Vec::new();
        let mut unique_mapping = HashMap::new();

        #[derive(Debug, PartialEq, Eq)]
        enum UniqueMapping {
            Canonical(usize),
            Canonicalized(usize),
        }

        // Map 0 first, so that it is the first canonical word.
        // This is realistically not inefficient because 0 is not mapped to by
        // anything else (a shift pattern could do it, but would be wasteful).
        //
        // However, 0s are quite common in the overall dataset, and it is quite
        // wasteful to have to go through a mapping function to determine that
        // we have a zero.
        //
        // FIXME: Experiment with choosing most common words in overall data set
        // for canonical when possible.
        while let Some((&to, _)) = mappings
            .iter()
            .find(|(&to, _)| to == 0)
            .or_else(|| mappings.iter().max_by_key(|m| m.1.len()))
        {
            // Get the mapping with the most entries. Currently, no mapping can
            // only exist transitively (i.e., there is no A, B, C such that A
            // does not map to C and but A maps to B maps to C), so this is
            // guaranteed to be acceptable.
            //
            // In the future, we may need a more sophisticated algorithm to
            // identify which keys to prefer as canonical.
            let mapped_from = mappings.remove(&to).unwrap();
            for (from, how) in &mapped_from {
                // Remove the entries which mapped to this one.
                // Noting that it should be associated with the Nth canonical word.
                //
                // We do not assert that this is present, because there may be
                // no mappings to the `from` word; that's fine.
                mappings.remove(from);
                assert_eq!(
                    unique_mapping
                        .insert(*from, UniqueMapping::Canonicalized(canonicalized_words.len())),
                    None
                );
                canonicalized_words.push((canonical_words.len(), *how));

                // Remove the now-canonicalized word from other mappings,
                // to ensure that we deprioritize them in the next iteration of
                // the while loop.
                for mapped in mappings.values_mut() {
                    let mut i = 0;
                    while i != mapped.len() {
                        if mapped[i].0 == *from {
                            mapped.remove(i);
                        } else {
                            i += 1;
                        }
                    }
                }
            }
            assert!(
                unique_mapping
                    .insert(to, UniqueMapping::Canonical(canonical_words.len()))
                    .is_none()
            );
            canonical_words.push(to);

            // Remove the now-canonical word from other mappings, to ensure that
            // we deprioritize them in the next iteration of the while loop.
            for mapped in mappings.values_mut() {
                let mut i = 0;
                while i != mapped.len() {
                    if mapped[i].0 == to {
                        mapped.remove(i);
                    } else {
                        i += 1;
                    }
                }
            }
        }

        // Any words which we couldn't shrink, just stick into the canonical
        // words.
        //
        // FIXME: work harder -- there are more possibilities for mapping
        // functions (e.g., multiplication, shifting instead of rotation, etc.)
        // We'll probably always have some slack though so this loop will still
        // be needed.
        for &w in unique_words {
            if !unique_mapping.contains_key(&w) {
                assert!(
                    unique_mapping
                        .insert(w, UniqueMapping::Canonical(canonical_words.len()))
                        .is_none()
                );
                canonical_words.push(w);
            }
        }
        assert_eq!(canonicalized_words.len() + canonical_words.len(), unique_words.len());
        assert_eq!(unique_mapping.len(), unique_words.len());

        let unique_mapping = unique_mapping
            .into_iter()
            .map(|(key, value)| {
                (
                    key,
                    match value {
                        UniqueMapping::Canonicalized(idx) => {
                            u8::try_from(canonical_words.len() + idx).unwrap()
                        }
                        UniqueMapping::Canonical(idx) => u8::try_from(idx).unwrap(),
                    },
                )
            })
            .collect::<HashMap<_, _>>();

        let mut distinct_indices = BTreeSet::new();
        for &w in unique_words {
            let idx = unique_mapping.get(&w).unwrap();
            assert!(distinct_indices.insert(idx));
        }

        const LOWER_6: u32 = (1 << 6) - 1;

        let canonicalized_words = canonicalized_words
            .into_iter()
            .map(|v| {
                (
                    u8::try_from(v.0).unwrap(),
                    match v.1 {
                        Mapping::RotateAndInvert(amount) => {
                            assert_eq!(amount, amount & LOWER_6);
                            1 << 6 | (amount as u8)
                        }
                        Mapping::Rotate(amount) => {
                            assert_eq!(amount, amount & LOWER_6);
                            amount as u8
                        }
                        Mapping::Invert => 1 << 6,
                        Mapping::ShiftRight(shift_by) => {
                            assert_eq!(shift_by, shift_by & LOWER_6);
                            1 << 7 | (shift_by as u8)
                        }
                    },
                )
            })
            .collect::<Vec<(u8, u8)>>();
        Canonicalized { unique_mapping, canonical_words, canonicalized_words }
    }
}
<commit_msg>Let unicode-table-generator fail gracefully for bitsets<commit_after>use crate::fmt_list;
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::convert::TryFrom;
use std::fmt::{self, Write};
use std::ops::Range;

#[derive(Clone)]
pub struct RawEmitter {
    pub file: String,
    pub desc: String,
    pub bytes_used: usize,
}

impl RawEmitter {
    pub fn new() -> RawEmitter {
        RawEmitter { file: String::new(), bytes_used: 0, desc: String::new() }
    }

    fn blank_line(&mut self) {
        if self.file.is_empty() || self.file.ends_with("\n\n") {
            return;
        }
        writeln!(&mut self.file).unwrap();
    }

    fn emit_bitset(&mut self, ranges: &[Range<u32>]) -> Result<(), String> {
        let last_code_point = ranges.last().unwrap().end;
        // bitset for every bit in the codepoint range
        //
        // + 2 to ensure an all zero word to use for padding
        let mut buckets = vec![0u64; (last_code_point as usize / 64) + 2];
        for range in ranges {
            for codepoint in range.clone() {
                let bucket = codepoint as usize / 64;
                let bit = codepoint as u64 % 64;
                buckets[bucket] |= 1 << bit;
            }
        }

        let mut words = buckets;
        // Ensure that there's a zero word in the dataset, used for padding and
        // such.
        words.push(0);
        let unique_words =
            words.iter().cloned().collect::<BTreeSet<_>>().into_iter().collect::<Vec<_>>();
        if unique_words.len() > u8::MAX as usize {
            return Err(format!("cannot pack {} into 8 bits", unique_words.len()));
        }
        // needed for the chunk mapping to work
        assert_eq!(unique_words[0], 0, "has a zero word");
        let canonicalized = Canonicalized::canonicalize(&unique_words);

        let word_indices = canonicalized.unique_mapping.clone();
        let compressed_words = words.iter().map(|w| word_indices[w]).collect::<Vec<u8>>();

        let mut best = None;
        for length in 1..=64 {
            let mut temp = self.clone();
            temp.emit_chunk_map(word_indices[&0], &compressed_words, length);
            if let Some((_, size)) = best {
                if temp.bytes_used < size {
                    best = Some((length, temp.bytes_used));
                }
            } else {
                best = Some((length, temp.bytes_used));
            }
        }
        self.emit_chunk_map(word_indices[&0], &compressed_words, best.unwrap().0);

        struct Bits(u64);
        impl fmt::Debug for Bits {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                write!(f, "0b{:064b}", self.0)
            }
        }

        writeln!(
            &mut self.file,
            "static BITSET_CANONICAL: [u64; {}] = [{}];",
            canonicalized.canonical_words.len(),
            fmt_list(canonicalized.canonical_words.iter().map(|v| Bits(*v))),
        )
        .unwrap();
        self.bytes_used += 8 * canonicalized.canonical_words.len();
        writeln!(
            &mut self.file,
            "static BITSET_MAPPING: [(u8, u8); {}] = [{}];",
            canonicalized.canonicalized_words.len(),
            fmt_list(&canonicalized.canonicalized_words),
        )
        .unwrap();
        // 8 bit index into shifted words, 7 bits for shift + optional flip
        // We only need it for the words that we removed by applying a shift and
        // flip to them.
        self.bytes_used += 2 * canonicalized.canonicalized_words.len();

        self.blank_line();

        writeln!(&mut self.file, "pub fn lookup(c: char) -> bool {{").unwrap();
        writeln!(&mut self.file, "    super::bitset_search(",).unwrap();
        writeln!(&mut self.file, "        c as u32,").unwrap();
        writeln!(&mut self.file, "        &BITSET_CHUNKS_MAP,").unwrap();
        writeln!(&mut self.file, "        &BITSET_INDEX_CHUNKS,").unwrap();
        writeln!(&mut self.file, "        &BITSET_CANONICAL,").unwrap();
        writeln!(&mut self.file, "        &BITSET_MAPPING,").unwrap();
        writeln!(&mut self.file, "    )").unwrap();
        writeln!(&mut self.file, "}}").unwrap();

        Ok(())
    }

    fn emit_chunk_map(&mut self, zero_at: u8, compressed_words: &[u8], chunk_length: usize) {
        let mut compressed_words = compressed_words.to_vec();
        for _ in 0..(chunk_length - (compressed_words.len() % chunk_length)) {
            // pad out bitset index with zero words so we have all chunks of
            // chunkchunk_length
            compressed_words.push(zero_at);
        }

        let mut chunks = BTreeSet::new();
        for chunk in compressed_words.chunks(chunk_length) {
            chunks.insert(chunk);
        }
        let chunk_map = chunks
            .clone()
            .into_iter()
            .enumerate()
            .map(|(idx, chunk)| (chunk, idx))
            .collect::<HashMap<_, _>>();
        let mut chunk_indices = Vec::new();
        for chunk in compressed_words.chunks(chunk_length) {
            chunk_indices.push(chunk_map[chunk]);
        }

        writeln!(
            &mut self.file,
            "static BITSET_CHUNKS_MAP: [u8; {}] = [{}];",
            chunk_indices.len(),
            fmt_list(&chunk_indices),
        )
        .unwrap();
        self.bytes_used += chunk_indices.len();
        writeln!(
            &mut self.file,
            "static BITSET_INDEX_CHUNKS: [[u8; {}]; {}] = [{}];",
            chunk_length,
            chunks.len(),
            fmt_list(chunks.iter()),
        )
        .unwrap();
        self.bytes_used += chunk_length * chunks.len();
    }
}

pub fn emit_codepoints(emitter: &mut RawEmitter, ranges: &[Range<u32>]) {
    emitter.blank_line();

    let mut bitset = emitter.clone();
    let bitset_ok = bitset.emit_bitset(&ranges).is_ok();

    let mut skiplist = emitter.clone();
    skiplist.emit_skiplist(&ranges);

    if bitset_ok && bitset.bytes_used <= skiplist.bytes_used {
        *emitter = bitset;
        emitter.desc = String::from("bitset");
    } else {
        *emitter = skiplist;
        emitter.desc = String::from("skiplist");
    }
}

struct Canonicalized {
    canonical_words: Vec<u64>,
    canonicalized_words: Vec<(u8, u8)>,

    /// Maps an input unique word to the associated index (u8) which is into
    /// canonical_words or canonicalized_words (in order).
    unique_mapping: HashMap<u64, u8>,
}

impl Canonicalized {
    fn canonicalize(unique_words: &[u64]) -> Self {
        #[derive(Copy, Clone, Debug)]
        enum Mapping {
            Rotate(u32),
            Invert,
            RotateAndInvert(u32),
            ShiftRight(u32),
        }

        // key is the word being mapped to
        let mut mappings: BTreeMap<u64, Vec<(u64, Mapping)>> = BTreeMap::new();
        for &a in unique_words {
            'b: for &b in unique_words {
                // skip self
                if a == b {
                    continue;
                }

                // All possible distinct rotations
                for rotation in 1..64 {
                    if a.rotate_right(rotation) == b {
                        mappings.entry(b).or_default().push((a, Mapping::Rotate(rotation)));
                        // We're not interested in further mappings between a and b
                        continue 'b;
                    }
                }

                if (!a) == b {
                    mappings.entry(b).or_default().push((a, Mapping::Invert));
                    // We're not interested in further mappings between a and b
                    continue 'b;
                }

                // All possible distinct rotations, inverted
                for rotation in 1..64 {
                    if (!a.rotate_right(rotation)) == b {
                        mappings
                            .entry(b)
                            .or_default()
                            .push((a, Mapping::RotateAndInvert(rotation)));
                        // We're not interested in further mappings between a and b
                        continue 'b;
                    }
                }

                // All possible shifts
                for shift_by in 1..64 {
                    if a == (b >> shift_by) {
                        mappings
                            .entry(b)
                            .or_default()
                            .push((a, Mapping::ShiftRight(shift_by as u32)));
                        // We're not interested in further mappings between a and b
                        continue 'b;
                    }
                }
            }
        }
        // These are the bitset words which will be represented "raw" (as a u64)
        let mut canonical_words = Vec::new();
        // These are mapped words, which will be represented by an index into
        // the canonical_words and a Mapping; u16 when encoded.
        let mut canonicalized_words = Vec::new();
        let mut unique_mapping = HashMap::new();

        #[derive(Debug, PartialEq, Eq)]
        enum UniqueMapping {
            Canonical(usize),
            Canonicalized(usize),
        }

        // Map 0 first, so that it is the first canonical word.
        // This is realistically not inefficient because 0 is not mapped to by
        // anything else (a shift pattern could do it, but would be wasteful).
        //
        // However, 0s are quite common in the overall dataset, and it is quite
        // wasteful to have to go through a mapping function to determine that
        // we have a zero.
        //
        // FIXME: Experiment with choosing most common words in overall data set
        // for canonical when possible.
        while let Some((&to, _)) = mappings
            .iter()
            .find(|(&to, _)| to == 0)
            .or_else(|| mappings.iter().max_by_key(|m| m.1.len()))
        {
            // Get the mapping with the most entries. Currently, no mapping can
            // only exist transitively (i.e., there is no A, B, C such that A
            // does not map to C and but A maps to B maps to C), so this is
            // guaranteed to be acceptable.
            //
            // In the future, we may need a more sophisticated algorithm to
            // identify which keys to prefer as canonical.
            let mapped_from = mappings.remove(&to).unwrap();
            for (from, how) in &mapped_from {
                // Remove the entries which mapped to this one.
                // Noting that it should be associated with the Nth canonical word.
                //
                // We do not assert that this is present, because there may be
                // no mappings to the `from` word; that's fine.
                mappings.remove(from);
                assert_eq!(
                    unique_mapping
                        .insert(*from, UniqueMapping::Canonicalized(canonicalized_words.len())),
                    None
                );
                canonicalized_words.push((canonical_words.len(), *how));

                // Remove the now-canonicalized word from other mappings,
                // to ensure that we deprioritize them in the next iteration of
                // the while loop.
                for mapped in mappings.values_mut() {
                    let mut i = 0;
                    while i != mapped.len() {
                        if mapped[i].0 == *from {
                            mapped.remove(i);
                        } else {
                            i += 1;
                        }
                    }
                }
            }
            assert!(
                unique_mapping
                    .insert(to, UniqueMapping::Canonical(canonical_words.len()))
                    .is_none()
            );
            canonical_words.push(to);

            // Remove the now-canonical word from other mappings, to ensure that
            // we deprioritize them in the next iteration of the while loop.
            for mapped in mappings.values_mut() {
                let mut i = 0;
                while i != mapped.len() {
                    if mapped[i].0 == to {
                        mapped.remove(i);
                    } else {
                        i += 1;
                    }
                }
            }
        }

        // Any words which we couldn't shrink, just stick into the canonical
        // words.
        //
        // FIXME: work harder -- there are more possibilities for mapping
        // functions (e.g., multiplication, shifting instead of rotation, etc.)
        // We'll probably always have some slack though so this loop will still
        // be needed.
        for &w in unique_words {
            if !unique_mapping.contains_key(&w) {
                assert!(
                    unique_mapping
                        .insert(w, UniqueMapping::Canonical(canonical_words.len()))
                        .is_none()
                );
                canonical_words.push(w);
            }
        }
        assert_eq!(canonicalized_words.len() + canonical_words.len(), unique_words.len());
        assert_eq!(unique_mapping.len(), unique_words.len());

        let unique_mapping = unique_mapping
            .into_iter()
            .map(|(key, value)| {
                (
                    key,
                    match value {
                        UniqueMapping::Canonicalized(idx) => {
                            u8::try_from(canonical_words.len() + idx).unwrap()
                        }
                        UniqueMapping::Canonical(idx) => u8::try_from(idx).unwrap(),
                    },
                )
            })
            .collect::<HashMap<_, _>>();

        let mut distinct_indices = BTreeSet::new();
        for &w in unique_words {
            let idx = unique_mapping.get(&w).unwrap();
            assert!(distinct_indices.insert(idx));
        }

        const LOWER_6: u32 = (1 << 6) - 1;

        let canonicalized_words = canonicalized_words
            .into_iter()
            .map(|v| {
                (
                    u8::try_from(v.0).unwrap(),
                    match v.1 {
                        Mapping::RotateAndInvert(amount) => {
                            assert_eq!(amount, amount & LOWER_6);
                            1 << 6 | (amount as u8)
                        }
                        Mapping::Rotate(amount) => {
                            assert_eq!(amount, amount & LOWER_6);
                            amount as u8
                        }
                        Mapping::Invert => 1 << 6,
                        Mapping::ShiftRight(shift_by) => {
                            assert_eq!(shift_by, shift_by & LOWER_6);
                            1 << 7 | (shift_by as u8)
                        }
                    },
                )
            })
            .collect::<Vec<(u8, u8)>>();
        Canonicalized { unique_mapping, canonical_words, canonicalized_words }
    }
}
<|endoftext|>


<commit_before>use field::PuyoPlainField;
use frame;
use rensa_result::RensaResult;

#[cfg(all(target_feature = "avx2", target_feature = "bmi2"))]
use field::BitField;

fn run_plainfield_test(mut pf: PuyoPlainField, expected_result: &RensaResult) {
    let actual_result = pf.simulate();

    assert_eq!(actual_result.chain, expected_result.chain);
    assert_eq!(actual_result.score, expected_result.score);
    // Check only frames is not zero.
    if expected_result.frame > 0 {
        assert_eq!(actual_result.frame, expected_result.frame);
    }
    assert_eq!(actual_result.quick, expected_result.quick);
}

#[cfg(all(target_feature = "avx2", target_feature = "bmi2"))]
fn run_bitfield_test(mut bf: BitField, expected_result: &RensaResult) {
    let actual_result = bf.simulate();

    assert_eq!(actual_result.chain, expected_result.chain);
    assert_eq!(actual_result.score, expected_result.score);
    // Check only frames is not zero.
    if expected_result.frame > 0 {
        assert_eq!(actual_result.frame, expected_result.frame);
    }
    assert_eq!(actual_result.quick, expected_result.quick);
}

#[cfg(all(target_feature = "avx2", target_feature = "bmi2"))]
fn run_test(src: &str, expected_result: RensaResult) {
    run_plainfield_test(PuyoPlainField::from_str(src), &expected_result);
    run_bitfield_test(BitField::from_str(src), &expected_result);
}

#[cfg(not(all(target_feature = "avx2", target_feature = "bmi2")))]
fn run_test(src: &str, expected_result: RensaResult) {
    run_plainfield_test(PuyoPlainField::from_str(src), &expected_result);
}

#[test]
fn test_simulate_1rensa_quick() {
    let src = "..RRRR";

    let expected_result = RensaResult {
        chain: 1,
        score: 40,
        frame: frame::FRAMES_VANISH_ANIMATION,
        quick: true,
    };

    run_test(src, expected_result);
}

#[test]
fn test_simulate_1rensa_nonquick_case1() {
    let src = concat!(
        ".....Y",
        "..RRRR");

    let expected_result = RensaResult {
        chain: 1,
        score: 40,
        frame: frame::FRAMES_VANISH_ANIMATION + frame::FRAMES_TO_DROP_FAST[1] + frame::FRAMES_GROUNDING,
        quick: false,
    };

    run_test(src, expected_result);
}

#[test]
fn test_simulate_1rensa_nonquick_case2() {
    let src = concat!(
        ".....Y",
        ".....R",
        "...RRR");

    let expected_result = RensaResult {
        chain: 1,
        score: 40,
        frame: frame::FRAMES_VANISH_ANIMATION + frame::FRAMES_TO_DROP_FAST[2] + frame::FRAMES_GROUNDING,
        quick: false,
    };

    run_test(src, expected_result);
}

#[test]
fn test_simulate_1rensa_nonquick_case3() {
    let src = concat!(
        ".....Y",
        "....YR",
        "...RRR");

    let expected_result = RensaResult {
        chain: 1,
        score: 40,
        frame: frame::FRAMES_VANISH_ANIMATION + frame::FRAMES_TO_DROP_FAST[2] + frame::FRAMES_GROUNDING,
        quick: false,
    };

    run_test(src, expected_result);
}

#[test]
fn test_simulate_2rensa_quick() {
    let src = concat!(
        "Y.....",
        "RYY...",
        "RRRY..");

    let expected_result = RensaResult {
        chain: 2,
        score: 40 + 40 * 8,
        frame: frame::FRAMES_VANISH_ANIMATION + frame::FRAMES_TO_DROP_FAST[2] + frame::FRAMES_GROUNDING +
            frame::FRAMES_VANISH_ANIMATION,
        quick: true,
    };

    run_test(src, expected_result);
}

#[test]
fn test_simulate_2rensa_nonquick_case1() {
    let src = concat!(
        "YB....",
        "RYY...",
        "RRRY..");

    let expected_result = RensaResult {
        chain: 2,
        score: 40 + 40 * 8,
        frame: frame::FRAMES_VANISH_ANIMATION + frame::FRAMES_TO_DROP_FAST[2] + frame::FRAMES_GROUNDING +
            frame::FRAMES_VANISH_ANIMATION + frame::FRAMES_TO_DROP_FAST[1] + frame::FRAMES_GROUNDING,
        quick: false,
    };

    run_test(src, expected_result);
}

#[test]
fn test_simulate_2rensa_nonquick_case2() {
    let src = concat!(
        "..B...",
        "..BBYB",
        "RRRRBB");

    let expected_result = RensaResult {
        chain: 2,
        score: 700,
        frame: frame::FRAMES_VANISH_ANIMATION + frame::FRAMES_TO_DROP_FAST[1] + frame::FRAMES_GROUNDING +
            frame::FRAMES_VANISH_ANIMATION + frame::FRAMES_TO_DROP_FAST[1] + frame::FRAMES_GROUNDING,
        quick: false
    };

    run_test(src, expected_result);
}

#[test]
fn test_simulate_19rensa_case1() {
    let src = concat!(
        ".G.BRG",
        "GBRRYR",
        "RRYYBY",
        "RGYRBR",
        "YGYRBY",
        "YGBGYR",
        "GRBGYR",
        "BRBYBY",
        "RYYBYY",
        "BRBYBR",
        "BGBYRR",
        "YGBGBG",
        "RBGBGG");

    let expected_result = RensaResult {
        chain: 19,
        score: 175080,
        frame: 0,
        quick: true,
    };

    run_test(src, expected_result);
}

#[test]
fn test_simulate() {
    run_test(".B.GRBBGRRYRRRYYGYRBYRGRYBYRGYYBGBYRBRGBYRGRGYGYRYYGYYGRGYGRGBGYRRYBGBGBRGBGBB",
             RensaResult::new(19, 175080, 0, true));
    run_test("B..RYGGYGYGBGRRRBRGBRYBGRRGGYGYYGYRRYGRBRBRBBGYGRGGYRRRBGRGRYBYRBBRRYGGBRBBYRY",
             RensaResult::new(19, 175080, 0, true));
    run_test("...BB..B.RBB.RBRBO.RBGRB.GRGRB.GRYRB.YGYGR.YGYGR.BYBYG.BYBYGBOBOYGRRRROYBBBOBB",
             RensaResult::new(2, 38540, 0, true));
    run_test(".B.BB..R.RBB.GBRBO.GBGRB.YRGRB.YRYRB.YGYGR.BGYGRGRGBGRGGYBYGYGBOBYYRRROBBBBOBB",
             RensaResult::new(3, 43260, 0, true));
    run_test("...BB..R.RBB.GBRBOGGBGRBRYRGRBRYRYRBRYGYGRRBGYGRORGBGRGGYBYGYGBOBYYRRROBBBBOBB",
             RensaResult::new(4, 50140, 0, true));
    run_test("GRBBB.BGYRBBYYYRBOOGBGRBBYRGRBBYRGRBBYGYGROBGYGRGRGBGRGGYBYYYOBOBYYRRROBBBBOBB",
             RensaResult::new(5, 68700, 0, true));
    run_test("RRRROOOOROROROROOROOROOORORORRROOOORROROOOOORRORROOOORRROOOROOOORORRROROOOOORO",
             RensaResult::new(4, 4840, 0, true));
    run_test("BRBBRRBRRRBRRBRBRBRBRBRBRBRBRBBRBRBRRRBBRRBBRRBBRBRBRBBRBRBRBBRBRRRRBRBBRBBRRB",
             RensaResult::new(9, 49950, 0, true));
    run_test("RRRRRYBRRYOORRYOYRBYRRROBRYOYYBYBYOBRBRBBORRORRROOOOOOOOOOOOOOOOOOOOOOOOOOOOOO",
             RensaResult::new(9, 32760, 0, true));
    run_test("YYGBRGRYYBBBYYOYGGRGORGBRBORRGRYOYYYYYOBRGRBGRGGBBYRRYGGYBBBGRRYRYRGYRYYGRRBBB",
             RensaResult::new(18, 155980, 0, true));
    run_test("RRR.RRORRROROORORRROOROORORORRORORORRORORORROROROORROORROORRROROROORORORORRORR",
             RensaResult::new(11, 47080, 0, true));
    run_test("......RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR",
             RensaResult::new(1, 7200, 0, true));
}
<commit_msg>:recycle: Refactoring: Rename run_plainfield_test<commit_after>use field::PuyoPlainField;
use frame;
use rensa_result::RensaResult;

#[cfg(all(target_feature = "avx2", target_feature = "bmi2"))]
use field::BitField;

fn run_puyoplainfield_test(mut pf: PuyoPlainField, expected_result: &RensaResult) {
    let actual_result = pf.simulate();

    assert_eq!(actual_result.chain, expected_result.chain);
    assert_eq!(actual_result.score, expected_result.score);
    // Check only frames is not zero.
    if expected_result.frame > 0 {
        assert_eq!(actual_result.frame, expected_result.frame);
    }
    assert_eq!(actual_result.quick, expected_result.quick);
}

#[cfg(all(target_feature = "avx2", target_feature = "bmi2"))]
fn run_bitfield_test(mut bf: BitField, expected_result: &RensaResult) {
    let actual_result = bf.simulate();

    assert_eq!(actual_result.chain, expected_result.chain);
    assert_eq!(actual_result.score, expected_result.score);
    // Check only frames is not zero.
    if expected_result.frame > 0 {
        assert_eq!(actual_result.frame, expected_result.frame);
    }
    assert_eq!(actual_result.quick, expected_result.quick);
}

#[cfg(all(target_feature = "avx2", target_feature = "bmi2"))]
fn run_test(src: &str, expected_result: RensaResult) {
    run_puyoplainfield_test(PuyoPlainField::from_str(src), &expected_result);
    run_bitfield_test(BitField::from_str(src), &expected_result);
}

#[cfg(not(all(target_feature = "avx2", target_feature = "bmi2")))]
fn run_test(src: &str, expected_result: RensaResult) {
    run_puyoplainfield_test(PuyoPlainField::from_str(src), &expected_result);
}

#[test]
fn test_simulate_1rensa_quick() {
    let src = "..RRRR";

    let expected_result = RensaResult {
        chain: 1,
        score: 40,
        frame: frame::FRAMES_VANISH_ANIMATION,
        quick: true,
    };

    run_test(src, expected_result);
}

#[test]
fn test_simulate_1rensa_nonquick_case1() {
    let src = concat!(
        ".....Y",
        "..RRRR");

    let expected_result = RensaResult {
        chain: 1,
        score: 40,
        frame: frame::FRAMES_VANISH_ANIMATION + frame::FRAMES_TO_DROP_FAST[1] + frame::FRAMES_GROUNDING,
        quick: false,
    };

    run_test(src, expected_result);
}

#[test]
fn test_simulate_1rensa_nonquick_case2() {
    let src = concat!(
        ".....Y",
        ".....R",
        "...RRR");

    let expected_result = RensaResult {
        chain: 1,
        score: 40,
        frame: frame::FRAMES_VANISH_ANIMATION + frame::FRAMES_TO_DROP_FAST[2] + frame::FRAMES_GROUNDING,
        quick: false,
    };

    run_test(src, expected_result);
}

#[test]
fn test_simulate_1rensa_nonquick_case3() {
    let src = concat!(
        ".....Y",
        "....YR",
        "...RRR");

    let expected_result = RensaResult {
        chain: 1,
        score: 40,
        frame: frame::FRAMES_VANISH_ANIMATION + frame::FRAMES_TO_DROP_FAST[2] + frame::FRAMES_GROUNDING,
        quick: false,
    };

    run_test(src, expected_result);
}

#[test]
fn test_simulate_2rensa_quick() {
    let src = concat!(
        "Y.....",
        "RYY...",
        "RRRY..");

    let expected_result = RensaResult {
        chain: 2,
        score: 40 + 40 * 8,
        frame: frame::FRAMES_VANISH_ANIMATION + frame::FRAMES_TO_DROP_FAST[2] + frame::FRAMES_GROUNDING +
            frame::FRAMES_VANISH_ANIMATION,
        quick: true,
    };

    run_test(src, expected_result);
}

#[test]
fn test_simulate_2rensa_nonquick_case1() {
    let src = concat!(
        "YB....",
        "RYY...",
        "RRRY..");

    let expected_result = RensaResult {
        chain: 2,
        score: 40 + 40 * 8,
        frame: frame::FRAMES_VANISH_ANIMATION + frame::FRAMES_TO_DROP_FAST[2] + frame::FRAMES_GROUNDING +
            frame::FRAMES_VANISH_ANIMATION + frame::FRAMES_TO_DROP_FAST[1] + frame::FRAMES_GROUNDING,
        quick: false,
    };

    run_test(src, expected_result);
}

#[test]
fn test_simulate_2rensa_nonquick_case2() {
    let src = concat!(
        "..B...",
        "..BBYB",
        "RRRRBB");

    let expected_result = RensaResult {
        chain: 2,
        score: 700,
        frame: frame::FRAMES_VANISH_ANIMATION + frame::FRAMES_TO_DROP_FAST[1] + frame::FRAMES_GROUNDING +
            frame::FRAMES_VANISH_ANIMATION + frame::FRAMES_TO_DROP_FAST[1] + frame::FRAMES_GROUNDING,
        quick: false
    };

    run_test(src, expected_result);
}

#[test]
fn test_simulate_19rensa_case1() {
    let src = concat!(
        ".G.BRG",
        "GBRRYR",
        "RRYYBY",
        "RGYRBR",
        "YGYRBY",
        "YGBGYR",
        "GRBGYR",
        "BRBYBY",
        "RYYBYY",
        "BRBYBR",
        "BGBYRR",
        "YGBGBG",
        "RBGBGG");

    let expected_result = RensaResult {
        chain: 19,
        score: 175080,
        frame: 0,
        quick: true,
    };

    run_test(src, expected_result);
}

#[test]
fn test_simulate() {
    run_test(".B.GRBBGRRYRRRYYGYRBYRGRYBYRGYYBGBYRBRGBYRGRGYGYRYYGYYGRGYGRGBGYRRYBGBGBRGBGBB",
             RensaResult::new(19, 175080, 0, true));
    run_test("B..RYGGYGYGBGRRRBRGBRYBGRRGGYGYYGYRRYGRBRBRBBGYGRGGYRRRBGRGRYBYRBBRRYGGBRBBYRY",
             RensaResult::new(19, 175080, 0, true));
    run_test("...BB..B.RBB.RBRBO.RBGRB.GRGRB.GRYRB.YGYGR.YGYGR.BYBYG.BYBYGBOBOYGRRRROYBBBOBB",
             RensaResult::new(2, 38540, 0, true));
    run_test(".B.BB..R.RBB.GBRBO.GBGRB.YRGRB.YRYRB.YGYGR.BGYGRGRGBGRGGYBYGYGBOBYYRRROBBBBOBB",
             RensaResult::new(3, 43260, 0, true));
    run_test("...BB..R.RBB.GBRBOGGBGRBRYRGRBRYRYRBRYGYGRRBGYGRORGBGRGGYBYGYGBOBYYRRROBBBBOBB",
             RensaResult::new(4, 50140, 0, true));
    run_test("GRBBB.BGYRBBYYYRBOOGBGRBBYRGRBBYRGRBBYGYGROBGYGRGRGBGRGGYBYYYOBOBYYRRROBBBBOBB",
             RensaResult::new(5, 68700, 0, true));
    run_test("RRRROOOOROROROROOROOROOORORORRROOOORROROOOOORRORROOOORRROOOROOOORORRROROOOOORO",
             RensaResult::new(4, 4840, 0, true));
    run_test("BRBBRRBRRRBRRBRBRBRBRBRBRBRBRBBRBRBRRRBBRRBBRRBBRBRBRBBRBRBRBBRBRRRRBRBBRBBRRB",
             RensaResult::new(9, 49950, 0, true));
    run_test("RRRRRYBRRYOORRYOYRBYRRROBRYOYYBYBYOBRBRBBORRORRROOOOOOOOOOOOOOOOOOOOOOOOOOOOOO",
             RensaResult::new(9, 32760, 0, true));
    run_test("YYGBRGRYYBBBYYOYGGRGORGBRBORRGRYOYYYYYOBRGRBGRGGBBYRRYGGYBBBGRRYRYRGYRYYGRRBBB",
             RensaResult::new(18, 155980, 0, true));
    run_test("RRR.RRORRROROORORRROOROORORORRORORORRORORORROROROORROORROORRROROROORORORORRORR",
             RensaResult::new(11, 47080, 0, true));
    run_test("......RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR",
             RensaResult::new(1, 7200, 0, true));
}
<|endoftext|>