use html_escape::{encode_double_quoted_attribute, encode_single_quoted_attribute}; use crate::analyze::{ConstantExpression, ConstantSymbolId, ConstantValue, ScopedSymbolTable}; fn symbol_table_from_path<'a>( symbol_table: &'a ScopedSymbolTable<'a>, path: &'a str, ) -> Option<&'a ScopedSymbolTable<'a>> { let mut symbol_table = symbol_table; for component in path.split("::") { if let Some(module) = symbol_table .modules .values() .find(|module| module.name == Some(component)) .and_then(|module| module.symbol_table) { symbol_table = module; } else { return None; } } Some(symbol_table) } fn compile_attribute_value<'a>( buffer: &mut String, symbol_table: &'a ScopedSymbolTable<'a>, name: &'a str, value: Option, ) { buffer.push(' '); buffer.push_str(name); let Some(value) = value else { return; }; buffer.push('='); let mut new_buffer = String::new(); compile_constant(&mut new_buffer, symbol_table, value); let use_double_quotes = !new_buffer.contains('"') || new_buffer.contains('\''); let delimiter = if !use_double_quotes { '\'' } else { '"' }; let value = if use_double_quotes { encode_double_quoted_attribute(&new_buffer) } else { encode_single_quoted_attribute(&new_buffer) }; buffer.push(delimiter); buffer.push_str(&value); buffer.push(delimiter); } fn compile_constant<'a>( buffer: &mut String, symbol_table: &'a ScopedSymbolTable<'a>, id: ConstantSymbolId, ) { let Some(constant) = symbol_table.constants.get(&id) else { return; }; let Some(expression) = &constant.value else { return; }; match expression { ConstantExpression::Value(value) => match value { ConstantValue::String(value) => buffer.push_str(value), }, ConstantExpression::Name(name) => { let mut name = *name; let mut symbol_table = symbol_table; if let Some((path, item)) = name.rsplit_once("::") && let Some(module) = symbol_table_from_path(symbol_table, path) { name = item; symbol_table = module; } if let Some(id) = symbol_table .constants .values() .find_map(|value| (value.name == Some(name)).then_some(value.id)) { compile_constant(buffer, symbol_table, id); } } ConstantExpression::Fragment(constant_symbol_ids) => { for constant in *constant_symbol_ids { compile_constant(buffer, symbol_table, *constant); } } ConstantExpression::Block { tag_name_lowercase, attributes, child, } => { buffer.push('<'); buffer.push_str(tag_name_lowercase); for (name, value) in attributes { compile_attribute_value(buffer, symbol_table, name, *value); } buffer.push('>'); compile_constant(buffer, symbol_table, *child); buffer.push_str("'); } } } fn compile_head<'a>(buffer: &mut String, symbol_tables: &[ScopedSymbolTable<'a>]) { for symbol_table in symbol_tables { for head in symbol_table.heads.values().filter_map(|head| head.contents) { compile_constant(buffer, symbol_table, head); } } } fn compile_body<'a>(buffer: &mut String, symbol_tables: &[ScopedSymbolTable<'a>]) { for symbol_table in symbol_tables { for body in symbol_table .bodies .values() .filter_map(|body| body.contents) { compile_constant(buffer, symbol_table, body); } } } pub fn compile<'a>(symbol_tables: &[ScopedSymbolTable<'a>]) -> Box { let mut buffer = String::from(""); compile_head(&mut buffer, symbol_tables); buffer.push_str(""); compile_body(&mut buffer, symbol_tables); buffer.push_str(""); buffer.into_boxed_str() }