/* OpenTally: Open-source election vote counting * Copyright © 2021 Lee Yingtong Li (RunasSudo) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use crate::election::{Candidate, CandidateState, CountCard, CountState, CountStateOrRef, Election, StageResult}; use crate::numbers::{NativeFloat64, Number, Rational}; use crate::stv; extern crate console_error_panic_hook; use wasm_bindgen::prelude::wasm_bindgen; // Logging #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_namespace = console)] fn log(s: &str); } /// println! to console macro_rules! cprintln { ($($t:tt)*) => ( #[allow(unused_unsafe)] unsafe { log(&format_args!($($t)*).to_string()) } ) } // Helper macros for making functions macro_rules! impl_type { ($type:ident) => { paste::item! { // Exported functions #[wasm_bindgen] #[allow(non_snake_case)] pub fn [](text: String) -> [] { // Install panic! hook console_error_panic_hook::set_once(); let election: Election<$type> = Election::from_blt(text.lines().map(|s| s.to_string()).into_iter()); return [](election); } #[wasm_bindgen] #[allow(non_snake_case)] pub fn [](state: &mut [], opts: &stv::STVOptions) { stv::count_init(&mut state.0, &opts); } #[wasm_bindgen] #[allow(non_snake_case)] pub fn [](state: &mut [], opts: &stv::STVOptions) -> bool { return stv::count_one_stage(&mut state.0, &opts); } // Reporting #[wasm_bindgen] #[allow(non_snake_case)] pub fn [](stage_num: usize, state: &[]) { let result = StageResult { kind: state.0.kind, title: &state.0.title, logs: state.0.logger.render(), state: CountStateOrRef::from(&state.0), }; print_stage(stage_num, &result); } // Wrapper structs // Required as we cannot specify &'static in wasm-bindgen: issue #1187 #[wasm_bindgen] pub struct [](CountState<'static, $type>); #[wasm_bindgen] impl [] { pub fn new(election: &[]) -> Self { return [](CountState::new(election.as_static())); } } #[wasm_bindgen] pub struct [](Election<$type>); #[wasm_bindgen] impl [] { pub fn seats(&self) -> usize { self.0.seats } fn as_static(&self) -> &'static Election<$type> { // Need to have this as we cannot specify &'static in wasm-bindgen: issue #1187 unsafe { let ptr = &self.0 as *const Election<$type>; &*ptr } } } }} } impl_type!(Rational); impl_type!(NativeFloat64); // Reporting fn print_candidates<'a, N: 'a + Number, I: Iterator)>>(candidates: I) { for (candidate, count_card) in candidates { if count_card.state == CandidateState::ELECTED { cprintln!("- {}: {:.dps$} ({:.dps$}) - ELECTED {}", candidate.name, count_card.votes, count_card.transfers, count_card.order_elected, dps=2); } else if count_card.state == CandidateState::EXCLUDED { cprintln!("- {}: {:.dps$} ({:.dps$}) - Excluded {}", candidate.name, count_card.votes, count_card.transfers, -count_card.order_elected, dps=2); } else { cprintln!("- {}: {:.dps$} ({:.dps$})", candidate.name, count_card.votes, count_card.transfers, dps=2); } } } fn print_stage(stage_num: usize, result: &StageResult) { // Print stage details match result.kind { None => { cprintln!("{}. {}", stage_num, result.title); } Some(kind) => { cprintln!("{}. {} {}", stage_num, kind, result.title); } }; cprintln!("{}", result.logs.join(" ")); let state = result.state.as_ref(); // Print candidates let candidates = state.election.candidates.iter() .map(|c| (c, state.candidates.get(c).unwrap())); print_candidates(candidates); // Print summary rows cprintln!("Exhausted: {:.dps$} ({:.dps$})", state.exhausted.votes, state.exhausted.transfers, dps=2); cprintln!("Loss by fraction: {:.dps$} ({:.dps$})", state.loss_fraction.votes, state.loss_fraction.transfers, dps=2); let mut total_vote = state.candidates.values().fold(N::zero(), |acc, cc| { acc + &cc.votes }); total_vote += &state.exhausted.votes; total_vote += &state.loss_fraction.votes; cprintln!("Total votes: {:.dps$}", total_vote, dps=2); cprintln!("Quota: {:.dps$}", state.quota, dps=2); cprintln!(""); }