aboutsummaryrefslogtreecommitdiff
path: root/src/parser/blt.rs
blob: d64a36a1fbea0003231e02b443d61c0d2e892a64 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
1/*  OpenTally: Open-source election vote counting
2 *  Copyright © 2021 Lee Yingtong Li (RunasSudo)
3 *
4 *  This program is free software: you can redistribute it and/or modify
5 *  it under the terms of the GNU Affero General Public License as published by
6 *  the Free Software Foundation, either version 3 of the License, or
7 *  (at your option) any later version.
8 *
9 *  This program is distributed in the hope that it will be useful,
10 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 *  GNU Affero General Public License for more details.
13 *
14 *  You should have received a copy of the GNU Affero General Public License
15 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
16 */
17
18use crate::election::{Ballot, Candidate, Election};
19use crate::numbers::Number;
20
21use std::fmt;
22use std::iter::Peekable;
23
24/// Utility for parsing a BLT file
25pub struct BLTParser<N: Number, I: Iterator<Item=char>> {
26	/// The peekable iterator of chars representing the BLT file
27	chars: Peekable<I>,
28
29	/// Temporary buffer for parsing ballot values
30	ballot_value_buf: String,
31	/// Current line number
32	line_no: u32,
33	/// Current column number
34	col_no: u32,
35	
36	/// Number of candidates
37	num_candidates: usize,
38	/// Parsed [Election]
39	election: Election<N>,
40}
41
42/// An error when parsing a BLT file
43pub enum ParseError {
44	/// Unexpected character
45	Unexpected(u32, u32, char),
46	/// Unexpected character, expected ...
47	Expected(u32, u32, char, &'static str),
48}
49
50impl fmt::Display for ParseError {
51	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52		match self {
53			ParseError::Unexpected(line_no, col_no, char) => {
54				f.write_fmt(format_args!("Line {} col {}, unexpected '{}'", line_no, col_no, char))?;
55			}
56			ParseError::Expected(line_no, col_no, char, expected) => {
57				f.write_fmt(format_args!("Line {} col {}, unexpected '{}', expected {}", line_no, col_no, char, expected))?;
58			}
59		}
60		return Ok(());
61	}
62}
63
64impl<N: Number, I: Iterator<Item=char>> BLTParser<N, I> {
65	// NON-TERMINALS - HIGHER LEVEL
66	
67	/// Parse the BLT file
68	pub fn parse_blt(&mut self) -> Result<(), ParseError> {
69		self.delimiter();
70		
71		self.header()?;
72		self.withdrawn_candidates()?;
73		self.ballots()?;
74		self.strings()?;
75		
76		return Ok(());
77	}
78	
79	/// Parse the header
80	fn header(&mut self) -> Result<(), ParseError> {
81		self.num_candidates = self.usize()?;
82		self.delimiter();
83		self.election.seats = self.usize()?;
84		self.delimiter();
85		return Ok(());
86	}
87	
88	/// Parse the withdrawn candidates (if any)
89	fn withdrawn_candidates(&mut self) -> Result<(), ParseError> {
90		while self.lookahead() == '-' {
91			self.accept(); // Minus sign
92			let index = self.usize()? - 1;
93			self.election.withdrawn_candidates.push(index);
94			self.delimiter();
95		}
96		return Ok(());
97	}
98	
99	/// Parse the list of ballots
100	fn ballots(&mut self) -> Result<(), ParseError> {
101		loop {
102			if self.lookahead() == '0' {
103				// End of ballots, or start of decimal?
104				self.accept();
105				
106				if self.lookahead() == '.' {
107					// Decimal
108					self.ballot_value_buf.clear();
109					self.ballot_value_buf.push('0');
110					self.ballot()?;
111				} else {
112					// End of ballots
113					self.delimiter();
114					break;
115				}
116			} else {
117				self.ballot_value_buf.clear();
118				self.ballot()?;
119			}
120		}
121		return Ok(());
122	}
123	
124	/// Parse a ballot
125	fn ballot(&mut self) -> Result<(), ParseError> {
126		self.ballot_value()?;
127		
128		self.delimiter_not_nl();
129		
130		// Read preferences
131		let mut preferences = Vec::new();
132		loop {
133			if self.lookahead() == '0' || self.lookahead() == '\n' {
134				// End of preferences
135				self.accept();
136				break;
137			} else {
138				preferences.push(self.usize()? - 1);
139				self.delimiter_not_nl();
140			}
141		}
142		
143		self.delimiter();
144		
145		let ballot = Ballot {
146			orig_value: N::parse(&self.ballot_value_buf),
147			preferences: preferences,
148		};
149		self.election.ballots.push(ballot);
150		
151		return Ok(());
152	}
153	
154	/// Parse the list of strings at the end of the BLT file
155	fn strings(&mut self) -> Result<(), ParseError> {
156		for _ in 0..self.num_candidates {
157			let name = self.string()?;
158			self.election.candidates.push(Candidate {
159				name
160			});
161		}
162		let name = self.string()?;
163		self.election.name = name;
164		return Ok(());
165	}
166	
167	// NON-TERMINALS - LOWER LEVEL
168	
169	/// Parse an integer into a [usize]
170	fn usize(&mut self) -> Result<usize, ParseError> {
171		return Ok(self.integer()?.parse().expect("Invalid usize"));
172	}
173	
174	/// Parse an integer as a [String]
175	fn integer(&mut self) -> Result<String, ParseError> {
176		let mut result = String::from(self.digit_nonzero()?);
177		loop {
178			match self.digit() {
179				Err(_) => { break; }
180				Ok(d) => { result.push(d); }
181			}
182		}
183		return Ok(result);
184	}
185	
186	/// Parse a number as an instance of N
187	fn ballot_value(&mut self) -> Result<(), ParseError> {
188		loop {
189			match self.ballot_value_element() {
190				Err(_) => { break; }
191				Ok(d) => { self.ballot_value_buf.push(d); }
192			}
193		}
194		return Ok(());
195	}
196	
197	/// Parse a quoted or raw string
198	fn string(&mut self) -> Result<String, ParseError> {
199		match self.quoted_string() {
200			Ok(s) => { return Ok(s); }
201			Err(_) => {}
202		}
203		match self.raw_string() {
204			Ok(s) => { return Ok(s); }
205			Err(_) => {}
206		}
207		return Err(ParseError::Expected(self.line_no, self.col_no, self.lookahead(), "string"));
208	}
209	
210	/// Parse a quoted string
211	fn quoted_string(&mut self) -> Result<String, ParseError> {
212		if self.lookahead() == '"' {
213			self.accept(); // Opening quotation mark
214			let mut result = String::new();
215			while self.lookahead() != '"' {
216				// TODO: BufRead::read_until ?
217				result.push(self.accept());
218			}
219			self.accept(); // Closing quotation mark
220			if !self.eof() {
221				self.delimiter();
222			}
223			return Ok(result);
224		} else {
225			return Err(ParseError::Expected(self.line_no, self.col_no, self.lookahead(), "'\"'"));
226		}
227	}
228	
229	/// Parse a raw (unquoted) string
230	fn raw_string(&mut self) -> Result<String, ParseError> {
231		let mut result = String::new();
232		while !self.lookahead().is_whitespace() && !self.eof() {
233			result.push(self.accept());
234		}
235		if !self.eof() {
236			self.delimiter();
237		}
238		return Ok(result);
239	}
240	
241	/// Skip any sequence of whitespace or comments
242	fn delimiter(&mut self) {
243		loop {
244			if self.eof() {
245				break;
246			} else if self.lookahead() == '#' {
247				self.dnl();
248				if !self.eof() {
249					self.accept(); // Trailing newline
250				}
251			} else if self.lookahead().is_whitespace() {
252				self.accept();
253				while !self.eof() && self.lookahead().is_whitespace() { self.accept(); }
254			} else {
255				break;
256			}
257		}
258	}
259	
260	/// Skip any sequence of whitespace or comments, but do not accept any newline and leave it trailing
261	fn delimiter_not_nl(&mut self) {
262		loop {
263			if self.eof() {
264				break;
265			} else if self.lookahead() == '#' {
266				self.dnl();
267			} else if self.lookahead().is_whitespace() && self.lookahead() != '\n' {
268				self.accept();
269				while !self.eof() && self.lookahead().is_whitespace() && self.lookahead() != '\n' { self.accept(); }
270			} else {
271				break;
272			}
273		}
274	}
275	
276	/// Skip to the next newline
277	fn dnl(&mut self) {
278		while !self.eof() && self.lookahead() != '\n' {
279			// TODO: BufRead::read_until ?
280			self.accept();
281		}
282	}
283	
284	// TERMINALS
285	
286	/// Read a nonzero digit
287	fn digit_nonzero(&mut self) -> Result<char, ParseError> {
288		if self.lookahead() >= '1' && self.lookahead() <= '9' {
289			return Ok(self.accept());
290		} else {
291			return Err(ParseError::Expected(self.line_no, self.col_no, self.lookahead(), "nonzero digit"));
292		}
293	}
294	
295	/// Read any digit
296	fn digit(&mut self) -> Result<char, ParseError> {
297		if self.lookahead() >= '0' && self.lookahead() <= '9' {
298			return Ok(self.accept());
299		} else {
300			return Err(ParseError::Expected(self.line_no, self.col_no, self.lookahead(), "digit"));
301		}
302	}
303	
304	/// Read any element of a valid number, i.e. a digit, decimal point or slash
305	fn ballot_value_element(&mut self) -> Result<char, ParseError> {
306		if (self.lookahead() >= '0' && self.lookahead() <= '9') || self.lookahead() == '.' || self.lookahead() == '/' {
307			return Ok(self.accept());
308		} else {
309			return Err(ParseError::Expected(self.line_no, self.col_no, self.lookahead(), "number"));
310		}
311	}
312	
313	// UTILITIES
314	
315	/// Return if this is the end of the file
316	fn eof(&mut self) -> bool {
317		return self.chars.peek().is_none();
318	}
319	
320	/// Peek at the next character in the stream
321	fn lookahead(&mut self) -> char {
322		// TODO: Cache this?
323		return *self.chars.peek().expect("Unexpected EOF");
324	}
325	
326	/// Read and return one character from the stream
327	fn accept(&mut self) -> char {
328		let result = self.chars.next().expect("Unexpected EOF");
329		if result == '\n' {
330			self.line_no += 1;
331			self.col_no = 1;
332		} else {
333			self.col_no += 1;
334		}
335		return result;
336	}
337	
338	// PUBLIC API
339	
340	/// Return a new [BLTParser]
341	pub fn new(chars: Peekable<I>) -> Self {
342		Self {
343			chars,
344			ballot_value_buf: String::new(),
345			line_no: 1,
346			col_no: 1,
347			num_candidates: 0,
348			election: Election {
349				name: String::new(),
350				seats: 0,
351				candidates: Vec::new(),
352				withdrawn_candidates: Vec::new(),
353				ballots: Vec::new(),
354				constraints: None,
355			},
356		}
357	}
358	
359	/// Return the parsed [Election]
360	pub fn as_election(self) -> Election<N> {
361		return self.election;
362	}
363}
Contact (issues, pull requests, etc.) at git@yingtongli.me. Generated by cgit.