Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

191 lines
6.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package clones
import (
"strings"
"unicode/utf8"
)
// universalKeywords is a deliberately broad, language-agnostic set of
// control-flow and declaration keywords. Tokens in this set are kept
// verbatim during normalisation so the structural skeleton of a
// function body survives; every other identifier collapses to the
// placeholder "v". A language whose exotic keyword is missing here
// simply has that keyword normalised to "v" — detection degrades
// gracefully rather than breaking.
var universalKeywords = map[string]struct{}{
// conditionals / loops
"if": {}, "else": {}, "elif": {}, "elsif": {}, "unless": {},
"for": {}, "while": {}, "do": {}, "loop": {}, "foreach": {},
"switch": {}, "case": {}, "default": {}, "match": {}, "when": {},
"break": {}, "continue": {}, "goto": {}, "return": {}, "yield": {},
// declarations
"func": {}, "function": {}, "fn": {}, "def": {}, "fun": {}, "sub": {}, "proc": {},
"class": {}, "struct": {}, "interface": {}, "enum": {}, "trait": {},
"impl": {}, "type": {}, "typedef": {}, "record": {}, "module": {}, "package": {},
"var": {}, "let": {}, "const": {}, "final": {}, "val": {}, "static": {}, "mut": {},
"public": {}, "private": {}, "protected": {}, "internal": {}, "abstract": {},
"export": {}, "import": {}, "namespace": {}, "use": {}, "using": {}, "from": {},
// objects
"new": {}, "delete": {}, "this": {}, "self": {}, "super": {}, "extends": {},
"implements": {}, "override": {}, "virtual": {},
// errors / exceptions
"try": {}, "catch": {}, "finally": {}, "throw": {}, "throws": {},
"raise": {}, "except": {}, "rescue": {}, "ensure": {}, "defer": {}, "panic": {},
"recover": {},
// concurrency
"async": {}, "await": {}, "go": {}, "chan": {}, "select": {}, "spawn": {},
"synchronized": {}, "volatile": {},
// boolean / logical
"true": {}, "false": {}, "nil": {}, "null": {}, "none": {}, "undefined": {},
"void": {}, "and": {}, "or": {}, "not": {}, "in": {}, "is": {}, "as": {},
"of": {}, "with": {}, "where": {}, "then": {}, "begin": {}, "end": {},
"lambda": {}, "where_": {},
}
// operatorRunChars are the punctuation characters that can chain into a
// single multi-character operator token (==, !=, :=, <=, &&, ->, =>,
// ::, ++, <<, etc.). Brackets, braces, parentheses, commas and
// semicolons are intentionally excluded — each is its own single-char
// token so call/block structure stays granular.
const operatorRunChars = "+-*/%=<>!&|^~?:.@"
// Tokenize reduces a source body to a normalised, language-agnostic
// token stream:
//
// - identifier in universalKeywords → the lower-cased keyword
// - any other identifier → "v"
// - numeric literal → "0"
// - string / char / raw literal → "s"
// - run of operator characters → the verbatim run (e.g. "==")
// - single bracket / brace / paren / comma / semicolon → itself
//
// Whitespace is dropped. Comments are not stripped — copy-pasted
// clones carry copy-pasted comments, and the Jaccard threshold absorbs
// the occasional divergence. The result is deterministic and depends
// only on the input bytes.
func Tokenize(body string) []string {
// Capacity hint sized for typical code density (~1 token per 8
// bytes for ordinary identifier-heavy source). Smaller estimate
// than the previous len/4 hint, which over-allocated by ~2× on
// the median function body; append will grow the slice geometrically
// when bodies tokenize denser than expected.
tokens := make([]string, 0, len(body)/8+8)
n := len(body)
i := 0
for i < n {
// Decode one rune in place rather than materialising the whole
// body as a []rune up front — that bulk conversion was the
// single biggest allocation in this function (590 MB / 30 s
// during cold-start indexing in profile #3).
c, size := utf8.DecodeRuneInString(body[i:])
switch {
case c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v':
i += size
case isIdentStart(c):
j := i + size
for j < n {
rr, rsize := utf8.DecodeRuneInString(body[j:])
if !isIdentPart(rr) {
break
}
j += rsize
}
// body[i:j] is a zero-copy substring (shares body's
// underlying bytes) — no allocation here. strings.ToLower
// only allocates when the slice actually has uppercase
// content, so all-lowercase keywords stay free.
word := body[i:j]
lower := strings.ToLower(word)
if _, ok := universalKeywords[lower]; ok {
tokens = append(tokens, lower)
} else {
tokens = append(tokens, "v")
}
i = j
case c >= '0' && c <= '9':
j := i + size
for j < n {
rr, rsize := utf8.DecodeRuneInString(body[j:])
if !isNumberPart(rr) {
break
}
j += rsize
}
tokens = append(tokens, "0")
i = j
case c == '"' || c == '\'' || c == '`':
i = skipStringLiteral(body, i)
tokens = append(tokens, "s")
case strings.ContainsRune(operatorRunChars, c):
j := i + size
for j < n {
rr, rsize := utf8.DecodeRuneInString(body[j:])
if !strings.ContainsRune(operatorRunChars, rr) {
break
}
j += rsize
}
tokens = append(tokens, body[i:j])
i = j
case c == '(' || c == ')' || c == '[' || c == ']' ||
c == '{' || c == '}' || c == ',' || c == ';':
tokens = append(tokens, body[i:i+size])
i += size
default:
// Unknown punctuation / non-ASCII symbol — keep it as a
// single token so it still contributes to the shape.
tokens = append(tokens, body[i:i+size])
i += size
}
}
return tokens
}
// skipStringLiteral returns the byte index just past a string, char,
// or raw literal starting at body[start]. Backslash escapes are
// honoured for "/' quotes; backtick raw strings run to the next
// backtick. An unterminated literal consumes to end-of-input.
//
// Operates on the source bytes directly via utf8.DecodeRuneInString so
// no []rune intermediate is needed — mirrors Tokenize's per-rune
// streaming approach.
func skipStringLiteral(body string, start int) int {
quote, qsize := utf8.DecodeRuneInString(body[start:])
i := start + qsize
n := len(body)
for i < n {
c, csize := utf8.DecodeRuneInString(body[i:])
if c == '\\' && quote != '`' {
// Skip the backslash and whatever rune it escapes.
i += csize
if i < n {
_, esize := utf8.DecodeRuneInString(body[i:])
i += esize
}
continue
}
if c == quote {
return i + csize
}
i += csize
}
return n
}
func isIdentStart(c rune) bool {
return c == '_' || c == '$' ||
(c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
c > 127
}
func isIdentPart(c rune) bool {
return isIdentStart(c) || (c >= '0' && c <= '9')
}
func isNumberPart(c rune) bool {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F') || c == '.' || c == '_' ||
c == 'x' || c == 'X' || c == 'o' || c == 'O' ||
c == 'b' || c == 'B' || c == 'e' || c == 'E'
}