///|
pub(all) struct WorkflowError {
field : String
message : String
} derive(Eq, Debug)
///|
fn workflow_error(field : String, message : String) -> WorkflowError {
{ field, message, }
}
///|
pub extend WorkflowError with Eq::{equal, not_equal}
///|
pub extend WorkflowError with Debug::{to_repr}
///|
fn selection_space(text : String, start : Int32) -> Int32 {
let mut i = start
while i < text.length() {
match text[i] {
32 | 9 | 10 | 13 => i += 1
_ => break
}
}
i
}
///|
fn selection_number(
text : String,
start : Int32,
) -> Result[(Int32, Int32), WorkflowError] {
let mut i = start
let mut value = 0
while i < text.length() && text[i] >= 48 && text[i] <= 57 {
value = value * 10 + text[i].to_int() - 48
if value > 100000 {
return Err(workflow_error("selection[\{start}]", "page exceeds 100000"))
}
i += 1
}
if i == start || value == 0 {
return Err(
workflow_error("selection[\{start}]", "expected a positive page number"),
)
}
Ok((value, i))
}
///|
/// Comma-separated page numbers, ascending/descending inclusive ranges and `b`.
/// Order and duplicates are intentional. Offsets are UTF-16 code-unit indices.
pub fn parse_selection(
text : String,
source_pages : Int32,
) -> Result[Array[Slot], WorkflowError] {
if source_pages < 1 || source_pages > 100000 {
return Err(workflow_error("source_pages", "expected 1..100000"))
}
if text.length() > 1000000 {
return Err(workflow_error("selection", "input exceeds 1000000 code units"))
}
let pages : Array[Slot] = []
let mut i = selection_space(text, 0)
while i < text.length() {
if text[i] == 98 {
pages.push(Blank)
i += 1
} else {
let (first, end) = match selection_number(text, i) {
Ok(pair) => pair
Err(e) => return Err(e)
}
i = selection_space(text, end)
let mut last = first
if i < text.length() && text[i] == 45 {
i = selection_space(text, i + 1)
let (number, end) = match selection_number(text, i) {
Ok(pair) => pair
Err(e) => return Err(e)
}
last = number
i = end
}
if first > source_pages || last > source_pages {
return Err(
workflow_error("selection[\{i}]", "page outside source document"),
)
}
let count = if first <= last {
last - first + 1
} else {
first - last + 1
}
if pages.length() + count > 100000 {
return Err(
workflow_error("selection", "expanded selection exceeds 100000 slots"),
)
}
let step = if first <= last { 1 } else { -1 }
for offset in 0.. 100000 {
return Err(
workflow_error("selection", "expanded selection exceeds 100000 slots"),
)
}
i = selection_space(text, i)
if i == text.length() {
break
}
if text[i] != 44 {
return Err(workflow_error("selection[\{i}]", "expected a comma"))
}
i = selection_space(text, i + 1)
if i == text.length() {
return Err(workflow_error("selection[\{i}]", "missing item after comma"))
}
}
if pages.is_empty() {
return Err(workflow_error("selection", "selection is empty"))
}
Ok(pages)
}