| 📦 Turbo JS 91999d1 k33g 12h ago | 1 | package jslang |
| 2 | |
| 3 | // The literals of JavaScript that need more than a helper call: template |
| 4 | // literals, which may cross lines, and numbers, which come in more shapes than |
| 5 | // a run of digits. |
| 6 | |
| 7 | import "rickub.com/turbo-editors/turbo-core/syntax" |
| 8 | |
| 9 | // --- template literals ------------------------------------------------------- |
| 10 | |
| 11 | // openTemplate colours a template literal that starts here, and reports |
| 12 | // whether it also ended on this line. |
| 13 | // |
| 14 | // The whole literal is a string, interpolations included. Colouring the code |
| 15 | // inside ${…} would mean the scanner reaching back into itself with a nesting |
| 16 | // depth to carry, for a construct that is usually one short expression; the |
| 17 | // cost accepted is that a backtick inside an interpolation ends the literal |
| 18 | // early. The reference says so. |
| 19 | func openTemplate(s *syntax.LineScanner) bool { |
| 20 | start := s.Pos() |
| 21 | s.Advance(1) // past the opening backtick, so it is not found as the closing one |
| 22 | return takeTemplateFrom(s, start) |
| 23 | } |
| 24 | |
| 25 | // finishTemplate colours the continuation of a template opened on an earlier |
| 26 | // line, and reports whether it ended on this one. |
| 27 | func finishTemplate(s *syntax.LineScanner) bool { return takeTemplateFrom(s, s.Pos()) } |
| 28 | |
| 29 | // takeTemplateFrom colours from start to the end of the template, or to the |
| 30 | // end of the line when it does not close here. |
| 31 | // |
| 32 | // The start is passed in rather than read back from the scanner afterwards: |
| 33 | // runToBacktick has already moved the position, and Emit drops empty spans, so |
| 34 | // anything that tried to colour "what is left" would colour nothing at all. |
| 35 | func takeTemplateFrom(s *syntax.LineScanner, start int) bool { |
| 36 | closed := runToBacktick(s) |
| 37 | if !closed { |
| 38 | s.Advance(s.Len() - s.Pos()) |
| 39 | } |
| 40 | s.Emit(start, s.Pos(), syntax.ClassString) |
| 41 | return closed |
| 42 | } |
| 43 | |
| 44 | // runToBacktick advances to just past the next unescaped backtick, and reports |
| 45 | // whether it found one. |
| 46 | func runToBacktick(s *syntax.LineScanner) bool { |
| 47 | for !s.AtEnd() { |
| 48 | switch s.Peek(0) { |
| 49 | case '\\': |
| 50 | s.Advance(2) |
| 51 | case '`': |
| 52 | s.Advance(1) |
| 53 | return true |
| 54 | default: |
| 55 | s.Advance(1) |
| 56 | } |
| 57 | } |
| 58 | return false |
| 59 | } |
| 60 | |
| 61 | // --- numbers ----------------------------------------------------------------- |
| 62 | |
| 63 | // takeNumber colours a numeric literal in every shape the language has: |
| 64 | // 1_000, 0xFF, 0o17, 0b1010, 1.5e-3, .5, and the trailing n of a BigInt. |
| 65 | // |
| 66 | // The n is taken as part of the number rather than as an identifier beside it, |
| 67 | // because 10n is one literal. A dot is part of the number only when a digit |
| 68 | // follows it, so 1.toString() — invalid, but typed — stops at the 1, and the |
| 69 | // sign after an exponent's e is part of the number only outside a hexadecimal |
| 70 | // literal, where 0xE + 1 is a sum and not an exponent. |
| 71 | func takeNumber(s *syntax.LineScanner) { |
| 72 | start := s.Pos() |
| 73 | hexadecimal := s.Peek(0) == '0' && (s.Peek(1) == 'x' || s.Peek(1) == 'X') |
| 74 | s.Advance(1) |
| 75 | |
| 76 | for !s.AtEnd() { |
| 77 | r := s.Peek(0) |
| 78 | switch { |
| 79 | case syntax.IsWordRune(r) || (r == '.' && syntax.IsDigit(s.Peek(1))): |
| 80 | s.Advance(1) |
| 81 | case (r == '+' || r == '-') && !hexadecimal && isExponent(s.Peek(-1)): |
| 82 | s.Advance(1) |
| 83 | default: |
| 84 | s.Emit(start, s.Pos(), syntax.ClassNumber) |
| 85 | return |
| 86 | } |
| 87 | } |
| 88 | s.Emit(start, s.Pos(), syntax.ClassNumber) |
| 89 | } |
| 90 | |
| 91 | // isExponent reports whether a rune is the e of an exponent, which is what |
| 92 | // makes the sign after it part of the number rather than an operator. |
| 93 | func isExponent(r rune) bool { return r == 'e' || r == 'E' } |