/* 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}; 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); } macro_rules! cprintln { ($($t:tt)*) => ( #[allow(unused_unsafe)] unsafe { log(&format_args!($($t)*).to_string()) } ) } // Exported functions #[wasm_bindgen] pub fn election_from_blt_float64(text: String) -> ElectionFloat64 { // Install panic! hook console_error_panic_hook::set_once(); let election: Election = Election::from_blt(text.lines().map(|s| s.to_string()).into_iter()); return ElectionFloat64(election); } #[wasm_bindgen] pub fn count_init_float64(state: &mut CountStateFloat64, opts: &STVOptions) { stv::count_init(&mut state.0, &opts.0); } #[wasm_bindgen] pub fn count_one_stage_float64(state: &mut CountStateFloat64, opts: &STVOptions) -> bool { return stv::count_one_stage(&mut state.0, &opts.0); } // 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!(""); } #[wasm_bindgen] pub fn make_and_print_result_float64(stage_num: usize, state: &CountStateFloat64) { 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 CountStateFloat64(CountState<'static, NativeFloat64>); #[wasm_bindgen] impl CountStateFloat64 { pub fn new(election: &ElectionFloat64) -> Self { return CountStateFloat64(CountState::new(election.as_static())); } } #[wasm_bindgen] pub struct ElectionFloat64(Election); #[wasm_bindgen] impl ElectionFloat64 { pub fn seats(&self) -> usize { self.0.seats } fn as_static(&self) -> &'static Election { // Need to have this as we cannot specify &'static in wasm-bindgen: issue #1187 unsafe { let ptr = &self.0 as *const Election; &*ptr } } } #[wasm_bindgen] pub struct STVOptions(stv::STVOptions<'static>); #[wasm_bindgen] impl STVOptions { pub fn new(round_votes: Option, exclusion: String) -> Self { if exclusion == "one_round" { return STVOptions(stv::STVOptions { round_votes: round_votes, exclusion: &"one_round", }); } else { panic!("Unknown --exclusion"); } } }