///|
pub(all) struct SourceExcerpt {
line_number : Int
column : Int
source_line : String
marker : String
} derive(Eq, Debug)
///|
fn line_bounds(source : String, requested_line : Int) -> (Int, Int) {
let target = requested_line.max(1)
let mut line = 1
let mut start = 0
let mut index = 0
while index < source.length() && line < target {
if source[index] == '\n' {
line = line + 1
start = index + 1
}
index = index + 1
}
let mut end = start
while end < source.length() && source[end] != '\n' && source[end] != '\r' {
end = end + 1
}
(start, end)
}
///|
fn marker_prefix(source_line : String, width : Int) -> String {
let builder = StringBuilder()
for index = 0
index < width && index < source_line.length()
index = index + 1 {
if source_line[index] == '\t' {
builder.write_char('\t')
} else {
builder.write_char(' ')
}
}
builder.to_string()
}
///|
fn repeat_marker(count : Int) -> String {
let builder = StringBuilder()
for index = 0; index < count.max(1); index = index + 1 {
builder.write_char('^')
}
builder.to_string()
}
///|
/// Extract a single-line source frame for a diagnostic span.
pub fn source_excerpt(source : String, span : SourceSpan) -> SourceExcerpt {
let (start, end) = line_bounds(source, span.line)
let source_line = source[start:end].to_owned()
let column = span.column.max(1)
let offset = (column - 1).min(source_line.length())
let available = (source_line.length() - offset).max(1)
let marker_width = span.length().max(1).min(available)
let marker = marker_prefix(source_line, offset) + repeat_marker(marker_width)
{ line_number: span.line.max(1), column, source_line, marker }
}
///|
pub fn SourceExcerpt::render(self : SourceExcerpt) -> String {
" \{self.line_number} | \{self.source_line}\n \{blank_gutter(self.line_number)} | \{self.marker}"
}
///|
fn blank_gutter(line_number : Int) -> String {
let builder = StringBuilder()
for index = 0; index < line_number.to_string().length(); index = index + 1 {
builder.write_char(' ')
}
builder.to_string()
}
///|
pub fn Diagnostic::render_with_source(
self : Diagnostic,
source : String,
) -> String {
let excerpt = source_excerpt(source, self.span)
"\{self.severity.label()}[\{self.code}]: \{self.message}\n \{excerpt.line_number} | \{excerpt.source_line}\n \{blank_gutter(excerpt.line_number)} | \{excerpt.marker}"
}
///|
pub fn EvalError::span(self : EvalError) -> SourceSpan {
match self {
UnboundVariable(span~, ..)
| TypeMismatch(span~, ..)
| NoSuchField(span~, ..)
| NoSuchKey(span~, ..)
| IndexOutOfBounds(span~, ..)
| DivisionByZero(span~)
| NumericOverflow(span~)
| UnknownFunction(span~, ..)
| InvalidArgument(span~, ..)
| CostLimitExceeded(span~, ..)
| DepthLimitExceeded(span~, ..)
| IterationLimitExceeded(span~, ..) => span
}
}
///|
pub fn EvalError::code(self : EvalError) -> String {
match self {
UnboundVariable(..) => "CEL-EVAL-NAME"
TypeMismatch(..) => "CEL-EVAL-TYPE"
NoSuchField(..) => "CEL-EVAL-FIELD"
NoSuchKey(..) => "CEL-EVAL-KEY"
IndexOutOfBounds(..) => "CEL-EVAL-INDEX"
DivisionByZero(..) => "CEL-EVAL-DIV-ZERO"
NumericOverflow(..) => "CEL-EVAL-OVERFLOW"
UnknownFunction(..) => "CEL-EVAL-FUNCTION"
InvalidArgument(..) => "CEL-EVAL-ARGUMENT"
CostLimitExceeded(..) => "CEL-EVAL-COST"
DepthLimitExceeded(..) => "CEL-EVAL-DEPTH"
IterationLimitExceeded(..) => "CEL-EVAL-ITERATION"
}
}
///|
pub fn EvalError::message(self : EvalError) -> String {
match self {
UnboundVariable(name~, ..) => "unbound variable '\{name}'"
TypeMismatch(expected~, actual~, ..) =>
"expected \{expected}, found \{actual}"
NoSuchField(field~, ..) => "no such field '\{field}'"
NoSuchKey(key~, ..) => "no such key \{key}"
IndexOutOfBounds(index~, length~, ..) =>
"index \{index} is outside collection length \{length}"
DivisionByZero(..) => "division by zero"
NumericOverflow(..) => "numeric overflow"
UnknownFunction(name~, ..) => "unknown function '\{name}'"
InvalidArgument(message~, ..) => message
CostLimitExceeded(limit~, ..) => "evaluation cost exceeded \{limit}"
DepthLimitExceeded(limit~, ..) => "evaluation depth exceeded \{limit}"
IterationLimitExceeded(limit~, ..) =>
"collection iterations exceeded \{limit}"
}
}
///|
pub fn EvalError::to_diagnostic(self : EvalError) -> Diagnostic {
Diagnostic::error(self.code(), self.message(), self.span())
}
///|
pub fn EvalError::render_with_source(
self : EvalError,
source : String,
) -> String {
self.to_diagnostic().render_with_source(source)
}