use spec::*;
use util::*;
use std::collections::{HashMap, HashSet};
use itertools::Itertools;
pub struct TypeDeanonymizer {
builder: SpecBuilder,
supersums_of: HashMap<NodeName, HashSet<NodeName>>,
}
impl TypeDeanonymizer {
pub fn new(spec: &Spec) -> Self {
let mut result = TypeDeanonymizer {
builder: SpecBuilder::new(),
supersums_of: HashMap::new(),
};
let mut skip_name_map: HashMap<&FieldName, FieldName> = HashMap::new();
for (_, name) in spec.field_names() {
result.builder.import_field_name(name)
}
for (_, interface) in spec.interfaces_by_name() {
for field in interface.contents().fields() {
if field.is_lazy() {
let skip_name = result
.builder
.field_name(format!("{}_skip", field.name().to_str()).to_str());
skip_name_map.insert(field.name(), skip_name);
}
}
}
for (name, interface) in spec.interfaces_by_name() {
result.builder.import_node_name(name);
let mut fields = vec![];
for field in interface.contents().fields() {
result.import_type(spec, field.type_(), None);
fields.push(field.clone());
}
let mut declaration = result.builder.add_interface(name).unwrap();
for field in fields.drain(..) {
if field.is_lazy() {
declaration.with_field(
skip_name_map.get(field.name()).unwrap(),
Type::offset().required(),
);
}
declaration.with_field_laziness(
field.name(),
field.type_().clone(),
field.laziness(),
);
}
if let Some(ref field_name) = interface.scoped_dictionary() {
declaration.with_scoped_dictionary(field_name);
}
}
for (name, definition) in spec.typedefs_by_name() {
result.builder.import_node_name(name);
if result.builder.get_typedef(name).is_some() {
continue;
}
result.import_type(spec, &definition, Some(name.clone()));
}
for (name, definition) in spec.string_enums_by_name() {
result.builder.import_node_name(name);
let mut strings: Vec<_> = definition.strings().iter().collect();
let mut declaration = result.builder.add_string_enum(name).unwrap();
for string in strings.drain(..) {
declaration.with_string(&string);
}
}
debug!(target: "export_utils", "Names: {:?}", result.builder.names().keys().format(", "));
result
}
pub fn supersums(&self) -> &HashMap<NodeName, HashSet<NodeName>> {
&self.supersums_of
}
pub fn into_spec(self, options: SpecOptions) -> Spec {
self.builder.into_spec(options)
}
pub fn get_node_name(&self, name: &str) -> Option<NodeName> {
self.builder.get_node_name(name)
}
fn import_type(
&mut self,
spec: &Spec,
type_: &Type,
public_name: Option<NodeName>,
) -> (Option<HashSet<NodeName>>, NodeName) {
debug!(target: "export_utils", "import_type {:?} => {:?}", public_name, type_);
if type_.is_optional() {
let (_, spec_name) = self.import_typespec(spec, &type_.spec, None);
let my_name = match public_name {
None => self.builder.node_name(&format!("Optional{}", spec_name)),
Some(ref name) => name.clone(),
};
let deanonymized = Type::named(&spec_name).optional().unwrap();
if let Some(ref mut typedef) = self.builder.add_typedef(&my_name) {
debug!(target: "export_utils", "import_type introduced {:?}", my_name);
typedef.with_type(deanonymized.clone());
} else {
debug!(target: "export_utils", "import_type: Attempting to redefine typedef {name}", name = my_name.to_str());
}
(None, my_name)
} else {
self.import_typespec(spec, &type_.spec, public_name)
}
}
fn import_typespec(
&mut self,
spec: &Spec,
type_spec: &TypeSpec,
public_name: Option<NodeName>,
) -> (Option<HashSet<NodeName>>, NodeName) {
debug!(target: "export_utils", "import_typespec {:?} => {:?}", public_name, type_spec);
match *type_spec {
TypeSpec::Boolean
| TypeSpec::Number
| TypeSpec::UnsignedLong
| TypeSpec::PropertyKey
| TypeSpec::IdentifierName
| TypeSpec::String
| TypeSpec::Offset
| TypeSpec::Void => {
if let Some(ref my_name) = public_name {
if let Some(ref mut typedef) = self.builder.add_typedef(&my_name) {
debug!(target: "export_utils", "import_typespec: Defining {name} (primitive)", name = my_name.to_str());
typedef.with_type(type_spec.clone().required());
} else {
debug!(target: "export_utils", "import_typespec: Attempting to redefine typedef {name}", name = my_name.to_str());
}
}
let name = match *type_spec {
TypeSpec::PropertyKey => self.builder.node_name("PropertyKey"),
TypeSpec::IdentifierName => self.builder.node_name("IdentifierName"),
_ => self.builder.node_name(&format!("@@{:?}", type_spec)),
};
(None, name)
}
TypeSpec::NamedType(ref link) => {
let resolved = spec.get_type_by_name(link)
.unwrap_or_else(|| panic!("While deanonymizing, could not find the definition of {} in the original spec.", link.to_str()));
let (sum, rewrite, primitive) = match resolved {
NamedType::StringEnum(_) => {
(None, None, None)
}
NamedType::Typedef(ref type_) => {
let (sum, name) = self.import_type(spec, type_, Some(link.clone()));
(sum, Some(name), type_.get_primitive(spec))
}
NamedType::Interface(_) => {
let sum = [link.clone()].iter().cloned().collect();
(Some(sum), None, None)
}
};
debug!(target: "export_utils", "import_typespec dealing with named type {}, public name {:?} => {:?}",
link, public_name, rewrite);
if let Some(ref my_name) = public_name {
if let Some(content) = rewrite {
let deanonymized = match primitive {
None
| Some(IsNullable {
is_nullable: true, ..
})
| Some(IsNullable {
content: Primitive::Interface(_),
..
}) => Type::named(&content).required(),
Some(IsNullable {
content: Primitive::String,
..
}) => Type::string().required(),
Some(IsNullable {
content: Primitive::IdentifierName,
..
}) => Type::identifier_name().required(),
Some(IsNullable {
content: Primitive::PropertyKey,
..
}) => Type::property_key().required(),
Some(IsNullable {
content: Primitive::Number,
..
}) => Type::number().required(),
Some(IsNullable {
content: Primitive::UnsignedLong,
..
}) => Type::unsigned_long().required(),
Some(IsNullable {
content: Primitive::Boolean,
..
}) => Type::bool().required(),
Some(IsNullable {
content: Primitive::Offset,
..
}) => Type::offset().required(),
Some(IsNullable {
content: Primitive::Void,
..
}) => Type::void().required(),
};
debug!(target: "export_utils", "import_typespec aliasing {:?} => {:?}",
my_name, deanonymized);
if let Some(ref mut typedef) = self.builder.add_typedef(&my_name) {
debug!(target: "export_utils", "import_typespec: Defining {name} (name to content)", name = my_name.to_str());
typedef.with_type(deanonymized.clone());
} else {
debug!(target: "export_utils", "import_typespec: Attempting to redefine typedef {name}", name = my_name.to_str());
}
}
let deanonymized = Type::named(link).required();
if let Some(ref mut typedef) = self.builder.add_typedef(&my_name) {
debug!(target: "export_utils", "import_typespec: Defining {name} (name to link)", name = my_name.to_str());
typedef.with_type(deanonymized.clone());
} else {
debug!(target: "export_utils", "import_typespec: Attempting to redefine typedef {name}", name = my_name.to_str());
}
}
(sum, link.clone())
}
TypeSpec::Array {
ref contents,
ref supports_empty,
} => {
let (_, contents_name) = self.import_type(spec, contents, None);
let my_name = match public_name {
None => self.builder.node_name(&format!(
"{non_empty}ListOf{content}",
non_empty = if *supports_empty { "" } else { "NonEmpty" },
content = contents_name.to_str()
)),
Some(ref name) => name.clone(),
};
let deanonymized = if *supports_empty {
Type::named(&contents_name).array()
} else {
Type::named(&contents_name).non_empty_array()
};
if let Some(ref mut typedef) = self.builder.add_typedef(&my_name) {
debug!(target: "export_utils", "import_typespec: Defining {name} (name to list)",
name = my_name.to_str());
typedef.with_type(deanonymized.clone());
} else {
debug!(target: "export_utils", "import_typespec: Attempting to redefine typedef {name}", name = my_name.to_str());
}
(None, my_name)
}
TypeSpec::TypeSum(ref sum) => {
let mut full_sum = HashSet::new();
let mut names = vec![];
let mut subsums = vec![];
for sub_type in sum.types() {
let (sub_sum, name) = self.import_typespec(spec, sub_type, None);
let mut sub_sum = sub_sum.unwrap_or_else(
|| panic!("While treating {:?}, attempting to create a sum containing {}, which isn't an interface or a sum of interfaces", type_spec, name)
);
if sub_sum.len() > 1 {
subsums.push(name.clone())
}
names.push(name);
for item in sub_sum.drain() {
full_sum.insert(item);
}
}
let my_name = match public_name {
None => self.builder.node_name(&format!(
"{}",
names.into_iter().sorted().into_iter().format("Or")
)),
Some(ref name) => name.clone(),
};
for subsum_name in subsums {
let supersum_entry = self
.supersums_of
.entry(subsum_name.clone())
.or_insert_with(|| HashSet::new());
supersum_entry.insert(my_name.clone());
}
let sum: Vec<_> = full_sum.iter().map(Type::named).collect();
let deanonymized = Type::sum(&sum).required();
if let Some(ref mut typedef) = self.builder.add_typedef(&my_name) {
debug!(target: "export_utils", "import_typespec: Defining {name} (name to sum)", name = my_name.to_str());
typedef.with_type(deanonymized.clone());
} else {
debug!(target: "export_utils", "import_type: Attempting to redefine typedef {name}", name = my_name.to_str());
}
(Some(full_sum), my_name)
}
}
}
}
pub struct TypeName;
impl TypeName {
pub fn type_(type_: &Type) -> String {
let spec_name = Self::type_spec(type_.spec());
if type_.is_optional() {
format!("Optional{}", spec_name)
} else {
spec_name
}
}
pub fn type_spec(spec: &TypeSpec) -> String {
match *spec {
TypeSpec::Array {
ref contents,
supports_empty: false,
} => format!("NonEmptyListOf{}", Self::type_(contents)),
TypeSpec::Array {
ref contents,
supports_empty: true,
} => format!("ListOf{}", Self::type_(contents)),
TypeSpec::NamedType(ref name) => name.to_string().clone(),
TypeSpec::Offset => "_Offset".to_string(),
TypeSpec::Boolean => "_Bool".to_string(),
TypeSpec::Number => "_Number".to_string(),
TypeSpec::UnsignedLong => "_UnsignedLong".to_string(),
TypeSpec::String => "_String".to_string(),
TypeSpec::Void => "_Void".to_string(),
TypeSpec::IdentifierName => "IdentifierName".to_string(),
TypeSpec::PropertyKey => "PropertyKey".to_string(),
TypeSpec::TypeSum(ref sum) => format!(
"{}",
sum.types()
.iter()
.map(Self::type_spec)
.sorted()
.into_iter()
.format("Or")
),
}
}
}
pub struct ToWebidl;
impl ToWebidl {
pub fn spec(spec: &TypeSpec, prefix: &str, indent: &str) -> Option<String> {
let result = match *spec {
TypeSpec::Offset => {
return None;
}
TypeSpec::Array {
ref contents,
ref supports_empty,
} => match Self::type_(&*contents, prefix, indent) {
None => {
return None;
}
Some(description) => format!(
"{emptiness}FrozenArray<{}>",
description,
emptiness = if *supports_empty { "" } else { "[NonEmpty] " }
),
},
TypeSpec::Boolean => "bool".to_string(),
TypeSpec::String => "string".to_string(),
TypeSpec::PropertyKey => "[PropertyKey] string".to_string(),
TypeSpec::IdentifierName => "[IdentifierName] string".to_string(),
TypeSpec::Number => "number".to_string(),
TypeSpec::UnsignedLong => "unsigned long".to_string(),
TypeSpec::NamedType(ref name) => name.to_str().to_string(),
TypeSpec::TypeSum(ref sum) => format!(
"({})",
sum.types()
.iter()
.filter_map(|x| Self::spec(x, "", indent))
.format(" or ")
),
TypeSpec::Void => "void".to_string(),
};
Some(result)
}
pub fn type_(type_: &Type, prefix: &str, indent: &str) -> Option<String> {
let pretty_type = Self::spec(type_.spec(), prefix, indent);
match pretty_type {
None => None,
Some(pretty_type) => Some(format!(
"{}{}",
pretty_type,
if type_.is_optional() { "?" } else { "" }
)),
}
}
pub fn interface(interface: &Interface, prefix: &str, indent: &str) -> String {
let mut result = format!(
"{prefix} interface {name} : Node {{\n",
prefix = prefix,
name = interface.name().to_str()
);
{
let prefix = format!("{prefix}{indent}", prefix = prefix, indent = indent);
for field in interface.contents().fields() {
match Self::type_(field.type_(), &prefix, indent) {
None =>
{}
Some(description) => {
if let Some(ref doc) = field.doc() {
result.push_str(&format!(
"{prefix}// {doc}\n",
prefix = prefix,
doc = doc
));
}
result.push_str(&format!(
"{prefix}{description} {name};\n",
prefix = prefix,
name = field.name().to_str(),
description = description
));
if field.doc().is_some() {
result.push_str("\n");
}
}
}
}
}
result.push_str(&format!("{prefix} }}\n", prefix = prefix));
result
}
}