From 34ec352df85df623ac62c756c513db60c4630b3c Mon Sep 17 00:00:00 2001 From: gahag Date: Sun, 24 Apr 2022 00:02:01 -0300 Subject: [PATCH] Implement std.json --- Cargo.lock | 31 ++++++ Cargo.toml | 3 + src/runtime/lib.rs | 26 +++-- src/runtime/lib/json.rs | 198 ++++++++++++++++++++++++++++++++++++++ src/runtime/value/dict.rs | 2 +- 5 files changed, 253 insertions(+), 7 deletions(-) create mode 100644 src/runtime/lib/json.rs diff --git a/Cargo.lock b/Cargo.lock index d9ab9c7..9d265c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -148,6 +148,8 @@ dependencies = [ "inventory", "os_pipe", "regex", + "serde", + "serde_json", "serial_test", "termion", ] @@ -189,6 +191,12 @@ dependencies = [ "syn", ] +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + [[package]] name = "lazy_static" version = "1.4.0" @@ -314,12 +322,35 @@ version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "serde" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" + +[[package]] +name = "serde_json" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "serial_test" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index a42965e..c871719 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,9 @@ glob = "0.3" serial_test = "0.5" +serde = "1.0" +serde_json = "1.0" + [dev-dependencies] assert_matches = "1.5" diff --git a/src/runtime/lib.rs b/src/runtime/lib.rs index f0332f3..520a677 100644 --- a/src/runtime/lib.rs +++ b/src/runtime/lib.rs @@ -1,7 +1,5 @@ automod::dir!("src/runtime/lib"); -use std::collections::HashMap; - use super::{ keys, Array, @@ -24,16 +22,32 @@ inventory::collect!(RustFun); /// Instantiate the stdlib. pub fn new() -> Value { - let mut dict = HashMap::new(); + let mut dict = Dict::default(); for fun in inventory::iter:: { - let name = fun + let path = fun .name() .strip_prefix("std.") .expect("Builtin function name missing std prefix."); - dict.insert(name.into(), fun.copy().into()); + insert(path, fun.copy().into(), &mut dict); } - Dict::new(dict).into() + dict.into() +} + + +fn insert(path: &str, value: Value, dict: &mut Dict) { + match path.split_once('.') { + None => dict.insert(path.into(), value), + Some((key, path)) => { + let mut dict = dict.borrow_mut(); + let dict = dict.entry(key.into()).or_insert(Dict::default().into()); + + match dict { + Value::Dict(dict) => insert(path, value, dict), + _ => panic!("invalid value in std initialization"), + } + }, + } } diff --git a/src/runtime/lib/json.rs b/src/runtime/lib/json.rs new file mode 100644 index 0000000..aae98ae --- /dev/null +++ b/src/runtime/lib/json.rs @@ -0,0 +1,198 @@ +use std::{fmt, collections::HashMap, convert::TryFrom}; + +use gc::{Finalize, Trace}; +use serde::{ + Deserialize, + Serialize, + de::{self, Visitor, SeqAccess, MapAccess}, + ser::{self, SerializeMap}, + Serializer, + Deserializer +}; + +use super::{ + Dict, + Error, + Float, + NativeFun, + Panic, + RustFun, + Value, + CallContext, +}; + + +inventory::submit! { RustFun::from(Dump) } +inventory::submit! { RustFun::from(Parse) } + +#[derive(Trace, Finalize)] +struct Dump; + +impl NativeFun for Dump { + fn name(&self) -> &'static str { "std.json.dump" } + + fn call(&self, context: CallContext) -> Result { + match context.args() { + [ value ] => serde_json::to_string_pretty(value) + .map(Into::into) + .map_err( + |_| Panic::value_error( + value.copy(), + "nil, bool, byte, int, float, string, array or dict", + context.pos.copy() + ) + ), + + args => Err(Panic::invalid_args(args.len() as u32, 1, context.pos)) + } + } +} + +#[derive(Trace, Finalize)] +struct Parse; + +impl NativeFun for Parse { + fn name(&self) -> &'static str { "std.json.parse" } + + fn call(&self, context: CallContext) -> Result { + match context.args() { + [ value @ Value::String(ref string) ] => Ok( + serde_json::from_slice(string.as_bytes()) + .unwrap_or_else( + |error| Error::new(error.to_string().into(), value.copy()).into() + ) + ), + + args => Err(Panic::invalid_args(args.len() as u32, 1, context.pos)) + } + } +} + +impl<'de> Deserialize<'de> for Value { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct ValueVisitor; + + impl<'de> Visitor<'de> for ValueVisitor { + type Value = Value; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("any valid JSON value") + } + + fn visit_bool(self, value: bool) -> Result { + Ok(Value::Bool(value)) + } + + fn visit_i64(self, value: i64) -> Result { + Ok(Value::Int(value)) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + i64::try_from(value) + .map(Into::into) + .map_err(|error| de::Error::custom(error)) + } + + fn visit_f64(self, value: f64) -> Result { + Ok(Value::Float(value.into())) + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + self.visit_string(String::from(value)) + } + + fn visit_string(self, value: String) -> Result { + Ok(value.into()) + } + + fn visit_none(self) -> Result { + Ok(Value::Nil) + } + + fn visit_some(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Deserialize::deserialize(deserializer) + } + + fn visit_unit(self) -> Result { + Ok(Value::Nil) + } + + fn visit_seq(self, mut visitor: V) -> Result + where + V: SeqAccess<'de>, + { + let mut vec = Vec::new(); + + while let Some(elem) = visitor.next_element()? { + vec.push(elem); + } + + Ok(vec.into()) + } + + fn visit_map(self, mut visitor: V) -> Result + where + V: MapAccess<'de>, + { + match visitor.next_key()? { + Some(key) => { + let mut values = HashMap::new(); + + values.insert(key, visitor.next_value()?); + while let Some((key, value)) = visitor.next_entry()? { + values.insert(key, value); + } + + Ok(Dict::new(values).into()) + } + + None => Ok(Dict::default().into()), + } + } + } + + deserializer.deserialize_any(ValueVisitor) + } +} + +impl Serialize for Value { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Value::Nil => serializer.serialize_unit(), + Value::Bool(b) => serializer.serialize_bool(*b), + Value::Byte(b) => serializer.serialize_str(&String::from_utf8_lossy(&[*b])), + Value::Int(n) => n.serialize(serializer), + Value::Float(Float(n)) => n.serialize(serializer), + Value::String(s) => serializer.serialize_str(&String::from_utf8_lossy(s.as_bytes())), + Value::Array(v) => v.borrow().serialize(serializer), + Value::Dict(m) => { + let mut map = serializer.serialize_map(Some(m.borrow().len()))?; + for (key, value) in m.borrow().iter() { + match key { + Value::String(_) => map.serialize_entry(key, value)?, + _ => return Err(ser::Error::custom("json object key must be string")), + } + } + map.end() + } + + Value::Function(_) => Err(ser::Error::custom("can't serialize function")), + Value::Error(_) => Err(ser::Error::custom("can't serialize error")), + } + } +} diff --git a/src/runtime/value/dict.rs b/src/runtime/value/dict.rs index c10ff42..cdf7bb1 100644 --- a/src/runtime/value/dict.rs +++ b/src/runtime/value/dict.rs @@ -26,7 +26,7 @@ pub mod keys { /// A dict in the language. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Default, PartialEq, Eq)] #[derive(Trace, Finalize)] pub struct Dict(Gc>>);