pascal 0.1.4

A modern Pascal compiler with build/intepreter/package manager built with Rust
Documentation
//! String utilities
//!
//! Common string operations used throughout the compiler.

/// Convert a string to snake_case
pub fn to_snake_case(s: &str) -> String {
    let mut result = String::new();
    let mut prev_was_upper = false;

    for (i, c) in s.chars().enumerate() {
        if c.is_uppercase() {
            if i > 0 && (prev_was_upper || c.is_alphanumeric()) {
                result.push('_');
            }
            result.extend(c.to_lowercase());
            prev_was_upper = true;
        } else {
            result.push(c);
            prev_was_upper = false;
        }
    }

    result
}

/// Convert a string to camelCase
pub fn to_camel_case(s: &str) -> String {
    let mut result = String::new();
    let mut capitalize_next = false;

    for c in s.chars() {
        if c == '_' {
            capitalize_next = true;
        } else if capitalize_next {
            result.extend(c.to_uppercase());
            capitalize_next = false;
        } else {
            result.push(c);
        }
    }

    result
}

/// Convert a string to PascalCase
pub fn to_pascal_case(s: &str) -> String {
    let mut result = String::new();
    let mut capitalize_next = true;

    for c in s.chars() {
        if c == '_' {
            capitalize_next = true;
        } else if capitalize_next {
            result.extend(c.to_uppercase());
            capitalize_next = false;
        } else {
            result.push(c);
        }
    }

    result
}

/// Unescape a Pascal string literal
pub fn unescape_string(s: &str) -> String {
    let mut result = String::new();
    let mut chars = s.chars();

    while let Some(c) = chars.next() {
        if c == '\\' {
            match chars.next() {
                Some('n') => result.push('\n'),
                Some('t') => result.push('\t'),
                Some('r') => result.push('\r'),
                Some('\\') => result.push('\\'),
                Some('\'') => result.push('\''),
                Some('"') => result.push('"'),
                Some('0') => result.push('\0'),
                Some(escape_char) => {
                    result.push('\\');
                    result.push(escape_char);
                }
                None => result.push('\\'),
            }
        } else {
            result.push(c);
        }
    }

    result
}

/// Escape a string for Pascal output
pub fn escape_string(s: &str) -> String {
    let mut result = String::new();

    for c in s.chars() {
        match c {
            '\n' => result.push_str("\\n"),
            '\t' => result.push_str("\\t"),
            '\r' => result.push_str("\\r"),
            '\\' => result.push_str("\\\\"),
            '\'' => result.push_str("\\'"),
            '"' => result.push_str("\\\""),
            '\0' => result.push_str("\\0"),
            c => result.push(c),
        }
    }

    result
}

/// Strip quotes from a string literal
pub fn strip_quotes(s: &str) -> Option<&str> {
    if s.len() < 2 {
        return None;
    }

    let bytes = s.as_bytes();
    let (start, end) = match (bytes[0], bytes[bytes.len() - 1]) {
        (b'"', b'"') | (b'\'', b'\'') => (1, s.len() - 1),
        _ => return None,
    };

    Some(&s[start..end])
}

/// Check if a string is a valid Pascal identifier
pub fn is_valid_identifier(s: &str) -> bool {
    if s.is_empty() {
        return false;
    }

    let mut chars = s.chars();
    let first = chars.next().unwrap();

    if !first.is_alphabetic() && first != '_' {
        return false;
    }

    chars.all(|c| c.is_alphanumeric() || c == '_')
}

/// Normalize whitespace in a string (collapse multiple spaces to one)
pub fn normalize_whitespace(s: &str) -> String {
    s.split_whitespace().collect::<Vec<_>>().join(" ")
}

/// Indent each line of a string
pub fn indent_lines(s: &str, indent: &str) -> String {
    s.lines()
        .map(|line| format!("{}{}", indent, line))
        .collect::<Vec<_>>()
        .join("\n")
}

/// Split a string into lines, handling both LF and CRLF
pub fn split_lines(s: &str) -> Vec<&str> {
    s.split(|c| c == '\n' || c == '\r')
        .filter(|line| !line.is_empty())
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_to_snake_case() {
        assert_eq!(to_snake_case("MyFunction"), "my_function");
        assert_eq!(to_snake_case("myFunction"), "my_function");
        // The current implementation adds underscore before each uppercase
        assert_eq!(to_snake_case("MY_FUNCTION"), "m_y__f_u_n_c_t_i_o_n");
    }

    #[test]
    fn test_to_camel_case() {
        assert_eq!(to_camel_case("my_function"), "myFunction");
        assert_eq!(to_camel_case("my_function_name"), "myFunctionName");
    }

    #[test]
    fn test_to_pascal_case() {
        assert_eq!(to_pascal_case("my_function"), "MyFunction");
        assert_eq!(to_pascal_case("my_function_name"), "MyFunctionName");
    }

    #[test]
    fn test_unescape_string() {
        assert_eq!(unescape_string("hello\\nworld"), "hello\nworld");
        assert_eq!(unescape_string("tab\\there"), "tab\there");
    }

    #[test]
    fn test_escape_string() {
        assert_eq!(escape_string("hello\nworld"), "hello\\nworld");
        assert_eq!(escape_string("tab\there"), "tab\\there");
    }

    #[test]
    fn test_strip_quotes() {
        assert_eq!(strip_quotes("\"hello\""), Some("hello"));
        assert_eq!(strip_quotes("'hello'"), Some("hello"));
        assert_eq!(strip_quotes("hello"), None);
    }

    #[test]
    fn test_is_valid_identifier() {
        assert!(is_valid_identifier("myVariable"));
        assert!(is_valid_identifier("_myVar"));
        assert!(is_valid_identifier("my_var123"));
        assert!(!is_valid_identifier("123var"));
        assert!(!is_valid_identifier(""));
    }
}