Skip to content

Commit 4a853dd

Browse files
committed
feat: comand write
1 parent b6f1aaf commit 4a853dd

18 files changed

Lines changed: 899 additions & 104 deletions

File tree

README.md

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,17 @@
1818

1919
![ABCoder](images/ABCoder.png)
2020

21-
ABCoder, an AI-oriented code handling tool, is designed to enhance coding-context for Large-Language-Model (LLM).
22-
21+
ABCoder, an AI-oriented code-processing tool, is designed to enhance coding-context for Large-Language-Model (LLM), simplify AI-assisted-coding process.
2322

2423
## Features
2524

26-
- Universal Abstract Syntax Tree (UniAST), an language-independent and AI-friendly coding-context, provides ample and recursive code information for AI or programs.
25+
- Universal Abstract Syntax Tree (UniAST), an language-independent and AI-friendly coding-context AST specfication, providing ample and recursive code information for both AI and hunman.
2726

2827
- Universal Parser, parses abitary languages to UniAST.
2928

3029
- Univeral Writer, transforms UniAST back to codes.
3130

32-
- (WIP) Code Understanding and Semantic Querying, which can be used to retrieve codes with natural language for either human or AI.
33-
34-
Based on these features, ABCoder can help developers to easily implement or enhance many AI-assisted coding applications, such as code reviewer, IDE copilot and so on.
31+
Based on these features, developers can easily implement or enhance their AI-assisted-coding agent or workflow, such as reviewing, optimizing, translating...
3532

3633
## Getting Started
3734

