Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions Cargo.lock

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

37 changes: 37 additions & 0 deletions tests/snippets/json_snippet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import json

def round_trip_test(obj):
# serde_json and Python's json module produce slightly differently spaced
# output; direct string comparison can't pass on both so we use this as a
# proxy
assert obj == json.loads(json.dumps(obj))

assert '"string"' == json.dumps("string")
assert "1" == json.dumps(1)
assert "1.0" == json.dumps(1.0)
assert "true" == json.dumps(True)
assert "false" == json.dumps(False)

assert '[]' == json.dumps([])
assert '[1]' == json.dumps([1])
assert '[[1]]' == json.dumps([[1]])
round_trip_test([1, "string", 1.0, True])

assert '{}' == json.dumps({})
# TODO: uncomment once dict comparison is implemented
# round_trip_test({'a': 'b'})

assert 1 == json.loads("1")
assert -1 == json.loads("-1")
assert 1.0 == json.loads("1.0")
# TODO: uncomment once negative floats are implemented
# assert -1.0 == json.loads("-1.0")
assert "str" == json.loads('"str"')
# TODO: Use "is" once implemented
assert True == json.loads('true')
assert False == json.loads('false')
# TODO: uncomment once None comparison is implemented
# assert None == json.loads('null')
assert [] == json.loads('[]')
assert ['a'] == json.loads('["a"]')
assert [['a'], 'b'] == json.loads('[["a"], "b"]')
File renamed without changes.
3 changes: 3 additions & 0 deletions vm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ authors = ["Shing Lyu <[email protected]>"]
[dependencies]
log = "0.3"
rustpython_parser = {path = "../parser"}
serde = "1.0.66"
serde_derive = "1.0.66"
serde_json = "1.0.26"
7 changes: 7 additions & 0 deletions vm/src/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use std::path::PathBuf;
use self::rustpython_parser::parser;
use super::compile;
use super::pyobject::{DictProtocol, PyObject, PyObjectKind, PyResult};
use super::stdlib;
use super::vm::VirtualMachine;

fn import_module(vm: &mut VirtualMachine, module: &String) -> PyResult {
Expand All @@ -20,8 +21,14 @@ fn import_module(vm: &mut VirtualMachine, module: &String) -> PyResult {
return Ok(module);
}

// Check for Rust-native modules
if let Some(module) = stdlib::get_modules(vm).get(module) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_modules is now called often, it's return value should be stored in ctx somewhere or in the vm maybe.

return Ok(module.clone());
}

// TODO: introduce import error:
let import_error = vm.context().exceptions.exception_type.clone();

// Time to search for module in any place:
let filepath = find_source(vm, module)
.map_err(|e| vm.new_exception(import_error.clone(), format!("Error: {:?}", e)))?;
Expand Down
3 changes: 3 additions & 0 deletions vm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#[macro_use]
extern crate log;
// extern crate env_logger;
extern crate serde;
extern crate serde_json;

//extern crate eval; use eval::eval::*;
// use py_code_object::{Function, NativeType, PyCodeObject};
Expand All @@ -22,6 +24,7 @@ mod objsequence;
mod objstr;
mod objtype;
pub mod pyobject;
pub mod stdlib;
mod sysmodule;
mod traceback;
mod vm;
Expand Down
199 changes: 199 additions & 0 deletions vm/src/stdlib/json.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
use std::collections::HashMap;
use std::fmt;

use serde;
use serde::de::Visitor;
use serde::ser::{SerializeMap, SerializeSeq};
use serde_json;

use super::super::pyobject::{
DictProtocol, PyContext, PyFuncArgs, PyObject, PyObjectKind, PyObjectRef, PyResult,
};
use super::super::VirtualMachine;

impl serde::Serialize for PyObjectKind {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
PyObjectKind::String { value } => serializer.serialize_str(value),

@windelbouwman windelbouwman Aug 26, 2018 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of using ObjectKind, use isinstance or alike to check for the type. This allows for subclasses of str, like class MyStr(str) to work as well. We can do this later though..

PyObjectKind::Integer { value } => serializer.serialize_i32(*value),
PyObjectKind::Float { value } => serializer.serialize_f64(*value),
PyObjectKind::Boolean { value } => serializer.serialize_bool(*value),
PyObjectKind::List { elements } | PyObjectKind::Tuple { elements } => {
let mut seq = serializer.serialize_seq(Some(elements.len()))?;
for e in elements {
match e.borrow().kind {
ref kind => seq.serialize_element(kind)?,
}
}
seq.end()
}
PyObjectKind::Dict { elements } => {
let mut map = serializer.serialize_map(Some(elements.len()))?;
for (key, e) in elements {
map.serialize_entry(key, &e.borrow().kind)?;
}
map.end()
}
PyObjectKind::None => serializer.serialize_none(),
kind => unimplemented!("Object of type '{:?}' is not serializable", kind),
}
}
}

