use crate::convert::{
AttrChoice, AttrInterfaceRef, AttrRecord, AttrTemplate, AttrVariant, data_type_string_from_type,
extract_all_choices, extract_enum, extract_record, extract_template, extract_variant,
};
use crate::{CodeGeneratorParameters, RenderModeArg};
use daml_codegen::generator::{ModuleMatcher, RenderMethod};
use daml_codegen::renderer::full::{
quote_choice, quote_daml_enum, quote_daml_interface, quote_daml_record, quote_daml_template, quote_daml_variant,
};
use daml_codegen::renderer::quote_archive;
use daml_codegen::renderer::{RenderContext, RenderFilterMode};
use daml_lf::element::{
DamlArchive, DamlChoice, DamlEnum, DamlInterface, DamlModule, DamlPackage, DamlRecord, DamlTemplate, DamlType,
DamlVariant,
};
use daml_lf::{DarFile, LanguageVersion};
use darling::FromMeta;
use darling::ast::NestedMeta;
use std::borrow::Cow;
use std::collections::HashMap;
use syn::{Data, DataStruct, DeriveInput, Fields, ItemImpl};
pub fn generate_tokens(args: Vec<NestedMeta>) -> proc_macro::TokenStream {
let params: CodeGeneratorParameters = CodeGeneratorParameters::from_list(&args).unwrap_or_else(|e| panic!("{}", e));
let archive = DarFile::from_file(¶ms.dar_file)
.unwrap_or_else(|e| panic!("failed to load Dar file from {}, error was: {}", ¶ms.dar_file, e));
let filters: Vec<_> = params.module_filter_regex.iter().map(String::as_str).collect();
let render_method = match params.mode {
Some(RenderModeArg::Intermediate) => RenderMethod::Intermediate,
Some(RenderModeArg::Full) | None => RenderMethod::Full,
};
let applied =
archive.apply(|archive| ModuleMatcher::new(&filters).map(|mm| quote_archive(archive, &mm, &render_method)));
match applied {
Ok(Ok(tokens)) => proc_macro::TokenStream::from(tokens),
Ok(Err(e)) => panic!("failed to generate Daml code: {e}"),
Err(e) => panic!("Daml-LF error in Daml code generator: {e}"),
}
}
pub fn generate_template(
input: DeriveInput,
package_name: Option<String>,
package_id: String,
module_name: String,
implements: String,
) -> proc_macro::TokenStream {
let struct_name = input.ident.to_string();
match &input.data {
Data::Struct(DataStruct {
fields: Fields::Named(fields_named),
..
}) => {
let implements = parse_implements_list(&implements);
let template: AttrTemplate =
extract_template(struct_name, package_id.clone(), module_name, fields_named, implements);
let daml_template = DamlTemplate::from(&template);
let archive = synthesize_archive(&package_id, package_name.as_deref());
let ctx = RenderContext::with_archive(&archive, RenderFilterMode::default());
let expanded = quote_daml_template(&ctx, &daml_template);
proc_macro::TokenStream::from(expanded)
},
_ => panic!("the DamlTemplate attribute may only be applied to a named struct type"),
}
}
fn parse_implements_list(raw: &str) -> Vec<AttrInterfaceRef> {
raw.split(',')
.map(str::trim)
.filter(|s| !s.is_empty())
.map(|entry| {
let parts: Vec<&str> = entry.split(':').collect();
if parts.len() != 3 {
panic!("#[DamlTemplate(implements = ...)]: expected `<pkg>:<Module.Path>:<Entity>`, got `{entry}`");
}
AttrInterfaceRef {
package_name: parts[0].to_string(),
module_path: parts[1].split('.').map(ToOwned::to_owned).collect(),
entity_name: parts[2].to_string(),
}
})
.collect()
}
pub fn generate_interface(
input: DeriveInput,
package_name: Option<String>,
package_id: String,
module_name: String,
) -> proc_macro::TokenStream {
let interface_name = input.ident.to_string();
let module_path: Vec<Cow<'_, str>> = module_name.split('.').map(|s| Cow::Owned(s.to_string())).collect();
let daml_interface = DamlInterface::new(
Cow::Owned(interface_name),
Cow::Owned(package_id.clone()),
module_path,
Cow::Borrowed("this"),
vec![],
vec![],
DamlType::Unit,
vec![],
);
let archive = synthesize_archive(&package_id, package_name.as_deref());
let ctx = RenderContext::with_archive(&archive, RenderFilterMode::default());
let expanded = quote_daml_interface(&ctx, &daml_interface);
proc_macro::TokenStream::from(expanded)
}
fn synthesize_archive(package_id: &str, package_name: Option<&str>) -> DamlArchive<'static> {
let mut packages = HashMap::new();
let package = DamlPackage::new(
Cow::Owned(package_name.unwrap_or_default().to_string()),
Cow::Owned(package_id.to_string()),
None,
LanguageVersion::V2_1,
DamlModule::new_root(),
);
packages.insert(Cow::Owned(package_id.to_string()), package);
DamlArchive::new(Cow::Borrowed(""), Cow::Owned(package_id.to_string()), packages)
}
pub fn generate_choices(input: ItemImpl) -> proc_macro::TokenStream {
let struct_name = data_type_string_from_type(input.self_ty.as_ref());
let all_choices: Vec<AttrChoice> = extract_all_choices(&input.items);
let all_daml_choices: Vec<DamlChoice<'_>> = all_choices.iter().map(DamlChoice::from).collect();
let ctx = RenderContext::default();
let all_choice_methods_tokens = quote_choice(&ctx, &struct_name, &all_daml_choices);
proc_macro::TokenStream::from(all_choice_methods_tokens)
}
pub fn generate_data_struct(input: DeriveInput) -> proc_macro::TokenStream {
let struct_name = input.ident.to_string();
let tokens = match input.data {
Data::Struct(data_struct) => match &data_struct.fields {
Fields::Named(fields_named) => {
let record: AttrRecord = extract_record(struct_name, fields_named, &input.generics);
let daml_record = DamlRecord::from(&record);
let ctx = RenderContext::default();
quote_daml_record(&ctx, &daml_record)
},
Fields::Unnamed(_) => panic!("tuple struct not supported"),
Fields::Unit => panic!("unit struct not supported"),
},
_ => panic!("the DamlData attribute may only be applied to struct types"),
};
proc_macro::TokenStream::from(tokens)
}
pub fn generate_data_variant(input: DeriveInput) -> proc_macro::TokenStream {
let variant_name = input.ident.to_string();
let tokens = match input.data {
Data::Enum(data_enum) => {
let variant: AttrVariant = extract_variant(variant_name, &data_enum, &input.generics);
let daml_variant = DamlVariant::from(&variant);
let ctx = RenderContext::default();
quote_daml_variant(&ctx, &daml_variant)
},
_ => panic!("the DamlVariant attribute may only be applied to enum types"),
};
proc_macro::TokenStream::from(tokens)
}
pub fn generate_data_enum(input: DeriveInput) -> proc_macro::TokenStream {
let enum_name = input.ident.to_string();
let tokens = match input.data {
Data::Enum(data_enum) => {
let enum_variants = extract_enum(enum_name, &data_enum, &input.generics);
let daml_enum = DamlEnum::from(&enum_variants);
let ctx = RenderContext::default();
quote_daml_enum(&ctx, &daml_enum)
},
_ => panic!("the DamlEnum attribute may only be applied to enum types"),
};
proc_macro::TokenStream::from(tokens)
}