1use solar_ast::{
4 token::{BinOpToken, CommentKind, Delimiter, Token, TokenKind, TokenLitKind},
5 Base,
6};
7use solar_interface::{
8 diagnostics::DiagCtxt, source_map::SourceFile, BytePos, Session, Span, Symbol,
9};
10
11mod cursor;
12use cursor::token::{RawLiteralKind, RawToken, RawTokenKind};
13pub use cursor::*;
14
15pub mod unescape;
16
17mod unicode_chars;
18
19mod utf8;
20
21pub struct Lexer<'sess, 'src> {
27 pub(crate) sess: &'sess Session,
29
30 start_pos: BytePos,
32
33 pos: BytePos,
35
36 src: &'src str,
38
39 cursor: Cursor<'src>,
41
42 token: Token,
44
45 nbsp_is_whitespace: bool,
49}
50
51impl<'sess, 'src> Lexer<'sess, 'src> {
52 pub fn new(sess: &'sess Session, src: &'src str) -> Self {
54 Self::with_start_pos(sess, src, BytePos(0))
55 }
56
57 pub fn from_source_file(sess: &'sess Session, file: &'src SourceFile) -> Self {
61 Self::with_start_pos(sess, &file.src, file.start_pos)
62 }
63
64 pub fn with_start_pos(sess: &'sess Session, src: &'src str, start_pos: BytePos) -> Self {
66 let mut lexer = Self {
67 sess,
68 start_pos,
69 pos: start_pos,
70 src,
71 cursor: Cursor::new(src),
72 token: Token::DUMMY,
73 nbsp_is_whitespace: false,
74 };
75 (lexer.token, _) = lexer.bump();
76 lexer
77 }
78
79 #[inline]
81 pub fn dcx(&self) -> &'sess DiagCtxt {
82 &self.sess.dcx
83 }
84
85 #[instrument(name = "lex", level = "debug", skip_all)]
91 pub fn into_tokens(mut self) -> Vec<Token> {
92 let mut tokens = Vec::with_capacity(self.src.len() / 4);
94 loop {
95 let token = self.next_token();
96 if token.is_eof() {
97 break;
98 }
99 if token.is_comment() {
100 continue;
101 }
102 tokens.push(token);
103 }
104 trace!(
105 src.len = self.src.len(),
106 tokens.len = tokens.len(),
107 tokens.capacity = tokens.capacity(),
108 ratio = %format_args!("{:.2}", self.src.len() as f64 / tokens.len() as f64),
109 "lexed"
110 );
111 tokens
112 }
113
114 pub fn next_token(&mut self) -> Token {
116 let mut next_token;
117 loop {
118 let preceded_by_whitespace;
119 (next_token, preceded_by_whitespace) = self.bump();
120 if preceded_by_whitespace {
121 break;
122 } else if let Some(glued) = self.token.glue(next_token) {
123 self.token = glued;
124 } else {
125 break;
126 }
127 }
128 std::mem::replace(&mut self.token, next_token)
129 }
130
131 fn bump(&mut self) -> (Token, bool) {
132 let mut preceded_by_whitespace = false;
133 let mut swallow_next_invalid = 0;
134 loop {
135 let RawToken { kind: raw_kind, len } = self.cursor.advance_token();
136 let start = self.pos;
137 self.pos += len;
138
139 let kind = match raw_kind {
142 RawTokenKind::LineComment { is_doc } => {
143 preceded_by_whitespace = true;
144
145 let content_start = start + BytePos(if is_doc { 3 } else { 2 });
147 let content = self.str_from(content_start);
148 self.cook_doc_comment(content_start, content, is_doc, CommentKind::Line)
149 }
150 RawTokenKind::BlockComment { is_doc, terminated } => {
151 preceded_by_whitespace = true;
152
153 if !terminated {
154 let msg = if is_doc {
155 "unterminated block doc-comment"
156 } else {
157 "unterminated block comment"
158 };
159 self.dcx().err(msg).span(self.new_span(start, self.pos)).emit();
160 }
161
162 let content_start = start + BytePos(if is_doc { 3 } else { 2 });
164 let content_end = self.pos - (terminated as u32) * 2;
165 let content = self.str_from_to(content_start, content_end);
166 self.cook_doc_comment(content_start, content, is_doc, CommentKind::Block)
167 }
168 RawTokenKind::Whitespace => {
169 preceded_by_whitespace = true;
170 continue;
171 }
172 RawTokenKind::Ident => {
173 let sym = self.symbol_from(start);
174 TokenKind::Ident(sym)
175 }
176 RawTokenKind::Literal { kind } => {
177 let (kind, symbol) = self.cook_literal(start, self.pos, kind);
178 TokenKind::Literal(kind, symbol)
179 }
180
181 RawTokenKind::Semi => TokenKind::Semi,
182 RawTokenKind::Comma => TokenKind::Comma,
183 RawTokenKind::Dot => TokenKind::Dot,
184 RawTokenKind::OpenParen => TokenKind::OpenDelim(Delimiter::Parenthesis),
185 RawTokenKind::CloseParen => TokenKind::CloseDelim(Delimiter::Parenthesis),
186 RawTokenKind::OpenBrace => TokenKind::OpenDelim(Delimiter::Brace),
187 RawTokenKind::CloseBrace => TokenKind::CloseDelim(Delimiter::Brace),
188 RawTokenKind::OpenBracket => TokenKind::OpenDelim(Delimiter::Bracket),
189 RawTokenKind::CloseBracket => TokenKind::CloseDelim(Delimiter::Bracket),
190 RawTokenKind::Tilde => TokenKind::Tilde,
191 RawTokenKind::Question => TokenKind::Question,
192 RawTokenKind::Colon => TokenKind::Colon,
193 RawTokenKind::Eq => TokenKind::Eq,
194 RawTokenKind::Bang => TokenKind::Not,
195 RawTokenKind::Lt => TokenKind::Lt,
196 RawTokenKind::Gt => TokenKind::Gt,
197 RawTokenKind::Minus => TokenKind::BinOp(BinOpToken::Minus),
198 RawTokenKind::And => TokenKind::BinOp(BinOpToken::And),
199 RawTokenKind::Or => TokenKind::BinOp(BinOpToken::Or),
200 RawTokenKind::Plus => TokenKind::BinOp(BinOpToken::Plus),
201 RawTokenKind::Star => TokenKind::BinOp(BinOpToken::Star),
202 RawTokenKind::Slash => TokenKind::BinOp(BinOpToken::Slash),
203 RawTokenKind::Caret => TokenKind::BinOp(BinOpToken::Caret),
204 RawTokenKind::Percent => TokenKind::BinOp(BinOpToken::Percent),
205
206 RawTokenKind::Unknown => {
207 if swallow_next_invalid > 0 {
209 swallow_next_invalid -= 1;
210 continue;
211 }
212 let mut it = self.str_from_to_end(start).chars();
213 let c = it.next().unwrap();
214 if c == '\u{00a0}' {
215 if self.nbsp_is_whitespace {
219 preceded_by_whitespace = true;
220 continue;
221 }
222 self.nbsp_is_whitespace = true;
223 }
224
225 let repeats = it.take_while(|c1| *c1 == c).count();
226 swallow_next_invalid = repeats;
227
228 let (token, sugg) =
229 unicode_chars::check_for_substitution(self, start, c, repeats + 1);
230
231 let span = self
232 .new_span(start, self.pos + BytePos::from_usize(repeats * c.len_utf8()));
233 let msg = format!("unknown start of token: {}", escaped_char(c));
234 let mut err = self.dcx().err(msg).span(span);
235 if let Some(sugg) = sugg {
236 match sugg {
237 unicode_chars::TokenSubstitution::DirectedQuotes {
238 span,
239 suggestion: _,
240 ascii_str,
241 ascii_name,
242 } => {
243 let msg = format!("Unicode characters '“' (Left Double Quotation Mark) and '”' (Right Double Quotation Mark) look like '{ascii_str}' ({ascii_name}), but are not");
244 err = err.span_help(span, msg);
245 }
246 unicode_chars::TokenSubstitution::Other {
247 span,
248 suggestion: _,
249 ch,
250 u_name,
251 ascii_str,
252 ascii_name,
253 } => {
254 let msg = format!("Unicode character '{ch}' ({u_name}) looks like '{ascii_str}' ({ascii_name}), but it is not");
255 err = err.span_help(span, msg);
256 }
257 }
258 }
259 if c == '\0' {
260 let help = "source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used";
261 err = err.help(help);
262 }
263 if repeats > 0 {
264 let note = match repeats {
265 1 => "once more".to_string(),
266 _ => format!("{repeats} more times"),
267 };
268 err = err.note(format!("character repeats {note}"));
269 }
270 err.emit();
271
272 if let Some(token) = token {
273 token
274 } else {
275 preceded_by_whitespace = true;
276 continue;
277 }
278 }
279
280 RawTokenKind::Eof => TokenKind::Eof,
281 };
282 let span = self.new_span(start, self.pos);
283 return (Token::new(kind, span), preceded_by_whitespace);
284 }
285 }
286
287 fn cook_doc_comment(
288 &self,
289 _content_start: BytePos,
290 content: &str,
291 is_doc: bool,
292 comment_kind: CommentKind,
293 ) -> TokenKind {
294 TokenKind::Comment(is_doc, comment_kind, Symbol::intern(content))
295 }
296
297 fn cook_literal(
298 &self,
299 start: BytePos,
300 end: BytePos,
301 kind: RawLiteralKind,
302 ) -> (TokenLitKind, Symbol) {
303 match kind {
304 RawLiteralKind::Str { terminated, unicode } => {
305 if !terminated {
306 let span = self.new_span(start, end);
307 let guar = self.dcx().err("unterminated string").span(span).emit();
308 (TokenLitKind::Err(guar), self.symbol_from_to(start, end))
309 } else {
310 let kind = if unicode { TokenLitKind::UnicodeStr } else { TokenLitKind::Str };
311 self.cook_quoted(kind, start, end)
312 }
313 }
314 RawLiteralKind::HexStr { terminated } => {
315 if !terminated {
316 let span = self.new_span(start, end);
317 let guar = self.dcx().err("unterminated hex string").span(span).emit();
318 (TokenLitKind::Err(guar), self.symbol_from_to(start, end))
319 } else {
320 self.cook_quoted(TokenLitKind::HexStr, start, end)
321 }
322 }
323 RawLiteralKind::Int { base, empty_int } => {
324 if empty_int {
325 let span = self.new_span(start, end);
326 self.dcx().err("no valid digits found for number").span(span).emit();
327 (TokenLitKind::Integer, self.symbol_from_to(start, end))
328 } else {
329 if matches!(base, Base::Binary | Base::Octal) {
330 let start = start + 2;
331 let msg = format!("integers in base {base} are not supported");
346 self.dcx().err(msg).span(self.new_span(start, end)).emit();
347 }
348 (TokenLitKind::Integer, self.symbol_from_to(start, end))
349 }
350 }
351 RawLiteralKind::Rational { base, empty_exponent } => {
352 if empty_exponent {
353 let span = self.new_span(start, self.pos);
354 self.dcx().err("expected at least one digit in exponent").span(span).emit();
355 }
356
357 let unsupported_base =
358 matches!(base, Base::Binary | Base::Octal | Base::Hexadecimal);
359 if unsupported_base {
360 let msg = format!("{base} rational numbers are not supported");
361 self.dcx().err(msg).span(self.new_span(start, end)).emit();
362 }
363
364 (TokenLitKind::Rational, self.symbol_from_to(start, end))
365 }
366 }
367 }
368
369 fn cook_quoted(
370 &self,
371 kind: TokenLitKind,
372 start: BytePos,
373 end: BytePos,
374 ) -> (TokenLitKind, Symbol) {
375 let (mode, prefix_len) = match kind {
376 TokenLitKind::Str => (unescape::Mode::Str, 0),
377 TokenLitKind::UnicodeStr => (unescape::Mode::UnicodeStr, 7),
378 TokenLitKind::HexStr => (unescape::Mode::HexStr, 3),
379 _ => unreachable!(),
380 };
381
382 let content_start = start + 1 + BytePos(prefix_len);
384 let content_end = end - 1;
385 let lit_content = self.str_from_to(content_start, content_end);
386
387 let mut has_err = false;
388 unescape::unescape_literal(lit_content, mode, |range, result| {
389 if let Err(err) = result {
391 has_err = true;
392 let (start, end) = (range.start as u32, range.end as u32);
393 let lo = content_start + BytePos(start);
394 let hi = lo + BytePos(end - start);
395 let span = self.new_span(lo, hi);
396 unescape::emit_unescape_error(self.dcx(), lit_content, span, range, err);
397 }
398 });
399
400 let symbol =
403 if has_err { self.symbol_from_to(start, end) } else { Symbol::intern(lit_content) };
404 (kind, symbol)
405 }
406
407 #[inline]
408 fn new_span(&self, lo: BytePos, hi: BytePos) -> Span {
409 Span::new(lo, hi)
410 }
411
412 #[inline]
413 fn src_index(&self, pos: BytePos) -> usize {
414 (pos - self.start_pos).to_usize()
415 }
416
417 fn symbol_from(&self, start: BytePos) -> Symbol {
420 self.symbol_from_to(start, self.pos)
421 }
422
423 fn str_from(&self, start: BytePos) -> &'src str {
426 self.str_from_to(start, self.pos)
427 }
428
429 fn symbol_from_to(&self, start: BytePos, end: BytePos) -> Symbol {
431 Symbol::intern(self.str_from_to(start, end))
432 }
433
434 #[track_caller]
436 fn str_from_to(&self, start: BytePos, end: BytePos) -> &'src str {
437 &self.src[self.src_index(start)..self.src_index(end)]
438 }
439
440 fn str_from_to_end(&self, start: BytePos) -> &'src str {
442 &self.src[self.src_index(start)..]
443 }
444}
445
446impl Iterator for Lexer<'_, '_> {
447 type Item = Token;
448
449 #[inline]
450 fn next(&mut self) -> Option<Token> {
451 let token = self.next_token();
452 if token.is_eof() {
453 None
454 } else {
455 Some(token)
456 }
457 }
458}
459
460impl std::iter::FusedIterator for Lexer<'_, '_> {}
461
462fn escaped_char(c: char) -> String {
464 match c {
465 '\u{20}'..='\u{7e}' => {
466 c.to_string()
468 }
469 _ => c.escape_default().to_string(),
470 }
471}
472
473#[cfg(test)]
474mod tests {
475 use super::*;
476 use std::ops::Range;
477 use BinOpToken::*;
478 use TokenKind::*;
479
480 type Expected<'a> = &'a [(Range<usize>, TokenKind)];
481
482 fn check(src: &str, should_fail: bool, expected: Expected<'_>) {
483 let sess = Session::builder().with_silent_emitter(None).build();
484 let tokens: Vec<_> = Lexer::new(&sess, src)
485 .filter(|t| !t.is_comment())
486 .map(|t| (t.span.lo().to_usize()..t.span.hi().to_usize(), t.kind))
487 .collect();
488 assert_eq!(sess.dcx.has_errors().is_err(), should_fail, "{src:?}");
489 assert_eq!(tokens, expected, "{src:?}");
490 }
491
492 fn checks(tests: &[(&str, Expected<'_>)]) {
493 for &(src, expected) in tests {
494 check(src, false, expected);
495 }
496 }
497
498 fn checks_full(tests: &[(&str, bool, Expected<'_>)]) {
499 for &(src, should_fail, expected) in tests {
500 check(src, should_fail, expected);
501 }
502 }
503
504 fn lit(kind: TokenLitKind, symbol: &str) -> TokenKind {
505 Literal(kind, sym(symbol))
506 }
507
508 fn id(symbol: &str) -> TokenKind {
509 Ident(sym(symbol))
510 }
511
512 fn sym(s: &str) -> Symbol {
513 Symbol::intern(s)
514 }
515
516 #[test]
517 fn empty() {
518 checks(&[
519 ("", &[]),
520 (" ", &[]),
521 (" \n", &[]),
522 ("\n", &[]),
523 ("\n\t", &[]),
524 ("\n \t", &[]),
525 ("\n \t ", &[]),
526 (" \n \t \t", &[]),
527 ]);
528 }
529
530 #[test]
531 fn literals() {
532 use TokenLitKind::*;
533 solar_interface::SessionGlobals::new().set(|| {
534 checks(&[
535 ("\"\"", &[(0..2, lit(Str, ""))]),
536 ("\"\"\"\"", &[(0..2, lit(Str, "")), (2..4, lit(Str, ""))]),
537 ("\"\" \"\"", &[(0..2, lit(Str, "")), (3..5, lit(Str, ""))]),
538 ("\"\\\"\"", &[(0..4, lit(Str, "\\\""))]),
539 ("unicode\"\"", &[(0..9, lit(UnicodeStr, ""))]),
540 ("unicode \"\"", &[(0..7, id("unicode")), (8..10, lit(Str, ""))]),
541 ("hex\"\"", &[(0..5, lit(HexStr, ""))]),
542 ("hex \"\"", &[(0..3, id("hex")), (4..6, lit(Str, ""))]),
543 ("0", &[(0..1, lit(Integer, "0"))]),
545 ("0a", &[(0..1, lit(Integer, "0")), (1..2, id("a"))]),
546 ("0.e1", &[(0..1, lit(Integer, "0")), (1..2, Dot), (2..4, id("e1"))]),
547 (
548 "0.e-1",
549 &[
550 (0..1, lit(Integer, "0")),
551 (1..2, Dot),
552 (2..3, id("e")),
553 (3..4, BinOp(Minus)),
554 (4..5, lit(Integer, "1")),
555 ],
556 ),
557 ("0.0", &[(0..3, lit(Rational, "0.0"))]),
558 ("0.", &[(0..2, lit(Rational, "0."))]),
559 (".0", &[(0..2, lit(Rational, ".0"))]),
560 ("0.0e1", &[(0..5, lit(Rational, "0.0e1"))]),
561 ("0.0e-1", &[(0..6, lit(Rational, "0.0e-1"))]),
562 ("0e1", &[(0..3, lit(Rational, "0e1"))]),
563 ("0e1.", &[(0..3, lit(Rational, "0e1")), (3..4, Dot)]),
564 ]);
565
566 checks_full(&[
567 ("0b0", true, &[(0..3, lit(Integer, "0b0"))]),
568 ("0B0", false, &[(0..1, lit(Integer, "0")), (1..3, id("B0"))]),
569 ("0o0", true, &[(0..3, lit(Integer, "0o0"))]),
570 ("0O0", false, &[(0..1, lit(Integer, "0")), (1..3, id("O0"))]),
571 ("0xa", false, &[(0..3, lit(Integer, "0xa"))]),
572 ("0Xa", false, &[(0..1, lit(Integer, "0")), (1..3, id("Xa"))]),
573 ]);
574 });
575 }
576
577 #[test]
578 fn idents() {
579 solar_interface::SessionGlobals::new().set(|| {
580 checks(&[
581 ("$", &[(0..1, id("$"))]),
582 ("a$", &[(0..2, id("a$"))]),
583 ("a_$123_", &[(0..7, id("a_$123_"))]),
584 (" b", &[(3..4, id("b"))]),
585 (" c\t ", &[(1..2, id("c"))]),
586 (" \td ", &[(2..3, id("d"))]),
587 (" \t\nef ", &[(3..5, id("ef"))]),
588 (" \t\n\tghi ", &[(4..7, id("ghi"))]),
589 ]);
590 });
591 }
592
593 #[test]
594 fn doc_comments() {
595 use CommentKind::*;
596
597 fn doc(kind: CommentKind, symbol: &str) -> TokenKind {
598 Comment(true, kind, sym(symbol))
599 }
600
601 solar_interface::SessionGlobals::new().set(|| {
602 checks(&[
603 ("// line comment", &[]),
604 ("// / line comment", &[]),
605 ("// ! line comment", &[]),
606 ("// /* line comment", &[]), ("/// line doc-comment", &[(0..20, doc(Line, " line doc-comment"))]),
608 ("//// invalid doc-comment", &[]),
609 ("///// invalid doc-comment", &[]),
610 ("/**/", &[]),
612 ("/***/", &[]),
613 ("/****/", &[]),
614 ("/*/*/", &[]),
615 ("/* /*/", &[]),
616 ("/*/**/", &[]),
617 ("/* /**/", &[]),
618 ("/* normal block comment */", &[]),
619 ("/* /* normal block comment */", &[]),
620 ("/** block doc-comment */", &[(0..24, doc(Block, " block doc-comment "))]),
621 ("/** /* block doc-comment */", &[(0..27, doc(Block, " /* block doc-comment "))]),
622 ("/** block doc-comment /*/", &[(0..25, doc(Block, " block doc-comment /"))]),
623 ]);
624 });
625 }
626
627 #[test]
628 fn operators() {
629 use Delimiter::*;
630 checks(&[
632 (")", &[(0..1, CloseDelim(Parenthesis))]),
633 ("(", &[(0..1, OpenDelim(Parenthesis))]),
634 ("[", &[(0..1, OpenDelim(Bracket))]),
635 ("]", &[(0..1, CloseDelim(Bracket))]),
636 ("{", &[(0..1, OpenDelim(Brace))]),
637 ("}", &[(0..1, CloseDelim(Brace))]),
638 (":", &[(0..1, Colon)]),
639 (";", &[(0..1, Semi)]),
640 (".", &[(0..1, Dot)]),
641 ("?", &[(0..1, Question)]),
642 ("=>", &[(0..2, FatArrow)]),
643 ("->", &[(0..2, Arrow)]),
644 ("=", &[(0..1, Eq)]),
645 ("|=", &[(0..2, BinOpEq(Or))]),
646 ("^=", &[(0..2, BinOpEq(Caret))]),
647 ("&=", &[(0..2, BinOpEq(And))]),
648 ("<<=", &[(0..3, BinOpEq(Shl))]),
649 (">>=", &[(0..3, BinOpEq(Shr))]),
650 (">>>=", &[(0..4, BinOpEq(Sar))]),
651 ("+=", &[(0..2, BinOpEq(Plus))]),
652 ("-=", &[(0..2, BinOpEq(Minus))]),
653 ("*=", &[(0..2, BinOpEq(Star))]),
654 ("/=", &[(0..2, BinOpEq(Slash))]),
655 ("%=", &[(0..2, BinOpEq(Percent))]),
656 (",", &[(0..1, Comma)]),
657 ("||", &[(0..2, OrOr)]),
658 ("&&", &[(0..2, AndAnd)]),
659 ("|", &[(0..1, BinOp(Or))]),
660 ("^", &[(0..1, BinOp(Caret))]),
661 ("&", &[(0..1, BinOp(And))]),
662 ("<<", &[(0..2, BinOp(Shl))]),
663 (">>", &[(0..2, BinOp(Shr))]),
664 (">>>", &[(0..3, BinOp(Sar))]),
665 ("+", &[(0..1, BinOp(Plus))]),
666 ("-", &[(0..1, BinOp(Minus))]),
667 ("*", &[(0..1, BinOp(Star))]),
668 ("/", &[(0..1, BinOp(Slash))]),
669 ("%", &[(0..1, BinOp(Percent))]),
670 ("**", &[(0..2, StarStar)]),
671 ("==", &[(0..2, EqEq)]),
672 ("!=", &[(0..2, Ne)]),
673 ("<", &[(0..1, Lt)]),
674 (">", &[(0..1, Gt)]),
675 ("<=", &[(0..2, Le)]),
676 (">=", &[(0..2, Ge)]),
677 ("!", &[(0..1, Not)]),
678 ("~", &[(0..1, Tilde)]),
679 ("++", &[(0..2, PlusPlus)]),
680 ("--", &[(0..2, MinusMinus)]),
681 (":=", &[(0..2, Walrus)]),
682 ]);
683 }
684
685 #[test]
686 fn glueing() {
687 checks(&[
688 ("=", &[(0..1, Eq)]),
689 ("==", &[(0..2, EqEq)]),
690 ("= =", &[(0..1, Eq), (2..3, Eq)]),
691 ("===", &[(0..2, EqEq), (2..3, Eq)]),
692 ("== =", &[(0..2, EqEq), (3..4, Eq)]),
693 ("= ==", &[(0..1, Eq), (2..4, EqEq)]),
694 ("====", &[(0..2, EqEq), (2..4, EqEq)]),
695 ("== ==", &[(0..2, EqEq), (3..5, EqEq)]),
696 ("= ===", &[(0..1, Eq), (2..4, EqEq), (4..5, Eq)]),
697 ("=====", &[(0..2, EqEq), (2..4, EqEq), (4..5, Eq)]),
698 (" <", &[(1..2, Lt)]),
700 (" <=", &[(1..3, Le)]),
701 (" < =", &[(1..2, Lt), (3..4, Eq)]),
702 (" <<", &[(1..3, BinOp(Shl))]),
703 (" <<=", &[(1..4, BinOpEq(Shl))]),
704 (" >", &[(1..2, Gt)]),
706 (" >=", &[(1..3, Ge)]),
707 (" > =", &[(1..2, Gt), (3..4, Eq)]),
708 (" >>", &[(1..3, BinOp(Shr))]),
709 (" >>>", &[(1..4, BinOp(Sar))]),
710 (" >>>=", &[(1..5, BinOpEq(Sar))]),
711 ("+", &[(0..1, BinOp(Plus))]),
713 ("++", &[(0..2, PlusPlus)]),
714 ("+++", &[(0..2, PlusPlus), (2..3, BinOp(Plus))]),
715 ("+ =", &[(0..1, BinOp(Plus)), (2..3, Eq)]),
716 ("+ +=", &[(0..1, BinOp(Plus)), (2..4, BinOpEq(Plus))]),
717 ("+++=", &[(0..2, PlusPlus), (2..4, BinOpEq(Plus))]),
718 ("+ +", &[(0..1, BinOp(Plus)), (2..3, BinOp(Plus))]),
719 ("-", &[(0..1, BinOp(Minus))]),
721 ("--", &[(0..2, MinusMinus)]),
722 ("---", &[(0..2, MinusMinus), (2..3, BinOp(Minus))]),
723 ("- =", &[(0..1, BinOp(Minus)), (2..3, Eq)]),
724 ("- -=", &[(0..1, BinOp(Minus)), (2..4, BinOpEq(Minus))]),
725 ("---=", &[(0..2, MinusMinus), (2..4, BinOpEq(Minus))]),
726 ("- -", &[(0..1, BinOp(Minus)), (2..3, BinOp(Minus))]),
727 ]);
728 }
729}