mod client;
mod encode;
mod functions;
mod program;
mod types;
use std::collections::{HashMap, HashSet, VecDeque};
use std::path;
use lutra_bin::ir;
use lutra_compiler::Project;
use crate::{GenerateOptions, infer_names};
fn canonical_std_type_reexport(module_path: &[String], name: &str) -> Option<&'static str> {
match (module_path, name) {
([std], "Timestamp") if std == "std" => Some("std::Timestamp"),
([std], "Date") if std == "std" => Some("std::Date"),
([std], "Time") if std == "std" => Some("std::Time"),
([std], "Decimal") if std == "std" => Some("std::Decimal"),
([std, ops], "Ordering") if std == "std" && ops == "ops" => Some("std::ops::Ordering"),
_ => None,
}
}
#[derive(Debug)]
pub struct Context<'a> {
current_rust_mod: Vec<String>,
def_buffer: VecDeque<ir::Ty>,
emitted_types: HashSet<String>,
options: &'a GenerateOptions,
ty_defs: &'a HashMap<ir::Path, &'a ir::Ty>,
project: &'a Project,
out_dir: path::PathBuf,
}
impl<'a> Context<'a> {
fn is_done(&self) -> bool {
self.def_buffer.is_empty()
}
#[allow(dead_code)]
fn get_ty_mat<'t: 'a>(&'t self, ty: &'t ir::Ty) -> &'t ir::Ty {
if let ir::TyKind::Ident(path) = &ty.kind {
self.ty_defs.get(path).unwrap()
} else {
ty
}
}
}
pub(crate) fn run(
project: &Project,
options: &GenerateOptions,
out_dir: path::PathBuf,
) -> Result<String, std::fmt::Error> {
use std::fmt::Write;
let module = lutra_compiler::project_to_types(project);
let ty_defs = module.iter_types_re().collect();
let mut w = String::new();
writeln!(w, "//# Generated by lutra-codegen\n")?;
let mut ctx = Context {
current_rust_mod: vec![],
def_buffer: VecDeque::new(),
emitted_types: HashSet::new(),
options,
ty_defs: &ty_defs,
project,
out_dir,
};
let module_path = vec![];
codegen_module(&mut w, &module, module_path, &mut ctx)?;
Ok(w)
}
fn codegen_module(
w: &mut impl std::fmt::Write,
module: &ir::Module,
module_path: Vec<String>,
ctx: &mut Context,
) -> Result<(), std::fmt::Error> {
let mut tys = Vec::new();
let mut functions = Vec::new();
let mut sub_modules = Vec::new();
let root_mod = &ctx.project.root_module;
let pr_mod = root_mod.get_module(&module_path).unwrap();
for (name, pr_def) in &pr_mod.defs {
let Some(decl) = module.decls.iter().find(|d| &d.name == name) else {
continue;
};
match &decl.decl {
ir::Decl::Mod(module) => {
let is_dep = module_path.is_empty()
&& ctx.project.dependencies.iter().any(|d| &d.name == name);
if is_dep {
continue;
}
sub_modules.push((name, module));
}
ir::Decl::Ty(ty) => {
let mut ty = ty.clone();
infer_names(name, &mut ty);
tys.push((ty, pr_def.annotations.as_slice()));
}
ir::Decl::Var(ty) => {
let mut ty = ty.clone();
infer_names(name, &mut ty);
if let ir::TyKind::Function(func) = ty.kind {
functions.push((name, *func));
}
}
}
}
ctx.current_rust_mod = module_path.clone();
let mut all_tys = Vec::new();
if ctx.options.generates_types() {
let mut tys_to_import = Vec::new();
let mut tys_to_generate = Vec::new();
for (ty, annotations) in tys {
let name = ty.name.as_deref().unwrap();
if let Some(path) = canonical_std_type_reexport(&module_path, name) {
tys_to_import.push((name.to_string(), path));
} else {
tys_to_generate.push((ty, annotations));
}
}
let lutra_bin = &ctx.options.lutra_bin_path;
for (name, path) in &tys_to_import {
writeln!(w, "pub use {lutra_bin}::{path} as {name};")?;
}
if !tys_to_import.is_empty() {
writeln!(w)?;
}
all_tys.extend(types::write_tys(w, tys_to_generate, ctx)?);
};
if ctx.options.generates_function_traits() {
functions::write_functions(w, &functions, ctx)?;
all_tys.extend(types::write_tys_in_buffer(w, ctx)?);
}
let prog_repr = get_programs_repr_of(ctx, &module_path);
if let Some(format) = prog_repr {
program::write_rr_programs(w, &functions, format, ctx)?;
all_tys.extend(types::write_tys_in_buffer(w, ctx)?);
}
let client_sub_modules = sub_modules
.iter()
.map(|(name, _)| *name)
.filter(|name| {
let mut path = module_path.clone();
path.push(name.to_string());
has_programs_in_subtree(ctx, &path)
})
.collect::<Vec<_>>();
if ctx.options.generates_client() && (prog_repr.is_some() || !client_sub_modules.is_empty()) {
let functions_here = if prog_repr.is_some() {
functions.as_slice()
} else {
&[]
};
client::write_client(w, functions_here, &client_sub_modules, prog_repr, ctx)?;
all_tys.extend(types::write_tys_in_buffer(w, ctx)?);
}
for (name, sub_mod) in sub_modules {
writeln!(w, "pub mod {name} {{")?;
let mut path = module_path.clone();
path.push(name.clone());
codegen_module(w, sub_mod, path, ctx)?;
writeln!(w, "}}\n")?;
}
ctx.current_rust_mod = module_path.clone();
if ctx.options.generates_encode_decode() {
encode::write_encode_impls(w, &all_tys, ctx)?;
}
assert!(ctx.is_done(), "{ctx:?}");
Ok(())
}
fn module_path_string(module_path: &[String]) -> String {
module_path.join("::")
}
fn get_programs_repr_of(
ctx: &Context,
module_path: &[String],
) -> Option<lutra_compiler::ProgramRepr> {
let module_path_str = module_path_string(module_path);
ctx.options.included_program_repr(&module_path_str)
}
fn has_programs_in_subtree(ctx: &Context, module_path: &[String]) -> bool {
let module_path_str = module_path_string(module_path);
ctx.options.has_programs_in_subtree(&module_path_str)
}