///|
pub(all) suberror JinjaError {
LexerError(String)
ParseError(String)
RenderError(String)
}
///|
/// UTF-8 byte and human-readable source range.
pub(all) struct SourceSpan {
start : Int
end : Int
line : Int
column : Int
end_line : Int
end_column : Int
} derive(Eq, Debug)
///|
/// Structured view over a lexer, parser, or render failure.
pub struct ErrorDiagnostic {
priv stage : ErrorStage
priv message : String
priv template_name : String?
priv span : SourceSpan?
priv template_trace : Array[String]
}
///|
pub fn ErrorDiagnostic::stage(self : ErrorDiagnostic) -> ErrorStage {
self.stage
}
///|
pub fn ErrorDiagnostic::message(self : ErrorDiagnostic) -> String {
self.message
}
///|
pub fn ErrorDiagnostic::template_name(self : ErrorDiagnostic) -> String? {
self.template_name
}
///|
pub fn ErrorDiagnostic::span(self : ErrorDiagnostic) -> SourceSpan? {
self.span
}
///|
pub fn ErrorDiagnostic::template_trace(self : ErrorDiagnostic) -> Array[String] {
self.template_trace.copy()
}
///|
pub(all) enum ErrorStage {
Lexer
Parser
Render
} derive(Eq, Debug)
///|
pub fn JinjaError::stage(self : JinjaError) -> ErrorStage {
match self {
LexerError(_) => Lexer
ParseError(_) => Parser
RenderError(_) => Render
}
}
///|
pub fn JinjaError::message(self : JinjaError) -> String {
match self {
LexerError(message) | ParseError(message) | RenderError(message) => message
}
}
///|
pub fn JinjaError::diagnostic(self : JinjaError) -> ErrorDiagnostic {
let message = self.message()
let template_trace = diagnostic_template_trace(message)
let template_name = template_trace.last()
{
stage: self.stage(),
message,
template_name,
span: diagnostic_span(message),
template_trace,
}
}
///|
fn diagnostic_template_trace(message : String) -> Array[String] {
let marker = "Template '"
let trace : Array[String] = []
let mut remaining = message
while remaining.find(marker) is Some(offset) {
let start = offset + marker.length()
let tail = remaining[start:].to_owned()
guard tail.find("'") is Some(length) else { break }
trace.push(tail[:length].to_owned())
remaining = tail[length + 1:].to_owned()
}
trace
}
///|
fn parse_positive_int(text : String) -> Int? {
if text == "" {
return None
}
let mut value = 0
for char in text {
if char < '0' || char > '9' {
return None
}
value = value * 10 + char.to_int() - '0'.to_int()
}
Some(value)
}
///|
fn diagnostic_span(message : String) -> SourceSpan? {
guard message.find(" (bytes ") is Some(bytes_marker) else { return None }
let bytes_tail = message[bytes_marker + 8:].to_owned()
guard bytes_tail.find("..") is Some(separator) else { return None }
guard bytes_tail.find(")") is Some(close) else { return None }
guard parse_positive_int(bytes_tail[:separator].to_owned()) is Some(start) else {
return None
}
guard parse_positive_int(bytes_tail[separator + 2:close].to_owned())
is Some(end) else {
return None
}
let before_bytes = message[:bytes_marker].to_owned()
guard before_bytes.rev_find(" at ") is Some(at_marker) else { return None }
let position = before_bytes[at_marker + 4:].to_owned()
guard position.find(":") is Some(colon) else { return None }
guard parse_positive_int(position[:colon].to_owned()) is Some(line) else {
return None
}
guard parse_positive_int(position[colon + 1:].to_owned()) is Some(column) else {
return None
}
Some({ start, end, line, column, end_line: line, end_column: column })
}