use rucc_base::{ScopeMap, Symbol};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IdentKind {
Typedef,
Ordinary,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TagKind {
Struct,
Union,
Enum,
}
#[derive(Debug, Default)]
pub struct Scopes {
ordinary: ScopeMap<IdentKind>,
tags: ScopeMap<TagKind>,
}
impl Scopes {
#[must_use]
pub fn new() -> Self {
Scopes::default()
}
pub fn push(&mut self) {
self.ordinary.push();
self.tags.push();
}
pub fn pop(&mut self) {
self.ordinary.pop();
self.tags.pop();
}
#[inline]
#[must_use]
pub fn at_file_scope(&self) -> bool {
self.ordinary.at_file_scope()
}
#[inline]
#[must_use]
pub fn depth(&self) -> u32 {
self.ordinary.depth()
}
#[inline]
#[must_use]
pub fn ident(&self, name: Symbol) -> Option<IdentKind> {
self.ordinary.get(name)
}
#[inline]
#[must_use]
pub fn is_typedef_name(&self, name: Symbol) -> bool {
self.ordinary.get(name) == Some(IdentKind::Typedef)
}
pub fn declare(&mut self, name: Symbol, kind: IdentKind) -> Option<IdentKind> {
self.ordinary.declare(name, kind)
}
pub fn declare_tag(&mut self, name: Symbol, kind: TagKind) -> Option<TagKind> {
self.tags.declare(name, kind)
}
#[inline]
#[must_use]
pub fn tag(&self, name: Symbol) -> Option<TagKind> {
self.tags.get(name)
}
}
#[cfg(test)]
mod tests {
use super::*;
const T: Symbol = Symbol::from_raw(1);
const X: Symbol = Symbol::from_raw(2);
#[test]
fn an_undeclared_name_is_not_a_type_name() {
let scopes = Scopes::new();
assert_eq!(scopes.ident(T), None);
assert!(!scopes.is_typedef_name(T));
}
#[test]
fn a_parameter_takes_the_name_away_from_the_typedef() {
let mut scopes = Scopes::new();
scopes.declare(T, IdentKind::Typedef);
scopes.push();
assert!(scopes.is_typedef_name(T));
scopes.declare(T, IdentKind::Ordinary);
assert!(!scopes.is_typedef_name(T));
assert_eq!(scopes.ident(T), Some(IdentKind::Ordinary));
scopes.pop();
assert!(scopes.is_typedef_name(T));
}
#[test]
fn a_variable_may_take_the_name_of_the_typedef_that_gave_it_its_type() {
let mut scopes = Scopes::new();
scopes.declare(T, IdentKind::Typedef);
assert!(scopes.is_typedef_name(T));
assert_eq!(scopes.declare(T, IdentKind::Ordinary), Some(IdentKind::Typedef));
assert_eq!(scopes.ident(T), Some(IdentKind::Ordinary));
}
#[test]
fn a_typedef_is_exposed_again_when_the_inner_scope_closes() {
let mut scopes = Scopes::new();
scopes.declare(T, IdentKind::Typedef);
scopes.push();
assert_eq!(scopes.declare(T, IdentKind::Ordinary), None);
assert_eq!(scopes.ident(T), Some(IdentKind::Ordinary));
scopes.push();
assert_eq!(scopes.declare(T, IdentKind::Typedef), None);
assert!(scopes.is_typedef_name(T));
scopes.pop();
assert_eq!(scopes.ident(T), Some(IdentKind::Ordinary));
scopes.pop();
assert!(scopes.is_typedef_name(T));
}
#[test]
fn a_tag_does_not_disturb_the_ordinary_name() {
let mut scopes = Scopes::new();
scopes.declare(T, IdentKind::Typedef);
assert_eq!(scopes.declare_tag(T, TagKind::Struct), None);
assert_eq!(scopes.tag(T), Some(TagKind::Struct));
assert!(scopes.is_typedef_name(T));
assert_eq!(scopes.tag(X), None);
}
#[test]
fn a_binding_in_an_inner_scope_is_not_a_redeclaration() {
let mut scopes = Scopes::new();
assert_eq!(scopes.declare(X, IdentKind::Ordinary), None);
scopes.push();
assert_eq!(scopes.declare(X, IdentKind::Ordinary), None);
assert_eq!(scopes.declare(X, IdentKind::Typedef), Some(IdentKind::Ordinary));
scopes.pop();
}
#[test]
fn closing_a_scope_leaves_nothing_behind() {
let mut scopes = Scopes::new();
assert!(scopes.at_file_scope());
for _ in 0..64 {
scopes.push();
scopes.declare(X, IdentKind::Ordinary);
scopes.declare_tag(X, TagKind::Union);
}
assert_eq!(scopes.depth(), 65);
for _ in 0..64 {
scopes.pop();
}
assert!(scopes.at_file_scope());
assert_eq!(scopes.ident(X), None);
assert_eq!(scopes.tag(X), None);
}
#[test]
#[should_panic(expected = "the file scope is never closed")]
fn the_file_scope_cannot_be_closed() {
Scopes::new().pop();
}
}