runmat-vm 0.5.0

RunMat virtual machine and bytecode interpreter
Documentation
use crate::interpreter::errors::mex;
use runmat_builtins::{self, Access, ClassDef, MethodDef, PropertyDef, Value};
use runmat_runtime::RuntimeError;

pub fn register_class(
    name: String,
    super_class: Option<String>,
    is_sealed: bool,
    is_abstract: bool,
    properties: Vec<(String, bool, bool, Option<Value>, String, String)>,
    methods: Vec<(String, String, bool, bool, bool, String)>,
    enumerations: Vec<String>,
) -> Result<(), RuntimeError> {
    if let Some(parent) = super_class.as_deref() {
        if runmat_builtins::is_class_sealed(parent) {
            return Err(mex(
                "RunMat:ClassSealed",
                &format!("Cannot subclass sealed class '{}'.", parent),
            ));
        }
    }
    let mut prop_map = std::collections::HashMap::new();
    for (p, is_static, is_constant, default_value, get_access, set_access) in properties {
        let gacc = if get_access.eq_ignore_ascii_case("private") {
            Access::Private
        } else if get_access.eq_ignore_ascii_case("protected") {
            Access::Protected
        } else {
            Access::Public
        };
        let sacc = if set_access.eq_ignore_ascii_case("private") {
            Access::Private
        } else if set_access.eq_ignore_ascii_case("protected") {
            Access::Protected
        } else {
            Access::Public
        };
        let (is_dep, clean_name) = if let Some(stripped) = p.strip_prefix("@dep:") {
            (true, stripped.to_string())
        } else {
            (false, p.clone())
        };
        prop_map.insert(
            clean_name.clone(),
            PropertyDef {
                name: clean_name,
                is_static,
                is_constant,
                is_dependent: is_dep,
                get_access: gacc,
                set_access: sacc,
                default_value,
            },
        );
    }
    let mut method_map = std::collections::HashMap::new();
    for (mname, fname, is_static, is_method_abstract, is_method_sealed, access) in methods {
        let access = if access.eq_ignore_ascii_case("private") {
            Access::Private
        } else if access.eq_ignore_ascii_case("protected") {
            Access::Protected
        } else {
            Access::Public
        };
        method_map.insert(
            mname.clone(),
            MethodDef {
                name: mname,
                is_static,
                is_abstract: is_method_abstract,
                is_sealed: is_method_sealed,
                access,
                function_name: fname,
                implicit_class_argument: None,
            },
        );
    }

    let inherited_sealed = collect_inherited_sealed_methods(super_class.as_deref());
    if let Some(conflict) = method_map
        .keys()
        .find(|method_name| inherited_sealed.contains(*method_name))
    {
        return Err(mex(
            "RunMat:MethodSealed",
            &format!(
                "Class '{}' cannot override sealed method '{}'.",
                name, conflict
            ),
        ));
    }

    if !is_abstract {
        let mut required_abstract = collect_required_abstract_methods(super_class.as_deref());
        for (method_name, method) in &method_map {
            if method.is_abstract {
                required_abstract.insert(method_name.clone());
            } else {
                required_abstract.remove(method_name);
            }
        }
        if let Some(missing) = required_abstract.into_iter().next() {
            return Err(mex(
                "RunMat:AbstractMethodMissing",
                &format!(
                    "Class '{}' must implement abstract method '{}'.",
                    name, missing
                ),
            ));
        }
    }
    let def = ClassDef {
        name: name.clone(),
        parent: super_class.clone(),
        properties: prop_map,
        methods: method_map,
    };
    runmat_builtins::register_class_with_modifiers(def, is_sealed, is_abstract);
    runmat_builtins::register_class_enumerations(&name, enumerations);
    Ok(())
}

fn collect_required_abstract_methods(
    super_class: Option<&str>,
) -> std::collections::HashSet<String> {
    let mut lineage = Vec::new();
    let mut visited = std::collections::HashSet::new();
    let mut cursor = super_class.map(str::to_string);
    while let Some(class_name) = cursor {
        if !visited.insert(class_name.clone()) {
            break;
        }
        let Some(class_def) = runmat_builtins::get_class(&class_name) else {
            break;
        };
        cursor = class_def.parent.clone();
        lineage.push(class_def);
    }
    lineage.reverse();
    let mut required = std::collections::HashSet::new();
    for class_def in lineage {
        for (method_name, method) in class_def.methods {
            if method.is_abstract {
                required.insert(method_name);
            } else {
                required.remove(&method_name);
            }
        }
    }
    required
}

fn collect_inherited_sealed_methods(
    super_class: Option<&str>,
) -> std::collections::HashSet<String> {
    let mut sealed = std::collections::HashSet::new();
    let mut visited = std::collections::HashSet::new();
    let mut cursor = super_class.map(str::to_string);
    while let Some(class_name) = cursor {
        if !visited.insert(class_name.clone()) {
            break;
        }
        let Some(class_def) = runmat_builtins::get_class(&class_name) else {
            break;
        };
        for (method_name, method) in &class_def.methods {
            if method.is_sealed {
                sealed.insert(method_name.clone());
            }
        }
        cursor = class_def.parent;
    }
    sealed
}