Skip to content

Commit cfab0fd

Browse files
committed
feat: cpp init
1 parent 6c6f76b commit cfab0fd

8 files changed

Lines changed: 598 additions & 77 deletions

File tree

go.sum

Lines changed: 0 additions & 63 deletions
Large diffs are not rendered by default.

lang/collect/collect.go

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727

2828
sitter "github.com/smacker/go-tree-sitter"
2929

30+
"github.com/cloudwego/abcoder/lang/cpp"
3031
"github.com/cloudwego/abcoder/lang/cxx"
3132
"github.com/cloudwego/abcoder/lang/java"
3233
javaipc "github.com/cloudwego/abcoder/lang/java/ipc"
@@ -124,6 +125,8 @@ func switchSpec(l uniast.Language, repo string) LanguageSpec {
124125
return python.NewPythonSpec()
125126
case uniast.Java:
126127
return java.NewJavaSpec(repo)
128+
case uniast.Cpp:
129+
return cpp.NewCppSpec()
127130
default:
128131
panic(fmt.Sprintf("unsupported language %s", l))
129132
}
@@ -1836,11 +1839,13 @@ func (c *Collector) getSymbolByLocation(ctx context.Context, loc Location, depth
18361839
// return sym, nil
18371840
// }
18381841

1839-
// 1. already loaded
1840-
// Optimization: only search in symbols of the same file
1841-
if fileSyms, ok := c.symsByFile[loc.URI]; ok {
1842-
if sym := c.findMatchingSymbolIn(loc, fileSyms); sym != nil {
1843-
return sym, nil
1842+
if !(from.Type == "typeParameter" && c.Language == uniast.Cpp) {
1843+
// 1. already loaded
1844+
// Optimization: only search in symbols of the same file
1845+
if fileSyms, ok := c.symsByFile[loc.URI]; ok {
1846+
if sym := c.findMatchingSymbolIn(loc, fileSyms); sym != nil {
1847+
return sym, nil
1848+
}
18441849
}
18451850
}
18461851

@@ -2070,11 +2075,11 @@ func (c *Collector) processSymbol(ctx context.Context, sym *DocumentSymbol, dept
20702075

20712076
// function info: type params, inputs, outputs, receiver (if !needImpl)
20722077
if sym.Kind == SKFunction || sym.Kind == SKMethod {
2073-
var rsym *dependency
2078+
var rd *dependency
20742079
rec, tps, ips, ops := c.spec.FunctionSymbol(*sym)
2075-
2076-
if !hasImpl && rec >= 0 {
2080+
if (!hasImpl || c.Language == uniast.Cpp) && rec >= 0 {
20772081
rsym, err := c.getSymbolByTokenWithLimit(ctx, sym.Tokens[rec], depth)
2082+
rd = &dependency{sym.Tokens[rec].Location, rsym}
20782083
if err != nil || rsym == nil {
20792084
log.Error("get receiver symbol for token %v failed: %v\n", rec, err)
20802085
}
@@ -2083,6 +2088,18 @@ func (c *Collector) processSymbol(ctx context.Context, sym *DocumentSymbol, dept
20832088
ipsyms, is := c.getDepsWithLimit(ctx, sym, ips, depth-1)
20842089
opsyms, os := c.getDepsWithLimit(ctx, sym, ops, depth-1)
20852090

2091+
// filter tsym is type parameter
2092+
if c.Language == uniast.Cpp {
2093+
tsFiltered := make([]dependency, 0, len(ts))
2094+
for _, d := range ts {
2095+
if d.Symbol == nil || d.Symbol.Kind == SKTypeParameter {
2096+
continue
2097+
}
2098+
tsFiltered = append(tsFiltered, d)
2099+
}
2100+
ts = tsFiltered
2101+
}
2102+
20862103
//get last token of params for get signature
20872104
lastToken := rec
20882105
for _, t := range tps {
@@ -2101,18 +2118,28 @@ func (c *Collector) processSymbol(ctx context.Context, sym *DocumentSymbol, dept
21012118
}
21022119
}
21032120

2104-
c.updateFunctionInfo(sym, tsyms, ipsyms, opsyms, ts, is, os, rsym, lastToken)
2121+
c.updateFunctionInfo(sym, tsyms, ipsyms, opsyms, ts, is, os, rd, lastToken)
21052122
}
21062123

21072124
// variable info: type
21082125
if sym.Kind == SKVariable || sym.Kind == SKConstant {
21092126
i := c.spec.DeclareTokenOfSymbol(*sym)
2127+
// in cpp, it should search form behind to front to find the first entity token
21102128
// find first entity token
2111-
for i = i + 1; i < len(sym.Tokens); i++ {
2112-
if c.spec.IsEntityToken(sym.Tokens[i]) {
2113-
break
2129+
if c.Language == uniast.Cpp {
2130+
for i = i - 1; i >= 0; i-- {
2131+
if c.spec.IsEntityToken(sym.Tokens[i]) {
2132+
break
2133+
}
2134+
}
2135+
} else {
2136+
for i = i + 1; i < len(sym.Tokens); i++ {
2137+
if c.spec.IsEntityToken(sym.Tokens[i]) {
2138+
break
2139+
}
21142140
}
21152141
}
2142+
21162143
if i < 0 || i >= len(sym.Tokens) {
21172144
log.Error("get type token of variable symbol %s failed\n", sym)
21182145
return

lang/collect/export.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"strings"
2525

2626
"github.com/cloudwego/abcoder/lang/log"
27+
"github.com/cloudwego/abcoder/lang/lsp"
2728
. "github.com/cloudwego/abcoder/lang/lsp"
2829
"github.com/cloudwego/abcoder/lang/uniast"
2930
"github.com/cloudwego/abcoder/lang/utils"
@@ -336,6 +337,21 @@ func (c *Collector) exportSymbol(repo *uniast.Repository, symbol *DocumentSymbol
336337
// NOTICE: use refName as id when symbol name is missing
337338
name = refName
338339
}
340+
341+
if c.Language == uniast.Cpp {
342+
// for function override, use call signature as id
343+
if symbol.Kind == SKMethod || symbol.Kind == SKFunction {
344+
name = c.extractCppCallSig(symbol)
345+
}
346+
347+
// join name with namespace
348+
if ns := c.scopePrefix(symbol); ns != "" {
349+
if !strings.HasPrefix(name, ns+"::") {
350+
name = ns + "::" + name
351+
}
352+
}
353+
}
354+
339355
tmp := uniast.NewIdentity(mod, path, name)
340356
id = &tmp
341357
// Save to visited ONLY WHEN no errors occur
@@ -461,6 +477,18 @@ func (c *Collector) exportSymbol(repo *uniast.Repository, symbol *DocumentSymbol
461477
id.Name = iid.Name + "<" + id.Name + ">"
462478
}
463479
}
480+
481+
// cpp get method name without class name
482+
if c.Language == uniast.Cpp && rid != nil {
483+
rec := strings.TrimSpace(rid.Name)
484+
if rec != "" {
485+
searchStr := rec + "::"
486+
if idx := strings.Index(name, searchStr); idx >= 0 {
487+
name = name[idx+len(searchStr):]
488+
}
489+
}
490+
}
491+
464492
if k == SKFunction {
465493
// NOTICE: class static method name is: type::method
466494
id.Name += "::" + name
@@ -604,3 +632,130 @@ func mapKind(kind SymbolKind) uniast.TypeKind {
604632
panic(fmt.Sprintf("unexpected kind %v", kind))
605633
}
606634
}
635+
636+
func (c *Collector) scopePrefix(sym *DocumentSymbol) string {
637+
parts := []string{}
638+
cur := sym
639+
for {
640+
p := c.cli.GetParent(cur)
641+
if p == nil {
642+
break
643+
}
644+
if p.Kind == lsp.SKNamespace {
645+
if p.Name != "" {
646+
parts = append([]string{p.Name}, parts...)
647+
}
648+
}
649+
cur = p
650+
}
651+
return strings.Join(parts, "::") // "a::b"
652+
}
653+
654+
func (c *Collector) cppBaseName(n string) string {
655+
n = strings.TrimSpace(n)
656+
if i := strings.LastIndex(n, "::"); i >= 0 {
657+
n = n[i+2:]
658+
}
659+
n = strings.TrimSpace(n)
660+
// optional: strip template args on the function name itself: foo<T> -> foo
661+
if j := strings.IndexByte(n, '<'); j >= 0 {
662+
n = n[:j]
663+
}
664+
return strings.TrimSpace(n)
665+
}
666+
667+
// extractCppCallSig returns "sym.Name(params)" where params is extracted from sym.Text.
668+
func (c *Collector) extractCppCallSig(sym *lsp.DocumentSymbol) (ret string) {
669+
name := strings.TrimSpace(sym.Name)
670+
if name == "" {
671+
return ""
672+
}
673+
text := sym.Text
674+
if text == "" {
675+
return name + "()"
676+
}
677+
678+
want := c.cppBaseName(name)
679+
if want == "" {
680+
want = name
681+
}
682+
fallback := name + "()"
683+
684+
isIdent := func(b byte) bool {
685+
return (b >= 'a' && b <= 'z') ||
686+
(b >= 'A' && b <= 'Z') ||
687+
(b >= '0' && b <= '9') ||
688+
b == '_'
689+
}
690+
isWholeIdentAt := func(s string, pos int, w string) bool {
691+
if pos < 0 || pos+len(w) > len(s) || s[pos:pos+len(w)] != w {
692+
return false
693+
}
694+
if pos > 0 && isIdent(s[pos-1]) {
695+
return false
696+
}
697+
if pos+len(w) < len(s) && isIdent(s[pos+len(w)]) {
698+
return false
699+
}
700+
return true
701+
}
702+
findMatchingParenIn := func(s string, openIdx int, end int) int {
703+
if openIdx < 0 || openIdx >= len(s) || s[openIdx] != '(' {
704+
return -1
705+
}
706+
if end > len(s) {
707+
end = len(s)
708+
}
709+
depth := 0
710+
for i := openIdx; i < end; i++ {
711+
switch s[i] {
712+
case '(':
713+
depth++
714+
case ')':
715+
depth--
716+
if depth == 0 {
717+
return i
718+
}
719+
}
720+
}
721+
return -1
722+
}
723+
724+
headerEnd := len(text)
725+
if i := strings.IndexByte(text, '{'); i >= 0 && i < headerEnd {
726+
headerEnd = i
727+
}
728+
if i := strings.IndexByte(text, ';'); i >= 0 && i < headerEnd {
729+
headerEnd = i
730+
}
731+
header := text[:headerEnd]
732+
733+
namePos := -1
734+
for i := 0; i+len(want) <= len(header); i++ {
735+
if isWholeIdentAt(header, i, want) {
736+
namePos = i
737+
break
738+
}
739+
}
740+
if namePos < 0 {
741+
return fallback
742+
}
743+
744+
openIdx := -1
745+
for i := namePos + len(want); i < len(header); i++ {
746+
if header[i] == '(' {
747+
openIdx = i
748+
break
749+
}
750+
}
751+
if openIdx < 0 {
752+
return fallback
753+
}
754+
755+
closeIdx := findMatchingParenIn(header, openIdx, len(header))
756+
if closeIdx < 0 {
757+
return fallback
758+
}
759+
760+
return name + header[openIdx:closeIdx+1]
761+
}

lang/cpp/lib.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2025 CloudWeGo Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package cpp
16+
17+
import (
18+
"fmt"
19+
"time"
20+
21+
"github.com/cloudwego/abcoder/lang/uniast"
22+
"github.com/cloudwego/abcoder/lang/utils"
23+
)
24+
25+
const MaxWaitDuration = 5 * time.Minute
26+
27+
func InstallLanguageServer() (string, error) {
28+
return "", fmt.Errorf("please install clangd-18 manually. See https://releases.llvm.org/ (clangd is in clang-extra)")
29+
}
30+
31+
func GetDefaultLSP() (lang uniast.Language, name string) {
32+
return uniast.Cpp, "clangd-18 --background-index=false -j=2 --clang-tidy=false"
33+
}
34+
35+
func CheckRepo(repo string) (string, time.Duration) {
36+
openfile := ""
37+
// TODO: check if the project compiles.
38+
39+
// NOTICE: wait for Rust projects based on code files
40+
_, size := utils.CountFiles(repo, ".cpp", "build/")
41+
wait := 2*time.Second + time.Second*time.Duration(size/1024)
42+
if wait > MaxWaitDuration {
43+
wait = MaxWaitDuration
44+
}
45+
return openfile, wait
46+
}

0 commit comments

Comments
 (0)