turbo-editors/turbo-pythonpublic Fork 0
v1.0.1
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-python.git
git clone ssh://git@rickub.com/turbo-editors/turbo-python.git

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

literals.go · 129 lines · 4.2 KBGo Blame HistoryRaw
📦 Turbo Python 6fc62ea k33g 10h ago1package pythonlang
2
3// The strings of Python: eight prefixes, two quotes, and each of those in a
4// single and a triple form — sixteen spellings of one construct, plus the two
5// different ways one of them reaches the next line.
6
7import (
8 "strings"
9
10 "rickub.com/turbo-editors/turbo-core/syntax"
11)
12
13// maxStringPrefix is how many letters may come before the quote: rb, fr and
14// their case variants are two, and nothing in the language is three.
15const maxStringPrefix = 2
16
17// isStringStart reports whether a string literal opens at the scanner's
18// position, prefix included.
19func isStringStart(s *syntax.LineScanner) bool {
20 _, opens := stringPrefixLength(s)
21 return opens
22}
23
24// stringPrefixLength returns how many prefix letters come before the quote, and
25// whether a string opens here at all.
26//
27// It accepts any one or two of f, r, b and u in any order and in any case,
28// which is a little more generous than the language: `bu"…"` is not a Python
29// string and is coloured as one. Being generous is the right way to be wrong
30// here — the alternative is a table of the twenty-four accepted spellings, and
31// a half-typed prefix under the cursor colouring as an identifier and a string
32// that are not there.
33func stringPrefixLength(s *syntax.LineScanner) (int, bool) {
34 for length := 0; length <= maxStringPrefix; length++ {
35 switch r := s.Peek(length); {
36 case r == '"' || r == '\'':
37 return length, true
38 case !isStringPrefixRune(r):
39 return 0, false
40 }
41 }
42 return 0, false
43}
44
45// isStringPrefixRune reports whether a rune may appear in a string's prefix.
46func isStringPrefixRune(r rune) bool {
47 switch r {
48 case 'f', 'F', 'r', 'R', 'b', 'B', 'u', 'U':
49 return true
50 }
51 return false
52}
53
54// takeString colours a string from its prefix, remembering what would close it.
55//
56// An f-string's {expression} is *not* scanned as code. Since Python 3.12 it may
57// hold anything, nested quotes and comments included, so colouring it properly
58// means running the whole scanner inside itself; colouring it half-properly
59// means a brace in a format spec — "{n:{width}}" — ending the string early. One
60// flat run is the honest answer, and it is the one this scanner gives.
61func takeString(s *syntax.LineScanner, open *carry) {
62 start := s.Pos()
63
64 prefix, _ := stringPrefixLength(s)
65 s.Advance(prefix)
66
67 quote := s.Peek(0)
68 triple := s.Peek(1) == quote && s.Peek(2) == quote
69 if triple {
70 s.Advance(3)
71 } else {
72 s.Advance(1)
73 }
74 open.open, open.quote, open.triple = true, quote, triple
75
76 consumeString(s, open)
77 s.Emit(start, s.Pos(), syntax.ClassString)
78}
79
80// continueString colours the rest of a string opened on an earlier line, and
81// reports whether the line has code after it.
82func continueString(s *syntax.LineScanner, open *carry) bool {
83 consumeString(s, open)
84 s.Emit(0, s.Pos(), syntax.ClassString)
85 return !open.open && !s.AtEnd()
86}
87
88// consumeString runs to whatever closes the string, or to the end of the line,
89// and decides there whether the string carries on to the next one.
90//
91// A backslash takes the rune after it out of consideration, in a raw string as
92// much as in an ordinary one — that is what makes r"\"" one string rather than
93// two. A backslash that is itself the last rune on the line escapes the newline
94// instead, which is the only way a single-quoted string reaches the next line.
95func consumeString(s *syntax.LineScanner, open *carry) {
96 closer := stringCloser(*open)
97 continued := false
98
99 for !s.AtEnd() {
100 switch {
101 case s.Peek(0) == '\\':
102 continued = s.Pos()+2 > s.Len()
103 s.Advance(2)
104 case s.HasPrefix(0, closer):
105 s.Advance(len([]rune(closer)))
106 open.open = false
107 return
108 default:
109 continued = false
110 s.Advance(1)
111 }
112 }
113
114 // The line ended with the string still open. A triple-quoted one is meant
115 // to do that; a single-quoted one only does it when the newline was
116 // escaped. Anything else is source in the middle of being typed, and is
117 // left behind rather than carried into the rest of the file.
118 if !open.triple && !continued {
119 open.open = false
120 }
121}
122
123// stringCloser returns the text that ends the string being scanned.
124func stringCloser(open carry) string {
125 if open.triple {
126 return strings.Repeat(string(open.quote), 3)
127 }
128 return string(open.quote)
129}