use crate::ast::{BinOp, Expr, Stmt};
use alloc::collections::BTreeMap;
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Type {
Number,
Str,
Bool,
Null,
Unknown,
}
pub struct Resolver {
scopes: Vec<BTreeMap<String, Type>>,
arities: BTreeMap<String, usize>,
errors: Vec<String>,
}
impl Resolver {
pub fn new() -> Self {
Resolver {
scopes: alloc::vec![BTreeMap::new()],
arities: BTreeMap::new(),
errors: Vec::new(),
}
}
pub fn resolve(program: &[Stmt]) -> Result<(), Vec<String>> {
let mut resolver = Resolver::new();
resolver.resolve_stmts(program);
if resolver.errors.is_empty() {
Ok(())
} else {
Err(resolver.errors)
}
}
fn push_scope(&mut self) {
self.scopes.push(BTreeMap::new());
}
fn pop_scope(&mut self) {
self.scopes.pop();
}
fn declare(&mut self, name: &str, ty: Type) {
self.scopes
.last_mut()
.expect("resolver always has at least the global scope")
.insert(name.into(), ty);
}
fn is_declared(&self, name: &str) -> bool {
self.scopes.iter().rev().any(|scope| scope.contains_key(name))
}
fn type_of(&self, name: &str) -> Type {
self.scopes
.iter()
.rev()
.find_map(|scope| scope.get(name).copied())
.unwrap_or(Type::Unknown)
}
fn widen_on_assign(&mut self, name: &str, assigned_ty: Type) {
for scope in self.scopes.iter_mut().rev() {
if let Some(existing) = scope.get_mut(name) {
if *existing != assigned_ty {
*existing = Type::Unknown;
}
return;
}
}
}
fn resolve_stmts(&mut self, stmts: &[Stmt]) {
for stmt in stmts {
self.resolve_stmt(stmt);
}
}
fn resolve_stmt(&mut self, stmt: &Stmt) {
match stmt {
Stmt::Let(name, expr) => {
let ty = self.resolve_expr(expr);
self.declare(name, ty);
}
Stmt::Print(exprs) => {
for expr in exprs {
self.resolve_expr(expr);
}
}
Stmt::ExprStmt(expr) => {
self.resolve_expr(expr);
}
Stmt::If(cond, then_branch, else_branch) => {
self.resolve_expr(cond);
self.push_scope();
self.resolve_stmts(then_branch);
self.pop_scope();
if let Some(else_branch) = else_branch {
self.push_scope();
self.resolve_stmts(else_branch);
self.pop_scope();
}
}
Stmt::While(cond, body) => {
self.resolve_expr(cond);
self.push_scope();
self.resolve_stmts(body);
self.pop_scope();
}
Stmt::FunctionDecl(name, params, body) => {
self.arities.insert(name.clone(), params.len());
self.declare(name, Type::Unknown);
self.push_scope();
for param in params {
self.declare(param, Type::Unknown);
}
self.resolve_stmts(body);
self.pop_scope();
}
Stmt::Return(expr) => {
if let Some(expr) = expr {
self.resolve_expr(expr);
}
}
Stmt::Import(_) => {}
}
}
fn resolve_expr(&mut self, expr: &Expr) -> Type {
match expr {
Expr::Number(_) => Type::Number,
Expr::StringLit(_) => Type::Str,
Expr::Bool(_) => Type::Bool,
Expr::Null => Type::Null,
Expr::Ident(name) => {
if !self.is_declared(name) {
self.errors.push(format!("undefined variable '{}'", name));
Type::Unknown
} else {
self.type_of(name)
}
}
Expr::Neg(inner) => {
let ty = self.resolve_expr(inner);
if ty != Type::Unknown && ty != Type::Number {
self.errors
.push(format!("cannot negate a {}", describe(ty)));
}
Type::Number
}
Expr::Not(inner) => {
self.resolve_expr(inner);
Type::Bool
}
Expr::Assign(name, value) => {
let ty = self.resolve_expr(value);
if !self.is_declared(name) {
self.errors
.push(format!("assignment to undefined variable '{}'", name));
} else {
self.widen_on_assign(name, ty);
}
ty
}
Expr::Binary(op, left, right) => {
let lt = self.resolve_expr(left);
let rt = self.resolve_expr(right);
self.check_binary(op.clone(), lt, rt)
}
Expr::ArrayLit(elements) => {
for e in elements {
self.resolve_expr(e);
}
Type::Unknown
}
Expr::Index(object, index) => {
self.resolve_expr(object);
self.resolve_expr(index);
Type::Unknown
}
Expr::IndexAssign(object, index, value) => {
self.resolve_expr(object);
self.resolve_expr(index);
self.resolve_expr(value)
}
Expr::Call(callee, args) => {
let is_builtin_call = matches!(
callee.as_ref(),
Expr::Ident(name) if crate::interpreter::is_builtin(name)
);
if !is_builtin_call {
self.resolve_expr(callee);
}
for arg in args {
self.resolve_expr(arg);
}
if let Expr::Ident(name) = callee.as_ref() {
if let Some(&expected) = self.arities.get(name) {
if expected != args.len() {
self.errors.push(format!(
"function '{}' expects {} argument(s), got {}",
name,
expected,
args.len()
));
}
}
}
Type::Unknown
}
}
}
fn check_binary(&mut self, op: BinOp, lt: Type, rt: Type) -> Type {
use BinOp::*;
match op {
Add => {
if lt == Type::Number && rt == Type::Number {
Type::Number
} else {
Type::Str
}
}
Sub | Mul | Div | Mod => {
if is_confirmed_non_number(lt) || is_confirmed_non_number(rt) {
self.errors.push(format!(
"arithmetic ({}) on non-number types: {} and {}",
op_symbol(op),
describe(lt),
describe(rt)
));
}
Type::Number
}
Eq | NotEq => Type::Bool,
And | Or => Type::Bool,
Lt | Gt | Lte | Gte => {
if is_confirmed_non_number(lt) || is_confirmed_non_number(rt) {
self.errors.push(format!(
"comparison ({}) on non-number types: {} and {}",
op_symbol(op),
describe(lt),
describe(rt)
));
}
Type::Bool
}
}
}
}
fn is_confirmed_non_number(ty: Type) -> bool {
matches!(ty, Type::Str | Type::Bool | Type::Null)
}
fn describe(ty: Type) -> &'static str {
match ty {
Type::Number => "number",
Type::Str => "string",
Type::Bool => "boolean",
Type::Null => "null",
Type::Unknown => "unknown",
}
}
fn op_symbol(op: BinOp) -> &'static str {
use BinOp::*;
match op {
Add => "+",
Sub => "-",
Mul => "*",
Div => "/",
Mod => "%",
Eq => "==",
NotEq => "!=",
Lt => "<",
Gt => ">",
Lte => "<=",
Gte => ">=",
And => "र",
Or => "वा",
}
}
impl Default for Resolver {
fn default() -> Self {
Self::new()
}
}