diff --git a/ast-generator/src/build_fn_gen.rs b/ast-generator/src/build_fn_gen.rs index f5cb49e..3b9c650 100644 --- a/ast-generator/src/build_fn_gen.rs +++ b/ast-generator/src/build_fn_gen.rs @@ -90,6 +90,57 @@ fn make_rule_matcher(child_spec: &ChildSpec) -> TokenStream { } } +fn make_child_arg(child_spec: &ChildSpec) -> Option { + match child_spec { + ChildSpec::SkipChild(_) => None, + ChildSpec::VecChild(vec_child) => { + let child_ident = match vec_child.build() { + VecChildToBuild::Type(vec_type_child) => format_ident!("{}", vec_type_child.var_name()) + }; + Some(quote! { #child_ident }) + }, + ChildSpec::SingleChild(single_child) => { + match single_child.build() { + SingleChildToBuild::Type(single_type_child) => { + let child_ident = format_ident!("{}", single_type_child.var_name()); + if single_type_child.optional() { + Some(quote! { #child_ident }) + } else if let Some(or_else) = single_type_child.or_else() { + let child_type_ident = format_ident!("{}", single_type_child.build()); + let or_else_ident = format_ident!("{}", or_else); + Some(quote! { + #child_ident.unwrap_or_else(|| Box::new(#child_type_ident::#or_else_ident())) + }) + } else { + Some(quote! { #child_ident.unwrap() }) + } + }, + SingleChildToBuild::Boolean(single_boolean_child) => { + let child_ident = format_ident!("{}", single_boolean_child.var_name()); + Some(quote! { #child_ident }) + } + } + } + } +} + +fn make_return_value_stream(build_spec: &StructBuildSpec) -> TokenStream { + let type_ident = format_ident!("{}", build_spec.build()); + let child_args = build_spec + .children() + .iter() + .map(|child| make_child_arg(child)) + .filter(|child_arg| child_arg.is_some()) + .map(|child_arg| child_arg.unwrap()) + .collect::>(); + + quote! { + #type_ident::new( + #(#child_args,)* + ) + } +} + 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)); @@ -117,11 +168,15 @@ pub fn make_struct_build_fn(build_spec: &StructBuildSpec) -> TokenStream { } }; + let new_stream = make_return_value_stream(build_spec); + quote! { fn #build_fn_ident(#pair_ident: Pair) -> #return_type_ident { #(#child_holders;)* #iter_stream + + #new_stream } } } diff --git a/ast-generator/src/deserialize.rs b/ast-generator/src/deserialize.rs index f5bbffa..6230770 100644 --- a/ast-generator/src/deserialize.rs +++ b/ast-generator/src/deserialize.rs @@ -48,34 +48,56 @@ fn get_vec_child(name: &str, rule: &str, build: &Yaml) -> ChildSpec { fn get_single_child_to_build(name: &str, rule: &str, optional: bool, 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") + match build["type"].as_str() { + Some(r#type) => { + 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") + } + }, + None => { + let or_else = build["or_else"] + .as_str() + .map(|s| s.to_string()) + .or_else(|| { + let or_else_default = build["or_else_default"] + .as_bool() + .unwrap_or_else(|| false); + if or_else_default { + Some(String::from("default")) + } else { + None + } + }); + SingleChildToBuild::Type(SingleTypeChildToBuild::from_build_or_rule( + rule, + or_else, + optional, + )) + } } } else { match build.as_str() { - Some(s) => SingleChildToBuild::Type(SingleTypeChildToBuild::from_build_or_rule(s, optional)), - None => SingleChildToBuild::Type(SingleTypeChildToBuild::from_build_or_rule(rule, optional)), + Some(s) => SingleChildToBuild::Type(SingleTypeChildToBuild::from_build_or_rule(s, None, optional)), + None => SingleChildToBuild::Type(SingleTypeChildToBuild::from_build_or_rule(rule, None, optional)), } } } -fn get_single_child(name: &str, rule: &str, optional: bool, or_else: Option, build: &Yaml) -> ChildSpec { +fn get_single_child(name: &str, rule: &str, optional: bool, build: &Yaml) -> ChildSpec { ChildSpec::SingleChild(SingleChild::new( name, rule, get_single_child_to_build(name, rule, optional, build), - or_else )) } @@ -110,21 +132,8 @@ fn get_child_specs(children: &Yaml) -> Vec { let optional = props["optional"] .as_bool() .unwrap_or_else(|| false); - let or_else = props["or_else"] - .as_str() - .map(|s| s.to_string()) - .or_else(|| { - let or_else_default = props["or_else_default"] - .as_bool() - .unwrap_or_else(|| false); - if or_else_default { - Some(String::from("default")) - } else { - None - } - }); - get_single_child(name, &rule, optional, or_else, build) + get_single_child(name, &rule, optional, build) } } else { ChildSpec::SingleChild(SingleChild::from_name_snake(child_spec.as_str().unwrap())) diff --git a/ast-generator/src/lib.rs b/ast-generator/src/lib.rs index c657e1c..6f5716a 100644 --- a/ast-generator/src/lib.rs +++ b/ast-generator/src/lib.rs @@ -9,6 +9,7 @@ use proc_macro2::TokenStream; use quote::quote; use spec::BuildSpec; use syn::File; +use syn::spanned::Spanned; fn debug_built_spec(build_spec: &BuildSpec, token_stream: &TokenStream) { println!("*** BuildSpec ***"); diff --git a/ast-generator/src/spec.rs b/ast-generator/src/spec.rs index f42b85c..96e966a 100644 --- a/ast-generator/src/spec.rs +++ b/ast-generator/src/spec.rs @@ -224,7 +224,6 @@ pub struct SingleChild { name: String, rule: String, build: SingleChildToBuild, - or_else: Option } impl SingleChild { @@ -234,18 +233,17 @@ impl SingleChild { rule: name.to_case(Case::Pascal), build: SingleChildToBuild::Type(SingleTypeChildToBuild::from_build_or_rule( &name.to_case(Case::Pascal), + None, false, )), - or_else: None, } } - pub fn new(name: &str, rule: &str, build: SingleChildToBuild, or_else: Option) -> Self { + pub fn new(name: &str, rule: &str, build: SingleChildToBuild) -> Self { Self { name: name.to_string(), rule: rule.to_string(), build, - or_else, } } @@ -276,15 +274,21 @@ pub struct SingleTypeChildToBuild { build: String, var_name: String, with: String, + or_else: Option, optional: bool, } impl SingleTypeChildToBuild { - pub fn from_build_or_rule(build_or_rule: &str, optional: bool) -> Self { + pub fn from_build_or_rule( + build_or_rule: &str, + or_else: Option, + optional: bool, + ) -> 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), + or_else, optional, } } @@ -304,6 +308,11 @@ impl SingleTypeChildToBuild { &self.with } + /// The default fn to call when unwrapping the child (before passing as arg to new). + pub fn or_else(&self) -> Option<&str> { + self.or_else.as_deref() + } + /// If the type should be wrapped in an Option. pub fn optional(&self) -> bool { self.optional diff --git a/src/parser/ast.schema.yaml b/src/parser/ast.schema.yaml index a62f4ca..d8081f7 100644 --- a/src/parser/ast.schema.yaml +++ b/src/parser/ast.schema.yaml @@ -82,12 +82,6 @@ $defs: optional: type: boolean description: If true, this child will be stored as an Option. - or_else: - type: string - description: The method name to call upon the built-type if the rule is not found. Takes precedence over "or_else_default". - or_else_default: - type: boolean - description: Whether to call the default method on the built-type if the rule is not found. build: oneOf: - type: string @@ -97,7 +91,19 @@ $defs: additionalProperties: false description: A definition of what exactly to build for a given child rule. oneOf: + - $ref: "#/$defs/BuildSingleTypeChild" - $ref: "#/$defs/BuildBooleanChild" + BuildSingleTypeChild: + type: object + additionalProperties: false + description: A definition of a single-type child to build. + properties: + or_else: + type: string + description: The method name to call upon the built-type if the rule is not found. Takes precedence over "or_else_default". + or_else_default: + type: boolean + description: Whether to call the default method on the built-type if the rule is not found. BuildBooleanChild: type: object additionalProperties: false diff --git a/src/parser/ast.yaml b/src/parser/ast.yaml index 41b1894..d4f58ce 100644 --- a/src/parser/ast.yaml +++ b/src/parser/ast.yaml @@ -112,9 +112,11 @@ Interface: skip: true - identifier - generic_parameters: - or_else_default: true + build: + or_else_default: true - implements_list: - or_else_default: true + build: + or_else_default: true - declarations: rule: InterfaceLevelDeclaration vec: true @@ -133,11 +135,14 @@ Class: skip: true - identifier - generic_parameters: - or_else_default: true + build: + or_else_default: true - class_constructor: - or_else_default: true + build: + or_else_default: true - implements_list: - or_else_default: true + build: + or_else_default: true - class_level_declarations: rule: ClassLevelDeclaration vec: true @@ -155,11 +160,11 @@ Function: skip: true - generics: rule: GenericParameters - or_else_default: true - identifier - parameters - return_type: - or_else: void + build: + or_else: void - function_body OperatorFunction: children: @@ -173,11 +178,13 @@ OperatorFunction: skip: true - generics: rule: GenericParameters - or_else_default: true + build: + or_else_default: true - operator - parameters - return_type: - or_else: void + build: + or_else: void - function_body PlatformFunction: children: @@ -194,7 +201,8 @@ PlatformFunction: skip: true - generics: rule: GenericParameters - or_else_default: true + build: + or_else_default: true - identifier - parameters - return_type