diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index fa2db9d..ca56610 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -10,7 +10,7 @@ jobs: linux: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Get processor arch run: echo "PROCESSOR_ARCH=`uname -p`" >> $GITHUB_ENV - name: Build @@ -20,14 +20,14 @@ jobs: - name: Prepare Release run: tar --transform 's/.*\///g' -zcvf ${{github.workspace}}/${{ runner.os }}-${{ env.PROCESSOR_ARCH }}-packet_generator.tar.gz target/*/packet_generator - name: Upload linux build - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v6 with: name: linux_build path: ${{github.workspace}}/${{ runner.os }}-${{ env.PROCESSOR_ARCH }}-packet_generator.tar.gz windows: runs-on: windows-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Build run: cargo build --release --verbose - name: Run tests @@ -37,7 +37,7 @@ jobs: copy-item ${{github.workspace}}\target\release\packet_generator.exe -destination ${{github.workspace}}\packet_generator.exe 7z a ${{github.workspace}}/windows-amd64-packet_generator.zip ${{github.workspace}}\packet_generator.exe - name: Upload windows build - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v6 with: name: windows_build path: ${{github.workspace}}/windows-amd64-packet_generator.zip @@ -48,11 +48,11 @@ jobs: if: github.ref == 'refs/heads/master' steps: - name: Download linux build - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v6 with: name: linux_build - name: Download windows build - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v6 with: name: windows_build - name: Release diff --git a/Cargo.lock b/Cargo.lock index dd3dadb..a7695d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -226,7 +226,7 @@ dependencies = [ [[package]] name = "packet_generator" -version = "0.2.0" +version = "0.2.2" dependencies = [ "clap", "failure", diff --git a/README.md b/README.md index 0e748e5..101bb9c 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,5 @@ packet generator for osiROSE-new ##### Build status -[![lin-badge]][lin-link] [![win-badge]][win-link] -[lin-badge]: https://travis-ci.com/dev-osrose/IDL.svg?branch=master "Linux build status" -[lin-link]: https://travis-ci.com/dev-osrose/IDL "Linux build status" -[win-badge]: https://ci.appveyor.com/api/projects/status/vok7xs5wr1ajqpbc?svg=true "Windows build status" -[win-link]: https://ci.appveyor.com/project/RavenX8/idl "Windows build status" +Check the actions tab for build status. \ No newline at end of file diff --git a/generator/Cargo.toml b/generator/Cargo.toml index 6772f60..eee54c8 100644 --- a/generator/Cargo.toml +++ b/generator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "packet_generator" -version = "0.2.0" +version = "0.2.2" authors = ["L3nn0x "] [dependencies] diff --git a/generator/src/codegen/mod.rs b/generator/src/codegen/mod.rs index 8958777..31fd64a 100644 --- a/generator/src/codegen/mod.rs +++ b/generator/src/codegen/mod.rs @@ -16,11 +16,14 @@ pub(crate) trait Codegen { } pub mod cpp; +pub mod rust; use clap::Subcommand; #[derive(Subcommand, Debug)] pub enum CodegenCommands { #[command(name = "cpp")] - CppCommand(cpp::CppArgs) + CppCommand(cpp::CppArgs), + #[command(name = "rust")] + RustCommand(rust::RustArgs) } \ No newline at end of file diff --git a/generator/src/codegen/rust/codegen_source.rs b/generator/src/codegen/rust/codegen_source.rs new file mode 100644 index 0000000..bc78f05 --- /dev/null +++ b/generator/src/codegen/rust/codegen_source.rs @@ -0,0 +1,762 @@ +use ::flat_ast::*; +use std::io::{Result, Write}; +use ::heck::*; +use std::collections::HashMap; +use flat_ast::RestrictionContent::{Enumeration, Length, MaxValue, MinValue}; + +pub (crate) struct CodeSourceGenerator<'a, W: Write + 'a> { + writer: &'a mut ::writer::Writer, + version: String +} + +impl<'a, W: Write> CodeSourceGenerator<'a, W> { + pub fn new(writer: &'a mut ::writer::Writer, version: String) -> Self { + Self { + writer, + version + } + } + + fn indent(&mut self) { + self.writer.indent(); + } + + fn dedent(&mut self) { + self.writer.dedent(); + } + + fn write(&mut self, val: impl AsRef) -> Result<&mut Self> { + self.writer.write(val)?; + Ok(self) + } + + pub fn generate(&mut self, packet: &Packet) -> Result<()> { + let version = self.version.clone(); + cg!(self, "/* This file is @generated with IDL v{} */\n", version); + cg!(self, r#"use bincode::{{Encode, Decode, enc::Encoder, de::Decoder, error::DecodeError}};"#); + cg!(self, r#"use bincode::de::read::Reader;"#); + cg!(self, r#"use bincode::enc::write::Writer;"#); + cg!(self, r#"use utils::null_string::NullTerminatedString;"#); + cg!(self, r#"use crate::enums::*;"#); + cg!(self, r#"use crate::types::*;"#); + cg!(self, r#"use crate::dataconsts::*;"#); + cg!(self, r#"use crate::packet::PacketPayload;"#); + + let mut iserialize: HashMap = HashMap::new(); + iserialize.insert("int8_t".to_string(), "i8".to_string()); + iserialize.insert("uint8_t".to_string(), "u8".to_string()); + iserialize.insert("int16_t".to_string(), "i16".to_string()); + iserialize.insert("uint16_t".to_string(), "u16".to_string()); + iserialize.insert("int32_t".to_string(), "i32".to_string()); + iserialize.insert("uint32_t".to_string(), "u32".to_string()); + iserialize.insert("int64_t".to_string(), "i64".to_string()); + iserialize.insert("uint64_t".to_string(), "u64".to_string()); + iserialize.insert("char".to_string(), "u8".to_string()); + iserialize.insert("int".to_string(), "i32".to_string()); + iserialize.insert("unsigned int".to_string(), "u32".to_string()); + iserialize.insert("float".to_string(), "f32".to_string()); + iserialize.insert("double".to_string(), "f64".to_string()); + iserialize.insert("std::string".to_string(), "NullTerminatedString".to_string()); + + + for content in packet.contents() { + use self::PacketContent::*; + match content { + Simple(simple) => self.simple_type(simple, &iserialize)?, + _ => {} + } + } + cg!(self); + for content in packet.contents() { + use self::PacketContent::*; + match content { + Complex(ref complex) => self.complex_type(complex, &iserialize)?, + _ => {} + }; + } + + cg!(self); + + cg!(self, r#"#[derive(Debug)]"#); + cg!(self, "pub struct {} {{", packet.class_name().to_upper_camel_case()); + self.indent(); + for content in packet.contents() { + use self::PacketContent::*; + match content { + Element(ref elem) => self.element(elem, &iserialize)?, + _ => {} + }; + } + self.dedent(); + cg!(self, "}}"); + + cg!(self); + cg!(self, "impl PacketPayload for {} {{}}", packet.class_name().to_upper_camel_case()); + cg!(self); + cg!(self, "impl Encode for {} {{", packet.class_name().to_upper_camel_case()); + self.indent(); + cg!(self, "fn encode(&self, encoder: &mut E) -> std::result::Result<(), bincode::error::EncodeError> {{"); + self.indent(); + for content in packet.contents() { + use self::PacketContent::*; + match content { + Element(ref elem) => { + let name = rename_if_reserved(elem.name()); + let trimmed_type = elem.type_().trim().to_string(); + let mut is_rust_native = true; + let rust_type = iserialize.get(elem.type_().trim()).unwrap_or_else(|| { + debug!(r#"Type "{}" not found, outputting anyway"#, elem.type_()); + is_rust_native = false; + &trimmed_type + }); + + if let Some(ref o) = elem.occurs() { + use ::flat_ast::Occurs::*; + match o { + Unbounded => { + cg!(self, "self.{}.encode(encoder)?;", name); + } + Num(n) => { + cg!(self, "for value in &self.{} {{", name); + self.indent(); + cg!(self, "value.encode(encoder)?;"); + self.dedent(); + cg!(self, "}}"); + } + }; + } else { + cg!(self, "self.{}.encode(encoder)?;", name); + } + }, + _ => {} + }; + } + cg!(self, "Ok(())"); + self.dedent(); + cg!(self, "}}"); + self.dedent(); + cg!(self, "}}"); + + cg!(self); + cg!(self, "impl Decode for {} {{", packet.class_name().to_upper_camel_case()); + self.indent(); + cg!(self, "fn decode(decoder: &mut D) -> std::result::Result {{"); + self.indent(); + for content in packet.contents() { + use self::PacketContent::*; + match content { + Element(ref elem) => { + let name = rename_if_reserved(elem.name()); + let trimmed_type = elem.type_().trim().to_string(); + let mut is_rust_native = true; + let rust_type = iserialize.get(elem.type_().trim()).unwrap_or_else(|| { + debug!(r#"Type "{}" not found, outputting anyway"#, elem.type_()); + is_rust_native = false; + &trimmed_type + }); + if let Some(ref o) = elem.occurs() { + use ::flat_ast::Occurs::*; + match o { + Unbounded => { + cg!(self, "let {} = Vec::decode(decoder)?;", name); + } + Num(n) => { + let mut type_prefix = "0"; + if "String" == rust_type { + type_prefix = ""; + } + + if false == is_rust_native { + type_prefix = ""; + if n.parse::().is_ok() { + cg!(self, "let mut {}: [{}{}; {}] = core::array::from_fn(|i| {}{}::default());", name, type_prefix, rust_type, n, type_prefix, rust_type); + } else { + cg!(self, "let mut {}: [{}{}; ({} as usize)] = core::array::from_fn(|i| {}{}::default());", name, type_prefix, rust_type, n, type_prefix, rust_type); + } + + cg!(self, "for index in 0..{} as usize {{", n); + self.indent(); + cg!(self, "{}[index] = {}::decode(decoder)?;", name, rust_type); + self.dedent(); + cg!(self, "}}"); + } else { + if n.parse::().is_ok() { + cg!(self, "let mut {} = [{}{}; {}];", name, type_prefix, rust_type, n); + } else { + cg!(self, "let mut {} = [{}{}; ({} as usize)];", name, type_prefix, rust_type, n); + } + cg!(self, "for value in &mut {} {{", name); + self.indent(); + cg!(self, "*value = {}::decode(decoder)?;", rust_type); + self.dedent(); + cg!(self, "}}"); + } + } + }; + } else { + cg!(self, "let {} = {}::decode(decoder)?;", name, rust_type.to_owned().to_string()); + }; + }, + _ => {} + }; + } + let mut output_list = Vec::new(); + for content in packet.contents() { + use self::PacketContent::*; + match content { + Element(ref elem) => { + output_list.push(rename_if_reserved(elem.name())); + }, + _ => {} + }; + } + + cg!(self, "Ok(Self {{ {} }})", output_list.join(", ")); + self.dedent(); + cg!(self, "}}"); + self.dedent(); + cg!(self, "}}"); + + + Ok(()) + } + + fn doc(&mut self, doc: &Option) -> Result<()> { + match doc { + None => (), + Some(doc) => { + for line in doc.lines() { + match line.trim() { + "" => (), + line => { + cg!(self, "// {}", line); + } + } + } + } + }; + Ok(()) + } + + fn get_bitfield_type(bit_count: usize) -> &'static str { + match bit_count { + 1..=8 => "u8", + 9..=16 => "u16", + 17..=32 => "u32", + 33..=64 => "u64", + _ => panic!("Unsupported bit count: {}", bit_count), + } + } + + fn get_size_of_type(type_name: &str) -> usize { + match type_name { + "u8" => std::mem::size_of::(), + "u16" => std::mem::size_of::(), + "u32" => std::mem::size_of::(), + "u64" => std::mem::size_of::(), + _ => panic!("Unsupported type: {}", type_name), + } + } + + fn complex_type(&mut self, complex: &ComplexType, iserialize: &HashMap) -> Result<()> { + use ::flat_ast::ComplexTypeContent::*; + if complex.inline() == false { + // All unions need to be outside the struct + match complex.content() { + Choice(ref c) => { + for elem in c.elements() { + if let Some(ref seq) = c.inline_seqs().get(elem.name()) { + cg!(self, r#"#[derive(Debug)]"#); + cg!(self, "struct {} {{", elem.name()); + self.indent(); + for e in seq.elements() { + self.element(e, &iserialize)?; + } + self.dedent(); + cg!(self, "}}"); + + // Get the max size of the union + let mut max_bit_size = 0; + + for elem2 in c.elements() { + if let Some(ref seq2) = c.inline_seqs().get(elem2.name()) { + // Do nothing + } else { + let trimmed_type = elem2.type_().trim().to_string(); + let rust_type = iserialize.get(elem2.type_().trim()).map(|s| s.to_string()).unwrap_or_else(|| { + warn!(r#"Type "{}" not found, outputting anyway"#, elem2.type_()); + trimmed_type.clone() + }); + max_bit_size = Self::get_size_of_type(&rust_type) as u32 * 8; + } + } + let rust_type = Self::get_bitfield_type(max_bit_size as usize); + + cg!(self); + cg!(self, "impl {} {{", elem.name()); + self.indent(); + cg!(self, "fn encode_bitfield(value: {}, size: {}, offset: &mut {}) -> {} {{", rust_type, rust_type, rust_type, rust_type); + self.indent(); + cg!(self, "let encoded = (value & ((1 << size) - 1)) << *offset;"); + cg!(self, "*offset += size; // Update offset for the next field"); + cg!(self, "encoded"); + self.dedent(); + cg!(self, "}}"); + cg!(self); + cg!(self, "fn decode_bitfield(encoded: {}, size: {}, offset: &mut {}) -> {} {{", rust_type, rust_type, rust_type, rust_type); + self.indent(); + cg!(self, "let value = (encoded >> *offset) & ((1 << size) - 1);"); + cg!(self, "*offset += size; // Update offset for the next field"); + cg!(self, "value"); + self.dedent(); + cg!(self, "}}"); + cg!(self); + cg!(self, "pub fn encode_data(&self) -> {} {{", rust_type); + self.indent(); + cg!(self, "let mut offset = 0;"); + let mut variable_names = Vec::new(); + for e in seq.elements() { + let name = rename_if_reserved(e.name()); + let bits = e.bits().map_or_else(|| "".to_string(), |b| format!("{}", b)); + cg!(self, "let {}_bits = Self::encode_bitfield(self.{}, {}, &mut offset);", e.name().to_snake_case(), name, bits); + variable_names.push(format!("{}_bits", e.name().to_snake_case())); + } + cg!(self, "{}", variable_names.join(" | ")); + self.dedent(); + cg!(self, "}}"); + self.dedent(); + cg!(self, "}}"); + cg!(self); + + cg!(self, "impl Encode for {} {{", elem.name()); + self.indent(); + cg!(self, "fn encode(&self, encoder: &mut E) -> std::result::Result<(), bincode::error::EncodeError> {{"); + self.indent(); + cg!(self, "self.encode_data().encode(encoder)?;"); + cg!(self, "Ok(())"); + self.dedent(); + cg!(self, "}}"); + self.dedent(); + cg!(self, "}}"); + + cg!(self); + cg!(self, "impl Decode for {} {{", elem.name()); + self.indent(); + cg!(self, "fn decode(decoder: &mut D) -> std::result::Result {{"); + self.indent(); + cg!(self, "let bitfield = {}::decode(decoder)?;", rust_type); + cg!(self, "let mut offset = 0;"); + let mut variable_names = Vec::new(); + for e in seq.elements() { + let name = rename_if_reserved(e.name()); + let bits = e.bits().map_or_else(|| "".to_string(), |b| format!("{}", b)); + cg!(self, "let {} = Self::decode_bitfield(bitfield, {}, &mut offset);", name, bits); + variable_names.push(format!("{}", name)); + } + cg!(self, "Ok(Self {{ {} }})", variable_names.join(", ")); + self.dedent(); + cg!(self, "}}"); + self.dedent(); + cg!(self, "}}"); + } + } + }, + _ => {} + } + + cg!(self); + cg!(self, r#"#[derive(Debug, Clone, Default)]"#); + cg!(self, "pub struct {} {{", complex.name()); + self.indent(); + match complex.content() { + Seq(ref s) => { + for elem in s.elements() { + self.element(elem, &iserialize)?; + } + }, + Choice(ref c) => { + for elem in c.elements() { + if let Some(ref _seq) = c.inline_seqs().get(elem.name()) { + cg!(self, "{}: {},", elem.name().to_snake_case(), elem.name()); + } else { + self.element(elem, &iserialize)?; + } + } + }, + Empty => {} + } + self.dedent(); + cg!(self, "}}"); + cg!(self); + let _ = self.complex_encode(complex, iserialize); + cg!(self); + let _ = self.complex_decode(complex, iserialize); + } + Ok(()) + } + + fn complex_encode(&mut self, complex: &ComplexType, _iserialize: &HashMap) -> Result<()> { + use ::flat_ast::ComplexTypeContent::*; + cg!(self, "impl Encode for {} {{", complex.name()); + self.indent(); + cg!(self, "fn encode(&self, encoder: &mut E) -> std::result::Result<(), bincode::error::EncodeError> {{"); + self.indent(); + + match complex.content() { + Seq(ref s) => { + for elem in s.elements() { + let name = rename_if_reserved(elem.name()); + cg!(self, "self.{}.encode(encoder)?;", name); + } + }, + Choice(ref c) => { + for elem in c.elements() { + let name = rename_if_reserved(elem.name()); + cg!(self, "self.{}.encode(encoder)?;", name); + } + }, + Empty => {} + } + cg!(self, "Ok(())"); + + self.dedent(); + cg!(self, "}}"); + self.dedent(); + cg!(self, "}}"); + Ok(()) + } + + fn complex_decode(&mut self, complex: &ComplexType, iserialize: &HashMap) -> Result<()> { + use ::flat_ast::ComplexTypeContent::*; + cg!(self, "impl Decode for {} {{", complex.name().to_upper_camel_case()); + self.indent(); + cg!(self, "fn decode(decoder: &mut D) -> std::result::Result {{"); + self.indent(); + + let mut output_list = Vec::new(); + match complex.content() { + Seq(ref s) => { + for elem in s.elements() { + let name = rename_if_reserved(elem.name()); + let trimmed_type = elem.type_().trim().to_string(); + let mut is_rust_native = true; + let rust_type = iserialize.get(elem.type_().trim()).map(|s| s.to_string()).unwrap_or_else(|| { + debug!(r#"Type "{}" not found, outputting anyway"#, elem.type_()); + is_rust_native = false; + trimmed_type.clone() + }); + + if let Some(ref o) = elem.occurs() { + use ::flat_ast::Occurs::*; + match o { + Unbounded => { + cg!(self, "let {} = Vec::decode(decoder)?;", name); + } + Num(n) => { + let mut type_prefix = "0"; + if "String" == rust_type { + type_prefix = ""; + } + + if false == is_rust_native { + type_prefix = ""; + if n.parse::().is_ok() { + cg!(self, "let mut {}: [{}{}; {}] = core::array::from_fn(|i| {}{}::default());", name, type_prefix, rust_type, n, type_prefix, rust_type); + } else { + cg!(self, "let mut {}: [{}{}; ({} as usize)] = core::array::from_fn(|i| {}{}::default());", name, type_prefix, rust_type, n, type_prefix, rust_type); + } + + cg!(self, "for index in 0..{} as usize {{", n); + self.indent(); + cg!(self, "{}[index] = {}::decode(decoder)?;", name, rust_type); + self.dedent(); + cg!(self, "}}"); + } else { + if n.parse::().is_ok() { + cg!(self, "let mut {} = [{}{}; {}];", name, type_prefix, rust_type, n); + } else { + cg!(self, "let mut {} = [{}{}; ({} as usize)];", name, type_prefix, rust_type, n); + } + cg!(self, "for value in &mut {} {{", name); + self.indent(); + cg!(self, "*value = {}::decode(decoder)?;", rust_type); + self.dedent(); + cg!(self, "}}"); + } + } + }; + } else { + cg!(self, "let {} = {}::decode(decoder)?;", name, rust_type); + } + output_list.push(name); + } + }, + Choice(ref c) => { + for elem in c.elements() { + let name = rename_if_reserved(elem.name()); + let trimmed_type = elem.type_().trim().to_string(); + let rust_type = iserialize.get(elem.type_().trim()).map(|s| s.to_string()).unwrap_or_else(|| { + debug!(r#"Type "{}" not found, outputting anyway"#, elem.type_()); + trimmed_type.clone() + }); + + cg!(self, "let {} = {}::decode(decoder)?;", name, rust_type); + output_list.push(name); + } + }, + Empty => {} + } + cg!(self, "Ok(Self {{ {} }})", output_list.join(", ")); + + self.dedent(); + cg!(self, "}}"); + self.dedent(); + cg!(self, "}}"); + Ok(()) + } + + + fn simple_type(&mut self, simple: &SimpleType, iserialize: &HashMap) -> Result<()> { + cg!(self); + self.doc(simple.doc())?; + for content in simple.contents() { + match content { + SimpleTypeContent::Restriction(res) => self.restrict(res, simple.name(), &iserialize)? + } + } + Ok(()) + } + + fn element(&mut self, elem: &Element, iserialize: &HashMap) -> Result<()> { + self.doc(elem.doc())?; + + if let Some(bitset) = elem.bitset() { + if bitset.start == 0 { + cg!(self, "{}: [bool; {}],", bitset.name, bitset.size); + } + return Ok(()); + } + + let trimmed_type = elem.type_().trim().to_string(); + let mut is_rust_native = true; + let rust_type = iserialize.get(elem.type_().trim()).unwrap_or_else(|| { + debug!(r#"Type "{}" not found, outputting anyway"#, elem.type_()); + is_rust_native = false; + &trimmed_type + }); + + let (type_, bits) = if let Some(ref o) = elem.occurs() { + use ::flat_ast::Occurs::*; + let type_ = match o { + Unbounded => format!("Vec<{}>", rust_type), + Num(n) => { + if false == is_rust_native { + format!("[{}; ({} as usize)]", rust_type, n) + } else { + if n.parse::().is_ok() { + format!("[{}; {}]", rust_type, n) + } else { + format!("[{}; ({} as usize)]", rust_type, n) + } + } + } + }; + (type_, "".to_string()) + } else { + let bits = elem.bits().map_or_else(|| "".to_string(), |b| format!("// {} bits", b)); + (rust_type.to_owned().to_string(), bits) + }; + // let default = match elem.init() { + // self::ElementInitValue::Default(d) => " = ".to_string() + d, + // _ => "".to_string() + // }; + let name = rename_if_reserved(elem.name()); + // cg!(self, "{}: {}{}{},", elem.name(), type_, bits, default); + cg!(self, "pub(crate) {}: {}, {}", name, type_, bits); + Ok(()) + } + + fn restrict(&mut self, restrict: &Restriction, name: &str, iserialize: &HashMap) -> Result<()> { + use self::RestrictionContent::*; + let is_enum = restrict.contents().iter().find(|content| match content { + Enumeration(_) => true, + _ => false + }).is_some(); + self.doc(restrict.doc())?; + let base = restrict.base().trim().to_string(); + let mut rust_type = iserialize.get(restrict.base().trim()).map(|s| s.to_string()).unwrap_or_else(|| { + debug!(r#"Type "{}" not found, outputting anyway"#, base); + base.clone() + }); + + if "NullTerminatedString" == rust_type { + rust_type = "String".to_string(); + } + + if is_enum { + cg!(self, r#"#[repr({})]"#, rust_type); + cg!(self, r#"#[derive(Debug, Clone)]"#); + cg!(self, "pub(crate) enum {} {{", name.to_upper_camel_case()); + self.indent(); + for content in restrict.contents() { + if let Enumeration(en) = content { + self.doc(en.doc())?; + cg!(self, "{} = {},", en.value().to_upper_camel_case(), en.id()); + } + } + } else { + cg!(self, r#"#[derive(Debug)]"#); + cg!(self, "pub struct {} {{", name.to_upper_camel_case()); + self.indent(); + cg!(self, "pub(crate) {}: {},", name.to_string().to_snake_case(), rust_type); + } + + self.dedent(); + cg!(self, "}}"); + + cg!(self); + self.restrict_encode(&restrict, name, iserialize)?; + cg!(self); + self.restrict_decode(&restrict, name, iserialize)?; + Ok(()) + } + + fn restrict_encode(&mut self, restrict: &Restriction, name: &str, _iserialize: &HashMap) -> Result<()> { + let is_enum = restrict.contents().iter().find(|content| match content { + Enumeration(_) => true, + _ => false + }).is_some(); + + cg!(self, "impl Encode for {} {{", name.to_upper_camel_case()); + self.indent(); + cg!(self, "fn encode(&self, encoder: &mut E) -> std::result::Result<(), bincode::error::EncodeError> {{"); + self.indent(); + if is_enum { + cg!(self, "encoder.writer().write(&[self.clone() as u8]).map_err(Into::into)"); + } else { + let data = name.to_string().to_snake_case(); + cg!(self, "let bytes = self.{}.as_bytes();", data); + for content in restrict.contents() { + match content { + Length(l) => { + cg!(self, "let fixed_length = {};", l); + cg!(self, "if bytes.len() > fixed_length {{"); + self.indent(); + cg!(self, "return Err(bincode::error::EncodeError::OtherString(format!("); + cg!(self, "\"{} length exceeds fixed size: {{}} > {{}}\", bytes.len(), fixed_length)));", data); + self.dedent(); + cg!(self, "}}"); + cg!(self, "encoder.writer().write(bytes)?;"); + cg!(self, "encoder.writer().write(&vec![0; fixed_length - bytes.len()])?;"); + cg!(self, "Ok(())"); + }, + MinValue(_v) => { + + }, + MaxValue(_v) => { + + }, + _ => panic!("enumeration in restrict when there shouldn't be one") + } + } + } + self.dedent(); + cg!(self, "}}"); + self.dedent(); + cg!(self, "}}"); + + Ok(()) + } + + fn restrict_decode(&mut self, restrict: &Restriction, name: &str, iserialize: &HashMap) -> Result<()> { + let is_enum = restrict.contents().iter().find(|content| match content { + Enumeration(_) => true, + _ => false + }).is_some(); + let trimmed_type = restrict.base().trim().to_string(); + let mut rust_type = iserialize.get(restrict.base().trim()).map(|s| s.to_string()).unwrap_or_else(|| { + debug!(r#"Type "{}" not found, outputting anyway"#, restrict.base()); + trimmed_type.clone() + }); + + if "NullTerminatedString" == rust_type { + rust_type = "String".to_string(); + } + + cg!(self, "impl Decode for {} {{", name.to_upper_camel_case()); + self.indent(); + cg!(self, "fn decode(decoder: &mut D) -> std::result::Result {{"); + self.indent(); + if is_enum { + cg!(self, "let value = {}::decode(decoder)?;", rust_type); + cg!(self, "match value {{"); + self.indent(); + for content in restrict.contents() { + if let Enumeration(en) = content { + cg!(self, "{} => Ok({}::{}),", en.id(), name.to_upper_camel_case(), en.value().to_upper_camel_case()); + } + } + cg!(self, "_ => Err(bincode::error::DecodeError::OtherString(format!(\"Invalid value for {}: {{}}\", value))),", name.to_upper_camel_case()); + self.dedent(); + cg!(self, "}}"); + } else { + let data = name.to_string().to_snake_case(); + let mut fixed_length = 64; + let mut min_value_check = String::new(); + let mut max_value_check = String::new(); + for content in restrict.contents() { + match content { + Length(l) => { + fixed_length = *l; + }, + MinValue(v) => { + min_value_check = format!("if {} < {} {{Err(bincode::error::DecodeError::OtherString(format!(\"Invalid value for {}: {{}} < {{}}\", {}, {})))}}", data, v, data, data, v).into(); + }, + MaxValue(v) => { + max_value_check = format!("if {} > {} {{Err(bincode::error::DecodeError::OtherString(format!(\"Invalid value for {}: {{}} > {{}}\", {}, {})))}}", data, v, data, data, v).into(); + }, + _ => panic!("enumeration in restrict when there shouldn't be one") + } + } + + if rust_type == "String" { + cg!(self, "let mut buffer = vec![0u8; {}];", fixed_length); + cg!(self, "decoder.reader().read(&mut buffer)?;"); + cg!(self, "let {} = {}::from_utf8(buffer)", data, rust_type); + cg!(self, ".map_err(|e| DecodeError::OtherString(format!(\"Invalid UTF-8: {{}}\", e)))?"); + cg!(self, ".trim_end_matches('\\0')"); + cg!(self, ".to_string();"); + } else { + cg!(self, "let {} = {}::decode(buffer)?;", data, rust_type); + + cg!(self, "{}", min_value_check); + + cg!(self, "{}", max_value_check); + } + cg!(self, "Ok(Self {{ {} }})", data); + } + self.dedent(); + cg!(self, "}}"); + self.dedent(); + cg!(self, "}}"); + + Ok(()) + } +} + +fn rename_if_reserved(name: &str) -> String { + let reserved_keywords = [ + "as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", + "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", + "pub", "ref", "return", "self", "Self", "static", "struct", "super", "trait", + "true", "type", "unsafe", "use", "where", "while", "async", "await", "dyn", + "abstract", "become", "box", "do", "final", "macro", "override", "priv", + "try", "typeof", "unsized", "virtual", "yield", + ]; + + if reserved_keywords.contains(&name) { + format!("{}_", name.to_snake_case()) // Append a suffix to avoid conflicts + } else { + name.to_string().to_snake_case() + } +} diff --git a/generator/src/codegen/rust/mod.rs b/generator/src/codegen/rust/mod.rs new file mode 100644 index 0000000..32e0301 --- /dev/null +++ b/generator/src/codegen/rust/mod.rs @@ -0,0 +1,84 @@ +use std::fs::File; +use std::path::PathBuf; +use codegen::Codegen; +use ::{flat_ast, writer}; + +mod codegen_source; + +pub struct Generator { + output: PathBuf +} + +impl Generator { + pub fn new(args: &RustArgs) -> Self { + Self{ + output: args.output_folder.clone().into() + } + } +} + +impl Codegen for Generator { + fn generate(&mut self, version: &str, packet: &flat_ast::Packet) -> Result<(), failure::Error> { + let source_output = File::create(self.output.to_str().unwrap().to_owned() + &format!("/{}.rs", packet.filename()))?; + debug!("source {:?}", source_output); + let mut writer = writer::Writer::new(source_output); + let mut codegen = codegen_source::CodeSourceGenerator::new(&mut writer, version.to_string()); + codegen.generate(&packet)?; + Ok(()) + } +} + +#[derive(clap::Args, Debug)] +#[command(name="rust")] +pub struct RustArgs { + #[arg(long)] + output_folder: String +} + +#[cfg(test)] +mod tests { + use crate::{flat_ast::Packet, writer::Writer}; + use super::{codegen_source}; + + struct StringWriter { + output: String + } + + impl StringWriter { + fn new() -> Self { + Self { output: String::new() } + } + } + + impl std::io::Write for StringWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.output += std::str::from_utf8(buf).unwrap(); + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } + } + + impl Into for StringWriter { + fn into(self) -> String { + self.output + } + } + + fn call_header(packet: &Packet) -> std::io::Result { + let writer = StringWriter::new(); + let mut writer = Writer::new(writer); + let mut codegen = codegen_source::CodeSourceGenerator::new(&mut writer, "0".to_string()); + codegen.generate(packet)?; + Ok(writer.into().into()) + } + + #[test] + fn empty_packet() { + let packet = Packet::new("PAKCS_PACKET".to_owned(), None); + let result = call_header(&packet); + assert!(result.is_ok()); + } +} \ No newline at end of file diff --git a/generator/src/flat_ast.rs b/generator/src/flat_ast.rs index fd5e0ab..e547671 100644 --- a/generator/src/flat_ast.rs +++ b/generator/src/flat_ast.rs @@ -31,6 +31,7 @@ pub enum ComplexTypeContent { Empty } +use heck::ToUpperCamelCase; pub use ::schema::ast::Occurs; #[derive(Debug, Clone)] @@ -145,7 +146,7 @@ impl Packet { pub fn new(type_: String , doc: Option) -> Self { use ::heck::*; - let name = type_.clone().to_lower_camel_case(); + let name = type_.clone().to_upper_camel_case(); let (class_name, filename) = if name.starts_with("Isc") { (name.clone(), name.clone().to_snake_case()) @@ -438,8 +439,7 @@ impl Bitset { impl SimpleType { pub fn new(name: String, doc: Option) -> Self { - use heck::ToLowerCamelCase; - SimpleType{ name: name.to_lower_camel_case(), contents: Vec::new(), doc } + SimpleType{ name: name.to_upper_camel_case(), contents: Vec::new(), doc } } pub fn add_content(&mut self, content: SimpleTypeContent) { diff --git a/generator/src/graph_passes.rs b/generator/src/graph_passes.rs index 4289b90..4c99d4a 100644 --- a/generator/src/graph_passes.rs +++ b/generator/src/graph_passes.rs @@ -1,6 +1,6 @@ use ::flat_ast::*; use std::collections::{BTreeMap, HashSet}; -use heck::ToLowerCamelCase; +use heck::{ToLowerCamelCase, ToUpperCamelCase}; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] struct NodeId(usize); @@ -250,7 +250,7 @@ pub fn run(mut packet: Packet) -> Result { for content in s.contents() { match content { Restriction(ref r) => { - if let Ok(node) = graph.get_node(&r.base().to_owned().to_lower_camel_case()) { + if let Ok(node) = graph.get_node(&r.base().to_owned().to_upper_camel_case()) { let to = graph.get_node(s.name()).unwrap(); graph.add_edge(to, node); } @@ -260,7 +260,7 @@ pub fn run(mut packet: Packet) -> Result { }, PacketContent::Element(ref e) => { trace!("adding start node {}", e.type_()); - graph.add_start_node(&e.type_().to_owned().to_lower_camel_case()); + graph.add_start_node(&e.type_().to_owned().to_upper_camel_case()); match e.occurs() { Some(self::Occurs::Unbounded) => vector = true, Some(self::Occurs::Num(_)) => array = true, diff --git a/generator/src/main.rs b/generator/src/main.rs index ab9b384..15416c1 100644 --- a/generator/src/main.rs +++ b/generator/src/main.rs @@ -11,7 +11,7 @@ mod writer; mod codegen; mod graph_passes; -use codegen::{cpp, Codegen, CodegenCommands}; +use codegen::{cpp, rust, Codegen, CodegenCommands}; use log::Level; @@ -24,7 +24,7 @@ struct Args { #[arg(short, long)] inputs: Vec, #[command(subcommand)] - command: codegen::CodegenCommands, + command: CodegenCommands, #[arg(short, long, action = clap::ArgAction::Count)] verbose: u8 @@ -53,8 +53,9 @@ fn main() -> Result<(), failure::Error> { trace!("packet {:?}", packet); let packet = graph_passes::run(packet)?; debug!("packet {:#?}", packet); - let mut generator = match &args.command { - CodegenCommands::CppCommand(args) => cpp::Generator::new(args) + let mut generator: Box = match &args.command { + CodegenCommands::CppCommand(args) => Box::new(cpp::Generator::new(args)), + CodegenCommands::RustCommand(args) => Box::new(rust::Generator::new(args)), }; generator.generate(VERSION, &packet)?; info!("Generated packet {}", packet.type_());