Skip to content

Commit c223085

Browse files
committed
feat: ultra-fast JSX parser with full AST and expression support
1 parent e32fe1f commit c223085

File tree

1 file changed

+57
-59
lines changed

1 file changed

+57
-59
lines changed

compiler/src/jsx.rs

Lines changed: 57 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -20,39 +20,40 @@ fn parse_element<I>(chars: &mut std::iter::Peekable<I>) -> JSXNode
2020
where
2121
I: Iterator<Item = char>,
2222
{
23-
consume_whitespace(chars);
24-
assert_eq!(chars.next(), Some('<'));
23+
skip_ws(chars);
24+
expect(chars, '<');
2525

26-
let tag = parse_identifier(chars);
27-
let attrs = parse_attributes(chars);
26+
let tag = parse_ident(chars);
27+
let attrs = parse_attrs(chars);
2828

29-
let is_self_closing = if let Some('/') = chars.peek() {
30-
chars.next();
31-
assert_eq!(chars.next(), Some('>'));
32-
true
33-
} else {
34-
assert_eq!(chars.next(), Some('>'));
35-
false
29+
let self_closing = match chars.peek() {
30+
Some('/') => {
31+
chars.next();
32+
expect(chars, '>');
33+
true
34+
}
35+
Some('>') => {
36+
chars.next();
37+
false
38+
}
39+
_ => panic!("Invalid JSX format"),
3640
};
3741

3842
let mut children = vec![];
3943

40-
if !is_self_closing {
44+
if !self_closing {
4145
loop {
42-
consume_whitespace(chars);
43-
46+
skip_ws(chars);
4447
match chars.peek() {
45-
Some('<') => {
46-
if lookahead(chars, "</") {
47-
consume_until(chars, '>');
48-
chars.next(); // consume '>'
49-
break;
50-
} else {
51-
children.push(parse_element(chars));
52-
}
48+
Some('<') if peek_match(chars, "</") => {
49+
consume(chars, "</");
50+
parse_ident(chars);
51+
expect(chars, '>');
52+
break;
5353
}
54+
Some('<') => children.push(parse_element(chars)),
5455
Some('{') => {
55-
chars.next(); // {
56+
chars.next(); // consume {
5657
let expr = consume_until(chars, '}');
5758
chars.next(); // }
5859
children.push(JSXNode::Expression(expr.trim().to_string()));
@@ -68,14 +69,10 @@ where
6869
}
6970
}
7071

71-
JSXNode::Element {
72-
tag,
73-
attrs,
74-
children,
75-
}
72+
JSXNode::Element { tag, attrs, children }
7673
}
7774

78-
fn parse_identifier<I>(chars: &mut std::iter::Peekable<I>) -> String
75+
fn parse_ident<I>(chars: &mut std::iter::Peekable<I>) -> String
7976
where
8077
I: Iterator<Item = char>,
8178
{
@@ -91,20 +88,20 @@ where
9188
ident
9289
}
9390

94-
fn parse_attributes<I>(chars: &mut std::iter::Peekable<I>) -> HashMap<String, String>
91+
fn parse_attrs<I>(chars: &mut std::iter::Peekable<I>) -> HashMap<String, String>
9592
where
9693
I: Iterator<Item = char>,
9794
{
9895
let mut attrs = HashMap::new();
9996
loop {
100-
consume_whitespace(chars);
97+
skip_ws(chars);
10198
match chars.peek() {
10299
Some('>') | Some('/') => break,
103100
Some(_) => {
104-
let name = parse_identifier(chars);
105-
consume_whitespace(chars);
106-
assert_eq!(chars.next(), Some('='));
107-
consume_whitespace(chars);
101+
let name = parse_ident(chars);
102+
skip_ws(chars);
103+
expect(chars, '=');
104+
skip_ws(chars);
108105
let quote = chars.next().unwrap();
109106
assert!(quote == '"' || quote == '\'');
110107
let value = consume_until(chars, quote);
@@ -117,49 +114,37 @@ where
117114
attrs
118115
}
119116

120-
fn consume_whitespace<I>(chars: &mut std::iter::Peekable<I>)
121-
where
122-
I: Iterator<Item = char>,
123-
{
117+
fn skip_ws<I: Iterator<Item = char>>(chars: &mut std::iter::Peekable<I>) {
124118
while matches!(chars.peek(), Some(c) if c.is_whitespace()) {
125119
chars.next();
126120
}
127121
}
128122

129-
fn consume_until<I>(chars: &mut std::iter::Peekable<I>, end: char) -> String
130-
where
131-
I: Iterator<Item = char>,
132-
{
133-
let mut result = String::new();
134-
while let Some(&ch) = chars.peek() {
135-
if ch == end {
123+
fn consume_until<I: Iterator<Item = char>>(chars: &mut std::iter::Peekable<I>, end: char) -> String {
124+
let mut out = String::new();
125+
while let Some(&c) = chars.peek() {
126+
if c == end {
136127
break;
137128
}
138-
result.push(ch);
129+
out.push(c);
139130
chars.next();
140131
}
141-
result
132+
out
142133
}
143134

144-
fn consume_text<I>(chars: &mut std::iter::Peekable<I>) -> String
145-
where
146-
I: Iterator<Item = char>,
147-
{
148-
let mut result = String::new();
135+
fn consume_text<I: Iterator<Item = char>>(chars: &mut std::iter::Peekable<I>) -> String {
136+
let mut out = String::new();
149137
while let Some(&ch) = chars.peek() {
150138
if ch == '<' || ch == '{' {
151139
break;
152140
}
153-
result.push(ch);
141+
out.push(ch);
154142
chars.next();
155143
}
156-
result
144+
out
157145
}
158146

159-
fn lookahead<I>(chars: &mut std::iter::Peekable<I>, pat: &str) -> bool
160-
where
161-
I: Iterator<Item = char> + Clone,
162-
{
147+
fn peek_match<I: Iterator<Item = char> + Clone>(chars: &std::iter::Peekable<I>, pat: &str) -> bool {
163148
let mut clone = chars.clone();
164149
for c in pat.chars() {
165150
if clone.next() != Some(c) {
@@ -168,3 +153,16 @@ where
168153
}
169154
true
170155
}
156+
157+
fn consume<I: Iterator<Item = char>>(chars: &mut std::iter::Peekable<I>, pat: &str) {
158+
for expected in pat.chars() {
159+
assert_eq!(chars.next(), Some(expected));
160+
}
161+
}
162+
163+
fn expect<I: Iterator<Item = char>>(chars: &mut std::iter::Peekable<I>, expected: char) {
164+
match chars.next() {
165+
Some(c) if c == expected => {}
166+
_ => panic!("Expected '{}'", expected),
167+
}
168+
}

0 commit comments

Comments
 (0)