runmat-hir 0.4.5

High-level IR for RunMat with type inference and lowering utilities
Documentation
use crate::inference::function_outputs::infer_function_output_types;
use crate::inference::function_vars::infer_function_variable_types;
use crate::inference::globals::infer_global_variable_types;
use crate::validation::classdefs::validate_classdefs;
use crate::{HirProgram, HirStmt, LoweringContext, LoweringResult, SemanticError, Type, VarId};
use runmat_parser::Program as AstProgram;
use std::collections::HashMap;

pub(crate) struct Scope {
    pub(crate) parent: Option<usize>,
    pub(crate) bindings: HashMap<String, VarId>,
}

pub(crate) struct Ctx {
    pub(crate) scopes: Vec<Scope>,
    pub(crate) var_types: Vec<Type>,
    pub(crate) next_var: usize,
    pub(crate) functions: HashMap<String, HirStmt>,
    pub(crate) var_names: Vec<Option<String>>,
    pub(crate) allow_unqualified_statics: bool,
}

pub fn lower(
    prog: &AstProgram,
    context: &LoweringContext<'_>,
) -> Result<LoweringResult, SemanticError> {
    let mut ctx = Ctx::new();

    for (name, var_id) in context.variables {
        ctx.scopes[0].bindings.insert(name.clone(), VarId(*var_id));
        while ctx.var_types.len() <= *var_id {
            ctx.var_types.push(Type::Unknown);
        }
        while ctx.var_names.len() <= *var_id {
            ctx.var_names.push(None);
        }
        ctx.var_names[*var_id] = Some(name.clone());
        if *var_id >= ctx.next_var {
            ctx.next_var = var_id + 1;
        }
    }

    for (name, func_stmt) in context.functions {
        ctx.functions.insert(name.clone(), func_stmt.clone());
    }

    let body = ctx.lower_stmts(&prog.body)?;
    let var_types = ctx.var_types.clone();
    let hir = HirProgram { body, var_types };
    validate_classdefs(&hir)?;

    let mut variables: HashMap<String, usize> = HashMap::new();
    for (name, var_id) in &ctx.scopes[0].bindings {
        variables.insert(name.clone(), var_id.0);
    }
    let mut var_names = HashMap::new();
    for (idx, name_opt) in ctx.var_names.iter().enumerate() {
        if let Some(name) = name_opt {
            var_names.insert(VarId(idx), name.clone());
        }
    }

    let inferred_function_returns = infer_function_output_types(&hir);
    let inferred_function_envs = infer_function_variable_types(&hir);
    let inferred_globals = infer_global_variable_types(&hir, &inferred_function_returns);

    Ok(LoweringResult {
        hir,
        variables,
        functions: ctx.functions,
        var_types: ctx.var_types,
        var_names,
        inferred_globals,
        inferred_function_envs,
        inferred_function_returns,
    })
}

impl Ctx {
    pub(crate) fn new() -> Self {
        Self {
            scopes: vec![Scope {
                parent: None,
                bindings: HashMap::new(),
            }],
            var_types: Vec::new(),
            next_var: 0,
            functions: HashMap::new(),
            var_names: Vec::new(),
            allow_unqualified_statics: false,
        }
    }

    pub(crate) fn push_scope(&mut self) -> usize {
        let parent = Some(self.scopes.len() - 1);
        self.scopes.push(Scope {
            parent,
            bindings: HashMap::new(),
        });
        self.scopes.len() - 1
    }

    pub(crate) fn pop_scope(&mut self) {
        self.scopes.pop();
    }

    pub(crate) fn define(&mut self, name: String) -> VarId {
        let id = VarId(self.next_var);
        self.next_var += 1;
        let current = self.scopes.len() - 1;
        self.scopes[current].bindings.insert(name.clone(), id);
        self.var_types.push(Type::Unknown);
        self.var_names.push(Some(name));
        id
    }

    pub(crate) fn lookup_current_scope(&self, name: &str) -> Option<VarId> {
        let current = self.scopes.len() - 1;
        self.scopes[current].bindings.get(name).copied()
    }

    pub(crate) fn lookup(&self, name: &str) -> Option<VarId> {
        let mut scope_idx = Some(self.scopes.len() - 1);
        while let Some(idx) = scope_idx {
            if let Some(id) = self.scopes[idx].bindings.get(name) {
                return Some(*id);
            }
            scope_idx = self.scopes[idx].parent;
        }
        None
    }

    pub(crate) fn is_constant(&self, name: &str) -> bool {
        runmat_builtins::constants().iter().any(|c| c.name == name)
    }

    pub(crate) fn is_builtin_function(&self, name: &str) -> bool {
        runmat_builtins::builtin_functions()
            .iter()
            .any(|b| b.name == name)
    }

    pub(crate) fn is_user_defined_function(&self, name: &str) -> bool {
        self.functions.contains_key(name)
    }

    pub(crate) fn is_function(&self, name: &str) -> bool {
        self.is_user_defined_function(name) || self.is_builtin_function(name)
    }

    pub(crate) fn is_static_method_class(&self, name: &str) -> bool {
        matches!(
            name,
            "gpuArray"
                | "logical"
                | "double"
                | "single"
                | "int8"
                | "int16"
                | "int32"
                | "int64"
                | "uint8"
                | "uint16"
                | "uint32"
                | "uint64"
                | "char"
                | "string"
                | "cell"
                | "struct"
        )
    }
}