@@ -41,11 +38,12 @@ go install github.com/cloudwego/abcoder@latest
4138
```
4239
2. Use ABCoder to parse a repository to UniAST (JSON)
4340
```bash
44-
abcoder parse <language> <repo-path> > <AST-path>
41+
abcoder parse {language} {repo-path} > ast.json
4542
```
46-
3. Use ABCoder as a writer
43+
3. Do your magic with UniAST...
44+
4. Use ABCoder to write a UniAST back to codes
4745
```bash
48-
abcoder write <AST-path>
46+
abcoder write {language} ast.json
4947
```
5048

5149
## Universal-Abstract-Syntax-Tree Specification
@@ -57,11 +55,12 @@ see [UniAST Specification](docs/uniast-zh.md)
5755

5856
ABCoder currently supports the following languages:
5957

60-
| Language | Parser | Writer |
61-
| -------- | ------ | ------ |
62-
| Go |||
63-
| Rust || WIP |
64-
| C | WIP ||
58+
| Language | Parser | Writer |
59+
| -------- | ----------- | ----------- |
60+
| Go |||
61+
| Rust || Coming Soon |
62+
| Kotlin | Coming Soon ||
63+
| C | WIP ||
6564

6665

6766
## Getting Involved

lang/collect/collect.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ import (
2323
"unicode"
2424

2525
"github.com/cloudwego/abcoder/lang/log"
26-
"github.com/cloudwego/abcoder/lang/lsp"
2726
. "github.com/cloudwego/abcoder/lang/lsp"
2827
"github.com/cloudwego/abcoder/lang/rust"
28+
"github.com/cloudwego/abcoder/lang/uniast"
2929
)
3030

3131
type CollectOption struct {
32-
Language lsp.Language
32+
Language uniast.Language
3333
LoadExternalSymbol bool
3434
NeedStdSymbol bool
3535
NoNeedComment bool
@@ -75,9 +75,9 @@ type functionInfo struct {
7575
OutputsSorted []dependency `json:"-"`
7676
}
7777

78-
func switchSpec(l lsp.Language) lsp.LanguageSpec {
78+
func switchSpec(l uniast.Language) LanguageSpec {
7979
switch l {
80-
case Rust:
80+
case uniast.Rust:
8181
return &rust.RustSpec{}
8282
default:
8383
panic(fmt.Sprintf("unsupported language %s", l))
@@ -94,7 +94,7 @@ func NewCollector(repo string, cli *LSPClient) *Collector {
9494
deps: map[*DocumentSymbol][]dependency{},
9595
vars: map[*DocumentSymbol]dependency{},
9696
}
97-
if cli.Language == Rust {
97+
if cli.Language == uniast.Rust {
9898
ret.modPatcher = &rust.RustModulePatcher{Root: repo}
9999
}
100100
return ret
@@ -522,7 +522,7 @@ func (c *Collector) collectImpl(ctx context.Context, sym *DocumentSymbol, depth
522522
}
523523
var impl string
524524
if fn > 0 && fn < len(sym.Tokens) {
525-
impl = lsp.ChunkHead(sym.Text, sym.Location.Range.Start, sym.Tokens[fn].Location.Range.Start)
525+
impl = ChunkHead(sym.Text, sym.Location.Range.Start, sym.Tokens[fn].Location.Range.Start)
526526
}
527527
if impl == "" || len(impl) < len(sym.Name) {
528528
impl = sym.Name

lang/golang/writer/write.go

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,14 @@ import (
2222
"go/parser"
2323
"go/token"
2424
"os"
25+
"os/exec"
2526
"path/filepath"
27+
"regexp"
2628
"sort"
2729
"strconv"
2830
"strings"
2931

32+
"github.com/cloudwego/abcoder/lang/log"
3033
"github.com/cloudwego/abcoder/lang/uniast"
3134
"github.com/cloudwego/abcoder/lang/utils"
3235
)
@@ -36,7 +39,7 @@ var _ uniast.Writer = (*Writer)(nil)
3639
type Options struct {
3740
// RepoDir string
3841
// OutDir string
39-
GoVersion string
42+
CompilerPath string
4043
}
4144

4245
type Writer struct {
@@ -55,6 +58,9 @@ type chunk struct {
5558
}
5659

5760
func NewWriter(opts Options) *Writer {
61+
if opts.CompilerPath == "" {
62+
opts.CompilerPath = "go"
63+
}
5864
return &Writer{
5965
Options: opts,
6066
visited: make(map[string]map[string]*fileNode),
@@ -126,12 +132,16 @@ func (w *Writer) WriteModule(repo *uniast.Repository, modPath string, outDir str
126132
}
127133
}
128134

129-
// go mod
135+
// create go mod
130136
var bs strings.Builder
131137
bs.WriteString("module ")
132138
bs.WriteString(mod.Name)
133139
bs.WriteString("\n\ngo ")
134-
bs.WriteString(w.Options.GoVersion)
140+
goVersion, err := w.GetGoVersion()
141+
if err != nil {
142+
goVersion = "1.21"
143+
}
144+
bs.WriteString(goVersion)
135145
bs.WriteString("\n\n")
136146
if len(mod.Dependencies) > 0 {
137147
bs.WriteString("require (\n")
@@ -151,9 +161,31 @@ func (w *Writer) WriteModule(repo *uniast.Repository, modPath string, outDir str
151161
return fmt.Errorf("write go.mod failed: %v", err)
152162
}
153163

164+
// go mod tidy
165+
cmd := exec.Command(w.Options.CompilerPath, "mod", "tidy")
166+
cmd.Dir = outdir
167+
if err := cmd.Run(); err != nil {
168+
log.Error("go mod tidy failed: %v", err)
169+
}
154170
return nil
155171
}
156172

173+
var goVersionRegex = regexp.MustCompile(`go(\d+\.\d+(\.\d+)?)`)
174+
175+
func (w *Writer) GetGoVersion() (string, error) {
176+
cmd := exec.Command(w.Options.CompilerPath, "version")
177+
out, err := cmd.Output()
178+
if err != nil {
179+
return "", fmt.Errorf("get go version failed: %v", err)
180+
}
181+
// extract with regexp
182+
matches := goVersionRegex.FindStringSubmatch(string(out))
183+
if len(matches) == 0 {
184+
return "", fmt.Errorf("get go version failed: %v", err)
185+
}
186+
return matches[1], nil
187+
}
188+
157189
func (w *Writer) appendPackage(repo *uniast.Repository, pkg *uniast.Package) error {
158190
for _, v := range pkg.Vars {
159191
n := repo.GetNode(v.Identity)

lang/golang/writer/write_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func TestWriter_WriteRepo(t *testing.T) {
4646
name: "test",
4747
fields: fields{
4848
Options: Options{
49-
GoVersion: "1.18",
49+
CompilerPath: "1.18",
5050
},
5151
},
5252
args: args{repo: repo},

lang/lsp/client.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"time"
2525

2626
"github.com/cloudwego/abcoder/lang/log"
27+
"github.com/cloudwego/abcoder/lang/uniast"
2728
lsp "github.com/sourcegraph/go-lsp"
2829
"github.com/sourcegraph/jsonrpc2"
2930
)
@@ -39,7 +40,7 @@ type LSPClient struct {
3940

4041
type ClientOptions struct {
4142
Server string
42-
Language
43+
uniast.Language
4344
Verbose bool
4445
}
4546

lang/lsp/spec.go

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,41 +15,9 @@
1515
package lsp
1616

1717
import (
18-
"strings"
19-
2018
"github.com/cloudwego/abcoder/lang/uniast"
2119
)
2220

23-
type Language string
24-
25-
const (
26-
Rust Language = "rust"
27-
Golang Language = "golang"
28-
Unknown Language = ""
29-
)
30-
31-
func NewLanguage(l string) Language {
32-
switch strings.ToLower(l) {
33-
case "rust":
34-
return Rust
35-
case "go", "golang":
36-
return Golang
37-
default:
38-
return Unknown
39-
}
40-
}
41-
42-
func (l Language) String() string {
43-
switch l {
44-
case Rust:
45-
return "rust"
46-
case Golang:
47-
return "go"
48-
default:
49-
return "unknown"
50-
}
51-
}
52-
5321
type LanguageSpec interface {
5422
// initialize a root workspace, and return all modules [modulename=>abs-path] inside
5523
WorkSpace(root string) (map[string]string, error)

lang/parse.go

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,11 @@ import (
3333
"github.com/cloudwego/abcoder/lang/uniast"
3434
)
3535

36+
// ParseOptions is the options for parsing the repo.
3637
type ParseOptions struct {
37-
LSP string
38+
// LSP sever executable path
39+
LSP string
40+
// Language of the repo
3841
Verbose bool
3942
collect.CollectOption
4043
}
@@ -43,7 +46,10 @@ func Parse(ctx context.Context, uri string, args ParseOptions) ([]byte, error) {
4346
if !filepath.IsAbs(uri) {
4447
uri, _ = filepath.Abs(uri)
4548
}
46-
l, lspPath := checkLSP(args.Language, args.LSP)
49+
l, lspPath, err := checkLSP(args.Language, args.LSP)
50+
if err != nil {
51+
return nil, err
52+
}
4753
openfile, opentime, err := checkRepoPath(uri, l)
4854
if err != nil {
4955
return nil, err
@@ -80,12 +86,12 @@ func Parse(ctx context.Context, uri string, args ParseOptions) ([]byte, error) {
8086
return out, nil
8187
}
8288

83-
func checkRepoPath(repoPath string, language lsp.Language) (openfile string, wait time.Duration, err error) {
89+
func checkRepoPath(repoPath string, language uniast.Language) (openfile string, wait time.Duration, err error) {
8490
if _, err := os.Stat(repoPath); os.IsNotExist(err) {
8591
return "", 0, fmt.Errorf("repository not found: %s", repoPath)
8692
}
8793
switch language {
88-
case lsp.Rust:
94+
case uniast.Rust:
8995
// NOTICE: open the Cargo.toml file is required for Rust projects
9096
openfile, wait = rust.CheckRepo(repoPath)
9197
default:
@@ -97,40 +103,29 @@ func checkRepoPath(repoPath string, language lsp.Language) (openfile string, wai
97103
return
98104
}
99105

100-
func checkVerbose(verbose bool, debug bool) {
101-
if debug {
102-
103-
} else if verbose {
104-
log.SetLogLevel(log.InfoLevel)
105-
} else {
106-
log.SetLogLevel(log.ErrorLevel)
107-
}
108-
}
109-
110-
func checkLSP(language lsp.Language, lspPath string) (l lsp.Language, s string) {
106+
func checkLSP(language uniast.Language, lspPath string) (l uniast.Language, s string, err error) {
111107
switch language {
112-
case lsp.Rust:
108+
case uniast.Rust:
113109
l, s = rust.GetDefaultLSP()
114-
case lsp.Golang:
115-
l = lsp.Golang
110+
case uniast.Golang:
111+
l = uniast.Golang
116112
s = ""
117113
if _, err := exec.LookPath("go"); err != nil {
118114
if _, err := os.Stat(lspPath); os.IsNotExist(err) {
119115
log.Error("Go compiler not found, please make it excutable!\n", lspPath)
120-
os.Exit(1)
116+
return uniast.Unknown, "", err
121117
}
122118
}
123119
return
124120
default:
125-
log.Error("Unsupported language: %s\n", language)
126-
os.Exit(1)
121+
return uniast.Unknown, "", fmt.Errorf("unsupported language: %s", language)
127122
}
128123
// check if lsp excutable
129124
if lspPath != "" {
130125
if _, err := exec.LookPath(lspPath); err != nil {
131126
if _, err := os.Stat(lspPath); os.IsNotExist(err) {
132127
log.Error("Language server %s not found, please make it excutable!\n", lspPath)
133-
os.Exit(1)
128+
return uniast.Unknown, "", err
134129
}
135130
}
136131
s = lspPath
@@ -140,7 +135,7 @@ func checkLSP(language lsp.Language, lspPath string) (l lsp.Language, s string)
140135
}
141136

142137
func collectSymbol(ctx context.Context, cli *lsp.LSPClient, repoPath string, opts collect.CollectOption) (repo *uniast.Repository, err error) {
143-
if opts.Language == lsp.Golang {
138+
if opts.Language == uniast.Golang {
144139
repo, err = callGoParser(ctx, repoPath, opts)
145140
if err != nil {
146141
return nil, err

lang/rust/repo.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323
"time"
2424

2525
"github.com/cloudwego/abcoder/lang/log"
26-
"github.com/cloudwego/abcoder/lang/lsp"
26+
"github.com/cloudwego/abcoder/lang/uniast"
2727
"github.com/cloudwego/abcoder/lang/utils"
2828
)
2929

@@ -73,8 +73,8 @@ next:
7373
return openfile, wait
7474
}
7575

76-
func GetDefaultLSP() (lang lsp.Language, name string) {
77-
return lsp.Rust, "rust-analyzer"
76+
func GetDefaultLSP() (lang uniast.Language, name string) {
77+
return uniast.Rust, "rust-analyzer"
7878
}
7979

8080
func GetLastCommitTime(repo string) time.Time {

lang/uniast/ast.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,20 @@ type Language string
2828
const (
2929
Golang Language = "go"
3030
Rust Language = "rust"
31-
Unknown Language = "unknown"
31+
Unknown Language = ""
3232
)
3333

34+
func (l Language) String() string {
35+
switch l {
36+
case Rust:
37+
return "rust"
38+
case Golang:
39+
return "go"
40+
default:
41+
return string(l)
42+
}
43+
}
44+
3445
func NewLanguage(lang string) (l Language) {
3546
// sp := strings.Split(lang, "@")
3647
// if len(sp) > 1 {

0 commit comments

Comments
 (0)