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