browser_window/
javascript.rs1use std::{borrow::Cow, collections::HashMap, fmt, str::FromStr};
2
3use json::JsonValue;
4pub use num_bigfloat::BigFloat;
5
6#[derive(Clone, Debug)]
8pub enum JsValue {
9 Array(Vec<JsValue>),
10 Boolean(bool),
11 Null,
12 Number(BigFloat),
13 Object(HashMap<String, JsValue>),
14 String(String),
15 Undefined,
16 Other(String),
21}
22
23impl JsValue {
24 pub fn from_json(string: &str) -> Self {
27 match json::parse(string) {
28 Err(_) => JsValue::Other(string.to_string()),
29 Ok(value) => Self::_from_json(value),
30 }
31 }
32
33 pub fn from_string(string: &str) -> Self {
34 if string.len() == 0 {
35 return Self::Other(String::new());
36 }
37
38 if "0123456789".contains(|c| c == string.chars().nth(0).unwrap()) {
40 return match BigFloat::from_str(string) {
41 Err(_) => Self::Other(format!("unable to parse number: {}", string)),
42 Ok(f) => Self::Number(f),
43 };
44 }
45 if string == "null" {
46 return Self::Null;
47 }
48 if string == "undefined" {
49 return Self::Undefined;
50 }
51 if string == "true" {
52 return Self::Boolean(true);
53 }
54 if string == "false" {
55 return Self::Boolean(false);
56 }
57 if string.chars().nth(0) == Some('\"') && string.chars().last() == Some('\"') {
58 return Self::String(string[1..(string.len() - 1)].to_string());
59 }
60 if string.chars().nth(0) == Some('[') && string.chars().last() == Some(']') {
61 let mut array = Vec::new();
62 for part in string[1..(string.len() - 1)].split(',') {
63 array.push(Self::from_string(part));
64 }
65 return Self::Array(array);
66 }
67 if string.chars().nth(0) == Some('{') && string.chars().last() == Some('}') {
68 let mut map = HashMap::new();
69 for part in string[1..(string.len() - 1)].split(',') {
70 if let Some((key, value)) = part.split_once(':') {
71 map.insert(key.to_string(), Self::from_string(value));
72 }
73 }
74 return Self::Object(map);
75 }
76
77 Self::Other(string.to_string())
78 }
79
80 fn _from_json(value: JsonValue) -> Self {
81 match value {
82 JsonValue::Null => Self::Null,
83 JsonValue::Short(s) => Self::String(s.to_string()),
84 JsonValue::String(s) => {
85 println!("S '{}'", s);
86 Self::String(s)
87 }
88 JsonValue::Number(n) => {
89 let (sign, mantissa, exponent) = n.as_parts();
90
91 let big: BigFloat = mantissa.into();
92 for _i in 0..exponent {
93 big.mul(&10.into());
94 }
95 if !sign {
96 big.inv_sign();
97 }
98 Self::Number(big)
99 }
100 JsonValue::Boolean(b) => Self::Boolean(b),
101 JsonValue::Object(o) => {
102 let mut map = HashMap::with_capacity(o.len());
103 for (key, value) in o.iter() {
104 map.insert(key.to_string(), Self::_from_json(value.clone()));
105 }
106 Self::Object(map)
107 }
108 JsonValue::Array(a) =>
109 Self::Array(a.into_iter().map(|i| Self::_from_json(i)).collect()),
110 }
111 }
112
113 pub fn to_string_unenclosed(&self) -> Cow<'_, str> {
116 match self {
117 Self::String(s) => Cow::Borrowed(s),
118 other => Cow::Owned(other.to_string()),
119 }
120 }
121}
122
123impl fmt::Display for JsValue {
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 match self {
126 Self::Array(a) => {
127 write!(f, "[")?;
128 if a.len() > 0 {
129 write!(f, "{}", a[0])?;
130 for i in 1..a.len() {
131 write!(f, ",{}", a[i])?;
132 }
133 }
134 write!(f, "]")
135 }
136 Self::Boolean(b) => write!(f, "{}", b),
137 Self::Number(n) => write!(f, "{}", n),
138 Self::Object(o) => {
139 write!(f, "{{")?;
140 for (k, v) in o.iter() {
141 write!(f, "\"{}\":{}", k, v)?;
142 }
143 write!(f, "}}")
144 }
145 Self::Null => write!(f, "null"),
146 Self::String(s) => write!(f, "\"{}\"", escape_string(s)),
147 Self::Undefined => write!(f, "undefined"),
148 Self::Other(code) => write!(f, "{}", code),
149 }
150 }
151}
152
153const UNESCAPED_CHARACTERS: &str =
154 " -_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&()*+,./:;<=>?@[]^`{|}~";
155
156fn escape_string(string: &str) -> Cow<'_, str> {
157 if string.len() == 0 {
158 return Cow::Borrowed(string);
159 }
160
161 let mut result = String::with_capacity(string.len() * 2);
162 let mut escaped = 0;
163 for char in string.chars() {
164 if !UNESCAPED_CHARACTERS.contains(char) {
166 escaped += 1;
167 if char == '\n' {
168 result.push('\\');
169 result.push('n');
170 } else if char == '\'' {
171 result.push('\\');
172 result.push('\'');
173 } else if char == '"' {
174 result.push('\\');
175 result.push('"');
176 } else if char == '\r' {
177 result.push('\\');
178 result.push('r');
179 } else if char == '\t' {
180 result.push('\\');
181 result.push('t');
182 } else if char == '\r' {
183 result.push('\\');
184 result.push('r');
185 } else if char == '\x08' {
186 result.push('\\');
187 result.push('b');
188 } else if char == '\x0c' {
189 result.push('\\');
190 result.push('f');
191 } else if char == '\x0b' {
192 result.push('\\');
193 result.push('v');
194 } else if char == '\0' {
195 result.push('\\');
196 result.push('0');
197 } else if (char as u32) < 256 {
198 let codepoint = char as u32;
199 result.push('\\');
200 result.push('x');
201 result.push(char::from_digit(codepoint >> 4, 16).unwrap());
202 result.push(char::from_digit(codepoint & 0x0F, 16).unwrap());
203 } else {
204 result.extend(char.escape_unicode().into_iter());
205 }
206 } else {
207 result.push(char);
208 }
209 }
210
211 if escaped == 0 {
214 Cow::Borrowed(string)
215 } else {
216 Cow::Owned(result)
217 }
218}
219
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn test_string_escaping() {
227 let input = " test\r\n\t\x08\x0b\x0c\0\x7f\u{1234}❤©";
228 let output = " test\\r\\n\\t\\b\\v\\f\\0\\x7f\\u{1234}\\u{2764}\\xa9";
229 assert_eq!(output, escape_string(input))
230 }
231}