hara-native 0.1.21

HAL-free native host runtime and package launcher for Hara
Documentation
#[path = "report/bundle.rs"]
mod bundle;
#[path = "report/form.rs"]
mod form;

use super::graph::{Analysis, ModuleAnalysis, RetentionReason};
use super::plan::BuildPlan;
use super::source::Diagnostic;
use super::unit::UnitAnalysis;
use crate::kernel::Form;
use form::{
    boolean, keyword, map, nil, number, source_form, string, string_vector, strings, symbol,
    symbol_vector, symbols, vector,
};

pub const ANALYSIS_FORMAT: &str = "hara.production-analysis/0-alpha";
pub const SHAKE_FORMAT: &str = "hara.production-shake/0-alpha";

pub(super) use bundle::BundleSummary;

pub fn report_source(
    plan: &BuildPlan,
    analysis: &Analysis,
    output: Option<&BundleSummary>,
) -> String {
    format!("{}\n", report_form(plan, analysis, output))
}

fn report_form(plan: &BuildPlan, analysis: &Analysis, output: Option<&BundleSummary>) -> Form {
    let dynamic_diagnostics = analysis
        .diagnostics
        .iter()
        .filter(|diagnostic| {
            diagnostic.code.contains("dynamic")
                || diagnostic.code.contains("eval")
                || diagnostic.code.contains("generated-source")
                || diagnostic.code.contains("load-string")
        })
        .map(diagnostic_form);
    let status = if !analysis.succeeded() {
        "analysis-failed"
    } else if output.is_some() {
        "bundle-complete"
    } else {
        "analysis-complete"
    };
    map(vec![
        ("shake/format", string(SHAKE_FORMAT)),
        ("shake/status", keyword(status)),
        (
            "shake/project",
            map(vec![
                ("project/id", symbol(&plan.project_id)),
                ("project/version", string(&plan.project_version)),
            ]),
        ),
        ("shake/profile", keyword(&plan.profile)),
        ("shake/language", keyword(&plan.language)),
        ("shake/main", symbol(&plan.main)),
        (
            "shake/entrypoints",
            symbol_vector(plan.entrypoints.iter().cloned()),
        ),
        ("shake/default-entrypoint", symbol(&plan.default_entrypoint)),
        (
            "shake/keep-vars",
            symbol_vector(plan.keep_vars.iter().cloned()),
        ),
        (
            "shake/keep-namespaces",
            symbol_vector(plan.keep_namespaces.iter().cloned()),
        ),
        (
            "shake/retained",
            map(vec![
                ("vars", symbols(&analysis.retained_vars)),
                ("namespaces", symbols(&analysis.retained_namespaces)),
            ]),
        ),
        (
            "shake/removed",
            map(vec![
                ("vars", symbols(&analysis.removed_vars)),
                ("namespaces", symbols(&analysis.removed_namespaces)),
            ]),
        ),
        (
            "shake/retention-reasons",
            vector(analysis.reasons.iter().map(reason_form)),
        ),
        (
            "shake/dynamic-access-diagnostics",
            vector(dynamic_diagnostics),
        ),
        (
            "shake/native-roots",
            map(vec![
                ("primitives", strings(&analysis.native_primitives)),
                ("types", strings(&analysis.native_types)),
                ("protocols", strings(&analysis.native_protocols)),
            ]),
        ),
        (
            "shake/sizes",
            map(vec![
                ("input-bytes", number(analysis.input_bytes)),
                (
                    "output-bytes",
                    output.map_or_else(nil, |summary| number(summary.output_bytes)),
                ),
            ]),
        ),
        (
            "shake/digests",
            map(vec![
                ("algorithm", keyword("sha-256")),
                ("input", string(&analysis.input_digest)),
                (
                    "output",
                    output.map_or_else(nil, |summary| string(&summary.output_digest)),
                ),
            ]),
        ),
        (
            "shake/output",
            map(vec![
                ("bundle", string(&plan.output_bundle)),
                ("report", string(&plan.output_report)),
                (
                    "module-count",
                    output.map_or_else(nil, |summary| number(summary.module_count)),
                ),
            ]),
        ),
        ("shake/analysis", analysis_form(analysis)),
    ])
}

