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