use llmcc_core::context::CompileUnit;
use llmcc_core::ir::{HirId, HirNode};
use tree_sitter::Node;
use super::function::{parse_type_expr, FnVisibility, TypeExpr};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StructKind {
Named,
Tuple,
Unit,
}
#[derive(Debug, Clone)]
pub struct StructDescriptor {
pub hir_id: HirId,
pub name: String,
pub fqn: String,
pub visibility: FnVisibility,
pub generics: Option<String>,
pub fields: Vec<StructField>,
pub kind: StructKind,
}
#[derive(Debug, Clone)]
pub struct StructField {
pub name: Option<String>,
pub ty: Option<TypeExpr>,
}
impl StructDescriptor {
pub fn from_struct<'tcx>(
unit: CompileUnit<'tcx>,
node: &HirNode<'tcx>,
fqn: String,
) -> Option<Self> {
let ts_node = match node.inner_ts_node() {
ts if ts.kind() == "struct_item" => ts,
_ => return None,
};
#[cfg(test)]
eprintln!(
"struct_item fields? {} tuple? {} sexp: {}",
ts_node.child_by_field_name("fields").is_some(),
ts_node.child_by_field_name("body").is_some(),
ts_node.to_sexp()
);
let name_node = ts_node.child_by_field_name("name")?;
let name = clean(&node_text(unit, name_node));
let header_text = unit
.file()
.get_text(ts_node.start_byte(), name_node.start_byte());
let visibility = FnVisibility::from_header(&header_text);
let generics = ts_node
.child_by_field_name("type_parameters")
.map(|n| clean(&node_text(unit, n)));
let (fields, kind) = parse_struct_fields(unit, ts_node);
Some(StructDescriptor {
hir_id: node.hir_id(),
name,
fqn,
visibility,
generics,
fields,
kind,
})
}
}
fn parse_struct_fields<'tcx>(
unit: CompileUnit<'tcx>,
node: Node<'tcx>,
) -> (Vec<StructField>, StructKind) {
match node.kind() {
"field_declaration_list" => return (parse_named_fields(unit, node), StructKind::Named),
"tuple_field_declaration_list" | "ordered_field_declaration_list" => {
return (parse_tuple_fields(unit, node), StructKind::Tuple)
}
_ => {}
}
let mut named = Vec::new();
let mut tuple = Vec::new();
let child_count = node.child_count();
for i in 0..child_count {
if let Some(child) = node.child(i) {
match child.kind() {
"field_declaration_list" => {
return (parse_named_fields(unit, child), StructKind::Named)
}
"tuple_field_declaration_list" | "ordered_field_declaration_list" => {
return (parse_tuple_fields(unit, child), StructKind::Tuple)
}
"field_declaration" => named.push(parse_named_field_node(unit, child)),
"tuple_field_declaration" | "ordered_field_declaration" => {
tuple.push(parse_tuple_field_node(unit, child))
}
_ => {
let (fields, kind) = parse_struct_fields(unit, child);
match kind {
StructKind::Named if !fields.is_empty() => {
return (fields, StructKind::Named)
}
StructKind::Tuple if !fields.is_empty() => {
return (fields, StructKind::Tuple)
}
_ => {}
}
}
}
}
}
if !named.is_empty() {
return (named, StructKind::Named);
}
if !tuple.is_empty() {
return (tuple, StructKind::Tuple);
}
(Vec::new(), StructKind::Unit)
}
fn parse_named_fields<'tcx>(unit: CompileUnit<'tcx>, list: Node<'tcx>) -> Vec<StructField> {
let mut fields = Vec::new();
let mut cursor = list.walk();
for child in list.named_children(&mut cursor) {
if child.kind() == "field_declaration" {
let name = child
.child_by_field_name("name")
.map(|n| clean(&node_text(unit, n)));
let ty = child
.child_by_field_name("type")
.map(|n| parse_type_expr(unit, n));
fields.push(StructField { name, ty });
}
}
fields
}
fn parse_tuple_fields<'tcx>(unit: CompileUnit<'tcx>, list: Node<'tcx>) -> Vec<StructField> {
let mut fields = Vec::new();
let mut cursor = list.walk();
for child in list.children(&mut cursor) {
match child.kind() {
"tuple_field_declaration" | "ordered_field_declaration" => {
let ty = child
.child_by_field_name("type")
.map(|n| parse_type_expr(unit, n))
.or_else(|| {
child
.children(&mut child.walk())
.find_map(|n| match n.kind() {
"type_identifier"
| "primitive_type"
| "scoped_type_identifier"
| "generic_type"
| "tuple_type"
| "reference_type"
| "impl_trait_type" => Some(parse_type_expr(unit, n)),
_ => None,
})
});
fields.push(StructField { name: None, ty });
}
kind if is_type_kind(kind) => {
fields.push(StructField {
name: None,
ty: Some(parse_type_expr(unit, child)),
});
}
_ => {}
}
}
fields
}
fn is_type_kind(kind: &str) -> bool {
matches!(
kind,
"type_identifier"
| "primitive_type"
| "scoped_type_identifier"
| "generic_type"
| "tuple_type"
| "reference_type"
| "impl_trait_type"
)
}
fn node_text<'tcx>(unit: CompileUnit<'tcx>, node: Node<'tcx>) -> String {
unit.file().get_text(node.start_byte(), node.end_byte())
}
fn clean(text: &str) -> String {
let mut out = String::new();
let mut last_was_ws = false;
for ch in text.chars() {
if ch.is_whitespace() {
if !last_was_ws && !out.is_empty() {
out.push(' ');
}
last_was_ws = true;
} else {
out.push(ch);
last_was_ws = false;
}
}
out.trim().to_string()
}
fn parse_named_field_node<'tcx>(unit: CompileUnit<'tcx>, node: Node<'tcx>) -> StructField {
let name = node
.child_by_field_name("name")
.map(|n| clean(&node_text(unit, n)));
let ty = node
.child_by_field_name("type")
.map(|n| parse_type_expr(unit, n));
StructField { name, ty }
}
fn parse_tuple_field_node<'tcx>(unit: CompileUnit<'tcx>, node: Node<'tcx>) -> StructField {
let ty = node
.child_by_field_name("type")
.map(|n| parse_type_expr(unit, n));
StructField { name: None, ty }
}