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<ConstantSymbolId>,
) {
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("</");
buffer.push_str(tag_name_lowercase);
buffer.push('>');
}
}
}
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<str> {
let mut buffer = String::from("<!DOCTYPE html><html lang=\"en\"><head>");
compile_head(&mut buffer, symbol_tables);
buffer.push_str("</head><body>");
compile_body(&mut buffer, symbol_tables);
buffer.push_str("</body></html>");
buffer.into_boxed_str()
}
|