use crate::js::number;
#[derive(Clone, Debug, Default, PartialEq)]
pub enum Value {
#[default]
Null,
Bool(bool),
Number(f64),
String(String),
Array(Vec<Value>),
Object(Map),
}
impl Value {
pub fn as_str(&self) -> Option<&str> {
match self {
Value::String(s) => Some(s),
_ => None,
}
}
pub fn as_f64(&self) -> Option<f64> {
match *self {
Value::Number(n) => Some(n),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match *self {
Value::Bool(b) => Some(b),
_ => None,
}
}
pub fn as_array(&self) -> Option<&[Value]> {
match self {
Value::Array(a) => Some(a),
_ => None,
}
}
pub fn as_object(&self) -> Option<&Map> {
match self {
Value::Object(m) => Some(m),
_ => None,
}
}
pub fn is_null(&self) -> bool {
matches!(self, Value::Null)
}
pub(crate) fn to_js_string(&self) -> String {
match self {
Value::Null => "null".into(),
Value::Bool(b) => b.to_string(),
Value::Number(n) => number::to_string(*n),
Value::String(s) => s.clone(),
Value::Array(items) => items
.iter()
.map(|v| {
if v.is_null() {
String::new()
} else {
v.to_js_string()
}
})
.collect::<Vec<_>>()
.join(","),
Value::Object(_) => "[object Object]".into(),
}
}
pub(crate) fn truthy(&self) -> bool {
match self {
Value::Null => false,
Value::Bool(b) => *b,
Value::Number(n) => *n != 0.0 && !n.is_nan(),
Value::String(s) => !s.is_empty(),
Value::Array(_) | Value::Object(_) => true,
}
}
pub(crate) fn to_number(&self) -> f64 {
match self {
Value::Null => 0.0,
Value::Bool(b) => f64::from(u8::from(*b)),
Value::Number(n) => *n,
Value::String(s) => number::number(s),
Value::Array(_) => number::number(&self.to_js_string()),
Value::Object(_) => f64::NAN,
}
}
}
impl From<bool> for Value {
fn from(b: bool) -> Value {
Value::Bool(b)
}
}
impl From<f64> for Value {
fn from(n: f64) -> Value {
Value::Number(n)
}
}
impl From<&str> for Value {
fn from(s: &str) -> Value {
Value::String(s.into())
}
}
impl From<String> for Value {
fn from(s: String) -> Value {
Value::String(s)
}
}
impl From<Vec<Value>> for Value {
fn from(a: Vec<Value>) -> Value {
Value::Array(a)
}
}
impl From<Map> for Value {
fn from(m: Map) -> Value {
Value::Object(m)
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Map {
entries: Vec<(String, Value)>,
}
impl Map {
pub fn new() -> Map {
Map::default()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn get(&self, key: &str) -> Option<&Value> {
self.entries.iter().find(|(k, _)| k == key).map(|(_, v)| v)
}
pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> {
self.entries
.iter_mut()
.find(|(k, _)| k == key)
.map(|(_, v)| v)
}
pub fn contains_key(&self, key: &str) -> bool {
self.get(key).is_some()
}
pub fn insert(&mut self, key: impl Into<String>, value: Value) {
let key = key.into();
if let Some(v) = self.get_mut(&key) {
*v = value;
return;
}
match index_of(&key) {
Some(n) => {
let at = self
.entries
.iter()
.position(|(k, _)| index_of(k).is_none_or(|m| m > n))
.unwrap_or(self.entries.len());
self.entries.insert(at, (key, value));
}
None => self.entries.push((key, value)),
}
}
pub fn remove(&mut self, key: &str) -> Option<Value> {
let at = self.entries.iter().position(|(k, _)| k == key)?;
Some(self.entries.remove(at).1)
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &Value)> {
self.entries.iter().map(|(k, v)| (k.as_str(), v))
}
pub fn keys(&self) -> impl Iterator<Item = &str> {
self.entries.iter().map(|(k, _)| k.as_str())
}
}
impl<K: Into<String>> FromIterator<(K, Value)> for Map {
fn from_iter<I: IntoIterator<Item = (K, Value)>>(iter: I) -> Map {
let mut m = Map::new();
for (k, v) in iter {
m.insert(k, v);
}
m
}
}
impl<'a> IntoIterator for &'a Map {
type Item = (&'a str, &'a Value);
type IntoIter = Box<dyn Iterator<Item = (&'a str, &'a Value)> + 'a>;
fn into_iter(self) -> Self::IntoIter {
Box::new(self.iter())
}
}
#[cfg(test)]
impl Map {
pub(crate) fn sorted(entries: Vec<(&str, Value)>) -> Map {
Map {
entries: entries
.into_iter()
.map(|(k, v)| (k.to_string(), v))
.collect(),
}
}
}
fn index_of(k: &str) -> Option<u32> {
if k.is_empty() || k.len() > 10 || k.len() > 1 && k.starts_with('0') {
return None;
}
if !k.bytes().all(|c| c.is_ascii_digit()) {
return None;
}
k.parse::<u64>()
.ok()
.filter(|&n| n < 4294967295)
.map(|n| n as u32)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn keys_in_javascript_order() {
let m: Map = [
("b", Value::Null),
("10", 1.0.into()),
("a", true.into()),
("2", "x".into()),
]
.into_iter()
.collect();
assert_eq!(m.keys().collect::<Vec<_>>(), ["2", "10", "b", "a"]);
let mut m = m;
m.insert("b", 2.0.into());
m.insert("01", Value::Null);
m.insert("4294967295", Value::Null);
assert_eq!(
m.keys().collect::<Vec<_>>(),
["2", "10", "b", "a", "01", "4294967295"]
);
assert_eq!(m.get("b"), Some(&Value::Number(2.0)));
}
#[test]
fn javascript_conversions() {
let a = Value::Array(vec![1.0.into(), Value::Null, "x".into()]);
assert_eq!(a.to_js_string(), "1,,x");
assert!(Value::Array(vec![]).truthy());
assert!(Value::Array(vec![]).to_number() == 0.0);
assert!(!Value::Number(f64::NAN).truthy());
}
}