Skip to content

Commit

Permalink
feat(codegen): Map Rust's struct enums to records in Gluon.
Browse files Browse the repository at this point in the history
  • Loading branch information
mluszczyk authored and Marwes committed Aug 5, 2019
1 parent efc7511 commit afb682e
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 43 deletions.
24 changes: 16 additions & 8 deletions codegen/src/getable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,10 +243,10 @@ fn gen_variant_match(ident: &Ident, tag: usize, variant: &Variant) -> TokenStrea
}
}
Fields::Named(FieldsNamed { named, .. }) => {
let cons = gen_struct_variant_cons(named);
let cons = gen_struct_variant_cons(ident, variant_ident, named);

quote! {
#tag => #ident::#variant_ident#cons
#tag => #cons
}
}
}
Expand All @@ -273,7 +273,7 @@ where
}
}

fn gen_struct_variant_cons<'a, I>(fields: I) -> TokenStream
fn gen_struct_variant_cons<'a, I>(ident: &Ident, variant_ident: &Ident, fields: I) -> TokenStream
where
I: IntoIterator<Item = &'a Field>,
{
Expand All @@ -283,19 +283,27 @@ where
.ident
.as_ref()
.expect("Struct fields always have names");

let quoted_field_ident = format!("{}", quote! { #field_ident });
quote! {
#field_ident: if let Some(val) = data.get_variant(#idx) {
#field_ident: if let Some(val) = inner_data.lookup_field(vm, #quoted_field_ident) {
<#field_ty as _gluon_api::Getable<'__vm, '__value>>::from_value(vm, val)
} else {
panic!("Enum does not contain data at index '{}'. Do the type definitions match?", #idx)
}
}
});

quote! {
{#(#fields),*}
}
quote! {{
if let Some(val) = data.get_variant(0) {
let inner_data = match val.as_ref() {
_gluon_api::ValueRef::Data(data) => data,
val => panic!("Unexpected value: '{:?}'. Do the type definitions match?", val),
};
#ident::#variant_ident{#(#fields),*}
} else {
panic!("Enum does not contain data at index '0'. Do the type definitions match?")
}
}}
}

fn create_getable_bounds(generics: &Generics) -> Vec<TokenStream> {
Expand Down
22 changes: 17 additions & 5 deletions codegen/src/pushable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,23 @@ fn derive_enum(
Fields::Unit => quote! { #ident::#variant_ident },
};

let push_impl = gen_push_impl(Some(tag), &field_idents, &field_types);

quote! {
#pattern => {
#push_impl
match &variant.fields {
Fields::Named(_) => {
let push_impl = gen_push_impl(None, &field_idents, &field_types);
quote! {
#pattern => {
#push_impl
ctx.context().push_new_data(vm, #tag as _gluon_types::VmTag, 1)?;
}
}
},
_ => {
let push_impl = gen_push_impl(Some(tag), &field_idents, &field_types);
quote! {
#pattern => {
#push_impl
}
}
}
}
});
Expand Down
60 changes: 48 additions & 12 deletions codegen/src/vm_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,19 +101,55 @@ fn gen_impl(container: &Container, ident: Ident, generics: Generics, data: &Data
Data::Enum(ref enum_) => {
let variants = enum_.variants.iter().map(|variant| {
let ident = variant.ident.to_string();
let args = variant.fields.iter().map(|field| {
let typ = &field.ty;
quote! {
<#typ as _gluon_api::VmType>::make_type(vm)
match variant.fields {
Fields::Named(ref fields) => {
let fields = fields.named.iter().map(|field| {
let ident = field.ident.as_ref().unwrap().to_string();
let typ = &field.ty;
quote! {
_gluon_base::types::Field {
name: _gluon_base::symbol::Symbol::from(#ident),
typ: <#typ as _gluon_api::VmType>::make_type(vm),
}
}
});
quote! {{
let ctor_name = _gluon_base::symbol::Symbol::from(#ident);
let typ = _gluon_base::types::Type::record(
vec![],
vec![#(#fields),*],
);
_gluon_base::types::Field::ctor(
ctor_name,
vec![typ],
)
}}
}
});
quote! {{
let ctor_name = _gluon_base::symbol::Symbol::from(#ident);
_gluon_base::types::Field::ctor(
ctor_name,
vec![#(#args),*],
)
}}
Fields::Unnamed(ref fields) => {
let args = fields.unnamed.iter().map(|field| {
let typ = &field.ty;
quote! {
<#typ as _gluon_api::VmType>::make_type(vm)
}
});
quote! {{
let ctor_name = _gluon_base::symbol::Symbol::from(#ident);
_gluon_base::types::Field::ctor(
ctor_name,
vec![#(#args),*],
)
}}
}
Fields::Unit => quote! {{
let ctor_name = _gluon_base::symbol::Symbol::from(#ident);
_gluon_base::types::Field::ctor(
ctor_name, vec![],
)
}}
}
//----------------------------------------------------

//----------------------------------------------------
});
quote! {
_gluon_base::types::Type::variant(
Expand Down
5 changes: 3 additions & 2 deletions codegen/tests/derive_getable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ fn enum_struct_variants() {
let mut compiler = Compiler::new();

let src = api::typ::make_source::<StructEnum>(&vm).unwrap();
println!("Types:\n{}", src);
compiler.load_script(&vm, "types", &src).unwrap();
import::add_extern_module(&vm, "functions", load_struct_enum_mod);

Expand All @@ -103,8 +104,8 @@ fn enum_struct_variants() {
let { struct_enum_to_str } = import! functions
let { assert } = import! std.test
assert (struct_enum_to_str (OneField 1337) == "OneField { field: 1337 }")
assert (struct_enum_to_str (TwoFields "Pi" 3.14) == "TwoFields { name: \"Pi\", val: 3.14 }")
assert (struct_enum_to_str (OneField { field = 1337 }) == "OneField { field: 1337 }")
assert (struct_enum_to_str (TwoFields { name = "Pi", val = 3.14 }) == "TwoFields { name: \"Pi\", val: 3.14 }")
"#;

if let Err(why) = compiler.run_expr::<()>(&vm, "test", script) {
Expand Down
2 changes: 1 addition & 1 deletion codegen/tests/derive_pushable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ fn normal_enum() {
assert (x == 1920)
assert (y == 1080)
1
| Struct key value ->
| Struct { key, value } ->
assert (key == "under the doormat")
assert (value == "lots of gold")
2
Expand Down
10 changes: 8 additions & 2 deletions codegen/tests/derive_vm_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ extern crate gluon;
mod init;

use gluon::{base::types::Type, vm::api::VmType};
use gluon::vm::api;
#[macro_use]
extern crate serde_derive;
use init::new_vm;

#[derive(VmType)]
Expand All @@ -25,7 +28,7 @@ fn struct_() {
);
}

#[derive(VmType)]
#[derive(VmType, Serialize, Deserialize)]
#[allow(unused)]
enum Enum {
One,
Expand All @@ -37,9 +40,12 @@ enum Enum {
fn enum_() {
let vm = new_vm();

let src = api::typ::make_source::<Enum>(&vm).unwrap();
println!("Enum Types:\n{}", src);

assert_eq!(
Enum::make_type(&vm).to_string(),
"| One\n| Two Int\n| Three String"
"| One\n| Two Int\n| Three { id : String }"
);
}

Expand Down
8 changes: 4 additions & 4 deletions tests/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ type TestFn = OwnedFunction<fn(()) -> TestEff>;

#[derive(Deserialize)]
enum TestCase {
Test { name: String, test: TestFn },
Group { name: String, tests: Vec<TestCase> },
Test(String, TestFn),
Group(String, Vec<TestCase>),
}

define_test_type! { TestCase Hole }
Expand Down Expand Up @@ -163,12 +163,12 @@ fn make_tensile_test(name: String, test: TestFn) -> tensile::Test<Error> {
impl TestCase {
fn into_tensile_test(self) -> tensile::Test<Error> {
match self {
TestCase::Test { name, test } => {
TestCase::Test(name, test) => {
let child_thread = test.vm().new_thread().unwrap();
let test = TestFn::from_value(&child_thread, test.get_variant());
make_tensile_test(name, test)
}
TestCase::Group { name, tests } => tensile::Test::Group {
TestCase::Group(name, tests) => tensile::Test::Group {
name,
tests: tests.into_iter().map(TestCase::into_tensile_test).collect(),
},
Expand Down
38 changes: 35 additions & 3 deletions vm/src/api/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ Compiler::new()
.load_script(
&thread,
"test",
r#" type Enum = | A Int | B String Float in { Enum } "#,
r#" type Enum = | A Int | B { string : String, test : Float } in { Enum } "#,
)
.unwrap_or_else(|err| panic!("{}", err));
Expand All @@ -142,7 +142,7 @@ let (De(enum_), _) = Compiler::new()
.run_expr::<De<Enum>>(
&thread,
"test",
r#" let { Enum } = import! "test" in B "abc" 3.14 "#,
r#" let { Enum } = import! "test" in B { string = "abc", test = 3.14 } "#,
)
.unwrap_or_else(|err| panic!("{}", err));
assert_eq!(
Expand Down Expand Up @@ -856,6 +856,38 @@ impl<'de, 'a, 't> VariantAccess<'de> for Enum<'a, 'de, 't> {
where
V: Visitor<'de>,
{
de::Deserializer::deserialize_seq(self.de, visitor)
let typ = resolve::remove_aliases_cow(self.de.state.env, &mut NullInterner, self.de.typ);
match (self.de.input.as_ref(), &**typ) {
(ValueRef::Data(data), &Type::Variant(ref row)) => {
match row.row_iter().nth(data.tag() as usize) {
Some(field) => {
let typ = resolve::remove_aliases_cow(
self.de.state.env, &mut NullInterner, &field.typ);
match (data.get_variant(0).unwrap().as_ref(), &**typ) {
(ValueRef::Data(ref data), &Type::Function(_, ref typ, _)) => {
let iter = typ.row_iter().flat_map(|field| {
data.lookup_field(self.de.state.thread, field.name.as_ref())
.map(|variant| (variant, &field.name, &field.typ))
});
visitor.visit_map(
MapDeserializer::new(self.de.state.clone(), iter))
},
_ => Err(VmError::Message(format!(
"Unable to deserialize `{}`",
self.de.typ
))),
}
}
None => Err(VmError::Message(format!(
"Unable to deserialize `{}`",
self.de.typ
))),
}
},
_ => Err(VmError::Message(format!(
"Unable to deserialize `{}`",
self.de.typ
))),
}
}
}
17 changes: 12 additions & 5 deletions vm/src/api/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ use gluon::vm::api::ser::Ser;
enum Enum {
A(i32),
B(String, i32),
C { foo: f64, bar: i32 },
}
impl VmType for Enum {
Expand All @@ -113,12 +114,13 @@ impl VmType for Enum {
let thread = new_vm();
let expr = r#"
type Enum = | A Int | B String Int
type Enum = | A Int | B String Int | C { foo : Float, bar : Int }
let f e =
match e with
| A a -> a
| B b c -> c
| C { foo, bar } -> bar
{ Enum, f }
"#;
Expand All @@ -131,8 +133,10 @@ let mut f: FunctionRef<fn (Ser<Enum>) -> i32> = thread
.get_global("test.f")
.unwrap_or_else(|err| panic!("{}", err));
let result = f.call(Ser(Enum::B("".to_string(), 4))).unwrap_or_else(|err| panic!("{}", err));
assert_eq!(result, 4);
let result1 = f.call(Ser(Enum::B("".to_string(), 4))).unwrap_or_else(|err| panic!("{}", err));
assert_eq!(result1, 4);
let result2 = f.call(Ser(Enum::C{foo: 3.14, bar: 10})).unwrap_or_else(|err| panic!("{}", err));
assert_eq!(result2, 10);
# }
```
Expand Down Expand Up @@ -559,17 +563,20 @@ impl<'s, 'a, 'vm> ser::SerializeStructVariant for RecordSerializer<'s, 'a, 'vm>
type Ok = ();
type Error = Error;

fn serialize_field<T>(&mut self, _key: &'static str, value: &T) -> Result<()>
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
let field = self.thread.global_env().intern(key)?;
self.fields.push(field);
value.serialize(&mut **self)?;
self.values += 1;
Ok(())
}

fn end(self) -> Result<Self::Ok> {
self.serializer.alloc(self.variant_index, self.values)
self.serializer.alloc_record(&self.fields, self.values)?;
self.serializer.alloc(self.variant_index, 1)
}
}

Expand Down
17 changes: 16 additions & 1 deletion vm/src/api/typ.rs
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,18 @@ impl<'de, 'a> VariantAccess<'de> for Enum<'a, 'de> {
where
V: Visitor<'de>,
{
self.tuple_variant(fields.len(), visitor)
let (value, types) = {
let mut map_deserializer = MapDeserializer::new(&mut *self.de, fields.iter().cloned());
(
visitor.visit_map(&mut map_deserializer)?,
map_deserializer.types,
)
};
self.de.variant = Some(Field::ctor(
self.de.state.symbols.symbol(self.variant),
vec![self.de.state.cache.record(vec![], types)],
));
Ok(value)
}
}

Expand Down Expand Up @@ -599,6 +610,7 @@ mod tests {
A,
B(i32),
C(String, f64),
D { foo: i32 },
}

#[test]
Expand All @@ -613,6 +625,9 @@ mod tests {
Field::ctor(symbols.symbol("A"), vec![]),
Field::ctor(symbols.symbol("B"), vec![Type::int()]),
Field::ctor(symbols.symbol("C"), vec![Type::string(), Type::float()],),
Field::ctor(symbols.symbol("D"), vec![
Type::record(vec![], vec![Field::new(symbols.symbol("foo"), Type::int())])
]),
])
);
}
Expand Down

0 comments on commit afb682e

Please sign in to comment.