summaryrefslogtreecommitdiff
path: root/src/compile.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/compile.rs')
-rw-r--r--src/compile.rs140
1 files changed, 140 insertions, 0 deletions
diff --git a/src/compile.rs b/src/compile.rs
new file mode 100644
index 0000000..ccbf6f0
--- /dev/null
+++ b/src/compile.rs
@@ -0,0 +1,140 @@
+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()
+}