100 lines
2.7 KiB
Go
100 lines
2.7 KiB
Go
package lexer
|
|
|
|
import (
|
|
"html"
|
|
"strings"
|
|
)
|
|
|
|
var goKeywords = map[string]bool{
|
|
"break": true, "case": true, "chan": true, "const": true, "continue": true,
|
|
"default": true, "defer": true, "else": true, "fallthrough": true, "for": true,
|
|
"func": true, "go": true, "goto": true, "if": true, "import": true,
|
|
"interface": true, "map": true, "package": true, "range": true, "return": true,
|
|
"select": true, "struct": true, "switch": true, "type": true, "var": true,
|
|
// Not keywords to the Go spec — predeclared identifiers — but every editor colours
|
|
// them, and a reader looking for `nil` is looking for the same kind of thing.
|
|
"nil": true, "true": true, "false": true, "iota": true,
|
|
"string": true, "int": true, "int64": true, "float64": true, "bool": true,
|
|
"byte": true, "rune": true, "any": true, "error": true,
|
|
}
|
|
|
|
// HighlightGo turns Go source into HTML with the tokens wrapped in coloured spans.
|
|
func HighlightGo(src string) string {
|
|
var b strings.Builder
|
|
b.Grow(len(src) * 2)
|
|
|
|
i := 0
|
|
for i < len(src) {
|
|
c := src[i]
|
|
|
|
switch {
|
|
// Line comment — including the //gowasm: directives, which are the most important
|
|
// line in several of the snippets this was written for.
|
|
case c == '/' && i+1 < len(src) && src[i+1] == '/':
|
|
end := strings.IndexByte(src[i:], '\n')
|
|
if end < 0 {
|
|
end = len(src)
|
|
} else {
|
|
end += i
|
|
}
|
|
span(&b, CommentClass, src[i:end])
|
|
i = end
|
|
|
|
// Block comment.
|
|
case c == '/' && i+1 < len(src) && src[i+1] == '*':
|
|
end := strings.Index(src[i+2:], "*/")
|
|
if end < 0 {
|
|
end = len(src)
|
|
} else {
|
|
end = i + 2 + end + 2
|
|
}
|
|
span(&b, CommentClass, src[i:end])
|
|
i = end
|
|
|
|
// Interpreted string. Ends at the closing quote or the line's end — an unterminated
|
|
// string is a typo in a snippet, not a reason to paint the rest of the file green.
|
|
case c == '"':
|
|
i = quoted(&b, src, i, '"', true)
|
|
|
|
// Raw string: no escapes, and it may span lines.
|
|
case c == '`':
|
|
i = quoted(&b, src, i, '`', false)
|
|
|
|
// Rune literal.
|
|
case c == '\'':
|
|
i = quoted(&b, src, i, '\'', true)
|
|
|
|
case isDigit(c):
|
|
j := i
|
|
for j < len(src) && (isDigit(src[j]) || isHexish(src[j])) {
|
|
j++
|
|
}
|
|
span(&b, NumberClass, src[i:j])
|
|
i = j
|
|
|
|
case isIdentStart(c):
|
|
j := i
|
|
for j < len(src) && isIdentPart(src[j]) {
|
|
j++
|
|
}
|
|
word := src[i:j]
|
|
switch {
|
|
case goKeywords[word]:
|
|
span(&b, KeywordClass, word)
|
|
case callAhead(src, j):
|
|
// Colouring the callee is what makes the shape of a snippet readable at a
|
|
// glance: you see what it DOES before you read what it says.
|
|
span(&b, FuncClass, word)
|
|
default:
|
|
b.WriteString(html.EscapeString(word))
|
|
}
|
|
i = j
|
|
|
|
default:
|
|
b.WriteString(html.EscapeString(string(c)))
|
|
i++
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|