use crate::spec::{AlternativeAction, AlternativeBuild, AlternativeBuildChild, AlternativeChild, AlternativeTest, BooleanChildToBuild, BuildSpec, EnumBuildSpec, EnumRule, EnumRuleChild, EnumRuleChildKind, EnumRuleNodeChild, LeafEnumBuildSpec, LeafEnumRule, LeafStructBuildSpec, LeafStructMember, LeafStructMemberKind, MemberChild, MemberChildToBuild, NameAndKind, NodeChildToBuild, PolymorphicBuildAlternative, PolymorphicBuildBuildSpec, PolymorphicEnumBuildSpec, PolymorphicEnumMember, PolymorphicEnumRule, PolymorphicTypeBuildSpec, ProductionBuildSpec, ProductionKind, ProductionStringFrom, SkipChild, StructBuildSpec, StructChildSpec, VecChild, VecChildToBuild, VecNodeChildToBuild}; use convert_case::{Case, Casing}; use yaml_rust2::{Yaml, YamlLoader}; fn get_as_bool(yaml: &Yaml) -> bool { yaml.as_bool().unwrap_or_else(|| false) } fn unwrap_single_member_hash(hash: &Yaml) -> (String, &Yaml) { let as_hash = hash.as_hash().unwrap(); if as_hash.is_empty() { panic!("empty hash"); } else if as_hash.len() > 1 { panic!("hash contains more than one key"); } let (member_key, member_value) = as_hash.iter().collect::>()[0]; let key_as_string = member_key.as_str().unwrap().to_string(); (key_as_string, member_value) } fn deserialize_polymorphic_enum_build_spec(name: &str, build_spec: &Yaml) -> PolymorphicEnumBuildSpec { let return_type = build_spec["return_type"].as_str().unwrap(); let rules = build_spec["rules"] .as_vec() .unwrap() .iter() .map(|rule_yaml| { let (rule_name, props) = unwrap_single_member_hash(rule_yaml); if props["wrap"].is_hash() { PolymorphicEnumRule::Wrap(NameAndKind::new( &rule_name, props["wrap"]["enum_variant"].as_str().unwrap() )) } else if props["return_build"].is_hash() { PolymorphicEnumRule::ReturnBuild(NameAndKind::new( &rule_name, props["return_build"]["kind"].as_str().unwrap() )) } else { panic!() } }) .map(Box::new) .collect::>(); PolymorphicEnumBuildSpec::new(name, return_type, rules) } fn deserialize_polymorphic_action(action_yaml: &Yaml) -> AlternativeAction { if action_yaml["return_build"].is_hash() { let kind = action_yaml["return_build"]["kind"].as_str().unwrap(); AlternativeAction::ReturnBuild(kind.to_string()) } else if action_yaml["build"].is_hash() { let build_children = action_yaml["build"]["children"] .as_vec() .unwrap() .iter() .map(|child_yaml| { let (child_name, child_props) = unwrap_single_member_hash(child_yaml); if get_as_bool(&child_props["skip"]) { AlternativeChild::Skip } else { let kind = child_props["kind"].as_str().unwrap(); let rule = child_props["rule"].as_str().unwrap(); AlternativeChild::Build(AlternativeBuildChild::new(&child_name, kind, rule)) } }) .map(Box::new) .collect(); let enum_variant = action_yaml["build"]["enum_variant"].as_str().unwrap(); let build = AlternativeBuild::new(enum_variant, build_children); AlternativeAction::Build(build) } else { panic!("return_build or build is required for an alternative") } } fn deserialize_polymorphic_build_build_spec( name: &str, polymorphic_build_yaml: &Yaml ) -> PolymorphicBuildBuildSpec { let return_type = polymorphic_build_yaml["return_type"].as_str().unwrap(); let alternatives = polymorphic_build_yaml["alternatives"] .as_vec() .unwrap() .iter() .map(|alternative_yaml| { let number_of_pairs = alternative_yaml["test"]["number_of_pairs"].as_i64().unwrap(); let action = deserialize_polymorphic_action(&alternative_yaml["action"]); PolymorphicBuildAlternative::new( AlternativeTest::NumberOfPairs(number_of_pairs), action ) }) .map(Box::new) .collect(); PolymorphicBuildBuildSpec::new(name, return_type, alternatives) } fn deserialize_polymorphic_enum_members( enum_members_yaml: &Yaml, ) -> Vec> { enum_members_yaml .as_vec() .unwrap() .iter() .map(|enum_member_yaml| { let (member_name, member_hash) = unwrap_single_member_hash(enum_member_yaml); let inner_kind = member_hash["inner"]["kind"].as_str().unwrap(); PolymorphicEnumMember::new(&member_name, inner_kind) }) .map(Box::new) .collect() } fn deserialize_polymorphic_spec(name: &str, spec_props: &Yaml) -> PolymorphicTypeBuildSpec { let enum_members = deserialize_polymorphic_enum_members(&spec_props["enum_members"]); let build_kind = spec_props["build"]["kind"].as_str().unwrap(); PolymorphicTypeBuildSpec::new(name, enum_members, build_kind) } fn deserialize_production_spec(rule: &str, production_yaml: &Yaml) -> ProductionBuildSpec { let kind = if production_yaml["kind"].is_hash() { let node = production_yaml["kind"]["node"].as_str().unwrap(); ProductionKind::Node(node.to_string()) } else { match production_yaml["kind"].as_str().unwrap() { "int" => ProductionKind::Int, "long" => ProductionKind::Long, "double" => ProductionKind::Double, "boolean" => ProductionKind::Boolean, "string" => { let from = match production_yaml["from"].as_str().unwrap() { "string_inner" => ProductionStringFrom::StringInner, "whole_pair" => ProductionStringFrom::WholePair, _ => panic!( "invalid from: {}", production_yaml["from"].as_str().unwrap() ), }; ProductionKind::String(from) } _ => panic!( "invalid kind: {}", production_yaml["kind"].as_str().unwrap() ), } }; ProductionBuildSpec::new(rule, kind) } fn deserialize_leaf_enum_rule(rule_yaml: &Yaml) -> Box { Box::new(LeafEnumRule::new(rule_yaml.as_str().unwrap())) } fn deserialize_leaf_enum_rules(rules_yaml: &Yaml) -> Vec> { rules_yaml .as_vec() .unwrap() .iter() .map(|rule_yaml| deserialize_leaf_enum_rule(rule_yaml)) .collect() } fn deserialize_enum_rule_custom_child(rule_props: &Yaml) -> Option> { if !rule_props["child"].as_bool().unwrap_or(true) { None } else { let kind = match rule_props["kind"].as_str().unwrap() { "int" => EnumRuleChildKind::Int, "long" => EnumRuleChildKind::Long, "double" => EnumRuleChildKind::Double, "usize" => EnumRuleChildKind::USize, "string" => EnumRuleChildKind::String, "boolean" => EnumRuleChildKind::Boolean, _ => panic!( "unsupported enum rule kind: {}", rule_props["kind"].as_str().unwrap() ), }; Some(Box::new(EnumRuleChild::new(Box::new(kind)))) } } fn deserialize_enum_rule_node_child(rule: &str) -> Box { Box::new(EnumRuleChild::new(Box::new(EnumRuleChildKind::Node( EnumRuleNodeChild::new(rule), )))) } fn deserialize_enum_rule(rule_yaml: &Yaml) -> Box { if rule_yaml.is_hash() { let (rule, rule_props) = unwrap_single_member_hash(rule_yaml); Box::new(EnumRule::new( &rule, deserialize_enum_rule_custom_child(rule_props), )) } else { let rule_as_str = rule_yaml.as_str().unwrap(); Box::new(EnumRule::new( rule_as_str, Some(deserialize_enum_rule_node_child(rule_as_str)), )) } } fn deserialize_enum_rules(rules_yaml: &Yaml) -> Vec> { rules_yaml .as_vec() .unwrap() .iter() .map(|rule_yaml| deserialize_enum_rule(rule_yaml)) .collect() } fn deserialize_leaf_struct_member(name: &str, props: &Yaml) -> Box { let kind = props["kind"].as_str().unwrap(); if kind == "string" { Box::new(LeafStructMember::new(name, LeafStructMemberKind::String)) } else { panic!("invalid member kind: {}", kind); } } fn deserialize_leaf_struct_members(members_yaml: &Yaml) -> Vec> { members_yaml .as_vec() .unwrap() .iter() .map(|member| { let (member_name, member_props) = unwrap_single_member_hash(member); deserialize_leaf_struct_member(&member_name, member_props) }) .collect() } fn deserialize_member_node_to_build( rule: &str, build_props: &Yaml, optional: bool, ) -> Box { let or_else = build_props["or_else"] .as_str() .or_else(|| { if get_as_bool(&build_props["or_else_default"]) { Some("default") } else { None } }) .map(ToString::to_string); Box::new(MemberChildToBuild::Node(NodeChildToBuild::new( rule, or_else, optional, ))) } fn deserialize_member_boolean_to_build(name: &str) -> Box { Box::new(MemberChildToBuild::Boolean(BooleanChildToBuild::new(name))) } fn deserialize_member_child_to_build( name: &str, rule: &str, props: &Yaml, optional: bool, ) -> Box { if props["build"].is_hash() { let build_props = &props["build"]; let kind = build_props["kind"].as_str().or(Some("node")).unwrap(); if kind == "node" { deserialize_member_node_to_build(rule, build_props, optional) } else if kind == "boolean" { deserialize_member_boolean_to_build(name) } else { panic!("unsupported kind: {}", kind) } } else { let optional = get_as_bool(&props["optional"]); Box::new(MemberChildToBuild::Node(NodeChildToBuild::new( rule, None, optional, ))) } } fn deserialize_member_child( name: &str, rule: &str, optional: bool, props: &Yaml, ) -> Box { Box::new(StructChildSpec::MemberChild(MemberChild::new( name, rule, deserialize_member_child_to_build(name, rule, props, optional), ))) } fn deserialize_vec_node_child(name: &str, props: &Yaml) -> Box { let rule = props["rule"].as_str().unwrap(); Box::new(StructChildSpec::VecChild(VecChild::new( name, rule, Box::new(VecChildToBuild::Node(VecNodeChildToBuild::new(rule))), ))) } fn deserialize_vec_string_child(name: &str, props: &Yaml) -> Box { let rule = props["rule"].as_str().unwrap(); Box::new(StructChildSpec::VecChild(VecChild::new( name, rule, Box::new(VecChildToBuild::String), ))) } fn deserialize_vec_child(name: &str, props: &Yaml) -> Box { let kind = props["kind"].as_str().or_else(|| Some("node")).unwrap(); if kind == "node" { deserialize_vec_node_child(name, props) } else if kind == "string" { deserialize_vec_string_child(name, props) } else { panic!("invalid kind: {}", kind); } } fn deserialize_skip_child(name: &str, rule: &str) -> Box { Box::new(StructChildSpec::SkipChild(SkipChild::new(name, rule))) } fn deserialize_struct_hash_child(child: &Yaml) -> Box { let (name, props) = unwrap_single_member_hash(child); let rule = props["rule"] .as_str() .map(|s| s.to_string()) .unwrap_or(name.to_case(Case::Pascal)); if get_as_bool(&props["skip"]) { deserialize_skip_child(&name, &rule) } else { if get_as_bool(&props["vec"]) { deserialize_vec_child(&name, props) } else { let optional = get_as_bool(&props["optional"]); deserialize_member_child(&name, &rule, optional, props) } } } fn deserialize_struct_string_child(child: &Yaml) -> Box { let child_as_str = child.as_str().unwrap(); let child_name_pascal = child_as_str.to_case(Case::Pascal); Box::new(StructChildSpec::MemberChild(MemberChild::new( child_as_str, &child_name_pascal, Box::new(MemberChildToBuild::Node(NodeChildToBuild::new( &child_name_pascal, None, false, ))), ))) } fn deserialize_struct_children(children: &Yaml) -> Vec> { children .as_vec() .unwrap() .iter() .map(|child_spec| { if child_spec.is_hash() { deserialize_struct_hash_child(child_spec) } else { deserialize_struct_string_child(child_spec) } }) .collect() } fn deserialize_build_spec(build_spec_name: &str, build_spec: &Yaml) -> BuildSpec { if build_spec["children"].is_array() { let children = deserialize_struct_children(&build_spec["children"]); BuildSpec::Struct(StructBuildSpec::new(build_spec_name, children)) } else if build_spec["members"].is_array() { let members = deserialize_leaf_struct_members(&build_spec["members"]); BuildSpec::LeafStruct(LeafStructBuildSpec::new(build_spec_name, members)) } else if build_spec["rules"].is_array() { let rules = deserialize_enum_rules(&build_spec["rules"]); BuildSpec::Enum(EnumBuildSpec::new(build_spec_name, rules)) } else if build_spec["leaf_rules"].is_array() { let leaf_rules = deserialize_leaf_enum_rules(&build_spec["leaf_rules"]); BuildSpec::LeafEnum(LeafEnumBuildSpec::new(build_spec_name, leaf_rules)) } else if build_spec["produce"].is_hash() { BuildSpec::Production(deserialize_production_spec( build_spec_name, &build_spec["produce"], )) } else if build_spec["polymorphic_type"].is_hash() { BuildSpec::Polymorphic(deserialize_polymorphic_spec( build_spec_name, &build_spec["polymorphic_type"], )) } else if build_spec["polymorphic_build"].is_hash() { BuildSpec::PolymorphicBuild(deserialize_polymorphic_build_build_spec( build_spec_name, &build_spec["polymorphic_build"], )) } else if build_spec["polymorphic_enum"].is_hash() { BuildSpec::PolymorphicEnum(deserialize_polymorphic_enum_build_spec( build_spec_name, &build_spec["polymorphic_enum"], )) } else { panic!( "Expected a node spec for either a struct, leaf_struct, enum, leaf_enum node type, production, polymorphic type, or polymorphic build type." ); } } 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)| { let name_as_str = build_spec_name.as_str().unwrap(); deserialize_build_spec(name_as_str, build_spec) }) .collect() }