gitstack

grindlemire/go-tui code browser

2.6 KB Go 113 lines 2026-05-30 · dbdeb45 raw
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
package highlight

import "unicode"

var jsKeywords = wordsSet(`await break case catch class const continue debugger default delete
	do else export extends finally for function if import in instanceof let new return super switch
	this throw try typeof var void while with yield async of as from get set static interface type
	enum implements public private protected readonly`)

var jsLiterals = wordsSet(`true false null undefined NaN Infinity`)

func lexJS(code string) []Token {
	rs := []rune(code)
	n := len(rs)
	var toks []Token
	var plain []rune
	flush := func() {
		if len(plain) > 0 {
			toks = append(toks, Token{KindPlain, string(plain)})
			plain = plain[:0]
		}
	}
	expectName := false // previous keyword was function/class/new
	for i := 0; i < n; {
		r := rs[i]
		switch {
		case r == '/' && i+1 < n && rs[i+1] == '/':
			flush()
			expectName = false
			j := i
			for j < n && rs[j] != '\n' {
				j++
			}
			toks = append(toks, Token{KindComment, string(rs[i:j])})
			i = j
		case r == '/' && i+1 < n && rs[i+1] == '*':
			flush()
			expectName = false
			j := i + 2
			for j < n && !(rs[j] == '*' && j+1 < n && rs[j+1] == '/') {
				j++
			}
			if j < n {
				j += 2
			} else {
				j = n
			}
			toks = append(toks, Token{KindComment, string(rs[i:j])})
			i = j
		case r == '`':
			flush()
			expectName = false
			j := i + 1
			for j < n && rs[j] != '`' {
				if rs[j] == '\\' && j+1 < n {
					j += 2
				} else {
					j++
				}
			}
			if j < n {
				j++
			}
			toks = append(toks, Token{KindString, string(rs[i:j])})
			i = j
		case r == '"' || r == '\'':
			flush()
			expectName = false
			j := scanQuoted(rs, i)
			toks = append(toks, Token{KindString, string(rs[i:j])})
			i = j
		case isDigit(r) || (r == '.' && i+1 < n && isDigit(rs[i+1])):
			flush()
			expectName = false
			j := scanNumber(rs, i)
			toks = append(toks, Token{KindNumber, string(rs[i:j])})
			i = j
		case isIdentStart(r):
			flush()
			j := i + 1
			for j < n && isIdentPart(rs[j]) {
				j++
			}
			word := string(rs[i:j])
			kind := KindPlain
			switch {
			case jsKeywords[word]:
				kind = KindKeyword
			case jsLiterals[word]:
				kind = KindLiteral
			case expectName:
				kind = KindType
			case nextNonSpaceIs(rs, j, '('):
				kind = KindType
			case unicode.IsUpper(rs[i]):
				kind = KindType
			}
			expectName = (word == "function" || word == "class" || word == "new") && nextNonSpaceIsIdent(rs, j)
			toks = append(toks, Token{kind, word})
			i = j
		case isOperator(r):
			flush()
			expectName = false
			toks = append(toks, Token{KindOperator, string(r)})
			i++
		default:
			plain = append(plain, r)
			i++
		}
	}
	flush()
	return toks
}