turbo-editors/turbo-rustpublic Fork 0
5adadd10bd599bcb7e6091b2a7e061728f3afd16
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-rust.git
git clone ssh://git@rickub.com/turbo-editors/turbo-rust.git

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

literals.go · 205 lines · 5.6 KBGo Blame HistoryRaw
📦 Turbo Rust 713ea5c k33g 23h ago1package rustlang
2
3// The literals of Rust: strings ordinary and raw, byte strings, characters —
4// and lifetimes, which begin with the same rune a character does and are the
5// one place this scanner has to make a decision rather than read one.
6
7import (
8 "strings"
9
10 "rickub.com/turbo-editors/turbo-core/syntax"
11)
12
13// --- strings ----------------------------------------------------------------
14
15// isRawStringStart reports whether a raw string opens here: r", r#", br#" and
16// so on.
17func isRawStringStart(s *syntax.LineScanner) bool {
18 at := 0
19 if s.Peek(at) == 'b' {
20 at++
21 }
22 if s.Peek(at) != 'r' {
23 return false
24 }
25 at++
26 for s.Peek(at) == '#' {
27 at++
28 }
29 return s.Peek(at) == '"'
30}
31
32// startRawString colours a raw string from its opener, counting the hashes that
33// will have to close it.
34func startRawString(s *syntax.LineScanner, open *carry) {
35 start := s.Pos()
36 if s.Peek(0) == 'b' {
37 s.Advance(1)
38 }
39 s.Advance(1) // the r
40
41 hashes := 0
42 for s.Peek(0) == '#' {
43 hashes++
44 s.Advance(1)
45 }
46 s.Advance(1) // the opening quote
47
48 open.rawOpen, open.rawHashes = true, hashes
49 consumeRaw(s, open)
50 s.Emit(start, s.Pos(), syntax.ClassString)
51}
52
53// continueRawString colours the rest of a raw string opened on an earlier line.
54func continueRawString(s *syntax.LineScanner, open *carry) bool {
55 consumeRaw(s, open)
56 s.Emit(0, s.Pos(), syntax.ClassString)
57 return !open.rawOpen && !s.AtEnd()
58}
59
60// consumeRaw runs to the closing quote-plus-hashes, or to the end of the line.
61//
62// A raw string has no escapes at all, which is the whole point of it: the only
63// thing that ends one is a quote followed by exactly as many hashes as opened
64// it.
65func consumeRaw(s *syntax.LineScanner, open *carry) {
66 closer := `"` + strings.Repeat("#", open.rawHashes)
67
68 for !s.AtEnd() {
69 if s.HasPrefix(0, closer) {
70 s.Advance(len([]rune(closer)))
71 open.rawOpen, open.rawHashes = false, 0
72 return
73 }
74 s.Advance(1)
75 }
76}
77
78// isByteOrCharStart reports whether a byte string or byte character opens here:
79// b"…" or b'…'. The raw forms are caught by isRawStringStart first.
80func isByteOrCharStart(s *syntax.LineScanner) bool {
81 return s.Peek(0) == 'b' && (s.Peek(1) == '"' || s.Peek(1) == '\'')
82}
83
84// takeByteLiteral colours b"…" and b'…'.
85func takeByteLiteral(s *syntax.LineScanner, open *carry) {
86 if s.Peek(1) == '\'' {
87 start := s.Pos()
88 s.Advance(1)
89 consumeQuoted(s, '\'')
90 s.Emit(start, s.Pos(), syntax.ClassChar)
91 return
92 }
93
94 start := s.Pos()
95 s.Advance(1) // the b
96 s.Advance(1) // the opening quote
97 open.stringOpen = true
98 consumeString(s, open)
99 s.Emit(start, s.Pos(), syntax.ClassString)
100}
101
102// startString colours an ordinary "…" string.
103func startString(s *syntax.LineScanner, open *carry) {
104 start := s.Pos()
105 s.Advance(1)
106 open.stringOpen = true
107
108 consumeString(s, open)
109 s.Emit(start, s.Pos(), syntax.ClassString)
110}
111
112// continueString colours the rest of a string opened on an earlier line. Rust
113// allows a real newline inside "…", so this is not the error state it would be
114// in most languages.
115func continueString(s *syntax.LineScanner, open *carry) bool {
116 consumeString(s, open)
117 s.Emit(0, s.Pos(), syntax.ClassString)
118 return !open.stringOpen && !s.AtEnd()
119}
120
121// consumeString runs to the closing quote or to the end of the line, honouring
122// backslash escapes.
123//
124// A backslash at the very end of a line is Rust's line continuation, which eats
125// the newline and the indentation that follows it. The string stays open either
126// way, so nothing here has to tell the two apart.
127func consumeString(s *syntax.LineScanner, open *carry) {
128 for !s.AtEnd() {
129 if s.Peek(0) == '\\' {
130 s.Advance(2)
131 continue
132 }
133 if s.Peek(0) == '"' {
134 s.Advance(1)
135 open.stringOpen = false
136 return
137 }
138 s.Advance(1)
139 }
140}
141
142// consumeQuoted runs to a closing quote on this line, honouring escapes.
143func consumeQuoted(s *syntax.LineScanner, quote rune) {
144 s.Advance(1) // the opening quote
145 for !s.AtEnd() {
146 if s.Peek(0) == '\\' {
147 s.Advance(2)
148 continue
149 }
150 if s.Peek(0) == quote {
151 s.Advance(1)
152 return
153 }
154 s.Advance(1)
155 }
156}
157
158// --- characters and lifetimes -----------------------------------------------
159
160// takeQuoteOrLifetime tells a character literal from a lifetime.
161//
162// They begin with the same rune, and Rust settles it by what follows: 'a' is a
163// character and 'a is a lifetime. The rule here is to look for the closing
164// quote where a character literal would have to put it — one rune along, or two
165// for an escape — and to read a lifetime when it is not there. That gets
166// 'static, '\n', 'a', '\u{1F600}' and 'a right, and it is decided entirely from
167// the line in front of it.
168func takeQuoteOrLifetime(s *syntax.LineScanner) {
169 if isCharLiteral(s) {
170 start := s.Pos()
171 consumeQuoted(s, '\'')
172 s.Emit(start, s.Pos(), syntax.ClassChar)
173 return
174 }
175
176 // A lifetime is coloured as a type: it is a generic parameter, declared and
177 // used in the same places one is. Quote and name go out as **one** span —
178 // emitting the name first and the quote after it put the line's spans out
179 // of order, which the editor draws wrongly rather than noticing.
180 start := s.Pos()
181 s.Advance(1)
182 for !s.AtEnd() && syntax.IsWordRune(s.Peek(0)) {
183 s.Advance(1)
184 }
185 s.Emit(start, s.Pos(), syntax.ClassType)
186}
187
188// isCharLiteral reports whether the quote at the scanner's position opens a
189// character literal rather than a lifetime.
190func isCharLiteral(s *syntax.LineScanner) bool {
191 if s.Peek(1) == '\\' {
192 // An escape: '\n' closes at 3, '\u{1F600}' further along. Look for the
193 // quote rather than decoding the escape.
194 for at := 2; at < 12; at++ {
195 if s.Peek(at) == '\'' {
196 return true
197 }
198 if s.Peek(at) == 0 {
199 return false
200 }
201 }
202 return false
203 }
204 return s.Peek(1) != 0 && s.Peek(2) == '\''
205}