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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
package moonbitlang
// The literals of MoonBit: five quoted forms, and one rule that governs all of
// them — none may reach the next line.
import "rickub.com/turbo-editors/turbo-core/syntax"
// literalPrefixes are the letters that may come before a quote, and what
// opening quote each of them expects.
//
// b takes either quote — b"bytes" and b'x' are both byte literals — while re
// takes only the double one. A prefix is only a prefix when the quote touches
// it, which is what keeps the variable b in `b + 1` from starting a literal.
var literalPrefixes = []struct {
prefix string
quotes string
}{
{"b", `"'`},
{"re", `"`},
}
// isLiteralStart reports whether a quoted literal opens at the scanner's
// position, prefix included.
func isLiteralStart(s *syntax.LineScanner) bool {
_, opens := literalPrefixLength(s)
return opens
}
// literalPrefixLength returns how many prefix runes come before the quote, and
// whether a literal opens here at all.
func literalPrefixLength(s *syntax.LineScanner) (int, bool) {
if isQuote(s.Peek(0)) {
return 0, true
}
for _, candidate := range literalPrefixes {
length := len(candidate.prefix)
if s.HasPrefix(0, candidate.prefix) && containsRune(candidate.quotes, s.Peek(length)) {
return length, true
}
}
return 0, false
}
// isQuote reports whether a rune opens a literal on its own.
func isQuote(r rune) bool { return r == '"' || r == '\'' }
// containsRune reports whether a set of runes, written as a string, holds one.
func containsRune(set string, r rune) bool {
for _, member := range set {
if member == r {
return true
}
}
return false
}
// takeLiteral colours a quoted literal from its prefix to its closing quote, or
// to the end of the line when it has none.
//
// An unterminated literal is coloured to the end of the line and left there. In
// most languages that is a tolerance; in MoonBit it is the rule, because "a
// newline before the closing quote reports an unterminated string literal" — so
// a line ending inside a literal is broken source, and carrying the colour onto
// the next line would paint the rest of the file for one stray quote.
//
// An interpolated \{expression} is *not* scanned as code. The grammar matches
// it to "the matching }", with braces inside nested literals not counting, so
// finding where one ends needs the parser rather than the scanner; and a brace
// counter that got it wrong would end the string early, which is the loudest
// way a highlighter can be broken. One flat run is the honest answer, and it is
// the one Turbo Python gives an f-string for the same reason.
func takeLiteral(s *syntax.LineScanner) {
start := s.Pos()
prefix, _ := literalPrefixLength(s)
s.Advance(prefix)
quote := s.Peek(0)
s.Advance(1)
consumeLiteral(s, quote)
s.Emit(start, s.Pos(), classOfQuote(quote))
}
// classOfQuote says which class a literal gets from the quote that opened it.
//
// The apostrophe forms — 'c' and b'x' — are characters; the double-quoted
// forms — "s", b"s" and re"s" — are strings. A regex literal is a string
// rather than a class of its own: the closed Class set has no regex, and a
// regex is a string with a second reader.
func classOfQuote(quote rune) syntax.Class {
if quote == '\'' {
return syntax.ClassChar
}
return syntax.ClassString
}
// consumeLiteral runs to the closing quote, or to the end of the line.
//
// A backslash takes the rune after it out of consideration. That one rule
// covers every escape the language has — \n, \u{1F600}, \xFF and the \{ that
// opens an interpolation — because all any of them need from this scanner is
// that the rune after the backslash cannot close the literal.
func consumeLiteral(s *syntax.LineScanner, quote rune) {
for !s.AtEnd() {
switch s.Peek(0) {
case '\\':
s.Advance(2)
case quote:
s.Advance(1)
return
default:
s.Advance(1)
}
}
}
|