///|
/// Returns `true` if `s` contains `pat` at UTF-16 offset `off`.
fn starts_with_at(s : String, off : Int, pat : String) -> Bool {
let plen = pat.length()
if off < 0 || off + plen > s.length() {
return false
}
for i in 0.. Int? {
let nlen = haystack.length()
let plen = needle.length()
if plen == 0 {
return Some(0)
}
let mut i = off
while i + plen <= nlen {
if starts_with_at(haystack, i, needle) {
return Some(i - off)
}
i += 1
}
None
}
///|
/// Returns the code unit at `idx` or `-1` when out of bounds.
fn unit_at(s : String, idx : Int) -> Int {
if idx >= 0 && idx < s.length() {
s[idx].to_int()
} else {
-1
}
}
///|
/// Decodes the character starting at UTF-16 offset `idx`.
fn char_at(s : String, idx : Int) -> Char? {
if idx < 0 || idx >= s.length() {
return None
}
let c1 = s[idx].to_int()
if c1 >= 0xD800 && c1 <= 0xDBFF && idx + 1 < s.length() {
let c2 = s[idx + 1].to_int()
if c2 >= 0xDC00 && c2 <= 0xDFFF {
let cp = 0x10000 + ((c1 - 0xD800) << 10) + (c2 - 0xDC00)
return Some(cp.unsafe_to_char())
}
}
Some(c1.unsafe_to_char())
}
///|
/// Port of Rust's `char::is_whitespace` (Unicode `White_Space`).
fn is_rust_whitespace(c : Char) -> Bool {
match c.to_int() {
0x09..=0x0D | 0x20 | 0x85 | 0xA0 | 0x1680 => true
0x2000..=0x200A | 0x2028 | 0x2029 | 0x202F | 0x205F | 0x3000 => true
_ => false
}
}
///|
/// Port of Rust's `u8::is_ascii_whitespace` applied to a code unit.
fn is_ascii_ws_unit(u : Int) -> Bool {
u == 0x20 || u == 0x09 || u == 0x0A || u == 0x0C || u == 0x0D
}
///|
/// Port of Rust's `str::trim_end`.
fn trim_end_view(s : StringView) -> StringView {
let mut end = s.length()
while end > 0 {
let u = s[end - 1].to_int()
if u >= 0xDC00 && u <= 0xDFFF {
// surrogate pairs are never whitespace
break
}
if is_rust_whitespace(u.unsafe_to_char()) {
end -= 1
} else {
break
}
}
s.view(end_offset=end)
}
///|
/// Port of Rust's `str::trim_start`.
fn trim_start_view(s : StringView) -> StringView {
let mut start = 0
let len = s.length()
while start < len {
let u = s[start].to_int()
if u >= 0xD800 && u <= 0xDFFF {
break
}
if is_rust_whitespace(u.unsafe_to_char()) {
start += 1
} else {
break
}
}
s.view(start_offset=start)
}
///|
/// Port of Rust's `str::trim`.
fn trim_view(s : StringView) -> StringView {
trim_start_view(trim_end_view(s))
}
///|
/// Number of Unicode scalars in a string.
fn scalar_count(s : StringView) -> Int {
s.char_length()
}
///|
priv struct Unescaper {
out : StringBuilder
mut pending_surrogate : Int
}
///|
fn Unescaper::push_char(self : Unescaper, c : Char) -> Unit raise TemplateError {
if self.pending_surrogate != 0 {
raise TemplateError::from_kind(BadEscape)
}
self.out.write_char(c)
}
///|
fn Unescaper::push_u16(self : Unescaper, c : Int) -> Unit raise TemplateError {
let is_surrogate = c >= 0xD800 && c <= 0xDFFF
if self.pending_surrogate == 0 && !is_surrogate {
self.out.write_char(c.unsafe_to_char())
} else if !is_surrogate {
raise TemplateError::from_kind(BadEscape)
} else if self.pending_surrogate == 0 {
self.pending_surrogate = c
} else {
let prev = self.pending_surrogate
if prev >= 0xD800 && prev <= 0xDBFF && c >= 0xDC00 && c <= 0xDFFF {
let cp = 0x10000 + ((prev - 0xD800) << 10) + (c - 0xDC00)
self.out.write_char(cp.unsafe_to_char())
self.pending_surrogate = 0
} else {
raise TemplateError::from_kind(BadEscape)
}
}
}
///|
fn hex_value(c : Char) -> Int {
match c {
'0'..='9' => c.to_int() - '0'.to_int()
'a'..='f' => c.to_int() - 'a'.to_int() + 10
'A'..='F' => c.to_int() - 'A'.to_int() + 10
_ => -1
}
}
///|
/// Unescapes a string literal from a template.
fn unescape(s : StringView) -> String raise TemplateError {
let u = { out: StringBuilder(), pending_surrogate: 0, }
let chars = s.iter().to_array()
let n = chars.length()
let mut i = 0
while i < n {
let c = chars[i]
i += 1
if c != '\\' {
u.push_char(c)
continue
}
if i >= n {
raise TemplateError::from_kind(BadEscape)
}
let d = chars[i]
i += 1
match d {
'"' | '\\' | '/' | '\'' => u.push_char(d)
'b' => u.push_char('\u{08}')
'f' => u.push_char('\u{0C}')
'n' => u.push_char('\n')
'r' => u.push_char('\r')
't' => u.push_char('\t')
'u' => {
// exactly four hex digits (missing ones are treated as invalid)
let mut val = 0
for _ in 0..<4 {
let h = if i < n { hex_value(chars[i]) } else { -1 }
if h < 0 {
raise TemplateError::from_kind(BadEscape)
}
val = val * 16 + h
i += 1
}
u.push_u16(val)
}
'x' => {
if i + 2 > n {
raise TemplateError::from_kind(BadEscape)
}
let h1 = hex_value(chars[i])
let h2 = hex_value(chars[i + 1])
i += 2
if h1 < 0 || h2 < 0 {
raise TemplateError::from_kind(BadEscape)
}
u.push_char((h1 * 16 + h2).unsafe_to_char())
}
'0'..='7' => {
let mut val = d.to_int() - '0'.to_int()
let mut count = 0
while count < 2 && i < n && chars[i] >= '0' && chars[i] <= '7' {
val = val * 8 + (chars[i].to_int() - '0'.to_int())
i += 1
count += 1
}
if val > 255 {
raise TemplateError::from_kind(BadEscape)
}
u.push_char(val.unsafe_to_char())
}
_ => {
u.push_char('\\')
u.push_char(d)
}
}
}
if u.pending_surrogate != 0 {
raise TemplateError::from_kind(BadEscape)
}
u.out.to_string()
}