From 8399983fd45bc0f11eac9def280c0833ffb93c04 Mon Sep 17 00:00:00 2001 From: Mica White Date: Sun, 12 Jul 2026 19:49:14 -0400 Subject: initial commit --- src/analyze.rs | 383 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 383 insertions(+) create mode 100644 src/analyze.rs (limited to 'src/analyze.rs') diff --git a/src/analyze.rs b/src/analyze.rs new file mode 100644 index 0000000..7debb0d --- /dev/null +++ b/src/analyze.rs @@ -0,0 +1,383 @@ +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)); + } +} -- cgit v1.2.3