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
129 lines
3.2 KiB
Go
129 lines
3.2 KiB
Go
package languages
|
|
|
|
// findIndentedBlockEnd returns the 1-based line number of the last
|
|
// line belonging to an indentation-delimited block that starts at
|
|
// startLine. It scans forward from the first content line inside the
|
|
// block, records its indent, and returns the line before the first
|
|
// subsequent non-empty line whose indent drops to or below the
|
|
// starting line's indent.
|
|
//
|
|
// Used by regex-based extractors for languages without brace
|
|
// delimiters: Verse, GDScript, Fortran (as a fallback), Nix
|
|
// `let ... in` scopes, Python (when tree-sitter is unavailable).
|
|
//
|
|
// Falls back to startLine when no content lines follow so callers
|
|
// always get a sensible range.
|
|
func findIndentedBlockEnd(lines []string, startLine int) int {
|
|
if startLine < 1 || startLine > len(lines) {
|
|
return startLine
|
|
}
|
|
startIndent := leadingIndent(lines[startLine-1])
|
|
// Walk forward; first non-empty line sets the block indent.
|
|
blockIndent := -1
|
|
lastContent := startLine
|
|
for i := startLine; i < len(lines); i++ {
|
|
line := lines[i]
|
|
if trimmed(line) == "" {
|
|
continue
|
|
}
|
|
indent := leadingIndent(line)
|
|
if blockIndent == -1 {
|
|
if indent <= startIndent {
|
|
// No deeper block — single-line definition.
|
|
return startLine
|
|
}
|
|
blockIndent = indent
|
|
lastContent = i + 1
|
|
continue
|
|
}
|
|
if indent < blockIndent || (indent <= startIndent && indent <= blockIndent) {
|
|
return lastContent
|
|
}
|
|
lastContent = i + 1
|
|
}
|
|
return lastContent
|
|
}
|
|
|
|
func leadingIndent(s string) int {
|
|
n := 0
|
|
for _, r := range s {
|
|
switch r {
|
|
case ' ':
|
|
n++
|
|
case '\t':
|
|
n += 4
|
|
default:
|
|
return n
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
// findKeywordBlockEnd scans forward from startLine for the first line
|
|
// whose trimmed prefix matches any of the given end-keywords
|
|
// (case-insensitive). Used for languages with explicit block
|
|
// terminators like Fortran's `end subroutine` / `end function` /
|
|
// `end module`, SQL's `end;`, and VB-ish `End Sub`.
|
|
//
|
|
// Returns startLine when no terminator is found, matching the
|
|
// convention of findBlockEnd.
|
|
func findKeywordBlockEnd(lines []string, startLine int, endKeywords ...string) int {
|
|
if startLine < 1 || startLine > len(lines) {
|
|
return startLine
|
|
}
|
|
for i := startLine; i < len(lines); i++ {
|
|
line := trimmed(lines[i])
|
|
if line == "" {
|
|
continue
|
|
}
|
|
lower := toLower(line)
|
|
for _, kw := range endKeywords {
|
|
if hasPrefixWord(lower, kw) {
|
|
return i + 1
|
|
}
|
|
}
|
|
}
|
|
return startLine
|
|
}
|
|
|
|
func toLower(s string) string {
|
|
b := make([]byte, len(s))
|
|
for i := 0; i < len(s); i++ {
|
|
c := s[i]
|
|
if c >= 'A' && c <= 'Z' {
|
|
c += 'a' - 'A'
|
|
}
|
|
b[i] = c
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
// hasPrefixWord reports whether s begins with kw followed by end-of-
|
|
// string, whitespace, or punctuation. Avoids matching `enddo` when
|
|
// looking for `end`.
|
|
func hasPrefixWord(s, kw string) bool {
|
|
if len(s) < len(kw) {
|
|
return false
|
|
}
|
|
if s[:len(kw)] != kw {
|
|
return false
|
|
}
|
|
if len(s) == len(kw) {
|
|
return true
|
|
}
|
|
c := s[len(kw)]
|
|
return c == ' ' || c == '\t' || c == ';' || c == '(' || c == ':'
|
|
}
|
|
|
|
func trimmed(s string) string {
|
|
i := 0
|
|
j := len(s)
|
|
for i < j && (s[i] == ' ' || s[i] == '\t' || s[i] == '\r') {
|
|
i++
|
|
}
|
|
for j > i && (s[j-1] == ' ' || s[j-1] == '\t' || s[j-1] == '\r') {
|
|
j--
|
|
}
|
|
return s[i:j]
|
|
}
|