use rucc_base::{ScopeMap, Symbol};
use rucc_types::TypeId;
use crate::decl::DeclId;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Binding {
Decl(DeclId),
Typedef(TypeId),
Enumerator {
value: i128,
ty: TypeId,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TagKind {
Struct,
Union,
Enum,
}
impl TagKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
TagKind::Struct => "struct",
TagKind::Union => "union",
TagKind::Enum => "enum",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Tag {
pub kind: TagKind,
pub ty: TypeId,
}
#[derive(Debug, Default)]
pub struct Scopes {
ordinary: ScopeMap<Binding>,
tags: ScopeMap<Tag>,
}
impl Scopes {
#[must_use]
pub fn new() -> Scopes {
Scopes::default()
}
pub fn push(&mut self) {
self.ordinary.push();
self.tags.push();
}
pub fn pop(&mut self) {
self.ordinary.pop();
self.tags.pop();
}
#[must_use]
pub fn at_file_scope(&self) -> bool {
self.ordinary.at_file_scope()
}
#[must_use]
pub fn depth(&self) -> u32 {
self.ordinary.depth()
}
pub fn declare(&mut self, name: Symbol, binding: Binding) -> Option<Binding> {
self.ordinary.declare(name, binding)
}
pub fn declare_at_file_scope(&mut self, name: Symbol, binding: Binding) -> bool {
self.ordinary.declare_at_file_scope(name, binding)
}
#[must_use]
pub fn lookup(&self, name: Symbol) -> Option<Binding> {
self.ordinary.get(name)
}
#[must_use]
pub fn lookup_here(&self, name: Symbol) -> Option<Binding> {
self.ordinary.get_here(name)
}
pub fn declare_tag(&mut self, name: Symbol, tag: Tag) -> Option<Tag> {
self.tags.declare(name, tag)
}
#[must_use]
pub fn tag(&self, name: Symbol) -> Option<Tag> {
self.tags.get(name)
}
#[must_use]
pub fn tag_here(&self, name: Symbol) -> Option<Tag> {
self.tags.get_here(name)
}
}
#[cfg(test)]
mod tests {
use rucc_base::Idx;
use rucc_types::{IntKind, Types};
use super::*;
const S: Symbol = Symbol::from_raw(1);
#[test]
fn a_tag_and_an_ordinary_name_do_not_disturb_each_other() {
let types = Types::new();
let int = types.int(IntKind::Int);
let mut scopes = Scopes::new();
scopes.declare(S, Binding::Typedef(int));
scopes.declare_tag(S, Tag { kind: TagKind::Struct, ty: int });
assert_eq!(scopes.lookup(S), Some(Binding::Typedef(int)));
assert_eq!(scopes.tag(S).map(|tag| tag.kind), Some(TagKind::Struct));
}
#[test]
fn an_inner_declaration_hides_an_outer_one_until_its_scope_closes() {
let outer = Binding::Decl(Idx::from_usize(0));
let inner = Binding::Decl(Idx::from_usize(1));
let mut scopes = Scopes::new();
scopes.declare(S, outer);
scopes.push();
assert_eq!(scopes.declare(S, inner), None);
assert_eq!(scopes.lookup(S), Some(inner));
scopes.pop();
assert_eq!(scopes.lookup(S), Some(outer));
}
#[test]
fn a_tag_declared_again_in_an_inner_scope_is_a_new_type() {
let types = Types::new();
let int = types.int(IntKind::Int);
let long = types.int(IntKind::Long);
let mut scopes = Scopes::new();
scopes.declare_tag(S, Tag { kind: TagKind::Struct, ty: int });
scopes.push();
assert_eq!(scopes.tag_here(S), None);
scopes.declare_tag(S, Tag { kind: TagKind::Struct, ty: long });
assert_eq!(scopes.tag(S).map(|tag| tag.ty), Some(long));
scopes.pop();
assert_eq!(scopes.tag(S).map(|tag| tag.ty), Some(int));
}
#[test]
fn a_redeclaration_in_one_scope_says_what_it_was() {
let first = Binding::Decl(Idx::from_usize(0));
let second = Binding::Decl(Idx::from_usize(1));
let mut scopes = Scopes::new();
assert_eq!(scopes.declare(S, first), None);
assert_eq!(scopes.declare(S, second), Some(first));
}
}