use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::Path;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LuxonManifest {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub structs: HashMap<String, LuxonStruct>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub enums: HashMap<String, LuxonEnum>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub functions: HashMap<String, LuxonFunction>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LuxonStruct {
pub fields: HashMap<String, String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub derives: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LuxonEnum {
pub variants: HashMap<String, LuxonVariant>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub derives: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum LuxonVariant {
Unit,
Tuple(Vec<String>),
Struct(HashMap<String, String>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LuxonFunction {
pub params: Vec<LuxonParam>,
#[serde(skip_serializing_if = "Option::is_none")]
pub returns: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub type_params: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LuxonParam {
pub name: String,
#[serde(rename = "type")]
pub ty: String,
}
impl LuxonManifest {
pub fn new(name: String) -> Self {
Self {
name,
version: None,
structs: HashMap::new(),
enums: HashMap::new(),
functions: HashMap::new(),
}
}
pub fn load(path: &Path) -> Result<Self, String> {
let content = fs::read_to_string(path)
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
serde_json::from_str(&content)
.map_err(|e| format!("Failed to parse {}: {}", path.display(), e))
}
pub fn save(&self, path: &Path) -> Result<(), String> {
let content = serde_json::to_string_pretty(self)
.map_err(|e| format!("Failed to serialize manifest: {}", e))?;
fs::write(path, content)
.map_err(|e| format!("Failed to write {}: {}", path.display(), e))
}
pub fn add_struct(&mut self, name: String, fields: HashMap<String, String>, derives: Vec<String>) {
self.structs.insert(name, LuxonStruct { fields, derives });
}
pub fn add_enum(&mut self, name: String, variants: HashMap<String, LuxonVariant>, derives: Vec<String>) {
self.enums.insert(name, LuxonEnum { variants, derives });
}
pub fn add_function(&mut self, name: String, params: Vec<LuxonParam>, returns: Option<String>, type_params: Vec<String>) {
self.functions.insert(name, LuxonFunction { params, returns, type_params });
}
}
pub fn type_to_luxon_string(ty: &crate::parser::Type) -> String {
use crate::parser::Type;
match ty {
Type::Primitive(name) => name.clone(),
Type::Named(name) => name.clone(),
Type::Reference { mutable, inner } => {
if *mutable {
format!("&mut {}", type_to_luxon_string(inner))
} else {
format!("&{}", type_to_luxon_string(inner))
}
}
Type::Optional(inner) => format!("Option<{}>", type_to_luxon_string(inner)),
Type::Container { name, type_args } => {
if type_args.is_empty() {
name.clone()
} else {
let args: Vec<String> = type_args.iter().map(type_to_luxon_string).collect();
format!("{}<{}>", name, args.join(", "))
}
}
Type::Tuple(types) => {
let inner: Vec<String> = types.iter().map(type_to_luxon_string).collect();
format!("({})", inner.join(", "))
}
Type::Array { element } => {
format!("[{}]", type_to_luxon_string(element))
}
Type::FnTrait { params, return_type } => {
let params_str: Vec<String> = params.iter().map(type_to_luxon_string).collect();
format!("Fn({}) -> {}", params_str.join(", "), type_to_luxon_string(return_type))
}
Type::RawPointer { mutable, inner } => {
if *mutable {
format!("*mut {}", type_to_luxon_string(inner))
} else {
format!("*const {}", type_to_luxon_string(inner))
}
}
Type::AstNode(name) => name.clone(),
Type::Unit => "()".to_string(),
}
}
pub fn extract_manifest(program: &crate::parser::Program, name: String) -> LuxonManifest {
extract_manifest_with_base_dir(program, name, std::path::Path::new("."))
}
pub fn extract_manifest_with_base_dir(program: &crate::parser::Program, name: String, base_dir: &Path) -> LuxonManifest {
use crate::parser::{TopLevelDecl, PluginItem, EnumVariantFields};
let mut manifest = LuxonManifest::new(name);
let items: &[PluginItem] = match &program.decl {
TopLevelDecl::Plugin(p) => &p.body,
TopLevelDecl::Writer(w) => &w.body,
TopLevelDecl::Module(m) => &m.items,
TopLevelDecl::Interface(_) => return manifest,
};
for item in items {
match item {
PluginItem::Struct(s) => {
let fields: HashMap<String, String> = s.fields.iter()
.map(|f| (f.name.clone(), type_to_luxon_string(&f.ty)))
.collect();
manifest.add_struct(s.name.clone(), fields, s.derives.clone());
}
PluginItem::Enum(e) => {
let variants: HashMap<String, LuxonVariant> = e.variants.iter()
.map(|v| {
let variant = match &v.fields {
EnumVariantFields::Unit => LuxonVariant::Unit,
EnumVariantFields::Tuple(types) => {
LuxonVariant::Tuple(types.iter().map(type_to_luxon_string).collect())
}
EnumVariantFields::Struct(fields) => {
LuxonVariant::Struct(fields.iter()
.map(|(name, ty)| (name.clone(), type_to_luxon_string(ty)))
.collect())
}
};
(v.name.clone(), variant)
})
.collect();
manifest.add_enum(e.name.clone(), variants, e.derives.clone());
}
PluginItem::Function(f) => {
let params: Vec<LuxonParam> = f.params.iter()
.map(|p| LuxonParam {
name: p.name.clone(),
ty: type_to_luxon_string(&p.ty),
})
.collect();
let returns = f.return_type.as_ref().map(type_to_luxon_string);
let type_params: Vec<String> = f.type_params.iter()
.map(|p| p.name.clone())
.collect();
manifest.add_function(f.name.clone(), params, returns, type_params);
}
PluginItem::PubUse(use_stmt) => {
if use_stmt.path.starts_with("./") || use_stmt.path.starts_with("../") {
let stripped_path = use_stmt.path.trim_start_matches("./").trim_start_matches("../");
let luxon_path = base_dir.join(stripped_path).join("lib.luxon");
if let Ok(imported_manifest) = LuxonManifest::load(&luxon_path) {
for symbol in &use_stmt.imports {
if let Some(struct_def) = imported_manifest.structs.get(symbol) {
manifest.structs.insert(symbol.clone(), struct_def.clone());
}
if let Some(enum_def) = imported_manifest.enums.get(symbol) {
manifest.enums.insert(symbol.clone(), enum_def.clone());
}
if let Some(func_def) = imported_manifest.functions.get(symbol) {
manifest.functions.insert(symbol.clone(), func_def.clone());
}
}
}
}
}
_ => {}
}
}
manifest
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_manifest_roundtrip() {
let mut manifest = LuxonManifest::new("test_module".to_string());
manifest.version = Some("1.0.0".to_string());
let mut fields = HashMap::new();
fields.insert("value".to_string(), "i32".to_string());
manifest.add_struct("Counter".to_string(), fields, vec!["Clone".to_string(), "Debug".to_string()]);
manifest.add_function(
"increment".to_string(),
vec![LuxonParam { name: "c".to_string(), ty: "&mut Counter".to_string() }],
None,
vec![],
);
let json = serde_json::to_string_pretty(&manifest).unwrap();
let parsed: LuxonManifest = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.name, "test_module");
assert_eq!(parsed.structs.len(), 1);
assert_eq!(parsed.functions.len(), 1);
}
}