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

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

scan.go · 182 lines · 5.9 KBGo Blame HistoryRaw
📦 Turbo Go 3d7798b k33g 9h ago1package golang
2
3import (
4 "go/scanner"
5 "go/token"
6
7 "rickub.com/turbo-editors/turbo-core/syntax"
8)
9
10// rawToken is one token as go/scanner reports it, reduced to what colouring
11// needs: a byte range and the class it belongs to.
12type rawToken struct {
13 start int
14 end int
15 tok token.Token
16 lit string
17}
18
19// scanTokens tokenises src, ignoring every syntax error.
20//
21// Errors are expected: the file is being typed into. The scanner still returns
22// a usable token for broken input — an unterminated string comes back as a
23// STRING running to the end of the line — which is exactly what keeps the
24// colours steady while the user types.
25func scanTokens(src string) []rawToken {
26 fileSet := token.NewFileSet()
27 file := fileSet.AddFile("", fileSet.Base(), len(src))
28
29 var s scanner.Scanner
30 s.Init(file, []byte(src), func(token.Position, string) {}, scanner.ScanComments)
31
32 var tokens []rawToken
33 for {
34 pos, tok, lit := s.Scan()
35 if tok == token.EOF {
36 return tokens
37 }
38
39 start := file.Offset(pos)
40 width := tokenWidth(tok, lit)
41 if width == 0 {
42 continue // an automatically inserted semicolon covers no text
43 }
44 tokens = append(tokens, rawToken{start: start, end: start + width, tok: tok, lit: lit})
45 }
46}
47
48// tokenWidth returns how many bytes of source a token covers.
49//
50// Operators and punctuation come back with an empty literal, so their width is
51// that of their spelling; a semicolon the scanner inserted itself covers
52// nothing at all.
53func tokenWidth(tok token.Token, lit string) int {
54 if tok == token.SEMICOLON && lit == "\n" {
55 return 0
56 }
57 if lit != "" {
58 return len(lit)
59 }
60 return len(tok.String())
61}
62
63// classify assigns a colouring class to each token, using the token that
64// follows and the one before where that is what distinguishes them: an
65// identifier before "(" is a call, and one after "type" is a type name.
66func classify(tokens []rawToken) []syntax.Class {
67 classes := make([]syntax.Class, len(tokens))
68 for i, t := range tokens {
69 classes[i] = classOf(t, previous(tokens, i), next(tokens, i))
70 }
71 return classes
72}
73
74// literalClasses are the tokens whose class follows from the token alone.
75var literalClasses = map[token.Token]syntax.Class{
76 token.COMMENT: syntax.ClassComment,
77 token.STRING: syntax.ClassString,
78 token.CHAR: syntax.ClassChar,
79 token.INT: syntax.ClassNumber,
80 token.FLOAT: syntax.ClassNumber,
81 token.IMAG: syntax.ClassNumber,
82}
83
84// classOf returns the class of a single token, given its neighbours.
85func classOf(t rawToken, before, after token.Token) syntax.Class {
86 if class, ok := literalClasses[t.tok]; ok {
87 return class
88 }
89 if t.tok == token.IDENT {
90 return identifierClass(t.lit, before, after)
91 }
92 return symbolClass(t.tok)
93}
94
95// symbolClass returns the class of anything that is neither a literal nor an
96// identifier: a keyword, a bracket, or an operator.
97func symbolClass(tok token.Token) syntax.Class {
98 switch {
99 case tok.IsKeyword():
100 return syntax.ClassKeyword
101 case isPunctuation(tok):
102 return syntax.ClassPunctuation
103 case tok.IsOperator():
104 return syntax.ClassOperator
105 }
106 return syntax.ClassIdentifier
107}
108
109// predeclaredClasses are the identifiers the language itself provides, and
110// what each of them is. They are recognised by name because they are not
111// keywords: a file may shadow "len" or "any", and colouring it as the
112// predeclared one anyway is what every other Go editor does too.
113var predeclaredClasses = map[string]syntax.Class{
114 // Types.
115 "any": syntax.ClassType, "bool": syntax.ClassType, "byte": syntax.ClassType,
116 "comparable": syntax.ClassType, "complex64": syntax.ClassType, "complex128": syntax.ClassType,
117 "error": syntax.ClassType, "float32": syntax.ClassType, "float64": syntax.ClassType,
118 "int": syntax.ClassType, "int8": syntax.ClassType, "int16": syntax.ClassType,
119 "int32": syntax.ClassType, "int64": syntax.ClassType, "rune": syntax.ClassType,
120 "string": syntax.ClassType, "uint": syntax.ClassType, "uint8": syntax.ClassType,
121 "uint16": syntax.ClassType, "uint32": syntax.ClassType, "uint64": syntax.ClassType,
122 "uintptr": syntax.ClassType,
123
124 // Constants.
125 "true": syntax.ClassConstant, "false": syntax.ClassConstant,
126 "iota": syntax.ClassConstant, "nil": syntax.ClassConstant,
127
128 // Functions.
129 "append": syntax.ClassBuiltin, "cap": syntax.ClassBuiltin, "clear": syntax.ClassBuiltin,
130 "close": syntax.ClassBuiltin, "complex": syntax.ClassBuiltin, "copy": syntax.ClassBuiltin,
131 "delete": syntax.ClassBuiltin, "imag": syntax.ClassBuiltin, "len": syntax.ClassBuiltin,
132 "make": syntax.ClassBuiltin, "max": syntax.ClassBuiltin, "min": syntax.ClassBuiltin,
133 "new": syntax.ClassBuiltin, "panic": syntax.ClassBuiltin, "print": syntax.ClassBuiltin,
134 "println": syntax.ClassBuiltin, "real": syntax.ClassBuiltin, "recover": syntax.ClassBuiltin,
135}
136
137// identifierClass tells apart the several things an identifier can be.
138func identifierClass(name string, before, after token.Token) syntax.Class {
139 if class, ok := predeclaredClasses[name]; ok {
140 return class
141 }
142
143 class := syntax.ClassIdentifier
144 switch {
145 case before == token.TYPE, before == token.STRUCT, before == token.INTERFACE:
146 class = syntax.ClassType
147 case after == token.LPAREN, before == token.FUNC:
148 class = syntax.ClassFunction
149 }
150 return class
151}
152
153// previous returns the token before index i, or ILLEGAL at the start.
154func previous(tokens []rawToken, i int) token.Token {
155 if i == 0 {
156 return token.ILLEGAL
157 }
158 return tokens[i-1].tok
159}
160
161// next returns the token after index i, or ILLEGAL at the end.
162func next(tokens []rawToken, i int) token.Token {
163 if i+1 >= len(tokens) {
164 return token.ILLEGAL
165 }
166 return tokens[i+1].tok
167}
168
169// isPunctuation reports whether a token is structure rather than computation.
170// Brackets, commas and the like are usually themed more quietly than the
171// operators that actually do something.
172func isPunctuation(tok token.Token) bool {
173 switch tok {
174 case token.LPAREN, token.RPAREN,
175 token.LBRACK, token.RBRACK,
176 token.LBRACE, token.RBRACE,
177 token.COMMA, token.SEMICOLON, token.COLON, token.PERIOD:
178 return true
179 default:
180 return false
181 }
182}