Switch to handwritten BLT parser

This commit is contained in:
RunasSudo 2021-07-29 03:24:51 +10:00
parent 470f1e550e
commit 3801d30527
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
13 changed files with 388 additions and 280 deletions

152
Cargo.lock generated
View File

@ -15,6 +15,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "assert_cmd"
version = "1.0.5"
@ -47,34 +53,13 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "block-buffer"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
dependencies = [
"block-padding",
"byte-tools",
"byteorder",
"generic-array 0.12.4",
]
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"generic-array 0.14.4",
]
[[package]]
name = "block-padding"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
dependencies = [
"byte-tools",
"generic-array",
]
[[package]]
@ -95,12 +80,6 @@ version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631"
[[package]]
name = "byte-tools"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
[[package]]
name = "byteorder"
version = "1.4.3"
@ -225,22 +204,13 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
[[package]]
name = "digest"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
dependencies = [
"generic-array 0.12.4",
]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array 0.14.4",
"generic-array",
]
[[package]]
@ -255,12 +225,6 @@ version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "fake-simd"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "flate2"
version = "1.0.20"
@ -282,15 +246,6 @@ dependencies = [
"num-traits",
]
[[package]]
name = "generic-array"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
dependencies = [
"typenum",
]
[[package]]
name = "generic-array"
version = "0.14.4"
@ -416,12 +371,6 @@ dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "matrixmultiply"
version = "0.3.1"
@ -517,12 +466,6 @@ dependencies = [
"autocfg",
]
[[package]]
name = "opaque-debug"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
[[package]]
name = "opaque-debug"
version = "0.3.0"
@ -548,11 +491,10 @@ dependencies = [
"num-rational",
"num-traits",
"paste",
"pest",
"pest_derive",
"predicates",
"rug",
"sha2",
"utf8-chars",
"wasm-bindgen",
"xmltree",
]
@ -569,49 +511,6 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58"
[[package]]
name = "pest"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
dependencies = [
"ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
dependencies = [
"pest",
"pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pest_meta"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
dependencies = [
"maplit",
"pest",
"sha-1",
]
[[package]]
name = "predicates"
version = "1.0.8"
@ -759,29 +658,17 @@ version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
[[package]]
name = "sha-1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
dependencies = [
"block-buffer 0.7.3",
"digest 0.8.1",
"fake-simd",
"opaque-debug 0.2.3",
]
[[package]]
name = "sha2"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12"
dependencies = [
"block-buffer 0.9.0",
"block-buffer",
"cfg-if 1.0.0",
"cpufeatures",
"digest 0.9.0",
"opaque-debug 0.3.0",
"digest",
"opaque-debug",
]
[[package]]
@ -819,12 +706,6 @@ version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
[[package]]
name = "ucd-trie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "unicode-segmentation"
version = "1.7.1"
@ -837,6 +718,15 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "utf8-chars"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1348d8face79d019be7cbc0198e36bf93e160ddbfaa7bb54c9592627b9ec841"
dependencies = [
"arrayvec",
]
[[package]]
name = "vec_map"
version = "0.8.2"

View File

@ -13,8 +13,6 @@ git-version = "0.3.4"
ibig = "0.3.2"
itertools = "0.10.1"
ndarray = "0.15.3"
pest = "2.1.3"
pest_derive = "2.1.0"
predicates = "1.0.8"
num-traits = "0.2"
sha2 = "0.9.5"
@ -28,11 +26,12 @@ num-bigint = "0.4.0"
num-rational = "0.4.0"
paste = "1.0.5"
# For tests
# For tests/CLI only
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
assert_cmd = "1.0.5"
csv = "1.1.6"
flate2 = "1.0"
utf8-chars = "1.0.2"
xmltree = "0.10.3"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.rug]

View File

@ -18,12 +18,11 @@
use crate::constraints::{Constraints, ConstraintMatrix};
use crate::logger::Logger;
use crate::numbers::Number;
use crate::parser::blt;
use crate::parser::blt::BLTParser;
use crate::sharandom::SHARandom;
use pest::Parser;
use std::collections::HashMap;
use std::iter::Peekable;
/// An election to be counted
pub struct Election<N> {
@ -43,62 +42,10 @@ pub struct Election<N> {
impl<N: Number> Election<N> {
/// Parse the given BLT file and return an [Election]
pub fn from_blt(input: &str) -> Self {
let mut main = blt::BLTParser::parse(blt::Rule::main, input).expect("Syntax Error").next().unwrap().into_inner();
let mut header = main.next().unwrap().into_inner();
let num_candidates = header.next().unwrap().as_str().parse().unwrap();
let seats = header.next().unwrap().as_str().parse().unwrap();
// Initialise the Election object
let mut election = Election {
name: String::new(),
seats: seats,
candidates: Vec::with_capacity(num_candidates),
withdrawn_candidates: Vec::new(),
ballots: Vec::new(),
constraints: None,
};
// Read withdrawn candidates
let withdrawn_pairs = main.next().unwrap().into_inner();
for withdrawn_cand in withdrawn_pairs {
let val = withdrawn_cand.as_str();
let val: usize = val[1..val.len()].parse().unwrap();
election.withdrawn_candidates.push(val - 1);
}
// Read ballots
let ballot_list = main.next().unwrap().into_inner();
for ballot in ballot_list {
let mut ballot_pairs = ballot.into_inner();
let value = N::parse(ballot_pairs.next().unwrap().as_str());
let mut ballot = Ballot {
orig_value: value,
preferences: Vec::new(),
};
for preference in ballot_pairs {
let preference: usize = preference.as_str().parse().unwrap();
ballot.preferences.push(preference - 1);
}
election.ballots.push(ballot);
}
// Read candidates
for _ in 0..num_candidates {
let string = main.next().expect("Expected candidate name");
election.candidates.push(Candidate { name: blt::unwrap_string(string).to_string() });
}
// Read name
let string = main.next().expect("Expected election title");
election.name.push_str(blt::unwrap_string(string));
return election;
pub fn from_blt<I: Iterator<Item=char>>(input: Peekable<I>) -> Self {
let mut parser = BLTParser::new(input);
parser.parse_blt().expect("Syntax Error");
return parser.as_election();
}
/// Convert ballots with weight >1 to multiple ballots of weight 1

View File

@ -38,8 +38,6 @@ pub mod ties;
use git_version::git_version;
use wasm_bindgen::prelude::wasm_bindgen;
#[macro_use]
extern crate pest_derive;
/// The git revision of this OpenTally build
pub const VERSION: &str = git_version!(args=["--always", "--dirty=-dev"], fallback="unknown");

View File

@ -21,10 +21,11 @@ use opentally::numbers::{Fixed, GuardedFixed, NativeFloat64, Number, Rational};
use opentally::stv;
use clap::{AppSettings, Clap};
use utf8_chars::BufReadCharsExt;
use std::cmp::max;
use std::fs::{self, File};
use std::io::{self, BufRead};
use std::fs::File;
use std::io::{self, BufRead, BufReader};
use std::ops;
/// Open-source election vote counting
@ -190,28 +191,29 @@ fn main() {
let Command::STV(cmd_opts) = opts.command;
// Read BLT file
let file = fs::read_to_string(&cmd_opts.filename).expect("IO Error");
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" {
let mut election: Election<Rational> = Election::from_blt(&file);
let mut election: Election<Rational> = Election::from_blt(chars);
maybe_load_constraints(&mut election, &cmd_opts.constraints);
// Must specify ::<N> here and in a few other places because ndarray causes E0275 otherwise
count_election::<Rational>(election, cmd_opts);
} else if cmd_opts.numbers == "float64" {
let mut election: Election<NativeFloat64> = Election::from_blt(&file);
let mut election: Election<NativeFloat64> = Election::from_blt(chars);
maybe_load_constraints(&mut election, &cmd_opts.constraints);
count_election::<NativeFloat64>(election, cmd_opts);
} else if cmd_opts.numbers == "fixed" {
Fixed::set_dps(cmd_opts.decimals);
let mut election: Election<Fixed> = Election::from_blt(&file);
let mut election: Election<Fixed> = Election::from_blt(chars);
maybe_load_constraints(&mut election, &cmd_opts.constraints);
count_election::<Fixed>(election, cmd_opts);
} else if cmd_opts.numbers == "gfixed" {
GuardedFixed::set_dps(cmd_opts.decimals);
let mut election: Election<GuardedFixed> = Election::from_blt(&file);
let mut election: Election<GuardedFixed> = Election::from_blt(chars);
maybe_load_constraints(&mut election, &cmd_opts.constraints);
count_election::<GuardedFixed>(election, cmd_opts);
}

View File

@ -1,34 +0,0 @@
main = {
SOI ~ NEWLINE*
~ header ~ NEWLINE*
~ withdrawn ~ NEWLINE*
~ ballot_list
~ "0" ~ NEWLINE*
~ (string ~ NEWLINE*)+
~ EOI
}
header = {
integer ~ integer
}
withdrawn = {
(withdrawn_cand ~ NEWLINE*)*
}
withdrawn_cand = @{ "-" ~ integer }
ballot_list = { (ballot ~ NEWLINE*)* }
ballot = {
number ~ integer* ~ ("0" ~ NEWLINE? | NEWLINE)
}
integer = @{ ASCII_NONZERO_DIGIT ~ ASCII_DIGIT* }
decimal = @{ "0." ~ ASCII_DIGIT+ | integer ~ "." ~ ASCII_DIGIT+ }
number = { decimal | integer }
quoted_string = @{ "\"" ~ (!"\"" ~ !NEWLINE ~ ANY)* ~ "\"" }
raw_string = @{ (!" " ~ !NEWLINE ~ ANY)+ }
string = { quoted_string | raw_string }
WHITESPACE = _{ " " }
COMMENT = _{ "#" ~ (!NEWLINE ~ ANY)* ~ (&NEWLINE | &EOI) }

View File

@ -15,30 +15,320 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#![allow(missing_docs)]
use crate::election::{Ballot, Candidate, Election};
use crate::numbers::Number;
use pest::iterators::Pair;
use std::iter::Peekable;
#[derive(Parser)]
#[grammar = "parser/blt.pest"]
pub struct BLTParser;
/// Utility for parsing a BLT file
pub struct BLTParser<N: Number, I: Iterator<Item=char>> {
/// The peekable iterator of chars representing the BLT file
chars: Peekable<I>,
/// Convert the specified [Pair<Rule>] into a &str
pub fn unwrap_string(string: Pair<Rule>) -> &str {
match string.as_rule() {
Rule::string => {
let string = string.into_inner().next().unwrap();
match string.as_rule() {
Rule::raw_string => {
return string.as_str();
/// Temporary buffer for parsing ballot values
buf_ballot_value: String,
/// Number of candidates
num_candidates: usize,
/// Parsed [Election]
election: Election<N>,
}
Rule::quoted_string => {
let string = string.as_str();
return &string[1..string.len()-1];
/// An error when parsing a BLT file
#[derive(Debug)]
pub enum ParseError {
/// Invalid syntax
Invalid
}
_ => unreachable!()
impl<N: Number, I: Iterator<Item=char>> BLTParser<N, I> {
// NON-TERMINALS - HIGHER LEVEL
/// Parse the BLT file
pub fn parse_blt(&mut self) -> Result<(), ParseError> {
self.delimiter();
self.header()?;
self.withdrawn_candidates()?;
self.ballots()?;
self.strings()?;
return Ok(());
}
/// Parse the header
fn header(&mut self) -> Result<(), ParseError> {
self.num_candidates = self.usize()?;
self.delimiter();
self.election.seats = self.usize()?;
self.delimiter();
return Ok(());
}
/// Parse the withdrawn candidates (if any)
fn withdrawn_candidates(&mut self) -> Result<(), ParseError> {
while self.lookahead() == '-' {
self.accept(); // Minus sign
let index = self.usize()? - 1;
self.election.withdrawn_candidates.push(index);
self.delimiter();
}
return Ok(());
}
/// Parse the list of ballots
fn ballots(&mut self) -> Result<(), ParseError> {
loop {
if self.lookahead() == '0' {
// End of ballots, or start of decimal?
self.accept();
if self.lookahead() == '.' {
// Decimal
self.buf_ballot_value.clear();
self.buf_ballot_value.push('0');
self.ballot()?;
} else {
// End of ballots
self.delimiter();
break;
}
} else {
self.buf_ballot_value.clear();
self.ballot()?;
}
}
_ => panic!("Non-string passed to unwrap_string")
return Ok(());
}
/// Parse a ballot
fn ballot(&mut self) -> Result<(), ParseError> {
self.ballot_value()?;
self.delimiter_not_nl();
// Read preferences
let mut preferences = Vec::new();
loop {
if self.lookahead() == '0' || self.lookahead() == '\n' {
// End of preferences
self.accept();
break;
} else {
preferences.push(self.usize()? - 1);
self.delimiter_not_nl();
}
}
self.delimiter();
let ballot = Ballot {
orig_value: N::parse(&self.buf_ballot_value),
preferences: preferences,
};
self.election.ballots.push(ballot);
return Ok(());
}
/// Parse the list of strings at the end of the BLT file
fn strings(&mut self) -> Result<(), ParseError> {
for _ in 0..self.num_candidates {
let name = self.string()?;
self.election.candidates.push(Candidate {
name
});
}
let name = self.string()?;
self.election.name = name;
return Ok(());
}
// NON-TERMINALS - LOWER LEVEL
/// Parse an integer into a [usize]
fn usize(&mut self) -> Result<usize, ParseError> {
return Ok(self.integer()?.parse().expect("Invalid usize"));
}
/// Parse an integer as a [String]
fn integer(&mut self) -> Result<String, ParseError> {
let mut result = String::from(self.digit_nonzero()?);
loop {
match self.digit() {
Err(_) => { break; }
Ok(d) => { result.push(d); }
}
}
return Ok(result);
}
/// Parse a number as an instance of N
fn ballot_value(&mut self) -> Result<(), ParseError> {
loop {
match self.ballot_value_element() {
Err(_) => { break; }
Ok(d) => { self.buf_ballot_value.push(d); }
}
}
return Ok(());
}
/// Parse a quoted or raw string
fn string(&mut self) -> Result<String, ParseError> {
match self.quoted_string() {
Ok(s) => { return Ok(s); }
Err(_) => {}
}
match self.raw_string() {
Ok(s) => { return Ok(s); }
Err(_) => {}
}
return Err(ParseError::Invalid);
}
/// Parse a quoted string
fn quoted_string(&mut self) -> Result<String, ParseError> {
if self.lookahead() == '"' {
self.accept(); // Opening quotation mark
let mut result = String::new();
while self.lookahead() != '"' {
// TODO: BufRead::read_until ?
result.push(self.accept());
}
self.accept(); // Closing quotation mark
if !self.eof() {
self.delimiter();
}
return Ok(result);
} else {
return Err(ParseError::Invalid);
}
}
/// Parse a raw (unquoted) string
fn raw_string(&mut self) -> Result<String, ParseError> {
let mut result = String::new();
while !self.lookahead().is_whitespace() && !self.eof() {
result.push(self.accept());
}
if !self.eof() {
self.delimiter();
}
return Ok(result);
}
/// Skip any sequence of whitespace or comments
fn delimiter(&mut self) {
loop {
if self.eof() {
break;
} else if self.lookahead() == '#' {
self.dnl();
if !self.eof() {
self.accept(); // Trailing newline
}
} else if self.lookahead().is_whitespace() {
self.accept();
while !self.eof() && self.lookahead().is_whitespace() { self.accept(); }
} else {
break;
}
}
}
/// Skip any sequence of whitespace or comments, but do not accept any newline and leave it trailing
fn delimiter_not_nl(&mut self) {
loop {
if self.eof() {
break;
} else if self.lookahead() == '#' {
self.dnl();
} else if self.lookahead().is_whitespace() && self.lookahead() != '\n' {
self.accept();
while !self.eof() && self.lookahead().is_whitespace() && self.lookahead() != '\n' { self.accept(); }
} else {
break;
}
}
}
/// Skip to the next newline
fn dnl(&mut self) {
while !self.eof() && self.lookahead() != '\n' {
// TODO: BufRead::read_until ?
self.accept();
}
}
// TERMINALS
/// Read a nonzero digit
fn digit_nonzero(&mut self) -> Result<char, ParseError> {
if self.lookahead() >= '1' && self.lookahead() <= '9' {
return Ok(self.accept());
} else {
return Err(ParseError::Invalid);
}
}
/// Read any digit
fn digit(&mut self) -> Result<char, ParseError> {
if self.lookahead() >= '0' && self.lookahead() <= '9' {
return Ok(self.accept());
} else {
return Err(ParseError::Invalid);
}
}
/// Read any element of a valid number, i.e. a digit, decimal point or slash
fn ballot_value_element(&mut self) -> Result<char, ParseError> {
if (self.lookahead() >= '0' && self.lookahead() <= '9') || self.lookahead() == '.' || self.lookahead() == '/' {
return Ok(self.accept());
} else {
return Err(ParseError::Invalid);
}
}
// UTILITIES
/// Return if this is the end of the file
fn eof(&mut self) -> bool {
return self.chars.peek().is_none();
}
/// Peek at the next character in the stream
fn lookahead(&mut self) -> char {
// TODO: Cache this?
return *self.chars.peek().expect("Unexpected EOF");
}
/// Read and return one character from the stream
fn accept(&mut self) -> char {
return self.chars.next().expect("Unexpected EOF");
}
// PUBLIC API
/// Return a new [BLTParser]
pub fn new(chars: Peekable<I>) -> Self {
Self {
chars,
buf_ballot_value: String::new(),
num_candidates: 0,
election: Election {
name: String::new(),
seats: 0,
candidates: Vec::new(),
withdrawn_candidates: Vec::new(),
ballots: Vec::new(),
constraints: None,
},
}
}
/// Return the parsed [Election]
pub fn as_election(self) -> Election<N> {
return self.election;
}
}

View File

@ -56,7 +56,7 @@ macro_rules! impl_type {
// Install panic! hook
console_error_panic_hook::set_once();
let election: Election<$type> = Election::from_blt(&text);
let election: Election<$type> = Election::from_blt(text.chars().peekable());
return [<Election$type>](election);
}

View File

@ -23,9 +23,10 @@ use opentally::stv;
use csv::StringRecord;
use flate2::bufread::GzDecoder;
use utf8_chars::BufReadCharsExt;
use std::fs::File;
use std::io::{self, Read};
use std::io::{self, BufReader};
#[test]
fn aec_tas19_rational() {
@ -45,12 +46,13 @@ fn aec_tas19_rational() {
// Decompress BLT
let file = File::open("tests/data/aec-senate-formalpreferences-24310-TAS.blt.gz").expect("IO Error");
let file_reader = io::BufReader::new(file);
let mut gz_decoder = GzDecoder::new(file_reader);
let mut input = String::new();
gz_decoder.read_to_string(&mut input).expect("IO Error");
let gz_decoder = GzDecoder::new(file_reader);
let mut reader = BufReader::new(gz_decoder);
let chars = reader.chars().map(|r| r.expect("IO Error")).peekable();
// Read BLT
let election: Election<Rational> = Election::from_blt(&input);
let election: Election<Rational> = Election::from_blt(chars);
// Validate candidate names
for (i, candidate) in candidates.iter().enumerate() {

View File

@ -21,17 +21,19 @@ use opentally::constraints::Constraints;
use opentally::election::{CandidateState, CountState, Election};
use opentally::numbers::Rational;
use opentally::stv;
use utf8_chars::BufReadCharsExt;
use std::fs::{self, File};
use std::io::{self, BufRead};
use std::fs::File;
use std::io::{self, BufRead, BufReader};
#[test]
fn prsa1_constr1_rational() {
// FIXME: This is unvalidated!
// Read BLT
let file = fs::read_to_string("tests/data/prsa1.blt").expect("IO Error");
let mut election: Election<Rational> = Election::from_blt(&file);
let mut reader = BufReader::new(File::open("tests/data/prsa1.blt").expect("IO Error"));
let chars = reader.chars().map(|r| r.expect("IO Error")).peekable();
let mut election: Election<Rational> = Election::from_blt(chars);
// Read CON
let file = File::open("tests/data/prsa1_constr1.con").expect("IO Error");
@ -92,8 +94,9 @@ fn prsa1_constr2_rational() {
// FIXME: This is unvalidated!
// Read BLT
let file = fs::read_to_string("tests/data/prsa1.blt").expect("IO Error");
let mut election: Election<Rational> = Election::from_blt(&file);
let mut reader = BufReader::new(File::open("tests/data/prsa1.blt").expect("IO Error"));
let chars = reader.chars().map(|r| r.expect("IO Error")).peekable();
let mut election: Election<Rational> = Election::from_blt(chars);
// Read CON
let file = File::open("tests/data/prsa1_constr2.con").expect("IO Error");
@ -154,8 +157,9 @@ fn prsa1_constr3_rational() {
// FIXME: This is unvalidated!
// Read BLT
let file = fs::read_to_string("tests/data/prsa1.blt").expect("IO Error");
let mut election: Election<Rational> = Election::from_blt(&file);
let mut reader = BufReader::new(File::open("tests/data/prsa1.blt").expect("IO Error"));
let chars = reader.chars().map(|r| r.expect("IO Error")).peekable();
let mut election: Election<Rational> = Election::from_blt(chars);
// Read CON
let file = File::open("tests/data/prsa1_constr3.con").expect("IO Error");

View File

@ -20,8 +20,10 @@ mod utils;
use opentally::election::{CandidateState, CountState, Election};
use opentally::numbers::{Fixed, NativeFloat64, Number};
use opentally::stv;
use utf8_chars::BufReadCharsExt;
use std::fs;
use std::fs::File;
use std::io::BufReader;
// Compare ers97.blt count with result produced by 1987 Hill–Wichmann–Woodall reference implementation
#[test]
@ -85,8 +87,9 @@ fn meek06_ers97_fixed12() {
Fixed::set_dps(12);
// Read BLT
let file = fs::read_to_string("tests/data/ers97.blt").expect("IO Error");
let election: Election<Fixed> = Election::from_blt(&file);
let mut reader = BufReader::new(File::open("tests/data/ers97.blt").expect("IO Error"));
let chars = reader.chars().map(|r| r.expect("IO Error")).peekable();
let election: Election<Fixed> = Election::from_blt(chars);
// Initialise count state
let mut state = CountState::new(&election);
@ -158,8 +161,9 @@ fn meeknz_ers97_fixed12() {
Fixed::set_dps(12);
// Read BLT
let file = fs::read_to_string("tests/data/ers97.blt").expect("IO Error");
let election: Election<Fixed> = Election::from_blt(&file);
let mut reader = BufReader::new(File::open("tests/data/ers97.blt").expect("IO Error"));
let chars = reader.chars().map(|r| r.expect("IO Error")).peekable();
let election: Election<Fixed> = Election::from_blt(chars);
// Initialise count state
let mut state = CountState::new(&election);

View File

@ -19,9 +19,11 @@ use opentally::election::{CandidateState, CountState, Election};
use opentally::numbers::{Fixed, GuardedFixed, Number};
use opentally::stv;
use utf8_chars::BufReadCharsExt;
use xmltree::Element;
use std::fs::{self, File};
use std::fs::File;
use std::io::BufReader;
use std::ops;
#[test]
@ -111,8 +113,9 @@ where
let num_stages = root.get_child("headerrow").expect("Syntax Error").children.len();
// Read BLT
let file = fs::read_to_string("tests/data/linn07.blt").expect("IO Error");
let mut election: Election<N> = Election::from_blt(&file);
let mut reader = BufReader::new(File::open("tests/data/linn07.blt").expect("IO Error"));
let chars = reader.chars().map(|r| r.expect("IO Error")).peekable();
let mut election: Election<N> = Election::from_blt(chars);
// !!! FOR SCOTTISH STV !!!
election.normalise_ballots();

View File

@ -20,8 +20,10 @@ use opentally::numbers::Number;
use opentally::stv;
use csv::StringRecord;
use utf8_chars::BufReadCharsExt;
use std::fs;
use std::fs::File;
use std::io::BufReader;
use std::ops;
#[allow(dead_code)] // Suppress false positive
@ -47,9 +49,10 @@ where
let stages: Vec<usize> = records.first().unwrap().iter().skip(1).step_by(2).map(|s| s.parse().unwrap()).collect();
// Read BLT
let file = fs::read_to_string(blt_file).expect("IO Error");
let mut reader = BufReader::new(File::open(blt_file).expect("IO Error"));
let chars = reader.chars().map(|r| r.expect("IO Error")).peekable();
let election: Election<N> = Election::from_blt(&file);
let election: Election<N> = Election::from_blt(chars);
// Validate candidate names
for (i, candidate) in candidates.iter().enumerate() {