use std::ops::Range;
use haematite_query::stage::rg::Flags;
use super::{Input, Refused, Span, Spanned, Stage, first};
pub(super) fn read(text: &str, start: usize) -> Result<Spanned<Stage>, Refused> {
let mut input = Input { text, rest: text };
let result = arguments(&mut input).map(|value| Spanned {
value,
span: Span {
start,
end: start + input.position(),
},
});
result.map_err(|refused| shifted(refused, start))
}
fn arguments(input: &mut Input<'_>) -> Result<Stage, Refused> {
input.trim();
let name = input.word();
if !matches!(name, "rg" | "grep") {
return Err(input.parse("an rg stage starts with `rg`"));
}
input.advance(name.len());
input.require("(", "`rg` takes its arguments in brackets")?;
input.trim();
let field = if input.take(".") {
let length = input
.rest
.find(|character| !first(character))
.unwrap_or(input.rest.len());
if length == 0 {
return Err(input.parse("an rg field has a name after its dot"));
}
let field = input.rest.split_at(length).0.to_owned();
input.advance(length);
input.require(";", "an rg field is followed by `;` and a quoted pattern")?;
Some(field)
} else {
None
};
let pattern = input.string("an rg pattern")?;
input.trim();
let flags = if input.take(";") {
input.trim();
let start = input.position();
let literal = input.rest;
let flags = input.string("rg flags")?;
let (literal, _) = literal.split_at(input.position() - start);
if let Err(refusal) = Flags::parse_spanned(&flags) {
let span = flag_span(literal, &flags, refusal.span, start)?;
return Err(Refused::Flag(refusal.message, span));
}
flags
} else {
String::new()
};
input.require(
")",
"`rg(` ends after its pattern and optional flags with `)`",
)?;
Ok(Stage::Rg {
field,
pattern,
flags,
})
}
fn flag_span(
literal: &str,
decoded: &str,
range: Range<usize>,
offset: usize,
) -> Result<Span, Refused> {
let invalid = || {
Refused::Parse(
"rg flag span cannot be mapped to its quoted string".to_owned(),
Span {
start: offset,
end: offset + literal.len(),
},
)
};
let mut written = literal.char_indices().skip(1);
let mut start = None;
let mut end = None;
for (index, character) in decoded.char_indices() {
let (raw_start, raw) = written.next().ok_or_else(invalid)?;
let mut raw_end = raw_start + raw.len_utf8();
if raw == '\\' {
let (position, escape) = written.next().ok_or_else(invalid)?;
raw_end = position + escape.len_utf8();
if escape == 'u' {
let count = if character.len_utf16() == 2 { 10 } else { 4 };
for _ in 0..count {
let (position, character) = written.next().ok_or_else(invalid)?;
raw_end = position + character.len_utf8();
}
}
}
if index == range.start {
start = Some(offset + raw_start);
}
if index + character.len_utf8() == range.end {
end = Some(offset + raw_end);
break;
}
}
Ok(Span {
start: start.ok_or_else(invalid)?,
end: end.ok_or_else(invalid)?,
})
}
fn shifted(refused: Refused, offset: usize) -> Refused {
let span = refused.span();
let span = Span {
start: span.start + offset,
end: span.end + offset,
};
match refused {
Refused::NotYet(name, _) => Refused::NotYet(name, span),
Refused::Unserved(message, _) => Refused::Unserved(message, span),
Refused::Parse(message, _) => Refused::Parse(message, span),
Refused::WholeRecords(message, _) => Refused::WholeRecords(message, span),
Refused::Percentile(message, _) => Refused::Percentile(message, span),
Refused::Window(code, message, _) => Refused::Window(code, message, span),
Refused::Flag(message, _) => Refused::Flag(message, span),
}
}
#[cfg(test)]
#[path = "rg_tests.rs"]
mod rg_tests;