hara-native 0.1.17

HAL-free native host runtime and package launcher for Hara
Documentation
use crate::kernel::Form;

use super::wit::{digest, keyword, named_type, string, symbol};
use super::wit_parser::{self, Function, Interface, Type, TypeDecl};
use super::{
    HaraValueType, Lowering, Ownership, WasmValueType, WitDiagnostic, WitDiagnosticSeverity,
    WitRoute, WASM_INTERFACE_SCHEMA, WIT_IR_SCHEMA,
};

pub(super) fn skeleton_source(
    namespace: &str,
    module: &str,
    exports: &[(
        &Function,
        Vec<(String, HaraValueType)>,
        Option<HaraValueType>,
    )],
    memory: bool,
    _route: WitRoute,
) -> String {
    let mut entries = vec![
        (keyword("schema"), string(WASM_INTERFACE_SCHEMA)),
        (keyword("namespace"), symbol(namespace)),
        (keyword("module"), string(module)),
    ];
    if memory {
        entries.push((
            keyword("memory"),
            Form::Map(vec![(keyword("export"), string("memory"))]),
        ));
    }
    entries.push((
        keyword("exports"),
        Form::Map(
            exports
                .iter()
                .map(|(function, arguments, result)| {
                    (
                        symbol(&function.name),
                        Form::Map(vec![
                            (keyword("wasm/export"), string(&function.name)),
                            (
                                keyword("arguments"),
                                Form::Vector(
                                    arguments
                                        .iter()
                                        .enumerate()
                                        .map(|(index, (name, ty))| {
                                            parameter_form(
                                                name,
                                                ty,
                                                index,
                                                function.arguments[index].1.clone(),
                                            )
                                        })
                                        .collect(),
                                ),
                            ),
                            (
                                keyword("returns"),
                                result_form(result.as_ref(), function.result.as_ref()),
                            ),
                        ]),
                    )
                })
                .collect(),
        ),
    ));
    Form::List(vec![symbol("wasm/interface"), Form::Map(entries)]).to_string()
}

fn parameter_form(name: &str, ty: &HaraValueType, index: usize, original: Type) -> Form {
    let name = if name.is_empty() {
        format!("arg-{index}")
    } else {
        name.to_owned()
    };
    let mut entries = vec![
        (keyword("name"), symbol(&name)),
        (keyword("hara/type"), hara_type_form(ty)),
    ];
    match ty {
        HaraValueType::String | HaraValueType::Bytes => {
            entries.push((keyword("wasm/type"), keyword("i32")));
            entries.push((
                keyword("lower"),
                Form::Vector(vec![keyword("pointer"), keyword("length")]),
            ));
            entries.push((keyword("ownership"), keyword("borrowed")));
        }
        HaraValueType::Record(_)
        | HaraValueType::Variant(_)
        | HaraValueType::Handle(_)
        | HaraValueType::Callback(_) => {
            entries.push((keyword("wasm/type"), keyword("unresolved")));
        }
        _ => entries.push((keyword("wasm/type"), keyword(wasm_type_for(ty, &original)))),
    }
    Form::Map(entries)
}

fn result_form(mapped: Option<&HaraValueType>, original: Option<&Type>) -> Form {
    let ty = mapped.cloned().unwrap_or(HaraValueType::Void);
    let mut entries = vec![(keyword("hara/type"), hara_type_form(&ty))];
    match ty {
        HaraValueType::String | HaraValueType::Bytes => {
            entries.push((keyword("wasm/type"), keyword("i64")));
            entries.push((keyword("lift"), keyword("packed-i64")));
            entries.push((keyword("ownership"), keyword("caller")));
        }
        HaraValueType::Record(_)
        | HaraValueType::Variant(_)
        | HaraValueType::Handle(_)
        | HaraValueType::Callback(_) => {
            entries.push((keyword("wasm/type"), keyword("unresolved")));
        }
        _ => entries.push((
            keyword("wasm/type"),
            keyword(wasm_type_for(
                &ty,
                original.unwrap_or(&Type::Atom("unit".into())),
            )),
        )),
    }
    Form::Map(entries)
}

