diff --git a/ast-generator/src/build_fn_gen.rs b/ast-generator/src/build_fn_gen.rs new file mode 100644 index 0000000..f5cb49e --- /dev/null +++ b/ast-generator/src/build_fn_gen.rs @@ -0,0 +1,127 @@ +use crate::spec::{ + BuildBooleanOn, ChildSpec, SingleChildToBuild, StructBuildSpec, VecChildToBuild, +}; +use convert_case::{Case, Casing}; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; + +pub fn make_build_fn_name(s: &str) -> String { + format!("build_{}", s.to_case(Case::Snake)) +} + +fn make_child_holder(child_spec: &ChildSpec) -> Option { + match child_spec { + ChildSpec::SkipChild(_) => None, + ChildSpec::VecChild(vec_child) => { + let (child_ident, child_type_ident) = match vec_child.build() { + VecChildToBuild::Type(vec_type_child) => ( + format_ident!("{}", vec_type_child.var_name()), + format_ident!("{}", vec_type_child.build()), + ), + }; + Some(quote! { + let mut #child_ident: Vec<#child_type_ident> = vec![] + }) + } + ChildSpec::SingleChild(single_child) => match single_child.build() { + SingleChildToBuild::Type(single_type_child) => { + let child_ident = format_ident!("{}", single_type_child.var_name()); + let child_type_ident = format_ident!("{}", single_type_child.build()); + Some(quote! { + let mut #child_ident: Option<#child_type_ident> = None; + }) + } + SingleChildToBuild::Boolean(boolean_child) => { + let child_ident = format_ident!("{}", boolean_child.var_name()); + Some(quote! { + let #child_ident: bool = false + }) + } + }, + } +} + +fn make_match_action(child_spec: &ChildSpec) -> TokenStream { + match child_spec { + ChildSpec::SkipChild(_) => quote! {}, + ChildSpec::VecChild(vec_child) => { + let (child_name_ident, build_fn_ident) = match vec_child.build() { + VecChildToBuild::Type(vec_type_child) => ( + format_ident!("{}", vec_type_child.var_name()), + format_ident!("{}", vec_type_child.with()), + ), + }; + quote! { + #child_name_ident.push(#build_fn_ident(inner_pair)) + } + } + ChildSpec::SingleChild(single_child) => match single_child.build() { + SingleChildToBuild::Type(single_type_child) => { + let child_name_ident = format_ident!("{}", single_type_child.var_name()); + let build_fn_ident = format_ident!("{}", single_type_child.with()); + quote! { + #child_name_ident = Some(#build_fn_ident(inner_pair)) + } + } + SingleChildToBuild::Boolean(single_boolean_child) => { + let child_name_ident = format_ident!("{}", single_boolean_child.var_name()); + match single_boolean_child.on() { + BuildBooleanOn::RulePresent => quote! { + #child_name_ident = true + }, + } + } + }, + } +} + +fn make_rule_matcher(child_spec: &ChildSpec) -> TokenStream { + let rule_ident = match child_spec { + ChildSpec::SkipChild(skip_child) => format_ident!("{}", skip_child.rule()), + ChildSpec::VecChild(vec_child) => format_ident!("{}", vec_child.rule()), + ChildSpec::SingleChild(single_child) => format_ident!("{}", single_child.rule()), + }; + let action = make_match_action(child_spec); + + quote! { + Rule::#rule_ident => { + #action; + } + } +} + +pub fn make_struct_build_fn(build_spec: &StructBuildSpec) -> TokenStream { + let build_fn_ident = format_ident!("{}", build_spec.with()); + let pair_ident = format_ident!("{}_pair", build_spec.build().to_case(Case::Snake)); + let return_type_ident = format_ident!("{}", build_spec.build()); + + let child_holders = build_spec + .children() + .iter() + .map(|child_spec| make_child_holder(child_spec)) + .filter(|child_holder| child_holder.is_some()) + .map(|child_holder| child_holder.unwrap()) + .collect::>(); + + let rule_matchers = build_spec + .children() + .iter() + .map(|child_spec| make_rule_matcher(child_spec)) + .collect::>(); + + let iter_stream = quote! { + for inner_pair in #pair_ident.into_inner() { + match inner_pair.as_rule() { + #(#rule_matchers)* + } + } + }; + + quote! { + fn #build_fn_ident(#pair_ident: Pair) -> #return_type_ident { + #(#child_holders;)* + + #iter_stream + } + } +} diff --git a/ast-generator/src/deserialize.rs b/ast-generator/src/deserialize.rs new file mode 100644 index 0000000..869a2e7 --- /dev/null +++ b/ast-generator/src/deserialize.rs @@ -0,0 +1,167 @@ +use crate::build_fn_gen::make_build_fn_name; +use crate::spec::{ + BuildBooleanOn, BuildSpec, ChildSpec, EnumBuildSpec, EnumRule, SingleBooleanChildToBuild, + SingleChild, SingleChildToBuild, SingleTypeChildToBuild, SkipChild, StructBuildSpec, VecChild, + VecChildToBuild, VecTypeChildToBuild, +}; +use convert_case::{Case, Casing}; +use yaml_rust2::{Yaml, YamlLoader}; + +fn get_skip(skip: &Yaml) -> bool { + skip.as_bool().unwrap_or_else(|| false) +} + +fn get_vec(vec: &Yaml) -> bool { + vec.as_bool().unwrap_or_else(|| false) +} + +fn get_vec_child_to_build(build: &Yaml, rule: &str) -> VecChildToBuild { + if build.is_hash() { + let build_type = build["build"].as_str().unwrap(); + let var_name = build["var"] + .as_str() + .map(|s| s.to_string()) + .unwrap_or_else(|| build_type.to_case(Case::Snake)); + let with = build["with"] + .as_str() + .map(|s| s.to_string()) + .unwrap_or_else(|| make_build_fn_name(build_type)); + + VecChildToBuild::Type(VecTypeChildToBuild::new(build_type, &var_name, &with)) + } else { + let build_as_str = build.as_str().unwrap_or(rule); + VecChildToBuild::Type(VecTypeChildToBuild::new( + build_as_str, + build_as_str.to_case(Case::Snake).as_str(), + make_build_fn_name(build_as_str).as_str(), + )) + } +} + +fn get_vec_child(name: &str, rule: &str, build: &Yaml) -> ChildSpec { + ChildSpec::VecChild(VecChild::new( + name, + rule, + get_vec_child_to_build(build, rule), + )) +} + +fn get_single_child_to_build(name: &str, rule: &str, build: &Yaml) -> SingleChildToBuild { + if build.is_hash() { + let r#type = build["type"].as_str().unwrap(); + let var_name = build["var"] + .as_str() + .map(|s| s.to_string()) + .unwrap_or(name.to_string()); + let on = build["on"].as_str().unwrap(); + if r#type.eq("boolean") && on.eq("rule_present") { + SingleChildToBuild::Boolean(SingleBooleanChildToBuild::new( + &var_name, + BuildBooleanOn::RulePresent, + )) + } else { + todo!("currently on boolean types with on: rule_present are supported") + } + } else { + match build.as_str() { + Some(s) => SingleChildToBuild::Type(SingleTypeChildToBuild::from_build_or_rule(s)), + None => SingleChildToBuild::Type(SingleTypeChildToBuild::from_build_or_rule(rule)), + } + } +} + +fn get_single_child(name: &str, rule: &str, build: &Yaml) -> ChildSpec { + ChildSpec::SingleChild(SingleChild::new( + name, + rule, + get_single_child_to_build(name, rule, build), + )) +} + +fn get_child_specs(children: &Yaml) -> Vec { + children + .as_vec() + .unwrap() + .iter() + .map(|child_spec| { + if child_spec.is_hash() { + let as_hash = child_spec.as_hash().unwrap(); + let only_pair = as_hash.iter().next().unwrap(); + + let name = only_pair.0.as_str().unwrap(); + let props = only_pair.1; + + let rule = props["rule"].as_str().unwrap(); + + if get_skip(&props["skip"]) { + return ChildSpec::SkipChild(SkipChild::new(name, rule)); + } + + let build = &props["build"]; + + if get_vec(&props["vec"]) { + get_vec_child(name, rule, build) + } else { + get_single_child(name, rule, build) + } + } else { + ChildSpec::SingleChild(SingleChild::from_name_snake(child_spec.as_str().unwrap())) + } + }) + .collect() +} + +fn get_enum_rule_specs(rule_specs: &Yaml) -> Vec { + rule_specs + .as_vec() + .unwrap() + .iter() + .map(|rule_spec| { + if rule_spec.is_hash() { + let rule = rule_spec["rule"].as_str().unwrap(); + let build = rule_spec["build"].as_str().unwrap(); + let with = if !rule_spec["with"].is_badvalue() { + rule_spec.as_str().unwrap().to_string() + } else { + make_build_fn_name(build) + }; + + EnumRule::new(rule, build, &with) + } else { + EnumRule::from_rule(rule_spec.as_str().unwrap()) + } + }) + .collect() +} + +fn deserialize_build_spec(build_spec_name: &Yaml, build_spec: &Yaml) -> BuildSpec { + let build_spec_name_pascal = build_spec_name.as_str().unwrap(); + let children = &build_spec["children"]; + + if children.is_array() { + let child_specs = get_child_specs(children); + BuildSpec::Struct(StructBuildSpec::from_name( + build_spec_name_pascal, + child_specs, + )) + } else { + let rule_specs = &build_spec["rules"]; + if rule_specs.is_array() { + let enum_rules = get_enum_rule_specs(rule_specs); + BuildSpec::Enum(EnumBuildSpec::from_name(build_spec_name_pascal, enum_rules)) + } else { + panic!("either children or rules must be present on the build spec"); + } + } +} + +pub fn deserialize_yaml_spec(yaml: &str) -> Vec { + let docs = YamlLoader::load_from_str(yaml).unwrap(); + let doc = &docs[0]; + + doc.as_hash() + .unwrap() + .iter() + .map(|(build_spec_name, build_spec)| deserialize_build_spec(build_spec_name, build_spec)) + .collect() +} diff --git a/ast-generator/src/lib.rs b/ast-generator/src/lib.rs index e8bde7b..c657e1c 100644 --- a/ast-generator/src/lib.rs +++ b/ast-generator/src/lib.rs @@ -1,310 +1,47 @@ -use convert_case::{Case, Casing}; -use proc_macro2::{Ident, TokenStream}; -use quote::{format_ident, quote}; -use yaml_rust2::YamlLoader; +mod build_fn_gen; +mod deserialize; +mod spec; +mod type_gen; -enum BuildSpec { - Enum(EnumBuildSpec), - Struct(StructBuildSpec), -} +use crate::build_fn_gen::make_struct_build_fn; +use crate::type_gen::make_type; +use proc_macro2::TokenStream; +use quote::quote; +use spec::BuildSpec; +use syn::File; -struct EnumBuildSpec { - name: String, - build: String, - rules: Vec, -} - -struct EnumRule { - rule: String, - build: String, -} - -impl EnumRule { - fn from_rule_name(rule_name: &str) -> Self { - Self { - rule: rule_name.to_string(), - build: rule_name.to_string(), +fn debug_built_spec(build_spec: &BuildSpec, token_stream: &TokenStream) { + println!("*** BuildSpec ***"); + match build_spec { + BuildSpec::Enum(enum_build_spec) => { + println!("Spec name: {}", enum_build_spec.name()); + } + BuildSpec::Struct(struct_build_spec) => { + println!("Spec name: {}", struct_build_spec.name()); } } - - fn new(rule: &str, build: &str) -> Self { - Self { - rule: rule.to_string(), - build: build.to_string(), - } - } -} - -struct StructBuildSpec { - name: String, - build: String, - children: Vec, -} - -#[derive(Debug)] -struct ChildSpec { - name: String, - rule: String, - vec: bool, - skip: bool, - build: ChildToBuild, -} - -impl ChildSpec { - fn from_child_name(child_name: &str) -> Self { - Self { - name: child_name.to_string(), - rule: child_name.to_case(Case::Pascal), - skip: false, - vec: false, - build: ChildToBuild::TypeRef(child_name.to_case(Case::Pascal)), - } - } - - fn new(name: &str, rule: &str, vec: bool, skip: bool, build: ChildToBuild) -> Self { - Self { - name: name.to_string(), - rule: rule.to_string(), - vec, - skip, - build, - } - } -} - -#[derive(Debug)] -enum ChildToBuild { - TypeRef(String), - Boolean { on: BuildBooleanOn }, -} - -#[derive(Debug)] -enum BuildBooleanOn { - RulePresent, -} - -fn make_enum_type(build_spec: &EnumBuildSpec) -> TokenStream { - let children: Vec = build_spec - .rules - .iter() - .map(|rule| { - let member_name_ident = format_ident!("{}", rule.rule); - let child_name_ident = format_ident!("{}", rule.build); - quote! { - #member_name_ident(#child_name_ident) - } - }) - .collect(); - let type_name_ident = format_ident!("{}", build_spec.build); - quote! { - pub enum #type_name_ident { - #(#children),* - } - } -} - -fn make_child_type_ident(child_spec: &ChildSpec) -> Ident { - match &child_spec.build { - ChildToBuild::TypeRef(type_name) => format_ident!("{}", type_name), - ChildToBuild::Boolean { on: _ } => format_ident!("bool"), - } -} - -fn make_child_ident(child_spec: &ChildSpec) -> Ident { - format_ident!("{}", child_spec.name) -} - -fn make_struct_type(build_spec: &StructBuildSpec) -> TokenStream { - let mut member_names: Vec = vec![]; - let mut annotated_members: Vec = vec![]; - let mut accessors: Vec = vec![]; - - for child_spec in build_spec.children.iter() { - println!("{:?}", child_spec); - if !child_spec.skip { - let child_ident = make_child_ident(child_spec); - member_names.push(child_ident.clone()); - - let child_type_ident = make_child_type_ident(child_spec); - let type_annotation = if child_spec.vec { - quote! { Vec<#child_type_ident> } - } else { - quote! { #child_type_ident } - }; - annotated_members.push(quote! { - #child_ident: #type_annotation - }); - - accessors.push(quote! { - pub fn #child_ident(&self) -> &#type_annotation { - &self.#child_ident - } - }) - } - } - - let type_ident = format_ident!("{}", build_spec.build); - - quote! { - pub struct #type_ident { - #(#annotated_members),* - } - - impl #type_ident { - pub fn new(#(#annotated_members),*) -> Self { - Self { - #(#member_names),* - } - } - - #(#accessors)* - } - } -} - -fn make_child_holder(child_spec: &ChildSpec) -> TokenStream { - if child_spec.skip { - return quote! {}; - } - let child_ident = make_child_ident(child_spec); - let child_type_ident = make_child_type_ident(child_spec); - - if child_spec.vec { - quote! { let mut #child_ident: Vec<#child_type_ident> = vec![] } - } else { - match &child_spec.build { - ChildToBuild::TypeRef(_) => quote! { - let mut #child_ident: Option<#child_type_ident> = None - }, - ChildToBuild::Boolean { on } => match on { - BuildBooleanOn::RulePresent => { - quote! { let mut #child_ident: bool = false } - } - }, - } - } -} - -fn make_struct_build_fn(build_spec: &StructBuildSpec) -> TokenStream { - let child_holders = build_spec - .children - .iter() - .map(|child_spec| make_child_holder(child_spec)) - .collect::>(); - - let build_fn_identifier = format_ident!("build_{}", build_spec.name); - let return_type_ident = format_ident!("{}", build_spec.build); - quote! { - fn #build_fn_identifier() -> #return_type_ident { - #(#child_holders);* - } - } -} - -fn deserialize_yaml_file() -> Vec { - let docs = YamlLoader::load_from_str(include_str!("../../src/parser/ast.yaml")).unwrap(); - let doc = &docs[0]; - let mut build_specs: Vec = vec![]; - - for (build_spec_name, build_spec) in doc.as_hash().unwrap() { - let build_spec_name_pascal = build_spec_name.as_str().unwrap(); - let children = &build_spec["children"]; - - if children.is_array() { - let mut child_specs: Vec = vec![]; - for child_spec in children.as_vec().unwrap() { - if child_spec.is_hash() { - let as_hash = child_spec.as_hash().unwrap(); - let only_pair = as_hash.iter().next().unwrap(); - - let name = only_pair.0.as_str().unwrap(); - let props = only_pair.1; - - let rule = props["rule"].as_str().unwrap(); - - let skip = if !props["skip"].is_badvalue() { - props["skip"].as_bool().unwrap() - } else { - false - }; - - let vec = if !props["vec"].is_badvalue() { - props["vec"].as_bool().unwrap() - } else { - false - }; - - let build = &props["build"]; - let child_to_build = if build.is_hash() { - let r#type = build["type"].as_str().unwrap(); - let on = build["on"].as_str().unwrap(); - if r#type.eq("boolean") && on.eq("rule_present") { - ChildToBuild::Boolean { - on: BuildBooleanOn::RulePresent, - } - } else { - todo!("currently on boolean types are supported") - } - } else { - match build.as_str() { - Some(s) => ChildToBuild::TypeRef(s.to_string()), - None => ChildToBuild::TypeRef(rule.to_string()), - } - }; - - child_specs.push(ChildSpec::new(name, rule, vec, skip, child_to_build)) - } else { - child_specs.push(ChildSpec::from_child_name(child_spec.as_str().unwrap())); - } - } - - build_specs.push(BuildSpec::Struct(StructBuildSpec { - name: build_spec_name_pascal.to_string(), - build: build_spec_name_pascal.to_string(), - children: child_specs, - })); - } else { - let rule_specs = &build_spec["rules"]; - if rule_specs.is_array() { - let mut enum_rules: Vec = vec![]; - for rule_spec in rule_specs.as_vec().unwrap() { - if rule_spec.is_hash() { - let rule = rule_spec["rule"].as_str().unwrap(); - let build = rule_spec["build"].as_str().unwrap(); - enum_rules.push(EnumRule::new(rule, build)); - } else { - enum_rules.push(EnumRule::from_rule_name(rule_spec.as_str().unwrap())); - } - } - } else { - panic!("either children or rules must be present on the build spec"); - } - } - } - - build_specs + println!("{:#?}", token_stream); + let parsed: File = syn::parse2(token_stream.clone()).unwrap(); + println!("{}", prettyplease::unparse(&parsed)); } pub fn test_dump() -> String { - let build_specs = deserialize_yaml_file(); + let build_specs = deserialize::deserialize_yaml_spec(include_str!("../../src/parser/ast.yaml")); let mut streams: Vec = vec![]; for build_spec in &build_specs { - match build_spec { - BuildSpec::Enum(enum_spec) => { - streams.push(make_enum_type(enum_spec)); - } - BuildSpec::Struct(struct_spec) => { - streams.push(make_struct_type(struct_spec)); - } - } + let type_stream = make_type(build_spec); + debug_built_spec(build_spec, &type_stream); + streams.push(type_stream); } for build_spec in &build_specs { match build_spec { BuildSpec::Enum(_) => {} BuildSpec::Struct(struct_spec) => { - streams.push(make_struct_type(struct_spec)); + let struct_build_fn_stream = make_struct_build_fn(struct_spec); + debug_built_spec(build_spec, &struct_build_fn_stream); + streams.push(struct_build_fn_stream); } } } @@ -312,7 +49,6 @@ pub fn test_dump() -> String { let combined = quote! { #(#streams)* }; - - let syntax_tree = syn::parse2(combined).unwrap(); - prettyplease::unparse(&syntax_tree) + let file: File = syn::parse2(combined).unwrap(); + prettyplease::unparse(&file) } diff --git a/ast-generator/src/spec.rs b/ast-generator/src/spec.rs new file mode 100644 index 0000000..c6bddb4 --- /dev/null +++ b/ast-generator/src/spec.rs @@ -0,0 +1,328 @@ +use crate::build_fn_gen::make_build_fn_name; +use convert_case::{Case, Casing}; + +pub enum BuildSpec { + Enum(EnumBuildSpec), + Struct(StructBuildSpec), +} + +pub struct EnumBuildSpec { + name: String, + build: String, + rules: Vec, +} + +impl EnumBuildSpec { + pub fn from_name(name: &str, rules: Vec) -> Self { + EnumBuildSpec { + name: name.to_string(), + build: name.to_string(), + rules, + } + } + + /// The top-level key for the build spec in the yaml file. + pub fn name(&self) -> &str { + &self.name + } + + /// The enum type to be built, in Pascal case. + pub fn build(&self) -> &str { + &self.build + } + + /// The individual rule specs. + pub fn rules(&self) -> &[EnumRule] { + &self.rules + } +} + +pub struct EnumRule { + rule: String, + build: String, + with: String, +} + +impl EnumRule { + pub fn from_rule(rule: &str) -> Self { + Self { + rule: rule.to_string(), + build: rule.to_string(), + with: make_build_fn_name(rule), + } + } + + pub fn new(rule: &str, build: &str, with: &str) -> Self { + Self { + rule: rule.to_string(), + build: build.to_string(), + with: with.to_string(), + } + } + + /// The enum rule to match, in Pascal case. + pub fn rule(&self) -> &str { + &self.rule + } + + /// The type to build, in Pascal case. + pub fn build(&self) -> &str { + &self.build + } + + /// The build-fn name, in snake case. + pub fn with(&self) -> &str { + &self.with + } +} + +pub struct StructBuildSpec { + name: String, + build: String, + var_name: String, + with: String, + children: Vec, +} + +impl StructBuildSpec { + pub fn from_name(name: &str, child_specs: Vec) -> Self { + Self { + name: name.to_string(), + build: name.to_string(), + var_name: name.to_case(Case::Snake), + with: make_build_fn_name(name), + children: child_specs, + } + } + + /// The top-level name of this build spec. + pub fn name(&self) -> &str { + &self.name + } + + /// The type to be built, in Pascal case. + pub fn build(&self) -> &str { + &self.build + } + + /// The name of the variable to be built, in snake case. + pub fn var_name(&self) -> &str { + &self.var_name + } + + /// The build-fn name, in snake case. + pub fn with(&self) -> &str { + &self.with + } + + /// The children for this build spec. + pub fn children(&self) -> &[ChildSpec] { + &self.children + } +} + +pub enum ChildSpec { + SkipChild(SkipChild), + VecChild(VecChild), + SingleChild(SingleChild), +} + +pub struct SkipChild { + name: String, + rule: String, +} + +impl SkipChild { + pub fn new(name: &str, rule: &str) -> Self { + Self { + name: name.to_string(), + rule: rule.to_string(), + } + } + + /// The name of this child spec. + pub fn name(&self) -> &str { + &self.name + } + + /// The grammar rule to match. + pub fn rule(&self) -> &str { + &self.rule + } +} + +pub struct VecChild { + name: String, + rule: String, + build: VecChildToBuild, +} + +impl VecChild { + pub fn new(name: &str, rule: &str, build: VecChildToBuild) -> Self { + Self { + name: name.to_string(), + rule: rule.to_string(), + build, + } + } + + /// The name of this child. + pub fn name(&self) -> &str { + &self.name + } + + /// The rule to match to build this child. + pub fn rule(&self) -> &str { + &self.rule + } + + /// The build info for this child. + pub fn build(&self) -> &VecChildToBuild { + &self.build + } +} + +#[derive(Debug)] +pub enum VecChildToBuild { + Type(VecTypeChildToBuild), +} + +#[derive(Debug)] +pub struct VecTypeChildToBuild { + build: String, + var_name: String, + with: String, +} + +impl VecTypeChildToBuild { + pub fn new(build: &str, var_name: &str, with: &str) -> Self { + Self { + build: build.to_string(), + var_name: var_name.to_string(), + with: with.to_string(), + } + } + + /// The type to build, in Pascal case. + pub fn build(&self) -> &str { + &self.build + } + + /// The name of the variable to build, in snake case. + pub fn var_name(&self) -> &str { + &self.var_name + } + + /// The build-fn name. + pub fn with(&self) -> &str { + &self.with + } +} + +#[derive(Debug)] +pub struct SingleChild { + name: String, + rule: String, + build: SingleChildToBuild, +} + +impl SingleChild { + pub fn from_name_snake(name: &str) -> Self { + Self { + name: name.to_string(), + rule: name.to_case(Case::Pascal), + build: SingleChildToBuild::Type(SingleTypeChildToBuild::from_build_or_rule( + &name.to_case(Case::Pascal), + )), + } + } + + pub fn new(name: &str, rule: &str, build: SingleChildToBuild) -> Self { + Self { + name: name.to_string(), + rule: rule.to_string(), + build, + } + } + + /// The name of this child in the yaml file, in snake case. + pub fn name(&self) -> &str { + &self.name + } + + /// The grammar rule to match to build this child. + pub fn rule(&self) -> &str { + &self.rule + } + + /// The specification for what to actually build. + pub fn build(&self) -> &SingleChildToBuild { + &self.build + } +} + +#[derive(Debug)] +pub enum SingleChildToBuild { + Type(SingleTypeChildToBuild), + Boolean(SingleBooleanChildToBuild), +} + +#[derive(Debug)] +pub struct SingleTypeChildToBuild { + build: String, + var_name: String, + with: String, +} + +impl SingleTypeChildToBuild { + pub fn from_build_or_rule(build_or_rule: &str) -> Self { + Self { + build: build_or_rule.to_string(), + var_name: build_or_rule.to_case(Case::Snake), + with: make_build_fn_name(build_or_rule), + } + } + + /// The type to build, in Pascal case. + pub fn build(&self) -> &str { + &self.build + } + + /// The variable name to build, in snake case. + pub fn var_name(&self) -> &str { + &self.var_name + } + + /// The build-fn name. + pub fn with(&self) -> &str { + &self.with + } +} + +#[derive(Debug)] +pub struct SingleBooleanChildToBuild { + var_name: String, + on: BuildBooleanOn, +} + +impl SingleBooleanChildToBuild { + pub fn new(var_name: &str, on: BuildBooleanOn) -> Self { + Self { + var_name: var_name.to_string(), + on, + } + } + + pub fn var_name(&self) -> &str { + &self.var_name + } + + pub fn on(&self) -> &BuildBooleanOn { + &self.on + } +} + +#[derive(Debug)] +pub enum BuildBooleanOn { + RulePresent, +} diff --git a/ast-generator/src/type_gen.rs b/ast-generator/src/type_gen.rs new file mode 100644 index 0000000..4c60280 --- /dev/null +++ b/ast-generator/src/type_gen.rs @@ -0,0 +1,152 @@ +use crate::spec::{ + BuildSpec, ChildSpec, EnumBuildSpec, SingleBooleanChildToBuild, SingleChildToBuild, + SingleTypeChildToBuild, StructBuildSpec, VecChild, VecChildToBuild, +}; +use proc_macro2::{Ident, TokenStream}; +use quote::{format_ident, quote}; + +fn make_enum_type(build_spec: &EnumBuildSpec) -> TokenStream { + let children: Vec = build_spec + .rules() + .iter() + .map(|rule| { + let member_name_ident = format_ident!("{}", rule.rule()); + let child_name_ident = format_ident!("{}", rule.build()); + quote! { + #member_name_ident(#child_name_ident) + } + }) + .collect(); + let type_name_ident = format_ident!("{}", build_spec.build()); + quote! { + pub enum #type_name_ident { + #(#children),* + } + } +} + +fn handle_vec_child( + vec_child: &VecChild, + member_names: &mut Vec, + annotated_members: &mut Vec, + accessors: &mut Vec, +) { + let (child_ident, child_type_ident) = match vec_child.build() { + VecChildToBuild::Type(vec_type_child) => ( + format_ident!("{}", vec_type_child.var_name()), + format_ident!("{}", vec_type_child.build()), + ), + }; + + member_names.push(child_ident.clone()); + annotated_members.push(quote! { + #child_ident: Vec<#child_type_ident> + }); + accessors.push(quote! { + pub fn #child_ident(&self) -> &[#child_type_ident] { + &self.#child_ident + } + }); +} + +fn handle_single_type_child( + single_type_child: &SingleTypeChildToBuild, + member_names: &mut Vec, + annotated_members: &mut Vec, + accessors: &mut Vec, +) { + let child_ident = format_ident!("{}", single_type_child.var_name()); + let child_type_ident = format_ident!("{}", single_type_child.build()); + member_names.push(child_ident.clone()); + annotated_members.push(quote! { + #child_ident: #child_type_ident + }); + accessors.push(quote! { + pub fn #child_ident(&self) -> &#child_type_ident { + &self.#child_ident + } + }); +} + +fn handle_single_boolean_child( + single_boolean_child: &SingleBooleanChildToBuild, + member_names: &mut Vec, + annotated_members: &mut Vec, + accessors: &mut Vec, +) { + let child_ident = format_ident!("{}", single_boolean_child.var_name()); + member_names.push(child_ident.clone()); + annotated_members.push(quote! { + #child_ident: bool + }); + accessors.push(quote! { + pub fn #child_ident(&self) -> bool { + self.#child_ident + } + }) +} + +fn make_struct_type(build_spec: &StructBuildSpec) -> TokenStream { + let mut member_names: Vec = vec![]; + let mut annotated_members: Vec = vec![]; + let mut accessors: Vec = vec![]; + + for child_spec in build_spec.children().iter() { + match child_spec { + ChildSpec::SkipChild(_) => {} + ChildSpec::VecChild(vec_child) => { + handle_vec_child( + vec_child, + &mut member_names, + &mut annotated_members, + &mut accessors, + ); + } + ChildSpec::SingleChild(single_child) => { + match single_child.build() { + SingleChildToBuild::Type(single_type_child) => { + handle_single_type_child( + single_type_child, + &mut member_names, + &mut annotated_members, + &mut accessors, + ); + } + SingleChildToBuild::Boolean(single_boolean_child) => { + handle_single_boolean_child( + single_boolean_child, + &mut member_names, + &mut annotated_members, + &mut accessors, + ); + } + }; + } + } + } + + let type_ident = format_ident!("{}", build_spec.build()); + + quote! { + pub struct #type_ident { + #(#annotated_members),* + } + + impl #type_ident { + pub fn new(#(#annotated_members),*) -> Self { + Self { + #(#member_names),* + } + } + + #(#accessors)* + } + } +} + +pub fn make_type(build_spec: &BuildSpec) -> TokenStream { + match build_spec { + BuildSpec::Enum(enum_build_spec) => make_enum_type(enum_build_spec), + BuildSpec::Struct(struct_build_spec) => make_struct_type(struct_build_spec), + } +}