Skip to content

Commit

Permalink
Implement std.json
Browse files Browse the repository at this point in the history
  • Loading branch information
gahag committed Apr 24, 2022
1 parent cbf4788 commit 34ec352
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 7 deletions.
31 changes: 31 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ glob = "0.3"

serial_test = "0.5"

serde = "1.0"
serde_json = "1.0"

[dev-dependencies]
assert_matches = "1.5"

Expand Down
26 changes: 20 additions & 6 deletions src/runtime/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
automod::dir!("src/runtime/lib");

use std::collections::HashMap;

use super::{
keys,
Array,
Expand All @@ -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::<RustFun> {
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"),
}
},
}
}
198 changes: 198 additions & 0 deletions src/runtime/lib/json.rs
Original file line number Diff line number Diff line change
@@ -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<Value, Panic> {
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<Value, Panic> {
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<D>(deserializer: D) -> Result<Value, D::Error>
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<E>(self, value: bool) -> Result<Value, E> {
Ok(Value::Bool(value))
}

fn visit_i64<E>(self, value: i64) -> Result<Value, E> {
Ok(Value::Int(value))
}

fn visit_u64<E>(self, value: u64) -> Result<Value, E>
where
E: de::Error,
{
i64::try_from(value)
.map(Into::into)
.map_err(|error| de::Error::custom(error))
}

fn visit_f64<E>(self, value: f64) -> Result<Value, E> {
Ok(Value::Float(value.into()))
}

fn visit_str<E>(self, value: &str) -> Result<Value, E>
where
E: de::Error,
{
self.visit_string(String::from(value))
}

fn visit_string<E>(self, value: String) -> Result<Value, E> {
Ok(value.into())
}

fn visit_none<E>(self) -> Result<Value, E> {
Ok(Value::Nil)
}

fn visit_some<D>(self, deserializer: D) -> Result<Value, D::Error>
where
D: Deserializer<'de>,
{
Deserialize::deserialize(deserializer)
}

fn visit_unit<E>(self) -> Result<Value, E> {
Ok(Value::Nil)
}

fn visit_seq<V>(self, mut visitor: V) -> Result<Value, V::Error>
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<V>(self, mut visitor: V) -> Result<Value, V::Error>
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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")),
}
}
}
2 changes: 1 addition & 1 deletion src/runtime/value/dict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<GcCell<HashMap<Value, Value>>>);

Expand Down

0 comments on commit 34ec352

Please sign in to comment.