aeri 0.2.2

Aeri is the Cardano smart contract language created by Trevor Knott at Knott Dynamics, with its official compiler and CLI.
Documentation
use crate::{
    Result,
    ast::{Function, Item, Module, Param, TypeDecl, TypeRef, Validator, Variant},
    parser::parse_module,
};

pub fn docs_source(file: &str, source: &str) -> Result<String> {
    crate::compile_source(file, source)?;
    let module = parse_module(file, source).map_err(|error| error.with_source(source))?;
    Ok(render_module_docs(&module))
}

pub fn render_module_docs(module: &Module) -> String {
    let mut docs = format!("# Module `{}`\n\n", module.name);

    let types = module
        .items
        .iter()
        .filter_map(|item| match item {
            Item::Type(type_decl) => Some(type_decl),
            _ => None,
        })
        .collect::<Vec<_>>();
    if !types.is_empty() {
        docs.push_str("## Types\n\n");
        for type_decl in types {
            docs.push_str(&format_type_decl(type_decl));
        }
    }

    let constants = module
        .items
        .iter()
        .filter_map(|item| match item {
            Item::Const(constant) => Some(constant),
            _ => None,
        })
        .collect::<Vec<_>>();
    if !constants.is_empty() {
        docs.push_str("## Constants\n\n");
        for constant in constants {
            docs.push_str(&format!(
                "- `{}`: `{}`\n",
                constant.name,
                format_type_ref(&constant.ty)
            ));
        }
        docs.push('\n');
    }

    let functions = module
        .items
        .iter()
        .filter_map(|item| match item {
            Item::Function(function) => Some(function),
            _ => None,
        })
        .collect::<Vec<_>>();
    if !functions.is_empty() {
        docs.push_str("## Functions\n\n");
        for function in functions {
            docs.push_str(&format_function(function));
        }
        docs.push('\n');
    }

    let validators = module
        .items
        .iter()
        .filter_map(|item| match item {
            Item::Validator(validator) => Some(validator),
            _ => None,
        })
        .collect::<Vec<_>>();
    if !validators.is_empty() {
        docs.push_str("## Validators\n\n");
        for validator in validators {
            docs.push_str(&format_validator(validator));
        }
        docs.push('\n');
    }

    let tests = module
        .items
        .iter()
        .filter_map(|item| match item {
            Item::Test(test) => Some(test),
            _ => None,
        })
        .collect::<Vec<_>>();
    if !tests.is_empty() {
        docs.push_str("## Tests\n\n");
        for test in tests {
            if test.should_fail {
                docs.push_str(&format!("- `{}` (expected failure)\n", test.name));
            } else {
                docs.push_str(&format!("- `{}`\n", test.name));
            }
        }
        docs.push('\n');
    }

    docs
}

fn format_type_decl(type_decl: &TypeDecl) -> String {
    let mut docs = format!("### `{}`\n\n", type_decl.name);
    for variant in &type_decl.variants {
        docs.push_str(&format_variant(variant));
    }
    docs.push('\n');
    docs
}

fn format_variant(variant: &Variant) -> String {
    if variant.fields.is_empty() {
        return format!("- `{}`\n", variant.name);
    }

    let fields = variant
        .fields
        .iter()
        .map(|field| match &field.name {
            Some(name) => format!("{name}: {}", format_type_ref(&field.ty)),
            None => format_type_ref(&field.ty),
        })
        .collect::<Vec<_>>()
        .join(", ");
    format!("- `{}({fields})`\n", variant.name)
}

fn format_function(function: &Function) -> String {
    format!(
        "- `fn {}({}) -> {}`\n",
        function.name,
        format_params(&function.params),
        format_type_ref(&function.return_type)
    )
}

fn format_validator(validator: &Validator) -> String {
    format!(
        "- `validator {}({})`\n",
        validator.name,
        format_params(&validator.params)
    )
}

fn format_params(params: &[Param]) -> String {
    params
        .iter()
        .map(|param| format!("{}: {}", param.name, format_type_ref(&param.ty)))
        .collect::<Vec<_>>()
        .join(", ")
}

fn format_type_ref(ty: &TypeRef) -> String {
    if ty.args.is_empty() {
        ty.name.clone()
    } else {
        let args = ty
            .args
            .iter()
            .map(format_type_ref)
            .collect::<Vec<_>>()
            .join(", ");
        format!("{}<{args}>", ty.name)
    }
}