///|
/// Read the versioned, bounded profile format documented in docs/FORMAT.md.
pub fn parse_profile(input : String) -> Result[Election, Diagnostic] {
if input.length() > 2000000 {
return Err(problem("input.limit", "at most 2000000 code units"))
}
let candidates : Array[String] = []
let ballots : Array[Ballot] = []
let mut stage = 0
let mut line_no = 0
let mut total = 0
for raw in input.split("\n") {
line_no += 1
let line = raw.trim().to_owned()
if line.is_empty() || line.has_prefix("#") {
continue
}
if line.length() > 4096 {
return Err({
code: "line.limit",
line: line_no,
detail: "at most 4096 code units",
})
}
if stage == 0 {
if line != "moonballot 1" {
return Err({
code: "profile.version",
line: line_no,
detail: "expected moonballot 1",
})
}
stage = 1
} else if stage == 1 {
if !line.has_prefix("candidates:") {
return Err({
code: "profile.candidates",
line: line_no,
detail: "expected candidates: A,B",
})
}
for item in line[11:].split(",") {
candidates.push(item.trim().to_owned())
}
match Election::new(candidates, []) {
Err(e) => return Err({ ..e, line: line_no })
Ok(_) => ()
}
stage = 2
} else {
let ballot = match parse_ballot(line) {
Err(e) => return Err({ ..e, line: line_no })
Ok(b) => b
}
for id in ballot.ranking {
if !candidates.contains(id) {
return Err({ code: "ranking.unknown", line: line_no, detail: id })
}
}
if total > 1000000 - ballot.weight {
return Err({
code: "total.limit",
line: line_no,
detail: "total weight exceeds 1000000",
})
}
total += ballot.weight
ballots.push(ballot)
if ballots.length() > 10000 {
return Err({
code: "ballots.limit",
line: line_no,
detail: "at most 10000 rows",
})
}
}
}
if stage < 2 {
return Err({
code: "profile.incomplete",
line: line_no,
detail: "version and candidates required",
})
}
Election::new(candidates, ballots)
}