Better error messages
This commit is contained in:
parent
bfeec6f839
commit
83d0a9bb80
|
@ -4,9 +4,9 @@ PATH=$PATH:$HOME/.cargo/bin
|
||||||
# Build cargo
|
# Build cargo
|
||||||
PROFILE=${1:-release}
|
PROFILE=${1:-release}
|
||||||
if [ $PROFILE == 'debug' ]; then
|
if [ $PROFILE == 'debug' ]; then
|
||||||
cargo build --lib --target wasm32-unknown-unknown
|
cargo build --lib --target wasm32-unknown-unknown || exit 1
|
||||||
else
|
else
|
||||||
cargo build --lib --target wasm32-unknown-unknown --$PROFILE
|
cargo build --lib --target wasm32-unknown-unknown --$PROFILE || exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Apply wasm-bindgen
|
# Apply wasm-bindgen
|
||||||
|
|
|
@ -97,6 +97,9 @@ worker.onmessage = function(evt) {
|
||||||
response = window.prompt(evt.data.message);
|
response = window.prompt(evt.data.message);
|
||||||
}
|
}
|
||||||
worker.postMessage({'type': 'userInput', 'response': response});
|
worker.postMessage({'type': 'userInput', 'response': response});
|
||||||
|
|
||||||
|
} else if (evt.data.type === 'errorMessage') {
|
||||||
|
divLogs2.insertAdjacentHTML('beforeend', evt.data.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,6 +158,9 @@ async function clickCount() {
|
||||||
parseInt(document.getElementById('txtPPDP').value),
|
parseInt(document.getElementById('txtPPDP').value),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Reset UI
|
||||||
|
document.getElementById('printPane').style.display = 'none';
|
||||||
|
divLogs2.innerHTML = ''; // Might have error messages from previous execution
|
||||||
|
|
||||||
// Dispatch to worker
|
// Dispatch to worker
|
||||||
worker.postMessage({
|
worker.postMessage({
|
||||||
|
|
|
@ -20,7 +20,10 @@ initWasm();
|
||||||
var numbers, election, opts, state, stageNum;
|
var numbers, election, opts, state, stageNum;
|
||||||
|
|
||||||
onmessage = function(evt) {
|
onmessage = function(evt) {
|
||||||
|
try {
|
||||||
if (evt.data.type === 'countElection') {
|
if (evt.data.type === 'countElection') {
|
||||||
|
errored = false;
|
||||||
|
|
||||||
if (evt.data.numbers === 'fixed') {
|
if (evt.data.numbers === 'fixed') {
|
||||||
numbers = 'Fixed';
|
numbers = 'Fixed';
|
||||||
wasm.fixed_set_dps(evt.data.decimals);
|
wasm.fixed_set_dps(evt.data.decimals);
|
||||||
|
@ -78,6 +81,13 @@ onmessage = function(evt) {
|
||||||
wasmRaw.asyncify_start_rewind(DATA_ADDR);
|
wasmRaw.asyncify_start_rewind(DATA_ADDR);
|
||||||
resumeCount();
|
resumeCount();
|
||||||
}
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
if (errored) {
|
||||||
|
// Panic already logged and sent to UI
|
||||||
|
} else {
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resumeCount() {
|
function resumeCount() {
|
||||||
|
@ -102,6 +112,12 @@ function resumeCount() {
|
||||||
postMessage({'type': 'finalResultSummary', 'summary': wasm['final_result_summary_' + numbers](state, opts)});
|
postMessage({'type': 'finalResultSummary', 'summary': wasm['final_result_summary_' + numbers](state, opts)});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errored = false;
|
||||||
|
function wasm_error(message) {
|
||||||
|
postMessage({'type': 'errorMessage', 'message': message});
|
||||||
|
errored = true;
|
||||||
|
}
|
||||||
|
|
||||||
var userInputBuffer = null;
|
var userInputBuffer = null;
|
||||||
|
|
||||||
function get_user_input(message) {
|
function get_user_input(message) {
|
||||||
|
|
|
@ -18,12 +18,21 @@
|
||||||
use crate::constraints::{Constraints, ConstraintMatrix};
|
use crate::constraints::{Constraints, ConstraintMatrix};
|
||||||
use crate::logger::Logger;
|
use crate::logger::Logger;
|
||||||
use crate::numbers::Number;
|
use crate::numbers::Number;
|
||||||
use crate::parser::blt::BLTParser;
|
use crate::parser::blt::{BLTParser, ParseError};
|
||||||
use crate::sharandom::SHARandom;
|
use crate::sharandom::SHARandom;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::iter::Peekable;
|
use std::iter::Peekable;
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use utf8_chars::BufReadCharsExt;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use std::fs::File;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use std::io::BufReader;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
/// An election to be counted
|
/// An election to be counted
|
||||||
pub struct Election<N> {
|
pub struct Election<N> {
|
||||||
/// Name of the election
|
/// Name of the election
|
||||||
|
@ -42,12 +51,18 @@ pub struct Election<N> {
|
||||||
|
|
||||||
impl<N: Number> Election<N> {
|
impl<N: Number> Election<N> {
|
||||||
/// Parse the given BLT file and return an [Election]
|
/// Parse the given BLT file and return an [Election]
|
||||||
pub fn from_blt<I: Iterator<Item=char>>(input: Peekable<I>) -> Self {
|
pub fn from_blt<I: Iterator<Item=char>>(input: Peekable<I>) -> Result<Self, ParseError> {
|
||||||
let mut parser = BLTParser::new(input);
|
let mut parser = BLTParser::new(input);
|
||||||
match parser.parse_blt() {
|
parser.parse_blt()?;
|
||||||
Ok(_) => { return parser.as_election(); }
|
return Ok(parser.as_election());
|
||||||
Err(e) => { panic!("Syntax Error: {}", e); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse the BLT file at the given path and return an [Election]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, ParseError> {
|
||||||
|
let mut reader = BufReader::new(File::open(path).expect("IO Error"));
|
||||||
|
let chars = reader.chars().map(|r| r.expect("IO Error")).peekable();
|
||||||
|
return Ok(Election::from_blt(chars)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert ballots with weight >1 to multiple ballots of weight 1
|
/// Convert ballots with weight >1 to multiple ballots of weight 1
|
||||||
|
|
80
src/main.rs
80
src/main.rs
|
@ -21,11 +21,10 @@ use opentally::numbers::{Fixed, GuardedFixed, NativeFloat64, Number, Rational};
|
||||||
use opentally::stv;
|
use opentally::stv;
|
||||||
|
|
||||||
use clap::{AppSettings, Clap};
|
use clap::{AppSettings, Clap};
|
||||||
use utf8_chars::BufReadCharsExt;
|
|
||||||
|
|
||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, BufRead, BufReader};
|
use std::io::{self, BufRead};
|
||||||
use std::ops;
|
use std::ops;
|
||||||
|
|
||||||
/// Open-source election vote counting
|
/// Open-source election vote counting
|
||||||
|
@ -186,36 +185,54 @@ struct STV {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
match main_() {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(code) => {
|
||||||
|
std::process::exit(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main_() -> Result<(), i32> {
|
||||||
// Read arguments
|
// Read arguments
|
||||||
let opts: Opts = Opts::parse();
|
let opts: Opts = Opts::parse();
|
||||||
let Command::STV(cmd_opts) = opts.command;
|
let Command::STV(cmd_opts) = opts.command;
|
||||||
|
|
||||||
// Read BLT file
|
// Read and count election according to --numbers
|
||||||
let mut reader = BufReader::new(File::open(&cmd_opts.filename).expect("IO Error"));
|
|
||||||
let chars = reader.chars().map(|r| r.expect("IO Error")).peekable();
|
|
||||||
|
|
||||||
// Create and count election according to --numbers
|
|
||||||
if cmd_opts.numbers == "rational" {
|
if cmd_opts.numbers == "rational" {
|
||||||
let mut election: Election<Rational> = Election::from_blt(chars);
|
let mut election = election_from_file(&cmd_opts.filename)?;
|
||||||
maybe_load_constraints(&mut election, &cmd_opts.constraints);
|
maybe_load_constraints(&mut election, &cmd_opts.constraints);
|
||||||
|
|
||||||
// Must specify ::<N> here and in a few other places because ndarray causes E0275 otherwise
|
// Must specify ::<N> here and in a few other places because ndarray causes E0275 otherwise
|
||||||
count_election::<Rational>(election, cmd_opts);
|
count_election::<Rational>(election, cmd_opts)?;
|
||||||
} else if cmd_opts.numbers == "float64" {
|
} else if cmd_opts.numbers == "float64" {
|
||||||
let mut election: Election<NativeFloat64> = Election::from_blt(chars);
|
let mut election = election_from_file(&cmd_opts.filename)?;
|
||||||
maybe_load_constraints(&mut election, &cmd_opts.constraints);
|
maybe_load_constraints(&mut election, &cmd_opts.constraints);
|
||||||
count_election::<NativeFloat64>(election, cmd_opts);
|
count_election::<NativeFloat64>(election, cmd_opts)?;
|
||||||
} else if cmd_opts.numbers == "fixed" {
|
} else if cmd_opts.numbers == "fixed" {
|
||||||
Fixed::set_dps(cmd_opts.decimals);
|
Fixed::set_dps(cmd_opts.decimals);
|
||||||
let mut election: Election<Fixed> = Election::from_blt(chars);
|
|
||||||
|
let mut election = election_from_file(&cmd_opts.filename)?;
|
||||||
maybe_load_constraints(&mut election, &cmd_opts.constraints);
|
maybe_load_constraints(&mut election, &cmd_opts.constraints);
|
||||||
count_election::<Fixed>(election, cmd_opts);
|
count_election::<Fixed>(election, cmd_opts)?;
|
||||||
} else if cmd_opts.numbers == "gfixed" {
|
} else if cmd_opts.numbers == "gfixed" {
|
||||||
GuardedFixed::set_dps(cmd_opts.decimals);
|
GuardedFixed::set_dps(cmd_opts.decimals);
|
||||||
|
|
||||||
let mut election: Election<GuardedFixed> = Election::from_blt(chars);
|
let mut election = election_from_file(&cmd_opts.filename)?;
|
||||||
maybe_load_constraints(&mut election, &cmd_opts.constraints);
|
maybe_load_constraints(&mut election, &cmd_opts.constraints);
|
||||||
count_election::<GuardedFixed>(election, cmd_opts);
|
count_election::<GuardedFixed>(election, cmd_opts)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn election_from_file<N: Number>(path: &str) -> Result<Election<N>, i32> {
|
||||||
|
match Election::from_file(path) {
|
||||||
|
Ok(e) => return Ok(e),
|
||||||
|
Err(err) => {
|
||||||
|
println!("Syntax Error: {}", err);
|
||||||
|
return Err(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,7 +244,7 @@ fn maybe_load_constraints<N: Number>(election: &mut Election<N>, constraints: &O
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn count_election<N: Number>(mut election: Election<N>, cmd_opts: STV)
|
fn count_election<N: Number>(mut election: Election<N>, cmd_opts: STV) -> Result<(), i32>
|
||||||
where
|
where
|
||||||
for<'r> &'r N: ops::Sub<&'r N, Output=N>,
|
for<'r> &'r N: ops::Sub<&'r N, Output=N>,
|
||||||
for<'r> &'r N: ops::Mul<&'r N, Output=N>,
|
for<'r> &'r N: ops::Mul<&'r N, Output=N>,
|
||||||
|
@ -263,7 +280,13 @@ where
|
||||||
);
|
);
|
||||||
|
|
||||||
// Validate options
|
// Validate options
|
||||||
stv_opts.validate();
|
match stv_opts.validate() {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => {
|
||||||
|
println!("Error: {}", err.describe());
|
||||||
|
return Err(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Describe count
|
// Describe count
|
||||||
let total_ballots = election.ballots.iter().fold(N::zero(), |acc, b| { acc + &b.orig_value });
|
let total_ballots = election.ballots.iter().fold(N::zero(), |acc, b| { acc + &b.orig_value });
|
||||||
|
@ -285,15 +308,30 @@ where
|
||||||
let mut state = CountState::new(&election);
|
let mut state = CountState::new(&election);
|
||||||
|
|
||||||
// Distribute first preferences
|
// Distribute first preferences
|
||||||
stv::count_init(&mut state, &stv_opts).unwrap();
|
match stv::count_init(&mut state, &stv_opts) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => {
|
||||||
|
println!("Error: {}", err.describe());
|
||||||
|
return Err(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut stage_num = 1;
|
let mut stage_num = 1;
|
||||||
print_stage(stage_num, &state, &cmd_opts);
|
print_stage(stage_num, &state, &cmd_opts);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let is_done = stv::count_one_stage(&mut state, &stv_opts);
|
match stv::count_one_stage(&mut state, &stv_opts) {
|
||||||
if is_done.unwrap() {
|
Ok(is_done) => {
|
||||||
|
if is_done {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
println!("Error: {}", err.describe());
|
||||||
|
return Err(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
stage_num += 1;
|
stage_num += 1;
|
||||||
print_stage(stage_num, &state, &cmd_opts);
|
print_stage(stage_num, &state, &cmd_opts);
|
||||||
}
|
}
|
||||||
|
@ -315,6 +353,8 @@ where
|
||||||
println!("{}. {}", i + 1, winner.name);
|
println!("{}. {}", i + 1, winner.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_candidates<'a, N: 'a + Number, I: Iterator<Item=(&'a Candidate, &'a CountCard<'a, N>)>>(candidates: I, cmd_opts: &STV) {
|
fn print_candidates<'a, N: 'a + Number, I: Iterator<Item=(&'a Candidate, &'a CountCard<'a, N>)>>(candidates: I, cmd_opts: &STV) {
|
||||||
|
|
|
@ -61,6 +61,12 @@ impl fmt::Display for ParseError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for ParseError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
return fmt::Display::fmt(self, f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<N: Number, I: Iterator<Item=char>> BLTParser<N, I> {
|
impl<N: Number, I: Iterator<Item=char>> BLTParser<N, I> {
|
||||||
// NON-TERMINALS - HIGHER LEVEL
|
// NON-TERMINALS - HIGHER LEVEL
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ use itertools::Itertools;
|
||||||
use wasm_bindgen::prelude::wasm_bindgen;
|
use wasm_bindgen::prelude::wasm_bindgen;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::fmt;
|
||||||
use std::ops;
|
use std::ops;
|
||||||
|
|
||||||
/// Options for conducting an STV count
|
/// Options for conducting an STV count
|
||||||
|
@ -228,11 +229,12 @@ impl STVOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validate the combination of [STVOptions] and panic if invalid
|
/// Validate the combination of [STVOptions] and panic if invalid
|
||||||
pub fn validate(&self) {
|
pub fn validate(&self) -> Result<(), STVError> {
|
||||||
if self.surplus == SurplusMethod::Meek {
|
if self.surplus == SurplusMethod::Meek {
|
||||||
if self.transferable_only { panic!("--surplus meek is incompatible with --transferable-only"); }
|
if self.transferable_only { return Err(STVError::InvalidOptions("--surplus meek is incompatible with --transferable-only")); }
|
||||||
if self.exclusion != ExclusionMethod::SingleStage { panic!("--surplus meek requires --exclusion single_stage"); }
|
if self.exclusion != ExclusionMethod::SingleStage { return Err(STVError::InvalidOptions("--surplus meek requires --exclusion single_stage")); }
|
||||||
}
|
}
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,25 +433,31 @@ impl ConstraintMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An error during the STV count
|
/// An error during the STV count
|
||||||
#[wasm_bindgen]
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum STVError {
|
pub enum STVError {
|
||||||
/// User input is required
|
/// Options for the count are invalid
|
||||||
RequireInput,
|
InvalidOptions(&'static str),
|
||||||
/// Tie could not be resolved
|
/// Tie could not be resolved
|
||||||
UnresolvedTie,
|
UnresolvedTie,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl STVError {
|
impl STVError {
|
||||||
/// Return the name of the error as a string
|
/// Describe the error
|
||||||
pub fn name(&self) -> &'static str {
|
pub fn describe(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
STVError::RequireInput => "RequireInput",
|
STVError::InvalidOptions(s) => s,
|
||||||
STVError::UnresolvedTie => "UnresolvedTie",
|
STVError::UnresolvedTie => "Unable to resolve tie",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for STVError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(self.describe())?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Distribute first preferences, and initialise other states such as the random number generator and tie-breaking rules
|
/// Distribute first preferences, and initialise other states such as the random number generator and tie-breaking rules
|
||||||
pub fn count_init<'a, N: Number>(state: &mut CountState<'a, N>, opts: &'a STVOptions) -> Result<bool, STVError>
|
pub fn count_init<'a, N: Number>(state: &mut CountState<'a, N>, opts: &'a STVOptions) -> Result<bool, STVError>
|
||||||
where
|
where
|
||||||
|
@ -1288,7 +1296,7 @@ fn choose_highest<'c, N: Number>(state: &mut CountState<N>, opts: &STVOptions, c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panic!("Unable to resolve tie");
|
return Err(STVError::UnresolvedTie);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Break a tie between the given candidates according to [STVOptions::ties], selecting the lowest candidate
|
/// Break a tie between the given candidates according to [STVOptions::ties], selecting the lowest candidate
|
||||||
|
@ -1309,7 +1317,7 @@ fn choose_lowest<'c, N: Number>(state: &mut CountState<N>, opts: &STVOptions, ca
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panic!("Unable to resolve tie");
|
return Err(STVError::UnresolvedTie);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If required, initialise the state of the forwards or backwards tie-breaking strategies, according to [STVOptions::ties]
|
/// If required, initialise the state of the forwards or backwards tie-breaking strategies, according to [STVOptions::ties]
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#![allow(rustdoc::private_intra_doc_links)]
|
#![allow(rustdoc::private_intra_doc_links)]
|
||||||
|
#![allow(unused_unsafe)] // Confuses cargo check
|
||||||
|
|
||||||
use crate::constraints::Constraints;
|
use crate::constraints::Constraints;
|
||||||
use crate::election::{CandidateState, CountState, Election};
|
use crate::election::{CandidateState, CountState, Election};
|
||||||
|
@ -25,10 +26,24 @@ use crate::stv;
|
||||||
extern crate console_error_panic_hook;
|
extern crate console_error_panic_hook;
|
||||||
|
|
||||||
use js_sys::Array;
|
use js_sys::Array;
|
||||||
use wasm_bindgen::{JsValue, prelude::wasm_bindgen};
|
use wasm_bindgen::prelude::wasm_bindgen;
|
||||||
|
|
||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern "C" {
|
||||||
|
fn wasm_error(message: String);
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! wasm_error {
|
||||||
|
($type:expr, $err:expr) => { {
|
||||||
|
unsafe { wasm_error(format!("{}: {}", $type, $err)); }
|
||||||
|
panic!("{}: {}", $type, $err);
|
||||||
|
} }
|
||||||
|
}
|
||||||
|
|
||||||
// Init
|
// Init
|
||||||
|
|
||||||
/// Wrapper for [Fixed::set_dps]
|
/// Wrapper for [Fixed::set_dps]
|
||||||
|
@ -56,7 +71,10 @@ macro_rules! impl_type {
|
||||||
// Install panic! hook
|
// Install panic! hook
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
let election: Election<$type> = Election::from_blt(text.chars().peekable());
|
let election: Election<$type> = match Election::from_blt(text.chars().peekable()) {
|
||||||
|
Ok(e) => e,
|
||||||
|
Err(err) => wasm_error!("Syntax Error", err),
|
||||||
|
};
|
||||||
return [<Election$type>](election);
|
return [<Election$type>](election);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,20 +95,20 @@ macro_rules! impl_type {
|
||||||
/// Wrapper for [stv::count_init]
|
/// Wrapper for [stv::count_init]
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn [<count_init_$type>](state: &mut [<CountState$type>], opts: &STVOptions) -> Result<bool, JsValue> {
|
pub fn [<count_init_$type>](state: &mut [<CountState$type>], opts: &STVOptions) -> bool {
|
||||||
match stv::count_init(&mut state.0, opts.as_static()) {
|
match stv::count_init(&mut state.0, opts.as_static()) {
|
||||||
Ok(v) => Ok(v),
|
Ok(v) => v,
|
||||||
Err(e) => Err(e.name().into()),
|
Err(err) => wasm_error!("Error", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper for [stv::count_one_stage]
|
/// Wrapper for [stv::count_one_stage]
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn [<count_one_stage_$type>](state: &mut [<CountState$type>], opts: &STVOptions) -> Result<bool, JsValue> {
|
pub fn [<count_one_stage_$type>](state: &mut [<CountState$type>], opts: &STVOptions) -> bool {
|
||||||
match stv::count_one_stage::<[<$type>]>(&mut state.0, &opts.0) {
|
match stv::count_one_stage::<[<$type>]>(&mut state.0, &opts.0) {
|
||||||
Ok(v) => Ok(v),
|
Ok(v) => v,
|
||||||
Err(e) => Err(e.name().into()),
|
Err(err) => wasm_error!("Error", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,7 +266,10 @@ impl STVOptions {
|
||||||
|
|
||||||
/// Wrapper for [stv::STVOptions::validate]
|
/// Wrapper for [stv::STVOptions::validate]
|
||||||
pub fn validate(&self) {
|
pub fn validate(&self) {
|
||||||
self.0.validate();
|
match self.0.validate() {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => { wasm_error!("Error", err) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ fn aec_tas19_rational() {
|
||||||
let chars = reader.chars().map(|r| r.expect("IO Error")).peekable();
|
let chars = reader.chars().map(|r| r.expect("IO Error")).peekable();
|
||||||
|
|
||||||
// Read BLT
|
// Read BLT
|
||||||
let election: Election<Rational> = Election::from_blt(chars);
|
let election: Election<Rational> = Election::from_blt(chars).expect("Syntax Error");
|
||||||
|
|
||||||
// Validate candidate names
|
// Validate candidate names
|
||||||
for (i, candidate) in candidates.iter().enumerate() {
|
for (i, candidate) in candidates.iter().enumerate() {
|
||||||
|
|
|
@ -21,19 +21,16 @@ use opentally::constraints::Constraints;
|
||||||
use opentally::election::{CandidateState, CountState, Election};
|
use opentally::election::{CandidateState, CountState, Election};
|
||||||
use opentally::numbers::Rational;
|
use opentally::numbers::Rational;
|
||||||
use opentally::stv;
|
use opentally::stv;
|
||||||
use utf8_chars::BufReadCharsExt;
|
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, BufRead, BufReader};
|
use std::io::{self, BufRead};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn prsa1_constr1_rational() {
|
fn prsa1_constr1_rational() {
|
||||||
// FIXME: This is unvalidated!
|
// FIXME: This is unvalidated!
|
||||||
|
|
||||||
// Read BLT
|
// Read BLT
|
||||||
let mut reader = BufReader::new(File::open("tests/data/prsa1.blt").expect("IO Error"));
|
let mut election: Election<Rational> = Election::from_file("tests/data/prsa1.blt").expect("Syntax Error");
|
||||||
let chars = reader.chars().map(|r| r.expect("IO Error")).peekable();
|
|
||||||
let mut election: Election<Rational> = Election::from_blt(chars);
|
|
||||||
|
|
||||||
// Read CON
|
// Read CON
|
||||||
let file = File::open("tests/data/prsa1_constr1.con").expect("IO Error");
|
let file = File::open("tests/data/prsa1_constr1.con").expect("IO Error");
|
||||||
|
@ -94,9 +91,7 @@ fn prsa1_constr2_rational() {
|
||||||
// FIXME: This is unvalidated!
|
// FIXME: This is unvalidated!
|
||||||
|
|
||||||
// Read BLT
|
// Read BLT
|
||||||
let mut reader = BufReader::new(File::open("tests/data/prsa1.blt").expect("IO Error"));
|
let mut election: Election<Rational> = Election::from_file("tests/data/prsa1.blt").expect("Syntax Error");
|
||||||
let chars = reader.chars().map(|r| r.expect("IO Error")).peekable();
|
|
||||||
let mut election: Election<Rational> = Election::from_blt(chars);
|
|
||||||
|
|
||||||
// Read CON
|
// Read CON
|
||||||
let file = File::open("tests/data/prsa1_constr2.con").expect("IO Error");
|
let file = File::open("tests/data/prsa1_constr2.con").expect("IO Error");
|
||||||
|
@ -157,9 +152,7 @@ fn prsa1_constr3_rational() {
|
||||||
// FIXME: This is unvalidated!
|
// FIXME: This is unvalidated!
|
||||||
|
|
||||||
// Read BLT
|
// Read BLT
|
||||||
let mut reader = BufReader::new(File::open("tests/data/prsa1.blt").expect("IO Error"));
|
let mut election: Election<Rational> = Election::from_file("tests/data/prsa1.blt").expect("Syntax Error");
|
||||||
let chars = reader.chars().map(|r| r.expect("IO Error")).peekable();
|
|
||||||
let mut election: Election<Rational> = Election::from_blt(chars);
|
|
||||||
|
|
||||||
// Read CON
|
// Read CON
|
||||||
let file = File::open("tests/data/prsa1_constr3.con").expect("IO Error");
|
let file = File::open("tests/data/prsa1_constr3.con").expect("IO Error");
|
||||||
|
|
|
@ -87,9 +87,7 @@ fn meek06_ers97_fixed12() {
|
||||||
Fixed::set_dps(12);
|
Fixed::set_dps(12);
|
||||||
|
|
||||||
// Read BLT
|
// Read BLT
|
||||||
let mut reader = BufReader::new(File::open("tests/data/ers97.blt").expect("IO Error"));
|
let election: Election<Fixed> = Election::from_file("tests/data/ers97.blt").expect("Syntax Error");
|
||||||
let chars = reader.chars().map(|r| r.expect("IO Error")).peekable();
|
|
||||||
let election: Election<Fixed> = Election::from_blt(chars);
|
|
||||||
|
|
||||||
// Initialise count state
|
// Initialise count state
|
||||||
let mut state = CountState::new(&election);
|
let mut state = CountState::new(&election);
|
||||||
|
@ -161,9 +159,7 @@ fn meeknz_ers97_fixed12() {
|
||||||
Fixed::set_dps(12);
|
Fixed::set_dps(12);
|
||||||
|
|
||||||
// Read BLT
|
// Read BLT
|
||||||
let mut reader = BufReader::new(File::open("tests/data/ers97.blt").expect("IO Error"));
|
let election: Election<Fixed> = Election::from_file("tests/data/ers97.blt").expect("Syntax Error");
|
||||||
let chars = reader.chars().map(|r| r.expect("IO Error")).peekable();
|
|
||||||
let election: Election<Fixed> = Election::from_blt(chars);
|
|
||||||
|
|
||||||
// Initialise count state
|
// Initialise count state
|
||||||
let mut state = CountState::new(&election);
|
let mut state = CountState::new(&election);
|
||||||
|
|
|
@ -113,9 +113,7 @@ where
|
||||||
let num_stages = root.get_child("headerrow").expect("Syntax Error").children.len();
|
let num_stages = root.get_child("headerrow").expect("Syntax Error").children.len();
|
||||||
|
|
||||||
// Read BLT
|
// Read BLT
|
||||||
let mut reader = BufReader::new(File::open("tests/data/linn07.blt").expect("IO Error"));
|
let mut election: Election<N> = Election::from_file("tests/data/linn07.blt").expect("Syntax Error");
|
||||||
let chars = reader.chars().map(|r| r.expect("IO Error")).peekable();
|
|
||||||
let mut election: Election<N> = Election::from_blt(chars);
|
|
||||||
|
|
||||||
// !!! FOR SCOTTISH STV !!!
|
// !!! FOR SCOTTISH STV !!!
|
||||||
election.normalise_ballots();
|
election.normalise_ballots();
|
||||||
|
|
|
@ -20,10 +20,7 @@ use opentally::numbers::Number;
|
||||||
use opentally::stv;
|
use opentally::stv;
|
||||||
|
|
||||||
use csv::StringRecord;
|
use csv::StringRecord;
|
||||||
use utf8_chars::BufReadCharsExt;
|
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::BufReader;
|
|
||||||
use std::ops;
|
use std::ops;
|
||||||
|
|
||||||
#[allow(dead_code)] // Suppress false positive
|
#[allow(dead_code)] // Suppress false positive
|
||||||
|
@ -49,10 +46,7 @@ where
|
||||||
let stages: Vec<usize> = records.first().unwrap().iter().skip(1).step_by(2).map(|s| s.parse().unwrap()).collect();
|
let stages: Vec<usize> = records.first().unwrap().iter().skip(1).step_by(2).map(|s| s.parse().unwrap()).collect();
|
||||||
|
|
||||||
// Read BLT
|
// Read BLT
|
||||||
let mut reader = BufReader::new(File::open(blt_file).expect("IO Error"));
|
let election: Election<N> = Election::from_file(blt_file).expect("Syntax Error");
|
||||||
let chars = reader.chars().map(|r| r.expect("IO Error")).peekable();
|
|
||||||
|
|
||||||
let election: Election<N> = Election::from_blt(chars);
|
|
||||||
|
|
||||||
// Validate candidate names
|
// Validate candidate names
|
||||||
for (i, candidate) in candidates.iter().enumerate() {
|
for (i, candidate) in candidates.iter().enumerate() {
|
||||||
|
|
Loading…
Reference in New Issue