Skip to main content

konjure_lang/
native.rs

1//! Explicit native-function boundary. Registered functions must be pure and bounded.
2pub use crate::catalog::functions::BUILTIN_NAMES;
3use crate::{Type, Value};
4use std::collections::BTreeMap;
5
6/// A host-owned pure operation. It must not access devices, perform I/O, mutate
7/// external state, or panic. Such effects cannot be rolled back by the interpreter.
8#[derive(Clone)]
9pub struct NativeFunction {
10    /// Unique function name, excluding interpreter-reserved builtins.
11    pub name: String,
12    /// Argument types in call order.
13    pub parameters: Vec<Type>,
14    /// Required result type, revalidated after every call.
15    pub returns: Type,
16    /// Pure, bounded callback; see NativeFunction for the host safety contract.
17    pub call: fn(&[Value]) -> Result<Value, String>,
18}
19/// Native signatures are supplied to [`crate::compile_with_registry`]; the runtime
20/// rechecks host arguments and results at every callback boundary.
21#[derive(Clone, Default)]
22pub struct NativeRegistry {
23    pub(crate) functions: BTreeMap<String, NativeFunction>,
24}
25impl NativeRegistry {
26    /// Register a pure native function. Duplicate names are rejected.
27    pub fn register(&mut self, function: NativeFunction) -> Result<(), String> {
28        fn concrete(ty: &Type) -> bool {
29            match ty {
30                Type::Applied { arguments, .. } => arguments.iter().all(concrete),
31                Type::List(inner) | Type::Tensor(inner) | Type::ComponentReference(inner) => {
32                    concrete(inner)
33                }
34                Type::Function {
35                    parameters,
36                    returns,
37                } => parameters.iter().all(concrete) && concrete(returns),
38                Type::Named(name) => name != "Any" && name != "List",
39            }
40        }
41        if !function.parameters.iter().all(concrete) || !concrete(&function.returns) {
42            return Err(
43                "native signatures require concrete types; use List[T] instead of List or Any"
44                    .into(),
45            );
46        }
47        if self.functions.contains_key(&function.name) || is_builtin(&function.name) {
48            return Err(format!(
49                "native function `{}` is already defined or reserved",
50                function.name
51            ));
52        }
53        self.functions.insert(function.name.clone(), function);
54        Ok(())
55    }
56    /// Names and signatures can be used by a host's language-service UI.
57    pub fn functions(&self) -> impl Iterator<Item = &NativeFunction> {
58        self.functions.values()
59    }
60    pub(crate) fn signatures(&self) -> BTreeMap<String, (Vec<Type>, Type)> {
61        self.functions
62            .values()
63            .map(|function| {
64                (
65                    function.name.clone(),
66                    (function.parameters.clone(), function.returns.clone()),
67                )
68            })
69            .collect()
70    }
71}
72pub(crate) fn is_builtin(name: &str) -> bool {
73    BUILTIN_NAMES.contains(&name) && !crate::methods::is_owned_global(name)
74}
75
76/// Concrete builtin signature. Polymorphic builtins require an expected function
77/// type; they are checked by the compiler using their ordinary call contracts.
78pub(crate) fn monomorphic_signature(name: &str) -> Option<Type> {
79    if crate::methods::is_owned_global(name) {
80        return None;
81    }
82    crate::catalog::functions::monomorphic_signature(name)
83}
84
85pub(crate) fn valid_builtin_specialization(
86    name: &str,
87    signature: &Type,
88    is_class: impl Fn(&str) -> bool,
89) -> bool {
90    if crate::methods::is_owned_global(name) {
91        return false;
92    }
93    if crate::data::valid_data_signature(name, signature) {
94        return true;
95    }
96    if let Some(known) = monomorphic_signature(name) {
97        return known == *signature;
98    }
99    let Type::Function {
100        parameters,
101        returns,
102    } = signature
103    else {
104        return false;
105    };
106    let unit = Type::named("Unit");
107    let number = Type::named("f64");
108    let entity = Type::named("Entity");
109    match (name, parameters.as_slice()) {
110        ("print", [_]) => **returns == unit,
111        ("len", [Type::List(_)]) => **returns == number,
112        ("len", [Type::Named(name)]) => name == "Str" && **returns == number,
113        ("append", [list @ Type::List(inner), element]) => {
114            inner.as_ref() == element && returns.as_ref() == list
115        }
116        ("range", [one]) => {
117            *one == number && **returns == crate::data::result_type(Type::List(Box::new(number)))
118        }
119        ("range", [one, two]) => {
120            *one == number
121                && *two == number
122                && **returns == crate::data::result_type(Type::List(Box::new(number)))
123        }
124        ("spawn", [Type::Named(class)]) => is_class(class) && **returns == entity,
125        ("add" | "set", [first, Type::Named(class)]) => {
126            *first == entity && is_class(class) && **returns == unit
127        }
128        _ => false,
129    }
130}