2023-02-16 11:28:17 +00:00
|
|
|
use anyhow::{Context, Result};
|
2023-02-22 11:18:30 +00:00
|
|
|
use full_moon::tokenizer::Symbol;
|
2023-02-21 21:14:00 +00:00
|
|
|
use serde_yaml::{Mapping as YamlMapping, Sequence as YamlSequence, Value as YamlValue};
|
2023-02-16 11:28:17 +00:00
|
|
|
|
2023-02-22 09:53:00 +00:00
|
|
|
use crate::gen::definitions::DefinitionsItemTag;
|
2023-02-21 21:14:00 +00:00
|
|
|
|
2023-02-22 11:18:30 +00:00
|
|
|
use super::definitions::{DefinitionsItem, DefinitionsItemKind, DefinitionsTree};
|
2023-02-16 11:28:17 +00:00
|
|
|
|
2023-02-26 20:33:49 +00:00
|
|
|
const USE_TYPE_UNIONS: bool = false;
|
|
|
|
|
2023-02-16 11:28:17 +00:00
|
|
|
pub fn generate_from_type_definitions(contents: &str) -> Result<String> {
|
2023-02-22 09:53:00 +00:00
|
|
|
let tree = DefinitionsTree::from_type_definitions(contents)?;
|
2023-02-21 21:14:00 +00:00
|
|
|
let mut globals = YamlMapping::new();
|
2023-02-22 09:48:04 +00:00
|
|
|
let top_level_exported_items = tree.children().iter().filter(|top_level| {
|
|
|
|
top_level.is_exported()
|
|
|
|
&& (top_level.is_function()
|
|
|
|
|| top_level.children().iter().any(|top_level_child| {
|
|
|
|
top_level_child.is_tag() && top_level_child.get_name().unwrap() == "class"
|
|
|
|
}))
|
2023-02-21 21:14:00 +00:00
|
|
|
});
|
2023-02-22 09:48:04 +00:00
|
|
|
for top_level_item in top_level_exported_items {
|
2023-02-21 21:14:00 +00:00
|
|
|
match top_level_item.kind() {
|
2023-02-22 09:53:00 +00:00
|
|
|
DefinitionsItemKind::Table => {
|
2023-02-21 21:14:00 +00:00
|
|
|
let top_level_name = top_level_item
|
|
|
|
.get_name()
|
|
|
|
.context("Missing name for top-level doc item")?
|
|
|
|
.to_string();
|
|
|
|
for child_item in top_level_item
|
|
|
|
.children()
|
|
|
|
.iter()
|
2023-02-22 09:48:04 +00:00
|
|
|
.filter(|item| item.is_function() || item.is_table() || item.is_property())
|
2023-02-21 21:14:00 +00:00
|
|
|
{
|
|
|
|
let child_name = child_item
|
|
|
|
.get_name()
|
|
|
|
.context("Missing name for top-level child doc item")?
|
|
|
|
.to_string();
|
|
|
|
globals.insert(
|
|
|
|
YamlValue::String(format!("{top_level_name}.{child_name}")),
|
|
|
|
YamlValue::Mapping(doc_item_to_selene_yaml_mapping(child_item)?),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2023-02-22 09:53:00 +00:00
|
|
|
DefinitionsItemKind::Function => {
|
2023-02-21 21:14:00 +00:00
|
|
|
globals.insert(
|
|
|
|
YamlValue::String(
|
|
|
|
top_level_item
|
|
|
|
.get_name()
|
|
|
|
.context("Missing name for top-level doc item")?
|
|
|
|
.to_string(),
|
|
|
|
),
|
|
|
|
YamlValue::Mapping(doc_item_to_selene_yaml_mapping(top_level_item)?),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
_ => unimplemented!("Globals other than tables and functions are not yet implemented"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let mut contents = YamlMapping::new();
|
|
|
|
contents.insert(
|
|
|
|
YamlValue::String("globals".to_string()),
|
|
|
|
YamlValue::Mapping(globals),
|
|
|
|
);
|
|
|
|
Ok(format!(
|
|
|
|
"# Lune v{}\n---\n{}",
|
|
|
|
env!("CARGO_PKG_VERSION"),
|
|
|
|
serde_yaml::to_string(&contents).context("Failed to encode type definitions as yaml")?
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
2023-02-22 09:53:00 +00:00
|
|
|
fn doc_item_to_selene_yaml_mapping(item: &DefinitionsItem) -> Result<YamlMapping> {
|
2023-02-21 21:14:00 +00:00
|
|
|
let mut mapping = YamlMapping::new();
|
2023-02-22 09:48:04 +00:00
|
|
|
if item.is_property() || item.is_table() {
|
2023-02-21 21:14:00 +00:00
|
|
|
let property_access_tag = item
|
|
|
|
.children()
|
|
|
|
.iter()
|
|
|
|
.find_map(|child| {
|
2023-02-22 09:53:00 +00:00
|
|
|
if let Ok(tag) = DefinitionsItemTag::try_from(child) {
|
2023-02-21 21:14:00 +00:00
|
|
|
if tag.is_read_only() || tag.is_read_write() {
|
|
|
|
Some(tag)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.with_context(|| {
|
|
|
|
format!(
|
|
|
|
"Missing property access tag for doc item:\n{}",
|
|
|
|
item.get_name().unwrap()
|
|
|
|
)
|
|
|
|
})?;
|
|
|
|
mapping.insert(
|
|
|
|
YamlValue::String("property".to_string()),
|
|
|
|
YamlValue::String(
|
|
|
|
match property_access_tag {
|
2023-02-22 09:53:00 +00:00
|
|
|
DefinitionsItemTag::ReadOnly => "read-only",
|
|
|
|
DefinitionsItemTag::ReadWrite => "new-fields",
|
2023-02-21 21:14:00 +00:00
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
|
|
|
.to_string(),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
} else if item.is_function() {
|
|
|
|
let is_must_use = item.children().iter().any(|child| {
|
2023-02-22 09:53:00 +00:00
|
|
|
if let Ok(tag) = DefinitionsItemTag::try_from(child) {
|
2023-02-21 21:14:00 +00:00
|
|
|
tag.is_must_use()
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if is_must_use {
|
|
|
|
mapping.insert(
|
|
|
|
YamlValue::String("must_use".to_string()),
|
|
|
|
YamlValue::Bool(true),
|
|
|
|
);
|
|
|
|
}
|
2023-02-22 09:11:36 +00:00
|
|
|
let mut args = YamlSequence::new();
|
|
|
|
for arg_type in item.arg_types() {
|
|
|
|
let mut arg_mapping = YamlMapping::new();
|
|
|
|
let (type_str, type_opt) = match arg_type.strip_suffix('?') {
|
|
|
|
Some(stripped) => (stripped, true),
|
|
|
|
None => (arg_type, false),
|
|
|
|
};
|
|
|
|
if type_opt {
|
|
|
|
arg_mapping.insert(
|
|
|
|
YamlValue::String("required".to_string()),
|
|
|
|
YamlValue::Bool(false),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
arg_mapping.insert(
|
|
|
|
YamlValue::String("type".to_string()),
|
|
|
|
YamlValue::String(simplify_type_str_into_primitives(
|
|
|
|
type_str.trim_start_matches('(').trim_end_matches(')'),
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
args.push(YamlValue::Mapping(arg_mapping));
|
|
|
|
}
|
2023-02-21 21:14:00 +00:00
|
|
|
mapping.insert(
|
|
|
|
YamlValue::String("args".to_string()),
|
2023-02-22 09:11:36 +00:00
|
|
|
YamlValue::Sequence(args),
|
2023-02-21 21:14:00 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
Ok(mapping)
|
2023-02-16 11:28:17 +00:00
|
|
|
}
|
2023-02-22 09:11:36 +00:00
|
|
|
|
|
|
|
fn simplify_type_str_into_primitives(type_str: &str) -> String {
|
2023-02-22 11:18:30 +00:00
|
|
|
let separator = format!(" {} ", Symbol::Pipe);
|
|
|
|
// Simplify type strings even further into ones that selene can understand,
|
2023-02-26 20:33:49 +00:00
|
|
|
// turning types such as `{ bool }` or `"string-literal"` into `table` and `string`
|
2023-02-22 09:11:36 +00:00
|
|
|
let mut primitives = Vec::new();
|
2023-02-22 11:18:30 +00:00
|
|
|
for type_inner in type_str.split(&separator) {
|
2023-02-22 09:11:36 +00:00
|
|
|
if type_inner.starts_with('{') && type_inner.ends_with('}') {
|
2023-02-26 20:33:49 +00:00
|
|
|
primitives.push("table".to_string());
|
2023-02-22 09:11:36 +00:00
|
|
|
} else if type_inner.starts_with('"') && type_inner.ends_with('"') {
|
2023-02-22 11:18:30 +00:00
|
|
|
primitives.push("string".to_string());
|
2023-02-26 20:33:49 +00:00
|
|
|
} else if type_inner == "boolean" {
|
|
|
|
primitives.push("bool".to_string());
|
2023-02-22 09:11:36 +00:00
|
|
|
} else {
|
2023-02-22 11:18:30 +00:00
|
|
|
primitives.push(type_inner.to_string());
|
2023-02-22 09:11:36 +00:00
|
|
|
}
|
|
|
|
}
|
2023-02-26 20:33:49 +00:00
|
|
|
if primitives.len() > 1 && !USE_TYPE_UNIONS {
|
|
|
|
"any".to_string()
|
|
|
|
} else {
|
|
|
|
primitives.sort_unstable();
|
|
|
|
primitives.dedup();
|
|
|
|
primitives.join(&separator)
|
|
|
|
}
|
2023-02-22 09:11:36 +00:00
|
|
|
}
|