// Value tokenizer (src/value.js VAL_TOKEN_RE / unescapeStr). Implements the
// declares in spec.mbt.
///|
fn is_val_space(c : Char) -> Bool {
// JS \s (the subset that can occur in attribute values)
c is (' ' | '\t' | '\n' | '\r' | '\u{b}' | '\u{c}' | '\u{a0}')
}
///|
/// Scan a quoted run starting at the opening quote index (the `'`). Returns
/// the index just past the closing quote, or None when unterminated.
/// Inside the run `\` escapes the next character (JS `\\.`).
fn scan_quoted(chars : Array[Char], open : Int) -> Int? {
let mut i = open + 1
while i < chars.length() {
match chars[i] {
'\\' => i = i + 2
'\'' => return Some(i + 1)
_ => i = i + 1
}
}
None
}
///|
/// The single value tokenizer: a `$'…'` template run, a plain `'…'` literal
/// (escapes kept in the token), or a maximal non-whitespace run. Quoted
/// alternatives win only at their start; an unterminated quote falls back to
/// the plain run (mirrors the JS regex alternation).
pub fn tokenize_value(s : String) -> Array[String] {
let chars : Array[Char] = s.iter().collect()
let out : Array[String] = []
let mut i = 0
while i < chars.length() {
if is_val_space(chars[i]) {
i = i + 1
continue
}
let start = i
let quote_end : Int? = if chars[i] == '\'' {
scan_quoted(chars, i)
} else if chars[i] == '$' && i + 1 < chars.length() && chars[i + 1] == '\'' {
scan_quoted(chars, i + 1)
} else {
None
}
let end = match quote_end {
Some(e) => e
None => {
// Plain run: everything up to the next whitespace.
let mut j = start
while j < chars.length() && !is_val_space(chars[j]) {
j = j + 1
}
j
}
}
let buf = StringBuilder::new()
for k in start.. String {
let chars : Array[Char] = s.iter().collect()
let buf = StringBuilder::new()
let mut i = 0
while i < chars.length() {
if chars[i] == '\\' &&
i + 1 < chars.length() &&
chars[i + 1] is ('\'' | '\\') {
buf.write_char(chars[i + 1])
i = i + 2
} else {
buf.write_char(chars[i])
i = i + 1
}
}
buf.to_string()
}
///|
/// Escape `'` and `\` into `buf` (shared by ConstVal quoting and StrTpl
/// re-escaping in value_show).
fn escape_str_into(buf : StringBuilder, s : String) -> Unit {
for c in s {
if c is ('\'' | '\\') {
buf.write_char('\\')
}
buf.write_char(c)
}
}
///|
/// JS ConstVal.toString quoting: `'…'` with `'` and `\` escaped, one pass.
fn escape_str_literal(s : String) -> String {
let buf = StringBuilder::new()
buf.write_char('\'')
escape_str_into(buf, s)
buf.write_char('\'')
buf.to_string()
}