use super::{Input, Question, Refused, Span, Spanned, Stage, first};
use crate::serve::protocol::Metric;
pub(super) fn read(text: &str, start: usize) -> Result<Spanned<Stage>, Refused> {
let mut input = Input { text, rest: text };
parse(&mut input)
.map(|value| Spanned {
value,
span: Span {
start,
end: start + input.position(),
},
})
.map_err(|refused| offset(refused, start))
}
fn parse(input: &mut Input<'_>) -> Result<Stage, Refused> {
input.trim();
if input.word() != "rerank" {
return Err(input.parse("a rerank stage starts with `rerank`"));
}
input.advance(6);
input.trim();
if !input.take("(") {
return Err(Refused::Parse(
"`rerank` takes its arguments in brackets".to_owned(),
Span { start: 0, end: 6 },
));
}
input.require(".", "`rerank` names a vector field with a leading dot")?;
let length = input
.rest
.find(|character| !first(character))
.unwrap_or(input.rest.len());
if length == 0 {
return Err(input.parse("`rerank` names a field after its dot"));
}
let field = input.rest.split_at(length).0.to_owned();
input.advance(length);
separator(input)?;
let question = if input.rest.starts_with('"') {
let text = input.string("the rerank question")?;
if text.trim().is_empty() {
return Err(input.parse("`rerank` takes nonempty question words"));
}
Question::Text(text)
} else {
if input.word() != "record" {
return Err(input.parse("`rerank` takes quoted words or `record(\"id\")`"));
}
input.advance(6);
input.require("(", "`record` takes its id in brackets")?;
let id = input.string("the rerank record id")?;
if id.trim().is_empty() {
return Err(input.parse("the rerank record id is nonempty"));
}
input.require(")", "`record(` takes one quoted id and a `)`")?;
Question::Record(id)
};
separator(input)?;
let name = input.word();
let measure = match name {
"cosine" => Metric::Cosine,
"l2" => Metric::L2,
"dot" => Metric::Dot,
_ => return Err(input.parse("`rerank` names its measure: cosine, l2 or dot")),
};
input.advance(name.len());
input.require(")", "`rerank(` is not closed")?;
Ok(Stage::Rerank {
field,
question,
measure,
})
}
fn separator(input: &mut Input<'_>) -> Result<(), Refused> {
input.require(
";",
"`rerank` separates its field, question and measure with `;`",
)?;
input.trim();
Ok(())
}
fn offset(refused: Refused, start: usize) -> Refused {
let span = refused.span();
let span = Span {
start: span.start + start,
end: span.end + start,
};
match refused {
Refused::NotYet(message, _) => Refused::NotYet(message, 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::Flag(message, _) => Refused::Flag(message, span),
Refused::Window(code, message, _) => Refused::Window(code, message, span),
}
}