///|
/// Whether an element must be quoted so that the joined line can be split
/// back into its elements (#11): it is empty, or contains whitespace, a double
/// quote, or the (non-empty) separator.
fn needs_quotes(part : String, join : String) -> Bool {
if part.is_empty() {
return true
}
if join != "" && part.contains(join) {
return true
}
for c in part {
if c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '"' {
return true
}
}
false
}
///|
/// Renders each element with `Show`, wrapping it in double quotes when
/// `needs_quotes` says so; inside quotes, `\` and `"` are escaped with a
/// backslash. Line breaks stay literal, so multi-line elements still render on
/// several lines (#9).
fn[T : Show] render_parts(a : Array[T], join : String) -> Array[String] {
a.map(x => {
let part = x.to_string()
if !needs_quotes(part, join) {
return part
}
let sb = StringBuilder()
sb.write_char('"')
for c in part {
if c == '\\' || c == '"' {
sb.write_char('\\')
}
sb.write_char(c)
}
sb.write_char('"')
sb.to_string()
})
}
///|
/// Formats an error type and error message into a standardized error string.
///
/// Parameters:
///
/// * `err_type` : An optional error type identifier that will be included in
/// parentheses if present.
/// * `err_msg` : The main error message to be displayed.
///
/// Returns a formatted error string in the format "Error(type): message" if
/// error type is provided, or "Error: message" if no error type is specified.
fn gen_err_msg(err_type : String?, err_msg : String) -> String {
let et = match err_type {
Some(s) => "(\{s})"
None => ""
}
"Error\{et}: \{err_msg}"
}
///|
/// The number of scalars of `text` that start before UTF-16 offset `offset`:
/// a scalar that the half-open range `[_, offset)` touches counts.
fn scalars_starting_before(text : String, offset : Int32) -> Int32 {
let mut units = 0
let mut scalars = 0
for c in text {
if units >= offset {
break
}
units += if c.to_int() > 0xFFFF { 2 } else { 1 }
scalars += 1
}
scalars
}
///|
/// Renders the sequence, a marker line under the span and the message, in
/// the compact layout of `Report::render`.
///
/// Kept for one release (0.2.x) for compatibility; use `Report::render`. The
/// 0.1 units are kept: the span is `err_pos` to `err_pos + err_offset`, both
/// included; for a string sequence the positions are UTF-16 offsets, and every
/// character the span touches is marked; a negative `err_pos` means no
/// position; a negative `err_offset` counts as 0. The last line is
/// `gen_msg(err_type, err_msg)`, and `marker` is the marker character.
#deprecated("Use `Report::render` instead (see the section Migrating from 0.1 in the manual)", skip_current_package=true)
pub fn[T : Show] ErrorData::mark(
self : ErrorData,
seq : Sequence[T],
marker~ : Char = '^',
gen_msg~ : (String?, String) -> String = gen_err_msg,
) -> String {
let msg = gen_msg(self.err_type, self.err_msg)
if self.err_pos < 0 {
return seq.rendered() + "\n\n" + msg
}
let pos = self.err_pos
let offset = if self.err_offset < 0 { 0 } else { self.err_offset }
// Exclusive end of the inclusive 0.1 span, without overflowing.
let last = if offset >= 2147483646 - pos {
2147483647
} else {
pos + offset + 1
}
let span = match seq {
Arr(_, _) => Span::new(pos, last)
Str(s) =>
Span::new(utf16_to_scalar(s, pos), scalars_starting_before(s, last))
}
Report::new(Error, self.err_msg)
.with_mark(Mark::primary(span))
.render_impl(seq, @cursor.Model::new(), false, marker, Some(msg))
}