///|
/// An enum describing the error kind.
pub(all) enum ErrorKind {
/// A non primitive value was encountered where one was expected.
NonPrimitive
/// A value is not valid for a key in a map.
NonKey
/// An invalid operation was attempted.
InvalidOperation
/// The template has a syntax error
SyntaxError
/// A template was not found.
TemplateNotFound
/// Too many arguments were passed to a function.
TooManyArguments
/// A expected argument was missing
MissingArgument
/// A filter is unknown
UnknownFilter
/// A test is unknown
UnknownTest
/// A function is unknown
UnknownFunction
/// Un unknown method was called
UnknownMethod
/// A bad escape sequence in a string was encountered.
BadEscape
/// An operation on an undefined value was attempted.
UndefinedError
/// Not able to serialize this value.
BadSerialization
/// Not able to deserialize this value.
CannotDeserialize
/// An error happened in an include.
BadInclude
/// An error happened in a super block.
EvalBlock
/// Unable to unpack a value.
CannotUnpack
/// Failed writing output.
WriteFailure
/// Engine ran out of fuel
OutOfFuel
/// Invalid custom delimiters
InvalidDelimiter
/// A block is unknown
UnknownBlock
} derive(Eq, Hash, Debug)
///|
/// The name of the kind as Rust's `Debug` would print it.
pub fn ErrorKind::name(self : ErrorKind) -> String {
match self {
NonPrimitive => "NonPrimitive"
NonKey => "NonKey"
InvalidOperation => "InvalidOperation"
SyntaxError => "SyntaxError"
TemplateNotFound => "TemplateNotFound"
TooManyArguments => "TooManyArguments"
MissingArgument => "MissingArgument"
UnknownFilter => "UnknownFilter"
UnknownTest => "UnknownTest"
UnknownFunction => "UnknownFunction"
UnknownMethod => "UnknownMethod"
BadEscape => "BadEscape"
UndefinedError => "UndefinedError"
BadSerialization => "BadSerialization"
CannotDeserialize => "CannotDeserialize"
BadInclude => "BadInclude"
EvalBlock => "EvalBlock"
CannotUnpack => "CannotUnpack"
WriteFailure => "WriteFailure"
OutOfFuel => "OutOfFuel"
InvalidDelimiter => "InvalidDelimiter"
UnknownBlock => "UnknownBlock"
}
}
///|
/// A short human readable description of the kind.
pub fn ErrorKind::description(self : ErrorKind) -> String {
match self {
NonPrimitive => "not a primitive"
NonKey => "not a key type"
InvalidOperation => "invalid operation"
SyntaxError => "syntax error"
TemplateNotFound => "template not found"
TooManyArguments => "too many arguments"
MissingArgument => "missing argument"
UnknownFilter => "unknown filter"
UnknownFunction => "unknown function"
UnknownTest => "unknown test"
UnknownMethod => "unknown method"
BadEscape => "bad string escape"
UndefinedError => "undefined value"
BadSerialization => "could not serialize to value"
BadInclude => "could not render include"
EvalBlock => "could not render block"
CannotUnpack => "cannot unpack"
WriteFailure => "failed to write output"
CannotDeserialize => "cannot deserialize"
OutOfFuel => "engine ran out of fuel"
InvalidDelimiter => "invalid custom delimiters"
UnknownBlock => "unknown block"
}
}
///|
pub impl Show for ErrorKind with fn output(self, logger) {
logger.write_string(self.description())
}
///|
/// Debug information that is attached to errors raised during rendering.
priv struct DebugInfo {
template_source : String?
referenced_locals : Map[String, Value]
}
///|
struct ErrorData {
kind : ErrorKind
mut detail : String?
mut name : String?
mut lineno : Int
mut span : Span?
mut source : TemplateError?
mut debug_info : DebugInfo?
}
///|
/// Represents template errors.
///
/// Errors carry a [`ErrorKind`], an optional detail message, and — once the
/// engine knows about it — the name of the template and the location in it.
pub suberror TemplateError {
TemplateError(ErrorData)
}
///|
fn TemplateError::data(self : TemplateError) -> ErrorData {
match self {
TemplateError(d) => d
}
}
///|
/// Creates a new error with a kind and a detail message.
pub fn TemplateError::new(kind : ErrorKind, detail : String) -> TemplateError {
TemplateError({
kind,
detail: Some(detail),
name: None,
lineno: 0,
span: None,
source: None,
debug_info: None,
})
}
///|
/// Creates a new error with just a kind.
pub fn TemplateError::from_kind(kind : ErrorKind) -> TemplateError {
TemplateError({
kind,
detail: None,
name: None,
lineno: 0,
span: None,
source: None,
debug_info: None,
})
}
///|
fn TemplateError::new_not_found(name : String) -> TemplateError {
TemplateError::new(
TemplateNotFound,
"template \{@rfmt.str_debug(name)} does not exist",
)
}
///|
/// Returns a copy of the error that does not share mutable state.
fn TemplateError::internal_clone(self : TemplateError) -> TemplateError {
let d = self.data()
TemplateError({
kind: d.kind,
detail: d.detail,
name: d.name,
lineno: d.lineno,
span: d.span,
source: d.source,
debug_info: d.debug_info,
})
}
///|
/// Attaches another error as source to this error.
pub fn TemplateError::with_source(
self : TemplateError,
source : TemplateError,
) -> TemplateError {
self.data().source = Some(source)
self
}
///|
/// Returns the error kind.
pub fn TemplateError::kind(self : TemplateError) -> ErrorKind {
self.data().kind
}
///|
/// Returns the error detail.
pub fn TemplateError::detail(self : TemplateError) -> String? {
self.data().detail
}
///|
fn TemplateError::set_detail(self : TemplateError, detail : String) -> Unit {
self.data().detail = Some(detail)
}
///|
/// Returns the filename of the template that caused the error.
pub fn TemplateError::name(self : TemplateError) -> String? {
self.data().name
}
///|
/// Returns the line number where the error occurred.
pub fn TemplateError::line(self : TemplateError) -> Int? {
let lineno = self.data().lineno
if lineno > 0 {
Some(lineno)
} else {
None
}
}
///|
/// Returns the source error if there is one.
pub fn TemplateError::source(self : TemplateError) -> TemplateError? {
self.data().source
}
///|
/// Returns the span of the error in the template source, if known.
pub fn TemplateError::span(self : TemplateError) -> Span? {
self.data().span
}
///|
/// Returns the byte range of where the error occurred if available. In
/// MoonBit the offsets are UTF-16 code unit offsets into the source.
pub fn TemplateError::range(self : TemplateError) -> (Int, Int)? {
match self.data().span {
Some(span) => Some((span.start_offset, span.end_offset))
None => None
}
}
///|
fn TemplateError::set_filename_and_line(
self : TemplateError,
filename : String,
lineno : Int,
) -> Unit {
let d = self.data()
d.name = Some(filename)
d.lineno = lineno
}
///|
fn TemplateError::set_filename_and_span(
self : TemplateError,
filename : String,
span : Span,
) -> Unit {
let d = self.data()
d.name = Some(filename)
d.span = Some(span)
d.lineno = span.start_line
}
///|
fn TemplateError::debug_info(self : TemplateError) -> DebugInfo? {
self.data().debug_info
}
///|
fn TemplateError::attach_debug_info(
self : TemplateError,
info : DebugInfo,
) -> Unit {
self.data().debug_info = Some(info)
}
///|
/// Returns the template source if available.
pub fn TemplateError::template_source(self : TemplateError) -> String? {
match self.data().debug_info {
Some(info) => info.template_source
None => None
}
}
///|
/// Formats the error like Rust's `Display` (`{}`).
pub fn TemplateError::to_string(self : TemplateError) -> String {
let sb = StringBuilder()
self.write_display(sb, false)
sb.to_string()
}
///|
/// Formats the error like Rust's alternate `Display` (`{:#}`) which includes
/// the debug info (template source excerpt and referenced variables).
pub fn TemplateError::display_with_debug_info(self : TemplateError) -> String {
let sb = StringBuilder()
self.write_display(sb, true)
sb.to_string()
}
///|
fn TemplateError::write_display(
self : TemplateError,
sb : StringBuilder,
alternate : Bool,
) -> Unit {
let d = self.data()
match d.detail {
Some(detail) => {
sb.write_string(d.kind.description())
sb.write_string(": ")
sb.write_string(detail)
}
None => sb.write_string(d.kind.description())
}
if d.name is Some(name) {
sb.write_string(" (in \{name}:\{self.line().unwrap_or(0)})")
}
if alternate && d.debug_info is Some(info) {
sb.write_string(self.render_debug_info(info))
}
}
///|
/// Renders only the debug info section of the error.
pub fn TemplateError::display_debug_info(self : TemplateError) -> String {
match self.data().debug_info {
Some(info) => self.render_debug_info(info)
None => ""
}
}
///|
pub impl Show for TemplateError with fn output(self, logger) {
logger.write_string(self.to_string())
}
///|
/// Formats the error like Rust's `Debug` (`{:?}` or `{:#?}` when `pretty`
/// is set). This does not include the debug info.
pub fn TemplateError::debug_string(
self : TemplateError,
pretty? : Bool = false,
) -> String {
let f = @rfmt.Formatter::new(alternate=pretty)
self.fmt_debug(f)
f.to_string()
}
///|
fn TemplateError::fmt_debug(self : TemplateError, f : @rfmt.Formatter) -> Unit {
let d = self.data()
let s = f.debug_struct("Error")
s.field("kind", f => f.write_str(d.kind.name())) |> ignore
if d.detail is Some(detail) {
s.field("detail", f => f.write_str(@rfmt.str_debug(detail))) |> ignore
}
if d.name is Some(name) {
s.field("name", f => f.write_str(@rfmt.str_debug(name))) |> ignore
}
if self.line() is Some(line) {
s.field("line", f => f.write_str(line.to_string())) |> ignore
}
if d.source is Some(source) {
s.field("source", f => source.fmt_debug(f)) |> ignore
}
s.finish()
}
///|
fn TemplateError::render_debug_info(
self : TemplateError,
info : DebugInfo,
) -> String {
let d = self.data()
let sb = StringBuilder()
if info.template_source is Some(source) {
let name = d.name.unwrap_or("")
let base = match name.rev_find("/") {
Some(idx) => name.view(start_offset=idx + 1).to_owned()
None => name
}
let base = match base.rev_find("\\") {
Some(idx) => base.view(start_offset=idx + 1).to_owned()
None => base
}
let title = " \{base} "
sb.write_char('\n')
sb.write_string(center_pad(title, 79, '-'))
sb.write_char('\n')
let lines = rust_lines(source)
let idx = {
let l = self.line().unwrap_or(1) - 1
if l < 0 {
0
} else {
l
}
}
let skip = if idx - 3 < 0 { 0 } else { idx - 3 }
let pre_count = if idx < 3 { idx } else { 3 }
for i in skip..<(skip + pre_count) {
if i < lines.length() {
sb.write_string(
"\{right_align((i + 1).to_string(), 4)} | \{lines[i]}\n",
)
}
}
if idx < lines.length() {
sb.write_string(
"\{right_align((idx + 1).to_string(), 4)} > \{lines[idx]}\n",
)
}
if d.span is Some(span) && span.start_line == span.end_line {
sb.write_string(" i ")
for _ in 0.. compare_str(a, b))
let s = f.debug_struct("Referenced variables:")
for key in keys {
s.field(key, f => vars[key].fmt_debug(f)) |> ignore
}
s.finish()
}
sb.write_string(f.to_string())
sb.write_char('\n')
sb.write_string(center_pad("", 79, '-'))
sb.to_string()
}
///|
/// Port of Rust's `{:^width$}` with a fill character.
fn center_pad(s : String, width : Int, fill : Char) -> String {
let len = s.char_length()
if len >= width {
return s
}
let total = width - len
let left = total / 2
let right = total - left
let sb = StringBuilder()
for _ in 0..width$}`.
fn right_align(s : String, width : Int) -> String {
let len = s.char_length()
if len >= width {
return s
}
let sb = StringBuilder()
for _ in 0..<(width - len) {
sb.write_char(' ')
}
sb.write_string(s)
sb.to_string()
}
///|
/// Port of Rust's `str::lines` (splits on `\n`, strips a trailing `\r`, and
/// does not yield a final empty line).
fn rust_lines(s : String) -> Array[String] {
let rv = []
let mut start = 0
let len = s.length()
while start < len {
let mut end = start
while end < len && s[end] != '\n' {
end += 1
}
let mut line_end = end
if line_end > start && s[line_end - 1] == '\r' {
line_end -= 1
}
rv.push(s.view(start_offset=start, end_offset=line_end).to_owned())
start = end + 1
}
rv
}
///|
pub impl @debug.Debug for TemplateError with fn to_repr(self) {
@debug.Repr::Repr(self.debug_string())
}