pub(super) fn normalized_source(
    namespace: &str,
    package: Option<&str>,
    world: Option<&str>,
    interface_name: &str,
    route: WitRoute,
    interface: &Interface,
    exports: &[(
        &Function,
        Vec<(String, HaraValueType)>,
        Option<HaraValueType>,
    )],
    diagnostics: &[WitDiagnostic],
    source: &str,
    origin: &str,
) -> String {
    let types = interface
        .types
        .iter()
        .map(|(name, declaration)| {
            Form::Map(vec![
                (keyword("name"), symbol(name)),
                (keyword("kind"), keyword(type_decl_kind(declaration))),
                (keyword("source"), string(&format!("{declaration:?}"))),
            ])
        })
        .collect();
    Form::Map(vec![
        (keyword("schema"), string(WIT_IR_SCHEMA)),
        (keyword("namespace"), symbol(namespace)),
        (keyword("package"), package.map(string).unwrap_or(Form::Nil)),
        (keyword("world"), world.map(symbol).unwrap_or(Form::Nil)),
        (keyword("interface"), symbol(interface_name)),
        (keyword("route"), keyword(route.as_keyword())),
        (keyword("types"), Form::Vector(types)),
        (
            keyword("exports"),
            Form::Vector(
                exports
                    .iter()
                    .map(|(function, arguments, result)| {
                        Form::Map(vec![
                            (keyword("name"), symbol(&function.name)),
                            (
                                keyword("arguments"),
                                Form::Vector(
                                    arguments
                                        .iter()
                                        .enumerate()
                                        .map(|(index, (name, ty))| {
                                            Form::Map(vec![
                                                (keyword("name"), symbol(name)),
                                                (keyword("hara/type"), hara_type_form(ty)),
                                                (
                                                    keyword("wit/type"),
                                                    string(&wit_parser::type_label(
                                                        &function.arguments[index].1,
                                                    )),
                                                ),
                                            ])
                                        })
                                        .collect(),
                                ),
                            ),
                            (
                                keyword("returns"),
                                result.as_ref().map_or_else(
                                    || {
                                        Form::Map(vec![
                                            (keyword("hara/type"), keyword("void")),
                                            (keyword("wit/type"), keyword("unit")),
                                        ])
                                    },
                                    |result| {
                                        Form::Map(vec![
                                            (keyword("hara/type"), hara_type_form(result)),
                                            (
                                                keyword("wit/type"),
                                                string(
                                                    &function
                                                        .result
                                                        .as_ref()
                                                        .map(wit_parser::type_label)
                                                        .unwrap_or_else(|| "unit".into()),
                                                ),
                                            ),
                                        ])
                                    },
                                ),
                            ),
                        ])
                    })
                    .collect(),
            ),
        ),
        (
            keyword("diagnostics"),
            Form::Vector(diagnostics.iter().map(diagnostic_form).collect()),
        ),
        (
            keyword("provenance"),
            Form::Map(vec![
                (keyword("origin"), string(origin)),
                (keyword("source-digest"), string(&digest(source.as_bytes()))),
            ]),
        ),
    ])
    .to_string()
}

