summaryrefslogtreecommitdiff
path: root/src/analyze.rs
diff options
context:
space:
mode:
authorMica White <botahamec@outlook.com>2026-07-12 19:49:14 -0400
committerMica White <botahamec@outlook.com>2026-07-12 19:49:14 -0400
commit8399983fd45bc0f11eac9def280c0833ffb93c04 (patch)
treef84173ca30a3c62db7abb92a0d869ff5d5199c51 /src/analyze.rs
initial commitHEADmain
Diffstat (limited to 'src/analyze.rs')
-rw-r--r--src/analyze.rs383
1 files changed, 383 insertions, 0 deletions
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<SymbolTableId, ScopedSymbolTable<'a>>,
+}
+
+pub struct ScopedSymbolTable<'a> {
+ pub modules: HashMap<ModuleSymbolId, ModuleSymbol<'a>>,
+ pub constants: HashMap<ConstantSymbolId, ConstantSymbol<'a>>,
+ pub heads: HashMap<HeadSymbolId, HeadSymbol<'a>>,
+ pub bodies: HashMap<HeadSymbolId, HeadSymbol<'a>>,
+}
+
+#[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<Element<'a>>,
+}
+
+pub struct ConstantSymbol<'a> {
+ pub id: ConstantSymbolId,
+ pub name: Option<&'a str>,
+ pub ty: Option<ConstantType<'a>>,
+ pub value: Option<ConstantExpression<'a>>,
+ pub definition_site: Option<Element<'a>>,
+}
+
+pub struct HeadSymbol<'a> {
+ pub id: HeadSymbolId,
+ pub contents: Option<ConstantSymbolId>,
+ pub definition_site: Option<Element<'a>>,
+}
+
+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<ConstantSymbolId>>,
+ 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<ConstantSymbolId>> {
+ 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::<Box<_>>(),
+ );
+ ConstantExpression::Fragment(children)
+}
+
+fn node_to_constant<'a>(
+ arena: &'a Bump,
+ symbol_table: &mut ScopedSymbolTable<'a>,
+ node: &'a Node<'a>,
+) -> Option<ConstantSymbolId> {
+ 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 <import> with <module "base" file="../base.kuht" />
+// TODO support builtin modules
+// TODO also support <import "foo::bar" as="baz" />
+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));
+ }
+}