|
| 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 cxx |
| 16 | + |
| 17 | +import ( |
| 18 | + "fmt" |
| 19 | + "path/filepath" |
| 20 | + "strings" |
| 21 | + |
| 22 | + lsp "github.com/cloudwego/abcoder/lang/lsp" |
| 23 | + "github.com/cloudwego/abcoder/lang/utils" |
| 24 | +) |
| 25 | + |
| 26 | +type CxxSpec struct { |
| 27 | + repo string |
| 28 | +} |
| 29 | + |
| 30 | +func NewCxxSpec() *CxxSpec { |
| 31 | + return &CxxSpec{} |
| 32 | +} |
| 33 | + |
| 34 | +// XXX: maybe multi module support for C++? |
| 35 | +func (c *CxxSpec) WorkSpace(root string) (map[string]string, error) { |
| 36 | + c.repo = root |
| 37 | + rets := map[string]string{} |
| 38 | + absPath, err := filepath.Abs(root) |
| 39 | + if err != nil { |
| 40 | + return nil, fmt.Errorf("failed to get absolute path: %w", err) |
| 41 | + } |
| 42 | + rets["current"] = absPath |
| 43 | + return rets, nil |
| 44 | +} |
| 45 | + |
| 46 | +// returns: modname, pathpath, error |
| 47 | +// Multiple symbols with the same name could occur (for example in the Linux kernel). |
| 48 | +// The identify is mod::pkg::name. So we use the pkg (the file name) to distinguish them. |
| 49 | +func (c *CxxSpec) NameSpace(path string) (string, string, error) { |
| 50 | + // external lib: only standard library (system headers), in /usr/ |
| 51 | + if !strings.HasPrefix(path, c.repo) { |
| 52 | + if strings.HasPrefix(path, "/usr") { |
| 53 | + // assume it is c system library |
| 54 | + return "cstdlib", "cstdlib", nil |
| 55 | + } |
| 56 | + panic(fmt.Sprintf("external lib: %s\n", path)) |
| 57 | + } |
| 58 | + |
| 59 | + relpath, _ := filepath.Rel(c.repo, path) |
| 60 | + return "current", relpath, nil |
| 61 | +} |
| 62 | + |
| 63 | +func (c *CxxSpec) ShouldSkip(path string) bool { |
| 64 | + if strings.HasSuffix(path, ".c") || strings.HasSuffix(path, ".h") { |
| 65 | + return false |
| 66 | + } |
| 67 | + return true |
| 68 | +} |
| 69 | + |
| 70 | +func (c *CxxSpec) IsDocToken(tok lsp.Token) bool { |
| 71 | + return tok.Type == "comment" |
| 72 | +} |
| 73 | + |
| 74 | +func (c *CxxSpec) DeclareTokenOfSymbol(sym lsp.DocumentSymbol) int { |
| 75 | + for i, t := range sym.Tokens { |
| 76 | + if c.IsDocToken(t) { |
| 77 | + continue |
| 78 | + } |
| 79 | + for _, m := range t.Modifiers { |
| 80 | + if m == "declaration" { |
| 81 | + return i |
| 82 | + } |
| 83 | + } |
| 84 | + } |
| 85 | + return -1 |
| 86 | +} |
| 87 | + |
| 88 | +func (c *CxxSpec) IsEntityToken(tok lsp.Token) bool { |
| 89 | + return tok.Type == "class" || tok.Type == "function" || tok.Type == "variable" |
| 90 | +} |
| 91 | + |
| 92 | +func (c *CxxSpec) IsStdToken(tok lsp.Token) bool { |
| 93 | + panic("TODO") |
| 94 | +} |
| 95 | + |
| 96 | +func (c *CxxSpec) TokenKind(tok lsp.Token) lsp.SymbolKind { |
| 97 | + switch tok.Type { |
| 98 | + case "class": |
| 99 | + return lsp.SKStruct |
| 100 | + case "enum": |
| 101 | + return lsp.SKEnum |
| 102 | + case "enumMember": |
| 103 | + return lsp.SKEnumMember |
| 104 | + case "function", "macro": |
| 105 | + return lsp.SKFunction |
| 106 | + // rust spec does not treat parameter as a variable |
| 107 | + case "parameter": |
| 108 | + return lsp.SKVariable |
| 109 | + case "typeParameter": |
| 110 | + return lsp.SKTypeParameter |
| 111 | + // type: TODO |
| 112 | + case "interface", "concept", "method", "modifier", "namespace", "type": |
| 113 | + panic(fmt.Sprintf("Unsupported token type: %s at %+v\n", tok.Type, tok.Location)) |
| 114 | + case "bracket", "comment", "label", "operator", "property", "unknown": |
| 115 | + return lsp.SKUnknown |
| 116 | + } |
| 117 | + panic(fmt.Sprintf("Weird token type: %s at %+v\n", tok.Type, tok.Location)) |
| 118 | +} |
| 119 | + |
| 120 | +func (c *CxxSpec) IsMainFunction(sym lsp.DocumentSymbol) bool { |
| 121 | + return sym.Kind == lsp.SKFunction && sym.Name == "main" |
| 122 | +} |
| 123 | + |
| 124 | +func (c *CxxSpec) IsEntitySymbol(sym lsp.DocumentSymbol) bool { |
| 125 | + typ := sym.Kind |
| 126 | + return typ == lsp.SKFunction || typ == lsp.SKVariable || typ == lsp.SKClass |
| 127 | + |
| 128 | +} |
| 129 | + |
| 130 | +func (c *CxxSpec) IsPublicSymbol(sym lsp.DocumentSymbol) bool { |
| 131 | + id := c.DeclareTokenOfSymbol(sym) |
| 132 | + if id == -1 { |
| 133 | + return false |
| 134 | + } |
| 135 | + for _, m := range sym.Tokens[id].Modifiers { |
| 136 | + if m == "globalScope" { |
| 137 | + return true |
| 138 | + } |
| 139 | + } |
| 140 | + return false |
| 141 | +} |
| 142 | + |
| 143 | +// TODO(cpp): support C++ OOP |
| 144 | +func (c *CxxSpec) HasImplSymbol() bool { |
| 145 | + return false |
| 146 | +} |
| 147 | + |
| 148 | +func (c *CxxSpec) ImplSymbol(sym lsp.DocumentSymbol) (int, int, int) { |
| 149 | + panic("TODO") |
| 150 | +} |
| 151 | + |
| 152 | +func (c *CxxSpec) FunctionSymbol(sym lsp.DocumentSymbol) (int, []int, []int, []int) { |
| 153 | + // No receiver and no type params for C |
| 154 | + if sym.Kind != lsp.SKFunction { |
| 155 | + return -1, nil, nil, nil |
| 156 | + } |
| 157 | + receiver := -1 |
| 158 | + typeParams := []int{} |
| 159 | + inputParams := []int{} |
| 160 | + outputs := []int{} |
| 161 | + |
| 162 | + // general format: RETURNVALUE NAME "(" PARAMS ")" BODY |
| 163 | + // -------- |
| 164 | + // fnNameText |
| 165 | + // state machine phase 0 phase 1 phase 2: break |
| 166 | + // TODO: attributes may contain parens. also inline structs. |
| 167 | + |
| 168 | + endRelOffset := 0 |
| 169 | + lines := utils.CountLinesCached(sym.Text) |
| 170 | + phase := 0 |
| 171 | + for i, tok := range sym.Tokens { |
| 172 | + switch phase { |
| 173 | + case 0: |
| 174 | + if tok.Type == "function" { |
| 175 | + offset := lsp.RelativePostionWithLines(*lines, sym.Location.Range.Start, tok.Location.Range.Start) |
| 176 | + endRelOffset = offset + strings.Index(sym.Text[offset:], ")") |
| 177 | + phase = 1 |
| 178 | + continue |
| 179 | + } |
| 180 | + if c.IsEntityToken(tok) { |
| 181 | + outputs = append(outputs, i) |
| 182 | + } |
| 183 | + case 1: |
| 184 | + offset := lsp.RelativePostionWithLines(*lines, sym.Location.Range.Start, tok.Location.Range.Start) |
| 185 | + if offset > endRelOffset { |
| 186 | + phase = 2 |
| 187 | + continue |
| 188 | + } |
| 189 | + if c.IsEntityToken(tok) { |
| 190 | + inputParams = append(inputParams, i) |
| 191 | + } |
| 192 | + } |
| 193 | + } |
| 194 | + return receiver, typeParams, inputParams, outputs |
| 195 | +} |
| 196 | + |
| 197 | +func (c *CxxSpec) GetUnloadedSymbol(from lsp.Token, define lsp.Location) (string, error) { |
| 198 | + panic("TODO") |
| 199 | +} |
0 commit comments