use super::{Input, Refused, Span, Spanned, Stage, first};
pub(super) fn read(text: &str, start: usize) -> Result<Spanned<Stage>, Refused> {
parse(text)
.map(|mut parsed| {
parsed.span.start += start;
parsed.span.end += start;
parsed
})
.map_err(|refusal| match refusal {
Refused::Parse(message, span) => Refused::Parse(message, shifted(span, start)),
Refused::WholeRecords(message, span) => {
Refused::WholeRecords(message, shifted(span, start))
}
Refused::Percentile(message, span) => {
Refused::Percentile(message, shifted(span, start))
}
Refused::NotYet(message, span) => Refused::NotYet(message, shifted(span, start)),
Refused::Unserved(message, span) => Refused::Unserved(message, shifted(span, start)),
Refused::Flag(message, span) => Refused::Flag(message, shifted(span, start)),
Refused::Window(code, message, span) => {
Refused::Window(code, message, shifted(span, start))
}
})
}
const fn shifted(span: Span, start: usize) -> Span {
Span {
start: span.start + start,
end: span.end + start,
}
}
fn parse(text: &str) -> Result<Spanned<Stage>, Refused> {
let mut input = Input { text, rest: text };
input.trim();
let start = input.position();
if input.word() != "group_by" {
return Err(input.parse("`group_by` names the grouping stage"));
}
input.advance(8);
input.require("(", "`group_by` takes one top-level field in brackets")?;
input.require(
".",
"`group_by` takes a field with a leading dot, as `.ward`",
)?;
let length = input
.rest
.find(|character| !first(character))
.unwrap_or(input.rest.len());
if length == 0 {
return Err(input.parse("`group_by` needs a field name after its dot"));
}
let field = input.rest.split_at(length).0.to_owned();
input.advance(length);
input.require(
")",
"`group_by` takes exactly one top-level field and a closing `)`",
)?;
Ok(Spanned {
value: Stage::GroupBy { field },
span: Span {
start,
end: input.position(),
},
})
}
#[cfg(test)]
#[path = "group_by_tests.rs"]
mod tests;