diff --git a/Makefile b/Makefile index ccaa5ab..5eb6cd8 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ ci: @set -eu; \ export RUSTFLAGS='-D warnings'; \ - make fmt clippy; \ - make cargo-test ci-examples ci-crates; \ + make fmt clippy check; \ + make ci-examples ci-crates; \ echo "Success!" RUST_DEV_PROJS = examples/ci-tests tests @@ -23,6 +23,10 @@ clean: cd - > /dev/null; \ done +check: + @set -eu; \ + diff std/rust/primitive_types.mol tools/codegen/primitive_types.mol + fmt: @set -eu; \ for dir in ${RUST_PROJS}; do \ @@ -40,14 +44,6 @@ clippy: cd - > /dev/null; \ done -cargo-test: - @set -eu; \ - for dir in ${RUST_PROJS}; do \ - cd "$${dir}"; \ - cargo test; \ - cd - > /dev/null; \ - done - ci-msrv: @set -eu; \ diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs index 528c57b..a7084d3 100644 --- a/bindings/rust/src/lib.rs +++ b/bindings/rust/src/lib.rs @@ -21,7 +21,7 @@ cfg_if::cfg_if! { pub mod error; pub mod prelude; -mod primitive; +pub mod primitive; // Little Endian pub type Number = u32; diff --git a/std/rust/.gitignore b/std/rust/.gitignore new file mode 100644 index 0000000..66ba3f1 --- /dev/null +++ b/std/rust/.gitignore @@ -0,0 +1,3 @@ +target/ +Cargo.lock + diff --git a/std/rust/Cargo.toml b/std/rust/Cargo.toml new file mode 100644 index 0000000..7fee698 --- /dev/null +++ b/std/rust/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "molecule-std" +version = "0.0.1" +authors = ["Nervos Core Dev "] +edition = "2018" +description = "std primitive types for molecule." +homepage = "https://github.com/nervosnetwork/molecule" +repository = "https://github.com/nervosnetwork/molecule" +keywords = ["molecule", "code-generation", "serialization"] +categories = [ + "parser-implementations", + "development-tools::build-utils", + "encoding", + "data-structures" +] +license = "MIT" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +molecule = { path = "../../bindings/rust" } + +[features] +default = ["std"] +with-primitive-types = [] +std = ["molecule/bytes", "molecule/faster-hex"] + +[build-dependencies] +codegen = {package ="molecule-codegen", version = "=0.7.5", path = "../../tools/codegen" } + diff --git a/std/rust/build.rs b/std/rust/build.rs new file mode 100644 index 0000000..36f2d8c --- /dev/null +++ b/std/rust/build.rs @@ -0,0 +1,19 @@ +use codegen::{Compiler, Language}; + +fn compile_schema(schema: &str) { + let mut compiler = Compiler::new(); + compiler + .input_schema_file(schema) + .generate_code(Language::Rust) + .output_dir_set_default() + .run() + .unwrap(); + println!("cargo:rerun-if-changed={}", schema); +} + +fn main() { + println!("cargo:rerun-if-changed=primitive_types.mol"); + compile_schema("primitive_types.mol"); + let out_dir = ::std::env::var("OUT_DIR").unwrap(); + println!("{}", out_dir); +} diff --git a/std/rust/primitive_types.mol b/std/rust/primitive_types.mol new file mode 100644 index 0000000..8ddbaf8 --- /dev/null +++ b/std/rust/primitive_types.mol @@ -0,0 +1,14 @@ +// Unsigned integers +array uint64 [byte; 8]; +array uint32 [byte; 4]; +array uint16 [byte; 2]; +array uint8 [byte; 1]; + +// Signed integers +array int64 [byte; 8]; +array int32 [byte; 4]; +array int16 [byte; 2]; +array int8 [byte; 1]; + +// Bool +array bool [byte; 1]; diff --git a/std/rust/src/lib.rs b/std/rust/src/lib.rs new file mode 100644 index 0000000..852e743 --- /dev/null +++ b/std/rust/src/lib.rs @@ -0,0 +1,35 @@ +pub use crate as molecule_std; + +pub use molecule::*; + +#[cfg(feature = "with-primitive-types")] +pub mod primitives { + include!(concat!(env!("OUT_DIR"), "/primitive_types.rs")); +} + +pub mod prelude { + pub use molecule::prelude::*; + + #[cfg(feature = "with-primitive-types")] + pub use crate::primitives::*; +} + +#[cfg(feature = "with-primitive-types")] +mod primitives_pack; + +pub mod pack { + use crate::prelude::Entity; + pub trait Unpack { + /// Unpack binary data into rust types. + fn unpack(&self) -> T; + } + + /// A syntactic sugar to convert a rust type into binary data. + pub trait Pack { + /// Packs a rust type into binary data. + fn pack(&self) -> T; + } + + #[cfg(feature = "with-primitive-types")] + pub use crate::primitives_pack::*; +} diff --git a/std/rust/src/primitives_pack.rs b/std/rust/src/primitives_pack.rs new file mode 100644 index 0000000..23011a3 --- /dev/null +++ b/std/rust/src/primitives_pack.rs @@ -0,0 +1,154 @@ +use crate::bytes::*; +use crate::pack::*; +use crate::prelude::*; + +macro_rules! impl_conversion_for_entity_unpack { + ($original:ty, $entity:ident) => { + impl Unpack<$original> for $entity { + fn unpack(&self) -> $original { + self.as_reader().unpack() + } + } + }; +} + +impl Pack for u64 { + fn pack(&self) -> Uint64 { + Uint64::new_unchecked(Bytes::from(self.to_le_bytes().to_vec())) + } +} + +impl<'r> Unpack for Uint64Reader<'r> { + fn unpack(&self) -> u64 { + let mut b = [0u8; 8]; + b.copy_from_slice(self.as_slice()); + u64::from_le_bytes(b) + } +} +impl_conversion_for_entity_unpack!(u64, Uint64); + +impl Pack for u32 { + fn pack(&self) -> Uint32 { + Uint32::new_unchecked(Bytes::from(self.to_le_bytes().to_vec())) + } +} + +impl<'r> Unpack for Uint32Reader<'r> { + fn unpack(&self) -> u32 { + let mut b = [0u8; 4]; + b.copy_from_slice(self.as_slice()); + u32::from_le_bytes(b) + } +} +impl_conversion_for_entity_unpack!(u32, Uint32); + +impl Pack for u16 { + fn pack(&self) -> Uint16 { + Uint16::new_unchecked(Bytes::from(self.to_le_bytes().to_vec())) + } +} + +impl<'r> Unpack for Uint16Reader<'r> { + fn unpack(&self) -> u16 { + let mut b = [0u8; 2]; + b.copy_from_slice(self.as_slice()); + u16::from_le_bytes(b) + } +} +impl_conversion_for_entity_unpack!(u16, Uint16); + +impl Pack for u8 { + fn pack(&self) -> Uint8 { + Uint8::new_unchecked(Bytes::from(self.to_le_bytes().to_vec())) + } +} + +impl<'r> Unpack for Uint8Reader<'r> { + fn unpack(&self) -> u8 { + let mut b = [0u8; 1]; + b.copy_from_slice(self.as_slice()); + u8::from_le_bytes(b) + } +} +impl_conversion_for_entity_unpack!(u8, Uint8); + +// Signed integers + +impl Pack for i64 { + fn pack(&self) -> Int64 { + Int64::new_unchecked(Bytes::from(self.to_le_bytes().to_vec())) + } +} + +impl<'r> Unpack for Int64Reader<'r> { + fn unpack(&self) -> i64 { + let mut b = [0u8; 8]; + b.copy_from_slice(self.as_slice()); + i64::from_le_bytes(b) + } +} +impl_conversion_for_entity_unpack!(i64, Int64); + +impl Pack for i32 { + fn pack(&self) -> Int32 { + Int32::new_unchecked(Bytes::from(self.to_le_bytes().to_vec())) + } +} + +impl<'r> Unpack for Int32Reader<'r> { + fn unpack(&self) -> i32 { + let mut b = [0u8; 4]; + b.copy_from_slice(self.as_slice()); + i32::from_le_bytes(b) + } +} +impl_conversion_for_entity_unpack!(i32, Int32); + +impl Pack for i16 { + fn pack(&self) -> Int16 { + Int16::new_unchecked(Bytes::from(self.to_le_bytes().to_vec())) + } +} + +impl<'r> Unpack for Int16Reader<'r> { + fn unpack(&self) -> i16 { + let mut b = [0u8; 2]; + b.copy_from_slice(self.as_slice()); + i16::from_le_bytes(b) + } +} +impl_conversion_for_entity_unpack!(i16, Int16); + +impl Pack for i8 { + fn pack(&self) -> Int8 { + Int8::new_unchecked(Bytes::from(self.to_le_bytes().to_vec())) + } +} + +impl<'r> Unpack for Int8Reader<'r> { + fn unpack(&self) -> i8 { + let mut b = [0u8; 1]; + b.copy_from_slice(self.as_slice()); + i8::from_le_bytes(b) + } +} +impl_conversion_for_entity_unpack!(i8, Int8); + +// Bool +impl Pack for bool { + fn pack(&self) -> Bool { + let b = u8::from(*self); + Bool::new_unchecked(Bytes::from(vec![b])) + } +} + +impl<'r> Unpack for BoolReader<'r> { + fn unpack(&self) -> bool { + match self.as_slice()[0] { + 0 => false, + 1 => true, + _ => unreachable!(), + } + } +} +impl_conversion_for_entity_unpack!(bool, Bool); diff --git a/tools/codegen/primitive_types.mol b/tools/codegen/primitive_types.mol new file mode 100644 index 0000000..8ddbaf8 --- /dev/null +++ b/tools/codegen/primitive_types.mol @@ -0,0 +1,14 @@ +// Unsigned integers +array uint64 [byte; 8]; +array uint32 [byte; 4]; +array uint16 [byte; 2]; +array uint8 [byte; 1]; + +// Signed integers +array int64 [byte; 8]; +array int32 [byte; 4]; +array int16 [byte; 2]; +array int8 [byte; 1]; + +// Bool +array bool [byte; 1]; diff --git a/tools/codegen/src/ast/raw/mod.rs b/tools/codegen/src/ast/raw/mod.rs index 3444ecd..bd27e95 100644 --- a/tools/codegen/src/ast/raw/mod.rs +++ b/tools/codegen/src/ast/raw/mod.rs @@ -32,6 +32,12 @@ pub struct SyntaxVersion { version: usize, } +impl SyntaxVersion { + pub fn support_primitive_types(&self) -> bool { + self.version >= 2 + } +} + #[derive(Debug, Clone, Property)] pub(crate) struct ImportStmt { name: String, @@ -65,7 +71,7 @@ pub(crate) struct UnionDecl { imported_depth: usize, } -#[derive(Debug, Property)] +#[derive(Debug, Property, Clone)] pub(crate) struct ArrayDecl { name: String, item: ItemDecl, @@ -94,7 +100,7 @@ pub(crate) struct TableDecl { imported_depth: usize, } -#[derive(Debug, Property)] +#[derive(Debug, Property, Clone)] pub(crate) struct ItemDecl { typ: String, } diff --git a/tools/codegen/src/ast/verified/complete.rs b/tools/codegen/src/ast/verified/complete.rs index a5d9efe..7e22818 100644 --- a/tools/codegen/src/ast/verified/complete.rs +++ b/tools/codegen/src/ast/verified/complete.rs @@ -1,9 +1,12 @@ +use crate::ast::SyntaxVersion; use std::{ collections::{HashMap, HashSet}, rc::Rc, }; -use super::super::raw; +use super::must_get_primitive_types; + +use super::{super::raw, Primitive}; trait CompleteRawDecl { fn complete(&self, deps: &super::Deps) -> Option; @@ -168,20 +171,40 @@ impl super::Ast { pub(crate) fn complete(raw: raw::Ast) -> Self { let mut decls_idx = HashMap::new(); let mut decls_keys = HashSet::new(); + let expand_primitive = !raw + .syntax_version() + .unwrap_or(&SyntaxVersion::default()) + .support_primitive_types(); for decl in raw.decls() { let name = decl.name(); - if super::TopDecl::new_primitive(name.to_lowercase().as_str()).is_some() { + if super::TopDecl::new_primitive(name.to_lowercase().as_str(), !expand_primitive) + .is_some() + { panic!("the name `{}` is reserved", name); } if decls_idx.insert(name, decl).is_some() || !decls_keys.insert(name) { panic!("the name `{}` is used more than once", name); }; } + + let mut primitives = vec![Primitive { + name: "byte".to_string(), + size: 1, + }]; + + if !expand_primitive { + primitives.extend(must_get_primitive_types()); + } + dbg!(expand_primitive); + let mut decls_result = HashMap::new(); - decls_result.insert( - "byte", - Rc::new(super::TopDecl::new_primitive("byte").unwrap()), - ); + primitives.iter().for_each(|primitive_type| { + decls_result.insert( + primitive_type.name(), + Rc::new(super::TopDecl::new_primitive(primitive_type.name(), true).unwrap()), + ); + }); + loop { if decls_keys.is_empty() { break; diff --git a/tools/codegen/src/ast/verified/mod.rs b/tools/codegen/src/ast/verified/mod.rs index 9052ccd..aa500b0 100644 --- a/tools/codegen/src/ast/verified/mod.rs +++ b/tools/codegen/src/ast/verified/mod.rs @@ -16,6 +16,25 @@ use crate::ast::SyntaxVersion; type Deps<'a> = HashMap<&'a str, Rc>; +pub fn must_get_primitive_types() -> Vec { + use crate::utils::ParserUtils; + crate::parser::Parser::preprocess(&std::path::PathBuf::from(concat!( + env!("CARGO_MANIFEST_DIR"), + "/primitive_types.mol" + ))) + .unwrap() + .decls() + .iter() + .map(|decl| match decl { + crate::ast::raw::TopDecl::Array(array) => Primitive { + name: array.name().to_string(), + size: array.item_count(), + }, + _ => panic!("primitive types is not array"), + }) + .collect::>() +} + #[derive(Debug, Property)] #[property(get(public))] pub struct Ast { @@ -45,7 +64,7 @@ pub enum TopDecl { Table(Table), } -#[derive(Debug, Property)] +#[derive(Debug, Property, Clone)] #[property(get(public))] pub struct Primitive { name: String, @@ -143,15 +162,21 @@ impl Ast { } impl TopDecl { - fn new_primitive(name: &str) -> Option { - match name { - "byte" => Some(Primitive { + fn new_primitive(name: &str, with_primitive_ext: bool) -> Option { + if name.eq("byte") { + return Some(Primitive { name: name.to_owned(), size: 1, - }), - _ => None, + }) + .map(Self::Primitive); } - .map(Self::Primitive) + if !with_primitive_ext { + return None; + } + must_get_primitive_types() + .iter() + .find(|primitive_decl| primitive_decl.name().eq(name)) + .map(|v| Self::Primitive(v.clone())) } pub fn is_byte(&self) -> bool { diff --git a/tools/codegen/src/ast/verified/recover.rs b/tools/codegen/src/ast/verified/recover.rs index 060f992..53a9083 100644 --- a/tools/codegen/src/ast/verified/recover.rs +++ b/tools/codegen/src/ast/verified/recover.rs @@ -1,3 +1,4 @@ +use crate::ast::verified; use std::{ collections::{HashMap, HashSet}, rc::Rc, @@ -5,6 +6,8 @@ use std::{ use crate::ir; +use super::must_get_primitive_types; + trait RecoverFromIr { fn recover(&self, deps: &super::Deps) -> Option; } @@ -174,22 +177,37 @@ impl RecoverFromIr for ir::Table { impl super::Ast { pub(crate) fn recover(ir: ir::Ir) -> Self { + let expand_primitive = !ir.syntax_version().support_primitive_types(); let mut decls_idx = HashMap::new(); let mut decls_keys = HashSet::new(); for decl in ir.decls() { let name = decl.name(); - if super::TopDecl::new_primitive(name.to_lowercase().as_str()).is_some() { + if super::TopDecl::new_primitive(name.to_lowercase().as_str(), !expand_primitive) + .is_some() + { panic!("the name `{}` is reserved", name); } if decls_idx.insert(name, decl).is_some() || !decls_keys.insert(name) { panic!("the name `{}` is used more than once", name); }; } + let mut primitives = vec![verified::Primitive { + name: "byte".to_string(), + size: 1, + }]; + + if !expand_primitive { + primitives.extend(must_get_primitive_types()); + } + let mut decls_result = HashMap::new(); - decls_result.insert( - "byte", - Rc::new(super::TopDecl::new_primitive("byte").unwrap()), - ); + primitives.iter().for_each(|primitive_type| { + decls_result.insert( + primitive_type.name(), + Rc::new(super::TopDecl::new_primitive(primitive_type.name(), true).unwrap()), + ); + }); + loop { if decls_keys.is_empty() { break; diff --git a/tools/codegen/src/generator/languages/c/mod.rs b/tools/codegen/src/generator/languages/c/mod.rs index 8faa078..28ae156 100644 --- a/tools/codegen/src/generator/languages/c/mod.rs +++ b/tools/codegen/src/generator/languages/c/mod.rs @@ -72,7 +72,11 @@ impl Generator { } impl super::LanguageGenerator for Generator { - fn generate(writer: &mut W, ast: &ast::Ast) -> io::Result<()> { + fn generate( + writer: &mut W, + ast: &ast::Ast, + _expand_primitive: bool, + ) -> io::Result<()> { writeln!(writer, "// Generated by Molecule {}", VERSION)?; writeln!(writer)?; Self::define_version(writer)?; diff --git a/tools/codegen/src/generator/languages/mod.rs b/tools/codegen/src/generator/languages/mod.rs index 6b15a44..9bf4788 100644 --- a/tools/codegen/src/generator/languages/mod.rs +++ b/tools/codegen/src/generator/languages/mod.rs @@ -12,7 +12,11 @@ pub enum Language { } pub(super) trait LanguageGenerator { - fn generate(writer: &mut W, ast: &ast::Ast) -> io::Result<()>; + fn generate( + writer: &mut W, + ast: &ast::Ast, + expand_primitive: bool, + ) -> io::Result<()>; } impl fmt::Display for Language { @@ -44,9 +48,10 @@ impl Language { } pub(crate) fn generate(self, writer: &mut W, ast: &ast::Ast) -> io::Result<()> { + let expand_primitive = ast.syntax_version().support_primitive_types(); match self { - Self::C => c::Generator::generate(writer, ast), - Self::Rust => rust::Generator::generate(writer, ast), + Self::C => c::Generator::generate(writer, ast, expand_primitive), + Self::Rust => rust::Generator::generate(writer, ast, expand_primitive), } } } diff --git a/tools/codegen/src/generator/languages/rust/mod.rs b/tools/codegen/src/generator/languages/rust/mod.rs index 86bebb3..9702d1c 100644 --- a/tools/codegen/src/generator/languages/rust/mod.rs +++ b/tools/codegen/src/generator/languages/rust/mod.rs @@ -37,13 +37,27 @@ use generator::Generator as _; pub(crate) struct Generator; impl super::LanguageGenerator for Generator { - fn generate(writer: &mut W, ast: &ast::Ast) -> io::Result<()> { + fn generate( + writer: &mut W, + ast: &ast::Ast, + expand_primitive: bool, + ) -> io::Result<()> { writeln!(writer, "// Generated by Molecule {}", VERSION)?; writeln!(writer)?; - let code = quote!( - use molecule::prelude::*; - ); - write!(writer, "{}", code)?; + + if expand_primitive { + let code = quote!( + use molecule::prelude::*; + ); + write!(writer, "{}", code)?; + } else { + let code = quote!( + use molecule_std as molecule; + use molecule::prelude::*; + ); + write!(writer, "{}", code)?; + } + let imports = ast.imports(); if !imports.is_empty() { writeln!(writer)?;