#![allow(dead_code)]
use anyhow::{Error, Result};
use serde_json::{Map, Number, Value as SerdeValue};
use std::collections::BTreeMap;
use std::convert::{TryFrom, TryInto};
use std::fmt;
use crate::interpreter::Context;
pub(crate) type Object = BTreeMap<String, Value>;
#[derive(Clone)]
pub(crate) struct Function {
name: &'static str,
f: fn(&Context, &[Value]) -> Result<Value>,
}
impl fmt::Debug for Function {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Function({}, ..)", self.name)
}
}
impl PartialEq for Function {
fn eq(&self, other: &Self) -> bool {
self.f as *const () == other.f as *const () && self.name == other.name
}
}
impl Function {
pub(crate) fn new(name: &'static str, f: fn(&Context, &[Value]) -> Result<Value>) -> Function {
Function { name, f }
}
pub(crate) fn call(&self, context: &Context, args: &[Value]) -> Result<Value> {
(self.f)(context, args)
}
}
#[derive(Debug, PartialEq, Clone)]
pub(crate) enum Value {
Null,
String(String),
Number(f64),
Bool(bool),
Object(Object),
Array(Vec<Value>),
DeletionMarker,
Function(Function),
}
impl Value {
pub(crate) fn to_json(&self) -> Result<String> {
let v: SerdeValue = self.try_into()?;
Ok(serde_json::to_string(&v)?)
}
pub(crate) fn is_null(&self) -> bool {
matches!(self, Value::Null)
}
pub(crate) fn is_string(&self) -> bool {
matches!(self, Value::String(_))
}
pub(crate) fn is_number(&self) -> bool {
matches!(self, Value::Number(_))
}
pub(crate) fn is_bool(&self) -> bool {
matches!(self, Value::Bool(_))
}
pub(crate) fn is_object(&self) -> bool {
matches!(self, Value::Object(_))
}
pub(crate) fn is_array(&self) -> bool {
matches!(self, Value::Array(_))
}
pub(crate) fn is_deletion_marker(&self) -> bool {
matches!(self, Value::DeletionMarker)
}
pub(crate) fn as_str(&self) -> Option<&str> {
match self {
Value::String(s) => Some(s.as_ref()),
_ => None,
}
}
pub(crate) fn as_f64(&self) -> Option<&f64> {
match self {
Value::Number(n) => Some(n),
_ => None,
}
}
pub(crate) fn stringify(&self) -> Result<String> {
Ok(match self {
Value::Null => "null".to_owned(),
Value::String(s) => s.clone(),
Value::Number(n) => format!("{}", n),
Value::Bool(b) if *b => "true".to_owned(),
Value::Bool(b) if !*b => "false".to_owned(),
_ => Err(template_error!("cannot stringify value"))?,
})
}
}
fn f64_to_serde_number(value: f64) -> Result<Number> {
if value.fract() == 0.0 {
if value < 0.0 && value > -(u32::MAX as f64) {
return Ok((value as i64).into());
} else if value >= 0.0 && value < u32::MAX as f64 {
return Ok((value as u64).into());
}
}
Number::from_f64(value).ok_or_else(|| template_error!("{} cannot be represented in JSON", value))
}
impl From<&Value> for bool {
fn from(value: &Value) -> bool {
match value {
Value::Number(n) => *n != 0.0,
Value::Bool(b) => *b,
Value::Null => false,
Value::String(s) => !s.is_empty(),
Value::Array(a) => !a.is_empty(),
Value::Object(o) => !o.is_empty(),
Value::DeletionMarker => false,
Value::Function(_) => true,
}
}
}
impl From<Value> for bool {
fn from(value: Value) -> bool {
(&value).into()
}
}
impl From<&SerdeValue> for Value {
fn from(value: &SerdeValue) -> Value {
match value {
SerdeValue::Null => Value::Null,
SerdeValue::String(s) => Value::String(s.into()),
SerdeValue::Number(n) => Value::Number(n.as_f64().unwrap()),
SerdeValue::Bool(b) => Value::Bool(*b),
SerdeValue::Object(o) => {
Value::Object(o.iter().map(|(k, v)| (k.into(), v.into())).collect())
}
SerdeValue::Array(a) => Value::Array(a.iter().map(|v| v.into()).collect()),
}
}
}
impl From<SerdeValue> for Value {
fn from(value: SerdeValue) -> Value {
(&value).into()
}
}
impl TryFrom<&Value> for SerdeValue {
type Error = Error;
fn try_from(value: &Value) -> Result<SerdeValue> {
Ok(match value {
Value::Null => SerdeValue::Null,
Value::String(s) => SerdeValue::String(s.into()),
Value::Number(n) => SerdeValue::Number(f64_to_serde_number(*n)?),
Value::Bool(b) => SerdeValue::Bool(*b),
Value::Object(o) => SerdeValue::Object(
o.iter()
.map(|(k, v)| Ok((k.into(), SerdeValue::try_from(v)?)))
.collect::<Result<Map<String, SerdeValue>>>()?,
),
Value::Array(a) => SerdeValue::Array(
a.iter()
.map(SerdeValue::try_from)
.collect::<Result<Vec<SerdeValue>>>()?,
),
Value::DeletionMarker => SerdeValue::Null,
Value::Function(_) => {
Err(template_error!("cannot represent JSON-e functions as JSON"))?
}
})
}
}
impl TryFrom<Value> for SerdeValue {
type Error = Error;
fn try_from(value: Value) -> Result<SerdeValue> {
SerdeValue::try_from(&value)
}
}
#[cfg(test)]
mod test {
use super::*;
use serde_json::json;
use std::convert::TryInto;
use Value::*;
#[test]
fn conversions() {
let small_float = -100_000_000_000_000f64;
let big_float = 100_000_000_000_000f64;
let serde_value = json!({
"the": "quick",
"brown": ["fox", 2, 3.5, -5, small_float, big_float],
"over": true,
"the": false,
"lazy": 0,
"dog": null,
});
let jsone_value = Object(
vec![
("the".to_string(), String("quick".to_string())),
(
"brown".to_string(),
Array(vec![
String("fox".to_string()),
Number(2.0),
Number(3.5),
Number(-5.0),
Number(small_float),
Number(big_float),
]),
),
("over".to_string(), Bool(true)),
("the".to_string(), Bool(false)),
("lazy".to_string(), Number(0.0)),
("dog".to_string(), Null),
]
.drain(..)
.collect(),
);
let converted: Value = (&serde_value).into();
assert_eq!(converted, jsone_value);
let converted: SerdeValue = (&jsone_value).try_into().unwrap();
assert_eq!(converted, serde_value);
}
#[test]
fn convert_ref() {
let serde_value = json!(true);
let converted: Value = (&serde_value).into();
assert_eq!(converted, Value::Bool(true));
}
#[test]
fn convert_value() {
let serde_value = json!(true);
let converted: Value = serde_value.into();
assert_eq!(converted, Value::Bool(true));
}
#[test]
fn test_is_truthy() {
let obj = vec![("x".to_string(), Null)].drain(..).collect();
let long = "very very very very very very very very very very long string".to_string();
let tests = vec![
(Null, false),
(Array(vec![]), false),
(Array(vec![Number(1.0)]), true),
(Object(super::Object::new()), false),
(Object(obj), true),
(String("".to_string()), false),
(String("short string".to_string()), true),
(String(long), true),
(Number(0.0), false),
(Number(1.0), true),
(Number(-1.0), true),
(Bool(false), false),
(Bool(true), true),
(DeletionMarker, false),
];
for (value, expected) in tests {
let got: bool = (&value).into();
assert_eq!(got, expected, "{:?} should be {}", value, expected);
}
}
}