turbo-editors/turbo-jspublic Fork 0
v1.0.0
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-js.git
git clone ssh://git@rickub.com/turbo-editors/turbo-js.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

literals.go · 93 lines · 3.3 KBGo Blame HistoryRaw
📦 Turbo JS 91999d1 k33g 12h ago1package 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
7import "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.
19func 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.
27func 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.
35func 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.
46func 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.
71func 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.
93func isExponent(r rune) bool { return r == 'e' || r == 'E' }