1use crate::parser::SyntaxKind;
10
11#[derive(Default)]
12pub struct LexState {
13 template_string_stack: Vec<u32>,
16}
17
18pub trait LexingRule {
21 fn lex(&self, text: &str, state: &mut LexState) -> usize;
23}
24
25impl LexingRule for &str {
26 #[inline]
27 fn lex(&self, text: &str, _: &mut LexState) -> usize {
28 if text.starts_with(*self) { self.len() } else { 0 }
29 }
30}
31
32impl<F: Fn(&str, &mut LexState) -> usize> LexingRule for F {
33 #[inline]
34 fn lex(&self, text: &str, state: &mut LexState) -> usize {
35 (self)(text, state)
36 }
37}
38
39pub fn lex_whitespace(text: &str, _: &mut LexState) -> usize {
40 let mut len = 0;
41 let chars = text.chars();
42 for c in chars {
43 if !c.is_whitespace() && !['\u{0002}', '\u{0003}'].contains(&c) {
44 break;
45 }
46 len += c.len_utf8();
47 }
48 len
49}
50
51pub fn lex_comment(text: &str, _: &mut LexState) -> usize {
52 if text.starts_with("//") {
54 return text.find(&['\n', '\r'] as &[_]).unwrap_or(text.len());
55 }
56 if text.starts_with("/*") {
57 let mut nested = 0;
58 let mut offset = 2;
59 let bytes = text.as_bytes();
60 while offset < bytes.len() {
61 if let Some(star) = bytes[offset..].iter().position(|c| *c == b'*') {
62 let star = star + offset;
63 if star > offset && bytes[star - 1] == b'/' {
64 nested += 1;
65 offset = star + 1;
66 } else if star < bytes.len() - 1 && bytes[star + 1] == b'/' {
67 if nested == 0 {
68 return star + 2;
69 }
70 nested -= 1;
71 offset = star + 2;
72 } else {
73 offset = star + 1;
74 }
75 } else {
76 return 0;
78 }
79 }
80 return 0;
82 }
83
84 0
85}
86
87pub fn lex_string(text: &str, state: &mut LexState) -> usize {
88 if let Some(brace_level) = state.template_string_stack.last_mut() {
89 if text.starts_with('{') {
90 *brace_level += 1;
91 return 0;
92 } else if text.starts_with('}') {
93 if *brace_level > 0 {
94 *brace_level -= 1;
95 return 0;
96 } else {
97 state.template_string_stack.pop();
98 }
99 } else if !text.starts_with('"') {
100 return 0;
101 }
102 } else if !text.starts_with('"') {
103 return 0;
104 }
105 let text_len = text.len();
106 let mut end = 1; loop {
108 let stop = match text[end..].find(&['"', '\\'][..]) {
109 Some(stop) => end + stop,
110 None => return 0,
112 };
113 match text.as_bytes()[stop] {
114 b'"' => {
115 return stop + 1;
116 }
117 b'\\' => {
118 if text_len <= stop + 1 {
119 return 0;
121 }
122 if text.as_bytes()[stop + 1] == b'{' {
123 state.template_string_stack.push(0);
124 return stop + 2;
125 }
126 end = stop + 1 + text[stop + 1..].chars().next().map_or(0, |c| c.len_utf8())
127 }
128 _ => unreachable!(),
129 }
130 }
131}
132
133pub fn lex_number(text: &str, _: &mut LexState) -> usize {
134 let mut len = 0;
135 let mut chars = text.chars();
136 let mut had_period = false;
137 while let Some(c) = chars.next() {
138 if !c.is_ascii_digit() {
139 if !had_period && c == '.' && len > 0 {
140 had_period = true;
141 } else {
142 if len > 0 {
143 if c == '%' {
144 return len + 1;
145 }
146 if c.is_ascii_alphabetic() {
147 len += c.len_utf8();
148 for c in chars {
150 if !c.is_ascii_alphabetic() {
151 return len;
152 }
153 len += c.len_utf8();
154 }
155 }
156 }
157 break;
158 }
159 }
160 len += c.len_utf8();
161 }
162 len
163}
164
165pub fn lex_color(text: &str, _: &mut LexState) -> usize {
166 if !text.starts_with('#') {
167 return 0;
168 }
169 let mut len = 1;
170 let chars = text[1..].chars();
171 for c in chars {
172 if !c.is_ascii_alphanumeric() {
173 break;
174 }
175 len += c.len_utf8();
176 }
177 len
178}
179
180pub fn lex_identifier(text: &str, _: &mut LexState) -> usize {
181 let xid_start = icu_properties::CodePointSetData::new::<icu_properties::props::XidStart>();
188 let xid_continue =
189 icu_properties::CodePointSetData::new::<icu_properties::props::XidContinue>();
190 let mut len = 0;
191 for c in text.chars() {
192 let valid = if len == 0 {
193 c == '_' || xid_start.contains(c)
194 } else {
195 c == '-' || xid_continue.contains(c)
196 };
197 if !valid {
198 break;
199 }
200 len += c.len_utf8();
201 }
202 len
203}
204
205#[allow(clippy::needless_update)] pub fn lex(mut source: &str) -> Vec<crate::parser::Token> {
207 let mut result = Vec::new();
208 let mut offset = 0;
209 let mut state = LexState::default();
210 if source.starts_with("\u{FEFF}") {
211 result.push(crate::parser::Token {
213 kind: SyntaxKind::Whitespace,
214 text: source[..3].into(),
215 offset: 0,
216 ..Default::default()
217 });
218 source = &source[3..];
219 offset += 3;
220 }
221 while !source.is_empty() {
222 let (len, kind) = crate::parser::lex_next_token(source, &mut state).unwrap_or_else(|| {
223 (source.ceil_char_boundary(1), SyntaxKind::Error)
229 });
230 result.push(crate::parser::Token {
231 kind,
232 text: source[..len].into(),
233 offset,
234 ..Default::default()
235 });
236 offset += len;
237 source = &source[len..];
238 }
239 result
240}
241
242#[test]
243fn basic_lexer_test() {
244 fn compare(source: &str, expected: &[(SyntaxKind, &str)]) {
245 let actual = lex(source);
246 let actual =
247 actual.iter().map(|token| (token.kind, token.text.as_str())).collect::<Vec<_>>();
248 assert_eq!(actual.as_slice(), expected);
249 }
250
251 compare(
252 r#"45 /*hi/*_*/ho*/ "string""#,
253 &[
254 (SyntaxKind::NumberLiteral, "45"),
255 (SyntaxKind::Whitespace, " "),
256 (SyntaxKind::Comment, "/*hi/*_*/ho*/"),
257 (SyntaxKind::Whitespace, " "),
258 (SyntaxKind::StringLiteral, r#""string""#),
259 ],
260 );
261
262 compare(
263 r#"12px+5.2+=0.7%"#,
264 &[
265 (SyntaxKind::NumberLiteral, "12px"),
266 (SyntaxKind::Plus, "+"),
267 (SyntaxKind::NumberLiteral, "5.2"),
268 (SyntaxKind::PlusEqual, "+="),
269 (SyntaxKind::NumberLiteral, "0.7%"),
270 ],
271 );
272 compare(
273 r#"aa_a.b1,c"#,
274 &[
275 (SyntaxKind::Identifier, "aa_a"),
276 (SyntaxKind::Dot, "."),
277 (SyntaxKind::Identifier, "b1"),
278 (SyntaxKind::Comma, ","),
279 (SyntaxKind::Identifier, "c"),
280 ],
281 );
282 compare(
283 r#"/*/**/*//**/*"#,
284 &[
285 (SyntaxKind::Comment, "/*/**/*/"),
286 (SyntaxKind::Comment, "/**/"),
287 (SyntaxKind::Star, "*"),
288 ],
289 );
290 compare(
291 "a//x\nb//y\r\nc//z",
292 &[
293 (SyntaxKind::Identifier, "a"),
294 (SyntaxKind::Comment, "//x"),
295 (SyntaxKind::Whitespace, "\n"),
296 (SyntaxKind::Identifier, "b"),
297 (SyntaxKind::Comment, "//y"),
298 (SyntaxKind::Whitespace, "\r\n"),
299 (SyntaxKind::Identifier, "c"),
300 (SyntaxKind::Comment, "//z"),
301 ],
302 );
303 compare(r#""x""#, &[(SyntaxKind::StringLiteral, r#""x""#)]);
304 compare(
305 r#"a"\"\\"x"#,
306 &[
307 (SyntaxKind::Identifier, "a"),
308 (SyntaxKind::StringLiteral, r#""\"\\""#),
309 (SyntaxKind::Identifier, "x"),
310 ],
311 );
312 compare(
313 r#""a\{b{c}d"e\{f}g"h}i"j"#,
314 &[
315 (SyntaxKind::StringLiteral, r#""a\{"#),
316 (SyntaxKind::Identifier, "b"),
317 (SyntaxKind::LBrace, "{"),
318 (SyntaxKind::Identifier, "c"),
319 (SyntaxKind::RBrace, "}"),
320 (SyntaxKind::Identifier, "d"),
321 (SyntaxKind::StringLiteral, r#""e\{"#),
322 (SyntaxKind::Identifier, "f"),
323 (SyntaxKind::StringLiteral, r#"}g""#),
324 (SyntaxKind::Identifier, "h"),
325 (SyntaxKind::StringLiteral, r#"}i""#),
326 (SyntaxKind::Identifier, "j"),
327 ],
328 );
329
330 compare(r#"/**"#, &[(SyntaxKind::Div, "/"), (SyntaxKind::Star, "*"), (SyntaxKind::Star, "*")]);
332 compare(r#""\"#, &[(SyntaxKind::Error, "\""), (SyntaxKind::Error, "\\")]);
333 compare(
334 r#""\ޱ"#,
335 &[(SyntaxKind::Error, "\""), (SyntaxKind::Error, "\\"), (SyntaxKind::Identifier, "ޱ")],
336 );
337}
338
339#[test]
342fn identifier_and_number_tokens() {
343 let kinds = |source: &str| lex(source).iter().map(|t| t.kind).collect::<Vec<_>>();
344 let single = |source: &str| {
345 let k = kinds(source);
346 assert_eq!(k.len(), 1, "{source:?} lexed into {k:?}, expected a single token");
347 k[0]
348 };
349
350 for id in ["foo", "_foo", "foo-bar", "snake_case", "x1", "café", "Über", "λ", "名前"] {
355 assert_eq!(single(id), SyntaxKind::Identifier, "{id:?}");
356 }
357
358 for n in ["45", "1.5", "100%", "10px", "1.5deg"] {
362 assert_eq!(single(n), SyntaxKind::NumberLiteral, "{n:?}");
363 }
364
365 assert_eq!(single("1abc"), SyntaxKind::NumberLiteral);
370 assert_eq!(single("42"), SyntaxKind::NumberLiteral);
371
372 assert_eq!(kinds("-foo"), [SyntaxKind::Minus, SyntaxKind::Identifier]);
375
376 assert_eq!(kinds("x½"), [SyntaxKind::Identifier, SyntaxKind::Error]);
381 assert_eq!(kinds("½x"), [SyntaxKind::Error, SyntaxKind::Identifier]);
382}
383
384pub fn locate_slint_macro(rust_source: &str) -> impl Iterator<Item = core::ops::Range<usize>> + '_ {
387 let mut begin = 0;
388 std::iter::from_fn(move || {
389 let (open, close) = loop {
390 let m = rust_source[begin..].find("slint")?;
391 if let Some(x) = rust_source[begin..(begin + m)].rfind(['\\', '\n', '/', '\"'])
393 && rust_source.as_bytes()[begin + x] != b'\n'
394 {
395 begin += m + 5;
396 begin += rust_source[begin..].find(['\n']).unwrap_or(0);
397 continue;
398 }
399 begin += m + 5;
400 while rust_source[begin..].starts_with(' ') {
401 begin += 1;
402 }
403 if !rust_source[begin..].starts_with('!') {
404 continue;
405 }
406 begin += 1;
407 while rust_source[begin..].starts_with(' ') {
408 begin += 1;
409 }
410 let Some(open) = rust_source.as_bytes().get(begin) else { continue };
411 match open {
412 b'{' => break (SyntaxKind::LBrace, SyntaxKind::RBrace),
413 b'[' => break (SyntaxKind::LBracket, SyntaxKind::RBracket),
414 b'(' => break (SyntaxKind::LParent, SyntaxKind::RParent),
415 _ => continue,
416 }
417 };
418
419 begin += 1;
420
421 let mut state = LexState::default();
424 let start = begin;
425 let mut end = begin;
426 let mut level = 0;
427 while !rust_source[end..].is_empty() {
428 let len = match crate::parser::lex_next_token(&rust_source[end..], &mut state) {
429 Some((len, x)) if x == open => {
430 level += 1;
431 len
432 }
433 Some((_, x)) if x == close && level == 0 => {
434 break;
435 }
436 Some((len, x)) if x == close => {
437 level -= 1;
438 len
439 }
440 Some((len, _)) => len,
441 None => {
442 break;
444 }
445 };
446 if len == 0 {
447 break; }
449 end += len;
450 }
451 begin = end;
452 Some(start..end)
453 })
454}
455
456#[test]
457fn test_locate_rust_macro() {
458 #[track_caller]
459 fn do_test(source: &str, captures: &[&str]) {
460 let result = locate_slint_macro(source).map(|r| &source[r]).collect::<Vec<_>>();
461 assert_eq!(&result, captures);
462 }
463
464 do_test("\nslint{!{}}", &[]);
465 do_test(
466 "//slint!(123)\nslint!(456)\nslint ![789]\n/*slint!{abc}*/\nslint! {def}",
467 &["456", "789", "def"],
468 );
469 do_test("slint!(slint!(abc))slint!()", &["slint!(abc)", ""]);
470}
471
472pub fn extract_rust_macro(rust_source: String) -> Option<String> {
484 let core::ops::Range { start, end } = locate_slint_macro(&rust_source).next()?;
485 let mut bytes = rust_source.into_bytes();
486 for c in &mut bytes[..start] {
487 if *c != b'\n' {
488 *c = b' '
489 }
490 }
491
492 if start > 0 {
493 bytes[start - 1] = 2;
494 }
495 if end < bytes.len() {
496 bytes[end] = 3;
497
498 for c in &mut bytes[end + 1..] {
499 if *c != b'\n' {
500 *c = b' '
501 }
502 }
503 }
504 Some(String::from_utf8(bytes).expect("We just added spaces"))
505}
506
507#[test]
508fn test_extract_rust_macro() {
509 assert_eq!(extract_rust_macro("\nslint{!{}}".into()), None);
510 assert_eq!(
511 extract_rust_macro(
512 "abc\n€\nslint ! {x \" \\\" }🦀\" { () {}\n {} }xx =}- ;}\n xxx \n yyy {}\n".into(),
513 ),
514 Some(
515 " \n \n \u{2}x \" \\\" }🦀\" { () {}\n {} }xx =\u{3} \n \n \n".into(),
516 )
517 );
518
519 assert_eq!(
520 extract_rust_macro("xx\nabcd::slint!{abc{}efg".into()),
521 Some(" \n \u{2}abc{}efg".into())
522 );
523 assert_eq!(
524 extract_rust_macro("slint!\nnot.\nslint!{\nunterminated\nxxx".into()),
525 Some(" \n \n \u{2}\nunterminated\nxxx".into())
526 );
527 assert_eq!(extract_rust_macro("foo\n/* slint! { hello }\n".into()), None);
528 assert_eq!(extract_rust_macro("foo\n/* slint::slint! { hello }\n".into()), None);
529 assert_eq!(
530 extract_rust_macro("foo\n// slint! { hello }\nslint!{world}\na".into()),
531 Some(" \n \n \u{2}world\u{3}\n ".into())
532 );
533 assert_eq!(extract_rust_macro("foo\n\" slint! { hello }\"\n".into()), None);
534 assert_eq!(
535 extract_rust_macro(
536 "abc\n€\nslint ! (x /* \\\" )🦀*/ { () {}\n {} }xx =)- ;}\n xxx \n yyy {}\n".into(),
537 ),
538 Some(
539 " \n \n \u{2}x /* \\\" )🦀*/ { () {}\n {} }xx =\u{3} \n \n \n".into(),
540 )
541 );
542 assert_eq!(
543 extract_rust_macro("abc slint![x slint!() [{[]}] s] abc".into()),
544 Some(" \u{0002}x slint!() [{[]}] s\u{0003} ".into()),
545 );
546}