1#[derive(Debug)]
2enum Attr {
3 KeyValue(String, String),
4 Spread(String),
5}
6
7#[derive(Debug)]
8enum Node {
9 Element {
10 tag: String,
11 attrs: Vec<Attr>,
12 children: Vec<Node>,
13 },
14 Text(String),
15 JSExpression(String),
16}
17
18struct Parser<'a> {
19 input: &'a [u8],
20 pos: usize,
21}
22
23impl<'a> Parser<'a> {
24 fn new(input: &'a str) -> Self {
25 Parser {
26 input: input.as_bytes(),
27 pos: 0,
28 }
29 }
30
31 fn parse(&mut self) -> Vec<Node> {
32 let mut nodes = Vec::new();
33 while self.pos < self.input.len() {
34 self.skip_whitespace();
35 if self.starts_with("</") {
36 break;
37 } else if self.starts_with("<") {
38 nodes.push(self.parse_element());
39 } else {
40 nodes.push(self.parse_text());
41 }
42 }
43 nodes
44 }
45
46 fn parse_element(&mut self) -> Node {
47 self.consume("<");
48 let tag = self.consume_identifier();
49 let attrs = self.parse_attributes();
50
51 if self.starts_with("/>") {
52 self.consume("/>");
53 Node::Element {
54 tag,
55 attrs,
56 children: Vec::new(),
57 }
58 } else {
59 self.consume(">");
60 let children = self.parse();
61 self.consume("</");
62 let end_tag = self.consume_identifier();
63 assert_eq!(tag, end_tag, "Mismatched closing tag");
64 self.consume(">");
65 Node::Element {
66 tag,
67 attrs,
68 children,
69 }
70 }
71 }
72
73 fn parse_braced_content(&mut self) -> Node {
74 self.consume("{");
75 let mut output = String::new();
76
77 while self.pos < self.input.len() {
78 if self.starts_with("}") {
79 self.consume("}");
80 break;
81 } else if self.starts_with("<") {
82 let jsx_node = self.parse_element();
84 let compiled_jsx = compile_node(&jsx_node, None);
85 output.push_str(&compiled_jsx);
86 } else {
87 output.push(self.advance());
88 }
89 }
90
91 Node::JSExpression(output.trim().to_string())
92 }
93
94 fn parse_attributes(&mut self) -> Vec<Attr> {
95 let mut attrs = Vec::new();
96 loop {
97 self.skip_whitespace();
98 if self.peek() == '>' || self.starts_with("/>") {
99 break;
100 }
101
102 if self.starts_with("{...") {
103 self.consume("{...");
104 let mut expr = String::new();
105 while self.peek() != '}' {
106 expr.push(self.advance());
107 }
108 self.consume("}");
109 attrs.push(Attr::Spread(expr.trim().to_string()));
110 continue;
111 }
112
113 let name = self.consume_identifier();
114 self.skip_whitespace();
115 self.consume("=");
116 self.skip_whitespace();
117 let value = if self.starts_with("\"") {
118 self.consume_quoted_string()
119 } else if self.starts_with("{") {
120 self.parse_braced_attribute()
121 } else {
122 panic!("Expected attribute value");
123 };
124 attrs.push(Attr::KeyValue(name, value));
125 }
126 attrs
127 }
128
129 fn parse_braced_attribute(&mut self) -> String {
130 self.consume("{");
131 let mut output = String::new();
132
133 while self.pos < self.input.len() {
134 if self.starts_with("}") {
135 self.consume("}");
136 break;
137 } else if self.starts_with("<") {
138 let jsx_node = self.parse_element();
140 output.push_str(&compile_node(&jsx_node, None));
141 } else {
142 output.push(self.advance());
143 }
144 }
145
146 output.trim().to_string()
147 }
148
149 fn parse_text(&mut self) -> Node {
150 let mut text = String::new();
151 while self.pos < self.input.len() && !self.starts_with("<") && !self.starts_with("{") {
152 text.push(self.advance());
153 }
154 if self.starts_with("{") {
155 return self.parse_braced_content();
156 }
157 Node::Text(text.trim().to_string())
158 }
159
160 fn starts_with(&self, s: &str) -> bool {
163 self.input[self.pos..].starts_with(s.as_bytes())
164 }
165
166 fn peek(&self) -> char {
167 self.input[self.pos] as char
168 }
169
170 fn advance(&mut self) -> char {
171 let c = self.input[self.pos] as char;
172 self.pos += 1;
173 c
174 }
175
176 fn consume(&mut self, s: &str) {
177 assert!(self.starts_with(s), "Expected '{}'", s);
178 self.pos += s.len();
179 }
180
181 fn consume_identifier(&mut self) -> String {
182 let mut ident = String::new();
183 while self.pos < self.input.len() {
184 let c = self.peek();
185 if c.is_alphanumeric() || c == '-' || c == '_' {
186 ident.push(self.advance());
187 } else {
188 break;
189 }
190 }
191 ident
192 }
193
194 fn consume_quoted_string(&mut self) -> String {
214 self.consume("\"");
215 let mut value = String::new();
216 while self.peek() != '"' {
217 value.push(self.advance());
218 }
219 self.consume("\"");
220 value
221 }
222
223 fn skip_whitespace(&mut self) {
224 while self.pos < self.input.len() && self.peek().is_whitespace() {
225 self.advance();
226 }
227 }
228}
229
230fn compile_node(node: &Node, pragma: Option<String>) -> String {
231 match node {
232 Node::Text(text) => {
233 if text.trim().is_empty() {
234 String::new()
235 } else if text.starts_with('{') && text.ends_with('}') {
236 text[1..text.len() - 1].trim().to_string()
238 } else {
239 format!(r#""{}""#, text)
240 }
241 }
242 Node::Element {
243 tag,
244 attrs,
245 children,
246 } => {
247 let mut parts = Vec::new();
248 for attr in attrs {
249 match attr {
250 Attr::KeyValue(k, v) => {
251 if v.starts_with("jsx(") || v.contains("(") || v.contains("=>") || v.contains(".") {
252 parts.push(format!(r#"{k}: {}"#, v)); } else {
254 parts.push(format!(r#"{k}: "{}""#, v)); }
256 }
257 Attr::Spread(expr) => {
258 parts.push(format!("...{}", expr));
259 }
260 }
261 }
262 let props = format!("{{{}}}", parts.join(", "));
263
264 let compiled_children: Vec<String> = children
265 .iter()
266 .map(|x| compile_node(x, pragma.clone()))
267 .filter(|c| !c.is_empty())
268 .collect();
269 let children_js = if compiled_children.is_empty() {
270 "null".to_string()
271 } else {
272 compiled_children.join(", ")
273 };
274
275 let element = if tag == &tag.to_lowercase() {
276 format!(r#""{}""#, tag)
277 } else {
278 tag.to_string()
279 };
280
281 let prefix = pragma.unwrap_or("JSX.prototype.new".to_string());
282
283 format!(r#"{prefix}({element}, {props}, {children_js})"#)
284 }
285 Node::JSExpression(code) => code.clone(),
286 }
287}
288
289#[derive(Debug, Clone)]
290enum Token {
291 Symbol(String),
292 String(String),
293 Identifier(String),
294 Comment(String),
295 Whitespace(String),
296 #[allow(unused)]
297 Other(String),
298}
299
300fn tokenize(source: &str) -> Vec<Token> {
301 let mut tokens = vec![];
302 let mut chars = source.chars().peekable();
303
304 while let Some(&c) = chars.peek() {
305 if c.is_whitespace() {
306 let mut ws = String::new();
307 while let Some(&c2) = chars.peek() {
308 if c2.is_whitespace() {
309 ws.push(c2);
310 chars.next();
311 } else {
312 break;
313 }
314 }
315 tokens.push(Token::Whitespace(ws));
316 } else if c == '"' || c == '\'' {
317 let quote = c;
318 let mut s = String::new();
319 s.push(c);
320 chars.next();
321 for ch in chars.by_ref() {
322 s.push(ch);
323 if ch == quote {
324 break;
325 }
326 }
327 tokens.push(Token::String(s));
328 } else if c == '/' && chars.clone().nth(1) == Some('/') {
329 let mut comment = String::new();
330 for ch in chars.by_ref() {
331 comment.push(ch);
332 if ch == '\n' {
333 break;
334 }
335 }
336 tokens.push(Token::Comment(comment));
337 } else if c.is_alphanumeric() || c == '_' {
338 let mut ident = String::new();
339 while let Some(&ch) = chars.peek() {
340 if ch.is_alphanumeric() || ch == '_' {
341 ident.push(ch);
342 chars.next();
343 } else {
344 break;
345 }
346 }
347 tokens.push(Token::Identifier(ident));
348 } else {
349 let mut sym = String::new();
350 sym.push(c);
351 chars.next();
352
353 if sym == "<" && chars.peek() == Some(&'/') {
355 sym.push('/');
356 chars.next();
357 }
358
359 tokens.push(Token::Symbol(sym));
360 }
361 }
362
363 tokens
364}
365
366fn compile_jsx_fragments(tokens: &[Token], pragma: Option<String>) -> String {
367 let mut output = String::new();
368 let mut i = 0;
369
370 while i < tokens.len() {
371 let is_open = if let Token::Symbol(ref sym1) = tokens[i] {
373 if sym1 == "<" {
374 if let Some(Token::Symbol(sym2)) = tokens.get(i + 1) {
375 sym2 == ">"
376 } else {
377 false
378 }
379 } else {
380 false
381 }
382 } else {
383 false
384 };
385
386 if is_open {
387 i += 2;
389
390 let mut jsx = String::new();
391 while i + 1 < tokens.len() {
392 let is_close = if let Token::Symbol(sym1) = &tokens[i] {
394 if sym1 == "</" {
395 if let Some(Token::Symbol(sym2)) = tokens.get(i + 1) {
396 sym2 == ">"
397 } else {
398 false
399 }
400 } else {
401 false
402 }
403 } else {
404 false
405 };
406
407 if is_close {
408 i += 2;
410 break; }
412
413 match &tokens[i] {
414 Token::Whitespace(s)
415 | Token::Identifier(s)
416 | Token::Symbol(s)
417 | Token::String(s)
418 | Token::Comment(s)
419 | Token::Other(s) => jsx.push_str(s),
420 }
421 i += 1;
422 }
423
424 let mut parser = Parser::new(&jsx);
425 for node in parser.parse() {
426 output.push_str(&compile_node(&node, pragma.clone()));
427 }
428
429 continue;
430 }
431
432 match &tokens[i] {
433 Token::Whitespace(s)
434 | Token::Identifier(s)
435 | Token::Symbol(s)
436 | Token::String(s)
437 | Token::Comment(s)
438 | Token::Other(s) => output.push_str(s),
439 }
440 i += 1;
441 }
442
443 output
444}
445
446pub fn compile_jsx(input: String, pragma: Option<String>) -> String {
447 compile_jsx_fragments(&tokenize(&input), pragma)
448}
449
450#[cfg(test)]
451mod tests {
452 use super::*;
453
454 #[test]
455 fn test_jsx(){
456 assert_eq!(
457 compile_jsx("<><div>{something.map((i) => <p>{i}</p>)}<Element name={<i>{u}</i>} /></div></>".into(), None),
458 "JSX.prototype.new(\"div\", {}, something.map((i) => JSX.prototype.new(\"p\", {}, i)), JSX.prototype.new(Element, {name: JSX.prototype.new(\"i\", {}, u)}, null))"
459 )
460 }
461}