turbo-editors/turbo-jspublic Fork 0
main
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.

📦 Turbo JS 91999d1 · on main · k33g · 12h ago
literals.go · 93 lines · 3.3 KBGo Blame HistoryRaw
 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' }