Use Pest-based parser for BLT files
Support comments, optional newlines, etc.
This commit is contained in:
parent
3b8ccd097e
commit
cca097f943
136
Cargo.lock
generated
136
Cargo.lock
generated
@ -47,13 +47,34 @@ 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",
|
||||
"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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -74,6 +95,12 @@ 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"
|
||||
@ -198,13 +225,22 @@ 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",
|
||||
"generic-array 0.14.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -219,6 +255,12 @@ 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"
|
||||
@ -240,6 +282,15 @@ 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"
|
||||
@ -365,6 +416,12 @@ 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"
|
||||
@ -460,6 +517,12 @@ 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"
|
||||
@ -485,6 +548,8 @@ dependencies = [
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
"paste",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"predicates",
|
||||
"rug",
|
||||
"sha2",
|
||||
@ -504,6 +569,49 @@ 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"
|
||||
@ -651,17 +759,29 @@ 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",
|
||||
"block-buffer 0.9.0",
|
||||
"cfg-if 1.0.0",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
"opaque-debug",
|
||||
"digest 0.9.0",
|
||||
"opaque-debug 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -699,6 +819,12 @@ 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"
|
||||
|
@ -13,6 +13,8 @@ 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"
|
||||
|
16
docs/blt.md
16
docs/blt.md
@ -1,6 +1,6 @@
|
||||
# BLT file format
|
||||
|
||||
OpenTally accepts ballot data in the BLT file format, as described by [Hill, Wichmann & Woodall](https://www.dia.govt.nz/diawebsite.NSF/Files/meekm/%24file/meekm.pdf) for their implementation of Meek STV. The BLT file format is also used by [OpenSTV/OpaVote](https://www.opavote.com/openstv), Lundell's [Droop](https://github.com/jklundell/droop/wiki/BltFileFormat) and Otten's [eSTV](https://web.archive.org/web/20020606014623/http://estv.otten.co.uk/) (where it is known as a DAT data transfer file).
|
||||
OpenTally accepts ballot data in the BLT file format, as described by [Hill, Wichmann & Woodall](https://www.dia.govt.nz/diawebsite.NSF/Files/meekm/%24file/meekm.pdf) for their implementation of Meek STV. The BLT file format is also used by [OpenSTV/OpaVote](https://www.opavote.com/help/overview#blt-file-format), Lundell's [Droop](https://github.com/jklundell/droop/wiki/BltFileFormat) and Otten's [eSTV](https://web.archive.org/web/20020606014623/http://estv.otten.co.uk/) (where it is known as a DAT data transfer file).
|
||||
|
||||
The file format is as follows:
|
||||
|
||||
@ -21,14 +21,16 @@ The file format is as follows:
|
||||
"Title"
|
||||
```
|
||||
|
||||
The first line (`4 2`) indicates that there are 4 candidates for 2 vacancies. This must be on its own line.
|
||||
The first line (`4 2`) indicates that there are 4 candidates for 2 vacancies.
|
||||
|
||||
The second line (`-2`), which is optional, indicates that the 2nd candidate (Basil) has withdrawn. Multiple withdrawn candidates may be specified on this line, e.g. `-2 -3 -4`. This must, if present, be on its own line.
|
||||
The second line (`-2`), which is optional, indicates that the 2nd candidate (Basil) has withdrawn. Multiple withdrawn candidates may be specified on this line, e.g. `-2 -3 -4`.
|
||||
|
||||
The third line (second, if there are no withdrawn candidates) begins the ballot data. `3 1 3 4 0` indicates that there were 3 ballots which voted, in order of preference, for the 1st candidate (Adam), then the 3rd candidate (Charlotte), then the 4th candidate (Donald). A `0` optionally indicates the end of the list of preferences. Each such set of ballots must be on its own line.
|
||||
The third line (second, if there are no withdrawn candidates) begins the ballot data. `3 1 3 4 0` indicates that there were 3 ballots which voted, in order of preference, for the 1st candidate (Adam), then the 3rd candidate (Charlotte), then the 4th candidate (Donald). A `0` optionally indicates the end of the list of preferences.
|
||||
|
||||
The end of the list of ballots must be indicated with a single `0`, which must be on its own line.
|
||||
The end of the list of ballots must be indicated with a single `0`.
|
||||
|
||||
The next lines give the names of the candidates, up to the number of candidates specified on the first line (in this case, 4). Each candidate's name must be surrounded by quotation marks, and must appear on its own line.
|
||||
The next lines give the names of the candidates, up to the number of candidates specified on the first line (in this case, 4). Each candidate's name, unless a single word, must be surrounded by quotation marks.
|
||||
|
||||
The final line gives the name of the election, which must appear on its own line.
|
||||
The final line gives the name of the election, which, unless a single word, must be surrounded by quotation marks.
|
||||
|
||||
Newlines are optional, but if not provided, the `0` at the end of each ballot's preferences is mandatory.
|
||||
|
@ -18,8 +18,11 @@
|
||||
use crate::constraints::{Constraints, ConstraintMatrix};
|
||||
use crate::logger::Logger;
|
||||
use crate::numbers::Number;
|
||||
use crate::parser::blt;
|
||||
use crate::sharandom::SHARandom;
|
||||
|
||||
use pest::Parser;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// An election to be counted
|
||||
@ -40,12 +43,12 @@ pub struct Election<N> {
|
||||
|
||||
impl<N: Number> Election<N> {
|
||||
/// Parse the given BLT file and return an [Election]
|
||||
pub fn from_blt<I: Iterator<Item=String>>(mut lines: I) -> Self {
|
||||
// Read first line
|
||||
let line = lines.next().expect("Unexpected EOF");
|
||||
let mut bits = line.split(" ");
|
||||
let num_candidates = bits.next().expect("Syntax Error").parse().expect("Syntax Error");
|
||||
let seats: usize = bits.next().expect("Syntax Error").parse().expect("Syntax Error");
|
||||
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 {
|
||||
@ -57,57 +60,47 @@ impl<N: Number> Election<N> {
|
||||
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
|
||||
for line in &mut lines {
|
||||
if line == "0" {
|
||||
break;
|
||||
}
|
||||
let ballot_list = main.next().unwrap().into_inner();
|
||||
for ballot in ballot_list {
|
||||
let mut ballot_pairs = ballot.into_inner();
|
||||
|
||||
let mut bits = line.split(" ");
|
||||
|
||||
if line.starts_with("-") {
|
||||
// Withdrawn candidates
|
||||
for bit in bits.into_iter() {
|
||||
let val = bit[1..bit.len()].parse::<usize>().expect("Syntax Error");
|
||||
election.withdrawn_candidates.push(val - 1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
let value = N::parse(bits.next().expect("Syntax Error"));
|
||||
let value = N::parse(ballot_pairs.next().unwrap().as_str());
|
||||
|
||||
let mut ballot = Ballot {
|
||||
orig_value: value,
|
||||
preferences: Vec::new(),
|
||||
};
|
||||
|
||||
for preference in bits {
|
||||
if preference != "0" {
|
||||
let preference = preference.parse::<usize>().expect("Syntax Error");
|
||||
ballot.preferences.push(preference - 1);
|
||||
}
|
||||
let ballot_preferences = ballot_pairs.next().unwrap().into_inner();
|
||||
for preference in ballot_preferences {
|
||||
let preference: usize = preference.as_str().parse().unwrap();
|
||||
ballot.preferences.push(preference - 1);
|
||||
}
|
||||
|
||||
election.ballots.push(ballot);
|
||||
}
|
||||
|
||||
// Zero line
|
||||
main.next();
|
||||
|
||||
// Read candidates
|
||||
for line in lines.by_ref().take(num_candidates) {
|
||||
let mut line = &line[..];
|
||||
if line.starts_with("\"") && line.ends_with("\"") {
|
||||
line = &line[1..line.len()-1];
|
||||
}
|
||||
|
||||
election.candidates.push(Candidate { name: line.to_string() });
|
||||
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 line = lines.next().expect("Syntax Error");
|
||||
let mut line = &line[..];
|
||||
if line.starts_with("\"") && line.ends_with("\"") {
|
||||
line = &line[1..line.len()-1];
|
||||
}
|
||||
election.name.push_str(line);
|
||||
let string = main.next().expect("Expected election title");
|
||||
election.name.push_str(blt::unwrap_string(string));
|
||||
|
||||
return election;
|
||||
}
|
||||
@ -321,7 +314,7 @@ impl<'a, N: Number> CountCard<'a, N> {
|
||||
pub struct Parcel<'a, N> {
|
||||
/// [Vote]s in this parcel
|
||||
pub votes: Vec<Vote<'a, N>>,
|
||||
/// Order for sorting with [opentally::stv::ExclusionMethod::BySource]
|
||||
/// Order for sorting with [crate::stv::ExclusionMethod::BySource]
|
||||
pub source_order: usize,
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,8 @@
|
||||
|
||||
//! Open source counting software for various preferential voting election systems
|
||||
|
||||
/// File parsers
|
||||
pub mod parser;
|
||||
/// Data types and logic for constraints on elections
|
||||
pub mod constraints;
|
||||
/// Data types for representing abstract elections
|
||||
@ -36,6 +38,8 @@ 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");
|
||||
|
13
src/main.rs
13
src/main.rs
@ -23,7 +23,7 @@ use opentally::stv;
|
||||
use clap::{AppSettings, Clap};
|
||||
|
||||
use std::cmp::max;
|
||||
use std::fs::File;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, BufRead};
|
||||
use std::ops;
|
||||
|
||||
@ -190,29 +190,28 @@ fn main() {
|
||||
let Command::STV(cmd_opts) = opts.command;
|
||||
|
||||
// Read BLT file
|
||||
let file = File::open(&cmd_opts.filename).expect("IO Error");
|
||||
let lines = io::BufReader::new(file).lines();
|
||||
let file = fs::read_to_string(&cmd_opts.filename).expect("IO Error");
|
||||
|
||||
// Create and count election according to --numbers
|
||||
if cmd_opts.numbers == "rational" {
|
||||
let mut election: Election<Rational> = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
|
||||
let mut election: Election<Rational> = Election::from_blt(&file);
|
||||
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(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
|
||||
let mut election: Election<NativeFloat64> = Election::from_blt(&file);
|
||||
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(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
|
||||
let mut election: Election<Fixed> = Election::from_blt(&file);
|
||||
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(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
|
||||
let mut election: Election<GuardedFixed> = Election::from_blt(&file);
|
||||
maybe_load_constraints(&mut election, &cmd_opts.constraints);
|
||||
count_election::<GuardedFixed>(election, cmd_opts);
|
||||
}
|
||||
|
36
src/parser/blt.pest
Normal file
36
src/parser/blt.pest
Normal file
@ -0,0 +1,36 @@
|
||||
main = {
|
||||
SOI ~ NEWLINE*
|
||||
~ header ~ NEWLINE*
|
||||
~ withdrawn ~ NEWLINE*
|
||||
~ ballot_list
|
||||
~ zero ~ NEWLINE*
|
||||
~ (string ~ NEWLINE*)+
|
||||
~ EOI
|
||||
}
|
||||
|
||||
header = {
|
||||
integer ~ integer
|
||||
}
|
||||
|
||||
withdrawn = {
|
||||
(withdrawn_cand ~ NEWLINE*)*
|
||||
}
|
||||
withdrawn_cand = @{ "-" ~ integer }
|
||||
|
||||
ballot_list = { (ballot ~ NEWLINE*)* }
|
||||
ballot = {
|
||||
number ~ ballot_preferences ~ (zero ~ NEWLINE? | NEWLINE)
|
||||
}
|
||||
ballot_preferences = { integer* }
|
||||
|
||||
zero = { "0" }
|
||||
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) }
|
44
src/parser/blt.rs
Normal file
44
src/parser/blt.rs
Normal file
@ -0,0 +1,44 @@
|
||||
/* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use pest::iterators::Pair;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[grammar = "parser/blt.pest"]
|
||||
pub struct BLTParser;
|
||||
|
||||
/// 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();
|
||||
}
|
||||
Rule::quoted_string => {
|
||||
let string = string.as_str();
|
||||
return &string[1..string.len()-1];
|
||||
}
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
_ => panic!("Non-string passed to unwrap_string")
|
||||
}
|
||||
}
|
19
src/parser/mod.rs
Normal file
19
src/parser/mod.rs
Normal file
@ -0,0 +1,19 @@
|
||||
/* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/// BLT file parser
|
||||
pub mod blt;
|
@ -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.lines().map(|s| s.to_string()).into_iter());
|
||||
let election: Election<$type> = Election::from_blt(&text);
|
||||
return [<Election$type>](election);
|
||||
}
|
||||
|
||||
|
10
tests/aec.rs
10
tests/aec.rs
@ -25,7 +25,7 @@ use csv::StringRecord;
|
||||
use flate2::bufread::GzDecoder;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufRead};
|
||||
use std::io::{self, Read};
|
||||
|
||||
#[test]
|
||||
fn aec_tas19_rational() {
|
||||
@ -45,12 +45,12 @@ 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 gz_decoder = GzDecoder::new(file_reader);
|
||||
let gz_reader = io::BufReader::new(gz_decoder);
|
||||
let lines = gz_reader.lines();
|
||||
let mut gz_decoder = GzDecoder::new(file_reader);
|
||||
let mut input = String::new();
|
||||
gz_decoder.read_to_string(&mut input).expect("IO Error");
|
||||
|
||||
// Read BLT
|
||||
let election: Election<Rational> = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
|
||||
let election: Election<Rational> = Election::from_blt(&input);
|
||||
|
||||
// Validate candidate names
|
||||
for (i, candidate) in candidates.iter().enumerate() {
|
||||
|
@ -22,7 +22,7 @@ use opentally::election::{CandidateState, CountState, Election};
|
||||
use opentally::numbers::Rational;
|
||||
use opentally::stv;
|
||||
|
||||
use std::fs::File;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, BufRead};
|
||||
|
||||
#[test]
|
||||
@ -30,10 +30,8 @@ fn prsa1_constr1_rational() {
|
||||
// FIXME: This is unvalidated!
|
||||
|
||||
// Read BLT
|
||||
let file = File::open("tests/data/prsa1.blt").expect("IO Error");
|
||||
let file_reader = io::BufReader::new(file);
|
||||
let lines = file_reader.lines();
|
||||
let mut election: Election<Rational> = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
|
||||
let file = fs::read_to_string("tests/data/prsa1.blt").expect("IO Error");
|
||||
let mut election: Election<Rational> = Election::from_blt(&file);
|
||||
|
||||
// Read CON
|
||||
let file = File::open("tests/data/prsa1_constr1.con").expect("IO Error");
|
||||
@ -94,10 +92,8 @@ fn prsa1_constr2_rational() {
|
||||
// FIXME: This is unvalidated!
|
||||
|
||||
// Read BLT
|
||||
let file = File::open("tests/data/prsa1.blt").expect("IO Error");
|
||||
let file_reader = io::BufReader::new(file);
|
||||
let lines = file_reader.lines();
|
||||
let mut election: Election<Rational> = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
|
||||
let file = fs::read_to_string("tests/data/prsa1.blt").expect("IO Error");
|
||||
let mut election: Election<Rational> = Election::from_blt(&file);
|
||||
|
||||
// Read CON
|
||||
let file = File::open("tests/data/prsa1_constr2.con").expect("IO Error");
|
||||
@ -158,10 +154,8 @@ fn prsa1_constr3_rational() {
|
||||
// FIXME: This is unvalidated!
|
||||
|
||||
// Read BLT
|
||||
let file = File::open("tests/data/prsa1.blt").expect("IO Error");
|
||||
let file_reader = io::BufReader::new(file);
|
||||
let lines = file_reader.lines();
|
||||
let mut election: Election<Rational> = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
|
||||
let file = fs::read_to_string("tests/data/prsa1.blt").expect("IO Error");
|
||||
let mut election: Election<Rational> = Election::from_blt(&file);
|
||||
|
||||
// Read CON
|
||||
let file = File::open("tests/data/prsa1_constr3.con").expect("IO Error");
|
||||
|
@ -21,8 +21,7 @@ use opentally::election::{CandidateState, CountState, Election};
|
||||
use opentally::numbers::{Fixed, NativeFloat64, Number};
|
||||
use opentally::stv;
|
||||
|
||||
use std::io::{self, BufRead};
|
||||
use std::fs::File;
|
||||
use std::fs;
|
||||
|
||||
// Compare ers97.blt count with result produced by 1987 Hill–Wichmann–Woodall reference implementation
|
||||
#[test]
|
||||
@ -86,11 +85,8 @@ fn meek06_ers97_fixed12() {
|
||||
Fixed::set_dps(12);
|
||||
|
||||
// Read BLT
|
||||
let file = File::open("tests/data/ers97.blt").expect("IO Error");
|
||||
let file_reader = io::BufReader::new(file);
|
||||
let lines = file_reader.lines();
|
||||
|
||||
let election: Election<Fixed> = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
|
||||
let file = fs::read_to_string("tests/data/ers97.blt").expect("IO Error");
|
||||
let election: Election<Fixed> = Election::from_blt(&file);
|
||||
|
||||
// Initialise count state
|
||||
let mut state = CountState::new(&election);
|
||||
@ -162,11 +158,8 @@ fn meeknz_ers97_fixed12() {
|
||||
Fixed::set_dps(12);
|
||||
|
||||
// Read BLT
|
||||
let file = File::open("tests/data/ers97.blt").expect("IO Error");
|
||||
let file_reader = io::BufReader::new(file);
|
||||
let lines = file_reader.lines();
|
||||
|
||||
let election: Election<Fixed> = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
|
||||
let file = fs::read_to_string("tests/data/ers97.blt").expect("IO Error");
|
||||
let election: Election<Fixed> = Election::from_blt(&file);
|
||||
|
||||
// Initialise count state
|
||||
let mut state = CountState::new(&election);
|
||||
|
@ -15,16 +15,13 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// https://web.archive.org/web/20121004213938/http://www.glasgow.gov.uk/en/YourCouncil/Elections_Voting/Election_Results/ElectionScotland2007/LGWardResults.htm?ward=1&wardname=1%20-%20Linn
|
||||
|
||||
use opentally::election::{CandidateState, CountState, Election};
|
||||
use opentally::numbers::{Fixed, GuardedFixed, Number};
|
||||
use opentally::stv;
|
||||
|
||||
use xmltree::Element;
|
||||
|
||||
use std::io::{self, BufRead};
|
||||
use std::fs::File;
|
||||
use std::fs::{self, File};
|
||||
use std::ops;
|
||||
|
||||
#[test]
|
||||
@ -114,11 +111,8 @@ where
|
||||
let num_stages = root.get_child("headerrow").expect("Syntax Error").children.len();
|
||||
|
||||
// Read BLT
|
||||
let file = File::open("tests/data/linn07.blt").expect("IO Error");
|
||||
let file_reader = io::BufReader::new(file);
|
||||
let lines = file_reader.lines();
|
||||
|
||||
let mut election: Election<N> = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
|
||||
let file = fs::read_to_string("tests/data/linn07.blt").expect("IO Error");
|
||||
let mut election: Election<N> = Election::from_blt(&file);
|
||||
|
||||
// !!! FOR SCOTTISH STV !!!
|
||||
election.normalise_ballots();
|
||||
|
@ -21,8 +21,7 @@ use opentally::stv;
|
||||
|
||||
use csv::StringRecord;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufRead};
|
||||
use std::fs;
|
||||
use std::ops;
|
||||
|
||||
#[allow(dead_code)] // Suppress false positive
|
||||
@ -48,11 +47,9 @@ where
|
||||
let stages: Vec<usize> = records.first().unwrap().iter().skip(1).step_by(2).map(|s| s.parse().unwrap()).collect();
|
||||
|
||||
// Read BLT
|
||||
let file = File::open(blt_file).expect("IO Error");
|
||||
let file_reader = io::BufReader::new(file);
|
||||
let lines = file_reader.lines();
|
||||
let file = fs::read_to_string(blt_file).expect("IO Error");
|
||||
|
||||
let election: Election<N> = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
|
||||
let election: Election<N> = Election::from_blt(&file);
|
||||
|
||||
// Validate candidate names
|
||||
for (i, candidate) in candidates.iter().enumerate() {
|
||||
|
Loading…
Reference in New Issue
Block a user