browser_window/
javascript.rs

1use std::{borrow::Cow, collections::HashMap, fmt, str::FromStr};
2
3use json::JsonValue;
4pub use num_bigfloat::BigFloat;
5
6/// A JavaScript value.
7#[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	/// When a javascript value is returned that does not fit any of the other
17	/// value types in this enum, `JsValue::Other` is returned with a string
18	/// representation of the value. For example, functions get returned as an
19	/// instance of `JsValue::Other`.
20	Other(String),
21}
22
23impl JsValue {
24	/// Parses the given JSON string into a `JsValue`. If parsing failed, the
25	/// string is returned as a `JsValue::Other`.
26	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 the symbol starts with a digit, interpret it as a (positive) number
39		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	/// Gets the string of the `JsValue::String`, or otherwise just a normal
114	/// string representation of the value.
115	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		// TODO: Clean up this code
165		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	// Even though we've already created a new string, we still save memory by
212	// dropping it:
213	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}