use std::{ collections::HashMap, sync::atomic::{AtomicUsize, Ordering::Relaxed}, }; use bumpalo::Bump; use crate::{ parse::{Attribute, Document, Element, Node}, tokenize::{AttributeTokenType, TokenType}, }; static NEXT_ID: AtomicUsize = AtomicUsize::new(0); pub struct SymbolTableSet<'a> { symbol_tables: HashMap>, } pub struct ScopedSymbolTable<'a> { pub modules: HashMap>, pub constants: HashMap>, pub heads: HashMap>, pub bodies: HashMap>, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct SymbolTableId(pub usize); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ModuleSymbolId(pub usize); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ConstantSymbolId(pub usize); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct HeadSymbolId(pub usize); pub struct ModuleSymbol<'a> { pub id: ModuleSymbolId, pub name: Option<&'a str>, pub symbol_table: Option<&'a ScopedSymbolTable<'a>>, pub definition_site: Option>, } pub struct ConstantSymbol<'a> { pub id: ConstantSymbolId, pub name: Option<&'a str>, pub ty: Option>, pub value: Option>, pub definition_site: Option>, } pub struct HeadSymbol<'a> { pub id: HeadSymbolId, pub contents: Option, pub definition_site: Option>, } pub enum ConstantType<'a> { String, Block { direct_children_tags: Option<&'a [&'a str]>, transitive_children_tags: Option<&'a [&'a str]>, }, } pub enum ConstantValue<'a> { String(&'a str), } pub enum ConstantExpression<'a> { Name(&'a str), Value(ConstantValue<'a>), Fragment(&'a [ConstantSymbolId]), Block { tag_name_lowercase: &'a str, attributes: HashMap<&'a str, Option>, child: ConstantSymbolId, }, } fn next_id() -> usize { NEXT_ID.fetch_add(1, Relaxed) } fn element_attributes<'a>( symbol_table: &mut ScopedSymbolTable<'a>, element: &'a Element<'a>, ) -> HashMap<&'a str, Option> { let mut attributes = HashMap::new(); for attribute in element.attributes { let Some(name) = attribute.name_lowercase() else { continue; }; let value = attribute.value_str(); let id = value.map(|value| { let id = ConstantSymbolId(next_id()); symbol_table.constants.insert( id, ConstantSymbol { id, name: None, ty: Some(ConstantType::String), value: Some(ConstantExpression::Value(ConstantValue::String(value))), definition_site: None, }, ); id }); attributes.insert(name, id); } attributes } fn fragment<'a>( arena: &'a Bump, symbol_table: &mut ScopedSymbolTable<'a>, children: &'a [Node<'a>], ) -> ConstantExpression<'a> { let children = arena.alloc_slice_copy( &children .iter() .filter_map(|node| node_to_constant(arena, symbol_table, node)) .collect::>(), ); ConstantExpression::Fragment(children) } fn node_to_constant<'a>( arena: &'a Bump, symbol_table: &mut ScopedSymbolTable<'a>, node: &'a Node<'a>, ) -> Option { match node { Node::CharacterData(token) => { let id = ConstantSymbolId(next_id()); let TokenType::Data(value) = token.ty else { return None; }; symbol_table.constants.insert( id, ConstantSymbol { id, name: None, ty: Some(ConstantType::String), value: Some(ConstantExpression::Value(ConstantValue::String(value))), definition_site: None, }, ); Some(id) } Node::Element(element) if element.tag_name_lowercase() == Some("kuht-var") => { let id = ConstantSymbolId(next_id()); let name = element .attribute("name") .and_then(|attribute| attribute.value_str())?; let fragment_id = ConstantSymbolId(next_id()); let fragment = fragment(arena, symbol_table, element.children); symbol_table.constants.insert( fragment_id, ConstantSymbol { id, name: None, // TODO fragment type ty: None, value: Some(fragment), definition_site: None, }, ); symbol_table.constants.insert( id, ConstantSymbol { id, name: None, // TODO determine this type ty: None, value: Some(ConstantExpression::Name(name)), // TODO we need some way to get where these come from definition_site: None, }, ); Some(id) } Node::Element(element) => { let id = ConstantSymbolId(next_id()); let tag_name_lowercase = element.tag_name_lowercase()?; let attributes = element_attributes(symbol_table, element); let fragment_id = ConstantSymbolId(next_id()); let fragment = fragment(arena, symbol_table, element.children); symbol_table.constants.insert( fragment_id, ConstantSymbol { id, name: None, // TODO fragment type ty: None, value: Some(fragment), definition_site: None, }, ); symbol_table.constants.insert( id, ConstantSymbol { id, name: None, // TODO type here ty: None, value: Some(ConstantExpression::Block { tag_name_lowercase, attributes, child: fragment_id, }), definition_site: None, }, ); Some(id) } Node::StrayEndTag(_) | Node::Comment(_) => None, } } // TODO replace with // TODO support builtin modules // TODO also support fn add_import<'a>(symbol_table: &mut ScopedSymbolTable<'a>, element: &Element<'a>) { let id = ModuleSymbolId(next_id()); let name = if let Some(Attribute::PositionalArgument { value }) = element.attributes.first() && let AttributeTokenType::String { value, .. } = value.ty { Some(value) } else { None }; let definition_site = Some(*element); symbol_table.modules.insert( id, ModuleSymbol { id, name, symbol_table: None, definition_site, }, ); } fn add_constant<'a>(symbol_table: &mut ScopedSymbolTable<'a>, element: &'a Element<'a>) { let id = ConstantSymbolId(next_id()); let name = element.attribute("name").and_then(|attr| attr.value_str()); let value = element.attribute("value").and_then(|attr| attr.value_str()); symbol_table.constants.insert( id, ConstantSymbol { id, name, ty: Some(ConstantType::String), value: value.map(|value| ConstantExpression::Value(ConstantValue::String(value))), definition_site: Some(*element), }, ); } fn add_block<'a>( arena: &'a Bump, symbol_table: &mut ScopedSymbolTable<'a>, element: &'a Element<'a>, ) { let id = ConstantSymbolId(next_id()); let name = element.attribute("name").and_then(|attr| attr.value_str()); let fragment = fragment(arena, symbol_table, element.children); symbol_table.constants.insert( id, ConstantSymbol { id, name, // fill this in ty: None, value: Some(fragment), definition_site: Some(*element), }, ); } fn add_head<'a>( arena: &'a Bump, symbol_table: &mut ScopedSymbolTable<'a>, element: &'a Element<'a>, ) { let id = HeadSymbolId(next_id()); let fragment_id = ConstantSymbolId(next_id()); let fragment = fragment(arena, symbol_table, element.children); symbol_table.constants.insert( fragment_id, ConstantSymbol { id: fragment_id, name: None, ty: None, value: Some(fragment), definition_site: None, }, ); symbol_table.heads.insert( id, HeadSymbol { id, contents: Some(fragment_id), definition_site: Some(*element), }, ); } fn add_body<'a>( arena: &'a Bump, symbol_table: &mut ScopedSymbolTable<'a>, element: &'a Element<'a>, ) { let id = HeadSymbolId(next_id()); let fragment_id = ConstantSymbolId(next_id()); let fragment = fragment(arena, symbol_table, element.children); symbol_table.constants.insert( fragment_id, ConstantSymbol { id: fragment_id, name: None, ty: None, value: Some(fragment), definition_site: None, }, ); symbol_table.bodies.insert( id, HeadSymbol { id, contents: Some(fragment_id), definition_site: Some(*element), }, ); } pub fn symbol_table<'a>(arena: &'a Bump, document: Document<'a>) -> ScopedSymbolTable<'a> { let mut symbol_table = ScopedSymbolTable { modules: HashMap::new(), constants: HashMap::new(), heads: HashMap::new(), bodies: HashMap::new(), }; for node in document.children { match node { Node::Element(element) if element.tag_name_lowercase() == Some("import") => { add_import(&mut symbol_table, element) } Node::Element(element) if element.tag_name_lowercase() == Some("const") => { add_constant(&mut symbol_table, element) } Node::Element(element) if element.tag_name_lowercase() == Some("block") => { add_block(arena, &mut symbol_table, element) } Node::Element(element) if element.tag_name_lowercase() == Some("head") => { add_head(arena, &mut symbol_table, element) } Node::Element(element) if element.tag_name_lowercase() == Some("body") => { add_body(arena, &mut symbol_table, element) } _ => {} } } symbol_table } impl<'a> SymbolTableSet<'a> { pub fn new() -> Self { Self { symbol_tables: HashMap::new(), } } pub fn add_document(&mut self, arena: &'a Bump, document: Document<'a>) { self.symbol_tables .insert(SymbolTableId(next_id()), symbol_table(arena, document)); } }