better parser generation

This commit is contained in:
nora 2022-02-12 21:53:09 +01:00
parent c43126af1f
commit 83778ac2c9
10 changed files with 859 additions and 516 deletions

View file

@ -1,5 +1,6 @@
use crate::{Amqp, Class, Domain, Method};
use anyhow::Result;
use crate::{
field_type, resolve_type_from_domain, snake_case, Amqp, Assert, Class, Domain, Method,
};
use heck::{ToSnakeCase, ToUpperCamelCase};
use itertools::Itertools;
@ -15,7 +16,7 @@ fn domain_function_name(domain_name: &str) -> String {
format!("domain_{domain_name}")
}
pub(crate) fn codegen_parser(amqp: &Amqp) -> Result<()> {
pub(crate) fn codegen_parser(amqp: &Amqp) {
println!(
"pub mod parse {{
use super::*;
@ -39,7 +40,7 @@ pub type IResult<'a, T> = nom::IResult<&'a [u8], T, TransError>;
);
for domain in &amqp.domains {
domain_parser(domain)?;
domain_parser(domain);
}
for class in &amqp.classes {
@ -56,78 +57,120 @@ pub type IResult<'a, T> = nom::IResult<&'a [u8], T, TransError>;
" let (input, _) = tag([{class_index}])(input)?;
alt(({all_methods}))(input)"
);
Ok(())
})?;
});
for method in &class.methods {
method_parser(class, method)?;
method_parser(amqp, class, method);
}
}
println!("\n}}");
Ok(())
}
fn domain_parser(domain: &Domain) -> Result<()> {
fn domain_parser(domain: &Domain) {
let fn_name = domain_function_name(&domain.name);
let type_name = domain.kind.to_snake_case();
function(&fn_name, &domain.name.to_upper_camel_case(), || {
if domain.asserts.is_empty() {
if type_name == "bit" {
println!(" todo!() // bit")
} else {
// 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)?;");
} else {
println!(" let (input, result) = {type_name}(input)?;");
for assert in &domain.asserts {
match &*assert.check {
"notnull" => { /* todo */ }
"regexp" => {
let value = assert.value.as_ref().unwrap();
println!(
r#" static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"{value}").unwrap());"#
);
println!(" if !REGEX.is_match(&result) {{ fail!() }}");
}
"le" => {} // can't validate this here
"length" => {
let length = assert.value.as_ref().unwrap();
println!(" if result.len() > {length} {{ fail!() }}");
}
_ => unimplemented!(),
for assert in &domain.asserts {
assert_check(assert, &type_name, "result");
}
println!(" Ok((input, result))");
}
println!(" Ok((input, result))");
}
Ok(())
})
});
}
}
fn method_parser(class: &Class, method: &Method) -> Result<()> {
fn method_parser(amqp: &Amqp, class: &Class, method: &Method) {
let class_name = class.name.to_snake_case();
let function_name = method_function_name(&class_name)(method);
function(&function_name, "Class", || {
let method_index = method.index;
println!(" let (input, _) = tag([{method_index}])(input)?;");
println!(" todo!()");
for _field in &method.fields {}
Ok(())
})?;
let mut iter = method.fields.iter().peekable();
while let Some(field) = iter.next() {
let type_name = resolve_type_from_domain(amqp, field_type(field));
Ok(())
if type_name == "bit" {
let mut fields_with_bit = vec![field];
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());
} else {
break;
}
}
let amount = fields_with_bit.len();
println!(" let (input, bits) = bit(input, {amount})?;");
for (i, field) in fields_with_bit.iter().enumerate() {
let field_name = snake_case(&field.name);
println!(" let {field_name} = bits[{i}];");
}
} else {
let fn_name = domain_function_name(field_type(field));
let field_name = snake_case(&field.name);
println!(" let (input, {field_name}) = {fn_name}(input)?;");
for assert in &field.asserts {
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, Class::{class_name}({class_name}::{method_name} {{");
for field in &method.fields {
let field_name = snake_case(&field.name);
println!(" {field_name},");
}
println!(" }})))");
});
}
fn function<F>(name: &str, ret_ty: &str, body: F) -> Result<()>
fn assert_check(assert: &Assert, type_name: &str, var_name: &str) {
match &*assert.check {
"notnull" => match type_name {
"shortstr" | "longstr" => {
println!(" if {var_name}.is_empty() {{ fail!() }}")
}
"short" => println!(" if {var_name} == 0 {{ fail!() }}"),
_ => unimplemented!(),
},
"regexp" => {
let value = assert.value.as_ref().unwrap();
println!(
r#" static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"{value}").unwrap());"#
);
println!(" if !REGEX.is_match(&{var_name}) {{ fail!() }}");
}
"le" => {} // can't validate this here
"length" => {
let length = assert.value.as_ref().unwrap();
println!(" if {var_name}.len() > {length} {{ fail!() }}");
}
_ => unimplemented!(),
}
}
fn function<F>(name: &str, ret_ty: &str, body: F)
where
F: FnOnce() -> Result<()>,
F: FnOnce(),
{
println!("fn {name}(input: &[u8]) -> IResult<{ret_ty}> {{");
body()?;
body();
println!("}}");
Ok(())
}