turbo-editors/turbo-moonbitpublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-moonbit.git
git clone ssh://git@rickub.com/turbo-editors/turbo-moonbit.git

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

literals.go · 116 lines · 3.8 KBGo Blame HistoryRaw
📦 Turbo MoonBit cc1f595 k33g 11h ago1package moonbitlang
2
3// The literals of MoonBit: five quoted forms, and one rule that governs all of
4// them — none may reach the next line.
5
6import "rickub.com/turbo-editors/turbo-core/syntax"
7
8// literalPrefixes are the letters that may come before a quote, and what
9// opening quote each of them expects.
10//
11// b takes either quote — b"bytes" and b'x' are both byte literals — while re
12// takes only the double one. A prefix is only a prefix when the quote touches
13// it, which is what keeps the variable b in `b + 1` from starting a literal.
14var literalPrefixes = []struct {
15 prefix string
16 quotes string
17}{
18 {"b", `"'`},
19 {"re", `"`},
20}
21
22// isLiteralStart reports whether a quoted literal opens at the scanner's
23// position, prefix included.
24func isLiteralStart(s *syntax.LineScanner) bool {
25 _, opens := literalPrefixLength(s)
26 return opens
27}
28
29// literalPrefixLength returns how many prefix runes come before the quote, and
30// whether a literal opens here at all.
31func literalPrefixLength(s *syntax.LineScanner) (int, bool) {
32 if isQuote(s.Peek(0)) {
33 return 0, true
34 }
35 for _, candidate := range literalPrefixes {
36 length := len(candidate.prefix)
37 if s.HasPrefix(0, candidate.prefix) && containsRune(candidate.quotes, s.Peek(length)) {
38 return length, true
39 }
40 }
41 return 0, false
42}
43
44// isQuote reports whether a rune opens a literal on its own.
45func isQuote(r rune) bool { return r == '"' || r == '\'' }
46
47// containsRune reports whether a set of runes, written as a string, holds one.
48func containsRune(set string, r rune) bool {
49 for _, member := range set {
50 if member == r {
51 return true
52 }
53 }
54 return false
55}
56
57// takeLiteral colours a quoted literal from its prefix to its closing quote, or
58// to the end of the line when it has none.
59//
60// An unterminated literal is coloured to the end of the line and left there. In
61// most languages that is a tolerance; in MoonBit it is the rule, because "a
62// newline before the closing quote reports an unterminated string literal" — so
63// a line ending inside a literal is broken source, and carrying the colour onto
64// the next line would paint the rest of the file for one stray quote.
65//
66// An interpolated \{expression} is *not* scanned as code. The grammar matches
67// it to "the matching }", with braces inside nested literals not counting, so
68// finding where one ends needs the parser rather than the scanner; and a brace
69// counter that got it wrong would end the string early, which is the loudest
70// way a highlighter can be broken. One flat run is the honest answer, and it is
71// the one Turbo Python gives an f-string for the same reason.
72func takeLiteral(s *syntax.LineScanner) {
73 start := s.Pos()
74
75 prefix, _ := literalPrefixLength(s)
76 s.Advance(prefix)
77
78 quote := s.Peek(0)
79 s.Advance(1)
80 consumeLiteral(s, quote)
81
82 s.Emit(start, s.Pos(), classOfQuote(quote))
83}
84
85// classOfQuote says which class a literal gets from the quote that opened it.
86//
87// The apostrophe forms — 'c' and b'x' — are characters; the double-quoted
88// forms — "s", b"s" and re"s" — are strings. A regex literal is a string
89// rather than a class of its own: the closed Class set has no regex, and a
90// regex is a string with a second reader.
91func classOfQuote(quote rune) syntax.Class {
92 if quote == '\'' {
93 return syntax.ClassChar
94 }
95 return syntax.ClassString
96}
97
98// consumeLiteral runs to the closing quote, or to the end of the line.
99//
100// A backslash takes the rune after it out of consideration. That one rule
101// covers every escape the language has — \n, \u{1F600}, \xFF and the \{ that
102// opens an interpolation — because all any of them need from this scanner is
103// that the rune after the backslash cannot close the literal.
104func consumeLiteral(s *syntax.LineScanner, quote rune) {
105 for !s.AtEnd() {
106 switch s.Peek(0) {
107 case '\\':
108 s.Advance(2)
109 case quote:
110 s.Advance(1)
111 return
112 default:
113 s.Advance(1)
114 }
115 }
116}