Implement most of the selene typedefs generation

This commit is contained in:
Filip Tibell 2023-02-21 22:14:00 +01:00
parent c252db4598
commit f4c55ff2cf
No known key found for this signature in database
6 changed files with 172 additions and 26 deletions

View file

@ -305,7 +305,7 @@ declare process: {
cwd: string,
--[=[
@within process
@new_fields
@read_write
Current environment variables for this process.

View file

@ -71,6 +71,10 @@ impl DocItem {
self.kind.is_tag()
}
pub fn kind(&self) -> DocItemKind {
self.kind
}
pub fn get_name(&self) -> Option<&str> {
self.name.as_deref()
}
@ -82,12 +86,8 @@ impl DocItem {
pub fn get_value(&self) -> Option<&str> {
self.value.as_deref()
}
}
impl IntoIterator for DocItem {
type Item = DocItem;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.children.into_iter()
pub fn children(&self) -> &[DocItem] {
&self.children
}
}

View file

@ -10,6 +10,7 @@ use regex::Regex;
use super::{builder::DocItemBuilder, item::DocItem, kind::DocItemKind};
#[derive(Debug, Clone)]
struct DocVisitorItem {
name: String,
comment: Option<String>,
@ -20,9 +21,9 @@ struct DocVisitorItem {
impl From<DocVisitorItem> for DocItem {
fn from(value: DocVisitorItem) -> Self {
let mut builder = DocItemBuilder::new()
.with_kind(match value.type_info {
.with_kind(match &value.type_info {
TypeInfo::Array { .. } | TypeInfo::Table { .. } => DocItemKind::Table,
TypeInfo::Callback { .. } => DocItemKind::Function,
typ if type_info_is_fn(typ) => DocItemKind::Function,
_ => unimplemented!("Support for globals that are not properties or functions is not yet implemented")
})
.with_name(value.name);
@ -39,7 +40,7 @@ impl From<DocVisitorItem> for DocItem {
builder = builder.with_child(
DocItemBuilder::new()
.with_kind(match field.value() {
TypeInfo::Callback { .. } => DocItemKind::Function,
typ if type_info_is_fn(typ) => DocItemKind::Function,
_ => DocItemKind::Property,
})
.with_name(name.token().to_string())
@ -84,6 +85,18 @@ where
.collect())
}
fn type_info_is_fn(typ: &TypeInfo) -> bool {
match typ {
TypeInfo::Callback { .. } => true,
TypeInfo::Tuple { types, .. } => types.iter().all(type_info_is_fn),
TypeInfo::Union { left, right, .. } => type_info_is_fn(left) && type_info_is_fn(right),
TypeInfo::Intersection { left, right, .. } => {
type_info_is_fn(left) && type_info_is_fn(right)
}
_ => false,
}
}
fn should_separate_tag_meta(tag_kind: &str) -> bool {
matches!(tag_kind.trim().to_ascii_lowercase().as_ref(), "param")
}

View file

@ -12,12 +12,43 @@ pub enum DocsItemTag {
Return(String),
MustUse,
ReadOnly,
NewFields,
ReadWrite,
}
impl TryFrom<DocItem> for DocsItemTag {
#[allow(dead_code)]
impl DocsItemTag {
pub fn is_class(&self) -> bool {
matches!(self, Self::Class(_))
}
pub fn is_within(&self) -> bool {
matches!(self, Self::Within(_))
}
pub fn is_param(&self) -> bool {
matches!(self, Self::Param(_))
}
pub fn is_return(&self) -> bool {
matches!(self, Self::Return(_))
}
pub fn is_must_use(&self) -> bool {
self == &Self::MustUse
}
pub fn is_read_only(&self) -> bool {
self == &Self::ReadOnly
}
pub fn is_read_write(&self) -> bool {
self == &Self::ReadWrite
}
}
impl TryFrom<&DocItem> for DocsItemTag {
type Error = anyhow::Error;
fn try_from(value: DocItem) -> Result<Self> {
fn try_from(value: &DocItem) -> Result<Self> {
if let Some(name) = value.get_name() {
Ok(match name.trim().to_ascii_lowercase().as_ref() {
"class" => Self::Class(
@ -50,7 +81,7 @@ impl TryFrom<DocItem> for DocsItemTag {
),
"must_use" => Self::MustUse,
"read_only" => Self::ReadOnly,
"new_fields" => Self::NewFields,
"read_write" => Self::ReadWrite,
s => bail!("Unknown docs tag: '{}'", s),
})
} else {

View file

@ -46,11 +46,3 @@ impl DerefMut for DocTree {
&mut self.0
}
}
impl IntoIterator for DocTree {
type Item = DocItem;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}

View file

@ -1,9 +1,119 @@
use anyhow::{Context, Result};
use serde_yaml::Value as YamlValue;
use serde_yaml::{Mapping as YamlMapping, Sequence as YamlSequence, Value as YamlValue};
use super::doc::DocumentationVisitor;
use crate::gen::doc2::DocsItemTag;
use super::doc2::{DocItem, DocItemKind, DocTree};
pub fn generate_from_type_definitions(contents: &str) -> Result<String> {
let _visitor = DocumentationVisitor::from_definitions(contents)?;
serde_yaml::to_string(&YamlValue::Null).context("Failed to encode docs as json")
let tree = DocTree::from_type_definitions(contents)?;
let mut globals = YamlMapping::new();
let top_level_items = tree.children().iter().filter(|top_level| {
top_level.is_function()
|| top_level.children().iter().any(|top_level_child| {
top_level_child.is_tag() && top_level_child.get_name().unwrap() == "class"
})
});
for top_level_item in top_level_items {
match top_level_item.kind() {
DocItemKind::Table => {
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()
.filter(|item| item.is_function() || item.is_property())
{
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)?),
);
}
}
DocItemKind::Function => {
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")?
))
}
fn doc_item_to_selene_yaml_mapping(item: &DocItem) -> Result<YamlMapping> {
let mut mapping = YamlMapping::new();
if item.is_property() {
let property_access_tag = item
.children()
.iter()
.find_map(|child| {
if let Ok(tag) = DocsItemTag::try_from(child) {
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 {
DocsItemTag::ReadOnly => "read-only",
DocsItemTag::ReadWrite => "new-fields",
_ => unreachable!(),
}
.to_string(),
),
);
} else if item.is_function() {
let is_must_use = item.children().iter().any(|child| {
if let Ok(tag) = DocsItemTag::try_from(child) {
tag.is_must_use()
} else {
false
}
});
if is_must_use {
mapping.insert(
YamlValue::String("must_use".to_string()),
YamlValue::Bool(true),
);
}
mapping.insert(
YamlValue::String("args".to_string()),
YamlValue::Mapping(YamlMapping::new()),
);
}
Ok(mapping)
}