veryl 0.20.3

A modern hardware description language
use crate::OptDoc;
use crate::doc::{ComponentItem, DocBuilder, TopLevelItem};
use crate::pipeline::{self, AnalyzeOptions};
use log::warn;
use miette::Result;
use std::collections::BTreeMap;
use veryl_analyzer::symbol::{SymbolId, SymbolKind};
use veryl_analyzer::symbol_table;
use veryl_metadata::Metadata;
use veryl_parser::resource_table;

pub struct CmdDoc {
    opt: OptDoc,
}

impl CmdDoc {
    pub fn new(opt: OptDoc) -> Self {
        Self { opt }
    }

    pub fn exec(&self, metadata: &mut Metadata) -> Result<bool> {
        let mut paths = metadata.paths(&self.opt.files, true, true)?;
        paths.retain(|x| !x.example);

        // Abort on a fatal error rather than document a half-analyzed tree.
        let options = AnalyzeOptions {
            defines: &[],
            emit_mode: false,
            incremental: false,
            fail_fast: true,
        };
        let _ = pipeline::analyze(metadata, &paths, options, None, None)?;

        let mut modules = BTreeMap::new();
        let mut proto_modules = BTreeMap::new();
        let mut interfaces = BTreeMap::new();
        let mut packages = BTreeMap::new();

        for symbol in veryl_analyzer::symbol_table::get_all() {
            let text = resource_table::get_str_value(symbol.token.text).unwrap();
            let file_name = text.clone();
            let symbol = symbol.clone();
            if format!("{}", symbol.namespace) == metadata.project.name && symbol.public {
                match &symbol.kind {
                    SymbolKind::Module(x) if !x.is_proto => {
                        let html_name = fmt_generic_parameters(&text, &x.generic_parameters);
                        let item = TopLevelItem {
                            file_name,
                            html_name,
                            symbol,
                        };
                        modules.insert(text, item);
                    }
                    SymbolKind::Module(x) if x.is_proto => {
                        let html_name = file_name.clone();
                        let item = TopLevelItem {
                            file_name,
                            html_name,
                            symbol,
                        };
                        proto_modules.insert(text, item);
                    }
                    SymbolKind::Interface(x) if !x.is_proto => {
                        let html_name = fmt_generic_parameters(&text, &x.generic_parameters);
                        let item = TopLevelItem {
                            file_name,
                            html_name,
                            symbol,
                        };
                        interfaces.insert(text, item);
                    }
                    SymbolKind::Package(x) if !x.is_proto => {
                        let html_name = fmt_generic_parameters(&text, &x.generic_parameters);
                        let item = TopLevelItem {
                            file_name,
                            html_name,
                            symbol,
                        };
                        packages.insert(text, item);
                    }
                    _ => (),
                }
            }
        }

        let modules: Vec<_> = modules.into_values().collect();
        let proto_modules: Vec<_> = proto_modules.into_values().collect();
        let interfaces: Vec<_> = interfaces.into_values().collect();
        let packages: Vec<_> = packages.into_values().collect();
        let components = gather_components(metadata);

        let builder = DocBuilder::new(
            metadata,
            modules,
            proto_modules,
            interfaces,
            packages,
            components,
        )?;
        builder.build()?;

        Ok(true)
    }
}

/// The project's own `[[components]]` exports, documented from their
/// interface manifests. A package with no manifest yet (never built nor
/// published) contributes no pages, so its exports are unknown here.
fn gather_components(metadata: &Metadata) -> Vec<ComponentItem> {
    if metadata.components.is_empty() {
        return vec![];
    }
    let root = metadata.project_path();
    let target_dir = root.join("target/veryl-components");
    let mut ret = BTreeMap::new();
    for def in &metadata.components {
        let manifests = def.collect_manifests(&root, &target_dir);
        if manifests.is_empty() {
            warn!(
                "component package ({}) has no interface manifest; its components get no pages — the build failed or cargo is unavailable (a manifest committed by `veryl publish` also works)",
                def.path.display()
            );
        }
        for (name, manifest) in manifests {
            // First declaration wins, as everywhere exports are collected.
            ret.entry(name.clone()).or_insert_with(|| ComponentItem {
                // Prefixed to avoid clashing with a Veryl item of the
                // same name.
                file_name: format!("component_{name}"),
                name,
                manifest,
            });
        }
    }
    ret.into_values().collect()
}

fn fmt_generic_parameters(name: &str, params: &[SymbolId]) -> String {
    if params.is_empty() {
        name.to_string()
    } else {
        let mut name = name.to_string();
        name.push_str("::&lt;");
        for param in params {
            let symbol = symbol_table::get(*param).unwrap();
            name.push_str(&format!("{}, ", symbol.token.text));
        }
        format!("{}&gt;", name.strip_suffix(", ").unwrap())
    }
}