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()
}