hermes_parser/lexer/
jsx.rs1use hermes_unicode::UNICODE_MAX_VALUE;
8
9use crate::html_entities;
10use crate::token::Token;
11use crate::token_kinds::TokenKind;
12use crate::utf8::{append_unicode_to_storage, is_utf8_start};
13
14use super::{is_ascii_digit, JSLexer};
15
16impl<'a> JSLexer<'a> {
17 pub(crate) fn consume_html_entity_optional(&mut self) -> Option<u32> {
23 debug_assert!(self.cursor.peek() == b'&');
24 let start = self.cursor.offset();
25
26 if self.cursor.peek_at(1) == b'#' {
27 if self.cursor.peek_at(2) == b'x' {
28 self.cursor.advance(3);
30 let number_start = self.cursor.offset();
31
32 let mut code_point: u32 = 0;
33 let mut ch = self.cursor.peek();
34
35 loop {
38 if ch == b';' && self.cursor.offset() != number_start {
39 self.cursor.advance(1);
40 return Some(code_point);
41 } else if is_ascii_digit(ch) {
42 ch -= b'0';
43 } else {
44 ch |= 32;
45 if (b'a'..=b'f').contains(&ch) {
46 ch -= b'a' - 10;
47 } else {
48 break;
49 }
50 }
51
52 code_point = (code_point << 4) + ch as u32;
54 if code_point > UNICODE_MAX_VALUE {
55 break;
56 }
57
58 self.cursor.advance(1);
59 ch = self.cursor.peek();
60 }
61 } else {
62 self.cursor.advance(2);
64 let number_start = self.cursor.offset();
65
66 let mut code_point: u32 = 0;
67 let mut ch = self.cursor.peek();
68
69 loop {
72 if ch == b';' && self.cursor.offset() != number_start {
73 self.cursor.advance(1);
74 return Some(code_point);
75 } else if is_ascii_digit(ch) {
76 code_point = code_point * 10 + (ch - b'0') as u32;
78 if code_point > UNICODE_MAX_VALUE {
79 break;
80 }
81 } else {
82 break;
83 }
84
85 self.cursor.advance(1);
86 ch = self.cursor.peek();
87 }
88 }
89 } else {
90 self.cursor.advance(1);
92
93 for i in 0..9 {
99 let ch = self.cursor.peek();
100 if ch == b';' {
101 let name = self.cursor.slice(self.cursor.offset() - i, self.cursor.offset());
102 match html_entities::lookup(name) {
103 None => break,
104 Some(value) => {
105 self.cursor.advance(1);
106 return Some(value);
107 }
108 }
109 } else if ((ch | 32) >= b'a' && (ch | 32) <= b'z') || is_ascii_digit(ch) {
110 self.cursor.advance(1);
111 } else {
112 break;
113 }
114 }
115 }
116
117 self.cursor.seek(start);
118 None
119 }
120
121 pub fn advance_in_jsx_child(&mut self) -> &Token {
127 self.token.set_start(self.cur_loc());
128 #[allow(clippy::never_loop)]
132 loop {
133 debug_assert!(
134 (self.cursor.offset() as usize) <= self.cursor.raw().len(),
135 "lexing past end of input"
136 );
137 match self.cursor.peek() {
138 b'{' => {
139 self.punc_l1_1(TokenKind::l_brace);
140 }
141 b'<' => {
142 self.punc_l1_1(TokenKind::less);
143 }
144
145 0 if self.cursor.at_end() => {
146 self.token.set_eof();
147 }
148
149 _ => {
151 let start = self.cur_loc();
152 self.token.set_start(start);
153
154 self.tmp_storage.clear();
156 self.raw_storage.clear();
157 loop {
158 let c = self.cursor.peek();
159
160 if is_utf8_start(c) {
161 let codepoint = self.decode_utf8_advance();
162 append_unicode_to_storage(&mut self.tmp_storage, codepoint);
163 append_unicode_to_storage(&mut self.raw_storage, codepoint);
164 continue;
165 } else if c == b'&' {
166 let html_start = self.cursor.offset();
167 if let Some(code_point) = self.consume_html_entity_optional() {
168 append_unicode_to_storage(&mut self.tmp_storage, code_point);
169 let consumed = self.cursor.slice(html_start, self.cursor.offset());
170 self.raw_storage.extend_from_slice(consumed);
171 continue;
172 }
173 } else if (c == 0 && self.cursor.at_end()) || c == b'{' || c == b'<' {
174 let value = self.get_string_literal(self.tmp_storage.as_slice());
175 let raw = self.get_string_literal(self.raw_storage.as_slice());
176 self.token.set_jsx_text(value, raw);
177 break;
178 }
179 self.tmp_storage.push(c);
180 self.raw_storage.push(c);
181 self.cursor.advance(1);
182 }
183 }
184 }
185
186 break;
188 }
189 self.finish_token();
190 &self.token
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use hermes_atom_table::AtomTable;
197 use hermes_support::manager::SourceErrorManager;
198
199 use super::super::{GrammarContext, JSLexer};
200 use crate::token_kinds::TokenKind;
201
202 fn entity(src: &str) -> Option<u32> {
205 let mut sm = SourceErrorManager::new();
206 let id = sm.add_buffer("t", src);
207 let tab = AtomTable::new();
208 let mut lex = JSLexer::new(id, &mut sm, &tab, GrammarContext::AllowJSXIdentifier);
209 lex.consume_html_entity_optional()
210 }
211
212 #[test]
213 fn html_entities() {
214 assert_eq!(entity("&"), Some(0x26)); assert_eq!(entity("A"), Some(65)); assert_eq!(entity("A"), Some(0x41)); assert_eq!(entity("&nope;"), None); assert_eq!(entity("&"), None); }
220
221 fn advance_jsx(src: &str) -> Vec<TokenKind> {
223 let mut sm = SourceErrorManager::new();
224 let id = sm.add_buffer("t", src);
225 let tab = AtomTable::new();
226 let mut lex = JSLexer::new(id, &mut sm, &tab, GrammarContext::AllowJSXIdentifier);
227 let mut kinds = Vec::new();
228 loop {
229 let k = lex.advance_in_jsx_child().kind();
230 kinds.push(k);
231 if k == TokenKind::eof {
232 break;
233 }
234 }
235 kinds
236 }
237
238 fn jsx_text_value(src: &str) -> (Vec<u8>, Vec<u8>) {
240 let mut sm = SourceErrorManager::new();
241 let id = sm.add_buffer("t", src);
242 let tab = AtomTable::new();
243 let mut lex = JSLexer::new(id, &mut sm, &tab, GrammarContext::AllowJSXIdentifier);
244 let tok = lex.advance_in_jsx_child();
245 assert_eq!(tok.kind(), TokenKind::jsx_text);
246 let value = tab.bytes(tok.get_jsx_text_value()).to_vec();
247 let raw = tab.bytes(tok.get_jsx_text_raw()).to_vec();
248 (value, raw)
249 }
250
251 #[test]
252 fn jsx_child() {
253 use TokenKind::*;
254 assert_eq!(advance_jsx("hello{x"), vec![jsx_text, l_brace, jsx_text, eof]);
257 assert_eq!(advance_jsx("a<b"), vec![jsx_text, less, jsx_text, eof]);
258 assert_eq!(
260 jsx_text_value("a&b{"),
261 (b"a&b".to_vec(), b"a&b".to_vec())
262 );
263 }
264}