use std::borrow::Cow;
use crate::keywords::parse_keyword;
fn is_lower_alphanumeric_or_underscore(c: char) -> bool {
matches!(c, 'a'..='z' | '0'..='9' | '_')
}
fn is_lower_alphabetic_or_underscore(c: char) -> bool {
matches!(c, 'a'..='z' | '_')
}
pub fn quote_identifier(identifier: &str) -> Cow<'_, str> {
let mut chars = identifier.chars();
let first = chars.next();
let mut needs_quoting = first
.map(|c| !is_lower_alphabetic_or_underscore(c))
.unwrap_or(false);
let mut num_quotes = 0;
for c in chars {
if !is_lower_alphanumeric_or_underscore(c) {
needs_quoting = true;
}
if c == '"' {
num_quotes += 1;
}
}
if !needs_quoting && parse_keyword(identifier).is_some() {
needs_quoting = true;
}
if needs_quoting {
num_quotes += 2; let capacity = identifier.len() + num_quotes;
let mut result = String::with_capacity(capacity);
result.push('"');
for c in identifier.chars() {
if c == '"' {
result.push('"');
}
result.push(c);
}
result.push('"');
Cow::Owned(result)
} else {
Cow::Borrowed(identifier)
}
}
#[cfg(test)]
mod tests {
use crate::keywords::KEYWORDS;
use super::quote_identifier;
fn run_test(ident: &str, expected_quoted_ident: &str) {
let actual_quoted_ident = quote_identifier(ident);
assert_eq!(actual_quoted_ident, expected_quoted_ident);
}
#[test]
pub fn empty_str_ident_is_not_quoted() {
run_test("", "");
}
#[test]
pub fn underscore_ident_is_not_quoted() {
run_test("_", "_");
}
#[test]
pub fn lowercase_alphabet_idents_are_not_quoted() {
for c in 'a'..='z' {
let ident = c.to_string();
run_test(&ident, &ident);
}
}
#[test]
pub fn idents_with_numerics_in_the_middle_are_not_quoted() {
for c in '0'..='9' {
let ident = format!("_asdf{c}qwer");
run_test(&ident, &ident);
}
}
#[test]
pub fn uppercase_alphabet_idents_are_quoted() {
for c in 'A'..='Z' {
let ident = c.to_string();
let expected = format!(r#""{ident}""#);
run_test(&ident, &expected);
}
}
#[test]
pub fn idents_starting_with_numerics_are_quoted() {
for c in '0'..='9' {
let ident = format!("{c}uiop");
let expected = format!(r#""{ident}""#);
run_test(&ident, &expected);
}
}
#[test]
pub fn keywords_are_quoted() {
for keyword in KEYWORDS.keys() {
let quoted_keyword = format!(r#""{keyword}""#);
run_test(keyword, "ed_keyword);
}
}
#[test]
pub fn idents_with_double_quote_are_quoted() {
run_test(r#"""#, r#""""""#);
run_test(r#"asdf"qwer"#, r#""asdf""qwer""#);
}
#[test]
pub fn non_ascii_idents_are_quoted() {
run_test("हिन्दी", r#""हिन्दी""#);
run_test("mIxEdCaSe", r#""mIxEdCaSe""#);
}
}