use crate::chunk::Op;
use super::{Compiler, LocalBinding, LocalBindingKind, LocalStorage};
enum CallableDefinitionTarget {
Slot(u16),
Environment,
ShadowedByValue,
}
impl Compiler {
#[inline]
fn is_boxed_capture(&self, binding: &harn_parser::lexical::BindingId, mutable: bool) -> bool {
mutable && self.captured_bindings.contains(binding)
}
pub(super) fn define_local_slot(&mut self, name: &str, mutable: bool) -> Option<u16> {
if self.module_level || harn_parser::is_discard_name(name) {
return None;
}
let current = self.local_scopes.last_mut()?;
if let Some(existing) = current.get_mut(name) {
if existing.kind == LocalBindingKind::Callable {
existing.kind = LocalBindingKind::Value;
existing.mutable = mutable;
return match existing.storage {
LocalStorage::Slot(slot) => {
if let Some(info) = self.chunk.local_slots.get_mut(slot as usize) {
info.mutable = mutable;
}
Some(slot)
}
LocalStorage::Environment => None,
};
}
if existing.mutable || mutable {
if mutable {
existing.mutable = true;
if let LocalStorage::Slot(slot) = existing.storage {
if let Some(info) = self.chunk.local_slots.get_mut(slot as usize) {
info.mutable = true;
}
}
}
return match existing.storage {
LocalStorage::Slot(slot) => Some(slot),
LocalStorage::Environment => None,
};
}
return None;
}
let slot = self
.chunk
.add_local_slot(name.to_string(), mutable, self.scope_depth);
current.insert(
name.to_string(),
LocalBinding {
storage: LocalStorage::Slot(slot),
kind: LocalBindingKind::Value,
mutable,
},
);
Some(slot)
}
fn resolve_local_binding(&self, name: &str) -> Option<LocalBinding> {
if self.module_level {
return None;
}
for scope in self.local_scopes.iter().rev() {
if let Some(binding) = scope.get(name) {
return Some(*binding);
}
}
None
}
pub(super) fn has_local_binding(&self, name: &str) -> bool {
self.resolve_local_binding(name).is_some()
}
pub(super) fn resolve_local_slot(&self, name: &str) -> Option<u16> {
match self.resolve_local_binding(name)?.storage {
LocalStorage::Slot(slot) => Some(slot),
LocalStorage::Environment => None,
}
}
pub(super) fn emit_get_binding(&mut self, name: &str) {
if let Some(slot) = self.resolve_local_slot(name) {
self.chunk.emit_u16(Op::GetLocalSlot, slot, self.line);
} else {
let idx = self.string_constant(name);
self.chunk.emit_u16(Op::GetVar, idx, self.line);
}
}
pub(super) fn emit_define_binding(&mut self, name: &str, mutable: bool) {
if let Some(slot) = self.define_local_slot(name, mutable) {
self.chunk.emit_u16(Op::DefLocalSlot, slot, self.line);
} else {
let idx = self.string_constant(name);
let op = if mutable { Op::DefVar } else { Op::DefLet };
self.chunk.emit_u16(op, idx, self.line);
}
}
fn define_callable_target(&mut self, name: &str) -> CallableDefinitionTarget {
if self.module_level || harn_parser::is_discard_name(name) {
return CallableDefinitionTarget::Environment;
}
let Some(current) = self.local_scopes.last_mut() else {
return CallableDefinitionTarget::Environment;
};
if let Some(existing) = current.get(name) {
return match (existing.kind, existing.storage) {
(LocalBindingKind::Value, _) => CallableDefinitionTarget::ShadowedByValue,
(LocalBindingKind::Callable, LocalStorage::Slot(slot)) => {
CallableDefinitionTarget::Slot(slot)
}
(LocalBindingKind::Callable, LocalStorage::Environment) => {
CallableDefinitionTarget::Environment
}
};
}
let slot = self
.chunk
.add_local_slot(name.to_string(), false, self.scope_depth);
current.insert(
name.to_string(),
LocalBinding {
storage: LocalStorage::Slot(slot),
kind: LocalBindingKind::Callable,
mutable: false,
},
);
CallableDefinitionTarget::Slot(slot)
}
pub(super) fn emit_callable_binding(&mut self, name: &str) {
match self.define_callable_target(name) {
CallableDefinitionTarget::Slot(slot) => {
self.chunk.emit_u16(Op::DefLocalSlot, slot, self.line);
}
CallableDefinitionTarget::Environment => {
let idx = self.string_constant(name);
self.chunk.emit_u16(Op::DefLet, idx, self.line);
}
CallableDefinitionTarget::ShadowedByValue => {
self.chunk.emit(Op::Pop, self.line);
}
}
}
pub(super) fn emit_source_binding(
&mut self,
name: &str,
mutable: bool,
binding: harn_parser::lexical::BindingId,
) {
if self.is_boxed_capture(&binding, mutable) {
if !self.module_level && !harn_parser::is_discard_name(name) {
if let Some(scope) = self.local_scopes.last_mut() {
scope.insert(
name.to_string(),
LocalBinding {
storage: LocalStorage::Environment,
kind: LocalBindingKind::Value,
mutable,
},
);
}
}
let idx = self.string_constant(name);
self.chunk.emit_u16(Op::DefCell, idx, self.line);
} else {
self.emit_define_binding(name, mutable);
}
}
pub(super) fn emit_init_or_define_binding(&mut self, name: &str, mutable: bool) {
if let Some(slot) = self.resolve_local_slot(name) {
self.chunk.emit_u16(Op::DefLocalSlot, slot, self.line);
} else {
self.emit_define_binding(name, mutable);
}
}
pub(super) fn emit_set_binding(&mut self, name: &str) {
if let Some(slot) = self.resolve_local_slot(name) {
self.chunk.emit_u16(Op::SetLocalSlot, slot, self.line);
} else {
let idx = self.string_constant(name);
self.chunk.emit_u16(Op::SetVar, idx, self.line);
}
}
}