pub(super) fn wit_projection_type(
    ty: &HaraValueType,
    wasm: WasmValueType,
    lowering: Option<Lowering>,
    ownership: Option<Ownership>,
    diagnostics: &mut Vec<WitDiagnostic>,
    path: &str,
) -> Option<String> {
    let exact_scalar = match ty {
        HaraValueType::Boolean if wasm == WasmValueType::I32 => Some("bool"),
        HaraValueType::I32 if wasm == WasmValueType::I32 => Some("s32"),
        HaraValueType::I64 if wasm == WasmValueType::I64 => Some("s64"),
        HaraValueType::F32 if wasm == WasmValueType::F32 => Some("f32"),
        HaraValueType::F64 if wasm == WasmValueType::F64 => Some("f64"),
        HaraValueType::Void if wasm == WasmValueType::Void => Some("unit"),
        _ => None,
    };
    if let Some(value) = exact_scalar {
        return Some(value.into());
    }
    let memory = lowering.is_some()
        && ownership.is_some()
        && matches!(lowering, Some(Lowering::PointerLength));
    match ty {
        HaraValueType::String if wasm == WasmValueType::I32 || wasm == WasmValueType::I64 => {
            if memory {
                Some("string".into())
            } else {
                projection_error(
                    diagnostics,
                    path,
                    "string lacks an explicit memory lowering",
                );
                None
            }
        }
        HaraValueType::Bytes if wasm == WasmValueType::I32 || wasm == WasmValueType::I64 => {
            if memory {
                Some("list<u8>".into())
            } else {
                projection_error(diagnostics, path, "bytes lacks an explicit memory lowering");
                None
            }
        }
        HaraValueType::Record(_)
        | HaraValueType::Variant(_)
        | HaraValueType::Handle(_)
        | HaraValueType::Callback(_) => {
            projection_error(
                diagnostics,
                path,
                "named Hara types require their canonical definitions before projection",
            );
            None
        }
        _ => {
            projection_error(
                diagnostics,
                path,
                "Hara and Wasm types do not form an exact WIT mapping",
            );
            None
        }
    }
}

fn projection_error(diagnostics: &mut Vec<WitDiagnostic>, path: &str, message: &str) {
    diagnostic(
        diagnostics,
        WitDiagnosticSeverity::Unsupported,
        "projection",
        path,
        message,
    );
}

pub(super) fn diagnostic(
    diagnostics: &mut Vec<WitDiagnostic>,
    severity: WitDiagnosticSeverity,
    code: &str,
    path: &str,
    message: &str,
) {
    diagnostics.push(WitDiagnostic {
        severity,
        code: code.into(),
        path: path.into(),
        message: message.into(),
    });
}

pub(super) fn diagnostic_form(diagnostic: &WitDiagnostic) -> Form {
    Form::Map(vec![
        (
            keyword("severity"),
            keyword(diagnostic.severity.as_keyword()),
        ),
        (keyword("code"), keyword(&diagnostic.code)),
        (keyword("path"), string(&diagnostic.path)),
        (keyword("message"), string(&diagnostic.message)),
    ])
}

fn hara_type_form(ty: &HaraValueType) -> Form {
    match ty {
        HaraValueType::I32 => keyword("i32"),
        HaraValueType::I64 => keyword("i64"),
        HaraValueType::F32 => keyword("f32"),
        HaraValueType::F64 => keyword("f64"),
        HaraValueType::Boolean => keyword("boolean"),
        HaraValueType::String => keyword("string"),
        HaraValueType::Bytes => keyword("bytes"),
        HaraValueType::Record(name) => named_type("record", name),
        HaraValueType::Variant(name) => named_type("variant", name),
        HaraValueType::Handle(name) => named_type("handle", name),
        HaraValueType::Callback(name) => named_type("callback", name),
        HaraValueType::Void => keyword("void"),
    }
}

fn wasm_type_for(ty: &HaraValueType, original: &Type) -> &'static str {
    match ty {
        HaraValueType::Boolean | HaraValueType::I32 => "i32",
        HaraValueType::I64 => "i64",
        HaraValueType::F32 => "f32",
        HaraValueType::F64 => "f64",
        HaraValueType::Void => "void",
        HaraValueType::String | HaraValueType::Bytes => {
            if matches!(original, Type::List(_)) {
                "i32"
            } else {
                "i32"
            }
        }
        _ => "unresolved",
    }
}

fn type_decl_kind(declaration: &TypeDecl) -> &'static str {
    match declaration {
        TypeDecl::Alias(_) => "alias",
        TypeDecl::Record(_) => "record",
        TypeDecl::Variant(_) => "variant",
        TypeDecl::Resource => "resource",
        TypeDecl::Unsupported(_) => "unsupported",
    }
}