Expand Gotlin language and tooling
This commit is contained in:
parent
8f4f858d86
commit
de1262b4cf
41 changed files with 6059 additions and 379 deletions
|
|
@ -11,6 +11,7 @@ import (
|
|||
"go/parser"
|
||||
"go/token"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
|
@ -35,6 +36,7 @@ const (
|
|||
symbolKindVariable = 13
|
||||
symbolKindFunction = 12
|
||||
symbolKindInterface = 11
|
||||
symbolKindEnum = 10
|
||||
symbolKindModule = 2
|
||||
)
|
||||
|
||||
|
|
@ -62,10 +64,11 @@ type server struct {
|
|||
}
|
||||
|
||||
type documentState struct {
|
||||
text string
|
||||
program *lang.Program
|
||||
diagnostics []diagnostic
|
||||
symbols []symbol
|
||||
text string
|
||||
program *lang.Program
|
||||
diagnostics []diagnostic
|
||||
symbols []symbol
|
||||
packageSymbols []symbol
|
||||
}
|
||||
|
||||
type symbol struct {
|
||||
|
|
@ -74,6 +77,7 @@ type symbol struct {
|
|||
Detail string
|
||||
Range rng
|
||||
Targets []string
|
||||
URI string
|
||||
}
|
||||
|
||||
type variableDeclInfo struct {
|
||||
|
|
@ -238,7 +242,7 @@ func (s *server) handle(req request) error {
|
|||
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
||||
return err
|
||||
}
|
||||
s.docs[params.TextDocument.URI] = buildDocumentState(params.TextDocument.Text)
|
||||
s.docs[params.TextDocument.URI] = s.buildDocumentState(params.TextDocument.URI, params.TextDocument.Text)
|
||||
return s.publishDiagnostics(params.TextDocument.URI)
|
||||
case "textDocument/didChange":
|
||||
var params didChangeParams
|
||||
|
|
@ -249,7 +253,7 @@ func (s *server) handle(req request) error {
|
|||
if len(params.ContentChanges) > 0 {
|
||||
text = params.ContentChanges[len(params.ContentChanges)-1].Text
|
||||
}
|
||||
s.docs[params.TextDocument.URI] = buildDocumentState(text)
|
||||
s.docs[params.TextDocument.URI] = s.buildDocumentState(params.TextDocument.URI, text)
|
||||
return s.publishDiagnostics(params.TextDocument.URI)
|
||||
case "textDocument/didClose":
|
||||
var params didCloseParams
|
||||
|
|
@ -310,6 +314,89 @@ func buildDocumentState(text string) documentState {
|
|||
return state
|
||||
}
|
||||
|
||||
func (s *server) buildDocumentState(uri, text string) documentState {
|
||||
state := documentState{text: text, diagnostics: []diagnostic{}}
|
||||
program, err := lang.Parse(text)
|
||||
if err != nil {
|
||||
state.diagnostics = []diagnostic{diagnosticFromError(text, err)}
|
||||
return state
|
||||
}
|
||||
state.program = program
|
||||
state.symbols = indexSymbols(text, program)
|
||||
programs, symbols := s.packageContext(uri, program.PackagePath, text)
|
||||
state.packageSymbols = symbols
|
||||
state.diagnostics = semanticDiagnosticsWithPackage(text, program, state.symbols, programs)
|
||||
return state
|
||||
}
|
||||
|
||||
func (s *server) packageContext(currentURI, packagePath, currentText string) ([]*lang.Program, []symbol) {
|
||||
path, ok := filePathFromURI(currentURI)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
root := nearestModuleRoot(filepath.Dir(path))
|
||||
if root == "" {
|
||||
root = filepath.Dir(path)
|
||||
}
|
||||
var programs []*lang.Program
|
||||
var symbols []symbol
|
||||
_ = filepath.WalkDir(root, func(candidate string, entry os.DirEntry, err error) error {
|
||||
if err != nil || entry.IsDir() || filepath.Ext(candidate) != ".gt" {
|
||||
return nil
|
||||
}
|
||||
candidateURI := fileURI(candidate)
|
||||
text := ""
|
||||
if candidateURI == currentURI {
|
||||
text = currentText
|
||||
} else if open, found := s.docs[candidateURI]; found {
|
||||
text = open.text
|
||||
} else if data, readErr := os.ReadFile(candidate); readErr == nil {
|
||||
text = string(data)
|
||||
}
|
||||
if text == "" {
|
||||
return nil
|
||||
}
|
||||
program, parseErr := lang.Parse(text)
|
||||
if parseErr != nil || program.PackagePath != packagePath {
|
||||
return nil
|
||||
}
|
||||
programs = append(programs, program)
|
||||
for _, sym := range indexSymbols(text, program) {
|
||||
sym.URI = candidateURI
|
||||
symbols = append(symbols, sym)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return programs, symbols
|
||||
}
|
||||
|
||||
func nearestModuleRoot(directory string) string {
|
||||
for {
|
||||
if _, err := os.Stat(filepath.Join(directory, "go.mod")); err == nil {
|
||||
return directory
|
||||
}
|
||||
parent := filepath.Dir(directory)
|
||||
if parent == directory {
|
||||
return ""
|
||||
}
|
||||
directory = parent
|
||||
}
|
||||
}
|
||||
|
||||
func filePathFromURI(uri string) (string, bool) {
|
||||
parsed, err := url.Parse(uri)
|
||||
if err != nil || parsed.Scheme != "file" {
|
||||
return "", false
|
||||
}
|
||||
path, err := url.PathUnescape(parsed.Path)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
return filepath.Clean(filepath.FromSlash(path)), true
|
||||
}
|
||||
|
||||
func fileURI(path string) string { return "file://" + filepath.ToSlash(filepath.Clean(path)) }
|
||||
|
||||
func (s *server) publishDiagnostics(uri string) error {
|
||||
state, ok := s.docs[uri]
|
||||
if !ok {
|
||||
|
|
@ -355,6 +442,11 @@ func (s *server) hover(uri string, pos position) any {
|
|||
},
|
||||
}
|
||||
}
|
||||
for _, sym := range state.packageSymbols {
|
||||
if sym.Name == word {
|
||||
return map[string]any{"contents": map[string]any{"kind": "markdown", "value": "```gotlin\n" + sym.Detail + "\n```"}}
|
||||
}
|
||||
}
|
||||
if value, ok := builtinHoverDetail(word); ok {
|
||||
return map[string]any{
|
||||
"contents": map[string]any{
|
||||
|
|
@ -386,6 +478,11 @@ func (s *server) definition(uri string, pos position) any {
|
|||
return []location{{URI: uri, Range: sym.Range}}
|
||||
}
|
||||
}
|
||||
for _, sym := range state.packageSymbols {
|
||||
if sym.Name == word && sym.URI != "" {
|
||||
return []location{{URI: sym.URI, Range: sym.Range}}
|
||||
}
|
||||
}
|
||||
if result := s.goplsDefinition(state, pos); result != nil {
|
||||
return result
|
||||
}
|
||||
|
|
@ -541,7 +638,19 @@ func indexSymbols(text string, program *lang.Program) []symbol {
|
|||
}
|
||||
}
|
||||
|
||||
classRanges := findAllLineMatches(lines, regexp.MustCompile(`^\s*class\s+([A-Za-z_][A-Za-z0-9_]*)`))
|
||||
enumRanges := findAllLineMatches(lines, regexp.MustCompile(`^\s*enum\s+([A-Za-z_][A-Za-z0-9_]*)`))
|
||||
for i, decl := range program.Enums {
|
||||
r := rng{}
|
||||
if i < len(enumRanges) {
|
||||
r = enumRanges[i]
|
||||
}
|
||||
symbols = append(symbols, symbol{Name: decl.Name, Kind: symbolKindEnum, Detail: "enum " + decl.Name, Range: r})
|
||||
for _, variant := range decl.Variants {
|
||||
symbols = append(symbols, symbol{Name: variant.Name, Kind: symbolKindVariable, Detail: decl.Name + "::" + variant.Name, Range: r, Targets: []string{decl.Name}})
|
||||
}
|
||||
}
|
||||
|
||||
classRanges := findAllLineMatches(lines, regexp.MustCompile(`^\s*(?:data\s+)?class\s+([A-Za-z_][A-Za-z0-9_]*)`))
|
||||
for i, decl := range program.Classes {
|
||||
r := rng{}
|
||||
if i < len(classRanges) {
|
||||
|
|
@ -650,6 +759,10 @@ func indexSymbols(text string, program *lang.Program) []symbol {
|
|||
}
|
||||
|
||||
func semanticDiagnostics(text string, program *lang.Program, symbols []symbol) []diagnostic {
|
||||
return semanticDiagnosticsWithPackage(text, program, symbols, nil)
|
||||
}
|
||||
|
||||
func semanticDiagnosticsWithPackage(text string, program *lang.Program, symbols []symbol, packagePrograms []*lang.Program) []diagnostic {
|
||||
var diagnostics []diagnostic
|
||||
|
||||
funcSymbols := map[string]symbol{}
|
||||
|
|
@ -671,7 +784,7 @@ func semanticDiagnostics(text string, program *lang.Program, symbols []symbol) [
|
|||
} else {
|
||||
importSymbols[sym.Name] = sym
|
||||
}
|
||||
case symbolKindClass, symbolKindInterface:
|
||||
case symbolKindClass, symbolKindInterface, symbolKindEnum:
|
||||
if prev, ok := typeSymbols[sym.Name]; ok {
|
||||
diagnostics = append(diagnostics, duplicateDiagnostic(sym.Range, "duplicate type "+sym.Name))
|
||||
diagnostics = append(diagnostics, duplicateDiagnostic(prev.Range, "duplicate type "+sym.Name))
|
||||
|
|
@ -699,6 +812,26 @@ func semanticDiagnostics(text string, program *lang.Program, symbols []symbol) [
|
|||
for _, decl := range program.Workers {
|
||||
types[decl.Name] = true
|
||||
}
|
||||
for _, decl := range program.Enums {
|
||||
types[decl.Name] = true
|
||||
}
|
||||
for _, sibling := range packagePrograms {
|
||||
for _, fn := range sibling.Functions {
|
||||
functions[fn.Name] = fn
|
||||
}
|
||||
for _, decl := range sibling.Interfaces {
|
||||
types[decl.Name] = true
|
||||
}
|
||||
for _, decl := range sibling.Classes {
|
||||
types[decl.Name] = true
|
||||
}
|
||||
for _, decl := range sibling.Workers {
|
||||
types[decl.Name] = true
|
||||
}
|
||||
for _, decl := range sibling.Enums {
|
||||
types[decl.Name] = true
|
||||
}
|
||||
}
|
||||
for _, fn := range program.Functions {
|
||||
diagnostics = append(diagnostics, functionSemanticDiagnostics(text, fn, functions, imports, types, nil)...)
|
||||
}
|
||||
|
|
@ -782,6 +915,8 @@ func functionSemanticDiagnostics(text string, fn lang.FunctionDecl, functions ma
|
|||
walkExpr(s.Value, scope)
|
||||
case lang.GoStmt:
|
||||
walkExpr(s.Value, scope)
|
||||
case lang.DeferStmt:
|
||||
walkExpr(s.Value, scope)
|
||||
case lang.ExprStmt:
|
||||
walkExpr(s.Value, scope)
|
||||
case lang.IfStmt:
|
||||
|
|
@ -794,6 +929,11 @@ func functionSemanticDiagnostics(text string, fn lang.FunctionDecl, functions ma
|
|||
walkExpr(s.Cond, scope)
|
||||
bodyScope := copyScope(scope)
|
||||
walkStmts(s.Body, bodyScope)
|
||||
case lang.ForEachStmt:
|
||||
walkExpr(s.Source, scope)
|
||||
bodyScope := copyScope(scope)
|
||||
bodyScope[s.Name] = true
|
||||
walkStmts(s.Body, bodyScope)
|
||||
case lang.SelectStmt:
|
||||
for _, c := range s.Cases {
|
||||
walkExpr(c.Source, scope)
|
||||
|
|
@ -801,6 +941,15 @@ func functionSemanticDiagnostics(text string, fn lang.FunctionDecl, functions ma
|
|||
caseScope["it"] = true
|
||||
walkStmts(c.Body, caseScope)
|
||||
}
|
||||
case lang.MatchStmt:
|
||||
walkExpr(s.Value, scope)
|
||||
for _, matchCase := range s.Cases {
|
||||
caseScope := copyScope(scope)
|
||||
for _, binding := range matchCase.Bindings {
|
||||
caseScope[binding] = true
|
||||
}
|
||||
walkStmts(matchCase.Body, caseScope)
|
||||
}
|
||||
case lang.TryCatchStmt:
|
||||
tryScope := copyScope(scope)
|
||||
walkStmts(s.TryBody, tryScope)
|
||||
|
|
@ -823,32 +972,39 @@ func functionSemanticDiagnostics(text string, fn lang.FunctionDecl, functions ma
|
|||
case lang.BinaryExpr:
|
||||
walkExpr(e.Left, scope)
|
||||
walkExpr(e.Right, scope)
|
||||
case lang.CallExpr:
|
||||
if ident, ok := e.Callee.(lang.IdentExpr); ok {
|
||||
switch ident.Name {
|
||||
case "after", "every":
|
||||
if len(e.Args) != 1 {
|
||||
diagnostics = append(diagnostics, undefinedNameDiagnostic(text, ident.Name, ident.Name+"(ms) expects exactly one Int argument"))
|
||||
case lang.CallExpr:
|
||||
if ident, ok := e.Callee.(lang.IdentExpr); ok {
|
||||
switch ident.Name {
|
||||
case "after", "every":
|
||||
if len(e.Args) != 1 {
|
||||
diagnostics = append(diagnostics, undefinedNameDiagnostic(text, ident.Name, ident.Name+"(ms) expects exactly one Int argument"))
|
||||
}
|
||||
if len(e.Args) == 1 {
|
||||
switch e.Args[0].(type) {
|
||||
case lang.StringExpr, lang.BoolExpr, lang.NullExpr:
|
||||
diagnostics = append(diagnostics, undefinedNameDiagnostic(text, ident.Name, ident.Name+"(ms) expects an Int argument"))
|
||||
}
|
||||
if len(e.Args) == 1 {
|
||||
switch e.Args[0].(type) {
|
||||
case lang.StringExpr, lang.BoolExpr, lang.NullExpr:
|
||||
diagnostics = append(diagnostics, undefinedNameDiagnostic(text, ident.Name, ident.Name+"(ms) expects an Int argument"))
|
||||
}
|
||||
}
|
||||
if ident.Name == "every" && len(e.Args) == 1 {
|
||||
if value, ok := staticIntExprValue(e.Args[0]); ok && value <= 0 {
|
||||
diagnostics = append(diagnostics, undefinedNameDiagnostic(text, ident.Name, "every(ms) requires ms > 0"))
|
||||
}
|
||||
}
|
||||
if ident.Name == "every" && len(e.Args) == 1 {
|
||||
if value, ok := staticIntExprValue(e.Args[0]); ok && value <= 0 {
|
||||
diagnostics = append(diagnostics, undefinedNameDiagnostic(text, ident.Name, "every(ms) requires ms > 0"))
|
||||
}
|
||||
}
|
||||
}
|
||||
walkExpr(e.Callee, scope)
|
||||
for _, arg := range e.Args {
|
||||
walkExpr(arg, scope)
|
||||
}
|
||||
}
|
||||
walkExpr(e.Callee, scope)
|
||||
for _, arg := range e.Args {
|
||||
walkExpr(arg, scope)
|
||||
}
|
||||
case lang.SelectorExpr:
|
||||
walkExpr(e.Receiver, scope)
|
||||
case lang.IndexExpr:
|
||||
walkExpr(e.Receiver, scope)
|
||||
walkExpr(e.Index, scope)
|
||||
case lang.EnumVariantExpr:
|
||||
for _, value := range e.Values {
|
||||
walkExpr(value, scope)
|
||||
}
|
||||
case lang.LambdaExpr:
|
||||
lambdaScope := copyScope(scope)
|
||||
if e.ImplicitIt {
|
||||
|
|
@ -2026,23 +2182,43 @@ func copyScope(scope map[string]bool) map[string]bool {
|
|||
}
|
||||
|
||||
func isBuiltin(name string) bool {
|
||||
switch name {
|
||||
case "println", "runCatching", "Channel", "after", "every", "listOf", "mutableListOf", "mapOf", "mutableMapOf":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
_, ok := builtinDetails[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
func builtinHoverDetail(name string) (string, bool) {
|
||||
switch name {
|
||||
case "after":
|
||||
return "fun after(ms: Int): Channel<time.Time>", true
|
||||
case "every":
|
||||
return "fun every(ms: Int): Channel<time.Time>", true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
value, ok := builtinDetails[name]
|
||||
return value, ok
|
||||
}
|
||||
|
||||
var builtinDetails = map[string]string{
|
||||
"println": "fun println(value: Any): Unit",
|
||||
"runCatching": "fun runCatching(block: () -> Unit): Result",
|
||||
"Channel": "fun Channel<T>(capacity: Int = 0): Channel<T>",
|
||||
"after": "fun after(ms: Int): Channel<time.Time>",
|
||||
"every": "fun every(ms: Int): Channel<time.Time>",
|
||||
"listOf": "fun listOf<T>(values: T...): List<T>",
|
||||
"mutableListOf": "fun mutableListOf<T>(values: T...): MutableList<T>",
|
||||
"mapOf": "fun mapOf<K, V>(pairs: Any...): Map<K, V>",
|
||||
"mutableMapOf": "fun mutableMapOf<K, V>(pairs: Any...): MutableMap<K, V>",
|
||||
"ByteSlice": "fun ByteSlice(value: String): ByteSlice",
|
||||
"append": "fun append<T>(values: List<T>, value: T): List<T>",
|
||||
"len": "fun len(value: Any): Int",
|
||||
"cap": "fun cap(value: Any): Int",
|
||||
"make": "fun make<T>(size: Int): T",
|
||||
"new": "fun new<T>(): *T",
|
||||
"copy": "fun copy(target: Any, source: Any): Int",
|
||||
"delete": "fun delete(map: Any, key: Any): Unit",
|
||||
"close": "fun close(channel: Any): Unit",
|
||||
"panic": "fun panic(value: Any): Unit",
|
||||
"recover": "fun recover(): Any",
|
||||
"string": "fun string(value: Any): String",
|
||||
"int": "fun int(value: Any): Int",
|
||||
"float64": "fun float64(value: Any): Double",
|
||||
"bool": "fun bool(value: Any): Boolean",
|
||||
"sql": "typed PostgreSQL query DSL",
|
||||
"set": "fun set(target: Any, value: Any): Unit",
|
||||
"now": "fun now(): time.Time",
|
||||
}
|
||||
|
||||
func contains(values []string, needle string) bool {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,104 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gotlin/internal/lang"
|
||||
)
|
||||
|
||||
func TestPackageWideDiagnosticsAndDefinition(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(root, "go.mod"), []byte("module example\n\ngo 1.25\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(root, "model"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
modelPath := filepath.Join(root, "model", "command.gt")
|
||||
modelText := "package main\n\ndata class CreateAccountCommand(var name: String)\n"
|
||||
if err := os.WriteFile(modelPath, []byte(modelText), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mainPath := filepath.Join(root, "main.gt")
|
||||
mainText := "package main\n\nfun main() {\n val command = CreateAccountCommand(\"demo\")\n println(command)\n}\n"
|
||||
if err := os.WriteFile(mainPath, []byte(mainText), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
uri := fileURI(mainPath)
|
||||
s := server{docs: map[string]documentState{}}
|
||||
state := s.buildDocumentState(uri, mainText)
|
||||
for _, d := range state.diagnostics {
|
||||
if strings.Contains(d.Message, "undefined identifier CreateAccountCommand") {
|
||||
t.Fatalf("unexpected cross-file diagnostic: %+v", d)
|
||||
}
|
||||
}
|
||||
s.docs[uri] = state
|
||||
character := strings.Index(strings.Split(mainText, "\n")[3], "CreateAccountCommand")
|
||||
result, ok := s.definition(uri, position{Line: 3, Character: character + 2}).([]location)
|
||||
if !ok || len(result) != 1 {
|
||||
t.Fatalf("expected cross-file definition, got %#v", s.definition(uri, position{Line: 3, Character: character + 2}))
|
||||
}
|
||||
if result[0].URI != fileURI(modelPath) {
|
||||
t.Fatalf("definition URI = %s, want %s", result[0].URI, fileURI(modelPath))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackageIndexStopsAtNearestGoModule(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
first := filepath.Join(root, "first")
|
||||
second := filepath.Join(root, "second")
|
||||
for _, dir := range []string{first, second} {
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module example\n\ngo 1.25\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(second, "other.gt"), []byte("package main\ndata class OtherRootType(var id: String)\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mainPath := filepath.Join(first, "main.gt")
|
||||
mainText := "package main\nfun main() { OtherRootType(\"x\") }\n"
|
||||
if err := os.WriteFile(mainPath, []byte(mainText), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s := server{docs: map[string]documentState{}}
|
||||
state := s.buildDocumentState(fileURI(mainPath), mainText)
|
||||
found := false
|
||||
for _, d := range state.diagnostics {
|
||||
if strings.Contains(d.Message, "undefined identifier OtherRootType") {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("expected module-isolated undefined diagnostic, got %+v", state.diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrentBuiltinsDoNotProduceUndefinedDiagnostics(t *testing.T) {
|
||||
state := buildDocumentState(`package demo
|
||||
fun main() {
|
||||
val bytes = ByteSlice("hello")
|
||||
var values: MutableList<String> = mutableListOf<String>()
|
||||
values = append(values, string(bytes))
|
||||
println(len(values))
|
||||
}`)
|
||||
for _, diagnostic := range state.diagnostics {
|
||||
if strings.Contains(diagnostic.Message, "undefined identifier") {
|
||||
t.Fatalf("unexpected builtin diagnostic: %+v", diagnostic)
|
||||
}
|
||||
}
|
||||
for _, name := range []string{"ByteSlice", "append", "len", "sql", "set", "now"} {
|
||||
if !isBuiltin(name) {
|
||||
t.Fatalf("%s is not registered as builtin", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveStdlibTarget(t *testing.T) {
|
||||
text := strings.TrimSpace(`
|
||||
package examples.http
|
||||
|
|
@ -668,7 +760,7 @@ func TestBuildDocumentStateWorkerSemantics(t *testing.T) {
|
|||
package demo
|
||||
|
||||
worker Counter {
|
||||
val counter = 0
|
||||
var counter = 0
|
||||
|
||||
fun getCount(): Int {
|
||||
return counter
|
||||
|
|
|
|||
|
|
@ -42,13 +42,13 @@ func runBuild(args []string) {
|
|||
if err := fs.Parse(normalizedArgs); err != nil {
|
||||
fail(err)
|
||||
}
|
||||
if fs.NArg() != 1 {
|
||||
if fs.NArg() < 1 {
|
||||
fs.Usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
inputPath := fs.Arg(0)
|
||||
goSrc := compileFile(inputPath, !*srcOnly)
|
||||
goSrc := compileFiles(fs.Args(), !*srcOnly)
|
||||
|
||||
if *srcOnly {
|
||||
if *outPath == "" {
|
||||
|
|
@ -113,17 +113,42 @@ func runRun(args []string) {
|
|||
}
|
||||
|
||||
func compileFile(inputPath string, forceMain bool) string {
|
||||
src, err := os.ReadFile(inputPath)
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
return compileFiles([]string{inputPath}, forceMain)
|
||||
}
|
||||
|
||||
program, err := lang.Parse(string(src))
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
func compileFiles(inputPaths []string, forceMain bool) string {
|
||||
program := &lang.Program{}
|
||||
var firstSource string
|
||||
for _, inputPath := range inputPaths {
|
||||
src, err := os.ReadFile(inputPath)
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
|
||||
parsed, err := lang.Parse(string(src))
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
|
||||
if program.PackagePath == "" {
|
||||
program.PackagePath = parsed.PackagePath
|
||||
}
|
||||
if parsed.PackagePath != program.PackagePath {
|
||||
fail(fmt.Errorf("Gotlin files must use the same package"))
|
||||
}
|
||||
program.Imports = append(program.Imports, parsed.Imports...)
|
||||
program.Interfaces = append(program.Interfaces, parsed.Interfaces...)
|
||||
program.Enums = append(program.Enums, parsed.Enums...)
|
||||
program.Classes = append(program.Classes, parsed.Classes...)
|
||||
program.Workers = append(program.Workers, parsed.Workers...)
|
||||
program.Functions = append(program.Functions, parsed.Functions...)
|
||||
program.Embeds = append(program.Embeds, parsed.Embeds...)
|
||||
if firstSource == "" {
|
||||
firstSource = string(src)
|
||||
}
|
||||
}
|
||||
var goSrc []byte
|
||||
var err error
|
||||
if forceMain {
|
||||
goSrc, err = lang.GenerateGoMain(program)
|
||||
} else {
|
||||
|
|
@ -133,7 +158,7 @@ func compileFile(inputPath string, forceMain bool) string {
|
|||
fail(err)
|
||||
}
|
||||
if forceMain {
|
||||
return addBestEffortLineDirectives(string(goSrc), inputPath, string(src))
|
||||
return addBestEffortLineDirectives(string(goSrc), inputPaths[0], firstSource)
|
||||
}
|
||||
return string(goSrc)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue