use std::collections::HashMap;
use super::{CodeGenerator, ExpressionResult};
use crate::ll::{
ast::{Ast, NodeId},
bytecode::{CaptureKind, GlobalIndex, Opcode, Opr24},
error::{LanguageError, LanguageErrorKind},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct LocalIndex(Opr24);
impl LocalIndex {
pub(crate) fn to_u32(self) -> u32 {
u32::from(self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct UpvalueIndex(Opr24);
impl UpvalueIndex {
pub(crate) fn to_u32(self) -> u32 {
u32::from(self.0)
}
}
#[derive(Debug)]
struct Variable {
stack_slot: LocalIndex,
is_captured: bool,
}
#[derive(Debug, Default)]
struct Scope {
variables_by_name: HashMap<String, Variable>,
allocated_variable_count: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum VariablePlace {
Global(GlobalIndex),
Local(LocalIndex),
Upvalue(UpvalueIndex),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum VariableAllocation {
Inherit,
Allocate,
}
#[derive(Default)]
pub(super) struct Locals {
pub(super) parent: Option<Box<Self>>,
scopes: Vec<Scope>,
local_count: u32,
allocated_local_count: u32,
pub(super) captures: Vec<CaptureKind>,
}
impl Locals {
fn create_local(
&mut self,
name: &str,
allocation: VariableAllocation,
) -> Result<VariablePlace, LanguageErrorKind> {
let slot = Opr24::new(self.local_count).map_err(|_| LanguageErrorKind::TooManyLocals)?;
let slot = LocalIndex(slot);
let scope = self.scopes.last_mut().unwrap();
scope
.variables_by_name
.insert(name.to_owned(), Variable { stack_slot: slot, is_captured: false });
self.local_count += 1;
if allocation == VariableAllocation::Allocate {
self.allocated_local_count += 1;
scope.allocated_variable_count += 1;
}
Ok(VariablePlace::Local(slot))
}
fn variables_in_scope_mut(&mut self) -> impl Iterator<Item = (&str, &'_ mut Variable)> {
self.scopes
.iter_mut()
.rev()
.flat_map(|scope| scope.variables_by_name.iter_mut().map(|(s, v)| (s.as_str(), v)))
}
fn capture_index(&mut self, capture: CaptureKind) -> Result<UpvalueIndex, LanguageErrorKind> {
let index = self.captures.iter().rposition(|c| c.eq(&capture)).unwrap_or_else(|| {
let index = self.captures.len();
self.captures.push(capture);
index
});
Ok(UpvalueIndex(Opr24::try_from(index).map_err(|_| LanguageErrorKind::TooManyCaptures)?))
}
fn lookup(&mut self, name: &str) -> Result<Option<VariablePlace>, LanguageErrorKind> {
for scope in self.scopes.iter().rev() {
if let Some(var) = scope.variables_by_name.get(name) {
return Ok(Some(VariablePlace::Local(var.stack_slot)));
}
}
if let Some(parent) = self.parent.as_mut() {
if let Some(place) = parent.lookup(name)? {
match place {
VariablePlace::Local(local_slot) => {
let (_, variable) = parent
.variables_in_scope_mut()
.find(|(_, var)| var.stack_slot == local_slot)
.unwrap();
variable.is_captured = true;
let stack_slot = variable.stack_slot;
let upvalue_index = self.capture_index(CaptureKind::Local(stack_slot))?;
return Ok(Some(VariablePlace::Upvalue(upvalue_index)));
}
VariablePlace::Upvalue(upvalue_index) => {
let own_index = self.capture_index(CaptureKind::Upvalue(upvalue_index))?;
return Ok(Some(VariablePlace::Upvalue(own_index)));
}
VariablePlace::Global(_) => unreachable!(),
}
}
}
Ok(None)
}
fn push_scope(&mut self) {
self.scopes.push(Default::default());
}
fn pop_scope(&mut self) -> Scope {
let scope = self.scopes.pop().expect("no scopes left on the stack");
self.local_count -= scope.variables_by_name.len() as u32;
self.allocated_local_count -= scope.allocated_variable_count;
scope
}
}
impl<'e> CodeGenerator<'e> {
pub(super) fn create_variable(
&mut self,
name: &str,
allocation: VariableAllocation,
) -> Result<VariablePlace, LanguageErrorKind> {
if !self.locals.scopes.is_empty() {
let place = self.locals.create_local(name, allocation)?;
self.chunk.preallocate_stack_slots =
self.chunk.preallocate_stack_slots.max(self.locals.allocated_local_count);
Ok(place)
} else {
let slot = self.env.create_global(name)?;
Ok(VariablePlace::Global(slot))
}
}
pub(super) fn lookup_variable(
&mut self,
name: &str,
) -> Result<Option<VariablePlace>, LanguageErrorKind> {
if let Some(place) = self.locals.lookup(name)? {
return Ok(Some(place));
}
Ok(self.env.get_global(name).map(VariablePlace::Global))
}
pub(super) fn push_scope(&mut self) {
self.locals.push_scope();
}
pub(super) fn pop_scope(&mut self) {
let scope = self.locals.pop_scope();
for variable in scope.variables_by_name.into_values() {
if variable.is_captured {
self.chunk.emit((Opcode::CloseLocal, variable.stack_slot.0));
}
}
}
pub(super) fn generate_variable_load(&mut self, variable: VariablePlace) {
self.chunk.emit(match variable {
VariablePlace::Global(slot) => (Opcode::GetGlobal, slot.to_opr24()),
VariablePlace::Local(slot) => (Opcode::GetLocal, slot.0),
VariablePlace::Upvalue(slot) => (Opcode::GetUpvalue, slot.0),
});
}
pub(super) fn generate_variable_assign(&mut self, variable: VariablePlace) {
self.chunk.emit(match variable {
VariablePlace::Global(slot) => (Opcode::AssignGlobal, slot.to_opr24()),
VariablePlace::Local(slot) => (Opcode::AssignLocal, slot.0),
VariablePlace::Upvalue(slot) => (Opcode::AssignUpvalue, slot.0),
});
}
pub(super) fn generate_variable_sink(&mut self, variable: VariablePlace) {
self.chunk.emit(match variable {
VariablePlace::Global(slot) => (Opcode::SinkGlobal, slot.to_opr24()),
VariablePlace::Local(slot) => (Opcode::SinkLocal, slot.0),
VariablePlace::Upvalue(slot) => (Opcode::SinkUpvalue, slot.0),
});
}
}
impl<'e> CodeGenerator<'e> {
pub(super) fn generate_variable(
&mut self,
ast: &Ast,
node: NodeId,
) -> Result<ExpressionResult, LanguageError> {
let name = ast.string(node).unwrap();
if let Some(variable) = self.lookup_variable(name).map_err(|kind| ast.error(node, kind))? {
self.generate_variable_load(variable);
Ok(ExpressionResult::Present)
} else {
Err(ast.error(node, LanguageErrorKind::VariableDoesNotExist(name.to_owned())))
}
}
}