1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
use std::fmt::{self, Debug, Display};
use std::prelude::v1::*;

pub struct Punctuated<'a, T: Iterator + Clone>(pub T, pub &'a str);
macro_rules! impl_punctuated {
    ($($req:ident => $fmt:literal),*$(,)?) => {$(
        impl<'a, T: Iterator + Clone> $req for Punctuated<'a, T> where <T as Iterator>::Item: $req {
            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
                let mut vals = self.0.clone();
                if let Some(first) = vals.next() {
                    write!(f, $fmt, first)?;
                    for rest in vals {
                        write!(f, concat!("{}", $fmt), self.1, rest)?;
                    }
                }
                Ok(())
            }
        }
    )*}
}
impl_punctuated! { Debug => "{:?}", Display => "{}" }

/// Returns a new string which is indented by 4 spaces.
pub fn indent(code: &str) -> String {
    Punctuated(code.lines().map(|s| format!("    {}", s)), "\n").to_string()
}
#[test]
fn test_indent() {
    assert_eq!(indent(""), "");
    assert_eq!(indent("hello"), "    hello");
    assert_eq!(indent("hello\nworld"), "    hello\n    world");
}

/// Returns a new string which encodes special characters as the typical backslash escape sequences.
/// Notably, this includes single and double quotes, so you can safely translate a string literal by wrapping the result in quotes.
pub fn escape(raw: &str) -> String {
    let mut res = String::with_capacity(raw.len());
    for c in raw.chars() {
        match c {
            '\"' => res += "\\\"",
            '\\' => res += "\\\\",
            '\'' => res += "\\'",
            '\n' => res += "\\n",
            '\r' => res += "\\r",
            '\t' => res += "\\t",
            _ => res.push(c),
        }
    }
    res
}
#[test]
fn test_escape() {
    assert_eq!(escape("hello world"), "hello world");
    assert_eq!(escape("hello\n\r\t\\'\"world"), "hello\\n\\r\\t\\\\\\'\\\"world");
}

pub fn normalize_space(raw: &str) -> String {
    let mut res = String::new();
    let mut chars = raw.trim().chars();
    while let Some(c) = chars.next() {
        if c.is_whitespace() {
            res.push(' ');
            for cc in chars.by_ref() {
                if !cc.is_whitespace() {
                    res.push(cc);
                    break
                }
            }
        }
        else { res.push(c) }
    }
    res
}
#[test]
fn test_normalize_space() {
    assert_eq!(normalize_space(" \t  hello \r\n \r\n\n \t\t   \t world \t\t  "), "hello world");
}

/// Converts a Snap! identifier into a valid C-like identifier.
pub fn c_ident(raw: &str) -> Result<String, ()> {
    let cleaned: String = raw.chars().map(|ch| match ch {
        '_' | 'a'..='z' | 'A'..='Z' | '0'..='9' => ch,
        _ => ' ',
    }).collect();
    let res = Punctuated(cleaned.split_ascii_whitespace(), "_").to_string();
    match res.chars().next() {
        None => Err(()),
        Some(v) => Ok(if ('0'..='9').contains(&v) { format!("var_{}", res) } else { res })
    }
}
#[test]
fn test_c_ident() {
    assert_eq!(c_ident("foo").unwrap(), "foo");
    assert_eq!(c_ident("foo!").unwrap(), "foo");
    assert_eq!(c_ident("foo[]").unwrap(), "foo");
    assert_eq!(c_ident("(foo)").unwrap(), "foo");
    assert_eq!(c_ident(" (foo) ").unwrap(), "foo");
    assert_eq!(c_ident(" (foo-bar) ").unwrap(), "foo_bar");
    assert_eq!(c_ident(" (foo    bar 27) ").unwrap(), "foo_bar_27");
    assert_eq!(c_ident(" (foo bar*} 27)[]{} ").unwrap(), "foo_bar_27");
    assert_eq!(c_ident(" ( foo bar*} 27)[]{} ").unwrap(), "foo_bar_27");
    assert_eq!(c_ident(" ( foo ba*[]}r*} 27)[]{} ").unwrap(), "foo_ba_r_27");
    assert_eq!(c_ident("foo's parent").unwrap(), "foo_s_parent");
    assert_eq!(c_ident("6foo").unwrap(), "var_6foo");
    assert_eq!(c_ident("[6foo").unwrap(), "var_6foo");
    assert_eq!(c_ident("[ 6foo").unwrap(), "var_6foo");
}