/* 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::{Ballot, Candidate, Election}; use crate::numbers::Number; use std::iter::Peekable; /// Utility for parsing a BLT file pub struct BLTParser> { /// The peekable iterator of chars representing the BLT file chars: Peekable, /// Temporary buffer for parsing ballot values buf_ballot_value: String, /// Number of candidates num_candidates: usize, /// Parsed [Election] election: Election, } /// An error when parsing a BLT file #[derive(Debug)] pub enum ParseError { /// Invalid syntax Invalid } impl> BLTParser { // 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()?; } } 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 { return Ok(self.integer()?.parse().expect("Invalid usize")); } /// Parse an integer as a [String] fn integer(&mut self) -> Result { 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 { 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 { 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 { 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 { if self.lookahead() >= '1' && self.lookahead() <= '9' { return Ok(self.accept()); } else { return Err(ParseError::Invalid); } } /// Read any digit fn digit(&mut self) -> Result { 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 { 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) -> 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 { return self.election; } }