fn analysis_form(analysis: &Analysis) -> Form {
    let mut modules = analysis.modules.iter().collect::<Vec<_>>();
    modules.sort_by(|left, right| left.name.cmp(&right.name));
    let mut units = analysis.units.iter().collect::<Vec<_>>();
    units.sort_by(|left, right| left.id.cmp(&right.id));
    map(vec![
        ("analysis/format", string(ANALYSIS_FORMAT)),
        (
            "analysis/modules",
            vector(modules.into_iter().map(module_form)),
        ),
        ("analysis/units", vector(units.into_iter().map(unit_form))),
        (
            "analysis/runtime",
            map(vec![
                ("roots", symbols(&analysis.runtime_roots)),
                ("closure", symbols(&analysis.runtime_closure)),
                (
                    "unit-ids",
                    string_vector(analysis.runtime_unit_ids.iter().cloned()),
                ),
            ]),
        ),
        (
            "analysis/compile-time",
            map(vec![
                ("roots", symbols(&analysis.compile_time_roots)),
                ("closure", symbols(&analysis.compile_time_closure)),
                (
                    "unit-ids",
                    string_vector(analysis.compile_time_unit_ids.iter().cloned()),
                ),
            ]),
        ),
        (
            "analysis/retained-unit-ids",
            string_vector(analysis.retained_unit_ids.iter().cloned()),
        ),
        (
            "analysis/removed-unit-ids",
            string_vector(analysis.removed_unit_ids.iter().cloned()),
        ),
        (
            "analysis/diagnostics",
            vector(analysis.diagnostics.iter().map(diagnostic_form)),
        ),
    ])
}

fn module_form(module: &ModuleAnalysis) -> Form {
    map(vec![
        ("module/name", symbol(&module.name)),
        ("module/path", string(&module.path)),
        ("module/digest", string(&module.digest)),
        ("module/input-bytes", number(module.input_bytes)),
        (
            "module/dependencies",
            symbol_vector(module.dependencies.iter().cloned()),
        ),
        (
            "module/unit-ids",
            string_vector(module.unit_ids.iter().cloned()),
        ),
        ("module/standard-library", boolean(module.standard_library)),
    ])
}

fn unit_form(unit: &UnitAnalysis) -> Form {
    map(vec![
        ("unit/id", string(&unit.id)),
        ("unit/module", symbol(&unit.module)),
        ("unit/index", number(unit.index)),
        ("unit/kind", keyword(unit.kind.keyword())),
        ("unit/effect", keyword(unit.effect.keyword())),
        ("unit/source", source_form(&unit.location)),
        ("unit/provides", symbols(&unit.provides)),
        ("unit/runtime-edges", symbols(&unit.runtime_edges)),
        ("unit/compile-time-edges", symbols(&unit.compile_time_edges)),
        ("unit/namespace-edges", symbols(&unit.namespace_edges)),
        ("unit/native-primitives", strings(&unit.native_primitives)),
        ("unit/native-types", strings(&unit.native_types)),
        ("unit/native-protocols", strings(&unit.native_protocols)),
        (
            "unit/diagnostics",
            vector(unit.diagnostics.iter().map(diagnostic_form)),
        ),
    ])
}

fn reason_form(reason: &RetentionReason) -> Form {
    map(vec![
        ("reason/unit", string(&reason.unit_id)),
        (
            "reason/subject",
            reason.subject.as_deref().map_or_else(nil, symbol),
        ),
        (
            "reason/code",
            keyword(&format!("production/{}", reason.code)),
        ),
        ("reason/detail", string(&reason.detail)),
    ])
}

fn diagnostic_form(diagnostic: &Diagnostic) -> Form {
    map(vec![
        ("diagnostic/code", keyword(&diagnostic.code)),
        ("diagnostic/operation", keyword(&diagnostic.operation)),
        ("diagnostic/module", symbol(&diagnostic.module)),
        ("diagnostic/source", source_form(&diagnostic.location)),
        ("diagnostic/message", string(&diagnostic.message)),
    ])
}