shelly-shell 0.5.0

A Rust based Unix style shell with a typed and structured language syntax.

use std::collections::HashMap;

use serde_json::{ Value as Json, Number, Map, from_str, to_string };

use crate::language::data::{ value::{ Value, Executable }, map_key::MapKey, types::TypeKind };


pub(super) fn encode(value: &Value) -> Result<String, String>
{
    to_string(&to_json(value)?).map_err(|error| error.to_string())
}


fn to_json(value: &Value) -> Result<Json, String>
{
    Ok(match value.underlying()
        {
            Value::None => Json::Null,
            Value::Boolean(value) => Json::Bool(*value),
            Value::Integer(value) => Json::Number(Number::from(*value)),
            Value::Float(value, _) => Json::Number(Number::from_f64(*value)
                .ok_or_else(|| "JSON requires finite numbers".to_string())?),
            Value::String(value, Executable::No) => Json::String(value.clone()),
            Value::Array(items) => Json::Array(items.iter().map(to_json)
                .collect::<Result<_, _>>()?),
            Value::HashMap(items) =>
                {
                    let mut object = Map::new();
                    for (key, value) in items.iter()
                    {
                        let MapKey::String(key) = key else
                            { return Err("JSON object keys must be Strings".into()); };
                        object.insert(key.clone(), to_json(value)?);
                    }
                    Json::Object(object)
                },
            Value::Struct(item) =>
                {
                    let TypeKind::Struct(fields) = &item.definition.kind else { unreachable!(); };
                    let mut object = Map::new();
                    for (field, value) in fields.iter().zip(&item.fields)
                    { object.insert(field.name.clone(), to_json(value)?); }
                    Json::Object(object)
                },
            value => return Err(format!("JSON cannot encode {}", value.type_name())),
        })
}


pub(super) fn decode(text: &str) -> Result<Value, String>
{
    from_json(from_str(text).map_err(|error| error.to_string())?)
}


fn from_json(value: Json) -> Result<Value, String>
{
    Ok(match value
        {
            Json::Null => Value::None,
            Json::Bool(value) => Value::Boolean(value),
            Json::Number(value) =>
                {
                    if let Some(number) = value.as_i64() { Value::Integer(number) }
                    else if value.is_u64()
                    { return Err("JSON integer exceeds the signed 64-bit range".into()); }
                    else { Value::Float(value.as_f64().ok_or_else(||
                        "JSON number is out of range".to_string())?, None) }
                },
            Json::String(value) => Value::from_string(value),
            Json::Array(values) => Value::from_array(values.into_iter().map(from_json)
                .collect::<Result<_, _>>()?),
            Json::Object(values) => Value::from_hash_map(values.into_iter()
                .map(|(key, value)| Ok((MapKey::String(key), from_json(value)?)))
                .collect::<Result<HashMap<_, _>, String>>()?),
        })
}