git.delta.rocks / jrsonnet / refs/heads / master

difftreelog

source

crates/jrsonnet-lexer/src/string.rs1.7 KiBsourcehistory
1//! Jsonnet-specific non-verbatim string unescaping23use std::str::Chars;45fn decode_unicode(chars: &mut Chars) -> Option<u16> {6	#[expect(7		clippy::cast_possible_truncation,8		reason = "truncated value is a single digit, thus it would never truncate anything"9	)]10	IntoIterator::into_iter([chars.next()?, chars.next()?, chars.next()?, chars.next()?])11		.map(|c| c.to_digit(16).map(|f| f as u16))12		.try_fold(0u16, |acc, v| Some((acc << 4) | (v?)))13}1415/// Unescape escape characters in jsonnet string16pub fn unescape(s: &str) -> Option<String> {17	let mut chars = s.chars();18	let mut out = String::with_capacity(s.len());1920	while let Some(c) = chars.next() {21		if c != '\\' {22			out.push(c);23			continue;24		}25		match chars.next()? {26			c @ ('\\' | '"' | '\'') => out.push(c),27			'b' => out.push('\u{0008}'),28			'f' => out.push('\u{000c}'),29			'n' => out.push('\n'),30			'r' => out.push('\r'),31			't' => out.push('\t'),32			'u' => match decode_unicode(&mut chars)? {33				// May only be second byte34				0xDC00..=0xDFFF => return None,35				// Surrogate pair36				n1 @ 0xD800..=0xDBFF => {37					if chars.next() != Some('\\') {38						return None;39					}40					if chars.next() != Some('u') {41						return None;42					}43					let n2 = decode_unicode(&mut chars)?;44					if !matches!(n2, 0xDC00..=0xDFFF) {45						return None;46					}47					let n = (u32::from(n1 - 0xD800) << 10 | u32::from(n2 - 0xDC00)) + 0x1_0000;48					out.push(char::from_u32(n)?);49				}50				n => out.push(char::from_u32(u32::from(n))?),51			},52			'x' => {53				let c = IntoIterator::into_iter([chars.next()?, chars.next()?])54					.map(|c| c.to_digit(16))55					.try_fold(0u32, |acc, v| Some((acc << 8) | (v?)))?;56				out.push(char::from_u32(c)?);57			}58			_ => return None,59		}60	}61	Some(out)62}