use super::scanner::Scanner;
pub struct StringEngine;
impl StringEngine {
#[inline(always)]
pub fn is_string_start(ch: char) -> bool {
ch == '"' || ch == '\''
}
#[inline(always)]
pub fn is_long_bracket_start(scanner: &Scanner<'_>) -> bool {
scanner.peek() == '[' && scanner.peek_next().is_some_and(|c| c == '[' || c == '=')
}
#[inline(always)]
pub fn is_interp_start(ch: char) -> bool {
ch == '`'
}
pub fn scan_quoted_string(scanner: &mut Scanner<'_>, quote: char) -> Result<String, String> {
let start_line = scanner.line();
scanner.advance(); let mut out = String::new();
while !scanner.is_at_end() {
let ch = scanner.advance();
if ch == quote {
return Ok(out);
}
if ch == '\\' {
Self::scan_escape(scanner, &mut out)?;
} else {
out.push(ch);
}
}
Err(format!("[line {}] unfinished string literal", start_line))
}
fn scan_escape(scanner: &mut Scanner<'_>, out: &mut String) -> Result<(), String> {
if scanner.is_at_end() {
return Err(format!(
"[line {}] unfinished string escape at EOF",
scanner.line()
));
}
let esc = scanner.advance();
match esc {
'a' => out.push('\x07'),
'b' => out.push('\x08'),
'f' => out.push('\x0C'),
'n' => out.push('\n'),
'r' => out.push('\r'),
't' => out.push('\t'),
'v' => out.push('\x0B'),
'0'..='9' => {
let mut value = esc.to_digit(10).unwrap_or(0);
for _ in 0..2 {
if scanner.is_at_end() {
break;
}
let Some(digit) = scanner.peek().to_digit(10) else {
break;
};
value = value * 10 + digit;
scanner.advance();
}
if value > 255 {
return Err(format!(
"[line {}] invalid escape \\{}: decimal escapes go up to 255",
scanner.line(),
value
));
}
out.push(char::from(value as u8));
}
'x' => {
let hi = scanner.advance();
let lo = scanner.advance();
let hex = format!("{}{}", hi, lo);
let value = u8::from_str_radix(&hex, 16).map_err(|_| {
format!(
"[line {}] invalid escape \\x{}: expected two hex digits",
scanner.line(),
hex
)
})?;
out.push(value as char);
}
'u' if scanner.peek() == '{' => {
scanner.advance();
let mut digits = String::new();
while !scanner.is_at_end() && scanner.peek() != '}' {
digits.push(scanner.advance());
}
if scanner.peek() != '}' {
return Err(format!(
"[line {}] unfinished escape \\u{{{}",
scanner.line(),
digits
));
}
scanner.advance();
let c = u32::from_str_radix(&digits, 16)
.ok()
.and_then(char::from_u32)
.ok_or_else(|| {
format!(
"[line {}] invalid escape \\u{{{}}}: not a Unicode scalar value",
scanner.line(),
digits
)
})?;
out.push(c);
}
'z' => {
while !scanner.is_at_end() && matches!(scanner.peek(), ' ' | '\t' | '\r' | '\n') {
scanner.advance();
}
}
'\r' => {
if scanner.peek() == '\n' {
scanner.advance();
}
out.push('\n');
}
_ => out.push(esc),
}
Ok(())
}
pub fn scan_long_bracket(scanner: &mut Scanner<'_>) -> Result<String, String> {
let start_pos = scanner.current_pos();
let start_line = scanner.line();
if scanner.peek() != '[' {
return Err(format!("[line {}] long bracket expected", start_line));
}
scanner.advance();
let mut level = 0;
while !scanner.is_at_end() && scanner.peek() == '=' {
level += 1;
scanner.advance();
}
if scanner.peek() != '[' {
scanner.restore(start_pos);
return Err(format!("[line {}] long bracket expected", start_line));
}
scanner.advance();
if scanner.peek() == '\r' {
scanner.advance();
}
if scanner.peek() == '\n' {
scanner.advance();
}
let closer = format!("]{}]", "=".repeat(level));
let content_start = scanner.pos();
let mut found = None;
while !scanner.is_at_end() {
if scanner.starts_with(&closer) {
found = Some(scanner.pos());
break;
}
scanner.advance();
}
let Some(index) = found else {
scanner.restore(start_pos);
return Err(format!("[line {}] unfinished long bracket", start_line));
};
let content = scanner.slice_from(content_start)[..index - content_start].to_owned();
scanner.advance_bytes(closer.len());
Ok(content)
}
pub fn scan_interpolated_string(scanner: &mut Scanner<'_>) -> Result<String, String> {
let start_line = scanner.line();
scanner.advance(); let mut encoded = String::new();
let mut literal = String::new();
while !scanner.is_at_end() {
let ch = scanner.advance();
match ch {
'`' => {
push_part(&mut encoded, 'L', &literal);
return Ok(encoded);
}
'\\' => Self::scan_escape(scanner, &mut literal)?,
'{' => {
push_part(&mut encoded, 'L', &literal);
literal.clear();
let expression = Self::scan_interp_expression(scanner, start_line)?;
push_part(&mut encoded, 'E', &expression);
}
_ => literal.push(ch),
}
}
Err(format!(
"[line {}] unfinished interpolated string",
start_line
))
}
fn scan_interp_expression(
scanner: &mut Scanner<'_>,
start_line: usize,
) -> Result<String, String> {
let mut depth = 1;
let mut source = String::new();
while !scanner.is_at_end() {
let c = scanner.advance();
match c {
'{' => depth += 1,
'}' => {
depth -= 1;
if depth == 0 {
return Ok(source);
}
}
'"' | '\'' => {
source.push(c);
while !scanner.is_at_end() {
let inner = scanner.advance();
source.push(inner);
if inner == '\\' && !scanner.is_at_end() {
source.push(scanner.advance());
} else if inner == c {
break;
}
}
continue;
}
_ => {}
}
source.push(c);
}
Err(format!(
"[line {}] unfinished expression in interpolated string",
start_line
))
}
}
fn push_part(encoded: &mut String, tag: char, text: &str) {
if tag == 'L' && text.is_empty() {
return;
}
encoded.push(tag);
encoded.push_str(&text.len().to_string());
encoded.push(':');
encoded.push_str(text);
}