init
This commit is contained in:
commit
8f4f858d86
30 changed files with 9765 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
node_modules
|
||||||
|
.gocache
|
||||||
|
bin
|
||||||
|
out
|
||||||
138
README.md
Normal file
138
README.md
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
# Gotlin
|
||||||
|
|
||||||
|
`Gotlin` is a small Kotlin-like frontend implemented in Go that targets the Go toolchain.
|
||||||
|
|
||||||
|
This is the practical boundary of the prototype:
|
||||||
|
|
||||||
|
- It is a Kotlin-flavored language frontend.
|
||||||
|
- It targets the Go toolchain by generating valid Go source and building through `go build`.
|
||||||
|
- It is not a direct integration into Go's internal `cmd/compile` backend APIs.
|
||||||
|
|
||||||
|
## Supported language slice
|
||||||
|
|
||||||
|
- `fun` declarations
|
||||||
|
- `val` and `var`
|
||||||
|
- `Int`, `String`, `Boolean`, `Unit`
|
||||||
|
- function types like `(String) -> Unit`
|
||||||
|
- `if`, `else`, `while`
|
||||||
|
- function calls
|
||||||
|
- lambdas like `{ x: Int -> println(x) }` and `{ println(it) }`
|
||||||
|
- `class` with primary-constructor fields and methods
|
||||||
|
- `interface` with method signatures
|
||||||
|
- `println(...)`
|
||||||
|
- arithmetic, comparison, and boolean operators
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
package demo
|
||||||
|
|
||||||
|
fun fib(n: Int): Int {
|
||||||
|
if (n < 2) {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
return fib(n - 1) + fib(n - 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
println(fib(8))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Imports from Go packages are supported:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
package demo
|
||||||
|
|
||||||
|
import strings
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
println(strings.ToUpper("gotlin"))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
HTTP server example:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
package demo.web
|
||||||
|
|
||||||
|
import fmt
|
||||||
|
import net.http
|
||||||
|
|
||||||
|
fun helloHandler(w: http.ResponseWriter, r: *http.Request) {
|
||||||
|
fmt.Fprintln(w, "hello from gotlin")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
http.HandleFunc("/", helloHandler)
|
||||||
|
fmt.Println("serving http://localhost:8080")
|
||||||
|
http.ListenAndServe(":8080", http.DefaultServeMux)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Classes and interfaces:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
package demo
|
||||||
|
|
||||||
|
interface Greeter {
|
||||||
|
fun greet(name: String): String
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConsoleGreeter(val prefix: String) {
|
||||||
|
fun greet(name: String): String {
|
||||||
|
return prefix + name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val greeter: Greeter = ConsoleGreeter("hello, ")
|
||||||
|
println(greeter.greet("gotlin"))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go run ./cmd/gotlinc build ./examples/hello.gt
|
||||||
|
./hello
|
||||||
|
```
|
||||||
|
|
||||||
|
Emit Go source instead:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go run ./cmd/gotlinc build -src ./examples/hello.gt -o /tmp/hello.go
|
||||||
|
go run /tmp/hello.go
|
||||||
|
```
|
||||||
|
|
||||||
|
Run directly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go run ./cmd/gotlinc run ./examples/hello.gt
|
||||||
|
```
|
||||||
|
|
||||||
|
Language server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build -o ./bin/gotlin-lsp ./cmd/gotlin-lsp
|
||||||
|
./bin/gotlin-lsp
|
||||||
|
```
|
||||||
|
|
||||||
|
VS Code extension:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ./tools/vscode-gotlin
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- `val` and `var` currently compile to the same local-variable semantics in Go.
|
||||||
|
- Type inference is local to declarations without an explicit type.
|
||||||
|
- Top-level declarations currently support functions, classes, and interfaces.
|
||||||
|
- `gotlinc build` produces an executable by default. If `-o` is omitted, the output name is derived from the input file name.
|
||||||
|
- `gotlinc build -src` emits Go source instead of a binary.
|
||||||
|
- `gotlinc` supports `build` and `run`, and defaults to `build` if no subcommand is given.
|
||||||
|
- Gotlin source files use the `.gt` extension.
|
||||||
|
- `gotlin-lsp` provides diagnostics, hover, and go-to-definition over stdio.
|
||||||
|
- `gotlin-lsp` can optionally use `gopls` for hover and definition on Go-imported symbols.
|
||||||
|
- the VS Code extension adds syntax highlighting, snippets, and launches the LSP for `.gt` files.
|
||||||
39
cmd/gotlin-lsp/README.md
Normal file
39
cmd/gotlin-lsp/README.md
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
# gotlin-lsp
|
||||||
|
|
||||||
|
Minimal stdio language server for `.gt` files.
|
||||||
|
|
||||||
|
Current support:
|
||||||
|
|
||||||
|
- `initialize`
|
||||||
|
- `shutdown`
|
||||||
|
- `exit`
|
||||||
|
- `textDocument/didOpen`
|
||||||
|
- `textDocument/didChange`
|
||||||
|
- `textDocument/didClose`
|
||||||
|
- publish diagnostics from the existing Gotlin parser/code generator
|
||||||
|
- `textDocument/hover` for packages, imports, and top-level functions
|
||||||
|
- `textDocument/definition` for imported namespaces and top-level functions
|
||||||
|
- simple semantic diagnostics for duplicate imports/functions and undefined names
|
||||||
|
- optional fallback to `gopls` for hover/definition on Go-imported symbols
|
||||||
|
|
||||||
|
Build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build -o ./bin/gotlin-lsp ./cmd/gotlin-lsp
|
||||||
|
```
|
||||||
|
|
||||||
|
Use your editor's custom LSP configuration to launch:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/gotlin-lsp
|
||||||
|
```
|
||||||
|
|
||||||
|
## gopls bridge
|
||||||
|
|
||||||
|
If `gopls` is installed, `gotlin-lsp` will try to use it as a fallback for hover and definition on Go-imported symbols.
|
||||||
|
|
||||||
|
You can set an explicit path with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GOTLIN_GOPLS_PATH=/absolute/path/to/gopls ./bin/gotlin-lsp
|
||||||
|
```
|
||||||
2293
cmd/gotlin-lsp/main.go
Normal file
2293
cmd/gotlin-lsp/main.go
Normal file
File diff suppressed because it is too large
Load diff
841
cmd/gotlin-lsp/main_test.go
Normal file
841
cmd/gotlin-lsp/main_test.go
Normal file
|
|
@ -0,0 +1,841 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gotlin/internal/lang"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResolveStdlibTarget(t *testing.T) {
|
||||||
|
text := strings.TrimSpace(`
|
||||||
|
package examples.http
|
||||||
|
|
||||||
|
import fmt
|
||||||
|
import net.http
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
fmt.Println("hello")
|
||||||
|
http.HandleFunc("/") { w, r -> helloHandler(w, r) }
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
program, err := lang.Parse(text)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
state := documentState{
|
||||||
|
text: text,
|
||||||
|
program: program,
|
||||||
|
}
|
||||||
|
|
||||||
|
printPos := position{Line: 6, Character: 9}
|
||||||
|
target, ok := resolveStdlibTarget(state, printPos)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected fmt.Println to resolve as stdlib target")
|
||||||
|
}
|
||||||
|
if target.PackagePath != "fmt" || target.SymbolName != "Println" {
|
||||||
|
t.Fatalf("unexpected target: %+v", target)
|
||||||
|
}
|
||||||
|
|
||||||
|
httpPos := position{Line: 7, Character: 6}
|
||||||
|
target, ok = resolveStdlibTarget(state, httpPos)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected import alias to resolve as stdlib package")
|
||||||
|
}
|
||||||
|
if target.PackagePath != "net/http" || target.SymbolName != "" {
|
||||||
|
t.Fatalf("unexpected package target: %+v", target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindStdlibSymbol(t *testing.T) {
|
||||||
|
sym, ok := findStdlibSymbol(stdlibTarget{
|
||||||
|
PackagePath: "fmt",
|
||||||
|
SymbolName: "Println",
|
||||||
|
})
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected fmt.Println to resolve in local Go stdlib")
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(sym.FileName, "/src/fmt/print.go") {
|
||||||
|
t.Fatalf("unexpected file: %s", sym.FileName)
|
||||||
|
}
|
||||||
|
if !strings.Contains(sym.Decl, "func Println") {
|
||||||
|
t.Fatalf("unexpected decl: %s", sym.Decl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindStdlibSymbolMethod(t *testing.T) {
|
||||||
|
sym, ok := findStdlibSymbol(stdlibTarget{
|
||||||
|
PackagePath: "bytes",
|
||||||
|
SymbolName: "WriteString",
|
||||||
|
})
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected bytes.(*Buffer).WriteString to resolve in local Go sources")
|
||||||
|
}
|
||||||
|
if !strings.Contains(sym.Decl, "WriteString") {
|
||||||
|
t.Fatalf("unexpected decl: %s", sym.Decl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveStdlibTargetThirdPartyImportPathMapping(t *testing.T) {
|
||||||
|
text := strings.TrimSpace(`
|
||||||
|
package demo
|
||||||
|
|
||||||
|
import github.com.uptrace.bun
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
bun.NewDB(nil, nil)
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
program, err := lang.Parse(text)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse failed: %v", err)
|
||||||
|
}
|
||||||
|
state := documentState{
|
||||||
|
text: text,
|
||||||
|
program: program,
|
||||||
|
}
|
||||||
|
|
||||||
|
pos := position{Line: 5, Character: 8}
|
||||||
|
target, ok := resolveStdlibTarget(state, pos)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected bun.NewDB to resolve as package target")
|
||||||
|
}
|
||||||
|
if target.PackagePath != "github.com/uptrace/bun" || target.SymbolName != "NewDB" {
|
||||||
|
t.Fatalf("unexpected target: %+v", target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveStdlibTargetFieldSelectorImportMapping(t *testing.T) {
|
||||||
|
text := strings.TrimSpace(`
|
||||||
|
package demo
|
||||||
|
|
||||||
|
import github.com.uptrace.bun
|
||||||
|
|
||||||
|
class EpicControllerImpl(val db: *bun.DB) {
|
||||||
|
fun bunHealth() {
|
||||||
|
db.NewSelect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
program, err := lang.Parse(text)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse failed: %v", err)
|
||||||
|
}
|
||||||
|
state := documentState{
|
||||||
|
text: text,
|
||||||
|
program: program,
|
||||||
|
}
|
||||||
|
|
||||||
|
pos := position{Line: 6, Character: 12}
|
||||||
|
target, ok := resolveStdlibTarget(state, pos)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected db.NewSelect to resolve as imported package symbol")
|
||||||
|
}
|
||||||
|
if target.PackagePath != "github.com/uptrace/bun" || target.SymbolName != "NewSelect" {
|
||||||
|
t.Fatalf("unexpected target: %+v", target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveStdlibTargetNestedFieldSelectorImportMapping(t *testing.T) {
|
||||||
|
text := strings.TrimSpace(`
|
||||||
|
package demo
|
||||||
|
|
||||||
|
import context
|
||||||
|
import github.com.uptrace.bun
|
||||||
|
|
||||||
|
class EpicControllerImpl(val db: *bun.DB) {
|
||||||
|
fun bunHealth() {
|
||||||
|
val ctx = context.Background()
|
||||||
|
db.NewSelect().ColumnExpr("1").Scan(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
program, err := lang.Parse(text)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse failed: %v", err)
|
||||||
|
}
|
||||||
|
state := documentState{
|
||||||
|
text: text,
|
||||||
|
program: program,
|
||||||
|
}
|
||||||
|
|
||||||
|
pos := position{Line: 8, Character: 40}
|
||||||
|
target, ok := resolveStdlibTarget(state, pos)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected nested db selector to resolve as imported package symbol")
|
||||||
|
}
|
||||||
|
if target.PackagePath != "github.com/uptrace/bun" || target.SymbolName != "Scan" {
|
||||||
|
t.Fatalf("unexpected target: %+v", target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildDocumentStateIndexesClassesAndInterfaces(t *testing.T) {
|
||||||
|
text := strings.TrimSpace(`
|
||||||
|
package demo
|
||||||
|
|
||||||
|
interface Greeter {
|
||||||
|
fun greet(name: String): String
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConsoleGreeter(val prefix: String) {
|
||||||
|
fun greet(name: String): String {
|
||||||
|
return prefix + name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
state := buildDocumentState(text)
|
||||||
|
if state.program == nil {
|
||||||
|
t.Fatal("expected parsed program")
|
||||||
|
}
|
||||||
|
|
||||||
|
wantKinds := map[string]int{
|
||||||
|
"Greeter": symbolKindInterface,
|
||||||
|
"ConsoleGreeter": symbolKindClass,
|
||||||
|
"greet": symbolKindMethod,
|
||||||
|
}
|
||||||
|
for name, kind := range wantKinds {
|
||||||
|
found := false
|
||||||
|
for _, sym := range state.symbols {
|
||||||
|
if sym.Name == name && sym.Kind == kind {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("missing symbol %q of kind %d", name, kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildDocumentStateClassSemantics(t *testing.T) {
|
||||||
|
text := strings.TrimSpace(`
|
||||||
|
package demo
|
||||||
|
|
||||||
|
interface Greeter {
|
||||||
|
fun greet(name: String): String
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConsoleGreeter(val prefix: String) {
|
||||||
|
fun greet(name: String): String {
|
||||||
|
return prefix + name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val greeter: Greeter = ConsoleGreeter("hello, ")
|
||||||
|
println(greeter.greet("gotlin"))
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
state := buildDocumentState(text)
|
||||||
|
if len(state.diagnostics) != 0 {
|
||||||
|
t.Fatalf("expected no diagnostics, got %+v", state.diagnostics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildDocumentStateHTTPServerClassSyntax(t *testing.T) {
|
||||||
|
text := strings.TrimSpace(`
|
||||||
|
package examples.http
|
||||||
|
|
||||||
|
import fmt
|
||||||
|
import net.http
|
||||||
|
|
||||||
|
interface EpicController {
|
||||||
|
fun hello(w: http.ResponseWriter, r: *http.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
class EpicControllerImpl: EpicController {
|
||||||
|
override fun hello(w: http.ResponseWriter, r: *http.Request) {
|
||||||
|
fmt.Fprintln(w, "hello from gotlin")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
state := buildDocumentState(text)
|
||||||
|
if state.program == nil {
|
||||||
|
t.Fatal("expected parsed program")
|
||||||
|
}
|
||||||
|
if len(state.diagnostics) != 0 {
|
||||||
|
t.Fatalf("expected no diagnostics, got %+v", state.diagnostics)
|
||||||
|
}
|
||||||
|
|
||||||
|
foundClass := false
|
||||||
|
foundMethod := false
|
||||||
|
for _, sym := range state.symbols {
|
||||||
|
if sym.Name == "EpicControllerImpl" && sym.Kind == symbolKindClass {
|
||||||
|
foundClass = true
|
||||||
|
if sym.Detail != "class EpicControllerImpl: EpicController" {
|
||||||
|
t.Fatalf("unexpected class detail: %q", sym.Detail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sym.Name == "hello" && sym.Kind == symbolKindMethod && strings.Contains(sym.Detail, "EpicControllerImpl.fun hello") {
|
||||||
|
foundMethod = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundClass {
|
||||||
|
t.Fatal("expected EpicControllerImpl class symbol")
|
||||||
|
}
|
||||||
|
if !foundMethod {
|
||||||
|
t.Fatal("expected EpicControllerImpl.hello method symbol")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildDocumentStateIndexesClassFieldSymbols(t *testing.T) {
|
||||||
|
text := strings.TrimSpace(`
|
||||||
|
package demo
|
||||||
|
|
||||||
|
import github.com.uptrace.bun
|
||||||
|
|
||||||
|
class EpicControllerImpl(val db: *bun.DB) {
|
||||||
|
fun hello() {
|
||||||
|
println(db)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
state := buildDocumentState(text)
|
||||||
|
if state.program == nil {
|
||||||
|
t.Fatal("expected parsed program")
|
||||||
|
}
|
||||||
|
|
||||||
|
foundField := false
|
||||||
|
for _, sym := range state.symbols {
|
||||||
|
if sym.Name == "db" && sym.Kind == symbolKindField {
|
||||||
|
foundField = true
|
||||||
|
if !strings.Contains(sym.Detail, "EpicControllerImpl.val db: *bun.DB") {
|
||||||
|
t.Fatalf("unexpected field detail: %q", sym.Detail)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !foundField {
|
||||||
|
t.Fatal("expected db field symbol")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildDocumentStateIndexesLocalVariableSymbols(t *testing.T) {
|
||||||
|
text := strings.TrimSpace(`
|
||||||
|
package demo
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val ctx = "x"
|
||||||
|
var total = 1
|
||||||
|
println(ctx)
|
||||||
|
println(total)
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
state := buildDocumentState(text)
|
||||||
|
if state.program == nil {
|
||||||
|
t.Fatal("expected parsed program")
|
||||||
|
}
|
||||||
|
|
||||||
|
foundCtx := false
|
||||||
|
foundTotal := false
|
||||||
|
for _, sym := range state.symbols {
|
||||||
|
if sym.Name == "ctx" && sym.Kind == symbolKindVariable {
|
||||||
|
foundCtx = true
|
||||||
|
if sym.Detail != "val ctx: String" {
|
||||||
|
t.Fatalf("unexpected ctx detail: %q", sym.Detail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sym.Name == "total" && sym.Kind == symbolKindVariable {
|
||||||
|
foundTotal = true
|
||||||
|
if sym.Detail != "var total: Int" {
|
||||||
|
t.Fatalf("unexpected total detail: %q", sym.Detail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !foundCtx || !foundTotal {
|
||||||
|
t.Fatalf("expected local variable symbols, got %+v", state.symbols)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildDocumentStateInfersHttpServerVariableTypes(t *testing.T) {
|
||||||
|
text := strings.TrimSpace(`
|
||||||
|
package demo
|
||||||
|
|
||||||
|
import context
|
||||||
|
import github.com.uptrace.bun
|
||||||
|
|
||||||
|
class C(val db: *bun.DB) {
|
||||||
|
fun run() {
|
||||||
|
val ctx = context.Background()
|
||||||
|
val total = db.NewSelect().ColumnExpr("1").Count(ctx)
|
||||||
|
println(total)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
state := buildDocumentState(text)
|
||||||
|
if state.program == nil {
|
||||||
|
t.Fatal("expected parsed program")
|
||||||
|
}
|
||||||
|
|
||||||
|
seenCtx := false
|
||||||
|
seenTotal := false
|
||||||
|
for _, sym := range state.symbols {
|
||||||
|
if sym.Kind != symbolKindVariable {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if sym.Name == "ctx" {
|
||||||
|
seenCtx = true
|
||||||
|
if sym.Detail != "val ctx: context.Context" {
|
||||||
|
t.Fatalf("unexpected ctx detail: %q", sym.Detail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sym.Name == "total" {
|
||||||
|
seenTotal = true
|
||||||
|
if sym.Detail != "val total: Int" {
|
||||||
|
t.Fatalf("unexpected total detail: %q", sym.Detail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !seenCtx || !seenTotal {
|
||||||
|
t.Fatalf("missing inferred variable symbols; got %+v", state.symbols)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildDocumentStateVariableTypeMatchingByName(t *testing.T) {
|
||||||
|
text := strings.TrimSpace(`
|
||||||
|
package demo
|
||||||
|
|
||||||
|
class BunUser(val Name: String)
|
||||||
|
class EpicControllerImpl
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val epicController = EpicControllerImpl()
|
||||||
|
val user = BunUser("user-from-gotlin")
|
||||||
|
println(epicController)
|
||||||
|
println(user)
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
state := buildDocumentState(text)
|
||||||
|
if state.program == nil {
|
||||||
|
t.Fatal("expected parsed program")
|
||||||
|
}
|
||||||
|
|
||||||
|
var userDetail string
|
||||||
|
var controllerDetail string
|
||||||
|
for _, sym := range state.symbols {
|
||||||
|
if sym.Kind != symbolKindVariable {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if sym.Name == "user" {
|
||||||
|
userDetail = sym.Detail
|
||||||
|
}
|
||||||
|
if sym.Name == "epicController" {
|
||||||
|
controllerDetail = sym.Detail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if userDetail != "val user: BunUser" {
|
||||||
|
t.Fatalf("unexpected user detail: %q", userDetail)
|
||||||
|
}
|
||||||
|
if controllerDetail != "val epicController: EpicControllerImpl" {
|
||||||
|
t.Fatalf("unexpected epicController detail: %q", controllerDetail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildDocumentStateMultiAssignAndNullSemantics(t *testing.T) {
|
||||||
|
text := strings.TrimSpace(`
|
||||||
|
package demo
|
||||||
|
|
||||||
|
import database.sql
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val db, err = sql.Open("postgres", "postgres://localhost/postgres?sslmode=disable")
|
||||||
|
if (err != null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
db.Close()
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
state := buildDocumentState(text)
|
||||||
|
if state.program == nil {
|
||||||
|
t.Fatal("expected parsed program")
|
||||||
|
}
|
||||||
|
if len(state.diagnostics) != 0 {
|
||||||
|
t.Fatalf("expected no diagnostics, got %+v", state.diagnostics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildDocumentStateExceptionsAndRunCatchingSemantics(t *testing.T) {
|
||||||
|
text := strings.TrimSpace(`
|
||||||
|
package demo
|
||||||
|
|
||||||
|
fun mayFail() {
|
||||||
|
throw "boom"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val result = runCatching({ mayFail() })
|
||||||
|
if (result.isSuccess()) {
|
||||||
|
println("ok")
|
||||||
|
} else {
|
||||||
|
println(result.exceptionOrNull())
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
mayFail()
|
||||||
|
} catch (e: String) {
|
||||||
|
println(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
state := buildDocumentState(text)
|
||||||
|
if state.program == nil {
|
||||||
|
t.Fatal("expected parsed program")
|
||||||
|
}
|
||||||
|
if len(state.diagnostics) != 0 {
|
||||||
|
t.Fatalf("expected no diagnostics, got %+v", state.diagnostics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildDocumentStateCollectionLiteralBuiltins(t *testing.T) {
|
||||||
|
text := strings.TrimSpace(`
|
||||||
|
package demo
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val names = listOf("alice", "bob")
|
||||||
|
val test = listOf(1, 7)
|
||||||
|
val ages = mapOf("alice", 30, "bob", 25)
|
||||||
|
println(names)
|
||||||
|
println(test)
|
||||||
|
println(ages)
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
state := buildDocumentState(text)
|
||||||
|
if state.program == nil {
|
||||||
|
t.Fatal("expected parsed program")
|
||||||
|
}
|
||||||
|
if len(state.diagnostics) != 0 {
|
||||||
|
t.Fatalf("expected no diagnostics, got %+v", state.diagnostics)
|
||||||
|
}
|
||||||
|
|
||||||
|
var namesDetail string
|
||||||
|
var testDetail string
|
||||||
|
var agesDetail string
|
||||||
|
for _, sym := range state.symbols {
|
||||||
|
if sym.Kind != symbolKindVariable {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if sym.Name == "names" {
|
||||||
|
namesDetail = sym.Detail
|
||||||
|
}
|
||||||
|
if sym.Name == "test" {
|
||||||
|
testDetail = sym.Detail
|
||||||
|
}
|
||||||
|
if sym.Name == "ages" {
|
||||||
|
agesDetail = sym.Detail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if namesDetail != "val names: List<String>" {
|
||||||
|
t.Fatalf("unexpected names detail: %q", namesDetail)
|
||||||
|
}
|
||||||
|
if testDetail != "val test: List<Int>" {
|
||||||
|
t.Fatalf("unexpected test detail: %q", testDetail)
|
||||||
|
}
|
||||||
|
if agesDetail != "val ages: Map<String, Int>" {
|
||||||
|
t.Fatalf("unexpected ages detail: %q", agesDetail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildDocumentStateCollectionLiteralBuiltinsWithTypeArgs(t *testing.T) {
|
||||||
|
text := strings.TrimSpace(`
|
||||||
|
package demo
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val test = listOf<Int>()
|
||||||
|
val labels = mapOf<String, Int>()
|
||||||
|
println(test)
|
||||||
|
println(labels)
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
state := buildDocumentState(text)
|
||||||
|
if state.program == nil {
|
||||||
|
t.Fatal("expected parsed program")
|
||||||
|
}
|
||||||
|
if len(state.diagnostics) != 0 {
|
||||||
|
t.Fatalf("expected no diagnostics, got %+v", state.diagnostics)
|
||||||
|
}
|
||||||
|
|
||||||
|
var testDetail string
|
||||||
|
var labelsDetail string
|
||||||
|
for _, sym := range state.symbols {
|
||||||
|
if sym.Kind != symbolKindVariable {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if sym.Name == "test" {
|
||||||
|
testDetail = sym.Detail
|
||||||
|
}
|
||||||
|
if sym.Name == "labels" {
|
||||||
|
labelsDetail = sym.Detail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if testDetail != "val test: List<Int>" {
|
||||||
|
t.Fatalf("unexpected test detail: %q", testDetail)
|
||||||
|
}
|
||||||
|
if labelsDetail != "val labels: Map<String, Int>" {
|
||||||
|
t.Fatalf("unexpected labels detail: %q", labelsDetail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildDocumentStateGoStatementSemantics(t *testing.T) {
|
||||||
|
text := strings.TrimSpace(`
|
||||||
|
package demo
|
||||||
|
|
||||||
|
fun runWorker(name: String) {
|
||||||
|
println(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
go runWorker("alice")
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
state := buildDocumentState(text)
|
||||||
|
if state.program == nil {
|
||||||
|
t.Fatal("expected parsed program")
|
||||||
|
}
|
||||||
|
if len(state.diagnostics) != 0 {
|
||||||
|
t.Fatalf("expected no diagnostics, got %+v", state.diagnostics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildDocumentStateChannelsAndSelectSemantics(t *testing.T) {
|
||||||
|
text := strings.TrimSpace(`
|
||||||
|
package demo
|
||||||
|
|
||||||
|
fun writer(ch: Channel<Int>) {
|
||||||
|
ch.send(7)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val ch = Channel<Int>()
|
||||||
|
go writer(ch)
|
||||||
|
select {
|
||||||
|
ch -> println(it)
|
||||||
|
}
|
||||||
|
val v = ch.read()
|
||||||
|
println(v)
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
state := buildDocumentState(text)
|
||||||
|
if state.program == nil {
|
||||||
|
t.Fatal("expected parsed program")
|
||||||
|
}
|
||||||
|
if len(state.diagnostics) != 0 {
|
||||||
|
t.Fatalf("expected no diagnostics, got %+v", state.diagnostics)
|
||||||
|
}
|
||||||
|
|
||||||
|
var chDetail string
|
||||||
|
var vDetail string
|
||||||
|
for _, sym := range state.symbols {
|
||||||
|
if sym.Kind != symbolKindVariable {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if sym.Name == "ch" {
|
||||||
|
chDetail = sym.Detail
|
||||||
|
}
|
||||||
|
if sym.Name == "v" {
|
||||||
|
vDetail = sym.Detail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if chDetail != "val ch: Channel<Int>" {
|
||||||
|
t.Fatalf("unexpected ch detail: %q", chDetail)
|
||||||
|
}
|
||||||
|
if vDetail != "val v: Int" {
|
||||||
|
t.Fatalf("unexpected v detail: %q", vDetail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildDocumentStateWorkerSemantics(t *testing.T) {
|
||||||
|
text := strings.TrimSpace(`
|
||||||
|
package demo
|
||||||
|
|
||||||
|
worker Counter {
|
||||||
|
val counter = 0
|
||||||
|
|
||||||
|
fun getCount(): Int {
|
||||||
|
return counter
|
||||||
|
}
|
||||||
|
fun increment() {
|
||||||
|
counter += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val counter = Counter()
|
||||||
|
counter.increment()
|
||||||
|
println(counter.getCount())
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
state := buildDocumentState(text)
|
||||||
|
if state.program == nil {
|
||||||
|
t.Fatal("expected parsed program")
|
||||||
|
}
|
||||||
|
if len(state.diagnostics) != 0 {
|
||||||
|
t.Fatalf("expected no diagnostics, got %+v", state.diagnostics)
|
||||||
|
}
|
||||||
|
|
||||||
|
var counterDetail string
|
||||||
|
for _, sym := range state.symbols {
|
||||||
|
if sym.Kind == symbolKindVariable && sym.Name == "counter" {
|
||||||
|
counterDetail = sym.Detail
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if counterDetail != "val counter: Counter" {
|
||||||
|
t.Fatalf("unexpected counter detail: %q", counterDetail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildDocumentStateTimerBuiltinsSemantics(t *testing.T) {
|
||||||
|
text := strings.TrimSpace(`
|
||||||
|
package demo
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val once = after(1000)
|
||||||
|
val repeat = every(250)
|
||||||
|
select {
|
||||||
|
after(1000) -> println(it)
|
||||||
|
every(250) -> println("tick")
|
||||||
|
}
|
||||||
|
println(once)
|
||||||
|
println(repeat)
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
state := buildDocumentState(text)
|
||||||
|
if state.program == nil {
|
||||||
|
t.Fatal("expected parsed program")
|
||||||
|
}
|
||||||
|
if len(state.diagnostics) != 0 {
|
||||||
|
t.Fatalf("expected no diagnostics, got %+v", state.diagnostics)
|
||||||
|
}
|
||||||
|
|
||||||
|
var onceDetail string
|
||||||
|
var repeatDetail string
|
||||||
|
for _, sym := range state.symbols {
|
||||||
|
if sym.Kind != symbolKindVariable {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if sym.Name == "once" {
|
||||||
|
onceDetail = sym.Detail
|
||||||
|
}
|
||||||
|
if sym.Name == "repeat" {
|
||||||
|
repeatDetail = sym.Detail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if onceDetail != "val once: Channel<time.Time>" {
|
||||||
|
t.Fatalf("unexpected once detail: %q", onceDetail)
|
||||||
|
}
|
||||||
|
if repeatDetail != "val repeat: Channel<time.Time>" {
|
||||||
|
t.Fatalf("unexpected repeat detail: %q", repeatDetail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildDocumentStateEveryNonPositiveDiagnostics(t *testing.T) {
|
||||||
|
text := strings.TrimSpace(`
|
||||||
|
package demo
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
select {
|
||||||
|
every(0) -> println("x")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
state := buildDocumentState(text)
|
||||||
|
if state.program == nil {
|
||||||
|
t.Fatal("expected parsed program")
|
||||||
|
}
|
||||||
|
if len(state.diagnostics) == 0 {
|
||||||
|
t.Fatal("expected diagnostics")
|
||||||
|
}
|
||||||
|
found := false
|
||||||
|
for _, d := range state.diagnostics {
|
||||||
|
if strings.Contains(d.Message, "every(ms) requires ms > 0") {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("expected every(ms) diagnostic, got %+v", state.diagnostics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildDocumentStateAfterIntArgumentDiagnostics(t *testing.T) {
|
||||||
|
text := strings.TrimSpace(`
|
||||||
|
package demo
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
select {
|
||||||
|
after("1s") -> println("x")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
state := buildDocumentState(text)
|
||||||
|
if state.program == nil {
|
||||||
|
t.Fatal("expected parsed program")
|
||||||
|
}
|
||||||
|
found := false
|
||||||
|
for _, d := range state.diagnostics {
|
||||||
|
if strings.Contains(d.Message, "after(ms) expects an Int argument") {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("expected after int argument diagnostic, got %+v", state.diagnostics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHoverBuiltinEverySignature(t *testing.T) {
|
||||||
|
text := strings.TrimSpace(`
|
||||||
|
package demo
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
every(100)
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
uri := "file:///tmp/demo.gt"
|
||||||
|
s := &server{
|
||||||
|
docs: map[string]documentState{
|
||||||
|
uri: buildDocumentState(text),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := s.hover(uri, position{Line: 3, Character: 5})
|
||||||
|
if result == nil {
|
||||||
|
t.Fatal("expected hover result")
|
||||||
|
}
|
||||||
|
m, ok := result.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("unexpected hover type: %T", result)
|
||||||
|
}
|
||||||
|
contents, ok := m["contents"].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("unexpected hover contents: %+v", m)
|
||||||
|
}
|
||||||
|
value, _ := contents["value"].(string)
|
||||||
|
if !strings.Contains(value, "fun every(ms: Int): Channel<time.Time>") {
|
||||||
|
t.Fatalf("unexpected hover value: %q", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
247
cmd/gotlinc/main.go
Normal file
247
cmd/gotlinc/main.go
Normal file
|
|
@ -0,0 +1,247 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gotlin/internal/lang"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch os.Args[1] {
|
||||||
|
case "build":
|
||||||
|
runBuild(os.Args[2:])
|
||||||
|
case "run":
|
||||||
|
runRun(os.Args[2:])
|
||||||
|
default:
|
||||||
|
runBuild(os.Args[1:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runBuild(args []string) {
|
||||||
|
fs := flag.NewFlagSet("build", flag.ExitOnError)
|
||||||
|
srcOnly := fs.Bool("src", false, "emit Go source instead of building an executable")
|
||||||
|
outPath := fs.String("o", "", "output file path")
|
||||||
|
fs.Usage = func() {
|
||||||
|
fmt.Fprintln(os.Stderr, "usage: gotlinc build [-src] [-o output] <input.gt>")
|
||||||
|
}
|
||||||
|
normalizedArgs, err := normalizeBuildArgs(args)
|
||||||
|
if err != nil {
|
||||||
|
fail(err)
|
||||||
|
}
|
||||||
|
if err := fs.Parse(normalizedArgs); err != nil {
|
||||||
|
fail(err)
|
||||||
|
}
|
||||||
|
if fs.NArg() != 1 {
|
||||||
|
fs.Usage()
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
inputPath := fs.Arg(0)
|
||||||
|
goSrc := compileFile(inputPath, !*srcOnly)
|
||||||
|
|
||||||
|
if *srcOnly {
|
||||||
|
if *outPath == "" {
|
||||||
|
_, _ = os.Stdout.WriteString(goSrc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(*outPath, []byte(goSrc), 0o644); err != nil {
|
||||||
|
fail(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
outputPath := *outPath
|
||||||
|
if outputPath == "" {
|
||||||
|
outputPath = defaultExecutablePath(inputPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := buildExecutable(goSrc, outputPath); err != nil {
|
||||||
|
fail(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runRun(args []string) {
|
||||||
|
fs := flag.NewFlagSet("run", flag.ExitOnError)
|
||||||
|
fs.Usage = func() {
|
||||||
|
fmt.Fprintln(os.Stderr, "usage: gotlinc run <input.gt> [program args...]")
|
||||||
|
}
|
||||||
|
if err := fs.Parse(args); err != nil {
|
||||||
|
fail(err)
|
||||||
|
}
|
||||||
|
if fs.NArg() < 1 {
|
||||||
|
fs.Usage()
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
inputPath := fs.Arg(0)
|
||||||
|
goSrc := compileFile(inputPath, true)
|
||||||
|
|
||||||
|
tmpFile, err := os.CreateTemp("", "gotlinc-*.go")
|
||||||
|
if err != nil {
|
||||||
|
fail(err)
|
||||||
|
}
|
||||||
|
tmpPath := tmpFile.Name()
|
||||||
|
defer os.Remove(tmpPath)
|
||||||
|
|
||||||
|
if _, err := tmpFile.WriteString(goSrc); err != nil {
|
||||||
|
_ = tmpFile.Close()
|
||||||
|
fail(err)
|
||||||
|
}
|
||||||
|
if err := tmpFile.Close(); err != nil {
|
||||||
|
fail(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdArgs := append([]string{"run", tmpPath}, fs.Args()[1:]...)
|
||||||
|
cmd := exec.Command("go", cmdArgs...)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
fail(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func compileFile(inputPath string, forceMain bool) string {
|
||||||
|
src, err := os.ReadFile(inputPath)
|
||||||
|
if err != nil {
|
||||||
|
fail(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
program, err := lang.Parse(string(src))
|
||||||
|
if err != nil {
|
||||||
|
fail(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var goSrc []byte
|
||||||
|
if forceMain {
|
||||||
|
goSrc, err = lang.GenerateGoMain(program)
|
||||||
|
} else {
|
||||||
|
goSrc, err = lang.GenerateGo(program)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
fail(err)
|
||||||
|
}
|
||||||
|
if forceMain {
|
||||||
|
return addBestEffortLineDirectives(string(goSrc), inputPath, string(src))
|
||||||
|
}
|
||||||
|
return string(goSrc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildExecutable(goSrc string, outputPath string) error {
|
||||||
|
tmpFile, err := os.CreateTemp("", "gotlinc-build-*.go")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tmpPath := tmpFile.Name()
|
||||||
|
defer os.Remove(tmpPath)
|
||||||
|
|
||||||
|
if _, err := tmpFile.WriteString(goSrc); err != nil {
|
||||||
|
_ = tmpFile.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tmpFile.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command("go", "build", "-o", outputPath, tmpPath)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultExecutablePath(inputPath string) string {
|
||||||
|
base := strings.TrimSuffix(filepath.Base(inputPath), filepath.Ext(inputPath))
|
||||||
|
if runtime.GOOS == "windows" && filepath.Ext(base) != ".exe" {
|
||||||
|
base += ".exe"
|
||||||
|
}
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeBuildArgs(args []string) ([]string, error) {
|
||||||
|
var flags []string
|
||||||
|
var positional []string
|
||||||
|
|
||||||
|
for i := 0; i < len(args); i++ {
|
||||||
|
arg := args[i]
|
||||||
|
switch {
|
||||||
|
case arg == "-src":
|
||||||
|
flags = append(flags, arg)
|
||||||
|
case arg == "-o":
|
||||||
|
if i+1 >= len(args) {
|
||||||
|
return nil, fmt.Errorf("missing value for -o")
|
||||||
|
}
|
||||||
|
flags = append(flags, arg, args[i+1])
|
||||||
|
i++
|
||||||
|
case strings.HasPrefix(arg, "-o="):
|
||||||
|
flags = append(flags, arg)
|
||||||
|
case strings.HasPrefix(arg, "-"):
|
||||||
|
flags = append(flags, arg)
|
||||||
|
default:
|
||||||
|
positional = append(positional, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(flags, positional...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func usage() {
|
||||||
|
fmt.Fprintln(os.Stderr, "usage:")
|
||||||
|
fmt.Fprintln(os.Stderr, " gotlinc build [-src] [-o output] <input.gt>")
|
||||||
|
fmt.Fprintln(os.Stderr, " gotlinc run <input.gt> [program args...]")
|
||||||
|
fmt.Fprintln(os.Stderr, "")
|
||||||
|
fmt.Fprintln(os.Stderr, "default command: build")
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fail(err error) {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addBestEffortLineDirectives(goSrc string, inputPath string, gtSrc string) string {
|
||||||
|
fnLines := sourceFunctionLines(gtSrc)
|
||||||
|
if len(fnLines) == 0 {
|
||||||
|
return goSrc
|
||||||
|
}
|
||||||
|
|
||||||
|
pattern := regexp.MustCompile(`^\s*func\s+(?:\(\s*[^)]*\)\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*\(`)
|
||||||
|
lines := strings.Split(goSrc, "\n")
|
||||||
|
out := make([]string, 0, len(lines)+len(fnLines))
|
||||||
|
for _, line := range lines {
|
||||||
|
m := pattern.FindStringSubmatch(line)
|
||||||
|
if len(m) == 2 {
|
||||||
|
if gtLine, ok := fnLines[m[1]]; ok {
|
||||||
|
out = append(out, fmt.Sprintf("//line %s:%d", inputPath, gtLine))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out = append(out, line)
|
||||||
|
}
|
||||||
|
return strings.Join(out, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func sourceFunctionLines(src string) map[string]int {
|
||||||
|
out := map[string]int{}
|
||||||
|
pattern := regexp.MustCompile(`^\s*fun\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(`)
|
||||||
|
lines := strings.Split(src, "\n")
|
||||||
|
for i, line := range lines {
|
||||||
|
m := pattern.FindStringSubmatch(line)
|
||||||
|
if len(m) == 2 {
|
||||||
|
if _, exists := out[m[1]]; !exists {
|
||||||
|
out[m[1]] = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
16
examples/classes.gt
Normal file
16
examples/classes.gt
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
package demo
|
||||||
|
|
||||||
|
interface Greeter {
|
||||||
|
fun greet(name: String): String
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConsoleGreeter(val prefix: String) {
|
||||||
|
fun greet(name: String): String {
|
||||||
|
return prefix + name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val greeter: Greeter = ConsoleGreeter("hello, ")
|
||||||
|
println(greeter.greet("gotlin"))
|
||||||
|
}
|
||||||
10
examples/hello.gt
Normal file
10
examples/hello.gt
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
package examples.hello
|
||||||
|
|
||||||
|
fun greet(name: String): String {
|
||||||
|
return "Hello, " + name
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val message = greet("Go backend")
|
||||||
|
println(message)
|
||||||
|
}
|
||||||
76
examples/http_server.gt
Normal file
76
examples/http_server.gt
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
package examples.http
|
||||||
|
|
||||||
|
import context
|
||||||
|
import fmt
|
||||||
|
import github.com.uptrace.bun
|
||||||
|
import github.com.uptrace.bun.dialect.pgdialect
|
||||||
|
import github.com.uptrace.bun.driver.pgdriver
|
||||||
|
import database.sql
|
||||||
|
import net.http
|
||||||
|
import os
|
||||||
|
|
||||||
|
class BunUser(val Name: String)
|
||||||
|
|
||||||
|
class EpicControllerImpl(val db: *bun.DB) {
|
||||||
|
|
||||||
|
fun hello(w: http.ResponseWriter, r: *http.Request) {
|
||||||
|
fmt.Fprintln(w, "hello from gotlin")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bunHealth(w: http.ResponseWriter, r: *http.Request) {
|
||||||
|
val ctx = context.Background()
|
||||||
|
db.NewSelect().ColumnExpr("1").Scan(ctx)
|
||||||
|
fmt.Fprintln(w, "bun ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bunUsers(w: http.ResponseWriter, r: *http.Request) {
|
||||||
|
val ctx = context.Background()
|
||||||
|
val model = BunUser("gotlin")
|
||||||
|
|
||||||
|
db.NewCreateTable().Model(model).IfNotExists().Exec(ctx)
|
||||||
|
val user = BunUser("user-from-gotlin")
|
||||||
|
db.NewInsert().Model(user).Exec(ctx)
|
||||||
|
val total = db.NewSelect().Model(model).Count(ctx)
|
||||||
|
|
||||||
|
fmt.Fprintln(w, "bun users total:", total)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
worker Counter {
|
||||||
|
val counter = 0
|
||||||
|
|
||||||
|
fun getCount(): Int {
|
||||||
|
return counter
|
||||||
|
}
|
||||||
|
fun increment() {
|
||||||
|
counter += 1
|
||||||
|
println(counter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val postgresDsn = "postgresql://postgres:postgres@localhost/postgres?sslmode=disable"
|
||||||
|
val sqlDb = sql.OpenDB(
|
||||||
|
pgdriver.NewConnector(
|
||||||
|
pgdriver.WithDSN(postgresDsn)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val db = bun.NewDB(sqlDb, pgdialect.New())
|
||||||
|
val counter = Counter()
|
||||||
|
go {
|
||||||
|
while(true) {
|
||||||
|
select {
|
||||||
|
every(1000) -> counter.increment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val epicController = EpicControllerImpl(db)
|
||||||
|
fmt.Println("serving http://localhost:8080")
|
||||||
|
http.HandleFunc("/", epicController.hello)
|
||||||
|
http.HandleFunc("/bun", epicController.bunHealth)
|
||||||
|
http.HandleFunc("/bun/users", epicController.bunUsers)
|
||||||
|
http.HandleFunc("/counter") { w, r ->
|
||||||
|
fmt.Fprintln(w, "bun users total:", counter.getCount())
|
||||||
|
}
|
||||||
|
http.ListenAndServe(":8080", http.DefaultServeMux)
|
||||||
|
}
|
||||||
8
examples/imports.gt
Normal file
8
examples/imports.gt
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
package examples.imports
|
||||||
|
|
||||||
|
import go.strings
|
||||||
|
import go.fmt
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
fmt.Println(strings.ToUpper("gotlinn"))
|
||||||
|
}
|
||||||
9
examples/lambdas.gt
Normal file
9
examples/lambdas.gt
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
package examples.lambdas
|
||||||
|
|
||||||
|
fun apply(value: String, fn: (String) -> Unit) {
|
||||||
|
fn(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
apply("from lambda", { println(it) })
|
||||||
|
}
|
||||||
78
examples/showcase.gt
Normal file
78
examples/showcase.gt
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
package examples.showcase
|
||||||
|
|
||||||
|
import fmt
|
||||||
|
import strings
|
||||||
|
|
||||||
|
interface Greeter {
|
||||||
|
fun greet(name: String): String
|
||||||
|
}
|
||||||
|
|
||||||
|
class PrefixGreeter(val prefix: String): Greeter {
|
||||||
|
fun greet(name: String): String {
|
||||||
|
return prefix + " " + name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
worker Counter {
|
||||||
|
val count = 0
|
||||||
|
|
||||||
|
fun increment() {
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun value(): Int {
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun risky(input: String): String {
|
||||||
|
if (input == "boom") {
|
||||||
|
throw "boom requested"
|
||||||
|
}
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val greeter: Greeter = PrefixGreeter("hello")
|
||||||
|
println(greeter.greet("gotlin"))
|
||||||
|
|
||||||
|
val names = listOf<String>("ada", "linus")
|
||||||
|
val scores = mapOf<String, Int>("ada", 10, "linus", 8)
|
||||||
|
println(names)
|
||||||
|
println(scores)
|
||||||
|
|
||||||
|
val upper = strings.ToUpper("gotlin")
|
||||||
|
fmt.Println("interop:", upper)
|
||||||
|
|
||||||
|
val result = runCatching({ risky("boom") })
|
||||||
|
if (result.isSuccess()) {
|
||||||
|
println("runCatching: success")
|
||||||
|
} else {
|
||||||
|
println("runCatching:")
|
||||||
|
println(result.exceptionOrNull())
|
||||||
|
}
|
||||||
|
|
||||||
|
val maybe: any = null
|
||||||
|
if (maybe == null) {
|
||||||
|
println("null check works")
|
||||||
|
}
|
||||||
|
|
||||||
|
val counter = Counter()
|
||||||
|
counter.increment()
|
||||||
|
counter.increment()
|
||||||
|
fmt.Println("worker value:", counter.value())
|
||||||
|
|
||||||
|
val ready = Channel<String>()
|
||||||
|
go {
|
||||||
|
select {
|
||||||
|
after(120) -> ready.send("timer fired")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
ready -> println("channel says: " + it)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
every(50) -> println("one periodic tick")
|
||||||
|
}
|
||||||
|
}
|
||||||
21
go.mod
Normal file
21
go.mod
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
module gotlin
|
||||||
|
|
||||||
|
go 1.25.6
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/lib/pq v1.11.2 // indirect
|
||||||
|
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
||||||
|
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||||
|
github.com/uptrace/bun v1.2.18 // indirect
|
||||||
|
github.com/uptrace/bun/dialect/pgdialect v1.2.18 // indirect
|
||||||
|
github.com/uptrace/bun/driver/pgdriver v1.2.18 // indirect
|
||||||
|
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||||
|
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.40.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
||||||
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
|
mellium.im/sasl v0.3.2 // indirect
|
||||||
|
)
|
||||||
30
go.sum
Normal file
30
go.sum
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs=
|
||||||
|
github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||||
|
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
||||||
|
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||||
|
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||||
|
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||||
|
github.com/uptrace/bun v1.2.18 h1:3HnRcMfS6OBPMG1eSOzlbFJ/X/AyMEJb7rMxE6VQvDU=
|
||||||
|
github.com/uptrace/bun v1.2.18/go.mod h1:wNltaKJk4JtOt4SG5I5zmA7v0/Mzjh1+/S906Rayd3Y=
|
||||||
|
github.com/uptrace/bun/dialect/pgdialect v1.2.18 h1:IZ6nM2+OYrL8lkEAy7UkSEZvoa3vluTAUlZfPtlRB2k=
|
||||||
|
github.com/uptrace/bun/dialect/pgdialect v1.2.18/go.mod h1:Tqdf4QP1okrGYpXfodXvCOK6Ob1OOTwSaoAzCgBB3IU=
|
||||||
|
github.com/uptrace/bun/driver/pgdriver v1.2.18 h1:Zojuc83ulApocXomBLEcx1DqCZweREafHCjPfyXo88I=
|
||||||
|
github.com/uptrace/bun/driver/pgdriver v1.2.18/go.mod h1:ZRJcARw93nxbQ5WawTrc5EO+F+GygkcYgDLEnT17CcE=
|
||||||
|
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
||||||
|
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
||||||
|
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||||
|
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||||
|
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
||||||
|
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
|
||||||
|
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
||||||
|
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
||||||
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
mellium.im/sasl v0.3.2 h1:PT6Xp7ccn9XaXAnJ03FcEjmAn7kK1x7aoXV6F+Vmrl0=
|
||||||
|
mellium.im/sasl v0.3.2/go.mod h1:NKXDi1zkr+BlMHLQjY3ofYuU4KSPFxknb8mfEu6SveY=
|
||||||
12
hello.go
Normal file
12
hello.go
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func greet(name string) string {
|
||||||
|
return "Hello, " + name
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
message := greet("Go backend")
|
||||||
|
fmt.Println(message)
|
||||||
|
}
|
||||||
10
imports.go
Normal file
10
imports.go
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println(strings.ToUpper("gotlin"))
|
||||||
|
}
|
||||||
235
internal/lang/ast.go
Normal file
235
internal/lang/ast.go
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
package lang
|
||||||
|
|
||||||
|
type Program struct {
|
||||||
|
PackagePath string
|
||||||
|
Imports []ImportDecl
|
||||||
|
Interfaces []InterfaceDecl
|
||||||
|
Classes []ClassDecl
|
||||||
|
Workers []WorkerDecl
|
||||||
|
Functions []FunctionDecl
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImportDecl struct {
|
||||||
|
Alias string
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
type InterfaceDecl struct {
|
||||||
|
Name string
|
||||||
|
Methods []FunctionSignature
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClassDecl struct {
|
||||||
|
Name string
|
||||||
|
Fields []FieldDecl
|
||||||
|
Parents []string
|
||||||
|
Methods []FunctionDecl
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorkerDecl struct {
|
||||||
|
Name string
|
||||||
|
Fields []WorkerFieldDecl
|
||||||
|
Methods []FunctionDecl
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorkerFieldDecl struct {
|
||||||
|
Mutable bool
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
Value Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
type FieldDecl struct {
|
||||||
|
Mutable bool
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
}
|
||||||
|
|
||||||
|
type FunctionSignature struct {
|
||||||
|
Name string
|
||||||
|
Params []Param
|
||||||
|
ReturnType string
|
||||||
|
}
|
||||||
|
|
||||||
|
type FunctionDecl struct {
|
||||||
|
Name string
|
||||||
|
Params []Param
|
||||||
|
ReturnType string
|
||||||
|
Body []Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
type Param struct {
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Stmt interface {
|
||||||
|
stmtNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Expr interface {
|
||||||
|
exprNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
type VarDecl struct {
|
||||||
|
Mutable bool
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
Value Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (VarDecl) stmtNode() {}
|
||||||
|
|
||||||
|
type MultiVarDecl struct {
|
||||||
|
Mutable bool
|
||||||
|
Names []string
|
||||||
|
Value Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MultiVarDecl) stmtNode() {}
|
||||||
|
|
||||||
|
type AssignStmt struct {
|
||||||
|
Name string
|
||||||
|
Value Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (AssignStmt) stmtNode() {}
|
||||||
|
|
||||||
|
type AddAssignStmt struct {
|
||||||
|
Name string
|
||||||
|
Value Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (AddAssignStmt) stmtNode() {}
|
||||||
|
|
||||||
|
type MultiAssignStmt struct {
|
||||||
|
Names []string
|
||||||
|
Value Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MultiAssignStmt) stmtNode() {}
|
||||||
|
|
||||||
|
type ReturnStmt struct {
|
||||||
|
Value Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ReturnStmt) stmtNode() {}
|
||||||
|
|
||||||
|
type ThrowStmt struct {
|
||||||
|
Value Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ThrowStmt) stmtNode() {}
|
||||||
|
|
||||||
|
type GoStmt struct {
|
||||||
|
Value Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (GoStmt) stmtNode() {}
|
||||||
|
|
||||||
|
type ExprStmt struct {
|
||||||
|
Value Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ExprStmt) stmtNode() {}
|
||||||
|
|
||||||
|
type IfStmt struct {
|
||||||
|
Cond Expr
|
||||||
|
Then []Stmt
|
||||||
|
Else []Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (IfStmt) stmtNode() {}
|
||||||
|
|
||||||
|
type WhileStmt struct {
|
||||||
|
Cond Expr
|
||||||
|
Body []Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (WhileStmt) stmtNode() {}
|
||||||
|
|
||||||
|
type TryCatchStmt struct {
|
||||||
|
TryBody []Stmt
|
||||||
|
CatchName string
|
||||||
|
CatchType string
|
||||||
|
CatchBody []Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (TryCatchStmt) stmtNode() {}
|
||||||
|
|
||||||
|
type SelectStmt struct {
|
||||||
|
Cases []SelectCase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (SelectStmt) stmtNode() {}
|
||||||
|
|
||||||
|
type SelectCase struct {
|
||||||
|
Source Expr
|
||||||
|
Body []Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
type IdentExpr struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (IdentExpr) exprNode() {}
|
||||||
|
|
||||||
|
type IntExpr struct {
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (IntExpr) exprNode() {}
|
||||||
|
|
||||||
|
type StringExpr struct {
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (StringExpr) exprNode() {}
|
||||||
|
|
||||||
|
type BoolExpr struct {
|
||||||
|
Value bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (BoolExpr) exprNode() {}
|
||||||
|
|
||||||
|
type NullExpr struct{}
|
||||||
|
|
||||||
|
func (NullExpr) exprNode() {}
|
||||||
|
|
||||||
|
type UnaryExpr struct {
|
||||||
|
Op string
|
||||||
|
Value Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnaryExpr) exprNode() {}
|
||||||
|
|
||||||
|
type BinaryExpr struct {
|
||||||
|
Left Expr
|
||||||
|
Op string
|
||||||
|
Right Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (BinaryExpr) exprNode() {}
|
||||||
|
|
||||||
|
type CallExpr struct {
|
||||||
|
Callee Expr
|
||||||
|
Args []Expr
|
||||||
|
TypeArgs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (CallExpr) exprNode() {}
|
||||||
|
|
||||||
|
type SelectorExpr struct {
|
||||||
|
Receiver Expr
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (SelectorExpr) exprNode() {}
|
||||||
|
|
||||||
|
type LambdaExpr struct {
|
||||||
|
Params []Param
|
||||||
|
ImplicitIt bool
|
||||||
|
Body []Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (LambdaExpr) exprNode() {}
|
||||||
1189
internal/lang/compiler_test.go
Normal file
1189
internal/lang/compiler_test.go
Normal file
File diff suppressed because it is too large
Load diff
2507
internal/lang/generate_go.go
Normal file
2507
internal/lang/generate_go.go
Normal file
File diff suppressed because it is too large
Load diff
167
internal/lang/lexer.go
Normal file
167
internal/lang/lexer.go
Normal file
|
|
@ -0,0 +1,167 @@
|
||||||
|
package lang
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
type lexer struct {
|
||||||
|
src []rune
|
||||||
|
pos int
|
||||||
|
}
|
||||||
|
|
||||||
|
func lex(input string) ([]token, error) {
|
||||||
|
l := &lexer{src: []rune(input)}
|
||||||
|
var tokens []token
|
||||||
|
for {
|
||||||
|
tok, err := l.next()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tokens = append(tokens, tok)
|
||||||
|
if tok.kind == tokenEOF {
|
||||||
|
return tokens, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) next() (token, error) {
|
||||||
|
l.skipWhitespace()
|
||||||
|
start := l.pos
|
||||||
|
if l.pos >= len(l.src) {
|
||||||
|
return token{kind: tokenEOF, pos: start}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := l.src[l.pos]
|
||||||
|
switch {
|
||||||
|
case isIdentStart(ch):
|
||||||
|
l.pos++
|
||||||
|
for l.pos < len(l.src) && isIdentPart(l.src[l.pos]) {
|
||||||
|
l.pos++
|
||||||
|
}
|
||||||
|
lexeme := string(l.src[start:l.pos])
|
||||||
|
if kind, ok := keywords[lexeme]; ok {
|
||||||
|
return token{kind: kind, lexeme: lexeme, pos: start}, nil
|
||||||
|
}
|
||||||
|
return token{kind: tokenIdent, lexeme: lexeme, pos: start}, nil
|
||||||
|
case unicode.IsDigit(ch):
|
||||||
|
l.pos++
|
||||||
|
for l.pos < len(l.src) && unicode.IsDigit(l.src[l.pos]) {
|
||||||
|
l.pos++
|
||||||
|
}
|
||||||
|
return token{kind: tokenInt, lexeme: string(l.src[start:l.pos]), pos: start}, nil
|
||||||
|
case ch == '"':
|
||||||
|
l.pos++
|
||||||
|
for l.pos < len(l.src) && l.src[l.pos] != '"' {
|
||||||
|
if l.src[l.pos] == '\\' {
|
||||||
|
l.pos++
|
||||||
|
if l.pos >= len(l.src) {
|
||||||
|
return token{}, fmt.Errorf("unterminated string at %d", start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.pos++
|
||||||
|
}
|
||||||
|
if l.pos >= len(l.src) {
|
||||||
|
return token{}, fmt.Errorf("unterminated string at %d", start)
|
||||||
|
}
|
||||||
|
l.pos++
|
||||||
|
return token{kind: tokenString, lexeme: string(l.src[start:l.pos]), pos: start}, nil
|
||||||
|
default:
|
||||||
|
l.pos++
|
||||||
|
switch ch {
|
||||||
|
case '(':
|
||||||
|
return token{kind: tokenLParen, lexeme: "(", pos: start}, nil
|
||||||
|
case ')':
|
||||||
|
return token{kind: tokenRParen, lexeme: ")", pos: start}, nil
|
||||||
|
case '{':
|
||||||
|
return token{kind: tokenLBrace, lexeme: "{", pos: start}, nil
|
||||||
|
case '}':
|
||||||
|
return token{kind: tokenRBrace, lexeme: "}", pos: start}, nil
|
||||||
|
case ',':
|
||||||
|
return token{kind: tokenComma, lexeme: ",", pos: start}, nil
|
||||||
|
case '.':
|
||||||
|
return token{kind: tokenDot, lexeme: ".", pos: start}, nil
|
||||||
|
case ':':
|
||||||
|
return token{kind: tokenColon, lexeme: ":", pos: start}, nil
|
||||||
|
case ';':
|
||||||
|
return token{kind: tokenSemicolon, lexeme: ";", pos: start}, nil
|
||||||
|
case '+':
|
||||||
|
if l.match('=') {
|
||||||
|
return token{kind: tokenPlusAssign, lexeme: "+=", pos: start}, nil
|
||||||
|
}
|
||||||
|
return token{kind: tokenPlus, lexeme: "+", pos: start}, nil
|
||||||
|
case '-':
|
||||||
|
if l.match('>') {
|
||||||
|
return token{kind: tokenArrow, lexeme: "->", pos: start}, nil
|
||||||
|
}
|
||||||
|
return token{kind: tokenMinus, lexeme: "-", pos: start}, nil
|
||||||
|
case '*':
|
||||||
|
return token{kind: tokenStar, lexeme: "*", pos: start}, nil
|
||||||
|
case '/':
|
||||||
|
if l.match('/') {
|
||||||
|
for l.pos < len(l.src) && l.src[l.pos] != '\n' {
|
||||||
|
l.pos++
|
||||||
|
}
|
||||||
|
return l.next()
|
||||||
|
}
|
||||||
|
return token{kind: tokenSlash, lexeme: "/", pos: start}, nil
|
||||||
|
case '%':
|
||||||
|
return token{kind: tokenPercent, lexeme: "%", pos: start}, nil
|
||||||
|
case '!':
|
||||||
|
if l.match('=') {
|
||||||
|
return token{kind: tokenNeq, lexeme: "!=", pos: start}, nil
|
||||||
|
}
|
||||||
|
return token{kind: tokenBang, lexeme: "!", pos: start}, nil
|
||||||
|
case '=':
|
||||||
|
if l.match('=') {
|
||||||
|
return token{kind: tokenEq, lexeme: "==", pos: start}, nil
|
||||||
|
}
|
||||||
|
return token{kind: tokenAssign, lexeme: "=", pos: start}, nil
|
||||||
|
case '<':
|
||||||
|
if l.match('=') {
|
||||||
|
return token{kind: tokenLte, lexeme: "<=", pos: start}, nil
|
||||||
|
}
|
||||||
|
return token{kind: tokenLt, lexeme: "<", pos: start}, nil
|
||||||
|
case '>':
|
||||||
|
if l.match('=') {
|
||||||
|
return token{kind: tokenGte, lexeme: ">=", pos: start}, nil
|
||||||
|
}
|
||||||
|
return token{kind: tokenGt, lexeme: ">", pos: start}, nil
|
||||||
|
case '&':
|
||||||
|
if l.match('&') {
|
||||||
|
return token{kind: tokenAnd, lexeme: "&&", pos: start}, nil
|
||||||
|
}
|
||||||
|
case '|':
|
||||||
|
if l.match('|') {
|
||||||
|
return token{kind: tokenOr, lexeme: "||", pos: start}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return token{}, fmt.Errorf("unexpected character %q at %d", ch, start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) match(expected rune) bool {
|
||||||
|
if l.pos >= len(l.src) || l.src[l.pos] != expected {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
l.pos++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) skipWhitespace() {
|
||||||
|
for l.pos < len(l.src) {
|
||||||
|
if unicode.IsSpace(l.src[l.pos]) {
|
||||||
|
l.pos++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isIdentStart(ch rune) bool {
|
||||||
|
return unicode.IsLetter(ch) || ch == '_'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isIdentPart(ch rune) bool {
|
||||||
|
return isIdentStart(ch) || unicode.IsDigit(ch)
|
||||||
|
}
|
||||||
1056
internal/lang/parser.go
Normal file
1056
internal/lang/parser.go
Normal file
File diff suppressed because it is too large
Load diff
86
internal/lang/token.go
Normal file
86
internal/lang/token.go
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
package lang
|
||||||
|
|
||||||
|
type tokenKind string
|
||||||
|
|
||||||
|
const (
|
||||||
|
tokenEOF tokenKind = "EOF"
|
||||||
|
tokenIdent tokenKind = "IDENT"
|
||||||
|
tokenInt tokenKind = "INT"
|
||||||
|
tokenString tokenKind = "STRING"
|
||||||
|
tokenTrue tokenKind = "TRUE"
|
||||||
|
tokenFalse tokenKind = "FALSE"
|
||||||
|
tokenNull tokenKind = "NULL"
|
||||||
|
tokenImport tokenKind = "IMPORT"
|
||||||
|
tokenPackage tokenKind = "PACKAGE"
|
||||||
|
tokenClass tokenKind = "CLASS"
|
||||||
|
tokenWorker tokenKind = "WORKER"
|
||||||
|
tokenInterface tokenKind = "INTERFACE"
|
||||||
|
tokenFun tokenKind = "FUN"
|
||||||
|
tokenOverride tokenKind = "OVERRIDE"
|
||||||
|
tokenVal tokenKind = "VAL"
|
||||||
|
tokenVar tokenKind = "VAR"
|
||||||
|
tokenIf tokenKind = "IF"
|
||||||
|
tokenElse tokenKind = "ELSE"
|
||||||
|
tokenWhile tokenKind = "WHILE"
|
||||||
|
tokenSelect tokenKind = "SELECT"
|
||||||
|
tokenReturn tokenKind = "RETURN"
|
||||||
|
tokenGo tokenKind = "GO"
|
||||||
|
tokenTry tokenKind = "TRY"
|
||||||
|
tokenCatch tokenKind = "CATCH"
|
||||||
|
tokenThrow tokenKind = "THROW"
|
||||||
|
tokenLParen tokenKind = "("
|
||||||
|
tokenRParen tokenKind = ")"
|
||||||
|
tokenLBrace tokenKind = "{"
|
||||||
|
tokenRBrace tokenKind = "}"
|
||||||
|
tokenComma tokenKind = ","
|
||||||
|
tokenDot tokenKind = "."
|
||||||
|
tokenColon tokenKind = ":"
|
||||||
|
tokenSemicolon tokenKind = ";"
|
||||||
|
tokenPlus tokenKind = "+"
|
||||||
|
tokenMinus tokenKind = "-"
|
||||||
|
tokenStar tokenKind = "*"
|
||||||
|
tokenSlash tokenKind = "/"
|
||||||
|
tokenPercent tokenKind = "%"
|
||||||
|
tokenBang tokenKind = "!"
|
||||||
|
tokenAssign tokenKind = "="
|
||||||
|
tokenPlusAssign tokenKind = "+="
|
||||||
|
tokenEq tokenKind = "=="
|
||||||
|
tokenNeq tokenKind = "!="
|
||||||
|
tokenLt tokenKind = "<"
|
||||||
|
tokenLte tokenKind = "<="
|
||||||
|
tokenGt tokenKind = ">"
|
||||||
|
tokenGte tokenKind = ">="
|
||||||
|
tokenAnd tokenKind = "&&"
|
||||||
|
tokenOr tokenKind = "||"
|
||||||
|
tokenArrow tokenKind = "->"
|
||||||
|
)
|
||||||
|
|
||||||
|
var keywords = map[string]tokenKind{
|
||||||
|
"fun": tokenFun,
|
||||||
|
"import": tokenImport,
|
||||||
|
"package": tokenPackage,
|
||||||
|
"class": tokenClass,
|
||||||
|
"worker": tokenWorker,
|
||||||
|
"interface": tokenInterface,
|
||||||
|
"val": tokenVal,
|
||||||
|
"var": tokenVar,
|
||||||
|
"override": tokenOverride,
|
||||||
|
"if": tokenIf,
|
||||||
|
"else": tokenElse,
|
||||||
|
"while": tokenWhile,
|
||||||
|
"select": tokenSelect,
|
||||||
|
"return": tokenReturn,
|
||||||
|
"go": tokenGo,
|
||||||
|
"try": tokenTry,
|
||||||
|
"catch": tokenCatch,
|
||||||
|
"throw": tokenThrow,
|
||||||
|
"true": tokenTrue,
|
||||||
|
"false": tokenFalse,
|
||||||
|
"null": tokenNull,
|
||||||
|
}
|
||||||
|
|
||||||
|
type token struct {
|
||||||
|
kind tokenKind
|
||||||
|
lexeme string
|
||||||
|
pos int
|
||||||
|
}
|
||||||
58
tools/vscode-gotlin/README.md
Normal file
58
tools/vscode-gotlin/README.md
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
# vscode-gotlin
|
||||||
|
|
||||||
|
Minimal VS Code extension for `.gt` files.
|
||||||
|
|
||||||
|
It does two things:
|
||||||
|
|
||||||
|
- registers `.gt` as the `gotlin` language
|
||||||
|
- launches `gotlin-lsp` over stdio
|
||||||
|
|
||||||
|
It also includes:
|
||||||
|
|
||||||
|
- syntax highlighting
|
||||||
|
- bracket/comment configuration
|
||||||
|
- basic Gotlin snippets
|
||||||
|
- optional `gopls` bridge for hover/definition on Go-imported symbols
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
From this folder:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Then in VS Code:
|
||||||
|
|
||||||
|
1. Open this folder as an extension project.
|
||||||
|
2. Press `F5` to launch an Extension Development Host.
|
||||||
|
3. Open your Gotlin workspace in that host.
|
||||||
|
|
||||||
|
## Server path
|
||||||
|
|
||||||
|
By default the extension looks for:
|
||||||
|
|
||||||
|
```text
|
||||||
|
<workspace>/bin/gotlin-lsp
|
||||||
|
```
|
||||||
|
|
||||||
|
If your binary lives somewhere else, set:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"gotlin.serverPath": "/absolute/path/to/gotlin-lsp"
|
||||||
|
```
|
||||||
|
|
||||||
|
If `gopls` is not on your PATH, also set:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"gotlin.goplsPath": "/absolute/path/to/gopls"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build the language server
|
||||||
|
|
||||||
|
From the repo root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build -o ./bin/gotlin-lsp ./cmd/gotlin-lsp
|
||||||
|
```
|
||||||
56
tools/vscode-gotlin/language-configuration.json
Normal file
56
tools/vscode-gotlin/language-configuration.json
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
{
|
||||||
|
"comments": {
|
||||||
|
"lineComment": "//"
|
||||||
|
},
|
||||||
|
"wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\$\\%\\^\\&\\*\\(\\)\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\\"\\'\\,\\.\\<\\>\\/\\?\\s]+)",
|
||||||
|
"brackets": [
|
||||||
|
[
|
||||||
|
"{",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"(",
|
||||||
|
")"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"autoClosingPairs": [
|
||||||
|
{
|
||||||
|
"open": "{",
|
||||||
|
"close": "}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"open": "(",
|
||||||
|
"close": ")"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"open": "\"",
|
||||||
|
"close": "\""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"surroundingPairs": [
|
||||||
|
[
|
||||||
|
"{",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"(",
|
||||||
|
")"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"\"",
|
||||||
|
"\""
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"indentationRules": {
|
||||||
|
"increaseIndentPattern": "^.*\\{\\s*$",
|
||||||
|
"decreaseIndentPattern": "^\\s*\\}"
|
||||||
|
},
|
||||||
|
"onEnterRules": [
|
||||||
|
{
|
||||||
|
"beforeText": "^.*\\{\\s*$",
|
||||||
|
"action": {
|
||||||
|
"indent": "indent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
140
tools/vscode-gotlin/package-lock.json
generated
Normal file
140
tools/vscode-gotlin/package-lock.json
generated
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
{
|
||||||
|
"name": "gotlin-vscode",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "gotlin-vscode",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"dependencies": {
|
||||||
|
"vscode-languageclient": "^9.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.14.10",
|
||||||
|
"@types/vscode": "^1.90.0",
|
||||||
|
"typescript": "^5.5.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"vscode": "^1.90.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "20.19.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.35.tgz",
|
||||||
|
"integrity": "sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~6.21.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/vscode": {
|
||||||
|
"version": "1.109.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.109.0.tgz",
|
||||||
|
"integrity": "sha512-0Pf95rnwEIwDbmXGC08r0B4TQhAbsHQ5UyTIgVgoieDe4cOnf92usuR5dEczb6bTKEp7ziZH4TV1TRGPPCExtw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/balanced-match": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/brace-expansion": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"balanced-match": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/minimatch": {
|
||||||
|
"version": "5.1.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
|
||||||
|
"integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/semver": {
|
||||||
|
"version": "7.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||||
|
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "6.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||||
|
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/vscode-jsonrpc": {
|
||||||
|
"version": "8.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz",
|
||||||
|
"integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vscode-languageclient": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"minimatch": "^5.1.0",
|
||||||
|
"semver": "^7.3.7",
|
||||||
|
"vscode-languageserver-protocol": "3.17.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"vscode": "^1.82.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vscode-languageserver-protocol": {
|
||||||
|
"version": "3.17.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz",
|
||||||
|
"integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"vscode-jsonrpc": "8.2.0",
|
||||||
|
"vscode-languageserver-types": "3.17.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vscode-languageserver-types": {
|
||||||
|
"version": "3.17.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz",
|
||||||
|
"integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==",
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
73
tools/vscode-gotlin/package.json
Normal file
73
tools/vscode-gotlin/package.json
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
{
|
||||||
|
"name": "gotlin-vscode",
|
||||||
|
"displayName": "Gotlin",
|
||||||
|
"description": "VS Code support for Gotlin (.gt) files",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"publisher": "local",
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"engines": {
|
||||||
|
"vscode": "^1.90.0"
|
||||||
|
},
|
||||||
|
"categories": [
|
||||||
|
"Programming Languages"
|
||||||
|
],
|
||||||
|
"activationEvents": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"main": "./out/extension.js",
|
||||||
|
"contributes": {
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"id": "gotlin",
|
||||||
|
"aliases": [
|
||||||
|
"Gotlin",
|
||||||
|
"gotlin"
|
||||||
|
],
|
||||||
|
"extensions": [
|
||||||
|
".gt"
|
||||||
|
],
|
||||||
|
"configuration": "./language-configuration.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"snippets": [
|
||||||
|
{
|
||||||
|
"language": "gotlin",
|
||||||
|
"path": "./snippets/gotlin.code-snippets"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grammars": [
|
||||||
|
{
|
||||||
|
"language": "gotlin",
|
||||||
|
"scopeName": "source.gotlin",
|
||||||
|
"path": "./syntaxes/gotlin.tmLanguage.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configuration": {
|
||||||
|
"title": "Gotlin",
|
||||||
|
"properties": {
|
||||||
|
"gotlin.serverPath": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "Absolute path to the gotlin-lsp binary. If empty, the extension tries <workspace>/bin/gotlin-lsp first, then 'gotlin-lsp' on PATH."
|
||||||
|
},
|
||||||
|
"gotlin.goplsPath": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "Absolute path to gopls. If empty, gotlin-lsp tries 'gopls' on PATH."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc -p .",
|
||||||
|
"watch": "tsc -w -p ."
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"vscode-languageclient": "^9.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.14.10",
|
||||||
|
"@types/vscode": "^1.90.0",
|
||||||
|
"typescript": "^5.5.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
59
tools/vscode-gotlin/snippets/gotlin.code-snippets
Normal file
59
tools/vscode-gotlin/snippets/gotlin.code-snippets
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
{
|
||||||
|
"Function": {
|
||||||
|
"prefix": "fun",
|
||||||
|
"body": [
|
||||||
|
"fun ${1:name}(${2}): ${3:Unit} {",
|
||||||
|
" $0",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"description": "Gotlin function"
|
||||||
|
},
|
||||||
|
"Main Function": {
|
||||||
|
"prefix": "main",
|
||||||
|
"body": [
|
||||||
|
"fun main() {",
|
||||||
|
" $0",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"description": "Gotlin main function"
|
||||||
|
},
|
||||||
|
"Package": {
|
||||||
|
"prefix": "package",
|
||||||
|
"body": [
|
||||||
|
"package ${1:demo}"
|
||||||
|
],
|
||||||
|
"description": "Gotlin package declaration"
|
||||||
|
},
|
||||||
|
"Go Import": {
|
||||||
|
"prefix": "importgo",
|
||||||
|
"body": [
|
||||||
|
"import go.${1:fmt}"
|
||||||
|
],
|
||||||
|
"description": "Import a Go package"
|
||||||
|
},
|
||||||
|
"If": {
|
||||||
|
"prefix": "if",
|
||||||
|
"body": [
|
||||||
|
"if (${1:condition}) {",
|
||||||
|
" $0",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"description": "If statement"
|
||||||
|
},
|
||||||
|
"Lambda": {
|
||||||
|
"prefix": "lambda",
|
||||||
|
"body": [
|
||||||
|
"{ ${1:it} -> $0 }"
|
||||||
|
],
|
||||||
|
"description": "Lambda expression"
|
||||||
|
},
|
||||||
|
"Override Method": {
|
||||||
|
"prefix": "override",
|
||||||
|
"body": [
|
||||||
|
"override fun ${1:name}(${2}): ${3:Unit} {",
|
||||||
|
" $0",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"description": "Override a class or interface method"
|
||||||
|
}
|
||||||
|
}
|
||||||
86
tools/vscode-gotlin/src/extension.ts
Normal file
86
tools/vscode-gotlin/src/extension.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
import * as fs from "node:fs";
|
||||||
|
import * as path from "node:path";
|
||||||
|
import * as vscode from "vscode";
|
||||||
|
import {
|
||||||
|
LanguageClient,
|
||||||
|
LanguageClientOptions,
|
||||||
|
ServerOptions
|
||||||
|
} from "vscode-languageclient/node";
|
||||||
|
|
||||||
|
let client: LanguageClient | undefined;
|
||||||
|
|
||||||
|
export async function activate(context: vscode.ExtensionContext): Promise<void> {
|
||||||
|
const serverPath = resolveServerPath();
|
||||||
|
if (!serverPath) {
|
||||||
|
void vscode.window.showErrorMessage(
|
||||||
|
"Gotlin LSP binary was not found. Build ./bin/gotlin-lsp or set gotlin.serverPath."
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const serverOptions: ServerOptions = {
|
||||||
|
command: serverPath,
|
||||||
|
args: [],
|
||||||
|
options: {
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
GOTLIN_GOPLS_PATH: resolveGoplsPath()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const clientOptions: LanguageClientOptions = {
|
||||||
|
documentSelector: [{ scheme: "file", language: "gotlin" }],
|
||||||
|
outputChannelName: "Gotlin Language Server"
|
||||||
|
};
|
||||||
|
|
||||||
|
client = new LanguageClient(
|
||||||
|
"gotlin-lsp",
|
||||||
|
"Gotlin Language Server",
|
||||||
|
serverOptions,
|
||||||
|
clientOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
await client.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deactivate(): Promise<void> {
|
||||||
|
if (client) {
|
||||||
|
await client.stop();
|
||||||
|
client = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveServerPath(): string | undefined {
|
||||||
|
const configured = vscode.workspace
|
||||||
|
.getConfiguration("gotlin")
|
||||||
|
.get<string>("serverPath", "")
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
if (configured) {
|
||||||
|
return configured;
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
||||||
|
if (workspaceFolder) {
|
||||||
|
const candidate = path.join(workspaceFolder.uri.fsPath, "bin", platformBinaryName("gotlin-lsp"));
|
||||||
|
if (fs.existsSync(candidate)) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "gotlin-lsp";
|
||||||
|
}
|
||||||
|
|
||||||
|
function platformBinaryName(base: string): string {
|
||||||
|
return process.platform === "win32" ? `${base}.exe` : base;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveGoplsPath(): string {
|
||||||
|
const configured = vscode.workspace
|
||||||
|
.getConfiguration("gotlin")
|
||||||
|
.get<string>("goplsPath", "")
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
return configured;
|
||||||
|
}
|
||||||
203
tools/vscode-gotlin/syntaxes/gotlin.tmLanguage.json
Normal file
203
tools/vscode-gotlin/syntaxes/gotlin.tmLanguage.json
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
|
||||||
|
"name": "Gotlin",
|
||||||
|
"scopeName": "source.gotlin",
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"include": "#comments"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#imports"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#package"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#functions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#typesDecl"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#literals"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#keywords"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#types"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#strings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#numbers"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#operators"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"comments": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "comment.line.double-slash.gotlin",
|
||||||
|
"match": "//.*$"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"imports": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "meta.import.gotlin",
|
||||||
|
"match": "\\b(import)\\b\\s+((?:[A-Za-z_][\\w-]*\\.)*[A-Za-z_][\\w-]*)(?:\\s+((?:[A-Za-z_][\\w-]*\\.)*[A-Za-z_][\\w-]*))?",
|
||||||
|
"captures": {
|
||||||
|
"1": {
|
||||||
|
"name": "keyword.control.import.gotlin"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"name": "meta.path.gotlin"
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
"name": "meta.path.gotlin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"package": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "meta.package.gotlin",
|
||||||
|
"match": "\\b(package)\\b\\s+((?:[A-Za-z_][\\w-]*\\.)*[A-Za-z_][\\w-]*)",
|
||||||
|
"captures": {
|
||||||
|
"1": {
|
||||||
|
"name": "keyword.control.package.gotlin"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"name": "meta.path.gotlin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"functions": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "meta.function.gotlin",
|
||||||
|
"match": "\\b(?:(override)\\s+)?(fun)\\b\\s+([A-Za-z_][A-Za-z0-9_]*)",
|
||||||
|
"captures": {
|
||||||
|
"1": {
|
||||||
|
"name": "storage.modifier.gotlin"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"name": "keyword.control.function.gotlin"
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
"name": "entity.name.function.gotlin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"typesDecl": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "meta.interface.gotlin",
|
||||||
|
"match": "\\b(interface)\\b\\s+([A-Za-z_][A-Za-z0-9_]*)",
|
||||||
|
"captures": {
|
||||||
|
"1": {
|
||||||
|
"name": "storage.type.interface.gotlin"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"name": "entity.name.type.interface.gotlin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "meta.class.gotlin",
|
||||||
|
"match": "\\b(class)\\b\\s+([A-Za-z_][A-Za-z0-9_]*)",
|
||||||
|
"captures": {
|
||||||
|
"1": {
|
||||||
|
"name": "storage.type.class.gotlin"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"name": "entity.name.type.class.gotlin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"literals": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "constant.language.boolean.gotlin",
|
||||||
|
"match": "\\b(true|false)\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "constant.language.null.gotlin",
|
||||||
|
"match": "\\bnull\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "variable.language.this.gotlin",
|
||||||
|
"match": "\\bthis\\b"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"keywords": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "keyword.control.gotlin",
|
||||||
|
"match": "\\b(fun|val|var|override|if|else|while|return|try|catch|throw)\\b"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"types": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "storage.type.gotlin",
|
||||||
|
"match": "\\b(Int|String|Boolean|Unit)\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "support.type.gotlin",
|
||||||
|
"match": "\\b[A-Za-z_][A-Za-z0-9_]*(?:\\.[A-Za-z_][A-Za-z0-9_]*)+\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "entity.name.type.gotlin",
|
||||||
|
"match": "\\b[A-Z][A-Za-z0-9_]*\\b"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"strings": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "string.quoted.double.gotlin",
|
||||||
|
"begin": "\"",
|
||||||
|
"end": "\"",
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "constant.character.escape.gotlin",
|
||||||
|
"match": "\\\\."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"numbers": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "constant.numeric.gotlin",
|
||||||
|
"match": "\\b\\d+\\b"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"operators": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "keyword.operator.gotlin",
|
||||||
|
"match": "->|==|!=|<=|>=|&&|\\|\\||[=+\\-*/%<>!:.,]"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
tools/vscode-gotlin/tsconfig.json
Normal file
18
tools/vscode-gotlin/tsconfig.json
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "ES2020",
|
||||||
|
"lib": [
|
||||||
|
"ES2020"
|
||||||
|
],
|
||||||
|
"outDir": "out",
|
||||||
|
"rootDir": "src",
|
||||||
|
"strict": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue