use std::collections::{HashMap, HashSet};
use plotnik_core::{Interner, Symbol};
use crate::analyze::type_check::{
FieldInfo, TYPE_NODE, TYPE_STRING, TYPE_VOID, TypeContext, TypeId, TypeShape,
};
use plotnik_bytecode::{
StringId, TypeData, TypeDef, TypeId as BytecodeTypeId, TypeKind, TypeMember, TypeName,
};
use super::{EmitError, StringTableBuilder};
#[derive(Debug)]
pub struct TypeTableBuilder {
mapping: HashMap<TypeId, BytecodeTypeId>,
type_defs: Vec<TypeDef>,
type_members: Vec<TypeMember>,
type_names: Vec<TypeName>,
optional_wrappers: HashMap<BytecodeTypeId, BytecodeTypeId>,
member_cache: HashMap<(StringId, BytecodeTypeId), u16>,
}
impl TypeTableBuilder {
pub fn new() -> Self {
Self {
mapping: HashMap::new(),
type_defs: Vec::new(),
type_members: Vec::new(),
type_names: Vec::new(),
optional_wrappers: HashMap::new(),
member_cache: HashMap::new(),
}
}
pub fn build(
&mut self,
type_ctx: &TypeContext,
interner: &Interner,
strings: &mut StringTableBuilder,
) -> Result<(), EmitError> {
let mut ordered_types: Vec<TypeId> = Vec::new();
let mut seen: HashSet<TypeId> = HashSet::new();
for (_def_id, type_id) in type_ctx.iter_def_types() {
collect_types_dfs(type_id, type_ctx, &mut ordered_types, &mut seen);
}
for (type_id, _) in type_ctx.iter_types() {
collect_types_dfs(type_id, type_ctx, &mut ordered_types, &mut seen);
}
let mut used_builtins = [false; 3]; let mut seen = HashSet::new();
for &type_id in &ordered_types {
collect_builtin_refs(type_id, type_ctx, &mut used_builtins, &mut seen);
}
for (_def_id, type_id) in type_ctx.iter_def_types() {
if type_id == TYPE_VOID {
used_builtins[0] = true;
} else if type_id == TYPE_NODE {
used_builtins[1] = true;
} else if type_id == TYPE_STRING {
used_builtins[2] = true;
}
}
let builtin_types = [
(TYPE_VOID, TypeKind::Void),
(TYPE_NODE, TypeKind::Node),
(TYPE_STRING, TypeKind::String),
];
for (i, &(builtin_id, kind)) in builtin_types.iter().enumerate() {
if used_builtins[i] {
let bc_id = BytecodeTypeId(self.type_defs.len() as u16);
self.mapping.insert(builtin_id, bc_id);
self.type_defs.push(TypeDef::builtin(kind));
}
}
for &type_id in &ordered_types {
let bc_id = BytecodeTypeId(self.type_defs.len() as u16);
self.mapping.insert(type_id, bc_id);
self.type_defs.push(TypeDef::placeholder());
}
let builtin_count = used_builtins.iter().filter(|&&b| b).count();
for (i, &type_id) in ordered_types.iter().enumerate() {
let slot_index = builtin_count + i;
let type_shape = type_ctx
.get_type(type_id)
.expect("collected type must exist");
self.emit_type_at_slot(slot_index, type_id, type_shape, type_ctx, interner, strings)?;
}
for (def_id, type_id) in type_ctx.iter_def_types() {
let name_sym = type_ctx.def_name_sym(def_id);
let name = strings.get_or_intern(name_sym, interner)?;
let bc_type_id = self
.mapping
.get(&type_id)
.copied()
.unwrap_or(BytecodeTypeId(0));
self.type_names.push(TypeName::new(name, bc_type_id));
}
for (type_id, name_sym) in type_ctx.iter_type_names() {
if let Some(&bc_type_id) = self.mapping.get(&type_id) {
let name = strings.get_or_intern(name_sym, interner)?;
self.type_names.push(TypeName::new(name, bc_type_id));
}
}
Ok(())
}
fn emit_type_at_slot(
&mut self,
slot_index: usize,
_type_id: TypeId,
type_shape: &TypeShape,
type_ctx: &TypeContext,
interner: &Interner,
strings: &mut StringTableBuilder,
) -> Result<(), EmitError> {
match type_shape {
TypeShape::Void | TypeShape::Node | TypeShape::String => {
unreachable!("builtins should be handled separately")
}
TypeShape::Custom(sym) => {
let bc_type_id = BytecodeTypeId(slot_index as u16);
let name = strings.get_or_intern(*sym, interner)?;
self.type_names.push(TypeName::new(name, bc_type_id));
let node_bc_id = self
.mapping
.get(&TYPE_NODE)
.copied()
.unwrap_or(BytecodeTypeId(0));
self.type_defs[slot_index] = TypeDef::alias(node_bc_id);
Ok(())
}
TypeShape::Optional(inner) => {
let inner_bc = self.resolve_type(*inner, type_ctx)?;
self.type_defs[slot_index] = TypeDef::optional(inner_bc);
Ok(())
}
TypeShape::Array { element, non_empty } => {
let element_bc = self.resolve_type(*element, type_ctx)?;
self.type_defs[slot_index] = if *non_empty {
TypeDef::array_plus(element_bc)
} else {
TypeDef::array_star(element_bc)
};
Ok(())
}
TypeShape::Struct(fields) => {
let mut resolved_fields = Vec::with_capacity(fields.len());
for (field_sym, field_info) in fields {
let field_name = strings.get_or_intern(*field_sym, interner)?;
let field_type = self.resolve_field_type(field_info, type_ctx)?;
resolved_fields.push((field_name, field_type));
}
let member_start = self.type_members.len() as u16;
for (field_name, field_type) in resolved_fields {
self.type_members
.push(TypeMember::new(field_name, field_type));
}
let member_count = fields.len() as u8;
self.type_defs[slot_index] = TypeDef::struct_type(member_start, member_count);
Ok(())
}
TypeShape::Enum(variants) => {
let mut resolved_variants = Vec::with_capacity(variants.len());
for (variant_sym, variant_type_id) in variants {
let variant_name = strings.get_or_intern(*variant_sym, interner)?;
let variant_type = self.resolve_type(*variant_type_id, type_ctx)?;
resolved_variants.push((variant_name, variant_type));
}
let member_start = self.type_members.len() as u16;
for (variant_name, variant_type) in resolved_variants {
self.type_members
.push(TypeMember::new(variant_name, variant_type));
}
let member_count = variants.len() as u8;
self.type_defs[slot_index] = TypeDef::enum_type(member_start, member_count);
Ok(())
}
TypeShape::Ref(_def_id) => {
unreachable!("Ref types should not be collected for emission")
}
}
}
pub fn resolve_type(
&self,
type_id: TypeId,
type_ctx: &TypeContext,
) -> Result<BytecodeTypeId, EmitError> {
if let Some(&bc_id) = self.mapping.get(&type_id) {
return Ok(bc_id);
}
if let Some(type_shape) = type_ctx.get_type(type_id)
&& let TypeShape::Ref(def_id) = type_shape
&& let Some(def_type_id) = type_ctx.get_def_type(*def_id)
{
return self.resolve_type(def_type_id, type_ctx);
}
Ok(BytecodeTypeId(0))
}
fn resolve_field_type(
&mut self,
field_info: &FieldInfo,
type_ctx: &TypeContext,
) -> Result<BytecodeTypeId, EmitError> {
let base_type = self.resolve_type(field_info.type_id, type_ctx)?;
if field_info.optional {
self.get_or_create_optional(base_type)
} else {
Ok(base_type)
}
}
fn get_or_create_optional(
&mut self,
base_type: BytecodeTypeId,
) -> Result<BytecodeTypeId, EmitError> {
if let Some(&optional_id) = self.optional_wrappers.get(&base_type) {
return Ok(optional_id);
}
let optional_id = BytecodeTypeId(self.type_defs.len() as u16);
self.type_defs.push(TypeDef::optional(base_type));
self.optional_wrappers.insert(base_type, optional_id);
Ok(optional_id)
}
pub fn validate(&self) -> Result<(), EmitError> {
if self.type_defs.len() > 65535 {
return Err(EmitError::TooManyTypes(self.type_defs.len()));
}
if self.type_members.len() > 65535 {
return Err(EmitError::TooManyTypeMembers(self.type_members.len()));
}
Ok(())
}
pub fn get(&self, type_id: TypeId) -> Option<BytecodeTypeId> {
self.mapping.get(&type_id).copied()
}
pub fn get_member_base(&self, type_id: TypeId) -> Option<u16> {
let bc_type_id = self.mapping.get(&type_id)?;
let type_def = self.type_defs.get(bc_type_id.0 as usize)?;
match type_def.classify() {
TypeData::Composite { member_start, .. } => Some(member_start),
_ => None,
}
}
pub fn lookup_member(
&self,
field_name: Symbol,
field_type: TypeId,
strings: &StringTableBuilder,
) -> Option<u16> {
let string_id = strings.get(field_name)?;
let type_id = self.mapping.get(&field_type)?;
self.member_cache.get(&(string_id, *type_id)).copied()
}
pub fn emit(&self) -> (Vec<u8>, Vec<u8>, Vec<u8>) {
let mut defs_bytes = Vec::with_capacity(self.type_defs.len() * 4);
for def in &self.type_defs {
defs_bytes.extend_from_slice(&def.to_bytes());
}
let mut members_bytes = Vec::with_capacity(self.type_members.len() * 4);
for member in &self.type_members {
members_bytes.extend_from_slice(&member.to_bytes());
}
let mut names_bytes = Vec::with_capacity(self.type_names.len() * 4);
for type_name in &self.type_names {
names_bytes.extend_from_slice(&type_name.to_bytes());
}
(defs_bytes, members_bytes, names_bytes)
}
pub fn type_defs_count(&self) -> usize {
self.type_defs.len()
}
pub fn type_members_count(&self) -> usize {
self.type_members.len()
}
pub fn type_names_count(&self) -> usize {
self.type_names.len()
}
}
impl Default for TypeTableBuilder {
fn default() -> Self {
Self::new()
}
}
fn collect_types_dfs(
type_id: TypeId,
type_ctx: &TypeContext,
out: &mut Vec<TypeId>,
seen: &mut HashSet<TypeId>,
) {
if type_id.is_builtin() || seen.contains(&type_id) {
return;
}
let Some(type_shape) = type_ctx.get_type(type_id) else {
return;
};
if let TypeShape::Ref(def_id) = type_shape {
if let Some(target_id) = type_ctx.get_def_type(*def_id) {
collect_types_dfs(target_id, type_ctx, out, seen);
}
return;
}
seen.insert(type_id);
match type_shape {
TypeShape::Struct(fields) => {
for field_info in fields.values() {
collect_types_dfs(field_info.type_id, type_ctx, out, seen);
}
out.push(type_id);
}
TypeShape::Enum(variants) => {
for &variant_type_id in variants.values() {
collect_types_dfs(variant_type_id, type_ctx, out, seen);
}
out.push(type_id);
}
TypeShape::Array { element, .. } => {
collect_types_dfs(*element, type_ctx, out, seen);
out.push(type_id);
}
TypeShape::Optional(inner) => {
collect_types_dfs(*inner, type_ctx, out, seen);
out.push(type_id);
}
TypeShape::Custom(_) => {
out.push(type_id);
}
_ => {}
}
}
fn collect_builtin_refs(
type_id: TypeId,
type_ctx: &TypeContext,
used: &mut [bool; 3],
seen: &mut HashSet<TypeId>,
) {
if !seen.insert(type_id) {
return;
}
let Some(type_shape) = type_ctx.get_type(type_id) else {
return;
};
match type_shape {
TypeShape::Void => used[0] = true,
TypeShape::Node => used[1] = true,
TypeShape::String => used[2] = true,
TypeShape::Custom(_) => used[1] = true, TypeShape::Struct(fields) => {
for field_info in fields.values() {
collect_builtin_refs(field_info.type_id, type_ctx, used, seen);
}
}
TypeShape::Enum(variants) => {
for &variant_type_id in variants.values() {
collect_builtin_refs(variant_type_id, type_ctx, used, seen);
}
}
TypeShape::Array { element, .. } => {
collect_builtin_refs(*element, type_ctx, used, seen);
}
TypeShape::Optional(inner) => {
collect_builtin_refs(*inner, type_ctx, used, seen);
}
TypeShape::Ref(def_id) => {
if let Some(target_id) = type_ctx.get_def_type(*def_id) {
collect_builtin_refs(target_id, type_ctx, used, seen);
}
}
}
}