struct PyObjectKindVisitor;

impl<'de> Visitor<'de> for PyObjectKindVisitor {
type Value = PyObjectKind;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a type that can deserialise in Python")
}

fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, this rust code is above my paygrade 😄

{
Ok(PyObjectKind::String {
value: value.to_string(),
})
}

fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(PyObjectKind::String { value })
}

fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
// The JSON deserialiser always uses the i64/u64 deserialisers, so we only need to
// implement those for now
use std::i32;
if value >= i32::MIN as i64 && value <= i32::MAX as i64 {
Ok(PyObjectKind::Integer {
value: value as i32,
})
} else {
Err(E::custom(format!("i64 out of range: {}", value)))
}
}

fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
// The JSON deserialiser always uses the i64/u64 deserialisers, so we only need to
// implement those for now
use std::i32;
if value <= i32::MAX as u64 {
Ok(PyObjectKind::Integer {
value: value as i32,
})
} else {
Err(E::custom(format!("u64 out of range: {}", value)))
}
}

fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(PyObjectKind::Float { value })
}

fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(PyObjectKind::Boolean { value })
}

fn visit_seq<A>(self, mut access: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut seq = Vec::with_capacity(access.size_hint().unwrap_or(0));
while let Some(value) = access.next_element()? {
seq.push(
PyObject {
kind: value,
typ: None, // TODO: Determine the effect this None will have
}.into_ref(),
);
}
Ok(PyObjectKind::List { elements: seq })
}

fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
where
M: serde::de::MapAccess<'de>,
{
let mut map = HashMap::with_capacity(access.size_hint().unwrap_or(0));

while let Some((key, value)) = access.next_entry()? {
map.insert(
key,
PyObject {
kind: value,
typ: None, // TODO: Determine the effect this None will have
}.into_ref(),
);
}

Ok(PyObjectKind::Dict { elements: map })
}

fn visit_unit<E>(self) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(PyObjectKind::None)
}
}

impl<'de> serde::Deserialize<'de> for PyObjectKind {
fn deserialize<D>(deserializer: D) -> Result<PyObjectKind, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_any(PyObjectKindVisitor)
}
}

fn dumps(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
if args.args.len() != 1 {
// TODO: Raise an exception for wrong number of args
// TODO: Implement non-trivial serialisation case
unimplemented!("json.dumps only supports the trivial 1-arg case");
};
// TODO: Raise an exception for serialisation errors
let string = serde_json::to_string(&args.args[0].borrow().kind).unwrap();
Ok(vm.context().new_str(string))
}

fn loads(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
if args.args.len() != 1 {
// TODO: Raise an exception for wrong number of args
// TODO: Implement non-trivial serialisation case
unimplemented!("json.loads only supports the trivial 1-arg case");
};
// TODO: Raise an exception for deserialisation errors
let kind: PyObjectKind = match args.args[0].borrow().kind {
PyObjectKind::String { ref value } => serde_json::from_str(&value).unwrap(),
_ => unimplemented!("json.loads only handles strings"),
};
Ok(PyObject::new(kind, vm.get_type()))
}

pub fn mk_module(ctx: &PyContext) -> PyObjectRef {
let json_mod = ctx.new_module(&"json".to_string(), ctx.new_scope(None));
json_mod.set_item("dumps", ctx.new_rustfunc(dumps));
json_mod.set_item("loads", ctx.new_rustfunc(loads));
json_mod
}
11 changes: 11 additions & 0 deletions vm/src/stdlib/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
mod json;
use std::collections::HashMap;

use super::pyobject::PyObjectRef;
use super::vm::VirtualMachine;

pub fn get_modules(vm: &VirtualMachine) -> HashMap<String, PyObjectRef> {
let mut modules = HashMap::new();
modules.insert("json".to_string(), json::mk_module(vm.context()));
modules
}