nichlink-run-method 0.1.1

Runtime registry trees, transactions, tracing, plugins, and authoring for NichLink
Documentation
//! Face source parsing boundary.
//! 注册面源码解析边界。

use std::collections::BTreeMap;
use std::path::Path;

use crate::authoring::parse::{
    kind_from_source_path, module_name_from_path, module_source_from_node_path,
    parse_admission_expression, parse_flow_expression, rule_syntax_for_source,
    source_path_from_file,
};
use crate::authoring::validation::authoring_namespace;
use crate::{
    NodeId, ParentSyntax, RuntimeCheckSpec, parse_face as parse_face_syntax, root_node_id,
};

use super::super::FaceManifest;

/// Parse one generated face through the authoring parser.
/// 通过 authoring 解析器读取一个生成注册面。
pub(crate) fn source(path: &Path) -> Result<FaceManifest, String> {
    parse_face_macro_impl(
        path,
        &std::fs::read_to_string(path).map_err(|error| error.to_string())?,
    )
}

/// Recover an authored face without a sidecar manifest or central index.
/// 不依赖旁置清单或中央索引,直接恢复文件里的注册面。
fn parse_face_macro_impl(path: &Path, text: &str) -> Result<FaceManifest, String> {
    let face = parse_face_syntax(text)
        .map_err(|error| format!("cannot parse {}: {error}", path.display()))?
        .ok_or_else(|| "this module was not generated by NichLink".to_owned())?;
    let mut values = BTreeMap::new();
    let source = source_path_from_file(path);
    values.insert("source".to_owned(), source);
    values.insert("module".to_owned(), module_name_from_path(path));
    let declared_parent_registry = face
        .macro_name
        .strip_suffix("_object")
        // Studio must be able to open declarations written before hierarchy
        // macros existed. The build validator still rejects a newly generated
        // `control_object!` whose explicit parent is not `control`.
        // Studio 必须能打开层级宏出现前生成的声明;构建校验仍会拒绝新文件中
        // 与 control 父级不一致的 `control_object!`。
        .filter(|name| !matches!(*name, "control" | "external"));
    if let Some(parent_registry) = declared_parent_registry {
        values.insert(
            "parent_registry_name".to_owned(),
            parent_registry.to_owned(),
        );
    }

    for key in ["kind", "preset", "parts"] {
        if let Some(value) = face.path(key) {
            values.insert(key.to_owned(), value);
        }
    }
    for key in ["registry_rule_path"] {
        if let Some(value) = face.string(key) {
            values.insert(key.to_owned(), value);
        }
    }
    if let Some(value) = face.boolean("needs_registry") {
        values.insert("needs_registry".to_owned(), value.to_string());
    }
    let kind = values.get("kind").cloned().unwrap_or_default();
    values.insert(
        "stable_name".to_owned(),
        face.string("stable_name").unwrap_or_default(),
    );
    values.insert(
        "name_zh".to_owned(),
        face.localized("name", "zh").unwrap_or_else(|| kind.clone()),
    );
    values.insert(
        "name_en".to_owned(),
        face.localized("name", "en").unwrap_or_else(|| kind.clone()),
    );
    values.insert(
        "summary_zh".to_owned(),
        face.localized("summary", "zh").unwrap_or_default(),
    );
    values.insert(
        "summary_en".to_owned(),
        face.localized("summary", "en").unwrap_or_default(),
    );
    values.insert(
        "exports".to_owned(),
        face.string_list("exports").unwrap_or_default().join(","),
    );
    values.insert(
        "parts".to_owned(),
        values
            .get("parts")
            .cloned()
            .filter(|value| !value.is_empty())
            .unwrap_or_else(|| "NoParts".to_owned()),
    );
    values.insert(
        "preset".to_owned(),
        values
            .get("preset")
            .cloned()
            .filter(|value| !value.is_empty())
            .unwrap_or_else(|| "NoPreset".to_owned()),
    );
    values.insert(
        "handle".to_owned(),
        values
            .get("handle")
            .cloned()
            .filter(|value| !value.is_empty())
            .unwrap_or_else(|| kind.clone()),
    );
    if let Some(plugin) = face.field(nichlink::lexicon::FACE_FIELD_PLUGIN) {
        values.insert(nichlink::lexicon::FACE_FIELD_PLUGIN.to_owned(), plugin);
    }
    values.insert(
        "getting_from_other_registry".to_owned(),
        face.option_string("getting_from_other_registry")
            .flatten()
            .unwrap_or_default(),
    );
    values.insert(
        "needs_registry".to_owned(),
        values
            .get("needs_registry")
            .cloned()
            .unwrap_or_else(|| "false".to_owned()),
    );
    values.insert(
        "admission".to_owned(),
        parse_admission_expression(face.field("admission").as_deref().unwrap_or(""))?,
    );
    // The view derives the labels from the paths when there are paths
    // (`FaceSyntax::string_list`), so this reader states nothing twice and cannot
    // disagree with the build-time contract check that reads the same view.
    // 有路径时视图会从路径推导标签(`FaceSyntax::string_list`),因此本读取器不重复声明
    // 任何东西,也不可能与读同一视图的构建期合同检查分歧。
    values.insert(
        "handle_traits".to_owned(),
        face.string_list("handle_traits")
            .unwrap_or_default()
            .join(","),
    );
    values.insert(
        "handle_contracts".to_owned(),
        face.path_list("handle_contracts")
            .unwrap_or_default()
            .join(","),
    );
    values.insert(
        "part_contracts".to_owned(),
        face.path_list("part_contracts")
            .unwrap_or_default()
            .join(","),
    );
    values.insert(
        "part_traits".to_owned(),
        face.string_list("part_traits")
            .unwrap_or_default()
            .join(","),
    );
    values.insert(
        "requires".to_owned(),
        face.requirements("requires")
            .unwrap_or_default()
            .into_iter()
            .map(|(capability, provider)| format!("{capability}=>{provider}"))
            .collect::<Vec<_>>()
            .join(","),
    );
    values.insert(
        "provides".to_owned(),
        face.string_list("provides").unwrap_or_default().join(","),
    );
    values.insert(
        "runtime_checks".to_owned(),
        RuntimeCheckSpec::parse_list(face.field("runtime_checks").as_deref().unwrap_or(""))?
            .into_iter()
            .map(RuntimeCheckSpec::expression)
            .collect::<Vec<_>>()
            .join(","),
    );
    values.insert(
        "flow".to_owned(),
        parse_flow_expression(face.field("flow").as_deref().unwrap_or(""))?,
    );
    values.insert(
        "flow_provider".to_owned(),
        face.path("flow_provider").unwrap_or_default(),
    );

    values.insert(
        "declaration_line".to_owned(),
        face.location.line.to_string(),
    );

    let parent_syntax = face.parent();
    if declared_parent_registry.is_some() && parent_syntax.is_none() {
        return Err("parent-specific registration macro requires an explicit parent".to_owned());
    }
    let parent_syntax = parent_syntax.or(Some(ParentSyntax::Root));
    if let (Some(declared), Some(parent)) = (declared_parent_registry, parent_syntax.as_ref()) {
        let expected = match parent {
            ParentSyntax::Root => "root".to_owned(),
            ParentSyntax::FromPath { source, .. } => Path::new(source)
                .file_stem()
                .and_then(|stem| stem.to_str())
                .unwrap_or("root")
                .to_owned(),
            ParentSyntax::NodePath(module) => module
                .rsplit("::")
                .find(|segment| !segment.is_empty())
                .unwrap_or("root")
                .to_owned(),
        };
        if declared != expected {
            return Err(format!(
                "registration macro `{}_object!` does not match parent `{expected}`",
                declared
            ));
        }
    }
    match parent_syntax {
        Some(ParentSyntax::Root) => {
            let namespace = authoring_namespace();
            values.insert("namespace".to_owned(), namespace.clone());
            values.insert(
                "parent_node".to_owned(),
                root_node_id(&namespace).to_string(),
            );
            values.insert("parent_source".to_owned(), "<root>".to_owned());
            values.insert("parent_kind".to_owned(), "root".to_owned());
        }
        Some(ParentSyntax::FromPath { source, kind }) => {
            values.insert(
                "parent_node".to_owned(),
                NodeId::from_namespaced_path(&authoring_namespace(), &source, &kind).to_string(),
            );
            values.insert("parent_source".to_owned(), source);
            values.insert("parent_kind".to_owned(), kind);
        }
        Some(ParentSyntax::NodePath(module)) => {
            let source = module_source_from_node_path(&module)
                .ok_or_else(|| "generated face has an invalid parent path".to_owned())?;
            let kind = kind_from_source_path(&source)
                .ok_or_else(|| "generated face has an invalid parent path".to_owned())?;
            values.insert("parent_source".to_owned(), source.clone());
            values.insert("parent_kind".to_owned(), kind.clone());
            values.insert(
                "parent_node".to_owned(),
                NodeId::from_namespaced_path(&authoring_namespace(), &source, &kind).to_string(),
            );
        }
        None => return Err("generated face has an invalid parent path".to_owned()),
    }
    values.insert(
        "registration_rule".to_owned(),
        rule_syntax_for_source(path)?,
    );
    Ok(FaceManifest { values })
}