diff --git a/crates/stef-build/src/definition.rs b/crates/stef-build/src/definition.rs new file mode 100644 index 0000000..4fddcbb --- /dev/null +++ b/crates/stef-build/src/definition.rs @@ -0,0 +1,452 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{quote, ToTokens}; +use stef_parser::{ + Comment, Const, DataType, Definition, Enum, ExternalType, Fields, Generics, Import, Literal, + Module, NamedField, Schema, Struct, TypeAlias, UnnamedField, Variant, +}; + +pub(crate) fn compile_schema(Schema { definitions }: &Schema<'_>) -> TokenStream { + let definitions = definitions.iter().map(compile_definition); + + quote! { #(#definitions)* } +} + +fn compile_definition(definition: &Definition<'_>) -> TokenStream { + let definition = match definition { + Definition::Module(m) => compile_module(m), + Definition::Struct(s) => compile_struct(s), + Definition::Enum(e) => compile_enum(e), + Definition::TypeAlias(a) => compile_alias(a), + Definition::Const(c) => compile_const(c), + Definition::Import(i) => compile_import(i), + }; + + quote! { #definition } +} + +fn compile_module( + Module { + comment, + name, + definitions, + }: &Module<'_>, +) -> TokenStream { + let comment = compile_comment(comment); + let name = Ident::new(name, Span::call_site()); + let definitions = definitions.iter().map(compile_definition); + + quote! { + #comment + pub mod #name { + #(#definitions)* + } + } +} + +fn compile_struct( + Struct { + comment, + attributes: _, + name, + generics, + fields, + }: &Struct<'_>, +) -> TokenStream { + let comment = compile_comment(comment); + let name = Ident::new(name, Span::call_site()); + let generics = compile_generics(generics); + let fields = compile_fields(fields, true); + + quote! { + #comment + pub struct #name #generics #fields + } +} + +fn compile_enum( + Enum { + comment, + attributes: _, + name, + generics, + variants, + }: &Enum<'_>, +) -> TokenStream { + let comment = compile_comment(comment); + let name = Ident::new(name, Span::call_site()); + let generics = compile_generics(generics); + let variants = variants.iter().map(compile_variant); + + quote! { + #comment + pub enum #name #generics { + #(#variants,)* + } + } +} + +fn compile_variant( + Variant { + comment, + name, + fields, + id: _, + }: &Variant<'_>, +) -> TokenStream { + let comment = compile_comment(comment); + let name = Ident::new(name, Span::call_site()); + let fields = compile_fields(fields, false); + + quote! { + #comment + #name #fields + } +} + +fn compile_alias( + TypeAlias { + comment, + alias, + target, + }: &TypeAlias<'_>, +) -> TokenStream { + let comment = compile_comment(comment); + let alias = compile_data_type(alias); + let target = compile_data_type(target); + + quote! { + #comment + pub type #alias = #target; + } +} + +fn compile_const( + Const { + comment, + name, + ty, + value, + }: &Const<'_>, +) -> TokenStream { + let comment = compile_comment(comment); + let name = Ident::new(name, Span::call_site()); + let ty = compile_const_data_type(ty); + let value = compile_literal(value); + + quote! { + #comment + const #name: #ty = #value; + } +} + +fn compile_import(Import { segments, element }: &Import<'_>) -> TokenStream { + let segments = segments.iter().enumerate().map(|(i, segment)| { + let segment = Ident::new(segment, Span::call_site()); + if i > 0 { + quote! {::#segment} + } else { + quote! {#segment} + } + }); + let element = element.map(|element| { + let element = Ident::new(element, Span::call_site()); + quote! { ::#element} + }); + + quote! { + use #(#segments)*#element; + } +} + +fn compile_comment(Comment(lines): &Comment<'_>) -> TokenStream { + let lines = lines.iter().map(|line| format!(" {line}")); + quote! { #(#[doc = #lines])* } +} + +fn compile_generics(Generics(types): &Generics<'_>) -> Option { + (!types.is_empty()).then(|| quote! { <#(#types,)*> }) +} + +fn compile_fields(fields: &Fields<'_>, public: bool) -> TokenStream { + match fields { + Fields::Named(named) => { + let fields = named.iter().map( + |NamedField { + comment, + name, + ty, + id: _, + }| { + let comment = compile_comment(comment); + let public = public.then(|| quote! { pub }); + let name = Ident::new(name, Span::call_site()); + let ty = compile_data_type(ty); + quote! { + #comment + #public #name: #ty + } + }, + ); + + quote! { { + #(#fields,)* + } } + } + Fields::Unnamed(unnamed) => { + let fields = unnamed.iter().map(|UnnamedField { ty, id: _ }| { + let ty = compile_data_type(ty); + quote! { #ty } + }); + + quote! { (#(#fields,)*) } + } + Fields::Unit => quote! {}, + } +} + +fn compile_data_type(ty: &DataType<'_>) -> TokenStream { + match ty { + DataType::Bool => quote! { bool }, + DataType::U8 => quote! { u8 }, + DataType::U16 => quote! { u16 }, + DataType::U32 => quote! { u32 }, + DataType::U64 => quote! { u64 }, + DataType::U128 => quote! { u128 }, + DataType::I8 => quote! { i8 }, + DataType::I16 => quote! { i16 }, + DataType::I32 => quote! { i32 }, + DataType::I64 => quote! { i64 }, + DataType::I128 => quote! { i128 }, + DataType::F32 => quote! { f32 }, + DataType::F64 => quote! { f64 }, + DataType::String | DataType::StringRef => quote! { String }, + DataType::Bytes | DataType::BytesRef => quote! { Vec }, + DataType::Vec(ty) => { + let ty = compile_data_type(ty); + quote! { Vec<#ty> } + } + DataType::HashMap(kv) => { + let k = compile_data_type(&kv.0); + let v = compile_data_type(&kv.1); + quote! { HashMap<#k, #v> } + } + DataType::HashSet(ty) => { + let ty = compile_data_type(ty); + quote! { HashSet<#ty> } + } + DataType::Option(ty) => { + let ty = compile_data_type(ty); + quote! { Option<#ty> } + } + DataType::NonZero(ty) => match **ty { + DataType::U8 => quote! { NonZeroU8 }, + DataType::U16 => quote! { NonZeroU16 }, + DataType::U32 => quote! { NonZeroU32 }, + DataType::U64 => quote! { NonZeroU64 }, + DataType::U128 => quote! { NonZeroU128 }, + DataType::I8 => quote! { NonZeroI8 }, + DataType::I16 => quote! { NonZeroI16 }, + DataType::I32 => quote! { NonZeroI32 }, + DataType::I64 => quote! { NonZeroI64 }, + DataType::I128 => quote! { NonZeroI128 }, + _ => compile_data_type(ty), + }, + DataType::BoxString => quote! { Box }, + DataType::BoxBytes => quote! { Box<[u8]> }, + DataType::Tuple(types) => { + let types = types.iter().map(compile_data_type); + quote! { (#(#types,)*) } + } + DataType::Array(ty, size) => { + let ty = compile_data_type(ty); + let size = proc_macro2::Literal::u32_unsuffixed(*size); + quote! { [#ty; #size] } + } + DataType::External(ExternalType { + path, + name, + generics, + }) => { + let name = Ident::new(name, Span::call_site()); + let generics = (!generics.is_empty()).then(|| { + let types = generics.iter().map(compile_data_type); + quote! { <#(#types,)*> } + }); + + quote! { + #(#path::)* #name #generics + } + } + } +} + +fn compile_const_data_type(ty: &DataType<'_>) -> TokenStream { + match ty { + DataType::Bool => quote! { bool }, + DataType::U8 => quote! { u8 }, + DataType::U16 => quote! { u16 }, + DataType::U32 => quote! { u32 }, + DataType::U64 => quote! { u64 }, + DataType::U128 => quote! { u128 }, + DataType::I8 => quote! { i8 }, + DataType::I16 => quote! { i16 }, + DataType::I32 => quote! { i32 }, + DataType::I64 => quote! { i64 }, + DataType::I128 => quote! { i128 }, + DataType::F32 => quote! { f32 }, + DataType::F64 => quote! { f64 }, + DataType::String | DataType::StringRef => quote! { &str }, + DataType::Bytes | DataType::BytesRef => quote! { &[u8] }, + _ => panic!("invalid data type for const"), + } +} + +fn compile_literal(literal: &Literal) -> TokenStream { + match literal { + Literal::Bool(b) => quote! { #b }, + Literal::Int(i) => proc_macro2::Literal::i128_unsuffixed(*i).into_token_stream(), + Literal::Float(f) => proc_macro2::Literal::f64_unsuffixed(*f).into_token_stream(), + Literal::String(s) => proc_macro2::Literal::string(s).into_token_stream(), + Literal::Bytes(b) => proc_macro2::Literal::byte_string(b).into_token_stream(), + } +} + +#[cfg(test)] +mod tests { + use indoc::indoc; + use pretty_assertions::assert_eq; + + use super::*; + + fn parse(input: &str, expect: &str) { + let parsed = Schema::parse(input).unwrap(); + println!("==========\n{parsed}"); + + let compiled = compile_schema(&parsed); + println!("----------\n{compiled}"); + + let pretty = prettyplease::unparse(&syn::parse2(compiled.clone()).unwrap()); + println!("----------\n{pretty}=========="); + + assert_eq!(expect, pretty); + } + + #[test] + fn basic_module() { + let input = indoc! {r#" + /// Hello world! + mod sample {} + "#}; + let expect = indoc! {r#" + /// Hello world! + pub mod sample {} + "#}; + + parse(input, expect); + } + + #[test] + fn basic_struct() { + let input = indoc! {r#" + /// Hello world! + struct Sample { + field1: u32 @1, + field2: bytes @2, + field3: (bool, [i16; 4]) @3, + } + "#}; + let expect = indoc! {r#" + /// Hello world! + pub struct Sample { + pub field1: u32, + pub field2: Vec, + pub field3: (bool, [i16; 4]), + } + "#}; + + parse(input, expect); + } + + #[test] + fn basic_enum() { + let input = indoc! {r#" + /// Hello world! + enum Sample { + Variant1 @1, + Variant2(u32 @1, u8 @2) @2, + Variant3 { + field1: string @1, + field2: vec @2, + } @3, + } + "#}; + let expect = indoc! {r#" + /// Hello world! + pub enum Sample { + Variant1, + Variant2(u32, u8), + Variant3 { field1: String, field2: Vec }, + } + "#}; + + parse(input, expect); + } + + #[test] + fn basic_alias() { + let input = indoc! {r#" + /// Hello world! + type Sample = String; + "#}; + let expect = indoc! {r#" + /// Hello world! + pub type Sample = String; + "#}; + + parse(input, expect); + } + + #[test] + fn basic_const() { + let input = indoc! {r#" + /// A bool. + const BOOL: bool = true; + /// An integer. + const INT: u32 = 100; + /// A float. + const FLOAT: f64 = 5.0; + /// A string. + const STRING: string = "hello"; + /// Some bytes. + const BYTES: bytes = [1, 2, 3]; + "#}; + let expect = indoc! {r#" + /// A bool. + const BOOL: bool = true; + /// An integer. + const INT: u32 = 100; + /// A float. + const FLOAT: f64 = 5.0; + /// A string. + const STRING: &str = "hello"; + /// Some bytes. + const BYTES: &[u8] = b"\x01\x02\x03"; + "#}; + + parse(input, expect); + } + + #[test] + fn basic_import() { + let input = indoc! {r#" + use other::module; + use other::module::Type; + "#}; + let expect = indoc! {r#" + use other::module; + use other::module::Type; + "#}; + + parse(input, expect); + } +} diff --git a/crates/stef-build/src/encode.rs b/crates/stef-build/src/encode.rs new file mode 100644 index 0000000..6db57f1 --- /dev/null +++ b/crates/stef-build/src/encode.rs @@ -0,0 +1,365 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{quote, ToTokens}; +use stef_parser::{ + DataType, Definition, Enum, ExternalType, Fields, Module, NamedField, Schema, Struct, + UnnamedField, Variant, +}; + +pub(crate) fn compile_schema(Schema { definitions }: &Schema<'_>) -> TokenStream { + let definintions = definitions.iter().map(compile_definition); + + quote! { #(#definintions)* } +} + +fn compile_definition(definition: &Definition<'_>) -> TokenStream { + let definition = match definition { + Definition::Module(m) => compile_module(m), + Definition::Struct(s) => compile_struct(s), + Definition::Enum(e) => compile_enum(e), + Definition::TypeAlias(_) | Definition::Const(_) | Definition::Import(_) => quote! {}, + }; + + quote! { #definition } +} + +fn compile_module( + Module { + comment: _, + name, + definitions, + }: &Module<'_>, +) -> TokenStream { + let name = Ident::new(name, Span::call_site()); + let definitions = definitions.iter().map(compile_definition); + + quote! { + pub mod #name { + #(#definitions)* + } + } +} + +fn compile_struct( + Struct { + comment: _, + attributes: _, + name, + generics: _, + fields, + }: &Struct<'_>, +) -> TokenStream { + let name = Ident::new(name, Span::call_site()); + let fields = compile_struct_fields(fields); + + quote! { + impl ::stef::Encode for #name { + fn encode(&self, w: &mut impl ::stef::BufMut) { + #fields + } + } + } +} + +fn compile_struct_fields(fields: &Fields<'_>) -> TokenStream { + match fields { + Fields::Named(named) => { + let calls = named.iter().map( + |NamedField { + comment: _, + name, + ty, + id, + }| { + let id = proc_macro2::Literal::u32_unsuffixed(id.0); + let name = proc_macro2::Ident::new(name, Span::call_site()); + let ty = compile_data_type(ty, quote! { self.#name }); + + quote! { ::stef::write_field(w, #id, |w| { #ty }); } + }, + ); + + quote! { #(#calls)* } + } + Fields::Unnamed(unnamed) => { + let calls = unnamed + .iter() + .enumerate() + .map(|(idx, UnnamedField { ty, id })| { + let id = proc_macro2::Literal::u32_unsuffixed(id.0); + let idx = proc_macro2::Literal::usize_unsuffixed(idx); + let ty = compile_data_type(ty, quote! { self.#idx }); + + quote! { ::stef::write_field(w, #id, |w| { #ty }); } + }); + + quote! { #(#calls)* } + } + Fields::Unit => quote! {}, + } +} + +fn compile_enum( + Enum { + comment: _, + attributes: _, + name, + generics: _, + variants, + }: &Enum<'_>, +) -> TokenStream { + let name = Ident::new(name, Span::call_site()); + let variants = variants.iter().map(compile_variant); + + quote! { + impl ::stef::Encode for #name { + fn encode(&self, w: &mut impl ::stef::BufMut) { + match self { + #(#variants,)* + } + } + } + } +} + +fn compile_variant( + Variant { + comment: _, + name, + fields, + id, + }: &Variant<'_>, +) -> TokenStream { + let id = proc_macro2::Literal::u32_unsuffixed(id.0); + let name = Ident::new(name, Span::call_site()); + let fields_body = compile_variant_fields(fields); + + match fields { + Fields::Named(named) => { + let field_names = named + .iter() + .map(|NamedField { name, .. }| Ident::new(name, Span::call_site())); + + quote! { + Self::#name{ #(#field_names,)* } => { + ::stef::write_id(w, #id); + #fields_body + } + } + } + Fields::Unnamed(unnamed) => { + let field_names = unnamed + .iter() + .enumerate() + .map(|(idx, _)| Ident::new(&format!("unnamed_{idx}"), Span::call_site())); + + quote! { + Self::#name(#(#field_names,)*) => { + ::stef::write_id(w, #id); + #fields_body + } + } + } + Fields::Unit => quote! { + Self::#name => { + ::stef::write_id(w, #id); + #fields_body + } + }, + } +} + +fn compile_variant_fields(fields: &Fields<'_>) -> TokenStream { + match fields { + Fields::Named(named) => { + let calls = named.iter().map( + |NamedField { + comment: _, + name, + ty, + id, + }| { + let id = proc_macro2::Literal::u32_unsuffixed(id.0); + let name = proc_macro2::Ident::new(name, Span::call_site()); + let ty = compile_data_type(ty, quote! { #name }); + + quote! { ::stef::write_field(w, #id, |w| { #ty }); } + }, + ); + + quote! { #(#calls)* } + } + Fields::Unnamed(unnamed) => { + let calls = unnamed + .iter() + .enumerate() + .map(|(idx, UnnamedField { ty, id })| { + let id = proc_macro2::Literal::u32_unsuffixed(id.0); + let name = Ident::new(&format!("unnamed_{idx}"), Span::call_site()); + let ty = compile_data_type(ty, name.to_token_stream()); + + quote! { ::stef::write_field(w, #id, |w| { #ty }); } + }); + + quote! { #(#calls)* } + } + Fields::Unit => quote! {}, + } +} + +fn compile_data_type(ty: &DataType<'_>, name: TokenStream) -> TokenStream { + match ty { + DataType::Bool => quote! { ::stef::encode_bool(w, #name) }, + DataType::U8 => quote! { ::stef::encode_u8(w, #name) }, + DataType::U16 => quote! { ::stef::encode_u16(w, #name) }, + DataType::U32 => quote! { ::stef::encode_u32(w, #name) }, + DataType::U64 => quote! { ::stef::encode_u64(w, #name) }, + DataType::U128 => quote! { ::stef::encode_u128(w, #name) }, + DataType::I8 => quote! { ::stef::encode_i8(w, #name) }, + DataType::I16 => quote! { ::stef::encode_i16(w, #name) }, + DataType::I32 => quote! { ::stef::encode_i32(w, #name) }, + DataType::I64 => quote! { ::stef::encode_i64(w, #name) }, + DataType::I128 => quote! { ::stef::encode_i128(w, #name) }, + DataType::F32 => quote! { ::stef::encode_f32(w, #name) }, + DataType::F64 => quote! { ::stef::encode_f64(w, #name) }, + DataType::String | DataType::StringRef => quote! { ::stef::encode_string(w, &#name) }, + DataType::Bytes | DataType::BytesRef => quote! { ::stef::encode_bytes(w, &#name) }, + DataType::Vec(_ty) => quote! { ::stef::encode_vec(w, &#name) }, + DataType::HashMap(_kv) => quote! { ::stef::encode_hash_map(w, #name) }, + DataType::HashSet(_ty) => quote! { ::stef::encode_hash_set(w, #name) }, + DataType::Option(_ty) => quote! { ::stef::encode_option(w, #name) }, + DataType::NonZero(ty) => match **ty { + DataType::U8 => quote! { ::stef::encode_u8(w, #name.get()) }, + DataType::U16 => quote! { ::stef::encode_u16(w, #name.get()) }, + DataType::U32 => quote! { ::stef::encode_u32(w, #name.get()) }, + DataType::U64 => quote! { ::stef::encode_u64(w, #name.get()) }, + DataType::U128 => quote! { ::stef::encode_u128(w, #name.get()) }, + DataType::I8 => quote! { ::stef::encode_i8(w, #name.get()) }, + DataType::I16 => quote! { ::stef::encode_i16(w, #name.get()) }, + DataType::I32 => quote! { ::stef::encode_i32(w, #name.get()) }, + DataType::I64 => quote! { ::stef::encode_i64(w, #name.get()) }, + DataType::I128 => quote! { ::stef::encode_i128(w, #name.get()) }, + _ => compile_data_type(ty, name), + }, + DataType::BoxString => quote! { ::stef::encode_string(w, &*#name) }, + DataType::BoxBytes => quote! { ::stef::encode_bytes(w, &*#name) }, + DataType::Tuple(types) => { + let calls = types.iter().enumerate().map(|(idx, ty)| { + let idx = proc_macro2::Literal::usize_unsuffixed(idx); + compile_data_type(ty, quote! { #name.#idx }) + }); + quote! { #(#calls;)* } + } + DataType::Array(_ty, _size) => { + quote! { ::stef::encode_array(w, &#name) } + } + DataType::External(ExternalType { + path: _, + name: _, + generics: _, + }) => { + quote! { #name.encode(w) } + } + } +} + +#[cfg(test)] +mod tests { + use indoc::indoc; + use pretty_assertions::assert_eq; + + use super::*; + + fn parse(input: &str, expect: &str) { + let parsed = Schema::parse(input).unwrap(); + println!("==========\n{parsed}"); + + let compiled = compile_schema(&parsed); + println!("----------\n{compiled}"); + + let pretty = prettyplease::unparse(&syn::parse2(compiled.clone()).unwrap()); + println!("----------\n{pretty}=========="); + + assert_eq!(expect, pretty); + } + + #[test] + fn basic_module() { + let input = indoc! {r#" + /// Hello world! + mod sample {} + "#}; + let expect = indoc! {r#" + pub mod sample {} + "#}; + + parse(input, expect); + } + + #[test] + fn basic_struct() { + let input = indoc! {r#" + /// Hello world! + struct Sample { + field1: u32 @1, + field2: bytes @2, + field3: (bool, [i16; 4]) @3, + } + "#}; + let expect = indoc! {r#" + impl ::stef::Encode for Sample { + fn encode(&self, w: &mut impl ::stef::BufMut) { + ::stef::write_field(w, 1, |w| { ::stef::encode_u32(w, self.field1) }); + ::stef::write_field(w, 2, |w| { ::stef::encode_bytes(w, &self.field2) }); + ::stef::write_field( + w, + 3, + |w| { + ::stef::encode_bool(w, self.field3.0); + ::stef::encode_array(w, &self.field3.1); + }, + ); + } + } + "#}; + + parse(input, expect); + } + + #[test] + fn basic_enum() { + let input = indoc! {r#" + /// Hello world! + enum Sample { + Variant1 @1, + Variant2(u32 @1, u8 @2) @2, + Variant3 { + field1: string @1, + field2: vec @2, + } @3, + } + "#}; + let expect = indoc! {r#" + impl ::stef::Encode for Sample { + fn encode(&self, w: &mut impl ::stef::BufMut) { + match self { + Self::Variant1 => { + ::stef::write_id(w, 1); + } + Self::Variant2(unnamed_0, unnamed_1) => { + ::stef::write_id(w, 2); + ::stef::write_field(w, 1, |w| { ::stef::encode_u32(w, unnamed_0) }); + ::stef::write_field(w, 2, |w| { ::stef::encode_u8(w, unnamed_1) }); + } + Self::Variant3 { field1, field2 } => { + ::stef::write_id(w, 3); + ::stef::write_field(w, 1, |w| { ::stef::encode_string(w, &field1) }); + ::stef::write_field(w, 2, |w| { ::stef::encode_vec(w, &field2) }); + } + } + } + } + "#}; + + parse(input, expect); + } +} diff --git a/crates/stef-build/src/lib.rs b/crates/stef-build/src/lib.rs index 3734f9d..556bb47 100644 --- a/crates/stef-build/src/lib.rs +++ b/crates/stef-build/src/lib.rs @@ -3,14 +3,13 @@ use std::path::{Path, PathBuf}; -use proc_macro2::{Ident, Span, TokenStream}; -use quote::{quote, ToTokens}; -use stef_parser::{ - Comment, Const, DataType, Definition, Enum, ExternalType, Fields, Generics, Import, Literal, - Module, NamedField, Schema, Struct, TypeAlias, UnnamedField, Variant, -}; +use quote::quote; +use stef_parser::Schema; use thiserror::Error; +mod definition; +mod encode; + type Result = std::result::Result; #[derive(Debug, Error)] @@ -31,456 +30,17 @@ pub fn compile(schemas: &[impl AsRef], _includes: &[impl AsRef]) -> })?; let schema = Schema::parse(&input).unwrap(); - let code = compile_schema(&schema); - - println!("{code}"); - } - - Ok(()) -} - -fn compile_schema(Schema { definitions }: &Schema<'_>) -> TokenStream { - let definitions = definitions.iter().map(compile_definition); - - quote! { #(#definitions)* } -} - -fn compile_definition(definition: &Definition<'_>) -> TokenStream { - let definition = match definition { - Definition::Module(m) => compile_module(m), - Definition::Struct(s) => compile_struct(s), - Definition::Enum(e) => compile_enum(e), - Definition::TypeAlias(a) => compile_alias(a), - Definition::Const(c) => compile_const(c), - Definition::Import(i) => compile_import(i), - }; - - quote! { #definition } -} - -fn compile_module( - Module { - comment, - name, - definitions, - }: &Module<'_>, -) -> TokenStream { - let comment = compile_comment(comment); - let name = Ident::new(name, Span::call_site()); - let definitions = definitions.iter().map(compile_definition); - - quote! { - #comment - pub mod #name { - #(#definitions)* - } - } -} - -fn compile_struct( - Struct { - comment, - attributes: _, - name, - generics, - fields, - }: &Struct<'_>, -) -> TokenStream { - let comment = compile_comment(comment); - let name = Ident::new(name, Span::call_site()); - let generics = compile_generics(generics); - let fields = compile_fields(fields, true); - - quote! { - #comment - pub struct #name #generics #fields - } -} - -fn compile_enum( - Enum { - comment, - attributes: _, - name, - generics, - variants, - }: &Enum<'_>, -) -> TokenStream { - let comment = compile_comment(comment); - let name = Ident::new(name, Span::call_site()); - let generics = compile_generics(generics); - let variants = variants.iter().map(compile_variant); - - quote! { - #comment - pub enum #name #generics { - #(#variants,)* - } - } -} - -fn compile_variant( - Variant { - comment, - name, - fields, - id: _, - }: &Variant<'_>, -) -> TokenStream { - let comment = compile_comment(comment); - let name = Ident::new(name, Span::call_site()); - let fields = compile_fields(fields, false); - - quote! { - #comment - #name #fields - } -} - -fn compile_alias( - TypeAlias { - comment, - alias, - target, - }: &TypeAlias<'_>, -) -> TokenStream { - let comment = compile_comment(comment); - let alias = compile_data_type(alias); - let target = compile_data_type(target); - - quote! { - #comment - pub type #alias = #target; - } -} - -fn compile_const( - Const { - comment, - name, - ty, - value, - }: &Const<'_>, -) -> TokenStream { - let comment = compile_comment(comment); - let name = Ident::new(name, Span::call_site()); - let ty = compile_const_data_type(ty); - let value = compile_literal(value); - - quote! { - #comment - const #name: #ty = #value; - } -} - -fn compile_import(Import { segments, element }: &Import<'_>) -> TokenStream { - let segments = segments.iter().enumerate().map(|(i, segment)| { - let segment = Ident::new(segment, Span::call_site()); - if i > 0 { - quote! {::#segment} - } else { - quote! {#segment} - } - }); - let element = element.map(|element| { - let element = Ident::new(element, Span::call_site()); - quote! { ::#element} - }); - - quote! { - use #(#segments)*#element; - } -} - -fn compile_comment(Comment(lines): &Comment<'_>) -> TokenStream { - let lines = lines.iter().map(|line| format!(" {line}")); - quote! { #(#[doc = #lines])* } -} - -fn compile_generics(Generics(types): &Generics<'_>) -> Option { - (!types.is_empty()).then(|| quote! { <#(#types,)*> }) -} - -fn compile_fields(fields: &Fields<'_>, public: bool) -> TokenStream { - match fields { - Fields::Named(named) => { - let fields = named.iter().map( - |NamedField { - comment, - name, - ty, - id: _, - }| { - let comment = compile_comment(comment); - let public = public.then(|| quote! { pub }); - let name = Ident::new(name, Span::call_site()); - let ty = compile_data_type(ty); - quote! { - #comment - #public #name: #ty - } - }, - ); - - quote! { { - #(#fields,)* - } } - } - Fields::Unnamed(unnamed) => { - let fields = unnamed.iter().map(|UnnamedField { ty, id: _ }| { - let ty = compile_data_type(ty); - quote! { #ty } - }); - - quote! { (#(#fields,)*) } - } - Fields::Unit => quote! {}, - } -} - -fn compile_data_type(ty: &DataType<'_>) -> TokenStream { - match ty { - DataType::Bool => quote! { bool }, - DataType::U8 => quote! { u8 }, - DataType::U16 => quote! { u16 }, - DataType::U32 => quote! { u32 }, - DataType::U64 => quote! { u64 }, - DataType::U128 => quote! { u128 }, - DataType::I8 => quote! { i8 }, - DataType::I16 => quote! { i16 }, - DataType::I32 => quote! { i32 }, - DataType::I64 => quote! { i64 }, - DataType::I128 => quote! { i128 }, - DataType::F32 => quote! { f32 }, - DataType::F64 => quote! { f64 }, - DataType::String | DataType::StringRef => quote! { String }, - DataType::Bytes | DataType::BytesRef => quote! { Vec }, - DataType::Vec(ty) => { - let ty = compile_data_type(ty); - quote! { Vec<#ty> } - } - DataType::HashMap(kv) => { - let k = compile_data_type(&kv.0); - let v = compile_data_type(&kv.1); - quote! { HashMap<#k, #v> } - } - DataType::HashSet(ty) => { - let ty = compile_data_type(ty); - quote! { HashSet<#ty> } - } - DataType::Option(ty) => { - let ty = compile_data_type(ty); - quote! { Option<#ty> } - } - DataType::NonZero(ty) => match **ty { - DataType::U8 => quote! { NonZeroU8 }, - DataType::U16 => quote! { NonZeroU16 }, - DataType::U32 => quote! { NonZeroU32 }, - DataType::U64 => quote! { NonZeroU64 }, - DataType::U128 => quote! { NonZeroU128 }, - DataType::I8 => quote! { NonZeroI8 }, - DataType::I16 => quote! { NonZeroI16 }, - DataType::I32 => quote! { NonZeroI32 }, - DataType::I64 => quote! { NonZeroI64 }, - DataType::I128 => quote! { NonZeroI128 }, - _ => compile_data_type(ty), - }, - DataType::BoxString => quote! { Box }, - DataType::BoxBytes => quote! { Box<[u8]> }, - DataType::Tuple(types) => { - let types = types.iter().map(compile_data_type); - quote! { (#(#types,)*) } - } - DataType::Array(ty, size) => { - let ty = compile_data_type(ty); - let size = proc_macro2::Literal::u32_unsuffixed(*size); - quote! { [#ty; #size] } - } - DataType::External(ExternalType { - path, - name, - generics, - }) => { - let name = Ident::new(name, Span::call_site()); - let generics = (!generics.is_empty()).then(|| { - let types = generics.iter().map(compile_data_type); - quote! { <#(#types,)*> } - }); + let definition = definition::compile_schema(&schema); + let encode = encode::compile_schema(&schema); + println!( + "{}", quote! { - #(#path::)* #name #generics + #definition + #encode } - } - } -} - -fn compile_const_data_type(ty: &DataType<'_>) -> TokenStream { - match ty { - DataType::Bool => quote! { bool }, - DataType::U8 => quote! { u8 }, - DataType::U16 => quote! { u16 }, - DataType::U32 => quote! { u32 }, - DataType::U64 => quote! { u64 }, - DataType::U128 => quote! { u128 }, - DataType::I8 => quote! { i8 }, - DataType::I16 => quote! { i16 }, - DataType::I32 => quote! { i32 }, - DataType::I64 => quote! { i64 }, - DataType::I128 => quote! { i128 }, - DataType::F32 => quote! { f32 }, - DataType::F64 => quote! { f64 }, - DataType::String | DataType::StringRef => quote! { &str }, - DataType::Bytes | DataType::BytesRef => quote! { &[u8] }, - _ => panic!("invalid data type for const"), - } -} - -fn compile_literal(literal: &Literal) -> TokenStream { - match literal { - Literal::Bool(b) => quote! { #b }, - Literal::Int(i) => proc_macro2::Literal::i128_unsuffixed(*i).into_token_stream(), - Literal::Float(f) => proc_macro2::Literal::f64_unsuffixed(*f).into_token_stream(), - Literal::String(s) => proc_macro2::Literal::string(s).into_token_stream(), - Literal::Bytes(b) => proc_macro2::Literal::byte_string(b).into_token_stream(), + ); } -} - -#[cfg(test)] -mod tests { - use indoc::indoc; - use pretty_assertions::assert_eq; - - use super::*; - - fn parse(input: &str, expect: &str) { - let parsed = Schema::parse(input).unwrap(); - println!("==========\n{parsed}"); - - let compiled = compile_schema(&parsed); - println!("----------\n{compiled}"); - - let pretty = prettyplease::unparse(&syn::parse2(compiled.clone()).unwrap()); - println!("----------\n{pretty}=========="); - - assert_eq!(expect, pretty); - } - - #[test] - fn basic_module() { - let input = indoc! {r#" - /// Hello world! - mod sample {} - "#}; - let expect = indoc! {r#" - /// Hello world! - pub mod sample {} - "#}; - parse(input, expect); - } - - #[test] - fn basic_struct() { - let input = indoc! {r#" - /// Hello world! - struct Sample { - field1: u32 @1, - field2: bytes @2, - field3: (bool, [i16; 4]) @3, - } - "#}; - let expect = indoc! {r#" - /// Hello world! - pub struct Sample { - pub field1: u32, - pub field2: Vec, - pub field3: (bool, [i16; 4]), - } - "#}; - - parse(input, expect); - } - - #[test] - fn basic_enum() { - let input = indoc! {r#" - /// Hello world! - enum Sample { - Variant1 @1, - Variant2(u32 @1, u8 @2) @2, - Variant3 { - field1: string @1, - field2: Vec @2, - } @3, - } - "#}; - let expect = indoc! {r#" - /// Hello world! - pub enum Sample { - Variant1, - Variant2(u32, u8), - Variant3 { field1: String, field2: Vec }, - } - "#}; - - parse(input, expect); - } - - #[test] - fn basic_alias() { - let input = indoc! {r#" - /// Hello world! - type Sample = String; - "#}; - let expect = indoc! {r#" - /// Hello world! - pub type Sample = String; - "#}; - - parse(input, expect); - } - - #[test] - fn basic_const() { - let input = indoc! {r#" - /// A bool. - const BOOL: bool = true; - /// An integer. - const INT: u32 = 100; - /// A float. - const FLOAT: f64 = 5.0; - /// A string. - const STRING: string = "hello"; - /// Some bytes. - const BYTES: bytes = [1, 2, 3]; - "#}; - let expect = indoc! {r#" - /// A bool. - const BOOL: bool = true; - /// An integer. - const INT: u32 = 100; - /// A float. - const FLOAT: f64 = 5.0; - /// A string. - const STRING: &str = "hello"; - /// Some bytes. - const BYTES: &[u8] = b"\x01\x02\x03"; - "#}; - - parse(input, expect); - } - - #[test] - fn basic_import() { - let input = indoc! {r#" - use other::module; - use other::module::Type; - "#}; - let expect = indoc! {r#" - use other::module; - use other::module::Type; - "#}; - - parse(input, expect); - } + Ok(()) } diff --git a/crates/stef/src/lib.rs b/crates/stef/src/lib.rs index b3a108e..d437ba9 100644 --- a/crates/stef/src/lib.rs +++ b/crates/stef/src/lib.rs @@ -6,7 +6,7 @@ use std::{ }, }; -use bytes::BufMut; +pub use bytes::BufMut; pub mod varint; @@ -21,6 +21,7 @@ pub fn encode_u8(w: &mut impl BufMut, value: u8) { pub fn encode_i8(w: &mut impl BufMut, value: i8) { w.put_i8(value); } + macro_rules! encode_int { ($ty:ty) => { paste::paste! { @@ -57,11 +58,11 @@ pub fn write_discr(w: &mut impl BufMut, discr: u32) { w.put(&buf[..len]); } -pub fn write_string(w: &mut impl BufMut, value: &str) { - write_bytes(w, value.as_bytes()); +pub fn encode_string(w: &mut impl BufMut, value: &str) { + encode_bytes(w, value.as_bytes()); } -pub fn write_bytes(w: &mut impl BufMut, value: &[u8]) { +pub fn encode_bytes(w: &mut impl BufMut, value: &[u8]) { let (buf, len) = varint::encode_u64(value.len() as u64); w.put(&buf[..len]); @@ -72,7 +73,42 @@ pub trait Encode { fn encode(&self, w: &mut impl BufMut); } -pub fn write_vec(w: &mut impl BufMut, vec: &Vec) { +macro_rules! forward { + ($ty:ty) => { + paste::paste! { + impl Encode for $ty { + #[inline(always)] + fn encode(&self, w: &mut impl BufMut) { + [](w, *self) + } + } + } + }; + ($($ty:ty),+ $(,)?) => { + $(forward!($ty);)+ + } +} + +forward!(bool); +forward!(u8, u16, u32, u64, u128); +forward!(i8, i16, i32, i64, i128); +forward!(f32, f64); + +impl<'a> Encode for &'a str { + #[inline(always)] + fn encode(&self, w: &mut impl BufMut) { + encode_string(w, self) + } +} + +impl<'a> Encode for &'a [u8] { + #[inline(always)] + fn encode(&self, w: &mut impl BufMut) { + encode_bytes(w, self) + } +} + +pub fn encode_vec(w: &mut impl BufMut, vec: &Vec) { encode_u64(w, vec.len() as u64); for value in vec { @@ -80,7 +116,7 @@ pub fn write_vec(w: &mut impl BufMut, vec: &Vec) { } } -pub fn write_hash_map(w: &mut impl BufMut, map: &HashMap) { +pub fn encode_hash_map(w: &mut impl BufMut, map: &HashMap) { encode_u64(w, map.len() as u64); for (key, value) in map { @@ -89,7 +125,7 @@ pub fn write_hash_map(w: &mut impl BufMut, map: &HashMap(w: &mut impl BufMut, set: &HashSet) { +pub fn encode_hash_set(w: &mut impl BufMut, set: &HashSet) { encode_u64(w, set.len() as u64); for value in set { @@ -97,7 +133,7 @@ pub fn write_hash_set(w: &mut impl BufMut, set: &HashSet) { } } -pub fn write_option(w: &mut impl BufMut, option: &Option) { +pub fn encode_option(w: &mut impl BufMut, option: &Option) { if let Some(value) = option { value.encode(w); } @@ -105,7 +141,7 @@ pub fn write_option(w: &mut impl BufMut, option: &Option) { // TODO: NonZero -pub fn write_array(w: &mut impl BufMut, array: &[T; N]) { +pub fn encode_array(w: &mut impl BufMut, array: &[T; N]) { encode_u64(w, array.len() as u64); for value in array { @@ -401,3 +437,13 @@ impl Encode for NonZeroI128 { encode_i128(w, self.get()); } } + +#[inline(always)] +pub fn write_field(w: &mut W, id: u32, encode: E) +where + W: BufMut, + E: Fn(&mut W), +{ + write_id(w, id); + encode(w); +}