//! Documentation generation for Trident projects.
//!
//! Produces markdown documentation listing all public functions, structs,
//! constants, and events with their type signatures and cost annotations.
use std::path::Path;
use crate::ast;
use crate::ast::display::{format_ast_type, format_const_value, format_fn_signature};
use crate::ast::FileKind;
use crate::cost;
use crate::diagnostic::Diagnostic;
use crate::pipeline::PreparedProject;
use crate::target::TerrainConfig;
use crate::CompileOptions;
/// Generate markdown documentation for a Trident project.
///
/// Resolves all modules, parses and type-checks them, computes cost analysis,
/// and produces a markdown document listing all public functions, structs,
/// constants, and events with their type signatures and cost annotations.
pub(crate) fn generate_docs(
entry_path: &Path,
options: &CompileOptions,
) -> Result<String, Vec<Diagnostic>> {
let project = PreparedProject::build(entry_path, options)?;
// Compute cost analysis per module
let mut module_costs: Vec<Option<cost::ProgramCost>> = Vec::new();
for pm in &project.modules {
let pc = cost::CostAnalyzer::for_target(&options.target_config.name).analyze_file(&pm.file);
module_costs.push(Some(pc));
}
// Determine the program name from the entry module
let program_name = project
.program_module()
.map(|m| m.file.name.node.clone())
.unwrap_or_else(|| "project".to_string());
let mut doc = String::new();
doc.push_str(&format!("# {}\n", program_name));
// --- Functions ---
let mut fn_entries: Vec<String> = Vec::new();
for (i, pm) in project.modules.iter().enumerate() {
let module_name = &pm.file.name.node;
let costs = module_costs[i].as_ref();
for item in &pm.file.items {
if let ast::Item::Fn(func) = &item.node {
// Skip test functions, intrinsic-only, and non-pub functions in modules
if func.is_test {
continue;
}
if pm.file.kind == FileKind::Module && !func.is_pub {
continue;
}
// Skip cfg-excluded items
if let Some(ref cfg) = func.cfg {
if !options.cfg_flags.contains(&cfg.node) {
continue;
}
}
let sig = format_fn_signature(func);
let fn_cost =
costs.and_then(|pc| pc.functions.iter().find(|f| f.name == func.name.node));
let mut entry = format!("### `{}`\n", sig);
if let Some(fc) = fn_cost {
let c = &fc.cost;
let sn = costs
.expect("module_costs always contains Some")
.short_names();
entry.push_str(&format!(
"**Cost:** cc={}, hash={}, u32={} | dominant: {}\n",
c.get(0),
c.get(1),
c.get(2),
c.dominant_table(&sn)
));
}
entry.push_str(&format!("**Module:** {}\n", module_name));
fn_entries.push(entry);
}
}
}
if !fn_entries.is_empty() {
doc.push_str("\n## Functions\n\n");
for entry in &fn_entries {
doc.push_str(entry);
doc.push('\n');
}
}
// --- Structs ---
let mut struct_entries: Vec<String> = Vec::new();
for pm in project.modules.iter() {
for item in &pm.file.items {
if let ast::Item::Struct(sdef) = &item.node {
if pm.file.kind == FileKind::Module && !sdef.is_pub {
continue;
}
if let Some(ref cfg) = sdef.cfg {
if !options.cfg_flags.contains(&cfg.node) {
continue;
}
}
let mut entry = format!("### `struct {}`\n", sdef.name.node);
entry.push_str("| Field | Type | Width |\n");
entry.push_str("|-------|------|-------|\n");
let mut total_width: u32 = 0;
for field in &sdef.fields {
let ty_str = format_ast_type(&field.ty.node);
let width = ast_type_width(&field.ty.node, &options.target_config);
total_width += width;
entry.push_str(&format!(
"| {} | {} | {} |\n",
field.name.node, ty_str, width
));
}
entry.push_str(&format!("Total width: {} field elements\n", total_width));
struct_entries.push(entry);
}
}
}
if !struct_entries.is_empty() {
doc.push_str("\n## Structs\n\n");
for entry in &struct_entries {
doc.push_str(entry);
doc.push('\n');
}
}
// --- Constants ---
let mut const_entries: Vec<(String, String, String)> = Vec::new(); // (name, type, value)
for pm in project.modules.iter() {
for item in &pm.file.items {
if let ast::Item::Const(cdef) = &item.node {
if pm.file.kind == FileKind::Module && !cdef.is_pub {
continue;
}
if let Some(ref cfg) = cdef.cfg {
if !options.cfg_flags.contains(&cfg.node) {
continue;
}
}
let ty_str = format_ast_type(&cdef.ty.node);
let val_str = format_const_value(&cdef.value.node);
const_entries.push((cdef.name.node.clone(), ty_str, val_str));
}
}
}
if !const_entries.is_empty() {
doc.push_str("\n## Constants\n\n");
doc.push_str("| Name | Type | Value |\n");
doc.push_str("|------|------|-------|\n");
for (name, ty, val) in &const_entries {
doc.push_str(&format!("| {} | {} | {} |\n", name, ty, val));
}
doc.push('\n');
}
// --- Events ---
let mut event_entries: Vec<String> = Vec::new();
for pm in project.modules.iter() {
for item in &pm.file.items {
if let ast::Item::Event(edef) = &item.node {
if let Some(ref cfg) = edef.cfg {
if !options.cfg_flags.contains(&cfg.node) {
continue;
}
}
let mut entry = format!("### `event {}`\n", edef.name.node);
entry.push_str("| Field | Type |\n");
entry.push_str("|-------|------|\n");
for field in &edef.fields {
let ty_str = format_ast_type(&field.ty.node);
entry.push_str(&format!("| {} | {} |\n", field.name.node, ty_str));
}
event_entries.push(entry);
}
}
}
if !event_entries.is_empty() {
doc.push_str("\n## Events\n\n");
for entry in &event_entries {
doc.push_str(entry);
doc.push('\n');
}
}
// --- Cost Summary ---
// Aggregate costs across all modules โ use the program module's cost if it exists,
// otherwise sum all module costs.
let program_cost_idx = project
.modules
.iter()
.enumerate()
.find(|(_, m)| m.file.kind == FileKind::Program)
.map(|(i, _)| i);
let program_cost = program_cost_idx.and_then(|i| module_costs[i].as_ref());
let total_cost = if let Some(pc) = program_cost {
pc.total.clone()
} else {
// Sum across all modules
let mut total = cost::TableCost::ZERO;
for pc in module_costs.iter().flatten() {
total = total.add(&pc.total);
}
total
};
let padded_height = if let Some(pc) = program_cost {
pc.padded_height
} else {
cost::next_power_of_two(total_cost.max_height())
};
doc.push_str("\n## Cost Summary\n\n");
doc.push_str("| Table | Height |\n");
doc.push_str("|-------|--------|\n");
doc.push_str(&format!("| Processor | {} |\n", total_cost.get(0)));
doc.push_str(&format!("| Hash | {} |\n", total_cost.get(1)));
doc.push_str(&format!("| U32 | {} |\n", total_cost.get(2)));
doc.push_str(&format!("| Padded | {} |\n", padded_height));
Ok(doc)
}
/// Compute the width in field elements for an AST type (best-effort).
pub(crate) fn ast_type_width(ty: &ast::Type, config: &TerrainConfig) -> u32 {
match ty {
ast::Type::Field | ast::Type::Bool | ast::Type::U32 => 1,
ast::Type::XField => config.xfield_width,
ast::Type::Digest => config.digest_width,
ast::Type::Array(inner, size) => {
let inner_w = ast_type_width(inner, config);
let n = size.as_literal().unwrap_or(1) as u32;
inner_w * n
}
ast::Type::Tuple(elems) => elems.iter().map(|e| ast_type_width(e, config)).sum(),
ast::Type::Named(_) => 1, // unknown, default to 1
}
}
trident/src/api/doc.rs
ฯ 0.0%
//! Documentation generation for Trident projects.
//!
//! Produces markdown documentation listing all public functions, structs,
//! constants, and events with their type signatures and cost annotations.
use Path;
use crateast;
use crate;
use crateFileKind;
use cratecost;
use crateDiagnostic;
use cratePreparedProject;
use crateTerrainConfig;
use crateCompileOptions;
/// Generate markdown documentation for a Trident project.
///
/// Resolves all modules, parses and type-checks them, computes cost analysis,
/// and produces a markdown document listing all public functions, structs,
/// constants, and events with their type signatures and cost annotations.
pub
/// Compute the width in field elements for an AST type (best-effort).
pub