#[derive(Debug, Clone)]
pub struct Local {
name: String,
depth: usize,
initialized: bool,
}
#[derive(Debug, Clone, Default)]
pub struct Locals {
locals: Vec<Local>,
depth: usize,
}
impl Locals {
pub fn resolve_local(&self, name: &str) -> Option<usize> {
for (i, local) in self.locals.iter().enumerate().rev() {
if local.name == *name && local.initialized {
return Some(i);
}
}
None
}
pub fn check_local_on_depth(&self, name: &str, depth: usize) -> bool {
if let Some(offset) = self.resolve_local(name) {
return self.locals[offset].depth == depth;
}
false
}
pub fn check_local(&self, name: &str) -> bool {
self.check_local_on_depth(name, self.depth)
}
pub fn add_local(&mut self, name: &str) -> Local {
let local = Local {
name: name.to_string(),
depth: self.depth,
initialized: false,
};
self.locals.push(local.clone());
local
}
pub fn begin_scope(&mut self) {
self.depth += 1;
}
pub fn end_scope(&mut self) -> usize {
let locals_in_scope = self
.locals
.iter()
.rev()
.take_while(|local| local.depth == self.depth)
.count();
for _ in 0..locals_in_scope {
self.locals.pop();
}
self.depth -= 1;
locals_in_scope
}
pub fn initialize_last_local(&mut self) {
self.locals.last_mut().unwrap().initialized = true;
}
pub fn depth(&self) -> usize {
self.depth
}
pub fn last_index(&self) -> usize {
&self.locals.len() - 1
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn add_local() {
let mut locals = Locals::default();
locals.begin_scope();
let local = locals.add_local("a");
assert_eq!(local.depth, 1);
locals.begin_scope();
let local = locals.add_local("b");
assert_eq!(local.depth, 2);
locals.end_scope();
let local = locals.add_local("c");
assert_eq!(local.depth, 1);
locals.end_scope();
}
#[test]
fn begin_scope() {
let mut locals = Locals::default();
locals.begin_scope();
assert_eq!(locals.depth, 1);
locals.begin_scope();
assert_eq!(locals.depth, 2);
}
#[test]
fn end_scope() {
let mut locals = Locals::default();
locals.begin_scope(); locals.begin_scope(); locals.add_local("a");
locals.add_local("b");
let locals_in_scope = locals.end_scope();
assert_eq!(locals_in_scope, 2, "inner scope had 2 variables");
assert_eq!(locals.depth, 1, "inner scope ended");
let locals_in_scope = locals.end_scope();
assert_eq!(locals_in_scope, 0, "outer scope had no variables");
assert_eq!(locals.depth, 0, "outer scope ended");
}
#[test]
fn resolve_locals() {
let mut locals = Locals::default();
locals.begin_scope();
locals.add_local("a");
locals.initialize_last_local();
locals.begin_scope();
locals.add_local("b");
locals.initialize_last_local();
assert_eq!(locals.resolve_local("a"), Some(0));
assert_eq!(locals.resolve_local("b"), Some(1));
assert_eq!(locals.resolve_local("c"), None);
locals.end_scope();
assert_eq!(
locals.resolve_local("a"),
Some(0),
"a should still be in scope"
);
assert_eq!(locals.resolve_local("b"), None, "b should not be in scope");
}
#[test]
fn check_local_on_depth_level() {
let mut locals = Locals::default();
locals.begin_scope();
locals.add_local("a");
locals.initialize_last_local();
locals.begin_scope();
locals.add_local("b");
locals.initialize_last_local();
assert!(
locals.check_local_on_depth("a", 1),
"a should be on depth 1"
);
assert!(
!locals.check_local_on_depth("a", 2),
"a should not be on depth 2"
);
assert!(
!locals.check_local_on_depth("b", 1),
"b should not be on depth 1"
);
assert!(
locals.check_local_on_depth("b", 2),
"b should be on depth 2"
);
assert!(
!locals.check_local_on_depth("c", 2),
"c should not be on any level"
);
}
}