///|
/// Render a parser error with its source line and a caret.
pub fn render_error(source : StringView, error : RonError) -> String {
let message = error_message(error)
match error_position(error) {
Some(position) => {
let line = source_line(source, position.line)
let column = visual_column(line, position.column)
let caret = String::make(column, ' ')
"error: " +
message +
" at line \{position.line}, column \{position.column + 1}\n |\n" +
position.line.to_string() +
" | " +
line +
"\n | " +
caret +
"^"
}
None => "error: " + message
}
}
///|
/// Render a typed decoding error. Syntax errors include a source caret.
pub fn render_decode_error(
source : StringView,
error : RonDecodeError,
) -> String {
match error {
Syntax(error) => render_error(source, error)
TypeMismatch(path, expected, actual) =>
"error: expected " +
expected +
", found " +
actual +
"\n at " +
path.to_string()
MissingField(path, name) =>
"error: missing field " + name + "\n at " + path.to_string()
UnexpectedTupleLength(path, expected, actual) =>
"error: expected tuple length " +
expected.to_string() +
", found " +
actual.to_string() +
"\n at " +
path.to_string()
UnknownVariant(path, variant) =>
"error: unknown variant " + variant + "\n at " + path.to_string()
InvalidValue(path, message) =>
"error: " + message + "\n at " + path.to_string()
}
}
///|
fn error_position(error : RonError) -> RonPosition? {
match error {
UnexpectedChar(position, _) => Some(position)
UnexpectedEof(position) => Some(position)
InvalidEscape(position) => Some(position)
InvalidNumber(position, _) => Some(position)
InvalidValue(position, _) => Some(position)
UnknownExtension(position, _) => Some(position)
UnknownAttribute(position, _) => Some(position)
InvalidAttribute(position, _) => Some(position)
DepthLimitExceeded(_) => None
}
}
///|
fn error_message(error : RonError) -> String {
match error {
UnexpectedChar(_, char) => "unexpected character " + display_char(char)
UnexpectedEof(_) => "unexpected end of input"
InvalidEscape(_) => "invalid escape sequence"
InvalidNumber(_, token) => "invalid number " + token
InvalidValue(_, message) => message
UnknownExtension(_, name) => "unknown extension " + name
UnknownAttribute(_, name) => "unknown attribute " + name
InvalidAttribute(_, name) => "invalid attribute " + name
DepthLimitExceeded(limit) => "maximum nesting depth \{limit} exceeded"
}
}
///|
fn display_char(char : Char) -> String {
match char {
'\n' => "\\n"
'\r' => "\\r"
'\t' => "\\t"
char => "'" + char.to_string() + "'"
}
}
///|
fn source_line(source : StringView, line : Int32) -> String {
let lines = Array::from_iter(source.split("\n"))
match lines.get(line - 1) {
Some(value) => value.to_owned()
None => ""
}
}
///|
fn visual_column(line : String, column : Int32) -> Int32 {
let chars = line.to_array()
let end = if column < chars.length() { column } else { chars.length() }
let mut width = 0
for i = 0; i < end; i = i + 1 {
width = width + (if chars[i] == '\t' { 4 } else { 1 })
}
width
}