mirror of
https://github.com/Noratrieb/haesli.git
synced 2026-01-15 12:15:02 +01:00
split generated code so that methods are now in core
This commit is contained in:
parent
3b656b911a
commit
c333f20531
20 changed files with 1337 additions and 1206 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -4,15 +4,15 @@ mod parser;
|
|||
mod random;
|
||||
mod write;
|
||||
|
||||
use anyhow::Context;
|
||||
use heck::ToUpperCamelCase;
|
||||
use parser::codegen_parser;
|
||||
use random::codegen_random;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::iter::Peekable;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use strong_xml::XmlRead;
|
||||
use write::codegen_write;
|
||||
|
||||
#[derive(Debug, XmlRead)]
|
||||
#[xml(tag = "amqp")]
|
||||
|
|
@ -101,196 +101,235 @@ struct Doc {
|
|||
kind: Option<String>,
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
let this_file = PathBuf::from_str(file!()).unwrap();
|
||||
let expected_location = this_file
|
||||
.parent()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("amqp0-9-1.xml");
|
||||
let content = fs::read_to_string(expected_location).unwrap();
|
||||
|
||||
let amqp = match Amqp::from_str(&content) {
|
||||
Ok(amqp) => amqp,
|
||||
Err(err) => {
|
||||
eprintln!("{err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
codegen(&amqp);
|
||||
struct Codegen {
|
||||
output: Box<dyn Write>,
|
||||
}
|
||||
|
||||
fn codegen(amqp: &Amqp) {
|
||||
println!("#![allow(dead_code)]");
|
||||
println!("// This file has been generated by `xtask/src/codegen`. Do not edit it manually.\n");
|
||||
codegen_domain_defs(amqp);
|
||||
codegen_class_defs(amqp);
|
||||
codegen_parser(amqp);
|
||||
codegen_write(amqp);
|
||||
codegen_random(amqp);
|
||||
}
|
||||
pub fn main() -> anyhow::Result<()> {
|
||||
let this_file = PathBuf::from_str(file!()).context("own file path")?;
|
||||
let xtask_root = this_file
|
||||
.parent()
|
||||
.context("codegen directory path")?
|
||||
.parent()
|
||||
.context("src directory path")?
|
||||
.parent()
|
||||
.context("xtask root path")?;
|
||||
let amqp_spec = xtask_root.join("amqp0-9-1.xml");
|
||||
let project_root = xtask_root.parent().context("get project root parent")?;
|
||||
|
||||
fn codegen_domain_defs(amqp: &Amqp) {
|
||||
for domain in &amqp.domains {
|
||||
let invariants = invariants(domain.asserts.iter());
|
||||
let transport_generated_path = project_root.join("amqp_transport/src/methods/generated.rs");
|
||||
let core_generated_path = project_root.join("amqp_core/src/methods/generated.rs");
|
||||
|
||||
if let Some(label) = &domain.label {
|
||||
println!("/// {label}");
|
||||
}
|
||||
let content = fs::read_to_string(amqp_spec).context("read amqp spec file")?;
|
||||
|
||||
if !invariants.is_empty() {
|
||||
if domain.label.is_some() {
|
||||
println!("///");
|
||||
}
|
||||
println!("/// {invariants}");
|
||||
}
|
||||
if !domain.doc.is_empty() {
|
||||
println!("///");
|
||||
doc_comment(&domain.doc, 0);
|
||||
}
|
||||
println!(
|
||||
"pub type {} = {};\n",
|
||||
domain.name.to_upper_camel_case(),
|
||||
amqp_type_to_rust_type(&domain.kind),
|
||||
);
|
||||
let amqp = Amqp::from_str(&content).context("parse amqp spec file")?;
|
||||
|
||||
let transport_output =
|
||||
File::create(transport_generated_path).context("transport output file create")?;
|
||||
let core_output = File::create(core_generated_path).context("core output file create")?;
|
||||
|
||||
Codegen {
|
||||
output: Box::new(transport_output),
|
||||
}
|
||||
.transport_codegen(&amqp);
|
||||
|
||||
Codegen {
|
||||
output: Box::new(core_output),
|
||||
}
|
||||
.core_codegen(&amqp);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
impl Codegen {
|
||||
fn transport_codegen(&mut self, amqp: &Amqp) {
|
||||
writeln!(self.output, "#![allow(dead_code)]").ok();
|
||||
writeln!(
|
||||
self.output,
|
||||
"// This file has been generated by `xtask/src/codegen`. Do not edit it manually.\n"
|
||||
)
|
||||
.ok();
|
||||
self.codegen_parser(amqp);
|
||||
self.codegen_write(amqp);
|
||||
self.codegen_random(amqp);
|
||||
}
|
||||
|
||||
fn codegen_class_defs(amqp: &Amqp) {
|
||||
println!("#[derive(Debug, Clone, PartialEq)]");
|
||||
println!("pub enum Method {{");
|
||||
fn core_codegen(&mut self, amqp: &Amqp) {
|
||||
writeln!(self.output, "#![allow(dead_code)]").ok();
|
||||
writeln!(
|
||||
self.output,
|
||||
"// This file has been generated by `xtask/src/codegen`. Do not edit it manually.\n"
|
||||
)
|
||||
.ok();
|
||||
self.codegen_domain_defs(amqp);
|
||||
self.codegen_class_defs(amqp);
|
||||
}
|
||||
|
||||
for class in &amqp.classes {
|
||||
let enum_name = class.name.to_upper_camel_case();
|
||||
for method in &class.methods {
|
||||
let method_name = method.name.to_upper_camel_case();
|
||||
doc_comment(&class.doc, 4);
|
||||
doc_comment(&method.doc, 4);
|
||||
print!(" {enum_name}{method_name}");
|
||||
if !method.fields.is_empty() {
|
||||
println!(" {{");
|
||||
for field in &method.fields {
|
||||
let field_name = snake_case(&field.name);
|
||||
let (field_type, field_docs) =
|
||||
get_invariants_with_type(field_type(field), field.asserts.as_ref());
|
||||
if !field_docs.is_empty() {
|
||||
println!(" /// {field_docs}");
|
||||
if !field.doc.is_empty() {
|
||||
println!(" ///");
|
||||
doc_comment(&field.doc, 8);
|
||||
}
|
||||
} else {
|
||||
doc_comment(&field.doc, 8);
|
||||
}
|
||||
println!(" {field_name}: {field_type},");
|
||||
fn codegen_domain_defs(&mut self, amqp: &Amqp) {
|
||||
for domain in &amqp.domains {
|
||||
let invariants = self.invariants(domain.asserts.iter());
|
||||
|
||||
if let Some(label) = &domain.label {
|
||||
writeln!(self.output, "/// {label}").ok();
|
||||
}
|
||||
|
||||
if !invariants.is_empty() {
|
||||
if domain.label.is_some() {
|
||||
writeln!(self.output, "///").ok();
|
||||
}
|
||||
println!(" }},");
|
||||
} else {
|
||||
println!(",");
|
||||
writeln!(self.output, "/// {invariants}").ok();
|
||||
}
|
||||
if !domain.doc.is_empty() {
|
||||
writeln!(self.output, "///").ok();
|
||||
self.doc_comment(&domain.doc, 0);
|
||||
}
|
||||
writeln!(
|
||||
self.output,
|
||||
"pub type {} = {};\n",
|
||||
domain.name.to_upper_camel_case(),
|
||||
self.amqp_type_to_rust_type(&domain.kind),
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
println!("}}\n");
|
||||
}
|
||||
fn codegen_class_defs(&mut self, amqp: &Amqp) {
|
||||
writeln!(self.output, "#[derive(Debug, Clone, PartialEq)]").ok();
|
||||
writeln!(self.output, "pub enum Method {{").ok();
|
||||
|
||||
fn amqp_type_to_rust_type(amqp_type: &str) -> &'static str {
|
||||
match amqp_type {
|
||||
"octet" => "u8",
|
||||
"short" => "u16",
|
||||
"long" => "u32",
|
||||
"longlong" => "u64",
|
||||
"bit" => "bool",
|
||||
"shortstr" => "String",
|
||||
"longstr" => "Vec<u8>",
|
||||
"timestamp" => "u64",
|
||||
"table" => "super::Table",
|
||||
_ => unreachable!("invalid type {}", amqp_type),
|
||||
for class in &amqp.classes {
|
||||
let enum_name = class.name.to_upper_camel_case();
|
||||
for method in &class.methods {
|
||||
let method_name = method.name.to_upper_camel_case();
|
||||
self.doc_comment(&class.doc, 4);
|
||||
self.doc_comment(&method.doc, 4);
|
||||
write!(self.output, " {enum_name}{method_name}").ok();
|
||||
if !method.fields.is_empty() {
|
||||
writeln!(self.output, " {{").ok();
|
||||
for field in &method.fields {
|
||||
let field_name = self.snake_case(&field.name);
|
||||
let (field_type, field_docs) = self.get_invariants_with_type(
|
||||
self.field_type(field),
|
||||
field.asserts.as_ref(),
|
||||
);
|
||||
if !field_docs.is_empty() {
|
||||
writeln!(self.output, " /// {field_docs}").ok();
|
||||
if !field.doc.is_empty() {
|
||||
writeln!(self.output, " ///").ok();
|
||||
self.doc_comment(&field.doc, 8);
|
||||
}
|
||||
} else {
|
||||
self.doc_comment(&field.doc, 8);
|
||||
}
|
||||
writeln!(self.output, " {field_name}: {field_type},").ok();
|
||||
}
|
||||
writeln!(self.output, " }},").ok();
|
||||
} else {
|
||||
writeln!(self.output, ",").ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(self.output, "}}\n").ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn field_type(field: &Field) -> &String {
|
||||
field.domain.as_ref().or(field.kind.as_ref()).unwrap()
|
||||
}
|
||||
|
||||
fn resolve_type_from_domain(amqp: &Amqp, domain: &str) -> String {
|
||||
amqp.domains
|
||||
.iter()
|
||||
.find(|d| d.name == domain)
|
||||
.map(|d| d.kind.clone())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// returns (type name, invariant docs)
|
||||
fn get_invariants_with_type(domain: &str, asserts: &[Assert]) -> (String, String) {
|
||||
let additional_docs = invariants(asserts.iter());
|
||||
|
||||
let type_name = domain.to_upper_camel_case();
|
||||
|
||||
(type_name, additional_docs)
|
||||
}
|
||||
|
||||
fn snake_case(ident: &str) -> String {
|
||||
use heck::ToSnakeCase;
|
||||
|
||||
if ident == "type" {
|
||||
"r#type".to_string()
|
||||
} else {
|
||||
ident.to_snake_case()
|
||||
fn amqp_type_to_rust_type(&self, amqp_type: &str) -> &'static str {
|
||||
match amqp_type {
|
||||
"octet" => "u8",
|
||||
"short" => "u16",
|
||||
"long" => "u32",
|
||||
"longlong" => "u64",
|
||||
"bit" => "bool",
|
||||
"shortstr" => "String",
|
||||
"longstr" => "Vec<u8>",
|
||||
"timestamp" => "u64",
|
||||
"table" => "super::Table",
|
||||
_ => unreachable!("invalid type {}", amqp_type),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn subsequent_bit_fields<'a>(
|
||||
amqp: &Amqp,
|
||||
bit_field: &'a Field,
|
||||
iter: &mut Peekable<impl Iterator<Item = &'a Field>>,
|
||||
) -> Vec<&'a Field> {
|
||||
let mut fields_with_bit = vec![bit_field];
|
||||
fn field_type<'a>(&self, field: &'a Field) -> &'a String {
|
||||
field.domain.as_ref().or(field.kind.as_ref()).unwrap()
|
||||
}
|
||||
|
||||
loop {
|
||||
if iter
|
||||
.peek()
|
||||
.map(|f| resolve_type_from_domain(amqp, field_type(f)) == "bit")
|
||||
.unwrap_or(false)
|
||||
{
|
||||
fields_with_bit.push(iter.next().unwrap());
|
||||
fn resolve_type_from_domain(&self, amqp: &Amqp, domain: &str) -> String {
|
||||
amqp.domains
|
||||
.iter()
|
||||
.find(|d| d.name == domain)
|
||||
.map(|d| d.kind.clone())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// returns (type name, invariant docs)
|
||||
fn get_invariants_with_type(&self, domain: &str, asserts: &[Assert]) -> (String, String) {
|
||||
let additional_docs = self.invariants(asserts.iter());
|
||||
|
||||
let type_name = domain.to_upper_camel_case();
|
||||
|
||||
(type_name, additional_docs)
|
||||
}
|
||||
|
||||
fn snake_case(&self, ident: &str) -> String {
|
||||
use heck::ToSnakeCase;
|
||||
|
||||
if ident == "type" {
|
||||
"r#type".to_string()
|
||||
} else {
|
||||
break;
|
||||
ident.to_snake_case()
|
||||
}
|
||||
}
|
||||
fields_with_bit
|
||||
}
|
||||
|
||||
fn invariants<'a>(asserts: impl Iterator<Item = &'a Assert>) -> String {
|
||||
asserts
|
||||
.map(|assert| match &*assert.check {
|
||||
"notnull" => "must not be null".to_string(),
|
||||
"length" => format!("must be shorter than {}", assert.value.as_ref().unwrap()),
|
||||
"regexp" => format!("must match `{}`", assert.value.as_ref().unwrap()),
|
||||
"le" => {
|
||||
format!(
|
||||
"must be less than the {} field of the method {}",
|
||||
assert.method.as_ref().unwrap(),
|
||||
assert.field.as_ref().unwrap()
|
||||
)
|
||||
fn subsequent_bit_fields<'a>(
|
||||
&self,
|
||||
bit_field: &'a Field,
|
||||
iter: &mut Peekable<impl Iterator<Item = &'a Field>>,
|
||||
amqp: &Amqp,
|
||||
) -> Vec<&'a Field> {
|
||||
let mut fields_with_bit = vec![bit_field];
|
||||
|
||||
loop {
|
||||
if iter
|
||||
.peek()
|
||||
.map(|f| self.resolve_type_from_domain(amqp, self.field_type(f)) == "bit")
|
||||
.unwrap_or(false)
|
||||
{
|
||||
fields_with_bit.push(iter.next().unwrap());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
}
|
||||
|
||||
fn doc_comment(docs: &[Doc], indent: usize) {
|
||||
for doc in docs {
|
||||
if doc.kind == Some("grammar".to_string()) {
|
||||
continue;
|
||||
}
|
||||
for line in doc.text.lines() {
|
||||
let line = line.trim();
|
||||
if !line.is_empty() {
|
||||
let indent = " ".repeat(indent);
|
||||
println!("{indent}/// {line}");
|
||||
fields_with_bit
|
||||
}
|
||||
|
||||
fn invariants<'a>(&self, asserts: impl Iterator<Item = &'a Assert>) -> String {
|
||||
asserts
|
||||
.map(|assert| match &*assert.check {
|
||||
"notnull" => "must not be null".to_string(),
|
||||
"length" => format!("must be shorter than {}", assert.value.as_ref().unwrap()),
|
||||
"regexp" => format!("must match `{}`", assert.value.as_ref().unwrap()),
|
||||
"le" => {
|
||||
format!(
|
||||
"must be less than the {} field of the method {}",
|
||||
assert.method.as_ref().unwrap(),
|
||||
assert.field.as_ref().unwrap()
|
||||
)
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
}
|
||||
|
||||
fn doc_comment(&mut self, docs: &[Doc], indent: usize) {
|
||||
for doc in docs {
|
||||
if doc.kind == Some("grammar".to_string()) {
|
||||
continue;
|
||||
}
|
||||
for line in doc.text.lines() {
|
||||
let line = line.trim();
|
||||
if !line.is_empty() {
|
||||
let indent = " ".repeat(indent);
|
||||
writeln!(self.output, "{indent}/// {line}").ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
use super::{
|
||||
field_type, resolve_type_from_domain, snake_case, subsequent_bit_fields, Amqp, Assert, Class,
|
||||
Domain, Method,
|
||||
};
|
||||
use super::{Amqp, Assert, Class, Domain, Method};
|
||||
use crate::codegen::Codegen;
|
||||
use heck::{ToSnakeCase, ToUpperCamelCase};
|
||||
use itertools::Itertools;
|
||||
|
||||
|
|
@ -17,10 +15,12 @@ fn domain_function_name(domain_name: &str) -> String {
|
|||
format!("domain_{domain_name}")
|
||||
}
|
||||
|
||||
pub(super) fn codegen_parser(amqp: &Amqp) {
|
||||
println!(
|
||||
"pub mod parse {{
|
||||
use super::*;
|
||||
impl Codegen {
|
||||
pub(super) fn codegen_parser(&mut self, amqp: &Amqp) {
|
||||
writeln!(
|
||||
self.output,
|
||||
"pub mod parse {{
|
||||
use amqp_core::methods::*;
|
||||
use crate::methods::parse_helper::*;
|
||||
use crate::error::TransError;
|
||||
use nom::{{branch::alt, bytes::complete::tag}};
|
||||
|
|
@ -29,25 +29,29 @@ use once_cell::sync::Lazy;
|
|||
|
||||
pub type IResult<'a, T> = nom::IResult<&'a [u8], T, TransError>;
|
||||
"
|
||||
);
|
||||
println!(
|
||||
"pub fn parse_method(input: &[u8]) -> Result<(&[u8], Method), nom::Err<TransError>> {{
|
||||
)
|
||||
.ok();
|
||||
writeln!(
|
||||
self.output,
|
||||
"pub fn parse_method(input: &[u8]) -> Result<(&[u8], Method), nom::Err<TransError>> {{
|
||||
alt(({}))(input)
|
||||
}}",
|
||||
amqp.classes
|
||||
.iter()
|
||||
.map(|class| class.name.to_snake_case())
|
||||
.join(", ")
|
||||
);
|
||||
amqp.classes
|
||||
.iter()
|
||||
.map(|class| class.name.to_snake_case())
|
||||
.join(", ")
|
||||
)
|
||||
.ok();
|
||||
|
||||
for domain in &amqp.domains {
|
||||
domain_parser(domain);
|
||||
}
|
||||
for domain in &amqp.domains {
|
||||
self.domain_parser(domain);
|
||||
}
|
||||
|
||||
for class in &amqp.classes {
|
||||
let class_name = class.name.to_snake_case();
|
||||
for class in &amqp.classes {
|
||||
let class_name = class.name.to_snake_case();
|
||||
|
||||
self.function(&class_name, "Method");
|
||||
|
||||
function(&class_name, "Method", || {
|
||||
let class_index = class.index;
|
||||
let all_methods = class
|
||||
.methods
|
||||
|
|
@ -55,126 +59,158 @@ pub type IResult<'a, T> = nom::IResult<&'a [u8], T, TransError>;
|
|||
.map(method_function_name(&class_name))
|
||||
.join(", ");
|
||||
let class_name_raw = &class.name;
|
||||
println!(
|
||||
writeln!(
|
||||
self.output,
|
||||
r#" let (input, _) = tag({class_index}_u16.to_be_bytes())(input)?;
|
||||
alt(({all_methods}))(input).map_err(fail_err("class {class_name_raw}"))"#
|
||||
);
|
||||
});
|
||||
)
|
||||
.ok();
|
||||
|
||||
for method in &class.methods {
|
||||
method_parser(amqp, class, method);
|
||||
writeln!(self.output, "}}").ok();
|
||||
|
||||
for method in &class.methods {
|
||||
self.method_parser(class, method, amqp);
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(self.output, "\n}}").ok();
|
||||
}
|
||||
|
||||
fn domain_parser(&mut self, domain: &Domain) {
|
||||
let fn_name = domain_function_name(&domain.name);
|
||||
let type_name = domain.kind.to_snake_case();
|
||||
// don't even bother with bit domains, do them manually at call site
|
||||
if type_name != "bit" {
|
||||
self.function(&fn_name, &domain.name.to_upper_camel_case());
|
||||
|
||||
if domain.asserts.is_empty() {
|
||||
writeln!(self.output, " {type_name}(input)").ok();
|
||||
} else {
|
||||
writeln!(
|
||||
self.output,
|
||||
" let (input, result) = {type_name}(input)?;"
|
||||
)
|
||||
.ok();
|
||||
|
||||
for assert in &domain.asserts {
|
||||
self.assert_check(assert, &type_name, "result");
|
||||
}
|
||||
writeln!(self.output, " Ok((input, result))").ok();
|
||||
}
|
||||
|
||||
writeln!(self.output, "}}").ok();
|
||||
}
|
||||
}
|
||||
|
||||
println!("\n}}");
|
||||
}
|
||||
fn method_parser(&mut self, class: &Class, method: &Method, amqp: &Amqp) {
|
||||
let class_name = class.name.to_snake_case();
|
||||
let method_name_raw = &method.name;
|
||||
|
||||
fn domain_parser(domain: &Domain) {
|
||||
let fn_name = domain_function_name(&domain.name);
|
||||
let type_name = domain.kind.to_snake_case();
|
||||
// don't even bother with bit domains, do them manually at call site
|
||||
if type_name != "bit" {
|
||||
function(&fn_name, &domain.name.to_upper_camel_case(), || {
|
||||
if domain.asserts.is_empty() {
|
||||
println!(" {type_name}(input)");
|
||||
} else {
|
||||
println!(" let (input, result) = {type_name}(input)?;");
|
||||
|
||||
for assert in &domain.asserts {
|
||||
assert_check(assert, &type_name, "result");
|
||||
}
|
||||
println!(" Ok((input, result))");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn method_parser(amqp: &Amqp, class: &Class, method: &Method) {
|
||||
let class_name = class.name.to_snake_case();
|
||||
let method_name_raw = &method.name;
|
||||
|
||||
let function_name = method_function_name(&class_name)(method);
|
||||
function(&function_name, "Method", || {
|
||||
let function_name = method_function_name(&class_name)(method);
|
||||
self.function(&function_name, "Method");
|
||||
let method_index = method.index;
|
||||
println!(r#" let (input, _) = tag({method_index}_u16.to_be_bytes())(input)?;"#);
|
||||
writeln!(
|
||||
self.output,
|
||||
r#" let (input, _) = tag({method_index}_u16.to_be_bytes())(input)?;"#
|
||||
)
|
||||
.ok();
|
||||
let mut iter = method.fields.iter().peekable();
|
||||
while let Some(field) = iter.next() {
|
||||
let field_name_raw = &field.name;
|
||||
let type_name = resolve_type_from_domain(amqp, field_type(field));
|
||||
let type_name = self.resolve_type_from_domain(amqp, self.field_type(field));
|
||||
|
||||
if type_name == "bit" {
|
||||
let fields_with_bit = subsequent_bit_fields(amqp, field, &mut iter);
|
||||
let fields_with_bit = self.subsequent_bit_fields(field, &mut iter, amqp);
|
||||
|
||||
let amount = fields_with_bit.len();
|
||||
println!(
|
||||
writeln!(
|
||||
self.output,
|
||||
r#" let (input, bits) = bit(input, {amount}).map_err(fail_err("field {field_name_raw} in method {method_name_raw}"))?;"#
|
||||
);
|
||||
).ok();
|
||||
|
||||
for (i, field) in fields_with_bit.iter().enumerate() {
|
||||
let field_name = snake_case(&field.name);
|
||||
println!(" let {field_name} = bits[{i}];");
|
||||
let field_name = self.snake_case(&field.name);
|
||||
writeln!(self.output, " let {field_name} = bits[{i}];").ok();
|
||||
}
|
||||
} else {
|
||||
let fn_name = domain_function_name(field_type(field));
|
||||
let field_name = snake_case(&field.name);
|
||||
println!(
|
||||
let fn_name = domain_function_name(self.field_type(field));
|
||||
let field_name = self.snake_case(&field.name);
|
||||
writeln!(
|
||||
self.output,
|
||||
r#" let (input, {field_name}) = {fn_name}(input).map_err(fail_err("field {field_name_raw} in method {method_name_raw}"))?;"#
|
||||
);
|
||||
).ok();
|
||||
|
||||
for assert in &field.asserts {
|
||||
assert_check(assert, &type_name, &field_name);
|
||||
self.assert_check(assert, &type_name, &field_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
let class_name = class_name.to_upper_camel_case();
|
||||
let method_name = method.name.to_upper_camel_case();
|
||||
println!(" Ok((input, Method::{class_name}{method_name} {{");
|
||||
writeln!(
|
||||
self.output,
|
||||
" Ok((input, Method::{class_name}{method_name} {{"
|
||||
)
|
||||
.ok();
|
||||
for field in &method.fields {
|
||||
let field_name = snake_case(&field.name);
|
||||
println!(" {field_name},");
|
||||
let field_name = self.snake_case(&field.name);
|
||||
writeln!(self.output, " {field_name},").ok();
|
||||
}
|
||||
println!(" }}))");
|
||||
});
|
||||
}
|
||||
writeln!(self.output, " }}))").ok();
|
||||
|
||||
fn assert_check(assert: &Assert, type_name: &str, var_name: &str) {
|
||||
match &*assert.check {
|
||||
"notnull" => match type_name {
|
||||
"shortstr" | "longstr" => {
|
||||
println!(
|
||||
r#" if {var_name}.is_empty() {{ fail!("string was null for field {var_name}") }}"#
|
||||
);
|
||||
writeln!(self.output, "}}").ok();
|
||||
}
|
||||
|
||||
fn assert_check(&mut self, assert: &Assert, type_name: &str, var_name: &str) {
|
||||
match &*assert.check {
|
||||
"notnull" => match type_name {
|
||||
"shortstr" | "longstr" => {
|
||||
writeln!(
|
||||
self.output,
|
||||
r#" if {var_name}.is_empty() {{ fail!("string was null for field {var_name}") }}"#
|
||||
).ok();
|
||||
}
|
||||
"short" => {
|
||||
writeln!(
|
||||
self.output,
|
||||
r#" if {var_name} == 0 {{ fail!("number was 0 for field {var_name}") }}"#
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
},
|
||||
"regexp" => {
|
||||
let value = assert.value.as_ref().unwrap();
|
||||
writeln!(
|
||||
self.output,
|
||||
r#" static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"{value}").unwrap());"#
|
||||
).ok();
|
||||
let cause = format!("regex `{value}` did not match value for field {var_name}");
|
||||
writeln!(
|
||||
self.output,
|
||||
r#" if !REGEX.is_match(&{var_name}) {{ fail!(r"{cause}") }}"#
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
"short" => {
|
||||
println!(
|
||||
r#" if {var_name} == 0 {{ fail!("number was 0 for field {var_name}") }}"#
|
||||
);
|
||||
"le" => {} // can't validate this here
|
||||
"length" => {
|
||||
let length = assert.value.as_ref().unwrap();
|
||||
let cause = format!("value is shorter than {length} for field {var_name}");
|
||||
writeln!(
|
||||
self.output,
|
||||
r#" if {var_name}.len() > {length} {{ fail!("{cause}") }}"#
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
},
|
||||
"regexp" => {
|
||||
let value = assert.value.as_ref().unwrap();
|
||||
println!(
|
||||
r#" static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"{value}").unwrap());"#
|
||||
);
|
||||
let cause = format!("regex `{value}` did not match value for field {var_name}");
|
||||
println!(r#" if !REGEX.is_match(&{var_name}) {{ fail!(r"{cause}") }}"#);
|
||||
}
|
||||
"le" => {} // can't validate this here
|
||||
"length" => {
|
||||
let length = assert.value.as_ref().unwrap();
|
||||
let cause = format!("value is shorter than {length} for field {var_name}");
|
||||
println!(r#" if {var_name}.len() > {length} {{ fail!("{cause}") }}"#);
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
|
||||
fn function(&mut self, name: &str, ret_ty: &str) {
|
||||
writeln!(
|
||||
self.output,
|
||||
"fn {name}(input: &[u8]) -> IResult<'_, {ret_ty}> {{"
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn function<F>(name: &str, ret_ty: &str, body: F)
|
||||
where
|
||||
F: FnOnce(),
|
||||
{
|
||||
println!("fn {name}(input: &[u8]) -> IResult<'_, {ret_ty}> {{");
|
||||
body();
|
||||
println!("}}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,59 +1,78 @@
|
|||
use super::{snake_case, Amqp};
|
||||
use crate::codegen::{Amqp, Codegen};
|
||||
use heck::ToUpperCamelCase;
|
||||
|
||||
pub(super) fn codegen_random(amqp: &Amqp) {
|
||||
println!(
|
||||
"
|
||||
impl Codegen {
|
||||
pub fn codegen_random(&mut self, amqp: &Amqp) {
|
||||
writeln!(
|
||||
self.output,
|
||||
"
|
||||
mod random {{
|
||||
use rand::Rng;
|
||||
use amqp_core::methods::*;
|
||||
use crate::methods::RandomMethod;
|
||||
use super::*;
|
||||
"
|
||||
);
|
||||
)
|
||||
.ok();
|
||||
|
||||
writeln!(
|
||||
self.output,
|
||||
"impl<R: Rng> RandomMethod<R> for Method {{
|
||||
#[allow(unused_variables)]
|
||||
fn random(rng: &mut R) -> Self {{"
|
||||
)
|
||||
.ok();
|
||||
|
||||
impl_random("Method", || {
|
||||
let class_lens = amqp.classes.len();
|
||||
println!(" match rng.gen_range(0u32..{class_lens}) {{");
|
||||
writeln!(
|
||||
self.output,
|
||||
" match rng.gen_range(0u32..{class_lens}) {{"
|
||||
)
|
||||
.ok();
|
||||
for (i, class) in amqp.classes.iter().enumerate() {
|
||||
let class_name = class.name.to_upper_camel_case();
|
||||
println!(" {i} => {{");
|
||||
writeln!(self.output, " {i} => {{").ok();
|
||||
|
||||
let method_len = class.methods.len();
|
||||
println!(" match rng.gen_range(0u32..{method_len}) {{");
|
||||
writeln!(
|
||||
self.output,
|
||||
" match rng.gen_range(0u32..{method_len}) {{"
|
||||
)
|
||||
.ok();
|
||||
|
||||
for (i, method) in class.methods.iter().enumerate() {
|
||||
let method_name = method.name.to_upper_camel_case();
|
||||
println!(" {i} => Method::{class_name}{method_name} {{");
|
||||
writeln!(
|
||||
self.output,
|
||||
" {i} => Method::{class_name}{method_name} {{"
|
||||
)
|
||||
.ok();
|
||||
for field in &method.fields {
|
||||
let field_name = snake_case(&field.name);
|
||||
println!(" {field_name}: RandomMethod::random(rng),");
|
||||
let field_name = self.snake_case(&field.name);
|
||||
writeln!(
|
||||
self.output,
|
||||
" {field_name}: RandomMethod::random(rng),"
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
println!(" }},");
|
||||
writeln!(self.output, " }},").ok();
|
||||
}
|
||||
println!(
|
||||
writeln!(
|
||||
self.output,
|
||||
" _ => unreachable!(),
|
||||
}}"
|
||||
);
|
||||
)
|
||||
.ok();
|
||||
|
||||
println!(" }}");
|
||||
writeln!(self.output, " }}").ok();
|
||||
}
|
||||
println!(
|
||||
writeln!(
|
||||
self.output,
|
||||
" _ => unreachable!(),
|
||||
}}"
|
||||
);
|
||||
});
|
||||
)
|
||||
.ok();
|
||||
writeln!(self.output, " }}\n}}").ok();
|
||||
|
||||
println!("}}");
|
||||
}
|
||||
|
||||
fn impl_random(name: &str, body: impl FnOnce()) {
|
||||
println!(
|
||||
"impl<R: Rng> RandomMethod<R> for {name} {{
|
||||
#[allow(unused_variables)]
|
||||
fn random(rng: &mut R) -> Self {{"
|
||||
);
|
||||
|
||||
body();
|
||||
|
||||
println!(" }}\n}}");
|
||||
writeln!(self.output, "}}").ok();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,58 +1,72 @@
|
|||
use super::{field_type, resolve_type_from_domain, snake_case, subsequent_bit_fields, Amqp};
|
||||
use crate::codegen::{Amqp, Codegen};
|
||||
use heck::ToUpperCamelCase;
|
||||
|
||||
pub(super) fn codegen_write(amqp: &Amqp) {
|
||||
println!(
|
||||
"pub mod write {{
|
||||
use super::*;
|
||||
impl Codegen {
|
||||
pub fn codegen_write(&mut self, amqp: &Amqp) {
|
||||
writeln!(
|
||||
self.output,
|
||||
"pub mod write {{
|
||||
use amqp_core::methods::*;
|
||||
use crate::methods::write_helper::*;
|
||||
use crate::error::TransError;
|
||||
use std::io::Write;
|
||||
|
||||
pub fn write_method<W: Write>(method: Method, mut writer: W) -> Result<(), TransError> {{
|
||||
match method {{"
|
||||
);
|
||||
)
|
||||
.ok();
|
||||
|
||||
for class in &amqp.classes {
|
||||
let class_name = class.name.to_upper_camel_case();
|
||||
let class_index = class.index;
|
||||
for method in &class.methods {
|
||||
let method_name = method.name.to_upper_camel_case();
|
||||
let method_index = method.index;
|
||||
println!(" Method::{class_name}{method_name} {{");
|
||||
for field in &method.fields {
|
||||
let field_name = snake_case(&field.name);
|
||||
println!(" {field_name},");
|
||||
}
|
||||
println!(" }} => {{");
|
||||
let [ci0, ci1] = class_index.to_be_bytes();
|
||||
let [mi0, mi1] = method_index.to_be_bytes();
|
||||
println!(" writer.write_all(&[{ci0}, {ci1}, {mi0}, {mi1}])?;");
|
||||
let mut iter = method.fields.iter().peekable();
|
||||
|
||||
while let Some(field) = iter.next() {
|
||||
let field_name = snake_case(&field.name);
|
||||
let type_name = resolve_type_from_domain(amqp, field_type(field));
|
||||
if type_name == "bit" {
|
||||
let fields_with_bit = subsequent_bit_fields(amqp, field, &mut iter);
|
||||
print!(" bit(&[");
|
||||
for field in fields_with_bit {
|
||||
let field_name = snake_case(&field.name);
|
||||
print!("{field_name}, ");
|
||||
}
|
||||
println!("], &mut writer)?;");
|
||||
} else {
|
||||
println!(" {type_name}({field_name}, &mut writer)?;");
|
||||
for class in &amqp.classes {
|
||||
let class_name = class.name.to_upper_camel_case();
|
||||
let class_index = class.index;
|
||||
for method in &class.methods {
|
||||
let method_name = method.name.to_upper_camel_case();
|
||||
let method_index = method.index;
|
||||
writeln!(self.output, " Method::{class_name}{method_name} {{").ok();
|
||||
for field in &method.fields {
|
||||
let field_name = self.snake_case(&field.name);
|
||||
writeln!(self.output, " {field_name},").ok();
|
||||
}
|
||||
}
|
||||
println!(" }}");
|
||||
}
|
||||
}
|
||||
writeln!(self.output, " }} => {{").ok();
|
||||
let [ci0, ci1] = class_index.to_be_bytes();
|
||||
let [mi0, mi1] = method_index.to_be_bytes();
|
||||
writeln!(
|
||||
self.output,
|
||||
" writer.write_all(&[{ci0}, {ci1}, {mi0}, {mi1}])?;"
|
||||
)
|
||||
.ok();
|
||||
let mut iter = method.fields.iter().peekable();
|
||||
|
||||
println!(
|
||||
" }}
|
||||
while let Some(field) = iter.next() {
|
||||
let field_name = self.snake_case(&field.name);
|
||||
let type_name = self.resolve_type_from_domain(amqp, self.field_type(field));
|
||||
if type_name == "bit" {
|
||||
let fields_with_bit = self.subsequent_bit_fields(field, &mut iter, amqp);
|
||||
write!(self.output, " bit(&[").ok();
|
||||
for field in fields_with_bit {
|
||||
let field_name = self.snake_case(&field.name);
|
||||
write!(self.output, "{field_name}, ").ok();
|
||||
}
|
||||
writeln!(self.output, "], &mut writer)?;").ok();
|
||||
} else {
|
||||
writeln!(
|
||||
self.output,
|
||||
" {type_name}({field_name}, &mut writer)?;"
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
writeln!(self.output, " }}").ok();
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(
|
||||
self.output,
|
||||
" }}
|
||||
Ok(())
|
||||
}}
|
||||
}}"
|
||||
);
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,24 @@
|
|||
mod codegen;
|
||||
|
||||
fn main() {
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let command = std::env::args().nth(1).unwrap_or_else(|| {
|
||||
eprintln!("No task provided");
|
||||
eprintln!("Error: No task provided");
|
||||
help();
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
match command.as_str() {
|
||||
"generate" | "gen" => codegen::main(),
|
||||
_ => eprintln!("Unknown command {command}."),
|
||||
_ => {
|
||||
eprintln!("Unknown command {command}.");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn help() {
|
||||
println!(
|
||||
"Available tasks:
|
||||
generate - Generate amqp method code in `amqp_transport/src/methods/generated.rs.
|
||||
Dumps code to stdout and should be redirected manually."
|
||||
generate, gen - Generate amqp method code in `amqp_transport/src/methods/generated.rs and amqp_core/src/methods/generated.rs"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue