1use wasm_bindgen::prelude::*;
2use regex::Regex;
3use lazy_static::lazy_static;
4
5lazy_static! {
6 static ref DOCUMENT_START: Regex = Regex::new(r"π").unwrap();
7 static ref TEXT_PATTERN: Regex = Regex::new(r"π€([^π€]*)π€").unwrap();
8 static ref IMAGE_PATTERN: Regex = Regex::new(r"πΌοΈ\[(.*?)\]\((.*?)\)").unwrap();
9}
10
11#[wasm_bindgen]
12pub fn compile_to_html(input: &str) -> String {
13 println!("Raw input: {:?}", input);
14 let tokens = lex(input);
15 println!("Tokens: {:?}", tokens);
16 let ast = parse(tokens);
17 println!("AST: {:?}", ast);
18 match analyze(&ast) {
19 Ok(_) => match compile(&ast) {
20 Ok(html) => {
21 println!("Generated HTML: {}", html);
22 html
23 }
24 Err(_) => "Compile error".to_string(),
25 },
26 Err(_) => "Semantic analysis error".to_string(),
27 }
28}
29
30pub fn lex(input: &str) -> Vec<Token> {
31 println!("Raw input to lex: {:?}", input);
32 let input = input.trim();
33 let mut tokens = Vec::new();
34
35 if DOCUMENT_START.is_match(input) {
36 tokens.push(Token::DocumentStart);
37 }
38
39 for cap in TEXT_PATTERN.captures_iter(input) {
40 if let Some(text) = cap.get(1) {
41 tokens.push(Token::Text(text.as_str().to_string()));
42 }
43 }
44
45 for cap in IMAGE_PATTERN.captures_iter(input) {
46 if let (Some(alt), Some(url)) = (cap.get(1), cap.get(2)) {
47 tokens.push(Token::Image {
48 url: url.as_str().to_string(),
49 alt: alt.as_str().to_string(),
50 });
51 }
52 }
53
54 if tokens.is_empty() {
55 tokens.push(Token::Unknown);
56 }
57
58 println!("Tokens: {:?}", tokens);
59 tokens
60}
61
62pub fn parse(tokens: Vec<Token>) -> ASTNode {
63 let mut nodes = Vec::new();
64
65 for token in tokens {
66 match token {
67 Token::DocumentStart => nodes.push(ASTNode::DocumentStart),
68 Token::Text(text) => nodes.push(ASTNode::Paragraph(text)),
69 Token::Image { url, alt } => nodes.push(ASTNode::Image { url, alt }),
70 Token::Unknown => nodes.push(ASTNode::Unknown),
71 }
72 }
73
74 ASTNode::Document(nodes)
75}
76
77pub fn analyze(ast: &ASTNode) -> Result<(), SemanticError> {
78 match ast {
79 ASTNode::Document(nodes) => {
80 if nodes.is_empty() || !matches!(nodes[0], ASTNode::DocumentStart) {
81 return Err(SemanticError::MissingDocumentStart);
82 }
83 }
84 _ => {}
85 }
86
87 Ok(())
88}
89
90pub fn compile(ast: &ASTNode) -> Result<String, CompileError> {
91 match ast {
92 ASTNode::Document(nodes) => {
93 let mut html = String::from("<!DOCTYPE html>\n<html>\n<body>\n");
94 for node in nodes {
95 html.push_str(&compile_node(node)?);
96 }
97 html.push_str("</body>\n</html>");
98 Ok(html)
99 }
100 _ => Err(CompileError::GeneralError),
101 }
102}
103
104fn compile_node(node: &ASTNode) -> Result<String, CompileError> {
105 match node {
106 ASTNode::DocumentStart => Ok(String::new()),
107 ASTNode::Paragraph(text) => Ok(format!("<p>{}</p>\n", text)),
108 ASTNode::Image { url, alt } => Ok(format!("<img src=\"{}\" alt=\"{}\" />\n", url, alt)),
109 _ => Err(CompileError::GeneralError),
110 }
111}
112
113#[derive(Debug, PartialEq)]
114pub enum Token {
115 DocumentStart,
116 Text(String),
117 Image { url: String, alt: String },
118 Unknown,
119}
120
121#[derive(Debug)]
122pub enum ASTNode {
123 DocumentStart,
124 Paragraph(String),
125 Image { url: String, alt: String },
126 Document(Vec<ASTNode>),
127 Unknown,
128}
129
130#[derive(Debug)]
131pub enum SemanticError {
132 MissingDocumentStart,
133}
134
135#[derive(Debug)]
136pub enum CompileError {
137 GeneralError,
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn test_compile_to_html() {
146 let input = "ππ€Hello Worldπ€πΌοΈ[γ΅γ³γγ«η»ε](https://example.com/image.jpg)";
147 let output = compile_to_html(input);
148 assert_eq!(
149 output,
150 r#"<!DOCTYPE html>
151<html>
152<body>
153<p>Hello World</p>
154<img src="https://example.com/image.jpg" alt="γ΅γ³γγ«η»ε" />
155</body>
156</html>"#
157 );
158 }
159}